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   1118 use strict;
  168         367  
  168         4847  
16 168     168   812 use warnings;
  168         351  
  168         3802  
17 168     168   758 use integer;
  168         295  
  168         787  
18 168     168   2969 use utf8;
  168         310  
  168         713  
19 168     168   3989 use Carp;
  168         326  
  168         9395  
20             #use re 'debug';
21              
22 168     168   1108 use Date::Manip::Obj;
  168         341  
  168         4264  
23 168     168   74485 use Date::Manip::TZ_Base;
  168         443  
  168         9371  
24             our @ISA = qw(Date::Manip::Obj Date::Manip::TZ_Base);
25              
26 168     168   95682 use Encode qw(encode_utf8 from_to find_encoding decode _utf8_off _utf8_on is_utf8);
  168         1723476  
  168         131189  
27             require Date::Manip::Lang::index;
28              
29             our $VERSION;
30             $VERSION='6.91';
31 168     168   961 END { undef $VERSION; }
32              
33             ###############################################################################
34             # BASE METHODS
35             ###############################################################################
36              
37             sub _init {
38 499     499   1370 my($self) = @_;
39              
40 499         2298 $self->_init_cache();
41 499         2205 $self->_init_language();
42 499         2283 $self->_init_config();
43 499         1986 $self->_init_events();
44 499         1751 $self->_init_holidays();
45 499         1737 $self->_init_now();
46              
47 499         1245 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   1238 my($self) = @_;
54 499 50       3127 return if (exists $$self{'cache'}{'init'});
55 499         1620 $$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         1558 $$self{'cache'}{'ly'} = {};
62 499         1440 $$self{'cache'}{'ds1_mon'} = {};
63 499         1411 $$self{'cache'}{'dow_mon'} = {};
64              
65 499         1161 return;
66             }
67              
68             # Config dependent data. Needs to be reset every time the config is reset.
69             sub _init_data {
70 500     500   1234 my($self,$force) = @_;
71 500 100 66     1940 return if (exists $$self{'data'}{'calc'} && ! $force);
72              
73 499         1724 $$self{'data'}{'calc'} = {}; # Calculated values
74              
75 499         1030 return;
76             }
77              
78             # Initializes config dependent data
79             sub _init_config {
80 500     500   1382 my($self,$force) = @_;
81 500 50 66     2568 return if (exists $$self{'data'}{'sections'}{'conf'} && ! $force);
82 500         2025 $self->_init_data();
83              
84             #
85             # Set config defaults
86             #
87              
88 500         10552 $$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         3149 $$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         2199 $self->_calc_workweek();
196              
197             #
198             # Initialize some config variables that do some additional work.
199             #
200              
201 500         3236 $self->_config_var('workday24hr', 1);
202 500         1978 $self->_config_var('workdaybeg', '08:00:00');
203 500         2179 $self->_config_var('workdayend', '17:00:00');
204 500         2039 $self->_config_var('workday24hr', 0);
205              
206 500         1857 $self->_config_var('dateformat', 'US');
207 500         1911 $self->_config_var('yytoyyyy', 89);
208 500         1934 $self->_config_var('jan1week1', 0);
209 500         1880 $self->_config_var('printable', 0);
210 500         1877 $self->_config_var('firstday', 1);
211 500         1918 $self->_config_var('workweekbeg', 1);
212 500         1804 $self->_config_var('workweekend', 5);
213 500         1790 $self->_config_var('language', 'english');
214 500         2452 $self->_config_var('recurrange', 'none');
215 500         1908 $self->_config_var('maxrecurattempts', 100);
216 500         1754 $self->_config_var('defaulttime', 'midnight');
217              
218             # Set OS specific defaults
219              
220 500         2076 my $os = $self->_os();
221              
222 500         1148 return;
223             }
224              
225             sub _calc_workweek {
226 1528     1528   3388 my($self,$beg,$end) = @_;
227              
228 1528 100       6018 $beg = $self->_config('workweekbeg') if (! $beg);
229 1528 100       4886 $end = $self->_config('workweekend') if (! $end);
230              
231 1528         3977 $$self{'data'}{'len'}{'workweek'} = $end - $beg + 1;
232              
233 1528         2566 return;
234             }
235              
236             sub _calc_bdlength {
237 1538     1538   2927 my($self) = @_;
238              
239 1538         2222 my @beg = @{ $$self{'data'}{'calc'}{'workdaybeg'} };
  1538         3801  
240 1538         2394 my @end = @{ $$self{'data'}{'calc'}{'workdayend'} };
  1538         3148  
241              
242 1538         4296 $$self{'data'}{'len'}{'bdlength'} =
243             ($end[0]-$beg[0])*3600 + ($end[1]-$beg[1])*60 + ($end[2]-$beg[2]);
244              
245 1538         2904 return;
246             }
247              
248             sub _init_business_length {
249 2566     2566   4524 my($self) = @_;
250              
251 168     168   1469 no integer;
  168         363  
  168         1230  
252 2566         4443 my $x = $$self{'data'}{'len'}{'workweek'};
253 2566         5662 my $y_to_d = $x/7 * 365.2425;
254 2566         4092 my $d_to_s = $$self{'data'}{'len'}{'bdlength'};
255 2566         3664 my $w_to_d = $x;
256              
257 2566         12398 $$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         4640 return;
264             }
265              
266             # Events and holidays are reset only when they are read in.
267             sub _init_events {
268 513     513   1328 my($self,$force) = @_;
269 513 50 66     2101 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         1520 $$self{'data'}{'events'} = {};
295 513         1481 $$self{'data'}{'sections'}{'events'} = [];
296 513         1427 $$self{'data'}{'eventyears'} = {};
297 513         1168 $$self{'data'}{'eventobjs'} = 0;
298              
299 513         885 return;
300             }
301              
302             sub _init_holidays {
303 517     517   1277 my($self,$force) = @_;
304 517 50 66     2019 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         1736 $$self{'data'}{'holidays'} = {};
321 517         1442 $$self{'data'}{'sections'}{'holidays'} = [];
322 517         1117 $$self{'data'}{'init_holidays'} = 0;
323              
324 517         1012 return;
325             }
326              
327             sub _init_now {
328 499     499   1115 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         2206 $$self{'data'}{'now'} = {};
349 499         2540 $$self{'data'}{'now'}{'force'} = 0;
350 499         2030 $$self{'data'}{'now'}{'set'} = 0;
351 499         1214 $$self{'data'}{'tmpnow'} = [];
352              
353 499         1722 return;
354             }
355              
356             # Language information only needs to be initialized if the language changes.
357             sub _init_language {
358 1032     1032   2390 my($self,$force) = @_;
359 1032 50 66     4197 return if (exists $$self{'data'}{'lang'} && ! $force);
360              
361 1032         2667 $$self{'data'}{'lang'} = {}; # Current language info
362 1032         3618 $$self{'data'}{'rx'} = {}; # Regexps generated from language
363 1032         2296 $$self{'data'}{'words'} = {}; # Types of words in the language
364 1032         2210 $$self{'data'}{'wordval'} = {}; # Value of words in the language
365              
366 1032         1917 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 27416 my($self,$arg) = @_;
378              
379 12393 100       22691 if (ref($arg)) {
380 7664         12496 my($y,$m,$d) = @$arg;
381 7664         11020 $m = ($m + 9) % 12;
382 7664         10836 $y = $y - $m/10;
383 7664         22025 return 365*$y + $y/4 - $y/100 + $y/400 + ($m*306 + 5)/10 + ($d - 1) - 305;
384             } else {
385 4729         6456 my $g = $arg + 305;
386 168     168   98051 no integer;
  168         455  
  168         868  
387 4729         11162 my $y = int((10000*$g + 14780)/3652425);
388 168     168   7702 use integer;
  168         411  
  168         835  
389 4729         8715 my $ddd = $g - (365*$y + $y/4 - $y/100 + $y/400);
390 4729 100       9119 if ($ddd < 0) {
391 6         12 $y = $y - 1;
392 6         34 $ddd = $g - (365*$y + $y/4 - $y/100 + $y/400);
393             }
394 4729         7373 my $mi = (100*$ddd + 52)/3060;
395 4729         7079 my $mm = ($mi + 2) % 12 + 1;
396 4729         6921 $y = $y + ($mi + 2)/12;
397 4729         7235 my $dd = $ddd - ($mi*306 + 5)/10 + 1;
398 4729         10220 return [$y, $mm, $dd];
399             }
400             }
401              
402             # Algorithm from the Calendar FAQ
403             #
404             sub day_of_week {
405 9932     9932 1 24808 my($self,$date) = @_;
406 9932         16879 my($y,$m,$d) = @$date;
407              
408 9932         16939 my $a = (14-$m)/12;
409 9932         14124 $y = $y-$a;
410 9932         14556 $m = $m + 12*$a - 2;
411 9932         18925 my $dow = ($d + $y + $y/4 - $y/100 + $y/400 + (31*$m)/12) % 7;
412 9932 100       18969 $dow = 7 if ($dow==0);
413 9932         19301 return $dow;
414             }
415              
416             sub leapyear {
417 3780     3780 1 9104 my($self,$y) = @_;
418 3780 100 100     17464 return 1 if ( ( ($y % 4 == 0) and ($y % 100 != 0) ) or
      100        
419             $y % 400 == 0 );
420 2416         5363 return 0;
421             }
422              
423             sub days_in_year {
424 367     367 1 3331 my($self,$y) = @_;
425 367 100       733 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 74816 my($self,$y,$m) = @_;
433 37180 100       81113 if (! $m) {
    100          
434 2 100       6 return (31,29,31,30, 31,30,31,31, 30,31,30,31) if ($self->leapyear($y));
435 1         6 return (31,28,31,30, 31,30,31,31, 30,31,30,31);
436              
437             } elsif ($m == 2) {
438 2904         6910 return 28 + $self->leapyear($y);
439              
440             } else {
441 34274         71770 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 36329 my($self,@args) = @_;
454 168     168   76304 no integer;
  168         440  
  168         736  
455 674         1179 my($n,$ly,$tmp,$remain,$day,$y,$m,$d,$h,$mn,$s,$time);
456              
457 674 100       1721 if (@args == 2) {
458             # $date = day_of_year($y,$day);
459              
460 265         459 ($y,$tmp) = @args;
461              
462 265         559 $ly = $self->leapyear($y);
463 265 100       862 $time = 1 if ($tmp =~ /\./);
464 265         435 $n = int($tmp);
465 265         395 $remain = $tmp - $n;
466              
467             # Calculate the month and the day
468 265         640 for ($m=1; $m<=12; $m++) {
469 1065 100       2252 last if ($n<=($doy_days[$ly][$m]));
470             }
471 265         556 $d = $n-($doy_days[$ly][$m-1]);
472 265 100       1101 return [$y,$m,$d] if (! $time);
473              
474             # Calculate the hours, minutes, and seconds into the day.
475              
476 9         20 $s = $remain * 86400;
477 9         16 $mn = int($s/60);
478 9         18 $s = $s - ($mn*60);
479 9 100       67 $s = sprintf('%0.2f',$s) if ("$s" ne int($s));
480 9         16 $h = int($mn/60);
481 9         15 $mn = $mn % 60;
482              
483 9         43 return [$y,$m,$d,$h,$mn,$s];
484              
485             } else {
486 409         644 ($y,$m,$d,$h,$mn,$s) = @{ $args[0] };
  409         896  
487              
488 409 100       1110 $ly = ($m > 2 ? $self->leapyear($y) : 0);
489 409         972 $day = ($doy_days[$ly][$m-1]+$d);
490              
491 409 100       1287 return $day if (! defined $h);
492              
493 30         77 $day += ($h*3600 + $mn*60 + $s)/86400;
494 30         70 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 14379 my($self,$y,$n,$dow,$m) = @_;
504 997         1450 $y += 0;
505 997 100       1967 $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         1556 my($d,$max,$ddow);
512              
513 997 100       1712 if ($m) {
514 913         1741 $max = $self->days_in_month($y,$m);
515 913 100       1945 $d = ($n<0 ? $max : 1);
516 913         2389 $ddow = $self->day_of_week([$y,$m,$d]);
517             } else {
518 84         207 $max = $self->days_in_year($y);
519 84 50       190 $d = ($n<0 ? $max : 1);
520 84 50       162 if ($n<0) {
521 0         0 $d = $max;
522 0         0 $ddow = $self->day_of_week([$y,12,31]);
523             } else {
524 84         114 $d = 1;
525 84         226 $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       2359 if ($dow < $ddow) {
533 550         972 $d += 7 - ($ddow-$dow);
534             } else {
535 447         767 $d += ($dow-$ddow);
536             }
537 997 100       1930 $d -= 7 if ($d > $max);
538              
539             # Find the nth occurrence of $dow
540              
541 997 100       1931 if ($n > 1) {
    100          
542 847         1307 $d += 7*($n-1);
543 847 50       1622 return undef if ($d > $max);
544             } elsif ($n < -1) {
545 2         4 $d -= 7*(-1*$n-1);
546 2 50       4 return undef if ($d < 1);
547             }
548              
549             # Return the date
550              
551 997 100       1831 if ($m) {
552 913         2375 return [$y,$m,$d];
553             }
554 84         233 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   94206 no integer;
  168         466  
  168         800  
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 11881 my($self,$arg) = @_;
567              
568 2560 100       4701 if (ref($arg)) {
569 2555         4657 ($y,$m,$d,$h,$mn,$s) = @$arg;
570 2555         6015 $sec_0 = ($self->days_since_1BC([$y,$m,$d])-1)*24*3600 + $h*3600 +
571             $mn*60 + $s;
572 2555         4762 $sec = $sec_0 - $sec_70;
573 2555         4926 return $sec;
574              
575             } else {
576 5         10 ($sec) = $arg;
577 5         14 $sec_0 = $sec_70 + $sec;
578 5         19 $tmp = int($sec_0/24/3600)+1;
579 5         13 my $ymd = $self->days_since_1BC($tmp);
580 5         12 ($y,$m,$d) = @$ymd;
581 5         15 $sec_0 -= ($tmp-1)*24*3600;
582 5         9 $h = int($sec_0/3600);
583 5         8 $sec_0 -= $h*3600;
584 5         10 $mn = int($sec_0/60);
585 5         7 $s = $sec_0 - $mn*60;
586 5         21 return [$y,$m,$d,$h,$mn,$s];
587             }
588             }
589             }
590              
591             sub check {
592 15339     15339 1 37727 my($self,$date) = @_;
593 15339         31297 my($y,$m,$d,$h,$mn,$s) = @$date;
594              
595 15339 100 66     41172 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         42702 my $days = $self->days_in_month($y,$m);
600              
601 15328 100 66     49424 return 0 if ($d<1 || $d>$days);
602 15324         40151 return 1;
603             }
604              
605             sub check_time {
606 15403     15403 1 25188 my($self,$hms) = @_;
607 15403         25975 my($h,$mn,$s) = @$hms;
608              
609 15403 100 66     175927 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         109025 return 1;
613             }
614              
615             sub week1_day1 {
616 28     28 1 3911 my($self,$year) = @_;
617 28         65 my $firstday = $self->_config('firstday');
618 28         61 return $self->_week1_day1($firstday,$year);
619             }
620              
621             sub _week1_day1 {
622 600     600   1055 my($self,$firstday,$year) = @_;
623 600         1201 my $jan1week1 = $self->_config('jan1week1');
624             return $$self{'cache'}{'week1day1'}{$firstday}{$jan1week1}{$year}
625 600 100       2290 if (exists $$self{'cache'}{'week1day1'}{$firstday}{$jan1week1}{$year});
626              
627             # First week contains either Jan 4 (default) or Jan 1
628              
629 274         530 my($y,$m,$d) = ($year,1,4);
630 274 100       536 $d = 1 if ($jan1week1);
631              
632             # Go back to the previous (counting today) $firstday
633              
634 274         819 my $dow = $self->day_of_week([$y,$m,$d]);
635 274 100       699 if ($dow != $firstday) {
636 243 100       498 $firstday = 0 if ($firstday == 7);
637 243         355 $d -= ($dow-$firstday);
638 243 100       490 if ($d<1) {
639 153         254 $y--;
640 153         211 $m = 12;
641 153         220 $d += 31;
642             }
643             }
644              
645 274         878 $$self{'cache'}{'week1day1'}{$firstday}{$jan1week1}{$year} = [ $y,$m,$d ];
646 274         820 return [$y,$m,$d];
647             }
648              
649             sub weeks_in_year {
650 77     77 1 3234 my($self,$y) = @_;
651 77         203 my $firstday = $self->_config('firstday');
652 77         243 return $self->_weeks_in_year($firstday,$y);
653             }
654              
655             sub _weeks_in_year {
656 285     285   570 my($self,$firstday,$y) = @_;
657 285         649 my $jan1week1 = $self->_config('jan1week1');
658             return $$self{'cache'}{'wiy'}{$firstday}{$jan1week1}{$y}
659 285 100       1219 if (exists $$self{'cache'}{'wiy'}{$firstday}{$jan1week1}{$y});
660              
661             # Get the week1 day1 dates for this year and the next one.
662 108         199 my ($y1,$m1,$d1) = @{ $self->_week1_day1($firstday,$y) };
  108         259  
663 108         214 my ($y2,$m2,$d2) = @{ $self->_week1_day1($firstday,$y+1) };
  108         256  
664              
665             # Calculate the number of days between them.
666 108         315 my $diy = $self->days_in_year($y);
667 108 100       260 if ($y1 < $y) {
668 58         97 $diy += (32-$d1);
669             } else {
670 50         111 $diy -= ($d1-1);
671             }
672 108 100       262 if ($y2 < $y+1) {
673 60         133 $diy -= (32-$d2);
674             } else {
675 48         90 $diy += ($d2-1);
676             }
677              
678 108         188 $diy = $diy/7;
679 108         241 $$self{'cache'}{'wiy'}{$firstday}{$jan1week1}{$y} = $diy;
680 108         279 return $diy;
681             }
682              
683             sub week_of_year {
684 525     525 1 9787 my($self,@args) = @_;
685 525         1203 my $firstday = $self->_config('firstday');
686 525         1301 return $self->_week_of_year($firstday,@args);
687             }
688              
689             sub _week_of_year {
690 533     533   1082 my($self,$firstday,@args) = @_;
691 533         1082 my $jan1week1 = $self->_config('jan1week1');
692              
693 533 100       1354 if ($#args == 1) {
694             # (y,m,d) = week_of_year(y,w)
695 325         736 my($year,$w) = @args;
696              
697             return $$self{'cache'}{'woy1'}{$firstday}{$jan1week1}{$year}{$w}
698 325 100       1837 if (exists $$self{'cache'}{'woy1'}{$firstday}{$jan1week1}{$year}{$w});
699              
700 148         388 my $ymd = $self->_week1_day1($firstday,$year);
701 148 100       696 $ymd = $self->calc_date_days($ymd,($w-1)*7) if ($w > 1);
702              
703 148         520 $$self{'cache'}{'woy1'}{$firstday}{$jan1week1}{$year}{$w} = $ymd;
704 148         552 return $ymd;
705             }
706              
707             # (y,w) = week_of_year([y,m,d])
708 208         337 my($y,$m,$d) = @{ $args[0] };
  208         457  
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         298 my($y0,$m0,$d0) = @{ $self->_week1_day1($firstday,$y) };
  208         562  
714 208 100 100     951 if ($y0==$y && $m==1 && $d<$d0) {
      100        
715 3         10 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         709 my $n = $self->day_of_year([$y,$m,$d]);
722 205 100       525 if ($y0<$y) {
723 72         126 $n += (32-$d0);
724             } else {
725 133         224 $n -= ($d0-1);
726             }
727 205         397 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       496 if ($w>$self->_weeks_in_year($firstday,$y)) {
732 5         37 return($y+1,1);
733             }
734 200         689 return($y,$w);
735             }
736              
737             ###############################################################################
738             # CALC METHODS
739             ###############################################################################
740              
741             sub calc_date_date {
742 18     18 1 50678 my($self,$date0,$date1) = @_;
743              
744             # Order them so date0 < date1
745             # If $minus = 1, then the delta is negative
746              
747 18         27 my $minus = 0;
748 18         45 my $cmp = $self->cmp($date0,$date1);
749              
750 18 100       69 if ($cmp == 0) {
    100          
751 4         15 return [0,0,0];
752              
753             } elsif ($cmp == 1) {
754 7         11 $minus = 1;
755 7         11 my $tmp = $date1;
756 7         9 $date1 = $date0;
757 7         9 $date0 = $tmp;
758             }
759              
760 14         48 my($y0,$m0,$d0,$h0,$mn0,$s0) = @$date0;
761 14         29 my($y1,$m1,$d1,$h1,$mn1,$s1) = @$date1;
762              
763 14 100 100     59 my $sameday = ($y0 == $y1 && $m0 == $m1 && $d0 == $d1 ? 1 : 0);
764              
765             # Handle the various cases.
766              
767 14         21 my($dh,$dm,$ds);
768 14 100       20 if ($sameday) {
769 4         6 ($dh,$dm,$ds) = @{ $self->_calc_hms_hms([$h0,$mn0,$s0],[$h1,$mn1,$s1]) };
  4         18  
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         33 my $t1 = $self->_calc_hms_hms([$h0,$mn0,$s0],[24,0,0]);
776 10         34 my $t2 = $self->_calc_hms_hms([0,0,0],[$h1,$mn1,$s1]);
777 10         16 ($dh,$dm,$ds) = @{ $self->calc_time_time($t1,$t2) };
  10         23  
778              
779 10         33 my $dd0 = $self->days_since_1BC([$y0,$m0,$d0]);
780 10         19 $dd0++;
781 10         24 my $dd1 = $self->days_since_1BC([$y1,$m1,$d1]);
782 10         25 $dh += ($dd1-$dd0)*24;
783             }
784              
785 14 100       32 if ($minus) {
786 7         10 $dh *= -1;
787 7         12 $dm *= -1;
788 7         8 $ds *= -1;
789             }
790 14         35 return [$dh,$dm,$ds];
791             }
792              
793             sub calc_date_days {
794 4721     4721 1 25441 my($self,$date,$n,$subtract) = @_;
795 4721         8888 my($y,$m,$d,$h,$mn,$s) = @$date;
796 4721 100       9081 my($ymdonly) = (defined $h ? 0 : 1);
797              
798 4721 100       8645 $n *= -1 if ($subtract);
799 4721         12910 my $d1bc = $self->days_since_1BC([$y,$m,$d]);
800 4721         8695 $d1bc += $n;
801 4721         8439 my $ymd = $self->days_since_1BC($d1bc);
802              
803 4721 100       8728 if ($ymdonly) {
804 2607         7874 return $ymd;
805             } else {
806 2114         8355 return [@$ymd,$h*1,$mn*1,$s*1];
807             }
808             }
809              
810             sub calc_date_delta {
811 8     8 1 36280 my($self,$date,$delta,$subtract) = @_;
812 8         21 my($y,$m,$d,$h,$mn,$s) = @$date;
813 8         25 my($dy,$dm,$dw,$dd,$dh,$dmn,$ds) = @$delta;
814              
815             ($y,$m,$d) =
816 8         11 @{ $self->_calc_date_ymwd([$y,$m,$d], [$dy,$dm,$dw,$dd], $subtract) };
  8         35  
817              
818 8         42 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 87000 my($self,$date,$time,$subtract) = @_;
823 12713         22429 my($y,$m,$d,$h,$mn,$s) = @$date;
824 12713         21327 my($dh,$dmn,$ds) = @$time;
825              
826 12713 100 66     40729 if ($ds > 59 || $ds < -59) {
827 4         15 $dmn += int($ds/60);
828 4         9 $ds = $ds % 60;
829             }
830 12713 100 66     34926 if ($dmn > 59 || $dmn < -59) {
831 4         11 $dh += int($dmn/60);
832 4         8 $dmn = $dmn % 60;
833             }
834 12713         16822 my $dd = 0;
835 12713 100 100     34244 if ($dh > 23 || $dh < -23) {
836 34         60 $dd = int($dh/24);
837 34         48 $dh = $dh % 24;
838             }
839              
840             # Handle subtraction
841 12713 100       22237 if ($subtract) {
842 5592         7714 $dh *= -1;
843 5592         7080 $dmn *= -1;
844 5592         7159 $ds *= -1;
845 5592         7609 $dd *= -1;
846             }
847              
848 12713 100       20799 if ($dd == 0) {
849 12679         16131 $y *= 1;
850 12679         15256 $m *= 1;
851 12679         15622 $d *= 1;
852             } else {
853 34         54 ($y,$m,$d) = @{ $self->calc_date_days([$y,$m,$d],$dd) };
  34         125  
854             }
855              
856 12713         32331 $self->_mod_add(60,$ds,\$s,\$mn);
857 12713         30647 $self->_mod_add(60,$dmn,\$mn,\$h);
858 12713         28898 $self->_mod_add(24,$dh,\$h,\$d);
859              
860 12713 100       22459 if ($d<1) {
861 9         22 $m--;
862 9 100       36 $y--, $m=12 if ($m<1);
863 9         31 my $day_in_mon = $self->days_in_month($y,$m);
864 9         35 $d += $day_in_mon;
865             } else {
866 12704         24336 my $day_in_mon = $self->days_in_month($y,$m);
867 12704 100       24787 if ($d>$day_in_mon) {
868 68         107 $d -= $day_in_mon;
869 68         97 $m++;
870 68 100       176 $y++, $m=1 if ($m>12);
871             }
872             }
873              
874 12713         46975 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   35306 my($self,$date,$ymwd,$subtract) = @_;
890 2425         4511 my($y,$m,$d,$h,$mn,$s) = @$date;
891 2425         4292 my($dy,$dm,$dw,$dd) = @$ymwd;
892 2425 100       4605 my($ymdonly) = (defined $h ? 0 : 1);
893              
894 2425         3534 $dd += $dw*7;
895              
896 2425 100       4019 if ($subtract) {
897 1207         1690 $y -= $dy;
898 1207         3377 $self->_mod_add(-12,-1*$dm,\$m,\$y);
899 1207         2130 $dd *= -1;
900              
901             } else {
902 1218         1684 $y += $dy;
903 1218         2663 $self->_mod_add(-12,$dm,\$m,\$y);
904             }
905              
906 2425         4863 my $dim = $self->days_in_month($y,$m);
907 2425 100       4766 $d = $dim if ($d > $dim);
908              
909 2425         3106 my $ymd;
910 2425 100       3923 if ($dd == 0) {
911 2242         4098 $ymd = [$y,$m,$d];
912             } else {
913 183         550 $ymd = $self->calc_date_days([$y,$m,$d],$dd);
914             }
915              
916 2425 100       4381 if ($ymdonly) {
917 2423         5583 return $ymd;
918             } else {
919 2         8 return [@$ymd,$h,$mn,$s];
920             }
921             }
922              
923             sub _calc_hms_hms {
924 24     24   45 my($self,$hms0,$hms1) = @_;
925 24         46 my($h0,$m0,$s0,$h1,$m1,$s1) = (@$hms0,@$hms1);
926              
927 24         48 my($s) = ($h1-$h0)*3600 + ($m1-$m0)*60 + $s1-$s0;
928 24         34 my($m) = int($s/60);
929 24         36 $s -= $m*60;
930 24         59 my($h) = int($m/60);
931 24         29 $m -= $h*60;
932 24         49 return [$h,$m,$s];
933             }
934              
935             sub calc_time_time {
936 86     86 1 15857 my($self,$time0,$time1,$subtract) = @_;
937 86         209 my($h0,$m0,$s0,$h1,$m1,$s1) = (@$time0,@$time1);
938              
939 86 100       427 if ($subtract) {
940 51         87 $h1 *= -1;
941 51         71 $m1 *= -1;
942 51         72 $s1 *= -1;
943             }
944 86         196 my($s) = (($h0+$h1)*60 + ($m0+$m1))*60 + $s0+$s1;
945 86         166 my($m) = int($s/60);
946 86         135 $s -= $m*60;
947 86         127 my($h) = int($m/60);
948 86         121 $m -= $h*60;
949              
950 86         249 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 7101     7101 1 22749 my($self,$date0,$date1) = @_;
960 7101   66     42270 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   2170 my($self) = @_;
973              
974 1009         1878 my $os = '';
975              
976 1009 50 33     15133 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         1981 $os = 'Unix';
994             }
995              
996 1009         2431 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   471 my($self,$sect,$var,$val) = @_;
1013 253         411 $sect = lc($sect);
1014              
1015             #
1016             # $self->_section(SECT) creates a new section
1017             #
1018              
1019 253 0 33     473 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       482 if ($var eq '_vars') {
1030 0         0 return @{ $$self{'data'}{'sections'}{$sect} };
  0         0  
1031             }
1032              
1033 253         324 push @{ $$self{'data'}{'sections'}{$sect} },($var,$val);
  253         678  
1034 253         632 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   13471 my($self,$var,$val) = @_;
1042              
1043 7840 100 33     67990 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         92 $self->_init_holidays(1);
1050 18         70 return;
1051              
1052             } elsif ($var eq 'eraseevents') {
1053 14         73 $self->_init_events(1);
1054 14         48 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         2285 my $err = $self->_language($val);
1066 533 50       3419 return if ($err);
1067 533         5456 $err = $self->_config_var_encoding();
1068 533 50       1586 return if ($err);
1069              
1070             } elsif ($var eq 'yytoyyyy') {
1071 527         1310 $val = lc($val);
1072 527 50 100     6859 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         2370 my $err = $self->_config_var_workweekbeg($val);
1082 514 50       1784 return if ($err);
1083              
1084             } elsif ($var eq 'workweekend') {
1085 514         2082 my $err = $self->_config_var_workweekend($val);
1086 514 50       1639 return if ($err);
1087              
1088             } elsif ($var eq 'workday24hr') {
1089 1014         2963 my $err = $self->_config_var_workday24hr($val);
1090 1014 50       2725 return if ($err);
1091              
1092             } elsif ($var eq 'workdaybeg') {
1093 520         2175 my $err = $self->_config_var_workdaybegend(\$val,'WorkDayBeg');
1094 520 50       2588 return if ($err);
1095              
1096             } elsif ($var eq 'workdayend') {
1097 518         1717 my $err = $self->_config_var_workdaybegend(\$val,'WorkDayEnd');
1098 518 50       2013 return if ($err);
1099              
1100             } elsif ($var eq 'firstday') {
1101 536         2082 my $err = $self->_config_var_firstday($val);
1102 536 50       1601 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         1816 my $err = $self->_config_var_recurrange($val);
1113 514 50       1471 return if ($err);
1114              
1115             } elsif ($var eq 'defaulttime') {
1116 516         2017 my $err = $self->_config_var_defaulttime($val);
1117 516 50       1427 return if ($err);
1118              
1119             } elsif ($var eq 'periodtimesep') {
1120             # We have to redo the time regexp
1121 1         3 delete $$self{'data'}{'rx'}{'time'};
1122              
1123             } elsif ($var eq 'format_mmmyyyy') {
1124 4         49 my $err = $self->_config_var_format_mmmyyyy($val);
1125 4 50       18 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         17146 $$self{'data'}{'sections'}{'conf'}{$var} = $val;
1140 7807         16103 return;
1141             }
1142              
1143             ###############################################################################
1144             # Specific config variable functions
1145              
1146             sub _config_var_encoding {
1147 533     533   2288 my($self,$val) = @_;
1148              
1149 533 50       1481 if (! $val) {
    0          
1150 533         1772 $$self{'data'}{'calc'}{'enc_in'} = [ @{ $$self{'data'}{'enc'} } ];
  533         3019  
1151 533         2464 $$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       1750 if (! @{ $$self{'data'}{'calc'}{'enc_in'} }) {
  533         3680  
1198 516         2345 $$self{'data'}{'calc'}{'enc_in'} = [ qw(utf-8 perl) ];
1199             }
1200              
1201 533         1238 return 0;
1202             }
1203              
1204             sub _config_var_recurrange {
1205 514     514   1331 my($self,$val) = @_;
1206              
1207 514         3000 $val = lc($val);
1208 514 50       6448 if ($val =~ /^(none|year|month|week|day|all)$/o) {
1209 514         1297 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   1438 my($self,$val) = @_;
1218              
1219 514 50       1535 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       2150 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         2187 $self->_calc_workweek($val,'');
1229 514         1775 $self->_init_business_length();
1230 514         1075 return 0;
1231             }
1232              
1233             sub _config_var_workweekend {
1234 514     514   1427 my($self,$val) = @_;
1235              
1236 514 50       1524 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       1984 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         2067 $self->_calc_workweek('',$val);
1246 514         1616 $self->_init_business_length();
1247 514         998 return 0;
1248             }
1249              
1250             sub _config_var_workday24hr {
1251 1014     1014   2175 my($self,$val) = @_;
1252              
1253 1014 100       3524 if ($val) {
1254 500         1563 $$self{'data'}{'sections'}{'conf'}{'workdaybeg'} = '00:00:00';
1255 500         1070 $$self{'data'}{'sections'}{'conf'}{'workdayend'} = '24:00:00';
1256 500         1962 $$self{'data'}{'calc'}{'workdaybeg'} = [0,0,0];
1257 500         1566 $$self{'data'}{'calc'}{'workdayend'} = [24,0,0];
1258              
1259 500         1933 $self->_calc_bdlength();
1260 500         4667 $self->_init_business_length();
1261             }
1262              
1263 1014         2005 return 0;
1264             }
1265              
1266             sub _config_var_workdaybegend {
1267 1038     1038   2310 my($self,$val,$conf) = @_;
1268              
1269             # Must be a valid time. Entered as H, H:M, or H:M:S
1270              
1271 1038         3227 my $tmp = $self->split('hms',$$val);
1272 1038 50       2652 if (! defined $tmp) {
1273 0         0 carp "ERROR: [config_var] invalid: $conf: $$val";
1274 0         0 return 1;
1275             }
1276 1038         3434 $$self{'data'}{'calc'}{lc($conf)} = $tmp;
1277 1038         3194 $$val = $self->join('hms',$tmp);
1278              
1279             # workdaybeg < workdayend
1280              
1281 1038         1871 my @beg = @{ $$self{'data'}{'calc'}{'workdaybeg'} };
  1038         2871  
1282 1038         1765 my @end = @{ $$self{'data'}{'calc'}{'workdayend'} };
  1038         2495  
1283 1038         2551 my $beg = $beg[0]*3600 + $beg[1]*60 + $beg[2];
1284 1038         2083 my $end = $end[0]*3600 + $end[1]*60 + $end[2];
1285              
1286 1038 50       2675 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         2257 $$self{'data'}{'sections'}{'conf'}{'workday24hr'} = 0;
1294              
1295 1038         3053 $self->_calc_bdlength();
1296 1038         2736 $self->_init_business_length();
1297              
1298 1038         2780 return 0;
1299             }
1300              
1301             sub _config_var_firstday {
1302 536     536   1382 my($self,$val) = @_;
1303              
1304 536 50       1602 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         1320 return 0;
1310             }
1311              
1312             sub _config_var_defaulttime {
1313 516     516   1324 my($self,$val) = @_;
1314              
1315 516 50 66     2034 if (lc($val) eq 'midnight' ||
1316             lc($val) eq 'curr') {
1317 516         1160 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   16 my($self,$val) = @_;
1325              
1326 4 50 66     31 if (lc($val) eq 'first' ||
      33        
1327             lc($val) eq 'last' ||
1328             lc($val) eq '') {
1329 4         12 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   808359 no strict 'refs';
  168         445  
  168         33766  
1342             sub _language {
1343 533     533   1491 my($self,$lang) = @_;
1344 533         1288 $lang = lc($lang);
1345              
1346 533 50       2170 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     3935 $$self{'data'}{'sections'}{'conf'} eq $lang);
1353 533         2040 $self->_init_language(1);
1354              
1355 533         1472 my $mod = $Date::Manip::Lang::index::Lang{$lang};
1356 533         42369 eval "require Date::Manip::Lang::${mod}";
1357 533 50       3104 if ($@) {
1358 0         0 croak "ERROR: failed to load Date::Manip::Lang::${mod}: $@\n";
1359             }
1360              
1361 168     168   1476 no warnings 'once';
  168         421  
  168         44471  
1362 533         1001 $$self{'data'}{'lang'} = ${ "Date::Manip::Lang::${mod}::Language" };
  533         4020  
1363 533         1099 $$self{'data'}{'enc'} = [ @{ "Date::Manip::Lang::${mod}::Encodings" } ];
  533         2972  
1364              
1365             # Common words
1366 533         2560 $self->_rx_wordlist('at');
1367 533         2595 $self->_rx_wordlist('each');
1368 533         1632 $self->_rx_wordlist('last');
1369 533         3714 $self->_rx_wordlist('of');
1370 533         1860 $self->_rx_wordlist('on');
1371 533         4412 $self->_rx_wordlists('when');
1372              
1373             # Next/prev
1374 533         1723 $self->_rx_wordlists('nextprev');
1375              
1376             # Field names (years, year, yr, ...)
1377 533         2506 $self->_rx_wordlists('fields');
1378              
1379             # Numbers (first, 1st)
1380 533         2016 $self->_rx_wordlists('nth');
1381 533         2598 $self->_rx_wordlists('nth','nth_dom',31); # 1-31
1382 533         2791 $self->_rx_wordlists('nth','nth_wom',5); # 1-5
1383              
1384             # Calendar names (Mon, Tue and Jan, Feb)
1385 533         1946 $self->_rx_wordlists('day_abb');
1386 533         1930 $self->_rx_wordlists('day_char');
1387 533         2097 $self->_rx_wordlists('day_name');
1388 533         1988 $self->_rx_wordlists('month_abb');
1389 533         2065 $self->_rx_wordlists('month_name');
1390              
1391             # H:M:S separators
1392 533         2820 $self->_rx_simple('sephm');
1393 533         1555 $self->_rx_simple('sepms');
1394 533         1640 $self->_rx_simple('sepfr');
1395              
1396             # Time replacement strings
1397 533         2155 $self->_rx_replace('times');
1398              
1399             # Some offset strings
1400 533         2765 $self->_rx_replace('offset_date');
1401 533         2176 $self->_rx_replace('offset_time');
1402              
1403             # AM/PM strings
1404 533         3941 $self->_rx_wordlists('ampm');
1405              
1406             # Business/non-business mode
1407 533         1893 $self->_rx_wordlists('mode');
1408              
1409 533         1781 return 0;
1410             }
1411 168     168   1391 use strict 'refs';
  168         451  
  168         74625  
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   3090 my($self,$ele) = @_;
1421              
1422 1599 100       3570 if (exists $$self{'data'}{'lang'}{$ele}) {
1423 19 100       102 if (ref($$self{'data'}{'lang'}{$ele})) {
1424 16         42 @{ $$self{'data'}{'rx'}{$ele} } = @{ $$self{'data'}{'lang'}{$ele} };
  16         67  
  16         43  
1425             } else {
1426 3         15 $$self{'data'}{'rx'}{$ele} = $$self{'data'}{'lang'}{$ele};
1427             }
1428             } else {
1429 1580         3388 $$self{'data'}{'rx'}{$ele} = undef;
1430             }
1431              
1432 1599         2356 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   321651 my($string) = @_;
1440 223103         484732 $string =~ s/([-.+*?])/\\$1/g;
1441 223103         469172 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   5035 my($self,$ele) = @_;
1455              
1456 2665 50       6198 if (exists $$self{'data'}{'lang'}{$ele}) {
1457 2665         3489 my @tmp = @{ $$self{'data'}{'lang'}{$ele} };
  2665         7341  
1458              
1459 2665         6392 $$self{'data'}{'wordlist'}{$ele} = $tmp[0];
1460              
1461 2665         3787 my @tmp2;
1462 2665         4362 foreach my $tmp (@tmp) {
1463 4271 100       10640 push(@tmp2,_qe_quote($tmp)) if ($tmp);
1464             }
1465 2665         9418 @tmp2 = sort _sortByLength(@tmp2);
1466              
1467 2665         8107 $$self{'data'}{'rx'}{$ele} = join('|',@tmp2);
1468              
1469             } else {
1470 0         0 $$self{'data'}{'rx'}{$ele} = undef;
1471             }
1472              
1473 2665         5813 return;
1474             }
1475              
1476 168     168   1424 no strict 'vars';
  168         539  
  168         11700  
1477             sub _sortByLength {
1478 1135922     1135922   1407423 return (length $b <=> length $a);
1479             }
1480 168     168   1181 use strict 'vars';
  168         406  
  168         472779  
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   3617 my($self,$ele) = @_;
1489              
1490 1599 50       4759 if (! exists $$self{'data'}{'lang'}{$ele}) {
1491 0         0 $$self{'data'}{'rx'}{$ele} = [];
1492 0         0 return;
1493             }
1494              
1495 1599         2520 my(@key) = keys %{ $$self{'data'}{'lang'}{$ele} };
  1599         6561  
1496 1599         2930 my $i = 1;
1497 1599         4997 foreach my $key (sort(@key)) {
1498 4288         19775 my $val = $$self{'data'}{'lang'}{$ele}{$key};
1499 4288         7276 my $k = _qe_quote($key);
1500 4288         75237 $$self{'data'}{'rx'}{$ele}[$i++] = qr/(?:^|\b)($k)(?:\b|$)/i;
1501 4288         318808 $$self{'data'}{'wordmatch'}{$ele}{lc($key)} = $val;
1502             }
1503              
1504 1599         5658 @key = sort _sortByLength(@key);
1505 1599         3348 @key = map { _qe_quote($_) } @key;
  4288         8956  
1506 1599         6148 my $rx = join('|',@key);
1507              
1508 1599         54158 $$self{'data'}{'rx'}{$ele}[0] = qr/(?:^|\b)(?:$rx)(?:\b|$)/i;
1509              
1510 1599         7358 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   13919 my($self,$ele,$subset,$max) = @_;
1520 6929 100       15390 $subset = $ele if (! $subset);
1521              
1522 6929 50       16490 if (exists $$self{'data'}{'lang'}{$ele}) {
1523 6929         10746 my @vallist = @{ $$self{'data'}{'lang'}{$ele} };
  6929         18741  
1524 6929 100 66     20611 $max = $#vallist+1 if (! $max || $max > $#vallist+1);
1525 6929         9258 my (@all);
1526              
1527 6929         14101 for (my $i=1; $i<=$max; $i++) {
1528 79417         103628 my @tmp = @{ $$self{'data'}{'lang'}{$ele}[$i-1] };
  79417         177354  
1529 79417         151620 $$self{'data'}{'wordlist'}{$subset}[$i-1] = $tmp[0];
1530              
1531 79417         99228 my @str;
1532 79417         112196 foreach my $str (@tmp) {
1533 210260 100       336787 next if (! $str);
1534 210259     16   502465 $$self{'data'}{'wordmatch'}{$subset}{lc($str)} = $i;
  16         113  
  16         35  
  16         256  
1535 210259         482677 push(@str,_qe_quote($str));
1536             }
1537 79417         142083 push(@all,@str);
1538              
1539 79417         175837 @str = sort _sortByLength(@str);
1540 79417         309084 $$self{'data'}{'rx'}{$subset}[$i] = join('|',@str);
1541             }
1542              
1543 6929         17929 @all = sort _sortByLength(@all);
1544 6929         49723 $$self{'data'}{'rx'}{$subset}[0] = join('|',@all);
1545              
1546             } else {
1547 0         0 $$self{'data'}{'rx'}{$subset} = undef;
1548             }
1549              
1550 6929         12474 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   36 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   64647 my($self,$N,$add,$val,$rem)=@_;
1585 41084 50       66858 return if ($N==0);
1586 41084         52189 $$val+=$add;
1587 41084 100       61077 if ($N<0) {
1588             # 1 to N
1589 2809         4046 $N = -$N;
1590 2809 100       6630 if ($$val>$N) {
    100          
1591 65         169 $$rem+= int(($$val-1)/$N);
1592 65         224 $$val = ($$val-1)%$N +1;
1593             } elsif ($$val<1) {
1594 96         291 $$rem-= int(-$$val/$N)+1;
1595 96         191 $$val = $N-(-$$val % $N);
1596             }
1597              
1598             } else {
1599             # 0 to N-1
1600 38275 100       76547 if ($$val>($N-1)) {
    100          
1601 212         394 $$rem+= int($$val/$N);
1602 212         362 $$val = $$val%$N;
1603             } elsif ($$val<0) {
1604 159         360 $$rem-= int(-($$val+1)/$N)+1;
1605 159         291 $$val = ($N-1)-(-($$val+1)%$N);
1606             }
1607             }
1608              
1609 41084         56062 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   89361 my($self,$N,$low,$high)=@_;
1618 54001 100 66     360060 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         129575 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   65515 my($self,$N,$low,$high)=@_;
1631 40812 50 66     278650 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         96879 return 1;
1637             }
1638              
1639             ###############################################################################
1640             # Split/Join functions
1641              
1642             sub split {
1643 5732     5732 1 81818 my($self,$op,$string,$arg) = @_;
1644              
1645 5732         8173 my %opts;
1646 5732 100       15632 if (ref($arg) eq 'HASH') {
    100          
1647 1         2 %opts = %{ $arg };
  1         4  
1648             } elsif ($arg) {
1649             # ***DEPRECATED 7.0***
1650 1         5 %opts = ('nonorm' => 1);
1651             }
1652              
1653             # ***DEPRECATED 7.0***
1654 5732 100       14999 if ($op eq 'delta') {
    100          
1655 81         206 $opts{'mode'} = 'standard';
1656             } elsif ($op eq 'business') {
1657 6         12 $opts{'mode'} = 'business';
1658 6         10 $op = 'delta';
1659             }
1660              
1661 5732 100       12901 if ($op eq 'date') {
    100          
    100          
    100          
    50          
1662              
1663 4232 100 100     28964 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         11518 my($y,$m,$d,$h,$mn,$s) = ($1+0,$2+0,$3+0,$4+0,$5+0,$6+0);
1667 1925         7512 return [$y,$m,$d,$h,$mn,$s];
1668             } else {
1669 2307         6782 return undef;
1670             }
1671              
1672             } elsif ($op eq 'hms') {
1673 1106 100 33     14987 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         8882 my($err,$h,$mn,$s) = $self->_hms_fields( { 'out' => 'list' },[$1,$2,$3]);
1679 1103 100       3862 return undef if ($err);
1680 1102         4000 return [$h,$mn,$s];
1681             } else {
1682 3         41 return undef;
1683             }
1684              
1685             } elsif ($op eq 'offset') {
1686 294 100 100     3191 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         2120 my($err,$h,$mn,$s) = $self->_offset_fields( { 'source' => 'string',
1692             'out' => 'list'},
1693             [$1,$2,$3]);
1694 288 100       962 return undef if ($err);
1695 287         1022 return [$h,$mn,$s];
1696             } else {
1697 6         31 return undef;
1698             }
1699              
1700             } elsif ($op eq 'time') {
1701 13 100       67 if ($string =~ /^[-+]?\d+(:[-+]?\d+){0,2}$/o) {
1702             my($err,$dh,$dmn,$ds) =
1703             $self->_time_fields( { 'nonorm' =>
1704 12 100       75 (exists($opts{'nonorm'}) ? $opts{'nonorm'} : 0),
1705             'source' => 'string',
1706             'sign' => -1,
1707             }, [split(/:/,$string)]);
1708 12 50       36 return undef if ($err);
1709 12         44 return [$dh,$dmn,$ds];
1710             } else {
1711 1         3 return undef;
1712             }
1713              
1714             } elsif ($op eq 'delta') {
1715 87         252 my($err,@delta) = $self->_split_delta($string);
1716 87 50       256 return undef if ($err);
1717              
1718             ($err,@delta) =
1719             $self->_delta_fields( { 'mode' => $opts{'mode'},
1720             'nonorm' => (exists($opts{'nonorm'}) ?
1721 87 50       710 $opts{'nonorm'} : 0),
1722             'source' => 'string',
1723             'sign' => -1,
1724             }, [@delta]);
1725              
1726 87 50       356 return undef if ($err);
1727 87         521 return [@delta];
1728             }
1729             }
1730              
1731             sub join{
1732 27692     27692 1 118699 my($self,$op,$data,$arg) = @_;
1733              
1734 27692         35971 my %opts;
1735 27692 100       59586 if (ref($arg) eq 'HASH') {
    100          
1736 1         2 %opts = %{ $arg };
  1         5  
1737             } elsif ($arg) {
1738             # ***DEPRECATED 7.0***
1739 2         6 %opts = ('nonorm' => 1);
1740             }
1741              
1742             # ***DEPRECATED 7.0***
1743 27692 100       55824 if ($op eq 'delta') {
    100          
1744 10         20 $opts{'mode'} = 'standard';
1745             } elsif ($op eq 'business') {
1746 9         19 $opts{'mode'} = 'business';
1747 9         13 $op = 'delta';
1748             }
1749              
1750 27692         52152 my @data = @$data;
1751              
1752 27692 100       49212 if ($op eq 'date') {
    100          
    100          
    100          
    50          
1753              
1754 24831         45470 my($err,$y,$m,$d,$h,$mn,$s) = $self->_date_fields(@data);
1755 24831 50       51449 return undef if ($err);
1756 24831         56142 my $form = $self->_config('printable');
1757 24831 100       55522 if ($form == 1) {
    100          
1758 1         8 return "$y$m$d$h$mn$s";
1759             } elsif ($form == 2) {
1760 1         7 return "$y-$m-$d-$h:$mn:$s";
1761             } else {
1762 24829         95070 return "$y$m$d$h:$mn:$s";
1763             }
1764              
1765             } elsif ($op eq 'offset') {
1766 108         500 my($err,$h,$mn,$s) = $self->_offset_fields( { 'source' => 'list',
1767             'out' => 'string'},
1768             [@data]);
1769 108 100       391 return undef if ($err);
1770 105         597 return "$h:$mn:$s";
1771              
1772             } elsif ($op eq 'hms') {
1773 2721         9377 my($err,$h,$mn,$s) = $self->_hms_fields( { 'out' => 'string' },[@data]);
1774 2721 100       7790 return undef if ($err);
1775 2718         9989 return "$h:$mn:$s";
1776              
1777             } elsif ($op eq 'time') {
1778             my($err,$dh,$dmn,$ds) =
1779             $self->_time_fields( { 'nonorm' =>
1780 13 100       59 (exists($opts{'nonorm'}) ? $opts{'nonorm'} : 0),
1781             'source' => 'list',
1782             'sign' => 0,
1783             }, [@data]);
1784 13 100       42 return undef if ($err);
1785 12         50 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       104 $opts{'nonorm'} : 0),
1792             'source' => 'list',
1793             'sign' => 0,
1794             }, [@data]);
1795 19 50       64 return undef if ($err);
1796 19         108 return join(':',@delta);
1797             }
1798             }
1799              
1800             sub _split_delta {
1801 1034     1034   2220 my($self,$string) = @_;
1802              
1803 1034         1614 my $sign = '[-+]?';
1804 1034         1419 my $num = '(?:\d+(?:\.\d*)?|\.\d+)';
1805 1034         2247 my $f = "(?:$sign$num)?";
1806              
1807 1034 100       11068 if ($string =~ /^$f(:$f){0,6}$/o) {
1808 436         1181 $string =~ s/::/:0:/go;
1809 436         695 $string =~ s/^:/0:/o;
1810 436         737 $string =~ s/:$/:0/o;
1811 436         1714 my(@delta) = split(/:/,$string);
1812 436         2430 return(0,@delta);
1813             } else {
1814 598         1928 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   13942 my($self,$mode,$type,$type_from,@delta) = @_;
1830              
1831 5547         7826 my $est = 0;
1832 5547         9249 foreach my $f (@delta) {
1833 38767 100       66424 if (! $self->_is_int($f)) {
1834 5         11 $est = 1;
1835 5         9 last;
1836             }
1837             }
1838              
1839 5547         7910 my $approx = 0;
1840 5547 100       10604 if (! $est) {
1841 5542 100 100     15681 $approx = 1 if ($delta[0] || $delta[1]);
1842             }
1843              
1844 5547         7677 my $semi = 0;
1845 5547 100 100     17179 if (! $est && ! $approx) {
1846 2135 100       4639 if ($mode eq 'business') {
1847 287 100       651 $semi = 1 if ($delta[2]);
1848             } else {
1849 1848 100 100     5973 $semi = 1 if ($delta[2] || $delta[3]);
1850             }
1851             }
1852              
1853 5547 100       13187 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       13 if ($type_from eq 'opt') {
1858 1         5 return ("Type must be estimated for non-integers");
1859             }
1860 4         7 $type = 'estimated';
1861 4         8 $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     11813 if ($type ne 'approx' && $type ne 'estimated') {
1869 3397 100       6402 if ($type_from eq 'opt') {
1870 5         21 return("Type must be approx/estimated");
1871             }
1872 3392         5176 $type = 'approx';
1873 3392         4904 $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     2175 if ($type ne 'semi' && $type ne 'approx' && $type ne 'estimated') {
      100        
1881 373 100       816 if ($type_from eq 'opt') {
1882 5         21 return("Type must be semi/approx/estimated");
1883             }
1884 368         616 $type = 'semi';
1885 368         592 $type_from = 'det';
1886             }
1887              
1888             } else {
1889              
1890 1744 100       3503 if (! $type) {
1891 266         431 $type = 'exact';
1892 266         404 $type_from = 'det';
1893             }
1894             }
1895              
1896 5536         20791 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   11102 my($self,$opts,$fields) = @_;
1931 5904         14024 my @fields = @$fields;
1932 168     168   1552 no integer;
  168         466  
  168         985  
1933              
1934             #
1935             # Make sure that all fields are defined, numerical, and that there
1936             # are 7 of them.
1937             #
1938              
1939 5904         10094 foreach my $f (@fields) {
1940 40805 50       65997 $f=0 if (! defined($f));
1941 40805 100       65386 return ("Non-numerical field") if (! $self->_is_num($f));
1942             }
1943 5903 100       13201 return ("Delta may contain only 7 fields") if (@fields > 7);
1944 5902         12634 while (@fields < 7) {
1945 518         1022 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         11303 my $mode = $$opts{'mode'};
1954 5902         8830 my $source = $$opts{'source'};
1955 5902         12709 @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         10087 my ($type,$type_from);
1964 5902 100 66     18657 if (defined $source && $source eq 'delta') {
1965 5333 50       11487 if (! exists $$opts{'type'}) {
1966 0         0 return ("Type must be specified");
1967             }
1968 5333         8161 $type = $$opts{'type'};
1969              
1970             } else {
1971 569         770 my $err;
1972 569         1559 ($err,$type,$type_from) = $self->_check_delta_type($mode,'','init',@fields);
1973 569         1346 $$opts{'type'} = $type;
1974 569         987 $$opts{'type_from'} = $type_from;
1975 569 50       1168 return($err) if ($err);
1976             }
1977              
1978             #
1979             # Normalize values, if desired.
1980             #
1981              
1982 5902         10149 my $norm = 1-$$opts{'nonorm'};
1983 5902 100       11246 if ($norm) {
1984 5540 100       10247 if ($mode eq 'business') {
1985              
1986 354 100 100     1383 if ($type eq 'estimated') {
    100          
1987 10         35 @fields = $self->_normalize_bus_est(@fields);
1988              
1989             } elsif ($type eq 'approx' ||
1990             $type eq 'semi') {
1991 113         329 @fields = $self->_normalize_bus_approx(@fields);
1992              
1993             } else {
1994 231         688 @fields = $self->_normalize_bus_exact(@fields);
1995             }
1996              
1997             } else {
1998              
1999 5186 100 100     15665 if ($type eq 'estimated') {
    100          
2000 11         44 @fields = $self->_normalize_est(@fields);
2001              
2002             } elsif ($type eq 'approx' ||
2003             $type eq 'semi') {
2004 3709         9093 @fields = $self->_normalize_approx(@fields);
2005              
2006             } else {
2007 1466         4281 @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         14461 @fields = $self->_sign_fields($$opts{'sign'},@fields);
2018              
2019 5902         23658 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   15786 my($self,$source,@fields) = @_;
2028              
2029             # Needed to handle fractional fields
2030 168     168   65547 no integer;
  168         528  
  168         889  
2031 5926 100       11817 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         933 my $sign = '+';
2037 562         971 foreach my $f (@fields) {
2038 3886 100       7954 if ($f =~ /^([-+])/o) {
2039 356         855 $sign = $1;
2040             } else {
2041 3530         5628 $f = "$sign$f";
2042             }
2043 3886         7282 $f *= 1;
2044             }
2045              
2046             } else {
2047 5364         8760 foreach my $f (@fields) {
2048 37500         48757 $f *= 1;
2049             }
2050             }
2051              
2052 5926         18116 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   13370 my($self,$sign,@fields) = @_;
2063 5926 50       11815 $sign = 0 if (! defined $sign);
2064              
2065 5926 50       18032 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       796 my $s = ($fields[0] < 0 ? '-' : '+');
2074 370         1024 foreach my $f (@fields[1..$#fields]) {
2075 2172 100 100     5623 if ($f > 0 && $s eq '-') {
    100          
2076 26         100 $f = "+$f";
2077 26         61 $s = '+';
2078             } elsif ($f < 0) {
2079 323 100       635 if ($s eq '-') {
2080 197         319 $f *= -1;
2081             } else {
2082 126         234 $s = '-';
2083             }
2084             }
2085             }
2086             }
2087              
2088 5926         16286 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   50 my($self,$opts,$fields) = @_;
2104 25         61 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         45 foreach my $f (@fields) {
2112 67 50       118 $f=0 if (! defined($f));
2113 67 50       109 return (1) if (! $self->_is_int($f));
2114             }
2115 25 100       61 return (1) if (@fields > 3);
2116 24         67 while (@fields < 3) {
2117 9         24 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         43 my $source = $$opts{'source'};
2126 24         53 @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         45 my $norm = 1-$$opts{'nonorm'};
2134 24 100       47 if ($norm) {
2135 20         36 my($h,$mn,$s) = @fields;
2136 20         33 $s += $h*3600 + $mn*60;
2137 20         44 @fields = __normalize_hms($h,$mn,$s);
2138             }
2139              
2140             #
2141             # Now make sure that the signs are included as appropriate.
2142             #
2143              
2144 24         55 @fields = $self->_sign_fields($$opts{'sign'},@fields);
2145              
2146 24         58 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   7616 my($self,$opts,$fields) = @_;
2165 3858         7899 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         6603 foreach my $f (@fields) {
2173 11553 100       20031 $f=0 if (! $f);
2174 11553 100       19860 return (1) if (! $self->_is_int($f,0));
2175             }
2176 3857 100       8230 return (1) if (@fields > 3);
2177 3856         8009 while (@fields < 3) {
2178 20         47 push(@fields,0);
2179             }
2180              
2181             #
2182             # Check validity.
2183             #
2184              
2185 3856         7736 my ($h,$m,$s) = @fields;
2186 3856 0 66     22404 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       8797 if ($$opts{'out'} eq 'list') {
2194 1136         2496 foreach my $f ($h,$m,$s) {
2195 3408         5115 $f *= 1;
2196             }
2197              
2198             } else {
2199 2718         4951 foreach my $f ($h,$m,$s) {
2200 8154 100       20306 $f = "0$f" if (length($f)<2);
2201             }
2202             }
2203              
2204 3854         14181 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   781 my($self,$opts,$fields) = @_;
2223 396         936 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         764 foreach my $f (@fields) {
2231 1184 100 66     3583 $f=0 if (! defined $f || $f eq '');
2232 1184 50       2088 return (1) if (! $self->_is_int($f));
2233             }
2234 396 100       1034 return (1) if (@fields > 3);
2235 395         874 while (@fields < 3) {
2236 5         11 push(@fields,0);
2237             }
2238              
2239             #
2240             # Check validity.
2241             #
2242              
2243 395         896 my ($h,$m,$s) = @fields;
2244 395 100       998 if ($$opts{'source'} eq 'string') {
2245             # Values = -23 59 59 to +23 59 59
2246 288 50 33     2758 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       383 if ($h >0) {
    100          
    100          
    50          
2253 33 50 66     328 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     527 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     27 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     89 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       826 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       907 if ($h =~ /^\-/) {
    50          
2282 196         351 $h *= 1;
2283 196         323 $m *= -1;
2284 196         309 $s *= -1;
2285             } elsif ($m =~ /^\-/) {
2286 0         0 $h *= 1;
2287 0         0 $m *= 1;
2288 0         0 $s *= -1;
2289             } else {
2290 91         152 $h *= 1;
2291 91         126 $m *= 1;
2292 91         148 $s *= 1;
2293             }
2294              
2295             } else {
2296 105         222 foreach my $f (@fields) {
2297 315         444 $f *= 1;
2298             }
2299             }
2300              
2301             #
2302             # Format them. They're already done for 'list' output.
2303             #
2304              
2305 392 100       925 if ($$opts{'out'} eq 'string') {
2306 105         176 my $sign;
2307 105 100 66     491 if ($h<0 || $m<0 || $s<0) {
      66        
2308 54         114 $h = abs($h);
2309 54         95 $m = abs($m);
2310 54         86 $s = abs($s);
2311 54         102 $sign = '-';
2312             } else {
2313 51         96 $sign = '+';
2314             }
2315              
2316 105 100       494 $h = "0$h" if (length($h) < 2);
2317 105 100       323 $m = "0$m" if (length($m) < 2);
2318 105 100       360 $s = "0$s" if (length($s) < 2);
2319 105         237 $h = "$sign$h";
2320             }
2321              
2322 392         1465 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 55039     55039   105090 my($self,@fields) = @_;
2331 55039 50       108036 return (1) if (@fields != 6);
2332              
2333 55039         101778 my($y,$m,$d,$h,$mn,$s) = @fields;
2334              
2335 55039         117296 $y = "0$y" while (length($y) < 4);
2336 55039 100       132698 $m = "0$m" if (length($m)==1);
2337 55039 100       119050 $d = "0$d" if (length($d)==1);
2338 55039 100       109602 $h = "0$h" if (length($h)==1);
2339 55039 100       109856 $mn = "0$mn" if (length($mn)==1);
2340 55039 100       108219 $s = "0$s" if (length($s)==1);
2341              
2342 55039 100       96253 if (wantarray) {
2343 24831         100017 return (0,$y,$m,$d,$h,$mn,$s);
2344             } else {
2345 30208         98815 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   274 my($self,$format,$delta)=@_;
2354 94         307 my $fields = $self->split($format,$delta);
2355 94 100       295 return undef if (! defined $fields);
2356 93         280 return $self->join($format,$fields);
2357             }
2358              
2359             ###############################################################################
2360             # Normalize the different types of deltas
2361              
2362             sub __normalize_ym {
2363 3845     3845   7262 my($y,$m,$s,$mon) = @_;
2364 168     168   244856 no integer;
  168         498  
  168         907  
2365              
2366 3845 100       7089 if (defined($s)) {
2367 21         45 $m = int($s/$mon);
2368 21         212 $s -= int(sprintf('%f',$m*$mon));
2369 21         37 $y = int($m/12);
2370 21         33 $m -= $y*12;
2371              
2372 21         62 return($y,$m,$s);
2373             } else {
2374 3824         5505 $m += $y*12;
2375 3824         8164 $y = int($m/12);
2376 3824         5838 $m -= $y*12;
2377              
2378 3824         10358 return($y,$m);
2379             }
2380             }
2381             sub __normalize_wd {
2382 3845     3845   7342 my($w,$d,$s,$wk,$day) = @_;
2383 168     168   24492 no integer;
  168         451  
  168         847  
2384              
2385 3845         6540 $d = int($s/$day);
2386 3845         5933 $s -= int($d*$day);
2387 3845         5706 $w = int($d/$wk);
2388 3845         5287 $d -= $w*$wk;
2389              
2390 3845         7990 return($w,$d,$s);
2391             }
2392             sub __normalize_hms {
2393 5568     5568   9582 my($h,$mn,$s) = @_;
2394 168     168   16975 no integer;
  168         7808  
  168         985  
2395              
2396 5568         9373 $h = int($s/3600);
2397 5568         8235 $s -= $h*3600;
2398 5568         7847 $mn = int($s/60);
2399 5568         7554 $s -= $mn*60;
2400 5568         7131 $s = int($s);
2401              
2402 5568         11547 return($h,$mn,$s);
2403             }
2404              
2405             sub _normalize_est {
2406 11     11   31 my($self,$y,$m,$w,$d,$h,$mn,$s) = @_;
2407 168     168   16157 no integer;
  168         358  
  168         821  
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         19 my $mon = 2629746;
2414 11         15 my $day = 86400;
2415 11         20 my $wk = 7;
2416 11         32 $s += ($y*12+$m)*$mon + ($w*$wk + $d)*$day +
2417             $h*3600 + $mn*60;
2418              
2419 11         28 ($y,$m,$s) = __normalize_ym($y,$m,$s,$mon);
2420 11         48 ($w,$d,$s) = __normalize_wd($w,$d,$s,$wk,$day);
2421 11         28 ($h,$mn,$s) = __normalize_hms($h,$mn,$s);
2422              
2423 11         46 return ($y,$m,$w,$d,$h,$mn,$s);
2424             }
2425             sub _normalize_bus_est {
2426 10     10   26 my($self,$y,$m,$w,$d,$h,$mn,$s) = @_;
2427 168     168   30686 no integer;
  168         429  
  168         773  
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         28 my $day = $$self{'data'}{'len'}{'bdlength'};
2434 10         19 my $wk = $$self{'data'}{'len'}{'workweek'};
2435 10         30 my $mon = 365.2425/12 * $wk/7 * $day;
2436              
2437 10         33 $s += ($y*12+$m)*$mon + ($w*$wk + $d)*$day +
2438             $h*3600 + $mn*60;
2439              
2440 10         36 ($y,$m,$s) = __normalize_ym($y,$m,$s,$mon);
2441 10         28 ($w,$d,$s) = __normalize_wd($w,$d,$s,$wk,$day);
2442 10         34 ($h,$mn,$s) = __normalize_hms($h,$mn,$s);
2443              
2444 10         33 return ($y,$m,$w,$d,$h,$mn,$s);
2445             }
2446              
2447             sub _normalize_approx {
2448 3710     3710   8554 my($self,$y,$m,$w,$d,$h,$mn,$s) = @_;
2449 168     168   32331 no integer;
  168         411  
  168         792  
2450              
2451 3710         5136 my $wk = 7;
2452 3710         4886 my $day = 86400;
2453 3710         6866 $s += ($w*$wk + $d)*$day + $h*3600 + $mn*60;
2454              
2455 3710         8130 ($y,$m) = __normalize_ym($y,$m);
2456 3710         8780 ($w,$d,$s) = __normalize_wd($w,$d,$s,$wk,$day);
2457 3710         8433 ($h,$mn,$s) = __normalize_hms($h,$mn,$s);
2458              
2459 3710         10474 return ($y,$m,$w,$d,$h,$mn,$s);
2460             }
2461             sub _normalize_bus_approx {
2462 114     114   288 my($self,$y,$m,$w,$d,$h,$mn,$s) = @_;
2463 168     168   23226 no integer;
  168         418  
  168         849  
2464              
2465 114         237 my $wk = $$self{'data'}{'len'}{'workweek'};
2466 114         198 my $day = $$self{'data'}{'len'}{'bdlength'};
2467 114         256 $s += ($w*$wk + $d)*$day + $h*3600 + $mn*60;
2468              
2469 114         287 ($y,$m) = __normalize_ym($y,$m);
2470 114         288 ($w,$d,$s) = __normalize_wd($w,$d,$s,$wk,$day);
2471 114         284 ($h,$mn,$s) = __normalize_hms($h,$mn,$s);
2472              
2473 114         387 return ($y,$m,$w,$d,$h,$mn,$s);
2474             }
2475              
2476             sub _normalize_exact {
2477 1469     1469   3614 my($self,$y,$m,$w,$d,$h,$mn,$s) = @_;
2478 168     168   30797 no integer;
  168         368  
  168         758  
2479              
2480 1469         2674 $s += $h*3600 + $mn*60;
2481              
2482 1469         3678 ($h,$mn,$s) = __normalize_hms($h,$mn,$s);
2483              
2484 1469         4234 return ($y,$m,$w,$d,$h,$mn,$s);
2485             }
2486             sub _normalize_bus_exact {
2487 234     234   583 my($self,$y,$m,$w,$d,$h,$mn,$s) = @_;
2488 168     168   17857 no integer;
  168         370  
  168         729  
2489              
2490 234         511 my $day = $$self{'data'}{'len'}{'bdlength'};
2491              
2492 234         449 $s += $d*$day + $h*3600 + $mn*60;
2493              
2494             # Calculate d
2495              
2496 234         555 $d = int($s/$day);
2497 234         357 $s -= $d*$day;
2498              
2499 234         592 ($h,$mn,$s) = __normalize_hms($h,$mn,$s);
2500              
2501 234         716 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   72630 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         109 my($y,$m,$d) = ($year+0,$mon+0,1);
2548              
2549 43 100       141 if ($flag eq 'dom') {
    100          
    50          
    0          
2550 1         4 $d = $num;
2551              
2552             } elsif ($flag eq 'last') {
2553 4         18 my $ymd = $self->nth_day_of_week($year,-1,$dow,$mon);
2554 4         9 $d = $$ymd[2];
2555              
2556             } elsif ($flag eq 'ge') {
2557 38         108 my $ymd = $self->nth_day_of_week($year,1,$dow,$mon);
2558 38         68 $d = $$ymd[2];
2559 38         93 while ($d < $num) {
2560 24         63 $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         63 my($h,$mn,$s) = @{ $self->split('hms',$time) };
  43         107  
2577 43         110 my $date = [ $y,$m,$d,$h,$mn,$s ];
2578              
2579             #
2580             # Calculate all the relevant dates.
2581             #
2582              
2583 43         68 my($endUT,$endLT,$begUT,$begLT,$offset);
2584 43         84 $stdoff = $self->split('offset',$stdoff);
2585 43         95 $dstoff = $self->split('offset',$dstoff);
2586              
2587 43 100       91 if ($timetype eq 'w') {
    100          
2588 39 100       137 $begUT = $self->calc_date_time($date,($isdst ? $stdoff : $dstoff), 1);
2589             } elsif ($timetype eq 'u') {
2590 2         4 $begUT = $date;
2591             } else {
2592 2         9 $begUT = $self->calc_date_time($date,$stdoff, 1);
2593             }
2594              
2595 43         148 $endUT = $self->calc_date_time($begUT,[0,0,-1]);
2596 43 100       138 $endLT = $self->calc_date_time($endUT,($isdst ? $stdoff : $dstoff));
2597 43 100       118 $begLT = $self->calc_date_time($begUT,($isdst ? $dstoff : $stdoff));
2598              
2599 43         190 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   8850 my($self,$string) = @_;
2607 4606         6550 my @ret;
2608              
2609 4606         6521 foreach my $enc (@{ $$self{'data'}{'calc'}{'enc_in'} }) {
  4606         13342  
2610 9565 100       22849 if (lc($enc) eq 'utf-8') {
    100          
2611 4606         16067 _utf8_on($string);
2612 4606 100       17484 push(@ret,$string) if is_utf8($string, 1);
2613             } elsif (lc($enc) eq 'perl') {
2614 4606         13451 push(@ret,encode_utf8($string));
2615             } else {
2616 353         544 my $tmp = $string;
2617 353         844 _utf8_off($tmp);
2618 353         930 $tmp = encode_utf8(decode($enc, $tmp));
2619 353         37498 _utf8_on($tmp);
2620 353 50       1144 push(@ret,$tmp) if is_utf8($tmp, 1);;
2621             }
2622             }
2623              
2624 4606         45389 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: