File Coverage

blib/lib/DateTime/TimeZone.pm
Criterion Covered Total %
statement 288 295 97.6
branch 112 130 86.1
condition 40 51 78.4
subroutine 58 59 98.3
pod 20 23 86.9
total 518 558 92.8


line stmt bran cond sub pod time code
1             package DateTime::TimeZone;
2              
3 20     20   9544957 use 5.008004;
  20         95  
4              
5 20     20   112 use strict;
  20         39  
  20         544  
6 20     20   107 use warnings;
  20         40  
  20         2771  
7 20     20   783 use namespace::autoclean;
  20         29875  
  20         144  
8              
9             our $VERSION = '2.67';
10              
11             # Note that while we make use of DateTime::Duration in this module if we
12             # actually try to load it here all hell breaks loose with circular
13             # dependencies.
14 20     20   16440 use DateTime::TimeZone::Catalog;
  20         120  
  20         1427  
15 20     20   10126 use DateTime::TimeZone::Floating;
  20         161  
  20         1023  
16 20     20   10831 use DateTime::TimeZone::Local;
  20         70  
  20         850  
17 20     20   152 use DateTime::TimeZone::OffsetOnly;
  20         36  
  20         510  
18 20     20   9314 use DateTime::TimeZone::OlsonDB::Change;
  20         63  
  20         885  
19 20     20   523 use DateTime::TimeZone::UTC;
  20         42  
  20         721  
20 20     20   103 use Module::Runtime qw( require_module );
  20         39  
  20         149  
21 20     20   1783 use Params::ValidationCompiler 0.13 qw( validation_for );
  20         25450  
  20         1284  
22 20     20   641 use Specio::Library::Builtins;
  20         96823  
  20         351  
23 20     20   192437 use Specio::Library::String;
  20         16239  
  20         238  
24 20     20   43193 use Try::Tiny;
  20         68  
  20         2308  
25              
26             ## no critic (ValuesAndExpressions::ProhibitConstantPragma)
27 20     20   190 use constant INFINITY => 100**1000;
  20         61  
  20         2233  
28 20     20   127 use constant NEG_INFINITY => -1 * ( 100**1000 );
  20         37  
  20         1214  
29              
30             # the offsets for each span element
31 20     20   119 use constant UTC_START => 0;
  20         51  
  20         1167  
32 20     20   113 use constant UTC_END => 1;
  20         36  
  20         1006  
33 20     20   98 use constant LOCAL_START => 2;
  20         34  
  20         1181  
34 20     20   311 use constant LOCAL_END => 3;
  20         120  
  20         1129  
35 20     20   137 use constant OFFSET => 4;
  20         72  
  20         1186  
36 20     20   152 use constant IS_DST => 5;
  20         132  
  20         1117  
37 20     20   154 use constant SHORT_NAME => 6;
  20         46  
  20         85871  
38              
39             my %SpecialName = map { $_ => 1 }
40             qw( EST MST HST CET EET MET WET EST5EDT CST6CDT MST7MDT PST8PDT );
41              
42             {
43             my $validator = validation_for(
44             name => '_check_new_params',
45             name_is_optional => 1,
46             params => {
47             name => {
48             type => t('NonEmptyStr'),
49             },
50             },
51             );
52              
53             sub new {
54 4754     4754 1 14598063 shift;
55 4754         133078 my %p = $validator->(@_);
56              
57 4753 100       146483 if ( exists $DateTime::TimeZone::Catalog::LINKS{ $p{name} } ) {
    50          
58 541         1712 $p{name} = $DateTime::TimeZone::Catalog::LINKS{ $p{name} };
59             }
60             elsif ( exists $DateTime::TimeZone::Catalog::LINKS{ uc $p{name} } ) {
61 0         0 $p{name} = $DateTime::TimeZone::Catalog::LINKS{ uc $p{name} };
62             }
63              
64 4753 100 66     29879 unless ( $p{name} =~ m{/}
65             || $SpecialName{ $p{name} } ) {
66 3539 100       10467 if ( $p{name} eq 'floating' ) {
67 1018         4138 return DateTime::TimeZone::Floating->instance;
68             }
69              
70 2521 100       6191 if ( $p{name} eq 'local' ) {
71 2         25 return DateTime::TimeZone::Local->TimeZone();
72             }
73              
74 2519 100 100     7302 if ( $p{name} eq 'UTC' || $p{name} eq 'Z' ) {
75 2507         10610 return DateTime::TimeZone::UTC->instance;
76             }
77              
78 12         103 return DateTime::TimeZone::OffsetOnly->new( offset => $p{name} );
79             }
80              
81 1214 100       5760 if ( $p{name} =~ m{Etc/(?:GMT|UTC)(\+|-)(\d{1,2})}i ) {
82              
83             # Etc/GMT+4 is actually UTC-4. For more info, see
84             # https://data.iana.org/time-zones/tzdb/etcetera
85 61 100       260 my $sign = $1 eq '-' ? '+' : '-';
86 61         153 my $hours = $2;
87 61 100       263 die "The timezone '$p{name}' is an invalid name.\n"
88             unless $hours <= 14;
89 58         318 return DateTime::TimeZone::OffsetOnly->new(
90             offset => "${sign}${hours}:00" );
91             }
92              
93 1153         2723 my $subclass = $p{name};
94 1153         5384 $subclass =~ s{/}{::}g;
95 1153         2700 $subclass =~ s/-(\d)/_Minus$1/;
96 1153         2278 $subclass =~ s/\+/_Plus/;
97 1153         2496 $subclass =~ s/-/_/g;
98              
99 1153         2733 my $real_class = "DateTime::TimeZone::$subclass";
100              
101 1153 100       12792 die "The timezone '$p{name}' is an invalid name.\n"
102             unless $real_class =~ /^\w+(::\w+)*$/;
103              
104 1152 100       13174 unless ( $real_class->can('instance') ) {
105 568         3550 ($real_class)
106             = $real_class =~ m{\A([a-zA-Z0-9_]+(?:::[a-zA-Z0-9_]+)*)\z};
107              
108 568         1109 my $e;
109             try {
110             ## no critic (Variables::RequireInitializationForLocalVars)
111 568     568   34632 local $SIG{__DIE__};
112 568         2588 require_module($real_class);
113             }
114             catch {
115 9     9   3105 $e = $_;
116 568         5198 };
117              
118 568 100       15943 if ($e) {
119 9         55 my $regex = join '.', split /::/, $real_class;
120 9         27 $regex .= '\\.pm';
121              
122 9 50       337 if ( $e =~ /^Can't locate $regex/i ) {
123 9         94 die
124             "The timezone '$p{name}' could not be loaded, or is an invalid name.\n";
125             }
126             else {
127 0         0 die $e;
128             }
129             }
130             }
131              
132 1143         8072 my $zone = $real_class->instance( name => $p{name}, is_olson => 1 );
133              
134 1143 50       12427 if ( $zone->is_olson() ) {
135 1143 100       6350 my $object_version
136             = $zone->can('olson_version')
137             ? $zone->olson_version()
138             : 'unknown';
139 1143         7848 my $catalog_version = DateTime::TimeZone::Catalog->OlsonVersion();
140              
141 1143 100       3576 if ( $object_version ne $catalog_version ) {
142 2         112 warn
143             "Loaded $real_class, which is from a different version ($object_version) of the Olson database than this installation of DateTime::TimeZone ($catalog_version).\n";
144             }
145             }
146              
147 1143         5067 return $zone;
148             }
149             }
150              
151             {
152             my $validator = validation_for(
153             name => '_check_init_params',
154             name_is_optional => 1,
155             params => {
156             name => {
157             type => t('NonEmptyStr'),
158             },
159             spans => {
160             type => t('ArrayRef'),
161             },
162             is_olson => {
163             type => t('Bool'),
164             default => 0,
165             },
166             },
167             );
168              
169             ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
170             sub _init {
171 559     559   1305 my $class = shift;
172 559         19510 my %p = $validator->(@_);
173              
174             my $self = bless {
175             name => $p{name},
176             spans => $p{spans},
177             is_olson => $p{is_olson},
178 559         31351 }, $class;
179              
180 559         1869 foreach my $k (qw( last_offset last_observance rules max_year )) {
181 2236         3927 my $m = "_$k";
182 2236 100       14040 $self->{$k} = $self->$m() if $self->can($m);
183             }
184              
185 559         3345 return $self;
186             }
187             ## use critic
188             }
189              
190 1142     1142 1 4666 sub is_olson { $_[0]->{is_olson} }
191              
192             sub is_dst_for_datetime {
193 7     7 1 565 my $self = shift;
194              
195 7         24 my $span = $self->_span_for_datetime( 'utc', $_[0] );
196              
197 7         45 return $span->[IS_DST];
198             }
199              
200             sub offset_for_datetime {
201 187     187 1 4301 my $self = shift;
202              
203 187         454 my $span = $self->_span_for_datetime( 'utc', $_[0] );
204              
205 187         459 return $span->[OFFSET];
206             }
207              
208             sub offset_for_local_datetime {
209 62     62 1 493 my $self = shift;
210              
211 62         142 my $span = $self->_span_for_datetime( 'local', $_[0] );
212              
213 58         147 return $span->[OFFSET];
214             }
215              
216             sub short_name_for_datetime {
217 52     52 1 4240 my $self = shift;
218              
219 52         153 my $span = $self->_span_for_datetime( 'utc', $_[0] );
220              
221 52         230 return $span->[SHORT_NAME];
222             }
223              
224             sub _span_for_datetime {
225 308     308   396 my $self = shift;
226 308         438 my $type = shift;
227 308         415 my $dt = shift;
228              
229 308         480 my $method = $type . '_rd_as_seconds';
230              
231 308 100       620 my $end = $type eq 'utc' ? UTC_END : LOCAL_END;
232              
233 308         404 my $span;
234 308         844 my $seconds = $dt->$method();
235 308 100       1301 if ( $seconds < $self->max_span->[$end] ) {
236 299         630 $span = $self->_spans_binary_search( $type, $seconds );
237             }
238             else {
239 9         38 my $until_year = $dt->utc_year + 1;
240 9         71 $span = $self->_generate_spans_until_match(
241             $until_year, $seconds,
242             $type
243             );
244             }
245              
246             # This means someone gave a local time that doesn't exist
247             # (like during a transition into savings time)
248 308 100       590 unless ( defined $span ) {
249 4         6 my $err = 'Invalid local time for date';
250 4 50       9 $err .= q{ } . $dt->iso8601 if $type eq 'utc';
251 4         11 $err .= ' in time zone: ' . $self->name;
252 4         5 $err .= "\n";
253              
254 4         45 die $err;
255             }
256              
257 304         498 return $span;
258             }
259              
260             sub _spans_binary_search {
261 299     299   384 my $self = shift;
262 299         524 my ( $type, $seconds ) = @_;
263              
264 299         487 my ( $start, $end ) = _keys_for_type($type);
265              
266 299         415 my $min = 0;
267 299         323 my $max = scalar @{ $self->{spans} } + 1;
  299         531  
268 299         601 my $i = int( $max / 2 );
269              
270             # special case for when there are only 2 spans
271 299 100 100     1040 $i++ if $max % 2 && $max != 3;
272              
273 299 50       370 $i = 0 if @{ $self->{spans} } == 1;
  299         617  
274              
275 299         408 while (1) {
276 1948         2479 my $current = $self->{spans}[$i];
277              
278 1948 100       3193 if ( $seconds < $current->[$start] ) {
    100          
279 468         530 $max = $i;
280 468         652 my $c = int( ( $i - $min ) / 2 );
281 468   100     693 $c ||= 1;
282              
283 468         597 $i -= $c;
284              
285 468 50       740 return if $i < $min;
286             }
287             elsif ( $seconds >= $current->[$end] ) {
288 1184         1220 $min = $i;
289 1184         1521 my $c = int( ( $max - $i ) / 2 );
290 1184   100     1673 $c ||= 1;
291              
292 1184         1310 $i += $c;
293              
294 1184 100       1831 return if $i >= $max;
295             }
296             else {
297              
298             # Special case for overlapping ranges because of DST and
299             # other weirdness (like Alaska's change when bought from
300             # Russia by the US). Always prefer latest span.
301 296 100 100     864 if ( $current->[IS_DST] && $type eq 'local' ) {
302              
303             # Asia/Dhaka in 2009j goes into DST without any known
304             # end-of-DST date (wtf, Bangladesh).
305 23 50       57 return $current if $current->[UTC_END] == INFINITY;
306              
307 23         40 my $next = $self->{spans}[ $i + 1 ];
308              
309             # Sometimes we will get here and the span we're
310             # looking at is the last that's been generated so far.
311             # We need to try to generate one more or else we run
312             # out.
313 23   66     47 $next ||= $self->_generate_next_span;
314              
315 23 50       47 die "No next span in $self->{max_year}" unless defined $next;
316              
317 23 100 66     95 if ( ( !$next->[IS_DST] )
      66        
318             && $next->[$start] <= $seconds
319             && $seconds <= $next->[$end] ) {
320 2         8 return $next;
321             }
322             }
323              
324 294         682 return $current;
325             }
326             }
327             }
328              
329             sub _generate_next_span {
330 1     1   3 my $self = shift;
331              
332 1         2 my $last_idx = $#{ $self->{spans} };
  1         3  
333              
334 1         3 my $max_span = $self->max_span;
335              
336             # Kind of a hack, but AFAIK there are no zones where it takes
337             # _more_ than a year for a _future_ time zone change to occur, so
338             # by looking two years out we can ensure that we will find at
339             # least one more span. Of course, I will no doubt be proved wrong
340             # and this will cause errors.
341             $self->_generate_spans_until_match(
342 1         7 $self->{max_year} + 2,
343             $max_span->[UTC_END] + ( 366 * 86400 ), 'utc'
344             );
345              
346 1         4 return $self->{spans}[ $last_idx + 1 ];
347             }
348              
349             sub _generate_spans_until_match {
350 10     10   25 my $self = shift;
351 10         23 my $generate_until_year = shift;
352 10         15 my $seconds = shift;
353 10         16 my $type = shift;
354              
355 10         21 my @changes;
356 10         19 my @rules = @{ $self->_rules };
  10         47  
357 10         37 foreach my $year ( $self->{max_year} .. $generate_until_year ) {
358             ## no critic (ControlStructures::ProhibitCStyleForLoops)
359 241         702 for ( my $x = 0; $x < @rules; $x++ ) {
360 482         777 my $last_offset_from_std;
361              
362 482 50       1268 if ( @rules == 2 ) {
    0          
363 482 100       1680 $last_offset_from_std
364             = $x
365             ? $rules[0]->offset_from_std
366             : $rules[1]->offset_from_std;
367             }
368             elsif ( @rules == 1 ) {
369 0         0 $last_offset_from_std = $rules[0]->offset_from_std;
370             }
371             else {
372 0         0 my $count = scalar @rules;
373 0         0 die
374             "Cannot generate future changes for zone with $count infinite rules\n";
375             }
376              
377 482         874 my $rule = $rules[$x];
378              
379             my $next = $rule->utc_start_datetime_for_year(
380             $year,
381 482         2060 $self->{last_offset}, $last_offset_from_std
382             );
383              
384             # don't bother with changes we've seen already
385 482 100       1556 next if $next->utc_rd_as_seconds < $self->max_span->[UTC_END];
386              
387             push @changes,
388             DateTime::TimeZone::OlsonDB::Change->new(
389             type => 'rule',
390             utc_start_datetime => $next,
391             local_start_datetime => $next + DateTime::Duration->new(
392             seconds => $self->{last_observance}->total_offset
393             + $rule->offset_from_std
394             ),
395             short_name => $self->{last_observance}
396             ->formatted_short_name( $rule->letter, $rule ),
397             observance => $self->{last_observance},
398 460         2574 rule => $rule,
399             );
400             }
401             }
402              
403 10         26 $self->{max_year} = $generate_until_year;
404              
405             my @sorted
406 10         93 = sort { $a->utc_start_datetime <=> $b->utc_start_datetime } @changes;
  1531         29719  
407              
408 10         275 my ( $start, $end ) = _keys_for_type($type);
409              
410 10         23 my $match;
411             ## no critic (ControlStructures::ProhibitCStyleForLoops)
412 10         41 for ( my $x = 1; $x < @sorted; $x++ ) {
413 450         1073 my $span = DateTime::TimeZone::OlsonDB::Change::two_changes_as_span(
414             @sorted[ $x - 1, $x ] );
415              
416 450         908 $span = _span_as_array($span);
417              
418 450         712 push @{ $self->{spans} }, $span;
  450         960  
419              
420 450 100 100     1957 $match = $span
421             if $seconds >= $span->[$start] && $seconds < $span->[$end];
422             }
423              
424 10         2292 return $match;
425             }
426              
427 791     791 0 5165 sub max_span { $_[0]->{spans}[-1] }
428              
429             sub _keys_for_type {
430 309 100   309   736 $_[0] eq 'utc' ? ( UTC_START, UTC_END ) : ( LOCAL_START, LOCAL_END );
431             }
432              
433             sub _span_as_array {
434             [
435 450         1927 @{ $_[0] }{
436 450     450   692 qw( utc_start utc_end local_start local_end offset is_dst short_name )
437             }
438             ];
439             }
440              
441 5735     5735 1 221846 sub is_floating {0}
442              
443 444     444 1 4503 sub is_utc {0}
444              
445 2     2 1 48 sub has_dst_changes {0}
446              
447 668     668 1 228785 sub name { $_[0]->{name} }
448 0     0 1 0 sub category { ( split /\//, $_[0]->{name}, 2 )[0] }
449              
450             sub is_valid_name {
451 13     13 1 855989 my $class = shift;
452 13         22 my $name = shift;
453              
454             my $tz = try {
455             ## no critic (Variables::RequireInitializationForLocalVars)
456 13     13   376 local $SIG{__DIE__};
457 13         50 $class->new( name => $name );
458 13         88 };
459              
460 13 100 66     1061 return $tz && $tz->isa('DateTime::TimeZone') ? 1 : 0;
461             }
462              
463             sub STORABLE_freeze {
464 5     5 0 579 my $self = shift;
465              
466 5         12 return $self->name;
467             }
468              
469             sub STORABLE_thaw {
470 4     4 0 48 my $self = shift;
471 4         4 shift;
472 4         6 my $serialized = shift;
473              
474 4   33     11 my $class = ref $self || $self;
475              
476 4         5 my $obj;
477 4 50       17 if ( $class->isa(__PACKAGE__) ) {
478 4         10 $obj = __PACKAGE__->new( name => $serialized );
479             }
480             else {
481 0         0 $obj = $class->new( name => $serialized );
482             }
483              
484 4         24 %$self = %$obj;
485              
486 4         21 return $self;
487             }
488              
489             #
490             # Functions
491             #
492             sub offset_as_seconds {
493 273     273 1 5445 my $offset = shift;
494             $offset = shift if try {
495             ## no critic (Variables::RequireInitializationForLocalVars)
496 273     273   8346 local $SIG{__DIE__};
497 273         3196 $offset->isa('DateTime::TimeZone');
498 273 100       1799 };
499              
500 273 50       4875 return undef unless defined $offset;
501              
502 273 100       762 return 0 if $offset eq '0';
503              
504 270         558 my ( $sign, $hours, $minutes, $seconds );
505 270 100       2308 if ( $offset =~ /^([\+\-])?(\d\d?):(\d\d)(?::(\d\d))?$/ ) {
    100          
506 93         528 ( $sign, $hours, $minutes, $seconds ) = ( $1, $2, $3, $4 );
507             }
508             elsif ( $offset =~ /^([\+\-])?(\d\d)(\d\d)(\d\d)?$/ ) {
509 40         204 ( $sign, $hours, $minutes, $seconds ) = ( $1, $2, $3, $4 );
510             }
511             else {
512 137         531 return undef;
513             }
514              
515 133 100       348 $sign = '+' unless defined $sign;
516 133 50 33     741 return undef unless $hours >= 0 && $hours <= 99;
517 133 100 66     544 return undef unless $minutes >= 0 && $minutes <= 59;
518             return undef
519 115 100 66     381 unless !defined($seconds) || ( $seconds >= 0 && $seconds <= 59 );
      100        
520              
521 109         243 my $total = $hours * 3600 + $minutes * 60;
522 109 100       235 $total += $seconds if $seconds;
523 109 100       277 $total *= -1 if $sign eq '-';
524              
525 109         345 return $total;
526             }
527              
528             sub offset_as_string {
529 121     121 1 1137106 my $offset = shift;
530             $offset = shift if try {
531             ## no critic (Variables::RequireInitializationForLocalVars)
532 121     121   3124 local $SIG{__DIE__};
533 121         864 $offset->isa('DateTime::TimeZone');
534 121 100       605 };
535 121   100     1894 my $sep = shift || q{};
536              
537 121 50       285 return undef unless defined $offset;
538 121 100 100     463 return undef unless $offset >= -359999 && $offset <= 359999;
539              
540 119 100       269 my $sign = $offset < 0 ? '-' : '+';
541              
542 119         204 $offset = abs($offset);
543              
544 119         290 my $hours = int( $offset / 3600 );
545 119         220 $offset %= 3600;
546 119         211 my $mins = int( $offset / 60 );
547 119         156 $offset %= 60;
548 119         181 my $secs = int($offset);
549              
550             return (
551 119 100       935 $secs
552             ? sprintf(
553             '%s%02d%s%02d%s%02d', $sign, $hours, $sep, $mins, $sep, $secs
554             )
555             : sprintf( '%s%02d%s%02d', $sign, $hours, $sep, $mins )
556             );
557             }
558              
559             # These methods all operate on data contained in the DateTime/TimeZone/Catalog.pm file.
560              
561             sub all_names {
562             return wantarray
563             ? @DateTime::TimeZone::Catalog::ALL
564 3 100   3 1 1961414 : [@DateTime::TimeZone::Catalog::ALL];
565             }
566              
567             sub categories {
568             return wantarray
569             ? @DateTime::TimeZone::Catalog::CATEGORY_NAMES
570 2 100   2 1 1901 : [@DateTime::TimeZone::Catalog::CATEGORY_NAMES];
571             }
572              
573             sub links {
574             return wantarray
575 6 100   6 1 1181542 ? %DateTime::TimeZone::Catalog::LINKS
576             : {%DateTime::TimeZone::Catalog::LINKS};
577             }
578              
579             sub names_in_category {
580 3 100   3 1 2901 shift if $_[0]->isa('DateTime::TimeZone');
581 3 50       9 return unless exists $DateTime::TimeZone::Catalog::CATEGORIES{ $_[0] };
582              
583             return wantarray
584 2         576 ? @{ $DateTime::TimeZone::Catalog::CATEGORIES{ $_[0] } }
585 3 100       9 : $DateTime::TimeZone::Catalog::CATEGORIES{ $_[0] };
586             }
587              
588             sub countries {
589             wantarray
590 1 50   1 1 659 ? ( sort keys %DateTime::TimeZone::Catalog::ZONES_BY_COUNTRY )
591             : [ sort keys %DateTime::TimeZone::Catalog::ZONES_BY_COUNTRY ];
592             }
593              
594             sub names_in_country {
595 5 100   5 1 7055 shift if $_[0]->isa('DateTime::TimeZone');
596              
597             return
598             unless
599 5 50       17 exists $DateTime::TimeZone::Catalog::ZONES_BY_COUNTRY{ lc $_[0] };
600              
601             return
602             wantarray
603 4         18 ? @{ $DateTime::TimeZone::Catalog::ZONES_BY_COUNTRY{ lc $_[0] } }
604 5 100       14 : $DateTime::TimeZone::Catalog::ZONES_BY_COUNTRY{ lc $_[0] };
605             }
606              
607             1;
608              
609             # ABSTRACT: Time zone object base class and factory
610              
611             __END__
612              
613             =pod
614              
615             =encoding UTF-8
616              
617             =head1 NAME
618              
619             DateTime::TimeZone - Time zone object base class and factory
620              
621             =head1 VERSION
622              
623             version 2.67
624              
625             =head1 SYNOPSIS
626              
627             use DateTime;
628             use DateTime::TimeZone;
629              
630             my $tz = DateTime::TimeZone->new( name => 'America/Chicago' );
631              
632             my $dt = DateTime->now();
633             my $offset = $tz->offset_for_datetime($dt);
634              
635             =head1 DESCRIPTION
636              
637             This class is the base class for all time zone objects. A time zone is
638             represented internally as a set of observances, each of which describes the
639             offset from GMT for a given time period.
640              
641             Note that without the L<DateTime> module, this module does not do much. It's
642             primary interface is through a L<DateTime> object, and most users will not need
643             to directly use C<DateTime::TimeZone> methods.
644              
645             =head2 Special Case Platforms
646              
647             If you are on the Win32 platform, you will want to also install
648             L<DateTime::TimeZone::Local::Win32>. This will enable you to specify a time
649             zone of C<'local'> when creating a L<DateTime> object.
650              
651             If you are on HPUX, install L<DateTime::TimeZone::HPUX>. This provides support
652             for HPUX style time zones like C<'MET-1METDST'>.
653              
654             =head1 USAGE
655              
656             This class has the following methods:
657              
658             =head2 DateTime::TimeZone->new( name => $tz_name )
659              
660             Given a valid time zone name, this method returns a new time zone blessed into
661             the appropriate subclass. Subclasses are named for the given time zone, so
662             that the time zone "America/Chicago" is the
663             DateTime::TimeZone::America::Chicago class.
664              
665             If the name given is a "link" name in the Olson database, the object created
666             may have a different name. For example, there is a link from the old "EST5EDT"
667             name to "America/New_York".
668              
669             When loading a time zone from the Olson database, the constructor checks the
670             version of the loaded class to make sure it matches the version of the current
671             DateTime::TimeZone installation. If they do not match it will issue a warning.
672             This is useful because time zone names may fall out of use, but you may have an
673             old module file installed for that time zone.
674              
675             There are also several special values that can be given as names.
676              
677             If the "name" parameter is "floating", then a C<DateTime::TimeZone::Floating>
678             object is returned. A floating time zone does not have I<any> offset, and is
679             always the same time. This is useful for calendaring applications, which may
680             need to specify that a given event happens at the same I<local> time,
681             regardless of where it occurs. See L<RFC
682             2445|https://www.ietf.org/rfc/rfc2445.txt> for more details.
683              
684             If the "name" parameter is "UTC", then a C<DateTime::TimeZone::UTC> object is
685             returned.
686              
687             If the "name" is an offset string, it is converted to a number, and a
688             C<DateTime::TimeZone::OffsetOnly> object is returned.
689              
690             =head3 The "local" time zone
691              
692             If the "name" parameter is "local", then the module attempts to determine the
693             local time zone for the system.
694              
695             The method for finding the local zone varies by operating system. See the
696             appropriate module for details of how we check for the local time zone.
697              
698             =over 4
699              
700             =item * L<DateTime::TimeZone::Local::Unix>
701              
702             =item * L<DateTime::TimeZone::Local::Android>
703              
704             =item * L<DateTime::TimeZone::Local::hpux>
705              
706             =item * L<DateTime::TimeZone::Local::Win32>
707              
708             =item * L<DateTime::TimeZone::Local::VMS>
709              
710             =back
711              
712             If a local time zone is not found, then an exception will be thrown. This
713             exception will always stringify to something containing the text C<"Cannot
714             determine local time zone">.
715              
716             If you are writing code for users to run on systems you do not control, you
717             should try to account for the possibility that this exception may be thrown.
718             Falling back to UTC might be a reasonable alternative.
719              
720             When writing tests for your modules that might be run on others' systems, you
721             are strongly encouraged to either not use C<local> when creating L<DateTime>
722             objects or to set C<$ENV{TZ}> to a known value in your test code. All of the
723             per-OS classes check this environment variable.
724              
725             =head2 $tz->offset_for_datetime( $dt )
726              
727             Given a C<DateTime> object, this method returns the offset in seconds for the
728             given datetime. This takes into account historical time zone information, as
729             well as Daylight Saving Time. The offset is determined by looking at the
730             object's UTC Rata Die days and seconds.
731              
732             =head2 $tz->offset_for_local_datetime( $dt )
733              
734             Given a C<DateTime> object, this method returns the offset in seconds for the
735             given datetime. Unlike the previous method, this method uses the local time's
736             Rata Die days and seconds. This should only be done when the corresponding UTC
737             time is not yet known, because local times can be ambiguous due to Daylight
738             Saving Time rules.
739              
740             =head2 $tz->is_dst_for_datetime( $dt )
741              
742             Given a C<DateTime> object, this method returns true if the DateTime is
743             currently in Daylight Saving Time.
744              
745             =head2 $tz->name
746              
747             Returns the name of the time zone.
748              
749             =head2 $tz->short_name_for_datetime( $dt )
750              
751             Given a C<DateTime> object, this method returns the "short name" for the
752             current observance and rule this datetime is in. These are names like "EST",
753             "GMT", etc.
754              
755             It is B<strongly> recommended that you do not rely on these names for anything
756             other than display. These names are not official, and many of them are simply
757             the invention of the Olson database maintainers. Moreover, these names are not
758             unique. For example, there is an "EST" at both -0500 and +1000/+1100.
759              
760             =head2 $tz->is_floating
761              
762             Returns a boolean indicating whether or not this object represents a floating
763             time zone, as defined by L<RFC 2445|https://www.ietf.org/rfc/rfc2445.txt>.
764              
765             =head2 $tz->is_utc
766              
767             Indicates whether or not this object represents the UTC (GMT) time zone.
768              
769             =head2 $tz->has_dst_changes
770              
771             Indicates whether or not this zone has I<ever> had a change to and from DST,
772             either in the past or future.
773              
774             =head2 $tz->is_olson
775              
776             Returns true if the time zone is a named time zone from the Olson database.
777              
778             =head2 $tz->category
779              
780             Returns the part of the time zone name before the first slash. For example,
781             the "America/Chicago" time zone would return "America".
782              
783             =head2 DateTime::TimeZone->is_valid_name($name)
784              
785             Given a string, this method returns a boolean value indicating whether or not
786             the string is a valid time zone name. If you are using
787             C<DateTime::TimeZone::Alias>, any aliases you've created will be valid.
788              
789             =head2 DateTime::TimeZone->all_names
790              
791             This returns a pre-sorted list of all the time zone names. This list does not
792             include link names. In scalar context, it returns an array reference, while in
793             list context it returns an array.
794              
795             =head2 DateTime::TimeZone->categories
796              
797             This returns a list of all time zone categories. In scalar context, it returns
798             an array reference, while in list context it returns an array.
799              
800             =head2 DateTime::TimeZone->links
801              
802             This returns a hash of all time zone links, where the keys are the old,
803             deprecated names, and the values are the new names. In scalar context, it
804             returns a hash reference, while in list context it returns a hash.
805              
806             =head2 DateTime::TimeZone->names_in_category( $category )
807              
808             Given a valid category, this method returns a list of the names in that
809             category, without the category portion. So the list for the "America" category
810             would include the strings "Chicago", "Kentucky/Monticello", and "New_York". In
811             scalar context, it returns an array reference, while in list context it returns
812             an array.
813              
814             =head2 DateTime::TimeZone->countries()
815              
816             Returns a sorted list of all the valid country codes (in lower-case) which can
817             be passed to C<names_in_country()>. In scalar context, it returns an array
818             reference, while in list context it returns an array.
819              
820             If you need to convert country codes to names or vice versa you can use
821             C<Locale::Country> to do so. Note that one of the codes returned is "uk", which
822             is an alias for the country code "gb", and is not a valid ISO country code.
823              
824             =head2 DateTime::TimeZone->names_in_country( $country_code )
825              
826             Given a two-letter ISO3166 country code, this method returns a list of time
827             zones used in that country. The country code may be of any case. In scalar
828             context, it returns an array reference, while in list context it returns an
829             array.
830              
831             This list is returned in an order vaguely based on geography and population. In
832             general, the least used zones come last, but there are not guarantees of a
833             specific order from one release to the next. This order is probably the best
834             option for presenting zones names to end users.
835              
836             =head2 DateTime::TimeZone->offset_as_seconds( $offset )
837              
838             Given an offset as a string, this returns the number of seconds represented by
839             the offset as a positive or negative number. Returns C<undef> if $offset is
840             not in the range C<-99:59:59> to C<+99:59:59>.
841              
842             The offset is expected to match either
843             C</^([\+\-])?(\d\d?):(\d\d)(?::(\d\d))?$/> or
844             C</^([\+\-])?(\d\d)(\d\d)(\d\d)?$/>. If it doesn't match either of these,
845             C<undef> will be returned.
846              
847             This means that if you want to specify hours as a single digit, then each
848             element of the offset must be separated by a colon (:).
849              
850             =head2 DateTime::TimeZone->offset_as_string( $offset, $sep )
851              
852             Given an offset as a number, this returns the offset as a string. Returns
853             C<undef> if $offset is not in the range C<-359999> to C<359999>.
854              
855             You can also provide an optional separator which will go between the hours,
856             minutes, and seconds (if applicable) portions of the offset.
857              
858             =head2 Storable Hooks
859              
860             This module provides freeze and thaw hooks for C<Storable> so that the huge
861             data structures for Olson time zones are not actually stored in the serialized
862             structure.
863              
864             If you subclass C<DateTime::TimeZone>, you will inherit its hooks, which may
865             not work for your module, so please test the interaction of your module with
866             Storable.
867              
868             =head1 LOADING TIME ZONES IN A PRE-FORKING SYSTEM
869              
870             If you are running an application that does pre-forking (for example with
871             Starman), then you should try to load all the time zones that you'll need in
872             the parent process. Time zones are loaded on-demand, so loading them once in
873             each child will waste memory that could otherwise be shared.
874              
875             =head1 CREDITS
876              
877             This module was inspired by Jesse Vincent's work on Date::ICal::Timezone, and
878             written with much help from the datetime@perl.org list.
879              
880             =head1 SEE ALSO
881              
882             datetime@perl.org mailing list
883              
884             The tools directory of the DateTime::TimeZone distribution includes two scripts
885             that may be of interest to some people. They are parse_olson and
886             tests_from_zdump. Please run them with the --help flag to see what they can be
887             used for.
888              
889             =head1 SUPPORT
890              
891             Support for this module is provided via the datetime@perl.org email list.
892              
893             Bugs may be submitted at L<https://github.com/houseabsolute/DateTime-TimeZone/issues>.
894              
895             =head1 SOURCE
896              
897             The source code repository for DateTime-TimeZone can be found at L<https://github.com/houseabsolute/DateTime-TimeZone>.
898              
899             =head1 DONATIONS
900              
901             If you'd like to thank me for the work I've done on this module, please
902             consider making a "donation" to me via PayPal. I spend a lot of free time
903             creating free software, and would appreciate any support you'd care to offer.
904              
905             Please note that B<I am not suggesting that you must do this> in order for me
906             to continue working on this particular software. I will continue to do so,
907             inasmuch as I have in the past, for as long as it interests me.
908              
909             Similarly, a donation made in this way will probably not make me work on this
910             software much more, unless I get so many donations that I can consider working
911             on free software full time (let's all have a chuckle at that together).
912              
913             To donate, log into PayPal and send money to autarch@urth.org, or use the
914             button at L<https://houseabsolute.com/foss-donations/>.
915              
916             =head1 AUTHOR
917              
918             Dave Rolsky <autarch@urth.org>
919              
920             =head1 CONTRIBUTORS
921              
922             =for stopwords Alexey Molchanov Alfie John Andrew Paprocki Brian Fraser Bron Gondwana Daisuke Maki David Pinkowitz guillermo.galindo Iain Truskett Jakub Wilk James E Keenan Joshua Hoblitt Karen Etheridge karupanerura kclaggett Matthew Horsfall Mohammad S Anwar Olaf Alders Peter Rabbitson Tom Wyant
923              
924             =over 4
925              
926             =item *
927              
928             Alexey Molchanov <alexey.molchanov@gmail.com>
929              
930             =item *
931              
932             Alfie John <alfiej@fastmail.fm>
933              
934             =item *
935              
936             Andrew Paprocki <apaprocki@bloomberg.net>
937              
938             =item *
939              
940             Brian Fraser <brian.fraser@booking.com>
941              
942             =item *
943              
944             Bron Gondwana <brong@fastmail.fm>
945              
946             =item *
947              
948             Daisuke Maki <dmaki@cpan.org>
949              
950             =item *
951              
952             David Pinkowitz <dave@pinkowitz.com>
953              
954             =item *
955              
956             guillermo.galindo <guillermo.galindo@meteologica.com>
957              
958             =item *
959              
960             Iain Truskett <deceased>
961              
962             =item *
963              
964             Jakub Wilk <jwilk@jwilk.net>
965              
966             =item *
967              
968             James E Keenan <jkeenan@cpan.org>
969              
970             =item *
971              
972             Joshua Hoblitt <jhoblitt@cpan.org>
973              
974             =item *
975              
976             Karen Etheridge <ether@cpan.org>
977              
978             =item *
979              
980             karupanerura <karupa@cpan.org>
981              
982             =item *
983              
984             kclaggett <kclaggett@proofpoint.com>
985              
986             =item *
987              
988             Matthew Horsfall <wolfsage@gmail.com>
989              
990             =item *
991              
992             Mohammad S Anwar <mohammad.anwar@yahoo.com>
993              
994             =item *
995              
996             Olaf Alders <olaf@wundersolutions.com>
997              
998             =item *
999              
1000             Peter Rabbitson <ribasushi@cpan.org>
1001              
1002             =item *
1003              
1004             Tom Wyant <wyant@cpan.org>
1005              
1006             =back
1007              
1008             =head1 COPYRIGHT AND LICENSE
1009              
1010             This software is copyright (c) 2026 by Dave Rolsky.
1011              
1012             This is free software; you can redistribute it and/or modify it under
1013             the same terms as the Perl 5 programming language system itself.
1014              
1015             The full text of the license can be found in the
1016             F<LICENSE> file included with this distribution.
1017              
1018             =cut