File Coverage

blib/lib/DateTime/TimeZone.pm
Criterion Covered Total %
statement 287 295 97.2
branch 111 130 85.3
condition 38 51 74.5
subroutine 58 59 98.3
pod 20 23 86.9
total 514 558 92.1


line stmt bran cond sub pod time code
1             package DateTime::TimeZone;
2              
3 20     20   8745387 use 5.008004;
  20         85  
4              
5 20     20   118 use strict;
  20         41  
  20         577  
6 20     20   119 use warnings;
  20         44  
  20         3534  
7 20     20   1101 use namespace::autoclean;
  20         45693  
  20         166  
8              
9             our $VERSION = '2.66';
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   16621 use DateTime::TimeZone::Catalog;
  20         134  
  20         1424  
15 20     20   10082 use DateTime::TimeZone::Floating;
  20         154  
  20         962  
16 20     20   10984 use DateTime::TimeZone::Local;
  20         67  
  20         955  
17 20     20   140 use DateTime::TimeZone::OffsetOnly;
  20         56  
  20         569  
18 20     20   9451 use DateTime::TimeZone::OlsonDB::Change;
  20         63  
  20         990  
19 20     20   680 use DateTime::TimeZone::UTC;
  20         37  
  20         686  
20 20     20   97 use Module::Runtime qw( require_module );
  20         38  
  20         126  
21 20     20   1893 use Params::ValidationCompiler 0.13 qw( validation_for );
  20         36532  
  20         1300  
22 20     20   672 use Specio::Library::Builtins;
  20         99122  
  20         316  
23 20     20   195402 use Specio::Library::String;
  20         13296  
  20         189  
24 20     20   44009 use Try::Tiny;
  20         68  
  20         2140  
25              
26             ## no critic (ValuesAndExpressions::ProhibitConstantPragma)
27 20     20   182 use constant INFINITY => 100**1000;
  20         63  
  20         2036  
28 20     20   174 use constant NEG_INFINITY => -1 * ( 100**1000 );
  20         44  
  20         1191  
29              
30             # the offsets for each span element
31 20     20   113 use constant UTC_START => 0;
  20         37  
  20         1217  
32 20     20   141 use constant UTC_END => 1;
  20         36  
  20         1023  
33 20     20   102 use constant LOCAL_START => 2;
  20         51  
  20         1392  
34 20     20   203 use constant LOCAL_END => 3;
  20         198  
  20         1082  
35 20     20   126 use constant OFFSET => 4;
  20         92  
  20         1287  
36 20     20   207 use constant IS_DST => 5;
  20         76  
  20         979  
37 20     20   100 use constant SHORT_NAME => 6;
  20         35  
  20         89077  
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 4821     4821 1 16566770 shift;
55 4821         126780 my %p = $validator->(@_);
56              
57 4820 100       143214 if ( exists $DateTime::TimeZone::Catalog::LINKS{ $p{name} } ) {
    50          
58 541         2090 $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 4820 100 66     29256 unless ( $p{name} =~ m{/}
65             || $SpecialName{ $p{name} } ) {
66 3606 100       8045 if ( $p{name} eq 'floating' ) {
67 1040         3799 return DateTime::TimeZone::Floating->instance;
68             }
69              
70 2566 100       6780 if ( $p{name} eq 'local' ) {
71 2         31 return DateTime::TimeZone::Local->TimeZone();
72             }
73              
74 2564 100 100     6958 if ( $p{name} eq 'UTC' || $p{name} eq 'Z' ) {
75 2552         9915 return DateTime::TimeZone::UTC->instance;
76             }
77              
78 12         131 return DateTime::TimeZone::OffsetOnly->new( offset => $p{name} );
79             }
80              
81 1214 100       6714 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       246 my $sign = $1 eq '-' ? '+' : '-';
86 61         147 my $hours = $2;
87 61 100       232 die "The timezone '$p{name}' is an invalid name.\n"
88             unless $hours <= 14;
89 58         366 return DateTime::TimeZone::OffsetOnly->new(
90             offset => "${sign}${hours}:00" );
91             }
92              
93 1153         3049 my $subclass = $p{name};
94 1153         6009 $subclass =~ s{/}{::}g;
95 1153         3057 $subclass =~ s/-(\d)/_Minus$1/;
96 1153         3849 $subclass =~ s/\+/_Plus/;
97 1153         2592 $subclass =~ s/-/_/g;
98              
99 1153         2951 my $real_class = "DateTime::TimeZone::$subclass";
100              
101 1153 100       9382 die "The timezone '$p{name}' is an invalid name.\n"
102             unless $real_class =~ /^\w+(::\w+)*$/;
103              
104 1152 100       15066 unless ( $real_class->can('instance') ) {
105 568         4045 ($real_class)
106             = $real_class =~ m{\A([a-zA-Z0-9_]+(?:::[a-zA-Z0-9_]+)*)\z};
107              
108 568         1300 my $e;
109             try {
110             ## no critic (Variables::RequireInitializationForLocalVars)
111 568     568   43694 local $SIG{__DIE__};
112 568         3738 require_module($real_class);
113             }
114             catch {
115 9     9   2543 $e = $_;
116 568         7095 };
117              
118 568 100       18375 if ($e) {
119 9         66 my $regex = join '.', split /::/, $real_class;
120 9         30 $regex .= '\\.pm';
121              
122 9 50       445 if ( $e =~ /^Can't locate $regex/i ) {
123 9         119 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         10448 my $zone = $real_class->instance( name => $p{name}, is_olson => 1 );
133              
134 1143 50       12898 if ( $zone->is_olson() ) {
135 1143 100       9789 my $object_version
136             = $zone->can('olson_version')
137             ? $zone->olson_version()
138             : 'unknown';
139 1143         8540 my $catalog_version = DateTime::TimeZone::Catalog->OlsonVersion();
140              
141 1143 100       4174 if ( $object_version ne $catalog_version ) {
142 2         154 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         5583 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   1666 my $class = shift;
172 559         22077 my %p = $validator->(@_);
173              
174             my $self = bless {
175             name => $p{name},
176             spans => $p{spans},
177             is_olson => $p{is_olson},
178 559         38338 }, $class;
179              
180 559         2063 foreach my $k (qw( last_offset last_observance rules max_year )) {
181 2236         4617 my $m = "_$k";
182 2236 100       15736 $self->{$k} = $self->$m() if $self->can($m);
183             }
184              
185 559         3762 return $self;
186             }
187             ## use critic
188             }
189              
190 1142     1142 1 5076 sub is_olson { $_[0]->{is_olson} }
191              
192             sub is_dst_for_datetime {
193 7     7 1 489 my $self = shift;
194              
195 7         35 my $span = $self->_span_for_datetime( 'utc', $_[0] );
196              
197 7         56 return $span->[IS_DST];
198             }
199              
200             sub offset_for_datetime {
201 187     187 1 6948 my $self = shift;
202              
203 187         563 my $span = $self->_span_for_datetime( 'utc', $_[0] );
204              
205 187         724 return $span->[OFFSET];
206             }
207              
208             sub offset_for_local_datetime {
209 62     62 1 617 my $self = shift;
210              
211 62         228 my $span = $self->_span_for_datetime( 'local', $_[0] );
212              
213 58         227 return $span->[OFFSET];
214             }
215              
216             sub short_name_for_datetime {
217 52     52 1 5971 my $self = shift;
218              
219 52         226 my $span = $self->_span_for_datetime( 'utc', $_[0] );
220              
221 52         360 return $span->[SHORT_NAME];
222             }
223              
224             sub _span_for_datetime {
225 308     308   523 my $self = shift;
226 308         503 my $type = shift;
227 308         457 my $dt = shift;
228              
229 308         623 my $method = $type . '_rd_as_seconds';
230              
231 308 100       838 my $end = $type eq 'utc' ? UTC_END : LOCAL_END;
232              
233 308         525 my $span;
234 308         1131 my $seconds = $dt->$method();
235 308 100       1818 if ( $seconds < $self->max_span->[$end] ) {
236 299         962 $span = $self->_spans_binary_search( $type, $seconds );
237             }
238             else {
239 9         34 my $until_year = $dt->utc_year + 1;
240 9         76 $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       752 unless ( defined $span ) {
249 4         13 my $err = 'Invalid local time for date';
250 4 50       15 $err .= q{ } . $dt->iso8601 if $type eq 'utc';
251 4         18 $err .= ' in time zone: ' . $self->name;
252 4         10 $err .= "\n";
253              
254 4         77 die $err;
255             }
256              
257 304         669 return $span;
258             }
259              
260             sub _spans_binary_search {
261 299     299   464 my $self = shift;
262 299         643 my ( $type, $seconds ) = @_;
263              
264 299         745 my ( $start, $end ) = _keys_for_type($type);
265              
266 299         519 my $min = 0;
267 299         452 my $max = scalar @{ $self->{spans} } + 1;
  299         769  
268 299         838 my $i = int( $max / 2 );
269              
270             # special case for when there are only 2 spans
271 299 100 100     1540 $i++ if $max % 2 && $max != 3;
272              
273 299 50       513 $i = 0 if @{ $self->{spans} } == 1;
  299         836  
274              
275 299         587 while (1) {
276 1914         3371 my $current = $self->{spans}[$i];
277              
278 1914 100       4099 if ( $seconds < $current->[$start] ) {
    100          
279 439         712 $max = $i;
280 439         825 my $c = int( ( $i - $min ) / 2 );
281 439   100     988 $c ||= 1;
282              
283 439         601 $i -= $c;
284              
285 439 50       955 return if $i < $min;
286             }
287             elsif ( $seconds >= $current->[$end] ) {
288 1179         1782 $min = $i;
289 1179         2566 my $c = int( ( $max - $i ) / 2 );
290 1179   100     2186 $c ||= 1;
291              
292 1179         1810 $i += $c;
293              
294 1179 100       2360 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     1065 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 21 50       90 return $current if $current->[UTC_END] == INFINITY;
306              
307 21         61 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 21   66     100 $next ||= $self->_generate_next_span;
314              
315 21 50       67 die "No next span in $self->{max_year}" unless defined $next;
316              
317 21 50 33     141 if ( ( !$next->[IS_DST] )
      33        
318             && $next->[$start] <= $seconds
319             && $seconds <= $next->[$end] ) {
320 0         0 return $next;
321             }
322             }
323              
324 296         976 return $current;
325             }
326             }
327             }
328              
329             sub _generate_next_span {
330 1     1   3 my $self = shift;
331              
332 1         3 my $last_idx = $#{ $self->{spans} };
  1         4  
333              
334 1         4 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         8 $self->{max_year} + 2,
343             $max_span->[UTC_END] + ( 366 * 86400 ), 'utc'
344             );
345              
346 1         7 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         21 my $seconds = shift;
353 10         24 my $type = shift;
354              
355 10         18 my @changes;
356 10         17 my @rules = @{ $self->_rules };
  10         44  
357 10         42 foreach my $year ( $self->{max_year} .. $generate_until_year ) {
358             ## no critic (ControlStructures::ProhibitCStyleForLoops)
359 246         767 for ( my $x = 0; $x < @rules; $x++ ) {
360 492         1022 my $last_offset_from_std;
361              
362 492 50       1295 if ( @rules == 2 ) {
    0          
363 492 100       1801 $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 492         1224 my $rule = $rules[$x];
378              
379             my $next = $rule->utc_start_datetime_for_year(
380             $year,
381 492         1908 $self->{last_offset}, $last_offset_from_std
382             );
383              
384             # don't bother with changes we've seen already
385 492 100       1981 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 470         3153 rule => $rule,
399             );
400             }
401             }
402              
403 10         35 $self->{max_year} = $generate_until_year;
404              
405             my @sorted
406 10         107 = sort { $a->utc_start_datetime <=> $b->utc_start_datetime } @changes;
  1560         24574  
407              
408 10         299 my ( $start, $end ) = _keys_for_type($type);
409              
410 10         23 my $match;
411             ## no critic (ControlStructures::ProhibitCStyleForLoops)
412 10         50 for ( my $x = 1; $x < @sorted; $x++ ) {
413 460         870 my $span = DateTime::TimeZone::OlsonDB::Change::two_changes_as_span(
414             @sorted[ $x - 1, $x ] );
415              
416 460         664 $span = _span_as_array($span);
417              
418 460         552 push @{ $self->{spans} }, $span;
  460         793  
419              
420 460 100 100     1650 $match = $span
421             if $seconds >= $span->[$start] && $seconds < $span->[$end];
422             }
423              
424 10         2489 return $match;
425             }
426              
427 801     801 0 5947 sub max_span { $_[0]->{spans}[-1] }
428              
429             sub _keys_for_type {
430 309 100   309   1027 $_[0] eq 'utc' ? ( UTC_START, UTC_END ) : ( LOCAL_START, LOCAL_END );
431             }
432              
433             sub _span_as_array {
434             [
435 460         1526 @{ $_[0] }{
436 460     460   569 qw( utc_start utc_end local_start local_end offset is_dst short_name )
437             }
438             ];
439             }
440              
441 5825     5825 1 238189 sub is_floating {0}
442              
443 444     444 1 5988 sub is_utc {0}
444              
445 2     2 1 33 sub has_dst_changes {0}
446              
447 668     668 1 388163 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 1297774 my $class = shift;
452 13         37 my $name = shift;
453              
454             my $tz = try {
455             ## no critic (Variables::RequireInitializationForLocalVars)
456 13     13   456 local $SIG{__DIE__};
457 13         54 $class->new( name => $name );
458 13         108 };
459              
460 13 100 66     1541 return $tz && $tz->isa('DateTime::TimeZone') ? 1 : 0;
461             }
462              
463             sub STORABLE_freeze {
464 5     5 0 1080 my $self = shift;
465              
466 5         20 return $self->name;
467             }
468              
469             sub STORABLE_thaw {
470 4     4 0 116 my $self = shift;
471 4         8 shift;
472 4         10 my $serialized = shift;
473              
474 4   33     16 my $class = ref $self || $self;
475              
476 4         7 my $obj;
477 4 50       28 if ( $class->isa(__PACKAGE__) ) {
478 4         18 $obj = __PACKAGE__->new( name => $serialized );
479             }
480             else {
481 0         0 $obj = $class->new( name => $serialized );
482             }
483              
484 4         47 %$self = %$obj;
485              
486 4         35 return $self;
487             }
488              
489             #
490             # Functions
491             #
492             sub offset_as_seconds {
493 273     273 1 10712 my $offset = shift;
494             $offset = shift if try {
495             ## no critic (Variables::RequireInitializationForLocalVars)
496 273     273   8735 local $SIG{__DIE__};
497 273         3034 $offset->isa('DateTime::TimeZone');
498 273 100       1855 };
499              
500 273 50       5104 return undef unless defined $offset;
501              
502 273 100       849 return 0 if $offset eq '0';
503              
504 270         568 my ( $sign, $hours, $minutes, $seconds );
505 270 100       2332 if ( $offset =~ /^([\+\-])?(\d\d?):(\d\d)(?::(\d\d))?$/ ) {
    100          
506 93         548 ( $sign, $hours, $minutes, $seconds ) = ( $1, $2, $3, $4 );
507             }
508             elsif ( $offset =~ /^([\+\-])?(\d\d)(\d\d)(\d\d)?$/ ) {
509 40         343 ( $sign, $hours, $minutes, $seconds ) = ( $1, $2, $3, $4 );
510             }
511             else {
512 137         491 return undef;
513             }
514              
515 133 100       415 $sign = '+' unless defined $sign;
516 133 50 33     775 return undef unless $hours >= 0 && $hours <= 99;
517 133 100 66     606 return undef unless $minutes >= 0 && $minutes <= 59;
518             return undef
519 115 100 66     423 unless !defined($seconds) || ( $seconds >= 0 && $seconds <= 59 );
      100        
520              
521 109         239 my $total = $hours * 3600 + $minutes * 60;
522 109 100       286 $total += $seconds if $seconds;
523 109 100       301 $total *= -1 if $sign eq '-';
524              
525 109         399 return $total;
526             }
527              
528             sub offset_as_string {
529 121     121 1 977547 my $offset = shift;
530             $offset = shift if try {
531             ## no critic (Variables::RequireInitializationForLocalVars)
532 121     121   3828 local $SIG{__DIE__};
533 121         1050 $offset->isa('DateTime::TimeZone');
534 121 100       808 };
535 121   100     2266 my $sep = shift || q{};
536              
537 121 50       311 return undef unless defined $offset;
538 121 100 100     552 return undef unless $offset >= -359999 && $offset <= 359999;
539              
540 119 100       320 my $sign = $offset < 0 ? '-' : '+';
541              
542 119         226 $offset = abs($offset);
543              
544 119         315 my $hours = int( $offset / 3600 );
545 119         229 $offset %= 3600;
546 119         287 my $mins = int( $offset / 60 );
547 119         204 $offset %= 60;
548 119         242 my $secs = int($offset);
549              
550             return (
551 119 100       1119 $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 2621314 : [@DateTime::TimeZone::Catalog::ALL];
565             }
566              
567             sub categories {
568             return wantarray
569             ? @DateTime::TimeZone::Catalog::CATEGORY_NAMES
570 2 100   2 1 4172 : [@DateTime::TimeZone::Catalog::CATEGORY_NAMES];
571             }
572              
573             sub links {
574             return wantarray
575 6 100   6 1 1109404 ? %DateTime::TimeZone::Catalog::LINKS
576             : {%DateTime::TimeZone::Catalog::LINKS};
577             }
578              
579             sub names_in_category {
580 3 100   3 1 4222 shift if $_[0]->isa('DateTime::TimeZone');
581 3 50       14 return unless exists $DateTime::TimeZone::Catalog::CATEGORIES{ $_[0] };
582              
583             return wantarray
584 2         40 ? @{ $DateTime::TimeZone::Catalog::CATEGORIES{ $_[0] } }
585 3 100       11 : $DateTime::TimeZone::Catalog::CATEGORIES{ $_[0] };
586             }
587              
588             sub countries {
589             wantarray
590 1 50   1 1 1368 ? ( 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 7908 shift if $_[0]->isa('DateTime::TimeZone');
596              
597             return
598             unless
599 5 50       25 exists $DateTime::TimeZone::Catalog::ZONES_BY_COUNTRY{ lc $_[0] };
600              
601             return
602             wantarray
603 4         32 ? @{ $DateTime::TimeZone::Catalog::ZONES_BY_COUNTRY{ lc $_[0] } }
604 5 100       17 : $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.66
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) 2025 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