Subject: | local_rdn_values at leap seconds |
Date: | Tue, 27 Sep 2011 14:01:18 +0100 |
To: | bug-DateTime [...] rt.cpan.org |
From: | Zefram <zefram [...] fysh.org> |
(This is what I really wanted to investigate that led to me discovering
the preceding two bugs about timezones and leap seconds.)
For a time at a leap second, the local_rd_values that DateTime supplies
are problematic: they're generated inconsistently, and in the general
case result in ambiguity. For reference, here's what the related
utc_rd_values does:
$ perl -MDateTime -lwe '$dt=DateTime->new(year=>2008,month=>12,day=>31,hour=>23,minute=>59,second=>60, time_zone=>"UTC"); print $dt; print join",",$dt->utc_rd_values'
2008-12-31T23:59:60
733407,86400,0
It returns a seconds-of-day value of 86400, which of course can't occur in
a normal day. This uniquely identifies the leap second, and puts it in
its proper numerical place relative to all the other seconds of the day.
But that can only work when the leap second occurs right at the end of the
day, which it does in UTC but in the general case doesn't in a timezone.
Suppose we have a timezone offset of an hour:
$ perl -MDateTime -lwe '$dt=DateTime->new(year=>2008,month=>12,day=>31,hour=>23,minute=>59,second=>60, time_zone=>"UTC"); $dt->set_time_zone("+01:00"); print $dt; print join",",$dt->local_rd_values'
2009-01-01T00:59:60
733408,3600,0
$ perl -MDateTime -lwe '$dt=DateTime->new(year=>2009,month=>1,day=>1,hour=>0,minute=>0,second=>0, time_zone=>"UTC"); $dt->set_time_zone("+01:00"); print $dt; print join",",$dt->local_rd_values'
2009-01-01T01:00:00
733408,3600,0
Stringification shows the correct broken-down time of 00:59:60.
The local_rd_values show the correct day, and 3600 seconds into that day,
which is understandable since the preceding second (00:59:59) was actually
3559 seconds into the day. However, 3600 is the same seconds-of-day
value that one would normally expect for the time 01:00:00, and indeed
the following second does show the same seconds-of-day value despite
being a different time. So the local_rd_values result is ambiguous.
It's also calculated inconsistently. For a timezone offset of zero I
can get two different results, depending on whether the timezone is_utc:
$ perl -MDateTime -lwe '$dt=DateTime->new(year=>2008,month=>12,day=>31,hour=>23,minute=>59,second=>60, time_zone=>"UTC"); $dt->set_time_zone("UTC"); print $dt; print join",",$dt->local_rd_values'
2008-12-31T23:59:60
733407,86400,0
$ perl -MDateTime::TimeZone::SystemV -MDateTime -lwe '$dt=DateTime->new(year=>2008,month=>12,day=>31,hour=>23,minute=>59,second=>60, time_zone=>"UTC"); $dt->set_time_zone(DateTime::TimeZone::SystemV->new("UTC0")); print $dt; print join",",$dt->local_rd_values'
2009-01-01T23:59:60
733408,0,0
For the is_utc zone, local_rd_values returns the correct day and the same
distinctive (but irregular) seconds-since-day value as utc_rd_values.
For the non-is_utc zone, which otherwise behaves identically,
local_rd_values returns (as for the non-zero offsets) a regular-looking
seconds-of-day value that appears to identify the second following the
leap second. In this case that also involves showing the wrong day.
The Europe/London zone, which is not generally equivalent to UTC but
happens to have a zero offset at that time, also produces what looks
like the first second of the next day.
DateTime doesn't document what utc_rd_values and local_rd_values are
meant to do around a leap second. It does say they're meant for other
modules to be able to extract the time represented by the object. To have
local_rd_values return an apparently-regular value for the leap second,
ambiguous with the following second, defeats this purpose, because its
value doesn't fully identify the local time represented by the object.
Since providing seconds-of-day = 86400 only works at the very end of
the day, that's not a solution either. For local_rd_values to be fully
useful, it's going to need some other behaviour.
For the purposes of timezones, in the modern era timezones are all
UTC-compatible by virtue of using only offsets of integral minutes, so
usually the number of seconds into the minute doesn't matter. Timezones
therefore usually want to treat a leap second like the preceding second
(xx:xx:59). It would be convenient if leap seconds were indicated by
having seconds-of-day the same as it was for the preceding second and
letting the nanoseconds-of-second value exceed 10^9. This trick has
been proposed for struct timeval/timespec in some situations, and it
doesn't run into 32-bit trouble.
-zefram