diff --git a/docs/changelog/6.x.x.txt b/docs/changelog/6.x.x.txt index 02c747400..b2cc6d585 100644 --- a/docs/changelog/6.x.x.txt +++ b/docs/changelog/6.x.x.txt @@ -78,6 +78,9 @@ the shortcut. - bugfix [ 1016271 ]. all posts go to last forum in MB (Leendert Bottelberghs). - Added FastCGI support. (Kevin Wilson) + - Replaced Date::Calc with Date::Manip to allow for dates before 1970 and + after 2035. (Emiliano Bruni) + - Added Date::Manip to the WebGUI distribution. 6.1.1 diff --git a/docs/credits.txt b/docs/credits.txt index 415bd277a..be29b7779 100644 --- a/docs/credits.txt +++ b/docs/credits.txt @@ -53,6 +53,8 @@ Convert::ASN1........................Graham Barr Data::Config.........................Sébastien Aperghis-Tramoni +Date::Manip..........................Sullivan Beck + DBIx::FullTextSearch.................T.J. Mather DBIx::Tree::NestedSet................Dan Collis Puro diff --git a/docs/install.txt b/docs/install.txt index 7e3256b27..3ca1ba108 100644 --- a/docs/install.txt +++ b/docs/install.txt @@ -18,7 +18,6 @@ QnD INSTALL INSTRUCTIONS: DBI DBD::mysql Digest::MD5 - Date::Calc HTML::Parser Archive::Tar Compress::Zlib diff --git a/lib/Date/Manip.pm b/lib/Date/Manip.pm new file mode 100644 index 000000000..7f9e87e64 --- /dev/null +++ b/lib/Date/Manip.pm @@ -0,0 +1,7362 @@ +package Date::Manip; +# Copyright (c) 1995-2003 Sullivan Beck. All rights reserved. +# This program is free software; you can redistribute it and/or modify it +# under the same terms as Perl itself. + +########################################################################### +########################################################################### + +use vars qw($OS %Lang %Holiday %Events %Curr %Cnf %Zone $VERSION @ISA @EXPORT); + +# Determine the type of OS... +$OS="Unix"; +$OS="Windows" if ((defined $^O and + $^O =~ /MSWin32/i || + $^O =~ /Windows_95/i || + $^O =~ /Windows_NT/i) || + (defined $ENV{OS} and + $ENV{OS} =~ /MSWin32/i || + $ENV{OS} =~ /Windows_95/i || + $ENV{OS} =~ /Windows_NT/i)); +$OS="Netware" if (defined $^O and + $^O =~ /NetWare/i); +$OS="Mac" if ((defined $^O and + $^O =~ /MacOS/i) || + (defined $ENV{OS} and + $ENV{OS} =~ /MacOS/i)); +$OS="MPE" if (defined $^O and + $^O =~ /MPE/i); +$OS="OS2" if (defined $^O and + $^O =~ /os2/i); +$OS="VMS" if (defined $^O and + $^O =~ /VMS/i); + +# Determine if we're doing taint checking +$Date$^W; unlink "$^X$^T"; 1 }; + +########################################################################### +# CUSTOMIZATION +########################################################################### +# +# See the section of the POD documentation section CUSTOMIZING DATE::MANIP +# below for a complete description of each of these variables. + + +# Location of a the global config file. Tilde (~) expansions are allowed. +# This should be set in Date_Init arguments. +$Cnf{"GlobalCnf"}=""; +$Cnf{"IgnoreGlobalCnf"}=""; + +# Name of a personal config file and the path to search for it. Tilde (~) +# expansions are allowed. This should be set in Date_Init arguments or in +# the global config file. + +@Date::Manip::DatePath=(); +if ($OS eq "Windows") { + $Cnf{"PathSep"} = ";"; + $Cnf{"PersonalCnf"} = "Manip.cnf"; + $Cnf{"PersonalCnfPath"} = "."; + +} elsif ($OS eq "Netware") { + $Cnf{"PathSep"} = ";"; + $Cnf{"PersonalCnf"} = "Manip.cnf"; + $Cnf{"PersonalCnfPath"} = "."; + +} elsif ($OS eq "MPE") { + $Cnf{"PathSep"} = ":"; + $Cnf{"PersonalCnf"} = "Manip.cnf"; + $Cnf{"PersonalCnfPath"} = "."; + +} elsif ($OS eq "OS2") { + $Cnf{"PathSep"} = ":"; + $Cnf{"PersonalCnf"} = "Manip.cnf"; + $Cnf{"PersonalCnfPath"} = "."; + +} elsif ($OS eq "Mac") { + $Cnf{"PathSep"} = ":"; + $Cnf{"PersonalCnf"} = "Manip.cnf"; + $Cnf{"PersonalCnfPath"} = "."; + +} elsif ($OS eq "VMS") { + # VMS doesn't like files starting with "." + $Cnf{"PathSep"} = "\n"; + $Cnf{"PersonalCnf"} = "Manip.cnf"; + $Cnf{"PersonalCnfPath"} = ".\n~"; + +} else { + # Unix + $Cnf{"PathSep"} = ":"; + $Cnf{"PersonalCnf"} = ".DateManip.cnf"; + $Cnf{"PersonalCnfPath"} = ".:~"; + @Date::Manip::DatePath=qw(/bin /usr/bin /usr/local/bin); +} + +### Date::Manip variables set in the global or personal config file + +# Which language to use when parsing dates. +$Cnf{"Language"}="English"; + +# 12/10 = Dec 10 (US) or Oct 12 (anything else) +$Cnf{"DateFormat"}="US"; + +# Local timezone +$Cnf{"TZ"}=""; + +# Timezone to work in (""=local, "IGNORE", or a timezone) +$Cnf{"ConvTZ"}=""; + +# Date::Manip internal format (0=YYYYMMDDHH:MN:SS, 1=YYYYHHMMDDHHMNSS) +$Cnf{"Internal"}=0; + +# First day of the week (1=monday, 7=sunday). ISO 8601 says monday. +$Cnf{"FirstDay"}=1; + +# First and last day of the work week (1=monday, 7=sunday) +$Cnf{"WorkWeekBeg"}=1; +$Cnf{"WorkWeekEnd"}=5; + +# If non-nil, a work day is treated as 24 hours long (WorkDayBeg/WorkDayEnd +# ignored) +$Cnf{"WorkDay24Hr"}=0; + +# Start and end time of the work day (any time format allowed, seconds +# ignored) +$Cnf{"WorkDayBeg"}="08:00"; +$Cnf{"WorkDayEnd"}="17:00"; + +# If "today" is a holiday, we look either to "tomorrow" or "yesterday" for +# the nearest business day. By default, we'll always look "tomorrow" +# first. +$Cnf{"TomorrowFirst"}=1; + +# Erase the old holidays +$Cnf{"EraseHolidays"}=""; + +# Set this to non-zero to be produce completely backwards compatible deltas +$Cnf{"DeltaSigns"}=0; + +# If this is 0, use the ISO 8601 standard that Jan 4 is in week 1. If 1, +# make week 1 contain Jan 1. +$Cnf{"Jan1Week1"}=0; + +# 2 digit years fall into the 100 year period given by [ CURR-N, +# CURR+(99-N) ] where N is 0-99. Default behavior is 89, but other useful +# numbers might be 0 (forced to be this year or later) and 99 (forced to be +# this year or earlier). It can also be set to "c" (current century) or +# "cNN" (i.e. c18 forces the year to bet 1800-1899). Also accepts the +# form cNNNN to give the 100 year period NNNN to NNNN+99. +$Cnf{"YYtoYYYY"}=89; + +# Set this to 1 if you want a long-running script to always update the +# timezone. This will slow Date::Manip down. Read the POD documentation. +$Cnf{"UpdateCurrTZ"}=0; + +# Use an international character set. +$Cnf{"IntCharSet"}=0; + +# Use this to force the current date to be set to this: +$Cnf{"ForceDate"}=""; + +########################################################################### + +require 5.000; +require Exporter; +@ISA = qw(Exporter); +@EXPORT = qw( + DateManipVersion + Date_Init + ParseDateString + ParseDate + ParseRecur + Date_Cmp + DateCalc + ParseDateDelta + UnixDate + Delta_Format + Date_GetPrev + Date_GetNext + Date_SetTime + Date_SetDateField + Date_IsHoliday + Events_List + + Date_DaysInMonth + Date_DayOfWeek + Date_SecsSince1970 + Date_SecsSince1970GMT + Date_DaysSince1BC + Date_DayOfYear + Date_DaysInYear + Date_WeekOfYear + Date_LeapYear + Date_DaySuffix + Date_ConvTZ + Date_TimeZone + Date_IsWorkDay + Date_NextWorkDay + Date_PrevWorkDay + Date_NearestWorkDay + Date_NthDayOfYear +); +use strict; +use integer; +use Carp; + +use IO::File; + +$VERSION="5.42"; + +######################################################################## +######################################################################## + +$Curr{"InitLang"} = 1; # Whether a language is being init'ed +$Curr{"InitDone"} = 0; # Whether Init_Date has been called +$Curr{"InitFilesRead"} = 0; +$Curr{"ResetWorkDay"} = 1; +$Curr{"Debug"} = ""; +$Curr{"DebugVal"} = ""; + +$Holiday{"year"} = 0; +$Holiday{"dates"} = {}; +$Holiday{"desc"} = {}; + +$Events{"raw"} = []; +$Events{"parsed"} = 0; +$Events{"dates"} = []; +$Events{"recur"} = []; + +######################################################################## +######################################################################## +# THESE ARE THE MAIN ROUTINES +######################################################################## +######################################################################## + +# Get rid of a problem with old versions of perl +no strict "vars"; +# This sorts from longest to shortest element +sub sortByLength { + return (length $b <=> length $a); +} +use strict "vars"; + +sub DateManipVersion { + print "DEBUG: DateManipVersion\n" if ($Curr{"Debug"} =~ /trace/); + return $VERSION; +} + +sub Date_Init { + print "DEBUG: Date_Init\n" if ($Curr{"Debug"} =~ /trace/); + $Curr{"Debug"}=""; + + my(@args)=@_; + $Curr{"InitDone"}=1; + local($_)=(); + my($internal,$firstday)=(); + my($var,$val,$file,@tmp)=(); + + # InitFilesRead = 0 : no conf files read yet + # 1 : global read, no personal read + # 2 : personal read + + $Cnf{"EraseHolidays"}=0; + foreach (@args) { + s/\s*$//; + s/^\s*//; + /^(\S+) \s* = \s* (.+)$/x; + ($var,$val)=($1,$2); + if ($var =~ /^GlobalCnf$/i) { + $Cnf{"GlobalCnf"}=$val; + if ($val) { + $Curr{"InitFilesRead"}=0; + &EraseHolidays(); + } + } elsif ($var =~ /^PathSep$/i) { + $Cnf{"PathSep"}=$val; + } elsif ($var =~ /^PersonalCnf$/i) { + $Cnf{"PersonalCnf"}=$val; + $Curr{"InitFilesRead"}=1 if ($Curr{"InitFilesRead"}==2); + } elsif ($var =~ /^PersonalCnfPath$/i) { + $Cnf{"PersonalCnfPath"}=$val; + $Curr{"InitFilesRead"}=1 if ($Curr{"InitFilesRead"}==2); + } elsif ($var =~ /^IgnoreGlobalCnf$/i) { + $Curr{"InitFilesRead"}=1 if ($Curr{"InitFilesRead"}==0); + $Cnf{"IgnoreGlobalCnf"}=1; + } elsif ($var =~ /^EraseHolidays$/i) { + &EraseHolidays(); + } else { + push(@tmp,$_); + } + } + @args=@tmp; + + # Read global config file + if ($Curr{"InitFilesRead"}<1 && ! $Cnf{"IgnoreGlobalCnf"}) { + $Curr{"InitFilesRead"}=1; + + if ($Cnf{"GlobalCnf"}) { + $file=&ExpandTilde($Cnf{"GlobalCnf"}); + &Date_InitFile($file) if ($file); + } + } + + # Read personal config file + if ($Curr{"InitFilesRead"}<2) { + $Curr{"InitFilesRead"}=2; + + if ($Cnf{"PersonalCnf"} and $Cnf{"PersonalCnfPath"}) { + $file=&SearchPath($Cnf{"PersonalCnf"},$Cnf{"PersonalCnfPath"},"r"); + &Date_InitFile($file) if ($file); + } + } + + foreach (@args) { + s/\s*$//; + s/^\s*//; + /^(\S+) \s* = \s* (.*)$/x; + ($var,$val)=($1,$2); + $val="" if (! defined $val); + &Date_SetConfigVariable($var,$val); + } + + confess "ERROR: Unknown FirstDay in Date::Manip.\n" + if (! &IsInt($Cnf{"FirstDay"},1,7)); + confess "ERROR: Unknown WorkWeekBeg in Date::Manip.\n" + if (! &IsInt($Cnf{"WorkWeekBeg"},1,7)); + confess "ERROR: Unknown WorkWeekEnd in Date::Manip.\n" + if (! &IsInt($Cnf{"WorkWeekEnd"},1,7)); + confess "ERROR: Invalid WorkWeek in Date::Manip.\n" + if ($Cnf{"WorkWeekEnd"} <= $Cnf{"WorkWeekBeg"}); + + my(%lang, + $tmp,%tmp,$tmp2,@tmp2, + $i,$j,@tmp3, + $zonesrfc,@zones)=(); + + my($L)=$Cnf{"Language"}; + + if ($Curr{"InitLang"}) { + $Curr{"InitLang"}=0; + + if ($L eq "English") { + &Date_Init_English(\%lang); + + } elsif ($L eq "French") { + &Date_Init_French(\%lang); + + } elsif ($L eq "Swedish") { + &Date_Init_Swedish(\%lang); + + } elsif ($L eq "German") { + &Date_Init_German(\%lang); + + } elsif ($L eq "Polish") { + &Date_Init_Polish(\%lang); + + } elsif ($L eq "Dutch" || + $L eq "Nederlands") { + &Date_Init_Dutch(\%lang); + + } elsif ($L eq "Spanish") { + &Date_Init_Spanish(\%lang); + + } elsif ($L eq "Portuguese") { + &Date_Init_Portuguese(\%lang); + + } elsif ($L eq "Romanian") { + &Date_Init_Romanian(\%lang); + + } elsif ($L eq "Italian") { + &Date_Init_Italian(\%lang); + + } elsif ($L eq "Russian") { + &Date_Init_Russian(\%lang); + + } elsif ($L eq "Turkish") { + &Date_Init_Turkish(\%lang); + + } elsif ($L eq "Danish") { + &Date_Init_Danish(\%lang); + + } else { + confess "ERROR: Unknown language in Date::Manip.\n"; + } + + # variables for months + # Month = "(jan|january|feb|february ... )" + # MonL = [ "Jan","Feb",... ] + # MonthL = [ "January","February", ... ] + # MonthH = { "january"=>1, "jan"=>1, ... } + + $Lang{$L}{"MonthH"}={}; + $Lang{$L}{"MonthL"}=[]; + $Lang{$L}{"MonL"}=[]; + &Date_InitLists([$lang{"month_name"}, + $lang{"month_abb"}], + \$Lang{$L}{"Month"},"lc,sort,back", + [$Lang{$L}{"MonthL"}, + $Lang{$L}{"MonL"}], + [$Lang{$L}{"MonthH"},1]); + + # variables for day of week + # Week = "(mon|monday|tue|tuesday ... )" + # WL = [ "M","T",... ] + # WkL = [ "Mon","Tue",... ] + # WeekL = [ "Monday","Tudesday",... ] + # WeekH = { "monday"=>1,"mon"=>1,"m"=>1,... } + + $Lang{$L}{"WeekH"}={}; + $Lang{$L}{"WeekL"}=[]; + $Lang{$L}{"WkL"}=[]; + $Lang{$L}{"WL"}=[]; + &Date_InitLists([$lang{"day_name"}, + $lang{"day_abb"}], + \$Lang{$L}{"Week"},"lc,sort,back", + [$Lang{$L}{"WeekL"}, + $Lang{$L}{"WkL"}], + [$Lang{$L}{"WeekH"},1]); + &Date_InitLists([$lang{"day_char"}], + "","lc", + [$Lang{$L}{"WL"}], + [\%tmp,1]); + %{ $Lang{$L}{"WeekH"} } = + (%{ $Lang{$L}{"WeekH"} },%tmp); + + # variables for last + # Last = "(last)" + # LastL = [ "last" ] + # Each = "(each)" + # EachL = [ "each" ] + # variables for day of month + # DoM = "(1st|first ... 31st)" + # DoML = [ "1st","2nd",... "31st" ] + # DoMH = { "1st"=>1,"first"=>1, ... "31st"=>31 } + # variables for week of month + # WoM = "(1st|first| ... 5th|last)" + # WoMH = { "1st"=>1, ... "5th"=>5,"last"=>-1 } + + $Lang{$L}{"LastL"}=$lang{"last"}; + &Date_InitStrings($lang{"last"}, + \$Lang{$L}{"Last"},"lc,sort"); + + $Lang{$L}{"EachL"}=$lang{"each"}; + &Date_InitStrings($lang{"each"}, + \$Lang{$L}{"Each"},"lc,sort"); + + $Lang{$L}{"DoMH"}={}; + $Lang{$L}{"DoML"}=[]; + &Date_InitLists([$lang{"num_suff"}, + $lang{"num_word"}], + \$Lang{$L}{"DoM"},"lc,sort,back,escape", + [$Lang{$L}{"DoML"}, + \@tmp], + [$Lang{$L}{"DoMH"},1]); + + @tmp=(); + foreach $tmp (keys %{ $Lang{$L}{"DoMH"} }) { + $tmp2=$Lang{$L}{"DoMH"}{$tmp}; + if ($tmp2<6) { + $Lang{$L}{"WoMH"}{$tmp} = $tmp2; + push(@tmp,$tmp); + } + } + foreach $tmp (@{ $Lang{$L}{"LastL"} }) { + $Lang{$L}{"WoMH"}{$tmp} = -1; + push(@tmp,$tmp); + } + &Date_InitStrings(\@tmp,\$Lang{$L}{"WoM"}, + "lc,sort,back,escape"); + + # variables for AM or PM + # AM = "(am)" + # PM = "(pm)" + # AmPm = "(am|pm)" + # AMstr = "AM" + # PMstr = "PM" + + &Date_InitStrings($lang{"am"},\$Lang{$L}{"AM"},"lc,sort,escape"); + &Date_InitStrings($lang{"pm"},\$Lang{$L}{"PM"},"lc,sort,escape"); + &Date_InitStrings([ @{$lang{"am"}},@{$lang{"pm"}} ],\$Lang{$L}{"AmPm"}, + "lc,back,sort,escape"); + $Lang{$L}{"AMstr"}=$lang{"am"}[0]; + $Lang{$L}{"PMstr"}=$lang{"pm"}[0]; + + # variables for expressions used in parsing deltas + # Yabb = "(?:y|yr|year|years)" + # Mabb = similar for months + # Wabb = similar for weeks + # Dabb = similar for days + # Habb = similar for hours + # MNabb = similar for minutes + # Sabb = similar for seconds + # Repl = { "abb"=>"replacement" } + # Whenever an abbreviation could potentially refer to two different + # strings (M standing for Minutes or Months), the abbreviation must + # be listed in Repl instead of in the appropriate Xabb values. This + # only applies to abbreviations which are substrings of other values + # (so there is no confusion between Mn and Month). + + &Date_InitStrings($lang{"years"} ,\$Lang{$L}{"Yabb"}, "lc,sort"); + &Date_InitStrings($lang{"months"} ,\$Lang{$L}{"Mabb"}, "lc,sort"); + &Date_InitStrings($lang{"weeks"} ,\$Lang{$L}{"Wabb"}, "lc,sort"); + &Date_InitStrings($lang{"days"} ,\$Lang{$L}{"Dabb"}, "lc,sort"); + &Date_InitStrings($lang{"hours"} ,\$Lang{$L}{"Habb"}, "lc,sort"); + &Date_InitStrings($lang{"minutes"},\$Lang{$L}{"MNabb"},"lc,sort"); + &Date_InitStrings($lang{"seconds"},\$Lang{$L}{"Sabb"}, "lc,sort"); + $Lang{$L}{"Repl"}={}; + &Date_InitHash($lang{"replace"},undef,"lc",$Lang{$L}{"Repl"}); + + # variables for special dates that are offsets from now + # Now = "(now|today)" + # Offset = "(yesterday|tomorrow)" + # OffsetH = { "yesterday"=>"-0:0:0:1:0:0:0",... ] + # Times = "(noon|midnight)" + # TimesH = { "noon"=>"12:00:00","midnight"=>"00:00:00" } + # SepHM = hour/minute separator + # SepMS = minute/second separator + # SepSS = second/fraction separator + + $Lang{$L}{"TimesH"}={}; + &Date_InitHash($lang{"times"}, + \$Lang{$L}{"Times"},"lc,sort,back", + $Lang{$L}{"TimesH"}); + &Date_InitStrings($lang{"now"},\$Lang{$L}{"Now"},"lc,sort"); + $Lang{$L}{"OffsetH"}={}; + &Date_InitHash($lang{"offset"}, + \$Lang{$L}{"Offset"},"lc,sort,back", + $Lang{$L}{"OffsetH"}); + $Lang{$L}{"SepHM"}=$lang{"sephm"}; + $Lang{$L}{"SepMS"}=$lang{"sepms"}; + $Lang{$L}{"SepSS"}=$lang{"sepss"}; + + # variables for time zones + # zones = regular expression with all zone names (EST) + # n2o = a hash of all parsable zone names with their offsets + # tzones = reguar expression with all tzdata timezones (US/Eastern) + # tz2z = hash of all tzdata timezones to full timezone (EST#EDT) + + $zonesrfc= + "idlw -1200 ". # International Date Line West + "nt -1100 ". # Nome + "hst -1000 ". # Hawaii Standard + "cat -1000 ". # Central Alaska + "ahst -1000 ". # Alaska-Hawaii Standard + "akst -0900 ". # Alaska Standard + "yst -0900 ". # Yukon Standard + "hdt -0900 ". # Hawaii Daylight + "akdt -0800 ". # Alaska Daylight + "ydt -0800 ". # Yukon Daylight + "pst -0800 ". # Pacific Standard + "pdt -0700 ". # Pacific Daylight + "mst -0700 ". # Mountain Standard + "mdt -0600 ". # Mountain Daylight + "cst -0600 ". # Central Standard + "cdt -0500 ". # Central Daylight + "est -0500 ". # Eastern Standard + "act -0500 ". # Brazil, Acre + "sat -0400 ". # Chile + "bot -0400 ". # Bolivia + "amt -0400 ". # Brazil, Amazon + "acst -0400 ". # Brazil, Acre Daylight + "edt -0400 ". # Eastern Daylight + "ast -0400 ". # Atlantic Standard + #"nst -0330 ". # Newfoundland Standard nst=North Sumatra +0630 + "nft -0330 ". # Newfoundland + #"gst -0300 ". # Greenland Standard gst=Guam Standard +1000 + #"bst -0300 ". # Brazil Standard bst=British Summer +0100 + "brt -0300 ". # Brazil Standard (official time) + "brst -0300 ". # Brazil Standard + "adt -0300 ". # Atlantic Daylight + "art -0300 ". # Argentina + "amst -0300 ". # Brazil, Amazon Daylight + "ndt -0230 ". # Newfoundland Daylight + "brst -0200 ". # Brazil Daylight (official time) + "fnt -0200 ". # Brazil, Fernando de Noronha + "at -0200 ". # Azores + "wat -0100 ". # West Africa + "fnst -0100 ". # Brazil, Fernando de Noronha Daylight + "gmt +0000 ". # Greenwich Mean + "ut +0000 ". # Universal + "utc +0000 ". # Universal (Coordinated) + "wet +0000 ". # Western European + "cet +0100 ". # Central European + "fwt +0100 ". # French Winter + "met +0100 ". # Middle European + "mez +0100 ". # Middle European + "mewt +0100 ". # Middle European Winter + "swt +0100 ". # Swedish Winter + "bst +0100 ". # British Summer bst=Brazil standard -0300 + "gb +0100 ". # GMT with daylight savings + "west +0000 ". # Western European Daylight + "eet +0200 ". # Eastern Europe, USSR Zone 1 + "cest +0200 ". # Central European Summer + "fst +0200 ". # French Summer + "ist +0200 ". # Israel standard + "mest +0200 ". # Middle European Summer + "mesz +0200 ". # Middle European Summer + "metdst +0200 ". # An alias for mest used by HP-UX + "sast +0200 ". # South African Standard + "sst +0200 ". # Swedish Summer sst=South Sumatra +0700 + "bt +0300 ". # Baghdad, USSR Zone 2 + "eest +0300 ". # Eastern Europe Summer + "eetedt +0300 ". # Eastern Europe, USSR Zone 1 + "idt +0300 ". # Israel Daylight + "msk +0300 ". # Moscow + "eat +0300 ". # East Africa + "it +0330 ". # Iran + "zp4 +0400 ". # USSR Zone 3 + "msd +0400 ". # Moscow Daylight + "zp5 +0500 ". # USSR Zone 4 + "ist +0530 ". # Indian Standard + "zp6 +0600 ". # USSR Zone 5 + "novst +0600 ". # Novosibirsk time zone, Russia + "nst +0630 ". # North Sumatra nst=Newfoundland Std -0330 + #"sst +0700 ". # South Sumatra, USSR Zone 6 sst=Swedish Summer +0200 + "javt +0700 ". # Java + "hkt +0800 ". # Hong Kong + "sgt +0800 ". # Singapore + "cct +0800 ". # China Coast, USSR Zone 7 + "awst +0800 ". # Australian Western Standard + "wst +0800 ". # West Australian Standard + "pht +0800 ". # Asia Manila + "kst +0900 ". # Republic of Korea + "jst +0900 ". # Japan Standard, USSR Zone 8 + "rok +0900 ". # Republic of Korea + "acst +0930 ". # Australian Central Standard + "cast +0930 ". # Central Australian Standard + "aest +1000 ". # Australian Eastern Standard + "east +1000 ". # Eastern Australian Standard + "gst +1000 ". # Guam Standard, USSR Zone 9 gst=Greenland Std -0300 + "acdt +1030 ". # Australian Central Daylight + "cadt +1030 ". # Central Australian Daylight + "aedt +1100 ". # Australian Eastern Daylight + "eadt +1100 ". # Eastern Australian Daylight + "idle +1200 ". # International Date Line East + "nzst +1200 ". # New Zealand Standard + "nzt +1200 ". # New Zealand + "nzdt +1300 ". # New Zealand Daylight + "z +0000 ". + "a +0100 b +0200 c +0300 d +0400 e +0500 f +0600 g +0700 h +0800 ". + "i +0900 k +1000 l +1100 m +1200 ". + "n -0100 o -0200 p -0300 q -0400 r -0500 s -0600 t -0700 u -0800 ". + "v -0900 w -1000 x -1100 y -1200"; + + $Zone{"n2o"} = {}; + ($Zone{"zones"},%{ $Zone{"n2o"} })= + &Date_Regexp($zonesrfc,"sort,lc,under,back", + "keys"); + + $tmp= + "US/Pacific PST8PDT ". + "US/Mountain MST7MDT ". + "US/Central CST6CDT ". + "US/Eastern EST5EDT ". + "Canada/Pacific PST8PDT ". + "Canada/Mountain MST7MDT ". + "Canada/Central CST6CDT ". + "Canada/Eastern EST5EDT"; + + $Zone{"tz2z"} = {}; + ($Zone{"tzones"},%{ $Zone{"tz2z"} })= + &Date_Regexp($tmp,"lc,under,back","keys"); + $Cnf{"TZ"}=&Date_TimeZone; + + # misc. variables + # At = "(?:at)" + # Of = "(?:in|of)" + # On = "(?:on)" + # Future = "(?:in)" + # Later = "(?:later)" + # Past = "(?:ago)" + # Next = "(?:next)" + # Prev = "(?:last|previous)" + + &Date_InitStrings($lang{"at"}, \$Lang{$L}{"At"}, "lc,sort"); + &Date_InitStrings($lang{"on"}, \$Lang{$L}{"On"}, "lc,sort"); + &Date_InitStrings($lang{"future"},\$Lang{$L}{"Future"}, "lc,sort"); + &Date_InitStrings($lang{"later"}, \$Lang{$L}{"Later"}, "lc,sort"); + &Date_InitStrings($lang{"past"}, \$Lang{$L}{"Past"}, "lc,sort"); + &Date_InitStrings($lang{"next"}, \$Lang{$L}{"Next"}, "lc,sort"); + &Date_InitStrings($lang{"prev"}, \$Lang{$L}{"Prev"}, "lc,sort"); + &Date_InitStrings($lang{"of"}, \$Lang{$L}{"Of"}, "lc,sort"); + + # calc mode variables + # Approx = "(?:approximately)" + # Exact = "(?:exactly)" + # Business = "(?:business)" + + &Date_InitStrings($lang{"exact"}, \$Lang{$L}{"Exact"}, "lc,sort"); + &Date_InitStrings($lang{"approx"}, \$Lang{$L}{"Approx"}, "lc,sort"); + &Date_InitStrings($lang{"business"},\$Lang{$L}{"Business"},"lc,sort"); + + ############### END OF LANGUAGE INITIALIZATION + } + + if ($Curr{"ResetWorkDay"}) { + my($h1,$m1,$h2,$m2)=(); + if ($Cnf{"WorkDay24Hr"}) { + ($Curr{"WDBh"},$Curr{"WDBm"})=(0,0); + ($Curr{"WDEh"},$Curr{"WDEm"})=(24,0); + $Curr{"WDlen"}=24*60; + $Cnf{"WorkDayBeg"}="00:00"; + $Cnf{"WorkDayEnd"}="23:59"; + + } else { + confess "ERROR: Invalid WorkDayBeg in Date::Manip.\n" + if (! (($h1,$m1)=&CheckTime($Cnf{"WorkDayBeg"}))); + $Cnf{"WorkDayBeg"}="$h1:$m1"; + confess "ERROR: Invalid WorkDayEnd in Date::Manip.\n" + if (! (($h2,$m2)=&CheckTime($Cnf{"WorkDayEnd"}))); + $Cnf{"WorkDayEnd"}="$h2:$m2"; + + ($Curr{"WDBh"},$Curr{"WDBm"})=($h1,$m1); + ($Curr{"WDEh"},$Curr{"WDEm"})=($h2,$m2); + + # Work day length = h1:m1 or 0:len (len minutes) + $h1=$h2-$h1; + $m1=$m2-$m1; + if ($m1<0) { + $h1--; + $m1+=60; + } + $Curr{"WDlen"}=$h1*60+$m1; + } + $Curr{"ResetWorkDay"}=0; + } + + # current time + my($s,$mn,$h,$d,$m,$y,$wday,$yday,$isdst,$ampm,$wk)=(); + if ($Cnf{"ForceDate"}=~ + /^(\d{4})-(\d{2})-(\d{2})-(\d{2}):(\d{2}):(\d{2})$/) { + ($y,$m,$d,$h,$mn,$s)=($1,$2,$3,$4,$5,$6); + } else { + ($s,$mn,$h,$d,$m,$y,$wday,$yday,$isdst)=localtime(time); + $y+=1900; + $m++; + } + &Date_DateCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk); + $Curr{"Y"}=$y; + $Curr{"M"}=$m; + $Curr{"D"}=$d; + $Curr{"H"}=$h; + $Curr{"Mn"}=$mn; + $Curr{"S"}=$s; + $Curr{"AmPm"}=$ampm; + $Curr{"Now"}=&Date_Join($y,$m,$d,$h,$mn,$s); + + $Curr{"Debug"}=$Curr{"DebugVal"}; + + # If we're in array context, let's return a list of config variables + # that could be passed to Date_Init to get the same state as we're + # currently in. + if (wantarray) { + # Some special variables that have to be in a specific order + my(@special)=qw(IgnoreGlobalCnf GlobalCnf PersonalCnf PersonalCnfPath); + my(%tmp)=map { $_,1 } @special; + my(@tmp,$key,$val); + foreach $key (@special) { + $val=$Cnf{$key}; + push(@tmp,"$key=$val"); + } + foreach $key (keys %Cnf) { + next if (exists $tmp{$key}); + $val=$Cnf{$key}; + push(@tmp,"$key=$val"); + } + return @tmp; + } + return (); +} + +sub ParseDateString { + print "DEBUG: ParseDateString\n" if ($Curr{"Debug"} =~ /trace/); + local($_)=@_; + return "" if (! $_); + + my($y,$m,$d,$h,$mn,$s,$i,$wofm,$dofw,$wk,$tmp,$z,$num,$err,$iso,$ampm)=(); + my($date,$z2,$delta,$from,$falsefrom,$to,$which,$midnight)=(); + + # We only need to reinitialize if we have to determine what NOW is. + &Date_Init() if (! $Curr{"InitDone"} or $Cnf{"UpdateCurrTZ"}); + + my($L)=$Cnf{"Language"}; + my($type)=$Cnf{"DateFormat"}; + + # Mode is set in DateCalc. ParseDate only overrides it if the string + # contains a mode. + if ($Lang{$L}{"Exact"} && + s/$Lang{$L}{"Exact"}//) { + $Curr{"Mode"}=0; + } elsif ($Lang{$L}{"Approx"} && + s/$Lang{$L}{"Approx"}//) { + $Curr{"Mode"}=1; + } elsif ($Lang{$L}{"Business"} && + s/$Lang{$L}{"Business"}//) { + $Curr{"Mode"}=2; + } elsif (! exists $Curr{"Mode"}) { + $Curr{"Mode"}=0; + } + + # Unfortunately, some deltas can be parsed as dates. An example is + # 1 second == 1 2nd == 1 2 + # But, some dates can be parsed as deltas. The most important being: + # 1998010101:00:00 + # We'll check to see if a "date" can be parsed as a delta. If so, we'll + # assume that it is a delta (since they are much simpler, it is much + # less likely that we'll mistake a delta for a date than vice versa) + # unless it is an ISO-8601 date. + # + # This is important because we are using DateCalc to test whether a + # string is a date or a delta. Dates are tested first, so we need to + # be able to pass a delta into this routine and have it correctly NOT + # interpreted as a date. + # + # We will insist that the string contain something other than digits and + # colons so that the following will get correctly interpreted as a date + # rather than a delta: + # 12:30 + # 19980101 + + $delta=""; + $delta=&ParseDateDelta($_) if (/[^:0-9]/); + + # Put parse in a simple loop for an easy exit. + PARSE: { + my(@tmp)=&Date_Split($_); + if (@tmp) { + ($y,$m,$d,$h,$mn,$s)=@tmp; + last PARSE; + } + + # Fundamental regular expressions + + my($month)=$Lang{$L}{"Month"}; # (jan|january|...) + my(%month)=%{ $Lang{$L}{"MonthH"} }; # { jan=>1, ... } + my($week)=$Lang{$L}{"Week"}; # (mon|monday|...) + my(%week)=%{ $Lang{$L}{"WeekH"} }; # { mon=>1, monday=>1, ... } + my($wom)=$Lang{$L}{"WoM"}; # (1st|...|fifth|last) + my(%wom)=%{ $Lang{$L}{"WoMH"} }; # { 1st=>1,... fifth=>5,last=>-1 } + my($dom)=$Lang{$L}{"DoM"}; # (1st|first|...31st) + my(%dom)=%{ $Lang{$L}{"DoMH"} }; # { 1st=>1, first=>1, ... } + my($ampmexp)=$Lang{$L}{"AmPm"}; # (am|pm) + my($timeexp)=$Lang{$L}{"Times"}; # (noon|midnight) + my($now)=$Lang{$L}{"Now"}; # (now|today) + my($offset)=$Lang{$L}{"Offset"}; # (yesterday|tomorrow) + my($zone)=$Zone{"zones"} . '(?:\s+|$)'; # (edt|est|...)\s+ + my($day)='\s*'.$Lang{$L}{"Dabb"}; # \s*(?:d|day|days) + my($mabb)='\s*'.$Lang{$L}{"Mabb"}; # \s*(?:mon|month|months) + my($wkabb)='\s*'.$Lang{$L}{"Wabb"}; # \s*(?:w|wk|week|weeks) + my($next)='\s*'.$Lang{$L}{"Next"}; # \s*(?:next) + my($prev)='\s*'.$Lang{$L}{"Prev"}; # \s*(?:last|previous) + my($past)='\s*'.$Lang{$L}{"Past"}; # \s*(?:ago) + my($future)='\s*'.$Lang{$L}{"Future"}; # \s*(?:in) + my($later)='\s*'.$Lang{$L}{"Later"}; # \s*(?:later) + my($at)=$Lang{$L}{"At"}; # (?:at) + my($of)='\s*'.$Lang{$L}{"Of"}; # \s*(?:in|of) + my($on)='(?:\s*'.$Lang{$L}{"On"}.'\s*|\s+)'; + # \s*(?:on)\s* or \s+ + my($last)='\s*'.$Lang{$L}{"Last"}; # \s*(?:last) + my($hm)=$Lang{$L}{"SepHM"}; # : + my($ms)=$Lang{$L}{"SepMS"}; # : + my($ss)=$Lang{$L}{"SepSS"}; # . + + # Other regular expressions + + my($D4)='(\d{4})'; # 4 digits (yr) + my($YY)='(\d{4}|\d{2})'; # 2 or 4 digits (yr) + my($DD)='(\d{2})'; # 2 digits (mon/day/hr/min/sec) + my($D) ='(\d{1,2})'; # 1 or 2 digit (mon/day/hr) + my($FS)="(?:$ss\\d+)?"; # fractional secs + my($sep)='[\/.-]'; # non-ISO8601 m/d/yy separators + # absolute time zone +0700 (GMT) + my($hzone)='(?:[0-1][0-9]|2[0-3])'; # 00 - 23 + my($mzone)='(?:[0-5][0-9])'; # 00 - 59 + my($zone2)='(?:\s*([+-](?:'."$hzone$mzone|$hzone:$mzone|$hzone))". + # +0700 +07:00 -07 + '(?:\s*\([^)]+\))?)'; # (GMT) + + # A regular expression for the time EXCEPT for the hour part + my($mnsec)="$hm$DD(?:$ms$DD$FS)?(?:\\s*$ampmexp)?"; + + # A special regular expression for /YYYY:HH:MN:SS used by Apache + my($apachetime)='(/\d{4}):' . "$DD$hm$DD$ms$DD"; + + my($time)=""; + $ampm=""; + $date=""; + + # Substitute all special time expressions. + if (/(^|[^a-z])$timeexp($|[^a-z])/i) { + $tmp=$2; + $tmp=$Lang{$L}{"TimesH"}{lc($tmp)}; + s/(^|[^a-z])$timeexp($|[^a-z])/$1 $tmp $3/i; + } + + # Remove some punctuation + s/[,]/ /g; + + # Make sure that ...7EST works (i.e. a timezone immediately following + # a digit. + s/(\d)$zone(\s+|$|[0-9])/$1 $2$3/i; + $zone = '\s+'.$zone; + + # Remove the time + $iso=1; + $midnight=0; + $from="24${hm}00(?:${ms}00)?"; + $falsefrom="${hm}24${ms}00"; # Don't trap XX:24:00 + $to="00${hm}00${ms}00"; + $midnight=1 if (!/$falsefrom/ && s/$from/$to/); + + $h=$mn=$s=0; + if (/$D$mnsec/i || /$ampmexp/i) { + $iso=0; + $tmp=0; + $tmp=1 if (/$mnsec$zone2?\s*$/i); # or /$mnsec$zone/ ?? + $tmp=0 if (/$ampmexp/i); + if (s/$apachetime$zone()/$1 /i || + s/$apachetime$zone2?/$1 /i || + s/(^|[^a-z])$at\s*$D$mnsec$zone()/$1 /i || + s/(^|[^a-z])$at\s*$D$mnsec$zone2?/$1 /i || + s/(^|[^0-9])(\d)$mnsec$zone()/$1 /i || + s/(^|[^0-9])(\d)$mnsec$zone2?/$1 /i || + (s/(t)$D$mnsec$zone()/$1 /i and (($iso=-$tmp) || 1)) || + (s/(t)$D$mnsec$zone2?/$1 /i and (($iso=-$tmp) || 1)) || + (s/()$DD$mnsec$zone()/ /i and (($iso=$tmp) || 1)) || + (s/()$DD$mnsec$zone2?/ /i and (($iso=$tmp) || 1)) || + s/(^|$at\s*|\s+)$D()()\s*$ampmexp$zone()/ /i || + s/(^|$at\s*|\s+)$D()()\s*$ampmexp$zone2?/ /i || + 0 + ) { + ($h,$mn,$s,$ampm,$z,$z2)=($2,$3,$4,$5,$6,$7); + if (defined ($z)) { + if ($z =~ /^[+-]\d{2}:\d{2}$/) { + $z=~ s/://; + } elsif ($z =~ /^[+-]\d{2}$/) { + $z .= "00"; + } + } + $time=1; + &Date_TimeCheck(\$h,\$mn,\$s,\$ampm); + $y=$m=$d=""; + # We're going to be calling TimeCheck again below (when we check the + # final date), so get rid of $ampm so that we don't have an error + # due to "15:30:00 PM". It'll get reset below. + $ampm=""; + if (/^\s*$/) { + &Date_Init() if (! $Cnf{"UpdateCurrTZ"}); + last PARSE; + } + } + } + $time=0 if ($time ne "1"); + s/\s+$//; + s/^\s+//; + + # dateTtime ISO 8601 formats + my($orig)=$_; + s/t$//i if ($iso<0); + + # Parse ISO 8601 dates now (which may still have a zone stuck to it). + if ( ($iso && /^([0-9-]+(?:W[0-9-]+)?)$zone?$/i) || + ($iso && /^([0-9-]+(?:W[0-9-]+)?)$zone2?$/i) || + ($iso && /^([0-9-]+(?:T[0-9-]+)?)$zone?$/i) || + ($iso && /^([0-9-]+(?:T[0-9-]+)?)$zone2?$/i) || + 0) { + + # ISO 8601 dates + ($_,$z,$z2) = ($1,$2); + s,-, ,g; # Change all ISO8601 seps to spaces + s/^\s+//; + s/\s+$//; + + if (/^$D4\s*$DD\s*$DD\s*t?$DD(?:$DD(?:$DD(\d*))?)?$/i || + /^$DD\s+$DD\s*$DD\s*t?$DD(?:$DD(?:$DD(\d*))?)?$/i || + 0 + ) { + # ISO 8601 Dates with times + # YYYYMMDDHHMNSSFFFF... + # YYYYMMDDHHMNSS + # YYYYMMDDHHMN + # YYYYMMDDHH + # YY MMDDHHMNSSFFFF... + # YY MMDDHHMNSS + # YY MMDDHHMN + # YY MMDDHH + ($y,$m,$d,$h,$mn,$s,$tmp)=($1,$2,$3,$4,$5,$6,$7); + if ($h==24 && (! defined $mn || $mn==0) && (! defined $s || $s==0)) { + $h=0; + $midnight=1; + } + $z = "" if (! defined $h); + return "" if ($time && defined $h); + last PARSE; + + } elsif (/^$D4(?:\s*$DD(?:\s*$DD)?)?$/ || + /^$DD(?:\s+$DD(?:\s*$DD)?)?$/) { + # ISO 8601 Dates + # YYYYMMDD + # YYYYMM + # YYYY + # YY MMDD + # YY MM + # YY + ($y,$m,$d)=($1,$2,$3); + last PARSE; + + } elsif (/^$YY\s+$D\s+$D/) { + # YY-M-D + ($y,$m,$d)=($1,$2,$3); + last PARSE; + + } elsif (/^$YY\s*W$DD\s*(\d)?$/i) { + # YY-W##-D + ($y,$wofm,$dofw)=($1,$2,$3); + ($y,$m,$d)=&Date_NthWeekOfYear($y,$wofm,$dofw); + last PARSE; + + } elsif (/^$D4\s*(\d{3})$/ || + /^$DD\s*(\d{3})$/) { + # YYDOY + ($y,$which)=($1,$2); + ($y,$m,$d)=&Date_NthDayOfYear($y,$which); + last PARSE; + + } elsif ($iso<0) { + # We confused something like 1999/August12:00:00 + # with a dateTtime format + $_=$orig; + + } else { + return ""; + } + } + + # All deltas that are not ISO-8601 dates are NOT dates. + return "" if ($Curr{"InCalc"} && $delta); + if ($delta) { + &Date_Init() if (! $Cnf{"UpdateCurrTZ"}); + return &DateCalc_DateDelta($Curr{"Now"},$delta); + } + + # Check for some special types of dates (next, prev) + foreach $from (keys %{ $Lang{$L}{"Repl"} }) { + $to=$Lang{$L}{"Repl"}{$from}; + s/(^|[^a-z])$from($|[^a-z])/$1$to$2/i; + } + if (/$wom/i || /$future/i || /$later/i || /$past/i || + /$next/i || /$prev/i || /^$week$/i || /$wkabb/i) { + $tmp=0; + + if (/^$wom\s*$week$of\s*$month\s*$YY?$/i) { + # last friday in October 95 + ($wofm,$dofw,$m,$y)=($1,$2,$3,$4); + # fix $m, $y + return "" if (&Date_DateCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk)); + $dofw=$week{lc($dofw)}; + $wofm=$wom{lc($wofm)}; + # Get the first day of the month + $date=&Date_Join($y,$m,1,$h,$mn,$s); + if ($wofm==-1) { + $date=&DateCalc_DateDelta($date,"+0:1:0:0:0:0:0",\$err,0); + $date=&Date_GetPrev($date,$dofw,0); + } else { + for ($i=0; $i<$wofm; $i++) { + if ($i==0) { + $date=&Date_GetNext($date,$dofw,1); + } else { + $date=&Date_GetNext($date,$dofw,0); + } + } + } + last PARSE; + + } elsif (/^$last$day$of\s*$month(?:$of?\s*$YY)?/i) { + # last day in month + ($m,$y)=($1,$2); + &Date_Init() if (! $Cnf{"UpdateCurrTZ"}); + $y=&Date_FixYear($y) if (! defined $y or length($y)<4); + $m=$month{lc($m)}; + $d=&Date_DaysInMonth($m,$y); + last PARSE; + + } elsif (/^$week$/i) { + # friday + ($dofw)=($1); + &Date_Init() if (! $Cnf{"UpdateCurrTZ"}); + $date=&Date_GetPrev($Curr{"Now"},$Cnf{"FirstDay"},1); + $date=&Date_GetNext($date,$dofw,1,$h,$mn,$s); + last PARSE; + + } elsif (/^$next\s*$week$/i) { + # next friday + ($dofw)=($1); + &Date_Init() if (! $Cnf{"UpdateCurrTZ"}); + $date=&Date_GetNext($Curr{"Now"},$dofw,0,$h,$mn,$s); + last PARSE; + + } elsif (/^$prev\s*$week$/i) { + # last friday + ($dofw)=($1); + &Date_Init() if (! $Cnf{"UpdateCurrTZ"}); + $date=&Date_GetPrev($Curr{"Now"},$dofw,0,$h,$mn,$s); + last PARSE; + + } elsif (/^$next$wkabb$/i) { + # next week + &Date_Init() if (! $Cnf{"UpdateCurrTZ"}); + $date=&DateCalc_DateDelta($Curr{"Now"},"+0:0:1:0:0:0:0",\$err,0); + $date=&Date_SetTime($date,$h,$mn,$s) if (defined $h); + last PARSE; + } elsif (/^$prev$wkabb$/i) { + # last week + &Date_Init() if (! $Cnf{"UpdateCurrTZ"}); + $date=&DateCalc_DateDelta($Curr{"Now"},"-0:0:1:0:0:0:0",\$err,0); + $date=&Date_SetTime($date,$h,$mn,$s) if (defined $h); + last PARSE; + + } elsif (/^$next$mabb$/i) { + # next month + &Date_Init() if (! $Cnf{"UpdateCurrTZ"}); + $date=&DateCalc_DateDelta($Curr{"Now"},"+0:1:0:0:0:0:0",\$err,0); + $date=&Date_SetTime($date,$h,$mn,$s) if (defined $h); + last PARSE; + } elsif (/^$prev$mabb$/i) { + # last month + &Date_Init() if (! $Cnf{"UpdateCurrTZ"}); + $date=&DateCalc_DateDelta($Curr{"Now"},"-0:1:0:0:0:0:0",\$err,0); + $date=&Date_SetTime($date,$h,$mn,$s) if (defined $h); + last PARSE; + + } elsif (/^$future\s*(\d+)$day$/i || + /^(\d+)$day$later$/i) { + # in 2 days + # 2 days later + ($num)=($1); + &Date_Init() if (! $Cnf{"UpdateCurrTZ"}); + $date=&DateCalc_DateDelta($Curr{"Now"},"+0:0:0:$num:0:0:0", + \$err,0); + $date=&Date_SetTime($date,$h,$mn,$s) if (defined $h); + last PARSE; + } elsif (/^(\d+)$day$past$/i) { + # 2 days ago + ($num)=($1); + &Date_Init() if (! $Cnf{"UpdateCurrTZ"}); + $date=&DateCalc_DateDelta($Curr{"Now"},"-0:0:0:$num:0:0:0", + \$err,0); + $date=&Date_SetTime($date,$h,$mn,$s) if (defined $h); + last PARSE; + + } elsif (/^$future\s*(\d+)$wkabb$/i || + /^(\d+)$wkabb$later$/i) { + # in 2 weeks + # 2 weeks later + ($num)=($1); + &Date_Init() if (! $Cnf{"UpdateCurrTZ"}); + $date=&DateCalc_DateDelta($Curr{"Now"},"+0:0:$num:0:0:0:0", + \$err,0); + $date=&Date_SetTime($date,$h,$mn,$s) if (defined $h); + last PARSE; + } elsif (/^(\d+)$wkabb$past$/i) { + # 2 weeks ago + ($num)=($1); + &Date_Init() if (! $Cnf{"UpdateCurrTZ"}); + $date=&DateCalc_DateDelta($Curr{"Now"},"-0:0:$num:0:0:0:0", + \$err,0); + $date=&Date_SetTime($date,$h,$mn,$s) if (defined $h); + last PARSE; + + } elsif (/^$future\s*(\d+)$mabb$/i || + /^(\d+)$mabb$later$/i) { + # in 2 months + # 2 months later + ($num)=($1); + &Date_Init() if (! $Cnf{"UpdateCurrTZ"}); + $date=&DateCalc_DateDelta($Curr{"Now"},"+0:$num:0:0:0:0:0", + \$err,0); + $date=&Date_SetTime($date,$h,$mn,$s) if (defined $h); + last PARSE; + } elsif (/^(\d+)$mabb$past$/i) { + # 2 months ago + ($num)=($1); + &Date_Init() if (! $Cnf{"UpdateCurrTZ"}); + $date=&DateCalc_DateDelta($Curr{"Now"},"-0:$num:0:0:0:0:0", + \$err,0); + $date=&Date_SetTime($date,$h,$mn,$s) if (defined $h); + last PARSE; + + } elsif (/^$week$future\s*(\d+)$wkabb$/i || + /^$week\s*(\d+)$wkabb$later$/i) { + # friday in 2 weeks + # friday 2 weeks later + ($dofw,$num)=($1,$2); + $tmp="+"; + } elsif (/^$week\s*(\d+)$wkabb$past$/i) { + # friday 2 weeks ago + ($dofw,$num)=($1,$2); + $tmp="-"; + } elsif (/^$future\s*(\d+)$wkabb$on$week$/i || + /^(\d+)$wkabb$later$on$week$/i) { + # in 2 weeks on friday + # 2 weeks later on friday + ($num,$dofw)=($1,$2); + $tmp="+" + } elsif (/^(\d+)$wkabb$past$on$week$/i) { + # 2 weeks ago on friday + ($num,$dofw)=($1,$2); + $tmp="-"; + } elsif (/^$week\s*$wkabb$/i) { + # monday week (British date: in 1 week on monday) + $dofw=$1; + $num=1; + $tmp="+"; + } elsif (/^$now\s*$wkabb$/i) { + # today week (British date: 1 week from today) + &Date_Init() if (! $Cnf{"UpdateCurrTZ"}); + $date=&DateCalc_DateDelta($Curr{"Now"},"+0:0:1:0:0:0:0",\$err,0); + $date=&Date_SetTime($date,$h,$mn,$s) if (defined $h); + last PARSE; + } elsif (/^$offset\s*$wkabb$/i) { + # tomorrow week (British date: 1 week from tomorrow) + ($offset)=($1); + &Date_Init() if (! $Cnf{"UpdateCurrTZ"}); + $offset=$Lang{$L}{"OffsetH"}{lc($offset)}; + $date=&DateCalc_DateDelta($Curr{"Now"},$offset,\$err,0); + $date=&DateCalc_DateDelta($date,"+0:0:1:0:0:0:0",\$err,0); + if ($time) { + return "" + if (&Date_DateCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk)); + $date=&Date_SetTime($date,$h,$mn,$s); + } + last PARSE; + } + + if ($tmp) { + &Date_Init() if (! $Cnf{"UpdateCurrTZ"}); + $date=&DateCalc_DateDelta($Curr{"Now"}, + $tmp . "0:0:$num:0:0:0:0",\$err,0); + $date=&Date_GetPrev($date,$Cnf{"FirstDay"},1); + $date=&Date_GetNext($date,$dofw,1,$h,$mn,$s); + last PARSE; + } + } + + # Change (2nd, second) to 2 + $tmp=0; + if (/(^|[^a-z0-9])$dom($|[^a-z0-9])/i) { + if (/^\s*$dom\s*$/) { + ($d)=($1); + $d=$dom{lc($d)}; + $m=$Curr{"M"}; + last PARSE; + } + my $from = $2; + my $to = $dom{ lc($from) }; + s/(^|[^a-z])$from($|[^a-z])/$1 $to $2/i; + s/^\s+//; + s/\s+$//; + } + + # Another set of special dates (Nth week) + if (/^$D\s*$week(?:$of?\s*$YY)?$/i) { + # 22nd sunday in 1996 + ($which,$dofw,$y)=($1,$2,$3); + $y=$Curr{"Y"} if (! $y); + $y--; # previous year + $tmp=&Date_GetNext("$y-12-31",$dofw,0); + if ($which>1) { + $tmp=&DateCalc_DateDelta($tmp,"+0:0:".($which-1).":0:0:0:0",\$err,0); + } + ($y,$m,$d)=(&Date_Split($tmp, 1))[0..2]; + last PARSE; + } elsif (/^$week$wkabb\s*$D(?:$of?\s*$YY)?$/i || + /^$week\s*$D$wkabb(?:$of?\s*$YY)?$/i) { + # sunday week 22 in 1996 + # sunday 22nd week in 1996 + ($dofw,$which,$y)=($1,$2,$3); + ($y,$m,$d)=&Date_NthWeekOfYear($y,$which,$dofw); + last PARSE; + } + + # Get rid of day of week + if (/(^|[^a-z])$week($|[^a-z])/i) { + $wk=$2; + (s/(^|[^a-z])$week,/$1 /i) || + s/(^|[^a-z])$week($|[^a-z])/$1 $3/i; + s/^\s+//; + s/\s+$//; + } + + { + # So that we can handle negative epoch times, let's convert + # things like "epoch -" to "epochNEGATIVE " before we strip out + # the $sep chars, which include '-'. + s,epoch\s*-,epochNEGATIVE ,g; + + # Non-ISO8601 dates + s,\s*$sep\s*, ,g; # change all non-ISO8601 seps to spaces + s,^\s*,,; # remove leading/trailing space + s,\s*$,,; + + if (/^$D\s+$D(?:\s+$YY)?$/) { + # MM DD YY (DD MM YY non-US) + ($m,$d,$y)=($1,$2,$3); + ($m,$d)=($d,$m) if ($type ne "US"); + last PARSE; + + } elsif (/^$D4\s*$D\s*$D$/) { + # YYYY MM DD + ($y,$m,$d)=($1,$2,$3); + last PARSE; + + } elsif (s/(^|[^a-z])$month($|[^a-z])/$1 $3/i) { + ($m)=($2); + + if (/^\s*$D(?:\s+$YY)?\s*$/) { + # mmm DD YY + # DD mmm YY + # DD YY mmm + ($d,$y)=($1,$2); + last PARSE; + + } elsif (/^\s*$D$D4\s*$/) { + # mmm DD YYYY + # DD mmm YYYY + # DD YYYY mmm + ($d,$y)=($1,$2); + last PARSE; + + } elsif (/^\s*$D4\s*$D\s*$/) { + # mmm YYYY DD + # YYYY mmm DD + # YYYY DD mmm + ($y,$d)=($1,$2); + last PARSE; + + } elsif (/^\s*$D4\s*$/) { + # mmm YYYY + # YYYY mmm + ($y,$d)=($1,1); + last PARSE; + + } else { + return ""; + } + + } elsif (/^epochNEGATIVE (\d+)$/) { + $s=$1; + $date=&DateCalc("1970-01-01 00:00 GMT","-0:0:$s"); + } elsif (/^epoch\s*(\d+)$/i) { + $s=$1; + $date=&DateCalc("1970-01-01 00:00 GMT","+0:0:$s"); + + } elsif (/^$now$/i) { + # now, today + &Date_Init() if (! $Cnf{"UpdateCurrTZ"}); + $date=$Curr{"Now"}; + if ($time) { + return "" + if (&Date_DateCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk)); + $date=&Date_SetTime($date,$h,$mn,$s); + } + last PARSE; + + } elsif (/^$offset$/i) { + # yesterday, tomorrow + ($offset)=($1); + &Date_Init() if (! $Cnf{"UpdateCurrTZ"}); + $offset=$Lang{$L}{"OffsetH"}{lc($offset)}; + $date=&DateCalc_DateDelta($Curr{"Now"},$offset,\$err,0); + if ($time) { + return "" + if (&Date_DateCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk)); + $date=&Date_SetTime($date,$h,$mn,$s); + } + last PARSE; + + } else { + return ""; + } + } + } + + if (! $date) { + return "" if (&Date_DateCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk)); + $date=&Date_Join($y,$m,$d,$h,$mn,$s); + } + $date=&Date_ConvTZ($date,$z); + if ($midnight) { + $date=&DateCalc_DateDelta($date,"+0:0:0:1:0:0:0"); + } + return $date; +} + +sub ParseDate { + print "DEBUG: ParseDate\n" if ($Curr{"Debug"} =~ /trace/); + &Date_Init() if (! $Curr{"InitDone"}); + my($args,@args,@a,$ref,$date)=(); + @a=@_; + + # @a : is the list of args to ParseDate. Currently, only one argument + # is allowed and it must be a scalar (or a reference to a scalar) + # or a reference to an array. + + if ($#a!=0) { + print "ERROR: Invalid number of arguments to ParseDate.\n"; + return ""; + } + $args=$a[0]; + $ref=ref $args; + if (! $ref) { + return $args if (&Date_Split($args)); + @args=($args); + } elsif ($ref eq "ARRAY") { + @args=@$args; + } elsif ($ref eq "SCALAR") { + return $$args if (&Date_Split($$args)); + @args=($$args); + } else { + print "ERROR: Invalid arguments to ParseDate.\n"; + return ""; + } + @a=@args; + + # @args : a list containing all the arguments (dereferenced if appropriate) + # @a : a list containing all the arguments currently being examined + # $ref : nil, "SCALAR", or "ARRAY" depending on whether a scalar, a + # reference to a scalar, or a reference to an array was passed in + # $args : the scalar or refererence passed in + + PARSE: while($#a>=0) { + $date=join(" ",@a); + $date=&ParseDateString($date); + last if ($date); + pop(@a); + } # PARSE + + splice(@args,0,$#a + 1); + @$args= @args if (defined $ref and $ref eq "ARRAY"); + $date; +} + +sub Date_Cmp { + my($D1,$D2)=@_; + my($date1)=&ParseDateString($D1); + my($date2)=&ParseDateString($D2); + return $date1 cmp $date2; +} + +# **NOTE** +# The calc routines all call parse routines, so it is never necessary to +# call Date_Init in the calc routines. +sub DateCalc { + print "DEBUG: DateCalc\n" if ($Curr{"Debug"} =~ /trace/); + my($D1,$D2,@arg)=@_; + my($ref,$err,$errref,$mode)=(); + + $errref=shift(@arg); + $ref=0; + if (defined $errref) { + if (ref $errref) { + $mode=shift(@arg); + $ref=1; + } else { + $mode=$errref; + $errref=""; + } + } + + my(@date,@delta,$ret,$tmp,$old)=(); + + if (defined $mode and $mode>=0 and $mode<=3) { + $Curr{"Mode"}=$mode; + } else { + $Curr{"Mode"}=0; + } + + $old=$Curr{"InCalc"}; + $Curr{"InCalc"}=1; + + if ($tmp=&ParseDateString($D1)) { + # If we've already parsed the date, we don't want to do it a second + # time (so we don't convert timezones twice). + if (&Date_Split($D1)) { + push(@date,$D1); + } else { + push(@date,$tmp); + } + } elsif ($tmp=&ParseDateDelta($D1)) { + push(@delta,$tmp); + } else { + $$errref=1 if ($ref); + return; + } + + if ($tmp=&ParseDateString($D2)) { + if (&Date_Split($D2)) { + push(@date,$D2); + } else { + push(@date,$tmp); + } + } elsif ($tmp=&ParseDateDelta($D2)) { + push(@delta,$tmp); + } else { + $$errref=2 if ($ref); + return; + } + $mode=$Curr{"Mode"}; + $Curr{"InCalc"}=$old; + + if ($#date==1) { + $ret=&DateCalc_DateDate(@date,$mode); + } elsif ($#date==0) { + $ret=&DateCalc_DateDelta(@date,@delta,\$err,$mode); + $$errref=$err if ($ref); + } else { + $ret=&DateCalc_DeltaDelta(@delta,$mode); + } + $ret; +} + +sub ParseDateDelta { + print "DEBUG: ParseDateDelta\n" if ($Curr{"Debug"} =~ /trace/); + my($args,@args,@a,$ref)=(); + local($_)=(); + @a=@_; + + # @a : is the list of args to ParseDateDelta. Currently, only one argument + # is allowed and it must be a scalar (or a reference to a scalar) + # or a reference to an array. + + if ($#a!=0) { + print "ERROR: Invalid number of arguments to ParseDateDelta.\n"; + return ""; + } + $args=$a[0]; + $ref=ref $args; + if (! $ref) { + @args=($args); + } elsif ($ref eq "ARRAY") { + @args=@$args; + } elsif ($ref eq "SCALAR") { + @args=($$args); + } else { + print "ERROR: Invalid arguments to ParseDateDelta.\n"; + return ""; + } + @a=@args; + + # @args : a list containing all the arguments (dereferenced if appropriate) + # @a : a list containing all the arguments currently being examined + # $ref : nil, "SCALAR", or "ARRAY" depending on whether a scalar, a + # reference to a scalar, or a reference to an array was passed in + # $args : the scalar or refererence passed in + + my(@colon,@delta,$delta,$dir,$colon,$sign,$val)=(); + my($len,$tmp,$tmp2,$tmpl)=(); + my($from,$to)=(); + my($workweek)=$Cnf{"WorkWeekEnd"}-$Cnf{"WorkWeekBeg"}+1; + + &Date_Init() if (! $Curr{"InitDone"}); + # A sign can be a sequence of zero or more + and - signs, this + # allows for deltas like '+ -2 days'. + my($signexp)='((?:[+-]\s*)*)'; + my($numexp)='(\d+)'; + my($exp1)="(?: \\s* $signexp \\s* $numexp \\s*)"; + my($yexp,$mexp,$wexp,$dexp,$hexp,$mnexp,$sexp,$i)=(); + $yexp=$mexp=$wexp=$dexp=$hexp=$mnexp=$sexp="()()"; + $yexp ="(?: $exp1 ". $Lang{$Cnf{"Language"}}{"Yabb"} .")?"; + $mexp ="(?: $exp1 ". $Lang{$Cnf{"Language"}}{"Mabb"} .")?"; + $wexp ="(?: $exp1 ". $Lang{$Cnf{"Language"}}{"Wabb"} .")?"; + $dexp ="(?: $exp1 ". $Lang{$Cnf{"Language"}}{"Dabb"} .")?"; + $hexp ="(?: $exp1 ". $Lang{$Cnf{"Language"}}{"Habb"} .")?"; + $mnexp="(?: $exp1 ". $Lang{$Cnf{"Language"}}{"MNabb"}.")?"; + $sexp ="(?: $exp1 ". $Lang{$Cnf{"Language"}}{"Sabb"} ."?)?"; + my($future)=$Lang{$Cnf{"Language"}}{"Future"}; + my($later)=$Lang{$Cnf{"Language"}}{"Later"}; + my($past)=$Lang{$Cnf{"Language"}}{"Past"}; + + $delta=""; + PARSE: while (@a) { + $_ = join(" ", grep {defined;} @a); + s/\s+$//; + last if ($_ eq ""); + + # Mode is set in DateCalc. ParseDateDelta only overrides it if the + # string contains a mode. + if ($Lang{$Cnf{"Language"}}{"Exact"} && + s/$Lang{$Cnf{"Language"}}{"Exact"}//) { + $Curr{"Mode"}=0; + } elsif ($Lang{$Cnf{"Language"}}{"Approx"} && + s/$Lang{$Cnf{"Language"}}{"Approx"}//) { + $Curr{"Mode"}=1; + } elsif ($Lang{$Cnf{"Language"}}{"Business"} && + s/$Lang{$Cnf{"Language"}}{"Business"}//) { + $Curr{"Mode"}=2; + } elsif (! exists $Curr{"Mode"}) { + $Curr{"Mode"}=0; + } + $workweek=7 if ($Curr{"Mode"} != 2); + + foreach $from (keys %{ $Lang{$Cnf{"Language"}}{"Repl"} }) { + $to=$Lang{$Cnf{"Language"}}{"Repl"}{$from}; + s/(^|[^a-z])$from($|[^a-z])/$1$to$2/i; + } + + # in or ago + # + # We need to make sure that $later, $future, and $past don't contain each + # other... Romanian pointed this out where $past is "in urma" and $future + # is "in". When they do, we have to take this into account. + # $len length of best match (greatest wins) + # $tmp string after best match + # $dir direction (prior, after) of best match + # + # $tmp2 string before/after current match + # $tmpl length of current match + + $len=0; + $tmp=$_; + $dir=1; + + $tmp2=$_; + if ($tmp2 =~ s/(^|[^a-z])($future)($|[^a-z])/$1 $3/i) { + $tmpl=length($2); + if ($tmpl>$len) { + $tmp=$tmp2; + $dir=1; + $len=$tmpl; + } + } + + $tmp2=$_; + if ($tmp2 =~ s/(^|[^a-z])($later)($|[^a-z])/$1 $3/i) { + $tmpl=length($2); + if ($tmpl>$len) { + $tmp=$tmp2; + $dir=1; + $len=$tmpl; + } + } + + $tmp2=$_; + if ($tmp2 =~ s/(^|[^a-z])($past)($|[^a-z])/$1 $3/i) { + $tmpl=length($2); + if ($tmpl>$len) { + $tmp=$tmp2; + $dir=-1; + $len=$tmpl; + } + } + + $_ = $tmp; + s/\s*$//; + + # the colon part of the delta + $colon=""; + if (s/($signexp?$numexp?(:($signexp?$numexp)?){1,6})$//) { + $colon=$1; + s/\s+$//; + } + @colon=split(/:/,$colon); + + # the non-colon part of the delta + $sign="+"; + @delta=(); + $i=6; + foreach $exp1 ($yexp,$mexp,$wexp,$dexp,$hexp,$mnexp,$sexp) { + last if ($#colon>=$i--); + $val=0; + if (s/^$exp1//ix) { + $val=$2 if ($2); + $sign=$1 if ($1); + } + + # Collapse a sign like '+ -' into a single character like '-', + # by counting the occurrences of '-'. + # + $sign =~ s/\s+//g; + $sign =~ tr/+//d; + my $count = ($sign =~ tr/-//d); + die "bad characters in sign: $sign" if length $sign; + $sign = $count % 2 ? '-' : '+'; + + push(@delta,"$sign$val"); + } + if (! /^\s*$/) { + pop(@a); + next PARSE; + } + + # make sure that the colon part has a sign + for ($i=0; $i<=$#colon; $i++) { + $val=0; + if ($colon[$i] =~ /^$signexp$numexp?/) { + $val=$2 if ($2); + $sign=$1 if ($1); + } + $colon[$i] = "$sign$val"; + } + + # combine the two + push(@delta,@colon); + if ($dir<0) { + for ($i=0; $i<=$#delta; $i++) { + $delta[$i] =~ tr/-+/+-/; + } + } + + # form the delta and shift off the valid part + $delta=join(":",@delta); + splice(@args,0,$#a+1); + @$args=@args if (defined $ref and $ref eq "ARRAY"); + last PARSE; + } + + $delta=&Delta_Normalize($delta,$Curr{"Mode"}); + return $delta; +} + +sub UnixDate { + print "DEBUG: UnixDate\n" if ($Curr{"Debug"} =~ /trace/); + my($date,@format)=@_; + local($_)=(); + my($format,%f,$out,@out,$c,$date1,$date2,$tmp)=(); + my($scalar)=(); + $date=&ParseDateString($date); + return if (! $date); + + my($y,$m,$d,$h,$mn,$s)=($f{"Y"},$f{"m"},$f{"d"},$f{"H"},$f{"M"},$f{"S"})= + &Date_Split($date, 1); + $f{"y"}=substr $f{"Y"},2; + &Date_Init() if (! $Curr{"InitDone"}); + + if (! wantarray) { + $format=join(" ",@format); + @format=($format); + $scalar=1; + } + + # month, week + $_=$m; + s/^0//; + $f{"b"}=$f{"h"}=$Lang{$Cnf{"Language"}}{"MonL"}[$_-1]; + $f{"B"}=$Lang{$Cnf{"Language"}}{"MonthL"}[$_-1]; + $_=$m; + s/^0/ /; + $f{"f"}=$_; + $f{"U"}=&Date_WeekOfYear($m,$d,$y,7); + $f{"W"}=&Date_WeekOfYear($m,$d,$y,1); + + # check week 52,53 and 0 + $f{"G"}=$f{"L"}=$y; + if ($f{"W"}>=52 || $f{"U"}>=52) { + my($dd,$mm,$yy)=($d,$m,$y); + $dd+=7; + if ($dd>31) { + $dd-=31; + $mm=1; + $yy++; + if (&Date_WeekOfYear($mm,$dd,$yy,1)==2) { + $f{"G"}=$yy; + $f{"W"}=1; + } + if (&Date_WeekOfYear($mm,$dd,$yy,7)==2) { + $f{"L"}=$yy; + $f{"U"}=1; + } + } + } + if ($f{"W"}==0) { + my($dd,$mm,$yy)=($d,$m,$y); + $dd-=7; + $dd+=31 if ($dd<1); + $yy--; + $mm=12; + $f{"G"}=$yy; + $f{"W"}=&Date_WeekOfYear($mm,$dd,$yy,1)+1; + } + if ($f{"U"}==0) { + my($dd,$mm,$yy)=($d,$m,$y); + $dd-=7; + $dd+=31 if ($dd<1); + $yy--; + $mm=12; + $f{"L"}=$yy; + $f{"U"}=&Date_WeekOfYear($mm,$dd,$yy,7)+1; + } + + $f{"U"}="0".$f{"U"} if (length $f{"U"} < 2); + $f{"W"}="0".$f{"W"} if (length $f{"W"} < 2); + + # day + $f{"j"}=&Date_DayOfYear($m,$d,$y); + $f{"j"} = "0" . $f{"j"} while (length($f{"j"})<3); + $_=$d; + s/^0/ /; + $f{"e"}=$_; + $f{"w"}=&Date_DayOfWeek($m,$d,$y); + $f{"v"}=$Lang{$Cnf{"Language"}}{"WL"}[$f{"w"}-1]; + $f{"v"}=" ".$f{"v"} if (length $f{"v"} < 2); + $f{"a"}=$Lang{$Cnf{"Language"}}{"WkL"}[$f{"w"}-1]; + $f{"A"}=$Lang{$Cnf{"Language"}}{"WeekL"}[$f{"w"}-1]; + $f{"E"}=&Date_DaySuffix($f{"e"}); + + # hour + $_=$h; + s/^0/ /; + $f{"k"}=$_; + $f{"i"}=$f{"k"}+1; + $f{"i"}=$f{"k"}; + $f{"i"}=12 if ($f{"k"}==0); + $f{"i"}=$f{"k"}-12 if ($f{"k"}>12); + $f{"i"}=$f{"i"}-12 if ($f{"i"}>12); + $f{"i"}=" ".$f{"i"} if (length($f{"i"})<2); + $f{"I"}=$f{"i"}; + $f{"I"}=~ s/^ /0/; + $f{"p"}=$Lang{$Cnf{"Language"}}{"AMstr"}; + $f{"p"}=$Lang{$Cnf{"Language"}}{"PMstr"} if ($f{"k"}>11); + + # minute, second, timezone + $f{"o"}=&Date_SecsSince1970($m,$d,$y,$h,$mn,$s); + $f{"s"}=&Date_SecsSince1970GMT($m,$d,$y,$h,$mn,$s); + $f{"Z"}=($Cnf{"ConvTZ"} eq "IGNORE" or $Cnf{"ConvTZ"} eq "") ? + $Cnf{"TZ"} : $Cnf{"ConvTZ"}; + $f{"z"}=($f{"Z"}=~/^[+-]\d{4}/) ? $f{"Z"} : ($Zone{"n2o"}{lc $f{"Z"}} || ""); + + # date, time + $f{"c"}=qq|$f{"a"} $f{"b"} $f{"e"} $h:$mn:$s $y|; + $f{"C"}=$f{"u"}= + qq|$f{"a"} $f{"b"} $f{"e"} $h:$mn:$s $f{"z"} $y|; + $f{"g"}=qq|$f{"a"}, $d $f{"b"} $y $h:$mn:$s $f{"z"}|; + $f{"D"}=$f{"x"}=qq|$m/$d/$f{"y"}|; + $f{"r"}=qq|$f{"I"}:$mn:$s $f{"p"}|; + $f{"R"}=qq|$h:$mn|; + $f{"T"}=$f{"X"}=qq|$h:$mn:$s|; + $f{"V"}=qq|$m$d$h$mn$f{"y"}|; + $f{"Q"}="$y$m$d"; + $f{"q"}=qq|$y$m$d$h$mn$s|; + $f{"P"}=qq|$y$m$d$h:$mn:$s|; + $f{"F"}=qq|$f{"A"}, $f{"B"} $f{"e"}, $f{"Y"}|; + if ($f{"W"}==0) { + $y--; + $tmp=&Date_WeekOfYear(12,31,$y,1); + $tmp="0$tmp" if (length($tmp) < 2); + $f{"J"}=qq|$y-W$tmp-$f{"w"}|; + } else { + $f{"J"}=qq|$f{"G"}-W$f{"W"}-$f{"w"}|; + } + $f{"K"}=qq|$y-$f{"j"}|; + # %l is a special case. Since it requires the use of the calculator + # which requires this routine, an infinite recursion results. To get + # around this, %l is NOT determined every time this is called so the + # recursion breaks. + + # other formats + $f{"n"}="\n"; + $f{"t"}="\t"; + $f{"%"}="%"; + $f{"+"}="+"; + + foreach $format (@format) { + $format=reverse($format); + $out=""; + while ($format ne "") { + $c=chop($format); + if ($c eq "%") { + $c=chop($format); + if ($c eq "l") { + &Date_Init(); + $date1=&DateCalc_DateDelta($Curr{"Now"},"-0:6:0:0:0:0:0"); + $date2=&DateCalc_DateDelta($Curr{"Now"},"+0:6:0:0:0:0:0"); + if (&Date_Cmp($date,$date1)>=0 && &Date_Cmp($date,$date2)<=0) { + $f{"l"}=qq|$f{"b"} $f{"e"} $h:$mn|; + } else { + $f{"l"}=qq|$f{"b"} $f{"e"} $f{"Y"}|; + } + $out .= $f{"$c"}; + } elsif (exists $f{"$c"}) { + $out .= $f{"$c"}; + } else { + $out .= $c; + } + } else { + $out .= $c; + } + } + push(@out,$out); + } + if ($scalar) { + return $out[0]; + } else { + return (@out); + } +} + +# Can't be in "use integer" because we're doing decimal arithmatic +no integer; +sub Delta_Format { + print "DEBUG: Delta_Format\n" if ($Curr{"Debug"} =~ /trace/); + my($delta,$dec,@format)=@_; + $delta=&ParseDateDelta($delta); + return "" if (! $delta); + my(@out,%f,$out,$c1,$c2,$scalar,$format)=(); + local($_)=$delta; + my($y,$M,$w,$d,$h,$m,$s)=&Delta_Split($delta); + # Get rid of positive signs. + ($y,$M,$w,$d,$h,$m,$s)=map { 1*$_; }($y,$M,$w,$d,$h,$m,$s); + + if (defined $dec && $dec>0) { + $dec="%." . ($dec*1) . "f"; + } else { + $dec="%f"; + } + + if (! wantarray) { + $format=join(" ",@format); + @format=($format); + $scalar=1; + } + + # Length of each unit in seconds + my($sl,$ml,$hl,$dl,$wl,$yl)=(); + $sl = 1; + $ml = $sl*60; + $hl = $ml*60; + $dl = $hl*24; + $wl = $dl*7; + $yl = $dl*365.25; + + # The decimal amount of each unit contained in all smaller units + my($yd,$Md,$sd,$md,$hd,$dd,$wd)=(); + if ($M) { + $yd = $M/12; + $Md = 0; + } else { + $yd = ($w*$wl + $d*$dl + $h*$hl + $m*$ml + $s*$sl)/$yl; + $Md = 0; + } + + $wd = ($d*$dl + $h*$hl + $m*$ml + $s*$sl)/$wl; + $dd = ($h*$hl + $m*$ml + $s*$sl)/$dl; + $hd = ($m*$ml + $s*$sl)/$hl; + $md = ($s*$sl)/$ml; + $sd = 0; + + # The amount of each unit contained in higher units. + my($yh,$Mh,$sh,$mh,$hh,$dh,$wh)=(); + $yh = 0; + + if ($M) { + $Mh = ($yh+$y)*12; + $wh = 0; + $dh = ($wh+$w)*7; + } else { + $Mh = 0; + $wh = ($yh+$y)*365.25/7; + $dh = ($yh+$y)*365.25 + $w*7; + } + + $hh = ($dh+$d)*24; + $mh = ($hh+$h)*60; + $sh = ($mh+$m)*60; + + # Set up the formats + + $f{"yv"} = $y; + $f{"Mv"} = $M; + $f{"wv"} = $w; + $f{"dv"} = $d; + $f{"hv"} = $h; + $f{"mv"} = $m; + $f{"sv"} = $s; + + $f{"yh"} = $y+$yh; + $f{"Mh"} = $M+$Mh; + $f{"wh"} = $w+$wh; + $f{"dh"} = $d+$dh; + $f{"hh"} = $h+$hh; + $f{"mh"} = $m+$mh; + $f{"sh"} = $s+$sh; + + $f{"yd"} = sprintf($dec,$y+$yd); + $f{"Md"} = sprintf($dec,$M+$Md); + $f{"wd"} = sprintf($dec,$w+$wd); + $f{"dd"} = sprintf($dec,$d+$dd); + $f{"hd"} = sprintf($dec,$h+$hd); + $f{"md"} = sprintf($dec,$m+$md); + $f{"sd"} = sprintf($dec,$s+$sd); + + $f{"yt"} = sprintf($dec,$yh+$y+$yd); + $f{"Mt"} = sprintf($dec,$Mh+$M+$Md); + $f{"wt"} = sprintf($dec,$wh+$w+$wd); + $f{"dt"} = sprintf($dec,$dh+$d+$dd); + $f{"ht"} = sprintf($dec,$hh+$h+$hd); + $f{"mt"} = sprintf($dec,$mh+$m+$md); + $f{"st"} = sprintf($dec,$sh+$s+$sd); + + $f{"%"} = "%"; + + foreach $format (@format) { + $format=reverse($format); + $out=""; + PARSE: while ($format) { + $c1=chop($format); + if ($c1 eq "%") { + $c1=chop($format); + if (exists($f{$c1})) { + $out .= $f{$c1}; + next PARSE; + } + $c2=chop($format); + if (exists($f{"$c1$c2"})) { + $out .= $f{"$c1$c2"}; + next PARSE; + } + $out .= $c1; + $format .= $c2; + } else { + $out .= $c1; + } + } + push(@out,$out); + } + if ($scalar) { + return $out[0]; + } else { + return (@out); + } +} +use integer; + +sub ParseRecur { + print "DEBUG: ParseRecur\n" if ($Curr{"Debug"} =~ /trace/); + &Date_Init() if (! $Curr{"InitDone"}); + + my($recur,$dateb,$date0,$date1,$flag)=@_; + local($_)=$recur; + + my($recur_0,$recur_1,@recur0,@recur1)=(); + my(@tmp,$tmp,$each,$num,$y,$m,$d,$w,$h,$mn,$s,$delta,$y0,$y1,$yb)=(); + my($yy,$n,$dd,@d,@tmp2,$date,@date,@w,@tmp3,@m,@y,$tmp2,$d2,@flags)=(); + + # $date0, $date1, $dateb, $flag : passed in (these are always the final say + # in determining whether a date matches a + # recurrence IF they are present. + # $date_b, $date_0, $date_1 : if a value can be determined from the + # $flag_t recurrence, they are stored here. + # + # If values can be determined from the recurrence AND are passed in, the + # following are used: + # max($date0,$date_0) i.e. the later of the two dates + # min($date1,$date_1) i.e. the earlier of the two dates + # + # The base date that is used is the first one defined from + # $dateb $date_b + # The base date is only used if necessary (as determined by the recur). + # For example, "every other friday" requires a base date, but "2nd + # friday of every month" doesn't. + + my($date_b,$date_0,$date_1,$flag_t); + + # + # Check the arguments passed in. + # + + $date0="" if (! defined $date0); + $date1="" if (! defined $date1); + $dateb="" if (! defined $dateb); + $flag ="" if (! defined $flag); + + if ($dateb) { + $dateb=&ParseDateString($dateb); + return "" if (! $dateb); + } + if ($date0) { + $date0=&ParseDateString($date0); + return "" if (! $date0); + } + if ($date1) { + $date1=&ParseDateString($date1); + return "" if (! $date1); + } + + # + # Parse the recur. $date_b, $date_0, and $date_e are values obtained + # from the recur. + # + + @tmp=&Recur_Split($_); + + if (@tmp) { + ($recur_0,$recur_1,$flag_t,$date_b,$date_0,$date_1)=@tmp; + $recur_0 = "" if (! defined $recur_0); + $recur_1 = "" if (! defined $recur_1); + $flag_t = "" if (! defined $flag_t); + $date_b = "" if (! defined $date_b); + $date_0 = "" if (! defined $date_0); + $date_1 = "" if (! defined $date_1); + + @recur0 = split(/:/,$recur_0); + @recur1 = split(/:/,$recur_1); + return "" if ($#recur0 + $#recur1 + 2 != 7); + + if ($date_b) { + $date_b=&ParseDateString($date_b); + return "" if (! $date_b); + } + if ($date_0) { + $date_0=&ParseDateString($date_0); + return "" if (! $date_0); + } + if ($date_1) { + $date_1=&ParseDateString($date_1); + return "" if (! $date_1); + } + + } else { + + my($mmm)='\s*'.$Lang{$Cnf{"Language"}}{"Month"}; # \s*(jan|january|...) + my(%mmm)=%{ $Lang{$Cnf{"Language"}}{"MonthH"} }; # { jan=>1, ... } + my($wkexp)='\s*'.$Lang{$Cnf{"Language"}}{"Week"}; # \s*(mon|monday|...) + my(%week)=%{ $Lang{$Cnf{"Language"}}{"WeekH"} }; # { monday=>1, ... } + my($day)='\s*'.$Lang{$Cnf{"Language"}}{"Dabb"}; # \s*(?:d|day|days) + my($month)='\s*'.$Lang{$Cnf{"Language"}}{"Mabb"}; # \s*(?:mon|month|months) + my($week)='\s*'.$Lang{$Cnf{"Language"}}{"Wabb"}; # \s*(?:w|wk|week|weeks) + my($daysexp)=$Lang{$Cnf{"Language"}}{"DoM"}; # (1st|first|...31st) + my(%dayshash)=%{ $Lang{$Cnf{"Language"}}{"DoMH"} }; + # { 1st=>1,first=>1,...} + my($of)='\s*'.$Lang{$Cnf{"Language"}}{"Of"}; # \s*(?:in|of) + my($lastexp)=$Lang{$Cnf{"Language"}}{"Last"}; # (?:last) + my($each)=$Lang{$Cnf{"Language"}}{"Each"}; # (?:each|every) + + my($D)='\s*(\d+)'; + my($Y)='\s*(\d{4}|\d{2})'; + + # Change 1st to 1 + if (/(^|[^a-z])$daysexp($|[^a-z])/i) { + $tmp=lc($2); + $tmp=$dayshash{"$tmp"}; + s/(^|[^a-z])$daysexp($|[^a-z])/$1 $tmp $3/i; + } + s/\s*$//; + + # Get rid of "each" + if (/(^|[^a-z])$each($|[^a-z])/i) { + s/(^|[^a-z])$each($|[^a-z])/$1 $2/i; + $each=1; + } else { + $each=0; + } + + if ($each) { + + if (/^$D?$day(?:$of$mmm?$Y)?$/i || + /^$D?$day(?:$of$mmm())?$/i) { + # every [2nd] day in [june] 1997 + # every [2nd] day [in june] + ($num,$m,$y)=($1,$2,$3); + $num=1 if (! defined $num); + $m="" if (! defined $m); + $y="" if (! defined $y); + + $y=$Curr{"Y"} if (! $y); + if ($m) { + $m=$mmm{lc($m)}; + $date_0=&Date_Join($y,$m,1,0,0,0); + $date_1=&DateCalc_DateDelta($date_0,"+0:1:0:0:0:0:0",0); + } else { + $date_0=&Date_Join($y, 1,1,0,0,0); + $date_1=&Date_Join($y+1,1,1,0,0,0); + } + $date_b=&DateCalc($date_0,"-0:0:0:1:0:0:0",0); + @recur0=(0,0,0,$num,0,0,0); + @recur1=(); + + } elsif (/^$D$day?$of$month(?:$of?$Y)?$/) { + # 2nd [day] of every month [in 1997] + ($num,$y)=($1,$2); + $y=$Curr{"Y"} if (! $y); + + $date_0=&Date_Join($y, 1,1,0,0,0); + $date_1=&Date_Join($y+1,1,1,0,0,0); + $date_b=$date_0; + + @recur0=(0,1,0); + @recur1=($num,0,0,0); + + } elsif (/^$D$wkexp$of$month(?:$of?$Y)?$/ || + /^($lastexp)$wkexp$of$month(?:$of?$Y)?$/) { + # 2nd tuesday of every month [in 1997] + # last tuesday of every month [in 1997] + ($num,$d,$y)=($1,$2,$3); + $y=$Curr{"Y"} if (! $y); + $d=$week{lc($d)}; + $num=-1 if ($num !~ /^$D$/); + + $date_0=&Date_Join($y,1,1,0,0,0); + $date_1=&Date_Join($y+1,1,1,0,0,0); + $date_b=$date_0; + + @recur0=(0,1); + @recur1=($num,$d,0,0,0); + + } elsif (/^$D?$wkexp(?:$of$mmm?$Y)?$/i || + /^$D?$wkexp(?:$of$mmm())?$/i) { + # every tuesday in june 1997 + # every 2nd tuesday in june 1997 + ($num,$d,$m,$y)=($1,$2,$3,$4); + $y=$Curr{"Y"} if (! $y); + $num=1 if (! defined $num); + $m="" if (! defined $m); + $d=$week{lc($d)}; + + if ($m) { + $m=$mmm{lc($m)}; + $date_0=&Date_Join($y,$m,1,0,0,0); + $date_1=&DateCalc_DateDelta($date_0,"+0:1:0:0:0:0:0",0); + } else { + $date_0=&Date_Join($y,1,1,0,0,0); + $date_1=&Date_Join($y+1,1,1,0,0,0); + } + $date_b=&DateCalc($date_0,"-0:0:0:1:0:0:0",0); + + @recur0=(0,0,$num); + @recur1=($d,0,0,0); + + } else { + return ""; + } + + $date_0="" if ($date0); + $date_1="" if ($date1); + } else { + return ""; + } + } + + # + # Override with any values passed in + # + + if ($date0 && $date_0) { + $date0=( &Date_Cmp($date0,$date_0) > 1 ? $date0 : $date_0); + } elsif ($date_0) { + $date0 = $date_0; + } + + if ($date1 && $date_1) { + $date1=( &Date_Cmp($date1,$date_1) > 1 ? $date_1 : $date1); + } elsif ($date_1) { + $date1 = $date_1; + } + + $dateb=$date_b if (! $dateb); + + if ($flag =~ s/^\+//) { + if ($flag_t) { + $flag="$flag_t,$flag"; + } + } + $flag =$flag_t if (! $flag && $flag_t); + + if (! wantarray) { + $tmp = join(":",@recur0); + $tmp .= "*" . join(":",@recur1) if (@recur1); + $tmp .= "*$flag*$dateb*$date0*$date1"; + return $tmp; + } + if (@recur0) { + return () if (! $date0 || ! $date1); # dateb is NOT required in all case + } + + # + # Some flags affect parsing. + # + + @flags = split(/,/,$flag); + my($MDn) = 0; + my($MWn) = 7; + my($f); + foreach $f (@flags) { + if ($f =~ /^MW([1-7])$/i) { + $MWn=$1; + $MDn=0; + + } elsif ($f =~ /^MD([1-7])$/i) { + $MDn=$1; + $MWn=0; + + } elsif ($f =~ /^EASTER$/i) { + ($y,$m,$w,$d,$h,$mn,$s)=(@recur0,@recur1); + # We want something that will return Jan 1 for the given years. + if ($#recur0==-1) { + @recur1=($y,1,0,1,$h,$mn,$s); + } elsif ($#recur0<=3) { + @recur0=($y,0,0,0); + @recur1=($h,$mn,$s); + } elsif ($#recur0==4) { + @recur0=($y,0,0,0,0); + @recur1=($mn,$s); + } elsif ($#recur0==5) { + @recur0=($y,0,0,0,0,0); + @recur1=($s); + } else { + @recur0=($y,0,0,0,0,0,0); + } + } + } + + # + # Determine the dates referenced by the recur. Also, fix the base date + # as necessary for the recurrences which require it. + # + + ($y,$m,$w,$d,$h,$mn,$s)=(@recur0,@recur1); + @y=@m=@w=@d=(); + my(@time)=($h,$mn,$s); + + RECUR: while (1) { + + if ($#recur0==-1) { + # * Y-M-W-D-H-MN-S + if ($y eq "0") { + push(@recur0,0); + shift(@recur1); + + } else { + @y=&ReturnList($y); + foreach $y (@y) { + $y=&Date_FixYear($y) if (length($y)==2); + return () if (length($y)!=4 || ! &IsInt($y)); + } + @y=sort { $a<=>$b } @y; + + $date0=&ParseDate("0000-01-01") if (! $date0); + $date1=&ParseDate("9999-12-31 23:59:59") if (! $date1); + + if ($m eq "0" and $w eq "0") { + # * Y-0-0-0-H-MN-S + # * Y-0-0-DOY-H-MN-S + if ($d eq "0") { + @d=(1); + } else { + @d=&ReturnList($d); + return () if (! @d); + foreach $d (@d) { + return () if (! &IsInt($d,1,366)); + } + @d=sort { $a<=>$b } (@d); + } + + @date=(); + foreach $yy (@y) { + foreach $d (@d) { + ($y,$m,$dd)=&Date_NthDayOfYear($yy,$d); + push(@date, &Date_Join($y,$m,$dd,0,0,0)); + } + } + last RECUR; + + } elsif ($w eq "0") { + # * Y-M-0-0-H-MN-S + # * Y-M-0-DOM-H-MN-S + + @m=&ReturnList($m); + return () if (! @m); + foreach $m (@m) { + return () if (! &IsInt($m,1,12)); + } + @m=sort { $a<=>$b } (@m); + + if ($d eq "0") { + @d=(1); + } else { + @d=&ReturnList($d); + return () if (! @d); + foreach $d (@d) { + return () if (! &IsInt($d,1,31)); + } + @d=sort { $a<=>$b } (@d); + } + + @date=(); + foreach $y (@y) { + foreach $m (@m) { + foreach $d (@d) { + $date=&Date_Join($y,$m,$d,0,0,0); + push(@date,$date) if ($d<29 || &Date_Split($date)); + } + } + } + last RECUR; + + } elsif ($m eq "0") { + # * Y-0-WOY-DOW-H-MN-S + # * Y-0-WOY-0-H-MN-S + @w=&ReturnList($w); + return () if (! @w); + foreach $w (@w) { + return () if (! &IsInt($w,1,53)); + } + + if ($d eq "0") { + @d=($Cnf{"FirstDay"}); + } else { + @d=&ReturnList($d); + return () if (! @d); + foreach $d (@d) { + return () if (! &IsInt($d,1,7)); + } + @d=sort { $a<=>$b } (@d); + } + + @date=(); + foreach $y (@y) { + foreach $w (@w) { + $w="0$w" if (length($w)==1); + foreach $d (@d) { + $date=&ParseDateString("$y-W$w-$d"); + push(@date,$date); + } + } + } + last RECUR; + + } else { + # * Y-M-WOM-DOW-H-MN-S + # * Y-M-WOM-0-H-MN-S + + @m=&ReturnList($m); + return () if (! @m); + foreach $m (@m) { + return () if (! &IsInt($m,1,12)); + } + @m=sort { $a<=>$b } (@m); + + @w=&ReturnList($w); + + if ($d eq "0") { + @d=(); + } else { + @d=&ReturnList($d); + } + + @date=&Date_Recur_WoM(\@y,\@m,\@w,\@d,$MWn,$MDn); + last RECUR; + } + } + } + + if ($#recur0==0) { + # Y * M-W-D-H-MN-S + $n=$y; + $n=1 if ($n==0); + + @m=&ReturnList($m); + return () if (! @m); + foreach $m (@m) { + return () if (! &IsInt($m,1,12)); + } + @m=sort { $a<=>$b } (@m); + + if ($m eq "0") { + # Y * 0-W-D-H-MN-S (equiv to Y-0 * W-D-H-MN-S) + push(@recur0,0); + shift(@recur1); + + } elsif ($w eq "0") { + # Y * M-0-DOM-H-MN-S + return () if (! $dateb); + $d=1 if ($d eq "0"); + + @d=&ReturnList($d); + return () if (! @d); + foreach $d (@d) { + return () if (! &IsInt($d,1,31)); + } + @d=sort { $a<=>$b } (@d); + + # We need to find years that are a multiple of $n from $y(base) + ($y0)=( &Date_Split($date0, 1) )[0]; + ($y1)=( &Date_Split($date1, 1) )[0]; + ($yb)=( &Date_Split($dateb, 1) )[0]; + @date=(); + for ($yy=$y0; $yy<=$y1; $yy++) { + if (($yy-$yb)%$n == 0) { + foreach $m (@m) { + foreach $d (@d) { + $date=&Date_Join($yy,$m,$d,0,0,0); + push(@date,$date) if ($d<29 || &Date_Split($date)); + } + } + } + } + last RECUR; + + } else { + # Y * M-WOM-DOW-H-MN-S + # Y * M-WOM-0-H-MN-S + return () if (! $dateb); + @m=&ReturnList($m); + @w=&ReturnList($w); + if ($d eq "0") { + @d=(); + } else { + @d=&ReturnList($d); + } + + ($y0)=( &Date_Split($date0, 1) )[0]; + ($y1)=( &Date_Split($date1, 1) )[0]; + ($yb)=( &Date_Split($dateb, 1) )[0]; + @y=(); + for ($yy=$y0; $yy<=$y1; $yy++) { + if (($yy-$yb)%$n == 0) { + push(@y,$yy); + } + } + + @date=&Date_Recur_WoM(\@y,\@m,\@w,\@d,$MWn,$MDn); + last RECUR; + } + } + + if ($#recur0==1) { + # Y-M * W-D-H-MN-S + + if ($w eq "0") { + # Y-M * 0-D-H-MN-S (equiv to Y-M-0 * D-H-MN-S) + push(@recur0,0); + shift(@recur1); + + } elsif ($m==0) { + # Y-0 * WOY-0-H-MN-S + # Y-0 * WOY-DOW-H-MN-S + return () if (! $dateb); + $n=$y; + $n=1 if ($n==0); + + @w=&ReturnList($w); + return () if (! @w); + foreach $w (@w) { + return () if (! &IsInt($w,1,53)); + } + + if ($d eq "0") { + @d=($Cnf{"FirstDay"}); + } else { + @d=&ReturnList($d); + return () if (! @d); + foreach $d (@d) { + return () if (! &IsInt($d,1,7)); + } + @d=sort { $a<=>$b } (@d); + } + + # We need to find years that are a multiple of $n from $y(base) + ($y0)=( &Date_Split($date0, 1) )[0]; + ($y1)=( &Date_Split($date1, 1) )[0]; + ($yb)=( &Date_Split($dateb, 1) )[0]; + @date=(); + for ($yy=$y0; $yy<=$y1; $yy++) { + if (($yy-$yb)%$n == 0) { + foreach $w (@w) { + $w="0$w" if (length($w)==1); + foreach $tmp (@d) { + $date=&ParseDateString("$yy-W$w-$tmp"); + push(@date,$date); + } + } + } + } + last RECUR; + + } else { + # Y-M * WOM-0-H-MN-S + # Y-M * WOM-DOW-H-MN-S + return () if (! $dateb); + @tmp=(@recur0); + push(@tmp,0) while ($#tmp<6); + $delta=join(":",@tmp); + @tmp=&Date_Recur($date0,$date1,$dateb,$delta); + + @w=&ReturnList($w); + @m=(); + if ($d eq "0") { + @d=(); + } else { + @d=&ReturnList($d); + } + + @date=&Date_Recur_WoM(\@tmp,\@m,\@w,\@d,$MWn,$MDn); + last RECUR; + } + } + + if ($#recur0==2) { + # Y-M-W * D-H-MN-S + + if ($d eq "0") { + # Y-M-W * 0-H-MN-S + return () if (! $dateb); + $y=1 if ($y==0 && $m==0 && $w==0); + $delta="$y:$m:$w:0:0:0:0"; + @date=&Date_Recur($date0,$date1,$dateb,$delta); + last RECUR; + + } elsif ($m==0 && $w==0) { + # Y-0-0 * DOY-H-MN-S + $y=1 if ($y==0); + $n=$y; + return () if (! $dateb && $y!=1); + + @d=&ReturnList($d); + return () if (! @d); + foreach $d (@d) { + return () if (! &IsInt($d,1,366)); + } + @d=sort { $a<=>$b } (@d); + + # We need to find years that are a multiple of $n from $y(base) + ($y0)=( &Date_Split($date0, 1) )[0]; + ($y1)=( &Date_Split($date1, 1) )[0]; + ($yb)=( &Date_Split($dateb, 1) )[0]; + @date=(); + for ($yy=$y0; $yy<=$y1; $yy++) { + if (($yy-$yb)%$n == 0) { + foreach $d (@d) { + ($y,$m,$dd)=&Date_NthDayOfYear($yy,$d); + push(@date, &Date_Join($y,$m,$dd,0,0,0)); + } + } + } + last RECUR; + + } elsif ($w>0) { + # Y-M-W * DOW-H-MN-S + return () if (! $dateb); + @tmp=(@recur0); + push(@tmp,0) while ($#tmp<6); + $delta=join(":",@tmp); + + @d=&ReturnList($d); + return () if (! @d); + foreach $d (@d) { + return () if (! &IsInt($d,1,7)); + } + + # Find out what DofW the basedate is. + @tmp2=&Date_Split($dateb, 1); + $tmp=&Date_DayOfWeek($tmp2[1],$tmp2[2],$tmp2[0]); + + @date=(); + foreach $d (@d) { + $date_b=$dateb; + # Move basedate to DOW + if ($d != $tmp) { + if (($tmp>=$Cnf{"FirstDay"} && $d<$Cnf{"FirstDay"}) || + ($tmp>=$Cnf{"FirstDay"} && $d>$tmp) || + ($tmp<$d && $d<$Cnf{"FirstDay"})) { + $date_b=&Date_GetNext($date_b,$d); + } else { + $date_b=&Date_GetPrev($date_b,$d); + } + } + push(@date,&Date_Recur($date0,$date1,$date_b,$delta)); + } + @date=sort(@date); + last RECUR; + + } elsif ($m>0) { + # Y-M-0 * DOM-H-MN-S + return () if (! $dateb); + @tmp=(@recur0); + push(@tmp,0) while ($#tmp<6); + $delta=join(":",@tmp); + + @d=&ReturnList($d); + return () if (! @d); + foreach $d (@d) { + return () if (! &IsInt($d,-31,31) || $d==0); + } + @d=sort { $a<=>$b } (@d); + + @tmp2=&Date_Recur($date0,$date1,$dateb,$delta); + @date=(); + foreach $date (@tmp2) { + ($y,$m)=( &Date_Split($date, 1) )[0..1]; + $tmp2=&Date_DaysInMonth($m,$y); + foreach $d (@d) { + $d2=$d; + $d2=$tmp2+1+$d if ($d<0); + push(@date,&Date_Join($y,$m,$d2,0,0,0)) if ($d2<=$tmp2); + } + } + @date=sort (@date); + last RECUR; + + } else { + return (); + } + } + + if ($#recur0>2) { + # Y-M-W-D * H-MN-S + # Y-M-W-D-H * MN-S + # Y-M-W-D-H-MN * S + # Y-M-W-D-H-S + return () if (! $dateb); + @tmp=(@recur0); + push(@tmp,0) while ($#tmp<6); + $delta=join(":",@tmp); + return () if ($delta !~ /[1-9]/); # return if "0:0:0:0:0:0:0" + @date=&Date_Recur($date0,$date1,$dateb,$delta); + if (@recur1) { + unshift(@recur1,-1) while ($#recur1<2); + @time=@recur1; + } else { + shift(@date); + pop(@date); + @time=(); + } + } + + last RECUR; + } + @date=&Date_RecurSetTime($date0,$date1,\@date,@time) if (@time); + + # + # We've got a list of dates. Operate on them with the flags. + # + + my($sign,$forw,$today,$df,$db,$work,$i); + if (@flags) { + FLAG: foreach $f (@flags) { + $f = uc($f); + + if ($f =~ /^(P|N)(D|T)([1-7])$/) { + @tmp=($1,$2,$3); + $forw =($tmp[0] eq "P" ? 0 : 1); + $today=($tmp[1] eq "D" ? 0 : 1); + $d=$tmp[2]; + @tmp=(); + foreach $date (@date) { + if ($forw) { + push(@tmp, &Date_GetNext($date,$d,$today)); + } else { + push(@tmp, &Date_GetPrev($date,$d,$today)); + } + } + @date=@tmp; + next FLAG; + } + + # We want to go forward exact amounts of time instead of + # business mode calculations so that we don't change the time + # (which may have been set in the recur). + if ($f =~ /^(F|B)(D|W)(\d+)$/) { + @tmp=($1,$2,$3); + $sign="+"; + $sign="-" if ($tmp[0] eq "B"); + $work=0; + $work=1 if ($tmp[1] eq "W"); + $n=$tmp[2]; + @tmp=(); + foreach $date (@date) { + for ($i=1; $i<=$n; $i++) { + while (1) { + $date=&DateCalc($date,"${sign}0:0:0:1:0:0:0"); + last if (! $work || &Date_IsWorkDay($date,0)); + } + } + push(@tmp,$date); + } + @date=@tmp; + next FLAG; + } + + if ($f =~ /^CW(N|P|D)$/ || $f =~ /^(N|P|D)W(D)$/) { + $tmp=$1; + my $noalt = $2 ? 1 : 0; + if ($tmp eq "N" || ($tmp eq "D" && $Cnf{"TomorrowFirst"})) { + $forw=1; + } else { + $forw=0; + } + + @tmp=(); + DATE: foreach $date (@date) { + $df=$db=$date; + if (&Date_IsWorkDay($date)) { + push(@tmp,$date); + next DATE; + } + while (1) { + if ($forw) { + $d=$df=&DateCalc($df,"+0:0:0:1:0:0:0"); + } else { + $d=$db=&DateCalc($db,"-0:0:0:1:0:0:0"); + } + if (&Date_IsWorkDay($d)) { + push(@tmp,$d); + next DATE; + } + $forw=1-$forw if (! $noalt); + } + } + @date=@tmp; + next FLAG; + } + + if ($f eq "EASTER") { + @tmp=(); + foreach $date (@date) { + ($y,$m,$d,$h,$mn,$s)=&Date_Split($date, 1); + ($m,$d)=&Date_Easter($y); + $date=&Date_Join($y,$m,$d,$h,$mn,$s); + next if (&Date_Cmp($date,$date0)<0 || + &Date_Cmp($date,$date1)>0); + push(@tmp,$date); + } + @date=@tmp; + } + } + @date = sort(@date); + } + @date; +} + +sub Date_GetPrev { + print "DEBUG: Date_GetPrev\n" if ($Curr{"Debug"} =~ /trace/); + my($date,$dow,$today,$hr,$min,$sec)=@_; + &Date_Init() if (! $Curr{"InitDone"}); + my($y,$m,$d,$h,$mn,$s,$err,$curr_dow,%dow,$num,$delta,$th,$tm,$ts, + $adjust,$curr)=(); + $hr="00" if (defined $hr && $hr eq "0"); + $min="00" if (defined $min && $min eq "0"); + $sec="00" if (defined $sec && $sec eq "0"); + + if (! &Date_Split($date)) { + $date=&ParseDateString($date); + return "" if (! $date); + } + $curr=$date; + ($y,$m,$d)=( &Date_Split($date, 1) )[0..2]; + + if ($dow) { + $curr_dow=&Date_DayOfWeek($m,$d,$y); + %dow=%{ $Lang{$Cnf{"Language"}}{"WeekH"} }; + if (&IsInt($dow)) { + return "" if ($dow<1 || $dow>7); + } else { + return "" if (! exists $dow{lc($dow)}); + $dow=$dow{lc($dow)}; + } + if ($dow == $curr_dow) { + $date=&DateCalc_DateDelta($date,"-0:0:1:0:0:0:0",\$err,0) if (! $today); + $adjust=1 if ($today==2); + } else { + $dow -= 7 if ($dow>$curr_dow); # make sure previous day is less + $num = $curr_dow - $dow; + $date=&DateCalc_DateDelta($date,"-0:0:0:$num:0:0:0",\$err,0); + } + $date=&Date_SetTime($date,$hr,$min,$sec) if (defined $hr); + $date=&DateCalc_DateDelta($date,"-0:0:1:0:0:0:0",\$err,0) + if ($adjust && &Date_Cmp($date,$curr)>0); + + } else { + ($h,$mn,$s)=( &Date_Split($date, 1) )[3..5]; + ($th,$tm,$ts)=&Date_ParseTime($hr,$min,$sec); + if ($hr) { + ($hr,$min,$sec)=($th,$tm,$ts); + $delta="-0:0:0:1:0:0:0"; + } elsif ($min) { + ($hr,$min,$sec)=($h,$tm,$ts); + $delta="-0:0:0:0:1:0:0"; + } elsif ($sec) { + ($hr,$min,$sec)=($h,$mn,$ts); + $delta="-0:0:0:0:0:1:0"; + } else { + confess "ERROR: invalid arguments in Date_GetPrev.\n"; + } + + $d=&Date_SetTime($date,$hr,$min,$sec); + if ($today) { + $d=&DateCalc_DateDelta($d,$delta,\$err,0) if (&Date_Cmp($d,$date)>0); + } else { + $d=&DateCalc_DateDelta($d,$delta,\$err,0) if (&Date_Cmp($d,$date)>=0); + } + $date=$d; + } + return $date; +} + +sub Date_GetNext { + print "DEBUG: Date_GetNext\n" if ($Curr{"Debug"} =~ /trace/); + my($date,$dow,$today,$hr,$min,$sec)=@_; + &Date_Init() if (! $Curr{"InitDone"}); + my($y,$m,$d,$h,$mn,$s,$err,$curr_dow,%dow,$num,$delta,$th,$tm,$ts, + $adjust,$curr)=(); + $hr="00" if (defined $hr && $hr eq "0"); + $min="00" if (defined $min && $min eq "0"); + $sec="00" if (defined $sec && $sec eq "0"); + + if (! &Date_Split($date)) { + $date=&ParseDateString($date); + return "" if (! $date); + } + $curr=$date; + ($y,$m,$d)=( &Date_Split($date, 1) )[0..2]; + + if ($dow) { + $curr_dow=&Date_DayOfWeek($m,$d,$y); + %dow=%{ $Lang{$Cnf{"Language"}}{"WeekH"} }; + if (&IsInt($dow)) { + return "" if ($dow<1 || $dow>7); + } else { + return "" if (! exists $dow{lc($dow)}); + $dow=$dow{lc($dow)}; + } + if ($dow == $curr_dow) { + $date=&DateCalc_DateDelta($date,"+0:0:1:0:0:0:0",\$err,0) if (! $today); + $adjust=1 if ($today==2); + } else { + $curr_dow -= 7 if ($curr_dow>$dow); # make sure next date is greater + $num = $dow - $curr_dow; + $date=&DateCalc_DateDelta($date,"+0:0:0:$num:0:0:0",\$err,0); + } + $date=&Date_SetTime($date,$hr,$min,$sec) if (defined $hr); + $date=&DateCalc_DateDelta($date,"+0:0:1:0:0:0:0",\$err,0) + if ($adjust && &Date_Cmp($date,$curr)<0); + + } else { + ($h,$mn,$s)=( &Date_Split($date, 1) )[3..5]; + ($th,$tm,$ts)=&Date_ParseTime($hr,$min,$sec); + if ($hr) { + ($hr,$min,$sec)=($th,$tm,$ts); + $delta="+0:0:0:1:0:0:0"; + } elsif ($min) { + ($hr,$min,$sec)=($h,$tm,$ts); + $delta="+0:0:0:0:1:0:0"; + } elsif ($sec) { + ($hr,$min,$sec)=($h,$mn,$ts); + $delta="+0:0:0:0:0:1:0"; + } else { + confess "ERROR: invalid arguments in Date_GetNext.\n"; + } + + $d=&Date_SetTime($date,$hr,$min,$sec); + if ($today) { + $d=&DateCalc_DateDelta($d,$delta,\$err,0) if (&Date_Cmp($d,$date)<0); + } else { + $d=&DateCalc_DateDelta($d,$delta,\$err,0) if (&Date_Cmp($d,$date)<1); + } + $date=$d; + } + + return $date; +} + +sub Date_IsHoliday { + print "DEBUG: Date_IsHoliday\n" if ($Curr{"Debug"} =~ /trace/); + my($date)=@_; + &Date_Init() if (! $Curr{"InitDone"}); + $date=&ParseDateString($date); + return undef if (! $date); + $date=&Date_SetTime($date,0,0,0); + my($y)=(&Date_Split($date, 1))[0]; + &Date_UpdateHolidays($y) if (! exists $Holiday{"dates"}{$y}); + return undef if (! exists $Holiday{"dates"}{$y}{$date}); + my($name)=$Holiday{"dates"}{$y}{$date}; + return "" if (! $name); + $name; +} + +sub Events_List { + print "DEBUG: Events_List\n" if ($Curr{"Debug"} =~ /trace/); + my(@args)=@_; + &Date_Init() if (! $Curr{"InitDone"}); + &Events_ParseRaw(); + + my($tmp,$date0,$date1,$flag); + $date0=&ParseDateString($args[0]); + warn "Invalid date $args[0]", return undef if (! $date0); + + if ($#args == 0) { + return &Events_Calc($date0); + } + + if ($args[1]) { + $date1=&ParseDateString($args[1]); + warn "Invalid date $args[1]\n", return undef if (! $date1); + if (&Date_Cmp($date0,$date1)>0) { + $tmp=$date1; + $date1=$date0; + $date0=$tmp; + } + } else { + $date0=&Date_SetTime($date0,"00:00:00"); + $date1=&DateCalc_DateDelta($date0,"+0:0:0:1:0:0:0"); + } + + $tmp=&Events_Calc($date0,$date1); + + $flag=$args[2]; + return $tmp if (! $flag); + + my(@tmp,%ret,$delta)=(); + @tmp=@$tmp; + push(@tmp,$date1); + + if ($flag==1) { + while ($#tmp>0) { + ($date0,$tmp)=splice(@tmp,0,2); + $date1=$tmp[0]; + $delta=&DateCalc_DateDate($date0,$date1); + foreach $flag (@$tmp) { + if (exists $ret{$flag}) { + $ret{$flag}=&DateCalc_DeltaDelta($ret{$flag},$delta); + } else { + $ret{$flag}=$delta; + } + } + } + return \%ret; + + } elsif ($flag==2) { + while ($#tmp>0) { + ($date0,$tmp)=splice(@tmp,0,2); + $date1=$tmp[0]; + $delta=&DateCalc_DateDate($date0,$date1); + $flag=join("+",sort @$tmp); + next if (! $flag); + if (exists $ret{$flag}) { + $ret{$flag}=&DateCalc_DeltaDelta($ret{$flag},$delta); + } else { + $ret{$flag}=$delta; + } + } + return \%ret; + } + + warn "Invalid flag $flag\n"; + return undef; +} + +### +# NOTE: The following routines may be called in the routines below with very +# little time penalty. +### +sub Date_SetTime { + print "DEBUG: Date_SetTime\n" if ($Curr{"Debug"} =~ /trace/); + my($date,$h,$mn,$s)=@_; + &Date_Init() if (! $Curr{"InitDone"}); + my($y,$m,$d)=(); + + if (! &Date_Split($date)) { + $date=&ParseDateString($date); + return "" if (! $date); + } + + ($y,$m,$d)=( &Date_Split($date, 1) )[0..2]; + ($h,$mn,$s)=&Date_ParseTime($h,$mn,$s); + + my($ampm,$wk); + return "" if (&Date_DateCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk)); + &Date_Join($y,$m,$d,$h,$mn,$s); +} + +sub Date_SetDateField { + print "DEBUG: Date_SetDateField\n" if ($Curr{"Debug"} =~ /trace/); + my($date,$field,$val,$nocheck)=@_; + my($y,$m,$d,$h,$mn,$s)=(); + $nocheck=0 if (! defined $nocheck); + + ($y,$m,$d,$h,$mn,$s)=&Date_Split($date); + + if (! $y) { + $date=&ParseDateString($date); + return "" if (! $date); + ($y,$m,$d,$h,$mn,$s)=&Date_Split($date, 1); + } + + if (lc($field) eq "y") { + $y=$val; + } elsif (lc($field) eq "m") { + $m=$val; + } elsif (lc($field) eq "d") { + $d=$val; + } elsif (lc($field) eq "h") { + $h=$val; + } elsif (lc($field) eq "mn") { + $mn=$val; + } elsif (lc($field) eq "s") { + $s=$val; + } else { + confess "ERROR: Date_SetDateField: invalid field: $field\n"; + } + + $date=&Date_Join($y,$m,$d,$h,$mn,$s); + return $date if ($nocheck || &Date_Split($date)); + return ""; +} + +######################################################################## +# OTHER SUBROUTINES +######################################################################## +# NOTE: These routines should not call any of the routines above as +# there will be a severe time penalty (and the possibility of +# infinite recursion). The last couple routines above are +# exceptions. +# NOTE: Date_Init is a special case. It should be called (conditionally) +# in every routine that uses any variable from the Date::Manip +# namespace. +######################################################################## + +sub Date_DaysInMonth { + print "DEBUG: Date_DaysInMonth\n" if ($Curr{"Debug"} =~ /trace/); + my($m,$y)=@_; + $y=&Date_FixYear($y) if (length($y)!=4); + my(@d_in_m)=(0,31,28,31,30,31,30,31,31,30,31,30,31); + $d_in_m[2]=29 if (&Date_LeapYear($y)); + return $d_in_m[$m]; +} + +sub Date_DayOfWeek { + print "DEBUG: Date_DayOfWeek\n" if ($Curr{"Debug"} =~ /trace/); + my($m,$d,$y)=@_; + $y=&Date_FixYear($y) if (length($y)!=4); + my($dayofweek,$dec31)=(); + + $dec31=5; # Dec 31, 1BC was Friday + $dayofweek=(&Date_DaysSince1BC($m,$d,$y)+$dec31) % 7; + $dayofweek=7 if ($dayofweek==0); + return $dayofweek; +} + +# Can't be in "use integer" because the numbers are too big. +no integer; +sub Date_SecsSince1970 { + print "DEBUG: Date_SecsSince1970\n" if ($Curr{"Debug"} =~ /trace/); + my($m,$d,$y,$h,$mn,$s)=@_; + $y=&Date_FixYear($y) if (length($y)!=4); + my($sec_now,$sec_70)=(); + $sec_now=(&Date_DaysSince1BC($m,$d,$y)-1)*24*3600 + $h*3600 + $mn*60 + $s; +# $sec_70 =(&Date_DaysSince1BC(1,1,1970)-1)*24*3600; + $sec_70 =62167219200; + return ($sec_now-$sec_70); +} + +sub Date_SecsSince1970GMT { + print "DEBUG: Date_SecsSince1970GMT\n" if ($Curr{"Debug"} =~ /trace/); + my($m,$d,$y,$h,$mn,$s)=@_; + &Date_Init() if (! $Curr{"InitDone"}); + $y=&Date_FixYear($y) if (length($y)!=4); + + my($sec)=&Date_SecsSince1970($m,$d,$y,$h,$mn,$s); + return $sec if ($Cnf{"ConvTZ"} eq "IGNORE"); + + my($tz)=$Cnf{"ConvTZ"}; + $tz=$Cnf{"TZ"} if (! $tz); + $tz=$Zone{"n2o"}{lc($tz)} if ($tz !~ /^[+-]\d{4}$/); + + my($tzs)=1; + $tzs=-1 if ($tz<0); + $tz=~/.(..)(..)/; + my($tzh,$tzm)=($1,$2); + $sec - $tzs*($tzh*3600+$tzm*60); +} +use integer; + +sub Date_DaysSince1BC { + print "DEBUG: Date_DaysSince1BC\n" if ($Curr{"Debug"} =~ /trace/); + my($m,$d,$y)=@_; + $y=&Date_FixYear($y) if (length($y)!=4); + my($Ny,$N4,$N100,$N400,$dayofyear,$days)=(); + my($cc,$yy)=(); + + $y=~ /(\d{2})(\d{2})/; + ($cc,$yy)=($1,$2); + + # Number of full years since Dec 31, 1BC (counting the year 0000). + $Ny=$y; + + # Number of full 4th years (incl. 0000) since Dec 31, 1BC + $N4=($Ny-1)/4 + 1; + $N4=0 if ($y==0); + + # Number of full 100th years (incl. 0000) + $N100=$cc + 1; + $N100-- if ($yy==0); + $N100=0 if ($y==0); + + # Number of full 400th years (incl. 0000) + $N400=($N100-1)/4 + 1; + $N400=0 if ($y==0); + + $dayofyear=&Date_DayOfYear($m,$d,$y); + $days= $Ny*365 + $N4 - $N100 + $N400 + $dayofyear; + + return $days; +} + +sub Date_DayOfYear { + print "DEBUG: Date_DayOfYear\n" if ($Curr{"Debug"} =~ /trace/); + my($m,$d,$y)=@_; + $y=&Date_FixYear($y) if (length($y)!=4); + # DinM = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) + my(@days) = ( 0, 31, 59, 90,120,151,181,212,243,273,304,334,365); + my($ly)=0; + $ly=1 if ($m>2 && &Date_LeapYear($y)); + return ($days[$m-1]+$d+$ly); +} + +sub Date_DaysInYear { + print "DEBUG: Date_DaysInYear\n" if ($Curr{"Debug"} =~ /trace/); + my($y)=@_; + $y=&Date_FixYear($y) if (length($y)!=4); + return 366 if (&Date_LeapYear($y)); + return 365; +} + +sub Date_WeekOfYear { + print "DEBUG: Date_WeekOfYear\n" if ($Curr{"Debug"} =~ /trace/); + my($m,$d,$y,$f)=@_; + &Date_Init() if (! $Curr{"InitDone"}); + $y=&Date_FixYear($y) if (length($y)!=4); + + my($day,$dow,$doy)=(); + $doy=&Date_DayOfYear($m,$d,$y); + + # The current DayOfYear and DayOfWeek + if ($Cnf{"Jan1Week1"}) { + $day=1; + } else { + $day=4; + } + $dow=&Date_DayOfWeek(1,$day,$y); + + # Move back to the first day of week 1. + $f-=7 if ($f>$dow); + $day-= ($dow-$f); + + return 0 if ($day>$doy); # Day is in last week of previous year + return (($doy-$day)/7 + 1); +} + +sub Date_LeapYear { + print "DEBUG: Date_LeapYear\n" if ($Curr{"Debug"} =~ /trace/); + my($y)=@_; + $y=&Date_FixYear($y) if (length($y)!=4); + return 0 unless $y % 4 == 0; + return 1 unless $y % 100 == 0; + return 0 unless $y % 400 == 0; + return 1; +} + +sub Date_DaySuffix { + print "DEBUG: Date_DaySuffix\n" if ($Curr{"Debug"} =~ /trace/); + my($d)=@_; + &Date_Init() if (! $Curr{"InitDone"}); + return $Lang{$Cnf{"Language"}}{"DoML"}[$d-1]; +} + +sub Date_ConvTZ { + print "DEBUG: Date_ConvTZ\n" if ($Curr{"Debug"} =~ /trace/); + my($date,$from,$to)=@_; + if (not Date_Split($date)) { + croak "date passed in ('$date') is not a Date::Manip object"; + } + + &Date_Init() if (! $Curr{"InitDone"}); + my($gmt)=(); + + if (! $from) { + + if (! $to) { + # TZ -> ConvTZ + return $date if ($Cnf{"ConvTZ"} eq "IGNORE" or ! $Cnf{"ConvTZ"}); + $from=$Cnf{"TZ"}; + $to=$Cnf{"ConvTZ"}; + + } else { + # ConvTZ,TZ -> $to + $from=$Cnf{"ConvTZ"}; + $from=$Cnf{"TZ"} if (! $from); + } + + } else { + + if (! $to) { + # $from -> ConvTZ,TZ + return $date if ($Cnf{"ConvTZ"} eq "IGNORE"); + $to=$Cnf{"ConvTZ"}; + $to=$Cnf{"TZ"} if (! $to); + + } else { + # $from -> $to + } + } + + $to=$Zone{"n2o"}{lc($to)} + if (exists $Zone{"n2o"}{lc($to)}); + $from=$Zone{"n2o"}{lc($from)} + if (exists $Zone{"n2o"}{lc($from)}); + $gmt=$Zone{"n2o"}{"gmt"}; + + return $date if ($from !~ /^[+-]\d{4}$/ or $to !~ /^[+-]\d{4}$/); + return $date if ($from eq $to); + + my($s1,$h1,$m1,$s2,$h2,$m2,$d,$h,$m,$sign,$delta,$err,$yr,$mon,$sec)=(); + # We're going to try to do the calculation without calling DateCalc. + ($yr,$mon,$d,$h,$m,$sec)=&Date_Split($date, 1); + + # Convert $date from $from to GMT + $from=~/([+-])(\d{2})(\d{2})/; + ($s1,$h1,$m1)=($1,$2,$3); + $s1= ($s1 eq "-" ? "+" : "-"); # switch sign + $sign=$s1 . "1"; # + or - 1 + + # and from GMT to $to + $to=~/([+-])(\d{2})(\d{2})/; + ($s2,$h2,$m2)=($1,$2,$3); + + if ($s1 eq $s2) { + # Both the same sign + $m+= $sign*($m1+$m2); + $h+= $sign*($h1+$h2); + } else { + $sign=($s2 eq "-" ? +1 : -1) if ($h1<$h2 || ($h1==$h2 && $m1<$m2)); + $m+= $sign*($m1-$m2); + $h+= $sign*($h1-$h2); + } + + if ($m>59) { + $h+= $m/60; + $m-= ($m/60)*60; + } elsif ($m<0) { + $h+= ($m/60 - 1); + $m-= ($m/60 - 1)*60; + } + + if ($h>23) { + $delta=$h/24; + $h -= $delta*24; + if (($d + $delta) > 28) { + $date=&Date_Join($yr,$mon,$d,$h,$m,$sec); + return &DateCalc_DateDelta($date,"+0:0:0:$delta:0:0:0",\$err,0); + } + $d+= $delta; + } elsif ($h<0) { + $delta=-$h/24 + 1; + $h += $delta*24; + if (($d - $delta) < 1) { + $date=&Date_Join($yr,$mon,$d,$h,$m,$sec); + return &DateCalc_DateDelta($date,"-0:0:0:$delta:0:0:0",\$err,0); + } + $d-= $delta; + } + return &Date_Join($yr,$mon,$d,$h,$m,$sec); +} + +sub Date_TimeZone { + print "DEBUG: Date_TimeZone\n" if ($Curr{"Debug"} =~ /trace/); + my($null,$tz,@tz,$std,$dst,$time,$isdst,$tmp,$in)=(); + &Date_Init() if (! $Curr{"InitDone"}); + + # Get timezones from all of the relevant places + + push(@tz,$Cnf{"TZ"}) if (defined $Cnf{"TZ"}); # TZ config var + push(@tz,$ENV{"TZ"}) if (defined $ENV{"TZ"}); # TZ environ var + push(@tz,$ENV{'SYS$TIMEZONE_RULE'}) + if defined $ENV{'SYS$TIMEZONE_RULE'}; # VMS TZ environ var + push(@tz,$ENV{'SYS$TIMEZONE_NAME'}) + if defined $ENV{'SYS$TIMEZONE_NAME'}; # VMS TZ name environ var + push(@tz,$ENV{'UCX$TZ'}) + if defined $ENV{'UCX$TZ'}; # VMS TZ environ var + push(@tz,$ENV{'TCPIP$TZ'}) + if defined $ENV{'TCPIP$TZ'}; # VMS TZ environ var + + # The `date` command... if we're doing taint checking, we need to + # always call it with a full path... otherwise, use the user's path. + # + # Microsoft operating systems don't have a date command built in. Try + # to trap all the various ways of knowing we are on one of these systems. + # + # We'll try `date +%Z` first, and if that fails, we'll take just the + # `date` program and assume the output is of the format: + # Thu Aug 31 14:57:46 EDT 2000 + + unless (($^X =~ /perl\.exe$/i) or + ($OS eq "Windows") or + ($OS eq "Netware") or + ($OS eq "VMS")) { + if ($Date::Manip::NoTaint) { + if ($OS eq "VMS") { + $tz=$ENV{'SYS$TIMEZONE_NAME'}; + if (! $tz) { + $tz=$ENV{'MULTINET_TIMEZONE'}; + if (! $tz) { + $tz=$ENV{'SYS$TIMEZONE_DIFFERENTIAL'}/3600.; # e.g. '-4' for EDT + } + } + } else { + $tz=`date +%Z 2> /dev/null`; + chomp($tz); + if (! $tz) { + $tz=`date 2> /dev/null`; + chomp($tz); + $tz=(split(/\s+/,$tz))[4]; + } + } + push(@tz,$tz); + } else { + # We need to satisfy taint checking, but also look in all the + # directories in @DatePath. + # + local $ENV{PATH} = join(':', @Date::Manip::DatePath); + local $ENV{BASH_ENV} = ''; + $tz=`date +%Z 2> /dev/null`; + chomp($tz); + if (! $tz) { + $tz=`date 2> /dev/null`; + chomp($tz); + $tz=(split(/\s+/,$tz))[4]; + } + push(@tz,$tz); + } + } + + push(@tz,$main::TZ) if (defined $main::TZ); # $main::TZ + + if (-s "/etc/TIMEZONE") { # /etc/TIMEZONE + $in=new IO::File; + $in->open("/etc/TIMEZONE","r"); + while (! eof($in)) { + $tmp=<$in>; + if ($tmp =~ /^TZ\s*=\s*(.*?)\s*$/) { + push(@tz,$1); + last; + } + } + $in->close; + } + + if (-s "/etc/timezone") { # /etc/timezone + $in=new IO::File; + $in->open("/etc/timezone","r"); + while (! eof($in)) { + $tmp=<$in>; + next if ($tmp =~ /^\s*\043/); + chomp($tmp); + if ($tmp =~ /^\s*(.*?)\s*$/) { + push(@tz,$1); + last; + } + } + $in->close; + } + + # Now parse each one to find the first valid one. + foreach $tz (@tz) { + $tz =~ s/\s*$//; + $tz =~ s/^\s*//; + next if (! $tz); + + return uc($tz) + if (defined $Zone{"n2o"}{lc($tz)}); + + if ($tz =~ /^[+-]\d{4}$/) { + return $tz; + } elsif ($tz =~ /^([+-]\d{2})(?::(\d{2}))?$/) { + my($h,$m)=($1,$2); + $m="00" if (! $m); + return "$h$m"; + } + + # Handle US/Eastern format + if ($tz =~ /^$Zone{"tzones"}$/i) { + $tmp=lc $1; + $tz=$Zone{"tz2z"}{$tmp}; + } + + # Handle STD#DST# format (and STD-#DST-# formats) + if ($tz =~ /^([a-z]+)-?\d([a-z]+)-?\d?$/i) { + ($std,$dst)=($1,$2); + next if (! defined $Zone{"n2o"}{lc($std)} or + ! defined $Zone{"n2o"}{lc($dst)}); + $time = time(); + ($null,$null,$null,$null,$null,$null,$null,$null,$isdst) = + localtime($time); + return uc($dst) if ($isdst); + return uc($std); + } + } + + confess "ERROR: Date::Manip unable to determine TimeZone.\n"; +} + +# Returns 1 if $date is a work day. If $time is non-zero, the time is +# also checked to see if it falls within work hours. Returns "" if +# an invalid date is passed in. +sub Date_IsWorkDay { + print "DEBUG: Date_IsWorkDay\n" if ($Curr{"Debug"} =~ /trace/); + my($date,$time)=@_; + &Date_Init() if (! $Curr{"InitDone"}); + $date=&ParseDateString($date); + return "" if (! $date); + my($d)=$date; + $d=&Date_SetTime($date,$Cnf{"WorkDayBeg"}) if (! $time); + + my($y,$mon,$day,$tmp,$h,$m,$dow)=(); + ($y,$mon,$day,$h,$m,$tmp)=&Date_Split($d, 1); + $dow=&Date_DayOfWeek($mon,$day,$y); + + return 0 if ($dow<$Cnf{"WorkWeekBeg"} or + $dow>$Cnf{"WorkWeekEnd"} or + "$h:$m" lt $Cnf{"WorkDayBeg"} or + "$h:$m" gt $Cnf{"WorkDayEnd"}); + + if (! exists $Holiday{"dates"}{$y}) { + # There will be recursion problems if we ever end up here twice. + $Holiday{"dates"}{$y}={}; + &Date_UpdateHolidays($y) + } + $d=&Date_SetTime($date,"00:00:00"); + return 0 if (exists $Holiday{"dates"}{$y}{$d}); + 1; +} + +# Finds the day $off work days from now. If $time is passed in, we must +# also take into account the time of day. +# +# If $time is not passed in, day 0 is today (if today is a workday) or the +# next work day if it isn't. In any case, the time of day is unaffected. +# +# If $time is passed in, day 0 is now (if now is part of a workday) or the +# start of the very next work day. +sub Date_NextWorkDay { + print "DEBUG: Date_NextWorkDay\n" if ($Curr{"Debug"} =~ /trace/); + my($date,$off,$time)=@_; + &Date_Init() if (! $Curr{"InitDone"}); + $date=&ParseDateString($date); + my($err)=(); + + if (! &Date_IsWorkDay($date,$time)) { + if ($time) { + while (1) { + $date=&Date_GetNext($date,undef,0,$Cnf{"WorkDayBeg"}); + last if (&Date_IsWorkDay($date,$time)); + } + } else { + while (1) { + $date=&DateCalc_DateDelta($date,"+0:0:0:1:0:0:0",\$err,0); + last if (&Date_IsWorkDay($date,$time)); + } + } + } + + while ($off>0) { + while (1) { + $date=&DateCalc_DateDelta($date,"+0:0:0:1:0:0:0",\$err,0); + last if (&Date_IsWorkDay($date,$time)); + } + $off--; + } + + return $date; +} + +# Finds the day $off work days before now. If $time is passed in, we must +# also take into account the time of day. +# +# If $time is not passed in, day 0 is today (if today is a workday) or the +# previous work day if it isn't. In any case, the time of day is unaffected. +# +# If $time is passed in, day 0 is now (if now is part of a workday) or the +# end of the previous work period. Note that since the end of a work day +# will automatically be turned into the start of the next one, this time +# may actually be treated as AFTER the current time. +sub Date_PrevWorkDay { + print "DEBUG: Date_PrevWorkDay\n" if ($Curr{"Debug"} =~ /trace/); + my($date,$off,$time)=@_; + &Date_Init() if (! $Curr{"InitDone"}); + $date=&ParseDateString($date); + my($err)=(); + + if (! &Date_IsWorkDay($date,$time)) { + if ($time) { + while (1) { + $date=&Date_GetPrev($date,undef,0,$Cnf{"WorkDayEnd"}); + last if (&Date_IsWorkDay($date,$time)); + } + while (1) { + $date=&Date_GetNext($date,undef,0,$Cnf{"WorkDayBeg"}); + last if (&Date_IsWorkDay($date,$time)); + } + } else { + while (1) { + $date=&DateCalc_DateDelta($date,"-0:0:0:1:0:0:0",\$err,0); + last if (&Date_IsWorkDay($date,$time)); + } + } + } + + while ($off>0) { + while (1) { + $date=&DateCalc_DateDelta($date,"-0:0:0:1:0:0:0",\$err,0); + last if (&Date_IsWorkDay($date,$time)); + } + $off--; + } + + return $date; +} + +# This finds the nearest workday to $date. If $date is a workday, it +# is returned. +sub Date_NearestWorkDay { + print "DEBUG: Date_NearestWorkDay\n" if ($Curr{"Debug"} =~ /trace/); + my($date,$tomorrow)=@_; + &Date_Init() if (! $Curr{"InitDone"}); + $date=&ParseDateString($date); + my($a,$b,$dela,$delb,$err)=(); + $tomorrow=$Cnf{"TomorrowFirst"} if (! defined $tomorrow); + + return $date if (&Date_IsWorkDay($date)); + + # Find the nearest one. + if ($tomorrow) { + $dela="+0:0:0:1:0:0:0"; + $delb="-0:0:0:1:0:0:0"; + } else { + $dela="-0:0:0:1:0:0:0"; + $delb="+0:0:0:1:0:0:0"; + } + $a=$b=$date; + + while (1) { + $a=&DateCalc_DateDelta($a,$dela,\$err); + return $a if (&Date_IsWorkDay($a)); + $b=&DateCalc_DateDelta($b,$delb,\$err); + return $b if (&Date_IsWorkDay($b)); + } +} + +# &Date_NthDayOfYear($y,$n); +# Returns a list of (YYYY,MM,DD,HH,MM,SS) for the Nth day of the year. +sub Date_NthDayOfYear { + no integer; + print "DEBUG: Date_NthDayOfYear\n" if ($Curr{"Debug"} =~ /trace/); + my($y,$n)=@_; + $y=$Curr{"Y"} if (! $y); + $n=1 if (! defined $n or $n eq ""); + $n+=0; # to turn 023 into 23 + $y=&Date_FixYear($y) if (length($y)<4); + my $leap=&Date_LeapYear($y); + return () if ($n<1); + return () if ($n >= ($leap ? 367 : 366)); + + my(@d_in_m)=(31,28,31,30,31,30,31,31,30,31,30,31); + $d_in_m[1]=29 if ($leap); + + # Calculate the hours, minutes, and seconds into the day. + my $remain=($n - int($n))*24; + my $h=int($remain); + $remain=($remain - $h)*60; + my $mn=int($remain); + $remain=($remain - $mn)*60; + my $s=$remain; + + # Calculate the month and the day. + my($m,$d)=(0,0); + $n=int($n); + while ($n>0) { + $m++; + if ($n<=$d_in_m[0]) { + $d=int($n); + $n=0; + } else { + $n-= $d_in_m[0]; + shift(@d_in_m); + } + } + + ($y,$m,$d,$h,$mn,$s); +} + +######################################################################## +# NOT FOR EXPORT +######################################################################## + +# This is used in Date_Init to fill in a hash based on international +# data. It takes a list of keys and values and returns both a hash +# with these values and a regular expression of keys. +# +# IN: +# $data = [ key1 val1 key2 val2 ... ] +# $opts = lc : lowercase the keys in the regexp +# sort : sort (by length) the keys in the regexp +# back : create a regexp with a back reference +# escape : escape all strings in the regexp +# +# OUT: +# $regexp = '(?:key1|key2|...)' +# $hash = { key1=>val1 key2=>val2 ... } + +sub Date_InitHash { + print "DEBUG: Date_InitHash\n" if ($Curr{"Debug"} =~ /trace/); + my($data,$regexp,$opts,$hash)=@_; + my(@data)=@$data; + my($key,$val,@list)=(); + + # Parse the options + my($lc,$sort,$back,$escape)=(0,0,0,0); + $lc=1 if ($opts =~ /lc/i); + $sort=1 if ($opts =~ /sort/i); + $back=1 if ($opts =~ /back/i); + $escape=1 if ($opts =~ /escape/i); + + # Create the hash + while (@data) { + ($key,$val,@data)=@data; + $key=lc($key) if ($lc); + $$hash{$key}=$val; + } + + # Create the regular expression + if ($regexp) { + @list=keys(%$hash); + @list=sort sortByLength(@list) if ($sort); + if ($escape) { + foreach $val (@list) { + $val="\Q$val\E"; + } + } + if ($back) { + $$regexp="(" . join("|",@list) . ")"; + } else { + $$regexp="(?:" . join("|",@list) . ")"; + } + } +} + +# This is used in Date_Init to fill in regular expressions, lists, and +# hashes based on international data. It takes a list of lists which have +# to be stored as regular expressions (to find any element in the list), +# lists, and hashes (indicating the location in the lists). +# +# IN: +# $data = [ [ [ valA1 valA2 ... ][ valA1' valA2' ... ] ... ] +# [ [ valB1 valB2 ... ][ valB1' valB2' ... ] ... ] +# ... +# [ [ valZ1 valZ2 ... ] [valZ1' valZ1' ... ] ... ] ] +# $lists = [ \@listA \@listB ... \@listZ ] +# $opts = lc : lowercase the values in the regexp +# sort : sort (by length) the values in the regexp +# back : create a regexp with a back reference +# escape : escape all strings in the regexp +# $hash = [ \%hash, TYPE ] +# TYPE 0 : $hash{ valBn=>n-1 } +# TYPE 1 : $hash{ valBn=>n } +# +# OUT: +# $regexp = '(?:valA1|valA2|...|valB1|...)' +# $lists = [ [ valA1 valA2 ... ] # only the 1st list (or +# [ valB1 valB2 ... ] ... ] # 2nd for int. characters) +# $hash + +sub Date_InitLists { + print "DEBUG: Date_InitLists\n" if ($Curr{"Debug"} =~ /trace/); + my($data,$regexp,$opts,$lists,$hash)=@_; + my(@data)=@$data; + my(@lists)=@$lists; + my($i,@ele,$ele,@list,$j,$tmp)=(); + + # Parse the options + my($lc,$sort,$back,$escape)=(0,0,0,0); + $lc=1 if ($opts =~ /lc/i); + $sort=1 if ($opts =~ /sort/i); + $back=1 if ($opts =~ /back/i); + $escape=1 if ($opts =~ /escape/i); + + # Set each of the lists + if (@lists) { + confess "ERROR: Date_InitLists: lists must be 1 per data\n" + if ($#lists != $#data); + for ($i=0; $i<=$#data; $i++) { + @ele=@{ $data[$i] }; + if ($Cnf{"IntCharSet"} && $#ele>0) { + @{ $lists[$i] } = @{ $ele[1] }; + } else { + @{ $lists[$i] } = @{ $ele[0] }; + } + } + } + + # Create the hash + my($hashtype,$hashsave,%hash)=(); + if (@$hash) { + ($hash,$hashtype)=@$hash; + $hashsave=1; + } else { + $hashtype=0; + $hashsave=0; + } + for ($i=0; $i<=$#data; $i++) { + @ele=@{ $data[$i] }; + foreach $ele (@ele) { + @list = @{ $ele }; + for ($j=0; $j<=$#list; $j++) { + $tmp=$list[$j]; + next if (! $tmp); + $tmp=lc($tmp) if ($lc); + $hash{$tmp}= $j+$hashtype; + } + } + } + %$hash = %hash if ($hashsave); + + # Create the regular expression + if ($regexp) { + @list=keys(%hash); + @list=sort sortByLength(@list) if ($sort); + if ($escape) { + foreach $ele (@list) { + $ele="\Q$ele\E"; + } + } + if ($back) { + $$regexp="(" . join("|",@list) . ")"; + } else { + $$regexp="(?:" . join("|",@list) . ")"; + } + } +} + +# This is used in Date_Init to fill in regular expressions and lists based +# on international data. This takes a list of strings and returns a regular +# expression (to find any one of them). +# +# IN: +# $data = [ string1 string2 ... ] +# $opts = lc : lowercase the values in the regexp +# sort : sort (by length) the values in the regexp +# back : create a regexp with a back reference +# escape : escape all strings in the regexp +# +# OUT: +# $regexp = '(string1|string2|...)' + +sub Date_InitStrings { + print "DEBUG: Date_InitStrings\n" if ($Curr{"Debug"} =~ /trace/); + my($data,$regexp,$opts)=@_; + my(@list)=@{ $data }; + + # Parse the options + my($lc,$sort,$back,$escape)=(0,0,0,0); + $lc=1 if ($opts =~ /lc/i); + $sort=1 if ($opts =~ /sort/i); + $back=1 if ($opts =~ /back/i); + $escape=1 if ($opts =~ /escape/i); + + # Create the regular expression + my($ele)=(); + @list=sort sortByLength(@list) if ($sort); + if ($escape) { + foreach $ele (@list) { + $ele="\Q$ele\E"; + } + } + if ($back) { + $$regexp="(" . join("|",@list) . ")"; + } else { + $$regexp="(?:" . join("|",@list) . ")"; + } + $$regexp=lc($$regexp) if ($lc); +} + +# items is passed in (either as a space separated string, or a reference to +# a list) and a regular expression which matches any one of the items is +# prepared. The regular expression will be of one of the forms: +# "(a|b)" @list not empty, back option included +# "(?:a|b)" @list not empty +# "()" @list empty, back option included +# "" @list empty +# $options is a string which contains any of the following strings: +# back : the regular expression has a backreference +# opt : the regular expression is optional and a "?" is appended in +# the first two forms +# optws : the regular expression is optional and may be replaced by +# whitespace +# optWs : the regular expression is optional, but if not present, must +# be replaced by whitespace +# sort : the items in the list are sorted by length (longest first) +# lc : the string is lowercased +# under : any underscores are converted to spaces +# pre : it may be preceded by whitespace +# Pre : it must be preceded by whitespace +# PRE : it must be preceded by whitespace or the start +# post : it may be followed by whitespace +# Post : it must be followed by whitespace +# POST : it must be followed by whitespace or the end +# Spaces due to pre/post options will not be included in the back reference. +# +# If $array is included, then the elements will also be returned as a list. +# $array is a string which may contain any of the following: +# keys : treat the list as a hash and only the keys go into the regexp +# key0 : treat the list as the values of a hash with keys 0 .. N-1 +# key1 : treat the list as the values of a hash with keys 1 .. N +# val0 : treat the list as the keys of a hash with values 0 .. N-1 +# val1 : treat the list as the keys of a hash with values 1 .. N + +# &Date_InitLists([$lang{"month_name"},$lang{"month_abb"}], +# [\$Month,"lc,sort,back"], +# [\@Month,\@Mon], +# [\%Month,1]); + +# This is used in Date_Init to prepare regular expressions. A list of +# items is passed in (either as a space separated string, or a reference to +# a list) and a regular expression which matches any one of the items is +# prepared. The regular expression will be of one of the forms: +# "(a|b)" @list not empty, back option included +# "(?:a|b)" @list not empty +# "()" @list empty, back option included +# "" @list empty +# $options is a string which contains any of the following strings: +# back : the regular expression has a backreference +# opt : the regular expression is optional and a "?" is appended in +# the first two forms +# optws : the regular expression is optional and may be replaced by +# whitespace +# optWs : the regular expression is optional, but if not present, must +# be replaced by whitespace +# sort : the items in the list are sorted by length (longest first) +# lc : the string is lowercased +# under : any underscores are converted to spaces +# pre : it may be preceded by whitespace +# Pre : it must be preceded by whitespace +# PRE : it must be preceded by whitespace or the start +# post : it may be followed by whitespace +# Post : it must be followed by whitespace +# POST : it must be followed by whitespace or the end +# Spaces due to pre/post options will not be included in the back reference. +# +# If $array is included, then the elements will also be returned as a list. +# $array is a string which may contain any of the following: +# keys : treat the list as a hash and only the keys go into the regexp +# key0 : treat the list as the values of a hash with keys 0 .. N-1 +# key1 : treat the list as the values of a hash with keys 1 .. N +# val0 : treat the list as the keys of a hash with values 0 .. N-1 +# val1 : treat the list as the keys of a hash with values 1 .. N +sub Date_Regexp { + print "DEBUG: Date_Regexp\n" if ($Curr{"Debug"} =~ /trace/); + my($list,$options,$array)=@_; + my(@list,$ret,%hash,$i)=(); + local($_)=(); + $options="" if (! defined $options); + $array="" if (! defined $array); + + my($sort,$lc,$under)=(0,0,0); + $sort =1 if ($options =~ /sort/i); + $lc =1 if ($options =~ /lc/i); + $under=1 if ($options =~ /under/i); + my($back,$opt,$pre,$post,$ws)=("?:","","","",""); + $back ="" if ($options =~ /back/i); + $opt ="?" if ($options =~ /opt/i); + $pre ='\s*' if ($options =~ /pre/); + $pre ='\s+' if ($options =~ /Pre/); + $pre ='(?:\s+|^)' if ($options =~ /PRE/); + $post ='\s*' if ($options =~ /post/); + $post ='\s+' if ($options =~ /Post/); + $post ='(?:$|\s+)' if ($options =~ /POST/); + $ws ='\s*' if ($options =~ /optws/); + $ws ='\s+' if ($options =~ /optws/); + + my($hash,$keys,$key0,$key1,$val0,$val1)=(0,0,0,0,0,0); + $keys =1 if ($array =~ /keys/i); + $key0 =1 if ($array =~ /key0/i); + $key1 =1 if ($array =~ /key1/i); + $val0 =1 if ($array =~ /val0/i); + $val1 =1 if ($array =~ /val1/i); + $hash =1 if ($keys or $key0 or $key1 or $val0 or $val1); + + my($ref)=ref $list; + if (! $ref) { + $list =~ s/\s*$//; + $list =~ s/^\s*//; + $list =~ s/\s+/&&&/g; + } elsif ($ref eq "ARRAY") { + $list = join("&&&",@$list); + } else { + confess "ERROR: Date_Regexp.\n"; + } + + if (! $list) { + if ($back eq "") { + return "()"; + } else { + return ""; + } + } + + $list=lc($list) if ($lc); + $list=~ s/_/ /g if ($under); + @list=split(/&&&/,$list); + if ($keys) { + %hash=@list; + @list=keys %hash; + } elsif ($key0 or $key1 or $val0 or $val1) { + $i=0; + $i=1 if ($key1 or $val1); + if ($key0 or $key1) { + %hash= map { $_,$i++ } @list; + } else { + %hash= map { $i++,$_ } @list; + } + } + @list=sort sortByLength(@list) if ($sort); + + $ret="($back" . join("|",@list) . ")"; + $ret="(?:$pre$ret$post)" if ($pre or $post); + $ret.=$opt; + $ret="(?:$ret|$ws)" if ($ws); + + if ($array and $hash) { + return ($ret,%hash); + } elsif ($array) { + return ($ret,@list); + } else { + return $ret; + } +} + +# This will produce a delta with the correct number of signs. At most two +# signs will be in it normally (one before the year, and one in front of +# the day), but if appropriate, signs will be in front of all elements. +# Also, as many of the signs will be equivalent as possible. +sub Delta_Normalize { + print "DEBUG: Delta_Normalize\n" if ($Curr{"Debug"} =~ /trace/); + my($delta,$mode)=@_; + return "" if (! $delta); + return "+0:+0:+0:+0:+0:+0:+0" + if ($delta =~ /^([+-]?0+:){6}[+-]?0+$/ and $Cnf{"DeltaSigns"}); + return "+0:0:0:0:0:0:0" if ($delta =~ /^([+-]?0+:){6}[+-]?0+$/); + + my($tmp,$sign1,$sign2,$len)=(); + + # Calculate the length of the day in minutes + $len=24*60; + $len=$Curr{"WDlen"} if ($mode==2 || $mode==3); + + # We have to get the sign of every component explicitely so that a "-0" + # or "+0" doesn't get lost by treating it numerically (i.e. "-0:0:2" must + # be a negative delta). + + my($y,$mon,$w,$d,$h,$m,$s)=&Delta_Split($delta); + + # We need to make sure that the signs of all parts of a delta are the + # same. The easiest way to do this is to convert all of the large + # components to the smallest ones, then convert the smaller components + # back to the larger ones. + + # Do the year/month part + + $mon += $y*12; # convert y to m + $sign1="+"; + if ($mon<0) { + $mon *= -1; + $sign1="-"; + } + + $y = $mon/12; # convert m to y + $mon -= $y*12; + + $y=0 if ($y eq "-0"); # get around silly -0 problem + $mon=0 if ($mon eq "-0"); + + # Do the wk/day/hour/min/sec part + + { + # Unfortunately, $s is overflowing for dates more than ~70 years + # apart. + no integer; + + if ($mode==3 || $mode==2) { + $s += $d*$len*60 + $h*3600 + $m*60; # convert d/h/m to s + } else { + $s += ($d+7*$w)*$len*60 + $h*3600 + $m*60; # convert w/d/h/m to s + } + $sign2="+"; + if ($s<0) { + $s*=-1; + $sign2="-"; + } + + $m = int($s/60); # convert s to m + $s -= $m*60; + $d = int($m/$len); # convert m to d + $m -= $d*$len; + + # The rest should be fine. + } + $h = $m/60; # convert m to h + $m -= $h*60; + if ($mode == 3 || $mode == 2) { + $w = $w*1; # get around +0 problem + } else { + $w = $d/7; # convert d to w + $d -= $w*7; + } + + $w=0 if ($w eq "-0"); # get around silly -0 problem + $d=0 if ($d eq "-0"); + $h=0 if ($h eq "-0"); + $m=0 if ($m eq "-0"); + $s=0 if ($s eq "-0"); + + # Only include two signs if necessary + $sign1=$sign2 if ($y==0 and $mon==0); + $sign2=$sign1 if ($w==0 and $d==0 and $h==0 and $m==0 and $s==0); + $sign2="" if ($sign1 eq $sign2 and ! $Cnf{"DeltaSigns"}); + + if ($Cnf{"DeltaSigns"}) { + return "$sign1$y:$sign1$mon:$sign2$w:$sign2$d:$sign2$h:$sign2$m:$sign2$s"; + } else { + return "$sign1$y:$mon:$sign2$w:$d:$h:$m:$s"; + } +} + +# This checks a delta to make sure it is valid. If it is, it splits +# it and returns the elements with a sign on each. The 2nd argument +# specifies the default sign. Blank elements are set to 0. If the +# third element is non-nil, exactly 7 elements must be included. +sub Delta_Split { + print "DEBUG: Delta_Split\n" if ($Curr{"Debug"} =~ /trace/); + my($delta,$sign,$exact)=@_; + my(@delta)=split(/:/,$delta); + return () if ($exact and $#delta != 6); + my($i)=(); + $sign="+" if (! defined $sign); + for ($i=0; $i<=$#delta; $i++) { + $delta[$i]="0" if (! $delta[$i]); + return () if ($delta[$i] !~ /^[+-]?\d+$/); + $sign = ($delta[$i] =~ s/^([+-])// ? $1 : $sign); + $delta[$i] = $sign.$delta[$i]; + } + @delta; +} + +# Reads up to 3 arguments. $h may contain the time in any international +# format. Any empty elements are set to 0. +sub Date_ParseTime { + print "DEBUG: Date_ParseTime\n" if ($Curr{"Debug"} =~ /trace/); + my($h,$m,$s)=@_; + my($t)=&CheckTime("one"); + + if (defined $h and $h =~ /$t/) { + $h=$1; + $m=$2; + $s=$3 if (defined $3); + } + $h="00" if (! defined $h); + $m="00" if (! defined $m); + $s="00" if (! defined $s); + + ($h,$m,$s); +} + +# Forms a date with the 6 elements passed in (all of which must be defined). +# No check as to validity is made. +sub Date_Join { + print "DEBUG: Date_Join\n" if ($Curr{"Debug"} =~ /trace/); + foreach (0 .. $#_) { + croak "undefined arg $_ to Date_Join()" if not defined $_[$_]; + } + my($y,$m,$d,$h,$mn,$s)=@_; + my($ym,$md,$dh,$hmn,$mns)=(); + + if ($Cnf{"Internal"} == 0) { + $ym=$md=$dh=""; + $hmn=$mns=":"; + + } elsif ($Cnf{"Internal"} == 1) { + $ym=$md=$dh=$hmn=$mns=""; + + } elsif ($Cnf{"Internal"} == 2) { + $ym=$md="-"; + $dh=" "; + $hmn=$mns=":"; + + } else { + confess "ERROR: Invalid internal format in Date_Join.\n"; + } + $m="0$m" if (length($m)==1); + $d="0$d" if (length($d)==1); + $h="0$h" if (length($h)==1); + $mn="0$mn" if (length($mn)==1); + $s="0$s" if (length($s)==1); + "$y$ym$m$md$d$dh$h$hmn$mn$mns$s"; +} + +# This checks a time. If it is valid, it splits it and returns 3 elements. +# If "one" or "two" is passed in, a regexp with 1/2 or 2 digit hours is +# returned. +sub CheckTime { + print "DEBUG: CheckTime\n" if ($Curr{"Debug"} =~ /trace/); + my($time)=@_; + my($h)='(?:0?[0-9]|1[0-9]|2[0-3])'; + my($h2)='(?:0[0-9]|1[0-9]|2[0-3])'; + my($m)='[0-5][0-9]'; + my($s)=$m; + my($hm)="(?:". $Lang{$Cnf{"Language"}}{"SepHM"} ."|:)"; + my($ms)="(?:". $Lang{$Cnf{"Language"}}{"SepMS"} ."|:)"; + my($ss)=$Lang{$Cnf{"Language"}}{"SepSS"}; + my($t)="^($h)$hm($m)(?:$ms($s)(?:$ss\\d+)?)?\$"; + if ($time eq "one") { + return $t; + } elsif ($time eq "two") { + $t="^($h2)$hm($m)(?:$ms($s)(?:$ss\\d+)?)?\$"; + return $t; + } + + if ($time =~ /$t/i) { + ($h,$m,$s)=($1,$2,$3); + $h="0$h" if (length($h)<2); + $m="0$m" if (length($m)<2); + $s="00" if (! defined $s); + return ($h,$m,$s); + } else { + return (); + } +} + +# This checks a recurrence. If it is valid, it splits it and returns the +# elements. Otherwise, it returns an empty list. +# ($recur0,$recur1,$flags,$dateb,$date0,$date1)=&Recur_Split($recur); +sub Recur_Split { + print "DEBUG: Recur_Split\n" if ($Curr{"Debug"} =~ /trace/); + my($recur)=@_; + my(@ret,@tmp); + + my($R) = '(\*?(?:[-,0-9]+[:\*]){6}[-,0-9]+)'; + my($F) = '(?:\*([^*]*))'; + my($DB,$D0,$D1); + $DB=$D0=$D1=$F; + + if ($recur =~ /^$R$F?$DB?$D0?$D1?$/) { + @ret=($1,$2,$3,$4,$5); + @tmp=split(/\*/,shift(@ret)); + return () if ($#tmp>1); + return (@tmp,"",@ret) if ($#tmp==0); + return (@tmp,@ret); + } + return (); +} + +# This checks a date. If it is valid, it splits it and returns the elements. +# If no date is passed in, it returns a regular expression for the date. +# +# The optional second argument says 'I really expect this to be a +# valid Date::Manip object, please throw an exception if it is +# not'. Otherwise, errors are signalled by returning (). +# +sub Date_Split { + print "DEBUG: Date_Split\n" if ($Curr{"Debug"} =~ /trace/); + my($date, $definitely_valid)=@_; + $definitely_valid = 0 if not defined $definitely_valid; + my($ym,$md,$dh,$hmn,$mns)=(); + my($y)='(\d{4})'; + my($m)='(0[1-9]|1[0-2])'; + my($d)='(0[1-9]|[1-2][0-9]|3[0-1])'; + my($h)='([0-1][0-9]|2[0-3])'; + my($mn)='([0-5][0-9])'; + my($s)=$mn; + + if ($Cnf{"Internal"} == 0) { + $ym=$md=$dh=""; + $hmn=$mns=":"; + + } elsif ($Cnf{"Internal"} == 1) { + $ym=$md=$dh=$hmn=$mns=""; + + } elsif ($Cnf{"Internal"} == 2) { + $ym=$md="-"; + $dh=" "; + $hmn=$mns=":"; + + } else { + confess "ERROR: Invalid internal format in Date_Split.\n"; + } + + my($t)="^$y$ym$m$md$d$dh$h$hmn$mn$mns$s\$"; + + if (not defined $date or $date eq '') { + if ($definitely_valid) { + die "bad date '$date'"; + } else { + return $t; + } + } + + if ($date =~ /$t/) { + ($y,$m,$d,$h,$mn,$s)=($1,$2,$3,$4,$5,$6); + my(@d_in_m)=(0,31,28,31,30,31,30,31,31,30,31,30,31); + $d_in_m[2]=29 if (&Date_LeapYear($y)); + if ($d>$d_in_m[$m]) { + my $msg = "invalid date $date: day $d of month $m, but only $d_in_m[$m] days in that month"; + if ($definitely_valid) { + die $msg; + } + else { + warn $msg; + return (); + } + } + return ($y,$m,$d,$h,$mn,$s); + } + + if ($definitely_valid) { + die "invalid date $date: doesn't match regexp $t"; + } + return (); +} + +# This returns the date easter occurs on for a given year as ($month,$day). +# This is from the Calendar FAQ. +sub Date_Easter { + my($y)=@_; + $y=&Date_FixYear($y) if (length($y)==2); + + my($c) = $y/100; + my($g) = $y % 19; + my($k) = ($c-17)/25; + my($i) = ($c - $c/4 - ($c-$k)/3 + 19*$g + 15) % 30; + $i = $i - ($i/28)*(1 - ($i/28)*(29/($i+1))*((21-$g)/11)); + my($j) = ($y + $y/4 + $i + 2 - $c + $c/4) % 7; + my($l) = $i-$j; + my($m) = 3 + ($l+40)/44; + my($d) = $l + 28 - 31*($m/4); + return ($m,$d); +} + +# This takes a list of years, months, WeekOfMonth's, and optionally +# DayOfWeek's, and returns a list of dates. Optionally, a list of dates +# can be passed in as the 1st argument (with the 2nd argument the null list) +# and the year/month of these will be used. +# +# If $FDn is non-zero, the first week of the month contains the first +# occurence of this day (1=Monday). If $FIn is non-zero, the first week of +# the month contains the date (i.e. $FIn'th day of the month). +sub Date_Recur_WoM { + my($y,$m,$w,$d,$FDn,$FIn)=@_; + my(@y)=@$y; + my(@m)=@$m; + my(@w)=@$w; + my(@d)=@$d; + my($date0,$date1,@tmp,@date,$d0,$d1,@tmp2)=(); + + if (@m) { + @tmp=(); + foreach $y (@y) { + return () if (length($y)==1 || length($y)==3 || ! &IsInt($y,0,9999)); + $y=&Date_FixYear($y) if (length($y)==2); + push(@tmp,$y); + } + @y=sort { $a<=>$b } (@tmp); + + return () if (! @m); + foreach $m (@m) { + return () if (! &IsInt($m,1,12)); + } + @m=sort { $a<=>$b } (@m); + + @tmp=@tmp2=(); + foreach $y (@y) { + foreach $m (@m) { + push(@tmp,$y); + push(@tmp2,$m); + } + } + + @y=@tmp; + @m=@tmp2; + + } else { + foreach $d0 (@y) { + @tmp=&Date_Split($d0); + return () if (! @tmp); + push(@tmp2,$tmp[0]); + push(@m,$tmp[1]); + } + @y=@tmp2; + } + + return () if (! @w); + foreach $w (@w) { + return () if ($w==0 || ! &IsInt($w,-5,5)); + } + + if (@d) { + foreach $d (@d) { + return () if (! &IsInt($d,1,7)); + } + @d=sort { $a<=>$b } (@d); + } + + @date=(); + foreach $y (@y) { + $m=shift(@m); + + # Find 1st day of this month and next month + $date0=&Date_Join($y,$m,1,0,0,0); + $date1=&DateCalc($date0,"+0:1:0:0:0:0:0"); + + if (@d) { + foreach $d (@d) { + # Find 1st occurence of DOW (in both months) + $d0=&Date_GetNext($date0,$d,1); + $d1=&Date_GetNext($date1,$d,1); + + @tmp=(); + while (&Date_Cmp($d0,$d1)<0) { + push(@tmp,$d0); + $d0=&DateCalc($d0,"+0:0:1:0:0:0:0"); + } + + @tmp2=(); + foreach $w (@w) { + if ($w>0) { + push(@tmp2,$tmp[$w-1]); + } else { + push(@tmp2,$tmp[$#tmp+1+$w]); + } + } + @tmp2=sort(@tmp2); + push(@date,@tmp2); + } + + } else { + # Find 1st day of 1st week + if ($FDn != 0) { + $date0=&Date_GetNext($date0,$FDn,1); + } else { + $date0=&Date_Join($y,$m,$FIn,0,0,0); + } + $date0=&Date_GetPrev($date0,$Cnf{"FirstDay"},1); + + # Find 1st day of 1st week of next month + if ($FDn != 0) { + $date1=&Date_GetNext($date1,$FDn,1); + } else { + $date1=&DateCalc($date1,"+0:0:0:".($FIn-1).":0:0:0") if ($FIn>1); + } + $date1=&Date_GetPrev($date1,$Cnf{"FirstDay"},1); + + @tmp=(); + while (&Date_Cmp($date0,$date1)<0) { + push(@tmp,$date0); + $date0=&DateCalc($date0,"+0:0:1:0:0:0:0"); + } + + @tmp2=(); + foreach $w (@w) { + if ($w>0) { + push(@tmp2,$tmp[$w-1]); + } else { + push(@tmp2,$tmp[$#tmp+1+$w]); + } + } + @tmp2=sort(@tmp2); + push(@date,@tmp2); + } + } + + @date; +} + +# This returns a sorted list of dates formed by adding/subtracting +# $delta to $dateb in the range $date0<=$d<$dateb. The first date int +# the list is actually the first date<$date0 and the last date in the +# list is the first date>=$date1 (because sometimes the set part will +# move the date back into the range). +sub Date_Recur { + my($date0,$date1,$dateb,$delta)=@_; + my(@ret,$d)=(); + + while (&Date_Cmp($dateb,$date0)<0) { + $dateb=&DateCalc_DateDelta($dateb,$delta); + } + while (&Date_Cmp($dateb,$date1)>=0) { + $dateb=&DateCalc_DateDelta($dateb,"-$delta"); + } + + # Add the dates $date0..$dateb + $d=$dateb; + while (&Date_Cmp($d,$date0)>=0) { + unshift(@ret,$d); + $d=&DateCalc_DateDelta($d,"-$delta"); + } + # Add the first date earler than the range + unshift(@ret,$d); + + # Add the dates $dateb..$date1 + $d=&DateCalc_DateDelta($dateb,$delta); + while (&Date_Cmp($d,$date1)<0) { + push(@ret,$d); + $d=&DateCalc_DateDelta($d,$delta); + } + # Add the first date later than the range + push(@ret,$d); + + @ret; +} + +# This sets the values in each date of a recurrence. +# +# $h,$m,$s can each be values or lists "1-2,4". If any are equal to "-1", +# they are not set (and none of the larger elements are set). +sub Date_RecurSetTime { + my($date0,$date1,$dates,$h,$m,$s)=@_; + my(@dates)=@$dates; + my(@h,@m,@s,$date,@tmp)=(); + + $m="-1" if ($s eq "-1"); + $h="-1" if ($m eq "-1"); + + if ($h ne "-1") { + @h=&ReturnList($h); + return () if ! (@h); + @h=sort { $a<=>$b } (@h); + + @tmp=(); + foreach $date (@dates) { + foreach $h (@h) { + push(@tmp,&Date_SetDateField($date,"h",$h,1)); + } + } + @dates=@tmp; + } + + if ($m ne "-1") { + @m=&ReturnList($m); + return () if ! (@m); + @m=sort { $a<=>$b } (@m); + + @tmp=(); + foreach $date (@dates) { + foreach $m (@m) { + push(@tmp,&Date_SetDateField($date,"mn",$m,1)); + } + } + @dates=@tmp; + } + + if ($s ne "-1") { + @s=&ReturnList($s); + return () if ! (@s); + @s=sort { $a<=>$b } (@s); + + @tmp=(); + foreach $date (@dates) { + foreach $s (@s) { + push(@tmp,&Date_SetDateField($date,"s",$s,1)); + } + } + @dates=@tmp; + } + + @tmp=(); + foreach $date (@dates) { + push(@tmp,$date) if (&Date_Cmp($date,$date0)>=0 && + &Date_Cmp($date,$date1)<0 && + &Date_Split($date)); + } + + @tmp; +} + +sub DateCalc_DateDate { + print "DEBUG: DateCalc_DateDate\n" if ($Curr{"Debug"} =~ /trace/); + my($D1,$D2,$mode)=@_; + my(@d_in_m)=(0,31,28,31,30,31,30,31,31,30,31,30,31); + $mode=0 if (! defined $mode); + + # Exact mode + if ($mode==0) { + my($y1,$m1,$d1,$h1,$mn1,$s1)=&Date_Split($D1, 1); + my($y2,$m2,$d2,$h2,$mn2,$s2)=&Date_Split($D2, 1); + my($i,@delta,$d,$delta,$y)=(); + + # form the delta for hour/min/sec + $delta[4]=$h2-$h1; + $delta[5]=$mn2-$mn1; + $delta[6]=$s2-$s1; + + # form the delta for yr/mon/day + $delta[0]=$delta[1]=0; + $d=0; + if ($y2>$y1) { + $d=&Date_DaysInYear($y1) - &Date_DayOfYear($m1,$d1,$y1); + $d+=&Date_DayOfYear($m2,$d2,$y2); + for ($y=$y1+1; $y<$y2; $y++) { + $d+= &Date_DaysInYear($y); + } + } elsif ($y2<$y1) { + $d=&Date_DaysInYear($y2) - &Date_DayOfYear($m2,$d2,$y2); + $d+=&Date_DayOfYear($m1,$d1,$y1); + for ($y=$y2+1; $y<$y1; $y++) { + $d+= &Date_DaysInYear($y); + } + $d *= -1; + } else { + $d=&Date_DayOfYear($m2,$d2,$y2) - &Date_DayOfYear($m1,$d1,$y1); + } + $delta[2]=0; + $delta[3]=$d; + + for ($i=0; $i<7; $i++) { + $delta[$i]="+".$delta[$i] if ($delta[$i]>=0); + } + + $delta=join(":",@delta); + $delta=&Delta_Normalize($delta,0); + return $delta; + } + + my($date1,$date2)=($D1,$D2); + my($tmp,$sign,$err,@tmp)=(); + + # make sure both are work days + if ($mode==2 || $mode==3) { + $date1=&Date_NextWorkDay($date1,0,1); + $date2=&Date_NextWorkDay($date2,0,1); + } + + # make sure date1 comes before date2 + if (&Date_Cmp($date1,$date2)>0) { + $sign="-"; + $tmp=$date1; + $date1=$date2; + $date2=$tmp; + } else { + $sign="+"; + } + if (&Date_Cmp($date1,$date2)==0) { + return "+0:+0:+0:+0:+0:+0:+0" if ($Cnf{"DeltaSigns"}); + return "+0:0:0:0:0:0:0"; + } + + my($y1,$m1,$d1,$h1,$mn1,$s1)=&Date_Split($date1, 1); + my($y2,$m2,$d2,$h2,$mn2,$s2)=&Date_Split($date2, 1); + my($dy,$dm,$dw,$dd,$dh,$dmn,$ds,$ddd)=(0,0,0,0,0,0,0,0); + + if ($mode != 3) { + + # Do years + $dy=$y2-$y1; + $dm=0; + if ($dy>0) { + $tmp=&DateCalc_DateDelta($date1,"+$dy:0:0:0:0:0:0",\$err,0); + if (&Date_Cmp($tmp,$date2)>0) { + $dy--; + $tmp=$date1; + $tmp=&DateCalc_DateDelta($date1,"+$dy:0:0:0:0:0:0",\$err,0) + if ($dy>0); + $dm=12; + } + $date1=$tmp; + } + + # Do months + $dm+=$m2-$m1; + if ($dm>0) { + $tmp=&DateCalc_DateDelta($date1,"+0:$dm:0:0:0:0:0",\$err,0); + if (&Date_Cmp($tmp,$date2)>0) { + $dm--; + $tmp=$date1; + $tmp=&DateCalc_DateDelta($date1,"+0:$dm:0:0:0:0:0",\$err,0) + if ($dm>0); + } + $date1=$tmp; + } + + # At this point, check to see that we're on a business day again so that + # Aug 3 (Monday) -> Sep 3 (Sunday) -> Sep 4 (Monday) = 1 month + if ($mode==2) { + if (! &Date_IsWorkDay($date1,0)) { + $date1=&Date_NextWorkDay($date1,0,1); + } + } + } + + # Do days + if ($mode==2 || $mode==3) { + $dd=0; + while (1) { + $tmp=&Date_NextWorkDay($date1,1,1); + if (&Date_Cmp($tmp,$date2)<=0) { + $dd++; + $date1=$tmp; + } else { + last; + } + } + + } else { + ($y1,$m1,$d1)=( &Date_Split($date1, 1) )[0..2]; + $dd=0; + # If we're jumping across months, set $d1 to the first of the next month + # (or possibly the 0th of next month which is equivalent to the last day + # of this month) + if ($m1!=$m2) { + $d_in_m[2]=29 if (&Date_LeapYear($y1)); + $dd=$d_in_m[$m1]-$d1+1; + $d1=1; + $tmp=&DateCalc_DateDelta($date1,"+0:0:0:$dd:0:0:0",\$err,0); + if (&Date_Cmp($tmp,$date2)>0) { + $dd--; + $d1--; + $tmp=&DateCalc_DateDelta($date1,"+0:0:0:$dd:0:0:0",\$err,0); + } + $date1=$tmp; + } + + $ddd=0; + if ($d1<$d2) { + $ddd=$d2-$d1; + $tmp=&DateCalc_DateDelta($date1,"+0:0:0:$ddd:0:0:0",\$err,0); + if (&Date_Cmp($tmp,$date2)>0) { + $ddd--; + $tmp=&DateCalc_DateDelta($date1,"+0:0:0:$ddd:0:0:0",\$err,0); + } + $date1=$tmp; + } + $dd+=$ddd; + } + + # in business mode, make sure h1 comes before h2 (if not find delta between + # now and end of day and move to start of next business day) + $d1=( &Date_Split($date1, 1) )[2]; + $dh=$dmn=$ds=0; + if ($mode==2 || $mode==3 and $d1 != $d2) { + $tmp=&Date_SetTime($date1,$Cnf{"WorkDayEnd"}); + $tmp=&DateCalc_DateDelta($tmp,"+0:0:0:0:0:1:0") + if ($Cnf{"WorkDay24Hr"}); + $tmp=&DateCalc_DateDate($date1,$tmp,0); + ($tmp,$tmp,$tmp,$tmp,$dh,$dmn,$ds)=&Delta_Split($tmp); + $date1=&Date_NextWorkDay($date1,1,0); + $date1=&Date_SetTime($date1,$Cnf{"WorkDayBeg"}); + $d1=( &Date_Split($date1, 1) )[2]; + confess "ERROR: DateCalc DateDate Business.\n" if ($d1 != $d2); + } + + # Hours, minutes, seconds + $tmp=&DateCalc_DateDate($date1,$date2,0); + @tmp=&Delta_Split($tmp); + $dh += $tmp[4]; + $dmn += $tmp[5]; + $ds += $tmp[6]; + + $tmp="$sign$dy:$dm:0:$dd:$dh:$dmn:$ds"; + &Delta_Normalize($tmp,$mode); +} + +sub DateCalc_DeltaDelta { + print "DEBUG: DateCalc_DeltaDelta\n" if ($Curr{"Debug"} =~ /trace/); + my($D1,$D2,$mode)=@_; + my(@delta1,@delta2,$i,$delta,@delta)=(); + $mode=0 if (! defined $mode); + + @delta1=&Delta_Split($D1); + @delta2=&Delta_Split($D2); + for ($i=0; $i<7; $i++) { + $delta[$i]=$delta1[$i]+$delta2[$i]; + $delta[$i]="+".$delta[$i] if ($delta[$i]>=0); + } + + $delta=join(":",@delta); + $delta=&Delta_Normalize($delta,$mode); + return $delta; +} + +sub DateCalc_DateDelta { + print "DEBUG: DateCalc_DateDelta\n" if ($Curr{"Debug"} =~ /trace/); + my($D1,$D2,$errref,$mode)=@_; + my($date)=(); + my(@d_in_m)=(0,31,28,31,30,31,30,31,31,30,31,30,31); + my($h1,$m1,$h2,$m2,$len,$hh,$mm)=(); + $mode=0 if (! defined $mode); + + if ($mode==2 || $mode==3) { + $h1=$Curr{"WDBh"}; + $m1=$Curr{"WDBm"}; + $h2=$Curr{"WDEh"}; + $m2=$Curr{"WDEm"}; + $hh=$h2-$h1; + $mm=$m2-$m1; + if ($mm<0) { + $hh--; + $mm+=60; + } + } + + # Date, delta + my($y,$m,$d,$h,$mn,$s)=&Date_Split($D1, 1); + my($dy,$dm,$dw,$dd,$dh,$dmn,$ds)=&Delta_Split($D2); + + # do the month/year part + $y+=$dy; + while (length($y)<4) { + $y = "0$y"; + } + &ModuloAddition(-12,$dm,\$m,\$y); # -12 means 1-12 instead of 0-11 + $d_in_m[2]=29 if (&Date_LeapYear($y)); + + # if we have gone past the last day of a month, move the date back to + # the last day of the month + if ($d>$d_in_m[$m]) { + $d=$d_in_m[$m]; + } + + # do the week part + if ($mode==0 || $mode==1) { + $dd += $dw*7; + } else { + $date=&DateCalc_DateDelta(&Date_Join($y,$m,$d,$h,$mn,$s), + "+0:0:$dw:0:0:0:0",0); + ($y,$m,$d,$h,$mn,$s)=&Date_Split($date, 1); + } + + # in business mode, set the day to a work day at this point so the h/mn/s + # stuff will work out + if ($mode==2 || $mode==3) { + $d=$d_in_m[$m] if ($d>$d_in_m[$m]); + $date=&Date_NextWorkDay(&Date_Join($y,$m,$d,$h,$mn,$s),0,1); + ($y,$m,$d,$h,$mn,$s)=&Date_Split($date, 1); + } + + # seconds, minutes, hours + &ModuloAddition(60,$ds,\$s,\$mn); + if ($mode==2 || $mode==3) { + while (1) { + &ModuloAddition(60,$dmn,\$mn,\$h); + $h+= $dh; + + if ($h>$h2 or $h==$h2 && $mn>$m2) { + $dh=$h-$h2; + $dmn=$mn-$m2; + $h=$h1; + $mn=$m1; + $dd++; + + } elsif ($h<$h1 or $h==$h1 && $mn<$m1) { + $dh=$h-$h1; + $dmn=$m1-$mn; + $h=$h2; + $mn=$m2; + $dd--; + + } elsif ($h==$h2 && $mn==$m2) { + $dd++; + $dh=-$hh; + $dmn=-$mm; + + } else { + last; + } + } + + } else { + &ModuloAddition(60,$dmn,\$mn,\$h); + &ModuloAddition(24,$dh,\$h,\$d); + } + + # If we have just gone past the last day of the month, we need to make + # up for this: + if ($d>$d_in_m[$m]) { + $dd+= $d-$d_in_m[$m]; + $d=$d_in_m[$m]; + } + + # days + if ($mode==2 || $mode==3) { + if ($dd>=0) { + $date=&Date_NextWorkDay(&Date_Join($y,$m,$d,$h,$mn,$s),$dd,1); + } else { + $date=&Date_PrevWorkDay(&Date_Join($y,$m,$d,$h,$mn,$s),-$dd,1); + } + ($y,$m,$d,$h,$mn,$s)=&Date_Split($date, 1); + + } else { + $d_in_m[2]=29 if (&Date_LeapYear($y)); + $d=$d_in_m[$m] if ($d>$d_in_m[$m]); + $d += $dd; + while ($d<1) { + $m--; + if ($m==0) { + $m=12; + $y--; + if (&Date_LeapYear($y)) { + $d_in_m[2]=29; + } else { + $d_in_m[2]=28; + } + } + $d += $d_in_m[$m]; + } + while ($d>$d_in_m[$m]) { + $d -= $d_in_m[$m]; + $m++; + if ($m==13) { + $m=1; + $y++; + if (&Date_LeapYear($y)) { + $d_in_m[2]=29; + } else { + $d_in_m[2]=28; + } + } + } + } + + if ($y<0 or $y>9999) { + $$errref=3; + return; + } + &Date_Join($y,$m,$d,$h,$mn,$s); +} + +sub Date_UpdateHolidays { + print "DEBUG: Date_UpdateHolidays\n" if ($Curr{"Debug"} =~ /trace/); + my($year)=@_; + $Holiday{"year"}=$year; + $Holiday{"dates"}{$year}={}; + + my($date,$delta,$err)=(); + my($key,@tmp,$tmp); + + foreach $key (keys %{ $Holiday{"desc"} }) { + @tmp=&Recur_Split($key); + if (@tmp) { + $tmp=&ParseDateString("${year}010100:00:00"); + ($date)=&ParseRecur($key,$tmp,$tmp,($year+1)."-01-01"); + next if (! $date); + + } elsif ($key =~ /^(.*)([+-].*)$/) { + # Date +/- Delta + ($date,$delta)=($1,$2); + $tmp=&ParseDateString("$date $year"); + if ($tmp) { + $date=$tmp; + } else { + $date=&ParseDateString($date); + next if ($date !~ /^$year/); + } + $date=&DateCalc($date,$delta,\$err,0); + + } else { + # Date + $date=$key; + $tmp=&ParseDateString("$date $year"); + if ($tmp) { + $date=$tmp; + } else { + $date=&ParseDateString($date); + next if ($date !~ /^$year/); + } + } + $Holiday{"dates"}{$year}{$date}=$Holiday{"desc"}{$key}; + } +} + +# This sets a Date::Manip config variable. +sub Date_SetConfigVariable { + print "DEBUG: Date_SetConfigVariable\n" if ($Curr{"Debug"} =~ /trace/); + my($var,$val)=@_; + + # These are most appropriate for command line options instead of in files. + $Cnf{"PathSep"}=$val, return if ($var =~ /^PathSep$/i); + $Cnf{"PersonalCnf"}=$val, return if ($var =~ /^PersonalCnf$/i); + $Cnf{"PersonalCnfPath"}=$val, return if ($var =~ /^PersonalCnfPath$/i); + &EraseHolidays(), return if ($var =~ /^EraseHolidays$/i); + $Cnf{"IgnoreGlobalCnf"}=1, return if ($var =~ /^IgnoreGlobalCnf$/i); + $Cnf{"GlobalCnf"}=$val, return if ($var =~ /^GlobalCnf$/i); + + $Curr{"InitLang"}=1, + $Cnf{"Language"}=$val, return if ($var =~ /^Language$/i); + $Cnf{"DateFormat"}=$val, return if ($var =~ /^DateFormat$/i); + $Cnf{"TZ"}=$val, return if ($var =~ /^TZ$/i); + $Cnf{"ConvTZ"}=$val, return if ($var =~ /^ConvTZ$/i); + $Cnf{"Internal"}=$val, return if ($var =~ /^Internal$/i); + $Cnf{"FirstDay"}=$val, return if ($var =~ /^FirstDay$/i); + $Cnf{"WorkWeekBeg"}=$val, return if ($var =~ /^WorkWeekBeg$/i); + $Cnf{"WorkWeekEnd"}=$val, return if ($var =~ /^WorkWeekEnd$/i); + $Cnf{"WorkDayBeg"}=$val, + $Curr{"ResetWorkDay"}=1, return if ($var =~ /^WorkDayBeg$/i); + $Cnf{"WorkDayEnd"}=$val, + $Curr{"ResetWorkDay"}=1, return if ($var =~ /^WorkDayEnd$/i); + $Cnf{"WorkDay24Hr"}=$val, + $Curr{"ResetWorkDay"}=1, return if ($var =~ /^WorkDay24Hr$/i); + $Cnf{"DeltaSigns"}=$val, return if ($var =~ /^DeltaSigns$/i); + $Cnf{"Jan1Week1"}=$val, return if ($var =~ /^Jan1Week1$/i); + $Cnf{"YYtoYYYY"}=$val, return if ($var =~ /^YYtoYYYY$/i); + $Cnf{"UpdateCurrTZ"}=$val, return if ($var =~ /^UpdateCurrTZ$/i); + $Cnf{"IntCharSet"}=$val, return if ($var =~ /^IntCharSet$/i); + $Curr{"DebugVal"}=$val, return if ($var =~ /^Debug$/i); + $Cnf{"TomorrowFirst"}=$val, return if ($var =~ /^TomorrowFirst$/i); + $Cnf{"ForceDate"}=$val, return if ($var =~ /^ForceDate$/i); + + confess "ERROR: Unknown configuration variable $var in Date::Manip.\n"; +} + +sub EraseHolidays { + print "DEBUG: EraseHolidays\n" if ($Curr{"Debug"} =~ /trace/); + + $Cnf{"EraseHolidays"}=0; + delete $Holiday{"list"}; + $Holiday{"list"}={}; + delete $Holiday{"desc"}; + $Holiday{"desc"}={}; + $Holiday{"dates"}={}; +} + +# This returns a pointer to a list of times and events in the format +# [ date, [ events ], date, [ events ], ... ] +# where each list of events are events that are in effect at the date +# immediately preceding the list. +# +# This takes either one date or two dates as arguments. +sub Events_Calc { + print "DEBUG: Events_Calc\n" if ($Curr{"Debug"} =~ /trace/); + + my($date0,$date1)=@_; + + my($tmp); + $date0=&ParseDateString($date0); + return undef if (! $date0); + if ($date1) { + $date1=&ParseDateString($date1); + if (&Date_Cmp($date0,$date1)>0) { + $tmp=$date1; + $date1=$date0; + $date0=$tmp; + } + } else { + $date1=&DateCalc_DateDelta($date0,"+0:0:0:0:0:0:1"); + } + + # + # [ d0,d1,del,name ] => [ d0, d1+del ) + # [ d0,0,del,name ] => [ d0, d0+del ) + # + my(%ret,$d0,$d1,$del,$name,$c0,$c1); + my(@tmp)=@{ $Events{"dates"} }; + DATE: while (@tmp) { + ($d0,$d1,$del,$name)=splice(@tmp,0,4); + $d0=&ParseDateString($d0); + $d1=&ParseDateString($d1) if ($d1); + $del=&ParseDateDelta($del) if ($del); + if ($d1) { + if ($del) { + $d1=&DateCalc_DateDelta($d1,$del); + } + } else { + $d1=&DateCalc_DateDelta($d0,$del); + } + if (&Date_Cmp($d0,$d1)>0) { + $tmp=$d1; + $d1=$d0; + $d0=$tmp; + } + # [ date0,date1 ) + # [ d0,d1 ) OR [ d0,d1 ) + next DATE if (&Date_Cmp($d1,$date0)<=0 || + &Date_Cmp($d0,$date1)>=0); + # [ date0,date1 ) + # [ d0,d1 ) + # [ d0, d1 ) + if (&Date_Cmp($d0,$date0)<=0) { + push @{ $ret{$date0} },$name; + push @{ $ret{$d1} },"!$name" if (&Date_Cmp($d1,$date1)<0); + next DATE; + } + # [ date0,date1 ) + # [ d0,d1 ) + if (&Date_Cmp($d1,$date1)>=0) { + push @{ $ret{$d0} },$name; + next DATE; + } + # [ date0,date1 ) + # [ d0,d1 ) + push @{ $ret{$d0} },$name; + push @{ $ret{$d1} },"!$name"; + } + + # + # [ recur,delta0,delta1,name ] => [ {date-delta0},{date+delta1} ) + # + my($rec,$del0,$del1,@d); + @tmp=@{ $Events{"recur"} }; + RECUR: while (@tmp) { + ($rec,$del0,$del1,$name)=splice(@tmp,0,4); + @d=(); + + } + + # Sort them AND take into account the "!$name" entries. + my(%tmp,$date,@tmp2,@ret); + @d=sort { &Date_Cmp($a,$b) } keys %ret; + foreach $date (@d) { + @tmp=@{ $ret{$date} }; + @tmp2=(); + foreach $tmp (@tmp) { + push(@tmp2,$tmp), next if ($tmp =~ /^!/); + $tmp{$tmp}=1; + } + foreach $tmp (@tmp2) { + $tmp =~ s/^!//; + delete $tmp{$tmp}; + } + push(@ret,$date,[ keys %tmp ]); + } + + return \@ret; +} + +# This parses the raw events list +sub Events_ParseRaw { + print "DEBUG: Events_ParseRaw\n" if ($Curr{"Debug"} =~ /trace/); + + # Only need to be parsed once + my($force)=@_; + $Events{"parsed"}=0 if ($force); + return if ($Events{"parsed"}); + $Events{"parsed"}=1; + + my(@events)=@{ $Events{"raw"} }; + my($event,$name,@event,$date0,$date1,$tmp,$delta,$recur0,$recur1,@recur,$r, + $recur); + EVENT: while (@events) { + ($event,$name)=splice(@events,0,2); + @event=split(/\s*;\s*/,$event); + + if ($#event == 0) { + + if ($date0=&ParseDateString($event[0])) { + # + # date = event + # + $tmp=&ParseDateString("$event[0] 00:00:00"); + if ($tmp && $tmp eq $date0) { + $delta="+0:0:0:1:0:0:0"; + } else { + $delta="+0:0:0:0:1:0:0"; + } + push @{ $Events{"dates"} },($date0,0,$delta,$name); + + } elsif ($recur=&ParseRecur($event[0])) { + # + # recur = event + # + ($recur0,$recur1)=&Recur_Split($recur); + if ($recur0) { + if ($recur1) { + $r="$recur0:$recur1"; + } else { + $r=$recur0; + } + } else { + $r=$recur1; + } + (@recur)=split(/:/,$r); + if (pop(@recur)==0 && pop(@recur)==0 && pop(@recur)==0) { + $delta="+0:0:0:1:0:0:0"; + } else { + $delta="+0:0:0:0:1:0:0"; + } + push @{ $Events{"recur"} },($recur,0,$delta,$name); + + } else { + # ??? = event + warn "WARNING: illegal event ignored [ @event ]\n"; + next EVENT; + } + + } elsif ($#event == 1) { + + if ($date0=&ParseDateString($event[0])) { + + if ($date1=&ParseDateString($event[1])) { + # + # date ; date = event + # + $tmp=&ParseDateString("$event[1] 00:00:00"); + if ($tmp && $tmp eq $date1) { + $date1=&DateCalc_DateDelta($date1,"+0:0:0:1:0:0:0"); + } + push @{ $Events{"dates"} },($date0,$date1,0,$name); + + } elsif ($delta=&ParseDateDelta($event[1])) { + # + # date ; delta = event + # + push @{ $Events{"dates"} },($date0,0,$delta,$name); + + } else { + # date ; ??? = event + warn "WARNING: illegal event ignored [ @event ]\n"; + next EVENT; + } + + } elsif ($recur=&ParseRecur($event[0])) { + + if ($delta=&ParseDateDelta($event[1])) { + # + # recur ; delta = event + # + push @{ $Events{"recur"} },($recur,0,$delta,$name); + + } else { + # recur ; ??? = event + warn "WARNING: illegal event ignored [ @event ]\n"; + next EVENT; + } + + } else { + # ??? ; ??? = event + warn "WARNING: illegal event ignored [ @event ]\n"; + next EVENT; + } + + } else { + # date ; delta0 ; delta1 = event + # recur ; delta0 ; delta1 = event + # ??? ; ??? ; ??? ... = event + warn "WARNING: illegal event ignored [ @event ]\n"; + next EVENT; + } + } +} + +# This reads an init file. +sub Date_InitFile { + print "DEBUG: Date_InitFile\n" if ($Curr{"Debug"} =~ /trace/); + my($file)=@_; + my($in)=new IO::File; + local($_)=(); + my($section)="vars"; + my($var,$val,$recur,$name)=(); + + $in->open($file) || return; + while(defined ($_=<$in>)) { + chomp; + s/^\s+//; + s/\s+$//; + next if (! $_ or /^\#/); + + if (/^\*holiday/i) { + $section="holiday"; + &EraseHolidays() if ($section =~ /holiday/i && $Cnf{"EraseHolidays"}); + next; + } elsif (/^\*events/i) { + $section="events"; + next; + } + + if ($section =~ /var/i) { + confess "ERROR: invalid Date::Manip config file line.\n $_\n" + if (! /(.*\S)\s*=\s*(.*)$/); + ($var,$val)=($1,$2); + &Date_SetConfigVariable($var,$val); + + } elsif ($section =~ /holiday/i) { + confess "ERROR: invalid Date::Manip config file line.\n $_\n" + if (! /(.*\S)\s*=\s*(.*)$/); + ($recur,$name)=($1,$2); + $name="" if (! defined $name); + $Holiday{"desc"}{$recur}=$name; + + } elsif ($section =~ /events/i) { + confess "ERROR: invalid Date::Manip config file line.\n $_\n" + if (! /(.*\S)\s*=\s*(.*)$/); + ($val,$var)=($1,$2); + push @{ $Events{"raw"} },($val,$var); + + } else { + # A section not currently used by Date::Manip (but may be + # used by some extension to it). + next; + } + } + close($in); +} + +# $flag=&Date_TimeCheck(\$h,\$mn,\$s,\$ampm); +# Returns 1 if any of the fields are bad. All fields are optional, and +# all possible checks are done on the data. If a field is not passed in, +# it is set to default values. If data is missing, appropriate defaults +# are supplied. +sub Date_TimeCheck { + print "DEBUG: Date_TimeCheck\n" if ($Curr{"Debug"} =~ /trace/); + my($h,$mn,$s,$ampm)=@_; + my($tmp1,$tmp2,$tmp3)=(); + + $$h="" if (! defined $$h); + $$mn="" if (! defined $$mn); + $$s="" if (! defined $$s); + $$ampm="" if (! defined $$ampm); + $$ampm=uc($$ampm) if ($$ampm); + + # Check hour + $tmp1=$Lang{$Cnf{"Language"}}{"AmPm"}; + $tmp2=""; + if ($$ampm =~ /^$tmp1$/i) { + $tmp3=$Lang{$Cnf{"Language"}}{"AM"}; + $tmp2="AM" if ($$ampm =~ /^$tmp3$/i); + $tmp3=$Lang{$Cnf{"Language"}}{"PM"}; + $tmp2="PM" if ($$ampm =~ /^$tmp3$/i); + } elsif ($$ampm) { + return 1; + } + if ($tmp2 eq "AM" || $tmp2 eq "PM") { + $$h="0$$h" if (length($$h)==1); + return 1 if ($$h<1 || $$h>12); + $$h="00" if ($tmp2 eq "AM" and $$h==12); + $$h += 12 if ($tmp2 eq "PM" and $$h!=12); + } else { + $$h="00" if ($$h eq ""); + $$h="0$$h" if (length($$h)==1); + return 1 if (! &IsInt($$h,0,23)); + $tmp2="AM" if ($$h<12); + $tmp2="PM" if ($$h>=12); + } + $$ampm=$Lang{$Cnf{"Language"}}{"AMstr"}; + $$ampm=$Lang{$Cnf{"Language"}}{"PMstr"} if ($tmp2 eq "PM"); + + # Check minutes + $$mn="00" if ($$mn eq ""); + $$mn="0$$mn" if (length($$mn)==1); + return 1 if (! &IsInt($$mn,0,59)); + + # Check seconds + $$s="00" if ($$s eq ""); + $$s="0$$s" if (length($$s)==1); + return 1 if (! &IsInt($$s,0,59)); + + return 0; +} + +# $flag=&Date_DateCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk); +# Returns 1 if any of the fields are bad. All fields are optional, and +# all possible checks are done on the data. If a field is not passed in, +# it is set to default values. If data is missing, appropriate defaults +# are supplied. +# +# If the flag UpdateHolidays is set, the year is set to +# CurrHolidayYear. +sub Date_DateCheck { + print "DEBUG: Date_DateCheck\n" if ($Curr{"Debug"} =~ /trace/); + my($y,$m,$d,$h,$mn,$s,$ampm,$wk)=@_; + my($tmp1,$tmp2,$tmp3)=(); + + my(@d_in_m)=(0,31,28,31,30,31,30,31,31,30,31,30,31); + my($curr_y)=$Curr{"Y"}; + my($curr_m)=$Curr{"M"}; + my($curr_d)=$Curr{"D"}; + $$m=1, $$d=1 if (defined $$y and ! defined $$m and ! defined $$d); + $$y="" if (! defined $$y); + $$m="" if (! defined $$m); + $$d="" if (! defined $$d); + $$wk="" if (! defined $$wk); + $$d=$curr_d if ($$y eq "" and $$m eq "" and $$d eq ""); + + # Check year. + $$y=$curr_y if ($$y eq ""); + $$y=&Date_FixYear($$y) if (length($$y)<4); + return 1 if (! &IsInt($$y,0,9999)); + $d_in_m[2]=29 if (&Date_LeapYear($$y)); + + # Check month + $$m=$curr_m if ($$m eq ""); + $$m=$Lang{$Cnf{"Language"}}{"MonthH"}{lc($$m)} + if (exists $Lang{$Cnf{"Language"}}{"MonthH"}{lc($$m)}); + $$m="0$$m" if (length($$m)==1); + return 1 if (! &IsInt($$m,1,12)); + + # Check day + $$d="01" if ($$d eq ""); + $$d="0$$d" if (length($$d)==1); + return 1 if (! &IsInt($$d,1,$d_in_m[$$m])); + if ($$wk) { + $tmp1=&Date_DayOfWeek($$m,$$d,$$y); + $tmp2=$Lang{$Cnf{"Language"}}{"WeekH"}{lc($$wk)} + if (exists $Lang{$Cnf{"Language"}}{"WeekH"}{lc($$wk)}); + return 1 if ($tmp1 != $tmp2); + } + + return &Date_TimeCheck($h,$mn,$s,$ampm); +} + +# Takes a year in 2 digit form and returns it in 4 digit form +sub Date_FixYear { + print "DEBUG: Date_FixYear\n" if ($Curr{"Debug"} =~ /trace/); + my($y)=@_; + my($curr_y)=$Curr{"Y"}; + $y=$curr_y if (! defined $y or ! $y); + return $y if (length($y)==4); + confess "ERROR: Invalid year ($y)\n" if (length($y)!=2); + my($y1,$y2)=(); + + if (lc($Cnf{"YYtoYYYY"}) eq "c") { + $y1=substring($y,0,2); + $y="$y1$y"; + + } elsif ($Cnf{"YYtoYYYY"} =~ /^c(\d{2})$/i) { + $y1=$1; + $y="$y1$y"; + + } elsif ($Cnf{"YYtoYYYY"} =~ /^c(\d{2})(\d{2})$/i) { + $y1="$1$2"; + $y ="$1$y"; + $y += 100 if ($y<$y1); + + } else { + $y1=$curr_y-$Cnf{"YYtoYYYY"}; + $y2=$y1+99; + $y="19$y"; + while ($y<$y1) { + $y+=100; + } + while ($y>$y2) { + $y-=100; + } + } + $y; +} + +# &Date_NthWeekOfYear($y,$n); +# Returns a list of (YYYY,MM,DD) for the 1st day of the Nth week of the +# year. +# &Date_NthWeekOfYear($y,$n,$dow,$flag); +# Returns a list of (YYYY,MM,DD) for the Nth DoW of the year. If flag +# is nil, the first DoW of the year may actually be in the previous +# year (since the 1st week may include days from the previous year). +# If flag is non-nil, the 1st DoW of the year refers to the 1st one +# actually in the year +sub Date_NthWeekOfYear { + print "DEBUG: Date_NthWeekOfYear\n" if ($Curr{"Debug"} =~ /trace/); + my($y,$n,$dow,$flag)=@_; + my($m,$d,$err,$tmp,$date,%dow)=(); + $y=$Curr{"Y"} if (! defined $y or ! $y); + $n=1 if (! defined $n or $n eq ""); + return () if ($n<0 || $n>53); + if (defined $dow) { + $dow=lc($dow); + %dow=%{ $Lang{$Cnf{"Language"}}{"WeekH"} }; + $dow=$dow{$dow} if (exists $dow{$dow}); + return () if ($dow<1 || $dow>7); + $flag="" if (! defined $flag); + } else { + $dow=""; + $flag=""; + } + + $y=&Date_FixYear($y) if (length($y)<4); + if ($Cnf{"Jan1Week1"}) { + $date=&Date_Join($y,1,1,0,0,0); + } else { + $date=&Date_Join($y,1,4,0,0,0); + } + $date=&Date_GetPrev($date,$Cnf{"FirstDay"},1); + $date=&Date_GetNext($date,$dow,1) if ($dow ne ""); + + if ($flag) { + ($tmp)=&Date_Split($date, 1); + $n++ if ($tmp != $y); + } + + if ($n>1) { + $date=&DateCalc_DateDelta($date,"+0:0:". ($n-1) . ":0:0:0:0",\$err,0); + } elsif ($n==0) { + $date=&DateCalc_DateDelta($date,"-0:0:1:0:0:0:0",\$err,0); + } + ($y,$m,$d)=&Date_Split($date, 1); + ($y,$m,$d); +} + +######################################################################## +# LANGUAGE INITIALIZATION +######################################################################## + +# 8-bit international characters can be gotten by "\xXX". I don't know +# how to get 16-bit characters. I've got to read up on perllocale. +sub Char_8Bit { + my($hash)=@_; + + # grave ` + # A` 00c0 a` 00e0 + # E` 00c8 e` 00e8 + # I` 00cc i` 00ec + # O` 00d2 o` 00f2 + # U` 00d9 u` 00f9 + # W` 1e80 w` 1e81 + # Y` 1ef2 y` 1ef3 + + $$hash{"A`"} = "\xc0"; # LATIN CAPITAL LETTER A WITH GRAVE + $$hash{"E`"} = "\xc8"; # LATIN CAPITAL LETTER E WITH GRAVE + $$hash{"I`"} = "\xcc"; # LATIN CAPITAL LETTER I WITH GRAVE + $$hash{"O`"} = "\xd2"; # LATIN CAPITAL LETTER O WITH GRAVE + $$hash{"U`"} = "\xd9"; # LATIN CAPITAL LETTER U WITH GRAVE + $$hash{"a`"} = "\xe0"; # LATIN SMALL LETTER A WITH GRAVE + $$hash{"e`"} = "\xe8"; # LATIN SMALL LETTER E WITH GRAVE + $$hash{"i`"} = "\xec"; # LATIN SMALL LETTER I WITH GRAVE + $$hash{"o`"} = "\xf2"; # LATIN SMALL LETTER O WITH GRAVE + $$hash{"u`"} = "\xf9"; # LATIN SMALL LETTER U WITH GRAVE + + # acute ' + # A' 00c1 a' 00e1 + # C' 0106 c' 0107 + # E' 00c9 e' 00e9 + # I' 00cd i' 00ed + # L' 0139 l' 013a + # N' 0143 n' 0144 + # O' 00d3 o' 00f3 + # R' 0154 r' 0155 + # S' 015a s' 015b + # U' 00da u' 00fa + # W' 1e82 w' 1e83 + # Y' 00dd y' 00fd + # Z' 0179 z' 017a + + $$hash{"A'"} = "\xc1"; # LATIN CAPITAL LETTER A WITH ACUTE + $$hash{"E'"} = "\xc9"; # LATIN CAPITAL LETTER E WITH ACUTE + $$hash{"I'"} = "\xcd"; # LATIN CAPITAL LETTER I WITH ACUTE + $$hash{"O'"} = "\xd3"; # LATIN CAPITAL LETTER O WITH ACUTE + $$hash{"U'"} = "\xda"; # LATIN CAPITAL LETTER U WITH ACUTE + $$hash{"Y'"} = "\xdd"; # LATIN CAPITAL LETTER Y WITH ACUTE + $$hash{"a'"} = "\xe1"; # LATIN SMALL LETTER A WITH ACUTE + $$hash{"e'"} = "\xe9"; # LATIN SMALL LETTER E WITH ACUTE + $$hash{"i'"} = "\xed"; # LATIN SMALL LETTER I WITH ACUTE + $$hash{"o'"} = "\xf3"; # LATIN SMALL LETTER O WITH ACUTE + $$hash{"u'"} = "\xfa"; # LATIN SMALL LETTER U WITH ACUTE + $$hash{"y'"} = "\xfd"; # LATIN SMALL LETTER Y WITH ACUTE + + # double acute " " + # O" 0150 o" 0151 + # U" 0170 u" 0171 + + # circumflex ^ + # A^ 00c2 a^ 00e2 + # C^ 0108 c^ 0109 + # E^ 00ca e^ 00ea + # G^ 011c g^ 011d + # H^ 0124 h^ 0125 + # I^ 00ce i^ 00ee + # J^ 0134 j^ 0135 + # O^ 00d4 o^ 00f4 + # S^ 015c s^ 015d + # U^ 00db u^ 00fb + # W^ 0174 w^ 0175 + # Y^ 0176 y^ 0177 + + $$hash{"A^"} = "\xc2"; # LATIN CAPITAL LETTER A WITH CIRCUMFLEX + $$hash{"E^"} = "\xca"; # LATIN CAPITAL LETTER E WITH CIRCUMFLEX + $$hash{"I^"} = "\xce"; # LATIN CAPITAL LETTER I WITH CIRCUMFLEX + $$hash{"O^"} = "\xd4"; # LATIN CAPITAL LETTER O WITH CIRCUMFLEX + $$hash{"U^"} = "\xdb"; # LATIN CAPITAL LETTER U WITH CIRCUMFLEX + $$hash{"a^"} = "\xe2"; # LATIN SMALL LETTER A WITH CIRCUMFLEX + $$hash{"e^"} = "\xea"; # LATIN SMALL LETTER E WITH CIRCUMFLEX + $$hash{"i^"} = "\xee"; # LATIN SMALL LETTER I WITH CIRCUMFLEX + $$hash{"o^"} = "\xf4"; # LATIN SMALL LETTER O WITH CIRCUMFLEX + $$hash{"u^"} = "\xfb"; # LATIN SMALL LETTER U WITH CIRCUMFLEX + + # tilde ~ + # A~ 00c3 a~ 00e3 + # I~ 0128 i~ 0129 + # N~ 00d1 n~ 00f1 + # O~ 00d5 o~ 00f5 + # U~ 0168 u~ 0169 + + $$hash{"A~"} = "\xc3"; # LATIN CAPITAL LETTER A WITH TILDE + $$hash{"N~"} = "\xd1"; # LATIN CAPITAL LETTER N WITH TILDE + $$hash{"O~"} = "\xd5"; # LATIN CAPITAL LETTER O WITH TILDE + $$hash{"a~"} = "\xe3"; # LATIN SMALL LETTER A WITH TILDE + $$hash{"n~"} = "\xf1"; # LATIN SMALL LETTER N WITH TILDE + $$hash{"o~"} = "\xf5"; # LATIN SMALL LETTER O WITH TILDE + + # macron - + # A- 0100 a- 0101 + # E- 0112 e- 0113 + # I- 012a i- 012b + # O- 014c o- 014d + # U- 016a u- 016b + + # breve ( [half circle up] + # A( 0102 a( 0103 + # G( 011e g( 011f + # U( 016c u( 016d + + # dot . + # C. 010a c. 010b + # E. 0116 e. 0117 + # G. 0120 g. 0121 + # I. 0130 + # Z. 017b z. 017c + + # diaeresis : [side by side dots] + # A: 00c4 a: 00e4 + # E: 00cb e: 00eb + # I: 00cf i: 00ef + # O: 00d6 o: 00f6 + # U: 00dc u: 00fc + # W: 1e84 w: 1e85 + # Y: 0178 y: 00ff + + $$hash{"A:"} = "\xc4"; # LATIN CAPITAL LETTER A WITH DIAERESIS + $$hash{"E:"} = "\xcb"; # LATIN CAPITAL LETTER E WITH DIAERESIS + $$hash{"I:"} = "\xcf"; # LATIN CAPITAL LETTER I WITH DIAERESIS + $$hash{"O:"} = "\xd6"; # LATIN CAPITAL LETTER O WITH DIAERESIS + $$hash{"U:"} = "\xdc"; # LATIN CAPITAL LETTER U WITH DIAERESIS + $$hash{"a:"} = "\xe4"; # LATIN SMALL LETTER A WITH DIAERESIS + $$hash{"e:"} = "\xeb"; # LATIN SMALL LETTER E WITH DIAERESIS + $$hash{"i:"} = "\xef"; # LATIN SMALL LETTER I WITH DIAERESIS + $$hash{"o:"} = "\xf6"; # LATIN SMALL LETTER O WITH DIAERESIS + $$hash{"u:"} = "\xfc"; # LATIN SMALL LETTER U WITH DIAERESIS + $$hash{"y:"} = "\xff"; # LATIN SMALL LETTER Y WITH DIAERESIS + + # ring o + # U0 016e u0 016f + + # cedilla , [squiggle down and left below the letter] + # ,C 00c7 ,c 00e7 + # ,G 0122 ,g 0123 + # ,K 0136 ,k 0137 + # ,L 013b ,l 013c + # ,N 0145 ,n 0146 + # ,R 0156 ,r 0157 + # ,S 015e ,s 015f + # ,T 0162 ,t 0163 + + $$hash{",C"} = "\xc7"; # LATIN CAPITAL LETTER C WITH CEDILLA + $$hash{",c"} = "\xe7"; # LATIN SMALL LETTER C WITH CEDILLA + + # ogonek ; [squiggle down and right below the letter] + # A; 0104 a; 0105 + # E; 0118 e; 0119 + # I; 012e i; 012f + # U; 0172 u; 0173 + + # caron < [little v on top] + # A< 01cd a< 01ce + # C< 010c c< 010d + # D< 010e d< 010f + # E< 011a e< 011b + # L< 013d l< 013e + # N< 0147 n< 0148 + # R< 0158 r< 0159 + # S< 0160 s< 0161 + # T< 0164 t< 0165 + # Z< 017d z< 017e + + + # Other characters + + # First character is below, 2nd character is above + $$hash{"||"} = "\xa6"; # BROKEN BAR + $$hash{" :"} = "\xa8"; # DIAERESIS + $$hash{"-a"} = "\xaa"; # FEMININE ORDINAL INDICATOR + #$$hash{" -"}= "\xaf"; # MACRON (narrow bar) + $$hash{" -"} = "\xad"; # HYPHEN (wide bar) + $$hash{" o"} = "\xb0"; # DEGREE SIGN + $$hash{"-+"} = "\xb1"; # PLUS\342\200\220MINUS SIGN + $$hash{" 1"} = "\xb9"; # SUPERSCRIPT ONE + $$hash{" 2"} = "\xb2"; # SUPERSCRIPT TWO + $$hash{" 3"} = "\xb3"; # SUPERSCRIPT THREE + $$hash{" '"} = "\xb4"; # ACUTE ACCENT + $$hash{"-o"} = "\xba"; # MASCULINE ORDINAL INDICATOR + $$hash{" ."} = "\xb7"; # MIDDLE DOT + $$hash{", "} = "\xb8"; # CEDILLA + $$hash{"Ao"} = "\xc5"; # LATIN CAPITAL LETTER A WITH RING ABOVE + $$hash{"ao"} = "\xe5"; # LATIN SMALL LETTER A WITH RING ABOVE + $$hash{"ox"} = "\xf0"; # LATIN SMALL LETTER ETH + + # upside down characters + + $$hash{"ud!"} = "\xa1"; # INVERTED EXCLAMATION MARK + $$hash{"ud?"} = "\xbf"; # INVERTED QUESTION MARK + + # overlay characters + + $$hash{"X o"} = "\xa4"; # CURRENCY SIGN + $$hash{"Y ="} = "\xa5"; # YEN SIGN + $$hash{"S o"} = "\xa7"; # SECTION SIGN + $$hash{"O c"} = "\xa9"; # COPYRIGHT SIGN Copyright + $$hash{"O R"} = "\xae"; # REGISTERED SIGN + $$hash{"D -"} = "\xd0"; # LATIN CAPITAL LETTER ETH + $$hash{"O /"} = "\xd8"; # LATIN CAPITAL LETTER O WITH STROKE + $$hash{"o /"} = "\xf8"; # LATIN SMALL LETTER O WITH STROKE + + # special names + + $$hash{"1/4"} = "\xbc"; # VULGAR FRACTION ONE QUARTER + $$hash{"1/2"} = "\xbd"; # VULGAR FRACTION ONE HALF + $$hash{"3/4"} = "\xbe"; # VULGAR FRACTION THREE QUARTERS + $$hash{"<<"} = "\xab"; # LEFT POINTING DOUBLE ANGLE QUOTATION MARK + $$hash{">>"} = "\xbb"; # RIGHT POINTING DOUBLE ANGLE QUOTATION MARK + $$hash{"cent"}= "\xa2"; # CENT SIGN + $$hash{"lb"} = "\xa3"; # POUND SIGN + $$hash{"mu"} = "\xb5"; # MICRO SIGN + $$hash{"beta"}= "\xdf"; # LATIN SMALL LETTER SHARP S + $$hash{"para"}= "\xb6"; # PILCROW SIGN + $$hash{"-|"} = "\xac"; # NOT SIGN + $$hash{"AE"} = "\xc6"; # LATIN CAPITAL LETTER AE + $$hash{"ae"} = "\xe6"; # LATIN SMALL LETTER AE + $$hash{"x"} = "\xd7"; # MULTIPLICATION SIGN + $$hash{"P"} = "\xde"; # LATIN CAPITAL LETTER THORN + $$hash{"/"} = "\xf7"; # DIVISION SIGN + $$hash{"p"} = "\xfe"; # LATIN SMALL LETTER THORN +} + +# $hashref = &Date_Init_LANGUAGE; +# This returns a hash containing all of the initialization for a +# specific language. The hash elements are: +# +# @ month_name full month names January February ... +# @ month_abb month abbreviations Jan Feb ... +# @ day_name day names Monday Tuesday ... +# @ day_abb day abbreviations Mon Tue ... +# @ day_char day character abbrevs M T ... +# @ am AM notations +# @ pm PM notations +# +# @ num_suff number with suffix 1st 2nd ... +# @ num_word numbers spelled out first second ... +# +# $ now words which mean now now today ... +# $ last words which mean last last final ... +# $ each words which mean each each every ... +# $ of of (as in a member of) in of ... +# ex. 4th day OF June +# $ at at 4:00 at +# $ on on Sunday on +# $ future in the future in +# $ past in the past ago +# $ next next item next +# $ prev previous item last previous +# $ later 2 hours later +# +# % offset a hash of special dates { tomorrow->0:0:0:1:0:0:0 } +# % times a hash of times { noon->12:00:00 ... } +# +# $ years words for year y yr year ... +# $ months words for month +# $ weeks words for week +# $ days words for day +# $ hours words for hour +# $ minutes words for minute +# $ seconds words for second +# % replace +# The replace element is quite important, but a bit tricky. In +# English (and probably other languages), one of the abbreviations +# for the word month that would be nice is "m". The problem is that +# "m" matches the "m" in "minute" which causes the string to be +# improperly matched in some cases. Hence, the list of abbreviations +# for month is given as: +# "mon month months" +# In order to allow you to enter "m", replacements can be done. +# $replace is a list of pairs of words which are matched and replaced +# AS ENTIRE WORDS. Having $replace equal to "m"->"month" means that +# the entire word "m" will be replaced with "month". This allows the +# desired abbreviation to be used. Make sure that replace contains +# an even number of words (i.e. all must be pairs). Any time a +# desired abbreviation matches the start of any other, it has to go +# here. +# +# $ exact exact mode exactly +# $ approx approximate mode approximately +# $ business business mode business +# +# r sephm hour/minute separator (?::) +# r sepms minute/second separator (?::) +# r sepss second/fraction separator (?:[.:]) +# +# Elements marked with an asterix (@) are returned as a set of lists. +# Each list contains the strings for each element. The first set is used +# when the 7-bit ASCII (US) character set is wanted. The 2nd set is used +# when an international character set is available. Both of the 1st two +# sets should be complete (but the 2nd list can be left empty to force the +# first set to be used always). The 3rd set and later can be partial sets +# if desired. +# +# Elements marked with a dollar ($) are returned as a simple list of words. +# +# Elements marked with a percent (%) are returned as a hash list. +# +# Elements marked with (r) are regular expression elements which must not +# create a back reference. +# +# ***NOTE*** Every hash element (unless otherwise noted) MUST be defined in +# every language. + +sub Date_Init_English { + print "DEBUG: Date_Init_English\n" if ($Curr{"Debug"} =~ /trace/); + my($d)=@_; + + $$d{"month_name"}= + [["January","February","March","April","May","June", + "July","August","September","October","November","December"]]; + + $$d{"month_abb"}= + [["Jan","Feb","Mar","Apr","May","Jun", + "Jul","Aug","Sep","Oct","Nov","Dec"], + [], + ["","","","","","","","","Sept"]]; + + $$d{"day_name"}= + [["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"]]; + $$d{"day_abb"}= + [["Mon","Tue","Wed","Thu","Fri","Sat","Sun"], + ["", "Tues","", "Thur","", "", ""]]; + $$d{"day_char"}= + [["M","T","W","Th","F","Sa","S"]]; + + $$d{"num_suff"}= + [["1st","2nd","3rd","4th","5th","6th","7th","8th","9th","10th", + "11th","12th","13th","14th","15th","16th","17th","18th","19th","20th", + "21st","22nd","23rd","24th","25th","26th","27th","28th","29th","30th", + "31st"]]; + $$d{"num_word"}= + [["first","second","third","fourth","fifth","sixth","seventh","eighth", + "ninth","tenth","eleventh","twelfth","thirteenth","fourteenth", + "fifteenth","sixteenth","seventeenth","eighteenth","nineteenth", + "twentieth","twenty-first","twenty-second","twenty-third", + "twenty-fourth","twenty-fifth","twenty-sixth","twenty-seventh", + "twenty-eighth","twenty-ninth","thirtieth","thirty-first"]]; + + $$d{"now"} =["today","now"]; + $$d{"last"} =["last","final"]; + $$d{"each"} =["each","every"]; + $$d{"of"} =["in","of"]; + $$d{"at"} =["at"]; + $$d{"on"} =["on"]; + $$d{"future"} =["in"]; + $$d{"past"} =["ago"]; + $$d{"next"} =["next"]; + $$d{"prev"} =["previous","last"]; + $$d{"later"} =["later"]; + + $$d{"exact"} =["exactly"]; + $$d{"approx"} =["approximately"]; + $$d{"business"}=["business"]; + + $$d{"offset"} =["yesterday","-0:0:0:1:0:0:0","tomorrow","+0:0:0:1:0:0:0"]; + $$d{"times"} =["noon","12:00:00","midnight","00:00:00"]; + + $$d{"years"} =["y","yr","year","yrs","years"]; + $$d{"months"} =["mon","month","months"]; + $$d{"weeks"} =["w","wk","wks","week","weeks"]; + $$d{"days"} =["d","day","days"]; + $$d{"hours"} =["h","hr","hrs","hour","hours"]; + $$d{"minutes"} =["mn","min","minute","minutes"]; + $$d{"seconds"} =["s","sec","second","seconds"]; + $$d{"replace"} =["m","month"]; + + $$d{"sephm"} =':'; + $$d{"sepms"} =':'; + $$d{"sepss"} ='[.:]'; + + $$d{"am"} = ["AM","A.M."]; + $$d{"pm"} = ["PM","P.M."]; +} + +sub Date_Init_Italian { + print "DEBUG: Date_Init_Italian\n" if ($Curr{"Debug"} =~ /trace/); + my($d)=@_; + my(%h)=(); + &Char_8Bit(\%h); + my($i)=$h{"i'"}; + + $$d{"month_name"}= + [[qw(Gennaio Febbraio Marzo Aprile Maggio Giugno + Luglio Agosto Settembre Ottobre Novembre Dicembre)]]; + + $$d{"month_abb"}= + [[qw(Gen Feb Mar Apr Mag Giu Lug Ago Set Ott Nov Dic)]]; + + $$d{"day_name"}= + [[qw(Lunedi Martedi Mercoledi Giovedi Venerdi Sabato Domenica)], + [qw(Luned${i} Marted${i} Mercoled${i} Gioved${i} Venerd${i})]]; + $$d{"day_abb"}= + [[qw(Lun Mar Mer Gio Ven Sab Dom)]]; + $$d{"day_char"}= + [[qw(L Ma Me G V S D)]]; + + $$d{"num_suff"}= + [[qw(1mo 2do 3zo 4to 5to 6to 7mo 8vo 9no 10mo 11mo 12mo 13mo 14mo 15mo + 16mo 17mo 18mo 19mo 20mo 21mo 22mo 23mo 24mo 25mo 26mo 27mo 28mo + 29mo 3mo 31mo)]]; + $$d{"num_word"}= + [[qw(primo secondo terzo quarto quinto sesto settimo ottavo nono decimo + undicesimo dodicesimo tredicesimo quattordicesimo quindicesimo + sedicesimo diciassettesimo diciottesimo diciannovesimo ventesimo + ventunesimo ventiduesimo ventitreesimo ventiquattresimo + venticinquesimo ventiseiesimo ventisettesimo ventottesimo + ventinovesimo trentesimo trentunesimo)]]; + + $$d{"now"} =[qw(adesso oggi)]; + $$d{"last"} =[qw(ultimo)]; + $$d{"each"} =[qw(ogni)]; + $$d{"of"} =[qw(della del)]; + $$d{"at"} =[qw(alle)]; + $$d{"on"} =[qw(di)]; + $$d{"future"} =[qw(fra)]; + $$d{"past"} =[qw(fa)]; + $$d{"next"} =[qw(prossimo)]; + $$d{"prev"} =[qw(ultimo)]; + $$d{"later"} =[qw(dopo)]; + + $$d{"exact"} =[qw(esattamente)]; + $$d{"approx"} =[qw(circa)]; + $$d{"business"}=[qw(lavorativi lavorativo)]; + + $$d{"offset"} =[qw(ieri -0:0:0:1:0:0:0 domani +0:0:0:1:0:0:0)]; + $$d{"times"} =[qw(mezzogiorno 12:00:00 mezzanotte 00:00:00)]; + + $$d{"years"} =[qw(anni anno a)]; + $$d{"months"} =[qw(mesi mese mes)]; + $$d{"weeks"} =[qw(settimane settimana sett)]; + $$d{"days"} =[qw(giorni giorno g)]; + $$d{"hours"} =[qw(ore ora h)]; + $$d{"minutes"} =[qw(minuti minuto min)]; + $$d{"seconds"} =[qw(secondi secondo sec)]; + $$d{"replace"} =[qw(s sec m mes)]; + + $$d{"sephm"} =':'; + $$d{"sepms"} =':'; + $$d{"sepss"} ='[.:]'; + + $$d{"am"} = [qw(AM)]; + $$d{"pm"} = [qw(PM)]; +} + +sub Date_Init_French { + print "DEBUG: Date_Init_French\n" if ($Curr{"Debug"} =~ /trace/); + my($d)=@_; + my(%h)=(); + &Char_8Bit(\%h); + my($e)=$h{"e'"}; + my($u)=$h{"u^"}; + my($a)=$h{"a'"}; + + $$d{"month_name"}= + [["janvier","fevrier","mars","avril","mai","juin", + "juillet","aout","septembre","octobre","novembre","decembre"], + ["janvier","f${e}vrier","mars","avril","mai","juin", + "juillet","ao${u}t","septembre","octobre","novembre","d${e}cembre"]]; + $$d{"month_abb"}= + [["jan","fev","mar","avr","mai","juin", + "juil","aout","sept","oct","nov","dec"], + ["jan","f${e}v","mar","avr","mai","juin", + "juil","ao${u}t","sept","oct","nov","d${e}c"]]; + + $$d{"day_name"}= + [["lundi","mardi","mercredi","jeudi","vendredi","samedi","dimanche"]]; + $$d{"day_abb"}= + [["lun","mar","mer","jeu","ven","sam","dim"]]; + $$d{"day_char"}= + [["l","ma","me","j","v","s","d"]]; + + $$d{"num_suff"}= + [["1er","2e","3e","4e","5e","6e","7e","8e","9e","10e", + "11e","12e","13e","14e","15e","16e","17e","18e","19e","20e", + "21e","22e","23e","24e","25e","26e","27e","28e","29e","30e", + "31e"]]; + $$d{"num_word"}= + [["premier","deux","trois","quatre","cinq","six","sept","huit","neuf", + "dix","onze","douze","treize","quatorze","quinze","seize","dix-sept", + "dix-huit","dix-neuf","vingt","vingt et un","vingt-deux","vingt-trois", + "vingt-quatre","vingt-cinq","vingt-six","vingt-sept","vingt-huit", + "vingt-neuf","trente","trente et un"], + ["1re"]]; + + $$d{"now"} =["aujourd'hui","maintenant"]; + $$d{"last"} =["dernier"]; + $$d{"each"} =["chaque","tous les","toutes les"]; + $$d{"of"} =["en","de"]; + $$d{"at"} =["a","${a}0"]; + $$d{"on"} =["sur"]; + $$d{"future"} =["en"]; + $$d{"past"} =["il y a"]; + $$d{"next"} =["suivant"]; + $$d{"prev"} =["precedent","pr${e}c${e}dent"]; + $$d{"later"} =["plus tard"]; + + $$d{"exact"} =["exactement"]; + $$d{"approx"} =["approximativement"]; + $$d{"business"}=["professionel"]; + + $$d{"offset"} =["hier","-0:0:0:1:0:0:0","demain","+0:0:0:1:0:0:0"]; + $$d{"times"} =["midi","12:00:00","minuit","00:00:00"]; + + $$d{"years"} =["an","annee","ans","annees","ann${e}e","ann${e}es"]; + $$d{"months"} =["mois"]; + $$d{"weeks"} =["sem","semaine"]; + $$d{"days"} =["j","jour","jours"]; + $$d{"hours"} =["h","heure","heures"]; + $$d{"minutes"} =["mn","min","minute","minutes"]; + $$d{"seconds"} =["s","sec","seconde","secondes"]; + $$d{"replace"} =["m","mois"]; + + $$d{"sephm"} ='[h:]'; + $$d{"sepms"} =':'; + $$d{"sepss"} ='[.:,]'; + + $$d{"am"} = ["du matin"]; + $$d{"pm"} = ["du soir"]; +} + +sub Date_Init_Romanian { + print "DEBUG: Date_Init_Romanian\n" if ($Curr{"Debug"} =~ /trace/); + my($d)=@_; + my(%h)=(); + &Char_8Bit(\%h); + my($p)=$h{"p"}; + my($i)=$h{"i^"}; + my($a)=$h{"a~"}; + my($o)=$h{"-o"}; + + $$d{"month_name"}= + [["ianuarie","februarie","martie","aprilie","mai","iunie", + "iulie","august","septembrie","octombrie","noiembrie","decembrie"]]; + $$d{"month_abb"}= + [["ian","febr","mart","apr","mai","iun", + "iul","aug","sept","oct","nov","dec"], + ["","feb"]]; + + $$d{"day_name"}= + [["luni","marti","miercuri","joi","vineri","simbata","duminica"], + ["luni","mar${p}i","miercuri","joi","vineri","s${i}mb${a}t${a}", + "duminic${a}"]]; + $$d{"day_abb"}= + [["lun","mar","mie","joi","vin","sim","dum"], + ["lun","mar","mie","joi","vin","s${i}m","dum"]]; + $$d{"day_char"}= + [["L","Ma","Mi","J","V","S","D"]]; + + $$d{"num_suff"}= + [["prima","a doua","a 3-a","a 4-a","a 5-a","a 6-a","a 7-a","a 8-a", + "a 9-a","a 10-a","a 11-a","a 12-a","a 13-a","a 14-a","a 15-a", + "a 16-a","a 17-a","a 18-a","a 19-a","a 20-a","a 21-a","a 22-a", + "a 23-a","a 24-a","a 25-a","a 26-a","a 27-a","a 28-a","a 29-a", + "a 30-a","a 31-a"]]; + + $$d{"num_word"}= + [["prima","a doua","a treia","a patra","a cincea","a sasea","a saptea", + "a opta","a noua","a zecea","a unsprezecea","a doisprezecea", + "a treisprezecea","a patrusprezecea","a cincisprezecea","a saiprezecea", + "a saptesprezecea","a optsprezecea","a nouasprezecea","a douazecea", + "a douazecisiuna","a douazecisidoua","a douazecisitreia", + "a douazecisipatra","a douazecisicincea","a douazecisisasea", + "a douazecisisaptea","a douazecisiopta","a douazecisinoua","a treizecea", + "a treizecisiuna"], + ["prima","a doua","a treia","a patra","a cincea","a ${o}asea", + "a ${o}aptea","a opta","a noua","a zecea","a unsprezecea", + "a doisprezecea","a treisprezecea","a patrusprezecea","a cincisprezecea", + "a ${o}aiprezecea","a ${o}aptesprezecea","a optsprezecea", + "a nou${a}sprezecea","a dou${a}zecea","a dou${a}zeci${o}iuna", + "a dou${a}zeci${o}idoua","a dou${a}zeci${o}itreia", + "a dou${a}zeci${o}ipatra","a dou${a}zeci${o}icincea", + "a dou${a}zeci${o}i${o}asea","a dou${a}zeci${o}i${o}aptea", + "a dou${a}zeci${o}iopta","a dou${a}zeci${o}inoua","a treizecea", + "a treizeci${o}iuna"], + ["intii", "doi", "trei", "patru", "cinci", "sase", "sapte", + "opt","noua","zece","unsprezece","doisprezece", + "treisprezece","patrusprezece","cincisprezece","saiprezece", + "saptesprezece","optsprezece","nouasprezece","douazeci", + "douazecisiunu","douazecisidoi","douazecisitrei", + "douazecisipatru","douazecisicinci","douazecisisase","douazecisisapte", + "douazecisiopt","douazecisinoua","treizeci","treizecisiunu"], + ["${i}nt${i}i", "doi", "trei", "patru", "cinci", "${o}ase", "${o}apte", + "opt","nou${a}","zece","unsprezece","doisprezece", + "treisprezece","patrusprezece","cincisprezece","${o}aiprezece", + "${o}aptesprezece","optsprezece","nou${a}sprezece","dou${a}zeci", + "dou${a}zeci${o}iunu","dou${a}zeci${o}idoi","dou${a}zeci${o}itrei", + "dou${a}zecisipatru","dou${a}zeci${o}icinci","dou${a}zeci${o}i${o}ase", + "dou${a}zeci${o}i${o}apte","dou${a}zeci${o}iopt", + "dou${a}zeci${o}inou${a}","treizeci","treizeci${o}iunu"]]; + + $$d{"now"} =["acum","azi","astazi","ast${a}zi"]; + $$d{"last"} =["ultima"]; + $$d{"each"} =["fiecare"]; + $$d{"of"} =["din","in","n"]; + $$d{"at"} =["la"]; + $$d{"on"} =["on"]; + $$d{"future"} =["in","${i}n"]; + $$d{"past"} =["in urma", "${i}n urm${a}"]; + $$d{"next"} =["urmatoarea","urm${a}toarea"]; + $$d{"prev"} =["precedenta","ultima"]; + $$d{"later"} =["mai tirziu", "mai t${i}rziu"]; + + $$d{"exact"} =["exact"]; + $$d{"approx"} =["aproximativ"]; + $$d{"business"}=["de lucru","lucratoare","lucr${a}toare"]; + + $$d{"offset"} =["ieri","-0:0:0:1:0:0:0", + "alaltaieri", "-0:0:0:2:0:0:0", + "alalt${a}ieri","-0:0:0:2:0:0:0", + "miine","+0:0:0:1:0:0:0", + "m${i}ine","+0:0:0:1:0:0:0", + "poimiine","+0:0:0:2:0:0:0", + "poim${i}ine","+0:0:0:2:0:0:0"]; + $$d{"times"} =["amiaza","12:00:00", + "amiaz${a}","12:00:00", + "miezul noptii","00:00:00", + "miezul nop${p}ii","00:00:00"]; + + $$d{"years"} =["ani","an","a"]; + $$d{"months"} =["luni","luna","lun${a}","l"]; + $$d{"weeks"} =["saptamini","s${a}pt${a}m${i}ni","saptamina", + "s${a}pt${a}m${i}na","sapt","s${a}pt"]; + $$d{"days"} =["zile","zi","z"]; + $$d{"hours"} =["ore", "ora", "or${a}", "h"]; + $$d{"minutes"} =["minute","min","m"]; + $$d{"seconds"} =["secunde","sec",]; + $$d{"replace"} =["s","secunde"]; + + $$d{"sephm"} =':'; + $$d{"sepms"} =':'; + $$d{"sepss"} ='[.:,]'; + + $$d{"am"} = ["AM","A.M."]; + $$d{"pm"} = ["PM","P.M."]; +} + +sub Date_Init_Swedish { + print "DEBUG: Date_Init_Swedish\n" if ($Curr{"Debug"} =~ /trace/); + my($d)=@_; + my(%h)=(); + &Char_8Bit(\%h); + my($ao)=$h{"ao"}; + my($o) =$h{"o:"}; + my($a) =$h{"a:"}; + + $$d{"month_name"}= + [["Januari","Februari","Mars","April","Maj","Juni", + "Juli","Augusti","September","Oktober","November","December"]]; + $$d{"month_abb"}= + [["Jan","Feb","Mar","Apr","Maj","Jun", + "Jul","Aug","Sep","Okt","Nov","Dec"]]; + + $$d{"day_name"}= + [["Mandag","Tisdag","Onsdag","Torsdag","Fredag","Lordag","Sondag"], + ["M${ao}ndag","Tisdag","Onsdag","Torsdag","Fredag","L${o}rdag", + "S${o}ndag"]]; + $$d{"day_abb"}= + [["Man","Tis","Ons","Tor","Fre","Lor","Son"], + ["M${ao}n","Tis","Ons","Tor","Fre","L${o}r","S${o}n"]]; + $$d{"day_char"}= + [["M","Ti","O","To","F","L","S"]]; + + $$d{"num_suff"}= + [["1:a","2:a","3:e","4:e","5:e","6:e","7:e","8:e","9:e","10:e", + "11:e","12:e","13:e","14:e","15:e","16:e","17:e","18:e","19:e","20:e", + "21:a","22:a","23:e","24:e","25:e","26:e","27:e","28:e","29:e","30:e", + "31:a"]]; + $$d{"num_word"}= + [["forsta","andra","tredje","fjarde","femte","sjatte","sjunde", + "attonde","nionde","tionde","elfte","tolfte","trettonde","fjortonde", + "femtonde","sextonde","sjuttonde","artonde","nittonde","tjugonde", + "tjugoforsta","tjugoandra","tjugotredje","tjugofjarde","tjugofemte", + "tjugosjatte","tjugosjunde","tjugoattonde","tjugonionde", + "trettionde","trettioforsta"], + ["f${o}rsta","andra","tredje","fj${a}rde","femte","sj${a}tte","sjunde", + "${ao}ttonde","nionde","tionde","elfte","tolfte","trettonde","fjortonde", + "femtonde","sextonde","sjuttonde","artonde","nittonde","tjugonde", + "tjugof${o}rsta","tjugoandra","tjugotredje","tjugofj${a}rde","tjugofemte", + "tjugosj${a}tte","tjugosjunde","tjugo${ao}ttonde","tjugonionde", + "trettionde","trettiof${o}rsta"]]; + + $$d{"now"} =["idag","nu"]; + $$d{"last"} =["forra","f${o}rra","senaste"]; + $$d{"each"} =["varje"]; + $$d{"of"} =["om"]; + $$d{"at"} =["kl","kl.","klockan"]; + $$d{"on"} =["pa","p${ao}"]; + $$d{"future"} =["om"]; + $$d{"past"} =["sedan"]; + $$d{"next"} =["nasta","n${a}sta"]; + $$d{"prev"} =["forra","f${o}rra"]; + $$d{"later"} =["senare"]; + + $$d{"exact"} =["exakt"]; + $$d{"approx"} =["ungefar","ungef${a}r"]; + $$d{"business"}=["arbetsdag","arbetsdagar"]; + + $$d{"offset"} =["ig${ao}r","-0:0:0:1:0:0:0","igar","-0:0:0:1:0:0:0", + "imorgon","+0:0:0:1:0:0:0"]; + $$d{"times"} =["mitt pa dagen","12:00:00","mitt p${ao} dagen","12:00:00", + "midnatt","00:00:00"]; + + $$d{"years"} =["ar","${ao}r"]; + $$d{"months"} =["man","manad","manader","m${ao}n","m${ao}nad","m${ao}nader"]; + $$d{"weeks"} =["v","vecka","veckor"]; + $$d{"days"} =["d","dag","dagar"]; + $$d{"hours"} =["t","tim","timme","timmar"]; + $$d{"minutes"} =["min","minut","minuter"]; + $$d{"seconds"} =["s","sek","sekund","sekunder"]; + $$d{"replace"} =["m","minut"]; + + $$d{"sephm"} ='[.:]'; + $$d{"sepms"} =':'; + $$d{"sepss"} ='[.:]'; + + $$d{"am"} = ["FM"]; + $$d{"pm"} = ["EM"]; +} + +sub Date_Init_German { + print "DEBUG: Date_Init_German\n" if ($Curr{"Debug"} =~ /trace/); + my($d)=@_; + my(%h)=(); + &Char_8Bit(\%h); + my($a)=$h{"a:"}; + my($u)=$h{"u:"}; + my($o)=$h{"o:"}; + my($b)=$h{"beta"}; + + $$d{"month_name"}= + [["Januar","Februar","Maerz","April","Mai","Juni", + "Juli","August","September","Oktober","November","Dezember"], + ["J${a}nner","Februar","M${a}rz","April","Mai","Juni", + "Juli","August","September","Oktober","November","Dezember"]]; + $$d{"month_abb"}= + [["Jan","Feb","Mar","Apr","Mai","Jun", + "Jul","Aug","Sep","Okt","Nov","Dez"], + ["J${a}n","Feb","M${a}r","Apr","Mai","Jun", + "Jul","Aug","Sep","Okt","Nov","Dez"]]; + + $$d{"day_name"}= + [["Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag", + "Sonntag"]]; + $$d{"day_abb"}= + [["Mon","Die","Mit","Don","Fre","Sam","Son"]]; + $$d{"day_char"}= + [["M","Di","Mi","Do","F","Sa","So"]]; + + $$d{"num_suff"}= + [["1.","2.","3.","4.","5.","6.","7.","8.","9.","10.", + "11.","12.","13.","14.","15.","16.","17.","18.","19.","20.", + "21.","22.","23.","24.","25.","26.","27.","28.","29.","30.", + "31."]]; + $$d{"num_word"}= + [ + ["erste","zweite","dritte","vierte","funfte","sechste","siebente", + "achte","neunte","zehnte","elfte","zwolfte","dreizehnte","vierzehnte", + "funfzehnte","sechzehnte","siebzehnte","achtzehnte","neunzehnte", + "zwanzigste","einundzwanzigste","zweiundzwanzigste","dreiundzwanzigste", + "vierundzwanzigste","funfundzwanzigste","sechundzwanzigste", + "siebundzwanzigste","achtundzwanzigste","neunundzwanzigste", + "dreibigste","einunddreibigste"], + ["erste","zweite","dritte","vierte","f${u}nfte","sechste","siebente", + "achte","neunte","zehnte","elfte","zw${o}lfte","dreizehnte", + "vierzehnte","f${u}nfzehnte","sechzehnte","siebzehnte","achtzehnte", + "neunzehnte","zwanzigste","einundzwanzigste","zweiundzwanzigste", + "dreiundzwanzigste","vierundzwanzigste","f${u}nfundzwanzigste", + "sechundzwanzigste","siebundzwanzigste","achtundzwanzigste", + "neunundzwanzigste","drei${b}igste","einunddrei${b}igste"], + ["erster"]]; + + $$d{"now"} =["heute","jetzt"]; + $$d{"last"} =["letzte","letzten"]; + $$d{"each"} =["jeden"]; + $$d{"of"} =["der","im","des"]; + $$d{"at"} =["um"]; + $$d{"on"} =["am"]; + $$d{"future"} =["in"]; + $$d{"past"} =["vor"]; + $$d{"next"} =["nachste","n${a}chste","nachsten","n${a}chsten"]; + $$d{"prev"} =["vorherigen","vorherige","letzte","letzten"]; + $$d{"later"} =["spater","sp${a}ter"]; + + $$d{"exact"} =["genau"]; + $$d{"approx"} =["ungefahr","ungef${a}hr"]; + $$d{"business"}=["Arbeitstag"]; + + $$d{"offset"} =["gestern","-0:0:0:1:0:0:0","morgen","+0:0:0:1:0:0:0"]; + $$d{"times"} =["mittag","12:00:00","mitternacht","00:00:00"]; + + $$d{"years"} =["j","Jahr","Jahre"]; + $$d{"months"} =["Monat","Monate"]; + $$d{"weeks"} =["w","Woche","Wochen"]; + $$d{"days"} =["t","Tag","Tage"]; + $$d{"hours"} =["h","std","Stunde","Stunden"]; + $$d{"minutes"} =["min","Minute","Minuten"]; + $$d{"seconds"} =["s","sek","Sekunde","Sekunden"]; + $$d{"replace"} =["m","Monat"]; + + $$d{"sephm"} =':'; + $$d{"sepms"} ='[: ]'; + $$d{"sepss"} ='[.:]'; + + $$d{"am"} = ["FM"]; + $$d{"pm"} = ["EM"]; +} + +sub Date_Init_Dutch { + print "DEBUG: Date_Init_Dutch\n" if ($Curr{"Debug"} =~ /trace/); + my($d)=@_; + my(%h)=(); + &Char_8Bit(\%h); + + $$d{"month_name"}= + [["januari","februari","maart","april","mei","juni","juli","augustus", + "september","october","november","december"], + ["","","","","","","","","","oktober"]]; + + $$d{"month_abb"}= + [["jan","feb","maa","apr","mei","jun","jul", + "aug","sep","oct","nov","dec"], + ["","","mrt","","","","","","","okt"]]; + $$d{"day_name"}= + [["maandag","dinsdag","woensdag","donderdag","vrijdag","zaterdag", + "zondag"]]; + $$d{"day_abb"}= + [["ma","di","wo","do","vr","zat","zon"], + ["","","","","","za","zo"]]; + $$d{"day_char"}= + [["M","D","W","D","V","Za","Zo"]]; + + $$d{"num_suff"}= + [["1ste","2de","3de","4de","5de","6de","7de","8ste","9de","10de", + "11de","12de","13de","14de","15de","16de","17de","18de","19de","20ste", + "21ste","22ste","23ste","24ste","25ste","26ste","27ste","28ste","29ste", + "30ste","31ste"]]; + $$d{"num_word"}= + [["eerste","tweede","derde","vierde","vijfde","zesde","zevende","achtste", + "negende","tiende","elfde","twaalfde", + map {"${_}tiende";} qw (der veer vijf zes zeven acht negen), + "twintigste", + map {"${_}entwintigste";} qw (een twee drie vier vijf zes zeven acht + negen), + "dertigste","eenendertigste"], + ["","","","","","","","","","","","","","","","","","","","", + map {"${_}-en-twintigste";} qw (een twee drie vier vijf zes zeven acht + negen), + "dertigste","een-en-dertigste"], + ["een","twee","drie","vier","vijf","zes","zeven","acht","negen","tien", + "elf","twaalf", + map {"${_}tien"} qw (der veer vijf zes zeven acht negen), + "twintig", + map {"${_}entwintig"} qw (een twee drie vier vijf zes zeven acht negen), + "dertig","eenendertig"], + ["","","","","","","","","","","","","","","","","","","","", + map {"${_}-en-twintig"} qw (een twee drie vier vijf zes zeven acht + negen), + "dertig","een-en-dertig"]]; + + $$d{"now"} =["nu","nou","vandaag"]; + $$d{"last"} =["laatste"]; + $$d{"each"} =["elke","elk"]; + $$d{"of"} =["in","van"]; + $$d{"at"} =["om"]; + $$d{"on"} =["op"]; + $$d{"future"} =["over"]; + $$d{"past"} =["geleden","vroeger","eerder"]; + $$d{"next"} =["volgende","volgend"]; + $$d{"prev"} =["voorgaande","voorgaand"]; + $$d{"later"} =["later"]; + + $$d{"exact"} =["exact","precies","nauwkeurig"]; + $$d{"approx"} =["ongeveer","ong",'ong\.',"circa","ca",'ca\.']; + $$d{"business"}=["werk","zakelijke","zakelijk"]; + + $$d{"offset"} =["morgen","+0:0:0:1:0:0:0","overmorgen","+0:0:0:2:0:0:0", + "gisteren","-0:0:0:1:0:0:0","eergisteren","-0::00:2:0:0:0"]; + $$d{"times"} =["noen","12:00:00","middernacht","00:00:00"]; + + $$d{"years"} =["jaar","jaren","ja","j"]; + $$d{"months"} =["maand","maanden","mnd"]; + $$d{"weeks"} =["week","weken","w"]; + $$d{"days"} =["dag","dagen","d"]; + $$d{"hours"} =["uur","uren","u","h"]; + $$d{"minutes"} =["minuut","minuten","min"]; + $$d{"seconds"} =["seconde","seconden","sec","s"]; + $$d{"replace"} =["m","minuten"]; + + $$d{"sephm"} ='[:.uh]'; + $$d{"sepms"} ='[:.m]'; + $$d{"sepss"} ='[.:]'; + + $$d{"am"} = ["am","a.m.","vm","v.m.","voormiddag","'s_ochtends", + "ochtend","'s_nachts","nacht"]; + $$d{"pm"} = ["pm","p.m.","nm","n.m.","namiddag","'s_middags","middag", + "'s_avonds","avond"]; +} + +sub Date_Init_Polish { + print "DEBUG: Date_Init_Polish\n" if ($Curr{"Debug"} =~ /trace/); + my($d)=@_; + + $$d{"month_name"}= + [["stycznia","luty","marca","kwietnia","maja","czerwca", + "lipca","sierpnia","wrzesnia","pazdziernika","listopada","grudnia"], + ["stycznia","luty","marca","kwietnia","maja","czerwca","lipca", + "sierpnia","wrze\x9cnia","pa\x9fdziernika","listopada","grudnia"]]; + $$d{"month_abb"}= + [["sty.","lut.","mar.","kwi.","maj","cze.", + "lip.","sie.","wrz.","paz.","lis.","gru."], + ["sty.","lut.","mar.","kwi.","maj","cze.", + "lip.","sie.","wrz.","pa\x9f.","lis.","gru."]]; + + $$d{"day_name"}= + [["poniedzialek","wtorek","sroda","czwartek","piatek","sobota", + "niedziela"], + ["poniedzia\x81\xb3ek","wtorek","\x9croda","czwartek","pi\x81\xb9tek", + "sobota","niedziela"]]; + $$d{"day_abb"}= + [["po.","wt.","sr.","cz.","pi.","so.","ni."], + ["po.","wt.","\x9cr.","cz.","pi.","so.","ni."]]; + $$d{"day_char"}= + [["p","w","e","c","p","s","n"], + ["p","w","\x9c.","c","p","s","n"]]; + + $$d{"num_suff"}= + [["1.","2.","3.","4.","5.","6.","7.","8.","9.","10.", + "11.","12.","13.","14.","15.","16.","17.","18.","19.","20.", + "21.","22.","23.","24.","25.","26.","27.","28.","29.","30.", + "31."]]; + $$d{"num_word"}= + [["pierwszego","drugiego","trzeczego","czwartego","piatego","szostego", + "siodmego","osmego","dziewiatego","dziesiatego", + "jedenastego","dwunastego","trzynastego","czternastego","pietnastego", + "szestnastego","siedemnastego","osiemnastego","dziewietnastego", + "dwudziestego", + "dwudziestego pierwszego","dwudziestego drugiego", + "dwudziestego trzeczego","dwudziestego czwartego", + "dwudziestego piatego","dwudziestego szostego", + "dwudziestego siodmego","dwudziestego osmego", + "dwudziestego dziewiatego","trzydziestego","trzydziestego pierwszego"], + ["pierwszego","drugiego","trzeczego","czwartego","pi\x81\xb9tego", + "sz\x81\xf3stego","si\x81\xf3dmego","\x81\xf3smego","dziewi\x81\xb9tego", + "dziesi\x81\xb9tego","jedenastego","dwunastego","trzynastego", + "czternastego","pi\x81\xeatnastego","szestnastego","siedemnastego", + "osiemnastego","dziewietnastego","dwudziestego", + "dwudziestego pierwszego","dwudziestego drugiego", + "dwudziestego trzeczego","dwudziestego czwartego", + "dwudziestego pi\x81\xb9tego","dwudziestego sz\x81\xf3stego", + "dwudziestego si\x81\xf3dmego","dwudziestego \x81\xf3smego", + "dwudziestego dziewi\x81\xb9tego","trzydziestego", + "trzydziestego pierwszego"]]; + + $$d{"now"} =["dzisaj","teraz"]; + $$d{"last"} =["ostatni","ostatna"]; + $$d{"each"} =["kazdy","ka\x81\xbfdy", "kazdym","ka\x81\xbfdym"]; + $$d{"of"} =["w","z"]; + $$d{"at"} =["o","u"]; + $$d{"on"} =["na"]; + $$d{"future"} =["za"]; + $$d{"past"} =["temu"]; + $$d{"next"} =["nastepny","nast\x81\xeapny","nastepnym","nast\x81\xeapnym", + "przyszly","przysz\x81\xb3y","przyszlym", + "przysz\x81\xb3ym"]; + $$d{"prev"} =["zeszly","zesz\x81\xb3y","zeszlym","zesz\x81\xb3ym"]; + $$d{"later"} =["later"]; + + $$d{"exact"} =["doklandnie","dok\x81\xb3andnie"]; + $$d{"approx"} =["w przyblizeniu","w przybli\x81\xbfeniu","mniej wiecej", + "mniej wi\x81\xeacej","okolo","oko\x81\xb3o"]; + $$d{"business"}=["sluzbowy","s\x81\xb3u\x81\xbfbowy","sluzbowym", + "s\x81\xb3u\x81\xbfbowym"]; + + $$d{"times"} =["po\x81\xb3udnie","12:00:00", + "p\x81\xf3\x81\xb3noc","00:00:00", + "poludnie","12:00:00","polnoc","00:00:00"]; + $$d{"offset"} =["wczoraj","-0:0:1:0:0:0","jutro","+0:0:1:0:0:0"]; + + $$d{"years"} =["rok","lat","lata","latach"]; + $$d{"months"} =["m.","miesiac","miesi\x81\xb9c","miesiecy", + "miesi\x81\xeacy","miesiacu","miesi\x81\xb9cu"]; + $$d{"weeks"} =["ty.","tydzien","tydzie\x81\xf1","tygodniu"]; + $$d{"days"} =["d.","dzien","dzie\x81\xf1","dni"]; + $$d{"hours"} =["g.","godzina","godziny","godzinie"]; + $$d{"minutes"} =["mn.","min.","minut","minuty"]; + $$d{"seconds"} =["s.","sekund","sekundy"]; + $$d{"replace"} =["m.","miesiac"]; + + $$d{"sephm"} =':'; + $$d{"sepms"} =':'; + $$d{"sepss"} ='[.:]'; + + $$d{"am"} = ["AM","A.M."]; + $$d{"pm"} = ["PM","P.M."]; +} + +sub Date_Init_Spanish { + print "DEBUG: Date_Init_Spanish\n" if ($Curr{"Debug"} =~ /trace/); + my($d)=@_; + my(%h)=(); + &Char_8Bit(\%h); + + $$d{"month_name"}= + [["Enero","Febrero","Marzo","Abril","Mayo","Junio","Julio","Agosto", + "Septiembre","Octubre","Noviembre","Diciembre"]]; + + $$d{"month_abb"}= + [["Ene","Feb","Mar","Abr","May","Jun","Jul","Ago","Sep","Oct", + "Nov","Dic"]]; + + $$d{"day_name"}= + [["Lunes","Martes","Miercoles","Jueves","Viernes","Sabado","Domingo"]]; + $$d{"day_abb"}= + [["Lun","Mar","Mie","Jue","Vie","Sab","Dom"]]; + $$d{"day_char"}= + [["L","Ma","Mi","J","V","S","D"]]; + + $$d{"num_suff"}= + [["1o","2o","3o","4o","5o","6o","7o","8o","9o","10o", + "11o","12o","13o","14o","15o","16o","17o","18o","19o","20o", + "21o","22o","23o","24o","25o","26o","27o","28o","29o","30o","31o"], + ["1a","2a","3a","4a","5a","6a","7a","8a","9a","10a", + "11a","12a","13a","14a","15a","16a","17a","18a","19a","20a", + "21a","22a","23a","24a","25a","26a","27a","28a","29a","30a","31a"]]; + $$d{"num_word"}= + [["Primero","Segundo","Tercero","Cuarto","Quinto","Sexto","Septimo", + "Octavo","Noveno","Decimo","Decimo Primero","Decimo Segundo", + "Decimo Tercero","Decimo Cuarto","Decimo Quinto","Decimo Sexto", + "Decimo Septimo","Decimo Octavo","Decimo Noveno","Vigesimo", + "Vigesimo Primero","Vigesimo Segundo","Vigesimo Tercero", + "Vigesimo Cuarto","Vigesimo Quinto","Vigesimo Sexto", + "Vigesimo Septimo","Vigesimo Octavo","Vigesimo Noveno","Trigesimo", + "Trigesimo Primero"], + ["Primera","Segunda","Tercera","Cuarta","Quinta","Sexta","Septima", + "Octava","Novena","Decima","Decimo Primera","Decimo Segunda", + "Decimo Tercera","Decimo Cuarta","Decimo Quinta","Decimo Sexta", + "Decimo Septima","Decimo Octava","Decimo Novena","Vigesima", + "Vigesimo Primera","Vigesimo Segunda","Vigesimo Tercera", + "Vigesimo Cuarta","Vigesimo Quinta","Vigesimo Sexta", + "Vigesimo Septima","Vigesimo Octava","Vigesimo Novena","Trigesima", + "Trigesimo Primera"]]; + + $$d{"now"} =["Hoy","Ahora"]; + $$d{"last"} =["ultimo"]; + $$d{"each"} =["cada"]; + $$d{"of"} =["en","de"]; + $$d{"at"} =["a"]; + $$d{"on"} =["el"]; + $$d{"future"} =["en"]; + $$d{"past"} =["hace"]; + $$d{"next"} =["siguiente"]; + $$d{"prev"} =["anterior"]; + $$d{"later"} =["later"]; + + $$d{"exact"} =["exactamente"]; + $$d{"approx"} =["aproximadamente"]; + $$d{"business"}=["laborales"]; + + $$d{"offset"} =["ayer","-0:0:0:1:0:0:0","manana","+0:0:0:1:0:0:0"]; + $$d{"times"} =["mediodia","12:00:00","medianoche","00:00:00"]; + + $$d{"years"} =["a","ano","ano","anos","anos"]; + $$d{"months"} =["m","mes","mes","meses"]; + $$d{"weeks"} =["sem","semana","semana","semanas"]; + $$d{"days"} =["d","dia","dias"]; + $$d{"hours"} =["hr","hrs","hora","horas"]; + $$d{"minutes"} =["min","min","minuto","minutos"]; + $$d{"seconds"} =["s","seg","segundo","segundos"]; + $$d{"replace"} =["m","mes"]; + + $$d{"sephm"} =':'; + $$d{"sepms"} =':'; + $$d{"sepss"} ='[.:]'; + + $$d{"am"} = ["AM","A.M."]; + $$d{"pm"} = ["PM","P.M."]; +} + +sub Date_Init_Portuguese { + print "DEBUG: Date_Init_Portuguese\n" if ($Curr{"Debug"} =~ /trace/); + my($d)=@_; + my(%h)=(); + &Char_8Bit(\%h); + my($o) = $h{"-o"}; + my($c) = $h{",c"}; + my($a) = $h{"a'"}; + my($e) = $h{"e'"}; + my($u) = $h{"u'"}; + my($o2)= $h{"o'"}; + my($a2)= $h{"a`"}; + my($a3)= $h{"a~"}; + my($e2)= $h{"e^"}; + + $$d{"month_name"}= + [["Janeiro","Fevereiro","Marco","Abril","Maio","Junho", + "Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"], + ["Janeiro","Fevereiro","Mar${c}o","Abril","Maio","Junho", + "Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"]]; + + $$d{"month_abb"}= + [["Jan","Fev","Mar","Abr","Mai","Jun", + "Jul","Ago","Set","Out","Nov","Dez"]]; + + $$d{"day_name"}= + [["Segunda","Terca","Quarta","Quinta","Sexta","Sabado","Domingo"], + ["Segunda","Ter${c}a","Quarta","Quinta","Sexta","S${a}bado","Domingo"]]; + $$d{"day_abb"}= + [["Seg","Ter","Qua","Qui","Sex","Sab","Dom"], + ["Seg","Ter","Qua","Qui","Sex","S${a}b","Dom"]]; + $$d{"day_char"}= + [["Sg","T","Qa","Qi","Sx","Sb","D"]]; + + $$d{"num_suff"}= + [["1${o}","2${o}","3${o}","4${o}","5${o}","6${o}","7${o}","8${o}", + "9${o}","10${o}","11${o}","12${o}","13${o}","14${o}","15${o}", + "16${o}","17${o}","18${o}","19${o}","20${o}","21${o}","22${o}", + "23${o}","24${o}","25${o}","26${o}","27${o}","28${o}","29${o}", + "30${o}","31${o}"]]; + $$d{"num_word"}= + [["primeiro","segundo","terceiro","quarto","quinto","sexto","setimo", + "oitavo","nono","decimo","decimo primeiro","decimo segundo", + "decimo terceiro","decimo quarto","decimo quinto","decimo sexto", + "decimo setimo","decimo oitavo","decimo nono","vigesimo", + "vigesimo primeiro","vigesimo segundo","vigesimo terceiro", + "vigesimo quarto","vigesimo quinto","vigesimo sexto","vigesimo setimo", + "vigesimo oitavo","vigesimo nono","trigesimo","trigesimo primeiro"], + ["primeiro","segundo","terceiro","quarto","quinto","sexto","s${e}timo", + "oitavo","nono","d${e}cimo","d${e}cimo primeiro","d${e}cimo segundo", + "d${e}cimo terceiro","d${e}cimo quarto","d${e}cimo quinto", + "d${e}cimo sexto","d${e}cimo s${e}timo","d${e}cimo oitavo", + "d${e}cimo nono","vig${e}simo","vig${e}simo primeiro", + "vig${e}simo segundo","vig${e}simo terceiro","vig${e}simo quarto", + "vig${e}simo quinto","vig${e}simo sexto","vig${e}simo s${e}timo", + "vig${e}simo oitavo","vig${e}simo nono","trig${e}simo", + "trig${e}simo primeiro"]]; + + $$d{"now"} =["agora","hoje"]; + $$d{"last"} =["${u}ltimo","ultimo"]; + $$d{"each"} =["cada"]; + $$d{"of"} =["da","do"]; + $$d{"at"} =["as","${a2}s"]; + $$d{"on"} =["na","no"]; + $$d{"future"} =["em"]; + $$d{"past"} =["a","${a2}"]; + $$d{"next"} =["proxima","proximo","pr${o2}xima","pr${o2}ximo"]; + $$d{"prev"} =["ultima","ultimo","${u}ltima","${u}ltimo"]; + $$d{"later"} =["passadas","passados"]; + + $$d{"exact"} =["exactamente"]; + $$d{"approx"} =["aproximadamente"]; + $$d{"business"}=["util","uteis"]; + + $$d{"offset"} =["ontem","-0:0:0:1:0:0:0", + "amanha","+0:0:0:1:0:0:0","amanh${a3}","+0:0:0:1:0:0:0"]; + $$d{"times"} =["meio-dia","12:00:00","meia-noite","00:00:00"]; + + $$d{"years"} =["anos","ano","ans","an","a"]; + $$d{"months"} =["meses","m${e2}s","mes","m"]; + $$d{"weeks"} =["semanas","semana","sem","sems","s"]; + $$d{"days"} =["dias","dia","d"]; + $$d{"hours"} =["horas","hora","hr","hrs"]; + $$d{"minutes"} =["minutos","minuto","min","mn"]; + $$d{"seconds"} =["segundos","segundo","seg","sg"]; + $$d{"replace"} =["m","mes","s","sems"]; + + $$d{"sephm"} =':'; + $$d{"sepms"} =':'; + $$d{"sepss"} ='[,]'; + + $$d{"am"} = ["AM","A.M."]; + $$d{"pm"} = ["PM","P.M."]; +} + +sub Date_Init_Russian { + print "DEBUG: Date_Init_Russian\n" if ($Curr{"Debug"} =~ /trace/); + my($d)=@_; + my(%h)=(); + &Char_8Bit(\%h); + my($a) =$h{"a:"}; + + $$d{"month_name"}= + [ + ["\xd1\xce\xd7\xc1\xd2\xd1","\xc6\xc5\xd7\xd2\xc1\xcc\xd1", + "\xcd\xc1\xd2\xd4\xc1","\xc1\xd0\xd2\xc5\xcc\xd1","\xcd\xc1\xd1", + "\xc9\xc0\xce\xd1", + "\xc9\xc0\xcc\xd1","\xc1\xd7\xc7\xd5\xd3\xd4\xc1", + "\xd3\xc5\xce\xd4\xd1\xc2\xd2\xd1","\xcf\xcb\xd4\xd1\xc2\xd2\xd1", + "\xce\xcf\xd1\xc2\xd2\xd1","\xc4\xc5\xcb\xc1\xc2\xd2\xd1"], + ["\xd1\xce\xd7\xc1\xd2\xd8","\xc6\xc5\xd7\xd2\xc1\xcc\xd8", + "\xcd\xc1\xd2\xd4","\xc1\xd0\xd2\xc5\xcc\xd8","\xcd\xc1\xca", + "\xc9\xc0\xce\xd8", + "\xc9\xc0\xcc\xd8","\xc1\xd7\xc7\xd5\xd3\xd4", + "\xd3\xc5\xce\xd4\xd1\xc2\xd2\xd8","\xcf\xcb\xd4\xd1\xc2\xd2\xd8", + "\xce\xcf\xd1\xc2\xd2\xd8","\xc4\xc5\xcb\xc1\xc2\xd2\xd8"] + ]; + + $$d{"month_abb"}= + [["\xd1\xce\xd7","\xc6\xc5\xd7","\xcd\xd2\xd4","\xc1\xd0\xd2", + "\xcd\xc1\xca","\xc9\xc0\xce", + "\xc9\xc0\xcc","\xc1\xd7\xc7","\xd3\xce\xd4","\xcf\xcb\xd4", + "\xce\xcf\xd1\xc2","\xc4\xc5\xcb"], + ["","\xc6\xd7\xd2","","","\xcd\xc1\xd1","", + "","","\xd3\xc5\xce","\xcf\xcb\xd4","\xce\xcf\xd1",""]]; + + $$d{"day_name"}= + [["\xd0\xcf\xce\xc5\xc4\xc5\xcc\xd8\xce\xc9\xcb", + "\xd7\xd4\xcf\xd2\xce\xc9\xcb","\xd3\xd2\xc5\xc4\xc1", + "\xde\xc5\xd4\xd7\xc5\xd2\xc7","\xd0\xd1\xd4\xce\xc9\xc3\xc1", + "\xd3\xd5\xc2\xc2\xcf\xd4\xc1", + "\xd7\xcf\xd3\xcb\xd2\xc5\xd3\xc5\xce\xd8\xc5"]]; + $$d{"day_abb"}= + [["\xd0\xce\xc4","\xd7\xd4\xd2","\xd3\xd2\xc4","\xde\xd4\xd7", + "\xd0\xd4\xce","\xd3\xd5\xc2","\xd7\xd3\xcb"], + ["\xd0\xcf\xce","\xd7\xd4\xcf","\xd3\xd2e","\xde\xc5\xd4", + "\xd0\xd1\xd4","\xd3\xd5\xc2","\xd7\xcf\xd3\xcb"]]; + $$d{"day_char"}= + [["\xd0\xce","\xd7\xd4","\xd3\xd2","\xde\xd4","\xd0\xd4","\xd3\xc2", + "\xd7\xd3"]]; + + $$d{"num_suff"}= + [["1 ","2 ","3 ","4 ","5 ","6 ","7 ","8 ","9 ","10 ", + "11 ","12 ","13 ","14 ","15 ","16 ","17 ","18 ","19 ","20 ", + "21 ","22 ","23 ","24 ","25 ","26 ","27 ","28 ","29 ","30 ", + "31 "]]; + $$d{"num_word"}= + [["\xd0\xc5\xd2\xd7\xd9\xca","\xd7\xd4\xcf\xd2\xcf\xca", + "\xd4\xd2\xc5\xd4\xc9\xca","\xde\xc5\xd4\xd7\xc5\xd2\xd4\xd9\xca", + "\xd0\xd1\xd4\xd9\xca","\xdb\xc5\xd3\xd4\xcf\xca", + "\xd3\xc5\xc4\xd8\xcd\xcf\xca","\xd7\xcf\xd3\xd8\xcd\xcf\xca", + "\xc4\xc5\xd7\xd1\xd4\xd9\xca","\xc4\xc5\xd3\xd1\xd4\xd9\xca", + "\xcf\xc4\xc9\xce\xce\xc1\xc4\xc3\xc1\xd4\xd9\xca", + "\xc4\xd7\xc5\xce\xc1\xc4\xde\xc1\xd4\xd9\xca", + "\xd4\xd2\xc5\xce\xc1\xc4\xc3\xc1\xd4\xd9\xca", + "\xde\xc5\xd4\xd9\xd2\xce\xc1\xc4\xc3\xc1\xd4\xd9\xca", + "\xd0\xd1\xd4\xce\xc1\xc4\xc3\xc1\xd4\xd9\xca", + "\xdb\xc5\xd3\xd4\xce\xc1\xc4\xc3\xc1\xd4\xd9\xca", + "\xd3\xc5\xcd\xd8\xce\xc1\xc4\xc3\xc1\xd4\xd9\xca", + "\xd7\xcf\xd3\xc5\xcd\xd8\xce\xc1\xc4\xc3\xc1\xd4\xd9\xca", + "\xc4\xc5\xd7\xd1\xd4\xce\xc1\xc4\xc3\xc1\xd4\xd9\xca", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd9\xca", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd0\xc5\xd2\xd7\xd9\xca", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd7\xd4\xcf\xd2\xcf\xca", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd4\xd2\xc5\xd4\xc9\xca", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xde\xc5\xd4\xd7\xc5\xd2\xd4\xd9\xca", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd0\xd1\xd4\xd9\xca", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xdb\xc5\xd3\xd4\xcf\xca", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd3\xc5\xc4\xd8\xcd\xcf\xca", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd7\xcf\xd3\xd8\xcd\xcf\xca", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xc4\xc5\xd7\xd1\xd4\xd9\xca", + "\xd4\xd2\xc9\xc4\xc3\xc1\xd4\xd9\xca", + "\xd4\xd2\xc9\xc4\xc3\xc1\xd4\xd8 \xd0\xc5\xd2\xd7\xd9\xca"], + + ["\xd0\xc5\xd2\xd7\xcf\xc5","\xd7\xd4\xcf\xd2\xcf\xc5", + "\xd4\xd2\xc5\xd4\xd8\xc5","\xde\xc5\xd4\xd7\xc5\xd2\xd4\xcf\xc5", + "\xd0\xd1\xd4\xcf\xc5","\xdb\xc5\xd3\xd4\xcf\xc5", + "\xd3\xc5\xc4\xd8\xcd\xcf\xc5","\xd7\xcf\xd3\xd8\xcd\xcf\xc5", + "\xc4\xc5\xd7\xd1\xd4\xcf\xc5","\xc4\xc5\xd3\xd1\xd4\xcf\xc5", + "\xcf\xc4\xc9\xce\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc5", + "\xc4\xd7\xc5\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc5", + "\xd4\xd2\xc5\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc5", + "\xde\xc5\xd4\xd9\xd2\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc5", + "\xd0\xd1\xd4\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc5", + "\xdb\xc5\xd3\xd4\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc5", + "\xd3\xc5\xcd\xd8\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc5", + "\xd7\xcf\xd3\xc5\xcd\xd8\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc5", + "\xc4\xc5\xd7\xd1\xd4\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc5", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xcf\xc5", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd0\xc5\xd2\xd7\xcf\xc5", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd7\xd4\xcf\xd2\xcf\xc5", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd4\xd2\xc5\xd4\xd8\xc5", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xde\xc5\xd4\xd7\xc5\xd2\xd4\xcf\xc5", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd0\xd1\xd4\xcf\xc5", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xdb\xc5\xd3\xd4\xcf\xc5", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd3\xc5\xc4\xd8\xcd\xcf\xc5", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd7\xcf\xd3\xd8\xcd\xcf\xc5", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xc4\xc5\xd7\xd1\xd4\xcf\xc5", + "\xd4\xd2\xc9\xc4\xc3\xc1\xd4\xcf\xc5", + "\xd4\xd2\xc9\xc4\xc3\xc1\xd4\xd8 \xd0\xc5\xd2\xd7\xcf\xc5"], + + ["\xd0\xc5\xd2\xd7\xcf\xc7\xcf","\xd7\xd4\xcf\xd2\xcf\xc7\xcf", + "\xd4\xd2\xc5\xd4\xd8\xc5\xc7\xcf", + "\xde\xc5\xd4\xd7\xc5\xd2\xd4\xcf\xc7\xcf","\xd0\xd1\xd4\xcf\xc7\xcf", + "\xdb\xc5\xd3\xd4\xcf\xc7\xcf","\xd3\xc5\xc4\xd8\xcd\xcf\xc7\xcf", + "\xd7\xcf\xd3\xd8\xcd\xcf\xc7\xcf", + "\xc4\xc5\xd7\xd1\xd4\xcf\xc7\xcf","\xc4\xc5\xd3\xd1\xd4\xcf\xc7\xcf", + "\xcf\xc4\xc9\xce\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf", + "\xc4\xd7\xc5\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf", + "\xd4\xd2\xc5\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf", + "\xde\xc5\xd4\xd9\xd2\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf", + "\xd0\xd1\xd4\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf", + "\xdb\xc5\xd3\xd4\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf", + "\xd3\xc5\xcd\xd8\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf", + "\xd7\xcf\xd3\xc5\xcd\xd8\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf", + "\xc4\xc5\xd7\xd1\xd4\xce\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xcf\xc7\xcf", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd0\xc5\xd2\xd7\xcf\xc7\xcf", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd7\xd4\xcf\xd2\xcf\xc5", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd4\xd2\xc5\xd4\xd8\xc5\xc7\xcf", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xde\xc5\xd4\xd7\xc5\xd2\xd4\xcf\xc7\xcf", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd0\xd1\xd4\xcf\xc7\xcf", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xdb\xc5\xd3\xd4\xcf\xc7\xcf", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd3\xc5\xc4\xd8\xcd\xcf\xc7\xcf", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xd7\xcf\xd3\xd8\xcd\xcf\xc7\xcf", + "\xc4\xd7\xc1\xc4\xc3\xc1\xd4\xd8 \xc4\xc5\xd7\xd1\xd4\xcf\xc7\xcf", + "\xd4\xd2\xc9\xc4\xc3\xc1\xd4\xcf\xc7\xcf", + "\xd4\xd2\xc9\xc4\xc3\xc1\xd4\xd8 \xd0\xc5\xd2\xd7\xcf\xc7\xcf"]]; + + $$d{"now"} =["\xd3\xc5\xc7\xcf\xc4\xce\xd1","\xd3\xc5\xca\xde\xc1\xd3"]; + $$d{"last"} =["\xd0\xcf\xd3\xcc\xc5\xc4\xce\xc9\xca"]; + $$d{"each"} =["\xcb\xc1\xd6\xc4\xd9\xca"]; + $$d{"of"} =[" "]; + $$d{"at"} =["\xd7"]; + $$d{"on"} =["\xd7"]; + $$d{"future"} =["\xd7\xd0\xc5\xd2\xc5\xc4 \xce\xc1"]; + $$d{"past"} =["\xce\xc1\xda\xc1\xc4 \xce\xc1 "]; + $$d{"next"} =["\xd3\xcc\xc5\xc4\xd5\xc0\xdd\xc9\xca"]; + $$d{"prev"} =["\xd0\xd2\xc5\xc4\xd9\xc4\xd5\xdd\xc9\xca"]; + $$d{"later"} =["\xd0\xcf\xda\xd6\xc5"]; + + $$d{"exact"} =["\xd4\xcf\xde\xce\xcf"]; + $$d{"approx"} =["\xd0\xd2\xc9\xcd\xc5\xd2\xce\xcf"]; + $$d{"business"}=["\xd2\xc1\xc2\xcf\xde\xc9\xc8"]; + + $$d{"offset"} =["\xd0\xcf\xda\xc1\xd7\xde\xc5\xd2\xc1","-0:0:0:2:0:0:0", + "\xd7\xde\xc5\xd2\xc1","-0:0:0:1:0:0:0", + "\xda\xc1\xd7\xd4\xd2\xc1","+0:0:0:1:0:0:0", + "\xd0\xcf\xd3\xcc\xc5\xda\xc1\xd7\xd4\xd2\xc1", + "+0:0:0:2:0:0:0"]; + $$d{"times"} =["\xd0\xcf\xcc\xc4\xc5\xce\xd8","12:00:00", + "\xd0\xcf\xcc\xce\xcf\xde\xd8","00:00:00"]; + + $$d{"years"} =["\xc7","\xc7\xc4","\xc7\xcf\xc4","\xcc\xc5\xd4", + "\xcc\xc5\xd4","\xc7\xcf\xc4\xc1"]; + $$d{"months"} =["\xcd\xc5\xd3","\xcd\xc5\xd3\xd1\xc3", + "\xcd\xc5\xd3\xd1\xc3\xc5\xd7"]; + $$d{"weeks"} =["\xce\xc5\xc4\xc5\xcc\xd1","\xce\xc5\xc4\xc5\xcc\xd8", + "\xce\xc5\xc4\xc5\xcc\xc9","\xce\xc5\xc4\xc5\xcc\xc0"]; + $$d{"days"} =["\xc4","\xc4\xc5\xce\xd8","\xc4\xce\xc5\xca", + "\xc4\xce\xd1"]; + $$d{"hours"} =["\xde","\xde.","\xde\xd3","\xde\xd3\xd7","\xde\xc1\xd3", + "\xde\xc1\xd3\xcf\xd7","\xde\xc1\xd3\xc1"]; + $$d{"minutes"} =["\xcd\xce","\xcd\xc9\xce","\xcd\xc9\xce\xd5\xd4\xc1", + "\xcd\xc9\xce\xd5\xd4"]; + $$d{"seconds"} =["\xd3","\xd3\xc5\xcb","\xd3\xc5\xcb\xd5\xce\xc4\xc1", + "\xd3\xc5\xcb\xd5\xce\xc4"]; + $$d{"replace"} =[]; + + $$d{"sephm"} ="[:\xde]"; + $$d{"sepms"} ="[:\xcd]"; + $$d{"sepss"} ="[:.\xd3]"; + + $$d{"am"} = ["\xc4\xd0","${a}\xf0","${a}.\xf0.","\xce\xcf\xde\xc9", + "\xd5\xd4\xd2\xc1", + "\xc4\xcf \xd0\xcf\xcc\xd5\xc4\xce\xd1"]; + $$d{"pm"} = ["\xd0\xd0","\xf0\xf0","\xf0.\xf0.","\xc4\xce\xd1", + "\xd7\xc5\xde\xc5\xd2\xc1", + "\xd0\xcf\xd3\xcc\xc5 \xd0\xcf\xcc\xd5\xc4\xce\xd1", + "\xd0\xcf \xd0\xcf\xcc\xd5\xc4\xce\xc0"]; +} + +sub Date_Init_Turkish { + print "DEBUG: Date_Init_Turkish\n" if ($Curr{"Debug"} =~ /trace/); + my($d)=@_; + + $$d{"month_name"}= + [ + ["ocak","subat","mart","nisan","mayis","haziran", + "temmuz","agustos","eylul","ekim","kasim","aralik"], + ["ocak","\xfeubat","mart","nisan","may\xfds","haziran", + "temmuz","a\xf0ustos","eyl\xfcl","ekim","kas\xfdm","aral\xfdk"] + ]; + + $$d{"month_abb"}= + [ + ["oca","sub","mar","nis","may","haz", + "tem","agu","eyl","eki","kas","ara"], + ["oca","\xfeub","mar","nis","may","haz", + "tem","a\xf0u","eyl","eki","kas","ara"] + ]; + + $$d{"day_name"}= + [ + ["pazartesi","sali","carsamba","persembe","cuma","cumartesi","pazar"], + ["pazartesi","sal\xfd","\xe7ar\xfeamba","per\xfeembe","cuma", + "cumartesi","pazar"], + ]; + + $$d{"day_abb"}= + [ + ["pzt","sal","car","per","cum","cts","paz"], + ["pzt","sal","\xe7ar","per","cum","cts","paz"], + ]; + + $$d{"day_char"}= + [["Pt","S","Cr","Pr","C","Ct","P"], + ["Pt","S","\xc7","Pr","C","Ct","P"]]; + + $$d{"num_suff"}= + [[ "1.", "2.", "3.", "4.", "5.", "6.", "7.", "8.", "9.", "10.", + "11.", "12.", "13.", "14.", "15.", "16.", "17.", "18.", "19.", "20.", + "21.", "22.", "23.", "24.", "25.", "26.", "27.", "28.", "29.", "30.", + "31."]]; + + $$d{"num_word"}= + [ + ["birinci","ikinci","ucuncu","dorduncu", + "besinci","altinci","yedinci","sekizinci", + "dokuzuncu","onuncu","onbirinci","onikinci", + "onucuncu","ondordoncu", + "onbesinci","onaltinci","onyedinci","onsekizinci", + "ondokuzuncu","yirminci","yirmibirinci","yirmikinci", + "yirmiucuncu","yirmidorduncu", + "yirmibesinci","yirmialtinci","yirmiyedinci","yirmisekizinci", + "yirmidokuzuncu","otuzuncu","otuzbirinci"], + ["birinci","ikinci","\xfc\xe7\xfcnc\xfc","d\xf6rd\xfcnc\xfc", + "be\xfeinci","alt\xfdnc\xfd","yedinci","sekizinci", + "dokuzuncu","onuncu","onbirinci","onikinci", + "on\xfc\xe7\xfcnc\xfc","ond\xf6rd\xfcnc\xfc", + "onbe\xfeinci","onalt\xfdnc\xfd","onyedinci","onsekizinci", + "ondokuzuncu","yirminci","yirmibirinci","yirmikinci", + "yirmi\xfc\xe7\xfcnc\xfc","yirmid\xf6rd\xfcnc\xfc", + "yirmibe\xfeinci","yirmialt\xfdnc\xfd","yirmiyedinci","yirmisekizinci", + "yirmidokuzuncu","otuzuncu","otuzbirinci"] + ]; + + $$d{"now"} =["\xfeimdi", "simdi", "bugun","bug\xfcn"]; + $$d{"last"} =["son", "sonuncu"]; + $$d{"each"} =["her"]; + $$d{"of"} =["of"]; + $$d{"at"} =["saat"]; + $$d{"on"} =["on"]; + $$d{"future"} =["gelecek"]; + $$d{"past"} =["ge\xe7mi\xfe", "gecmis","gecen", "ge\xe7en"]; + $$d{"next"} =["gelecek","sonraki"]; + $$d{"prev"} =["onceki","\xf6nceki"]; + $$d{"later"} =["sonra"]; + + $$d{"exact"} =["tam"]; + $$d{"approx"} =["yakla\xfe\xfdk", "yaklasik"]; + $$d{"business"}=["i\xfe","\xe7al\xfd\xfema","is", "calisma"]; + + $$d{"offset"} =["d\xfcn","-0:0:0:1:0:0:0", + "dun", "-0:0:0:1:0:0:0", + "yar\xfdn","+0:0:0:1:0:0:0", + "yarin","+0:0:0:1:0:0:0"]; + + $$d{"times"} =["\xf6\xf0len","12:00:00", + "oglen","12:00:00", + "yarim","12:300:00", + "yar\xfdm","12:30:00", + "gece yar\xfds\xfd","00:00:00", + "gece yarisi","00:00:00"]; + + $$d{"years"} =["yil","y"]; + $$d{"months"} =["ay","a"]; + $$d{"weeks"} =["hafta", "h"]; + $$d{"days"} =["gun","g"]; + $$d{"hours"} =["saat"]; + $$d{"minutes"} =["dakika","dak","d"]; + $$d{"seconds"} =["saniye","sn",]; + $$d{"replace"} =["s","saat"]; + + $$d{"sephm"} =':'; + $$d{"sepms"} =':'; + $$d{"sepss"} ='[.:,]'; + + $$d{"am"} = ["\xf6gleden \xf6nce","ogleden once"]; + $$d{"pm"} = ["\xf6\xf0leden sonra","ogleden sonra"]; +} + +sub Date_Init_Danish { + print "DEBUG: Date_Init_Danish\n" if ($Curr{"Debug"} =~ /trace/); + my($d)=@_; + + $$d{"month_name"}= + [["Januar","Februar","Marts","April","Maj","Juni", + "Juli","August","September","Oktober","November","December"]]; + $$d{"month_abb"}= + [["Jan","Feb","Mar","Apr","Maj","Jun", + "Jul","Aug","Sep","Okt","Nov","Dec"]]; + + $$d{"day_name"}= + [["Mandag","Tirsdag","Onsdag","Torsdag","Fredag","Lordag","Sondag"], + ["Mandag","Tirsdag","Onsdag","Torsdag","Fredag","L\xf8rdag","S\xf8ndag"]]; + + $$d{"day_abb"}= + [["Man","Tis","Ons","Tor","Fre","Lor","Son"], + ["Man","Tis","Ons","Tor","Fre","L\xf8r","S\xf8n"]]; + $$d{"day_char"}= + [["M","Ti","O","To","F","L","S"]]; + + $$d{"num_suff"}= + [["1:e","2:e","3:e","4:e","5:e","6:e","7:e","8:e","9:e","10:e", + "11:e","12:e","13:e","14:e","15:e","16:e","17:e","18:e","19:e","20:e", + "21:e","22:e","23:e","24:e","25:e","26:e","27:e","28:e","29:e","30:e", + "31:e"]]; + $$d{"num_word"}= + [["forste","anden","tredie","fjerde","femte","sjette","syvende", + "ottende","niende","tiende","elfte","tolvte","trettende","fjortende", + "femtende","sekstende","syttende","attende","nittende","tyvende", + "enogtyvende","toogtyvende","treogtyvende","fireogtyvende","femogtyvende", + "seksogtyvende","syvogtyvende","otteogtyvende","niogtyvende", + "tredivte","enogtredivte"], + ["f\xf8rste","anden","tredie","fjerde","femte","sjette","syvende", + "ottende","niende","tiende","elfte","tolvte","trettende","fjortende", + "femtende","sekstende","syttende","attende","nittende","tyvende", + "enogtyvende","toogtyvende","treogtyvende","fireogtyvende","femogtyvende", + "seksogtyvende","syvogtyvende","otteogtyvende","niogtyvende", + "tredivte","enogtredivte"]]; + + $$d{"now"} =["idag","nu"]; + $$d{"last"} =["forrige","sidste","nyeste"]; + $$d{"each"} =["hver"]; + $$d{"of"} =["om"]; + $$d{"at"} =["kl","kl.","klokken"]; + $$d{"on"} =["pa","p\xe5"]; + $$d{"future"} =["om"]; + $$d{"past"} =["siden"]; + $$d{"next"} =["nasta","n\xe6ste"]; + $$d{"prev"} =["forrige"]; + $$d{"later"} =["senere"]; + + $$d{"exact"} =["pracist","pr\xe6cist"]; + $$d{"approx"} =["circa"]; + $$d{"business"}=["arbejdsdag","arbejdsdage"]; + + $$d{"offset"} =["ig\xe5r","-0:0:0:1:0:0:0","igar","-0:0:0:1:0:0:0", + "imorgen","+0:0:0:1:0:0:0"]; + $$d{"times"} =["midt pa dagen","12:00:00","midt p\xe5 dagen","12:00:00", + "midnat","00:00:00"]; + + $$d{"years"} =["ar","\xe5r"]; + $$d{"months"} =["man","maned","maneder","m\xe5n","m\xe5ned","m\xe5neder"]; + $$d{"weeks"} =["u","uge","uger"]; + $$d{"days"} =["d","dag","dage"]; + $$d{"hours"} =["t","tim","time","timer"]; + $$d{"minutes"} =["min","minut","minutter"]; + $$d{"seconds"} =["s","sek","sekund","sekunder"]; + $$d{"replace"} =["m","minut"]; + + $$d{"sephm"} ='[.:]'; + $$d{"sepms"} =':'; + $$d{"sepss"} ='[.:]'; + + $$d{"am"} = ["FM"]; + $$d{"pm"} = ["EM"]; +} + +######################################################################## +# FROM MY PERSONAL LIBRARIES +######################################################################## + +no integer; + +# &ModuloAddition($N,$add,\$val,\$rem); +# This calculates $val=$val+$add and forces $val to be in a certain range. +# This is useful for adding numbers for which only a certain range is +# allowed (for example, minutes can be between 0 and 59 or months can be +# between 1 and 12). The absolute value of $N determines the range and +# the sign of $N determines whether the range is 0 to N-1 (if N>0) or +# 1 to N (N<0). The remainder (as modulo N) is added to $rem. +# Example: +# To add 2 hours together (with the excess returned in days) use: +# &ModuloAddition(60,$s1,\$s,\$day); +sub ModuloAddition { + my($N,$add,$val,$rem)=@_; + return if ($N==0); + $$val+=$add; + if ($N<0) { + # 1 to N + $N = -$N; + if ($$val>$N) { + $$rem+= int(($$val-1)/$N); + $$val = ($$val-1)%$N +1; + } elsif ($$val<1) { + $$rem-= int(-$$val/$N)+1; + $$val = $N-(-$$val % $N); + } + + } else { + # 0 to N-1 + if ($$val>($N-1)) { + $$rem+= int($$val/$N); + $$val = $$val%$N; + } elsif ($$val<0) { + $$rem-= int(-($$val+1)/$N)+1; + $$val = ($N-1)-(-($$val+1)%$N); + } + } +} + +# $Flag=&IsInt($String [,$low, $high]); +# Returns 1 if $String is a valid integer, 0 otherwise. If $low is +# entered, $String must be >= $low. If $high is entered, $String must +# be <= $high. It is valid to check only one of the bounds. +sub IsInt { + my($N,$low,$high)=@_; + return 0 if (! defined $N or + $N !~ /^\s*[-+]?\d+\s*$/ or + defined $low && $N<$low or + defined $high && $N>$high); + return 1; +} + +# $Pos=&SinLindex(\@List,$Str [,$offset [,$CaseInsensitive]]); +# Searches for an exact string in a list. +# +# This is similar to RinLindex except that it searches for elements +# which are exactly equal to $Str (possibly case insensitive). +sub SinLindex { + my($listref,$Str,$offset,$Insensitive)=@_; + my($i,$len,$tmp)=(); + $len=$#$listref; + return -2 if ($len<0 or ! $Str); + return -1 if (&Index_First(\$offset,$len)); + $Str=uc($Str) if ($Insensitive); + for ($i=$offset; $i<=$len; $i++) { + $tmp=$$listref[$i]; + $tmp=uc($tmp) if ($Insensitive); + return $i if ($tmp eq $Str); + } + return -1; +} + +sub Index_First { + my($offsetref,$max)=@_; + $$offsetref=0 if (! $$offsetref); + if ($$offsetref < 0) { + $$offsetref += $max + 1; + $$offsetref=0 if ($$offsetref < 0); + } + return -1 if ($$offsetref > $max); + return 0; +} + +# $File=&CleanFile($file); +# This cleans up a path to remove the following things: +# double slash /a//b -> /a/b +# trailing dot /a/. -> /a +# leading dot ./a -> a +# trailing slash a/ -> a +sub CleanFile { + my($file)=@_; + $file =~ s/\s*$//; + $file =~ s/^\s*//; + $file =~ s|//+|/|g; # multiple slash + $file =~ s|/\.$|/|; # trailing /. (leaves trailing slash) + $file =~ s|^\./|| # leading ./ + if ($file ne "./"); + $file =~ s|/$|| # trailing slash + if ($file ne "/"); + return $file; +} + +# $File=&ExpandTilde($file); +# This checks to see if a "~" appears as the first character in a path. +# If it does, the "~" expansion is interpreted (if possible) and the full +# path is returned. If a "~" expansion is used but cannot be +# interpreted, an empty string is returned. +# +# This is Windows/Mac friendly. +# This is efficient. +sub ExpandTilde { + my($file)=shift; + my($user,$home)=(); + # ~aaa/bbb= ~ aaa /bbb + if ($file =~ s|^~([^/]*)||) { + $user=$1; + # Single user operating systems (Mac, MSWindows) don't have the getpwnam + # and getpwuid routines defined. Try to catch various different ways + # of knowing we are on one of these systems: + return "" if ($OS eq "Windows" or + $OS eq "Mac" or + $OS eq "Netware" or + $OS eq "MPE"); + $user="" if (! defined $user); + + if ($user) { + $home= (getpwnam($user))[7]; + } else { + $home= (getpwuid($<))[7]; + } + $home = VMS::Filespec::unixpath($home) if ($OS eq "VMS"); + return "" if (! $home); + $file="$home/$file"; + } + $file; +} + +# $File=&FullFilePath($file); +# Returns the full or relative path to $file (expanding "~" if necessary). +# Returns an empty string if a "~" expansion cannot be interpreted. The +# path does not need to exist. CleanFile is called. +sub FullFilePath { + my($file)=shift; + my($rootpat) = '^/'; #default pattern to match absolute path + $rootpat = '^(\\|/|([A-Za-z]:[\\/]))' if ($OS eq 'Windows'); + $file=&ExpandTilde($file); + return "" if (! $file); + return &CleanFile($file); +} + +# $Flag=&CheckFilePath($file [,$mode]); +# Checks to see if $file exists, to see what type it is, and whether +# the script can access it. If it exists and has the correct mode, 1 +# is returned. +# +# $mode is a string which may contain any of the valid file test operator +# characters except t, M, A, C. The appropriate test is run for each +# character. For example, if $mode is "re" the -r and -e tests are both +# run. +# +# An empty string is returned if the file doesn't exist. A 0 is returned +# if the file exists but any test fails. +# +# All characters in $mode which do not correspond to valid tests are +# ignored. +sub CheckFilePath { + my($file,$mode)=@_; + my($test)=(); + $file=&FullFilePath($file); + $mode = "" if (! defined $mode); + + # Run tests + return 0 if (! defined $file or ! $file); + return 0 if (( ! -e $file) or + ($mode =~ /r/ && ! -r $file) or + ($mode =~ /w/ && ! -w $file) or + ($mode =~ /x/ && ! -x $file) or + ($mode =~ /R/ && ! -R $file) or + ($mode =~ /W/ && ! -W $file) or + ($mode =~ /X/ && ! -X $file) or + ($mode =~ /o/ && ! -o $file) or + ($mode =~ /O/ && ! -O $file) or + ($mode =~ /z/ && ! -z $file) or + ($mode =~ /s/ && ! -s $file) or + ($mode =~ /f/ && ! -f $file) or + ($mode =~ /d/ && ! -d $file) or + ($mode =~ /l/ && ! -l $file) or + ($mode =~ /s/ && ! -s $file) or + ($mode =~ /p/ && ! -p $file) or + ($mode =~ /b/ && ! -b $file) or + ($mode =~ /c/ && ! -c $file) or + ($mode =~ /u/ && ! -u $file) or + ($mode =~ /g/ && ! -g $file) or + ($mode =~ /k/ && ! -k $file) or + ($mode =~ /T/ && ! -T $file) or + ($mode =~ /B/ && ! -B $file)); + return 1; +} +#&& + +# $Path=&FixPath($path [,$full] [,$mode] [,$error]); +# Makes sure that every directory in $path (a colon separated list of +# directories) appears as a full path or relative path. All "~" +# expansions are removed. All trailing slashes are removed also. If +# $full is non-nil, relative paths are expanded to full paths as well. +# +# If $mode is given, it may be either "e", "r", or "w". In this case, +# additional checking is done to each directory. If $mode is "e", it +# need ony exist to pass the check. If $mode is "r", it must have have +# read and execute permission. If $mode is "w", it must have read, +# write, and execute permission. +# +# The value of $error determines what happens if the directory does not +# pass the test. If it is non-nil, if any directory does not pass the +# test, the subroutine returns the empty string. Otherwise, it is simply +# removed from $path. +# +# The corrected path is returned. +sub FixPath { + my($path,$full,$mode,$err)=@_; + local($_)=""; + my(@dir)=split(/$Cnf{"PathSep"}/,$path); + $full=0 if (! defined $full); + $mode="" if (! defined $mode); + $err=0 if (! defined $err); + $path=""; + if ($mode eq "e") { + $mode="de"; + } elsif ($mode eq "r") { + $mode="derx"; + } elsif ($mode eq "w") { + $mode="derwx"; + } + + foreach (@dir) { + + # Expand path + if ($full) { + $_=&FullFilePath($_); + } else { + $_=&ExpandTilde($_); + } + if (! $_) { + return "" if ($err); + next; + } + + # Check mode + if (! $mode or &CheckFilePath($_,$mode)) { + $path .= $Cnf{"PathSep"} . $_; + } else { + return "" if ($err); + } + } + $path =~ s/^$Cnf{"PathSep"}//; + return $path; +} +#&& + +# $File=&SearchPath($file,$path [,$mode] [,@suffixes]); +# Searches through directories in $path for a file named $file. The +# full path is returned if one is found, or an empty string otherwise. +# The file may exist with one of the @suffixes. The mode is checked +# similar to &CheckFilePath. +# +# The first full path that matches the name and mode is returned. If none +# is found, an empty string is returned. +sub SearchPath { + my($file,$path,$mode,@suff)=@_; + my($f,$s,$d,@dir,$fs)=(); + $path=&FixPath($path,1,"r"); + @dir=split(/$Cnf{"PathSep"}/,$path); + foreach $d (@dir) { + $f="$d/$file"; + $f=~ s|//|/|g; + return $f if (&CheckFilePath($f,$mode)); + foreach $s (@suff) { + $fs="$f.$s"; + return $fs if (&CheckFilePath($fs,$mode)); + } + } + return ""; +} + +# @list=&ReturnList($str); +# This takes a string which should be a comma separated list of integers +# or ranges (5-7). It returns a sorted list of all integers referred to +# by the string, or () if there is an invalid element. +# +# Negative integers are also handled. "-2--1" is equivalent to "-2,-1". +sub ReturnList { + my($str)=@_; + my(@ret,@str,$from,$to,$tmp)=(); + @str=split(/,/,$str); + foreach $str (@str) { + if ($str =~ /^[-+]?\d+$/) { + push(@ret,$str); + } elsif ($str =~ /^([-+]?\d+)-([-+]?\d+)$/) { + ($from,$to)=($1,$2); + if ($from>$to) { + $tmp=$from; + $from=$to; + $to=$tmp; + } + push(@ret,$from..$to); + } else { + return (); + } + } + @ret; +} + +1; diff --git a/lib/Date/Manip.pod b/lib/Date/Manip.pod new file mode 100644 index 000000000..7784317a6 --- /dev/null +++ b/lib/Date/Manip.pod @@ -0,0 +1,2755 @@ +# Copyright (c) 1995-2003 Sullivan Beck. All rights reserved. +# This program is free software; you can redistribute it and/or modify it +# under the same terms as Perl itself. + +=head1 NAME + +Date::Manip - date manipulation routines + +=head1 SYNOPSIS + + use Date::Manip; + + $date = ParseDate(\@args); + $date = ParseDate($string); + $date = ParseDate(\$string); + + @date = UnixDate($date,@format); + $date = UnixDate($date,@format); + + $delta = ParseDateDelta(\@args); + $delta = ParseDateDelta($string); + $delta = ParseDateDelta(\$string); + + @str = Delta_Format($delta,$dec,@format); + $str = Delta_Format($delta,$dec,@format); + + $recur = ParseRecur($string,$base,$date0,$date1,$flags); + @dates = ParseRecur($string,$base,$date0,$date1,$flags); + + $flag = Date_Cmp($date1,$date2); + + $d = DateCalc($d1,$d2 [,$errref] [,$del]); + + $date = Date_SetTime($date,$hr,$min,$sec); + $date = Date_SetTime($date,$time); + + $date = Date_SetDateField($date,$field,$val [,$nocheck]); + + $date = Date_GetPrev($date,$dow,$today,$hr,$min,$sec); + $date = Date_GetPrev($date,$dow,$today,$time); + + $date = Date_GetNext($date,$dow,$today,$hr,$min,$sec); + $date = Date_GetNext($date,$dow,$today,$time); + + $version = DateManipVersion; + + $flag = Date_IsWorkDay($date [,$flag]); + + $date = Date_NextWorkDay($date,$off [,$time]); + $date = Date_PrevWorkDay($date,$off [,$time]); + + $name = Date_IsHoliday($date); + + $listref = Events_List($date); + $listref = Events_List($date0,$date1); + + &Date_Init(); + &Date_Init("VAR=VAL","VAR=VAL",...); + @list = Date_Init(); + @list = Date_Init("VAR=VAL","VAR=VAL",...); + +The above routines all check to make sure that Date_Init is called. If it +hasn't been, they will call it automatically. As a result, there is usually +no need to call Date_Init explicitely unless you want to change some of the +config variables (described below). + +The following routines are used by the above routines (though they can also +be called directly). $y may be entered as either a 2 or 4 digit year (it +will be converted to a 4 digit year based on the variable YYtoYYYY +described below). Month and day should be numeric in all cases. Most (if +not all) of the information below can be gotten from UnixDate which is +really the way I intended it to be gotten, but there are reasons to use +these (these are significantly faster). + +***NOTE*** Unlike the routines listed above, the following routines do NOT +explicitely call Date_Init. You must make sure that Date_Init has been +called, either by you explicitely, or by one of the above routines before you +use these routines. + + $day = Date_DayOfWeek($m,$d,$y); + $secs = Date_SecsSince1970($m,$d,$y,$h,$mn,$s); + $secs = Date_SecsSince1970GMT($m,$d,$y,$h,$mn,$s); + $days = Date_DaysSince1BC($m,$d,$y); + $day = Date_DayOfYear($m,$d,$y); + $days = Date_DaysInYear($y); + $wkno = Date_WeekOfYear($m,$d,$y,$first); + $flag = Date_LeapYear($y); + $day = Date_DaySuffix($d); + $tz = Date_TimeZone(); + ($y,$m,$d,$h,$mn,$s) = Date_NthDayOfYear($y,$n); + +=head1 DESCRIPTION + +This is a set of routines designed to make any common date/time +manipulation easy to do. Operations such as comparing two times, +calculating a time a given amount of time from another, or parsing +international times are all easily done. From the very beginning, the main +focus of Date::Manip has been to be able to do ANY desired date/time +operation easily, not necessarily quickly. Also, it is definitely oriented +towards the type of operations we (as people) tend to think of rather than +those operations used routinely by computers. There are other modules that +can do a subset of the operations available in Date::Manip much quicker +than those presented here, so be sure to read the section SHOULD I USE +DATE::MANIP below before deciding which of the Date and Time modules from +CPAN is for you. + +Date::Manip deals with time as it is presented the Gregorian calendar (the +one currently in use). The Julian calendar defined leap years as every 4th +year. The Gregorian calendar improved this by making every 100th year NOT +a leap year, unless it was also the 400th year. The Gregorian calendar has +been extrapolated back to the year 0000 AD and forward to the year 9999 AD. +Note that in historical context, the Julian calendar was in use until 1582 +when the Gregorian calendar was adopted by the Catholic church. Protestant +countries did not accept it until later; Germany and Netherlands in 1698, +British Empire in 1752, Russia in 1918. Note that the Gregorian calendar +is itself imperfect and at some point will need to be corrected. No attempt +is made to correct for that, and my great great great grandchildren will be +long dead before this even occurs, so it's not an immediate concern. Yes, +this is the same type of attitute that caused the great Y2K problem... but +I have an excuse: I don't know what the correction will be, so I can't +possible implement it. Nobody doubted that the year after 1999 would be +known as 2000 :-). + +Date::Manip is therefore not equipped to truly deal with historical dates, +but should be able to perform (virtually) any operation dealing with a +modern time and date. + +Date::Manip has (or will have) functionality to work with several fundamental +types of data. + +=over 4 + +=item DATE + +Although the word date is used extensively here, it is actually somewhat +misleading. Date::Manip works with the full date AND time (year, month, +day, hour, minute, second and weeks when appropriate). It doesn't work +with fractional seconds. Timezones are also supported to some extent. + +NOTE: Much better support for timezones (including Daylight Savings Time) +is planned for the future. + +=item DELTA + +This refers to a duration or elapsed time. One thing to note is that, as +used in this module, a delta refers only to the amount of time elapsed. It +includes no information about a starting or ending time. + +=item RECURRENCE + +A recurrence is simply a notation for defining when a recurring event +occurs. For example, if an event occurs every other Friday or every +4 hours, this can be defined as a recurrence. With a recurrence and a +starting and ending date, you can get a list of dates in that period when +a recurring event occurs. + +=item GRAIN + +The granularity of a time basically refers to how accurate you wish to +treat a date. For example, if you want to compare two dates to see if +they are identical at a granularity of days, then they only have to occur +on the same day. At a granularity of an hour, they have to occur within +an hour of each other, etc. + +NOTE: Support for this will be added in the future. + +=item HOLIDAYS and EVENTS + +These are basically a named time. Holidays are used in business mode +calculations. Events allow things like calendar and scheduling +applications to be designed much more easily. + +=back + +Among other things, Date::Manip allow you to: + +1. Enter a date and be able to choose any format convenient + +2. Compare two dates, entered in widely different formats + to determine which is earlier + +3. Extract any information you want from ANY date using a + format string similar to the Unix date command + +4. Determine the amount of time between two dates + +5. Add a time offset to a date to get a second date (i.e. + determine the date 132 days ago or 2 years and 3 months + after Jan 2, 1992) + +6. Work with dates with dates using international formats + (foreign month names, 12/10/95 referring to October + rather than December, etc.). + +7. To find a list of dates where a recurring event happens. + +Each of these tasks is trivial (one or two lines at most) with this package. + +=head1 EXAMPLES + +In the documentation below, US formats are used, but in most (if not all) +cases, a non-English equivalent will work equally well. + +1. Parsing a date from any convenient format + + $date = ParseDate("today"); + $date = ParseDate("1st thursday in June 1992"); + $date = ParseDate("05/10/93"); + $date = ParseDate("12:30 Dec 12th 1880"); + $date = ParseDate("8:00pm december tenth"); + if (! $date) { + # Error in the date + } + +2. Compare two dates + + $date1 = ParseDate($string1); + $date2 = ParseDate($string2); + $flag = Date_Cmp($date1,$date2); + if ($flag<0) { + # date1 is earlier + } elsif ($flag==0) { + # the two dates are identical + } else { + # date2 is earlier + } + +3. Extract information from a date. + + print &UnixDate("today","It is now %T on %b %e, %Y."); + => "It is now 13:24:08 on Feb 3, 1996." + +4. The amount of time between two dates. + + $date1 = ParseDate($string1); + $date2 = ParseDate($string2); + $delta = DateCalc($date1,$date2,\$err); + => 0:0:WK:DD:HH:MM:SS the weeks, days, hours, minutes, + and seconds between the two + $delta = DateCalc($date1,$date2,\$err,1); + => YY:MM:WK:DD:HH:MM:SS the years, months, etc. between + the two + + Read the documentation below for an explanation of the + difference. + +5. To determine a date a given offset from another. + + $date = DateCalc("today","+ 3hours 12minutes 6 seconds",\$err); + $date = DateCalc("12 hours ago","12:30 6Jan90",\$err); + + It even works with business days: + + $date = DateCalc("today","+ 3 business days",\$err); + +6. To work with dates in another language. + + &Date_Init("Language=French","DateFormat=non-US"); + $date = ParseDate("1er decembre 1990"); + +7. To find a list of dates where a recurring event happens + (including quite complex ones). + + # To find the 2nd tuesday of every month + @date = ParseRecur("0:1*2:2:0:0:0",$base,$start,$stop); + + # To find the Monday after easter in 1997-1999. + @date = ParseRecur("*1997-1999:0:0:0:0:0:0*EASTER,ND1"); + +NOTE: Some date forms do not work as well in languages other than English, +but this is not because Date::Manip is incapable of doing so (almost nothing +in this module is language dependent). It is simply that I do not have the +correct translation available for some words. If there is a date form that +works in English but does not work in a language you need, let me know and +if you can provide me the translation, I will fix Date::Manip. + +=head1 SHOULD I USE DATE::MANIP + +If you look in CPAN, you'll find that there are a number of Date and Time +packages. Is Date::Manip the one you should be using? In my opinion, the +answer is no most of the time. This sounds odd coming from the author of +the software, but read on. + +Date::Manip is written entirely in perl. It's the most powerful of the +date modules. It's also the biggest and slowest. + +Since Date::Manip is written entirely in perl, and depends on no other +module not in a standard perl distribution, Date::Manip has no dependancies +to meet. Other modules have dependancies on a C compiler or other perl +modules. Since it is fairly easy to satisfy these dependancies for +anyone who is reasonably familiar with perl modules, this is not a +huge advantage that Date::Manip has. + +On the other hand, simpler perl modules tend to be faster than Date::Manip, +and modules written in C are significantly faster than their perl +counterparts (at least if they're done right). The TimeDate and +Time-modules modules are written in perl, but are much simpler (and +hence, faster) than Date::Manip. The Date::Calc module is written in C +and is a good module for doing many date calculations much faster than +Date::Manip. Between these three, most of your common date operations +can be done. + +Date::Manip is certainly the most powerful of the Date modules. To the +best of my knowledge, it will do everything that any other date module will +do (not just the ones I listed above), and there are a number of features +that Date::Manip has that none of the other modules have. Date::Manip is +the "Swiss Army Knife" of Date modules. I'm trying to build a library +which can do _EVERY_ conceivable date/time manipulation that you'll run +into in everyday life. + +Although I am working on making Date::Manip faster, it will never be as +fast as other modules. And before anyone asks, Date::Manip will never +be translated to C (at least by me). I write C because I have to. I +write perl because I like to. Date::Manip is something I do because it +interests me, not something I'm paid for. + +Date::Manip is also big. The last time I looked, it's one of the largest +CPAN modules there is. If you ignore modules like Tk, LWP, etc. which are +actually packages of modules, it may be the largest. It's true that +Date::Manip will do almost every date operation you could imagine... but +you rarely need all that power. I'm working on reducing the footprint of +Date::Manip, but even at it's slimmest, it'll outweigh the other modules by +a good bit. + +If you are going to be using the module in cases where performance is an +important factor (started up in a CGI program being run by your web server +5,000 times a second), you should check out one of the other Date or Time +modules in CPAN. If you're only doing fairly simple date operations +(parsing common date formats, finding the difference between two dates, +etc.), the other modules will almost certainly suffice. If you're doing +one operation very repetitively (parsing 10,000 dates from a database), you +are probably better off writing your own functions (perhaps bypassing all +date modules entirely) designed specifically for your needs. + +On the other hand, if you want one solution for all your date needs, don't +need peak speed, or are trying to do more exotic date operations, +Date::Manip is for you. Operations on things like business dates, foreign +language dates, holidays and other recurring events, etc. are available +more-or-less exclusively in Date::Manip. + +=head1 ROUTINES + +=over 4 + +=item ParseDate + + $date = ParseDate(\@args); + $date = ParseDate($string); + $date = ParseDate(\$string); + +This takes an array or a string containing a date and parses it. When the +date is included as an array (for example, the arguments to a program) the +array should contain a valid date in the first one or more elements +(elements after a valid date are ignored). Elements containing a valid +date are shifted from the array. The largest possible number of elements +which can be correctly interpreted as a valid date are always used. If a +string is entered rather than an array, that string is tested for a valid +date. The string is unmodified, even if passed in by reference. + +The real work is done in the ParseDateString routine. + +The ParseDate routine is primarily used to handle command line arguments. +If you have a command where you want to enter a date as a command line +argument, you can use Date::Manip to make something like the following +work: + + mycommand -date Dec 10 1997 -arg -arg2 + +No more reading man pages to find out what date format is required in a +man page. + +Historical note: this is originally why the Date::Manip routines were +written (though long before they were released as the Date::Manip module). +I was using a bunch of programs (primarily batch queue managers) where +dates and times were entered as command line options and I was getting +highly annoyed at the many different (but not compatible) ways that they +had to be entered. Date::Manip originally consisted of basically 1 routine +which I could pass "@ARGV" to and have it remove a date from the beginning. + +=item ParseDateString + + $date = ParseDateString($string); + +This routine is called by ParseDate, but it may also be called directly +to save some time (a negligable amount). + +NOTE: One of the most frequently asked questions that I have gotten +is how to parse seconds since the epoch. ParseDateString cannot simply +parse a number as the seconds since the epoch (it conflicts with some +ISO-8601 date formats). There are two ways to get this information. +First, you can do the following: + + $secs = ... # seconds since Jan 1, 1970 00:00:00 GMT + $date = &DateCalc("Jan 1, 1970 00:00:00 GMT",$secs); + +Second, you can call it directly as: + + $date = &ParseDateString("epoch $secs"); + +To go backwards, just use the "%s" format of UnixDate: + + $secs = &UnixDate($date,"%s"); + +A full date actually includes 2 parts: date and time. A time must include +hours and minutes and can optionally include seconds, fractional seconds, +an am/pm type string, and a timezone. For example: + + [at] HH:MN [Zone] + [at] HH:MN [am] [Zone] + [at] HH:MN:SS [am] [Zone] + [at] HH:MN:SS.SSSS [am] [Zone] + [at] HH am [Zone] + +Hours can be written using 1 or 2 digits, but the single digit form may +only be used when no ambiguity is introduced (i.e. when it is not +immediately preceded by a digit). + +A time is usually entered in 24 hour mode, but 12 hour mode can be used +as well if AM/PM are entered (AM can be entered as AM or A.M. or other +variations depending on the language). + +Fractional seconds are also supported in parsing but the fractional part is +discarded (with NO rounding ocurring). + +Timezones always appear immediately after the time. A number of different +forms are supported (see the section TIMEZONEs below). + +Incidentally, the time is removed from the date before the date is parsed, +so the time may appear before or after the date, or between any two parts +of the date. + +Valid date formats include the ISO 8601 formats: + + YYYYMMDDHHMNSSF... + YYYYMMDDHHMNSS + YYYYMMDDHHMN + YYYYMMDDHH + YY-MMDDHHMNSSF... + YY-MMDDHHMNSS + YY-MMDDHHMN + YY-MMDDHH + YYYYMMDD + YYYYMM + YYYY + YY-MMDD + YY-MM + YY + YYYYwWWD ex. 1965-W02-2 + YYwWWD + YYYYDOY ex. 1965-045 + YYDOY + +In the above list, YYYY and YY signify 4 or 2 digit years, MM, DD, HH, MN, SS +refer to two digit month, day, hour, minute, and second respectively. F... +refers to fractional seconds (any number of digits) which will be ignored. +The last 4 formats can be explained by example: 1965-w02-2 refers to Tuesday +(day 2) of the 2nd week of 1965. 1965-045 refers to the 45th day of 1965. + +In all cases, parts of the date may be separated by dashes "-". If this is +done, 1 or 2 digit forms of MM, DD, etc. may be used. All dashes are +optional except for those given in the table above (which MUST be included +for that format to be correctly parsed). So 19980820, 1998-0820, +1998-08-20, 1998-8-20, and 199808-20 are all equivalent, but that date may +NOT be written as 980820 (it must be written as 98-0820). + +NOTE: Even though not allowed in the standard, the timezone for an ISO-8601 +date is flexible and may be any of the timezones understood by Date::Manip. + +Additional date formats are available which may or may not be common including: + + MM/DD ** + MM/DD/YY ** + MM/DD/YYYY ** + + mmmDD DDmmm mmmYYYY/DD mmmYYYY + mmmDD/YY DDmmmYY DD/YYmmm YYYYmmmDD YYYYmmm + mmmDDYYYY DDmmmYYYY DDYYYYmmm YYYY/DDmmm + +Where mmm refers to the name of a month. All parts of the date can be +separated by valid separators (space, "/", or "."). The separator "-" may +be used as long as it doesn't conflict with an ISO 8601 format, but this +is discouraged since it is easy to overlook conflicts. For example, the +format MM/DD/YY is just fine, but MM-DD-YY does not work since it conflicts +with YY-MM-DD. To be safe, if "-" is used as a separator in a non-ISO +format, they should be turned into "/" before calling the Date::Manip +routines. As with ISO 8601 formats, all separators are optional except for +those given as a "/" in the list above. + +** Note that with these formats, Americans tend to write month first, but +many other countries tend to write day first. The latter behavior can be +obtained by setting the config variable DateFormat to something other than +"US" (see CUSTOMIZING DATE::MANIP below). + +Date separators are treated very flexibly (they are converted to spaces), +so the following dates are all equivalent: + + 12/10/1965 + 12-10 / 1965 + 12 // 10 -. 1965 + +In some cases, this may actually be TOO flexible, but no attempt is made to +trap this. + +Years can be entered as 2 or 4 digits, days and months as 1 or 2 digits. +Both days and months must include 2 digits whenever they are immediately +adjacent to another numeric part of the date or time. Date separators +are required if single digit forms of DD or MM are used. If separators +are not used, the date will either be unparsable or will get parsed +incorrectly. + +Miscellaneous other allowed formats are: + which dofw in mmm in YY "first sunday in june 1996 at 14:00" ** + dofw week num YY "sunday week 22 1995" ** + which dofw YY "22nd sunday at noon" ** + dofw which week YY "sunday 22nd week in 1996" ** + next/last dofw "next friday at noon" + next/last week/month "next month" + in num days/weeks/months "in 3 weeks at 12:00" + num days/weeks/months later "3 weeks later" + num days/weeks/months ago "3 weeks ago" + dofw in num week "Friday in 2 weeks" + in num weeks dofw "in 2 weeks on friday" + dofw num week ago "Friday 2 weeks ago" + num week ago dofw "2 weeks ago friday" + last day in mmm in YY "last day of October" + dofw "Friday" (Friday of current week) + Nth "12th", "1st" (day of current month) + epoch SECS seconds since the epoch (negative values + are supported) + +** Note that the formats "sunday week 22" and "22nd sunday" give very +different bahaviors. "sunday week 22" returns the sunday of the 22nd week +of the year based on how week 1 is defined. ISO 8601 defines week one to +contain Jan 4, so "sunday week 1" might be the first or second sunday of +the current year, or the last sunday of the previous year. "22nd sunday" +gives the actual 22nd time sunday occurs in a given year, regardless of the +definition of a week. + +Note that certain words such as "in", "at", "of", etc. which commonly appear +in a date or time are ignored. Also, the year is always optional. + +In addition, the following strings are recognized: + today (exactly now OR today at a given time if a time is specified) + now (synonym for today) + yesterday (exactly 24 hours ago unless a time is specified) + tomorrow (exactly 24 hours from now unless a time is specifed) + noon (12:00:00) + midnight (00:00:00) +Other languages have similar (and in some cases additional) strings. + +Some things to note: + +All strings are case insensitive. "December" and "DEceMBer" both work. + +When a part of the date is not given, defaults are used: year defaults +to current year; hours, minutes, seconds to 00. + +The year may be entered as 2 or 4 digits. If entered as 2 digits, it will +be converted to a 4 digit year. There are several ways to do this based on +the value of the YYtoYYYY variable (described below). The default behavior +it to force the 2 digit year to be in the 100 year period CurrYear-89 to +CurrYear+10. So in 1996, the range is [1907 to 2006], and the 2 digit year +05 would refer to 2005 but 07 would refer to 1907. See CUSTOMIZING +DATE::MANIP below for information on YYtoYYYY for other methods. + +Dates are always checked to make sure they are valid. + +In all of the formats, the day of week ("Friday") can be entered anywhere +in the date and it will be checked for accuracy. In other words, + "Tue Jul 16 1996 13:17:00" +will work but + "Jul 16 1996 Wednesday 13:17:00" +will not (because Jul 16, 1996 is Tuesday, not Wednesday). Note that +depending on where the weekday comes, it may give unexpected results when +used in array context (with ParseDate). For example, the date +("Jun","25","Sun","1990") would return June 25 of the current year since +Jun 25, 1990 is not Sunday. + +The times "12:00 am", "12:00 pm", and "midnight" are not well defined. For +good or bad, I use the following convention in Date::Manip: + midnight = 12:00am = 00:00:00 + noon = 12:00pm = 12:00:00 +and the day goes from 00:00:00 to 23:59:59. In other words, midnight is the +beginning of a day rather than the end of one. The time 24:00:00 is also +allowed (though it is automatically transformed to 00:00:00 of the following +day). + +The format of the date returned is YYYYMMDDHH:MM:SS. The advantage of this +time format is that two times can be compared using simple string comparisons +to find out which is later. Also, it is readily understood by a human. +Alternate forms can be used if that is more convenient. See Date_Init below +and the config variable Internal. + +NOTE: The format for the date is going to change at some point in the future +to YYYYMMDDHH:MN:SS+HHMN*FLAGS. In order to maintain compatibility, you +should use UnixDate to extract information from a date, and Date_Cmp to compare +two dates. The simple string comparison will only work for dates in the same +timezone. + +=item UnixDate + + @date = UnixDate($date,@format); + $date = UnixDate($date,@format); + +This takes a date and a list of strings containing formats roughly +identical to the format strings used by the UNIX date(1) command. Each +format is parsed and an array of strings corresponding to each format is +returned. + +$date may be any string that can be parsed by ParseDateString. + +The format options are: + + Year + %y year - 00 to 99 + %Y year - 0001 to 9999 + %G year - 0001 to 9999 (see below) + %L year - 0001 to 9999 (see below) + Month, Week + %m month of year - 01 to 12 + %f month of year - " 1" to "12" + %b,%h month abbreviation - Jan to Dec + %B month name - January to December + %U week of year, Sunday + as first day of week - 01 to 53 + %W week of year, Monday + as first day of week - 01 to 53 + Day + %j day of the year - 001 to 366 + %d day of month - 01 to 31 + + %e day of month - " 1" to "31" + %v weekday abbreviation - " S"," M"," T"," W","Th"," F","Sa" + %a weekday abbreviation - Sun to Sat + %A weekday name - Sunday to Saturday + %w day of week - 1 (Monday) to 7 (Sunday) + %E day of month with suffix - 1st, 2nd, 3rd... + Hour + %H hour - 00 to 23 + %k hour - " 0" to "23" + %i hour - " 1" to "12" + %I hour - 01 to 12 + %p AM or PM + Minute, Second, Timezone + %M minute - 00 to 59 + %S second - 00 to 59 + %s seconds from 1/1/1970 GMT- negative if before 1/1/1970 + %o seconds from Jan 1, 1970 + in the current time zone + %Z timezone - "EDT" + %z timezone as GMT offset - "+0100" + Date, Time + %c %a %b %e %H:%M:%S %Y - Fri Apr 28 17:23:15 1995 + %C,%u %a %b %e %H:%M:%S %z %Y - Fri Apr 28 17:25:57 EDT 1995 + %g %a, %d %b %Y %H:%M:%S %z - Fri, 28 Apr 1995 17:23:15 EDT + %D,%x %m/%d/%y - 04/28/95 + %l date in ls(1) format + %b %e $H:$M - Apr 28 17:23 (if within 6 months) + %b %e %Y - Apr 28 1993 (otherwise) + %r %I:%M:%S %p - 05:39:55 PM + %R %H:%M - 17:40 + %T,%X %H:%M:%S - 17:40:58 + %V %m%d%H%M%y - 0428174095 + %Q %Y%m%d - 19961025 + %q %Y%m%d%H%M%S - 19961025174058 + %P %Y%m%d%H%M%S - 1996102517:40:58 + %F %A, %B %e, %Y - Sunday, January 1, 1996 + %J %G-W%W-%w - 1997-W02-2 + %K %Y-%j - 1997-045 + Other formats + %n insert a newline character + %t insert a tab character + %% insert a `%' character + %+ insert a `+' character + The following formats are currently unused but may be used in the future: + NO 1234567890 !@#$^&*()_|-=\`[];',./~{}:<>? + They currently insert the character following the %, but may (and probably + will) change in the future as new formats are added. + +If a lone percent is the final character in a format, it is ignored. + +Note that the ls format (%l) applies to date within the past OR future 6 +months! + +The %U, %W, %L, and %G formats are used to support the ISO-8601 format: +YYYY-wWW-D. In this format, a date is written as a year, the week of +the year, and the day of the week. Technically, the week may be considered +to start on any day of the week, but Sunday and Monday are the two most +common choices, so both are supported. + +The %U and %W formats return a week-of-year number from 01 to 53, and +%L and %G return a 4-digit year corresponding to the week. Most of the +time, the %L and %G formats returns the same value as the %Y format, +but there is a problem with days occuring in the first or last week of +the year. + +The ISO-8601 representation of Jan 1, 1993 written in the YYYY-wWWW-D +format is actually 1992-W53-5. In other words, Jan 1 is treates as being +in the last week of the preceding year. Depending on the year, days in +the first week of a year may belong to the previous year, and days in the +final week of a year may belong to the next year. + +The %L and %U formats contains the year and week-of-year values treating +weeks as starting on Sunday. The %G and %W formats are the year and +week-of-year values treating weeks as starting on Monday. + +%J returns the full ISO-8601 format (%G-W%W-%w). + +The formats used in this routine were originally based on date.pl (version +3.2) by Terry McGonigal, as well as a couple taken from different versions +of the Solaris date(1) command. Also, several have been added which are +unique to Date::Manip. + +=item ParseDateDelta + + $delta = ParseDateDelta(\@args); + $delta = ParseDateDelta($string); + $delta = ParseDateDelta(\$string); + +This takes an array and shifts a valid delta date (an amount of time) +from the array. Recognized deltas are of the form: + +Yy +Mm +Ww +Dd +Hh +MNmn +Ss + examples: + +4 hours +3mn -2second + + 4 hr 3 minutes -2 + 4 hour + 3 min -2 s + +Y:+M:+W:+D:+H:+MN:+S + examples: + 0:0:0:0:4:3:-2 + +4:3:-2 + mixed format + examples: + 4 hour 3:-2 + +A field in the format +Yy is a sign, a number, and a string specifying +the type of field. The sign is "+", "-", or absent (defaults to the +next larger element). The valid strings specifying the field type +are: + y: y, yr, year, years + m: m, mon, month, months + w: w, wk, ws, wks, week, weeks + d: d, day, days + h: h, hr, hour, hours + mn: mn, min, minute, minutes + s: s, sec, second, seconds + +Also, the "s" string may be omitted. The sign, number, and string may +all be separated from each other by any number of whitespaces. + +In the date, all fields must be given in the order: Y M W D H MN S. Any +number of them may be omitted provided the rest remain in the correct +order. In the 2nd (colon) format, from 2 to 7 of the fields may be given. +For example +D:+H:+MN:+S may be given to specify only four of the fields. +In any case, both the MN and S field may be present. No spaces may be +present in the colon format. + +Deltas may also be given as a combination of the two formats. For example, +the following is valid: +Yy +D:+H:+MN:+S. Again, all fields must be given +in the correct order. + +The word "in" may be given (prepended in English) to the delta ("in 5 years") +and the word "ago" may be given (appended in English) ("6 months ago"). The +"in" is completely ignored. The "ago" has the affect of reversing all signs +that appear in front of the components of the delta. I.e. "-12 yr 6 mon ago" +is identical to "+12yr +6mon" (don't forget that there is an implied minus +sign in front of the 6 because when no sign is explicitly given, it carries +the previously entered sign). + +One thing is worth noting. The year/month and day/hour/min/sec parts are +returned in a "normalized" form. That is, the signs are adjusted so as to +be all positive or all negative. For example, "+ 2 day - 2hour" does not +return "0:0:0:2:-2:0:0". It returns "+0:0:0:1:22:0:0" (1 day 22 hours +which is equivalent). I find (and I think most others agree) that this is +a more useful form. + +Since the year/month and day/hour/min/sec parts must be normalized +separately there is the possibility that the sign of the two parts will be +different. So, the delta "+ 2years -10 months - 2 days + 2 hours" produces +the delta "+1:2:-0:1:22:0:0". + +It is possible to include a sign for all elements that is output. See the +configuration variable DeltaSigns below. + +NOTE: The internal format of the delta changed in version 5.30 from +Y:M:D:H:MN:S to Y:M:W:D:H:MN:S . Also, it is going to change again at some +point in the future to Y:M:W:D:H:MN:S*FLAGS . Use the routine Delta_Format +to extract information rather than parsing it yourself. + +=item Delta_Format + + @str = Delta_Format($delta,$dec,@format); + $str = Delta_Format($delta,$dec,@format); + +This is similar to the UnixDate routine except that it extracts information +from a delta. Unlike the UnixDate routine, most of the formats are 2 +characters instead of 1. + +Formats currently understood are: + + %Xv : the value of the field named X + %Xd : the value of the field X, and all smaller fields, expressed in + units of X + %Xh : the value of field X, and all larger fields, expressed in units + of X + %Xt : the value of all fields expressed in units of X + + X is one of y,M,w,d,h,m,s (case sensitive). + + %% : returns a "%" + +NOTE: Delta_Format only understands "exact" relationships, so for any delta +that has a month component, there can be no mixing of the Y/M and +W/D/H/MN/S segments. In other words, the delta 1:6:1:1:1:1:1 has a month +component, so asking for the total number of years (using the %yd format) +will return 1.5 (which is what 1 year 6 months is). For deltas which have +NO month component, the relationship between years and days is known +(365.25 is used) and all formats work as expected (except that formats with +X equal to "M" are not allowed). + +So, the format "%hd" means the values of H, MN, and S expressed in hours. +So for the delta "0:0:0:0:2:30:0", this format returns 2.5. Similarly, the +format "%yd" means the value (in years) of both the Y and M fields, or, +if the month component is 0, it uses Y, W, D, H, MN, S. + +The format "%hh" returns the value of W, D, and H expressed in hours if +the month component is non-zero, or Y, W, D, H if the month component is 0. + +If $dec is non-zero, the %Xd and %Xt values are formatted to contain $dec +decimal places. + +=item ParseRecur + + $recur = ParseRecur($string [,$base,$date0,$date1,$flags]); + @dates = ParseRecur($string [,$base,$date0,$date1,$flags]); + +A recurrence refers to a recurring event. A fully specified recurrence +requires (in most cases) 4 items: a recur description (describing the +frequency of the event), a base date (a date when the event occurred and +which other occurrences are based on), and a start and end date. There may +be one or more flags included which modify the behavior of the recur +description. The fully specified recurrence is written as: + + recur*flags*base*date0*date1 + +Here, base, date0, and date1 are any strings (which must not contain any +asterixes) which can be parsed by ParseDate. flags is a comma separated +list of flags (described below), and recur is a string describing a +recurring event. + +If called in scalar context, it returns a string containing a fully +specified recurrence (or as much of it as can be determined with +unspecified fields left blank). In list context, it returns a list of all +dates referred to by a recurrence if enough information is given in the +recurrence. All dates returned are in the range: + + date0 <= date < date1 + +The argument $string can contain any of the parts of a full recurrence. +For example: + + recur + recur*flags + recur**base*date0*date1 + +The only part which is required is the recur description. Any values +contained in $string are overridden or modified by values passed in as +parameters to ParseRecur. + +A recur description is a string of the format Y:M:W:D:H:MN:S . Exactly one +of the colons may optionally be replaced by an asterisk, or an asterisk may +be prepended to the string. + +Any value "N" to the left of the asterisk refers to the "Nth" one. Any +value to the right of the asterisk refers to a value as it appears on a +calendar/clock. Values to the right can be listed a single values, ranges +(2 numbers separated by a dash "-"), or a comma separated list of values +or ranges. In a few cases, negative values are appropriate. + +This is best illustrated by example. + + 0:0:2:1:0:0:0 every 2 weeks and 1 day + 0:0:0:0:5:30:0 every 5 hours and 30 minutes + 0:0:0:2*12:30:0 every 2 days at 12:30 (each day) + 3*1:0:2:12:0:0 every 3 years on Jan 2 at noon + 0:1*0:2:12,14:0:0 2nd of every month at 12:00 and 14:00 + 1:0:0*45:0:0:0 45th day of every year + 0:1*4:2:0:0:0 4th tuesday (day 2) of every month + 0:1*-1:2:0:0:0 last tuesday of every month + 0:1:0*-2:0:0:0 2nd to last day of every month + 0:0:3*2:0:0:0 every 3rd tuesday (every 3 weeks on 2nd day of week) + 1:0*12:2:0:0:0 tuesday of the 12th week of each year + *1990-1995:12:0:1:0:0:0 + Dec 1 in 1990 through 1995 + + 0:1*2:0:0:0:0 the start of the 2nd week of every month (see Note 2) + 1*1:2:0:0:0:0 the start of the 2nd week in January each year (Note 2) + +I realize that this looks a bit cryptic, but after a discussion on the +CALENDAR mailing list, it looked like there was no concise, flexible +notation for handling recurring events. ISO 8601 notations were very bulky +and lacked the flexibility I wanted. As a result, I developed this +notation (based on crontab formats, but with much more flexibility) which +fits in well with this module, and which is able to express every type of +recurring event I could think of. + +NOTE: If a recurrence has a date0 and date1 in it AND a date0 and date1 +are passed in to the function, both sets of criteria apply. If flags are +passed in, they override any flags in the recurrence UNLESS the flags +passed in start with a plus (+) character in which case they are appended +to the flags in the recurrence. + +NOTE: There is no way to express the following with a single recurrence: + + every day at 12:30 and 1:00 + +You have to use two recurrences to do this. + +NOTE: A recurrence specifying the week of a month is NOT clearly defined +in common usage. What is the 1st week in a month? The behavior (with +respect to this module) is well defined (using the FDn and FIn flags +below), but in common usage, this is so ambiguous that this form should +probably never be used. It is included here solely for the sake of +completeness. + +NOTE: Depending on whether M and W are 0 or nonzero, D means different +things. This is given in the following table. + + M W D (when right of an asterisk) refers to + - - ------------------------------------------- + 0 0 day of year (1-366) + M 0 day of month (1-31) + 0 W day of week (1-7), W refers to the week of year + M W the Wth (1-5 or -1 to -5) occurrence of Dth (1-7) day of week in month + +NOTE: Base dates are only used with some types of recurrences. For example, + + 0:0:3*2:0:0:0 every 3rd tuesday + +requires a base date. If a base date is specified which doesn't match the +criteria (for example, if a base date falling on Monday were passed in with +this recurrence), the base date is moved forward to the first relevant date. + +Other dates do not require a base date. For example: + + 0:0*3:2:0:0:0 third tuesday of every month + +A recurrence written in the above format does NOT provide default values +for base, date0, or date1. They must be specified in order to get a list +of dates. + +A base date is not used entirely. It is only used to provide the parts +necessary for the left part of a recurrence. For example, the recurrence: + + 1:3*0:4:0:0:0 every 1 year, 3 months on the 4th day of the month + +would only use the year and month of the base date. + + +There are a small handful of English strings which can be parsed in place +of a numerical recur description. These include: + + every 2nd day [in 1997] + every 2nd day in June [1997] + 2nd day of every month [in 1997] + 2nd tuesday of every month [in 1997] + last tuesday of every month [in 1997] + every tuesday [in 1997] + every 2nd tuesday [in 1997] + every 2nd tuesday in June [1997] + +Each of these set base, date0, and date1 to a default value (the current +year with Jan 1 being the base date is the default if the year and month +are missing). + +The following flags (case insensitive) are understood: + + MWn : n is 1-7. The first week of the month is the week + which contains the first occurrence of day n (1=Monday). + MW2 means that the first week contains the first Tuesday + of the month. + MDn : n is 1-7. The first week of the month contains the + actual date (1st through 7th). MD4 means that the first + week of the month contains the 4th of that month. + + PDn : n is 1-7. Means the previous day n not counting today + PTn : n is 1-7. Means the previous day n counting today + NDn : n is 1-7. Means the next day n not counting today + NTn : n is 1-7. Means the next day n counting today + + FDn : n is any number. Means step forward n days. + BDn : n is any number. Means step backward n days. + FWn : n is any number. Means step forward n workdays. + BWn : n is any number. Means step backward n workdays. + + CWD : the closest work day (using the TomorrowFirst config variable). + CWN : the closest work day (looking forward first). + CWP : the closest work day (looking backward first). + + NWD : next work day counting today + PWD : previous work day counting today + DWD : next/previous work day (TomorrowFirst config) counting today + + EASTER: select easter for this year (the M, W, D fields are ignored + in the recur). + +NOTE: only one of MWn and MDn can be set. If both are set, only the +last one is used. The default is MW7 (i.e. the first week contains +the first Sunday). + +CWD, CWN, and CWP will usually return the same value, but if you are +starting at the middle day of a 3-day weekend (for example), it will return +either the first work day of the following week, or the last work day of +the previous week depending on whether it looks forward or backward first. + +All flags are applied AFTER the recurrence dates are calculated, and they +may move a date outside of the date0 to date1 range. No check is made for +this. + +The workday flags do not act exactly the same as a business mode calculation. +For example, a date that is Saturday with a FW1 steps forward to the first +workday (i.e. Monday). + +=item Date_Cmp + + $flag = Date_Cmp($date1,$date2); + +This takes two dates and compares them. Almost all dates can be compared +using the perl "cmp" command. The only time this will not work is when +comparing dates in different timezones. This routine will take that into +account. + +NOTE: This routine currently does little more than use "cmp", but once +the internal format for storing dates is in place (where timezone information +is kept as part of the date), this routine will become more important. You +should use this routine in prepartation for that version. + +=item DateCalc + + $d = DateCalc($d1,$d2 [,\$err] [,$mode]); + +This takes two dates, deltas, or one of each and performs the appropriate +calculation with them. Dates must be a string that can be parsed by +&ParseDateString. Deltas must be a string that can be parsed by +&ParseDateDelta. Two deltas add together to form a third delta. A date +and a delta returns a 2nd date. Two dates return a delta (the difference +between the two dates). + +Note that in many cases, it is somewhat ambiguous what the delta actually +refers to. Although it is ALWAYS known how many months in a year, hours in +a day, etc., it is NOT known how many days form a month. As a result, the +part of the delta containing month/year and the part with sec/min/hr/day +must be treated separately. For example, "Mar 31, 12:00:00" plus a delta +of 1month 2days would yield "May 2 12:00:00". The year/month is first +handled while keeping the same date. Mar 31 plus one month is Apr 31 (but +since Apr only has 30 days, it becomes Apr 30). Apr 30 + 2 days is May 2. +As a result, in the case where two dates are entered, the resulting delta +can take on two different forms. By default ($mode=0), an absolutely +correct delta (ignoring daylight savings time) is returned in days, hours, +minutes, and seconds. + +If $mode is 1, the math is done using an approximate mode where a delta is +returned using years and months as well. The year and month part is +calculated first followed by the rest. For example, the two dates "Mar 12 +1995" and "Apr 13 1995" would have an exact delta of "31 days" but in the +approximate mode, it would be returned as "1 month 1 day". Also, "Mar 31" +and "Apr 30" would have deltas of "30 days" or "1 month" (since Apr 31 +doesn't exist, it drops down to Apr 30). Approximate mode is a more human +way of looking at things (you'd say 1 month and 2 days more often then 33 +days), but it is less meaningful in terms of absolute time. In approximate +mode $d1 and $d2 must be dates. If either or both is a delta, the +calculation is done in exact mode. + +If $mode is 2, a business mode is used. That is, the calculation is done +using business days, ignoring holidays, weekends, etc. In order to +correctly use this mode, a config file must exist which contains the +section defining holidays (see documentation on the config file below). +The config file can also define the work week and the hours of the work +day, so it is possible to have different config files for different +businesses. + +For example, if a config file defines the workday as 08:00 to 18:00, a +work week consisting of Mon-Sat, and the standard (American) holidays, then +from Tuesday at 12:00 to the following Monday at 14:00 is 5 days and 2 +hours. If the "end" of the day is reached in a calculation, it +automatically switches to the next day. So, Tuesday at 12:00 plus 6 hours +is Wednesday at 08:00 (provided Wed is not a holiday). Also, a date that +is not during a workday automatically becomes the start of the next +workday. So, Sunday 12:00 and Monday at 03:00 both automatically becomes +Monday at 08:00 (provided Monday is not a holiday). In business mode, any +combination of date and delta may be entered, but a delta should not +contain a year or month field (weeks are fine though). + +See below for some additional comments about business mode calculations. + +Note that a business week is treated the same as an exact week (i.e. from +Tuesday to Tuesday, regardless of holidays). Because this means that the +relationship between days and weeks is NOT unambiguous, when a delta is +produced from two dates, it will be in terms of d/h/mn/s (i.e. no week +field). + +If $mode is 3 (which only applies when two dates are passed in), an exact +business mode is used. In this case, it returns a delta as an exact number +of business days/hours/etc. between the two. Weeks, months, and years are +ignored. + +Any other non-nil value of $mode is treated as $mode=1 (approximate mode). + +The mode can be automatically set in the dates/deltas passed by including a +key word somewhere in it. For example, in English, if the word +"approximately" is found in either of the date/delta arguments, approximate +mode is forced. Likewise, if the word "business" or "exactly" appears, +business/exact mode is forced (and $mode is ignored). So, the two +following are equivalent: + + $date = DateCalc("today","+ 2 business days",\$err); + $date = DateCalc("today","+ 2 days",\$err,2); + +Note that if the keyword method is used instead of passing in $mode, it is +important that the keyword actually appear in the argument passed in to +DateCalc. The following will NOT work: + + $delta = ParseDateDelta("+ 2 business days"); + $today = ParseDate("today"); + $date = DateCalc($today,$delta,\$err); + +because the mode keyword is removed from a date/delta by the parse routines, +and the mode is reset each time a parse routine is called. Since DateCalc +parses both of its arguments, whatever mode was previously set is ignored. + +If \$err is passed in, it is set to: + 1 is returned if $d1 is not a delta or date + 2 is returned if $d2 is not a delta or date + 3 is returned if the date is outside the years 1000 to 9999 +This argument is optional, but if included, it must come before $mode. + +Nothing is returned if an error occurs. + +When a delta is returned, the signs such that it is strictly positive or +strictly negative ("1 day - 2 hours" would never be returned for example). +The only time when this cannot be enforced is when two deltas with a +year/month component are entered. In this case, only the signs on the +day/hour/min/sec part are standardized. + +=item Date_SetTime + + $date = Date_SetTime($date,$hr,$min,$sec); + $date = Date_SetTime($date,$time); + +This takes a date (any string that may be parsed by ParseDateString) and +sets the time in that date. For example, one way to get the time for 7:30 +tomorrow would be to use the lines: + + $date = ParseDate("tomorrow"); + $date = Date_SetTime($date,"7:30"); + +Note that in this routine (as well as the other routines below which use +a time argument), no real parsing is done on the times. As a result, + + $date = Date_SetTime($date,"13:30"); + +works, but + + $date = Date_SetTime($date,"1:30 PM"); + +doesn't. + +=item Date_SetDateField + + $date = Date_SetDateField($date,$field,$val [,$nocheck]); + +This takes a date and sets one of it's fields to a new value. $field is +any of the strings "y", "m", "d", "h", "mn", "s" (case insensitive) and +$val is the new value. + +If $nocheck is non-zero, no check is made as to the validity of the date. + +=item Date_GetPrev + + $date = Date_GetPrev($date,$dow, $curr [,$hr,$min,$sec]); + $date = Date_GetPrev($date,$dow, $curr [,$time]); + $date = Date_GetPrev($date,undef,$curr,$hr,$min,$sec); + $date = Date_GetPrev($date,undef,$curr,$time); + +This takes a date (any string that may be parsed by ParseDateString) and finds +the previous occurrence of either a day of the week, or a certain time of day. + +If $dow is defined, the previous occurrence of the day of week is returned. +$dow may either be a string (such as "Fri" or "Friday") or a number +(between 1 and 7). The date of the previous $dow is returned. + +If $date falls on the day of week given by $dow, the date returned depends +on $curr. If $curr is 0, the date returned is a week before $date. If +$curr is 1, the date returned is the same as $date. If $curr is 2, the date +returned (including the time information) is required to be before $date. + +If a time is passed in (either as separate hours, minutes, seconds or as a +time in HH:MM:SS or HH:MM format), the time on this date is set to it. The +following examples should illustrate the use of Date_GetPrev: + + date dow curr time returns + Fri Nov 22 18:15:00 Thu any 12:30 Thu Nov 21 12:30:00 + Fri Nov 22 18:15:00 Fri 0 12:30 Fri Nov 15 12:30:00 + Fri Nov 22 18:15:00 Fri 1/2 12:30 Fri Nov 22 12:30:00 + + Fri Nov 22 18:15:00 Fri 1 18:30 Fri Nov 22 18:30:00 + Fri Nov 22 18:15:00 Fri 2 18:30 Fri Nov 15 18:30:00 + +If $dow is undefined, then a time must be entered, and the date returned is +the previous occurrence of this time. If $curr is non-zero, the current +time is returned if it matches the criteria passed in. In other words, the +time returned is the last time that a digital clock (in 24 hour mode) would +have displayed the time you passed in. If you define hours, minutes and +seconds default to 0 and you might jump back as much as an entire day. If +hours are undefined, you are looking for the last time the minutes/seconds +appeared on the digital clock, so at most, the time will jump back one hour. + + date curr hr min sec returns + Nov 22 18:15:00 0/1 18 undef undef Nov 22 18:00:00 + Nov 22 18:15:00 0/1 18 30 0 Nov 21 18:30:00 + Nov 22 18:15:00 0 18 15 undef Nov 21 18:15:00 + Nov 22 18:15:00 1 18 15 undef Nov 22 18:15:00 + Nov 22 18:15:00 0 undef 15 undef Nov 22 17:15:00 + Nov 22 18:15:00 1 undef 15 undef Nov 22 18:15:00 + +=item Date_GetNext + + $date = Date_GetNext($date,$dow, $curr [,$hr,$min,$sec]); + $date = Date_GetNext($date,$dow, $curr [,$time]); + $date = Date_GetNext($date,undef,$curr,$hr,$min,$sec); + $date = Date_GetNext($date,undef,$curr,$time); + +Similar to Date_GetPrev. + +=item Date_IsHoliday + + $name = Date_IsHoliday($date); + +This returns undef if $date is not a holiday, or a string containing the +name of the holiday otherwise. An empty string is returned for an unnamed +holiday. + +=item Events_List + + $ref = Events_List($date); + $ref = Events_List($date ,0 [,$flag]); + $ref = Events_List($date0,$date1 [,$flag]); + +This returns a list of events. Events are defined in the Events section +of the config file (discussed below). + +In the first form (a single argument), $date is any string containing a +date. A list of events active at that precise time will be returned. +The format is similar to when $flag=0, except only a single time will +be returned. + +In all other cases, a range of times will be used. If the 2nd argument +evaluates to 0, the range of times will be the 24 hour period from +midnight to midnight containing $date. Otherwise, the range is given +by the two dates. + +The value of $flag determines the format of the information that is +returned. + +With $flag=0, the events are returned as a reference to a list of the form: + + [ date, [ list_of_events ], date, [ list_of_events ], ... ] + +For example, if the following events are defined (using the syntax +discussed below in the description of the Event section of the config +file): + + 2000-01-01 ; 2000-03-21 = Winter + 2000-03-22 ; 2000-06-21 = Spring + 2000-02-01 = Event1 + 2000-05-01 = Event2 + 2000-04-01-12:00:00 = Event3 + +might result in the following output: + + &Events_List("2000-04-01") + => [ 2000040100:00:00, [ Spring ] ] + + &Events_List("2000-04-01 12:30"); + => [ 2000040112:30:00, [ Spring, Event3 ] ] + + &Events_List("2000-04-01",0); + => [ 2000040100:00:00, [ Spring ], + 2000040112:00:00, [ Spring, Event3 ], + 2000040113:00:00, [ Spring ] ] + + &Events_List("2000-03-15","2000-04-10"); + => [ 2000031500:00:00, [ Winter ], + 2000032200:00:00, [ Spring ] + 2000040112:00:00, [ Spring, Event3 ] + 2000040113:00:00, [ Spring ] ] + +Much more complicated events can be defined using recurrences. + +When $flag is non-zero, the format of the output is changed. If $flag +is 1, then a tally of the amount of time given to each event is returned. +Time for which two or more events apply is counted for both. + + &Events_List("2000-03-15","2000-04-10",1); + => { Winter => +0:0:1:0:0:0:0, + Spring => +0:0:2:5:0:0:0, + Event3 => +0:0:0:0:1:0:0 } + +When $flag is 2, a more complex tally with no event counted twice is +returned. + + &Events_List("2000-03-15","2000-04-10",2); + => { Winter => +0:0:1:0:0:0:0, + Spring => +0:0:2:4:23:0:0, + Event3+Spring => +0:0:0:0:1:0:0 } + +The hash contains one element for each combination of events. + +=item Date_DayOfWeek + + $day = Date_DayOfWeek($m,$d,$y); + +Returns the day of the week (1 for Monday, 7 for Sunday). + +All arguments must be numeric. + +=item Date_SecsSince1970 + + $secs = Date_SecsSince1970($m,$d,$y,$h,$mn,$s); + +Returns the number of seconds since Jan 1, 1970 00:00 (negative if date is +earlier). + +All arguments must be numeric. + +=item Date_SecsSince1970GMT + + $secs = Date_SecsSince1970GMT($m,$d,$y,$h,$mn,$s); + +Returns the number of seconds since Jan 1, 1970 00:00 GMT (negative if date +is earlier). If CurrTZ is "IGNORE", the number will be identical to +Date_SecsSince1970 (i.e. the date given will be treated as being in GMT). + +All arguments must be numeric. + +=item Date_DaysSince1BC + + $days = Date_DaysSince1BC($m,$d,$y); + +Returns the number of days since Dec 31, 1BC. This includes the year 0000. + +All arguments must be numeric. + +=item Date_DayOfYear + + $day = Date_DayOfYear($m,$d,$y); + +Returns the day of the year (001 to 366) + +All arguments must be numeric. + +=item Date_NthDayOfYear + + ($y,$m,$d,$h,$mn,$s) = Date_NthDayOfYear($y,$n); + +Returns the year, month, day, hour, minutes, and decimal seconds given +a floating point day of the year. + +All arguments must be numeric. $n must be greater than or equal to 1 +and less than 366 on non-leap years and 367 on leap years. + +NOTE: When $n is a decimal number, the results are non-intuitive perhaps. +Day 1 is Jan 01 00:00. Day 2 is Jan 02 00:00. Intuitively, you +might think of day 1.5 as being 1.5 days after Jan 01 00:00, but this +would mean that Day 1.5 was Jan 02 12:00 (which is later than Day 2). +The best way to think of this function is a timeline starting at 1 and +ending at 366 (in a non-leap year). In terms of a delta, think of $n +as the number of days after Dec 31 00:00 of the previous year. + +=item Date_DaysInYear + + $days = Date_DaysInYear($y); + +Returns the number of days in the year (365 or 366) + +=item Date_DaysInMonth + + $days = Date_DaysInMonth($m,$y); + +Returns the number of days in the month. + +=item Date_WeekOfYear + + $wkno = Date_WeekOfYear($m,$d,$y,$first); + +Figure out week number. $first is the first day of the week which is +usually 1 (Monday) or 7 (Sunday), but could be any number between 1 and 7 +in practice. + +All arguments must be numeric. + +NOTE: This routine should only be called in rare cases. Use UnixDate with +the %W, %U, %J, %L formats instead. This routine returns a week between 0 +and 53 which must then be "fixed" to get into the ISO-8601 weeks from 1 to +53. A date which returns a week of 0 actually belongs to the last week of +the previous year. A date which returns a week of 53 may belong to the +first week of the next year. + +=item Date_LeapYear + + $flag = Date_LeapYear($y); + +Returns 1 if the argument is a leap year +Written by David Muir Sharnoff + +=item Date_DaySuffix + + $day = Date_DaySuffix($d); + +Add `st', `nd', `rd', `th' to a date (ie 1st, 22nd, 29th). Works for +international dates. + +=item Date_TimeZone + + $tz = Date_TimeZone; + +This determines and returns the local timezone. If it is unable to determine +the local timezone, the following error occurs: + + ERROR: Date::Manip unable to determine TimeZone. + +See The TIMEZONES section below for more information. + +=item Date_ConvTZ + + $date = Date_ConvTZ($date); + $date = Date_ConvTZ($date,$from); + $date = Date_ConvTZ($date,"",$to); + $date = Date_ConvTZ($date,$from,$to); + +This converts a date (which MUST be in the format returned by ParseDate) +from one timezone to another. + +If it is called with no arguments, the date is converted from the local +timezone to the timezone specified by the config variable ConvTZ (see +documentation on ConvTZ below). If ConvTZ is set to "IGNORE", no +conversion is done. + +If called with $from but no $to, the timezone is converted from the +timezone in $from to ConvTZ (of TZ if ConvTZ is not set). Again, no +conversion is done if ConvTZ is set to "IGNORE". + +If called with $to but no $from, $from defaults to ConvTZ (if set) or the +local timezone otherwise. Although this does not seem immediately obvious, +it actually makes sense. By default, all dates that are parsed are +converted to ConvTZ, so most of the dates being worked with will be stored +in that timezone. + +If Date_ConvTZ is called with both $from and $to, the date is converted +from the timezone $from to $to. + +NOTE: As in all other cases, the $date returned from Date_ConvTZ has no +timezone information included as part of it, so calling UnixDate with the +"%z" format will return the timezone that Date::Manip is working in +(usually the local timezone). + +Example: To convert 2/2/96 noon PST to CST (regardless of what timezone +you are in, do the following: + + $date = ParseDate("2/2/96 noon"); + $date = Date_ConvTZ($date,"PST","CST"); + +Both timezones MUST be in one of the formats listed below in the section +TIMEZONES. + +=item Date_Init + + &Date_Init(); + &Date_Init("VAR=VAL","VAR=VAL",...); + @list = Date_Init(); + @list = Date_Init("VAR=VAL","VAR=VAL",...); + +Normally, it is not necessary to explicitly call Date_Init. The first +time any of the other routines are called, Date_Init will be called to set +everything up. If for some reason you want to change the configuration of +Date::Manip, you can pass the appropriate string or strings into Date_Init +to reinitialize things. + +The strings to pass in are of the form "VAR=VAL". Any number may be +included and they can come in any order. VAR may be any configuration +variable. A list of all configuration variables is given in the section +CUSTOMIZING DATE::MANIP below. VAL is any allowed value for that variable. +For example, to switch from English to French and use non-US format (so +that 12/10 is Oct 12), do the following: + + &Date_Init("Language=French","DateFormat=non-US"); + +If Date_Init is called in list context, it will return a list of all +config variables and their values suitable for passing in to Date_Init +to return Date::Manip to the current state. The only possible problem is +that by default, holidays will not be erased, so you may need to prepend +the "EraseHolidays=1" element to the list. + +=item Date_IsWorkDay + + $flag = Date_IsWorkDay($date [,$flag]); + +This returns 1 if $date is a work day. If $flag is non-zero, the time is +checked to see if it falls within work hours. It returns an empty string +if $date is not valid. + +=item Date_NextWorkDay + + $date = Date_NextWorkDay($date,$off [,$time]); + +Finds the day $off work days from now. If $time is passed in, we must also +take into account the time of day. + +If $time is not passed in, day 0 is today (if today is a workday) or the +next work day if it isn't. In any case, the time of day is unaffected. + +If $time is passed in, day 0 is now (if now is part of a workday) or the +start of the very next work day. + +=item Date_PrevWorkDay + + $date = Date_PrevWorkDay($date,$off [,$time]); + +Similar to Date_NextWorkDay. + +=item Date_NearestWorkDay + + $date = Date_NearestWorkDay($date [,$tomorrowfirst]); + +This looks for the work day nearest to $date. If $date is a work day, it +is returned. Otherwise, it will look forward or backwards in time 1 day +at a time until a work day is found. If $tomorrowfirst is non-zero (or if +it is omitted and the config variable TomorrowFirst is non-zero), we look +to the future first. Otherwise, we look in the past first. In other words, +in a normal week, if $date is Wednesday, $date is returned. If $date is +Saturday, Friday is returned. If $date is Sunday, Monday is returned. If +Wednesday is a holiday, Thursday is returned if $tomorrowfirst is non-nil +or Tuesday otherwise. + +=item DateManipVersion + + $version = DateManipVersion; + +Returns the version of Date::Manip. + +=back + +=head1 TIMEZONES + +The following timezone names are currently understood (and can be used in +parsing dates). These are zones defined in RFC 822. + + Universal: GMT, UT + US zones : EST, EDT, CST, CDT, MST, MDT, PST, PDT + Military : A to Z (except J) + Other : +HHMM or -HHMM + ISO 8601 : +HH:MM, +HH, -HH:MM, -HH + +In addition, the following timezone abbreviations are also accepted. In a +few cases, the same abbreviation is used for two different timezones (for +example, NST stands for Newfoundland Standard -0330 and North Sumatra +0630). +In these cases, only 1 of the two is available. The one preceded by a "#" +sign is NOT available but is documented here for completeness. This list of +zones comes in part from the Time::Zone module by Graham Barr, David Muir +Sharnoff, and Paul Foley (with several additions by myself). + + IDLW -1200 International Date Line West + NT -1100 Nome + HST -1000 Hawaii Standard + CAT -1000 Central Alaska + AHST -1000 Alaska-Hawaii Standard + AKST -0900 Alaska Standard + YST -0900 Yukon Standard + HDT -0900 Hawaii Daylight + AKDT -0800 Alaska Daylight + YDT -0800 Yukon Daylight + PST -0800 Pacific Standard + PDT -0700 Pacific Daylight + MST -0700 Mountain Standard + MDT -0600 Mountain Daylight + CST -0600 Central Standard + CDT -0500 Central Daylight + EST -0500 Eastern Standard + ACT -0500 Brazil, Acre + SAT -0400 Chile + BOT -0400 Bolivia + EDT -0400 Eastern Daylight + AST -0400 Atlantic Standard + AMT -0400 Brazil, Amazon + ACST -0400 Brazil, Acre Daylight + #NST -0330 Newfoundland Standard nst=North Sumatra +0630 + NFT -0330 Newfoundland + #GST -0300 Greenland Standard gst=Guam Standard +1000 + #BST -0300 Brazil Standard bst=British Summer +0100 + BRST -0300 Brazil Standard + BRT -0300 Brazil Standard + AMST -0300 Brazil, Amazon Daylight + ADT -0300 Atlantic Daylight + ART -0300 Argentina + NDT -0230 Newfoundland Daylight + AT -0200 Azores + BRST -0200 Brazil Daylight (official time) + FNT -0200 Brazil, Fernando de Noronha + WAT -0100 West Africa + FNST -0100 Brazil, Fernando de Noronha Daylight + GMT +0000 Greenwich Mean + UT +0000 Universal (Coordinated) + UTC +0000 Universal (Coordinated) + WET +0000 Western European + CET +0100 Central European + FWT +0100 French Winter + MET +0100 Middle European + MEZ +0100 Middle European + MEWT +0100 Middle European Winter + SWT +0100 Swedish Winter + BST +0100 British Summer bst=Brazil standard -0300 + GB +0100 GMT with daylight savings + WEST +0000 Western European Daylight + CEST +0200 Central European Summer + EET +0200 Eastern Europe, USSR Zone 1 + FST +0200 French Summer + MEST +0200 Middle European Summer + MESZ +0200 Middle European Summer + METDST +0200 An alias for MEST used by HP-UX + SAST +0200 South African Standard + SST +0200 Swedish Summer sst=South Sumatra +0700 + EEST +0300 Eastern Europe Summer + BT +0300 Baghdad, USSR Zone 2 + MSK +0300 Moscow + EAT +0300 East Africa + IT +0330 Iran + ZP4 +0400 USSR Zone 3 + MSD +0300 Moscow Daylight + ZP5 +0500 USSR Zone 4 + IST +0530 Indian Standard + ZP6 +0600 USSR Zone 5 + NOVST +0600 Novosibirsk time zone, Russia + NST +0630 North Sumatra nst=Newfoundland Std -0330 + #SST +0700 South Sumatra, USSR Zone 6 sst=Swedish Summer +0200 + JAVT +0700 Java + CCT +0800 China Coast, USSR Zone 7 + AWST +0800 Australian Western Standard + WST +0800 West Australian Standard + PHT +0800 Asia Manila + JST +0900 Japan Standard, USSR Zone 8 + ROK +0900 Republic of Korea + ACST +0930 Australian Central Standard + CAST +0930 Central Australian Standard + AEST +1000 Australian Eastern Standard + EAST +1000 Eastern Australian Standard + GST +1000 Guam Standard, USSR Zone 9 gst=Greenland Std -0300 + ACDT +1030 Australian Central Daylight + CADT +1030 Central Australian Daylight + AEDT +1100 Australian Eastern Daylight + EADT +1100 Eastern Australian Daylight + IDLE +1200 International Date Line East + NZST +1200 New Zealand Standard + NZT +1200 New Zealand + NZDT +1300 New Zealand Daylight + +Others can be added in the future upon request. + +Date::Manip must be able to determine the timezone the user is in. It does +this by looking in the following places: + + $Date::Manip::TZ (set with Date_Init or in Manip.pm) + $ENV{TZ} + the unix `date` command (if available) + $main::TZ + /etc/TIMEZONE + /etc/timezone + +At least one of these should contain a timezone in one of the supported +forms. If none do by default, the TZ variable must be set with Date_Init. + +The timezone may be in the STD#DST format (in which case both abbreviations +must be in the table above) or any of the formats described above. The +STD#DST format is NOT available when parsing a date however. The following +forms are also available and are treated similar to the STD#DST forms: + + US/Pacific + US/Mountain + US/Central + US/Eastern + Canada/Pacific + Canada/Mountain + Canada/Central + Canada/Eastern + +=head1 BUSINESS MODE + +Anyone using business mode is going to notice a few quirks about it which +should be explained. When I designed business mode, I had in mind what UPS +tells me when they say 2 day delivery, or what the local business which +promises 1 business day turnaround really means. + +If you do a business day calculation (with the workday set to 9:00-5:00), +you will get the following: + + Saturday at noon + 1 business day = Tuesday at 9:00 + Saturday at noon - 1 business day = Friday at 9:00 + +What does this mean? + +We have a business that works 9-5 and they have a drop box so I can drop +things off over the weekend and they promise 1 business day turnaround. If +I drop something off Friday night, Saturday, or Sunday, it doesn't matter. +They're going to get started on it Monday morning. It'll be 1 business day +to finish the job, so the earliest I can expect it to be done is around +17:00 Monday or 9:00 Tuesday morning. Unfortunately, there is some +ambiguity as to what day 17:00 really falls on, similar to the ambiguity +that occurs when you ask what day midnight falls on. Although it's not the +only answer, Date::Manip treats midnight as the beginning of a day rather +than the end of one. In the same way, 17:00 is equivalent to 9:00 the next +day and any time the date calculations encounter 17:00, it automatically +switch to 9:00 the next day. Although this introduces some quirks, I think +this is justified. You just have to treat 17:00/9:00 as being ambiguous +(in the same way you treat midnight as being ambiguous). + +Equivalently, if I want a job to be finished on Saturday (despite the fact +that I cannot pick it up since the business is closed), I have to drop it +off no later than Friday at 9:00. That gives them a full business day to +finish it off. Of course, I could just as easily drop it off at 17:00 +Thursday, or any time between then and 9:00 Friday. Again, it's a matter +of treating 9:00 as ambiguous. + +So, in case the business date calculations ever produce results that you +find confusing, I believe the solution is to write a wrapper which, +whenever it sees a date with the time of exactly 9:00, it treats it +specially (depending on what you want. + +So Saturday + 1 business day = Tuesday at 9:00 (which means anything +from Monday 17:00 to Tuesday 9:00), but Monday at 9:01 + 1 business +day = Tuesday at 9:01 which is exact. + +If this is not exactly what you have in mind, don't use the DateCalc +routine. You can probably get whatever behavior you want using the +routines Date_IsWorkDay, Date_NextWorkDay, and Date_PrevWorkDay described +above. + +=head1 CUSTOMIZING DATE::MANIP + +There are a number of variables which can be used to customize the way +Date::Manip behaves. There are also several ways to set these variables. + +At the top of the Manip.pm file, there is a section which contains all +customization variables. These provide the default values. + +These can be overridden in a global config file if one is present (this +file is optional). If the GlobalCnf variable is set in the Manip.pm file, +it contains the full path to a config file. If the file exists, it's +values will override those set in the Manip.pm file. A sample config file +is included with the Date::Manip distribution. Modify it as appropriate +and copy it to some appropriate directory and set the GlobalCnf variable in +the Manip.pm file. + +Each user can have a personal config file which is of the same form as the +global config file. The variables PersonalCnf and PersonalCnfPath set the +name and search path for the personal config file. This file is also +optional. If present, it overrides any values set in the global file. + +NOTE: if you use business mode calculations, you must have a config file +(either global or personal) since this is the only place where you can +define holidays. + +Finally, any variables passed in through Date_Init override all other +values. + +A config file can be composed of several sections. The first section sets +configuration variables. Lines in this section are of the form: + + VARIABLE = VALUE + +For example, to make the default language French, include the line: + + Language = French + +Only variables described below may be used. Blank lines and lines beginning +with a pound sign (#) are ignored. All spaces are optional and strings are +case insensitive. + +A line which starts with an asterisk (*) designates a new section. For +example, the HOLIDAY section starts with a line: + + *Holiday + +The various sections are defined below. + +=head1 DATE::MANIP VARIABLES + +All Date::Manip variables which can be used are described in the following +section. + +=over 4 + +=item IgnoreGlobalCnf + +If this variable is used (any value is ignored), the global config file +is not read. It must be present in the initial call to Date_Init or the +global config file will be read. + +=item EraseHolidays + +If this variable is used (any value is ignored), the current list of +defined holidays is erased. A new set will be set the next time a +config file is read in. This can be set in either the global config file +or as a Date_Init argument (in which case holidays can be read in from +both the global and personal config files) or in the personal config file +(in which case, only holidays in the personal config file are counted). + +=item PathSep + +This is a regular expression used to separate multiple paths. For example, +on Unix, it defaults to a colon (:) so that multiple paths can be written +PATH1:PATH2 . For Win32 platforms, it defaults to a semicolon (;) so that +paths such as "c:\;d:\" will work. + +=item GlobalCnf + +This variable can be passed into Date_Init to point to a global +configuration file. The value must be the complete path to a config file. + +By default, no global config file is read. Any time a global config file +is read, the holidays are erased. + +Paths may have a tilde (~) expansion on platforms where this is supported +(currently Unix and VMS). + +=item PersonalCnf + +This variable can be passed into Date_Init or set in a global config file +to set the name of the personal configuration file. + +The default name for the config file is .DateManip.cnf on all Unix +platforms and Manip.cnf on all non-Unix platforms (because some of them +insist on 8.3 character filenames :-). + +=item PersonalCnfPath + +This is a list of paths separated by the separator specified by the PathSep +variable. These paths are each checked for the PersonalCnf config file. + +Paths may have a tilde (~) expansion on platforms where this is supported +(currently Unix and VMS). + +=item Language + +Date::Manip can be used to parse dates in many different languages. +Currently, it is configured to read the following languages (the version +in which they added is included for historical interest): + + English (default) + French (5.02) + Swedish (5.05) + German (5.31) + Dutch (5.32) aka Nederlands + Polish (5.32) + Spanish (5.33) + Portuguese (5.34) + Romanian (5.35) + Italian (5.35) + Russian (5.41) + Turkish (5.41) + Danish (5.41) + +Others can be added easily. Language is set to the language used to parse +dates. If you are interested in providing a translation for a new +language, email me (see the AUTHOR section below) and I'll send you a list +of things that I need. + +=item DateFormat + +Different countries look at the date 12/10 as Dec 10 or Oct 12. In the +United States, the first is most common, but this certainly doesn't hold +true for other countries. Setting DateFormat to "US" forces the first +behavior (Dec 10). Setting DateFormat to anything else forces the second +behavior (Oct 12). + +=item TZ + +If set, this defines the local timezone. See the TIMEZONES section above +for information on it's format. + +=item ConvTZ + +All date comparisons and calculations must be done in a single time zone in +order for them to work correctly. So, when a date is parsed, it should be +converted to a specific timezone. This allows dates to easily be compared +and manipulated as if they are all in a single timezone. + +The ConvTZ variable determines which timezone should be used to store dates +in. If it is left blank, all dates are converted to the local timezone +(see the TZ variable above). If it is set to one of the timezones listed +above, all dates are converted to this timezone. Finally, if it is set to +the string "IGNORE", all timezone information is ignored as the dates are +read in (in this case, the two dates "1/1/96 12:00 GMT" and "1/1/96 12:00 +EST" would be treated as identical). + +=item Internal + +When a date is parsed using ParseDate, that date is stored in an internal +format which is understood by the Date::Manip routines UnixDate and +DateCalc. Originally, the format used to store the date internally was: + + YYYYMMDDHH:MN:SS + +It has been suggested that I remove the colons (:) to shorten this to: + + YYYYMMDDHHMNSS + +The main advantage of this is that some databases are colon delimited which +makes storing a date from Date::Manip tedious. + +In order to maintain backwards compatibility, the Internal variable was +introduced. Set it to 0 (to use the old format) or 1 (to use the new +format). + +=item FirstDay + +It is sometimes necessary to know what day of week is regarded as first. +By default, this is set to Monday, but many countries and people will +prefer Sunday (and in a few cases, a different day may be desired). Set +the FirstDay variable to be the first day of the week (1=Monday, 7=Sunday) +Monday should be chosen to to comply with ISO 8601. + +=item WorkWeekBeg, WorkWeekEnd + +The first and last days of the work week. By default, Monday and Friday. +WorkWeekBeg must come before WorkWeekEnd numerically. The days are +numbered from 1 (Monday) to 7 (Sunday). + +There is no way to handle an odd work week of Thu to Mon for example or 10 +days on, 4 days off. + +=item WorkDay24Hr + +If this is non-nil, a work day is treated as being 24 hours long. The +WorkDayBeg and WorkDayEnd variables are ignored in this case. + +=item WorkDayBeg, WorkDayEnd + +The times when the work day starts and ends. WorkDayBeg must come before +WorkDayEnd (i.e. there is no way to handle the night shift where the work +day starts one day and ends another). Also, the workday MUST be more than +one hour long (of course, if this isn't the case, let me know... I want a +job there!). + +The time in both can be in any valid time format (including international +formats), but seconds will be ignored. + +=item TomorrowFirst + +Periodically, if a day is not a business day, we need to find the nearest +business day to it. By default, we'll look to "tomorrow" first, but if this +variable is set to 0, we'll look to "yesterday" first. This is only used in +the Date_NearestWorkDay and is easily overridden (see documentation for that +function). + +=item DeltaSigns + +Prior to Date::Manip version 5.07, a negative delta would put negative +signs in front of every component (i.e. "0:0:-1:-3:0:-4"). By default, +5.07 changes this behavior to print only 1 or two signs in front of the +year and day elements (even if these elements might be zero) and the sign +for year/month and day/hour/minute/second are the same. Setting this +variable to non-zero forces deltas to be stored with a sign in front of +every element (including elements equal to 0). + +=item Jan1Week1 + +ISO 8601 states that the first week of the year is the one which contains +Jan 4 (i.e. it is the first week in which most of the days in that week +fall in that year). This means that the first 3 days of the year may +be treated as belonging to the last week of the previous year. If this +is set to non-nil, the ISO 8601 standard will be ignored and the first +week of the year contains Jan 1. + +=item YYtoYYYY + +By default, a 2 digit year is treated as falling in the 100 year period of +CURR-89 to CURR+10. YYtoYYYY may be set to any integer N to force a 2 +digit year into the period CURR-N to CURR+(99-N). A value of 0 forces +the year to be the current year or later. A value of 99 forces the year +to be the current year or earlier. Since I do no checking on the value of +YYtoYYYY, you can actually have it any positive or negative value to force +it into any century you want. + +YYtoYYYY can also be set to "C" to force it into the current century, or +to "C##" to force it into a specific century. So, no (1998), "C" forces +2 digit years to be 1900-1999 and "C18" would force it to be 1800-1899. + +It can also be set to the form "C####" to force it into a specific 100 +year period. C1950 refers to 1950-2049. + +=item UpdateCurrTZ + +If a script is running over a long period of time, the timezone may change +during the course of running it (i.e. when daylight savings time starts or +ends). As a result, parsing dates may start putting them in the wrong time +zone. Since a lot of overhead can be saved if we don't have to check the +current timezone every time a date is parsed, by default checking is turned +off. Setting this to non-nil will force timezone checking to be done every +time a date is parsed... but this will result in a considerable performance +penalty. + +A better solution would be to restart the process on the two days per year +where the timezone switch occurs. + +=item IntCharSet + +If set to 0, use the US character set (7-bit ASCII) to return strings such +as the month name. If set to 1, use the appropriate international character +set. For example, If you want your French representation of Decemeber to +have the accent over the first "e", you'll want to set this to 1. + +=item ForceDate + +This variable can be set to a date in the format: YYYY-MM-DD-HH:MN:SS +to force the current date to be interpreted as this date. Since the current +date is used in parsing, this string will not be parsed and MUST be in the +format given above. + +=back + +=head1 HOLIDAY SECTION + +The holiday section of the config file is used to define holidays. Each +line is of the form: + + DATE = HOLIDAY + +HOLIDAY is the name of the holiday (or it can be blank in which case the +day will still be treated as a holiday... for example the day after +Thanksgiving or Christmas is often a work holiday though neither are +named). + +DATE is a string which can be parsed to give a valid date in any year. It +can be of the form + + Date + Date + Delta + Date - Delta + Recur + +A valid holiday section would be: + + *Holiday + + 1/1 = New Year's Day + third Monday in Feb = Presidents' Day + fourth Thu in Nov = Thanksgiving + + # The Friday after Thanksgiving is an unnamed holiday most places + fourth Thu in Nov + 1 day = + + 1*0:0:0:0:0:0*EASTER = Easter + 1*11:0:11:0:0:0*CWD = Veteran's Day (observed) + 1*0:0:0:0:0:0*EASTER,PD5 = Good Friday + +In a Date + Delta or Date - Delta string, you can use business mode by +including the appropriate string (see documentation on DateCalc) in the +Date or Delta. So (in English), the first workday before Christmas could +be defined as: + + 12/25 - 1 business day = + +The date's may optionally contain the year. For example, the dates + + 1/1 + 1/1/1999 + +refers to Jan 1 in any year or in only 1999 respectively. For dates that +refer to any year, the date must be written such that by simply appending +the year (separated by spaces) it can be correctly interpreted. This +will work for everything except ISO 8601 dates, so ISO 8601 dates may +not be used in this case. + +In cases where you are interested in business type calculations, you'll +want to define most holidays using recurrences, since they can define +when a holiday is celebrated in the financial world. For example, +Christmas chould be defined as: + + 1*12:0:24:0:0:0*FW1 = Christmas + +NOTE: It was pointed out to me that using a similar type recurrence to +define New Years does not work. The recurrence: + + 1*12:0:31:0:0:0*FW1 + +fails (worse, it goes into an infinite loop). The problem is that each +holiday definition is applied to a specific year and it expects to find +the holiday for that year. When this recurrence is applied to the year +1995, it returns the holiday for 1996 and fails. + +Use the recurrence: + + 1*1:0:1:0:0:0*NWD + +instead. + +If you wanted to define both Christmas and Boxing days (Boxing is the +day after Christmas, and is celebrated in some parts of the world), you +could do it in one of the following ways: + + 1*12:0:24:0:0:0*FW1 = Christmas + 1*12:0:25:0:0:0*FW1 = Boxing + + 1*12:0:24:0:0:0*FW1 = Christmas + 01*12:0:24:0:0:0*FW1 = Boxing + + 1*12:0:24:0:0:0*FW1 = Christmas + 1*12:0:25:0:0:0*FW1,a = Boxing + +The following examples will NOT work: + + 1*12:0:24:0:0:0*FW1 = Christmas + 1*12:0:24:0:0:0*FW2 = Boxing + + 1*12:0:24:0:0:0*FW1 = Christmas + 1*12:0:24:0:0:0*FW1 = Boxing + +The reasoning behind all this is as follows: + +Holidays go into affect the minute they are parsed. So, in the case of: + + 1*12:0:24:0:0:0*FW1 = Christmas + 1*12:0:24:0:0:0*FW2 = Boxing + +the minute the first line is parsed, Christmas is defined as a holiday. +The second line then steps forward 2 work days (skipping Christmas since +that's no longer a work day) and define the work day two days after +Christmas, NOT the day after Christmas. + +An good alternative would appear to be: + + 1*12:0:24:0:0:0*FW1 = Christmas + 1*12:0:24:0:0:0*FW1 = Boxing + +This unfortunately fails because the recurrences are currently stored in a +hash. Since these two recurrences are identical, they fail (the first one +is overwritten by the second and in essense, Christmas is never defined). + +To fix this, make them unique with either a fake flag (which is ignored): + + 1*12:0:24:0:0:0*FW1,a = Boxing + +or adding an innocuous 0 somewhere: + + 01*12:0:24:0:0:0*FW1 = Boxing + +The other good alternative would be to make two completely different +recurrences such as: + + 1*12:0:24:0:0:0*FW1 = Christmas + 1*12:0:25:0:0:0*FW1 = Boxing + +At times, you may want to switch back and forth between two holiday files. +This can be done by calling the following: + + &Date_Init("EraseHolidays=1","PersonalCnf=FILE1"); + ... + &Date_Init("EraseHolidays=1","PersonalCnf=FILE2"); + ... + +=head1 EVENTS SECTION + +The Events section of the config file is similar to the Holiday section. +It is used to name certain days or times, but there are a few important +differences: + +=over 4 + +=item Events can be assigned to any time and duration + +All holidays are exactly 1 day long. They are assigned to a period +of time from midnight to midnight. + +Events can be based at any time of the day, and may be of any duration. + +=item Events don't affect business mode calculations + +Unlike holidays, events are completely ignored when doing business +mode calculations. + +=back + +Whereas holidays were added with business mode math in mind, events +were added with calendar and scheduling applications in mind. + +Every line in the events section is of the form: + + EVENT = NAME + +where NAME is the name of the event, and EVENT defines when it occurs +and it's duration. An EVENT can be defined in the following ways: + + Date + Date* + Recur [NYI] + Recur* [NYI] + + Date ; Date + Date ; Delta + Recur ; Delta [NYI] + + Date ; Delta ; Delta [NYI] + Recur ; Delta ; Delta [NYI] + +Here, Date* refers to a string containing a Date with NO TIME fields +(Jan 12, 1/1/2000, 2010-01-01) while Date does contain time fields. +Similarily, Recur* stands for a recurrence with the time fields all +equal to 0) while Recur stands for a recurrence with at least one +non-zero time field. + +Both Date* and Recur* refer to an event very similar to a holiday which +goes from midnight to midnight. + +Date and Recur refer to events which occur at the time given and with +a duration of 1 hour. + +Events given by "Date ; Date", "Date ; Delta", and "Recur ; Delta" +contain both the starting date and either ending date or duration. + +Events given as three elements "Date ; Delta ; Delta" or "Recur ; Delta ; +Delta" take a date and add both deltas to it to give the starting and +ending time of the event. The order and sign of the deltas is +unimportant (and both can be the same sign to give a range of times +which does not contain the base date). + +Items marked with [NYI] are not yet implemented but will be by the +time this is released. + +=head1 BACKWARDS INCOMPATIBILITIES + +For the most part, Date::Manip has remained backward compatible at every +release. There have been a few minor incompatibilities introduced at +various stages. Major differences are marked with bullets. + +=over 4 + +=item VERSION 5.41 + +=item Changed path separator for VMS + +Since ":" is used in some VMS paths, it should not have been used as +the path separator. It has been changed to a newline ("\n") character. + +=item Delta_Format behavior changed + +The entire delta is exact if no month component is present (previously, +no year or month component could be present). + +=item VERSION 5.38 + +=item Removed Date_DaysSince999 + +The Date_DaysSince999 function (deprecated in 5.35) has been removed. + +=item VERSION 5.35 + +=over 4 + +=item Deprected Date_DaysSince999 + +In fixing support for the years 0000-0999, I rewrote Date_DaysSince999 to +be Date_DaysSince1BC. The Date_DaysSince999 function will be removed. + +=item * Added PathSep variable + +In order to better support Win32 platforms, I added the PathSep config +variable. This will allow the use of paths such as "c:\date" on Win32 +platforms. Old config files on Win32 platforms (which were not working +correctly in many cases) may not work if they contain path information to +the personal config file. + +=back + +=item VERSION 5.34 + +=over 4 + +=item * All Date::Manip variables are no longer accessible + +Previously, Date::Manip variables were declared using a full package name. +Now, they are declared with the my() function. This means that internal +variables are no longer accessible outside of the module. + +=item Week interpretation in business mode deltas + +A business mode delta containing a week value used to be treated as 7 days. +A much more likely interpretation of a week is Monday to Monday, regardless +of holidays, so this is now the behavior. + +=item %z UnixDate format + +The %z UnixDate format used to return the Timezone abbreviation. It now +returns it as a GMT offset (i.e. -0500). %Z still returns the Timezone +abbreviation. + +=item Formats "22nd sunday" returns the intuitive value + +The date "22nd sunday" used to return the Sunday of the 22nd week of the +year (which could be the 21st, 22nd, or 23rd Sunday of the year depending +on how weeks were defined). Now, it returns the 22nd Sunday of the year +regardless. + +=item Separator in DD/YYmmm and mmmDD/YY formats no longer optional + +Previously, the date "Dec1065" would return Dec 10, 1965. After adding +the YYYYmmm and mmmYYYY formats, this was no longer possible. The separator +between DD and YY is no longer optional, so + + Dec1065 returns December 1, 1065 + Dec10/65 returns December 10, 1965 + +=item * Date_Cmp added + +This is not a backwards incompatibility... but is added to help prepare for +a future incompatibility. In one of the next versions of Date::Manip, the +internal format of the date will change to include timezone information. +All date comparisons should be made using Date_Cmp (which currently does +nothing more than call the perl "cmp" command, but which will important +when comparing dates that include the timezone). + +=back + +=item VERSION 5.32 + +=over 4 + +=item Date_Init arguments + +The old style Date_Init arguments that were deprecated in version 5.07 +have been removed. + +=item * DateManip.cnf change + +Changed .DateManip.cnf to Manip.cnf (to get rid of problems on OS's +that insist on 8.3 filenames) for all non-Unix platforms (Wintel, VMS, +Mac). For all Unix platforms, it's still .DateManip.cnf . It will only +look in the user's home directory on VMS and Unix. + +=back + +=item VERSION 5.30 + +=over 4 + +=item * Delta format changed + +A week field has been added to the internal format of the delta. It now +reads "Y:M:W:D:H:MN:S" instead of "Y:M:D:H:MN:S". + +=back + +=item VERSION 5.21 + +=over 4 + +=item Long running processes may give incorrect timezone + +A process that runs during a timezone change (Daylight Saving Time +specifically) may report the wrong timezone. See the UpdateCurrTZ variable +for more information. + +=item UnixDate "%J", "%W", and "%U" formats fixed + +The %J, %W, and %U will no longer report a week 0 or a week 53 if it should +really be week 1 of the following year. They now report the correct week +number according to ISO 8601. + +=back + +=item VERSION 5.20 + +=over 4 + +=item * ParseDate formats removed (ISO 8601 compatibility) + +Full support for ISO 8601 formats was added. As a result, some formats +which previously worked may no longer be parsed since they conflict with an +ISO 8601 format. These include MM-DD-YY (conflicts with YY-MM-DD) and +YYMMDD (conflicts with YYYYMM). MM/DD/YY still works, so the first form +can be kept easily by changing "-" to "/". YYMMDD can be changed to +YY-MM-DD before being parsed. Whenever parsing dates using dashes as +separators, they will be treated as ISO 8601 dates. You can get around +this by converting all dashes to slashes. + +=item * Week day numbering + +The day numbering was changed from 0-6 (sun-sat) to 1-7 (mon-sun) to be +ISO 8601 compatible. Weeks start on Monday (though this can be overridden +using the FirstDay config variable) and the 1st week of the year contains +Jan 4 (though it can be forced to contain Jan 1 with the Jan1Week1 config +variable). + +=back + +=item VERSION 5.07 + +=over 4 + +=item UnixDate "%s" format + +Used to return the number of seconds since 1/1/1970 in the current +timezone. It now returns the number of seconds since 1/1/1970 GMT. +The "%o" format was added which returns what "%s" previously did. + +=item Internal format of delta + +The format for the deltas returned by ParseDateDelta changed. Previously, +each element of a delta had a sign attached to it (+1:+2:+3:+4:+5:+6). The +new format removes all unnecessary signs by default (+1:2:3:4:5:6). Also, +because of the way deltas are normalized (see documentation on +ParseDateDelta), at most two signs are included. For backwards +compatibility, the config variable DeltaSigns was added. If set to 1, all +deltas include all 6 signs. + +=item Date_Init arguments + +The format of the Date_Init calling arguments changed. The +old method + + &Date_Init($language,$format,$tz,$convtz); + +is still supported , but this support will likely disappear in the future. +Use the new calling format instead: + + &Date_Init("var=val","var=val",...); + +NOTE: The old format is no longer supported as of version 5.32 . + +=back + +=back + +=head1 KNOWN PROBLEMS + +The following are not bugs in Date::Manip, but they may give some people +problems. + +=over 4 + +=item Unable to determine TimeZone + +Perhaps the most common problem occurs when you get the error: + + Error: Date::Manip unable to determine TimeZone. + +Date::Manip tries hard to determine the local timezone, but on some +machines, it cannot do this (especially non-unix systems). To fix this, +just set the TZ variable, either at the top of the Manip.pm file,, in the +DateManip.cnf file, or in a call to Date_Init. I suggest using the form +"EST5EDT" so you don't have to change it every 6 months when going to or +from daylight savings time. + +Windows NT does not seem to set the TimeZone by default. From the +Perl-Win32-Users mailing list: + + > How do I get the TimeZone on my NT? + > + > $time_zone = $ENV{'TZ'}; + > + You have to set the variable before, WinNT doesn't set it by + default. Open the properties of "My Computer" and set a SYSTEM + variable TZ to your timezone. Jenda@Krynicky.cz + +This might help out some NT users. + +A minor (false) assumption that some users might make is that since +Date::Manip passed all of it's tests at install time, this should not occur +and are surprised when it does. + +Some of the tests are timezone dependent. Since the tests all include +input and expected output, I needed to know in advance what timezone they +would be run in. So, the tests all explicitly set the timezone using the +TZ configuration variable passed into Date_Init. Since this overrides any +other method of determining the timezone, Date::Manip uses this and doesn't +have to look elsewhere for the timezone. + +When running outside the tests, Date::Manip has to rely on it's other +methods for determining the timezone. + +=item Complaining about getpwnam/getpwuid + +Another problem is when running on Micro$oft OS'es. I have added many +tests to catch them, but they still slip through occasionally. If any ever +complain about getpwnam/getpwuid, simply add one of the lines: + + $ENV{OS} = Windows_NT + $ENV{OS} = Windows_95 + +to your script before + + use Date::Manip + +=item Date::Manip is slow + +The reasons for this are covered in the SHOULD I USE DATE::MANIP section +above. + +Some things that will definitely help: + +Version 5.21 does run noticeably faster than earlier versions due to +rethinking some of the initialization, so at the very least, make sure you +are running this version or later. + +ISO-8601 dates are parsed first and fastest. Use them whenever possible. + +Avoid parsing dates that are referenced against the current time (in 2 +days, today at noon, etc.). These take a lot longer to parse. + + Example: parsing 1065 dates with version 5.11 took 48.6 seconds, 36.2 + seconds with version 5.21, and parsing 1065 ISO-8601 dates with version + 5.21 took 29.1 seconds (these were run on a slow, overloaded computer with + little memory... but the ratios should be reliable on a faster computer). + +Business date calculations are extremely slow. You should consider +alternatives if possible (i.e. doing the calculation in exact mode and then +multiplying by 5/7). There will be an approximate business mode in one of +the next versions which will be much faster (though less accurate) which +will do something like this. Whenever possible, use this mode. And who +needs a business date more accurate than "6 to 8 weeks" anyway huh :-) + +Never call Date_Init more than once. Unless you're doing something very +strange, there should never be a reason to anyway. + +=item Sorting Problems + +If you use Date::Manip to sort a number of dates, you must call Date_Init +either explicitly, or by way of some other Date::Manip routine before it +is used in the sort. For example, the following code fails: + + use Date::Manip; + # &Date_Init; + sub sortDate { + my($date1, $date2); + $date1 = &ParseDate($a); + $date2 = &ParseDate($b); + return (&Date_Cmp($date1,$date2)); + } + @dates = ("Fri 16 Aug 96", + "Mon 19 Aug 96", + "Thu 15 Aug 96"); + @i=sort sortDate @dates; + +but if you uncomment the Date_Init line, it works. The reason for this is +that the first time you call Date_Init, it initializes a number of items +used by Date::Manip. Some of these have to be sorted (regular expressions +sorted by length to ensure the longest match). It turns out that perl +has a bug in it which does not allow a sort within a sort. At some point, +this should be fixed, but for now, the best thing to do is to call Date_Init +explicitly. The bug exists in all versions up to 5.005 (I haven't +tested 5.6.0 yet). + +NOTE: This is an EXTREMELY inefficient way to sort data. Instead, you +should parse the dates with ParseDate, sort them using a normal string +comparison, and then convert them back to the format desired using +UnixDate. + +=item RCS Control + +If you try to put Date::Manip under RCS control, you are going to have +problems. Apparently, RCS replaces strings of the form "$Date...$" with +the current date. This form occurs all over in Date::Manip. To prevent the +RCS keyword expansion, checkout files using "co -ko". Since very few people +will ever have a desire to do this (and I don't use RCS), I have not worried +about it. + +=back + +=head1 KNOWN BUGS + +=over 4 + +=item Daylight Savings Times + +Date::Manip does not handle daylight savings time, though it does handle +timezones to a certain extent. Converting from EST to PST works fine. +Going from EST to PDT is unreliable. + +The following examples are run in the winter of the US East coast (i.e. +in the EST timezone). + + print UnixDate(ParseDate("6/1/97 noon"),"%u"),"\n"; + => Sun Jun 1 12:00:00 EST 1997 + +June 1 EST does not exist. June 1st is during EDT. It should print: + + => Sun Jun 1 00:00:00 EDT 1997 + +Even explicitly adding the timezone doesn't fix things (if anything, it +makes them worse): + + print UnixDate(ParseDate("6/1/97 noon EDT"),"%u"),"\n"; + => Sun Jun 1 11:00:00 EST 1997 + +Date::Manip converts everything to the current timezone (EST in this case). + +Related problems occur when trying to do date calculations over a timezone +change. These calculations may be off by an hour. + +Also, if you are running a script which uses Date::Manip over a period of +time which starts in one time zone and ends in another (i.e. it switches +form Daylight Savings Time to Standard Time or vice versa), many things may +be wrong (especially elapsed time). + +I hope to fix these problems in a future release so that it would convert +everything to the current zones (EST or EDT). + +=back + +=head1 BUGS AND QUESTIONS + +If you find a bug in Date::Manip, please send it directly to me (see the +AUTHOR section below) rather than posting it to one of the newsgroups. +Although I try to keep up with the comp.lang.perl.* groups, all too often I +miss news (flaky news server, articles expiring before I caught them, 1200 +articles to wade through and I missed one that I was interested in, etc.). + +When filing a bug report, please include the following information: + + o The version of Date::Manip you are using. You can get this by using + the script: + + use Date::Manip; + print &DateManipVersion(),"\n"; + + o The output from "perl -V" + +If you have a problem using Date::Manip that perhaps isn't a bug (can't +figure out the syntax, etc.), you're in the right place. Go right back to +the top of this man page and start reading. If this still doesn't answer +your question, mail me (again, please mail me rather than post to the +newsgroup). + +=head1 YEAR 2000 + +In hindsight, the fact that I've only been asked once (so far) if Date::Manip +is year 2000 compliant surprises me a bit. Still, as 2000 approaches and +this buzzword starts flying around more and more frantically, other's might +follow suit, so this section answers the question. + +Is Date::Manip year 2000 compliant? + +This question is largely meaningless. Date::Manip is basically just a +parser. You give it a date and it'll manipulate it. Date::Manip does +store the date internally as a 4 digit year, and performs all operations +using this internal representation, so I will state that Date::Manip is +CAPABLE of writing Y2K compliant code. + +But Date::Manip is simply a library. If you use it correctly, your code +can be Y2K compliant. If you don't, your code may not be Y2K compliant. + +The bottom line is this: + + Date::Manip is a library that is capable of being used to write Y2K + compliant code. It may also be used to write non-Y2K compliant code. + + If your code is NOT Y2K compliant, it is NOT due to any deficiency in + Date::Manip. Rather, it is due to poor programming on the part of the + person using Date::Manip. + +For an excellent treatment of the Y2K problem, see the article by Tom +Christiansen at: + + http://language.perl.com/news/y2k.html + +A slightly better question is "Is Perl year 2000 compliant"? This is +covered in the perl FAQ (section 4) and in the article by Tom Crhistiansen. + +The best question is "For what dates is Date::Manip useful?" It definitely +can't handle BC dates, or dates past Dec 31, 9999. So Date::Manip works +during the years 1000 to 9999. + +In practical terms however, Date::Manip deals with the Gregorian calendar, +and is therefore useful in the period that that calendar has been, or will +be, in effect. The Gregorian calendar was first adopted by the Catholic +church in 1582, but some countries were still using the Julian calendar as +late as the early part of the 20th century. Also, at some point (probably +no earlier than the year 3000 and possibly much later), the Gregorian +system is going to have to be modified slightly since the current system of +leap years is off by a few seconds a year. So... in practical terms, +Date::Manip is _probably_ useful from 1900 to 3000. + +One other note is that Date::Manip will NOT handle 3 digit years. So, if +you store the year as an offset from 1900 (which is 2 digits now, but will +become 3 digits in 2000), these will NOT be parsable by Date::Manip. + +=head1 VERSION NUMBERS + +A note about version numbers. + +Prior to version 5.00, Date::Manip was distributed as a perl4 library. +There were no numbering conventions in place, so I used a simple +MAJOR.MINOR numbering scheme. + +With version 5.00, I switched to a perl5 module and at that time switched +to the perl5 numbering convention of a major version followed by a 2 digit +minor version. + +As of 5.41/5.42, all versions released to CPAN will be even numbered. Odd +numbered will be development versions available from my web site. For +example, after 5.40 was released, I started making changes, and called +the development version 5.41. When released to CPAN, it was called 5.42. +I may add a third digit to development versions (i.e. 5.41.9) to keep +track of important changes in the development version. + +=head1 ACKNOWLEDGMENTS + +There are many people who have contributed to Date::Manip over the years +that I'd like to thank. The most important contributions have come in the +form of suggestions and bug reports by users. I have tried to include the +name of every person who first suggested each improvement or first reported +each bug. These are included in the HISTORY file in the Date::Manip +distribution in the order the changes are made. The list is simply too +long to appear here, but I appreciate their help. + +A number of people have made suggestions or reported bugs which are not +mentioned in the HISTORY file. These include suggestions which have not +been implemented and people who have made a suggestion or bug report which +has already been suggested/reported by someone else. For those who's +suggestions have not yet been implemented, they will be added to the +HISTORY file when (if) their suggestions are implemented. For everyone +else, thank you too. I'd much rather have a suggestion made twice than not +at all. + +Thanks to Alan Cezar and Greg Schiedler for paying me to implement the +Events_List routine. They gave me the idea, and were then willing to pay +me for my time to get it implemented quickly. + +I'd also like a couple of authors. Date::Manip has recently been getting +some really good press in a couple of books. Since no one's paying me to +write Date::Manip, seeing my module get a good review in a book written by +someone else really makes my day. My thanks to Nate Padwardhan and Clay +Irving (Programming with Perl Modules -- part of the O'Reilly Perl Resource +Kit); and Tom Christiansen and Nathan Torkington (The Perl Cookbook). +Also, thanks to any other authors who've written about Date::Manip who's +books I haven't seen. + +=head1 AUTHOR + +Sullivan Beck (sbeck@cpan.org) + +You can always get the newest beta version of Date::Manip (which may fix +problems in the current CPAN version... and may add others) from my home +page: + +http://www.cise.ufl.edu/~sbeck/ + +=cut diff --git a/lib/WebGUI/DateTime.pm b/lib/WebGUI/DateTime.pm index 93baa6a34..52fc41569 100644 --- a/lib/WebGUI/DateTime.pm +++ b/lib/WebGUI/DateTime.pm @@ -14,7 +14,7 @@ package WebGUI::DateTime; =cut -use Date::Calc; +use Date::Manip; use Exporter; use strict; use WebGUI::International; @@ -66,6 +66,14 @@ These functions are available from this package: =cut +sub epochToDate { + my $secs = shift; + return &ParseDateString("epoch $secs"); +} + +sub dateToEpoch { + return &UnixDate(shift,"%s"); +} @@ -98,11 +106,13 @@ The number of days to add to the epoch. =cut sub addToDate { - my ($year,$month,$day, $hour,$min,$sec, $newDate); - ($year,$month,$day, $hour,$min,$sec) = epochToArray($_[0]); - ($year,$month,$day) = Date::Calc::Add_Delta_YMD($year,$month,$day, $_[1],$_[2],$_[3]); - $newDate = arrayToEpoch($year,$month,$day, $hour,$min,$sec); - return $newDate; + my ($date,$years,$months,$days,$newDate); + $date = &epochToDate(shift); + $years = shift || 0; + $months = shift || 0; + $days = shift || 0; + $newDate = DateCalc($date,"$years:$months:0:$days:0:0:0"); + return &dateToEpoch($newDate); } #------------------------------------------------------------------- @@ -134,11 +144,13 @@ The number of seconds to add to the epoch. =cut sub addToTime { - my ($year,$month,$day, $hour,$min,$sec, $newDate); - ($year,$month,$day, $hour,$min,$sec) = epochToArray($_[0]); - ($year,$month,$day, $hour,$min,$sec) = Date::Calc::Add_Delta_DHMS($year,$month,$day,$hour,$min,$sec,0,$_[1],$_[2],$_[3]); - $newDate = arrayToEpoch($year,$month,$day, $hour,$min,$sec); - return $newDate; + my ($date,$hours,$mins,$secs,$newDate); + $date = &epochToDate(shift); + $hours = shift || 0; + $mins = shift || 0; + $secs = shift || 0; + $newDate = DateCalc($date,"0:0:0:0:$hours:$mins:$secs"); + return &dateToEpoch($newDate); } #------------------------------------------------------------------- @@ -158,13 +170,15 @@ An array of the format year, month, day, hour, min, sec. =cut sub arrayToEpoch { - my $year = shift; - my $month = shift; - my $day = shift; - my $hour = shift; - my $min = shift; - my $sec = shift; - return Date::Calc::Date_to_Time($year,$month,$day,$hour,$min,$sec); + my $year = shift || '0000'; + my $month = shift || '00'; + my $day = shift || '00'; + my $hour = shift || '00'; + my $min = shift || '00'; + my $sec = shift || '00'; + $min = "0$min" if (length($min) == 1); + $sec = "0$sec" if (length($sec) == 1); + return &dateToEpoch(&ParseDate("$year-$month-$day $hour:$min:$sec")); } @@ -187,8 +201,8 @@ The number of seconds since January 1, 1970. sub dayStartEnd { my ($year,$month,$day, $hour,$min,$sec, $start, $end); ($year,$month,$day, $hour,$min,$sec) = epochToArray($_[0]); - $start = Date::Calc::Date_to_Time($year,$month,$day,0,0,0); - $end = Date::Calc::Date_to_Time($year,$month,$day,23,59,59); + $start = &arrayToEpoch($year,$month,$day,0,0,0); + $end = &arrayToEpoch($year,$month,$day,23,59,59); return ($start, $end); } @@ -210,7 +224,7 @@ An epoch date. sub epochToArray { my $epoch = shift; - return Date::Calc::Time_to_Date($epoch); + return &UnixDate(epochToDate($epoch),'%Y','%m','%d','%H','%M','%S'); } @@ -264,6 +278,7 @@ sub epochToHuman { $offset = $offset*3600; $temp = int($_[0]) || WebGUI::DateTime::time(); $temp = $temp+$offset; + my $dt = epochToDate($temp); my ($year,$month,$day,$hour,$min,$sec) = epochToArray($temp); $output = $_[1] || "%z %Z"; #---GMT Offsets @@ -293,7 +308,7 @@ sub epochToHuman { $output =~ s/\%c/$temp/g; } if ($output =~ /\%C/) { - $temp = substr(Date::Calc::Month_to_Text($month),0,3); + $temp = &UnixDate($dt,'%b'); $output =~ s/\%C/$temp/g; } #---day stuff @@ -301,11 +316,11 @@ sub epochToHuman { $output =~ s/\%d/$value/g; $output =~ s/\%D/$day/g; if ($output =~ /\%w/) { - $temp = getDayName(Date::Calc::Day_of_Week($year,$month,$day)); + $temp = getDayName(&UnixDate($dt,'%w')); $output =~ s/\%w/$temp/g; } if ($output =~ /%W/) { - $temp = Date::Calc::Day_of_Week_Abbreviation(Date::Calc::Day_of_Week($year,$month,$day)); + $temp = &UnixDate($dt,'%a'); $output =~ s/\%W/$temp/g; } #---hour stuff @@ -463,7 +478,7 @@ An epoch date. sub getDaysInMonth { my $epoch = shift; my @date = WebGUI::DateTime::epochToArray($epoch); - return Date::Calc::Days_in_Month($date[0], $date[1]); + return &Date_DaysInMonth($date[1], $date[0]); } @@ -488,11 +503,11 @@ An epoch date. =cut sub getDaysInInterval { - my $start = shift; - my $end = shift; - my @start = WebGUI::DateTime::epochToArray($start); - my @end = WebGUI::DateTime::epochToArray($end); - return Date::Calc::Delta_Days($start[0], $start[1], $start[2], $end[0], $end[1], $end[2]); + my $start = &epochToDate(shift); + my $end = &epochToDate(shift); + my $err; + my $delta = &DateCalc($start,$end,\$err); + return &Delta_Format($delta,0,'%dh'); } @@ -516,7 +531,7 @@ An epoch date. sub getFirstDayInMonthPosition { my $epoch = shift; my @date = WebGUI::DateTime::epochToArray($epoch); - my $firstDayInFirstWeek = Date::Calc::Day_of_Week($date[0],$date[1],1); + my $firstDayInFirstWeek = &UnixDate("$date[0]-$date[1]-01",'%w'); unless ($session{user}{firstDayOfWeek}) { #american format $firstDayInFirstWeek++; if ($firstDayInFirstWeek > 7) { @@ -635,14 +650,15 @@ The number of seconds since January 1, 1970. Defaults to now. =cut sub localtime { - my $epoch = shift; - my ($year, $month, $day, $hour, $min, $sec) = Date::Calc::Today_and_Now(); + my $epoch = shift || &dateToEpoch(&ParseDate("today")); + my $date = &epochToDate($epoch); + my ($year, $month, $day, $hour, $min, $sec) = epochToArray($epoch); if ($epoch) { ($year, $month, $day, $hour, $min, $sec) = epochToArray($epoch); } - my $doy = Date::Calc::Day_of_Year($year,$month,$day); - my $dow = Date::Calc::Day_of_Week($year,$month,$day); - my @temp = Date::Calc::System_Clock(); + my $doy = &UnixDate($date,'%j'); + my $dow = &UnixDate($date,'%w'); + my @temp = localtime($epoch); return ($year, $month, $day, $hour, $min, $sec, $doy, $dow, $temp[8]); } @@ -666,10 +682,12 @@ An epoch datestamp corresponding to the last month. =cut sub monthCount { - my ($start, $end) = @_; - my @delta = Date::Calc::Delta_YMDHMS( epochToArray($start), epochToArray($end)); - my $change = (($delta[0]*12)+$delta[1])+1; - return $change; + my $start = &epochToDate(shift); + my $end = &epochToDate(shift); + my $err; + my $delta = &DateCalc($start,$end,\$err,1); + return $delta; + return &Delta_Format($delta,0,'%Mh'); } @@ -690,12 +708,11 @@ The number of seconds since January 1, 1970. =cut sub monthStartEnd { - my ($year,$month,$day, $hour,$min,$sec, $start, $end); - ($year,$month,$day, $hour,$min,$sec) = epochToArray($_[0]); - $start = arrayToEpoch($year,$month,1,0,0,0); - ($year,$month,$day, $hour,$min,$sec) = epochToArray(addToDate($_[0],0,1,0)); - $end = arrayToEpoch($year,$month,1,0,0,0)-1; - return ($start, $end); + my ($year,$month,$day, $hour,$min,$sec, $start, $end); + ($year,$month,$day, $hour,$min,$sec) = epochToArray($_[0]); + $start = &arrayToEpoch($year,$month,1,0,0,0) + 0; + $end = &UnixDate(&DateCalc(&epochToDate($start), "+1 month"),'%s')-1; + return ($start, $end); } #------------------------------------------------------------------- @@ -789,7 +806,7 @@ sub setToEpoch { my ($date,$time) = split(/ /,$_[0]); my ($year, $month, $day) = split(/\-/,$date); my ($hour, $minute, $second) = split(/\:/,$time); - if (int($year) < 2038 && int($year) > 1969) { + if (int($year) < 3000 && int($year) > 1000) { $year = int($year); } else { $year = $now[0]; @@ -816,7 +833,7 @@ Returns an epoch date for now. =cut sub time { - return arrayToEpoch(Date::Calc::Today_and_Now()); + return dateToEpoch(&ParseDate("now")); } #------------------------------------------------------------------- diff --git a/lib/WebGUI/Session.pm b/lib/WebGUI/Session.pm index 9ef864e44..ffa8e47a0 100644 --- a/lib/WebGUI/Session.pm +++ b/lib/WebGUI/Session.pm @@ -16,7 +16,7 @@ package WebGUI::Session; use CGI; -use Date::Calc; +use Date::Manip; use DBI; use Exporter; use strict; @@ -128,7 +128,7 @@ sub _setupUserInfo { #------------------------------------------------------------------- sub _time { - return Date::Calc::Date_to_Time(Date::Calc::Today_and_Now()); + return &UnixDate(&ParseDate("now")),"%s"); } diff --git a/sbin/preload.perl b/sbin/preload.perl index c24a35a57..1e6921f36 100644 --- a/sbin/preload.perl +++ b/sbin/preload.perl @@ -28,7 +28,6 @@ use Cache::FileCache (); use CGI (); CGI->compile(':all'); use CGI::Carp (); use CGI::Util (); -use Date::Calc (); use Digest::MD5 (); eval "use Image::Magick ();"; # eval, may not be installed use File::Copy (); @@ -56,6 +55,7 @@ DBI->install_driver("mysql"); # Change to match your database driver. #use HTML::Parser (); # commented because it is causing problems with attachments #use HTML::TagFilter (); # commented because it is causing problems with attachments use Parse::PlainConfig (); +use Date::Manip (); use Tie::CPHash (); use Tie::IxHash (); use Tree::DAG_Node (); diff --git a/sbin/testEnvironment.pl b/sbin/testEnvironment.pl index 57a96a538..d13e9d043 100644 --- a/sbin/testEnvironment.pl +++ b/sbin/testEnvironment.pl @@ -187,19 +187,6 @@ if (eval { require Net::SMTP }) { } } -print "Date::Calc module ........................ "; -if (eval { require Date::Calc }) { - print "OK\n"; -} else { - if ($< == 0 && $os eq "Linuxish") { - print "Attempting to install...\n"; - CPAN::Shell->install("Date::Calc"); - } else { - print "Please install.\n"; - $prereq = 0; - } -} - print "Cache::Cache module ...................... "; if (eval { require Cache::Cache }) { print "OK\n";