How do I get DateTime::Format::Strptime to parse the date/times of leap seconds?

Viewed 193

I'm trying to use DateTime::Format::Strptime to parse some date/times, and I can't seem to get it to parse the date/time for leap seconds (e.g., 2012-06-30T23:59:60UTC). The documentation says it supports up to 60 seconds for leap seconds. What am I doing wrong?

Here's my code:

use DateTime;
use DateTime::TimeZone;
use DateTime::Format::Strptime;

my $utc = DateTime::TimeZone->new(name => 'Etc/UTC');

my $ymdhms_parser = DateTime::Format::Strptime->new(pattern => '%F %T',
                                                    locale => 'en_US',
                                                    time_zone => $utc,
                                                    strict => 1,
                                                    on_error => 'croak');

my $date = '2012-06-30 23:59:60';
print "date: $date\n";
my $dt = $ymdhms_parser->parse_datetime($date);
print "dt: $dt\n";
print "tz: ",$dt->time_zone,"\n";

The output I get is:

date: 2012-06-30 23:59:60
Parsed values did not produce a valid date at /path/to/test.pl line 14.

Versions:

DateTime 1.52
DateTime::LeapSecond 1.52
DateTime::Format::Strptime 1.77
1 Answers

The following works as intended:

my $dt = DateTime->new(
   year      => 2012,
   month     =>    6,
   day       =>   30,
   hour      =>   23,
   minute    =>   59,
   second    =>   60,
   time_zone => $utc,
);

However, the call to parse_datetime is effectively doing the following:

my $dt = DateTime->new(
   year      => 2012,
   month     =>    6,
   day       =>   30,
   hour      =>   23,
   minute    =>   59,
   second    =>   60,
   time_zone => 'floating',
);

$dt->set_time_zone($utc);

The above results in DateTime->new to throwing an exception, which results in the error message received.

But I can't fault DateTime::Format::Strptime.

The time zone found in the string being parsed (e.g. if %z is being used) is passed to ->new, and the time zone passed to the constructor is passed to ->set_time zone. This allows for time zone conversions to happen when the time zone is specified in both places.

For example, say the input is 2020-03-25T00:00:00-05:00 (note the offset). And say you passed a time_zone => 'UTC' and an appropriate pattern to the constructor. The resulting DateTime object would be for 5am UTC instead of midnight UTC-05:00. This is good.


It's interesting that it is possible to create DateTime objects using the floating time zone with 60 for seconds.

$ perl -MDateTime -e'
   CORE::say
      DateTime
         ->new(
            year      => 2012,
            month     =>    6,
            day       =>   30,
            hour      =>   23,
            minute    =>   59,
            second    =>   60,
            time_zone => "UTC",
         )
            ->set_time_zone("floating");
'
2012-06-30T23:59:60

But DateTime is inconsistent about allowing it.

$ perl -MDateTime -e'
   CORE::say
      DateTime
         ->new(
            year      => 2012,
            month     =>    6,
            day       =>   30,
            hour      =>   23,
            minute    =>   59,
            second    =>   60,
            time_zone => "floating",
         );
'
Invalid second value (60)
 at -e line 1.

Bug ticket filed.


As a workaround, include the time zone in the data being parsed.

my $ymdhms_parser = DateTime::Format::Strptime->new(
   pattern  => '%F %T%z',
   locale   => 'en_US',
   strict   => 1,
   on_error => 'croak',
);

my $dt = $ymdhms_parser->parse_datetime($date.'Z');
Related