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-2022 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   1145 use strict;
  168         325  
  168         4924  
16 168     168   814 use warnings;
  168         332  
  168         3938  
17 168     168   791 use integer;
  168         347  
  168         776  
18 168     168   3100 use utf8;
  168         308  
  168         674  
19 168     168   4064 use Carp;
  168         366  
  168         9699  
20             #use re 'debug';
21              
22 168     168   1176 use Date::Manip::Obj;
  168         343  
  168         4501  
23 168     168   77731 use Date::Manip::TZ_Base;
  168         417  
  168         9343  
24             our @ISA = qw(Date::Manip::Obj Date::Manip::TZ_Base);
25              
26 168     168   100040 use Encode qw(encode_utf8 from_to find_encoding decode _utf8_off _utf8_on is_utf8);
  168         1778062  
  168         135251  
27             require Date::Manip::Lang::index;
28              
29             our $VERSION;
30             $VERSION='6.90';
31 168     168   987 END { undef $VERSION; }
32              
33             ###############################################################################
34             # BASE METHODS
35             ###############################################################################
36              
37             sub _init {
38 499     499   1315 my($self) = @_;
39              
40 499         2364 $self->_init_cache();
41 499         2147 $self->_init_language();
42 499         2277 $self->_init_config();
43 499         2075 $self->_init_events();
44 499         1794 $self->_init_holidays();
45 499         1709 $self->_init_now();
46              
47 499         1269 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   1199 my($self) = @_;
54 499 50       3146 return if (exists $$self{'cache'}{'init'});
55 499         1622 $$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         1650 $$self{'cache'}{'ly'} = {};
62 499         1419 $$self{'cache'}{'ds1_mon'} = {};
63 499         1369 $$self{'cache'}{'dow_mon'} = {};
64              
65 499         1093 return;
66             }
67              
68             # Config dependent data. Needs to be reset every time the config is reset.
69             sub _init_data {
70 500     500   1188 my($self,$force) = @_;
71 500 100 66     1898 return if (exists $$self{'data'}{'calc'} && ! $force);
72              
73 499         1395 $$self{'data'}{'calc'} = {}; # Calculated values
74              
75 499         1000 return;
76             }
77              
78             # Initializes config dependent data
79             sub _init_config {
80 500     500   1438 my($self,$force) = @_;
81 500 50 66     2627 return if (exists $$self{'data'}{'sections'}{'conf'} && ! $force);
82 500         2123 $self->_init_data();
83              
84             #
85             # Set config defaults
86             #
87              
88 500         10656 $$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         3217 $$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         2174 $self->_calc_workweek();
196              
197             #
198             # Initialize some config variables that do some additional work.
199             #
200              
201 500         3234 $self->_config_var('workday24hr', 1);
202 500         1927 $self->_config_var('workdaybeg', '08:00:00');
203 500         2274 $self->_config_var('workdayend', '17:00:00');
204 500         2321 $self->_config_var('workday24hr', 0);
205              
206 500         2114 $self->_config_var('dateformat', 'US');
207 500         1960 $self->_config_var('yytoyyyy', 89);
208 500         2124 $self->_config_var('jan1week1', 0);
209 500         1930 $self->_config_var('printable', 0);
210 500         2059 $self->_config_var('firstday', 1);
211 500         1952 $self->_config_var('workweekbeg', 1);
212 500         1976 $self->_config_var('workweekend', 5);
213 500         1903 $self->_config_var('language', 'english');
214 500         2441 $self->_config_var('recurrange', 'none');
215 500         1946 $self->_config_var('maxrecurattempts', 100);
216 500         1928 $self->_config_var('defaulttime', 'midnight');
217              
218             # Set OS specific defaults
219              
220 500         2066 my $os = $self->_os();
221              
222 500         1128 return;
223             }
224              
225             sub _calc_workweek {
226 1528     1528   3353 my($self,$beg,$end) = @_;
227              
228 1528 100       6074 $beg = $self->_config('workweekbeg') if (! $beg);
229 1528 100       4991 $end = $self->_config('workweekend') if (! $end);
230              
231 1528         4178 $$self{'data'}{'len'}{'workweek'} = $end - $beg + 1;
232              
233 1528         2409 return;
234             }
235              
236             sub _calc_bdlength {
237 1538     1538   2853 my($self) = @_;
238              
239 1538         2278 my @beg = @{ $$self{'data'}{'calc'}{'workdaybeg'} };
  1538         3860  
240 1538         2371 my @end = @{ $$self{'data'}{'calc'}{'workdayend'} };
  1538         3161  
241              
242 1538         4253 $$self{'data'}{'len'}{'bdlength'} =
243             ($end[0]-$beg[0])*3600 + ($end[1]-$beg[1])*60 + ($end[2]-$beg[2]);
244              
245 1538         2958 return;
246             }
247              
248             sub _init_business_length {
249 2566     2566   4529 my($self) = @_;
250              
251 168     168   1447 no integer;
  168         369  
  168         1237  
252 2566         4461 my $x = $$self{'data'}{'len'}{'workweek'};
253 2566         5822 my $y_to_d = $x/7 * 365.2425;
254 2566         4020 my $d_to_s = $$self{'data'}{'len'}{'bdlength'};
255 2566         3826 my $w_to_d = $x;
256              
257 2566         12568 $$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         5440 return;
264             }
265              
266             # Events and holidays are reset only when they are read in.
267             sub _init_events {
268 513     513   1382 my($self,$force) = @_;
269 513 50 66     2042 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         1506 $$self{'data'}{'events'} = {};
295 513         1430 $$self{'data'}{'sections'}{'events'} = [];
296 513         1376 $$self{'data'}{'eventyears'} = {};
297 513         1135 $$self{'data'}{'eventobjs'} = 0;
298              
299 513         879 return;
300             }
301              
302             sub _init_holidays {
303 517     517   1252 my($self,$force) = @_;
304 517 50 66     3645 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         1805 $$self{'data'}{'holidays'} = {};
321 517         1450 $$self{'data'}{'sections'}{'holidays'} = [];
322 517         1152 $$self{'data'}{'init_holidays'} = 0;
323              
324 517         872 return;
325             }
326              
327             sub _init_now {
328 499     499   1162 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         2259 $$self{'data'}{'now'} = {};
349 499         2357 $$self{'data'}{'now'}{'force'} = 0;
350 499         2004 $$self{'data'}{'now'}{'set'} = 0;
351 499         2080 $$self{'data'}{'tmpnow'} = [];
352              
353 499         899 return;
354             }
355              
356             # Language information only needs to be initialized if the language changes.
357             sub _init_language {
358 1032     1032   2347 my($self,$force) = @_;
359 1032 50 66     4275 return if (exists $$self{'data'}{'lang'} && ! $force);
360              
361 1032         2717 $$self{'data'}{'lang'} = {}; # Current language info
362 1032         3836 $$self{'data'}{'rx'} = {}; # Regexps generated from language
363 1032         2340 $$self{'data'}{'words'} = {}; # Types of words in the language
364 1032         2220 $$self{'data'}{'wordval'} = {}; # Value of words in the language
365              
366 1032         1849 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 27856 my($self,$arg) = @_;
378              
379 12393 100       21590 if (ref($arg)) {
380 7664         12626 my($y,$m,$d) = @$arg;
381 7664         11788 $m = ($m + 9) % 12;
382 7664         10926 $y = $y - $m/10;
383 7664         21477 return 365*$y + $y/4 - $y/100 + $y/400 + ($m*306 + 5)/10 + ($d - 1) - 305;
384             } else {
385 4729         6883 my $g = $arg + 305;
386 168     168   106332 no integer;
  168         415  
  168         893  
387 4729         10934 my $y = int((10000*$g + 14780)/3652425);
388 168     168   8072 use integer;
  168         430  
  168         787  
389 4729         8495 my $ddd = $g - (365*$y + $y/4 - $y/100 + $y/400);
390 4729 100       8952 if ($ddd < 0) {
391 6         10 $y = $y - 1;
392 6         26 $ddd = $g - (365*$y + $y/4 - $y/100 + $y/400);
393             }
394 4729         7226 my $mi = (100*$ddd + 52)/3060;
395 4729         7128 my $mm = ($mi + 2) % 12 + 1;
396 4729         6713 $y = $y + ($mi + 2)/12;
397 4729         7367 my $dd = $ddd - ($mi*306 + 5)/10 + 1;
398 4729         10012 return [$y, $mm, $dd];
399             }
400             }
401              
402             # Algorithm from the Calendar FAQ
403             #
404             sub day_of_week {
405 9932     9932 1 24691 my($self,$date) = @_;
406 9932         16955 my($y,$m,$d) = @$date;
407              
408 9932         15816 my $a = (14-$m)/12;
409 9932         14111 $y = $y-$a;
410 9932         14459 $m = $m + 12*$a - 2;
411 9932         19328 my $dow = ($d + $y + $y/4 - $y/100 + $y/400 + (31*$m)/12) % 7;
412 9932 100       19193 $dow = 7 if ($dow==0);
413 9932         19463 return $dow;
414             }
415              
416             sub leapyear {
417 3780     3780 1 9490 my($self,$y) = @_;
418 3780 100 100     17978 return 1 if ( ( ($y % 4 == 0) and ($y % 100 != 0) ) or
      100        
419             $y % 400 == 0 );
420 2416         5397 return 0;
421             }
422              
423             sub days_in_year {
424 367     367 1 3353 my($self,$y) = @_;
425 367 100       794 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 74432 my($self,$y,$m) = @_;
433 37180 100       79735 if (! $m) {
    100          
434 2 100       5 return (31,29,31,30, 31,30,31,31, 30,31,30,31) if ($self->leapyear($y));
435 1         9 return (31,28,31,30, 31,30,31,31, 30,31,30,31);
436              
437             } elsif ($m == 2) {
438 2904         7237 return 28 + $self->leapyear($y);
439              
440             } else {
441 34274         71356 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 37812 my($self,@args) = @_;
454 168     168   80050 no integer;
  168         424  
  168         768  
455 674         1223 my($n,$ly,$tmp,$remain,$day,$y,$m,$d,$h,$mn,$s,$time);
456              
457 674 100       1480 if (@args == 2) {
458             # $date = day_of_year($y,$day);
459              
460 265         482 ($y,$tmp) = @args;
461              
462 265         532 $ly = $self->leapyear($y);
463 265 100       828 $time = 1 if ($tmp =~ /\./);
464 265         428 $n = int($tmp);
465 265         414 $remain = $tmp - $n;
466              
467             # Calculate the month and the day
468 265         661 for ($m=1; $m<=12; $m++) {
469 1065 100       2331 last if ($n<=($doy_days[$ly][$m]));
470             }
471 265         480 $d = $n-($doy_days[$ly][$m-1]);
472 265 100       1141 return [$y,$m,$d] if (! $time);
473              
474             # Calculate the hours, minutes, and seconds into the day.
475              
476 9         21 $s = $remain * 86400;
477 9         18 $mn = int($s/60);
478 9         14 $s = $s - ($mn*60);
479 9 100       59 $s = sprintf('%0.2f',$s) if ("$s" ne int($s));
480 9         17 $h = int($mn/60);
481 9         18 $mn = $mn % 60;
482              
483 9         41 return [$y,$m,$d,$h,$mn,$s];
484              
485             } else {
486 409         634 ($y,$m,$d,$h,$mn,$s) = @{ $args[0] };
  409         883  
487              
488 409 100       1168 $ly = ($m > 2 ? $self->leapyear($y) : 0);
489 409         1035 $day = ($doy_days[$ly][$m-1]+$d);
490              
491 409 100       1278 return $day if (! defined $h);
492              
493 30         79 $day += ($h*3600 + $mn*60 + $s)/86400;
494 30         122 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 14284 my($self,$y,$n,$dow,$m) = @_;
504 997         1826 $y += 0;
505 997 100       1890 $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         1588 my($d,$max,$ddow);
512              
513 997 100       1762 if ($m) {
514 913         1771 $max = $self->days_in_month($y,$m);
515 913 100       1849 $d = ($n<0 ? $max : 1);
516 913         2310 $ddow = $self->day_of_week([$y,$m,$d]);
517             } else {
518 84         246 $max = $self->days_in_year($y);
519 84 50       190 $d = ($n<0 ? $max : 1);
520 84 50       173 if ($n<0) {
521 0         0 $d = $max;
522 0         0 $ddow = $self->day_of_week([$y,12,31]);
523             } else {
524 84         119 $d = 1;
525 84         233 $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       2446 if ($dow < $ddow) {
533 550         1004 $d += 7 - ($ddow-$dow);
534             } else {
535 447         806 $d += ($dow-$ddow);
536             }
537 997 100       1953 $d -= 7 if ($d > $max);
538              
539             # Find the nth occurrence of $dow
540              
541 997 100       1963 if ($n > 1) {
    100          
542 847         1352 $d += 7*($n-1);
543 847 50       1635 return undef if ($d > $max);
544             } elsif ($n < -1) {
545 2         4 $d -= 7*(-1*$n-1);
546 2 50       5 return undef if ($d < 1);
547             }
548              
549             # Return the date
550              
551 997 100       1861 if ($m) {
552 913         2366 return [$y,$m,$d];
553             }
554 84         251 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   98555 no integer;
  168         454  
  168         879  
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 11525 my($self,$arg) = @_;
567              
568 2560 100       4996 if (ref($arg)) {
569 2555         4693 ($y,$m,$d,$h,$mn,$s) = @$arg;
570 2555         6094 $sec_0 = ($self->days_since_1BC([$y,$m,$d])-1)*24*3600 + $h*3600 +
571             $mn*60 + $s;
572 2555         4571 $sec = $sec_0 - $sec_70;
573 2555         5052 return $sec;
574              
575             } else {
576 5         11 ($sec) = $arg;
577 5         13 $sec_0 = $sec_70 + $sec;
578 5         17 $tmp = int($sec_0/24/3600)+1;
579 5         15 my $ymd = $self->days_since_1BC($tmp);
580 5         12 ($y,$m,$d) = @$ymd;
581 5         13 $sec_0 -= ($tmp-1)*24*3600;
582 5         11 $h = int($sec_0/3600);
583 5         9 $sec_0 -= $h*3600;
584 5         7 $mn = int($sec_0/60);
585 5         10 $s = $sec_0 - $mn*60;
586 5         16 return [$y,$m,$d,$h,$mn,$s];
587             }
588             }
589             }
590              
591             sub check {
592 15339     15339 1 38546 my($self,$date) = @_;
593 15339         31115 my($y,$m,$d,$h,$mn,$s) = @$date;
594              
595 15339 100 66     41807 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         42752 my $days = $self->days_in_month($y,$m);
600              
601 15328 100 66     50125 return 0 if ($d<1 || $d>$days);
602 15324         39807 return 1;
603             }
604              
605             sub check_time {
606 15403     15403 1 25077 my($self,$hms) = @_;
607 15403         26333 my($h,$mn,$s) = @$hms;
608              
609 15403 100 66     179938 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         108993 return 1;
613             }
614              
615             sub week1_day1 {
616 28     28 1 3876 my($self,$year) = @_;
617 28         64 my $firstday = $self->_config('firstday');
618 28         65 return $self->_week1_day1($firstday,$year);
619             }
620              
621             sub _week1_day1 {
622 600     600   1071 my($self,$firstday,$year) = @_;
623 600         1152 my $jan1week1 = $self->_config('jan1week1');
624             return $$self{'cache'}{'week1day1'}{$firstday}{$jan1week1}{$year}
625 600 100       2279 if (exists $$self{'cache'}{'week1day1'}{$firstday}{$jan1week1}{$year});
626              
627             # First week contains either Jan 4 (default) or Jan 1
628              
629 274         518 my($y,$m,$d) = ($year,1,4);
630 274 100       531 $d = 1 if ($jan1week1);
631              
632             # Go back to the previous (counting today) $firstday
633              
634 274         758 my $dow = $self->day_of_week([$y,$m,$d]);
635 274 100       724 if ($dow != $firstday) {
636 243 100       500 $firstday = 0 if ($firstday == 7);
637 243         344 $d -= ($dow-$firstday);
638 243 100       493 if ($d<1) {
639 153         261 $y--;
640 153         206 $m = 12;
641 153         232 $d += 31;
642             }
643             }
644              
645 274         796 $$self{'cache'}{'week1day1'}{$firstday}{$jan1week1}{$year} = [ $y,$m,$d ];
646 274         789 return [$y,$m,$d];
647             }
648              
649             sub weeks_in_year {
650 77     77 1 3315 my($self,$y) = @_;
651 77         210 my $firstday = $self->_config('firstday');
652 77         225 return $self->_weeks_in_year($firstday,$y);
653             }
654              
655             sub _weeks_in_year {
656 285     285   553 my($self,$firstday,$y) = @_;
657 285         684 my $jan1week1 = $self->_config('jan1week1');
658             return $$self{'cache'}{'wiy'}{$firstday}{$jan1week1}{$y}
659 285 100       1272 if (exists $$self{'cache'}{'wiy'}{$firstday}{$jan1week1}{$y});
660              
661             # Get the week1 day1 dates for this year and the next one.
662 108         175 my ($y1,$m1,$d1) = @{ $self->_week1_day1($firstday,$y) };
  108         275  
663 108         204 my ($y2,$m2,$d2) = @{ $self->_week1_day1($firstday,$y+1) };
  108         255  
664              
665             # Calculate the number of days between them.
666 108         304 my $diy = $self->days_in_year($y);
667 108 100       288 if ($y1 < $y) {
668 58         127 $diy += (32-$d1);
669             } else {
670 50         117 $diy -= ($d1-1);
671             }
672 108 100       248 if ($y2 < $y+1) {
673 60         97 $diy -= (32-$d2);
674             } else {
675 48         100 $diy += ($d2-1);
676             }
677              
678 108         158 $diy = $diy/7;
679 108         244 $$self{'cache'}{'wiy'}{$firstday}{$jan1week1}{$y} = $diy;
680 108         270 return $diy;
681             }
682              
683             sub week_of_year {
684 525     525 1 10121 my($self,@args) = @_;
685 525         1181 my $firstday = $self->_config('firstday');
686 525         1388 return $self->_week_of_year($firstday,@args);
687             }
688              
689             sub _week_of_year {
690 533     533   1132 my($self,$firstday,@args) = @_;
691 533         1110 my $jan1week1 = $self->_config('jan1week1');
692              
693 533 100       1471 if ($#args == 1) {
694             # (y,m,d) = week_of_year(y,w)
695 325         659 my($year,$w) = @args;
696              
697             return $$self{'cache'}{'woy1'}{$firstday}{$jan1week1}{$year}{$w}
698 325 100       1794 if (exists $$self{'cache'}{'woy1'}{$firstday}{$jan1week1}{$year}{$w});
699              
700 148         382 my $ymd = $self->_week1_day1($firstday,$year);
701 148 100       624 $ymd = $self->calc_date_days($ymd,($w-1)*7) if ($w > 1);
702              
703 148         480 $$self{'cache'}{'woy1'}{$firstday}{$jan1week1}{$year}{$w} = $ymd;
704 148         539 return $ymd;
705             }
706              
707             # (y,w) = week_of_year([y,m,d])
708 208         320 my($y,$m,$d) = @{ $args[0] };
  208         446  
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         355 my($y0,$m0,$d0) = @{ $self->_week1_day1($firstday,$y) };
  208         545  
714 208 100 100     1075 if ($y0==$y && $m==1 && $d<$d0) {
      100        
715 3         7 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         687 my $n = $self->day_of_year([$y,$m,$d]);
722 205 100       516 if ($y0<$y) {
723 72         121 $n += (32-$d0);
724             } else {
725 133         244 $n -= ($d0-1);
726             }
727 205         435 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       547 if ($w>$self->_weeks_in_year($firstday,$y)) {
732 5         32 return($y+1,1);
733             }
734 200         732 return($y,$w);
735             }
736              
737             ###############################################################################
738             # CALC METHODS
739             ###############################################################################
740              
741             sub calc_date_date {
742 18     18 1 49757 my($self,$date0,$date1) = @_;
743              
744             # Order them so date0 < date1
745             # If $minus = 1, then the delta is negative
746              
747 18         28 my $minus = 0;
748 18         48 my $cmp = $self->cmp($date0,$date1);
749              
750 18 100       67 if ($cmp == 0) {
    100          
751 4         15 return [0,0,0];
752              
753             } elsif ($cmp == 1) {
754 7         10 $minus = 1;
755 7         10 my $tmp = $date1;
756 7         13 $date1 = $date0;
757 7         9 $date0 = $tmp;
758             }
759              
760 14         34 my($y0,$m0,$d0,$h0,$mn0,$s0) = @$date0;
761 14         27 my($y1,$m1,$d1,$h1,$mn1,$s1) = @$date1;
762              
763 14 100 100     55 my $sameday = ($y0 == $y1 && $m0 == $m1 && $d0 == $d1 ? 1 : 0);
764              
765             # Handle the various cases.
766              
767 14         22 my($dh,$dm,$ds);
768 14 100       22 if ($sameday) {
769 4         7 ($dh,$dm,$ds) = @{ $self->_calc_hms_hms([$h0,$mn0,$s0],[$h1,$mn1,$s1]) };
  4         17  
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         39 my $t2 = $self->_calc_hms_hms([0,0,0],[$h1,$mn1,$s1]);
777 10         18 ($dh,$dm,$ds) = @{ $self->calc_time_time($t1,$t2) };
  10         21  
778              
779 10         30 my $dd0 = $self->days_since_1BC([$y0,$m0,$d0]);
780 10         22 $dd0++;
781 10         22 my $dd1 = $self->days_since_1BC([$y1,$m1,$d1]);
782 10         23 $dh += ($dd1-$dd0)*24;
783             }
784              
785 14 100       33 if ($minus) {
786 7         21 $dh *= -1;
787 7         12 $dm *= -1;
788 7         11 $ds *= -1;
789             }
790 14         35 return [$dh,$dm,$ds];
791             }
792              
793             sub calc_date_days {
794 4721     4721 1 25027 my($self,$date,$n,$subtract) = @_;
795 4721         8787 my($y,$m,$d,$h,$mn,$s) = @$date;
796 4721 100       8968 my($ymdonly) = (defined $h ? 0 : 1);
797              
798 4721 100       8765 $n *= -1 if ($subtract);
799 4721         12771 my $d1bc = $self->days_since_1BC([$y,$m,$d]);
800 4721         8762 $d1bc += $n;
801 4721         8342 my $ymd = $self->days_since_1BC($d1bc);
802              
803 4721 100       8543 if ($ymdonly) {
804 2607         7868 return $ymd;
805             } else {
806 2114         8729 return [@$ymd,$h*1,$mn*1,$s*1];
807             }
808             }
809              
810             sub calc_date_delta {
811 8     8 1 36362 my($self,$date,$delta,$subtract) = @_;
812 8         20 my($y,$m,$d,$h,$mn,$s) = @$date;
813 8         19 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         33  
817              
818 8         37 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 86968 my($self,$date,$time,$subtract) = @_;
823 12713         22901 my($y,$m,$d,$h,$mn,$s) = @$date;
824 12713         21059 my($dh,$dmn,$ds) = @$time;
825              
826 12713 100 66     40045 if ($ds > 59 || $ds < -59) {
827 4         16 $dmn += int($ds/60);
828 4         9 $ds = $ds % 60;
829             }
830 12713 100 66     35074 if ($dmn > 59 || $dmn < -59) {
831 4         13 $dh += int($dmn/60);
832 4         17 $dmn = $dmn % 60;
833             }
834 12713         16854 my $dd = 0;
835 12713 100 100     35296 if ($dh > 23 || $dh < -23) {
836 34         70 $dd = int($dh/24);
837 34         59 $dh = $dh % 24;
838             }
839              
840             # Handle subtraction
841 12713 100       22034 if ($subtract) {
842 5592         7810 $dh *= -1;
843 5592         7280 $dmn *= -1;
844 5592         7114 $ds *= -1;
845 5592         7715 $dd *= -1;
846             }
847              
848 12713 100       21243 if ($dd == 0) {
849 12679         16209 $y *= 1;
850 12679         15486 $m *= 1;
851 12679         15958 $d *= 1;
852             } else {
853 34         57 ($y,$m,$d) = @{ $self->calc_date_days([$y,$m,$d],$dd) };
  34         145  
854             }
855              
856 12713         31853 $self->_mod_add(60,$ds,\$s,\$mn);
857 12713         30521 $self->_mod_add(60,$dmn,\$mn,\$h);
858 12713         28748 $self->_mod_add(24,$dh,\$h,\$d);
859              
860 12713 100       22993 if ($d<1) {
861 9         32 $m--;
862 9 100       39 $y--, $m=12 if ($m<1);
863 9         29 my $day_in_mon = $self->days_in_month($y,$m);
864 9         30 $d += $day_in_mon;
865             } else {
866 12704         24939 my $day_in_mon = $self->days_in_month($y,$m);
867 12704 100       24679 if ($d>$day_in_mon) {
868 68         110 $d -= $day_in_mon;
869 68         98 $m++;
870 68 100       184 $y++, $m=1 if ($m>12);
871             }
872             }
873              
874 12713         47405 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   36146 my($self,$date,$ymwd,$subtract) = @_;
890 2425         4494 my($y,$m,$d,$h,$mn,$s) = @$date;
891 2425         4392 my($dy,$dm,$dw,$dd) = @$ymwd;
892 2425 100       4437 my($ymdonly) = (defined $h ? 0 : 1);
893              
894 2425         3567 $dd += $dw*7;
895              
896 2425 100       4072 if ($subtract) {
897 1207         1829 $y -= $dy;
898 1207         3362 $self->_mod_add(-12,-1*$dm,\$m,\$y);
899 1207         2085 $dd *= -1;
900              
901             } else {
902 1218         1674 $y += $dy;
903 1218         2667 $self->_mod_add(-12,$dm,\$m,\$y);
904             }
905              
906 2425         4929 my $dim = $self->days_in_month($y,$m);
907 2425 100       4832 $d = $dim if ($d > $dim);
908              
909 2425         3181 my $ymd;
910 2425 100       4045 if ($dd == 0) {
911 2242         4194 $ymd = [$y,$m,$d];
912             } else {
913 183         485 $ymd = $self->calc_date_days([$y,$m,$d],$dd);
914             }
915              
916 2425 100       4233 if ($ymdonly) {
917 2423         5491 return $ymd;
918             } else {
919 2         11 return [@$ymd,$h,$mn,$s];
920             }
921             }
922              
923             sub _calc_hms_hms {
924 24     24   42 my($self,$hms0,$hms1) = @_;
925 24         47 my($h0,$m0,$s0,$h1,$m1,$s1) = (@$hms0,@$hms1);
926              
927 24         46 my($s) = ($h1-$h0)*3600 + ($m1-$m0)*60 + $s1-$s0;
928 24         34 my($m) = int($s/60);
929 24         29 $s -= $m*60;
930 24         32 my($h) = int($m/60);
931 24         32 $m -= $h*60;
932 24         44 return [$h,$m,$s];
933             }
934              
935             sub calc_time_time {
936 86     86 1 15695 my($self,$time0,$time1,$subtract) = @_;
937 86         213 my($h0,$m0,$s0,$h1,$m1,$s1) = (@$time0,@$time1);
938              
939 86 100       440 if ($subtract) {
940 51         93 $h1 *= -1;
941 51         68 $m1 *= -1;
942 51         123 $s1 *= -1;
943             }
944 86         189 my($s) = (($h0+$h1)*60 + ($m0+$m1))*60 + $s0+$s1;
945 86         149 my($m) = int($s/60);
946 86         122 $s -= $m*60;
947 86         127 my($h) = int($m/60);
948 86         143 $m -= $h*60;
949              
950 86         260 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 7493     7493 1 24029 my($self,$date0,$date1) = @_;
960 7493   66     43711 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   2175 my($self) = @_;
973              
974 1009         1838 my $os = '';
975              
976 1009 50 33     15645 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         2038 $os = 'Unix';
994             }
995              
996 1009         2413 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   506 my($self,$sect,$var,$val) = @_;
1013 253         409 $sect = lc($sect);
1014              
1015             #
1016             # $self->_section(SECT) creates a new section
1017             #
1018              
1019 253 0 33     489 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       472 if ($var eq '_vars') {
1030 0         0 return @{ $$self{'data'}{'sections'}{$sect} };
  0         0  
1031             }
1032              
1033 253         312 push @{ $$self{'data'}{'sections'}{$sect} },($var,$val);
  253         692  
1034 253         672 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   13532 my($self,$var,$val) = @_;
1042              
1043 7840 100 33     69120 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         4 $self->_init_config(1);
1046 1         5 return;
1047              
1048             } elsif ($var eq 'eraseholidays') {
1049 18         87 $self->_init_holidays(1);
1050 18         74 return;
1051              
1052             } elsif ($var eq 'eraseevents') {
1053 14         99 $self->_init_events(1);
1054 14         45 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         2477 my $err = $self->_language($val);
1066 533 50       4018 return if ($err);
1067 533         4006 $err = $self->_config_var_encoding();
1068 533 50       1566 return if ($err);
1069              
1070             } elsif ($var eq 'yytoyyyy') {
1071 527         1443 $val = lc($val);
1072 527 50 100     7069 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         2753 my $err = $self->_config_var_workweekbeg($val);
1082 514 50       1932 return if ($err);
1083              
1084             } elsif ($var eq 'workweekend') {
1085 514         2395 my $err = $self->_config_var_workweekend($val);
1086 514 50       1638 return if ($err);
1087              
1088             } elsif ($var eq 'workday24hr') {
1089 1014         2906 my $err = $self->_config_var_workday24hr($val);
1090 1014 50       2630 return if ($err);
1091              
1092             } elsif ($var eq 'workdaybeg') {
1093 520         2367 my $err = $self->_config_var_workdaybegend(\$val,'WorkDayBeg');
1094 520 50       2681 return if ($err);
1095              
1096             } elsif ($var eq 'workdayend') {
1097 518         1772 my $err = $self->_config_var_workdaybegend(\$val,'WorkDayEnd');
1098 518 50       2008 return if ($err);
1099              
1100             } elsif ($var eq 'firstday') {
1101 536         2228 my $err = $self->_config_var_firstday($val);
1102 536 50       1602 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         2860 my $err = $self->_config_var_recurrange($val);
1113 514 50       1441 return if ($err);
1114              
1115             } elsif ($var eq 'defaulttime') {
1116 516         2288 my $err = $self->_config_var_defaulttime($val);
1117 516 50       1526 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         20 my $err = $self->_config_var_format_mmmyyyy($val);
1125 4 50       19 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         17177 $$self{'data'}{'sections'}{'conf'}{$var} = $val;
1140 7807         15271 return;
1141             }
1142              
1143             ###############################################################################
1144             # Specific config variable functions
1145              
1146             sub _config_var_encoding {
1147 533     533   4214 my($self,$val) = @_;
1148              
1149 533 50       1466 if (! $val) {
    0          
1150 533         2178 $$self{'data'}{'calc'}{'enc_in'} = [ @{ $$self{'data'}{'enc'} } ];
  533         2925  
1151 533         2339 $$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       1682 if (! @{ $$self{'data'}{'calc'}{'enc_in'} }) {
  533         4619  
1198 516         1504 $$self{'data'}{'calc'}{'enc_in'} = [ qw(utf-8 perl) ];
1199             }
1200              
1201 533         1213 return 0;
1202             }
1203              
1204             sub _config_var_recurrange {
1205 514     514   2171 my($self,$val) = @_;
1206              
1207 514         2040 $val = lc($val);
1208 514 50       4603 if ($val =~ /^(none|year|month|week|day|all)$/o) {
1209 514         2156 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   1439 my($self,$val) = @_;
1218              
1219 514 50       1650 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       2303 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         2223 $self->_calc_workweek($val,'');
1229 514         1714 $self->_init_business_length();
1230 514         1135 return 0;
1231             }
1232              
1233             sub _config_var_workweekend {
1234 514     514   1488 my($self,$val) = @_;
1235              
1236 514 50       1548 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       2045 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         2026 $self->_calc_workweek('',$val);
1246 514         1666 $self->_init_business_length();
1247 514         1070 return 0;
1248             }
1249              
1250             sub _config_var_workday24hr {
1251 1014     1014   2209 my($self,$val) = @_;
1252              
1253 1014 100       3515 if ($val) {
1254 500         1921 $$self{'data'}{'sections'}{'conf'}{'workdaybeg'} = '00:00:00';
1255 500         1096 $$self{'data'}{'sections'}{'conf'}{'workdayend'} = '24:00:00';
1256 500         1855 $$self{'data'}{'calc'}{'workdaybeg'} = [0,0,0];
1257 500         1519 $$self{'data'}{'calc'}{'workdayend'} = [24,0,0];
1258              
1259 500         1878 $self->_calc_bdlength();
1260 500         4554 $self->_init_business_length();
1261             }
1262              
1263 1014         1993 return 0;
1264             }
1265              
1266             sub _config_var_workdaybegend {
1267 1038     1038   2368 my($self,$val,$conf) = @_;
1268              
1269             # Must be a valid time. Entered as H, H:M, or H:M:S
1270              
1271 1038         3312 my $tmp = $self->split('hms',$$val);
1272 1038 50       2654 if (! defined $tmp) {
1273 0         0 carp "ERROR: [config_var] invalid: $conf: $$val";
1274 0         0 return 1;
1275             }
1276 1038         3503 $$self{'data'}{'calc'}{lc($conf)} = $tmp;
1277 1038         3181 $$val = $self->join('hms',$tmp);
1278              
1279             # workdaybeg < workdayend
1280              
1281 1038         1839 my @beg = @{ $$self{'data'}{'calc'}{'workdaybeg'} };
  1038         2924  
1282 1038         1808 my @end = @{ $$self{'data'}{'calc'}{'workdayend'} };
  1038         2538  
1283 1038         2531 my $beg = $beg[0]*3600 + $beg[1]*60 + $beg[2];
1284 1038         2510 my $end = $end[0]*3600 + $end[1]*60 + $end[2];
1285              
1286 1038 50       2587 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         2290 $$self{'data'}{'sections'}{'conf'}{'workday24hr'} = 0;
1294              
1295 1038         3122 $self->_calc_bdlength();
1296 1038         2765 $self->_init_business_length();
1297              
1298 1038         2661 return 0;
1299             }
1300              
1301             sub _config_var_firstday {
1302 536     536   1496 my($self,$val) = @_;
1303              
1304 536 50       1710 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         1314 return 0;
1310             }
1311              
1312             sub _config_var_defaulttime {
1313 516     516   1516 my($self,$val) = @_;
1314              
1315 516 50 66     2166 if (lc($val) eq 'midnight' ||
1316             lc($val) eq 'curr') {
1317 516         1170 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   15 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         14 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   848984 no strict 'refs';
  168         477  
  168         35078  
1342             sub _language {
1343 533     533   1765 my($self,$lang) = @_;
1344 533         1356 $lang = lc($lang);
1345              
1346 533 50       2141 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     3983 $$self{'data'}{'sections'}{'conf'} eq $lang);
1353 533         2070 $self->_init_language(1);
1354              
1355 533         1504 my $mod = $Date::Manip::Lang::index::Lang{$lang};
1356 533         44739 eval "require Date::Manip::Lang::${mod}";
1357 533 50       3056 if ($@) {
1358 0         0 croak "ERROR: failed to load Date::Manip::Lang::${mod}: $@\n";
1359             }
1360              
1361 168     168   1489 no warnings 'once';
  168         454  
  168         45531  
1362 533         1054 $$self{'data'}{'lang'} = ${ "Date::Manip::Lang::${mod}::Language" };
  533         4082  
1363 533         1120 $$self{'data'}{'enc'} = [ @{ "Date::Manip::Lang::${mod}::Encodings" } ];
  533         3051  
1364              
1365             # Common words
1366 533         2549 $self->_rx_wordlist('at');
1367 533         1567 $self->_rx_wordlist('each');
1368 533         2538 $self->_rx_wordlist('last');
1369 533         3782 $self->_rx_wordlist('of');
1370 533         2125 $self->_rx_wordlist('on');
1371 533         3839 $self->_rx_wordlists('when');
1372              
1373             # Next/prev
1374 533         1740 $self->_rx_wordlists('nextprev');
1375              
1376             # Field names (years, year, yr, ...)
1377 533         1779 $self->_rx_wordlists('fields');
1378              
1379             # Numbers (first, 1st)
1380 533         2133 $self->_rx_wordlists('nth');
1381 533         2806 $self->_rx_wordlists('nth','nth_dom',31); # 1-31
1382 533         2524 $self->_rx_wordlists('nth','nth_wom',5); # 1-5
1383              
1384             # Calendar names (Mon, Tue and Jan, Feb)
1385 533         2191 $self->_rx_wordlists('day_abb');
1386 533         2279 $self->_rx_wordlists('day_char');
1387 533         2333 $self->_rx_wordlists('day_name');
1388 533         2175 $self->_rx_wordlists('month_abb');
1389 533         2308 $self->_rx_wordlists('month_name');
1390              
1391             # H:M:S separators
1392 533         3252 $self->_rx_simple('sephm');
1393 533         1521 $self->_rx_simple('sepms');
1394 533         1631 $self->_rx_simple('sepfr');
1395              
1396             # Time replacement strings
1397 533         2138 $self->_rx_replace('times');
1398              
1399             # Some offset strings
1400 533         1926 $self->_rx_replace('offset_date');
1401 533         3137 $self->_rx_replace('offset_time');
1402              
1403             # AM/PM strings
1404 533         3858 $self->_rx_wordlists('ampm');
1405              
1406             # Business/non-business mode
1407 533         2271 $self->_rx_wordlists('mode');
1408              
1409 533         1853 return 0;
1410             }
1411 168     168   1407 use strict 'refs';
  168         455  
  168         78199  
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   3126 my($self,$ele) = @_;
1421              
1422 1599 100       3661 if (exists $$self{'data'}{'lang'}{$ele}) {
1423 19 100       69 if (ref($$self{'data'}{'lang'}{$ele})) {
1424 16         36 @{ $$self{'data'}{'rx'}{$ele} } = @{ $$self{'data'}{'lang'}{$ele} };
  16         64  
  16         42  
1425             } else {
1426 3         15 $$self{'data'}{'rx'}{$ele} = $$self{'data'}{'lang'}{$ele};
1427             }
1428             } else {
1429 1580         3290 $$self{'data'}{'rx'}{$ele} = undef;
1430             }
1431              
1432 1599         2403 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   324468 my($string) = @_;
1440 223103         492689 $string =~ s/([-.+*?])/\\$1/g;
1441 223103         471499 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   4989 my($self,$ele) = @_;
1455              
1456 2665 50       5985 if (exists $$self{'data'}{'lang'}{$ele}) {
1457 2665         4403 my @tmp = @{ $$self{'data'}{'lang'}{$ele} };
  2665         6547  
1458              
1459 2665         5531 $$self{'data'}{'wordlist'}{$ele} = $tmp[0];
1460              
1461 2665         4517 my @tmp2;
1462 2665         4323 foreach my $tmp (@tmp) {
1463 4271 100       11195 push(@tmp2,_qe_quote($tmp)) if ($tmp);
1464             }
1465 2665         9303 @tmp2 = sort _sortByLength(@tmp2);
1466              
1467 2665         10204 $$self{'data'}{'rx'}{$ele} = join('|',@tmp2);
1468              
1469             } else {
1470 0         0 $$self{'data'}{'rx'}{$ele} = undef;
1471             }
1472              
1473 2665         5811 return;
1474             }
1475              
1476 168     168   1430 no strict 'vars';
  168         488  
  168         12461  
1477             sub _sortByLength {
1478 1135983     1135983   1424525 return (length $b <=> length $a);
1479             }
1480 168     168   1294 use strict 'vars';
  168         412  
  168         493919  
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   3597 my($self,$ele) = @_;
1489              
1490 1599 50       4846 if (! exists $$self{'data'}{'lang'}{$ele}) {
1491 0         0 $$self{'data'}{'rx'}{$ele} = [];
1492 0         0 return;
1493             }
1494              
1495 1599         2493 my(@key) = keys %{ $$self{'data'}{'lang'}{$ele} };
  1599         6837  
1496 1599         2908 my $i = 1;
1497 1599         4971 foreach my $key (sort(@key)) {
1498 4288         19680 my $val = $$self{'data'}{'lang'}{$ele}{$key};
1499 4288         7307 my $k = _qe_quote($key);
1500 4288         76747 $$self{'data'}{'rx'}{$ele}[$i++] = qr/(?:^|\b)($k)(?:\b|$)/i;
1501 4288         324118 $$self{'data'}{'wordmatch'}{$ele}{lc($key)} = $val;
1502             }
1503              
1504 1599         5945 @key = sort _sortByLength(@key);
1505 1599         5194 @key = map { _qe_quote($_) } @key;
  4288         8315  
1506 1599         6245 my $rx = join('|',@key);
1507              
1508 1599         56947 $$self{'data'}{'rx'}{$ele}[0] = qr/(?:^|\b)(?:$rx)(?:\b|$)/i;
1509              
1510 1599         6937 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   14135 my($self,$ele,$subset,$max) = @_;
1520 6929 100       15544 $subset = $ele if (! $subset);
1521              
1522 6929 50       17672 if (exists $$self{'data'}{'lang'}{$ele}) {
1523 6929         10736 my @vallist = @{ $$self{'data'}{'lang'}{$ele} };
  6929         19229  
1524 6929 100 66     20210 $max = $#vallist+1 if (! $max || $max > $#vallist+1);
1525 6929         9198 my (@all);
1526              
1527 6929         14615 for (my $i=1; $i<=$max; $i++) {
1528 79417         103974 my @tmp = @{ $$self{'data'}{'lang'}{$ele}[$i-1] };
  79417         180207  
1529 79417         155081 $$self{'data'}{'wordlist'}{$subset}[$i-1] = $tmp[0];
1530              
1531 79417         98990 my @str;
1532 79417         113496 foreach my $str (@tmp) {
1533 210260 100       342824 next if (! $str);
1534 210259     16   517779 $$self{'data'}{'wordmatch'}{$subset}{lc($str)} = $i;
  16         114  
  16         41  
  16         274  
1535 210259         489625 push(@str,_qe_quote($str));
1536             }
1537 79417         143717 push(@all,@str);
1538              
1539 79417         178893 @str = sort _sortByLength(@str);
1540 79417         315308 $$self{'data'}{'rx'}{$subset}[$i] = join('|',@str);
1541             }
1542              
1543 6929         17996 @all = sort _sortByLength(@all);
1544 6929         50777 $$self{'data'}{'rx'}{$subset}[0] = join('|',@all);
1545              
1546             } else {
1547 0         0 $$self{'data'}{'rx'}{$subset} = undef;
1548             }
1549              
1550 6929         13318 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   35 my($self,$method) = @_;
1564 4         26 $self->_config('yytoyyyy',$method);
1565              
1566 4         10 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   65921 my($self,$N,$add,$val,$rem)=@_;
1585 41084 50       68073 return if ($N==0);
1586 41084         52043 $$val+=$add;
1587 41084 100       62539 if ($N<0) {
1588             # 1 to N
1589 2809         4095 $N = -$N;
1590 2809 100       6608 if ($$val>$N) {
    100          
1591 65         147 $$rem+= int(($$val-1)/$N);
1592 65         167 $$val = ($$val-1)%$N +1;
1593             } elsif ($$val<1) {
1594 96         255 $$rem-= int(-$$val/$N)+1;
1595 96         220 $$val = $N-(-$$val % $N);
1596             }
1597              
1598             } else {
1599             # 0 to N-1
1600 38275 100       78075 if ($$val>($N-1)) {
    100          
1601 212         421 $$rem+= int($$val/$N);
1602 212         378 $$val = $$val%$N;
1603             } elsif ($$val<0) {
1604 159         355 $$rem-= int(-($$val+1)/$N)+1;
1605 159         299 $$val = ($N-1)-(-($$val+1)%$N);
1606             }
1607             }
1608              
1609 41084         55849 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   91693 my($self,$N,$low,$high)=@_;
1618 54001 100 66     364777 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         130404 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   64864 my($self,$N,$low,$high)=@_;
1631 40812 50 66     281738 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         97439 return 1;
1637             }
1638              
1639             ###############################################################################
1640             # Split/Join functions
1641              
1642             sub split {
1643 5732     5732 1 82921 my($self,$op,$string,$arg) = @_;
1644              
1645 5732         8041 my %opts;
1646 5732 100       15731 if (ref($arg) eq 'HASH') {
    100          
1647 1         8 %opts = %{ $arg };
  1         6  
1648             } elsif ($arg) {
1649             # ***DEPRECATED 7.0***
1650 1         6 %opts = ('nonorm' => 1);
1651             }
1652              
1653             # ***DEPRECATED 7.0***
1654 5732 100       15532 if ($op eq 'delta') {
    100          
1655 81         205 $opts{'mode'} = 'standard';
1656             } elsif ($op eq 'business') {
1657 6         12 $opts{'mode'} = 'business';
1658 6         11 $op = 'delta';
1659             }
1660              
1661 5732 100       12672 if ($op eq 'date') {
    100          
    100          
    100          
    50          
1662              
1663 4232 100 100     29050 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         11692 my($y,$m,$d,$h,$mn,$s) = ($1+0,$2+0,$3+0,$4+0,$5+0,$6+0);
1667 1925         7423 return [$y,$m,$d,$h,$mn,$s];
1668             } else {
1669 2307         6791 return undef;
1670             }
1671              
1672             } elsif ($op eq 'hms') {
1673 1106 100 33     15951 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         8760 my($err,$h,$mn,$s) = $self->_hms_fields( { 'out' => 'list' },[$1,$2,$3]);
1679 1103 100       3953 return undef if ($err);
1680 1102         4049 return [$h,$mn,$s];
1681             } else {
1682 3         15 return undef;
1683             }
1684              
1685             } elsif ($op eq 'offset') {
1686 294 100 100     3223 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         2075 my($err,$h,$mn,$s) = $self->_offset_fields( { 'source' => 'string',
1692             'out' => 'list'},
1693             [$1,$2,$3]);
1694 288 100       937 return undef if ($err);
1695 287         1101 return [$h,$mn,$s];
1696             } else {
1697 6         26 return undef;
1698             }
1699              
1700             } elsif ($op eq 'time') {
1701 13 100       90 if ($string =~ /^[-+]?\d+(:[-+]?\d+){0,2}$/o) {
1702             my($err,$dh,$dmn,$ds) =
1703             $self->_time_fields( { 'nonorm' =>
1704 12 100       83 (exists($opts{'nonorm'}) ? $opts{'nonorm'} : 0),
1705             'source' => 'string',
1706             'sign' => -1,
1707             }, [split(/:/,$string)]);
1708 12 50       39 return undef if ($err);
1709 12         39 return [$dh,$dmn,$ds];
1710             } else {
1711 1         4 return undef;
1712             }
1713              
1714             } elsif ($op eq 'delta') {
1715 87         241 my($err,@delta) = $self->_split_delta($string);
1716 87 50       245 return undef if ($err);
1717              
1718             ($err,@delta) =
1719             $self->_delta_fields( { 'mode' => $opts{'mode'},
1720             'nonorm' => (exists($opts{'nonorm'}) ?
1721 87 50       687 $opts{'nonorm'} : 0),
1722             'source' => 'string',
1723             'sign' => -1,
1724             }, [@delta]);
1725              
1726 87 50       362 return undef if ($err);
1727 87         521 return [@delta];
1728             }
1729             }
1730              
1731             sub join{
1732 27684     27684 1 119986 my($self,$op,$data,$arg) = @_;
1733              
1734 27684         35043 my %opts;
1735 27684 100       59777 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 27684 100       56983 if ($op eq 'delta') {
    100          
1744 10         21 $opts{'mode'} = 'standard';
1745             } elsif ($op eq 'business') {
1746 9         16 $opts{'mode'} = 'business';
1747 9         14 $op = 'delta';
1748             }
1749              
1750 27684         52195 my @data = @$data;
1751              
1752 27684 100       49110 if ($op eq 'date') {
    100          
    100          
    100          
    50          
1753              
1754 24823         46135 my($err,$y,$m,$d,$h,$mn,$s) = $self->_date_fields(@data);
1755 24823 50       51462 return undef if ($err);
1756 24823         55795 my $form = $self->_config('printable');
1757 24823 100       55058 if ($form == 1) {
    100          
1758 1         14 return "$y$m$d$h$mn$s";
1759             } elsif ($form == 2) {
1760 1         8 return "$y-$m-$d-$h:$mn:$s";
1761             } else {
1762 24821         95113 return "$y$m$d$h:$mn:$s";
1763             }
1764              
1765             } elsif ($op eq 'offset') {
1766 108         600 my($err,$h,$mn,$s) = $self->_offset_fields( { 'source' => 'list',
1767             'out' => 'string'},
1768             [@data]);
1769 108 100       404 return undef if ($err);
1770 105         626 return "$h:$mn:$s";
1771              
1772             } elsif ($op eq 'hms') {
1773 2721         9413 my($err,$h,$mn,$s) = $self->_hms_fields( { 'out' => 'string' },[@data]);
1774 2721 100       7727 return undef if ($err);
1775 2718         10199 return "$h:$mn:$s";
1776              
1777             } elsif ($op eq 'time') {
1778             my($err,$dh,$dmn,$ds) =
1779             $self->_time_fields( { 'nonorm' =>
1780 13 100       63 (exists($opts{'nonorm'}) ? $opts{'nonorm'} : 0),
1781             'source' => 'list',
1782             'sign' => 0,
1783             }, [@data]);
1784 13 100       43 return undef if ($err);
1785 12         51 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       109 $opts{'nonorm'} : 0),
1792             'source' => 'list',
1793             'sign' => 0,
1794             }, [@data]);
1795 19 50       73 return undef if ($err);
1796 19         115 return join(':',@delta);
1797             }
1798             }
1799              
1800             sub _split_delta {
1801 1034     1034   2152 my($self,$string) = @_;
1802              
1803 1034         1691 my $sign = '[-+]?';
1804 1034         1432 my $num = '(?:\d+(?:\.\d*)?|\.\d+)';
1805 1034         2319 my $f = "(?:$sign$num)?";
1806              
1807 1034 100       11798 if ($string =~ /^$f(:$f){0,6}$/o) {
1808 436         1183 $string =~ s/::/:0:/go;
1809 436         722 $string =~ s/^:/0:/o;
1810 436         731 $string =~ s/:$/:0/o;
1811 436         1783 my(@delta) = split(/:/,$string);
1812 436         2475 return(0,@delta);
1813             } else {
1814 598         1934 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   13991 my($self,$mode,$type,$type_from,@delta) = @_;
1830              
1831 5547         7705 my $est = 0;
1832 5547         8955 foreach my $f (@delta) {
1833 38767 100       67238 if (! $self->_is_int($f)) {
1834 5         11 $est = 1;
1835 5         9 last;
1836             }
1837             }
1838              
1839 5547         8453 my $approx = 0;
1840 5547 100       10578 if (! $est) {
1841 5542 100 100     15896 $approx = 1 if ($delta[0] || $delta[1]);
1842             }
1843              
1844 5547         7589 my $semi = 0;
1845 5547 100 100     16605 if (! $est && ! $approx) {
1846 2135 100       4685 if ($mode eq 'business') {
1847 287 100       625 $semi = 1 if ($delta[2]);
1848             } else {
1849 1848 100 100     6055 $semi = 1 if ($delta[2] || $delta[3]);
1850             }
1851             }
1852              
1853 5547 100       13226 if ($est) {
    100          
    100          
1854             # If some of the fields are non-integer, then type must be estimated.
1855              
1856 5 50       14 if ($type ne 'estimated') {
1857 5 100       14 if ($type_from eq 'opt') {
1858 1         4 return ("Type must be estimated for non-integers");
1859             }
1860 4         8 $type = 'estimated';
1861 4         9 $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     11447 if ($type ne 'approx' && $type ne 'estimated') {
1869 3397 100       6628 if ($type_from eq 'opt') {
1870 5         21 return("Type must be approx/estimated");
1871             }
1872 3392         5096 $type = 'approx';
1873 3392         4882 $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     2297 if ($type ne 'semi' && $type ne 'approx' && $type ne 'estimated') {
      100        
1881 373 100       839 if ($type_from eq 'opt') {
1882 5         23 return("Type must be semi/approx/estimated");
1883             }
1884 368         613 $type = 'semi';
1885 368         540 $type_from = 'det';
1886             }
1887              
1888             } else {
1889              
1890 1744 100       3461 if (! $type) {
1891 266         458 $type = 'exact';
1892 266         450 $type_from = 'det';
1893             }
1894             }
1895              
1896 5536         20621 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   11012 my($self,$opts,$fields) = @_;
1931 5904         14728 my @fields = @$fields;
1932 168     168   1585 no integer;
  168         485  
  168         986  
1933              
1934             #
1935             # Make sure that all fields are defined, numerical, and that there
1936             # are 7 of them.
1937             #
1938              
1939 5904         10131 foreach my $f (@fields) {
1940 40805 50       67004 $f=0 if (! defined($f));
1941 40805 100       65096 return ("Non-numerical field") if (! $self->_is_num($f));
1942             }
1943 5903 100       13040 return ("Delta may contain only 7 fields") if (@fields > 7);
1944 5902         13119 while (@fields < 7) {
1945 518         1112 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         10925 my $mode = $$opts{'mode'};
1954 5902         8962 my $source = $$opts{'source'};
1955 5902         13210 @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         10042 my ($type,$type_from);
1964 5902 100 66     19512 if (defined $source && $source eq 'delta') {
1965 5333 50       11217 if (! exists $$opts{'type'}) {
1966 0         0 return ("Type must be specified");
1967             }
1968 5333         8821 $type = $$opts{'type'};
1969              
1970             } else {
1971 569         809 my $err;
1972 569         1607 ($err,$type,$type_from) = $self->_check_delta_type($mode,'','init',@fields);
1973 569         1349 $$opts{'type'} = $type;
1974 569         981 $$opts{'type_from'} = $type_from;
1975 569 50       1253 return($err) if ($err);
1976             }
1977              
1978             #
1979             # Normalize values, if desired.
1980             #
1981              
1982 5902         10140 my $norm = 1-$$opts{'nonorm'};
1983 5902 100       11683 if ($norm) {
1984 5540 100       10165 if ($mode eq 'business') {
1985              
1986 354 100 100     1334 if ($type eq 'estimated') {
    100          
1987 10         36 @fields = $self->_normalize_bus_est(@fields);
1988              
1989             } elsif ($type eq 'approx' ||
1990             $type eq 'semi') {
1991 113         310 @fields = $self->_normalize_bus_approx(@fields);
1992              
1993             } else {
1994 231         685 @fields = $self->_normalize_bus_exact(@fields);
1995             }
1996              
1997             } else {
1998              
1999 5186 100 100     16030 if ($type eq 'estimated') {
    100          
2000 11         50 @fields = $self->_normalize_est(@fields);
2001              
2002             } elsif ($type eq 'approx' ||
2003             $type eq 'semi') {
2004 3709         8998 @fields = $self->_normalize_approx(@fields);
2005              
2006             } else {
2007 1466         4449 @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         14314 @fields = $self->_sign_fields($$opts{'sign'},@fields);
2018              
2019 5902         22871 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   15835 my($self,$source,@fields) = @_;
2028              
2029             # Needed to handle fractional fields
2030 168     168   68923 no integer;
  168         538  
  168         972  
2031 5926 100       11850 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         877 my $sign = '+';
2037 562         987 foreach my $f (@fields) {
2038 3886 100       7882 if ($f =~ /^([-+])/o) {
2039 356         903 $sign = $1;
2040             } else {
2041 3530         5686 $f = "$sign$f";
2042             }
2043 3886         7389 $f *= 1;
2044             }
2045              
2046             } else {
2047 5364         8655 foreach my $f (@fields) {
2048 37500         48486 $f *= 1;
2049             }
2050             }
2051              
2052 5926         18937 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   13442 my($self,$sign,@fields) = @_;
2063 5926 50       12186 $sign = 0 if (! defined $sign);
2064              
2065 5926 50       16389 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       826 my $s = ($fields[0] < 0 ? '-' : '+');
2074 370         1042 foreach my $f (@fields[1..$#fields]) {
2075 2172 100 100     5694 if ($f > 0 && $s eq '-') {
    100          
2076 26         65 $f = "+$f";
2077 26         54 $s = '+';
2078             } elsif ($f < 0) {
2079 323 100       567 if ($s eq '-') {
2080 197         328 $f *= -1;
2081             } else {
2082 126         227 $s = '-';
2083             }
2084             }
2085             }
2086             }
2087              
2088 5926         17144 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   52 my($self,$opts,$fields) = @_;
2104 25         51 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         55 foreach my $f (@fields) {
2112 67 50       122 $f=0 if (! defined($f));
2113 67 50       107 return (1) if (! $self->_is_int($f));
2114             }
2115 25 100       53 return (1) if (@fields > 3);
2116 24         50 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         48 my $source = $$opts{'source'};
2126 24         55 @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         49 my $norm = 1-$$opts{'nonorm'};
2134 24 100       46 if ($norm) {
2135 20         40 my($h,$mn,$s) = @fields;
2136 20         39 $s += $h*3600 + $mn*60;
2137 20         42 @fields = __normalize_hms($h,$mn,$s);
2138             }
2139              
2140             #
2141             # Now make sure that the signs are included as appropriate.
2142             #
2143              
2144 24         56 @fields = $self->_sign_fields($$opts{'sign'},@fields);
2145              
2146 24         61 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   7374 my($self,$opts,$fields) = @_;
2165 3858         7886 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         6706 foreach my $f (@fields) {
2173 11553 100       20044 $f=0 if (! $f);
2174 11553 100       20060 return (1) if (! $self->_is_int($f,0));
2175             }
2176 3857 100       8470 return (1) if (@fields > 3);
2177 3856         8342 while (@fields < 3) {
2178 20         47 push(@fields,0);
2179             }
2180              
2181             #
2182             # Check validity.
2183             #
2184              
2185 3856         7750 my ($h,$m,$s) = @fields;
2186 3856 0 66     22424 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       8738 if ($$opts{'out'} eq 'list') {
2194 1136         2514 foreach my $f ($h,$m,$s) {
2195 3408         5140 $f *= 1;
2196             }
2197              
2198             } else {
2199 2718         5023 foreach my $f ($h,$m,$s) {
2200 8154 100       20468 $f = "0$f" if (length($f)<2);
2201             }
2202             }
2203              
2204 3854         14324 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   825 my($self,$opts,$fields) = @_;
2223 396         963 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         773 foreach my $f (@fields) {
2231 1184 100 66     3610 $f=0 if (! defined $f || $f eq '');
2232 1184 50       2125 return (1) if (! $self->_is_int($f));
2233             }
2234 396 100       988 return (1) if (@fields > 3);
2235 395         924 while (@fields < 3) {
2236 5         14 push(@fields,0);
2237             }
2238              
2239             #
2240             # Check validity.
2241             #
2242              
2243 395         912 my ($h,$m,$s) = @fields;
2244 395 100       1052 if ($$opts{'source'} eq 'string') {
2245             # Values = -23 59 59 to +23 59 59
2246 288 50 33     2791 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       433 if ($h >0) {
    100          
    100          
    50          
2253 33 50 66     315 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     540 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     13 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     78 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       852 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       937 if ($h =~ /^\-/) {
    50          
2282 196         348 $h *= 1;
2283 196         312 $m *= -1;
2284 196         306 $s *= -1;
2285             } elsif ($m =~ /^\-/) {
2286 0         0 $h *= 1;
2287 0         0 $m *= 1;
2288 0         0 $s *= -1;
2289             } else {
2290 91         149 $h *= 1;
2291 91         130 $m *= 1;
2292 91         142 $s *= 1;
2293             }
2294              
2295             } else {
2296 105         218 foreach my $f (@fields) {
2297 315         460 $f *= 1;
2298             }
2299             }
2300              
2301             #
2302             # Format them. They're already done for 'list' output.
2303             #
2304              
2305 392 100       965 if ($$opts{'out'} eq 'string') {
2306 105         174 my $sign;
2307 105 100 66     550 if ($h<0 || $m<0 || $s<0) {
      66        
2308 54         132 $h = abs($h);
2309 54         87 $m = abs($m);
2310 54         87 $s = abs($s);
2311 54         103 $sign = '-';
2312             } else {
2313 51         109 $sign = '+';
2314             }
2315              
2316 105 100       363 $h = "0$h" if (length($h) < 2);
2317 105 100       330 $m = "0$m" if (length($m) < 2);
2318 105 100       336 $s = "0$s" if (length($s) < 2);
2319 105         224 $h = "$sign$h";
2320             }
2321              
2322 392         1591 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 55031     55031   106737 my($self,@fields) = @_;
2331 55031 50       108462 return (1) if (@fields != 6);
2332              
2333 55031         103555 my($y,$m,$d,$h,$mn,$s) = @fields;
2334              
2335 55031         116868 $y = "0$y" while (length($y) < 4);
2336 55031 100       131812 $m = "0$m" if (length($m)==1);
2337 55031 100       119109 $d = "0$d" if (length($d)==1);
2338 55031 100       111580 $h = "0$h" if (length($h)==1);
2339 55031 100       107636 $mn = "0$mn" if (length($mn)==1);
2340 55031 100       108290 $s = "0$s" if (length($s)==1);
2341              
2342 55031 100       96008 if (wantarray) {
2343 24823         99453 return (0,$y,$m,$d,$h,$mn,$s);
2344             } else {
2345 30208         98731 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   288 my($self,$format,$delta)=@_;
2354 94         308 my $fields = $self->split($format,$delta);
2355 94 100       334 return undef if (! defined $fields);
2356 93         324 return $self->join($format,$fields);
2357             }
2358              
2359             ###############################################################################
2360             # Normalize the different types of deltas
2361              
2362             sub __normalize_ym {
2363 3845     3845   7333 my($y,$m,$s,$mon) = @_;
2364 168     168   254637 no integer;
  168         565  
  168         923  
2365              
2366 3845 100       6912 if (defined($s)) {
2367 21         40 $m = int($s/$mon);
2368 21         228 $s -= int(sprintf('%f',$m*$mon));
2369 21         43 $y = int($m/12);
2370 21         33 $m -= $y*12;
2371              
2372 21         63 return($y,$m,$s);
2373             } else {
2374 3824         5500 $m += $y*12;
2375 3824         8492 $y = int($m/12);
2376 3824         5675 $m -= $y*12;
2377              
2378 3824         10406 return($y,$m);
2379             }
2380             }
2381             sub __normalize_wd {
2382 3845     3845   7402 my($w,$d,$s,$wk,$day) = @_;
2383 168     168   26175 no integer;
  168         447  
  168         787  
2384              
2385 3845         6573 $d = int($s/$day);
2386 3845         5990 $s -= int($d*$day);
2387 3845         5651 $w = int($d/$wk);
2388 3845         5506 $d -= $w*$wk;
2389              
2390 3845         8202 return($w,$d,$s);
2391             }
2392             sub __normalize_hms {
2393 5568     5568   9507 my($h,$mn,$s) = @_;
2394 168     168   17358 no integer;
  168         7896  
  168         995  
2395              
2396 5568         9555 $h = int($s/3600);
2397 5568         8207 $s -= $h*3600;
2398 5568         8282 $mn = int($s/60);
2399 5568         7693 $s -= $mn*60;
2400 5568         7178 $s = int($s);
2401              
2402 5568         11524 return($h,$mn,$s);
2403             }
2404              
2405             sub _normalize_est {
2406 11     11   30 my($self,$y,$m,$w,$d,$h,$mn,$s) = @_;
2407 168     168   17005 no integer;
  168         427  
  168         746  
2408              
2409             # Figure out how many seconds there are in the estimated delta
2410             #
2411             # 365.2425/12 days/month * 24 hours/day * 3600 sec/hour = 2629746 sec/month
2412              
2413 11         15 my $mon = 2629746;
2414 11         17 my $day = 86400;
2415 11         18 my $wk = 7;
2416 11         32 $s += ($y*12+$m)*$mon + ($w*$wk + $d)*$day +
2417             $h*3600 + $mn*60;
2418              
2419 11         29 ($y,$m,$s) = __normalize_ym($y,$m,$s,$mon);
2420 11         29 ($w,$d,$s) = __normalize_wd($w,$d,$s,$wk,$day);
2421 11         28 ($h,$mn,$s) = __normalize_hms($h,$mn,$s);
2422              
2423 11         43 return ($y,$m,$w,$d,$h,$mn,$s);
2424             }
2425             sub _normalize_bus_est {
2426 10     10   32 my($self,$y,$m,$w,$d,$h,$mn,$s) = @_;
2427 168     168   32234 no integer;
  168         524  
  168         830  
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         25 my $day = $$self{'data'}{'len'}{'bdlength'};
2434 10         17 my $wk = $$self{'data'}{'len'}{'workweek'};
2435 10         31 my $mon = 365.2425/12 * $wk/7 * $day;
2436              
2437 10         31 $s += ($y*12+$m)*$mon + ($w*$wk + $d)*$day +
2438             $h*3600 + $mn*60;
2439              
2440 10         25 ($y,$m,$s) = __normalize_ym($y,$m,$s,$mon);
2441 10         26 ($w,$d,$s) = __normalize_wd($w,$d,$s,$wk,$day);
2442 10         28 ($h,$mn,$s) = __normalize_hms($h,$mn,$s);
2443              
2444 10         36 return ($y,$m,$w,$d,$h,$mn,$s);
2445             }
2446              
2447             sub _normalize_approx {
2448 3710     3710   8448 my($self,$y,$m,$w,$d,$h,$mn,$s) = @_;
2449 168     168   34235 no integer;
  168         439  
  168         786  
2450              
2451 3710         5057 my $wk = 7;
2452 3710         4906 my $day = 86400;
2453 3710         7029 $s += ($w*$wk + $d)*$day + $h*3600 + $mn*60;
2454              
2455 3710         8473 ($y,$m) = __normalize_ym($y,$m);
2456 3710         8951 ($w,$d,$s) = __normalize_wd($w,$d,$s,$wk,$day);
2457 3710         8403 ($h,$mn,$s) = __normalize_hms($h,$mn,$s);
2458              
2459 3710         10682 return ($y,$m,$w,$d,$h,$mn,$s);
2460             }
2461             sub _normalize_bus_approx {
2462 114     114   279 my($self,$y,$m,$w,$d,$h,$mn,$s) = @_;
2463 168     168   24592 no integer;
  168         418  
  168         819  
2464              
2465 114         241 my $wk = $$self{'data'}{'len'}{'workweek'};
2466 114         183 my $day = $$self{'data'}{'len'}{'bdlength'};
2467 114         245 $s += ($w*$wk + $d)*$day + $h*3600 + $mn*60;
2468              
2469 114         260 ($y,$m) = __normalize_ym($y,$m);
2470 114         295 ($w,$d,$s) = __normalize_wd($w,$d,$s,$wk,$day);
2471 114         280 ($h,$mn,$s) = __normalize_hms($h,$mn,$s);
2472              
2473 114         351 return ($y,$m,$w,$d,$h,$mn,$s);
2474             }
2475              
2476             sub _normalize_exact {
2477 1469     1469   3605 my($self,$y,$m,$w,$d,$h,$mn,$s) = @_;
2478 168     168   32372 no integer;
  168         397  
  168         868  
2479              
2480 1469         2746 $s += $h*3600 + $mn*60;
2481              
2482 1469         3679 ($h,$mn,$s) = __normalize_hms($h,$mn,$s);
2483              
2484 1469         4318 return ($y,$m,$w,$d,$h,$mn,$s);
2485             }
2486             sub _normalize_bus_exact {
2487 234     234   561 my($self,$y,$m,$w,$d,$h,$mn,$s) = @_;
2488 168     168   18577 no integer;
  168         394  
  168         823  
2489              
2490 234         507 my $day = $$self{'data'}{'len'}{'bdlength'};
2491              
2492 234         462 $s += $d*$day + $h*3600 + $mn*60;
2493              
2494             # Calculate d
2495              
2496 234         550 $d = int($s/$day);
2497 234         411 $s -= $d*$day;
2498              
2499 234         601 ($h,$mn,$s) = __normalize_hms($h,$mn,$s);
2500              
2501 234         705 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   75523 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         115 my($y,$m,$d) = ($year+0,$mon+0,1);
2548              
2549 43 100       162 if ($flag eq 'dom') {
    100          
    50          
    0          
2550 1         2 $d = $num;
2551              
2552             } elsif ($flag eq 'last') {
2553 4         11 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         116 my $ymd = $self->nth_day_of_week($year,1,$dow,$mon);
2558 38         72 $d = $$ymd[2];
2559 38         97 while ($d < $num) {
2560 24         67 $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         66 my($h,$mn,$s) = @{ $self->split('hms',$time) };
  43         107  
2577 43         114 my $date = [ $y,$m,$d,$h,$mn,$s ];
2578              
2579             #
2580             # Calculate all the relevant dates.
2581             #
2582              
2583 43         73 my($endUT,$endLT,$begUT,$begLT,$offset);
2584 43         85 $stdoff = $self->split('offset',$stdoff);
2585 43         106 $dstoff = $self->split('offset',$dstoff);
2586              
2587 43 100       99 if ($timetype eq 'w') {
    100          
2588 39 100       122 $begUT = $self->calc_date_time($date,($isdst ? $stdoff : $dstoff), 1);
2589             } elsif ($timetype eq 'u') {
2590 2         3 $begUT = $date;
2591             } else {
2592 2         19 $begUT = $self->calc_date_time($date,$stdoff, 1);
2593             }
2594              
2595 43         145 $endUT = $self->calc_date_time($begUT,[0,0,-1]);
2596 43 100       127 $endLT = $self->calc_date_time($endUT,($isdst ? $stdoff : $dstoff));
2597 43 100       114 $begLT = $self->calc_date_time($begUT,($isdst ? $dstoff : $stdoff));
2598              
2599 43         196 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   9004 my($self,$string) = @_;
2607 4606         6397 my @ret;
2608              
2609 4606         6624 foreach my $enc (@{ $$self{'data'}{'calc'}{'enc_in'} }) {
  4606         13482  
2610 9565 100       23392 if (lc($enc) eq 'utf-8') {
    100          
2611 4606         16380 _utf8_on($string);
2612 4606 100       17497 push(@ret,$string) if is_utf8($string, 1);
2613             } elsif (lc($enc) eq 'perl') {
2614 4606         13613 push(@ret,encode_utf8($string));
2615             } else {
2616 353         502 my $tmp = $string;
2617 353         851 _utf8_off($tmp);
2618 353         934 $tmp = encode_utf8(decode($enc, $tmp));
2619 353         38137 _utf8_on($tmp);
2620 353 50       1209 push(@ret,$tmp) if is_utf8($tmp, 1);;
2621             }
2622             }
2623              
2624 4606         45923 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: