Time Zones

Dave Rolsky edited this page Jan 30, 2017 · 1 revision

What is the floating time zone?

The floating time zone is used when there is no known time zone that can be used, or when you simply do not care about time zones for what you are doing. If you compare a floating time with a time with a time zone using either the compare method or one of the overloaded comparisons (==, etc.) then the floating time is converted to the other zone for comparison:

my $dt1 = DateTime->new(
    year      => 2002, month  => 4, day => 7,
    hour      => 13,   minute => 55,
    time_zone => 'America/New_York'
);
my $dt2 = DateTime->new(
    year      => 2002, month  => 4, day => 7,
    hour      => 13,   minute => 55,
    time_zone => 'America/Los_Angeles'
);
my $dt_float = DateTime->new(
    year      => 2002, month  => 4, day => 7,
    hour      => 13,   minute => 55,
    time_zone => 'floating'
);

print "fixed date 1 == floating date\n" if $dt1 == $dt_float;
print "fixed date 2 == floating date\n" if $dt2 == $dt_float;
print "fixed date 1 != fixed date 2\n"  if $dt1 != $dt2;

If you want to treat the floating items as if they were in the UTC time zone (i.e. an offset of 0) then use the compareignorefloating class method.

If you are sorting dates using Perl's built-in sort(), you either need to convert every floating time zone to a fixed one, or use the compareignorefloating class method in a custom sort comparator to treat floating time zones as UTC.

Unless you really know what you are doing then you shouldn't mix floating time zones with fixed ones. Always convert the floating time zones to the appropriate fixed time zone (you will have to decide if local, UTC or something else is correct):

# Convert all floating dates to the given $time_zone
# Args:
#  $dates is an arrayref of the source dates
#  $time_zone is either a string or DateTime::TimeZone object
#  $clone governs whether or not to clone the list items
# Returns: an arrayref containing the cleaned dates (note that the
#   source array will be changed unless $clone is true)
sub unfloat_dates {
    my ( $dates, $time_zone, $clone ) = @_;
    $time_zone = "UTC" unless $time_zone;

    my @clean_dates = ();
    foreach my $d (@$dates) {
        $d = $d->clone() if $clone;
        $d->set_time_zone($time_zone)
            if $d->time_zone()->is_floating();
        push @clean_dates, $d;
    }

    return \@clean_dates;
}

my %time = (
    year => 2003, month  => 3, day => 1,
    hour => 1,    minute => 32
);
my @dates = (
    DateTime->new( %time, time_zone => "America/New_York" ),
    DateTime->new( %time, time_zone => "floating" ),
    DateTime->new( %time, time_zone => "UTC" ),
);

if ( $dates[0] == $dates[1] and $dates[2] == $dates[1] ) {

    # This will be true
    print "Floating time equals the other two\n";
}

unfloat_dates( \@dates, "UTC", 0 );

if ( $dates[0] != $dates[1] and $dates[2] == $dates[1] ) {

    # This will be true
    print "Floating time is now fixed to UTC\n";
}

For example MySQL does not store time zone information along with the dates so DateTime::Format::MySQL returns all of the dates it generates from MySQL style dates with a floating time zone. It is up to the user to know what time zone the dates are stored in. Hopefully the developers of the system using MySQL have thought about that and are writing them all in in a consistent time zone.

Also note that if an object's time zone is the floating time zone, then it ignores leap seconds when doing date math, because without knowing the time zone, it is impossible to know when to apply leap seconds.

You also need to use the floating time zone as an intermediate step if you want to convert a time from one zone to the other but want to keep the local time the same. See How do I change the time zone without changing the local time?.

If I know my local time, how do I determine the local time in another time zone?

my $source = DateTime->new(
    year      => 1998, month  => 4, day => 7,
    hour      => 13,   minute => 55,
    time_zone => 'America/New_York'
);
my $result = $source->clone()->set_time_zone('America/Los_Angeles');
print $source->strftime("%F %r %Z"), " became ",
    $result->strftime("%F %r %Z");

# Prints: 1998-04-07 01:55:00 PM EDT became 1998-04-07 10:55:00 AM PDT

How do I change the time zone without changing the local time?

You have to first switch to the floating time zone (see What is the floating time zone?) otherwise the displayed time will be adjusted (keeping the internal time the same) rather than keeping the local clock time the same.

my $source = DateTime->new(
    year      => 1998, month  => 4, day => 7,
    hour      => 13,   minute => 55,
    time_zone => 'America/New_York'
);
my $result = $source->clone()->set_time_zone('floating')
    ->set_time_zone('America/Los_Angeles');
print $source->strftime("%F %r %Z"), " became ",
    $result->strftime("%F %r %Z");

# Prints: 1998-04-07 01:55:00 PM EDT became 1998-04-07 01:55:00 PM PDT

Why doesn't DateTime accept the EST time zone?

Well there are several EST time zones... one in the United States and the other in Australia. If you want to use the US one then use EST5EDT, or preferably America/New_York.

The short names for time zones are not unique, and so any attempt to determine the actual time zone from such a name involves guessing. Use the long names instead.