Skip to content

Commit aac937e

Browse files
committed
offset_for_local_datetime: Add a 'ignore_missing_spans' argument
If this is true and the local time for the given $dt object does not exist in the time zone (due to DST changes for example), the previous span will be returned.
1 parent 48a08b9 commit aac937e

File tree

2 files changed

+61
-5
lines changed

2 files changed

+61
-5
lines changed

lib/DateTime/TimeZone.pm

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ sub offset_for_datetime {
193193
sub offset_for_local_datetime {
194194
my $self = shift;
195195

196-
my $span = $self->_span_for_datetime( 'local', $_[0] );
196+
my $span = $self->_span_for_datetime( 'local', $_[0], $_[1] );
197197

198198
return $span->[OFFSET];
199199
}
@@ -210,6 +210,7 @@ sub _span_for_datetime {
210210
my $self = shift;
211211
my $type = shift;
212212
my $dt = shift;
213+
my $ignore_missing_spans = shift;
213214

214215
my $method = $type . '_rd_as_seconds';
215216

@@ -218,7 +219,7 @@ sub _span_for_datetime {
218219
my $span;
219220
my $seconds = $dt->$method();
220221
if ( $seconds < $self->max_span->[$end] ) {
221-
$span = $self->_spans_binary_search( $type, $seconds );
222+
$span = $self->_spans_binary_search( $type, $seconds, $ignore_missing_spans );
222223
}
223224
else {
224225
my $until_year = $dt->utc_year + 1;
@@ -244,7 +245,7 @@ sub _span_for_datetime {
244245

245246
sub _spans_binary_search {
246247
my $self = shift;
247-
my ( $type, $seconds ) = @_;
248+
my ( $type, $seconds, $ignore_missing_spans ) = @_;
248249

249250
my ( $start, $end ) = _keys_for_type($type);
250251

@@ -276,7 +277,15 @@ sub _spans_binary_search {
276277

277278
$i += $c;
278279

279-
return if $i >= $max;
280+
if ($i >= $max) {
281+
# No span found for this time zone? If the user has asked,
282+
# return the previous span so the offset to utc is higher,
283+
# effectively moving the time forward whatever the difference
284+
# in the two spans is (typically 1 hour for DST).
285+
return $self->{spans}[ $i - 1 ] if $ignore_missing_spans;
286+
287+
return;
288+
}
280289
}
281290
else {
282291

@@ -687,14 +696,18 @@ for the given datetime. This takes into account historical time zone
687696
information, as well as Daylight Saving Time. The offset is
688697
determined by looking at the object's UTC Rata Die days and seconds.
689698
690-
=head2 $tz->offset_for_local_datetime( $dt )
699+
=head2 $tz->offset_for_local_datetime( $dt, [ $ignore_missing_spans ] )
691700
692701
Given a C<DateTime> object, this method returns the offset in seconds
693702
for the given datetime. Unlike the previous method, this method uses
694703
the local time's Rata Die days and seconds. This should only be done
695704
when the corresponding UTC time is not yet known, because local times
696705
can be ambiguous due to Daylight Saving Time rules.
697706
707+
If C<$ignore_missing_spans> is true and the local time for C<$dt> does not
708+
exist in the time zone (due to DST changes for example), the next span
709+
up will be returned.
710+
698711
=head2 $tz->is_dst_for_datetime( $dt )
699712
700713
Given a C<DateTime> object, this method returns true if the DateTime is

t/23ignore-missing-spans.t

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
use strict;
2+
use warnings;
3+
4+
use lib 't/lib';
5+
use T::RequireDateTime;
6+
7+
use Test::More;
8+
use Test::Fatal;
9+
10+
use DateTime::TimeZone;
11+
use Try::Tiny;
12+
13+
my $tz = DateTime::TimeZone->new( name => 'America/Denver' );
14+
15+
my $dt = DateTime->new(
16+
year => 2018,
17+
month => 3,
18+
day => 11,
19+
hour => 2,
20+
minute => 0,
21+
second => 0,
22+
time_zone => 'UTC',
23+
);
24+
25+
{
26+
my $error;
27+
28+
try {
29+
my $offset = $tz->offset_for_local_datetime($dt);
30+
} catch {
31+
$error = $_;
32+
};
33+
34+
like($error, qr/invalid local time/i, 'got correct error');
35+
}
36+
37+
{
38+
my $offset = $tz->offset_for_local_datetime($dt, 1);
39+
is($offset, -25200, 'got -7 offset (even though we should be -6)');
40+
}
41+
42+
done_testing();
43+

0 commit comments

Comments
 (0)