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   1177 use Date::Manip::Obj;
  168         347  
  168         4980  
15 168     168   987 use Date::Manip::TZ_Base;
  168         379  
  168         6402  
16             @ISA = qw(Date::Manip::Obj Date::Manip::TZ_Base);
17              
18             require 5.010000;
19 168     168   927 use warnings;
  168         357  
  168         4123  
20 168     168   852 use strict;
  168         326  
  168         3130  
21              
22 168     168   778 use IO::File;
  168         320  
  168         29712  
23             require Date::Manip::Zones;
24 168     168   1249 use Date::Manip::Base;
  168         389  
  168         3995  
25 168     168   104581 use Data::Dumper;
  168         1047978  
  168         9659  
26 168     168   1260 use Carp;
  168         380  
  168         81051  
27              
28             our $VERSION;
29             $VERSION='6.91';
30 168     168   992 END { undef $VERSION; }
31              
32             # To get rid of a 'used only once' warnings.
33             END {
34 168     168   549 my $tmp = \%Date::Manip::Zones::Module;
35 168         587 $tmp = \%Date::Manip::Zones::ZoneNames;
36 168         662 $tmp = \%Date::Manip::Zones::Alias;
37 168         493 $tmp = \%Date::Manip::Zones::Abbrev;
38 168         411 $tmp = \%Date::Manip::Zones::Offmod;
39 168         425 $tmp = $Date::Manip::Zones::FirstDate;
40 168         384 $tmp = $Date::Manip::Zones::LastDate;
41 168         380 $tmp = $Date::Manip::Zones::LastYear;
42 168         336 $tmp = $Date::Manip::Zones::TzcodeVersion;
43 168         652 $tmp = $Date::Manip::Zones::TzdataVersion;
44             }
45              
46             ########################################################################
47             # BASE METHODS
48             ########################################################################
49              
50             sub _init {
51 509     509   1264 my($self) = @_;
52              
53 509         12099 $$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         3107 my $dmb = $$self{'base'};
98 509         3416 my $os = $dmb->_os();
99              
100 509 50       3610 if ($os eq 'Unix') {
    0          
    0          
101 509         2421 $$self{'data'}{'path'} = '/bin:/usr/bin';
102 509         7963 $$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   1212 my($self) = @_;
158              
159 512         4464 $self->_set_curr_zone();
160             }
161              
162 168     168   1465 no strict 'refs';
  168         404  
  168         48560  
163             # This loads data from an offset module
164             #
165             sub _offmod {
166 91     91   237 my($self,$offset) = @_;
167 91 100       336 return if (exists $$self{'data'}{'Offsets'}{$offset});
168              
169 34         105 my $mod = $$self{'data'}{'Offmod'}{$offset};
170 34         2892 eval "require Date::Manip::Offset::${mod}";
171 34         174 my %off = %{ "Date::Manip::Offset::${mod}::Offset" };
  34         304  
172              
173 34         269 $$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   3755 my($self,$zone) = @_;
180 1635 50       4740 return if (exists $$self{'data'}{'Zones'}{$zone}{'Loaded'});
181              
182 1635         5498 my $mod = $$self{'data'}{'Module'}{$zone};
183 1635         119653 eval "require Date::Manip::TZ::${mod}";
184 1635         8359 my %dates = %{ "Date::Manip::TZ::${mod}::Dates" };
  1635         49227  
185 1635         7691 my %last = %{ "Date::Manip::TZ::${mod}::LastRule" };
  1635         7778  
186 1635         52787 $$self{'data'}{'Zones'}{$zone} =
187             {
188             'Dates' => { %dates },
189             'LastRule' => { %last },
190             'Loaded' => 1
191             };
192             }
193 168     168   1297 use strict 'refs';
  168         391  
  168         258056  
194              
195             ########################################################################
196             # CHECKING/MODIFYING ZONEINFO DATA
197             ########################################################################
198              
199             sub _zone {
200 48041     48041   80848 my($self,$zone) = @_;
201 48041         82642 $zone = lc($zone);
202              
203 48041 100       133351 if (exists $$self{'data'}{'MyAlias'}{$zone}) {
    100          
204 1         3 return $$self{'data'}{'MyAlias'}{$zone};
205             } elsif (exists $$self{'data'}{'Alias'}{$zone}) {
206 47990         105940 return $$self{'data'}{'Alias'}{$zone};
207             } else {
208 50         514 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 861 my($self,$alias,$zone) = @_;
224 2         4 $alias = lc($alias);
225              
226 2 100       5 if ($alias eq 'reset') {
227 1         3 $$self{'data'}{'MyAlias'} = {};
228 1         3 $$self{'data'}{'zonerx'} = undef;
229 1         3 return 0;
230             }
231 1 50       5 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         2 $zone = $self->_zone($zone);
238              
239 1 50       3 return 1 if (! $zone);
240 1         3 $$self{'data'}{'MyAlias'}{$alias} = $zone;
241 1         2 $$self{'data'}{'zonerx'} = undef;
242 1         3 return 0;
243             }
244              
245             sub define_abbrev {
246 6     6 1 3565 my($self,$abbrev,@zone) = @_;
247 6         14 $abbrev = lc($abbrev);
248              
249 6 100       17 if ($abbrev eq 'reset') {
250 2         10 $$self{'data'}{'MyAbbrev'} = {};
251 2         5 $$self{'data'}{'abbrx'} = undef;
252 2         5 return 0;
253             }
254 4 100 100     34 if ($#zone == 0 && lc($zone[0]) eq 'reset') {
255 1         3 delete $$self{'data'}{'MyAbbrev'}{$abbrev};
256 1         4 $$self{'data'}{'abbrx'} = undef;
257 1         3 return (0);
258             }
259              
260 3 50       15 if (! exists $$self{'data'}{'Abbrev'}{$abbrev}) {
261 0         0 return (1);
262             }
263              
264 3         7 my (@z,%z);
265 3         8 my %zone = map { $_,1 } @{ $$self{'data'}{'Abbrev'}{$abbrev} };
  50         99  
  3         15  
266 3         12 foreach my $z (@zone) {
267 5         15 my $zone = $self->_zone($z);
268 5 50       17 return (2,$z) if (! $zone);
269 5 50       14 return (3,$z) if (! exists $zone{$zone});
270 5 50       14 next if (exists $z{$zone});
271 5         12 $z{$zone} = 1;
272 5         12 push(@z,$zone);
273             }
274              
275 3         13 $$self{'data'}{'MyAbbrev'}{$abbrev} = [ @z ];
276 3         11 $$self{'data'}{'abbrx'} = undef;
277 3         13 return ();
278             }
279              
280             sub define_offset {
281 22     22 1 11919 my($self,$offset,@args) = @_;
282 22         35 my $dmb = $$self{'base'};
283              
284 22 100       47 if (lc($offset) eq 'reset') {
285 10         24 $$self{'data'}{'MyOffsets'} = {};
286 10         18 return (0);
287             }
288 12 50 33     34 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       22 if (ref($offset)) {
297 0         0 $offset = $dmb->join('offset',$offset);
298             } else {
299 12         37 $offset = $dmb->_delta_convert('offset',$offset);
300             }
301 12 100       24 return (9) if (! $offset);
302 11 100       36 return (1) if (! exists $$self{'data'}{'Offmod'}{$offset});
303              
304 10         28 $self->_offmod($offset);
305              
306             # Find out whether we're handling STD, DST, or both.
307              
308 10         17 my(@isdst) = (0,1);
309 10 50       47 if ($args[0] =~ /^std|dst|stdonly|dstonly$/i) {
310 10         22 my $tmp = lc(shift(@args));
311 10 100       28 if ($tmp eq 'stdonly') {
    100          
312 3         8 @isdst = (0);
313             } elsif ($tmp eq 'dstonly') {
314 2         4 @isdst = (1);
315             }
316             }
317 10         17 my @zone = @args;
318              
319 10 100 100     34 if ($#isdst == 0 &&
320             ! exists($$self{'data'}{'Offsets'}{$offset}{$isdst[0]})) {
321 2         7 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         16 foreach my $isdst (0,1) {
328 16 50       36 next if (! exists $$self{'data'}{'Offsets'}{$offset}{$isdst});
329 16         20 my @z = @{ $$self{'data'}{'Offsets'}{$offset}{$isdst} };
  16         43  
330 16         23 $tmp{$isdst} = { map { $_,1 } @z };
  182         338  
331             }
332              
333 8         15 foreach my $z (@zone) {
334 15         24 my $lcz = lc($z);
335 15 100 100     79 if (! exists $$self{'data'}{'ZoneNames'}{$lcz}) {
    100 100        
    100          
336 2         10 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         11 return (5,$z);
343             }
344 10         21 $z = $lcz;
345             }
346              
347             # Set the zones accordingly.
348              
349 3         5 foreach my $isdst (@isdst) {
350 6         10 my @z;
351 6         11 foreach my $z (@zone) {
352 10 100       24 push(@z,$z) if (exists $tmp{$isdst}{$z});
353             }
354 6         21 $$self{'data'}{'MyOffsets'}{$offset}{$isdst} = [ @z ];
355             }
356              
357 3         20 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   1981 my($self) = @_;
389 512         1940 my $dmb = $$self{'base'};
390 512         2590 my $currzone = $self->_get_curr_zone();
391              
392 512         1368 $$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   2009 my($self) = @_;
400 512         2853 my $dmb = $$self{'base'};
401              
402 512         1918 my $t = time;
403 512         21271 my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($t);
404 512         1928 my $currzone = '';
405 512 50       1507 my $dstflag = ($isdst ? 'dstonly' : 'stdonly');
406              
407 512         1778 my (@methods) = @{ $$self{'data'}{'methods'} };
  512         4043  
408 512 50       2098 my $debug = ($ENV{DATE_MANIP_DEBUG} ? 1 : 0);
409              
410             defined $$self{'data'}{'path'}
411 512 50       4749 and local $ENV{PATH} = $$self{'data'}{'path'};
412              
413             METHOD:
414 512         1697 while (@methods) {
415 2000         3405 my $method = shift(@methods);
416 2000         3097 my @zone = ();
417              
418 2000 50       3710 print "*** DEBUG *** METHOD: $method [" if ($debug);
419              
420 2000 100       6237 if ($method eq 'main') {
    100          
    50          
    0          
    0          
    0          
    0          
    0          
421              
422 512 50       2024 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         1076 my $var = shift(@methods);
428 512 50       1436 print "$var] " if ($debug);
429 168     168   1521 no strict "refs";
  168         429  
  168         9118  
430 512         803 my $val = ${ "::$var" };
  512         2392  
431 168     168   1126 use strict "refs";
  168         433  
  168         393924  
432 512 50       1371 if (defined $val) {
433 0         0 push(@zone,$val);
434 0 0       0 print "$val\n" if ($debug);
435             } else {
436 512 50       1408 print "undef\n" if ($debug);
437             }
438              
439             } elsif ($method eq 'env') {
440 512 50       1576 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         1332 my $type = lc( shift(@methods) );
446 512 50       1315 print "$type," if ($debug);
447              
448 512 50 33     1859 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         1033 my $var = shift(@methods);
456 512 50       1271 print "$var] " if ($debug);
457 512 100       1491 if (exists $ENV{$var}) {
458 24 50       48 if ($type eq 'zone') {
459 24         54 push(@zone,$ENV{$var});
460 24 50       55 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       1334 print "undef\n" if ($debug);
471             }
472              
473             } elsif ($method eq 'file') {
474 976 50       2114 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         1646 my $file = shift(@methods);
480 976 50       3467 print "$file] " if ($debug);
481 976 100       13179 if (! -f $file) {
482 488 50       1494 print "not found\n" if ($debug);
483 488         1768 next;
484             }
485              
486 488         4511 my $in = new IO::File;
487 488 50       24144 $in->open($file) || next;
488 488         27734 my $firstline = 1;
489              
490 488         1115 my @z;
491 488         3429 while (! $in->eof) {
492 488         17318 my $line = <$in>;
493 488         1520 chomp($line);
494 488 50 33     4852 next if ($line =~ /^\s*\043/ ||
495             $line =~ /^\s*$/);
496 488 50       2103 if ($firstline) {
497 488         949 $firstline = 0;
498 488         2155 $line =~ s/^\s*//;
499 488         2539 $line =~ s/\s*$//;
500 488         1314 $line =~ s/["']//g; # "
501 488         1057 $line =~ s/\s+/_/g;
502 488         1364 @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       2812 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         7995 close(IN);
556              
557 488 50       1908 push(@zone,@z) if (@z);
558              
559 488 50       8958 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         4694 while (@zone) {
672 512         1757 my $zone = lc(shift(@zone));
673              
674             # OpenUNIX puts a colon at the start
675 512         1564 $zone =~ s/^://;
676              
677             # If we got a zone name/alias
678 512         2162 $currzone = $self->_zone($zone);
679 512 50       2041 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       1354 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         3896 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   1526 no warnings;
  168         383  
  168         25321  
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 = ;
848 0         0 close(IN);
849 0         0 chomp(@out);
850 0         0 return @out;
851             }
852 168     168   1366 use warnings;
  168         430  
  168         841604  
853              
854             ########################################################################
855             # DETERMINING A TIMEZONE
856             ########################################################################
857              
858             sub zone {
859 62     62 1 34404 my($self,@args) = @_;
860 62         129 my $dmb = $$self{'base'};
861 62 100       156 if (! @args) {
862 1         19 my($tz) = $self->_now('tz',1);
863 1         7 return $$self{'data'}{'ZoneNames'}{$tz}
864             }
865              
866             # Parse the arguments
867              
868 61         186 my($zone,$abbrev,$offset,$dstflag) = ('','','','');
869 61         114 my $date = [];
870 61         90 my $tmp;
871 61         116 foreach my $arg (@args) {
872              
873 106 100       282 if (ref($arg) eq 'ARRAY') {
    50          
874 34 50       86 if ($#$arg == 5) {
    0          
875             # [Y,M,D,H,Mn,S]
876 34 50       68 return undef if (@$date);
877 34         62 $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         145 $arg = lc($arg);
894              
895 72 100 66     366 if ($arg =~ /^(std|dst|stdonly|dstonly)$/) {
    100          
    100          
    50          
    100          
    50          
896 11 50       25 return undef if ($dstflag);
897 11         22 $dstflag = $arg;
898              
899             } elsif ($tmp = $self->_zone($arg)) {
900 15 50       41 return undef if ($zone);
901 15         34 $zone = $tmp;
902              
903             } elsif (exists $$self{'data'}{'MyAbbrev'}{$arg} ||
904             exists $$self{'data'}{'Abbrev'}{$arg}) {
905 13 50       49 return undef if ($abbrev);
906 13         36 $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       79 return undef if ($offset);
913 30         95 $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         185 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   2289 my($self,$date,$offset,$zone,$abbrev,$dstflag) = @_;
936 561         1347 my $dmb = $$self{'base'};
937              
938             #
939             # Determine the zones that match all data.
940             #
941              
942 561         1006 my @zone;
943              
944 561         1043 while (1) {
945              
946             # No information
947              
948 561 100 100     2284 if (! $zone &&
      100        
949             ! $abbrev &&
950             ! $offset) {
951 3         11 my($z) = $self->_now('tz',1);
952 3         10 @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     2373 $dstflag = "dst" if ($offset && @$date && ! $dstflag);
      100        
962              
963 561         1024 my(@isdst);
964 561 100       4236 if ($dstflag eq 'stdonly') {
    100          
    100          
965 4         10 @isdst = (0);
966             } elsif ($dstflag eq 'dstonly') {
967 4         12 @isdst = (1);
968             } elsif ($dstflag eq 'dst') {
969 71         163 @isdst = (1,0);
970             } else {
971 482         1363 @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     3230 if ($zone && ! $abbrev) {
978 372 50 0     2026 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       1769 if ($zone) {
990             my $z = (exists $$self{'data'}{'Alias'}{$zone} ?
991 372 50       1910 $$self{'data'}{'Alias'}{$zone} : $zone);
992 372         1131 @zone = ($z);
993             }
994              
995             # $abbrev
996              
997 561 100       1702 if ($abbrev) {
998 118         206 my @abbrev_zones;
999 118 100       769 if (exists $$self{'data'}{'MyAbbrev'}{$abbrev}) {
    50          
1000 8         16 @abbrev_zones = @{ $$self{'data'}{'MyAbbrev'}{$abbrev} };
  8         24  
1001             } elsif (exists $$self{'data'}{'Abbrev'}{$abbrev}) {
1002 110         422 @abbrev_zones = @{ $$self{'data'}{'Abbrev'}{$abbrev} };
  110         669  
1003             }
1004              
1005 118         387 my @z;
1006 118         307 foreach my $isdst (@isdst) {
1007 236         977 my @tmp = $self->_check_abbrev_isdst($abbrev,$isdst,@abbrev_zones);
1008 236 100       779 if (@tmp) {
1009 155 100       497 if (@z) {
1010 37         181 @z = _list_add(\@z,\@tmp);
1011             } else {
1012 118         550 @z = @tmp;
1013             }
1014             }
1015             }
1016              
1017 118 50       513 if (@zone) {
1018 0         0 @zone = _list_union(\@z,\@zone);
1019             } else {
1020 118         572 @zone = @z;
1021             }
1022 118 50       689 last if (! @zone);
1023             }
1024              
1025             # $offset
1026              
1027 561 100       1746 if ($offset) {
1028 82 100       369 return undef if (! exists $$self{'data'}{'Offmod'}{$offset});
1029 81         337 $self->_offmod($offset);
1030              
1031 81         166 my @z;
1032 81         540 foreach my $isdst (@isdst) {
1033             my $tmp = $$self{'data'}{'MyOffsets'}{$offset}{$isdst} ||
1034 155   100     870 $$self{'data'}{'Offsets'}{$offset}{$isdst};
1035              
1036 155         241 my @tmp;
1037 155 100       334 if ($abbrev) {
1038 28         129 @tmp = $self->_check_offset_abbrev_isdst($offset,$abbrev,$isdst,$tmp);
1039             } else {
1040 127 100       662 @tmp = @$tmp if ($tmp);
1041             }
1042              
1043 155 100       406 if (@tmp) {
1044 134 100       287 if (@z) {
1045 55         241 @z = _list_add(\@z,\@tmp);
1046             } else {
1047 79         360 @z = @tmp;
1048             }
1049             }
1050             }
1051              
1052 81 100       274 if (@zone) {
1053 14         82 @zone = _list_union(\@zone,\@z);
1054             } else {
1055 67         297 @zone = @z;
1056             }
1057 81 100       320 last if (! @zone);
1058             }
1059              
1060             # $date
1061              
1062 558 100       1864 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         1012 my @tmp;
1071 533         1128 my $isdst = '';
1072 533 50       1476 $isdst = 0 if ($dstflag eq 'stdonly');
1073 533 50       1327 $isdst = 1 if ($dstflag eq 'dstonly');
1074              
1075             ZONE:
1076 533         1357 foreach my $z (@zone) {
1077 4129 100       16638 $self->_module($z) if (! exists $$self{'data'}{'Zones'}{$z}{'Loaded'});
1078 4129         9516 my $y = $$date[0];
1079 4129         9855 my @periods = $self->_all_periods($z,$y);
1080              
1081 4129         7623 foreach my $period (@periods) {
1082 7059 100 100     44641 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         4926 push(@tmp,$z);
1089 2234         5260 next ZONE;
1090             }
1091             }
1092 533         2029 @zone = @tmp;
1093 533 100       1855 last if (! @zone);
1094             }
1095              
1096 549         1360 last;
1097             }
1098              
1099             # Return the value/list
1100              
1101 560 100       1640 if (wantarray) {
1102 27         49 my @ret;
1103 27         59 foreach my $z (@zone) {
1104 69         187 push(@ret,$$self{'data'}{'ZoneNames'}{$z});
1105             }
1106 27         187 return @ret;
1107             }
1108              
1109 533 100       1613 return '' if (! @zone);
1110 524         4659 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   1045 my($self,$abbrev,$isdst,@zones) = @_;
1118              
1119 236         418 my @ret;
1120             ZONE:
1121 236         450 foreach my $zone (@zones) {
1122 3616 100       13911 $self->_module($zone) if (! exists $$self{'data'}{'Zones'}{$zone}{'Loaded'});
1123              
1124 3616         7220 foreach my $y (sort keys %{ $$self{'data'}{'Zones'}{$zone}{'Dates'} }) {
  3616         111838  
1125 167086         199071 my @periods = @{ $$self{'data'}{'Zones'}{$zone}{'Dates'}{$y} };
  167086         395357  
1126 167086         222225 foreach my $period (@periods) {
1127 321687         738741 my($dateUT,$dateLT,$off,$offref,$abb,$dst,$endUT,$endLT) = @$period;
1128 321687 100 100     814631 next if (lc($abbrev) ne lc($abb) ||
1129             $isdst != $dst);
1130 1845         3488 push(@ret,$zone);
1131 1845         9697 next ZONE;
1132             }
1133             }
1134             }
1135              
1136 236         1560 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   92 my($self,$offset,$abbrev,$isdst,$zones) = @_;
1144              
1145 28         52 my @ret;
1146 28         76 ZONE: foreach my $zone (@$zones) {
1147 804 100       3732 $self->_module($zone) if (! exists $$self{'data'}{'Zones'}{$zone}{'Loaded'});
1148              
1149 804         1503 foreach my $y (sort keys %{ $$self{'data'}{'Zones'}{$zone}{'Dates'} }) {
  804         24770  
1150 54253         65880 my @periods = @{ $$self{'data'}{'Zones'}{$zone}{'Dates'}{$y} };
  54253         130519  
1151 54253         74163 foreach my $period (@periods) {
1152 106098         241347 my($dateUT,$dateLT,$off,$offref,$abb,$dst,$endUT,$endLT) = @$period;
1153 106098 100 100     249152 next if (lc($abbrev) ne lc($abb) ||
      100        
1154             $offset ne $off ||
1155             $isdst != $dst);
1156 185         337 push(@ret,$zone);
1157 185         966 next ZONE;
1158             }
1159             }
1160             }
1161              
1162 28         162 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   45 my($list1,$list2) = @_;
1170 14         57 my(%list2) = map { $_,1 } @$list2;
  182         433  
1171 14         48 my(@ret);
1172 14         45 foreach my $ele (@$list1) {
1173 221 100       516 push(@ret,$ele) if (exists $list2{$ele});
1174             }
1175 14         90 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   259 my($list1,$list2) = @_;
1183 92         263 my(%list1) = map { $_,1 } @$list1;
  1109         2311  
1184 92         371 my(@ret) = @$list1;
1185 92         229 foreach my $ele (@$list2) {
1186 1793 100       3154 next if (exists $list1{$ele});
1187 1316         1720 push(@ret,$ele);
1188 1316         2439 $list1{$ele} = 1;
1189             }
1190 92         887 return @ret;
1191             }
1192              
1193             ########################################################################
1194             # PERIODS METHODS
1195             ########################################################################
1196              
1197             sub all_periods {
1198 7     7 1 111832 my($self,$zone,$year) = @_;
1199              
1200 7         19 my $z = $self->_zone($zone);
1201 7 50       19 if (! $z) {
1202 0         0 carp "ERROR: [periods] Invalid zone: $zone";
1203 0         0 return;
1204             }
1205 7         11 $zone = $z;
1206 7 50       24 $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         19 my @tmp = $self->_all_periods($zone,$year);
1211 7         13 my @ret;
1212 7         13 foreach my $ele (@tmp) {
1213             push(@ret,
1214 14         44 [ [ @{$$ele[0]} ],[ @{$$ele[1]} ],$$ele[2],[ @{$$ele[3]} ],$$ele[4],
  14         38  
  14         29  
1215 14         21 $$ele[5], [ @{$$ele[6]} ],[ @{$$ele[7]} ],$$ele[8],$$ele[9],
  14         30  
  14         57  
1216             $$ele[10],$$ele[11] ]);
1217             }
1218 7         21 return @ret;
1219             }
1220              
1221             sub _all_periods {
1222 34344     34344   63257 my($self,$zone,$year) = @_;
1223 34344         50596 $year += 0;
1224              
1225 34344 100       93299 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 3145         5187 my($ym1,$ym0);
1234 3145 100 100     10386 if ($year > $$self{'data'}{'LastYear'} &&
1235             exists $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'zone'}) {
1236 5         8 $ym1 = $year-1;
1237 5         11 $ym0 = $year;
1238              
1239             } else {
1240 3140         4631 foreach my $y (sort { $a <=> $b }
  1723990         1964155  
1241 3140         59535 keys %{ $$self{'data'}{'Zones'}{$zone}{'Dates'} }) {
1242 115161 100       175019 if ($y < $year) {
1243 112940         132788 $ym1 = $y;
1244 112940         144443 next;
1245             }
1246 2221 100       6624 $ym0 = $year if ($year == $y);
1247 2221         3787 last;
1248             }
1249             }
1250 3145 100       15982 $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 3145         4925 my(@periods);
1258              
1259             # $ym1 will be 0 in 0001
1260 3145 100       6242 if ($ym1) {
1261 3142         9380 my @tmp = $self->_periods($zone,$ym1);
1262 3142 50       11907 push(@periods,pop(@tmp)) if (@tmp);
1263             }
1264              
1265             #
1266             # Add on any periods from the current year.
1267             #
1268              
1269 3145 100       6777 if ($ym0) {
1270 1900         4735 push(@periods,$self->_periods($zone,$year));
1271             }
1272              
1273 3145         11114 $$self{'data'}{'Zones'}{$zone}{'AllDates'}{$year} = [ @periods ];
1274             }
1275              
1276 34344         45064 return @{ $$self{'data'}{'Zones'}{$zone}{'AllDates'}{$year} };
  34344         115096  
1277             }
1278              
1279             sub periods {
1280 8     8 1 96133 my($self,$zone,$year,$year1) = @_;
1281              
1282 8         18 my $z = $self->_zone($zone);
1283 8 50       22 if (! $z) {
1284 0         0 carp "ERROR: [periods] Invalid zone: $zone";
1285 0         0 return;
1286             }
1287 8         13 $zone = $z;
1288 8 100       25 $self->_module($zone) if (! exists $$self{'data'}{'Zones'}{$zone}{'Loaded'});
1289              
1290 8 100       17 if (! defined($year1)) {
1291 7         16 return $self->_periods($zone,$year);
1292             }
1293              
1294 1 50       6 $year = 1 if (! defined($year));
1295              
1296 1         2 my @ret;
1297 1         3 my $lastyear = $$self{'data'}{'LastYear'};
1298              
1299 1 50       4 if ($year <= $lastyear) {
1300 1         3 foreach my $y (sort { $a <=> $b }
  1112         1253  
1301 1         31 keys %{ $$self{'data'}{'Zones'}{$zone}{'Dates'} }) {
1302 4 100 66     19 last if ($y > $year1 || $y > $lastyear);
1303 3 50       8 next if ($y < $year);
1304 3         7 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 5052     5052   11045 my($self,$zone,$year) = @_;
1320 5052         7377 $year += 0;
1321              
1322 5052 100       14562 if (! exists $$self{'data'}{'Zones'}{$zone}{'Dates'}{$year}) {
1323              
1324 12         26 my @periods = ();
1325 12 100       35 if ($year > $$self{'data'}{'LastYear'}) {
1326             # Calculate periods using the LastRule method
1327 11         39 @periods = $self->_lastrule($zone,$year);
1328             }
1329              
1330 12         46 $$self{'data'}{'Zones'}{$zone}{'Dates'}{$year} = [ @periods ];
1331             }
1332              
1333             # A faster 'dclone' so we don't return the actual data
1334 5052         6846 my @ret;
1335 5052         6668 foreach my $ele (@{ $$self{'data'}{'Zones'}{$zone}{'Dates'}{$year} }) {
  5052         16191  
1336             push(@ret,
1337 9140         23972 [ [ @{$$ele[0]} ],[ @{$$ele[1]} ],$$ele[2],[ @{$$ele[3]} ],$$ele[4],$$ele[5],
  9140         19175  
  9140         18227  
1338 9140         12331 [ @{$$ele[6]} ],[ @{$$ele[7]} ],$$ele[8],$$ele[9],$$ele[10],$$ele[11] ]);
  9140         18974  
  9140         40637  
1339             }
1340 5052         12335 return @ret;
1341             }
1342              
1343             sub date_period {
1344 30208     30208 1 128186 my($self,$date,$zone,$wallclock,$isdst) = @_;
1345 30208 100       55484 $wallclock = 0 if (! $wallclock);
1346 30208 100       55698 $isdst = 0 if (! $isdst);
1347              
1348 30208         56754 my $z = $self->_zone($zone);
1349 30208 50       60398 if (! $z) {
1350 0         0 carp "ERROR: [date_period] Invalid zone: $zone";
1351 0         0 return;
1352             }
1353 30208         41860 $zone = $z;
1354 30208 100       71132 $self->_module($zone) if (! exists $$self{'data'}{'Zones'}{$zone}{'Loaded'});
1355              
1356 30208         43509 my $dmb = $$self{'base'};
1357 30208         57444 my @date = @$date;
1358 30208         42997 my $year = $date[0];
1359 30208         78781 my $dates= $dmb->_date_fields(@$date);
1360 30208 50 33     109940 return () if ($year < 0 || $year > 9999);
1361              
1362 30208 100       53065 if ($wallclock) {
1363             # A wallclock date
1364              
1365 24658         55208 my @period = $self->_all_periods($zone,$year);
1366 24658         45270 my $beg = $period[0]->[9];
1367 24658         37416 my $end = $period[-1]->[11];
1368 24658 50       65242 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         32697 my(@per);
1375 24658         42514 foreach my $period (@period) {
1376 69965         148159 my($begUT,$begLT,$offsetstr,$offset,$abbrev,$dst,$endUT,$endLT,
1377             $begUTs,$begLTs,$endUTs,$endLTs) = @$period;
1378 69965 100 100     194822 if (($dates cmp $begLTs) != -1 && ($dates cmp $endLTs) != 1) {
1379 24690         53373 push(@per,$period);
1380             }
1381             }
1382              
1383 24658 100       58217 if ($#per == -1) {
    100          
    50          
1384 8         38 return ();
1385             } elsif ($#per == 0) {
1386 24610         75804 return $per[0];
1387             } elsif ($#per == 1) {
1388 40 100       100 if ($per[0][5] == $isdst) {
1389 19         73 return $per[0];
1390             } else {
1391 21         80 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         12129 my @period = $self->_all_periods($zone,$year);
1402 5550         10128 foreach my $period (@period) {
1403 5759         14471 my($begUT,$begLT,$offsetstr,$offset,$abbrev,$isdst,$endUT,$endLT,
1404             $begUTs,$begLTs,$endUTs,$endLTs) = @$period;
1405 5759 100 66     20440 if (($dates cmp $begUTs) != -1 && ($dates cmp $endUTs) != 1) {
1406 5550         15510 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   54 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         31 %{ $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'} });
  22         129  
1436 22 50       56 return () if (! @mon);
1437              
1438             #
1439             # Analyze each time change.
1440             #
1441              
1442 22         36 my @dates = ();
1443 22         41 my $dmb = $$self{'base'};
1444              
1445 22         48 my $stdoff = $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'zone'}{'stdoff'};
1446 22         45 my $dstoff = $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'zone'}{'dstoff'};
1447              
1448 22         32 my (@period);
1449              
1450 22         42 foreach my $mon (@mon) {
1451             my $flag =
1452 33         81 $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'}{$mon}{'flag'};
1453             my $dow =
1454 33         61 $$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         246 $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'}{$mon}{'isdst'};
1459             my $time =
1460 33         72 $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'}{$mon}{'time'};
1461             my $type =
1462 33         59 $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'}{$mon}{'type'};
1463             my $abb =
1464 33         201 $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'}{$mon}{'abb'};
1465              
1466             # The end of the current period and the beginning of the next
1467 33         114 my($endUT,$endLT,$begUT,$begLT) =
1468             $dmb->_critical_date($year,$mon,$flag,$num,$dow,
1469             $isdst,$time,$type,$stdoff,$dstoff);
1470 33 100       134 return ($endUT,$endLT) if ($endonly);
1471              
1472 22 100       55 if (@period) {
1473 11         26 push(@period,$endUT,$endLT);
1474 11         28 push(@dates,[@period]);
1475             }
1476 22 100       48 my $offsetstr = ($isdst ? $dstoff : $stdoff);
1477 22         62 my $offset = $dmb->split('offset',$offsetstr);
1478              
1479 22         103 @period = ($begUT,$begLT,$offsetstr,$offset,$abb,$isdst);
1480             }
1481              
1482 11         60 push(@period,$self->_lastrule($zone,$year+1,1));
1483 11         31 push(@dates,[@period]);
1484              
1485 11         29 foreach my $period (@dates) {
1486 22         52 my($begUT,$begLT,$offsetstr,$offset,$abbrev,$dst,$endUT,$endLT) = @$period;
1487 22         55 my $begUTstr = $dmb->join("date",$begUT);
1488 22         56 my $begLTstr = $dmb->join("date",$begLT);
1489 22         58 my $endUTstr = $dmb->join("date",$endUT);
1490 22         61 my $endLTstr = $dmb->join("date",$endLT);
1491 22         105 $period = [$begUT,$begLT,$offsetstr,$offset,$abbrev,$dst,$endUT,$endLT,
1492             $begUTstr,$begLTstr,$endUTstr,$endLTstr];
1493             }
1494              
1495 11         62 return @dates;
1496             }
1497              
1498             ########################################################################
1499             # CONVERSION
1500             ########################################################################
1501              
1502             sub convert {
1503 41     41 1 64153 my($self,$date,$from,$to,$isdst) = @_;
1504 41         144 $self->_convert('convert',$date,$from,$to,$isdst);
1505             }
1506              
1507             sub convert_to_gmt {
1508 2860     2860 1 26527 my($self,$date,@arg) = @_;
1509 2860         7111 my($err,$from,$isdst) = _convert_args('convert_to_gmt',@arg);
1510 2860 50       6797 return (1) if ($err);
1511              
1512 2860         4674 my $dmb = $$self{'base'};
1513              
1514 2860 50       5901 if (! $from) {
1515 0         0 $from = $self->_now('tz',1);
1516             }
1517 2860         7393 $self->_convert('convert_to_gmt',$date,$from,'GMT',$isdst);
1518             }
1519              
1520             sub convert_from_gmt {
1521 12     12 1 18331 my($self,$date,@arg) = @_;
1522 12         47 my($err,$to,$isdst) = _convert_args('convert_from_gmt',@arg);
1523 12 50       38 return (1) if ($err);
1524              
1525 12         154 my $dmb = $$self{'base'};
1526              
1527 12 100       33 if (! $to) {
1528 7         41 $to = $self->_now('tz',1);
1529             }
1530 12         64 $self->_convert('convert_from_gmt',$date,'GMT',$to,$isdst);
1531             }
1532              
1533             sub convert_to_local {
1534 27     27 1 91 my($self,$date,@arg) = @_;
1535 27         96 my($err,$from,$isdst) = _convert_args('convert_to_local',@arg);
1536 27 50       88 return (1) if ($err);
1537              
1538 27         65 my $dmb = $$self{'base'};
1539              
1540 27 50       90 if (! $from) {
1541 0         0 $from = 'GMT';
1542             }
1543 27         88 $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   6301 my($caller,@args) = @_;
1561              
1562 2899 100       9150 if ($#args == -1) {
    100          
    50          
1563 7         25 return (0,'',0);
1564             } elsif ($#args == 0) {
1565 176 50 33     1510 if ($args[0] eq '0' ||
1566             $args[0] eq '1') {
1567 0         0 return (0,'',$args[0]);
1568             } else {
1569 176         976 return (0,$args[0],0);
1570             }
1571             } elsif ($#args == 1) {
1572 2716         8079 return (0,@args);
1573             } else {
1574 0         0 return (1,'',0);
1575             }
1576             }
1577              
1578             sub _convert {
1579 5736     5736   12750 my($self,$caller,$date,$from,$to,$isdst) = @_;
1580 5736         9286 my $dmb = $$self{'base'};
1581              
1582             # Handle $date as a reference and a string
1583 5736         7661 my (@date);
1584 5736 100       11811 if (ref($date)) {
1585 5735         11511 @date = @$date;
1586             } else {
1587 1         4 @date = @{ $dmb->split('date',$date) };
  1         23  
1588 1         4 $date = [@date];
1589             }
1590              
1591 5736 50       12048 if ($from ne $to) {
1592 5736         12200 my $tmp = $self->_zone($from);
1593 5736 50       12236 if (! $tmp) {
1594 0         0 return (2);
1595             }
1596 5736         8484 $from = $tmp;
1597              
1598 5736         9933 $tmp = $self->_zone($to);
1599 5736 50       11674 if (! $tmp) {
1600 0         0 return (3);
1601             }
1602 5736         9300 $to = $tmp;
1603             }
1604              
1605 5736 100       11299 if ($from eq $to) {
1606 187         719 my $per = $self->date_period($date,$from,1,$isdst);
1607 187         538 my $offset = $$per[3];
1608 187         476 my $abb = $$per[4];
1609 187         949 return (0,$date,$offset,$isdst,$abb);
1610             }
1611              
1612             # Convert $date from $from to GMT
1613              
1614 5549 50       11227 if ($from ne "Etc/GMT") {
1615 5549         12565 my $per = $self->date_period($date,$from,1,$isdst);
1616 5549 100       11700 if (! $per) {
1617 2         9 return (4);
1618             }
1619 5547         8152 my $offset = $$per[3];
1620 5547         7203 @date = @{ $dmb->calc_date_time(\@date,$offset,1) };
  5547         16877  
1621             }
1622              
1623             # Convert $date from GMT to $to
1624              
1625 5547         11418 $isdst = 0;
1626 5547         9414 my $offset = [0,0,0];
1627 5547         8585 my $abb = 'GMT';
1628              
1629 5547 50       11941 if ($to ne "Etc/GMT") {
1630 5547         13667 my $per = $self->date_period([@date],$to,0);
1631 5547         12196 $offset = $$per[3];
1632 5547         7905 $isdst = $$per[5];
1633 5547         7902 $abb = $$per[4];
1634 5547         7234 @date = @{ $dmb->calc_date_time(\@date,$offset) };
  5547         14928  
1635             }
1636              
1637 5547         26131 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   1333 my($self,$re) = @_;
1664 544 100       2743 return $$self{'data'}{$re} if (defined $$self{'data'}{$re});
1665              
1666             # Zone name
1667              
1668 79         184 my @zone;
1669 79 50       350 if (exists $ENV{'DATE_MANIP_DEBUG_ZONES'}) {
1670 0         0 @zone = split(/\s+/,$ENV{'DATE_MANIP_DEBUG_ZONES'});
1671             } else {
1672 79         14556 @zone = (keys %{ $$self{'data'}{'Alias'} },
1673 79         177 keys %{ $$self{'data'}{'MyAlias'} });
  79         1528  
1674             }
1675 79         1560 @zone = sort _sortByLength(@zone);
1676 79         442 foreach my $zone (@zone) {
1677 63200         99105 $zone =~ s/\057/\\057/g; # /
1678 63200         82612 $zone =~ s/\055/\\055/g; # -
1679 63200         75074 $zone =~ s/\056/\\056/g; # .
1680 63200         73831 $zone =~ s/\050/\\050/g; # (
1681 63200         73319 $zone =~ s/\051/\\051/g; # )
1682 63200         81870 $zone =~ s/\053/\\053/g; # +
1683             }
1684              
1685 79         7392 my $zone = join('|',@zone);
1686 79         266652 $zone = qr/(?$zone)/i;
1687              
1688             # Abbreviation
1689              
1690 79         3138 my @abb;
1691 79 50       566 if (exists $ENV{'DATE_MANIP_DEBUG_ABBREVS'}) {
1692 0         0 @abb = split(/\s+/,$ENV{'DATE_MANIP_DEBUG_ABBREVS'});
1693             } else {
1694 79         4227 @abb = (keys %{ $$self{'data'}{'Abbrev'} },
1695 79         243 keys %{ $$self{'data'}{'MyAbbrev'} });
  79         531  
1696             }
1697 79         653 @abb = sort _sortByLength(@abb);
1698 79         374 foreach my $abb (@abb) {
1699 13274         18108 $abb =~ s/\055/\\055/g; # -
1700 13274         17790 $abb =~ s/\053/\\053/g; # +
1701             }
1702              
1703 79         1117 my $abb = join('|',@abb);
1704 79         37252 $abb = qr/(?$abb)/i;
1705              
1706             # Offset (+HH, +HHMM, +HH:MM, +HH:MM:SS, +HHMMSS)
1707              
1708 79         1241 my($hr) = qr/(?:[0-1][0-9]|2[0-3])/; # 00 - 23
1709 79         342 my($mn) = qr/(?:[0-5][0-9])/; # 00 - 59
1710 79         325 my($ss) = qr/(?:[0-5][0-9])/; # 00 - 59
1711              
1712 79         5065 my($off) = qr/ (? [+-] (?: $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         242018 $$self{'data'}{'namerx'} = qr/(?$zone)/;
1731 79         42429 $$self{'data'}{'abbrx'} = qr/(?$abb)/;
1732 79         279015 $$self{'data'}{'zonerx'} = qr/(?(?:$abb|$zone))/;
1733 79         10291 $$self{'data'}{'offrx'} = qr/(?$off)/;
1734 79         30425 $$self{'data'}{'offabbrx'} = qr/(?$off\s+$abb)/;
1735 79         28627 $$self{'data'}{'offparrx'} = qr/(?$off\s*\($abb\))/;
1736 79         335717 $$self{'data'}{'zrx'} = qr/(?(?:$off\s*\($abb\)|$off\s+$abb|$abb|$zone|$off))/;
1737              
1738 79         9662 return $$self{'data'}{$re};
1739             }
1740              
1741             # This sorts from longest to shortest element
1742             #
1743 168     168   1580 no strict 'vars';
  168         479  
  168         13938  
1744             sub _sortByLength {
1745 578016     578016   678402 return (length $b <=> length $a);
1746             }
1747 168     168   1319 use strict 'vars';
  168         444  
  168         201132  
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   1348 my($self,$var,$val) = @_;
1758              
1759 384 50       2443 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         802 my $err = $self->_config_var_setdate($val,0);
1767 169 50       597 return if ($err);
1768 169         774 $$self{'data'}{'sections'}{'conf'}{'forcedate'} = 0;
1769 169         408 $val = 1;
1770              
1771             } elsif ($var eq 'forcedate') {
1772 188         869 my $err = $self->_config_var_setdate($val,1);
1773 188 50       752 return if ($err);
1774 188         913 $$self{'data'}{'sections'}{'conf'}{'setdate'} = 0;
1775 188         517 $val = 1;
1776              
1777             } elsif ($var eq 'configfile') {
1778 27         342 $self->_config_file($val);
1779 27         253 return;
1780             }
1781              
1782 357         925 my $base = $$self{'base'};
1783 357         1099 $$base{'data'}{'sections'}{'conf'}{$var} = $val;
1784 357         1961 return;
1785             }
1786              
1787             sub _config_var_setdate {
1788 357     357   1047 my($self,$val,$force) = @_;
1789 357         897 my $base = $$self{'base'};
1790              
1791 357         1892 my $dstrx = qr/(?:,\s*(stdonly|dstonly|std|dst))?/i;
1792 357         1295 my $zonrx = qr/,\s*(.+)/;
1793 357         1249 my $da1rx = qr/(\d\d\d\d)(\d\d)(\d\d)(\d\d):(\d\d):(\d\d)/;
1794 357         1110 my $da2rx = qr/(\d\d\d\d)\-(\d\d)\-(\d\d)\-(\d\d):(\d\d):(\d\d)/;
1795 357         914 my $time = time;
1796              
1797 357         877 my($op,$date,$dstflag,$zone,@date,$offset,$abb);
1798              
1799             #
1800             # Parse the argument
1801             #
1802              
1803 357 100 33     19863 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         802 $op = 'nowzone';
1809 267         1247 ($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         352 $op = 'datezone';
1826 90         280 my($y,$m,$d,$h,$mn,$s);
1827 90         964 ($y,$m,$d,$h,$mn,$s,$dstflag,$zone) = ($1,$2,$3,$4,$5,$6,$7,$8);
1828 90         436 $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       1383 $dstflag = 'std' if (! $dstflag);
1855              
1856             #
1857             # Get the date we're setting 'now' to
1858             #
1859              
1860 357 100       1319 if ($op eq 'nowzone') {
    50          
1861             # Use the system localtime
1862              
1863 267         6324 my($s,$mn,$h,$d,$m,$y) = localtime($time);
1864 267         1242 $y += 1900;
1865 267         521 $m++;
1866 267         1012 $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       1107 if ($zone) {
1882 357         767 my ($err,@args);
1883 357         967 my $dmb = $$self{'base'};
1884 357 50       1221 $date = [] if (! defined $date);
1885 357         2524 $zone = $self->__zone($date,'',lc($zone),'',lc($dstflag));
1886 357 50       1403 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         800 my($isdst,@isdst);
1900 357 50       1156 if ($dstflag eq 'std') {
    0          
    0          
1901 357         1020 @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     1860 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         643 my $per;
1916 357         854 foreach my $dst (@isdst) {
1917 714 100       1809 next if ($per);
1918 357         1636 $per = $self->date_period($date,$zone,1,$dst);
1919             }
1920              
1921 357 50       1104 if (! $per) {
1922 0         0 carp "ERROR: [config_var] invalid date: SetDate: $date, $zone";
1923 0         0 return 1;
1924             }
1925 357         866 $isdst = $$per[5];
1926 357         773 $abb = $$per[4];
1927 357         782 $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         1226 $$base{'data'}{'now'}{'date'} = $date;
1945 357         1280 $$base{'data'}{'now'}{'tz'} = $self->_zone($zone);
1946 357         1175 $$base{'data'}{'now'}{'isdst'} = $isdst;
1947 357         1055 $$base{'data'}{'now'}{'abb'} = $abb;
1948 357         1326 $$base{'data'}{'now'}{'offset'} = $offset;
1949              
1950             #
1951             # Treate SetDate/ForceDate
1952             #
1953              
1954 357 100       1189 if ($force) {
1955 188         565 $$base{'data'}{'now'}{'force'} = 1;
1956 188         602 $$base{'data'}{'now'}{'set'} = 0;
1957             } else {
1958 169         549 $$base{'data'}{'now'}{'force'} = 0;
1959 169         613 $$base{'data'}{'now'}{'set'} = 1;
1960 169         580 $$base{'data'}{'now'}{'setsecs'} = $time;
1961 169         996 my($err,$setdate) = $self->convert_to_gmt($date,$zone);
1962 169         612 $$base{'data'}{'now'}{'setdate'} = $setdate;
1963             }
1964              
1965 357         2356 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: