File Coverage

lib/Date/Manip/TZ.pm
Criterion Covered Total %
statement 700 967 72.3
branch 285 550 51.8
condition 76 132 57.5
subroutine 50 61 81.9
pod 16 16 100.0
total 1127 1726 65.3


line stmt bran cond sub pod time code
1             package Date::Manip::TZ;
2             # Copyright (c) 2008-2023 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 168     168   1004 use Date::Manip::Obj;
  168         266  
  168         4320  
15 168     168   705 use Date::Manip::TZ_Base;
  168         393  
  168         5019  
16             @ISA = qw(Date::Manip::Obj Date::Manip::TZ_Base);
17              
18             require 5.010000;
19 168     168   719 use warnings;
  168         263  
  168         3360  
20 168     168   680 use strict;
  168         257  
  168         2633  
21              
22 168     168   630 use IO::File;
  168         281  
  168         27020  
23             require Date::Manip::Zones;
24 168     168   1042 use Date::Manip::Base;
  168         294  
  168         4131  
25 168     168   86665 use Data::Dumper;
  168         880040  
  168         8678  
26 168     168   1064 use Carp;
  168         273  
  168         65360  
27              
28             our $VERSION;
29             $VERSION='6.92';
30 168     168   817 END { undef $VERSION; }
31              
32             # To get rid of a 'used only once' warnings.
33             END {
34 168     168   442 my $tmp = \%Date::Manip::Zones::Module;
35 168         467 $tmp = \%Date::Manip::Zones::ZoneNames;
36 168         380 $tmp = \%Date::Manip::Zones::Alias;
37 168         354 $tmp = \%Date::Manip::Zones::Abbrev;
38 168         323 $tmp = \%Date::Manip::Zones::Offmod;
39 168         355 $tmp = $Date::Manip::Zones::FirstDate;
40 168         308 $tmp = $Date::Manip::Zones::LastDate;
41 168         293 $tmp = $Date::Manip::Zones::LastYear;
42 168         311 $tmp = $Date::Manip::Zones::TzcodeVersion;
43 168         537 $tmp = $Date::Manip::Zones::TzdataVersion;
44             }
45              
46             ########################################################################
47             # BASE METHODS
48             ########################################################################
49              
50             sub _init {
51 509     509   1082 my($self) = @_;
52              
53 509         11788 $$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 509         1335 my $dmb = $$self{'base'};
98 509         1514 my $os = $dmb->_os();
99              
100 509 50       1487 if ($os eq 'Unix') {
    0          
    0          
101 509         1260 $$self{'data'}{'path'} = '/bin:/usr/bin';
102 509         7313 $$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 512     512   1198 my($self) = @_;
158              
159 512         1812 $self->_set_curr_zone();
160             }
161              
162 168     168   1224 no strict 'refs';
  168         317  
  168         42151  
163             # This loads data from an offset module
164             #
165             sub _offmod {
166 91     91   218 my($self,$offset) = @_;
167 91 100       370 return if (exists $$self{'data'}{'Offsets'}{$offset});
168              
169 34         89 my $mod = $$self{'data'}{'Offmod'}{$offset};
170 34         2337 eval "require Date::Manip::Offset::${mod}";
171 34         134 my %off = %{ "Date::Manip::Offset::${mod}::Offset" };
  34         241  
172              
173 34         197 $$self{'data'}{'Offsets'}{$offset} = { %off };
174             }
175              
176             # This loads data from a zone module (takes a lowercase zone)
177             #
178             sub _module {
179 1635     1635   3440 my($self,$zone) = @_;
180 1635 50       4082 return if (exists $$self{'data'}{'Zones'}{$zone}{'Loaded'});
181              
182 1635         4478 my $mod = $$self{'data'}{'Module'}{$zone};
183 1635         102350 eval "require Date::Manip::TZ::${mod}";
184 1635         7169 my %dates = %{ "Date::Manip::TZ::${mod}::Dates" };
  1635         42930  
185 1635         6316 my %last = %{ "Date::Manip::TZ::${mod}::LastRule" };
  1635         6433  
186 1635         44738 $$self{'data'}{'Zones'}{$zone} =
187             {
188             'Dates' => { %dates },
189             'LastRule' => { %last },
190             'Loaded' => 1
191             };
192             }
193 168     168   1085 use strict 'refs';
  168         315  
  168         223184  
194              
195             ########################################################################
196             # CHECKING/MODIFYING ZONEINFO DATA
197             ########################################################################
198              
199             sub _zone {
200 48041     48041   66971 my($self,$zone) = @_;
201 48041         67951 $zone = lc($zone);
202              
203 48041 100       114185 if (exists $$self{'data'}{'MyAlias'}{$zone}) {
    100          
204 1         4 return $$self{'data'}{'MyAlias'}{$zone};
205             } elsif (exists $$self{'data'}{'Alias'}{$zone}) {
206 47990         90725 return $$self{'data'}{'Alias'}{$zone};
207             } else {
208 50         387 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 869 my($self,$alias,$zone) = @_;
224 2         4 $alias = lc($alias);
225              
226 2 100       6 if ($alias eq 'reset') {
227 1         3 $$self{'data'}{'MyAlias'} = {};
228 1         2 $$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         3 $zone = $self->_zone($zone);
238              
239 1 50       3 return 1 if (! $zone);
240 1         3 $$self{'data'}{'MyAlias'}{$alias} = $zone;
241 1         3 $$self{'data'}{'zonerx'} = undef;
242 1         2 return 0;
243             }
244              
245             sub define_abbrev {
246 6     6 1 2808 my($self,$abbrev,@zone) = @_;
247 6         11 $abbrev = lc($abbrev);
248              
249 6 100       16 if ($abbrev eq 'reset') {
250 2         9 $$self{'data'}{'MyAbbrev'} = {};
251 2         5 $$self{'data'}{'abbrx'} = undef;
252 2         6 return 0;
253             }
254 4 100 100     23 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         3 return (0);
258             }
259              
260 3 50       14 if (! exists $$self{'data'}{'Abbrev'}{$abbrev}) {
261 0         0 return (1);
262             }
263              
264 3         7 my (@z,%z);
265 3         3 my %zone = map { $_,1 } @{ $$self{'data'}{'Abbrev'}{$abbrev} };
  50         79  
  3         11  
266 3         7 foreach my $z (@zone) {
267 5         12 my $zone = $self->_zone($z);
268 5 50       11 return (2,$z) if (! $zone);
269 5 50       11 return (3,$z) if (! exists $zone{$zone});
270 5 50       13 next if (exists $z{$zone});
271 5         7 $z{$zone} = 1;
272 5         10 push(@z,$zone);
273             }
274              
275 3         9 $$self{'data'}{'MyAbbrev'}{$abbrev} = [ @z ];
276 3         5 $$self{'data'}{'abbrx'} = undef;
277 3         10 return ();
278             }
279              
280             sub define_offset {
281 22     22 1 12554 my($self,$offset,@args) = @_;
282 22         32 my $dmb = $$self{'base'};
283              
284 22 100       44 if (lc($offset) eq 'reset') {
285 10         23 $$self{'data'}{'MyOffsets'} = {};
286 10         20 return (0);
287             }
288 12 50 33     30 if ($#args == 0 && lc($args[0]) eq 'reset') {
289 0         0 delete $$self{'data'}{'MyOffsets'}{$offset};
290 0         0 return (0);
291             }
292              
293             # Check that $offset is valid. If it is, load the
294             # appropriate module.
295              
296 12 50       23 if (ref($offset)) {
297 0         0 $offset = $dmb->join('offset',$offset);
298             } else {
299 12         36 $offset = $dmb->_delta_convert('offset',$offset);
300             }
301 12 100       29 return (9) if (! $offset);
302 11 100       129 return (1) if (! exists $$self{'data'}{'Offmod'}{$offset});
303              
304 10         29 $self->_offmod($offset);
305              
306             # Find out whether we're handling STD, DST, or both.
307              
308 10         20 my(@isdst) = (0,1);
309 10 50       45 if ($args[0] =~ /^std|dst|stdonly|dstonly$/i) {
310 10         23 my $tmp = lc(shift(@args));
311 10 100       27 if ($tmp eq 'stdonly') {
    100          
312 3         5 @isdst = (0);
313             } elsif ($tmp eq 'dstonly') {
314 2         5 @isdst = (1);
315             }
316             }
317 10         21 my @zone = @args;
318              
319 10 100 100     33 if ($#isdst == 0 &&
320             ! exists($$self{'data'}{'Offsets'}{$offset}{$isdst[0]})) {
321 2         9 return (2);
322             }
323              
324             # Check to see that each zone is valid, and contains this offset.
325              
326 8         12 my %tmp;
327 8         15 foreach my $isdst (0,1) {
328 16 50       33 next if (! exists $$self{'data'}{'Offsets'}{$offset}{$isdst});
329 16         18 my @z = @{ $$self{'data'}{'Offsets'}{$offset}{$isdst} };
  16         39  
330 16         21 $tmp{$isdst} = { map { $_,1 } @z };
  182         283  
331             }
332              
333 8         11 foreach my $z (@zone) {
334 15         26 my $lcz = lc($z);
335 15 100 100     71 if (! exists $$self{'data'}{'ZoneNames'}{$lcz}) {
    100 100        
    100          
336 2         9 return (3,$z);
337             } elsif (! exists $tmp{0}{$lcz} &&
338             ! exists $tmp{1}{$lcz}) {
339 1         6 return (4,$z);
340             } elsif ($#isdst == 0 &&
341             ! exists $tmp{$isdst[0]}{$lcz}) {
342 2         10 return (5,$z);
343             }
344 10         18 $z = $lcz;
345             }
346              
347             # Set the zones accordingly.
348              
349 3         7 foreach my $isdst (@isdst) {
350 6         7 my @z;
351 6         9 foreach my $z (@zone) {
352 10 100       24 push(@z,$z) if (exists $tmp{$isdst}{$z});
353             }
354 6         17 $$self{'data'}{'MyOffsets'}{$offset}{$isdst} = [ @z ];
355             }
356              
357 3         17 return (0);
358             }
359              
360             ########################################################################
361             # SYSTEM ZONE
362             ########################################################################
363              
364             sub curr_zone {
365 0     0 1 0 my($self,$reset) = @_;
366 0         0 my $dmb = $$self{'base'};
367              
368 0 0       0 if ($reset) {
369 0         0 $self->_set_curr_zone();
370             }
371              
372 0         0 my($ret) = $self->_now('systz',1);
373 0         0 return $$self{'data'}{'ZoneNames'}{$ret}
374             }
375              
376             sub curr_zone_methods {
377 0     0 1 0 my($self,@methods) = @_;
378              
379 0 0       0 if (${^TAINT}) {
380 0         0 carp "ERROR: [curr_zone_methods] not allowed when taint checking on";
381 0         0 return;
382             }
383              
384 0         0 $$self{'data'}{'methods'} = [ @methods ];
385             }
386              
387             sub _set_curr_zone {
388 512     512   1003 my($self) = @_;
389 512         971 my $dmb = $$self{'base'};
390 512         1677 my $currzone = $self->_get_curr_zone();
391              
392 512         1202 $$dmb{'data'}{'now'}{'systz'} = $self->_zone($currzone);
393             }
394              
395             # This determines the system timezone using all of the methods
396             # applicable to the operating system. The first match is used.
397             #
398             sub _get_curr_zone {
399 512     512   957 my($self) = @_;
400 512         959 my $dmb = $$self{'base'};
401              
402 512         974 my $t = time;
403 512         19839 my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($t);
404 512         1682 my $currzone = '';
405 512 50       1333 my $dstflag = ($isdst ? 'dstonly' : 'stdonly');
406              
407 512         815 my (@methods) = @{ $$self{'data'}{'methods'} };
  512         3320  
408 512 50       1798 my $debug = ($ENV{DATE_MANIP_DEBUG} ? 1 : 0);
409              
410             defined $$self{'data'}{'path'}
411 512 50       4378 and local $ENV{PATH} = $$self{'data'}{'path'};
412              
413             METHOD:
414 512         1674 while (@methods) {
415 2000         2951 my $method = shift(@methods);
416 2000         2677 my @zone = ();
417              
418 2000 50       3201 print "*** DEBUG *** METHOD: $method [" if ($debug);
419              
420 2000 100       5793 if ($method eq 'main') {
    100          
    50          
    0          
    0          
    0          
    0          
    0          
421              
422 512 50       1523 if (! @methods) {
423 0 0       0 print "]\n" if ($debug);
424 0         0 carp "ERROR: [_set_curr_zone] main requires argument";
425 0         0 return;
426             }
427 512         993 my $var = shift(@methods);
428 512 50       1221 print "$var] " if ($debug);
429 168     168   1224 no strict "refs";
  168         345  
  168         7201  
430 512         1038 my $val = ${ "::$var" };
  512         2126  
431 168     168   931 use strict "refs";
  168         303  
  168         324087  
432 512 50       1257 if (defined $val) {
433 0         0 push(@zone,$val);
434 0 0       0 print "$val\n" if ($debug);
435             } else {
436 512 50       1443 print "undef\n" if ($debug);
437             }
438              
439             } elsif ($method eq 'env') {
440 512 50       1489 if (@methods < 2) {
441 0 0       0 print "]\n" if ($debug);
442 0         0 carp "ERROR: [_set_curr_zone] env requires 2 argument";
443 0         0 return;
444             }
445 512         1151 my $type = lc( shift(@methods) );
446 512 50       1179 print "$type," if ($debug);
447              
448 512 50 33     1793 if ($type ne 'zone' &&
449             $type ne 'offset') {
450 0 0       0 print "?]\n" if ($debug);
451 0         0 carp "ERROR: [_set_curr_zone] env requires 'offset' or 'zone' " .
452             "as the first argument";
453 0         0 return;
454             }
455 512         967 my $var = shift(@methods);
456 512 50       1159 print "$var] " if ($debug);
457 512 100       1318 if (exists $ENV{$var}) {
458 24 50       51 if ($type eq 'zone') {
459 24         64 push(@zone,$ENV{$var});
460 24 50       50 print "$ENV{$var}\n" if ($debug);
461             } else {
462 0         0 my $off = $ENV{$var};
463 0 0       0 print "$ENV{$var} = " if ($debug);
464 0         0 $off = $dmb->_delta_convert('time',"0:0:$off");
465 0         0 $off = $dmb->_delta_convert('offset',$off);
466 0 0       0 print "$off\n" if ($debug);
467 0         0 push(@zone,$off);
468             }
469             } else {
470 488 50       1096 print "undef\n" if ($debug);
471             }
472              
473             } elsif ($method eq 'file') {
474 976 50       1869 if (! @methods) {
475 0 0       0 print "]\n" if ($debug);
476 0         0 carp "ERROR: [_set_curr_zone] file requires argument";
477 0         0 return;
478             }
479 976         1511 my $file = shift(@methods);
480 976 50       3379 print "$file] " if ($debug);
481 976 100       10661 if (! -f $file) {
482 488 50       1309 print "not found\n" if ($debug);
483 488         1350 next;
484             }
485              
486 488         4400 my $in = new IO::File;
487 488 50       21723 $in->open($file) || next;
488 488         23150 my $firstline = 1;
489              
490 488         891 my @z;
491 488         2833 while (! $in->eof) {
492 488         14412 my $line = <$in>;
493 488         1206 chomp($line);
494 488 50 33     4217 next if ($line =~ /^\s*\043/ ||
495             $line =~ /^\s*$/);
496 488 50       1372 if ($firstline) {
497 488         834 $firstline = 0;
498 488         1856 $line =~ s/^\s*//;
499 488         2080 $line =~ s/\s*$//;
500 488         1110 $line =~ s/["']//g; # "
501 488         985 $line =~ s/\s+/_/g;
502 488         1260 @z = ($line);
503             }
504              
505             # We're looking for lines of the form:
506             # TZ = string
507             # TIMEZONE = string
508             # ZONE = string
509             # Alternately, we may use a 1-line file (ignoring comments and
510             # whitespace) which contains only the zone name (it may be
511             # quoted or contain embedded whitespace).
512             #
513             # 'string' can be:
514             # the name of a timezone enclosed in single/double quotes
515             # with everything after the closing quote ignored (the
516             # name of the timezone may have spaces instead of underscores)
517             #
518             # a space delimited list of tokens, the first of which
519             # is the time zone
520             #
521             # the name of a timezone with underscores replaced by
522             # spaces and nothing after the timezone
523             #
524             # For some reason, RHEL6 desktop version stores timezones as
525             # America/New York
526             # instead of
527             # America/New_York
528             # which is why we have to handle the space/underscore
529             # substitution.
530              
531 488 50       2300 if ($line =~ /^\s*(?:TZ|TIMEZONE|ZONE)\s*=\s*(.*)\s*$/) {
532 0         0 my $val = $1;
533 0         0 @z = ();
534 0 0       0 last if (! $val);
535              
536 0 0       0 if ($val =~ /^(["'])(.*?)\1/) {
    0          
537 0         0 my $z = $2;
538 0 0       0 last if (! $z);
539 0         0 $z =~ s/\s+/_/g;
540 0         0 push(@zone,$z);
541              
542             } elsif ($val =~ /\s/) {
543 0         0 $val =~ /^(\S+)/;
544 0         0 push(@zone,$1);
545 0         0 $val =~ s/\s+/_/g;
546 0         0 push(@zone,$val);
547              
548             } else {
549 0         0 push(@zone,$val);
550             }
551              
552 0         0 last;
553             }
554             }
555 488         6788 close(IN);
556              
557 488 50       1714 push(@zone,@z) if (@z);
558              
559 488 50       6958 if ($debug) {
560 0 0       0 if (@zone) {
561 0         0 print "@zone\n";
562             } else {
563 0         0 print "no result\n";
564             }
565             }
566              
567             } elsif ($method eq 'tzdata') {
568 0 0       0 if (@methods < 2) {
569 0 0       0 print "]\n" if ($debug);
570 0         0 carp "ERROR: [_set_curr_zone] tzdata requires two arguments";
571 0         0 return;
572             }
573 0         0 my $file = shift(@methods);
574 0         0 my $dir = shift(@methods);
575 0 0       0 print "$file $dir" if ($debug);
576              
577 0         0 my $z;
578 0 0 0     0 if (-f $file && -d $dir) {
579 0         0 $z = _get_zoneinfo_zone($file,$dir);
580             }
581 0 0       0 if (defined($z)) {
    0          
582 0         0 push @zone, $z;
583 0 0       0 print "] $z\n" if ($debug);
584             } elsif ($debug) {
585 0         0 print "] no result\n";
586             }
587              
588             } elsif ($method eq 'command') {
589 0 0       0 if (! @methods) {
590 0 0       0 print "]\n" if ($debug);
591 0         0 carp "ERROR: [_set_curr_zone] command requires argument";
592 0         0 return;
593             }
594 0         0 my $command = shift(@methods);
595 0 0       0 print "$command] " if ($debug);
596 0         0 my ($out) = _cmd($command);
597 0 0       0 push(@zone,$out) if ($out);
598              
599 0 0       0 if ($debug) {
600 0 0       0 if ($out) {
601 0         0 print "$out\n";
602             } else {
603 0         0 print "no output\n";
604             }
605             }
606              
607             } elsif ($method eq 'cmdfield') {
608 0 0       0 if ($#methods < 1) {
609 0 0       0 print "]\n" if ($debug);
610 0         0 carp "ERROR: [_set_curr_zone] cmdfield requires 2 arguments";
611 0         0 return;
612             }
613 0         0 my $command = shift(@methods);
614 0         0 my $n = shift(@methods);
615 0 0       0 print "$command,$n]\n" if ($debug);
616 0         0 my ($out) = _cmd($command);
617 0         0 my $val;
618              
619 0 0       0 if ($out) {
620 0         0 $out =~ s/^\s*//;
621 0         0 $out =~ s/\s*$//;
622 0         0 my @out = split(/\s+/,$out);
623 0 0       0 $val = $out[$n] if (defined $out[$n]);
624 0         0 push(@zone,$val);
625             }
626              
627 0 0       0 if ($debug) {
628 0 0       0 if ($val) {
629 0         0 print "$val\n";
630             } else {
631 0         0 print "no result\n";
632             }
633             }
634              
635             } elsif ($method eq 'gmtoff') {
636 0 0       0 print "] " if ($debug);
637 0         0 my($secUT,$minUT,$hourUT,$mdayUT,$monUT,$yearUT,$wdayUT,$ydayUT,
638             $isdstUT) = gmtime($t);
639 0 0       0 if ($mdayUT>($mday+1)) {
    0          
640             # UT = 28-31 LT = 1
641 0         0 $mdayUT=0;
642             } elsif ($mdayUT<($mday-1)) {
643             # UT = 1 LT = 28-31
644 0         0 $mday=0;
645             }
646 0         0 $sec = (($mday*24 + $hour)*60 + $min)*60 + $sec;
647 0         0 $secUT = (($mdayUT*24 + $hourUT)*60 + $minUT)*60 + $secUT;
648 0         0 my $off = $sec-$secUT;
649              
650 0         0 $off = $dmb->_delta_convert('time',"0:0:$off");
651 0         0 $off = $dmb->_delta_convert('offset',$off);
652 0         0 push(@zone,$off);
653 0 0       0 print "$off\n" if ($debug);
654              
655             } elsif ($method eq 'registry') {
656 0 0       0 print "] " if ($debug);
657 0         0 my $z = $self->_windows_registry_val();
658 0 0       0 if ($z) {
659 0         0 push(@zone,$z);
660 0 0       0 print "$z\n" if ($debug);
661             } else {
662 0 0       0 print "no result\n" if ($debug);
663             }
664              
665             } else {
666 0 0       0 print "]\n" if ($debug);
667 0         0 carp "ERROR: [_set_curr_zone] invalid method: $method";
668 0         0 return;
669             }
670              
671 1512         4159 while (@zone) {
672 512         1436 my $zone = lc(shift(@zone));
673              
674             # OpenUNIX puts a colon at the start
675 512         1330 $zone =~ s/^://;
676              
677             # If we got a zone name/alias
678 512         1878 $currzone = $self->_zone($zone);
679 512 50       1779 last METHOD if ($currzone);
680              
681             # If we got an abbreviation (EST)
682 0 0       0 if (exists $$self{'data'}{'Abbrev'}{$zone}) {
683 0         0 $currzone = $$self{'data'}{'Abbrev'}{$zone}[0];
684 0         0 last METHOD;
685             }
686              
687             # If we got an offset
688              
689 0         0 $currzone = $self->__zone([],'',$zone,'',$dstflag);
690 0 0       0 last METHOD if ($currzone);
691             }
692             }
693              
694 512 50       1519 if (! $currzone) {
695 0         0 carp "ERROR: Date::Manip unable to determine Time Zone. GMT will be used.";
696 0         0 $currzone = 'Etc/GMT';
697             }
698              
699 512         3324 return $currzone;
700             }
701              
702             #######################
703             # The following section comes from the DateTime-TimeZone module
704              
705             {
706             my $want_content;
707             my $want_size;
708             my $zoneinfo;
709              
710             sub _get_zoneinfo_zone {
711 0     0   0 my($localtime,$z) = @_;
712 0         0 $zoneinfo = $z;
713              
714             # /etc/localtime should be either a link to a tzdata file in
715             # /usr/share/zoneinfo or a copy of one of the files there.
716              
717 0 0 0     0 return '' if (! -d $zoneinfo || ! -f $localtime);
718              
719 0         0 require Cwd;
720 0 0       0 if (-l $localtime) {
721 0         0 return _zoneinfo_file_name_to_zone(
722             Cwd::abs_path($localtime),
723             Cwd::abs_path($zoneinfo),
724             );
725             }
726              
727 0         0 $want_content = _zoneinfo_file_slurp($localtime);
728 0         0 $want_size = -s $localtime;
729              
730             # File::Find can't bail in the middle of a find, and we only want the
731             # first match, so we'll call it in an eval.
732              
733 0         0 local $@ = undef;
734 0 0       0 eval {
735 0         0 require File::Find;
736 0         0 File::Find::find
737             ({
738             wanted => \&_zoneinfo_find_file,
739             no_chdir => 1,
740             },
741             $zoneinfo,
742             );
743 0         0 1;
744             } and return;
745             ref $@
746 0 0       0 and return $@->{zone};
747 0         0 croak $@;
748             }
749              
750             sub _zoneinfo_find_file {
751 0     0   0 my $zone;
752 0 0 0     0 defined($zone = _zoneinfo_file_name_to_zone($File::Find::name,
      0        
      0        
753             $zoneinfo))
754             and -f $_
755             and $want_size == -s _
756             and ($want_content eq _zoneinfo_file_slurp($File::Find::name))
757             and croak { zone => $zone };
758             }
759             }
760              
761             sub _zoneinfo_file_name_to_zone {
762 0     0   0 my($file,$zoneinfo) = @_;
763 0         0 require File::Spec;
764 0         0 my $zone = File::Spec->abs2rel($file,$zoneinfo);
765             return $zone if (exists $Date::Manip::Zones::ZoneNames{lc($zone)} ||
766 0 0 0     0 exists $Date::Manip::Zones::Alias{lc($zone)});
767 0         0 return;
768             }
769              
770             sub _zoneinfo_file_slurp {
771 0     0   0 my($file) = @_;
772 0 0       0 open my $fh, '<', $file
773             or return;
774 0         0 binmode $fh;
775 0         0 local $/ = undef;
776 0         0 return <$fh>;
777             }
778              
779             sub _windows_registry_val {
780 0     0   0 my($self) = @_;
781              
782 0         0 require Win32::TieRegistry;
783              
784 0 0       0 my $lmachine = new Win32::TieRegistry 'LMachine',
785             { Access => Win32::TieRegistry::KEY_READ(),
786             Delimiter => '/' }
787             or return '';
788              
789 0         0 my $tzinfo = $lmachine->Open('SYSTEM/CurrentControlSet/Control/TimeZoneInformation/');
790              
791             #
792             # Windows Vista, Windows 2008 Server
793             #
794              
795 0         0 my $tzkn = $tzinfo->GetValue('TimeZoneKeyName');
796 0 0 0     0 if (defined($tzkn) && $tzkn) {
797             # For some reason, Vista is tacking on a bunch of stuff at the
798             # end of the timezone, starting with a chr(0). Strip it off.
799              
800 0         0 my $c = chr(0);
801 0         0 my $i = index($tzkn,$c);
802 0 0       0 if ($i != -1) {
803 0         0 $tzkn = substr($tzkn,0,$i);
804             }
805 0         0 my $z = $self->_zone($tzkn);
806 0 0       0 return $z if ($z);
807             }
808              
809             #
810             # Windows NT, Windows 2000, Windows XP, Windows 2003 Server
811             #
812              
813 0         0 my $stdnam = $tzinfo->GetValue('StandardName');
814 0         0 my $z = $self->_zone($stdnam);
815 0 0       0 return $z if ($z);
816              
817             #
818             # For non-English versions, we have to determine which timezone it
819             # actually is.
820             #
821              
822 0         0 my $atz = $lmachine->Open('SOFTWARE/Microsoft/Windows NT/CurrentVersion/Time Zones/');
823 0 0 0     0 if (! defined($atz) || ! $atz) {
824 0         0 $atz = $lmachine->Open('SOFTWARE/Microsoft/Windows/CurrentVersion/Time Zones/');
825             }
826              
827 0 0 0     0 return "" if (! defined($atz) || ! $atz);
828              
829 0         0 foreach my $z ($atz->SubKeyNames()) {
830 0         0 my $tmp = $atz->Open("$z/");
831 0         0 my $znam = $tmp->GetValue('Std');
832 0 0       0 return $z if ($znam eq $stdnam);
833             }
834             }
835              
836             # End of DateTime-TimeZone section
837             #######################
838              
839             # We will be testing commands that don't exist on all architectures,
840             # so disable warnings.
841             #
842 168     168   1287 no warnings;
  168         346  
  168         23588  
843             sub _cmd {
844 0     0   0 my($cmd) = @_;
845 0         0 local(*IN);
846 0 0       0 open(IN,"$cmd |") || return ();
847 0         0 my @out = <IN>;
848 0         0 close(IN);
849 0         0 chomp(@out);
850 0         0 return @out;
851             }
852 168     168   1104 use warnings;
  168         386  
  168         703191  
853              
854             ########################################################################
855             # DETERMINING A TIMEZONE
856             ########################################################################
857              
858             sub zone {
859 62     62 1 28023 my($self,@args) = @_;
860 62         120 my $dmb = $$self{'base'};
861 62 100       120 if (! @args) {
862 1         15 my($tz) = $self->_now('tz',1);
863 1         4 return $$self{'data'}{'ZoneNames'}{$tz}
864             }
865              
866             # Parse the arguments
867              
868 61         135 my($zone,$abbrev,$offset,$dstflag) = ('','','','');
869 61         92 my $date = [];
870 61         78 my $tmp;
871 61         107 foreach my $arg (@args) {
872              
873 106 100       259 if (ref($arg) eq 'ARRAY') {
    50          
874 34 50       71 if ($#$arg == 5) {
    0          
875             # [Y,M,D,H,Mn,S]
876 34 50       65 return undef if (@$date);
877 34         52 $date = $arg;
878              
879             } elsif ($#$arg == 2) {
880             # [H,Mn,S]
881 0 0       0 return undef if ($offset);
882 0         0 $offset = $dmb->join('offset',$arg);
883 0 0       0 return undef if (! $offset);
884              
885             } else {
886 0         0 return undef;
887             }
888              
889             } elsif (ref($arg)) {
890 0         0 return undef;
891              
892             } else {
893 72         130 $arg = lc($arg);
894              
895 72 100 66     315 if ($arg =~ /^(std|dst|stdonly|dstonly)$/) {
    100          
    100          
    50          
    100          
    50          
896 11 50       54 return undef if ($dstflag);
897 11         20 $dstflag = $arg;
898              
899             } elsif ($tmp = $self->_zone($arg)) {
900 15 50       36 return undef if ($zone);
901 15         31 $zone = $tmp;
902              
903             } elsif (exists $$self{'data'}{'MyAbbrev'}{$arg} ||
904             exists $$self{'data'}{'Abbrev'}{$arg}) {
905 13 50       38 return undef if ($abbrev);
906 13         33 $abbrev = $arg;
907             } elsif (exists $$self{'data'}{'Abbrev'}{$arg}) {
908 0 0       0 return undef if ($abbrev);
909 0         0 $abbrev = $arg;
910              
911             } elsif ($tmp = $dmb->split('offset',$arg)) {
912 30 50       62 return undef if ($offset);
913 30         130 $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         14 return undef;
921             }
922             }
923             }
924              
925 58         174 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 561     561   2118 my($self,$date,$offset,$zone,$abbrev,$dstflag) = @_;
936 561         1217 my $dmb = $$self{'base'};
937              
938             #
939             # Determine the zones that match all data.
940             #
941              
942 561         931 my @zone;
943              
944 561         902 while (1) {
945              
946             # No information
947              
948 561 100 100     1880 if (! $zone &&
      100        
949             ! $abbrev &&
950             ! $offset) {
951 3         10 my($z) = $self->_now('tz',1);
952 3         8 @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 561 100 100     1934 $dstflag = "dst" if ($offset && @$date && ! $dstflag);
      100        
962              
963 561         899 my(@isdst);
964 561 100       3732 if ($dstflag eq 'stdonly') {
    100          
    100          
965 4         8 @isdst = (0);
966             } elsif ($dstflag eq 'dstonly') {
967 4         10 @isdst = (1);
968             } elsif ($dstflag eq 'dst') {
969 71         127 @isdst = (1,0);
970             } else {
971 482         1215 @isdst = (0,1);
972             }
973              
974             # We may pass in $zone and not $abbrev when it really should be
975             # $abbrev.
976              
977 561 100 66     2894 if ($zone && ! $abbrev) {
978 372 50 0     1594 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 561 100       1390 if ($zone) {
990             my $z = (exists $$self{'data'}{'Alias'}{$zone} ?
991 372 50       1504 $$self{'data'}{'Alias'}{$zone} : $zone);
992 372         966 @zone = ($z);
993             }
994              
995             # $abbrev
996              
997 561 100       1607 if ($abbrev) {
998 118         187 my @abbrev_zones;
999 118 100       664 if (exists $$self{'data'}{'MyAbbrev'}{$abbrev}) {
    50          
1000 8         11 @abbrev_zones = @{ $$self{'data'}{'MyAbbrev'}{$abbrev} };
  8         21  
1001             } elsif (exists $$self{'data'}{'Abbrev'}{$abbrev}) {
1002 110         376 @abbrev_zones = @{ $$self{'data'}{'Abbrev'}{$abbrev} };
  110         587  
1003             }
1004              
1005 118         318 my @z;
1006 118         261 foreach my $isdst (@isdst) {
1007 236         835 my @tmp = $self->_check_abbrev_isdst($abbrev,$isdst,@abbrev_zones);
1008 236 100       660 if (@tmp) {
1009 155 100       419 if (@z) {
1010 37         176 @z = _list_add(\@z,\@tmp);
1011             } else {
1012 118         464 @z = @tmp;
1013             }
1014             }
1015             }
1016              
1017 118 50       421 if (@zone) {
1018 0         0 @zone = _list_union(\@z,\@zone);
1019             } else {
1020 118         477 @zone = @z;
1021             }
1022 118 50       630 last if (! @zone);
1023             }
1024              
1025             # $offset
1026              
1027 561 100       1618 if ($offset) {
1028 82 100       312 return undef if (! exists $$self{'data'}{'Offmod'}{$offset});
1029 81         300 $self->_offmod($offset);
1030              
1031 81         155 my @z;
1032 81         199 foreach my $isdst (@isdst) {
1033             my $tmp = $$self{'data'}{'MyOffsets'}{$offset}{$isdst} ||
1034 155   100     763 $$self{'data'}{'Offsets'}{$offset}{$isdst};
1035              
1036 155         206 my @tmp;
1037 155 100       290 if ($abbrev) {
1038 28         117 @tmp = $self->_check_offset_abbrev_isdst($offset,$abbrev,$isdst,$tmp);
1039             } else {
1040 127 100       608 @tmp = @$tmp if ($tmp);
1041             }
1042              
1043 155 100       375 if (@tmp) {
1044 134 100       250 if (@z) {
1045 55         184 @z = _list_add(\@z,\@tmp);
1046             } else {
1047 79         263 @z = @tmp;
1048             }
1049             }
1050             }
1051              
1052 81 100       190 if (@zone) {
1053 14         66 @zone = _list_union(\@zone,\@z);
1054             } else {
1055 67         228 @zone = @z;
1056             }
1057 81 100       264 last if (! @zone);
1058             }
1059              
1060             # $date
1061              
1062 558 100       1617 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 533         845 my @tmp;
1071 533         959 my $isdst = '';
1072 533 50       1354 $isdst = 0 if ($dstflag eq 'stdonly');
1073 533 50       1320 $isdst = 1 if ($dstflag eq 'dstonly');
1074              
1075             ZONE:
1076 533         1217 foreach my $z (@zone) {
1077 4121 100       13649 $self->_module($z) if (! exists $$self{'data'}{'Zones'}{$z}{'Loaded'});
1078 4121         7819 my $y = $$date[0];
1079 4121         8421 my @periods = $self->_all_periods($z,$y);
1080              
1081 4121         6319 foreach my $period (@periods) {
1082 7133 100 100     38783 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 2234         3945 push(@tmp,$z);
1089 2234         4485 next ZONE;
1090             }
1091             }
1092 533         1833 @zone = @tmp;
1093 533 100       1398 last if (! @zone);
1094             }
1095              
1096 549         1160 last;
1097             }
1098              
1099             # Return the value/list
1100              
1101 560 100       1380 if (wantarray) {
1102 27         34 my @ret;
1103 27         44 foreach my $z (@zone) {
1104 69         146 push(@ret,$$self{'data'}{'ZoneNames'}{$z});
1105             }
1106 27         174 return @ret;
1107             }
1108              
1109 533 100       1292 return '' if (! @zone);
1110 524         2834 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 236     236   995 my($self,$abbrev,$isdst,@zones) = @_;
1118              
1119 236         310 my @ret;
1120             ZONE:
1121 236         401 foreach my $zone (@zones) {
1122 3616 100       12017 $self->_module($zone) if (! exists $$self{'data'}{'Zones'}{$zone}{'Loaded'});
1123              
1124 3616         5920 foreach my $y (sort keys %{ $$self{'data'}{'Zones'}{$zone}{'Dates'} }) {
  3616         92385  
1125 168156         161339 my @periods = @{ $$self{'data'}{'Zones'}{$zone}{'Dates'}{$y} };
  168156         298616  
1126 168156         183809 foreach my $period (@periods) {
1127 323827         582854 my($dateUT,$dateLT,$off,$offref,$abb,$dst,$endUT,$endLT) = @$period;
1128 323827 100 100     661703 next if (lc($abbrev) ne lc($abb) ||
1129             $isdst != $dst);
1130 1845         2851 push(@ret,$zone);
1131 1845         8207 next ZONE;
1132             }
1133             }
1134             }
1135              
1136 236         1305 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   105 my($self,$offset,$abbrev,$isdst,$zones) = @_;
1144              
1145 28         48 my @ret;
1146 28         79 ZONE: foreach my $zone (@$zones) {
1147 798 100       3066 $self->_module($zone) if (! exists $$self{'data'}{'Zones'}{$zone}{'Loaded'});
1148              
1149 798         1755 foreach my $y (sort keys %{ $$self{'data'}{'Zones'}{$zone}{'Dates'} }) {
  798         20468  
1150 53846         54846 my @periods = @{ $$self{'data'}{'Zones'}{$zone}{'Dates'}{$y} };
  53846         94863  
1151 53846         56660 foreach my $period (@periods) {
1152 105302         186781 my($dateUT,$dateLT,$off,$offref,$abb,$dst,$endUT,$endLT) = @$period;
1153 105302 100 100     198586 next if (lc($abbrev) ne lc($abb) ||
      100        
1154             $offset ne $off ||
1155             $isdst != $dst);
1156 185         267 push(@ret,$zone);
1157 185         804 next ZONE;
1158             }
1159             }
1160             }
1161              
1162 28         144 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   42 my($list1,$list2) = @_;
1170 14         48 my(%list2) = map { $_,1 } @$list2;
  182         391  
1171 14         39 my(@ret);
1172 14         40 foreach my $ele (@$list1) {
1173 221 100       449 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   225 my($list1,$list2) = @_;
1183 92         235 my(%list1) = map { $_,1 } @$list1;
  1114         1987  
1184 92         314 my(@ret) = @$list1;
1185 92         200 foreach my $ele (@$list2) {
1186 1780 100       2524 next if (exists $list1{$ele});
1187 1303         1407 push(@ret,$ele);
1188 1303         1908 $list1{$ele} = 1;
1189             }
1190 92         738 return @ret;
1191             }
1192              
1193             ########################################################################
1194             # PERIODS METHODS
1195             ########################################################################
1196              
1197             sub all_periods {
1198 7     7 1 91037 my($self,$zone,$year) = @_;
1199              
1200 7         17 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         7 $zone = $z;
1206 7 50       19 $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         16 my @tmp = $self->_all_periods($zone,$year);
1211 7         9 my @ret;
1212 7         13 foreach my $ele (@tmp) {
1213             push(@ret,
1214 14         18 [ [ @{$$ele[0]} ],[ @{$$ele[1]} ],$$ele[2],[ @{$$ele[3]} ],$$ele[4],
  14         25  
  14         18  
1215 14         16 $$ele[5], [ @{$$ele[6]} ],[ @{$$ele[7]} ],$$ele[8],$$ele[9],
  14         31  
  14         40  
1216             $$ele[10],$$ele[11] ]);
1217             }
1218 7         17 return @ret;
1219             }
1220              
1221             sub _all_periods {
1222 34336     34336   53869 my($self,$zone,$year) = @_;
1223 34336         41709 $year += 0;
1224              
1225 34336 100       102987 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 3143         4332 my($ym1,$ym0);
1234 3143 100 100     9337 if ($year > $$self{'data'}{'LastYear'} &&
1235             exists $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'zone'}) {
1236 5         11 $ym1 = $year-1;
1237 5         8 $ym0 = $year;
1238              
1239             } else {
1240 3138         4171 foreach my $y (sort { $a <=> $b }
  1737328         1606447  
1241 3138         53268 keys %{ $$self{'data'}{'Zones'}{$zone}{'Dates'} }) {
1242 115050 100       142996 if ($y < $year) {
1243 112831         108226 $ym1 = $y;
1244 112831         119678 next;
1245             }
1246 2219 100       5265 $ym0 = $year if ($year == $y);
1247 2219         2924 last;
1248             }
1249             }
1250 3143 100       13567 $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 3143         4125 my(@periods);
1258              
1259             # $ym1 will be 0 in 0001
1260 3143 100       5519 if ($ym1) {
1261 3140         8631 my @tmp = $self->_periods($zone,$ym1);
1262 3140 50       10401 push(@periods,pop(@tmp)) if (@tmp);
1263             }
1264              
1265             #
1266             # Add on any periods from the current year.
1267             #
1268              
1269 3143 100       6200 if ($ym0) {
1270 1898         4123 push(@periods,$self->_periods($zone,$year));
1271             }
1272              
1273 3143         9475 $$self{'data'}{'Zones'}{$zone}{'AllDates'}{$year} = [ @periods ];
1274             }
1275              
1276 34336         38479 return @{ $$self{'data'}{'Zones'}{$zone}{'AllDates'}{$year} };
  34336         97606  
1277             }
1278              
1279             sub periods {
1280 8     8 1 79074 my($self,$zone,$year,$year1) = @_;
1281              
1282 8         19 my $z = $self->_zone($zone);
1283 8 50       17 if (! $z) {
1284 0         0 carp "ERROR: [periods] Invalid zone: $zone";
1285 0         0 return;
1286             }
1287 8         11 $zone = $z;
1288 8 100       23 $self->_module($zone) if (! exists $$self{'data'}{'Zones'}{$zone}{'Loaded'});
1289              
1290 8 100       20 if (! defined($year1)) {
1291 7         15 return $self->_periods($zone,$year);
1292             }
1293              
1294 1 50       3 $year = 1 if (! defined($year));
1295              
1296 1         2 my @ret;
1297 1         3 my $lastyear = $$self{'data'}{'LastYear'};
1298              
1299 1 50       2 if ($year <= $lastyear) {
1300 1         2 foreach my $y (sort { $a <=> $b }
  1119         1046  
1301 1         29 keys %{ $$self{'data'}{'Zones'}{$zone}{'Dates'} }) {
1302 4 100 66     16 last if ($y > $year1 || $y > $lastyear);
1303 3 50       5 next if ($y < $year);
1304 3         9 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         4 return @ret;
1316             }
1317              
1318             sub _periods {
1319 5048     5048   9475 my($self,$zone,$year) = @_;
1320 5048         6373 $year += 0;
1321              
1322 5048 100       13397 if (! exists $$self{'data'}{'Zones'}{$zone}{'Dates'}{$year}) {
1323              
1324 12         21 my @periods = ();
1325 12 100       29 if ($year > $$self{'data'}{'LastYear'}) {
1326             # Calculate periods using the LastRule method
1327 11         32 @periods = $self->_lastrule($zone,$year);
1328             }
1329              
1330 12         43 $$self{'data'}{'Zones'}{$zone}{'Dates'}{$year} = [ @periods ];
1331             }
1332              
1333             # A faster 'dclone' so we don't return the actual data
1334 5048         5883 my @ret;
1335 5048         5692 foreach my $ele (@{ $$self{'data'}{'Zones'}{$zone}{'Dates'}{$year} }) {
  5048         13026  
1336             push(@ret,
1337 9132         19086 [ [ @{$$ele[0]} ],[ @{$$ele[1]} ],$$ele[2],[ @{$$ele[3]} ],$$ele[4],$$ele[5],
  9132         16461  
  9132         15118  
1338 9132         10368 [ @{$$ele[6]} ],[ @{$$ele[7]} ],$$ele[8],$$ele[9],$$ele[10],$$ele[11] ]);
  9132         15592  
  9132         33300  
1339             }
1340 5048         10426 return @ret;
1341             }
1342              
1343             sub date_period {
1344 30208     30208 1 105543 my($self,$date,$zone,$wallclock,$isdst) = @_;
1345 30208 100       46322 $wallclock = 0 if (! $wallclock);
1346 30208 100       46138 $isdst = 0 if (! $isdst);
1347              
1348 30208         49483 my $z = $self->_zone($zone);
1349 30208 50       49853 if (! $z) {
1350 0         0 carp "ERROR: [date_period] Invalid zone: $zone";
1351 0         0 return;
1352             }
1353 30208         35269 $zone = $z;
1354 30208 100       60637 $self->_module($zone) if (! exists $$self{'data'}{'Zones'}{$zone}{'Loaded'});
1355              
1356 30208         36790 my $dmb = $$self{'base'};
1357 30208         48847 my @date = @$date;
1358 30208         35276 my $year = $date[0];
1359 30208         73332 my $dates= $dmb->_date_fields(@$date);
1360 30208 50 33     91719 return () if ($year < 0 || $year > 9999);
1361              
1362 30208 100       44307 if ($wallclock) {
1363             # A wallclock date
1364              
1365 24658         49051 my @period = $self->_all_periods($zone,$year);
1366 24658         37866 my $beg = $period[0]->[9];
1367 24658         30347 my $end = $period[-1]->[11];
1368 24658 50       54274 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 24658         28586 my(@per);
1375 24658         35595 foreach my $period (@period) {
1376 69965         122161 my($begUT,$begLT,$offsetstr,$offset,$abbrev,$dst,$endUT,$endLT,
1377             $begUTs,$begLTs,$endUTs,$endLTs) = @$period;
1378 69965 100 100     162666 if (($dates cmp $begLTs) != -1 && ($dates cmp $endLTs) != 1) {
1379 24690         44189 push(@per,$period);
1380             }
1381             }
1382              
1383 24658 100       49656 if ($#per == -1) {
    100          
    50          
1384 8         30 return ();
1385             } elsif ($#per == 0) {
1386 24610         64301 return $per[0];
1387             } elsif ($#per == 1) {
1388 40 100       81 if ($per[0][5] == $isdst) {
1389 19         58 return $per[0];
1390             } else {
1391 21         64 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 5550         10287 my @period = $self->_all_periods($zone,$year);
1402 5550         8720 foreach my $period (@period) {
1403 5759         13021 my($begUT,$begLT,$offsetstr,$offset,$abbrev,$isdst,$endUT,$endLT,
1404             $begUTs,$begLTs,$endUTs,$endLTs) = @$period;
1405 5759 100 66     17301 if (($dates cmp $begUTs) != -1 && ($dates cmp $endUTs) != 1) {
1406 5550         12711 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   39 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         34 %{ $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'} });
  22         108  
1436 22 50       52 return () if (! @mon);
1437              
1438             #
1439             # Analyze each time change.
1440             #
1441              
1442 22         28 my @dates = ();
1443 22         34 my $dmb = $$self{'base'};
1444              
1445 22         49 my $stdoff = $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'zone'}{'stdoff'};
1446 22         39 my $dstoff = $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'zone'}{'dstoff'};
1447              
1448 22         25 my (@period);
1449              
1450 22         32 foreach my $mon (@mon) {
1451             my $flag =
1452 33         81 $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'}{$mon}{'flag'};
1453             my $dow =
1454 33         50 $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'}{$mon}{'dow'};
1455             my $num =
1456 33         50 $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'}{$mon}{'num'};
1457             my $isdst=
1458 33         238 $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'}{$mon}{'isdst'};
1459             my $time =
1460 33         49 $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'}{$mon}{'time'};
1461             my $type =
1462 33         54 $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'}{$mon}{'type'};
1463             my $abb =
1464 33         169 $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'}{$mon}{'abb'};
1465              
1466             # The end of the current period and the beginning of the next
1467 33         97 my($endUT,$endLT,$begUT,$begLT) =
1468             $dmb->_critical_date($year,$mon,$flag,$num,$dow,
1469             $isdst,$time,$type,$stdoff,$dstoff);
1470 33 100       112 return ($endUT,$endLT) if ($endonly);
1471              
1472 22 100       78 if (@period) {
1473 11         20 push(@period,$endUT,$endLT);
1474 11         24 push(@dates,[@period]);
1475             }
1476 22 100       46 my $offsetstr = ($isdst ? $dstoff : $stdoff);
1477 22         52 my $offset = $dmb->split('offset',$offsetstr);
1478              
1479 22         83 @period = ($begUT,$begLT,$offsetstr,$offset,$abb,$isdst);
1480             }
1481              
1482 11         49 push(@period,$self->_lastrule($zone,$year+1,1));
1483 11         26 push(@dates,[@period]);
1484              
1485 11         22 foreach my $period (@dates) {
1486 22         51 my($begUT,$begLT,$offsetstr,$offset,$abbrev,$dst,$endUT,$endLT) = @$period;
1487 22         46 my $begUTstr = $dmb->join("date",$begUT);
1488 22         46 my $begLTstr = $dmb->join("date",$begLT);
1489 22         46 my $endUTstr = $dmb->join("date",$endUT);
1490 22         41 my $endLTstr = $dmb->join("date",$endLT);
1491 22         90 $period = [$begUT,$begLT,$offsetstr,$offset,$abbrev,$dst,$endUT,$endLT,
1492             $begUTstr,$begLTstr,$endUTstr,$endLTstr];
1493             }
1494              
1495 11         39 return @dates;
1496             }
1497              
1498             ########################################################################
1499             # CONVERSION
1500             ########################################################################
1501              
1502             sub convert {
1503 41     41 1 51643 my($self,$date,$from,$to,$isdst) = @_;
1504 41         117 $self->_convert('convert',$date,$from,$to,$isdst);
1505             }
1506              
1507             sub convert_to_gmt {
1508 2860     2860 1 21073 my($self,$date,@arg) = @_;
1509 2860         6884 my($err,$from,$isdst) = _convert_args('convert_to_gmt',@arg);
1510 2860 50       6023 return (1) if ($err);
1511              
1512 2860         4292 my $dmb = $$self{'base'};
1513              
1514 2860 50       5259 if (! $from) {
1515 0         0 $from = $self->_now('tz',1);
1516             }
1517 2860         7388 $self->_convert('convert_to_gmt',$date,$from,'GMT',$isdst);
1518             }
1519              
1520             sub convert_from_gmt {
1521 12     12 1 16435 my($self,$date,@arg) = @_;
1522 12         33 my($err,$to,$isdst) = _convert_args('convert_from_gmt',@arg);
1523 12 50       35 return (1) if ($err);
1524              
1525 12         24 my $dmb = $$self{'base'};
1526              
1527 12 100       45 if (! $to) {
1528 7         34 $to = $self->_now('tz',1);
1529             }
1530 12         59 $self->_convert('convert_from_gmt',$date,'GMT',$to,$isdst);
1531             }
1532              
1533             sub convert_to_local {
1534 27     27 1 74 my($self,$date,@arg) = @_;
1535 27         108 my($err,$from,$isdst) = _convert_args('convert_to_local',@arg);
1536 27 50       89 return (1) if ($err);
1537              
1538 27         60 my $dmb = $$self{'base'};
1539              
1540 27 50       76 if (! $from) {
1541 0         0 $from = 'GMT';
1542             }
1543 27         82 $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 2899     2899   5757 my($caller,@args) = @_;
1561              
1562 2899 100       8915 if ($#args == -1) {
    100          
    50          
1563 7         30 return (0,'',0);
1564             } elsif ($#args == 0) {
1565 176 50 33     1361 if ($args[0] eq '0' ||
1566             $args[0] eq '1') {
1567 0         0 return (0,'',$args[0]);
1568             } else {
1569 176         746 return (0,$args[0],0);
1570             }
1571             } elsif ($#args == 1) {
1572 2716         7083 return (0,@args);
1573             } else {
1574 0         0 return (1,'',0);
1575             }
1576             }
1577              
1578             sub _convert {
1579 5736     5736   10818 my($self,$caller,$date,$from,$to,$isdst) = @_;
1580 5736         7706 my $dmb = $$self{'base'};
1581              
1582             # Handle $date as a reference and a string
1583 5736         6314 my (@date);
1584 5736 100       10768 if (ref($date)) {
1585 5735         9992 @date = @$date;
1586             } else {
1587 1         2 @date = @{ $dmb->split('date',$date) };
  1         6  
1588 1         4 $date = [@date];
1589             }
1590              
1591 5736 50       10596 if ($from ne $to) {
1592 5736         10804 my $tmp = $self->_zone($from);
1593 5736 50       10618 if (! $tmp) {
1594 0         0 return (2);
1595             }
1596 5736         7117 $from = $tmp;
1597              
1598 5736         8369 $tmp = $self->_zone($to);
1599 5736 50       10049 if (! $tmp) {
1600 0         0 return (3);
1601             }
1602 5736         8064 $to = $tmp;
1603             }
1604              
1605 5736 100       10281 if ($from eq $to) {
1606 187         676 my $per = $self->date_period($date,$from,1,$isdst);
1607 187         447 my $offset = $$per[3];
1608 187         380 my $abb = $$per[4];
1609 187         747 return (0,$date,$offset,$isdst,$abb);
1610             }
1611              
1612             # Convert $date from $from to GMT
1613              
1614 5549 50       9728 if ($from ne "Etc/GMT") {
1615 5549         10409 my $per = $self->date_period($date,$from,1,$isdst);
1616 5549 100       10047 if (! $per) {
1617 2         7 return (4);
1618             }
1619 5547         7042 my $offset = $$per[3];
1620 5547         6144 @date = @{ $dmb->calc_date_time(\@date,$offset,1) };
  5547         14872  
1621             }
1622              
1623             # Convert $date from GMT to $to
1624              
1625 5547         9763 $isdst = 0;
1626 5547         8076 my $offset = [0,0,0];
1627 5547         7325 my $abb = 'GMT';
1628              
1629 5547 50       10158 if ($to ne "Etc/GMT") {
1630 5547         11986 my $per = $self->date_period([@date],$to,0);
1631 5547         10593 $offset = $$per[3];
1632 5547         7035 $isdst = $$per[5];
1633 5547         6815 $abb = $$per[4];
1634 5547         6313 @date = @{ $dmb->calc_date_time(\@date,$offset) };
  5547         12441  
1635             }
1636              
1637 5547         22249 return (0,[@date],$offset,$isdst,$abb);
1638             }
1639              
1640             ########################################################################
1641             # REGULAR EXPRESSIONS FOR TIMEZONE INFORMATION
1642             ########################################################################
1643              
1644             # Returns regular expressions capable of matching timezones.
1645             #
1646             # The timezone regular expressions are:
1647             # namerx : this will match a zone name or alias (America/New_York)
1648             # abbrx : this will match a zone abbreviation (EDT)
1649             # zonerx : this will match a zone name or an abbreviation
1650             # offrx : this will match a pure offset (+0400)
1651             # offabbrx : this will match an offset with an abbreviation (+0400 WET)
1652             # offparrx : this will match an offset and abbreviation if parentheses
1653             # ("+0400 (WET)")
1654             # zrx : this will match all forms
1655             #
1656             # The regular expression will have the following named matches:
1657             # tzstring : the full string matched
1658             # zone : the name/alias
1659             # abb : the zone abbrevation
1660             # off : the offset
1661             #
1662             sub _zrx {
1663 544     544   1133 my($self,$re) = @_;
1664 544 100       2043 return $$self{'data'}{$re} if (defined $$self{'data'}{$re});
1665              
1666             # Zone name
1667              
1668 79         153 my @zone;
1669 79 50       341 if (exists $ENV{'DATE_MANIP_DEBUG_ZONES'}) {
1670 0         0 @zone = split(/\s+/,$ENV{'DATE_MANIP_DEBUG_ZONES'});
1671             } else {
1672 79         13222 @zone = (keys %{ $$self{'data'}{'Alias'} },
1673 79         170 keys %{ $$self{'data'}{'MyAlias'} });
  79         1327  
1674             }
1675 79         1272 @zone = sort _sortByLength(@zone);
1676 79         343 foreach my $zone (@zone) {
1677 63200         81589 $zone =~ s/\057/\\057/g; # /
1678 63200         67408 $zone =~ s/\055/\\055/g; # -
1679 63200         61414 $zone =~ s/\056/\\056/g; # .
1680 63200         60483 $zone =~ s/\050/\\050/g; # (
1681 63200         60178 $zone =~ s/\051/\\051/g; # )
1682 63200         66704 $zone =~ s/\053/\\053/g; # +
1683             }
1684              
1685 79         6191 my $zone = join('|',@zone);
1686 79         215669 $zone = qr/(?<zone>$zone)/i;
1687              
1688             # Abbreviation
1689              
1690 79         2605 my @abb;
1691 79 50       456 if (exists $ENV{'DATE_MANIP_DEBUG_ABBREVS'}) {
1692 0         0 @abb = split(/\s+/,$ENV{'DATE_MANIP_DEBUG_ABBREVS'});
1693             } else {
1694 79         3630 @abb = (keys %{ $$self{'data'}{'Abbrev'} },
1695 79         195 keys %{ $$self{'data'}{'MyAbbrev'} });
  79         458  
1696             }
1697 79         615 @abb = sort _sortByLength(@abb);
1698 79         278 foreach my $abb (@abb) {
1699 13274         13390 $abb =~ s/\055/\\055/g; # -
1700 13274         14472 $abb =~ s/\053/\\053/g; # +
1701             }
1702              
1703 79         860 my $abb = join('|',@abb);
1704 79         32048 $abb = qr/(?<abb>$abb)/i;
1705              
1706             # Offset (+HH, +HHMM, +HH:MM, +HH:MM:SS, +HHMMSS)
1707              
1708 79         995 my($hr) = qr/(?:[0-1][0-9]|2[0-3])/; # 00 - 23
1709 79         302 my($mn) = qr/(?:[0-5][0-9])/; # 00 - 59
1710 79         293 my($ss) = qr/(?:[0-5][0-9])/; # 00 - 59
1711              
1712 79         4215 my($off) = qr/ (?<off> [+-] (?: $hr:$mn:$ss |
1713             $hr$mn$ss |
1714             $hr:?$mn |
1715             $hr
1716             )
1717             ) /ix;
1718              
1719             # Assemble everything
1720             #
1721             # A timezone can be any of the following in this order:
1722             # Offset (ABB)
1723             # Offset ABB
1724             # ABB
1725             # Zone
1726             # Offset
1727             # We put ABB before Zone so CET gets parse as the more common abbreviation
1728             # than the less common zone name.
1729              
1730 79         197482 $$self{'data'}{'namerx'} = qr/(?<tzstring>$zone)/;
1731 79         34288 $$self{'data'}{'abbrx'} = qr/(?<tzstring>$abb)/;
1732 79         225961 $$self{'data'}{'zonerx'} = qr/(?<tzstring>(?:$abb|$zone))/;
1733 79         8160 $$self{'data'}{'offrx'} = qr/(?<tzstring>$off)/;
1734 79         24663 $$self{'data'}{'offabbrx'} = qr/(?<tzstring>$off\s+$abb)/;
1735 79         23020 $$self{'data'}{'offparrx'} = qr/(?<tzstring>$off\s*\($abb\))/;
1736 79         273419 $$self{'data'}{'zrx'} = qr/(?<tzstring>(?:$off\s*\($abb\)|$off\s+$abb|$abb|$zone|$off))/;
1737              
1738 79         9703 return $$self{'data'}{$re};
1739             }
1740              
1741             # This sorts from longest to shortest element
1742             #
1743 168     168   1390 no strict 'vars';
  168         374  
  168         12252  
1744             sub _sortByLength {
1745 577929     577929   552200 return (length $b <=> length $a);
1746             }
1747 168     168   1049 use strict 'vars';
  168         360  
  168         168648  
1748              
1749             ########################################################################
1750             # CONFIG VARS
1751             ########################################################################
1752              
1753             # This sets a config variable. It also performs all side effects from
1754             # setting that variable.
1755             #
1756             sub _config_var_tz {
1757 384     384   1049 my($self,$var,$val) = @_;
1758              
1759 384 50       2024 if ($var eq 'tz') {
    100          
    100          
    50          
1760 0         0 my $err = $self->_config_var_setdate("now,$val",0);
1761 0 0       0 return if ($err);
1762 0         0 $$self{'data'}{'sections'}{'conf'}{'forcedate'} = 0;
1763 0         0 $val = 1;
1764              
1765             } elsif ($var eq 'setdate') {
1766 169         782 my $err = $self->_config_var_setdate($val,0);
1767 169 50       539 return if ($err);
1768 169         589 $$self{'data'}{'sections'}{'conf'}{'forcedate'} = 0;
1769 169         325 $val = 1;
1770              
1771             } elsif ($var eq 'forcedate') {
1772 188         793 my $err = $self->_config_var_setdate($val,1);
1773 188 50       687 return if ($err);
1774 188         781 $$self{'data'}{'sections'}{'conf'}{'setdate'} = 0;
1775 188         415 $val = 1;
1776              
1777             } elsif ($var eq 'configfile') {
1778 27         320 $self->_config_file($val);
1779 27         239 return;
1780             }
1781              
1782 357         749 my $base = $$self{'base'};
1783 357         1070 $$base{'data'}{'sections'}{'conf'}{$var} = $val;
1784 357         1827 return;
1785             }
1786              
1787             sub _config_var_setdate {
1788 357     357   1012 my($self,$val,$force) = @_;
1789 357         878 my $base = $$self{'base'};
1790              
1791 357         1870 my $dstrx = qr/(?:,\s*(stdonly|dstonly|std|dst))?/i;
1792 357         1147 my $zonrx = qr/,\s*(.+)/;
1793 357         1164 my $da1rx = qr/(\d\d\d\d)(\d\d)(\d\d)(\d\d):(\d\d):(\d\d)/;
1794 357         1076 my $da2rx = qr/(\d\d\d\d)\-(\d\d)\-(\d\d)\-(\d\d):(\d\d):(\d\d)/;
1795 357         770 my $time = time;
1796              
1797 357         755 my($op,$date,$dstflag,$zone,@date,$offset,$abb);
1798              
1799             #
1800             # Parse the argument
1801             #
1802              
1803 357 100 33     16401 if ($val =~ /^now${dstrx}${zonrx}$/oi) {
    50 0        
    50          
    0          
    0          
1804             # now,ZONE
1805             # now,DSTFLAG,ZONE
1806             # Sets now to the system date/time but sets the timezone to be ZONE
1807              
1808 267         738 $op = 'nowzone';
1809 267         1137 ($dstflag,$zone) = ($1,$2);
1810              
1811             } elsif ($val =~ /^zone${dstrx}${zonrx}$/oi) {
1812             # zone,ZONE
1813             # zone,DSTFLAG,ZONE
1814             # Converts 'now' to the alternate zone
1815              
1816 0         0 $op = 'zone';
1817 0         0 ($dstflag,$zone) = ($1,$2);
1818              
1819             } elsif ($val =~ /^${da1rx}${dstrx}${zonrx}$/o ||
1820             $val =~ /^${da2rx}${dstrx}${zonrx}$/o) {
1821             # DATE,ZONE
1822             # DATE,DSTFLAG,ZONE
1823             # Sets the date and zone
1824              
1825 90         310 $op = 'datezone';
1826 90         227 my($y,$m,$d,$h,$mn,$s);
1827 90         808 ($y,$m,$d,$h,$mn,$s,$dstflag,$zone) = ($1,$2,$3,$4,$5,$6,$7,$8);
1828 90         418 $date = [$y,$m,$d,$h,$mn,$s];
1829              
1830             } elsif ($val =~ /^${da1rx}$/o ||
1831             $val =~ /^${da2rx}$/o) {
1832             # DATE
1833             # Sets the date in the system timezone
1834              
1835 0         0 $op = 'date';
1836 0         0 my($y,$m,$d,$h,$mn,$s) = ($1,$2,$3,$4,$5,$6);
1837 0         0 $date = [$y,$m,$d,$h,$mn,$s];
1838 0         0 $zone = $self->_now('systz',1);
1839              
1840             } elsif (lc($val) eq 'now') {
1841             # now
1842             # Resets everything
1843              
1844 0         0 my $systz = $$base{'data'}{'now'}{'systz'};
1845 0         0 $base->_init_now();
1846 0         0 $$base{'data'}{'now'}{'systz'} = $systz;
1847 0         0 return 0;
1848              
1849             } else {
1850 0         0 carp "ERROR: [config_var] invalid SetDate/ForceDate value: $val";
1851 0         0 return 1;
1852             }
1853              
1854 357 50       1186 $dstflag = 'std' if (! $dstflag);
1855              
1856             #
1857             # Get the date we're setting 'now' to
1858             #
1859              
1860 357 100       1241 if ($op eq 'nowzone') {
    50          
1861             # Use the system localtime
1862              
1863 267         5472 my($s,$mn,$h,$d,$m,$y) = localtime($time);
1864 267         1057 $y += 1900;
1865 267         445 $m++;
1866 267         823 $date = [$y,$m,$d,$h,$mn,$s];
1867              
1868             } elsif ($op eq 'zone') {
1869             # Use the system GMT time
1870              
1871 0         0 my($s,$mn,$h,$d,$m,$y) = gmtime($time);
1872 0         0 $y += 1900;
1873 0         0 $m++;
1874 0         0 $date = [$y,$m,$d,$h,$mn,$s];
1875             }
1876              
1877             #
1878             # Find out what zone was passed in. It can be an alias or an offset.
1879             #
1880              
1881 357 50       1040 if ($zone) {
1882 357         667 my ($err,@args);
1883 357         824 my $dmb = $$self{'base'};
1884 357 50       1002 $date = [] if (! defined $date);
1885 357         2222 $zone = $self->__zone($date,'',lc($zone),'',lc($dstflag));
1886 357 50       1256 if (! $zone) {
1887 0         0 carp "ERROR: [config_var] invalid zone in SetDate: @args";
1888 0         0 return 1;
1889             }
1890              
1891             } else {
1892 0         0 $zone = $$base{'data'}{'now'}{'systz'};
1893             }
1894              
1895             #
1896             # Handle the zone
1897             #
1898              
1899 357         685 my($isdst,@isdst);
1900 357 50       1013 if ($dstflag eq 'std') {
    0          
    0          
1901 357         838 @isdst = (0,1);
1902             } elsif ($dstflag eq 'stdonly') {
1903 0         0 @isdst = (0);
1904             } elsif ($dstflag eq 'dst') {
1905 0         0 @isdst = (1,0);
1906             } else {
1907 0         0 @isdst = (1);
1908             }
1909              
1910 357 50 66     1567 if ($op eq 'nowzone' ||
    0 33        
1911             $op eq 'datezone' ||
1912             $op eq 'date') {
1913              
1914             # Check to make sure that the date can exist in this zone.
1915 357         605 my $per;
1916 357         651 foreach my $dst (@isdst) {
1917 714 100       1509 next if ($per);
1918 357         1297 $per = $self->date_period($date,$zone,1,$dst);
1919             }
1920              
1921 357 50       1005 if (! $per) {
1922 0         0 carp "ERROR: [config_var] invalid date: SetDate: $date, $zone";
1923 0         0 return 1;
1924             }
1925 357         773 $isdst = $$per[5];
1926 357         665 $abb = $$per[4];
1927 357         729 $offset = $$per[3];
1928              
1929             } elsif ($op eq 'zone') {
1930              
1931             # Convert to that zone
1932 0         0 my($err);
1933 0         0 ($err,$date,$offset,$isdst,$abb) = $self->convert_from_gmt($date,$zone);
1934 0 0       0 if ($err) {
1935 0         0 carp "ERROR: [config_var] invalid SetDate date/offset values: $date, $zone";
1936 0         0 return 1;
1937             }
1938             }
1939              
1940             #
1941             # Set NOW
1942             #
1943              
1944 357         1148 $$base{'data'}{'now'}{'date'} = $date;
1945 357         971 $$base{'data'}{'now'}{'tz'} = $self->_zone($zone);
1946 357         842 $$base{'data'}{'now'}{'isdst'} = $isdst;
1947 357         814 $$base{'data'}{'now'}{'abb'} = $abb;
1948 357         1220 $$base{'data'}{'now'}{'offset'} = $offset;
1949              
1950             #
1951             # Treate SetDate/ForceDate
1952             #
1953              
1954 357 100       1024 if ($force) {
1955 188         524 $$base{'data'}{'now'}{'force'} = 1;
1956 188         492 $$base{'data'}{'now'}{'set'} = 0;
1957             } else {
1958 169         486 $$base{'data'}{'now'}{'force'} = 0;
1959 169         382 $$base{'data'}{'now'}{'set'} = 1;
1960 169         435 $$base{'data'}{'now'}{'setsecs'} = $time;
1961 169         829 my($err,$setdate) = $self->convert_to_gmt($date,$zone);
1962 169         547 $$base{'data'}{'now'}{'setdate'} = $setdate;
1963             }
1964              
1965 357         2182 return 0;
1966             }
1967              
1968             1;
1969             # Local Variables:
1970             # mode: cperl
1971             # indent-tabs-mode: nil
1972             # cperl-indent-level: 3
1973             # cperl-continued-statement-offset: 2
1974             # cperl-continued-brace-offset: 0
1975             # cperl-brace-offset: 0
1976             # cperl-brace-imaginary-offset: 0
1977             # cperl-label-offset: 0
1978             # End: