File Coverage

lib/Date/Manip/TZ.pm
Criterion Covered Total %
statement 700 967 72.3
branch 285 550 51.8
condition 76 129 58.9
subroutine 50 61 81.9
pod 16 16 100.0
total 1127 1723 65.4


line stmt bran cond sub pod time code
1             package Date::Manip::TZ;
2             # Copyright (c) 2008-2022 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   1202 use Date::Manip::Obj;
  168         347  
  168         5005  
15 168     168   926 use Date::Manip::TZ_Base;
  168         406  
  168         6097  
16             @ISA = qw(Date::Manip::Obj Date::Manip::TZ_Base);
17              
18             require 5.010000;
19 168     168   1015 use warnings;
  168         397  
  168         4089  
20 168     168   843 use strict;
  168         372  
  168         3238  
21              
22 168     168   773 use IO::File;
  168         376  
  168         30174  
23             require Date::Manip::Zones;
24 168     168   1291 use Date::Manip::Base;
  168         375  
  168         4280  
25 168     168   108409 use Data::Dumper;
  168         1090729  
  168         9925  
26 168     168   1299 use Carp;
  168         426  
  168         81263  
27              
28             our $VERSION;
29             $VERSION='6.90';
30 168     168   1152 END { undef $VERSION; }
31              
32             # To get rid of a 'used only once' warnings.
33             END {
34 168     168   580 my $tmp = \%Date::Manip::Zones::Module;
35 168         528 $tmp = \%Date::Manip::Zones::ZoneNames;
36 168         532 $tmp = \%Date::Manip::Zones::Alias;
37 168         463 $tmp = \%Date::Manip::Zones::Abbrev;
38 168         419 $tmp = \%Date::Manip::Zones::Offmod;
39 168         435 $tmp = $Date::Manip::Zones::FirstDate;
40 168         383 $tmp = $Date::Manip::Zones::LastDate;
41 168         432 $tmp = $Date::Manip::Zones::LastYear;
42 168         377 $tmp = $Date::Manip::Zones::TzcodeVersion;
43 168         676 $tmp = $Date::Manip::Zones::TzdataVersion;
44             }
45              
46             ########################################################################
47             # BASE METHODS
48             ########################################################################
49              
50             sub _init {
51 509     509   1996 my($self) = @_;
52              
53 509         12169 $$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         2303 my $dmb = $$self{'base'};
98 509         3330 my $os = $dmb->_os();
99              
100 509 50       2674 if ($os eq 'Unix') {
    0          
    0          
101 509         2445 $$self{'data'}{'path'} = '/bin:/usr/bin';
102 509         8741 $$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   2913 my($self) = @_;
158              
159 512         2714 $self->_set_curr_zone();
160             }
161              
162 168     168   1457 no strict 'refs';
  168         476  
  168         51389  
163             # This loads data from an offset module
164             #
165             sub _offmod {
166 91     91   244 my($self,$offset) = @_;
167 91 100       364 return if (exists $$self{'data'}{'Offsets'}{$offset});
168              
169 34         120 my $mod = $$self{'data'}{'Offmod'}{$offset};
170 34         3119 eval "require Date::Manip::Offset::${mod}";
171 34         159 my %off = %{ "Date::Manip::Offset::${mod}::Offset" };
  34         326  
172              
173 34         241 $$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   4290 my($self,$zone) = @_;
180 1635 50       5029 return if (exists $$self{'data'}{'Zones'}{$zone}{'Loaded'});
181              
182 1635         6066 my $mod = $$self{'data'}{'Module'}{$zone};
183 1635         123879 eval "require Date::Manip::TZ::${mod}";
184 1635         8693 my %dates = %{ "Date::Manip::TZ::${mod}::Dates" };
  1635         48882  
185 1635         7787 my %last = %{ "Date::Manip::TZ::${mod}::LastRule" };
  1635         7532  
186 1635         54157 $$self{'data'}{'Zones'}{$zone} =
187             {
188             'Dates' => { %dates },
189             'LastRule' => { %last },
190             'Loaded' => 1
191             };
192             }
193 168     168   1371 use strict 'refs';
  168         421  
  168         269977  
194              
195             ########################################################################
196             # CHECKING/MODIFYING ZONEINFO DATA
197             ########################################################################
198              
199             sub _zone {
200 48041     48041   80396 my($self,$zone) = @_;
201 48041         80810 $zone = lc($zone);
202              
203 48041 100       134299 if (exists $$self{'data'}{'MyAlias'}{$zone}) {
    100          
204 1         5 return $$self{'data'}{'MyAlias'}{$zone};
205             } elsif (exists $$self{'data'}{'Alias'}{$zone}) {
206 47990         108208 return $$self{'data'}{'Alias'}{$zone};
207             } else {
208 50         420 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 1037 my($self,$alias,$zone) = @_;
224 2         4 $alias = lc($alias);
225              
226 2 100       7 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       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       4 return 1 if (! $zone);
240 1         3 $$self{'data'}{'MyAlias'}{$alias} = $zone;
241 1         4 $$self{'data'}{'zonerx'} = undef;
242 1         2 return 0;
243             }
244              
245             sub define_abbrev {
246 6     6 1 3613 my($self,$abbrev,@zone) = @_;
247 6         12 $abbrev = lc($abbrev);
248              
249 6 100       21 if ($abbrev eq 'reset') {
250 2         9 $$self{'data'}{'MyAbbrev'} = {};
251 2         6 $$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         3 delete $$self{'data'}{'MyAbbrev'}{$abbrev};
256 1         2 $$self{'data'}{'abbrx'} = undef;
257 1         2 return (0);
258             }
259              
260 3 50       18 if (! exists $$self{'data'}{'Abbrev'}{$abbrev}) {
261 0         0 return (1);
262             }
263              
264 3         6 my (@z,%z);
265 3         7 my %zone = map { $_,1 } @{ $$self{'data'}{'Abbrev'}{$abbrev} };
  50         107  
  3         12  
266 3         12 foreach my $z (@zone) {
267 5         15 my $zone = $self->_zone($z);
268 5 50       15 return (2,$z) if (! $zone);
269 5 50       15 return (3,$z) if (! exists $zone{$zone});
270 5 50       14 next if (exists $z{$zone});
271 5         10 $z{$zone} = 1;
272 5         13 push(@z,$zone);
273             }
274              
275 3         12 $$self{'data'}{'MyAbbrev'}{$abbrev} = [ @z ];
276 3         7 $$self{'data'}{'abbrx'} = undef;
277 3         13 return ();
278             }
279              
280             sub define_offset {
281 22     22 1 14961 my($self,$offset,@args) = @_;
282 22         35 my $dmb = $$self{'base'};
283              
284 22 100       56 if (lc($offset) eq 'reset') {
285 10         26 $$self{'data'}{'MyOffsets'} = {};
286 10         22 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       28 if (ref($offset)) {
297 0         0 $offset = $dmb->join('offset',$offset);
298             } else {
299 12         40 $offset = $dmb->_delta_convert('offset',$offset);
300             }
301 12 100       27 return (9) if (! $offset);
302 11 100       46 return (1) if (! exists $$self{'data'}{'Offmod'}{$offset});
303              
304 10         32 $self->_offmod($offset);
305              
306             # Find out whether we're handling STD, DST, or both.
307              
308 10         27 my(@isdst) = (0,1);
309 10 50       53 if ($args[0] =~ /^std|dst|stdonly|dstonly$/i) {
310 10         25 my $tmp = lc(shift(@args));
311 10 100       34 if ($tmp eq 'stdonly') {
    100          
312 3         10 @isdst = (0);
313             } elsif ($tmp eq 'dstonly') {
314 2         4 @isdst = (1);
315             }
316             }
317 10         19 my @zone = @args;
318              
319 10 100 100     45 if ($#isdst == 0 &&
320             ! exists($$self{'data'}{'Offsets'}{$offset}{$isdst[0]})) {
321 2         8 return (2);
322             }
323              
324             # Check to see that each zone is valid, and contains this offset.
325              
326 8         15 my %tmp;
327 8         17 foreach my $isdst (0,1) {
328 16 50       44 next if (! exists $$self{'data'}{'Offsets'}{$offset}{$isdst});
329 16         22 my @z = @{ $$self{'data'}{'Offsets'}{$offset}{$isdst} };
  16         51  
330 16         27 $tmp{$isdst} = { map { $_,1 } @z };
  182         373  
331             }
332              
333 8         22 foreach my $z (@zone) {
334 15         25 my $lcz = lc($z);
335 15 100 100     110 if (! exists $$self{'data'}{'ZoneNames'}{$lcz}) {
    100 100        
    100          
336 2         12 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         23 $z = $lcz;
345             }
346              
347             # Set the zones accordingly.
348              
349 3         25 foreach my $isdst (@isdst) {
350 6         12 my @z;
351 6         11 foreach my $z (@zone) {
352 10 100       28 push(@z,$z) if (exists $tmp{$isdst}{$z});
353             }
354 6         21 $$self{'data'}{'MyOffsets'}{$offset}{$isdst} = [ @z ];
355             }
356              
357 3         27 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   1862 my($self) = @_;
389 512         1912 my $dmb = $$self{'base'};
390 512         4104 my $currzone = $self->_get_curr_zone();
391              
392 512         1344 $$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   1099 my($self) = @_;
400 512         1827 my $dmb = $$self{'base'};
401              
402 512         1834 my $t = time;
403 512         22600 my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($t);
404 512         1907 my $currzone = '';
405 512 50       1521 my $dstflag = ($isdst ? 'dstonly' : 'stdonly');
406              
407 512         933 my (@methods) = @{ $$self{'data'}{'methods'} };
  512         4718  
408 512 50       2013 my $debug = ($ENV{DATE_MANIP_DEBUG} ? 1 : 0);
409              
410             defined $$self{'data'}{'path'}
411 512 50       5038 and local $ENV{PATH} = $$self{'data'}{'path'};
412              
413             METHOD:
414 512         1686 while (@methods) {
415 2000         3411 my $method = shift(@methods);
416 2000         3105 my @zone = ();
417              
418 2000 50       3656 print "*** DEBUG *** METHOD: $method [" if ($debug);
419              
420 2000 100       6348 if ($method eq 'main') {
    100          
    50          
    0          
    0          
    0          
    0          
    0          
421              
422 512 50       1555 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         1036 my $var = shift(@methods);
428 512 50       1341 print "$var] " if ($debug);
429 168     168   1482 no strict "refs";
  168         430  
  168         9303  
430 512         838 my $val = ${ "::$var" };
  512         2416  
431 168     168   1176 use strict "refs";
  168         416  
  168         402414  
432 512 50       1413 if (defined $val) {
433 0         0 push(@zone,$val);
434 0 0       0 print "$val\n" if ($debug);
435             } else {
436 512 50       1459 print "undef\n" if ($debug);
437             }
438              
439             } elsif ($method eq 'env') {
440 512 50       1595 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         1304 my $type = lc( shift(@methods) );
446 512 50       1258 print "$type," if ($debug);
447              
448 512 50 33     1794 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         1355 my $var = shift(@methods);
456 512 50       1209 print "$var] " if ($debug);
457 512 100       1468 if (exists $ENV{$var}) {
458 24 50       49 if ($type eq 'zone') {
459 24         51 push(@zone,$ENV{$var});
460 24 50       49 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       1217 print "undef\n" if ($debug);
471             }
472              
473             } elsif ($method eq 'file') {
474 976 50       2239 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         1630 my $file = shift(@methods);
480 976 50       3592 print "$file] " if ($debug);
481 976 100       15870 if (! -f $file) {
482 488 50       1522 print "not found\n" if ($debug);
483 488         2108 next;
484             }
485              
486 488         4897 my $in = new IO::File;
487 488 50       24146 $in->open($file) || next;
488 488         27298 my $firstline = 1;
489              
490 488         1079 my @z;
491 488         3446 while (! $in->eof) {
492 488         18235 my $line = <$in>;
493 488         1464 chomp($line);
494 488 50 33     4825 next if ($line =~ /^\s*\043/ ||
495             $line =~ /^\s*$/);
496 488 50       2129 if ($firstline) {
497 488         1012 $firstline = 0;
498 488         2318 $line =~ s/^\s*//;
499 488         2615 $line =~ s/\s*$//;
500 488         1302 $line =~ s/["']//g; # "
501 488         1042 $line =~ s/\s+/_/g;
502 488         1368 @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       2777 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         8641 close(IN);
556              
557 488 50       1939 push(@zone,@z) if (@z);
558              
559 488 50       8555 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         4714 while (@zone) {
672 512         1821 my $zone = lc(shift(@zone));
673              
674             # OpenUNIX puts a colon at the start
675 512         1590 $zone =~ s/^://;
676              
677             # If we got a zone name/alias
678 512         2064 $currzone = $self->_zone($zone);
679 512 50       2118 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       1342 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         3615 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 0 0       0 return $zone if (exists $Date::Manip::Zones::ZoneNames{lc($zone)});
766 0         0 return;
767             }
768              
769             sub _zoneinfo_file_slurp {
770 0     0   0 my($file) = @_;
771 0 0       0 open my $fh, '<', $file
772             or return;
773 0         0 binmode $fh;
774 0         0 local $/ = undef;
775 0         0 return <$fh>;
776             }
777              
778             sub _windows_registry_val {
779 0     0   0 my($self) = @_;
780              
781 0         0 require Win32::TieRegistry;
782              
783 0 0       0 my $lmachine = new Win32::TieRegistry 'LMachine',
784             { Access => Win32::TieRegistry::KEY_READ(),
785             Delimiter => '/' }
786             or return '';
787              
788 0         0 my $tzinfo = $lmachine->Open('SYSTEM/CurrentControlSet/Control/TimeZoneInformation/');
789              
790             #
791             # Windows Vista, Windows 2008 Server
792             #
793              
794 0         0 my $tzkn = $tzinfo->GetValue('TimeZoneKeyName');
795 0 0 0     0 if (defined($tzkn) && $tzkn) {
796             # For some reason, Vista is tacking on a bunch of stuff at the
797             # end of the timezone, starting with a chr(0). Strip it off.
798              
799 0         0 my $c = chr(0);
800 0         0 my $i = index($tzkn,$c);
801 0 0       0 if ($i != -1) {
802 0         0 $tzkn = substr($tzkn,0,$i);
803             }
804 0         0 my $z = $self->_zone($tzkn);
805 0 0       0 return $z if ($z);
806             }
807              
808             #
809             # Windows NT, Windows 2000, Windows XP, Windows 2003 Server
810             #
811              
812 0         0 my $stdnam = $tzinfo->GetValue('StandardName');
813 0         0 my $z = $self->_zone($stdnam);
814 0 0       0 return $z if ($z);
815              
816             #
817             # For non-English versions, we have to determine which timezone it
818             # actually is.
819             #
820              
821 0         0 my $atz = $lmachine->Open('SOFTWARE/Microsoft/Windows NT/CurrentVersion/Time Zones/');
822 0 0 0     0 if (! defined($atz) || ! $atz) {
823 0         0 $atz = $lmachine->Open('SOFTWARE/Microsoft/Windows/CurrentVersion/Time Zones/');
824             }
825              
826 0 0 0     0 return "" if (! defined($atz) || ! $atz);
827              
828 0         0 foreach my $z ($atz->SubKeyNames()) {
829 0         0 my $tmp = $atz->Open("$z/");
830 0         0 my $znam = $tmp->GetValue('Std');
831 0 0       0 return $z if ($znam eq $stdnam);
832             }
833             }
834              
835             # End of DateTime-TimeZone section
836             #######################
837              
838             # We will be testing commands that don't exist on all architectures,
839             # so disable warnings.
840             #
841 168     168   1578 no warnings;
  168         447  
  168         26969  
842             sub _cmd {
843 0     0   0 my($cmd) = @_;
844 0         0 local(*IN);
845 0 0       0 open(IN,"$cmd |") || return ();
846 0         0 my @out = ;
847 0         0 close(IN);
848 0         0 chomp(@out);
849 0         0 return @out;
850             }
851 168     168   1357 use warnings;
  168         515  
  168         879484  
852              
853             ########################################################################
854             # DETERMINING A TIMEZONE
855             ########################################################################
856              
857             sub zone {
858 62     62 1 33605 my($self,@args) = @_;
859 62         115 my $dmb = $$self{'base'};
860 62 100       167 if (! @args) {
861 1         22 my($tz) = $self->_now('tz',1);
862 1         7 return $$self{'data'}{'ZoneNames'}{$tz}
863             }
864              
865             # Parse the arguments
866              
867 61         179 my($zone,$abbrev,$offset,$dstflag) = ('','','','');
868 61         111 my $date = [];
869 61         87 my $tmp;
870 61         119 foreach my $arg (@args) {
871              
872 106 100       272 if (ref($arg) eq 'ARRAY') {
    50          
873 34 50       95 if ($#$arg == 5) {
    0          
874             # [Y,M,D,H,Mn,S]
875 34 50       81 return undef if (@$date);
876 34         70 $date = $arg;
877              
878             } elsif ($#$arg == 2) {
879             # [H,Mn,S]
880 0 0       0 return undef if ($offset);
881 0         0 $offset = $dmb->join('offset',$arg);
882 0 0       0 return undef if (! $offset);
883              
884             } else {
885 0         0 return undef;
886             }
887              
888             } elsif (ref($arg)) {
889 0         0 return undef;
890              
891             } else {
892 72         146 $arg = lc($arg);
893              
894 72 100 66     359 if ($arg =~ /^(std|dst|stdonly|dstonly)$/) {
    100          
    100          
    50          
    100          
    50          
895 11 50       28 return undef if ($dstflag);
896 11         25 $dstflag = $arg;
897              
898             } elsif ($tmp = $self->_zone($arg)) {
899 15 50       39 return undef if ($zone);
900 15         33 $zone = $tmp;
901              
902             } elsif (exists $$self{'data'}{'MyAbbrev'}{$arg} ||
903             exists $$self{'data'}{'Abbrev'}{$arg}) {
904 13 50       57 return undef if ($abbrev);
905 13         36 $abbrev = $arg;
906             } elsif (exists $$self{'data'}{'Abbrev'}{$arg}) {
907 0 0       0 return undef if ($abbrev);
908 0         0 $abbrev = $arg;
909              
910             } elsif ($tmp = $dmb->split('offset',$arg)) {
911 30 50       83 return undef if ($offset);
912 30         99 $offset = $dmb->_delta_convert('offset',$arg);
913              
914             } elsif ($tmp = $dmb->split('date',$arg)) {
915 0 0       0 return undef if ($date);
916 0         0 $date = $tmp;
917              
918             } else {
919 3         12 return undef;
920             }
921             }
922             }
923              
924 58         165 return $self->__zone($date,$offset,$zone,$abbrev,$dstflag);
925             }
926              
927             # $date = [Y,M,D,H,Mn,S]
928             # $offset = '-HH:Mn:SS'
929             # $zone = 'us/eastern' (lowercase)
930             # $abbrev = 'est' (lowercase)
931             # $dstflag= 'stdonly' (lowercase)
932             #
933             sub __zone {
934 561     561   2235 my($self,$date,$offset,$zone,$abbrev,$dstflag) = @_;
935 561         1326 my $dmb = $$self{'base'};
936              
937             #
938             # Determine the zones that match all data.
939             #
940              
941 561         1029 my @zone;
942              
943 561         1005 while (1) {
944              
945             # No information
946              
947 561 100 100     2354 if (! $zone &&
      100        
948             ! $abbrev &&
949             ! $offset) {
950 3         14 my($z) = $self->_now('tz',1);
951 3         9 @zone = (lc($z));
952             }
953              
954             # $dstflag
955             #
956             # $dstflag is "dst' if
957             # zone is passed in as an offset
958             # date is passed in
959              
960 561 100 100     2374 $dstflag = "dst" if ($offset && @$date && ! $dstflag);
      100        
961              
962 561         1051 my(@isdst);
963 561 100       3930 if ($dstflag eq 'stdonly') {
    100          
    100          
964 4         12 @isdst = (0);
965             } elsif ($dstflag eq 'dstonly') {
966 4         15 @isdst = (1);
967             } elsif ($dstflag eq 'dst') {
968 71         291 @isdst = (1,0);
969             } else {
970 482         1334 @isdst = (0,1);
971             }
972              
973             # We may pass in $zone and not $abbrev when it really should be
974             # $abbrev.
975              
976 561 100 66     3283 if ($zone && ! $abbrev) {
977 372 50 0     2031 if (exists $$self{'data'}{'Alias'}{$zone}) {
    0          
978             # no change
979             } elsif (exists $$self{'data'}{'MyAbbrev'}{$zone} ||
980             exists $$self{'data'}{'Abbrev'}{$zone}) {
981 0         0 $abbrev = $zone;
982 0         0 $zone = '';
983             }
984             }
985              
986             # $zone
987              
988 561 100       1719 if ($zone) {
989             my $z = (exists $$self{'data'}{'Alias'}{$zone} ?
990 372 50       1767 $$self{'data'}{'Alias'}{$zone} : $zone);
991 372         1194 @zone = ($z);
992             }
993              
994             # $abbrev
995              
996 561 100       1660 if ($abbrev) {
997 118         219 my @abbrev_zones;
998 118 100       772 if (exists $$self{'data'}{'MyAbbrev'}{$abbrev}) {
    50          
999 8         13 @abbrev_zones = @{ $$self{'data'}{'MyAbbrev'}{$abbrev} };
  8         24  
1000             } elsif (exists $$self{'data'}{'Abbrev'}{$abbrev}) {
1001 110         405 @abbrev_zones = @{ $$self{'data'}{'Abbrev'}{$abbrev} };
  110         661  
1002             }
1003              
1004 118         396 my @z;
1005 118         334 foreach my $isdst (@isdst) {
1006 236         939 my @tmp = $self->_check_abbrev_isdst($abbrev,$isdst,@abbrev_zones);
1007 236 100       850 if (@tmp) {
1008 155 100       491 if (@z) {
1009 37         218 @z = _list_add(\@z,\@tmp);
1010             } else {
1011 118         565 @z = @tmp;
1012             }
1013             }
1014             }
1015              
1016 118 50       494 if (@zone) {
1017 0         0 @zone = _list_union(\@z,\@zone);
1018             } else {
1019 118         554 @zone = @z;
1020             }
1021 118 50       715 last if (! @zone);
1022             }
1023              
1024             # $offset
1025              
1026 561 100       1668 if ($offset) {
1027 82 100       395 return undef if (! exists $$self{'data'}{'Offmod'}{$offset});
1028 81         339 $self->_offmod($offset);
1029              
1030 81         164 my @z;
1031 81         235 foreach my $isdst (@isdst) {
1032             my $tmp = $$self{'data'}{'MyOffsets'}{$offset}{$isdst} ||
1033 155   100     870 $$self{'data'}{'Offsets'}{$offset}{$isdst};
1034              
1035 155         278 my @tmp;
1036 155 100       343 if ($abbrev) {
1037 28         127 @tmp = $self->_check_offset_abbrev_isdst($offset,$abbrev,$isdst,$tmp);
1038             } else {
1039 127 100       699 @tmp = @$tmp if ($tmp);
1040             }
1041              
1042 155 100       417 if (@tmp) {
1043 134 100       275 if (@z) {
1044 55         270 @z = _list_add(\@z,\@tmp);
1045             } else {
1046 79         310 @z = @tmp;
1047             }
1048             }
1049             }
1050              
1051 81 100       263 if (@zone) {
1052 14         78 @zone = _list_union(\@zone,\@z);
1053             } else {
1054 67         295 @zone = @z;
1055             }
1056 81 100       330 last if (! @zone);
1057             }
1058              
1059             # $date
1060              
1061 558 100       1819 if (@$date) {
1062             # Get all periods for the year.
1063             #
1064             # Test all periods to make sure that $date is between the
1065             # wallclock times AND matches other criteria. All periods
1066             # must be tested since the same wallclock time can be in
1067             # multiple periods.
1068              
1069 533         880 my @tmp;
1070 533         1124 my $isdst = '';
1071 533 50       1500 $isdst = 0 if ($dstflag eq 'stdonly');
1072 533 50       1386 $isdst = 1 if ($dstflag eq 'dstonly');
1073              
1074             ZONE:
1075 533         1396 foreach my $z (@zone) {
1076 4129 100       18705 $self->_module($z) if (! exists $$self{'data'}{'Zones'}{$z}{'Loaded'});
1077 4129         9667 my $y = $$date[0];
1078 4129         9900 my @periods = $self->_all_periods($z,$y);
1079              
1080 4129         7990 foreach my $period (@periods) {
1081 7255 100 100     46317 next if (($abbrev ne '' && lc($abbrev) ne lc($$period[4])) ||
      100        
      100        
      33        
      66        
      66        
      100        
1082             ($offset ne '' && $offset ne $$period[2]) ||
1083             ($isdst ne '' && $isdst ne $$period[5]) ||
1084             $dmb->cmp($date,$$period[1]) == -1 ||
1085             $dmb->cmp($date,$$period[7]) == 1
1086             );
1087 2234         4829 push(@tmp,$z);
1088 2234         5413 next ZONE;
1089             }
1090             }
1091 533         2141 @zone = @tmp;
1092 533 100       1813 last if (! @zone);
1093             }
1094              
1095 549         1313 last;
1096             }
1097              
1098             # Return the value/list
1099              
1100 560 100       1701 if (wantarray) {
1101 27         50 my @ret;
1102 27         52 foreach my $z (@zone) {
1103 69         193 push(@ret,$$self{'data'}{'ZoneNames'}{$z});
1104             }
1105 27         185 return @ret;
1106             }
1107              
1108 533 100       1462 return '' if (! @zone);
1109 524         3100 return $$self{'data'}{'ZoneNames'}{$zone[0]}
1110             }
1111              
1112             # This returns a list of all timezones which have the correct
1113             # abbrev/isdst combination.
1114             #
1115             sub _check_abbrev_isdst {
1116 236     236   1070 my($self,$abbrev,$isdst,@zones) = @_;
1117              
1118 236         410 my @ret;
1119             ZONE:
1120 236         457 foreach my $zone (@zones) {
1121 3616 100       14814 $self->_module($zone) if (! exists $$self{'data'}{'Zones'}{$zone}{'Loaded'});
1122              
1123 3616         7152 foreach my $y (sort keys %{ $$self{'data'}{'Zones'}{$zone}{'Dates'} }) {
  3616         111500  
1124 167086         195822 my @periods = @{ $$self{'data'}{'Zones'}{$zone}{'Dates'}{$y} };
  167086         419725  
1125 167086         223974 foreach my $period (@periods) {
1126 321687         773238 my($dateUT,$dateLT,$off,$offref,$abb,$dst,$endUT,$endLT) = @$period;
1127 321687 100 100     819209 next if (lc($abbrev) ne lc($abb) ||
1128             $isdst != $dst);
1129 1845         3506 push(@ret,$zone);
1130 1845         9669 next ZONE;
1131             }
1132             }
1133             }
1134              
1135 236         1600 return @ret;
1136             }
1137              
1138             # This returns a list of all timezones which have the correct
1139             # abbrev/isdst combination.
1140             #
1141             sub _check_offset_abbrev_isdst {
1142 28     28   97 my($self,$offset,$abbrev,$isdst,$zones) = @_;
1143              
1144 28         93 my @ret;
1145 28         71 ZONE: foreach my $zone (@$zones) {
1146 804 100       4103 $self->_module($zone) if (! exists $$self{'data'}{'Zones'}{$zone}{'Loaded'});
1147              
1148 804         1488 foreach my $y (sort keys %{ $$self{'data'}{'Zones'}{$zone}{'Dates'} }) {
  804         25825  
1149 54253         64034 my @periods = @{ $$self{'data'}{'Zones'}{$zone}{'Dates'}{$y} };
  54253         139736  
1150 54253         72976 foreach my $period (@periods) {
1151 106098         256067 my($dateUT,$dateLT,$off,$offref,$abb,$dst,$endUT,$endLT) = @$period;
1152 106098 100 100     248348 next if (lc($abbrev) ne lc($abb) ||
      100        
1153             $offset ne $off ||
1154             $isdst != $dst);
1155 185         388 push(@ret,$zone);
1156 185         966 next ZONE;
1157             }
1158             }
1159             }
1160              
1161 28         187 return @ret;
1162             }
1163              
1164             # This finds the elements common to two lists, and preserves the order
1165             # from the first list.
1166             #
1167             sub _list_union {
1168 14     14   46 my($list1,$list2) = @_;
1169 14         52 my(%list2) = map { $_,1 } @$list2;
  182         450  
1170 14         42 my(@ret);
1171 14         50 foreach my $ele (@$list1) {
1172 221 100       555 push(@ret,$ele) if (exists $list2{$ele});
1173             }
1174 14         120 return @ret;
1175             }
1176              
1177             # This adds elements from the second list to the first list, provided
1178             # they are not already there.
1179             #
1180             sub _list_add {
1181 92     92   227 my($list1,$list2) = @_;
1182 92         236 my(%list1) = map { $_,1 } @$list1;
  1109         2491  
1183 92         365 my(@ret) = @$list1;
1184 92         228 foreach my $ele (@$list2) {
1185 1793 100       3080 next if (exists $list1{$ele});
1186 1316         1685 push(@ret,$ele);
1187 1316         2648 $list1{$ele} = 1;
1188             }
1189 92         902 return @ret;
1190             }
1191              
1192             ########################################################################
1193             # PERIODS METHODS
1194             ########################################################################
1195              
1196             sub all_periods {
1197 7     7 1 109834 my($self,$zone,$year) = @_;
1198              
1199 7         17 my $z = $self->_zone($zone);
1200 7 50       19 if (! $z) {
1201 0         0 carp "ERROR: [periods] Invalid zone: $zone";
1202 0         0 return;
1203             }
1204 7         11 $zone = $z;
1205 7 50       19 $self->_module($zone) if (! exists $$self{'data'}{'Zones'}{$zone}{'Loaded'});
1206              
1207             # Run a faster 'dclone' so we don't return the actual data.
1208              
1209 7         18 my @tmp = $self->_all_periods($zone,$year);
1210 7         13 my @ret;
1211 7         16 foreach my $ele (@tmp) {
1212             push(@ret,
1213 14         34 [ [ @{$$ele[0]} ],[ @{$$ele[1]} ],$$ele[2],[ @{$$ele[3]} ],$$ele[4],
  14         29  
  14         26  
1214 14         19 $$ele[5], [ @{$$ele[6]} ],[ @{$$ele[7]} ],$$ele[8],$$ele[9],
  14         25  
  14         58  
1215             $$ele[10],$$ele[11] ]);
1216             }
1217 7         23 return @ret;
1218             }
1219              
1220             sub _all_periods {
1221 34344     34344   63825 my($self,$zone,$year) = @_;
1222 34344         49339 $year += 0;
1223              
1224 34344 100       94126 if (! exists $$self{'data'}{'Zones'}{$zone}{'AllDates'}{$year}) {
1225              
1226             #
1227             # $ym1 is the year prior to $year which contains a rule (which will
1228             # end in $year or later). $y is $year IF the zone contains rules
1229             # for this year.
1230             #
1231              
1232 3145         5106 my($ym1,$ym0);
1233 3145 100 100     10492 if ($year > $$self{'data'}{'LastYear'} &&
1234             exists $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'zone'}) {
1235 5         17 $ym1 = $year-1;
1236 5         11 $ym0 = $year;
1237              
1238             } else {
1239 3140         4776 foreach my $y (sort { $a <=> $b }
  1724528         1973404  
1240 3140         62182 keys %{ $$self{'data'}{'Zones'}{$zone}{'Dates'} }) {
1241 115061 100       176076 if ($y < $year) {
1242 112840         133279 $ym1 = $y;
1243 112840         142427 next;
1244             }
1245 2221 100       6944 $ym0 = $year if ($year == $y);
1246 2221         3729 last;
1247             }
1248             }
1249 3145 100       15913 $ym1 = 0 if (! $ym1);
1250              
1251             #
1252             # Get the periods from the prior year. The last one is used (any others
1253             # are discarded).
1254             #
1255              
1256 3145         4920 my(@periods);
1257              
1258             # $ym1 will be 0 in 0001
1259 3145 100       6272 if ($ym1) {
1260 3142         9353 my @tmp = $self->_periods($zone,$ym1);
1261 3142 50       11958 push(@periods,pop(@tmp)) if (@tmp);
1262             }
1263              
1264             #
1265             # Add on any periods from the current year.
1266             #
1267              
1268 3145 100       6932 if ($ym0) {
1269 1900         4759 push(@periods,$self->_periods($zone,$year));
1270             }
1271              
1272 3145         11969 $$self{'data'}{'Zones'}{$zone}{'AllDates'}{$year} = [ @periods ];
1273             }
1274              
1275 34344         46622 return @{ $$self{'data'}{'Zones'}{$zone}{'AllDates'}{$year} };
  34344         117823  
1276             }
1277              
1278             sub periods {
1279 8     8 1 102336 my($self,$zone,$year,$year1) = @_;
1280              
1281 8         22 my $z = $self->_zone($zone);
1282 8 50       20 if (! $z) {
1283 0         0 carp "ERROR: [periods] Invalid zone: $zone";
1284 0         0 return;
1285             }
1286 8         11 $zone = $z;
1287 8 100       26 $self->_module($zone) if (! exists $$self{'data'}{'Zones'}{$zone}{'Loaded'});
1288              
1289 8 100       21 if (! defined($year1)) {
1290 7         17 return $self->_periods($zone,$year);
1291             }
1292              
1293 1 50       4 $year = 1 if (! defined($year));
1294              
1295 1         2 my @ret;
1296 1         4 my $lastyear = $$self{'data'}{'LastYear'};
1297              
1298 1 50       4 if ($year <= $lastyear) {
1299 1         2 foreach my $y (sort { $a <=> $b }
  1116         1305  
1300 1         32 keys %{ $$self{'data'}{'Zones'}{$zone}{'Dates'} }) {
1301 4 100 66     20 last if ($y > $year1 || $y > $lastyear);
1302 3 50       8 next if ($y < $year);
1303 3         8 push(@ret,$self->_periods($zone,$y));
1304             }
1305             }
1306              
1307 1 50       9 if ($year1 > $lastyear) {
1308 0 0       0 $year = $lastyear + 1 if ($year <= $lastyear);
1309 0         0 foreach my $y ($year..$year1) {
1310 0         0 push(@ret,$self->_periods($zone,$y));
1311             }
1312             }
1313              
1314 1         5 return @ret;
1315             }
1316              
1317             sub _periods {
1318 5052     5052   11108 my($self,$zone,$year) = @_;
1319 5052         7464 $year += 0;
1320              
1321 5052 100       14930 if (! exists $$self{'data'}{'Zones'}{$zone}{'Dates'}{$year}) {
1322              
1323 12         28 my @periods = ();
1324 12 100       36 if ($year > $$self{'data'}{'LastYear'}) {
1325             # Calculate periods using the LastRule method
1326 11         42 @periods = $self->_lastrule($zone,$year);
1327             }
1328              
1329 12         51 $$self{'data'}{'Zones'}{$zone}{'Dates'}{$year} = [ @periods ];
1330             }
1331              
1332             # A faster 'dclone' so we don't return the actual data
1333 5052         6921 my @ret;
1334 5052         6660 foreach my $ele (@{ $$self{'data'}{'Zones'}{$zone}{'Dates'}{$year} }) {
  5052         17481  
1335             push(@ret,
1336 9140         26336 [ [ @{$$ele[0]} ],[ @{$$ele[1]} ],$$ele[2],[ @{$$ele[3]} ],$$ele[4],$$ele[5],
  9140         20111  
  9140         18481  
1337 9140         12564 [ @{$$ele[6]} ],[ @{$$ele[7]} ],$$ele[8],$$ele[9],$$ele[10],$$ele[11] ]);
  9140         19614  
  9140         41634  
1338             }
1339 5052         12440 return @ret;
1340             }
1341              
1342             sub date_period {
1343 30208     30208 1 127712 my($self,$date,$zone,$wallclock,$isdst) = @_;
1344 30208 100       55929 $wallclock = 0 if (! $wallclock);
1345 30208 100       56161 $isdst = 0 if (! $isdst);
1346              
1347 30208         58683 my $z = $self->_zone($zone);
1348 30208 50       60668 if (! $z) {
1349 0         0 carp "ERROR: [date_period] Invalid zone: $zone";
1350 0         0 return;
1351             }
1352 30208         42523 $zone = $z;
1353 30208 100       71397 $self->_module($zone) if (! exists $$self{'data'}{'Zones'}{$zone}{'Loaded'});
1354              
1355 30208         43249 my $dmb = $$self{'base'};
1356 30208         59298 my @date = @$date;
1357 30208         43292 my $year = $date[0];
1358 30208         78444 my $dates= $dmb->_date_fields(@$date);
1359 30208 50 33     108844 return () if ($year < 0 || $year > 9999);
1360              
1361 30208 100       52869 if ($wallclock) {
1362             # A wallclock date
1363              
1364 24658         54447 my @period = $self->_all_periods($zone,$year);
1365 24658         46099 my $beg = $period[0]->[9];
1366 24658         38425 my $end = $period[-1]->[11];
1367 24658 50       65916 if (($dates cmp $beg) == -1) {
    50          
1368 0         0 @period = $self->_all_periods($zone,$year-1);
1369             } elsif (($dates cmp $end) == 1) {
1370 0         0 @period = $self->_all_periods($zone,$year+1);
1371             }
1372              
1373 24658         33028 my(@per);
1374 24658         42214 foreach my $period (@period) {
1375 69965         149857 my($begUT,$begLT,$offsetstr,$offset,$abbrev,$dst,$endUT,$endLT,
1376             $begUTs,$begLTs,$endUTs,$endLTs) = @$period;
1377 69965 100 100     195366 if (($dates cmp $begLTs) != -1 && ($dates cmp $endLTs) != 1) {
1378 24690         54482 push(@per,$period);
1379             }
1380             }
1381              
1382 24658 100       57858 if ($#per == -1) {
    100          
    50          
1383 8         35 return ();
1384             } elsif ($#per == 0) {
1385 24610         79835 return $per[0];
1386             } elsif ($#per == 1) {
1387 40 100       95 if ($per[0][5] == $isdst) {
1388 19         79 return $per[0];
1389             } else {
1390 21         78 return $per[1];
1391             }
1392             } else {
1393 0         0 carp "ERROR: [date_period] Impossible error";
1394 0         0 return;
1395             }
1396              
1397             } else {
1398             # A GMT date
1399              
1400 5550         12281 my @period = $self->_all_periods($zone,$year);
1401 5550         10460 foreach my $period (@period) {
1402 5759         14954 my($begUT,$begLT,$offsetstr,$offset,$abbrev,$isdst,$endUT,$endLT,
1403             $begUTs,$begLTs,$endUTs,$endLTs) = @$period;
1404 5759 100 66     20858 if (($dates cmp $begUTs) != -1 && ($dates cmp $endUTs) != 1) {
1405 5550         15276 return $period;
1406             }
1407             }
1408 0         0 carp "ERROR: [date_period] Impossible error";
1409 0         0 return;
1410             }
1411             }
1412              
1413             # Calculate critical dates from the last rule. If $endonly is passed
1414             # in, it only calculates the ending of the zone period before the
1415             # start of the first one. This is necessary so that the last period in
1416             # one year can find out when it ends (which is determined in the
1417             # following year).
1418             #
1419             # Returns:
1420             # [begUT, begLT, offsetstr, offset, abb, ISDST, endUT, endLT,
1421             # begUTstr, begLTstr, endUTstr, endLTstr]
1422             # for each.
1423             #
1424             sub _lastrule {
1425 22     22   58 my($self,$zone,$year,$endonly) = @_;
1426              
1427             #
1428             # Get the list of rules (actually, the month in which the
1429             # rule triggers a time change). If there are none, then
1430             # this zone doesn't have a LAST RULE.
1431             #
1432              
1433             my @mon = (sort keys
1434 22         37 %{ $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'} });
  22         143  
1435 22 50       66 return () if (! @mon);
1436              
1437             #
1438             # Analyze each time change.
1439             #
1440              
1441 22         42 my @dates = ();
1442 22         43 my $dmb = $$self{'base'};
1443              
1444 22         49 my $stdoff = $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'zone'}{'stdoff'};
1445 22         46 my $dstoff = $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'zone'}{'dstoff'};
1446              
1447 22         32 my (@period);
1448              
1449 22         40 foreach my $mon (@mon) {
1450             my $flag =
1451 33         91 $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'}{$mon}{'flag'};
1452             my $dow =
1453 33         66 $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'}{$mon}{'dow'};
1454             my $num =
1455 33         62 $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'}{$mon}{'num'};
1456             my $isdst=
1457 33         288 $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'}{$mon}{'isdst'};
1458             my $time =
1459 33         64 $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'}{$mon}{'time'};
1460             my $type =
1461 33         60 $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'}{$mon}{'type'};
1462             my $abb =
1463 33         217 $$self{'data'}{'Zones'}{$zone}{'LastRule'}{'rules'}{$mon}{'abb'};
1464              
1465             # The end of the current period and the beginning of the next
1466 33         112 my($endUT,$endLT,$begUT,$begLT) =
1467             $dmb->_critical_date($year,$mon,$flag,$num,$dow,
1468             $isdst,$time,$type,$stdoff,$dstoff);
1469 33 100       139 return ($endUT,$endLT) if ($endonly);
1470              
1471 22 100       81 if (@period) {
1472 11         26 push(@period,$endUT,$endLT);
1473 11         32 push(@dates,[@period]);
1474             }
1475 22 100       54 my $offsetstr = ($isdst ? $dstoff : $stdoff);
1476 22         61 my $offset = $dmb->split('offset',$offsetstr);
1477              
1478 22         143 @period = ($begUT,$begLT,$offsetstr,$offset,$abb,$isdst);
1479             }
1480              
1481 11         92 push(@period,$self->_lastrule($zone,$year+1,1));
1482 11         40 push(@dates,[@period]);
1483              
1484 11         29 foreach my $period (@dates) {
1485 22         58 my($begUT,$begLT,$offsetstr,$offset,$abbrev,$dst,$endUT,$endLT) = @$period;
1486 22         64 my $begUTstr = $dmb->join("date",$begUT);
1487 22         89 my $begLTstr = $dmb->join("date",$begLT);
1488 22         53 my $endUTstr = $dmb->join("date",$endUT);
1489 22         52 my $endLTstr = $dmb->join("date",$endLT);
1490 22         106 $period = [$begUT,$begLT,$offsetstr,$offset,$abbrev,$dst,$endUT,$endLT,
1491             $begUTstr,$begLTstr,$endUTstr,$endLTstr];
1492             }
1493              
1494 11         43 return @dates;
1495             }
1496              
1497             ########################################################################
1498             # CONVERSION
1499             ########################################################################
1500              
1501             sub convert {
1502 41     41 1 66673 my($self,$date,$from,$to,$isdst) = @_;
1503 41         116 $self->_convert('convert',$date,$from,$to,$isdst);
1504             }
1505              
1506             sub convert_to_gmt {
1507 2860     2860 1 25268 my($self,$date,@arg) = @_;
1508 2860         7095 my($err,$from,$isdst) = _convert_args('convert_to_gmt',@arg);
1509 2860 50       6790 return (1) if ($err);
1510              
1511 2860         5039 my $dmb = $$self{'base'};
1512              
1513 2860 50       5552 if (! $from) {
1514 0         0 $from = $self->_now('tz',1);
1515             }
1516 2860         7518 $self->_convert('convert_to_gmt',$date,$from,'GMT',$isdst);
1517             }
1518              
1519             sub convert_from_gmt {
1520 12     12 1 19855 my($self,$date,@arg) = @_;
1521 12         41 my($err,$to,$isdst) = _convert_args('convert_from_gmt',@arg);
1522 12 50       43 return (1) if ($err);
1523              
1524 12         32 my $dmb = $$self{'base'};
1525              
1526 12 100       36 if (! $to) {
1527 7         40 $to = $self->_now('tz',1);
1528             }
1529 12         52 $self->_convert('convert_from_gmt',$date,'GMT',$to,$isdst);
1530             }
1531              
1532             sub convert_to_local {
1533 27     27 1 80 my($self,$date,@arg) = @_;
1534 27         116 my($err,$from,$isdst) = _convert_args('convert_to_local',@arg);
1535 27 50       113 return (1) if ($err);
1536              
1537 27         53 my $dmb = $$self{'base'};
1538              
1539 27 50       77 if (! $from) {
1540 0         0 $from = 'GMT';
1541             }
1542 27         75 $self->_convert('convert_to_local',$date,$from,$self->_now('tz',1),$isdst);
1543             }
1544              
1545             sub convert_from_local {
1546 0     0 1 0 my($self,$date,@arg) = @_;
1547 0         0 my($err,$to,$isdst) = _convert_args('convert_from_local',@arg);
1548 0 0       0 return (1) if ($err);
1549              
1550 0         0 my $dmb = $$self{'base'};
1551              
1552 0 0       0 if (! $to) {
1553 0         0 $to = 'GMT';
1554             }
1555 0         0 $self->_convert('convert_from_local',$date,$self->_now('tz',1),$to,$isdst);
1556             }
1557              
1558             sub _convert_args {
1559 2899     2899   6339 my($caller,@args) = @_;
1560              
1561 2899 100       9797 if ($#args == -1) {
    100          
    50          
1562 7         28 return (0,'',0);
1563             } elsif ($#args == 0) {
1564 176 50 33     1530 if ($args[0] eq '0' ||
1565             $args[0] eq '1') {
1566 0         0 return (0,'',$args[0]);
1567             } else {
1568 176         1021 return (0,$args[0],0);
1569             }
1570             } elsif ($#args == 1) {
1571 2716         8025 return (0,@args);
1572             } else {
1573 0         0 return (1,'',0);
1574             }
1575             }
1576              
1577             sub _convert {
1578 5736     5736   12338 my($self,$caller,$date,$from,$to,$isdst) = @_;
1579 5736         9084 my $dmb = $$self{'base'};
1580              
1581             # Handle $date as a reference and a string
1582 5736         7841 my (@date);
1583 5736 100       12186 if (ref($date)) {
1584 5735         11623 @date = @$date;
1585             } else {
1586 1         6 @date = @{ $dmb->split('date',$date) };
  1         7  
1587 1         4 $date = [@date];
1588             }
1589              
1590 5736 50       11938 if ($from ne $to) {
1591 5736         12268 my $tmp = $self->_zone($from);
1592 5736 50       12376 if (! $tmp) {
1593 0         0 return (2);
1594             }
1595 5736         8445 $from = $tmp;
1596              
1597 5736         10072 $tmp = $self->_zone($to);
1598 5736 50       11818 if (! $tmp) {
1599 0         0 return (3);
1600             }
1601 5736         9605 $to = $tmp;
1602             }
1603              
1604 5736 100       11555 if ($from eq $to) {
1605 187         704 my $per = $self->date_period($date,$from,1,$isdst);
1606 187         595 my $offset = $$per[3];
1607 187         498 my $abb = $$per[4];
1608 187         947 return (0,$date,$offset,$isdst,$abb);
1609             }
1610              
1611             # Convert $date from $from to GMT
1612              
1613 5549 50       10900 if ($from ne "Etc/GMT") {
1614 5549         12027 my $per = $self->date_period($date,$from,1,$isdst);
1615 5549 100       11946 if (! $per) {
1616 2         7 return (4);
1617             }
1618 5547         8643 my $offset = $$per[3];
1619 5547         7203 @date = @{ $dmb->calc_date_time(\@date,$offset,1) };
  5547         16484  
1620             }
1621              
1622             # Convert $date from GMT to $to
1623              
1624 5547         11759 $isdst = 0;
1625 5547         9714 my $offset = [0,0,0];
1626 5547         8446 my $abb = 'GMT';
1627              
1628 5547 50       11952 if ($to ne "Etc/GMT") {
1629 5547         13590 my $per = $self->date_period([@date],$to,0);
1630 5547         11966 $offset = $$per[3];
1631 5547         7932 $isdst = $$per[5];
1632 5547         8085 $abb = $$per[4];
1633 5547         7460 @date = @{ $dmb->calc_date_time(\@date,$offset) };
  5547         14401  
1634             }
1635              
1636 5547         26061 return (0,[@date],$offset,$isdst,$abb);
1637             }
1638              
1639             ########################################################################
1640             # REGULAR EXPRESSIONS FOR TIMEZONE INFORMATION
1641             ########################################################################
1642              
1643             # Returns regular expressions capable of matching timezones.
1644             #
1645             # The timezone regular expressions are:
1646             # namerx : this will match a zone name or alias (America/New_York)
1647             # abbrx : this will match a zone abbreviation (EDT)
1648             # zonerx : this will match a zone name or an abbreviation
1649             # offrx : this will match a pure offset (+0400)
1650             # offabbrx : this will match an offset with an abbreviation (+0400 WET)
1651             # offparrx : this will match an offset and abbreviation if parentheses
1652             # ("+0400 (WET)")
1653             # zrx : this will match all forms
1654             #
1655             # The regular expression will have the following named matches:
1656             # tzstring : the full string matched
1657             # zone : the name/alias
1658             # abb : the zone abbrevation
1659             # off : the offset
1660             #
1661             sub _zrx {
1662 544     544   1303 my($self,$re) = @_;
1663 544 100       2375 return $$self{'data'}{$re} if (defined $$self{'data'}{$re});
1664              
1665             # Zone name
1666              
1667 79         175 my @zone;
1668 79 50       387 if (exists $ENV{'DATE_MANIP_DEBUG_ZONES'}) {
1669 0         0 @zone = split(/\s+/,$ENV{'DATE_MANIP_DEBUG_ZONES'});
1670             } else {
1671 79         14640 @zone = (keys %{ $$self{'data'}{'Alias'} },
1672 79         179 keys %{ $$self{'data'}{'MyAlias'} });
  79         1550  
1673             }
1674 79         1710 @zone = sort _sortByLength(@zone);
1675 79         482 foreach my $zone (@zone) {
1676 63200         99225 $zone =~ s/\057/\\057/g; # /
1677 63200         83355 $zone =~ s/\055/\\055/g; # -
1678 63200         74979 $zone =~ s/\056/\\056/g; # .
1679 63200         74359 $zone =~ s/\050/\\050/g; # (
1680 63200         73607 $zone =~ s/\051/\\051/g; # )
1681 63200         82351 $zone =~ s/\053/\\053/g; # +
1682             }
1683              
1684 79         7744 my $zone = join('|',@zone);
1685 79         268162 $zone = qr/(?$zone)/i;
1686              
1687             # Abbreviation
1688              
1689 79         3103 my @abb;
1690 79 50       565 if (exists $ENV{'DATE_MANIP_DEBUG_ABBREVS'}) {
1691 0         0 @abb = split(/\s+/,$ENV{'DATE_MANIP_DEBUG_ABBREVS'});
1692             } else {
1693 79         4267 @abb = (keys %{ $$self{'data'}{'Abbrev'} },
1694 79         241 keys %{ $$self{'data'}{'MyAbbrev'} });
  79         550  
1695             }
1696 79         703 @abb = sort _sortByLength(@abb);
1697 79         507 foreach my $abb (@abb) {
1698 13274         16476 $abb =~ s/\055/\\055/g; # -
1699 13274         18061 $abb =~ s/\053/\\053/g; # +
1700             }
1701              
1702 79         1133 my $abb = join('|',@abb);
1703 79         39090 $abb = qr/(?$abb)/i;
1704              
1705             # Offset (+HH, +HHMM, +HH:MM, +HH:MM:SS, +HHMMSS)
1706              
1707 79         1311 my($hr) = qr/(?:[0-1][0-9]|2[0-3])/; # 00 - 23
1708 79         364 my($mn) = qr/(?:[0-5][0-9])/; # 00 - 59
1709 79         321 my($ss) = qr/(?:[0-5][0-9])/; # 00 - 59
1710              
1711 79         5387 my($off) = qr/ (? [+-] (?: $hr:$mn:$ss |
1712             $hr$mn$ss |
1713             $hr:?$mn |
1714             $hr
1715             )
1716             ) /ix;
1717              
1718             # Assemble everything
1719             #
1720             # A timezone can be any of the following in this order:
1721             # Offset (ABB)
1722             # Offset ABB
1723             # ABB
1724             # Zone
1725             # Offset
1726             # We put ABB before Zone so CET gets parse as the more common abbreviation
1727             # than the less common zone name.
1728              
1729 79         243560 $$self{'data'}{'namerx'} = qr/(?$zone)/;
1730 79         42613 $$self{'data'}{'abbrx'} = qr/(?$abb)/;
1731 79         278773 $$self{'data'}{'zonerx'} = qr/(?(?:$abb|$zone))/;
1732 79         10281 $$self{'data'}{'offrx'} = qr/(?$off)/;
1733 79         32521 $$self{'data'}{'offabbrx'} = qr/(?$off\s+$abb)/;
1734 79         29204 $$self{'data'}{'offparrx'} = qr/(?$off\s*\($abb\))/;
1735 79         338862 $$self{'data'}{'zrx'} = qr/(?(?:$off\s*\($abb\)|$off\s+$abb|$abb|$zone|$off))/;
1736              
1737 79         9602 return $$self{'data'}{$re};
1738             }
1739              
1740             # This sorts from longest to shortest element
1741             #
1742 168     168   1672 no strict 'vars';
  168         436  
  168         11969  
1743             sub _sortByLength {
1744 578059     578059   681683 return (length $b <=> length $a);
1745             }
1746 168     168   1248 use strict 'vars';
  168         544  
  168         209534  
1747              
1748             ########################################################################
1749             # CONFIG VARS
1750             ########################################################################
1751              
1752             # This sets a config variable. It also performs all side effects from
1753             # setting that variable.
1754             #
1755             sub _config_var_tz {
1756 384     384   1341 my($self,$var,$val) = @_;
1757              
1758 384 50       2534 if ($var eq 'tz') {
    100          
    100          
    50          
1759 0         0 my $err = $self->_config_var_setdate("now,$val",0);
1760 0 0       0 return if ($err);
1761 0         0 $$self{'data'}{'sections'}{'conf'}{'forcedate'} = 0;
1762 0         0 $val = 1;
1763              
1764             } elsif ($var eq 'setdate') {
1765 169         938 my $err = $self->_config_var_setdate($val,0);
1766 169 50       617 return if ($err);
1767 169         772 $$self{'data'}{'sections'}{'conf'}{'forcedate'} = 0;
1768 169         411 $val = 1;
1769              
1770             } elsif ($var eq 'forcedate') {
1771 188         836 my $err = $self->_config_var_setdate($val,1);
1772 188 50       765 return if ($err);
1773 188         936 $$self{'data'}{'sections'}{'conf'}{'setdate'} = 0;
1774 188         557 $val = 1;
1775              
1776             } elsif ($var eq 'configfile') {
1777 27         380 $self->_config_file($val);
1778 27         240 return;
1779             }
1780              
1781 357         920 my $base = $$self{'base'};
1782 357         1124 $$base{'data'}{'sections'}{'conf'}{$var} = $val;
1783 357         1935 return;
1784             }
1785              
1786             sub _config_var_setdate {
1787 357     357   1078 my($self,$val,$force) = @_;
1788 357         851 my $base = $$self{'base'};
1789              
1790 357         1893 my $dstrx = qr/(?:,\s*(stdonly|dstonly|std|dst))?/i;
1791 357         1294 my $zonrx = qr/,\s*(.+)/;
1792 357         1283 my $da1rx = qr/(\d\d\d\d)(\d\d)(\d\d)(\d\d):(\d\d):(\d\d)/;
1793 357         1182 my $da2rx = qr/(\d\d\d\d)\-(\d\d)\-(\d\d)\-(\d\d):(\d\d):(\d\d)/;
1794 357         847 my $time = time;
1795              
1796 357         894 my($op,$date,$dstflag,$zone,@date,$offset,$abb);
1797              
1798             #
1799             # Parse the argument
1800             #
1801              
1802 357 100 33     21075 if ($val =~ /^now${dstrx}${zonrx}$/oi) {
    50 0        
    50          
    0          
    0          
1803             # now,ZONE
1804             # now,DSTFLAG,ZONE
1805             # Sets now to the system date/time but sets the timezone to be ZONE
1806              
1807 267         790 $op = 'nowzone';
1808 267         1271 ($dstflag,$zone) = ($1,$2);
1809              
1810             } elsif ($val =~ /^zone${dstrx}${zonrx}$/oi) {
1811             # zone,ZONE
1812             # zone,DSTFLAG,ZONE
1813             # Converts 'now' to the alternate zone
1814              
1815 0         0 $op = 'zone';
1816 0         0 ($dstflag,$zone) = ($1,$2);
1817              
1818             } elsif ($val =~ /^${da1rx}${dstrx}${zonrx}$/o ||
1819             $val =~ /^${da2rx}${dstrx}${zonrx}$/o) {
1820             # DATE,ZONE
1821             # DATE,DSTFLAG,ZONE
1822             # Sets the date and zone
1823              
1824 90         381 $op = 'datezone';
1825 90         331 my($y,$m,$d,$h,$mn,$s);
1826 90         980 ($y,$m,$d,$h,$mn,$s,$dstflag,$zone) = ($1,$2,$3,$4,$5,$6,$7,$8);
1827 90         431 $date = [$y,$m,$d,$h,$mn,$s];
1828              
1829             } elsif ($val =~ /^${da1rx}$/o ||
1830             $val =~ /^${da2rx}$/o) {
1831             # DATE
1832             # Sets the date in the system timezone
1833              
1834 0         0 $op = 'date';
1835 0         0 my($y,$m,$d,$h,$mn,$s) = ($1,$2,$3,$4,$5,$6);
1836 0         0 $date = [$y,$m,$d,$h,$mn,$s];
1837 0         0 $zone = $self->_now('systz',1);
1838              
1839             } elsif (lc($val) eq 'now') {
1840             # now
1841             # Resets everything
1842              
1843 0         0 my $systz = $$base{'data'}{'now'}{'systz'};
1844 0         0 $base->_init_now();
1845 0         0 $$base{'data'}{'now'}{'systz'} = $systz;
1846 0         0 return 0;
1847              
1848             } else {
1849 0         0 carp "ERROR: [config_var] invalid SetDate/ForceDate value: $val";
1850 0         0 return 1;
1851             }
1852              
1853 357 50       1345 $dstflag = 'std' if (! $dstflag);
1854              
1855             #
1856             # Get the date we're setting 'now' to
1857             #
1858              
1859 357 100       1348 if ($op eq 'nowzone') {
    50          
1860             # Use the system localtime
1861              
1862 267         6586 my($s,$mn,$h,$d,$m,$y) = localtime($time);
1863 267         1229 $y += 1900;
1864 267         525 $m++;
1865 267         1035 $date = [$y,$m,$d,$h,$mn,$s];
1866              
1867             } elsif ($op eq 'zone') {
1868             # Use the system GMT time
1869              
1870 0         0 my($s,$mn,$h,$d,$m,$y) = gmtime($time);
1871 0         0 $y += 1900;
1872 0         0 $m++;
1873 0         0 $date = [$y,$m,$d,$h,$mn,$s];
1874             }
1875              
1876             #
1877             # Find out what zone was passed in. It can be an alias or an offset.
1878             #
1879              
1880 357 50       1095 if ($zone) {
1881 357         784 my ($err,@args);
1882 357         978 my $dmb = $$self{'base'};
1883 357 50       1209 $date = [] if (! defined $date);
1884 357         2661 $zone = $self->__zone($date,'',lc($zone),'',lc($dstflag));
1885 357 50       1401 if (! $zone) {
1886 0         0 carp "ERROR: [config_var] invalid zone in SetDate: @args";
1887 0         0 return 1;
1888             }
1889              
1890             } else {
1891 0         0 $zone = $$base{'data'}{'now'}{'systz'};
1892             }
1893              
1894             #
1895             # Handle the zone
1896             #
1897              
1898 357         831 my($isdst,@isdst);
1899 357 50       1199 if ($dstflag eq 'std') {
    0          
    0          
1900 357         920 @isdst = (0,1);
1901             } elsif ($dstflag eq 'stdonly') {
1902 0         0 @isdst = (0);
1903             } elsif ($dstflag eq 'dst') {
1904 0         0 @isdst = (1,0);
1905             } else {
1906 0         0 @isdst = (1);
1907             }
1908              
1909 357 50 66     1847 if ($op eq 'nowzone' ||
    0 33        
1910             $op eq 'datezone' ||
1911             $op eq 'date') {
1912              
1913             # Check to make sure that the date can exist in this zone.
1914 357         698 my $per;
1915 357         843 foreach my $dst (@isdst) {
1916 714 100       1838 next if ($per);
1917 357         1742 $per = $self->date_period($date,$zone,1,$dst);
1918             }
1919              
1920 357 50       1174 if (! $per) {
1921 0         0 carp "ERROR: [config_var] invalid date: SetDate: $date, $zone";
1922 0         0 return 1;
1923             }
1924 357         870 $isdst = $$per[5];
1925 357         768 $abb = $$per[4];
1926 357         842 $offset = $$per[3];
1927              
1928             } elsif ($op eq 'zone') {
1929              
1930             # Convert to that zone
1931 0         0 my($err);
1932 0         0 ($err,$date,$offset,$isdst,$abb) = $self->convert_from_gmt($date,$zone);
1933 0 0       0 if ($err) {
1934 0         0 carp "ERROR: [config_var] invalid SetDate date/offset values: $date, $zone";
1935 0         0 return 1;
1936             }
1937             }
1938              
1939             #
1940             # Set NOW
1941             #
1942              
1943 357         1211 $$base{'data'}{'now'}{'date'} = $date;
1944 357         1210 $$base{'data'}{'now'}{'tz'} = $self->_zone($zone);
1945 357         1271 $$base{'data'}{'now'}{'isdst'} = $isdst;
1946 357         1032 $$base{'data'}{'now'}{'abb'} = $abb;
1947 357         1305 $$base{'data'}{'now'}{'offset'} = $offset;
1948              
1949             #
1950             # Treate SetDate/ForceDate
1951             #
1952              
1953 357 100       1199 if ($force) {
1954 188         601 $$base{'data'}{'now'}{'force'} = 1;
1955 188         554 $$base{'data'}{'now'}{'set'} = 0;
1956             } else {
1957 169         595 $$base{'data'}{'now'}{'force'} = 0;
1958 169         479 $$base{'data'}{'now'}{'set'} = 1;
1959 169         503 $$base{'data'}{'now'}{'setsecs'} = $time;
1960 169         967 my($err,$setdate) = $self->convert_to_gmt($date,$zone);
1961 169         676 $$base{'data'}{'now'}{'setdate'} = $setdate;
1962             }
1963              
1964 357         2349 return 0;
1965             }
1966              
1967             1;
1968             # Local Variables:
1969             # mode: cperl
1970             # indent-tabs-mode: nil
1971             # cperl-indent-level: 3
1972             # cperl-continued-statement-offset: 2
1973             # cperl-continued-brace-offset: 0
1974             # cperl-brace-offset: 0
1975             # cperl-brace-imaginary-offset: 0
1976             # cperl-label-offset: 0
1977             # End: