diff --git a/lib/DateTime/TimeZone.pm b/lib/DateTime/TimeZone.pm index c404d0fb..0d0da4e8 100644 --- a/lib/DateTime/TimeZone.pm +++ b/lib/DateTime/TimeZone.pm @@ -193,7 +193,7 @@ sub offset_for_datetime { sub offset_for_local_datetime { my $self = shift; - my $span = $self->_span_for_datetime( 'local', $_[0] ); + my $span = $self->_span_for_datetime( 'local', $_[0], $_[1] ); return $span->[OFFSET]; } @@ -210,6 +210,7 @@ sub _span_for_datetime { my $self = shift; my $type = shift; my $dt = shift; + my $ignore_missing_spans = shift; my $method = $type . '_rd_as_seconds'; @@ -218,7 +219,7 @@ sub _span_for_datetime { my $span; my $seconds = $dt->$method(); if ( $seconds < $self->max_span->[$end] ) { - $span = $self->_spans_binary_search( $type, $seconds ); + $span = $self->_spans_binary_search( $type, $seconds, $ignore_missing_spans ); } else { my $until_year = $dt->utc_year + 1; @@ -244,7 +245,7 @@ sub _span_for_datetime { sub _spans_binary_search { my $self = shift; - my ( $type, $seconds ) = @_; + my ( $type, $seconds, $ignore_missing_spans ) = @_; my ( $start, $end ) = _keys_for_type($type); @@ -276,7 +277,15 @@ sub _spans_binary_search { $i += $c; - return if $i >= $max; + if ($i >= $max) { + # No span found for this time zone? If the user has asked, + # return the previous span so the offset to utc is higher, + # effectively moving the time forward whatever the difference + # in the two spans is (typically 1 hour for DST). + return $self->{spans}[ $i - 1 ] if $ignore_missing_spans; + + return; + } } else { @@ -687,7 +696,7 @@ for the given datetime. This takes into account historical time zone information, as well as Daylight Saving Time. The offset is determined by looking at the object's UTC Rata Die days and seconds. -=head2 $tz->offset_for_local_datetime( $dt ) +=head2 $tz->offset_for_local_datetime( $dt, [ $ignore_missing_spans ] ) Given a C object, this method returns the offset in seconds for the given datetime. Unlike the previous method, this method uses @@ -695,6 +704,10 @@ the local time's Rata Die days and seconds. This should only be done when the corresponding UTC time is not yet known, because local times can be ambiguous due to Daylight Saving Time rules. +If C<$ignore_missing_spans> is true and the local time for C<$dt> does not +exist in the time zone (due to DST changes for example), the next span +up will be returned. + =head2 $tz->is_dst_for_datetime( $dt ) Given a C object, this method returns true if the DateTime is diff --git a/t/23ignore-missing-spans.t b/t/23ignore-missing-spans.t new file mode 100644 index 00000000..c767a612 --- /dev/null +++ b/t/23ignore-missing-spans.t @@ -0,0 +1,44 @@ +use strict; +use warnings; + +use lib 't/lib'; +use T::RequireDateTime; + +use Test::More; +use Test::Fatal; + +use DateTime::TimeZone; +use Try::Tiny; + +my $tz = DateTime::TimeZone->new( name => 'America/Denver' ); + +my $dt = DateTime->new( + year => 2018, + month => 3, + day => 11, + hour => 2, + minute => 0, + second => 0, + time_zone => 'UTC', +); + +{ + my $error; + + try { + my $offset = $tz->offset_for_local_datetime($dt); + } + catch { + $error = $_; + }; + + like( $error, qr/invalid local time/i, 'got correct error' ); +} + +{ + my $offset = $tz->offset_for_local_datetime( $dt, 1 ); + is( $offset, -25200, 'got -7 offset (even though we should be -6)' ); +} + +done_testing(); +