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[B>I]", 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; |