line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package DateTime::TimeZone; |
2
|
|
|
|
|
|
|
|
3
|
3
|
|
|
3
|
|
101689
|
use 5.008004; |
|
3
|
|
|
|
|
29
|
|
4
|
|
|
|
|
|
|
|
5
|
3
|
|
|
3
|
|
15
|
use strict; |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
57
|
|
6
|
3
|
|
|
3
|
|
12
|
use warnings; |
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
100
|
|
7
|
3
|
|
|
3
|
|
922
|
use namespace::autoclean; |
|
3
|
|
|
|
|
35914
|
|
|
3
|
|
|
|
|
14
|
|
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
our $VERSION = '2.60'; |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
# Note that while we make use of DateTime::Duration in this module if we |
12
|
|
|
|
|
|
|
# actually try to load it here all hell breaks loose with circular |
13
|
|
|
|
|
|
|
# dependencies. |
14
|
3
|
|
|
3
|
|
2228
|
use DateTime::TimeZone::Catalog; |
|
3
|
|
|
|
|
12
|
|
|
3
|
|
|
|
|
127
|
|
15
|
3
|
|
|
3
|
|
1224
|
use DateTime::TimeZone::Floating; |
|
3
|
|
|
|
|
11
|
|
|
3
|
|
|
|
|
108
|
|
16
|
3
|
|
|
3
|
|
1420
|
use DateTime::TimeZone::Local; |
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
92
|
|
17
|
3
|
|
|
3
|
|
23
|
use DateTime::TimeZone::OffsetOnly; |
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
66
|
|
18
|
3
|
|
|
3
|
|
1242
|
use DateTime::TimeZone::OlsonDB::Change; |
|
3
|
|
|
|
|
14
|
|
|
3
|
|
|
|
|
94
|
|
19
|
3
|
|
|
3
|
|
408
|
use DateTime::TimeZone::UTC; |
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
81
|
|
20
|
3
|
|
|
3
|
|
17
|
use Module::Runtime qw( require_module ); |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
16
|
|
21
|
3
|
|
|
3
|
|
592
|
use Params::ValidationCompiler 0.13 qw( validation_for ); |
|
3
|
|
|
|
|
25530
|
|
|
3
|
|
|
|
|
149
|
|
22
|
3
|
|
|
3
|
|
521
|
use Specio::Library::Builtins; |
|
3
|
|
|
|
|
92096
|
|
|
3
|
|
|
|
|
25
|
|
23
|
3
|
|
|
3
|
|
29613
|
use Specio::Library::String; |
|
3
|
|
|
|
|
10878
|
|
|
3
|
|
|
|
|
19
|
|
24
|
3
|
|
|
3
|
|
6482
|
use Try::Tiny; |
|
3
|
|
|
|
|
19
|
|
|
3
|
|
|
|
|
267
|
|
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
## no critic (ValuesAndExpressions::ProhibitConstantPragma) |
27
|
3
|
|
|
3
|
|
29
|
use constant INFINITY => 100**1000; |
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
286
|
|
28
|
3
|
|
|
3
|
|
18
|
use constant NEG_INFINITY => -1 * ( 100**1000 ); |
|
3
|
|
|
|
|
11
|
|
|
3
|
|
|
|
|
210
|
|
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
# the offsets for each span element |
31
|
3
|
|
|
3
|
|
18
|
use constant UTC_START => 0; |
|
3
|
|
|
|
|
11
|
|
|
3
|
|
|
|
|
174
|
|
32
|
3
|
|
|
3
|
|
22
|
use constant UTC_END => 1; |
|
3
|
|
|
|
|
15
|
|
|
3
|
|
|
|
|
162
|
|
33
|
3
|
|
|
3
|
|
17
|
use constant LOCAL_START => 2; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
188
|
|
34
|
3
|
|
|
3
|
|
19
|
use constant LOCAL_END => 3; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
191
|
|
35
|
3
|
|
|
3
|
|
18
|
use constant OFFSET => 4; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
143
|
|
36
|
3
|
|
|
3
|
|
17
|
use constant IS_DST => 5; |
|
3
|
|
|
|
|
18
|
|
|
3
|
|
|
|
|
169
|
|
37
|
3
|
|
|
3
|
|
18
|
use constant SHORT_NAME => 6; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
11031
|
|
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
my %SpecialName = map { $_ => 1 } |
40
|
|
|
|
|
|
|
qw( EST MST HST CET EET MET WET EST5EDT CST6CDT MST7MDT PST8PDT ); |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
{ |
43
|
|
|
|
|
|
|
my $validator = validation_for( |
44
|
|
|
|
|
|
|
name => '_check_new_params', |
45
|
|
|
|
|
|
|
name_is_optional => 1, |
46
|
|
|
|
|
|
|
params => { |
47
|
|
|
|
|
|
|
name => { |
48
|
|
|
|
|
|
|
type => t('NonEmptyStr'), |
49
|
|
|
|
|
|
|
}, |
50
|
|
|
|
|
|
|
}, |
51
|
|
|
|
|
|
|
); |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
sub new { |
54
|
1
|
|
|
1
|
1
|
179
|
shift; |
55
|
1
|
|
|
|
|
24
|
my %p = $validator->(@_); |
56
|
|
|
|
|
|
|
|
57
|
1
|
50
|
|
|
|
53
|
if ( exists $DateTime::TimeZone::Catalog::LINKS{ $p{name} } ) { |
|
|
50
|
|
|
|
|
|
58
|
0
|
|
|
|
|
0
|
$p{name} = $DateTime::TimeZone::Catalog::LINKS{ $p{name} }; |
59
|
|
|
|
|
|
|
} |
60
|
|
|
|
|
|
|
elsif ( exists $DateTime::TimeZone::Catalog::LINKS{ uc $p{name} } ) { |
61
|
0
|
|
|
|
|
0
|
$p{name} = $DateTime::TimeZone::Catalog::LINKS{ uc $p{name} }; |
62
|
|
|
|
|
|
|
} |
63
|
|
|
|
|
|
|
|
64
|
1
|
50
|
33
|
|
|
10
|
unless ( $p{name} =~ m{/} |
65
|
|
|
|
|
|
|
|| $SpecialName{ $p{name} } ) { |
66
|
1
|
50
|
|
|
|
5
|
if ( $p{name} eq 'floating' ) { |
67
|
0
|
|
|
|
|
0
|
return DateTime::TimeZone::Floating->instance; |
68
|
|
|
|
|
|
|
} |
69
|
|
|
|
|
|
|
|
70
|
1
|
50
|
|
|
|
3
|
if ( $p{name} eq 'local' ) { |
71
|
1
|
|
|
|
|
8
|
return DateTime::TimeZone::Local->TimeZone(); |
72
|
|
|
|
|
|
|
} |
73
|
|
|
|
|
|
|
|
74
|
0
|
0
|
0
|
|
|
0
|
if ( $p{name} eq 'UTC' || $p{name} eq 'Z' ) { |
75
|
0
|
|
|
|
|
0
|
return DateTime::TimeZone::UTC->instance; |
76
|
|
|
|
|
|
|
} |
77
|
|
|
|
|
|
|
|
78
|
0
|
|
|
|
|
0
|
return DateTime::TimeZone::OffsetOnly->new( offset => $p{name} ); |
79
|
|
|
|
|
|
|
} |
80
|
|
|
|
|
|
|
|
81
|
0
|
0
|
|
|
|
0
|
if ( $p{name} =~ m{Etc/(?:GMT|UTC)(\+|-)(\d{1,2})}i ) { |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
# Etc/GMT+4 is actually UTC-4. For more info, see |
84
|
|
|
|
|
|
|
# https://data.iana.org/time-zones/tzdb/etcetera |
85
|
0
|
0
|
|
|
|
0
|
my $sign = $1 eq '-' ? '+' : '-'; |
86
|
0
|
|
|
|
|
0
|
my $hours = $2; |
87
|
0
|
0
|
|
|
|
0
|
die "The timezone '$p{name}' is an invalid name.\n" |
88
|
|
|
|
|
|
|
unless $hours <= 14; |
89
|
0
|
|
|
|
|
0
|
return DateTime::TimeZone::OffsetOnly->new( |
90
|
|
|
|
|
|
|
offset => "${sign}${hours}:00" ); |
91
|
|
|
|
|
|
|
} |
92
|
|
|
|
|
|
|
|
93
|
0
|
|
|
|
|
0
|
my $subclass = $p{name}; |
94
|
0
|
|
|
|
|
0
|
$subclass =~ s{/}{::}g; |
95
|
0
|
|
|
|
|
0
|
$subclass =~ s/-(\d)/_Minus$1/; |
96
|
0
|
|
|
|
|
0
|
$subclass =~ s/\+/_Plus/; |
97
|
0
|
|
|
|
|
0
|
$subclass =~ s/-/_/g; |
98
|
|
|
|
|
|
|
|
99
|
0
|
|
|
|
|
0
|
my $real_class = "DateTime::TimeZone::$subclass"; |
100
|
|
|
|
|
|
|
|
101
|
0
|
0
|
|
|
|
0
|
die "The timezone '$p{name}' is an invalid name.\n" |
102
|
|
|
|
|
|
|
unless $real_class =~ /^\w+(::\w+)*$/; |
103
|
|
|
|
|
|
|
|
104
|
0
|
0
|
|
|
|
0
|
unless ( $real_class->can('instance') ) { |
105
|
0
|
|
|
|
|
0
|
($real_class) |
106
|
|
|
|
|
|
|
= $real_class =~ m{\A([a-zA-Z0-9_]+(?:::[a-zA-Z0-9_]+)*)\z}; |
107
|
|
|
|
|
|
|
|
108
|
0
|
|
|
|
|
0
|
my $e; |
109
|
|
|
|
|
|
|
try { |
110
|
|
|
|
|
|
|
## no critic (Variables::RequireInitializationForLocalVars) |
111
|
0
|
|
|
0
|
|
0
|
local $SIG{__DIE__}; |
112
|
0
|
|
|
|
|
0
|
require_module($real_class); |
113
|
|
|
|
|
|
|
} |
114
|
|
|
|
|
|
|
catch { |
115
|
0
|
|
|
0
|
|
0
|
$e = $_; |
116
|
0
|
|
|
|
|
0
|
}; |
117
|
|
|
|
|
|
|
|
118
|
0
|
0
|
|
|
|
0
|
if ($e) { |
119
|
0
|
|
|
|
|
0
|
my $regex = join '.', split /::/, $real_class; |
120
|
0
|
|
|
|
|
0
|
$regex .= '\\.pm'; |
121
|
|
|
|
|
|
|
|
122
|
0
|
0
|
|
|
|
0
|
if ( $e =~ /^Can't locate $regex/i ) { |
123
|
0
|
|
|
|
|
0
|
die |
124
|
|
|
|
|
|
|
"The timezone '$p{name}' could not be loaded, or is an invalid name.\n"; |
125
|
|
|
|
|
|
|
} |
126
|
|
|
|
|
|
|
else { |
127
|
0
|
|
|
|
|
0
|
die $e; |
128
|
|
|
|
|
|
|
} |
129
|
|
|
|
|
|
|
} |
130
|
|
|
|
|
|
|
} |
131
|
|
|
|
|
|
|
|
132
|
0
|
|
|
|
|
0
|
my $zone = $real_class->instance( name => $p{name}, is_olson => 1 ); |
133
|
|
|
|
|
|
|
|
134
|
0
|
0
|
|
|
|
0
|
if ( $zone->is_olson() ) { |
135
|
0
|
0
|
|
|
|
0
|
my $object_version |
136
|
|
|
|
|
|
|
= $zone->can('olson_version') |
137
|
|
|
|
|
|
|
? $zone->olson_version() |
138
|
|
|
|
|
|
|
: 'unknown'; |
139
|
0
|
|
|
|
|
0
|
my $catalog_version = DateTime::TimeZone::Catalog->OlsonVersion(); |
140
|
|
|
|
|
|
|
|
141
|
0
|
0
|
|
|
|
0
|
if ( $object_version ne $catalog_version ) { |
142
|
0
|
|
|
|
|
0
|
warn |
143
|
|
|
|
|
|
|
"Loaded $real_class, which is from a different version ($object_version) of the Olson database than this installation of DateTime::TimeZone ($catalog_version).\n"; |
144
|
|
|
|
|
|
|
} |
145
|
|
|
|
|
|
|
} |
146
|
|
|
|
|
|
|
|
147
|
0
|
|
|
|
|
0
|
return $zone; |
148
|
|
|
|
|
|
|
} |
149
|
|
|
|
|
|
|
} |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
{ |
152
|
|
|
|
|
|
|
my $validator = validation_for( |
153
|
|
|
|
|
|
|
name => '_check_init_params', |
154
|
|
|
|
|
|
|
name_is_optional => 1, |
155
|
|
|
|
|
|
|
params => { |
156
|
|
|
|
|
|
|
name => { |
157
|
|
|
|
|
|
|
type => t('NonEmptyStr'), |
158
|
|
|
|
|
|
|
}, |
159
|
|
|
|
|
|
|
spans => { |
160
|
|
|
|
|
|
|
type => t('ArrayRef'), |
161
|
|
|
|
|
|
|
}, |
162
|
|
|
|
|
|
|
is_olson => { |
163
|
|
|
|
|
|
|
type => t('Bool'), |
164
|
|
|
|
|
|
|
default => 0, |
165
|
|
|
|
|
|
|
}, |
166
|
|
|
|
|
|
|
}, |
167
|
|
|
|
|
|
|
); |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines) |
170
|
|
|
|
|
|
|
sub _init { |
171
|
0
|
|
|
0
|
|
0
|
my $class = shift; |
172
|
0
|
|
|
|
|
0
|
my %p = $validator->(@_); |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
my $self = bless { |
175
|
|
|
|
|
|
|
name => $p{name}, |
176
|
|
|
|
|
|
|
spans => $p{spans}, |
177
|
|
|
|
|
|
|
is_olson => $p{is_olson}, |
178
|
0
|
|
|
|
|
0
|
}, $class; |
179
|
|
|
|
|
|
|
|
180
|
0
|
|
|
|
|
0
|
foreach my $k (qw( last_offset last_observance rules max_year )) { |
181
|
0
|
|
|
|
|
0
|
my $m = "_$k"; |
182
|
0
|
0
|
|
|
|
0
|
$self->{$k} = $self->$m() if $self->can($m); |
183
|
|
|
|
|
|
|
} |
184
|
|
|
|
|
|
|
|
185
|
0
|
|
|
|
|
0
|
return $self; |
186
|
|
|
|
|
|
|
} |
187
|
|
|
|
|
|
|
## use critic |
188
|
|
|
|
|
|
|
} |
189
|
|
|
|
|
|
|
|
190
|
0
|
|
|
0
|
1
|
0
|
sub is_olson { $_[0]->{is_olson} } |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
sub is_dst_for_datetime { |
193
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
194
|
|
|
|
|
|
|
|
195
|
0
|
|
|
|
|
0
|
my $span = $self->_span_for_datetime( 'utc', $_[0] ); |
196
|
|
|
|
|
|
|
|
197
|
0
|
|
|
|
|
0
|
return $span->[IS_DST]; |
198
|
|
|
|
|
|
|
} |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
sub offset_for_datetime { |
201
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
202
|
|
|
|
|
|
|
|
203
|
0
|
|
|
|
|
0
|
my $span = $self->_span_for_datetime( 'utc', $_[0] ); |
204
|
|
|
|
|
|
|
|
205
|
0
|
|
|
|
|
0
|
return $span->[OFFSET]; |
206
|
|
|
|
|
|
|
} |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
sub offset_for_local_datetime { |
209
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
210
|
|
|
|
|
|
|
|
211
|
0
|
|
|
|
|
0
|
my $span = $self->_span_for_datetime( 'local', $_[0] ); |
212
|
|
|
|
|
|
|
|
213
|
0
|
|
|
|
|
0
|
return $span->[OFFSET]; |
214
|
|
|
|
|
|
|
} |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
sub short_name_for_datetime { |
217
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
218
|
|
|
|
|
|
|
|
219
|
0
|
|
|
|
|
0
|
my $span = $self->_span_for_datetime( 'utc', $_[0] ); |
220
|
|
|
|
|
|
|
|
221
|
0
|
|
|
|
|
0
|
return $span->[SHORT_NAME]; |
222
|
|
|
|
|
|
|
} |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
sub _span_for_datetime { |
225
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
226
|
0
|
|
|
|
|
0
|
my $type = shift; |
227
|
0
|
|
|
|
|
0
|
my $dt = shift; |
228
|
|
|
|
|
|
|
|
229
|
0
|
|
|
|
|
0
|
my $method = $type . '_rd_as_seconds'; |
230
|
|
|
|
|
|
|
|
231
|
0
|
0
|
|
|
|
0
|
my $end = $type eq 'utc' ? UTC_END : LOCAL_END; |
232
|
|
|
|
|
|
|
|
233
|
0
|
|
|
|
|
0
|
my $span; |
234
|
0
|
|
|
|
|
0
|
my $seconds = $dt->$method(); |
235
|
0
|
0
|
|
|
|
0
|
if ( $seconds < $self->max_span->[$end] ) { |
236
|
0
|
|
|
|
|
0
|
$span = $self->_spans_binary_search( $type, $seconds ); |
237
|
|
|
|
|
|
|
} |
238
|
|
|
|
|
|
|
else { |
239
|
0
|
|
|
|
|
0
|
my $until_year = $dt->utc_year + 1; |
240
|
0
|
|
|
|
|
0
|
$span = $self->_generate_spans_until_match( |
241
|
|
|
|
|
|
|
$until_year, $seconds, |
242
|
|
|
|
|
|
|
$type |
243
|
|
|
|
|
|
|
); |
244
|
|
|
|
|
|
|
} |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
# This means someone gave a local time that doesn't exist |
247
|
|
|
|
|
|
|
# (like during a transition into savings time) |
248
|
0
|
0
|
|
|
|
0
|
unless ( defined $span ) { |
249
|
0
|
|
|
|
|
0
|
my $err = 'Invalid local time for date'; |
250
|
0
|
0
|
|
|
|
0
|
$err .= q{ } . $dt->iso8601 if $type eq 'utc'; |
251
|
0
|
|
|
|
|
0
|
$err .= ' in time zone: ' . $self->name; |
252
|
0
|
|
|
|
|
0
|
$err .= "\n"; |
253
|
|
|
|
|
|
|
|
254
|
0
|
|
|
|
|
0
|
die $err; |
255
|
|
|
|
|
|
|
} |
256
|
|
|
|
|
|
|
|
257
|
0
|
|
|
|
|
0
|
return $span; |
258
|
|
|
|
|
|
|
} |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
sub _spans_binary_search { |
261
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
262
|
0
|
|
|
|
|
0
|
my ( $type, $seconds ) = @_; |
263
|
|
|
|
|
|
|
|
264
|
0
|
|
|
|
|
0
|
my ( $start, $end ) = _keys_for_type($type); |
265
|
|
|
|
|
|
|
|
266
|
0
|
|
|
|
|
0
|
my $min = 0; |
267
|
0
|
|
|
|
|
0
|
my $max = scalar @{ $self->{spans} } + 1; |
|
0
|
|
|
|
|
0
|
|
268
|
0
|
|
|
|
|
0
|
my $i = int( $max / 2 ); |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
# special case for when there are only 2 spans |
271
|
0
|
0
|
0
|
|
|
0
|
$i++ if $max % 2 && $max != 3; |
272
|
|
|
|
|
|
|
|
273
|
0
|
0
|
|
|
|
0
|
$i = 0 if @{ $self->{spans} } == 1; |
|
0
|
|
|
|
|
0
|
|
274
|
|
|
|
|
|
|
|
275
|
0
|
|
|
|
|
0
|
while (1) { |
276
|
0
|
|
|
|
|
0
|
my $current = $self->{spans}[$i]; |
277
|
|
|
|
|
|
|
|
278
|
0
|
0
|
|
|
|
0
|
if ( $seconds < $current->[$start] ) { |
|
|
0
|
|
|
|
|
|
279
|
0
|
|
|
|
|
0
|
$max = $i; |
280
|
0
|
|
|
|
|
0
|
my $c = int( ( $i - $min ) / 2 ); |
281
|
0
|
|
0
|
|
|
0
|
$c ||= 1; |
282
|
|
|
|
|
|
|
|
283
|
0
|
|
|
|
|
0
|
$i -= $c; |
284
|
|
|
|
|
|
|
|
285
|
0
|
0
|
|
|
|
0
|
return if $i < $min; |
286
|
|
|
|
|
|
|
} |
287
|
|
|
|
|
|
|
elsif ( $seconds >= $current->[$end] ) { |
288
|
0
|
|
|
|
|
0
|
$min = $i; |
289
|
0
|
|
|
|
|
0
|
my $c = int( ( $max - $i ) / 2 ); |
290
|
0
|
|
0
|
|
|
0
|
$c ||= 1; |
291
|
|
|
|
|
|
|
|
292
|
0
|
|
|
|
|
0
|
$i += $c; |
293
|
|
|
|
|
|
|
|
294
|
0
|
0
|
|
|
|
0
|
return if $i >= $max; |
295
|
|
|
|
|
|
|
} |
296
|
|
|
|
|
|
|
else { |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
# Special case for overlapping ranges because of DST and |
299
|
|
|
|
|
|
|
# other weirdness (like Alaska's change when bought from |
300
|
|
|
|
|
|
|
# Russia by the US). Always prefer latest span. |
301
|
0
|
0
|
0
|
|
|
0
|
if ( $current->[IS_DST] && $type eq 'local' ) { |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
# Asia/Dhaka in 2009j goes into DST without any known |
304
|
|
|
|
|
|
|
# end-of-DST date (wtf, Bangladesh). |
305
|
0
|
0
|
|
|
|
0
|
return $current if $current->[UTC_END] == INFINITY; |
306
|
|
|
|
|
|
|
|
307
|
0
|
|
|
|
|
0
|
my $next = $self->{spans}[ $i + 1 ]; |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
# Sometimes we will get here and the span we're |
310
|
|
|
|
|
|
|
# looking at is the last that's been generated so far. |
311
|
|
|
|
|
|
|
# We need to try to generate one more or else we run |
312
|
|
|
|
|
|
|
# out. |
313
|
0
|
|
0
|
|
|
0
|
$next ||= $self->_generate_next_span; |
314
|
|
|
|
|
|
|
|
315
|
0
|
0
|
|
|
|
0
|
die "No next span in $self->{max_year}" unless defined $next; |
316
|
|
|
|
|
|
|
|
317
|
0
|
0
|
0
|
|
|
0
|
if ( ( !$next->[IS_DST] ) |
|
|
|
0
|
|
|
|
|
318
|
|
|
|
|
|
|
&& $next->[$start] <= $seconds |
319
|
|
|
|
|
|
|
&& $seconds <= $next->[$end] ) { |
320
|
0
|
|
|
|
|
0
|
return $next; |
321
|
|
|
|
|
|
|
} |
322
|
|
|
|
|
|
|
} |
323
|
|
|
|
|
|
|
|
324
|
0
|
|
|
|
|
0
|
return $current; |
325
|
|
|
|
|
|
|
} |
326
|
|
|
|
|
|
|
} |
327
|
|
|
|
|
|
|
} |
328
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
sub _generate_next_span { |
330
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
331
|
|
|
|
|
|
|
|
332
|
0
|
|
|
|
|
0
|
my $last_idx = $#{ $self->{spans} }; |
|
0
|
|
|
|
|
0
|
|
333
|
|
|
|
|
|
|
|
334
|
0
|
|
|
|
|
0
|
my $max_span = $self->max_span; |
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
# Kind of a hack, but AFAIK there are no zones where it takes |
337
|
|
|
|
|
|
|
# _more_ than a year for a _future_ time zone change to occur, so |
338
|
|
|
|
|
|
|
# by looking two years out we can ensure that we will find at |
339
|
|
|
|
|
|
|
# least one more span. Of course, I will no doubt be proved wrong |
340
|
|
|
|
|
|
|
# and this will cause errors. |
341
|
|
|
|
|
|
|
$self->_generate_spans_until_match( |
342
|
0
|
|
|
|
|
0
|
$self->{max_year} + 2, |
343
|
|
|
|
|
|
|
$max_span->[UTC_END] + ( 366 * 86400 ), 'utc' |
344
|
|
|
|
|
|
|
); |
345
|
|
|
|
|
|
|
|
346
|
0
|
|
|
|
|
0
|
return $self->{spans}[ $last_idx + 1 ]; |
347
|
|
|
|
|
|
|
} |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
sub _generate_spans_until_match { |
350
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
351
|
0
|
|
|
|
|
0
|
my $generate_until_year = shift; |
352
|
0
|
|
|
|
|
0
|
my $seconds = shift; |
353
|
0
|
|
|
|
|
0
|
my $type = shift; |
354
|
|
|
|
|
|
|
|
355
|
0
|
|
|
|
|
0
|
my @changes; |
356
|
0
|
|
|
|
|
0
|
my @rules = @{ $self->_rules }; |
|
0
|
|
|
|
|
0
|
|
357
|
0
|
|
|
|
|
0
|
foreach my $year ( $self->{max_year} .. $generate_until_year ) { |
358
|
|
|
|
|
|
|
## no critic (ControlStructures::ProhibitCStyleForLoops) |
359
|
0
|
|
|
|
|
0
|
for ( my $x = 0; $x < @rules; $x++ ) { |
360
|
0
|
|
|
|
|
0
|
my $last_offset_from_std; |
361
|
|
|
|
|
|
|
|
362
|
0
|
0
|
|
|
|
0
|
if ( @rules == 2 ) { |
|
|
0
|
|
|
|
|
|
363
|
0
|
0
|
|
|
|
0
|
$last_offset_from_std |
364
|
|
|
|
|
|
|
= $x |
365
|
|
|
|
|
|
|
? $rules[0]->offset_from_std |
366
|
|
|
|
|
|
|
: $rules[1]->offset_from_std; |
367
|
|
|
|
|
|
|
} |
368
|
|
|
|
|
|
|
elsif ( @rules == 1 ) { |
369
|
0
|
|
|
|
|
0
|
$last_offset_from_std = $rules[0]->offset_from_std; |
370
|
|
|
|
|
|
|
} |
371
|
|
|
|
|
|
|
else { |
372
|
0
|
|
|
|
|
0
|
my $count = scalar @rules; |
373
|
0
|
|
|
|
|
0
|
die |
374
|
|
|
|
|
|
|
"Cannot generate future changes for zone with $count infinite rules\n"; |
375
|
|
|
|
|
|
|
} |
376
|
|
|
|
|
|
|
|
377
|
0
|
|
|
|
|
0
|
my $rule = $rules[$x]; |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
my $next = $rule->utc_start_datetime_for_year( |
380
|
|
|
|
|
|
|
$year, |
381
|
0
|
|
|
|
|
0
|
$self->{last_offset}, $last_offset_from_std |
382
|
|
|
|
|
|
|
); |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
# don't bother with changes we've seen already |
385
|
0
|
0
|
|
|
|
0
|
next if $next->utc_rd_as_seconds < $self->max_span->[UTC_END]; |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
push @changes, |
388
|
|
|
|
|
|
|
DateTime::TimeZone::OlsonDB::Change->new( |
389
|
|
|
|
|
|
|
type => 'rule', |
390
|
|
|
|
|
|
|
utc_start_datetime => $next, |
391
|
|
|
|
|
|
|
local_start_datetime => $next + DateTime::Duration->new( |
392
|
|
|
|
|
|
|
seconds => $self->{last_observance}->total_offset |
393
|
|
|
|
|
|
|
+ $rule->offset_from_std |
394
|
|
|
|
|
|
|
), |
395
|
|
|
|
|
|
|
short_name => $self->{last_observance} |
396
|
|
|
|
|
|
|
->formatted_short_name( $rule->letter ), |
397
|
|
|
|
|
|
|
observance => $self->{last_observance}, |
398
|
0
|
|
|
|
|
0
|
rule => $rule, |
399
|
|
|
|
|
|
|
); |
400
|
|
|
|
|
|
|
} |
401
|
|
|
|
|
|
|
} |
402
|
|
|
|
|
|
|
|
403
|
0
|
|
|
|
|
0
|
$self->{max_year} = $generate_until_year; |
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
my @sorted |
406
|
0
|
|
|
|
|
0
|
= sort { $a->utc_start_datetime <=> $b->utc_start_datetime } @changes; |
|
0
|
|
|
|
|
0
|
|
407
|
|
|
|
|
|
|
|
408
|
0
|
|
|
|
|
0
|
my ( $start, $end ) = _keys_for_type($type); |
409
|
|
|
|
|
|
|
|
410
|
0
|
|
|
|
|
0
|
my $match; |
411
|
|
|
|
|
|
|
## no critic (ControlStructures::ProhibitCStyleForLoops) |
412
|
0
|
|
|
|
|
0
|
for ( my $x = 1; $x < @sorted; $x++ ) { |
413
|
0
|
|
|
|
|
0
|
my $span = DateTime::TimeZone::OlsonDB::Change::two_changes_as_span( |
414
|
|
|
|
|
|
|
@sorted[ $x - 1, $x ] ); |
415
|
|
|
|
|
|
|
|
416
|
0
|
|
|
|
|
0
|
$span = _span_as_array($span); |
417
|
|
|
|
|
|
|
|
418
|
0
|
|
|
|
|
0
|
push @{ $self->{spans} }, $span; |
|
0
|
|
|
|
|
0
|
|
419
|
|
|
|
|
|
|
|
420
|
0
|
0
|
0
|
|
|
0
|
$match = $span |
421
|
|
|
|
|
|
|
if $seconds >= $span->[$start] && $seconds < $span->[$end]; |
422
|
|
|
|
|
|
|
} |
423
|
|
|
|
|
|
|
|
424
|
0
|
|
|
|
|
0
|
return $match; |
425
|
|
|
|
|
|
|
} |
426
|
|
|
|
|
|
|
|
427
|
0
|
|
|
0
|
0
|
0
|
sub max_span { $_[0]->{spans}[-1] } |
428
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
sub _keys_for_type { |
430
|
0
|
0
|
|
0
|
|
0
|
$_[0] eq 'utc' ? ( UTC_START, UTC_END ) : ( LOCAL_START, LOCAL_END ); |
431
|
|
|
|
|
|
|
} |
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
sub _span_as_array { |
434
|
|
|
|
|
|
|
[ |
435
|
0
|
|
|
|
|
0
|
@{ $_[0] }{ |
436
|
0
|
|
|
0
|
|
0
|
qw( utc_start utc_end local_start local_end offset is_dst short_name ) |
437
|
|
|
|
|
|
|
} |
438
|
|
|
|
|
|
|
]; |
439
|
|
|
|
|
|
|
} |
440
|
|
|
|
|
|
|
|
441
|
0
|
|
|
0
|
1
|
0
|
sub is_floating {0} |
442
|
|
|
|
|
|
|
|
443
|
0
|
|
|
0
|
1
|
0
|
sub is_utc {0} |
444
|
|
|
|
|
|
|
|
445
|
0
|
|
|
0
|
1
|
0
|
sub has_dst_changes {0} |
446
|
|
|
|
|
|
|
|
447
|
1
|
|
|
1
|
1
|
621
|
sub name { $_[0]->{name} } |
448
|
0
|
|
|
0
|
1
|
0
|
sub category { ( split /\//, $_[0]->{name}, 2 )[0] } |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
sub is_valid_name { |
451
|
0
|
|
|
0
|
1
|
0
|
my $class = shift; |
452
|
0
|
|
|
|
|
0
|
my $name = shift; |
453
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
my $tz = try { |
455
|
|
|
|
|
|
|
## no critic (Variables::RequireInitializationForLocalVars) |
456
|
0
|
|
|
0
|
|
0
|
local $SIG{__DIE__}; |
457
|
0
|
|
|
|
|
0
|
$class->new( name => $name ); |
458
|
0
|
|
|
|
|
0
|
}; |
459
|
|
|
|
|
|
|
|
460
|
0
|
0
|
0
|
|
|
0
|
return $tz && $tz->isa('DateTime::TimeZone') ? 1 : 0; |
461
|
|
|
|
|
|
|
} |
462
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
sub STORABLE_freeze { |
464
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
465
|
|
|
|
|
|
|
|
466
|
0
|
|
|
|
|
0
|
return $self->name; |
467
|
|
|
|
|
|
|
} |
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
sub STORABLE_thaw { |
470
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
471
|
0
|
|
|
|
|
0
|
shift; |
472
|
0
|
|
|
|
|
0
|
my $serialized = shift; |
473
|
|
|
|
|
|
|
|
474
|
0
|
|
0
|
|
|
0
|
my $class = ref $self || $self; |
475
|
|
|
|
|
|
|
|
476
|
0
|
|
|
|
|
0
|
my $obj; |
477
|
0
|
0
|
|
|
|
0
|
if ( $class->isa(__PACKAGE__) ) { |
478
|
0
|
|
|
|
|
0
|
$obj = __PACKAGE__->new( name => $serialized ); |
479
|
|
|
|
|
|
|
} |
480
|
|
|
|
|
|
|
else { |
481
|
0
|
|
|
|
|
0
|
$obj = $class->new( name => $serialized ); |
482
|
|
|
|
|
|
|
} |
483
|
|
|
|
|
|
|
|
484
|
0
|
|
|
|
|
0
|
%$self = %$obj; |
485
|
|
|
|
|
|
|
|
486
|
0
|
|
|
|
|
0
|
return $self; |
487
|
|
|
|
|
|
|
} |
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
# |
490
|
|
|
|
|
|
|
# Functions |
491
|
|
|
|
|
|
|
# |
492
|
|
|
|
|
|
|
sub offset_as_seconds { |
493
|
20
|
|
|
20
|
1
|
5780
|
my $offset = shift; |
494
|
|
|
|
|
|
|
$offset = shift if try { |
495
|
|
|
|
|
|
|
## no critic (Variables::RequireInitializationForLocalVars) |
496
|
20
|
|
|
20
|
|
510
|
local $SIG{__DIE__}; |
497
|
20
|
|
|
|
|
147
|
$offset->isa('DateTime::TimeZone'); |
498
|
20
|
100
|
|
|
|
99
|
}; |
499
|
|
|
|
|
|
|
|
500
|
20
|
50
|
|
|
|
278
|
return undef unless defined $offset; |
501
|
|
|
|
|
|
|
|
502
|
20
|
50
|
|
|
|
59
|
return 0 if $offset eq '0'; |
503
|
|
|
|
|
|
|
|
504
|
20
|
|
|
|
|
35
|
my ( $sign, $hours, $minutes, $seconds ); |
505
|
20
|
50
|
|
|
|
163
|
if ( $offset =~ /^([\+\-])?(\d\d?):(\d\d)(?::(\d\d))?$/ ) { |
|
|
50
|
|
|
|
|
|
506
|
0
|
|
|
|
|
0
|
( $sign, $hours, $minutes, $seconds ) = ( $1, $2, $3, $4 ); |
507
|
|
|
|
|
|
|
} |
508
|
|
|
|
|
|
|
elsif ( $offset =~ /^([\+\-])?(\d\d)(\d\d)(\d\d)?$/ ) { |
509
|
20
|
|
|
|
|
87
|
( $sign, $hours, $minutes, $seconds ) = ( $1, $2, $3, $4 ); |
510
|
|
|
|
|
|
|
} |
511
|
|
|
|
|
|
|
else { |
512
|
0
|
|
|
|
|
0
|
return undef; |
513
|
|
|
|
|
|
|
} |
514
|
|
|
|
|
|
|
|
515
|
20
|
100
|
|
|
|
51
|
$sign = '+' unless defined $sign; |
516
|
20
|
50
|
33
|
|
|
98
|
return undef unless $hours >= 0 && $hours <= 99; |
517
|
20
|
50
|
33
|
|
|
80
|
return undef unless $minutes >= 0 && $minutes <= 59; |
518
|
|
|
|
|
|
|
return undef |
519
|
20
|
50
|
33
|
|
|
62
|
unless !defined($seconds) || ( $seconds >= 0 && $seconds <= 59 ); |
|
|
|
66
|
|
|
|
|
520
|
|
|
|
|
|
|
|
521
|
20
|
|
|
|
|
52
|
my $total = $hours * 3600 + $minutes * 60; |
522
|
20
|
100
|
|
|
|
44
|
$total += $seconds if $seconds; |
523
|
20
|
100
|
|
|
|
44
|
$total *= -1 if $sign eq '-'; |
524
|
|
|
|
|
|
|
|
525
|
20
|
|
|
|
|
63
|
return $total; |
526
|
|
|
|
|
|
|
} |
527
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
sub offset_as_string { |
529
|
40
|
|
|
40
|
1
|
14352
|
my $offset = shift; |
530
|
|
|
|
|
|
|
$offset = shift if try { |
531
|
|
|
|
|
|
|
## no critic (Variables::RequireInitializationForLocalVars) |
532
|
40
|
|
|
40
|
|
1109
|
local $SIG{__DIE__}; |
533
|
40
|
|
|
|
|
334
|
$offset->isa('DateTime::TimeZone'); |
534
|
40
|
100
|
|
|
|
208
|
}; |
535
|
40
|
|
100
|
|
|
637
|
my $sep = shift || q{}; |
536
|
|
|
|
|
|
|
|
537
|
40
|
50
|
|
|
|
101
|
return undef unless defined $offset; |
538
|
40
|
100
|
100
|
|
|
162
|
return undef unless $offset >= -359999 && $offset <= 359999; |
539
|
|
|
|
|
|
|
|
540
|
38
|
100
|
|
|
|
99
|
my $sign = $offset < 0 ? '-' : '+'; |
541
|
|
|
|
|
|
|
|
542
|
38
|
|
|
|
|
61
|
$offset = abs($offset); |
543
|
|
|
|
|
|
|
|
544
|
38
|
|
|
|
|
96
|
my $hours = int( $offset / 3600 ); |
545
|
38
|
|
|
|
|
63
|
$offset %= 3600; |
546
|
38
|
|
|
|
|
62
|
my $mins = int( $offset / 60 ); |
547
|
38
|
|
|
|
|
48
|
$offset %= 60; |
548
|
38
|
|
|
|
|
67
|
my $secs = int($offset); |
549
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
return ( |
551
|
38
|
100
|
|
|
|
282
|
$secs |
552
|
|
|
|
|
|
|
? sprintf( |
553
|
|
|
|
|
|
|
'%s%02d%s%02d%s%02d', $sign, $hours, $sep, $mins, $sep, $secs |
554
|
|
|
|
|
|
|
) |
555
|
|
|
|
|
|
|
: sprintf( '%s%02d%s%02d', $sign, $hours, $sep, $mins ) |
556
|
|
|
|
|
|
|
); |
557
|
|
|
|
|
|
|
} |
558
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
# These methods all operate on data contained in the DateTime/TimeZone/Catalog.pm file. |
560
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
sub all_names { |
562
|
|
|
|
|
|
|
return wantarray |
563
|
|
|
|
|
|
|
? @DateTime::TimeZone::Catalog::ALL |
564
|
2
|
100
|
|
2
|
1
|
1359
|
: [@DateTime::TimeZone::Catalog::ALL]; |
565
|
|
|
|
|
|
|
} |
566
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
sub categories { |
568
|
|
|
|
|
|
|
return wantarray |
569
|
|
|
|
|
|
|
? @DateTime::TimeZone::Catalog::CATEGORY_NAMES |
570
|
2
|
100
|
|
2
|
1
|
3013
|
: [@DateTime::TimeZone::Catalog::CATEGORY_NAMES]; |
571
|
|
|
|
|
|
|
} |
572
|
|
|
|
|
|
|
|
573
|
|
|
|
|
|
|
sub links { |
574
|
|
|
|
|
|
|
return wantarray |
575
|
2
|
100
|
|
2
|
1
|
1815
|
? %DateTime::TimeZone::Catalog::LINKS |
576
|
|
|
|
|
|
|
: {%DateTime::TimeZone::Catalog::LINKS}; |
577
|
|
|
|
|
|
|
} |
578
|
|
|
|
|
|
|
|
579
|
|
|
|
|
|
|
sub names_in_category { |
580
|
3
|
100
|
|
3
|
1
|
3055
|
shift if $_[0]->isa('DateTime::TimeZone'); |
581
|
3
|
50
|
|
|
|
13
|
return unless exists $DateTime::TimeZone::Catalog::CATEGORIES{ $_[0] }; |
582
|
|
|
|
|
|
|
|
583
|
|
|
|
|
|
|
return wantarray |
584
|
2
|
|
|
|
|
31
|
? @{ $DateTime::TimeZone::Catalog::CATEGORIES{ $_[0] } } |
585
|
3
|
100
|
|
|
|
11
|
: $DateTime::TimeZone::Catalog::CATEGORIES{ $_[0] }; |
586
|
|
|
|
|
|
|
} |
587
|
|
|
|
|
|
|
|
588
|
|
|
|
|
|
|
sub countries { |
589
|
|
|
|
|
|
|
wantarray |
590
|
1
|
50
|
|
1
|
1
|
935
|
? ( sort keys %DateTime::TimeZone::Catalog::ZONES_BY_COUNTRY ) |
591
|
|
|
|
|
|
|
: [ sort keys %DateTime::TimeZone::Catalog::ZONES_BY_COUNTRY ]; |
592
|
|
|
|
|
|
|
} |
593
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
sub names_in_country { |
595
|
5
|
100
|
|
5
|
1
|
5177
|
shift if $_[0]->isa('DateTime::TimeZone'); |
596
|
|
|
|
|
|
|
|
597
|
|
|
|
|
|
|
return |
598
|
|
|
|
|
|
|
unless |
599
|
5
|
50
|
|
|
|
24
|
exists $DateTime::TimeZone::Catalog::ZONES_BY_COUNTRY{ lc $_[0] }; |
600
|
|
|
|
|
|
|
|
601
|
|
|
|
|
|
|
return |
602
|
|
|
|
|
|
|
wantarray |
603
|
4
|
|
|
|
|
30
|
? @{ $DateTime::TimeZone::Catalog::ZONES_BY_COUNTRY{ lc $_[0] } } |
604
|
5
|
100
|
|
|
|
14
|
: $DateTime::TimeZone::Catalog::ZONES_BY_COUNTRY{ lc $_[0] }; |
605
|
|
|
|
|
|
|
} |
606
|
|
|
|
|
|
|
|
607
|
|
|
|
|
|
|
1; |
608
|
|
|
|
|
|
|
|
609
|
|
|
|
|
|
|
# ABSTRACT: Time zone object base class and factory |
610
|
|
|
|
|
|
|
|
611
|
|
|
|
|
|
|
__END__ |
612
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
=pod |
614
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
=encoding UTF-8 |
616
|
|
|
|
|
|
|
|
617
|
|
|
|
|
|
|
=head1 NAME |
618
|
|
|
|
|
|
|
|
619
|
|
|
|
|
|
|
DateTime::TimeZone - Time zone object base class and factory |
620
|
|
|
|
|
|
|
|
621
|
|
|
|
|
|
|
=head1 VERSION |
622
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
version 2.60 |
624
|
|
|
|
|
|
|
|
625
|
|
|
|
|
|
|
=head1 SYNOPSIS |
626
|
|
|
|
|
|
|
|
627
|
|
|
|
|
|
|
use DateTime; |
628
|
|
|
|
|
|
|
use DateTime::TimeZone; |
629
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
my $tz = DateTime::TimeZone->new( name => 'America/Chicago' ); |
631
|
|
|
|
|
|
|
|
632
|
|
|
|
|
|
|
my $dt = DateTime->now(); |
633
|
|
|
|
|
|
|
my $offset = $tz->offset_for_datetime($dt); |
634
|
|
|
|
|
|
|
|
635
|
|
|
|
|
|
|
=head1 DESCRIPTION |
636
|
|
|
|
|
|
|
|
637
|
|
|
|
|
|
|
This class is the base class for all time zone objects. A time zone is |
638
|
|
|
|
|
|
|
represented internally as a set of observances, each of which describes the |
639
|
|
|
|
|
|
|
offset from GMT for a given time period. |
640
|
|
|
|
|
|
|
|
641
|
|
|
|
|
|
|
Note that without the L<DateTime> module, this module does not do much. It's |
642
|
|
|
|
|
|
|
primary interface is through a L<DateTime> object, and most users will not need |
643
|
|
|
|
|
|
|
to directly use C<DateTime::TimeZone> methods. |
644
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
=head2 Special Case Platforms |
646
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
If you are on the Win32 platform, you will want to also install |
648
|
|
|
|
|
|
|
L<DateTime::TimeZone::Local::Win32>. This will enable you to specify a time |
649
|
|
|
|
|
|
|
zone of C<'local'> when creating a L<DateTime> object. |
650
|
|
|
|
|
|
|
|
651
|
|
|
|
|
|
|
If you are on HPUX, install L<DateTime::TimeZone::HPUX>. This provides support |
652
|
|
|
|
|
|
|
for HPUX style time zones like C<'MET-1METDST'>. |
653
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
=head1 USAGE |
655
|
|
|
|
|
|
|
|
656
|
|
|
|
|
|
|
This class has the following methods: |
657
|
|
|
|
|
|
|
|
658
|
|
|
|
|
|
|
=head2 DateTime::TimeZone->new( name => $tz_name ) |
659
|
|
|
|
|
|
|
|
660
|
|
|
|
|
|
|
Given a valid time zone name, this method returns a new time zone blessed into |
661
|
|
|
|
|
|
|
the appropriate subclass. Subclasses are named for the given time zone, so |
662
|
|
|
|
|
|
|
that the time zone "America/Chicago" is the |
663
|
|
|
|
|
|
|
DateTime::TimeZone::America::Chicago class. |
664
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
If the name given is a "link" name in the Olson database, the object created |
666
|
|
|
|
|
|
|
may have a different name. For example, there is a link from the old "EST5EDT" |
667
|
|
|
|
|
|
|
name to "America/New_York". |
668
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
When loading a time zone from the Olson database, the constructor checks the |
670
|
|
|
|
|
|
|
version of the loaded class to make sure it matches the version of the current |
671
|
|
|
|
|
|
|
DateTime::TimeZone installation. If they do not match it will issue a warning. |
672
|
|
|
|
|
|
|
This is useful because time zone names may fall out of use, but you may have an |
673
|
|
|
|
|
|
|
old module file installed for that time zone. |
674
|
|
|
|
|
|
|
|
675
|
|
|
|
|
|
|
There are also several special values that can be given as names. |
676
|
|
|
|
|
|
|
|
677
|
|
|
|
|
|
|
If the "name" parameter is "floating", then a C<DateTime::TimeZone::Floating> |
678
|
|
|
|
|
|
|
object is returned. A floating time zone does not have I<any> offset, and is |
679
|
|
|
|
|
|
|
always the same time. This is useful for calendaring applications, which may |
680
|
|
|
|
|
|
|
need to specify that a given event happens at the same I<local> time, |
681
|
|
|
|
|
|
|
regardless of where it occurs. See L<RFC |
682
|
|
|
|
|
|
|
2445|https://www.ietf.org/rfc/rfc2445.txt> for more details. |
683
|
|
|
|
|
|
|
|
684
|
|
|
|
|
|
|
If the "name" parameter is "UTC", then a C<DateTime::TimeZone::UTC> object is |
685
|
|
|
|
|
|
|
returned. |
686
|
|
|
|
|
|
|
|
687
|
|
|
|
|
|
|
If the "name" is an offset string, it is converted to a number, and a |
688
|
|
|
|
|
|
|
C<DateTime::TimeZone::OffsetOnly> object is returned. |
689
|
|
|
|
|
|
|
|
690
|
|
|
|
|
|
|
=head3 The "local" time zone |
691
|
|
|
|
|
|
|
|
692
|
|
|
|
|
|
|
If the "name" parameter is "local", then the module attempts to determine the |
693
|
|
|
|
|
|
|
local time zone for the system. |
694
|
|
|
|
|
|
|
|
695
|
|
|
|
|
|
|
The method for finding the local zone varies by operating system. See the |
696
|
|
|
|
|
|
|
appropriate module for details of how we check for the local time zone. |
697
|
|
|
|
|
|
|
|
698
|
|
|
|
|
|
|
=over 4 |
699
|
|
|
|
|
|
|
|
700
|
|
|
|
|
|
|
=item * L<DateTime::TimeZone::Local::Unix> |
701
|
|
|
|
|
|
|
|
702
|
|
|
|
|
|
|
=item * L<DateTime::TimeZone::Local::Android> |
703
|
|
|
|
|
|
|
|
704
|
|
|
|
|
|
|
=item * L<DateTime::TimeZone::Local::hpux> |
705
|
|
|
|
|
|
|
|
706
|
|
|
|
|
|
|
=item * L<DateTime::TimeZone::Local::Win32> |
707
|
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
=item * L<DateTime::TimeZone::Local::VMS> |
709
|
|
|
|
|
|
|
|
710
|
|
|
|
|
|
|
=back |
711
|
|
|
|
|
|
|
|
712
|
|
|
|
|
|
|
If a local time zone is not found, then an exception will be thrown. This |
713
|
|
|
|
|
|
|
exception will always stringify to something containing the text C<"Cannot |
714
|
|
|
|
|
|
|
determine local time zone">. |
715
|
|
|
|
|
|
|
|
716
|
|
|
|
|
|
|
If you are writing code for users to run on systems you do not control, you |
717
|
|
|
|
|
|
|
should try to account for the possibility that this exception may be thrown. |
718
|
|
|
|
|
|
|
Falling back to UTC might be a reasonable alternative. |
719
|
|
|
|
|
|
|
|
720
|
|
|
|
|
|
|
When writing tests for your modules that might be run on others' systems, you |
721
|
|
|
|
|
|
|
are strongly encouraged to either not use C<local> when creating L<DateTime> |
722
|
|
|
|
|
|
|
objects or to set C<$ENV{TZ}> to a known value in your test code. All of the |
723
|
|
|
|
|
|
|
per-OS classes check this environment variable. |
724
|
|
|
|
|
|
|
|
725
|
|
|
|
|
|
|
=head2 $tz->offset_for_datetime( $dt ) |
726
|
|
|
|
|
|
|
|
727
|
|
|
|
|
|
|
Given a C<DateTime> object, this method returns the offset in seconds for the |
728
|
|
|
|
|
|
|
given datetime. This takes into account historical time zone information, as |
729
|
|
|
|
|
|
|
well as Daylight Saving Time. The offset is determined by looking at the |
730
|
|
|
|
|
|
|
object's UTC Rata Die days and seconds. |
731
|
|
|
|
|
|
|
|
732
|
|
|
|
|
|
|
=head2 $tz->offset_for_local_datetime( $dt ) |
733
|
|
|
|
|
|
|
|
734
|
|
|
|
|
|
|
Given a C<DateTime> object, this method returns the offset in seconds for the |
735
|
|
|
|
|
|
|
given datetime. Unlike the previous method, this method uses the local time's |
736
|
|
|
|
|
|
|
Rata Die days and seconds. This should only be done when the corresponding UTC |
737
|
|
|
|
|
|
|
time is not yet known, because local times can be ambiguous due to Daylight |
738
|
|
|
|
|
|
|
Saving Time rules. |
739
|
|
|
|
|
|
|
|
740
|
|
|
|
|
|
|
=head2 $tz->is_dst_for_datetime( $dt ) |
741
|
|
|
|
|
|
|
|
742
|
|
|
|
|
|
|
Given a C<DateTime> object, this method returns true if the DateTime is |
743
|
|
|
|
|
|
|
currently in Daylight Saving Time. |
744
|
|
|
|
|
|
|
|
745
|
|
|
|
|
|
|
=head2 $tz->name |
746
|
|
|
|
|
|
|
|
747
|
|
|
|
|
|
|
Returns the name of the time zone. |
748
|
|
|
|
|
|
|
|
749
|
|
|
|
|
|
|
=head2 $tz->short_name_for_datetime( $dt ) |
750
|
|
|
|
|
|
|
|
751
|
|
|
|
|
|
|
Given a C<DateTime> object, this method returns the "short name" for the |
752
|
|
|
|
|
|
|
current observance and rule this datetime is in. These are names like "EST", |
753
|
|
|
|
|
|
|
"GMT", etc. |
754
|
|
|
|
|
|
|
|
755
|
|
|
|
|
|
|
It is B<strongly> recommended that you do not rely on these names for anything |
756
|
|
|
|
|
|
|
other than display. These names are not official, and many of them are simply |
757
|
|
|
|
|
|
|
the invention of the Olson database maintainers. Moreover, these names are not |
758
|
|
|
|
|
|
|
unique. For example, there is an "EST" at both -0500 and +1000/+1100. |
759
|
|
|
|
|
|
|
|
760
|
|
|
|
|
|
|
=head2 $tz->is_floating |
761
|
|
|
|
|
|
|
|
762
|
|
|
|
|
|
|
Returns a boolean indicating whether or not this object represents a floating |
763
|
|
|
|
|
|
|
time zone, as defined by L<RFC 2445|https://www.ietf.org/rfc/rfc2445.txt>. |
764
|
|
|
|
|
|
|
|
765
|
|
|
|
|
|
|
=head2 $tz->is_utc |
766
|
|
|
|
|
|
|
|
767
|
|
|
|
|
|
|
Indicates whether or not this object represents the UTC (GMT) time zone. |
768
|
|
|
|
|
|
|
|
769
|
|
|
|
|
|
|
=head2 $tz->has_dst_changes |
770
|
|
|
|
|
|
|
|
771
|
|
|
|
|
|
|
Indicates whether or not this zone has I<ever> had a change to and from DST, |
772
|
|
|
|
|
|
|
either in the past or future. |
773
|
|
|
|
|
|
|
|
774
|
|
|
|
|
|
|
=head2 $tz->is_olson |
775
|
|
|
|
|
|
|
|
776
|
|
|
|
|
|
|
Returns true if the time zone is a named time zone from the Olson database. |
777
|
|
|
|
|
|
|
|
778
|
|
|
|
|
|
|
=head2 $tz->category |
779
|
|
|
|
|
|
|
|
780
|
|
|
|
|
|
|
Returns the part of the time zone name before the first slash. For example, |
781
|
|
|
|
|
|
|
the "America/Chicago" time zone would return "America". |
782
|
|
|
|
|
|
|
|
783
|
|
|
|
|
|
|
=head2 DateTime::TimeZone->is_valid_name($name) |
784
|
|
|
|
|
|
|
|
785
|
|
|
|
|
|
|
Given a string, this method returns a boolean value indicating whether or not |
786
|
|
|
|
|
|
|
the string is a valid time zone name. If you are using |
787
|
|
|
|
|
|
|
C<DateTime::TimeZone::Alias>, any aliases you've created will be valid. |
788
|
|
|
|
|
|
|
|
789
|
|
|
|
|
|
|
=head2 DateTime::TimeZone->all_names |
790
|
|
|
|
|
|
|
|
791
|
|
|
|
|
|
|
This returns a pre-sorted list of all the time zone names. This list does not |
792
|
|
|
|
|
|
|
include link names. In scalar context, it returns an array reference, while in |
793
|
|
|
|
|
|
|
list context it returns an array. |
794
|
|
|
|
|
|
|
|
795
|
|
|
|
|
|
|
=head2 DateTime::TimeZone->categories |
796
|
|
|
|
|
|
|
|
797
|
|
|
|
|
|
|
This returns a list of all time zone categories. In scalar context, it returns |
798
|
|
|
|
|
|
|
an array reference, while in list context it returns an array. |
799
|
|
|
|
|
|
|
|
800
|
|
|
|
|
|
|
=head2 DateTime::TimeZone->links |
801
|
|
|
|
|
|
|
|
802
|
|
|
|
|
|
|
This returns a hash of all time zone links, where the keys are the old, |
803
|
|
|
|
|
|
|
deprecated names, and the values are the new names. In scalar context, it |
804
|
|
|
|
|
|
|
returns a hash reference, while in list context it returns a hash. |
805
|
|
|
|
|
|
|
|
806
|
|
|
|
|
|
|
=head2 DateTime::TimeZone->names_in_category( $category ) |
807
|
|
|
|
|
|
|
|
808
|
|
|
|
|
|
|
Given a valid category, this method returns a list of the names in that |
809
|
|
|
|
|
|
|
category, without the category portion. So the list for the "America" category |
810
|
|
|
|
|
|
|
would include the strings "Chicago", "Kentucky/Monticello", and "New_York". In |
811
|
|
|
|
|
|
|
scalar context, it returns an array reference, while in list context it returns |
812
|
|
|
|
|
|
|
an array. |
813
|
|
|
|
|
|
|
|
814
|
|
|
|
|
|
|
=head2 DateTime::TimeZone->countries() |
815
|
|
|
|
|
|
|
|
816
|
|
|
|
|
|
|
Returns a sorted list of all the valid country codes (in lower-case) which can |
817
|
|
|
|
|
|
|
be passed to C<names_in_country()>. In scalar context, it returns an array |
818
|
|
|
|
|
|
|
reference, while in list context it returns an array. |
819
|
|
|
|
|
|
|
|
820
|
|
|
|
|
|
|
If you need to convert country codes to names or vice versa you can use |
821
|
|
|
|
|
|
|
C<Locale::Country> to do so. Note that one of the codes returned is "uk", which |
822
|
|
|
|
|
|
|
is an alias for the country code "gb", and is not a valid ISO country code. |
823
|
|
|
|
|
|
|
|
824
|
|
|
|
|
|
|
=head2 DateTime::TimeZone->names_in_country( $country_code ) |
825
|
|
|
|
|
|
|
|
826
|
|
|
|
|
|
|
Given a two-letter ISO3166 country code, this method returns a list of time |
827
|
|
|
|
|
|
|
zones used in that country. The country code may be of any case. In scalar |
828
|
|
|
|
|
|
|
context, it returns an array reference, while in list context it returns an |
829
|
|
|
|
|
|
|
array. |
830
|
|
|
|
|
|
|
|
831
|
|
|
|
|
|
|
This list is returned in an order vaguely based on geography and population. In |
832
|
|
|
|
|
|
|
general, the least used zones come last, but there are not guarantees of a |
833
|
|
|
|
|
|
|
specific order from one release to the next. This order is probably the best |
834
|
|
|
|
|
|
|
option for presenting zones names to end users. |
835
|
|
|
|
|
|
|
|
836
|
|
|
|
|
|
|
=head2 DateTime::TimeZone->offset_as_seconds( $offset ) |
837
|
|
|
|
|
|
|
|
838
|
|
|
|
|
|
|
Given an offset as a string, this returns the number of seconds represented by |
839
|
|
|
|
|
|
|
the offset as a positive or negative number. Returns C<undef> if $offset is |
840
|
|
|
|
|
|
|
not in the range C<-99:59:59> to C<+99:59:59>. |
841
|
|
|
|
|
|
|
|
842
|
|
|
|
|
|
|
The offset is expected to match either |
843
|
|
|
|
|
|
|
C</^([\+\-])?(\d\d?):(\d\d)(?::(\d\d))?$/> or |
844
|
|
|
|
|
|
|
C</^([\+\-])?(\d\d)(\d\d)(\d\d)?$/>. If it doesn't match either of these, |
845
|
|
|
|
|
|
|
C<undef> will be returned. |
846
|
|
|
|
|
|
|
|
847
|
|
|
|
|
|
|
This means that if you want to specify hours as a single digit, then each |
848
|
|
|
|
|
|
|
element of the offset must be separated by a colon (:). |
849
|
|
|
|
|
|
|
|
850
|
|
|
|
|
|
|
=head2 DateTime::TimeZone->offset_as_string( $offset, $sep ) |
851
|
|
|
|
|
|
|
|
852
|
|
|
|
|
|
|
Given an offset as a number, this returns the offset as a string. Returns |
853
|
|
|
|
|
|
|
C<undef> if $offset is not in the range C<-359999> to C<359999>. |
854
|
|
|
|
|
|
|
|
855
|
|
|
|
|
|
|
You can also provide an optional separator which will go between the hours, |
856
|
|
|
|
|
|
|
minutes, and seconds (if applicable) portions of the offset. |
857
|
|
|
|
|
|
|
|
858
|
|
|
|
|
|
|
=head2 Storable Hooks |
859
|
|
|
|
|
|
|
|
860
|
|
|
|
|
|
|
This module provides freeze and thaw hooks for C<Storable> so that the huge |
861
|
|
|
|
|
|
|
data structures for Olson time zones are not actually stored in the serialized |
862
|
|
|
|
|
|
|
structure. |
863
|
|
|
|
|
|
|
|
864
|
|
|
|
|
|
|
If you subclass C<DateTime::TimeZone>, you will inherit its hooks, which may |
865
|
|
|
|
|
|
|
not work for your module, so please test the interaction of your module with |
866
|
|
|
|
|
|
|
Storable. |
867
|
|
|
|
|
|
|
|
868
|
|
|
|
|
|
|
=head1 LOADING TIME ZONES IN A PRE-FORKING SYSTEM |
869
|
|
|
|
|
|
|
|
870
|
|
|
|
|
|
|
If you are running an application that does pre-forking (for example with |
871
|
|
|
|
|
|
|
Starman), then you should try to load all the time zones that you'll need in |
872
|
|
|
|
|
|
|
the parent process. Time zones are loaded on-demand, so loading them once in |
873
|
|
|
|
|
|
|
each child will waste memory that could otherwise be shared. |
874
|
|
|
|
|
|
|
|
875
|
|
|
|
|
|
|
=head1 CREDITS |
876
|
|
|
|
|
|
|
|
877
|
|
|
|
|
|
|
This module was inspired by Jesse Vincent's work on Date::ICal::Timezone, and |
878
|
|
|
|
|
|
|
written with much help from the datetime@perl.org list. |
879
|
|
|
|
|
|
|
|
880
|
|
|
|
|
|
|
=head1 SEE ALSO |
881
|
|
|
|
|
|
|
|
882
|
|
|
|
|
|
|
datetime@perl.org mailing list |
883
|
|
|
|
|
|
|
|
884
|
|
|
|
|
|
|
The tools directory of the DateTime::TimeZone distribution includes two scripts |
885
|
|
|
|
|
|
|
that may be of interest to some people. They are parse_olson and |
886
|
|
|
|
|
|
|
tests_from_zdump. Please run them with the --help flag to see what they can be |
887
|
|
|
|
|
|
|
used for. |
888
|
|
|
|
|
|
|
|
889
|
|
|
|
|
|
|
=head1 SUPPORT |
890
|
|
|
|
|
|
|
|
891
|
|
|
|
|
|
|
Support for this module is provided via the datetime@perl.org email list. |
892
|
|
|
|
|
|
|
|
893
|
|
|
|
|
|
|
Bugs may be submitted at L<https://github.com/houseabsolute/DateTime-TimeZone/issues>. |
894
|
|
|
|
|
|
|
|
895
|
|
|
|
|
|
|
=head1 SOURCE |
896
|
|
|
|
|
|
|
|
897
|
|
|
|
|
|
|
The source code repository for DateTime-TimeZone can be found at L<https://github.com/houseabsolute/DateTime-TimeZone>. |
898
|
|
|
|
|
|
|
|
899
|
|
|
|
|
|
|
=head1 DONATIONS |
900
|
|
|
|
|
|
|
|
901
|
|
|
|
|
|
|
If you'd like to thank me for the work I've done on this module, please |
902
|
|
|
|
|
|
|
consider making a "donation" to me via PayPal. I spend a lot of free time |
903
|
|
|
|
|
|
|
creating free software, and would appreciate any support you'd care to offer. |
904
|
|
|
|
|
|
|
|
905
|
|
|
|
|
|
|
Please note that B<I am not suggesting that you must do this> in order for me |
906
|
|
|
|
|
|
|
to continue working on this particular software. I will continue to do so, |
907
|
|
|
|
|
|
|
inasmuch as I have in the past, for as long as it interests me. |
908
|
|
|
|
|
|
|
|
909
|
|
|
|
|
|
|
Similarly, a donation made in this way will probably not make me work on this |
910
|
|
|
|
|
|
|
software much more, unless I get so many donations that I can consider working |
911
|
|
|
|
|
|
|
on free software full time (let's all have a chuckle at that together). |
912
|
|
|
|
|
|
|
|
913
|
|
|
|
|
|
|
To donate, log into PayPal and send money to autarch@urth.org, or use the |
914
|
|
|
|
|
|
|
button at L<https://houseabsolute.com/foss-donations/>. |
915
|
|
|
|
|
|
|
|
916
|
|
|
|
|
|
|
=head1 AUTHOR |
917
|
|
|
|
|
|
|
|
918
|
|
|
|
|
|
|
Dave Rolsky <autarch@urth.org> |
919
|
|
|
|
|
|
|
|
920
|
|
|
|
|
|
|
=head1 CONTRIBUTORS |
921
|
|
|
|
|
|
|
|
922
|
|
|
|
|
|
|
=for stopwords Alexey Molchanov Alfie John Andrew Paprocki Bron Gondwana Daisuke Maki David Pinkowitz Iain Truskett Jakub Wilk James E Keenan Joshua Hoblitt Karen Etheridge karupanerura kclaggett Matthew Horsfall Mohammad S Anwar Olaf Alders Peter Rabbitson Tom Wyant |
923
|
|
|
|
|
|
|
|
924
|
|
|
|
|
|
|
=over 4 |
925
|
|
|
|
|
|
|
|
926
|
|
|
|
|
|
|
=item * |
927
|
|
|
|
|
|
|
|
928
|
|
|
|
|
|
|
Alexey Molchanov <alexey.molchanov@gmail.com> |
929
|
|
|
|
|
|
|
|
930
|
|
|
|
|
|
|
=item * |
931
|
|
|
|
|
|
|
|
932
|
|
|
|
|
|
|
Alfie John <alfiej@fastmail.fm> |
933
|
|
|
|
|
|
|
|
934
|
|
|
|
|
|
|
=item * |
935
|
|
|
|
|
|
|
|
936
|
|
|
|
|
|
|
Andrew Paprocki <apaprocki@bloomberg.net> |
937
|
|
|
|
|
|
|
|
938
|
|
|
|
|
|
|
=item * |
939
|
|
|
|
|
|
|
|
940
|
|
|
|
|
|
|
Bron Gondwana <brong@fastmail.fm> |
941
|
|
|
|
|
|
|
|
942
|
|
|
|
|
|
|
=item * |
943
|
|
|
|
|
|
|
|
944
|
|
|
|
|
|
|
Daisuke Maki <dmaki@cpan.org> |
945
|
|
|
|
|
|
|
|
946
|
|
|
|
|
|
|
=item * |
947
|
|
|
|
|
|
|
|
948
|
|
|
|
|
|
|
David Pinkowitz <dave@pinkowitz.com> |
949
|
|
|
|
|
|
|
|
950
|
|
|
|
|
|
|
=item * |
951
|
|
|
|
|
|
|
|
952
|
|
|
|
|
|
|
Iain Truskett <deceased> |
953
|
|
|
|
|
|
|
|
954
|
|
|
|
|
|
|
=item * |
955
|
|
|
|
|
|
|
|
956
|
|
|
|
|
|
|
Jakub Wilk <jwilk@jwilk.net> |
957
|
|
|
|
|
|
|
|
958
|
|
|
|
|
|
|
=item * |
959
|
|
|
|
|
|
|
|
960
|
|
|
|
|
|
|
James E Keenan <jkeenan@cpan.org> |
961
|
|
|
|
|
|
|
|
962
|
|
|
|
|
|
|
=item * |
963
|
|
|
|
|
|
|
|
964
|
|
|
|
|
|
|
Joshua Hoblitt <jhoblitt@cpan.org> |
965
|
|
|
|
|
|
|
|
966
|
|
|
|
|
|
|
=item * |
967
|
|
|
|
|
|
|
|
968
|
|
|
|
|
|
|
Karen Etheridge <ether@cpan.org> |
969
|
|
|
|
|
|
|
|
970
|
|
|
|
|
|
|
=item * |
971
|
|
|
|
|
|
|
|
972
|
|
|
|
|
|
|
karupanerura <karupa@cpan.org> |
973
|
|
|
|
|
|
|
|
974
|
|
|
|
|
|
|
=item * |
975
|
|
|
|
|
|
|
|
976
|
|
|
|
|
|
|
kclaggett <kclaggett@proofpoint.com> |
977
|
|
|
|
|
|
|
|
978
|
|
|
|
|
|
|
=item * |
979
|
|
|
|
|
|
|
|
980
|
|
|
|
|
|
|
Matthew Horsfall <wolfsage@gmail.com> |
981
|
|
|
|
|
|
|
|
982
|
|
|
|
|
|
|
=item * |
983
|
|
|
|
|
|
|
|
984
|
|
|
|
|
|
|
Mohammad S Anwar <mohammad.anwar@yahoo.com> |
985
|
|
|
|
|
|
|
|
986
|
|
|
|
|
|
|
=item * |
987
|
|
|
|
|
|
|
|
988
|
|
|
|
|
|
|
Olaf Alders <olaf@wundersolutions.com> |
989
|
|
|
|
|
|
|
|
990
|
|
|
|
|
|
|
=item * |
991
|
|
|
|
|
|
|
|
992
|
|
|
|
|
|
|
Peter Rabbitson <ribasushi@cpan.org> |
993
|
|
|
|
|
|
|
|
994
|
|
|
|
|
|
|
=item * |
995
|
|
|
|
|
|
|
|
996
|
|
|
|
|
|
|
Tom Wyant <wyant@cpan.org> |
997
|
|
|
|
|
|
|
|
998
|
|
|
|
|
|
|
=back |
999
|
|
|
|
|
|
|
|
1000
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
1001
|
|
|
|
|
|
|
|
1002
|
|
|
|
|
|
|
This software is copyright (c) 2023 by Dave Rolsky. |
1003
|
|
|
|
|
|
|
|
1004
|
|
|
|
|
|
|
This is free software; you can redistribute it and/or modify it under |
1005
|
|
|
|
|
|
|
the same terms as the Perl 5 programming language system itself. |
1006
|
|
|
|
|
|
|
|
1007
|
|
|
|
|
|
|
The full text of the license can be found in the |
1008
|
|
|
|
|
|
|
F<LICENSE> file included with this distribution. |
1009
|
|
|
|
|
|
|
|
1010
|
|
|
|
|
|
|
=cut |