ViewVC Help
View File | Revision Log | Show Annotations | Root Listing
root/cvsroot/COMP/XCMSI/modules/Manip.pm
Revision: 1.3
Committed: Thu Aug 10 13:54:06 2006 UTC (18 years, 8 months ago) by krabbert
Content type: text/plain
Branch: MAIN
CVS Tags: XCMSI_1_0_1, XCMSI_1_0_0, XCMSI_0_9_1, HEAD
Changes since 1.2: +1 -1 lines
Log Message:
Had to switch off cvs keyword expansion ...

File Contents

# User Rev Content
1 krabbert 1.1 package Date::Manip;
2     # Copyright (c) 1995-2003 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     ###########################################################################
8    
9     use vars qw($OS %Lang %Holiday %Events %Curr %Cnf %Zone $VERSION @ISA @EXPORT);
10    
11     # Determine the type of OS...
12     $OS="Unix";
13     $OS="Windows" if ((defined $^O and
14     $^O =~ /MSWin32/i ||
15     $^O =~ /Windows_95/i ||
16     $^O =~ /Windows_NT/i) ||
17     (defined $ENV{OS} and
18     $ENV{OS} =~ /MSWin32/i ||
19     $ENV{OS} =~ /Windows_95/i ||
20     $ENV{OS} =~ /Windows_NT/i));
21     $OS="Netware" if (defined $^O and
22     $^O =~ /NetWare/i);
23     $OS="Mac" if ((defined $^O and
24     $^O =~ /MacOS/i) ||
25     (defined $ENV{OS} and
26     $ENV{OS} =~ /MacOS/i));
27     $OS="MPE" if (defined $^O and
28     $^O =~ /MPE/i);
29     $OS="OS2" if (defined $^O and
30     $^O =~ /os2/i);
31     $OS="VMS" if (defined $^O and
32     $^O =~ /VMS/i);
33    
34 krabbert 1.2 # kr: This is buggy from a crappy version of Date::Manip.pm
35     # kr: Replace by recipe from:
36     # http://mail.lon-capa.org/pipermail/lon-capa-admin/2006-April/001343.html
37     $Date::Manip::NoTaint = eval { local $^W=0; eval("#" . substr($^X, 0, 0)); 1 };
38 krabbert 1.1 # Determine if we're doing taint checking
39 krabbert 1.3 #$Date::Manip::NoTaint = eval { local $^W; unlink "$^X$^T"; 1 };
40 krabbert 1.2
41    
42 krabbert 1.1
43     ###########################################################################
44     # CUSTOMIZATION
45     ###########################################################################
46     #
47     # See the section of the POD documentation section CUSTOMIZING DATE::MANIP
48     # below for a complete description of each of these variables.
49    
50    
51     # Location of a the global config file. Tilde (~) expansions are allowed.
52     # This should be set in Date_Init arguments.
53     $Cnf{"GlobalCnf"}="";
54     $Cnf{"IgnoreGlobalCnf"}="";
55    
56     # Name of a personal config file and the path to search for it. Tilde (~)
57     # expansions are allowed. This should be set in Date_Init arguments or in
58     # the global config file.
59    
60     @Date::Manip::DatePath=();
61     if ($OS eq "Windows") {
62     $Cnf{"PathSep"} = ";";
63     $Cnf{"PersonalCnf"} = "Manip.cnf";
64     $Cnf{"PersonalCnfPath"} = ".";
65    
66     } elsif ($OS eq "Netware") {
67     $Cnf{"PathSep"} = ";";
68     $Cnf{"PersonalCnf"} = "Manip.cnf";
69     $Cnf{"PersonalCnfPath"} = ".";
70    
71     } elsif ($OS eq "MPE") {
72     $Cnf{"PathSep"} = ":";
73     $Cnf{"PersonalCnf"} = "Manip.cnf";
74     $Cnf{"PersonalCnfPath"} = ".";
75    
76     } elsif ($OS eq "OS2") {
77     $Cnf{"PathSep"} = ":";
78     $Cnf{"PersonalCnf"} = "Manip.cnf";
79     $Cnf{"PersonalCnfPath"} = ".";
80    
81     } elsif ($OS eq "Mac") {
82     $Cnf{"PathSep"} = ":";
83     $Cnf{"PersonalCnf"} = "Manip.cnf";
84     $Cnf{"PersonalCnfPath"} = ".";
85    
86     } elsif ($OS eq "VMS") {
87     # VMS doesn't like files starting with "."
88     $Cnf{"PathSep"} = "\n";
89     $Cnf{"PersonalCnf"} = "Manip.cnf";
90     $Cnf{"PersonalCnfPath"} = ".\n~";
91    
92     } else {
93     # Unix
94     $Cnf{"PathSep"} = ":";
95     $Cnf{"PersonalCnf"} = ".DateManip.cnf";
96     $Cnf{"PersonalCnfPath"} = ".:~";
97     @Date::Manip::DatePath=qw(/bin /usr/bin /usr/local/bin);
98     }
99    
100     ### Date::Manip variables set in the global or personal config file
101    
102     # Which language to use when parsing dates.
103     $Cnf{"Language"}="English";
104    
105     # 12/10 = Dec 10 (US) or Oct 12 (anything else)
106     $Cnf{"DateFormat"}="US";
107    
108     # Local timezone
109     $Cnf{"TZ"}="";
110    
111     # Timezone to work in (""=local, "IGNORE", or a timezone)
112     $Cnf{"ConvTZ"}="";
113    
114     # Date::Manip internal format (0=YYYYMMDDHH:MN:SS, 1=YYYYHHMMDDHHMNSS)
115     $Cnf{"Internal"}=0;
116    
117     # First day of the week (1=monday, 7=sunday). ISO 8601 says monday.
118     $Cnf{"FirstDay"}=1;
119    
120     # First and last day of the work week (1=monday, 7=sunday)
121     $Cnf{"WorkWeekBeg"}=1;
122     $Cnf{"WorkWeekEnd"}=5;
123    
124     # If non-nil, a work day is treated as 24 hours long (WorkDayBeg/WorkDayEnd
125     # ignored)
126     $Cnf{"WorkDay24Hr"}=0;
127    
128     # Start and end time of the work day (any time format allowed, seconds
129     # ignored)
130     $Cnf{"WorkDayBeg"}="08:00";
131     $Cnf{"WorkDayEnd"}="17:00";
132    
133     # If "today" is a holiday, we look either to "tomorrow" or "yesterday" for
134     # the nearest business day. By default, we'll always look "tomorrow"
135     # first.
136     $Cnf{"TomorrowFirst"}=1;
137    
138     # Erase the old holidays
139     $Cnf{"EraseHolidays"}="";
140    
141     # Set this to non-zero to be produce completely backwards compatible deltas
142     $Cnf{"DeltaSigns"}=0;
143    
144     # If this is 0, use the ISO 8601 standard that Jan 4 is in week 1. If 1,
145     # make week 1 contain Jan 1.
146     $Cnf{"Jan1Week1"}=0;
147    
148     # 2 digit years fall into the 100 year period given by [ CURR-N,
149     # CURR+(99-N) ] where N is 0-99. Default behavior is 89, but other useful
150     # numbers might be 0 (forced to be this year or later) and 99 (forced to be
151     # this year or earlier). It can also be set to "c" (current century) or
152     # "cNN" (i.e. c18 forces the year to bet 1800-1899). Also accepts the
153     # form cNNNN to give the 100 year period NNNN to NNNN+99.
154     $Cnf{"YYtoYYYY"}=89;
155    
156     # Set this to 1 if you want a long-running script to always update the
157     # timezone. This will slow Date::Manip down. Read the POD documentation.
158     $Cnf{"UpdateCurrTZ"}=0;
159    
160     # Use an international character set.
161     $Cnf{"IntCharSet"}=0;
162    
163     # Use this to force the current date to be set to this:
164     $Cnf{"ForceDate"}="";
165    
166     ###########################################################################
167    
168     require 5.000;
169     require Exporter;
170     @ISA = qw(Exporter);
171     @EXPORT = qw(
172     DateManipVersion
173     Date_Init
174     ParseDateString
175     ParseDate
176     ParseRecur
177     Date_Cmp
178     DateCalc
179     ParseDateDelta
180     UnixDate
181     Delta_Format
182     Date_GetPrev
183     Date_GetNext
184     Date_SetTime
185     Date_SetDateField
186     Date_IsHoliday
187     Events_List
188    
189     Date_DaysInMonth
190     Date_DayOfWeek
191     Date_SecsSince1970
192     Date_SecsSince1970GMT
193     Date_DaysSince1BC
194     Date_DayOfYear
195     Date_DaysInYear
196     Date_WeekOfYear
197     Date_LeapYear
198     Date_DaySuffix
199     Date_ConvTZ
200     Date_TimeZone
201     Date_IsWorkDay
202     Date_NextWorkDay
203     Date_PrevWorkDay
204     Date_NearestWorkDay
205     Date_NthDayOfYear
206     );
207     use strict;
208     use integer;
209     use Carp;
210    
211     use IO::File;
212    
213     $VERSION="5.42";
214    
215     ########################################################################
216     ########################################################################
217    
218     $Curr{"InitLang"} = 1; # Whether a language is being init'ed
219     $Curr{"InitDone"} = 0; # Whether Init_Date has been called
220     $Curr{"InitFilesRead"} = 0;
221     $Curr{"ResetWorkDay"} = 1;
222     $Curr{"Debug"} = "";
223     $Curr{"DebugVal"} = "";
224    
225     $Holiday{"year"} = 0;
226     $Holiday{"dates"} = {};
227     $Holiday{"desc"} = {};
228    
229     $Events{"raw"} = [];
230     $Events{"parsed"} = 0;
231     $Events{"dates"} = [];
232     $Events{"recur"} = [];
233    
234     ########################################################################
235     ########################################################################
236     # THESE ARE THE MAIN ROUTINES
237     ########################################################################
238     ########################################################################
239    
240     # Get rid of a problem with old versions of perl
241     no strict "vars";
242     # This sorts from longest to shortest element
243     sub sortByLength {
244     return (length $b <=> length $a);
245     }
246     use strict "vars";
247    
248     sub DateManipVersion {
249     print "DEBUG: DateManipVersion\n" if ($Curr{"Debug"} =~ /trace/);
250     return $VERSION;
251     }
252    
253     sub Date_Init {
254     print "DEBUG: Date_Init\n" if ($Curr{"Debug"} =~ /trace/);
255     $Curr{"Debug"}="";
256    
257     my(@args)=@_;
258     $Curr{"InitDone"}=1;
259     local($_)=();
260     my($internal,$firstday)=();
261     my($var,$val,$file,@tmp)=();
262    
263     # InitFilesRead = 0 : no conf files read yet
264     # 1 : global read, no personal read
265     # 2 : personal read
266    
267     $Cnf{"EraseHolidays"}=0;
268     foreach (@args) {
269     s/\s*$//;
270     s/^\s*//;
271     /^(\S+) \s* = \s* (.+)$/x;
272     ($var,$val)=($1,$2);
273     if ($var =~ /^GlobalCnf$/i) {
274     $Cnf{"GlobalCnf"}=$val;
275     if ($val) {
276     $Curr{"InitFilesRead"}=0;
277     &EraseHolidays();
278     }
279     } elsif ($var =~ /^PathSep$/i) {
280     $Cnf{"PathSep"}=$val;
281     } elsif ($var =~ /^PersonalCnf$/i) {
282     $Cnf{"PersonalCnf"}=$val;
283     $Curr{"InitFilesRead"}=1 if ($Curr{"InitFilesRead"}==2);
284     } elsif ($var =~ /^PersonalCnfPath$/i) {
285     $Cnf{"PersonalCnfPath"}=$val;
286     $Curr{"InitFilesRead"}=1 if ($Curr{"InitFilesRead"}==2);
287     } elsif ($var =~ /^IgnoreGlobalCnf$/i) {
288     $Curr{"InitFilesRead"}=1 if ($Curr{"InitFilesRead"}==0);
289     $Cnf{"IgnoreGlobalCnf"}=1;
290     } elsif ($var =~ /^EraseHolidays$/i) {
291     &EraseHolidays();
292     } else {
293     push(@tmp,$_);
294     }
295     }
296     @args=@tmp;
297    
298     # Read global config file
299     if ($Curr{"InitFilesRead"}<1 && ! $Cnf{"IgnoreGlobalCnf"}) {
300     $Curr{"InitFilesRead"}=1;
301    
302     if ($Cnf{"GlobalCnf"}) {
303     $file=&ExpandTilde($Cnf{"GlobalCnf"});
304     &Date_InitFile($file) if ($file);
305     }
306     }
307    
308     # Read personal config file
309     if ($Curr{"InitFilesRead"}<2) {
310     $Curr{"InitFilesRead"}=2;
311    
312     if ($Cnf{"PersonalCnf"} and $Cnf{"PersonalCnfPath"}) {
313     $file=&SearchPath($Cnf{"PersonalCnf"},$Cnf{"PersonalCnfPath"},"r");
314     &Date_InitFile($file) if ($file);
315     }
316     }
317    
318     foreach (@args) {
319     s/\s*$//;
320     s/^\s*//;
321     /^(\S+) \s* = \s* (.*)$/x;
322     ($var,$val)=($1,$2);
323     $val="" if (! defined $val);
324     &Date_SetConfigVariable($var,$val);
325     }
326    
327     confess "ERROR: Unknown FirstDay in Date::Manip.\n"
328     if (! &IsInt($Cnf{"FirstDay"},1,7));
329     confess "ERROR: Unknown WorkWeekBeg in Date::Manip.\n"
330     if (! &IsInt($Cnf{"WorkWeekBeg"},1,7));
331     confess "ERROR: Unknown WorkWeekEnd in Date::Manip.\n"
332     if (! &IsInt($Cnf{"WorkWeekEnd"},1,7));
333     confess "ERROR: Invalid WorkWeek in Date::Manip.\n"
334     if ($Cnf{"WorkWeekEnd"} <= $Cnf{"WorkWeekBeg"});
335    
336     my(%lang,
337     $tmp,%tmp,$tmp2,@tmp2,
338     $i,$j,@tmp3,
339     $zonesrfc,@zones)=();
340    
341     my($L)=$Cnf{"Language"};
342    
343     if ($Curr{"InitLang"}) {
344     $Curr{"InitLang"}=0;
345    
346     if ($L eq "English") {
347     &Date_Init_English(\%lang);
348    
349     } elsif ($L eq "French") {
350     &Date_Init_French(\%lang);
351    
352     } elsif ($L eq "Swedish") {
353     &Date_Init_Swedish(\%lang);
354    
355     } elsif ($L eq "German") {
356     &Date_Init_German(\%lang);
357    
358     } elsif ($L eq "Polish") {
359     &Date_Init_Polish(\%lang);
360    
361     } elsif ($L eq "Dutch" ||
362     $L eq "Nederlands") {
363     &Date_Init_Dutch(\%lang);
364    
365     } elsif ($L eq "Spanish") {
366     &Date_Init_Spanish(\%lang);
367    
368     } elsif ($L eq "Portuguese") {
369     &Date_Init_Portuguese(\%lang);
370    
371     } elsif ($L eq "Romanian") {
372     &Date_Init_Romanian(\%lang);
373    
374     } elsif ($L eq "Italian") {
375     &Date_Init_Italian(\%lang);
376    
377     } elsif ($L eq "Russian") {
378     &Date_Init_Russian(\%lang);
379    
380     } elsif ($L eq "Turkish") {
381     &Date_Init_Turkish(\%lang);
382    
383     } elsif ($L eq "Danish") {
384     &Date_Init_Danish(\%lang);
385    
386     } else {
387     confess "ERROR: Unknown language in Date::Manip.\n";
388     }
389    
390     # variables for months
391     # Month = "(jan|january|feb|february ... )"
392     # MonL = [ "Jan","Feb",... ]
393     # MonthL = [ "January","February", ... ]
394     # MonthH = { "january"=>1, "jan"=>1, ... }
395    
396     $Lang{$L}{"MonthH"}={};
397     $Lang{$L}{"MonthL"}=[];
398     $Lang{$L}{"MonL"}=[];
399     &Date_InitLists([$lang{"month_name"},
400     $lang{"month_abb"}],
401     \$Lang{$L}{"Month"},"lc,sort,back",
402     [$Lang{$L}{"MonthL"},
403     $Lang{$L}{"MonL"}],
404     [$Lang{$L}{"MonthH"},1]);
405    
406     # variables for day of week
407     # Week = "(mon|monday|tue|tuesday ... )"
408     # WL = [ "M","T",... ]
409     # WkL = [ "Mon","Tue",... ]
410     # WeekL = [ "Monday","Tudesday",... ]
411     # WeekH = { "monday"=>1,"mon"=>1,"m"=>1,... }
412    
413     $Lang{$L}{"WeekH"}={};
414     $Lang{$L}{"WeekL"}=[];
415     $Lang{$L}{"WkL"}=[];
416     $Lang{$L}{"WL"}=[];
417     &Date_InitLists([$lang{"day_name"},
418     $lang{"day_abb"}],
419     \$Lang{$L}{"Week"},"lc,sort,back",
420     [$Lang{$L}{"WeekL"},
421     $Lang{$L}{"WkL"}],
422     [$Lang{$L}{"WeekH"},1]);
423     &Date_InitLists([$lang{"day_char"}],
424     "","lc",
425     [$Lang{$L}{"WL"}],
426     [\%tmp,1]);
427     %{ $Lang{$L}{"WeekH"} } =
428     (%{ $Lang{$L}{"WeekH"} },%tmp);
429    
430     # variables for last
431     # Last = "(last)"
432     # LastL = [ "last" ]
433     # Each = "(each)"
434     # EachL = [ "each" ]
435     # variables for day of month
436     # DoM = "(1st|first ... 31st)"
437     # DoML = [ "1st","2nd",... "31st" ]
438     # DoMH = { "1st"=>1,"first"=>1, ... "31st"=>31 }
439     # variables for week of month
440     # WoM = "(1st|first| ... 5th|last)"
441     # WoMH = { "1st"=>1, ... "5th"=>5,"last"=>-1 }
442    
443     $Lang{$L}{"LastL"}=$lang{"last"};
444     &Date_InitStrings($lang{"last"},
445     \$Lang{$L}{"Last"},"lc,sort");
446    
447     $Lang{$L}{"EachL"}=$lang{"each"};
448     &Date_InitStrings($lang{"each"},
449     \$Lang{$L}{"Each"},"lc,sort");
450    
451     $Lang{$L}{"DoMH"}={};
452     $Lang{$L}{"DoML"}=[];
453     &Date_InitLists([$lang{"num_suff"},
454     $lang{"num_word"}],
455     \$Lang{$L}{"DoM"},"lc,sort,back,escape",
456     [$Lang{$L}{"DoML"},
457     \@tmp],
458     [$Lang{$L}{"DoMH"},1]);
459    
460     @tmp=();
461     foreach $tmp (keys %{ $Lang{$L}{"DoMH"} }) {
462     $tmp2=$Lang{$L}{"DoMH"}{$tmp};
463     if ($tmp2<6) {
464     $Lang{$L}{"WoMH"}{$tmp} = $tmp2;
465     push(@tmp,$tmp);
466     }
467     }
468     foreach $tmp (@{ $Lang{$L}{"LastL"} }) {
469     $Lang{$L}{"WoMH"}{$tmp} = -1;
470     push(@tmp,$tmp);
471     }
472     &Date_InitStrings(\@tmp,\$Lang{$L}{"WoM"},
473     "lc,sort,back,escape");
474    
475     # variables for AM or PM
476     # AM = "(am)"
477     # PM = "(pm)"
478     # AmPm = "(am|pm)"
479     # AMstr = "AM"
480     # PMstr = "PM"
481    
482     &Date_InitStrings($lang{"am"},\$Lang{$L}{"AM"},"lc,sort,escape");
483     &Date_InitStrings($lang{"pm"},\$Lang{$L}{"PM"},"lc,sort,escape");
484     &Date_InitStrings([ @{$lang{"am"}},@{$lang{"pm"}} ],\$Lang{$L}{"AmPm"},
485     "lc,back,sort,escape");
486     $Lang{$L}{"AMstr"}=$lang{"am"}[0];
487     $Lang{$L}{"PMstr"}=$lang{"pm"}[0];
488    
489     # variables for expressions used in parsing deltas
490     # Yabb = "(?:y|yr|year|years)"
491     # Mabb = similar for months
492     # Wabb = similar for weeks
493     # Dabb = similar for days
494     # Habb = similar for hours
495     # MNabb = similar for minutes
496     # Sabb = similar for seconds
497     # Repl = { "abb"=>"replacement" }
498     # Whenever an abbreviation could potentially refer to two different
499     # strings (M standing for Minutes or Months), the abbreviation must
500     # be listed in Repl instead of in the appropriate Xabb values. This
501     # only applies to abbreviations which are substrings of other values
502     # (so there is no confusion between Mn and Month).
503    
504     &Date_InitStrings($lang{"years"} ,\$Lang{$L}{"Yabb"}, "lc,sort");
505     &Date_InitStrings($lang{"months"} ,\$Lang{$L}{"Mabb"}, "lc,sort");
506     &Date_InitStrings($lang{"weeks"} ,\$Lang{$L}{"Wabb"}, "lc,sort");
507     &Date_InitStrings($lang{"days"} ,\$Lang{$L}{"Dabb"}, "lc,sort");
508     &Date_InitStrings($lang{"hours"} ,\$Lang{$L}{"Habb"}, "lc,sort");
509     &Date_InitStrings($lang{"minutes"},\$Lang{$L}{"MNabb"},"lc,sort");
510     &Date_InitStrings($lang{"seconds"},\$Lang{$L}{"Sabb"}, "lc,sort");
511     $Lang{$L}{"Repl"}={};
512     &Date_InitHash($lang{"replace"},undef,"lc",$Lang{$L}{"Repl"});
513    
514     # variables for special dates that are offsets from now
515     # Now = "(now|today)"
516     # Offset = "(yesterday|tomorrow)"
517     # OffsetH = { "yesterday"=>"-0:0:0:1:0:0:0",... ]
518     # Times = "(noon|midnight)"
519     # TimesH = { "noon"=>"12:00:00","midnight"=>"00:00:00" }
520     # SepHM = hour/minute separator
521     # SepMS = minute/second separator
522     # SepSS = second/fraction separator
523    
524     $Lang{$L}{"TimesH"}={};
525     &Date_InitHash($lang{"times"},
526     \$Lang{$L}{"Times"},"lc,sort,back",
527     $Lang{$L}{"TimesH"});
528     &Date_InitStrings($lang{"now"},\$Lang{$L}{"Now"},"lc,sort");
529     $Lang{$L}{"OffsetH"}={};
530     &Date_InitHash($lang{"offset"},
531     \$Lang{$L}{"Offset"},"lc,sort,back",
532     $Lang{$L}{"OffsetH"});
533     $Lang{$L}{"SepHM"}=$lang{"sephm"};
534     $Lang{$L}{"SepMS"}=$lang{"sepms"};
535     $Lang{$L}{"SepSS"}=$lang{"sepss"};
536    
537     # variables for time zones
538     # zones = regular expression with all zone names (EST)
539     # n2o = a hash of all parsable zone names with their offsets
540     # tzones = reguar expression with all tzdata timezones (US/Eastern)
541     # tz2z = hash of all tzdata timezones to full timezone (EST#EDT)
542    
543     $zonesrfc=
544     "idlw -1200 ". # International Date Line West
545     "nt -1100 ". # Nome
546     "hst -1000 ". # Hawaii Standard
547     "cat -1000 ". # Central Alaska
548     "ahst -1000 ". # Alaska-Hawaii Standard
549     "akst -0900 ". # Alaska Standard
550     "yst -0900 ". # Yukon Standard
551     "hdt -0900 ". # Hawaii Daylight
552     "akdt -0800 ". # Alaska Daylight
553     "ydt -0800 ". # Yukon Daylight
554     "pst -0800 ". # Pacific Standard
555     "pdt -0700 ". # Pacific Daylight
556     "mst -0700 ". # Mountain Standard
557     "mdt -0600 ". # Mountain Daylight
558     "cst -0600 ". # Central Standard
559     "cdt -0500 ". # Central Daylight
560     "est -0500 ". # Eastern Standard
561     "act -0500 ". # Brazil, Acre
562     "sat -0400 ". # Chile
563     "bot -0400 ". # Bolivia
564     "amt -0400 ". # Brazil, Amazon
565     "acst -0400 ". # Brazil, Acre Daylight
566     "edt -0400 ". # Eastern Daylight
567     "ast -0400 ". # Atlantic Standard
568     #"nst -0330 ". # Newfoundland Standard nst=North Sumatra +0630
569     "nft -0330 ". # Newfoundland
570     #"gst -0300 ". # Greenland Standard gst=Guam Standard +1000
571     #"bst -0300 ". # Brazil Standard bst=British Summer +0100
572     "brt -0300 ". # Brazil Standard (official time)
573     "brst -0300 ". # Brazil Standard
574     "adt -0300 ". # Atlantic Daylight
575     "art -0300 ". # Argentina
576     "amst -0300 ". # Brazil, Amazon Daylight
577     "ndt -0230 ". # Newfoundland Daylight
578     "brst -0200 ". # Brazil Daylight (official time)
579     "fnt -0200 ". # Brazil, Fernando de Noronha
580     "at -0200 ". # Azores
581     "wat -0100 ". # West Africa
582     "fnst -0100 ". # Brazil, Fernando de Noronha Daylight
583     "gmt +0000 ". # Greenwich Mean
584     "ut +0000 ". # Universal
585     "utc +0000 ". # Universal (Coordinated)
586     "wet +0000 ". # Western European
587     "cet +0100 ". # Central European
588     "fwt +0100 ". # French Winter
589     "met +0100 ". # Middle European
590     "mez +0100 ". # Middle European
591     "mewt +0100 ". # Middle European Winter
592     "swt +0100 ". # Swedish Winter
593     "bst +0100 ". # British Summer bst=Brazil standard -0300
594     "gb +0100 ". # GMT with daylight savings
595     "west +0000 ". # Western European Daylight
596     "eet +0200 ". # Eastern Europe, USSR Zone 1
597     "cest +0200 ". # Central European Summer
598     "fst +0200 ". # French Summer
599     "ist +0200 ". # Israel standard
600     "mest +0200 ". # Middle European Summer
601     "mesz +0200 ". # Middle European Summer
602     "metdst +0200 ". # An alias for mest used by HP-UX
603     "sast +0200 ". # South African Standard
604     "sst +0200 ". # Swedish Summer sst=South Sumatra +0700
605     "bt +0300 ". # Baghdad, USSR Zone 2
606     "eest +0300 ". # Eastern Europe Summer
607     "eetedt +0300 ". # Eastern Europe, USSR Zone 1
608     "idt +0300 ". # Israel Daylight
609     "msk +0300 ". # Moscow
610     "eat +0300 ". # East Africa
611     "it +0330 ". # Iran
612     "zp4 +0400 ". # USSR Zone 3
613     "msd +0400 ". # Moscow Daylight
614     "zp5 +0500 ". # USSR Zone 4
615     "ist +0530 ". # Indian Standard
616     "zp6 +0600 ". # USSR Zone 5
617     "novst +0600 ". # Novosibirsk time zone, Russia
618     "nst +0630 ". # North Sumatra nst=Newfoundland Std -0330
619     #"sst +0700 ". # South Sumatra, USSR Zone 6 sst=Swedish Summer +0200
620     "javt +0700 ". # Java
621     "hkt +0800 ". # Hong Kong
622     "sgt +0800 ". # Singapore
623     "cct +0800 ". # China Coast, USSR Zone 7
624     "awst +0800 ". # Australian Western Standard
625     "wst +0800 ". # West Australian Standard
626     "pht +0800 ". # Asia Manila
627     "kst +0900 ". # Republic of Korea
628     "jst +0900 ". # Japan Standard, USSR Zone 8
629     "rok +0900 ". # Republic of Korea
630     "acst +0930 ". # Australian Central Standard
631     "cast +0930 ". # Central Australian Standard
632     "aest +1000 ". # Australian Eastern Standard
633     "east +1000 ". # Eastern Australian Standard
634     "gst +1000 ". # Guam Standard, USSR Zone 9 gst=Greenland Std -0300
635     "acdt +1030 ". # Australian Central Daylight
636     "cadt +1030 ". # Central Australian Daylight
637     "aedt +1100 ". # Australian Eastern Daylight
638     "eadt +1100 ". # Eastern Australian Daylight
639     "idle +1200 ". # International Date Line East
640     "nzst +1200 ". # New Zealand Standard
641     "nzt +1200 ". # New Zealand
642     "nzdt +1300 ". # New Zealand Daylight
643     "z +0000 ".
644     "a +0100 b +0200 c +0300 d +0400 e +0500 f +0600 g +0700 h +0800 ".
645     "i +0900 k +1000 l +1100 m +1200 ".
646     "n -0100 o -0200 p -0300 q -0400 r -0500 s -0600 t -0700 u -0800 ".
647     "v -0900 w -1000 x -1100 y -1200";
648    
649     $Zone{"n2o"} = {};
650     ($Zone{"zones"},%{ $Zone{"n2o"} })=
651     &Date_Regexp($zonesrfc,"sort,lc,under,back",
652     "keys");
653    
654     $tmp=
655     "US/Pacific PST8PDT ".
656     "US/Mountain MST7MDT ".
657     "US/Central CST6CDT ".
658     "US/Eastern EST5EDT ".
659     "Canada/Pacific PST8PDT ".
660     "Canada/Mountain MST7MDT ".
661     "Canada/Central CST6CDT ".
662     "Canada/Eastern EST5EDT";
663    
664     $Zone{"tz2z"} = {};
665     ($Zone{"tzones"},%{ $Zone{"tz2z"} })=
666     &Date_Regexp($tmp,"lc,under,back","keys");
667     $Cnf{"TZ"}=&Date_TimeZone;
668    
669     # misc. variables
670     # At = "(?:at)"
671     # Of = "(?:in|of)"
672     # On = "(?:on)"
673     # Future = "(?:in)"
674     # Later = "(?:later)"
675     # Past = "(?:ago)"
676     # Next = "(?:next)"
677     # Prev = "(?:last|previous)"
678    
679     &Date_InitStrings($lang{"at"}, \$Lang{$L}{"At"}, "lc,sort");
680     &Date_InitStrings($lang{"on"}, \$Lang{$L}{"On"}, "lc,sort");
681     &Date_InitStrings($lang{"future"},\$Lang{$L}{"Future"}, "lc,sort");
682     &Date_InitStrings($lang{"later"}, \$Lang{$L}{"Later"}, "lc,sort");
683     &Date_InitStrings($lang{"past"}, \$Lang{$L}{"Past"}, "lc,sort");
684     &Date_InitStrings($lang{"next"}, \$Lang{$L}{"Next"}, "lc,sort");
685     &Date_InitStrings($lang{"prev"}, \$Lang{$L}{"Prev"}, "lc,sort");
686     &Date_InitStrings($lang{"of"}, \$Lang{$L}{"Of"}, "lc,sort");
687    
688     # calc mode variables
689     # Approx = "(?:approximately)"
690     # Exact = "(?:exactly)"
691     # Business = "(?:business)"
692    
693     &Date_InitStrings($lang{"exact"}, \$Lang{$L}{"Exact"}, "lc,sort");
694     &Date_InitStrings($lang{"approx"}, \$Lang{$L}{"Approx"}, "lc,sort");
695     &Date_InitStrings($lang{"business"},\$Lang{$L}{"Business"},"lc,sort");
696    
697     ############### END OF LANGUAGE INITIALIZATION
698     }
699    
700     if ($Curr{"ResetWorkDay"}) {
701     my($h1,$m1,$h2,$m2)=();
702     if ($Cnf{"WorkDay24Hr"}) {
703     ($Curr{"WDBh"},$Curr{"WDBm"})=(0,0);
704     ($Curr{"WDEh"},$Curr{"WDEm"})=(24,0);
705     $Curr{"WDlen"}=24*60;
706     $Cnf{"WorkDayBeg"}="00:00";
707     $Cnf{"WorkDayEnd"}="23:59";
708    
709     } else {
710     confess "ERROR: Invalid WorkDayBeg in Date::Manip.\n"
711     if (! (($h1,$m1)=&CheckTime($Cnf{"WorkDayBeg"})));
712     $Cnf{"WorkDayBeg"}="$h1:$m1";
713     confess "ERROR: Invalid WorkDayEnd in Date::Manip.\n"
714     if (! (($h2,$m2)=&CheckTime($Cnf{"WorkDayEnd"})));
715     $Cnf{"WorkDayEnd"}="$h2:$m2";
716    
717     ($Curr{"WDBh"},$Curr{"WDBm"})=($h1,$m1);
718     ($Curr{"WDEh"},$Curr{"WDEm"})=($h2,$m2);
719    
720     # Work day length = h1:m1 or 0:len (len minutes)
721     $h1=$h2-$h1;
722     $m1=$m2-$m1;
723     if ($m1<0) {
724     $h1--;
725     $m1+=60;
726     }
727     $Curr{"WDlen"}=$h1*60+$m1;
728     }
729     $Curr{"ResetWorkDay"}=0;
730     }
731    
732     # current time
733     my($s,$mn,$h,$d,$m,$y,$wday,$yday,$isdst,$ampm,$wk)=();
734     if ($Cnf{"ForceDate"}=~
735     /^(\d{4})-(\d{2})-(\d{2})-(\d{2}):(\d{2}):(\d{2})$/) {
736     ($y,$m,$d,$h,$mn,$s)=($1,$2,$3,$4,$5,$6);
737     } else {
738     ($s,$mn,$h,$d,$m,$y,$wday,$yday,$isdst)=localtime(time);
739     $y+=1900;
740     $m++;
741     }
742     &Date_DateCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk);
743     $Curr{"Y"}=$y;
744     $Curr{"M"}=$m;
745     $Curr{"D"}=$d;
746     $Curr{"H"}=$h;
747     $Curr{"Mn"}=$mn;
748     $Curr{"S"}=$s;
749     $Curr{"AmPm"}=$ampm;
750     $Curr{"Now"}=&Date_Join($y,$m,$d,$h,$mn,$s);
751    
752     $Curr{"Debug"}=$Curr{"DebugVal"};
753    
754     # If we're in array context, let's return a list of config variables
755     # that could be passed to Date_Init to get the same state as we're
756     # currently in.
757     if (wantarray) {
758     # Some special variables that have to be in a specific order
759     my(@special)=qw(IgnoreGlobalCnf GlobalCnf PersonalCnf PersonalCnfPath);
760     my(%tmp)=map { $_,1 } @special;
761     my(@tmp,$key,$val);
762     foreach $key (@special) {
763     $val=$Cnf{$key};
764     push(@tmp,"$key=$val");
765     }
766     foreach $key (keys %Cnf) {
767     next if (exists $tmp{$key});
768     $val=$Cnf{$key};
769     push(@tmp,"$key=$val");
770     }
771     return @tmp;
772     }
773     return ();
774     }
775    
776     sub ParseDateString {
777     print "DEBUG: ParseDateString\n" if ($Curr{"Debug"} =~ /trace/);
778     local($_)=@_;
779     return "" if (! $_);
780    
781     my($y,$m,$d,$h,$mn,$s,$i,$wofm,$dofw,$wk,$tmp,$z,$num,$err,$iso,$ampm)=();
782     my($date,$z2,$delta,$from,$falsefrom,$to,$which,$midnight)=();
783    
784     # We only need to reinitialize if we have to determine what NOW is.
785     &Date_Init() if (! $Curr{"InitDone"} or $Cnf{"UpdateCurrTZ"});
786    
787     my($L)=$Cnf{"Language"};
788     my($type)=$Cnf{"DateFormat"};
789    
790     # Mode is set in DateCalc. ParseDate only overrides it if the string
791     # contains a mode.
792     if ($Lang{$L}{"Exact"} &&
793     s/$Lang{$L}{"Exact"}//) {
794     $Curr{"Mode"}=0;
795     } elsif ($Lang{$L}{"Approx"} &&
796     s/$Lang{$L}{"Approx"}//) {
797     $Curr{"Mode"}=1;
798     } elsif ($Lang{$L}{"Business"} &&
799     s/$Lang{$L}{"Business"}//) {
800     $Curr{"Mode"}=2;
801     } elsif (! exists $Curr{"Mode"}) {
802     $Curr{"Mode"}=0;
803     }
804    
805     # Unfortunately, some deltas can be parsed as dates. An example is
806     # 1 second == 1 2nd == 1 2
807     # But, some dates can be parsed as deltas. The most important being:
808     # 1998010101:00:00
809     # We'll check to see if a "date" can be parsed as a delta. If so, we'll
810     # assume that it is a delta (since they are much simpler, it is much
811     # less likely that we'll mistake a delta for a date than vice versa)
812     # unless it is an ISO-8601 date.
813     #
814     # This is important because we are using DateCalc to test whether a
815     # string is a date or a delta. Dates are tested first, so we need to
816     # be able to pass a delta into this routine and have it correctly NOT
817     # interpreted as a date.
818     #
819     # We will insist that the string contain something other than digits and
820     # colons so that the following will get correctly interpreted as a date
821     # rather than a delta:
822     # 12:30
823     # 19980101
824    
825     $delta="";
826     $delta=&ParseDateDelta($_) if (/[^:0-9]/);
827    
828     # Put parse in a simple loop for an easy exit.
829     PARSE: {
830     my(@tmp)=&Date_Split($_);
831     if (@tmp) {
832     ($y,$m,$d,$h,$mn,$s)=@tmp;
833     last PARSE;
834     }
835    
836     # Fundamental regular expressions
837    
838     my($month)=$Lang{$L}{"Month"}; # (jan|january|...)
839     my(%month)=%{ $Lang{$L}{"MonthH"} }; # { jan=>1, ... }
840     my($week)=$Lang{$L}{"Week"}; # (mon|monday|...)
841     my(%week)=%{ $Lang{$L}{"WeekH"} }; # { mon=>1, monday=>1, ... }
842     my($wom)=$Lang{$L}{"WoM"}; # (1st|...|fifth|last)
843     my(%wom)=%{ $Lang{$L}{"WoMH"} }; # { 1st=>1,... fifth=>5,last=>-1 }
844     my($dom)=$Lang{$L}{"DoM"}; # (1st|first|...31st)
845     my(%dom)=%{ $Lang{$L}{"DoMH"} }; # { 1st=>1, first=>1, ... }
846     my($ampmexp)=$Lang{$L}{"AmPm"}; # (am|pm)
847     my($timeexp)=$Lang{$L}{"Times"}; # (noon|midnight)
848     my($now)=$Lang{$L}{"Now"}; # (now|today)
849     my($offset)=$Lang{$L}{"Offset"}; # (yesterday|tomorrow)
850     my($zone)=$Zone{"zones"} . '(?:\s+|$)'; # (edt|est|...)\s+
851     my($day)='\s*'.$Lang{$L}{"Dabb"}; # \s*(?:d|day|days)
852     my($mabb)='\s*'.$Lang{$L}{"Mabb"}; # \s*(?:mon|month|months)
853     my($wkabb)='\s*'.$Lang{$L}{"Wabb"}; # \s*(?:w|wk|week|weeks)
854     my($next)='\s*'.$Lang{$L}{"Next"}; # \s*(?:next)
855     my($prev)='\s*'.$Lang{$L}{"Prev"}; # \s*(?:last|previous)
856     my($past)='\s*'.$Lang{$L}{"Past"}; # \s*(?:ago)
857     my($future)='\s*'.$Lang{$L}{"Future"}; # \s*(?:in)
858     my($later)='\s*'.$Lang{$L}{"Later"}; # \s*(?:later)
859     my($at)=$Lang{$L}{"At"}; # (?:at)
860     my($of)='\s*'.$Lang{$L}{"Of"}; # \s*(?:in|of)
861     my($on)='(?:\s*'.$Lang{$L}{"On"}.'\s*|\s+)';
862     # \s*(?:on)\s* or \s+
863     my($last)='\s*'.$Lang{$L}{"Last"}; # \s*(?:last)
864     my($hm)=$Lang{$L}{"SepHM"}; # :
865     my($ms)=$Lang{$L}{"SepMS"}; # :
866     my($ss)=$Lang{$L}{"SepSS"}; # .
867    
868     # Other regular expressions
869    
870     my($D4)='(\d{4})'; # 4 digits (yr)
871     my($YY)='(\d{4}|\d{2})'; # 2 or 4 digits (yr)
872     my($DD)='(\d{2})'; # 2 digits (mon/day/hr/min/sec)
873     my($D) ='(\d{1,2})'; # 1 or 2 digit (mon/day/hr)
874     my($FS)="(?:$ss\\d+)?"; # fractional secs
875     my($sep)='[\/.-]'; # non-ISO8601 m/d/yy separators
876     # absolute time zone +0700 (GMT)
877     my($hzone)='(?:[0-1][0-9]|2[0-3])'; # 00 - 23
878     my($mzone)='(?:[0-5][0-9])'; # 00 - 59
879     my($zone2)='(?:\s*([+-](?:'."$hzone$mzone|$hzone:$mzone|$hzone))".
880     # +0700 +07:00 -07
881     '(?:\s*\([^)]+\))?)'; # (GMT)
882    
883     # A regular expression for the time EXCEPT for the hour part
884     my($mnsec)="$hm$DD(?:$ms$DD$FS)?(?:\\s*$ampmexp)?";
885    
886     # A special regular expression for /YYYY:HH:MN:SS used by Apache
887     my($apachetime)='(/\d{4}):' . "$DD$hm$DD$ms$DD";
888    
889     my($time)="";
890     $ampm="";
891     $date="";
892    
893     # Substitute all special time expressions.
894     if (/(^|[^a-z])$timeexp($|[^a-z])/i) {
895     $tmp=$2;
896     $tmp=$Lang{$L}{"TimesH"}{lc($tmp)};
897     s/(^|[^a-z])$timeexp($|[^a-z])/$1 $tmp $3/i;
898     }
899    
900     # Remove some punctuation
901     s/[,]/ /g;
902    
903     # Make sure that ...7EST works (i.e. a timezone immediately following
904     # a digit.
905     s/(\d)$zone(\s+|$|[0-9])/$1 $2$3/i;
906     $zone = '\s+'.$zone;
907    
908     # Remove the time
909     $iso=1;
910     $midnight=0;
911     $from="24${hm}00(?:${ms}00)?";
912     $falsefrom="${hm}24${ms}00"; # Don't trap XX:24:00
913     $to="00${hm}00${ms}00";
914     $midnight=1 if (!/$falsefrom/ && s/$from/$to/);
915    
916     $h=$mn=$s=0;
917     if (/$D$mnsec/i || /$ampmexp/i) {
918     $iso=0;
919     $tmp=0;
920     $tmp=1 if (/$mnsec$zone2?\s*$/i); # or /$mnsec$zone/ ??
921     $tmp=0 if (/$ampmexp/i);
922     if (s/$apachetime$zone()/$1 /i ||
923     s/$apachetime$zone2?/$1 /i ||
924     s/(^|[^a-z])$at\s*$D$mnsec$zone()/$1 /i ||
925     s/(^|[^a-z])$at\s*$D$mnsec$zone2?/$1 /i ||
926     s/(^|[^0-9])(\d)$mnsec$zone()/$1 /i ||
927     s/(^|[^0-9])(\d)$mnsec$zone2?/$1 /i ||
928     (s/(t)$D$mnsec$zone()/$1 /i and (($iso=-$tmp) || 1)) ||
929     (s/(t)$D$mnsec$zone2?/$1 /i and (($iso=-$tmp) || 1)) ||
930     (s/()$DD$mnsec$zone()/ /i and (($iso=$tmp) || 1)) ||
931     (s/()$DD$mnsec$zone2?/ /i and (($iso=$tmp) || 1)) ||
932     s/(^|$at\s*|\s+)$D()()\s*$ampmexp$zone()/ /i ||
933     s/(^|$at\s*|\s+)$D()()\s*$ampmexp$zone2?/ /i ||
934     0
935     ) {
936     ($h,$mn,$s,$ampm,$z,$z2)=($2,$3,$4,$5,$6,$7);
937     if (defined ($z)) {
938     if ($z =~ /^[+-]\d{2}:\d{2}$/) {
939     $z=~ s/://;
940     } elsif ($z =~ /^[+-]\d{2}$/) {
941     $z .= "00";
942     }
943     }
944     $time=1;
945     &Date_TimeCheck(\$h,\$mn,\$s,\$ampm);
946     $y=$m=$d="";
947     # We're going to be calling TimeCheck again below (when we check the
948     # final date), so get rid of $ampm so that we don't have an error
949     # due to "15:30:00 PM". It'll get reset below.
950     $ampm="";
951     if (/^\s*$/) {
952     &Date_Init() if (! $Cnf{"UpdateCurrTZ"});
953     last PARSE;
954     }
955     }
956     }
957     $time=0 if ($time ne "1");
958     s/\s+$//;
959     s/^\s+//;
960    
961     # dateTtime ISO 8601 formats
962     my($orig)=$_;
963     s/t$//i if ($iso<0);
964    
965     # Parse ISO 8601 dates now (which may still have a zone stuck to it).
966     if ( ($iso && /^([0-9-]+(?:W[0-9-]+)?)$zone?$/i) ||
967     ($iso && /^([0-9-]+(?:W[0-9-]+)?)$zone2?$/i) ||
968     ($iso && /^([0-9-]+(?:T[0-9-]+)?)$zone?$/i) ||
969     ($iso && /^([0-9-]+(?:T[0-9-]+)?)$zone2?$/i) ||
970     0) {
971    
972     # ISO 8601 dates
973     ($_,$z,$z2) = ($1,$2);
974     s,-, ,g; # Change all ISO8601 seps to spaces
975     s/^\s+//;
976     s/\s+$//;
977    
978     if (/^$D4\s*$DD\s*$DD\s*t?$DD(?:$DD(?:$DD(\d*))?)?$/i ||
979     /^$DD\s+$DD\s*$DD\s*t?$DD(?:$DD(?:$DD(\d*))?)?$/i ||
980     0
981     ) {
982     # ISO 8601 Dates with times
983     # YYYYMMDDHHMNSSFFFF...
984     # YYYYMMDDHHMNSS
985     # YYYYMMDDHHMN
986     # YYYYMMDDHH
987     # YY MMDDHHMNSSFFFF...
988     # YY MMDDHHMNSS
989     # YY MMDDHHMN
990     # YY MMDDHH
991     ($y,$m,$d,$h,$mn,$s,$tmp)=($1,$2,$3,$4,$5,$6,$7);
992     if ($h==24 && (! defined $mn || $mn==0) && (! defined $s || $s==0)) {
993     $h=0;
994     $midnight=1;
995     }
996     $z = "" if (! defined $h);
997     return "" if ($time && defined $h);
998     last PARSE;
999    
1000     } elsif (/^$D4(?:\s*$DD(?:\s*$DD)?)?$/ ||
1001     /^$DD(?:\s+$DD(?:\s*$DD)?)?$/) {
1002     # ISO 8601 Dates
1003     # YYYYMMDD
1004     # YYYYMM
1005     # YYYY
1006     # YY MMDD
1007     # YY MM
1008     # YY
1009     ($y,$m,$d)=($1,$2,$3);
1010     last PARSE;
1011    
1012     } elsif (/^$YY\s+$D\s+$D/) {
1013     # YY-M-D
1014     ($y,$m,$d)=($1,$2,$3);
1015     last PARSE;
1016    
1017     } elsif (/^$YY\s*W$DD\s*(\d)?$/i) {
1018     # YY-W##-D
1019     ($y,$wofm,$dofw)=($1,$2,$3);
1020     ($y,$m,$d)=&Date_NthWeekOfYear($y,$wofm,$dofw);
1021     last PARSE;
1022    
1023     } elsif (/^$D4\s*(\d{3})$/ ||
1024     /^$DD\s*(\d{3})$/) {
1025     # YYDOY
1026     ($y,$which)=($1,$2);
1027     ($y,$m,$d)=&Date_NthDayOfYear($y,$which);
1028     last PARSE;
1029    
1030     } elsif ($iso<0) {
1031     # We confused something like 1999/August12:00:00
1032     # with a dateTtime format
1033     $_=$orig;
1034    
1035     } else {
1036     return "";
1037     }
1038     }
1039    
1040     # All deltas that are not ISO-8601 dates are NOT dates.
1041     return "" if ($Curr{"InCalc"} && $delta);
1042     if ($delta) {
1043     &Date_Init() if (! $Cnf{"UpdateCurrTZ"});
1044     return &DateCalc_DateDelta($Curr{"Now"},$delta);
1045     }
1046    
1047     # Check for some special types of dates (next, prev)
1048     foreach $from (keys %{ $Lang{$L}{"Repl"} }) {
1049     $to=$Lang{$L}{"Repl"}{$from};
1050     s/(^|[^a-z])$from($|[^a-z])/$1$to$2/i;
1051     }
1052     if (/$wom/i || /$future/i || /$later/i || /$past/i ||
1053     /$next/i || /$prev/i || /^$week$/i || /$wkabb/i) {
1054     $tmp=0;
1055    
1056     if (/^$wom\s*$week$of\s*$month\s*$YY?$/i) {
1057     # last friday in October 95
1058     ($wofm,$dofw,$m,$y)=($1,$2,$3,$4);
1059     # fix $m, $y
1060     return "" if (&Date_DateCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk));
1061     $dofw=$week{lc($dofw)};
1062     $wofm=$wom{lc($wofm)};
1063     # Get the first day of the month
1064     $date=&Date_Join($y,$m,1,$h,$mn,$s);
1065     if ($wofm==-1) {
1066     $date=&DateCalc_DateDelta($date,"+0:1:0:0:0:0:0",\$err,0);
1067     $date=&Date_GetPrev($date,$dofw,0);
1068     } else {
1069     for ($i=0; $i<$wofm; $i++) {
1070     if ($i==0) {
1071     $date=&Date_GetNext($date,$dofw,1);
1072     } else {
1073     $date=&Date_GetNext($date,$dofw,0);
1074     }
1075     }
1076     }
1077     last PARSE;
1078    
1079     } elsif (/^$last$day$of\s*$month(?:$of?\s*$YY)?/i) {
1080     # last day in month
1081     ($m,$y)=($1,$2);
1082     &Date_Init() if (! $Cnf{"UpdateCurrTZ"});
1083     $y=&Date_FixYear($y) if (! defined $y or length($y)<4);
1084     $m=$month{lc($m)};
1085     $d=&Date_DaysInMonth($m,$y);
1086     last PARSE;
1087    
1088     } elsif (/^$week$/i) {
1089     # friday
1090     ($dofw)=($1);
1091     &Date_Init() if (! $Cnf{"UpdateCurrTZ"});
1092     $date=&Date_GetPrev($Curr{"Now"},$Cnf{"FirstDay"},1);
1093     $date=&Date_GetNext($date,$dofw,1,$h,$mn,$s);
1094     last PARSE;
1095    
1096     } elsif (/^$next\s*$week$/i) {
1097     # next friday
1098     ($dofw)=($1);
1099     &Date_Init() if (! $Cnf{"UpdateCurrTZ"});
1100     $date=&Date_GetNext($Curr{"Now"},$dofw,0,$h,$mn,$s);
1101     last PARSE;
1102    
1103     } elsif (/^$prev\s*$week$/i) {
1104     # last friday
1105     ($dofw)=($1);
1106     &Date_Init() if (! $Cnf{"UpdateCurrTZ"});
1107     $date=&Date_GetPrev($Curr{"Now"},$dofw,0,$h,$mn,$s);
1108     last PARSE;
1109    
1110     } elsif (/^$next$wkabb$/i) {
1111     # next week
1112     &Date_Init() if (! $Cnf{"UpdateCurrTZ"});
1113     $date=&DateCalc_DateDelta($Curr{"Now"},"+0:0:1:0:0:0:0",\$err,0);
1114     $date=&Date_SetTime($date,$h,$mn,$s) if (defined $h);
1115     last PARSE;
1116     } elsif (/^$prev$wkabb$/i) {
1117     # last week
1118     &Date_Init() if (! $Cnf{"UpdateCurrTZ"});
1119     $date=&DateCalc_DateDelta($Curr{"Now"},"-0:0:1:0:0:0:0",\$err,0);
1120     $date=&Date_SetTime($date,$h,$mn,$s) if (defined $h);
1121     last PARSE;
1122    
1123     } elsif (/^$next$mabb$/i) {
1124     # next month
1125     &Date_Init() if (! $Cnf{"UpdateCurrTZ"});
1126     $date=&DateCalc_DateDelta($Curr{"Now"},"+0:1:0:0:0:0:0",\$err,0);
1127     $date=&Date_SetTime($date,$h,$mn,$s) if (defined $h);
1128     last PARSE;
1129     } elsif (/^$prev$mabb$/i) {
1130     # last month
1131     &Date_Init() if (! $Cnf{"UpdateCurrTZ"});
1132     $date=&DateCalc_DateDelta($Curr{"Now"},"-0:1:0:0:0:0:0",\$err,0);
1133     $date=&Date_SetTime($date,$h,$mn,$s) if (defined $h);
1134     last PARSE;
1135    
1136     } elsif (/^$future\s*(\d+)$day$/i ||
1137     /^(\d+)$day$later$/i) {
1138     # in 2 days
1139     # 2 days later
1140     ($num)=($1);
1141     &Date_Init() if (! $Cnf{"UpdateCurrTZ"});
1142     $date=&DateCalc_DateDelta($Curr{"Now"},"+0:0:0:$num:0:0:0",
1143     \$err,0);
1144     $date=&Date_SetTime($date,$h,$mn,$s) if (defined $h);
1145     last PARSE;
1146     } elsif (/^(\d+)$day$past$/i) {
1147     # 2 days ago
1148     ($num)=($1);
1149     &Date_Init() if (! $Cnf{"UpdateCurrTZ"});
1150     $date=&DateCalc_DateDelta($Curr{"Now"},"-0:0:0:$num:0:0:0",
1151     \$err,0);
1152     $date=&Date_SetTime($date,$h,$mn,$s) if (defined $h);
1153     last PARSE;
1154    
1155     } elsif (/^$future\s*(\d+)$wkabb$/i ||
1156     /^(\d+)$wkabb$later$/i) {
1157     # in 2 weeks
1158     # 2 weeks later
1159     ($num)=($1);
1160     &Date_Init() if (! $Cnf{"UpdateCurrTZ"});
1161     $date=&DateCalc_DateDelta($Curr{"Now"},"+0:0:$num:0:0:0:0",
1162     \$err,0);
1163     $date=&Date_SetTime($date,$h,$mn,$s) if (defined $h);
1164     last PARSE;
1165     } elsif (/^(\d+)$wkabb$past$/i) {
1166     # 2 weeks ago
1167     ($num)=($1);
1168     &Date_Init() if (! $Cnf{"UpdateCurrTZ"});
1169     $date=&DateCalc_DateDelta($Curr{"Now"},"-0:0:$num:0:0:0:0",
1170     \$err,0);
1171     $date=&Date_SetTime($date,$h,$mn,$s) if (defined $h);
1172     last PARSE;
1173    
1174     } elsif (/^$future\s*(\d+)$mabb$/i ||
1175     /^(\d+)$mabb$later$/i) {
1176     # in 2 months
1177     # 2 months later
1178     ($num)=($1);
1179     &Date_Init() if (! $Cnf{"UpdateCurrTZ"});
1180     $date=&DateCalc_DateDelta($Curr{"Now"},"+0:$num:0:0:0:0:0",
1181     \$err,0);
1182     $date=&Date_SetTime($date,$h,$mn,$s) if (defined $h);
1183     last PARSE;
1184     } elsif (/^(\d+)$mabb$past$/i) {
1185     # 2 months ago
1186     ($num)=($1);
1187     &Date_Init() if (! $Cnf{"UpdateCurrTZ"});
1188     $date=&DateCalc_DateDelta($Curr{"Now"},"-0:$num:0:0:0:0:0",
1189     \$err,0);
1190     $date=&Date_SetTime($date,$h,$mn,$s) if (defined $h);
1191     last PARSE;
1192    
1193     } elsif (/^$week$future\s*(\d+)$wkabb$/i ||
1194     /^$week\s*(\d+)$wkabb$later$/i) {
1195     # friday in 2 weeks
1196     # friday 2 weeks later
1197     ($dofw,$num)=($1,$2);
1198     $tmp="+";
1199     } elsif (/^$week\s*(\d+)$wkabb$past$/i) {
1200     # friday 2 weeks ago
1201     ($dofw,$num)=($1,$2);
1202     $tmp="-";
1203     } elsif (/^$future\s*(\d+)$wkabb$on$week$/i ||
1204     /^(\d+)$wkabb$later$on$week$/i) {
1205     # in 2 weeks on friday
1206     # 2 weeks later on friday
1207     ($num,$dofw)=($1,$2);
1208     $tmp="+"
1209     } elsif (/^(\d+)$wkabb$past$on$week$/i) {
1210     # 2 weeks ago on friday
1211     ($num,$dofw)=($1,$2);
1212     $tmp="-";
1213     } elsif (/^$week\s*$wkabb$/i) {
1214     # monday week (British date: in 1 week on monday)
1215     $dofw=$1;
1216     $num=1;
1217     $tmp="+";
1218     } elsif (/^$now\s*$wkabb$/i) {
1219     # today week (British date: 1 week from today)
1220     &Date_Init() if (! $Cnf{"UpdateCurrTZ"});
1221     $date=&DateCalc_DateDelta($Curr{"Now"},"+0:0:1:0:0:0:0",\$err,0);
1222     $date=&Date_SetTime($date,$h,$mn,$s) if (defined $h);
1223     last PARSE;
1224     } elsif (/^$offset\s*$wkabb$/i) {
1225     # tomorrow week (British date: 1 week from tomorrow)
1226     ($offset)=($1);
1227     &Date_Init() if (! $Cnf{"UpdateCurrTZ"});
1228     $offset=$Lang{$L}{"OffsetH"}{lc($offset)};
1229     $date=&DateCalc_DateDelta($Curr{"Now"},$offset,\$err,0);
1230     $date=&DateCalc_DateDelta($date,"+0:0:1:0:0:0:0",\$err,0);
1231     if ($time) {
1232     return ""
1233     if (&Date_DateCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk));
1234     $date=&Date_SetTime($date,$h,$mn,$s);
1235     }
1236     last PARSE;
1237     }
1238    
1239     if ($tmp) {
1240     &Date_Init() if (! $Cnf{"UpdateCurrTZ"});
1241     $date=&DateCalc_DateDelta($Curr{"Now"},
1242     $tmp . "0:0:$num:0:0:0:0",\$err,0);
1243     $date=&Date_GetPrev($date,$Cnf{"FirstDay"},1);
1244     $date=&Date_GetNext($date,$dofw,1,$h,$mn,$s);
1245     last PARSE;
1246     }
1247     }
1248    
1249     # Change (2nd, second) to 2
1250     $tmp=0;
1251     if (/(^|[^a-z0-9])$dom($|[^a-z0-9])/i) {
1252     if (/^\s*$dom\s*$/) {
1253     ($d)=($1);
1254     $d=$dom{lc($d)};
1255     $m=$Curr{"M"};
1256     last PARSE;
1257     }
1258     my $from = $2;
1259     my $to = $dom{ lc($from) };
1260     s/(^|[^a-z])$from($|[^a-z])/$1 $to $2/i;
1261     s/^\s+//;
1262     s/\s+$//;
1263     }
1264    
1265     # Another set of special dates (Nth week)
1266     if (/^$D\s*$week(?:$of?\s*$YY)?$/i) {
1267     # 22nd sunday in 1996
1268     ($which,$dofw,$y)=($1,$2,$3);
1269     $y=$Curr{"Y"} if (! $y);
1270     $y--; # previous year
1271     $tmp=&Date_GetNext("$y-12-31",$dofw,0);
1272     if ($which>1) {
1273     $tmp=&DateCalc_DateDelta($tmp,"+0:0:".($which-1).":0:0:0:0",\$err,0);
1274     }
1275     ($y,$m,$d)=(&Date_Split($tmp, 1))[0..2];
1276     last PARSE;
1277     } elsif (/^$week$wkabb\s*$D(?:$of?\s*$YY)?$/i ||
1278     /^$week\s*$D$wkabb(?:$of?\s*$YY)?$/i) {
1279     # sunday week 22 in 1996
1280     # sunday 22nd week in 1996
1281     ($dofw,$which,$y)=($1,$2,$3);
1282     ($y,$m,$d)=&Date_NthWeekOfYear($y,$which,$dofw);
1283     last PARSE;
1284     }
1285    
1286     # Get rid of day of week
1287     if (/(^|[^a-z])$week($|[^a-z])/i) {
1288     $wk=$2;
1289     (s/(^|[^a-z])$week,/$1 /i) ||
1290     s/(^|[^a-z])$week($|[^a-z])/$1 $3/i;
1291     s/^\s+//;
1292     s/\s+$//;
1293     }
1294    
1295     {
1296     # So that we can handle negative epoch times, let's convert
1297     # things like "epoch -" to "epochNEGATIVE " before we strip out
1298     # the $sep chars, which include '-'.
1299     s,epoch\s*-,epochNEGATIVE ,g;
1300    
1301     # Non-ISO8601 dates
1302     s,\s*$sep\s*, ,g; # change all non-ISO8601 seps to spaces
1303     s,^\s*,,; # remove leading/trailing space
1304     s,\s*$,,;
1305    
1306     if (/^$D\s+$D(?:\s+$YY)?$/) {
1307     # MM DD YY (DD MM YY non-US)
1308     ($m,$d,$y)=($1,$2,$3);
1309     ($m,$d)=($d,$m) if ($type ne "US");
1310     last PARSE;
1311    
1312     } elsif (/^$D4\s*$D\s*$D$/) {
1313     # YYYY MM DD
1314     ($y,$m,$d)=($1,$2,$3);
1315     last PARSE;
1316    
1317     } elsif (s/(^|[^a-z])$month($|[^a-z])/$1 $3/i) {
1318     ($m)=($2);
1319    
1320     if (/^\s*$D(?:\s+$YY)?\s*$/) {
1321     # mmm DD YY
1322     # DD mmm YY
1323     # DD YY mmm
1324     ($d,$y)=($1,$2);
1325     last PARSE;
1326    
1327     } elsif (/^\s*$D$D4\s*$/) {
1328     # mmm DD YYYY
1329     # DD mmm YYYY
1330     # DD YYYY mmm
1331     ($d,$y)=($1,$2);
1332     last PARSE;
1333    
1334     } elsif (/^\s*$D4\s*$D\s*$/) {
1335     # mmm YYYY DD
1336     # YYYY mmm DD
1337     # YYYY DD mmm
1338     ($y,$d)=($1,$2);
1339     last PARSE;
1340    
1341     } elsif (/^\s*$D4\s*$/) {
1342     # mmm YYYY
1343     # YYYY mmm
1344     ($y,$d)=($1,1);
1345     last PARSE;
1346    
1347     } else {
1348     return "";
1349     }
1350    
1351     } elsif (/^epochNEGATIVE (\d+)$/) {
1352     $s=$1;
1353     $date=&DateCalc("1970-01-01 00:00 GMT","-0:0:$s");
1354     } elsif (/^epoch\s*(\d+)$/i) {
1355     $s=$1;
1356     $date=&DateCalc("1970-01-01 00:00 GMT","+0:0:$s");
1357    
1358     } elsif (/^$now$/i) {
1359     # now, today
1360     &Date_Init() if (! $Cnf{"UpdateCurrTZ"});
1361     $date=$Curr{"Now"};
1362     if ($time) {
1363     return ""
1364     if (&Date_DateCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk));
1365     $date=&Date_SetTime($date,$h,$mn,$s);
1366     }
1367     last PARSE;
1368    
1369     } elsif (/^$offset$/i) {
1370     # yesterday, tomorrow
1371     ($offset)=($1);
1372     &Date_Init() if (! $Cnf{"UpdateCurrTZ"});
1373     $offset=$Lang{$L}{"OffsetH"}{lc($offset)};
1374     $date=&DateCalc_DateDelta($Curr{"Now"},$offset,\$err,0);
1375     if ($time) {
1376     return ""
1377     if (&Date_DateCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk));
1378     $date=&Date_SetTime($date,$h,$mn,$s);
1379     }
1380     last PARSE;
1381    
1382     } else {
1383     return "";
1384     }
1385     }
1386     }
1387    
1388     if (! $date) {
1389     return "" if (&Date_DateCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk));
1390     $date=&Date_Join($y,$m,$d,$h,$mn,$s);
1391     }
1392     $date=&Date_ConvTZ($date,$z);
1393     if ($midnight) {
1394     $date=&DateCalc_DateDelta($date,"+0:0:0:1:0:0:0");
1395     }
1396     return $date;
1397     }
1398    
1399     sub ParseDate {
1400     print "DEBUG: ParseDate\n" if ($Curr{"Debug"} =~ /trace/);
1401     &Date_Init() if (! $Curr{"InitDone"});
1402     my($args,@args,@a,$ref,$date)=();
1403     @a=@_;
1404    
1405     # @a : is the list of args to ParseDate. Currently, only one argument
1406     # is allowed and it must be a scalar (or a reference to a scalar)
1407     # or a reference to an array.
1408    
1409     if ($#a!=0) {
1410     print "ERROR: Invalid number of arguments to ParseDate.\n";
1411     return "";
1412     }
1413     $args=$a[0];
1414     $ref=ref $args;
1415     if (! $ref) {
1416     return $args if (&Date_Split($args));
1417     @args=($args);
1418     } elsif ($ref eq "ARRAY") {
1419     @args=@$args;
1420     } elsif ($ref eq "SCALAR") {
1421     return $$args if (&Date_Split($$args));
1422     @args=($$args);
1423     } else {
1424     print "ERROR: Invalid arguments to ParseDate.\n";
1425     return "";
1426     }
1427     @a=@args;
1428    
1429     # @args : a list containing all the arguments (dereferenced if appropriate)
1430     # @a : a list containing all the arguments currently being examined
1431     # $ref : nil, "SCALAR", or "ARRAY" depending on whether a scalar, a
1432     # reference to a scalar, or a reference to an array was passed in
1433     # $args : the scalar or refererence passed in
1434    
1435     PARSE: while($#a>=0) {
1436     $date=join(" ",@a);
1437     $date=&ParseDateString($date);
1438     last if ($date);
1439     pop(@a);
1440     } # PARSE
1441    
1442     splice(@args,0,$#a + 1);
1443     @$args= @args if (defined $ref and $ref eq "ARRAY");
1444     $date;
1445     }
1446    
1447     sub Date_Cmp {
1448     my($D1,$D2)=@_;
1449     my($date1)=&ParseDateString($D1);
1450     my($date2)=&ParseDateString($D2);
1451     return $date1 cmp $date2;
1452     }
1453    
1454     # **NOTE**
1455     # The calc routines all call parse routines, so it is never necessary to
1456     # call Date_Init in the calc routines.
1457     sub DateCalc {
1458     print "DEBUG: DateCalc\n" if ($Curr{"Debug"} =~ /trace/);
1459     my($D1,$D2,@arg)=@_;
1460     my($ref,$err,$errref,$mode)=();
1461    
1462     $errref=shift(@arg);
1463     $ref=0;
1464     if (defined $errref) {
1465     if (ref $errref) {
1466     $mode=shift(@arg);
1467     $ref=1;
1468     } else {
1469     $mode=$errref;
1470     $errref="";
1471     }
1472     }
1473    
1474     my(@date,@delta,$ret,$tmp,$old)=();
1475    
1476     if (defined $mode and $mode>=0 and $mode<=3) {
1477     $Curr{"Mode"}=$mode;
1478     } else {
1479     $Curr{"Mode"}=0;
1480     }
1481    
1482     $old=$Curr{"InCalc"};
1483     $Curr{"InCalc"}=1;
1484    
1485     if ($tmp=&ParseDateString($D1)) {
1486     # If we've already parsed the date, we don't want to do it a second
1487     # time (so we don't convert timezones twice).
1488     if (&Date_Split($D1)) {
1489     push(@date,$D1);
1490     } else {
1491     push(@date,$tmp);
1492     }
1493     } elsif ($tmp=&ParseDateDelta($D1)) {
1494     push(@delta,$tmp);
1495     } else {
1496     $$errref=1 if ($ref);
1497     return;
1498     }
1499    
1500     if ($tmp=&ParseDateString($D2)) {
1501     if (&Date_Split($D2)) {
1502     push(@date,$D2);
1503     } else {
1504     push(@date,$tmp);
1505     }
1506     } elsif ($tmp=&ParseDateDelta($D2)) {
1507     push(@delta,$tmp);
1508     } else {
1509     $$errref=2 if ($ref);
1510     return;
1511     }
1512     $mode=$Curr{"Mode"};
1513     $Curr{"InCalc"}=$old;
1514    
1515     if ($#date==1) {
1516     $ret=&DateCalc_DateDate(@date,$mode);
1517     } elsif ($#date==0) {
1518     $ret=&DateCalc_DateDelta(@date,@delta,\$err,$mode);
1519     $$errref=$err if ($ref);
1520     } else {
1521     $ret=&DateCalc_DeltaDelta(@delta,$mode);
1522     }
1523     $ret;
1524     }
1525    
1526     sub ParseDateDelta {
1527     print "DEBUG: ParseDateDelta\n" if ($Curr{"Debug"} =~ /trace/);
1528     my($args,@args,@a,$ref)=();
1529     local($_)=();
1530     @a=@_;
1531    
1532     # @a : is the list of args to ParseDateDelta. Currently, only one argument
1533     # is allowed and it must be a scalar (or a reference to a scalar)
1534     # or a reference to an array.
1535    
1536     if ($#a!=0) {
1537     print "ERROR: Invalid number of arguments to ParseDateDelta.\n";
1538     return "";
1539     }
1540     $args=$a[0];
1541     $ref=ref $args;
1542     if (! $ref) {
1543     @args=($args);
1544     } elsif ($ref eq "ARRAY") {
1545     @args=@$args;
1546     } elsif ($ref eq "SCALAR") {
1547     @args=($$args);
1548     } else {
1549     print "ERROR: Invalid arguments to ParseDateDelta.\n";
1550     return "";
1551     }
1552     @a=@args;
1553    
1554     # @args : a list containing all the arguments (dereferenced if appropriate)
1555     # @a : a list containing all the arguments currently being examined
1556     # $ref : nil, "SCALAR", or "ARRAY" depending on whether a scalar, a
1557     # reference to a scalar, or a reference to an array was passed in
1558     # $args : the scalar or refererence passed in
1559    
1560     my(@colon,@delta,$delta,$dir,$colon,$sign,$val)=();
1561     my($len,$tmp,$tmp2,$tmpl)=();
1562     my($from,$to)=();
1563     my($workweek)=$Cnf{"WorkWeekEnd"}-$Cnf{"WorkWeekBeg"}+1;
1564    
1565     &Date_Init() if (! $Curr{"InitDone"});
1566     # A sign can be a sequence of zero or more + and - signs, this
1567     # allows for deltas like '+ -2 days'.
1568     my($signexp)='((?:[+-]\s*)*)';
1569     my($numexp)='(\d+)';
1570     my($exp1)="(?: \\s* $signexp \\s* $numexp \\s*)";
1571     my($yexp,$mexp,$wexp,$dexp,$hexp,$mnexp,$sexp,$i)=();
1572     $yexp=$mexp=$wexp=$dexp=$hexp=$mnexp=$sexp="()()";
1573     $yexp ="(?: $exp1 ". $Lang{$Cnf{"Language"}}{"Yabb"} .")?";
1574     $mexp ="(?: $exp1 ". $Lang{$Cnf{"Language"}}{"Mabb"} .")?";
1575     $wexp ="(?: $exp1 ". $Lang{$Cnf{"Language"}}{"Wabb"} .")?";
1576     $dexp ="(?: $exp1 ". $Lang{$Cnf{"Language"}}{"Dabb"} .")?";
1577     $hexp ="(?: $exp1 ". $Lang{$Cnf{"Language"}}{"Habb"} .")?";
1578     $mnexp="(?: $exp1 ". $Lang{$Cnf{"Language"}}{"MNabb"}.")?";
1579     $sexp ="(?: $exp1 ". $Lang{$Cnf{"Language"}}{"Sabb"} ."?)?";
1580     my($future)=$Lang{$Cnf{"Language"}}{"Future"};
1581     my($later)=$Lang{$Cnf{"Language"}}{"Later"};
1582     my($past)=$Lang{$Cnf{"Language"}}{"Past"};
1583    
1584     $delta="";
1585     PARSE: while (@a) {
1586     $_ = join(" ", grep {defined;} @a);
1587     s/\s+$//;
1588     last if ($_ eq "");
1589    
1590     # Mode is set in DateCalc. ParseDateDelta only overrides it if the
1591     # string contains a mode.
1592     if ($Lang{$Cnf{"Language"}}{"Exact"} &&
1593     s/$Lang{$Cnf{"Language"}}{"Exact"}//) {
1594     $Curr{"Mode"}=0;
1595     } elsif ($Lang{$Cnf{"Language"}}{"Approx"} &&
1596     s/$Lang{$Cnf{"Language"}}{"Approx"}//) {
1597     $Curr{"Mode"}=1;
1598     } elsif ($Lang{$Cnf{"Language"}}{"Business"} &&
1599     s/$Lang{$Cnf{"Language"}}{"Business"}//) {
1600     $Curr{"Mode"}=2;
1601     } elsif (! exists $Curr{"Mode"}) {
1602     $Curr{"Mode"}=0;
1603     }
1604     $workweek=7 if ($Curr{"Mode"} != 2);
1605    
1606     foreach $from (keys %{ $Lang{$Cnf{"Language"}}{"Repl"} }) {
1607     $to=$Lang{$Cnf{"Language"}}{"Repl"}{$from};
1608     s/(^|[^a-z])$from($|[^a-z])/$1$to$2/i;
1609     }
1610    
1611     # in or ago
1612     #
1613     # We need to make sure that $later, $future, and $past don't contain each
1614     # other... Romanian pointed this out where $past is "in urma" and $future
1615     # is "in". When they do, we have to take this into account.
1616     # $len length of best match (greatest wins)
1617     # $tmp string after best match
1618     # $dir direction (prior, after) of best match
1619     #
1620     # $tmp2 string before/after current match
1621     # $tmpl length of current match
1622    
1623     $len=0;
1624     $tmp=$_;
1625     $dir=1;
1626    
1627     $tmp2=$_;
1628     if ($tmp2 =~ s/(^|[^a-z])($future)($|[^a-z])/$1 $3/i) {
1629     $tmpl=length($2);
1630     if ($tmpl>$len) {
1631     $tmp=$tmp2;
1632     $dir=1;
1633     $len=$tmpl;
1634     }
1635     }
1636    
1637     $tmp2=$_;
1638     if ($tmp2 =~ s/(^|[^a-z])($later)($|[^a-z])/$1 $3/i) {
1639     $tmpl=length($2);
1640     if ($tmpl>$len) {
1641     $tmp=$tmp2;
1642     $dir=1;
1643     $len=$tmpl;
1644     }
1645     }
1646    
1647     $tmp2=$_;
1648     if ($tmp2 =~ s/(^|[^a-z])($past)($|[^a-z])/$1 $3/i) {
1649     $tmpl=length($2);
1650     if ($tmpl>$len) {
1651     $tmp=$tmp2;
1652     $dir=-1;
1653     $len=$tmpl;
1654     }
1655     }
1656    
1657     $_ = $tmp;
1658     s/\s*$//;
1659    
1660     # the colon part of the delta
1661     $colon="";
1662     if (s/($signexp?$numexp?(:($signexp?$numexp)?){1,6})$//) {
1663     $colon=$1;
1664     s/\s+$//;
1665     }
1666     @colon=split(/:/,$colon);
1667    
1668     # the non-colon part of the delta
1669     $sign="+";
1670     @delta=();
1671     $i=6;
1672     foreach $exp1 ($yexp,$mexp,$wexp,$dexp,$hexp,$mnexp,$sexp) {
1673     last if ($#colon>=$i--);
1674     $val=0;
1675     if (s/^$exp1//ix) {
1676     $val=$2 if ($2);
1677     $sign=$1 if ($1);
1678     }
1679    
1680     # Collapse a sign like '+ -' into a single character like '-',
1681     # by counting the occurrences of '-'.
1682     #
1683     $sign =~ s/\s+//g;
1684     $sign =~ tr/+//d;
1685     my $count = ($sign =~ tr/-//d);
1686     die "bad characters in sign: $sign" if length $sign;
1687     $sign = $count % 2 ? '-' : '+';
1688    
1689     push(@delta,"$sign$val");
1690     }
1691     if (! /^\s*$/) {
1692     pop(@a);
1693     next PARSE;
1694     }
1695    
1696     # make sure that the colon part has a sign
1697     for ($i=0; $i<=$#colon; $i++) {
1698     $val=0;
1699     if ($colon[$i] =~ /^$signexp$numexp?/) {
1700     $val=$2 if ($2);
1701     $sign=$1 if ($1);
1702     }
1703     $colon[$i] = "$sign$val";
1704     }
1705    
1706     # combine the two
1707     push(@delta,@colon);
1708     if ($dir<0) {
1709     for ($i=0; $i<=$#delta; $i++) {
1710     $delta[$i] =~ tr/-+/+-/;
1711     }
1712     }
1713    
1714     # form the delta and shift off the valid part
1715     $delta=join(":",@delta);
1716     splice(@args,0,$#a+1);
1717     @$args=@args if (defined $ref and $ref eq "ARRAY");
1718     last PARSE;
1719     }
1720    
1721     $delta=&Delta_Normalize($delta,$Curr{"Mode"});
1722     return $delta;
1723     }
1724    
1725     sub UnixDate {
1726     print "DEBUG: UnixDate\n" if ($Curr{"Debug"} =~ /trace/);
1727     my($date,@format)=@_;
1728     local($_)=();
1729     my($format,%f,$out,@out,$c,$date1,$date2,$tmp)=();
1730     my($scalar)=();
1731     $date=&ParseDateString($date);
1732     return if (! $date);
1733    
1734     my($y,$m,$d,$h,$mn,$s)=($f{"Y"},$f{"m"},$f{"d"},$f{"H"},$f{"M"},$f{"S"})=
1735     &Date_Split($date, 1);
1736     $f{"y"}=substr $f{"Y"},2;
1737     &Date_Init() if (! $Curr{"InitDone"});
1738    
1739     if (! wantarray) {
1740     $format=join(" ",@format);
1741     @format=($format);
1742     $scalar=1;
1743     }
1744    
1745     # month, week
1746     $_=$m;
1747     s/^0//;
1748     $f{"b"}=$f{"h"}=$Lang{$Cnf{"Language"}}{"MonL"}[$_-1];
1749     $f{"B"}=$Lang{$Cnf{"Language"}}{"MonthL"}[$_-1];
1750     $_=$m;
1751     s/^0/ /;
1752     $f{"f"}=$_;
1753     $f{"U"}=&Date_WeekOfYear($m,$d,$y,7);
1754     $f{"W"}=&Date_WeekOfYear($m,$d,$y,1);
1755    
1756     # check week 52,53 and 0
1757     $f{"G"}=$f{"L"}=$y;
1758     if ($f{"W"}>=52 || $f{"U"}>=52) {
1759     my($dd,$mm,$yy)=($d,$m,$y);
1760     $dd+=7;
1761     if ($dd>31) {
1762     $dd-=31;
1763     $mm=1;
1764     $yy++;
1765     if (&Date_WeekOfYear($mm,$dd,$yy,1)==2) {
1766     $f{"G"}=$yy;
1767     $f{"W"}=1;
1768     }
1769     if (&Date_WeekOfYear($mm,$dd,$yy,7)==2) {
1770     $f{"L"}=$yy;
1771     $f{"U"}=1;
1772     }
1773     }
1774     }
1775     if ($f{"W"}==0) {
1776     my($dd,$mm,$yy)=($d,$m,$y);
1777     $dd-=7;
1778     $dd+=31 if ($dd<1);
1779     $yy--;
1780     $mm=12;
1781     $f{"G"}=$yy;
1782     $f{"W"}=&Date_WeekOfYear($mm,$dd,$yy,1)+1;
1783     }
1784     if ($f{"U"}==0) {
1785     my($dd,$mm,$yy)=($d,$m,$y);
1786     $dd-=7;
1787     $dd+=31 if ($dd<1);
1788     $yy--;
1789     $mm=12;
1790     $f{"L"}=$yy;
1791     $f{"U"}=&Date_WeekOfYear($mm,$dd,$yy,7)+1;
1792     }
1793    
1794     $f{"U"}="0".$f{"U"} if (length $f{"U"} < 2);
1795     $f{"W"}="0".$f{"W"} if (length $f{"W"} < 2);
1796    
1797     # day
1798     $f{"j"}=&Date_DayOfYear($m,$d,$y);
1799     $f{"j"} = "0" . $f{"j"} while (length($f{"j"})<3);
1800     $_=$d;
1801     s/^0/ /;
1802     $f{"e"}=$_;
1803     $f{"w"}=&Date_DayOfWeek($m,$d,$y);
1804     $f{"v"}=$Lang{$Cnf{"Language"}}{"WL"}[$f{"w"}-1];
1805     $f{"v"}=" ".$f{"v"} if (length $f{"v"} < 2);
1806     $f{"a"}=$Lang{$Cnf{"Language"}}{"WkL"}[$f{"w"}-1];
1807     $f{"A"}=$Lang{$Cnf{"Language"}}{"WeekL"}[$f{"w"}-1];
1808     $f{"E"}=&Date_DaySuffix($f{"e"});
1809    
1810     # hour
1811     $_=$h;
1812     s/^0/ /;
1813     $f{"k"}=$_;
1814     $f{"i"}=$f{"k"}+1;
1815     $f{"i"}=$f{"k"};
1816     $f{"i"}=12 if ($f{"k"}==0);
1817     $f{"i"}=$f{"k"}-12 if ($f{"k"}>12);
1818     $f{"i"}=$f{"i"}-12 if ($f{"i"}>12);
1819     $f{"i"}=" ".$f{"i"} if (length($f{"i"})<2);
1820     $f{"I"}=$f{"i"};
1821     $f{"I"}=~ s/^ /0/;
1822     $f{"p"}=$Lang{$Cnf{"Language"}}{"AMstr"};
1823     $f{"p"}=$Lang{$Cnf{"Language"}}{"PMstr"} if ($f{"k"}>11);
1824    
1825     # minute, second, timezone
1826     $f{"o"}=&Date_SecsSince1970($m,$d,$y,$h,$mn,$s);
1827     $f{"s"}=&Date_SecsSince1970GMT($m,$d,$y,$h,$mn,$s);
1828     $f{"Z"}=($Cnf{"ConvTZ"} eq "IGNORE" or $Cnf{"ConvTZ"} eq "") ?
1829     $Cnf{"TZ"} : $Cnf{"ConvTZ"};
1830     $f{"z"}=($f{"Z"}=~/^[+-]\d{4}/) ? $f{"Z"} : ($Zone{"n2o"}{lc $f{"Z"}} || "");
1831    
1832     # date, time
1833     $f{"c"}=qq|$f{"a"} $f{"b"} $f{"e"} $h:$mn:$s $y|;
1834     $f{"C"}=$f{"u"}=
1835     qq|$f{"a"} $f{"b"} $f{"e"} $h:$mn:$s $f{"z"} $y|;
1836     $f{"g"}=qq|$f{"a"}, $d $f{"b"} $y $h:$mn:$s $f{"z"}|;
1837     $f{"D"}=$f{"x"}=qq|$m/$d/$f{"y"}|;
1838     $f{"r"}=qq|$f{"I"}:$mn:$s $f{"p"}|;
1839     $f{"R"}=qq|$h:$mn|;
1840     $f{"T"}=$f{"X"}=qq|$h:$mn:$s|;
1841     $f{"V"}=qq|$m$d$h$mn$f{"y"}|;
1842     $f{"Q"}="$y$m$d";
1843     $f{"q"}=qq|$y$m$d$h$mn$s|;
1844     $f{"P"}=qq|$y$m$d$h:$mn:$s|;
1845     $f{"F"}=qq|$f{"A"}, $f{"B"} $f{"e"}, $f{"Y"}|;
1846     if ($f{"W"}==0) {
1847     $y--;
1848     $tmp=&Date_WeekOfYear(12,31,$y,1);
1849     $tmp="0$tmp" if (length($tmp) < 2);
1850     $f{"J"}=qq|$y-W$tmp-$f{"w"}|;
1851     } else {
1852     $f{"J"}=qq|$f{"G"}-W$f{"W"}-$f{"w"}|;
1853     }
1854     $f{"K"}=qq|$y-$f{"j"}|;
1855     # %l is a special case. Since it requires the use of the calculator
1856     # which requires this routine, an infinite recursion results. To get
1857     # around this, %l is NOT determined every time this is called so the
1858     # recursion breaks.
1859    
1860     # other formats
1861     $f{"n"}="\n";
1862     $f{"t"}="\t";
1863     $f{"%"}="%";
1864     $f{"+"}="+";
1865    
1866     foreach $format (@format) {
1867     $format=reverse($format);
1868     $out="";
1869     while ($format ne "") {
1870     $c=chop($format);
1871     if ($c eq "%") {
1872     $c=chop($format);
1873     if ($c eq "l") {
1874     &Date_Init();
1875     $date1=&DateCalc_DateDelta($Curr{"Now"},"-0:6:0:0:0:0:0");
1876     $date2=&DateCalc_DateDelta($Curr{"Now"},"+0:6:0:0:0:0:0");
1877     if (&Date_Cmp($date,$date1)>=0 && &Date_Cmp($date,$date2)<=0) {
1878     $f{"l"}=qq|$f{"b"} $f{"e"} $h:$mn|;
1879     } else {
1880     $f{"l"}=qq|$f{"b"} $f{"e"} $f{"Y"}|;
1881     }
1882     $out .= $f{"$c"};
1883     } elsif (exists $f{"$c"}) {
1884     $out .= $f{"$c"};
1885     } else {
1886     $out .= $c;
1887     }
1888     } else {
1889     $out .= $c;
1890     }
1891     }
1892     push(@out,$out);
1893     }
1894     if ($scalar) {
1895     return $out[0];
1896     } else {
1897     return (@out);
1898     }
1899     }
1900    
1901     # Can't be in "use integer" because we're doing decimal arithmatic
1902     no integer;
1903     sub Delta_Format {
1904     print "DEBUG: Delta_Format\n" if ($Curr{"Debug"} =~ /trace/);
1905     my($delta,$dec,@format)=@_;
1906     $delta=&ParseDateDelta($delta);
1907     return "" if (! $delta);
1908     my(@out,%f,$out,$c1,$c2,$scalar,$format)=();
1909     local($_)=$delta;
1910     my($y,$M,$w,$d,$h,$m,$s)=&Delta_Split($delta);
1911     # Get rid of positive signs.
1912     ($y,$M,$w,$d,$h,$m,$s)=map { 1*$_; }($y,$M,$w,$d,$h,$m,$s);
1913    
1914     if (defined $dec && $dec>0) {
1915     $dec="%." . ($dec*1) . "f";
1916     } else {
1917     $dec="%f";
1918     }
1919    
1920     if (! wantarray) {
1921     $format=join(" ",@format);
1922     @format=($format);
1923     $scalar=1;
1924     }
1925    
1926     # Length of each unit in seconds
1927     my($sl,$ml,$hl,$dl,$wl,$yl)=();
1928     $sl = 1;
1929     $ml = $sl*60;
1930     $hl = $ml*60;
1931     $dl = $hl*24;
1932     $wl = $dl*7;
1933     $yl = $dl*365.25;
1934    
1935     # The decimal amount of each unit contained in all smaller units
1936     my($yd,$Md,$sd,$md,$hd,$dd,$wd)=();
1937     if ($M) {
1938     $yd = $M/12;
1939     $Md = 0;
1940     } else {
1941     $yd = ($w*$wl + $d*$dl + $h*$hl + $m*$ml + $s*$sl)/$yl;
1942     $Md = 0;
1943     }
1944    
1945     $wd = ($d*$dl + $h*$hl + $m*$ml + $s*$sl)/$wl;
1946     $dd = ($h*$hl + $m*$ml + $s*$sl)/$dl;
1947     $hd = ($m*$ml + $s*$sl)/$hl;
1948     $md = ($s*$sl)/$ml;
1949     $sd = 0;
1950    
1951     # The amount of each unit contained in higher units.
1952     my($yh,$Mh,$sh,$mh,$hh,$dh,$wh)=();
1953     $yh = 0;
1954    
1955     if ($M) {
1956     $Mh = ($yh+$y)*12;
1957     $wh = 0;
1958     $dh = ($wh+$w)*7;
1959     } else {
1960     $Mh = 0;
1961     $wh = ($yh+$y)*365.25/7;
1962     $dh = ($yh+$y)*365.25 + $w*7;
1963     }
1964    
1965     $hh = ($dh+$d)*24;
1966     $mh = ($hh+$h)*60;
1967     $sh = ($mh+$m)*60;
1968    
1969     # Set up the formats
1970    
1971     $f{"yv"} = $y;
1972     $f{"Mv"} = $M;
1973     $f{"wv"} = $w;
1974     $f{"dv"} = $d;
1975     $f{"hv"} = $h;
1976     $f{"mv"} = $m;
1977     $f{"sv"} = $s;
1978    
1979     $f{"yh"} = $y+$yh;
1980     $f{"Mh"} = $M+$Mh;
1981     $f{"wh"} = $w+$wh;
1982     $f{"dh"} = $d+$dh;
1983     $f{"hh"} = $h+$hh;
1984     $f{"mh"} = $m+$mh;
1985     $f{"sh"} = $s+$sh;
1986    
1987     $f{"yd"} = sprintf($dec,$y+$yd);
1988     $f{"Md"} = sprintf($dec,$M+$Md);
1989     $f{"wd"} = sprintf($dec,$w+$wd);
1990     $f{"dd"} = sprintf($dec,$d+$dd);
1991     $f{"hd"} = sprintf($dec,$h+$hd);
1992     $f{"md"} = sprintf($dec,$m+$md);
1993     $f{"sd"} = sprintf($dec,$s+$sd);
1994    
1995     $f{"yt"} = sprintf($dec,$yh+$y+$yd);
1996     $f{"Mt"} = sprintf($dec,$Mh+$M+$Md);
1997     $f{"wt"} = sprintf($dec,$wh+$w+$wd);
1998     $f{"dt"} = sprintf($dec,$dh+$d+$dd);
1999     $f{"ht"} = sprintf($dec,$hh+$h+$hd);
2000     $f{"mt"} = sprintf($dec,$mh+$m+$md);
2001     $f{"st"} = sprintf($dec,$sh+$s+$sd);
2002    
2003     $f{"%"} = "%";
2004    
2005     foreach $format (@format) {
2006     $format=reverse($format);
2007     $out="";
2008     PARSE: while ($format) {
2009     $c1=chop($format);
2010     if ($c1 eq "%") {
2011     $c1=chop($format);
2012     if (exists($f{$c1})) {
2013     $out .= $f{$c1};
2014     next PARSE;
2015     }
2016     $c2=chop($format);
2017     if (exists($f{"$c1$c2"})) {
2018     $out .= $f{"$c1$c2"};
2019     next PARSE;
2020     }
2021     $out .= $c1;
2022     $format .= $c2;
2023     } else {
2024     $out .= $c1;
2025     }
2026     }
2027     push(@out,$out);
2028     }
2029     if ($scalar) {
2030     return $out[0];
2031     } else {
2032     return (@out);
2033     }
2034     }
2035     use integer;
2036    
2037     sub ParseRecur {
2038     print "DEBUG: ParseRecur\n" if ($Curr{"Debug"} =~ /trace/);
2039     &Date_Init() if (! $Curr{"InitDone"});
2040    
2041     my($recur,$dateb,$date0,$date1,$flag)=@_;
2042     local($_)=$recur;
2043    
2044     my($recur_0,$recur_1,@recur0,@recur1)=();
2045     my(@tmp,$tmp,$each,$num,$y,$m,$d,$w,$h,$mn,$s,$delta,$y0,$y1,$yb)=();
2046     my($yy,$n,$dd,@d,@tmp2,$date,@date,@w,@tmp3,@m,@y,$tmp2,$d2,@flags)=();
2047    
2048     # $date0, $date1, $dateb, $flag : passed in (these are always the final say
2049     # in determining whether a date matches a
2050     # recurrence IF they are present.
2051     # $date_b, $date_0, $date_1 : if a value can be determined from the
2052     # $flag_t recurrence, they are stored here.
2053     #
2054     # If values can be determined from the recurrence AND are passed in, the
2055     # following are used:
2056     # max($date0,$date_0) i.e. the later of the two dates
2057     # min($date1,$date_1) i.e. the earlier of the two dates
2058     #
2059     # The base date that is used is the first one defined from
2060     # $dateb $date_b
2061     # The base date is only used if necessary (as determined by the recur).
2062     # For example, "every other friday" requires a base date, but "2nd
2063     # friday of every month" doesn't.
2064    
2065     my($date_b,$date_0,$date_1,$flag_t);
2066    
2067     #
2068     # Check the arguments passed in.
2069     #
2070    
2071     $date0="" if (! defined $date0);
2072     $date1="" if (! defined $date1);
2073     $dateb="" if (! defined $dateb);
2074     $flag ="" if (! defined $flag);
2075    
2076     if ($dateb) {
2077     $dateb=&ParseDateString($dateb);
2078     return "" if (! $dateb);
2079     }
2080     if ($date0) {
2081     $date0=&ParseDateString($date0);
2082     return "" if (! $date0);
2083     }
2084     if ($date1) {
2085     $date1=&ParseDateString($date1);
2086     return "" if (! $date1);
2087     }
2088    
2089     #
2090     # Parse the recur. $date_b, $date_0, and $date_e are values obtained
2091     # from the recur.
2092     #
2093    
2094     @tmp=&Recur_Split($_);
2095    
2096     if (@tmp) {
2097     ($recur_0,$recur_1,$flag_t,$date_b,$date_0,$date_1)=@tmp;
2098     $recur_0 = "" if (! defined $recur_0);
2099     $recur_1 = "" if (! defined $recur_1);
2100     $flag_t = "" if (! defined $flag_t);
2101     $date_b = "" if (! defined $date_b);
2102     $date_0 = "" if (! defined $date_0);
2103     $date_1 = "" if (! defined $date_1);
2104    
2105     @recur0 = split(/:/,$recur_0);
2106     @recur1 = split(/:/,$recur_1);
2107     return "" if ($#recur0 + $#recur1 + 2 != 7);
2108    
2109     if ($date_b) {
2110     $date_b=&ParseDateString($date_b);
2111     return "" if (! $date_b);
2112     }
2113     if ($date_0) {
2114     $date_0=&ParseDateString($date_0);
2115     return "" if (! $date_0);
2116     }
2117     if ($date_1) {
2118     $date_1=&ParseDateString($date_1);
2119     return "" if (! $date_1);
2120     }
2121    
2122     } else {
2123    
2124     my($mmm)='\s*'.$Lang{$Cnf{"Language"}}{"Month"}; # \s*(jan|january|...)
2125     my(%mmm)=%{ $Lang{$Cnf{"Language"}}{"MonthH"} }; # { jan=>1, ... }
2126     my($wkexp)='\s*'.$Lang{$Cnf{"Language"}}{"Week"}; # \s*(mon|monday|...)
2127     my(%week)=%{ $Lang{$Cnf{"Language"}}{"WeekH"} }; # { monday=>1, ... }
2128     my($day)='\s*'.$Lang{$Cnf{"Language"}}{"Dabb"}; # \s*(?:d|day|days)
2129     my($month)='\s*'.$Lang{$Cnf{"Language"}}{"Mabb"}; # \s*(?:mon|month|months)
2130     my($week)='\s*'.$Lang{$Cnf{"Language"}}{"Wabb"}; # \s*(?:w|wk|week|weeks)
2131     my($daysexp)=$Lang{$Cnf{"Language"}}{"DoM"}; # (1st|first|...31st)
2132     my(%dayshash)=%{ $Lang{$Cnf{"Language"}}{"DoMH"} };
2133     # { 1st=>1,first=>1,...}
2134     my($of)='\s*'.$Lang{$Cnf{"Language"}}{"Of"}; # \s*(?:in|of)
2135     my($lastexp)=$Lang{$Cnf{"Language"}}{"Last"}; # (?:last)
2136     my($each)=$Lang{$Cnf{"Language"}}{"Each"}; # (?:each|every)
2137    
2138     my($D)='\s*(\d+)';
2139     my($Y)='\s*(\d{4}|\d{2})';
2140    
2141     # Change 1st to 1
2142     if (/(^|[^a-z])$daysexp($|[^a-z])/i) {
2143     $tmp=lc($2);
2144     $tmp=$dayshash{"$tmp"};
2145     s/(^|[^a-z])$daysexp($|[^a-z])/$1 $tmp $3/i;
2146     }
2147     s/\s*$//;
2148    
2149     # Get rid of "each"
2150     if (/(^|[^a-z])$each($|[^a-z])/i) {
2151     s/(^|[^a-z])$each($|[^a-z])/$1 $2/i;
2152     $each=1;
2153     } else {
2154     $each=0;
2155     }
2156    
2157     if ($each) {
2158    
2159     if (/^$D?$day(?:$of$mmm?$Y)?$/i ||
2160     /^$D?$day(?:$of$mmm())?$/i) {
2161     # every [2nd] day in [june] 1997
2162     # every [2nd] day [in june]
2163     ($num,$m,$y)=($1,$2,$3);
2164     $num=1 if (! defined $num);
2165     $m="" if (! defined $m);
2166     $y="" if (! defined $y);
2167    
2168     $y=$Curr{"Y"} if (! $y);
2169     if ($m) {
2170     $m=$mmm{lc($m)};
2171     $date_0=&Date_Join($y,$m,1,0,0,0);
2172     $date_1=&DateCalc_DateDelta($date_0,"+0:1:0:0:0:0:0",0);
2173     } else {
2174     $date_0=&Date_Join($y, 1,1,0,0,0);
2175     $date_1=&Date_Join($y+1,1,1,0,0,0);
2176     }
2177     $date_b=&DateCalc($date_0,"-0:0:0:1:0:0:0",0);
2178     @recur0=(0,0,0,$num,0,0,0);
2179     @recur1=();
2180    
2181     } elsif (/^$D$day?$of$month(?:$of?$Y)?$/) {
2182     # 2nd [day] of every month [in 1997]
2183     ($num,$y)=($1,$2);
2184     $y=$Curr{"Y"} if (! $y);
2185    
2186     $date_0=&Date_Join($y, 1,1,0,0,0);
2187     $date_1=&Date_Join($y+1,1,1,0,0,0);
2188     $date_b=$date_0;
2189    
2190     @recur0=(0,1,0);
2191     @recur1=($num,0,0,0);
2192    
2193     } elsif (/^$D$wkexp$of$month(?:$of?$Y)?$/ ||
2194     /^($lastexp)$wkexp$of$month(?:$of?$Y)?$/) {
2195     # 2nd tuesday of every month [in 1997]
2196     # last tuesday of every month [in 1997]
2197     ($num,$d,$y)=($1,$2,$3);
2198     $y=$Curr{"Y"} if (! $y);
2199     $d=$week{lc($d)};
2200     $num=-1 if ($num !~ /^$D$/);
2201    
2202     $date_0=&Date_Join($y,1,1,0,0,0);
2203     $date_1=&Date_Join($y+1,1,1,0,0,0);
2204     $date_b=$date_0;
2205    
2206     @recur0=(0,1);
2207     @recur1=($num,$d,0,0,0);
2208    
2209     } elsif (/^$D?$wkexp(?:$of$mmm?$Y)?$/i ||
2210     /^$D?$wkexp(?:$of$mmm())?$/i) {
2211     # every tuesday in june 1997
2212     # every 2nd tuesday in june 1997
2213     ($num,$d,$m,$y)=($1,$2,$3,$4);
2214     $y=$Curr{"Y"} if (! $y);
2215     $num=1 if (! defined $num);
2216     $m="" if (! defined $m);
2217     $d=$week{lc($d)};
2218    
2219     if ($m) {
2220     $m=$mmm{lc($m)};
2221     $date_0=&Date_Join($y,$m,1,0,0,0);
2222     $date_1=&DateCalc_DateDelta($date_0,"+0:1:0:0:0:0:0",0);
2223     } else {
2224     $date_0=&Date_Join($y,1,1,0,0,0);
2225     $date_1=&Date_Join($y+1,1,1,0,0,0);
2226     }
2227     $date_b=&DateCalc($date_0,"-0:0:0:1:0:0:0",0);
2228    
2229     @recur0=(0,0,$num);
2230     @recur1=($d,0,0,0);
2231    
2232     } else {
2233     return "";
2234     }
2235    
2236     $date_0="" if ($date0);
2237     $date_1="" if ($date1);
2238     } else {
2239     return "";
2240     }
2241     }
2242    
2243     #
2244     # Override with any values passed in
2245     #
2246    
2247     if ($date0 && $date_0) {
2248     $date0=( &Date_Cmp($date0,$date_0) > 1 ? $date0 : $date_0);
2249     } elsif ($date_0) {
2250     $date0 = $date_0;
2251     }
2252    
2253     if ($date1 && $date_1) {
2254     $date1=( &Date_Cmp($date1,$date_1) > 1 ? $date_1 : $date1);
2255     } elsif ($date_1) {
2256     $date1 = $date_1;
2257     }
2258    
2259     $dateb=$date_b if (! $dateb);
2260    
2261     if ($flag =~ s/^\+//) {
2262     if ($flag_t) {
2263     $flag="$flag_t,$flag";
2264     }
2265     }
2266     $flag =$flag_t if (! $flag && $flag_t);
2267    
2268     if (! wantarray) {
2269     $tmp = join(":",@recur0);
2270     $tmp .= "*" . join(":",@recur1) if (@recur1);
2271     $tmp .= "*$flag*$dateb*$date0*$date1";
2272     return $tmp;
2273     }
2274     if (@recur0) {
2275     return () if (! $date0 || ! $date1); # dateb is NOT required in all case
2276     }
2277    
2278     #
2279     # Some flags affect parsing.
2280     #
2281    
2282     @flags = split(/,/,$flag);
2283     my($MDn) = 0;
2284     my($MWn) = 7;
2285     my($f);
2286     foreach $f (@flags) {
2287     if ($f =~ /^MW([1-7])$/i) {
2288     $MWn=$1;
2289     $MDn=0;
2290    
2291     } elsif ($f =~ /^MD([1-7])$/i) {
2292     $MDn=$1;
2293     $MWn=0;
2294    
2295     } elsif ($f =~ /^EASTER$/i) {
2296     ($y,$m,$w,$d,$h,$mn,$s)=(@recur0,@recur1);
2297     # We want something that will return Jan 1 for the given years.
2298     if ($#recur0==-1) {
2299     @recur1=($y,1,0,1,$h,$mn,$s);
2300     } elsif ($#recur0<=3) {
2301     @recur0=($y,0,0,0);
2302     @recur1=($h,$mn,$s);
2303     } elsif ($#recur0==4) {
2304     @recur0=($y,0,0,0,0);
2305     @recur1=($mn,$s);
2306     } elsif ($#recur0==5) {
2307     @recur0=($y,0,0,0,0,0);
2308     @recur1=($s);
2309     } else {
2310     @recur0=($y,0,0,0,0,0,0);
2311     }
2312     }
2313     }
2314    
2315     #
2316     # Determine the dates referenced by the recur. Also, fix the base date
2317     # as necessary for the recurrences which require it.
2318     #
2319    
2320     ($y,$m,$w,$d,$h,$mn,$s)=(@recur0,@recur1);
2321     @y=@m=@w=@d=();
2322     my(@time)=($h,$mn,$s);
2323    
2324     RECUR: while (1) {
2325    
2326     if ($#recur0==-1) {
2327     # * Y-M-W-D-H-MN-S
2328     if ($y eq "0") {
2329     push(@recur0,0);
2330     shift(@recur1);
2331    
2332     } else {
2333     @y=&ReturnList($y);
2334     foreach $y (@y) {
2335     $y=&Date_FixYear($y) if (length($y)==2);
2336     return () if (length($y)!=4 || ! &IsInt($y));
2337     }
2338     @y=sort { $a<=>$b } @y;
2339    
2340     $date0=&ParseDate("0000-01-01") if (! $date0);
2341     $date1=&ParseDate("9999-12-31 23:59:59") if (! $date1);
2342    
2343     if ($m eq "0" and $w eq "0") {
2344     # * Y-0-0-0-H-MN-S
2345     # * Y-0-0-DOY-H-MN-S
2346     if ($d eq "0") {
2347     @d=(1);
2348     } else {
2349     @d=&ReturnList($d);
2350     return () if (! @d);
2351     foreach $d (@d) {
2352     return () if (! &IsInt($d,1,366));
2353     }
2354     @d=sort { $a<=>$b } (@d);
2355     }
2356    
2357     @date=();
2358     foreach $yy (@y) {
2359     foreach $d (@d) {
2360     ($y,$m,$dd)=&Date_NthDayOfYear($yy,$d);
2361     push(@date, &Date_Join($y,$m,$dd,0,0,0));
2362     }
2363     }
2364     last RECUR;
2365    
2366     } elsif ($w eq "0") {
2367     # * Y-M-0-0-H-MN-S
2368     # * Y-M-0-DOM-H-MN-S
2369    
2370     @m=&ReturnList($m);
2371     return () if (! @m);
2372     foreach $m (@m) {
2373     return () if (! &IsInt($m,1,12));
2374     }
2375     @m=sort { $a<=>$b } (@m);
2376    
2377     if ($d eq "0") {
2378     @d=(1);
2379     } else {
2380     @d=&ReturnList($d);
2381     return () if (! @d);
2382     foreach $d (@d) {
2383     return () if (! &IsInt($d,1,31));
2384     }
2385     @d=sort { $a<=>$b } (@d);
2386     }
2387    
2388     @date=();
2389     foreach $y (@y) {
2390     foreach $m (@m) {
2391     foreach $d (@d) {
2392     $date=&Date_Join($y,$m,$d,0,0,0);
2393     push(@date,$date) if ($d<29 || &Date_Split($date));
2394     }
2395     }
2396     }
2397     last RECUR;
2398    
2399     } elsif ($m eq "0") {
2400     # * Y-0-WOY-DOW-H-MN-S
2401     # * Y-0-WOY-0-H-MN-S
2402     @w=&ReturnList($w);
2403     return () if (! @w);
2404     foreach $w (@w) {
2405     return () if (! &IsInt($w,1,53));
2406     }
2407    
2408     if ($d eq "0") {
2409     @d=($Cnf{"FirstDay"});
2410     } else {
2411     @d=&ReturnList($d);
2412     return () if (! @d);
2413     foreach $d (@d) {
2414     return () if (! &IsInt($d,1,7));
2415     }
2416     @d=sort { $a<=>$b } (@d);
2417     }
2418    
2419     @date=();
2420     foreach $y (@y) {
2421     foreach $w (@w) {
2422     $w="0$w" if (length($w)==1);
2423     foreach $d (@d) {
2424     $date=&ParseDateString("$y-W$w-$d");
2425     push(@date,$date);
2426     }
2427     }
2428     }
2429     last RECUR;
2430    
2431     } else {
2432     # * Y-M-WOM-DOW-H-MN-S
2433     # * Y-M-WOM-0-H-MN-S
2434    
2435     @m=&ReturnList($m);
2436     return () if (! @m);
2437     foreach $m (@m) {
2438     return () if (! &IsInt($m,1,12));
2439     }
2440     @m=sort { $a<=>$b } (@m);
2441    
2442     @w=&ReturnList($w);
2443    
2444     if ($d eq "0") {
2445     @d=();
2446     } else {
2447     @d=&ReturnList($d);
2448     }
2449    
2450     @date=&Date_Recur_WoM(\@y,\@m,\@w,\@d,$MWn,$MDn);
2451     last RECUR;
2452     }
2453     }
2454     }
2455    
2456     if ($#recur0==0) {
2457     # Y * M-W-D-H-MN-S
2458     $n=$y;
2459     $n=1 if ($n==0);
2460    
2461     @m=&ReturnList($m);
2462     return () if (! @m);
2463     foreach $m (@m) {
2464     return () if (! &IsInt($m,1,12));
2465     }
2466     @m=sort { $a<=>$b } (@m);
2467    
2468     if ($m eq "0") {
2469     # Y * 0-W-D-H-MN-S (equiv to Y-0 * W-D-H-MN-S)
2470     push(@recur0,0);
2471     shift(@recur1);
2472    
2473     } elsif ($w eq "0") {
2474     # Y * M-0-DOM-H-MN-S
2475     return () if (! $dateb);
2476     $d=1 if ($d eq "0");
2477    
2478     @d=&ReturnList($d);
2479     return () if (! @d);
2480     foreach $d (@d) {
2481     return () if (! &IsInt($d,1,31));
2482     }
2483     @d=sort { $a<=>$b } (@d);
2484    
2485     # We need to find years that are a multiple of $n from $y(base)
2486     ($y0)=( &Date_Split($date0, 1) )[0];
2487     ($y1)=( &Date_Split($date1, 1) )[0];
2488     ($yb)=( &Date_Split($dateb, 1) )[0];
2489     @date=();
2490     for ($yy=$y0; $yy<=$y1; $yy++) {
2491     if (($yy-$yb)%$n == 0) {
2492     foreach $m (@m) {
2493     foreach $d (@d) {
2494     $date=&Date_Join($yy,$m,$d,0,0,0);
2495     push(@date,$date) if ($d<29 || &Date_Split($date));
2496     }
2497     }
2498     }
2499     }
2500     last RECUR;
2501    
2502     } else {
2503     # Y * M-WOM-DOW-H-MN-S
2504     # Y * M-WOM-0-H-MN-S
2505     return () if (! $dateb);
2506     @m=&ReturnList($m);
2507     @w=&ReturnList($w);
2508     if ($d eq "0") {
2509     @d=();
2510     } else {
2511     @d=&ReturnList($d);
2512     }
2513    
2514     ($y0)=( &Date_Split($date0, 1) )[0];
2515     ($y1)=( &Date_Split($date1, 1) )[0];
2516     ($yb)=( &Date_Split($dateb, 1) )[0];
2517     @y=();
2518     for ($yy=$y0; $yy<=$y1; $yy++) {
2519     if (($yy-$yb)%$n == 0) {
2520     push(@y,$yy);
2521     }
2522     }
2523    
2524     @date=&Date_Recur_WoM(\@y,\@m,\@w,\@d,$MWn,$MDn);
2525     last RECUR;
2526     }
2527     }
2528    
2529     if ($#recur0==1) {
2530     # Y-M * W-D-H-MN-S
2531    
2532     if ($w eq "0") {
2533     # Y-M * 0-D-H-MN-S (equiv to Y-M-0 * D-H-MN-S)
2534     push(@recur0,0);
2535     shift(@recur1);
2536    
2537     } elsif ($m==0) {
2538     # Y-0 * WOY-0-H-MN-S
2539     # Y-0 * WOY-DOW-H-MN-S
2540     return () if (! $dateb);
2541     $n=$y;
2542     $n=1 if ($n==0);
2543    
2544     @w=&ReturnList($w);
2545     return () if (! @w);
2546     foreach $w (@w) {
2547     return () if (! &IsInt($w,1,53));
2548     }
2549    
2550     if ($d eq "0") {
2551     @d=($Cnf{"FirstDay"});
2552     } else {
2553     @d=&ReturnList($d);
2554     return () if (! @d);
2555     foreach $d (@d) {
2556     return () if (! &IsInt($d,1,7));
2557     }
2558     @d=sort { $a<=>$b } (@d);
2559     }
2560    
2561     # We need to find years that are a multiple of $n from $y(base)
2562     ($y0)=( &Date_Split($date0, 1) )[0];
2563     ($y1)=( &Date_Split($date1, 1) )[0];
2564     ($yb)=( &Date_Split($dateb, 1) )[0];
2565     @date=();
2566     for ($yy=$y0; $yy<=$y1; $yy++) {
2567     if (($yy-$yb)%$n == 0) {
2568     foreach $w (@w) {
2569     $w="0$w" if (length($w)==1);
2570     foreach $tmp (@d) {
2571     $date=&ParseDateString("$yy-W$w-$tmp");
2572     push(@date,$date);
2573     }
2574     }
2575     }
2576     }
2577     last RECUR;
2578    
2579     } else {
2580     # Y-M * WOM-0-H-MN-S
2581     # Y-M * WOM-DOW-H-MN-S
2582     return () if (! $dateb);
2583     @tmp=(@recur0);
2584     push(@tmp,0) while ($#tmp<6);
2585     $delta=join(":",@tmp);
2586     @tmp=&Date_Recur($date0,$date1,$dateb,$delta);
2587    
2588     @w=&ReturnList($w);
2589     @m=();
2590     if ($d eq "0") {
2591     @d=();
2592     } else {
2593     @d=&ReturnList($d);
2594     }
2595    
2596     @date=&Date_Recur_WoM(\@tmp,\@m,\@w,\@d,$MWn,$MDn);
2597     last RECUR;
2598     }
2599     }
2600    
2601     if ($#recur0==2) {
2602     # Y-M-W * D-H-MN-S
2603    
2604     if ($d eq "0") {
2605     # Y-M-W * 0-H-MN-S
2606     return () if (! $dateb);
2607     $y=1 if ($y==0 && $m==0 && $w==0);
2608     $delta="$y:$m:$w:0:0:0:0";
2609     @date=&Date_Recur($date0,$date1,$dateb,$delta);
2610     last RECUR;
2611    
2612     } elsif ($m==0 && $w==0) {
2613     # Y-0-0 * DOY-H-MN-S
2614     $y=1 if ($y==0);
2615     $n=$y;
2616     return () if (! $dateb && $y!=1);
2617    
2618     @d=&ReturnList($d);
2619     return () if (! @d);
2620     foreach $d (@d) {
2621     return () if (! &IsInt($d,1,366));
2622     }
2623     @d=sort { $a<=>$b } (@d);
2624    
2625     # We need to find years that are a multiple of $n from $y(base)
2626     ($y0)=( &Date_Split($date0, 1) )[0];
2627     ($y1)=( &Date_Split($date1, 1) )[0];
2628     ($yb)=( &Date_Split($dateb, 1) )[0];
2629     @date=();
2630     for ($yy=$y0; $yy<=$y1; $yy++) {
2631     if (($yy-$yb)%$n == 0) {
2632     foreach $d (@d) {
2633     ($y,$m,$dd)=&Date_NthDayOfYear($yy,$d);
2634     push(@date, &Date_Join($y,$m,$dd,0,0,0));
2635     }
2636     }
2637     }
2638     last RECUR;
2639    
2640     } elsif ($w>0) {
2641     # Y-M-W * DOW-H-MN-S
2642     return () if (! $dateb);
2643     @tmp=(@recur0);
2644     push(@tmp,0) while ($#tmp<6);
2645     $delta=join(":",@tmp);
2646    
2647     @d=&ReturnList($d);
2648     return () if (! @d);
2649     foreach $d (@d) {
2650     return () if (! &IsInt($d,1,7));
2651     }
2652    
2653     # Find out what DofW the basedate is.
2654     @tmp2=&Date_Split($dateb, 1);
2655     $tmp=&Date_DayOfWeek($tmp2[1],$tmp2[2],$tmp2[0]);
2656    
2657     @date=();
2658     foreach $d (@d) {
2659     $date_b=$dateb;
2660     # Move basedate to DOW
2661     if ($d != $tmp) {
2662     if (($tmp>=$Cnf{"FirstDay"} && $d<$Cnf{"FirstDay"}) ||
2663     ($tmp>=$Cnf{"FirstDay"} && $d>$tmp) ||
2664     ($tmp<$d && $d<$Cnf{"FirstDay"})) {
2665     $date_b=&Date_GetNext($date_b,$d);
2666     } else {
2667     $date_b=&Date_GetPrev($date_b,$d);
2668     }
2669     }
2670     push(@date,&Date_Recur($date0,$date1,$date_b,$delta));
2671     }
2672     @date=sort(@date);
2673     last RECUR;
2674    
2675     } elsif ($m>0) {
2676     # Y-M-0 * DOM-H-MN-S
2677     return () if (! $dateb);
2678     @tmp=(@recur0);
2679     push(@tmp,0) while ($#tmp<6);
2680     $delta=join(":",@tmp);
2681    
2682     @d=&ReturnList($d);
2683     return () if (! @d);
2684     foreach $d (@d) {
2685     return () if (! &IsInt($d,-31,31) || $d==0);
2686     }
2687     @d=sort { $a<=>$b } (@d);
2688    
2689     @tmp2=&Date_Recur($date0,$date1,$dateb,$delta);
2690     @date=();
2691     foreach $date (@tmp2) {
2692     ($y,$m)=( &Date_Split($date, 1) )[0..1];
2693     $tmp2=&Date_DaysInMonth($m,$y);
2694     foreach $d (@d) {
2695     $d2=$d;
2696     $d2=$tmp2+1+$d if ($d<0);
2697     push(@date,&Date_Join($y,$m,$d2,0,0,0)) if ($d2<=$tmp2);
2698     }
2699     }
2700     @date=sort (@date);
2701     last RECUR;
2702    
2703     } else {
2704     return ();
2705     }
2706     }
2707    
2708     if ($#recur0>2) {
2709     # Y-M-W-D * H-MN-S
2710     # Y-M-W-D-H * MN-S
2711     # Y-M-W-D-H-MN * S
2712     # Y-M-W-D-H-S
2713     return () if (! $dateb);
2714     @tmp=(@recur0);
2715     push(@tmp,0) while ($#tmp<6);
2716     $delta=join(":",@tmp);
2717     return () if ($delta !~ /[1-9]/); # return if "0:0:0:0:0:0:0"
2718     @date=&Date_Recur($date0,$date1,$dateb,$delta);
2719     if (@recur1) {
2720     unshift(@recur1,-1) while ($#recur1<2);
2721     @time=@recur1;
2722     } else {
2723     shift(@date);
2724     pop(@date);
2725     @time=();
2726     }
2727     }
2728    
2729     last RECUR;
2730     }
2731     @date=&Date_RecurSetTime($date0,$date1,\@date,@time) if (@time);
2732    
2733     #
2734     # We've got a list of dates. Operate on them with the flags.
2735     #
2736    
2737     my($sign,$forw,$today,$df,$db,$work,$i);
2738     if (@flags) {
2739     FLAG: foreach $f (@flags) {
2740     $f = uc($f);
2741    
2742     if ($f =~ /^(P|N)(D|T)([1-7])$/) {
2743     @tmp=($1,$2,$3);
2744     $forw =($tmp[0] eq "P" ? 0 : 1);
2745     $today=($tmp[1] eq "D" ? 0 : 1);
2746     $d=$tmp[2];
2747     @tmp=();
2748     foreach $date (@date) {
2749     if ($forw) {
2750     push(@tmp, &Date_GetNext($date,$d,$today));
2751     } else {
2752     push(@tmp, &Date_GetPrev($date,$d,$today));
2753     }
2754     }
2755     @date=@tmp;
2756     next FLAG;
2757     }
2758    
2759     # We want to go forward exact amounts of time instead of
2760     # business mode calculations so that we don't change the time
2761     # (which may have been set in the recur).
2762     if ($f =~ /^(F|B)(D|W)(\d+)$/) {
2763     @tmp=($1,$2,$3);
2764     $sign="+";
2765     $sign="-" if ($tmp[0] eq "B");
2766     $work=0;
2767     $work=1 if ($tmp[1] eq "W");
2768     $n=$tmp[2];
2769     @tmp=();
2770     foreach $date (@date) {
2771     for ($i=1; $i<=$n; $i++) {
2772     while (1) {
2773     $date=&DateCalc($date,"${sign}0:0:0:1:0:0:0");
2774     last if (! $work || &Date_IsWorkDay($date,0));
2775     }
2776     }
2777     push(@tmp,$date);
2778     }
2779     @date=@tmp;
2780     next FLAG;
2781     }
2782    
2783     if ($f =~ /^CW(N|P|D)$/ || $f =~ /^(N|P|D)W(D)$/) {
2784     $tmp=$1;
2785     my $noalt = $2 ? 1 : 0;
2786     if ($tmp eq "N" || ($tmp eq "D" && $Cnf{"TomorrowFirst"})) {
2787     $forw=1;
2788     } else {
2789     $forw=0;
2790     }
2791    
2792     @tmp=();
2793     DATE: foreach $date (@date) {
2794     $df=$db=$date;
2795     if (&Date_IsWorkDay($date)) {
2796     push(@tmp,$date);
2797     next DATE;
2798     }
2799     while (1) {
2800     if ($forw) {
2801     $d=$df=&DateCalc($df,"+0:0:0:1:0:0:0");
2802     } else {
2803     $d=$db=&DateCalc($db,"-0:0:0:1:0:0:0");
2804     }
2805     if (&Date_IsWorkDay($d)) {
2806     push(@tmp,$d);
2807     next DATE;
2808     }
2809     $forw=1-$forw if (! $noalt);
2810     }
2811     }
2812     @date=@tmp;
2813     next FLAG;
2814     }
2815    
2816     if ($f eq "EASTER") {
2817     @tmp=();
2818     foreach $date (@date) {
2819     ($y,$m,$d,$h,$mn,$s)=&Date_Split($date, 1);
2820     ($m,$d)=&Date_Easter($y);
2821     $date=&Date_Join($y,$m,$d,$h,$mn,$s);
2822     next if (&Date_Cmp($date,$date0)<0 ||
2823     &Date_Cmp($date,$date1)>0);
2824     push(@tmp,$date);
2825     }
2826     @date=@tmp;
2827     }
2828     }
2829     @date = sort(@date);
2830     }
2831     @date;
2832     }
2833    
2834     sub Date_GetPrev {
2835     print "DEBUG: Date_GetPrev\n" if ($Curr{"Debug"} =~ /trace/);
2836     my($date,$dow,$today,$hr,$min,$sec)=@_;
2837     &Date_Init() if (! $Curr{"InitDone"});
2838     my($y,$m,$d,$h,$mn,$s,$err,$curr_dow,%dow,$num,$delta,$th,$tm,$ts,
2839     $adjust,$curr)=();
2840     $hr="00" if (defined $hr && $hr eq "0");
2841     $min="00" if (defined $min && $min eq "0");
2842     $sec="00" if (defined $sec && $sec eq "0");
2843    
2844     if (! &Date_Split($date)) {
2845     $date=&ParseDateString($date);
2846     return "" if (! $date);
2847     }
2848     $curr=$date;
2849     ($y,$m,$d)=( &Date_Split($date, 1) )[0..2];
2850    
2851     if ($dow) {
2852     $curr_dow=&Date_DayOfWeek($m,$d,$y);
2853     %dow=%{ $Lang{$Cnf{"Language"}}{"WeekH"} };
2854     if (&IsInt($dow)) {
2855     return "" if ($dow<1 || $dow>7);
2856     } else {
2857     return "" if (! exists $dow{lc($dow)});
2858     $dow=$dow{lc($dow)};
2859     }
2860     if ($dow == $curr_dow) {
2861     $date=&DateCalc_DateDelta($date,"-0:0:1:0:0:0:0",\$err,0) if (! $today);
2862     $adjust=1 if ($today==2);
2863     } else {
2864     $dow -= 7 if ($dow>$curr_dow); # make sure previous day is less
2865     $num = $curr_dow - $dow;
2866     $date=&DateCalc_DateDelta($date,"-0:0:0:$num:0:0:0",\$err,0);
2867     }
2868     $date=&Date_SetTime($date,$hr,$min,$sec) if (defined $hr);
2869     $date=&DateCalc_DateDelta($date,"-0:0:1:0:0:0:0",\$err,0)
2870     if ($adjust && &Date_Cmp($date,$curr)>0);
2871    
2872     } else {
2873     ($h,$mn,$s)=( &Date_Split($date, 1) )[3..5];
2874     ($th,$tm,$ts)=&Date_ParseTime($hr,$min,$sec);
2875     if ($hr) {
2876     ($hr,$min,$sec)=($th,$tm,$ts);
2877     $delta="-0:0:0:1:0:0:0";
2878     } elsif ($min) {
2879     ($hr,$min,$sec)=($h,$tm,$ts);
2880     $delta="-0:0:0:0:1:0:0";
2881     } elsif ($sec) {
2882     ($hr,$min,$sec)=($h,$mn,$ts);
2883     $delta="-0:0:0:0:0:1:0";
2884     } else {
2885     confess "ERROR: invalid arguments in Date_GetPrev.\n";
2886     }
2887    
2888     $d=&Date_SetTime($date,$hr,$min,$sec);
2889     if ($today) {
2890     $d=&DateCalc_DateDelta($d,$delta,\$err,0) if (&Date_Cmp($d,$date)>0);
2891     } else {
2892     $d=&DateCalc_DateDelta($d,$delta,\$err,0) if (&Date_Cmp($d,$date)>=0);
2893     }
2894     $date=$d;
2895     }
2896     return $date;
2897     }
2898    
2899     sub Date_GetNext {
2900     print "DEBUG: Date_GetNext\n" if ($Curr{"Debug"} =~ /trace/);
2901     my($date,$dow,$today,$hr,$min,$sec)=@_;
2902     &Date_Init() if (! $Curr{"InitDone"});
2903     my($y,$m,$d,$h,$mn,$s,$err,$curr_dow,%dow,$num,$delta,$th,$tm,$ts,
2904     $adjust,$curr)=();
2905     $hr="00" if (defined $hr && $hr eq "0");
2906     $min="00" if (defined $min && $min eq "0");
2907     $sec="00" if (defined $sec && $sec eq "0");
2908    
2909     if (! &Date_Split($date)) {
2910     $date=&ParseDateString($date);
2911     return "" if (! $date);
2912     }
2913     $curr=$date;
2914     ($y,$m,$d)=( &Date_Split($date, 1) )[0..2];
2915    
2916     if ($dow) {
2917     $curr_dow=&Date_DayOfWeek($m,$d,$y);
2918     %dow=%{ $Lang{$Cnf{"Language"}}{"WeekH"} };
2919     if (&IsInt($dow)) {
2920     return "" if ($dow<1 || $dow>7);
2921     } else {
2922     return "" if (! exists $dow{lc($dow)});
2923     $dow=$dow{lc($dow)};
2924     }
2925     if ($dow == $curr_dow) {
2926     $date=&DateCalc_DateDelta($date,"+0:0:1:0:0:0:0",\$err,0) if (! $today);
2927     $adjust=1 if ($today==2);
2928     } else {
2929     $curr_dow -= 7 if ($curr_dow>$dow); # make sure next date is greater
2930     $num = $dow - $curr_dow;
2931     $date=&DateCalc_DateDelta($date,"+0:0:0:$num:0:0:0",\$err,0);
2932     }
2933     $date=&Date_SetTime($date,$hr,$min,$sec) if (defined $hr);
2934     $date=&DateCalc_DateDelta($date,"+0:0:1:0:0:0:0",\$err,0)
2935     if ($adjust && &Date_Cmp($date,$curr)<0);
2936    
2937     } else {
2938     ($h,$mn,$s)=( &Date_Split($date, 1) )[3..5];
2939     ($th,$tm,$ts)=&Date_ParseTime($hr,$min,$sec);
2940     if ($hr) {
2941     ($hr,$min,$sec)=($th,$tm,$ts);
2942     $delta="+0:0:0:1:0:0:0";
2943     } elsif ($min) {
2944     ($hr,$min,$sec)=($h,$tm,$ts);
2945     $delta="+0:0:0:0:1:0:0";
2946     } elsif ($sec) {
2947     ($hr,$min,$sec)=($h,$mn,$ts);
2948     $delta="+0:0:0:0:0:1:0";
2949     } else {
2950     confess "ERROR: invalid arguments in Date_GetNext.\n";
2951     }
2952    
2953     $d=&Date_SetTime($date,$hr,$min,$sec);
2954     if ($today) {
2955     $d=&DateCalc_DateDelta($d,$delta,\$err,0) if (&Date_Cmp($d,$date)<0);
2956     } else {
2957     $d=&DateCalc_DateDelta($d,$delta,\$err,0) if (&Date_Cmp($d,$date)<1);
2958     }
2959     $date=$d;
2960     }
2961    
2962     return $date;
2963     }
2964    
2965     sub Date_IsHoliday {
2966     print "DEBUG: Date_IsHoliday\n" if ($Curr{"Debug"} =~ /trace/);
2967     my($date)=@_;
2968     &Date_Init() if (! $Curr{"InitDone"});
2969     $date=&ParseDateString($date);
2970     return undef if (! $date);
2971     $date=&Date_SetTime($date,0,0,0);
2972     my($y)=(&Date_Split($date, 1))[0];
2973     &Date_UpdateHolidays($y) if (! exists $Holiday{"dates"}{$y});
2974     return undef if (! exists $Holiday{"dates"}{$y}{$date});
2975     my($name)=$Holiday{"dates"}{$y}{$date};
2976     return "" if (! $name);
2977     $name;
2978     }
2979    
2980     sub Events_List {
2981     print "DEBUG: Events_List\n" if ($Curr{"Debug"} =~ /trace/);
2982     my(@args)=@_;
2983     &Date_Init() if (! $Curr{"InitDone"});
2984     &Events_ParseRaw();
2985    
2986     my($tmp,$date0,$date1,$flag);
2987     $date0=&ParseDateString($args[0]);
2988     warn "Invalid date $args[0]", return undef if (! $date0);
2989    
2990     if ($#args == 0) {
2991     return &Events_Calc($date0);
2992     }
2993    
2994     if ($args[1]) {
2995     $date1=&ParseDateString($args[1]);
2996     warn "Invalid date $args[1]\n", return undef if (! $date1);
2997     if (&Date_Cmp($date0,$date1)>0) {
2998     $tmp=$date1;
2999     $date1=$date0;
3000     $date0=$tmp;
3001     }
3002     } else {
3003     $date0=&Date_SetTime($date0,"00:00:00");
3004     $date1=&DateCalc_DateDelta($date0,"+0:0:0:1:0:0:0");
3005     }
3006    
3007     $tmp=&Events_Calc($date0,$date1);
3008    
3009     $flag=$args[2];
3010     return $tmp if (! $flag);
3011    
3012     my(@tmp,%ret,$delta)=();
3013     @tmp=@$tmp;
3014     push(@tmp,$date1);
3015    
3016     if ($flag==1) {
3017     while ($#tmp>0) {
3018     ($date0,$tmp)=splice(@tmp,0,2);
3019     $date1=$tmp[0];
3020     $delta=&DateCalc_DateDate($date0,$date1);
3021     foreach $flag (@$tmp) {
3022     if (exists $ret{$flag}) {
3023     $ret{$flag}=&DateCalc_DeltaDelta($ret{$flag},$delta);
3024     } else {
3025     $ret{$flag}=$delta;
3026     }
3027     }
3028     }
3029     return \%ret;
3030    
3031     } elsif ($flag==2) {
3032     while ($#tmp>0) {
3033     ($date0,$tmp)=splice(@tmp,0,2);
3034     $date1=$tmp[0];
3035     $delta=&DateCalc_DateDate($date0,$date1);
3036     $flag=join("+",sort @$tmp);
3037     next if (! $flag);
3038     if (exists $ret{$flag}) {
3039     $ret{$flag}=&DateCalc_DeltaDelta($ret{$flag},$delta);
3040     } else {
3041     $ret{$flag}=$delta;
3042     }
3043     }
3044     return \%ret;
3045     }
3046    
3047     warn "Invalid flag $flag\n";
3048     return undef;
3049     }
3050    
3051     ###
3052     # NOTE: The following routines may be called in the routines below with very
3053     # little time penalty.
3054     ###
3055     sub Date_SetTime {
3056     print "DEBUG: Date_SetTime\n" if ($Curr{"Debug"} =~ /trace/);
3057     my($date,$h,$mn,$s)=@_;
3058     &Date_Init() if (! $Curr{"InitDone"});
3059     my($y,$m,$d)=();
3060    
3061     if (! &Date_Split($date)) {
3062     $date=&ParseDateString($date);
3063     return "" if (! $date);
3064     }
3065    
3066     ($y,$m,$d)=( &Date_Split($date, 1) )[0..2];
3067     ($h,$mn,$s)=&Date_ParseTime($h,$mn,$s);
3068    
3069     my($ampm,$wk);
3070     return "" if (&Date_DateCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk));
3071     &Date_Join($y,$m,$d,$h,$mn,$s);
3072     }
3073    
3074     sub Date_SetDateField {
3075     print "DEBUG: Date_SetDateField\n" if ($Curr{"Debug"} =~ /trace/);
3076     my($date,$field,$val,$nocheck)=@_;
3077     my($y,$m,$d,$h,$mn,$s)=();
3078     $nocheck=0 if (! defined $nocheck);
3079    
3080     ($y,$m,$d,$h,$mn,$s)=&Date_Split($date);
3081    
3082     if (! $y) {
3083     $date=&ParseDateString($date);
3084     return "" if (! $date);
3085     ($y,$m,$d,$h,$mn,$s)=&Date_Split($date, 1);
3086     }
3087    
3088     if (lc($field) eq "y") {
3089     $y=$val;
3090     } elsif (lc($field) eq "m") {
3091     $m=$val;
3092     } elsif (lc($field) eq "d") {
3093     $d=$val;
3094     } elsif (lc($field) eq "h") {
3095     $h=$val;
3096     } elsif (lc($field) eq "mn") {
3097     $mn=$val;
3098     } elsif (lc($field) eq "s") {
3099     $s=$val;
3100     } else {
3101     confess "ERROR: Date_SetDateField: invalid field: $field\n";
3102     }
3103    
3104     $date=&Date_Join($y,$m,$d,$h,$mn,$s);
3105     return $date if ($nocheck || &Date_Split($date));
3106     return "";
3107     }
3108    
3109     ########################################################################
3110     # OTHER SUBROUTINES
3111     ########################################################################
3112     # NOTE: These routines should not call any of the routines above as
3113     # there will be a severe time penalty (and the possibility of
3114     # infinite recursion). The last couple routines above are
3115     # exceptions.
3116     # NOTE: Date_Init is a special case. It should be called (conditionally)
3117     # in every routine that uses any variable from the Date::Manip
3118     # namespace.
3119     ########################################################################
3120    
3121     sub Date_DaysInMonth {
3122     print "DEBUG: Date_DaysInMonth\n" if ($Curr{"Debug"} =~ /trace/);
3123     my($m,$y)=@_;
3124     $y=&Date_FixYear($y) if (length($y)!=4);
3125     my(@d_in_m)=(0,31,28,31,30,31,30,31,31,30,31,30,31);
3126     $d_in_m[2]=29 if (&Date_LeapYear($y));
3127     return $d_in_m[$m];
3128     }
3129    
3130     sub Date_DayOfWeek {
3131     print "DEBUG: Date_DayOfWeek\n" if ($Curr{"Debug"} =~ /trace/);
3132     my($m,$d,$y)=@_;
3133     $y=&Date_FixYear($y) if (length($y)!=4);
3134     my($dayofweek,$dec31)=();
3135    
3136     $dec31=5; # Dec 31, 1BC was Friday
3137     $dayofweek=(&Date_DaysSince1BC($m,$d,$y)+$dec31) % 7;
3138     $dayofweek=7 if ($dayofweek==0);
3139     return $dayofweek;
3140     }
3141    
3142     # Can't be in "use integer" because the numbers are too big.
3143     no integer;
3144     sub Date_SecsSince1970 {
3145     print "DEBUG: Date_SecsSince1970\n" if ($Curr{"Debug"} =~ /trace/);
3146     my($m,$d,$y,$h,$mn,$s)=@_;
3147     $y=&Date_FixYear($y) if (length($y)!=4);
3148     my($sec_now,$sec_70)=();
3149     $sec_now=(&Date_DaysSince1BC($m,$d,$y)-1)*24*3600 + $h*3600 + $mn*60 + $s;
3150     # $sec_70 =(&Date_DaysSince1BC(1,1,1970)-1)*24*3600;
3151     $sec_70 =62167219200;
3152     return ($sec_now-$sec_70);
3153     }
3154    
3155     sub Date_SecsSince1970GMT {
3156     print "DEBUG: Date_SecsSince1970GMT\n" if ($Curr{"Debug"} =~ /trace/);
3157     my($m,$d,$y,$h,$mn,$s)=@_;
3158     &Date_Init() if (! $Curr{"InitDone"});
3159     $y=&Date_FixYear($y) if (length($y)!=4);
3160    
3161     my($sec)=&Date_SecsSince1970($m,$d,$y,$h,$mn,$s);
3162     return $sec if ($Cnf{"ConvTZ"} eq "IGNORE");
3163    
3164     my($tz)=$Cnf{"ConvTZ"};
3165     $tz=$Cnf{"TZ"} if (! $tz);
3166     $tz=$Zone{"n2o"}{lc($tz)} if ($tz !~ /^[+-]\d{4}$/);
3167    
3168     my($tzs)=1;
3169     $tzs=-1 if ($tz<0);
3170     $tz=~/.(..)(..)/;
3171     my($tzh,$tzm)=($1,$2);
3172     $sec - $tzs*($tzh*3600+$tzm*60);
3173     }
3174     use integer;
3175    
3176     sub Date_DaysSince1BC {
3177     print "DEBUG: Date_DaysSince1BC\n" if ($Curr{"Debug"} =~ /trace/);
3178     my($m,$d,$y)=@_;
3179     $y=&Date_FixYear($y) if (length($y)!=4);
3180     my($Ny,$N4,$N100,$N400,$dayofyear,$days)=();
3181     my($cc,$yy)=();
3182    
3183     $y=~ /(\d{2})(\d{2})/;
3184     ($cc,$yy)=($1,$2);
3185    
3186     # Number of full years since Dec 31, 1BC (counting the year 0000).
3187     $Ny=$y;
3188    
3189     # Number of full 4th years (incl. 0000) since Dec 31, 1BC
3190     $N4=($Ny-1)/4 + 1;
3191     $N4=0 if ($y==0);
3192    
3193     # Number of full 100th years (incl. 0000)
3194     $N100=$cc + 1;
3195     $N100-- if ($yy==0);
3196     $N100=0 if ($y==0);
3197    
3198     # Number of full 400th years (incl. 0000)
3199     $N400=($N100-1)/4 + 1;
3200     $N400=0 if ($y==0);
3201    
3202     $dayofyear=&Date_DayOfYear($m,$d,$y);
3203     $days= $Ny*365 + $N4 - $N100 + $N400 + $dayofyear;
3204    
3205     return $days;
3206     }
3207    
3208     sub Date_DayOfYear {
3209     print "DEBUG: Date_DayOfYear\n" if ($Curr{"Debug"} =~ /trace/);
3210     my($m,$d,$y)=@_;
3211     $y=&Date_FixYear($y) if (length($y)!=4);
3212     # DinM = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
3213     my(@days) = ( 0, 31, 59, 90,120,151,181,212,243,273,304,334,365);
3214     my($ly)=0;
3215     $ly=1 if ($m>2 && &Date_LeapYear($y));
3216     return ($days[$m-1]+$d+$ly);
3217     }
3218    
3219     sub Date_DaysInYear {
3220     print "DEBUG: Date_DaysInYear\n" if ($Curr{"Debug"} =~ /trace/);
3221     my($y)=@_;
3222     $y=&Date_FixYear($y) if (length($y)!=4);
3223     return 366 if (&Date_LeapYear($y));
3224     return 365;
3225     }
3226    
3227     sub Date_WeekOfYear {
3228     print "DEBUG: Date_WeekOfYear\n" if ($Curr{"Debug"} =~ /trace/);
3229     my($m,$d,$y,$f)=@_;
3230     &Date_Init() if (! $Curr{"InitDone"});
3231     $y=&Date_FixYear($y) if (length($y)!=4);
3232    
3233     my($day,$dow,$doy)=();
3234     $doy=&Date_DayOfYear($m,$d,$y);
3235    
3236     # The current DayOfYear and DayOfWeek
3237     if ($Cnf{"Jan1Week1"}) {
3238     $day=1;
3239     } else {
3240     $day=4;
3241     }
3242     $dow=&Date_DayOfWeek(1,$day,$y);
3243    
3244     # Move back to the first day of week 1.
3245     $f-=7 if ($f>$dow);
3246     $day-= ($dow-$f);
3247    
3248     return 0 if ($day>$doy); # Day is in last week of previous year
3249     return (($doy-$day)/7 + 1);
3250     }
3251    
3252     sub Date_LeapYear {
3253     print "DEBUG: Date_LeapYear\n" if ($Curr{"Debug"} =~ /trace/);
3254     my($y)=@_;
3255     $y=&Date_FixYear($y) if (length($y)!=4);
3256     return 0 unless $y % 4 == 0;
3257     return 1 unless $y % 100 == 0;
3258     return 0 unless $y % 400 == 0;
3259     return 1;
3260     }
3261    
3262     sub Date_DaySuffix {
3263     print "DEBUG: Date_DaySuffix\n" if ($Curr{"Debug"} =~ /trace/);
3264     my($d)=@_;
3265     &Date_Init() if (! $Curr{"InitDone"});
3266     return $Lang{$Cnf{"Language"}}{"DoML"}[$d-1];
3267     }
3268    
3269     sub Date_ConvTZ {
3270     print "DEBUG: Date_ConvTZ\n" if ($Curr{"Debug"} =~ /trace/);
3271     my($date,$from,$to)=@_;
3272     if (not Date_Split($date)) {
3273     croak "date passed in ('$date') is not a Date::Manip object";
3274     }
3275    
3276     &Date_Init() if (! $Curr{"InitDone"});
3277     my($gmt)=();
3278    
3279     if (! $from) {
3280    
3281     if (! $to) {
3282     # TZ -> ConvTZ
3283     return $date if ($Cnf{"ConvTZ"} eq "IGNORE" or ! $Cnf{"ConvTZ"});
3284     $from=$Cnf{"TZ"};
3285     $to=$Cnf{"ConvTZ"};
3286    
3287     } else {
3288     # ConvTZ,TZ -> $to
3289     $from=$Cnf{"ConvTZ"};
3290     $from=$Cnf{"TZ"} if (! $from);
3291     }
3292    
3293     } else {
3294    
3295     if (! $to) {
3296     # $from -> ConvTZ,TZ
3297     return $date if ($Cnf{"ConvTZ"} eq "IGNORE");
3298     $to=$Cnf{"ConvTZ"};
3299     $to=$Cnf{"TZ"} if (! $to);
3300    
3301     } else {
3302     # $from -> $to
3303     }
3304     }
3305    
3306     $to=$Zone{"n2o"}{lc($to)}
3307     if (exists $Zone{"n2o"}{lc($to)});
3308     $from=$Zone{"n2o"}{lc($from)}
3309     if (exists $Zone{"n2o"}{lc($from)});
3310     $gmt=$Zone{"n2o"}{"gmt"};
3311    
3312     return $date if ($from !~ /^[+-]\d{4}$/ or $to !~ /^[+-]\d{4}$/);
3313     return $date if ($from eq $to);
3314    
3315     my($s1,$h1,$m1,$s2,$h2,$m2,$d,$h,$m,$sign,$delta,$err,$yr,$mon,$sec)=();
3316     # We're going to try to do the calculation without calling DateCalc.
3317     ($yr,$mon,$d,$h,$m,$sec)=&Date_Split($date, 1);
3318    
3319     # Convert $date from $from to GMT
3320     $from=~/([+-])(\d{2})(\d{2})/;
3321     ($s1,$h1,$m1)=($1,$2,$3);
3322     $s1= ($s1 eq "-" ? "+" : "-"); # switch sign
3323     $sign=$s1 . "1"; # + or - 1
3324    
3325     # and from GMT to $to
3326     $to=~/([+-])(\d{2})(\d{2})/;
3327     ($s2,$h2,$m2)=($1,$2,$3);
3328    
3329     if ($s1 eq $s2) {
3330     # Both the same sign
3331     $m+= $sign*($m1+$m2);
3332     $h+= $sign*($h1+$h2);
3333     } else {
3334     $sign=($s2 eq "-" ? +1 : -1) if ($h1<$h2 || ($h1==$h2 && $m1<$m2));
3335     $m+= $sign*($m1-$m2);
3336     $h+= $sign*($h1-$h2);
3337     }
3338    
3339     if ($m>59) {
3340     $h+= $m/60;
3341     $m-= ($m/60)*60;
3342     } elsif ($m<0) {
3343     $h+= ($m/60 - 1);
3344     $m-= ($m/60 - 1)*60;
3345     }
3346    
3347     if ($h>23) {
3348     $delta=$h/24;
3349     $h -= $delta*24;
3350     if (($d + $delta) > 28) {
3351     $date=&Date_Join($yr,$mon,$d,$h,$m,$sec);
3352     return &DateCalc_DateDelta($date,"+0:0:0:$delta:0:0:0",\$err,0);
3353     }
3354     $d+= $delta;
3355     } elsif ($h<0) {
3356     $delta=-$h/24 + 1;
3357     $h += $delta*24;
3358     if (($d - $delta) < 1) {
3359     $date=&Date_Join($yr,$mon,$d,$h,$m,$sec);
3360     return &DateCalc_DateDelta($date,"-0:0:0:$delta:0:0:0",\$err,0);
3361     }
3362     $d-= $delta;
3363     }
3364     return &Date_Join($yr,$mon,$d,$h,$m,$sec);
3365     }
3366    
3367     sub Date_TimeZone {
3368     print "DEBUG: Date_TimeZone\n" if ($Curr{"Debug"} =~ /trace/);
3369     my($null,$tz,@tz,$std,$dst,$time,$isdst,$tmp,$in)=();
3370     &Date_Init() if (! $Curr{"InitDone"});
3371    
3372     # Get timezones from all of the relevant places
3373    
3374     push(@tz,$Cnf{"TZ"}) if (defined $Cnf{"TZ"}); # TZ config var
3375     push(@tz,$ENV{"TZ"}) if (defined $ENV{"TZ"}); # TZ environ var
3376     push(@tz,$ENV{'SYS$TIMEZONE_RULE'})
3377     if defined $ENV{'SYS$TIMEZONE_RULE'}; # VMS TZ environ var
3378     push(@tz,$ENV{'SYS$TIMEZONE_NAME'})
3379     if defined $ENV{'SYS$TIMEZONE_NAME'}; # VMS TZ name environ var
3380     push(@tz,$ENV{'UCX$TZ'})
3381     if defined $ENV{'UCX$TZ'}; # VMS TZ environ var
3382     push(@tz,$ENV{'TCPIP$TZ'})
3383     if defined $ENV{'TCPIP$TZ'}; # VMS TZ environ var
3384    
3385     # The `date` command... if we're doing taint checking, we need to
3386     # always call it with a full path... otherwise, use the user's path.
3387     #
3388     # Microsoft operating systems don't have a date command built in. Try
3389     # to trap all the various ways of knowing we are on one of these systems.
3390     #
3391     # We'll try `date +%Z` first, and if that fails, we'll take just the
3392     # `date` program and assume the output is of the format:
3393     # Thu Aug 31 14:57:46 EDT 2000
3394    
3395     unless (($^X =~ /perl\.exe$/i) or
3396     ($OS eq "Windows") or
3397     ($OS eq "Netware") or
3398     ($OS eq "VMS")) {
3399     if ($Date::Manip::NoTaint) {
3400     if ($OS eq "VMS") {
3401     $tz=$ENV{'SYS$TIMEZONE_NAME'};
3402     if (! $tz) {
3403     $tz=$ENV{'MULTINET_TIMEZONE'};
3404     if (! $tz) {
3405     $tz=$ENV{'SYS$TIMEZONE_DIFFERENTIAL'}/3600.; # e.g. '-4' for EDT
3406     }
3407     }
3408     } else {
3409     $tz=`date +%Z 2> /dev/null`;
3410     chomp($tz);
3411     if (! $tz) {
3412     $tz=`date 2> /dev/null`;
3413     chomp($tz);
3414     $tz=(split(/\s+/,$tz))[4];
3415     }
3416     }
3417     push(@tz,$tz);
3418     } else {
3419     # We need to satisfy taint checking, but also look in all the
3420     # directories in @DatePath.
3421     #
3422     local $ENV{PATH} = join(':', @Date::Manip::DatePath);
3423     local $ENV{BASH_ENV} = '';
3424     $tz=`date +%Z 2> /dev/null`;
3425     chomp($tz);
3426     if (! $tz) {
3427     $tz=`date 2> /dev/null`;
3428     chomp($tz);
3429     $tz=(split(/\s+/,$tz))[4];
3430     }
3431     push(@tz,$tz);
3432     }
3433     }
3434    
3435     push(@tz,$main::TZ) if (defined $main::TZ); # $main::TZ
3436    
3437     if (-s "/etc/TIMEZONE") { # /etc/TIMEZONE
3438     $in=new IO::File;
3439     $in->open("/etc/TIMEZONE","r");
3440     while (! eof($in)) {
3441     $tmp=<$in>;
3442     if ($tmp =~ /^TZ\s*=\s*(.*?)\s*$/) {
3443     push(@tz,$1);
3444     last;
3445     }
3446     }
3447     $in->close;
3448     }
3449    
3450     if (-s "/etc/timezone") { # /etc/timezone
3451     $in=new IO::File;
3452     $in->open("/etc/timezone","r");
3453     while (! eof($in)) {
3454     $tmp=<$in>;
3455     next if ($tmp =~ /^\s*\043/);
3456     chomp($tmp);
3457     if ($tmp =~ /^\s*(.*?)\s*$/) {
3458     push(@tz,$1);
3459     last;
3460     }
3461     }
3462     $in->close;
3463     }
3464    
3465     # Now parse each one to find the first valid one.
3466     foreach $tz (@tz) {
3467     $tz =~ s/\s*$//;
3468     $tz =~ s/^\s*//;
3469     next if (! $tz);
3470    
3471     return uc($tz)
3472     if (defined $Zone{"n2o"}{lc($tz)});
3473    
3474     if ($tz =~ /^[+-]\d{4}$/) {
3475     return $tz;
3476     } elsif ($tz =~ /^([+-]\d{2})(?::(\d{2}))?$/) {
3477     my($h,$m)=($1,$2);
3478     $m="00" if (! $m);
3479     return "$h$m";
3480     }
3481    
3482     # Handle US/Eastern format
3483     if ($tz =~ /^$Zone{"tzones"}$/i) {
3484     $tmp=lc $1;
3485     $tz=$Zone{"tz2z"}{$tmp};
3486     }
3487    
3488     # Handle STD#DST# format (and STD-#DST-# formats)
3489     if ($tz =~ /^([a-z]+)-?\d([a-z]+)-?\d?$/i) {
3490     ($std,$dst)=($1,$2);
3491     next if (! defined $Zone{"n2o"}{lc($std)} or
3492     ! defined $Zone{"n2o"}{lc($dst)});
3493     $time = time();
3494     ($null,$null,$null,$null,$null,$null,$null,$null,$isdst) =
3495     localtime($time);
3496     return uc($dst) if ($isdst);
3497     return uc($std);
3498     }
3499     }
3500    
3501     confess "ERROR: Date::Manip unable to determine TimeZone.\n";
3502     }
3503    
3504     # Returns 1 if $date is a work day. If $time is non-zero, the time is
3505     # also checked to see if it falls within work hours. Returns "" if
3506     # an invalid date is passed in.
3507     sub Date_IsWorkDay {
3508     print "DEBUG: Date_IsWorkDay\n" if ($Curr{"Debug"} =~ /trace/);
3509     my($date,$time)=@_;
3510     &Date_Init() if (! $Curr{"InitDone"});
3511     $date=&ParseDateString($date);
3512     return "" if (! $date);
3513     my($d)=$date;
3514     $d=&Date_SetTime($date,$Cnf{"WorkDayBeg"}) if (! $time);
3515    
3516     my($y,$mon,$day,$tmp,$h,$m,$dow)=();
3517     ($y,$mon,$day,$h,$m,$tmp)=&Date_Split($d, 1);
3518     $dow=&Date_DayOfWeek($mon,$day,$y);
3519    
3520     return 0 if ($dow<$Cnf{"WorkWeekBeg"} or
3521     $dow>$Cnf{"WorkWeekEnd"} or
3522     "$h:$m" lt $Cnf{"WorkDayBeg"} or
3523     "$h:$m" gt $Cnf{"WorkDayEnd"});
3524    
3525     if (! exists $Holiday{"dates"}{$y}) {
3526     # There will be recursion problems if we ever end up here twice.
3527     $Holiday{"dates"}{$y}={};
3528     &Date_UpdateHolidays($y)
3529     }
3530     $d=&Date_SetTime($date,"00:00:00");
3531     return 0 if (exists $Holiday{"dates"}{$y}{$d});
3532     1;
3533     }
3534    
3535     # Finds the day $off work days from now. If $time is passed in, we must
3536     # also take into account the time of day.
3537     #
3538     # If $time is not passed in, day 0 is today (if today is a workday) or the
3539     # next work day if it isn't. In any case, the time of day is unaffected.
3540     #
3541     # If $time is passed in, day 0 is now (if now is part of a workday) or the
3542     # start of the very next work day.
3543     sub Date_NextWorkDay {
3544     print "DEBUG: Date_NextWorkDay\n" if ($Curr{"Debug"} =~ /trace/);
3545     my($date,$off,$time)=@_;
3546     &Date_Init() if (! $Curr{"InitDone"});
3547     $date=&ParseDateString($date);
3548     my($err)=();
3549    
3550     if (! &Date_IsWorkDay($date,$time)) {
3551     if ($time) {
3552     while (1) {
3553     $date=&Date_GetNext($date,undef,0,$Cnf{"WorkDayBeg"});
3554     last if (&Date_IsWorkDay($date,$time));
3555     }
3556     } else {
3557     while (1) {
3558     $date=&DateCalc_DateDelta($date,"+0:0:0:1:0:0:0",\$err,0);
3559     last if (&Date_IsWorkDay($date,$time));
3560     }
3561     }
3562     }
3563    
3564     while ($off>0) {
3565     while (1) {
3566     $date=&DateCalc_DateDelta($date,"+0:0:0:1:0:0:0",\$err,0);
3567     last if (&Date_IsWorkDay($date,$time));
3568     }
3569     $off--;
3570     }
3571    
3572     return $date;
3573     }
3574    
3575     # Finds the day $off work days before now. If $time is passed in, we must
3576     # also take into account the time of day.
3577     #
3578     # If $time is not passed in, day 0 is today (if today is a workday) or the
3579     # previous work day if it isn't. In any case, the time of day is unaffected.
3580     #
3581     # If $time is passed in, day 0 is now (if now is part of a workday) or the
3582     # end of the previous work period. Note that since the end of a work day
3583     # will automatically be turned into the start of the next one, this time
3584     # may actually be treated as AFTER the current time.
3585     sub Date_PrevWorkDay {
3586     print "DEBUG: Date_PrevWorkDay\n" if ($Curr{"Debug"} =~ /trace/);
3587     my($date,$off,$time)=@_;
3588     &Date_Init() if (! $Curr{"InitDone"});
3589     $date=&ParseDateString($date);
3590     my($err)=();
3591    
3592     if (! &Date_IsWorkDay($date,$time)) {
3593     if ($time) {
3594     while (1) {
3595     $date=&Date_GetPrev($date,undef,0,$Cnf{"WorkDayEnd"});
3596     last if (&Date_IsWorkDay($date,$time));
3597     }
3598     while (1) {
3599     $date=&Date_GetNext($date,undef,0,$Cnf{"WorkDayBeg"});
3600     last if (&Date_IsWorkDay($date,$time));
3601     }
3602     } else {
3603     while (1) {
3604     $date=&DateCalc_DateDelta($date,"-0:0:0:1:0:0:0",\$err,0);
3605     last if (&Date_IsWorkDay($date,$time));
3606     }
3607     }
3608     }
3609    
3610     while ($off>0) {
3611     while (1) {
3612     $date=&DateCalc_DateDelta($date,"-0:0:0:1:0:0:0",\$err,0);
3613     last if (&Date_IsWorkDay($date,$time));
3614     }
3615     $off--;
3616     }
3617    
3618     return $date;
3619     }
3620    
3621     # This finds the nearest workday to $date. If $date is a workday, it
3622     # is returned.
3623     sub Date_NearestWorkDay {
3624     print "DEBUG: Date_NearestWorkDay\n" if ($Curr{"Debug"} =~ /trace/);
3625     my($date,$tomorrow)=@_;
3626     &Date_Init() if (! $Curr{"InitDone"});
3627     $date=&ParseDateString($date);
3628     my($a,$b,$dela,$delb,$err)=();
3629     $tomorrow=$Cnf{"TomorrowFirst"} if (! defined $tomorrow);
3630    
3631     return $date if (&Date_IsWorkDay($date));
3632    
3633     # Find the nearest one.
3634     if ($tomorrow) {
3635     $dela="+0:0:0:1:0:0:0";
3636     $delb="-0:0:0:1:0:0:0";
3637     } else {
3638     $dela="-0:0:0:1:0:0:0";
3639     $delb="+0:0:0:1:0:0:0";
3640     }
3641     $a=$b=$date;
3642    
3643     while (1) {
3644     $a=&DateCalc_DateDelta($a,$dela,\$err);
3645     return $a if (&Date_IsWorkDay($a));
3646     $b=&DateCalc_DateDelta($b,$delb,\$err);
3647     return $b if (&Date_IsWorkDay($b));
3648     }
3649     }
3650    
3651     # &Date_NthDayOfYear($y,$n);
3652     # Returns a list of (YYYY,MM,DD,HH,MM,SS) for the Nth day of the year.
3653     sub Date_NthDayOfYear {
3654     no integer;
3655     print "DEBUG: Date_NthDayOfYear\n" if ($Curr{"Debug"} =~ /trace/);
3656     my($y,$n)=@_;
3657     $y=$Curr{"Y"} if (! $y);
3658     $n=1 if (! defined $n or $n eq "");
3659     $n+=0; # to turn 023 into 23
3660     $y=&Date_FixYear($y) if (length($y)<4);
3661     my $leap=&Date_LeapYear($y);
3662     return () if ($n<1);
3663     return () if ($n >= ($leap ? 367 : 366));
3664    
3665     my(@d_in_m)=(31,28,31,30,31,30,31,31,30,31,30,31);
3666     $d_in_m[1]=29 if ($leap);
3667    
3668     # Calculate the hours, minutes, and seconds into the day.
3669     my $remain=($n - int($n))*24;
3670     my $h=int($remain);
3671     $remain=($remain - $h)*60;
3672     my $mn=int($remain);
3673     $remain=($remain - $mn)*60;
3674     my $s=$remain;
3675    
3676     # Calculate the month and the day.
3677     my($m,$d)=(0,0);
3678     $n=int($n);
3679     while ($n>0) {
3680     $m++;
3681     if ($n<=$d_in_m[0]) {
3682     $d=int($n);
3683     $n=0;
3684     } else {
3685     $n-= $d_in_m[0];
3686     shift(@d_in_m);
3687     }
3688     }
3689    
3690     ($y,$m,$d,$h,$mn,$s);
3691     }
3692    
3693     ########################################################################
3694     # NOT FOR EXPORT
3695     ########################################################################
3696    
3697     # This is used in Date_Init to fill in a hash based on international
3698     # data. It takes a list of keys and values and returns both a hash
3699     # with these values and a regular expression of keys.
3700     #
3701     # IN:
3702     # $data = [ key1 val1 key2 val2 ... ]
3703     # $opts = lc : lowercase the keys in the regexp
3704     # sort : sort (by length) the keys in the regexp
3705     # back : create a regexp with a back reference
3706     # escape : escape all strings in the regexp
3707     #
3708     # OUT:
3709     # $regexp = '(?:key1|key2|...)'
3710     # $hash = { key1=>val1 key2=>val2 ... }
3711    
3712     sub Date_InitHash {
3713     print "DEBUG: Date_InitHash\n" if ($Curr{"Debug"} =~ /trace/);
3714     my($data,$regexp,$opts,$hash)=@_;
3715     my(@data)=@$data;
3716     my($key,$val,@list)=();
3717    
3718     # Parse the options
3719     my($lc,$sort,$back,$escape)=(0,0,0,0);
3720     $lc=1 if ($opts =~ /lc/i);
3721     $sort=1 if ($opts =~ /sort/i);
3722     $back=1 if ($opts =~ /back/i);
3723     $escape=1 if ($opts =~ /escape/i);
3724    
3725     # Create the hash
3726     while (@data) {
3727     ($key,$val,@data)=@data;
3728     $key=lc($key) if ($lc);
3729     $$hash{$key}=$val;
3730     }
3731    
3732     # Create the regular expression
3733     if ($regexp) {
3734     @list=keys(%$hash);
3735     @list=sort sortByLength(@list) if ($sort);
3736     if ($escape) {
3737     foreach $val (@list) {
3738     $val="\Q$val\E";
3739     }
3740     }
3741     if ($back) {
3742     $$regexp="(" . join("|",@list) . ")";
3743     } else {
3744     $$regexp="(?:" . join("|",@list) . ")";
3745     }
3746     }
3747     }
3748    
3749     # This is used in Date_Init to fill in regular expressions, lists, and
3750     # hashes based on international data. It takes a list of lists which have
3751     # to be stored as regular expressions (to find any element in the list),
3752     # lists, and hashes (indicating the location in the lists).
3753     #
3754     # IN:
3755     # $data = [ [ [ valA1 valA2 ... ][ valA1' valA2' ... ] ... ]
3756     # [ [ valB1 valB2 ... ][ valB1' valB2' ... ] ... ]
3757     # ...
3758     # [ [ valZ1 valZ2 ... ] [valZ1' valZ1' ... ] ... ] ]
3759     # $lists = [ \@listA \@listB ... \@listZ ]
3760     # $opts = lc : lowercase the values in the regexp
3761     # sort : sort (by length) the values in the regexp
3762     # back : create a regexp with a back reference
3763     # escape : escape all strings in the regexp
3764     # $hash = [ \%hash, TYPE ]
3765     # TYPE 0 : $hash{ valBn=>n-1 }
3766     # TYPE 1 : $hash{ valBn=>n }
3767     #
3768     # OUT:
3769     # $regexp = '(?:valA1|valA2|...|valB1|...)'
3770     # $lists = [ [ valA1 valA2 ... ] # only the 1st list (or
3771     # [ valB1 valB2 ... ] ... ] # 2nd for int. characters)
3772     # $hash
3773    
3774     sub Date_InitLists {
3775     print "DEBUG: Date_InitLists\n" if ($Curr{"Debug"} =~ /trace/);
3776     my($data,$regexp,$opts,$lists,$hash)=@_;
3777     my(@data)=@$data;
3778     my(@lists)=@$lists;
3779     my($i,@ele,$ele,@list,$j,$tmp)=();
3780    
3781     # Parse the options
3782     my($lc,$sort,$back,$escape)=(0,0,0,0);
3783     $lc=1 if ($opts =~ /lc/i);
3784     $sort=1 if ($opts =~ /sort/i);
3785     $back=1 if ($opts =~ /back/i);
3786     $escape=1 if ($opts =~ /escape/i);
3787    
3788     # Set each of the lists
3789     if (@lists) {
3790     confess "ERROR: Date_InitLists: lists must be 1 per data\n"
3791     if ($#lists != $#data);
3792     for ($i=0; $i<=$#data; $i++) {
3793     @ele=@{ $data[$i] };
3794     if ($Cnf{"IntCharSet"} && $#ele>0) {
3795     @{ $lists[$i] } = @{ $ele[1] };
3796     } else {
3797     @{ $lists[$i] } = @{ $ele[0] };
3798     }
3799     }
3800     }
3801    
3802     # Create the hash
3803     my($hashtype,$hashsave,%hash)=();
3804     if (@$hash) {
3805     ($hash,$hashtype)=@$hash;
3806     $hashsave=1;
3807     } else {
3808     $hashtype=0;
3809     $hashsave=0;
3810     }
3811     for ($i=0; $i<=$#data; $i++) {
3812     @ele=@{ $data[$i] };
3813     foreach $ele (@ele) {
3814     @list = @{ $ele };
3815     for ($j=0; $j<=$#list; $j++) {
3816     $tmp=$list[$j];
3817     next if (! $tmp);
3818     $tmp=lc($tmp) if ($lc);
3819     $hash{$tmp}= $j+$hashtype;
3820     }
3821     }
3822     }
3823     %$hash = %hash if ($hashsave);
3824    
3825     # Create the regular expression
3826     if ($regexp) {
3827     @list=keys(%hash);
3828     @list=sort sortByLength(@list) if ($sort);
3829     if ($escape) {
3830     foreach $ele (@list) {
3831     $ele="\Q$ele\E";
3832     }
3833     }
3834     if ($back) {
3835     $$regexp="(" . join("|",@list) . ")";
3836     } else {
3837     $$regexp="(?:" . join("|",@list) . ")";
3838     }
3839     }
3840     }
3841    
3842     # This is used in Date_Init to fill in regular expressions and lists based
3843     # on international data. This takes a list of strings and returns a regular
3844     # expression (to find any one of them).
3845     #
3846     # IN:
3847     # $data = [ string1 string2 ... ]
3848     # $opts = lc : lowercase the values in the regexp
3849     # sort : sort (by length) the values in the regexp
3850     # back : create a regexp with a back reference
3851     # escape : escape all strings in the regexp
3852     #
3853     # OUT:
3854     # $regexp = '(string1|string2|...)'
3855    
3856     sub Date_InitStrings {
3857     print "DEBUG: Date_InitStrings\n" if ($Curr{"Debug"} =~ /trace/);
3858     my($data,$regexp,$opts)=@_;
3859     my(@list)=@{ $data };
3860    
3861     # Parse the options
3862     my($lc,$sort,$back,$escape)=(0,0,0,0);
3863     $lc=1 if ($opts =~ /lc/i);
3864     $sort=1 if ($opts =~ /sort/i);
3865     $back=1 if ($opts =~ /back/i);
3866     $escape=1 if ($opts =~ /escape/i);
3867    
3868     # Create the regular expression
3869     my($ele)=();
3870     @list=sort sortByLength(@list) if ($sort);
3871     if ($escape) {
3872     foreach $ele (@list) {
3873     $ele="\Q$ele\E";
3874     }
3875     }
3876     if ($back) {
3877     $$regexp="(" . join("|",@list) . ")";
3878     } else {
3879     $$regexp="(?:" . join("|",@list) . ")";
3880     }
3881     $$regexp=lc($$regexp) if ($lc);
3882     }
3883    
3884     # items is passed in (either as a space separated string, or a reference to
3885     # a list) and a regular expression which matches any one of the items is
3886     # prepared. The regular expression will be of one of the forms:
3887     # "(a|b)" @list not empty, back option included
3888     # "(?:a|b)" @list not empty
3889     # "()" @list empty, back option included
3890     # "" @list empty
3891     # $options is a string which contains any of the following strings:
3892     # back : the regular expression has a backreference
3893     # opt : the regular expression is optional and a "?" is appended in
3894     # the first two forms
3895     # optws : the regular expression is optional and may be replaced by
3896     # whitespace
3897     # optWs : the regular expression is optional, but if not present, must
3898     # be replaced by whitespace
3899     # sort : the items in the list are sorted by length (longest first)
3900     # lc : the string is lowercased
3901     # under : any underscores are converted to spaces
3902     # pre : it may be preceded by whitespace
3903     # Pre : it must be preceded by whitespace
3904     # PRE : it must be preceded by whitespace or the start
3905     # post : it may be followed by whitespace
3906     # Post : it must be followed by whitespace
3907     # POST : it must be followed by whitespace or the end
3908     # Spaces due to pre/post options will not be included in the back reference.
3909     #
3910     # If $array is included, then the elements will also be returned as a list.
3911     # $array is a string which may contain any of the following:
3912     # keys : treat the list as a hash and only the keys go into the regexp
3913     # key0 : treat the list as the values of a hash with keys 0 .. N-1
3914     # key1 : treat the list as the values of a hash with keys 1 .. N
3915     # val0 : treat the list as the keys of a hash with values 0 .. N-1
3916     # val1 : treat the list as the keys of a hash with values 1 .. N
3917    
3918     # &Date_InitLists([$lang{"month_name"},$lang{"month_abb"}],
3919     # [\$Month,"lc,sort,back"],
3920     # [\@Month,\@Mon],
3921     # [\%Month,1]);
3922    
3923     # This is used in Date_Init to prepare regular expressions. A list of
3924     # items is passed in (either as a space separated string, or a reference to
3925     # a list) and a regular expression which matches any one of the items is
3926     # prepared. The regular expression will be of one of the forms:
3927     # "(a|b)" @list not empty, back option included
3928     # "(?:a|b)" @list not empty
3929     # "()" @list empty, back option included
3930     # "" @list empty
3931     # $options is a string which contains any of the following strings:
3932     # back : the regular expression has a backreference
3933     # opt : the regular expression is optional and a "?" is appended in
3934     # the first two forms
3935     # optws : the regular expression is optional and may be replaced by
3936     # whitespace
3937     # optWs : the regular expression is optional, but if not present, must
3938     # be replaced by whitespace
3939     # sort : the items in the list are sorted by length (longest first)
3940     # lc : the string is lowercased
3941     # under : any underscores are converted to spaces
3942     # pre : it may be preceded by whitespace
3943     # Pre : it must be preceded by whitespace
3944     # PRE : it must be preceded by whitespace or the start
3945     # post : it may be followed by whitespace
3946     # Post : it must be followed by whitespace
3947     # POST : it must be followed by whitespace or the end
3948     # Spaces due to pre/post options will not be included in the back reference.
3949     #
3950     # If $array is included, then the elements will also be returned as a list.
3951     # $array is a string which may contain any of the following:
3952     # keys : treat the list as a hash and only the keys go into the regexp
3953     # key0 : treat the list as the values of a hash with keys 0 .. N-1
3954     # key1 : treat the list as the values of a hash with keys 1 .. N
3955     # val0 : treat the list as the keys of a hash with values 0 .. N-1
3956     # val1 : treat the list as the keys of a hash with values 1 .. N
3957     sub Date_Regexp {
3958     print "DEBUG: Date_Regexp\n" if ($Curr{"Debug"} =~ /trace/);
3959     my($list,$options,$array)=@_;
3960     my(@list,$ret,%hash,$i)=();
3961     local($_)=();
3962     $options="" if (! defined $options);
3963     $array="" if (! defined $array);
3964    
3965     my($sort,$lc,$under)=(0,0,0);
3966     $sort =1 if ($options =~ /sort/i);
3967     $lc =1 if ($options =~ /lc/i);
3968     $under=1 if ($options =~ /under/i);
3969     my($back,$opt,$pre,$post,$ws)=("?:","","","","");
3970     $back ="" if ($options =~ /back/i);
3971     $opt ="?" if ($options =~ /opt/i);
3972     $pre ='\s*' if ($options =~ /pre/);
3973     $pre ='\s+' if ($options =~ /Pre/);
3974     $pre ='(?:\s+|^)' if ($options =~ /PRE/);
3975     $post ='\s*' if ($options =~ /post/);
3976     $post ='\s+' if ($options =~ /Post/);
3977     $post ='(?:$|\s+)' if ($options =~ /POST/);
3978     $ws ='\s*' if ($options =~ /optws/);
3979     $ws ='\s+' if ($options =~ /optws/);
3980    
3981     my($hash,$keys,$key0,$key1,$val0,$val1)=(0,0,0,0,0,0);
3982     $keys =1 if ($array =~ /keys/i);
3983     $key0 =1 if ($array =~ /key0/i);
3984     $key1 =1 if ($array =~ /key1/i);
3985     $val0 =1 if ($array =~ /val0/i);
3986     $val1 =1 if ($array =~ /val1/i);
3987     $hash =1 if ($keys or $key0 or $key1 or $val0 or $val1);
3988    
3989     my($ref)=ref $list;
3990     if (! $ref) {
3991     $list =~ s/\s*$//;
3992     $list =~ s/^\s*//;
3993     $list =~ s/\s+/&&&/g;
3994     } elsif ($ref eq "ARRAY") {
3995     $list = join("&&&",@$list);
3996     } else {
3997     confess "ERROR: Date_Regexp.\n";
3998     }
3999    
4000     if (! $list) {
4001     if ($back eq "") {
4002     return "()";
4003     } else {
4004     return "";
4005     }
4006     }
4007    
4008     $list=lc($list) if ($lc);
4009     $list=~ s/_/ /g if ($under);
4010     @list=split(/&&&/,$list);
4011     if ($keys) {
4012     %hash=@list;
4013     @list=keys %hash;
4014     } elsif ($key0 or $key1 or $val0 or $val1) {
4015     $i=0;
4016     $i=1 if ($key1 or $val1);
4017     if ($key0 or $key1) {
4018     %hash= map { $_,$i++ } @list;
4019     } else {
4020     %hash= map { $i++,$_ } @list;
4021     }
4022     }
4023     @list=sort sortByLength(@list) if ($sort);
4024    
4025     $ret="($back" . join("|",@list) . ")";
4026     $ret="(?:$pre$ret$post)" if ($pre or $post);
4027     $ret.=$opt;
4028     $ret="(?:$ret|$ws)" if ($ws);
4029    
4030     if ($array and $hash) {
4031     return ($ret,%hash);
4032     } elsif ($array) {
4033     return ($ret,@list);
4034     } else {
4035     return $ret;
4036     }
4037     }
4038    
4039     # This will produce a delta with the correct number of signs. At most two
4040     # signs will be in it normally (one before the year, and one in front of
4041     # the day), but if appropriate, signs will be in front of all elements.
4042     # Also, as many of the signs will be equivalent as possible.
4043     sub Delta_Normalize {
4044     print "DEBUG: Delta_Normalize\n" if ($Curr{"Debug"} =~ /trace/);
4045     my($delta,$mode)=@_;
4046     return "" if (! $delta);
4047     return "+0:+0:+0:+0:+0:+0:+0"
4048     if ($delta =~ /^([+-]?0+:){6}[+-]?0+$/ and $Cnf{"DeltaSigns"});
4049     return "+0:0:0:0:0:0:0" if ($delta =~ /^([+-]?0+:){6}[+-]?0+$/);
4050    
4051     my($tmp,$sign1,$sign2,$len)=();
4052    
4053     # Calculate the length of the day in minutes
4054     $len=24*60;
4055     $len=$Curr{"WDlen"} if ($mode==2 || $mode==3);
4056    
4057     # We have to get the sign of every component explicitely so that a "-0"
4058     # or "+0" doesn't get lost by treating it numerically (i.e. "-0:0:2" must
4059     # be a negative delta).
4060    
4061     my($y,$mon,$w,$d,$h,$m,$s)=&Delta_Split($delta);
4062    
4063     # We need to make sure that the signs of all parts of a delta are the
4064     # same. The easiest way to do this is to convert all of the large
4065     # components to the smallest ones, then convert the smaller components
4066     # back to the larger ones.
4067    
4068     # Do the year/month part
4069    
4070     $mon += $y*12; # convert y to m
4071     $sign1="+";
4072     if ($mon<0) {
4073     $mon *= -1;
4074     $sign1="-";
4075     }
4076    
4077     $y = $mon/12; # convert m to y
4078     $mon -= $y*12;
4079    
4080     $y=0 if ($y eq "-0"); # get around silly -0 problem
4081     $mon=0 if ($mon eq "-0");
4082    
4083     # Do the wk/day/hour/min/sec part
4084    
4085     {
4086     # Unfortunately, $s is overflowing for dates more than ~70 years
4087     # apart.
4088     no integer;
4089    
4090     if ($mode==3 || $mode==2) {
4091     $s += $d*$len*60 + $h*3600 + $m*60; # convert d/h/m to s
4092     } else {
4093     $s += ($d+7*$w)*$len*60 + $h*3600 + $m*60; # convert w/d/h/m to s
4094     }
4095     $sign2="+";
4096     if ($s<0) {
4097     $s*=-1;
4098     $sign2="-";
4099     }
4100    
4101     $m = int($s/60); # convert s to m
4102     $s -= $m*60;
4103     $d = int($m/$len); # convert m to d
4104     $m -= $d*$len;
4105    
4106     # The rest should be fine.
4107     }
4108     $h = $m/60; # convert m to h
4109     $m -= $h*60;
4110     if ($mode == 3 || $mode == 2) {
4111     $w = $w*1; # get around +0 problem
4112     } else {
4113     $w = $d/7; # convert d to w
4114     $d -= $w*7;
4115     }
4116    
4117     $w=0 if ($w eq "-0"); # get around silly -0 problem
4118     $d=0 if ($d eq "-0");
4119     $h=0 if ($h eq "-0");
4120     $m=0 if ($m eq "-0");
4121     $s=0 if ($s eq "-0");
4122    
4123     # Only include two signs if necessary
4124     $sign1=$sign2 if ($y==0 and $mon==0);
4125     $sign2=$sign1 if ($w==0 and $d==0 and $h==0 and $m==0 and $s==0);
4126     $sign2="" if ($sign1 eq $sign2 and ! $Cnf{"DeltaSigns"});
4127    
4128     if ($Cnf{"DeltaSigns"}) {
4129     return "$sign1$y:$sign1$mon:$sign2$w:$sign2$d:$sign2$h:$sign2$m:$sign2$s";
4130     } else {
4131     return "$sign1$y:$mon:$sign2$w:$d:$h:$m:$s";
4132     }
4133     }
4134    
4135     # This checks a delta to make sure it is valid. If it is, it splits
4136     # it and returns the elements with a sign on each. The 2nd argument
4137     # specifies the default sign. Blank elements are set to 0. If the
4138     # third element is non-nil, exactly 7 elements must be included.
4139     sub Delta_Split {
4140     print "DEBUG: Delta_Split\n" if ($Curr{"Debug"} =~ /trace/);
4141     my($delta,$sign,$exact)=@_;
4142     my(@delta)=split(/:/,$delta);
4143     return () if ($exact and $#delta != 6);
4144     my($i)=();
4145     $sign="+" if (! defined $sign);
4146     for ($i=0; $i<=$#delta; $i++) {
4147     $delta[$i]="0" if (! $delta[$i]);
4148     return () if ($delta[$i] !~ /^[+-]?\d+$/);
4149     $sign = ($delta[$i] =~ s/^([+-])// ? $1 : $sign);
4150     $delta[$i] = $sign.$delta[$i];
4151     }
4152     @delta;
4153     }
4154    
4155     # Reads up to 3 arguments. $h may contain the time in any international
4156     # format. Any empty elements are set to 0.
4157     sub Date_ParseTime {
4158     print "DEBUG: Date_ParseTime\n" if ($Curr{"Debug"} =~ /trace/);
4159     my($h,$m,$s)=@_;
4160     my($t)=&CheckTime("one");
4161    
4162     if (defined $h and $h =~ /$t/) {
4163     $h=$1;
4164     $m=$2;
4165     $s=$3 if (defined $3);
4166     }
4167     $h="00" if (! defined $h);
4168     $m="00" if (! defined $m);
4169     $s="00" if (! defined $s);
4170    
4171     ($h,$m,$s);
4172     }
4173    
4174     # Forms a date with the 6 elements passed in (all of which must be defined).
4175     # No check as to validity is made.
4176     sub Date_Join {
4177     print "DEBUG: Date_Join\n" if ($Curr{"Debug"} =~ /trace/);
4178     foreach (0 .. $#_) {
4179     croak "undefined arg $_ to Date_Join()" if not defined $_[$_];
4180     }
4181     my($y,$m,$d,$h,$mn,$s)=@_;
4182     my($ym,$md,$dh,$hmn,$mns)=();
4183    
4184     if ($Cnf{"Internal"} == 0) {
4185     $ym=$md=$dh="";
4186     $hmn=$mns=":";
4187    
4188     } elsif ($Cnf{"Internal"} == 1) {
4189     $ym=$md=$dh=$hmn=$mns="";
4190    
4191     } elsif ($Cnf{"Internal"} == 2) {
4192     $ym=$md="-";
4193     $dh=" ";
4194     $hmn=$mns=":";
4195    
4196     } else {
4197     confess "ERROR: Invalid internal format in Date_Join.\n";
4198     }
4199     $m="0$m" if (length($m)==1);
4200     $d="0$d" if (length($d)==1);
4201     $h="0$h" if (length($h)==1);
4202     $mn="0$mn" if (length($mn)==1);
4203     $s="0$s" if (length($s)==1);
4204     "$y$ym$m$md$d$dh$h$hmn$mn$mns$s";
4205     }
4206    
4207     # This checks a time. If it is valid, it splits it and returns 3 elements.
4208     # If "one" or "two" is passed in, a regexp with 1/2 or 2 digit hours is
4209     # returned.
4210     sub CheckTime {
4211     print "DEBUG: CheckTime\n" if ($Curr{"Debug"} =~ /trace/);
4212     my($time)=@_;
4213     my($h)='(?:0?[0-9]|1[0-9]|2[0-3])';
4214     my($h2)='(?:0[0-9]|1[0-9]|2[0-3])';
4215     my($m)='[0-5][0-9]';
4216     my($s)=$m;
4217     my($hm)="(?:". $Lang{$Cnf{"Language"}}{"SepHM"} ."|:)";
4218     my($ms)="(?:". $Lang{$Cnf{"Language"}}{"SepMS"} ."|:)";
4219     my($ss)=$Lang{$Cnf{"Language"}}{"SepSS"};
4220     my($t)="^($h)$hm($m)(?:$ms($s)(?:$ss\\d+)?)?\$";
4221     if ($time eq "one") {
4222     return $t;
4223     } elsif ($time eq "two") {
4224     $t="^($h2)$hm($m)(?:$ms($s)(?:$ss\\d+)?)?\$";
4225     return $t;
4226     }
4227    
4228     if ($time =~ /$t/i) {
4229     ($h,$m,$s)=($1,$2,$3);
4230     $h="0$h" if (length($h)<2);
4231     $m="0$m" if (length($m)<2);
4232     $s="00" if (! defined $s);
4233     return ($h,$m,$s);
4234     } else {
4235     return ();
4236     }
4237     }
4238    
4239     # This checks a recurrence. If it is valid, it splits it and returns the
4240     # elements. Otherwise, it returns an empty list.
4241     # ($recur0,$recur1,$flags,$dateb,$date0,$date1)=&Recur_Split($recur);
4242     sub Recur_Split {
4243     print "DEBUG: Recur_Split\n" if ($Curr{"Debug"} =~ /trace/);
4244     my($recur)=@_;
4245     my(@ret,@tmp);
4246    
4247     my($R) = '(\*?(?:[-,0-9]+[:\*]){6}[-,0-9]+)';
4248     my($F) = '(?:\*([^*]*))';
4249     my($DB,$D0,$D1);
4250     $DB=$D0=$D1=$F;
4251    
4252     if ($recur =~ /^$R$F?$DB?$D0?$D1?$/) {
4253     @ret=($1,$2,$3,$4,$5);
4254     @tmp=split(/\*/,shift(@ret));
4255     return () if ($#tmp>1);
4256     return (@tmp,"",@ret) if ($#tmp==0);
4257     return (@tmp,@ret);
4258     }
4259     return ();
4260     }
4261    
4262     # This checks a date. If it is valid, it splits it and returns the elements.
4263     # If no date is passed in, it returns a regular expression for the date.
4264     #
4265     # The optional second argument says 'I really expect this to be a
4266     # valid Date::Manip object, please throw an exception if it is
4267     # not'. Otherwise, errors are signalled by returning ().
4268     #
4269     sub Date_Split {
4270     print "DEBUG: Date_Split\n" if ($Curr{"Debug"} =~ /trace/);
4271     my($date, $definitely_valid)=@_;
4272     $definitely_valid = 0 if not defined $definitely_valid;
4273     my($ym,$md,$dh,$hmn,$mns)=();
4274     my($y)='(\d{4})';
4275     my($m)='(0[1-9]|1[0-2])';
4276     my($d)='(0[1-9]|[1-2][0-9]|3[0-1])';
4277     my($h)='([0-1][0-9]|2[0-3])';
4278     my($mn)='([0-5][0-9])';
4279     my($s)=$mn;
4280    
4281     if ($Cnf{"Internal"} == 0) {
4282     $ym=$md=$dh="";
4283     $hmn=$mns=":";
4284    
4285     } elsif ($Cnf{"Internal"} == 1) {
4286     $ym=$md=$dh=$hmn=$mns="";
4287    
4288     } elsif ($Cnf{"Internal"} == 2) {
4289     $ym=$md="-";
4290     $dh=" ";
4291     $hmn=$mns=":";
4292    
4293     } else {
4294     confess "ERROR: Invalid internal format in Date_Split.\n";
4295     }
4296    
4297     my($t)="^$y$ym$m$md$d$dh$h$hmn$mn$mns$s\$";
4298    
4299     if (not defined $date or $date eq '') {
4300     if ($definitely_valid) {
4301     die "bad date '$date'";
4302     } else {
4303     return $t;
4304     }
4305     }
4306    
4307     if ($date =~ /$t/) {
4308     ($y,$m,$d,$h,$mn,$s)=($1,$2,$3,$4,$5,$6);
4309     my(@d_in_m)=(0,31,28,31,30,31,30,31,31,30,31,30,31);
4310     $d_in_m[2]=29 if (&Date_LeapYear($y));
4311     if ($d>$d_in_m[$m]) {
4312     my $msg = "invalid date $date: day $d of month $m, but only $d_in_m[$m] days in that month";
4313     if ($definitely_valid) {
4314     die $msg;
4315     }
4316     else {
4317     warn $msg;
4318     return ();
4319     }
4320     }
4321     return ($y,$m,$d,$h,$mn,$s);
4322     }
4323    
4324     if ($definitely_valid) {
4325     die "invalid date $date: doesn't match regexp $t";
4326     }
4327     return ();
4328     }
4329    
4330     # This returns the date easter occurs on for a given year as ($month,$day).
4331     # This is from the Calendar FAQ.
4332     sub Date_Easter {
4333     my($y)=@_;
4334     $y=&Date_FixYear($y) if (length($y)==2);
4335    
4336     my($c) = $y/100;
4337     my($g) = $y % 19;
4338     my($k) = ($c-17)/25;
4339     my($i) = ($c - $c/4 - ($c-$k)/3 + 19*$g + 15) % 30;
4340     $i = $i - ($i/28)*(1 - ($i/28)*(29/($i+1))*((21-$g)/11));
4341     my($j) = ($y + $y/4 + $i + 2 - $c + $c/4) % 7;
4342     my($l) = $i-$j;
4343     my($m) = 3 + ($l+40)/44;
4344     my($d) = $l + 28 - 31*($m/4);
4345     return ($m,$d);
4346     }
4347    
4348     # This takes a list of years, months, WeekOfMonth's, and optionally
4349     # DayOfWeek's, and returns a list of dates. Optionally, a list of dates
4350     # can be passed in as the 1st argument (with the 2nd argument the null list)
4351     # and the year/month of these will be used.
4352     #
4353     # If $FDn is non-zero, the first week of the month contains the first
4354     # occurence of this day (1=Monday). If $FIn is non-zero, the first week of
4355     # the month contains the date (i.e. $FIn'th day of the month).
4356     sub Date_Recur_WoM {
4357     my($y,$m,$w,$d,$FDn,$FIn)=@_;
4358     my(@y)=@$y;
4359     my(@m)=@$m;
4360     my(@w)=@$w;
4361     my(@d)=@$d;
4362     my($date0,$date1,@tmp,@date,$d0,$d1,@tmp2)=();
4363    
4364     if (@m) {
4365     @tmp=();
4366     foreach $y (@y) {
4367     return () if (length($y)==1 || length($y)==3 || ! &IsInt($y,0,9999));
4368     $y=&Date_FixYear($y) if (length($y)==2);
4369     push(@tmp,$y);
4370     }
4371     @y=sort { $a<=>$b } (@tmp);
4372    
4373     return () if (! @m);
4374     foreach $m (@m) {
4375     return () if (! &IsInt($m,1,12));
4376     }
4377     @m=sort { $a<=>$b } (@m);
4378    
4379     @tmp=@tmp2=();
4380     foreach $y (@y) {
4381     foreach $m (@m) {
4382     push(@tmp,$y);
4383     push(@tmp2,$m);
4384     }
4385     }
4386    
4387     @y=@tmp;
4388     @m=@tmp2;
4389    
4390     } else {
4391     foreach $d0 (@y) {
4392     @tmp=&Date_Split($d0);
4393     return () if (! @tmp);
4394     push(@tmp2,$tmp[0]);
4395     push(@m,$tmp[1]);
4396     }
4397     @y=@tmp2;
4398     }
4399    
4400     return () if (! @w);
4401     foreach $w (@w) {
4402     return () if ($w==0 || ! &IsInt($w,-5,5));
4403     }
4404    
4405     if (@d) {
4406     foreach $d (@d) {
4407     return () if (! &IsInt($d,1,7));
4408     }
4409     @d=sort { $a<=>$b } (@d);
4410     }
4411    
4412     @date=();
4413     foreach $y (@y) {
4414     $m=shift(@m);
4415    
4416     # Find 1st day of this month and next month
4417     $date0=&Date_Join($y,$m,1,0,0,0);
4418     $date1=&DateCalc($date0,"+0:1:0:0:0:0:0");
4419    
4420     if (@d) {
4421     foreach $d (@d) {
4422     # Find 1st occurence of DOW (in both months)
4423     $d0=&Date_GetNext($date0,$d,1);
4424     $d1=&Date_GetNext($date1,$d,1);
4425    
4426     @tmp=();
4427     while (&Date_Cmp($d0,$d1)<0) {
4428     push(@tmp,$d0);
4429     $d0=&DateCalc($d0,"+0:0:1:0:0:0:0");
4430     }
4431    
4432     @tmp2=();
4433     foreach $w (@w) {
4434     if ($w>0) {
4435     push(@tmp2,$tmp[$w-1]);
4436     } else {
4437     push(@tmp2,$tmp[$#tmp+1+$w]);
4438     }
4439     }
4440     @tmp2=sort(@tmp2);
4441     push(@date,@tmp2);
4442     }
4443    
4444     } else {
4445     # Find 1st day of 1st week
4446     if ($FDn != 0) {
4447     $date0=&Date_GetNext($date0,$FDn,1);
4448     } else {
4449     $date0=&Date_Join($y,$m,$FIn,0,0,0);
4450     }
4451     $date0=&Date_GetPrev($date0,$Cnf{"FirstDay"},1);
4452    
4453     # Find 1st day of 1st week of next month
4454     if ($FDn != 0) {
4455     $date1=&Date_GetNext($date1,$FDn,1);
4456     } else {
4457     $date1=&DateCalc($date1,"+0:0:0:".($FIn-1).":0:0:0") if ($FIn>1);
4458     }
4459     $date1=&Date_GetPrev($date1,$Cnf{"FirstDay"},1);
4460    
4461     @tmp=();
4462     while (&Date_Cmp($date0,$date1)<0) {
4463     push(@tmp,$date0);
4464     $date0=&DateCalc($date0,"+0:0:1:0:0:0:0");
4465     }
4466    
4467     @tmp2=();
4468     foreach $w (@w) {
4469     if ($w>0) {
4470     push(@tmp2,$tmp[$w-1]);
4471     } else {
4472     push(@tmp2,$tmp[$#tmp+1+$w]);
4473     }
4474     }
4475     @tmp2=sort(@tmp2);
4476     push(@date,@tmp2);
4477     }
4478     }
4479    
4480     @date;
4481     }
4482    
4483     # This returns a sorted list of dates formed by adding/subtracting
4484     # $delta to $dateb in the range $date0<=$d<$dateb. The first date int
4485     # the list is actually the first date<$date0 and the last date in the
4486     # list is the first date>=$date1 (because sometimes the set part will
4487     # move the date back into the range).
4488     sub Date_Recur {
4489     my($date0,$date1,$dateb,$delta)=@_;
4490     my(@ret,$d)=();
4491    
4492     while (&Date_Cmp($dateb,$date0)<0) {
4493     $dateb=&DateCalc_DateDelta($dateb,$delta);
4494     }
4495     while (&Date_Cmp($dateb,$date1)>=0) {
4496     $dateb=&DateCalc_DateDelta($dateb,"-$delta");
4497     }
4498    
4499     # Add the dates $date0..$dateb
4500     $d=$dateb;
4501     while (&Date_Cmp($d,$date0)>=0) {
4502     unshift(@ret,$d);
4503     $d=&DateCalc_DateDelta($d,"-$delta");
4504     }
4505     # Add the first date earler than the range
4506     unshift(@ret,$d);
4507    
4508     # Add the dates $dateb..$date1
4509     $d=&DateCalc_DateDelta($dateb,$delta);
4510     while (&Date_Cmp($d,$date1)<0) {
4511     push(@ret,$d);
4512     $d=&DateCalc_DateDelta($d,$delta);
4513     }
4514     # Add the first date later than the range
4515     push(@ret,$d);
4516    
4517     @ret;
4518     }
4519    
4520     # This sets the values in each date of a recurrence.
4521     #
4522     # $h,$m,$s can each be values or lists "1-2,4". If any are equal to "-1",
4523     # they are not set (and none of the larger elements are set).
4524     sub Date_RecurSetTime {
4525     my($date0,$date1,$dates,$h,$m,$s)=@_;
4526     my(@dates)=@$dates;
4527     my(@h,@m,@s,$date,@tmp)=();
4528    
4529     $m="-1" if ($s eq "-1");
4530     $h="-1" if ($m eq "-1");
4531    
4532     if ($h ne "-1") {
4533     @h=&ReturnList($h);
4534     return () if ! (@h);
4535     @h=sort { $a<=>$b } (@h);
4536    
4537     @tmp=();
4538     foreach $date (@dates) {
4539     foreach $h (@h) {
4540     push(@tmp,&Date_SetDateField($date,"h",$h,1));
4541     }
4542     }
4543     @dates=@tmp;
4544     }
4545    
4546     if ($m ne "-1") {
4547     @m=&ReturnList($m);
4548     return () if ! (@m);
4549     @m=sort { $a<=>$b } (@m);
4550    
4551     @tmp=();
4552     foreach $date (@dates) {
4553     foreach $m (@m) {
4554     push(@tmp,&Date_SetDateField($date,"mn",$m,1));
4555     }
4556     }
4557     @dates=@tmp;
4558     }
4559    
4560     if ($s ne "-1") {
4561     @s=&ReturnList($s);
4562     return () if ! (@s);
4563     @s=sort { $a<=>$b } (@s);
4564    
4565     @tmp=();
4566     foreach $date (@dates) {
4567     foreach $s (@s) {
4568     push(@tmp,&Date_SetDateField($date,"s",$s,1));
4569     }
4570     }
4571     @dates=@tmp;
4572     }
4573    
4574     @tmp=();
4575     foreach $date (@dates) {
4576     push(@tmp,$date) if (&Date_Cmp($date,$date0)>=0 &&
4577     &Date_Cmp($date,$date1)<0 &&
4578     &Date_Split($date));
4579     }
4580    
4581     @tmp;
4582     }
4583    
4584     sub DateCalc_DateDate {
4585     print "DEBUG: DateCalc_DateDate\n" if ($Curr{"Debug"} =~ /trace/);
4586     my($D1,$D2,$mode)=@_;
4587     my(@d_in_m)=(0,31,28,31,30,31,30,31,31,30,31,30,31);
4588     $mode=0 if (! defined $mode);
4589    
4590     # Exact mode
4591     if ($mode==0) {
4592     my($y1,$m1,$d1,$h1,$mn1,$s1)=&Date_Split($D1, 1);
4593     my($y2,$m2,$d2,$h2,$mn2,$s2)=&Date_Split($D2, 1);
4594     my($i,@delta,$d,$delta,$y)=();
4595    
4596     # form the delta for hour/min/sec
4597     $delta[4]=$h2-$h1;
4598     $delta[5]=$mn2-$mn1;
4599     $delta[6]=$s2-$s1;
4600    
4601     # form the delta for yr/mon/day
4602     $delta[0]=$delta[1]=0;
4603     $d=0;
4604     if ($y2>$y1) {
4605     $d=&Date_DaysInYear($y1) - &Date_DayOfYear($m1,$d1,$y1);
4606     $d+=&Date_DayOfYear($m2,$d2,$y2);
4607     for ($y=$y1+1; $y<$y2; $y++) {
4608     $d+= &Date_DaysInYear($y);
4609     }
4610     } elsif ($y2<$y1) {
4611     $d=&Date_DaysInYear($y2) - &Date_DayOfYear($m2,$d2,$y2);
4612     $d+=&Date_DayOfYear($m1,$d1,$y1);
4613     for ($y=$y2+1; $y<$y1; $y++) {
4614     $d+= &Date_DaysInYear($y);
4615     }
4616     $d *= -1;
4617     } else {
4618     $d=&Date_DayOfYear($m2,$d2,$y2) - &Date_DayOfYear($m1,$d1,$y1);
4619     }
4620     $delta[2]=0;
4621     $delta[3]=$d;
4622    
4623     for ($i=0; $i<7; $i++) {
4624     $delta[$i]="+".$delta[$i] if ($delta[$i]>=0);
4625     }
4626    
4627     $delta=join(":",@delta);
4628     $delta=&Delta_Normalize($delta,0);
4629     return $delta;
4630     }
4631    
4632     my($date1,$date2)=($D1,$D2);
4633     my($tmp,$sign,$err,@tmp)=();
4634    
4635     # make sure both are work days
4636     if ($mode==2 || $mode==3) {
4637     $date1=&Date_NextWorkDay($date1,0,1);
4638     $date2=&Date_NextWorkDay($date2,0,1);
4639     }
4640    
4641     # make sure date1 comes before date2
4642     if (&Date_Cmp($date1,$date2)>0) {
4643     $sign="-";
4644     $tmp=$date1;
4645     $date1=$date2;
4646     $date2=$tmp;
4647     } else {
4648     $sign="+";
4649     }
4650     if (&Date_Cmp($date1,$date2)==0) {
4651     return "+0:+0:+0:+0:+0:+0:+0" if ($Cnf{"DeltaSigns"});
4652     return "+0:0:0:0:0:0:0";
4653     }
4654    
4655     my($y1,$m1,$d1,$h1,$mn1,$s1)=&Date_Split($date1, 1);
4656     my($y2,$m2,$d2,$h2,$mn2,$s2)=&Date_Split($date2, 1);
4657     my($dy,$dm,$dw,$dd,$dh,$dmn,$ds,$ddd)=(0,0,0,0,0,0,0,0);
4658    
4659     if ($mode != 3) {
4660    
4661     # Do years
4662     $dy=$y2-$y1;
4663     $dm=0;
4664     if ($dy>0) {
4665     $tmp=&DateCalc_DateDelta($date1,"+$dy:0:0:0:0:0:0",\$err,0);
4666     if (&Date_Cmp($tmp,$date2)>0) {
4667     $dy--;
4668     $tmp=$date1;
4669     $tmp=&DateCalc_DateDelta($date1,"+$dy:0:0:0:0:0:0",\$err,0)
4670     if ($dy>0);
4671     $dm=12;
4672     }
4673     $date1=$tmp;
4674     }
4675    
4676     # Do months
4677     $dm+=$m2-$m1;
4678     if ($dm>0) {
4679     $tmp=&DateCalc_DateDelta($date1,"+0:$dm:0:0:0:0:0",\$err,0);
4680     if (&Date_Cmp($tmp,$date2)>0) {
4681     $dm--;
4682     $tmp=$date1;
4683     $tmp=&DateCalc_DateDelta($date1,"+0:$dm:0:0:0:0:0",\$err,0)
4684     if ($dm>0);
4685     }
4686     $date1=$tmp;
4687     }
4688    
4689     # At this point, check to see that we're on a business day again so that
4690     # Aug 3 (Monday) -> Sep 3 (Sunday) -> Sep 4 (Monday) = 1 month
4691     if ($mode==2) {
4692     if (! &Date_IsWorkDay($date1,0)) {
4693     $date1=&Date_NextWorkDay($date1,0,1);
4694     }
4695     }
4696     }
4697    
4698     # Do days
4699     if ($mode==2 || $mode==3) {
4700     $dd=0;
4701     while (1) {
4702     $tmp=&Date_NextWorkDay($date1,1,1);
4703     if (&Date_Cmp($tmp,$date2)<=0) {
4704     $dd++;
4705     $date1=$tmp;
4706     } else {
4707     last;
4708     }
4709     }
4710    
4711     } else {
4712     ($y1,$m1,$d1)=( &Date_Split($date1, 1) )[0..2];
4713     $dd=0;
4714     # If we're jumping across months, set $d1 to the first of the next month
4715     # (or possibly the 0th of next month which is equivalent to the last day
4716     # of this month)
4717     if ($m1!=$m2) {
4718     $d_in_m[2]=29 if (&Date_LeapYear($y1));
4719     $dd=$d_in_m[$m1]-$d1+1;
4720     $d1=1;
4721     $tmp=&DateCalc_DateDelta($date1,"+0:0:0:$dd:0:0:0",\$err,0);
4722     if (&Date_Cmp($tmp,$date2)>0) {
4723     $dd--;
4724     $d1--;
4725     $tmp=&DateCalc_DateDelta($date1,"+0:0:0:$dd:0:0:0",\$err,0);
4726     }
4727     $date1=$tmp;
4728     }
4729    
4730     $ddd=0;
4731     if ($d1<$d2) {
4732     $ddd=$d2-$d1;
4733     $tmp=&DateCalc_DateDelta($date1,"+0:0:0:$ddd:0:0:0",\$err,0);
4734     if (&Date_Cmp($tmp,$date2)>0) {
4735     $ddd--;
4736     $tmp=&DateCalc_DateDelta($date1,"+0:0:0:$ddd:0:0:0",\$err,0);
4737     }
4738     $date1=$tmp;
4739     }
4740     $dd+=$ddd;
4741     }
4742    
4743     # in business mode, make sure h1 comes before h2 (if not find delta between
4744     # now and end of day and move to start of next business day)
4745     $d1=( &Date_Split($date1, 1) )[2];
4746     $dh=$dmn=$ds=0;
4747     if ($mode==2 || $mode==3 and $d1 != $d2) {
4748     $tmp=&Date_SetTime($date1,$Cnf{"WorkDayEnd"});
4749     $tmp=&DateCalc_DateDelta($tmp,"+0:0:0:0:0:1:0")
4750     if ($Cnf{"WorkDay24Hr"});
4751     $tmp=&DateCalc_DateDate($date1,$tmp,0);
4752     ($tmp,$tmp,$tmp,$tmp,$dh,$dmn,$ds)=&Delta_Split($tmp);
4753     $date1=&Date_NextWorkDay($date1,1,0);
4754     $date1=&Date_SetTime($date1,$Cnf{"WorkDayBeg"});
4755     $d1=( &Date_Split($date1, 1) )[2];
4756     confess "ERROR: DateCalc DateDate Business.\n" if ($d1 != $d2);
4757     }
4758    
4759     # Hours, minutes, seconds
4760     $tmp=&DateCalc_DateDate($date1,$date2,0);
4761     @tmp=&Delta_Split($tmp);
4762     $dh += $tmp[4];
4763     $dmn += $tmp[5];
4764     $ds += $tmp[6];
4765    
4766     $tmp="$sign$dy:$dm:0:$dd:$dh:$dmn:$ds";
4767     &Delta_Normalize($tmp,$mode);
4768     }
4769    
4770     sub DateCalc_DeltaDelta {
4771     print "DEBUG: DateCalc_DeltaDelta\n" if ($Curr{"Debug"} =~ /trace/);
4772     my($D1,$D2,$mode)=@_;
4773     my(@delta1,@delta2,$i,$delta,@delta)=();
4774     $mode=0 if (! defined $mode);
4775    
4776     @delta1=&Delta_Split($D1);
4777     @delta2=&Delta_Split($D2);
4778     for ($i=0; $i<7; $i++) {
4779     $delta[$i]=$delta1[$i]+$delta2[$i];
4780     $delta[$i]="+".$delta[$i] if ($delta[$i]>=0);
4781     }
4782    
4783     $delta=join(":",@delta);
4784     $delta=&Delta_Normalize($delta,$mode);
4785     return $delta;
4786     }
4787    
4788     sub DateCalc_DateDelta {
4789     print "DEBUG: DateCalc_DateDelta\n" if ($Curr{"Debug"} =~ /trace/);
4790     my($D1,$D2,$errref,$mode)=@_;
4791     my($date)=();
4792     my(@d_in_m)=(0,31,28,31,30,31,30,31,31,30,31,30,31);
4793     my($h1,$m1,$h2,$m2,$len,$hh,$mm)=();
4794     $mode=0 if (! defined $mode);
4795    
4796     if ($mode==2 || $mode==3) {
4797     $h1=$Curr{"WDBh"};
4798     $m1=$Curr{"WDBm"};
4799     $h2=$Curr{"WDEh"};
4800     $m2=$Curr{"WDEm"};
4801     $hh=$h2-$h1;
4802     $mm=$m2-$m1;
4803     if ($mm<0) {
4804     $hh--;
4805     $mm+=60;
4806     }
4807     }
4808    
4809     # Date, delta
4810     my($y,$m,$d,$h,$mn,$s)=&Date_Split($D1, 1);
4811     my($dy,$dm,$dw,$dd,$dh,$dmn,$ds)=&Delta_Split($D2);
4812    
4813     # do the month/year part
4814     $y+=$dy;
4815     while (length($y)<4) {
4816     $y = "0$y";
4817     }
4818     &ModuloAddition(-12,$dm,\$m,\$y); # -12 means 1-12 instead of 0-11
4819     $d_in_m[2]=29 if (&Date_LeapYear($y));
4820    
4821     # if we have gone past the last day of a month, move the date back to
4822     # the last day of the month
4823     if ($d>$d_in_m[$m]) {
4824     $d=$d_in_m[$m];
4825     }
4826    
4827     # do the week part
4828     if ($mode==0 || $mode==1) {
4829     $dd += $dw*7;
4830     } else {
4831     $date=&DateCalc_DateDelta(&Date_Join($y,$m,$d,$h,$mn,$s),
4832     "+0:0:$dw:0:0:0:0",0);
4833     ($y,$m,$d,$h,$mn,$s)=&Date_Split($date, 1);
4834     }
4835    
4836     # in business mode, set the day to a work day at this point so the h/mn/s
4837     # stuff will work out
4838     if ($mode==2 || $mode==3) {
4839     $d=$d_in_m[$m] if ($d>$d_in_m[$m]);
4840     $date=&Date_NextWorkDay(&Date_Join($y,$m,$d,$h,$mn,$s),0,1);
4841     ($y,$m,$d,$h,$mn,$s)=&Date_Split($date, 1);
4842     }
4843    
4844     # seconds, minutes, hours
4845     &ModuloAddition(60,$ds,\$s,\$mn);
4846     if ($mode==2 || $mode==3) {
4847     while (1) {
4848     &ModuloAddition(60,$dmn,\$mn,\$h);
4849     $h+= $dh;
4850    
4851     if ($h>$h2 or $h==$h2 && $mn>$m2) {
4852     $dh=$h-$h2;
4853     $dmn=$mn-$m2;
4854     $h=$h1;
4855     $mn=$m1;
4856     $dd++;
4857    
4858     } elsif ($h<$h1 or $h==$h1 && $mn<$m1) {
4859     $dh=$h-$h1;
4860     $dmn=$m1-$mn;
4861     $h=$h2;
4862     $mn=$m2;
4863     $dd--;
4864    
4865     } elsif ($h==$h2 && $mn==$m2) {
4866     $dd++;
4867     $dh=-$hh;
4868     $dmn=-$mm;
4869    
4870     } else {
4871     last;
4872     }
4873     }
4874    
4875     } else {
4876     &ModuloAddition(60,$dmn,\$mn,\$h);
4877     &ModuloAddition(24,$dh,\$h,\$d);
4878     }
4879    
4880     # If we have just gone past the last day of the month, we need to make
4881     # up for this:
4882     if ($d>$d_in_m[$m]) {
4883     $dd+= $d-$d_in_m[$m];
4884     $d=$d_in_m[$m];
4885     }
4886    
4887     # days
4888     if ($mode==2 || $mode==3) {
4889     if ($dd>=0) {
4890     $date=&Date_NextWorkDay(&Date_Join($y,$m,$d,$h,$mn,$s),$dd,1);
4891     } else {
4892     $date=&Date_PrevWorkDay(&Date_Join($y,$m,$d,$h,$mn,$s),-$dd,1);
4893     }
4894     ($y,$m,$d,$h,$mn,$s)=&Date_Split($date, 1);
4895    
4896     } else {
4897     $d_in_m[2]=29 if (&Date_LeapYear($y));
4898     $d=$d_in_m[$m] if ($d>$d_in_m[$m]);
4899     $d += $dd;
4900     while ($d<1) {
4901     $m--;
4902     if ($m==0) {
4903     $m=12;
4904     $y--;
4905     if (&Date_LeapYear($y)) {
4906     $d_in_m[2]=29;
4907     } else {
4908     $d_in_m[2]=28;
4909     }
4910     }
4911     $d += $d_in_m[$m];
4912     }
4913     while ($d>$d_in_m[$m]) {
4914     $d -= $d_in_m[$m];
4915     $m++;
4916     if ($m==13) {
4917     $m=1;
4918     $y++;
4919     if (&Date_LeapYear($y)) {
4920     $d_in_m[2]=29;
4921     } else {
4922     $d_in_m[2]=28;
4923     }
4924     }
4925     }
4926     }
4927    
4928     if ($y<0 or $y>9999) {
4929     $$errref=3;
4930     return;
4931     }
4932     &Date_Join($y,$m,$d,$h,$mn,$s);
4933     }
4934    
4935     sub Date_UpdateHolidays {
4936     print "DEBUG: Date_UpdateHolidays\n" if ($Curr{"Debug"} =~ /trace/);
4937     my($year)=@_;
4938     $Holiday{"year"}=$year;
4939     $Holiday{"dates"}{$year}={};
4940    
4941     my($date,$delta,$err)=();
4942     my($key,@tmp,$tmp);
4943    
4944     foreach $key (keys %{ $Holiday{"desc"} }) {
4945     @tmp=&Recur_Split($key);
4946     if (@tmp) {
4947     $tmp=&ParseDateString("${year}010100:00:00");
4948     ($date)=&ParseRecur($key,$tmp,$tmp,($year+1)."-01-01");
4949     next if (! $date);
4950    
4951     } elsif ($key =~ /^(.*)([+-].*)$/) {
4952     # Date +/- Delta
4953     ($date,$delta)=($1,$2);
4954     $tmp=&ParseDateString("$date $year");
4955     if ($tmp) {
4956     $date=$tmp;
4957     } else {
4958     $date=&ParseDateString($date);
4959     next if ($date !~ /^$year/);
4960     }
4961     $date=&DateCalc($date,$delta,\$err,0);
4962    
4963     } else {
4964     # Date
4965     $date=$key;
4966     $tmp=&ParseDateString("$date $year");
4967     if ($tmp) {
4968     $date=$tmp;
4969     } else {
4970     $date=&ParseDateString($date);
4971     next if ($date !~ /^$year/);
4972     }
4973     }
4974     $Holiday{"dates"}{$year}{$date}=$Holiday{"desc"}{$key};
4975     }
4976     }
4977    
4978     # This sets a Date::Manip config variable.
4979     sub Date_SetConfigVariable {
4980     print "DEBUG: Date_SetConfigVariable\n" if ($Curr{"Debug"} =~ /trace/);
4981     my($var,$val)=@_;
4982    
4983     # These are most appropriate for command line options instead of in files.
4984     $Cnf{"PathSep"}=$val, return if ($var =~ /^PathSep$/i);
4985     $Cnf{"PersonalCnf"}=$val, return if ($var =~ /^PersonalCnf$/i);
4986     $Cnf{"PersonalCnfPath"}=$val, return if ($var =~ /^PersonalCnfPath$/i);
4987     &EraseHolidays(), return if ($var =~ /^EraseHolidays$/i);
4988     $Cnf{"IgnoreGlobalCnf"}=1, return if ($var =~ /^IgnoreGlobalCnf$/i);
4989     $Cnf{"GlobalCnf"}=$val, return if ($var =~ /^GlobalCnf$/i);
4990    
4991     $Curr{"InitLang"}=1,
4992     $Cnf{"Language"}=$val, return if ($var =~ /^Language$/i);
4993     $Cnf{"DateFormat"}=$val, return if ($var =~ /^DateFormat$/i);
4994     $Cnf{"TZ"}=$val, return if ($var =~ /^TZ$/i);
4995     $Cnf{"ConvTZ"}=$val, return if ($var =~ /^ConvTZ$/i);
4996     $Cnf{"Internal"}=$val, return if ($var =~ /^Internal$/i);
4997     $Cnf{"FirstDay"}=$val, return if ($var =~ /^FirstDay$/i);
4998     $Cnf{"WorkWeekBeg"}=$val, return if ($var =~ /^WorkWeekBeg$/i);
4999     $Cnf{"WorkWeekEnd"}=$val, return if ($var =~ /^WorkWeekEnd$/i);
5000     $Cnf{"WorkDayBeg"}=$val,
5001     $Curr{"ResetWorkDay"}=1, return if ($var =~ /^WorkDayBeg$/i);
5002     $Cnf{"WorkDayEnd"}=$val,
5003     $Curr{"ResetWorkDay"}=1, return if ($var =~ /^WorkDayEnd$/i);
5004     $Cnf{"WorkDay24Hr"}=$val,
5005     $Curr{"ResetWorkDay"}=1, return if ($var =~ /^WorkDay24Hr$/i);
5006     $Cnf{"DeltaSigns"}=$val, return if ($var =~ /^DeltaSigns$/i);
5007     $Cnf{"Jan1Week1"}=$val, return if ($var =~ /^Jan1Week1$/i);
5008     $Cnf{"YYtoYYYY"}=$val, return if ($var =~ /^YYtoYYYY$/i);
5009     $Cnf{"UpdateCurrTZ"}=$val, return if ($var =~ /^UpdateCurrTZ$/i);
5010     $Cnf{"IntCharSet"}=$val, return if ($var =~ /^IntCharSet$/i);
5011     $Curr{"DebugVal"}=$val, return if ($var =~ /^Debug$/i);
5012     $Cnf{"TomorrowFirst"}=$val, return if ($var =~ /^TomorrowFirst$/i);
5013     $Cnf{"ForceDate"}=$val, return if ($var =~ /^ForceDate$/i);
5014    
5015     confess "ERROR: Unknown configuration variable $var in Date::Manip.\n";
5016     }
5017    
5018     sub EraseHolidays {
5019     print "DEBUG: EraseHolidays\n" if ($Curr{"Debug"} =~ /trace/);
5020    
5021     $Cnf{"EraseHolidays"}=0;
5022     delete $Holiday{"list"};
5023     $Holiday{"list"}={};
5024     delete $Holiday{"desc"};
5025     $Holiday{"desc"}={};
5026     $Holiday{"dates"}={};
5027     }
5028    
5029     # This returns a pointer to a list of times and events in the format
5030     # [ date, [ events ], date, [ events ], ... ]
5031     # where each list of events are events that are in effect at the date
5032     # immediately preceding the list.
5033     #
5034     # This takes either one date or two dates as arguments.
5035     sub Events_Calc {
5036     print "DEBUG: Events_Calc\n" if ($Curr{"Debug"} =~ /trace/);
5037    
5038     my($date0,$date1)=@_;
5039    
5040     my($tmp);
5041     $date0=&ParseDateString($date0);
5042     return undef if (! $date0);
5043     if ($date1) {
5044     $date1=&ParseDateString($date1);
5045     if (&Date_Cmp($date0,$date1)>0) {
5046     $tmp=$date1;
5047     $date1=$date0;
5048     $date0=$tmp;
5049     }
5050     } else {
5051     $date1=&DateCalc_DateDelta($date0,"+0:0:0:0:0:0:1");
5052     }
5053    
5054     #
5055     # [ d0,d1,del,name ] => [ d0, d1+del )
5056     # [ d0,0,del,name ] => [ d0, d0+del )
5057     #
5058     my(%ret,$d0,$d1,$del,$name,$c0,$c1);
5059     my(@tmp)=@{ $Events{"dates"} };
5060     DATE: while (@tmp) {
5061     ($d0,$d1,$del,$name)=splice(@tmp,0,4);
5062     $d0=&ParseDateString($d0);
5063     $d1=&ParseDateString($d1) if ($d1);
5064     $del=&ParseDateDelta($del) if ($del);
5065     if ($d1) {
5066     if ($del) {
5067     $d1=&DateCalc_DateDelta($d1,$del);
5068     }
5069     } else {
5070     $d1=&DateCalc_DateDelta($d0,$del);
5071     }
5072     if (&Date_Cmp($d0,$d1)>0) {
5073     $tmp=$d1;
5074     $d1=$d0;
5075     $d0=$tmp;
5076     }
5077     # [ date0,date1 )
5078     # [ d0,d1 ) OR [ d0,d1 )
5079     next DATE if (&Date_Cmp($d1,$date0)<=0 ||
5080     &Date_Cmp($d0,$date1)>=0);
5081     # [ date0,date1 )
5082     # [ d0,d1 )
5083     # [ d0, d1 )
5084     if (&Date_Cmp($d0,$date0)<=0) {
5085     push @{ $ret{$date0} },$name;
5086     push @{ $ret{$d1} },"!$name" if (&Date_Cmp($d1,$date1)<0);
5087     next DATE;
5088     }
5089     # [ date0,date1 )
5090     # [ d0,d1 )
5091     if (&Date_Cmp($d1,$date1)>=0) {
5092     push @{ $ret{$d0} },$name;
5093     next DATE;
5094     }
5095     # [ date0,date1 )
5096     # [ d0,d1 )
5097     push @{ $ret{$d0} },$name;
5098     push @{ $ret{$d1} },"!$name";
5099     }
5100    
5101     #
5102     # [ recur,delta0,delta1,name ] => [ {date-delta0},{date+delta1} )
5103     #
5104     my($rec,$del0,$del1,@d);
5105     @tmp=@{ $Events{"recur"} };
5106     RECUR: while (@tmp) {
5107     ($rec,$del0,$del1,$name)=splice(@tmp,0,4);
5108     @d=();
5109    
5110     }
5111    
5112     # Sort them AND take into account the "!$name" entries.
5113     my(%tmp,$date,@tmp2,@ret);
5114     @d=sort { &Date_Cmp($a,$b) } keys %ret;
5115     foreach $date (@d) {
5116     @tmp=@{ $ret{$date} };
5117     @tmp2=();
5118     foreach $tmp (@tmp) {
5119     push(@tmp2,$tmp), next if ($tmp =~ /^!/);
5120     $tmp{$tmp}=1;
5121     }
5122     foreach $tmp (@tmp2) {
5123     $tmp =~ s/^!//;
5124     delete $tmp{$tmp};
5125     }
5126     push(@ret,$date,[ keys %tmp ]);
5127     }
5128    
5129     return \@ret;
5130     }
5131    
5132     # This parses the raw events list
5133     sub Events_ParseRaw {
5134     print "DEBUG: Events_ParseRaw\n" if ($Curr{"Debug"} =~ /trace/);
5135    
5136     # Only need to be parsed once
5137     my($force)=@_;
5138     $Events{"parsed"}=0 if ($force);
5139     return if ($Events{"parsed"});
5140     $Events{"parsed"}=1;
5141    
5142     my(@events)=@{ $Events{"raw"} };
5143     my($event,$name,@event,$date0,$date1,$tmp,$delta,$recur0,$recur1,@recur,$r,
5144     $recur);
5145     EVENT: while (@events) {
5146     ($event,$name)=splice(@events,0,2);
5147     @event=split(/\s*;\s*/,$event);
5148    
5149     if ($#event == 0) {
5150    
5151     if ($date0=&ParseDateString($event[0])) {
5152     #
5153     # date = event
5154     #
5155     $tmp=&ParseDateString("$event[0] 00:00:00");
5156     if ($tmp && $tmp eq $date0) {
5157     $delta="+0:0:0:1:0:0:0";
5158     } else {
5159     $delta="+0:0:0:0:1:0:0";
5160     }
5161     push @{ $Events{"dates"} },($date0,0,$delta,$name);
5162    
5163     } elsif ($recur=&ParseRecur($event[0])) {
5164     #
5165     # recur = event
5166     #
5167     ($recur0,$recur1)=&Recur_Split($recur);
5168     if ($recur0) {
5169     if ($recur1) {
5170     $r="$recur0:$recur1";
5171     } else {
5172     $r=$recur0;
5173     }
5174     } else {
5175     $r=$recur1;
5176     }
5177     (@recur)=split(/:/,$r);
5178     if (pop(@recur)==0 && pop(@recur)==0 && pop(@recur)==0) {
5179     $delta="+0:0:0:1:0:0:0";
5180     } else {
5181     $delta="+0:0:0:0:1:0:0";
5182     }
5183     push @{ $Events{"recur"} },($recur,0,$delta,$name);
5184    
5185     } else {
5186     # ??? = event
5187     warn "WARNING: illegal event ignored [ @event ]\n";
5188     next EVENT;
5189     }
5190    
5191     } elsif ($#event == 1) {
5192    
5193     if ($date0=&ParseDateString($event[0])) {
5194    
5195     if ($date1=&ParseDateString($event[1])) {
5196     #
5197     # date ; date = event
5198     #
5199     $tmp=&ParseDateString("$event[1] 00:00:00");
5200     if ($tmp && $tmp eq $date1) {
5201     $date1=&DateCalc_DateDelta($date1,"+0:0:0:1:0:0:0");
5202     }
5203     push @{ $Events{"dates"} },($date0,$date1,0,$name);
5204    
5205     } elsif ($delta=&ParseDateDelta($event[1])) {
5206     #
5207     # date ; delta = event
5208     #
5209     push @{ $Events{"dates"} },($date0,0,$delta,$name);
5210    
5211     } else {
5212     # date ; ??? = event
5213     warn "WARNING: illegal event ignored [ @event ]\n";
5214     next EVENT;
5215     }
5216    
5217     } elsif ($recur=&ParseRecur($event[0])) {
5218    
5219     if ($delta=&ParseDateDelta($event[1])) {
5220     #
5221     # recur ; delta = event
5222     #
5223     push @{ $Events{"recur"} },($recur,0,$delta,$name);
5224    
5225     } else {
5226     # recur ; ??? = event
5227     warn "WARNING: illegal event ignored [ @event ]\n";
5228     next EVENT;
5229     }
5230    
5231     } else {
5232     # ??? ; ??? = event
5233     warn "WARNING: illegal event ignored [ @event ]\n";
5234     next EVENT;
5235     }
5236    
5237     } else {
5238     # date ; delta0 ; delta1 = event
5239     # recur ; delta0 ; delta1 = event
5240     # ??? ; ??? ; ??? ... = event
5241     warn "WARNING: illegal event ignored [ @event ]\n";
5242     next EVENT;
5243     }
5244     }
5245     }
5246    
5247     # This reads an init file.
5248     sub Date_InitFile {
5249     print "DEBUG: Date_InitFile\n" if ($Curr{"Debug"} =~ /trace/);
5250     my($file)=@_;
5251     my($in)=new IO::File;
5252     local($_)=();
5253     my($section)="vars";
5254     my($var,$val,$recur,$name)=();
5255    
5256     $in->open($file) || return;
5257     while(defined ($_=<$in>)) {
5258     chomp;
5259     s/^\s+//;
5260     s/\s+$//;
5261     next if (! $_ or /^\#/);
5262    
5263     if (/^\*holiday/i) {
5264     $section="holiday";
5265     &EraseHolidays() if ($section =~ /holiday/i && $Cnf{"EraseHolidays"});
5266     next;
5267     } elsif (/^\*events/i) {
5268     $section="events";
5269     next;
5270     }
5271    
5272     if ($section =~ /var/i) {
5273     confess "ERROR: invalid Date::Manip config file line.\n $_\n"
5274     if (! /(.*\S)\s*=\s*(.*)$/);
5275     ($var,$val)=($1,$2);
5276     &Date_SetConfigVariable($var,$val);
5277    
5278     } elsif ($section =~ /holiday/i) {
5279     confess "ERROR: invalid Date::Manip config file line.\n $_\n"
5280     if (! /(.*\S)\s*=\s*(.*)$/);
5281     ($recur,$name)=($1,$2);
5282     $name="" if (! defined $name);
5283     $Holiday{"desc"}{$recur}=$name;
5284    
5285     } elsif ($section =~ /events/i) {
5286     confess "ERROR: invalid Date::Manip config file line.\n $_\n"
5287     if (! /(.*\S)\s*=\s*(.*)$/);
5288     ($val,$var)=($1,$2);
5289     push @{ $Events{"raw"} },($val,$var);
5290    
5291     } else {
5292     # A section not currently used by Date::Manip (but may be
5293     # used by some extension to it).
5294     next;
5295     }
5296     }
5297     close($in);
5298     }
5299    
5300     # $flag=&Date_TimeCheck(\$h,\$mn,\$s,\$ampm);
5301     # Returns 1 if any of the fields are bad. All fields are optional, and
5302     # all possible checks are done on the data. If a field is not passed in,
5303     # it is set to default values. If data is missing, appropriate defaults
5304     # are supplied.
5305     sub Date_TimeCheck {
5306     print "DEBUG: Date_TimeCheck\n" if ($Curr{"Debug"} =~ /trace/);
5307     my($h,$mn,$s,$ampm)=@_;
5308     my($tmp1,$tmp2,$tmp3)=();
5309    
5310     $$h="" if (! defined $$h);
5311     $$mn="" if (! defined $$mn);
5312     $$s="" if (! defined $$s);
5313     $$ampm="" if (! defined $$ampm);
5314     $$ampm=uc($$ampm) if ($$ampm);
5315    
5316     # Check hour
5317     $tmp1=$Lang{$Cnf{"Language"}}{"AmPm"};
5318     $tmp2="";
5319     if ($$ampm =~ /^$tmp1$/i) {
5320     $tmp3=$Lang{$Cnf{"Language"}}{"AM"};
5321     $tmp2="AM" if ($$ampm =~ /^$tmp3$/i);
5322     $tmp3=$Lang{$Cnf{"Language"}}{"PM"};
5323     $tmp2="PM" if ($$ampm =~ /^$tmp3$/i);
5324     } elsif ($$ampm) {
5325     return 1;
5326     }
5327     if ($tmp2 eq "AM" || $tmp2 eq "PM") {
5328     $$h="0$$h" if (length($$h)==1);
5329     return 1 if ($$h<1 || $$h>12);
5330     $$h="00" if ($tmp2 eq "AM" and $$h==12);
5331     $$h += 12 if ($tmp2 eq "PM" and $$h!=12);
5332     } else {
5333     $$h="00" if ($$h eq "");
5334     $$h="0$$h" if (length($$h)==1);
5335     return 1 if (! &IsInt($$h,0,23));
5336     $tmp2="AM" if ($$h<12);
5337     $tmp2="PM" if ($$h>=12);
5338     }
5339     $$ampm=$Lang{$Cnf{"Language"}}{"AMstr"};
5340     $$ampm=$Lang{$Cnf{"Language"}}{"PMstr"} if ($tmp2 eq "PM");
5341    
5342     # Check minutes
5343     $$mn="00" if ($$mn eq "");
5344     $$mn="0$$mn" if (length($$mn)==1);
5345     return 1 if (! &IsInt($$mn,0,59));
5346    
5347     # Check seconds
5348     $$s="00" if ($$s eq "");
5349     $$s="0$$s" if (length($$s)==1);
5350     return 1 if (! &IsInt($$s,0,59));
5351    
5352     return 0;
5353     }
5354    
5355     # $flag=&Date_DateCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk);
5356     # Returns 1 if any of the fields are bad. All fields are optional, and
5357     # all possible checks are done on the data. If a field is not passed in,
5358     # it is set to default values. If data is missing, appropriate defaults
5359     # are supplied.
5360     #
5361     # If the flag UpdateHolidays is set, the year is set to
5362     # CurrHolidayYear.
5363     sub Date_DateCheck {
5364     print "DEBUG: Date_DateCheck\n" if ($Curr{"Debug"} =~ /trace/);
5365     my($y,$m,$d,$h,$mn,$s,$ampm,$wk)=@_;
5366     my($tmp1,$tmp2,$tmp3)=();
5367    
5368     my(@d_in_m)=(0,31,28,31,30,31,30,31,31,30,31,30,31);
5369     my($curr_y)=$Curr{"Y"};
5370     my($curr_m)=$Curr{"M"};
5371     my($curr_d)=$Curr{"D"};
5372     $$m=1, $$d=1 if (defined $$y and ! defined $$m and ! defined $$d);
5373     $$y="" if (! defined $$y);
5374     $$m="" if (! defined $$m);
5375     $$d="" if (! defined $$d);
5376     $$wk="" if (! defined $$wk);
5377     $$d=$curr_d if ($$y eq "" and $$m eq "" and $$d eq "");
5378    
5379     # Check year.
5380     $$y=$curr_y if ($$y eq "");
5381     $$y=&Date_FixYear($$y) if (length($$y)<4);
5382     return 1 if (! &IsInt($$y,0,9999));
5383     $d_in_m[2]=29 if (&Date_LeapYear($$y));
5384    
5385     # Check month
5386     $$m=$curr_m if ($$m eq "");
5387     $$m=$Lang{$Cnf{"Language"}}{"MonthH"}{lc($$m)}
5388     if (exists $Lang{$Cnf{"Language"}}{"MonthH"}{lc($$m)});
5389     $$m="0$$m" if (length($$m)==1);
5390     return 1 if (! &IsInt($$m,1,12));
5391    
5392     # Check day
5393     $$d="01" if ($$d eq "");
5394     $$d="0$$d" if (length($$d)==1);
5395     return 1 if (! &IsInt($$d,1,$d_in_m[$$m]));
5396     if ($$wk) {
5397     $tmp1=&Date_DayOfWeek($$m,$$d,$$y);
5398     $tmp2=$Lang{$Cnf{"Language"}}{"WeekH"}{lc($$wk)}
5399     if (exists $Lang{$Cnf{"Language"}}{"WeekH"}{lc($$wk)});
5400     return 1 if ($tmp1 != $tmp2);
5401     }
5402    
5403     return &Date_TimeCheck($h,$mn,$s,$ampm);
5404     }
5405    
5406     # Takes a year in 2 digit form and returns it in 4 digit form
5407     sub Date_FixYear {
5408     print "DEBUG: Date_FixYear\n" if ($Curr{"Debug"} =~ /trace/);
5409     my($y)=@_;
5410     my($curr_y)=$Curr{"Y"};
5411     $y=$curr_y if (! defined $y or ! $y);
5412     return $y if (length($y)==4);
5413     confess "ERROR: Invalid year ($y)\n" if (length($y)!=2);
5414     my($y1,$y2)=();
5415    
5416     if (lc($Cnf{"YYtoYYYY"}) eq "c") {
5417     $y1=substring($y,0,2);
5418     $y="$y1$y";
5419    
5420     } elsif ($Cnf{"YYtoYYYY"} =~ /^c(\d{2})$/i) {
5421     $y1=$1;
5422     $y="$y1$y";
5423    
5424     } elsif ($Cnf{"YYtoYYYY"} =~ /^c(\d{2})(\d{2})$/i) {
5425     $y1="$1$2";
5426     $y ="$1$y";
5427     $y += 100 if ($y<$y1);
5428    
5429     } else {
5430     $y1=$curr_y-$Cnf{"YYtoYYYY"};
5431     $y2=$y1+99;
5432     $y="19$y";
5433     while ($y<$y1) {
5434     $y+=100;
5435     }
5436     while ($y>$y2) {
5437     $y-=100;
5438     }
5439     }
5440     $y;
5441     }
5442    
5443     # &Date_NthWeekOfYear($y,$n);
5444     # Returns a list of (YYYY,MM,DD) for the 1st day of the Nth week of the
5445     # year.
5446     # &Date_NthWeekOfYear($y,$n,$dow,$flag);
5447     # Returns a list of (YYYY,MM,DD) for the Nth DoW of the year. If flag
5448     # is nil, the first DoW of the year may actually be in the previous
5449     # year (since the 1st week may include days from the previous year).
5450     # If flag is non-nil, the 1st DoW of the year refers to the 1st one
5451     # actually in the year
5452     sub Date_NthWeekOfYear {
5453     print "DEBUG: Date_NthWeekOfYear\n" if ($Curr{"Debug"} =~ /trace/);
5454     my($y,$n,$dow,$flag)=@_;
5455     my($m,$d,$err,$tmp,$date,%dow)=();
5456     $y=$Curr{"Y"} if (! defined $y or ! $y);
5457     $n=1 if (! defined $n or $n eq "");
5458     return () if ($n<0 || $n>53);
5459     if (defined $dow) {
5460     $dow=lc($dow);
5461     %dow=%{ $Lang{$Cnf{"Language"}}{"WeekH"} };
5462     $dow=$dow{$dow} if (exists $dow{$dow});
5463     return () if ($dow<1 || $dow>7);
5464     $flag="" if (! defined $flag);
5465     } else {
5466     $dow="";
5467     $flag="";
5468     }
5469    
5470     $y=&Date_FixYear($y) if (length($y)<4);
5471     if ($Cnf{"Jan1Week1"}) {
5472     $date=&Date_Join($y,1,1,0,0,0);
5473     } else {
5474     $date=&Date_Join($y,1,4,0,0,0);
5475     }
5476     $date=&Date_GetPrev($date,$Cnf{"FirstDay"},1);
5477     $date=&Date_GetNext($date,$dow,1) if ($dow ne "");
5478    
5479     if ($flag) {
5480     ($tmp)=&Date_Split($date, 1);
5481     $n++ if ($tmp != $y);
5482     }
5483    
5484     if ($n>1) {
5485     $date=&DateCalc_DateDelta($date,"+0:0:". ($n-1) . ":0:0:0:0",\$err,0);
5486     } elsif ($n==0) {
5487     $date=&DateCalc_DateDelta($date,"-0:0:1:0:0:0:0",\$err,0);
5488     }
5489     ($y,$m,$d)=&Date_Split($date, 1);
5490     ($y,$m,$d);
5491     }
5492    
5493     ########################################################################
5494     # LANGUAGE INITIALIZATION
5495     ########################################################################
5496    
5497     # 8-bit international characters can be gotten by "\xXX". I don't know
5498     # how to get 16-bit characters. I've got to read up on perllocale.
5499     sub Char_8Bit {
5500     my($hash)=@_;
5501    
5502     # grave `
5503     # A` 00c0 a` 00e0
5504     # E` 00c8 e` 00e8
5505     # I` 00cc i` 00ec
5506     # O` 00d2 o` 00f2
5507     # U` 00d9 u` 00f9
5508     # W` 1e80 w` 1e81
5509     # Y` 1ef2 y` 1ef3
5510    
5511     $$hash{"A`"} = "\xc0"; # LATIN CAPITAL LETTER A WITH GRAVE
5512     $$hash{"E`"} = "\xc8"; # LATIN CAPITAL LETTER E WITH GRAVE
5513     $$hash{"I`"} = "\xcc"; # LATIN CAPITAL LETTER I WITH GRAVE
5514     $$hash{"O`"} = "\xd2"; # LATIN CAPITAL LETTER O WITH GRAVE
5515     $$hash{"U`"} = "\xd9"; # LATIN CAPITAL LETTER U WITH GRAVE
5516     $$hash{"a`"} = "\xe0"; # LATIN SMALL LETTER A WITH GRAVE
5517     $$hash{"e`"} = "\xe8"; # LATIN SMALL LETTER E WITH GRAVE
5518     $$hash{"i`"} = "\xec"; # LATIN SMALL LETTER I WITH GRAVE
5519     $$hash{"o`"} = "\xf2"; # LATIN SMALL LETTER O WITH GRAVE
5520     $$hash{"u`"} = "\xf9"; # LATIN SMALL LETTER U WITH GRAVE
5521    
5522     # acute '
5523     # A' 00c1 a' 00e1
5524     # C' 0106 c' 0107
5525     # E' 00c9 e' 00e9
5526     # I' 00cd i' 00ed
5527     # L' 0139 l' 013a
5528     # N' 0143 n' 0144
5529     # O' 00d3 o' 00f3
5530     # R' 0154 r' 0155
5531     # S' 015a s' 015b
5532     # U' 00da u' 00fa
5533     # W' 1e82 w' 1e83
5534     # Y' 00dd y' 00fd
5535     # Z' 0179 z' 017a
5536    
5537     $$hash{"A'"} = "\xc1"; # LATIN CAPITAL LETTER A WITH ACUTE
5538     $$hash{"E'"} = "\xc9"; # LATIN CAPITAL LETTER E WITH ACUTE
5539     $$hash{"I'"} = "\xcd"; # LATIN CAPITAL LETTER I WITH ACUTE
5540     $$hash{"O'"} = "\xd3"; # LATIN CAPITAL LETTER O WITH ACUTE
5541     $$hash{"U'"} = "\xda"; # LATIN CAPITAL LETTER U WITH ACUTE
5542     $$hash{"Y'"} = "\xdd"; # LATIN CAPITAL LETTER Y WITH ACUTE
5543     $$hash{"a'"} = "\xe1"; # LATIN SMALL LETTER A WITH ACUTE
5544     $$hash{"e'"} = "\xe9"; # LATIN SMALL LETTER E WITH ACUTE
5545     $$hash{"i'"} = "\xed"; # LATIN SMALL LETTER I WITH ACUTE
5546     $$hash{"o'"} = "\xf3"; # LATIN SMALL LETTER O WITH ACUTE
5547     $$hash{"u'"} = "\xfa"; # LATIN SMALL LETTER U WITH ACUTE
5548     $$hash{"y'"} = "\xfd"; # LATIN SMALL LETTER Y WITH ACUTE
5549    
5550     # double acute " "
5551     # O" 0150 o" 0151
5552     # U" 0170 u" 0171
5553    
5554     # circumflex ^
5555     # A^ 00c2 a^ 00e2
5556     # C^ 0108 c^ 0109
5557     # E^ 00ca e^ 00ea
5558     # G^ 011c g^ 011d
5559     # H^ 0124 h^ 0125
5560     # I^ 00ce i^ 00ee
5561     # J^ 0134 j^ 0135
5562     # O^ 00d4 o^ 00f4
5563     # S^ 015c s^ 015d
5564     # U^ 00db u^ 00fb
5565     # W^ 0174 w^ 0175
5566     # Y^ 0176 y^ 0177
5567    
5568     $$hash{"A^"} = "\xc2"; # LATIN CAPITAL LETTER A WITH CIRCUMFLEX
5569     $$hash{"E^"} = "\xca"; # LATIN CAPITAL LETTER E WITH CIRCUMFLEX
5570     $$hash{"I^"} = "\xce"; # LATIN CAPITAL LETTER I WITH CIRCUMFLEX
5571     $$hash{"O^"} = "\xd4"; # LATIN CAPITAL LETTER O WITH CIRCUMFLEX
5572     $$hash{"U^"} = "\xdb"; # LATIN CAPITAL LETTER U WITH CIRCUMFLEX
5573     $$hash{"a^"} = "\xe2"; # LATIN SMALL LETTER A WITH CIRCUMFLEX
5574     $$hash{"e^"} = "\xea"; # LATIN SMALL LETTER E WITH CIRCUMFLEX
5575     $$hash{"i^"} = "\xee"; # LATIN SMALL LETTER I WITH CIRCUMFLEX
5576     $$hash{"o^"} = "\xf4"; # LATIN SMALL LETTER O WITH CIRCUMFLEX
5577     $$hash{"u^"} = "\xfb"; # LATIN SMALL LETTER U WITH CIRCUMFLEX
5578    
5579     # tilde ~
5580     # A~ 00c3 a~ 00e3
5581     # I~ 0128 i~ 0129
5582     # N~ 00d1 n~ 00f1
5583     # O~ 00d5 o~ 00f5
5584     # U~ 0168 u~ 0169
5585    
5586     $$hash{"A~"} = "\xc3"; # LATIN CAPITAL LETTER A WITH TILDE
5587     $$hash{"N~"} = "\xd1"; # LATIN CAPITAL LETTER N WITH TILDE
5588     $$hash{"O~"} = "\xd5"; # LATIN CAPITAL LETTER O WITH TILDE
5589     $$hash{"a~"} = "\xe3"; # LATIN SMALL LETTER A WITH TILDE
5590     $$hash{"n~"} = "\xf1"; # LATIN SMALL LETTER N WITH TILDE
5591     $$hash{"o~"} = "\xf5"; # LATIN SMALL LETTER O WITH TILDE
5592    
5593     # macron -
5594     # A- 0100 a- 0101
5595     # E- 0112 e- 0113
5596     # I- 012a i- 012b
5597     # O- 014c o- 014d
5598     # U- 016a u- 016b
5599    
5600     # breve ( [half circle up]
5601     # A( 0102 a( 0103
5602     # G( 011e g( 011f
5603     # U( 016c u( 016d
5604    
5605     # dot .
5606     # C. 010a c. 010b
5607     # E. 0116 e. 0117
5608     # G. 0120 g. 0121
5609     # I. 0130
5610     # Z. 017b z. 017c
5611    
5612     # diaeresis : [side by side dots]
5613     # A: 00c4 a: 00e4
5614     # E: 00cb e: 00eb
5615     # I: 00cf i: 00ef
5616     # O: 00d6 o: 00f6
5617     # U: 00dc u: 00fc
5618     # W: 1e84 w: 1e85
5619     # Y: 0178 y: 00ff
5620    
5621     $$hash{"A:"} = "\xc4"; # LATIN CAPITAL LETTER A WITH DIAERESIS
5622     $$hash{"E:"} = "\xcb"; # LATIN CAPITAL LETTER E WITH DIAERESIS
5623     $$hash{"I:"} = "\xcf"; # LATIN CAPITAL LETTER I WITH DIAERESIS
5624     $$hash{"O:"} = "\xd6"; # LATIN CAPITAL LETTER O WITH DIAERESIS
5625     $$hash{"U:"} = "\xdc"; # LATIN CAPITAL LETTER U WITH DIAERESIS
5626     $$hash{"a:"} = "\xe4"; # LATIN SMALL LETTER A WITH DIAERESIS
5627     $$hash{"e:"} = "\xeb"; # LATIN SMALL LETTER E WITH DIAERESIS
5628     $$hash{"i:"} = "\xef"; # LATIN SMALL LETTER I WITH DIAERESIS
5629     $$hash{"o:"} = "\xf6"; # LATIN SMALL LETTER O WITH DIAERESIS
5630     $$hash{"u:"} = "\xfc"; # LATIN SMALL LETTER U WITH DIAERESIS
5631     $$hash{"y:"} = "\xff"; # LATIN SMALL LETTER Y WITH DIAERESIS
5632    
5633     # ring o
5634     # U0 016e u0 016f
5635    
5636     # cedilla , [squiggle down and left below the letter]
5637     # ,C 00c7 ,c 00e7
5638     # ,G 0122 ,g 0123
5639     # ,K 0136 ,k 0137
5640     # ,L 013b ,l 013c
5641     # ,N 0145 ,n 0146
5642     # ,R 0156 ,r 0157
5643     # ,S 015e ,s 015f
5644     # ,T 0162 ,t 0163
5645    
5646     $$hash{",C"} = "\xc7"; # LATIN CAPITAL LETTER C WITH CEDILLA
5647     $$hash{",c"} = "\xe7"; # LATIN SMALL LETTER C WITH CEDILLA
5648    
5649     # ogonek ; [squiggle down and right below the letter]
5650     # A; 0104 a; 0105
5651     # E; 0118 e; 0119
5652     # I; 012e i; 012f
5653     # U; 0172 u; 0173
5654    
5655     # caron < [little v on top]
5656     # A< 01cd a< 01ce
5657     # C< 010c c< 010d
5658     # D< 010e d< 010f
5659     # E< 011a e< 011b
5660     # L< 013d l< 013e
5661     # N< 0147 n< 0148
5662     # R< 0158 r< 0159
5663     # S< 0160 s< 0161
5664     # T< 0164 t< 0165
5665     # Z< 017d z< 017e
5666    
5667    
5668     # Other characters
5669    
5670     # First character is below, 2nd character is above
5671     $$hash{"||"} = "\xa6"; # BROKEN BAR
5672     $$hash{" :"} = "\xa8"; # DIAERESIS
5673     $$hash{"-a"} = "\xaa"; # FEMININE ORDINAL INDICATOR
5674     #$$hash{" -"}= "\xaf"; # MACRON (narrow bar)
5675     $$hash{" -"} = "\xad"; # HYPHEN (wide bar)
5676     $$hash{" o"} = "\xb0"; # DEGREE SIGN
5677     $$hash{"-+"} = "\xb1"; # PLUS\342\200\220MINUS SIGN
5678     $$hash{" 1"} = "\xb9"; # SUPERSCRIPT ONE
5679     $$hash{" 2"} = "\xb2"; # SUPERSCRIPT TWO
5680     $$hash{" 3"} = "\xb3"; # SUPERSCRIPT THREE
5681     $$hash{" '"} = "\xb4"; # ACUTE ACCENT
5682     $$hash{"-o"} = "\xba"; # MASCULINE ORDINAL INDICATOR
5683     $$hash{" ."} = "\xb7"; # MIDDLE DOT
5684     $$hash{", "} = "\xb8"; # CEDILLA
5685     $$hash{"Ao"} = "\xc5"; # LATIN CAPITAL LETTER A WITH RING ABOVE
5686     $$hash{"ao"} = "\xe5"; # LATIN SMALL LETTER A WITH RING ABOVE
5687     $$hash{"ox"} = "\xf0"; # LATIN SMALL LETTER ETH
5688    
5689     # upside down characters
5690    
5691     $$hash{"ud!"} = "\xa1"; # INVERTED EXCLAMATION MARK
5692     $$hash{"ud?"} = "\xbf"; # INVERTED QUESTION MARK
5693    
5694     # overlay characters
5695    
5696     $$hash{"X o"} = "\xa4"; # CURRENCY SIGN
5697     $$hash{"Y ="} = "\xa5"; # YEN SIGN
5698     $$hash{"S o"} = "\xa7"; # SECTION SIGN
5699     $$hash{"O c"} = "\xa9"; # COPYRIGHT SIGN Copyright
5700     $$hash{"O R"} = "\xae"; # REGISTERED SIGN
5701     $$hash{"D -"} = "\xd0"; # LATIN CAPITAL LETTER ETH
5702     $$hash{"O /"} = "\xd8"; # LATIN CAPITAL LETTER O WITH STROKE
5703     $$hash{"o /"} = "\xf8"; # LATIN SMALL LETTER O WITH STROKE
5704    
5705     # special names
5706    
5707     $$hash{"1/4"} = "\xbc"; # VULGAR FRACTION ONE QUARTER
5708     $$hash{"1/2"} = "\xbd"; # VULGAR FRACTION ONE HALF
5709     $$hash{"3/4"} = "\xbe"; # VULGAR FRACTION THREE QUARTERS
5710     $$hash{"<<"} = "\xab"; # LEFT POINTING DOUBLE ANGLE QUOTATION MARK
5711     $$hash{">>"} = "\xbb"; # RIGHT POINTING DOUBLE ANGLE QUOTATION MARK
5712     $$hash{"cent"}= "\xa2"; # CENT SIGN
5713     $$hash{"lb"} = "\xa3"; # POUND SIGN
5714     $$hash{"mu"} = "\xb5"; # MICRO SIGN
5715     $$hash{"beta"}= "\xdf"; # LATIN SMALL LETTER SHARP S
5716     $$hash{"para"}= "\xb6"; # PILCROW SIGN
5717     $$hash{"-|"} = "\xac"; # NOT SIGN
5718     $$hash{"AE"} = "\xc6"; # LATIN CAPITAL LETTER AE
5719     $$hash{"ae"} = "\xe6"; # LATIN SMALL LETTER AE
5720     $$hash{"x"} = "\xd7"; # MULTIPLICATION SIGN
5721     $$hash{"P"} = "\xde"; # LATIN CAPITAL LETTER THORN
5722     $$hash{"/"} = "\xf7"; # DIVISION SIGN
5723     $$hash{"p"} = "\xfe"; # LATIN SMALL LETTER THORN
5724     }
5725    
5726     # $hashref = &Date_Init_LANGUAGE;
5727     # This returns a hash containing all of the initialization for a
5728     # specific language. The hash elements are:
5729     #
5730     # @ month_name full month names January February ...
5731     # @ month_abb month abbreviations Jan Feb ...
5732     # @ day_name day names Monday Tuesday ...
5733     # @ day_abb day abbreviations Mon Tue ...
5734     # @ day_char day character abbrevs M T ...
5735     # @ am AM notations
5736     # @ pm PM notations
5737     #
5738     # @ num_suff number with suffix 1st 2nd ...
5739     # @ num_word numbers spelled out first second ...
5740     #
5741     # $ now words which mean now now today ...
5742     # $ last words which mean last last final ...
5743     # $ each words which mean each each every ...
5744     # $ of of (as in a member of) in of ...
5745     # ex. 4th day OF June
5746     # $ at at 4:00 at
5747     # $ on on Sunday on
5748     # $ future in the future in
5749     # $ past in the past ago
5750     # $ next next item next
5751     # $ prev previous item last previous
5752     # $ later 2 hours later
5753     #
5754     # % offset a hash of special dates { tomorrow->0:0:0:1:0:0:0 }
5755     # % times a hash of times { noon->12:00:00 ... }
5756     #
5757     # $ years words for year y yr year ...
5758     # $ months words for month
5759     # $ weeks words for week
5760     # $ days words for day
5761     # $ hours words for hour
5762     # $ minutes words for minute
5763     # $ seconds words for second
5764     # % replace
5765     # The replace element is quite important, but a bit tricky. In
5766     # English (and probably other languages), one of the abbreviations
5767     # for the word month that would be nice is "m". The problem is that
5768     # "m" matches the "m" in "minute" which causes the string to be
5769     # improperly matched in some cases. Hence, the list of abbreviations
5770     # for month is given as:
5771     # "mon month months"
5772     # In order to allow you to enter "m", replacements can be done.
5773     # $replace is a list of pairs of words which are matched and replaced
5774     # AS ENTIRE WORDS. Having $replace equal to "m"->"month" means that
5775     # the entire word "m" will be replaced with "month". This allows the
5776     # desired abbreviation to be used. Make sure that replace contains
5777     # an even number of words (i.e. all must be pairs). Any time a
5778     # desired abbreviation matches the start of any other, it has to go
5779     # here.
5780     #
5781     # $ exact exact mode exactly
5782     # $ approx approximate mode approximately
5783     # $ business business mode business
5784     #
5785     # r sephm hour/minute separator (?::)
5786     # r sepms minute/second separator (?::)
5787     # r sepss second/fraction separator (?:[.:])
5788     #
5789     # Elements marked with an asterix (@) are returned as a set of lists.
5790     # Each list contains the strings for each element. The first set is used
5791     # when the 7-bit ASCII (US) character set is wanted. The 2nd set is used
5792     # when an international character set is available. Both of the 1st two
5793     # sets should be complete (but the 2nd list can be left empty to force the
5794     # first set to be used always). The 3rd set and later can be partial sets
5795     # if desired.
5796     #
5797     # Elements marked with a dollar ($) are returned as a simple list of words.
5798     #
5799     # Elements marked with a percent (%) are returned as a hash list.
5800     #
5801     # Elements marked with (r) are regular expression elements which must not
5802     # create a back reference.
5803     #
5804     # ***NOTE*** Every hash element (unless otherwise noted) MUST be defined in
5805     # every language.
5806    
5807     sub Date_Init_English {
5808     print "DEBUG: Date_Init_English\n" if ($Curr{"Debug"} =~ /trace/);
5809     my($d)=@_;
5810    
5811     $$d{"month_name"}=
5812     [["January","February","March","April","May","June",
5813     "July","August","September","October","November","December"]];
5814    
5815     $$d{"month_abb"}=
5816     [["Jan","Feb","Mar","Apr","May","Jun",
5817     "Jul","Aug","Sep","Oct","Nov","Dec"],
5818     [],
5819     ["","","","","","","","","Sept"]];
5820    
5821     $$d{"day_name"}=
5822     [["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"]];
5823     $$d{"day_abb"}=
5824     [["Mon","Tue","Wed","Thu","Fri","Sat","Sun"],
5825     ["", "Tues","", "Thur","", "", ""]];
5826     $$d{"day_char"}=
5827     [["M","T","W","Th","F","Sa","S"]];
5828    
5829     $$d{"num_suff"}=
5830     [["1st","2nd","3rd","4th","5th","6th","7th","8th","9th","10th",
5831     "11th","12th","13th","14th","15th","16th","17th","18th","19th","20th",
5832     "21st","22nd","23rd","24th","25th","26th","27th","28th","29th","30th",
5833     "31st"]];
5834     $$d{"num_word"}=
5835     [["first","second","third","fourth","fifth","sixth","seventh","eighth",
5836     "ninth","tenth","eleventh","twelfth","thirteenth","fourteenth",
5837     "fifteenth","sixteenth","seventeenth","eighteenth","nineteenth",
5838     "twentieth","twenty-first","twenty-second","twenty-third",
5839     "twenty-fourth","twenty-fifth","twenty-sixth","twenty-seventh",
5840     "twenty-eighth","twenty-ninth","thirtieth","thirty-first"]];
5841    
5842     $$d{"now"} =["today","now"];
5843     $$d{"last"} =["last","final"];
5844     $$d{"each"} =["each","every"];
5845     $$d{"of"} =["in","of"];
5846     $$d{"at"} =["at"];
5847     $$d{"on"} =["on"];
5848     $$d{"future"} =["in"];
5849     $$d{"past"} =["ago"];
5850     $$d{"next"} =["next"];
5851     $$d{"prev"} =["previous","last"];
5852     $$d{"later"} =["later"];
5853    
5854     $$d{"exact"} =["exactly"];
5855     $$d{"approx"} =["approximately"];
5856     $$d{"business"}=["business"];
5857    
5858     $$d{"offset"} =["yesterday","-0:0:0:1:0:0:0","tomorrow","+0:0:0:1:0:0:0"];
5859     $$d{"times"} =["noon","12:00:00","midnight","00:00:00"];
5860    
5861     $$d{"years"} =["y","yr","year","yrs","years"];
5862     $$d{"months"} =["mon","month","months"];
5863     $$d{"weeks"} =["w","wk","wks","week","weeks"];
5864     $$d{"days"} =["d","day","days"];
5865     $$d{"hours"} =["h","hr","hrs","hour","hours"];
5866     $$d{"minutes"} =["mn","min","minute","minutes"];
5867     $$d{"seconds"} =["s","sec","second","seconds"];
5868     $$d{"replace"} =["m","month"];
5869    
5870     $$d{"sephm"} =':';
5871     $$d{"sepms"} =':';
5872     $$d{"sepss"} ='[.:]';
5873    
5874     $$d{"am"} = ["AM","A.M."];
5875     $$d{"pm"} = ["PM","P.M."];
5876     }
5877    
5878     sub Date_Init_Italian {
5879     print "DEBUG: Date_Init_Italian\n" if ($Curr{"Debug"} =~ /trace/);
5880     my($d)=@_;
5881     my(%h)=();
5882     &Char_8Bit(\%h);
5883     my($i)=$h{"i'"};
5884    
5885     $$d{"month_name"}=
5886     [[qw(Gennaio Febbraio Marzo Aprile Maggio Giugno
5887     Luglio Agosto Settembre Ottobre Novembre Dicembre)]];
5888    
5889     $$d{"month_abb"}=
5890     [[qw(Gen Feb Mar Apr Mag Giu Lug Ago Set Ott Nov Dic)]];
5891    
5892     $$d{"day_name"}=
5893     [[qw(Lunedi Martedi Mercoledi Giovedi Venerdi Sabato Domenica)],
5894     [qw(Luned${i} Marted${i} Mercoled${i} Gioved${i} Venerd${i})]];
5895     $$d{"day_abb"}=
5896     [[qw(Lun Mar Mer Gio Ven Sab Dom)]];
5897     $$d{"day_char"}=
5898     [[qw(L Ma Me G V S D)]];
5899    
5900     $$d{"num_suff"}=
5901     [[qw(1mo 2do 3zo 4to 5to 6to 7mo 8vo 9no 10mo 11mo 12mo 13mo 14mo 15mo
5902     16mo 17mo 18mo 19mo 20mo 21mo 22mo 23mo 24mo 25mo 26mo 27mo 28mo
5903     29mo 3mo 31mo)]];
5904     $$d{"num_word"}=
5905     [[qw(primo secondo terzo quarto quinto sesto settimo ottavo nono decimo
5906     undicesimo dodicesimo tredicesimo quattordicesimo quindicesimo
5907     sedicesimo diciassettesimo diciottesimo diciannovesimo ventesimo
5908     ventunesimo ventiduesimo ventitreesimo ventiquattresimo
5909     venticinquesimo ventiseiesimo ventisettesimo ventottesimo
5910     ventinovesimo trentesimo trentunesimo)]];
5911    
5912     $$d{"now"} =[qw(adesso oggi)];
5913     $$d{"last"} =[qw(ultimo)];
5914     $$d{"each"} =[qw(ogni)];
5915     $$d{"of"} =[qw(della del)];
5916     $$d{"at"} =[qw(alle)];
5917     $$d{"on"} =[qw(di)];
5918     $$d{"future"} =[qw(fra)];
5919     $$d{"past"} =[qw(fa)];
5920     $$d{"next"} =[qw(prossimo)];
5921     $$d{"prev"} =[qw(ultimo)];
5922     $$d{"later"} =[qw(dopo)];
5923    
5924     $$d{"exact"} =[qw(esattamente)];
5925     $$d{"approx"} =[qw(circa)];
5926     $$d{"business"}=[qw(lavorativi lavorativo)];
5927    
5928     $$d{"offset"} =[qw(ieri -0:0:0:1:0:0:0 domani +0:0:0:1:0:0:0)];
5929     $$d{"times"} =[qw(mezzogiorno 12:00:00 mezzanotte 00:00:00)];
5930    
5931     $$d{"years"} =[qw(anni anno a)];
5932     $$d{"months"} =[qw(mesi mese mes)];
5933     $$d{"weeks"} =[qw(settimane settimana sett)];
5934     $$d{"days"} =[qw(giorni giorno g)];
5935     $$d{"hours"} =[qw(ore ora h)];
5936     $$d{"minutes"} =[qw(minuti minuto min)];
5937     $$d{"seconds"} =[qw(secondi secondo sec)];
5938     $$d{"replace"} =[qw(s sec m mes)];
5939    
5940     $$d{"sephm"} =':';
5941     $$d{"sepms"} =':';
5942     $$d{"sepss"} ='[.:]';
5943    
5944     $$d{"am"} = [qw(AM)];
5945     $$d{"pm"} = [qw(PM)];
5946     }
5947    
5948     sub Date_Init_French {
5949     print "DEBUG: Date_Init_French\n" if ($Curr{"Debug"} =~ /trace/);
5950     my($d)=@_;
5951     my(%h)=();
5952     &Char_8Bit(\%h);
5953     my($e)=$h{"e'"};
5954     my($u)=$h{"u^"};
5955     my($a)=$h{"a'"};
5956    
5957     $$d{"month_name"}=
5958     [["janvier","fevrier","mars","avril","mai","juin",
5959     "juillet","aout","septembre","octobre","novembre","decembre"],
5960     ["janvier","f${e}vrier","mars","avril","mai","juin",
5961     "juillet","ao${u}t","septembre","octobre","novembre","d${e}cembre"]];
5962     $$d{"month_abb"}=
5963     [["jan","fev","mar","avr","mai","juin",
5964     "juil","aout","sept","oct","nov","dec"],
5965     ["jan","f${e}v","mar","avr","mai","juin",
5966     "juil","ao${u}t","sept","oct","nov","d${e}c"]];
5967    
5968     $$d{"day_name"}=
5969     [["lundi","mardi","mercredi","jeudi","vendredi","samedi","dimanche"]];
5970     $$d{"day_abb"}=
5971     [["lun","mar","mer","jeu","ven","sam","dim"]];
5972     $$d{"day_char"}=
5973     [["l","ma","me","j","v","s","d"]];
5974    
5975     $$d{"num_suff"}=
5976     [["1er","2e","3e","4e","5e","6e","7e","8e","9e","10e",
5977     "11e","12e","13e","14e","15e","16e","17e","18e","19e","20e",
5978     "21e","22e","23e","24e","25e","26e","27e","28e","29e","30e",
5979     "31e"]];
5980     $$d{"num_word"}=
5981     [["premier","deux","trois","quatre","cinq","six","sept","huit","neuf",
5982     "dix","onze","douze","treize","quatorze","quinze","seize","dix-sept",
5983     "dix-huit","dix-neuf","vingt","vingt et un","vingt-deux","vingt-trois",
5984     "vingt-quatre","vingt-cinq","vingt-six","vingt-sept","vingt-huit",
5985     "vingt-neuf","trente","trente et un"],
5986     ["1re"]];
5987    
5988     $$d{"now"} =["aujourd'hui","maintenant"];
5989     $$d{"last"} =["dernier"];
5990     $$d{"each"} =["chaque","tous les","toutes les"];
5991     $$d{"of"} =["en","de"];
5992     $$d{"at"} =["a","${a}0"];
5993     $$d{"on"} =["sur"];
5994     $$d{"future"} =["en"];
5995     $$d{"past"} =["il y a"];
5996     $$d{"next"} =["suivant"];
5997     $$d{"prev"} =["precedent","pr${e}c${e}dent"];
5998     $$d{"later"} =["plus tard"];
5999    
6000     $$d{"exact"} =["exactement"];
6001     $$d{"approx"} =["approximativement"];
6002     $$d{"business"}=["professionel"];
6003    
6004     $$d{"offset"} =["hier","-0:0:0:1:0:0:0","demain","+0:0:0:1:0:0:0"];
6005     $$d{"times"} =["midi","12:00:00","minuit","00:00:00"];
6006    
6007     $$d{"years"} =["an","annee","ans","annees","ann${e}e","ann${e}es"];
6008     $$d{"months"} =["mois"];
6009     $$d{"weeks"} =["sem","semaine"];
6010     $$d{"days"} =["j","jour","jours"];
6011     $$d{"hours"} =["h","heure","heures"];
6012     $$d{"minutes"} =["mn","min","minute","minutes"];
6013     $$d{"seconds"} =["s","sec","seconde","secondes"];
6014     $$d{"replace"} =["m","mois"];
6015    
6016     $$d{"sephm"} ='[h:]';
6017     $$d{"sepms"} =':';
6018     $$d{"sepss"} ='[.:,]';
6019    
6020     $$d{"am"} = ["du matin"];
6021     $$d{"pm"} = ["du soir"];
6022     }
6023    
6024     sub Date_Init_Romanian {
6025     print "DEBUG: Date_Init_Romanian\n" if ($Curr{"Debug"} =~ /trace/);
6026     my($d)=@_;
6027     my(%h)=();
6028     &Char_8Bit(\%h);
6029     my($p)=$h{"p"};
6030     my($i)=$h{"i^"};
6031     my($a)=$h{"a~"};
6032     my($o)=$h{"-o"};
6033    
6034     $$d{"month_name"}=
6035     [["ianuarie","februarie","martie","aprilie","mai","iunie",
6036     "iulie","august","septembrie","octombrie","noiembrie","decembrie"]];
6037     $$d{"month_abb"}=
6038     [["ian","febr","mart","apr","mai","iun",
6039     "iul","aug","sept","oct","nov","dec"],
6040     ["","feb"]];
6041    
6042     $$d{"day_name"}=
6043     [["luni","marti","miercuri","joi","vineri","simbata","duminica"],
6044     ["luni","mar${p}i","miercuri","joi","vineri","s${i}mb${a}t${a}",
6045     "duminic${a}"]];
6046     $$d{"day_abb"}=
6047     [["lun","mar","mie","joi","vin","sim","dum"],
6048     ["lun","mar","mie","joi","vin","s${i}m","dum"]];
6049     $$d{"day_char"}=
6050     [["L","Ma","Mi","J","V","S","D"]];
6051    
6052     $$d{"num_suff"}=
6053     [["prima","a doua","a 3-a","a 4-a","a 5-a","a 6-a","a 7-a","a 8-a",
6054     "a 9-a","a 10-a","a 11-a","a 12-a","a 13-a","a 14-a","a 15-a",
6055     "a 16-a","a 17-a","a 18-a","a 19-a","a 20-a","a 21-a","a 22-a",
6056     "a 23-a","a 24-a","a 25-a","a 26-a","a 27-a","a 28-a","a 29-a",
6057     "a 30-a","a 31-a"]];
6058    
6059     $$d{"num_word"}=
6060     [["prima","a doua","a treia","a patra","a cincea","a sasea","a saptea",
6061     "a opta","a noua","a zecea","a unsprezecea","a doisprezecea",
6062     "a treisprezecea","a patrusprezecea","a cincisprezecea","a saiprezecea",
6063     "a saptesprezecea","a optsprezecea","a nouasprezecea","a douazecea",
6064     "a douazecisiuna","a douazecisidoua","a douazecisitreia",
6065     "a douazecisipatra","a douazecisicincea","a douazecisisasea",
6066     "a douazecisisaptea","a douazecisiopta","a douazecisinoua","a treizecea",
6067     "a treizecisiuna"],
6068     ["prima","a doua","a treia","a patra","a cincea","a ${o}asea",
6069     "a ${o}aptea","a opta","a noua","a zecea","a unsprezecea",
6070     "a doisprezecea","a treisprezecea","a patrusprezecea","a cincisprezecea",
6071     "a ${o}aiprezecea","a ${o}aptesprezecea","a optsprezecea",
6072     "a nou${a}sprezecea","a dou${a}zecea","a dou${a}zeci${o}iuna",
6073     "a dou${a}zeci${o}idoua","a dou${a}zeci${o}itreia",
6074     "a dou${a}zeci${o}ipatra","a dou${a}zeci${o}icincea",
6075     "a dou${a}zeci${o}i${o}asea","a dou${a}zeci${o}i${o}aptea",
6076     "a dou${a}zeci${o}iopta","a dou${a}zeci${o}inoua","a treizecea",
6077     "a treizeci${o}iuna"],
6078     ["intii", "doi", "trei", "patru", "cinci", "sase", "sapte",
6079     "opt","noua","zece","unsprezece","doisprezece",
6080     "treisprezece","patrusprezece","cincisprezece","saiprezece",
6081     "saptesprezece","optsprezece","nouasprezece","douazeci",
6082     "douazecisiunu","douazecisidoi","douazecisitrei",
6083     "douazecisipatru","douazecisicinci","douazecisisase","douazecisisapte",
6084     "douazecisiopt","douazecisinoua","treizeci","treizecisiunu"],
6085     ["${i}nt${i}i", "doi", "trei", "patru", "cinci", "${o}ase", "${o}apte",
6086     "opt","nou${a}","zece","unsprezece","doisprezece",
6087     "treisprezece","patrusprezece","cincisprezece","${o}aiprezece",
6088     "${o}aptesprezece","optsprezece","nou${a}sprezece","dou${a}zeci",
6089     "dou${a}zeci${o}iunu","dou${a}zeci${o}idoi","dou${a}zeci${o}itrei",
6090     "dou${a}zecisipatru","dou${a}zeci${o}icinci","dou${a}zeci${o}i${o}ase",
6091     "dou${a}zeci${o}i${o}apte","dou${a}zeci${o}iopt",
6092     "dou${a}zeci${o}inou${a}","treizeci","treizeci${o}iunu"]];
6093    
6094     $$d{"now"} =["acum","azi","astazi","ast${a}zi"];
6095     $$d{"last"} =["ultima"];
6096     $$d{"each"} =["fiecare"];
6097     $$d{"of"} =["din","in","n"];
6098     $$d{"at"} =["la"];
6099     $$d{"on"} =["on"];
6100     $$d{"future"} =["in","${i}n"];
6101     $$d{"past"} =["in urma", "${i}n urm${a}"];
6102     $$d{"next"} =["urmatoarea","urm${a}toarea"];
6103     $$d{"prev"} =["precedenta","ultima"];
6104     $$d{"later"} =["mai tirziu", "mai t${i}rziu"];
6105    
6106     $$d{"exact"} =["exact"];
6107     $$d{"approx"} =["aproximativ"];
6108     $$d{"business"}=["de lucru","lucratoare","lucr${a}toare"];
6109    
6110     $$d{"offset"} =["ieri","-0:0:0:1:0:0:0",
6111     "alaltaieri", "-0:0:0:2:0:0:0",
6112     "alalt${a}ieri","-0:0:0:2:0:0:0",
6113     "miine","+0:0:0:1:0:0:0",
6114     "m${i}ine","+0:0:0:1:0:0:0",
6115     "poimiine","+0:0:0:2:0:0:0",
6116     "poim${i}ine","+0:0:0:2:0:0:0"];
6117     $$d{"times"} =["amiaza","12:00:00",
6118     "amiaz${a}","12:00:00",
6119     "miezul noptii","00:00:00",
6120     "miezul nop${p}ii","00:00:00"];
6121    
6122     $$d{"years"} =["ani","an","a"];
6123     $$d{"months"} =["luni","luna","lun${a}","l"];
6124     $$d{"weeks"} =["saptamini","s${a}pt${a}m${i}ni","saptamina",
6125     "s${a}pt${a}m${i}na","sapt","s${a}pt"];
6126     $$d{"days"} =["zile","zi","z"];
6127     $$d{"hours"} =["ore", "ora", "or${a}", "h"];
6128     $$d{"minutes"} =["minute","min","m"];
6129     $$d{"seconds"} =["secunde","sec",];
6130     $$d{"replace"} =["s","secunde"];
6131    
6132     $$d{"sephm"} =':';
6133     $$d{"sepms"} =':';
6134     $$d{"sepss"} ='[.:,]';
6135    
6136     $$d{"am"} = ["AM","A.M."];
6137     $$d{"pm"} = ["PM","P.M."];
6138     }
6139    
6140     sub Date_Init_Swedish {
6141     print "DEBUG: Date_Init_Swedish\n" if ($Curr{"Debug"} =~ /trace/);
6142     my($d)=@_;
6143     my(%h)=();
6144     &Char_8Bit(\%h);
6145     my($ao)=$h{"ao"};
6146     my($o) =$h{"o:"};
6147     my($a) =$h{"a:"};
6148    
6149     $$d{"month_name"}=
6150     [["Januari","Februari","Mars","April","Maj","Juni",
6151     "Juli","Augusti","September","Oktober","November","December"]];
6152     $$d{"month_abb"}=
6153     [["Jan","Feb","Mar","Apr","Maj","Jun",
6154     "Jul","Aug","Sep","Okt","Nov","Dec"]];
6155    
6156     $$d{"day_name"}=
6157     [["Mandag","Tisdag","Onsdag","Torsdag","Fredag","Lordag","Sondag"],
6158     ["M${ao}ndag","Tisdag","Onsdag","Torsdag","Fredag","L${o}rdag",
6159     "S${o}ndag"]];
6160     $$d{"day_abb"}=
6161     [["Man","Tis","Ons","Tor","Fre","Lor","Son"],
6162     ["M${ao}n","Tis","Ons","Tor","Fre","L${o}r","S${o}n"]];
6163     $$d{"day_char"}=
6164     [["M","Ti","O","To","F","L","S"]];
6165    
6166     $$d{"num_suff"}=
6167     [["1:a","2:a","3:e","4:e","5:e","6:e","7:e","8:e","9:e","10:e",
6168     "11:e","12:e","13:e","14:e","15:e","16:e","17:e","18:e","19:e","20:e",
6169     "21:a","22:a","23:e","24:e","25:e","26:e","27:e","28:e","29:e","30:e",
6170     "31:a"]];
6171     $$d{"num_word"}=
6172     [["forsta","andra","tredje","fjarde","femte","sjatte","sjunde",
6173     "attonde","nionde","tionde","elfte","tolfte","trettonde","fjortonde",
6174     "femtonde","sextonde","sjuttonde","artonde","nittonde","tjugonde",
6175     "tjugoforsta","tjugoandra","tjugotredje","tjugofjarde","tjugofemte",
6176     "tjugosjatte","tjugosjunde","tjugoattonde","tjugonionde",
6177     "trettionde","trettioforsta"],
6178     ["f${o}rsta","andra","tredje","fj${a}rde","femte","sj${a}tte","sjunde",
6179     "${ao}ttonde","nionde","tionde","elfte","tolfte","trettonde","fjortonde",
6180     "femtonde","sextonde","sjuttonde","artonde","nittonde","tjugonde",
6181     "tjugof${o}rsta","tjugoandra","tjugotredje","tjugofj${a}rde","tjugofemte",
6182     "tjugosj${a}tte","tjugosjunde","tjugo${ao}ttonde","tjugonionde",
6183     "trettionde","trettiof${o}rsta"]];
6184    
6185     $$d{"now"} =["idag","nu"];
6186     $$d{"last"} =["forra","f${o}rra","senaste"];
6187     $$d{"each"} =["varje"];
6188     $$d{"of"} =["om"];
6189     $$d{"at"} =["kl","kl.","klockan"];
6190     $$d{"on"} =["pa","p${ao}"];
6191     $$d{"future"} =["om"];
6192     $$d{"past"} =["sedan"];
6193     $$d{"next"} =["nasta","n${a}sta"];
6194     $$d{"prev"} =["forra","f${o}rra"];
6195     $$d{"later"} =["senare"];
6196    
6197     $$d{"exact"} =["exakt"];
6198     $$d{"approx"} =["ungefar","ungef${a}r"];
6199     $$d{"business"}=["arbetsdag","arbetsdagar"];
6200    
6201     $$d{"offset"} =["ig${ao}r","-0:0:0:1:0:0:0","igar","-0:0:0:1:0:0:0",
6202     "imorgon","+0:0:0:1:0:0:0"];
6203     $$d{"times"} =["mitt pa dagen","12:00:00","mitt p${ao} dagen","12:00:00",
6204     "midnatt","00:00:00"];
6205    
6206     $$d{"years"} =["ar","${ao}r"];
6207     $$d{"months"} =["man","manad","manader","m${ao}n","m${ao}nad","m${ao}nader"];
6208     $$d{"weeks"} =["v","vecka","veckor"];
6209     $$d{"days"} =["d","dag","dagar"];
6210     $$d{"hours"} =["t","tim","timme","timmar"];
6211     $$d{"minutes"} =["min","minut","minuter"];
6212     $$d{"seconds"} =["s","sek","sekund","sekunder"];
6213     $$d{"replace"} =["m","minut"];
6214    
6215     $$d{"sephm"} ='[.:]';
6216     $$d{"sepms"} =':';
6217     $$d{"sepss"} ='[.:]';
6218    
6219     $$d{"am"} = ["FM"];
6220     $$d{"pm"} = ["EM"];
6221     }
6222    
6223     sub Date_Init_German {
6224     print "DEBUG: Date_Init_German\n" if ($Curr{"Debug"} =~ /trace/);
6225     my($d)=@_;
6226     my(%h)=();
6227     &Char_8Bit(\%h);
6228     my($a)=$h{"a:"};
6229     my($u)=$h{"u:"};
6230     my($o)=$h{"o:"};
6231     my($b)=$h{"beta"};
6232    
6233     $$d{"month_name"}=
6234     [["Januar","Februar","Maerz","April","Mai","Juni",
6235     "Juli","August","September","Oktober","November","Dezember"],
6236     ["J${a}nner","Februar","M${a}rz","April","Mai","Juni",
6237     "Juli","August","September","Oktober","November","Dezember"]];
6238     $$d{"month_abb"}=
6239     [["Jan","Feb","Mar","Apr","Mai","Jun",
6240     "Jul","Aug","Sep","Okt","Nov","Dez"],
6241     ["J${a}n","Feb","M${a}r","Apr","Mai","Jun",
6242     "Jul","Aug","Sep","Okt","Nov","Dez"]];
6243    
6244     $$d{"day_name"}=
6245     [["Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag",
6246     "Sonntag"]];
6247     $$d{"day_abb"}=
6248     [["Mon","Die","Mit","Don","Fre","Sam","Son"]];
6249     $$d{"day_char"}=
6250     [["M","Di","Mi","Do","F","Sa","So"]];
6251    
6252     $$d{"num_suff"}=
6253     [["1.","2.","3.","4.","5.","6.","7.","8.","9.","10.",
6254     "11.","12.","13.","14.","15.","16.","17.","18.","19.","20.",
6255     "21.","22.","23.","24.","25.","26.","27.","28.","29.","30.",
6256     "31."]];
6257     $$d{"num_word"}=
6258     [
6259     ["erste","zweite","dritte","vierte","funfte","sechste","siebente",
6260     "achte","neunte","zehnte","elfte","zwolfte","dreizehnte","vierzehnte",
6261     "funfzehnte","sechzehnte","siebzehnte","achtzehnte","neunzehnte",
6262     "zwanzigste","einundzwanzigste","zweiundzwanzigste","dreiundzwanzigste",
6263     "vierundzwanzigste","funfundzwanzigste","sechundzwanzigste",
6264     "siebundzwanzigste","achtundzwanzigste","neunundzwanzigste",
6265     "dreibigste","einunddreibigste"],
6266     ["erste","zweite","dritte","vierte","f${u}nfte","sechste","siebente",
6267     "achte","neunte","zehnte","elfte","zw${o}lfte","dreizehnte",
6268     "vierzehnte","f${u}nfzehnte","sechzehnte","siebzehnte","achtzehnte",
6269     "neunzehnte","zwanzigste","einundzwanzigste","zweiundzwanzigste",
6270     "dreiundzwanzigste","vierundzwanzigste","f${u}nfundzwanzigste",
6271     "sechundzwanzigste","siebundzwanzigste","achtundzwanzigste",
6272     "neunundzwanzigste","drei${b}igste","einunddrei${b}igste"],
6273     ["erster"]];
6274    
6275     $$d{"now"} =["heute","jetzt"];
6276     $$d{"last"} =["letzte","letzten"];
6277     $$d{"each"} =["jeden"];
6278     $$d{"of"} =["der","im","des"];
6279     $$d{"at"} =["um"];
6280     $$d{"on"} =["am"];
6281     $$d{"future"} =["in"];
6282     $$d{"past"} =["vor"];
6283     $$d{"next"} =["nachste","n${a}chste","nachsten","n${a}chsten"];
6284     $$d{"prev"} =["vorherigen","vorherige","letzte","letzten"];
6285     $$d{"later"} =["spater","sp${a}ter"];
6286    
6287     $$d{"exact"} =["genau"];
6288     $$d{"approx"} =["ungefahr","ungef${a}hr"];
6289     $$d{"business"}=["Arbeitstag"];
6290    
6291     $$d{"offset"} =["gestern","-0:0:0:1:0:0:0","morgen","+0:0:0:1:0:0:0"];
6292     $$d{"times"} =["mittag","12:00:00","mitternacht","00:00:00"];
6293    
6294     $$d{"years"} =["j","Jahr","Jahre"];
6295     $$d{"months"} =["Monat","Monate"];
6296     $$d{"weeks"} =["w","Woche","Wochen"];
6297     $$d{"days"} =["t","Tag","Tage"];
6298     $$d{"hours"} =["h","std","Stunde","Stunden"];
6299     $$d{"minutes"} =["min","Minute","Minuten"];
6300     $$d{"seconds"} =["s","sek","Sekunde","Sekunden"];
6301     $$d{"replace"} =["m","Monat"];
6302    
6303     $$d{"sephm"} =':';
6304     $$d{"sepms"} ='[: ]';
6305     $$d{"sepss"} ='[.:]';
6306    
6307     $$d{"am"} = ["FM"];
6308     $$d{"pm"} = ["EM"];
6309     }
6310    
6311     sub Date_Init_Dutch {
6312     print "DEBUG: Date_Init_Dutch\n" if ($Curr{"Debug"} =~ /trace/);
6313     my($d)=@_;
6314     my(%h)=();
6315     &Char_8Bit(\%h);
6316    
6317     $$d{"month_name"}=
6318     [["januari","februari","maart","april","mei","juni","juli","augustus",
6319     "september","october","november","december"],
6320     ["","","","","","","","","","oktober"]];
6321    
6322     $$d{"month_abb"}=
6323     [["jan","feb","maa","apr","mei","jun","jul",
6324     "aug","sep","oct","nov","dec"],
6325     ["","","mrt","","","","","","","okt"]];
6326     $$d{"day_name"}=
6327     [["maandag","dinsdag","woensdag","donderdag","vrijdag","zaterdag",
6328     "zondag"]];
6329     $$d{"day_abb"}=
6330     [["ma","di","wo","do","vr","zat","zon"],
6331     ["","","","","","za","zo"]];
6332     $$d{"day_char"}=
6333     [["M","D","W","D","V","Za","Zo"]];
6334    
6335     $$d{"num_suff"}=
6336     [["1ste","2de","3de","4de","5de","6de","7de","8ste","9de","10de",
6337     "11de","12de","13de","14de","15de","16de","17de","18de","19de","20ste",
6338     "21ste","22ste","23ste","24ste","25ste","26ste","27ste","28ste","29ste",
6339     "30ste","31ste"]];
6340     $$d{"num_word"}=
6341     [["eerste","tweede","derde","vierde","vijfde","zesde","zevende","achtste",
6342     "negende","tiende","elfde","twaalfde",
6343     map {"${_}tiende";} qw (der veer vijf zes zeven acht negen),
6344     "twintigste",
6345     map {"${_}entwintigste";} qw (een twee drie vier vijf zes zeven acht
6346     negen),
6347     "dertigste","eenendertigste"],
6348     ["","","","","","","","","","","","","","","","","","","","",
6349     map {"${_}-en-twintigste";} qw (een twee drie vier vijf zes zeven acht
6350     negen),
6351     "dertigste","een-en-dertigste"],
6352     ["een","twee","drie","vier","vijf","zes","zeven","acht","negen","tien",
6353     "elf","twaalf",
6354     map {"${_}tien"} qw (der veer vijf zes zeven acht negen),
6355     "twintig",
6356     map {"${_}entwintig"} qw (een twee drie vier vijf zes zeven acht negen),
6357     "dertig","eenendertig"],
6358     ["","","","","","","","","","","","","","","","","","","","",
6359     map {"${_}-en-twintig"} qw (een twee drie vier vijf zes zeven acht
6360     negen),
6361     "dertig","een-en-dertig"]];
6362    
6363     $$d{"now"} =["nu","nou","vandaag"];
6364     $$d{"last"} =["laatste"];
6365     $$d{"each"} =["elke","elk"];
6366     $$d{"of"} =["in","van"];
6367     $$d{"at"} =["om"];
6368     $$d{"on"} =["op"];
6369     $$d{"future"} =["over"];
6370     $$d{"past"} =["geleden","vroeger","eerder"];
6371     $$d{"next"} =["volgende","volgend"];
6372     $$d{"prev"} =["voorgaande","voorgaand"];
6373     $$d{"later"} =["later"];
6374    
6375     $$d{"exact"} =["exact","precies","nauwkeurig"];
6376     $$d{"approx"} =["ongeveer","ong",'ong\.',"circa","ca",'ca\.'];
6377     $$d{"business"}=["werk","zakelijke","zakelijk"];
6378    
6379     $$d{"offset"} =["morgen","+0:0:0:1:0:0:0","overmorgen","+0:0:0:2:0:0:0",
6380     "gisteren","-0:0:0:1:0:0:0","eergisteren","-0::00:2:0:0:0"];
6381     $$d{"times"} =["noen","12:00:00","middernacht","00:00:00"];
6382    
6383     $$d{"years"} =["jaar","jaren","ja","j"];
6384     $$d{"months"} =["maand","maanden","mnd"];
6385     $$d{"weeks"} =["week","weken","w"];
6386     $$d{"days"} =["dag","dagen","d"];
6387     $$d{"hours"} =["uur","uren","u","h"];
6388     $$d{"minutes"} =["minuut","minuten","min"];
6389     $$d{"seconds"} =["seconde","seconden","sec","s"];
6390     $$d{"replace"} =["m","minuten"];
6391    
6392     $$d{"sephm"} ='[:.uh]';
6393     $$d{"sepms"} ='[:.m]';
6394     $$d{"sepss"} ='[.:]';
6395    
6396     $$d{"am"} = ["am","a.m.","vm","v.m.","voormiddag","'s_ochtends",
6397     "ochtend","'s_nachts","nacht"];
6398     $$d{"pm"} = ["pm","p.m.","nm","n.m.","namiddag","'s_middags","middag",
6399     "'s_avonds","avond"];
6400     }
6401    
6402     sub Date_Init_Polish {
6403     print "DEBUG: Date_Init_Polish\n" if ($Curr{"Debug"} =~ /trace/);
6404     my($d)=@_;
6405    
6406     $$d{"month_name"}=
6407     [["stycznia","luty","marca","kwietnia","maja","czerwca",
6408     "lipca","sierpnia","wrzesnia","pazdziernika","listopada","grudnia"],
6409     ["stycznia","luty","marca","kwietnia","maja","czerwca","lipca",
6410     "sierpnia","wrze\x9cnia","pa\x9fdziernika","listopada","grudnia"]];
6411     $$d{"month_abb"}=
6412     [["sty.","lut.","mar.","kwi.","maj","cze.",
6413     "lip.","sie.","wrz.","paz.","lis.","gru."],
6414     ["sty.","lut.","mar.","kwi.","maj","cze.",
6415     "lip.","sie.","wrz.","pa\x9f.","lis.","gru."]];
6416    
6417     $$d{"day_name"}=
6418     [["poniedzialek","wtorek","sroda","czwartek","piatek","sobota",
6419     "niedziela"],
6420     ["poniedzia\x81\xb3ek","wtorek","\x9croda","czwartek","pi\x81\xb9tek",
6421     "sobota","niedziela"]];
6422     $$d{"day_abb"}=
6423     [["po.","wt.","sr.","cz.","pi.","so.","ni."],
6424     ["po.","wt.","\x9cr.","cz.","pi.","so.","ni."]];
6425     $$d{"day_char"}=
6426     [["p","w","e","c","p","s","n"],
6427     ["p","w","\x9c.","c","p","s","n"]];
6428    
6429     $$d{"num_suff"}=
6430     [["1.","2.","3.","4.","5.","6.","7.","8.","9.","10.",
6431     "11.","12.","13.","14.","15.","16.","17.","18.","19.","20.",
6432     "21.","22.","23.","24.","25.","26.","27.","28.","29.","30.",
6433     "31."]];
6434     $$d{"num_word"}=
6435     [["pierwszego","drugiego","trzeczego","czwartego","piatego","szostego",
6436     "siodmego","osmego","dziewiatego","dziesiatego",
6437     "jedenastego","dwunastego","trzynastego","czternastego","pietnastego",
6438     "szestnastego","siedemnastego","osiemnastego","dziewietnastego",
6439     "dwudziestego",
6440     "dwudziestego pierwszego","dwudziestego drugiego",
6441     "dwudziestego trzeczego","dwudziestego czwartego",
6442     "dwudziestego piatego","dwudziestego szostego",
6443     "dwudziestego siodmego","dwudziestego osmego",
6444     "dwudziestego dziewiatego","trzydziestego","trzydziestego pierwszego"],
6445     ["pierwszego","drugiego","trzeczego","czwartego","pi\x81\xb9tego",
6446     "sz\x81\xf3stego","si\x81\xf3dmego","\x81\xf3smego","dziewi\x81\xb9tego",
6447     "dziesi\x81\xb9tego","jedenastego","dwunastego","trzynastego",
6448     "czternastego","pi\x81\xeatnastego","szestnastego","siedemnastego",
6449     "osiemnastego","dziewietnastego","dwudziestego",
6450     "dwudziestego pierwszego","dwudziestego drugiego",
6451     "dwudziestego trzeczego","dwudziestego czwartego",
6452     "dwudziestego pi\x81\xb9tego","dwudziestego sz\x81\xf3stego",
6453     "dwudziestego si\x81\xf3dmego","dwudziestego \x81\xf3smego",
6454     "dwudziestego dziewi\x81\xb9tego","trzydziestego",
6455     "trzydziestego pierwszego"]];
6456    
6457     $$d{"now"} =["dzisaj","teraz"];
6458     $$d{"last"} =["ostatni","ostatna"];
6459     $$d{"each"} =["kazdy","ka\x81\xbfdy", "kazdym","ka\x81\xbfdym"];
6460     $$d{"of"} =["w","z"];
6461     $$d{"at"} =["o","u"];
6462     $$d{"on"} =["na"];
6463     $$d{"future"} =["za"];
6464     $$d{"past"} =["temu"];
6465     $$d{"next"} =["nastepny","nast\x81\xeapny","nastepnym","nast\x81\xeapnym",
6466     "przyszly","przysz\x81\xb3y","przyszlym",
6467     "przysz\x81\xb3ym"];
6468     $$d{"prev"} =["zeszly","zesz\x81\xb3y","zeszlym","zesz\x81\xb3ym"];
6469     $$d{"later"} =["later"];
6470    
6471     $$d{"exact"} =["doklandnie","dok\x81\xb3andnie"];
6472     $$d{"approx"} =["w przyblizeniu","w przybli\x81\xbfeniu","mniej wiecej",
6473     "mniej wi\x81\xeacej","okolo","oko\x81\xb3o"];
6474     $$d{"business"}=["sluzbowy","s\x81\xb3u\x81\xbfbowy","sluzbowym",
6475     "s\x81\xb3u\x81\xbfbowym"];
6476    
6477     $$d{"times"} =["po\x81\xb3udnie","12:00:00",
6478     "p\x81\xf3\x81\xb3noc","00:00:00",
6479     "poludnie","12:00:00","polnoc","00:00:00"];
6480     $$d{"offset"} =["wczoraj","-0:0:1:0:0:0","jutro","+0:0:1:0:0:0"];
6481    
6482     $$d{"years"} =["rok","lat","lata","latach"];
6483     $$d{"months"} =["m.","miesiac","miesi\x81\xb9c","miesiecy",
6484     "miesi\x81\xeacy","miesiacu","miesi\x81\xb9cu"];
6485     $$d{"weeks"} =["ty.","tydzien","tydzie\x81\xf1","tygodniu"];
6486     $$d{"days"} =["d.","dzien","dzie\x81\xf1","dni"];
6487     $$d{"hours"} =["g.","godzina","godziny","godzinie"];
6488     $$d{"minutes"} =["mn.","min.","minut","minuty"];
6489     $$d{"seconds"} =["s.","sekund","sekundy"];
6490     $$d{"replace"} =["m.","miesiac"];
6491    
6492     $$d{"sephm"} =':';
6493     $$d{"sepms"} =':';
6494     $$d{"sepss"} ='[.:]';
6495    
6496     $$d{"am"} = ["AM","A.M."];
6497     $$d{"pm"} = ["PM","P.M."];
6498     }
6499    
6500     sub Date_Init_Spanish {
6501     print "DEBUG: Date_Init_Spanish\n" if ($Curr{"Debug"} =~ /trace/);
6502     my($d)=@_;
6503     my(%h)=();
6504     &Char_8Bit(\%h);
6505    
6506     $$d{"month_name"}=
6507     [["Enero","Febrero","Marzo","Abril","Mayo","Junio","Julio","Agosto",
6508     "Septiembre","Octubre","Noviembre","Diciembre"]];
6509    
6510     $$d{"month_abb"}=
6511     [["Ene","Feb","Mar","Abr","May","Jun","Jul","Ago","Sep","Oct",
6512     "Nov","Dic"]];
6513    
6514     $$d{"day_name"}=
6515     [["Lunes","Martes","Miercoles","Jueves","Viernes","Sabado","Domingo"]];
6516     $$d{"day_abb"}=
6517     [["Lun","Mar","Mie","Jue","Vie","Sab","Dom"]];
6518     $$d{"day_char"}=
6519     [["L","Ma","Mi","J","V","S","D"]];
6520    
6521     $$d{"num_suff"}=
6522     [["1o","2o","3o","4o","5o","6o","7o","8o","9o","10o",
6523     "11o","12o","13o","14o","15o","16o","17o","18o","19o","20o",
6524     "21o","22o","23o","24o","25o","26o","27o","28o","29o","30o","31o"],
6525     ["1a","2a","3a","4a","5a","6a","7a","8a","9a","10a",
6526     "11a","12a","13a","14a","15a","16a","17a","18a","19a","20a",
6527     "21a","22a","23a","24a","25a","26a","27a","28a","29a","30a","31a"]];
6528     $$d{"num_word"}=
6529     [["Primero","Segundo","Tercero","Cuarto","Quinto","Sexto","Septimo",
6530     "Octavo","Noveno","Decimo","Decimo Primero","Decimo Segundo",
6531     "Decimo Tercero","Decimo Cuarto","Decimo Quinto","Decimo Sexto",
6532     "Decimo Septimo","Decimo Octavo","Decimo Noveno","Vigesimo",
6533     "Vigesimo Primero","Vigesimo Segundo","Vigesimo Tercero",
6534     "Vigesimo Cuarto","Vigesimo Quinto","Vigesimo Sexto",
6535     "Vigesimo Septimo","Vigesimo Octavo","Vigesimo Noveno","Trigesimo",
6536     "Trigesimo Primero"],
6537     ["Primera","Segunda","Tercera","Cuarta","Quinta","Sexta","Septima",
6538     "Octava","Novena","Decima","Decimo Primera","Decimo Segunda",
6539     "Decimo Tercera","Decimo Cuarta","Decimo Quinta","Decimo Sexta",
6540     "Decimo Septima","Decimo Octava","Decimo Novena","Vigesima",
6541     "Vigesimo Primera","Vigesimo Segunda","Vigesimo Tercera",
6542     "Vigesimo Cuarta","Vigesimo Quinta","Vigesimo Sexta",
6543     "Vigesimo Septima","Vigesimo Octava","Vigesimo Novena","Trigesima",
6544     "Trigesimo Primera"]];
6545    
6546     $$d{"now"} =["Hoy","Ahora"];
6547     $$d{"last"} =["ultimo"];
6548     $$d{"each"} =["cada"];
6549     $$d{"of"} =["en","de"];
6550     $$d{"at"} =["a"];
6551     $$d{"on"} =["el"];
6552     $$d{"future"} =["en"];
6553     $$d{"past"} =["hace"];
6554     $$d{"next"} =["siguiente"];
6555     $$d{"prev"} =["anterior"];
6556     $$d{"later"} =["later"];
6557    
6558     $$d{"exact"} =["exactamente"];
6559     $$d{"approx"} =["aproximadamente"];
6560     $$d{"business"}=["laborales"];
6561    
6562     $$d{"offset"} =["ayer","-0:0:0:1:0:0:0","manana","+0:0:0:1:0:0:0"];
6563     $$d{"times"} =["mediodia","12:00:00","medianoche","00:00:00"];
6564    
6565     $$d{"years"} =["a","ano","ano","anos","anos"];
6566     $$d{"months"} =["m","mes","mes","meses"];
6567     $$d{"weeks"} =["sem","semana","semana","semanas"];
6568     $$d{"days"} =["d","dia","dias"];
6569     $$d{"hours"} =["hr","hrs","hora","horas"];
6570     $$d{"minutes"} =["min","min","minuto","minutos"];
6571     $$d{"seconds"} =["s","seg","segundo","segundos"];
6572     $$d{"replace"} =["m","mes"];
6573    
6574     $$d{"sephm"} =':';
6575     $$d{"sepms"} =':';
6576     $$d{"sepss"} ='[.:]';
6577    
6578     $$d{"am"} = ["AM","A.M."];
6579     $$d{"pm"} = ["PM","P.M."];
6580     }
6581    
6582     sub Date_Init_Portuguese {
6583     print "DEBUG: Date_Init_Portuguese\n" if ($Curr{"Debug"} =~ /trace/);
6584     my($d)=@_;
6585     my(%h)=();
6586     &Char_8Bit(\%h);
6587     my($o) = $h{"-o"};
6588     my($c) = $h{",c"};
6589     my($a) = $h{"a'"};
6590     my($e) = $h{"e'"};
6591     my($u) = $h{"u'"};
6592     my($o2)= $h{"o'"};
6593     my($a2)= $h{"a`"};
6594     my($a3)= $h{"a~"};
6595     my($e2)= $h{"e^"};
6596    
6597     $$d{"month_name"}=
6598     [["Janeiro","Fevereiro","Marco","Abril","Maio","Junho",
6599     "Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],
6600     ["Janeiro","Fevereiro","Mar${c}o","Abril","Maio","Junho",
6601     "Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"]];
6602    
6603     $$d{"month_abb"}=
6604     [["Jan","Fev","Mar","Abr","Mai","Jun",
6605     "Jul","Ago","Set","Out","Nov","Dez"]];
6606    
6607     $$d{"day_name"}=
6608     [["Segunda","Terca","Quarta","Quinta","Sexta","Sabado","Domingo"],
6609     ["Segunda","Ter${c}a","Quarta","Quinta","Sexta","S${a}bado","Domingo"]];
6610     $$d{"day_abb"}=
6611     [["Seg","Ter","Qua","Qui","Sex","Sab","Dom"],
6612     ["Seg","Ter","Qua","Qui","Sex","S${a}b","Dom"]];
6613     $$d{"day_char"}=
6614     [["Sg","T","Qa","Qi","Sx","Sb","D"]];
6615    
6616     $$d{"num_suff"}=
6617     [["1${o}","2${o}","3${o}","4${o}","5${o}","6${o}","7${o}","8${o}",
6618     "9${o}","10${o}","11${o}","12${o}","13${o}","14${o}","15${o}",
6619     "16${o}","17${o}","18${o}","19${o}","20${o}","21${o}","22${o}",
6620     "23${o}","24${o}","25${o}","26${o}","27${o}","28${o}","29${o}",
6621     "30${o}","31${o}"]];
6622     $$d{"num_word"}=
6623     [["primeiro","segundo","terceiro","quarto","quinto","sexto","setimo",
6624     "oitavo","nono","decimo","decimo primeiro","decimo segundo",
6625     "decimo terceiro","decimo quarto","decimo quinto","decimo sexto",
6626     "decimo setimo","decimo oitavo","decimo nono","vigesimo",
6627     "vigesimo primeiro","vigesimo segundo","vigesimo terceiro",
6628     "vigesimo quarto","vigesimo quinto","vigesimo sexto","vigesimo setimo",
6629     "vigesimo oitavo","vigesimo nono","trigesimo","trigesimo primeiro"],
6630     ["primeiro","segundo","terceiro","quarto","quinto","sexto","s${e}timo",
6631     "oitavo","nono","d${e}cimo","d${e}cimo primeiro","d${e}cimo segundo",
6632     "d${e}cimo terceiro","d${e}cimo quarto","d${e}cimo quinto",
6633     "d${e}cimo sexto","d${e}cimo s${e}timo","d${e}cimo oitavo",
6634     "d${e}cimo nono","vig${e}simo","vig${e}simo primeiro",
6635     "vig${e}simo segundo","vig${e}simo terceiro","vig${e}simo quarto",
6636     "vig${e}simo quinto","vig${e}simo sexto","vig${e}simo s${e}timo",
6637     "vig${e}simo oitavo","vig${e}simo nono","trig${e}simo",
6638     "trig${e}simo primeiro"]];
6639    
6640     $$d{"now"} =["agora","hoje"];
6641     $$d{"last"} =["${u}ltimo","ultimo"];
6642     $$d{"each"} =["cada"];
6643     $$d{"of"} =["da","do"];
6644     $$d{"at"} =["as","${a2}s"];
6645     $$d{"on"} =["na","no"];
6646     $$d{"future"} =["em"];
6647     $$d{"past"} =["a","${a2}"];
6648     $$d{"next"} =["proxima","proximo","pr${o2}xima","pr${o2}ximo"];
6649     $$d{"prev"} =["ultima","ultimo","${u}ltima","${u}ltimo"];
6650     $$d{"later"} =["passadas","passados"];
6651    
6652     $$d{"exact"} =["exactamente"];
6653     $$d{"approx"} =["aproximadamente"];
6654     $$d{"business"}=["util","uteis"];
6655    
6656     $$d{"offset"} =["ontem","-0:0:0:1:0:0:0",
6657     "amanha","+0:0:0:1:0:0:0","amanh${a3}","+0:0:0:1:0:0:0"];
6658     $$d{"times"} =["meio-dia","12:00:00","meia-noite","00:00:00"];
6659    
6660     $$d{"years"} =["anos","ano","ans","an","a"];
6661     $$d{"months"} =["meses","m${e2}s","mes","m"];
6662     $$d{"weeks"} =["semanas","semana","sem","sems","s"];
6663     $$d{"days"} =["dias","dia","d"];
6664     $$d{"hours"} =["horas","hora","hr","hrs"];
6665     $$d{"minutes"} =["minutos","minuto","min","mn"];
6666     $$d{"seconds"} =["segundos","segundo","seg","sg"];
6667     $$d{"replace"} =["m","mes","s","sems"];
6668    
6669     $$d{"sephm"} =':';
6670     $$d{"sepms"} =':';
6671     $$d{"sepss"} ='[,]';
6672    
6673     $$d{"am"} = ["AM","A.M."];
6674     $$d{"pm"} = ["PM","P.M."];
6675     }
6676    
6677     sub Date_Init_Russian {
6678     print "DEBUG: Date_Init_Russian\n" if ($Curr{"Debug"} =~ /trace/);
6679     my($d)=@_;
6680     my(%h)=();
6681     &Char_8Bit(\%h);
6682     my($a) =$h{"a:"};
6683    
6684     $$d{"month_name"}=
6685     [
6686     ["\xd1\xce\xd7\xc1\xd2\xd1","\xc6\xc5\xd7\xd2\xc1\xcc\xd1",
6687     "\xcd\xc1\xd2\xd4\xc1","\xc1\xd0\xd2\xc5\xcc\xd1","\xcd\xc1\xd1",
6688     "\xc9\xc0\xce\xd1",
6689     "\xc9\xc0\xcc\xd1","\xc1\xd7\xc7\xd5\xd3\xd4\xc1",
6690     "\xd3\xc5\xce\xd4\xd1\xc2\xd2\xd1","\xcf\xcb\xd4\xd1\xc2\xd2\xd1",
6691     "\xce\xcf\xd1\xc2\xd2\xd1","\xc4\xc5\xcb\xc1\xc2\xd2\xd1"],
6692     ["\xd1\xce\xd7\xc1\xd2\xd8","\xc6\xc5\xd7\xd2\xc1\xcc\xd8",
6693     "\xcd\xc1\xd2\xd4","\xc1\xd0\xd2\xc5\xcc\xd8","\xcd\xc1\xca",
6694     "\xc9\xc0\xce\xd8",
6695     "\xc9\xc0\xcc\xd8","\xc1\xd7\xc7\xd5\xd3\xd4",
6696     "\xd3\xc5\xce\xd4\xd1\xc2\xd2\xd8","\xcf\xcb\xd4\xd1\xc2\xd2\xd8",
6697     "\xce\xcf\xd1\xc2\xd2\xd8","\xc4\xc5\xcb\xc1\xc2\xd2\xd8"]
6698     ];
6699    
6700     $$d{"month_abb"}=
6701     [["\xd1\xce\xd7","\xc6\xc5\xd7","\xcd\xd2\xd4","\xc1\xd0\xd2",
6702     "\xcd\xc1\xca","\xc9\xc0\xce",
6703     "\xc9\xc0\xcc","\xc1\xd7\xc7","\xd3\xce\xd4","\xcf\xcb\xd4",
6704     "\xce\xcf\xd1\xc2","\xc4\xc5\xcb"],
6705     ["","\xc6\xd7\xd2","","","\xcd\xc1\xd1","",
6706     "","","\xd3\xc5\xce","\xcf\xcb\xd4","\xce\xcf\xd1",""]];
6707    
6708     $$d{"day_name"}=
6709     [["\xd0\xcf\xce\xc5\xc4\xc5\xcc\xd8\xce\xc9\xcb",
6710     "\xd7\xd4\xcf\xd2\xce\xc9\xcb","\xd3\xd2\xc5\xc4\xc1",
6711     "\xde\xc5\xd4\xd7\xc5\xd2\xc7","\xd0\xd1\xd4\xce\xc9\xc3\xc1",
6712     "\xd3\xd5\xc2\xc2\xcf\xd4\xc1",
6713     "\xd7\xcf\xd3\xcb\xd2\xc5\xd3\xc5\xce\xd8\xc5"]];
6714     $$d{"day_abb"}=
6715     [["\xd0\xce\xc4","\xd7\xd4\xd2","\xd3\xd2\xc4","\xde\xd4\xd7",
6716     "\xd0\xd4\xce","\xd3\xd5\xc2","\xd7\xd3\xcb"],
6717     ["\xd0\xcf\xce","\xd7\xd4\xcf","\xd3\xd2e","\xde\xc5\xd4",
6718     "\xd0\xd1\xd4","\xd3\xd5\xc2","\xd7\xcf\xd3\xcb"]];
6719     $$d{"day_char"}=
6720     [["\xd0\xce","\xd7\xd4","\xd3\xd2","\xde\xd4","\xd0\xd4","\xd3\xc2",
6721     "\xd7\xd3"]];
6722    
6723     $$d{"num_suff"}=
6724     [["1 ","2 ","3 ","4 ","5 ","6 ","7 ","8 ","9 ","10 ",
6725     "11 ","12 ","13 ","14 ","15 ","16 ","17 ","18 ","19 ","20 ",
6726     "21 ","22 ","23 ","24 ","25 ","26 ","27 ","28 ","29 ","30 ",
6727     "31 "]];
6728     $$d{"num_word"}=
6729     [["\xd0\xc5\xd2\xd7\xd9\xca","\xd7\xd4\xcf\xd2\xcf\xca",
6730     "\xd4\xd2\xc5\xd4\xc9\xca","\xde\xc5\xd4\xd7\xc5\xd2\xd4\xd9\xca",
6731     "\xd0\xd1\xd4\xd9\xca","\xdb\xc5\xd3\xd4\xcf\xca",
6732     "\xd3\xc5\xc4\xd8\xcd\xcf\xca","\xd7\xcf\xd3\xd8\xcd\xcf\xca",
6733     "\xc4\xc5\xd7\xd1\xd4\xd9\xca","\xc4\xc5\xd3\xd1\xd4\xd9\xca",
6734     "\xcf\xc4\xc9\xce\xce\xc1\xc4\xc3\xc1\xd4\xd9\xca",
6735     "\xc4\xd7\xc5\xce\xc1\xc4\xde\xc1\xd4\xd9\xca",
6736     "\xd4\xd2\xc5\xce\xc1\xc4\xc3\xc1\xd4\xd9\xca",
6737     "\xde\xc5\xd4\xd9\xd2\xce\xc1\xc4\xc3\xc1\xd4\xd9\xca",
6738     "\xd0\xd1\xd4\xce\xc1\xc4\xc3\xc1\xd4\xd9\xca",
6739     "\xdb\xc5\xd3\xd4\xce\xc1\xc4\xc3\xc1\xd4\xd9\xca",
6740     "\xd3\xc5\xcd\xd8\xce\xc1\xc4\xc3\xc1\xd4\xd9\xca",
6741     "\xd7\xcf\xd3\xc5\xcd\xd8\xce\xc1\xc4\xc3\xc1\xd4\xd9\xca",
6742     "\xc4\xc5\xd7\xd1\xd4\xce\xc1\xc4\xc3\xc1\xd4\xd9\xca",
6743     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd9\xca",
6744     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd0\xc5\xd2\xd7\xd9\xca",
6745     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd7\xd4\xcf\xd2\xcf\xca",
6746     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd4\xd2\xc5\xd4\xc9\xca",
6747     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xde\xc5\xd4\xd7\xc5\xd2\xd4\xd9\xca",
6748     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd0\xd1\xd4\xd9\xca",
6749     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xdb\xc5\xd3\xd4\xcf\xca",
6750     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd3\xc5\xc4\xd8\xcd\xcf\xca",
6751     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd7\xcf\xd3\xd8\xcd\xcf\xca",
6752     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xc4\xc5\xd7\xd1\xd4\xd9\xca",
6753     "\xd4\xd2\xc9\xc4\xc3\xc1\xd4\xd9\xca",
6754     "\xd4\xd2\xc9\xc4\xc3\xc1\xd4\xd8 \xd0\xc5\xd2\xd7\xd9\xca"],
6755    
6756     ["\xd0\xc5\xd2\xd7\xcf\xc5","\xd7\xd4\xcf\xd2\xcf\xc5",
6757     "\xd4\xd2\xc5\xd4\xd8\xc5","\xde\xc5\xd4\xd7\xc5\xd2\xd4\xcf\xc5",
6758     "\xd0\xd1\xd4\xcf\xc5","\xdb\xc5\xd3\xd4\xcf\xc5",
6759     "\xd3\xc5\xc4\xd8\xcd\xcf\xc5","\xd7\xcf\xd3\xd8\xcd\xcf\xc5",
6760     "\xc4\xc5\xd7\xd1\xd4\xcf\xc5","\xc4\xc5\xd3\xd1\xd4\xcf\xc5",
6761     "\xcf\xc4\xc9\xce\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc5",
6762     "\xc4\xd7\xc5\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc5",
6763     "\xd4\xd2\xc5\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc5",
6764     "\xde\xc5\xd4\xd9\xd2\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc5",
6765     "\xd0\xd1\xd4\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc5",
6766     "\xdb\xc5\xd3\xd4\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc5",
6767     "\xd3\xc5\xcd\xd8\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc5",
6768     "\xd7\xcf\xd3\xc5\xcd\xd8\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc5",
6769     "\xc4\xc5\xd7\xd1\xd4\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc5",
6770     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xcf\xc5",
6771     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd0\xc5\xd2\xd7\xcf\xc5",
6772     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd7\xd4\xcf\xd2\xcf\xc5",
6773     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd4\xd2\xc5\xd4\xd8\xc5",
6774     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xde\xc5\xd4\xd7\xc5\xd2\xd4\xcf\xc5",
6775     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd0\xd1\xd4\xcf\xc5",
6776     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xdb\xc5\xd3\xd4\xcf\xc5",
6777     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd3\xc5\xc4\xd8\xcd\xcf\xc5",
6778     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd7\xcf\xd3\xd8\xcd\xcf\xc5",
6779     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xc4\xc5\xd7\xd1\xd4\xcf\xc5",
6780     "\xd4\xd2\xc9\xc4\xc3\xc1\xd4\xcf\xc5",
6781     "\xd4\xd2\xc9\xc4\xc3\xc1\xd4\xd8 \xd0\xc5\xd2\xd7\xcf\xc5"],
6782    
6783     ["\xd0\xc5\xd2\xd7\xcf\xc7\xcf","\xd7\xd4\xcf\xd2\xcf\xc7\xcf",
6784     "\xd4\xd2\xc5\xd4\xd8\xc5\xc7\xcf",
6785     "\xde\xc5\xd4\xd7\xc5\xd2\xd4\xcf\xc7\xcf","\xd0\xd1\xd4\xcf\xc7\xcf",
6786     "\xdb\xc5\xd3\xd4\xcf\xc7\xcf","\xd3\xc5\xc4\xd8\xcd\xcf\xc7\xcf",
6787     "\xd7\xcf\xd3\xd8\xcd\xcf\xc7\xcf",
6788     "\xc4\xc5\xd7\xd1\xd4\xcf\xc7\xcf","\xc4\xc5\xd3\xd1\xd4\xcf\xc7\xcf",
6789     "\xcf\xc4\xc9\xce\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf",
6790     "\xc4\xd7\xc5\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf",
6791     "\xd4\xd2\xc5\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf",
6792     "\xde\xc5\xd4\xd9\xd2\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf",
6793     "\xd0\xd1\xd4\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf",
6794     "\xdb\xc5\xd3\xd4\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf",
6795     "\xd3\xc5\xcd\xd8\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf",
6796     "\xd7\xcf\xd3\xc5\xcd\xd8\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf",
6797     "\xc4\xc5\xd7\xd1\xd4\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf",
6798     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf",
6799     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd0\xc5\xd2\xd7\xcf\xc7\xcf",
6800     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd7\xd4\xcf\xd2\xcf\xc5",
6801     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd4\xd2\xc5\xd4\xd8\xc5\xc7\xcf",
6802     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xde\xc5\xd4\xd7\xc5\xd2\xd4\xcf\xc7\xcf",
6803     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd0\xd1\xd4\xcf\xc7\xcf",
6804     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xdb\xc5\xd3\xd4\xcf\xc7\xcf",
6805     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd3\xc5\xc4\xd8\xcd\xcf\xc7\xcf",
6806     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd7\xcf\xd3\xd8\xcd\xcf\xc7\xcf",
6807     "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xc4\xc5\xd7\xd1\xd4\xcf\xc7\xcf",
6808     "\xd4\xd2\xc9\xc4\xc3\xc1\xd4\xcf\xc7\xcf",
6809     "\xd4\xd2\xc9\xc4\xc3\xc1\xd4\xd8 \xd0\xc5\xd2\xd7\xcf\xc7\xcf"]];
6810    
6811     $$d{"now"} =["\xd3\xc5\xc7\xcf\xc4\xce\xd1","\xd3\xc5\xca\xde\xc1\xd3"];
6812     $$d{"last"} =["\xd0\xcf\xd3\xcc\xc5\xc4\xce\xc9\xca"];
6813     $$d{"each"} =["\xcb\xc1\xd6\xc4\xd9\xca"];
6814     $$d{"of"} =[" "];
6815     $$d{"at"} =["\xd7"];
6816     $$d{"on"} =["\xd7"];
6817     $$d{"future"} =["\xd7\xd0\xc5\xd2\xc5\xc4 \xce\xc1"];
6818     $$d{"past"} =["\xce\xc1\xda\xc1\xc4 \xce\xc1 "];
6819     $$d{"next"} =["\xd3\xcc\xc5\xc4\xd5\xc0\xdd\xc9\xca"];
6820     $$d{"prev"} =["\xd0\xd2\xc5\xc4\xd9\xc4\xd5\xdd\xc9\xca"];
6821     $$d{"later"} =["\xd0\xcf\xda\xd6\xc5"];
6822    
6823     $$d{"exact"} =["\xd4\xcf\xde\xce\xcf"];
6824     $$d{"approx"} =["\xd0\xd2\xc9\xcd\xc5\xd2\xce\xcf"];
6825     $$d{"business"}=["\xd2\xc1\xc2\xcf\xde\xc9\xc8"];
6826    
6827     $$d{"offset"} =["\xd0\xcf\xda\xc1\xd7\xde\xc5\xd2\xc1","-0:0:0:2:0:0:0",
6828     "\xd7\xde\xc5\xd2\xc1","-0:0:0:1:0:0:0",
6829     "\xda\xc1\xd7\xd4\xd2\xc1","+0:0:0:1:0:0:0",
6830     "\xd0\xcf\xd3\xcc\xc5\xda\xc1\xd7\xd4\xd2\xc1",
6831     "+0:0:0:2:0:0:0"];
6832     $$d{"times"} =["\xd0\xcf\xcc\xc4\xc5\xce\xd8","12:00:00",
6833     "\xd0\xcf\xcc\xce\xcf\xde\xd8","00:00:00"];
6834    
6835     $$d{"years"} =["\xc7","\xc7\xc4","\xc7\xcf\xc4","\xcc\xc5\xd4",
6836     "\xcc\xc5\xd4","\xc7\xcf\xc4\xc1"];
6837     $$d{"months"} =["\xcd\xc5\xd3","\xcd\xc5\xd3\xd1\xc3",
6838     "\xcd\xc5\xd3\xd1\xc3\xc5\xd7"];
6839     $$d{"weeks"} =["\xce\xc5\xc4\xc5\xcc\xd1","\xce\xc5\xc4\xc5\xcc\xd8",
6840     "\xce\xc5\xc4\xc5\xcc\xc9","\xce\xc5\xc4\xc5\xcc\xc0"];
6841     $$d{"days"} =["\xc4","\xc4\xc5\xce\xd8","\xc4\xce\xc5\xca",
6842     "\xc4\xce\xd1"];
6843     $$d{"hours"} =["\xde","\xde.","\xde\xd3","\xde\xd3\xd7","\xde\xc1\xd3",
6844     "\xde\xc1\xd3\xcf\xd7","\xde\xc1\xd3\xc1"];
6845     $$d{"minutes"} =["\xcd\xce","\xcd\xc9\xce","\xcd\xc9\xce\xd5\xd4\xc1",
6846     "\xcd\xc9\xce\xd5\xd4"];
6847     $$d{"seconds"} =["\xd3","\xd3\xc5\xcb","\xd3\xc5\xcb\xd5\xce\xc4\xc1",
6848     "\xd3\xc5\xcb\xd5\xce\xc4"];
6849     $$d{"replace"} =[];
6850    
6851     $$d{"sephm"} ="[:\xde]";
6852     $$d{"sepms"} ="[:\xcd]";
6853     $$d{"sepss"} ="[:.\xd3]";
6854    
6855     $$d{"am"} = ["\xc4\xd0","${a}\xf0","${a}.\xf0.","\xce\xcf\xde\xc9",
6856     "\xd5\xd4\xd2\xc1",
6857     "\xc4\xcf \xd0\xcf\xcc\xd5\xc4\xce\xd1"];
6858     $$d{"pm"} = ["\xd0\xd0","\xf0\xf0","\xf0.\xf0.","\xc4\xce\xd1",
6859     "\xd7\xc5\xde\xc5\xd2\xc1",
6860     "\xd0\xcf\xd3\xcc\xc5 \xd0\xcf\xcc\xd5\xc4\xce\xd1",
6861     "\xd0\xcf \xd0\xcf\xcc\xd5\xc4\xce\xc0"];
6862     }
6863    
6864     sub Date_Init_Turkish {
6865     print "DEBUG: Date_Init_Turkish\n" if ($Curr{"Debug"} =~ /trace/);
6866     my($d)=@_;
6867    
6868     $$d{"month_name"}=
6869     [
6870     ["ocak","subat","mart","nisan","mayis","haziran",
6871     "temmuz","agustos","eylul","ekim","kasim","aralik"],
6872     ["ocak","\xfeubat","mart","nisan","may\xfds","haziran",
6873     "temmuz","a\xf0ustos","eyl\xfcl","ekim","kas\xfdm","aral\xfdk"]
6874     ];
6875    
6876     $$d{"month_abb"}=
6877     [
6878     ["oca","sub","mar","nis","may","haz",
6879     "tem","agu","eyl","eki","kas","ara"],
6880     ["oca","\xfeub","mar","nis","may","haz",
6881     "tem","a\xf0u","eyl","eki","kas","ara"]
6882     ];
6883    
6884     $$d{"day_name"}=
6885     [
6886     ["pazartesi","sali","carsamba","persembe","cuma","cumartesi","pazar"],
6887     ["pazartesi","sal\xfd","\xe7ar\xfeamba","per\xfeembe","cuma",
6888     "cumartesi","pazar"],
6889     ];
6890    
6891     $$d{"day_abb"}=
6892     [
6893     ["pzt","sal","car","per","cum","cts","paz"],
6894     ["pzt","sal","\xe7ar","per","cum","cts","paz"],
6895     ];
6896    
6897     $$d{"day_char"}=
6898     [["Pt","S","Cr","Pr","C","Ct","P"],
6899     ["Pt","S","\xc7","Pr","C","Ct","P"]];
6900    
6901     $$d{"num_suff"}=
6902     [[ "1.", "2.", "3.", "4.", "5.", "6.", "7.", "8.", "9.", "10.",
6903     "11.", "12.", "13.", "14.", "15.", "16.", "17.", "18.", "19.", "20.",
6904     "21.", "22.", "23.", "24.", "25.", "26.", "27.", "28.", "29.", "30.",
6905     "31."]];
6906    
6907     $$d{"num_word"}=
6908     [
6909     ["birinci","ikinci","ucuncu","dorduncu",
6910     "besinci","altinci","yedinci","sekizinci",
6911     "dokuzuncu","onuncu","onbirinci","onikinci",
6912     "onucuncu","ondordoncu",
6913     "onbesinci","onaltinci","onyedinci","onsekizinci",
6914     "ondokuzuncu","yirminci","yirmibirinci","yirmikinci",
6915     "yirmiucuncu","yirmidorduncu",
6916     "yirmibesinci","yirmialtinci","yirmiyedinci","yirmisekizinci",
6917     "yirmidokuzuncu","otuzuncu","otuzbirinci"],
6918     ["birinci","ikinci","\xfc\xe7\xfcnc\xfc","d\xf6rd\xfcnc\xfc",
6919     "be\xfeinci","alt\xfdnc\xfd","yedinci","sekizinci",
6920     "dokuzuncu","onuncu","onbirinci","onikinci",
6921     "on\xfc\xe7\xfcnc\xfc","ond\xf6rd\xfcnc\xfc",
6922     "onbe\xfeinci","onalt\xfdnc\xfd","onyedinci","onsekizinci",
6923     "ondokuzuncu","yirminci","yirmibirinci","yirmikinci",
6924     "yirmi\xfc\xe7\xfcnc\xfc","yirmid\xf6rd\xfcnc\xfc",
6925     "yirmibe\xfeinci","yirmialt\xfdnc\xfd","yirmiyedinci","yirmisekizinci",
6926     "yirmidokuzuncu","otuzuncu","otuzbirinci"]
6927     ];
6928    
6929     $$d{"now"} =["\xfeimdi", "simdi", "bugun","bug\xfcn"];
6930     $$d{"last"} =["son", "sonuncu"];
6931     $$d{"each"} =["her"];
6932     $$d{"of"} =["of"];
6933     $$d{"at"} =["saat"];
6934     $$d{"on"} =["on"];
6935     $$d{"future"} =["gelecek"];
6936     $$d{"past"} =["ge\xe7mi\xfe", "gecmis","gecen", "ge\xe7en"];
6937     $$d{"next"} =["gelecek","sonraki"];
6938     $$d{"prev"} =["onceki","\xf6nceki"];
6939     $$d{"later"} =["sonra"];
6940    
6941     $$d{"exact"} =["tam"];
6942     $$d{"approx"} =["yakla\xfe\xfdk", "yaklasik"];
6943     $$d{"business"}=["i\xfe","\xe7al\xfd\xfema","is", "calisma"];
6944    
6945     $$d{"offset"} =["d\xfcn","-0:0:0:1:0:0:0",
6946     "dun", "-0:0:0:1:0:0:0",
6947     "yar\xfdn","+0:0:0:1:0:0:0",
6948     "yarin","+0:0:0:1:0:0:0"];
6949    
6950     $$d{"times"} =["\xf6\xf0len","12:00:00",
6951     "oglen","12:00:00",
6952     "yarim","12:300:00",
6953     "yar\xfdm","12:30:00",
6954     "gece yar\xfds\xfd","00:00:00",
6955     "gece yarisi","00:00:00"];
6956    
6957     $$d{"years"} =["yil","y"];
6958     $$d{"months"} =["ay","a"];
6959     $$d{"weeks"} =["hafta", "h"];
6960     $$d{"days"} =["gun","g"];
6961     $$d{"hours"} =["saat"];
6962     $$d{"minutes"} =["dakika","dak","d"];
6963     $$d{"seconds"} =["saniye","sn",];
6964     $$d{"replace"} =["s","saat"];
6965    
6966     $$d{"sephm"} =':';
6967     $$d{"sepms"} =':';
6968     $$d{"sepss"} ='[.:,]';
6969    
6970     $$d{"am"} = ["\xf6gleden \xf6nce","ogleden once"];
6971     $$d{"pm"} = ["\xf6\xf0leden sonra","ogleden sonra"];
6972     }
6973    
6974     sub Date_Init_Danish {
6975     print "DEBUG: Date_Init_Danish\n" if ($Curr{"Debug"} =~ /trace/);
6976     my($d)=@_;
6977    
6978     $$d{"month_name"}=
6979     [["Januar","Februar","Marts","April","Maj","Juni",
6980     "Juli","August","September","Oktober","November","December"]];
6981     $$d{"month_abb"}=
6982     [["Jan","Feb","Mar","Apr","Maj","Jun",
6983     "Jul","Aug","Sep","Okt","Nov","Dec"]];
6984    
6985     $$d{"day_name"}=
6986     [["Mandag","Tirsdag","Onsdag","Torsdag","Fredag","Lordag","Sondag"],
6987     ["Mandag","Tirsdag","Onsdag","Torsdag","Fredag","L\xf8rdag","S\xf8ndag"]];
6988    
6989     $$d{"day_abb"}=
6990     [["Man","Tis","Ons","Tor","Fre","Lor","Son"],
6991     ["Man","Tis","Ons","Tor","Fre","L\xf8r","S\xf8n"]];
6992     $$d{"day_char"}=
6993     [["M","Ti","O","To","F","L","S"]];
6994    
6995     $$d{"num_suff"}=
6996     [["1:e","2:e","3:e","4:e","5:e","6:e","7:e","8:e","9:e","10:e",
6997     "11:e","12:e","13:e","14:e","15:e","16:e","17:e","18:e","19:e","20:e",
6998     "21:e","22:e","23:e","24:e","25:e","26:e","27:e","28:e","29:e","30:e",
6999     "31:e"]];
7000     $$d{"num_word"}=
7001     [["forste","anden","tredie","fjerde","femte","sjette","syvende",
7002     "ottende","niende","tiende","elfte","tolvte","trettende","fjortende",
7003     "femtende","sekstende","syttende","attende","nittende","tyvende",
7004     "enogtyvende","toogtyvende","treogtyvende","fireogtyvende","femogtyvende",
7005     "seksogtyvende","syvogtyvende","otteogtyvende","niogtyvende",
7006     "tredivte","enogtredivte"],
7007     ["f\xf8rste","anden","tredie","fjerde","femte","sjette","syvende",
7008     "ottende","niende","tiende","elfte","tolvte","trettende","fjortende",
7009     "femtende","sekstende","syttende","attende","nittende","tyvende",
7010     "enogtyvende","toogtyvende","treogtyvende","fireogtyvende","femogtyvende",
7011     "seksogtyvende","syvogtyvende","otteogtyvende","niogtyvende",
7012     "tredivte","enogtredivte"]];
7013    
7014     $$d{"now"} =["idag","nu"];
7015     $$d{"last"} =["forrige","sidste","nyeste"];
7016     $$d{"each"} =["hver"];
7017     $$d{"of"} =["om"];
7018     $$d{"at"} =["kl","kl.","klokken"];
7019     $$d{"on"} =["pa","p\xe5"];
7020     $$d{"future"} =["om"];
7021     $$d{"past"} =["siden"];
7022     $$d{"next"} =["nasta","n\xe6ste"];
7023     $$d{"prev"} =["forrige"];
7024     $$d{"later"} =["senere"];
7025    
7026     $$d{"exact"} =["pracist","pr\xe6cist"];
7027     $$d{"approx"} =["circa"];
7028     $$d{"business"}=["arbejdsdag","arbejdsdage"];
7029    
7030     $$d{"offset"} =["ig\xe5r","-0:0:0:1:0:0:0","igar","-0:0:0:1:0:0:0",
7031     "imorgen","+0:0:0:1:0:0:0"];
7032     $$d{"times"} =["midt pa dagen","12:00:00","midt p\xe5 dagen","12:00:00",
7033     "midnat","00:00:00"];
7034    
7035     $$d{"years"} =["ar","\xe5r"];
7036     $$d{"months"} =["man","maned","maneder","m\xe5n","m\xe5ned","m\xe5neder"];
7037     $$d{"weeks"} =["u","uge","uger"];
7038     $$d{"days"} =["d","dag","dage"];
7039     $$d{"hours"} =["t","tim","time","timer"];
7040     $$d{"minutes"} =["min","minut","minutter"];
7041     $$d{"seconds"} =["s","sek","sekund","sekunder"];
7042     $$d{"replace"} =["m","minut"];
7043    
7044     $$d{"sephm"} ='[.:]';
7045     $$d{"sepms"} =':';
7046     $$d{"sepss"} ='[.:]';
7047    
7048     $$d{"am"} = ["FM"];
7049     $$d{"pm"} = ["EM"];
7050     }
7051    
7052     ########################################################################
7053     # FROM MY PERSONAL LIBRARIES
7054     ########################################################################
7055    
7056     no integer;
7057    
7058     # &ModuloAddition($N,$add,\$val,\$rem);
7059     # This calculates $val=$val+$add and forces $val to be in a certain range.
7060     # This is useful for adding numbers for which only a certain range is
7061     # allowed (for example, minutes can be between 0 and 59 or months can be
7062     # between 1 and 12). The absolute value of $N determines the range and
7063     # the sign of $N determines whether the range is 0 to N-1 (if N>0) or
7064     # 1 to N (N<0). The remainder (as modulo N) is added to $rem.
7065     # Example:
7066     # To add 2 hours together (with the excess returned in days) use:
7067     # &ModuloAddition(60,$s1,\$s,\$day);
7068     sub ModuloAddition {
7069     my($N,$add,$val,$rem)=@_;
7070     return if ($N==0);
7071     $$val+=$add;
7072     if ($N<0) {
7073     # 1 to N
7074     $N = -$N;
7075     if ($$val>$N) {
7076     $$rem+= int(($$val-1)/$N);
7077     $$val = ($$val-1)%$N +1;
7078     } elsif ($$val<1) {
7079     $$rem-= int(-$$val/$N)+1;
7080     $$val = $N-(-$$val % $N);
7081     }
7082    
7083     } else {
7084     # 0 to N-1
7085     if ($$val>($N-1)) {
7086     $$rem+= int($$val/$N);
7087     $$val = $$val%$N;
7088     } elsif ($$val<0) {
7089     $$rem-= int(-($$val+1)/$N)+1;
7090     $$val = ($N-1)-(-($$val+1)%$N);
7091     }
7092     }
7093     }
7094    
7095     # $Flag=&IsInt($String [,$low, $high]);
7096     # Returns 1 if $String is a valid integer, 0 otherwise. If $low is
7097     # entered, $String must be >= $low. If $high is entered, $String must
7098     # be <= $high. It is valid to check only one of the bounds.
7099     sub IsInt {
7100     my($N,$low,$high)=@_;
7101     return 0 if (! defined $N or
7102     $N !~ /^\s*[-+]?\d+\s*$/ or
7103     defined $low && $N<$low or
7104     defined $high && $N>$high);
7105     return 1;
7106     }
7107    
7108     # $Pos=&SinLindex(\@List,$Str [,$offset [,$CaseInsensitive]]);
7109     # Searches for an exact string in a list.
7110     #
7111     # This is similar to RinLindex except that it searches for elements
7112     # which are exactly equal to $Str (possibly case insensitive).
7113     sub SinLindex {
7114     my($listref,$Str,$offset,$Insensitive)=@_;
7115     my($i,$len,$tmp)=();
7116     $len=$#$listref;
7117     return -2 if ($len<0 or ! $Str);
7118     return -1 if (&Index_First(\$offset,$len));
7119     $Str=uc($Str) if ($Insensitive);
7120     for ($i=$offset; $i<=$len; $i++) {
7121     $tmp=$$listref[$i];
7122     $tmp=uc($tmp) if ($Insensitive);
7123     return $i if ($tmp eq $Str);
7124     }
7125     return -1;
7126     }
7127    
7128     sub Index_First {
7129     my($offsetref,$max)=@_;
7130     $$offsetref=0 if (! $$offsetref);
7131     if ($$offsetref < 0) {
7132     $$offsetref += $max + 1;
7133     $$offsetref=0 if ($$offsetref < 0);
7134     }
7135     return -1 if ($$offsetref > $max);
7136     return 0;
7137     }
7138    
7139     # $File=&CleanFile($file);
7140     # This cleans up a path to remove the following things:
7141     # double slash /a//b -> /a/b
7142     # trailing dot /a/. -> /a
7143     # leading dot ./a -> a
7144     # trailing slash a/ -> a
7145     sub CleanFile {
7146     my($file)=@_;
7147     $file =~ s/\s*$//;
7148     $file =~ s/^\s*//;
7149     $file =~ s|//+|/|g; # multiple slash
7150     $file =~ s|/\.$|/|; # trailing /. (leaves trailing slash)
7151     $file =~ s|^\./|| # leading ./
7152     if ($file ne "./");
7153     $file =~ s|/$|| # trailing slash
7154     if ($file ne "/");
7155     return $file;
7156     }
7157    
7158     # $File=&ExpandTilde($file);
7159     # This checks to see if a "~" appears as the first character in a path.
7160     # If it does, the "~" expansion is interpreted (if possible) and the full
7161     # path is returned. If a "~" expansion is used but cannot be
7162     # interpreted, an empty string is returned.
7163     #
7164     # This is Windows/Mac friendly.
7165     # This is efficient.
7166     sub ExpandTilde {
7167     my($file)=shift;
7168     my($user,$home)=();
7169     # ~aaa/bbb= ~ aaa /bbb
7170     if ($file =~ s|^~([^/]*)||) {
7171     $user=$1;
7172     # Single user operating systems (Mac, MSWindows) don't have the getpwnam
7173     # and getpwuid routines defined. Try to catch various different ways
7174     # of knowing we are on one of these systems:
7175     return "" if ($OS eq "Windows" or
7176     $OS eq "Mac" or
7177     $OS eq "Netware" or
7178     $OS eq "MPE");
7179     $user="" if (! defined $user);
7180    
7181     if ($user) {
7182     $home= (getpwnam($user))[7];
7183     } else {
7184     $home= (getpwuid($<))[7];
7185     }
7186     $home = VMS::Filespec::unixpath($home) if ($OS eq "VMS");
7187     return "" if (! $home);
7188     $file="$home/$file";
7189     }
7190     $file;
7191     }
7192    
7193     # $File=&FullFilePath($file);
7194     # Returns the full or relative path to $file (expanding "~" if necessary).
7195     # Returns an empty string if a "~" expansion cannot be interpreted. The
7196     # path does not need to exist. CleanFile is called.
7197     sub FullFilePath {
7198     my($file)=shift;
7199     my($rootpat) = '^/'; #default pattern to match absolute path
7200     $rootpat = '^(\\|/|([A-Za-z]:[\\/]))' if ($OS eq 'Windows');
7201     $file=&ExpandTilde($file);
7202     return "" if (! $file);
7203     return &CleanFile($file);
7204     }
7205    
7206     # $Flag=&CheckFilePath($file [,$mode]);
7207     # Checks to see if $file exists, to see what type it is, and whether
7208     # the script can access it. If it exists and has the correct mode, 1
7209     # is returned.
7210     #
7211     # $mode is a string which may contain any of the valid file test operator
7212     # characters except t, M, A, C. The appropriate test is run for each
7213     # character. For example, if $mode is "re" the -r and -e tests are both
7214     # run.
7215     #
7216     # An empty string is returned if the file doesn't exist. A 0 is returned
7217     # if the file exists but any test fails.
7218     #
7219     # All characters in $mode which do not correspond to valid tests are
7220     # ignored.
7221     sub CheckFilePath {
7222     my($file,$mode)=@_;
7223     my($test)=();
7224     $file=&FullFilePath($file);
7225     $mode = "" if (! defined $mode);
7226    
7227     # Run tests
7228     return 0 if (! defined $file or ! $file);
7229     return 0 if (( ! -e $file) or
7230     ($mode =~ /r/ && ! -r $file) or
7231     ($mode =~ /w/ && ! -w $file) or
7232     ($mode =~ /x/ && ! -x $file) or
7233     ($mode =~ /R/ && ! -R $file) or
7234     ($mode =~ /W/ && ! -W $file) or
7235     ($mode =~ /X/ && ! -X $file) or
7236     ($mode =~ /o/ && ! -o $file) or
7237     ($mode =~ /O/ && ! -O $file) or
7238     ($mode =~ /z/ && ! -z $file) or
7239     ($mode =~ /s/ && ! -s $file) or
7240     ($mode =~ /f/ && ! -f $file) or
7241     ($mode =~ /d/ && ! -d $file) or
7242     ($mode =~ /l/ && ! -l $file) or
7243     ($mode =~ /s/ && ! -s $file) or
7244     ($mode =~ /p/ && ! -p $file) or
7245     ($mode =~ /b/ && ! -b $file) or
7246     ($mode =~ /c/ && ! -c $file) or
7247     ($mode =~ /u/ && ! -u $file) or
7248     ($mode =~ /g/ && ! -g $file) or
7249     ($mode =~ /k/ && ! -k $file) or
7250     ($mode =~ /T/ && ! -T $file) or
7251     ($mode =~ /B/ && ! -B $file));
7252     return 1;
7253     }
7254     #&&
7255    
7256     # $Path=&FixPath($path [,$full] [,$mode] [,$error]);
7257     # Makes sure that every directory in $path (a colon separated list of
7258     # directories) appears as a full path or relative path. All "~"
7259     # expansions are removed. All trailing slashes are removed also. If
7260     # $full is non-nil, relative paths are expanded to full paths as well.
7261     #
7262     # If $mode is given, it may be either "e", "r", or "w". In this case,
7263     # additional checking is done to each directory. If $mode is "e", it
7264     # need ony exist to pass the check. If $mode is "r", it must have have
7265     # read and execute permission. If $mode is "w", it must have read,
7266     # write, and execute permission.
7267     #
7268     # The value of $error determines what happens if the directory does not
7269     # pass the test. If it is non-nil, if any directory does not pass the
7270     # test, the subroutine returns the empty string. Otherwise, it is simply
7271     # removed from $path.
7272     #
7273     # The corrected path is returned.
7274     sub FixPath {
7275     my($path,$full,$mode,$err)=@_;
7276     local($_)="";
7277     my(@dir)=split(/$Cnf{"PathSep"}/,$path);
7278     $full=0 if (! defined $full);
7279     $mode="" if (! defined $mode);
7280     $err=0 if (! defined $err);
7281     $path="";
7282     if ($mode eq "e") {
7283     $mode="de";
7284     } elsif ($mode eq "r") {
7285     $mode="derx";
7286     } elsif ($mode eq "w") {
7287     $mode="derwx";
7288     }
7289    
7290     foreach (@dir) {
7291    
7292     # Expand path
7293     if ($full) {
7294     $_=&FullFilePath($_);
7295     } else {
7296     $_=&ExpandTilde($_);
7297     }
7298     if (! $_) {
7299     return "" if ($err);
7300     next;
7301     }
7302    
7303     # Check mode
7304     if (! $mode or &CheckFilePath($_,$mode)) {
7305     $path .= $Cnf{"PathSep"} . $_;
7306     } else {
7307     return "" if ($err);
7308     }
7309     }
7310     $path =~ s/^$Cnf{"PathSep"}//;
7311     return $path;
7312     }
7313     #&&
7314    
7315     # $File=&SearchPath($file,$path [,$mode] [,@suffixes]);
7316     # Searches through directories in $path for a file named $file. The
7317     # full path is returned if one is found, or an empty string otherwise.
7318     # The file may exist with one of the @suffixes. The mode is checked
7319     # similar to &CheckFilePath.
7320     #
7321     # The first full path that matches the name and mode is returned. If none
7322     # is found, an empty string is returned.
7323     sub SearchPath {
7324     my($file,$path,$mode,@suff)=@_;
7325     my($f,$s,$d,@dir,$fs)=();
7326     $path=&FixPath($path,1,"r");
7327     @dir=split(/$Cnf{"PathSep"}/,$path);
7328     foreach $d (@dir) {
7329     $f="$d/$file";
7330     $f=~ s|//|/|g;
7331     return $f if (&CheckFilePath($f,$mode));
7332     foreach $s (@suff) {
7333     $fs="$f.$s";
7334     return $fs if (&CheckFilePath($fs,$mode));
7335     }
7336     }
7337     return "";
7338     }
7339    
7340     # @list=&ReturnList($str);
7341     # This takes a string which should be a comma separated list of integers
7342     # or ranges (5-7). It returns a sorted list of all integers referred to
7343     # by the string, or () if there is an invalid element.
7344     #
7345     # Negative integers are also handled. "-2--1" is equivalent to "-2,-1".
7346     sub ReturnList {
7347     my($str)=@_;
7348     my(@ret,@str,$from,$to,$tmp)=();
7349     @str=split(/,/,$str);
7350     foreach $str (@str) {
7351     if ($str =~ /^[-+]?\d+$/) {
7352     push(@ret,$str);
7353     } elsif ($str =~ /^([-+]?\d+)-([-+]?\d+)$/) {
7354     ($from,$to)=($1,$2);
7355     if ($from>$to) {
7356     $tmp=$from;
7357     $from=$to;
7358     $to=$tmp;
7359     }
7360     push(@ret,$from..$to);
7361     } else {
7362     return ();
7363     }
7364     }
7365     @ret;
7366     }
7367    
7368     1;