A Date with Perl
        
          Dave Rolsky
          
          autarch@urth.org
          
          IRC: autarch
        
        
          Copyright © David Rolsky 2012-2017
          
 
          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
        
          - Calendars
- Time Zones
- Daylight Saving Time
- Leap Seconds!
Do Not Write Your Own Date and Time Manipulation Code!
        
          - Do Not Write Your Own Date and Time Manipulation Code!
- Do Not Write Your Own Date and Time Manipulation Code!
- Do Not Write Your Own Date and Time Manipulation Code!
Seriously
        
      
      
        Gregorian Calendar
        
          - Based only on earth's revolution around the sun
- Current world standard
- DateTime.pm == Gregorian
Gregorian Calendar for Dummies
        
          - 365 days in a regular year
- 366 in a leap year
- Begins on January 1, year 1 (0001-01-01)
Gregorian Calendar for Dummies
        
          - Year 0 == 1 BC(E)
- May need to tweak the leap year algorithm around year 3000
- Earth's revolution is slowing down
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
        
          - Day length == 1 rotation of the Earth around its axis
- 1 day == 24 hours
- First hour is hour 0
- Last hour is hour 23
- 1 hour == 60 minutes
- 1 minute == 60 seconds (almost all of the time)
- 1 day == 86,400 seconds (more or less)
Atomic Clocks
        
          - Ties length of second to physicsy stuff
- Length of second never changes
- TAI is the international atomic time standard
Leap Seconds
        
          - AKA "The Devil", "A Really, Really Bad Idea"
- The earth's rotation is slowing down
- The length of a second is not
- We need to resync midnight
- Bam, leap second announced!
UTC
        
          - Coordinated Universal Time
- Temps Universel Coordineé
- UTC = TAI (atomic time) + leap seconds to date (37)
- Current world standard
- Based on time in Greenwich, England
- Time zones are based on an offset from UTC
Time Zone Warning
        
          - Time zones are political
- Change all the time for dumb reasons
- Last US change was pointless
Time Zone Standards
        
          - IANA (née Olson) time zone database is the standard
- http://www.iana.org/time-zones
- An open source data/software project
- Microsoft does their own thing (of course)
Time Zone 101
        
          - An offset in minutes and hours from UTC
- Washington, DC is currently at -04:00
- Also has a name
- Names are (mostly) continent or ocean + major city
            
              - America/Chicago
- Asia/Taipei
- Pacific/Fiji
- America/Argentina/San_Juan
 
Time Zone 102
        
          - A named zone is a collection of rules
- Rules define historical and future DST changes
- Also define short names like CDT
- Short names are not unique!
- Only use short names for display
Picking Time Zones
        
          - The Olson database includes many historical zones
- America/Chicago == America/Menominee
- Menominee moved from Eastern to Central in 1973
- No API for finding current time zones (yet?)
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',
);
        
          - No time zone at all
- No offset conversion when set to a real zone
- No leap seconds
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
        
          - An epoch is a reference point for a calendar's start date & time
- The Unix epoch == 1970-01-01T00:00:00 UTC
- Unix epoch is counted in seconds
- Not really UTC since POSIX says we skip leap seconds
- But not really TAI cause NTP is UTC-based
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
        
          - The epoch will no longer fit in a 32-bit int
- 2038-01-19T03:14:07 UTC
- A 30-year mortgage in 2013 ends in 2043
- As of Perl 5.12, Perl always uses a large-enough epoch
DateTime::* Ecosystem
        
          - Formatter/parsers
- Other calendars
- Event and recurrence modules
- DateTimeX modules
Recommendations and Gotchas
      
      
        There's no DateTime::Date Class
        
          - If I could do it all over again ...
- Use the floating time zone
- Use the delta_md()anddelta_daysmethods for math
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
        
          - What moron created this?
- Internally it stores months, days, minutes, seconds, and nanoseconds
- Externally has years(),months(),weeks(),days(), etc. methods
- When you call $duration->days()you get days but not weeks
- Use $duration->in_units('days')instead
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
        
          - End of month modes
            
              - wrap - default for adding months
- preserve - default for subtracting months
- limit
 
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
        
          - Math is not always reversible
            
              - $dt1 - $dt2 = $dur
- $dt2 + $dur != $dt1
 
- Math across DST changes is confusing
            
              - $dst_date - $non_dst_date = ?
- Does the duration include the DST change's hour?
 
- Leap years
- Leap seconds
How to Do Math Safely
        
          - Always use add(),delta_days(),subtract(), etc.
- Never write something like this:
 $dt->set( day => $dt->day() + 1 )
- Use the floating time zone if you can
- Use UTC if you can - UTC has no DST changes
Ambiguous Local Times
        my $dt = DateTime->new(
    year      => 2003,
    month     => 10,
    day       => 26,
    hour      => 1,
    minute    => 30,
    second    => 0,
    time_zone => 'America/Chicago',
);
        
          - Is this standard or DST time?
- There is a DST change on 2003-10-26 from 01:59:59 (DST) to 01:00:00 (standard)
- DateTime.pm always picks the latest UTC time
Storage and Presentation
        
          - Store datetimes as floating or UTC whenever possible
- Or store them as a datetime + time zone (Pgs's TIMESTAMP WITH TZtype)
- Also store the named time zone if the database only stores an offset
- Don't store an epoch, store a datetime
- Use time zones for presentation to users
- Never just store datetimes in the machine's current local time zone
- What happens when you move?
(Stupid?) Performance tricks
      
      
        My Rules of Optimization
        
          - Don't optimize
- Don't optimize, I'm serious
- Don't optimize without benchmarking first
- Don't benchmark without profiling first
- 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
        
          - If your data only comes in one flavor
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,
);
      
      
        Thank You