LaxZonedDateTimes
Provides LaxZonedDateTime, an alternative to TimeZones.jl's ZonedDateTime that does not raise exceptions when a time that is ambiguous or doesn't exist is encountered.
Examples
julia> using LaxZonedDateTimes, TimeZones
julia> winnipeg = TimeZone("America/Winnipeg")
America/Winnipeg (UTC-6/UTC-5)
julia> LaxZonedDateTime(DateTime(2016), winnipeg)
2016-01-01T00:00:00-06:00
julia> LaxZonedDateTime(DateTime(2016, 3, 13, 2, 45), winnipeg)
2016-03-13T02:45:00-DNE
julia> LaxZonedDateTime(DateTime(2016, 11, 6, 1, 45), winnipeg)
2016-11-06T01:45:00-AMBOne of the advantages of using LaxZonedDateTimes is that when you encounter a time that is ambiguous or doesn't exist, you don't lose the information. For example, consider the following case:
julia> lzdt = LaxZonedDateTime(DateTime(2016, 3, 12, 2), winnipeg)
2016-03-11T02:00:00-06:00
julia> nonexistent = lzdt + Dates.Day(1)
2016-03-13T02:00:00-DNE
julia> nonexistent + Dates.Day(1)
2016-03-14T02:00:00-05:00In some cases, however, it's difficult to determine what the result should be. While arithmetic with DatePeriods will always work, attempting to add or subtract a TimePeriod value from an ambiguous or nonexistent LaxZonedDateTime will result in an unrepresentable value (which is an unrecoverable state):
julia> lzdt = LaxZonedDateTime(DateTime(2016, 3, 13, 2), winnipeg)
2016-03-13T02:00:00-DNE
julia> lzdt + Dates.Hour(2)
INVALIDYou can test a LaxZonedDateTime for validity using isnonexistent, isambiguous, and isvalid (the last of which returns false if the value is nonexistent, ambiguous, or unrepresentable).
Ranges
julia> lzdt = LaxZonedDateTime(DateTime(2016, 3, 11, 2), winnipeg)
2016-03-11T02:00:00-06:00
julia> r = lzdt:Dates.Day(1):(lzdt + Dates.Day(5))
2016-03-16T02:00:00-05:002016-03-11T02:00:00-06:00
julia> collect(r)
6-element Array{LaxZonedDateTimes.LaxZonedDateTime,1}:
2016-03-11T02:00:00-06:00
2016-03-12T02:00:00-06:00
2016-03-13T02:00:00-DNE
2016-03-14T02:00:00-05:00
2016-03-15T02:00:00-05:00
2016-03-16T02:00:00-05:00Notice that the third element represents a nonexistent time (a time that has no UTC representation).
Note that ambiguous and nonexistent values only occur in places where a ZonedDateTime would raise an exception. Here, we step right past the nonexistent time:
julia> lzdt = LaxZonedDateTime(DateTime(2016, 3, 13), TimeZone("America/Winnipeg"))
2016-03-13T00:00:00-06:00
julia> r = lzdt:Dates.Hour(1):(lzdt + Dates.Hour(5))
2016-03-16T02:00:00-05:002016-03-11T02:00:00-06:00
julia> collect(r)
6-element Array{LaxZonedDateTimes.LaxZonedDateTime,1}:
2016-03-13T00:00:00-06:00
2016-03-13T01:00:00-06:00
2016-03-13T03:00:00-05:00
2016-03-13T04:00:00-05:00
2016-03-13T05:00:00-05:00
2016-03-13T06:00:00-05:00Ranges should generally work as expected, but here are the rules for some of the edge cases that you might encounter:
- If start and/or finish is unrepresentable, the range collects to nothing
- If start is AMB/DNE, and step is a
DatePeriod, it works (first element will be DNE/AMB) - If start is AMB/DNE, and step is a
TimePeriod, the range collects to nothing - If finish is AMB/DNE, and step is a
DatePeriod, it works (last element may be DNE/AMB) - If finish is AMB/DNE, and step is a
TimePeriod, it works (last element omitted for DNE, both versions included for AMB)
(The last two descriptions above assume that step divides evenly into the range. If it doesn't, then the last element won't actually hit the AMB/DNE value.)
When transitions occur between the start and end of a range, they are skipped over as per the TimeZones.jl implementation (e.g., when stepping one hour at a time, a "spring forward" will result in the range collecting to 0:00, 1:00, 3:00, ...). A DNE LaxZonedDateTime will only appear in cases where TimeZones.jl would throw an error (e.g., stepping through the "spring forward" transition one day at a time, and landing on the missing hour).