1 package DateTime::TimeZone::OlsonDB;
6 use vars qw( %MONTHS %DAYS $PLUS_ONE_DAY_DUR $MINUS_ONE_DAY_DUR );
8 use DateTime::TimeZone::OlsonDB::Rule;
9 use DateTime::TimeZone::OlsonDB::Zone;
10 use Params::Validate qw( validate SCALAR );
14 %MONTHS = map { $_ => $x++ }
15 qw( Jan Feb Mar Apr May Jun
16 Jul Aug Sep Oct Nov Dec);
19 %DAYS = map { $_ => $x++ }
20 qw( Mon Tue Wed Thu Fri Sat Sun );
22 $PLUS_ONE_DAY_DUR = DateTime::Duration->new( days => 1 );
23 $MINUS_ONE_DAY_DUR = DateTime::Duration->new( days => -1 );
29 return bless { rules => {},
41 or die "Cannot read $file: $!";
46 $self->_parse_line($_);
55 return if $line =~ /^\s+$/;
56 return if $line =~ /^#/;
58 # remove any comments at the end of the line
61 if ( $self->{in_zone} && $line =~ /^\t/ )
63 $self->_parse_zone( $line, $self->{in_zone} );
67 foreach ( qw( Rule Zone Link ) )
69 if ( substr( $line, 0, 4 ) eq $_ )
71 my $m = '_parse_' . lc $_;
82 my @items = split /\s+/, $rule, 10;
85 my $name = shift @items;
88 @rule{ qw( from to type in on at save letter ) } = @items;
89 delete $rule{letter} if $rule{letter} eq '-';
91 # As of the 2003a data, there are no rules with a type set
92 delete $rule{type} if $rule{type} eq '-';
94 push @{ $self->{rules}{$name} },
95 DateTime::TimeZone::OlsonDB::Rule->new( name => $name, %rule );
97 undef $self->{in_zone};
106 my $expect = $name ? 5 : 6;
107 my @items = grep { defined && length } split /\s+/, $zone, $expect;
112 shift @items; # remove "Zone"
113 $name = shift @items;
116 @obs{ qw( gmtoff rules format until ) } = @items;
118 if ( $obs{rules} =~ /\d\d?:\d\d/ )
120 $obs{offset_from_std} = delete $obs{rules};
124 delete $obs{rules} if $obs{rules} eq '-';
127 delete $obs{until} unless defined $obs{until};
129 push @{ $self->{zones}{$name} }, \%obs;
131 $self->{in_zone} = $name;
139 my @items = split /\s+/, $link, 3;
141 $self->{links}{ $items[2] } = $items[1];
143 undef $self->{in_zone};
146 sub links { %{ $_[0]->{links} } }
148 sub zone_names { keys %{ $_[0]->{zones} } }
155 die "Invalid zone name $name"
156 unless exists $self->{zones}{$name};
159 DateTime::TimeZone::OlsonDB::Zone->new
161 observances => $self->{zones}{$name},
169 my %p = validate( @_, { name => { type => SCALAR },
170 expand_to_year => { type => SCALAR,
171 default => (localtime)[5] + 1910 },
174 my $zone = $self->zone( $p{name} );
176 $zone->expand_observances( $self, $p{expand_to_year} );
186 return unless defined $name;
188 die "Invalid rule name $name"
189 unless exists $self->{rules}{$name};
191 return @{ $self->{rules}{$name} };
196 my ( $day, $month, $year ) = @_;
198 return $day if $day =~ /^\d+$/;
200 if ( $day =~ /^last(\w\w\w)$/ )
204 my $last_day = DateTime->last_day_of_month( year => $year,
206 time_zone => 'floating',
210 DateTime->new( year => $year,
212 day => $last_day->day,
213 time_zone => 'floating',
216 while ( $dt->day_of_week != $dow )
218 $dt -= $PLUS_ONE_DAY_DUR;
223 elsif ( $day =~ /^(\w\w\w)([><])=(\d\d?)$/ )
227 my $dt = DateTime->new( year => $year,
230 time_zone => 'floating',
233 my $dur = $2 eq '<' ? $MINUS_ONE_DAY_DUR : $PLUS_ONE_DAY_DUR;
235 while ( $dt->day_of_week != $dow )
244 die "Invalid on spec for rule: $day\n";
248 sub utc_datetime_for_time_spec
250 my %p = validate( @_, { spec => { type => SCALAR },
251 year => { type => SCALAR },
252 month => { type => SCALAR },
253 day => { type => SCALAR },
254 offset_from_utc => { type => SCALAR },
255 offset_from_std => { type => SCALAR },
259 # 'w'all - ignore it, because that's the default
262 # 'g'reenwich, 'u'tc, or 'z'ulu
263 my $is_utc = $p{spec} =~ s/[guz]$//;
265 # 's'tandard time - ignore DS offset
266 my $is_std = $p{spec} =~ s/s$//;
268 my ($hour, $minute, $second) = split /:/, $p{spec};
269 $minute = 0 unless defined $minute;
270 $second = 0 unless defined $second;
282 $utc = DateTime->new( year => $p{year},
288 time_zone => 'floating',
293 my $local = DateTime->new( year => $p{year},
299 time_zone => 'floating',
302 $p{offset_from_std} = 0 if $is_std;
305 DateTime::Duration->new
306 ( seconds => $p{offset_from_utc} + $p{offset_from_std} );
308 $utc = $local - $dur;
311 $utc->add( days => 1 ) if $add_day;
322 DateTime::TimeZone::OlsonDB - An object to represent an Olson time zone database
330 This module parses the Olson database time zone definition files and
331 creates various objects representing time zone data.
333 Each time zone is broken down into several parts. The first piece is
334 an observance, which is an offset from UTC and an abbreviation. A
335 single zone may contain many observances, reflecting historical
336 changes in that time zone over time. An observance may also refer to
339 Rules are named, and may apply to many different zones. For example,
340 the "US" rules apply to most of the time zones in the US,
341 unsurprisingly. Rules are made of an offset from standard time and a
342 definition of when that offset changes. Changes can be a one time
343 thing, or they can recur at regular times through a span of years.
345 Each rule may have an associated letter, which is used to generate an
346 abbreviated name for the time zone, along with the offset's
347 abbreviation. For example, if the offset's abbreviation is "C%sT",
348 and the a rule specifies the letter "S", then the abbreviation when
349 that rule is in effect is "CST".
353 Not yet documented. This stuff is a mess.
357 Dave Rolsky, <autarch@urth.org>
359 =head1 COPYRIGHT & LICENSE
361 Copyright (c) 2003-2008 David Rolsky. All rights reserved. This
362 program is free software; you can redistribute it and/or modify it
363 under the same terms as Perl itself.
365 The full text of the license can be found in the LICENSE file included