Time: honor Daylight Saving and ENV["TZ"]#2481
Conversation
|
|
||
| offset1.should_not eq(offset2) | ||
| ensure | ||
| ENV["TZ"] = "" |
There was a problem hiding this comment.
Shouldn't TZ be restored to the previous value instead of set to empty string?
There was a problem hiding this comment.
You are right, I'll fix it.
|
Maybe out of scope of this, but should |
0386fb6 to
b6620ab
Compare
|
@jhass Ruby can do that because each Time carries a timezone. In Crystal (like in C#) a Time only knows if it's local or UTC (or that info is unknown). To be honest, I don't really like that. I think we can make it so that each Time carries around timezone info. In the future we thought about adding another type for this, TimeWithZone, but it will make things harder to work with, I think. For example in Go a Time carries that info, so I don't see why we can't do that too. Time will be a bit bigger, probably not the size of an @waj what do you think? |
|
To add to the above comment, if I run this in Ruby: ENV["TZ"] = "Europe/Berlin"
t1 = Time.now
puts t1
ENV["TZ"] = "America/New_York"
t2 = Time.now
puts t2
puts t1
puts t2I get: while in Crystal I get: So the result is actually incorrect in Crystal if In any case, I'd like By the way, if I run this in Ruby: ENV["TZ"] = "Europe/Berlin"
t1 = Time.now
ENV["TZ"] = "America/New_York"
t2 = Time.now
puts t1
puts t2I get: Maybe a bug in Ruby? |
|
Yes, let's have |
|
The needs for Yet properly handling it would be better I think. |
|
I don't think we'll add methods to change a time's properties, |
|
Btw |
|
From my experiments and readings:
I'm not sure we need to retain a timezone on Time. Wouldn't retaining the offset be enough? Along with a |
|
Also: just changing the |
| LibC.localtime_r(pointerof(sec), out tm) | ||
|
|
||
| tz = -LibC.timezone.to_i64 / 60 | ||
| tz += 60 if tm.tm_isdst != 0 |
There was a problem hiding this comment.
Bold assumption. Those 2 lines can be replaced with offset = tm.tm_gmtoff. Ideally:
ifdef darwin
ret = LibC.gettimeofday(out timeval, nil)
raise Errno.new("gettimeofday") unless ret == 0
tv_sec, tv_usec = timeval.tv_sec, timeval.tv_usec
else
ret = LibC.clock_gettime(LibC::CLOCK_REALTIME, out timespec)
raise Errno.new("clock_gettime") unless ret == 0
tv_sec, tv_usec = timespec.tv_sec, timespec.tv_nsec / 1_000
end
ret = LibC.localtime_r(pointerof(tv_sec), out tm)
raise Errno.new("localtime_r") unless ret == 0
offset = tm.tm_gmtoff / 60
ticks = tv_sec.to_i64 * Span::TicksPerSecond + tv_usec.to_i64 * 10_i64 + UnixEpoch
yield ticks, offset * Span::TicksPerMinuteThere was a problem hiding this comment.
BTW: what about using offset instead of tz, in order to reflect that offset is tz + dst?
There was a problem hiding this comment.
Also we may:
if LibC.daylight == 0
# current TZ doesn't have any DST, neither in past, present or future
offset = LibC.timezone / 60
else
# current TZ may have DST, either in past, present or future
ret = LibC.localtime_r(pointerof(tv_sec), out tm)
raise Errno.new("localtime_r") unless ret == 0
offset = tm.tm_gmtoff / 60
endb6620ab to
5e20af6
Compare
|
@ysbaddaden I applied all your suggestions and they worked like a charm! ❤️🌟 Well, except |
|
@ysbaddaden Nevermind, seems to be |
5e20af6 to
05943ac
Compare
| compute_ticks do |t, offset| | ||
| ticks - offset | ||
| end | ||
| end |
There was a problem hiding this comment.
I don't follow the difference between this one and utc_ticks above, and why they're implemented differently.
05943ac to
4c74bd0
Compare
4c74bd0 to
59713f9
Compare
|
I did some more refactors, and only use |
|
I agree this is enough for now. For future:
What about we merely retain the offset value as If you agree with that, I'll try to push a PR. |
|
@ysbaddaden I agree, but I'll have to discuss this with @waj. I remember he told me, because that's how it's also done in C#, that in most cases you don't the timezone of a Time so it's much more lightweight if it's just represented as an UInt64... though probably nowadays it doesn't add much overhead if we carry more information (nanoseconds precision and timezone/offset info). |
With this,
Time.nowbehaves exactly like in Ruby, andENV["TZ"]is honored.Running this script:
produces the same output in Ruby and Crystal, so I guess this implementation is right.
It turned out that
gettimeofdaydoesn't set the timezone and doesn't honorENV["TZ"], or so it seems. Checking Ruby's source code I see they use other C functions. One of them islocaltime_r, and that does set the daylight saving in thetmstruct. I guess if it'stm_isdstis not zero then the change is by one hour, I don't know if in other places DST changes time in a different way.It would be awesome if you can test this change in your timezone.
I added a spec to check that the timezone does change when changing
ENV["TZ"], though testing that dst is picked up correctly is probably harder.