File Coverage

blib/lib/DateTime/TimeZone.pm
Criterion Covered Total %
statement 123 295 41.6
branch 46 130 35.3
condition 11 51 21.5
subroutine 36 59 61.0
pod 20 23 86.9
total 236 558 42.2


line stmt bran cond sub pod time code
1             package DateTime::TimeZone;
2              
3 3     3   104834 use 5.008004;
  3         27  
4              
5 3     3   18 use strict;
  3         6  
  3         57  
6 3     3   14 use warnings;
  3         10  
  3         103  
7 3     3   999 use namespace::autoclean;
  3         35920  
  3         12  
8              
9             our $VERSION = '2.58';
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 3     3   2319 use DateTime::TimeZone::Catalog;
  3         10  
  3         144  
15 3     3   1268 use DateTime::TimeZone::Floating;
  3         9  
  3         115  
16 3     3   1366 use DateTime::TimeZone::Local;
  3         7  
  3         95  
17 3     3   24 use DateTime::TimeZone::OffsetOnly;
  3         6  
  3         65  
18 3     3   1304 use DateTime::TimeZone::OlsonDB::Change;
  3         8  
  3         90  
19 3     3   405 use DateTime::TimeZone::UTC;
  3         8  
  3         80  
20 3     3   16 use Module::Runtime qw( require_module );
  3         6  
  3         18  
21 3     3   622 use Params::ValidationCompiler 0.13 qw( validation_for );
  3         25937  
  3         158  
22 3     3   519 use Specio::Library::Builtins;
  3         91054  
  3         27  
23 3     3   29825 use Specio::Library::String;
  3         10880  
  3         20  
24 3     3   6859 use Try::Tiny;
  3         6  
  3         263  
25              
26             ## no critic (ValuesAndExpressions::ProhibitConstantPragma)
27 3     3   20 use constant INFINITY => 100**1000;
  3         6  
  3         299  
28 3     3   22 use constant NEG_INFINITY => -1 * ( 100**1000 );
  3         7  
  3         186  
29              
30             # the offsets for each span element
31 3     3   20 use constant UTC_START => 0;
  3         7  
  3         192  
32 3     3   22 use constant UTC_END => 1;
  3         13  
  3         176  
33 3     3   18 use constant LOCAL_START => 2;
  3         6  
  3         147  
34 3     3   17 use constant LOCAL_END => 3;
  3         6  
  3         210  
35 3     3   19 use constant OFFSET => 4;
  3         5  
  3         154  
36 3     3   17 use constant IS_DST => 5;
  3         12  
  3         159  
37 3     3   18 use constant SHORT_NAME => 6;
  3         5  
  3         11155  
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 1     1 1 181 shift;
55 1         24 my %p = $validator->(@_);
56              
57 1 50       44 if ( exists $DateTime::TimeZone::Catalog::LINKS{ $p{name} } ) {
    50          
58 0         0 $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 1 50 33     10 unless ( $p{name} =~ m{/}
65             || $SpecialName{ $p{name} } ) {
66 1 50       4 if ( $p{name} eq 'floating' ) {
67 0         0 return DateTime::TimeZone::Floating->instance;
68             }
69              
70 1 50       5 if ( $p{name} eq 'local' ) {
71 1         8 return DateTime::TimeZone::Local->TimeZone();
72             }
73              
74 0 0 0     0 if ( $p{name} eq 'UTC' || $p{name} eq 'Z' ) {
75 0         0 return DateTime::TimeZone::UTC->instance;
76             }
77              
78 0         0 return DateTime::TimeZone::OffsetOnly->new( offset => $p{name} );
79             }
80              
81 0 0       0 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 0 0       0 my $sign = $1 eq '-' ? '+' : '-';
86 0         0 my $hours = $2;
87 0 0       0 die "The timezone '$p{name}' is an invalid name.\n"
88             unless $hours <= 14;
89 0         0 return DateTime::TimeZone::OffsetOnly->new(
90             offset => "${sign}${hours}:00" );
91             }
92              
93 0         0 my $subclass = $p{name};
94 0         0 $subclass =~ s{/}{::}g;
95 0         0 $subclass =~ s/-(\d)/_Minus$1/;
96 0         0 $subclass =~ s/\+/_Plus/;
97 0         0 $subclass =~ s/-/_/g;
98              
99 0         0 my $real_class = "DateTime::TimeZone::$subclass";
100              
101 0 0       0 die "The timezone '$p{name}' is an invalid name.\n"
102             unless $real_class =~ /^\w+(::\w+)*$/;
103              
104 0 0       0 unless ( $real_class->can('instance') ) {
105 0         0 ($real_class)
106             = $real_class =~ m{\A([a-zA-Z0-9_]+(?:::[a-zA-Z0-9_]+)*)\z};
107              
108 0         0 my $e;
109             try {
110             ## no critic (Variables::RequireInitializationForLocalVars)
111 0     0   0 local $SIG{__DIE__};
112 0         0 require_module($real_class);
113             }
114             catch {
115 0     0   0 $e = $_;
116 0         0 };
117              
118 0 0       0 if ($e) {
119 0         0 my $regex = join '.', split /::/, $real_class;
120 0         0 $regex .= '\\.pm';
121              
122 0 0       0 if ( $e =~ /^Can't locate $regex/i ) {
123 0         0 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 0         0 my $zone = $real_class->instance( name => $p{name}, is_olson => 1 );
133              
134 0 0       0 if ( $zone->is_olson() ) {
135 0 0       0 my $object_version
136             = $zone->can('olson_version')
137             ? $zone->olson_version()
138             : 'unknown';
139 0         0 my $catalog_version = DateTime::TimeZone::Catalog->OlsonVersion();
140              
141 0 0       0 if ( $object_version ne $catalog_version ) {
142 0         0 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 0         0 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 0     0   0 my $class = shift;
172 0         0 my %p = $validator->(@_);
173              
174             my $self = bless {
175             name => $p{name},
176             spans => $p{spans},
177             is_olson => $p{is_olson},
178 0         0 }, $class;
179              
180 0         0 foreach my $k (qw( last_offset last_observance rules max_year )) {
181 0         0 my $m = "_$k";
182 0 0       0 $self->{$k} = $self->$m() if $self->can($m);
183             }
184              
185 0         0 return $self;
186             }
187             ## use critic
188             }
189              
190 0     0 1 0 sub is_olson { $_[0]->{is_olson} }
191              
192             sub is_dst_for_datetime {
193 0     0 1 0 my $self = shift;
194              
195 0         0 my $span = $self->_span_for_datetime( 'utc', $_[0] );
196              
197 0         0 return $span->[IS_DST];
198             }
199              
200             sub offset_for_datetime {
201 0     0 1 0 my $self = shift;
202              
203 0         0 my $span = $self->_span_for_datetime( 'utc', $_[0] );
204              
205 0         0 return $span->[OFFSET];
206             }
207              
208             sub offset_for_local_datetime {
209 0     0 1 0 my $self = shift;
210              
211 0         0 my $span = $self->_span_for_datetime( 'local', $_[0] );
212              
213 0         0 return $span->[OFFSET];
214             }
215              
216             sub short_name_for_datetime {
217 0     0 1 0 my $self = shift;
218              
219 0         0 my $span = $self->_span_for_datetime( 'utc', $_[0] );
220              
221 0         0 return $span->[SHORT_NAME];
222             }
223              
224             sub _span_for_datetime {
225 0     0   0 my $self = shift;
226 0         0 my $type = shift;
227 0         0 my $dt = shift;
228              
229 0         0 my $method = $type . '_rd_as_seconds';
230              
231 0 0       0 my $end = $type eq 'utc' ? UTC_END : LOCAL_END;
232              
233 0         0 my $span;
234 0         0 my $seconds = $dt->$method();
235 0 0       0 if ( $seconds < $self->max_span->[$end] ) {
236 0         0 $span = $self->_spans_binary_search( $type, $seconds );
237             }
238             else {
239 0         0 my $until_year = $dt->utc_year + 1;
240 0         0 $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 0 0       0 unless ( defined $span ) {
249 0         0 my $err = 'Invalid local time for date';
250 0 0       0 $err .= q{ } . $dt->iso8601 if $type eq 'utc';
251 0         0 $err .= ' in time zone: ' . $self->name;
252 0         0 $err .= "\n";
253              
254 0         0 die $err;
255             }
256              
257 0         0 return $span;
258             }
259              
260             sub _spans_binary_search {
261 0     0   0 my $self = shift;
262 0         0 my ( $type, $seconds ) = @_;
263              
264 0         0 my ( $start, $end ) = _keys_for_type($type);
265              
266 0         0 my $min = 0;
267 0         0 my $max = scalar @{ $self->{spans} } + 1;
  0         0  
268 0         0 my $i = int( $max / 2 );
269              
270             # special case for when there are only 2 spans
271 0 0 0     0 $i++ if $max % 2 && $max != 3;
272              
273 0 0       0 $i = 0 if @{ $self->{spans} } == 1;
  0         0  
274              
275 0         0 while (1) {
276 0         0 my $current = $self->{spans}[$i];
277              
278 0 0       0 if ( $seconds < $current->[$start] ) {
    0          
279 0         0 $max = $i;
280 0         0 my $c = int( ( $i - $min ) / 2 );
281 0   0     0 $c ||= 1;
282              
283 0         0 $i -= $c;
284              
285 0 0       0 return if $i < $min;
286             }
287             elsif ( $seconds >= $current->[$end] ) {
288 0         0 $min = $i;
289 0         0 my $c = int( ( $max - $i ) / 2 );
290 0   0     0 $c ||= 1;
291              
292 0         0 $i += $c;
293              
294 0 0       0 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 0 0 0     0 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 0 0       0 return $current if $current->[UTC_END] == INFINITY;
306              
307 0         0 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 0   0     0 $next ||= $self->_generate_next_span;
314              
315 0 0       0 die "No next span in $self->{max_year}" unless defined $next;
316              
317 0 0 0     0 if ( ( !$next->[IS_DST] )
      0        
318             && $next->[$start] <= $seconds
319             && $seconds <= $next->[$end] ) {
320 0         0 return $next;
321             }
322             }
323              
324 0         0 return $current;
325             }
326             }
327             }
328              
329             sub _generate_next_span {
330 0     0   0 my $self = shift;
331              
332 0         0 my $last_idx = $#{ $self->{spans} };
  0         0  
333              
334 0         0 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 0         0 $self->{max_year} + 2,
343             $max_span->[UTC_END] + ( 366 * 86400 ), 'utc'
344             );
345              
346 0         0 return $self->{spans}[ $last_idx + 1 ];
347             }
348              
349             sub _generate_spans_until_match {
350 0     0   0 my $self = shift;
351 0         0 my $generate_until_year = shift;
352 0         0 my $seconds = shift;
353 0         0 my $type = shift;
354              
355 0         0 my @changes;
356 0         0 my @rules = @{ $self->_rules };
  0         0  
357 0         0 foreach my $year ( $self->{max_year} .. $generate_until_year ) {
358             ## no critic (ControlStructures::ProhibitCStyleForLoops)
359 0         0 for ( my $x = 0; $x < @rules; $x++ ) {
360 0         0 my $last_offset_from_std;
361              
362 0 0       0 if ( @rules == 2 ) {
    0          
363 0 0       0 $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 0         0 my $rule = $rules[$x];
378              
379             my $next = $rule->utc_start_datetime_for_year(
380             $year,
381 0         0 $self->{last_offset}, $last_offset_from_std
382             );
383              
384             # don't bother with changes we've seen already
385 0 0       0 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 ),
397             observance => $self->{last_observance},
398 0         0 rule => $rule,
399             );
400             }
401             }
402              
403 0         0 $self->{max_year} = $generate_until_year;
404              
405             my @sorted
406 0         0 = sort { $a->utc_start_datetime <=> $b->utc_start_datetime } @changes;
  0         0  
407              
408 0         0 my ( $start, $end ) = _keys_for_type($type);
409              
410 0         0 my $match;
411             ## no critic (ControlStructures::ProhibitCStyleForLoops)
412 0         0 for ( my $x = 1; $x < @sorted; $x++ ) {
413 0         0 my $span = DateTime::TimeZone::OlsonDB::Change::two_changes_as_span(
414             @sorted[ $x - 1, $x ] );
415              
416 0         0 $span = _span_as_array($span);
417              
418 0         0 push @{ $self->{spans} }, $span;
  0         0  
419              
420 0 0 0     0 $match = $span
421             if $seconds >= $span->[$start] && $seconds < $span->[$end];
422             }
423              
424 0         0 return $match;
425             }
426              
427 0     0 0 0 sub max_span { $_[0]->{spans}[-1] }
428              
429             sub _keys_for_type {
430 0 0   0   0 $_[0] eq 'utc' ? ( UTC_START, UTC_END ) : ( LOCAL_START, LOCAL_END );
431             }
432              
433             sub _span_as_array {
434             [
435 0         0 @{ $_[0] }{
436 0     0   0 qw( utc_start utc_end local_start local_end offset is_dst short_name )
437             }
438             ];
439             }
440              
441 0     0 1 0 sub is_floating {0}
442              
443 0     0 1 0 sub is_utc {0}
444              
445 0     0 1 0 sub has_dst_changes {0}
446              
447 1     1 1 1189 sub name { $_[0]->{name} }
448 0     0 1 0 sub category { ( split /\//, $_[0]->{name}, 2 )[0] }
449              
450             sub is_valid_name {
451 0     0 1 0 my $class = shift;
452 0         0 my $name = shift;
453              
454             my $tz = try {
455             ## no critic (Variables::RequireInitializationForLocalVars)
456 0     0   0 local $SIG{__DIE__};
457 0         0 $class->new( name => $name );
458 0         0 };
459              
460 0 0 0     0 return $tz && $tz->isa('DateTime::TimeZone') ? 1 : 0;
461             }
462              
463             sub STORABLE_freeze {
464 0     0 0 0 my $self = shift;
465              
466 0         0 return $self->name;
467             }
468              
469             sub STORABLE_thaw {
470 0     0 0 0 my $self = shift;
471 0         0 shift;
472 0         0 my $serialized = shift;
473              
474 0   0     0 my $class = ref $self || $self;
475              
476 0         0 my $obj;
477 0 0       0 if ( $class->isa(__PACKAGE__) ) {
478 0         0 $obj = __PACKAGE__->new( name => $serialized );
479             }
480             else {
481 0         0 $obj = $class->new( name => $serialized );
482             }
483              
484 0         0 %$self = %$obj;
485              
486 0         0 return $self;
487             }
488              
489             #
490             # Functions
491             #
492             sub offset_as_seconds {
493 20     20 1 5628 my $offset = shift;
494             $offset = shift if try {
495             ## no critic (Variables::RequireInitializationForLocalVars)
496 20     20   695 local $SIG{__DIE__};
497 20         172 $offset->isa('DateTime::TimeZone');
498 20 100       98 };
499              
500 20 50       276 return undef unless defined $offset;
501              
502 20 50       51 return 0 if $offset eq '0';
503              
504 20         34 my ( $sign, $hours, $minutes, $seconds );
505 20 50       143 if ( $offset =~ /^([\+\-])?(\d\d?):(\d\d)(?::(\d\d))?$/ ) {
    50          
506 0         0 ( $sign, $hours, $minutes, $seconds ) = ( $1, $2, $3, $4 );
507             }
508             elsif ( $offset =~ /^([\+\-])?(\d\d)(\d\d)(\d\d)?$/ ) {
509 20         91 ( $sign, $hours, $minutes, $seconds ) = ( $1, $2, $3, $4 );
510             }
511             else {
512 0         0 return undef;
513             }
514              
515 20 100       50 $sign = '+' unless defined $sign;
516 20 50 33     105 return undef unless $hours >= 0 && $hours <= 99;
517 20 50 33     67 return undef unless $minutes >= 0 && $minutes <= 59;
518             return undef
519 20 50 33     53 unless !defined($seconds) || ( $seconds >= 0 && $seconds <= 59 );
      66        
520              
521 20         46 my $total = $hours * 3600 + $minutes * 60;
522 20 100       41 $total += $seconds if $seconds;
523 20 100       45 $total *= -1 if $sign eq '-';
524              
525 20         62 return $total;
526             }
527              
528             sub offset_as_string {
529 40     40 1 14289 my $offset = shift;
530             $offset = shift if try {
531             ## no critic (Variables::RequireInitializationForLocalVars)
532 40     40   1591 local $SIG{__DIE__};
533 40         313 $offset->isa('DateTime::TimeZone');
534 40 100       208 };
535 40   100     769 my $sep = shift || q{};
536              
537 40 50       92 return undef unless defined $offset;
538 40 100 100     186 return undef unless $offset >= -359999 && $offset <= 359999;
539              
540 38 100       88 my $sign = $offset < 0 ? '-' : '+';
541              
542 38         59 $offset = abs($offset);
543              
544 38         84 my $hours = int( $offset / 3600 );
545 38         62 $offset %= 3600;
546 38         52 my $mins = int( $offset / 60 );
547 38         55 $offset %= 60;
548 38         55 my $secs = int($offset);
549              
550             return (
551 38 100       286 $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 2 100   2 1 1955 : [@DateTime::TimeZone::Catalog::ALL];
565             }
566              
567             sub categories {
568             return wantarray
569             ? @DateTime::TimeZone::Catalog::CATEGORY_NAMES
570 2 100   2 1 4271 : [@DateTime::TimeZone::Catalog::CATEGORY_NAMES];
571             }
572              
573             sub links {
574             return wantarray
575 2 100   2 1 2223 ? %DateTime::TimeZone::Catalog::LINKS
576             : {%DateTime::TimeZone::Catalog::LINKS};
577             }
578              
579             sub names_in_category {
580 3 100   3 1 3711 shift if $_[0]->isa('DateTime::TimeZone');
581 3 50       14 return unless exists $DateTime::TimeZone::Catalog::CATEGORIES{ $_[0] };
582              
583             return wantarray
584 2         31 ? @{ $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 1162 ? ( 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 6409 shift if $_[0]->isa('DateTime::TimeZone');
596              
597             return
598             unless
599 5 50       20 exists $DateTime::TimeZone::Catalog::ZONES_BY_COUNTRY{ lc $_[0] };
600              
601             return
602             wantarray
603 4         31 ? @{ $DateTime::TimeZone::Catalog::ZONES_BY_COUNTRY{ lc $_[0] } }
604 5 100       16 : $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.58
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 Bron Gondwana Daisuke Maki David Pinkowitz 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             Bron Gondwana <brong@fastmail.fm>
941              
942             =item *
943              
944             Daisuke Maki <dmaki@cpan.org>
945              
946             =item *
947              
948             David Pinkowitz <dave@pinkowitz.com>
949              
950             =item *
951              
952             Iain Truskett <deceased>
953              
954             =item *
955              
956             Jakub Wilk <jwilk@jwilk.net>
957              
958             =item *
959              
960             James E Keenan <jkeenan@cpan.org>
961              
962             =item *
963              
964             Joshua Hoblitt <jhoblitt@cpan.org>
965              
966             =item *
967              
968             Karen Etheridge <ether@cpan.org>
969              
970             =item *
971              
972             karupanerura <karupa@cpan.org>
973              
974             =item *
975              
976             kclaggett <kclaggett@proofpoint.com>
977              
978             =item *
979              
980             Matthew Horsfall <wolfsage@gmail.com>
981              
982             =item *
983              
984             Mohammad S Anwar <mohammad.anwar@yahoo.com>
985              
986             =item *
987              
988             Olaf Alders <olaf@wundersolutions.com>
989              
990             =item *
991              
992             Peter Rabbitson <ribasushi@cpan.org>
993              
994             =item *
995              
996             Tom Wyant <wyant@cpan.org>
997              
998             =back
999              
1000             =head1 COPYRIGHT AND LICENSE
1001              
1002             This software is copyright (c) 2023 by Dave Rolsky.
1003              
1004             This is free software; you can redistribute it and/or modify it under
1005             the same terms as the Perl 5 programming language system itself.
1006              
1007             The full text of the license can be found in the
1008             F<LICENSE> file included with this distribution.
1009              
1010             =cut