JuliaTime / TimeZones.jl

IANA time zone database access for the Julia programming language
Other
86 stars 52 forks source link

Parameterize `ZonedDateTime` by `TimeZone` instance #318

Open omus opened 3 years ago

omus commented 3 years ago

Allows for enforcing that a Vector contains ZonedDateTimes all using the same TimeZone. This idea has been floated around before so I made this issue to centralize the conversation.

omus commented 3 years ago

Requested in: https://github.com/JuliaData/Arrow.jl/pull/50#issue-509313802

oxinabox commented 3 years ago

For it to be parametric on the value of the the timezone (ratehr than the type of the timezone) would require that the TimeZone type isbits. Which is #271

omus commented 3 years ago

For it to be parametric on the value of the the timezone (ratehr than the type of the timezone) would require that the TimeZone type isbits. Which is #271

So this issue is referring to having something like ZonedDateTime{tz"America/Winnipeg"}. Effectively this would mean having a separate type for each time zone making each time zone a singleton. The advantage here is you can use type constraints to enforce all elements of an array to be the same time zone and excise storing that information in the struct. The downside however additional overhead on Julia's type system with introducing 300+ types; complications with custom time zones and possibly serialization

omus commented 3 years ago

An idea I'm experimenting with is using an intermediate type for the time zone. The advantage of such an approach is that it allows us to use a time zone as a type parameter without requiring the type storing the time zone information to have to be isbits which can be quite challenging.

Essentially this could work like:

using Dates: DateTime
using TimeZones: TimeZones, TimeZone, @tz_str

struct TZ{name} end
TZ{name}() where {name} = TimeZone(String(name))

macro TZ_str(name)
    :(TZ{$(Expr(:quote, Symbol(name)))})
end

struct ZonedDateTime{TZ}
    utc_instant::DateTime
    # zone::FixedTimeZone
end

function ZonedDateTime(dt::DateTime, tz::TimeZone)
    tz_type = TZ{Symbol(tz.name)}
    ZonedDateTime{tz_type}(dt)
end
TimeZones.timezone(::ZonedDateTime{TZ}) where TZ = TZ()
julia> ZonedDateTime{TZ"America/Winnipeg"}(DateTime(2021))
ZonedDateTime{TZ{Symbol("America/Winnipeg")}}(DateTime("2021-01-01T00:00:00"))

julia> ZonedDateTime(DateTime(2021), tz"America/Winnipeg")
ZonedDateTime{TZ{Symbol("America/Winnipeg")}}(DateTime("2021-01-01T00:00:00"))

There's even some room for optimization with this approach as TZ{name}() where {name} can be optimized with specialized methods such as TZ{Symbol("...")} = tz"...":

using TimeZones

struct TZ1{name} end
struct TZ2{name} end

macro TZ1_str(name)
    :(TZ1{$(Expr(:quote, Symbol(name)))})
end
macro TZ2_str(name)
    :(TZ2{$(Expr(:quote, Symbol(name)))})
end

TZ1{name}() where {name} = TimeZone(String(name))
TZ2"America/Winnipeg"() = tz"America/Winnipeg"
julia> @btime TZ1"America/Winnipeg"()
  84.699 ns (3 allocations: 112 bytes)
America/Winnipeg (UTC-6/UTC-5)

julia> @btime TZ2"America/Winnipeg"()
  1.563 ns (0 allocations: 0 bytes)
America/Winnipeg (UTC-6/UTC-5)

One thing I'm not loving about this approach is how the type is displayed but overall that's pretty minor. I need to do some additional experimentation yet.

omus commented 3 years ago

Trying to get some activity on custom type printing: https://github.com/JuliaLang/julia/issues/24195#issuecomment-901385487