File Coverage

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


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