A Date with Perl

Dave Rolsky
autarch@urth.org
IRC: autarch
June 24, 2014
YAPC::NA 2014

Copyright © David Rolsky 2012-2014
A Date with Perl by David Rolsky is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.

Dates and Times are Insane

Do Not Write Your Own Date and Time Manipulation Code!

Seriously

Gregorian Calendar

Gregorian Calendar for Dummies

Gregorian Calendar for Dummies

Simple Dates

use DateTime;
my $dt = DateTime->new(
    year   => 2013,
    month  => 6,
    day    => 5,
);
say $dt->date();       # 2013-06-05
say $dt->month_name(); # June

Les Dates Simples

use DateTime;
my $dt = DateTime->new(
    year   => 2013,
    month  => 6,
    day    => 5,
    locale => 'fr',
);
say $dt->date();       # 2013-06-05
say $dt->month_name(); # Juin

Other Calendars

use DateTime;
use DateTime::Calendar::Chinese;
my $dt = DateTime->new(
    year   => 2013,
    month  => 6,
    day    => 5,
);
my $chdt = DateTime::Calendar::Chinese->from_object( object => $dt );
say $chdt->cycle();         # 78
say $chdt->zodiac_animal(); # snake
say $chdt->celestial_stem(), $chdt->terrestrial_branch(); # 癸巳

Time for Dummies

Atomic Clocks

Leap Seconds

UTC

Time Zone Warning

Time Zone Standards

Time Zone 101

Time Zone 102

Picking Time Zones

What Time is it There?

use DateTime;
my $dt = DateTime->new(
    year      => 2013,
    month     => 6,
    day       => 5,
    hour      => 9,
    minute    => 30,
    time_zone => 'America/Chicago',
);

say $dt->datetime(); # 2013-06-05T09:30:00
$dt->set_time_zone('Asia/Taipei');
say $dt->datetime(); # 2013-06-05T22:30:00

The Floating Time Zone

use DateTime;
my $dt = DateTime->now(
    time_zone => 'floating',
);

The Floating Time Zone

use DateTime;
my $dt = DateTime->new(
    year      => 2013,
    month     => 6,
    day       => 5,
    hour      => 9,
    minute    => 30,
    time_zone => 'floating',
);

say $dt->datetime(); # 2013-06-05T09:30:00
$dt->set_time_zone('Asia/Taipei');
say $dt->datetime(); # 2013-06-05T09:30:00

Epochs and the Unix Epoch

Calculating the Epoch

use DateTime;
my $dt = DateTime->new(
    year      => 2013,
    month     => 6,
    day       => 5,
    hour      => 9,
    minute    => 30,
    time_zone => 'America/Chicago',
);

say $dt->epoch(); # 1370442600

The y2.038k problem

DateTime::* Ecosystem

Recommendations and Gotchas

There's no DateTime::Date Class

Calculating the Difference Between Two Dates

my $dt1 = DateTime->new(
    year      => 2013,
    month     => 6,
    day       => 5,
    time_zone => 'floating',
);

my $dt2 = DateTime->new(
    year      => 1973,
    month     => 12,
    day       => 6,
    time_zone => 'floating',
);

my $duration = $dt1->delta_days($dt2);
say $duration->in_units('days'); # 14426

DateTime::Duration Has a Terrible API

Say What, DateTime::Duration?

my $dt1 = DateTime->new(
    year      => 2013,
    month     => 6,
    day       => 5,
    time_zone => 'floating',
);

my $dt2 = DateTime->new(
    year      => 1973,
    month     => 12,
    day       => 6,
    time_zone => 'floating',
);

my $duration = $dt1->delta_days($dt2);
say $duration->days();           # 6 ... WTF?
say $duration->weeks();          # 2060
say $duration->in_units('days'); # 14426

DateTime Math is Hard
Let's Go Shopping

How Long is a Month?

my $dt = ...; # 2009-02-01
$dt->add( days => 28 );
say $dt; # 2009-03-01
$dt->add( days => 28 );
say $dt; # 2009-03-29

How Long is a Month??

my $dt = ...; # 2009-01-30
$dt->add( months => 1 );
say $dt; # 2009-03-02
$dt->add( months => 1 );
say $dt; # 2009-04-02

How Long is a Month??!

my $dt = ...; # 2009-01-30
$dt->add( months => 1, end_of_month => 'limit' );
say $dt; # 2009-02-28
$dt->add( months => 1 );
say $dt; # 2009-03-28

How Long is a Day?

my $dt = DateTime->new(
    year      => 2012,
    month     => 11,
    day       => 4,
    hour      => 0,
    time_zone => 'America/Chicago',
);

say $dt; # 2012-11-04T00:00:00
$dt->add( hours => 1 );
say $dt; # 2012-11-04T01:00:00
$dt->add( hours => 1 );
say $dt; # 2012-11-04T01:00:00

How Long is a Day?

my $dt = DateTime->new(
    year      => 2012,
    month     => 3,
    day       => 11,
    hour      => 0,
    time_zone => 'America/Chicago',
);

say $dt; # 2012-03-11T00:00:00
$dt->add( hours => 1 );
say $dt; # 2012-03-11T01:00:00
$dt->add( hours => 1 );
say $dt; # 2012-03-11T03:00:00

No 02:00 for You!

my $dt = DateTime->new(
    year      => 2012,
    month     => 3,
    day       => 10,
    hour      => 2,
    time_zone => 'America/Chicago',
);

say $dt; # 2012-03-11T00:02:00
$dt->add( days => 1 ); # Throws an exception ...
# Invalid local time for date in time zone: America/Chicago

# But this works
$dt->add( hours => 24 );
say $dt; # 2012-03-11T03:00:00

Math Order Matters

my $dt = DateTime->new(
    year      => 2011,
    month     => 2,
    day       => 28,
);

$dt->add( months => 1, days => 1 );
say $dt; # 2011-04-01, not 2011-03-29

Want control? Make separate calls:

$dt->add( months => 1 )->add( days => 1, );
say $dt; # 2011-03-29

More math gotchas

How to Do Math Safely

Ambiguous Local Times

my $dt = DateTime->new(
    year      => 2003,
    month     => 10,
    day       => 26,
    hour      => 1,
    minute    => 30,
    second    => 0,
    time_zone => 'America/Chicago',
);

Storage and Presentation

(Stupid?) Performance tricks

My Rules of Optimization

  1. Don't optimize
  2. Don't optimize, I'm serious
  3. Don't optimize without benchmarking first
  4. Don't benchmark without profiling first
  5. See rule #1

Cache the time zone object

my $dt = DateTime->new(
    year      => 2013,
    month     => 6,
    day       => 5,
    hour      => 9,
    minute    => 30,
    time_zone => 'America/Chicago',
);
my $tz = DateTime::TimeZone->new( name => 'America/Chicago' );
my $dt = DateTime->new(
    year      => 2013,
    month     => 6,
    day       => 5,
    hour      => 9,
    minute    => 30,
    time_zone => $tz,
);

Don't Use a Parser

my $dt = DateTime::Format::Foo->parse_datetime($string);
my ( $y, $m, $d ) = $string =~ /^(\d{4})-(\d{2})-(\d{2})/;

my $dt = DateTime->new(
    year  => $y,
    month => $m,
    day   => $d,
);

Don't Validate

local $Params::Validate::NO_VALIDATION = 1;
my $dt = DateTime->new(
    year  => $y,
    month => $m,
    day   => $d,
);

Questions?

Thank You