File Coverage

lib/Date/Manip/TZ.pm
Criterion Covered Total %
statement 696 967 71.9
branch 286 548 52.1
condition 75 132 56.8
subroutine 51 61 83.6
pod 16 16 100.0
total 1124 1724 65.2


line stmt bran cond sub pod time code
1             package Date::Manip::TZ;
2             # Copyright (c) 2008-2026 Sullivan Beck. All rights reserved.
3             # This program is free software; you can redistribute it and/or modify it
4             # under the same terms as Perl itself.
5              
6             ########################################################################
7             # Any routine that starts with an underscore (_) is NOT intended for
8             # public use. They are for internal use in the the Date::Manip
9             # modules and are subject to change without warning or notice.
10             #
11             # ABSOLUTELY NO USER SUPPORT IS OFFERED FOR THESE ROUTINES!
12             ########################################################################
13              
14 170     170   868 use Date::Manip::Obj;
  170         229  
  170         4929  
15 170     170   587 use Date::Manip::TZ_Base;
  170         215  
  170         5377  
16             @ISA = qw(Date::Manip::Obj Date::Manip::TZ_Base);
17              
18             require 5.010000;
19 170     170   561 use warnings;
  170         196  
  170         5815  
20 170     170   550 use strict;
  170         201  
  170         2389  
21              
22 170     170   494 use IO::File;
  170         241  
  170         25479  
23             require Date::Manip::Zones;
24 170     170   779 use Date::Manip::Base;
  170         220  
  170         2898  
25 170     170   88404 use Data::Dumper;
  170         1051975  
  170         10150  
26 170     170   978 use Carp;
  170         264  
  170         67309  
27              
28             our $VERSION;
29             $VERSION='6.99';
30 170     170   1288 END { undef $VERSION; }
31              
32             # To get rid of a 'used only once' warnings.
33             END {
34 170     170   747 my $tmp = \%Date::Manip::Zones::Module;
35 170         867 $tmp = \%Date::Manip::Zones::ZoneNames;
36 170         318 $tmp = \%Date::Manip::Zones::Alias;
37 170         314 $tmp = \%Date::Manip::Zones::Abbrev;
38 170         290 $tmp = \%Date::Manip::Zones::Offmod;
39 170         756 $tmp = $Date::Manip::Zones::FirstDate;
40 170         663 $tmp = $Date::Manip::Zones::LastDate;
41 170         296 $tmp = $Date::Manip::Zones::LastYear;
42 170         867 $tmp = $Date::Manip::Zones::TzcodeVersion;
43 170         603 $tmp = $Date::Manip::Zones::TzdataVersion;
44             }
45              
46             ########################################################################
47             # BASE METHODS
48             ########################################################################
49              
50             sub _init {
51 515     515   1147 my($self) = @_;
52              
53 515         21529 $$self{'data'} =
54             {
55             # These are the variables defined in Date::Manip::Zones
56             'Module' => \%Date::Manip::Zones::Module,
57             'ZoneNames' => \%Date::Manip::Zones::ZoneNames,
58             'Alias' => \%Date::Manip::Zones::Alias,
59             'Abbrev' => \%Date::Manip::Zones::Abbrev,
60             'Offmod' => \%Date::Manip::Zones::Offmod,
61             'FirstDate' => $Date::Manip::Zones::FirstDate,
62             'LastDate' => $Date::Manip::Zones::LastDate,
63             'LastYear' => $Date::Manip::Zones::LastYear,
64              
65             # These override values from Date::Manip::Zones
66             'MyAlias' => {},
67             'MyAbbrev' => {},
68             'MyOffsets' => {},
69              
70             # Each timezone/offset module that is loaded goes here
71             'Zones' => {},
72             'Offsets' => {},
73              
74             # methods a list of methods used for determining the
75             # current zone
76             # path the PATH to set for determining the current
77             # zone
78             # dates critical dates on a per/year (UT) basis
79             # zonerx the regular expression for matching timezone
80             # names/aliases
81             # abbrx the regular expression for matching timezone
82             # abbreviations
83             # offrx the regular expression for matching a valid
84             # timezone offset
85             # zrx the regular expression to match all timezone
86             # information
87             'methods' => [],
88             'path' => undef,
89             'zonerx' => undef,
90             'abbrx' => undef,
91             'offrx' => undef,
92             'zrx' => undef,
93             };
94              
95             # OS specific stuff
96              
97 515         1357 my $dmb = $$self{'base'};
98 515         1447 my $os = $dmb->_os();
99              
100 515 50       1482 if ($os eq 'Unix') {
    0          
    0          
101 515         1116 $$self{'data'}{'path'} = '/bin:/usr/bin';
102 515         10732 $$self{'data'}{'methods'} = [
103             qw(main TZ
104             env zone TZ
105             file /etc/TIMEZONE
106             file /etc/timezone
107             file /etc/sysconfig/clock
108             file /etc/default/init
109             ),
110             'command', '/bin/date +%Z',
111             'command', '/usr/bin/date +%Z',
112             'command', '/usr/local/bin/date +%Z',
113             qw(cmdfield /bin/date -2
114             cmdfield /usr/bin/date -2
115             cmdfield /usr/local/bin/date -2
116             ),
117             'command', '/bin/date +%z',
118             'command', '/usr/bin/date +%z',
119             'command', '/usr/local/bin/date +%z',
120             qw( tzdata /etc/localtime /usr/share/zoneinfo
121             ),
122             'gmtoff'
123             ];
124              
125             } elsif ($os eq 'Windows') {
126 0         0 $$self{'data'}{'methods'} = [
127             qw(main TZ
128             env zone TZ
129             registry
130             gmtoff),
131             ];
132              
133             } elsif ($os eq 'VMS') {
134 0         0 $$self{'data'}{'methods'} = [
135             qw(main TZ
136             env zone TZ
137             env zone SYS$TIMEZONE_NAME
138             env zone UCX$TZ
139             env zone TCPIP$TZ
140             env zone MULTINET_TIMEZONE
141             env offset SYS$TIMEZONE_DIFFERENTIAL
142             gmtoff
143             ),
144             ];
145              
146             } else {
147 0         0 $$self{'data'}{'methods'} = [
148             qw(main TZ
149             env zone TZ
150             gmtoff
151             ),
152             ];
153             }
154             }
155              
156             sub _init_final {
157 518     518   998 my($self) = @_;
158              
159 518         1979 $self->_set_curr_zone();
160             }
161              
162 170     170   1083 no strict 'refs';
  170         234  
  170         41833  
163             # This loads data from an offset module
164             #
165             sub _offmod {
166 91     91   182 my($self,$offset) = @_;
167 91 100       333 return if (exists $$self{'data'}{'Offsets'}{$offset});
168              
169 34         128 my $mod = $$self{'data'}{'Offmod'}{$offset};
170 34         2828 eval "require Date::Manip::Offset::${mod}";
171 34         135 my %off = %{ "Date::Manip::Offset::${mod}::Offset" };
  34         255  
172              
173 34         247 $$self{'data'}{'Offsets'}{$offset} = { %off };
174             }
175              
176             # This loads data from a zone module (takes a lowercase zone)
177             #
178             sub _module {
179 1626     1626   3515 my($self,$zone) = @_;
180 1626 50       3910 return if (exists $$self{'data'}{'Zones'}{$zone}{'Loaded'});
181              
182 1626         5194 my $mod = $$self{'data'}{'Module'}{$zone};
183 1626         152583 eval "require Date::Manip::TZ::${mod}";
184 1626         7016 my %dates = %{ "Date::Manip::TZ::${mod}::Dates" };
  1626         54989  
185 1626         6941 my %last = %{ "Date::Manip::TZ::${mod}::LastRule" };
  1626         6829  
186 1626         53075 $$self{'data'}{'Zones'}{$zone} =
187             {
188             'Dates' => { %dates },
189             'LastRule' => { %last },
190             'Loaded' => 1
191             };
192             }
193 170     170   878 use strict 'refs';
  170         323  
  170         207411  
194              
195             ########################################################################
196             # CHECKING/MODIFYING ZONEINFO DATA
197             ########################################################################
198              
199             sub _zone {
200 48102     48102   67379 my($self,$zone) = @_;
201 48102         66539 $zone = lc($zone);
202              
203 48102 100       114854 if (exists $$self{'data'}{'MyAlias'}{$zone}) {
    100          
204 1         4 return $$self{'data'}{'MyAlias'}{$zone};
205             } elsif (exists $$self{'data'}{'Alias'}{$zone}) {
206 48051         101067 return $$self{'data'}{'Alias'}{$zone};
207             } else {
208 50         412 return '';
209             }
210             }
211              
212             sub tzdata {
213 0     0 1 0 my($self) = @_;
214 0         0 return $Date::Manip::Zones::TzdataVersion;
215             }
216              
217             sub tzcode {
218 0     0 1 0 my($self) = @_;
219 0         0 return $Date::Manip::Zones::TzcodeVersion;
220             }
221              
222             sub define_alias {
223 2     2 1 1487 my($self,$alias,$zone) = @_;
224 2         5 $alias = lc($alias);
225              
226 2 100       24 if ($alias eq 'reset') {
227 1         5 $$self{'data'}{'MyAlias'} = {};
228 1         3 $$self{'data'}{'zonerx'} = undef;
229 1         2 return 0;
230             }
231 1 50       4 if (lc($zone) eq 'reset') {
232 0         0 delete $$self{'data'}{'MyAlias'}{$alias};
233 0         0 $$self{'data'}{'zonerx'} = undef;
234 0         0 return 0;
235             }
236              
237 1         5 $zone = $self->_zone($zone);
238              
239 1 50       3 return 1 if (! $zone);
240 1         3 $$self{'data'}{'MyAlias'}{$alias} = $zone;
241 1         4 $$self{'data'}{'zonerx'} = undef;
242 1         2 return 0;
243             }
244              
245             sub define_abbrev {
246 6     6 1 4152 my($self,$abbrev,@zone) = @_;
247 6         13 $abbrev = lc($abbrev);
248              
249 6 100       16 if ($abbrev eq 'reset') {
250 2         7 $$self{'data'}{'MyAbbrev'} = {};
251 2         4 $$self{'data'}{'abbrx'} = undef;
252 2         7 return 0;
253             }
254 4 100 100     22 if ($#zone == 0 && lc($zone[0]) eq 'reset') {
255 1         2 delete $$self{'data'}{'MyAbbrev'}{$abbrev};
256 1         2 $$self{'data'}{'abbrx'} = undef;
257 1         1 return (0);
258             }
259              
260 3 50       14 if (! exists $$self{'data'}{'Abbrev'}{$abbrev}) {
261 0         0 return (1);
262             }
263              
264 3         4 my (@z,%z);
265 3         5 my %zone = map { $_,1 } @{ $$self{'data'}{'Abbrev'}{$abbrev} };
  50         95  
  3         31  
266 3         12 foreach my $z (@zone) {
267 5         12 my $zone = $self->_zone($z);
268 5 50       9 return (2,$z) if (! $zone);
269 5 50       10 return (3,$z) if (! exists $zone{$zone});
270 5 50       10 next if (exists $z{$zone});
271 5         8 $z{$zone} = 1;
272 5         7 push(@z,$zone);
273             }
274              
275 3         10 $$self{'data'}{'MyAbbrev'}{$abbrev} = [ @z ];
276 3         169 $$self{'data'}{'abbrx'} = undef;
277             # Some abbreviations are timezone aliases, so get rid of them (if any)
278             # so this abbreviation won't falsely identify as another timezone.
279 3         13 delete $$self{'data'}{'Alias'}{$abbrev};
280 3         11 return ();
281             }
282              
283             sub define_offset {
284 22     22 1 19382 my($self,$offset,@args) = @_;
285 22         29 my $dmb = $$self{'base'};
286              
287 22 100       43 if (lc($offset) eq 'reset') {
288 10         23 $$self{'data'}{'MyOffsets'} = {};
289 10         19 return (0);
290             }
291 12 50 33     25 if ($#args == 0 && lc($args[0]) eq 'reset') {
292 0         0 delete $$self{'data'}{'MyOffsets'}{$offset};
293 0         0 return (0);
294             }
295              
296             # Check that $offset is valid. If it is, load the
297             # appropriate module.
298              
299 12 50       20 if (ref($offset)) {
300 0         0 $offset = $dmb->join('offset',$offset);
301             } else {
302 12         47 $offset = $dmb->_delta_convert('offset',$offset);
303             }
304 12 100       28 return (9) if (! $offset);
305 11 100       33 return (1) if (! exists $$self{'data'}{'Offmod'}{$offset});
306              
307 10         35 $self->_offmod($offset);
308              
309             # Find out whether we're handling STD, DST, or both.
310              
311 10         17 my(@isdst) = (0,1);
312 10 50       49 if ($args[0] =~ /^std|dst|stdonly|dstonly$/i) {
313 10         23 my $tmp = lc(shift(@args));
314 10 100       33 if ($tmp eq 'stdonly') {
    100          
315 3         5 @isdst = (0);
316             } elsif ($tmp eq 'dstonly') {
317 2         4 @isdst = (1);
318             }
319             }
320 10         17 my @zone = @args;
321              
322 10 100 100     37 if ($#isdst == 0 &&
323             ! exists($$self{'data'}{'Offsets'}{$offset}{$isdst[0]})) {
324 2         8 return (2);
325             }
326              
327             # Check to see that each zone is valid, and contains this offset.
328              
329 8         9 my %tmp;
330 8         21 foreach my $isdst (0,1) {
331 16 50       228 next if (! exists $$self{'data'}{'Offsets'}{$offset}{$isdst});
332 16         16 my @z = @{ $$self{'data'}{'Offsets'}{$offset}{$isdst} };
  16         66  
333 16         26 $tmp{$isdst} = { map { $_,1 } @z };
  185         314  
334             }
335              
336 8         12 foreach my $z (@zone) {
337 15         19 my $lcz = lc($z);
338 15 100 100     85 if (! exists $$self{'data'}{'ZoneNames'}{$lcz}) {
    100 100        
    100          
339 2         11 return (3,$z);
340             } elsif (! exists $tmp{0}{$lcz} &&
341             ! exists $tmp{1}{$lcz}) {
342 1         6 return (4,$z);
343             } elsif ($#isdst == 0 &&
344             ! exists $tmp{$isdst[0]}{$lcz}) {
345 2         9 return (5,$z);
346             }
347 10         16 $z = $lcz;
348             }
349              
350             # Set the zones accordingly.
351              
352 3         203 foreach my $isdst (@isdst) {
353 6         10 my @z;
354 6         8 foreach my $z (@zone) {
355 10 100       31 push(@z,$z) if (exists $tmp{$isdst}{$z});
356             }
357 6         17 $$self{'data'}{'MyOffsets'}{$offset}{$isdst} = [ @z ];
358             }
359              
360 3         21 return (0);
361             }
362              
363             ########################################################################
364             # SYSTEM ZONE
365             ########################################################################
366              
367             sub curr_zone {
368 0     0 1 0 my($self,$reset) = @_;
369 0         0 my $dmb = $$self{'base'};
370              
371 0 0       0 if ($reset) {
372 0         0 $self->_set_curr_zone();
373             }
374              
375 0         0 my($ret) = $self->_now('systz',1);
376 0         0 return $$self{'data'}{'ZoneNames'}{$ret}
377             }
378              
379             sub curr_zone_methods {
380 0     0 1 0 my($self,@methods) = @_;
381              
382 0 0       0 if (${^TAINT}) {
383 0         0 carp "ERROR: [curr_zone_methods] not allowed when taint checking on";
384 0         0 return;
385             }
386              
387 0         0 $$self{'data'}{'methods'} = [ @methods ];
388             }
389              
390             sub _set_curr_zone {
391 518     518   915 my($self) = @_;
392 518         938 my $dmb = $$self{'base'};
393 518         1691 my $currzone = $self->_get_curr_zone();
394              
395 518         2732 $$dmb{'data'}{'now'}{'systz'} = $self->_zone($currzone);
396             }
397              
398             # This determines the system timezone using all of the methods
399             # applicable to the operating system. The first match is used.
400             #
401             sub _get_curr_zone {
402 518     518   899 my($self) = @_;
403 518         944 my $dmb = $$self{'base'};
404              
405 518         974 my $t = time;
406 518         15990 my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($t);
407 518         1277 my $currzone = '';
408 518 50       1439 my $dstflag = ($isdst ? 'dstonly' : 'stdonly');
409              
410 518         775 my (@methods) = @{ $$self{'data'}{'methods'} };
  518         4351  
411 518 50       1785 my $debug = ($ENV{DATE_MANIP_DEBUG} ? 1 : 0);
412              
413             defined $$self{'data'}{'path'}
414 518 50       6206 and local $ENV{PATH} = $$self{'data'}{'path'};
415              
416             METHOD:
417 518         1273 while (@methods) {
418 3506         4016 my $method = shift(@methods);
419 3506         3909 my @zone = ();
420              
421 3506 50       4659 print "*** DEBUG *** METHOD: $method [" if ($debug);
422              
423 3506 100       8088 if ($method eq 'main') {
    100          
    100          
    50          
    50          
    0          
    0          
    0          
424              
425 518 50       1394 if (! @methods) {
426 0 0       0 print "]\n" if ($debug);
427 0         0 carp "ERROR: [_set_curr_zone] main requires argument";
428 0         0 return;
429             }
430 518         827 my $var = shift(@methods);
431 518 50       1187 print "$var] " if ($debug);
432 170     170   1186 no strict "refs";
  170         235  
  170         7984  
433 518         727 my $val = ${ "::$var" };
  518         2210  
434 170     170   648 use strict "refs";
  170         222  
  170         359101  
435 518 50       1151 if (defined $val) {
436 0         0 push(@zone,$val);
437 0 0       0 print "$val\n" if ($debug);
438             } else {
439 518 50       1255 print "undef\n" if ($debug);
440             }
441              
442             } elsif ($method eq 'env') {
443 518 50       1411 if (@methods < 2) {
444 0 0       0 print "]\n" if ($debug);
445 0         0 carp "ERROR: [_set_curr_zone] env requires 2 argument";
446 0         0 return;
447             }
448 518         1569 my $type = lc( shift(@methods) );
449 518 50       1182 print "$type," if ($debug);
450              
451 518 50 33     1682 if ($type ne 'zone' &&
452             $type ne 'offset') {
453 0 0       0 print "?]\n" if ($debug);
454 0         0 carp "ERROR: [_set_curr_zone] env requires 'offset' or 'zone' " .
455             "as the first argument";
456 0         0 return;
457             }
458 518         888 my $var = shift(@methods);
459 518 50       1203 print "$var] " if ($debug);
460 518 100       1384 if (exists $ENV{$var}) {
461 24 50       52 if ($type eq 'zone') {
462 24         47 push(@zone,$ENV{$var});
463 24 50       56 print "$ENV{$var}\n" if ($debug);
464             } else {
465 0         0 my $off = $ENV{$var};
466 0 0       0 print "$ENV{$var} = " if ($debug);
467 0         0 $off = $dmb->_delta_convert('time',"0:0:$off");
468 0         0 $off = $dmb->_delta_convert('offset',$off);
469 0 0       0 print "$off\n" if ($debug);
470 0         0 push(@zone,$off);
471             }
472             } else {
473 494 50       1175 print "undef\n" if ($debug);
474             }
475              
476             } elsif ($method eq 'file') {
477 1976 50       2958 if (! @methods) {
478 0 0       0 print "]\n" if ($debug);
479 0         0 carp "ERROR: [_set_curr_zone] file requires argument";
480 0         0 return;
481             }
482 1976         2212 my $file = shift(@methods);
483 1976 50       2778 print "$file] " if ($debug);
484 1976 50       12196 if (! -f $file) {
485 1976 50       3038 print "not found\n" if ($debug);
486 1976         3388 next;
487             }
488              
489 0         0 my $in = new IO::File;
490 0 0       0 $in->open($file) || next;
491 0         0 my $firstline = 1;
492              
493 0         0 my @z;
494 0         0 while (! $in->eof) {
495 0         0 my $line = <$in>;
496 0         0 chomp($line);
497 0 0 0     0 next if ($line =~ /^\s*\043/ ||
498             $line =~ /^\s*$/);
499 0 0       0 if ($firstline) {
500 0         0 $firstline = 0;
501 0         0 $line =~ s/^\s*//;
502 0         0 $line =~ s/\s*$//;
503 0         0 $line =~ s/["']//g; # "
504 0         0 $line =~ s/\s+/_/g;
505 0         0 @z = ($line);
506             }
507              
508             # We're looking for lines of the form:
509             # TZ = string
510             # TIMEZONE = string
511             # ZONE = string
512             # Alternately, we may use a 1-line file (ignoring comments and
513             # whitespace) which contains only the zone name (it may be
514             # quoted or contain embedded whitespace).
515             #
516             # 'string' can be:
517             # the name of a timezone enclosed in single/double quotes
518             # with everything after the closing quote ignored (the
519             # name of the timezone may have spaces instead of underscores)
520             #
521             # a space delimited list of tokens, the first of which
522             # is the time zone
523             #
524             # the name of a timezone with underscores replaced by
525             # spaces and nothing after the timezone
526             #
527             # For some reason, RHEL6 desktop version stores timezones as
528             # America/New York
529             # instead of
530             # America/New_York
531             # which is why we have to handle the space/underscore
532             # substitution.
533              
534 0 0       0 if ($line =~ /^\s*(?:TZ|TIMEZONE|ZONE)\s*=\s*(.*)\s*$/) {
535 0         0 my $val = $1;
536 0         0 @z = ();
537 0 0       0 last if (! $val);
538              
539 0 0       0 if ($val =~ /^(["'])(.*?)\1/) {
    0          
540 0         0 my $z = $2;
541 0 0       0 last if (! $z);
542 0         0 $z =~ s/\s+/_/g;
543 0         0 push(@zone,$z);
544              
545             } elsif ($val =~ /\s/) {
546 0         0 $val =~ /^(\S+)/;
547 0         0 push(@zone,$1);
548 0         0 $val =~ s/\s+/_/g;
549 0         0 push(@zone,$val);
550              
551             } else {
552 0         0 push(@zone,$val);
553             }
554              
555 0         0 last;
556             }
557             }
558 0         0 close(IN);
559              
560 0 0       0 push(@zone,@z) if (@z);
561              
562 0 0       0 if ($debug) {
563 0 0       0 if (@zone) {
564 0         0 print "@zone\n";
565             } else {
566 0         0 print "no result\n";
567             }
568             }
569              
570             } elsif ($method eq 'tzdata') {
571 0 0       0 if (@methods < 2) {
572 0 0       0 print "]\n" if ($debug);
573 0         0 carp "ERROR: [_set_curr_zone] tzdata requires two arguments";
574 0         0 return;
575             }
576 0         0 my $file = shift(@methods);
577 0         0 my $dir = shift(@methods);
578 0 0       0 print "$file $dir" if ($debug);
579              
580 0         0 my $z;
581 0 0 0     0 if (-f $file && -d $dir) {
582 0         0 $z = _get_zoneinfo_zone($file,$dir);
583             }
584 0 0       0 if (defined($z)) {
    0          
585 0         0 push @zone, $z;
586 0 0       0 print "] $z\n" if ($debug);
587             } elsif ($debug) {
588 0         0 print "] no result\n";
589             }
590              
591             } elsif ($method eq 'command') {
592 494 50       1137 if (! @methods) {
593 0 0       0 print "]\n" if ($debug);
594 0         0 carp "ERROR: [_set_curr_zone] command requires argument";
595 0         0 return;
596             }
597 494         823 my $command = shift(@methods);
598 494 50       1018 print "$command] " if ($debug);
599 494         1709 my ($out) = _cmd($command);
600 494 50       6393 push(@zone,$out) if ($out);
601              
602 494 50       3399 if ($debug) {
603 0 0       0 if ($out) {
604 0         0 print "$out\n";
605             } else {
606 0         0 print "no output\n";
607             }
608             }
609              
610             } elsif ($method eq 'cmdfield') {
611 0 0       0 if ($#methods < 1) {
612 0 0       0 print "]\n" if ($debug);
613 0         0 carp "ERROR: [_set_curr_zone] cmdfield requires 2 arguments";
614 0         0 return;
615             }
616 0         0 my $command = shift(@methods);
617 0         0 my $n = shift(@methods);
618 0 0       0 print "$command,$n]\n" if ($debug);
619 0         0 my ($out) = _cmd($command);
620 0         0 my $val;
621              
622 0 0       0 if ($out) {
623 0         0 $out =~ s/^\s*//;
624 0         0 $out =~ s/\s*$//;
625 0         0 my @out = split(/\s+/,$out);
626 0 0       0 $val = $out[$n] if (defined $out[$n]);
627 0         0 push(@zone,$val);
628             }
629              
630 0 0       0 if ($debug) {
631 0 0       0 if ($val) {
632 0         0 print "$val\n";
633             } else {
634 0         0 print "no result\n";
635             }
636             }
637              
638             } elsif ($method eq 'gmtoff') {
639 0 0       0 print "] " if ($debug);
640 0         0 my($secUT,$minUT,$hourUT,$mdayUT,$monUT,$yearUT,$wdayUT,$ydayUT,
641             $isdstUT) = gmtime($t);
642 0 0       0 if ($mdayUT>($mday+1)) {
    0          
643             # UT = 28-31 LT = 1
644 0         0 $mdayUT=0;
645             } elsif ($mdayUT<($mday-1)) {
646             # UT = 1 LT = 28-31
647 0         0 $mday=0;
648             }
649 0         0 $sec = (($mday*24 + $hour)*60 + $min)*60 + $sec;
650 0         0 $secUT = (($mdayUT*24 + $hourUT)*60 + $minUT)*60 + $secUT;
651 0         0 my $off = $sec-$secUT;
652              
653 0         0 $off = $dmb->_delta_convert('time',"0:0:$off");
654 0         0 $off = $dmb->_delta_convert('offset',$off);
655 0         0 push(@zone,$off);
656 0 0       0 print "$off\n" if ($debug);
657              
658             } elsif ($method eq 'registry') {
659 0 0       0 print "] " if ($debug);
660 0         0 my $z = $self->_windows_registry_val();
661 0 0       0 if ($z) {
662 0         0 push(@zone,$z);
663 0 0       0 print "$z\n" if ($debug);
664             } else {
665 0 0       0 print "no result\n" if ($debug);
666             }
667              
668             } else {
669 0 0       0 print "]\n" if ($debug);
670 0         0 carp "ERROR: [_set_curr_zone] invalid method: $method";
671 0         0 return;
672             }
673              
674 1530         4783 while (@zone) {
675 518         4813 my $zone = lc(shift(@zone));
676              
677             # OpenUNIX puts a colon at the start
678 518         4250 $zone =~ s/^://;
679              
680             # If we got a zone name/alias
681 518         7744 $currzone = $self->_zone($zone);
682 518 50       4668 last METHOD if ($currzone);
683              
684             # If we got an abbreviation (EST)
685 0 0       0 if (exists $$self{'data'}{'Abbrev'}{$zone}) {
686 0         0 $currzone = $$self{'data'}{'Abbrev'}{$zone}[0];
687 0         0 last METHOD;
688             }
689              
690             # If we got an offset
691              
692 0         0 $currzone = $self->__zone([],'',$zone,'',$dstflag);
693 0 0       0 last METHOD if ($currzone);
694             }
695             }
696              
697 518 50       2153 if (! $currzone) {
698 0         0 carp "ERROR: Date::Manip unable to determine Time Zone. GMT will be used.";
699 0         0 $currzone = 'Etc/GMT';
700             }
701              
702 518         24969 return $currzone;
703             }
704              
705             #######################
706             # The following section comes from the DateTime-TimeZone module
707              
708             {
709             my $want_content;
710             my $want_size;
711             my $zoneinfo;
712              
713             sub _get_zoneinfo_zone {
714 0     0   0 my($localtime,$z) = @_;
715 0         0 $zoneinfo = $z;
716              
717             # /etc/localtime should be either a link to a tzdata file in
718             # /usr/share/zoneinfo or a copy of one of the files there.
719              
720 0 0 0     0 return '' if (! -d $zoneinfo || ! -f $localtime);
721              
722 0         0 require Cwd;
723 0 0       0 if (-l $localtime) {
724 0         0 return _zoneinfo_file_name_to_zone(
725             Cwd::abs_path($localtime),
726             Cwd::abs_path($zoneinfo),
727             );
728             }
729              
730 0         0 $want_content = _zoneinfo_file_slurp($localtime);
731 0         0 $want_size = -s $localtime;
732              
733             # File::Find can't bail in the middle of a find, and we only want the
734             # first match, so we'll call it in an eval.
735              
736 0         0 local $@ = undef;
737 0 0       0 eval {
738 0         0 require File::Find;
739 0         0 File::Find::find
740             ({
741             wanted => \&_zoneinfo_find_file,
742             no_chdir => 1,
743             },
744             $zoneinfo,
745             );
746 0         0 1;
747             } and return;
748             ref $@
749 0 0       0 and return $@->{zone};
750 0         0 croak $@;
751             }
752              
753             sub _zoneinfo_find_file {
754 0     0   0 my $zone;
755 0 0 0     0 defined($zone = _zoneinfo_file_name_to_zone($File::Find::name,
      0        
      0        
756             $zoneinfo))
757             and -f $_
758             and $want_size == -s _
759             and ($want_content eq _zoneinfo_file_slurp($File::Find::name))
760             and croak { zone => $zone };
761             }
762             }
763              
764             sub _zoneinfo_file_name_to_zone {
765 0     0   0 my($file,$zoneinfo) = @_;
766 0         0 require File::Spec;
767 0         0 my $zone = File::Spec->abs2rel($file,$zoneinfo);
768             return $zone if (exists $Date::Manip::Zones::ZoneNames{lc($zone)} ||
769 0 0 0     0 exists $Date::Manip::Zones::Alias{lc($zone)});
770 0         0 return;
771             }
772              
773             sub _zoneinfo_file_slurp {
774 0     0   0 my($file) = @_;
775 0 0       0 open my $fh, '<', $file
776             or return;
777 0         0 binmode $fh;
778 0         0 local $/ = undef;
779 0         0 return <$fh>;
780             }
781              
782             sub _windows_registry_val {
783 0     0   0 my($self) = @_;
784              
785 0         0 require Win32::TieRegistry;
786              
787 0 0       0 my $lmachine = new Win32::TieRegistry 'LMachine',
788             { Access => Win32::TieRegistry::KEY_READ(),
789             Delimiter => '/' }
790             or return '';
791              
792 0         0 my $tzinfo = $lmachine->Open('SYSTEM/CurrentControlSet/Control/TimeZoneInformation/');
793              
794             #
795             # Windows Vista, Windows 2008 Server
796             #
797              
798 0         0 my $tzkn = $tzinfo->GetValue('TimeZoneKeyName');
799 0 0 0     0 if (defined($tzkn) && $tzkn) {
800             # For some reason, Vista is tacking on a bunch of stuff at the
801             # end of the timezone, starting with a chr(0). Strip it off.
802              
803 0         0 my $c = chr(0);
804 0         0 my $i = index($tzkn,$c);
805 0 0       0 if ($i != -1) {
806 0         0 $tzkn = substr($tzkn,0,$i);
807             }
808 0         0 my $z = $self->_zone($tzkn);
809 0 0       0 return $z if ($z);
810             }
811              
812             #
813             # Windows NT, Windows 2000, Windows XP, Windows 2003 Server
814             #
815              
816 0         0 my $stdnam = $tzinfo->GetValue('StandardName');
817 0         0 my $z = $self->_zone($stdnam);
818 0 0       0 return $z if ($z);
819              
820             #
821             # For non-English versions, we have to determine which timezone it
822             # actually is.
823             #
824              
825 0         0 my $atz = $lmachine->Open('SOFTWARE/Microsoft/Windows NT/CurrentVersion/Time Zones/');
826 0 0 0     0 if (! defined($atz) || ! $atz) {
827 0         0 $atz = $lmachine->Open('SOFTWARE/Microsoft/Windows/CurrentVersion/Time Zones/');
828             }
829              
830 0 0 0     0 return "" if (! defined($atz) || ! $atz);
831              
832 0         0 foreach my $z ($atz->SubKeyNames()) {
833 0         0 my $tmp = $atz->Open("$z/");
834 0         0 my $znam = $tmp->GetValue('Std');
835 0 0       0 return $z if ($znam eq $stdnam);
836             }
837             }
838              
839             # End of DateTime-TimeZone section
840             #######################
841              
842             # We will be testing commands that don't exist on all architectures,
843             # so disable warnings.
844             #
845 170     170   1262 no warnings;
  170         249  
  170         18718  
846             sub _cmd {
847 494     494   997 my($cmd) = @_;
848 494         1391 local(*IN);
849 494 50       1951791 open(IN,"$cmd |") || return ();
850 494         370685 my @out = ;
851 494         61092 close(IN);
852 494         6011 chomp(@out);
853 494         15879 return @out;
854             }
855 170     170   811 use warnings;
  170         328  
  170         720759  
856              
857             ########################################################################
858             # DETERMINING A TIMEZONE
859             ########################################################################
860              
861             sub zone {
862 62     62 1 38775 my($self,@args) = @_;
863 62         104 my $dmb = $$self{'base'};
864 62 100       151 if (! @args) {
865 1         25 my($tz) = $self->_now('tz',1);
866 1         5 return $$self{'data'}{'ZoneNames'}{$tz}
867             }
868              
869             # Parse the arguments
870              
871 61         128 my($zone,$abbrev,$offset,$dstflag) = ('','','','');
872 61         68 my $date = [];
873 61         65 my $tmp;
874 61         91 foreach my $arg (@args) {
875              
876 106 100       229 if (ref($arg) eq 'ARRAY') {
    50          
877 34 50       58 if ($#$arg == 5) {
    0          
878             # [Y,M,D,H,Mn,S]
879 34 50       53 return undef if (@$date);
880 34         49 $date = $arg;
881              
882             } elsif ($#$arg == 2) {
883             # [H,Mn,S]
884 0 0       0 return undef if ($offset);
885 0         0 $offset = $dmb->join('offset',$arg);
886 0 0       0 return undef if (! $offset);
887              
888             } else {
889 0         0 return undef;
890             }
891              
892             } elsif (ref($arg)) {
893 0         0 return undef;
894              
895             } else {
896 72         96 $arg = lc($arg);
897              
898 72 100 66     328 if ($arg =~ /^(std|dst|stdonly|dstonly)$/) {
    100          
    100          
    100          
    50          
899 11 50       19 return undef if ($dstflag);
900 11         17 $dstflag = $arg;
901              
902             } elsif ($tmp = $self->_zone($arg)) {
903 15 50       33 return undef if ($zone);
904 15         28 $zone = $tmp;
905              
906             } elsif (exists $$self{'data'}{'MyAbbrev'}{$arg} ||
907             exists $$self{'data'}{'Abbrev'}{$arg}) {
908 13 50       34 return undef if ($abbrev);
909 13         29 $abbrev = $arg;
910              
911             } elsif ($tmp = $dmb->split('offset',$arg)) {
912 30 50       47 return undef if ($offset);
913 30         76 $offset = $dmb->_delta_convert('offset',$arg);
914              
915             } elsif ($tmp = $dmb->split('date',$arg)) {
916 0 0       0 return undef if ($date);
917 0         0 $date = $tmp;
918              
919             } else {
920 3         12 return undef;
921             }
922             }
923             }
924              
925 58         151 return $self->__zone($date,$offset,$zone,$abbrev,$dstflag);
926             }
927              
928             # $date = [Y,M,D,H,Mn,S]
929             # $offset = '-HH:Mn:SS'
930             # $zone = 'us/eastern' (lowercase)
931             # $abbrev = 'est' (lowercase)
932             # $dstflag= 'stdonly' (lowercase)
933             #
934             sub __zone {
935 564     564   2666 my($self,$date,$offset,$zone,$abbrev,$dstflag) = @_;
936 564         1231 my $dmb = $$self{'base'};
937              
938             #
939             # Determine the zones that match all data.
940             #
941              
942 564         1144 my @zone;
943              
944 564         1039 while (1) {
945              
946             # No information
947              
948 564 100 100     2163 if (! $zone &&
      100        
949             ! $abbrev &&
950             ! $offset) {
951 3         6 my($z) = $self->_now('tz',1);
952 3         7 @zone = (lc($z));
953             }
954              
955             # $dstflag
956             #
957             # $dstflag is "dst' if
958             # zone is passed in as an offset
959             # date is passed in
960              
961 564 100 100     2153 $dstflag = "dst" if ($offset && @$date && ! $dstflag);
      100        
962              
963 564         894 my(@isdst);
964 564 100       2648 if ($dstflag eq 'stdonly') {
    100          
    100          
965 4         114 @isdst = (0);
966             } elsif ($dstflag eq 'dstonly') {
967 4         7 @isdst = (1);
968             } elsif ($dstflag eq 'dst') {
969 71         133 @isdst = (1,0);
970             } else {
971 485         1471 @isdst = (0,1);
972             }
973              
974             # We may pass in $zone and not $abbrev when it really should be
975             # $abbrev.
976              
977 564 100 66     3311 if ($zone && ! $abbrev) {
978 376 50 0     1806 if (exists $$self{'data'}{'Alias'}{$zone}) {
    0          
979             # no change
980             } elsif (exists $$self{'data'}{'MyAbbrev'}{$zone} ||
981             exists $$self{'data'}{'Abbrev'}{$zone}) {
982 0         0 $abbrev = $zone;
983 0         0 $zone = '';
984             }
985             }
986              
987             # $zone
988              
989 564 100       1445 if ($zone) {
990             my $z = (exists $$self{'data'}{'Alias'}{$zone} ?
991 376 50       2217 $$self{'data'}{'Alias'}{$zone} : $zone);
992 376         1258 @zone = ($z);
993             }
994              
995             # $abbrev
996              
997 564 100       1836 if ($abbrev) {
998 117         173 my @abbrev_zones;
999 117 100       692 if (exists $$self{'data'}{'MyAbbrev'}{$abbrev}) {
    50          
1000 8         10 @abbrev_zones = @{ $$self{'data'}{'MyAbbrev'}{$abbrev} };
  8         20  
1001             } elsif (exists $$self{'data'}{'Abbrev'}{$abbrev}) {
1002 109         162 @abbrev_zones = @{ $$self{'data'}{'Abbrev'}{$abbrev} };
  109         895  
1003             }
1004              
1005 117         228 my @z;
1006 117         237 foreach my $isdst (@isdst) {
1007 234         820 my @tmp = $self->_check_abbrev_isdst($abbrev,$isdst,@abbrev_zones);
1008 234 100       548 if (@tmp) {
1009 154 100       343 if (@z) {
1010 37         169 @z = _list_add(\@z,\@tmp);
1011             } else {
1012 117         471 @z = @tmp;
1013             }
1014             }
1015             }
1016              
1017 117 50       368 if (@zone) {
1018 0         0 @zone = _list_union(\@z,\@zone);
1019             } else {
1020 117         509 @zone = @z;
1021             }
1022 117 50       572 last if (! @zone);
1023             }
1024              
1025             # $offset
1026              
1027 564 100       2144 if ($offset) {
1028 82 100       298 return undef if (! exists $$self{'data'}{'Offmod'}{$offset});
1029 81         335 $self->_offmod($offset);
1030              
1031 81         127 my @z;
1032 81         168 foreach my $isdst (@isdst) {
1033             my $tmp = $$self{'data'}{'MyOffsets'}{$offset}{$isdst} ||
1034 155   100     4414 $$self{'data'}{'Offsets'}{$offset}{$isdst};
1035              
1036 155         199 my @tmp;
1037 155 100       256 if ($abbrev) {
1038 28         152 @tmp = $self->_check_offset_abbrev_isdst($offset,$abbrev,$isdst,$tmp);
1039             } else {
1040 127 100       617 @tmp = @$tmp if ($tmp);
1041             }
1042              
1043 155 100       283 if (@tmp) {
1044 134 100       199 if (@z) {
1045 55         188 @z = _list_add(\@z,\@tmp);
1046             } else {
1047 79         274 @z = @tmp;
1048             }
1049             }
1050             }
1051              
1052 81 100       149 if (@zone) {
1053 14         68 @zone = _list_union(\@zone,\@z);
1054             } else {
1055 67         267 @zone = @z;
1056             }
1057 81 100       247 last if (! @zone);
1058             }
1059              
1060             # $date
1061              
1062 561 100       1661 if (@$date) {
1063             # Get all periods for the year.
1064             #
1065             # Test all periods to make sure that $date is between the
1066             # wallclock times AND matches other criteria. All periods
1067             # must be tested since the same wallclock time can be in
1068             # multiple periods.
1069              
1070 536         1238 my @tmp;
1071 536         1675 my $isdst = '';
1072 536 50       1465 $isdst = 0 if ($dstflag eq 'stdonly');
1073 536 50       1306 $isdst = 1 if ($dstflag eq 'dstonly');
1074              
1075             ZONE:
1076 536         1671 foreach my $z (@zone) {
1077 4100 100       15560 $self->_module($z) if (! exists $$self{'data'}{'Zones'}{$z}{'Loaded'});
1078 4100         8092 my $y = $$date[0];
1079 4100         9067 my @periods = $self->_all_periods($z,$y);
1080              
1081 4100         5051 foreach my $period (@periods) {
1082 7091 100 100     37436 next if (($abbrev ne '' && lc($abbrev) ne lc($$period[4])) ||
      100        
      100        
      33        
      66        
      66        
      100        
1083             ($offset ne '' && $offset ne $$period[2]) ||
1084             ($isdst ne '' && $isdst ne $$period[5]) ||
1085             $dmb->cmp($date,$$period[1]) == -1 ||
1086             $dmb->cmp($date,$$period[7]) == 1
1087             );
1088 2214         3186 push(@tmp,$z);
1089 2214         4014 next ZONE;
1090             }
1091             }
1092 536         1933 @zone = @tmp;
1093 536 100       3230 last if (! @zone);
1094             }
1095              
1096 552         1312 last;
1097             }
1098              
1099             # Return the value/list
1100              
1101 563 100       2163 if (wantarray) {
1102 27         35 my @ret;
1103 27         36 foreach my $z (@zone) {
1104 69         185 push(@ret,$$self{'data'}{'ZoneNames'}{$z});
1105             }
1106 27         164 return @ret;
1107             }
1108              
1109 536 100       1764 return '' if (! @zone);
1110 527         3891 return $$self{'data'}{'ZoneNames'}{$zone[0]}
1111             }
1112              
1113             # This returns a list of all timezones which have the correct
1114             # abbrev/isdst combination.
1115             #
1116             sub _check_abbrev_isdst {
1117 234     234   914 my($self,$abbrev,$isdst,@zones) = @_;
1118              
1119 234         309 my @ret;
1120             ZONE:
1121 234         384 foreach my $zone (@zones) {
1122 3586 100       11821 $self->_module($zone) if (! exists $$self{'data'}{'Zones'}{$zone}{'Loaded'});
1123              
1124 3586         5805 foreach my $y (sort keys %{ $$self{'data'}{'Zones'}{$zone}{'Dates'} }) {
  3586         107569  
1125 170730         146608 my @periods = @{ $$self{'data'}{'Zones'}{$zone}{'Dates'}{$y} };
  170730         276126  
1126 170730         161955 foreach my $period (@periods) {
1127 329117         533251 my($dateUT,$dateLT,$off,$offref,$abb,$dst,$endUT,$endLT) = @$period;
1128 329117 100 100     602257 next if (lc($abbrev) ne lc($abb) ||
1129             $isdst != $dst);
1130 1830         2560 push(@ret,$zone);
1131 1830         8605 next ZONE;
1132             }
1133             }
1134             }
1135              
1136 234         1452 return @ret;
1137             }
1138              
1139             # This returns a list of all timezones which have the correct
1140             # abbrev/isdst combination.
1141             #
1142             sub _check_offset_abbrev_isdst {
1143 28     28   78 my($self,$offset,$abbrev,$isdst,$zones) = @_;
1144              
1145 28         43 my @ret;
1146 28         63 ZONE: foreach my $zone (@$zones) {
1147 803 100       2755 $self->_module($zone) if (! exists $$self{'data'}{'Zones'}{$zone}{'Loaded'});
1148              
1149 803         1150 foreach my $y (sort keys %{ $$self{'data'}{'Zones'}{$zone}{'Dates'} }) {
  803         22762  
1150 54299         44705 my @periods = @{ $$self{'data'}{'Zones'}{$zone}{'Dates'}{$y} };
  54299         83775  
1151 54299         48667 foreach my $period (@periods) {
1152 106174         169807 my($dateUT,$dateLT,$off,$offref,$abb,$dst,$endUT,$endLT) = @$period;
1153 106174 100 100     174081 next if (lc($abbrev) ne lc($abb) ||
      100        
1154             $offset ne $off ||
1155             $isdst != $dst);
1156 185         269 push(@ret,$zone);
1157 185         826 next ZONE;
1158             }
1159             }
1160             }
1161              
1162 28         149 return @ret;
1163             }
1164              
1165             # This finds the elements common to two lists, and preserves the order
1166             # from the first list.
1167             #
1168             sub _list_union {
1169 14     14   32 my($list1,$list2) = @_;
1170 14         43 my(%list2) = map { $_,1 } @$list2;
  182         338  
1171 14         31 my(@ret);
1172 14         35 foreach my $ele (@$list1) {
1173 221 100       358 push(@ret,$ele) if (exists $list2{$ele});
1174             }
1175 14         79 return @ret;
1176             }
1177              
1178             # This adds elements from the second list to the first list, provided
1179             # they are not already there.
1180             #
1181             sub _list_add {
1182 92     92   193 my($list1,$list2) = @_;
1183 92         275 my(%list1) = map { $_,1 } @$list1;
  1119         1901  
1184 92         308 my(@ret) = @$list1;
1185 92         165 foreach my $ele (@$list2) {
1186 1770 100       2165 next if (exists $list1{$ele});
1187 1288         1263 push(@ret,$ele);
1188 1288         1894 $list1{$ele} = 1;
1189             }
1190 92         732 return @ret;
1191             }
1192              
1193             ########################################################################
1194             # PERIODS METHODS
1195             ########################################################################
1196              
1197             sub all_periods {
1198 7     7 1 142157 my($self,$zone,$year) = @_;
1199              
1200 7         18 my $z = $self->_zone($zone);
1201 7 50       17 if (! $z) {
1202 0         0 carp "ERROR: [periods] Invalid zone: $zone";
1203 0         0 return;
1204             }
1205 7         14 $zone = $z;
1206 7 50       17 $self->_module($zone) if (! exists $$self{'data'}{'Zones'}{$zone}{'Loaded'});
1207              
1208             # Run a faster 'dclone' so we don't return the actual data.
1209              
1210 7         15 my @tmp = $self->_all_periods($zone,$year);
1211 7         10 my @ret;
1212 7         12 foreach my $ele (@tmp) {
1213             push(@ret,
1214 14         18 [ [ @{$$ele[0]} ],[ @{$$ele[1]} ],$$ele[2],[ @{$$ele[3]} ],$$ele[4],
  14         19  
  14         23  
1215 14         14 $$ele[5], [ @{$$ele[6]} ],[ @{$$ele[7]} ],$$ele[8],$$ele[9],
  14         18  
  14         48  
1216             $$ele[10],$$ele[11] ]);
1217             }
1218 7         20 return @ret;
1219             }
1220              
1221             sub _all_periods {
1222 34345     34345   51789 my($self,$zone,$year) = @_;
1223 34345         38347 $year += 0;
1224              
1225 34345 100       75835 if (! exists $$self{'data'}{'Zones'}{$zone}{'AllDates'}{$year}) {
1226              
1227             #
1228             # $ym1 is the year prior to $year which contains a rule (which will
1229             # end in $year or later). $y is $year IF the zone contains rules
1230             # for this year.
1231             #
1232              
1233 3142         4141 my($ym1,$ym0);
1234 3142 100 100     9232 if ($year > $$self{'data'}{'LastYear'} &&
1235             exists $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'zone'}) {
1236 5         7 $ym1 = $year-1;
1237 5         8 $ym0 = $year;
1238              
1239             } else {
1240 3137         3754 foreach my $y (sort { $a <=> $b }
  1776694         1529724  
1241 3137         53231 keys %{ $$self{'data'}{'Zones'}{$zone}{'Dates'} }) {
1242 116203 100       129521 if ($y < $year) {
1243 113985         100435 $ym1 = $y;
1244 113985         106108 next;
1245             }
1246 2218 100       5599 $ym0 = $year if ($year == $y);
1247 2218         2857 last;
1248             }
1249             }
1250 3142 100       19752 $ym1 = 0 if (! $ym1);
1251              
1252             #
1253             # Get the periods from the prior year. The last one is used (any others
1254             # are discarded).
1255             #
1256              
1257 3142         3799 my(@periods);
1258              
1259             # $ym1 will be 0 in 0001
1260 3142 100       5118 if ($ym1) {
1261 3139         9035 my @tmp = $self->_periods($zone,$ym1);
1262 3139 50       10032 push(@periods,pop(@tmp)) if (@tmp);
1263             }
1264              
1265             #
1266             # Add on any periods from the current year.
1267             #
1268              
1269 3142 100       5559 if ($ym0) {
1270 1894         3751 push(@periods,$self->_periods($zone,$year));
1271             }
1272              
1273 3142         9538 $$self{'data'}{'Zones'}{$zone}{'AllDates'}{$year} = [ @periods ];
1274             }
1275              
1276 34345         35619 return @{ $$self{'data'}{'Zones'}{$zone}{'AllDates'}{$year} };
  34345         74514  
1277             }
1278              
1279             sub periods {
1280 8     8 1 148482 my($self,$zone,$year,$year1) = @_;
1281              
1282 8         19 my $z = $self->_zone($zone);
1283 8 50       19 if (! $z) {
1284 0         0 carp "ERROR: [periods] Invalid zone: $zone";
1285 0         0 return;
1286             }
1287 8         8 $zone = $z;
1288 8 100       23 $self->_module($zone) if (! exists $$self{'data'}{'Zones'}{$zone}{'Loaded'});
1289              
1290 8 100       18 if (! defined($year1)) {
1291 7         14 return $self->_periods($zone,$year);
1292             }
1293              
1294 1 50       3 $year = 1 if (! defined($year));
1295              
1296 1         1 my @ret;
1297 1         11 my $lastyear = $$self{'data'}{'LastYear'};
1298              
1299 1 50       3 if ($year <= $lastyear) {
1300 1         2 foreach my $y (sort { $a <=> $b }
  1141         933  
1301 1         26 keys %{ $$self{'data'}{'Zones'}{$zone}{'Dates'} }) {
1302 4 100 66     15 last if ($y > $year1 || $y > $lastyear);
1303 3 50       7 next if ($y < $year);
1304 3         4 push(@ret,$self->_periods($zone,$y));
1305             }
1306             }
1307              
1308 1 50       8 if ($year1 > $lastyear) {
1309 0 0       0 $year = $lastyear + 1 if ($year <= $lastyear);
1310 0         0 foreach my $y ($year..$year1) {
1311 0         0 push(@ret,$self->_periods($zone,$y));
1312             }
1313             }
1314              
1315 1         3 return @ret;
1316             }
1317              
1318             sub _periods {
1319 5043     5043   9002 my($self,$zone,$year) = @_;
1320 5043         5925 $year += 0;
1321              
1322 5043 100       12367 if (! exists $$self{'data'}{'Zones'}{$zone}{'Dates'}{$year}) {
1323              
1324 12         16 my @periods = ();
1325 12 100       27 if ($year > $$self{'data'}{'LastYear'}) {
1326             # Calculate periods using the LastRule method
1327 11         30 @periods = $self->_lastrule($zone,$year);
1328             }
1329              
1330 12         47 $$self{'data'}{'Zones'}{$zone}{'Dates'}{$year} = [ @periods ];
1331             }
1332              
1333             # A faster 'dclone' so we don't return the actual data
1334 5043         5596 my @ret;
1335 5043         5109 foreach my $ele (@{ $$self{'data'}{'Zones'}{$zone}{'Dates'}{$year} }) {
  5043         12580  
1336             push(@ret,
1337 9120         16697 [ [ @{$$ele[0]} ],[ @{$$ele[1]} ],$$ele[2],[ @{$$ele[3]} ],$$ele[4],$$ele[5],
  9120         13868  
  9120         14018  
1338 9120         9502 [ @{$$ele[6]} ],[ @{$$ele[7]} ],$$ele[8],$$ele[9],$$ele[10],$$ele[11] ]);
  9120         13894  
  9120         34095  
1339             }
1340 5043         9521 return @ret;
1341             }
1342              
1343             sub date_period {
1344 30238     30238 1 142705 my($self,$date,$zone,$wallclock,$isdst) = @_;
1345 30238 100       43810 $wallclock = 0 if (! $wallclock);
1346 30238 100       42187 $isdst = 0 if (! $isdst);
1347              
1348 30238         51793 my $z = $self->_zone($zone);
1349 30238 50       44563 if (! $z) {
1350 0         0 carp "ERROR: [date_period] Invalid zone: $zone";
1351 0         0 return;
1352             }
1353 30238         31385 $zone = $z;
1354 30238 100       58318 $self->_module($zone) if (! exists $$self{'data'}{'Zones'}{$zone}{'Loaded'});
1355              
1356 30238         34526 my $dmb = $$self{'base'};
1357 30238         47215 my @date = @$date;
1358 30238         32541 my $year = $date[0];
1359 30238         65624 my $dates= $dmb->_date_fields(@$date);
1360 30238 50 33     74888 return () if ($year < 0 || $year > 9999);
1361              
1362 30238 100       38419 if ($wallclock) {
1363             # A wallclock date
1364              
1365 24689         46531 my @period = $self->_all_periods($zone,$year);
1366 24689         35042 my $beg = $period[0]->[9];
1367 24689         29679 my $end = $period[-1]->[11];
1368 24689 50       52310 if (($dates cmp $beg) == -1) {
    50          
1369 0         0 @period = $self->_all_periods($zone,$year-1);
1370             } elsif (($dates cmp $end) == 1) {
1371 0         0 @period = $self->_all_periods($zone,$year+1);
1372             }
1373              
1374 24689         24810 my(@per);
1375 24689         32948 foreach my $period (@period) {
1376 70056         119651 my($begUT,$begLT,$offsetstr,$offset,$abbrev,$dst,$endUT,$endLT,
1377             $begUTs,$begLTs,$endUTs,$endLTs) = @$period;
1378 70056 100 100     148750 if (($dates cmp $begLTs) != -1 && ($dates cmp $endLTs) != 1) {
1379 24721         43338 push(@per,$period);
1380             }
1381             }
1382              
1383 24689 100       44349 if ($#per == -1) {
    100          
    50          
1384 8         31 return ();
1385             } elsif ($#per == 0) {
1386 24641         62241 return $per[0];
1387             } elsif ($#per == 1) {
1388 40 100       82 if ($per[0][5] == $isdst) {
1389 19         60 return $per[0];
1390             } else {
1391 21         81 return $per[1];
1392             }
1393             } else {
1394 0         0 carp "ERROR: [date_period] Impossible error";
1395 0         0 return;
1396             }
1397              
1398             } else {
1399             # A GMT date
1400              
1401 5549         8763 my @period = $self->_all_periods($zone,$year);
1402 5549         7435 foreach my $period (@period) {
1403 5758         12009 my($begUT,$begLT,$offsetstr,$offset,$abbrev,$isdst,$endUT,$endLT,
1404             $begUTs,$begLTs,$endUTs,$endLTs) = @$period;
1405 5758 100 66     15256 if (($dates cmp $begUTs) != -1 && ($dates cmp $endUTs) != 1) {
1406 5549         12778 return $period;
1407             }
1408             }
1409 0         0 carp "ERROR: [date_period] Impossible error";
1410 0         0 return;
1411             }
1412             }
1413              
1414             # Calculate critical dates from the last rule. If $endonly is passed
1415             # in, it only calculates the ending of the zone period before the
1416             # start of the first one. This is necessary so that the last period in
1417             # one year can find out when it ends (which is determined in the
1418             # following year).
1419             #
1420             # Returns:
1421             # [begUT, begLT, offsetstr, offset, abb, ISDST, endUT, endLT,
1422             # begUTstr, begLTstr, endUTstr, endLTstr]
1423             # for each.
1424             #
1425             sub _lastrule {
1426 22     22   46 my($self,$zone,$year,$endonly) = @_;
1427              
1428             #
1429             # Get the list of rules (actually, the month in which the
1430             # rule triggers a time change). If there are none, then
1431             # this zone doesn't have a LAST RULE.
1432             #
1433              
1434             my @mon = (sort keys
1435 22         26 %{ $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'} });
  22         119  
1436 22 50       48 return () if (! @mon);
1437              
1438             #
1439             # Analyze each time change.
1440             #
1441              
1442 22         31 my @dates = ();
1443 22         26 my $dmb = $$self{'base'};
1444              
1445 22         37 my $stdoff = $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'zone'}{'stdoff'};
1446 22         43 my $dstoff = $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'zone'}{'dstoff'};
1447              
1448 22         26 my (@period);
1449              
1450 22         32 foreach my $mon (@mon) {
1451             my $flag =
1452 33         70 $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'}{$mon}{'flag'};
1453             my $dow =
1454 33         59 $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'}{$mon}{'dow'};
1455             my $num =
1456 33         55 $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'}{$mon}{'num'};
1457             my $isdst=
1458 33         51 $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'}{$mon}{'isdst'};
1459             my $time =
1460 33         53 $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'}{$mon}{'time'};
1461             my $type =
1462 33         44 $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'}{$mon}{'type'};
1463             my $abb =
1464 33         50 $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'}{$mon}{'abb'};
1465              
1466             # The end of the current period and the beginning of the next
1467 33         129 my($endUT,$endLT,$begUT,$begLT) =
1468             $dmb->_critical_date($year,$mon,$flag,$num,$dow,
1469             $isdst,$time,$type,$stdoff,$dstoff);
1470 33 100       83 return ($endUT,$endLT) if ($endonly);
1471              
1472 22 100       35 if (@period) {
1473 11         32 push(@period,$endUT,$endLT);
1474 11         21 push(@dates,[@period]);
1475             }
1476 22 100       36 my $offsetstr = ($isdst ? $dstoff : $stdoff);
1477 22         37 my $offset = $dmb->split('offset',$offsetstr);
1478              
1479 22         74 @period = ($begUT,$begLT,$offsetstr,$offset,$abb,$isdst);
1480             }
1481              
1482 11         47 push(@period,$self->_lastrule($zone,$year+1,1));
1483 11         26 push(@dates,[@period]);
1484              
1485 11         16 foreach my $period (@dates) {
1486 22         43 my($begUT,$begLT,$offsetstr,$offset,$abbrev,$dst,$endUT,$endLT) = @$period;
1487 22         73 my $begUTstr = $dmb->join("date",$begUT);
1488 22         41 my $begLTstr = $dmb->join("date",$begLT);
1489 22         61 my $endUTstr = $dmb->join("date",$endUT);
1490 22         37 my $endLTstr = $dmb->join("date",$endLT);
1491 22         75 $period = [$begUT,$begLT,$offsetstr,$offset,$abbrev,$dst,$endUT,$endLT,
1492             $begUTstr,$begLTstr,$endUTstr,$endLTstr];
1493             }
1494              
1495 11         137 return @dates;
1496             }
1497              
1498             ########################################################################
1499             # CONVERSION
1500             ########################################################################
1501              
1502             sub convert {
1503 41     41 1 87725 my($self,$date,$from,$to,$isdst) = @_;
1504 41         123 $self->_convert('convert',$date,$from,$to,$isdst);
1505             }
1506              
1507             sub convert_to_gmt {
1508 2861     2861 1 29966 my($self,$date,@arg) = @_;
1509 2861         6475 my($err,$from,$isdst) = _convert_args('convert_to_gmt',@arg);
1510 2861 50       5091 return (1) if ($err);
1511              
1512 2861         3409 my $dmb = $$self{'base'};
1513              
1514 2861 50       4631 if (! $from) {
1515 0         0 $from = $self->_now('tz',1);
1516             }
1517 2861         6519 $self->_convert('convert_to_gmt',$date,$from,'GMT',$isdst);
1518             }
1519              
1520             sub convert_from_gmt {
1521 12     12 1 23942 my($self,$date,@arg) = @_;
1522 12         47 my($err,$to,$isdst) = _convert_args('convert_from_gmt',@arg);
1523 12 50       48 return (1) if ($err);
1524              
1525 12         22 my $dmb = $$self{'base'};
1526              
1527 12 100       36 if (! $to) {
1528 7         45 $to = $self->_now('tz',1);
1529             }
1530 12         49 $self->_convert('convert_from_gmt',$date,'GMT',$to,$isdst);
1531             }
1532              
1533             sub convert_to_local {
1534 27     27 1 68 my($self,$date,@arg) = @_;
1535 27         107 my($err,$from,$isdst) = _convert_args('convert_to_local',@arg);
1536 27 50       61 return (1) if ($err);
1537              
1538 27         40 my $dmb = $$self{'base'};
1539              
1540 27 50       59 if (! $from) {
1541 0         0 $from = 'GMT';
1542             }
1543 27         66 $self->_convert('convert_to_local',$date,$from,$self->_now('tz',1),$isdst);
1544             }
1545              
1546             sub convert_from_local {
1547 0     0 1 0 my($self,$date,@arg) = @_;
1548 0         0 my($err,$to,$isdst) = _convert_args('convert_from_local',@arg);
1549 0 0       0 return (1) if ($err);
1550              
1551 0         0 my $dmb = $$self{'base'};
1552              
1553 0 0       0 if (! $to) {
1554 0         0 $to = 'GMT';
1555             }
1556 0         0 $self->_convert('convert_from_local',$date,$self->_now('tz',1),$to,$isdst);
1557             }
1558              
1559             sub _convert_args {
1560 2900     2900   5656 my($caller,@args) = @_;
1561              
1562 2900 100       7180 if ($#args == -1) {
    100          
    50          
1563 7         26 return (0,'',0);
1564             } elsif ($#args == 0) {
1565 178 50 33     1388 if ($args[0] eq '0' ||
1566             $args[0] eq '1') {
1567 0         0 return (0,'',$args[0]);
1568             } else {
1569 178         781 return (0,$args[0],0);
1570             }
1571             } elsif ($#args == 1) {
1572 2715         6822 return (0,@args);
1573             } else {
1574 0         0 return (1,'',0);
1575             }
1576             }
1577              
1578             sub _convert {
1579 5737     5737   10920 my($self,$caller,$date,$from,$to,$isdst) = @_;
1580 5737         7028 my $dmb = $$self{'base'};
1581              
1582             # Handle $date as a reference and a string
1583 5737         5917 my (@date);
1584 5737 100       8186 if (ref($date)) {
1585 5736         9244 @date = @$date;
1586             } else {
1587 1         15 my $split = $dmb->split('date',$date);
1588 1 50       3 return (4) if (! defined $split);
1589 1         1 @date = @$split;
1590 1         3 $date = [@date];
1591             }
1592              
1593 5737 50       9259 if ($from ne $to) {
1594 5737         10566 my $tmp = $self->_zone($from);
1595 5737 50       9422 if (! $tmp) {
1596 0         0 return (2);
1597             }
1598 5737         6251 $from = $tmp;
1599              
1600 5737         7419 $tmp = $self->_zone($to);
1601 5737 50       8857 if (! $tmp) {
1602 0         0 return (3);
1603             }
1604 5737         7260 $to = $tmp;
1605             }
1606              
1607 5737 100       9202 if ($from eq $to) {
1608 189         629 my $per = $self->date_period($date,$from,1,$isdst);
1609 189         445 my $offset = $$per[3];
1610 189         380 my $abb = $$per[4];
1611 189         1082 return (0,$date,$offset,$isdst,$abb);
1612             }
1613              
1614             # Convert $date from $from to GMT
1615              
1616 5548 50       8469 if ($from ne "Etc/GMT") {
1617 5548         10189 my $per = $self->date_period($date,$from,1,$isdst);
1618 5548 100       8931 if (! $per) {
1619 2         7 return (4);
1620             }
1621 5546         6547 my $offset = $$per[3];
1622 5546         5506 @date = @{ $dmb->calc_date_time(\@date,$offset,1) };
  5546         13585  
1623             }
1624              
1625             # Convert $date from GMT to $to
1626              
1627 5546         8567 $isdst = 0;
1628 5546         7350 my $offset = [0,0,0];
1629 5546         6523 my $abb = 'GMT';
1630              
1631 5546 50       8861 if ($to ne "Etc/GMT") {
1632 5546         10779 my $per = $self->date_period([@date],$to,0);
1633 5546         9048 $offset = $$per[3];
1634 5546         6286 $isdst = $$per[5];
1635 5546         5816 $abb = $$per[4];
1636 5546         5496 @date = @{ $dmb->calc_date_time(\@date,$offset) };
  5546         10013  
1637             }
1638              
1639 5546         21335 return (0,[@date],$offset,$isdst,$abb);
1640             }
1641              
1642             ########################################################################
1643             # REGULAR EXPRESSIONS FOR TIMEZONE INFORMATION
1644             ########################################################################
1645              
1646             # Returns regular expressions capable of matching timezones.
1647             #
1648             # The timezone regular expressions are:
1649             # namerx : this will match a zone name or alias (America/New_York)
1650             # abbrx : this will match a zone abbreviation (EDT)
1651             # zonerx : this will match a zone name or an abbreviation
1652             # offrx : this will match a pure offset (+0400)
1653             # offabbrx : this will match an offset with an abbreviation (+0400 WET)
1654             # offparrx : this will match an offset and abbreviation if parentheses
1655             # ("+0400 (WET)")
1656             # zrx : this will match all forms
1657             #
1658             # The regular expression will have the following named matches:
1659             # tzstring : the full string matched
1660             # zone : the name/alias
1661             # abb : the zone abbrevation
1662             # off : the offset
1663             #
1664             sub _zrx {
1665 552     552   1628 my($self,$re) = @_;
1666 552 100       2289 return $$self{'data'}{$re} if (defined $$self{'data'}{$re});
1667              
1668             # Zone name
1669              
1670 81         854 my @zone;
1671 81 50       857 if (exists $ENV{'DATE_MANIP_DEBUG_ZONES'}) {
1672 0         0 @zone = split(/\s+/,$ENV{'DATE_MANIP_DEBUG_ZONES'});
1673             } else {
1674 81         19993 @zone = (keys %{ $$self{'data'}{'Alias'} },
1675 81         493 keys %{ $$self{'data'}{'MyAlias'} });
  81         1555  
1676             }
1677 81         1577 @zone = sort _sortByLength(@zone);
1678 81         523 foreach my $zone (@zone) {
1679 68525         86389 $zone =~ s/\057/\\057/g; # /
1680 68525         68098 $zone =~ s/\055/\\055/g; # -
1681 68525         62191 $zone =~ s/\056/\\056/g; # .
1682 68525         60958 $zone =~ s/\050/\\050/g; # (
1683 68525         61252 $zone =~ s/\051/\\051/g; # )
1684 68525         68890 $zone =~ s/\053/\\053/g; # +
1685             }
1686              
1687 81         7733 my $zone = join('|',@zone);
1688 81         202509 $zone = qr/(?$zone)/i;
1689              
1690             # Abbreviation
1691              
1692 81         3351 my @abb;
1693 81 50       656 if (exists $ENV{'DATE_MANIP_DEBUG_ABBREVS'}) {
1694 0         0 @abb = split(/\s+/,$ENV{'DATE_MANIP_DEBUG_ABBREVS'});
1695             } else {
1696 81         5460 @abb = (keys %{ $$self{'data'}{'Abbrev'} },
1697 81         157 keys %{ $$self{'data'}{'MyAbbrev'} });
  81         1979  
1698             }
1699 81         758 @abb = sort _sortByLength(@abb);
1700 81         286 foreach my $abb (@abb) {
1701 13448         13428 $abb =~ s/\055/\\055/g; # -
1702 13448         15094 $abb =~ s/\053/\\053/g; # +
1703             }
1704              
1705 81         1699 my $abb = join('|',@abb);
1706 81         33000 $abb = qr/(?$abb)/i;
1707              
1708             # Offset (+HH, +HHMM, +HH:MM, +HH:MM:SS, +HHMMSS)
1709              
1710 81         969 my($hr) = qr/(?:[0-1][0-9]|2[0-3])/; # 00 - 23
1711 81         1172 my($mn) = qr/(?:[0-5][0-9])/; # 00 - 59
1712 81         1747 my($ss) = qr/(?:[0-5][0-9])/; # 00 - 59
1713              
1714 81         9202 my($off) = qr/ (? [+-] (?: $hr:$mn:$ss |
1715             $hr$mn$ss |
1716             $hr:?$mn |
1717             $hr
1718             )
1719             ) /ix;
1720              
1721             # Assemble everything
1722             #
1723             # A timezone can be any of the following in this order:
1724             # Offset (ABB)
1725             # Offset ABB
1726             # ABB
1727             # Zone
1728             # Offset
1729             # We put ABB before Zone so CET gets parse as the more common abbreviation
1730             # than the less common zone name.
1731              
1732 81         187679 $$self{'data'}{'namerx'} = qr/(?$zone)/;
1733 81         34685 $$self{'data'}{'abbrx'} = qr/(?$abb)/;
1734 81         212154 $$self{'data'}{'zonerx'} = qr/(?(?:$abb|$zone))/;
1735 81         12430 $$self{'data'}{'offrx'} = qr/(?$off)/;
1736 81         29097 $$self{'data'}{'offabbrx'} = qr/(?$off\s+$abb)/;
1737 81         26802 $$self{'data'}{'offparrx'} = qr/(?$off\s*\($abb\))/;
1738 81         266530 $$self{'data'}{'zrx'} = qr/(?(?:$off\s*\($abb\)|$off\s+$abb|$abb|$zone|$off))/;
1739              
1740 81         11395 return $$self{'data'}{$re};
1741             }
1742              
1743             # This sorts from longest to shortest element
1744             #
1745 170     170   1281 no strict 'vars';
  170         251  
  170         10964  
1746             sub _sortByLength {
1747 627454     627454   536695 return (length $b <=> length $a);
1748             }
1749 170     170   710 use strict 'vars';
  170         224  
  170         192655  
1750              
1751             ########################################################################
1752             # CONFIG VARS
1753             ########################################################################
1754              
1755             # This sets a config variable. It also performs all side effects from
1756             # setting that variable.
1757             #
1758             sub _config_var_tz {
1759 388     388   1803 my($self,$var,$val) = @_;
1760              
1761 388 50       3497 if ($var eq 'tz') {
    100          
    100          
    50          
1762 0         0 my $err = $self->_config_var_setdate("now,$val",0);
1763 0 0       0 return if ($err);
1764 0         0 $$self{'data'}{'sections'}{'conf'}{'forcedate'} = 0;
1765 0         0 $val = 1;
1766              
1767             } elsif ($var eq 'setdate') {
1768 171         1324 my $err = $self->_config_var_setdate($val,0);
1769 171 50       1152 return if ($err);
1770 171         1409 $$self{'data'}{'sections'}{'conf'}{'forcedate'} = 0;
1771 171         396 $val = 1;
1772              
1773             } elsif ($var eq 'forcedate') {
1774 190         2998 my $err = $self->_config_var_setdate($val,1);
1775 190 50       756 return if ($err);
1776 190         1600 $$self{'data'}{'sections'}{'conf'}{'setdate'} = 0;
1777 190         474 $val = 1;
1778              
1779             } elsif ($var eq 'configfile') {
1780 27         560 $self->_config_file($val);
1781 27         262 return;
1782             }
1783              
1784 361         773 my $base = $$self{'base'};
1785 361         1584 $$base{'data'}{'sections'}{'conf'}{$var} = $val;
1786 361         2130 return;
1787             }
1788              
1789             sub _config_var_setdate {
1790 361     361   1071 my($self,$val,$force) = @_;
1791 361         1048 my $base = $$self{'base'};
1792              
1793 361         5301 my $dstrx = qr/(?:,\s*(stdonly|dstonly|std|dst))?/i;
1794 361         1608 my $zonrx = qr/,\s*(.+)/;
1795 361         3297 my $da1rx = qr/(\d\d\d\d)(\d\d)(\d\d)(\d\d):(\d\d):(\d\d)/;
1796 361         1864 my $da2rx = qr/(\d\d\d\d)\-(\d\d)\-(\d\d)\-(\d\d):(\d\d):(\d\d)/;
1797 361         976 my $time = time;
1798              
1799 361         888 my($op,$date,$dstflag,$zone,@date,$offset,$abb);
1800              
1801             #
1802             # Parse the argument
1803             #
1804              
1805 361 100 33     50371 if ($val =~ /^now${dstrx}${zonrx}$/oi) {
    50 0        
    50          
    0          
    0          
1806             # now,ZONE
1807             # now,DSTFLAG,ZONE
1808             # Sets now to the system date/time but sets the timezone to be ZONE
1809              
1810 271         1329 $op = 'nowzone';
1811 271         3268 ($dstflag,$zone) = ($1,$2);
1812              
1813             } elsif ($val =~ /^zone${dstrx}${zonrx}$/oi) {
1814             # zone,ZONE
1815             # zone,DSTFLAG,ZONE
1816             # Converts 'now' to the alternate zone
1817              
1818 0         0 $op = 'zone';
1819 0         0 ($dstflag,$zone) = ($1,$2);
1820              
1821             } elsif ($val =~ /^${da1rx}${dstrx}${zonrx}$/o ||
1822             $val =~ /^${da2rx}${dstrx}${zonrx}$/o) {
1823             # DATE,ZONE
1824             # DATE,DSTFLAG,ZONE
1825             # Sets the date and zone
1826              
1827 90         676 $op = 'datezone';
1828 90         468 my($y,$m,$d,$h,$mn,$s);
1829 90         2619 ($y,$m,$d,$h,$mn,$s,$dstflag,$zone) = ($1,$2,$3,$4,$5,$6,$7,$8);
1830 90         668 $date = [$y,$m,$d,$h,$mn,$s];
1831              
1832             } elsif ($val =~ /^${da1rx}$/o ||
1833             $val =~ /^${da2rx}$/o) {
1834             # DATE
1835             # Sets the date in the system timezone
1836              
1837 0         0 $op = 'date';
1838 0         0 my($y,$m,$d,$h,$mn,$s) = ($1,$2,$3,$4,$5,$6);
1839 0         0 $date = [$y,$m,$d,$h,$mn,$s];
1840 0         0 $zone = $self->_now('systz',1);
1841              
1842             } elsif (lc($val) eq 'now') {
1843             # now
1844             # Resets everything
1845              
1846 0         0 my $systz = $$base{'data'}{'now'}{'systz'};
1847 0         0 $base->_init_now();
1848 0         0 $$base{'data'}{'now'}{'systz'} = $systz;
1849 0         0 return 0;
1850              
1851             } else {
1852 0         0 carp "ERROR: [config_var] invalid SetDate/ForceDate value: $val";
1853 0         0 return 1;
1854             }
1855              
1856 361 50       2368 $dstflag = 'std' if (! $dstflag);
1857              
1858             #
1859             # Get the date we're setting 'now' to
1860             #
1861              
1862 361 100       1575 if ($op eq 'nowzone') {
    50          
1863             # Use the system localtime
1864              
1865 271         8415 my($s,$mn,$h,$d,$m,$y) = localtime($time);
1866 271         972 $y += 1900;
1867 271         536 $m++;
1868 271         1136 $date = [$y,$m,$d,$h,$mn,$s];
1869              
1870             } elsif ($op eq 'zone') {
1871             # Use the system GMT time
1872              
1873 0         0 my($s,$mn,$h,$d,$m,$y) = gmtime($time);
1874 0         0 $y += 1900;
1875 0         0 $m++;
1876 0         0 $date = [$y,$m,$d,$h,$mn,$s];
1877             }
1878              
1879             #
1880             # Find out what zone was passed in. It can be an alias or an offset.
1881             #
1882              
1883 361 50       1240 if ($zone) {
1884 361         888 my ($err,@args);
1885 361         942 my $dmb = $$self{'base'};
1886 361 50       1218 $date = [] if (! defined $date);
1887 361         3644 $zone = $self->__zone($date,'',lc($zone),'',lc($dstflag));
1888 361 50       1518 if (! $zone) {
1889 0         0 carp "ERROR: [config_var] invalid zone in SetDate: @args";
1890 0         0 return 1;
1891             }
1892              
1893             } else {
1894 0         0 $zone = $$base{'data'}{'now'}{'systz'};
1895             }
1896              
1897             #
1898             # Handle the zone
1899             #
1900              
1901 361         844 my($isdst,@isdst);
1902 361 50       1119 if ($dstflag eq 'std') {
    0          
    0          
1903 361         964 @isdst = (0,1);
1904             } elsif ($dstflag eq 'stdonly') {
1905 0         0 @isdst = (0);
1906             } elsif ($dstflag eq 'dst') {
1907 0         0 @isdst = (1,0);
1908             } else {
1909 0         0 @isdst = (1);
1910             }
1911              
1912 361 50 66     2534 if ($op eq 'nowzone' ||
    0 33        
1913             $op eq 'datezone' ||
1914             $op eq 'date') {
1915              
1916             # Check to make sure that the date can exist in this zone.
1917 361         747 my $per;
1918 361         1291 foreach my $dst (@isdst) {
1919 722 100       1740 next if ($per);
1920 361         2265 $per = $self->date_period($date,$zone,1,$dst);
1921             }
1922              
1923 361 50       954 if (! $per) {
1924 0         0 carp "ERROR: [config_var] invalid date: SetDate: $date, $zone";
1925 0         0 return 1;
1926             }
1927 361         680 $isdst = $$per[5];
1928 361         692 $abb = $$per[4];
1929 361         723 $offset = $$per[3];
1930              
1931             } elsif ($op eq 'zone') {
1932              
1933             # Convert to that zone
1934 0         0 my($err);
1935 0         0 ($err,$date,$offset,$isdst,$abb) = $self->convert_from_gmt($date,$zone);
1936 0 0       0 if ($err) {
1937 0         0 carp "ERROR: [config_var] invalid SetDate date/offset values: $date, $zone";
1938 0         0 return 1;
1939             }
1940             }
1941              
1942             #
1943             # Set NOW
1944             #
1945              
1946 361         1310 $$base{'data'}{'now'}{'date'} = $date;
1947 361         992 $$base{'data'}{'now'}{'tz'} = $self->_zone($zone);
1948 361         1138 $$base{'data'}{'now'}{'isdst'} = $isdst;
1949 361         1040 $$base{'data'}{'now'}{'abb'} = $abb;
1950 361         1066 $$base{'data'}{'now'}{'offset'} = $offset;
1951              
1952             #
1953             # Treate SetDate/ForceDate
1954             #
1955              
1956 361 100       1337 if ($force) {
1957 190         634 $$base{'data'}{'now'}{'force'} = 1;
1958 190         606 $$base{'data'}{'now'}{'set'} = 0;
1959             } else {
1960 171         499 $$base{'data'}{'now'}{'force'} = 0;
1961 171         402 $$base{'data'}{'now'}{'set'} = 1;
1962 171         701 $$base{'data'}{'now'}{'setsecs'} = $time;
1963 171         1153 my($err,$setdate) = $self->convert_to_gmt($date,$zone);
1964 171         817 $$base{'data'}{'now'}{'setdate'} = $setdate;
1965             }
1966              
1967 361         2505 return 0;
1968             }
1969              
1970             1;
1971             # Local Variables:
1972             # mode: cperl
1973             # indent-tabs-mode: nil
1974             # cperl-indent-level: 3
1975             # cperl-continued-statement-offset: 2
1976             # cperl-continued-brace-offset: 0
1977             # cperl-brace-offset: 0
1978             # cperl-brace-imaginary-offset: 0
1979             # cperl-label-offset: 0
1980             # End: