File Coverage

lib/Date/Manip/Date.pm
Criterion Covered Total %
statement 2277 2633 86.4
branch 1120 1478 75.7
condition 389 549 70.8
subroutine 89 90 98.8
pod 24 24 100.0
total 3899 4774 81.6


line stmt bran cond sub pod time code
1             package Date::Manip::Date;
2             # Copyright (c) 1995-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   65438 use Date::Manip::Obj;
  168         434  
  168         7783  
15             @ISA = ('Date::Manip::Obj');
16              
17             require 5.010000;
18 168     168   1030 use warnings;
  168         330  
  168         4514  
19 168     168   858 use strict;
  168         406  
  168         3156  
20 168     168   795 use integer;
  168         290  
  168         764  
21 168     168   97975 use utf8;
  168         2447  
  168         930  
22 168     168   5279 use IO::File;
  168         358  
  168         22940  
23 168     168   1036 use Storable qw(dclone);
  168         333  
  168         6102  
24 168     168   924 use Carp;
  168         330  
  168         10176  
25             #use re 'debug';
26              
27 168     168   121393 use Date::Manip::Base;
  168         489  
  168         6655  
28 168     168   103608 use Date::Manip::TZ;
  168         873  
  168         758940  
29              
30             our $VERSION;
31             $VERSION='6.90';
32 168     168   1157 END { undef $VERSION; }
33              
34             ########################################################################
35             # BASE METHODS
36             ########################################################################
37              
38             sub is_date {
39 1     1 1 1966 return 1;
40             }
41              
42             # Call this every time a new date is put in to make sure everything is
43             # correctly initialized.
44             #
45             sub _init {
46 24318     24318   179904 my($self) = @_;
47              
48 24318         42329 $$self{'err'} = '';
49              
50 24318         215797 $$self{'data'} =
51             {
52             'set' => 0, # 1 if the date has been set
53             # 2 if the date is in the process of being set
54              
55             # The date as input
56             'in' => '', # the string that was parsed (if any)
57             'zin' => '', # the timezone that was parsed (if any)
58              
59             # The date in the parsed timezone
60             'date' => [], # the parsed date split
61             'def' => [0,0,0,0,0,0],
62             # 1 for each field that came from
63             # defaults rather than parsed
64             # '' for an implied field
65             'tz' => '', # the timezone of the date
66             'isdst' => '', # 1 if the date is in DST.
67             'offset' => [], # The offset from GMT
68             'abb' => '', # The timezone abbreviation.
69             'f' => {}, # fields used in printing a date
70              
71             # The date in GMT
72             'gmt' => [], # the date converted to GMT
73              
74             # The date in local timezone
75             'loc' => [], # the date converted to local timezone
76             };
77 24318         53734 return;
78             }
79              
80             sub _init_args {
81 11     11   23 my($self) = @_;
82              
83 11         21 my @args = @{ $$self{'args'} };
  11         30  
84 11         92 $self->parse(@args);
85 11         29 return;
86             }
87              
88             sub input {
89 0     0 1 0 my($self) = @_;
90 0         0 return $$self{'data'}{'in'};
91             }
92              
93             ########################################################################
94             # DATE PARSING
95             ########################################################################
96              
97             sub parse {
98 4093     4093 1 760050 my($self,$instring,@opts) = @_;
99 4093         10533 $self->_init();
100 4093         6430 my $noupdate = 0;
101              
102 4093 50       10037 if (! $instring) {
103 0         0 $$self{'err'} = '[parse] Empty date string';
104 0         0 return 1;
105             }
106              
107 4093         8488 my %opts = map { $_,1 } @opts;
  253         938  
108              
109 4093         7168 my $dmt = $$self{'tz'};
110 4093         6418 my $dmb = $$dmt{'base'};
111 4093         6902 delete $$self{'data'}{'default_time'};
112              
113 4093         7867 my($done,$y,$m,$d,$h,$mn,$s,$tzstring,$zone,$abb,$off,$dow,$got_time,
114             $default_time,$firsterr);
115              
116             ENCODING:
117 4093         12857 foreach my $string ($dmb->_encoding($instring)) {
118 4226         6704 $got_time = 0;
119 4226         5981 $default_time = 0;
120              
121             # Put parse in a simple loop for an easy exit.
122             PARSE:
123             {
124 4226         6032 my(@tmp,$tmp);
  4226         6409  
125 4226         7325 $$self{'err'} = '';
126              
127             ###################
128             # Handle some special language-specific rules
129              
130             # Some languages add a trailing period in some places. For example,
131             # the default German date format (running the system date command)
132             # produces: Mo 3. Jan 11:00:00 EST 2022
133             # The '3.' needs to have the period stripped.
134              
135 4226 100       10224 if ($self->_parse_rule('remove_trailing_period')) {
136 8         42 $string =~ s/\.\s/ /g;
137 8         26 $string =~ s/\.$//;
138             }
139              
140             # Some languages add parenthese. For example, the default date
141             # output in russian in some cases puts the timezone in parentheses.
142              
143 4226 100       8363 if ($self->_parse_rule('remove_parens')) {
144 42         109 $string =~ s/\(//g;
145 42         78 $string =~ s/\)//;
146             }
147              
148 4226         8081 my $words = $self->_parse_rule('strip_word');
149 4226 100       8837 if ($words) {
150 42         78 foreach my $w (@$words) {
151 42         307 $string =~ s/(?:^|\s)\Q$w\E(?:\s|$)/ /;
152             }
153             }
154              
155             ###################
156              
157             # Check the standard date format
158              
159 4226         12517 $tmp = $dmb->split('date',$string);
160 4226 100       9811 if (defined($tmp)) {
161 1922         4212 ($y,$m,$d,$h,$mn,$s) = @$tmp;
162 1922         2758 $got_time = 1;
163 1922         4597 last PARSE;
164             }
165              
166             # Parse ISO 8601 dates now (which may have a timezone).
167              
168 2304 100       5658 if (! exists $opts{'noiso8601'}) {
169 2299         6655 ($done,@tmp) = $self->_parse_datetime_iso8601($string,\$noupdate);
170 2299 100       6101 if ($done) {
171 314         891 ($y,$m,$d,$h,$mn,$s,$tzstring,$zone,$abb,$off) = @tmp;
172 314         525 $got_time = 1;
173 314         869 last PARSE;
174             }
175             }
176              
177             # There's lots of ways that commas may be included. Remove
178             # them (unless it's preceded and followed by a digit in
179             # which case it's probably a fractional separator).
180              
181 1990         5277 $string =~ s/(?
182 1990         3755 $string =~ s/,(?!\d)/ /g;
183              
184             # Some special full date/time formats ('now', 'epoch')
185              
186 1990 50       4922 if (! exists $opts{'nospecial'}) {
187 1990         6075 ($done,@tmp) = $self->_parse_datetime_other($string,\$noupdate);
188 1990 100       4716 if ($done) {
189 24         87 ($y,$m,$d,$h,$mn,$s,$tzstring,$zone,$abb,$off) = @tmp;
190 24         56 $got_time = 1;
191 24         81 last PARSE;
192             }
193             }
194              
195             # Parse (and remove) the time (and an immediately following timezone).
196              
197 1966         6429 ($got_time,@tmp) = $self->_parse_time('parse',$string,\$noupdate,%opts);
198 1966 100       4725 if ($got_time) {
199 1103         2892 ($string,$h,$mn,$s,$tzstring,$zone,$abb,$off) = @tmp;
200             }
201              
202 1966 100       4401 if (! $string) {
203 10         221 ($y,$m,$d) = $self->_def_date($y,$m,$d,\$noupdate);
204 10         31 last;
205             }
206              
207             # Parse (and remove) the day of week. Also, handle the simple DoW
208             # formats.
209              
210 1956 50       4492 if (! exists $opts{'nodow'}) {
211 1956         5265 ($done,@tmp) = $self->_parse_dow($string,\$noupdate);
212 1956 100       4811 if (@tmp) {
213 597 100       1318 if ($done) {
214 12         25 ($y,$m,$d) = @tmp;
215 12         17 $default_time = 1;
216 12         29 last PARSE;
217             } else {
218 585         1201 ($string,$dow) = @tmp;
219             }
220             }
221             }
222 1944 100       4629 $dow = 0 if (! $dow);
223              
224             # At this point, the string might contain the following dates:
225             #
226             # OTHER
227             # OTHER ZONE / ZONE OTHER
228             # DELTA
229             # DELTA ZONE / ZONE DELTA
230             # HOLIDAY
231             # HOLIDAY ZONE / ZONE HOLIDAY
232             #
233             # ZONE is only allowed if it wasn't parsed with the time
234              
235             # Unfortunately, there are some conflicts between zones and
236             # some other formats, so try parsing the entire string as a date.
237              
238 1944         5892 (@tmp) = $self->_parse_date($string,$dow,\$noupdate,%opts);
239 1944 100       4494 if (@tmp) {
240 1650         2284 my $dow2;
241 1650         3511 ($y,$m,$d,$dow2) = @tmp;
242 1650 50 66     5880 if ($dow2 && $dow && $dow != $dow2) {
      66        
243 0         0 $$self{'err'} = '[parse] Day of week invalid';
244 0         0 last PARSE;
245             }
246 1650 100       3180 $dow = $dow2 if ($dow2);
247 1650         2217 $default_time = 1;
248 1650         3519 last PARSE;
249             }
250              
251             # Parse any timezone
252              
253 294 100       622 if (! $tzstring) {
254 281         721 ($string,@tmp) = $self->_parse_tz($string,\$noupdate);
255 281 100       774 ($tzstring,$zone,$abb,$off) = @tmp if (@tmp);
256 281 50       661 last PARSE if (! $string);
257             }
258              
259             # Try the remainder of the string as a date.
260              
261 294 100       716 if ($tzstring) {
262 22         91 (@tmp) = $self->_parse_date($string,$dow,\$noupdate,%opts);
263 22 100       83 if (@tmp) {
264 1         7 ($y,$m,$d,$dow) = @tmp;
265 1         4 $default_time = 1;
266 1         3 last PARSE;
267             }
268             }
269              
270             # Parse deltas
271             #
272             # Occasionally, a delta is entered for a date (which is
273             # interpreted as the date relative to now). There can be some
274             # confusion between a date and a delta, but the most
275             # important conflicts are the ISO 8601 dates (many of which
276             # could be interpreted as a delta), but those have already
277             # been taken care of.
278             #
279             # We may have already gotten the time:
280             # 3 days ago at midnight UTC
281             # (we already stripped off the 'at midnight UTC' above).
282             #
283             # We also need to handle the sitution of a delta and a timezone.
284             # in 2 hours EST
285             # in 2 days EST
286             # but only if no time was entered.
287              
288 293 100       734 if (! exists $opts{'nodelta'}) {
289              
290 185         647 ($done,@tmp) =
291             $self->_parse_delta($string,$dow,$got_time,$h,$mn,$s,\$noupdate);
292 185 100       523 if (@tmp) {
293 30         74 ($y,$m,$d,$h,$mn,$s) = @tmp;
294 30         51 $got_time = 1;
295 30         48 $dow = '';
296             }
297 185 100       486 last PARSE if ($done);
298              
299             # We'll also check the original string to see if it's a valid
300             # delta since some deltas may have interpreted part of it as
301             # a time or timezone.
302              
303 149         431 ($done,@tmp) =
304             $self->_parse_delta($instring,$dow,$got_time,$h,$mn,$s,\$noupdate);
305 149 50       402 if (@tmp) {
306 0         0 ($y,$m,$d,$h,$mn,$s) = @tmp;
307 0         0 $got_time = 1;
308 0         0 $dow = '';
309 0         0 ($tzstring,$zone,$abb,$off) = ();
310             }
311 149 50       411 last PARSE if ($done);
312             }
313              
314             # Parse holidays
315              
316 257 50       651 unless (exists $opts{'noholidays'}) {
317 257         803 ($done,@tmp) =
318             $self->_parse_holidays($string,\$noupdate);
319 257 100       559 if (@tmp) {
320 9         21 ($y,$m,$d) = @tmp;
321             }
322 257 100       542 last PARSE if ($done);
323             }
324              
325 248         422 $$self{'err'} = '[parse] Invalid date string';
326 248         481 last PARSE;
327             }
328              
329             # We got an error parsing this encoding of the string. It could
330             # be that it is a genuine error, or it may be that we simply
331             # need to try a different encoding. If ALL encodings fail, we'll
332             # return the error from the first one.
333              
334 4226 100       9904 if ($$self{'err'}) {
335 254 100       545 if (! $firsterr) {
336 129         244 $firsterr = $$self{'err'};
337             }
338 254         536 next ENCODING;
339             }
340              
341             # If we didn't get an error, this is the string to use.
342              
343 3972         6177 last ENCODING;
344             }
345              
346 4093 100       9761 if ($$self{'err'}) {
347 121         261 $$self{'err'} = $firsterr;
348 121         440 return 1;
349             }
350              
351             # Make sure that a time is set
352              
353 3972 100       8121 if (! $got_time) {
354 603 100       1273 if ($default_time) {
355 598 100       2248 if (exists $$self{'data'}{'default_time'}) {
    100          
356 8         11 ($h,$mn,$s) = @{ $$self{'data'}{'default_time'} };
  8         20  
357 8         21 delete $$self{'data'}{'default_time'};
358             } elsif ($dmb->_config('defaulttime') eq 'midnight') {
359 574         1248 ($h,$mn,$s) = (0,0,0);
360             } else {
361 16         37 ($h,$mn,$s) = $dmt->_now('time',$noupdate);
362 16         28 $noupdate = 1;
363             }
364 598         1018 $got_time = 1;
365             } else {
366 5         19 ($h,$mn,$s) = $self->_def_time(undef,undef,undef,\$noupdate);
367             }
368             }
369              
370 3972         7197 $$self{'data'}{'set'} = 2;
371 3972         11001 return $self->_parse_check('parse',$instring,
372             $y,$m,$d,$h,$mn,$s,$dow,$tzstring,$zone,$abb,$off);
373             }
374              
375             sub parse_time {
376 30     30 1 121 my($self,$string,@opts) = @_;
377 30         55 my %opts = map { $_,1 } @opts;
  0         0  
378 30         44 my $noupdate = 0;
379              
380 30 50       79 if (! $string) {
381 0         0 $$self{'err'} = '[parse_time] Empty time string';
382 0         0 return 1;
383             }
384              
385 30         45 my($y,$m,$d,$h,$mn,$s);
386              
387 30 50       65 if ($$self{'err'}) {
388 0         0 $self->_init();
389             }
390 30 50       64 if ($$self{'data'}{'set'}) {
391 0         0 ($y,$m,$d,$h,$mn,$s) = @{ $$self{'data'}{'date'} };
  0         0  
392             } else {
393 30         46 my $dmt = $$self{'tz'};
394 30         95 ($y,$m,$d,$h,$mn,$s) = $dmt->_now('now',$noupdate);
395 30         62 $noupdate = 1;
396             }
397 30         45 my($tzstring,$zone,$abb,$off);
398              
399 30         85 ($h,$mn,$s,$tzstring,$zone,$abb,$off) =
400             $self->_parse_time('parse_time',$string,\$noupdate,%opts);
401              
402 30 100       83 return 1 if ($$self{'err'});
403              
404 25         46 $$self{'data'}{'set'} = 2;
405 25         63 return $self->_parse_check('parse_time','',
406             $y,$m,$d,$h,$mn,$s,'',$tzstring,$zone,$abb,$off);
407             }
408              
409             sub parse_date {
410 1012     1012 1 7125 my($self,$string,@opts) = @_;
411 1012         2071 my %opts = map { $_,1 } @opts;
  0         0  
412 1012         1570 my $noupdate = 0;
413              
414 1012 50       2674 if (! $string) {
415 0         0 $$self{'err'} = '[parse_date] Empty date string';
416 0         0 return 1;
417             }
418              
419 1012         1660 my $dmt = $$self{'tz'};
420 1012         1571 my $dmb = $$dmt{'base'};
421 1012         1682 my($y,$m,$d,$h,$mn,$s);
422              
423 1012 100       2172 if ($$self{'err'}) {
424 2         8 $self->_init();
425             }
426 1012 100       2181 if ($$self{'data'}{'set'}) {
427 7         19 ($y,$m,$d,$h,$mn,$s) = @{ $$self{'data'}{'date'} };
  7         33  
428             } else {
429 1005         1901 ($h,$mn,$s) = (0,0,0);
430             }
431              
432             # Put parse in a simple loop for an easy exit.
433 1012         1555 my($done,@tmp,$dow);
434             PARSE:
435             {
436              
437             # Parse ISO 8601 dates now
438              
439 1012 50       1486 unless (exists $opts{'noiso8601'}) {
  1012         2244  
440 1012         2879 ($done,@tmp) = $self->_parse_date_iso8601($string,\$noupdate);
441 1012 100       2686 if ($done) {
442 70         140 ($y,$m,$d) = @tmp;
443 70         143 last PARSE;
444             }
445             }
446              
447 942         3209 (@tmp) = $self->_parse_date($string,undef,\$noupdate,%opts);
448 942 100       2379 if (@tmp) {
449 818         1548 ($y,$m,$d,$dow) = @tmp;
450 818         1348 last PARSE;
451             }
452              
453 124         286 $$self{'err'} = '[parse_date] Invalid date string';
454 124         364 return 1;
455             }
456              
457 888 50       2229 return 1 if ($$self{'err'});
458              
459 888         2636 $y = $dmt->_fix_year($y);
460              
461 888         1837 $$self{'data'}{'set'} = 2;
462 888         2729 return $self->_parse_check('parse_date','',$y,$m,$d,$h,$mn,$s,$dow);
463             }
464              
465             sub _parse_date {
466 2908     2908   7027 my($self,$string,$dow,$noupdate,%opts) = @_;
467              
468             # There's lots of ways that commas may be included. Remove
469             # them.
470             #
471             # Also remove some words we should ignore.
472              
473 2908         6083 $string =~ s/,/ /g;
474              
475 2908         4968 my $dmt = $$self{'tz'};
476 2908         4456 my $dmb = $$dmt{'base'};
477             my $ign = (exists $$dmb{'data'}{'rx'}{'other'}{'ignore'} ?
478 2908 100       8476 $$dmb{'data'}{'rx'}{'other'}{'ignore'} :
479             $self->_other_rx('ignore'));
480 2908         19190 $string =~ s/$ign/ /g;
481 2908         16051 my $of = $+{'of'};
482              
483 2908         18466 $string =~ s/\s*$//;
484 2908 50       7638 return () if (! $string);
485              
486 2908         4839 my($done,$y,$m,$d,@tmp);
487              
488             # Put parse in a simple loop for an easy exit.
489             PARSE:
490             {
491              
492             # Parse (and remove) the day of week. Also, handle the simple DoW
493             # formats.
494              
495 2908 50       4082 unless (exists $opts{'nodow'}) {
  2908         6153  
496 2908 100       6356 if (! defined($dow)) {
497 942         2672 ($done,@tmp) = $self->_parse_dow($string,$noupdate);
498 942 100       2434 if (@tmp) {
499 664 100       1325 if ($done) {
500 6         13 ($y,$m,$d) = @tmp;
501 6         18 last PARSE;
502             } else {
503 658         1440 ($string,$dow) = @tmp;
504             }
505             }
506 936 100       2253 $dow = 0 if (! $dow);
507             }
508             }
509              
510             # Parse common dates
511              
512 2902 50       5923 unless (exists $opts{'nocommon'}) {
513 2902         7319 (@tmp) = $self->_parse_date_common($string,$noupdate);
514 2902 100       6728 if (@tmp) {
515 1573         3232 ($y,$m,$d) = @tmp;
516 1573         3192 last PARSE;
517             }
518             }
519              
520             # Parse less common dates
521              
522 1329 50       3436 unless (exists $opts{'noother'}) {
523 1329         4199 (@tmp) = $self->_parse_date_other($string,$dow,$of,$noupdate);
524 1329 100       3387 if (@tmp) {
525 874         1829 ($y,$m,$d,$dow) = @tmp;
526 874         1768 last PARSE;
527             }
528             }
529              
530             # Parse truncated dates
531              
532 455 100 100     1980 if (! $dow && ! $of) {
533 420         1224 (@tmp) = $self->_parse_date_truncated($string,$noupdate);
534 420 100       958 if (@tmp) {
535 16         42 ($y,$m,$d,$dow) = @tmp;
536 16         39 last PARSE;
537             }
538             }
539              
540 439         1201 return ();
541             }
542              
543 2469         8954 return($y,$m,$d,$dow);
544             }
545              
546             sub parse_format {
547 7     7 1 3875 my($self,$format,$string) = @_;
548 7         29 $self->_init();
549 7         15 my $noupdate = 0;
550              
551 7 50       26 if (! $string) {
552 0         0 $$self{'err'} = '[parse_format] Empty date string';
553 0         0 return 1;
554             }
555              
556 7         14 my $dmt = $$self{'tz'};
557 7         16 my $dmb = $$dmt{'base'};
558              
559 7         33 my($err,$re) = $self->_format_regexp($format);
560 7 50       27 return $err if ($err);
561 7 50       254 return 1 if ($string !~ $re);
562              
563             my($y,$m,$d,$h,$mn,$s,
564             $mon_name,$mon_abb,$dow_name,$dow_abb,$dow_char,$dow_num,
565             $doy,$nth,$ampm,$epochs,$epocho,
566             $tzstring,$off,$abb,$zone,
567             $g,$w,$l,$u) =
568 7         330 @+{qw(y m d h mn s
569             mon_name mon_abb dow_name dow_abb dow_char dow_num doy
570             nth ampm epochs epocho tzstring off abb zone g w l u)};
571              
572 7         49 while (1) {
573             # Get y/m/d/h/mn/s from:
574             # $epochs,$epocho
575              
576 7 50       38 if (defined($epochs)) {
577 0         0 ($y,$m,$d,$h,$mn,$s) = @{ $dmb->secs_since_1970($epochs) };
  0         0  
578 0         0 my $z;
579 0 0 0     0 if ($zone) {
    0          
580 0         0 $z = $dmt->_zone($zone);
581 0 0       0 return 'Invalid zone' if (! $z);
582             } elsif ($abb || $off) {
583 0         0 my $offset = $dmb->_delta_convert('offset',$off);
584 0         0 $z = $dmt->__zone([],$offset,'',$abb,'');
585 0 0       0 if (! $z) {
586 0         0 $z = $dmt->__zone([],$offset,$abb,'','');
587             }
588 0 0       0 return 'Invalid zone' if (! $z);
589             } else {
590 0         0 $z = $dmt->_now('tz',$noupdate);
591 0         0 $noupdate = 1;
592             }
593 0         0 my($err,$date) = $dmt->convert_from_gmt([$y,$m,$d,$h,$mn,$s],$z);
594 0         0 ($y,$m,$d,$h,$mn,$s) = @$date;
595 0         0 last;
596             }
597              
598 7 50       30 if (defined($epocho)) {
599 0         0 ($y,$m,$d,$h,$mn,$s) = @{ $dmb->secs_since_1970($epocho) };
  0         0  
600 0         0 last;
601             }
602              
603             # Get y/m/d from:
604             # $y,$m,$d,
605             # $mon_name,$mon_abb
606             # $doy,$nth
607             # $g/$w,$l/$u
608              
609 7 50       41 if ($mon_name) {
    100          
610 0         0 $m = $$dmb{'data'}{'wordmatch'}{'month_name'}{lc($mon_name)};
611             } elsif ($mon_abb) {
612 2         15 $m = $$dmb{'data'}{'wordmatch'}{'month_abb'}{lc($mon_abb)};
613             }
614              
615 7 50       23 if ($nth) {
616 0         0 $d = $$dmb{'data'}{'wordmatch'}{'nth'}{lc($nth)};
617             }
618              
619 7 50       42 if ($doy) {
    50          
    50          
    100          
620 0 0       0 $y = $dmt->_now('y',$noupdate) if (! $y);
621 0         0 $noupdate = 1;
622 0         0 ($y,$m,$d) = @{ $dmb->day_of_year($y,$doy) };
  0         0  
623              
624             } elsif ($g) {
625 0 0       0 $y = $dmt->_now('y',$noupdate) if (! $y);
626 0         0 $noupdate = 1;
627 0         0 ($y,$m,$d) = @{ $dmb->_week_of_year($g,$w,1) };
  0         0  
628              
629             } elsif ($l) {
630 0 0       0 $y = $dmt->_now('y',$noupdate) if (! $y);
631 0         0 $noupdate = 1;
632 0         0 ($y,$m,$d) = @{ $dmb->_week_of_year($l,$u,7) };
  0         0  
633              
634             } elsif ($m) {
635 5         26 ($y,$m,$d) = $self->_def_date($y,$m,$d,\$noupdate);
636             }
637              
638             # Get h/mn/s from:
639             # $h,$mn,$s,$ampm
640              
641 7 100       73 if (defined($h)) {
642 4         28 ($h,$mn,$s) = $self->_def_time($h,$mn,$s,\$noupdate);
643             }
644              
645 7 100       27 if ($ampm) {
646 2 50       16 if ($$dmb{'data'}{'wordmatch'}{'ampm'}{lc($ampm)} == 2) {
647             # pm times
648 0 0       0 $h+=12 unless ($h==12);
649             } else {
650             # am times
651 2 50       10 $h=0 if ($h==12);
652             }
653             }
654              
655             # Get dow from:
656             # $dow_name,$dow_abb,$dow_char,$dow_num
657              
658 7 50       38 if ($dow_name) {
    50          
    50          
659 0         0 $dow_num = $$dmb{'data'}{'wordmatch'}{'day_name'}{lc($dow_name)};
660             } elsif ($dow_abb) {
661 0         0 $dow_num = $$dmb{'data'}{'wordmatch'}{'day_abb'}{lc($dow_abb)};
662             } elsif ($dow_char) {
663 0         0 $dow_num = $$dmb{'data'}{'wordmatch'}{'day_char'}{lc($dow_char)};
664             }
665              
666 7         18 last;
667             }
668              
669 7 100       21 if (! $m) {
670 2         12 ($y,$m,$d) = $dmt->_now('now',$noupdate);
671 2         7 $noupdate = 1;
672             }
673 7 100       32 if (! defined($h)) {
674 3         17 ($h,$mn,$s) = (0,0,0);
675             }
676              
677 7         29 $$self{'data'}{'set'} = 2;
678 7         38 $err = $self->_parse_check('parse_format',$string,
679             $y,$m,$d,$h,$mn,$s,$dow_num,
680             $tzstring,$zone,$abb,$off);
681              
682 7 100       37 if (wantarray) {
683 1         5 my %tmp = %{ dclone(\%+) };
  1         137  
684 1         23 return ($err,%tmp);
685             }
686 6         23 return $err;
687             }
688              
689 0         0 BEGIN {
690 168     168   1059 my %y_form = map { $_,1 } qw( Y y s o G L );
  1008         2824  
691 168         527 my %m_form = map { $_,1 } qw( m f b h B j s o W U );
  1680         3397  
692 168         515 my %d_form = map { $_,1 } qw( j d e E s o W U );
  1344         2437  
693 168         455 my %h_form = map { $_,1 } qw( H I k i s o );
  1008         1831  
694 168         401 my %mn_form = map { $_,1 } qw( M s o );
  504         1057  
695 168         382 my %s_form = map { $_,1 } qw( S s o );
  504         976  
696              
697 168         350 my %dow_form = map { $_,1 } qw( v a A w );
  672         1296  
698 168         430 my %am_form = map { $_,1 } qw( p s o );
  504         986  
699 168         401 my %z_form = map { $_,1 } qw( Z z N );
  504         1104  
700 168         408 my %mon_form = map { $_,1 } qw( b h B );
  504         1117  
701 168         411 my %day_form = map { $_,1 } qw( v a A );
  504         368317  
702              
703             sub _format_regexp {
704 7     7   21 my($self,$format) = @_;
705 7         16 my $dmt = $$self{'tz'};
706 7         25 my $dmb = $$dmt{'base'};
707              
708 7 50       30 if (exists $$dmb{'data'}{'format'}{$format}) {
709 0         0 return @{ $$dmb{'data'}{'format'}{$format} };
  0         0  
710             }
711              
712 7         17 my $re;
713             my $err;
714 7         21 my($y,$m,$d,$h,$mn,$s) = (0,0,0,0,0,0);
715 7         20 my($dow,$ampm,$zone,$G,$W,$L,$U) = (0,0,0,0,0,0,0);
716              
717 7         20 while ($format) {
718 65 50       110 last if ($format eq '%');
719              
720 65 100       225 if ($format =~ s/^([^%]+)//) {
721 30         60 $re .= $1;
722 30         65 next;
723             }
724              
725 35         96 $format =~ s/^%(.)//;
726 35         73 my $f = $1;
727              
728 35 100       84 if (exists $y_form{$f}) {
729 5 50       24 if ($y) {
730 0         0 $err = 'Year specified multiple times';
731 0         0 last;
732             }
733 5         10 $y = 1;
734             }
735              
736 35 100       80 if (exists $m_form{$f}) {
737 5 50       13 if ($m) {
738 0         0 $err = 'Month specified multiple times';
739 0         0 last;
740             }
741 5         7 $m = 1;
742             }
743              
744 35 100       75 if (exists $d_form{$f}) {
745 5 50       15 if ($d) {
746 0         0 $err = 'Day specified multiple times';
747 0         0 last;
748             }
749 5         16 $d = 1;
750             }
751              
752 35 100       77 if (exists $h_form{$f}) {
753 4 50       15 if ($h) {
754 0         0 $err = 'Hour specified multiple times';
755 0         0 last;
756             }
757 4         11 $h = 1;
758             }
759              
760 35 100       71 if (exists $mn_form{$f}) {
761 4 50       18 if ($mn) {
762 0         0 $err = 'Minutes specified multiple times';
763 0         0 last;
764             }
765 4         5 $mn = 1;
766             }
767              
768 35 100       70 if (exists $s_form{$f}) {
769 4 50       16 if ($s) {
770 0         0 $err = 'Seconds specified multiple times';
771 0         0 last;
772             }
773 4         11 $s = 1;
774             }
775              
776 35 50       69 if (exists $dow_form{$f}) {
777 0 0       0 if ($dow) {
778 0         0 $err = 'Day-of-week specified multiple times';
779 0         0 last;
780             }
781 0         0 $dow = 1;
782             }
783              
784 35 100       70 if (exists $am_form{$f}) {
785 2 50       17 if ($ampm) {
786 0         0 $err = 'AM/PM specified multiple times';
787 0         0 last;
788             }
789 2         5 $ampm = 1;
790             }
791              
792 35 100       75 if (exists $z_form{$f}) {
793 2 50       5 if ($zone) {
794 0         0 $err = 'Zone specified multiple times';
795 0         0 last;
796             }
797 2         10 $zone = 1;
798             }
799              
800 35 50       119 if ($f eq 'G') {
    50          
    50          
    50          
801 0 0       0 if ($G) {
802 0         0 $err = 'G specified multiple times';
803 0         0 last;
804             }
805 0         0 $G = 1;
806              
807             } elsif ($f eq 'W') {
808 0 0       0 if ($W) {
809 0         0 $err = 'W specified multiple times';
810 0         0 last;
811             }
812 0         0 $W = 1;
813              
814             } elsif ($f eq 'L') {
815 0 0       0 if ($L) {
816 0         0 $err = 'L specified multiple times';
817 0         0 last;
818             }
819 0         0 $L = 1;
820              
821             } elsif ($f eq 'U') {
822 0 0       0 if ($U) {
823 0         0 $err = 'U specified multiple times';
824 0         0 last;
825             }
826 0         0 $U = 1;
827             }
828              
829             ###
830              
831 35 100 100     383 if ($f eq 'Y') {
    50 33        
    100 33        
    50 33        
    100          
    50          
    100          
    50          
    50          
    50          
    50          
    100          
    50          
    100          
    100          
    100          
    100          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    100          
    50          
    50          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
832 5         23 $re .= '(?\d\d\d\d)';
833              
834             } elsif ($f eq 'y') {
835 0         0 $re .= '(?\d\d)';
836              
837             } elsif ($f eq 'm') {
838 3         11 $re .= '(?\d\d)';
839              
840             } elsif ($f eq 'f') {
841 0         0 $re .= '(?:(?\d\d)| ?(?\d))';
842              
843             } elsif (exists $mon_form{$f}) {
844 2         7 my $abb = $$dmb{'data'}{'rx'}{'month_abb'}[0];
845 2         7 my $nam = $$dmb{'data'}{'rx'}{'month_name'}[0];
846 2         10 $re .= "(?:(?$nam)|(?$abb))";
847              
848             } elsif ($f eq 'j') {
849 0         0 $re .= '(?\d\d\d)';
850              
851             } elsif ($f eq 'd') {
852 5         15 $re .= '(?\d\d)';
853              
854             } elsif ($f eq 'e') {
855 0         0 $re .= '(?:(?\d\d)| ?(?\d))';
856              
857             } elsif (exists $day_form{$f}) {
858 0         0 my $abb = $$dmb{'data'}{'rx'}{'day_abb'}[0];
859 0         0 my $name = $$dmb{'data'}{'rx'}{'day_name'}[0];
860 0         0 my $char = $$dmb{'data'}{'rx'}{'day_char'}[0];
861 0         0 $re .= "(?:(?$name)|(?$abb)|(?$char))";
862              
863             } elsif ($f eq 'w') {
864 0         0 $re .= '(?[1-7])';
865              
866             } elsif ($f eq 'E') {
867 0         0 my $nth = $$dmb{'data'}{'rx'}{'nth'}[0];
868 0         0 $re .= "(?$nth)"
869              
870             } elsif ($f eq 'H' || $f eq 'I') {
871 4         19 $re .= '(?\d\d)';
872              
873             } elsif ($f eq 'k' || $f eq 'i') {
874 0         0 $re .= '(?:(?\d\d)| ?(?\d))';
875              
876             } elsif ($f eq 'p') {
877 2         15 my $ampm = $$dmb{data}{rx}{ampm}[0];
878 2         10 $re .= "(?$ampm)";
879              
880             } elsif ($f eq 'M') {
881 4         10 $re .= '(?\d\d)';
882              
883             } elsif ($f eq 'S') {
884 4         12 $re .= '(?\d\d)';
885              
886             } elsif (exists $z_form{$f}) {
887 2         17 $re .= $dmt->_zrx('zrx');
888              
889             } elsif ($f eq 's') {
890 0         0 $re .= '(?\d+)';
891              
892             } elsif ($f eq 'o') {
893 0         0 $re .= '(?\d+)';
894              
895             } elsif ($f eq 'G') {
896 0         0 $re .= '(?\d\d\d\d)';
897              
898             } elsif ($f eq 'W') {
899 0         0 $re .= '(?\d\d)';
900              
901             } elsif ($f eq 'L') {
902 0         0 $re .= '(?\d\d\d\d)';
903              
904             } elsif ($f eq 'U') {
905 0         0 $re .= '(?\d\d)';
906              
907             } elsif ($f eq 'c') {
908 0         0 $format = '%a %b %e %H:%M:%S %Y' . $format;
909              
910             } elsif ($f eq 'C' || $f eq 'u') {
911 0         0 $format = '%a %b %e %H:%M:%S %Z %Y' . $format;
912              
913             } elsif ($f eq 'g') {
914 0         0 $format = '%a, %d %b %Y %H:%M:%S %Z' . $format;
915              
916             } elsif ($f eq 'D') {
917 0         0 $format = '%m/%d/%y' . $format;
918              
919             } elsif ($f eq 'r') {
920 2         8 $format = '%I:%M:%S %p' . $format;
921              
922             } elsif ($f eq 'R') {
923 0         0 $format = '%H:%M' . $format;
924              
925             } elsif ($f eq 'T' || $f eq 'X') {
926 2         9 $format = '%H:%M:%S' . $format;
927              
928             } elsif ($f eq 'V') {
929 0         0 $format = '%m%d%H%M%y' . $format;
930              
931             } elsif ($f eq 'Q') {
932 0         0 $format = '%Y%m%d' . $format;
933              
934             } elsif ($f eq 'q') {
935 0         0 $format = '%Y%m%d%H%M%S' . $format;
936              
937             } elsif ($f eq 'P') {
938 0         0 $format = '%Y%m%d%H:%M:%S' . $format;
939              
940             } elsif ($f eq 'O') {
941 0         0 $format = '%Y\\-%m\\-%dT%H:%M:%S' . $format;
942              
943             } elsif ($f eq 'F') {
944 0         0 $format = '%A, %B %e, %Y' . $format;
945              
946             } elsif ($f eq 'K') {
947 0         0 $format = '%Y-%j' . $format;
948              
949             } elsif ($f eq 'J') {
950 0         0 $format = '%G-W%W-%w' . $format;
951              
952             } elsif ($f eq 'x') {
953 0 0       0 if ($dmb->_config('dateformat') eq 'US') {
954 0         0 $format = '%m/%d/%y' . $format;
955             } else {
956 0         0 $format = '%d/%m/%y' . $format;
957             }
958              
959             } elsif ($f eq 't') {
960 0         0 $re .= "\t";
961              
962             } elsif ($f eq '%') {
963 0         0 $re .= '%';
964              
965             } elsif ($f eq '+') {
966 0         0 $re .= '\\+';
967             }
968             }
969              
970 7 50 66     115 if ($m != $d) {
    50 33        
    50 66        
    50 66        
    50          
971 0         0 $err = 'Date not fully specified';
972             } elsif ( ($h || $mn || $s) && (! $h || ! $mn) ) {
973 0         0 $err = 'Time not fully specified';
974             } elsif ($ampm && ! $h) {
975 0         0 $err = 'Time not fully specified';
976             } elsif ($G != $W) {
977 0         0 $err = 'G/W must both be specified';
978             } elsif ($L != $U) {
979 0         0 $err = 'L/U must both be specified';
980             }
981              
982 7 50       23 if ($err) {
983 0         0 $$dmb{'data'}{'format'}{$format} = [$err];
984 0         0 return ($err);
985             }
986              
987 7         7290 $$dmb{'data'}{'format'}{$format} = [0, qr/$re/i];
988 7         38 return @{ $$dmb{'data'}{'format'}{$format} };
  7         38  
989             }
990             }
991              
992             # This returns 1 if a given rule is set in the language _special_rules.
993             #
994             sub _parse_rule {
995 12678     12678   20464 my($self,$rule) = @_;
996              
997 12678         17267 my $dmt = $$self{'tz'};
998 12678         16918 my $dmb = $$dmt{'base'};
999              
1000 12678 100 66     42755 if (exists $$dmb{'data'}{'lang'}{'_special_rules'} &&
1001             exists $$dmb{'data'}{'lang'}{'_special_rules'}{$rule}) {
1002 92         249 return $$dmb{'data'}{'lang'}{'_special_rules'}{$rule};
1003             }
1004 12586         25815 return 0;
1005             }
1006              
1007             ########################################################################
1008             # DATE FORMATS
1009             ########################################################################
1010              
1011             sub _parse_check {
1012 4892     4892   14705 my($self,$caller,$instring,
1013             $y,$m,$d,$h,$mn,$s,$dow,$tzstring,$zone,$abb,$off) = @_;
1014 4892         7900 my $dmt = $$self{'tz'};
1015 4892         7619 my $dmb = $$dmt{'base'};
1016              
1017             # Check day_of_week for validity BEFORE converting 24:00:00 to the
1018             # next day
1019              
1020 4892 100       10078 if ($dow) {
1021 1105         4028 my $tmp = $dmb->day_of_week([$y,$m,$d]);
1022 1105 100       3319 if ($tmp != $dow) {
1023 4         32 $$self{'err'} = "[$caller] Day of week invalid";
1024 4         17 return 1;
1025             }
1026             }
1027              
1028             # Handle 24:00:00 times.
1029              
1030 4888 100       10293 if ($h == 24) {
1031 5         32 ($h,$mn,$s) = (0,0,0);
1032 5         15 ($y,$m,$d) = @{ $dmb->calc_date_days([$y,$m,$d],1) };
  5         36  
1033             }
1034              
1035 4888 100       18291 if (! $dmb->check([$y,$m,$d,$h,$mn,$s])) {
1036 8         49 $$self{'err'} = "[$caller] Invalid date";
1037 8         42 return 1;
1038             }
1039 4880         15598 my $date = [$y+0,$m+0,$d+0,$h+0,$mn+0,$s+0];
1040              
1041             #
1042             # We need to check that the date is valid in a timezone. The
1043             # timezone may be referred to with $zone, $abb, or $off, and
1044             # unfortunately, $abb MAY be the name of an abbrevation OR a
1045             # zone in a few cases.
1046             #
1047              
1048 4880         7339 my $zonename;
1049 4880 100       10470 my $abbrev = (defined $abb ? lc($abb) : '');
1050 4880 100       9249 my $offset = (defined $off ? $dmb->_delta_convert('offset',$off) : '');
1051 4880         8218 my @tmp;
1052              
1053 4880 100 100     17240 if (defined($zone)) {
    100          
1054 8         32 $zonename = $dmt->_zone($zone);
1055 8 50       25 if ($zonename) {
1056 8         81 @tmp = $self->__parse_check($date,$zonename,$off,$abb);
1057             }
1058              
1059             } elsif (defined($abb) || defined($off)) {
1060              
1061 144         723 $zonename = $dmt->__zone($date,$offset,'',$abbrev,'');
1062 144 100       525 if ($zonename) {
1063 137         723 @tmp = $self->__parse_check($date,$zonename,$off,$abb);
1064             }
1065              
1066 144 100 100     621 if (! @tmp && defined($abb)) {
1067 4         18 my $tmp = $dmt->_zone($abb);
1068 4 50       15 if ($tmp) {
1069 0         0 $zonename = $tmp;
1070 0         0 @tmp = $self->__parse_check($date,$zonename,$off,undef);
1071             }
1072             }
1073              
1074             } else {
1075 4728         14189 $zonename = $dmt->_now('tz');
1076 4728 50       10426 if ($zonename) {
1077 4728         12305 @tmp = $self->__parse_check($date,$zonename,$off,$abb);
1078             }
1079             }
1080              
1081 4880 100       10635 if (! $zonename) {
1082 7 50       17 if (defined($zone)) {
1083 0         0 $$self{'err'} = "[$caller] Unable to determine timezone: $zone";
1084             } else {
1085 7         25 $$self{'err'} = "[$caller] Unable to determine timezone";
1086             }
1087 7         34 return 1;
1088             }
1089              
1090 4873 100       9780 if (! @tmp) {
1091 1         4 $$self{'err'} = "[$caller] Invalid date in timezone";
1092 1         5 return 1;
1093             }
1094              
1095             # Store the date
1096              
1097 4872         9539 my($a,$o,$isdst) = @tmp;
1098              
1099 4872         14785 $self->set('zdate',$zonename,$date,$isdst);
1100 4872 50       11266 return 1 if ($$self{'err'});
1101              
1102 4872         8963 $$self{'data'}{'in'} = $instring;
1103 4872 100       9414 $$self{'data'}{'zin'} = $zone if (defined($zone));
1104              
1105 4872         20751 return 0;
1106             }
1107              
1108             sub __parse_check {
1109 4873     4873   10048 my($self,$date,$zonename,$off,$abb) = @_;
1110 4873         7761 my $dmt = $$self{'tz'};
1111 4873         7046 my $dmb = $$dmt{'base'};
1112              
1113 4873 100       9884 if (defined ($off)) {
1114 49         258 $off = $dmb->split('offset',$off);
1115             }
1116              
1117 4873         9635 foreach my $isdst (0,1) {
1118 4877         14780 my $per = $dmt->date_period($date,$zonename,1,$isdst);
1119 4877 100       11291 next if (! $per);
1120 4875         8013 my $a = $$per[4];
1121 4875         7392 my $o = $$per[3];
1122              
1123             # If $abb is defined, it must match.
1124 4875 100 100     11908 next if (defined $abb && lc($a) ne lc($abb));
1125              
1126             # If $off is defined, it must match.
1127 4873 100       9417 if (defined ($off)) {
1128 50 50 66     500 next if ($$off[0] != $$o[0] ||
      66        
1129             $$off[1] != $$o[1] ||
1130             $$off[2] != $$o[2]);
1131             }
1132              
1133 4872         14124 return ($a,$o,$isdst);
1134             }
1135 1         4 return ();
1136             }
1137              
1138             # Set up the regular expressions for ISO 8601 parsing. Returns the
1139             # requested regexp. $rx can be:
1140             # cdate : regular expression for a complete date
1141             # tdate : regular expression for a truncated date
1142             # ctime : regular expression for a complete time
1143             # ttime : regular expression for a truncated time
1144             # date : regular expression for a date only
1145             # time : regular expression for a time only
1146             # UNDEF : regular expression for a valid date and/or time
1147             #
1148             # Date matches are:
1149             # y m d doy w dow yod c
1150             # Time matches are:
1151             # h h24 mn s fh fm
1152             #
1153             sub _iso8601_rx {
1154 3668     3668   6874 my($self,$rx) = @_;
1155 3668         5779 my $dmt = $$self{'tz'};
1156 3668         5783 my $dmb = $$dmt{'base'};
1157              
1158             return $$dmb{'data'}{'rx'}{'iso'}{$rx}
1159 3668 100       13259 if (exists $$dmb{'data'}{'rx'}{'iso'}{$rx});
1160              
1161 265 100 66     2405 if ($rx eq 'cdate' || $rx eq 'tdate') {
    100 66        
    100          
    100          
    50          
1162              
1163 86         268 my $y4 = '(?\d\d\d\d)';
1164 86         215 my $y2 = '(?\d\d)';
1165 86         206 my $m = '(?0[1-9]|1[0-2])';
1166 86         209 my $d = '(?0[1-9]|[12][0-9]|3[01])';
1167 86         222 my $doy = '(?00[1-9]|0[1-9][0-9]|[1-2][0-9][0-9]|3[0-5][0-9]|36[0-6])';
1168 86         206 my $w = '(?0[1-9]|[1-4][0-9]|5[0-3])';
1169 86         197 my $dow = '(?[1-7])';
1170 86         193 my $yod = '(?\d)';
1171 86         197 my $cc = '(?\d\d)';
1172              
1173 86         2950 my @cdaterx =
1174             (
1175             "${y4}${m}${d}", # CCYYMMDD
1176             "${y4}\\-${m}\\-${d}", # CCYY-MM-DD
1177             "\\-${y2}${m}${d}", # -YYMMDD
1178             "\\-${y2}\\-${m}\\-${d}", # -YY-MM-DD
1179             "\\-?${y2}${m}${d}", # YYMMDD
1180             "\\-?${y2}\\-${m}\\-${d}", # YY-MM-DD
1181             "\\-\\-${m}\\-?${d}", # --MM-DD --MMDD
1182             "\\-\\-\\-${d}", # ---DD
1183              
1184             "${y4}\\-?${doy}", # CCYY-DoY CCYYDoY
1185             "\\-?${y2}\\-?${doy}", # YY-DoY -YY-DoY
1186             # YYDoY -YYDoY
1187             "\\-${doy}", # -DoY
1188              
1189             "${y4}W${w}${dow}", # CCYYWwwD
1190             "${y4}\\-W${w}\\-${dow}", # CCYY-Www-D
1191             "\\-?${y2}W${w}${dow}", # YYWwwD -YYWwwD
1192             "\\-?${y2}\\-W${w}\\-${dow}", # YY-Www-D -YY-Www-D
1193              
1194             "\\-?${yod}W${w}${dow}", # YWwwD -YWwwD
1195             "\\-?${yod}\\-W${w}\\-${dow}", # Y-Www-D -Y-Www-D
1196             "\\-W${w}\\-?${dow}", # -Www-D -WwwD
1197             "\\-W\\-${dow}", # -W-D
1198             "\\-\\-\\-${dow}", # ---D
1199             );
1200 86         763 my $cdaterx = join('|',@cdaterx);
1201 86         30569 $cdaterx = qr/(?:$cdaterx)/i;
1202              
1203 86         2218 my @tdaterx =
1204             (
1205             "${y4}\\-${m}", # CCYY-MM
1206             "${y4}", # CCYY
1207             "\\-${y2}\\-?${m}", # -YY-MM -YYMM
1208             "\\-${y2}", # -YY
1209             "\\-\\-${m}", # --MM
1210              
1211             "${y4}\\-?W${w}", # CCYYWww CCYY-Www
1212             "\\-?${y2}\\-?W${w}", # YY-Www YYWww
1213             # -YY-Www -YYWww
1214             "\\-?W${w}", # -Www Www
1215              
1216             "${cc}", # CC
1217             );
1218 86         441 my $tdaterx = join('|',@tdaterx);
1219 86         7709 $tdaterx = qr/(?:$tdaterx)/i;
1220              
1221 86         781 $$dmb{'data'}{'rx'}{'iso'}{'cdate'} = $cdaterx;
1222 86         596 $$dmb{'data'}{'rx'}{'iso'}{'tdate'} = $tdaterx;
1223              
1224             } elsif ($rx eq 'ctime' || $rx eq 'ttime') {
1225              
1226 75         273 my $hh = '(?[0-1][0-9]|2[0-3])';
1227 75         202 my $mn = '(?[0-5][0-9])';
1228 75         166 my $ss = '(?[0-5][0-9])';
1229 75         189 my $h24a = '(?24(?::00){0,2})';
1230 75         170 my $h24b = '(?24(?:00){0,2})';
1231 75         154 my $h = '(?[0-9])';
1232              
1233 75         153 my $fh = '(?:[\.,](?\d*))'; # fractional hours (keep)
1234 75         167 my $fm = '(?:[\.,](?\d*))'; # fractional seconds (keep)
1235 75         174 my $fs = '(?:[\.,]\d*)'; # fractional hours (discard)
1236              
1237 75         512 my $zrx = $dmt->_zrx('zrx');
1238              
1239 75         3127 my @ctimerx =
1240             (
1241             "${hh}${mn}${ss}${fs}?", # HHMNSS[,S+]
1242             "${hh}:${mn}:${ss}${fs}?", # HH:MN:SS[,S+]
1243             "${hh}:?${mn}${fm}", # HH:MN,M+ HHMN,M+
1244             "${hh}${fh}", # HH,H+
1245             "\\-${mn}:?${ss}${fs}?", # -MN:SS[,S+] -MNSS[,S+]
1246             "\\-${mn}${fm}", # -MN,M+
1247             "\\-\\-${ss}${fs}?", # --SS[,S+]
1248             "${hh}:?${mn}", # HH:MN HHMN
1249             "${h24a}", # 24:00:00 24:00 24
1250             "${h24b}", # 240000 2400
1251             "${h}:${mn}:${ss}${fs}?", # H:MN:SS[,S+]
1252             "${h}:${mn}${fm}", # H:MN,M+
1253             );
1254 150         405 my $ctimerx = join('|',@ctimerx);
1255 150         271757 $ctimerx = qr/(?:$ctimerx)(?:\s*$zrx)?/;
1256              
1257 150         1428 my @ttimerx =
1258             (
1259             "${hh}", # HH
1260             "\\-${mn}", # -MN
1261             );
1262 150         305 my $ttimerx = join('|',@ttimerx);
1263 150         2140 $ttimerx = qr/(?:$ttimerx)/;
1264              
1265 150         801 $$dmb{'data'}{'rx'}{'iso'}{'ctime'} = $ctimerx;
1266 150         449 $$dmb{'data'}{'rx'}{'iso'}{'ttime'} = $ttimerx;
1267              
1268             } elsif ($rx eq 'date') {
1269              
1270 29         340 my $cdaterx = $self->_iso8601_rx('cdate');
1271 29         157 my $tdaterx = $self->_iso8601_rx('tdate');
1272 29         12871 $$dmb{'data'}{'rx'}{'iso'}{'date'} = qr/(?:$cdaterx|$tdaterx)/;
1273              
1274             } elsif ($rx eq 'time') {
1275              
1276 1         13 my $ctimerx = $self->_iso8601_rx('ctime');
1277 1         6 my $ttimerx = $self->_iso8601_rx('ttime');
1278 1         3941 $$dmb{'data'}{'rx'}{'iso'}{'time'} = qr/(?:$ctimerx|$ttimerx)/;
1279              
1280             } elsif ($rx eq 'fulldate') {
1281              
1282             # A parseable string contains:
1283             # a complete date and complete time
1284             # a complete date and truncated time
1285             # a truncated date
1286             # a complete time
1287             # a truncated time
1288              
1289             # If the string contains both a time and date, they may be adjacent
1290             # or separated by:
1291             # whitespace
1292             # T (which must be followed by a number)
1293             # a dash
1294              
1295 74         1248 my $cdaterx = $self->_iso8601_rx('cdate');
1296 74         360 my $tdaterx = $self->_iso8601_rx('tdate');
1297 74         385 my $ctimerx = $self->_iso8601_rx('ctime');
1298 74         608 my $ttimerx = $self->_iso8601_rx('ttime');
1299              
1300 74         402 my $sep = qr/(?:T|\-|\s*)/i;
1301              
1302 74         585606 my $daterx = qr/^\s*(?: $cdaterx(?:$sep(?:$ctimerx|$ttimerx))? |
1303             $tdaterx |
1304             $ctimerx |
1305             $ttimerx
1306             )\s*$/x;
1307              
1308 74         3817 $$dmb{'data'}{'rx'}{'iso'}{'fulldate'} = $daterx;
1309             }
1310              
1311 340         1667 return $$dmb{'data'}{'rx'}{'iso'}{$rx};
1312             }
1313              
1314             sub _parse_datetime_iso8601 {
1315 2299     2299   4728 my($self,$string,$noupdate) = @_;
1316 2299         4123 my $dmt = $$self{'tz'};
1317 2299         3562 my $dmb = $$dmt{'base'};
1318 2299         5278 my $daterx = $self->_iso8601_rx('fulldate');
1319              
1320 2299         7013 my($y,$m,$d,$h,$mn,$s,$tzstring,$zone,$abb,$off);
1321 2299         0 my($doy,$dow,$yod,$c,$w,$fh,$fm,$h24);
1322              
1323 2299 100       47896 if ($string =~ $daterx) {
1324             ($y,$m,$d,$h,$mn,$s,$doy,$dow,$yod,$c,$w,$fh,$fm,$h24,
1325             $tzstring,$zone,$abb,$off) =
1326 314         10373 @+{qw(y m d h mn s doy dow yod c w fh fm h24 tzstring zone abb off)};
1327              
1328 314 100 100     2823 if (defined $w || defined $dow) {
    100          
1329 39         126 ($y,$m,$d) = $self->_def_date_dow($y,$w,$dow,$noupdate);
1330             } elsif (defined $doy) {
1331 16         67 ($y,$m,$d) = $self->_def_date_doy($y,$doy,$noupdate);
1332             } else {
1333 259 50       716 $y = $c . '00' if (defined $c);
1334 259         934 ($y,$m,$d) = $self->_def_date($y,$m,$d,$noupdate);
1335             }
1336              
1337 314         1184 ($h,$mn,$s) = $self->_time($h,$mn,$s,$fh,$fm,$h24,undef,$noupdate);
1338             } else {
1339 1985         6950 return (0);
1340             }
1341              
1342 314         1482 return (1,$y,$m,$d,$h,$mn,$s,$tzstring,$zone,$abb,$off);
1343             }
1344              
1345             sub _parse_date_iso8601 {
1346 1012     1012   2026 my($self,$string,$noupdate) = @_;
1347 1012         1758 my $dmt = $$self{'tz'};
1348 1012         1543 my $dmb = $$dmt{'base'};
1349 1012         2382 my $daterx = $self->_iso8601_rx('date');
1350              
1351 1012         2794 my($y,$m,$d);
1352 1012         0 my($doy,$dow,$yod,$c,$w);
1353              
1354 1012 100       26811 if ($string =~ /^$daterx$/) {
1355             ($y,$m,$d,$doy,$dow,$yod,$c,$w) =
1356 70         1178 @+{qw(y m d doy dow yod c w)};
1357              
1358 70 100 100     447 if (defined $w || defined $dow) {
    100          
1359 30         85 ($y,$m,$d) = $self->_def_date_dow($y,$w,$dow,$noupdate);
1360             } elsif (defined $doy) {
1361 7         28 ($y,$m,$d) = $self->_def_date_doy($y,$doy,$noupdate);
1362             } else {
1363 33 50       81 $y = $c . '00' if (defined $c);
1364 33         105 ($y,$m,$d) = $self->_def_date($y,$m,$d,$noupdate);
1365             }
1366             } else {
1367 942         3857 return (0);
1368             }
1369              
1370 70         342 return (1,$y,$m,$d);
1371             }
1372              
1373             # Handle all of the time fields.
1374             #
1375 168     168   1749 no integer;
  168         436  
  168         1560  
1376             sub _time {
1377 1442     1442   3888 my($self,$h,$mn,$s,$fh,$fm,$h24,$ampm,$noupdate) = @_;
1378              
1379 1442 100 66     3767 if (defined($ampm) && $ampm) {
1380 76         184 my $dmt = $$self{'tz'};
1381 76         134 my $dmb = $$dmt{'base'};
1382 76 100       356 if ($$dmb{'data'}{'wordmatch'}{'ampm'}{lc($ampm)} == 2) {
1383             # pm times
1384 70 50       244 $h+=12 unless ($h==12);
1385             } else {
1386             # am times
1387 6 100       34 $h=0 if ($h==12);
1388             }
1389             }
1390              
1391 1442 100 66     6448 if (defined $h24) {
    100 66        
    100          
1392 4         21 return(24,0,0);
1393             } elsif (defined $fh && $fh ne "") {
1394 12         35 $fh = "0.$fh";
1395 12         53 $s = int($fh * 3600);
1396 12         35 $mn = int($s/60);
1397 12         24 $s -= $mn*60;
1398             } elsif (defined $fm && $fm ne "") {
1399 8         20 $fm = "0.$fm";
1400 8         185 $s = int($fm*60);
1401             }
1402 1438         3487 ($h,$mn,$s) = $self->_def_time($h,$mn,$s,$noupdate);
1403 1438         4204 return($h,$mn,$s);
1404             }
1405 168     168   41203 use integer;
  168         450  
  168         1099  
1406              
1407             # Set up the regular expressions for other date and time formats. Returns the
1408             # requested regexp.
1409             #
1410             sub _other_rx {
1411 488     488   1266 my($self,$rx) = @_;
1412 488         912 my $dmt = $$self{'tz'};
1413 488         821 my $dmb = $$dmt{'base'};
1414 488 50       1255 $rx = '_' if (! defined $rx);
1415              
1416 488 100       3265 if ($rx eq 'time') {
    100          
    100          
    100          
    100          
    100          
    100          
    50          
1417              
1418 60         172 my $h24 = '(?2[0-3]|1[0-9]|0?[0-9])'; # 0-23 00-23
1419 60         243 my $h12 = '(?1[0-2]|0?[1-9])'; # 1-12 01-12
1420 60         160 my $mn = '(?[0-5][0-9])'; # 00-59
1421 60         196 my $ss = '(?[0-5][0-9])'; # 00-59
1422              
1423             # how to express fractions
1424              
1425 60         170 my($f1,$f2,$sepfr);
1426 60 100 66     701 if (exists $$dmb{'data'}{'rx'}{'sepfr'} &&
1427             $$dmb{'data'}{'rx'}{'sepfr'}) {
1428 3         11 $sepfr = $$dmb{'data'}{'rx'}{'sepfr'};
1429             } else {
1430 57         195 $sepfr = '';
1431             }
1432              
1433 60 100       215 if ($sepfr) {
1434 3         13 $f1 = "(?:[.,]|$sepfr)";
1435 3         14 $f2 = "(?:[.,:]|$sepfr)";
1436             } else {
1437 57         148 $f1 = "[.,]";
1438 57         138 $f2 = "[.,:]";
1439             }
1440 60         243 my $fh = "(?:$f1(?\\d*))"; # fractional hours (keep)
1441 60         204 my $fm = "(?:$f1(?\\d*))"; # fractional minutes (keep)
1442 60         197 my $fs = "(?:$f2\\d*)"; # fractional seconds
1443              
1444             # AM/PM
1445              
1446 60         117 my($ampm);
1447 60 50       268 if (exists $$dmb{'data'}{'rx'}{'ampm'}) {
1448 60         328 $ampm = "(?:\\s*(?$$dmb{data}{rx}{ampm}[0]))";
1449             }
1450              
1451             # H:MN and MN:S separators
1452              
1453 60         211 my @hm = ("\Q:\E");
1454 60         181 my @ms = ("\Q:\E");
1455 60 100       474 if ($dmb->_config('periodtimesep')) {
1456 1         4 push(@hm,"\Q.\E");
1457 1         2 push(@ms,"\Q.\E");
1458             }
1459 60 50 66     692 if (exists $$dmb{'data'}{'rx'}{'sephm'} &&
      66        
      33        
1460             defined $$dmb{'data'}{'rx'}{'sephm'} &&
1461             exists $$dmb{'data'}{'rx'}{'sepms'} &&
1462             defined $$dmb{'data'}{'rx'}{'sepms'}) {
1463 8         25 push(@hm,@{ $$dmb{'data'}{'rx'}{'sephm'} });
  8         33  
1464 8         18 push(@ms,@{ $$dmb{'data'}{'rx'}{'sepms'} });
  8         27  
1465             }
1466              
1467             # How to express the time
1468             # matches = (H, FH, MN, FMN, S, AM, TZSTRING, ZONE, ABB, OFF, ABB)
1469              
1470 60         183 my @timerx;
1471              
1472 60         381 for (my $i=0; $i<=$#hm; $i++) {
1473 70         199 my $hm = $hm[$i];
1474 70         170 my $ms = $ms[$i];
1475 70 50       680 push(@timerx,
1476             "${h12}$hm${mn}$ms${ss}${fs}?${ampm}?", # H12:MN:SS[,S+] [AM]
1477             ) if ($ampm);
1478              
1479 70         621 push(@timerx,
1480             "${h24}$hm${mn}$ms${ss}${fs}?", # H24:MN:SS[,S+]
1481             "(?24)$hm(?00)$ms(?00)", # 24:00:00
1482             );
1483             }
1484 60         377 for (my $i=0; $i<=$#hm; $i++) {
1485 70         195 my $hm = $hm[$i];
1486 70         203 my $ms = $ms[$i];
1487 70 50       633 push(@timerx,
1488             "${h12}$hm${mn}${fm}${ampm}?", # H12:MN,M+ [AM]
1489             ) if ($ampm);
1490 70         391 push(@timerx,
1491             "${h24}$hm${mn}${fm}", # H24:MN,M+
1492             );
1493             }
1494 60         594 for (my $i=0; $i<=$#hm; $i++) {
1495 70         192 my $hm = $hm[$i];
1496 70         166 my $ms = $ms[$i];
1497 70 50       397 push(@timerx,
1498             "${h12}$hm${mn}${ampm}?", # H12:MN [AM]
1499             ) if ($ampm);
1500 70         396 push(@timerx,
1501             "${h24}$hm${mn}", # H24:MN
1502             "(?24)$hm(?00)", # 24:00
1503             );
1504             }
1505              
1506 60 50       584 push(@timerx,
1507             "${h12}${fh}${ampm}", # H12,H+ AM
1508             "${h12}${ampm}", # H12 AM
1509             ) if ($ampm);
1510 60         215 push(@timerx,
1511             "${h24}${fh}", # H24,H+
1512             );
1513              
1514 60         369 my $timerx = join('|',@timerx);
1515 60         340 my $zrx = $dmt->_zrx('zrx');
1516 60         304 my $at = $$dmb{'data'}{'rx'}{'at'};
1517 60         1578 my $atrx = qr/(?:^|\s+)(?:$at)\s+/;
1518 60         239229 $timerx = qr/(?:$atrx|^|\s+)(?:$timerx)(?:\s*$zrx)?(?:\s+|$)/i;
1519              
1520 60         1730 $$dmb{'data'}{'rx'}{'other'}{$rx} = $timerx;
1521              
1522             } elsif ($rx eq 'common_1') {
1523              
1524             # These are of the format M/D/Y
1525              
1526             # Do NOT replace and with a regular expression to
1527             # match 1-12 since the DateFormat config may reverse the two.
1528 71         278 my $y4 = '(?\d\d\d\d)';
1529 71         181 my $y2 = '(?\d\d)';
1530 71         191 my $m = '(?\d\d?)';
1531 71         187 my $d = '(?\d\d?)';
1532 71         186 my $sep = '(?[\s\.\/\-])';
1533              
1534 71         700 my @daterx =
1535             (
1536             "${m}${sep}${d}\\k$y4", # M/D/YYYY
1537             "${m}${sep}${d}\\k$y2", # M/D/YY
1538             "${m}${sep}${d}", # M/D
1539             );
1540 71         334 my $daterx = join('|',@daterx);
1541              
1542 71         4332 $daterx = qr/^\s*(?:$daterx)\s*$/;
1543 71         693 $$dmb{'data'}{'rx'}{'other'}{$rx} = $daterx;
1544              
1545             } elsif ($rx eq 'common_2') {
1546              
1547 71         342 my $abb = $$dmb{'data'}{'rx'}{'month_abb'}[0];
1548 71         318 my $nam = $$dmb{'data'}{'rx'}{'month_name'}[0];
1549              
1550 71         208 my $y4 = '(?\d\d\d\d)';
1551 71         174 my $y2 = '(?\d\d)';
1552 71         181 my $m = '(?\d\d?)';
1553 71         164 my $d = '(?\d\d?)';
1554 71         175 my $dd = '(?\d\d)';
1555 71         420 my $mmm = "(?:(?$abb)|(?$nam))";
1556 71         195 my $sep = '(?[\s\.\/\-])';
1557              
1558 71         366 my $format_mmmyyyy = $dmb->_config('format_mmmyyyy');
1559              
1560 71         270 my @daterx = ();
1561 71         683 push(@daterx,
1562             "${y4}${sep}${m}\\k$d", # YYYY/M/D
1563             "${mmm}\\s*${dd}\\s*${y4}", # mmmDDYYYY
1564             );
1565 71 100       466 push(@daterx,
1566             "${mmm}\\s*${dd}\\s*${y2}", # mmmDDYY
1567             ) if (! $format_mmmyyyy);
1568 71         2724 push(@daterx,
1569             "${mmm}\\s*${d}", # mmmD
1570             "${d}\\s*${mmm}\\s*${y4}", # DmmmYYYY
1571             "${d}\\s*${mmm}\\s*${y2}", # DmmmYY
1572             "${d}\\s*${mmm}", # Dmmm
1573             "${y4}\\s*${mmm}\\s*${d}", # YYYYmmmD
1574              
1575             "${mmm}${sep}${d}\\k${y4}", # mmm/D/YYYY
1576             "${mmm}${sep}${d}\\k${y2}", # mmm/D/YY
1577             "${mmm}${sep}${d}", # mmm/D
1578             "${d}${sep}${mmm}\\k${y4}", # D/mmm/YYYY
1579             "${d}${sep}${mmm}\\k${y2}", # D/mmm/YY
1580             "${d}${sep}${mmm}", # D/mmm
1581             "${y4}${sep}${mmm}\\k${d}", # YYYY/mmm/D
1582              
1583             "${mmm}${sep}?${d}\\s+${y2}", # mmmD YY mmm/D YY
1584             "${mmm}${sep}?${d}\\s+${y4}", # mmmD YYYY mmm/D YYYY
1585             "${d}${sep}?${mmm}\\s+${y2}", # Dmmm YY D/mmm YY
1586             "${d}${sep}?${mmm}\\s+${y4}", # Dmmm YYYY D/mmm YYYY
1587              
1588             "${y2}\\s+${mmm}${sep}?${d}", # YY mmmD YY mmm/D
1589             "${y4}\\s+${mmm}${sep}?${d}", # YYYY mmmD YYYY mmm/D
1590             "${y2}\\s+${d}${sep}?${mmm}", # YY Dmmm YY D/mmm
1591             "${y4}\\s+${d}${sep}?${mmm}", # YYYY Dmmm YYYY D/mmm
1592              
1593             "${y4}:${m}:${d}", # YYYY:MM:DD
1594             );
1595 71         989 my $daterx = join('|',@daterx);
1596              
1597 71         147985 $daterx = qr/^\s*(?:$daterx)\s*$/i;
1598 71         2615 $$dmb{'data'}{'rx'}{'other'}{$rx} = $daterx;
1599              
1600             } elsif ($rx eq 'truncated') {
1601              
1602 35         175 my $abb = $$dmb{'data'}{'rx'}{'month_abb'}[0];
1603 35         132 my $nam = $$dmb{'data'}{'rx'}{'month_name'}[0];
1604              
1605 35         151 my $y4 = '(?\d\d\d\d)';
1606 35         227 my $mmm = "(?:(?$abb)|(?$nam))";
1607 35         88 my $sep = '(?[\s\.\/\-])';
1608              
1609 35         184 my $format_mmmyyyy = $dmb->_config('format_mmmyyyy');
1610              
1611 35         167 my @daterx = ();
1612 35 100       181 push(@daterx,
1613             "${mmm}\\s*${y4}", # mmmYYYY
1614             "${y4}\\s*${mmm}", # YYYYmmm
1615              
1616             "${y4}${sep}${mmm}", # YYYY/mmm
1617             "${mmm}${sep}${y4}", # mmm/YYYY
1618             ) if ($format_mmmyyyy);
1619              
1620 35 100       182 if (@daterx) {
1621 4         19 my $daterx = join('|',@daterx);
1622 4         1568 $daterx = qr/^\s*(?:$daterx)\s*$/i;
1623 4         65 $$dmb{'data'}{'rx'}{'other'}{$rx} = $daterx;
1624             } else {
1625 31         132 $$dmb{'data'}{'rx'}{'other'}{$rx} = '';
1626             }
1627              
1628             } elsif ($rx eq 'dow') {
1629              
1630 71         380 my $day_abb = $$dmb{'data'}{'rx'}{'day_abb'}[0];
1631 71         289 my $day_name = $$dmb{'data'}{'rx'}{'day_name'}[0];
1632              
1633 71         250 my $on = $$dmb{'data'}{'rx'}{'on'};
1634 71         1620 my $onrx = qr/(?:^|\s+)(?:$on)\s+/;
1635 71     1   8750 my $dowrx = qr/(?:$onrx|^|\s+)(?$day_name|$day_abb)($|\s+)/i;
  1         12  
  1         2  
  1         18  
1636              
1637 71         27852 $$dmb{'data'}{'rx'}{'other'}{$rx} = $dowrx;
1638              
1639             } elsif ($rx eq 'ignore') {
1640              
1641 71         348 my $of = $$dmb{'data'}{'rx'}{'of'};
1642              
1643 71         2270 my $ignrx = qr/(?:^|\s+)(?$of)(\s+|$)/;
1644 71         612 $$dmb{'data'}{'rx'}{'other'}{$rx} = $ignrx;
1645              
1646             } elsif ($rx eq 'miscdatetime') {
1647              
1648 63         246 my $special = $$dmb{'data'}{'rx'}{'offset_time'}[0];
1649              
1650 63         345 $special = "(?$special)";
1651 63         191 my $secs = "(?[-+]?\\d+)";
1652 63         277 my $abb = $$dmb{'data'}{'rx'}{'month_abb'}[0];
1653 63         247 my $mmm = "(?$abb)";
1654 63         174 my $y4 = '(?\d\d\d\d)';
1655 63         168 my $dd = '(?\d\d)';
1656 63         169 my $h24 = '(?2[0-3]|[01][0-9])'; # 00-23
1657 63         131 my $mn = '(?[0-5][0-9])'; # 00-59
1658 63         150 my $ss = '(?[0-5][0-9])'; # 00-59
1659 63         395 my $offrx = $dmt->_zrx('offrx');
1660 63         272 my $zrx = $dmt->_zrx('zrx');
1661              
1662 63         1912 my @daterx =
1663             (
1664             "${special}", # now
1665             "${special}\\s+${zrx}", # now EDT
1666              
1667             "epoch\\s+$secs", # epoch SECS
1668             "epoch\\s+$secs\\s+${zrx}", # epoch SECS EDT
1669              
1670             "${dd}\\/${mmm}\\/${y4}:${h24}:${mn}:${ss}\\s*${offrx}",
1671             # Common log format: 10/Oct/2000:13:55:36 -0700
1672             );
1673 63         3967 my $daterx = join('|',@daterx);
1674              
1675 63         461952 $daterx = qr/^\s*(?:$daterx)\s*$/i;
1676 63         1872 $$dmb{'data'}{'rx'}{'other'}{$rx} = $daterx;
1677              
1678             } elsif ($rx eq 'misc') {
1679              
1680 46         203 my $abb = $$dmb{'data'}{'rx'}{'month_abb'}[0];
1681 46         164 my $nam = $$dmb{'data'}{'rx'}{'month_name'}[0];
1682 46         174 my $next = $$dmb{'data'}{'rx'}{'nextprev'}[0];
1683 46         150 my $last = $$dmb{'data'}{'rx'}{'last'};
1684 46         157 my $yf = $$dmb{data}{rx}{fields}[1];
1685 46         139 my $mf = $$dmb{data}{rx}{fields}[2];
1686 46         155 my $wf = $$dmb{data}{rx}{fields}[3];
1687 46         152 my $df = $$dmb{data}{rx}{fields}[4];
1688 46         174 my $nth = $$dmb{'data'}{'rx'}{'nth'}[0];
1689 46         166 my $nth_wom = $$dmb{'data'}{'rx'}{'nth_wom'}[0];
1690 46         159 my $special = $$dmb{'data'}{'rx'}{'offset_date'}[0];
1691              
1692 46         109 my $y = '(?:(?\d\d\d\d)|(?\d\d))';
1693 46         260 my $mmm = "(?:(?$abb)|(?$nam))";
1694 46         219 $next = "(?$next)";
1695 46         523 $last = "(?$last)";
1696 46         151 $yf = "(?$yf)";
1697 46         143 $mf = "(?$mf)";
1698 46         141 $wf = "(?$wf)";
1699 46         136 $df = "(?$df)";
1700 46         226 my $fld = "(?:$yf|$mf|$wf)";
1701 46         260 $nth = "(?$nth)";
1702 46         176 $nth_wom = "(?$nth_wom)";
1703 46         202 $special = "(?$special)";
1704              
1705 46         2195 my @daterx =
1706             (
1707             "${mmm}\\s+${nth}\\s*$y?", # Dec 1st [1970]
1708             "${nth}\\s+${mmm}\\s*$y?", # 1st Dec [1970]
1709             "$y\\s+${mmm}\\s+${nth}", # 1970 Dec 1st
1710             "$y\\s+${nth}\\s+${mmm}", # 1970 1st Dec
1711              
1712             "${next}\\s+${fld}", # next year, next month, next week
1713             "${next}", # next friday
1714              
1715             "${last}\\s+${mmm}\\s*$y?", # last friday in october 95
1716             "${last}\\s+${df}\\s+${mmm}\\s*$y?",
1717             # last day in october 95
1718             "${last}\\s*$y?", # last friday in 95
1719              
1720             "${nth_wom}\\s+${mmm}\\s*$y?", # nth DoW in MMM [YYYY]
1721             "${nth}\\s*$y?", # nth DoW in [YYYY]
1722              
1723             "${nth}\\s+$df\\s+${mmm}\\s*$y?",
1724             # nth day in MMM [YYYY]
1725              
1726             "${nth}\\s+${wf}\\s*$y?", # DoW Nth week [YYYY]
1727             "${wf}\\s+(?\\d+)\\s*$y?", # DoW week N [YYYY]
1728              
1729             "${special}", # today, tomorrow
1730             "${special}\\s+${wf}", # today week
1731             # British: same as 1 week from today
1732              
1733             "${nth}", # nth
1734              
1735             "${wf}", # monday week
1736             # British: same as 'in 1 week on monday'
1737             );
1738 46         2317 my $daterx = join('|',@daterx);
1739              
1740 46         306967 $daterx = qr/^\s*(?:$daterx)\s*$/i;
1741 46         3441 $$dmb{'data'}{'rx'}{'other'}{$rx} = $daterx;
1742              
1743             }
1744              
1745 488         2141 return $$dmb{'data'}{'rx'}{'other'}{$rx};
1746             }
1747              
1748             sub _parse_time {
1749 1996     1996   5211 my($self,$caller,$string,$noupdate,%opts) = @_;
1750 1996         3456 my $dmt = $$self{'tz'};
1751 1996         3197 my $dmb = $$dmt{'base'};
1752              
1753 1996         4060 my($timerx,$h,$mn,$s,$fh,$fm,$h24,$ampm,$tzstring,$zone,$abb,$off);
1754 1996         2916 my $got_time = 0;
1755              
1756             # Check for ISO 8601 time
1757             #
1758             # This is only called via. parse_time (parse_date uses a regexp
1759             # that matches a full ISO 8601 date/time instead of parsing them
1760             # separately. Since some ISO 8601 times are a substring of non-ISO
1761             # 8601 times (i.e. 12:30 is a substring of '12:30 PM'), we need to
1762             # match entire strings here.
1763              
1764 1996 100       4685 if ($caller eq 'parse_time') {
1765             $timerx = (exists $$dmb{'data'}{'rx'}{'iso'}{'time'} ?
1766 30 100       95 $$dmb{'data'}{'rx'}{'iso'}{'time'} :
1767             $self->_iso8601_rx('time'));
1768              
1769 30 50       63 if (! exists $opts{'noiso8601'}) {
1770 30 100       4628 if ($string =~ s/^\s*$timerx\s*$//) {
1771             ($h,$fh,$mn,$fm,$s,$ampm,$tzstring,$zone,$abb,$off) =
1772 14         243 @+{qw(h fh mn fm s ampm tzstring zone abb off)};
1773              
1774 14         79 ($h,$mn,$s) = $self->_def_time($h,$mn,$s,$noupdate);
1775 14 0 33     51 $h24 = 1 if ($h == 24 && $mn == 0 && $s == 0);
      33        
1776 14         64 $string =~ s/\s*$//;
1777 14         33 $got_time = 1;
1778             }
1779             }
1780             }
1781              
1782             # Make time substitutions (i.e. noon => 12:00:00)
1783              
1784 1996 50 66     8048 if (! $got_time &&
1785             ! exists $opts{'noother'}) {
1786 1982         2834 my @rx = @{ $$dmb{'data'}{'rx'}{'times'} };
  1982         5299  
1787 1982         3539 shift(@rx);
1788 1982         4013 foreach my $rx (@rx) {
1789 4053 100       25918 if ($string =~ $rx) {
1790 179         927 my $repl = $$dmb{'data'}{'wordmatch'}{'times'}{lc($1)};
1791 179         1853 $string =~ s/$rx/$repl/g;
1792             }
1793             }
1794             }
1795              
1796             # Check to see if there is a time in the string
1797              
1798 1996 100       4701 if (! $got_time) {
1799             $timerx = (exists $$dmb{'data'}{'rx'}{'other'}{'time'} ?
1800 1982 100       5954 $$dmb{'data'}{'rx'}{'other'}{'time'} :
1801             $self->_other_rx('time'));
1802              
1803 1982 100       46146 if ($string =~ s/$timerx/ /) {
1804             ($h,$fh,$mn,$fm,$s,$ampm,$tzstring,$zone,$abb,$off) =
1805 1119         21570 @+{qw(h fh mn fm s ampm tzstring zone abb off)};
1806              
1807 1119         5848 ($h,$mn,$s) = $self->_def_time($h,$mn,$s,$noupdate);
1808 1119 50 66     3875 $h24 = 1 if ($h == 24 && $mn == 0 && $s == 0);
      66        
1809 1119         7359 $string =~ s/\s*$//;
1810 1119         2362 $got_time = 1;
1811             }
1812             }
1813              
1814             # If we called this from $date->parse()
1815             # returns the string and a list of time components
1816              
1817 1996 100       5120 if ($caller eq 'parse') {
1818 1966 100       3796 if ($got_time) {
1819 1103         3189 ($h,$mn,$s) = $self->_time($h,$mn,$s,$fh,$fm,$h24,$ampm,$noupdate);
1820 1103         5409 return ($got_time,$string,$h,$mn,$s,$tzstring,$zone,$abb,$off);
1821             } else {
1822 863         2663 return (0);
1823             }
1824             }
1825              
1826             # If we called this from $date->parse_time()
1827              
1828 30 100 66     136 if (! $got_time || $string) {
1829 5         16 $$self{'err'} = "[$caller] Invalid time string";
1830 5         16 return ();
1831             }
1832              
1833 25         71 ($h,$mn,$s) = $self->_time($h,$mn,$s,$fh,$fm,$h24,$ampm,$noupdate);
1834 25         101 return ($h,$mn,$s,$tzstring,$zone,$abb,$off);
1835             }
1836              
1837             # Parse common dates
1838             sub _parse_date_common {
1839 2902     2902   6159 my($self,$string,$noupdate) = @_;
1840 2902         4906 my $dmt = $$self{'tz'};
1841 2902         4495 my $dmb = $$dmt{'base'};
1842              
1843             # Since we want whitespace to be used as a separator, turn all
1844             # whitespace into single spaces. This is necessary since the
1845             # regexps do backreferences to make sure that separators are
1846             # not mixed.
1847 2902         13266 $string =~ s/\s+/ /g;
1848              
1849             my $daterx = (exists $$dmb{'data'}{'rx'}{'other'}{'common_1'} ?
1850 2902 100       9104 $$dmb{'data'}{'rx'}{'other'}{'common_1'} :
1851             $self->_other_rx('common_1'));
1852              
1853 2902 100       19470 if ($string =~ $daterx) {
1854 228         1951 my($y,$m,$d) = @+{qw(y m d)};
1855              
1856 228 100       1234 if ($dmb->_config('dateformat') ne 'US') {
1857 20         60 ($m,$d) = ($d,$m);
1858             }
1859              
1860 228         769 ($y,$m,$d) = $self->_def_date($y,$m,$d,$noupdate);
1861 228         807 return($y,$m,$d);
1862             }
1863              
1864             $daterx = (exists $$dmb{'data'}{'rx'}{'other'}{'common_2'} ?
1865 2674 100       8323 $$dmb{'data'}{'rx'}{'other'}{'common_2'} :
1866             $self->_other_rx('common_2'));
1867              
1868 2674 100       42244 if ($string =~ $daterx) {
1869 1345         15584 my($y,$m,$d,$mmm,$month) = @+{qw(y m d mmm month)};
1870              
1871 1345 100       4941 if ($mmm) {
    100          
1872 1224         4518 $m = $$dmb{'data'}{'wordmatch'}{'month_abb'}{lc($mmm)};
1873             } elsif ($month) {
1874 115         463 $m = $$dmb{'data'}{'wordmatch'}{'month_name'}{lc($month)};
1875             }
1876              
1877 1345         3878 ($y,$m,$d) = $self->_def_date($y,$m,$d,$noupdate);
1878 1345         4469 return($y,$m,$d);
1879             }
1880              
1881 1329         4075 return ();
1882             }
1883              
1884             # Parse truncated dates
1885             sub _parse_date_truncated {
1886 420     420   950 my($self,$string,$noupdate) = @_;
1887 420         816 my $dmt = $$self{'tz'};
1888 420         704 my $dmb = $$dmt{'base'};
1889              
1890             my $daterx = (exists $$dmb{'data'}{'rx'}{'other'}{'truncated'} ?
1891 420 100       1258 $$dmb{'data'}{'rx'}{'other'}{'truncated'} :
1892             $self->_other_rx('truncated'));
1893              
1894 420 100       1117 return () if (! $daterx);
1895              
1896             # Since we want whitespace to be used as a separator, turn all
1897             # whitespace into single spaces. This is necessary since the
1898             # regexps do backreferences to make sure that separators are
1899             # not mixed.
1900 16         47 $string =~ s/\s+/ /g;
1901              
1902 16 50       129 if ($string =~ $daterx) {
1903 16         180 my($y,$mmm,$month) = @+{qw(y mmm month)};
1904              
1905 16         53 my ($m,$d);
1906 16 50       38 if ($mmm) {
    0          
1907 16         60 $m = $$dmb{'data'}{'wordmatch'}{'month_abb'}{lc($mmm)};
1908             } elsif ($month) {
1909 0         0 $m = $$dmb{'data'}{'wordmatch'}{'month_name'}{lc($month)};
1910             }
1911              
1912             # Handle all of the mmmYYYY formats
1913              
1914 16 50 33     73 if ($y && $m) {
1915              
1916 16         57 my $format_mmmyyyy = $dmb->_config('format_mmmyyyy');
1917 16 100       46 if ($format_mmmyyyy eq 'first') {
1918 8         12 $d=1;
1919 8         28 $$self{'data'}{'default_time'} = [0,0,0];
1920             } else {
1921 8         32 $d=$dmb->days_in_month($y,$m);
1922 8         30 $$self{'data'}{'default_time'} = [23,59,59];
1923             }
1924              
1925 16         50 $$self{'data'}{'def'}[0] = '';
1926 16         29 $$self{'data'}{'def'}[1] = '';
1927 16         44 $$self{'data'}{'def'}[2] = 1;
1928 16         55 return($y,$m,$d);
1929             }
1930             }
1931              
1932 0         0 return ();
1933             }
1934              
1935             sub _parse_tz {
1936 281     281   605 my($self,$string,$noupdate) = @_;
1937 281         467 my $dmt = $$self{'tz'};
1938 281         439 my($tzstring,$zone,$abb,$off);
1939              
1940 281         1041 my $rx = $dmt->_zrx('zrx');
1941 281 100       81463 if ($string =~ s/(?:^|\s)$rx(?:$|\s)/ /) {
1942 9         156 ($tzstring,$zone,$abb,$off) = @+{qw(tzstring zone abb off)};
1943 9         70 return($string,$tzstring,$zone,$abb,$off);
1944             }
1945 272         2151 return($string);
1946             }
1947              
1948             sub _parse_dow {
1949 2898     2898   6116 my($self,$string,$noupdate) = @_;
1950 2898         4968 my $dmt = $$self{'tz'};
1951 2898         4422 my $dmb = $$dmt{'base'};
1952 2898         4629 my($y,$m,$d,$dow);
1953              
1954             # Remove the day of week
1955              
1956             my $rx = (exists $$dmb{'data'}{'rx'}{'other'}{'dow'} ?
1957 2898 100       8092 $$dmb{'data'}{'rx'}{'other'}{'dow'} :
1958             $self->_other_rx('dow'));
1959 2898 100       24193 if ($string =~ s/$rx/ /) {
1960 1261         6816 $dow = $+{'dow'};
1961 1261         3527 $dow = lc($dow);
1962              
1963             $dow = $$dmb{'data'}{'wordmatch'}{'day_abb'}{$dow}
1964 1261 100       4816 if (exists $$dmb{'data'}{'wordmatch'}{'day_abb'}{$dow});
1965             $dow = $$dmb{'data'}{'wordmatch'}{'day_name'}{$dow}
1966 1261 100       4112 if (exists $$dmb{'data'}{'wordmatch'}{'day_name'}{$dow});
1967             } else {
1968 1637         4805 return (0);
1969             }
1970              
1971 1261         6594 $string =~ s/\s*$//;
1972 1261         3920 $string =~ s/^\s*//;
1973              
1974 1261 100       5715 return (0,$string,$dow) if ($string);
1975              
1976             # Handle the simple DoW format
1977              
1978 18         64 ($y,$m,$d) = $self->_def_date($y,$m,$d,$noupdate);
1979              
1980 18         36 my($w,$dow1);
1981              
1982 18         78 ($y,$w) = $dmb->week_of_year([$y,$m,$d]); # week of year
1983 18         37 ($y,$m,$d) = @{ $dmb->week_of_year($y,$w) }; # first day
  18         46  
1984 18         72 $dow1 = $dmb->day_of_week([$y,$m,$d]); # DoW of first day
1985 18 50       62 $dow1 -= 7 if ($dow1 > $dow);
1986 18         26 ($y,$m,$d) = @{ $dmb->calc_date_days([$y,$m,$d],$dow-$dow1) };
  18         63  
1987              
1988 18         101 return(1,$y,$m,$d);
1989             }
1990              
1991             sub _parse_holidays {
1992 257     257   526 my($self,$string,$noupdate) = @_;
1993 257         483 my $dmt = $$self{'tz'};
1994 257         403 my $dmb = $$dmt{'base'};
1995 257         405 my($y,$m,$d);
1996              
1997 257 100       762 if (! exists $$dmb{'data'}{'rx'}{'holidays'}) {
1998 150         372 return (0);
1999             }
2000              
2001 107         666 $string =~ s/\s*$//;
2002 107         366 $string =~ s/^\s*//;
2003              
2004 107         246 my $rx = $$dmb{'data'}{'rx'}{'holidays'};
2005 107 100       659 if ($string =~ $rx) {
2006 9         15 my $hol;
2007 9         83 ($y,$hol) = @+{qw(y holiday)};
2008 9 100       46 $y = $dmt->_now('y',$noupdate) if (! $y);
2009 9         23 $y += 0;
2010              
2011 9         41 $self->_holidays($y-1);
2012 9         25 $self->_holidays($y);
2013 9         37 $self->_holidays($y+1);
2014 9 50       39 return (0) if (! exists $$dmb{'data'}{'holidays'}{'yhols'}{$y+0}{$hol});
2015 9         15 my ($y,$m,$d) = @{ $$dmb{'data'}{'holidays'}{'yhols'}{$y+0}{$hol} };
  9         32  
2016 9         34 return(1,$y,$m,$d);
2017             }
2018              
2019 98         232 return (0);
2020             }
2021              
2022 168     168   704232 no integer;
  168         479  
  168         872  
2023             sub _parse_delta {
2024 334     334   877 my($self,$string,$dow,$got_time,$h,$mn,$s,$noupdate) = @_;
2025 334         678 my $dmt = $$self{'tz'};
2026 334         605 my $dmb = $$dmt{'base'};
2027 334         488 my($y,$m,$d);
2028              
2029 334         1047 my $delta = $self->new_delta();
2030 334         966 my $err = $delta->parse($string);
2031 334         1189 my $tz = $dmt->_now('tz');
2032 334         831 my $isdst = $dmt->_now('isdst');
2033              
2034 334 100       828 if (! $err) {
2035 36         63 my($dy,$dm,$dw,$dd,$dh,$dmn,$ds) = @{ $$delta{'data'}{'delta'} };
  36         116  
2036              
2037             # We can't handle a delta longer than 10000 years
2038 36 50 33     469 if (abs($dy) > 10000 ||
      33        
      33        
      33        
      33        
      33        
2039             abs($dm) > 120000 || # 10000*12
2040             abs($dw) > 530000 || # 10000*53
2041             abs($dd) > 3660000 || # 10000*366
2042             abs($dh) > 87840000 || # 10000*366*24
2043             abs($dmn) > 5270400000 || # 10000*366*24*60
2044             abs($ds) > 316224000000) { # 10000*366*24*60*60
2045 0         0 $$self{'err'} = '[parse] Delta too large';
2046 0         0 return (1);
2047             }
2048              
2049 36 100 66     149 if ($got_time &&
      66        
2050             ($dh != 0 || $dmn != 0 || $ds != 0)) {
2051 6         17 $$self{'err'} = '[parse] Two times entered or implied';
2052 6         35 return (1);
2053             }
2054              
2055 30 100       61 if ($got_time) {
2056 6         24 ($y,$m,$d) = $self->_def_date($y,$m,$d,$noupdate);
2057             } else {
2058 24         71 ($y,$m,$d,$h,$mn,$s) = $dmt->_now('now',$$noupdate);
2059 24         58 $$noupdate = 1;
2060             }
2061              
2062 30 50       86 my $business = ($$delta{'data'}{'mode'} eq 'business' ? 1 : 0);
2063              
2064 30         77 my($date2,$offset,$abbrev);
2065 30         164 ($err,$date2,$offset,$isdst,$abbrev) =
2066             $self->__calc_date_delta([$y,$m,$d,$h,$mn,$s],
2067             [$dy,$dm,$dw,$dd,$dh,$dmn,$ds],
2068             0,$business,$tz,$isdst);
2069 30         116 ($y,$m,$d,$h,$mn,$s) = @$date2;
2070              
2071 30 100       73 if ($dow) {
2072 10 50 33     67 if ($dd != 0 || $dh != 0 || $dmn != 0 || $ds != 0) {
      33        
      33        
2073 0         0 $$self{'err'} = '[parse] Day of week not allowed';
2074 0         0 return (1);
2075             }
2076              
2077 10         15 my($w,$dow1);
2078              
2079 10         41 ($y,$w) = $dmb->week_of_year([$y,$m,$d]); # week of year
2080 10         21 ($y,$m,$d) = @{ $dmb->week_of_year($y,$w) }; # first day
  10         23  
2081 10         31 $dow1 = $dmb->day_of_week([$y,$m,$d]); # DoW of first day
2082 10 50       27 $dow1 -= 7 if ($dow1 > $dow);
2083 10         13 ($y,$m,$d) = @{ $dmb->calc_date_days([$y,$m,$d],$dow-$dow1) };
  10         30  
2084             }
2085              
2086 30         303 return (1,$y,$m,$d,$h,$mn,$s);
2087             }
2088              
2089 298         1694 return (0);
2090             }
2091 168     168   76741 use integer;
  168         471  
  168         778  
2092              
2093             sub _parse_datetime_other {
2094 1990     1990   4186 my($self,$string,$noupdate) = @_;
2095 1990         3529 my $dmt = $$self{'tz'};
2096 1990         3688 my $dmb = $$dmt{'base'};
2097              
2098             my $rx = (exists $$dmb{'data'}{'rx'}{'other'}{'miscdatetime'} ?
2099 1990 100       6259 $$dmb{'data'}{'rx'}{'other'}{'miscdatetime'} :
2100             $self->_other_rx('miscdatetime'));
2101              
2102 1990 100       16555 if ($string =~ $rx) {
2103             my ($special,$epoch,$y,$mmm,$d,$h,$mn,$s,$tzstring,$zone,$abb,$off) =
2104 24         659 @+{qw(special epoch y mmm d h mn s tzstring zone abb off)};
2105              
2106 24 100       173 if (defined($special)) {
    100          
    50          
2107 18         99 my $delta = $$dmb{'data'}{'wordmatch'}{'offset_time'}{lc($special)};
2108 18         36 my @delta = @{ $dmb->split('delta',$delta) };
  18         80  
2109 18         121 my @date = $dmt->_now('now',$$noupdate);
2110 18         61 my $tz = $dmt->_now('tz');
2111 18         62 my $isdst = $dmt->_now('isdst');
2112 18         48 $$noupdate = 1;
2113              
2114 18         39 my($err,$date2,$offset,$abbrev);
2115 18         116 ($err,$date2,$offset,$isdst,$abbrev) =
2116             $self->__calc_date_delta([@date],[@delta],0,0,$tz,$isdst);
2117              
2118 18 100       77 if ($tzstring) {
2119              
2120 1 50       7 $date2 = [] if (! defined $date2);
2121 1 50       7 my $offset = (defined $off ? $dmb->_delta_convert('offset',$off) : '');
2122 1 50       4 $zone = (defined $zone ? lc($zone) : '');
2123 1 50       3 my $abbrev = (defined $abb ? lc($abb) : '');
2124              
2125             # In some cases, a valid abbreviation is also a valid timezone
2126 1         10 my $tmp = $dmt->__zone($date2,$offset,$zone,$abbrev,'');
2127 1 0 33     6 if (! $tmp && $abbrev && ! $zone) {
      33        
2128 0         0 $abbrev = $dmt->_zone($abbrev);
2129 0 0       0 $tmp = $dmt->__zone($date2,$offset,$abbrev,'','') if ($abbrev);
2130             }
2131 1         4 $zone = $tmp;
2132              
2133 1 50       4 return (0) if (! $zone);
2134              
2135 1         7 my(@tmp) = $dmt->_convert('_parse_datetime_other',$date2,$tz,$zone);
2136 1         4 $date2 = $tmp[1];
2137             }
2138              
2139 18         61 @date = @$date2;
2140              
2141 18         120 return (1,@date,$tzstring,$zone,$abb,$off);
2142              
2143             } elsif (defined($epoch)) {
2144 5         21 my $date = [1970,1,1,0,0,0];
2145 5         17 my @delta = (0,0,$epoch);
2146 5         26 $date = $dmb->calc_date_time($date,\@delta);
2147 5         27 my($err);
2148 5 100       34 if ($tzstring) {
2149              
2150 1 50       5 my $offset = (defined $off ? $dmb->_delta_convert('offset',$off) : '');
2151 1 50       4 $zone = (defined $zone ? lc($zone) : '');
2152 1 50       4 my $abbrev = (defined $abb ? lc($abb) : '');
2153              
2154             # In some cases, a valid abbreviation is also a valid timezone
2155 1         6 my $tmp = $dmt->__zone($date,$offset,$zone,$abbrev,'');
2156 1 0 33     12 if (! $tmp && $abbrev && ! $zone) {
      33        
2157 0         0 $abbrev = $dmt->_zone($abbrev);
2158 0 0       0 $tmp = $dmt->__zone($date,$offset,$abbrev,'','') if ($abbrev);
2159             }
2160 1         19 $zone = $tmp;
2161              
2162 1 50       10 return (0) if (! $zone);
2163              
2164 1         9 ($err,$date) = $dmt->convert_from_gmt($date,$zone);
2165             } else {
2166 4         18 ($err,$date) = $dmt->convert_from_gmt($date);
2167             }
2168 5         47 return (1,@$date,$tzstring,$zone,$abb,$off);
2169              
2170             } elsif (defined($y)) {
2171 1         8 my $m = $$dmb{'data'}{'wordmatch'}{'month_abb'}{lc($mmm)};
2172 1         6 return (1,$y,$m,$d,$h,$mn,$s,$tzstring,$zone,$abb,$off);
2173             }
2174             }
2175              
2176 1966         5420 return (0);
2177             }
2178              
2179             sub _parse_date_other {
2180 1329     1329   3403 my($self,$string,$dow,$of,$noupdate) = @_;
2181 1329         2280 my $dmt = $$self{'tz'};
2182 1329         2444 my $dmb = $$dmt{'base'};
2183 1329         2308 my($y,$m,$d,$h,$mn,$s);
2184              
2185             my $rx = (exists $$dmb{'data'}{'rx'}{'other'}{'misc'} ?
2186 1329 100       3884 $$dmb{'data'}{'rx'}{'other'}{'misc'} :
2187             $self->_other_rx('misc'));
2188              
2189 1329         3692 my($mmm,$month,$nextprev,$last,$field_y,$field_m,$field_w,$field_d,$nth);
2190 1329         0 my($special,$got_m,$n,$got_y);
2191              
2192 1329 100       16845 if ($string =~ $rx) {
2193             ($y,$mmm,$month,$nextprev,$last,$field_y,$field_m,$field_w,$field_d,$nth,
2194             $special,$n) =
2195 879         17708 @+{qw(y mmm month next last field_y field_m field_w field_d
2196             nth special n)};
2197              
2198 879 100       4125 if (defined($y)) {
2199 90         452 $y = $dmt->_fix_year($y);
2200 90         191 $got_y = 1;
2201 90 50       267 return () if (! $y);
2202             } else {
2203 789         3075 $y = $dmt->_now('y',$$noupdate);
2204 789         1448 $$noupdate = 1;
2205 789         1158 $got_y = 0;
2206 789         1857 $$self{'data'}{'def'}[0] = '';
2207             }
2208              
2209 879 100       1900 if (defined($mmm)) {
    100          
2210 698         2586 $m = $$dmb{'data'}{'wordmatch'}{'month_abb'}{lc($mmm)};
2211 698         1288 $got_m = 1;
2212             } elsif ($month) {
2213 31         161 $m = $$dmb{'data'}{'wordmatch'}{'month_name'}{lc($month)};
2214 31         59 $got_m = 1;
2215             }
2216              
2217 879 100       1825 if ($nth) {
2218 632         1874 $nth = $$dmb{'data'}{'wordmatch'}{'nth'}{lc($nth)};
2219             }
2220              
2221 879 100 100     8823 if ($got_m && $nth && ! $dow) {
    100 100        
    100 100        
    100 100        
    100 100        
    100 66        
    50          
2222             # Dec 1st 1970
2223             # 1st Dec 1970
2224             # 1970 Dec 1st
2225             # 1970 1st Dec
2226              
2227 32         59 $d = $nth;
2228              
2229             } elsif ($nextprev) {
2230              
2231 50         76 my $next = 0;
2232 50         69 my $sign = -1;
2233 50 100       189 if ($$dmb{'data'}{'wordmatch'}{'nextprev'}{lc($nextprev)} == 1) {
2234 22         35 $next = 1;
2235 22         33 $sign = 1;
2236             }
2237              
2238 50 100 100     295 if ($field_y || $field_m || $field_w) {
    50 100        
2239             # next/prev year/month/week
2240              
2241 28         47 my(@delta);
2242 28 100       81 if ($field_y) {
    100          
2243 8         28 @delta = ($sign*1,0,0,0,0,0,0);
2244             } elsif ($field_m) {
2245 10         33 @delta = (0,$sign*1,0,0,0,0,0);
2246             } else {
2247 10         32 @delta = (0,0,$sign*1,0,0,0,0);
2248             }
2249              
2250 28         74 my @now = $dmt->_now('now',$$noupdate);
2251 28         73 my $tz = $dmt->_now('tz');
2252 28         67 my $isdst = $dmt->_now('isdst');
2253 28         46 $$noupdate = 1;
2254              
2255 28         48 my($err,$offset,$abbrev,$date2);
2256 28         140 ($err,$date2,$offset,$isdst,$abbrev) =
2257             $self->__calc_date_delta([@now],[@delta],0,0,$tz,$isdst);
2258 28         148 ($y,$m,$d,$h,$mn,$s) = @$date2;
2259              
2260             } elsif ($dow) {
2261             # next/prev friday
2262              
2263 22         76 my @now = $dmt->_now('now',$$noupdate);
2264 22         38 $$noupdate = 1;
2265 22         35 ($y,$m,$d,$h,$mn,$s) = @{ $self->__next_prev(\@now,$next,$dow,0) };
  22         69  
2266 22         57 $dow = 0;
2267              
2268             } else {
2269 0         0 return ();
2270             }
2271              
2272             } elsif ($last) {
2273              
2274 127 100 66     1031 if ($field_d && $got_m) {
    100 66        
    50          
2275             # last day in october 95
2276              
2277 6         28 $d = $dmb->days_in_month($y,$m);
2278              
2279             } elsif ($dow && $got_m) {
2280             # last friday in october 95
2281              
2282 120         473 $d = $dmb->days_in_month($y,$m);
2283             ($y,$m,$d,$h,$mn,$s) =
2284 120         330 @{ $self->__next_prev([$y,$m,$d,0,0,0],0,$dow,1) };
  120         556  
2285 120         395 $dow = 0;
2286              
2287             } elsif ($dow) {
2288             # last friday in 95
2289              
2290             ($y,$m,$d,$h,$mn,$s) =
2291 1         3 @{ $self->__next_prev([$y,12,31,0,0,0],0,$dow,0) };
  1         5  
2292              
2293             } else {
2294 0         0 return ();
2295             }
2296              
2297             } elsif ($nth && $dow && ! $field_w) {
2298              
2299 584 100       1141 if ($got_m) {
2300 571 100       1086 if ($of) {
2301             # nth DoW of MMM [YYYY]
2302 569 100       1343 return () if ($nth > 5);
2303              
2304 567         832 $d = 1;
2305             ($y,$m,$d,$h,$mn,$s) =
2306 567         818 @{ $self->__next_prev([$y,$m,1,0,0,0],1,$dow,1) };
  567         2226  
2307 567         1215 my $m2 = $m;
2308 567 100       1352 ($y,$m2,$d) = @{ $dmb->calc_date_days([$y,$m,$d],7*($nth-1)) }
  454         1665  
2309             if ($nth > 1);
2310 567 50 33     2635 return () if (! $m2 || $m2 != $m);
2311              
2312             } else {
2313             # DoW, nth MMM [YYYY] (i.e. Sunday, 9th Dec 2008)
2314 2         5 $d = $nth;
2315             }
2316              
2317             } else {
2318             # nth DoW [in YYYY]
2319              
2320 13         23 ($y,$m,$d,$h,$mn,$s) = @{ $self->__next_prev([$y,1,1,0,0,0],1,$dow,1) };
  13         59  
2321 13 100       49 ($y,$m,$d) = @{ $dmb->calc_date_days([$y,$m,$d],7*($nth-1)) }
  9         36  
2322             if ($nth > 1);
2323             }
2324              
2325             } elsif ($field_w && $dow) {
2326              
2327 25 100 100     102 if (defined($n) || $nth) {
2328             # sunday week 22 in 1996
2329             # sunday 22nd week in 1996
2330              
2331 23 100       58 $n = $nth if ($nth);
2332 23 100       57 return () if (! $n);
2333 21         38 ($y,$m,$d) = @{ $dmb->week_of_year($y,$n) };
  21         75  
2334 21         36 ($y,$m,$d) = @{ $self->__next_prev([$y,$m,$d,0,0,0],1,$dow,1) };
  21         98  
2335              
2336             } else {
2337             # DoW week
2338              
2339 2         6 ($y,$m,$d) = $dmt->_now('now',$$noupdate);
2340 2         6 $$noupdate = 1;
2341 2         7 my $tmp = $dmb->_config('firstday');
2342 2         3 ($y,$m,$d) = @{ $self->__next_prev([$y,$m,$d,0,0,0],1,$tmp,0) };
  2         11  
2343 2         6 ($y,$m,$d) = @{ $self->__next_prev([$y,$m,$d,0,0,0],1,$dow,1) };
  2         8  
2344             }
2345              
2346             } elsif ($nth && ! $got_y) {
2347             # 'in one week' makes it here too so return nothing in that case so it
2348             # drops through to the deltas.
2349 5 50 66     45 return () if ($field_d || $field_w || $field_m || $field_y);
      66        
      66        
2350 4         15 ($y,$m,$d) = $dmt->_now('now',$$noupdate);
2351 4         18 $$noupdate = 1;
2352 4         9 $d = $nth;
2353              
2354             } elsif ($special) {
2355              
2356 56         287 my $delta = $$dmb{'data'}{'wordmatch'}{'offset_date'}{lc($special)};
2357 56         95 my @delta = @{ $dmb->split('delta',$delta) };
  56         190  
2358 56         249 ($y,$m,$d) = $dmt->_now('now',$$noupdate);
2359 56         259 my $tz = $dmt->_now('tz');
2360 56         187 my $isdst = $dmt->_now('isdst');
2361 56         134 $$noupdate = 1;
2362 56         103 my($err,$offset,$abbrev,$date2);
2363 56         302 ($err,$date2,$offset,$isdst,$abbrev) =
2364             $self->__calc_date_delta([$y,$m,$d,0,0,0],[@delta],0,0,$tz,$isdst);
2365 56         187 ($y,$m,$d) = @$date2;
2366              
2367 56 100       189 if ($field_w) {
2368 8         13 ($y,$m,$d) = @{ $dmb->calc_date_days([$y,$m,$d],7) };
  8         27  
2369             }
2370             }
2371              
2372             } else {
2373 450         1349 return ();
2374             }
2375              
2376 874         3209 return($y,$m,$d,$dow);
2377             }
2378              
2379             # Supply defaults for missing values (Y/M/D)
2380             sub _def_date {
2381 1904     1904   4766 my($self,$y,$m,$d,$noupdate) = @_;
2382 1904 100       4441 $y = '' if (! defined $y);
2383 1904 100       3928 $m = '' if (! defined $m);
2384 1904 100       3724 $d = '' if (! defined $d);
2385 1904         2908 my $defined = 0;
2386 1904         3350 my $dmt = $$self{'tz'};
2387 1904         3081 my $dmb = $$dmt{'base'};
2388              
2389             # If year was not specified, defaults to current year.
2390             #
2391             # We'll also fix the year (turn 2-digit into 4-digit).
2392              
2393 1904 100       4013 if ($y eq '') {
2394 324         1258 $y = $dmt->_now('y',$$noupdate);
2395 324         665 $$noupdate = 1;
2396 324         749 $$self{'data'}{'def'}[0] = '';
2397             } else {
2398 1580         6229 $y = $dmt->_fix_year($y);
2399 1580         2969 $defined = 1;
2400             }
2401              
2402             # If the month was not specifed, but the year was, a default of
2403             # 01 is supplied (this is a truncated date).
2404             #
2405             # If neither was specified, month defaults to the current month.
2406              
2407 1904 100       4313 if ($m ne '') {
    100          
2408 1839         2851 $defined = 1;
2409             } elsif ($defined) {
2410 4         11 $m = 1;
2411 4         13 $$self{'data'}{'def'}[1] = 1;
2412             } else {
2413 61         163 $m = $dmt->_now('m',$$noupdate);
2414 61         108 $$noupdate = 1;
2415 61         143 $$self{'data'}{'def'}[1] = '';
2416             }
2417              
2418             # If the day was not specified, but the year or month was, a default
2419             # of 01 is supplied (this is a truncated date).
2420             #
2421             # If none were specified, it default to the current day.
2422              
2423 1904 100       3866 if ($d ne '') {
    100          
2424 1835         2671 $defined = 1;
2425             } elsif ($defined) {
2426 13         25 $d = 1;
2427 13         34 $$self{'data'}{'def'}[2] = 1;
2428             } else {
2429 56         199 $d = $dmt->_now('d',$$noupdate);
2430 56         98 $$noupdate = 1;
2431 56         117 $$self{'data'}{'def'}[2] = '';
2432             }
2433              
2434 1904         6202 return($y,$m,$d);
2435             }
2436              
2437             # Supply defaults for missing values (Y/DoY)
2438             sub _def_date_doy {
2439 23     23   65 my($self,$y,$doy,$noupdate) = @_;
2440 23 100       57 $y = '' if (! defined $y);
2441 23         53 my $dmt = $$self{'tz'};
2442 23         37 my $dmb = $$dmt{'base'};
2443              
2444             # If year was not specified, defaults to current year.
2445             #
2446             # We'll also fix the year (turn 2-digit into 4-digit).
2447              
2448 23 100       61 if ($y eq '') {
2449 2         15 $y = $dmt->_now('y',$$noupdate);
2450 2         10 $$noupdate = 1;
2451 2         6 $$self{'data'}{'def'}[0] = '';
2452             } else {
2453 21         72 $y = $dmt->_fix_year($y);
2454             }
2455              
2456             # DoY must be specified.
2457              
2458 23         75 my($m,$d);
2459 23         87 my $ymd = $dmb->day_of_year($y,$doy);
2460              
2461 23         91 return @$ymd;
2462             }
2463              
2464             # Supply defaults for missing values (YY/Www/D) and (Y/Www/D)
2465             sub _def_date_dow {
2466 69     69   158 my($self,$y,$w,$dow,$noupdate) = @_;
2467 69 100       145 $y = '' if (! defined $y);
2468 69 100       168 $w = '' if (! defined $w);
2469 69 100       143 $dow = '' if (! defined $dow);
2470 69         124 my $dmt = $$self{'tz'};
2471 69         103 my $dmb = $$dmt{'base'};
2472              
2473             # If year was not specified, defaults to current year.
2474             #
2475             # If it was specified and is a single digit, it is the
2476             # year in the current decade.
2477             #
2478             # We'll also fix the year (turn 2-digit into 4-digit).
2479              
2480 69 100       152 if ($y ne '') {
2481 49 50       120 if (length($y) == 1) {
2482 0         0 my $tmp = $dmt->_now('y',$$noupdate);
2483 0         0 $tmp =~ s/.$/$y/;
2484 0         0 $y = $tmp;
2485 0         0 $$noupdate = 1;
2486              
2487             } else {
2488 49         170 $y = $dmt->_fix_year($y);
2489              
2490             }
2491              
2492             } else {
2493 20         75 $y = $dmt->_now('y',$$noupdate);
2494 20         32 $$noupdate = 1;
2495 20         57 $$self{'data'}{'def'}[0] = '';
2496             }
2497              
2498             # If week was not specified, it defaults to the current
2499             # week. Get the first day of the week.
2500              
2501 69         130 my($m,$d);
2502 69 100       136 if ($w ne '') {
2503 61         82 ($y,$m,$d) = @{ $dmb->week_of_year($y,$w) };
  61         180  
2504             } else {
2505 8         27 my($nowy,$nowm,$nowd) = $dmt->_now('now',$$noupdate);
2506 8         29 $$noupdate = 1;
2507 8         11 my $noww;
2508 8         40 ($nowy,$noww) = $dmb->week_of_year([$nowy,$nowm,$nowd]);
2509 8         26 ($y,$m,$d) = @{ $dmb->week_of_year($nowy,$noww) };
  8         25  
2510             }
2511              
2512             # Handle the DoW
2513              
2514 69 100       166 if ($dow eq '') {
2515 18         42 $dow = 1;
2516             }
2517 69         182 my $n = $dmb->days_in_month($y,$m);
2518 69         119 $d += ($dow-1);
2519 69 100       155 if ($d > $n) {
2520 5         7 $m++;
2521 5 50       15 if ($m==13) {
2522 0         0 $y++;
2523 0         0 $m = 1;
2524             }
2525 5         9 $d = $d-$n;
2526             }
2527              
2528 69         203 return($y,$m,$d);
2529             }
2530              
2531             # Supply defaults for missing values (HH:MN:SS)
2532             sub _def_time {
2533 2580     2580   5494 my($self,$h,$m,$s,$noupdate) = @_;
2534 2580 100       5359 $h = '' if (! defined $h);
2535 2580 100       4810 $m = '' if (! defined $m);
2536 2580 100       4781 $s = '' if (! defined $s);
2537 2580         3561 my $defined = 0;
2538 2580         4092 my $dmt = $$self{'tz'};
2539 2580         3744 my $dmb = $$dmt{'base'};
2540              
2541             # If no time was specified, defaults to 00:00:00.
2542              
2543 2580 50 66     6446 if ($h eq '' &&
      66        
2544             $m eq '' &&
2545             $s eq '') {
2546 126         281 $$self{'data'}{'def'}[3] = 1;
2547 126         230 $$self{'data'}{'def'}[4] = 1;
2548 126         227 $$self{'data'}{'def'}[5] = 1;
2549 126         362 return(0,0,0);
2550             }
2551              
2552             # If hour was not specified, defaults to current hour.
2553              
2554 2454 50       4382 if ($h ne '') {
2555 2454         3578 $defined = 1;
2556             } else {
2557 0         0 $h = $dmt->_now('h',$$noupdate);
2558 0         0 $$noupdate = 1;
2559 0         0 $$self{'data'}{'def'}[3] = '';
2560             }
2561              
2562             # If the minute was not specifed, but the hour was, a default of
2563             # 00 is supplied (this is a truncated time).
2564             #
2565             # If neither was specified, minute defaults to the current minute.
2566              
2567 2454 100       4184 if ($m ne '') {
    50          
2568 2437         3433 $defined = 1;
2569             } elsif ($defined) {
2570 17         48 $m = 0;
2571 17         38 $$self{'data'}{'def'}[4] = 1;
2572             } else {
2573 0         0 $m = $dmt->_now('mn',$$noupdate);
2574 0         0 $$noupdate = 1;
2575 0         0 $$self{'data'}{'def'}[4] = '';
2576             }
2577              
2578             # If the second was not specified (either the hour or the minute were),
2579             # a default of 00 is supplied (this is a truncated time).
2580              
2581 2454 100       4813 if ($s eq '') {
2582 288         646 $s = 0;
2583 288         703 $$self{'data'}{'def'}[5] = 1;
2584             }
2585              
2586 2454         7364 return($h,$m,$s);
2587             }
2588              
2589             ########################################################################
2590             # OTHER DATE METHODS
2591             ########################################################################
2592              
2593             # Gets the date in the parsed timezone (if $type = ''), local timezone
2594             # (if $type = 'local') or GMT timezone (if $type = 'gmt').
2595             #
2596             # Gets the string value in scalar context, the split value in list
2597             # context.
2598             #
2599             sub value {
2600 32668     32668 1 62758 my($self,$type) = @_;
2601 32668         47368 my $dmt = $$self{'tz'};
2602 32668         45123 my $dmb = $$dmt{'base'};
2603 32668         41660 my $date;
2604              
2605 32668         40971 while (1) {
2606 32668 100       63724 if (! $$self{'data'}{'set'}) {
2607 15         23 $$self{'err'} = '[value] Object does not contain a date';
2608 15         21 last;
2609             }
2610              
2611 32653 100       59300 $type = '' if (! $type);
2612              
2613 32653 100       64207 if ($type eq 'gmt') {
    100          
2614              
2615 2989 100       3921 if (! @{ $$self{'data'}{'gmt'} }) {
  2989         7532  
2616 2687         4540 my $zone = $$self{'data'}{'tz'};
2617 2687         4222 my $date = $$self{'data'}{'date'};
2618              
2619 2687 50       4786 if ($zone eq 'Etc/GMT') {
2620 0         0 $$self{'data'}{'gmt'} = $date;
2621              
2622             } else {
2623 2687         4389 my $isdst = $$self{'data'}{'isdst'};
2624 2687         7957 my($err,$d) = $dmt->convert_to_gmt($date,$zone,$isdst);
2625 2687 50       6518 if ($err) {
2626 0         0 $$self{'err'} = '[value] Unable to convert date to GMT';
2627 0         0 last;
2628             }
2629 2687         6554 $$self{'data'}{'gmt'} = $d;
2630             }
2631             }
2632 2989         5493 $date = $$self{'data'}{'gmt'};
2633              
2634             } elsif ($type eq 'local') {
2635              
2636 219 50       290 if (! @{ $$self{'data'}{'loc'} }) {
  219         517  
2637 219         404 my $zone = $$self{'data'}{'tz'};
2638 219         336 $date = $$self{'data'}{'date'};
2639 219         718 my $local = $dmt->_now('tz',1);
2640              
2641 219 100       534 if ($zone eq $local) {
2642 192         432 $$self{'data'}{'loc'} = $date;
2643              
2644             } else {
2645 27         66 my $isdst = $$self{'data'}{'isdst'};
2646 27         136 my($err,$d) = $dmt->convert_to_local($date,$zone,$isdst);
2647 27 50       93 if ($err) {
2648 0         0 $$self{'err'} = '[value] Unable to convert date to localtime';
2649 0         0 last;
2650             }
2651 27         93 $$self{'data'}{'loc'} = $d;
2652             }
2653             }
2654 219         401 $date = $$self{'data'}{'loc'};
2655              
2656             } else {
2657              
2658 29445         45017 $date = $$self{'data'}{'date'};
2659              
2660             }
2661              
2662 32653         45401 last;
2663             }
2664              
2665 32668 100       60112 if ($$self{'err'}) {
2666 18 50       52 if (wantarray) {
2667 18         79 return ();
2668             } else {
2669 0         0 return '';
2670             }
2671             }
2672              
2673 32650 100       51634 if (wantarray) {
2674 7934         24096 return @$date;
2675             } else {
2676 24716         54906 return $dmb->join('date',$date);
2677             }
2678             }
2679              
2680             sub cmp {
2681 10659     10659 1 18281 my($self,$date) = @_;
2682 10659 50 33     37714 if ($$self{'err'} || ! $$self{'data'}{'set'}) {
2683 0         0 carp "WARNING: [cmp] Arguments must be valid dates: date1";
2684 0         0 return undef;
2685             }
2686              
2687 10659 50       23504 if (! ref($date) eq 'Date::Manip::Date') {
2688 0         0 carp "WARNING: [cmp] Argument must be a Date::Manip::Date object";
2689 0         0 return undef;
2690             }
2691 10659 50 33     33842 if ($$date{'err'} || ! $$date{'data'}{'set'}) {
2692 0         0 carp "WARNING: [cmp] Arguments must be valid dates: date2";
2693 0         0 return undef;
2694             }
2695              
2696 10659         15057 my($d1,$d2);
2697 10659 100       21511 if ($$self{'data'}{'tz'} eq $$date{'data'}{'tz'}) {
2698 10658         19646 $d1 = $self->value();
2699 10658         21182 $d2 = $date->value();
2700             } else {
2701 1         19 $d1 = $self->value('gmt');
2702 1         5 $d2 = $date->value('gmt');
2703             }
2704              
2705 10659         41918 return ($d1 cmp $d2);
2706             }
2707              
2708 0         0 BEGIN {
2709 168     168   1067362 my %field = qw(y 0 m 1 d 2 h 3 mn 4 s 5);
2710              
2711             sub set {
2712 10511     10511 1 484974 my($self,$field,@val) = @_;
2713 10511         18564 $field = lc($field);
2714 10511         16241 my $dmt = $$self{'tz'};
2715 10511         15584 my $dmb = $$dmt{'base'};
2716              
2717             # Make sure $self includes a valid date (unless the entire date is
2718             # being set, in which case it doesn't matter).
2719              
2720 10511         16350 my $date = [];
2721 10511         16512 my(@def,$tz,$isdst);
2722              
2723 10511 100       23722 if ($field eq 'zdate') {
    100          
2724             # If {data}{set} = 2, we want to preserve the defaults. Also, we've
2725             # already initialized.
2726             #
2727             # It is only set in the parse routines which means that this was
2728             # called via _parse_check.
2729              
2730 4876 100       11310 $self->_init() if ($$self{'data'}{'set'} != 2);
2731 4876         6704 @def = @{ $$self{'data'}{'def'} };
  4876         11250  
2732              
2733             } elsif ($field eq 'date') {
2734 5569 100 66     15114 if ($$self{'data'}{'set'} && ! $$self{'err'}) {
2735 319         631 $tz = $$self{'data'}{'tz'};
2736             } else {
2737 5250         15008 $tz = $dmt->_now('tz',1);
2738             }
2739 5569         14129 $self->_init();
2740 5569         9449 @def = @{ $$self{'data'}{'def'} };
  5569         12324  
2741              
2742             } else {
2743 66 50 33     271 return 1 if ($$self{'err'} || ! $$self{'data'}{'set'});
2744 66         123 $date = $$self{'data'}{'date'};
2745 66         101 $tz = $$self{'data'}{'tz'};
2746 66         117 $isdst = $$self{'data'}{'isdst'};
2747 66         85 @def = @{ $$self{'data'}{'def'} };
  66         151  
2748 66         164 $self->_init();
2749             }
2750              
2751             # Check the arguments
2752              
2753 10511         17780 my($err,$new_tz,$new_date,$new_time);
2754              
2755 10511 100       25107 if ($field eq 'date') {
    100          
    100          
    50          
    50          
2756              
2757 5569 100       11335 if ($#val == 0) {
    50          
2758             # date,DATE
2759 5554         8173 $new_date = $val[0];
2760             } elsif ($#val == 1) {
2761             # date,DATE,ISDST
2762 15         28 ($new_date,$isdst) = @val;
2763             } else {
2764 0         0 $err = 1;
2765             }
2766 5569         13778 for (my $i=0; $i<=5; $i++) {
2767 33414 50       69859 $def[$i] = 0 if ($def[$i]);
2768             }
2769              
2770             } elsif ($field eq 'time') {
2771              
2772 64 50       170 if ($#val == 0) {
    0          
2773             # time,TIME
2774 64         137 $new_time = $val[0];
2775             } elsif ($#val == 1) {
2776             # time,TIME,ISDST
2777 0         0 ($new_time,$isdst) = @val;
2778             } else {
2779 0         0 $err = 1;
2780             }
2781 64 50       171 $def[3] = 0 if ($def[3]);
2782 64 50       110 $def[4] = 0 if ($def[4]);
2783 64 100       130 $def[5] = 0 if ($def[5]);
2784              
2785             } elsif ($field eq 'zdate') {
2786              
2787 4876 100 33     19299 if ($#val == 0) {
    50 66        
    100          
    50          
2788             # zdate,DATE
2789 2         8 $new_date = $val[0];
2790             } elsif ($#val == 1 && ($val[1] eq '0' || $val[1] eq '1')) {
2791             # zdate,DATE,ISDST
2792 0         0 ($new_date,$isdst) = @val;
2793             } elsif ($#val == 1) {
2794             # zdate,ZONE,DATE
2795 2         6 ($new_tz,$new_date) = @val;
2796             } elsif ($#val == 2) {
2797             # zdate,ZONE,DATE,ISDST
2798 4872         9066 ($new_tz,$new_date,$isdst) = @val;
2799             } else {
2800 0         0 $err = 1;
2801             }
2802 4876 100       12211 if ($$self{'data'}{'set'} != 2) {
2803 4         24 for (my $i=0; $i<=5; $i++) {
2804 24 50       55 $def[$i] = 0 if ($def[$i]);
2805             }
2806             }
2807 4876 100       9772 $tz = $dmt->_now('tz',1) if (! $new_tz);
2808              
2809             } elsif ($field eq 'zone') {
2810              
2811 0 0 0     0 if ($#val == -1) {
    0 0        
    0          
    0          
2812             # zone
2813             } elsif ($#val == 0 && ($val[0] eq '0' || $val[0] eq '1')) {
2814             # zone,ISDST
2815 0         0 $isdst = $val[0];
2816             } elsif ($#val == 0) {
2817             # zone,ZONE
2818 0         0 $new_tz = $val[0];
2819             } elsif ($#val == 1) {
2820             # zone,ZONE,ISDST
2821 0         0 ($new_tz,$isdst) = @val;
2822             } else {
2823 0         0 $err = 1;
2824             }
2825 0 0       0 $tz = $dmt->_now('tz',1) if (! $new_tz);
2826              
2827             } elsif (exists $field{$field}) {
2828              
2829 2         6 my $i = $field{$field};
2830 2         8 my $val;
2831 2 50       19 if ($#val == 0) {
    0          
2832 2         4 $val = $val[0];
2833             } elsif ($#val == 1) {
2834 0         0 ($val,$isdst) = @val;
2835             } else {
2836 0         0 $err = 1;
2837             }
2838              
2839 2         5 $$date[$i] = $val;
2840 2 50       6 $def[$i] = 0 if ($def[$i]);
2841              
2842             } else {
2843              
2844 0         0 $err = 2;
2845              
2846             }
2847              
2848 10511 50       21483 if ($err) {
2849 0 0       0 if ($err == 1) {
2850 0         0 $$self{'err'} = '[set] Invalid arguments';
2851             } else {
2852 0         0 $$self{'err'} = '[set] Invalid field';
2853             }
2854 0         0 return 1;
2855             }
2856              
2857             # Handle the arguments (it can be a zone or an offset)
2858              
2859 10511 100       18991 if ($new_tz) {
2860 4874         12044 my $tmp = $dmt->_zone($new_tz);
2861 4874 50       9476 if ($tmp) {
2862             # A zone/alias
2863 4874         8091 $tz = $tmp;
2864              
2865             } else {
2866             # An offset
2867              
2868 0         0 my $dstflag = '';
2869 0 0       0 $dstflag = ($isdst ? 'dstonly' : 'stdonly') if (defined $isdst);
    0          
2870              
2871 0         0 $tz = $dmb->__zone($date,lc($new_tz),'',$dstflag);
2872              
2873 0 0       0 if (! $tz) {
2874 0         0 $$self{'err'} = "[set] Invalid timezone argument: $new_tz";
2875 0         0 return 1;
2876             }
2877             }
2878             }
2879              
2880 10511 100       20283 if ($new_date) {
2881 10445 100       28284 if ($dmb->check($new_date)) {
2882 10441         18454 $date = $new_date;
2883             } else {
2884 4         17 $$self{'err'} = '[set] Invalid date argument';
2885 4         22 return 1;
2886             }
2887             }
2888              
2889 10507 100       20807 if ($new_time) {
2890 64 50       199 if ($dmb->check_time($new_time)) {
2891 64         139 $$date[3] = $$new_time[0];
2892 64         100 $$date[4] = $$new_time[1];
2893 64         117 $$date[5] = $$new_time[2];
2894             } else {
2895 0         0 $$self{'err'} = '[set] Invalid time argument';
2896 0         0 return 1;
2897             }
2898             }
2899              
2900             # Check the date/timezone combination
2901              
2902 10507         15831 my($abb,$off);
2903 10507 100       19222 if ($tz eq 'etc/gmt') {
2904 42         90 $abb = 'GMT';
2905 42         92 $off = [0,0,0];
2906 42         73 $isdst = 0;
2907             } else {
2908 10465         28412 my $per = $dmt->date_period($date,$tz,1,$isdst);
2909 10465 100       22861 if (! $per) {
2910 3         8 $$self{'err'} = '[set] Invalid date/timezone';
2911 3         12 return 1;
2912             }
2913 10462         16816 $isdst = $$per[5];
2914 10462         14962 $abb = $$per[4];
2915 10462         16010 $off = $$per[3];
2916             }
2917              
2918             # Set the information
2919              
2920 10504         18513 $$self{'data'}{'set'} = 1;
2921 10504         18344 $$self{'data'}{'date'} = $date;
2922 10504         17386 $$self{'data'}{'tz'} = $tz;
2923 10504         18070 $$self{'data'}{'isdst'} = $isdst;
2924 10504         16454 $$self{'data'}{'offset'}= $off;
2925 10504         16398 $$self{'data'}{'abb'} = $abb;
2926 10504         26463 $$self{'data'}{'def'} = [ @def ];
2927              
2928 10504         29672 return 0;
2929             }
2930             }
2931              
2932             ########################################################################
2933             # NEXT/PREV METHODS
2934              
2935             sub prev {
2936 75     75 1 269 my($self,@args) = @_;
2937 75 50 33     287 return 1 if ($$self{'err'} || ! $$self{'data'}{'set'});
2938 75         123 my $date = $$self{'data'}{'date'};
2939              
2940 75         175 $date = $self->__next_prev($date,0,@args);
2941              
2942 75 50       181 return 1 if (! defined($date));
2943 75         197 $self->set('date',$date);
2944 75         198 return 0;
2945             }
2946              
2947             sub next {
2948 75     75 1 268 my($self,@args) = @_;
2949 75 50 33     265 return 1 if ($$self{'err'} || ! $$self{'data'}{'set'});
2950 75         120 my $date = $$self{'data'}{'date'};
2951              
2952 75         155 $date = $self->__next_prev($date,1,@args);
2953              
2954 75 50       192 return 1 if (! defined($date));
2955 75         201 $self->set('date',$date);
2956 75         187 return 0;
2957             }
2958              
2959             sub __next_prev {
2960 1198     1198   2931 my($self,$date,$next,$dow,$curr,$time) = @_;
2961              
2962 1198         1799 my ($caller,$sign,$prev);
2963 1198 100       2501 if ($next) {
2964 944         1428 $caller = 'next';
2965 944         1373 $sign = 1;
2966 944         1297 $prev = 0;
2967             } else {
2968 254         452 $caller = 'prev';
2969 254         417 $sign = -1;
2970 254         399 $prev = 1;
2971             }
2972              
2973 1198         1955 my $dmt = $$self{'tz'};
2974 1198         1780 my $dmb = $$dmt{'base'};
2975 1198         2783 my $orig = [ @$date ];
2976              
2977             # Check the time (if any)
2978              
2979 1198 100       2783 if (defined($time)) {
2980 366 100       686 if ($dow) {
2981             # $time will refer to a full [H,MN,S]
2982 34         150 my($err,$h,$mn,$s) = $dmb->_hms_fields({ 'out' => 'list' },$time);
2983 34 50       104 if ($err) {
2984 0         0 $$self{'err'} = "[$caller] invalid time argument";
2985 0         0 return undef;
2986             }
2987 34         82 $time = [$h,$mn,$s];
2988             } else {
2989             # $time may have leading undefs
2990 332         709 my @tmp = @$time;
2991 332 50       702 if ($#tmp != 2) {
2992 0         0 $$self{'err'} = "[$caller] invalid time argument";
2993 0         0 return undef;
2994             }
2995 332         604 my($h,$mn,$s) = @$time;
2996 332 100       624 if (defined($h)) {
    100          
2997 296 100       551 $mn = 0 if (! defined($mn));
2998 296 100       550 $s = 0 if (! defined($s));
2999             } elsif (defined($mn)) {
3000 24 50       53 $s = 0 if (! defined($s));
3001             } else {
3002 12 50       36 $s = 0 if (! defined($s));
3003             }
3004 332         806 $time = [$h,$mn,$s];
3005             }
3006             }
3007              
3008             # Find the next DoW
3009              
3010 1198 100       2512 if ($dow) {
3011              
3012 866 50       2710 if (! $dmb->_is_int($dow,1,7)) {
3013 0         0 $$self{'err'} = "[$caller] Invalid DOW: $dow";
3014 0         0 return undef;
3015             }
3016              
3017             # Find the next/previous occurrence of DoW
3018              
3019 866         2526 my $curr_dow = $dmb->day_of_week($date);
3020 866         1720 my $adjust = 0;
3021              
3022 866 100       1866 if ($dow == $curr_dow) {
3023 182 100       640 $adjust = 1 if ($curr == 0);
3024              
3025             } else {
3026 684         961 my $num;
3027 684 100       1298 if ($next) {
3028             # force $dow to be more than $curr_dow
3029 559 100       1222 $dow += 7 if ($dow<$curr_dow);
3030 559         837 $num = $dow - $curr_dow;
3031             } else {
3032             # force $dow to be less than $curr_dow
3033 125 100       442 $dow -= 7 if ($dow>$curr_dow);
3034 125         261 $num = $curr_dow - $dow;
3035 125         203 $num *= -1;
3036             }
3037              
3038             # Add/subtract $num days
3039 684         1823 $date = $dmb->calc_date_days($date,$num);
3040             }
3041              
3042 866 100       2222 if (defined($time)) {
3043 34         84 my ($y,$m,$d,$h,$mn,$s) = @$date;
3044 34         90 ($h,$mn,$s) = @$time;
3045 34         107 $date = [$y,$m,$d,$h,$mn,$s];
3046             }
3047              
3048 866         2667 my $cmp = $dmb->cmp($orig,$date);
3049 866 100 100     2356 $adjust = 1 if ($curr == 2 && $cmp != -1*$sign);
3050              
3051 866 100       1748 if ($adjust) {
3052             # Add/subtract 1 week
3053 70         207 $date = $dmb->calc_date_days($date,$sign*7);
3054             }
3055              
3056 866         3242 return $date;
3057             }
3058              
3059             # Find the next Time
3060              
3061 332 50       663 if (defined($time)) {
3062              
3063 332         595 my ($h,$mn,$s) = @$time;
3064 332         670 my $orig = [ @$date ];
3065              
3066 332         491 my $cmp;
3067 332 100       665 if (defined $h) {
    100          
3068             # Find next/prev HH:MN:SS
3069              
3070 296         658 @$date[3..5] = @$time;
3071 296         859 $cmp = $dmb->cmp($orig,$date);
3072 296 100       818 if ($cmp == -1) {
    100          
3073 109 100       261 if ($prev) {
3074 10         29 $date = $dmb->calc_date_days($date,-1);
3075             }
3076             } elsif ($cmp == 1) {
3077 69 50       177 if ($next) {
3078 69         193 $date = $dmb->calc_date_days($date,1);
3079             }
3080             } else {
3081 118 100       263 if (! $curr) {
3082 102         237 $date = $dmb->calc_date_days($date,$sign);
3083             }
3084             }
3085              
3086             } elsif (defined $mn) {
3087             # Find next/prev MN:SS
3088              
3089 24         72 @$date[4..5] = @$time[1..2];
3090              
3091 24         80 $cmp = $dmb->cmp($orig,$date);
3092 24 50       81 if ($cmp == -1) {
    100          
3093 0 0       0 if ($prev) {
3094 0         0 $date = $dmb->calc_date_time($date,[-1,0,0]);
3095             }
3096             } elsif ($cmp == 1) {
3097 8 100       54 if ($next) {
3098 4         50 $date = $dmb->calc_date_time($date,[1,0,0]);
3099             }
3100             } else {
3101 16 100       44 if (! $curr) {
3102 8         32 $date = $dmb->calc_date_time($date,[$sign,0,0]);
3103             }
3104             }
3105              
3106             } else {
3107             # Find next/prev SS
3108              
3109 12         34 $$date[5] = $$time[2];
3110              
3111 12         45 $cmp = $dmb->cmp($orig,$date);
3112 12 50       52 if ($cmp == -1) {
    50          
3113 0 0       0 if ($prev) {
3114 0         0 $date = $dmb->calc_date_time($date,[0,-1,0]);
3115             }
3116             } elsif ($cmp == 1) {
3117 0 0       0 if ($next) {
3118 0         0 $date = $dmb->calc_date_time($date,[0,1,0]);
3119             }
3120             } else {
3121 12 100       34 if (! $curr) {
3122 8         36 $date = $dmb->calc_date_time($date,[0,$sign,0]);
3123             }
3124             }
3125             }
3126              
3127 332         1719 return $date;
3128             }
3129              
3130 0         0 $$self{'err'} = "[$caller] Either DoW or time (or both) required";
3131 0         0 return undef;
3132             }
3133              
3134             ########################################################################
3135             # CALC METHOD
3136              
3137             sub calc {
3138 4608     4608 1 11815 my($self,$obj,@args) = @_;
3139              
3140 4608 100       13155 if (ref($obj) eq 'Date::Manip::Date') {
    50          
3141 1430         3942 return $self->_calc_date_date($obj,@args);
3142              
3143             } elsif (ref($obj) eq 'Date::Manip::Delta') {
3144 3178         8359 return $self->_calc_date_delta($obj,@args);
3145              
3146             } else {
3147 0         0 return undef;
3148             }
3149             }
3150              
3151             sub _calc_date_date {
3152 1430     1430   2715 my($self,$date,@args) = @_;
3153 1430         3929 my $ret = $self->new_delta();
3154              
3155 1430 50 33     6888 if ($$self{'err'} || ! $$self{'data'}{'set'}) {
3156 0         0 $$ret{'err'} = '[calc] First object invalid (date)';
3157 0         0 return $ret;
3158             }
3159              
3160 1430 50 33     5458 if ($$date{'err'} || ! $$date{'data'}{'set'}) {
3161 0         0 $$ret{'err'} = '[calc] Second object invalid (date)';
3162 0         0 return $ret;
3163             }
3164              
3165             # Handle subtract/mode arguments
3166              
3167 1430         2311 my($subtract,$mode);
3168              
3169 1430 100       3477 if ($#args == -1) {
    100          
    50          
3170 1155         2309 ($subtract,$mode) = (0,'');
3171             } elsif ($#args == 0) {
3172 226 50 33     767 if ($args[0] eq '0' || $args[0] eq '1') {
3173 0         0 ($subtract,$mode) = ($args[0],'');
3174             } else {
3175 226         523 ($subtract,$mode) = (0,$args[0]);
3176             }
3177              
3178             } elsif ($#args == 1) {
3179 49         119 ($subtract,$mode) = @args;
3180             } else {
3181 0         0 $$ret{'err'} = '[calc] Invalid arguments';
3182 0         0 return $ret;
3183             }
3184 1430 100       3392 $mode = 'exact' if (! $mode);
3185              
3186 1430 50       7554 if ($mode !~ /^(business|bsemi|bapprox|approx|semi|exact)$/i) {
3187 0         0 $$ret{'err'} = '[calc] Invalid mode argument';
3188 0         0 return $ret;
3189             }
3190              
3191             # if business mode
3192             # dates must be in the same timezone
3193             # use dates in that zone
3194             #
3195             # otherwise if both dates are in the same timezone && approx/semi mode
3196             # use the dates in that zone
3197             #
3198             # otherwise
3199             # convert to gmt
3200             # use those dates
3201              
3202 1430         2686 my($date1,$date2,$tz1,$isdst1,$tz2,$isdst2);
3203 1430 100 100     9994 if ($mode eq 'business' || $mode eq 'bapprox' || $mode eq 'bsemi') {
    100 100        
      100        
      100        
3204 156 50       408 if ($$self{'data'}{'tz'} eq $$date{'data'}{'tz'}) {
3205 156         384 $date1 = [ $self->value() ];
3206 156         376 $date2 = [ $date->value() ];
3207 156         303 $tz1 = $$self{'data'}{'tz'};
3208 156         219 $tz2 = $tz1;
3209 156         249 $isdst1 = $$self{'data'}{'isdst'};
3210 156         243 $isdst2 = $$date{'data'}{'isdst'};
3211             } else {
3212 0         0 $$ret{'err'} = '[calc] Dates must be in the same timezone for ' .
3213             'business mode calculations';
3214 0         0 return $ret;
3215             }
3216              
3217             } elsif (($mode eq 'approx' || $mode eq 'semi') &&
3218             $$self{'data'}{'tz'} eq $$date{'data'}{'tz'}) {
3219 86         242 $date1 = [ $self->value() ];
3220 86         185 $date2 = [ $date->value() ];
3221 86         169 $tz1 = $$self{'data'}{'tz'};
3222 86         110 $tz2 = $tz1;
3223 86         147 $isdst1 = $$self{'data'}{'isdst'};
3224 86         137 $isdst2 = $$date{'data'}{'isdst'};
3225              
3226             } else {
3227 1188         3586 $date1 = [ $self->value('gmt') ];
3228 1188         3232 $date2 = [ $date->value('gmt') ];
3229 1188         2403 $tz1 = 'GMT';
3230 1188         1855 $tz2 = $tz1;
3231 1188         1658 $isdst1 = 0;
3232 1188         2097 $isdst2 = 0;
3233             }
3234              
3235             # Do the calculation
3236              
3237 1430         2000 my(@delta);
3238 1430 100       2767 if ($subtract) {
3239 42 100 100     229 if ($mode eq 'business' || $mode eq 'exact' || $subtract == 2) {
      100        
3240 23         42 @delta = @{ $self->__calc_date_date($mode,$date2,$tz2,$isdst2,
  23         62  
3241             $date1,$tz1,$isdst1) };
3242             } else {
3243 19         39 @delta = @{ $self->__calc_date_date($mode,$date1,$tz1,$isdst1,
  19         53  
3244             $date2,$tz2,$isdst2) };
3245 19         80 @delta = map { -1*$_ } @delta;
  133         219  
3246             }
3247             } else {
3248 1388         1916 @delta = @{ $self->__calc_date_date($mode,$date1,$tz1,$isdst1,
  1388         4030  
3249             $date2,$tz2,$isdst2) };
3250             }
3251              
3252             # Save the delta
3253              
3254 1430 100 100     7385 if ($mode eq 'business' || $mode eq 'bapprox' || $mode eq 'bsemi') {
      100        
3255 156         582 $ret->set('business',\@delta);
3256             } else {
3257 1274         4435 $ret->set('delta',\@delta);
3258             }
3259 1430         6990 return $ret;
3260             }
3261              
3262             sub __calc_date_date {
3263 1430     1430   3502 my($self,$mode,$date1,$tz1,$isdst1,$date2,$tz2,$isdst2) = @_;
3264 1430         2417 my $dmt = $$self{'tz'};
3265 1430         2437 my $dmb = $$dmt{'base'};
3266              
3267 1430         3156 my($dy,$dm,$dw,$dd,$dh,$dmn,$ds) = (0,0,0,0,0,0,0);
3268              
3269 1430 100 100     5608 if ($mode eq 'approx' || $mode eq 'bapprox') {
3270 112         229 my($y1,$m1,$d1,$h1,$mn1,$s1) = @$date1;
3271 112         204 my($y2,$m2,$d2,$h2,$mn2,$s2) = @$date2;
3272 112         151 $dy = $y2-$y1;
3273 112         190 $dm = $m2-$m1;
3274              
3275 112 100 100     309 if ($dy || $dm) {
3276             # If $d1 is greater than the number of days allowed in the
3277             # month $y2/$m2, set it equal to the number of days. In other
3278             # words:
3279             # Jan 31 2006 to Feb 28 2008 = 2 years 1 month
3280             #
3281 90         259 my $dim = $dmb->days_in_month($y2,$m2);
3282 90 100       208 $d1 = $dim if ($d1 > $dim);
3283              
3284 90         249 $date1 = [$y2,$m2,$d1,$h1,$mn1,$s1];
3285             }
3286             }
3287              
3288 1430 100 100     4782 if ($mode eq 'semi' || $mode eq 'approx') {
3289              
3290             # Calculate the number of weeks/days apart (temporarily ignoring
3291             # DST effects).
3292              
3293 88         255 $dd = $dmb->days_since_1BC($date2) -
3294             $dmb->days_since_1BC($date1);
3295 88         156 $dw = int($dd/7);
3296 88         141 $dd -= $dw*7;
3297              
3298             # Adding $dd to $date1 gives: ($y2,$m2,$d2, $h1,$mn1,$s1)
3299             # Make sure this is valid (taking into account DST effects).
3300             # If it isn't, make it valid.
3301              
3302 88 100 100     281 if ($dw || $dd) {
3303 69         136 my($y1,$m1,$d1,$h1,$mn1,$s1) = @$date1;
3304 69         113 my($y2,$m2,$d2,$h2,$mn2,$s2) = @$date2;
3305 69         211 $date1 = [$y2,$m2,$d2,$h1,$mn1,$s1];
3306             }
3307 88 100 100     345 if ($dy || $dm || $dw || $dd) {
      100        
      100        
3308 81 100 100     290 my $force = ( ($dw > 0 || $dd > 0) ? 1 : -1 );
3309 81         125 my($off,$isdst,$abb);
3310 81         207 ($date1,$off,$isdst,$abb) =
3311             $self->_calc_date_check_dst($date1,$tz2,$isdst2,$force);
3312             }
3313             }
3314              
3315 1430 100 100     4888 if ($mode eq 'bsemi' || $mode eq 'bapprox') {
3316             # Calculate the number of weeks. Ignore the days
3317             # part. Also, since there are no DST effects, we don't
3318             # have to check for validity.
3319              
3320 94         280 $dd = $dmb->days_since_1BC($date2) -
3321             $dmb->days_since_1BC($date1);
3322 94         159 $dw = int($dd/7);
3323 94         153 $dd = 0;
3324 94         239 $date1 = $dmb->calc_date_days($date1,$dw*7);
3325             }
3326              
3327 1430 100 100     4561 if ($mode eq 'exact' || $mode eq 'semi' || $mode eq 'approx') {
      100        
3328 1274         3668 my $sec1 = $dmb->secs_since_1970($date1);
3329 1274         2971 my $sec2 = $dmb->secs_since_1970($date2);
3330 1274         2130 $ds = $sec2 - $sec1;
3331              
3332             {
3333 168     168   1715 no integer;
  168         478  
  168         1005  
  1274         1882  
3334 1274         2965 $dh = int($ds/3600);
3335 1274         2204 $ds -= $dh*3600;
3336             }
3337 1274         1955 $dmn = int($ds/60);
3338 1274         2142 $ds -= $dmn*60;
3339             }
3340              
3341 1430 100 100     7135 if ($mode eq 'business' || $mode eq 'bsemi' || $mode eq 'bapprox') {
      100        
3342              
3343             # Make sure both are work days
3344              
3345 156         437 $date1 = $self->__nextprev_business_day(0,0,1,$date1);
3346 156         386 $date2 = $self->__nextprev_business_day(0,0,1,$date2);
3347              
3348 156         381 my($y1,$m1,$d1,$h1,$mn1,$s1) = @$date1;
3349 156         302 my($y2,$m2,$d2,$h2,$mn2,$s2) = @$date2;
3350              
3351             # Find out which direction we need to move $date1 to get to $date2
3352              
3353 156         231 my $dir = 0;
3354 156 100       703 if ($y1 < $y2) {
    100          
    100          
    100          
    100          
    100          
3355 2         8 $dir = 1;
3356             } elsif ($y1 > $y2) {
3357 3         18 $dir = -1;
3358             } elsif ($m1 < $m2) {
3359 2         16 $dir = 1;
3360             } elsif ($m1 > $m2) {
3361 3         14 $dir = -1;
3362             } elsif ($d1 < $d2) {
3363 73         110 $dir = 1;
3364             } elsif ($d1 > $d2) {
3365 33         59 $dir = -1;
3366             }
3367              
3368             # Now do the day part (to get to the same day)
3369              
3370 156         219 $dd = 0;
3371 156         300 while ($dir) {
3372 456         587 ($y1,$m1,$d1) = @{ $dmb->calc_date_days([$y1,$m1,$d1],$dir) };
  456         1218  
3373 456 100       1335 $dd += $dir if ($self->__is_business_day([$y1,$m1,$d1,0,0,0],0));
3374 456 100 100     2240 $dir = 0 if ($y1 == $y2 && $m1 == $m2 && $d1 == $d2);
      100        
3375             }
3376              
3377             # Both dates are now on a business day, and during business
3378             # hours, so do the hr/min/sec part trivially
3379              
3380 156         233 $dh = $h2-$h1;
3381 156         203 $dmn = $mn2-$mn1;
3382 156         249 $ds = $s2-$s1;
3383             }
3384              
3385 1430         5987 return [ $dy,$dm,$dw,$dd,$dh,$dmn,$ds ];
3386             }
3387              
3388 168     168   53684 no integer;
  168         472  
  168         884  
3389             sub _calc_date_delta {
3390 3178     3178   6299 my($self,$delta,$subtract) = @_;
3391 3178         8428 my $ret = $self->new_date();
3392              
3393 3178 50 33     13701 if ($$self{'err'} || ! $$self{'data'}{'set'}) {
3394 0         0 $$ret{'err'} = '[calc] Date object invalid';
3395 0         0 return $ret;
3396             }
3397              
3398 3178 50       7037 if ($$delta{'err'}) {
3399 0         0 $$ret{'err'} = '[calc] Delta object invalid';
3400 0         0 return $ret;
3401             }
3402              
3403             # Get the date/delta fields
3404              
3405 3178 100       6468 $subtract = 0 if (! $subtract);
3406 3178         4370 my @delta = @{ $$delta{'data'}{'delta'} };
  3178         8557  
3407 3178         5024 my @date = @{ $$self{'data'}{'date'} };
  3178         6790  
3408 3178 100       7750 my $business = ($$delta{'data'}{'mode'} eq 'business' ? 1 : 0);
3409 3178         5279 my $tz = $$self{'data'}{'tz'};
3410 3178         4933 my $isdst = $$self{'data'}{'isdst'};
3411              
3412             # We can't handle a delta longer than 10000 years
3413 3178         6780 my($dy,$dm,$dw,$dd,$dh,$dmn,$ds) = @delta;
3414 3178 50 33     28518 if (abs($dy) > 10000 ||
      66        
      66        
      100        
      66        
      66        
3415             abs($dm) > 120000 || # 10000*12
3416             abs($dw) > 530000 || # 10000*53
3417             abs($dd) > 3660000 || # 10000*366
3418             abs($dh) > 87840000 || # 10000*366*24
3419             abs($dmn) > 5270400000 || # 10000*366*24*60
3420             abs($ds) > 316224000000) { # 10000*366*24*60*60
3421 2         5 $$ret{'err'} = '[calc] Delta too large';
3422 2         12 return $ret;
3423             }
3424              
3425 3176         4992 my($err,$date2,$offset,$abbrev);
3426 3176         16004 ($err,$date2,$offset,$isdst,$abbrev) =
3427             $self->__calc_date_delta([@date],[@delta],$subtract,$business,$tz,$isdst);
3428              
3429 3176 100 66     20352 if (ref($date2) eq 'ARRAY' && ($$date2[0]<0 || $$date2[0]>9999)) {
    100 100        
3430 1         5 $$ret{'err'} = '[calc] Delta produces date outside valid range';
3431             } elsif ($err) {
3432 2         5 $$ret{'err'} = '[calc] Unable to perform calculation';
3433             } else {
3434 3173         6577 $$ret{'data'}{'set'} = 1;
3435 3173         5599 $$ret{'data'}{'date'} = $date2;
3436 3173         5413 $$ret{'data'}{'tz'} = $tz;
3437 3173         5433 $$ret{'data'}{'isdst'} = $isdst;
3438 3173         5066 $$ret{'data'}{'offset'}= $offset;
3439 3173         5139 $$ret{'data'}{'abb'} = $abbrev;
3440             }
3441 3176         23733 return $ret;
3442             }
3443 168     168   69900 use integer;
  168         477  
  168         752  
3444              
3445             sub __calc_date_delta {
3446 3308     3308   7596 my($self,$date,$delta,$subtract,$business,$tz,$isdst) = @_;
3447              
3448 3308         6604 my ($dy,$dm,$dw,$dd,$dh,$dmn,$ds) = @$delta;
3449 3308         6993 my @date = @$date;
3450              
3451 3308         7586 my ($err,$date2,$offset,$abbrev);
3452              
3453             # In business mode, daylight saving time is ignored, so days are
3454             # of a constant, known length, so they'll be done in the exact
3455             # function. Otherwise, they'll be done in the approximate function.
3456             #
3457             # Also in business mode, if $subtract = 2, then the starting date
3458             # must be a business date or an error occurs.
3459              
3460 3308         0 my($dd_exact,$dd_approx);
3461 3308 100       6096 if ($business) {
3462 75         122 $dd_exact = $dd;
3463 75         117 $dd_approx = 0;
3464              
3465 75 100 66     210 if ($subtract == 2 && ! $self->__is_business_day($date,1)) {
3466 2         7 return (1);
3467             }
3468              
3469             } else {
3470 3233         4521 $dd_exact = 0;
3471 3233         4755 $dd_approx = $dd;
3472             }
3473              
3474 3306 100 100     11040 if ($subtract == 2 && ($dy || $dm || $dw || $dd_approx)) {
      100        
3475             # For subtract=2:
3476             # DATE = RET + DELTA
3477             #
3478             # The delta consisists of an approximate part (which is added first)
3479             # and an exact part (added second):
3480             # DATE = RET + DELTA(approx) + DELTA(exact)
3481             # DATE = RET' + DELTA(exact)
3482             # where RET' = RET + DELTA(approx)
3483             #
3484             # For an exact delta, subtract==2 and subtract==1 are equivalent,
3485             # so this can be written:
3486             # DATE - DELTA(exact) = RET'
3487             #
3488             # So the inverse subtract only needs to include the approximate
3489             # portion of the delta.
3490              
3491 1198         6395 ($err,$date2,$offset,$isdst,$abbrev) =
3492             $self->__calc_date_delta_exact([@date],[-1*$dd_exact,-1*$dh,-1*$dmn,-1*$ds],
3493             $business,$tz,$isdst);
3494              
3495 1198 50       6483 ($err,$date2,$offset,$isdst,$abbrev) =
3496             $self->__calc_date_delta_inverse($date2,[$dy,$dm,$dw,$dd_approx],
3497             $business,$tz,$isdst)
3498             if (! $err);
3499              
3500             } else {
3501             # We'll add the approximate part, followed by the exact part.
3502             # After the approximate part, we need to make sure we're on
3503             # a valid business day in business mode.
3504              
3505             ($dy,$dm,$dw,$dd_exact,$dd_approx,$dh,$dmn,$ds) =
3506 2108 100       4452 map { -1*$_ } ($dy,$dm,$dw,$dd_exact,$dd_approx,$dh,$dmn,$ds)
  288         462  
3507             if ($subtract);
3508 2108         6672 @$date2 = @date;
3509              
3510 2108 100 100     8158 if ($dy || $dm || $dw || $dd) {
    100 100        
      100        
3511 1867         7599 ($err,$date2,$offset,$isdst,$abbrev) =
3512             $self->__calc_date_delta_approx($date2,[$dy,$dm,$dw,$dd_approx],
3513             $business,$tz,$isdst);
3514             } elsif ($business) {
3515 48         153 $date2 = $self->__nextprev_business_day(0,0,1,$date2);
3516             }
3517              
3518 2108 100 100     17362 ($err,$date2,$offset,$isdst,$abbrev) =
      66        
3519             $self->__calc_date_delta_exact($date2,[$dd_exact,$dh,$dmn,$ds],
3520             $business,$tz,$isdst)
3521             if (! $err && ($dd_exact || $dh || $dmn || $ds));
3522             }
3523              
3524 3306         11955 return($err,$date2,$offset,$isdst,$abbrev);
3525             }
3526              
3527             # Do the inverse part of a calculation.
3528             #
3529             # $delta = [$dy,$dm,$dw,$dd]
3530             #
3531             sub __calc_date_delta_inverse {
3532 1198     1198   2837 my($self,$date,$delta,$business,$tz,$isdst) = @_;
3533 1198         2217 my $dmt = $$self{'tz'};
3534 1198         2016 my $dmb = $$dmt{'base'};
3535 1198         1659 my @date2;
3536              
3537             # Given: DATE1, DELTA
3538             # Find: DATE2
3539             # where DATE2 + DELTA = DATE1
3540             #
3541             # Start with:
3542             # DATE2 = DATE1 - DELTA
3543             #
3544             # if (DATE2+DELTA < DATE1)
3545             # while (1)
3546             # DATE2 = DATE2 + 1 day
3547             # if DATE2+DELTA < DATE1
3548             # next
3549             # elsif DATE2+DELTA > DATE1
3550             # return ERROR
3551             # else
3552             # return DATE2
3553             # done
3554             #
3555             # elsif (DATE2+DELTA > DATE1)
3556             # while (1)
3557             # DATE2 = DATE2 - 1 day
3558             # if DATE2+DELTA > DATE1
3559             # next
3560             # elsif DATE2+DELTA < DATE1
3561             # return ERROR
3562             # else
3563             # return DATE2
3564             # done
3565             #
3566             # else
3567             # return DATE2
3568              
3569 1198 50       2293 if ($business) {
3570              
3571 0         0 my $date1 = $date;
3572 0         0 my ($err,$date2,$off,$isd,$abb,@del,$tmp,$cmp);
3573 0         0 @del = map { $_*-1 } @$delta;
  0         0  
3574              
3575 0         0 ($err,$date2,$off,$isd,$abb) =
3576             $self->__calc_date_delta_approx($date,[@del],$business,$tz,$isdst);
3577              
3578 0         0 ($err,$tmp,$off,$isd,$abb) =
3579             $self->__calc_date_delta_approx($date2,$delta,$business,$tz,$isdst);
3580              
3581 0         0 $cmp = $self->_cmp_date($tmp,$date1);
3582              
3583 0 0       0 if ($cmp < 0) {
    0          
3584 0         0 while (1) {
3585 0         0 $date2 = $self->__nextprev_business_day(0,1,0,$date2);
3586 0         0 ($err,$tmp,$off,$isd,$abb) =
3587             $self->__calc_date_delta_approx($date2,$delta,$business,$tz,$isdst);
3588 0         0 $cmp = $self->_cmp_date($tmp,$date1);
3589 0 0       0 if ($cmp < 0) {
    0          
3590 0         0 next;
3591             } elsif ($cmp > 0) {
3592 0         0 return (1);
3593             } else {
3594 0         0 last;
3595             }
3596             }
3597              
3598             } elsif ($cmp > 0) {
3599 0         0 while (1) {
3600 0         0 $date2 = $self->__nextprev_business_day(1,1,0,$date2);
3601 0         0 ($err,$tmp,$off,$isd,$abb) =
3602             $self->__calc_date_delta_approx($date2,$delta,$business,$tz,$isdst);
3603 0         0 $cmp = $self->_cmp_date($tmp,$date1);
3604 0 0       0 if ($cmp > 0) {
    0          
3605 0         0 next;
3606             } elsif ($cmp < 0) {
3607 0         0 return (1);
3608             } else {
3609 0         0 last;
3610             }
3611             }
3612             }
3613              
3614 0         0 @date2 = @$date2;
3615              
3616             } else {
3617              
3618 1198         3096 my @tmp = @$date[0..2]; # [y,m,d]
3619 1198         2211 my @hms = @$date[3..5]; # [h,m,s]
3620 1198         2078 my $date1 = [@tmp];
3621              
3622 1198         3648 my $date2 = $dmb->_calc_date_ymwd($date1,$delta,1);
3623 1198         2746 my $tmp = $dmb->_calc_date_ymwd($date2,$delta);
3624 1198         3389 my $cmp = $self->_cmp_date($tmp,$date1);
3625              
3626 1198 100       4029 if ($cmp < 0) {
    100          
3627 8         30 while (1) {
3628 9         30 $date2 = $dmb->calc_date_days($date2,1);
3629 9         73 $tmp = $dmb->_calc_date_ymwd($date2,$delta);
3630 9         33 $cmp = $self->_cmp_date($tmp,$date1);
3631 9 100       45 if ($cmp < 0) {
    50          
3632 1         3 next;
3633             } elsif ($cmp > 0) {
3634 0         0 return (1);
3635             } else {
3636 8         18 last;
3637             }
3638             }
3639              
3640             } elsif ($cmp > 0) {
3641 2         4 while (1) {
3642 2         8 $date2 = $dmb->calc_date_days($date2,-1);
3643 2         13 $tmp = $dmb->_calc_date_ymwd($date2,$delta);
3644 2         6 $cmp = $self->_cmp_date($tmp,$date1);
3645 2 50       7 if ($cmp > 0) {
    50          
3646 0         0 next;
3647             } elsif ($cmp < 0) {
3648 0         0 return (1);
3649             } else {
3650 2         5 last;
3651             }
3652             }
3653             }
3654              
3655 1198         3826 @date2 = (@$date2,@hms);
3656             }
3657              
3658             # Make sure DATE2 is valid (within DST constraints) and
3659             # return it.
3660              
3661 1198         2120 my($date2,$abb,$off,$err);
3662 1198         3532 ($date2,$off,$isdst,$abb) = $self->_calc_date_check_dst([@date2],$tz,$isdst,0);
3663              
3664 1198 50       2917 return (1) if (! defined($date2));
3665 1198         4168 return (0,$date2,$off,$isdst,$abb);
3666             }
3667              
3668             sub _cmp_date {
3669 1209     1209   2413 my($self,$date0,$date1) = @_;
3670 1209   100     7116 return ($$date0[0] <=> $$date1[0] ||
3671             $$date0[1] <=> $$date1[1] ||
3672             $$date0[2] <=> $$date1[2]);
3673             }
3674              
3675             # Do the approximate part of a calculation.
3676             #
3677             sub __calc_date_delta_approx {
3678 1867     1867   4238 my($self,$date,$delta,$business,$tz,$isdst) = @_;
3679              
3680 1867         3161 my $dmt = $$self{'tz'};
3681 1867         2936 my $dmb = $$dmt{'base'};
3682 1867         3976 my($y,$m,$d,$h,$mn,$s) = @$date;
3683 1867         3580 my($dy,$dm,$dw,$dd) = @$delta;
3684              
3685             #
3686             # Do the year/month part.
3687             #
3688             # If we are past the last day of a month, move the date back to
3689             # the last day of the month. i.e. Jan 31 + 1 month = Feb 28.
3690             #
3691              
3692 1867 100       3879 $y += $dy if ($dy);
3693 1867 100       4566 $dmb->_mod_add(-12,$dm,\$m,\$y) # -12 means 1-12 instead of 0-11
3694             if ($dm);
3695              
3696 1867         7103 my $dim = $dmb->days_in_month($y,$m);
3697 1867 100       4510 $d = $dim if ($d > $dim);
3698              
3699             #
3700             # Do the week part.
3701             #
3702             # The week is treated as 7 days for both business and non-business
3703             # calculations.
3704             #
3705             # In a business calculation, make sure we're on a business date.
3706             #
3707              
3708 1867 100       3435 if ($business) {
3709 25 100       68 ($y,$m,$d) = @{ $dmb->calc_date_days([$y,$m,$d],$dw*7) } if ($dw);
  5         18  
3710             ($y,$m,$d,$h,$mn,$s) =
3711 25         44 @{ $self->__nextprev_business_day(0,0,1,[$y,$m,$d,$h,$mn,$s]) };
  25         104  
3712             } else {
3713 1842         3068 $dd += $dw*7;
3714             }
3715              
3716             #
3717             # Now do the day part. $dd is always 0 in business calculations.
3718             #
3719              
3720 1867 100       3601 if ($dd) {
3721 267         402 ($y,$m,$d) = @{ $dmb->calc_date_days([$y,$m,$d],$dd) };
  267         1021  
3722             }
3723              
3724             #
3725             # At this point, we need to make sure that we're a valid date
3726             # (within the constraints of DST).
3727             #
3728             # If it is not valid in this offset, try the other one. If neither
3729             # works, then we want the the date to be 24 hours later than the
3730             # previous day at this time (if $dd > 0) or 24 hours earlier than
3731             # the next day at this time (if $dd < 0). We'll use the 24 hour
3732             # definition even for business days, but then we'll double check
3733             # that the resulting date is a business date.
3734             #
3735              
3736 1867 100 100     10634 my $force = ( ($dd > 0 || $dw > 0 || $dm > 0 || $dy > 0) ? 1 : -1 );
3737 1867         2968 my($off,$abb);
3738 1867         7472 ($date,$off,$isdst,$abb) =
3739             $self->_calc_date_check_dst([$y,$m,$d,$h,$mn,$s],$tz,$isdst,$force);
3740 1867         6910 return (0,$date,$off,$isdst,$abb);
3741             }
3742              
3743             # Do the exact part of a calculation.
3744             #
3745             sub __calc_date_delta_exact {
3746 1466     1466   3325 my($self,$date,$delta,$business,$tz,$isdst) = @_;
3747 1466         2440 my $dmt = $$self{'tz'};
3748 1466         2667 my $dmb = $$dmt{'base'};
3749              
3750 1466 100       2839 if ($business) {
3751              
3752             # Simplify hours/minutes/seconds where the day length is defined
3753             # by the start/end of the business day.
3754              
3755 68         132 my ($dd,$dh,$dmn,$ds) = @$delta;
3756 68         135 my ($y,$m,$d,$h,$mn,$s)= @$date;
3757 68         100 my ($hbeg,$mbeg,$sbeg) = @{ $$dmb{'data'}{'calc'}{'workdaybeg'} };
  68         178  
3758 68         103 my ($hend,$mend,$send) = @{ $$dmb{'data'}{'calc'}{'workdayend'} };
  68         154  
3759 68         121 my $bdlen = $$dmb{'data'}{'len'}{'bdlength'};
3760              
3761 168     168   231981 no integer;
  168         549  
  168         874  
3762 68         1840 my $tmp;
3763 68         137 $ds += $dh*3600 + $dmn*60;
3764 68         163 $tmp = int($ds/$bdlen);
3765 68         98 $dd += $tmp;
3766 68         108 $ds -= $tmp*$bdlen;
3767 68         113 $dh = int($ds/3600);
3768 68         113 $ds -= $dh*3600;
3769 68         96 $dmn = int($ds/60);
3770 68         107 $ds -= $dmn*60;
3771 168     168   12066 use integer;
  168         508  
  168         787  
3772              
3773 68 100       155 if ($dd) {
3774 20         51 my $prev = 0;
3775 20 100       63 if ($dd < 1) {
3776 4         16 $prev = 1;
3777 4         16 $dd *= -1;
3778             }
3779              
3780             ($y,$m,$d,$h,$mn,$s) =
3781 20         37 @{ $self->__nextprev_business_day($prev,$dd,0,[$y,$m,$d,$h,$mn,$s]) };
  20         79  
3782             }
3783              
3784             # At this point, we're adding less than a day for the
3785             # hours/minutes/seconds part AND we know that the current
3786             # day is during business hours.
3787             #
3788             # We'll add them (without affecting days... we'll need to
3789             # test things by hand to make sure we should or shouldn't
3790             # do that.
3791              
3792 68         287 $dmb->_mod_add(60,$ds,\$s,\$mn);
3793 68         224 $dmb->_mod_add(60,$dmn,\$mn,\$h);
3794 68         125 $h += $dh;
3795             # Note: it's possible that $h > 23 at this point or $h < 0
3796              
3797 68 100 66     889 if ($h > $hend ||
    100 66        
      100        
      66        
      33        
      100        
      66        
      66        
      66        
      66        
      66        
      66        
      33        
3798             ($h == $hend && $mn > $mend) ||
3799             ($h == $hend && $mn == $mend && $s > $send) ||
3800             ($h == $hend && $mn == $mend && $s == $send)) {
3801              
3802             # We've gone past the end of the business day.
3803              
3804 20         463 my $t2 = $dmb->calc_time_time([$h,$mn,$s],[$hend,$mend,$send],1);
3805              
3806 20         60 while (1) {
3807 26         45 ($y,$m,$d) = @{ $dmb->calc_date_days([$y,$m,$d],1) };
  26         97  
3808 26 100       107 last if ($self->__is_business_day([$y,$m,$d,$h,$mn,$s]));
3809             }
3810              
3811 20         60 ($h,$mn,$s) = @{ $dmb->calc_time_time([$hbeg,$mbeg,$sbeg],$t2) };
  20         81  
3812              
3813             } elsif ($h < $hbeg ||
3814             ($h == $hbeg && $mn < $mbeg) ||
3815             ($h == $hbeg && $mn == $mbeg && $s < $sbeg)) {
3816              
3817             # We've gone back past the start of the business day.
3818              
3819 15         66 my $t2 = $dmb->calc_time_time([$hbeg,$mbeg,$sbeg],[$h,$mn,$s],1);
3820              
3821 15         32 while (1) {
3822 17         26 ($y,$m,$d) = @{ $dmb->calc_date_days([$y,$m,$d],-1) };
  17         47  
3823 17 100       64 last if ($self->__is_business_day([$y,$m,$d,$h,$mn,$s]));
3824             }
3825              
3826 15         34 ($h,$mn,$s) = @{ $dmb->calc_time_time([$hend,$mend,$send],$t2,1) };
  15         47  
3827             }
3828              
3829             # Now make sure that the date is valid within DST constraints.
3830              
3831 68 100 100     540 my $force = ( ($dd > 0 || $dh > 0 || $dmn > 0 || $ds > 0) ? 1 : -1 );
3832 68         121 my($off,$abb);
3833 68         221 ($date,$off,$isdst,$abb) =
3834             $self->_calc_date_check_dst([$y,$m,$d,$h,$mn,$s],$tz,$isdst,$force);
3835 68         262 return (0,$date,$off,$isdst,$abb);
3836              
3837             } else {
3838              
3839             # Convert to GTM
3840             # Do the calculation
3841             # Convert back
3842              
3843 1398         3026 my ($dd,$dh,$dm,$ds) = @$delta; # $dd is always 0
3844 1398         3464 my $del = [$dh,$dm,$ds];
3845 1398         2280 my ($err,$offset,$abbrev);
3846              
3847 1398         4648 ($err,$date,$offset,$isdst,$abbrev) =
3848             $dmt->_convert('__calc_date_delta_exact',$date,$tz,'GMT',$isdst);
3849              
3850 1398         4386 $date = $dmb->calc_date_time($date,$del,0);
3851 1398 100 66     5918 return($err,$date,$offset,$isdst,$abbrev)
3852             if ($$date[0] < 0 || $$date[0] > 9999);
3853              
3854 1397         3918 ($err,$date,$offset,$isdst,$abbrev) =
3855             $dmt->_convert('__calc_date_delta_exact',$date,'GMT',$tz,$isdst);
3856              
3857 1397         6441 return($err,$date,$offset,$isdst,$abbrev);
3858             }
3859             }
3860              
3861             # This checks to see which time (STD or DST) a date is in. It checks
3862             # $isdst first, and the other value (1-$isdst) second.
3863             #
3864             # If the date is found in either time, it is returned.
3865             #
3866             # If the date is NOT found, then we got here by adding/subtracting 1 day
3867             # from a different value, and we've obtained an invalid value. In this
3868             # case, if $force = 0, then return nothing.
3869             #
3870             # If $force = 1, then go to the previous day and add 24 hours. If force
3871             # is -1, then go to the next day and subtract 24 hours.
3872             #
3873             # Returns:
3874             # ($date,$off,$isdst,$abb)
3875             # or
3876             # (undef)
3877             #
3878             sub _calc_date_check_dst {
3879 3214     3214   7007 my($self,$date,$tz,$isdst,$force) = @_;
3880 3214         5179 my $dmt = $$self{'tz'};
3881 3214         4704 my $dmb = $$dmt{'base'};
3882 3214         4875 my($abb,$off,$err);
3883              
3884             # Try the date as is in both ISDST and 1-ISDST times
3885              
3886 3214         9219 my $per = $dmt->date_period($date,$tz,1,$isdst);
3887 3214 50       7475 if ($per) {
3888 3214         5687 $abb = $$per[4];
3889 3214         4589 $off = $$per[3];
3890 3214         9738 return($date,$off,$isdst,$abb);
3891             }
3892              
3893 0         0 $per = $dmt->date_period($date,$tz,1,1-$isdst);
3894 0 0       0 if ($per) {
3895 0         0 $isdst = 1-$isdst;
3896 0         0 $abb = $$per[4];
3897 0         0 $off = $$per[3];
3898 0         0 return($date,$off,$isdst,$abb);
3899             }
3900              
3901             # If we made it here, the date is invalid in this timezone.
3902             # Either return undef, or add/subtract a day from the date
3903             # and find out what time period we're in (all we care about
3904             # is the ISDST value).
3905              
3906 0 0       0 if (! $force) {
3907 0         0 return(undef);
3908             }
3909              
3910 0         0 my($dd);
3911 0 0       0 if ($force > 0) {
3912 0         0 $date = $dmb->calc_date_days($date,-1);
3913 0         0 $dd = 1;
3914             } else {
3915 0         0 $date = $dmb->calc_date_days($date,+1);
3916 0         0 $dd = -1;
3917             }
3918              
3919 0         0 $per = $dmt->date_period($date,$tz,1,$isdst);
3920 0 0       0 $isdst = (1-$isdst) if (! $per);
3921              
3922             # Now, convert it to GMT, add/subtract 24 hours, and convert
3923             # it back.
3924              
3925 0         0 ($err,$date,$off,$isdst,$abb) = $dmt->convert_to_gmt($date,$tz,$isdst);
3926 0         0 $date = $dmb->calc_date_days($date,$dd);
3927 0         0 ($err,$date,$off,$isdst,$abb) = $dmt->convert_from_gmt($date,$tz);
3928              
3929 0         0 return($date,$off,$isdst,$abb);
3930             }
3931              
3932             ########################################################################
3933             # MISC METHODS
3934              
3935             sub secs_since_1970_GMT {
3936 8     8 1 2210 my($self,$secs) = @_;
3937              
3938 8         20 my $dmt = $$self{'tz'};
3939 8         18 my $dmb = $$dmt{'base'};
3940              
3941 8 100       22 if (defined $secs) {
3942 3         14 my $date = $dmb->secs_since_1970($secs);
3943 3         6 my $err;
3944 3         10 ($err,$date) = $dmt->convert_from_gmt($date);
3945 3 50       10 return 1 if ($err);
3946 3         13 $self->set('date',$date);
3947 3         7 return 0;
3948             }
3949              
3950 5 50 33     31 if ($$self{'err'} || ! $$self{'data'}{'set'}) {
3951 0         0 carp "WARNING: [secs_since_1970_GMT] Object must contain a valid date";
3952 0         0 return undef;
3953             }
3954              
3955 5         21 my @date = $self->value('gmt');
3956 5         22 $secs = $dmb->secs_since_1970(\@date);
3957 5         15 return $secs;
3958             }
3959              
3960             sub week_of_year {
3961 27     27 1 113 my($self,$first) = @_;
3962 27 50 33     104 if ($$self{'err'} || ! $$self{'data'}{'set'}) {
3963 0         0 carp "WARNING: [week_of_year] Object must contain a valid date";
3964 0         0 return undef;
3965             }
3966              
3967 27         41 my $dmt = $$self{'tz'};
3968 27         43 my $dmb = $$dmt{'base'};
3969 27         43 my $date = $$self{'data'}{'date'};
3970 27         38 my $y = $$date[0];
3971              
3972 27         46 my($day,$dow,$doy,$f);
3973 27         65 $doy = $dmb->day_of_year($date);
3974              
3975             # The date in January which must belong to the first week, and
3976             # it's DayOfWeek.
3977 27 100       82 if ($dmb->_config('jan1week1')) {
3978 9         17 $day=1;
3979             } else {
3980 18         28 $day=4;
3981             }
3982 27         86 $dow = $dmb->day_of_week([$y,1,$day]);
3983              
3984             # The start DayOfWeek. If $first is passed in, use it. Otherwise,
3985             # use FirstDay.
3986              
3987 27 50       70 if (! $first) {
3988 0         0 $first = $dmb->_config('firstday');
3989             }
3990              
3991             # Find the pseudo-date of the first day of the first week (it may
3992             # be negative meaning it occurs last year).
3993              
3994 27 100       55 $first -= 7 if ($first > $dow);
3995 27         38 $day -= ($dow-$first);
3996              
3997 27 100       59 return 0 if ($day>$doy); # Day is in last week of previous year
3998 25         80 return (($doy-$day)/7 + 1);
3999             }
4000              
4001             sub complete {
4002 7     7 1 53 my($self,$field) = @_;
4003 7 50 33     57 if ($$self{'err'} || ! $$self{'data'}{'set'}) {
4004 0         0 carp "WARNING: [complete] Object must contain a valid date";
4005 0         0 return undef;
4006             }
4007              
4008 7 100       23 if (! $field) {
4009             return 1 if (! $$self{'data'}{'def'}[1] &&
4010             ! $$self{'data'}{'def'}[2] &&
4011             ! $$self{'data'}{'def'}[3] &&
4012             ! $$self{'data'}{'def'}[4] &&
4013 4 100 66     61 ! $$self{'data'}{'def'}[5]);
      100        
      66        
      66        
4014 3         8 return 0;
4015             }
4016              
4017 3 100       15 if ($field eq 'm') {
4018 1 50       24 return 1 if (! $$self{'data'}{'def'}[1]);
4019             }
4020              
4021 2 50       12 if ($field eq 'd') {
4022 0 0       0 return 1 if (! $$self{'data'}{'def'}[2]);
4023             }
4024              
4025 2 100       9 if ($field eq 'h') {
4026 1 50       9 return 1 if (! $$self{'data'}{'def'}[3]);
4027             }
4028              
4029 1 50       11 if ($field eq 'mn') {
4030 0 0       0 return 1 if (! $$self{'data'}{'def'}[4]);
4031             }
4032              
4033 1 50       8 if ($field eq 's') {
4034 1 50       9 return 1 if (! $$self{'data'}{'def'}[5]);
4035             }
4036 1         7 return 0;
4037             }
4038              
4039             sub convert {
4040 12     12 1 62 my($self,$zone) = @_;
4041 12 50 33     53 if ($$self{'err'} || ! $$self{'data'}{'set'}) {
4042 0         0 carp "WARNING: [convert] Object must contain a valid date";
4043 0         0 return 1;
4044             }
4045 12         21 my $dmt = $$self{'tz'};
4046 12         20 my $dmb = $$dmt{'base'};
4047              
4048 12         29 my $zonename = $dmt->_zone($zone);
4049              
4050 12 50       26 if (! $zonename) {
4051 0         0 $$self{'err'} = "[convert] Unable to determine timezone: $zone";
4052 0         0 return 1;
4053             }
4054              
4055 12         18 my $date0 = $$self{'data'}{'date'};
4056 12         23 my $zone0 = $$self{'data'}{'tz'};
4057 12         14 my $isdst0 = $$self{'data'}{'isdst'};
4058              
4059 12         32 my($err,$date,$off,$isdst,$abb) = $dmt->convert($date0,$zone0,$zonename,$isdst0);
4060              
4061 12 50       29 if ($err) {
4062 0         0 $$self{'err'} = '[convert] Unable to convert date to new timezone';
4063 0         0 return 1;
4064             }
4065              
4066 12         33 $self->_init();
4067 12         20 $$self{'data'}{'date'} = $date;
4068 12         21 $$self{'data'}{'tz'} = $zonename;
4069 12         17 $$self{'data'}{'isdst'} = $isdst;
4070 12         21 $$self{'data'}{'offset'} = $off;
4071 12         16 $$self{'data'}{'abb'} = $abb;
4072 12         18 $$self{'data'}{'set'} = 1;
4073              
4074 12         32 return 0;
4075             }
4076              
4077             ########################################################################
4078             # BUSINESS DAY METHODS
4079              
4080             sub is_business_day {
4081 13     13 1 59 my($self,$checktime) = @_;
4082 13 50 33     74 if ($$self{'err'} || ! $$self{'data'}{'set'}) {
4083 0         0 carp "WARNING: [is_business_day] Object must contain a valid date";
4084 0         0 return undef;
4085             }
4086 13         21 my $date = $$self{'data'}{'date'};
4087 13         32 return $self->__is_business_day($date,$checktime);
4088             }
4089              
4090             sub __is_business_day {
4091 4515     4515   8429 my($self,$date,$checktime) = @_;
4092 4515         8301 my($y,$m,$d,$h,$mn,$s) = @$date;
4093              
4094 4515         6987 my $dmt = $$self{'tz'};
4095 4515         6821 my $dmb = $$dmt{'base'};
4096              
4097             # Return 0 if it's a weekend.
4098              
4099 4515         13065 my $dow = $dmb->day_of_week([$y,$m,$d]);
4100 4515 100 66     13029 return 0 if ($dow < $dmb->_config('workweekbeg') ||
4101             $dow > $dmb->_config('workweekend'));
4102              
4103             # Return 0 if it's not during work hours (and we're checking
4104             # for that).
4105              
4106 3271 100 66     8932 if ($checktime &&
4107             ! $dmb->_config('workday24hr')) {
4108 559         1813 my $t = $dmb->join('hms',[$h,$mn,$s]);
4109 559         1863 my $t0 = $dmb->join('hms',$$dmb{'data'}{'calc'}{'workdaybeg'});
4110 559         1628 my $t1 = $dmb->join('hms',$$dmb{'data'}{'calc'}{'workdayend'});
4111 559 100 100     2423 return 0 if ($t lt $t0 || $t gt $t1);
4112             }
4113              
4114             # Check for holidays
4115              
4116 3142 100       7231 if (! $$dmb{'data'}{'init_holidays'}) {
4117 1111         3171 $self->_holidays($y-1);
4118 1111         2655 $self->_holidays($y);
4119 1111         2070 $self->_holidays($y+1);
4120             }
4121              
4122             return 0 if (exists $$dmb{'data'}{'holidays'}{'dates'} &&
4123             exists $$dmb{'data'}{'holidays'}{'dates'}{$y+0} &&
4124             exists $$dmb{'data'}{'holidays'}{'dates'}{$y+0}{$m+0} &&
4125 3142 100 100     22667 exists $$dmb{'data'}{'holidays'}{'dates'}{$y+0}{$m+0}{$d+0});
      100        
      100        
4126              
4127 2557         8529 return 1;
4128             }
4129              
4130             sub list_holidays {
4131 84     84 1 31920 my($self,$y) = @_;
4132 84         177 my $dmt = $$self{'tz'};
4133 84         156 my $dmb = $$dmt{'base'};
4134              
4135 84 100 100     267 $y = $$self{'data'}{'date'}[0] if (! $y && $$self{'data'}{'set'} == 1);
4136 84 100       209 $y = $dmt->_now('y',1) if (! $y);
4137 84         306 $self->_holidays($y-1);
4138 84         241 $self->_holidays($y);
4139 84         250 $self->_holidays($y+1);
4140              
4141 84         148 my @ret;
4142 84         148 my @m = sort { $a <=> $b } keys %{ $$dmb{'data'}{'holidays'}{'dates'}{$y+0} };
  90         226  
  84         551  
4143 84         209 foreach my $m (@m) {
4144 130         200 my @d = sort { $a <=> $b } keys %{ $$dmb{'data'}{'holidays'}{'dates'}{$y+0}{$m} };
  37         137  
  130         666  
4145 130         279 foreach my $d (@d) {
4146 163         473 my $hol = $self->new_date();
4147 163         778 $hol->set('date',[$y,$m,$d,0,0,0]);
4148 163         453 push(@ret,$hol);
4149             }
4150             }
4151              
4152 84         431 return @ret;
4153             }
4154              
4155             sub holiday {
4156 33     33 1 228 my($self) = @_;
4157 33 50 33     210 if ($$self{'err'} || ! $$self{'data'}{'set'}) {
4158 0         0 carp "WARNING: [holiday] Object must contain a valid date";
4159 0         0 return undef;
4160             }
4161 33         69 my $dmt = $$self{'tz'};
4162 33         53 my $dmb = $$dmt{'base'};
4163              
4164 33         55 my($y,$m,$d) = @{ $$self{'data'}{'date'} };
  33         105  
4165 33         144 $self->_holidays($y-1);
4166 33         98 $self->_holidays($y);
4167 33         160 $self->_holidays($y+1);
4168              
4169 33 100 66     370 if (exists $$dmb{'data'}{'holidays'}{'dates'}{$y+0} &&
      100        
4170             exists $$dmb{'data'}{'holidays'}{'dates'}{$y+0}{$m+0} &&
4171             exists $$dmb{'data'}{'holidays'}{'dates'}{$y+0}{$m+0}{$d+0}) {
4172 23         43 my @tmp = @{ $$dmb{'data'}{'holidays'}{'dates'}{$y+0}{$m+0}{$d+0} };
  23         130  
4173              
4174 23         57 foreach my $tmp (@tmp) {
4175 28 100       141 $tmp = '' if ($tmp =~ /DMunnamed/);
4176             }
4177              
4178 23 100       54 if (wantarray) {
4179 22 50       55 return () if (! @tmp);
4180 22         100 return @tmp;
4181             } else {
4182 1 50       3 return '' if (! @tmp);
4183 1         5 return $tmp[0];
4184             }
4185             }
4186 10         54 return undef;
4187             }
4188              
4189             sub next_business_day {
4190 12     12 1 55 my($self,$off,$checktime) = @_;
4191 12 50 33     45 if ($$self{'err'} || ! $$self{'data'}{'set'}) {
4192 0         0 carp "WARNING: [next_business_day] Object must contain a valid date";
4193 0         0 return undef;
4194             }
4195 12         19 my $date = $$self{'data'}{'date'};
4196              
4197 12         31 $date = $self->__nextprev_business_day(0,$off,$checktime,$date);
4198 12         31 $self->set('date',$date);
4199 12         25 return;
4200             }
4201              
4202             sub prev_business_day {
4203 12     12 1 60 my($self,$off,$checktime) = @_;
4204 12 50 33     46 if ($$self{'err'} || ! $$self{'data'}{'set'}) {
4205 0         0 carp "WARNING: [prev_business_day] Object must contain a valid date";
4206 0         0 return undef;
4207             }
4208 12         17 my $date = $$self{'data'}{'date'};
4209              
4210 12         28 $date = $self->__nextprev_business_day(1,$off,$checktime,$date);
4211 12         32 $self->set('date',$date);
4212 12         26 return;
4213             }
4214              
4215             sub __nextprev_business_day {
4216 530     530   1282 my($self,$prev,$off,$checktime,$date) = @_;
4217 530         1030 my($y,$m,$d,$h,$mn,$s) = @$date;
4218              
4219 530         856 my $dmt = $$self{'tz'};
4220 530         914 my $dmb = $$dmt{'base'};
4221              
4222             # Get day 0
4223              
4224 530         1727 while (! $self->__is_business_day([$y,$m,$d,$h,$mn,$s],$checktime)) {
4225 455 100       1123 if ($checktime) {
4226             ($y,$m,$d,$h,$mn,$s) =
4227 244         366 @{ $self->__next_prev([$y,$m,$d,$h,$mn,$s],1,undef,0,
4228 244         935 $$dmb{'data'}{'calc'}{'workdaybeg'}) };
4229             } else {
4230             # Move forward 1 day
4231 211         298 ($y,$m,$d) = @{ $dmb->calc_date_days([$y,$m,$d],1) };
  211         638  
4232             }
4233             }
4234              
4235             # Move $off days into the future/past
4236              
4237 530         1478 while ($off > 0) {
4238 140         214 while (1) {
4239 221 100       391 if ($prev) {
4240             # Move backward 1 day
4241 92         128 ($y,$m,$d) = @{ $dmb->calc_date_days([$y,$m,$d],-1) };
  92         251  
4242             } else {
4243             # Move forward 1 day
4244 129         188 ($y,$m,$d) = @{ $dmb->calc_date_days([$y,$m,$d],1) };
  129         372  
4245             }
4246 221 100       680 last if ($self->__is_business_day([$y,$m,$d,$h,$mn,$s]));
4247             }
4248 140         361 $off--;
4249             }
4250              
4251 530         1925 return [$y,$m,$d,$h,$mn,$s];
4252             }
4253              
4254             sub nearest_business_day {
4255 6     6 1 33 my($self,$tomorrow) = @_;
4256 6 50 33     26 if ($$self{'err'} || ! $$self{'data'}{'set'}) {
4257 0         0 carp "WARNING: [nearest_business_day] Object must contain a valid date";
4258 0         0 return undef;
4259             }
4260              
4261 6         12 my $date = $$self{'data'}{'date'};
4262 6         16 $date = $self->__nearest_business_day($tomorrow,$date);
4263              
4264             # If @date is empty, the date is a business day and doesn't need
4265             # to be changed.
4266              
4267 6 100       14 return if (! defined($date));
4268              
4269 2         15 $self->set('date',$date);
4270 2         5 return;
4271             }
4272              
4273             sub __nearest_business_day {
4274 6     6   10 my($self,$tomorrow,$date) = @_;
4275              
4276             # We're done if this is a business day
4277 6 100       16 return undef if ($self->__is_business_day($date,0));
4278              
4279 2         5 my $dmt = $$self{'tz'};
4280 2         5 my $dmb = $$dmt{'base'};
4281              
4282 2 50       8 $tomorrow = $dmb->_config('tomorrowfirst') if (! defined $tomorrow);
4283              
4284 2         3 my($a1,$a2);
4285 2 50       6 if ($tomorrow) {
4286 2         40 ($a1,$a2) = (1,-1);
4287             } else {
4288 0         0 ($a1,$a2) = (-1,1);
4289             }
4290              
4291 2         6 my ($y,$m,$d,$h,$mn,$s) = @$date;
4292 2         4 my ($y1,$m1,$d1) = ($y,$m,$d);
4293 2         6 my ($y2,$m2,$d2) = ($y,$m,$d);
4294              
4295 2         3 while (1) {
4296 2         4 ($y1,$m1,$d1) = @{ $dmb->calc_date_days([$y1,$m1,$d1],$a1) };
  2         9  
4297 2 100       10 if ($self->__is_business_day([$y1,$m1,$d1,$h,$mn,$s],0)) {
4298 1         4 ($y,$m,$d) = ($y1,$m1,$d1);
4299 1         2 last;
4300             }
4301 1         4 ($y2,$m2,$d2) = @{ $dmb->calc_date_days([$y2,$m2,$d2],$a2) };
  1         5  
4302 1 50       8 if ($self->__is_business_day([$y2,$m2,$d2,$h,$mn,$s],0)) {
4303 1         3 ($y,$m,$d) = ($y2,$m2,$d2);
4304 1         4 last;
4305             }
4306             }
4307              
4308 2         8 return [$y,$m,$d,$h,$mn,$s];
4309             }
4310              
4311             # We need to create all the objects which will be used to determine holidays.
4312             # By doing this once only, a lot of time is saved.
4313             #
4314             sub _holiday_objs {
4315 34     34   117 my($self) = @_;
4316 34         105 my $dmt = $$self{'tz'};
4317 34         86 my $dmb = $$dmt{'base'};
4318              
4319 34         105 $$dmb{'data'}{'holidays'}{'init'} = 1;
4320              
4321             # Go through all of the strings from the config file.
4322             #
4323 34         85 my (@str) = @{ $$dmb{'data'}{'sections'}{'holidays'} };
  34         271  
4324 34         188 $$dmb{'data'}{'holidays'}{'defs'} = [];
4325              
4326             # Keep track of the holiday names
4327 34         85 my $unnamed = 0;
4328              
4329             LINE:
4330 34         149 while (@str) {
4331 207         488 my($string) = shift(@str);
4332 207         375 my($name) = shift(@str);
4333 207 100       445 if (! $name) {
4334 14         41 $unnamed++;
4335 14         55 $name = "DMunnamed $unnamed";
4336             }
4337              
4338             # If $string is a parse_date string AND it contains a year, we'll
4339             # store the date as a holiday, but not store the holiday description
4340             # so it never needs to be re-parsed.
4341              
4342 207         674 my $date = $self->new_date();
4343 207         636 my $err = $date->parse_date($string);
4344              
4345 207 100       502 if (! $err) {
4346 105         546 my($y,$m,$d) = @{ $$date{'data'}{'date'} };
  105         268  
4347              
4348 105 100       287 if ($$date{'data'}{'def'}[0] eq '') {
4349             # Lines of the form: Jun 12
4350             #
4351             # We will NOT cache this holiday because we want to only
4352             # cache holidays from lines like 'Jun 12 1972' during this
4353             # phase so we find conflicts.
4354              
4355 92         143 push(@{ $$dmb{'data'}{'holidays'}{'defs'} },$name,$string);
  92         333  
4356              
4357             } else {
4358             # Lines of the form: Jun 12 1972
4359             #
4360             # We'll cache these to make sure we don't have two lines:
4361             # Jun 12 1972 = Some Holiday
4362             # Jun 13 1972 = Some Holiday
4363              
4364 13 50       140 if (exists $$dmb{'data'}{'holidays'}{'hols'}{$name}{$y+0}) {
4365 0         0 carp "WARNING: Holiday defined twice for one year: $name [$y]";
4366 0         0 next LINE;
4367             }
4368              
4369 13         99 $$dmb{'data'}{'holidays'}{'yhols'}{$y+0}{$name} = [$y,$m,$d];
4370 13         67 $$dmb{'data'}{'holidays'}{'hols'}{$name}{$y+0} = [$y,$m,$d];
4371              
4372 13 50       128 if (exists $$dmb{'data'}{'holidays'}{'dates'}{$y+0}{$m+0}{$d+0}) {
4373 0         0 push @{ $$dmb{'data'}{'holidays'}{'dates'}{$y+0}{$m+0}{$d+0} },$name;
  0         0  
4374             } else {
4375 13         119 $$dmb{'data'}{'holidays'}{'dates'}{$y+0}{$m+0}{$d+0} = [ $name ];
4376             }
4377             }
4378 105         653 next LINE;
4379             }
4380 102         512 $date->err(1);
4381              
4382             # If $string is a recurrence, we'll create a Recur object (which we
4383             # only have to do once) and store it.
4384              
4385 102         307 my $recur = $self->new_recur();
4386 102         367 $err = $recur->parse($string);
4387 102 50       304 if (! $err) {
4388 102         157 push(@{ $$dmb{'data'}{'holidays'}{'defs'} },$name,$recur);
  102         425  
4389 102         898 next LINE;
4390             }
4391 0         0 $recur->err(1);
4392              
4393 0         0 carp "WARNING: invalid holiday description: $string";
4394             }
4395 34         122 return;
4396             }
4397              
4398             # Make sure that holidays are done for a given year.
4399             #
4400             sub _holidays {
4401 3711     3711   5726 my($self,$year) = @_;
4402              
4403 3711         5115 my $dmt = $$self{'tz'};
4404 3711         4948 my $dmb = $$dmt{'base'};
4405              
4406 3711 100       9348 return if ($$dmb{'data'}{'holidays'}{'ydone'}{$year+0});
4407 265 100       973 $self->_holiday_objs() if (! $$dmb{'data'}{'holidays'}{'init'});
4408              
4409             # Parse the year
4410              
4411             # Get the objects and set them to use the new year. Also, get the
4412             # range for recurrences.
4413              
4414 265         497 my @hol = @{ $$dmb{'data'}{'holidays'}{'defs'} };
  265         1515  
4415              
4416 265         797 my $beg = "$year-01-01-00:00:00";
4417 265         572 my $end = "$year-12-31-23:59:59";
4418              
4419             # Get the date for each holiday.
4420              
4421 265         527 $$dmb{'data'}{'init_holidays'} = 1;
4422 265         900 $$dmb{'data'}{'tmpnow'} = [$year,1,1,0,0,0];
4423              
4424             HOLIDAY:
4425 265         732 while (@hol) {
4426              
4427 1374         2750 my $name = shift(@hol);
4428 1374         2344 my $obj = shift(@hol);
4429              
4430             # Each holiday only gets defined once per year
4431 1374 100       4775 next if (exists $$dmb{'data'}{'holidays'}{'hols'}{$name}{$year+0});
4432              
4433 1350 100       3369 if (ref($obj)) {
4434             # It's a recurrence
4435              
4436             # We have to initialize the recurrence as it may contain idates
4437             # and dates outside of this range that are not correct.
4438              
4439 766         2628 $obj->_init_dates();
4440              
4441             # If the recurrence has a date range built in, we won't override it.
4442             # Otherwise, we'll only look for dates in this year.
4443              
4444 766         1047 my @dates;
4445 766 100 66     1959 if ($obj->start() && $obj->end()) {
4446 84         284 @dates = $obj->dates();
4447             } else {
4448 682         1946 @dates = $obj->dates($beg,$end,1);
4449             }
4450              
4451 766         1918 foreach my $date (@dates) {
4452 878         1623 my($y,$m,$d) = @{ $$date{'data'}{'date'} };
  878         2389  
4453              
4454 878         4140 $$dmb{'data'}{'holidays'}{'yhols'}{$year+0}{$name} = [$y,$m,$d];
4455 878         2903 $$dmb{'data'}{'holidays'}{'hols'}{$name}{$year+0} = [$y,$m,$d];
4456              
4457 878 100       3863 if (exists $$dmb{'data'}{'holidays'}{'dates'}{$y+0}{$m+0}{$d+0}) {
4458 213         341 push @{ $$dmb{'data'}{'holidays'}{'dates'}{$y+0}{$m+0}{$d+0} },$name;
  213         1008  
4459             } else {
4460 665         4132 $$dmb{'data'}{'holidays'}{'dates'}{$y+0}{$m+0}{$d+0} = [$name];
4461             }
4462             }
4463              
4464             } else {
4465 584         1727 my $date = $self->new_date();
4466 584         2107 $date->parse_date($obj);
4467 584         1009 my($y,$m,$d) = @{ $$date{'data'}{'date'} };
  584         1626  
4468              
4469 584         2567 $$dmb{'data'}{'holidays'}{'yhols'}{$year+0}{$name} = [$y,$m,$d];
4470 584         2032 $$dmb{'data'}{'holidays'}{'hols'}{$name}{$year+0} = [$y,$m,$d];
4471              
4472 584 100       2519 if (exists $$dmb{'data'}{'holidays'}{'dates'}{$y+0}{$m+0}{$d+0}) {
4473 8         14 push @{ $$dmb{'data'}{'holidays'}{'dates'}{$y+0}{$m+0}{$d+0} },$name;
  8         73  
4474             } else {
4475 576         4930 $$dmb{'data'}{'holidays'}{'dates'}{$y+0}{$m+0}{$d+0} = [$name];
4476             }
4477             }
4478             }
4479              
4480 265         722 $$dmb{'data'}{'init_holidays'} = 0;
4481 265         744 $$dmb{'data'}{'tmpnow'} = [];
4482 265         802 $$dmb{'data'}{'holidays'}{'ydone'}{$year+0} = 1;
4483 265         684 return;
4484             }
4485              
4486             ########################################################################
4487             # PRINTF METHOD
4488              
4489 0         0 BEGIN {
4490 168     168   1046121 my %pad_0 = map { $_,1 } qw ( Y m d H M S I j G W L U );
  2016         5217  
4491 168         642 my %pad_sp = map { $_,1 } qw ( y f e k i );
  840         1800  
4492 168         604 my %hr = map { $_,1 } qw ( H k I i );
  672         1745  
4493 168         561 my %dow = map { $_,1 } qw ( v a A w );
  672         1576  
4494 168         455 my %num = map { $_,1 } qw ( Y m d H M S y f e k I i j G W L U );
  2856         473657  
4495              
4496             sub printf {
4497 47     47 1 273 my($self,@in) = @_;
4498 47 50 33     203 if ($$self{'err'} || ! $$self{'data'}{'set'}) {
4499 0         0 carp "WARNING: [printf] Object must contain a valid date";
4500 0         0 return undef;
4501             }
4502              
4503 47         71 my $dmt = $$self{'tz'};
4504 47         75 my $dmb = $$dmt{'base'};
4505              
4506 47         64 my($y,$m,$d,$h,$mn,$s) = @{ $$self{'data'}{'date'} };
  47         100  
4507              
4508 47         64 my(@out);
4509 47         80 foreach my $in (@in) {
4510 49         77 my $out = '';
4511 49         95 while ($in) {
4512 559 50       981 last if ($in eq '%');
4513              
4514             # Everything up to the first '%'
4515              
4516 559 100       1620 if ($in =~ s/^([^%]+)//) {
4517 230         428 $out .= $1;
4518 230         417 next;
4519             }
4520              
4521             # Extended formats: %<...>
4522              
4523 329 100       600 if ($in =~ s/^%<([^>]+)>//) {
4524 20         44 my $f = $1;
4525 20         23 my $val;
4526              
4527 20 100       119 if ($f =~ /^a=([1-7])$/) {
    100          
    100          
    100          
    100          
    100          
    50          
4528 3         12 $val = $$dmb{'data'}{'wordlist'}{'day_abb'}[$1-1];
4529              
4530             } elsif ($f =~ /^v=([1-7])$/) {
4531 3         11 $val = $$dmb{'data'}{'wordlist'}{'day_char'}[$1-1];
4532              
4533             } elsif ($f =~ /^A=([1-7])$/) {
4534 3         18 $val = $$dmb{'data'}{'wordlist'}{'day_name'}[$1-1];
4535              
4536             } elsif ($f =~ /^p=([1-2])$/) {
4537 2         13 $val = $$dmb{'data'}{'wordlist'}{'ampm'}[$1-1];
4538              
4539             } elsif ($f =~ /^b=(0?[1-9]|1[0-2])$/) {
4540 3         15 $val = $$dmb{'data'}{'wordlist'}{'month_abb'}[$1-1];
4541              
4542             } elsif ($f =~ /^B=(0?[1-9]|1[0-2])$/) {
4543 3         16 $val = $$dmb{'data'}{'wordlist'}{'month_name'}[$1-1];
4544              
4545             } elsif ($f =~ /^E=(0?[1-9]|[1-4][0-9]|5[0-3])$/) {
4546 3         19 $val = $$dmb{'data'}{'wordlist'}{'nth'}[$1-1];
4547              
4548             } else {
4549 0         0 $val = '%<' . $1 . '>';
4550             }
4551 20         36 $out .= $val;
4552 20         44 next;
4553             }
4554              
4555             # Normals one-character formats
4556              
4557 309         770 $in =~ s/^%(.)//s;
4558 309         587 my $f = $1;
4559              
4560 309 100       676 if (exists $$self{'data'}{'f'}{$f}) {
4561 27         39 $out .= $$self{'data'}{'f'}{$f};
4562 27         47 next;
4563             }
4564              
4565 282         381 my ($val,$pad,$len,$dow);
4566              
4567 282 100       541 if (exists $pad_0{$f}) {
4568 133         180 $pad = '0';
4569             }
4570              
4571 282 100       490 if (exists $pad_sp{$f}) {
4572 23         33 $pad = ' ';
4573             }
4574              
4575 282 100 100     850 if ($f eq 'G' || $f eq 'W') {
4576 5         21 my($yy,$ww) = $dmb->_week_of_year(1,[$y,$m,$d]);
4577 5 100       12 if ($f eq 'G') {
4578 2         4 $val = $yy;
4579 2         3 $len = 4;
4580             } else {
4581 3         7 $val = $ww;
4582 3         36 $len = 2;
4583             }
4584             }
4585              
4586 282 100 100     789 if ($f eq 'L' || $f eq 'U') {
4587 3         18 my($yy,$ww) = $dmb->_week_of_year(7,[$y,$m,$d]);
4588 3 100       11 if ($f eq 'L') {
4589 1         2 $val = $yy;
4590 1         3 $len = 4;
4591             } else {
4592 2         5 $val = $ww;
4593 2         5 $len = 2;
4594             }
4595             }
4596              
4597 282 100 100     678 if ($f eq 'Y' || $f eq 'y') {
4598 28         41 $val = $y;
4599 28         49 $len = 4;
4600             }
4601              
4602 282 100 100     732 if ($f eq 'm' || $f eq 'f') {
4603 8         20 $val = $m;
4604 8         12 $len = 2;
4605             }
4606              
4607 282 100 100     767 if ($f eq 'd' || $f eq 'e') {
4608 29         60 $val = $d;
4609 29         41 $len = 2;
4610             }
4611              
4612 282 100       452 if ($f eq 'j') {
4613 3         19 $val = $dmb->day_of_year([$y,$m,$d]);
4614 3         9 $len = 3;
4615             }
4616              
4617              
4618 282 100       474 if (exists $hr{$f}) {
4619 34         68 $val = $h;
4620 34 100 100     107 if ($f eq 'I' || $f eq 'i') {
4621 7 100       26 $val -= 12 if ($val > 12);
4622 7 50       23 $val = 12 if ($val == 0);
4623             }
4624 34         49 $len = 2;
4625             }
4626              
4627 282 100       454 if ($f eq 'M') {
4628 24         37 $val = $mn;
4629 24         29 $len = 2;
4630             }
4631              
4632 282 100       443 if ($f eq 'S') {
4633 22         36 $val = $s;
4634 22         34 $len = 2;
4635             }
4636              
4637 282 100       482 if (exists $dow{$f}) {
4638 26         98 $dow = $dmb->day_of_week([$y,$m,$d]);
4639             }
4640              
4641             ###
4642              
4643 282 100 100     1401 if (exists $num{$f}) {
    100 100        
    100 100        
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
    0          
    0          
4644 156         304 while (length($val) < $len) {
4645 106         253 $val = "$pad$val";
4646             }
4647              
4648 156 100       279 $val = substr($val,2,2) if ($f eq 'y');
4649              
4650             } elsif ($f eq 'b' || $f eq 'h') {
4651 24         65 $val = $$dmb{'data'}{'wordlist'}{'month_abb'}[$m-1];
4652              
4653             } elsif ($f eq 'B') {
4654 3         12 $val = $$dmb{'data'}{'wordlist'}{'month_name'}[$m-1];
4655              
4656             } elsif ($f eq 'v') {
4657 2         13 $val = $$dmb{'data'}{'wordlist'}{'day_char'}[$dow-1];
4658              
4659             } elsif ($f eq 'a') {
4660 18         102 $val = $$dmb{'data'}{'wordlist'}{'day_abb'}[$dow-1];
4661              
4662             } elsif ($f eq 'A') {
4663 3         12 $val = $$dmb{'data'}{'wordlist'}{'day_name'}[$dow-1];
4664              
4665             } elsif ($f eq 'w') {
4666 3         7 $val = $dow;
4667              
4668             } elsif ($f eq 'p') {
4669 4 100       18 my $i = ($h >= 12 ? 1 : 0);
4670 4         17 $val = $$dmb{'data'}{'wordlist'}{'ampm'}[$i];
4671              
4672             } elsif ($f eq 'Z') {
4673 19         39 $val = $$self{'data'}{'abb'};
4674              
4675             } elsif ($f eq 'N') {
4676 4         15 my $off = $$self{'data'}{'offset'};
4677 4         16 $val = $dmb->join('offset',$off);
4678              
4679             } elsif ($f eq 'z') {
4680 4         11 my $off = $$self{'data'}{'offset'};
4681 4         17 $val = $dmb->join('offset',$off);
4682 4         20 $val =~ s/://g;
4683 4         15 $val =~ s/00$//;
4684              
4685             } elsif ($f eq 'E') {
4686 2         10 $val = $$dmb{'data'}{'wordlist'}{'nth_dom'}[$d-1];
4687              
4688             } elsif ($f eq 's') {
4689 2         11 $val = $self->secs_since_1970_GMT();
4690              
4691             } elsif ($f eq 'o') {
4692 2         208 my $date2 = $self->new_date();
4693 2         18 $date2->parse('1970-01-01 00:00:00');
4694 2         12 my $delta = $date2->calc($self);
4695 2         12 $val = $delta->printf('%sys');
4696              
4697             } elsif ($f eq 'l') {
4698 4         13 my $d0 = $self->new_date();
4699 4         11 my $d1 = $self->new_date();
4700 4         14 $d0->parse('-0:6:0:0:0:0:0'); # 6 months ago
4701 4         21 $d1->parse('+0:6:0:0:0:0:0'); # in 6 months
4702 4         10 $d0 = $d0->value();
4703 4         23 $d1 = $d1->value();
4704 4         19 my $date = $self->value();
4705 4 100 100     19 if ($date lt $d0 || $date ge $d1) {
4706 2         14 $in = '%b %e %Y' . $in;
4707             } else {
4708 2         5 $in = '%b %e %H:%M' . $in;
4709             }
4710 4         9 $val = '';
4711              
4712             } elsif ($f eq 'c') {
4713 1         4 $in = '%a %b %e %H:%M:%S %Y' . $in;
4714 1         3 $val = '';
4715              
4716             } elsif ($f eq 'C' || $f eq 'u') {
4717 2         7 $in = '%a %b %e %H:%M:%S %Z %Y' . $in;
4718 2         5 $val = '';
4719              
4720             } elsif ($f eq 'g') {
4721 13         41 $in = '%a, %d %b %Y %H:%M:%S %Z' . $in;
4722 13         26 $val = '';
4723              
4724             } elsif ($f eq 'D') {
4725 2         7 $in = '%m/%d/%y' . $in;
4726 2         4 $val = '';
4727              
4728             } elsif ($f eq 'r') {
4729 1         4 $in = '%I:%M:%S %p' . $in;
4730 1         2 $val = '';
4731              
4732             } elsif ($f eq 'R') {
4733 1         4 $in = '%H:%M' . $in;
4734 1         3 $val = '';
4735              
4736             } elsif ($f eq 'T' || $f eq 'X') {
4737 2         7 $in = '%H:%M:%S' . $in;
4738 2         4 $val = '';
4739              
4740             } elsif ($f eq 'V') {
4741 1         7 $in = '%m%d%H%M%y' . $in;
4742 1         2 $val = '';
4743              
4744             } elsif ($f eq 'Q') {
4745 1         4 $in = '%Y%m%d' . $in;
4746 1         2 $val = '';
4747              
4748             } elsif ($f eq 'q') {
4749 1         3 $in = '%Y%m%d%H%M%S' . $in;
4750 1         3 $val = '';
4751              
4752             } elsif ($f eq 'P') {
4753 1         15 $in = '%Y%m%d%H:%M:%S' . $in;
4754 1         3 $val = '';
4755              
4756             } elsif ($f eq 'O') {
4757 1         4 $in = '%Y-%m-%dT%H:%M:%S' . $in;
4758 1         3 $val = '';
4759              
4760             } elsif ($f eq 'F') {
4761 1         3 $in = '%A, %B %e, %Y' . $in;
4762 1         3 $val = '';
4763              
4764             } elsif ($f eq 'K') {
4765 1         3 $in = '%Y-%j' . $in;
4766 1         2 $val = '';
4767              
4768             } elsif ($f eq 'x') {
4769 2 100       23 if ($dmb->_config('dateformat') eq 'US') {
4770 1         4 $in = '%m/%d/%y' . $in;
4771             } else {
4772 1         5 $in = '%d/%m/%y' . $in;
4773             }
4774 2         6 $val = '';
4775              
4776             } elsif ($f eq 'J') {
4777 1         3 $in = '%G-W%W-%w' . $in;
4778 1         2 $val = '';
4779              
4780             } elsif ($f eq 'n') {
4781 0         0 $val = "\n";
4782              
4783             } elsif ($f eq 't') {
4784 0         0 $val = "\t";
4785              
4786             } else {
4787 0         0 $val = $f;
4788             }
4789              
4790 282 100       520 if ($val ne '') {
4791 246         526 $$self{'data'}{'f'}{$f} = $val;
4792 246         514 $out .= $val;
4793             }
4794             }
4795 49         169 push(@out,$out);
4796             }
4797              
4798 47 100       105 if (wantarray) {
    50          
4799 35         176 return @out;
4800             } elsif (@out == 1) {
4801 12         44 return $out[0];
4802             }
4803              
4804 0         0 return ''
4805             }
4806             }
4807              
4808             ########################################################################
4809             # EVENT METHODS
4810              
4811             sub list_events {
4812 21     21 1 132 my($self,@args) = @_;
4813 21 50 33     119 if ($$self{'err'} || ! $$self{'data'}{'set'}) {
4814 0         0 carp "WARNING: [list_events] Object must contain a valid date";
4815 0         0 return undef;
4816             }
4817 21         47 my $dmt = $$self{'tz'};
4818 21         37 my $dmb = $$dmt{'base'};
4819              
4820             # Arguments
4821              
4822 21         39 my($date,$day,$format);
4823 21 100 100     83 if (@args && $args[$#args] eq 'dates') {
4824 9         15 pop(@args);
4825 9         19 $format = 'dates';
4826             } else {
4827 12         20 $format = 'std';
4828             }
4829              
4830 21 100 66     147 if (@args && $#args==0 && ref($args[0]) eq 'Date::Manip::Date') {
    100 100        
    50 66        
      66        
4831 4         10 $date = $args[0];
4832             } elsif (@args && $#args==0 && $args[0]==0) {
4833 2         5 $day = 1;
4834             } elsif (@args) {
4835 0         0 carp "ERROR: [list_events] unknown argument list";
4836 0         0 return [];
4837             }
4838              
4839             # Get the beginning/end dates we're looking for events in
4840              
4841 21         43 my($beg,$end);
4842 21 100       53 if ($date) {
    100          
4843 4         11 $beg = $self;
4844 4         6 $end = $date;
4845             } elsif ($day) {
4846 2         8 $beg = $self->new_date();
4847 2         19 $end = $self->new_date();
4848 2         8 my($y,$m,$d) = $self->value();
4849 2         11 $beg->set('date',[$y,$m,$d,0,0,0]);
4850 2         12 $end->set('date',[$y,$m,$d,23,59,59]);
4851             } else {
4852 15         22 $beg = $self;
4853 15         22 $end = $self;
4854             }
4855              
4856 21 50       59 if ($beg->cmp($end) == 1) {
4857 0         0 my $tmp = $beg;
4858 0         0 $beg = $end;
4859 0         0 $end = $tmp;
4860             }
4861              
4862             # We need to get a list of all events which may apply.
4863              
4864 21         47 my($y0) = $beg->value();
4865 21         53 my($y1) = $end->value();
4866 21         73 foreach my $y ($y0..$y1) {
4867 21         68 $self->_events_year($y);
4868             }
4869              
4870 21         47 my @events = ();
4871 21         33 foreach my $i (keys %{ $$dmb{'data'}{'events'} }) {
  21         83  
4872 231         390 my $event = $$dmb{'data'}{'events'}{$i};
4873 231         347 my $type = $$event{'type'};
4874 231         854 my $name = $$event{'name'};
4875              
4876 231 100 100     621 if ($type eq 'specified') {
    100          
    50          
4877 129         204 my $d0 = $$dmb{'data'}{'events'}{$i}{'beg'};
4878 129         192 my $d1 = $$dmb{'data'}{'events'}{$i}{'end'};
4879 129         289 push @events,[$d0,$d1,$name];
4880              
4881             } elsif ($type eq 'ym' || $type eq 'date') {
4882 52         125 foreach my $y ($y0..$y1) {
4883 52 50       137 if (exists $$dmb{'data'}{'events'}{$i}{$y}) {
4884 52         77 my($d0,$d1) = @{ $$dmb{'data'}{'events'}{$i}{$y} };
  52         104  
4885 52         177 push @events,[$d0,$d1,$name];
4886             }
4887             }
4888              
4889             } elsif ($type eq 'recur') {
4890 50         98 my $rec = $$dmb{'data'}{'events'}{$i}{'recur'};
4891 50         84 my $del = $$dmb{'data'}{'events'}{$i}{'delta'};
4892 50         158 my @d = $rec->dates($beg,$end);
4893 50         151 foreach my $d0 (@d) {
4894 4         16 my $d1 = $d0->calc($del);
4895 4         25 push @events,[$d0,$d1,$name];
4896             }
4897             }
4898             }
4899              
4900             # Next we need to see which ones apply.
4901              
4902 21         64 my @tmp;
4903 21         49 foreach my $e (@events) {
4904 185         344 my($d0,$d1,$name) = @$e;
4905              
4906 185 100 100     332 push(@tmp,$e) if ($beg->cmp($d1) != 1 &&
4907             $end->cmp($d0) != -1);
4908             }
4909              
4910             # Now format them...
4911              
4912 21 100       82 if ($format eq 'std') {
    50          
4913 12 50 100     84 @events = sort { $$a[0]->cmp($$b[0]) ||
  19         50  
4914             $$a[1]->cmp($$b[1]) ||
4915             $$a[2] cmp $$b[2] } @tmp;
4916              
4917             } elsif ($format eq 'dates') {
4918 9         28 my $p1s = $self->new_delta();
4919 9         43 $p1s->parse('+0:0:0:0:0:0:1');
4920              
4921 9         42 @events = ();
4922 9         17 my (@tmp2);
4923 9         21 foreach my $e (@tmp) {
4924 22         42 my $name = $$e[2];
4925 22 100       58 if ($$e[0]->cmp($beg) == -1) {
4926             # Event begins before the start
4927 9         28 push(@tmp2,[$beg,'+',$name]);
4928             } else {
4929 13         41 push(@tmp2,[$$e[0],'+',$name]);
4930             }
4931              
4932 22         62 my $d1 = $$e[1]->calc($p1s);
4933              
4934 22 100       58 if ($d1->cmp($end) == -1) {
4935             # Event ends before the end
4936 12         41 push(@tmp2,[$d1,'-',$name]);
4937             }
4938             }
4939              
4940 9 50       31 return () if (! @tmp2);
4941 9 50 100     56 @tmp2 = sort { $$a[0]->cmp($$b[0]) ||
  51         110  
4942             $$a[1] cmp $$b[1] ||
4943             $$a[2] cmp $$b[2] } @tmp2;
4944              
4945             # @tmp2 is now:
4946             # ( [ DATE1, OP1, NAME1 ], [ DATE2, OP2, NAME2 ], ... )
4947             # which is sorted by date.
4948              
4949 9         21 my $d = $tmp2[0]->[0];
4950              
4951 9 100       27 if ($beg->cmp($d) != 0) {
4952 1         7 push(@events,[$beg]);
4953             }
4954              
4955 9         23 my %e;
4956 9         17 while (1) {
4957              
4958             # If the first element is the same date as we're
4959             # currently working with, just perform the operation
4960             # and remove it from the list. If the list is not empty,
4961             # we'll proceed to the next element.
4962              
4963 50         77 my $d0 = $tmp2[0]->[0];
4964 50 100       91 if ($d->cmp($d0) == 0) {
4965 34         57 my $e = shift(@tmp2);
4966 34         60 my $op = $$e[1];
4967 34         59 my $n = $$e[2];
4968 34 100       74 if ($op eq '+') {
4969 22         58 $e{$n} = 1;
4970             } else {
4971 12         26 delete $e{$n};
4972             }
4973              
4974 34 100       106 next if (@tmp2);
4975             }
4976              
4977             # We need to store the existing %e.
4978              
4979 25         93 my @n = sort keys %e;
4980 25         66 push(@events,[$d,@n]);
4981              
4982             # If the list is empty, we're done. Otherwise, we need to
4983             # reset the date and continue.
4984              
4985 25 100       101 last if (! @tmp2);
4986 16         34 $d = $tmp2[0]->[0];
4987             }
4988             }
4989              
4990 21         141 return @events;
4991             }
4992              
4993             # The events of type date and ym are determined on a year-by-year basis
4994             #
4995             sub _events_year {
4996 21     21   42 my($self,$y) = @_;
4997 21         44 my $dmt = $$self{'tz'};
4998 21         44 my $dmb = $$dmt{'base'};
4999 21         70 my $tz = $dmt->_now('tz',1);
5000 21 50       72 return if (exists $$dmb{'data'}{'eventyears'}{$y});
5001 21 100       87 $self->_event_objs() if (! $$dmb{'data'}{'eventobjs'});
5002              
5003 21         69 my $d = $self->new_date();
5004 21         137 $d->config('forcedate',"${y}-01-01-00:00:00,$tz");
5005              
5006 21         71 my $hrM1 = $d->new_delta();
5007 21         124 $hrM1->set('delta',[0,0,0,0,0,59,59]);
5008              
5009 21         112 my $dayM1 = $d->new_delta();
5010 21         113 $dayM1->set('delta',[0,0,0,0,23,59,59]);
5011              
5012 21         47 foreach my $i (keys %{ $$dmb{'data'}{'events'} }) {
  21         125  
5013 231         397 my $event = $$dmb{'data'}{'events'}{$i};
5014 231         340 my $type = $$event{'type'};
5015              
5016 231 100       511 if ($type eq 'ym') {
    100          
5017 26         48 my $beg = $$event{'beg'};
5018 26         44 my $end = $$event{'end'};
5019 26         72 my $d0 = $d->new_date();
5020 26         109 $d0->parse_date($beg);
5021 26         124 $d0->set('time',[0,0,0]);
5022              
5023 26         50 my $d1;
5024 26 100       62 if ($end) {
5025 13         47 $d1 = $d0->new_date();
5026 13         54 $d1->parse_date($end);
5027 13         67 $d1->set('time',[23,59,59]);
5028             } else {
5029 13         38 $d1 = $d0->calc($dayM1);
5030             }
5031 26         278 $$dmb{'data'}{'events'}{$i}{$y} = [ $d0,$d1 ];
5032              
5033             } elsif ($type eq 'date') {
5034 26         53 my $beg = $$event{'beg'};
5035 26         48 my $end = $$event{'end'};
5036 26         39 my $del = $$event{'delta'};
5037 26         77 my $d0 = $d->new_date();
5038 26         87 $d0->parse($beg);
5039              
5040 26         37 my $d1;
5041 26 50       98 if ($end) {
    50          
5042 0         0 $d1 = $d0->new_date();
5043 0         0 $d1->parse($end);
5044             } elsif ($del) {
5045 26         75 $d1 = $d0->calc($del);
5046             } else {
5047 0         0 $d1 = $d0->calc($hrM1);
5048             }
5049 26         305 $$dmb{'data'}{'events'}{$i}{$y} = [ $d0,$d1 ];
5050             }
5051             }
5052              
5053 21         216 return;
5054             }
5055              
5056             # This parses the raw event list. It only has to be done once.
5057             #
5058             sub _event_objs {
5059 3     3   8 my($self) = @_;
5060 3         8 my $dmt = $$self{'tz'};
5061 3         7 my $dmb = $$dmt{'base'};
5062             # Only parse once.
5063 3         8 $$dmb{'data'}{'eventobjs'} = 1;
5064              
5065 3         14 my $hrM1 = $self->new_delta();
5066 3         20 $hrM1->set('delta',[0,0,0,0,0,59,59]);
5067              
5068 3         15 my $M1 = $self->new_delta();
5069 3         15 $M1->set('delta',[0,0,0,0,0,0,-1]);
5070              
5071 3         15 my @tmp = @{ $$dmb{'data'}{'sections'}{'events'} };
  3         45  
5072 3         8 my $i = 0;
5073 3         12 while (@tmp) {
5074 33         80 my $string = shift(@tmp);
5075 33         59 my $name = shift(@tmp);
5076 33         194 my @event = split(/\s*;\s*/,$string);
5077              
5078 33 100       103 if ($#event == 0) {
    50          
5079              
5080             # YMD/YM
5081              
5082 15         51 my $d1 = $self->new_date();
5083 15         52 my $err = $d1->parse_date($event[0]);
5084 15 100       42 if (! $err) {
5085 6 100       25 if ($$d1{'data'}{'def'}[0] eq '') {
5086             # YM
5087 2         27 $$dmb{'data'}{'events'}{$i++} = { 'type' => 'ym',
5088             'name' => $name,
5089             'beg' => $event[0] };
5090             } else {
5091             # YMD
5092 4         16 my $d2 = $d1->new_date();
5093 4         22 my ($y,$m,$d) = $d1->value();
5094 4         19 $d1->set('time',[0,0,0]);
5095 4         22 $d2->set('date',[$y,$m,$d,23,59,59]);
5096 4         60 $$dmb{'data'}{'events'}{$i++} = { 'type' => 'specified',
5097             'name' => $name,
5098             'beg' => $d1,
5099             'end' => $d2 };
5100             }
5101 6         60 next;
5102             }
5103              
5104             # Date
5105              
5106 9         32 $err = $d1->parse($event[0]);
5107 9 100       42 if (! $err) {
5108 5 100       29 if ($$d1{'data'}{'def'}[0] eq '') {
5109             # Date (no year)
5110 2         20 $$dmb{'data'}{'events'}{$i++} = { 'type' => 'date',
5111             'name' => $name,
5112             'beg' => $event[0],
5113             'delta' => $hrM1
5114             };
5115             } else {
5116             # Date (year)
5117 3         15 my $d2 = $d1->calc($hrM1);
5118 3         19 $$dmb{'data'}{'events'}{$i++} = { 'type' => 'specified',
5119             'name' => $name,
5120             'beg' => $d1,
5121             'end' => $d2
5122             };
5123             }
5124 5         25 next;
5125             }
5126              
5127             # Recur
5128              
5129 4         22 my $r = $self->new_recur();
5130 4         32 $err = $r->parse($event[0]);
5131 4 50       15 if ($err) {
5132 0         0 carp "ERROR: invalid event definition (must be Date, YMD, YM, or Recur)\n"
5133             . " $string\n";
5134 0         0 next;
5135             }
5136              
5137 4         24 my @d = $r->dates();
5138 4 50       15 if (@d) {
5139 0         0 foreach my $d (@d) {
5140 0         0 my $d2 = $d->calc($hrM1);
5141 0         0 $$dmb{'data'}{'events'}{$i++} = { 'type' => 'specified',
5142             'name' => $name,
5143             'beg' => $d1,
5144             'end' => $d2
5145             };
5146             }
5147             } else {
5148 4         71 $$dmb{'data'}{'events'}{$i++} = { 'type' => 'recur',
5149             'name' => $name,
5150             'recur' => $r,
5151             'delta' => $hrM1
5152             };
5153             }
5154              
5155             } elsif ($#event == 1) {
5156 18         47 my($o1,$o2) = @event;
5157              
5158             # YMD;YMD
5159             # YM;YM
5160              
5161 18         65 my $d1 = $self->new_date();
5162 18         105 my $err = $d1->parse_date($o1);
5163 18 100       66 if (! $err) {
5164 9         38 my $d2 = $self->new_date();
5165 9         66 $err = $d2->parse_date($o2);
5166 9 50       65 if ($err) {
    50          
5167 0         0 carp "ERROR: invalid event definition (must be YMD;YMD or YM;YM)\n"
5168             . " $string\n";
5169 0         0 next;
5170             } elsif ($$d1{'data'}{'def'}[0] ne $$d2{'data'}{'def'}[0]) {
5171 0         0 carp "ERROR: invalid event definition (YMD;YM or YM;YMD not allowed)\n"
5172             . " $string\n";
5173 0         0 next;
5174             }
5175              
5176 9 100       32 if ($$d1{'data'}{'def'}[0] eq '') {
5177             # YM;YM
5178 2         21 $$dmb{'data'}{'events'}{$i++} = { 'type' => 'ym',
5179             'name' => $name,
5180             'beg' => $o1,
5181             'end' => $o2
5182             };
5183             } else {
5184             # YMD;YMD
5185 7         30 $d1->set('time',[0,0,0]);
5186 7         37 $d2->set('time',[23,59,59]);
5187 7         57 $$dmb{'data'}{'events'}{$i++} = { 'type' => 'specified',
5188             'name' => $name,
5189             'beg' => $d1,
5190             'end' => $d2 };
5191             }
5192 9         51 next;
5193             }
5194              
5195             # Date;Date
5196             # Date;Delta
5197              
5198 9         36 $err = $d1->parse($o1);
5199 9 100       46 if (! $err) {
5200              
5201 6         51 my $d2 = $self->new_date();
5202 6         21 $err = $d2->parse($o2,'nodelta');
5203              
5204 6 100       22 if (! $err) {
5205             # Date;Date
5206 2 50       16 if ($$d1{'data'}{'def'}[0] ne $$d2{'data'}{'def'}[0]) {
5207 0         0 carp "ERROR: invalid event definition (year must be absent or\n"
5208             . " included in both dats in Date;Date)\n"
5209             . " $string\n";
5210 0         0 next;
5211             }
5212              
5213 2 50       15 if ($$d1{'data'}{'def'}[0] eq '') {
5214             # Date (no year)
5215 0         0 $$dmb{'data'}{'events'}{$i++} = { 'type' => 'date',
5216             'name' => $name,
5217             'beg' => $o1,
5218             'end' => $o2
5219             };
5220             } else {
5221             # Date (year)
5222 2         21 $$dmb{'data'}{'events'}{$i++} = { 'type' => 'specified',
5223             'name' => $name,
5224             'beg' => $d1,
5225             'end' => $d2
5226             };
5227             }
5228 2         12 next;
5229             }
5230              
5231             # Date;Delta
5232 4         20 my $del = $self->new_delta();
5233 4         13 $err = $del->parse($o2);
5234              
5235 4 50       11 if ($err) {
5236 0         0 carp "ERROR: invalid event definition (must be Date;Date or\n"
5237             . " Date;Delta) $string\n";
5238 0         0 next;
5239             }
5240              
5241 4         20 $del = $del->calc($M1);
5242 4 100       20 if ($$d1{'data'}{'def'}[0] eq '') {
5243             # Date (no year)
5244 2         17 $$dmb{'data'}{'events'}{$i++} = { 'type' => 'date',
5245             'name' => $name,
5246             'beg' => $o1,
5247             'delta' => $del
5248             };
5249             } else {
5250             # Date (year)
5251 2         10 $d2 = $d1->calc($del);
5252 2         24 $$dmb{'data'}{'events'}{$i++} = { 'type' => 'specified',
5253             'name' => $name,
5254             'beg' => $d1,
5255             'end' => $d2
5256             };
5257             }
5258 4         30 next;
5259             }
5260              
5261             # Recur;Delta
5262              
5263 3         20 my $r = $self->new_recur();
5264 3         16 $err = $r->parse($o1);
5265              
5266 3         17 my $del = $self->new_delta();
5267 3 50       14 if (! $err) {
5268 3         16 $err = $del->parse($o2);
5269             }
5270              
5271 3 50       16 if ($err) {
5272 0         0 carp "ERROR: invalid event definition (must be Date;Date, YMD;YMD, "
5273             . " YM;YM, Date;Delta, or Recur;Delta)\n"
5274             . " $string\n";
5275 0         0 next;
5276             }
5277              
5278 3         15 $del = $del->calc($M1);
5279 3         17 my @d = $r->dates();
5280 3 50       13 if (@d) {
5281 0         0 foreach my $d1 (@d) {
5282 0         0 my $d2 = $d1->calc($del);
5283 0         0 $$dmb{'data'}{'events'}{$i++} = { 'type' => 'specified',
5284             'name' => $name,
5285             'beg' => $d1,
5286             'end' => $d2
5287             };
5288             }
5289             } else {
5290 3         41 $$dmb{'data'}{'events'}{$i++} = { 'type' => 'recur',
5291             'name' => $name,
5292             'recur' => $r,
5293             'delta' => $del
5294             };
5295             }
5296              
5297             } else {
5298 0         0 carp "ERROR: invalid event definition\n"
5299             . " $string\n";
5300 0         0 next;
5301             }
5302             }
5303              
5304 3         21 return;
5305             }
5306              
5307             1;
5308             # Local Variables:
5309             # mode: cperl
5310             # indent-tabs-mode: nil
5311             # cperl-indent-level: 3
5312             # cperl-continued-statement-offset: 2
5313             # cperl-continued-brace-offset: 0
5314             # cperl-brace-offset: 0
5315             # cperl-brace-imaginary-offset: 0
5316             # cperl-label-offset: 0
5317             # End: