File Coverage

lib/Date/Manip/Base.pm
Criterion Covered Total %
statement 1073 1169 91.7
branch 469 578 81.1
condition 202 306 66.0
subroutine 112 113 99.1
pod 21 21 100.0
total 1877 2187 85.8


line stmt bran cond sub pod time code
1             package Date::Manip::Base;
2             # Copyright (c) 1995-2023 Sullivan Beck. All rights reserved.
3             # This program is free software; you can redistribute it and/or modify it
4             # under the same terms as Perl itself.
5              
6             ###############################################################################
7             # Any routine that starts with an underscore (_) is NOT intended for
8             # public use. They are for internal use in the the Date::Manip
9             # modules and are subject to change without warning or notice.
10             #
11             # ABSOLUTELY NO USER SUPPORT IS OFFERED FOR THESE ROUTINES!
12             ###############################################################################
13              
14             require 5.010000;
15 168     168   1027 use strict;
  168         307  
  168         4204  
16 168     168   702 use warnings;
  168         248  
  168         3224  
17 168     168   636 use integer;
  168         264  
  168         748  
18 168     168   2543 use utf8;
  168         246  
  168         554  
19 168     168   3395 use Carp;
  168         293  
  168         8188  
20             #use re 'debug';
21              
22 168     168   918 use Date::Manip::Obj;
  168         311  
  168         3744  
23 168     168   64368 use Date::Manip::TZ_Base;
  168         376  
  168         7620  
24             our @ISA = qw(Date::Manip::Obj Date::Manip::TZ_Base);
25              
26 168     168   75336 use Encode qw(encode_utf8 from_to find_encoding decode _utf8_off _utf8_on is_utf8);
  168         1585266  
  168         116882  
27             require Date::Manip::Lang::index;
28              
29             our $VERSION;
30             $VERSION='6.92';
31 168     168   771 END { undef $VERSION; }
32              
33             ###############################################################################
34             # BASE METHODS
35             ###############################################################################
36              
37             sub _init {
38 499     499   1163 my($self) = @_;
39              
40 499         2064 $self->_init_cache();
41 499         2001 $self->_init_language();
42 499         1948 $self->_init_config();
43 499         1952 $self->_init_events();
44 499         1591 $self->_init_holidays();
45 499         1579 $self->_init_now();
46              
47 499         1250 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 499     499   1094 my($self) = @_;
54 499 50       2676 return if (exists $$self{'cache'}{'init'});
55 499         1403 $$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 499         1311 $$self{'cache'}{'ly'} = {};
62 499         1282 $$self{'cache'}{'ds1_mon'} = {};
63 499         1321 $$self{'cache'}{'dow_mon'} = {};
64              
65 499         994 return;
66             }
67              
68             # Config dependent data. Needs to be reset every time the config is reset.
69             sub _init_data {
70 500     500   1196 my($self,$force) = @_;
71 500 100 66     2005 return if (exists $$self{'data'}{'calc'} && ! $force);
72              
73 499         1327 $$self{'data'}{'calc'} = {}; # Calculated values
74              
75 499         909 return;
76             }
77              
78             # Initializes config dependent data
79             sub _init_config {
80 500     500   1285 my($self,$force) = @_;
81 500 50 66     2279 return if (exists $$self{'data'}{'sections'}{'conf'} && ! $force);
82 500         1957 $self->_init_data();
83              
84             #
85             # Set config defaults
86             #
87              
88 500         9933 $$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 0, use the ISO 8601 standard that Jan 4 is in week
140             # 1. If 1, make week 1 contain Jan 1.
141              
142             'jan1week1' => '',
143              
144             # Date::Manip printable format
145             # 0 = YYYYMMDDHH:MN:SS
146             # 1 = YYYYHHMMDDHHMNSS
147             # 2 = YYYY-MM-DD-HH:MN:SS
148              
149             'printable' => '',
150              
151             # If 'today' is a holiday, we look either to 'tomorrow' or
152             # 'yesterday' for the nearest business day. By default, we'll
153             # always look 'tomorrow' first.
154              
155             'tomorrowfirst' => 1,
156              
157             # Used to set the current date/time/timezone.
158              
159             'forcedate' => 0,
160             'setdate' => 0,
161              
162             # Use this to set the default range of the recurrence.
163              
164             'recurrange' => '',
165             'maxrecurattempts' => 100,
166              
167             # Use this to set the default time.
168              
169             'defaulttime' => 'midnight',
170              
171             # Whether or not to use a period as a time separator.
172              
173             'periodtimesep' => 0,
174              
175             # How to parse mmm#### strings
176              
177             'format_mmmyyyy' => '',
178              
179             # *** DEPRECATED 7.0 ***
180              
181             'tz' => '',
182             };
183              
184             #
185             # Calculate delta field lengths
186             #
187              
188             # non-business
189 500         3066 $$self{'data'}{'len'}{'standard'} =
190             { 'yl' => 31556952, # 365.2425 * 24 * 3600
191             'ml' => 2629746, # yl / 12
192             'wl' => 604800, # 6 * 24 * 3600
193             'dl' => 86400, # 24 * 3600
194             };
195 500         2001 $self->_calc_workweek();
196              
197             #
198             # Initialize some config variables that do some additional work.
199             #
200              
201 500         2790 $self->_config_var('workday24hr', 1);
202 500         1697 $self->_config_var('workdaybeg', '08:00:00');
203 500         1790 $self->_config_var('workdayend', '17:00:00');
204 500         1778 $self->_config_var('workday24hr', 0);
205              
206 500         1456 $self->_config_var('dateformat', 'US');
207 500         1499 $self->_config_var('yytoyyyy', 89);
208 500         1518 $self->_config_var('jan1week1', 0);
209 500         1512 $self->_config_var('printable', 0);
210 500         1439 $self->_config_var('firstday', 1);
211 500         1489 $self->_config_var('workweekbeg', 1);
212 500         1571 $self->_config_var('workweekend', 5);
213 500         1495 $self->_config_var('language', 'english');
214 500         2434 $self->_config_var('recurrange', 'none');
215 500         2802 $self->_config_var('maxrecurattempts', 100);
216 500         1570 $self->_config_var('defaulttime', 'midnight');
217              
218             # Set OS specific defaults
219              
220 500         1702 my $os = $self->_os();
221              
222 500         1026 return;
223             }
224              
225             sub _calc_workweek {
226 1528     1528   2999 my($self,$beg,$end) = @_;
227              
228 1528 100       5208 $beg = $self->_config('workweekbeg') if (! $beg);
229 1528 100       4242 $end = $self->_config('workweekend') if (! $end);
230              
231 1528         3322 $$self{'data'}{'len'}{'workweek'} = $end - $beg + 1;
232              
233 1528         1929 return;
234             }
235              
236             sub _calc_bdlength {
237 1538     1538   2766 my($self) = @_;
238              
239 1538         1878 my @beg = @{ $$self{'data'}{'calc'}{'workdaybeg'} };
  1538         3479  
240 1538         2035 my @end = @{ $$self{'data'}{'calc'}{'workdayend'} };
  1538         2742  
241              
242 1538         3765 $$self{'data'}{'len'}{'bdlength'} =
243             ($end[0]-$beg[0])*3600 + ($end[1]-$beg[1])*60 + ($end[2]-$beg[2]);
244              
245 1538         2465 return;
246             }
247              
248             sub _init_business_length {
249 2566     2566   3853 my($self) = @_;
250              
251 168     168   1363 no integer;
  168         320  
  168         1033  
252 2566         4014 my $x = $$self{'data'}{'len'}{'workweek'};
253 2566         4754 my $y_to_d = $x/7 * 365.2425;
254 2566         3557 my $d_to_s = $$self{'data'}{'len'}{'bdlength'};
255 2566         3072 my $w_to_d = $x;
256              
257 2566         10586 $$self{'data'}{'len'}{'business'} = { 'yl' => $y_to_d * $d_to_s,
258             'ml' => $y_to_d * $d_to_s / 12,
259             'wl' => $w_to_d * $d_to_s,
260             'dl' => $d_to_s,
261             };
262              
263 2566         3989 return;
264             }
265              
266             # Events and holidays are reset only when they are read in.
267             sub _init_events {
268 513     513   1358 my($self,$force) = @_;
269 513 50 66     1965 return if (exists $$self{'data'}{'events'} && ! $force);
270              
271             # {data}{sections}{events} = [ STRING, EVENT_NAME, ... ]
272             #
273             # {data}{events}{I}{type} = TYPE
274             # {name} = NAME
275             # TYPE: specified An event with a start/end date (only parsed once)
276             # {beg} = DATE_OBJECT
277             # {end} = DATE_OBJECT
278             # TYPE: ym
279             # {beg} = YM_STRING
280             # {end} = YM_STRING (only for YM;YM)
281             # {YEAR} = [ DATE_OBJECT, DATE_OBJECT ]
282             # TYPE: date An event specified by a date string and delta
283             # {beg} = DATE_STRING
284             # {end} = DATE_STRING (only for Date;Date)
285             # {delta} = DELTA_OBJECT (only for Date;Delta)
286             # {YEAR} = [ DATE_OBJECT, DATE_OBJECT ]
287             # TYPE: recur
288             # {recur} = RECUR_OBJECT
289             # {delta} = DELTA_OBJECT
290             #
291             # {data}{eventyears}{YEAR} = 0/1
292             # {data}{eventobjs} = 0/1
293              
294 513         1432 $$self{'data'}{'events'} = {};
295 513         1396 $$self{'data'}{'sections'}{'events'} = [];
296 513         1284 $$self{'data'}{'eventyears'} = {};
297 513         1104 $$self{'data'}{'eventobjs'} = 0;
298              
299 513         805 return;
300             }
301              
302             sub _init_holidays {
303 517     517   1130 my($self,$force) = @_;
304 517 50 66     1816 return if (exists $$self{'data'}{'holidays'} && ! $force);
305              
306             # {data}{sections}{holidays} = [ STRING, HOLIDAY_NAME, ... ]
307             #
308             # {data}{holidays}{init} = 1 if holidays have been initialized
309             # {ydone} = { Y => 1 }
310             # {yhols} = { Y => NAME => [Y,M,D] }
311             # {hols} = { NAME => Y => [Y,M,D] }
312             # {dates} = { Y => M => D => NAME }
313             # {defs} = [ NAME DEF NAME DEF ... ]
314             # NAME is the name of a holiday (it will
315             # be 'DMunnamed I' for the Ith unnamed
316             # holiday)
317             # DEF is a string or a Recur
318             # {data}{init_holidays} = 1 if currently initializing holidays
319              
320 517         1609 $$self{'data'}{'holidays'} = {};
321 517         1278 $$self{'data'}{'sections'}{'holidays'} = [];
322 517         1127 $$self{'data'}{'init_holidays'} = 0;
323              
324 517         791 return;
325             }
326              
327             sub _init_now {
328 499     499   972 my($self) = @_;
329              
330             # {'data'}{'now'} = {
331             # date => [Y,M,D,H,MN,S] now
332             # isdst => ISDST
333             # offset => [H,MN,S]
334             # abb => ABBREV
335             #
336             # force => 0/1 SetDate/ForceDate information
337             # set => 0/1
338             # setsecs => SECS time (secs since epoch) when
339             # SetDate was called
340             # setdate => [Y,M,D,H,MN,S] date (IN GMT) we're calling
341             # now when SetDate was called
342             #
343             # tz => ZONE timezone we're working in
344             # systz => ZONE timezone of the system
345             # }
346             #
347              
348 499         1849 $$self{'data'}{'now'} = {};
349 499         1430 $$self{'data'}{'now'}{'force'} = 0;
350 499         1285 $$self{'data'}{'now'}{'set'} = 0;
351 499         1123 $$self{'data'}{'tmpnow'} = [];
352              
353 499         783 return;
354             }
355              
356             # Language information only needs to be initialized if the language changes.
357             sub _init_language {
358 1032     1032   2082 my($self,$force) = @_;
359 1032 50 66     3946 return if (exists $$self{'data'}{'lang'} && ! $force);
360              
361 1032         2555 $$self{'data'}{'lang'} = {}; # Current language info
362 1032         3600 $$self{'data'}{'rx'} = {}; # Regexps generated from language
363 1032         2031 $$self{'data'}{'words'} = {}; # Types of words in the language
364 1032         2816 $$self{'data'}{'wordval'} = {}; # Value of words in the language
365              
366 1032         1597 return;
367             }
368              
369             ###############################################################################
370             # MAIN METHODS
371             ###############################################################################
372              
373             # Use an algorithm from Calendar FAQ (except that I subtract 305 to get
374             # Jan 1, 0001 = day #1).
375             #
376             sub days_since_1BC {
377 12393     12393 1 23149 my($self,$arg) = @_;
378              
379 12393 100       17594 if (ref($arg)) {
380 7664         10738 my($y,$m,$d) = @$arg;
381 7664         9528 $m = ($m + 9) % 12;
382 7664         9790 $y = $y - $m/10;
383 7664         19273 return 365*$y + $y/4 - $y/100 + $y/400 + ($m*306 + 5)/10 + ($d - 1) - 305;
384             } else {
385 4729         5485 my $g = $arg + 305;
386 168     168   79705 no integer;
  168         360  
  168         688  
387 4729         9191 my $y = int((10000*$g + 14780)/3652425);
388 168     168   7096 use integer;
  168         313  
  168         678  
389 4729         7474 my $ddd = $g - (365*$y + $y/4 - $y/100 + $y/400);
390 4729 100       7670 if ($ddd < 0) {
391 6         9 $y = $y - 1;
392 6         13 $ddd = $g - (365*$y + $y/4 - $y/100 + $y/400);
393             }
394 4729         6321 my $mi = (100*$ddd + 52)/3060;
395 4729         6029 my $mm = ($mi + 2) % 12 + 1;
396 4729         5627 $y = $y + ($mi + 2)/12;
397 4729         6111 my $dd = $ddd - ($mi*306 + 5)/10 + 1;
398 4729         8504 return [$y, $mm, $dd];
399             }
400             }
401              
402             # Algorithm from the Calendar FAQ
403             #
404             sub day_of_week {
405 9932     9932 1 20656 my($self,$date) = @_;
406 9932         14440 my($y,$m,$d) = @$date;
407              
408 9932         13226 my $a = (14-$m)/12;
409 9932         11517 $y = $y-$a;
410 9932         11926 $m = $m + 12*$a - 2;
411 9932         16597 my $dow = ($d + $y + $y/4 - $y/100 + $y/400 + (31*$m)/12) % 7;
412 9932 100       16079 $dow = 7 if ($dow==0);
413 9932         15861 return $dow;
414             }
415              
416             sub leapyear {
417 3780     3780 1 7970 my($self,$y) = @_;
418 3780 100 100     15416 return 1 if ( ( ($y % 4 == 0) and ($y % 100 != 0) ) or
      100        
419             $y % 400 == 0 );
420 2416         4420 return 0;
421             }
422              
423             sub days_in_year {
424 367     367 1 3106 my($self,$y) = @_;
425 367 100       700 return ($self->leapyear($y) ? 366 : 365);
426             }
427              
428             # Uses algorithm from:
429             # http://www.dispersiondesign.com/articles/time/number_of_days_in_a_month
430             #
431             sub days_in_month {
432 37180     37180 1 63441 my($self,$y,$m) = @_;
433 37180 100       67584 if (! $m) {
    100          
434 2 100       4 return (31,29,31,30, 31,30,31,31, 30,31,30,31) if ($self->leapyear($y));
435 1         5 return (31,28,31,30, 31,30,31,31, 30,31,30,31);
436              
437             } elsif ($m == 2) {
438 2904         5889 return 28 + $self->leapyear($y);
439              
440             } else {
441 34274         60625 return 31 - ($m-1) % 7 % 2;
442             }
443             }
444              
445             {
446             # DinM = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
447             my(@doy_days) = ( [0, 31, 59, 90,120,151,181,212,243,273,304,334,365],
448             [0, 31, 60, 91,121,152,182,213,244,274,305,335,366],
449             );
450              
451              
452             sub day_of_year {
453 674     674 1 30340 my($self,@args) = @_;
454 168     168   63531 no integer;
  168         322  
  168         600  
455 674         1009 my($n,$ly,$tmp,$remain,$day,$y,$m,$d,$h,$mn,$s,$time);
456              
457 674 100       1250 if (@args == 2) {
458             # $date = day_of_year($y,$day);
459              
460 265         419 ($y,$tmp) = @args;
461              
462 265         440 $ly = $self->leapyear($y);
463 265 100       735 $time = 1 if ($tmp =~ /\./);
464 265         336 $n = int($tmp);
465 265         330 $remain = $tmp - $n;
466              
467             # Calculate the month and the day
468 265         542 for ($m=1; $m<=12; $m++) {
469 1065 100       1925 last if ($n<=($doy_days[$ly][$m]));
470             }
471 265         421 $d = $n-($doy_days[$ly][$m-1]);
472 265 100       1026 return [$y,$m,$d] if (! $time);
473              
474             # Calculate the hours, minutes, and seconds into the day.
475              
476 9         14 $s = $remain * 86400;
477 9         13 $mn = int($s/60);
478 9         12 $s = $s - ($mn*60);
479 9 100       50 $s = sprintf('%0.2f',$s) if ("$s" ne int($s));
480 9         14 $h = int($mn/60);
481 9         14 $mn = $mn % 60;
482              
483 9         36 return [$y,$m,$d,$h,$mn,$s];
484              
485             } else {
486 409         505 ($y,$m,$d,$h,$mn,$s) = @{ $args[0] };
  409         746  
487              
488 409 100       910 $ly = ($m > 2 ? $self->leapyear($y) : 0);
489 409         810 $day = ($doy_days[$ly][$m-1]+$d);
490              
491 409 100       1048 return $day if (! defined $h);
492              
493 30         63 $day += ($h*3600 + $mn*60 + $s)/86400;
494 30         57 return $day;
495             }
496             }
497             }
498              
499             # Can be the nth DoW of year or month (if $m given). Returns undef if
500             # the date doesn't exists (i.e. 5th Sunday in a month with only 4).
501             #
502             sub nth_day_of_week {
503 997     997 1 11618 my($self,$y,$n,$dow,$m) = @_;
504 997         1211 $y += 0;
505 997 100       1649 $m = ($m ? $m+0 : 0);
506              
507             # $d is the current DoM (if $m) or DoY
508             # $max is the max value allowed for $d
509             # $ddow is the DoW of $d
510              
511 997         1341 my($d,$max,$ddow);
512              
513 997 100       1491 if ($m) {
514 913         1532 $max = $self->days_in_month($y,$m);
515 913 100       1587 $d = ($n<0 ? $max : 1);
516 913         1968 $ddow = $self->day_of_week([$y,$m,$d]);
517             } else {
518 84         171 $max = $self->days_in_year($y);
519 84 50       149 $d = ($n<0 ? $max : 1);
520 84 50       137 if ($n<0) {
521 0         0 $d = $max;
522 0         0 $ddow = $self->day_of_week([$y,12,31]);
523             } else {
524 84         103 $d = 1;
525 84         187 $ddow = $self->day_of_week([$y,1,1]);
526             }
527             }
528              
529             # Find the first occurrence of $dow on or after $d (if $n>0)
530             # or the last occurrence of $dow on or before $d (if ($n<0);
531              
532 997 100       1977 if ($dow < $ddow) {
533 550         768 $d += 7 - ($ddow-$dow);
534             } else {
535 447         649 $d += ($dow-$ddow);
536             }
537 997 100       1624 $d -= 7 if ($d > $max);
538              
539             # Find the nth occurrence of $dow
540              
541 997 100       1717 if ($n > 1) {
    100          
542 847         1150 $d += 7*($n-1);
543 847 50       1454 return undef if ($d > $max);
544             } elsif ($n < -1) {
545 2         4 $d -= 7*(-1*$n-1);
546 2 50       3 return undef if ($d < 1);
547             }
548              
549             # Return the date
550              
551 997 100       1636 if ($m) {
552 913         2006 return [$y,$m,$d];
553             }
554 84         170 return $self->day_of_year($y,$d);
555             }
556              
557             {
558             # Integer arithmetic doesn't work due to the size of the numbers.
559 168     168   79701 no integer;
  168         374  
  168         663  
560             # my $sec_70 =($self->days_since_1BC([1970,1,1])-1)*24*3600;
561             my $sec_70 = 62135596800;
562              
563             # Using 'global' variables saves 4%
564             my($y,$m,$d,$h,$mn,$s,$sec,$sec_0,$tmp);
565             sub secs_since_1970 {
566 2560     2560 1 9803 my($self,$arg) = @_;
567              
568 2560 100       4519 if (ref($arg)) {
569 2555         4450 ($y,$m,$d,$h,$mn,$s) = @$arg;
570 2555         5718 $sec_0 = ($self->days_since_1BC([$y,$m,$d])-1)*24*3600 + $h*3600 +
571             $mn*60 + $s;
572 2555         3803 $sec = $sec_0 - $sec_70;
573 2555         4860 return $sec;
574              
575             } else {
576 5         9 ($sec) = $arg;
577 5         10 $sec_0 = $sec_70 + $sec;
578 5         14 $tmp = int($sec_0/24/3600)+1;
579 5         10 my $ymd = $self->days_since_1BC($tmp);
580 5         10 ($y,$m,$d) = @$ymd;
581 5         10 $sec_0 -= ($tmp-1)*24*3600;
582 5         7 $h = int($sec_0/3600);
583 5         6 $sec_0 -= $h*3600;
584 5         7 $mn = int($sec_0/60);
585 5         8 $s = $sec_0 - $mn*60;
586 5         14 return [$y,$m,$d,$h,$mn,$s];
587             }
588             }
589             }
590              
591             sub check {
592 15339     15339 1 33398 my($self,$date) = @_;
593 15339         26263 my($y,$m,$d,$h,$mn,$s) = @$date;
594              
595 15339 100 66     36849 return 0 if (! $self->check_time([$h,$mn,$s]) ||
      66        
      33        
      66        
596             $y<1 || $y>9999 ||
597             $m<1 || $m>12);
598              
599 15328         36829 my $days = $self->days_in_month($y,$m);
600              
601 15328 100 66     43000 return 0 if ($d<1 || $d>$days);
602 15324         33947 return 1;
603             }
604              
605             sub check_time {
606 15403     15403 1 21086 my($self,$hms) = @_;
607 15403         21814 my($h,$mn,$s) = @$hms;
608              
609 15403 100 66     147996 return 0 if ("$h:$mn:$s" !~ /^\d\d?:\d\d?:\d\d?$/o ||
      66        
      66        
      66        
      66        
      66        
610             $h > 24 || $mn > 59 || $s > 59 ||
611             ($h == 24 && ($mn || $s)));
612 15399         95444 return 1;
613             }
614              
615             sub week1_day1 {
616 28     28 1 3315 my($self,$year) = @_;
617 28         58 my $firstday = $self->_config('firstday');
618 28         50 return $self->_week1_day1($firstday,$year);
619             }
620              
621             sub _week1_day1 {
622 600     600   954 my($self,$firstday,$year) = @_;
623 600         1003 my $jan1week1 = $self->_config('jan1week1');
624             return $$self{'cache'}{'week1day1'}{$firstday}{$jan1week1}{$year}
625 600 100       1886 if (exists $$self{'cache'}{'week1day1'}{$firstday}{$jan1week1}{$year});
626              
627             # First week contains either Jan 4 (default) or Jan 1
628              
629 274         443 my($y,$m,$d) = ($year,1,4);
630 274 100       461 $d = 1 if ($jan1week1);
631              
632             # Go back to the previous (counting today) $firstday
633              
634 274         680 my $dow = $self->day_of_week([$y,$m,$d]);
635 274 100       581 if ($dow != $firstday) {
636 243 100       412 $firstday = 0 if ($firstday == 7);
637 243         287 $d -= ($dow-$firstday);
638 243 100       382 if ($d<1) {
639 153         201 $y--;
640 153         227 $m = 12;
641 153         176 $d += 31;
642             }
643             }
644              
645 274         667 $$self{'cache'}{'week1day1'}{$firstday}{$jan1week1}{$year} = [ $y,$m,$d ];
646 274         634 return [$y,$m,$d];
647             }
648              
649             sub weeks_in_year {
650 77     77 1 2624 my($self,$y) = @_;
651 77         190 my $firstday = $self->_config('firstday');
652 77         191 return $self->_weeks_in_year($firstday,$y);
653             }
654              
655             sub _weeks_in_year {
656 285     285   485 my($self,$firstday,$y) = @_;
657 285         542 my $jan1week1 = $self->_config('jan1week1');
658             return $$self{'cache'}{'wiy'}{$firstday}{$jan1week1}{$y}
659 285 100       1048 if (exists $$self{'cache'}{'wiy'}{$firstday}{$jan1week1}{$y});
660              
661             # Get the week1 day1 dates for this year and the next one.
662 108         137 my ($y1,$m1,$d1) = @{ $self->_week1_day1($firstday,$y) };
  108         227  
663 108         200 my ($y2,$m2,$d2) = @{ $self->_week1_day1($firstday,$y+1) };
  108         215  
664              
665             # Calculate the number of days between them.
666 108         260 my $diy = $self->days_in_year($y);
667 108 100       203 if ($y1 < $y) {
668 58         84 $diy += (32-$d1);
669             } else {
670 50         91 $diy -= ($d1-1);
671             }
672 108 100       218 if ($y2 < $y+1) {
673 60         73 $diy -= (32-$d2);
674             } else {
675 48         81 $diy += ($d2-1);
676             }
677              
678 108         136 $diy = $diy/7;
679 108         192 $$self{'cache'}{'wiy'}{$firstday}{$jan1week1}{$y} = $diy;
680 108         241 return $diy;
681             }
682              
683             sub week_of_year {
684 525     525 1 7999 my($self,@args) = @_;
685 525         993 my $firstday = $self->_config('firstday');
686 525         1124 return $self->_week_of_year($firstday,@args);
687             }
688              
689             sub _week_of_year {
690 533     533   969 my($self,$firstday,@args) = @_;
691 533         886 my $jan1week1 = $self->_config('jan1week1');
692              
693 533 100       1098 if ($#args == 1) {
694             # (y,m,d) = week_of_year(y,w)
695 325         498 my($year,$w) = @args;
696              
697             return $$self{'cache'}{'woy1'}{$firstday}{$jan1week1}{$year}{$w}
698 325 100       1414 if (exists $$self{'cache'}{'woy1'}{$firstday}{$jan1week1}{$year}{$w});
699              
700 148         319 my $ymd = $self->_week1_day1($firstday,$year);
701 148 100       463 $ymd = $self->calc_date_days($ymd,($w-1)*7) if ($w > 1);
702              
703 148         369 $$self{'cache'}{'woy1'}{$firstday}{$jan1week1}{$year}{$w} = $ymd;
704 148         478 return $ymd;
705             }
706              
707             # (y,w) = week_of_year([y,m,d])
708 208         266 my($y,$m,$d) = @{ $args[0] };
  208         380  
709              
710             # Get the first day of the first week. If the date is before that,
711             # it's the last week of last year.
712              
713 208         256 my($y0,$m0,$d0) = @{ $self->_week1_day1($firstday,$y) };
  208         449  
714 208 100 100     848 if ($y0==$y && $m==1 && $d<$d0) {
      100        
715 3         8 return($y-1,$self->_weeks_in_year($firstday,$y-1));
716             }
717              
718             # Otherwise, we'll figure out how many days are between the two and
719             # divide by 7 to figure out how many weeks in it is.
720              
721 205         599 my $n = $self->day_of_year([$y,$m,$d]);
722 205 100       406 if ($y0<$y) {
723 72         108 $n += (32-$d0);
724             } else {
725 133         195 $n -= ($d0-1);
726             }
727 205         331 my $w = 1+int(($n-1)/7);
728              
729             # Make sure we're not into the first week of next year.
730              
731 205 100       452 if ($w>$self->_weeks_in_year($firstday,$y)) {
732 5         18 return($y+1,1);
733             }
734 200         555 return($y,$w);
735             }
736              
737             ###############################################################################
738             # CALC METHODS
739             ###############################################################################
740              
741             sub calc_date_date {
742 18     18 1 40182 my($self,$date0,$date1) = @_;
743              
744             # Order them so date0 < date1
745             # If $minus = 1, then the delta is negative
746              
747 18         22 my $minus = 0;
748 18         45 my $cmp = $self->cmp($date0,$date1);
749              
750 18 100       56 if ($cmp == 0) {
    100          
751 4         14 return [0,0,0];
752              
753             } elsif ($cmp == 1) {
754 7         8 $minus = 1;
755 7         9 my $tmp = $date1;
756 7         7 $date1 = $date0;
757 7         8 $date0 = $tmp;
758             }
759              
760 14         27 my($y0,$m0,$d0,$h0,$mn0,$s0) = @$date0;
761 14         23 my($y1,$m1,$d1,$h1,$mn1,$s1) = @$date1;
762              
763 14 100 100     69 my $sameday = ($y0 == $y1 && $m0 == $m1 && $d0 == $d1 ? 1 : 0);
764              
765             # Handle the various cases.
766              
767 14         17 my($dh,$dm,$ds);
768 14 100       18 if ($sameday) {
769 4         4 ($dh,$dm,$ds) = @{ $self->_calc_hms_hms([$h0,$mn0,$s0],[$h1,$mn1,$s1]) };
  4         16  
770              
771             } else {
772             # y0-m0-d0 h0:mn0:s0 -> y0-m0-d0 24:00:00
773             # y1-m1-d1 h1:mn1:s1 -> y1-m1-d1 00:00:00
774              
775 10         30 my $t1 = $self->_calc_hms_hms([$h0,$mn0,$s0],[24,0,0]);
776 10         28 my $t2 = $self->_calc_hms_hms([0,0,0],[$h1,$mn1,$s1]);
777 10         15 ($dh,$dm,$ds) = @{ $self->calc_time_time($t1,$t2) };
  10         15  
778              
779 10         25 my $dd0 = $self->days_since_1BC([$y0,$m0,$d0]);
780 10         15 $dd0++;
781 10         22 my $dd1 = $self->days_since_1BC([$y1,$m1,$d1]);
782 10         18 $dh += ($dd1-$dd0)*24;
783             }
784              
785 14 100       25 if ($minus) {
786 7         9 $dh *= -1;
787 7         8 $dm *= -1;
788 7         8 $ds *= -1;
789             }
790 14         31 return [$dh,$dm,$ds];
791             }
792              
793             sub calc_date_days {
794 4721     4721 1 20682 my($self,$date,$n,$subtract) = @_;
795 4721         7756 my($y,$m,$d,$h,$mn,$s) = @$date;
796 4721 100       7798 my($ymdonly) = (defined $h ? 0 : 1);
797              
798 4721 100       7250 $n *= -1 if ($subtract);
799 4721         11346 my $d1bc = $self->days_since_1BC([$y,$m,$d]);
800 4721         7460 $d1bc += $n;
801 4721         7070 my $ymd = $self->days_since_1BC($d1bc);
802              
803 4721 100       7422 if ($ymdonly) {
804 2607         6513 return $ymd;
805             } else {
806 2114         7189 return [@$ymd,$h*1,$mn*1,$s*1];
807             }
808             }
809              
810             sub calc_date_delta {
811 8     8 1 29133 my($self,$date,$delta,$subtract) = @_;
812 8         16 my($y,$m,$d,$h,$mn,$s) = @$date;
813 8         17 my($dy,$dm,$dw,$dd,$dh,$dmn,$ds) = @$delta;
814              
815             ($y,$m,$d) =
816 8         9 @{ $self->_calc_date_ymwd([$y,$m,$d], [$dy,$dm,$dw,$dd], $subtract) };
  8         27  
817              
818 8         31 return $self->calc_date_time([$y,$m,$d,$h,$mn,$s],[$dh,$dmn,$ds],$subtract);
819             }
820              
821             sub calc_date_time {
822 12713     12713 1 73104 my($self,$date,$time,$subtract) = @_;
823 12713         19120 my($y,$m,$d,$h,$mn,$s) = @$date;
824 12713         17910 my($dh,$dmn,$ds) = @$time;
825              
826 12713 100 66     34542 if ($ds > 59 || $ds < -59) {
827 4         12 $dmn += int($ds/60);
828 4         7 $ds = $ds % 60;
829             }
830 12713 100 66     30076 if ($dmn > 59 || $dmn < -59) {
831 4         7 $dh += int($dmn/60);
832 4         7 $dmn = $dmn % 60;
833             }
834 12713         14252 my $dd = 0;
835 12713 100 100     29732 if ($dh > 23 || $dh < -23) {
836 34         50 $dd = int($dh/24);
837 34         43 $dh = $dh % 24;
838             }
839              
840             # Handle subtraction
841 12713 100       18565 if ($subtract) {
842 5592         6558 $dh *= -1;
843 5592         5831 $dmn *= -1;
844 5592         5891 $ds *= -1;
845 5592         6440 $dd *= -1;
846             }
847              
848 12713 100       17738 if ($dd == 0) {
849 12679         13223 $y *= 1;
850 12679         12839 $m *= 1;
851 12679         14387 $d *= 1;
852             } else {
853 34         42 ($y,$m,$d) = @{ $self->calc_date_days([$y,$m,$d],$dd) };
  34         94  
854             }
855              
856 12713         27289 $self->_mod_add(60,$ds,\$s,\$mn);
857 12713         25470 $self->_mod_add(60,$dmn,\$mn,\$h);
858 12713         23964 $self->_mod_add(24,$dh,\$h,\$d);
859              
860 12713 100       17772 if ($d<1) {
861 9         18 $m--;
862 9 100       27 $y--, $m=12 if ($m<1);
863 9         28 my $day_in_mon = $self->days_in_month($y,$m);
864 9         15 $d += $day_in_mon;
865             } else {
866 12704         20162 my $day_in_mon = $self->days_in_month($y,$m);
867 12704 100       20713 if ($d>$day_in_mon) {
868 68         87 $d -= $day_in_mon;
869 68         108 $m++;
870 68 100       161 $y++, $m=1 if ($m>12);
871             }
872             }
873              
874 12713         41590 return [$y,$m,$d,$h,$mn,$s];
875             }
876              
877             sub _calc_date_time_strings {
878 0     0   0 my($self,$date,$time,$subtract) = @_;
879 0         0 my @date = @{ $self->split('date',$date) };
  0         0  
880 0 0       0 return '' if (! @date);
881 0         0 my @time = @{ $self->split('time',$time) };
  0         0  
882              
883 0         0 my @date2 = @{ $self->calc_date_time(\@date,\@time,$subtract) };
  0         0  
884              
885 0         0 return $self->join('date',\@date2);
886             }
887              
888             sub _calc_date_ymwd {
889 2425     2425   32384 my($self,$date,$ymwd,$subtract) = @_;
890 2425         3985 my($y,$m,$d,$h,$mn,$s) = @$date;
891 2425         3906 my($dy,$dm,$dw,$dd) = @$ymwd;
892 2425 100       3975 my($ymdonly) = (defined $h ? 0 : 1);
893              
894 2425         3084 $dd += $dw*7;
895              
896 2425 100       3671 if ($subtract) {
897 1207         1540 $y -= $dy;
898 1207         3220 $self->_mod_add(-12,-1*$dm,\$m,\$y);
899 1207         1824 $dd *= -1;
900              
901             } else {
902 1218         1481 $y += $dy;
903 1218         2450 $self->_mod_add(-12,$dm,\$m,\$y);
904             }
905              
906 2425         4348 my $dim = $self->days_in_month($y,$m);
907 2425 100       4298 $d = $dim if ($d > $dim);
908              
909 2425         2704 my $ymd;
910 2425 100       3563 if ($dd == 0) {
911 2242         3668 $ymd = [$y,$m,$d];
912             } else {
913 183         424 $ymd = $self->calc_date_days([$y,$m,$d],$dd);
914             }
915              
916 2425 100       3689 if ($ymdonly) {
917 2423         4748 return $ymd;
918             } else {
919 2         50 return [@$ymd,$h,$mn,$s];
920             }
921             }
922              
923             sub _calc_hms_hms {
924 24     24   32 my($self,$hms0,$hms1) = @_;
925 24         36 my($h0,$m0,$s0,$h1,$m1,$s1) = (@$hms0,@$hms1);
926              
927 24         39 my($s) = ($h1-$h0)*3600 + ($m1-$m0)*60 + $s1-$s0;
928 24         26 my($m) = int($s/60);
929 24         25 $s -= $m*60;
930 24         26 my($h) = int($m/60);
931 24         22 $m -= $h*60;
932 24         45 return [$h,$m,$s];
933             }
934              
935             sub calc_time_time {
936 86     86 1 13107 my($self,$time0,$time1,$subtract) = @_;
937 86         165 my($h0,$m0,$s0,$h1,$m1,$s1) = (@$time0,@$time1);
938              
939 86 100       368 if ($subtract) {
940 51         78 $h1 *= -1;
941 51         62 $m1 *= -1;
942 51         66 $s1 *= -1;
943             }
944 86         153 my($s) = (($h0+$h1)*60 + ($m0+$m1))*60 + $s0+$s1;
945 86         108 my($m) = int($s/60);
946 86         104 $s -= $m*60;
947 86         108 my($h) = int($m/60);
948 86         99 $m -= $h*60;
949              
950 86         206 return [$h,$m,$s];
951             }
952              
953             ###############################################################################
954              
955             # Returns -1 if date0 is before date1, 0 if date0 is the same as date1, and
956             # 1 if date0 is after date1.
957             #
958             sub cmp {
959 7294     7294 1 19120 my($self,$date0,$date1) = @_;
960 7294   66     36809 return ($$date0[0] <=> $$date1[0] ||
961             $$date0[1] <=> $$date1[1] ||
962             $$date0[2] <=> $$date1[2] ||
963             $$date0[3] <=> $$date1[3] ||
964             $$date0[4] <=> $$date1[4] ||
965             $$date0[5] <=> $$date1[5]);
966             }
967              
968             ###############################################################################
969             # This determines the OS.
970              
971             sub _os {
972 1009     1009   1779 my($self) = @_;
973              
974 1009         1546 my $os = '';
975              
976 1009 50 33     13266 if ($^O =~ /MSWin32/io ||
    50 33        
    50 33        
      33        
      33        
977             $^O =~ /Windows_95/io ||
978             $^O =~ /Windows_NT/io
979             ) {
980 0         0 $os = 'Windows';
981              
982             } elsif ($^O =~ /MacOS/io ||
983             $^O =~ /MPE/io ||
984             $^O =~ /OS2/io ||
985             $^O =~ /NetWare/io
986             ) {
987 0         0 $os = 'Other';
988              
989             } elsif ($^O =~ /VMS/io) {
990 0         0 $os = 'VMS';
991              
992             } else {
993 1009         1710 $os = 'Unix';
994             }
995              
996 1009         2073 return $os;
997             }
998              
999             ###############################################################################
1000             # Config variable functions
1001              
1002             # $self->config(SECT);
1003             # Creates a new section (if it doesn't already exist).
1004             #
1005             # $self->config(SECT,'_vars');
1006             # Returns a list of (VAR VAL VAR VAL ...)
1007             #
1008             # $self->config(SECT,VAR,VAL);
1009             # Adds (VAR,VAL) to the list.
1010             #
1011             sub _section {
1012 253     253   413 my($self,$sect,$var,$val) = @_;
1013 253         331 $sect = lc($sect);
1014              
1015             #
1016             # $self->_section(SECT) creates a new section
1017             #
1018              
1019 253 0 33     427 if (! defined $var &&
1020             ! exists $$self{'data'}{'sections'}{$sect}) {
1021 0 0       0 if ($sect eq 'conf') {
1022 0         0 $$self{'data'}{'sections'}{$sect} = {};
1023             } else {
1024 0         0 $$self{'data'}{'sections'}{$sect} = [];
1025             }
1026 0         0 return '';
1027             }
1028              
1029 253 50       437 if ($var eq '_vars') {
1030 0         0 return @{ $$self{'data'}{'sections'}{$sect} };
  0         0  
1031             }
1032              
1033 253         257 push @{ $$self{'data'}{'sections'}{$sect} },($var,$val);
  253         591  
1034 253         543 return;
1035             }
1036              
1037             # This sets a config variable. It also performs all side effects from
1038             # setting that variable.
1039             #
1040             sub _config_var_base {
1041 7840     7840   11281 my($self,$var,$val) = @_;
1042              
1043 7840 100 33     57621 if ($var eq 'defaults') {
    100 33        
    100 100        
    50 100        
    50 100        
    100 66        
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
    100          
    100          
    100          
    100          
    50          
1044             # Reset the configuration if desired.
1045 1         5 $self->_init_config(1);
1046 1         5 return;
1047              
1048             } elsif ($var eq 'eraseholidays') {
1049 18         88 $self->_init_holidays(1);
1050 18         60 return;
1051              
1052             } elsif ($var eq 'eraseevents') {
1053 14         65 $self->_init_events(1);
1054 14         38 return;
1055              
1056             } elsif ($var eq 'configfile') {
1057 0         0 $self->_config_file($val);
1058 0         0 return;
1059              
1060             } elsif ($var eq 'encoding') {
1061 0         0 my $err = $self->_config_var_encoding($val);
1062 0 0       0 return if ($err);
1063              
1064             } elsif ($var eq 'language') {
1065 533         3440 my $err = $self->_language($val);
1066 533 50       1451 return if ($err);
1067 533         4599 $err = $self->_config_var_encoding();
1068 533 50       2989 return if ($err);
1069              
1070             } elsif ($var eq 'yytoyyyy') {
1071 527         1038 $val = lc($val);
1072 527 50 100     5973 if ($val ne 'c' &&
      100        
      66        
1073             $val !~ /^c\d\d$/o &&
1074             $val !~ /^c\d\d\d\d$/o &&
1075             $val !~ /^\d+$/o) {
1076 0         0 carp "ERROR: [config_var] invalid: YYtoYYYY: $val";
1077 0         0 return;
1078             }
1079              
1080             } elsif ($var eq 'workweekbeg') {
1081 514         1883 my $err = $self->_config_var_workweekbeg($val);
1082 514 50       2154 return if ($err);
1083              
1084             } elsif ($var eq 'workweekend') {
1085 514         2363 my $err = $self->_config_var_workweekend($val);
1086 514 50       2976 return if ($err);
1087              
1088             } elsif ($var eq 'workday24hr') {
1089 1014         2766 my $err = $self->_config_var_workday24hr($val);
1090 1014 50       2199 return if ($err);
1091              
1092             } elsif ($var eq 'workdaybeg') {
1093 520         1899 my $err = $self->_config_var_workdaybegend(\$val,'WorkDayBeg');
1094 520 50       2258 return if ($err);
1095              
1096             } elsif ($var eq 'workdayend') {
1097 518         1437 my $err = $self->_config_var_workdaybegend(\$val,'WorkDayEnd');
1098 518 50       1470 return if ($err);
1099              
1100             } elsif ($var eq 'firstday') {
1101 536         1606 my $err = $self->_config_var_firstday($val);
1102 536 50       2358 return if ($err);
1103              
1104             } elsif ($var eq 'tz' ||
1105             $var eq 'forcedate' ||
1106             $var eq 'setdate') {
1107             # These can only be used if the Date::Manip::TZ module has been loaded
1108 0         0 carp "ERROR: [config_var] $var config variable requires TZ module";
1109 0         0 return;
1110              
1111             } elsif ($var eq 'recurrange') {
1112 514         4137 my $err = $self->_config_var_recurrange($val);
1113 514 50       1330 return if ($err);
1114              
1115             } elsif ($var eq 'defaulttime') {
1116 516         1587 my $err = $self->_config_var_defaulttime($val);
1117 516 50       1256 return if ($err);
1118              
1119             } elsif ($var eq 'periodtimesep') {
1120             # We have to redo the time regexp
1121 1         2 delete $$self{'data'}{'rx'}{'time'};
1122              
1123             } elsif ($var eq 'format_mmmyyyy') {
1124 4         19 my $err = $self->_config_var_format_mmmyyyy($val);
1125 4 50       12 return if ($err);
1126              
1127             } elsif ($var eq 'dateformat' ||
1128             $var eq 'jan1week1' ||
1129             $var eq 'printable' ||
1130             $var eq 'maxrecurattempts' ||
1131             $var eq 'tomorrowfirst') {
1132             # do nothing
1133              
1134             } else {
1135 0         0 carp "ERROR: [config_var] invalid config variable: $var";
1136 0         0 return '';
1137             }
1138              
1139 7807         14916 $$self{'data'}{'sections'}{'conf'}{$var} = $val;
1140 7807         12935 return;
1141             }
1142              
1143             ###############################################################################
1144             # Specific config variable functions
1145              
1146             sub _config_var_encoding {
1147 533     533   1242 my($self,$val) = @_;
1148              
1149 533 50       2880 if (! $val) {
    0          
1150 533         2413 $$self{'data'}{'calc'}{'enc_in'} = [ @{ $$self{'data'}{'enc'} } ];
  533         1985  
1151 533         2917 $$self{'data'}{'calc'}{'enc_out'} = 'UTF-8';
1152              
1153             } elsif ($val =~ /^(.*),(.*)$/o) {
1154 0         0 my($in,$out) = ($1,$2);
1155 0 0       0 if ($in) {
1156 0         0 my $o = find_encoding($in);
1157 0 0       0 if (! $o) {
1158 0         0 carp "ERROR: [config_var] invalid: Encoding: $in";
1159 0         0 return 1;
1160             }
1161             }
1162 0 0       0 if ($out) {
1163 0         0 my $o = find_encoding($out);
1164 0 0       0 if (! $o) {
1165 0         0 carp "ERROR: [config_var] invalid: Encoding: $out";
1166 0         0 return 1;
1167             }
1168             }
1169              
1170 0 0 0     0 if ($in && $out) {
    0          
    0          
1171 0         0 $$self{'data'}{'calc'}{'enc_in'} = [ $in ];
1172 0         0 $$self{'data'}{'calc'}{'enc_out'} = $out;
1173              
1174             } elsif ($in) {
1175 0         0 $$self{'data'}{'calc'}{'enc_in'} = [ $in ];
1176 0         0 $$self{'data'}{'calc'}{'enc_out'} = 'UTF-8';
1177              
1178             } elsif ($out) {
1179 0         0 $$self{'data'}{'calc'}{'enc_in'} = [ @{ $$self{'data'}{'enc'} } ];
  0         0  
1180 0         0 $$self{'data'}{'calc'}{'enc_out'} = $out;
1181              
1182             } else {
1183 0         0 $$self{'data'}{'calc'}{'enc_in'} = [ @{ $$self{'data'}{'enc'} } ];
  0         0  
1184 0         0 $$self{'data'}{'calc'}{'enc_out'} = 'UTF-8';
1185             }
1186              
1187             } else {
1188 0         0 my $o = find_encoding($val);
1189 0 0       0 if (! $o) {
1190 0         0 carp "ERROR: [config_var] invalid: Encoding: $val";
1191 0         0 return 1;
1192             }
1193 0         0 $$self{'data'}{'calc'}{'enc_in'} = [ $val ];
1194 0         0 $$self{'data'}{'calc'}{'enc_out'} = $val;
1195             }
1196              
1197 533 100       2341 if (! @{ $$self{'data'}{'calc'}{'enc_in'} }) {
  533         2513  
1198 516         2961 $$self{'data'}{'calc'}{'enc_in'} = [ qw(utf-8 perl) ];
1199             }
1200              
1201 533         1957 return 0;
1202             }
1203              
1204             sub _config_var_recurrange {
1205 514     514   2028 my($self,$val) = @_;
1206              
1207 514         1909 $val = lc($val);
1208 514 50       4163 if ($val =~ /^(none|year|month|week|day|all)$/o) {
1209 514         1181 return 0;
1210             }
1211              
1212 0         0 carp "ERROR: [config_var] invalid: RecurRange: $val";
1213 0         0 return 1;
1214             }
1215              
1216             sub _config_var_workweekbeg {
1217 514     514   1228 my($self,$val) = @_;
1218              
1219 514 50       1427 if (! $self->_is_int($val,1,7)) {
1220 0         0 carp "ERROR: [config_var] invalid: WorkWeekBeg: $val";
1221 0         0 return 1;
1222             }
1223 514 50       1753 if ($val >= $self->_config('workweekend')) {
1224 0         0 carp "ERROR: [config_var] WorkWeekBeg must be before WorkWeekEnd";
1225 0         0 return 1;
1226             }
1227              
1228 514         1826 $self->_calc_workweek($val,'');
1229 514         1304 $self->_init_business_length();
1230 514         850 return 0;
1231             }
1232              
1233             sub _config_var_workweekend {
1234 514     514   1151 my($self,$val) = @_;
1235              
1236 514 50       2207 if (! $self->_is_int($val,1,7)) {
1237 0         0 carp "ERROR: [config_var] invalid: WorkWeekBeg: $val";
1238 0         0 return 1;
1239             }
1240 514 50       3287 if ($val <= $self->_config('workweekbeg')) {
1241 0         0 carp "ERROR: [config_var] WorkWeekEnd must be after WorkWeekBeg";
1242 0         0 return 1;
1243             }
1244              
1245 514         3241 $self->_calc_workweek('',$val);
1246 514         2091 $self->_init_business_length();
1247 514         795 return 0;
1248             }
1249              
1250             sub _config_var_workday24hr {
1251 1014     1014   1926 my($self,$val) = @_;
1252              
1253 1014 100       3032 if ($val) {
1254 500         1377 $$self{'data'}{'sections'}{'conf'}{'workdaybeg'} = '00:00:00';
1255 500         1019 $$self{'data'}{'sections'}{'conf'}{'workdayend'} = '24:00:00';
1256 500         1712 $$self{'data'}{'calc'}{'workdaybeg'} = [0,0,0];
1257 500         1452 $$self{'data'}{'calc'}{'workdayend'} = [24,0,0];
1258              
1259 500         1885 $self->_calc_bdlength();
1260 500         4248 $self->_init_business_length();
1261             }
1262              
1263 1014         1685 return 0;
1264             }
1265              
1266             sub _config_var_workdaybegend {
1267 1038     1038   2035 my($self,$val,$conf) = @_;
1268              
1269             # Must be a valid time. Entered as H, H:M, or H:M:S
1270              
1271 1038         2963 my $tmp = $self->split('hms',$$val);
1272 1038 50       2341 if (! defined $tmp) {
1273 0         0 carp "ERROR: [config_var] invalid: $conf: $$val";
1274 0         0 return 1;
1275             }
1276 1038         2958 $$self{'data'}{'calc'}{lc($conf)} = $tmp;
1277 1038         2903 $$val = $self->join('hms',$tmp);
1278              
1279             # workdaybeg < workdayend
1280              
1281 1038         1566 my @beg = @{ $$self{'data'}{'calc'}{'workdaybeg'} };
  1038         2540  
1282 1038         1480 my @end = @{ $$self{'data'}{'calc'}{'workdayend'} };
  1038         2184  
1283 1038         2176 my $beg = $beg[0]*3600 + $beg[1]*60 + $beg[2];
1284 1038         1791 my $end = $end[0]*3600 + $end[1]*60 + $end[2];
1285              
1286 1038 50       2341 if ($beg > $end) {
1287 0         0 carp "ERROR: [config_var] WorkDayBeg not before WorkDayEnd";
1288 0         0 return 1;
1289             }
1290              
1291             # Calculate bdlength
1292              
1293 1038         1857 $$self{'data'}{'sections'}{'conf'}{'workday24hr'} = 0;
1294              
1295 1038         2586 $self->_calc_bdlength();
1296 1038         2386 $self->_init_business_length();
1297              
1298 1038         2041 return 0;
1299             }
1300              
1301             sub _config_var_firstday {
1302 536     536   1229 my($self,$val) = @_;
1303              
1304 536 50       2283 if (! $self->_is_int($val,1,7)) {
1305 0         0 carp "ERROR: [config_var] invalid: FirstDay: $val";
1306 0         0 return 1;
1307             }
1308              
1309 536         1077 return 0;
1310             }
1311              
1312             sub _config_var_defaulttime {
1313 516     516   1143 my($self,$val) = @_;
1314              
1315 516 50 66     1916 if (lc($val) eq 'midnight' ||
1316             lc($val) eq 'curr') {
1317 516         990 return 0;
1318             }
1319 0         0 carp "ERROR: [config_var] invalid: DefaultTime: $val";
1320 0         0 return 1;
1321             }
1322              
1323             sub _config_var_format_mmmyyyy {
1324 4     4   12 my($self,$val) = @_;
1325              
1326 4 50 66     26 if (lc($val) eq 'first' ||
      33        
1327             lc($val) eq 'last' ||
1328             lc($val) eq '') {
1329 4         10 return 0;
1330             }
1331 0         0 carp "ERROR: [config_var] invalid: Format_MMMYYYY: $val";
1332 0         0 return 1;
1333             }
1334              
1335             ###############################################################################
1336             # Language functions
1337              
1338             # This reads in a langauge module and sets regular expressions
1339             # and word lists based on it.
1340             #
1341 168     168   675614 no strict 'refs';
  168         355  
  168         28642  
1342             sub _language {
1343 533     533   1249 my($self,$lang) = @_;
1344 533         2711 $lang = lc($lang);
1345              
1346 533 50       2708 if (! exists $Date::Manip::Lang::index::Lang{$lang}) {
1347 0         0 carp "ERROR: [language] invalid: $lang";
1348 0         0 return 1;
1349             }
1350              
1351             return 0 if (exists $$self{'data'}{'sections'}{'conf'} &&
1352 533 50 33     7060 $$self{'data'}{'sections'}{'conf'} eq $lang);
1353 533         2631 $self->_init_language(1);
1354              
1355 533         1162 my $mod = $Date::Manip::Lang::index::Lang{$lang};
1356 533         38354 eval "require Date::Manip::Lang::${mod}";
1357 533 50       2503 if ($@) {
1358 0         0 croak "ERROR: failed to load Date::Manip::Lang::${mod}: $@\n";
1359             }
1360              
1361 168     168   1178 no warnings 'once';
  168         339  
  168         38806  
1362 533         818 $$self{'data'}{'lang'} = ${ "Date::Manip::Lang::${mod}::Language" };
  533         3431  
1363 533         977 $$self{'data'}{'enc'} = [ @{ "Date::Manip::Lang::${mod}::Encodings" } ];
  533         2615  
1364              
1365             # Common words
1366 533         2316 $self->_rx_wordlist('at');
1367 533         1323 $self->_rx_wordlist('each');
1368 533         1346 $self->_rx_wordlist('last');
1369 533         1324 $self->_rx_wordlist('of');
1370 533         1341 $self->_rx_wordlist('on');
1371 533         1862 $self->_rx_wordlists('when');
1372              
1373             # Next/prev
1374 533         1407 $self->_rx_wordlists('nextprev');
1375              
1376             # Field names (years, year, yr, ...)
1377 533         1363 $self->_rx_wordlists('fields');
1378              
1379             # Numbers (first, 1st)
1380 533         2250 $self->_rx_wordlists('nth');
1381 533         2073 $self->_rx_wordlists('nth','nth_dom',31); # 1-31
1382 533         1967 $self->_rx_wordlists('nth','nth_wom',5); # 1-5
1383              
1384             # Calendar names (Mon, Tue and Jan, Feb)
1385 533         1534 $self->_rx_wordlists('day_abb');
1386 533         2454 $self->_rx_wordlists('day_char');
1387 533         3240 $self->_rx_wordlists('day_name');
1388 533         2372 $self->_rx_wordlists('month_abb');
1389 533         1619 $self->_rx_wordlists('month_name');
1390              
1391             # H:M:S separators
1392 533         2085 $self->_rx_simple('sephm');
1393 533         1531 $self->_rx_simple('sepms');
1394 533         1349 $self->_rx_simple('sepfr');
1395              
1396             # Time replacement strings
1397 533         1857 $self->_rx_replace('times');
1398              
1399             # Some offset strings
1400 533         1632 $self->_rx_replace('offset_date');
1401 533         1811 $self->_rx_replace('offset_time');
1402              
1403             # AM/PM strings
1404 533         2383 $self->_rx_wordlists('ampm');
1405              
1406             # Business/non-business mode
1407 533         3161 $self->_rx_wordlists('mode');
1408              
1409 533         1468 return 0;
1410             }
1411 168     168   1105 use strict 'refs';
  168         374  
  168         60269  
1412              
1413             # This takes a string or strings from the language file which is a
1414             # regular expression and copies it to the regular expression cache.
1415             #
1416             # If the language file contains a list of strings, a list of strings
1417             # is stored in the regexp cache.
1418             #
1419             sub _rx_simple {
1420 1599     1599   2595 my($self,$ele) = @_;
1421              
1422 1599 100       3250 if (exists $$self{'data'}{'lang'}{$ele}) {
1423 19 100       54 if (ref($$self{'data'}{'lang'}{$ele})) {
1424 16         26 @{ $$self{'data'}{'rx'}{$ele} } = @{ $$self{'data'}{'lang'}{$ele} };
  16         85  
  16         33  
1425             } else {
1426 3         9 $$self{'data'}{'rx'}{$ele} = $$self{'data'}{'lang'}{$ele};
1427             }
1428             } else {
1429 1580         2934 $$self{'data'}{'rx'}{$ele} = undef;
1430             }
1431              
1432 1599         2027 return;
1433             }
1434              
1435             # We need to quote strings that will be used in regexps, but we don't
1436             # want to quote UTF-8 characters.
1437             #
1438             sub _qe_quote {
1439 223103     223103   265818 my($string) = @_;
1440 223103         405354 $string =~ s/([-.+*?])/\\$1/g;
1441 223103         390092 return $string;
1442             }
1443              
1444             # This takes a list of words and creates a simple regexp which matches
1445             # any of them.
1446             #
1447             # The first word in the list is the default way to express the word using
1448             # a normal ASCII character set.
1449             #
1450             # The second word in the list is the default way to express the word using
1451             # a locale character set. If it isn't defined, it defaults to the first word.
1452             #
1453             sub _rx_wordlist {
1454 2665     2665   4200 my($self,$ele) = @_;
1455              
1456 2665 50       5153 if (exists $$self{'data'}{'lang'}{$ele}) {
1457 2665         2847 my @tmp = @{ $$self{'data'}{'lang'}{$ele} };
  2665         5569  
1458              
1459 2665         4591 $$self{'data'}{'wordlist'}{$ele} = $tmp[0];
1460              
1461 2665         2860 my @tmp2;
1462 2665         3629 foreach my $tmp (@tmp) {
1463 4271 100       7741 push(@tmp2,_qe_quote($tmp)) if ($tmp);
1464             }
1465 2665         6635 @tmp2 = sort _sortByLength(@tmp2);
1466              
1467 2665         6794 $$self{'data'}{'rx'}{$ele} = join('|',@tmp2);
1468              
1469             } else {
1470 0         0 $$self{'data'}{'rx'}{$ele} = undef;
1471             }
1472              
1473 2665         3449 return;
1474             }
1475              
1476 168     168   1240 no strict 'vars';
  168         372  
  168         10109  
1477             sub _sortByLength {
1478 1135948     1135948   1163293 return (length $b <=> length $a);
1479             }
1480 168     168   1052 use strict 'vars';
  168         339  
  168         394529  
1481              
1482             # This takes a hash of the form:
1483             # word => string
1484             # and creates a regular expression to match word (which must be surrounded
1485             # by word boundaries).
1486             #
1487             sub _rx_replace {
1488 1599     1599   2974 my($self,$ele) = @_;
1489              
1490 1599 50       4414 if (! exists $$self{'data'}{'lang'}{$ele}) {
1491 0         0 $$self{'data'}{'rx'}{$ele} = [];
1492 0         0 return;
1493             }
1494              
1495 1599         2133 my(@key) = keys %{ $$self{'data'}{'lang'}{$ele} };
  1599         5688  
1496 1599         2861 my $i = 1;
1497 1599         4250 foreach my $key (sort(@key)) {
1498 4288         16542 my $val = $$self{'data'}{'lang'}{$ele}{$key};
1499 4288         6162 my $k = _qe_quote($key);
1500 4288         61440 $$self{'data'}{'rx'}{$ele}[$i++] = qr/(?:^|\b)($k)(?:\b|$)/i;
1501 4288         263850 $$self{'data'}{'wordmatch'}{$ele}{lc($key)} = $val;
1502             }
1503              
1504 1599         4691 @key = sort _sortByLength(@key);
1505 1599         2690 @key = map { _qe_quote($_) } @key;
  4288         5548  
1506 1599         3665 my $rx = join('|',@key);
1507              
1508 1599         46055 $$self{'data'}{'rx'}{$ele}[0] = qr/(?:^|\b)(?:$rx)(?:\b|$)/i;
1509              
1510 1599         5153 return;
1511             }
1512              
1513             # This takes a list of values, each of which can be expressed in multiple
1514             # ways, and gets a regular expression which matches any of them, a default
1515             # way to express each value, and a hash which matches a matched string to
1516             # a value (the value is 1..N where N is the number of values).
1517             #
1518             sub _rx_wordlists {
1519 6929     6929   11859 my($self,$ele,$subset,$max) = @_;
1520 6929 100       14054 $subset = $ele if (! $subset);
1521              
1522 6929 50       14383 if (exists $$self{'data'}{'lang'}{$ele}) {
1523 6929         8257 my @vallist = @{ $$self{'data'}{'lang'}{$ele} };
  6929         15673  
1524 6929 100 66     19454 $max = $#vallist+1 if (! $max || $max > $#vallist+1);
1525 6929         9466 my (@all);
1526              
1527 6929         15426 for (my $i=1; $i<=$max; $i++) {
1528 79417         85514 my @tmp = @{ $$self{'data'}{'lang'}{$ele}[$i-1] };
  79417         151958  
1529 79417         128157 $$self{'data'}{'wordlist'}{$subset}[$i-1] = $tmp[0];
1530              
1531 79417         80379 my @str;
1532 79417         97811 foreach my $str (@tmp) {
1533 210260 100       278458 next if (! $str);
1534 210259     16   422488 $$self{'data'}{'wordmatch'}{$subset}{lc($str)} = $i;
  16         99  
  16         35  
  16         188  
1535 210259         398495 push(@str,_qe_quote($str));
1536             }
1537 79417         117101 push(@all,@str);
1538              
1539 79417         149303 @str = sort _sortByLength(@str);
1540 79417         262214 $$self{'data'}{'rx'}{$subset}[$i] = join('|',@str);
1541             }
1542              
1543 6929         15539 @all = sort _sortByLength(@all);
1544 6929         44814 $$self{'data'}{'rx'}{$subset}[0] = join('|',@all);
1545              
1546             } else {
1547 0         0 $$self{'data'}{'rx'}{$subset} = undef;
1548             }
1549              
1550 6929         10382 return;
1551             }
1552              
1553             ###############################################################################
1554             # Year functions
1555             #
1556             # $self->_method(METHOD) use METHOD as the method for YY->YYYY
1557             # conversions
1558             #
1559             # YEAR = _fix_year(YR) converts a 2-digit to 4-digit year
1560             # _fix_year is in TZ_Base
1561              
1562             sub _method {
1563 4     4   31 my($self,$method) = @_;
1564 4         27 $self->_config('yytoyyyy',$method);
1565              
1566 4         9 return;
1567             }
1568              
1569             ###############################################################################
1570             # $self->_mod_add($N,$add,\$val,\$rem);
1571             # This calculates $val=$val+$add and forces $val to be in a certain
1572             # range. This is useful for adding numbers for which only a certain
1573             # range is allowed (for example, minutes can be between 0 and 59 or
1574             # months can be between 1 and 12). The absolute value of $N determines
1575             # the range and the sign of $N determines whether the range is 0 to N-1
1576             # (if N>0) or 1 to N (N<0). $rem is adjusted to force $val into the
1577             # appropriate range.
1578             # Example:
1579             # To add 2 hours together (with the excess returned in days) use:
1580             # $self->_mod_add(-24,$h1,\$h,\$day);
1581             # To add 2 minutes together (with the excess returned in hours):
1582             # $self->_mod_add(60,$mn1,\$mn,\$hr);
1583             sub _mod_add {
1584 41084     41084   53754 my($self,$N,$add,$val,$rem)=@_;
1585 41084 50       56706 return if ($N==0);
1586 41084         42268 $$val+=$add;
1587 41084 100       50142 if ($N<0) {
1588             # 1 to N
1589 2809         3362 $N = -$N;
1590 2809 100       6080 if ($$val>$N) {
    100          
1591 65         125 $$rem+= int(($$val-1)/$N);
1592 65         156 $$val = ($$val-1)%$N +1;
1593             } elsif ($$val<1) {
1594 96         192 $$rem-= int(-$$val/$N)+1;
1595 96         166 $$val = $N-(-$$val % $N);
1596             }
1597              
1598             } else {
1599             # 0 to N-1
1600 38275 100       63350 if ($$val>($N-1)) {
    100          
1601 212         339 $$rem+= int($$val/$N);
1602 212         316 $$val = $$val%$N;
1603             } elsif ($$val<0) {
1604 159         255 $$rem-= int(-($$val+1)/$N)+1;
1605 159         242 $$val = ($N-1)-(-($$val+1)%$N);
1606             }
1607             }
1608              
1609 41084         46326 return;
1610             }
1611              
1612             # $flag = $self->_is_int($string [,$low, $high]);
1613             # Returns 1 if $string is a valid integer, 0 otherwise. If $low is
1614             # entered, $string must be >= $low. If $high is entered, $string must
1615             # be <= $high. It is valid to check only one of the bounds.
1616             sub _is_int {
1617 54001     54001   73762 my($self,$N,$low,$high)=@_;
1618 54001 100 66     303442 return 0 if (! defined $N or
      100        
      100        
      66        
      66        
1619             $N !~ /^\s*[-+]?\d+\s*$/o or
1620             defined $low && $N<$low or
1621             defined $high && $N>$high);
1622 53995         109184 return 1;
1623             }
1624              
1625             # $flag = $self->_is_num($string [,$low, $high]);
1626             # Returns 1 if $string is a valid number (integer or real), 0 otherwise.
1627             # If $low is entered, $string must be >= $low. If $high is entered,
1628             # $string must be <= $high. It is valid to check only one of the bounds.
1629             sub _is_num {
1630 40812     40812   53189 my($self,$N,$low,$high)=@_;
1631 40812 50 66     234196 return 0 if (! defined $N or
      33        
      66        
      33        
      33        
1632             ($N !~ /^\s*[-+]?\d+(\.\d*)?\s*$/o &&
1633             $N !~ /^\s*[-+]?\.\d+\s*$/o) or
1634             defined $low && $N<$low or
1635             defined $high && $N>$high);
1636 40810         81285 return 1;
1637             }
1638              
1639             ###############################################################################
1640             # Split/Join functions
1641              
1642             sub split {
1643 5732     5732 1 68874 my($self,$op,$string,$arg) = @_;
1644              
1645 5732         7072 my %opts;
1646 5732 100       14194 if (ref($arg) eq 'HASH') {
    100          
1647 1         2 %opts = %{ $arg };
  1         4  
1648             } elsif ($arg) {
1649             # ***DEPRECATED 7.0***
1650 1         3 %opts = ('nonorm' => 1);
1651             }
1652              
1653             # ***DEPRECATED 7.0***
1654 5732 100       13818 if ($op eq 'delta') {
    100          
1655 81         168 $opts{'mode'} = 'standard';
1656             } elsif ($op eq 'business') {
1657 6         8 $opts{'mode'} = 'business';
1658 6         8 $op = 'delta';
1659             }
1660              
1661 5732 100       11904 if ($op eq 'date') {
    100          
    100          
    100          
    50          
1662              
1663 4232 100 100     24963 if ($string =~ /^(\d\d\d\d)(\d\d)(\d\d)(\d\d):(\d\d):(\d\d)$/o ||
      100        
1664             $string =~ /^(\d\d\d\d)\-(\d\d)\-(\d\d)\-(\d\d):(\d\d):(\d\d)$/o ||
1665             $string =~ /^(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/o) {
1666 1925         11017 my($y,$m,$d,$h,$mn,$s) = ($1+0,$2+0,$3+0,$4+0,$5+0,$6+0);
1667 1925         6559 return [$y,$m,$d,$h,$mn,$s];
1668             } else {
1669 2307         5532 return undef;
1670             }
1671              
1672             } elsif ($op eq 'hms') {
1673 1106 100 33     13578 if ($string =~ /^(\d\d)(\d\d)(\d\d)$/o ||
      66        
      100        
      100        
1674             $string =~ /^(\d\d)(\d\d)()$/o ||
1675             $string =~ /^(\d\d?):(\d\d):(\d\d)$/o ||
1676             $string =~ /^(\d\d?):(\d\d)()$/o ||
1677             $string =~ /^(\d\d?)()()$/o) {
1678 1103         8224 my($err,$h,$mn,$s) = $self->_hms_fields( { 'out' => 'list' },[$1,$2,$3]);
1679 1103 100       3390 return undef if ($err);
1680 1102         3557 return [$h,$mn,$s];
1681             } else {
1682 3         7 return undef;
1683             }
1684              
1685             } elsif ($op eq 'offset') {
1686 294 100 100     2640 if ($string =~ /^([-+]?\d\d)(\d\d)(\d\d)$/o ||
      100        
      100        
      100        
1687             $string =~ /^([-+]?\d\d)(\d\d)()$/o ||
1688             $string =~ /^([-+]?\d\d?):(\d\d?):(\d\d?)$/o ||
1689             $string =~ /^([-+]?\d\d?):(\d\d?)()$/o ||
1690             $string =~ /^([-+]?\d\d?)()()$/o) {
1691 288         1822 my($err,$h,$mn,$s) = $self->_offset_fields( { 'source' => 'string',
1692             'out' => 'list'},
1693             [$1,$2,$3]);
1694 288 100       783 return undef if ($err);
1695 287         876 return [$h,$mn,$s];
1696             } else {
1697 6         22 return undef;
1698             }
1699              
1700             } elsif ($op eq 'time') {
1701 13 100       58 if ($string =~ /^[-+]?\d+(:[-+]?\d+){0,2}$/o) {
1702             my($err,$dh,$dmn,$ds) =
1703             $self->_time_fields( { 'nonorm' =>
1704 12 100       59 (exists($opts{'nonorm'}) ? $opts{'nonorm'} : 0),
1705             'source' => 'string',
1706             'sign' => -1,
1707             }, [split(/:/,$string)]);
1708 12 50       32 return undef if ($err);
1709 12         36 return [$dh,$dmn,$ds];
1710             } else {
1711 1         2 return undef;
1712             }
1713              
1714             } elsif ($op eq 'delta') {
1715 87         220 my($err,@delta) = $self->_split_delta($string);
1716 87 50       196 return undef if ($err);
1717              
1718             ($err,@delta) =
1719             $self->_delta_fields( { 'mode' => $opts{'mode'},
1720             'nonorm' => (exists($opts{'nonorm'}) ?
1721 87 50       588 $opts{'nonorm'} : 0),
1722             'source' => 'string',
1723             'sign' => -1,
1724             }, [@delta]);
1725              
1726 87 50       282 return undef if ($err);
1727 87         424 return [@delta];
1728             }
1729             }
1730              
1731             sub join{
1732 27688     27688 1 108893 my($self,$op,$data,$arg) = @_;
1733              
1734 27688         30316 my %opts;
1735 27688 100       51525 if (ref($arg) eq 'HASH') {
    100          
1736 1         2 %opts = %{ $arg };
  1         6  
1737             } elsif ($arg) {
1738             # ***DEPRECATED 7.0***
1739 2         5 %opts = ('nonorm' => 1);
1740             }
1741              
1742             # ***DEPRECATED 7.0***
1743 27688 100       47267 if ($op eq 'delta') {
    100          
1744 10         19 $opts{'mode'} = 'standard';
1745             } elsif ($op eq 'business') {
1746 9         16 $opts{'mode'} = 'business';
1747 9         13 $op = 'delta';
1748             }
1749              
1750 27688         45147 my @data = @$data;
1751              
1752 27688 100       40790 if ($op eq 'date') {
    100          
    100          
    100          
    50          
1753              
1754 24827         38886 my($err,$y,$m,$d,$h,$mn,$s) = $self->_date_fields(@data);
1755 24827 50       43094 return undef if ($err);
1756 24827         48778 my $form = $self->_config('printable');
1757 24827 100       46866 if ($form == 1) {
    100          
1758 1         6 return "$y$m$d$h$mn$s";
1759             } elsif ($form == 2) {
1760 1         6 return "$y-$m-$d-$h:$mn:$s";
1761             } else {
1762 24825         78714 return "$y$m$d$h:$mn:$s";
1763             }
1764              
1765             } elsif ($op eq 'offset') {
1766 108         459 my($err,$h,$mn,$s) = $self->_offset_fields( { 'source' => 'list',
1767             'out' => 'string'},
1768             [@data]);
1769 108 100       319 return undef if ($err);
1770 105         586 return "$h:$mn:$s";
1771              
1772             } elsif ($op eq 'hms') {
1773 2721         8063 my($err,$h,$mn,$s) = $self->_hms_fields( { 'out' => 'string' },[@data]);
1774 2721 100       6444 return undef if ($err);
1775 2718         8470 return "$h:$mn:$s";
1776              
1777             } elsif ($op eq 'time') {
1778             my($err,$dh,$dmn,$ds) =
1779             $self->_time_fields( { 'nonorm' =>
1780 13 100       55 (exists($opts{'nonorm'}) ? $opts{'nonorm'} : 0),
1781             'source' => 'list',
1782             'sign' => 0,
1783             }, [@data]);
1784 13 100       34 return undef if ($err);
1785 12         44 return "$dh:$dmn:$ds";
1786              
1787             } elsif ($op eq 'delta') {
1788             my ($err,@delta) =
1789             $self->_delta_fields( { 'mode' => $opts{'mode'},
1790             'nonorm' => (exists($opts{'nonorm'}) ?
1791 19 100       94 $opts{'nonorm'} : 0),
1792             'source' => 'list',
1793             'sign' => 0,
1794             }, [@data]);
1795 19 50       59 return undef if ($err);
1796 19         106 return join(':',@delta);
1797             }
1798             }
1799              
1800             sub _split_delta {
1801 1034     1034   1803 my($self,$string) = @_;
1802              
1803 1034         1341 my $sign = '[-+]?';
1804 1034         1232 my $num = '(?:\d+(?:\.\d*)?|\.\d+)';
1805 1034         1892 my $f = "(?:$sign$num)?";
1806              
1807 1034 100       9286 if ($string =~ /^$f(:$f){0,6}$/o) {
1808 436         946 $string =~ s/::/:0:/go;
1809 436         613 $string =~ s/^:/0:/o;
1810 436         609 $string =~ s/:$/:0/o;
1811 436         1420 my(@delta) = split(/:/,$string);
1812 436         2202 return(0,@delta);
1813             } else {
1814 598         1525 return(1);
1815             }
1816             }
1817              
1818             # Check that type is not inconsistent with @delta.
1819             #
1820             # An exact delta cannot have semi-exact or approximate fields set.
1821             # A semi-exact delta cannot have approximate fields set.
1822             # An exact, semi-exact, or approximate delta cannot have non-integer values.
1823             #
1824             # If the type was not explicitly specified, guess what it is.
1825             #
1826             # Returns ($err,$type,$type_from)
1827             #
1828             sub _check_delta_type {
1829 5547     5547   12456 my($self,$mode,$type,$type_from,@delta) = @_;
1830              
1831 5547         6405 my $est = 0;
1832 5547         7665 foreach my $f (@delta) {
1833 38767 100       54732 if (! $self->_is_int($f)) {
1834 5         12 $est = 1;
1835 5         8 last;
1836             }
1837             }
1838              
1839 5547         6699 my $approx = 0;
1840 5547 100       9227 if (! $est) {
1841 5542 100 100     14215 $approx = 1 if ($delta[0] || $delta[1]);
1842             }
1843              
1844 5547         6391 my $semi = 0;
1845 5547 100 100     14608 if (! $est && ! $approx) {
1846 2135 100       4229 if ($mode eq 'business') {
1847 287 100       527 $semi = 1 if ($delta[2]);
1848             } else {
1849 1848 100 100     5659 $semi = 1 if ($delta[2] || $delta[3]);
1850             }
1851             }
1852              
1853 5547 100       11558 if ($est) {
    100          
    100          
1854             # If some of the fields are non-integer, then type must be estimated.
1855              
1856 5 50       13 if ($type ne 'estimated') {
1857 5 100       10 if ($type_from eq 'opt') {
1858 1         4 return ("Type must be estimated for non-integers");
1859             }
1860 4         16 $type = 'estimated';
1861 4         7 $type_from = 'det';
1862             }
1863              
1864             } elsif ($approx) {
1865             # If some of the approximate fields are set, then type must be
1866             # approx or estimated.
1867              
1868 3407 100 100     10400 if ($type ne 'approx' && $type ne 'estimated') {
1869 3397 100       6259 if ($type_from eq 'opt') {
1870 5         17 return("Type must be approx/estimated");
1871             }
1872 3392         4296 $type = 'approx';
1873 3392         4076 $type_from = 'det';
1874             }
1875              
1876             } elsif ($semi) {
1877             # If some of the semi-exact fields are set, then type must be
1878             # semi, approx, or estimated
1879              
1880 391 100 100     1951 if ($type ne 'semi' && $type ne 'approx' && $type ne 'estimated') {
      100        
1881 373 100       712 if ($type_from eq 'opt') {
1882 5         20 return("Type must be semi/approx/estimated");
1883             }
1884 368         513 $type = 'semi';
1885 368         463 $type_from = 'det';
1886             }
1887              
1888             } else {
1889              
1890 1744 100       3048 if (! $type) {
1891 266         398 $type = 'exact';
1892 266         363 $type_from = 'det';
1893             }
1894             }
1895              
1896 5536         17392 return ('',$type,$type_from);
1897             }
1898              
1899             # This function returns the fields in a delta in the desired format.
1900             #
1901             # $opts = { mode => standard/business
1902             # type => exact/semi/approx/estimated
1903             # nonorm => 0/1,
1904             # source => string, list, delta
1905             # sign => 0/1/-1
1906             # }
1907             # $fields = [Y,M,W,D,H,Mn,S]
1908             #
1909             # If the business option is 1, treat it as a business delta.
1910             #
1911             # If the nonorm option is 1, fields are NOT normalized. By default,
1912             # they are normalized.
1913             #
1914             # If source is 'string', then the source of the fields is a string
1915             # that has been split, so we need to handle carrying the signs. If
1916             # the option is 'list', then the source is a valid delta, so each
1917             # field is correctly signed already. In both cases, the type of
1918             # delta will need to be determined. If the source is 'delta', then
1919             # it comes from a Date::Manip::Delta object. In this case the type
1920             # must be specified. If type is not passed in, it will be set.
1921             #
1922             # If the sign option is 1, a sign is added to every field. If the
1923             # sign option is -1, all negative fields are signed. If the sign
1924             # option is 0, the minimum number of signs (for fields who's sign is
1925             # different from the next higher field) will be added.
1926             #
1927             # It returns ($err,@fields)
1928             #
1929             sub _delta_fields {
1930 5904     5904   9886 my($self,$opts,$fields) = @_;
1931 5904         11409 my @fields = @$fields;
1932 168     168   1340 no integer;
  168         367  
  168         858  
1933              
1934             #
1935             # Make sure that all fields are defined, numerical, and that there
1936             # are 7 of them.
1937             #
1938              
1939 5904         8484 foreach my $f (@fields) {
1940 40805 50       56051 $f=0 if (! defined($f));
1941 40805 100       55569 return ("Non-numerical field") if (! $self->_is_num($f));
1942             }
1943 5903 100       11187 return ("Delta may contain only 7 fields") if (@fields > 7);
1944 5902         12619 while (@fields < 7) {
1945 518         933 unshift(@fields,0);
1946             }
1947              
1948             #
1949             # Make sure each field is the correct sign so that the math will
1950             # work correctly. Get rid of all positive signs and leading 0's.
1951             #
1952              
1953 5902         11019 my $mode = $$opts{'mode'};
1954 5902         7601 my $source = $$opts{'source'};
1955 5902         11576 @fields = $self->_sign_source($source,@fields);
1956              
1957             #
1958             # Figure out the type of delta. When called from Date::Manip::Base, it'll
1959             # be determined from the data. When called from Date::Manip::Delta, it'll
1960             # be specified.
1961             #
1962              
1963 5902         8519 my ($type,$type_from);
1964 5902 100 66     16422 if (defined $source && $source eq 'delta') {
1965 5333 50       9560 if (! exists $$opts{'type'}) {
1966 0         0 return ("Type must be specified");
1967             }
1968 5333         6796 $type = $$opts{'type'};
1969              
1970             } else {
1971 569         694 my $err;
1972 569         1331 ($err,$type,$type_from) = $self->_check_delta_type($mode,'','init',@fields);
1973 569         1149 $$opts{'type'} = $type;
1974 569         826 $$opts{'type_from'} = $type_from;
1975 569 50       1005 return($err) if ($err);
1976             }
1977              
1978             #
1979             # Normalize values, if desired.
1980             #
1981              
1982 5902         8876 my $norm = 1-$$opts{'nonorm'};
1983 5902 100       9280 if ($norm) {
1984 5540 100       8548 if ($mode eq 'business') {
1985              
1986 354 100 100     1132 if ($type eq 'estimated') {
    100          
1987 10         30 @fields = $self->_normalize_bus_est(@fields);
1988              
1989             } elsif ($type eq 'approx' ||
1990             $type eq 'semi') {
1991 113         264 @fields = $self->_normalize_bus_approx(@fields);
1992              
1993             } else {
1994 231         588 @fields = $self->_normalize_bus_exact(@fields);
1995             }
1996              
1997             } else {
1998              
1999 5186 100 100     13767 if ($type eq 'estimated') {
    100          
2000 11         30 @fields = $self->_normalize_est(@fields);
2001              
2002             } elsif ($type eq 'approx' ||
2003             $type eq 'semi') {
2004 3709         8062 @fields = $self->_normalize_approx(@fields);
2005              
2006             } else {
2007 1466         4159 @fields = $self->_normalize_exact(@fields);
2008             }
2009              
2010             }
2011             }
2012              
2013             #
2014             # Now make sure that the signs are included as appropriate.
2015             #
2016              
2017 5902         12525 @fields = $self->_sign_fields($$opts{'sign'},@fields);
2018              
2019 5902         20284 return (0,@fields);
2020             }
2021              
2022             # If a set of fields came from splitting a string, not all of the fields
2023             # are signed. If it comes from a list, we just want to remove extra '+'
2024             # signs.
2025             #
2026             sub _sign_source {
2027 5926     5926   13293 my($self,$source,@fields) = @_;
2028              
2029             # Needed to handle fractional fields
2030 168     168   55455 no integer;
  168         370  
  168         731  
2031 5926 100       10176 if ($source eq 'string') {
2032              
2033             # if the source is splitting a delta, not all fields are signed,
2034             # so we need to carry the negative signs.
2035              
2036 562         718 my $sign = '+';
2037 562         870 foreach my $f (@fields) {
2038 3886 100       6675 if ($f =~ /^([-+])/o) {
2039 356         693 $sign = $1;
2040             } else {
2041 3530         4647 $f = "$sign$f";
2042             }
2043 3886         5786 $f *= 1;
2044             }
2045              
2046             } else {
2047 5364         7507 foreach my $f (@fields) {
2048 37500         39823 $f *= 1;
2049             }
2050             }
2051              
2052 5926         14898 return @fields;
2053             }
2054              
2055             # This applies the correct sign to each field based on the $sign option:
2056             #
2057             # 1 : all fields signed
2058             # -1 : all negative fields signed
2059             # 0 : minimum number of signs for a joined set of fields
2060             #
2061             sub _sign_fields {
2062 5926     5926   11350 my($self,$sign,@fields) = @_;
2063 5926 50       10571 $sign = 0 if (! defined $sign);
2064              
2065 5926 50       12647 if ($sign == 1) {
    100          
2066             # All fields signed
2067 0         0 foreach my $f (@fields) {
2068 0 0       0 $f = "+$f" if ($f > 0);
2069             }
2070              
2071             } elsif ($sign == 0) {
2072             # Minimum number of signs
2073 370 100       669 my $s = ($fields[0] < 0 ? '-' : '+');
2074 370         893 foreach my $f (@fields[1..$#fields]) {
2075 2172 100 100     5363 if ($f > 0 && $s eq '-') {
    100          
2076 26         54 $f = "+$f";
2077 26         44 $s = '+';
2078             } elsif ($f < 0) {
2079 323 100       465 if ($s eq '-') {
2080 197         248 $f *= -1;
2081             } else {
2082 126         202 $s = '-';
2083             }
2084             }
2085             }
2086             }
2087              
2088 5926         13167 return @fields;
2089             }
2090              
2091             # $opts = { nonorm => 0/1,
2092             # source => string, list
2093             # sign => 0/1/-1
2094             # }
2095             # $fields = [H,M,S]
2096             #
2097             # This function formats the fields in an amount of time measured in
2098             # hours, minutes, and seconds.
2099             #
2100             # It is similar to how _delta_fields (above) works.
2101             #
2102             sub _time_fields {
2103 25     25   45 my($self,$opts,$fields) = @_;
2104 25         43 my @fields = @$fields;
2105              
2106             #
2107             # Make sure that all fields are defined, numerical, and that there
2108             # are 3 of them.
2109             #
2110              
2111 25         36 foreach my $f (@fields) {
2112 67 50       96 $f=0 if (! defined($f));
2113 67 50       95 return (1) if (! $self->_is_int($f));
2114             }
2115 25 100       50 return (1) if (@fields > 3);
2116 24         89 while (@fields < 3) {
2117 9         20 unshift(@fields,0);
2118             }
2119              
2120             #
2121             # Make sure each field is the correct sign so that the math will
2122             # work correctly. Get rid of all positive signs and leading 0's.
2123             #
2124              
2125 24         35 my $source = $$opts{'source'};
2126 24         44 @fields = $self->_sign_source($source,@fields);
2127              
2128             #
2129             # Normalize them. Values will be signed only if they are
2130             # negative.
2131             #
2132              
2133 24         38 my $norm = 1-$$opts{'nonorm'};
2134 24 100       34 if ($norm) {
2135 20         92 my($h,$mn,$s) = @fields;
2136 20         31 $s += $h*3600 + $mn*60;
2137 20         33 @fields = __normalize_hms($h,$mn,$s);
2138             }
2139              
2140             #
2141             # Now make sure that the signs are included as appropriate.
2142             #
2143              
2144 24         48 @fields = $self->_sign_fields($$opts{'sign'},@fields);
2145              
2146 24         48 return (0,@fields);
2147             }
2148              
2149             # $opts = { out => string, list
2150             # }
2151             # $fields = [H,M,S]
2152             #
2153             # This function formats the fields in an HMS.
2154             #
2155             # If the out options is string, it prepares the fields to be joined (i.e.
2156             # they are all 2 digits long). Otherwise, they are just numerical values
2157             # (not necessarily 2 digits long).
2158             #
2159             # HH:MN:SS is always between 00:00:00 and 24:00:00.
2160             #
2161             # It returns ($err,@fields)
2162             #
2163             sub _hms_fields {
2164 3858     3858   6552 my($self,$opts,$fields) = @_;
2165 3858         6814 my @fields = @$fields;
2166              
2167             #
2168             # Make sure that all fields are defined, numerical (with no sign),
2169             # and that there are 3 of them.
2170             #
2171              
2172 3858         5665 foreach my $f (@fields) {
2173 11553 100       16441 $f=0 if (! $f);
2174 11553 100       16925 return (1) if (! $self->_is_int($f,0));
2175             }
2176 3857 100       7583 return (1) if (@fields > 3);
2177 3856         6820 while (@fields < 3) {
2178 20         41 push(@fields,0);
2179             }
2180              
2181             #
2182             # Check validity.
2183             #
2184              
2185 3856         6626 my ($h,$m,$s) = @fields;
2186 3856 0 66     17911 return (1) if ($h > 24 || $m > 59 || $s > 59 ||
      66        
      0        
      33        
      66        
2187             ($h==24 && ($m > 0 || $s > 0)));
2188              
2189             #
2190             # Format
2191             #
2192              
2193 3854 100       7722 if ($$opts{'out'} eq 'list') {
2194 1136         2179 foreach my $f ($h,$m,$s) {
2195 3408         4336 $f *= 1;
2196             }
2197              
2198             } else {
2199 2718         4057 foreach my $f ($h,$m,$s) {
2200 8154 100       16854 $f = "0$f" if (length($f)<2);
2201             }
2202             }
2203              
2204 3854         11993 return (0,$h,$m,$s);
2205             }
2206              
2207             # $opts = { source => string, list
2208             # out => string, list
2209             # }
2210             # $fields = [H,M,S]
2211             #
2212             # This function formats the fields in a timezone offset measured in
2213             # hours, minutes, and seconds.
2214             #
2215             # All offsets must be -23:59:59 <= offset <= 23:59:59 .
2216             #
2217             # The data comes from an offset in string or list format, and is
2218             # formatted so that it can be used to create a string or list format
2219             # output.
2220             #
2221             sub _offset_fields {
2222 396     396   736 my($self,$opts,$fields) = @_;
2223 396         835 my @fields = @$fields;
2224              
2225             #
2226             # Make sure that all fields are defined, numerical, and that there
2227             # are 3 of them.
2228             #
2229              
2230 396         631 foreach my $f (@fields) {
2231 1184 100 66     3174 $f=0 if (! defined $f || $f eq '');
2232 1184 50       1888 return (1) if (! $self->_is_int($f));
2233             }
2234 396 100       820 return (1) if (@fields > 3);
2235 395         787 while (@fields < 3) {
2236 5         11 push(@fields,0);
2237             }
2238              
2239             #
2240             # Check validity.
2241             #
2242              
2243 395         807 my ($h,$m,$s) = @fields;
2244 395 100       820 if ($$opts{'source'} eq 'string') {
2245             # Values = -23 59 59 to +23 59 59
2246 288 50 33     2565 return (1) if ($h < -23 || $h > 23 ||
      33        
      66        
      66        
      66        
2247             $m < 0 || $m > 59 ||
2248             $s < 0 || $s > 59);
2249             } else {
2250             # Values (-23,-59,-59) to (23,59,59)
2251             # Non-zero values must have the same sign
2252 107 100       347 if ($h >0) {
    100          
    100          
    50          
2253 33 50 66     272 return (1) if ( $h > 23 ||
      100        
      66        
      66        
2254             $m < 0 || $m > 59 ||
2255             $s < 0 || $s > 59);
2256             } elsif ($h < 0) {
2257 54 50 33     437 return (1) if ($h < -23 ||
      33        
      33        
      33        
2258             $m < -59 || $m > 0 ||
2259             $s < -59 || $s > 0);
2260             } elsif ($m > 0) {
2261 2 50 33     11 return (1) if ( $m > 59 ||
      33        
2262             $s < 0 || $s > 59);
2263             } elsif ($m < 0) {
2264 0 0 0     0 return (1) if ($m < -59 ||
      0        
2265             $s < -59 || $s > 0);
2266             } else {
2267 18 50 33     75 return (1) if ($s < -59 || $s > 59);
2268             }
2269             }
2270              
2271             #
2272             # Make sure each field is the correct sign so that the math will
2273             # work correctly. Get rid of all positive signs and leading 0's.
2274             #
2275              
2276 392 100       766 if ($$opts{'source'} eq 'string') {
2277              
2278             # In a string offset, only the first field is signed, so we need
2279             # to carry negative signs.
2280              
2281 287 100       791 if ($h =~ /^\-/) {
    50          
2282 196         337 $h *= 1;
2283 196         275 $m *= -1;
2284 196         261 $s *= -1;
2285             } elsif ($m =~ /^\-/) {
2286 0         0 $h *= 1;
2287 0         0 $m *= 1;
2288 0         0 $s *= -1;
2289             } else {
2290 91         121 $h *= 1;
2291 91         109 $m *= 1;
2292 91         122 $s *= 1;
2293             }
2294              
2295             } else {
2296 105         196 foreach my $f (@fields) {
2297 315         391 $f *= 1;
2298             }
2299             }
2300              
2301             #
2302             # Format them. They're already done for 'list' output.
2303             #
2304              
2305 392 100       808 if ($$opts{'out'} eq 'string') {
2306 105         160 my $sign;
2307 105 100 66     506 if ($h<0 || $m<0 || $s<0) {
      66        
2308 54         95 $h = abs($h);
2309 54         72 $m = abs($m);
2310 54         69 $s = abs($s);
2311 54         108 $sign = '-';
2312             } else {
2313 51         88 $sign = '+';
2314             }
2315              
2316 105 100       311 $h = "0$h" if (length($h) < 2);
2317 105 100       263 $m = "0$m" if (length($m) < 2);
2318 105 100       287 $s = "0$s" if (length($s) < 2);
2319 105         191 $h = "$sign$h";
2320             }
2321              
2322 392         1309 return (0,$h,$m,$s);
2323             }
2324              
2325             # ($err,$y,$m,$d,$h,$mn,$s) = $self->_date_fields($y,$m,$d,$h,$mn,$s);
2326             #
2327             # Makes sure the fields are the right length.
2328             #
2329             sub _date_fields {
2330 55035     55035   85453 my($self,@fields) = @_;
2331 55035 50       88523 return (1) if (@fields != 6);
2332              
2333 55035         86452 my($y,$m,$d,$h,$mn,$s) = @fields;
2334              
2335 55035         99231 $y = "0$y" while (length($y) < 4);
2336 55035 100       108855 $m = "0$m" if (length($m)==1);
2337 55035 100       98613 $d = "0$d" if (length($d)==1);
2338 55035 100       93651 $h = "0$h" if (length($h)==1);
2339 55035 100       92047 $mn = "0$mn" if (length($mn)==1);
2340 55035 100       90582 $s = "0$s" if (length($s)==1);
2341              
2342 55035 100       79225 if (wantarray) {
2343 24827         83618 return (0,$y,$m,$d,$h,$mn,$s);
2344             } else {
2345 30208         82017 return "$y$m$d$h:$mn:$s";
2346             }
2347             }
2348              
2349             # $self->_delta_convert(FORMAT,DELTA)
2350             # This converts delta into the given format. Returns '' if invalid.
2351             #
2352             sub _delta_convert {
2353 94     94   234 my($self,$format,$delta)=@_;
2354 94         251 my $fields = $self->split($format,$delta);
2355 94 100       218 return undef if (! defined $fields);
2356 93         304 return $self->join($format,$fields);
2357             }
2358              
2359             ###############################################################################
2360             # Normalize the different types of deltas
2361              
2362             sub __normalize_ym {
2363 3845     3845   6639 my($y,$m,$s,$mon) = @_;
2364 168     168   207753 no integer;
  168         394  
  168         754  
2365              
2366 3845 100       6094 if (defined($s)) {
2367 21         35 $m = int($s/$mon);
2368 21         156 $s -= int(sprintf('%f',$m*$mon));
2369 21         37 $y = int($m/12);
2370 21         28 $m -= $y*12;
2371              
2372 21         53 return($y,$m,$s);
2373             } else {
2374 3824         4864 $m += $y*12;
2375 3824         6944 $y = int($m/12);
2376 3824         4745 $m -= $y*12;
2377              
2378 3824         8914 return($y,$m);
2379             }
2380             }
2381             sub __normalize_wd {
2382 3845     3845   6502 my($w,$d,$s,$wk,$day) = @_;
2383 168     168   20266 no integer;
  168         355  
  168         879  
2384              
2385 3845         5515 $d = int($s/$day);
2386 3845         5198 $s -= int($d*$day);
2387 3845         5015 $w = int($d/$wk);
2388 3845         4652 $d -= $w*$wk;
2389              
2390 3845         6803 return($w,$d,$s);
2391             }
2392             sub __normalize_hms {
2393 5568     5568   8358 my($h,$mn,$s) = @_;
2394 168     168   13351 no integer;
  168         7600  
  168         884  
2395              
2396 5568         7991 $h = int($s/3600);
2397 5568         6607 $s -= $h*3600;
2398 5568         6624 $mn = int($s/60);
2399 5568         6628 $s -= $mn*60;
2400 5568         5887 $s = int($s);
2401              
2402 5568         9927 return($h,$mn,$s);
2403             }
2404              
2405             sub _normalize_est {
2406 11     11   25 my($self,$y,$m,$w,$d,$h,$mn,$s) = @_;
2407 168     168   13517 no integer;
  168         358  
  168         683  
2408              
2409             # Figure out how many seconds there are in the estimated delta
2410             #
2411             # 365.2425/12 days/month * 24 hours/day * 3600 sec/hour = 2629746 sec/month
2412              
2413 11         15 my $mon = 2629746;
2414 11         12 my $day = 86400;
2415 11         16 my $wk = 7;
2416 11         25 $s += ($y*12+$m)*$mon + ($w*$wk + $d)*$day +
2417             $h*3600 + $mn*60;
2418              
2419 11         26 ($y,$m,$s) = __normalize_ym($y,$m,$s,$mon);
2420 11         37 ($w,$d,$s) = __normalize_wd($w,$d,$s,$wk,$day);
2421 11         25 ($h,$mn,$s) = __normalize_hms($h,$mn,$s);
2422              
2423 11         33 return ($y,$m,$w,$d,$h,$mn,$s);
2424             }
2425             sub _normalize_bus_est {
2426 10     10   23 my($self,$y,$m,$w,$d,$h,$mn,$s) = @_;
2427 168     168   24054 no integer;
  168         378  
  168         818  
2428              
2429             # Figure out how many seconds there are in the estimated delta
2430             #
2431             # 365.2425/12 * wk_len/7 days/month * day sec/day = X sec/month
2432              
2433 10         22 my $day = $$self{'data'}{'len'}{'bdlength'};
2434 10         15 my $wk = $$self{'data'}{'len'}{'workweek'};
2435 10         26 my $mon = 365.2425/12 * $wk/7 * $day;
2436              
2437 10         28 $s += ($y*12+$m)*$mon + ($w*$wk + $d)*$day +
2438             $h*3600 + $mn*60;
2439              
2440 10         25 ($y,$m,$s) = __normalize_ym($y,$m,$s,$mon);
2441 10         20 ($w,$d,$s) = __normalize_wd($w,$d,$s,$wk,$day);
2442 10         23 ($h,$mn,$s) = __normalize_hms($h,$mn,$s);
2443              
2444 10         29 return ($y,$m,$w,$d,$h,$mn,$s);
2445             }
2446              
2447             sub _normalize_approx {
2448 3710     3710   7573 my($self,$y,$m,$w,$d,$h,$mn,$s) = @_;
2449 168     168   27233 no integer;
  168         318  
  168         658  
2450              
2451 3710         4621 my $wk = 7;
2452 3710         4168 my $day = 86400;
2453 3710         6167 $s += ($w*$wk + $d)*$day + $h*3600 + $mn*60;
2454              
2455 3710         7633 ($y,$m) = __normalize_ym($y,$m);
2456 3710         7962 ($w,$d,$s) = __normalize_wd($w,$d,$s,$wk,$day);
2457 3710         7218 ($h,$mn,$s) = __normalize_hms($h,$mn,$s);
2458              
2459 3710         9046 return ($y,$m,$w,$d,$h,$mn,$s);
2460             }
2461             sub _normalize_bus_approx {
2462 114     114   244 my($self,$y,$m,$w,$d,$h,$mn,$s) = @_;
2463 168     168   19192 no integer;
  168         491  
  168         757  
2464              
2465 114         227 my $wk = $$self{'data'}{'len'}{'workweek'};
2466 114         153 my $day = $$self{'data'}{'len'}{'bdlength'};
2467 114         214 $s += ($w*$wk + $d)*$day + $h*3600 + $mn*60;
2468              
2469 114         270 ($y,$m) = __normalize_ym($y,$m);
2470 114         240 ($w,$d,$s) = __normalize_wd($w,$d,$s,$wk,$day);
2471 114         231 ($h,$mn,$s) = __normalize_hms($h,$mn,$s);
2472              
2473 114         291 return ($y,$m,$w,$d,$h,$mn,$s);
2474             }
2475              
2476             sub _normalize_exact {
2477 1469     1469   3358 my($self,$y,$m,$w,$d,$h,$mn,$s) = @_;
2478 168     168   25511 no integer;
  168         308  
  168         662  
2479              
2480 1469         2572 $s += $h*3600 + $mn*60;
2481              
2482 1469         3522 ($h,$mn,$s) = __normalize_hms($h,$mn,$s);
2483              
2484 1469         3862 return ($y,$m,$w,$d,$h,$mn,$s);
2485             }
2486             sub _normalize_bus_exact {
2487 234     234   485 my($self,$y,$m,$w,$d,$h,$mn,$s) = @_;
2488 168     168   15477 no integer;
  168         335  
  168         738  
2489              
2490 234         455 my $day = $$self{'data'}{'len'}{'bdlength'};
2491              
2492 234         396 $s += $d*$day + $h*3600 + $mn*60;
2493              
2494             # Calculate d
2495              
2496 234         432 $d = int($s/$day);
2497 234         374 $s -= $d*$day;
2498              
2499 234         523 ($h,$mn,$s) = __normalize_hms($h,$mn,$s);
2500              
2501 234         577 return ($y,$m,$w,$d,$h,$mn,$s);
2502             }
2503              
2504             ###############################################################################
2505             # Timezone critical dates
2506              
2507             # NOTE: Although I would prefer to stick this routine in the
2508             # Date::Manip::TZ module where it would be more appropriate, it must
2509             # appear here as it will be used to generate the data that will be
2510             # used by the Date::Manip::TZ module.
2511             #
2512             # This calculates a critical date based on timezone information. The
2513             # critical date is the date (usually in the current time) at which
2514             # the current timezone period ENDS.
2515             #
2516             # Input is:
2517             # $year,$mon,$flag,$num,$dow
2518             # This is information from the appropriate Rule line from the
2519             # zoneinfo files. These are used to determine the date (Y/M/D)
2520             # when the timezone period will end.
2521             # $isdst
2522             # Whether or not the next timezone period is a Daylight Saving
2523             # Time period.
2524             # $time,$timetype
2525             # The time of day when the change occurs. The timetype can be
2526             # 'w' (wallclock time in the current period), 's' (standard
2527             # time which will match wallclock time in a non-DST period, or
2528             # be off an hour in a DST period), and 'u' (universal time).
2529             #
2530             # Output is:
2531             # $endUT, $endLT, $begUT, $begLT
2532             # endUT is the actual last second of the current timezone
2533             # period. endLT is the same time expressed in local time.
2534             # begUT is the start (in UT) of the next time period. Note that
2535             # the begUT date is the one which actually corresponds to the
2536             # date/time specified in the input. begLT is the time in the new
2537             # local time. The endUT/endLT are the time one second earlier.
2538             #
2539             sub _critical_date {
2540 43     43   59198 my($self,$year,$mon,$flag,$num,$dow,
2541             $isdst,$time,$timetype,$stdoff,$dstoff) = @_;
2542              
2543             #
2544             # Get the predicted Y/M/D
2545             #
2546              
2547 43         93 my($y,$m,$d) = ($year+0,$mon+0,1);
2548              
2549 43 100       120 if ($flag eq 'dom') {
    100          
    50          
    0          
2550 1         2 $d = $num;
2551              
2552             } elsif ($flag eq 'last') {
2553 4         9 my $ymd = $self->nth_day_of_week($year,-1,$dow,$mon);
2554 4         8 $d = $$ymd[2];
2555              
2556             } elsif ($flag eq 'ge') {
2557 38         90 my $ymd = $self->nth_day_of_week($year,1,$dow,$mon);
2558 38         50 $d = $$ymd[2];
2559 38         77 while ($d < $num) {
2560 24         48 $d += 7;
2561             }
2562              
2563             } elsif ($flag eq 'le') {
2564 0         0 my $ymd = $self->nth_day_of_week($year,-1,$dow,$mon);
2565 0         0 $d = $$ymd[2];
2566 0         0 while ($d > $num) {
2567 0         0 $d -= 7;
2568             }
2569             }
2570              
2571             #
2572             # Get the predicted time and the date (not yet taking into
2573             # account time type).
2574             #
2575              
2576 43         50 my($h,$mn,$s) = @{ $self->split('hms',$time) };
  43         91  
2577 43         86 my $date = [ $y,$m,$d,$h,$mn,$s ];
2578              
2579             #
2580             # Calculate all the relevant dates.
2581             #
2582              
2583 43         57 my($endUT,$endLT,$begUT,$begLT,$offset);
2584 43         72 $stdoff = $self->split('offset',$stdoff);
2585 43         80 $dstoff = $self->split('offset',$dstoff);
2586              
2587 43 100       86 if ($timetype eq 'w') {
    100          
2588 39 100       106 $begUT = $self->calc_date_time($date,($isdst ? $stdoff : $dstoff), 1);
2589             } elsif ($timetype eq 'u') {
2590 2         3 $begUT = $date;
2591             } else {
2592 2         5 $begUT = $self->calc_date_time($date,$stdoff, 1);
2593             }
2594              
2595 43         104 $endUT = $self->calc_date_time($begUT,[0,0,-1]);
2596 43 100       105 $endLT = $self->calc_date_time($endUT,($isdst ? $stdoff : $dstoff));
2597 43 100       90 $begLT = $self->calc_date_time($begUT,($isdst ? $dstoff : $stdoff));
2598              
2599 43         169 return ($endUT,$endLT,$begUT,$begLT);
2600             }
2601              
2602             ###############################################################################
2603             # Get a list of strings to try to parse.
2604              
2605             sub _encoding {
2606 4606     4606   7748 my($self,$string) = @_;
2607 4606         5745 my @ret;
2608              
2609 4606         5555 foreach my $enc (@{ $$self{'data'}{'calc'}{'enc_in'} }) {
  4606         12145  
2610 9565 100       20479 if (lc($enc) eq 'utf-8') {
    100          
2611 4606         14564 _utf8_on($string);
2612 4606 100       14856 push(@ret,$string) if is_utf8($string, 1);
2613             } elsif (lc($enc) eq 'perl') {
2614 4606         13828 push(@ret,encode_utf8($string));
2615             } else {
2616 353         433 my $tmp = $string;
2617 353         706 _utf8_off($tmp);
2618 353         1376 $tmp = encode_utf8(decode($enc, $tmp));
2619 353         33875 _utf8_on($tmp);
2620 353 50       965 push(@ret,$tmp) if is_utf8($tmp, 1);;
2621             }
2622             }
2623              
2624 4606         12414 return @ret;
2625             }
2626              
2627             1;
2628             # Local Variables:
2629             # mode: cperl
2630             # indent-tabs-mode: nil
2631             # cperl-indent-level: 3
2632             # cperl-continued-statement-offset: 2
2633             # cperl-continued-brace-offset: 0
2634             # cperl-brace-offset: 0
2635             # cperl-brace-imaginary-offset: 0
2636             # cperl-label-offset: 0
2637             # End: