File Coverage

lib/Date/Manip/Date.pm
Criterion Covered Total %
statement 2298 2659 86.4
branch 1132 1490 75.9
condition 489 657 74.4
subroutine 88 89 98.8
pod 24 24 100.0
total 4031 4919 81.9


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