Time zone offset change on the date of Day light saving time when calling since method #8076

Closed
drchanimal opened this Issue Oct 30, 2012 · 15 comments

Projects

None yet

5 participants

@drchanimal

I tested this in Rails 3.2.3 and also 2.3.14 on a Mac.

ruby-1.9.2-p180 :009 > Time.now.since(4.day)
=> 2012-11-03 14:38:11 -0400
ruby-1.9.2-p180 :010 > Time.now.since(5.day)
=> 2012-11-04 14:38:15 -0500

See on Nov 4, the timezone offset is -0500. When calling Time.now.since(4.day) which is Nov 3, the timezone offset is -0400 which is fine because my Mac is EST time zone. It seems that the since method is not doing calculation correctly.

@al2o3cr

What's the behavior you were expecting? Under the hood, since is implemented using advance, which explicitly moves forward/back by days (in this case) such that the times are constant. For instance, imagine telling somebody "be back here at this same time in 4 days".

If you actually want "add groups of exactly 24 hours", do something like this:

Time.now.since(24.hours * 4)
@drchanimal

I am expecting time zone offset won't change. If I am on EST time zone, moving forward should not change Time Zone offset. The problem is that it changed from 0400 to 0500 for the time zone offset. It is like saying that because we move forward 4 days and due to daylight saving time, we are now in a different time zone. It doesn't make sense. It will make sense to show 2012-11-12 13:38:15 -0400 if you want to account for daylight saving time.

@kytrinyx

This seems to be the expected behavior. Can this be closed? /cc @steveklabnik, @jeremy

@steveklabnik
Ruby on Rails member

I'm not sure what the expected behavior is; it's weird that it would change time zones.

@drchanimal

Ok, so it seems that once it is daylight saving time, time zone offset for EST is now -0500. Before daylight saying time change, it is -0400. That makes sense. However, then the expected behavior in my use case should then be:

Time.now.since(5.day) => 2012-11-04 13:38:15 -0500 when Time.now is 2012-10-30 14:38:15 -0400

Instead it is showing 2012-11-04 14:38:15 -0500. If the time zone offset change, the time should change. In this case, the time doesn't change and the time zone offset is change. That is the reason that it doesn't seem right.

@pixeltrix
Ruby on Rails member

Works as expected for me:

>> t = 5.weeks.ago
=> Mon, 22 Oct 2012 16:29:40 BST +01:00
>> t.since(14.days)
=> Mon, 05 Nov 2012 16:29:40 GMT +00:00
>> t = 5.weeks.ago.in_time_zone('Eastern Time (US & Canada)')
=> Mon, 22 Oct 2012 11:29:16 EDT -04:00
>> t.since(14.days)
=> Mon, 05 Nov 2012 11:29:16 EST -05:00

@drchanimal you need to use Time.current not Time.now which returns a local Time not a TimeWithZone.

@steveklabnik yes, you are changing time zones - from EDT to EST. EST exists throughout the year but EDT only exists during the summer months. If you force the timezone to be EST during the summer then the timezone doesn't change, e.g:

>> t = 5.weeks.ago.in_time_zone('EST')
=> Mon, 22 Oct 2012 10:38:12 EST -05:00
>> t.since(14.days)
=> Mon, 05 Nov 2012 10:38:12 EST -05:00
@pixeltrix pixeltrix closed this Nov 26, 2012
@drchanimal

I still don't understand how the two cases you mentioned above are the same:
case one:

t = 5.weeks.ago.in_time_zone('Eastern Time (US & Canada)')
=> Mon, 22 Oct 2012 11:29:16 EDT -04:00
t.since(14.days)
=> Mon, 05 Nov 2012 11:29:16 EST -05:00

case two:

t = 5.weeks.ago.in_time_zone('EST')
=> Mon, 22 Oct 2012 10:38:12 EST -05:00
t.since(14.days)
=> Mon, 05 Nov 2012 10:38:12 EST -05:00

In case one, the time (hour:minute:second) doesn't change but the time zone offset change. In case two, the time (hour:minute:second) doesn't change and the time zone offset doesn't change.

Should we see it like this for case one:

t = 5.weeks.ago.in_time_zone('Eastern Time (US & Canada)')
=> Mon, 22 Oct 2012 11:29:16 EDT -04:00
t.since(14.days)
=> Mon, 05 Nov 2012 10:29:16 EST -05:00

@steveklabnik
Ruby on Rails member

Ahhhh daylight savings. Right.

@pixeltrix
Ruby on Rails member

@drchanimal EST is a timezone with a fixed offset from from UTC, whereas Eastern Time (US & Canada) maps to America/New_York which follows the daylight savings time changes. The same goes for GMT and Europe/London - the latter follows the daylight savings time changes between GMT and BST.

@drchanimal

@pixeltrix,
I understand the daylight saving time. If you look at this example, you will know what what I mean it is not done correctly. Please following it slowly:

t1 = 5.weeks.ago.in_time_zone('Eastern Time (US & Canada)')
=> Mon, 22 Oct 2012 23:18:03 EDT -04:00

t1 is in EDT

now let's convert t1 to UTC and see what it looks like

t1_utc = t1.in_time_zone('UTC')
=> Tue, 23 Oct 2012 03:18:03 UTC +00:00

t2 is now 14 days after using t1 in EDT
t2 is now in EST

t2 = t1.since(14.days)
=> Mon, 05 Nov 2012 23:18:03 EST -05:00

convert t2 to utc and assigned to t2_utc_a, note the time is 04:18:03

t2_utc_a = t2.in_time_zone('UTC')
==> Tue, 06 Nov 2012 04:18:03 UTC +00:00

calculate t2_utc_b from 14 days after t1_utc. It should be the same as t2_utc_a
but it is not! The time is 03:18:03

t2_utc_b = t1_utc.since(14.days)
=> Tue, 06 Nov 2012 03:18:03 UTC +00:00

Why is t2_utc_a different from t2_utc_b? One is Tue, 06 Nov 2012 04:18:03 UTC +00:00 and the other is Tue, 06 Nov 2012 03:18:03 UTC +00:00. They are both 14 days from the same time. Now tell me how is that not wrong?

@pixeltrix
Ruby on Rails member

@drchanimal an ActiveSupport::TimeWithZone instance has a period attribute that stores the daylight savings transition data for that time, e.g:

>> t = 5.weeks.ago.in_time_zone('Eastern Time (US & Canada)')
=> Tue, 23 Oct 2012 00:59:51 EDT -04:00
>> t.period.utc_start
=> Sun, 11 Mar 2012 07:00:00 +0000
>> t.period.utc_end
=> Sun, 04 Nov 2012 06:00:00 +0000

if you convert it to a raw timezone like EST, UTC, GMT, etc. then this period is nil, e.g:

>> u = t.in_time_zone('UTC')
=> Tue, 23 Oct 2012 04:59:51 UTC +00:00
>> u.period.utc_start
=> nil
>> u.period.utc_end
=> nil

when you use since it takes account of these transition points so that you end up with the same time of day when crossing the boundary, e.g:

>> t2 = t.since(14.days)
=> Tue, 06 Nov 2012 00:59:51 EST -05:00
>> u2 = u.since(14.days)
=> Tue, 06 Nov 2012 04:59:51 UTC +00:00

this is because we used a number of days - if we used a number of hours then the time of day will change for the time with a transition boundary, e.g:

>> t3 = t.since(336.hours)
=> Mon, 05 Nov 2012 23:59:51 EST -05:00
>> u3 = u.since(336.hours)
=> Tue, 06 Nov 2012 04:59:51 UTC +00:00

it will also do this intelligently so if you want to advance the time 14 days and 1 hour across a daylight savings boundary you can do this:

>> t.since(14.days + 1.hour)
=> Tue, 06 Nov 2012 01:59:51 EST -05:00

one note of caution - if you're multiplying durations make sure that you do it before using days, etc otherwise the duration is converted to an integer and it's treated like a seconds value, e.g:

>> t.since(2 * 7.days)
=> Mon, 05 Nov 2012 23:59:51 EST -05:00
>> t.since((2 * 7).days)
=> Tue, 06 Nov 2012 00:59:51 EST -05:00

you can also use to_i if you want it to advanced a fixed time period. e.g:

>> t.since(14.days.to_i)
=> Mon, 05 Nov 2012 23:59:51 EST -05:00

TL;DR

If you want to advance the time a fixed number of hours, minutes, etc. and ignore daylight savings transition boundaries then either use a time duration like 24.hours instead of 1.day or convert it to an integer first, e.g: 1.day.to_i.

@drchanimal

@pixeltrix, thanks for the explanation. I am glad there is a workaround. However, I have to say that it is quite confusing when using since(14.days) is different from since(14.days.to_i) and using since(24.hours) different from since(1.day). I don't know how someone will be able to figure that out.

@al2o3cr

@drchanimal - the rule is pretty simple: write what you mean. Because of DST boundaries, 1.day != 24.hours - that's actually a major complication in languages that don't have methods like since.

@pixeltrix
Ruby on Rails member

@drchanimal also there's a similar type of behaviour with 1.month and 30.days which takes account of leap years, different number of days in a month, etc.

>> t = (9.months + 2.weeks).ago
=> Tue, 14 Feb 2012 03:15:53 GMT +00:00
>> t.since(1.month)
=> Wed, 14 Mar 2012 03:15:53 GMT +00:00
>> t.since(30.days)
=> Thu, 15 Mar 2012 03:15:53 GMT +00:00
>> t = (1.year + 9.months + 2.weeks).ago
=> Mon, 14 Feb 2011 03:16:04 GMT +00:00
>> t.since(1.month)
=> Mon, 14 Mar 2011 03:16:04 GMT +00:00
>> t.since(30.days)
=> Wed, 16 Mar 2011 03:16:04 GMT +00:00
@drchanimal

@pixeltrix, I would say months and leap year behavior is different from the example that I list. In my example, the start point is the exactly the same time and date (when convert to UTC) and it is exactly the same number of days going forward. In your example, the start point is difference. However, I see what you are trying to show using leap year as example. Your point is that 1 day is not 24 hours in the day light saving time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment