, %, %,
+%, and % formats. Chris Jackson
+
+=item Can now parse formats where the time and zone are not adjacent
+
+A string like 'Jan 21 17:13:27 2010 -0400' can now be parsed. Requested
+on perlmonks ('Can Date::Manip parse a unix timestamp' thread).
+
+=item Added PeriodTimeSep config variable.
+
+This allows you to use a period as a time separator. Ed Avis
+
+=item Holidays can be used as date strings
+
+You can now parse a holiday name in the Date::Manip::Date::parse method.
+For example, parse('Christmas'). Requested by Abigail.
+
+=item Added new recur modifiers
+
+Added WDn, IBD, and NBD modifiers.
+
+=item Added a new date format
+
+You can now parse '2nd day in May' formats. Randy Harmon
+
+=item More flexibility in parsing timezones with both offset and abbrev
+
+If you include both the offset and abbreviation in the timezone portion
+of a date, the parenthesis around the abbreviation are now optional, so
+you can parse both:
+
+ -04:00 (EDT)
+ -04:00 EDT
+
+Requested by Steven Melendez.
+
+=item Deltas now support fractional values.
+
+You can now use a delta of 1.5 days. RT 42699
+
+=item Multiple holidays supported
+
+A date may now have multiple holidays. Keith Minkler
+
+=item Bug fixes
+
+Fixed a bug where abbreviations were not being examined case insensitively.
+Jurgen Muck
+
+The Holidays section may be safely split across multiple config files.
+A bug prevented this from working before.
+
+=item Language fixes
+
+The Norwegian translation was broken due to a typo in the language module.
+
+Included complete documentation for each language.
+
+Fixed a couple problems with Danish. Nicholas Oxhaj
+
+Added Finnish (from a VERY old mail that I overlooked somehow). Iikka
+Virkkunen
+
+Cleaned up the values used by printf directives to give the expected
+result.
+
+=item Documentation fixes
+
+Fixed a typo. Nicholas Bamber
+
+=back
+
+=head1 VERSION 6.30 (2012-01-11)
+
+=over 4
+
+=item B<(*) (!) Reworked deltas>
+
+Much of the delta code was reworked.
+
+The constraint that a day is treated as 24 hours was removed (by
+adding the concept of semi-exact deltas) to better handle daylight
+saving time calculations.
+
+Made cosmetic changes to which signs will be included in a delta to
+make the deltas more readable. Signs which are the same as the next
+higher field will be omitted, even if they cross set boundaries.
+
+Added support for non-normalized deltas. See the $no_normalize
+option for the parse and set methods.
+
+Removed limitations on subtract=2 not working with business
+calculations.
+
+Thanks to discussion on perlmonks, and RT 65774 that prompted me to do
+this. I'd been wanting to do it for some time, but the discussion on
+perlmonks made me realize that this needed to be much higher priority.
+
+=item B<(!) Modified Delta_Format>
+
+In conjunction with the above work, added the 'semi' mode to
+Delta_Format.
+
+=item B<(!) Removed some deprecated config variables>
+
+The following config variables have been removed.
+
+ GlobalCnf
+ IgnoreGlobalCnf
+ PersonalCnf
+ PersonalCnfPath
+ PathSep
+
+ Internal
+ DeltaSigns
+ UpdateCurrTZ
+ ConvTZ
+ OldConfigFiles
+ ResetWorkDay
+
+=item Time zone fixes
+
+Newest zoneinfo data (tzdata 2011n). RT 71595
+
+Corrects a bug where +0000 and -0000 offsets were not parsed correctly.
+Aaron Hall
+
+The zoneinfo data includes rules more than 20 years in the future, so we
+now store 30 years of future dates instead of 20 to catch these rules.
+
+When parsing the timezone portion of the date, timezone abbreviations
+now take higher precedence than zone names (since that is how timezone
+information is typically specified). That only impacts dates where
+the a timezone name is the same as an abbreviation, such as 'CET'.
+
+Previously, a date with CET in it was interpreted as in the CET timezone.
+Now it is interpreted as in a timezone with the CET abbreviation.
+
+=item Better handling of undef in DM6
+
+The date/delta parsing routines in DM6 will now handle an undef argument
+without issuing a warning. Earl C. Ruby III
+
+=item Bug fixes
+
+Fixed a bug with the parse_format %f and %i formats. Tommi Rintala
+
+Fixed a bug where the Date::Manip::Delta::set function didn't work
+to set the month value.
+
+Fixed a bug where parsing some dates near during a DST change failed.
+
+Minor bug fix when using Delta_Format. Prompted while investigating
+RT 41095.
+
+=back
+
+=head1 VERSION 6.25 (2011-08-31)
+
+=over 4
+
+=item Relaxed one constraint in ISO 8601 dates
+
+A time separated by whitespace from the date can use a single digit hour.
+Yuming Philip Xiang
+
+=item B<(*) Set official removal dates for old config variables>
+
+Config variables will be removed 2 years after they are deprecated (except
+for the TZ variable which, due to it's wide use, will be kept for 4 years).
+
+=item Time zone fixes
+
+Newest zoneinfo data (tzdata 2011i)
+
+=back
+
+=head1 VERSION 6.24 (2011-06-13)
+
+=over 4
+
+=item New features
+
+Spaces are ignored in the SetDate/ForceDate config values. Zsban Ambrus
+
+=item Bug fixes
+
+Fixed a bug where 'in one week' wasn't correctly parsed. E. M. Shtern
+
+Fixed a bug where options passed in to the 'new' as a listref weren't handled
+properly. Zsban Ambrus
+
+=item Time zone fixes
+
+Newest zoneinfo data (tzdata 2011g)
+
+=back
+
+=head1 VERSION 6.23 (2011-04-15)
+
+=over 4
+
+=item B<(!) Renamed one Date::Manip::Recur method>
+
+The Date::Manip::Recur::base method has been renamed to basedate . The
+Date::Manip::Recur::base method should return the Date::Manip::Base object
+like all the other Date::Manip modules.
+
+=item B<(*) Reworked holidays defined as recurrences>
+
+Improved dealing with the bootstrap problem of defining holidays, especially
+those that contain business day flags. Mike Tonks
+
+=item New features
+
+The printf function will now take multiple format strings and return a list
+of values. Zsban Ambrus
+
+=item Bug fixes
+
+Fixed a bug where GlobalCnf wasn't working. Peter Edwards
+
+Improved error messages in a few cases.
+
+Fixed a bug where one invalid date/timezone check was ignored. Morten Bjornsvik
+
+Fixed a bug where '$base2 = new Date::Manip::Base $base1' wasn't working. RT 67143
+
+Fixed a bug where passing dates in to the Recur->dates method failed. RT 67144
+
+Fixed a bug where the mode wasn't being preserved correctly for a delta. RT 67150
+
+Fixed a bug in recurrences where a base date outside of a date range with a very
+uncommon recurrence format would not work correctly.
+
+Fixed a problem where the '%s' printf option didn't work in GMT.
+Jean-Michel Hiver
+
+=item Time zone fixes
+
+Newest zoneinfo data (tzdata 2011f)
+
+=item Documentation fixes
+
+Fixed two bad recurrence examples in the documentation. Peter Edwards
+and Mike Tonks
+
+=back
+
+=head1 VERSION 6.22 (2011-03-07)
+
+=over 4
+
+=item Time zone fixes
+
+Newest zoneinfo data (tzdata 2011b)
+
+Fixed a bug where the gmtoff method of getting the local timezone was broken.
+Martin Zinser.
+
+Fixed the 'env' method of determining the local time zone to allow the
+value to be an offset of seconds since UTC. This allows the VMS
+SYS$TIMEZONE_DIFFERENTIAL variable to work correctly. Martin Zinser.
+
+Removed the SYS$TIMEZONE_RULE method from VMS since the value stored there
+is not the name of a timezone (it's a rule in a non-standard format). Based
+on discussion with Martin Zinser.
+
+Improved the order in which aliases, abbreviations, etc., are tested
+to test current usage before non-current usage (there were a few cases
+where old usages were getting tested before current usage.
+
+=item Language fixes
+
+The module will now die if a language module cannot be loaded (most
+likely due to a YAML::Syck issue). Based on discussion with Martin Zinser.
+
+=item Documentation fixes
+
+Added a sample config file document. Based on discussion with Rich Duzenbury.
+
+=back
+
+=head1 VERSION 6.21 (2011-01-10)
+
+=over 4
+
+=item New features
+
+Deltas may now contain spelled out numbers, so 'in 2 weeks' and 'in two weeks'
+will both work. Daniel Shahaf
+
+=item Bug fixes
+
+Fixed a bug where week_of_year didn't work in some cases. Chris Eveland.
+
+Fixed a minor potential bug. Geraint Edwards.
+
+=item Time zone fixes
+
+Updated windows time zone aliases. Daniel Harding
+
+=item Language fixes
+
+Added Norwegian. Glenn Sogn
+
+=back
+
+=head1 VERSION 6.20 (2010-12-01)
+
+=over 4
+
+=item B<(*) (!) Reworked recurrences>
+
+Recurrences were reworked in a (slightly) backward incompatible way to
+improve their usefulness (and to make them conform to the expected
+results). Most recurrences will work the same as previously, but a few will
+differ. Most of this was suggested by Jay Jacobs.
+
+A recurring event is now calculated relative to the base date, NOT relative
+to a previous event. For example, if a recurrence occurs every month, and
+the base date was Jan 31, then previously, recurring events would have
+been (in a non-leap year):
+
+ D(0) = Jan 31
+ D(1) = D(0) + 1 month = Feb 28
+ D(2) = D(1) + 1 month = Mar 28
+ ...
+
+The new behavior is:
+
+ D(0) = Jan 31
+ D(1) = D(0) + 1*(1 month) = Feb 28
+ D(2) = D(0) + 2*(1 month) = Mar 31
+ ...
+
+Previously, if a base date were not specified, it was not determined
+from the date range. Now, the start date of the date range acts as the
+base date.
+
+The meaning of the base date has changed slightly. It is much more
+meaningful and useful now.
+
+Added iterator functions. Daniel LaLiberte
+
+The RecurNumFudgeDays variable is no longer used and is deprecated.
+
+=item Time zone fixes
+
+Newest zoneinfo data (tzdata 2010o)
+
+=back
+
+=head1 VERSION 6.14 (2010-10-20)
+
+=over 4
+
+=item B<(*) Date::Manip 5.xx fully integrated with 6.xx>
+
+Date::Manip 5.xx and 6.xx are both installed automatically, and the
+correct one will be chosen.
+
+=item Bug fixes
+
+Fixed a bug where recurrence handling was broken. RT 62128
+
+=item Documentation fixes
+
+A lot of documentation was cleaned up to be easier to read, and better
+organized.
+
+=back
+
+=head1 VERSION 6.13 (2010-10-13)
+
+=over 4
+
+=item New features
+
+Added the input methods to Date::Manip::Date and Date::Manip::Delta. Ed Avis.
+
+The 'date +%z' command will also be used to determine the timezone. Oliver Schulze
+
+=item Bug fixes
+
+Several changes to try to get rid of a memory leaks reported in RT
+54937. Huge thanks to BrowserUK on perlmonks for help. Unfortunately, it
+ended up being a bug in perl, and will only be resolved when that bug is
+fixed. See the Date::Manip::Problems document for more information.
+
+ Reorganized Base/TZ to get rid of circular references.
+ Added end blocks to clean some global variables.
+ Got rid of switch/given structures.
+
+Fixed a bug where an incomplete date with 'last' in it was causing an
+error. RT 60138
+
+Fixed a bug where 'Sunday, 9th Jan 1972' wasn't parsed correctly. RT 57832
+
+=item Time zone fixes
+
+Fixed a bug where Zones.pm was generated with the abbreviations in the wrong
+order. Amish Chana.
+
+=item Language fixes
+
+French month abbreviations now support periods. Bernard Haerri
+
+=item Test fixes
+
+Added tests from RT 29655 to make sure that the problem never recurs.
+
+=item Documentation fixes
+
+Fixed documentation problem with the new_* methods in Date::Manip::Obj. Options
+must be passed in as \@opts rather than @opts.
+
+Cleaned up some of the documentation.
+
+=back
+
+=head1 VERSION 6.12 (2010-09-27)
+
+=over 4
+
+=item B<(!) IntCharSet config variable deprecated>
+
+With better support for international character sets, the old IntCharSet
+config variable (which was a bandaid at best) is deprecated. Currently, the
+functionality still exists, but it will be removed at some point.
+
+=item New features
+
+Added the Encoding config variable.
+
+Now supports parsing the EXIF date format. Rhesa Rozendaal
+
+=item Bug fixes
+
+Fixed Build.PL to not require perl 5.010 since the distribution as a whole
+does not require that (and I want that fact to be in META.yml).
+
+Fixed a bug where the Date::Manip::Date::set method was broken when setting
+individual fields. Helmut A. Bender
+
+Fixed a bug where set didn't work in Date::Manip::Delta. Patch provided in
+RT 59096.
+
+=item Time zone fixes
+
+Newest zoneinfo data (tzdata 2010m)
+
+=item Language fixes
+
+B<(*) Converted all language files to UTF-8 and added rudimentary support for
+character encodings. Some assistance by Stephen Ostermiller.>
+
+Fixed problem in Polish, Dutch. Stephen Ostermiller
+
+Extended support for 'nth' up to 53rd. Paco Regodon
+
+Added some corrections to German. Dieter Lange
+
+=item Documentation fixes
+
+Clarified Date::Manip::Recur documentation based on RT 59132.
+
+=back
+
+=head1 VERSION 6.11 (2010-04-30)
+
+=over 4
+
+=item Bug fixes
+
+Fixed a problem in Build.PL that had an incorrect module requirement.
+
+=back
+
+=head1 VERSION 6.10 (2010-04-29)
+
+=over 4
+
+=item B<(*) Combined 5.xx and 6.xx releases into one distribution>
+
+Because the automatic module management tools cpan/cpanp would try
+to upgrade Date::Manip to the most recent version, and the most
+recent version will only work if perl 5.10.0 or higher is installed,
+both the 5.xx and 6.xx releases are now combined into a single
+distribution.
+
+This is described more fully in the Date::Manip::Problems document.
+
+=item B<(!) Zones specified by offset>
+
+In all operations involving time zones, the time zone must be determined.
+By default, it would take all of the information available (date, ISDST,
+etc.) and determine the most likely time zone. It would take every time zone
+that matched each piece of information, starting with those that matched
+in a standard time followed by those that matched in a daylight saving
+time.
+
+When zones are specified by an offset, a standard time would always match
+since there are standard time zones that match all year long (the military
+time zones A-Z and the standard time zones of the form Etc/GMT+01). As a
+result, a daylight saving time match would never occur.
+
+Since (if the date falls during a daylight saving time period) you usually
+want to use a time zone that has that offset in daylight saving time,
+the default is now to check daylight saving time zones first, followed
+by standard times.
+
+See the Date::Manip::TZ manual (under the zone method) for more
+information.
+
+=item Bug fixes
+
+Fixed a bug where Date_ConvTZ not working correctly with time zones
+specified by offset. Chris Butler
+
+Fixed a bug where business mode calculations involving minutes was not
+handled correctly. Damien Moore
+
+Fixed a bug where business mode calculations failed in some cases. RT
+56638
+
+=item Time zone fixes
+
+Newest zoneinfo data (tzdata 2010i)
+
+Changed Date::Manip::TZ::zone so "dst" is sometimes the default
+$dstflag. Based on the bug report by Chris Butler.
+
+OpenUNIX puts a colon at the start of some time zones. It's removed.
+Jean Hassler
+
+=item Test fixes
+
+Converted tests to Test::Inter
+
+=item Documentation fixes
+
+Fixed a documentation bug in Date::Manip::TZ where "stdonly" was
+listed as the default value for $dstflag, but in actuality, "std" is
+the default.
+
+=back
+
+=head1 VERSION 6.07 (2010-02-05)
+
+=over 4
+
+=item Bug fixes
+
+Fixed bug in Date_TimeZone where it wasn't returning the time zone.
+Robert Eden
+
+=back
+
+=head1 VERSION 6.06 (2010-02-05)
+
+=over 4
+
+=item Bug fixes
+
+Minor bug where %Z printf format didn't always work.
+
+Added support for 5pm back in (it was omitted accidentally). Mark Kennedy
+
+Fixed a minor warning. Morten Bjoernsvik
+
+Some additional speedups.
+
+=item Time zone fixes
+
+Newest zoneinfo data (tzdata 2010b)
+
+Added dm_zdump example script.
+
+Improved TZ::periods functionality
+
+Fixed bug in Date_ConvTZ where empty values weren't defaulting to local time zone.
+Robert Eden
+
+Fixed a couple of problems in the generated time zones for some odd
+cases (America/Resolute and Asia/Tehran).
+
+=back
+
+=head1 VERSION 6.05 (2009-12-09)
+
+=over 4
+
+=item B<(!) %z format>
+
+In Date::Manip 5.xx, the %z format in UnixDate printed the offset in
+the form -0500. In 6.00, I changed that to -05:00:00, but this broke
+RFC 822 compliance.
+
+I've changed %z back to -0500, and introduced a new format (%N) which
+returns -05:00:00.
+
+Incidentally, this is the LAST unused letter, so I am now going to
+have to either stop adding formats, or add some extended format
+syntax. Not sure yet which, but this may involve a backwards
+incompatible change in the future.
+
+=item B<(*) Significant speedups.>
+
+Thanks to Moritz Lenz and BrowserUK on perlmonks for suggestions (and
+a number of other people on perlmonks for suggestions that I did not
+end up using, but which provided a great discussion).
+
+=item Bug fixes
+
+Fixed a bug in parse_date where the current time was getting used
+instead of the documented 00:00:00
+
+Bug fix where DateCalc didn't work with $mode in some cases.
+
+Fixed Makefile.PL/Build.PL to handle Win32::TieRegistry requirement.
+
+Changed %z printf behavior back to 5.xx and added %N format. Gilles
+Lamiral
+
+Added dm_date example script.
+
+=item Time zone fixes
+
+Fixed bug where non-English Windows versions didn't get the
+time zone. Thanks to Rene Schickbauer for testing.
+
+=item Test fixes
+
+Reduced the precision of 1 test to avoid a rounding difference when
+using a perl compiled with uselongdouble. Andreas Koenig
+
+=back
+
+=head1 VERSION 6.04 (2009-11-25)
+
+=over 4
+
+=item Bug fixes
+
+Fixed a bug where events were not interpreted in the correct time zone
+if SetDate/ForceDate used.
+
+=back
+
+=head1 VERSION 6.03 (2009-11-24)
+
+=over 4
+
+=item Bug fixes
+
+Corrects a backward incompatibility with UnixDate. Rene Schickbauer
+
+=item Test fixes
+
+A couple more corrections to the tests.
+
+=back
+
+=head1 VERSION 6.02 (2009-11-24)
+
+=over 4
+
+=item Bug fixes
+
+Disabled curr_zone_methods when taint checking on. I believe that Date::Manip
+is completely taint friendly at this point.
+
+=item Test fixes
+
+A quick fix to make sure that the tests run correctly in other time zones.
+
+=back
+
+=head1 VERSION 6.01 (2009-11-23)
+
+=over 4
+
+=item Bug fixes
+
+B<(*) Fixed a bug where dates were sometimes getting the wrong time zone when
+SetDate/ForceDate in effect.>
+
+=back
+
+=head1 VERSION 6.00 (2009-11-23)
+
+Date::Manip 6.00 is a total rethink of the module, and a nearly complete
+rewrite. Please refer to the Date::Manip::Changes5to6 document for a list
+of incompatible changes.
+
+=over 4
+
+=item Reorganization
+
+B<(*) Massive reorganization and near total rewrite.>
+
+B<(*) Broke into several smaller modules>
+
+=item New Features
+
+B<(*) Full time zone support (using tzdata 2009s)>
+
+Added some functionality (suggested by James Elson to improve setting
+the "current time". Done with the ForceDate config variable.
+
+B<(*) Converted languages to YAML for much easier maintenance. Patch and
+suggestion provided by Evan Carroll>
+
+Added much better formats for deltas. Suggested by Jim Hranicky.
+
+Borrowed the _FindWindowsTZName function from the DateTime-TimeZone
+module.
+
+Added SetDate config variable (based on a suggestion by Christian Campbell).
+
+Added parse_format which was first suggested by Kim Ryan.
+
+=item Other changes
+
+Several config variables deprecated
+
+Thanks to Jonathan Hogue for helping test Windows additions.
+
+=item Bug fixes (correcting problems in the 5.xx releases)
+
+Fixed a bug where "YYtoYYYY=c" wouldn't work.
+
+VMS bugfix to not call `date` command. Lane
+
+New Year's Day defined using a recurrence which might push the observed
+day to the previous year was broken. Reported by Jerry Wilcox.
+
+=item Language fixes
+
+Fixed typo in Turkish translation.
+
+Spelling fix in Dutch. Bart Van Loon
+
+=item Additional credits
+
+I have received many suggestions over time which were automatically
+handled during the 6.00 rewrite. Although the changes weren't made
+because of the suggestions specifically, I wanted to acknowledge
+them since I appreciate the suggestions.
+
+I believe the first person to suggest writing Date::Manip as an OO
+module was Eduard Derksen.
+
+Delta_Format initialization done outside of the function. Eric Boehm
+
+Added $subtract to calculation routines. First suggested by Steve Berlage.
+
+Added ability to set individual parts of the date (Date::Manip::Date::set).
+First suggested by Martin Thurn.
+
+UnixDate (i.e. Date::Manip::Date::printf) only calculates formats when
+they are needed. Eduard Derksen
+
+Parsing will skip some date/time formats if requested. This was first
+suggested by Eduard Derksen.
+
+It has been suggested several times to support multiple languages,
+multiple config files, or multiple sets of Date_Init
+options. These suggestions (by Meng Fang, Ed Avis, Christian
+Campbell, and perhaps others) were at the back of my mind as I
+developed the Date::Manip::Base class.
+
+The regular expressions are all i18n friendly in anticipation of much
+better support for localization. First suggested by Alex Kapranoff.
+
+Parsing a date ('today', 'Monday') gives a time of '00:00:00'. Suggested
+by Mark Aitchison.
+
+Working with fractional days was suggested by Peter van Hardenberg.
+This is implemented in Date::Manip::Base::day_of_year method.
+
+=back
+
+=head1 BUGS AND QUESTIONS
+
+Please refer to the L documentation for
+information on submitting bug reports or questions to the author.
+
+=head1 SEE ALSO
+
+L - main module documentation
+
+=head1 LICENSE
+
+This script is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself.
+
+=head1 AUTHOR
+
+Sullivan Beck (sbeck@cpan.org)
+
+=cut
diff --git a/lib/Date/Manip/Config.pod b/lib/Date/Manip/Config.pod
new file mode 100644
index 0000000..8b27a25
--- /dev/null
+++ b/lib/Date/Manip/Config.pod
@@ -0,0 +1,706 @@
+# Copyright (c) 1996-2017 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.
+
+=pod
+
+=head1 NAME
+
+Date::Manip::Config - Date::Manip configuration
+
+=head1 SYNOPSIS
+
+This documents the configuration information which is stored in
+each L object, how to modify this information,
+and how the information is used in the other Date::Manip modules.
+
+=head1 DESCRIPTION
+
+Date::Manip is a very configurable bundle of modules. Many of it's
+behaviors can be modified to change how date operations are done. To
+do this, a list of configuration variables may be set which define
+many Date::Manip behaviors.
+
+There are three ways to set config variables. The first two are to
+pass them in when creating an object, or to pass them to the config
+method after the object is created. All of the main Date::Manip
+modules (L, L, L,
+L, and L) have the config method.
+
+As an example, you can create and configure a
+L object using the commands:
+
+ $date = new Date::Manip::Date;
+ $date->config($var1,$val1,$var2,$val2,...);
+
+This can be shortened to:
+
+ $date = new Date::Manip::Date [$var1,$val1,...];
+
+The values of the config variables are stored in the L
+object. So, if you have a L object, it has a
+L object associated with it, and the configuration
+information is stored there. The same L object may be
+used by any number of higher objects, and all will share the same
+configuration. If multiple L objects share the same
+L object, setting a configuration variable on any of
+them affects all of the L objects. If you need to work
+with different configurations simultaneously, it is necessary to work
+with multiple L objects. This is covered in the
+L document.
+
+An alternate method exists if you are using one of the functional
+interfaces. To set a variable using the functional interface, use the
+call:
+
+ Date_Init("$var1=$val1");
+
+The third way to set config variables is to store them in a config
+file. The config file is read in by passing the appropriate values to
+the config method as described below. A config file is a good way to
+easily change a large number of settings. They are also necessary for
+other purposes (such as events and holidays which are covered
+in the L document).
+
+=head1 CONFIG FILES
+
+One of the variables that can be passed to the config method is
+"ConfigFile". The value of this variable is the path to a config
+file.
+
+When any Date::Manip::* object is configured, any number of config
+files may be read (and the config files can specify additional files
+to read).
+
+The starting section of a config file contains general configuration
+variables. A list of all config variables is given below.
+
+Following this, any number of special sections may be included in
+the config file. The special sections are used to specify other
+types of information, such as a list of holidays or special events.
+These special sections are described elsewhere in the documentation.
+
+The syntax of the config file is very simple. Every line is of
+the form:
+
+ VAR = VAL
+
+or
+
+ *SECTION
+
+Blank lines and lines beginning with a pound sign (#) are ignored.
+All whitespace is optional. Variables names in the main section and
+section names are case insensitive (though values in the main section
+are typically case sensitive). Strings in other sections (both variables
+and values) are case sensitive.
+
+The following is a sample config file:
+
+ DateFormat = US
+ Language = English
+
+ *Holidays
+
+ Dec 25 = Christmas
+ Jan 1 = New Year's
+
+All config variables that may appear in the main part of a config file
+are described in the next section. Other sections are described elsewhere.
+The *Holidays and *Events sections are both described in the
+L documentation.
+
+A sample config file is included with the Date::Manip distribution.
+Modify it as appropriate and copy it to some appropriate directory and
+use the ConfigFile variable to access it. For example, if a config
+file is stored in F, you can load it by:
+
+ $date->config("ConfigFile","/home/foo/Manip.cnf");
+
+or (if using a functional interface):
+
+ Date_Init("ConfigFile=/home/foo/Manip.cnf");
+
+NOTE: if you use business mode calculations, you must have a config
+file since this is the only place where you can define holidays.
+
+In the top section, only variables described below may be used. In
+other sections, checking (if any) is done in the module that uses
+the data from that section.
+
+=head1 BASIC CONFIGURATION VARIABLES
+
+This section describes the basic Date::Manip configuration variables
+which can be used in a config file, or which may be passed in using
+the appropriate functions for each module.
+
+Variable names are case insensitive, both as arguments to the config
+function and in the config file. The values are case sensitive except
+where specified otherwise.
+
+=over 4
+
+=item B
+
+The value for this config variable is ignored. Whenever the Defaults
+config variable is encountered, the defaults for all config variables
+are restored, overriding ALL changes that have been made.
+
+In other words, in the following call:
+
+ $date->config("Language","Russian",
+ "Defaults","1");
+
+the first option will end up being ignored since the Defaults config
+variable will set the language back to it's default value which is
+English.
+
+When using a functional interface, use:
+
+ Date_Init("Defaults=1");
+
+=item B
+
+The ConfigFile variable defines a config file which will be parsed for
+configuration information. It may be included any number of times, each
+one including the path to a single config file. The value of this
+variable is a full path to a file.
+
+An example call to the config function might be:
+
+ $date->config("ConfigFile","/tmp/file1",
+ 'Language',$val);
+
+Config files are parsed immediately when encountered. So in this example,
+the file F will be parsed before the next variable ('Language').
+In addition, if a config file contains a ConfigFile variable, that file
+will immediately be parsed before continuing with the original file.
+
+The path to the file may be specified in any way valid for the
+operating system. If a file is not found, a warning will be issued,
+but execution will continue.
+
+Multiple config files are safe, and a section may safely be split
+across multiple files.
+
+When using a functional interface, use:
+
+ Date_Init("ConfigFile=/tmp/file1");
+
+=item B
+
+Date::Manip can be used to parse dates in many different languages.
+A list of the languages is given in the L document.
+
+To parse dates in a different language, just use the Language config
+variable with the name of the language as the value. Language names
+are case insensitive.
+
+Additional languages may be added with the help of someone fluent in
+English and the other language. If you are interested in providing a
+translation for a new language, please refer to the L
+document for instructions.
+
+=item B
+
+Date::Manip has some support for handling date strings encoded in
+alternate character encodings.
+
+By default, input strings may be tested using multiple encodings that
+are commonly used for the specific languages, as well as using
+standard perl escape sequences, and output is done in UTF-8.
+
+The input, output, or both can be overridden using the Encoding
+variable.
+
+Setting Encoding to the name of a single encoding (a name supported
+by the Encoding perl module), will force all input and output to be
+done in that encoding.
+
+So, setting:
+
+ Encoding = iso-8859-1
+
+means that all input and output will be in that encoding. The
+encoding 'perl' has the special meaning of storing the string in
+perl escape sequences.
+
+Encoding can also be set to the name of two encoding (separated
+by a comma).
+
+ Encoding = iso-8859-1,utf-16
+
+which means that all input is in iso-8859-1 encoding, but all output
+will be utf-16.
+
+Encoding may also be set as follows:
+
+ Encoding = iso-8859-1,
+
+meaning that input is in iso-8859-1 and output is in the default (i.e.
+UTF-8) encoding.
+
+ Encoding = ,utf-16
+
+means to check the input in all of the encodings, but all output will
+be in utf-16 encoding.
+
+Note that any time you change languages, it will reset the encodings,
+so you should set this config variable AFTER setting the language.
+
+=item B
+
+It is sometimes necessary to know what day of week is regarded as
+first. By default, this is set to Monday as that conforms to ISO
+8601, 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).
+
+=item B
+
+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 B
+
+Some commands may produce a printable version of a date. By default,
+the printable version of the date is of the format:
+
+ YYYYMMDDHH:MN:SS
+
+Two other simple versions have been created. If the Printable variable is
+set to 1, the format is:
+
+ YYYYMMDDHHMNSS
+
+If Printable is set to 2, the format is:
+
+ YYYY-MM-DD-HH:MN:SS
+
+This config variable is present in order to maintain backward
+compatibility, and may actually be deprecated at some point. As such,
+additional formats will not be added. Instead, use the printf method
+in the L module to extract information with complete
+flexibility.
+
+=back
+
+=head1 DATE PARSING CONFIGURATION VARIABLES
+
+=over 4
+
+=item B
+
+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"
+(case insensitive) forces the first behavior (Dec 10). Setting
+DateFormat to anything else forces the second behavior (Oct 12). The
+"US" setting is the default (sorry about that... I live in the US).
+
+=item B
+
+When parsing a date containing a 2-digit year, the year must be converted
+to 4 digits. This config variable determines how this is done.
+
+By default, a 2 digit year is treated as falling in the 100 year period of
+CURR-89 to CURR+10. So in the year 2005, a two digit year will be somewhere
+in the range 1916 to 2015.
+
+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. Although the most common choice of values
+will be somewhere between 0 and 99, there is no restriction on N that
+forces it to be so. It can actually be any positive or negative number
+you want to force it into any 100 year period desired.
+
+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, in 1998, "C" forces
+2 digit years to be 1900-1999. "C18" would always force a 2 digit year to
+be in the range 1800-1899. Note: I'm aware that the actual definitions of
+century are 1901-2000, NOT 1900-1999, so for purists, treat this as
+the way to supply the first two digits rather than as supplying a
+century.
+
+It can also be set to the form "C####" to force it into a specific 100
+year period. C1950 refers to 1950-2049.
+
+=item B
+
+When a date is parsed from one of the formats listed in the "Common date formats"
+or "Less common formats" sections of the L document, and no time
+is explicitly included, the default time can be determined by the value of this
+variable. The two possible values are:
+
+ midnight the default time is 00:00:00
+ curr the default time is the current time
+
+"midnight" is the default value.
+
+NOTE: this only applies to dates parsed with the parse method. Dates parsed
+using the parse_date method always default to 00:00:00.
+
+=item B
+
+By default, the time separator (i.e. the character that separates
+hours from minutes and minutes from seconds) is specified in the
+language translations and in most cases it does not include a period.
+In English, the only defined time separator is a colon (:), so the time
+can be written as 12:15:30 .
+
+If you want to use a period (.) as a time separator as well, set this
+to 1. Then you can write the time as 12.15.30 .
+
+By default, a period is used as a date separator, so 12.15.30 would be
+interpreted as Dec 15 1930 (or 2030), so if you use the period as a
+date separator, it should not be used as a time separator too.
+
+=item B
+
+By default, when parsing a string like 'Jun 1925', it will be interpreted
+as 'Jun 19, 2025' (i.e. MMM DDYY). Also, the string '1925 Jun' is not allowed.
+
+This variable can be set to either 'first' or 'last', and in that case,
+both 'Jun 1925' and '1925 Jun' will be allowed, and will refer to either
+the first or last day of June in 1925.
+
+=back
+
+=head1 BUSINESS CONFIGURATION VARIABLES
+
+These are configuration variables used to define work days and
+holidays used in business mode calculations. Refer to the
+L documentation for details on these calculations.
+
+=over 4
+
+=item B
+
+=item B
+
+The first and last days of the work week. These default to Monday and
+Friday. Days are numbered from 1 (Monday) to 7 (Sunday). WorkWeekBeg
+must come before WorkWeekEnd numerically so there is no way to handle
+a work week of Sunday to Thursday using these variables.
+
+There is also no way to handle an odd work schedule such as 10 days
+on, 4 days off.
+
+However, both of these situations can be handled using a fairly simple
+workaround.
+
+To handle a work week of Sunday to Thursday, just set WorkWeekBeg=1
+and WorkWeekEnd=7 and defined a holiday that occurs every Friday and
+Saturday.
+
+To handle a 10 days on, 4 days off schedule, do something similar
+but defined a holiday that occurs on all of the 4 days off.
+
+Both of these can be done using recurrences. Refer to the L
+documentation for details.
+
+=item B
+
+=item B
+
+=item B
+
+If WorkDay24Hr is non-zero, a work day is treated as usually being 24
+hours long (daylight saving time changes ARE taken into account). The
+WorkDayBeg and WorkDayEnd variables are ignored in this case.
+
+By default, WorkDay24Hr is zero, and the work day is defined by the
+WorkDayBeg and WorkDayEnd variables. These are the times when the work
+day starts and ends respectively. 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).
+
+The time in both should be a valid time format (H, H:M, or H:M:S).
+
+Note that setting WorkDay24Hr to a non-zero value automatically sets
+WorkDayBeg and WorkDayEnd to "00:00:00" and "24:00:00" respectively,
+so to switch back to a non-24 hour day, you will need to reset both
+of those config variables.
+
+Similarly, setting either the WorkDayBeg or WorkDayEnd variables
+automatically turns off WorkDay24Hr.
+
+=item B
+
+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
+C method (and the
+C function) and is easily overridden (see
+documentation for the nearest_business_day method).
+
+=item B
+
+=item B
+
+If these variables are used (a value must be passed in, but is
+ignored), the current list of defined holidays or events is erased. A
+new set will be set the next time a config file is read in.
+
+Although these variables are supported, the best way to have multiple
+holiday or events lists will be to create multiple L
+objects based on separate config files.
+
+=back
+
+=head1 RECURRENCE CONFIGURATION VARIABLES
+
+The following config variables help in the handling of recurrences.
+
+=over 4
+
+=item B
+
+When a recurrence is created, it begins with a default range (start
+and end date). The range selected depends on the value of this
+variable, and can be set to any of the following:
+
+ none no default range supplied
+ year the current year
+ month the current month
+ week the current week
+ day the current day
+ all Jan 2, 0001 to Dec 30, 9999
+
+The default value is "none".
+
+=back
+
+=head1 TIME ZONE RELATED CONFIGURATION VARIABLES
+
+The following configuration variables may alter the current
+time zone. As such, they are only available once the L
+module is available. An easy way to handle this is to only pass them
+to the config method of a L object or one of the high
+level objects (L, L, or
+L).
+
+Many of Date::Manip's operations rely on knowing what time it is
+now. This consists of three things: knowing what date and time it is,
+knowing what time zone it is, and knowing whether it is daylight
+saving or not. All of this is necessary in order to correctly handle
+every possible date.
+
+The daylight saving time information is only used for a couple hours
+each year during daylight saving time changes (at all other times, the
+date, time, and time zone are sufficient information), so it is
+optional, and defaults to standard time if omitted.
+
+The default behavior of Date::Manip is to use the system localtime
+function to determine the date, time, and daylight saving time
+information, and to use various methods (see
+L) to determine what
+time zone the computer is in.
+
+=over 4
+
+=item B
+
+This variable is deprecated, but will be supported for several
+releases. The SetDate or ForceDate variables (described next) should be
+used instead.
+
+The following are equivalent:
+
+ $date->config("tz","Europe/Rome");
+ $date->config("setdate","now,Europe/Rome");
+
+or in the functional interface:
+
+ Date_Init("tz=Europe/Rome");
+ Date_Init("setdate=now,Europe/Rome");
+
+=item B
+
+The SetDate config variable is used to set the current date, time, or
+time zone, but then allow it to change over time using the rules of
+that time zone.
+
+There are several cases where this may be useful.
+
+Often, you may want to use the system time to get the date and time, but
+you want to work in another time zone. For this, use the call:
+
+ $date->config("setdate","now,ZONE");
+
+or in the function interface:
+
+ Date_Init("setdate=now,ZONE");
+
+If it is currently
+
+ Jun 6, 2009 12:00:00 in the America/New_York time zone
+
+and you call:
+
+ $date->config("setdate","now,Europe/Rome");
+
+the Date::Manip will treat that exact instant as
+
+ Jun 6, 2009 12:00:00 in the Europe/Rome time zone
+
+At that precise moment, looking at the system time and parsing the
+date "now" in Date::Manip will give the same date and time.
+
+The time will continue to advance, but it will use time change rules
+from the Europe/Rome time zone. What that means is that if a daylight
+saving time occurs on the computer, but NOT in the Europe/Rome
+time zone (or vice versa), the system date and time will no longer
+match the results of parsing the date "now" in Date::Manip.
+
+In general (unless the program runs for an extended period of
+time), the system date and time WILL match the value of "now", so
+this is a good way to simulate placing the computer in another
+time zone.
+
+If the current date/time is ambiguous (i.e. it exists in both
+standard and daylight saving time in the alternate zone), you
+can use the call:
+
+ $date->config("setdate","now,DSTFLAG,ZONE");
+
+to force it to be in one or the other. DSTFLAG can be "std",
+"dst", "stdonly", or "dstonly". "std" and "dst" mean that
+the date can be in either standard or saving time, but will
+try standard first (for "dst") or saving time first (if "dst"),
+and will only try the other if the date is not valid. If
+"stdonly" or "dstonly" is used, the date will be forced to
+be standard or saving time respectively (an error will be
+triggered if there is no valid date in that time).
+
+If the current date/time doesn't exist in the alternate zone,
+an error will occur.
+
+The other common operation is that you might want to see results
+as they would appear on a computer running in a different time zone.
+
+This can be done using the call:
+
+ $date->config("setdate","zone,ZONE");
+ $date->config("setdate","zone,DSTFLAG,ZONE");
+
+If it is currently
+
+ Jun 6, 2009 12:00:00 in the America/New_York time zone
+
+and you call:
+
+ $date->config("setdate","zone,America/Chicago");
+
+then parsing "now" at precisely that moment will return "Jun 6, 2009
+11:00:00". This is equivalent to working in the current zone, but
+then converting everything to the alternate zone.
+
+Note that DSTFLAG is only used if ZONE is entered as an offset.
+
+The final case where the SetDate config variable is used is to alter
+the date and time to some other value (completely independent of
+the current date and time) and allow it to advance normally from
+that point.
+
+ $date->config("setdate","DATE");
+ $date->config("setdate","DATE,ZONE");
+ $date->config("setdate","DATE,DSTFLAG,ZONE");
+
+set both the date/time and zone.
+
+If DATE is not valid in the time zone (either the local time zone
+or the specified one), and error occurs.
+
+The call:
+
+ $date->config("setdate","now");
+
+resets everything to use the current date/time and zone and lets it
+advance normally.
+
+=item B
+
+The ForceDate config variable is similar to the SetDate variable, except
+that once "now" is set, it is not allowed to change. Parsing the date "now"
+will not change, regardless of how long the program runs (unless either
+the SetDate or ForceDate variables are set to some other value).
+
+ $date->config("forcedate","now,ZONE");
+ $date->config("forcedate","now,DSTFLAG,ZONE");
+ $date->config("forcedate","zone,ZONE");
+ $date->config("forcedate","zone,DSTFLAG,ZONE");
+ $date->config("forcedate","DATE");
+ $date->config("forcedate","DATE,ZONE");
+ $date->config("forcedate","DATE,DSTFLAG,ZONE");
+ $date->config("forcedate","now");
+
+all set "now" in the same way as the SetDate variable. Spaces after commas are
+ignored.
+
+=back
+
+ZONE can be any time zone name, alias, abbreviation, or offset, and
+the best time zone will be determined from all given information.
+
+It should be noted that setting the SetDate or ForceDate variable
+twice will always refer to the system date/time as a starting point.
+For example, if a program is running, and calls the method:
+
+ $date->config("forcedate","now");
+
+at Jun 6, 2009 at 12:00, that time will be treated as now from that
+point on. If the same call is done an hour later, "now" will then
+be Jun 6, 2009 at 13:00 from that moment on.
+
+Since the current date is used in the date parsing routines, no
+parsing can be done on the DATE value in any of the calls. Instead,
+DATE must be a date in one of the two formats:
+
+ YYYY-MM-DD-HH:MN:SS
+ YYYYMMDDHH:MN:SS
+
+=head1 DEPRECATED CONFIGURATION VARIABLES
+
+The following config variables are currently supported, but are
+deprecated. They will be removed in a future Date::Manip release:
+
+=over 4
+
+=item B
+
+This is discussed above. Use SetDate or ForceDate instead.
+
+Scheduled for removal 2016-03-01
+
+=back
+
+=head1 KNOWN BUGS
+
+None known.
+
+=head1 BUGS AND QUESTIONS
+
+Please refer to the L documentation for
+information on submitting bug reports or questions to the author.
+
+=head1 SEE ALSO
+
+L - main module documentation
+
+=head1 LICENSE
+
+This script is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself.
+
+=head1 AUTHOR
+
+Sullivan Beck (sbeck@cpan.org)
+
+=cut
diff --git a/lib/Date/Manip/ConfigFile.pod b/lib/Date/Manip/ConfigFile.pod
new file mode 100644
index 0000000..1f04b91
--- /dev/null
+++ b/lib/Date/Manip/ConfigFile.pod
@@ -0,0 +1,164 @@
+# Copyright (c) 2011-2017 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.
+
+=pod
+
+=head1 NAME
+
+Date::Manip::ConfigFile - sample config file
+
+=head1 DESCRIPTION
+
+Date::Manip is a highly configurable module. Many of the options
+can be set in a config file. This document includes a sample config
+file.
+
+The config file consists of three sections. The first section is
+config variables. This is described more fully in the L
+document.
+
+The second section is the holiday definition section. The third section
+is the event definition section. These are both described more fully
+in the L document.
+
+=head1 SAMPLE CONFIG FILE
+
+The sample config file below works for newer versions of Date::Manip.
+Some of the config variables may change, or be deprecated, and some
+may not work with earlier versions of Date::Manip, so it is strongly
+suggested that you check out the documentation for the version of
+Date::Manip you are using to confirm any of the config variables you
+want to include.
+
+ ################################
+ # CONFIG VARIABLES
+ ################################
+ # See Date::Manip::Config man page for a description of all
+ # config variables.
+
+ # To include configuration information from additional
+ # config files:
+
+ ConfigFile = /path/to/another/config/file
+ ConfigFile = /path/to/another/config/file2
+
+ # For handling other languages
+
+ Language = English
+ DateFormat = US
+ Encoding =
+
+ # Set the current timezone:
+
+ SetDate = now,America/New_York
+
+ # Set the work work
+
+ WorkWeekBeg = 1
+ WorkWeekEnd = 5
+ WorkDay24Hr = 0
+ WorkDayBeg = 08:00
+ WorkDayEnd = 17:00
+ TomorrowFirst = 1
+
+ # Misc. variables
+
+ YYtoYYYY = 89
+ FirstDay = 1
+ Jan1Week1 = 0
+ Printable = 0
+ DefaultTime = midnight
+ RecurRange = none
+
+ ################################
+ # HOLIDAYS
+ ################################
+ # See the Date::Manip::Holidays man page for a description of
+ # this section.
+ *HOLIDAYS
+
+ # FEDERAL HOLIDAYS
+ ##################
+
+ # You can express New Year's Day as the actual day (Jan 1)
+ # or the observed day (Jan 1 or the nearest week day).
+ # You can't include BOTH because once a day is marked as
+ # a holiday, a second definition will treat it the same
+ # as a weekend and choose another day to assign the holiday
+ # to (so there would be two days designated as New Years).
+
+ # Jan 1 = New Year's Day
+ 1*1:0:1:0:0:0*DWD = New Year's Day (observed)
+
+ # Two different ways to defined MLK day
+
+ third Monday in Jan = Martin Luther King Jr.'s Birthday
+ # 1*1:3:1:0:0:0 = Martin Luther King Jr.'s Birthday
+
+ # Observed by federal employees in Washington D.C.
+
+ # Jan 20 = Inauguration day
+ third Monday in Feb = Washington's Birthday
+ last Monday in May = Memorial Day
+ 1st Monday in Sep = Labor Day
+ second Monday in Oct = Columbus Day
+
+ # Jul 4 = Independence Day
+ 1*7:0:4:0:0:0*DWD = Independence Day
+
+ # 11/11 = Veterans Day
+ 1*11:0:11:0:0:0*DWD = Veteran's Day
+
+ # To define both Thanksgiving and the day after, use the
+ # following two lines:
+
+ fourth Thu in Nov = Thanksgiving
+ 1*11:4:4:0:0:0*FD1 = Day after Thanksgiving
+
+ # Dec 25 = Christmas
+ 1*12:0:25:0:0:0*DWD = Christmas
+
+ # SAMPLE HOLIDAYS
+ ##################
+
+ # You can define a one-time-only holiday by specifying
+ # the day and year.
+
+ 6/2/1999 = A special test holiday for 1999
+
+ ################################
+ # EVENTS
+ ################################
+ # See the Date::Manip::Holidays man page for a description of
+ # this section.
+ *EVENTS
+
+ 2000-02-01 = Event01
+ 2000-02-01-12:00:00 = Event02
+ 02-01 = Event03
+ 02-01 12:00:00 = Event04
+ 1*2:0:3:13:00:00 = Event05
+
+ 2000-02-05 10:00:00 ; 2000-02-05 10:59:59 = Event06
+ 2000-02-05 ; 2000-02-06 = Event07
+ 02-05 ; 02-06 = Event08
+
+ 2000-02-07 10:00:00 ; 0:0:0:0:3:0:0 = Event09
+ 02-07 10:00:00 ; 0:0:0:0:4:0:0 = Event10
+ 1*2:0:7:10:00:00 ; 0:0:0:0:5:0:0 = Event11
+
+=head1 SEE ALSO
+
+L - main module documentation
+
+=head1 LICENSE
+
+This script is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself.
+
+=head1 AUTHOR
+
+Sullivan Beck (sbeck@cpan.org)
+
+=cut
diff --git a/lib/Date/Manip/DM5.pm b/lib/Date/Manip/DM5.pm
new file mode 100644
index 0000000..397e623
--- /dev/null
+++ b/lib/Date/Manip/DM5.pm
@@ -0,0 +1,7485 @@
+package Date::Manip::DM5;
+# Copyright (c) 1995-2017 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 warnings;
+
+our($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="Unix" if (defined $^O and
+ $^O =~ /cygwin/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);
+$OS="AIX" if (defined $^O and
+ $^O =~ /aix/i);
+
+# Determine if we're doing taint checking
+#if ($] < 5.0080) {
+ $Date::Manip::DM5::NoTaint = eval { local $^W=0; eval("#" . substr($^X, 0, 0)); 1 };
+#} else {
+# $Date::Manip::DM5::NoTaint = (${^TAINT} == 0 ? 1 : 0);
+#}
+
+###########################################################################
+# 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::DM5::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"} = ",";
+ $Cnf{"PersonalCnf"} = "Manip.cnf";
+ $Cnf{"PersonalCnfPath"} = "/sys\$login";
+
+} else {
+ # Unix
+ $Cnf{"PathSep"} = ":";
+ $Cnf{"PersonalCnf"} = ".DateManip.cnf";
+ $Cnf{"PersonalCnfPath"} = ".:~";
+ @Date::Manip::DM5::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"}="";
+
+# Use this to make "today" mean "today at midnight".
+$Cnf{"TodayIsMidnight"}=0;
+
+###########################################################################
+
+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;
+
+our($Abbrevs);
+use Date::Manip::DM5abbrevs;
+
+$VERSION='6.60';
+our $DM5_VERSION = '5.66';
+
+########################################################################
+########################################################################
+
+$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 $DM5_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,
+ @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);
+
+ } elsif ($L eq "Catalan") {
+ _Date_Init_Catalan(\%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 = "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");
+ _Date_InitStrings($lang{"today"},\$Lang{$L}{"Today"},"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)
+
+ $Zone{"n2o"} = {};
+ ($Zone{"zones"},%{ $Zone{"n2o"} })=
+ _Date_Regexp($Abbrevs,"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);
+ if ($Cnf{"TodayIsMidnight"}) {
+ $Curr{"Today"}=_Date_Join($y,$m,$d,0,0,0);
+ } else {
+ $Curr{"Today"}=$Curr{"Now"};
+ }
+
+ $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
+ my($today)=$Lang{$L}{"Today"}; # today
+ my($offset)=$Lang{$L}{"Offset"}; # (yesterday|tomorrow)
+ my($zone)=$Zone{"zones"}; # (edt|est|...)
+ 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;
+
+ # When we have a digit followed immediately by a timezone (7EST), we
+ # will put a space between the digit, EXCEPT in the case of a single
+ # character military timezone. If the single character is followed
+ # by anything, no space is added.
+ $tmp = "";
+ while ( s/^(.*?\d)$zone(\s|$|[0-9])/$3/i ) {
+ my($bef,$z,$aft) = ($1,$2,$3);
+ if (length($z) != 1 || length($aft) == 0) {
+ $tmp .= "$bef $z";
+ } else {
+ $tmp .= "$bef$z";
+ }
+ }
+ $_ = "$tmp$_";
+ $zone = '\s+' . $zone . '(?:\s+|$)';
+
+ # 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\s*$/i);
+ $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+//;
+
+ # if a zone was found, get rid of the regexps
+ if ($z) {
+ $zone="";
+ $zone2="";
+ }
+
+ # dateTtime ISO 8601 formats
+ my($orig)=$_;
+
+ # 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) ||
+ ($iso && /^([0-9-]+)T$zone?$/i) ||
+ ($iso && /^([0-9-]+)T$zone2?$/i) ||
+ 0) {
+
+ # If we already got a timezone, don't get another one.
+ my(@z);
+ if ($z) {
+ @z=($z,$z2);
+ $z="";
+ }
+ ($_,$z,$z2) = ($1,$2,$3);
+ ($z,$z2)=@z if (@z);
+
+ s,([0-9])\s*-,$1 ,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
+ # YYYYMMDDtHHMNSSFFFF...
+ # YYYYMMDDtHHMNSS
+ # YYYYMMDDtHHMN
+ # YYYYMMDDtHH
+ # YY MMDDtHHMNSSFFFF...
+ # YY MMDDtHHMNSS
+ # YY MMDDtHHMN
+ # YY MMDDtHH
+ # The t is an optional letter "t".
+ ($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 && ($tmp="Now")) ||
+ (/^$today\s*$wkabb$/i && ($tmp="Today")) ) {
+ # now week (British date: 1 week from now)
+ # today week (British date: 1 week from today)
+ Date_Init() if (! $Cnf{"UpdateCurrTZ"});
+ $date=_DateCalc_DateDelta($Curr{$tmp},"+0:0:1:0:0:0:0",\$err,0);
+ $date=Date_SetTime($date,$h,$mn,$s) if ($time);
+ 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 && ($tmp="Now")) ||
+ (/^$today$/i && ($tmp="Today")) ) {
+ # now, today
+ Date_Init() if (! $Cnf{"UpdateCurrTZ"});
+ $date=$Curr{$tmp};
+ 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,$mode) = (@arg);
+ $ref=0;
+
+ if (defined $errref) {
+ if (ref $errref) {
+ $ref=1;
+ } elsif (! defined $mode) {
+ $mode=$errref;
+ $errref="";
+ }
+ }
+
+ my(@date,@delta,$ret,$tmp,$oldincalc,$oldmode)=();
+
+ if (exists $Curr{"Mode"}) {
+ $oldmode = $Curr{"Mode"};
+ } else {
+ $oldmode = 0;
+ }
+
+ if (defined $mode and $mode>=0 and $mode<=3) {
+ $Curr{"Mode"}=$mode;
+ } else {
+ $Curr{"Mode"}=0;
+ }
+
+ if (exists $Curr{"InCalc"}) {
+ $oldincalc = $Curr{"InCalc"};
+ } else {
+ $oldincalc = 0;
+ }
+ $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);
+ $Curr{"InCalc"} = $oldincalc;
+ $Curr{"Mode"} = $oldmode;
+ return;
+ }
+
+ if ($tmp=ParseDateString($D2)) {
+ if (_Date_Split($D2)) {
+ push(@date,$D2);
+ } else {
+ push(@date,$tmp);
+ }
+ } elsif ($tmp=ParseDateDelta($D2)) {
+ push(@delta,$tmp);
+ $mode = $Curr{"Mode"};
+ } else {
+ $$errref=2 if ($ref);
+ $Curr{"InCalc"} = $oldincalc;
+ $Curr{"Mode"} = $oldmode;
+ return;
+ }
+
+ $Curr{"InCalc"} = $oldincalc;
+ $Curr{"Mode"} = $oldmode;
+
+ 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 = sprintf "%04d", $yy-1;
+ $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 = sprintf "%04d", $yy-1;
+ $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{"x"}=qq|$d/$m/$f{"y"}| if ($Cnf{"DateFormat"} ne "US");
+ $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{"O"}=qq|$y-$m-${d}T$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,@arg)=@_;
+ my($mode);
+ if (lc($arg[0]) eq "approx") {
+ $mode = "approx";
+ shift(@arg);
+ } else {
+ $mode = "exact";
+ }
+ my($dec,@format) = @arg;
+
+ $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,$Ml,$yl)=();
+ $sl = 1;
+ $ml = $sl*60;
+ $hl = $ml*60;
+ $dl = $hl*24;
+ $wl = $dl*7;
+ $yl = $dl*365.25;
+ $Ml = $yl/12;
+
+ # The decimal amount of each unit contained in all smaller units
+ my($yd,$Md,$sd,$md,$hd,$dd,$wd)=();
+ if ($mode eq "exact") {
+ $yd = $M/12;
+ $Md = 0;
+ } else {
+ $yd = ($M*$Ml + $w*$wl + $d*$dl + $h*$hl + $m*$ml + $s*$sl)/$yl;
+ $Md = ($w*$wl + $d*$dl + $h*$hl + $m*$ml + $s*$sl)/$Ml;
+ }
+
+ $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;
+ $Mh = ($yh+$y)*12;
+
+ if ($mode eq "exact") {
+ $wh = 0;
+ $dh = ($wh+$w)*7;
+ } else {
+ $wh = ($yh+$y+$M/12)*365.25/7;
+ $dh = ($wh+$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
+ #
+
+ $date0 = $date_0 if (! $date0);
+ $date1 = $date_1 if (! $date1);
+ $dateb = $date_b if (! $dateb);
+ if ($flag =~ s/^\+//) {
+ $flag = "$flag_t,$flag" if ($flag_t);
+ }
+ $flag = $flag_t if (! $flag);
+ $flag = "" if (! $flag);
+
+ 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($f);
+ foreach $f (@flags) {
+ if ($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) {
+ # * 0-M-W-D-H-MN-S => 0 * M-W-D-H-MN-S
+
+ if ($y eq "0") {
+ push(@recur0,1);
+ shift(@recur1);
+ next RECUR;
+ }
+
+ # Y-M-W-D-H-MN-S
+
+ @y=_ReturnList($y);
+ foreach $y (@y) {
+ $y=_Date_FixYear($y) if (length($y)==2);
+ return () if (length($y)!=4 || ! _IsInt($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,-366,366) || $d==0);
+ }
+ }
+
+ @date=();
+ foreach $yy (@y) {
+ my $diy = Date_DaysInYear($yy);
+ foreach $d (@d) {
+ my $tmpd = $d;
+ $tmpd += ($diy+1) if ($tmpd < 0);
+ next if (! _IsInt($tmpd,1,$diy));
+ ($y,$m,$dd)=Date_NthDayOfYear($yy,$tmpd);
+ 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));
+ }
+
+ if ($d eq "0") {
+ @d=(1);
+ } else {
+ @d=_ReturnList($d);
+ return () if (! @d);
+ foreach $d (@d) {
+ return () if (! _IsInt($d,-31,31) || $d==0);
+ }
+ }
+
+ @date=();
+ foreach $y (@y) {
+ foreach $m (@m) {
+ my $dim = Date_DaysInMonth($m,$y);
+ foreach $d (@d) {
+ my $tmpd = $d;
+ $tmpd += ($dim+1) if ($d<0);
+ next if (! _IsInt($tmpd,1,$dim));
+ $date=_Date_Join($y,$m,$tmpd,0,0,0);
+ push(@date,$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,-53,53) || $w==0);
+ }
+
+ if ($d eq "0") {
+ @d=(1);
+ } else {
+ @d=_ReturnList($d);
+ return () if (! @d);
+ foreach $d (@d) {
+ $d += 8 if ($d<0);
+ return () if (! _IsInt($d,1,7));
+ }
+ }
+
+ @date=();
+ foreach $y (@y) {
+ foreach $w (@w) {
+ foreach $d (@d) {
+ my($tmpw,$del);
+ if ($w<0) {
+ $date="$y-12-31-00:00:00";
+ $tmpw = (-$w)-1;
+ $del="-0:0:$tmpw:0:0:0:0";
+ $date=Date_GetPrev($date,$d,1);
+ } else {
+ $date="$y-01-01-00:00:00";
+ $tmpw = ($w)-1;
+ $del="0:0:$tmpw:0:0:0:0";
+ $date=Date_GetNext($date,$d,1);
+ }
+ $date=_DateCalc_DateDelta($date,$del);
+ push(@date,$date) if ( (_Date_Split($date))[0] == $y);
+ }
+ }
+ }
+ last RECUR;
+
+ } else {
+
+ # * Y-M-WOM-DOW-H-MN-S
+ # * Y-M-WOM-0-H-MN-S
+
+ @m=_ReturnList($m);
+ return () if (! @m);
+ @w=_ReturnList($w);
+ return () if (! @w);
+ if ($d eq "0") {
+ @d=(1);
+ } else {
+ @d=_ReturnList($d);
+ }
+
+ @date=_Date_Recur_WoM(\@y,\@m,\@w,\@d);
+ last RECUR;
+ }
+ }
+
+ if ($#recur0==0) {
+
+ # Y * M-W-D-H-MN-S
+ $n=$y;
+ $n=1 if ($n==0);
+
+ if ($m eq "0") {
+
+ # Y * 0-W-D-H-MN-S => 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 && $y != 1);
+
+ @m=_ReturnList($m);
+ return () if (! @m);
+ foreach $m (@m) {
+ return () if (! _IsInt($m,1,12));
+ }
+
+ if ($d eq "0") {
+ @d = (1);
+ } else {
+ @d=_ReturnList($d);
+ return () if (! @d);
+ foreach $d (@d) {
+ return () if (! _IsInt($d,-31,31) || $d==0);
+ }
+ }
+
+ # 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];
+ if ($dateb) {
+ ($yb)=( _Date_Split($dateb, 1) )[0];
+ } else {
+ # If $y=1, there is no base year
+ $yb=0;
+ }
+
+ @date=();
+ for ($yy=$y0; $yy<=$y1; $yy++) {
+ if (($yy-$yb)%$n == 0) {
+ foreach $m (@m) {
+ foreach $d (@d) {
+ my $dim = Date_DaysInMonth($m,$yy);
+ my $tmpd = $d;
+ if ($tmpd < 0) {
+ $tmpd += ($dim+1);
+ }
+ next if (! _IsInt($tmpd,1,$dim));
+ $date=_Date_Join($yy,$m,$tmpd,0,0,0);
+ push(@date,$date);
+ }
+ }
+ }
+ }
+ last RECUR;
+
+ } else {
+
+ # Y * M-WOM-DOW-H-MN-S
+ # Y * M-WOM-0-H-MN-S
+ return () if (! $dateb && $y != 1);
+
+ @m=_ReturnList($m);
+ return () if (! @m);
+ @w=_ReturnList($w);
+ return () if (! @w);
+
+ if ($d eq "0") {
+ @d=(1);
+ } else {
+ @d=_ReturnList($d);
+ }
+
+ ($y0)=( _Date_Split($date0, 1) )[0];
+ ($y1)=( _Date_Split($date1, 1) )[0];
+ if ($dateb) {
+ ($yb)=( _Date_Split($dateb, 1) )[0];
+ } else {
+ # If $y=1, there is no base year
+ $yb=0;
+ }
+ @y=();
+ for ($yy=$y0; $yy<=$y1; $yy++) {
+ if (($yy-$yb)%$n == 0) {
+ push(@y,$yy);
+ }
+ }
+
+ @date=_Date_Recur_WoM(\@y,\@m,\@w,\@d);
+ last RECUR;
+ }
+ }
+
+ if ($#recur0==1) {
+
+ # Y-M * W-D-H-MN-S
+
+ if ($w eq "0") {
+ # Y-M * 0-D-H-MN-S => 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 && $y != 1);
+ $n=$y;
+ $n=1 if ($n==0);
+
+ @w=_ReturnList($w);
+ return () if (! @w);
+ foreach $w (@w) {
+ return () if ($w==0 || ! _IsInt($w,-53,53));
+ }
+
+ if ($d eq "0") {
+ @d=(1);
+ } else {
+ @d=_ReturnList($d);
+ return () if (! @d);
+ foreach $d (@d) {
+ $d += 8 if ($d<0);
+ return () if (! _IsInt($d,1,7));
+ }
+ }
+
+ # 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];
+ if ($dateb) {
+ ($yb)=( _Date_Split($dateb, 1) )[0];
+ } else {
+ # If $y=1, there is no base year
+ $yb=0;
+ }
+
+ @date=();
+ for ($yy=$y0; $yy<=$y1; $yy++) {
+ if (($yy-$yb)%$n == 0) {
+ foreach $w (@w) {
+ foreach $d (@d) {
+ my($tmpw,$del);
+ if ($w<0) {
+ $date="$yy-12-31-00:00:00";
+ $tmpw = (-$w)-1;
+ $del="-0:0:$tmpw:0:0:0:0";
+ $date=Date_GetPrev($date,$d,1);
+ } else {
+ $date="$yy-01-01-00:00:00";
+ $tmpw = ($w)-1;
+ $del="0:0:$tmpw:0:0:0:0";
+ $date=Date_GetNext($date,$d,1);
+ }
+ $date=DateCalc($date,$del);
+ next if ((_Date_Split($date))[0] != $yy);
+ push(@date,$date);
+ }
+ }
+ }
+ }
+ last RECUR;
+
+ } else {
+
+ # Y-M * WOM-0-H-MN-S
+ # Y-M * WOM-DOW-H-MN-S
+ return () if (! $dateb && ($y != 0 || $m != 1));
+ @tmp=(@recur0);
+ push(@tmp,0) while ($#tmp<6);
+ $delta=join(":",@tmp);
+ $dateb=$date0 if (! $dateb);
+ @tmp=_Date_Recur($date0,$date1,$dateb,$delta);
+
+ @w=_ReturnList($w);
+ @m=();
+ if ($d eq "0") {
+ @d=(1);
+ } else {
+ @d=_ReturnList($d);
+ }
+
+ @date=_Date_Recur_WoM(\@tmp,\@m,\@w,\@d);
+ 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);
+
+