File Coverage

blib/lib/DateTime/TimeZone/SystemV.pm
Criterion Covered Total %
statement 135 138 97.8
branch 75 78 96.1
condition 10 14 71.4
subroutine 24 24 100.0
pod 11 11 100.0
total 255 265 96.2


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             DateTime::TimeZone::SystemV - System V and POSIX timezone strings
4              
5             =head1 SYNOPSIS
6              
7             use DateTime::TimeZone::SystemV;
8              
9             $tz = DateTime::TimeZone::SystemV->new(
10             name => "US Eastern",
11             recipe => "EST5EDT,M3.2.0,M11.1.0");
12             $tz = DateTime::TimeZone::SystemV->new(
13             "EST5EDT,M3.2.0,M11.1.0");
14              
15             if($tz->is_floating) { ...
16             if($tz->is_utc) { ...
17             if($tz->is_olson) { ...
18             $category = $tz->category;
19             $tz_string = $tz->name;
20              
21             if($tz->has_dst_changes) { ...
22             if($tz->is_dst_for_datetime($dt)) { ...
23             $offset = $tz->offset_for_datetime($dt);
24             $abbrev = $tz->short_name_for_datetime($dt);
25             $offset = $tz->offset_for_local_datetime($dt);
26              
27             =head1 DESCRIPTION
28              
29             An instance of this class represents a timezone that was specified by
30             means of a System V timezone recipe or an extended form of the same syntax
31             (such as that specified by POSIX). These can express a plain offset from
32             Universal Time, or a system of two offsets (standard and daylight saving
33             time) switching on a yearly cycle according to certain types of rule.
34              
35             This class implements the L interface, so that its
36             instances can be used with L objects.
37              
38             =head1 SYSTEM V TIMEZONE RECIPE SYSTEM
39              
40             This module supports multiple versions of the timezone recipe syntax
41             derived from System V. Specifically, it supports the version specified
42             by POSIX.1, and the extension of the POSIX format that is used by version
43             3 of the L file format.
44              
45             A timezone may be specified that has a fixed offset by the
46             syntax "II", or a timezone with DST by the syntax
47             "III[I]B<,>IB<,>I". "I" specifies an
48             abbreviation by which an offset is known, "I" specifies the offset,
49             and "I" is a rule for when DST starts or ends. For backward
50             compatibility, the rules part may also be omitted from a DST-using
51             timezone, in which case some built-in default rules are used; don't rely
52             on those rules being useful.
53              
54             An abbreviation must be a string of three or more characters from ASCII
55             alphanumerics, "B<+>", and "B<->". If it contains only ASCII alphabetic
56             characters then the abbreviation specification "I" may be simply
57             the abbreviation. Otherwise "I" must consist of the abbreviation
58             wrapped in angle brackets ("B<< < >>...B<< > >>"). The angle bracket
59             form is always allowed. POSIX allows an implementation to set an upper
60             limit on the length of timezone abbreviations. The limit is known as
61             C, and is required to be no less than 6 (characters/bytes).
62             Abbreviations longer than 6 characters are therefore not portable.
63             This class imposes no such limit.
64              
65             An offset (from Universal Time), "I", is given in hours, or
66             hours and minutes, or hours and minutes and seconds, with an optional
67             preceding sign. Hours, minutes, and seconds must be separated by colons.
68             The hours may be one or two digits, and the minutes and seconds must be
69             two digits each. The maximum magnitude permitted is 24:59:59. The sign
70             in the specification is the opposite of the sign of the actual offset.
71             If no sign is given then the default is "B<+>", meaning a timezone that
72             is behind UT (or equal to UT if the offset is zero). If no DST offset
73             is specified, it defaults to one hour ahead of the standard offset.
74              
75             A DST-using timezone has one transition to DST and one transition to
76             standard time in each Gregorian year. The transitions may be in either
77             order within the year. If the transitions are in different orders from
78             year to year then the behaviour is undefined; don't rely on it remaining
79             the same in future versions. Likewise, the behaviour is generally
80             undefined if transitions coincide. However, in the L variant,
81             if the rules specify a transition to DST at 00:00 standard time on 1
82             January and a transition to standard time at 24:00 standard time on 31
83             December, which makes the transitions coincide with those of adjacent
84             years, then the timezone is treated as observing DST all year.
85              
86             A transition rule "I" takes the form "I[BI]", where
87             "I" is the rule giving the day on which the transition notionally
88             takes place and "I" is the time of day at which the transition
89             takes place. (A time of day outside the usual 24-hour range can make
90             the transition actually take place on a different day.) The time may be
91             given in hours, or hours and minutes, or hours and minutes and seconds.
92             Hours, minutes, and seconds must be separated by colons. The minutes
93             and seconds must be two digits each. In the POSIX variant, the hours
94             may be one or two digits, with no preceding sign, and the time stated may
95             range from 00:00:00 to 24:59:59 (almost an hour into the following day).
96             In the L variant, the hours may be one to three digits, with
97             optional preceding sign, and the time stated may range from -167:59:59
98             to +167:59:59 (a span of a little over two weeks). If the time is not
99             stated then it defaults to 02:00:00. The time for the transition to DST
100             is interpreted according to the standard offset, and the time for the
101             transition to standard time is interpreted according to the DST offset.
102             (Thus normally the transition time is interpreted according to the offset
103             that prevailed immediately before the transition.)
104              
105             A day rule "I" may take three forms. Firstly, "BI" means the
106             month-day date that is the Ith day of a non-leap year. Thus "B"
107             means the February 28 and "B" means March 1 (even in a leap year).
108             February 29 cannot be specified this way.
109              
110             Secondly, if "I" is just a decimal number, it means the (1+I)th
111             day of the year. February 29 counts in this case, and it is not possible
112             to specify December 31 of a leap year.
113              
114             Thirdly, "I" may have the form "BIB<.>IB<.>I" means day
115             I of the Ith week of the Ith month. The day is given as a single
116             digit, with "B<0>" meaning Sunday and "B<6>" meaning Saturday. The first
117             week contains days 1 to 7 of the month, the second week contains days 8
118             to 14, and so on. If "I" is "B<5>" then the last week of the month
119             (containing its last seven days) is used, rather than the fifth week
120             (which is incomplete).
121              
122             Examples:
123              
124             =over
125              
126             =item MUT-4
127              
128             Mauritius time, since 1907: 4 hours ahead of UT all year.
129              
130             =item EST5EDT,M3.2.0,M11.1.0
131              
132             US Eastern timezone with DST, from 2007 onwards. 5 hours behind UT in
133             winter and 4 hours behind in summer. Changes on the second Sunday in
134             March and the first Sunday in November, in each case at 02:00 local time.
135              
136             =item NST3:30NDT,M3.2.0/0:01,M11.1.0/0:01
137              
138             Newfoundland timezone with DST, from 2007 onwards. 3.5 hours behind UT
139             in winter and 2.5 hours behind in summer. Changes on the second Sunday in
140             March and the first Sunday in November, in each case at 00:01 local time.
141              
142             =item GMT0BST,M3.5.0/1,M10.5.0
143              
144             UK civil time, from 1996 onwards. On UT during the winter, calling
145             it "GMT", and 1 hour ahead of UT during the summer, called "BST".
146             Changes on the last Sunday in March and the last Sunday in October,
147             in each case at 01:00 UT.
148              
149             =item EST-10EST,M10.5.0,M3.5.0/3
150              
151             Australian Eastern timezone, from 2007 onwards. 10 hours ahead of UT in
152             the southern winter (the middle of the calendar year), and 11 hours ahead
153             in the southern summer. Changes to DST on the last Sunday in October,
154             and back on the last Sunday in March, in each case at 02:00 standard time
155             (16:00 UT of the preceding day).
156              
157             =item EET-2EEST,M3.5.4/24,M9.3.6/145
158              
159             Palestinian civil time, from 2012 onwards. 2 hours ahead of UT in winter
160             and 3 hours ahead in summer. Changes at the end (24:00 local time) of
161             the last Thursday in March and 01:00 local time on the Friday following
162             the third Saturday in September (that is, the Friday falling between
163             September 21 and September 27 inclusive). The extended time-of-day "145",
164             meaning 01:00 of the day six days after the nominal day, is only valid
165             in the L variant of the System V syntax. The time-of-day
166             "24" is not so restricted, being permitted by POSIX.
167              
168             =back
169              
170             =cut
171              
172             package DateTime::TimeZone::SystemV;
173              
174 4     4   7513 { use 5.006; }
  4         17  
  4         188  
175 4     4   23 use warnings;
  4         9  
  4         132  
176 4     4   23 use strict;
  4         15  
  4         139  
177              
178 4     4   25 use Carp qw(croak);
  4         6  
  4         308  
179             use Date::ISO8601 0.000
180 4     4   9232 qw(month_days ymd_to_cjdn present_ymd year_days cjdn_to_yd cjdn_to_ywd);
  4         11617  
  4         480  
181 4     4   5031 use Params::Classify 0.000 qw(is_undef is_string);
  4         25269  
  4         11514  
182              
183             our $VERSION = "0.009";
184              
185             my $rdn_epoch_cjdn = 1721425;
186              
187             my $abbrev_rx = qr#[A-Za-z]{3,}|\<[-+0-9A-Za-z]{3,}\>#;
188             my $offset_rx = qr#[-+]?(?:2[0-4]|[01]?[0-9])(?::[0-5][0-9](?::[0-5][0-9])?)?#;
189             my $rule_date_rx = qr#J0*(?:3(?:[0-5][0-9]|6[0-5])|[12]?[0-9][0-9]|[1-9])
190             |0*(?:3(?:[0-5][0-9]|6[0-4])|[12]?[0-9][0-9]|[0-9])
191             |M0*(?:1[0-2]|[1-9])\.0*[1-5]\.0*[0-6]#x;
192             my $posix_rule_time_rx =
193             qr#(?:2[0-4]|[01]?[0-9])(?::[0-5][0-9](?::[0-5][0-9])?)?#;
194             my $tzfile3_rule_time_rx =
195             qr#[-+]?(?:16[0-7]|1[0-5][0-9]|0[0-9][0-9]|[0-9]{1,2})
196             (?::[0-5][0-9](?::[0-5][0-9])?)?#x;
197             my $posix_rule_dt_rx = qr#${rule_date_rx}(?:/${posix_rule_time_rx})?#o;
198             my $tzfile3_rule_dt_rx = qr#${rule_date_rx}(?:/${tzfile3_rule_time_rx})?#o;
199             my $posix_tz_rx = qr#${abbrev_rx}${offset_rx}
200             (?:${abbrev_rx}(?:${offset_rx})?
201             (?:,${posix_rule_dt_rx},${posix_rule_dt_rx})?)?#xo;
202             my $tzfile3_tz_rx = qr#${abbrev_rx}${offset_rx}
203             (?:${abbrev_rx}(?:${offset_rx})?
204             (?:,${tzfile3_rule_dt_rx},${tzfile3_rule_dt_rx})?)?#xo;
205              
206             my %tz_rx = (
207             posix => $posix_tz_rx,
208             tzfile3 => $tzfile3_tz_rx,
209             );
210              
211             sub _parse_abbrev($) {
212 58     58   87 my($spec) = @_;
213 58 100       245 return $spec =~ /\A\<(.*)\>\z/s ? $1 : $spec;
214             }
215              
216             sub _parse_offset($) {
217 64     64   95 my($spec) = @_;
218 64         312 my($sign, $h, $m, $s) =
219             ($spec =~ /\A([-+]?)([0-9]+)(?::([0-9]+)(?::([0-9]+))?)?\z/);
220 64   100     514 return ($sign eq "-" ? 1 : -1) *
221             ($h*3600 + (defined($m) ? $m*60 + (defined($s) ? $s : 0) : 0))
222             || 0;
223             }
224              
225             sub _parse_rule($$) {
226 52     52   79 my($spec, $offset) = @_;
227 52         148 my($drule, $tod) = split(m#/#, $spec);
228             return {
229 52 100       286 drule => $drule,
230             sod => -$offset +
231             (defined($tod) ? -_parse_offset($tod) : 7200),
232             };
233             }
234              
235             =head1 CONSTRUCTOR
236              
237             =over
238              
239             =item DateTime::TimeZone::SystemV->new(ATTR => VALUE, ...)
240              
241             Constructs and returns a L-compatible timezone object that
242             implements the timezone described by the recipe given in the arguments.
243             The following attributes may be given:
244              
245             =over
246              
247             =item B
248              
249             Name for the timezone object. This will be returned by the C
250             method described below, and will be included in certain error messages.
251             If not given, then the recipe is used as the timezone name.
252              
253             =item B
254              
255             The short textual timezone recipe, as described in L
256             RECIPE SYSTEM>. Must be given.
257              
258             =item B
259              
260             Keyword identifying the particular variant of the recipe system according
261             to which the recipe is to be interpreted. It may be:
262              
263             =over
264              
265             =item B (default)
266              
267             As specified by POSIX.1.
268              
269             =item B
270              
271             As specified by version 3 of the L file format.
272              
273             =back
274              
275             =back
276              
277             =item DateTime::TimeZone::SystemV->new(RECIPE)
278              
279             Simpler way to invoke the above constructor in the usual case. Only the
280             recipe is given; it will be interpreted according to POSIX system,
281             and the recipe will also be used as the timezone name.
282              
283             =cut
284              
285             sub new {
286 56     56 1 18645 my $class = shift;
287 56 100       217 unshift @_, "recipe" if @_ == 1;
288 56         196 my $self = bless({}, $class);
289 56         91 my $recipe;
290             my $system;
291 56         147 while(@_) {
292 70         111 my $attr = shift;
293 70         106 my $value = shift;
294 70 100       241 if($attr eq "name") {
    100          
    100          
295 10 100       1730 croak "timezone name specified redundantly"
296             if exists $self->{name};
297 9 100       470 croak "timezone name must be a string"
298             unless is_string($value);
299 5         18 $self->{name} = $value;
300             } elsif($attr eq "recipe") {
301 43 100       230 croak "recipe specified redundantly"
302             if defined $recipe;
303 42 100       477 croak "recipe must be a string"
304             unless is_string($value);
305 38         131 $recipe = $value;
306             } elsif($attr eq "system") {
307 16 100       165 croak "system identifier specified redundantly"
308             if defined $system;
309 15 100       498 croak "system identifier must be a string"
310             unless is_string($value);
311 11 100       241 croak "system identifier not recognised"
312             unless exists $tz_rx{$value};
313 10         26 $system = $value;
314             } else {
315 1         93 croak "unrecognised attribute `$attr'";
316             }
317             }
318 39 100       353 croak "recipe not specified" unless defined $recipe;
319 37 100       179 $self->{name} = $recipe unless exists $self->{name};
320 37 100       95 $system = "posix" unless defined $system;
321 37 100       3699 croak "not a valid SysV-style timezone recipe"
322             unless $recipe =~ /\A$tz_rx{$system}\z/;
323 32         359 $recipe =~ /\A($abbrev_rx)($offset_rx)/og;
324 32         115 my($std_abbrev, $std_offset) = ($1, $2);
325 32         85 $self->{std_abbrev} = _parse_abbrev($std_abbrev);
326 32         80 $self->{std_offset} = _parse_offset($std_offset);
327 32 100       129 return $self if $recipe =~ /\G\z/gc;
328 26         381 $recipe =~ /\G($abbrev_rx)($offset_rx)?/g;
329 26         81 my($dst_abbrev, $dst_offset) = ($1, $2);
330 26         53 $self->{dst_abbrev} = _parse_abbrev($dst_abbrev);
331 26 100       93 $self->{dst_offset} = defined($dst_offset) ?
332             _parse_offset($dst_offset) : $self->{std_offset} + 3600;
333 26         34 my($start_rule, $end_rule);
334 26 100       119 if($recipe =~ /\G,(.*),(.*)/g) {
335 16         53 ($start_rule, $end_rule) = ($1, $2);
336             } else {
337             # default to US 1976 rules, which is what the ruleless
338             # old SysV style specs were expected to do
339 10         23 ($start_rule, $end_rule) = ("M4.5.0", "M10.5.0");
340             }
341 26         74 $self->{start_rule} = _parse_rule($start_rule, $self->{std_offset});
342 26         69 $self->{end_rule} = _parse_rule($end_rule, $self->{dst_offset});
343 26 50 100     177 if($system eq "tzfile3" &&
      66        
      66        
      33        
344             $self->{start_rule}->{drule} =~ /\A(?:J0*1|0+)\z/ &&
345             $self->{start_rule}->{sod} == -$self->{std_offset} &&
346             $self->{end_rule}->{drule} =~ /\AJ0*365\z/ &&
347             $self->{end_rule}->{sod} == 86400-$self->{std_offset}) {
348             delete $self->{$_}
349 2         14 foreach qw(std_abbrev std_offset start_rule end_rule);
350             }
351 26         128 return $self;
352             }
353              
354             =back
355              
356             =head1 METHODS
357              
358             These methods are all part of the L interface.
359             See that class for the general meaning of these methods; the documentation
360             below only comments on the specific behaviour of this class.
361              
362             =head2 Identification
363              
364             =over
365              
366             =item $tz->is_floating
367              
368             Returns false.
369              
370             =cut
371              
372 2     2 1 544 sub is_floating { 0 }
373              
374             =item $tz->is_utc
375              
376             Returns false.
377              
378             =cut
379              
380 2     2 1 34 sub is_utc { 0 }
381              
382             =item $tz->is_olson
383              
384             Returns false.
385              
386             =cut
387              
388 2     2 1 8 sub is_olson { 0 }
389              
390             =item $tz->category
391              
392             Returns C, because the category concept doesn't properly apply
393             to these timezones.
394              
395             =cut
396              
397 2     2 1 10 sub category { undef }
398              
399             =item $tz->name
400              
401             Returns the timezone name. Usually this is the recipe that was supplied
402             to the constructor, but it can be overridden by the constructor's B
403             attribute.
404              
405             =cut
406              
407 6     6 1 977 sub name { $_[0]->{name} }
408              
409             =back
410              
411             =head2 Offsets
412              
413             =over
414              
415             =item $tz->has_dst_changes
416              
417             Returns a truth value indicating whether the timezone includes a DST offset.
418              
419             =cut
420              
421 2     2 1 10 sub has_dst_changes { exists $_[0]->{dst_abbrev} }
422              
423             =item $tz->is_dst_for_datetime(DT)
424              
425             I
must be a L-compatible object (specifically, it must
426             implement the C method). Returns a truth value indicating
427             whether the timezone is on DST at the instant represented by I
.
428              
429             =cut
430              
431             sub _rule_doy($$) {
432 4483     4483   6465 my($drule, $year) = @_;
433 4483 100       19751 if($drule =~ /\AJ([0-9]+)\z/) {
    100          
    50          
434 1497         2480 my $j = $1;
435 1497 100       2808 if($j < 60) {
436 390         1162 return $j;
437             } else {
438 1107         2862 return year_days($year) - 365 + $j;
439             }
440             } elsif($drule =~ /\A([0-9]+)\z/) {
441 384         1237 return 1 + $1;
442             } elsif($drule =~ /\AM([0-9]+)\.([0-9]+)\.([0-9]+)\z/) {
443 2602         6302 my($m, $w, $dow) = ($1, $2, $3);
444 2602 100       7477 my $fdom = ($w == 5 ? month_days($year, $m) : $w*7) - 6;
445 2602         15533 my(undef, undef, $fdow) =
446             cjdn_to_ywd(ymd_to_cjdn($year, $m, $fdom));
447 2602         309938 my $dom = $fdom + ($dow + 7 - $fdow) % 7;
448 2602         5990 my(undef, $doy) = cjdn_to_yd(ymd_to_cjdn($year, $m, $dom));
449 2602         155287 return $doy;
450             } else {
451 0         0 die "internal error: unrecognised day rule";
452             }
453             }
454              
455             sub _is_dst_for_utc_rdn_sod {
456 907     907   1296 my($self, $rdn, $sod) = @_;
457 907         3016 my($year, $doy) = cjdn_to_yd($rdn + $rdn_epoch_cjdn);
458 907         23061 my $soy = $doy * 86400 + $sod;
459 907         1072 my @latest_change;
460 907         1442 foreach my $change_type (qw(end_rule start_rule)) {
461 1814         4606 for(my $y = $year+1, my $doff = year_days($year); ;
462             $doff -= year_days(--$y)) {
463 4483         61664 my $change_soy =
464             ($doff + _rule_doy($self->{$change_type}
465             ->{drule}, $y))
466             * 86400 + $self->{$change_type}->{sod};
467 4483 100       25342 if($change_soy <= $soy) {
468 1814         2312 push @latest_change, $change_soy;
469 1814         3761 last;
470             }
471             }
472             }
473 907         5456 return $latest_change[1] > $latest_change[0];
474             }
475              
476             sub is_dst_for_datetime {
477 816     816 1 137605 my($self, $dt) = @_;
478 816 100       2526 return 0 unless exists $self->{dst_abbrev};
479 789 100       2234 return 1 unless exists $self->{std_abbrev};
480 735         1894 my($utc_rdn, $utc_sod) = $dt->utc_rd_values;
481 735 100       4562 $utc_sod = 86399 if $utc_sod >= 86400;
482 735         1747 return $self->_is_dst_for_utc_rdn_sod($utc_rdn, $utc_sod);
483             }
484              
485             =item $tz->offset_for_datetime(DT)
486              
487             I
must be a L-compatible object (specifically, it must
488             implement the C method). Returns the offset from UT that
489             is in effect at the instant represented by I
, in seconds.
490              
491             =cut
492              
493             sub offset_for_datetime {
494 272     272 1 542 my($self, $dt) = @_;
495 272 100       617 return $self->{$self->is_dst_for_datetime($dt) ?
496             "dst_offset" : "std_offset"};
497             }
498              
499             =item $tz->short_name_for_datetime(DT)
500              
501             I
must be a L-compatible object (specifically, it
502             must implement the C method). Returns the time scale
503             abbreviation for the offset that is in effect at the instant represented
504             by I
.
505              
506             =cut
507              
508             sub short_name_for_datetime {
509 272     272 1 1030 my($self, $dt) = @_;
510 272 100       631 return $self->{$self->is_dst_for_datetime($dt) ?
511             "dst_abbrev" : "std_abbrev"};
512             }
513              
514             =item $tz->offset_for_local_datetime(DT)
515              
516             I
must be a L-compatible object (specifically, it
517             must implement the C method). Takes the local
518             time represented by I
(regardless of what absolute time it also
519             represents), and interprets that as a local time in the timezone of the
520             timezone object (not the timezone used in I
). Returns the offset
521             from UT that is in effect at that local time, in seconds.
522              
523             If the local time given is ambiguous due to a nearby offset change, the
524             numerically lower offset (usually the standard one) is returned with no
525             warning of the situation. If the local time given does not exist due
526             to a nearby offset change, the method Cs saying so.
527              
528             =cut
529              
530             sub _local_to_utc_rdn_sod($$$) {
531 172     172   206 my($rdn, $sod, $offset) = @_;
532 172         188 $sod -= $offset;
533 172         350 while($sod < 0) {
534 0         0 $rdn--;
535 0         0 $sod += 86400;
536             }
537 172         332 while($sod >= 86400) {
538 36         35 $rdn++;
539 36         67 $sod -= 86400;
540             }
541 172         318 return ($rdn, $sod);
542             }
543              
544             sub _is_dst_for_local_datetime {
545 96     96   150 my($self, $dt) = @_;
546 96 100       256 return 0 unless exists $self->{dst_abbrev};
547 90 100       219 return 1 unless exists $self->{std_abbrev};
548 86         205 my($lcl_rdn, $lcl_sod) = $dt->local_rd_values;
549 86 50       431 $lcl_sod = 86399 if $lcl_sod >= 86400;
550 86         198 my($std_rdn, $std_sod) =
551             _local_to_utc_rdn_sod($lcl_rdn, $lcl_sod, $self->{std_offset});
552 86         177 my($dst_rdn, $dst_sod) =
553             _local_to_utc_rdn_sod($lcl_rdn, $lcl_sod, $self->{dst_offset});
554 86         183 my $std_ok = !$self->_is_dst_for_utc_rdn_sod($std_rdn, $std_sod);
555 86         196 my $dst_ok = $self->_is_dst_for_utc_rdn_sod($dst_rdn, $dst_sod);
556 86 100       168 if($std_ok) {
557 43 100       76 if($dst_ok) {
558 4         27 return $self->{std_offset} > $self->{dst_offset};
559             } else {
560 39         218 return 0;
561             }
562             } else {
563 43 100       82 if($dst_ok) {
564 39         212 return 1;
565             } else {
566 4         9 croak "local time @{[
  4         262  
567 4         16 present_ymd($lcl_rdn + $rdn_epoch_cjdn)
568 4         604 ]}T@{[
569             sprintf(q(%02d:%02d:%02d),
570             int($lcl_sod/3600),
571             int($lcl_sod/60)%60,
572             $lcl_sod%60)
573             ]} does not exist in the @{[$self->{name}]} timezone ".
574             "due to offset change";
575             }
576             }
577             }
578              
579             sub offset_for_local_datetime {
580 96     96 1 5164 my($self, $dt) = @_;
581 96 100       213 return $self->{$self->_is_dst_for_local_datetime($dt) ?
582             "dst_offset" : "std_offset"};
583             }
584              
585             =back
586              
587             =head1 SEE ALSO
588              
589             L,
590             L,
591             L,
592             L
593              
594             =head1 AUTHOR
595              
596             Andrew Main (Zefram)
597              
598             =head1 COPYRIGHT
599              
600             Copyright (C) 2007, 2009, 2010, 2011, 2012, 2013
601             Andrew Main (Zefram)
602              
603             =head1 LICENSE
604              
605             This module is free software; you can redistribute it and/or modify it
606             under the same terms as Perl itself.
607              
608             =cut
609              
610             1;