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-2023 Sullivan Beck. All rights reserved.
3             # This program is free software; you can redistribute it and/or modify it
4             # under the same terms as Perl itself.
5              
6             ########################################################################
7             # Any routine that starts with an underscore (_) is NOT intended for
8             # public use. They are for internal use in the the Date::Manip
9             # modules and are subject to change without warning or notice.
10             #
11             # ABSOLUTELY NO USER SUPPORT IS OFFERED FOR THESE ROUTINES!
12             ########################################################################
13              
14 168     168   54909 use Date::Manip::Obj;
  168         370  
  168         6631  
15             @ISA = ('Date::Manip::Obj');
16              
17             require 5.010000;
18 168     168   847 use warnings;
  168         267  
  168         3736  
19 168     168   683 use strict;
  168         303  
  168         2671  
20 168     168   625 use integer;
  168         238  
  168         673  
21 168     168   80992 use utf8;
  168         1998  
  168         738  
22 168     168   4365 use IO::File;
  168         310  
  168         19042  
23 168     168   879 use Storable qw(dclone);
  168         272  
  168         5130  
24 168     168   725 use Carp;
  168         259  
  168         8257  
25             #use re 'debug';
26              
27 168     168   103252 use Date::Manip::Base;
  168         410  
  168         5886  
28 168     168   83174 use Date::Manip::TZ;
  168         712  
  168         622410  
29              
30             our $VERSION;
31             $VERSION='6.92';
32 168     168   950 END { undef $VERSION; }
33              
34             ########################################################################
35             # BASE METHODS
36             ########################################################################
37              
38             sub is_date {
39 1     1 1 1497 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   147437 my($self) = @_;
47              
48 24318         37901 $$self{'err'} = '';
49              
50 24318         184111 $$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         45216 return;
78             }
79              
80             sub _init_args {
81 11     11   26 my($self) = @_;
82              
83 11         22 my @args = @{ $$self{'args'} };
  11         36  
84 11         82 $self->parse(@args);
85 11         27 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 618757 my($self,$instring,@opts) = @_;
99 4093         9531 $self->_init();
100 4093         5635 my $noupdate = 0;
101              
102 4093 50       8512 if (! $instring) {
103 0         0 $$self{'err'} = '[parse] Empty date string';
104 0         0 return 1;
105             }
106              
107 4093         7352 my %opts = map { $_,1 } @opts;
  253         857  
108              
109 4093         6113 my $dmt = $$self{'tz'};
110 4093         5847 my $dmb = $$dmt{'base'};
111 4093         6190 delete $$self{'data'}{'default_time'};
112              
113 4093         6928 my($done,$y,$m,$d,$h,$mn,$s,$tzstring,$zone,$abb,$off,$dow,$got_time,
114             $default_time,$firsterr);
115              
116             ENCODING:
117 4093         11979 foreach my $string ($dmb->_encoding($instring)) {
118 4226         5981 $got_time = 0;
119 4226         4955 $default_time = 0;
120              
121             # Put parse in a simple loop for an easy exit.
122             PARSE:
123             {
124 4226         5221 my(@tmp,$tmp);
  4226         5675  
125 4226         6738 $$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       9747 if ($self->_parse_rule('remove_trailing_period')) {
136 8         34 $string =~ s/\.\s/ /g;
137 8         17 $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       7447 if ($self->_parse_rule('remove_parens')) {
144 42         90 $string =~ s/\(//g;
145 42         62 $string =~ s/\)//;
146             }
147              
148 4226         6896 my $words = $self->_parse_rule('strip_word');
149 4226 100       7596 if ($words) {
150 42         71 foreach my $w (@$words) {
151 42         247 $string =~ s/(?:^|\s)\Q$w\E(?:\s|$)/ /;
152             }
153             }
154              
155             ###################
156              
157             # Check the standard date format
158              
159 4226         11764 $tmp = $dmb->split('date',$string);
160 4226 100       8780 if (defined($tmp)) {
161 1922         3832 ($y,$m,$d,$h,$mn,$s) = @$tmp;
162 1922         2495 $got_time = 1;
163 1922         4108 last PARSE;
164             }
165              
166             # Parse ISO 8601 dates now (which may have a timezone).
167              
168 2304 100       4690 if (! exists $opts{'noiso8601'}) {
169 2299         6106 ($done,@tmp) = $self->_parse_datetime_iso8601($string,\$noupdate);
170 2299 100       5179 if ($done) {
171 314         739 ($y,$m,$d,$h,$mn,$s,$tzstring,$zone,$abb,$off) = @tmp;
172 314         461 $got_time = 1;
173 314         737 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         4507 $string =~ s/(?<!\d),/ /g;
182 1990         3281 $string =~ s/,(?!\d)/ /g;
183              
184             # Some special full date/time formats ('now', 'epoch')
185              
186 1990 50       4427 if (! exists $opts{'nospecial'}) {
187 1990         5331 ($done,@tmp) = $self->_parse_datetime_other($string,\$noupdate);
188 1990 100       4036 if ($done) {
189 24         92 ($y,$m,$d,$h,$mn,$s,$tzstring,$zone,$abb,$off) = @tmp;
190 24         34 $got_time = 1;
191 24         64 last PARSE;
192             }
193             }
194              
195             # Parse (and remove) the time (and an immediately following timezone).
196              
197 1966         6123 ($got_time,@tmp) = $self->_parse_time('parse',$string,\$noupdate,%opts);
198 1966 100       4090 if ($got_time) {
199 1103         2554 ($string,$h,$mn,$s,$tzstring,$zone,$abb,$off) = @tmp;
200             }
201              
202 1966 100       3721 if (! $string) {
203 10         206 ($y,$m,$d) = $self->_def_date($y,$m,$d,\$noupdate);
204 10         23 last;
205             }
206              
207             # Parse (and remove) the day of week. Also, handle the simple DoW
208             # formats.
209              
210 1956 50       3818 if (! exists $opts{'nodow'}) {
211 1956         4718 ($done,@tmp) = $self->_parse_dow($string,\$noupdate);
212 1956 100       4138 if (@tmp) {
213 597 100       1071 if ($done) {
214 12         17 ($y,$m,$d) = @tmp;
215 12         15 $default_time = 1;
216 12         25 last PARSE;
217             } else {
218 585         1065 ($string,$dow) = @tmp;
219             }
220             }
221             }
222 1944 100       3866 $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         5267 (@tmp) = $self->_parse_date($string,$dow,\$noupdate,%opts);
239 1944 100       3888 if (@tmp) {
240 1650         1954 my $dow2;
241 1650         2926 ($y,$m,$d,$dow2) = @tmp;
242 1650 50 66     4758 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       2761 $dow = $dow2 if ($dow2);
247 1650         1976 $default_time = 1;
248 1650         3074 last PARSE;
249             }
250              
251             # Parse any timezone
252              
253 294 100       589 if (! $tzstring) {
254 281         657 ($string,@tmp) = $self->_parse_tz($string,\$noupdate);
255 281 100       634 ($tzstring,$zone,$abb,$off) = @tmp if (@tmp);
256 281 50       595 last PARSE if (! $string);
257             }
258              
259             # Try the remainder of the string as a date.
260              
261 294 100       533 if ($tzstring) {
262 22         71 (@tmp) = $self->_parse_date($string,$dow,\$noupdate,%opts);
263 22 100       61 if (@tmp) {
264 1         3 ($y,$m,$d,$dow) = @tmp;
265 1         2 $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       640 if (! exists $opts{'nodelta'}) {
289              
290 185         569 ($done,@tmp) =
291             $self->_parse_delta($string,$dow,$got_time,$h,$mn,$s,\$noupdate);
292 185 100       432 if (@tmp) {
293 30         64 ($y,$m,$d,$h,$mn,$s) = @tmp;
294 30         45 $got_time = 1;
295 30         39 $dow = '';
296             }
297 185 100       393 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         356 ($done,@tmp) =
304             $self->_parse_delta($instring,$dow,$got_time,$h,$mn,$s,\$noupdate);
305 149 50       356 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       297 last PARSE if ($done);
312             }
313              
314             # Parse holidays
315              
316 257 50       565 unless (exists $opts{'noholidays'}) {
317 257         640 ($done,@tmp) =
318             $self->_parse_holidays($string,\$noupdate);
319 257 100       492 if (@tmp) {
320 9         15 ($y,$m,$d) = @tmp;
321             }
322 257 100       424 last PARSE if ($done);
323             }
324              
325 248         428 $$self{'err'} = '[parse] Invalid date string';
326 248         390 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       8895 if ($$self{'err'}) {
335 254 100       498 if (! $firsterr) {
336 129         194 $firsterr = $$self{'err'};
337             }
338 254         422 next ENCODING;
339             }
340              
341             # If we didn't get an error, this is the string to use.
342              
343 3972         5460 last ENCODING;
344             }
345              
346 4093 100       8718 if ($$self{'err'}) {
347 121         217 $$self{'err'} = $firsterr;
348 121         374 return 1;
349             }
350              
351             # Make sure that a time is set
352              
353 3972 100       7225 if (! $got_time) {
354 603 100       1048 if ($default_time) {
355 598 100       1953 if (exists $$self{'data'}{'default_time'}) {
    100          
356 8         10 ($h,$mn,$s) = @{ $$self{'data'}{'default_time'} };
  8         15  
357 8         17 delete $$self{'data'}{'default_time'};
358             } elsif ($dmb->_config('defaulttime') eq 'midnight') {
359 574         1060 ($h,$mn,$s) = (0,0,0);
360             } else {
361 16         38 ($h,$mn,$s) = $dmt->_now('time',$noupdate);
362 16         23 $noupdate = 1;
363             }
364 598         754 $got_time = 1;
365             } else {
366 5         18 ($h,$mn,$s) = $self->_def_time(undef,undef,undef,\$noupdate);
367             }
368             }
369              
370 3972         6269 $$self{'data'}{'set'} = 2;
371 3972         10091 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 102 my($self,$string,@opts) = @_;
377 30         47 my %opts = map { $_,1 } @opts;
  0         0  
378 30         38 my $noupdate = 0;
379              
380 30 50       54 if (! $string) {
381 0         0 $$self{'err'} = '[parse_time] Empty time string';
382 0         0 return 1;
383             }
384              
385 30         40 my($y,$m,$d,$h,$mn,$s);
386              
387 30 50       57 if ($$self{'err'}) {
388 0         0 $self->_init();
389             }
390 30 50       56 if ($$self{'data'}{'set'}) {
391 0         0 ($y,$m,$d,$h,$mn,$s) = @{ $$self{'data'}{'date'} };
  0         0  
392             } else {
393 30         33 my $dmt = $$self{'tz'};
394 30         82 ($y,$m,$d,$h,$mn,$s) = $dmt->_now('now',$noupdate);
395 30         40 $noupdate = 1;
396             }
397 30         41 my($tzstring,$zone,$abb,$off);
398              
399 30         67 ($h,$mn,$s,$tzstring,$zone,$abb,$off) =
400             $self->_parse_time('parse_time',$string,\$noupdate,%opts);
401              
402 30 100       69 return 1 if ($$self{'err'});
403              
404 25         40 $$self{'data'}{'set'} = 2;
405 25         54 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 5810 my($self,$string,@opts) = @_;
411 1012         1834 my %opts = map { $_,1 } @opts;
  0         0  
412 1012         1473 my $noupdate = 0;
413              
414 1012 50       2044 if (! $string) {
415 0         0 $$self{'err'} = '[parse_date] Empty date string';
416 0         0 return 1;
417             }
418              
419 1012         1541 my $dmt = $$self{'tz'};
420 1012         1541 my $dmb = $$dmt{'base'};
421 1012         1506 my($y,$m,$d,$h,$mn,$s);
422              
423 1012 100       1906 if ($$self{'err'}) {
424 2         6 $self->_init();
425             }
426 1012 100       2054 if ($$self{'data'}{'set'}) {
427 7         10 ($y,$m,$d,$h,$mn,$s) = @{ $$self{'data'}{'date'} };
  7         18  
428             } else {
429 1005         1895 ($h,$mn,$s) = (0,0,0);
430             }
431              
432             # Put parse in a simple loop for an easy exit.
433 1012         1369 my($done,@tmp,$dow);
434             PARSE:
435             {
436              
437             # Parse ISO 8601 dates now
438              
439 1012 50       1506 unless (exists $opts{'noiso8601'}) {
  1012         2025  
440 1012         2699 ($done,@tmp) = $self->_parse_date_iso8601($string,\$noupdate);
441 1012 100       2317 if ($done) {
442 70         124 ($y,$m,$d) = @tmp;
443 70         107 last PARSE;
444             }
445             }
446              
447 942         3019 (@tmp) = $self->_parse_date($string,undef,\$noupdate,%opts);
448 942 100       2176 if (@tmp) {
449 818         1393 ($y,$m,$d,$dow) = @tmp;
450 818         1147 last PARSE;
451             }
452              
453 124         234 $$self{'err'} = '[parse_date] Invalid date string';
454 124         305 return 1;
455             }
456              
457 888 50       1931 return 1 if ($$self{'err'});
458              
459 888         2392 $y = $dmt->_fix_year($y);
460              
461 888         1536 $$self{'data'}{'set'} = 2;
462 888         2578 return $self->_parse_check('parse_date','',$y,$m,$d,$h,$mn,$s,$dow);
463             }
464              
465             sub _parse_date {
466 2908     2908   6227 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         5229 $string =~ s/,/ /g;
474              
475 2908         4395 my $dmt = $$self{'tz'};
476 2908         4421 my $dmb = $$dmt{'base'};
477             my $ign = (exists $$dmb{'data'}{'rx'}{'other'}{'ignore'} ?
478 2908 100       7264 $$dmb{'data'}{'rx'}{'other'}{'ignore'} :
479             $self->_other_rx('ignore'));
480 2908         16034 $string =~ s/$ign/ /g;
481 2908         14069 my $of = $+{'of'};
482              
483 2908         14530 $string =~ s/\s*$//;
484 2908 50       6399 return () if (! $string);
485              
486 2908         4264 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       3671 unless (exists $opts{'nodow'}) {
  2908         5244  
496 2908 100       5237 if (! defined($dow)) {
497 942         2433 ($done,@tmp) = $self->_parse_dow($string,$noupdate);
498 942 100       2172 if (@tmp) {
499 664 100       1267 if ($done) {
500 6         10 ($y,$m,$d) = @tmp;
501 6         12 last PARSE;
502             } else {
503 658         1298 ($string,$dow) = @tmp;
504             }
505             }
506 936 100       1924 $dow = 0 if (! $dow);
507             }
508             }
509              
510             # Parse common dates
511              
512 2902 50       5190 unless (exists $opts{'nocommon'}) {
513 2902         6562 (@tmp) = $self->_parse_date_common($string,$noupdate);
514 2902 100       5904 if (@tmp) {
515 1573         2770 ($y,$m,$d) = @tmp;
516 1573         2735 last PARSE;
517             }
518             }
519              
520             # Parse less common dates
521              
522 1329 50       2928 unless (exists $opts{'noother'}) {
523 1329         4101 (@tmp) = $self->_parse_date_other($string,$dow,$of,$noupdate);
524 1329 100       2942 if (@tmp) {
525 874         1566 ($y,$m,$d,$dow) = @tmp;
526 874         1614 last PARSE;
527             }
528             }
529              
530             # Parse truncated dates
531              
532 455 100 100     1710 if (! $dow && ! $of) {
533 420         1076 (@tmp) = $self->_parse_date_truncated($string,$noupdate);
534 420 100       867 if (@tmp) {
535 16         29 ($y,$m,$d,$dow) = @tmp;
536 16         34 last PARSE;
537             }
538             }
539              
540 439         1035 return ();
541             }
542              
543 2469         8070 return($y,$m,$d,$dow);
544             }
545              
546             sub parse_format {
547 7     7 1 3271 my($self,$format,$string) = @_;
548 7         28 $self->_init();
549 7         12 my $noupdate = 0;
550              
551 7 50       23 if (! $string) {
552 0         0 $$self{'err'} = '[parse_format] Empty date string';
553 0         0 return 1;
554             }
555              
556 7         15 my $dmt = $$self{'tz'};
557 7         14 my $dmb = $$dmt{'base'};
558              
559 7         28 my($err,$re) = $self->_format_regexp($format);
560 7 50       22 return $err if ($err);
561 7 50       220 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         287 @+{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         38 while (1) {
573             # Get y/m/d/h/mn/s from:
574             # $epochs,$epocho
575              
576 7 50       22 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       28 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       37 if ($mon_name) {
    100          
610 0         0 $m = $$dmb{'data'}{'wordmatch'}{'month_name'}{lc($mon_name)};
611             } elsif ($mon_abb) {
612 2         13 $m = $$dmb{'data'}{'wordmatch'}{'month_abb'}{lc($mon_abb)};
613             }
614              
615 7 50       32 if ($nth) {
616 0         0 $d = $$dmb{'data'}{'wordmatch'}{'nth'}{lc($nth)};
617             }
618              
619 7 50       39 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         32 ($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       71 if (defined($h)) {
642 4         20 ($h,$mn,$s) = $self->_def_time($h,$mn,$s,\$noupdate);
643             }
644              
645 7 100       23 if ($ampm) {
646 2 50       13 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       11 $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       34 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         13 last;
667             }
668              
669 7 100       20 if (! $m) {
670 2         13 ($y,$m,$d) = $dmt->_now('now',$noupdate);
671 2         5 $noupdate = 1;
672             }
673 7 100       22 if (! defined($h)) {
674 3         8 ($h,$mn,$s) = (0,0,0);
675             }
676              
677 7         18 $$self{'data'}{'set'} = 2;
678 7         42 $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       19 if (wantarray) {
683 1         1 my %tmp = %{ dclone(\%+) };
  1         116  
684 1         16 return ($err,%tmp);
685             }
686 6         20 return $err;
687             }
688              
689 0         0 BEGIN {
690 168     168   807 my %y_form = map { $_,1 } qw( Y y s o G L );
  1008         2067  
691 168         423 my %m_form = map { $_,1 } qw( m f b h B j s o W U );
  1680         2618  
692 168         400 my %d_form = map { $_,1 } qw( j d e E s o W U );
  1344         1929  
693 168         375 my %h_form = map { $_,1 } qw( H I k i s o );
  1008         1451  
694 168         332 my %mn_form = map { $_,1 } qw( M s o );
  504         849  
695 168         308 my %s_form = map { $_,1 } qw( S s o );
  504         846  
696              
697 168         296 my %dow_form = map { $_,1 } qw( v a A w );
  672         1046  
698 168         339 my %am_form = map { $_,1 } qw( p s o );
  504         1002  
699 168         316 my %z_form = map { $_,1 } qw( Z z N );
  504         912  
700 168         365 my %mon_form = map { $_,1 } qw( b h B );
  504         862  
701 168         361 my %day_form = map { $_,1 } qw( v a A );
  504         297492  
702              
703             sub _format_regexp {
704 7     7   20 my($self,$format) = @_;
705 7         15 my $dmt = $$self{'tz'};
706 7         13 my $dmb = $$dmt{'base'};
707              
708 7 50       22 if (exists $$dmb{'data'}{'format'}{$format}) {
709 0         0 return @{ $$dmb{'data'}{'format'}{$format} };
  0         0  
710             }
711              
712 7         11 my $re;
713             my $err;
714 7         17 my($y,$m,$d,$h,$mn,$s) = (0,0,0,0,0,0);
715 7         18 my($dow,$ampm,$zone,$G,$W,$L,$U) = (0,0,0,0,0,0,0);
716              
717 7         17 while ($format) {
718 65 50       108 last if ($format eq '%');
719              
720 65 100       178 if ($format =~ s/^([^%]+)//) {
721 30         61 $re .= $1;
722 30         59 next;
723             }
724              
725 35         83 $format =~ s/^%(.)//;
726 35         55 my $f = $1;
727              
728 35 100       68 if (exists $y_form{$f}) {
729 5 50       9 if ($y) {
730 0         0 $err = 'Year specified multiple times';
731 0         0 last;
732             }
733 5         17 $y = 1;
734             }
735              
736 35 100       63 if (exists $m_form{$f}) {
737 5 50       14 if ($m) {
738 0         0 $err = 'Month specified multiple times';
739 0         0 last;
740             }
741 5         9 $m = 1;
742             }
743              
744 35 100       69 if (exists $d_form{$f}) {
745 5 50       11 if ($d) {
746 0         0 $err = 'Day specified multiple times';
747 0         0 last;
748             }
749 5         7 $d = 1;
750             }
751              
752 35 100       63 if (exists $h_form{$f}) {
753 4 50       11 if ($h) {
754 0         0 $err = 'Hour specified multiple times';
755 0         0 last;
756             }
757 4         7 $h = 1;
758             }
759              
760 35 100       59 if (exists $mn_form{$f}) {
761 4 50       11 if ($mn) {
762 0         0 $err = 'Minutes specified multiple times';
763 0         0 last;
764             }
765 4         6 $mn = 1;
766             }
767              
768 35 100       60 if (exists $s_form{$f}) {
769 4 50       12 if ($s) {
770 0         0 $err = 'Seconds specified multiple times';
771 0         0 last;
772             }
773 4         4 $s = 1;
774             }
775              
776 35 50       57 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       58 if (exists $am_form{$f}) {
785 2 50       8 if ($ampm) {
786 0         0 $err = 'AM/PM specified multiple times';
787 0         0 last;
788             }
789 2         3 $ampm = 1;
790             }
791              
792 35 100       53 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         4 $zone = 1;
798             }
799              
800 35 50       101 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     332 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         13 $re .= '(?<y>\d\d\d\d)';
833              
834             } elsif ($f eq 'y') {
835 0         0 $re .= '(?<y>\d\d)';
836              
837             } elsif ($f eq 'm') {
838 3         10 $re .= '(?<m>\d\d)';
839              
840             } elsif ($f eq 'f') {
841 0         0 $re .= '(?:(?<m>\d\d)| ?(?<m>\d))';
842              
843             } elsif (exists $mon_form{$f}) {
844 2         9 my $abb = $$dmb{'data'}{'rx'}{'month_abb'}[0];
845 2         5 my $nam = $$dmb{'data'}{'rx'}{'month_name'}[0];
846 2         18 $re .= "(?:(?<mon_name>$nam)|(?<mon_abb>$abb))";
847              
848             } elsif ($f eq 'j') {
849 0         0 $re .= '(?<doy>\d\d\d)';
850              
851             } elsif ($f eq 'd') {
852 5         12 $re .= '(?<d>\d\d)';
853              
854             } elsif ($f eq 'e') {
855 0         0 $re .= '(?:(?<d>\d\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 .= "(?:(?<dow_name>$name)|(?<dow_abb>$abb)|(?<dow_char>$char))";
862              
863             } elsif ($f eq 'w') {
864 0         0 $re .= '(?<dow_num>[1-7])';
865              
866             } elsif ($f eq 'E') {
867 0         0 my $nth = $$dmb{'data'}{'rx'}{'nth'}[0];
868 0         0 $re .= "(?<nth>$nth)"
869              
870             } elsif ($f eq 'H' || $f eq 'I') {
871 4         9 $re .= '(?<h>\d\d)';
872              
873             } elsif ($f eq 'k' || $f eq 'i') {
874 0         0 $re .= '(?:(?<h>\d\d)| ?(?<h>\d))';
875              
876             } elsif ($f eq 'p') {
877 2         11 my $ampm = $$dmb{data}{rx}{ampm}[0];
878 2         7 $re .= "(?<ampm>$ampm)";
879              
880             } elsif ($f eq 'M') {
881 4         7 $re .= '(?<mn>\d\d)';
882              
883             } elsif ($f eq 'S') {
884 4         8 $re .= '(?<s>\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 .= '(?<epochs>\d+)';
891              
892             } elsif ($f eq 'o') {
893 0         0 $re .= '(?<epocho>\d+)';
894              
895             } elsif ($f eq 'G') {
896 0         0 $re .= '(?<g>\d\d\d\d)';
897              
898             } elsif ($f eq 'W') {
899 0         0 $re .= '(?<w>\d\d)';
900              
901             } elsif ($f eq 'L') {
902 0         0 $re .= '(?<l>\d\d\d\d)';
903              
904             } elsif ($f eq 'U') {
905 0         0 $re .= '(?<u>\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         9 $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         10 $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     122 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       21 if ($err) {
983 0         0 $$dmb{'data'}{'format'}{$format} = [$err];
984 0         0 return ($err);
985             }
986              
987 7         6162 $$dmb{'data'}{'format'}{$format} = [0, qr/$re/i];
988 7         30 return @{ $$dmb{'data'}{'format'}{$format} };
  7         31  
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   17712 my($self,$rule) = @_;
996              
997 12678         14571 my $dmt = $$self{'tz'};
998 12678         14149 my $dmb = $$dmt{'base'};
999              
1000 12678 100 66     37149 if (exists $$dmb{'data'}{'lang'}{'_special_rules'} &&
1001             exists $$dmb{'data'}{'lang'}{'_special_rules'}{$rule}) {
1002 92         193 return $$dmb{'data'}{'lang'}{'_special_rules'}{$rule};
1003             }
1004 12586         21573 return 0;
1005             }
1006              
1007             ########################################################################
1008             # DATE FORMATS
1009             ########################################################################
1010              
1011             sub _parse_check {
1012 4892     4892   13216 my($self,$caller,$instring,
1013             $y,$m,$d,$h,$mn,$s,$dow,$tzstring,$zone,$abb,$off) = @_;
1014 4892         7212 my $dmt = $$self{'tz'};
1015 4892         6610 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       8710 if ($dow) {
1021 1105         3549 my $tmp = $dmb->day_of_week([$y,$m,$d]);
1022 1105 100       2838 if ($tmp != $dow) {
1023 4         15 $$self{'err'} = "[$caller] Day of week invalid";
1024 4         14 return 1;
1025             }
1026             }
1027              
1028             # Handle 24:00:00 times.
1029              
1030 4888 100       8918 if ($h == 24) {
1031 5         16 ($h,$mn,$s) = (0,0,0);
1032 5         10 ($y,$m,$d) = @{ $dmb->calc_date_days([$y,$m,$d],1) };
  5         24  
1033             }
1034              
1035 4888 100       16236 if (! $dmb->check([$y,$m,$d,$h,$mn,$s])) {
1036 8         27 $$self{'err'} = "[$caller] Invalid date";
1037 8         46 return 1;
1038             }
1039 4880         13364 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         6218 my $zonename;
1049 4880 100       8894 my $abbrev = (defined $abb ? lc($abb) : '');
1050 4880 100       8137 my $offset = (defined $off ? $dmb->_delta_convert('offset',$off) : '');
1051 4880         6235 my @tmp;
1052              
1053 4880 100 100     15613 if (defined($zone)) {
    100          
1054 8         25 $zonename = $dmt->_zone($zone);
1055 8 50       21 if ($zonename) {
1056 8         25 @tmp = $self->__parse_check($date,$zonename,$off,$abb);
1057             }
1058              
1059             } elsif (defined($abb) || defined($off)) {
1060              
1061 144         644 $zonename = $dmt->__zone($date,$offset,'',$abbrev,'');
1062 144 100       505 if ($zonename) {
1063 137         722 @tmp = $self->__parse_check($date,$zonename,$off,$abb);
1064             }
1065              
1066 144 100 100     582 if (! @tmp && defined($abb)) {
1067 4         16 my $tmp = $dmt->_zone($abb);
1068 4 50       12 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         12290 $zonename = $dmt->_now('tz');
1076 4728 50       9102 if ($zonename) {
1077 4728         10433 @tmp = $self->__parse_check($date,$zonename,$off,$abb);
1078             }
1079             }
1080              
1081 4880 100       9022 if (! $zonename) {
1082 7 50       15 if (defined($zone)) {
1083 0         0 $$self{'err'} = "[$caller] Unable to determine timezone: $zone";
1084             } else {
1085 7         22 $$self{'err'} = "[$caller] Unable to determine timezone";
1086             }
1087 7         31 return 1;
1088             }
1089              
1090 4873 100       8564 if (! @tmp) {
1091 1         5 $$self{'err'} = "[$caller] Invalid date in timezone";
1092 1         5 return 1;
1093             }
1094              
1095             # Store the date
1096              
1097 4872         7911 my($a,$o,$isdst) = @tmp;
1098              
1099 4872         12993 $self->set('zdate',$zonename,$date,$isdst);
1100 4872 50       9850 return 1 if ($$self{'err'});
1101              
1102 4872         7871 $$self{'data'}{'in'} = $instring;
1103 4872 100       8358 $$self{'data'}{'zin'} = $zone if (defined($zone));
1104              
1105 4872         18071 return 0;
1106             }
1107              
1108             sub __parse_check {
1109 4873     4873   9059 my($self,$date,$zonename,$off,$abb) = @_;
1110 4873         6821 my $dmt = $$self{'tz'};
1111 4873         6388 my $dmb = $$dmt{'base'};
1112              
1113 4873 100       8474 if (defined ($off)) {
1114 49         250 $off = $dmb->split('offset',$off);
1115             }
1116              
1117 4873         8311 foreach my $isdst (0,1) {
1118 4877         13085 my $per = $dmt->date_period($date,$zonename,1,$isdst);
1119 4877 100       9626 next if (! $per);
1120 4875         7054 my $a = $$per[4];
1121 4875         6193 my $o = $$per[3];
1122              
1123             # If $abb is defined, it must match.
1124 4875 100 100     10315 next if (defined $abb && lc($a) ne lc($abb));
1125              
1126             # If $off is defined, it must match.
1127 4873 100       7889 if (defined ($off)) {
1128 50 50 66     471 next if ($$off[0] != $$o[0] ||
      66        
1129             $$off[1] != $$o[1] ||
1130             $$off[2] != $$o[2]);
1131             }
1132              
1133 4872         12385 return ($a,$o,$isdst);
1134             }
1135 1         2 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   5953 my($self,$rx) = @_;
1155 3668         7892 my $dmt = $$self{'tz'};
1156 3668         6273 my $dmb = $$dmt{'base'};
1157              
1158             return $$dmb{'data'}{'rx'}{'iso'}{$rx}
1159 3668 100       12320 if (exists $$dmb{'data'}{'rx'}{'iso'}{$rx});
1160              
1161 265 100 66     2012 if ($rx eq 'cdate' || $rx eq 'tdate') {
    100 66        
    100          
    100          
    50          
1162              
1163 86         210 my $y4 = '(?<y>\d\d\d\d)';
1164 86         186 my $y2 = '(?<y>\d\d)';
1165 86         164 my $m = '(?<m>0[1-9]|1[0-2])';
1166 86         161 my $d = '(?<d>0[1-9]|[12][0-9]|3[01])';
1167 86         177 my $doy = '(?<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         157 my $w = '(?<w>0[1-9]|[1-4][0-9]|5[0-3])';
1169 86         171 my $dow = '(?<dow>[1-7])';
1170 86         162 my $yod = '(?<yod>\d)';
1171 86         178 my $cc = '(?<c>\d\d)';
1172              
1173 86         2172 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         614 my $cdaterx = join('|',@cdaterx);
1201 86         24317 $cdaterx = qr/(?:$cdaterx)/i;
1202              
1203 86         1584 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         341 my $tdaterx = join('|',@tdaterx);
1219 86         6188 $tdaterx = qr/(?:$tdaterx)/i;
1220              
1221 86         552 $$dmb{'data'}{'rx'}{'iso'}{'cdate'} = $cdaterx;
1222 86         453 $$dmb{'data'}{'rx'}{'iso'}{'tdate'} = $tdaterx;
1223              
1224             } elsif ($rx eq 'ctime' || $rx eq 'ttime') {
1225              
1226 75         155 my $hh = '(?<h>[0-1][0-9]|2[0-3])';
1227 75         149 my $mn = '(?<mn>[0-5][0-9])';
1228 75         157 my $ss = '(?<s>[0-5][0-9])';
1229 75         133 my $h24a = '(?<h24>24(?::00){0,2})';
1230 75         127 my $h24b = '(?<h24>24(?:00){0,2})';
1231 75         134 my $h = '(?<h>[0-9])';
1232              
1233 75         125 my $fh = '(?:[\.,](?<fh>\d*))'; # fractional hours (keep)
1234 75         130 my $fm = '(?:[\.,](?<fm>\d*))'; # fractional seconds (keep)
1235 75         147 my $fs = '(?:[\.,]\d*)'; # fractional hours (discard)
1236              
1237 75         496 my $zrx = $dmt->_zrx('zrx');
1238              
1239 75         2747 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         343 my $ctimerx = join('|',@ctimerx);
1255 150         220514 $ctimerx = qr/(?:$ctimerx)(?:\s*$zrx)?/;
1256              
1257 150         1172 my @ttimerx =
1258             (
1259             "${hh}", # HH
1260             "\\-${mn}", # -MN
1261             );
1262 150         215 my $ttimerx = join('|',@ttimerx);
1263 150         1774 $ttimerx = qr/(?:$ttimerx)/;
1264              
1265 150         462 $$dmb{'data'}{'rx'}{'iso'}{'ctime'} = $ctimerx;
1266 150         344 $$dmb{'data'}{'rx'}{'iso'}{'ttime'} = $ttimerx;
1267              
1268             } elsif ($rx eq 'date') {
1269              
1270 29         307 my $cdaterx = $self->_iso8601_rx('cdate');
1271 29         107 my $tdaterx = $self->_iso8601_rx('tdate');
1272 29         10383 $$dmb{'data'}{'rx'}{'iso'}{'date'} = qr/(?:$cdaterx|$tdaterx)/;
1273              
1274             } elsif ($rx eq 'time') {
1275              
1276 1         19 my $ctimerx = $self->_iso8601_rx('ctime');
1277 1         7 my $ttimerx = $self->_iso8601_rx('ttime');
1278 1         2858 $$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         959 my $cdaterx = $self->_iso8601_rx('cdate');
1296 74         278 my $tdaterx = $self->_iso8601_rx('tdate');
1297 74         245 my $ctimerx = $self->_iso8601_rx('ctime');
1298 74         540 my $ttimerx = $self->_iso8601_rx('ttime');
1299              
1300 74         309 my $sep = qr/(?:T|\-|\s*)/i;
1301              
1302 74         471467 my $daterx = qr/^\s*(?: $cdaterx(?:$sep(?:$ctimerx|$ttimerx))? |
1303             $tdaterx |
1304             $ctimerx |
1305             $ttimerx
1306             )\s*$/x;
1307              
1308 74         2875 $$dmb{'data'}{'rx'}{'iso'}{'fulldate'} = $daterx;
1309             }
1310              
1311 340         1358 return $$dmb{'data'}{'rx'}{'iso'}{$rx};
1312             }
1313              
1314             sub _parse_datetime_iso8601 {
1315 2299     2299   4131 my($self,$string,$noupdate) = @_;
1316 2299         3406 my $dmt = $$self{'tz'};
1317 2299         3127 my $dmb = $$dmt{'base'};
1318 2299         4792 my $daterx = $self->_iso8601_rx('fulldate');
1319              
1320 2299         6110 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       39684 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         8242 @+{qw(y m d h mn s doy dow yod c w fh fm h24 tzstring zone abb off)};
1327              
1328 314 100 100     2279 if (defined $w || defined $dow) {
    100          
1329 39         95 ($y,$m,$d) = $self->_def_date_dow($y,$w,$dow,$noupdate);
1330             } elsif (defined $doy) {
1331 16         47 ($y,$m,$d) = $self->_def_date_doy($y,$doy,$noupdate);
1332             } else {
1333 259 50       646 $y = $c . '00' if (defined $c);
1334 259         814 ($y,$m,$d) = $self->_def_date($y,$m,$d,$noupdate);
1335             }
1336              
1337 314         971 ($h,$mn,$s) = $self->_time($h,$mn,$s,$fh,$fm,$h24,undef,$noupdate);
1338             } else {
1339 1985         5859 return (0);
1340             }
1341              
1342 314         1177 return (1,$y,$m,$d,$h,$mn,$s,$tzstring,$zone,$abb,$off);
1343             }
1344              
1345             sub _parse_date_iso8601 {
1346 1012     1012   1875 my($self,$string,$noupdate) = @_;
1347 1012         1544 my $dmt = $$self{'tz'};
1348 1012         1394 my $dmb = $$dmt{'base'};
1349 1012         2286 my $daterx = $self->_iso8601_rx('date');
1350              
1351 1012         2514 my($y,$m,$d);
1352 1012         0 my($doy,$dow,$yod,$c,$w);
1353              
1354 1012 100       22504 if ($string =~ /^$daterx$/) {
1355             ($y,$m,$d,$doy,$dow,$yod,$c,$w) =
1356 70         951 @+{qw(y m d doy dow yod c w)};
1357              
1358 70 100 100     345 if (defined $w || defined $dow) {
    100          
1359 30         64 ($y,$m,$d) = $self->_def_date_dow($y,$w,$dow,$noupdate);
1360             } elsif (defined $doy) {
1361 7         18 ($y,$m,$d) = $self->_def_date_doy($y,$doy,$noupdate);
1362             } else {
1363 33 50       64 $y = $c . '00' if (defined $c);
1364 33         79 ($y,$m,$d) = $self->_def_date($y,$m,$d,$noupdate);
1365             }
1366             } else {
1367 942         3369 return (0);
1368             }
1369              
1370 70         261 return (1,$y,$m,$d);
1371             }
1372              
1373             # Handle all of the time fields.
1374             #
1375 168     168   1429 no integer;
  168         385  
  168         1290  
1376             sub _time {
1377 1442     1442   3505 my($self,$h,$mn,$s,$fh,$fm,$h24,$ampm,$noupdate) = @_;
1378              
1379 1442 100 66     3258 if (defined($ampm) && $ampm) {
1380 76         139 my $dmt = $$self{'tz'};
1381 76         119 my $dmb = $$dmt{'base'};
1382 76 100       315 if ($$dmb{'data'}{'wordmatch'}{'ampm'}{lc($ampm)} == 2) {
1383             # pm times
1384 70 50       226 $h+=12 unless ($h==12);
1385             } else {
1386             # am times
1387 6 100       22 $h=0 if ($h==12);
1388             }
1389             }
1390              
1391 1442 100 66     5513 if (defined $h24) {
    100 66        
    100          
1392 4         13 return(24,0,0);
1393             } elsif (defined $fh && $fh ne "") {
1394 12         25 $fh = "0.$fh";
1395 12         41 $s = int($fh * 3600);
1396 12         23 $mn = int($s/60);
1397 12         17 $s -= $mn*60;
1398             } elsif (defined $fm && $fm ne "") {
1399 8         16 $fm = "0.$fm";
1400 8         162 $s = int($fm*60);
1401             }
1402 1438         2856 ($h,$mn,$s) = $self->_def_time($h,$mn,$s,$noupdate);
1403 1438         3362 return($h,$mn,$s);
1404             }
1405 168     168   33260 use integer;
  168         362  
  168         761  
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   1046 my($self,$rx) = @_;
1412 488         759 my $dmt = $$self{'tz'};
1413 488         723 my $dmb = $$dmt{'base'};
1414 488 50       1037 $rx = '_' if (! defined $rx);
1415              
1416 488 100       2579 if ($rx eq 'time') {
    100          
    100          
    100          
    100          
    100          
    100          
    50          
1417              
1418 60         126 my $h24 = '(?<h>2[0-3]|1[0-9]|0?[0-9])'; # 0-23 00-23
1419 60         139 my $h12 = '(?<h>1[0-2]|0?[1-9])'; # 1-12 01-12
1420 60         129 my $mn = '(?<mn>[0-5][0-9])'; # 00-59
1421 60         136 my $ss = '(?<s>[0-5][0-9])'; # 00-59
1422              
1423             # how to express fractions
1424              
1425 60         134 my($f1,$f2,$sepfr);
1426 60 100 66     477 if (exists $$dmb{'data'}{'rx'}{'sepfr'} &&
1427             $$dmb{'data'}{'rx'}{'sepfr'}) {
1428 3         8 $sepfr = $$dmb{'data'}{'rx'}{'sepfr'};
1429             } else {
1430 57         138 $sepfr = '';
1431             }
1432              
1433 60 100       182 if ($sepfr) {
1434 3         12 $f1 = "(?:[.,]|$sepfr)";
1435 3         10 $f2 = "(?:[.,:]|$sepfr)";
1436             } else {
1437 57         132 $f1 = "[.,]";
1438 57         113 $f2 = "[.,:]";
1439             }
1440 60         194 my $fh = "(?:$f1(?<fh>\\d*))"; # fractional hours (keep)
1441 60         181 my $fm = "(?:$f1(?<fm>\\d*))"; # fractional minutes (keep)
1442 60         154 my $fs = "(?:$f2\\d*)"; # fractional seconds
1443              
1444             # AM/PM
1445              
1446 60         106 my($ampm);
1447 60 50       241 if (exists $$dmb{'data'}{'rx'}{'ampm'}) {
1448 60         248 $ampm = "(?:\\s*(?<ampm>$$dmb{data}{rx}{ampm}[0]))";
1449             }
1450              
1451             # H:MN and MN:S separators
1452              
1453 60         175 my @hm = ("\Q:\E");
1454 60         159 my @ms = ("\Q:\E");
1455 60 100       430 if ($dmb->_config('periodtimesep')) {
1456 1         3 push(@hm,"\Q.\E");
1457 1         2 push(@ms,"\Q.\E");
1458             }
1459 60 50 66     533 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         19 push(@hm,@{ $$dmb{'data'}{'rx'}{'sephm'} });
  8         29  
1464 8         16 push(@ms,@{ $$dmb{'data'}{'rx'}{'sepms'} });
  8         28  
1465             }
1466              
1467             # How to express the time
1468             # matches = (H, FH, MN, FMN, S, AM, TZSTRING, ZONE, ABB, OFF, ABB)
1469              
1470 60         126 my @timerx;
1471              
1472 60         327 for (my $i=0; $i<=$#hm; $i++) {
1473 70         159 my $hm = $hm[$i];
1474 70         141 my $ms = $ms[$i];
1475 70 50       585 push(@timerx,
1476             "${h12}$hm${mn}$ms${ss}${fs}?${ampm}?", # H12:MN:SS[,S+] [AM]
1477             ) if ($ampm);
1478              
1479 70         457 push(@timerx,
1480             "${h24}$hm${mn}$ms${ss}${fs}?", # H24:MN:SS[,S+]
1481             "(?<h>24)$hm(?<mn>00)$ms(?<s>00)", # 24:00:00
1482             );
1483             }
1484 60         274 for (my $i=0; $i<=$#hm; $i++) {
1485 70         151 my $hm = $hm[$i];
1486 70         138 my $ms = $ms[$i];
1487 70 50       544 push(@timerx,
1488             "${h12}$hm${mn}${fm}${ampm}?", # H12:MN,M+ [AM]
1489             ) if ($ampm);
1490 70         294 push(@timerx,
1491             "${h24}$hm${mn}${fm}", # H24:MN,M+
1492             );
1493             }
1494 60         517 for (my $i=0; $i<=$#hm; $i++) {
1495 70         156 my $hm = $hm[$i];
1496 70         144 my $ms = $ms[$i];
1497 70 50       332 push(@timerx,
1498             "${h12}$hm${mn}${ampm}?", # H12:MN [AM]
1499             ) if ($ampm);
1500 70         332 push(@timerx,
1501             "${h24}$hm${mn}", # H24:MN
1502             "(?<h>24)$hm(?<mn>00)", # 24:00
1503             );
1504             }
1505              
1506 60 50       513 push(@timerx,
1507             "${h12}${fh}${ampm}", # H12,H+ AM
1508             "${h12}${ampm}", # H12 AM
1509             ) if ($ampm);
1510 60         176 push(@timerx,
1511             "${h24}${fh}", # H24,H+
1512             );
1513              
1514 60         292 my $timerx = join('|',@timerx);
1515 60         282 my $zrx = $dmt->_zrx('zrx');
1516 60         204 my $at = $$dmb{'data'}{'rx'}{'at'};
1517 60         1220 my $atrx = qr/(?:^|\s+)(?:$at)\s+/;
1518 60         193090 $timerx = qr/(?:$atrx|^|\s+)(?:$timerx)(?:\s*$zrx)?(?:\s+|$)/i;
1519              
1520 60         1451 $$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 <m> and <d> with a regular expression to
1527             # match 1-12 since the DateFormat config may reverse the two.
1528 71         149 my $y4 = '(?<y>\d\d\d\d)';
1529 71         147 my $y2 = '(?<y>\d\d)';
1530 71         132 my $m = '(?<m>\d\d?)';
1531 71         132 my $d = '(?<d>\d\d?)';
1532 71         132 my $sep = '(?<sep>[\s\.\/\-])';
1533              
1534 71         474 my @daterx =
1535             (
1536             "${m}${sep}${d}\\k<sep>$y4", # M/D/YYYY
1537             "${m}${sep}${d}\\k<sep>$y2", # M/D/YY
1538             "${m}${sep}${d}", # M/D
1539             );
1540 71         258 my $daterx = join('|',@daterx);
1541              
1542 71         4416 $daterx = qr/^\s*(?:$daterx)\s*$/;
1543 71         405 $$dmb{'data'}{'rx'}{'other'}{$rx} = $daterx;
1544              
1545             } elsif ($rx eq 'common_2') {
1546              
1547 71         219 my $abb = $$dmb{'data'}{'rx'}{'month_abb'}[0];
1548 71         224 my $nam = $$dmb{'data'}{'rx'}{'month_name'}[0];
1549              
1550 71         175 my $y4 = '(?<y>\d\d\d\d)';
1551 71         147 my $y2 = '(?<y>\d\d)';
1552 71         133 my $m = '(?<m>\d\d?)';
1553 71         132 my $d = '(?<d>\d\d?)';
1554 71         139 my $dd = '(?<d>\d\d)';
1555 71         284 my $mmm = "(?:(?<mmm>$abb)|(?<month>$nam))";
1556 71         145 my $sep = '(?<sep>[\s\.\/\-])';
1557              
1558 71         323 my $format_mmmyyyy = $dmb->_config('format_mmmyyyy');
1559              
1560 71         175 my @daterx = ();
1561 71         424 push(@daterx,
1562             "${y4}${sep}${m}\\k<sep>$d", # YYYY/M/D
1563             "${mmm}\\s*${dd}\\s*${y4}", # mmmDDYYYY
1564             );
1565 71 100       363 push(@daterx,
1566             "${mmm}\\s*${dd}\\s*${y2}", # mmmDDYY
1567             ) if (! $format_mmmyyyy);
1568 71         2280 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<sep>${y4}", # mmm/D/YYYY
1576             "${mmm}${sep}${d}\\k<sep>${y2}", # mmm/D/YY
1577             "${mmm}${sep}${d}", # mmm/D
1578             "${d}${sep}${mmm}\\k<sep>${y4}", # D/mmm/YYYY
1579             "${d}${sep}${mmm}\\k<sep>${y2}", # D/mmm/YY
1580             "${d}${sep}${mmm}", # D/mmm
1581             "${y4}${sep}${mmm}\\k<sep>${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         862 my $daterx = join('|',@daterx);
1596              
1597 71         119768 $daterx = qr/^\s*(?:$daterx)\s*$/i;
1598 71         2183 $$dmb{'data'}{'rx'}{'other'}{$rx} = $daterx;
1599              
1600             } elsif ($rx eq 'truncated') {
1601              
1602 35         116 my $abb = $$dmb{'data'}{'rx'}{'month_abb'}[0];
1603 35         103 my $nam = $$dmb{'data'}{'rx'}{'month_name'}[0];
1604              
1605 35         79 my $y4 = '(?<y>\d\d\d\d)';
1606 35         171 my $mmm = "(?:(?<mmm>$abb)|(?<month>$nam))";
1607 35         81 my $sep = '(?<sep>[\s\.\/\-])';
1608              
1609 35         204 my $format_mmmyyyy = $dmb->_config('format_mmmyyyy');
1610              
1611 35         92 my @daterx = ();
1612 35 100       156 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       137 if (@daterx) {
1621 4         17 my $daterx = join('|',@daterx);
1622 4         1228 $daterx = qr/^\s*(?:$daterx)\s*$/i;
1623 4         49 $$dmb{'data'}{'rx'}{'other'}{$rx} = $daterx;
1624             } else {
1625 31         119 $$dmb{'data'}{'rx'}{'other'}{$rx} = '';
1626             }
1627              
1628             } elsif ($rx eq 'dow') {
1629              
1630 71         272 my $day_abb = $$dmb{'data'}{'rx'}{'day_abb'}[0];
1631 71         242 my $day_name = $$dmb{'data'}{'rx'}{'day_name'}[0];
1632              
1633 71         235 my $on = $$dmb{'data'}{'rx'}{'on'};
1634 71         1454 my $onrx = qr/(?:^|\s+)(?:$on)\s+/;
1635 71     1   7841 my $dowrx = qr/(?:$onrx|^|\s+)(?<dow>$day_name|$day_abb)($|\s+)/i;
  1         10  
  1         1  
  1         12  
1636              
1637 71         22871 $$dmb{'data'}{'rx'}{'other'}{$rx} = $dowrx;
1638              
1639             } elsif ($rx eq 'ignore') {
1640              
1641 71         222 my $of = $$dmb{'data'}{'rx'}{'of'};
1642              
1643 71         1719 my $ignrx = qr/(?:^|\s+)(?<of>$of)(\s+|$)/;
1644 71         332 $$dmb{'data'}{'rx'}{'other'}{$rx} = $ignrx;
1645              
1646             } elsif ($rx eq 'miscdatetime') {
1647              
1648 63         258 my $special = $$dmb{'data'}{'rx'}{'offset_time'}[0];
1649              
1650 63         276 $special = "(?<special>$special)";
1651 63         148 my $secs = "(?<epoch>[-+]?\\d+)";
1652 63         222 my $abb = $$dmb{'data'}{'rx'}{'month_abb'}[0];
1653 63         198 my $mmm = "(?<mmm>$abb)";
1654 63         164 my $y4 = '(?<y>\d\d\d\d)';
1655 63         124 my $dd = '(?<d>\d\d)';
1656 63         149 my $h24 = '(?<h>2[0-3]|[01][0-9])'; # 00-23
1657 63         125 my $mn = '(?<mn>[0-5][0-9])'; # 00-59
1658 63         122 my $ss = '(?<s>[0-5][0-9])'; # 00-59
1659 63         391 my $offrx = $dmt->_zrx('offrx');
1660 63         237 my $zrx = $dmt->_zrx('zrx');
1661              
1662 63         1528 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         3130 my $daterx = join('|',@daterx);
1674              
1675 63         373428 $daterx = qr/^\s*(?:$daterx)\s*$/i;
1676 63         1677 $$dmb{'data'}{'rx'}{'other'}{$rx} = $daterx;
1677              
1678             } elsif ($rx eq 'misc') {
1679              
1680 46         163 my $abb = $$dmb{'data'}{'rx'}{'month_abb'}[0];
1681 46         122 my $nam = $$dmb{'data'}{'rx'}{'month_name'}[0];
1682 46         145 my $next = $$dmb{'data'}{'rx'}{'nextprev'}[0];
1683 46         127 my $last = $$dmb{'data'}{'rx'}{'last'};
1684 46         136 my $yf = $$dmb{data}{rx}{fields}[1];
1685 46         113 my $mf = $$dmb{data}{rx}{fields}[2];
1686 46         117 my $wf = $$dmb{data}{rx}{fields}[3];
1687 46         118 my $df = $$dmb{data}{rx}{fields}[4];
1688 46         137 my $nth = $$dmb{'data'}{'rx'}{'nth'}[0];
1689 46         139 my $nth_wom = $$dmb{'data'}{'rx'}{'nth_wom'}[0];
1690 46         132 my $special = $$dmb{'data'}{'rx'}{'offset_date'}[0];
1691              
1692 46         102 my $y = '(?:(?<y>\d\d\d\d)|(?<y>\d\d))';
1693 46         204 my $mmm = "(?:(?<mmm>$abb)|(?<month>$nam))";
1694 46         144 $next = "(?<next>$next)";
1695 46         128 $last = "(?<last>$last)";
1696 46         114 $yf = "(?<field_y>$yf)";
1697 46         121 $mf = "(?<field_m>$mf)";
1698 46         121 $wf = "(?<field_w>$wf)";
1699 46         116 $df = "(?<field_d>$df)";
1700 46         174 my $fld = "(?:$yf|$mf|$wf)";
1701 46         216 $nth = "(?<nth>$nth)";
1702 46         132 $nth_wom = "(?<nth>$nth_wom)";
1703 46         159 $special = "(?<special>$special)";
1704              
1705 46         1806 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+(?<n>\\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         1791 my $daterx = join('|',@daterx);
1739              
1740 46         247644 $daterx = qr/^\s*(?:$daterx)\s*$/i;
1741 46         2688 $$dmb{'data'}{'rx'}{'other'}{$rx} = $daterx;
1742              
1743             }
1744              
1745 488         1684 return $$dmb{'data'}{'rx'}{'other'}{$rx};
1746             }
1747              
1748             sub _parse_time {
1749 1996     1996   4573 my($self,$caller,$string,$noupdate,%opts) = @_;
1750 1996         2976 my $dmt = $$self{'tz'};
1751 1996         2804 my $dmb = $$dmt{'base'};
1752              
1753 1996         3312 my($timerx,$h,$mn,$s,$fh,$fm,$h24,$ampm,$tzstring,$zone,$abb,$off);
1754 1996         2458 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       4069 if ($caller eq 'parse_time') {
1765             $timerx = (exists $$dmb{'data'}{'rx'}{'iso'}{'time'} ?
1766 30 100       77 $$dmb{'data'}{'rx'}{'iso'}{'time'} :
1767             $self->_iso8601_rx('time'));
1768              
1769 30 50       58 if (! exists $opts{'noiso8601'}) {
1770 30 100       3356 if ($string =~ s/^\s*$timerx\s*$//) {
1771             ($h,$fh,$mn,$fm,$s,$ampm,$tzstring,$zone,$abb,$off) =
1772 14         226 @+{qw(h fh mn fm s ampm tzstring zone abb off)};
1773              
1774 14         66 ($h,$mn,$s) = $self->_def_time($h,$mn,$s,$noupdate);
1775 14 0 33     38 $h24 = 1 if ($h == 24 && $mn == 0 && $s == 0);
      33        
1776 14         44 $string =~ s/\s*$//;
1777 14         26 $got_time = 1;
1778             }
1779             }
1780             }
1781              
1782             # Make time substitutions (i.e. noon => 12:00:00)
1783              
1784 1996 50 66     8621 if (! $got_time &&
1785             ! exists $opts{'noother'}) {
1786 1982         2420 my @rx = @{ $$dmb{'data'}{'rx'}{'times'} };
  1982         4619  
1787 1982         2845 shift(@rx);
1788 1982         3298 foreach my $rx (@rx) {
1789 4053 100       21823 if ($string =~ $rx) {
1790 179         823 my $repl = $$dmb{'data'}{'wordmatch'}{'times'}{lc($1)};
1791 179         1702 $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       3767 if (! $got_time) {
1799             $timerx = (exists $$dmb{'data'}{'rx'}{'other'}{'time'} ?
1800 1982 100       5042 $$dmb{'data'}{'rx'}{'other'}{'time'} :
1801             $self->_other_rx('time'));
1802              
1803 1982 100       38692 if ($string =~ s/$timerx/ /) {
1804             ($h,$fh,$mn,$fm,$s,$ampm,$tzstring,$zone,$abb,$off) =
1805 1119         18320 @+{qw(h fh mn fm s ampm tzstring zone abb off)};
1806              
1807 1119         5379 ($h,$mn,$s) = $self->_def_time($h,$mn,$s,$noupdate);
1808 1119 50 66     3365 $h24 = 1 if ($h == 24 && $mn == 0 && $s == 0);
      66        
1809 1119         6302 $string =~ s/\s*$//;
1810 1119         2004 $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       4985 if ($caller eq 'parse') {
1818 1966 100       3510 if ($got_time) {
1819 1103         2760 ($h,$mn,$s) = $self->_time($h,$mn,$s,$fh,$fm,$h24,$ampm,$noupdate);
1820 1103         4961 return ($got_time,$string,$h,$mn,$s,$tzstring,$zone,$abb,$off);
1821             } else {
1822 863         2265 return (0);
1823             }
1824             }
1825              
1826             # If we called this from $date->parse_time()
1827              
1828 30 100 66     88 if (! $got_time || $string) {
1829 5         13 $$self{'err'} = "[$caller] Invalid time string";
1830 5         14 return ();
1831             }
1832              
1833 25         57 ($h,$mn,$s) = $self->_time($h,$mn,$s,$fh,$fm,$h24,$ampm,$noupdate);
1834 25         83 return ($h,$mn,$s,$tzstring,$zone,$abb,$off);
1835             }
1836              
1837             # Parse common dates
1838             sub _parse_date_common {
1839 2902     2902   5284 my($self,$string,$noupdate) = @_;
1840 2902         4455 my $dmt = $$self{'tz'};
1841 2902         3821 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         10639 $string =~ s/\s+/ /g;
1848              
1849             my $daterx = (exists $$dmb{'data'}{'rx'}{'other'}{'common_1'} ?
1850 2902 100       7985 $$dmb{'data'}{'rx'}{'other'}{'common_1'} :
1851             $self->_other_rx('common_1'));
1852              
1853 2902 100       16607 if ($string =~ $daterx) {
1854 228         1632 my($y,$m,$d) = @+{qw(y m d)};
1855              
1856 228 100       1055 if ($dmb->_config('dateformat') ne 'US') {
1857 20         52 ($m,$d) = ($d,$m);
1858             }
1859              
1860 228         609 ($y,$m,$d) = $self->_def_date($y,$m,$d,$noupdate);
1861 228         661 return($y,$m,$d);
1862             }
1863              
1864             $daterx = (exists $$dmb{'data'}{'rx'}{'other'}{'common_2'} ?
1865 2674 100       7233 $$dmb{'data'}{'rx'}{'other'}{'common_2'} :
1866             $self->_other_rx('common_2'));
1867              
1868 2674 100       35276 if ($string =~ $daterx) {
1869 1345         13372 my($y,$m,$d,$mmm,$month) = @+{qw(y m d mmm month)};
1870              
1871 1345 100       4319 if ($mmm) {
    100          
1872 1224         4038 $m = $$dmb{'data'}{'wordmatch'}{'month_abb'}{lc($mmm)};
1873             } elsif ($month) {
1874 115         391 $m = $$dmb{'data'}{'wordmatch'}{'month_name'}{lc($month)};
1875             }
1876              
1877 1345         3511 ($y,$m,$d) = $self->_def_date($y,$m,$d,$noupdate);
1878 1345         4172 return($y,$m,$d);
1879             }
1880              
1881 1329         3396 return ();
1882             }
1883              
1884             # Parse truncated dates
1885             sub _parse_date_truncated {
1886 420     420   766 my($self,$string,$noupdate) = @_;
1887 420         771 my $dmt = $$self{'tz'};
1888 420         597 my $dmb = $$dmt{'base'};
1889              
1890             my $daterx = (exists $$dmb{'data'}{'rx'}{'other'}{'truncated'} ?
1891 420 100       1122 $$dmb{'data'}{'rx'}{'other'}{'truncated'} :
1892             $self->_other_rx('truncated'));
1893              
1894 420 100       965 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         38 $string =~ s/\s+/ /g;
1901              
1902 16 50       110 if ($string =~ $daterx) {
1903 16         141 my($y,$mmm,$month) = @+{qw(y mmm month)};
1904              
1905 16         41 my ($m,$d);
1906 16 50       94 if ($mmm) {
    0          
1907 16         53 $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     89 if ($y && $m) {
1915              
1916 16         53 my $format_mmmyyyy = $dmb->_config('format_mmmyyyy');
1917 16 100       38 if ($format_mmmyyyy eq 'first') {
1918 8         10 $d=1;
1919 8         23 $$self{'data'}{'default_time'} = [0,0,0];
1920             } else {
1921 8         25 $d=$dmb->days_in_month($y,$m);
1922 8         23 $$self{'data'}{'default_time'} = [23,59,59];
1923             }
1924              
1925 16         34 $$self{'data'}{'def'}[0] = '';
1926 16         29 $$self{'data'}{'def'}[1] = '';
1927 16         22 $$self{'data'}{'def'}[2] = 1;
1928 16         51 return($y,$m,$d);
1929             }
1930             }
1931              
1932 0         0 return ();
1933             }
1934              
1935             sub _parse_tz {
1936 281     281   488 my($self,$string,$noupdate) = @_;
1937 281         362 my $dmt = $$self{'tz'};
1938 281         400 my($tzstring,$zone,$abb,$off);
1939              
1940 281         896 my $rx = $dmt->_zrx('zrx');
1941 281 100       67688 if ($string =~ s/(?:^|\s)$rx(?:$|\s)/ /) {
1942 9         115 ($tzstring,$zone,$abb,$off) = @+{qw(tzstring zone abb off)};
1943 9         75 return($string,$tzstring,$zone,$abb,$off);
1944             }
1945 272         1937 return($string);
1946             }
1947              
1948             sub _parse_dow {
1949 2898     2898   5491 my($self,$string,$noupdate) = @_;
1950 2898         4372 my $dmt = $$self{'tz'};
1951 2898         3778 my $dmb = $$dmt{'base'};
1952 2898         4148 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       7100 $$dmb{'data'}{'rx'}{'other'}{'dow'} :
1958             $self->_other_rx('dow'));
1959 2898 100       20665 if ($string =~ s/$rx/ /) {
1960 1261         5535 $dow = $+{'dow'};
1961 1261         3043 $dow = lc($dow);
1962              
1963             $dow = $$dmb{'data'}{'wordmatch'}{'day_abb'}{$dow}
1964 1261 100       4236 if (exists $$dmb{'data'}{'wordmatch'}{'day_abb'}{$dow});
1965             $dow = $$dmb{'data'}{'wordmatch'}{'day_name'}{$dow}
1966 1261 100       3783 if (exists $$dmb{'data'}{'wordmatch'}{'day_name'}{$dow});
1967             } else {
1968 1637         4005 return (0);
1969             }
1970              
1971 1261         5471 $string =~ s/\s*$//;
1972 1261         3396 $string =~ s/^\s*//;
1973              
1974 1261 100       4912 return (0,$string,$dow) if ($string);
1975              
1976             # Handle the simple DoW format
1977              
1978 18         50 ($y,$m,$d) = $self->_def_date($y,$m,$d,$noupdate);
1979              
1980 18         26 my($w,$dow1);
1981              
1982 18         67 ($y,$w) = $dmb->week_of_year([$y,$m,$d]); # week of year
1983 18         34 ($y,$m,$d) = @{ $dmb->week_of_year($y,$w) }; # first day
  18         36  
1984 18         69 $dow1 = $dmb->day_of_week([$y,$m,$d]); # DoW of first day
1985 18 50       39 $dow1 -= 7 if ($dow1 > $dow);
1986 18         27 ($y,$m,$d) = @{ $dmb->calc_date_days([$y,$m,$d],$dow-$dow1) };
  18         51  
1987              
1988 18         59 return(1,$y,$m,$d);
1989             }
1990              
1991             sub _parse_holidays {
1992 257     257   460 my($self,$string,$noupdate) = @_;
1993 257         460 my $dmt = $$self{'tz'};
1994 257         343 my $dmb = $$dmt{'base'};
1995 257         371 my($y,$m,$d);
1996              
1997 257 100       561 if (! exists $$dmb{'data'}{'rx'}{'holidays'}) {
1998 150         275 return (0);
1999             }
2000              
2001 107         589 $string =~ s/\s*$//;
2002 107         322 $string =~ s/^\s*//;
2003              
2004 107         185 my $rx = $$dmb{'data'}{'rx'}{'holidays'};
2005 107 100       538 if ($string =~ $rx) {
2006 9         13 my $hol;
2007 9         68 ($y,$hol) = @+{qw(y holiday)};
2008 9 100       31 $y = $dmt->_now('y',$noupdate) if (! $y);
2009 9         17 $y += 0;
2010              
2011 9         31 $self->_holidays($y-1);
2012 9         24 $self->_holidays($y);
2013 9         26 $self->_holidays($y+1);
2014 9 50       36 return (0) if (! exists $$dmb{'data'}{'holidays'}{'yhols'}{$y+0}{$hol});
2015 9         14 my ($y,$m,$d) = @{ $$dmb{'data'}{'holidays'}{'yhols'}{$y+0}{$hol} };
  9         28  
2016 9         29 return(1,$y,$m,$d);
2017             }
2018              
2019 98         205 return (0);
2020             }
2021              
2022 168     168   572450 no integer;
  168         376  
  168         698  
2023             sub _parse_delta {
2024 334     334   724 my($self,$string,$dow,$got_time,$h,$mn,$s,$noupdate) = @_;
2025 334         502 my $dmt = $$self{'tz'};
2026 334         424 my $dmb = $$dmt{'base'};
2027 334         470 my($y,$m,$d);
2028              
2029 334         857 my $delta = $self->new_delta();
2030 334         857 my $err = $delta->parse($string);
2031 334         1123 my $tz = $dmt->_now('tz');
2032 334         716 my $isdst = $dmt->_now('isdst');
2033              
2034 334 100       619 if (! $err) {
2035 36         45 my($dy,$dm,$dw,$dd,$dh,$dmn,$ds) = @{ $$delta{'data'}{'delta'} };
  36         103  
2036              
2037             # We can't handle a delta longer than 10000 years
2038 36 50 33     467 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     114 if ($got_time &&
      66        
2050             ($dh != 0 || $dmn != 0 || $ds != 0)) {
2051 6         12 $$self{'err'} = '[parse] Two times entered or implied';
2052 6         31 return (1);
2053             }
2054              
2055 30 100       63 if ($got_time) {
2056 6         23 ($y,$m,$d) = $self->_def_date($y,$m,$d,$noupdate);
2057             } else {
2058 24         91 ($y,$m,$d,$h,$mn,$s) = $dmt->_now('now',$$noupdate);
2059 24         40 $$noupdate = 1;
2060             }
2061              
2062 30 50       71 my $business = ($$delta{'data'}{'mode'} eq 'business' ? 1 : 0);
2063              
2064 30         43 my($date2,$offset,$abbrev);
2065 30         142 ($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         126 ($y,$m,$d,$h,$mn,$s) = @$date2;
2070              
2071 30 100       59 if ($dow) {
2072 10 50 33     55 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         12 my($w,$dow1);
2078              
2079 10         33 ($y,$w) = $dmb->week_of_year([$y,$m,$d]); # week of year
2080 10         15 ($y,$m,$d) = @{ $dmb->week_of_year($y,$w) }; # first day
  10         19  
2081 10         28 $dow1 = $dmb->day_of_week([$y,$m,$d]); # DoW of first day
2082 10 50       24 $dow1 -= 7 if ($dow1 > $dow);
2083 10         12 ($y,$m,$d) = @{ $dmb->calc_date_days([$y,$m,$d],$dow-$dow1) };
  10         29  
2084             }
2085              
2086 30         257 return (1,$y,$m,$d,$h,$mn,$s);
2087             }
2088              
2089 298         1380 return (0);
2090             }
2091 168     168   62908 use integer;
  168         367  
  168         682  
2092              
2093             sub _parse_datetime_other {
2094 1990     1990   5232 my($self,$string,$noupdate) = @_;
2095 1990         3054 my $dmt = $$self{'tz'};
2096 1990         2956 my $dmb = $$dmt{'base'};
2097              
2098             my $rx = (exists $$dmb{'data'}{'rx'}{'other'}{'miscdatetime'} ?
2099 1990 100       5568 $$dmb{'data'}{'rx'}{'other'}{'miscdatetime'} :
2100             $self->_other_rx('miscdatetime'));
2101              
2102 1990 100       13795 if ($string =~ $rx) {
2103             my ($special,$epoch,$y,$mmm,$d,$h,$mn,$s,$tzstring,$zone,$abb,$off) =
2104 24         575 @+{qw(special epoch y mmm d h mn s tzstring zone abb off)};
2105              
2106 24 100       137 if (defined($special)) {
    100          
    50          
2107 18         103 my $delta = $$dmb{'data'}{'wordmatch'}{'offset_time'}{lc($special)};
2108 18         32 my @delta = @{ $dmb->split('delta',$delta) };
  18         80  
2109 18         109 my @date = $dmt->_now('now',$$noupdate);
2110 18         56 my $tz = $dmt->_now('tz');
2111 18         49 my $isdst = $dmt->_now('isdst');
2112 18         34 $$noupdate = 1;
2113              
2114 18         36 my($err,$date2,$offset,$abbrev);
2115 18         104 ($err,$date2,$offset,$isdst,$abbrev) =
2116             $self->__calc_date_delta([@date],[@delta],0,0,$tz,$isdst);
2117              
2118 18 100       64 if ($tzstring) {
2119              
2120 1 50       3 $date2 = [] if (! defined $date2);
2121 1 50       4 my $offset = (defined $off ? $dmb->_delta_convert('offset',$off) : '');
2122 1 50       3 $zone = (defined $zone ? lc($zone) : '');
2123 1 50       5 my $abbrev = (defined $abb ? lc($abb) : '');
2124              
2125             # In some cases, a valid abbreviation is also a valid timezone
2126 1         7 my $tmp = $dmt->__zone($date2,$offset,$zone,$abbrev,'');
2127 1 0 33     8 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         3 $zone = $tmp;
2132              
2133 1 50       3 return (0) if (! $zone);
2134              
2135 1         7 my(@tmp) = $dmt->_convert('_parse_datetime_other',$date2,$tz,$zone);
2136 1         3 $date2 = $tmp[1];
2137             }
2138              
2139 18         56 @date = @$date2;
2140              
2141 18         107 return (1,@date,$tzstring,$zone,$abb,$off);
2142              
2143             } elsif (defined($epoch)) {
2144 5         17 my $date = [1970,1,1,0,0,0];
2145 5         15 my @delta = (0,0,$epoch);
2146 5         21 $date = $dmb->calc_date_time($date,\@delta);
2147 5         10 my($err);
2148 5 100       17 if ($tzstring) {
2149              
2150 1 50       5 my $offset = (defined $off ? $dmb->_delta_convert('offset',$off) : '');
2151 1 50       5 $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         19 my $tmp = $dmt->__zone($date,$offset,$zone,$abbrev,'');
2156 1 0 33     6 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         3 $zone = $tmp;
2161              
2162 1 50       3 return (0) if (! $zone);
2163              
2164 1         7 ($err,$date) = $dmt->convert_from_gmt($date,$zone);
2165             } else {
2166 4         17 ($err,$date) = $dmt->convert_from_gmt($date);
2167             }
2168 5         33 return (1,@$date,$tzstring,$zone,$abb,$off);
2169              
2170             } elsif (defined($y)) {
2171 1         6 my $m = $$dmb{'data'}{'wordmatch'}{'month_abb'}{lc($mmm)};
2172 1         5 return (1,$y,$m,$d,$h,$mn,$s,$tzstring,$zone,$abb,$off);
2173             }
2174             }
2175              
2176 1966         4333 return (0);
2177             }
2178              
2179             sub _parse_date_other {
2180 1329     1329   3710 my($self,$string,$dow,$of,$noupdate) = @_;
2181 1329         2072 my $dmt = $$self{'tz'};
2182 1329         1862 my $dmb = $$dmt{'base'};
2183 1329         2031 my($y,$m,$d,$h,$mn,$s);
2184              
2185             my $rx = (exists $$dmb{'data'}{'rx'}{'other'}{'misc'} ?
2186 1329 100       3686 $$dmb{'data'}{'rx'}{'other'}{'misc'} :
2187             $self->_other_rx('misc'));
2188              
2189 1329         3182 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       14120 if ($string =~ $rx) {
2193             ($y,$mmm,$month,$nextprev,$last,$field_y,$field_m,$field_w,$field_d,$nth,
2194             $special,$n) =
2195 879         15465 @+{qw(y mmm month next last field_y field_m field_w field_d
2196             nth special n)};
2197              
2198 879 100       3433 if (defined($y)) {
2199 90         364 $y = $dmt->_fix_year($y);
2200 90         153 $got_y = 1;
2201 90 50       175 return () if (! $y);
2202             } else {
2203 789         2818 $y = $dmt->_now('y',$$noupdate);
2204 789         1160 $$noupdate = 1;
2205 789         1096 $got_y = 0;
2206 789         1636 $$self{'data'}{'def'}[0] = '';
2207             }
2208              
2209 879 100       1650 if (defined($mmm)) {
    100          
2210 698         2011 $m = $$dmb{'data'}{'wordmatch'}{'month_abb'}{lc($mmm)};
2211 698         1159 $got_m = 1;
2212             } elsif ($month) {
2213 31         116 $m = $$dmb{'data'}{'wordmatch'}{'month_name'}{lc($month)};
2214 31         50 $got_m = 1;
2215             }
2216              
2217 879 100       1679 if ($nth) {
2218 632         1748 $nth = $$dmb{'data'}{'wordmatch'}{'nth'}{lc($nth)};
2219             }
2220              
2221 879 100 100     10175 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         51 $d = $nth;
2228              
2229             } elsif ($nextprev) {
2230              
2231 50         58 my $next = 0;
2232 50         60 my $sign = -1;
2233 50 100       162 if ($$dmb{'data'}{'wordmatch'}{'nextprev'}{lc($nextprev)} == 1) {
2234 22         29 $next = 1;
2235 22         28 $sign = 1;
2236             }
2237              
2238 50 100 100     226 if ($field_y || $field_m || $field_w) {
    50 100        
2239             # next/prev year/month/week
2240              
2241 28         36 my(@delta);
2242 28 100       53 if ($field_y) {
    100          
2243 8         22 @delta = ($sign*1,0,0,0,0,0,0);
2244             } elsif ($field_m) {
2245 10         25 @delta = (0,$sign*1,0,0,0,0,0);
2246             } else {
2247 10         24 @delta = (0,0,$sign*1,0,0,0,0);
2248             }
2249              
2250 28         63 my @now = $dmt->_now('now',$$noupdate);
2251 28         58 my $tz = $dmt->_now('tz');
2252 28         54 my $isdst = $dmt->_now('isdst');
2253 28         40 $$noupdate = 1;
2254              
2255 28         41 my($err,$offset,$abbrev,$date2);
2256 28         103 ($err,$date2,$offset,$isdst,$abbrev) =
2257             $self->__calc_date_delta([@now],[@delta],0,0,$tz,$isdst);
2258 28         123 ($y,$m,$d,$h,$mn,$s) = @$date2;
2259              
2260             } elsif ($dow) {
2261             # next/prev friday
2262              
2263 22         54 my @now = $dmt->_now('now',$$noupdate);
2264 22         36 $$noupdate = 1;
2265 22         29 ($y,$m,$d,$h,$mn,$s) = @{ $self->__next_prev(\@now,$next,$dow,0) };
  22         59  
2266 22         46 $dow = 0;
2267              
2268             } else {
2269 0         0 return ();
2270             }
2271              
2272             } elsif ($last) {
2273              
2274 127 100 66     740 if ($field_d && $got_m) {
    100 66        
    50          
2275             # last day in october 95
2276              
2277 6         23 $d = $dmb->days_in_month($y,$m);
2278              
2279             } elsif ($dow && $got_m) {
2280             # last friday in october 95
2281              
2282 120         402 $d = $dmb->days_in_month($y,$m);
2283             ($y,$m,$d,$h,$mn,$s) =
2284 120         228 @{ $self->__next_prev([$y,$m,$d,0,0,0],0,$dow,1) };
  120         501  
2285 120         271 $dow = 0;
2286              
2287             } elsif ($dow) {
2288             # last friday in 95
2289              
2290             ($y,$m,$d,$h,$mn,$s) =
2291 1         2 @{ $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       1048 if ($got_m) {
2300 571 100       946 if ($of) {
2301             # nth DoW of MMM [YYYY]
2302 569 100       1170 return () if ($nth > 5);
2303              
2304 567         704 $d = 1;
2305             ($y,$m,$d,$h,$mn,$s) =
2306 567         666 @{ $self->__next_prev([$y,$m,1,0,0,0],1,$dow,1) };
  567         2226  
2307 567         1074 my $m2 = $m;
2308 567 100       1156 ($y,$m2,$d) = @{ $dmb->calc_date_days([$y,$m,$d],7*($nth-1)) }
  454         1524  
2309             if ($nth > 1);
2310 567 50 33     2426 return () if (! $m2 || $m2 != $m);
2311              
2312             } else {
2313             # DoW, nth MMM [YYYY] (i.e. Sunday, 9th Dec 2008)
2314 2         4 $d = $nth;
2315             }
2316              
2317             } else {
2318             # nth DoW [in YYYY]
2319              
2320 13         17 ($y,$m,$d,$h,$mn,$s) = @{ $self->__next_prev([$y,1,1,0,0,0],1,$dow,1) };
  13         51  
2321 13 100       38 ($y,$m,$d) = @{ $dmb->calc_date_days([$y,$m,$d],7*($nth-1)) }
  9         29  
2322             if ($nth > 1);
2323             }
2324              
2325             } elsif ($field_w && $dow) {
2326              
2327 25 100 100     85 if (defined($n) || $nth) {
2328             # sunday week 22 in 1996
2329             # sunday 22nd week in 1996
2330              
2331 23 100       52 $n = $nth if ($nth);
2332 23 100       50 return () if (! $n);
2333 21         30 ($y,$m,$d) = @{ $dmb->week_of_year($y,$n) };
  21         80  
2334 21         34 ($y,$m,$d) = @{ $self->__next_prev([$y,$m,$d,0,0,0],1,$dow,1) };
  21         85  
2335              
2336             } else {
2337             # DoW week
2338              
2339 2         6 ($y,$m,$d) = $dmt->_now('now',$$noupdate);
2340 2         3 $$noupdate = 1;
2341 2         6 my $tmp = $dmb->_config('firstday');
2342 2         4 ($y,$m,$d) = @{ $self->__next_prev([$y,$m,$d,0,0,0],1,$tmp,0) };
  2         7  
2343 2         5 ($y,$m,$d) = @{ $self->__next_prev([$y,$m,$d,0,0,0],1,$dow,1) };
  2         6  
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     37 return () if ($field_d || $field_w || $field_m || $field_y);
      66        
      66        
2350 4         12 ($y,$m,$d) = $dmt->_now('now',$$noupdate);
2351 4         9 $$noupdate = 1;
2352 4         8 $d = $nth;
2353              
2354             } elsif ($special) {
2355              
2356 56         239 my $delta = $$dmb{'data'}{'wordmatch'}{'offset_date'}{lc($special)};
2357 56         72 my @delta = @{ $dmb->split('delta',$delta) };
  56         184  
2358 56         198 ($y,$m,$d) = $dmt->_now('now',$$noupdate);
2359 56         202 my $tz = $dmt->_now('tz');
2360 56         116 my $isdst = $dmt->_now('isdst');
2361 56         77 $$noupdate = 1;
2362 56         81 my($err,$offset,$abbrev,$date2);
2363 56         263 ($err,$date2,$offset,$isdst,$abbrev) =
2364             $self->__calc_date_delta([$y,$m,$d,0,0,0],[@delta],0,0,$tz,$isdst);
2365 56         157 ($y,$m,$d) = @$date2;
2366              
2367 56 100       148 if ($field_w) {
2368 8         11 ($y,$m,$d) = @{ $dmb->calc_date_days([$y,$m,$d],7) };
  8         24  
2369             }
2370             }
2371              
2372             } else {
2373 450         1116 return ();
2374             }
2375              
2376 874         2687 return($y,$m,$d,$dow);
2377             }
2378              
2379             # Supply defaults for missing values (Y/M/D)
2380             sub _def_date {
2381 1904     1904   4130 my($self,$y,$m,$d,$noupdate) = @_;
2382 1904 100       3738 $y = '' if (! defined $y);
2383 1904 100       3311 $m = '' if (! defined $m);
2384 1904 100       3354 $d = '' if (! defined $d);
2385 1904         2352 my $defined = 0;
2386 1904         2792 my $dmt = $$self{'tz'};
2387 1904         3001 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       3456 if ($y eq '') {
2394 324         1050 $y = $dmt->_now('y',$$noupdate);
2395 324         486 $$noupdate = 1;
2396 324         792 $$self{'data'}{'def'}[0] = '';
2397             } else {
2398 1580         5579 $y = $dmt->_fix_year($y);
2399 1580         2474 $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       3579 if ($m ne '') {
    100          
2408 1839         2314 $defined = 1;
2409             } elsif ($defined) {
2410 4         7 $m = 1;
2411 4         9 $$self{'data'}{'def'}[1] = 1;
2412             } else {
2413 61         156 $m = $dmt->_now('m',$$noupdate);
2414 61         86 $$noupdate = 1;
2415 61         113 $$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       3100 if ($d ne '') {
    100          
2424 1835         2275 $defined = 1;
2425             } elsif ($defined) {
2426 13         18 $d = 1;
2427 13         25 $$self{'data'}{'def'}[2] = 1;
2428             } else {
2429 56         117 $d = $dmt->_now('d',$$noupdate);
2430 56         79 $$noupdate = 1;
2431 56         98 $$self{'data'}{'def'}[2] = '';
2432             }
2433              
2434 1904         5184 return($y,$m,$d);
2435             }
2436              
2437             # Supply defaults for missing values (Y/DoY)
2438             sub _def_date_doy {
2439 23     23   49 my($self,$y,$doy,$noupdate) = @_;
2440 23 100       49 $y = '' if (! defined $y);
2441 23         38 my $dmt = $$self{'tz'};
2442 23         33 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       115 if ($y eq '') {
2449 2         8 $y = $dmt->_now('y',$$noupdate);
2450 2         4 $$noupdate = 1;
2451 2         6 $$self{'data'}{'def'}[0] = '';
2452             } else {
2453 21         62 $y = $dmt->_fix_year($y);
2454             }
2455              
2456             # DoY must be specified.
2457              
2458 23         32 my($m,$d);
2459 23         73 my $ymd = $dmb->day_of_year($y,$doy);
2460              
2461 23         74 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   150 my($self,$y,$w,$dow,$noupdate) = @_;
2467 69 100       132 $y = '' if (! defined $y);
2468 69 100       115 $w = '' if (! defined $w);
2469 69 100       115 $dow = '' if (! defined $dow);
2470 69         96 my $dmt = $$self{'tz'};
2471 69         89 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       117 if ($y ne '') {
2481 49 50       91 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         142 $y = $dmt->_fix_year($y);
2489              
2490             }
2491              
2492             } else {
2493 20         65 $y = $dmt->_now('y',$$noupdate);
2494 20         28 $$noupdate = 1;
2495 20         40 $$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         98 my($m,$d);
2502 69 100       109 if ($w ne '') {
2503 61         66 ($y,$m,$d) = @{ $dmb->week_of_year($y,$w) };
  61         133  
2504             } else {
2505 8         20 my($nowy,$nowm,$nowd) = $dmt->_now('now',$$noupdate);
2506 8         13 $$noupdate = 1;
2507 8         15 my $noww;
2508 8         26 ($nowy,$noww) = $dmb->week_of_year([$nowy,$nowm,$nowd]);
2509 8         16 ($y,$m,$d) = @{ $dmb->week_of_year($nowy,$noww) };
  8         14  
2510             }
2511              
2512             # Handle the DoW
2513              
2514 69 100       137 if ($dow eq '') {
2515 18         26 $dow = 1;
2516             }
2517 69         141 my $n = $dmb->days_in_month($y,$m);
2518 69         102 $d += ($dow-1);
2519 69 100       118 if ($d > $n) {
2520 5         7 $m++;
2521 5 50       11 if ($m==13) {
2522 0         0 $y++;
2523 0         0 $m = 1;
2524             }
2525 5         8 $d = $d-$n;
2526             }
2527              
2528 69         166 return($y,$m,$d);
2529             }
2530              
2531             # Supply defaults for missing values (HH:MN:SS)
2532             sub _def_time {
2533 2580     2580   4796 my($self,$h,$m,$s,$noupdate) = @_;
2534 2580 100       4505 $h = '' if (! defined $h);
2535 2580 100       4154 $m = '' if (! defined $m);
2536 2580 100       4043 $s = '' if (! defined $s);
2537 2580         3006 my $defined = 0;
2538 2580         3654 my $dmt = $$self{'tz'};
2539 2580         3127 my $dmb = $$dmt{'base'};
2540              
2541             # If no time was specified, defaults to 00:00:00.
2542              
2543 2580 50 66     5264 if ($h eq '' &&
      66        
2544             $m eq '' &&
2545             $s eq '') {
2546 126         237 $$self{'data'}{'def'}[3] = 1;
2547 126         178 $$self{'data'}{'def'}[4] = 1;
2548 126         181 $$self{'data'}{'def'}[5] = 1;
2549 126         278 return(0,0,0);
2550             }
2551              
2552             # If hour was not specified, defaults to current hour.
2553              
2554 2454 50       3808 if ($h ne '') {
2555 2454         2910 $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       3732 if ($m ne '') {
    50          
2568 2437         2763 $defined = 1;
2569             } elsif ($defined) {
2570 17         27 $m = 0;
2571 17         34 $$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       4131 if ($s eq '') {
2582 288         523 $s = 0;
2583 288         519 $$self{'data'}{'def'}[5] = 1;
2584             }
2585              
2586 2454         6315 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 32672     32672 1 52646 my($self,$type) = @_;
2601 32672         40311 my $dmt = $$self{'tz'};
2602 32672         37113 my $dmb = $$dmt{'base'};
2603 32672         34542 my $date;
2604              
2605 32672         34522 while (1) {
2606 32672 100       53385 if (! $$self{'data'}{'set'}) {
2607 15         23 $$self{'err'} = '[value] Object does not contain a date';
2608 15         16 last;
2609             }
2610              
2611 32657 100       50180 $type = '' if (! $type);
2612              
2613 32657 100       54755 if ($type eq 'gmt') {
    100          
2614              
2615 2989 100       3389 if (! @{ $$self{'data'}{'gmt'} }) {
  2989         6553  
2616 2687         4195 my $zone = $$self{'data'}{'tz'};
2617 2687         3866 my $date = $$self{'data'}{'date'};
2618              
2619 2687 50       4183 if ($zone eq 'Etc/GMT') {
2620 0         0 $$self{'data'}{'gmt'} = $date;
2621              
2622             } else {
2623 2687         3953 my $isdst = $$self{'data'}{'isdst'};
2624 2687         7330 my($err,$d) = $dmt->convert_to_gmt($date,$zone,$isdst);
2625 2687 50       5673 if ($err) {
2626 0         0 $$self{'err'} = '[value] Unable to convert date to GMT';
2627 0         0 last;
2628             }
2629 2687         5924 $$self{'data'}{'gmt'} = $d;
2630             }
2631             }
2632 2989         4835 $date = $$self{'data'}{'gmt'};
2633              
2634             } elsif ($type eq 'local') {
2635              
2636 219 50       250 if (! @{ $$self{'data'}{'loc'} }) {
  219         463  
2637 219         313 my $zone = $$self{'data'}{'tz'};
2638 219         278 $date = $$self{'data'}{'date'};
2639 219         649 my $local = $dmt->_now('tz',1);
2640              
2641 219 100       371 if ($zone eq $local) {
2642 192         315 $$self{'data'}{'loc'} = $date;
2643              
2644             } else {
2645 27         68 my $isdst = $$self{'data'}{'isdst'};
2646 27         126 my($err,$d) = $dmt->convert_to_local($date,$zone,$isdst);
2647 27 50       75 if ($err) {
2648 0         0 $$self{'err'} = '[value] Unable to convert date to localtime';
2649 0         0 last;
2650             }
2651 27         84 $$self{'data'}{'loc'} = $d;
2652             }
2653             }
2654 219         358 $date = $$self{'data'}{'loc'};
2655              
2656             } else {
2657              
2658 29449         36341 $date = $$self{'data'}{'date'};
2659              
2660             }
2661              
2662 32657         37574 last;
2663             }
2664              
2665 32672 100       49596 if ($$self{'err'}) {
2666 18 50       51 if (wantarray) {
2667 18         66 return ();
2668             } else {
2669 0         0 return '';
2670             }
2671             }
2672              
2673 32654 100       43246 if (wantarray) {
2674 7934         20242 return @$date;
2675             } else {
2676 24720         49744 return $dmb->join('date',$date);
2677             }
2678             }
2679              
2680             sub cmp {
2681 10661     10661 1 15539 my($self,$date) = @_;
2682 10661 50 33     32564 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 10661 50       19555 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 10661 50 33     29307 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 10661         12325 my($d1,$d2);
2697 10661 100       18030 if ($$self{'data'}{'tz'} eq $$date{'data'}{'tz'}) {
2698 10660         17392 $d1 = $self->value();
2699 10660         18243 $d2 = $date->value();
2700             } else {
2701 1         6 $d1 = $self->value('gmt');
2702 1         3 $d2 = $date->value('gmt');
2703             }
2704              
2705 10661         34617 return ($d1 cmp $d2);
2706             }
2707              
2708 0         0 BEGIN {
2709 168     168   865744 my %field = qw(y 0 m 1 d 2 h 3 mn 4 s 5);
2710              
2711             sub set {
2712 10511     10511 1 403225 my($self,$field,@val) = @_;
2713 10511         14950 $field = lc($field);
2714 10511         13779 my $dmt = $$self{'tz'};
2715 10511         13460 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         13558 my $date = [];
2721 10511         14812 my(@def,$tz,$isdst);
2722              
2723 10511 100       21097 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       10128 $self->_init() if ($$self{'data'}{'set'} != 2);
2731 4876         5915 @def = @{ $$self{'data'}{'def'} };
  4876         9696  
2732              
2733             } elsif ($field eq 'date') {
2734 5569 100 66     12547 if ($$self{'data'}{'set'} && ! $$self{'err'}) {
2735 319         478 $tz = $$self{'data'}{'tz'};
2736             } else {
2737 5250         13518 $tz = $dmt->_now('tz',1);
2738             }
2739 5569         12514 $self->_init();
2740 5569         6593 @def = @{ $$self{'data'}{'def'} };
  5569         10219  
2741              
2742             } else {
2743 66 50 33     238 return 1 if ($$self{'err'} || ! $$self{'data'}{'set'});
2744 66         134 $date = $$self{'data'}{'date'};
2745 66         110 $tz = $$self{'data'}{'tz'};
2746 66         94 $isdst = $$self{'data'}{'isdst'};
2747 66         81 @def = @{ $$self{'data'}{'def'} };
  66         134  
2748 66         141 $self->_init();
2749             }
2750              
2751             # Check the arguments
2752              
2753 10511         15481 my($err,$new_tz,$new_date,$new_time);
2754              
2755 10511 100       22135 if ($field eq 'date') {
    100          
    100          
    50          
    50          
2756              
2757 5569 100       9348 if ($#val == 0) {
    50          
2758             # date,DATE
2759 5554         7310 $new_date = $val[0];
2760             } elsif ($#val == 1) {
2761             # date,DATE,ISDST
2762 15         32 ($new_date,$isdst) = @val;
2763             } else {
2764 0         0 $err = 1;
2765             }
2766 5569         11869 for (my $i=0; $i<=5; $i++) {
2767 33414 50       57245 $def[$i] = 0 if ($def[$i]);
2768             }
2769              
2770             } elsif ($field eq 'time') {
2771              
2772 64 50       142 if ($#val == 0) {
    0          
2773             # time,TIME
2774 64         89 $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       123 $def[3] = 0 if ($def[3]);
2782 64 50       127 $def[4] = 0 if ($def[4]);
2783 64 100       153 $def[5] = 0 if ($def[5]);
2784              
2785             } elsif ($field eq 'zdate') {
2786              
2787 4876 100 33     17346 if ($#val == 0) {
    50 66        
    100          
    50          
2788             # zdate,DATE
2789 2         5 $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         7556 ($new_tz,$new_date,$isdst) = @val;
2799             } else {
2800 0         0 $err = 1;
2801             }
2802 4876 100       10341 if ($$self{'data'}{'set'} != 2) {
2803 4         13 for (my $i=0; $i<=5; $i++) {
2804 24 50       65 $def[$i] = 0 if ($def[$i]);
2805             }
2806             }
2807 4876 100       8502 $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         4 my $i = $field{$field};
2830 2         2 my $val;
2831 2 50       14 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       4 $def[$i] = 0 if ($def[$i]);
2841              
2842             } else {
2843              
2844 0         0 $err = 2;
2845              
2846             }
2847              
2848 10511 50       16967 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       15658 if ($new_tz) {
2860 4874         10417 my $tmp = $dmt->_zone($new_tz);
2861 4874 50       7901 if ($tmp) {
2862             # A zone/alias
2863 4874         6917 $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       17382 if ($new_date) {
2881 10445 100       25138 if ($dmb->check($new_date)) {
2882 10441         15222 $date = $new_date;
2883             } else {
2884 4         12 $$self{'err'} = '[set] Invalid date argument';
2885 4         16 return 1;
2886             }
2887             }
2888              
2889 10507 100       17382 if ($new_time) {
2890 64 50       184 if ($dmb->check_time($new_time)) {
2891 64         109 $$date[3] = $$new_time[0];
2892 64         88 $$date[4] = $$new_time[1];
2893 64         85 $$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         13633 my($abb,$off);
2903 10507 100       16608 if ($tz eq 'etc/gmt') {
2904 42         76 $abb = 'GMT';
2905 42         90 $off = [0,0,0];
2906 42         71 $isdst = 0;
2907             } else {
2908 10465         24385 my $per = $dmt->date_period($date,$tz,1,$isdst);
2909 10465 100       18493 if (! $per) {
2910 3         8 $$self{'err'} = '[set] Invalid date/timezone';
2911 3         10 return 1;
2912             }
2913 10462         14353 $isdst = $$per[5];
2914 10462         12445 $abb = $$per[4];
2915 10462         13436 $off = $$per[3];
2916             }
2917              
2918             # Set the information
2919              
2920 10504         16056 $$self{'data'}{'set'} = 1;
2921 10504         16259 $$self{'data'}{'date'} = $date;
2922 10504         14577 $$self{'data'}{'tz'} = $tz;
2923 10504         15001 $$self{'data'}{'isdst'} = $isdst;
2924 10504         14074 $$self{'data'}{'offset'}= $off;
2925 10504         13598 $$self{'data'}{'abb'} = $abb;
2926 10504         22419 $$self{'data'}{'def'} = [ @def ];
2927              
2928 10504         24529 return 0;
2929             }
2930             }
2931              
2932             ########################################################################
2933             # NEXT/PREV METHODS
2934              
2935             sub prev {
2936 75     75 1 225 my($self,@args) = @_;
2937 75 50 33     224 return 1 if ($$self{'err'} || ! $$self{'data'}{'set'});
2938 75         102 my $date = $$self{'data'}{'date'};
2939              
2940 75         139 $date = $self->__next_prev($date,0,@args);
2941              
2942 75 50       151 return 1 if (! defined($date));
2943 75         153 $self->set('date',$date);
2944 75         158 return 0;
2945             }
2946              
2947             sub next {
2948 75     75 1 228 my($self,@args) = @_;
2949 75 50 33     234 return 1 if ($$self{'err'} || ! $$self{'data'}{'set'});
2950 75         96 my $date = $$self{'data'}{'date'};
2951              
2952 75         132 $date = $self->__next_prev($date,1,@args);
2953              
2954 75 50       149 return 1 if (! defined($date));
2955 75         172 $self->set('date',$date);
2956 75         155 return 0;
2957             }
2958              
2959             sub __next_prev {
2960 1198     1198   2467 my($self,$date,$next,$dow,$curr,$time) = @_;
2961              
2962 1198         1542 my ($caller,$sign,$prev);
2963 1198 100       1985 if ($next) {
2964 944         1245 $caller = 'next';
2965 944         1058 $sign = 1;
2966 944         1121 $prev = 0;
2967             } else {
2968 254         386 $caller = 'prev';
2969 254         325 $sign = -1;
2970 254         321 $prev = 1;
2971             }
2972              
2973 1198         1705 my $dmt = $$self{'tz'};
2974 1198         1533 my $dmb = $$dmt{'base'};
2975 1198         2356 my $orig = [ @$date ];
2976              
2977             # Check the time (if any)
2978              
2979 1198 100       2418 if (defined($time)) {
2980 366 100       538 if ($dow) {
2981             # $time will refer to a full [H,MN,S]
2982 34         117 my($err,$h,$mn,$s) = $dmb->_hms_fields({ 'out' => 'list' },$time);
2983 34 50       81 if ($err) {
2984 0         0 $$self{'err'} = "[$caller] invalid time argument";
2985 0         0 return undef;
2986             }
2987 34         71 $time = [$h,$mn,$s];
2988             } else {
2989             # $time may have leading undefs
2990 332         544 my @tmp = @$time;
2991 332 50       603 if ($#tmp != 2) {
2992 0         0 $$self{'err'} = "[$caller] invalid time argument";
2993 0         0 return undef;
2994             }
2995 332         479 my($h,$mn,$s) = @$time;
2996 332 100       526 if (defined($h)) {
    100          
2997 296 100       490 $mn = 0 if (! defined($mn));
2998 296 100       442 $s = 0 if (! defined($s));
2999             } elsif (defined($mn)) {
3000 24 50       45 $s = 0 if (! defined($s));
3001             } else {
3002 12 50       27 $s = 0 if (! defined($s));
3003             }
3004 332         661 $time = [$h,$mn,$s];
3005             }
3006             }
3007              
3008             # Find the next DoW
3009              
3010 1198 100       2123 if ($dow) {
3011              
3012 866 50       2464 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         2223 my $curr_dow = $dmb->day_of_week($date);
3020 866         1379 my $adjust = 0;
3021              
3022 866 100       1595 if ($dow == $curr_dow) {
3023 182 100       423 $adjust = 1 if ($curr == 0);
3024              
3025             } else {
3026 684         866 my $num;
3027 684 100       1184 if ($next) {
3028             # force $dow to be more than $curr_dow
3029 559 100       1072 $dow += 7 if ($dow<$curr_dow);
3030 559         744 $num = $dow - $curr_dow;
3031             } else {
3032             # force $dow to be less than $curr_dow
3033 125 100       302 $dow -= 7 if ($dow>$curr_dow);
3034 125         179 $num = $curr_dow - $dow;
3035 125         179 $num *= -1;
3036             }
3037              
3038             # Add/subtract $num days
3039 684         1756 $date = $dmb->calc_date_days($date,$num);
3040             }
3041              
3042 866 100       1838 if (defined($time)) {
3043 34         65 my ($y,$m,$d,$h,$mn,$s) = @$date;
3044 34         53 ($h,$mn,$s) = @$time;
3045 34         88 $date = [$y,$m,$d,$h,$mn,$s];
3046             }
3047              
3048 866         2484 my $cmp = $dmb->cmp($orig,$date);
3049 866 100 100     2552 $adjust = 1 if ($curr == 2 && $cmp != -1*$sign);
3050              
3051 866 100       1523 if ($adjust) {
3052             # Add/subtract 1 week
3053 70         183 $date = $dmb->calc_date_days($date,$sign*7);
3054             }
3055              
3056 866         2726 return $date;
3057             }
3058              
3059             # Find the next Time
3060              
3061 332 50       538 if (defined($time)) {
3062              
3063 332         502 my ($h,$mn,$s) = @$time;
3064 332         564 my $orig = [ @$date ];
3065              
3066 332         378 my $cmp;
3067 332 100       541 if (defined $h) {
    100          
3068             # Find next/prev HH:MN:SS
3069              
3070 296         629 @$date[3..5] = @$time;
3071 296         729 $cmp = $dmb->cmp($orig,$date);
3072 296 100       639 if ($cmp == -1) {
    100          
3073 109 100       214 if ($prev) {
3074 10         26 $date = $dmb->calc_date_days($date,-1);
3075             }
3076             } elsif ($cmp == 1) {
3077 69 50       132 if ($next) {
3078 69         161 $date = $dmb->calc_date_days($date,1);
3079             }
3080             } else {
3081 118 100       217 if (! $curr) {
3082 102         229 $date = $dmb->calc_date_days($date,$sign);
3083             }
3084             }
3085              
3086             } elsif (defined $mn) {
3087             # Find next/prev MN:SS
3088              
3089 24         58 @$date[4..5] = @$time[1..2];
3090              
3091 24         66 $cmp = $dmb->cmp($orig,$date);
3092 24 50       59 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       21 if ($next) {
3098 4         15 $date = $dmb->calc_date_time($date,[1,0,0]);
3099             }
3100             } else {
3101 16 100       36 if (! $curr) {
3102 8         26 $date = $dmb->calc_date_time($date,[$sign,0,0]);
3103             }
3104             }
3105              
3106             } else {
3107             # Find next/prev SS
3108              
3109 12         26 $$date[5] = $$time[2];
3110              
3111 12         34 $cmp = $dmb->cmp($orig,$date);
3112 12 50       41 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       38 if (! $curr) {
3122 8         28 $date = $dmb->calc_date_time($date,[0,$sign,0]);
3123             }
3124             }
3125             }
3126              
3127 332         1483 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 10290 my($self,$obj,@args) = @_;
3139              
3140 4608 100       11982 if (ref($obj) eq 'Date::Manip::Date') {
    50          
3141 1430         3885 return $self->_calc_date_date($obj,@args);
3142              
3143             } elsif (ref($obj) eq 'Date::Manip::Delta') {
3144 3178         8132 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   2660 my($self,$date,@args) = @_;
3153 1430         3873 my $ret = $self->new_delta();
3154              
3155 1430 50 33     6314 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     5406 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         2162 my($subtract,$mode);
3168              
3169 1430 100       3349 if ($#args == -1) {
    100          
    50          
3170 1155         2140 ($subtract,$mode) = (0,'');
3171             } elsif ($#args == 0) {
3172 226 50 33     761 if ($args[0] eq '0' || $args[0] eq '1') {
3173 0         0 ($subtract,$mode) = ($args[0],'');
3174             } else {
3175 226         412 ($subtract,$mode) = (0,$args[0]);
3176             }
3177              
3178             } elsif ($#args == 1) {
3179 49         97 ($subtract,$mode) = @args;
3180             } else {
3181 0         0 $$ret{'err'} = '[calc] Invalid arguments';
3182 0         0 return $ret;
3183             }
3184 1430 100       3397 $mode = 'exact' if (! $mode);
3185              
3186 1430 50       7352 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         2435 my($date1,$date2,$tz1,$isdst1,$tz2,$isdst2);
3203 1430 100 100     10086 if ($mode eq 'business' || $mode eq 'bapprox' || $mode eq 'bsemi') {
    100 100        
      100        
      100        
3204 156 50       367 if ($$self{'data'}{'tz'} eq $$date{'data'}{'tz'}) {
3205 156         335 $date1 = [ $self->value() ];
3206 156         280 $date2 = [ $date->value() ];
3207 156         252 $tz1 = $$self{'data'}{'tz'};
3208 156         227 $tz2 = $tz1;
3209 156         205 $isdst1 = $$self{'data'}{'isdst'};
3210 156         260 $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         209 $date1 = [ $self->value() ];
3220 86         162 $date2 = [ $date->value() ];
3221 86         173 $tz1 = $$self{'data'}{'tz'};
3222 86         113 $tz2 = $tz1;
3223 86         149 $isdst1 = $$self{'data'}{'isdst'};
3224 86         107 $isdst2 = $$date{'data'}{'isdst'};
3225              
3226             } else {
3227 1188         2905 $date1 = [ $self->value('gmt') ];
3228 1188         2953 $date2 = [ $date->value('gmt') ];
3229 1188         2023 $tz1 = 'GMT';
3230 1188         1649 $tz2 = $tz1;
3231 1188         1543 $isdst1 = 0;
3232 1188         1541 $isdst2 = 0;
3233             }
3234              
3235             # Do the calculation
3236              
3237 1430         1866 my(@delta);
3238 1430 100       2429 if ($subtract) {
3239 42 100 100     181 if ($mode eq 'business' || $mode eq 'exact' || $subtract == 2) {
      100        
3240 23         33 @delta = @{ $self->__calc_date_date($mode,$date2,$tz2,$isdst2,
  23         52  
3241             $date1,$tz1,$isdst1) };
3242             } else {
3243 19         31 @delta = @{ $self->__calc_date_date($mode,$date1,$tz1,$isdst1,
  19         44  
3244             $date2,$tz2,$isdst2) };
3245 19         48 @delta = map { -1*$_ } @delta;
  133         212  
3246             }
3247             } else {
3248 1388         1761 @delta = @{ $self->__calc_date_date($mode,$date1,$tz1,$isdst1,
  1388         3993  
3249             $date2,$tz2,$isdst2) };
3250             }
3251              
3252             # Save the delta
3253              
3254 1430 100 100     7130 if ($mode eq 'business' || $mode eq 'bapprox' || $mode eq 'bsemi') {
      100        
3255 156         502 $ret->set('business',\@delta);
3256             } else {
3257 1274         4280 $ret->set('delta',\@delta);
3258             }
3259 1430         6221 return $ret;
3260             }
3261              
3262             sub __calc_date_date {
3263 1430     1430   3517 my($self,$mode,$date1,$tz1,$isdst1,$date2,$tz2,$isdst2) = @_;
3264 1430         2308 my $dmt = $$self{'tz'};
3265 1430         2257 my $dmb = $$dmt{'base'};
3266              
3267 1430         3045 my($dy,$dm,$dw,$dd,$dh,$dmn,$ds) = (0,0,0,0,0,0,0);
3268              
3269 1430 100 100     5483 if ($mode eq 'approx' || $mode eq 'bapprox') {
3270 112         181 my($y1,$m1,$d1,$h1,$mn1,$s1) = @$date1;
3271 112         163 my($y2,$m2,$d2,$h2,$mn2,$s2) = @$date2;
3272 112         127 $dy = $y2-$y1;
3273 112         118 $dm = $m2-$m1;
3274              
3275 112 100 100     284 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         225 my $dim = $dmb->days_in_month($y2,$m2);
3282 90 100       163 $d1 = $dim if ($d1 > $dim);
3283              
3284 90         233 $date1 = [$y2,$m2,$d1,$h1,$mn1,$s1];
3285             }
3286             }
3287              
3288 1430 100 100     4878 if ($mode eq 'semi' || $mode eq 'approx') {
3289              
3290             # Calculate the number of weeks/days apart (temporarily ignoring
3291             # DST effects).
3292              
3293 88         220 $dd = $dmb->days_since_1BC($date2) -
3294             $dmb->days_since_1BC($date1);
3295 88         130 $dw = int($dd/7);
3296 88         106 $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     249 if ($dw || $dd) {
3303 69         116 my($y1,$m1,$d1,$h1,$mn1,$s1) = @$date1;
3304 69         96 my($y2,$m2,$d2,$h2,$mn2,$s2) = @$date2;
3305 69         157 $date1 = [$y2,$m2,$d2,$h1,$mn1,$s1];
3306             }
3307 88 100 100     360 if ($dy || $dm || $dw || $dd) {
      100        
      100        
3308 81 100 100     331 my $force = ( ($dw > 0 || $dd > 0) ? 1 : -1 );
3309 81         93 my($off,$isdst,$abb);
3310 81         196 ($date1,$off,$isdst,$abb) =
3311             $self->_calc_date_check_dst($date1,$tz2,$isdst2,$force);
3312             }
3313             }
3314              
3315 1430 100 100     5013 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         262 $dd = $dmb->days_since_1BC($date2) -
3321             $dmb->days_since_1BC($date1);
3322 94         142 $dw = int($dd/7);
3323 94         116 $dd = 0;
3324 94         209 $date1 = $dmb->calc_date_days($date1,$dw*7);
3325             }
3326              
3327 1430 100 100     4069 if ($mode eq 'exact' || $mode eq 'semi' || $mode eq 'approx') {
      100        
3328 1274         3837 my $sec1 = $dmb->secs_since_1970($date1);
3329 1274         2839 my $sec2 = $dmb->secs_since_1970($date2);
3330 1274         1976 $ds = $sec2 - $sec1;
3331              
3332             {
3333 168     168   1386 no integer;
  168         386  
  168         849  
  1274         1644  
3334 1274         2682 $dh = int($ds/3600);
3335 1274         2007 $ds -= $dh*3600;
3336             }
3337 1274         1759 $dmn = int($ds/60);
3338 1274         1945 $ds -= $dmn*60;
3339             }
3340              
3341 1430 100 100     6657 if ($mode eq 'business' || $mode eq 'bsemi' || $mode eq 'bapprox') {
      100        
3342              
3343             # Make sure both are work days
3344              
3345 156         383 $date1 = $self->__nextprev_business_day(0,0,1,$date1);
3346 156         424 $date2 = $self->__nextprev_business_day(0,0,1,$date2);
3347              
3348 156         349 my($y1,$m1,$d1,$h1,$mn1,$s1) = @$date1;
3349 156         264 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         191 my $dir = 0;
3354 156 100       595 if ($y1 < $y2) {
    100          
    100          
    100          
    100          
    100          
3355 2         4 $dir = 1;
3356             } elsif ($y1 > $y2) {
3357 3         7 $dir = -1;
3358             } elsif ($m1 < $m2) {
3359 2         5 $dir = 1;
3360             } elsif ($m1 > $m2) {
3361 3         5 $dir = -1;
3362             } elsif ($d1 < $d2) {
3363 73         94 $dir = 1;
3364             } elsif ($d1 > $d2) {
3365 33         92 $dir = -1;
3366             }
3367              
3368             # Now do the day part (to get to the same day)
3369              
3370 156         184 $dd = 0;
3371 156         282 while ($dir) {
3372 456         542 ($y1,$m1,$d1) = @{ $dmb->calc_date_days([$y1,$m1,$d1],$dir) };
  456         1207  
3373 456 100       1148 $dd += $dir if ($self->__is_business_day([$y1,$m1,$d1,0,0,0],0));
3374 456 100 100     1871 $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         190 $dh = $h2-$h1;
3381 156         172 $dmn = $mn2-$mn1;
3382 156         219 $ds = $s2-$s1;
3383             }
3384              
3385 1430         5642 return [ $dy,$dm,$dw,$dd,$dh,$dmn,$ds ];
3386             }
3387              
3388 168     168   45148 no integer;
  168         397  
  168         707  
3389             sub _calc_date_delta {
3390 3178     3178   5082 my($self,$delta,$subtract) = @_;
3391 3178         7671 my $ret = $self->new_date();
3392              
3393 3178 50 33     12497 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       6053 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       6311 $subtract = 0 if (! $subtract);
3406 3178         3734 my @delta = @{ $$delta{'data'}{'delta'} };
  3178         7693  
3407 3178         4006 my @date = @{ $$self{'data'}{'date'} };
  3178         5830  
3408 3178 100       6733 my $business = ($$delta{'data'}{'mode'} eq 'business' ? 1 : 0);
3409 3178         4874 my $tz = $$self{'data'}{'tz'};
3410 3178         4487 my $isdst = $$self{'data'}{'isdst'};
3411              
3412             # We can't handle a delta longer than 10000 years
3413 3178         6082 my($dy,$dm,$dw,$dd,$dh,$dmn,$ds) = @delta;
3414 3178 50 33     24982 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         4 $$ret{'err'} = '[calc] Delta too large';
3422 2         9 return $ret;
3423             }
3424              
3425 3176         4499 my($err,$date2,$offset,$abbrev);
3426 3176         14066 ($err,$date2,$offset,$isdst,$abbrev) =
3427             $self->__calc_date_delta([@date],[@delta],$subtract,$business,$tz,$isdst);
3428              
3429 3176 100 66     17600 if (ref($date2) eq 'ARRAY' && ($$date2[0]<0 || $$date2[0]>9999)) {
    100 100        
3430 1         4 $$ret{'err'} = '[calc] Delta produces date outside valid range';
3431             } elsif ($err) {
3432 2         4 $$ret{'err'} = '[calc] Unable to perform calculation';
3433             } else {
3434 3173         5973 $$ret{'data'}{'set'} = 1;
3435 3173         4951 $$ret{'data'}{'date'} = $date2;
3436 3173         4670 $$ret{'data'}{'tz'} = $tz;
3437 3173         5181 $$ret{'data'}{'isdst'} = $isdst;
3438 3173         4365 $$ret{'data'}{'offset'}= $offset;
3439 3173         4500 $$ret{'data'}{'abb'} = $abbrev;
3440             }
3441 3176         20724 return $ret;
3442             }
3443 168     168   56823 use integer;
  168         369  
  168         666  
3444              
3445             sub __calc_date_delta {
3446 3308     3308   6888 my($self,$date,$delta,$subtract,$business,$tz,$isdst) = @_;
3447              
3448 3308         5962 my ($dy,$dm,$dw,$dd,$dh,$dmn,$ds) = @$delta;
3449 3308         6027 my @date = @$date;
3450              
3451 3308         6472 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       5620 if ($business) {
3462 75         89 $dd_exact = $dd;
3463 75         101 $dd_approx = 0;
3464              
3465 75 100 66     178 if ($subtract == 2 && ! $self->__is_business_day($date,1)) {
3466 2         5 return (1);
3467             }
3468              
3469             } else {
3470 3233         3846 $dd_exact = 0;
3471 3233         4173 $dd_approx = $dd;
3472             }
3473              
3474 3306 100 100     10590 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         6474 ($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       6457 ($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       3940 map { -1*$_ } ($dy,$dm,$dw,$dd_exact,$dd_approx,$dh,$dmn,$ds)
  288         395  
3507             if ($subtract);
3508 2108         5523 @$date2 = @date;
3509              
3510 2108 100 100     7461 if ($dy || $dm || $dw || $dd) {
    100 100        
      100        
3511 1867         7050 ($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         134 $date2 = $self->__nextprev_business_day(0,0,1,$date2);
3516             }
3517              
3518 2108 100 100     15629 ($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         10289 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   2850 my($self,$date,$delta,$business,$tz,$isdst) = @_;
3533 1198         2061 my $dmt = $$self{'tz'};
3534 1198         1781 my $dmb = $$dmt{'base'};
3535 1198         1551 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       2264 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         3183 my @tmp = @$date[0..2]; # [y,m,d]
3619 1198         2292 my @hms = @$date[3..5]; # [h,m,s]
3620 1198         2009 my $date1 = [@tmp];
3621              
3622 1198         3463 my $date2 = $dmb->_calc_date_ymwd($date1,$delta,1);
3623 1198         2615 my $tmp = $dmb->_calc_date_ymwd($date2,$delta);
3624 1198         3175 my $cmp = $self->_cmp_date($tmp,$date1);
3625              
3626 1198 100       3638 if ($cmp < 0) {
    100          
3627 8         16 while (1) {
3628 9         23 $date2 = $dmb->calc_date_days($date2,1);
3629 9         27 $tmp = $dmb->_calc_date_ymwd($date2,$delta);
3630 9         24 $cmp = $self->_cmp_date($tmp,$date1);
3631 9 100       33 if ($cmp < 0) {
    50          
3632 1         2 next;
3633             } elsif ($cmp > 0) {
3634 0         0 return (1);
3635             } else {
3636 8         13 last;
3637             }
3638             }
3639              
3640             } elsif ($cmp > 0) {
3641 2         5 while (1) {
3642 2         7 $date2 = $dmb->calc_date_days($date2,-1);
3643 2         6 $tmp = $dmb->_calc_date_ymwd($date2,$delta);
3644 2         5 $cmp = $self->_cmp_date($tmp,$date1);
3645 2 50       10 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         3343 @date2 = (@$date2,@hms);
3656             }
3657              
3658             # Make sure DATE2 is valid (within DST constraints) and
3659             # return it.
3660              
3661 1198         2053 my($date2,$abb,$off,$err);
3662 1198         3829 ($date2,$off,$isdst,$abb) = $self->_calc_date_check_dst([@date2],$tz,$isdst,0);
3663              
3664 1198 50       2739 return (1) if (! defined($date2));
3665 1198         3559 return (0,$date2,$off,$isdst,$abb);
3666             }
3667              
3668             sub _cmp_date {
3669 1209     1209   2340 my($self,$date0,$date1) = @_;
3670 1209   100     7168 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   3937 my($self,$date,$delta,$business,$tz,$isdst) = @_;
3679              
3680 1867         2879 my $dmt = $$self{'tz'};
3681 1867         2749 my $dmb = $$dmt{'base'};
3682 1867         3518 my($y,$m,$d,$h,$mn,$s) = @$date;
3683 1867         3291 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       3967 $y += $dy if ($dy);
3693 1867 100       4140 $dmb->_mod_add(-12,$dm,\$m,\$y) # -12 means 1-12 instead of 0-11
3694             if ($dm);
3695              
3696 1867         5095 my $dim = $dmb->days_in_month($y,$m);
3697 1867 100       3995 $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       3133 if ($business) {
3709 25 100       50 ($y,$m,$d) = @{ $dmb->calc_date_days([$y,$m,$d],$dw*7) } if ($dw);
  5         16  
3710             ($y,$m,$d,$h,$mn,$s) =
3711 25         33 @{ $self->__nextprev_business_day(0,0,1,[$y,$m,$d,$h,$mn,$s]) };
  25         81  
3712             } else {
3713 1842         2742 $dd += $dw*7;
3714             }
3715              
3716             #
3717             # Now do the day part. $dd is always 0 in business calculations.
3718             #
3719              
3720 1867 100       3265 if ($dd) {
3721 267         332 ($y,$m,$d) = @{ $dmb->calc_date_days([$y,$m,$d],$dd) };
  267         843  
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     10067 my $force = ( ($dd > 0 || $dw > 0 || $dm > 0 || $dy > 0) ? 1 : -1 );
3737 1867         2659 my($off,$abb);
3738 1867         7038 ($date,$off,$isdst,$abb) =
3739             $self->_calc_date_check_dst([$y,$m,$d,$h,$mn,$s],$tz,$isdst,$force);
3740 1867         6174 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   3475 my($self,$date,$delta,$business,$tz,$isdst) = @_;
3747 1466         2480 my $dmt = $$self{'tz'};
3748 1466         2383 my $dmb = $$dmt{'base'};
3749              
3750 1466 100       2656 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         130 my ($dd,$dh,$dmn,$ds) = @$delta;
3756 68         120 my ($y,$m,$d,$h,$mn,$s)= @$date;
3757 68         88 my ($hbeg,$mbeg,$sbeg) = @{ $$dmb{'data'}{'calc'}{'workdaybeg'} };
  68         149  
3758 68         101 my ($hend,$mend,$send) = @{ $$dmb{'data'}{'calc'}{'workdayend'} };
  68         138  
3759 68         108 my $bdlen = $$dmb{'data'}{'len'}{'bdlength'};
3760              
3761 168     168   185643 no integer;
  168         401  
  168         731  
3762 68         76 my $tmp;
3763 68         116 $ds += $dh*3600 + $dmn*60;
3764 68         124 $tmp = int($ds/$bdlen);
3765 68         78 $dd += $tmp;
3766 68         94 $ds -= $tmp*$bdlen;
3767 68         92 $dh = int($ds/3600);
3768 68         83 $ds -= $dh*3600;
3769 68         87 $dmn = int($ds/60);
3770 68         87 $ds -= $dmn*60;
3771 168     168   10047 use integer;
  168         355  
  168         630  
3772              
3773 68 100       120 if ($dd) {
3774 20         26 my $prev = 0;
3775 20 100       48 if ($dd < 1) {
3776 4         8 $prev = 1;
3777 4         8 $dd *= -1;
3778             }
3779              
3780             ($y,$m,$d,$h,$mn,$s) =
3781 20         36 @{ $self->__nextprev_business_day($prev,$dd,0,[$y,$m,$d,$h,$mn,$s]) };
  20         61  
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         245 $dmb->_mod_add(60,$ds,\$s,\$mn);
3793 68         164 $dmb->_mod_add(60,$dmn,\$mn,\$h);
3794 68         90 $h += $dh;
3795             # Note: it's possible that $h > 23 at this point or $h < 0
3796              
3797 68 100 66     799 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         414 my $t2 = $dmb->calc_time_time([$h,$mn,$s],[$hend,$mend,$send],1);
3805              
3806 20         41 while (1) {
3807 26         35 ($y,$m,$d) = @{ $dmb->calc_date_days([$y,$m,$d],1) };
  26         85  
3808 26 100       80 last if ($self->__is_business_day([$y,$m,$d,$h,$mn,$s]));
3809             }
3810              
3811 20         35 ($h,$mn,$s) = @{ $dmb->calc_time_time([$hbeg,$mbeg,$sbeg],$t2) };
  20         67  
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         83 my $t2 = $dmb->calc_time_time([$hbeg,$mbeg,$sbeg],[$h,$mn,$s],1);
3820              
3821 15         24 while (1) {
3822 17         25 ($y,$m,$d) = @{ $dmb->calc_date_days([$y,$m,$d],-1) };
  17         47  
3823 17 100       60 last if ($self->__is_business_day([$y,$m,$d,$h,$mn,$s]));
3824             }
3825              
3826 15         23 ($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     479 my $force = ( ($dd > 0 || $dh > 0 || $dmn > 0 || $ds > 0) ? 1 : -1 );
3832 68         99 my($off,$abb);
3833 68         198 ($date,$off,$isdst,$abb) =
3834             $self->_calc_date_check_dst([$y,$m,$d,$h,$mn,$s],$tz,$isdst,$force);
3835 68         218 return (0,$date,$off,$isdst,$abb);
3836              
3837             } else {
3838              
3839             # Convert to GTM
3840             # Do the calculation
3841             # Convert back
3842              
3843 1398         2786 my ($dd,$dh,$dm,$ds) = @$delta; # $dd is always 0
3844 1398         3318 my $del = [$dh,$dm,$ds];
3845 1398         2124 my ($err,$offset,$abbrev);
3846              
3847 1398         4626 ($err,$date,$offset,$isdst,$abbrev) =
3848             $dmt->_convert('__calc_date_delta_exact',$date,$tz,'GMT',$isdst);
3849              
3850 1398         3584 $date = $dmb->calc_date_time($date,$del,0);
3851 1398 100 66     5458 return($err,$date,$offset,$isdst,$abbrev)
3852             if ($$date[0] < 0 || $$date[0] > 9999);
3853              
3854 1397         3643 ($err,$date,$offset,$isdst,$abbrev) =
3855             $dmt->_convert('__calc_date_delta_exact',$date,'GMT',$tz,$isdst);
3856              
3857 1397         5372 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   6343 my($self,$date,$tz,$isdst,$force) = @_;
3880 3214         4613 my $dmt = $$self{'tz'};
3881 3214         4203 my $dmb = $$dmt{'base'};
3882 3214         4231 my($abb,$off,$err);
3883              
3884             # Try the date as is in both ISDST and 1-ISDST times
3885              
3886 3214         8230 my $per = $dmt->date_period($date,$tz,1,$isdst);
3887 3214 50       6540 if ($per) {
3888 3214         4797 $abb = $$per[4];
3889 3214         4006 $off = $$per[3];
3890 3214         8552 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 1780 my($self,$secs) = @_;
3937              
3938 8         18 my $dmt = $$self{'tz'};
3939 8         14 my $dmb = $$dmt{'base'};
3940              
3941 8 100       21 if (defined $secs) {
3942 3         10 my $date = $dmb->secs_since_1970($secs);
3943 3         4 my $err;
3944 3         10 ($err,$date) = $dmt->convert_from_gmt($date);
3945 3 50       8 return 1 if ($err);
3946 3         9 $self->set('date',$date);
3947 3         7 return 0;
3948             }
3949              
3950 5 50 33     24 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         19 my @date = $self->value('gmt');
3956 5         19 $secs = $dmb->secs_since_1970(\@date);
3957 5         13 return $secs;
3958             }
3959              
3960             sub week_of_year {
3961 27     27 1 97 my($self,$first) = @_;
3962 27 50 33     85 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         39 my $dmt = $$self{'tz'};
3968 27         33 my $dmb = $$dmt{'base'};
3969 27         30 my $date = $$self{'data'}{'date'};
3970 27         31 my $y = $$date[0];
3971              
3972 27         36 my($day,$dow,$doy,$f);
3973 27         58 $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       65 if ($dmb->_config('jan1week1')) {
3978 9         11 $day=1;
3979             } else {
3980 18         26 $day=4;
3981             }
3982 27         72 $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       56 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       48 $first -= 7 if ($first > $dow);
3995 27         30 $day -= ($dow-$first);
3996              
3997 27 100       42 return 0 if ($day>$doy); # Day is in last week of previous year
3998 25         65 return (($doy-$day)/7 + 1);
3999             }
4000              
4001             sub complete {
4002 7     7 1 29 my($self,$field) = @_;
4003 7 50 33     28 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       13 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     24 ! $$self{'data'}{'def'}[5]);
      100        
      66        
      66        
4014 3         8 return 0;
4015             }
4016              
4017 3 100       8 if ($field eq 'm') {
4018 1 50       5 return 1 if (! $$self{'data'}{'def'}[1]);
4019             }
4020              
4021 2 50       4 if ($field eq 'd') {
4022 0 0       0 return 1 if (! $$self{'data'}{'def'}[2]);
4023             }
4024              
4025 2 100       5 if ($field eq 'h') {
4026 1 50       4 return 1 if (! $$self{'data'}{'def'}[3]);
4027             }
4028              
4029 1 50       3 if ($field eq 'mn') {
4030 0 0       0 return 1 if (! $$self{'data'}{'def'}[4]);
4031             }
4032              
4033 1 50       4 if ($field eq 's') {
4034 1 50       3 return 1 if (! $$self{'data'}{'def'}[5]);
4035             }
4036 1         2 return 0;
4037             }
4038              
4039             sub convert {
4040 12     12 1 51 my($self,$zone) = @_;
4041 12 50 33     38 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         18 my $dmt = $$self{'tz'};
4046 12         14 my $dmb = $$dmt{'base'};
4047              
4048 12         34 my $zonename = $dmt->_zone($zone);
4049              
4050 12 50       23 if (! $zonename) {
4051 0         0 $$self{'err'} = "[convert] Unable to determine timezone: $zone";
4052 0         0 return 1;
4053             }
4054              
4055 12         15 my $date0 = $$self{'data'}{'date'};
4056 12         17 my $zone0 = $$self{'data'}{'tz'};
4057 12         15 my $isdst0 = $$self{'data'}{'isdst'};
4058              
4059 12         30 my($err,$date,$off,$isdst,$abb) = $dmt->convert($date0,$zone0,$zonename,$isdst0);
4060              
4061 12 50       25 if ($err) {
4062 0         0 $$self{'err'} = '[convert] Unable to convert date to new timezone';
4063 0         0 return 1;
4064             }
4065              
4066 12         31 $self->_init();
4067 12         17 $$self{'data'}{'date'} = $date;
4068 12         15 $$self{'data'}{'tz'} = $zonename;
4069 12         20 $$self{'data'}{'isdst'} = $isdst;
4070 12         14 $$self{'data'}{'offset'} = $off;
4071 12         15 $$self{'data'}{'abb'} = $abb;
4072 12         14 $$self{'data'}{'set'} = 1;
4073              
4074 12         28 return 0;
4075             }
4076              
4077             ########################################################################
4078             # BUSINESS DAY METHODS
4079              
4080             sub is_business_day {
4081 13     13 1 50 my($self,$checktime) = @_;
4082 13 50 33     65 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         18 my $date = $$self{'data'}{'date'};
4087 13         26 return $self->__is_business_day($date,$checktime);
4088             }
4089              
4090             sub __is_business_day {
4091 4515     4515   7530 my($self,$date,$checktime) = @_;
4092 4515         7108 my($y,$m,$d,$h,$mn,$s) = @$date;
4093              
4094 4515         6248 my $dmt = $$self{'tz'};
4095 4515         5668 my $dmb = $$dmt{'base'};
4096              
4097             # Return 0 if it's a weekend.
4098              
4099 4515         11689 my $dow = $dmb->day_of_week([$y,$m,$d]);
4100 4515 100 66     11665 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     7994 if ($checktime &&
4107             ! $dmb->_config('workday24hr')) {
4108 559         1529 my $t = $dmb->join('hms',[$h,$mn,$s]);
4109 559         1554 my $t0 = $dmb->join('hms',$$dmb{'data'}{'calc'}{'workdaybeg'});
4110 559         1387 my $t1 = $dmb->join('hms',$$dmb{'data'}{'calc'}{'workdayend'});
4111 559 100 100     2008 return 0 if ($t lt $t0 || $t gt $t1);
4112             }
4113              
4114             # Check for holidays
4115              
4116 3142 100       7741 if (! $$dmb{'data'}{'init_holidays'}) {
4117 1111         2751 $self->_holidays($y-1);
4118 1111         2161 $self->_holidays($y);
4119 1111         1781 $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     20633 exists $$dmb{'data'}{'holidays'}{'dates'}{$y+0}{$m+0}{$d+0});
      100        
      100        
4126              
4127 2557         7308 return 1;
4128             }
4129              
4130             sub list_holidays {
4131 84     84 1 26087 my($self,$y) = @_;
4132 84         152 my $dmt = $$self{'tz'};
4133 84         176 my $dmb = $$dmt{'base'};
4134              
4135 84 100 100     275 $y = $$self{'data'}{'date'}[0] if (! $y && $$self{'data'}{'set'} == 1);
4136 84 100       209 $y = $dmt->_now('y',1) if (! $y);
4137 84         333 $self->_holidays($y-1);
4138 84         233 $self->_holidays($y);
4139 84         226 $self->_holidays($y+1);
4140              
4141 84         180 my @ret;
4142 84         135 my @m = sort { $a <=> $b } keys %{ $$dmb{'data'}{'holidays'}{'dates'}{$y+0} };
  91         206  
  84         511  
4143 84         203 foreach my $m (@m) {
4144 130         194 my @d = sort { $a <=> $b } keys %{ $$dmb{'data'}{'holidays'}{'dates'}{$y+0}{$m} };
  37         119  
  130         595  
4145 130         261 foreach my $d (@d) {
4146 163         470 my $hol = $self->new_date();
4147 163         610 $hol->set('date',[$y,$m,$d,0,0,0]);
4148 163         377 push(@ret,$hol);
4149             }
4150             }
4151              
4152 84         387 return @ret;
4153             }
4154              
4155             sub holiday {
4156 33     33 1 136 my($self) = @_;
4157 33 50 33     161 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         56 my $dmt = $$self{'tz'};
4162 33         45 my $dmb = $$dmt{'base'};
4163              
4164 33         40 my($y,$m,$d) = @{ $$self{'data'}{'date'} };
  33         82  
4165 33         109 $self->_holidays($y-1);
4166 33         76 $self->_holidays($y);
4167 33         148 $self->_holidays($y+1);
4168              
4169 33 100 66     276 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         36 my @tmp = @{ $$dmb{'data'}{'holidays'}{'dates'}{$y+0}{$m+0}{$d+0} };
  23         77  
4173              
4174 23         44 foreach my $tmp (@tmp) {
4175 28 100       77 $tmp = '' if ($tmp =~ /DMunnamed/);
4176             }
4177              
4178 23 100       46 if (wantarray) {
4179 22 50       41 return () if (! @tmp);
4180 22         79 return @tmp;
4181             } else {
4182 1 50       3 return '' if (! @tmp);
4183 1         6 return $tmp[0];
4184             }
4185             }
4186 10         42 return undef;
4187             }
4188              
4189             sub next_business_day {
4190 12     12 1 50 my($self,$off,$checktime) = @_;
4191 12 50 33     41 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         14 my $date = $$self{'data'}{'date'};
4196              
4197 12         28 $date = $self->__nextprev_business_day(0,$off,$checktime,$date);
4198 12         28 $self->set('date',$date);
4199 12         23 return;
4200             }
4201              
4202             sub prev_business_day {
4203 12     12 1 47 my($self,$off,$checktime) = @_;
4204 12 50 33     39 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         20 my $date = $$self{'data'}{'date'};
4209              
4210 12         25 $date = $self->__nextprev_business_day(1,$off,$checktime,$date);
4211 12         26 $self->set('date',$date);
4212 12         20 return;
4213             }
4214              
4215             sub __nextprev_business_day {
4216 530     530   1148 my($self,$prev,$off,$checktime,$date) = @_;
4217 530         883 my($y,$m,$d,$h,$mn,$s) = @$date;
4218              
4219 530         758 my $dmt = $$self{'tz'};
4220 530         764 my $dmb = $$dmt{'base'};
4221              
4222             # Get day 0
4223              
4224 530         1521 while (! $self->__is_business_day([$y,$m,$d,$h,$mn,$s],$checktime)) {
4225 455 100       958 if ($checktime) {
4226             ($y,$m,$d,$h,$mn,$s) =
4227 244         271 @{ $self->__next_prev([$y,$m,$d,$h,$mn,$s],1,undef,0,
4228 244         826 $$dmb{'data'}{'calc'}{'workdaybeg'}) };
4229             } else {
4230             # Move forward 1 day
4231 211         244 ($y,$m,$d) = @{ $dmb->calc_date_days([$y,$m,$d],1) };
  211         565  
4232             }
4233             }
4234              
4235             # Move $off days into the future/past
4236              
4237 530         1309 while ($off > 0) {
4238 140         157 while (1) {
4239 221 100       370 if ($prev) {
4240             # Move backward 1 day
4241 92         99 ($y,$m,$d) = @{ $dmb->calc_date_days([$y,$m,$d],-1) };
  92         233  
4242             } else {
4243             # Move forward 1 day
4244 129         136 ($y,$m,$d) = @{ $dmb->calc_date_days([$y,$m,$d],1) };
  129         326  
4245             }
4246 221 100       562 last if ($self->__is_business_day([$y,$m,$d,$h,$mn,$s]));
4247             }
4248 140         313 $off--;
4249             }
4250              
4251 530         1635 return [$y,$m,$d,$h,$mn,$s];
4252             }
4253              
4254             sub nearest_business_day {
4255 6     6 1 25 my($self,$tomorrow) = @_;
4256 6 50 33     21 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         9 my $date = $$self{'data'}{'date'};
4262 6         13 $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       12 return if (! defined($date));
4268              
4269 2         5 $self->set('date',$date);
4270 2         4 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       13 return undef if ($self->__is_business_day($date,0));
4278              
4279 2         5 my $dmt = $$self{'tz'};
4280 2         4 my $dmb = $$dmt{'base'};
4281              
4282 2 50       6 $tomorrow = $dmb->_config('tomorrowfirst') if (! defined $tomorrow);
4283              
4284 2         2 my($a1,$a2);
4285 2 50       5 if ($tomorrow) {
4286 2         4 ($a1,$a2) = (1,-1);
4287             } else {
4288 0         0 ($a1,$a2) = (-1,1);
4289             }
4290              
4291 2         4 my ($y,$m,$d,$h,$mn,$s) = @$date;
4292 2         3 my ($y1,$m1,$d1) = ($y,$m,$d);
4293 2         4 my ($y2,$m2,$d2) = ($y,$m,$d);
4294              
4295 2         2 while (1) {
4296 2         3 ($y1,$m1,$d1) = @{ $dmb->calc_date_days([$y1,$m1,$d1],$a1) };
  2         7  
4297 2 100       7 if ($self->__is_business_day([$y1,$m1,$d1,$h,$mn,$s],0)) {
4298 1         2 ($y,$m,$d) = ($y1,$m1,$d1);
4299 1         2 last;
4300             }
4301 1         2 ($y2,$m2,$d2) = @{ $dmb->calc_date_days([$y2,$m2,$d2],$a2) };
  1         4  
4302 1 50       12 if ($self->__is_business_day([$y2,$m2,$d2,$h,$mn,$s],0)) {
4303 1         3 ($y,$m,$d) = ($y2,$m2,$d2);
4304 1         2 last;
4305             }
4306             }
4307              
4308 2         7 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   90 my($self) = @_;
4316 34         76 my $dmt = $$self{'tz'};
4317 34         71 my $dmb = $$dmt{'base'};
4318              
4319 34         109 $$dmb{'data'}{'holidays'}{'init'} = 1;
4320              
4321             # Go through all of the strings from the config file.
4322             #
4323 34         65 my (@str) = @{ $$dmb{'data'}{'sections'}{'holidays'} };
  34         252  
4324 34         140 $$dmb{'data'}{'holidays'}{'defs'} = [];
4325              
4326             # Keep track of the holiday names
4327 34         82 my $unnamed = 0;
4328              
4329             LINE:
4330 34         147 while (@str) {
4331 207         389 my($string) = shift(@str);
4332 207         313 my($name) = shift(@str);
4333 207 100       409 if (! $name) {
4334 14         38 $unnamed++;
4335 14         42 $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         572 my $date = $self->new_date();
4343 207         485 my $err = $date->parse_date($string);
4344              
4345 207 100       420 if (! $err) {
4346 105         150 my($y,$m,$d) = @{ $$date{'data'}{'date'} };
  105         232  
4347              
4348 105 100       252 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         122 push(@{ $$dmb{'data'}{'holidays'}{'defs'} },$name,$string);
  92         257  
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       89 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         76 $$dmb{'data'}{'holidays'}{'yhols'}{$y+0}{$name} = [$y,$m,$d];
4370 13         66 $$dmb{'data'}{'holidays'}{'hols'}{$name}{$y+0} = [$y,$m,$d];
4371              
4372 13 50       102 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         78 $$dmb{'data'}{'holidays'}{'dates'}{$y+0}{$m+0}{$d+0} = [ $name ];
4376             }
4377             }
4378 105         565 next LINE;
4379             }
4380 102         439 $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         274 my $recur = $self->new_recur();
4386 102         300 $err = $recur->parse($string);
4387 102 50       247 if (! $err) {
4388 102         133 push(@{ $$dmb{'data'}{'holidays'}{'defs'} },$name,$recur);
  102         396  
4389 102         829 next LINE;
4390             }
4391 0         0 $recur->err(1);
4392              
4393 0         0 carp "WARNING: invalid holiday description: $string";
4394             }
4395 34         104 return;
4396             }
4397              
4398             # Make sure that holidays are done for a given year.
4399             #
4400             sub _holidays {
4401 3711     3711   4745 my($self,$year) = @_;
4402              
4403 3711         4326 my $dmt = $$self{'tz'};
4404 3711         4119 my $dmb = $$dmt{'base'};
4405              
4406 3711 100       7924 return if ($$dmb{'data'}{'holidays'}{'ydone'}{$year+0});
4407 265 100       836 $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         431 my @hol = @{ $$dmb{'data'}{'holidays'}{'defs'} };
  265         1599  
4415              
4416 265         702 my $beg = "$year-01-01-00:00:00";
4417 265         540 my $end = "$year-12-31-23:59:59";
4418              
4419             # Get the date for each holiday.
4420              
4421 265         482 $$dmb{'data'}{'init_holidays'} = 1;
4422 265         793 $$dmb{'data'}{'tmpnow'} = [$year,1,1,0,0,0];
4423              
4424             HOLIDAY:
4425 265         590 while (@hol) {
4426              
4427 1374         2561 my $name = shift(@hol);
4428 1374         2129 my $obj = shift(@hol);
4429              
4430             # Each holiday only gets defined once per year
4431 1374 100       4423 next if (exists $$dmb{'data'}{'holidays'}{'hols'}{$name}{$year+0});
4432              
4433 1350 100       2728 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         2744 $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         952 my @dates;
4445 766 100 66     2262 if ($obj->start() && $obj->end()) {
4446 84         277 @dates = $obj->dates();
4447             } else {
4448 682         2067 @dates = $obj->dates($beg,$end,1);
4449             }
4450              
4451 766         2029 foreach my $date (@dates) {
4452 878         1145 my($y,$m,$d) = @{ $$date{'data'}{'date'} };
  878         2402  
4453              
4454 878         4133 $$dmb{'data'}{'holidays'}{'yhols'}{$year+0}{$name} = [$y,$m,$d];
4455 878         2937 $$dmb{'data'}{'holidays'}{'hols'}{$name}{$year+0} = [$y,$m,$d];
4456              
4457 878 100       3470 if (exists $$dmb{'data'}{'holidays'}{'dates'}{$y+0}{$m+0}{$d+0}) {
4458 213         263 push @{ $$dmb{'data'}{'holidays'}{'dates'}{$y+0}{$m+0}{$d+0} },$name;
  213         820  
4459             } else {
4460 665         3901 $$dmb{'data'}{'holidays'}{'dates'}{$y+0}{$m+0}{$d+0} = [$name];
4461             }
4462             }
4463              
4464             } else {
4465 584         1587 my $date = $self->new_date();
4466 584         1756 $date->parse_date($obj);
4467 584         845 my($y,$m,$d) = @{ $$date{'data'}{'date'} };
  584         1382  
4468              
4469 584         2344 $$dmb{'data'}{'holidays'}{'yhols'}{$year+0}{$name} = [$y,$m,$d];
4470 584         1771 $$dmb{'data'}{'holidays'}{'hols'}{$name}{$year+0} = [$y,$m,$d];
4471              
4472 584 100       2238 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         60  
4474             } else {
4475 576         4115 $$dmb{'data'}{'holidays'}{'dates'}{$y+0}{$m+0}{$d+0} = [$name];
4476             }
4477             }
4478             }
4479              
4480 265         585 $$dmb{'data'}{'init_holidays'} = 0;
4481 265         613 $$dmb{'data'}{'tmpnow'} = [];
4482 265         737 $$dmb{'data'}{'holidays'}{'ydone'}{$year+0} = 1;
4483 265         595 return;
4484             }
4485              
4486             ########################################################################
4487             # PRINTF METHOD
4488              
4489 0         0 BEGIN {
4490 168     168   846819 my %pad_0 = map { $_,1 } qw ( Y m d H M S I j G W L U );
  2016         3777  
4491 168         508 my %pad_sp = map { $_,1 } qw ( y f e k i );
  840         1484  
4492 168         492 my %hr = map { $_,1 } qw ( H k I i );
  672         1238  
4493 168         468 my %dow = map { $_,1 } qw ( v a A w );
  672         1452  
4494 168         498 my %num = map { $_,1 } qw ( Y m d H M S y f e k I i j G W L U );
  2856         381981  
4495              
4496             sub printf {
4497 47     47 1 218 my($self,@in) = @_;
4498 47 50 33     190 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         67 my $dmt = $$self{'tz'};
4504 47         58 my $dmb = $$dmt{'base'};
4505              
4506 47         53 my($y,$m,$d,$h,$mn,$s) = @{ $$self{'data'}{'date'} };
  47         96  
4507              
4508 47         55 my(@out);
4509 47         68 foreach my $in (@in) {
4510 49         52 my $out = '';
4511 49         82 while ($in) {
4512 559 50       730 last if ($in eq '%');
4513              
4514             # Everything up to the first '%'
4515              
4516 559 100       1382 if ($in =~ s/^([^%]+)//) {
4517 230         355 $out .= $1;
4518 230         349 next;
4519             }
4520              
4521             # Extended formats: %<...>
4522              
4523 329 100       514 if ($in =~ s/^%<([^>]+)>//) {
4524 20         34 my $f = $1;
4525 20         20 my $val;
4526              
4527 20 100       80 if ($f =~ /^a=([1-7])$/) {
    100          
    100          
    100          
    100          
    100          
    50          
4528 3         8 $val = $$dmb{'data'}{'wordlist'}{'day_abb'}[$1-1];
4529              
4530             } elsif ($f =~ /^v=([1-7])$/) {
4531 3         9 $val = $$dmb{'data'}{'wordlist'}{'day_char'}[$1-1];
4532              
4533             } elsif ($f =~ /^A=([1-7])$/) {
4534 3         8 $val = $$dmb{'data'}{'wordlist'}{'day_name'}[$1-1];
4535              
4536             } elsif ($f =~ /^p=([1-2])$/) {
4537 2         7 $val = $$dmb{'data'}{'wordlist'}{'ampm'}[$1-1];
4538              
4539             } elsif ($f =~ /^b=(0?[1-9]|1[0-2])$/) {
4540 3         8 $val = $$dmb{'data'}{'wordlist'}{'month_abb'}[$1-1];
4541              
4542             } elsif ($f =~ /^B=(0?[1-9]|1[0-2])$/) {
4543 3         9 $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         9 $val = $$dmb{'data'}{'wordlist'}{'nth'}[$1-1];
4547              
4548             } else {
4549 0         0 $val = '%<' . $1 . '>';
4550             }
4551 20         25 $out .= $val;
4552 20         36 next;
4553             }
4554              
4555             # Normals one-character formats
4556              
4557 309         651 $in =~ s/^%(.)//s;
4558 309         478 my $f = $1;
4559              
4560 309 100       520 if (exists $$self{'data'}{'f'}{$f}) {
4561 27         38 $out .= $$self{'data'}{'f'}{$f};
4562 27         51 next;
4563             }
4564              
4565 282         315 my ($val,$pad,$len,$dow);
4566              
4567 282 100       417 if (exists $pad_0{$f}) {
4568 133         159 $pad = '0';
4569             }
4570              
4571 282 100       394 if (exists $pad_sp{$f}) {
4572 23         27 $pad = ' ';
4573             }
4574              
4575 282 100 100     688 if ($f eq 'G' || $f eq 'W') {
4576 5         18 my($yy,$ww) = $dmb->_week_of_year(1,[$y,$m,$d]);
4577 5 100       11 if ($f eq 'G') {
4578 2         3 $val = $yy;
4579 2         3 $len = 4;
4580             } else {
4581 3         6 $val = $ww;
4582 3         6 $len = 2;
4583             }
4584             }
4585              
4586 282 100 100     642 if ($f eq 'L' || $f eq 'U') {
4587 3         16 my($yy,$ww) = $dmb->_week_of_year(7,[$y,$m,$d]);
4588 3 100       10 if ($f eq 'L') {
4589 1         2 $val = $yy;
4590 1         2 $len = 4;
4591             } else {
4592 2         3 $val = $ww;
4593 2         3 $len = 2;
4594             }
4595             }
4596              
4597 282 100 100     589 if ($f eq 'Y' || $f eq 'y') {
4598 28         38 $val = $y;
4599 28         33 $len = 4;
4600             }
4601              
4602 282 100 100     582 if ($f eq 'm' || $f eq 'f') {
4603 8         10 $val = $m;
4604 8         10 $len = 2;
4605             }
4606              
4607 282 100 100     588 if ($f eq 'd' || $f eq 'e') {
4608 29         39 $val = $d;
4609 29         34 $len = 2;
4610             }
4611              
4612 282 100       367 if ($f eq 'j') {
4613 3         14 $val = $dmb->day_of_year([$y,$m,$d]);
4614 3         8 $len = 3;
4615             }
4616              
4617              
4618 282 100       398 if (exists $hr{$f}) {
4619 34         49 $val = $h;
4620 34 100 100     294 if ($f eq 'I' || $f eq 'i') {
4621 7 100       14 $val -= 12 if ($val > 12);
4622 7 50       15 $val = 12 if ($val == 0);
4623             }
4624 34         39 $len = 2;
4625             }
4626              
4627 282 100       374 if ($f eq 'M') {
4628 24         30 $val = $mn;
4629 24         33 $len = 2;
4630             }
4631              
4632 282 100       358 if ($f eq 'S') {
4633 22         27 $val = $s;
4634 22         23 $len = 2;
4635             }
4636              
4637 282 100       403 if (exists $dow{$f}) {
4638 26         88 $dow = $dmb->day_of_week([$y,$m,$d]);
4639             }
4640              
4641             ###
4642              
4643 282 100 100     1193 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         263 while (length($val) < $len) {
4645 106         209 $val = "$pad$val";
4646             }
4647              
4648 156 100       230 $val = substr($val,2,2) if ($f eq 'y');
4649              
4650             } elsif ($f eq 'b' || $f eq 'h') {
4651 24         52 $val = $$dmb{'data'}{'wordlist'}{'month_abb'}[$m-1];
4652              
4653             } elsif ($f eq 'B') {
4654 3         11 $val = $$dmb{'data'}{'wordlist'}{'month_name'}[$m-1];
4655              
4656             } elsif ($f eq 'v') {
4657 2         7 $val = $$dmb{'data'}{'wordlist'}{'day_char'}[$dow-1];
4658              
4659             } elsif ($f eq 'a') {
4660 18         64 $val = $$dmb{'data'}{'wordlist'}{'day_abb'}[$dow-1];
4661              
4662             } elsif ($f eq 'A') {
4663 3         10 $val = $$dmb{'data'}{'wordlist'}{'day_name'}[$dow-1];
4664              
4665             } elsif ($f eq 'w') {
4666 3         5 $val = $dow;
4667              
4668             } elsif ($f eq 'p') {
4669 4 100       12 my $i = ($h >= 12 ? 1 : 0);
4670 4         12 $val = $$dmb{'data'}{'wordlist'}{'ampm'}[$i];
4671              
4672             } elsif ($f eq 'Z') {
4673 19         29 $val = $$self{'data'}{'abb'};
4674              
4675             } elsif ($f eq 'N') {
4676 4         8 my $off = $$self{'data'}{'offset'};
4677 4         11 $val = $dmb->join('offset',$off);
4678              
4679             } elsif ($f eq 'z') {
4680 4         9 my $off = $$self{'data'}{'offset'};
4681 4         15 $val = $dmb->join('offset',$off);
4682 4         15 $val =~ s/://g;
4683 4         13 $val =~ s/00$//;
4684              
4685             } elsif ($f eq 'E') {
4686 2         9 $val = $$dmb{'data'}{'wordlist'}{'nth_dom'}[$d-1];
4687              
4688             } elsif ($f eq 's') {
4689 2         10 $val = $self->secs_since_1970_GMT();
4690              
4691             } elsif ($f eq 'o') {
4692 2         178 my $date2 = $self->new_date();
4693 2         7 $date2->parse('1970-01-01 00:00:00');
4694 2         9 my $delta = $date2->calc($self);
4695 2         10 $val = $delta->printf('%sys');
4696              
4697             } elsif ($f eq 'l') {
4698 4         14 my $d0 = $self->new_date();
4699 4         9 my $d1 = $self->new_date();
4700 4         10 $d0->parse('-0:6:0:0:0:0:0'); # 6 months ago
4701 4         17 $d1->parse('+0:6:0:0:0:0:0'); # in 6 months
4702 4         12 $d0 = $d0->value();
4703 4         17 $d1 = $d1->value();
4704 4         18 my $date = $self->value();
4705 4 100 100     16 if ($date lt $d0 || $date ge $d1) {
4706 2         4 $in = '%b %e %Y' . $in;
4707             } else {
4708 2         5 $in = '%b %e %H:%M' . $in;
4709             }
4710 4         8 $val = '';
4711              
4712             } elsif ($f eq 'c') {
4713 1         3 $in = '%a %b %e %H:%M:%S %Y' . $in;
4714 1         2 $val = '';
4715              
4716             } elsif ($f eq 'C' || $f eq 'u') {
4717 2         5 $in = '%a %b %e %H:%M:%S %Z %Y' . $in;
4718 2         4 $val = '';
4719              
4720             } elsif ($f eq 'g') {
4721 13         32 $in = '%a, %d %b %Y %H:%M:%S %Z' . $in;
4722 13         23 $val = '';
4723              
4724             } elsif ($f eq 'D') {
4725 2         6 $in = '%m/%d/%y' . $in;
4726 2         4 $val = '';
4727              
4728             } elsif ($f eq 'r') {
4729 1         3 $in = '%I:%M:%S %p' . $in;
4730 1         3 $val = '';
4731              
4732             } elsif ($f eq 'R') {
4733 1         3 $in = '%H:%M' . $in;
4734 1         3 $val = '';
4735              
4736             } elsif ($f eq 'T' || $f eq 'X') {
4737 2         5 $in = '%H:%M:%S' . $in;
4738 2         3 $val = '';
4739              
4740             } elsif ($f eq 'V') {
4741 1         3 $in = '%m%d%H%M%y' . $in;
4742 1         3 $val = '';
4743              
4744             } elsif ($f eq 'Q') {
4745 1         3 $in = '%Y%m%d' . $in;
4746 1         4 $val = '';
4747              
4748             } elsif ($f eq 'q') {
4749 1         3 $in = '%Y%m%d%H%M%S' . $in;
4750 1         1 $val = '';
4751              
4752             } elsif ($f eq 'P') {
4753 1         3 $in = '%Y%m%d%H:%M:%S' . $in;
4754 1         2 $val = '';
4755              
4756             } elsif ($f eq 'O') {
4757 1         3 $in = '%Y-%m-%dT%H:%M:%S' . $in;
4758 1         2 $val = '';
4759              
4760             } elsif ($f eq 'F') {
4761 1         3 $in = '%A, %B %e, %Y' . $in;
4762 1         2 $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       10 if ($dmb->_config('dateformat') eq 'US') {
4770 1         3 $in = '%m/%d/%y' . $in;
4771             } else {
4772 1         3 $in = '%d/%m/%y' . $in;
4773             }
4774 2         4 $val = '';
4775              
4776             } elsif ($f eq 'J') {
4777 1         3 $in = '%G-W%W-%w' . $in;
4778 1         3 $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       444 if ($val ne '') {
4791 246         459 $$self{'data'}{'f'}{$f} = $val;
4792 246         436 $out .= $val;
4793             }
4794             }
4795 49         114 push(@out,$out);
4796             }
4797              
4798 47 100       98 if (wantarray) {
    50          
4799 35         166 return @out;
4800             } elsif (@out == 1) {
4801 12         39 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 97 my($self,@args) = @_;
4813 21 50 33     97 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         45 my $dmt = $$self{'tz'};
4818 21         33 my $dmb = $$dmt{'base'};
4819              
4820             # Arguments
4821              
4822 21         43 my($date,$day,$format);
4823 21 100 100     83 if (@args && $args[$#args] eq 'dates') {
4824 9         13 pop(@args);
4825 9         17 $format = 'dates';
4826             } else {
4827 12         24 $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         8 $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         34 my($beg,$end);
4842 21 100       52 if ($date) {
    100          
4843 4         4 $beg = $self;
4844 4         5 $end = $date;
4845             } elsif ($day) {
4846 2         8 $beg = $self->new_date();
4847 2         6 $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         9 $end->set('date',[$y,$m,$d,23,59,59]);
4851             } else {
4852 15         26 $beg = $self;
4853 15         24 $end = $self;
4854             }
4855              
4856 21 50       54 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         50 my($y0) = $beg->value();
4865 21         50 my($y1) = $end->value();
4866 21         57 foreach my $y ($y0..$y1) {
4867 21         79 $self->_events_year($y);
4868             }
4869              
4870 21         39 my @events = ();
4871 21         73 foreach my $i (keys %{ $$dmb{'data'}{'events'} }) {
  21         91  
4872 231         356 my $event = $$dmb{'data'}{'events'}{$i};
4873 231         304 my $type = $$event{'type'};
4874 231         324 my $name = $$event{'name'};
4875              
4876 231 100 100     563 if ($type eq 'specified') {
    100          
    50          
4877 129         192 my $d0 = $$dmb{'data'}{'events'}{$i}{'beg'};
4878 129         167 my $d1 = $$dmb{'data'}{'events'}{$i}{'end'};
4879 129         269 push @events,[$d0,$d1,$name];
4880              
4881             } elsif ($type eq 'ym' || $type eq 'date') {
4882 52         100 foreach my $y ($y0..$y1) {
4883 52 50       133 if (exists $$dmb{'data'}{'events'}{$i}{$y}) {
4884 52         64 my($d0,$d1) = @{ $$dmb{'data'}{'events'}{$i}{$y} };
  52         112  
4885 52         148 push @events,[$d0,$d1,$name];
4886             }
4887             }
4888              
4889             } elsif ($type eq 'recur') {
4890 50         83 my $rec = $$dmb{'data'}{'events'}{$i}{'recur'};
4891 50         81 my $del = $$dmb{'data'}{'events'}{$i}{'delta'};
4892 50         154 my @d = $rec->dates($beg,$end);
4893 50         136 foreach my $d0 (@d) {
4894 4         11 my $d1 = $d0->calc($del);
4895 4         17 push @events,[$d0,$d1,$name];
4896             }
4897             }
4898             }
4899              
4900             # Next we need to see which ones apply.
4901              
4902 21         81 my @tmp;
4903 21         47 foreach my $e (@events) {
4904 185         340 my($d0,$d1,$name) = @$e;
4905              
4906 185 100 100     320 push(@tmp,$e) if ($beg->cmp($d1) != 1 &&
4907             $end->cmp($d0) != -1);
4908             }
4909              
4910             # Now format them...
4911              
4912 21 100       71 if ($format eq 'std') {
    50          
4913 12 50 100     146 @events = sort { $$a[0]->cmp($$b[0]) ||
  19         52  
4914             $$a[1]->cmp($$b[1]) ||
4915             $$a[2] cmp $$b[2] } @tmp;
4916              
4917             } elsif ($format eq 'dates') {
4918 9         33 my $p1s = $self->new_delta();
4919 9         37 $p1s->parse('+0:0:0:0:0:0:1');
4920              
4921 9         36 @events = ();
4922 9         16 my (@tmp2);
4923 9         21 foreach my $e (@tmp) {
4924 22         39 my $name = $$e[2];
4925 22 100       50 if ($$e[0]->cmp($beg) == -1) {
4926             # Event begins before the start
4927 9         22 push(@tmp2,[$beg,'+',$name]);
4928             } else {
4929 13         32 push(@tmp2,[$$e[0],'+',$name]);
4930             }
4931              
4932 22         54 my $d1 = $$e[1]->calc($p1s);
4933              
4934 22 100       55 if ($d1->cmp($end) == -1) {
4935             # Event ends before the end
4936 12         35 push(@tmp2,[$d1,'-',$name]);
4937             }
4938             }
4939              
4940 9 50       28 return () if (! @tmp2);
4941 9 50 100     46 @tmp2 = sort { $$a[0]->cmp($$b[0]) ||
  53         101  
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         18 my $d = $tmp2[0]->[0];
4950              
4951 9 100       25 if ($beg->cmp($d) != 0) {
4952 1         3 push(@events,[$beg]);
4953             }
4954              
4955 9         16 my %e;
4956 9         13 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         70 my $d0 = $tmp2[0]->[0];
4964 50 100       81 if ($d->cmp($d0) == 0) {
4965 34         48 my $e = shift(@tmp2);
4966 34         46 my $op = $$e[1];
4967 34         39 my $n = $$e[2];
4968 34 100       63 if ($op eq '+') {
4969 22         44 $e{$n} = 1;
4970             } else {
4971 12         20 delete $e{$n};
4972             }
4973              
4974 34 100       97 next if (@tmp2);
4975             }
4976              
4977             # We need to store the existing %e.
4978              
4979 25         74 my @n = sort keys %e;
4980 25         53 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       77 last if (! @tmp2);
4986 16         31 $d = $tmp2[0]->[0];
4987             }
4988             }
4989              
4990 21         135 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   46 my($self,$y) = @_;
4997 21         36 my $dmt = $$self{'tz'};
4998 21         40 my $dmb = $$dmt{'base'};
4999 21         59 my $tz = $dmt->_now('tz',1);
5000 21 50       66 return if (exists $$dmb{'data'}{'eventyears'}{$y});
5001 21 100       64 $self->_event_objs() if (! $$dmb{'data'}{'eventobjs'});
5002              
5003 21         58 my $d = $self->new_date();
5004 21         119 $d->config('forcedate',"${y}-01-01-00:00:00,$tz");
5005              
5006 21         73 my $hrM1 = $d->new_delta();
5007 21         94 $hrM1->set('delta',[0,0,0,0,0,59,59]);
5008              
5009 21         71 my $dayM1 = $d->new_delta();
5010 21         90 $dayM1->set('delta',[0,0,0,0,23,59,59]);
5011              
5012 21         37 foreach my $i (keys %{ $$dmb{'data'}{'events'} }) {
  21         116  
5013 231         318 my $event = $$dmb{'data'}{'events'}{$i};
5014 231         314 my $type = $$event{'type'};
5015              
5016 231 100       439 if ($type eq 'ym') {
    100          
5017 26         56 my $beg = $$event{'beg'};
5018 26         45 my $end = $$event{'end'};
5019 26         68 my $d0 = $d->new_date();
5020 26         75 $d0->parse_date($beg);
5021 26         87 $d0->set('time',[0,0,0]);
5022              
5023 26         47 my $d1;
5024 26 100       49 if ($end) {
5025 13         56 $d1 = $d0->new_date();
5026 13         42 $d1->parse_date($end);
5027 13         48 $d1->set('time',[23,59,59]);
5028             } else {
5029 13         33 $d1 = $d0->calc($dayM1);
5030             }
5031 26         247 $$dmb{'data'}{'events'}{$i}{$y} = [ $d0,$d1 ];
5032              
5033             } elsif ($type eq 'date') {
5034 26         43 my $beg = $$event{'beg'};
5035 26         40 my $end = $$event{'end'};
5036 26         35 my $del = $$event{'delta'};
5037 26         74 my $d0 = $d->new_date();
5038 26         60 $d0->parse($beg);
5039              
5040 26         37 my $d1;
5041 26 50       75 if ($end) {
    50          
5042 0         0 $d1 = $d0->new_date();
5043 0         0 $d1->parse($end);
5044             } elsif ($del) {
5045 26         58 $d1 = $d0->calc($del);
5046             } else {
5047 0         0 $d1 = $d0->calc($hrM1);
5048             }
5049 26         253 $$dmb{'data'}{'events'}{$i}{$y} = [ $d0,$d1 ];
5050             }
5051             }
5052              
5053 21         224 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   7 my($self) = @_;
5060 3         7 my $dmt = $$self{'tz'};
5061 3         5 my $dmb = $$dmt{'base'};
5062             # Only parse once.
5063 3         6 $$dmb{'data'}{'eventobjs'} = 1;
5064              
5065 3         14 my $hrM1 = $self->new_delta();
5066 3         17 $hrM1->set('delta',[0,0,0,0,0,59,59]);
5067              
5068 3         10 my $M1 = $self->new_delta();
5069 3         13 $M1->set('delta',[0,0,0,0,0,0,-1]);
5070              
5071 3         6 my @tmp = @{ $$dmb{'data'}{'sections'}{'events'} };
  3         32  
5072 3         7 my $i = 0;
5073 3         9 while (@tmp) {
5074 33         56 my $string = shift(@tmp);
5075 33         50 my $name = shift(@tmp);
5076 33         173 my @event = split(/\s*;\s*/,$string);
5077              
5078 33 100       80 if ($#event == 0) {
    50          
5079              
5080             # YMD/YM
5081              
5082 15         48 my $d1 = $self->new_date();
5083 15         44 my $err = $d1->parse_date($event[0]);
5084 15 100       35 if (! $err) {
5085 6 100       19 if ($$d1{'data'}{'def'}[0] eq '') {
5086             # YM
5087 2         14 $$dmb{'data'}{'events'}{$i++} = { 'type' => 'ym',
5088             'name' => $name,
5089             'beg' => $event[0] };
5090             } else {
5091             # YMD
5092 4         15 my $d2 = $d1->new_date();
5093 4         14 my ($y,$m,$d) = $d1->value();
5094 4         15 $d1->set('time',[0,0,0]);
5095 4         15 $d2->set('date',[$y,$m,$d,23,59,59]);
5096 4         25 $$dmb{'data'}{'events'}{$i++} = { 'type' => 'specified',
5097             'name' => $name,
5098             'beg' => $d1,
5099             'end' => $d2 };
5100             }
5101 6         43 next;
5102             }
5103              
5104             # Date
5105              
5106 9         29 $err = $d1->parse($event[0]);
5107 9 100       27 if (! $err) {
5108 5 100       19 if ($$d1{'data'}{'def'}[0] eq '') {
5109             # Date (no year)
5110 2         21 $$dmb{'data'}{'events'}{$i++} = { 'type' => 'date',
5111             'name' => $name,
5112             'beg' => $event[0],
5113             'delta' => $hrM1
5114             };
5115             } else {
5116             # Date (year)
5117 3         13 my $d2 = $d1->calc($hrM1);
5118 3         20 $$dmb{'data'}{'events'}{$i++} = { 'type' => 'specified',
5119             'name' => $name,
5120             'beg' => $d1,
5121             'end' => $d2
5122             };
5123             }
5124 5         26 next;
5125             }
5126              
5127             # Recur
5128              
5129 4         29 my $r = $self->new_recur();
5130 4         19 $err = $r->parse($event[0]);
5131 4 50       10 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         16 my @d = $r->dates();
5138 4 50       8 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         56 $$dmb{'data'}{'events'}{$i++} = { 'type' => 'recur',
5149             'name' => $name,
5150             'recur' => $r,
5151             'delta' => $hrM1
5152             };
5153             }
5154              
5155             } elsif ($#event == 1) {
5156 18         37 my($o1,$o2) = @event;
5157              
5158             # YMD;YMD
5159             # YM;YM
5160              
5161 18         50 my $d1 = $self->new_date();
5162 18         39 my $err = $d1->parse_date($o1);
5163 18 100       36 if (! $err) {
5164 9         30 my $d2 = $self->new_date();
5165 9         19 $err = $d2->parse_date($o2);
5166 9 50       41 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       23 if ($$d1{'data'}{'def'}[0] eq '') {
5177             # YM;YM
5178 2         17 $$dmb{'data'}{'events'}{$i++} = { 'type' => 'ym',
5179             'name' => $name,
5180             'beg' => $o1,
5181             'end' => $o2
5182             };
5183             } else {
5184             # YMD;YMD
5185 7         48 $d1->set('time',[0,0,0]);
5186 7         25 $d2->set('time',[23,59,59]);
5187 7         40 $$dmb{'data'}{'events'}{$i++} = { 'type' => 'specified',
5188             'name' => $name,
5189             'beg' => $d1,
5190             'end' => $d2 };
5191             }
5192 9         40 next;
5193             }
5194              
5195             # Date;Date
5196             # Date;Delta
5197              
5198 9         23 $err = $d1->parse($o1);
5199 9 100       24 if (! $err) {
5200              
5201 6         19 my $d2 = $self->new_date();
5202 6         14 $err = $d2->parse($o2,'nodelta');
5203              
5204 6 100       17 if (! $err) {
5205             # Date;Date
5206 2 50       11 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       7 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         13 $$dmb{'data'}{'events'}{$i++} = { 'type' => 'specified',
5223             'name' => $name,
5224             'beg' => $d1,
5225             'end' => $d2
5226             };
5227             }
5228 2         7 next;
5229             }
5230              
5231             # Date;Delta
5232 4         17 my $del = $self->new_delta();
5233 4         15 $err = $del->parse($o2);
5234              
5235 4 50       9 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         14 $del = $del->calc($M1);
5242 4 100       13 if ($$d1{'data'}{'def'}[0] eq '') {
5243             # Date (no year)
5244 2         13 $$dmb{'data'}{'events'}{$i++} = { 'type' => 'date',
5245             'name' => $name,
5246             'beg' => $o1,
5247             'delta' => $del
5248             };
5249             } else {
5250             # Date (year)
5251 2         9 $d2 = $d1->calc($del);
5252 2         15 $$dmb{'data'}{'events'}{$i++} = { 'type' => 'specified',
5253             'name' => $name,
5254             'beg' => $d1,
5255             'end' => $d2
5256             };
5257             }
5258 4         26 next;
5259             }
5260              
5261             # Recur;Delta
5262              
5263 3         12 my $r = $self->new_recur();
5264 3         13 $err = $r->parse($o1);
5265              
5266 3         14 my $del = $self->new_delta();
5267 3 50       11 if (! $err) {
5268 3         11 $err = $del->parse($o2);
5269             }
5270              
5271 3 50       11 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         14 $del = $del->calc($M1);
5279 3         15 my @d = $r->dates();
5280 3 50       9 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         36 $$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         15 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: