Add built local::lib
[catagits/Gitalist.git] / local-lib5 / lib / perl5 / DateTime / TimeZone.pm
1 package DateTime::TimeZone;
2
3 use 5.006;
4
5 use strict;
6 use warnings;
7
8 our $VERSION = '1.05';
9
10 use DateTime::TimeZone::Catalog;
11 use DateTime::TimeZone::Floating;
12 use DateTime::TimeZone::Local;
13 use DateTime::TimeZone::OffsetOnly;
14 use DateTime::TimeZone::UTC;
15 use Params::Validate qw( validate validate_pos SCALAR ARRAYREF BOOLEAN );
16
17 use constant INFINITY     =>       100 ** 1000 ;
18 use constant NEG_INFINITY => -1 * (100 ** 1000);
19
20 # the offsets for each span element
21 use constant UTC_START   => 0;
22 use constant UTC_END     => 1;
23 use constant LOCAL_START => 2;
24 use constant LOCAL_END   => 3;
25 use constant OFFSET      => 4;
26 use constant IS_DST      => 5;
27 use constant SHORT_NAME  => 6;
28
29 my %SpecialName = map { $_ => 1 } qw( EST MST HST CET EET MET WET EST5EDT CST6CDT MST7MDT PST8PDT );
30
31 sub new
32 {
33     my $class = shift;
34     my %p = validate( @_,
35                       { name => { type => SCALAR } },
36                     );
37
38     if ( exists $DateTime::TimeZone::Catalog::LINKS{ $p{name} } )
39     {
40         $p{name} = $DateTime::TimeZone::Catalog::LINKS{ $p{name} };
41     }
42     elsif ( exists $DateTime::TimeZone::Catalog::LINKS{ uc $p{name} } )
43     {
44         $p{name} = $DateTime::TimeZone::Catalog::LINKS{ uc $p{name} };
45     }
46
47     unless ( $p{name} =~ m,/,
48              || $SpecialName{ $p{name} }
49            )
50     {
51         if ( $p{name} eq 'floating' )
52         {
53             return DateTime::TimeZone::Floating->new;
54         }
55
56         if ( $p{name} eq 'local' )
57         {
58             return DateTime::TimeZone::Local->TimeZone();
59         }
60
61         if ( $p{name} eq 'UTC' || $p{name} eq 'Z' )
62         {
63             return DateTime::TimeZone::UTC->new;
64         }
65
66         return DateTime::TimeZone::OffsetOnly->new( offset => $p{name} );
67     }
68
69     my $subclass = $p{name};
70     $subclass =~ s/-/_/g;
71     $subclass =~ s{/}{::}g;
72     my $real_class = "DateTime::TimeZone::$subclass";
73
74     die "The timezone '$p{name}' in an invalid name.\n"
75         unless $real_class =~ /^\w+(::\w+)*$/;
76
77     unless ( $real_class->can('instance') )
78     {
79         my $e = do { local $@;
80                      local $SIG{__DIE__};
81                      eval "require $real_class";
82                      $@;
83                    };
84
85         if ($e)
86         {
87             my $regex = join '.', split /::/, $real_class;
88             $regex .= '\\.pm';
89
90             if ( $e =~ /^Can't locate $regex/i )
91             {
92                 die "The timezone '$p{name}' could not be loaded, or is an invalid name.\n";
93             }
94             else
95             {
96                 die $e;
97             }
98         }
99     }
100
101     my $zone = $real_class->instance( name => $p{name}, is_olson => 1 );
102
103     if ( $zone->is_olson() )
104     {
105         my $object_version =
106             $zone->can('olson_version')
107             ? $zone->olson_version()
108             : 'unknown';
109         my $catalog_version = DateTime::TimeZone::Catalog->OlsonVersion();
110
111         if ( $object_version ne $catalog_version )
112         {
113             warn "Loaded $real_class, which is from an older version ($object_version) of the Olson database than this installation of DateTime::TimeZone ($catalog_version).\n";
114         }
115     }
116
117     return $zone;
118 }
119
120 sub _init
121 {
122     my $class = shift;
123     my %p = validate( @_,
124                       { name     => { type => SCALAR },
125                         spans    => { type => ARRAYREF },
126                         is_olson => { type => BOOLEAN, default => 0 },
127                       },
128                     );
129
130     my $self = bless { name     => $p{name},
131                        spans    => $p{spans},
132                        is_olson => $p{is_olson},
133                      }, $class;
134
135     foreach my $k ( qw( last_offset last_observance rules max_year ) )
136     {
137         my $m = "_$k";
138         $self->{$k} = $self->$m() if $self->can($m);
139     }
140
141     return $self;
142 }
143
144 sub is_olson { $_[0]->{is_olson} }
145
146 sub is_dst_for_datetime
147 {
148     my $self = shift;
149
150     my $span = $self->_span_for_datetime( 'utc', $_[0] );
151
152     return $span->[IS_DST];
153 }
154
155 sub offset_for_datetime
156 {
157     my $self = shift;
158
159     my $span = $self->_span_for_datetime( 'utc', $_[0] );
160
161     return $span->[OFFSET];
162 }
163
164 sub offset_for_local_datetime
165 {
166     my $self = shift;
167
168     my $span = $self->_span_for_datetime( 'local', $_[0] );
169
170     return $span->[OFFSET];
171 }
172
173 sub short_name_for_datetime
174 {
175     my $self = shift;
176
177     my $span = $self->_span_for_datetime( 'utc', $_[0] );
178
179     return $span->[SHORT_NAME];
180 }
181
182 sub _span_for_datetime
183 {
184     my $self = shift;
185     my $type = shift;
186     my $dt   = shift;
187
188     my $method = $type . '_rd_as_seconds';
189
190     my $end = $type eq 'utc' ? UTC_END : LOCAL_END;
191
192     my $span;
193     my $seconds = $dt->$method();
194     if ( $seconds < $self->max_span->[$end] )
195     {
196         $span = $self->_spans_binary_search( $type, $seconds );
197     }
198     else
199     {
200         my $until_year = $dt->utc_year + 1;
201         $span = $self->_generate_spans_until_match( $until_year, $seconds, $type );
202     }
203
204     # This means someone gave a local time that doesn't exist
205     # (like during a transition into savings time)
206     unless ( defined $span )
207     {
208         my $err = 'Invalid local time for date';
209         $err .= ' ' . $dt->iso8601 if $type eq 'utc';
210         $err .= " in time zone: " . $self->name;
211         $err .= "\n";
212
213         die $err;
214     }
215
216     return $span;
217 }
218
219 sub _spans_binary_search
220 {
221     my $self = shift;
222     my ( $type, $seconds ) = @_;
223
224     my ( $start, $end ) = _keys_for_type($type);
225
226     my $min = 0;
227     my $max = scalar @{ $self->{spans} } + 1;
228     my $i = int( $max / 2 );
229     # special case for when there are only 2 spans
230     $i++ if $max % 2 && $max != 3;
231
232     $i = 0 if @{ $self->{spans} } == 1;
233
234     while (1)
235     {
236         my $current = $self->{spans}[$i];
237
238         if ( $seconds < $current->[$start] )
239         {
240             $max = $i;
241             my $c = int( ( $i - $min ) / 2 );
242             $c ||= 1;
243
244             $i -= $c;
245
246             return if $i < $min;
247         }
248         elsif ( $seconds >= $current->[$end] )
249         {
250             $min = $i;
251             my $c = int( ( $max - $i ) / 2 );
252             $c ||= 1;
253
254             $i += $c;
255
256             return if $i >= $max;
257         }
258         else
259         {
260             # Special case for overlapping ranges because of DST and
261             # other weirdness (like Alaska's change when bought from
262             # Russia by the US).  Always prefer latest span.
263             if ( $current->[IS_DST] && $type eq 'local' )
264             {
265                 # Asia/Dhaka in 2009j goes into DST without any known
266                 # end-of-DST date (wtf, Bangladesh).
267                 return $current if $current->[UTC_END] == INFINITY;
268
269                 my $next = $self->{spans}[$i + 1];
270                 # Sometimes we will get here and the span we're
271                 # looking at is the last that's been generated so far.
272                 # We need to try to generate one more or else we run
273                 # out.
274                 $next ||= $self->_generate_next_span;
275
276                 die "No next span in $self->{max_year}" unless defined $next;
277
278                 if ( ( ! $next->[IS_DST] )
279                      && $next->[$start] <= $seconds
280                      && $seconds        <= $next->[$end]
281                    )
282                 {
283                     return $next;
284                 }
285             }
286
287             return $current;
288         }
289     }
290 }
291
292 sub _generate_next_span
293 {
294     my $self = shift;
295
296     my $last_idx = $#{ $self->{spans} };
297
298     my $max_span = $self->max_span;
299
300     # Kind of a hack, but AFAIK there are no zones where it takes
301     # _more_ than a year for a _future_ time zone change to occur, so
302     # by looking two years out we can ensure that we will find at
303     # least one more span.  Of course, I will no doubt be proved wrong
304     # and this will cause errors.
305     $self->_generate_spans_until_match
306         ( $self->{max_year} + 2, $max_span->[UTC_END] + ( 366 * 86400 ), 'utc' );
307
308     return $self->{spans}[ $last_idx + 1 ];
309 }
310
311 sub _generate_spans_until_match
312 {
313     my $self = shift;
314     my $generate_until_year = shift;
315     my $seconds = shift;
316     my $type = shift;
317
318     my @changes;
319     my @rules = @{ $self->_rules };
320     foreach my $year ( $self->{max_year} .. $generate_until_year )
321     {
322         for ( my $x = 0; $x < @rules; $x++ )
323         {
324             my $last_offset_from_std;
325
326             if ( @rules == 2 )
327             {
328                 $last_offset_from_std =
329                     $x ? $rules[0]->offset_from_std : $rules[1]->offset_from_std;
330             }
331             elsif ( @rules == 1 )
332             {
333                 $last_offset_from_std = $rules[0]->offset_from_std;
334             }
335             else
336             {
337                 my $count = scalar @rules;
338                 die "Cannot generate future changes for zone with $count infinite rules\n";
339             }
340
341             my $rule = $rules[$x];
342
343             my $next =
344                 $rule->utc_start_datetime_for_year
345                     ( $year, $self->{last_offset}, $last_offset_from_std );
346
347             # don't bother with changes we've seen already
348             next if $next->utc_rd_as_seconds < $self->max_span->[UTC_END];
349
350             push @changes,
351                 DateTime::TimeZone::OlsonDB::Change->new
352                     ( type => 'rule',
353                       utc_start_datetime   => $next,
354                       local_start_datetime =>
355                       $next +
356                       DateTime::Duration->new
357                           ( seconds => $self->{last_observance}->total_offset +
358                                        $rule->offset_from_std ),
359                       short_name =>
360                       sprintf( $self->{last_observance}->format, $rule->letter ),
361                       observance => $self->{last_observance},
362                       rule       => $rule,
363                     );
364         }
365     }
366
367     $self->{max_year} = $generate_until_year;
368
369     my @sorted = sort { $a->utc_start_datetime <=> $b->utc_start_datetime } @changes;
370
371     my ( $start, $end ) = _keys_for_type($type);
372
373     my $match;
374     for ( my $x = 1; $x < @sorted; $x++ )
375     {
376         my $last_total_offset =
377             $x == 1 ? $self->max_span->[OFFSET] : $sorted[ $x - 2 ]->total_offset;
378
379         my $span =
380             DateTime::TimeZone::OlsonDB::Change::two_changes_as_span
381                 ( @sorted[ $x - 1, $x ], $last_total_offset );
382
383         $span = _span_as_array($span);
384
385         push @{ $self->{spans} }, $span;
386
387         $match = $span
388             if $seconds >= $span->[$start] && $seconds < $span->[$end];
389     }
390
391     return $match;
392 }
393
394 sub max_span { $_[0]->{spans}[-1] }
395
396 sub _keys_for_type
397 {
398     $_[0] eq 'utc' ? ( UTC_START, UTC_END ) : ( LOCAL_START, LOCAL_END );
399 }
400
401 sub _span_as_array
402 {
403     [ @{ $_[0] }{ qw( utc_start utc_end local_start local_end offset is_dst short_name ) } ];
404 }
405
406 sub is_floating { 0 }
407
408 sub is_utc { 0 }
409
410 sub has_dst_changes { 0 }
411
412 sub name      { $_[0]->{name} }
413 sub category  { (split /\//, $_[0]->{name}, 2)[0] }
414
415 sub is_valid_name
416 {
417     my $tz;
418     {
419         local $@;
420         local $SIG{__DIE__};
421         $tz = eval { $_[0]->new( name => $_[1] ) };
422     }
423
424     return $tz && $tz->isa('DateTime::TimeZone') ? 1 : 0
425 }
426
427 sub STORABLE_freeze
428 {
429     my $self = shift;
430
431     return $self->name;
432 }
433
434 sub STORABLE_thaw
435 {
436     my $self = shift;
437     my $cloning = shift;
438     my $serialized = shift;
439
440     my $class = ref $self || $self;
441
442     my $obj;
443     if ( $class->isa(__PACKAGE__) )
444     {
445         $obj = __PACKAGE__->new( name => $serialized );
446     }
447     else
448     {
449         $obj = $class->new( name => $serialized );
450     }
451
452     %$self = %$obj;
453
454     return $self;
455 }
456
457 #
458 # Functions
459 #
460 sub offset_as_seconds
461 {
462     {
463         local $@;
464         local $SIG{__DIE__};
465         shift if eval { $_[0]->isa('DateTime::TimeZone') };
466     }
467
468     my $offset = shift;
469
470     return undef unless defined $offset;
471
472     return 0 if $offset eq '0';
473
474     my ( $sign, $hours, $minutes, $seconds );
475     if ( $offset =~ /^([\+\-])?(\d\d?):(\d\d)(?::(\d\d))?$/ )
476     {
477         ( $sign, $hours, $minutes, $seconds ) = ( $1, $2, $3, $4 );
478     }
479     elsif ( $offset =~ /^([\+\-])?(\d\d)(\d\d)(\d\d)?$/ )
480     {
481         ( $sign, $hours, $minutes, $seconds ) = ( $1, $2, $3, $4 );
482     }
483     else
484     {
485         return undef;
486     }
487
488     $sign = '+' unless defined $sign;
489     return undef unless $hours >= 0 && $hours <= 99;
490     return undef unless $minutes >= 0 && $minutes <= 59;
491     return undef unless ! defined( $seconds ) || ( $seconds >= 0 && $seconds <= 59 );
492
493     my $total =  $hours * 3600 + $minutes * 60;
494     $total += $seconds if $seconds;
495     $total *= -1 if $sign eq '-';
496
497     return $total;
498 }
499
500 sub offset_as_string
501 {
502     {
503         local $@;
504         local $SIG{__DIE__};
505         shift if eval { $_[0]->isa('DateTime::TimeZone') };
506     }
507
508     my $offset = shift;
509
510     return undef unless defined $offset;
511     return undef unless $offset >= -359999 && $offset <= 359999;
512
513     my $sign = $offset < 0 ? '-' : '+';
514
515     $offset = abs($offset);
516
517     my $hours = int( $offset / 3600 );
518     $offset %= 3600;
519     my $mins = int( $offset / 60 );
520     $offset %= 60;
521     my $secs = int( $offset );
522
523     return ( $secs ?
524              sprintf( '%s%02d%02d%02d', $sign, $hours, $mins, $secs ) :
525              sprintf( '%s%02d%02d', $sign, $hours, $mins )
526            );
527 }
528
529 # These methods all operate on data contained in the DateTime/TimeZone/Catalog.pm file.
530
531 sub all_names
532 {
533     return wantarray ? @DateTime::TimeZone::Catalog::ALL : [@DateTime::TimeZone::Catalog::ALL];
534 }
535
536 sub categories
537 {
538     return wantarray
539         ? @DateTime::TimeZone::Catalog::CATEGORY_NAMES
540         : [@DateTime::TimeZone::Catalog::CATEGORY_NAMES];
541 }
542
543 sub links
544 {
545     return
546         wantarray ? %DateTime::TimeZone::Catalog::LINKS : {%DateTime::TimeZone::Catalog::LINKS};
547 }
548
549 sub names_in_category
550 {
551     shift if $_[0]->isa('DateTime::TimeZone');
552     return unless exists $DateTime::TimeZone::Catalog::CATEGORIES{ $_[0] };
553
554     return
555         wantarray
556         ? @{ $DateTime::TimeZone::Catalog::CATEGORIES{ $_[0] } }
557         : [ $DateTime::TimeZone::Catalog::CATEGORIES{ $_[0] } ];
558 }
559
560 sub countries
561 {
562     wantarray
563         ? ( sort keys %DateTime::TimeZone::Catalog::ZONES_BY_COUNTRY )
564         : [ sort keys %DateTime::TimeZone::Catalog::ZONES_BY_COUNTRY ];
565 }
566
567 sub names_in_country
568 {
569     shift if $_[0]->isa('DateTime::TimeZone');
570
571     return unless exists $DateTime::TimeZone::Catalog::ZONES_BY_COUNTRY{ lc $_[0] };
572
573     return
574         wantarray
575         ? @{ $DateTime::TimeZone::Catalog::ZONES_BY_COUNTRY{ lc $_[0] } }
576         : $DateTime::TimeZone::Catalog::ZONES_BY_COUNTRY{ lc $_[0] };
577 }
578
579
580 1;
581
582 __END__
583
584 =head1 NAME
585
586 DateTime::TimeZone - Time zone object base class and factory
587
588 =head1 SYNOPSIS
589
590   use DateTime;
591   use DateTime::TimeZone;
592
593   my $tz = DateTime::TimeZone->new( name => 'America/Chicago' );
594
595   my $dt = DateTime->now();
596   my $offset = $tz->offset_for_datetime($dt);
597
598 =head1 DESCRIPTION
599
600 This class is the base class for all time zone objects.  A time zone
601 is represented internally as a set of observances, each of which
602 describes the offset from GMT for a given time period.
603
604 Note that without the C<DateTime.pm> module, this module does not do
605 much.  It's primary interface is through a C<DateTime> object, and
606 most users will not need to directly use C<DateTime::TimeZone>
607 methods.
608
609 =head1 USAGE
610
611 This class has the following methods:
612
613 =head2 DateTime::TimeZone->new( name => $tz_name )
614
615 Given a valid time zone name, this method returns a new time zone
616 blessed into the appropriate subclass.  Subclasses are named for the
617 given time zone, so that the time zone "America/Chicago" is the
618 DateTime::TimeZone::America::Chicago class.
619
620 If the name given is a "link" name in the Olson database, the object
621 created may have a different name.  For example, there is a link from
622 the old "EST5EDT" name to "America/New_York".
623
624 When loading a time zone from the Olson database, the constructor
625 checks the version of the loaded class to make sure it matches the
626 version of the current DateTime::TimeZone installation. If they do not
627 match it will issue a warning. This is useful because time zone names
628 may fall out of use, but you may have an old module file installed for
629 that time zone.
630
631 There are also several special values that can be given as names.
632
633 If the "name" parameter is "floating", then a
634 C<DateTime::TimeZone::Floating> object is returned.  A floating time
635 zone does have I<any> offset, and is always the same time.  This is
636 useful for calendaring applications, which may need to specify that a
637 given event happens at the same I<local> time, regardless of where it
638 occurs.  See RFC 2445 for more details.
639
640 If the "name" parameter is "UTC", then a C<DateTime::TimeZone::UTC>
641 object is returned.
642
643 If the "name" is an offset string, it is converted to a number, and a
644 C<DateTime::TimeZone::OffsetOnly> object is returned.
645
646 =head3 The "local" time zone
647
648 If the "name" parameter is "local", then the module attempts to
649 determine the local time zone for the system.
650
651 The method for finding the local zone varies by operating system. See
652 the appropriate module for details of how we check for the local time
653 zone.
654
655 =over 4
656
657 =item * L<DateTime::TimeZone::Local::Unix>
658
659 =item * L<DateTime::TimeZone::Local::Win32>
660
661 =item * L<DateTime::TimeZone::Local::VMS>
662
663 =back
664
665 If a local time zone is not found, then an exception will be thrown.
666
667 =head2 $tz->offset_for_datetime( $dt )
668
669 Given a C<DateTime> object, this method returns the offset in seconds
670 for the given datetime.  This takes into account historical time zone
671 information, as well as Daylight Saving Time.  The offset is
672 determined by looking at the object's UTC Rata Die days and seconds.
673
674 =head2 $tz->offset_for_local_datetime( $dt )
675
676 Given a C<DateTime> object, this method returns the offset in seconds
677 for the given datetime.  Unlike the previous method, this method uses
678 the local time's Rata Die days and seconds.  This should only be done
679 when the corresponding UTC time is not yet known, because local times
680 can be ambiguous due to Daylight Saving Time rules.
681
682 =head2 $tz->name
683
684 Returns the name of the time zone.  If this value is passed to the
685 C<new()> method, it is guaranteed to create the same object.
686
687 =head2 $tz->short_name_for_datetime( $dt )
688
689 Given a C<DateTime> object, this method returns the "short name" for
690 the current observance and rule this datetime is in.  These are names
691 like "EST", "GMT", etc.
692
693 It is B<strongly> recommended that you do not rely on these names for
694 anything other than display.  These names are not official, and many
695 of them are simply the invention of the Olson database maintainers.
696 Moreover, these names are not unique.  For example, there is an "EST"
697 at both -0500 and +1000/+1100.
698
699 =head2 $tz->is_floating
700
701 Returns a boolean indicating whether or not this object represents a
702 floating time zone, as defined by RFC 2445.
703
704 =head2 $tz->is_utc
705
706 Indicates whether or not this object represents the UTC (GMT) time
707 zone.
708
709 =head2 $tz->has_dst_changes
710
711 Indicates whether or not this zone has I<ever> had a change to and
712 from DST, either in the past or future.
713
714 =head2 $tz->is_olson
715
716 Returns true if the time zone is a named time zone from the Olson
717 database.
718
719 =head2 $tz->category
720
721 Returns the part of the time zone name before the first slash.  For
722 example, the "America/Chicago" time zone would return "America".
723
724 =head2 DateTime::TimeZone->is_valid_name($name)
725
726 Given a string, this method returns a boolean value indicating whether
727 or not the string is a valid time zone name.  If you are using
728 C<DateTime::TimeZone::Alias>, any aliases you've created will be valid.
729
730 =head2 DateTime::TimeZone->all_names
731
732 This returns a pre-sorted list of all the time zone names.  This list
733 does not include link names.  In scalar context, it returns an array
734 reference, while in list context it returns an array.
735
736 =head2 DateTime::TimeZone->categories
737
738 This returns a list of all time zone categories.  In scalar context,
739 it returns an array reference, while in list context it returns an
740 array.
741
742 =head2 DateTime::TimeZone->links
743
744 This returns a hash of all time zone links, where the keys are the
745 old, deprecated names, and the values are the new names.  In scalar
746 context, it returns a hash reference, while in list context it returns
747 a hash.
748
749 =head2 DateTime::TimeZone->names_in_category( $category )
750
751 Given a valid category, this method returns a list of the names in
752 that category, without the category portion.  So the list for the
753 "America" category would include the strings "Chicago",
754 "Kentucky/Monticello", and "New_York". In scalar context, it returns
755 an array reference, while in list context it returns an array.
756
757 The list is returned in order of population by zone, which should mean
758 that this order will be the best to use for most UIs.
759
760 =head2 DateTime::TimeZone->countries()
761
762 Returns a sorted list of all the valid country codes (in lower-case)
763 which can be passed to C<names_in_country()>. In scalar context, it
764 returns an array reference, while in list context it returns an array.
765
766 If you need to convert country codes to names or vice versa you can
767 use C<Locale::Country> to do so.
768
769 =head2 DateTime::TimeZone->names_in_country( $country_code )
770
771 Given a two-letter ISO3166 country code, this method returns a list of
772 time zones used in that country. The country code may be of any
773 case. In scalar context, it returns an array reference, while in list
774 context it returns an array.
775
776 =head2 DateTime::TimeZone->offset_as_seconds( $offset )
777
778 Given an offset as a string, this returns the number of seconds
779 represented by the offset as a positive or negative number.  Returns
780 C<undef> if $offset is not in the range C<-99:59:59> to C<+99:59:59>.
781
782 The offset is expected to match either
783 C</^([\+\-])?(\d\d?):(\d\d)(?::(\d\d))?$/> or
784 C</^([\+\-])?(\d\d)(\d\d)(\d\d)?$/>.  If it doesn't match either of
785 these, C<undef> will be returned.
786
787 This means that if you want to specify hours as a single digit, then
788 each element of the offset must be separated by a colon (:).
789
790 =head2 DateTime::TimeZone->offset_as_string( $offset )
791
792 Given an offset as a number, this returns the offset as a string.
793 Returns C<undef> if $offset is not in the range C<-359999> to C<359999>.
794
795 =head2 Storable Hooks
796
797 This module provides freeze and thaw hooks for C<Storable> so that the
798 huge data structures for Olson time zones are not actually stored in
799 the serialized structure.
800
801 If you subclass C<DateTime::TimeZone>, you will inherit its hooks,
802 which may not work for your module, so please test the interaction of
803 your module with Storable.
804
805 =head1 SUPPORT
806
807 Support for this module is provided via the datetime@perl.org email
808 list. See http://datetime.perl.org/?MailingList for details.
809
810 Please submit bugs to the CPAN RT system at
811 http://rt.cpan.org/NoAuth/ReportBug.html?Queue=datetime%3A%3Atimezone
812 or via email at bug-datetime-timezone@rt.cpan.org.
813
814 =head1 DONATIONS
815
816 If you'd like to thank me for the work I've done on this module,
817 please consider making a "donation" to me via PayPal. I spend a lot of
818 free time creating free software, and would appreciate any support
819 you'd care to offer.
820
821 Please note that B<I am not suggesting that you must do this> in order
822 for me to continue working on this particular software. I will
823 continue to do so, inasmuch as I have in the past, for as long as it
824 interests me.
825
826 Similarly, a donation made in this way will probably not make me work
827 on this software much more, unless I get so many donations that I can
828 consider working on free software full time, which seems unlikely at
829 best.
830
831 To donate, log into PayPal and send money to autarch@urth.org or use
832 the button on this page:
833 L<http://www.urth.org/~autarch/fs-donation.html>
834
835 =head1 AUTHOR
836
837 Dave Rolsky <autarch@urth.org>
838
839 =head1 CREDITS
840
841 This module was inspired by Jesse Vincent's work on
842 Date::ICal::Timezone, and written with much help from the
843 datetime@perl.org list.
844
845 =head1 COPYRIGHT
846
847 Copyright (c) 2003-2008 David Rolsky.  All rights reserved.  This
848 program is free software; you can redistribute it and/or modify it
849 under the same terms as Perl itself.
850
851 The full text of the license can be found in the LICENSE file included
852 with this module.
853
854 =head1 SEE ALSO
855
856 datetime@perl.org mailing list
857
858 http://datetime.perl.org/
859
860 The tools directory of the DateTime::TimeZone distribution includes
861 two scripts that may be of interest to some people.  They are
862 parse_olson and tests_from_zdump.  Please run them with the --help
863 flag to see what they can be used for.
864
865 =cut