crystal-lang / crystal

The Crystal Programming Language
https://crystal-lang.org
Apache License 2.0
19.32k stars 1.61k forks source link

Time::Span serialization based on RFC standards #11942

Open samueleaton opened 2 years ago

samueleaton commented 2 years ago

Feature Request

Related to #4446, opened and closed in 2017, reopening for discussion. I propose adding serialization (JSON/YAML) support for Time::Span.

RFC 3339 and RFC 5545 both outline representations for date/time durations (spans).

These are even referenced in the application/schema+json RFC spec.

Some examples are given in the RFCs:

Example:  A duration of 15 days, 5 hours, and 20 seconds would be:

  P15DT5H0M20S

A duration of 7 weeks would be:

  P7W

Comparable

Golang has support for marshaling/unmarshaling time spans which was added 10 years ago, but its not based on an RFC that I'm aware of.

The example in Go:

hours, _ := time.ParseDuration("10h")
complex, _ := time.ParseDuration("1h10m10s")
micro, _ := time.ParseDuration("1µs")
// The package also accepts the incorrect but common prefix u for micro.
micro2, _ := time.ParseDuration("1us")

Loose Tolerances

Although the specs start with a P, it might be nice to somehow make that optional, or define a format flag so that parsing 3H is interpreted as a 3 hour span. Time Spans are fairly common in yaml configs so would be nice to have built-in support.

straight-shoota commented 2 years ago

The governing standard is actually ISO 8601 which defines the representation of a time duration. Time::Span falls under 5.5.1 b): "A time-interval shall be expressed [...] by a duration not associated with any start or end;"

Note that Time::Span uses seconds (and fractions of a second) as base unit. All units are expressed as multiples of a second, reflecting their standard duration. It does not have a conceptual representation of higher calendrical units. Thus, a duration of 1d can have unexpected behaviour when it's applied around a DST switch (ref #6522) and the format cannot support year and month units (as per RFC 5545).

But I think this should work well for Time::Span as long as the limitations are known.

Regarding the implementation, there should probably be generic serialization and deserialization methods for the ISO 8601 format, similar to Time.parse and Time#format. The json and yaml methods can just make use of that.