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