Perl DateTime objects, time zones, and ISO8601

Here’s what I want to do. DateTime objects conveniently stringify to their ISO8601 date representation. But last week the clocks changed, and all my code broke, because this representation doesn’t include the time zone. I want a DateTime object that will stringify to an ISO8601 date including a timezone. Shouldn’t be too hard.

Let’s look at the perldoc for DateTime::Format::ISO8601. There’s an awful lot of things that can be called an ISO8601 date string. 20060101T120000+0100 is a valid date. 2006-01-01T12:00:00+01:00 is also a valid date string. Personally, I prefer the latter. And fortunately, the W3C have looked at this mess and decided ‘It’s too complicated‘. And they agree with me. So this is the string that I want to produce.

DateTime.pm provides a formatter method – you can define a formatter class that will stringify the object. Horay. But using DateTime::Formatter::ISO8601 doesn’t produce a string with a time zone. My next plan is to use DateTime::Format::Strptime and provide a strptime spec that will produce the right string. To the DateTime / strftime docs! Apparently, %FT%T produces a nice 2006-01-01T12:00:00, so all we need is the time zone. And again, here is where we fail. There’s %z, but that produces +0100, and I need +01:00.

At this point, I write a hack and get back to work. DateTime lets you use the magic format specifier %{ method_name } to call a method of the DateTime object. So I push a method into the DateTime namespace that produces the string that I want.

sub DateTime::iso8601_with_tz {
  my $self = shift;
  my $val = $self->strftime('%FT%T%z');
  $val =~ s/(dd)$/:$1/;
  return $val;
}

Then I can create a formatter that will format DateTime objects the way I want them.

use DateTime::Format::Strptime;
my $formatter = DateTime::Format::Strptime->new( pattern => "%{iso8601_with_tz}" )

Finally, I can use this as my formatter.

$my_datetime->formatter( $formatter );

Nasty.