File Coverage

lib/Date/Manip/Base.pm
Criterion Covered Total %
statement 1084 1187 91.3
branch 477 592 80.5
condition 207 312 66.3
subroutine 111 112 99.1
pod 21 21 100.0
total 1900 2224 85.4


line stmt bran cond sub pod time code
1             package Date::Manip::Base;
2             # Copyright (c) 1995-2026 Sullivan Beck. All rights reserved.
3             # This program is free software; you can redistribute it and/or modify it
4             # under the same terms as Perl itself.
5              
6             ###############################################################################
7             # Any routine that starts with an underscore (_) is NOT intended for
8             # public use. They are for internal use in the the Date::Manip
9             # modules and are subject to change without warning or notice.
10             #
11             # ABSOLUTELY NO USER SUPPORT IS OFFERED FOR THESE ROUTINES!
12             ###############################################################################
13              
14             require 5.010000;
15 170     170   900 use strict;
  170         262  
  170         4915  
16 170     170   540 use warnings;
  170         232  
  170         5697  
17 170     170   581 use integer;
  170         204  
  170         857  
18 170     170   2692 use utf8;
  170         399  
  170         752  
19 170     170   2715 use Carp;
  170         260  
  170         7161  
20             #use re 'debug';
21              
22 170     170   593 use Date::Manip::Obj;
  170         217  
  170         2263  
23 170     170   65979 use Date::Manip::TZ_Base;
  170         452  
  170         8993  
24             our @ISA = qw(Date::Manip::Obj Date::Manip::TZ_Base);
25              
26 170     170   677 use Encode qw(encode_utf8 from_to find_encoding decode _utf8_off _utf8_on is_utf8);
  170         191  
  170         115523  
27             require Date::Manip::Lang::index;
28              
29             our $VERSION;
30             $VERSION='6.99';
31 170     170   1474 END { undef $VERSION; }
32              
33             ###############################################################################
34             # BASE METHODS
35             ###############################################################################
36              
37             sub _init {
38 505     505   1209 my($self) = @_;
39              
40 505         2556 $self->_init_cache();
41 505         2807 $self->_init_language();
42 505         2266 $self->_init_config();
43 505         2019 $self->_init_events();
44 505         1586 $self->_init_holidays();
45 505         1521 $self->_init_now();
46              
47 505         1112 return;
48             }
49              
50             # The base object has some config-independant information which is
51             # always reused, and only needs to be initialized once.
52             sub _init_cache {
53 505     505   1151 my($self) = @_;
54 505 50       3513 return if (exists $$self{'cache'}{'init'});
55 505         2233 $$self{'cache'}{'init'} = 1;
56              
57             # ly => {Y} = 0/1 1 if it is a leap year
58             # ds1_mon => {Y}{M} = N days since 1BC for Y/M/1
59             # dow_mon => {Y}{M} = DOW day of week of Y/M/1
60              
61 505         1641 $$self{'cache'}{'ly'} = {};
62 505         1736 $$self{'cache'}{'ds1_mon'} = {};
63 505         1198 $$self{'cache'}{'dow_mon'} = {};
64              
65 505         1040 return;
66             }
67              
68             # Config dependent data. Needs to be reset every time the config is reset.
69             sub _init_data {
70 506     506   1115 my($self,$force) = @_;
71 506 100 66     1871 return if (exists $$self{'data'}{'calc'} && ! $force);
72              
73 505         1722 $$self{'data'}{'calc'} = {}; # Calculated values
74              
75 505         787 return;
76             }
77              
78             # Initializes config dependent data
79             sub _init_config {
80 506     506   1213 my($self,$force) = @_;
81 506 50 66     1986 return if (exists $$self{'data'}{'sections'}{'conf'} && ! $force);
82 506         2097 $self->_init_data();
83              
84             #
85             # Set config defaults
86             #
87              
88 506         11957 $$self{'data'}{'sections'}{'conf'} =
89             {
90             # Reset config, holiday lists, or events lists
91              
92             'defaults' => '',
93             'eraseholidays' => '',
94             'eraseevents' => '',
95              
96             # Which language to use when parsing dates.
97              
98             'language' => '',
99              
100             # 12/10 = Dec 10 (US) or Oct 12 (anything else)
101              
102             'dateformat' => '',
103              
104             # Define the work week (1=monday, 7=sunday)
105             #
106             # These have to be predefined to avoid a bootstrap issue, but
107             # the true defaults are defined below.
108              
109             'workweekbeg' => 1,
110             'workweekend' => 5,
111              
112             # If non-nil, a work day is treated as 24 hours long
113             # (WorkDayBeg/WorkDayEnd ignored)
114              
115             'workday24hr' => '',
116              
117             # Start and end time of the work day (any time format allowed,
118             # seconds ignored). If the defaults change, be sure to change
119             # the starting value of bdlength above.
120              
121             'workdaybeg' => '',
122             'workdayend' => '',
123              
124             # 2 digit years fall into the 100 year period given by [ CURR-N,
125             # CURR+(99-N) ] where N is 0-99. Default behavior is 89, but
126             # other useful numbers might be 0 (forced to be this year or
127             # later) and 99 (forced to be this year or earlier). It can
128             # also be set to 'c' (current century) or 'cNN' (i.e. c18
129             # forces the year to bet 1800-1899). Also accepts the form
130             # cNNNN to give the 100 year period NNNN to NNNN+99.
131              
132             'yytoyyyy' => '',
133              
134             # First day of the week (1=monday, 7=sunday). ISO 8601 says
135             # monday.
136              
137             'firstday' => '',
138              
139             # If this is a value 'jan1' to 'jan7', then it means that the first
140             # week of the year contains that date. If the value is 'dow1' to
141             # 'dow7', then the first week of the year is the one that contains
142             # the first occurence of the DoW given. If the value is 'firstday',
143             # then the first week of the year is the one that contains the first
144             # occurence of the DoW given in the FirstDay config variable.
145             #
146             # The default ISO 8601 definition is the first week is the one which
147             # contains Jan 4 (so at least 4 of the days).
148              
149             'week1ofyear' => '',
150              
151             # Date::Manip printable format
152             # 0 = YYYYMMDDHH:MN:SS
153             # 1 = YYYYHHMMDDHHMNSS
154             # 2 = YYYY-MM-DD-HH:MN:SS
155              
156             'printable' => '',
157              
158             # If 'today' is a holiday, we look either to 'tomorrow' or
159             # 'yesterday' for the nearest business day. By default, we'll
160             # always look 'tomorrow' first.
161              
162             'tomorrowfirst' => 1,
163              
164             # Used to set the current date/time/timezone.
165              
166             'forcedate' => 0,
167             'setdate' => 0,
168              
169             # Use this to set the default range of the recurrence.
170              
171             'recurrange' => '',
172             'maxrecurattempts' => 100,
173              
174             # Use this to set the default time.
175              
176             'defaulttime' => 'midnight',
177              
178             # Whether or not to use a period as a time separator.
179              
180             'periodtimesep' => 0,
181              
182             # How to parse mmm#### strings
183              
184             'format_mmmyyyy' => '',
185              
186             # Whether to use the default printf formats or the POSIX ones.
187              
188             'use_posix_printf' => 0,
189              
190             # *** DEPRECATED 7.0 ***
191              
192             'tz' => '',
193             'jan1week1' => '',
194             };
195              
196             #
197             # Calculate delta field lengths
198             #
199              
200             # non-business
201 506         3078 $$self{'data'}{'len'}{'standard'} =
202             { 'yl' => 31556952, # 365.2425 * 24 * 3600
203             'ml' => 2629746, # yl / 12
204             'wl' => 604800, # 6 * 24 * 3600
205             'dl' => 86400, # 24 * 3600
206             };
207 506         2190 $self->_calc_workweek();
208              
209             #
210             # Initialize some config variables that do some additional work.
211             #
212              
213 506         3265 $self->_config_var('workday24hr', 1);
214 506         1595 $self->_config_var('workdaybeg', '08:00:00');
215 506         1661 $self->_config_var('workdayend', '17:00:00');
216 506         1547 $self->_config_var('workday24hr', 0);
217 506         1301 $self->_config_var('dateformat', 'US');
218 506         1310 $self->_config_var('yytoyyyy', 89);
219 506         1314 $self->_config_var('printable', 0);
220 506         1259 $self->_config_var('firstday', 1);
221 506         1248 $self->_config_var('week1ofyear', 'jan4');
222 506         1277 $self->_config_var('workweekbeg', 1);
223 506         1391 $self->_config_var('workweekend', 5);
224 506         1260 $self->_config_var('language', 'english');
225 506         3066 $self->_config_var('recurrange', 'none');
226 506         1455 $self->_config_var('maxrecurattempts', 100);
227 506         1483 $self->_config_var('defaulttime', 'midnight');
228              
229             # Set OS specific defaults
230              
231 506         1804 my $os = $self->_os();
232              
233 506         1116 return;
234             }
235              
236             sub _calc_workweek {
237 1546     1546   4056 my($self,$beg,$end) = @_;
238              
239 1546 100       6183 $beg = $self->_config('workweekbeg') if (! $beg);
240 1546 100       3772 $end = $self->_config('workweekend') if (! $end);
241              
242 1546         3230 $$self{'data'}{'len'}{'workweek'} = $end - $beg + 1;
243              
244 1546         1958 return;
245             }
246              
247             sub _calc_bdlength {
248 1556     1556   2410 my($self) = @_;
249              
250 1556         1812 my @beg = @{ $$self{'data'}{'calc'}{'workdaybeg'} };
  1556         3348  
251 1556         1863 my @end = @{ $$self{'data'}{'calc'}{'workdayend'} };
  1556         3425  
252              
253 1556         4326 $$self{'data'}{'len'}{'bdlength'} =
254             ($end[0]-$beg[0])*3600 + ($end[1]-$beg[1])*60 + ($end[2]-$beg[2]);
255              
256 1556         2668 return;
257             }
258              
259             sub _init_business_length {
260 2596     2596   3364 my($self) = @_;
261              
262 170     170   1134 no integer;
  170         233  
  170         987  
263 2596         3809 my $x = $$self{'data'}{'len'}{'workweek'};
264 2596         4762 my $y_to_d = $x/7 * 365.2425;
265 2596         3478 my $d_to_s = $$self{'data'}{'len'}{'bdlength'};
266 2596         3019 my $w_to_d = $x;
267              
268 2596         10579 $$self{'data'}{'len'}{'business'} = { 'yl' => $y_to_d * $d_to_s,
269             'ml' => $y_to_d * $d_to_s / 12,
270             'wl' => $w_to_d * $d_to_s,
271             'dl' => $d_to_s,
272             };
273              
274 2596         3605 return;
275             }
276              
277             # Events and holidays are reset only when they are read in.
278             sub _init_events {
279 519     519   1151 my($self,$force) = @_;
280 519 50 66     1744 return if (exists $$self{'data'}{'events'} && ! $force);
281              
282             # {data}{sections}{events} = [ STRING, EVENT_NAME, ... ]
283             #
284             # {data}{events}{I}{type} = TYPE
285             # {name} = NAME
286             # TYPE: specified An event with a start/end date (only parsed once)
287             # {beg} = DATE_OBJECT
288             # {end} = DATE_OBJECT
289             # TYPE: ym
290             # {beg} = YM_STRING
291             # {end} = YM_STRING (only for YM;YM)
292             # {YEAR} = [ DATE_OBJECT, DATE_OBJECT ]
293             # TYPE: date An event specified by a date string and delta
294             # {beg} = DATE_STRING
295             # {end} = DATE_STRING (only for Date;Date)
296             # {delta} = DELTA_OBJECT (only for Date;Delta)
297             # {YEAR} = [ DATE_OBJECT, DATE_OBJECT ]
298             # TYPE: recur
299             # {recur} = RECUR_OBJECT
300             # {delta} = DELTA_OBJECT
301             #
302             # {data}{eventyears}{YEAR} = 0/1
303             # {data}{eventobjs} = 0/1
304              
305 519         2121 $$self{'data'}{'events'} = {};
306 519         1218 $$self{'data'}{'sections'}{'events'} = [];
307 519         1282 $$self{'data'}{'eventyears'} = {};
308 519         1195 $$self{'data'}{'eventobjs'} = 0;
309              
310 519         763 return;
311             }
312              
313             sub _init_holidays {
314 523     523   1173 my($self,$force) = @_;
315 523 50 66     1901 return if (exists $$self{'data'}{'holidays'} && ! $force);
316              
317             # {data}{sections}{holidays} = [ STRING, HOLIDAY_NAME, ... ]
318             #
319             # {data}{holidays}{init} = 1 if holidays have been initialized
320             # {ydone} = { Y => 1 }
321             # {yhols} = { Y => NAME => [Y,M,D] }
322             # {hols} = { NAME => Y => [Y,M,D] }
323             # {dates} = { Y => M => D => NAME }
324             # {defs} = [ NAME DEF NAME DEF ... ]
325             # NAME is the name of a holiday (it will
326             # be 'DMunnamed I' for the Ith unnamed
327             # holiday)
328             # DEF is a string or a Recur
329             # {data}{init_holidays} = 1 if currently initializing holidays
330              
331 523         2052 $$self{'data'}{'holidays'} = {};
332 523         1170 $$self{'data'}{'sections'}{'holidays'} = [];
333 523         1128 $$self{'data'}{'init_holidays'} = 0;
334              
335 523         792 return;
336             }
337              
338             sub _init_now {
339 505     505   960 my($self) = @_;
340              
341             # {'data'}{'now'} = {
342             # date => [Y,M,D,H,MN,S] now
343             # isdst => ISDST
344             # offset => [H,MN,S]
345             # abb => ABBREV
346             #
347             # force => 0/1 SetDate/ForceDate information
348             # set => 0/1
349             # setsecs => SECS time (secs since epoch) when
350             # SetDate was called
351             # setdate => [Y,M,D,H,MN,S] date (IN GMT) we're calling
352             # now when SetDate was called
353             #
354             # tz => ZONE timezone we're working in
355             # systz => ZONE timezone of the system
356             # }
357             #
358              
359 505         1043 $$self{'data'}{'now'} = {};
360 505         2019 $$self{'data'}{'now'}{'force'} = 0;
361 505         1218 $$self{'data'}{'now'}{'set'} = 0;
362 505         1336 $$self{'data'}{'tmpnow'} = [];
363              
364 505         726 return;
365             }
366              
367             # Language information only needs to be initialized if the language changes.
368             sub _init_language {
369 1044     1044   2267 my($self,$force) = @_;
370 1044 50 66     4230 return if (exists $$self{'data'}{'lang'} && ! $force);
371              
372 1044         3114 $$self{'data'}{'lang'} = {}; # Current language info
373 1044         7632 $$self{'data'}{'rx'} = {}; # Regexps generated from language
374 1044         2646 $$self{'data'}{'words'} = {}; # Types of words in the language
375 1044         2411 $$self{'data'}{'wordval'} = {}; # Value of words in the language
376              
377 1044         1481 return;
378             }
379              
380             ###############################################################################
381             # MAIN METHODS
382             ###############################################################################
383              
384             # Use an algorithm from Calendar FAQ (except that I subtract 305 to get
385             # Jan 1, 0001 = day #1).
386             #
387             sub days_since_1BC {
388 12393     12393 1 27422 my($self,$arg) = @_;
389              
390 12393 100       15048 if (ref($arg)) {
391 7664         9901 my($y,$m,$d) = @$arg;
392 7664         8767 $m = ($m + 9) % 12;
393 7664         8443 $y = $y - $m/10;
394 7664         17685 return 365*$y + $y/4 - $y/100 + $y/400 + ($m*306 + 5)/10 + ($d - 1) - 305;
395             } else {
396 4729         5111 my $g = $arg + 305;
397 170     170   80655 no integer;
  170         237  
  170         614  
398 4729         8488 my $y = int((10000*$g + 14780)/3652425);
399 170     170   6092 use integer;
  170         255  
  170         550  
400 4729         6890 my $ddd = $g - (365*$y + $y/4 - $y/100 + $y/400);
401 4729 100       6736 if ($ddd < 0) {
402 6         8 $y = $y - 1;
403 6         11 $ddd = $g - (365*$y + $y/4 - $y/100 + $y/400);
404             }
405 4729         5606 my $mi = (100*$ddd + 52)/3060;
406 4729         5289 my $mm = ($mi + 2) % 12 + 1;
407 4729         8708 $y = $y + ($mi + 2)/12;
408 4729         5601 my $dd = $ddd - ($mi*306 + 5)/10 + 1;
409 4729         7695 return [$y, $mm, $dd];
410             }
411             }
412              
413             # Algorithm from the Calendar FAQ
414             #
415             sub day_of_week {
416 10622     10622 1 24368 my($self,$date) = @_;
417 10622         14441 my($y,$m,$d) = @$date;
418              
419 10622         12509 my $a = (14-$m)/12;
420 10622         11670 $y = $y-$a;
421 10622         11692 $m = $m + 12*$a - 2;
422 10622         16956 my $dow = ($d + $y + $y/4 - $y/100 + $y/400 + (31*$m)/12) % 7;
423 10622 100       15279 $dow = 7 if ($dow==0);
424 10622         15301 return $dow;
425             }
426              
427             sub leapyear {
428 3793     3793 1 8204 my($self,$y) = @_;
429 3793 100 100     14323 return 1 if ( ( ($y % 4 == 0) and ($y % 100 != 0) ) or
      100        
430             $y % 400 == 0 );
431 2424         4077 return 0;
432             }
433              
434             sub days_in_year {
435 380     380 1 4351 my($self,$y) = @_;
436 380 100       728 return ($self->leapyear($y) ? 366 : 365);
437             }
438              
439             # Uses algorithm from:
440             # http://www.dispersiondesign.com/articles/time/number_of_days_in_a_month
441             #
442             sub days_in_month {
443 37204     37204 1 71737 my($self,$y,$m) = @_;
444 37204 100       60928 if (! $m) {
    100          
445 2 100       8 return (31,29,31,30, 31,30,31,31, 30,31,30,31) if ($self->leapyear($y));
446 1         8 return (31,28,31,30, 31,30,31,31, 30,31,30,31);
447              
448             } elsif ($m == 2) {
449 2904         5925 return 28 + $self->leapyear($y);
450              
451             } else {
452 34298         54615 return 31 - ($m-1) % 7 % 2;
453             }
454             }
455              
456             {
457             # DinM = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
458             my(@doy_days) = ( [0, 31, 59, 90,120,151,181,212,243,273,304,334,365],
459             [0, 31, 60, 91,121,152,182,213,244,274,305,335,366],
460             );
461              
462              
463             sub day_of_year {
464 698     698 1 60376 my($self,@args) = @_;
465 170     170   61758 no integer;
  170         265  
  170         1783  
466 698         1019 my($n,$ly,$tmp,$remain,$day,$y,$m,$d,$h,$mn,$s,$time);
467              
468 698 100       1023 if (@args == 2) {
469             # $date = day_of_year($y,$day);
470              
471 265         338 ($y,$tmp) = @args;
472              
473 265         446 $ly = $self->leapyear($y);
474 265 100       620 $time = 1 if ($tmp =~ /\./);
475 265         320 $n = int($tmp);
476 265         335 $remain = $tmp - $n;
477              
478             # Calculate the month and the day
479 265         463 for ($m=1; $m<=12; $m++) {
480 1065 100       1750 last if ($n<=($doy_days[$ly][$m]));
481             }
482 265         360 $d = $n-($doy_days[$ly][$m-1]);
483 265 100       883 return [$y,$m,$d] if (! $time);
484              
485             # Calculate the hours, minutes, and seconds into the day.
486              
487 9         14 $s = $remain * 86400;
488 9         14 $mn = int($s/60);
489 9         14 $s = $s - ($mn*60);
490 9 100       42 $s = sprintf('%0.2f',$s) if ("$s" ne int($s));
491 9         14 $h = int($mn/60);
492 9         13 $mn = $mn % 60;
493              
494 9         32 return [$y,$m,$d,$h,$mn,$s];
495              
496             } else {
497 433         483 ($y,$m,$d,$h,$mn,$s) = @{ $args[0] };
  433         734  
498              
499 433 100       969 $ly = ($m > 2 ? $self->leapyear($y) : 0);
500 433         828 $day = ($doy_days[$ly][$m-1]+$d);
501              
502 433 100       1050 return $day if (! defined $h);
503              
504 3         12 $day += ($h*3600 + $mn*60 + $s)/86400;
505 3         5 return $day;
506             }
507             }
508             }
509              
510             # Can be the nth DoW of year or month (if $m given). Returns undef if
511             # the date doesn't exists (i.e. 5th Sunday in a month with only 4).
512             #
513             sub nth_day_of_week {
514 997     997 1 17701 my($self,$y,$n,$dow,$m) = @_;
515 997         1122 $y += 0;
516 997 100       1513 $m = ($m ? $m+0 : 0);
517              
518             # $d is the current DoM (if $m) or DoY
519             # $max is the max value allowed for $d
520             # $ddow is the DoW of $d
521              
522 997         1166 my($d,$max,$ddow);
523              
524 997 100       1215 if ($m) {
525 913         1292 $max = $self->days_in_month($y,$m);
526 913 100       1334 $d = ($n<0 ? $max : 1);
527 913         1692 $ddow = $self->day_of_week([$y,$m,$d]);
528             } else {
529 84         151 $max = $self->days_in_year($y);
530 84 50       168 $d = ($n<0 ? $max : 1);
531 84 50       145 if ($n<0) {
532 0         0 $d = $max;
533 0         0 $ddow = $self->day_of_week([$y,12,31]);
534             } else {
535 84         87 $d = 1;
536 84         182 $ddow = $self->day_of_week([$y,1,1]);
537             }
538             }
539              
540             # Find the first occurrence of $dow on or after $d (if $n>0)
541             # or the last occurrence of $dow on or before $d (if ($n<0);
542              
543 997 100       1606 if ($dow < $ddow) {
544 550         662 $d += 7 - ($ddow-$dow);
545             } else {
546 447         582 $d += ($dow-$ddow);
547             }
548 997 100       1534 $d -= 7 if ($d > $max);
549              
550             # Find the nth occurrence of $dow
551              
552 997 100       1333 if ($n > 1) {
    100          
553 847         1055 $d += 7*($n-1);
554 847 50       1309 return undef if ($d > $max);
555             } elsif ($n < -1) {
556 2         3 $d -= 7*(-1*$n-1);
557 2 50       4 return undef if ($d < 1);
558             }
559              
560             # Return the date
561              
562 997 100       1334 if ($m) {
563 913         1686 return [$y,$m,$d];
564             }
565 84         124 return $self->day_of_year($y,$d);
566             }
567              
568             {
569             # Integer arithmetic doesn't work due to the size of the numbers.
570 170     170   81509 no integer;
  170         448  
  170         733  
571             # my $sec_70 =($self->days_since_1BC([1970,1,1])-1)*24*3600;
572             my $sec_70 = 62135596800;
573              
574             # Using 'global' variables saves 4%
575             my($y,$m,$d,$h,$mn,$s,$sec,$sec_0,$tmp);
576             sub secs_since_1970 {
577 2560     2560 1 12736 my($self,$arg) = @_;
578              
579 2560 100       3485 if (ref($arg)) {
580 2555         3941 ($y,$m,$d,$h,$mn,$s) = @$arg;
581 2555         5178 $sec_0 = ($self->days_since_1BC([$y,$m,$d])-1)*24*3600 + $h*3600 +
582             $mn*60 + $s;
583 2555         3346 $sec = $sec_0 - $sec_70;
584 2555         3533 return $sec;
585              
586             } else {
587 5         8 ($sec) = $arg;
588 5         10 $sec_0 = $sec_70 + $sec;
589 5         30 $tmp = int($sec_0/24/3600)+1;
590 5         16 my $ymd = $self->days_since_1BC($tmp);
591 5         11 ($y,$m,$d) = @$ymd;
592 5         10 $sec_0 -= ($tmp-1)*24*3600;
593 5         7 $h = int($sec_0/3600);
594 5         5 $sec_0 -= $h*3600;
595 5         28 $mn = int($sec_0/60);
596 5         9 $s = $sec_0 - $mn*60;
597 5         14 return [$y,$m,$d,$h,$mn,$s];
598             }
599             }
600             }
601              
602             sub check {
603 15365     15365 1 35322 my($self,$date) = @_;
604 15365         26160 my($y,$m,$d,$h,$mn,$s) = @$date;
605              
606 15365 100 66     37186 return 0 if (! $self->check_time([$h,$mn,$s]) ||
      66        
      33        
      66        
607             $y<1 || $y>9999 ||
608             $m<1 || $m>12);
609              
610 15354         34896 my $days = $self->days_in_month($y,$m);
611              
612 15354 100 66     40352 return 0 if ($d<1 || $d>$days);
613 15350         28669 return 1;
614             }
615              
616             sub check_time {
617 15429     15429 1 19798 my($self,$hms) = @_;
618 15429         22014 my($h,$mn,$s) = @$hms;
619              
620 15429 100 66     149265 return 0 if ("$h:$mn:$s" !~ /^\d\d?:\d\d?:\d\d?$/o ||
      66        
      66        
      66        
      66        
      66        
621             $h > 24 || $mn > 59 || $s > 59 ||
622             ($h == 24 && ($mn || $s)));
623 15425         83303 return 1;
624             }
625              
626             sub week1_day1 {
627 735     735 1 105508 my($self,$year) = @_;
628 735         2010 my $firstday = $self->_config('firstday');
629 735         1528 my $week1ofyear = $self->_config('week1ofyear');
630 735         1536 $self->_week1_day1($year,$firstday,$week1ofyear);
631             }
632             sub _week1_day1 {
633 1400     1400   2498 my($self,$year,$firstday,$week1ofyear) = @_;
634             return $$self{'cache'}{'week1day1'}{$firstday}{$week1ofyear}{$year}
635 1400 100       4574 if (exists $$self{'cache'}{'week1day1'}{$firstday}{$week1ofyear}{$year});
636              
637 981         1469 my($y,$m,$d,$firstdow);
638              
639             # Get the starting date.
640              
641 981 100       2854 if ($week1ofyear =~ /^jan([1-7])$/) {
642             # First week contains 1/D/YYYY where D is $1
643 581         1429 ($y,$m,$d) = ($year,1,$1);
644             } else {
645             # First week contains the first DOW given by $1 or firstday
646             # We'll start at Jan 1.
647 400 100       1406 if ($week1ofyear =~ /^dow([1-7])$/) {
648 343         839 $firstdow = $1;
649             } else {
650 57         112 $firstdow = $firstday;
651             }
652              
653             # Start at Jan 1 and move forward to the first DOW
654 400         729 ($y,$m,$d) = ($year,1,1);
655             }
656              
657 981         3027 my $dow = $self->day_of_week([$y,$m,$d]);
658              
659             # If we're looking for the first occurence of a DOW, move forward to it
660             # (counting today).
661              
662 981 100       2254 if ($firstdow) {
663 400         4214 my $forward = $firstdow - $dow;
664 400 100       821 $forward += 7 if ($forward < 0);
665 400         532 $d += $forward;
666 400         549 $dow = $firstdow;
667             }
668              
669             # Go back to the previous (counting today) $firstday
670              
671 981         1248 my $backward = $dow - $firstday;
672 981 100       1736 $backward += 7 if ($backward < 0);
673 981         1144 $d -= $backward;
674 981 100       1595 if ($d<1) {
675 419         599 $y--;
676 419         512 $m = 12;
677 419         484 $d += 31;
678             }
679              
680 981         3309 $$self{'cache'}{'week1day1'}{$firstday}{$week1ofyear}{$year} = [ $y,$m,$d ];
681 981         3809 return [$y,$m,$d];
682             }
683              
684             sub weeks_in_year {
685 352     352 1 3084 my($self,$y) = @_;
686 352         683 my $firstday = $self->_config('firstday');
687 352         553 my $week1ofyear = $self->_config('week1ofyear');
688             return $$self{'cache'}{'wiy'}{$firstday}{$week1ofyear}{$y}
689 352 100       1278 if (exists $$self{'cache'}{'wiy'}{$firstday}{$week1ofyear}{$y});
690              
691             # Get the week1 day1 dates for this year and the next one.
692 121         140 my ($y1,$m1,$d1) = @{ $self->_week1_day1($y,$firstday,$week1ofyear) };
  121         227  
693 121         150 my ($y2,$m2,$d2) = @{ $self->_week1_day1($y+1,$firstday,$week1ofyear) };
  121         224  
694              
695             # Calculate the number of days between them.
696 121         283 my $diy = $self->days_in_year($y);
697 121 100       214 if ($y1 < $y) {
698 65         83 $diy += (32-$d1);
699             } else {
700 56         82 $diy -= ($d1-1);
701             }
702 121 100       202 if ($y2 < $y+1) {
703 63         177 $diy -= (32-$d2);
704             } else {
705 58         73 $diy += ($d2-1);
706             }
707              
708 121         152 $diy = $diy/7;
709 121         253 $$self{'cache'}{'wiy'}{$firstday}{$week1ofyear}{$y} = $diy;
710 121         258 return $diy;
711             }
712              
713             sub week_of_year {
714 552     552 1 7430 my($self,@args) = @_;
715              
716 552         1075 my $firstday = $self->_config('firstday');
717 552         877 my $week1ofyear = $self->_config('week1ofyear');
718 552         1109 $self->_week_of_year($firstday,$week1ofyear,@args);
719             }
720             sub _week_of_year {
721 600     600   1043 my($self,$firstday,$week1ofyear,@args) = @_;
722              
723 600 100       959 if ($#args == 1) {
724             # (y,m,d) = week_of_year(y,w)
725 325         490 my($year,$w) = @args;
726              
727             return $$self{'cache'}{'woy'}{$firstday}{$week1ofyear}{$year}{$w}
728 325 100       1431 if (exists $$self{'cache'}{'woy'}{$firstday}{$week1ofyear}{$year}{$w});
729              
730 148         336 my $ymd = $self->_week1_day1($year,$firstday,$week1ofyear);
731 148 100       484 $ymd = $self->calc_date_days($ymd,($w-1)*7) if ($w > 1);
732              
733 148         423 $$self{'cache'}{'woy'}{$firstday}{$week1ofyear}{$year}{$w} = $ymd;
734 148         426 return $ymd;
735             }
736              
737             # (y,w) = week_of_year([y,m,d])
738 275         286 my($y,$m,$d) = @{ $args[0] };
  275         494  
739              
740             # Get the first day of the first week. If the date is before that,
741             # it's the last week of last year.
742              
743 275         331 my($y0,$m0,$d0) = @{ $self->_week1_day1($y,$firstday,$week1ofyear) };
  275         540  
744 275 100 100     1042 if ($y0==$y && $m==1 && $d<$d0) {
      100        
745 19         51 return($y-1,$self->weeks_in_year($y-1));
746             }
747              
748             # Otherwise, we'll figure out how many days are between the two and
749             # divide by 7 to figure out how many weeks in it is.
750              
751 256         699 my $n = $self->day_of_year([$y,$m,$d]);
752 256 100       457 if ($y0<$y) {
753 106         146 $n += (32-$d0);
754             } else {
755 150         184 $n -= ($d0-1);
756             }
757 256         368 my $w = 1+int(($n-1)/7);
758              
759             # Make sure we're not into the first week of next year.
760              
761 256 100       435 if ($w>$self->weeks_in_year($y)) {
762 8         25 return($y+1,1);
763             }
764 248         671 return($y,$w);
765             }
766              
767             ###############################################################################
768             # CALC METHODS
769             ###############################################################################
770              
771             sub calc_date_date {
772 18     18 1 65052 my($self,$date0,$date1) = @_;
773              
774             # Order them so date0 < date1
775             # If $minus = 1, then the delta is negative
776              
777 18         21 my $minus = 0;
778 18         77 my $cmp = $self->cmp($date0,$date1);
779              
780 18 100       52 if ($cmp == 0) {
    100          
781 4         10 return [0,0,0];
782              
783             } elsif ($cmp == 1) {
784 7         9 $minus = 1;
785 7         5 my $tmp = $date1;
786 7         6 $date1 = $date0;
787 7         7 $date0 = $tmp;
788             }
789              
790 14         27 my($y0,$m0,$d0,$h0,$mn0,$s0) = @$date0;
791 14         21 my($y1,$m1,$d1,$h1,$mn1,$s1) = @$date1;
792              
793 14 100 100     38 my $sameday = ($y0 == $y1 && $m0 == $m1 && $d0 == $d1 ? 1 : 0);
794              
795             # Handle the various cases.
796              
797 14         17 my($dh,$dm,$ds);
798 14 100       23 if ($sameday) {
799 4         3 ($dh,$dm,$ds) = @{ $self->_calc_hms_hms([$h0,$mn0,$s0],[$h1,$mn1,$s1]) };
  4         14  
800              
801             } else {
802             # y0-m0-d0 h0:mn0:s0 -> y0-m0-d0 24:00:00
803             # y1-m1-d1 h1:mn1:s1 -> y1-m1-d1 00:00:00
804              
805 10         20 my $t1 = $self->_calc_hms_hms([$h0,$mn0,$s0],[24,0,0]);
806 10         22 my $t2 = $self->_calc_hms_hms([0,0,0],[$h1,$mn1,$s1]);
807 10         15 ($dh,$dm,$ds) = @{ $self->calc_time_time($t1,$t2) };
  10         15  
808              
809 10         28 my $dd0 = $self->days_since_1BC([$y0,$m0,$d0]);
810 10         12 $dd0++;
811 10         26 my $dd1 = $self->days_since_1BC([$y1,$m1,$d1]);
812 10         18 $dh += ($dd1-$dd0)*24;
813             }
814              
815 14 100       22 if ($minus) {
816 7         7 $dh *= -1;
817 7         7 $dm *= -1;
818 7         7 $ds *= -1;
819             }
820 14         25 return [$dh,$dm,$ds];
821             }
822              
823             sub calc_date_days {
824 4721     4721 1 28593 my($self,$date,$n,$subtract) = @_;
825 4721         7329 my($y,$m,$d,$h,$mn,$s) = @$date;
826 4721 100       7247 my($ymdonly) = (defined $h ? 0 : 1);
827              
828 4721 100       6647 $n *= -1 if ($subtract);
829 4721         10925 my $d1bc = $self->days_since_1BC([$y,$m,$d]);
830 4721         6535 $d1bc += $n;
831 4721         9789 my $ymd = $self->days_since_1BC($d1bc);
832              
833 4721 100       6237 if ($ymdonly) {
834 2607         5965 return $ymd;
835             } else {
836 2114         6847 return [@$ymd,$h*1,$mn*1,$s*1];
837             }
838             }
839              
840             sub calc_date_delta {
841 8     8 1 50032 my($self,$date,$delta,$subtract) = @_;
842 8         16 my($y,$m,$d,$h,$mn,$s) = @$date;
843 8         74 my($dy,$dm,$dw,$dd,$dh,$dmn,$ds) = @$delta;
844              
845             ($y,$m,$d) =
846 8         11 @{ $self->_calc_date_ymwd([$y,$m,$d], [$dy,$dm,$dw,$dd], $subtract) };
  8         34  
847              
848 8         33 return $self->calc_date_time([$y,$m,$d,$h,$mn,$s],[$dh,$dmn,$ds],$subtract);
849             }
850              
851             sub calc_date_time {
852 12711     12711 1 97920 my($self,$date,$time,$subtract) = @_;
853 12711         17590 my($y,$m,$d,$h,$mn,$s) = @$date;
854 12711         16589 my($dh,$dmn,$ds) = @$time;
855              
856 12711 100 66     27995 if ($ds > 59 || $ds < -59) {
857 4         11 $dmn += int($ds/60);
858 4         6 $ds = $ds % 60;
859             }
860 12711 100 66     26164 if ($dmn > 59 || $dmn < -59) {
861 4         9 $dh += int($dmn/60);
862 4         11 $dmn = $dmn % 60;
863             }
864 12711         12519 my $dd = 0;
865 12711 100 100     25411 if ($dh > 23 || $dh < -23) {
866 34         52 $dd = int($dh/24);
867 34         44 $dh = $dh % 24;
868             }
869              
870             # Handle subtraction
871 12711 100       16654 if ($subtract) {
872 5591         5687 $dh *= -1;
873 5591         5586 $dmn *= -1;
874 5591         5038 $ds *= -1;
875 5591         5763 $dd *= -1;
876             }
877              
878 12711 100       15370 if ($dd == 0) {
879 12677         11858 $y *= 1;
880 12677         11696 $m *= 1;
881 12677         11881 $d *= 1;
882             } else {
883 34         39 ($y,$m,$d) = @{ $self->calc_date_days([$y,$m,$d],$dd) };
  34         118  
884             }
885              
886 12711         24963 $self->_mod_add(60,$ds,\$s,\$mn);
887 12711         21564 $self->_mod_add(60,$dmn,\$mn,\$h);
888 12711         20170 $self->_mod_add(24,$dh,\$h,\$d);
889              
890 12711 100       15528 if ($d<1) {
891 9         19 $m--;
892 9 100       30 $y--, $m=12 if ($m<1);
893 9         31 my $day_in_mon = $self->days_in_month($y,$m);
894 9         30 $d += $day_in_mon;
895             } else {
896 12702         19544 my $day_in_mon = $self->days_in_month($y,$m);
897 12702 100       18830 if ($d>$day_in_mon) {
898 68         100 $d -= $day_in_mon;
899 68         80 $m++;
900 68 100       167 $y++, $m=1 if ($m>12);
901             }
902             }
903              
904 12711         36354 return [$y,$m,$d,$h,$mn,$s];
905             }
906              
907             sub _calc_date_time_strings {
908 0     0   0 my($self,$date,$time,$subtract) = @_;
909 0         0 my @date = @{ $self->split('date',$date) };
  0         0  
910 0 0       0 return '' if (! @date);
911 0         0 my @time = @{ $self->split('time',$time) };
  0         0  
912              
913 0         0 my @date2 = @{ $self->calc_date_time(\@date,\@time,$subtract) };
  0         0  
914              
915 0         0 return $self->join('date',\@date2);
916             }
917              
918             sub _calc_date_ymwd {
919 2425     2425   46171 my($self,$date,$ymwd,$subtract) = @_;
920 2425         3505 my($y,$m,$d,$h,$mn,$s) = @$date;
921 2425         3544 my($dy,$dm,$dw,$dd) = @$ymwd;
922 2425 100       3475 my($ymdonly) = (defined $h ? 0 : 1);
923              
924 2425         2984 $dd += $dw*7;
925              
926 2425 100       3017 if ($subtract) {
927 1207         1417 $y -= $dy;
928 1207         2926 $self->_mod_add(-12,-1*$dm,\$m,\$y);
929 1207         1586 $dd *= -1;
930              
931             } else {
932 1218         1652 $y += $dy;
933 1218         1931 $self->_mod_add(-12,$dm,\$m,\$y);
934             }
935              
936 2425         3492 my $dim = $self->days_in_month($y,$m);
937 2425 100       3296 $d = $dim if ($d > $dim);
938              
939 2425         2465 my $ymd;
940 2425 100       3023 if ($dd == 0) {
941 2242         2980 $ymd = [$y,$m,$d];
942             } else {
943 183         397 $ymd = $self->calc_date_days([$y,$m,$d],$dd);
944             }
945              
946 2425 100       3160 if ($ymdonly) {
947 2423         3953 return $ymd;
948             } else {
949 2         6 return [@$ymd,$h,$mn,$s];
950             }
951             }
952              
953             sub _calc_hms_hms {
954 24     24   30 my($self,$hms0,$hms1) = @_;
955 24         35 my($h0,$m0,$s0,$h1,$m1,$s1) = (@$hms0,@$hms1);
956              
957 24         37 my($s) = ($h1-$h0)*3600 + ($m1-$m0)*60 + $s1-$s0;
958 24         24 my($m) = int($s/60);
959 24         19 $s -= $m*60;
960 24         19 my($h) = int($m/60);
961 24         24 $m -= $h*60;
962 24         36 return [$h,$m,$s];
963             }
964              
965             sub calc_time_time {
966 86     86 1 20239 my($self,$time0,$time1,$subtract) = @_;
967 86         136 my($h0,$m0,$s0,$h1,$m1,$s1) = (@$time0,@$time1);
968              
969 86 100       128 if ($subtract) {
970 51         60 $h1 *= -1;
971 51         78 $m1 *= -1;
972 51         60 $s1 *= -1;
973             }
974 86         148 my($s) = (($h0+$h1)*60 + ($m0+$m1))*60 + $s0+$s1;
975 86         118 my($m) = int($s/60);
976 86         88 $s -= $m*60;
977 86         99 my($h) = int($m/60);
978 86         112 $m -= $h*60;
979              
980 86         196 return [$h,$m,$s];
981             }
982              
983             ###############################################################################
984              
985             # Returns -1 if date0 is before date1, 0 if date0 is the same as date1, and
986             # 1 if date0 is after date1.
987             #
988             sub cmp {
989 7224     7224 1 24001 my($self,$date0,$date1) = @_;
990 7224   66     34288 return ($$date0[0] <=> $$date1[0] ||
991             $$date0[1] <=> $$date1[1] ||
992             $$date0[2] <=> $$date1[2] ||
993             $$date0[3] <=> $$date1[3] ||
994             $$date0[4] <=> $$date1[4] ||
995             $$date0[5] <=> $$date1[5]);
996             }
997              
998             ###############################################################################
999             # This determines the OS.
1000              
1001             sub _os {
1002 1021     1021   1629 my($self) = @_;
1003              
1004 1021         1852 my $os = '';
1005              
1006 1021 50 33     14745 if ($^O =~ /MSWin32/io ||
    50 33        
    50 33        
      33        
      33        
1007             $^O =~ /Windows_95/io ||
1008             $^O =~ /Windows_NT/io
1009             ) {
1010 0         0 $os = 'Windows';
1011              
1012             } elsif ($^O =~ /MacOS/io ||
1013             $^O =~ /MPE/io ||
1014             $^O =~ /OS2/io ||
1015             $^O =~ /NetWare/io
1016             ) {
1017 0         0 $os = 'Other';
1018              
1019             } elsif ($^O =~ /VMS/io) {
1020 0         0 $os = 'VMS';
1021              
1022             } else {
1023 1021         1649 $os = 'Unix';
1024             }
1025              
1026 1021         2187 return $os;
1027             }
1028              
1029             ###############################################################################
1030             # Config variable functions
1031              
1032             # $self->config(SECT);
1033             # Creates a new section (if it doesn't already exist).
1034             #
1035             # $self->config(SECT,'_vars');
1036             # Returns a list of (VAR VAL VAR VAL ...)
1037             #
1038             # $self->config(SECT,VAR,VAL);
1039             # Adds (VAR,VAL) to the list.
1040             #
1041             sub _section {
1042 253     253   404 my($self,$sect,$var,$val) = @_;
1043 253         305 $sect = lc($sect);
1044              
1045             #
1046             # $self->_section(SECT) creates a new section
1047             #
1048              
1049 253 0 33     384 if (! defined $var &&
1050             ! exists $$self{'data'}{'sections'}{$sect}) {
1051 0 0       0 if ($sect eq 'conf') {
1052 0         0 $$self{'data'}{'sections'}{$sect} = {};
1053             } else {
1054 0         0 $$self{'data'}{'sections'}{$sect} = [];
1055             }
1056 0         0 return '';
1057             }
1058              
1059 253 50       392 if ($var eq '_vars') {
1060 0         0 return @{ $$self{'data'}{'sections'}{$sect} };
  0         0  
1061             }
1062              
1063 253         257 push @{ $$self{'data'}{'sections'}{$sect} },($var,$val);
  253         632  
1064 253         545 return;
1065             }
1066              
1067             # This sets a config variable. It also performs all side effects from
1068             # setting that variable.
1069             #
1070             sub _config_var_base {
1071 8193     8193   12090 my($self,$var,$val) = @_;
1072              
1073 8193 100 33     60025 if ($var eq 'defaults') {
    100 33        
    100 100        
    50 100        
    50 100        
    100 66        
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
    100          
    100          
    100          
    100          
    50          
    0          
1074             # Reset the configuration if desired.
1075 1         4 $self->_init_config(1);
1076 1         4 return;
1077              
1078             } elsif ($var eq 'eraseholidays') {
1079 18         160 $self->_init_holidays(1);
1080 18         69 return;
1081              
1082             } elsif ($var eq 'eraseevents') {
1083 14         86 $self->_init_events(1);
1084 14         38 return;
1085              
1086             } elsif ($var eq 'configfile') {
1087 0         0 $self->_config_file($val);
1088 0         0 return;
1089              
1090             } elsif ($var eq 'encoding') {
1091 0         0 my $err = $self->_config_var_encoding($val);
1092 0 0       0 return if ($err);
1093              
1094             } elsif ($var eq 'language') {
1095 539         2368 my $err = $self->_language($val);
1096 539 50       1575 return if ($err);
1097 539         3241 $err = $self->_config_var_encoding();
1098 539 50       1221 return if ($err);
1099              
1100             } elsif ($var eq 'yytoyyyy') {
1101 533         1148 $val = lc($val);
1102 533 50 100     7838 if ($val ne 'c' &&
      100        
      66        
1103             $val !~ /^c\d\d$/o &&
1104             $val !~ /^c\d\d\d\d$/o &&
1105             $val !~ /^\d+$/o) {
1106 0         0 carp "ERROR: [config_var] invalid: YYtoYYYY: $val";
1107 0         0 return;
1108             }
1109              
1110             } elsif ($var eq 'workweekbeg') {
1111 520         2152 my $err = $self->_config_var_workweekbeg($val);
1112 520 50       1779 return if ($err);
1113              
1114             } elsif ($var eq 'workweekend') {
1115 520         1633 my $err = $self->_config_var_workweekend($val);
1116 520 50       1118 return if ($err);
1117              
1118             } elsif ($var eq 'workday24hr') {
1119 1026         3553 my $err = $self->_config_var_workday24hr($val);
1120 1026 50       2094 return if ($err);
1121              
1122             } elsif ($var eq 'workdaybeg') {
1123 526         2624 my $err = $self->_config_var_workdaybegend(\$val,'WorkDayBeg');
1124 526 50       1307 return if ($err);
1125              
1126             } elsif ($var eq 'workdayend') {
1127 524         1407 my $err = $self->_config_var_workdaybegend(\$val,'WorkDayEnd');
1128 524 50       1263 return if ($err);
1129              
1130             } elsif ($var eq 'firstday') {
1131 699         2343 my $err = $self->_config_var_firstday($val);
1132 699 50       1506 return if ($err);
1133              
1134             } elsif ($var eq 'week1ofyear') {
1135 642         2226 my $err = $self->_config_var_week1ofyear($val);
1136 642 50       1379 return if ($err);
1137              
1138             } elsif ($var eq 'tz' ||
1139             $var eq 'forcedate' ||
1140             $var eq 'setdate') {
1141             # These can only be used if the Date::Manip::TZ module has been loaded
1142 0         0 carp "ERROR: [config_var] $var config variable requires TZ module";
1143 0         0 return;
1144              
1145             } elsif ($var eq 'recurrange') {
1146 520         2173 my $err = $self->_config_var_recurrange($val);
1147 520 50       1170 return if ($err);
1148              
1149             } elsif ($var eq 'defaulttime') {
1150 522         1803 my $err = $self->_config_var_defaulttime($val);
1151 522 50       1170 return if ($err);
1152              
1153             } elsif ($var eq 'periodtimesep') {
1154             # We have to redo the time regexp
1155 1         16 delete $$self{'data'}{'rx'}{'time'};
1156              
1157             } elsif ($var eq 'format_mmmyyyy') {
1158 4         33 my $err = $self->_config_var_format_mmmyyyy($val);
1159 4 50       16 return if ($err);
1160              
1161             } elsif ($var eq 'dateformat' ||
1162             $var eq 'printable' ||
1163             $var eq 'tomorrowfirst' ||
1164             $var eq 'use_posix_printf' ||
1165             $var eq 'maxrecurattempts') {
1166             # do nothing
1167              
1168             } elsif ($var eq 'jan1week1') {
1169 0         0 carp "WARNING: the jan1week1 Date::Manip config variable is deprecated\n" .
1170             " and will be removed in version 7.00. Please use\n" .
1171             " the Week1ofYear config variable instead.\n";
1172 0 0       0 if ($val) {
1173 0         0 $self->_config_var_base('week1ofyear','jan1');
1174             } else {
1175 0         0 $self->_config_var_base('week1ofyear','jan4');
1176             }
1177 0         0 return;
1178              
1179             } else {
1180 0         0 carp "ERROR: [config_var] invalid config variable: $var";
1181 0         0 return '';
1182             }
1183              
1184 8160         15737 $$self{'data'}{'sections'}{'conf'}{$var} = $val;
1185 8160         13716 return;
1186             }
1187              
1188             ###############################################################################
1189             # Specific config variable functions
1190              
1191             sub _config_var_encoding {
1192 539     539   1274 my($self,$val) = @_;
1193              
1194 539 50       1288 if (! $val) {
    0          
1195 539         747 $$self{'data'}{'calc'}{'enc_in'} = [ @{ $$self{'data'}{'enc'} } ];
  539         2281  
1196 539         2363 $$self{'data'}{'calc'}{'enc_out'} = 'UTF-8';
1197              
1198             } elsif ($val =~ /^(.*),(.*)$/o) {
1199 0         0 my($in,$out) = ($1,$2);
1200 0 0       0 if ($in) {
1201 0         0 my $o = find_encoding($in);
1202 0 0       0 if (! $o) {
1203 0         0 carp "ERROR: [config_var] invalid: Encoding: $in";
1204 0         0 return 1;
1205             }
1206             }
1207 0 0       0 if ($out) {
1208 0         0 my $o = find_encoding($out);
1209 0 0       0 if (! $o) {
1210 0         0 carp "ERROR: [config_var] invalid: Encoding: $out";
1211 0         0 return 1;
1212             }
1213             }
1214              
1215 0 0 0     0 if ($in && $out) {
    0          
    0          
1216 0         0 $$self{'data'}{'calc'}{'enc_in'} = [ $in ];
1217 0         0 $$self{'data'}{'calc'}{'enc_out'} = $out;
1218              
1219             } elsif ($in) {
1220 0         0 $$self{'data'}{'calc'}{'enc_in'} = [ $in ];
1221 0         0 $$self{'data'}{'calc'}{'enc_out'} = 'UTF-8';
1222              
1223             } elsif ($out) {
1224 0         0 $$self{'data'}{'calc'}{'enc_in'} = [ @{ $$self{'data'}{'enc'} } ];
  0         0  
1225 0         0 $$self{'data'}{'calc'}{'enc_out'} = $out;
1226              
1227             } else {
1228 0         0 $$self{'data'}{'calc'}{'enc_in'} = [ @{ $$self{'data'}{'enc'} } ];
  0         0  
1229 0         0 $$self{'data'}{'calc'}{'enc_out'} = 'UTF-8';
1230             }
1231              
1232             } else {
1233 0         0 my $o = find_encoding($val);
1234 0 0       0 if (! $o) {
1235 0         0 carp "ERROR: [config_var] invalid: Encoding: $val";
1236 0         0 return 1;
1237             }
1238 0         0 $$self{'data'}{'calc'}{'enc_in'} = [ $val ];
1239 0         0 $$self{'data'}{'calc'}{'enc_out'} = $val;
1240             }
1241              
1242 539 100       746 if (! @{ $$self{'data'}{'calc'}{'enc_in'} }) {
  539         1656  
1243 522         1865 $$self{'data'}{'calc'}{'enc_in'} = [ qw(utf-8 perl) ];
1244             }
1245              
1246 539         1061 return 0;
1247             }
1248              
1249             sub _config_var_recurrange {
1250 520     520   1126 my($self,$val) = @_;
1251              
1252 520         1099 $val = lc($val);
1253 520 50       4066 if ($val =~ /^(none|year|month|week|day|all)$/o) {
1254 520         1144 return 0;
1255             }
1256              
1257 0         0 carp "ERROR: [config_var] invalid: RecurRange: $val";
1258 0         0 return 1;
1259             }
1260              
1261             sub _config_var_workweekbeg {
1262 520     520   1253 my($self,$val) = @_;
1263              
1264 520 50       1349 if (! $self->_is_int($val,1,7)) {
1265 0         0 carp "ERROR: [config_var] invalid: WorkWeekBeg: $val";
1266 0         0 return 1;
1267             }
1268 520 50       1643 if ($val >= $self->_config('workweekend')) {
1269 0         0 carp "ERROR: [config_var] WorkWeekBeg must be before WorkWeekEnd";
1270 0         0 return 1;
1271             }
1272              
1273 520         1639 $self->_calc_workweek($val,'');
1274 520         1333 $self->_init_business_length();
1275 520         839 return 0;
1276             }
1277              
1278             sub _config_var_workweekend {
1279 520     520   1086 my($self,$val) = @_;
1280              
1281 520 50       1264 if (! $self->_is_int($val,1,7)) {
1282 0         0 carp "ERROR: [config_var] invalid: WorkWeekBeg: $val";
1283 0         0 return 1;
1284             }
1285 520 50       1354 if ($val <= $self->_config('workweekbeg')) {
1286 0         0 carp "ERROR: [config_var] WorkWeekEnd must be after WorkWeekBeg";
1287 0         0 return 1;
1288             }
1289              
1290 520         1394 $self->_calc_workweek('',$val);
1291 520         1166 $self->_init_business_length();
1292 520         742 return 0;
1293             }
1294              
1295             sub _config_var_workday24hr {
1296 1026     1026   1947 my($self,$val) = @_;
1297              
1298 1026 100       2052 if ($val) {
1299 506         1835 $$self{'data'}{'sections'}{'conf'}{'workdaybeg'} = '00:00:00';
1300 506         1462 $$self{'data'}{'sections'}{'conf'}{'workdayend'} = '24:00:00';
1301 506         1647 $$self{'data'}{'calc'}{'workdaybeg'} = [0,0,0];
1302 506         1338 $$self{'data'}{'calc'}{'workdayend'} = [24,0,0];
1303              
1304 506         1993 $self->_calc_bdlength();
1305 506         1644 $self->_init_business_length();
1306             }
1307              
1308 1026         1762 return 0;
1309             }
1310              
1311             sub _config_var_workdaybegend {
1312 1050     1050   2691 my($self,$val,$conf) = @_;
1313              
1314             # Must be a valid time. Entered as H, H:M, or H:M:S
1315              
1316 1050         4739 my $tmp = $self->split('hms',$$val);
1317 1050 50       2105 if (! defined $tmp) {
1318 0         0 carp "ERROR: [config_var] invalid: $conf: $$val";
1319 0         0 return 1;
1320             }
1321 1050         3106 $$self{'data'}{'calc'}{lc($conf)} = $tmp;
1322 1050         3481 $$val = $self->join('hms',$tmp);
1323              
1324             # workdaybeg < workdayend
1325              
1326 1050         1443 my @beg = @{ $$self{'data'}{'calc'}{'workdaybeg'} };
  1050         2362  
1327 1050         1359 my @end = @{ $$self{'data'}{'calc'}{'workdayend'} };
  1050         2336  
1328 1050         2115 my $beg = $beg[0]*3600 + $beg[1]*60 + $beg[2];
1329 1050         1709 my $end = $end[0]*3600 + $end[1]*60 + $end[2];
1330              
1331 1050 50       2389 if ($beg > $end) {
1332 0         0 carp "ERROR: [config_var] WorkDayBeg not before WorkDayEnd";
1333 0         0 return 1;
1334             }
1335              
1336             # Calculate bdlength
1337              
1338 1050         2025 $$self{'data'}{'sections'}{'conf'}{'workday24hr'} = 0;
1339              
1340 1050         2425 $self->_calc_bdlength();
1341 1050         2156 $self->_init_business_length();
1342              
1343 1050         2145 return 0;
1344             }
1345              
1346             sub _config_var_firstday {
1347 699     699   1421 my($self,$val) = @_;
1348              
1349 699 50       1843 if (! $self->_is_int($val,1,7)) {
1350 0         0 carp "ERROR: [config_var] invalid: FirstDay: $val";
1351 0         0 return 1;
1352             }
1353              
1354 699         1439 return 0;
1355             }
1356              
1357             sub _config_var_week1ofyear {
1358 642     642   1429 my($self,$val) = @_;
1359 642         1366 $val = lc($val);
1360              
1361 642 50 100     4046 if ($val =~ /^jan[1-7]$/ ||
      66        
1362             $val =~ /^dow[1-7]$/ ||
1363             $val eq 'firstday') {
1364 642         1259 return 0;
1365             }
1366 0         0 carp "ERROR: [config_var] invalid: Week1ofYear: $val";
1367 0         0 return 1;
1368             }
1369              
1370             sub _config_var_defaulttime {
1371 522     522   1126 my($self,$val) = @_;
1372              
1373 522 50 66     2135 if (lc($val) eq 'midnight' ||
1374             lc($val) eq 'curr') {
1375 522         934 return 0;
1376             }
1377 0         0 carp "ERROR: [config_var] invalid: DefaultTime: $val";
1378 0         0 return 1;
1379             }
1380              
1381             sub _config_var_format_mmmyyyy {
1382 4     4   16 my($self,$val) = @_;
1383              
1384 4 50 66     34 if (lc($val) eq 'first' ||
      33        
1385             lc($val) eq 'last' ||
1386             lc($val) eq '') {
1387 4         12 return 0;
1388             }
1389 0         0 carp "ERROR: [config_var] invalid: Format_MMMYYYY: $val";
1390 0         0 return 1;
1391             }
1392              
1393             ###############################################################################
1394             # Language functions
1395              
1396             # This reads in a langauge module and sets regular expressions
1397             # and word lists based on it.
1398             #
1399 170     170   721545 no strict 'refs';
  170         364  
  170         25947  
1400             sub _language {
1401 539     539   1359 my($self,$lang) = @_;
1402 539         1113 $lang = lc($lang);
1403              
1404 539 50       1855 if (! exists $Date::Manip::Lang::index::Lang{$lang}) {
1405 0         0 carp "ERROR: [language] invalid: $lang";
1406 0         0 return 1;
1407             }
1408              
1409             return 0 if (exists $$self{'data'}{'sections'}{'conf'} &&
1410 539 50 33     4332 $$self{'data'}{'sections'}{'conf'} eq $lang);
1411 539         1973 $self->_init_language(1);
1412              
1413 539         1412 my $mod = $Date::Manip::Lang::index::Lang{$lang};
1414 539         45682 eval "require Date::Manip::Lang::${mod}";
1415 539 50       2546 if ($@) {
1416 0         0 croak "ERROR: failed to load Date::Manip::Lang::${mod}: $@\n";
1417             }
1418              
1419 170     170   863 no warnings 'once';
  170         242  
  170         42296  
1420 539         790 $$self{'data'}{'lang'} = ${ "Date::Manip::Lang::${mod}::Language" };
  539         4300  
1421 539         901 $$self{'data'}{'enc'} = [ @{ "Date::Manip::Lang::${mod}::Encodings" } ];
  539         3051  
1422              
1423             # Common words
1424 539         3527 $self->_rx_wordlist('at');
1425 539         1354 $self->_rx_wordlist('each');
1426 539         1264 $self->_rx_wordlist('last');
1427 539         1281 $self->_rx_wordlist('of');
1428 539         1212 $self->_rx_wordlist('on');
1429 539         2523 $self->_rx_wordlists('when');
1430              
1431             # Next/prev
1432 539         2003 $self->_rx_wordlists('nextprev');
1433              
1434             # Field names (years, year, yr, ...)
1435 539         1662 $self->_rx_wordlists('fields');
1436              
1437             # Numbers (first, 1st)
1438 539         1604 $self->_rx_wordlists('nth');
1439 539         2218 $self->_rx_wordlists('nth','nth_dom',31); # 1-31
1440 539         1844 $self->_rx_wordlists('nth','nth_wom',5); # 1-5
1441              
1442             # Calendar names (Mon, Tue and Jan, Feb)
1443 539         1426 $self->_rx_wordlists('day_abb');
1444 539         1437 $self->_rx_wordlists('day_char');
1445 539         1366 $self->_rx_wordlists('day_name');
1446 539         2021 $self->_rx_wordlists('month_abb');
1447 539         1424 $self->_rx_wordlists('month_name');
1448              
1449             # H:M:S separators
1450 539         2582 $self->_rx_simple('sephm');
1451 539         1320 $self->_rx_simple('sepms');
1452 539         1255 $self->_rx_simple('sepfr');
1453              
1454             # Time replacement strings
1455 539         2084 $self->_rx_replace('times');
1456              
1457             # Some offset strings
1458 539         5881 $self->_rx_replace('offset_date');
1459 539         3894 $self->_rx_replace('offset_time');
1460              
1461             # AM/PM strings
1462 539         2642 $self->_rx_wordlists('ampm');
1463              
1464             # Business/non-business mode
1465 539         2264 $self->_rx_wordlists('mode');
1466              
1467 539         2020 return 0;
1468             }
1469 170     170   933 use strict 'refs';
  170         264  
  170         63358  
1470              
1471             # This takes a string or strings from the language file which is a
1472             # regular expression and copies it to the regular expression cache.
1473             #
1474             # If the language file contains a list of strings, a list of strings
1475             # is stored in the regexp cache.
1476             #
1477             sub _rx_simple {
1478 1617     1617   3541 my($self,$ele) = @_;
1479              
1480 1617 100       3383 if (exists $$self{'data'}{'lang'}{$ele}) {
1481 19 100       66 if (ref($$self{'data'}{'lang'}{$ele})) {
1482 16         24 @{ $$self{'data'}{'rx'}{$ele} } = @{ $$self{'data'}{'lang'}{$ele} };
  16         54  
  16         31  
1483             } else {
1484 3         8 $$self{'data'}{'rx'}{$ele} = $$self{'data'}{'lang'}{$ele};
1485             }
1486             } else {
1487 1598         3141 $$self{'data'}{'rx'}{$ele} = undef;
1488             }
1489              
1490 1617         1975 return;
1491             }
1492              
1493             # We need to quote strings that will be used in regexps, but we don't
1494             # want to quote UTF-8 characters.
1495             #
1496             sub _qe_quote {
1497 226114     226114   230716 my($string) = @_;
1498 226114         338120 $string =~ s/([-.+*?])/\\$1/g;
1499 226114         315965 return $string;
1500             }
1501              
1502             # This takes a list of words and creates a simple regexp which matches
1503             # any of them.
1504             #
1505             # The first word in the list is the default way to express the word using
1506             # a normal ASCII character set.
1507             #
1508             # The second word in the list is the default way to express the word using
1509             # a locale character set. If it isn't defined, it defaults to the first word.
1510             #
1511             sub _rx_wordlist {
1512 2695     2695   5111 my($self,$ele) = @_;
1513              
1514 2695 50       5042 if (exists $$self{'data'}{'lang'}{$ele}) {
1515 2695         2693 my @tmp = @{ $$self{'data'}{'lang'}{$ele} };
  2695         7960  
1516              
1517 2695         6910 $$self{'data'}{'wordlist'}{$ele} = $tmp[0];
1518              
1519 2695         2774 my @tmp2;
1520 2695         3927 foreach my $tmp (@tmp) {
1521 4319 100       7207 push(@tmp2,_qe_quote($tmp)) if ($tmp);
1522             }
1523 2695         8017 @tmp2 = sort _sortByLength(@tmp2);
1524              
1525 2695         7148 $$self{'data'}{'rx'}{$ele} = join('|',@tmp2);
1526              
1527             } else {
1528 0         0 $$self{'data'}{'rx'}{$ele} = undef;
1529             }
1530              
1531 2695         3817 return;
1532             }
1533              
1534 170     170   1031 no strict 'vars';
  170         328  
  170         11717  
1535             sub _sortByLength {
1536 1152063     1152063   1061558 return (length $b <=> length $a);
1537             }
1538 170     170   695 use strict 'vars';
  170         239  
  170         446725  
1539              
1540             # This takes a hash of the form:
1541             # word => string
1542             # and creates a regular expression to match word (which must be surrounded
1543             # by word boundaries).
1544             #
1545             sub _rx_replace {
1546 1617     1617   2873 my($self,$ele) = @_;
1547              
1548 1617 50       4559 if (! exists $$self{'data'}{'lang'}{$ele}) {
1549 0         0 $$self{'data'}{'rx'}{$ele} = [];
1550 0         0 return;
1551             }
1552              
1553 1617         1991 my(@key) = keys %{ $$self{'data'}{'lang'}{$ele} };
  1617         7083  
1554 1617         2361 my $i = 1;
1555 1617         5603 foreach my $key (sort(@key)) {
1556 4336         6953 my $val = $$self{'data'}{'lang'}{$ele}{$key};
1557 4336         5749 my $k = _qe_quote($key);
1558 4336         72512 $$self{'data'}{'rx'}{$ele}[$i++] = qr/(?:^|\b)($k)(?:\b|$)/i;
1559 4336         13203 $$self{'data'}{'wordmatch'}{$ele}{lc($key)} = $val;
1560             }
1561              
1562 1617         4210 @key = sort _sortByLength(@key);
1563 1617         2574 @key = map { _qe_quote($_) } @key;
  4336         5064  
1564 1617         4167 my $rx = join('|',@key);
1565              
1566 1617         56500 $$self{'data'}{'rx'}{$ele}[0] = qr/(?:^|\b)(?:$rx)(?:\b|$)/i;
1567              
1568 1617         4461 return;
1569             }
1570              
1571             # This takes a list of values, each of which can be expressed in multiple
1572             # ways, and gets a regular expression which matches any of them, a default
1573             # way to express each value, and a hash which matches a matched string to
1574             # a value (the value is 1..N where N is the number of values).
1575             #
1576             sub _rx_wordlists {
1577 7007     7007   12042 my($self,$ele,$subset,$max) = @_;
1578 7007 100       11769 $subset = $ele if (! $subset);
1579              
1580 7007 50       12883 if (exists $$self{'data'}{'lang'}{$ele}) {
1581 7007         7104 my @vallist = @{ $$self{'data'}{'lang'}{$ele} };
  7007         18942  
1582 7007 100 66     17496 $max = $#vallist+1 if (! $max || $max > $#vallist+1);
1583 7007         7236 my (@all);
1584              
1585 7007         11128 for (my $i=1; $i<=$max; $i++) {
1586 80311         73960 my @tmp = @{ $$self{'data'}{'lang'}{$ele}[$i-1] };
  80311         140038  
1587 80311         120950 $$self{'data'}{'wordlist'}{$subset}[$i-1] = $tmp[0];
1588              
1589 80311         71417 my @str;
1590 80311         82013 foreach my $str (@tmp) {
1591 213127 100       246182 next if (! $str);
1592 213126         344097 $$self{'data'}{'wordmatch'}{$subset}{lc($str)} = $i;
1593 213126         223001 push(@str,_qe_quote($str));
1594             }
1595 80311         112426 push(@all,@str);
1596              
1597 80311         125332 @str = sort _sortByLength(@str);
1598 80311         223862 $$self{'data'}{'rx'}{$subset}[$i] = join('|',@str);
1599             }
1600              
1601 7007         18505 @all = sort _sortByLength(@all);
1602 7007         45182 $$self{'data'}{'rx'}{$subset}[0] = join('|',@all);
1603              
1604             } else {
1605 0         0 $$self{'data'}{'rx'}{$subset} = undef;
1606             }
1607              
1608 7007         11923 return;
1609             }
1610              
1611             ###############################################################################
1612             # Year functions
1613             #
1614             # $self->_method(METHOD) use METHOD as the method for YY->YYYY
1615             # conversions
1616             #
1617             # YEAR = _fix_year(YR) converts a 2-digit to 4-digit year
1618             # _fix_year is in TZ_Base
1619              
1620             sub _method {
1621 4     4   52 my($self,$method) = @_;
1622 4         52 $self->_config('yytoyyyy',$method);
1623              
1624 4         9 return;
1625             }
1626              
1627             ###############################################################################
1628             # $self->_mod_add($N,$add,\$val,\$rem);
1629             # This calculates $val=$val+$add and forces $val to be in a certain
1630             # range. This is useful for adding numbers for which only a certain
1631             # range is allowed (for example, minutes can be between 0 and 59 or
1632             # months can be between 1 and 12). The absolute value of $N determines
1633             # the range and the sign of $N determines whether the range is 0 to N-1
1634             # (if N>0) or 1 to N (N<0). $rem is adjusted to force $val into the
1635             # appropriate range.
1636             # Example:
1637             # To add 2 hours together (with the excess returned in days) use:
1638             # $self->_mod_add(-24,$h1,\$h,\$day);
1639             # To add 2 minutes together (with the excess returned in hours):
1640             # $self->_mod_add(60,$mn1,\$mn,\$hr);
1641             sub _mod_add {
1642 41078     41078   48592 my($self,$N,$add,$val,$rem)=@_;
1643 41078 50       48448 return if ($N==0);
1644 41078         38461 $$val+=$add;
1645 41078 100       43717 if ($N<0) {
1646             # 1 to N
1647 2809         2800 $N = -$N;
1648 2809 100       5064 if ($$val>$N) {
    100          
1649 65         98 $$rem+= int(($$val-1)/$N);
1650 65         103 $$val = ($$val-1)%$N +1;
1651             } elsif ($$val<1) {
1652 96         197 $$rem-= int(-$$val/$N)+1;
1653 96         175 $$val = $N-(-$$val % $N);
1654             }
1655              
1656             } else {
1657             # 0 to N-1
1658 38269 100       55895 if ($$val>($N-1)) {
    100          
1659 212         325 $$rem+= int($$val/$N);
1660 212         291 $$val = $$val%$N;
1661             } elsif ($$val<0) {
1662 159         263 $$rem-= int(-($$val+1)/$N)+1;
1663 159         237 $$val = ($N-1)-(-($$val+1)%$N);
1664             }
1665             }
1666              
1667 41078         42431 return;
1668             }
1669              
1670             # $flag = $self->_is_int($string [,$low, $high]);
1671             # Returns 1 if $string is a valid integer, 0 otherwise. If $low is
1672             # entered, $string must be >= $low. If $high is entered, $string must
1673             # be <= $high. It is valid to check only one of the bounds.
1674             sub _is_int {
1675 54248     54248   67268 my($self,$N,$low,$high)=@_;
1676 54248 100 66     265274 return 0 if (! defined $N or
      100        
      100        
      66        
      66        
1677             $N !~ /^\s*[-+]?\d+\s*$/o or
1678             defined $low && $N<$low or
1679             defined $high && $N>$high);
1680 54242         85454 return 1;
1681             }
1682              
1683             # $flag = $self->_is_num($string [,$low, $high]);
1684             # Returns 1 if $string is a valid number (integer or real), 0 otherwise.
1685             # If $low is entered, $string must be >= $low. If $high is entered,
1686             # $string must be <= $high. It is valid to check only one of the bounds.
1687             sub _is_num {
1688 40812     40812   46146 my($self,$N,$low,$high)=@_;
1689 40812 50 66     187386 return 0 if (! defined $N or
      33        
      66        
      33        
      33        
1690             ($N !~ /^\s*[-+]?\d+(\.\d*)?\s*$/o &&
1691             $N !~ /^\s*[-+]?\.\d+\s*$/o) or
1692             defined $low && $N<$low or
1693             defined $high && $N>$high);
1694 40810         62411 return 1;
1695             }
1696              
1697             ###############################################################################
1698             # Split/Join functions
1699              
1700             sub split {
1701 5757     5757 1 106093 my($self,$op,$string,$arg) = @_;
1702              
1703 5757         6586 my %opts;
1704 5757 100       13192 if (ref($arg) eq 'HASH') {
    100          
1705 1         2 %opts = %{ $arg };
  1         4  
1706             } elsif ($arg) {
1707             # ***DEPRECATED 7.0***
1708 1         3 %opts = ('nonorm' => 1);
1709             }
1710              
1711             # ***DEPRECATED 7.0***
1712 5757 100       12515 if ($op eq 'delta') {
    100          
1713 81         210 $opts{'mode'} = 'standard';
1714             } elsif ($op eq 'business') {
1715 6         15 $opts{'mode'} = 'business';
1716 6         7 $op = 'delta';
1717             }
1718              
1719 5757 100       11197 if ($op eq 'date') {
    100          
    100          
    100          
    50          
1720              
1721 4245 100 100     25685 if ($string =~ /^(\d\d\d\d)(\d\d)(\d\d)(\d\d):(\d\d):(\d\d)$/o ||
      100        
1722             $string =~ /^(\d\d\d\d)\-(\d\d)\-(\d\d)\-(\d\d):(\d\d):(\d\d)$/o ||
1723             $string =~ /^(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/o) {
1724 1925         11145 my($y,$m,$d,$h,$mn,$s) = ($1+0,$2+0,$3+0,$4+0,$5+0,$6+0);
1725 1925         5444 return [$y,$m,$d,$h,$mn,$s];
1726             } else {
1727 2320         5393 return undef;
1728             }
1729              
1730             } elsif ($op eq 'hms') {
1731 1118 100 33     16813 if ($string =~ /^(\d\d)(\d\d)(\d\d)$/o ||
      66        
      100        
      100        
1732             $string =~ /^(\d\d)(\d\d)()$/o ||
1733             $string =~ /^(\d\d?):(\d\d):(\d\d)$/o ||
1734             $string =~ /^(\d\d?):(\d\d)()$/o ||
1735             $string =~ /^(\d\d?)()()$/o) {
1736 1115         11637 my($err,$h,$mn,$s) = $self->_hms_fields( { 'out' => 'list' },[$1,$2,$3]);
1737 1115 100       3354 return undef if ($err);
1738 1114         3047 return [$h,$mn,$s];
1739             } else {
1740 3         6 return undef;
1741             }
1742              
1743             } elsif ($op eq 'offset') {
1744 294 100 100     2660 if ($string =~ /^([-+]?\d\d)(\d\d)(\d\d)$/o ||
      100        
      100        
      100        
1745             $string =~ /^([-+]?\d\d)(\d\d)()$/o ||
1746             $string =~ /^([-+]?\d\d?):(\d\d?):(\d\d?)$/o ||
1747             $string =~ /^([-+]?\d\d?):(\d\d?)()$/o ||
1748             $string =~ /^([-+]?\d\d?)()()$/o) {
1749 288         1897 my($err,$h,$mn,$s) = $self->_offset_fields( { 'source' => 'string',
1750             'out' => 'list'},
1751             [$1,$2,$3]);
1752 288 100       729 return undef if ($err);
1753 287         791 return [$h,$mn,$s];
1754             } else {
1755 6         24 return undef;
1756             }
1757              
1758             } elsif ($op eq 'time') {
1759 13 100       45 if ($string =~ /^[-+]?\d+(:[-+]?\d+){0,2}$/o) {
1760             my($err,$dh,$dmn,$ds) =
1761             $self->_time_fields( { 'nonorm' =>
1762 12 100       66 (exists($opts{'nonorm'}) ? $opts{'nonorm'} : 0),
1763             'source' => 'string',
1764             'sign' => -1,
1765             }, [split(/:/,$string)]);
1766 12 50       32 return undef if ($err);
1767 12         28 return [$dh,$dmn,$ds];
1768             } else {
1769 1         3 return undef;
1770             }
1771              
1772             } elsif ($op eq 'delta') {
1773 87         242 my($err,@delta) = $self->_split_delta($string);
1774 87 50       188 return undef if ($err);
1775              
1776             ($err,@delta) =
1777             $self->_delta_fields( { 'mode' => $opts{'mode'},
1778             'nonorm' => (exists($opts{'nonorm'}) ?
1779 87 50       706 $opts{'nonorm'} : 0),
1780             'source' => 'string',
1781             'sign' => -1,
1782             }, [@delta]);
1783              
1784 87 50       385 return undef if ($err);
1785 87         472 return [@delta];
1786             }
1787             }
1788              
1789             sub join{
1790 27694     27694 1 129842 my($self,$op,$data,$arg) = @_;
1791              
1792 27694         27279 my %opts;
1793 27694 100       46025 if (ref($arg) eq 'HASH') {
    100          
1794 1         3 %opts = %{ $arg };
  1         3  
1795             } elsif ($arg) {
1796             # ***DEPRECATED 7.0***
1797 2         3 %opts = ('nonorm' => 1);
1798             }
1799              
1800             # ***DEPRECATED 7.0***
1801 27694 100       43391 if ($op eq 'delta') {
    100          
1802 10         22 $opts{'mode'} = 'standard';
1803             } elsif ($op eq 'business') {
1804 9         16 $opts{'mode'} = 'business';
1805 9         9 $op = 'delta';
1806             }
1807              
1808 27694         40642 my @data = @$data;
1809              
1810 27694 100       35893 if ($op eq 'date') {
    100          
    100          
    100          
    50          
1811              
1812 24821         38303 my($err,$y,$m,$d,$h,$mn,$s) = $self->_date_fields(@data);
1813 24821 50       39074 return undef if ($err);
1814 24821         42810 my $form = $self->_config('printable');
1815 24821 100       39040 if ($form == 1) {
    100          
1816 1         7 return "$y$m$d$h$mn$s";
1817             } elsif ($form == 2) {
1818 1         6 return "$y-$m-$d-$h:$mn:$s";
1819             } else {
1820 24819         66142 return "$y$m$d$h:$mn:$s";
1821             }
1822              
1823             } elsif ($op eq 'offset') {
1824 108         462 my($err,$h,$mn,$s) = $self->_offset_fields( { 'source' => 'list',
1825             'out' => 'string'},
1826             [@data]);
1827 108 100       279 return undef if ($err);
1828 105         440 return "$h:$mn:$s";
1829              
1830             } elsif ($op eq 'hms') {
1831 2733         9025 my($err,$h,$mn,$s) = $self->_hms_fields( { 'out' => 'string' },[@data]);
1832 2733 100       5955 return undef if ($err);
1833 2730         7349 return "$h:$mn:$s";
1834              
1835             } elsif ($op eq 'time') {
1836             my($err,$dh,$dmn,$ds) =
1837             $self->_time_fields( { 'nonorm' =>
1838 13 100       64 (exists($opts{'nonorm'}) ? $opts{'nonorm'} : 0),
1839             'source' => 'list',
1840             'sign' => 0,
1841             }, [@data]);
1842 13 100       31 return undef if ($err);
1843 12         31 return "$dh:$dmn:$ds";
1844              
1845             } elsif ($op eq 'delta') {
1846             my ($err,@delta) =
1847             $self->_delta_fields( { 'mode' => $opts{'mode'},
1848             'nonorm' => (exists($opts{'nonorm'}) ?
1849 19 100       85 $opts{'nonorm'} : 0),
1850             'source' => 'list',
1851             'sign' => 0,
1852             }, [@data]);
1853 19 50       56 return undef if ($err);
1854 19         120 return join(':',@delta);
1855             }
1856             }
1857              
1858             sub _split_delta {
1859 1034     1034   1739 my($self,$string) = @_;
1860              
1861 1034         1244 my $sign = '[-+]?';
1862 1034         1211 my $num = '(?:\d+(?:\.\d*)?|\.\d+)';
1863 1034         1464 my $f = "(?:$sign$num)?";
1864              
1865 1034 100       12101 if ($string =~ /^$f(:$f){0,6}$/o) {
1866 436         1039 $string =~ s/::/:0:/go;
1867 436         580 $string =~ s/^:/0:/o;
1868 436         563 $string =~ s/:$/:0/o;
1869 436         1549 my(@delta) = split(/:/,$string);
1870 436         2188 return(0,@delta);
1871             } else {
1872 598         1364 return(1);
1873             }
1874             }
1875              
1876             # Check that type is not inconsistent with @delta.
1877             #
1878             # An exact delta cannot have semi-exact or approximate fields set.
1879             # A semi-exact delta cannot have approximate fields set.
1880             # An exact, semi-exact, or approximate delta cannot have non-integer values.
1881             #
1882             # If the type was not explicitly specified, guess what it is.
1883             #
1884             # Returns ($err,$type,$type_from)
1885             #
1886             sub _check_delta_type {
1887 5547     5547   11929 my($self,$mode,$type,$type_from,@delta) = @_;
1888              
1889 5547         6040 my $est = 0;
1890 5547         6974 foreach my $f (@delta) {
1891 38767 100       48253 if (! $self->_is_int($f)) {
1892 5         7 $est = 1;
1893 5         7 last;
1894             }
1895             }
1896              
1897 5547         6175 my $approx = 0;
1898 5547 100       8285 if (! $est) {
1899 5542 100 100     12547 $approx = 1 if ($delta[0] || $delta[1]);
1900             }
1901              
1902 5547         5842 my $semi = 0;
1903 5547 100 100     13152 if (! $est && ! $approx) {
1904 2135 100       3574 if ($mode eq 'business') {
1905 287 100       602 $semi = 1 if ($delta[2]);
1906             } else {
1907 1848 100 100     5012 $semi = 1 if ($delta[2] || $delta[3]);
1908             }
1909             }
1910              
1911 5547 100       10559 if ($est) {
    100          
    100          
1912             # If some of the fields are non-integer, then type must be estimated.
1913              
1914 5 50       9 if ($type ne 'estimated') {
1915 5 100       10 if ($type_from eq 'opt') {
1916 1         7 return ("Type must be estimated for non-integers");
1917             }
1918 4         6 $type = 'estimated';
1919 4         7 $type_from = 'det';
1920             }
1921              
1922             } elsif ($approx) {
1923             # If some of the approximate fields are set, then type must be
1924             # approx or estimated.
1925              
1926 3407 100 100     9403 if ($type ne 'approx' && $type ne 'estimated') {
1927 3397 100       5007 if ($type_from eq 'opt') {
1928 5         16 return("Type must be approx/estimated");
1929             }
1930 3392         4187 $type = 'approx';
1931 3392         3936 $type_from = 'det';
1932             }
1933              
1934             } elsif ($semi) {
1935             # If some of the semi-exact fields are set, then type must be
1936             # semi, approx, or estimated
1937              
1938 391 100 100     1872 if ($type ne 'semi' && $type ne 'approx' && $type ne 'estimated') {
      100        
1939 373 100       637 if ($type_from eq 'opt') {
1940 5         18 return("Type must be semi/approx/estimated");
1941             }
1942 368         468 $type = 'semi';
1943 368         667 $type_from = 'det';
1944             }
1945              
1946             } else {
1947              
1948 1744 100       2817 if (! $type) {
1949 266         348 $type = 'exact';
1950 266         329 $type_from = 'det';
1951             }
1952             }
1953              
1954 5536         17371 return ('',$type,$type_from);
1955             }
1956              
1957             # This function returns the fields in a delta in the desired format.
1958             #
1959             # $opts = { mode => standard/business
1960             # type => exact/semi/approx/estimated
1961             # nonorm => 0/1,
1962             # source => string, list, delta
1963             # sign => 0/1/-1
1964             # }
1965             # $fields = [Y,M,W,D,H,Mn,S]
1966             #
1967             # If the business option is 1, treat it as a business delta.
1968             #
1969             # If the nonorm option is 1, fields are NOT normalized. By default,
1970             # they are normalized.
1971             #
1972             # If source is 'string', then the source of the fields is a string
1973             # that has been split, so we need to handle carrying the signs. If
1974             # the option is 'list', then the source is a valid delta, so each
1975             # field is correctly signed already. In both cases, the type of
1976             # delta will need to be determined. If the source is 'delta', then
1977             # it comes from a Date::Manip::Delta object. In this case the type
1978             # must be specified. If type is not passed in, it will be set.
1979             #
1980             # If the sign option is 1, a sign is added to every field. If the
1981             # sign option is -1, all negative fields are signed. If the sign
1982             # option is 0, the minimum number of signs (for fields who's sign is
1983             # different from the next higher field) will be added.
1984             #
1985             # It returns ($err,@fields)
1986             #
1987             sub _delta_fields {
1988 5904     5904   8680 my($self,$opts,$fields) = @_;
1989 5904         12216 my @fields = @$fields;
1990 170     170   1224 no integer;
  170         265  
  170         918  
1991              
1992             #
1993             # Make sure that all fields are defined, numerical, and that there
1994             # are 7 of them.
1995             #
1996              
1997 5904         7170 foreach my $f (@fields) {
1998 40805 50       49396 $f=0 if (! defined($f));
1999 40805 100       49269 return ("Non-numerical field") if (! $self->_is_num($f));
2000             }
2001 5903 100       9549 return ("Delta may contain only 7 fields") if (@fields > 7);
2002 5902         10210 while (@fields < 7) {
2003 518         788 unshift(@fields,0);
2004             }
2005              
2006             #
2007             # Make sure each field is the correct sign so that the math will
2008             # work correctly. Get rid of all positive signs and leading 0's.
2009             #
2010              
2011 5902         8580 my $mode = $$opts{'mode'};
2012 5902         7422 my $source = $$opts{'source'};
2013 5902         11757 @fields = $self->_sign_source($source,@fields);
2014              
2015             #
2016             # Figure out the type of delta. When called from Date::Manip::Base, it'll
2017             # be determined from the data. When called from Date::Manip::Delta, it'll
2018             # be specified.
2019             #
2020              
2021 5902         7536 my ($type,$type_from);
2022 5902 100 66     14240 if (defined $source && $source eq 'delta') {
2023 5333 50       9085 if (! exists $$opts{'type'}) {
2024 0         0 return ("Type must be specified");
2025             }
2026 5333         6517 $type = $$opts{'type'};
2027              
2028             } else {
2029 569         607 my $err;
2030 569         1273 ($err,$type,$type_from) = $self->_check_delta_type($mode,'','init',@fields);
2031 569         1169 $$opts{'type'} = $type;
2032 569         915 $$opts{'type_from'} = $type_from;
2033 569 50       1354 return($err) if ($err);
2034             }
2035              
2036             #
2037             # Normalize values, if desired.
2038             #
2039              
2040 5902         8467 my $norm = 1-$$opts{'nonorm'};
2041 5902 100       12172 if ($norm) {
2042 5540 100       7435 if ($mode eq 'business') {
2043              
2044 354 100 100     1056 if ($type eq 'estimated') {
    100          
2045 10         26 @fields = $self->_normalize_bus_est(@fields);
2046              
2047             } elsif ($type eq 'approx' ||
2048             $type eq 'semi') {
2049 113         322 @fields = $self->_normalize_bus_approx(@fields);
2050              
2051             } else {
2052 231         604 @fields = $self->_normalize_bus_exact(@fields);
2053             }
2054              
2055             } else {
2056              
2057 5186 100 100     12640 if ($type eq 'estimated') {
    100          
2058 11         64 @fields = $self->_normalize_est(@fields);
2059              
2060             } elsif ($type eq 'approx' ||
2061             $type eq 'semi') {
2062 3709         8183 @fields = $self->_normalize_approx(@fields);
2063              
2064             } else {
2065 1466         4254 @fields = $self->_normalize_exact(@fields);
2066             }
2067              
2068             }
2069             }
2070              
2071             #
2072             # Now make sure that the signs are included as appropriate.
2073             #
2074              
2075 5902         13044 @fields = $self->_sign_fields($$opts{'sign'},@fields);
2076              
2077 5902         18753 return (0,@fields);
2078             }
2079              
2080             # If a set of fields came from splitting a string, not all of the fields
2081             # are signed. If it comes from a list, we just want to remove extra '+'
2082             # signs.
2083             #
2084             sub _sign_source {
2085 5926     5926   13404 my($self,$source,@fields) = @_;
2086              
2087             # Needed to handle fractional fields
2088 170     170   59611 no integer;
  170         314  
  170         751  
2089 5926 100       8564 if ($source eq 'string') {
2090              
2091             # if the source is splitting a delta, not all fields are signed,
2092             # so we need to carry the negative signs.
2093              
2094 562         766 my $sign = '+';
2095 562         723 foreach my $f (@fields) {
2096 3886 100       5622 if ($f =~ /^([-+])/o) {
2097 356         4733 $sign = $1;
2098             } else {
2099 3530         3558 $f = "$sign$f";
2100             }
2101 3886         4897 $f *= 1;
2102             }
2103              
2104             } else {
2105 5364         6192 foreach my $f (@fields) {
2106 37500         35808 $f *= 1;
2107             }
2108             }
2109              
2110 5926         14954 return @fields;
2111             }
2112              
2113             # This applies the correct sign to each field based on the $sign option:
2114             #
2115             # 1 : all fields signed
2116             # -1 : all negative fields signed
2117             # 0 : minimum number of signs for a joined set of fields
2118             #
2119             sub _sign_fields {
2120 5926     5926   11282 my($self,$sign,@fields) = @_;
2121 5926 50       8668 $sign = 0 if (! defined $sign);
2122              
2123 5926 50       12125 if ($sign == 1) {
    100          
2124             # All fields signed
2125 0         0 foreach my $f (@fields) {
2126 0 0       0 $f = "+$f" if ($f > 0);
2127             }
2128              
2129             } elsif ($sign == 0) {
2130             # Minimum number of signs
2131 370 100       4399 my $s = ($fields[0] < 0 ? '-' : '+');
2132 370         938 foreach my $f (@fields[1..$#fields]) {
2133 2172 100 100     4151 if ($f > 0 && $s eq '-') {
    100          
2134 26         54 $f = "+$f";
2135 26         59 $s = '+';
2136             } elsif ($f < 0) {
2137 323 100       479 if ($s eq '-') {
2138 197         252 $f *= -1;
2139             } else {
2140 126         293 $s = '-';
2141             }
2142             }
2143             }
2144             }
2145              
2146 5926         12587 return @fields;
2147             }
2148              
2149             # $opts = { nonorm => 0/1,
2150             # source => string, list
2151             # sign => 0/1/-1
2152             # }
2153             # $fields = [H,M,S]
2154             #
2155             # This function formats the fields in an amount of time measured in
2156             # hours, minutes, and seconds.
2157             #
2158             # It is similar to how _delta_fields (above) works.
2159             #
2160             sub _time_fields {
2161 25     25   35 my($self,$opts,$fields) = @_;
2162 25         37 my @fields = @$fields;
2163              
2164             #
2165             # Make sure that all fields are defined, numerical, and that there
2166             # are 3 of them.
2167             #
2168              
2169 25         34 foreach my $f (@fields) {
2170 67 50       78 $f=0 if (! defined($f));
2171 67 50       100 return (1) if (! $self->_is_int($f));
2172             }
2173 25 100       36 return (1) if (@fields > 3);
2174 24         58 while (@fields < 3) {
2175 9         19 unshift(@fields,0);
2176             }
2177              
2178             #
2179             # Make sure each field is the correct sign so that the math will
2180             # work correctly. Get rid of all positive signs and leading 0's.
2181             #
2182              
2183 24         30 my $source = $$opts{'source'};
2184 24         44 @fields = $self->_sign_source($source,@fields);
2185              
2186             #
2187             # Normalize them. Values will be signed only if they are
2188             # negative.
2189             #
2190              
2191 24         49 my $norm = 1-$$opts{'nonorm'};
2192 24 100       43 if ($norm) {
2193 20         26 my($h,$mn,$s) = @fields;
2194 20         23 $s += $h*3600 + $mn*60;
2195 20         33 @fields = __normalize_hms($h,$mn,$s);
2196             }
2197              
2198             #
2199             # Now make sure that the signs are included as appropriate.
2200             #
2201              
2202 24         38 @fields = $self->_sign_fields($$opts{'sign'},@fields);
2203              
2204 24         45 return (0,@fields);
2205             }
2206              
2207             # $opts = { out => string, list
2208             # }
2209             # $fields = [H,M,S]
2210             #
2211             # This function formats the fields in an HMS.
2212             #
2213             # If the out options is string, it prepares the fields to be joined (i.e.
2214             # they are all 2 digits long). Otherwise, they are just numerical values
2215             # (not necessarily 2 digits long).
2216             #
2217             # HH:MN:SS is always between 00:00:00 and 24:00:00.
2218             #
2219             # It returns ($err,@fields)
2220             #
2221             sub _hms_fields {
2222 3882     3882   5657 my($self,$opts,$fields) = @_;
2223 3882         7747 my @fields = @$fields;
2224              
2225             #
2226             # Make sure that all fields are defined, numerical (with no sign),
2227             # and that there are 3 of them.
2228             #
2229              
2230 3882         5826 foreach my $f (@fields) {
2231 11625 100       15149 $f=0 if (! $f);
2232 11625 100       16070 return (1) if (! $self->_is_int($f,0));
2233             }
2234 3881 100       5778 return (1) if (@fields > 3);
2235 3880         6458 while (@fields < 3) {
2236 20         37 push(@fields,0);
2237             }
2238              
2239             #
2240             # Check validity.
2241             #
2242              
2243 3880         6131 my ($h,$m,$s) = @fields;
2244 3880 0 66     18327 return (1) if ($h > 24 || $m > 59 || $s > 59 ||
      66        
      0        
      33        
      66        
2245             ($h==24 && ($m > 0 || $s > 0)));
2246              
2247             #
2248             # Format
2249             #
2250              
2251 3878 100       6202 if ($$opts{'out'} eq 'list') {
2252 1148         1882 foreach my $f ($h,$m,$s) {
2253 3444         4070 $f *= 1;
2254             }
2255              
2256             } else {
2257 2730         3562 foreach my $f ($h,$m,$s) {
2258 8190 100       13087 $f = "0$f" if (length($f)<2);
2259             }
2260             }
2261              
2262 3878         13001 return (0,$h,$m,$s);
2263             }
2264              
2265             # $opts = { source => string, list
2266             # out => string, list
2267             # }
2268             # $fields = [H,M,S]
2269             #
2270             # This function formats the fields in a timezone offset measured in
2271             # hours, minutes, and seconds.
2272             #
2273             # All offsets must be -23:59:59 <= offset <= 23:59:59 .
2274             #
2275             # The data comes from an offset in string or list format, and is
2276             # formatted so that it can be used to create a string or list format
2277             # output.
2278             #
2279             sub _offset_fields {
2280 396     396   582 my($self,$opts,$fields) = @_;
2281 396         853 my @fields = @$fields;
2282              
2283             #
2284             # Make sure that all fields are defined, numerical, and that there
2285             # are 3 of them.
2286             #
2287              
2288 396         574 foreach my $f (@fields) {
2289 1184 100 66     2730 $f=0 if (! defined $f || $f eq '');
2290 1184 50       1692 return (1) if (! $self->_is_int($f));
2291             }
2292 396 100       632 return (1) if (@fields > 3);
2293 395         695 while (@fields < 3) {
2294 5         9 push(@fields,0);
2295             }
2296              
2297             #
2298             # Check validity.
2299             #
2300              
2301 395         673 my ($h,$m,$s) = @fields;
2302 395 100       688 if ($$opts{'source'} eq 'string') {
2303             # Values = -23 59 59 to +23 59 59
2304 288 50 33     2099 return (1) if ($h < -23 || $h > 23 ||
      33        
      66        
      66        
      66        
2305             $m < 0 || $m > 59 ||
2306             $s < 0 || $s > 59);
2307             } else {
2308             # Values (-23,-59,-59) to (23,59,59)
2309             # Non-zero values must have the same sign
2310 107 100       272 if ($h >0) {
    100          
    100          
    50          
2311 33 50 66     244 return (1) if ( $h > 23 ||
      100        
      66        
      66        
2312             $m < 0 || $m > 59 ||
2313             $s < 0 || $s > 59);
2314             } elsif ($h < 0) {
2315 54 50 33     488 return (1) if ($h < -23 ||
      33        
      33        
      33        
2316             $m < -59 || $m > 0 ||
2317             $s < -59 || $s > 0);
2318             } elsif ($m > 0) {
2319 2 50 33     10 return (1) if ( $m > 59 ||
      33        
2320             $s < 0 || $s > 59);
2321             } elsif ($m < 0) {
2322 0 0 0     0 return (1) if ($m < -59 ||
      0        
2323             $s < -59 || $s > 0);
2324             } else {
2325 18 50 33     92 return (1) if ($s < -59 || $s > 59);
2326             }
2327             }
2328              
2329             #
2330             # Make sure each field is the correct sign so that the math will
2331             # work correctly. Get rid of all positive signs and leading 0's.
2332             #
2333              
2334 392 100       597 if ($$opts{'source'} eq 'string') {
2335              
2336             # In a string offset, only the first field is signed, so we need
2337             # to carry negative signs.
2338              
2339 287 100       682 if ($h =~ /^\-/) {
    50          
2340 196         255 $h *= 1;
2341 196         224 $m *= -1;
2342 196         237 $s *= -1;
2343             } elsif ($m =~ /^\-/) {
2344 0         0 $h *= 1;
2345 0         0 $m *= 1;
2346 0         0 $s *= -1;
2347             } else {
2348 91         114 $h *= 1;
2349 91         103 $m *= 1;
2350 91         310 $s *= 1;
2351             }
2352              
2353             } else {
2354 105         147 foreach my $f (@fields) {
2355 315         323 $f *= 1;
2356             }
2357             }
2358              
2359             #
2360             # Format them. They're already done for 'list' output.
2361             #
2362              
2363 392 100       615 if ($$opts{'out'} eq 'string') {
2364 105         3795 my $sign;
2365 105 100 66     435 if ($h<0 || $m<0 || $s<0) {
      66        
2366 54         82 $h = abs($h);
2367 54         68 $m = abs($m);
2368 54         69 $s = abs($s);
2369 54         104 $sign = '-';
2370             } else {
2371 51         72 $sign = '+';
2372             }
2373              
2374 105 100       274 $h = "0$h" if (length($h) < 2);
2375 105 100       252 $m = "0$m" if (length($m) < 2);
2376 105 100       223 $s = "0$s" if (length($s) < 2);
2377 105         167 $h = "$sign$h";
2378             }
2379              
2380 392         1160 return (0,$h,$m,$s);
2381             }
2382              
2383             # ($err,$y,$m,$d,$h,$mn,$s) = $self->_date_fields($y,$m,$d,$h,$mn,$s);
2384             #
2385             # Makes sure the fields are the right length.
2386             #
2387             sub _date_fields {
2388 55059     55059   83485 my($self,@fields) = @_;
2389 55059 50       77351 return (1) if (@fields != 6);
2390              
2391 55059         79030 my($y,$m,$d,$h,$mn,$s) = @fields;
2392              
2393 55059         88234 $y = "0$y" while (length($y) < 4);
2394 55059 100       89057 $m = "0$m" if (length($m)==1);
2395 55059 100       76965 $d = "0$d" if (length($d)==1);
2396 55059 100       76643 $h = "0$h" if (length($h)==1);
2397 55059 100       73363 $mn = "0$mn" if (length($mn)==1);
2398 55059 100       73794 $s = "0$s" if (length($s)==1);
2399              
2400 55059 100       66002 if (wantarray) {
2401 24821         84433 return (0,$y,$m,$d,$h,$mn,$s);
2402             } else {
2403 30238         69256 return "$y$m$d$h:$mn:$s";
2404             }
2405             }
2406              
2407             # $self->_delta_convert(FORMAT,DELTA)
2408             # This converts delta into the given format. Returns '' if invalid.
2409             #
2410             sub _delta_convert {
2411 94     94   222 my($self,$format,$delta)=@_;
2412 94         228 my $fields = $self->split($format,$delta);
2413 94 100       203 return undef if (! defined $fields);
2414 93         244 return $self->join($format,$fields);
2415             }
2416              
2417             ###############################################################################
2418             # Normalize the different types of deltas
2419              
2420             sub __normalize_ym {
2421 3845     3845   5774 my($y,$m,$s,$mon) = @_;
2422 170     170   208335 no integer;
  170         274  
  170         721  
2423              
2424 3845 100       5470 if (defined($s)) {
2425 21         33 $m = int($s/$mon);
2426 21         260 $s -= int(sprintf('%f',$m*$mon));
2427 21         31 $y = int($m/12);
2428 21         21 $m -= $y*12;
2429              
2430 21         49 return($y,$m,$s);
2431             } else {
2432 3824         4216 $m += $y*12;
2433 3824         6721 $y = int($m/12);
2434 3824         4314 $m -= $y*12;
2435              
2436 3824         8688 return($y,$m);
2437             }
2438             }
2439             sub __normalize_wd {
2440 3845     3845   6064 my($w,$d,$s,$wk,$day) = @_;
2441 170     170   18647 no integer;
  170         278  
  170         550  
2442              
2443 3845         4970 $d = int($s/$day);
2444 3845         4499 $s -= int($d*$day);
2445 3845         4276 $w = int($d/$wk);
2446 3845         3980 $d -= $w*$wk;
2447              
2448 3845         6751 return($w,$d,$s);
2449             }
2450             sub __normalize_hms {
2451 5568     5568   7962 my($h,$mn,$s) = @_;
2452 170     170   14465 no integer;
  170         317  
  170         490  
2453              
2454 5568         6780 $h = int($s/3600);
2455 5568         5871 $s -= $h*3600;
2456 5568         5897 $mn = int($s/60);
2457 5568         5684 $s -= $mn*60;
2458 5568         5584 $s = int($s);
2459              
2460 5568         9294 return($h,$mn,$s);
2461             }
2462              
2463             sub _normalize_est {
2464 11     11   22 my($self,$y,$m,$w,$d,$h,$mn,$s) = @_;
2465 170     170   11885 no integer;
  170         257  
  170         489  
2466              
2467             # Figure out how many seconds there are in the estimated delta
2468             #
2469             # 365.2425/12 days/month * 24 hours/day * 3600 sec/hour = 2629746 sec/month
2470              
2471 11         16 my $mon = 2629746;
2472 11         13 my $day = 86400;
2473 11         10 my $wk = 7;
2474 11         23 $s += ($y*12+$m)*$mon + ($w*$wk + $d)*$day +
2475             $h*3600 + $mn*60;
2476              
2477 11         26 ($y,$m,$s) = __normalize_ym($y,$m,$s,$mon);
2478 11         25 ($w,$d,$s) = __normalize_wd($w,$d,$s,$wk,$day);
2479 11         27 ($h,$mn,$s) = __normalize_hms($h,$mn,$s);
2480              
2481 11         27 return ($y,$m,$w,$d,$h,$mn,$s);
2482             }
2483             sub _normalize_bus_est {
2484 10     10   23 my($self,$y,$m,$w,$d,$h,$mn,$s) = @_;
2485 170     170   21683 no integer;
  170         956  
  170         767  
2486              
2487             # Figure out how many seconds there are in the estimated delta
2488             #
2489             # 365.2425/12 * wk_len/7 days/month * day sec/day = X sec/month
2490              
2491 10         18 my $day = $$self{'data'}{'len'}{'bdlength'};
2492 10         15 my $wk = $$self{'data'}{'len'}{'workweek'};
2493 10         48 my $mon = 365.2425/12 * $wk/7 * $day;
2494              
2495 10         47 $s += ($y*12+$m)*$mon + ($w*$wk + $d)*$day +
2496             $h*3600 + $mn*60;
2497              
2498 10         23 ($y,$m,$s) = __normalize_ym($y,$m,$s,$mon);
2499 10         22 ($w,$d,$s) = __normalize_wd($w,$d,$s,$wk,$day);
2500 10         21 ($h,$mn,$s) = __normalize_hms($h,$mn,$s);
2501              
2502 10         24 return ($y,$m,$w,$d,$h,$mn,$s);
2503             }
2504              
2505             sub _normalize_approx {
2506 3710     3710   6841 my($self,$y,$m,$w,$d,$h,$mn,$s) = @_;
2507 170     170   28639 no integer;
  170         347  
  170         570  
2508              
2509 3710         4006 my $wk = 7;
2510 3710         3968 my $day = 86400;
2511 3710         5793 $s += ($w*$wk + $d)*$day + $h*3600 + $mn*60;
2512              
2513 3710         7686 ($y,$m) = __normalize_ym($y,$m);
2514 3710         7316 ($w,$d,$s) = __normalize_wd($w,$d,$s,$wk,$day);
2515 3710         7231 ($h,$mn,$s) = __normalize_hms($h,$mn,$s);
2516              
2517 3710         12718 return ($y,$m,$w,$d,$h,$mn,$s);
2518             }
2519             sub _normalize_bus_approx {
2520 114     114   213 my($self,$y,$m,$w,$d,$h,$mn,$s) = @_;
2521 170     170   18108 no integer;
  170         218  
  170         558  
2522              
2523 114         230 my $wk = $$self{'data'}{'len'}{'workweek'};
2524 114         151 my $day = $$self{'data'}{'len'}{'bdlength'};
2525 114         218 $s += ($w*$wk + $d)*$day + $h*3600 + $mn*60;
2526              
2527 114         240 ($y,$m) = __normalize_ym($y,$m);
2528 114         232 ($w,$d,$s) = __normalize_wd($w,$d,$s,$wk,$day);
2529 114         211 ($h,$mn,$s) = __normalize_hms($h,$mn,$s);
2530              
2531 114         278 return ($y,$m,$w,$d,$h,$mn,$s);
2532             }
2533              
2534             sub _normalize_exact {
2535 1469     1469   3107 my($self,$y,$m,$w,$d,$h,$mn,$s) = @_;
2536 170     170   19868 no integer;
  170         227  
  170         453  
2537              
2538 1469         2480 $s += $h*3600 + $mn*60;
2539              
2540 1469         3193 ($h,$mn,$s) = __normalize_hms($h,$mn,$s);
2541              
2542 1469         3513 return ($y,$m,$w,$d,$h,$mn,$s);
2543             }
2544             sub _normalize_bus_exact {
2545 234     234   547 my($self,$y,$m,$w,$d,$h,$mn,$s) = @_;
2546 170     170   11738 no integer;
  170         221  
  170         498  
2547              
2548 234         427 my $day = $$self{'data'}{'len'}{'bdlength'};
2549              
2550 234         478 $s += $d*$day + $h*3600 + $mn*60;
2551              
2552             # Calculate d
2553              
2554 234         445 $d = int($s/$day);
2555 234         320 $s -= $d*$day;
2556              
2557 234         514 ($h,$mn,$s) = __normalize_hms($h,$mn,$s);
2558              
2559 234         557 return ($y,$m,$w,$d,$h,$mn,$s);
2560             }
2561              
2562             ###############################################################################
2563             # Timezone critical dates
2564              
2565             # NOTE: Although I would prefer to stick this routine in the
2566             # Date::Manip::TZ module where it would be more appropriate, it must
2567             # appear here as it will be used to generate the data that will be
2568             # used by the Date::Manip::TZ module.
2569             #
2570             # This calculates a critical date based on timezone information. The
2571             # critical date is the date (usually in the current time) at which
2572             # the current timezone period ENDS.
2573             #
2574             # Input is:
2575             # $year,$mon,$flag,$num,$dow
2576             # This is information from the appropriate Rule line from the
2577             # zoneinfo files. These are used to determine the date (Y/M/D)
2578             # when the timezone period will end.
2579             # $isdst
2580             # Whether or not the next timezone period is a Daylight Saving
2581             # Time period.
2582             # $time,$timetype
2583             # The time of day when the change occurs. The timetype can be
2584             # 'w' (wallclock time in the current period), 's' (standard
2585             # time which will match wallclock time in a non-DST period, or
2586             # be off an hour in a DST period), and 'u' (universal time).
2587             #
2588             # Output is:
2589             # $endUT, $endLT, $begUT, $begLT
2590             # endUT is the actual last second of the current timezone
2591             # period. endLT is the same time expressed in local time.
2592             # begUT is the start (in UT) of the next time period. Note that
2593             # the begUT date is the one which actually corresponds to the
2594             # date/time specified in the input. begLT is the time in the new
2595             # local time. The endUT/endLT are the time one second earlier.
2596             #
2597             sub _critical_date {
2598 43     43   97519 my($self,$year,$mon,$flag,$num,$dow,
2599             $isdst,$time,$timetype,$stdoff,$dstoff) = @_;
2600              
2601             #
2602             # Get the predicted Y/M/D
2603             #
2604              
2605 43         88 my($y,$m,$d) = ($year+0,$mon+0,1);
2606              
2607 43 100       3794 if ($flag eq 'dom') {
    100          
    50          
    0          
2608 1         3 $d = $num;
2609              
2610             } elsif ($flag eq 'last') {
2611 4         10 my $ymd = $self->nth_day_of_week($year,-1,$dow,$mon);
2612 4         222 $d = $$ymd[2];
2613              
2614             } elsif ($flag eq 'ge') {
2615 38         109 my $ymd = $self->nth_day_of_week($year,1,$dow,$mon);
2616 38         48 $d = $$ymd[2];
2617 38         65 while ($d < $num) {
2618 24         50 $d += 7;
2619             }
2620              
2621             } elsif ($flag eq 'le') {
2622 0         0 my $ymd = $self->nth_day_of_week($year,-1,$dow,$mon);
2623 0         0 $d = $$ymd[2];
2624 0         0 while ($d > $num) {
2625 0         0 $d -= 7;
2626             }
2627             }
2628              
2629             #
2630             # Get the predicted time and the date (not yet taking into
2631             # account time type).
2632             #
2633              
2634 43         48 my($h,$mn,$s) = @{ $self->split('hms',$time) };
  43         106  
2635 43         87 my $date = [ $y,$m,$d,$h,$mn,$s ];
2636              
2637             #
2638             # Calculate all the relevant dates.
2639             #
2640              
2641 43         51 my($endUT,$endLT,$begUT,$begLT,$offset);
2642 43         67 $stdoff = $self->split('offset',$stdoff);
2643 43         70 $dstoff = $self->split('offset',$dstoff);
2644              
2645 43 100       71 if ($timetype eq 'w') {
    100          
2646 39 100       107 $begUT = $self->calc_date_time($date,($isdst ? $stdoff : $dstoff), 1);
2647             } elsif ($timetype eq 'u') {
2648 2         3 $begUT = $date;
2649             } else {
2650 2         5 $begUT = $self->calc_date_time($date,$stdoff, 1);
2651             }
2652              
2653 43         86 $endUT = $self->calc_date_time($begUT,[0,0,-1]);
2654 43 100       92 $endLT = $self->calc_date_time($endUT,($isdst ? $stdoff : $dstoff));
2655 43 100       80 $begLT = $self->calc_date_time($begUT,($isdst ? $dstoff : $stdoff));
2656              
2657 43         162 return ($endUT,$endLT,$begUT,$begLT);
2658             }
2659              
2660             ###############################################################################
2661             # Get a list of strings to try to parse.
2662              
2663             sub _encoding {
2664 4619     4619   7148 my($self,$string) = @_;
2665 4619         5522 my @ret;
2666              
2667 4619         5078 foreach my $enc (@{ $$self{'data'}{'calc'}{'enc_in'} }) {
  4619         12764  
2668 9591 100       18255 if (lc($enc) eq 'utf-8') {
    100          
2669 4619         13524 _utf8_on($string);
2670 4619 100       13496 push(@ret,$string) if is_utf8($string, 1);
2671             } elsif (lc($enc) eq 'perl') {
2672 4619         13249 push(@ret,encode_utf8($string));
2673             } else {
2674 353         430 my $tmp = $string;
2675 353         662 _utf8_off($tmp);
2676 353         2072 $tmp = encode_utf8(decode($enc, $tmp));
2677 353         38777 _utf8_on($tmp);
2678 353 50       989 push(@ret,$tmp) if is_utf8($tmp, 1);;
2679             }
2680             }
2681              
2682 4619         11655 return @ret;
2683             }
2684              
2685             1;
2686             # Local Variables:
2687             # mode: cperl
2688             # indent-tabs-mode: nil
2689             # cperl-indent-level: 3
2690             # cperl-continued-statement-offset: 2
2691             # cperl-continued-brace-offset: 0
2692             # cperl-brace-offset: 0
2693             # cperl-brace-imaginary-offset: 0
2694             # cperl-label-offset: 0
2695             # End: