home *** CD-ROM | disk | FTP | other *** search
/ CD Actual Thematic 7: Programming / CDAT7.iso / Share / Editores / Perl5 / perl / lib / site / Date / Manip.pm < prev   
Encoding:
Perl POD Document  |  1997-08-10  |  168.7 KB  |  4,978 lines

  1. package Date::Manip;
  2.  
  3. # Copyright (c) 1995-1997 Sullivan Beck. All rights reserved.
  4. # This program is free software; you can redistribute it and/or modify it
  5. # under the same terms as Perl itself.
  6.  
  7. ###########################################################################
  8. # CUSTOMIZATION
  9. ###########################################################################
  10. #
  11. # See the section of the POD documentation section CUSTOMIZING DATE::MANIP
  12. # below for a complete description of each of these variables.
  13.  
  14. # Location of a the global config file.  Tilde (~) expansions are allowed.
  15. $Date::Manip::GlobalCnf="";
  16. $Date::Manip::IgnoreGlobalCnf="";
  17.  
  18. ### Date::Manip variables set in the global config file
  19.  
  20. # Name of a personal config file and the path to search for it.  Tilde (~)
  21. # expansions are allowed.
  22. $Date::Manip::PersonalCnf=".DateManip.cnf";
  23. $Date::Manip::PersonalCnfPath=".:~";
  24.  
  25. ### Date::Manip variables set in the global or personal config file
  26.  
  27. # Which language to use when parsing dates.
  28. $Date::Manip::Language="English";
  29.  
  30. # 12/10 = Dec 10 (US) or Oct 12 (anything else)
  31. $Date::Manip::DateFormat="US";
  32.  
  33. # Local timezone
  34. $Date::Manip::TZ="";
  35.  
  36. # Timezone to work in (""=local, "IGNORE"=ignore timezones, or a timezone)
  37. $Date::Manip::ConvTZ="";
  38.  
  39. # Date::Manip internal format (0=YYYYMMDDHH:MN:SS, 1=YYYYHHMMDDHHMNSS)
  40. $Date::Manip::Internal=0;
  41.  
  42. # First day of the week (0=sunday, 6=saturday)
  43. $Date::Manip::FirstDay=0;
  44.  
  45. # First and last day of the work week  (0=sunday, 6=saturday)
  46. $Date::Manip::WorkWeekBeg=1;
  47. $Date::Manip::WorkWeekEnd=5;
  48.  
  49. # If non-nil, a work day is treated as 24 hours long (WorkDayBeg/WorkDayEnd
  50. # ignored)
  51. $Date::Manip::WorkDay24Hr=0;
  52.  
  53. # Start and end time of the work day (any time format allowed, seconds ignored)
  54. $Date::Manip::WorkDayBeg="08:00";
  55. $Date::Manip::WorkDayEnd="17:00";
  56.  
  57. # Erase the old holidays
  58. $Date::Manip::EraseHolidays="";
  59.  
  60. # Set this to non-zero to be produce completely backwards compatible deltas
  61. $Date::Manip::DeltaSigns=0;
  62.  
  63. ###########################################################################
  64.  
  65. require 5.000;
  66. require Exporter;
  67. @ISA = qw(Exporter);
  68. @EXPORT = qw(
  69.    DateManipVersion
  70.    ParseDate
  71.    UnixDate
  72.    DateCalc
  73.    ParseDateDelta
  74.  
  75.    Date_SetTime
  76.    Date_GetPrev
  77.    Date_GetNext
  78.    Date_DayOfWeek
  79.    Date_SecsSince1970
  80.    Date_SecsSince1970GMT
  81.    Date_DaysSince999
  82.    Date_DayOfYear
  83.    Date_DaysInYear
  84.    Date_WeekOfYear
  85.    Date_LeapYear
  86.    Date_DaySuffix
  87.    Date_TimeZone
  88.    Date_ConvTZ
  89.    Date_Init
  90.    Date_IsWorkDay
  91.    Date_NextWorkDay
  92.    Date_PrevWorkDay
  93. );
  94. use strict;
  95. use Carp;
  96. use Cwd;
  97. #use POSIX qw(tzname);
  98.  
  99. ########################################################################
  100. # HISTORY
  101. ########################################################################
  102.  
  103. # Written by:
  104. #    Sullivan Beck (beck@qtp.ufl.edu)
  105. # Any suggestions, bug reports, or donations :-) should be sent to me.
  106.  
  107. # Version 1.0  01/20/95
  108. #    Combined all routines into one library
  109. #
  110. # Version 1.1  02/08/95
  111. #    Added leap year checking
  112. #    Both "Feb" and "February" formats available
  113. #
  114. # Version 1.2  03/31/95
  115. #    Made months case insensitive
  116. #    Added a few date formats
  117. #
  118. # Version 2.0  04/17/95
  119. #    Included ideas from packages
  120. #       Time::ParseDate (David Muir Sharnoff <muir@idiom.com>)
  121. #       date.pl 3.2     (Terry McGonigal <tmcgonigal@gvc.com>)
  122. #    Made error checking much nicer
  123. #    Added seconds to ParseDate
  124. #
  125. # Version 3.0  05/03/95
  126. #    Added %DATE_ global variable to clean some stuff up
  127. #    Simplified several routines
  128. #    Added today/now/tomorrows/etc. formats
  129. #    Added UnixDate
  130. #    Added ParseDateDelta
  131. #
  132. # Version 4.0  08/13/95
  133. #    Switched to perl 5
  134. #    Cleaned up ParseDate, ParseDateDelta
  135. #    Added time first formats to ParseDate
  136. #    First public release
  137. #
  138. # Version 4.1  10/18/95
  139. #    Changed %DATE_ to %DateManip::Date
  140. #    Rewrote ParseDateDelta
  141. #    Added DateCalc
  142. #
  143. # Version 4.2  10/23/95
  144. #    UnixDate will now return a scalar or list depending on context
  145. #    ParseDate/ParseDateDelta will now take a scalar, a reference to a
  146. #       scalar, or a eference to an array
  147. #    Added copyright notice (requested by Tim Bunce <Tim.Bunce@ig.co.uk>)
  148. #    Simple timezone handling
  149. #    Added Date_SetTime, Date_GetPrev, Date_GetNext
  150. #
  151. # Version 4.3  10/26/95
  152. #    Added "which dofw in mmm" formats to ParseDate
  153. #    Added a bugfix of Adam Nevins where "12:xx pm" used to be parsed
  154. #        "24:xx:00".
  155. #
  156. # Version 5.00  06/21/96
  157. #    Switched to a package (patch supplied by Peter Bray
  158. #       <pbray@ind.tansu.com.au>)
  159. #       o  renamed to Date::Manip
  160. #       o  changed version number to 2 decimal places
  161. #       o  added POD documentation
  162. #       Thanks to Peter Bray, Randal Schwartz, Andreas Koenig for suggestions
  163. #    Fixed a bug pointed out by Peter Bray where it was complaining of
  164. #       an uninitialized variable.
  165. #
  166. # Version 5.01  06/24/96
  167. #    Fixes suggested by Rob Perelman <robp@electriciti.com>
  168. #       o  Fixed a typo (Friday misspelled Fridat)
  169. #       o  Documentation problem for \$err in DateCalc
  170. #       o  Added %F formtat to UnixDate
  171. #    Reworked a number of the ParseDate regular expressions to make
  172. #       them more flexible
  173. #    Added "Date at Time" types
  174. #    Weekdays can be entered and checked
  175. #    Two digit years fall in the range CurrYear-89 to CurrYear+10
  176. #
  177. # Version 5.02  07/15/96
  178. #    Fixed a bug where repeated calls to ParseDate("today") was not reset
  179. #    Replaced the %Date::Manip::Date variable with a large number of
  180. #       other, more flexible variables
  181. #    Added some internationalization (most of the routines had to be
  182. #       modified at least slightly)
  183. #    Rewrote the Init routine
  184. #
  185. # Version 5.03  07/17/96
  186. #    Fixed a couple of bugs in UnixDate.
  187. #    Declared package variables to avoid warning "Identifier XXX used
  188. #       only once".  Thanks to Peter Bray for the suggestion.
  189. #
  190. # Version 5.04  08/01/96
  191. #    Added support for fractional seconds (as generated by Sybase).  They
  192. #       are parsed and ignored.  Added by Kurt Stephens
  193. #       <stephens@il.us.swissbank.com>.
  194. #    Fixed bugs reported by J.B. Nicholson-Owens
  195. #       <jbn@mystery-train.cu-online.com>
  196. #       o  "Tue Jun 25 1996" wasn't parsed correctly (regexp was case
  197. #          sensitive)
  198. #       o  full day names not parsed correctly
  199. #       o  the default day in ErrorCheck should be 1, NOT currd since when
  200. #          currd>28, it may not be a valid date for the month
  201. #
  202. # Version 5.05  10/11/96
  203. #    Added Swedish translation (thanks to Andreas Johansson
  204. #       <Andreas.XS.Johansson@trab.se>
  205. #    Fixed bad mistake in documentation (use Date::Manip instead of
  206. #       use DateManip) pointed out by tuc@valhalla.stormking.com
  207. #    Fixed bug introduced in 5.04 when default day set to 1.  When no
  208. #       date given, have day default to today rather than 1.  It only
  209. #       defaults to one if a partial date is given.
  210. #    Changed deltas to be all positive or all negative when produced by
  211. #       DateCalc.  Suggested by Steve Braun <braun@gandalf.sp.trw.com>
  212. #    Fixed bug where Date_DaysSince999 returned the wrong value (the
  213. #       error did not affect any other functions in Date::Manip due to
  214. #       the way it was called and the nature of the error).  Pointed out
  215. #       by Jason Baker <bm11455@themis.ag.gov.bc.ca>.
  216. #    Minor improvements to documentation.
  217. #    Documented the 'sort within a sort' bug.
  218. #    Added DateManipVersion routine.
  219. #    Dates with commas in them are now read properly.
  220. #    Now supports timezones.
  221. #    Parses RFC 822 dates (thanks to J.B. Nicholson-Owens
  222. #       <jbn@mystery-train.cu-online.com> for suggestion).
  223. #    Parses ctime() date formats (suggested by Matthew R. Sheahan
  224. #       <chaos@crystal.palace.net>).
  225. #    Added Date_ConvTZ routine for timezone support.
  226. #    Fixed two places where a variable was declared twice using my (thanks
  227. #       to Ric Steinberger <ric@isl.sri.com>).
  228. #    Hopefully fixed installation problems.
  229. #    Now supports times like "noon" and "midnight".
  230. #    Got rid of the last (I think) couple of US specific strings.
  231. #    The time separators are now language specific so the French can
  232. #       write "10h30" and the Swedes can write "10.30".  Suggested by
  233. #       Andreas Johansson <Andreas.XS.Johansson@trab.se>.
  234. #    Fixed type in documentation/README pointed out by James K. Bence
  235. #       <jbence@math.ucla.edu>.
  236. #    Fixed bug in Date_SetTime (didn't work with $hr,$min,$sec < 10).
  237. #    Added ModuloAddition routine and simplified DateCalc.
  238. #    Date_TimeZone will now also check `date '+%Z'` suggested by
  239. #       Aharon Schkolnik <aharon@healdb.matat.health.gov.il>.
  240. #
  241. # Version 5.06  10/25/96
  242. #    Fixed another two places where a variable was declared twice using my
  243. #       (thanks to Ric Steinberger <ric@isl.sri.com>).
  244. #    Fixed a bug where fractional seconds weren't parsed correctly.
  245. #    Fixed a bug where "noon" and other special times were not parsed
  246. #       in the "which day of month" formats.
  247. #    Added "today at time" formats.
  248. #    Fixed a minor bug where a few matches were case sensitive.
  249. #    ParseDateDelta now normalizes the delta as well as DateCalc.
  250. #    Added %Q format "YYYYMMDD" to UnixDate.  Requested by Rob Perelman
  251. #       <robp@electriciti.com>.
  252. #    The command "date +%Z" doesn't work on SunOS machines (and perhaps
  253. #        others) so 5.05 is effectively broken.  5.06 released to fix this.
  254. #        Reported by Rob Perelman <robp@electriciti.com>.
  255. #
  256. # Version 5.07  12/10/96
  257. #    Huge number of code changes to clean things up.
  258. #    Added %q format "YYYYMMDDHHMMSS" to UnixDate.  Requested by Rob Perelman
  259. #       <robp@electriciti.com>.  Also added %P format "YYYYMMDDHH:MM:SS".
  260. #    Added a new config variable to allow you to work with multiple internal
  261. #       formats (with and without colons).  Requested by Rob Perelman
  262. #       <robp@electriciti.com>.  See Date_Init documentation.
  263. #    Added the following formats suggested by Andreas Johansson
  264. #       <Andreas.XS.Johansson@trab.se>:
  265. #          sunday week 22 [in 1996] [at 12:00]
  266. #          22nd sunday [in 1996] [at 12:00]
  267. #          sunday 22nd week [in 1996] [at 12:00]
  268. #    Added weeks to ParseDateDelta.  Suggested by Mike Bassman
  269. #       <mbassman@fia21-43.fiadev21.lehman.com>.  Note that since
  270. #       this is a late addition, I did not change the internal format
  271. #       of a delta.  Instead, it is added to the days field.
  272. #    Added a new config variable to allow you to define the first day of
  273. #       the week.  See Date_Init documentation.
  274. #    Added the following formats to ParseDate for conveniance (some were
  275. #       suggested by Mike Bassman <mbassman@fia21-43.fiadev21.lehman.com>):
  276. #          next/last friday [at time]
  277. #          next/last week [at time]
  278. #          in 2 weeks [at time]
  279. #          2 weeks ago [at time]
  280. #          Friday in 2 weeks
  281. #          in 2 weeks on friday
  282. #          Friday 2 weeks ago
  283. #          2 weeks ago friday
  284. #    Added Date_SecsSince1970GMT, moved the %s format to %o (secs since 1/1/70)
  285. #       and added %s format (secs since 1/1/70 GMT).  Based on suggestions by
  286. #       Mark Osbourne <marko@lexis-nexis.com>.  Note this introduces a minot
  287. #       backward incompatibility!
  288. #    Date_SetTime now works with international time separators.
  289. #    Changed how Date_Init arguments work.
  290. #    Fixed bug in Date_TimeZone where it didn't recognize +HHMN type time
  291. #       zones.  Thanks to Are Bryne <are.bryne@communique.no>.
  292. #    Added the %g format (%a, %d %b %Y %H:%M:%S %z) for an RFC 1123 date.
  293. #       Suggested by Are Bryne <are.bryne@communique.no>.
  294. #    Added WindowsNT check to Date_TimeZone to get around NT's weird date
  295. #       command.  Thanks to Are Bryne <are.bryne@communique.no>.
  296. #    Subroutines now check to see if 4 digit years are entered.  Suggested
  297. #       by Are Bryne <are.bryne@communique.no>.
  298. #    Fixed typo (CSD instead of CST).
  299. #    Now reads a config file.
  300. #    Added options to delete existing holidays and ignore global config file.
  301. #    The d:h:mn:s of ALL deltas are normalized.
  302. #    Added local($_) to all routines which use $_.  Suggested by Rob
  303. #       Perelman <robp@electriciti.com>.
  304. #    Date_GetNext and Date_GetPrev now return the next/prev occurence of a
  305. #       time as well as a day.  Suggested by Are Bryne
  306. #       <are.bryne@communique.no>.
  307. #    Complete rewrite of DateCalc.
  308. #    In approximate mode, deltas now come out completely normalized (only 1
  309. #       sign).  Suggested by Rob Perelman <robp@electriciti.com>.
  310. #    Added business mode.  See documentation.  Suggested by Mike Bassman
  311. #       <mbassman@fia21-43.fiadev21.lehman.com>.
  312. #    Modified how deltas are normalized and added the DeltaSigns config
  313. #       variable.
  314. #    Added test suite!
  315. #    Fixed sign in military timezones making Date::Manip RFC 1123 compliant
  316. #       (except that timezone information is not stored in any format)
  317. #    Added Date::Manip::InitDone so initialization isn't duplicated.
  318. #    Added a 3rd internal format to store YYYY-MM-DD HH:MN:SS (iso 8601).
  319. #    Fixed a bug where UnixDate %E format didn't work with single digit
  320. #       dates.  Patch supplied by J\yrgen N\yrgaard <jnp@www.ifs.dk>.
  321. #    Added a config variable to allow you to work with 24 hour business
  322. #       days.  Suggested by Mike Bassman
  323. #       <mbassman@fia21-43.fiadev21.lehman.com>.
  324. #    ParseDateDelta now returns "" rather than "+0:0:0:0:0:0" when there is
  325. #       an error.
  326. #    Fixed a bug where "today" was not converted to the correct timezone.
  327. #
  328. # Version 5.07p2  01/03/97
  329. #    Added lots of timezone abbreviations.
  330. #    Can now understand PST8PDT type zones (but only in Date_TimeZone).
  331. #    Fixed some tests (good for another year).
  332. #    Fixed a bug where a delta component of "-0" would mess things up.
  333. #       Reported by Nigel Chapman <nigel@macavon.demon.co.uk>.
  334. #    Released two patches for 5.07.
  335. #
  336. # Version 5.08  01/24/97
  337. #    Fixed serious bug in ConvTZ pointed out by David Hall
  338. #       <dhall@sportsline.com>.
  339. #    Modified Date_ConvTZ (and documented it).
  340. #    Released 5.08 to get this and the other two patches into circulation.
  341. #
  342. # Version 5.09  01/28/97
  343. #    Upgraded to 5.003_23 and fixed one problem associated with it.
  344. #    Used carp and changed all die's to confess.
  345. #    Replaced some UNIX commands with perl equivalents (date with localtime
  346. #       in the tests, pwd with cwd in the path routines).
  347. #    Cleaned up all routines working with the path.
  348. #    Tests work again (broke in 5.08).  Thanks to Alex Lewin <lewin@vgi.com>,
  349. #       and Michael Fuhr <mfuhr@blackhole.dimensional.com> for running
  350. #       debugging tests.
  351. #
  352. # Version 5.10  03/19/97
  353. #    Tests will now run regardless of the timezone you are in.
  354. #    Test will always read the DateManip.cnf file in t/ now.
  355. #    A failed test will now give slightly more information.
  356. #    Cleaned up In, At, and On regexps.
  357. #    DateManip.cnf file in t/ now sets ALL options to override any changes
  358. #       made in the Manip.pm file.
  359. #    Added documentation for backwards incompatibilities to POD.
  360. #    Added 2 checks for MSWin32 (date command and getpw* didn't work).  Thanks
  361. #       to Alan Humphrey <alanh@velleb.com>.
  362. #    Fixed two bugs in the DateCalc routines.  Pointed out by Kevin Baker
  363. #       <ol@twics.com>
  364. #    Fixed some problems in POD documentation.  Thanks to Marvin Solomon
  365. #       <solomon@cs.wisc.edu>.
  366. #    Fixed some problems with how "US/Eastern" type timezones were used.
  367. #       Thanks to Marvin Solomon <solomon@cs.wisc.edu>.
  368. #    Fixed minor POD error pointed out by John Perkins <john@cs.wisc.edu>.
  369. #    Added a check for Windows_95.  Thanks to charliew@atfppc.ppc.att.com.
  370. #    Changed documentation for Date_IsWorkDay (it was quite confusing using
  371. #       a variable named $time).  Thanks to Erik M. Schwartz
  372. #       <eriks@library.nrl.navy.mil>.
  373. #    Cleaned up checks for MacOS and Microsoft OS's.  Hopefully I'm catching
  374. #       everything.  Thanks to Charlie Wu <charwu@ibm.net> for one more check.
  375. #    Fixed typo in docs (midnight mispelled).  Thanks to Timothy Kimball
  376. #       <kimball@stsci.edu>.
  377. #    Fixed a typo which broke Time%Date (Date=dd%mmm%yy) format.  Thanks to
  378. #       Timothy Kimball <kimball@stsci.edu>.
  379.  
  380. # Backwards incompatibilities
  381. #
  382. # In 5.07
  383. #   %s format changed
  384. #   By default, the signs of stored in a different format (only minimum
  385. #     number of signs stored).  Backwards compatible if you set DeltaSigns=1.
  386. #   Date_Init arguments changed (old method supported but depreciated)
  387.  
  388. $Date::Manip::Version="5.10";
  389.  
  390. ########################################################################
  391. # TODO
  392. ########################################################################
  393.  
  394. ################ NEXT VERSION
  395.  
  396. ### SPEEDUPS
  397.  
  398. # use integer;   whenever possible
  399.  
  400. # in ParseDate/ParseDateDelta check for the internal format right away
  401.  
  402. # &Date_Init  if (! $Date::Manip::Initiailized)   to all calls to Date_Init
  403.  
  404. # UpdateHolidays, don't use ParseDate to parse dates of form DD/MM or MM/DD.
  405.  
  406. ### ISO
  407.  
  408. # Make sure "-" is not a date separator ever. (reserved by ISO 8601).
  409.  
  410. # In order to conform to ISO 8601:
  411. #
  412. #   date=YYYY-MM-DD
  413. #        YY-MM-DD
  414. #        YYYY-MM
  415. #        YY-MM
  416. #        YYYYMMDD
  417. #        YYMMDD
  418. #        YYYY
  419. #        YY
  420. #
  421. # By default, the week starts with Monday.
  422. # The 1st week of the year is the first full week which contains at least
  423. #    4 days in the year (so Jan 1 may actually be in one of the weeks of
  424. #    the previous year or week 1 may contain Dec 31 of the previous year!).
  425. #
  426. # The first week of the year 1997 lasts from 1996-12-30 to 1997-01-05 can
  427. # be specified:
  428. #        1995-W01
  429. #        1995W01
  430. #        95W01
  431. #
  432. # The day 1996-12-31, which is the Tuesday (day 2) of the first week of 1997,
  433. # can also be written as
  434. #        1997-W01-2
  435. #        1997W012
  436. #        97W012
  437. # Weeks are 1-53.
  438. #
  439. # Weekdays are numbered 1 (monday) to 7 (sunday).  Support sunday as 0 as
  440. # well, and typically sunday will be referred to as day 0.
  441. #
  442. # Formats with the day-of-year (001 to 366)
  443. #        YYYY-DOY
  444. #        YYYYDOY
  445. #
  446. # Times are given by:
  447. #        HH:MN:SS
  448. #        HH:MN
  449. #        HHMNSS
  450. #        HHMN
  451. #        HH
  452. #        HH:MN:SS.FFFFFFF
  453. #        HHMNSSFFFFFFF
  454. #
  455. # Note:  24:00:00 is NOT supported (though ISO allows it).
  456. #
  457. # All times may have a timezone attached to the end (space between time
  458. # and zone is optional) of the forms listed above as well as:
  459. #        +HH:MN
  460. #        +HHMN
  461. #        +HH
  462.  
  463. # Add an option to treat Jan 1 as the 1st week of the year.
  464.  
  465. # Add a UnixDate format to print out a date in the format:
  466. #   YYYY-Wwwd
  467. #   YYYY-doy
  468.  
  469. # check "Sunday 22nd week", "22nd Sunday" vs. ISO weeks
  470.  
  471. ### TESTS
  472.  
  473. # Add tests for all the new ParseDate formats to the test suite.
  474.  
  475. ### GRANULARITY
  476.  
  477. # $flag=&Date_GranularityTest($date,$base,$granularity [,$flags] [$width])
  478. #    $date and $base are dates
  479. #    $granularity and $width are deltas
  480. #    $flags is a list of flags
  481. #
  482. #    To test if a day is one of every other Friday (starting at Friday
  483. #    Feb 7, 1997), go:
  484. #       $base=&ParseDate("Friday Feb 7 1997");
  485. #       $date=&ParseDate("...");
  486. #       $granularity=&ParseDateDelta("+ 2 weeks");
  487. #       $flag=&Date_Granularity($date,$base,$granularity,"exact");
  488. #    If $flag is 1, the $date is a 2nd Friday from Feb 7.
  489. #
  490. #    The most important field in $granularity is the last non-zero element.
  491. #    In the above example, 2 weeks returns the delta 0:0:14:0:0:0 so the
  492. #    last non-zero element is days with a value of 14.
  493. #
  494. #    If $flags is empty, $date is checked to see if it occurs some multiple
  495. #    of 14 days before or after $base.  In this case, hourse, minutes, and
  496. #    seconds are completely ignored.
  497. #
  498. #    If $flags contains the words "before" or "after", $date must come
  499. #    before or after $base.
  500. #
  501. #    If $flags contains any other options, or if $width is passed in, the
  502. #    test is treated in an approximate way.  A flag of "approx" forces this
  503. #    behavior.
  504. #
  505. #    If $width is not passed in in an approximate comparison, it defaults
  506. #    to 1 in the last non-zero element.  Here, the default width is 1 day.
  507. #    If the flag "half" is used, the width (default or passed in) is
  508. #    halved.
  509. #
  510. #    For example if $width is 1 day, add a multiple of $granularity to
  511. #    $base to get as close to $date as possible.  If $date is within plus
  512. #    or minus 1 day of this new base, the test is successful.  A flag of
  513. #    "plus" or "minus" means that $date must be with plus 1 day or within
  514. #    minus one day of this new base.  Flags of "before" or "after" work
  515. #    as well.
  516.  
  517. # @list=&Date_GranularityList($date,$N,$granularity)
  518. #    Returns a list of $N dates AFTER $date which are created by adding
  519. #    $granularity to $date $N times.  If $N<0, it returns $N dates BEFORE
  520. #    $date (the list is in chronological order).
  521.  
  522. ### DAYLIGHT SAVINGS TIME
  523.  
  524. # Use POSIX tzset/tzname (and perhaps GNU date) to handle timezone and
  525. # daylight savings time correctly.  See messages by Marvin Soloman.
  526.  
  527. # If ignoring TIMEZONE info, treat all dates as in current timezone with
  528. # no d.s.t. effects (i.e. Jun 1 12:00 EDT == Jun 1 12:00 EST).
  529.  
  530. # To do calculations, convert to current timezone (Jun 1 12:00 EDT -> Jun 1
  531. # 11:00 EST even if that date doesn't really exist)
  532.  
  533. # Determine zone pairings EST/EDT, PST/PDT for all zones.  Store EST#EDT in
  534. # $Date::Manip::TZ rather than just EST or EDT.  Make sure everything is
  535. # paired up.  Places with only a single timezone should work as well.
  536.  
  537. # Make a 2nd hash where EST -> EST#EDT for all timezones.
  538.  
  539. # When doing date calculations, if neither date has a time (or if both are
  540. # at the exact same time and are in the same timezone or in timezones
  541. # related through daylight savings time such as EST and EDT), ignore the
  542. # time gain/loss from savings time transitions IFF the variable IgnoreDST
  543. # is on (it is by default).  Otherwise, do the calculation exactly.
  544.  
  545. # Add an option to all date calculations to ignore daylight savings time
  546. # transitions.
  547.  
  548. ### MISC
  549.  
  550. # Try to get rid of all `date` and other `UNIX COMMAND` things in Date::Manip
  551. #    `grep ^TZ`; `date`  in Date_TimeZone
  552.  
  553. # Add to ParseDate (Rob Perelman)
  554. #    dofw           (Friday == &Date_GetNext("today","friday",0,"00:00:00")
  555.  
  556. # Document how you need to use the stock .DateManip.cnf file when running
  557. # the tests.  Make sure that TZ=EST is set in the sample one.
  558.  
  559. # Fix Date::Manip:: Future,Past,Next,Prev
  560.  
  561. # Clean up ParseDate
  562.  
  563. # Clean up ParseDateDelta
  564.  
  565. # Combine GetNext and GetPrev (?)
  566.  
  567. # Make err an optional argument to DateCalc (by checking to see if the
  568. # 3rd argument exists).  If it does and is a reference, it is err, otherwise,
  569. # it is mode.
  570.  
  571. # Add a "SPECIAL HOLIDAY" section to fully specify holidays so weird ones
  572. # can be defined for each year.  Add Easter calculations here as well:
  573. #   Easter = easter
  574. # means that Easter is calculated using the method easter.
  575.  
  576. # Add
  577. #   Spanish
  578. #   German
  579. #   Italian
  580. #   Japanese (Kevin Baker will help)
  581.  
  582. # Fill in some of the language variables ($past, $future, $zones).
  583.  
  584. # Check Swedish/French special characters.
  585.  
  586. # Change EXPORT to EXPORT_OK (message 9 by Peter Bray)
  587.  
  588. # Add equivalent of UnixDate to print out Deltas in various formats
  589. # (mess 37 by Alan Burlison).  Nothing fancy.
  590. #
  591. # It prints out exact deltas as:
  592. #    plus/minus d:h:mn:s
  593. #    plus/minus s         (converted to s)
  594. #    plus/minus d         (converted to d, returned as a floating point)
  595. #    plus/minus h         (similar)
  596. #    plus/minus mn        (similar)
  597. # Approximate deltas as:
  598. #    plus/minus y:m  plus/minus d:h:m:s
  599. #    or, you can give a date as an argument which says take the approx.
  600. #    delta from that date and turn the result into an exact delta which
  601. #    can be printed in any of the exact formats.
  602.  
  603. ################ MAYBE (undecided whether it should be added)
  604.  
  605. # Mike Bassman (mess 49)
  606. #    "friday before last"
  607.  
  608. # $Date problems with RCS (mess 35 by Tim Freeman)
  609.  
  610. # Add "delta FROM date", "IN delta ON date", "delta AGO ON date"
  611.  
  612. ########################################################################
  613. ########################################################################
  614. #
  615. # Declare variables so we don't get any warnings about variables only
  616. # being used once.  In Date_Init, I often define a whole batch of related
  617. # variables knowing that I only have immediate use for some of them but
  618. # I may need others in the future.  To avoid the "Identifier XXX used only
  619. # once: possibly typo" warnings, all are declared here.
  620. #
  621. # Pacakge Variables
  622. #
  623.  
  624. $Date::Manip::Am = undef;
  625. $Date::Manip::AmExp = undef;
  626. $Date::Manip::AmPmExp = undef;
  627. $Date::Manip::Approx = undef;
  628. $Date::Manip::At = undef;
  629. $Date::Manip::Business = undef;
  630. $Date::Manip::Curr = undef;
  631. $Date::Manip::CurrAmPm = undef;
  632. $Date::Manip::CurrD = undef;
  633. $Date::Manip::CurrH = undef;
  634. $Date::Manip::CurrHolidayYear = 0;
  635. $Date::Manip::CurrM = undef;
  636. $Date::Manip::CurrMn = undef;
  637. $Date::Manip::CurrS = undef;
  638. $Date::Manip::CurrY = undef;
  639. $Date::Manip::CurrZoneExp = undef;
  640. $Date::Manip::DExp = undef;
  641. $Date::Manip::DayExp = undef;
  642. $Date::Manip::Exact = undef;
  643. $Date::Manip::Future = undef;
  644. $Date::Manip::HExp = undef;
  645. $Date::Manip::In = undef;
  646. $Date::Manip::Init = 0;
  647. $Date::Manip::InitDone = 0;
  648. $Date::Manip::InitFilesRead = 0;
  649. $Date::Manip::MExp = undef;
  650. $Date::Manip::MnExp = undef;
  651. $Date::Manip::Mode = undef;
  652. $Date::Manip::MonExp = undef;
  653. $Date::Manip::Next = undef;
  654. $Date::Manip::Now = undef;
  655. $Date::Manip::Offset = undef;
  656. $Date::Manip::On = undef;
  657. $Date::Manip::Past = undef;
  658. $Date::Manip::Pm = undef;
  659. $Date::Manip::PmExp = undef;
  660. $Date::Manip::Prev = undef;
  661. $Date::Manip::ResetWorkDay = 1;
  662. $Date::Manip::SepHM = undef;
  663. $Date::Manip::SepMS = undef;
  664. $Date::Manip::SepSS = undef;
  665. $Date::Manip::SExp = undef;
  666. $Date::Manip::TimesExp = undef;
  667. $Date::Manip::UpdateHolidays = 0;
  668. $Date::Manip::WDBh = undef;
  669. $Date::Manip::WDBm = undef;
  670. $Date::Manip::WDEh = undef;
  671. $Date::Manip::WDEm = undef;
  672. $Date::Manip::WDlen = undef;
  673. $Date::Manip::WExp = undef;
  674. $Date::Manip::WhichExp = undef;
  675. $Date::Manip::WkExp = undef;
  676. $Date::Manip::YExp = undef;
  677. $Date::Manip::ZoneExp = undef;
  678.  
  679. @Date::Manip::Day = ();
  680. @Date::Manip::Mon = ();
  681. @Date::Manip::Month = ();
  682. @Date::Manip::W = ();
  683. @Date::Manip::Week = ();
  684. @Date::Manip::Wk = ();
  685.  
  686. %Date::Manip::AmPm = ();
  687. %Date::Manip::CurrHolidays = ();
  688. %Date::Manip::CurrZone = ();
  689. %Date::Manip::Day = ();
  690. %Date::Manip::Holidays = ();
  691. %Date::Manip::Month = ();
  692. %Date::Manip::Offset = ();
  693. %Date::Manip::Times = ();
  694. %Date::Manip::Replace = ();
  695. %Date::Manip::Week = ();
  696. %Date::Manip::Which = ();
  697. %Date::Manip::Zone = ();
  698.  
  699. ########################################################################
  700. ########################################################################
  701.  
  702. sub DateManipVersion {
  703.   return $Date::Manip::Version;
  704. }
  705.  
  706. sub Date_SetTime {
  707.   my($date,$h,$mn,$s)=@_;
  708.   &Date_Init()  if (! $Date::Manip::InitDone);
  709.   my($y,$m,$d)=();
  710.  
  711.   if (! &CheckDate($date)) {
  712.     $date=&ParseDate($date);
  713.     return ""  if (! $date);
  714.   }
  715.  
  716.   ($y,$m,$d)=( &CheckDate($date) )[0..2];
  717.   ($h,$mn,$s)=&ParseTime($h,$mn,$s);
  718.  
  719.   my($ampm,$wk);
  720.   return ""  if (&Date_ErrorCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk));
  721.   &FormDate($y,$m,$d,$h,$mn,$s);
  722. }
  723.  
  724. sub Date_GetPrev {
  725.   my($date,$day,$today,$hr,$min,$sec)=@_;
  726.   &Date_Init()  if (! $Date::Manip::InitDone);
  727.   my($y,$m,$d,$h,$mn,$s,$err)=();
  728.  
  729.   if (! &CheckDate($date)) {
  730.     $date=&ParseDate($date);
  731.     return ""  if (! $date);
  732.   }
  733.   ($y,$m,$d)=( &CheckDate($date) )[0..2];
  734.  
  735.   if (defined $day and $day ne "") {
  736.     my($day_w)=();
  737.     my($date_w)=&Date_DayOfWeek($m,$d,$y);
  738.     my(%days)=%Date::Manip::Week;
  739.     if (&IsInt($day)) {
  740.       $day_w=$day;
  741.     } else {
  742.       return ""  if (! exists $days{lc($day)});
  743.       $day_w=$days{lc($day)};
  744.     }
  745.     if ($day_w == $date_w) {
  746.       $date=&DateCalc_DateDelta($date,"-0:0:7:0:0:0",\$err,0)  if (! $today);
  747.     } else {
  748.       $day_w -= 7  if ($day_w>$date_w); # make sure previous day is less
  749.       $day = $date_w - $day_w;
  750.       $date=&DateCalc_DateDelta($date,"-0:0:$day:0:0:0",\$err,0);
  751.     }
  752.     $date=&Date_SetTime($date,$hr,$min,$sec)  if (defined $hr);
  753.  
  754.   } else {
  755.     ($h,$mn,$s)=( &CheckDate($date) )[3..5];
  756.     my($delta,$d,$err)=();
  757.     my($th,$tm,$ts)=&ParseTime($hr,$min,$sec);
  758.     if (defined $hr and $hr ne "") {
  759.       ($hr,$min,$sec)=($th,$tm,$ts);
  760.       $delta="-0:0:1:0:0:0";
  761.     } elsif (defined $min and $min ne "") {
  762.       ($hr,$min,$sec)=($h,$tm,$ts);
  763.       $delta="-0:0:0:1:0:0";
  764.     } elsif (defined $sec and $sec ne "") {
  765.       ($hr,$min,$sec)=($h,$mn,$ts);
  766.       $delta="-0:0:0:0:1:0";
  767.     } else {
  768.       confess "ERROR: invalid arguments in Date_GetPrev.\n";
  769.     }
  770.  
  771.     $d=&Date_SetTime($date,$hr,$min,$sec);
  772.     if ($today) {
  773.       $d=&DateCalc_DateDelta($d,$delta,\$err,0)  if ($d gt $date);
  774.     } else {
  775.       $d=&DateCalc_DateDelta($d,$delta,\$err,0)  if ($d ge $date);
  776.     }
  777.     $date=$d;
  778.   }
  779.   return $date;
  780. }
  781.  
  782. sub Date_GetNext {
  783.   my($date,$day,$today,$hr,$min,$sec)=@_;
  784.   &Date_Init()  if (! $Date::Manip::InitDone);
  785.   my($y,$m,$d,$h,$mn,$s,$err)=();
  786.  
  787.   if (! &CheckDate($date)) {
  788.     $date=&ParseDate($date);
  789.     return ""  if (! $date);
  790.   }
  791.   ($y,$m,$d)=( &CheckDate($date) )[0..2];
  792.  
  793.   if (defined $day and $day ne "") {
  794.     my($day_w)=();
  795.     my($date_w)=&Date_DayOfWeek($m,$d,$y);
  796.     my(%days)=%Date::Manip::Week;
  797.     if (&IsInt($day)) {
  798.       $day_w=$day;
  799.     } else {
  800.       return ""  if (! exists $days{lc($day)});
  801.       $day_w=$days{lc($day)};
  802.     }
  803.     if ($day_w == $date_w) {
  804.       $date=&DateCalc_DateDelta($date,"+0:0:7:0:0:0",\$err,0)  if (! $today);
  805.     } else {
  806.       $date_w -= 7  if ($date_w>$day_w); # make sure next date is greater
  807.       $day = $day_w - $date_w;
  808.       $date=&DateCalc_DateDelta($date,"+0:0:$day:0:0:0",\$err,0);
  809.     }
  810.     $date=&Date_SetTime($date,$hr,$min,$sec)  if (defined $hr);
  811.  
  812.   } else {
  813.     ($h,$mn,$s)=( &CheckDate($date) )[3..5];
  814.     my($delta,$d,$err)=();
  815.     my($th,$tm,$ts)=&ParseTime($hr,$min,$sec);
  816.     if (defined $hr and $hr ne "") {
  817.       ($hr,$min,$sec)=($th,$tm,$ts);
  818.       $delta="+0:0:1:0:0:0";
  819.     } elsif (defined $min and $min ne "") {
  820.       ($hr,$min,$sec)=($h,$tm,$ts);
  821.       $delta="+0:0:0:1:0:0";
  822.     } elsif (defined $sec and $sec ne "") {
  823.       ($hr,$min,$sec)=($h,$mn,$ts);
  824.       $delta="+0:0:0:0:1:0";
  825.     } else {
  826.       confess "ERROR: invalid arguments in Date_GetNext.\n";
  827.     }
  828.  
  829.     $d=&Date_SetTime($date,$hr,$min,$sec);
  830.     if ($today) {
  831.       $d=&DateCalc_DateDelta($d,$delta,\$err,0)  if ($d lt $date);
  832.     } else {
  833.       $d=&DateCalc_DateDelta($d,$delta,\$err,0)  if ($d le $date);
  834.     }
  835.     $date=$d;
  836.   }
  837.  
  838.   return $date;
  839. }
  840.  
  841. sub DateCalc {
  842.   my($D1,$D2,$errref,$mode)=@_;
  843.   my(@date,@delta,$ret,$tmp)=();
  844.  
  845.   if (defined $mode  and  $mode>=0  and  $mode<=2) {
  846.     $Date::Manip::Mode=$mode;
  847.   } else {
  848.     $Date::Manip::Mode=0;
  849.   }
  850.  
  851.   if ($tmp=&ParseDate($D1)) {
  852.     push(@date,$tmp);
  853.   } elsif ($tmp=&ParseDateDelta($D1)) {
  854.     push(@delta,$tmp);
  855.   } else {
  856.     $$errref=1;
  857.     return;
  858.   }
  859.  
  860.   if ($tmp=&ParseDate($D2)) {
  861.     push(@date,$tmp);
  862.   } elsif ($tmp=&ParseDateDelta($D2)) {
  863.     push(@delta,$tmp);
  864.   } else {
  865.     $$errref=2;
  866.     return;
  867.   }
  868.   $mode=$Date::Manip::Mode;
  869.  
  870.   if ($#date==1) {
  871.     $ret=&DateCalc_DateDate(@date,$mode);
  872.   } elsif ($#date==0) {
  873.     $ret=&DateCalc_DateDelta(@date,@delta,$errref,$mode);
  874.   } else {
  875.     $ret=&DateCalc_DeltaDelta(@delta,$mode);
  876.   }
  877.   $ret;
  878. }
  879.  
  880. sub ParseDateDelta {
  881.   my($args,@args,@a,$ref,$date)=();
  882.   local($_)=();
  883.   @a=@_;
  884.  
  885.   # @a : is the list of args to ParseDateDelta.  Currently, only one argument
  886.   #      is allowed and it must be a scalar (or a reference to a scalar)
  887.   #      or a reference to an array.
  888.  
  889.   if ($#a!=0) {
  890.     print "ERROR:  Invalid number of arguments to ParseDateDelta.\n";
  891.     return "";
  892.   }
  893.   $args=$a[0];
  894.   $ref=ref $args;
  895.   if (! $ref) {
  896.     @args=($args);
  897.   } elsif ($ref eq "ARRAY") {
  898.     @args=@$args;
  899.   } elsif ($ref eq "SCALAR") {
  900.     @args=($$args);
  901.   } else {
  902.     print "ERROR:  Invalid arguments to ParseDateDelta.\n";
  903.     return "";
  904.   }
  905.   @a=@args;
  906.  
  907.   # @args : a list containing all the arguments (dereferenced if appropriate)
  908.   # @a    : a list containing all the arguments currently being examined
  909.   # $ref  : nil, "SCALAR", or "ARRAY" depending on whether a scalar, a
  910.   #         reference to a scalar, or a reference to an array was passed in
  911.   # $args : the scalar or refererence passed in
  912.  
  913.   my($y,$m,$w,$d,$h,$mn,$s,$ys,$ms,$ws,$ds,$hs,$mns,$ss,$dir)=();
  914.   my($def,@delta1,@delta2,$colon,$sign,$delta,$i,$sign)=();
  915.   my($from,$to)=();
  916.   my($workweek)=$Date::Manip::WorkWeekEnd-$Date::Manip::WorkWeekBeg+1;
  917.  
  918.   &Date_Init();
  919.   my($signexp)='([+-]?)';
  920.   my($numexp)='(\d+)';
  921.   my($exp1)="(?: \\s* $signexp \\s* $numexp \\s*)";
  922.   my($yexp,$mexp,$wexp,$dexp,$hexp,$mnexp,$sexp)=();
  923.   $yexp =$mexp=$wexp=$dexp=$hexp=$mnexp=$sexp="()()";
  924.   $yexp ="(?: $exp1 $Date::Manip::YExp)?"  if ($Date::Manip::YExp);
  925.   $mexp ="(?: $exp1 $Date::Manip::MExp)?"  if ($Date::Manip::MExp);
  926.   $wexp ="(?: $exp1 $Date::Manip::WExp)?"  if ($Date::Manip::WExp);
  927.   $dexp ="(?: $exp1 $Date::Manip::DExp)?"  if ($Date::Manip::DExp);
  928.   $hexp ="(?: $exp1 $Date::Manip::HExp)?"  if ($Date::Manip::HExp);
  929.   $mnexp="(?: $exp1 $Date::Manip::MnExp)?" if ($Date::Manip::MnExp);
  930.   $sexp ="(?: $exp1 $Date::Manip::SExp)?"  if ($Date::Manip::SExp);
  931.   my($future)=$Date::Manip::Future;
  932.   my($past)=$Date::Manip::Past;
  933.  
  934.   $delta="";
  935.   PARSE: while (@a) {
  936.     $_ = join(" ",@a);
  937.     s/\s*$//;
  938.  
  939.     # Mode is set in DateCalc.  ParseDateDelta only overrides it if the
  940.     # string contains a mode.
  941.     if      ($Date::Manip::Exact and s/$Date::Manip::Exact//) {
  942.       $Date::Manip::Mode=0;
  943.     } elsif ($Date::Manip::Approx and s/$Date::Manip::Approx//) {
  944.       $Date::Manip::Mode=1;
  945.     } elsif ($Date::Manip::Business and s/$Date::Manip::Business//) {
  946.       $Date::Manip::Mode=2;
  947.     } elsif (! defined $Date::Manip::Mode) {
  948.       $Date::Manip::Mode=0;
  949.     }
  950.     $workweek=7  if ($Date::Manip::Mode != 2);
  951.  
  952.     foreach $from (keys %Date::Manip::Replace) {
  953.       $to=$Date::Manip::Replace{$from};
  954.       s/(^|[^a-z])$from($|[^a-z])/$1$to$2/i;
  955.     }
  956.  
  957.     # in or ago
  958.     s/^\s* $future \s*//ix  if ($future ne "(?:)");
  959.     $dir=1;
  960.     $dir=-1  if ($past  and  s/\s*$past\s*//i);
  961.  
  962.     # the colon part of the delta
  963.     $colon="";
  964.     if (s/$signexp?$numexp?(:($signexp?$numexp)?)+$//) {
  965.       $colon=$&;
  966.       s/\s*$//;
  967.     }
  968.  
  969.     # the non-colon part of the delta
  970.     $sign="+";
  971.     s/^$yexp $mexp $wexp $dexp $hexp $mnexp $sexp$//xi;
  972.     ($ys,$y,$ms,$m,$ws,$w,$ds,$d,$hs,$h,$mns,$mn,$ss,$s)=
  973.       ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14);
  974.     (defined($ys)  and $ys)  ? ($sign=$ys)  : ($ys=$sign);
  975.     (defined($ms)  and $ms)  ? ($sign=$ms)  : ($ms=$ys);
  976.     (defined($ws)  and $ws)  ? ($sign=$ws)  : ($ws=$ms);
  977.     (defined($ds)  and $ds)  ? ($sign=$ds)  : ($ds=$ws);
  978.     (defined($hs)  and $hs)  ? ($sign=$hs)  : ($hs=$ds);
  979.     (defined($mns) and $mns) ? ($sign=$mns) : ($mns=$hs);
  980.     (defined($ss)  and $ss)  ? ($sign=$ss)  : ($ss=$ms);
  981.  
  982.     # keep track of the last defined element (from -1 to 5)
  983.     $def=-1;
  984.     (defined($y))  ? ($def=0) : ($y=0);
  985.     (defined($m))  ? ($def=1) : ($m=0);
  986.     (defined($d))  ? ($def=2) : ($d=0);
  987.     (defined($h))  ? ($def=3) : ($h=0);
  988.     (defined($mn)) ? ($def=4) : ($mn=0);
  989.     (defined($s))  ? ($def=5) : ($s=0);
  990.  
  991.     # add weeks to the days
  992.     if (defined $w  and  $w) {
  993.       $d= "$ds$d" + $workweek*"$ws$w";
  994.       if ($d<0) {
  995.         $ds="-";
  996.         $d=-$d;
  997.       }
  998.     }
  999.  
  1000.     @delta1=("$ys$y","$ms$m","$ds$d","$hs$h","$mns$mn","$ss$s");
  1001.  
  1002.     # Split the colon part and set the sign
  1003.     @delta2=&CheckDelta($colon,$sign);
  1004.  
  1005.     # check to see that too many fields have not been entered and that
  1006.     # the entire argument list has been used
  1007.     if ($_ or ($def+$#delta2)>4) {
  1008.       pop(@a);
  1009.       next PARSE;
  1010.     }
  1011.  
  1012.     # add the colon and non-colon part together (and take care of dir)
  1013.     # (force it to carry a sign)
  1014.     unshift (@delta2,"+0")  while ($#delta2<5);
  1015.     for ($i=0; $i<=5; $i++) {
  1016.       $delta1[$i] += $delta2[$i];
  1017.       $delta1[$i] *= $dir  if ($delta1[$i] != 0);
  1018.       $delta1[$i] = "+".$delta1[$i]  if ($delta1[$i]>0);
  1019.     }
  1020.  
  1021.     # form the delta and shift off the valid part
  1022.     $delta=join(":",@delta1);
  1023.     splice(@args,0,$#a+1);
  1024.     @$args=@args  if (defined $ref  and  $ref eq "ARRAY");
  1025.     last PARSE;
  1026.   }
  1027.  
  1028.   $delta=&NormalizeDelta($delta,$Date::Manip::Mode);
  1029.   return $delta;
  1030. }
  1031.  
  1032. sub UnixDate {
  1033.   my($date,@format)=@_;
  1034.   local($_)=();
  1035.   my($format,%f,$out,@out,$c,$date1,$date2)=();
  1036.   my($scalar)=();
  1037.   $date=&ParseDate($date);
  1038.   return  if (! $date);
  1039.  
  1040.   ($f{"Y"},$f{"m"},$f{"d"},$f{"H"},$f{"M"},$f{"S"})=&CheckDate($date);
  1041.   $f{"y"}=substr $f{"Y"},2;
  1042.   my($m,$d,$y)=($f{"m"},$f{"d"},$f{"Y"});
  1043.   &Date_Init();
  1044.  
  1045.   if (! wantarray) {
  1046.     $format=join(" ",@format);
  1047.     @format=($format);
  1048.     $scalar=1;
  1049.   }
  1050.  
  1051.   # month, week
  1052.   $_=$m;
  1053.   s/^0//;
  1054.   $f{"b"}=$f{"h"}=$Date::Manip::Mon[$_-1];
  1055.   $f{"B"}=$Date::Manip::Month[$_-1];
  1056.   $_=$m;
  1057.   s/^0/ /;
  1058.   $f{"f"}=$_;
  1059.   $f{"U"}=&Date_WeekOfYear($m,$d,$y,0);
  1060.   $f{"W"}=&Date_WeekOfYear($m,$d,$y,1);
  1061.   $f{"U"}="0".$f{"U"}  if (length $f{"U"} < 2);
  1062.   $f{"W"}="0".$f{"W"}  if (length $f{"W"} < 2);
  1063.  
  1064.   # day
  1065.   $f{"j"}=&Date_DayOfYear($m,$d,$y);
  1066.   $_=$d;
  1067.   s/^0/ /;
  1068.   $f{"e"}=$_;
  1069.   $f{"w"}=&Date_DayOfWeek($m,$d,$y);
  1070.   $f{"v"}=$Date::Manip::W[$f{"w"}];
  1071.   $f{"v"}=" ".$f{"v"}  if (length $f{"v"} < 2);
  1072.   $f{"a"}=$Date::Manip::Wk[$f{"w"}];
  1073.   $f{"A"}=$Date::Manip::Week[$f{"w"}];
  1074.   $f{"E"}=&Date_DaySuffix($f{"e"});
  1075.  
  1076.   # hour
  1077.   $_=$f{"H"};
  1078.   s/^0/ /;
  1079.   $f{"k"}=$_;
  1080.   $f{"i"}=$f{"k"}+1;
  1081.   $f{"i"}=$f{"k"};
  1082.   $f{"i"}=12          if ($f{"k"}==0);
  1083.   $f{"i"}=$f{"k"}-12  if ($f{"k"}>12);
  1084.   $f{"i"}=$f{"i"}-12  if ($f{"i"}>12);
  1085.   $f{"i"}=" ".$f{"i"} if (length($f{"i"})<2);
  1086.   $f{"I"}=$f{"i"};
  1087.   $f{"I"}=~ s/^ /0/;
  1088.   $f{"p"}=$Date::Manip::Am;
  1089.   $f{"p"}=$Date::Manip::Pm  if ($f{"k"}>11);
  1090.  
  1091.   # minute, second, timezone
  1092.   $f{"o"}=&Date_SecsSince1970($m,$d,$y,$f{"H"},$f{"M"},$f{"S"});
  1093.   $f{"s"}=&Date_SecsSince1970GMT($m,$d,$y,$f{"H"},$f{"M"},$f{"S"});
  1094.   $f{"z"}=$f{"Z"}=
  1095.     ($Date::Manip::ConvTZ eq "IGNORE" or $Date::Manip::ConvTZ eq "" ?
  1096.      $Date::Manip::TZ : $Date::Manip::ConvTZ);
  1097.  
  1098.   # date, time
  1099.   $f{"c"}=qq|$f{"a"} $f{"b"} $f{"e"} $f{"H"}:$f{"M"}:$f{"S"} $y|;
  1100.   $f{"C"}=$f{"u"}=
  1101.     qq|$f{"a"} $f{"b"} $f{"e"} $f{"H"}:$f{"M"}:$f{"S"} $f{"z"} $y|;
  1102.   $f{"g"}=qq|$f{"a"},$d $f{"b"} $y $f{"H"}:$f{"M"}:$f{"S"} $f{"z"}|;
  1103.   $f{"D"}=$f{"x"}=qq|$m/$d/$f{"y"}|;
  1104.   $f{"r"}=qq|$f{"I"}:$f{"M"}:$f{"S"} $f{"p"}|;
  1105.   $f{"R"}=qq|$f{"H"}:$f{"M"}|;
  1106.   $f{"T"}=$f{"X"}=qq|$f{"H"}:$f{"M"}:$f{"S"}|;
  1107.   $f{"V"}=qq|$m$d$f{"H"}$f{"M"}$f{"y"}|;
  1108.   $f{"Q"}="$y$m$d";
  1109.   $f{"q"}=qq|$y$m$d$f{"H"}$f{"M"}$f{"S"}|;
  1110.   $f{"P"}=qq|$y$m$d$f{"H"}:$f{"M"}:$f{"S"}|;
  1111.   $f{"F"}=qq|$f{"A"}, $f{"B"} $f{"e"}, $f{"Y"}|;
  1112.   # %l is a special case.  Since it requires the use of the calculator
  1113.   # which requires this routine, an infinite recursion results.  To get
  1114.   # around this, %l is NOT determined every time this is called so the
  1115.   # recursion breaks.
  1116.  
  1117.   # other formats
  1118.   $f{"n"}="\n";
  1119.   $f{"t"}="\t";
  1120.   $f{"%"}="%";
  1121.   $f{"+"}="+";
  1122.  
  1123.   foreach $format (@format) {
  1124.     $format=reverse($format);
  1125.     $out="";
  1126.     while ($format) {
  1127.       $c=chop($format);
  1128.       if ($c eq "%") {
  1129.         $c=chop($format);
  1130.         if ($c eq "l") {
  1131.           $date1=&DateCalc_DateDelta($Date::Manip::Curr,"-0:6:0:0:0:0");
  1132.           $date2=&DateCalc_DateDelta($Date::Manip::Curr,"+0:6:0:0:0:0");
  1133.           if ($date gt $date1  and  $date lt $date2) {
  1134.             $f{"l"}=qq|$f{"b"} $f{"e"} $f{"H"}:$f{"M"}|;
  1135.           } else {
  1136.             $f{"l"}=qq|$f{"b"} $f{"e"}  $f{"Y"}|;
  1137.           }
  1138.           $out .= $f{"$c"};
  1139.         } elsif (exists $f{"$c"}) {
  1140.           $out .= $f{"$c"};
  1141.         } else {
  1142.           $out .= $c;
  1143.         }
  1144.       } else {
  1145.         $out .= $c;
  1146.       }
  1147.     }
  1148.     push(@out,$out);
  1149.   }
  1150.   if ($scalar) {
  1151.     return $out[0];
  1152.   } else {
  1153.     return (@out);
  1154.   }
  1155. }
  1156.  
  1157. sub ParseDate {
  1158.   my($args,@args,@a,$ref,$date)=();
  1159.   local($_)=();
  1160.   @a=@_;
  1161.   my($y,$m,$d,$h,$mn,$s,$i,$which,$dofw,$wk,$tmp,$z,$num,$err)=();
  1162.  
  1163.   # @a : is the list of args to ParseDate.  Currently, only one argument
  1164.   #      is allowed and it must be a scalar (or a reference to a scalar)
  1165.   #      or a reference to an array.
  1166.  
  1167.   if ($#a!=0) {
  1168.     print "ERROR:  Invalid number of arguments to ParseDate.\n";
  1169.     return "";
  1170.   }
  1171.   $args=$a[0];
  1172.   $ref=ref $args;
  1173.   if (! $ref) {
  1174.     return $args  if (&CheckDate($args));
  1175.     @args=($args);
  1176.   } elsif ($ref eq "ARRAY") {
  1177.     @args=@$args;
  1178.   } elsif ($ref eq "SCALAR") {
  1179.     return $$args  if (&CheckDate($$args));
  1180.     @args=($$args);
  1181.   } else {
  1182.     print "ERROR:  Invalid arguments to ParseDate.\n";
  1183.     return "";
  1184.   }
  1185.   @a=@args;
  1186.  
  1187.   # @args : a list containing all the arguments (dereferenced if appropriate)
  1188.   # @a    : a list containing all the arguments currently being examined
  1189.   # $ref  : nil, "SCALAR", or "ARRAY" depending on whether a scalar, a
  1190.   #         reference to a scalar, or a reference to an array was passed in
  1191.   # $args : the scalar or refererence passed in
  1192.  
  1193.   &Date_Init();
  1194.   my($type)=$Date::Manip::DateFormat;
  1195.   my($mmm)=$Date::Manip::MonExp;
  1196.   my($now)=$Date::Manip::Now;
  1197.   my($offset)=$Date::Manip::Offset;
  1198.   my($wkexp)=$Date::Manip::WkExp;
  1199.   my($timeexp)=$Date::Manip::TimesExp;
  1200.   my(%dofw)=%Date::Manip::Week;
  1201.   my($whichexp)=$Date::Manip::WhichExp;
  1202.   my(%which)=%Date::Manip::Which;
  1203.   my($daysexp)=$Date::Manip::DayExp;
  1204.   my(%dayshash)=%Date::Manip::Day;
  1205.   my($ampm)=$Date::Manip::AmPmExp;
  1206.   my($weeks)=$Date::Manip::WExp;
  1207.   my($next)=$Date::Manip::Next;
  1208.   my($prev)=$Date::Manip::Prev;
  1209.   my($ago)=$Date::Manip::Past;
  1210.  
  1211.   # Regular expressions for part of the date
  1212.   my($hm)=$Date::Manip::SepHM;
  1213.   my($ms)=$Date::Manip::SepMS;
  1214.   my($ss)=$Date::Manip::SepSS;
  1215.   my($YY) ='(\d{2}|\d{4})'; # 2 or 4 digits (year)
  1216.   my($DD)='(\d{2})';        # 2 digits      (month/day/hour/minute/second)
  1217.   my($D) ='(\d{1,2})';      # 1 or 2 digit  (month/day/hour)
  1218.   my($FD)="(?:$ss\\d+)?";   # fractional secs
  1219.   # There are two forms of the time.  Time/time are used when the time is
  1220.   # not the last element of the string.  TimeL/timeL are used when the time
  1221.   # is the last element.
  1222.   my($zone)=$Date::Manip::ZoneExp;
  1223.   # time in HH:MM:SS [Zone]
  1224.   my($Time) ="(?:$DD$hm$DD(?:$ms$DD$FD)?(?:\\s*$ampm)?$zone)";
  1225.   # time in hh:MM:SS [Zone]
  1226.   my($time) ="(?:$D$hm$DD(?:$ms$DD$FD)?(?:\\s*$ampm)?$zone)";
  1227.   my($sep)='([\/ .-])';
  1228.   my($at)='\s*'.$Date::Manip::At;
  1229.   my($in)='\s*'.$Date::Manip::In;
  1230.   my($on)='\s*'.$Date::Manip::On;
  1231.   my($com)=',?';
  1232.  
  1233.   $ampm="";
  1234.  
  1235.   $date="";
  1236.   PARSE: while($#a>=0) {
  1237.     $_=join(" ",@a);
  1238.  
  1239.     # Mode is set in DateCalc.  ParseDate only overrides it if the string
  1240.     # contains a mode.
  1241.     if      ($Date::Manip::Exact and s/$Date::Manip::Exact//) {
  1242.       $Date::Manip::Mode=0;
  1243.     } elsif ($Date::Manip::Approx and s/$Date::Manip::Approx//) {
  1244.       $Date::Manip::Mode=1;
  1245.     } elsif ($Date::Manip::Business and s/$Date::Manip::Business//) {
  1246.       $Date::Manip::Mode=2;
  1247.     } elsif (! defined $Date::Manip::Mode) {
  1248.       $Date::Manip::Mode=0;
  1249.     }
  1250.  
  1251.     # Substitute all special time expressions.
  1252.     if ($timeexp ne "()"  and  /$timeexp/i) {
  1253.       $tmp=$1;
  1254.       $tmp=$Date::Manip::Times{$tmp};
  1255.       s/$timeexp/ $tmp /;
  1256.     }
  1257.  
  1258.     $tmp=0;
  1259.     if (/^\s*$whichexp\s*$wkexp$in$mmm\s*$YY?(?:$at$time)?\s*$/i) {
  1260.       # last friday in October 95
  1261.       ($which,$dofw,$m,$y,$h,$mn,$s,$ampm,$z)=($1,$2,$3,$4,$5,$6,$7,$8,$9);
  1262.       # fix $m, $y
  1263.       &Date_ErrorCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk);
  1264.       $dofw=$dofw{lc($dofw)};
  1265.       $which=$which{lc($which)};
  1266.       # Get the first day of the month
  1267.       if ($Date::Manip::DateFormat eq "US") {
  1268.         $date=&ParseDate("$m 1 $y $h:$mn:$s");
  1269.       } else {
  1270.         $date=&ParseDate("1 $m $y $h:$mn:$s");
  1271.       }
  1272.       if ($which==-1) {
  1273.         $date=&DateCalc_DateDelta($date,"+0:1:0:0:0:0",\$err,0);
  1274.         $date=&Date_GetPrev($date,$dofw,0);
  1275.       } else {
  1276.         for ($i=0; $i<$which; $i++) {
  1277.           if ($i==0) {
  1278.             $date=&Date_GetNext($date,$dofw,1);
  1279.           } else {
  1280.             $date=&Date_GetNext($date,$dofw,0);
  1281.           }
  1282.           $date="err", last PARSE  if (! $date);
  1283.         }
  1284.       }
  1285.       last PARSE;
  1286.  
  1287.     } elsif (/^\s*$next\s*$wkexp\s*(?:$at$time)?\s*$/i) {
  1288.       # next friday
  1289.       ($dofw,$h,$mn,$s,$ampm,$z)=($1,$2,$3,$4,$5,$6);
  1290.       $date=&Date_GetNext($Date::Manip::Curr,$dofw,0,$h,$mn,$s);
  1291.       last PARSE;
  1292.  
  1293.     } elsif (/^\s*$prev\s*$wkexp\s*(?:$at$time)?\s*$/i) {
  1294.       # last friday
  1295.       ($dofw,$h,$mn,$s,$ampm,$z)=($1,$2,$3,$4,$5,$6);
  1296.       $date=&Date_GetPrev($Date::Manip::Curr,$dofw,0,$h,$mn,$s);
  1297.       last PARSE;
  1298.  
  1299.     } elsif (/^\s*$next\s*$weeks\s*(?:$at$time)?\s*$/i) {
  1300.       # next week
  1301.       ($h,$mn,$s,$ampm,$z)=($1,$2,$3,$4,$5,$6);
  1302.       $date=&DateCalc_DateDelta($Date::Manip::Curr,"+0:0:7:0:0:0",\$err,0);
  1303.       $date=&Date_SetTime($date,$h,$mn,$s)  if (defined $h);
  1304.       last PARSE;
  1305.     } elsif (/^\s*$prev\s*$weeks\s*(?:$at$time)?\s*$/i) {
  1306.       # last week
  1307.       ($h,$mn,$s,$ampm,$z)=($1,$2,$3,$4,$5,$6);
  1308.       $date=&DateCalc_DateDelta($Date::Manip::Curr,"-0:0:7:0:0:0",\$err,0);
  1309.       $date=&Date_SetTime($date,$h,$mn,$s)  if (defined $h);
  1310.       last PARSE;
  1311.  
  1312.     } elsif (/^\s*$in\s*(\d+)\s*$weeks\s*(?:$at$time)?\s*$/i) {
  1313.       # in 2 weeks
  1314.       ($num,$h,$mn,$s,$ampm,$z)=($1,$2,$3,$4,$5,$6);
  1315.       $date=&DateCalc_DateDelta($Date::Manip::Curr,"+0:0:" .7*$num. ":0:0:0",
  1316.                                 \$err,0);
  1317.       $date=&Date_SetTime($date,$h,$mn,$s)  if (defined $h);
  1318.       last PARSE;
  1319.     } elsif (/^\s*(\d+)\s*$weeks\s*$ago\s*(?:$at$time)?\s*$/i) {
  1320.       # 2 weeks ago
  1321.       ($num,$h,$mn,$s,$ampm,$z)=($1,$2,$3,$4,$5,$6);
  1322.       $date=&DateCalc_DateDelta($Date::Manip::Curr,"-0:0:" .7*$num. ":0:0:0",
  1323.                                \$err,0);
  1324.       $date=&Date_SetTime($date,$h,$mn,$s)  if (defined $h);
  1325.       last PARSE;
  1326.  
  1327.     } elsif (/^\s*$wkexp\s*$in\s*(\d+)\s*$weeks(?:$at$time)?\s*$/i) {
  1328.       # friday in 2 weeks
  1329.       ($dofw,$num,$h,$mn,$s,$ampm,$z)=($1,$2,$3,$4,$5,$6,$7);
  1330.       $tmp="+";
  1331.     } elsif (/^\s*$wkexp\s*(\d+)\s*$weeks\s*$ago(?:$at$time)?\s*$/i) {
  1332.       # friday 2 weeks ago
  1333.       ($dofw,$num,$h,$mn,$s,$ampm,$z)=($1,$2,$3,$4,$5,$6,$7);
  1334.       $tmp="-";
  1335.     } elsif (/^\s*$in\s*(\d+)\s*$weeks$on$wkexp(?:$at$time)?\s*$/i) {
  1336.       # in 2 weeks on friday
  1337.       ($num,$dofw,$h,$mn,$s,$ampm,$z)=($1,$2,$3,$4,$5,$6,$7);
  1338.       $tmp="+"
  1339.     } elsif (/^\s*(\d+)\s*$weeks\s*$ago$on$wkexp(?:$at$time)?\s*$/i) {
  1340.       # 2 weeks ago on friday
  1341.       ($num,$dofw,$h,$mn,$s,$ampm,$z)=($1,$2,$3,$4,$5,$6,$7);
  1342.       $tmp="-";
  1343.     }
  1344.     if ($tmp) {
  1345.       $date=&DateCalc_DateDelta($Date::Manip::Curr,
  1346.                                 $tmp . "0:0:" .7*$num. ":0:0:0",\$err,0);
  1347.       $date=&Date_GetPrev($date,$Date::Manip::FirstDay,1);
  1348.       $date=&Date_GetNext($date,$dofw,1,$h,$mn,$s);
  1349.       last PARSE;
  1350.     }
  1351.  
  1352.     # Change 2nd, second to 2
  1353.     if (/$daysexp/i) {
  1354.       $tmp=lc($1);
  1355.       $tmp=$dayshash{"$tmp"};
  1356.       s/\s*$daysexp\s*/ $tmp /;
  1357.       s/^\s+//;
  1358.       s/\s+$//;
  1359.     }
  1360.  
  1361.     $tmp=0;
  1362.     if (/^\s*$D\s*$wkexp\s*(?:$in?\s*$YY)?(?:\s*$at?\s*$time)?\s*$/i) {
  1363.       # 22nd sunday [in 1996] [at 12:00]
  1364.       ($which,$dofw,$y,$h,$mn,$s,$ampm,$z)=($1,$2,$3,$4,$5,$6,$7,$8);
  1365.       $tmp=1;
  1366.     } elsif (/^\s*$wkexp\s*$weeks\s*$D(?:$in?\s*$YY)?(?:\s*$at?\s*$time)?\s*$/i) {
  1367.       # sunday week 22 [in 1996] [at 12:00]
  1368.       ($dofw,$which,$y,$h,$mn,$s,$ampm,$z)=($1,$2,$3,$4,$5,$6,$7,$8);
  1369.       $tmp=1;
  1370.     } elsif (/^\s*$wkexp\s*$D\s*$weeks(?:$in?\s*$YY)?(?:\s*$at?\s*$time)?\s*$/i) {
  1371.       # sunday 22nd week [in 1996] [at 12:00]
  1372.       ($dofw,$which,$y,$h,$mn,$s,$ampm,$z)=($1,$2,$3,$4,$5,$6,$7,$8);
  1373.       $tmp=1;
  1374.     }
  1375.     if ($tmp) {
  1376.       $y=""  if (! defined $y);
  1377.       $date=&ParseDate("1 1 $y");
  1378.       $date=&Date_GetNext($date,$dofw,1);
  1379.       $date=&DateCalc_DateDelta($date,"+0:0:". ($which-1)*7 . ":0:0:0",\$err,0)
  1380.         if ($which>1);
  1381.       $date=&Date_SetTime($date,$h,$mn,$s)  if (defined $h);
  1382.       last PARSE;
  1383.     }
  1384.  
  1385.     if ($wkexp ne "()" and /$wkexp/i) {
  1386.       $wk=$1;
  1387.       s/$wkexp$com/ /i;
  1388.     }
  1389.     s/\s+/ /g;                  # all whitespace are now a single space
  1390.     s/^\s+//;
  1391.     s/\s+$//;
  1392.  
  1393.     if (/^$YY$DD$DD$at?$Time?$/i) {
  1394.       # DateTime
  1395.       #    Date=YYMMDD
  1396.       ($y,$m,$d,$h,$mn,$s,$ampm,$z)=($1,$2,$3,$4,$5,$6,$7,$8);
  1397.  
  1398.     } elsif (/^(\d{4})$DD$DD$DD$DD$DD$/i) {
  1399.       # YYYYMMDDHHMNSS
  1400.       ($y,$m,$d,$h,$mn,$s)=($1,$2,$3,$4,$5,$6);
  1401.  
  1402.     } elsif (/^$D$sep$D(?:\2$YY)?(?:(?:$com\s+|$com$at|\2)$time)?$/i) {
  1403.       # Date Time
  1404.       # Date%Time
  1405.       #   Date=mm%dd, mm%dd%YY
  1406.       ($m,$d,$y,$h,$mn,$s,$ampm,$z)=($1,$3,$4,$5,$6,$7,$8,$9);
  1407.       ($m,$d)=($d,$m)  if ($type ne "US");
  1408.  
  1409.     } elsif (/^$mmm$sep$D(?:(?:$com\s*|\2)$YY)?$com(?:(?:\s+|$at)$time)?$/i) {
  1410.       # Date Time
  1411.       #   Date=mmm%dd mmm%dd%YY
  1412.       ($m,$d,$y,$h,$mn,$s,$ampm,$z)=($1,$3,$4,$5,$6,$7,$8,$9);
  1413.  
  1414.     } elsif (/^$mmm$sep$D(?:\2$YY)?(?:\2$time)?$/i) {
  1415.       # Date%Time
  1416.       #   Date=mmm%dd mmm%dd%YY
  1417.       ($m,$d,$y,$h,$mn,$s,$ampm,$z)=($1,$3,$4,$5,$6,$7,$8,$9);
  1418.  
  1419.     } elsif (/^$D$sep*$mmm(?:(?:$com\s*|\2)$YY)?$com(?:(?:\s+|$at)$time)?$/i) {
  1420.       # Date Time
  1421.       #   Date=dd%mmm, dd%mmm%YY
  1422.       ($d,$m,$y,$h,$mn,$s,$ampm,$z)=($1,$3,$4,$5,$6,$7,$8,$9);
  1423.  
  1424.     } elsif (/^$D$sep*$mmm(?:\2$YY)?(?:\2$time)?$/i) {
  1425.       # Date%Time
  1426.       #   Date=dd%mmm, dd%mmm%YY
  1427.       ($d,$m,$y,$h,$mn,$s,$ampm,$z)=($1,$3,$4,$5,$6,$7,$8,$9);
  1428.  
  1429.     } elsif (/^$D\s*$mmm(?:$com\s*$YY)?$com(?:(?:\s+|$at)$time)?$/i) {
  1430.       # Date Time
  1431.       #   Date=ddmmm, ddmmmYY, ddmmm YY, dd mmmYY
  1432.       ($d,$m,$y,$h,$mn,$s,$ampm,$z)=($1,$2,$3,$4,$5,$6,$7,$8);
  1433.  
  1434.     } elsif (/^$mmm$D(?:$com\s*$YY)?$com(?:(?:\s+|$at)$time)?$/i) {
  1435.       # Date Time
  1436.       #   Date=mmmdd, mmmdd YY
  1437.       ($m,$d,$y,$h,$mn,$s,$ampm,$z)=($1,$2,$3,$4,$5,$6,$7,$8);
  1438.  
  1439.     } elsif (/^$mmm\s*$DD(?:$com$YY)?$com(?:(?:\s+|$at)$time)?$/i) {
  1440.       # Date Time
  1441.       #   Date=mmm DDYY, mmmDDYY
  1442.       ($m,$d,$y,$h,$mn,$s,$ampm,$z)=($1,$2,$3,$4,$5,$6,$7,$8);
  1443.  
  1444.  
  1445.     } elsif (/^$time$/) {
  1446.       ($h,$mn,$s,$ampm,$z)=($1,$2,$3,$4,$5);
  1447.  
  1448.     } elsif (/^$time\s*$D$sep$D(?:\7$YY)?$/i) {
  1449.       # TimeDate
  1450.       # Time Date
  1451.       #   Date=mm%dd, mm%dd%YY
  1452.       ($h,$mn,$s,$ampm,$z,$m,$d,$y)=($1,$2,$3,$4,$5,$6,$8,$9);
  1453.       ($m,$d)=($d,$m)  if ($type ne "US");
  1454.  
  1455.     } elsif (/^$time$sep$D\6$D(?:\6$YY)?$/i) {
  1456.       # Time%Date
  1457.       #   Date=mm%dd mm%dd%YY
  1458.       ($h,$mn,$s,$ampm,$z,$m,$d,$y)=($1,$2,$3,$4,$5,$7,$8,$9);
  1459.       ($m,$d)=($d,$m)  if ($type ne "US");
  1460.  
  1461.     } elsif (/^$time\s*$mmm$sep$D(?:(?:$com\s*|\7)$YY)?$/i) {
  1462.       # TimeDate
  1463.       # Time Date
  1464.       #   Date=mmm%dd mmm%dd%YY
  1465.       ($h,$mn,$s,$ampm,$z,$m,$d,$y)=($1,$2,$3,$4,$5,$6,$8,$9);
  1466.  
  1467.     } elsif (/^$time$sep$mmm\6$D(?:\6$YY)?$/i) {
  1468.       # Time%Date
  1469.       #   Date=mmm%dd mmm%dd%YY
  1470.       ($h,$mn,$s,$ampm,$z,$m,$d,$y)=($1,$2,$3,$4,$5,$7,$8,$9);
  1471.  
  1472.     } elsif (/^$time\s*$D$sep$mmm(?:(?:$com\s*|\7)$YY)?$/i) {
  1473.       # TimeDate
  1474.       # Time Date
  1475.       #   Date=dd%mmm dd%mmm%YY
  1476.       ($h,$mn,$s,$ampm,$z,$d,$m,$y)=($1,$2,$3,$4,$5,$6,$8,$9);
  1477.  
  1478.     } elsif (/^$time$sep$D\6$mmm(?:\6$YY)?$/i) {
  1479.       # Time%Date
  1480.       #   Date=dd%mmm dd%mmm%YY
  1481.       ($h,$mn,$s,$ampm,$z,$d,$m,$y)=($1,$2,$3,$4,$5,$7,$8,$9);
  1482.  
  1483.     } elsif (/^$time\s*$mmm\s*$D(?:$com\s+$YY)?$/i) {
  1484.       # TimeDate
  1485.       # Time Date
  1486.       #   Date=mmmdd, mmmdd YY
  1487.       ($h,$mn,$s,$ampm,$z,$m,$d,$y)=($1,$2,$3,$4,$5,$6,$7,$8);
  1488.  
  1489.     } elsif (/^$time\s*$mmm\s*$DD$com$YY$/i) {
  1490.       # TimeDate
  1491.       # Time Date
  1492.       #   Date=mmmDDYY
  1493.       ($h,$mn,$s,$ampm,$z,$m,$d,$y)=($1,$2,$3,$4,$5,$6,$7,$8);
  1494.  
  1495.     } elsif (/^$time\s*$D\s*$mmm(?:$com\s*$YY)?$/i) {
  1496.       # TimeDate
  1497.       # Time Date
  1498.       #   Date=ddmmm, ddmmm YY, ddmmmYY
  1499.       ($h,$mn,$s,$ampm,$z,$d,$m,$y)=($1,$2,$3,$4,$5,$6,$7,$8);
  1500.  
  1501.     } elsif (/^$now$at?$time?$/i) {
  1502.       # now, today
  1503.       ($h,$mn,$s,$ampm,$z)=($2,$3,$4,$5,$6);
  1504.       $date=$Date::Manip::Curr;
  1505.       if (defined $h) {
  1506.         if (&Date_ErrorCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk)) {
  1507.           pop(@a);
  1508.           next PARSE;
  1509.         }
  1510.         $date=&Date_SetTime($date,$h,$mn,$s);
  1511.       }
  1512.       $date=&Date_ConvTZ($date,$z);
  1513.       return $date;
  1514.  
  1515.     } elsif (/^$mmm\s*$D\s+$time\s*$YY$/i) {
  1516.       # mmmdd time YY   (ctime format)
  1517.       ($m,$d,$h,$mn,$s,$ampm,$z,$y)=($1,$2,$3,$4,$5,$6,$7,$8);
  1518.  
  1519.     } elsif (/^$offset$at?$time?$/i) {
  1520.       # yesterday, tomorrow
  1521.       ($offset,$h,$mn,$s,$ampm,$z)=($1,$2,$3,$4,$5,$6);
  1522.       $offset=$Date::Manip::Offset{lc($offset)};
  1523.       $date=&DateCalc_DateDelta($Date::Manip::Curr,$offset,\$err,0);
  1524.       if (defined $h) {
  1525.         if (&Date_ErrorCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk)) {
  1526.           pop(@a);
  1527.           next PARSE;
  1528.         }
  1529.         $date=&Date_SetTime($date,$h,$mn,$s);
  1530.         $date=&Date_ConvTZ($date,$z);
  1531.       }
  1532.       return $date;
  1533.  
  1534.     } else {
  1535.       pop(@a);
  1536.       next PARSE;
  1537.     }
  1538.  
  1539.     if (&Date_ErrorCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk)) {
  1540.       pop(@a);
  1541.     } else {
  1542.       last PARSE;
  1543.     }
  1544.   }
  1545.  
  1546.   if ($date ne "err") {
  1547.     if (@a) {
  1548.       splice(@args,0,$#a+1);
  1549.       @$args=@args  if (defined $ref  and  $ref eq "ARRAY");
  1550.       $date=&FormDate($y,$m,$d,$h,$mn,$s)  if (! $date);
  1551.       $date=&Date_ConvTZ($date,$z);
  1552.       return $date;
  1553.     }
  1554.   }
  1555.   return "";
  1556. }
  1557.  
  1558. ########################################################################
  1559. # OTHER SUBROUTINES
  1560. ########################################################################
  1561.  
  1562. sub Date_DayOfWeek {
  1563.   my($m,$d,$y)=@_;
  1564.   confess "ERROR: Date_DayOfWeek requires a 4 digit year.\n"
  1565.     if ($y!~/^\d{4}$/);
  1566.   my($dayofweek,$dec31)=();
  1567.  
  1568.   $dec31=2;                     # Dec 31, 0999 was Tuesday
  1569.   $dayofweek=(&Date_DaysSince999($m,$d,$y)+$dec31) % 7;
  1570.   return $dayofweek;
  1571. }
  1572.  
  1573. sub Date_SecsSince1970 {
  1574.   my($m,$d,$y,$h,$mn,$s)=@_;
  1575.   confess "ERROR: Date_SecsSince1970 requires a 4 digit year.\n"
  1576.     if ($y!~/^\d{4}$/);
  1577.   my($sec_now,$sec_70)=();
  1578.   $sec_now=(&Date_DaysSince999($m,$d,$y)-1)*24*3600 + $h*3600 + $mn*60 + $s;
  1579. # $sec_70 =(&Date_DaysSince999(1,1,1970)-1)*24*3600;
  1580.   $sec_70 =30610224000;
  1581.   return ($sec_now-$sec_70);
  1582. }
  1583.  
  1584. sub Date_SecsSince1970GMT {
  1585.   my($m,$d,$y,$h,$mn,$s)=@_;
  1586.   confess "ERROR: Date_SecsSince1970GMT requires a 4 digit year.\n"
  1587.     if ($y!~/^\d{4}$/);
  1588.  
  1589.   my($sec)=&Date_SecsSince1970($m,$d,$y,$h,$mn,$s);
  1590.   return $sec   if ($Date::Manip::ConvTZ eq "IGNORE");
  1591.  
  1592.   my($tz)=$Date::Manip::ConvTZ;
  1593.   $tz=$Date::Manip::TZ  if (! $tz);
  1594.   $tz=$Date::Manip::Zone{lc($tz)}  if ($tz !~ /^[+-]\d{4}$/);
  1595.  
  1596.   my($tzs)=1;
  1597.   $tzs=-1 if ($tz<0);
  1598.   $tz=~/.(..)(..)/;
  1599.   my($tzh,$tzm)=($1,$2);
  1600.   $sec - $tzs*($tzh*3600+$tzm*60);
  1601. }
  1602.  
  1603. sub Date_DaysSince999 {
  1604.   my($m,$d,$y)=@_;
  1605.   confess "ERROR: Date_DaysSince999 requires a 4 digit year.\n"
  1606.     if ($y!~/^\d{4}$/);
  1607.   my($Ny,$N4,$N100,$N400,$dayofyear,$days)=();
  1608.   my($cc,$yy)=();
  1609.  
  1610.   $y=~ /(\d{2})(\d{2})/;
  1611.   ($cc,$yy)=($1,$2);
  1612.  
  1613.   # Number of full years since Dec 31, 0999
  1614.   $Ny=$y-1000;
  1615.  
  1616.   # Number of full 4th years (incl. 1000) since Dec 31, 0999
  1617.   $N4=int(($Ny-1)/4)+1;
  1618.   $N4=0         if ($y==1000);
  1619.  
  1620.   # Number of full 100th years (incl. 1000)
  1621.   $N100=$cc-9;
  1622.   $N100--       if ($yy==0);
  1623.  
  1624.   # Number of full 400th years
  1625.   $N400=int(($N100+1)/4);
  1626.  
  1627.   $dayofyear=&Date_DayOfYear($m,$d,$y);
  1628.   $days= $Ny*365 + $N4 - $N100 + $N400 + $dayofyear;
  1629.  
  1630.   return $days;
  1631. }
  1632.  
  1633. sub Date_DayOfYear {
  1634.   my($m,$d,$y)=@_;
  1635.   confess "ERROR: Date_DayOfYear requires a 4 digit year.\n"
  1636.     if ($y!~/^\d{4}$/);
  1637.   my(@daysinmonth)=(0,31,28,31,30,31,30,31,31,30,31,30,31);
  1638.   my($daynum,$i)=();
  1639.   $daysinmonth[2]=29  if (&Date_LeapYear($y));
  1640.   $daynum=0;
  1641.   for ($i=1; $i<$m; $i++) {
  1642.     $daynum += $daysinmonth[$i];
  1643.   }
  1644.   $daynum += $d;
  1645.   $daynum="0$daynum"   if ($daynum<10);
  1646.   $daynum="0$daynum"   if ($daynum<100);
  1647.   return $daynum;
  1648. }
  1649.  
  1650. sub Date_DaysInYear {
  1651.   my($y)=@_;
  1652.   confess "ERROR: Date_DaysInYear requires a 4 digit year.\n"
  1653.     if ($y!~/^\d{4}$/);
  1654.   return 366  if (&Date_LeapYear($y));
  1655.   return 365;
  1656. }
  1657.  
  1658. sub Date_WeekOfYear {
  1659.   my($m,$d,$y,$f)=@_;
  1660.   confess "ERROR: Date_WeekOfWeek requires a 4 digit year.\n"
  1661.     if ($y!~/^\d{4}$/);
  1662.   my($jan1)=&Date_DayOfWeek(1,1,$y); # Jan 1 is what day of week
  1663.   my($dofy)=&Date_DayOfYear($m,$d,$y);
  1664.  
  1665.   # Renumber the days (still 0 to 6) so that the first day of
  1666.   # the week is always 0.
  1667.   $jan1=$jan1-$f;
  1668.   $jan1+=7 if ($jan1<0);
  1669.  
  1670.   # Add days to the beginning of the year so that the first day
  1671.   # of this "extended" year falls on the first day of the week and
  1672.   # is numbered day 0 (rather than day 1).
  1673.   $dofy+=$jan1-1;
  1674.  
  1675.   return (int($dofy/7)+1);
  1676. }
  1677.  
  1678. sub Date_LeapYear {
  1679.   my($y)=@_;
  1680.   confess "ERROR: Date_LeapYear requires a 4 digit year.\n"
  1681.     if ($y!~/^\d{4}$/);
  1682.   return 0 unless $y % 4 == 0;
  1683.   return 1 unless $y % 100 == 0;
  1684.   return 0 unless $y % 400 == 0;
  1685.   return 1;
  1686. }
  1687.  
  1688. sub Date_DaySuffix {
  1689.   my($d)=@_;
  1690.   return $Date::Manip::Day[$d-1];
  1691. }
  1692.  
  1693. sub Date_ConvTZ {
  1694.   my($date,$from,$to)=@_;
  1695.   my($gmt)=();
  1696.  
  1697.   if (! defined $to  or  ! $to) {
  1698.     # 2 argument form, convert $from to $Date::Manip::ConvTZ
  1699.     return $date
  1700.       if (! defined $from  or  ! $from  or  $Date::Manip::ConvTZ eq "IGNORE");
  1701.  
  1702.     $to=$Date::Manip::ConvTZ  if (! defined $to  or  ! $to);
  1703.     $to=$Date::Manip::TZ  if (! $to);
  1704.   }
  1705.   if (! defined $from  or  ! $from) {
  1706.     $from=$Date::Manip::ConvTZ;
  1707.     $from=$Date::Manip::TZ  if (! $from);
  1708.   }
  1709.  
  1710.   $to=$Date::Manip::Zone{lc($to)}
  1711.     if (exists $Date::Manip::Zone{lc($to)});
  1712.   $from=$Date::Manip::Zone{lc($from)} 
  1713.     if (exists $Date::Manip::Zone{lc($from)});
  1714.   $gmt=$Date::Manip::Zone{gmt};
  1715.  
  1716.   return $date  if ($from !~ /^[+-]\d{4}$/ or $to !~ /^[+-]\d{4}$/);
  1717.   return $date  if ($from eq $to);
  1718.  
  1719.   my($h,$m,$err)=();
  1720.   # Convert $date from $from to GMT
  1721.   if ($from ne $gmt) {
  1722.     $from=~/([+-]\d{2})(\d{2})/;
  1723.     ($h,$m)=($1,$2);
  1724.     $h=-$h;
  1725.     $date=&DateCalc_DateDelta($date,"+0:0:0:$h:$m:00",\$err,0);
  1726.   }
  1727.   # Convert $date from GMT to $to
  1728.   if ($to ne $gmt) {
  1729.     $to=~/([+-]\d{2})(\d{2})/;
  1730.     ($h,$m)=($1,$2);
  1731.     $date=&DateCalc_DateDelta($date,"+0:0:0:$h:$m:00",\$err,0);
  1732.   }
  1733.   return $date;
  1734. }
  1735.  
  1736. sub Date_TimeZone {
  1737.   my($null,$tz,@tz,$std,$dst,$time,$isdst,$tmp)=();
  1738.  
  1739.   # Get timezones from all of the relevant places
  1740.  
  1741.   push(@tz,$Date::Manip::TZ)  if (defined $Date::Manip::TZ);  # TZ config var
  1742.   push(@tz,$ENV{"TZ"})        if (exists $ENV{"TZ"});         # TZ environ var
  1743.   # Microsoft operating systems don't have a date command built in.  Try
  1744.   # to trap all the various ways of knowing we are on one of these systems:
  1745.   unless ((defined $^O and
  1746.            $^O =~ /MSWin32/i ||
  1747.            $^O =~ /Windows_95/i ||
  1748.            $^O =~ /Windows_NT/i) or
  1749.           (defined $ENV{OS} and
  1750.            $ENV{OS} =~ /MSWin32/i ||
  1751.            $ENV{OS} =~ /Windows_95/i ||
  1752.            $ENV{OS} =~ /Windows_NT/i)) {
  1753.     $tz = `date`;
  1754.     chomp($tz);
  1755.     $tz=(split(/\s+/,$tz))[4];
  1756.     push(@tz,$tz);
  1757.   }
  1758.   push(@tz,$main::TZ)         if (defined $main::TZ);         # $main::TZ
  1759.   if (-s "/etc/TIMEZONE") {                                   # /etc/TIMEZONE
  1760.     ($null,$tz) = split (/\=/,`grep ^TZ /etc/TIMEZONE`);
  1761.     chomp($tz);
  1762.     $tz=~ s/\s+//g;
  1763.     push(@tz,$tz);
  1764.   }
  1765.  
  1766.   # Now parse each one to find the first valid one.
  1767.   foreach $tz (@tz) {
  1768.     return uc($tz)
  1769.       if (defined $Date::Manip::Zone{lc($tz)} or $tz=~/^[+-]\d{4}/);
  1770.  
  1771.     # Handle US/Eastern format
  1772.     if ($tz =~ /^$Date::Manip::CurrZoneExp$/i) {
  1773.       $tmp=lc $1;
  1774.       $tz=$Date::Manip::CurrZone{$tmp};
  1775.     }
  1776.  
  1777.     # Handle STD#DST# format
  1778.     if ($tz =~ /^([a-z]+)\d([a-z]+)\d?$/i) {
  1779.       ($std,$dst)=($1,$2);
  1780.       next  if (! defined $Date::Manip::Zone{lc($std)} or
  1781.                 ! defined $Date::Manip::Zone{lc($dst)});
  1782.       $time = time();
  1783.       ($null,$null,$null,$null,$null,$null,$null,$null,$isdst) =
  1784.         localtime($time);
  1785.       return uc($dst)  if ($isdst);
  1786.       return uc($std);
  1787.     }
  1788.   }
  1789.  
  1790.   confess "ERROR: Date::Manip unable to determine TimeZone.\n";
  1791. }
  1792.  
  1793. sub Date_Init {
  1794.   my($language,$format,$tz,$convtz,@args)=@_;
  1795.   $Date::Manip::InitDone=1;
  1796.   local($_)=();
  1797.   my($internal,$firstday)=();
  1798.   my($var,$val,$file)=();
  1799.  
  1800.   #### Backwards compatibility junk
  1801.   if (defined $language  and  $language) {
  1802.     if ($language=~ /=/) {
  1803.       push(@args,$language);
  1804.     } else {
  1805.       push(@args,"Language=$language");
  1806.     }
  1807.   }
  1808.   if (defined $format  and  $format) {
  1809.     if ($format=~ /=/) {
  1810.       push(@args,$format);
  1811.     } else {
  1812.       push(@args,"DateFormat=$format");
  1813.     }
  1814.   }
  1815.   if (defined $tz  and  $tz) {
  1816.     if ($tz=~ /=/) {
  1817.       push(@args,$tz);
  1818.     } else {
  1819.       push(@args,"TZ=$tz");
  1820.     }
  1821.   }
  1822.   if (defined $convtz  and  $convtz) {
  1823.     if ($convtz=~ /=/) {
  1824.       push(@args,$convtz);
  1825.     } else {
  1826.       push(@args,"ConvTZ=$convtz");
  1827.     }
  1828.   }
  1829.   #### End backwards compatibility junk
  1830.  
  1831.   $Date::Manip::EraseHolidays=0;
  1832.   foreach (@args) {
  1833.     s/\s*$//;
  1834.     s/^\s*//;
  1835.     /^(\S+) \s* = \s* (.+)$/x;
  1836.     ($var,$val)=($1,$2);
  1837.     $Date::Manip::InitFilesRead--,
  1838.     $Date::Manip::PersonalCnf=$val,      next  if ($var eq "PersonalCnf");
  1839.     $Date::Manip::PersonalCnfPath=$val,  next  if ($var eq "PersonalCnfPath");
  1840.   }
  1841.  
  1842.   $Date::Manip::InitFilesRead=1  if ($Date::Manip::IgnoreGlobalCnf);
  1843.   if ($Date::Manip::InitFilesRead<1) {
  1844.     $Date::Manip::InitFilesRead=1;
  1845.     # Read Global Init file
  1846.     if ($Date::Manip::GlobalCnf) {
  1847.       $file=&ExpandTilde($Date::Manip::GlobalCnf);
  1848.     }
  1849.     &ReadInitFile($file)  if (defined $file  and  $file  and  -r $file  and
  1850.                               -s $file  and  -f $file);
  1851.   }
  1852.   if ($Date::Manip::InitFilesRead<2) {
  1853.     $Date::Manip::InitFilesRead=2;
  1854.     # Read Personal Init file
  1855.     if ($Date::Manip::PersonalCnf  and  $Date::Manip::PersonalCnfPath) {
  1856.       $file=&SearchPath($Date::Manip::PersonalCnf,
  1857.                         $Date::Manip::PersonalCnfPath,"r");
  1858.     }
  1859.     &ReadInitFile($file)  if (defined $file  and  $file  and  -r $file  and
  1860.                               -s $file  and  -f $file);
  1861.   }
  1862.  
  1863.   foreach (@args) {
  1864.     s/\s*$//;
  1865.     s/^\s*//;
  1866.     /^(\S+) \s* = \s* (.+)$/x;
  1867.     ($var,$val)=($1,$2);
  1868.  
  1869.     &Date_SetConfigVariable($var,$val);
  1870.   }
  1871.  
  1872.   confess "ERROR: Unknown FirstDay in Date::Manip.\n"
  1873.     if (! &IsInt($Date::Manip::FirstDay,0,6));
  1874.   confess "ERROR: Unknown WorkWeekBeg in Date::Manip.\n"
  1875.     if (! &IsInt($Date::Manip::WorkWeekBeg,0,6));
  1876.   confess "ERROR: Unknown WorkWeekEnd in Date::Manip.\n"
  1877.     if (! &IsInt($Date::Manip::WorkWeekEnd,0,6));
  1878.   confess "ERROR: Invalid WorkWeek in Date::Manip.\n"
  1879.     if ($Date::Manip::WorkWeekEnd <= $Date::Manip::WorkWeekBeg);
  1880.  
  1881.   my($i,$j,@tmp,@tmp2,@tmp3,$a,$b,$now,$offset,$last,$in,$at,$on,$tmp,%tmp,
  1882.      $mon,$month,@mon,@month,
  1883.      $w,$wk,$week,@w,@wk,@week,$weeks,
  1884.      $days,@days,$am,$pm,
  1885.      $zones,$zonesrfc,@zones,$times,$future,$past,$sephm,$sepms,$sepss,
  1886.      $years,$months,$days,$hours,$minutes,$seconds,$replace,$next,$prev,
  1887.      $approx,$exact,$business)=();
  1888.   my($lang)=$Date::Manip::Language;
  1889.  
  1890.   if (! $Date::Manip::Init) {
  1891.     $Date::Manip::Init=1;
  1892.  
  1893.     # Set the following variables based on the language.  They should all
  1894.     # be capitalized correctly, and any spaces appearing in the string
  1895.     # should be replaced with an underscore (_) (they will be correctly
  1896.     # parsed as spaces).
  1897.  
  1898.     #  $month   : space separated string containing months spelled out
  1899.     #  $mon     : space separated string containing months abbreviated
  1900.     #  $week    : space separated string containing weekdays spelled out
  1901.     #  $wk      : space separated string containing weekdays abbreviated
  1902.     #  $w       : space separated string containing weekdays very abbreviated
  1903.     #  $am,$pm  : different ways of expressing AM (PM), the first one in each
  1904.     #             list is the one that will be used when printing out an AM
  1905.     #             or PM string
  1906.     #  @days    : different ways that numbers can appear as days (first, 1st,
  1907.     #             etc.  Each element of @days has a space separated string
  1908.     #             with up to 31 values).  The first one should contain the
  1909.     #             nubers in the 1st, 2nd, etc. format.
  1910.     #  $last    : strings containing synonyms for last
  1911.     #  $years   : string containing abbreviations for the word year
  1912.     #  $months  : string containing abbreviations for the word month
  1913.     #  $weeks   : string containing abbreviations for the work week
  1914.     #  $days    : string containing abbreviations for the word day
  1915.     #  $hours   : string containing abbreviations for the word hour
  1916.     #  $minutes : string containing abbreviations for the word minute
  1917.     #  $seconds : string containing abbreviations for the word second
  1918.     #  $now     : string containing words referring to now
  1919.     #  $in      : strings fitting "1st sunday in June"
  1920.     #  $at      : strings fitting "at 12:00"
  1921.     #  $on      : strings fitting "on June 1st"
  1922.     #  $future  : strings to indicate the future
  1923.     #  $past    : strings to indicate the past
  1924.     #  $next    : strings to indicate the "next" of something
  1925.     #  $prev    : strings to indicate the "previous" of something
  1926.     #  $times   : different strings which stand for specific times and
  1927.     #             the time they translate to (ex. "noon 12:00:00")
  1928.     #  $zones   : a space separated string containing additional timezone
  1929.     #             strings (beyond the RFC 822 zones) along with their
  1930.     #             translatrion.  So, the string "EST -0500 EDT -0400"
  1931.     #             contain two time zones, EST and EDT, which have offsets
  1932.     #             of -0500 and -0400 respectively from Universal Time.
  1933.     #  $sephm   : the separator used between the hours and minutes of a time
  1934.     #  $sepms   : the separator used between the minutes and seconds of a time
  1935.     #  $sepss   : the separator used between seconds and fractional seconds
  1936.     #             NOTE:  all three of the separators can be any format suitable
  1937.     #             for a regular expression PROVIDED it does not create a
  1938.     #             back-reference.  For example, in french, the hour/minute
  1939.     #             separator might be a colon or the letter h.  This would be
  1940.     #             defined as (?::|h) or [:h] but NOT as (:|h) since the latter
  1941.     #             produces a back-reference.  Also, the dot "." should be
  1942.     #             defined as '\.' since it is in a regular expression.
  1943.     #  $approx  : strings which force approximate mode in DateCalc.
  1944.     #  $exact   : exact mode
  1945.     #  $business: business mode
  1946.     #
  1947.     # One important variable is $replace.  In English (and probably
  1948.     # other languages), one of the abbreviations for the word month that
  1949.     # would be nice is "m".  The problem is that "m" matches the "m" in
  1950.     # "minute" which causes the string to be improperly matched in some
  1951.     # cases.  Hence, the list of abbreviations for month is given as:
  1952.     #   "mon month months"
  1953.     # In order to allow you to enter "m", replacements can be done.
  1954.     # $replace is a list of pairs of words which are matched and replaced
  1955.     # AS ENTIRE WORDS".  Having $replace equal to "m month" means that
  1956.     # the entire word "m" will be replaced with "month".  This allows the
  1957.     # desired abbreviation to be used.  Make sure that $replace contains
  1958.     # an even number of words (i.e. all must be pairs).
  1959.     #
  1960.     # One other variable to set is $offset.  This contains a space separated
  1961.     # set of dates which are defined as offsets from the current time.
  1962.     #
  1963.     # If a string contains spaces, replace the space(s) with underscores.
  1964.  
  1965.     if ($lang eq "English") {
  1966.       $month="January February March April May June ".
  1967.         "July August September October November December";
  1968.       $mon="Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec";
  1969.  
  1970.       $week="Sunday Monday Tuesday Wednesday Thursday Friday Saturday";
  1971.       $wk="Sun Mon Tue Wed Thu Fri Sat";
  1972.       $w="S M T W Th F Sa";
  1973.  
  1974.       $days[0]="1st 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th ".
  1975.         "15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th 25th 26th 27th ".
  1976.         "28th 29th 30th 31st";
  1977.       $days[1]="first second third fourth fifth sixth seventh eighth ninth ".
  1978.         "tenth eleventh twelfth thirteenth fourteenth fifteenth sixteenth ".
  1979.         "seventeenth eighteenth nineteenth twentieth twenty-first ".
  1980.         "twenty-second twenty-third twenty-fourth twenty-fifth twenty-sixth ".
  1981.         "twenty-seventh twenty-eighth twenty-ninth thirtieth thirty-first";
  1982.  
  1983.       $last="last";
  1984.       $in="in of";
  1985.       $at="at";
  1986.       $on="on";
  1987.       $future="in";
  1988.       $past="ago";
  1989.       $next="next";
  1990.       $prev="previous last";
  1991.  
  1992.       $am="AM";
  1993.       $pm="PM";
  1994.  
  1995.       $years  ="y yr year yrs years";
  1996.       $months ="mon month months";
  1997.       $weeks  ="w wk wks week weeks";
  1998.       $days   ="d day days";
  1999.       $hours  ="h hr hrs hour hours";
  2000.       $minutes="mn min minute minutes";
  2001.       $seconds="s sec second seconds";
  2002.       $replace="m month";
  2003.  
  2004.       $now="today now";
  2005.       $offset="yesterday -0:0:1:0:0:0 tomorrow +0:0:1:0:0:0";
  2006.       $times="noon 12:00:00 midnight 00:00:00";
  2007.  
  2008.       $sephm=':';
  2009.       $sepms=':';
  2010.       $sepss='[.:]';
  2011.       $zones="";
  2012.  
  2013.       $exact="exactly";
  2014.       $approx="approximately";
  2015.       $business="business";
  2016.  
  2017.     } elsif ($lang eq "Swedish") {
  2018.       $month="Januari Februari Mars April Maj Juni ".
  2019.         "Juli Augusti September Oktober November December";
  2020.       $mon="Jan Feb Mar Apr Maj Jun Jul Aug Sep Okt Nov Dec";
  2021.  
  2022.       $week="Sundag Mondag Tisdag Onsdag Torsdag Fredag Lurdag";
  2023.       $wk="Sun Mon Tis Ons Tor Fre Lur";
  2024.       $w="S M Ti O To F Lu";
  2025.  
  2026.       $days[0]="1:a 2:a 3:e 4:e 5:e 6:e 7:e 8:e 9:e 10:e 11:e 12:e 13:e 14:e ".
  2027.         "15:e 16:e 17:e 18:e 19:e 20:e 21:a 22:a 23:e 24:e 25:e 26:e 27:e ".
  2028.         "28:e 29:e 30:e 31:a";
  2029.       $days[1]="fursta andra tredje fj=E4rde femte sj=E4tte sjunde ottonde ".
  2030.         "nionde tionde elte tolfte trettonde fjortonde femtonde sextonde ".
  2031.         "sjuttonde artonde nittonde tjugonde tjugofursta ".
  2032.         "tjugoandra tjugotredje tjugofj=E4rde tjugofemte tjugosj=E4tte ".
  2033.         "tjugosjunde tjugoottonde tjugonionde trettionde trettiofursta";
  2034.  
  2035.       $last="furra senaste";
  2036.       $in="om";
  2037.       $on="";
  2038.       $at="kl kl. klockan";
  2039.       $future="";
  2040.       $past="";
  2041.       $next="";
  2042.       $prev="";
  2043.  
  2044.       $am="FM";
  2045.       $pm="EM";
  2046.  
  2047.       $years  ="o or";
  2048.       $months ="mon monad monader";
  2049.       $weeks  ="";
  2050.       $days   ="d dag dagar";
  2051.       $hours  ="t tim timme timmar";
  2052.       $minutes="mn min minut minuter";
  2053.       $seconds="s sek sekund sekunder";
  2054.       $replace="m monad";
  2055.  
  2056.       $now="idag nu";
  2057.       $offset="igor -0:0:1:0:0:0 imorgon +0:0:1:0:0:0";
  2058.       $times="";
  2059.       $sephm='[:.]';
  2060.       $sepms=':';
  2061.       $sepss='[.:]';
  2062.       $zones="";
  2063.  
  2064.       $exact="";
  2065.       $approx="";
  2066.       $business="";
  2067.  
  2068.     } elsif ($lang eq "French") {
  2069.       $month="janvier fevrier mars avril mai juin juillet aout ".
  2070.         "septembre octobre novembre decembre";
  2071.       # NOTE: I am not sure what the abbreviation for juin and juillet are.
  2072.       $mon="jan fev mar avr mai juin juil aou sep oct nov dec";
  2073.  
  2074.       $week="dimanche lundi mardi mercredi jeudi vendredi samedi";
  2075.       $wk="dim lun mar mer jeu ven sam";
  2076.       $w="d l ma me j v s";
  2077.  
  2078.       @tmp=map { ($_."e"); } (1...31);
  2079.       $tmp[0] = "1er";
  2080.       $days[0]=join " ",@tmp;   # 1er 2e 3e ...
  2081.       $days[1]="1re";           # 1re
  2082.       $days[2]="premier deux trois quatre cinq six sept huit neuf dix onze ".
  2083.         "douze treize quatorze quinze size dix-sept dix-huit dix-neuf vingt ".
  2084.         "vingt_et_un vingt-deux vingt-trois vingt-quatre vingt-cinq ".
  2085.         "vingt-six vingt-sept vingt-huit vingt-neuf trente trente_et_un";
  2086.  
  2087.       $last="dernier";
  2088.       $in="en de";
  2089.       $on="";
  2090.       $at="a";
  2091.       $future="en";
  2092.       $past="il_y_a";
  2093.       $next="";
  2094.       $prev="";
  2095.  
  2096.       $am="du_matin";
  2097.       $pm="du_soir";
  2098.  
  2099.       $years  ="an annee ans annees";
  2100.       $months ="mois";
  2101.       $weeks  ="";
  2102.       $days   ="j jour jours";
  2103.       $hours  ="h heure heures";
  2104.       $minutes="mn min minute minutes";
  2105.       $seconds="s sec seconde secondes";
  2106.       $replace="m mois";
  2107.  
  2108.       $now="aujourd'hui maintenant";
  2109.       $offset="hier -0:0:1:0:0:0 demain +0:0:1:0:0:0";
  2110.       $times="";
  2111.       $sephm='[:h]';
  2112.       $sepms=':';
  2113.       $sepss='[.:,]';
  2114.  
  2115.       $zones="";
  2116.  
  2117.       $exact="";
  2118.       $approx="";
  2119.       $business="";
  2120.  
  2121.       # } elsif ($lang eq "Spanish") {
  2122.       #   $month="enero febrero marzo abril mayo junio julio agosto ".
  2123.       #     "septiembre octubre noviembre diciembre";
  2124.       #   $mon="ene feb mar abr may jun jul ago sep oct nov dic";
  2125.  
  2126.       #   $week="domingo lunes martes miercoles jueves viernes sabado";
  2127.       #   $wk="dom lun mar mier jue vie sab";
  2128.       #   $w="d l ma mi j v s";
  2129.  
  2130.       # } elsif ($lang eq "Italian") {
  2131.       # } elsif ($lang eq "Portugese") {
  2132.       # } elsif ($lang eq "German") {
  2133.       # } elsif ($lang eq "Russian") {
  2134.  
  2135.     } else {
  2136.       confess "ERROR: Unknown language in Date::Manip.\n";
  2137.     }
  2138.  
  2139.     # Date::Manip:: variables for months
  2140.     #   $MonExp   : "(jan|january|feb|february ... )"
  2141.     #   @Mon      : ("Jan","Feb",...)
  2142.     #   @Month    : ("January","February", ...)
  2143.     #   %Month    : ("january",1,"jan",1, ...)
  2144.     $Date::Manip::MonExp=&Date_Regexp("$mon $month","lc,under,sort,back");
  2145.     ($tmp,@Date::Manip::Mon)  =&Date_Regexp($mon,"under",1);
  2146.     ($tmp,@Date::Manip::Month)=&Date_Regexp($month,"under",1);
  2147.     ($tmp,@tmp2)  =&Date_Regexp($mon,"","val1");
  2148.     ($tmp,@tmp3)=&Date_Regexp($month,"","val1");
  2149.     @tmp=reverse(@tmp2,@tmp3);
  2150.     ($tmp,%Date::Manip::Month)=&Date_Regexp(\@tmp,"lc,under","1");
  2151.  
  2152.     # Date::Manip:: variables for day of week
  2153.     #   $WkExp  : "(sun|sunday|mon|monday ... )"
  2154.     #   @W      : ("S","M",...)
  2155.     #   @Wk     : ("Sun","Mon",...)
  2156.     #   @Week   : ("Sunday","Monday",...)
  2157.     #   %Week   : ("sunday",0,"sun",0,"s",0,...)
  2158.     $Date::Manip::WkExp=&Date_Regexp("$wk $week","lc,under,sort,back");
  2159.     ($tmp,@Date::Manip::W)   =&Date_Regexp($w,"",1);
  2160.     ($tmp,@Date::Manip::Wk)  =&Date_Regexp($wk,"",1);
  2161.     ($tmp,@Date::Manip::Week)=&Date_Regexp($week,"",1);
  2162.     ($tmp,@tmp )=&Date_Regexp($w,"","val0");
  2163.     ($tmp,@tmp2)=&Date_Regexp($wk,"","val0");
  2164.     ($tmp,@tmp3)=&Date_Regexp($week,"","val0");
  2165.     @tmp=reverse(@tmp,@tmp2,@tmp3);
  2166.     ($tmp,%Date::Manip::Week)=&Date_Regexp(\@tmp,"lc,under","1");
  2167.  
  2168.     # Date::Manip:: variables for day of week
  2169.     #   $DayExp   : "(1st|first|2nd|second ... )"
  2170.     #   %Day      : ("1st",1,"first",1, ... )"
  2171.     #   @Day      : ("1st","2nd",...);
  2172.     # Date::Manip:: variables for week of month
  2173.     #   $WhichExp : "(1st|first|2nd|second ... fifth|last)"
  2174.     #   %Which    : ("1st",1,"first",1, ... "fifth",5,"last",-1)"
  2175.     $Date::Manip::DayExp=&Date_Regexp(join(" ",@days),"back,sort,lc,under");
  2176.     ($tmp,@Date::Manip::Day)=&Date_Regexp($days[0],"lc,under",1);
  2177.     %Date::Manip::Day  =();
  2178.     %Date::Manip::Which=();
  2179.     @tmp2=@tmp3=();
  2180.     foreach $days (@days) {
  2181.       ($tmp,%tmp) =&Date_Regexp($days,"lc,under","val1");
  2182.       @tmp=();
  2183.       foreach (1,2,3,4,5) {
  2184.         push(@tmp,($_,$tmp{$_}))  if (exists $tmp{$_});
  2185.       }
  2186.       push(@tmp3,reverse (@tmp));
  2187.  
  2188.       @tmp=%tmp;
  2189.       push(@tmp2,reverse(@tmp));
  2190.     }
  2191.     %Date::Manip::Day=@tmp2;
  2192.     ($tmp,@tmp) =&Date_Regexp($last,"lc,under",1);
  2193.     push(@tmp3, map { $_,-1 } @tmp);
  2194.     ($Date::Manip::WhichExp,@tmp)= &Date_Regexp(\@tmp3,"sort,back","keys");
  2195.     %Date::Manip::Which=@tmp3;
  2196.  
  2197.     # Date::Manip:: variables for AM or PM
  2198.     #   $AmExp   : "(am)"
  2199.     #   $PmExp   : "(pm)"
  2200.     #   $AmPmExp : "(am|pm)"
  2201.     #   %AmPm    : (am,1,pm,2)
  2202.     #   $Am      : "AM"
  2203.     #   $Pm      : "PM"
  2204.     $Date::Manip::AmPmExp=&Date_Regexp("$am $pm","lc,back,under");
  2205.     ($Date::Manip::AmExp,@tmp2)=&Date_Regexp("$am","lc,back,under",1);
  2206.     ($Date::Manip::PmExp,@tmp3)=&Date_Regexp("$pm","lc,back,under",1);
  2207.     @tmp=map { $_,1 } @tmp2;
  2208.     push(@tmp,map { $_,2 } @tmp3);
  2209.     %Date::Manip::AmPm=@tmp;
  2210.     ($tmp,@tmp2)=&Date_Regexp("$am","under",1);
  2211.     ($tmp,@tmp3)=&Date_Regexp("$pm","under",1);
  2212.     $Date::Manip::Am=shift(@tmp2);
  2213.     $Date::Manip::Pm=shift(@tmp3);
  2214.  
  2215.     # Date::Manip:: variables for expressions used in parsing deltas
  2216.     #    $YExp   : "(?:y|yr|year|years)"
  2217.     #    $MExp   : similar for months
  2218.     #    $WExp   : similar for weeks
  2219.     #    $DExp   : similar for days
  2220.     #    $HExp   : similar for hours
  2221.     #    $MnExp  : similar for minutes
  2222.     #    $SExp   : similar for seconds
  2223.     #    %Replace: a list of replacements
  2224.     $Date::Manip::YExp   =&Date_Regexp($years,"lc,sort,under");
  2225.     $Date::Manip::MExp   =&Date_Regexp($months,"lc,sort,under");
  2226.     $Date::Manip::WExp   =&Date_Regexp($weeks,"lc,sort,under");
  2227.     $Date::Manip::DExp   =&Date_Regexp($days,"lc,sort,under");
  2228.     $Date::Manip::HExp   =&Date_Regexp($hours,"lc,sort,under");
  2229.     $Date::Manip::MnExp  =&Date_Regexp($minutes,"lc,sort,under");
  2230.     $Date::Manip::SExp   =&Date_Regexp($seconds,"lc,sort,under,opt");
  2231.     %Date::Manip::Replace=split(/\s+/,lc($replace));
  2232.  
  2233.     # Date::Manip:: variables for special dates that are offsets from now
  2234.     #    $Now      : "(now|today)"
  2235.     #    $Offset   : "(yesterday|tomorrow)"
  2236.     #    %Offset   : ("yesterday","-1:0:0:0",...)
  2237.     #    $TimesExp : "(noon|midnight)"
  2238.     #    %Times    : ("noon","12:00:00","midnight","00:00:00")
  2239.     $Date::Manip::Now=   &Date_Regexp($now,"lc,back,under");
  2240.     ($Date::Manip::Offset,%Date::Manip::Offset)=
  2241.       &Date_Regexp($offset,"lc,under,back","keys");
  2242.     ($Date::Manip::TimesExp,%Date::Manip::Times)=
  2243.       &Date_Regexp($times,"lc,under,back","keys");
  2244.     $Date::Manip::SepHM=$sephm;
  2245.     $Date::Manip::SepMS=$sepms;
  2246.     $Date::Manip::SepSS=$sepss;
  2247.  
  2248.     # Date::Manip:: variables for time zones
  2249.     #    $ZoneExp     : regular expression
  2250.     #    %Zone        : all parsable zones with their translation
  2251.     #    $Zone        : the current time zone
  2252.     #    $CurrZoneExp : "(us/eastern|us/central)"
  2253.     #    %CurrZone    : ("us/eastern","est7edt","us/central","cst6cdt")
  2254.     $zonesrfc=
  2255.       "idlw -1200 ".  # International Date Line West
  2256.       "nt   -1100 ".  # Nome
  2257.       "hst  -1000 ".  # Hawaii Standard
  2258.       "cat  -1000 ".  # Central Alaska
  2259.       "ahst -1000 ".  # Alaska-Hawaii Standard
  2260.       "yst  -0900 ".  # Yukon Standard
  2261.       "hdt  -0900 ".  # Hawaii Daylight
  2262.       "ydt  -0800 ".  # Yukon Daylight
  2263.       "pst  -0800 ".  # Pacific Standard
  2264.       "pdt  -0700 ".  # Pacific Daylight
  2265.       "mst  -0700 ".  # Mountain Standard
  2266.       "mdt  -0600 ".  # Mountain Daylight
  2267.       "cst  -0600 ".  # Central Standard
  2268.       "cdt  -0500 ".  # Central Daylight
  2269.       "est  -0500 ".  # Eastern Standard
  2270.       "edt  -0400 ".  # Eastern Daylight
  2271.       "ast  -0400 ".  # Atlantic Standard
  2272.       #"nst -0330 ".  # Newfoundland Standard       nst=North Sumatra    +0630
  2273.       "nft  -0330 ".  # Newfoundland
  2274.       #"gst -0300 ".  # Greenland Standard          gst=Guam Standard    +1000
  2275.       "bst  -0300 ".  # Brazil Standard             bst=British Summer   +0100
  2276.       "adt  -0300 ".  # Atlantic Daylight
  2277.       "ndt  -0230 ".  # Newfoundland Daylight
  2278.       "at   -0200 ".  # Azores
  2279.       "wat  -0100 ".  # West Africa
  2280.       "gmt  +0000 ".  # Greenwich Mean
  2281.       "ut   +0000 ".  # Universal (Coordinated)
  2282.       "utc  +0000 ".  # Universal (Coordinated)
  2283.       "wet  +0000 ".  # Western European
  2284.       "cet  +0100 ".  # Central European
  2285.       "fwt  +0100 ".  # French Winter
  2286.       "met  +0100 ".  # Middle European
  2287.       "mewt +0100 ".  # Middle European Winter
  2288.       "swt  +0100 ".  # Swedish Winter
  2289.       #"bst +0100 ".  # British Summer              bst=Brazil standard  -0300
  2290.       "eet  +0200 ".  # Eastern Europe, USSR Zone 1
  2291.       "fst  +0200 ".  # French Summer
  2292.       "mest +0200 ".  # Middle European Summer
  2293.       "sst  +0200 ".  # Swedish Summer              sst=South Sumatra    +0700
  2294.       "bt   +0300 ".  # Baghdad, USSR Zone 2
  2295.       "it   +0330 ".  # Iran
  2296.       "zp4  +0400 ".  # USSR Zone 3
  2297.       "zp5  +0500 ".  # USSR Zone 4
  2298.       "ist  +0530 ".  # Indian Standard
  2299.       "zp6  +0600 ".  # USSR Zone 5
  2300.       "nst  +0630 ".  # North Sumatra               nst=Newfoundland Std -0330
  2301.       "wast +0700 ".  # West Australian Standard
  2302.       #"sst +0700 ".  # South Sumatra, USSR Zone 6  sst=Swedish Summer   +0200
  2303.       "jt   +0730 ".  # Java (3pm in Cronusland!)
  2304.       "cct  +0800 ".  # China Coast, USSR Zone 7
  2305.       "wadt +0800 ".  # West Australian Daylight
  2306.       "jst  +0900 ".  # Japan Standard, USSR Zone 8
  2307.       "cast +0930 ".  # Central Australian Standard
  2308.       "east +1000 ".  # Eastern Australian Standard
  2309.       "gst  +1000 ".  # Guam Standard, USSR Zone 9  gst=Greenland Std    -0300
  2310.       "cadt +1030 ".  # Central Australian Daylight
  2311.       "eadt +1100 ".  # Eastern Australian Daylight
  2312.       "idle +1200 ".  # International Date Line East
  2313.       "nzst +1200 ".  # New Zealand Standard
  2314.       "nzt  +1200 ".  # New Zealand
  2315.       "nzdt +1300 ".  # New Zealand Daylight
  2316.       "z +0000 ".
  2317.       "a -0100 b -0200 c -0300 d -0400 e -0500 f -0600 g -0700 h -0800 ".
  2318.       "i -0900 k -1000 l -1100 m -1200 ".
  2319.       "n +0100 o +0200 p +0300 q +0400 r +0500 s +0600 t +0700 u +0800 ".
  2320.       "v +0900 w +1000 x +1100 y +1200 ".
  2321.       '[+-]\d{4} 0000';
  2322.     ($Date::Manip::ZoneExp,%Date::Manip::Zone)=
  2323.       &Date_Regexp("$zonesrfc $zones","sort,lc,under,back,opt,PRE,POST",
  2324.                    "keys");
  2325.     $tmp=
  2326.       "US/Pacific  PST8PDT ".
  2327.       "US/Mountain MST7MDT ".
  2328.       "US/Central  CST6CDT ".
  2329.       "US/Eastern  EST5EDT";
  2330.     ($Date::Manip::CurrZoneExp,%Date::Manip::CurrZone)=
  2331.       &Date_Regexp($tmp,"lc,under,back","keys");
  2332.     $Date::Manip::TZ=&Date_TimeZone;
  2333.  
  2334.     # Date::Manip:: misc. variables
  2335.     #    $At     : "(?:at)"
  2336.     #    $In     : "(?:in|of)"
  2337.     #    $On     : "(?:on)"
  2338.     #    $Future : "(?:in)"
  2339.     #    $Past   : "(?:ago)"
  2340.     #    $Next   : "(?:next)"
  2341.     #    $Prev   : "(?:last|previous)"
  2342.     $Date::Manip::At    =&Date_Regexp($at,"lc,under,pre,post,optws");
  2343.     $Date::Manip::In    =&Date_Regexp($in,"lc,under,pre,post");
  2344.     $Date::Manip::On    =&Date_Regexp($on,"lc,under,pre,post,optws");
  2345.     $Date::Manip::Future=&Date_Regexp($future,"lc,under");
  2346.     $Date::Manip::Past  =&Date_Regexp($past,"lc,under");
  2347.     $Date::Manip::Next  =&Date_Regexp($next,"lc,under");
  2348.     $Date::Manip::Prev  =&Date_Regexp($prev,"lc,under");
  2349.  
  2350.     # Date::Manip:: calc mode variables
  2351.     #    $Approx  : "(?:approximately)"
  2352.     #    $Exact   : "(?:exactly)"
  2353.     #    $Business: "(?:business)"
  2354.     $Date::Manip::Exact   =&Date_Regexp($exact,"lc,under");
  2355.     $Date::Manip::Approx  =&Date_Regexp($approx,"lc,under");
  2356.     $Date::Manip::Business=&Date_Regexp($business,"lc,under");
  2357.  
  2358.     ############### END OF LANGUAGE INITIALIZATION
  2359.   }
  2360.  
  2361.   if ($Date::Manip::ResetWorkDay) {
  2362.     my($h1,$m1,$h2,$m2)=();
  2363.     if ($Date::Manip::WorkDay24Hr) {
  2364.       ($Date::Manip::WDBh,$Date::Manip::WDBm)=(0,0);
  2365.       ($Date::Manip::WDEh,$Date::Manip::WDEm)=(24,0);
  2366.       $Date::Manip::WDlen=24*60;
  2367.       $Date::Manip::WorkDayBeg="00:00";
  2368.       $Date::Manip::WorkDayEnd="23:59";
  2369.  
  2370.     } else {
  2371.       confess "ERROR: Invalid WorkDayBeg in Date::Manip.\n"
  2372.         if (! (($h1,$m1)=&CheckTime($Date::Manip::WorkDayBeg)));
  2373.       confess "ERROR: Invalid WorkDayEnd in Date::Manip.\n"
  2374.         if (! (($h2,$m2)=&CheckTime($Date::Manip::WorkDayEnd)));
  2375.  
  2376.       ($Date::Manip::WDBh,$Date::Manip::WDBm)=($h1,$m1);
  2377.       ($Date::Manip::WDEh,$Date::Manip::WDEm)=($h2,$m2);
  2378.  
  2379.       # Work day length = h1:m1  or  0:len (len minutes)
  2380.       $h1=$h2-$h1;
  2381.       $m1=$m2-$m1;
  2382.       if ($m1<0) {
  2383.         $h1--;
  2384.         $m1+=60;
  2385.       }
  2386.       $Date::Manip::WDlen=$h1*60+$m1;
  2387.     }
  2388.     $Date::Manip::ResetWorkDay=0;
  2389.   }
  2390.  
  2391.   # current time
  2392.   my($s,$mn,$h,$d,$m,$y,$wday,$yday,$isdst)=localtime(time);
  2393.   $y+=1900;
  2394.   my($ampm)=();
  2395.   $wk="";
  2396.   $m++;
  2397.   &Date_ErrorCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk);
  2398.   $Date::Manip::CurrY=$y;
  2399.   $Date::Manip::CurrM=$m;
  2400.   $Date::Manip::CurrD=$d;
  2401.   $Date::Manip::CurrH=$h;
  2402.   $Date::Manip::CurrMn=$mn;
  2403.   $Date::Manip::CurrS=$s;
  2404.   $Date::Manip::CurrAmPm=$ampm;
  2405.   $Date::Manip::Curr=&FormDate($y,$m,$d,$h,$mn,$s);
  2406. }
  2407.  
  2408. # Returns 1 if $date is a work day.  If $time is non-zero, the time is
  2409. # also checked to see if it falls within work hours.
  2410. sub Date_IsWorkDay {
  2411.   my($date,$time)=@_;
  2412.   &Date_Init()  if (! $Date::Manip::InitDone);
  2413.   my($d)=$date;
  2414.   $d=&Date_SetTime($date,$Date::Manip::WorkDayBeg)
  2415.     if (! defined $time  or  ! $time);
  2416.  
  2417.   my($y,$dofw,$h,$m)=&UnixDate($d,"%Y","%w","%H","%M");
  2418.   return 0  if ($dofw<$Date::Manip::WorkWeekBeg or
  2419.                 $dofw>$Date::Manip::WorkWeekEnd or
  2420.                 "$h:$m" lt $Date::Manip::WorkDayBeg or
  2421.                 "$h:$m" gt $Date::Manip::WorkDayEnd);
  2422.   if ($y!=$Date::Manip::CurrHolidayYear) {
  2423.     $Date::Manip::CurrHolidayYear=$y;
  2424.     &Date_UpdateHolidays;
  2425.   }
  2426.   $d=&Date_SetTime($date,"00:00:00");
  2427.   return 0  if (exists $Date::Manip::CurrHolidays{$d});
  2428.   1;
  2429. }
  2430.  
  2431. # Finds the day $off work days from now.  If $time is passed in, we must
  2432. # also take into account the time of day.
  2433. #
  2434. # If $time is not passed in, day 0 is today (if today is a workday) or the
  2435. # next work day if it isn't.  In any case, the time of day is unaffected.
  2436. #
  2437. # If $time is passed in, day 0 is now (if now is part of a workday) or the
  2438. # start of the very next work day.
  2439. sub Date_NextWorkDay {
  2440.   my($date,$off,$time)=@_;
  2441.   &Date_Init()  if (! $Date::Manip::InitDone);
  2442.   my($err)=();
  2443.  
  2444.   if (! &Date_IsWorkDay($date,$time)) {
  2445.     if (defined $time and $time) {
  2446.       while (1) {
  2447.         $date=&Date_GetNext($date,undef,0,$Date::Manip::WorkDayBeg);
  2448.         last  if (&Date_IsWorkDay($date,$time));
  2449.       }
  2450.     } else {
  2451.       while (1) {
  2452.         $date=&DateCalc_DateDelta($date,"+0:0:1:0:0:0",\$err,0);
  2453.         last  if (&Date_IsWorkDay($date,$time));
  2454.       }
  2455.     }
  2456.   }
  2457.  
  2458.   while ($off>0) {
  2459.     while (1) {
  2460.       $date=&DateCalc_DateDelta($date,"+0:0:1:0:0:0",\$err,0);
  2461.       last  if (&Date_IsWorkDay($date,$time));
  2462.     }
  2463.     $off--;
  2464.   }
  2465.  
  2466.   return $date;
  2467. }
  2468.  
  2469. # Finds the day $off work days before now.  If $time is passed in, we must
  2470. # also take into account the time of day.
  2471. #
  2472. # If $time is not passed in, day 0 is today (if today is a workday) or the
  2473. # previous work day if it isn't.  In any case, the time of day is unaffected.
  2474. #
  2475. # If $time is passed in, day 0 is now (if now is part of a workday) or the
  2476. # end of the previous work period.  Note that since the end of a work day
  2477. # will automatically be turned into the start of the next one, this time
  2478. # may actually be treated as AFTER the current time.
  2479. sub Date_PrevWorkDay {
  2480.   my($date,$off,$time)=@_;
  2481.   &Date_Init()  if (! $Date::Manip::InitDone);
  2482.   my($err)=();
  2483.  
  2484.   if (! &Date_IsWorkDay($date,$time)) {
  2485.     if (defined $time and $time) {
  2486.       while (1) {
  2487.         $date=&Date_GetPrev($date,undef,0,$Date::Manip::WorkDayEnd);
  2488.         last  if (&Date_IsWorkDay($date,$time));
  2489.       }
  2490.       while (1) {
  2491.         $date=&Date_GetNext($date,undef,0,$Date::Manip::WorkDayBeg);
  2492.         last  if (&Date_IsWorkDay($date,$time));
  2493.       }
  2494.     } else {
  2495.       while (1) {
  2496.         $date=&DateCalc_DateDelta($date,"-0:0:1:0:0:0",\$err,0);
  2497.         last  if (&Date_IsWorkDay($date,$time));
  2498.       }
  2499.     }
  2500.   }
  2501.  
  2502.   while ($off>0) {
  2503.     while (1) {
  2504.       $date=&DateCalc_DateDelta($date,"-0:0:1:0:0:0",\$err,0);
  2505.       last  if (&Date_IsWorkDay($date,$time));
  2506.     }
  2507.     $off--;
  2508.   }
  2509.  
  2510.   return $date;
  2511. }
  2512.  
  2513. ########################################################################
  2514. # NOT FOR EXPORT
  2515. ########################################################################
  2516.  
  2517. # This is used in Date_Init to prepare regular expressions.  A list of
  2518. # items is passed in (either as a space separated string, or a reference to
  2519. # a list) and a regular expression which matches any one of the items is
  2520. # prepared.  The regular expression will be of one of the forms:
  2521. #   "(a|b)"       @list not empty, back option included
  2522. #   "(?:a|b)"     @list not empty
  2523. #   "()"          @list empty,     back option included
  2524. #   ""            @list empty
  2525. # $options is a string which contains any of the following strings:
  2526. #   back     : the regular expression has a backreference
  2527. #   opt      : the regular expression is optional and a "?" is appended in
  2528. #              the first two forms
  2529. #   optws    : the regular expression is optional and may be replaced by
  2530. #              whitespace
  2531. #   optWs    : the regular expression is optional, but if not present, must
  2532. #              be replaced by whitespace
  2533. #   sort     : the items in the list are sorted by length (longest first)
  2534. #   lc       : the string is lowercased
  2535. #   under    : any underscores are converted to spaces
  2536. #   pre      : it may be preceded by whitespace
  2537. #   Pre      : it must be preceded by whitespace
  2538. #   PRE      : it must be preceded by whitespace or the start
  2539. #   post     : it may be followed by whitespace
  2540. #   Post     : it must be followed by whitespace
  2541. #   POST     : it must be followed by whitespace or the end
  2542. # Spaces due to pre/post options will not be included in the back reference.
  2543. #
  2544. # If $array is included, then the elements will also be returned as a list.
  2545. # $array is a string which may contain any of the following:
  2546. #   keys     : treat the list as a hash and only the keys go into the regexp
  2547. #   key0     : treat the list as the values of a hash with keys 0 .. N-1
  2548. #   key1     : treat the list as the values of a hash with keys 1 .. N
  2549. #   val0     : treat the list as the keys of a hash with values 0 .. N-1
  2550. #   val1     : treat the list as the keys of a hash with values 1 .. N
  2551. sub Date_Regexp {
  2552.   my($list,$options,$array)=@_;
  2553.   my(@list,$ret,%hash,$i)=();
  2554.   $options=""  if (! defined $options);
  2555.   $array=""    if (! defined $array);
  2556.  
  2557.   my($sort,$lc,$under)=(0,0,0);
  2558.   $sort =1  if ($options =~ /sort/i);
  2559.   $lc   =1  if ($options =~ /lc/i);
  2560.   $under=1  if ($options =~ /under/i);
  2561.   my($back,$opt,$pre,$post,$ws)=("?:","","","","");
  2562.   $back =""          if ($options =~ /back/i);
  2563.   $opt  ="?"         if ($options =~ /opt/i);
  2564.   $pre  ='\s*'       if ($options =~ /pre/);
  2565.   $pre  ='\s+'       if ($options =~ /Pre/);
  2566.   $pre  ='(?:\s+|^)' if ($options =~ /PRE/);
  2567.   $post ='\s*'       if ($options =~ /post/);
  2568.   $post ='\s+'       if ($options =~ /Post/);
  2569.   $post ='(?:$|\s+)' if ($options =~ /POST/);
  2570.   $ws   ='\s*'       if ($options =~ /optws/);
  2571.   $ws   ='\s+'       if ($options =~ /optws/);
  2572.  
  2573.   my($hash,$keys,$key0,$key1,$val0,$val1)=(0,0,0,0,0,0);
  2574.   $keys =1     if ($array =~ /keys/i);
  2575.   $key0 =1     if ($array =~ /key0/i);
  2576.   $key1 =1     if ($array =~ /key1/i);
  2577.   $val0 =1     if ($array =~ /val0/i);
  2578.   $val1 =1     if ($array =~ /val1/i);
  2579.   $hash =1     if ($keys or $key0 or $key1 or $val0 or $val1);
  2580.  
  2581.   my($ref)=ref $list;
  2582.   if (! $ref) {
  2583.     $list =~ s/\s*$//;
  2584.     $list =~ s/^\s*//;
  2585.     $list =~ s/\s+/&&&/g;
  2586.   } elsif ($ref eq "ARRAY") {
  2587.     $list = join("&&&",@$list);
  2588.   } else {
  2589.     confess "ERROR: Date_Regexp.\n";
  2590.   }
  2591.  
  2592.   if (! $list) {
  2593.     if ($back eq "") {
  2594.       return "()";
  2595.     } else {
  2596.       return "";
  2597.     }
  2598.   }
  2599.  
  2600.   $list=lc($list)  if ($lc);
  2601.   $list=~ s/_/ /g  if ($under);
  2602.   @list=split(/&&&/,$list);
  2603.   if ($keys) {
  2604.     %hash=@list;
  2605.     @list=keys %hash;
  2606.   } elsif ($key0 or $key1 or $val0 or $val1) {
  2607.     $i=0;
  2608.     $i=1  if ($key1 or $val1);
  2609.     if ($key0 or $key1) {
  2610.       %hash= map { $_,$i++ } @list;
  2611.     } else {
  2612.       %hash= map { $i++,$_ } @list;
  2613.     }
  2614.   }
  2615.   @list=sort sortByLength(@list)  if ($sort);
  2616.  
  2617.   $ret="($back" . join("|",@list) . ")";
  2618.   $ret="(?:$pre$ret$post)"  if ($pre or $post);
  2619.   $ret.=$opt;
  2620.   $ret="(?:$ret|$ws)"  if ($ws);
  2621.  
  2622.   if ($array and $hash) {
  2623.     return ($ret,%hash);
  2624.   } elsif ($array) {
  2625.     return ($ret,@list);
  2626.   } else {
  2627.     return $ret;
  2628.   }
  2629. }
  2630.  
  2631. # This will produce a delta with the correct number of signs.  At most two
  2632. # signs will be in it normally (one before the year, and one in front of
  2633. # the day), but if appropriate, signs will be in front of all elements.
  2634. # Also, as many of the signs will be equivalent as possible.
  2635. sub NormalizeDelta {
  2636.   my($delta,$mode)=@_;
  2637.   return "" if (! defined $delta  or  ! $delta);
  2638.   return "+0:+0:+0:+0:+0:+0"
  2639.     if ($delta =~ /^([+-]?0+:){5}[+-]?0+/ and $Date::Manip::DeltaSigns);
  2640.   return "+0:0:0:0:0:0" if ($delta =~ /^([+-]?0+:){5}[+-]?0+/);
  2641.  
  2642.   my($tmp,$sign1,$sign2,$len)=();
  2643.  
  2644.   # Calculate the length of the day in minutes
  2645.   $len=24*60;
  2646.   $len=$Date::Manip::WDlen  if ($mode==2);
  2647.  
  2648.   # We have to get the sign of every component explicitely so that a "-0"
  2649.   # or "+0" doesn't get lost by treating it numerically (i.e. "-0:0:2" must
  2650.   # be a negative delta).
  2651.  
  2652.   my($y,$mon,$d,$h,$m,$s)=&CheckDelta($delta);
  2653.  
  2654.   # We need to make sure that the signs of all parts of a delta are the
  2655.   # same.  The easiest way to do this is to convert all of the large
  2656.   # components to the smallest ones, then convert the smaller components
  2657.   # back to the larger ones.
  2658.  
  2659.   # Do the year/month part
  2660.  
  2661.   $mon += $y*12;                         # convert y to m
  2662.   $sign1="+";
  2663.   if ($mon<0) {
  2664.     $mon *= -1;
  2665.     $sign1="-";
  2666.   }
  2667.  
  2668.   $y    = ($tmp=int($mon/12));           # convert m to y
  2669.   $mon -= $tmp*12;
  2670.  
  2671.   $y=0    if ($y eq "-0");               # get around silly -0 problem
  2672.   $mon=0  if ($mon eq "-0");
  2673.  
  2674.   # Do the day/hour/min/sec part
  2675.  
  2676.   $s += $d*$len*60 + $h*3600 + $m*60;    # convert d/h/m to s
  2677.   $sign2="+";
  2678.   if ($s<0) {
  2679.     $s*=-1;
  2680.     $sign2="-";
  2681.   }
  2682.  
  2683.   $m  = ($tmp=int($s/60));               # convert s to m
  2684.   $s -= $tmp*60;
  2685.   $d  = ($tmp=int($m/$len));             # convert m to d
  2686.   $m -= $tmp*$len;
  2687.   $h  = ($tmp=int($m/60));               # convert m to h
  2688.   $m -= $tmp*60;
  2689.  
  2690.   $d=0    if ($d eq "-0");               # get around silly -0 problem
  2691.   $h=0    if ($h eq "-0");
  2692.   $m=0    if ($m eq "-0");
  2693.   $s=0    if ($s eq "-0");
  2694.  
  2695.   # Only include two signs if necessary
  2696.   $sign1=$sign2  if ($y==0 and $mon==0);
  2697.   $sign2=$sign1  if ($d==0 and $h==0 and $m==0 and $s==0);
  2698.   $sign2=""  if ($sign1 eq $sign2  and  ! $Date::Manip::DeltaSigns);
  2699.  
  2700.   if ($Date::Manip::DeltaSigns) {
  2701.     return "$sign1$y:$sign1$mon:$sign2$d:$sign2$h:$sign2$m:$sign2$s";
  2702.   } else {
  2703.     return "$sign1$y:$mon:$sign2$d:$h:$m:$s";
  2704.   }
  2705. }
  2706.  
  2707. # This checks a delta to make sure it is valid.  If it is, it splits
  2708. # it and returns the elements with a sign on each.  The 2nd argument
  2709. # specifies the default sign.  Blank elements are set to 0.  If the
  2710. # third element is non-nil, exactly 6 elements must be included.
  2711. sub CheckDelta {
  2712.   my($delta,$sign,$exact)=@_;
  2713.   my(@delta)=split(/:/,$delta);
  2714.   return ()  if (defined $exact  and  $exact  and $#delta != 5);
  2715.   my($i)=();
  2716.   $sign="+"  if (! defined $sign);
  2717.   for ($i=0; $i<=$#delta; $i++) {
  2718.     $delta[$i]="0"  if (! $delta[$i]);
  2719.     return ()  if ($delta[$i] !~ /^[+-]?\d+$/);
  2720.     $sign = ($delta[$i] =~ s/^([+-])// ? $1 : $sign);
  2721.     $delta[$i] = $sign.$delta[$i];
  2722.   }
  2723.   @delta;
  2724. }
  2725.  
  2726. # Reads up to 3 arguments.  $h may contain the time in any international
  2727. # fomrat.  Any empty elements are set to 0.
  2728. sub ParseTime {
  2729.   my($h,$m,$s)=@_;
  2730.   my($t)=&CheckTime("one");
  2731.  
  2732.   if (defined $h  and  $h =~ /$t/) {
  2733.     $h=$1;
  2734.     $m=$2;
  2735.     $s=$3   if (defined $3);
  2736.   }
  2737.   $h="00"  if (! defined $h);
  2738.   $m="00"  if (! defined $m);
  2739.   $s="00"  if (! defined $s);
  2740.  
  2741.   ($h,$m,$s);
  2742. }
  2743.  
  2744. # Forms a date with the 6 elements passed in (all of which must be defined).
  2745. # No check as to validity is made.
  2746. sub FormDate {
  2747.   my($y,$m,$d,$h,$mn,$s)=@_;
  2748.   my($ym,$md,$dh,$hmn,$mns)=();
  2749.  
  2750.   if      ($Date::Manip::Internal == 0) {
  2751.     $ym=$md=$dh="";
  2752.     $hmn=$mns=":";
  2753.  
  2754.   } elsif ($Date::Manip::Internal == 1) {
  2755.     $ym=$md=$dh=$hmn=$mns="";
  2756.  
  2757.   } elsif ($Date::Manip::Internal == 2) {
  2758.     $ym=$md="-";
  2759.     $dh=" ";
  2760.     $hmn=$mns=":";
  2761.  
  2762.   } else {
  2763.     confess "ERROR: Invalid internal format in Date_FormDate.\n";
  2764.   }
  2765.   $m="0$m"    if (length($m)==1);
  2766.   $d="0$d"    if (length($d)==1);
  2767.   $h="0$h"    if (length($h)==1);
  2768.   $mn="0$mn"  if (length($mn)==1);
  2769.   $s="0$s"    if (length($s)==1);
  2770.   "$y$ym$m$md$d$dh$h$hmn$mn$mns$s";
  2771. }
  2772.  
  2773. # This checks a time.  If it is valid, it splits it and returns 3 elements.
  2774. # If "one" or "two" is passed in, a regexp with 1/2 or 2 digit hours is
  2775. # returned.
  2776. sub CheckTime {
  2777.   my($time)=@_;
  2778.   my($h)='(?:0?[0-9]|1[0-9]|2[0-3])';
  2779.   my($h2)='(?:0[0-9]|1[0-9]|2[0-3])';
  2780.   my($m)='[0-5][0-9]';
  2781.   my($s)=$m;
  2782.   my($hm)="(?:$Date::Manip::SepHM|:)";
  2783.   my($ms)="(?:$Date::Manip::SepMS|:)";
  2784.   my($ss)=$Date::Manip::SepSS;
  2785.   my($t)="^($h)$hm($m)(?:$ms($s)(?:$ss\d+)?)?\$";
  2786.   if ($time eq "one") {
  2787.     return $t;
  2788.   } elsif ($time eq "two") {
  2789.     $t="^($h2)$hm($m)(?:$ms($s)(?:$ss\d+)?)?\$";
  2790.     return $t;
  2791.   }
  2792.  
  2793.   if ($time =~ /$t/i) {
  2794.     ($h,$m,$s)=($1,$2,$3);
  2795.     $h="0$h" if (length($h)<2);
  2796.     $m="0$m" if (length($m)<2);
  2797.     $s="00"  if (! defined $s);
  2798.     return ($h,$m,$s);
  2799.   } else {
  2800.     return ();
  2801.   }
  2802. }
  2803.  
  2804. # This checks a date.  If it is valid, it splits it and returns the elements.
  2805. # If no date is passed in, it returns a regular expression for the date.
  2806. sub CheckDate {
  2807.   my($date)=@_;
  2808.   my($ym,$md,$dh,$hmn,$mns)=();
  2809.   my($y)='(\d{4})';
  2810.   my($m)='(0[1-9]|1[0-2])';
  2811.   my($d)='(0[1-9]|[1-2][0-9]|3[0-1])';
  2812.   my($h)='([0-1][0-9]|2[0-3])';
  2813.   my($mn)='([0-5][0-9])';
  2814.   my($s)=$mn;
  2815.  
  2816.   if      ($Date::Manip::Internal == 0) {
  2817.     $ym=$md=$dh="";
  2818.     $hmn=$mns=":";
  2819.  
  2820.   } elsif ($Date::Manip::Internal == 1) {
  2821.     $ym=$md=$dh=$hmn=$mns="";
  2822.  
  2823.   } elsif ($Date::Manip::Internal == 2) {
  2824.     $ym=$md="-";
  2825.     $dh=" ";
  2826.     $hmn=$mns=":";
  2827.  
  2828.   } else {
  2829.     confess "ERROR: Invalid internal format in Date_CheckDate.\n";
  2830.   }
  2831.  
  2832.   my($t)="^$y$ym$m$md$d$dh$h$hmn$mn$mns$s\$";
  2833.   return $t  if (! $date);
  2834.  
  2835.   return ($1,$2,$3,$4,$5,$6)  if ($date =~ /$t/);
  2836.   return ();
  2837. }
  2838.  
  2839. sub DateCalc_DateDate {
  2840.   my($D1,$D2,$mode)=@_;
  2841.   my(@d_in_m)=(0,31,28,31,30,31,30,31,31,30,31,30,31);
  2842.   $mode=0  if (! defined $mode);
  2843.  
  2844.   # Exact mode
  2845.   if ($mode==0) {
  2846.     my($y1,$m1,$d1,$h1,$mn1,$s1)=&CheckDate($D1);
  2847.     my($y2,$m2,$d2,$h2,$mn2,$s2)=&CheckDate($D2);
  2848.     my($i,@delta,$d,$delta,$y)=();
  2849.  
  2850.     # form the delta for hour/min/sec
  2851.     $delta[3]=$h2-$h1;
  2852.     $delta[4]=$mn2-$mn1;
  2853.     $delta[5]=$s2-$s1;
  2854.  
  2855.     # form the delta for yr/mon/day
  2856.     $delta[0]=$delta[1]=0;
  2857.     $d=0;
  2858.     if ($y2>$y1) {
  2859.       $d=&Date_DaysInYear($y1) - &Date_DayOfYear($m1,$d1,$y1);
  2860.       $d+=&Date_DayOfYear($m2,$d2,$y2);
  2861.       for ($y=$y1+1; $y<$y2; $y++) {
  2862.         $d+= &Date_DaysInYear($y);
  2863.       }
  2864.     } elsif ($y2<$y1) {
  2865.       $d=&Date_DaysInYear($y2) - &Date_DayOfYear($m2,$d2,$y2);
  2866.       $d+=&Date_DayOfYear($m1,$d1,$y1);
  2867.       for ($y=$y2+1; $y<$y1; $y++) {
  2868.         $d+= &Date_DaysInYear($y);
  2869.       }
  2870.       $d *= -1;
  2871.     } else {
  2872.       $d=&Date_DayOfYear($m2,$d2,$y2) - &Date_DayOfYear($m1,$d1,$y1);
  2873.     }
  2874.     $delta[2]=$d;
  2875.  
  2876.     for ($i=0; $i<6; $i++) {
  2877.       $delta[$i]="+".$delta[$i]  if ($delta[$i]>=0);
  2878.     }
  2879.  
  2880.     $delta=join(":",@delta);
  2881.     $delta=&NormalizeDelta($delta,0);
  2882.     return $delta;
  2883.   }
  2884.  
  2885.   my($date1,$date2)=($D1,$D2);
  2886.   my($tmp,$sign,$err,@tmp)=();
  2887.  
  2888.   # make sure both are work days
  2889.   if ($mode==2) {
  2890.     $date1=&Date_NextWorkDay($date1,0,1);
  2891.     $date2=&Date_NextWorkDay($date2,0,1);
  2892.   }
  2893.  
  2894.   # make sure date1 comes before date2
  2895.   if ($date1 gt $date2) {
  2896.     $sign="-";
  2897.     $tmp=$date1;
  2898.     $date1=$date2;
  2899.     $date2=$tmp;
  2900.   } else {
  2901.     $sign="+";
  2902.   }
  2903.   if ($date1 eq $date2) {
  2904.     return "+0:+0:+0:+0:+0:+0"  if ($Date::Manip::DeltaSigns);
  2905.     return "+0:0:0:0:0:0";
  2906.   }
  2907.  
  2908.   my($y1,$m1,$d1,$h1,$mn1,$s1)=&CheckDate($date1);
  2909.   my($y2,$m2,$d2,$h2,$mn2,$s2)=&CheckDate($date2);
  2910.   my($dy,$dm,$dd,$dh,$dmn,$ds,$ddd)=();
  2911.  
  2912.   # Do years
  2913.   $dy=$y2-$y1;
  2914.   $dm=0;
  2915.   if ($dy>0) {
  2916.     $tmp=&DateCalc_DateDelta($date1,"+$dy:0:0:0:0:0",\$err,0);
  2917.     if ($tmp gt $date2) {
  2918.       $dy--;
  2919.       $tmp=$date1;
  2920.       $tmp=&DateCalc_DateDelta($date1,"+$dy:0:0:0:0:0",\$err,0)  if ($dy>0);
  2921.       $dm=12;
  2922.     }
  2923.     $date1=$tmp;
  2924.   }
  2925.  
  2926.   # Do months
  2927.   $dm+=$m2-$m1;
  2928.   if ($dm>0) {
  2929.     $tmp=&DateCalc_DateDelta($date1,"+0:$dm:0:0:0:0",\$err,0);
  2930.     if ($tmp gt $date2) {
  2931.       $dm--;
  2932.       $tmp=$date1;
  2933.       $tmp=&DateCalc_DateDelta($date1,"+0:$dm:0:0:0:0",\$err,0)  if ($dm>0);
  2934.     }
  2935.     $date1=$tmp;
  2936.   }
  2937.  
  2938.   # Do days
  2939.   if ($mode==2) {
  2940.     $dd=0;
  2941.     while (1) {
  2942.       $tmp=&Date_NextWorkDay($date1,1,1);
  2943.       if ($tmp le $date2) {
  2944.         $dd++;
  2945.         $date1=$tmp;
  2946.       } else {
  2947.         last;
  2948.       }
  2949.     }
  2950.  
  2951.   } else {
  2952.     ($y1,$m1,$d1)=( &CheckDate($date1) )[0..2];
  2953.     $dd=0;
  2954.     # If we're jumping across months, set $d1 to the first of the next month
  2955.     # (or possibly the 0th of next month which is equivalent to the last day
  2956.     # of this month)
  2957.     if ($m1!=$m2) {
  2958.       $d_in_m[2]=29  if (&Date_LeapYear($y1));
  2959.       $dd=$d_in_m[$m1]-$d1+1;
  2960.       $d1=1;
  2961.       $tmp=&DateCalc_DateDelta($date1,"+0:0:$dd:0:0:0",\$err,0);
  2962.       if ($tmp gt $date2) {
  2963.         $dd--;
  2964.         $d1--;
  2965.         $tmp=&DateCalc_DateDelta($date1,"+0:0:$dd:0:0:0",\$err,0);
  2966.       }
  2967.       $date1=$tmp;
  2968.     }
  2969.  
  2970.     $ddd=0;
  2971.     if ($d1<$d2) {
  2972.       $ddd=$d2-$d1;
  2973.       $tmp=&DateCalc_DateDelta($date1,"+0:0:$ddd:0:0:0",\$err,0);
  2974.       if ($tmp gt $date2) {
  2975.         $ddd--;
  2976.         $tmp=&DateCalc_DateDelta($date1,"+0:0:$ddd:0:0:0",\$err,0);
  2977.       }
  2978.       $date1=$tmp;
  2979.     }
  2980.     $dd+=$ddd;
  2981.   }
  2982.  
  2983.   # in business mode, make sure h1 comes before h2 (if not find delta between
  2984.   # now and end of day and move to start of next business day)
  2985.   $d1=( &CheckDate($date1) )[2];
  2986.   $dh=$dmn=$ds=0;
  2987.   if ($mode==2  and  $d1<$d2) {
  2988.     $tmp=&Date_SetTime($date1,$Date::Manip::WorkDayEnd);
  2989.     $tmp=&DateCalc_DateDelta($tmp,"+0:0:0:0:1:0")
  2990.       if ($Date::Manip::WorkDay24Hr);
  2991.     $tmp=&DateCalc_DateDate($date1,$tmp,0);
  2992.     ($tmp,$tmp,$tmp,$dh,$dmn,$ds)=&CheckDelta($tmp);
  2993.     $date1=&Date_NextWorkDay($date1,1,0);
  2994.     $date1=&Date_SetTime($date1,$Date::Manip::WorkDayBeg);
  2995.     $d1=( &CheckDate($date1) )[2];
  2996.     confess "ERROR: DateCalc DateDate Business.\n"  if ($d1 != $d2);
  2997.   }
  2998.  
  2999.   # Hours, minutes, seconds
  3000.   $tmp=&DateCalc_DateDate($date1,$date2,0);
  3001.   @tmp=&CheckDelta($tmp);
  3002.   $dh  += $tmp[3];
  3003.   $dmn += $tmp[4];
  3004.   $ds  += $tmp[5];
  3005.  
  3006.   if ($Date::Manip::DeltaSigns) {
  3007.     return "$sign$dy:$sign$dm:$sign$dd:$sign$dh:$sign$dmn:$sign$ds";
  3008.   } else {
  3009.     return "$sign$dy:$dm:$dd:$dh:$dmn:$ds";
  3010.   }
  3011. }
  3012.  
  3013. sub DateCalc_DeltaDelta {
  3014.   my($D1,$D2,$mode)=@_;
  3015.   my(@delta1,@delta2,$i,$delta,@delta)=();
  3016.   $mode=0  if (! defined $mode);
  3017.  
  3018.   @delta1=&CheckDelta($D1);
  3019.   @delta2=&CheckDelta($D2);
  3020.   for ($i=0; $i<6; $i++) {
  3021.     $delta[$i]=$delta1[$i]+$delta2[$i];
  3022.     $delta[$i]="+".$delta[$i]  if ($delta[$i]>=0);
  3023.   }
  3024.  
  3025.   $delta=join(":",@delta);
  3026.   $delta=&NormalizeDelta($delta,$mode);
  3027.   return $delta;
  3028. }
  3029.  
  3030. sub DateCalc_DateDelta {
  3031.   my($D1,$D2,$errref,$mode)=@_;
  3032.   my($date)=();
  3033.   my(@d_in_m)=(0,31,28,31,30,31,30,31,31,30,31,30,31);
  3034.   my($h1,$m1,$h2,$m2,$len,$hh,$mm)=();
  3035.   $mode=0  if (! defined $mode);
  3036.  
  3037.   if ($mode==2) {
  3038.     $h1=$Date::Manip::WDBh;
  3039.     $m1=$Date::Manip::WDBm;
  3040.     $h2=$Date::Manip::WDEh;
  3041.     $m2=$Date::Manip::WDEm;
  3042.     $hh=$h2-$h1;
  3043.     $mm=$m2-$m1;
  3044.     if ($mm<0) {
  3045.       $hh--;
  3046.       $mm+=60;
  3047.     }
  3048.   }
  3049.  
  3050.   # Date, delta
  3051.   my($y,$m,$d,$h,$mn,$s)=&CheckDate($D1);
  3052.   my($dy,$dm,$dd,$dh,$dmn,$ds)=&CheckDelta($D2);
  3053.  
  3054.   # do the month/year part
  3055.   $y+=$dy;
  3056.   &ModuloAddition(-12,$dm,\$m,\$y);   # -12 means 1-12 instead of 0-11
  3057.   $d_in_m[2]=29  if (&Date_LeapYear($y));
  3058.  
  3059.   # in business mode, set the day to a work day at this point so the h/mn/s
  3060.   # stuff will work out
  3061.   if ($mode==2) {
  3062.     $d=$d_in_m[$m] if ($d>$d_in_m[$m]);
  3063.     $date=&Date_NextWorkDay(&FormDate($y,$m,$d,$h,$mn,$s),0,1);
  3064.     ($y,$m,$d,$h,$mn,$s)=&CheckDate($date);
  3065.   }
  3066.  
  3067.   # seconds, minutes, hours
  3068.   &ModuloAddition(60,$ds,\$s,\$mn);
  3069.   if ($mode==2) {
  3070.     while (1) {
  3071.       &ModuloAddition(60,$dmn,\$mn,\$h);
  3072.       $h+= $dh;
  3073.  
  3074.       if ($h>$h2  or  $h==$h2 && $mn>$m2) {
  3075.         $dh=$h-$h2;
  3076.         $dmn=$mn-$m2;
  3077.         $h=$h1;
  3078.         $mn=$m1;
  3079.         $dd++;
  3080.  
  3081.       } elsif ($h<$h1  or  $h==$h1 && $mn<$m1) {
  3082.         $dh=$h1-$h;
  3083.         $dmn=$m1-$mn;
  3084.         $h=$h2;
  3085.         $mn=$m2;
  3086.         $dd--;
  3087.  
  3088.       } elsif ($h==$h2  &&  $mn==$m2) {
  3089.         $dd++;
  3090.         $dh=-$hh;
  3091.         $dmn=-$mm;
  3092.  
  3093.       } else {
  3094.         last;
  3095.       }
  3096.     }
  3097.  
  3098.   } else {
  3099.     &ModuloAddition(60,$dmn,\$mn,\$h);
  3100.     &ModuloAddition(24,$dh,\$h,\$d);
  3101.   }
  3102.  
  3103.   # If we have just gone past the last day of the month, we need to make
  3104.   # up for this:
  3105.   if ($d>$d_in_m[$m]) {
  3106.     $dd+= $d-$d_in_m[$m];
  3107.     $d=$d_in_m[$m];
  3108.   }
  3109.  
  3110.   # days
  3111.   if ($mode==2) {
  3112.     if ($dd>=0) {
  3113.       $date=&Date_NextWorkDay(&FormDate($y,$m,$d,$h,$mn,$s),$dd,1);
  3114.     } else {
  3115.       $date=&Date_PrevWorkDay(&FormDate($y,$m,$d,$h,$mn,$s),-$dd,1);
  3116.     }
  3117.     ($y,$m,$d,$h,$mn,$s)=&CheckDate($date);
  3118.  
  3119.   } else {
  3120.     $d_in_m[2]=29  if (&Date_LeapYear($y));
  3121.     $d=$d_in_m[$m]  if ($d>$d_in_m[$m]);
  3122.     $d += $dd;
  3123.     while ($d<1) {
  3124.       $m--;
  3125.       if ($m==0) {
  3126.         $m=12;
  3127.         $y--;
  3128.         if (&Date_LeapYear($y)) {
  3129.           $d_in_m[2]=29;
  3130.         } else {
  3131.           $d_in_m[2]=28;
  3132.         }
  3133.       }
  3134.       $d += $d_in_m[$m];
  3135.     }
  3136.     while ($d>$d_in_m[$m]) {
  3137.       $d -= $d_in_m[$m];
  3138.       $m++;
  3139.       if ($m==13) {
  3140.         $m=1;
  3141.         $y++;
  3142.         if (&Date_LeapYear($y)) {
  3143.           $d_in_m[2]=29;
  3144.         } else {
  3145.           $d_in_m[2]=28;
  3146.         }
  3147.       }
  3148.     }
  3149.   }
  3150.  
  3151.   if ($y<1000 or $y>9999) {
  3152.     $$errref=3;
  3153.     return;
  3154.   }
  3155.   &FormDate($y,$m,$d,$h,$mn,$s);
  3156. }
  3157.  
  3158. sub Date_UpdateHolidays {
  3159.   my($date,$delta,$err)=();
  3160.   local($_)=();
  3161.   foreach (keys %Date::Manip::Holidays) {
  3162.     if (/^(.*)([+-].*)$/) {
  3163.       # Date +/- Delta
  3164.       ($date,$delta)=($1,$2);
  3165.       $Date::Manip::UpdateHolidays=1;
  3166.       $date=&ParseDate($date);
  3167.       $Date::Manip::UpdateHolidays=0;
  3168.       $date=&DateCalc($date,$delta,\$err,0);
  3169.  
  3170.     } else {
  3171.       # Date
  3172.       $Date::Manip::UpdateHolidays=1;
  3173.       $date=&ParseDate($_);
  3174.       $Date::Manip::UpdateHolidays=0;
  3175.     }
  3176.     $Date::Manip::CurrHolidays{$date}=1;
  3177.   }
  3178. }
  3179.  
  3180. # This sets a Date::Manip config variable.
  3181. sub Date_SetConfigVariable {
  3182.   my($var,$val)=@_;
  3183.  
  3184.   return  if ($var =~ /^PersonalCnf$/i);
  3185.   return  if ($var =~ /^PersonalCnfPath$/i);
  3186.  
  3187.   $Date::Manip::InitFilesRead=1,   return  if ($var =~ /^IgnoreGlobalCnf$/i);
  3188.   %Date::Manip::Holidays=(),       return  if ($var =~ /^EraseHolidays$/i);
  3189.   $Date::Manip::Init=0,
  3190.   $Date::Manip::Language=$val,     return  if ($var =~ /^Language$/i);
  3191.   $Date::Manip::DateFormat=$val,   return  if ($var =~ /^DateFormat$/i);
  3192.   $Date::Manip::TZ=$val,           return  if ($var =~ /^TZ$/i);
  3193.   $Date::Manip::ConvTZ=$val,       return  if ($var =~ /^ConvTZ$/i);
  3194.   $Date::Manip::Internal=$val,     return  if ($var =~ /^Internal$/i);
  3195.   $Date::Manip::FirstDay=$val,     return  if ($var =~ /^FirstDay$/i);
  3196.   $Date::Manip::WorkWeekBeg=$val,  return  if ($var =~ /^WorkWeekBeg$/i);
  3197.   $Date::Manip::WorkWeekEnd=$val,  return  if ($var =~ /^WorkWeekEnd$/i);
  3198.   $Date::Manip::WorkDayBeg=$val,
  3199.   $Date::Manip::ResetWorkDay=1,    return  if ($var =~ /^WorkDayBeg$/i);
  3200.   $Date::Manip::WorkDayEnd=$val,
  3201.   $Date::Manip::ResetWorkDay=1,    return  if ($var =~ /^WorkDayEnd$/i);
  3202.   $Date::Manip::WorkDay24Hr=$val,
  3203.   $Date::Manip::ResetWorkDay=1,    return  if ($var =~ /^WorkDay24Hr$/i);
  3204.   $Date::Manip::DeltaSigns=$val,   return  if ($var =~ /^DeltaSigns$/i);
  3205.  
  3206.   confess "ERROR: Unknown configuration variable $var in Date::Manip.\n";
  3207. }
  3208.  
  3209. # This reads an init file.
  3210. sub ReadInitFile {
  3211.   my($file)=@_;
  3212.   local($_)=();
  3213.   my($section)="vars";
  3214.   my($var,$val,$date,$name)=();
  3215.  
  3216.   open(IN,$file);
  3217.   while(defined ($_=<IN>)) {
  3218.     chomp;
  3219.     s/^\s+//;
  3220.     s/\s+$//;
  3221.     next  if (! $_  or  /^\#/);
  3222.     if (s/^\*\s*//) {
  3223.       $section=$_;
  3224.       next;
  3225.     }
  3226.  
  3227.     if ($section =~ /var/) {
  3228.       confess "ERROR: invalid Date::Manip config file line.\n  $_\n"
  3229.         if (! /(.*\S)\s*=\s*(.*)$/);
  3230.       ($var,$val)=($1,$2);
  3231.       &Date_SetConfigVariable($var,$val);
  3232.  
  3233.     } elsif ($section =~ /holiday/i) {
  3234.       confess "ERROR: invalid Date::Manip config file line.\n  $_\n"
  3235.         if (! /(.*\S)\s*=\s*(.*)$/);
  3236.       ($date,$name)=($1,$2);
  3237.       $name=""  if (! defined $name);
  3238.       $Date::Manip::Holidays{$date}=$name;
  3239.  
  3240.     } else {
  3241.       # A section not currently used by Date::Manip (but may be
  3242.       # used by some extension to it).
  3243.       next;
  3244.     }
  3245.   }
  3246.   close(IN);
  3247. }
  3248.  
  3249. # Get rid of a problem on old versions of perl
  3250. no strict "vars";
  3251. # This sorts from longest to shortest element
  3252. sub sortByLength {
  3253.   return (length $b <=> length $a);
  3254. }
  3255. use strict "vars";
  3256.  
  3257. # $flag=&Date_ErrorCheck(\$y,\$m,\$d,\$h,\$mn,\$s,\$ampm,\$wk);
  3258. #   Returns 1 if any of the fields are bad.  All fields are optional, and
  3259. #   all possible checks are done on the data.  If a field is not passed in,
  3260. #   it is set to default values.  If data is missing, appropriate defaults
  3261. #   are supplied.
  3262. #
  3263. #   If the flag Date::Manip::UpdateHolidays is set, the year is set to
  3264. #   Date::Manip::CurrHolidayYear.
  3265. sub Date_ErrorCheck {
  3266.   my($y,$m,$d,$h,$mn,$s,$ampm,$wk)=@_;
  3267.   my($tmp1,$tmp2,$tmp3)=();
  3268.  
  3269.   my(@d_in_m)=(0,31,28,31,30,31,30,31,31,30,31,30,31);
  3270.   my($curr_y)=$Date::Manip::CurrY;
  3271.   my($curr_m)=$Date::Manip::CurrM;
  3272.   my($curr_d)=$Date::Manip::CurrD;
  3273.   $$y=""     if (! defined $$y);
  3274.   $$m=""     if (! defined $$m);
  3275.   $$d=""     if (! defined $$d);
  3276.   $$h=""     if (! defined $$h);
  3277.   $$mn=""    if (! defined $$mn);
  3278.   $$s=""     if (! defined $$s);
  3279.   $$ampm=""  if (! defined $$ampm);
  3280.   $$ampm=uc($$ampm)  if ($$ampm);
  3281.   $$wk=""    if (! defined $$wk);
  3282.   $$d=$curr_d  if ($$y eq "" and $$m eq "" and $$d eq "");
  3283.  
  3284.   # Check year.
  3285.   $$y=$Date::Manip::CurrHolidayYear  if ($Date::Manip::UpdateHolidays);
  3286.   $$y=$curr_y    if ($$y eq "");
  3287.   if (length($$y)==2) {
  3288.     $tmp1=$curr_y-89;
  3289.     $$y="19$$y";
  3290.     while ($$y<$tmp1) {
  3291.       $$y+=100;
  3292.     }
  3293.   }
  3294.   return 1       if (! &IsInt($$y,1,9999));
  3295.   $d_in_m[2]=29  if (&Date_LeapYear($$y));
  3296.  
  3297.   # Check month
  3298.   $$m=$curr_m     if ($$m eq "");
  3299.   $$m=$Date::Manip::Month{lc($$m)}  if (exists $Date::Manip::Month{lc($$m)});
  3300.   $$m="0$$m"      if (length($$m)==1);
  3301.   return 1        if (! &IsInt($$m,1,12));
  3302.  
  3303.   # Check day
  3304.   $$d="01"        if ($$d eq "");
  3305.   $$d="0$$d"      if (length($$d)==1);
  3306.   return 1        if (! &IsInt($$d,1,$d_in_m[$$m]));
  3307.   if ($$wk) {
  3308.     $tmp1=&Date_DayOfWeek($$m,$$d,$$y);
  3309.     $tmp2=$Date::Manip::Week{lc($$wk)}
  3310.       if (exists $Date::Manip::Week{lc($$wk)});
  3311.     return 1      if ($tmp1 != $tmp2);
  3312.   }
  3313.  
  3314.   # Check hour
  3315.   $tmp1=$Date::Manip::AmPmExp;
  3316.   $tmp2="";
  3317.   if ($$ampm =~ /^$tmp1$/i) {
  3318.     $tmp3=$Date::Manip::AmExp;
  3319.     $tmp2="AM"  if ($$ampm =~ /^$tmp3$/i);
  3320.     $tmp3=$Date::Manip::PmExp;
  3321.     $tmp2="PM"  if ($$ampm =~ /^$tmp3$/i);
  3322.   } elsif ($$ampm) {
  3323.     return 1;
  3324.   }
  3325.   if ($tmp2 eq "AM" || $tmp2 eq "PM") {
  3326.     $$h="0$$h"    if (length($$h)==1);
  3327.     return 1      if ($$h<1 || $$h>12);
  3328.     $$h="00"      if ($tmp2 eq "AM"  and  $$h==12);
  3329.     $$h += 12     if ($tmp2 eq "PM"  and  $$h!=12);
  3330.   } else {
  3331.     $$h="00"      if ($$h eq "");
  3332.     $$h="0$$h"    if (length($$h)==1);
  3333.     return 1      if (! &IsInt($$h,0,23));
  3334.     $tmp2="AM"    if ($$h<12);
  3335.     $tmp2="PM"    if ($$h>=12);
  3336.   }
  3337.   $$ampm=$Date::Manip::Am;
  3338.   $$ampm=$Date::Manip::Pm  if ($tmp2 eq "PM");
  3339.  
  3340.   # Check minutes
  3341.   $$mn="00"       if ($$mn eq "");
  3342.   $$mn="0$$mn"    if (length($$mn)==1);
  3343.   return 1        if (! &IsInt($$mn,0,59));
  3344.  
  3345.   # Check seconds
  3346.   $$s="00"        if ($$s eq "");
  3347.   $$s="0$$s"      if (length($$s)==1);
  3348.   return 1        if (! &IsInt($$s,0,59));
  3349.  
  3350.   return 0;
  3351. }
  3352.  
  3353. ########################################################################
  3354. # FROM MY PERSONAL LIBRARIES
  3355. ########################################################################
  3356.  
  3357. # This takes 4 numbers ($N,$add,\$val,\$rem), adds $add to $val, and forces
  3358. # $val to be in a certain range.  This is useful for adding numbers for
  3359. # which only a certain range is allowed (for example, minutes can be
  3360. # between 0 and 59 or months can be between 1 and 12).  The absolute value
  3361. # of $N determines the range and the sign of $N determines whether the
  3362. # range is 0 to N-1 (if N>0) or 1 to N (N<0).  The remainder (as modulo N)
  3363. # is added to $rem.
  3364. sub ModuloAddition {
  3365.   my($N,$add,$val,$rem)=@_;
  3366.   return  if ($N==0);
  3367.   $$val+=$add;
  3368.   if ($N<0) {
  3369.     # 1 to N
  3370.     $N = -$N;
  3371.     if ($$val>$N) {
  3372.       $$rem+= int(($$val-1)/$N);
  3373.       $$val = ($$val-1)%$N +1;
  3374.     } elsif ($$val<1) {
  3375.       $$rem-= int(-$$val/$N)+1;
  3376.       $$val = $N-(-$$val % $N);
  3377.     }
  3378.  
  3379.   } else {
  3380.     # 0 to N-1
  3381.     if ($$val>($N-1)) {
  3382.       $$rem+= int($$val/$N);
  3383.       $$val = $$val%$N;
  3384.     } elsif ($$val<0) {
  3385.       $$rem-= int(-($$val+1)/$N)+1;
  3386.       $$val = ($N-1)-(-($$val+1)%$N);
  3387.     }
  3388.   }
  3389. }
  3390.  
  3391. # Returns 1 if $String is a valid integer, 0 otherwise.  If $low
  3392. # and $high are entered, the integer must be in that range.
  3393. sub IsInt {
  3394.   my($N,$low,$high)=@_;
  3395.   return 0 if ($N eq "");
  3396.   my($sign)='^\s* [-+]? \s*';
  3397.   my($int) ='\d+ \s* $ ';
  3398.   if ($N =~ /$sign $int/x) {
  3399.     if (defined $low  and  defined $high) {
  3400.       return 1  if ($N>=$low  and  $N<=$high);
  3401.       return 0;
  3402.     }
  3403.     return 1;
  3404.   }
  3405.   return 0;
  3406. }
  3407.  
  3408. # $Pos=&SinLindex(\@List,$Str [,$Offset [,$CaseInsensitive]]);
  3409. #    Searches for a list element exactly equal to $Str.
  3410. #
  3411. #    This returns the location of first element (starting at $Offset) in
  3412. #    @List containing $Str.  $Offset defaults to 0.  A negative offset
  3413. #    refers to the number of elements before the end of the list (-1 is the
  3414. #    last element).  The regular expression is case sensitive by default.
  3415. #
  3416. #    -1 is returned if it is not found and -2 is returned if an error is
  3417. #    encountered.
  3418. sub SinLindex {
  3419.   my($listref,$Str,$Offset,$Insensitive)=@_;
  3420.   my($i,$len,$tmp)=();
  3421.   $len=$#$listref;
  3422.   return -2  if ($len<0 or ! $Str);
  3423.   return -1  if (&Index_First(\$Offset,$len));
  3424.   $Str=uc($Str)  if ($Insensitive);
  3425.   for ($i=$Offset; $i<=$len; $i++) {
  3426.     $tmp=$$listref[$i];
  3427.     $tmp=uc($tmp)  if ($Insensitive);
  3428.     return $i  if ($tmp eq $Str);
  3429.   }
  3430.   return -1;
  3431. }
  3432. sub Index_First {
  3433.   my($Offsetref,$max)=@_;
  3434.   $$Offsetref=0  if (! $$Offsetref);
  3435.   if ($$Offsetref < 0) {
  3436.     $$Offsetref += $max + 1;
  3437.     $$Offsetref=0  if ($$Offsetref < 0);
  3438.   }
  3439.   return -1 if ($$Offsetref > $max);
  3440.   return 0;
  3441. }
  3442.  
  3443. # $File=&CleanFile($file);
  3444. #   This cleans up a path to remove the following things:
  3445. #     double slash       /a//b  -> /a/b
  3446. #     trailing dot       /a/.   -> /a
  3447. #     leading dot        ./a    -> a
  3448. #     trailing slash     a/     -> a
  3449. sub CleanFile {
  3450.   my($file)=@_;
  3451.   $file =~ s/\s*$//;
  3452.   $file =~ s/^\s*//;
  3453.   $file =~ s|//+|/|g;  # multiple slash
  3454.   $file =~ s|/\.$|/|;  # trailing /. (leaves trailing slash)
  3455.   $file =~ s|^\./||    # leading ./
  3456.     if ($file ne "./");
  3457.   $file =~ s|/$||      # trailing slash
  3458.     if ($file ne "/");
  3459.   return $file;
  3460. }
  3461.  
  3462. # $File=&ExpandTilde($file);
  3463. #   This checks to see if a "~" appears as the first character in a path.
  3464. #   If it does, the "~" expansion is interpreted (if possible) and the full
  3465. #   path is returned.  If a "~" expansion is used but cannot be
  3466. #   interpreted, an empty string is returned.  CleanFile is called.
  3467. sub ExpandTilde {
  3468.   my($file)=shift;
  3469.   my($user)=();
  3470.   my($name,$passwd,$uid,$gid,$quota,$comment,$gcos,$dir,$shell)=();
  3471.   # ~aaa/bbb=      ~  aaa      /bbb
  3472.   if ($file =~ m% ^~ ([^\/]*) (\/.*)? %x) {
  3473.     ($user,$file)=($1,$2);
  3474.     # Single user operating systems (Mac, MSWindows) don't have the getpwnam
  3475.     # and getpwuid routines defined.  Try to catch various different ways
  3476.     # of knowing we are on one of these systems:
  3477.     return ""  if (defined $^O and
  3478.                    $^O =~ /MacOS/i ||
  3479.                    $^O =~ /MSWin32/i ||
  3480.                    $^O =~ /Windows_95/i);
  3481.     return ""  if (defined $ENV{OS} and
  3482.                    $ENV{OS} =~ /MacOS/i ||
  3483.                    $ENV{OS} =~ /MSWin32/i ||
  3484.                    $ENV{OS} =~ /Windows_95/i);
  3485.     $user=""  if (! defined $user);
  3486.     $file=""  if (! defined $file);
  3487.     if ($user) {
  3488.       ($name,$passwd,$uid,$gid,$quota,$comment,$gcos,$dir,$shell)=
  3489.         getpwnam($user);
  3490.     } else {
  3491.       ($name,$passwd,$uid,$gid,$quota,$comment,$gcos,$dir,$shell)=
  3492.         getpwuid($<);
  3493.     }
  3494.     return ""  if (! $dir);
  3495.  
  3496.     $file="$dir/$file";
  3497.   }
  3498.   return &CleanFile($file);
  3499. }
  3500.  
  3501. # $File=&FullFilePath($file);
  3502. #   Returns the full path to $file.  Returns an empty string if a "~"
  3503. #   expansion cannot be interpreted.  The path does not need to exist.
  3504. #   CleanFile is called.
  3505. sub FullFilePath {
  3506.   my($file)=shift;
  3507.   $file=&ExpandTilde($file);
  3508.   return ""  if (! $file);
  3509.   $file=cwd . "/$file"  if ($file !~ m|^/|);   # $file = "a/b/c"
  3510.   return &CleanFile($file);
  3511. }
  3512.  
  3513. # $Flag=&CheckFilePath($file [,$mode]);
  3514. #   Checks to see if $file exists, to see what type it is, and whether
  3515. #   the script can access it.  If it exists and has the correct mode, 1
  3516. #   is returned.
  3517. #
  3518. #   $mode is a string which may contain any of the valid file test operator
  3519. #   characters except t, M, A, C.  The appropriate test is run for each
  3520. #   character.  For example, if $mode is "re" the -r and -e tests are both
  3521. #   run.
  3522. #
  3523. #   An empty string is returned if the file doesn't exist.  A 0 is returned
  3524. #   if the file exists but any test fails.
  3525. #
  3526. #   All characters in $mode which do not correspond to valid tests are
  3527. #   ignored.
  3528. sub CheckFilePath {
  3529.   my($file,$mode)=@_;
  3530.   my($test)=();
  3531.   $file=&FullFilePath($file);
  3532.   $mode = ""  if (! defined $mode);
  3533.  
  3534.   # File doesn't exist
  3535.   return "" if (! defined $file  or  ! $file  or  ! -e $file );
  3536.  
  3537.   # Run tests
  3538.   foreach $test ("r","w","x","R","W","X","o","O","e","z","s","f","d","l","s",
  3539.                  "p","b","c","u","g","k","T","B") {
  3540.     return 0  if ($mode =~ /$test/  and  ! eval "-$test '$file'");
  3541.   }
  3542.  
  3543.   return 1;
  3544. }
  3545.  
  3546. # $Path=&FixPath($path [,$full] [,$mode] [,$error]);
  3547. #   Makes sure that every directory in $path (a colon separated list of
  3548. #   directories) appears as a full path or relative path.  All "~"
  3549. #   expansions are removed.  All trailing slashes are removed also.  If
  3550. #   $full is non-nil, relative paths are expanded to full paths as well.
  3551. #
  3552. #   If $mode is given, it may be either "e", "r", or "w".  In this case,
  3553. #   additional checking is done to each directory.  If $mode is "e", it
  3554. #   need ony exist to pass the check.  If $mode is "r", it must have have
  3555. #   read and execute permission.  If $mode is "w", it must have read,
  3556. #   write, and execute permission.
  3557. #
  3558. #   The value of $error determines what happens if the directory does not
  3559. #   pass the test.  If it is non-nil, if any directory does not pass the
  3560. #   test, the subroutine returns the empty string.  Otherwise, it is simply
  3561. #   removed from $path.
  3562. #
  3563. #   The corrected path is returned.
  3564. sub FixPath {
  3565.   my($path,$full,$mode,$err)=@_;
  3566.   my(@dir)=split(/:/,$path);
  3567.   $full=0  if (! defined $full);
  3568.   $mode="" if (! defined $mode);
  3569.   $err=0   if (! defined $err);
  3570.   $path="";
  3571.   if ($mode eq "e") {
  3572.     $mode="de";
  3573.   } elsif ($mode eq "r") {
  3574.     $mode="derx";
  3575.   } elsif ($mode eq "w") {
  3576.     $mode="derwx";
  3577.   }
  3578.  
  3579.   foreach (@dir) {
  3580.  
  3581.     # Expand path
  3582.     if ($full) {
  3583.       $_=&FullFilePath($_);
  3584.     } else {
  3585.       $_=&ExpandTilde($_);
  3586.     }
  3587.     if (! $_) {
  3588.       return ""  if ($err);
  3589.       next;
  3590.     }
  3591.  
  3592.     # Check mode
  3593.     if (! $mode  or  &CheckFilePath($_,$mode)) {
  3594.       $path .= ":$_";
  3595.     } else {
  3596.       return "" if ($err);
  3597.     }
  3598.   }
  3599.   $path =~ s/^://;
  3600.   return $path;
  3601. }
  3602.  
  3603. # $File=&SearchPath($file,$path [,$mode] [,@suffixes]);
  3604. #   Searches through directories in $path for a file named $file.  The
  3605. #   full path is returned if one is found, or an empty string otherwise.
  3606. #   The file may exist with one of the @suffixes.  The mode is checked
  3607. #   similar to &CheckFilePath.
  3608. #
  3609. #   The first full path that matches the name and mode is returned.  If none
  3610. #   is found, an empty string is returned.
  3611. sub SearchPath {
  3612.   my($file,$path,$mode,@suff)=@_;
  3613.   my($f,$s,$d,@dir,$fs)=();
  3614.   $path=&FixPath($path,1,"r");
  3615.   @dir=split(/:/,$path);
  3616.   foreach $d (@dir) {
  3617.     $f="$d/$file";
  3618.     $f=~ s|//|/|g;
  3619.     return $f if (&CheckFilePath($f,$mode));
  3620.     foreach $s (@suff) {
  3621.       $fs="$f.$s";
  3622.       return $fs if (&CheckFilePath($fs,$mode));
  3623.     }
  3624.   }
  3625.   return "";
  3626. }
  3627.  
  3628.  
  3629. 1;
  3630.  
  3631. ########################################################################
  3632. ########################################################################
  3633. # POD
  3634. ########################################################################
  3635. ########################################################################
  3636.  
  3637. =head1 NAME
  3638.  
  3639. Date::Manip - date manipulation routines
  3640.  
  3641. =head1 SYNOPSIS
  3642.  
  3643.  use Date::Manip;
  3644.  
  3645.  $date=&ParseDate(\@args)
  3646.  $date=&ParseDate($string)
  3647.  $date=&ParseDate(\$string)
  3648.  
  3649.  @date=&UnixDate($date,@format)
  3650.  $date=&UnixDate($date,@format)
  3651.  
  3652.  $delta=&ParseDateDelta(\@args)
  3653.  $delta=&ParseDateDelta($string)
  3654.  $delta=&ParseDateDelta(\$string)
  3655.  
  3656.  $d=&DateCalc($d1,$d2,$errref,$del)
  3657.  
  3658.  $date=&Date_SetTime($date,$hr,$min,$sec)
  3659.  $date=&Date_SetTime($date,$time)
  3660.  
  3661.  $date=&Date_GetPrev($date,$dow,$today,$hr,$min,$sec)
  3662.  $date=&Date_GetPrev($date,$dow,$today,$time)
  3663.  
  3664.  $date=&Date_GetNext($date,$dow,$today,$hr,$min,$sec)
  3665.  $date=&Date_GetNext($date,$dow,$today,$time)
  3666.  
  3667.  &Date_Init()
  3668.  &Date_Init("VAR=VAL",...)
  3669.  
  3670.  $version=&DateManipVersion
  3671.  
  3672.  $flag=&Date_IsWorkDay($date [,$flag]);
  3673.  
  3674.  $date=&Date_NextWorkDay($date,$off [,$time]);
  3675.  $date=&Date_PrevWorkDay($date,$off [,$time]);
  3676.  
  3677. The following routines are used by the above routines (though they can
  3678. also be called directly).  Make sure that $y is entered as the full 4
  3679. digit year (it will die if a 2 digit years is entered).  Most (if not
  3680. all) of the information below can be gotten from UnixDate which is really
  3681. the way I intended it to be gotten.
  3682.  
  3683.  $day=&Date_DayOfWeek($m,$d,$y)
  3684.  $secs=&Date_SecsSince1970($m,$d,$y,$h,$mn,$s)
  3685.  $secs=&Date_SecsSince1970GMT($m,$d,$y,$h,$mn,$s)
  3686.  $days=&Date_DaysSince999($m,$d,$y)
  3687.  $day=&Date_DayOfYear($m,$d,$y)
  3688.  $days=&Date_DaysInYear($y)
  3689.  $wkno=&Date_WeekOfYear($m,$d,$y,$first)
  3690.  $flag=&Date_LeapYear($y)
  3691.  $day=&Date_DaySuffix($d)
  3692.  $tz=&Date_TimeZone()
  3693.  
  3694. =head1 DESCRIPTION
  3695.  
  3696. This is a set of routines designed to make any common date/time
  3697. manipulation easy to do.  Operations such as comparing two times,
  3698. calculating a time a given amount of time from another, or parsing
  3699. international times are all easily done.
  3700.  
  3701. Date::Manip deals only with the Gregorian calendar (the one currently in
  3702. use).  The Julian calendar defined leap years as every 4th year.  The
  3703. Gregorian calendar improved this by making every 100th year NOT a leap
  3704. year, unless it was also the 400th year.  The Gregorian calendar has been
  3705. extrapolated back to the year 1000 AD and forward to the year 9999 AD.
  3706. Note that in historical context, the Julian calendar was in use until 1582
  3707. when the Gregorian calendar was adopted by the Catholic church.  Protestant
  3708. countries did not accept it until later; Germany and Netherlands in 1698,
  3709. British Empire in 1752, Russia in 1918.  Note that the Gregorian calendar
  3710. is itself imperfect.  Each year is on average 26 seconds too long, which
  3711. means that every 3,323 years, a day should be removed from the calendar.
  3712. No attempt is made to correct for that.
  3713.  
  3714. Date::Manip is therefore not equipped to truly deal with historacle dates,
  3715. but should be able to perform (virtually) any operation dealing with a
  3716. modern time and date.
  3717.  
  3718. Among other things, Date::Manip allow you to:
  3719.  
  3720. 1.  Enter a date and be able to choose any format conveniant
  3721.  
  3722. 2.  Compare two dates, entered in widely different formats to determine
  3723.     which is earlier
  3724.  
  3725. 3.  Extract any information you want from ANY date using a format string
  3726.     similar to the Unix date command
  3727.  
  3728. 4.  Determine the amount of time between two dates
  3729.  
  3730. 5.  Add a time offset to a date to get a second date (i.e. determine the
  3731.     date 132 days ago or 2 years and 3 months after Jan 2, 1992)
  3732.  
  3733. 6.  Work with dates with dates using international formats (foreign month
  3734.     names, 12-10-95 referring to October rather than December, etc.).
  3735.  
  3736. Each of these tasks is trivial (one or two lines at most) with this package.
  3737.  
  3738. Although the word date is used extensively here, it is actually somewhat
  3739. misleading.  Date::Manip works with the full date AND time (year, month,
  3740. day, hour, minute, second).
  3741.  
  3742. In the documentation below, US formats are used, but in most (if not all)
  3743. cases, a non-English equivalent will work equally well.
  3744.  
  3745. =head1 EXAMPLES
  3746.  
  3747. 1.  Parsing a date from any conveniant format
  3748.  
  3749.   $date=&ParseDate("today");
  3750.   $date=&ParseDate("1st thursday in June 1992");
  3751.   $date=&ParseDate("05-10-93");
  3752.   $date=&ParseDate("12:30 Dec 12th 1880");
  3753.   $date=&ParseDate("8:00pm december tenth");
  3754.   if (! $date) {
  3755.     # Error in the date
  3756.   }
  3757.  
  3758. 2.  Compare two dates
  3759.  
  3760.   $date1=&ParseDate($string1);
  3761.   $date2=&ParseDate($string2);
  3762.   if ($date1 lt $date2) {
  3763.     # date1 is earlier
  3764.   } else {
  3765.     # date2 is earlier (or the two dates are identical)
  3766.   }
  3767.  
  3768. 3.  Extract information from a date.
  3769.  
  3770.   print &UnixDate("today","The time is now %T on %b %e, %Y.");
  3771.   =>  "The time is now 13:24:08 on Feb  3, 1996."
  3772.  
  3773. 4.  The amount of time between two dates.
  3774.  
  3775.   $date1=&ParseDate($string1);
  3776.   $date2=&ParseDate($string2);
  3777.   $delta=&DateCalc($date1,$date2,\$err);
  3778.   => 0:0:DD:HH:MM:SS   the days, hours, minutes, and seconds between the two
  3779.   $delta=&DateCalc($date1,$date2,\$err,1);
  3780.   => YY:MM:DD:HH:MM:SS  the years, months, etc. between the two
  3781.  
  3782.   Read the documentation below for an explanation of the difference.
  3783.  
  3784. 5.  To determine a date a given offset from another.
  3785.  
  3786.   $date=&DateCalc("today","+ 3hours 12minutes 6 seconds",\$err);
  3787.   $date=&DateCalc("12 hours ago","12:30 6Jan90",\$err);
  3788.  
  3789.   It even works with business days:
  3790.  
  3791.   $date=&DateCalc("today","+ 3 business days",\$err);
  3792.  
  3793. 6.  To work with dates in another language.
  3794.  
  3795.   &Date_Init("Language=French","DateFormat=non-US");
  3796.   $date=&ParseDate("1er decembre 1990");
  3797.  
  3798. NOTE: Some date forms do not work as well in languages other than English,
  3799. but this is not because DateManip is incapable of doing so (almost nothing
  3800. in this module is language dependent).  It is simply that I do not have the
  3801. correct translation available for some words.  If there is a date form that
  3802. works in English but does not work in a language you need, let me know and
  3803. if you can provide me the translation, I will fix DateManip.
  3804.  
  3805. =head1 ROUTINES
  3806.  
  3807. =over 4
  3808.  
  3809. =item ParseDate
  3810.  
  3811.  $date=&ParseDate(\@args)
  3812.  $date=&ParseDate($string)
  3813.  $date=&ParseDate(\$string)
  3814.  
  3815. This takes an array or a string containing a date and parses it.  When the
  3816. date is included as an array (for example, the arguments to a program) the
  3817. array should contain a valid date in the first one or more elements
  3818. (elements after a valid date are ignored).  Elements containing a valid
  3819. date are shifted from the array.  The largest possible number of elements
  3820. which can be correctly interpreted as a valid date are always used.  If a
  3821. string is entered rather than an array, that string is tested for a valid
  3822. date.  The string is unmodified, even if passed in by reference.
  3823.  
  3824. A date actually includes 2 parts: date and time.  A time must include
  3825. hours and minutes and can optionally include seconds, fractional seconds,
  3826. an am/pm type string, and a timezone.  For example:
  3827.  
  3828.      HH:MN  [Zone]
  3829.      HH:MN:SS  [Zone]
  3830.      HH:MN am  [Zone]
  3831.      HH:MN:SS am  [Zone]
  3832.      HH:MN:SS:SSSS  [Zone]
  3833.      HH:MN:SS.SSSS am [Zone]
  3834.  
  3835. Hours can be written using 1 or 2 digits when the time follows the date and
  3836. is separated from the date with spaces or some other separator.  Any time
  3837. there is no space separating the time from a date and the part of the
  3838. date immediately preceding the hour is a digit, 2 digits must be used
  3839. for the hours.
  3840.  
  3841. Fractional seconds are also supported in parsing but the fractional part is
  3842. discarded.
  3843.  
  3844. Timezones always appear after the time and must be separated from all other
  3845. parts of the time/date by spaces.  For now, only rudimentary timezone
  3846. handling is done.  At the time the date is parsed, it is converted to a
  3847. specific time zone (which defaults to whatever time zone you are in, but
  3848. this can be overridden using the Date_Init routine described below).  After
  3849. that, the time zone is never used.  Once converted, information about the
  3850. time zone is no longer stored or used.
  3851.  
  3852. See the section below on TIMEZONEs for a list of all defined timezone
  3853. names.
  3854.  
  3855. Spaces in the date are almost always optional when there is absolutely
  3856. no ambiguity if they are not present.  Years can be entered as 2 or 4 digits,
  3857. days and months as 1 or 2 digits.  Both days and months must include 2 digits
  3858. whenver they are immediately adjacent to another part of the date or time
  3859. Valid formats for a full date and time (and examples of how Dec 10, 1965 at
  3860. 9:00 pm might appear) are:
  3861.   DateTime
  3862.      Date=YYMMDD             1965121021:00:00
  3863.                              65121021:00
  3864.  
  3865.   Date Time
  3866.   Date%Time
  3867.     Date=mm%dd, mm%dd%YY     12/10/65 21:00
  3868.                              12 10 1965 9:00pm
  3869.     Date=mmm%dd, mmm%dd%YY   December-10-65-9:00:00pm
  3870.     Date=dd%mmm, dd%mmm%YY   10/December/65 9:00:00pm
  3871.  
  3872.   Date Time
  3873.     Date=mmmdd, mmmdd YY,    Dec10 65 9:00:00 pm
  3874.          mmmDDYY, mmm DDYY   December 10 1965 9:00pm
  3875.  
  3876.     Date=ddmmm, ddmmm YY, ddmmmYY, dd mmmYY
  3877.                              10Dec65 9:00:00 pm     10 December 1965 9:00pm
  3878.  
  3879.   TimeDate
  3880.   Time Date
  3881.   Time%Date
  3882.     Date=mm%dd, mm%dd%YY     9:00pm 12.10.65      21:00 12/10/1965
  3883.     Date=mmm%dd, mmm%dd%YY   9:00pm December/10/65
  3884.     Date=dd%mmm, dd%mmm%YY   9:00pm 10-December-65  21:00/10/Dec/65
  3885.  
  3886.   TimeDate
  3887.   Time Date
  3888.     Date=mmmdd, mmmdd YY, mmmDDYY
  3889.                              21:00:00DeCeMbEr10
  3890.     Date=ddmmm, ddmmm YY, ddmmmYY, dd mmmYY
  3891.                              21:00 10Dec95
  3892.  
  3893. Miscellaneous other allowed formats are:
  3894.   which dofw in mmm [at time]
  3895.   which dofw in mmm YY [at time]    "first sunday in june 1996 at 14:00"
  3896.  
  3897.   dofw week num [in YY] [at time]   "sunday week 22 in 1995"
  3898.   which dofw [in YY] [at time]      "22nd sunday in 1996 at noon"
  3899.   dofw which week [in YY] [at time] "sunday 22nd week in 1996"
  3900.   next/last dofw [at time]          "next friday at noon"
  3901.   in num weeks [at time]            "in 3 weeks at 12:00"
  3902.   num weeks ago [at time]           "3 weeks ago"
  3903.   dofw in num week [at time]        "Friday in 2 weeks"
  3904.   in num weeks on dofw [at time]    "in 2 weeks on friday"
  3905.   dofw num week ago [at time]       "Friday 2 weeks ago"
  3906.   num week ago dofw [at time]       "2 weeks ago friday"
  3907.  
  3908. In addition, the following strings are recognized:
  3909.   today
  3910.   now       (synonym for today)
  3911.   yesterday (exactly 24 hours before now)
  3912.   tomorrow  (exactly 24 hours from now)
  3913.   noon      (12:00:00)
  3914.   midnight  (00:00:00)
  3915.  
  3916.  %       One of the valid date separators: - . / or whitespace (the same
  3917.          character must be used for all occurences of a single date)
  3918.          example: mm%dd%YY works for 1-1-95, 1 1 95, or 1/1/95
  3919.  YY      year in 2 or 4 digit format
  3920.  MM      two digit month (01 to 12)
  3921.  mm      one or two digit month (1 to 12 or 01 to 12)
  3922.  mmm     month name or 3 character abbreviation
  3923.  DD      two digit day (01 to 31)
  3924.  dd      one or two digit day (1 to 31 or 01 to 31)
  3925.  HH      one or two digit hour in 12 or 24 hour mode (0 to 23 or 00 to 23)
  3926.  MN      two digit minutes (00 to 59)
  3927.  SS      two digit seconds (00 to 59)
  3928.  which   one of the strings (first-fifth, 1st-5th, or last)
  3929.  dofw    either the 3 character abbreviation or full name of a day of
  3930.          the week
  3931.  
  3932. Some things to note:
  3933.  
  3934. All strings are case insensitive.  "December" and "DEceMBer" both work.
  3935.  
  3936. When a part of the date is not given, defaults are used: year defaults
  3937. to current year; hours, minutes, seconds to 00.
  3938.  
  3939. In the above, the mm%dd formats can be switched to dd%mm by calling
  3940. Date_Init and telling it to use a non-US date format.
  3941.  
  3942. All "Date Time" and "DateTime" type formats allow the word "at" in them
  3943. (i.e.  Jan 12 at 12:00) (and at can replace the space).  So the following
  3944. are both acceptable: "Jan 12at12:00" and "Jan 12 at 12:00".
  3945.  
  3946. A time is usually entered in 24 hour mode.  It can be followed by "am" or
  3947. "pm" to force it to be read in in 12 hour mode.
  3948.  
  3949. The year may be entered as 2 or 4 digits.  If entered as 2 digits, it is
  3950. taken to be the year in the range CurrYear-89 to CurrYear+10.  So, if the
  3951. current year is 1996, the range is [1907 to 2006] so entering the year 00
  3952. refers to 2000, 05 to 2005, but 07 refers to 1907.  Use 4 digit years to
  3953. avoid confusion!
  3954.  
  3955. Any number of spaces or tabs can be used anyhere whitespace is appropriate.
  3956.  
  3957. Dates are always checked to make sure they are valid.
  3958.  
  3959. In all of the formats, the day of week ("Friday") can be entered anywhere
  3960. in the date and it will be checked for accuracy.  In other words,
  3961.   "Tue Jul 16 1996 13:17:00"
  3962. will work but
  3963.   "Jul 16 1996 Wednesday 13:17:00"
  3964. will not (because Jul 16, 1996 is Tuesday, not Wednesday).  Note that
  3965. depending on where the weekday comes, it may give unexpected results when
  3966. used in array context.  For example, the date ("Jun","25","Sun","1990")
  3967. would return June 25 of the current year since Jun 25, 1990 is not Sunday.
  3968.  
  3969. The times "12:00 am", "12:00 pm", and "midnight" are not well defined.  For
  3970. good or bad, I use the following convention in Date::Manip:
  3971.   midnight = 12:00am = 00:00:00
  3972.   noon     = 12:00pm = 12:00:00
  3973. and the day goes from 00:00:00 to 23:59:59.  In otherwords, midnight is the
  3974. beginning of a day rather than the end of one.  At midnight on July 5, July
  3975. 5 has just begun.  The time 24:00:00 is NOT allowed.
  3976.  
  3977. The format of the date returned is YYYYMMDDHH:MM:SS.  The advantage of this
  3978. time format is that two times can be compared using simple string comparisons
  3979. to find out which is later.  Also, it is readily understood by a human.
  3980. Alternate forms can be used if that is more conveniant.  See Date_Init below
  3981. and the config variable Internal.
  3982.  
  3983. =item UnixDate
  3984.  
  3985.  @date=&UnixDate($date,@format)
  3986.  $date=&UnixDate($date,@format)
  3987.  
  3988. This takes a date and a list of strings containing formats roughly
  3989. identical to the format strings used by the UNIX date(1) command.  Each
  3990. format is parsed and an array of strings corresponding to each format is
  3991. returned.
  3992.  
  3993. $date must be of the form produced by &ParseDate.
  3994.  
  3995. The format options are:
  3996.  
  3997.  Year
  3998.      %y     year                     - 00 to 99
  3999.      %Y     year                     - 0001 to 9999
  4000.  Month, Week
  4001.      %m     month of year            - 01 to 12
  4002.      %f     month of year            - " 1" to "12"
  4003.      %b,%h  month abbreviation       - Jan to Dec
  4004.      %B     month name               - January to December
  4005.      %U     week of year, Sunday
  4006.             as first day of week     - 00 to 53
  4007.      %W     week of year, Monday
  4008.             as first day of week     - 00 to 53
  4009.  Day
  4010.      %j     day of the year          - 001 to 366
  4011.      %d     day of month             - 01 to 31
  4012.  
  4013.      %e     day of month             - " 1" to "31"
  4014.      %v     weekday abbreviation     - " S"," M"," T"," W","Th"," F","Sa"
  4015.      %a     weekday abbreviation     - Sun to Sat
  4016.      %A     weekday name             - Sunday to Saturday
  4017.      %w     day of week              - 0 (Sunday) to 6
  4018.      %E     day of month with suffix - 1st, 2nd, 3rd...
  4019.  Hour
  4020.      %H     hour                     - 00 to 23
  4021.      %k     hour                     - " 0" to "23"
  4022.      %i     hour                     - " 1" to "12"
  4023.      %I     hour                     - 01 to 12
  4024.      %p     AM or PM
  4025.  Minute, Second, Timezone
  4026.      %M     minute                   - 00 to 59
  4027.      %S     second                   - 00 to 59
  4028.      %s     seconds from Jan 1, 1970 GMT
  4029.                                      - negative if before 1/1/1970
  4030.      %o     seconds from Jan 1, 1970 in the current time zone
  4031.      %z,%Z  timezone (3 characters)  - "EDT"
  4032.  Date, Time
  4033.      %c     %a %b %e %H:%M:%S %Y     - Fri Apr 28 17:23:15 1995
  4034.      %C,%u  %a %b %e %H:%M:%S %z %Y  - Fri Apr 28 17:25:57 EDT 1995
  4035.      %g     %a, %d %b %Y %H:%M:%S %z - Fri, 28 Apr 1995 17:23:15 EDT
  4036.      %D,%x  %m/%d/%y                 - 04/28/95
  4037.      %l     date in ls(1) format
  4038.               %b %e $H:$M            - Apr 28 17:23  (if within 6 months)
  4039.               %b %e  %Y              - Apr 28  1993  (otherwise)
  4040.      %r     %I:%M:%S %p              - 05:39:55 PM
  4041.      %R     %H:%M                    - 17:40
  4042.      %T,%X  %H:%M:%S                 - 17:40:58
  4043.      %V     %m%d%H%M%y               - 0428174095
  4044.      %Q     %Y%m%d                   - 19961025
  4045.      %q     %Y%m%d%H%M%S             - 19961025174058
  4046.      %P     %Y%m%d%H%M%S             - 1996102517:40:58
  4047.      %F     %A, %B %e, %Y            - Sunday, January  1, 1996
  4048.  Other formats
  4049.      %n     insert a newline character
  4050.      %t     insert a tab character
  4051.      %%     insert a `%' character
  4052.      %+     insert a `+' character
  4053.  The following formats are currently unused but may be used in the future:
  4054.      GJKLNO 1234567890 !@#$^&*()_|-=\`[];',./~{}:<>?
  4055.  They currently insert the character following the %, but may (and probably
  4056.  will) change in the future as new formats are requested.
  4057.  
  4058. If a lone percent is the final character in a format, it is ignored.
  4059.  
  4060. Note that the ls format applies to date within the past OR future 6 months!
  4061.  
  4062. Note that the %s format was introduced in version 5.07.  Prior to that,
  4063. %s referred to the seconds since 1/1/70.  This was moved to %o in 5.07.
  4064.  
  4065. This routine is loosely based on date.pl (version 3.2) by Terry McGonigal.
  4066. No code was used, but most of his formats were.
  4067.  
  4068. =item ParseDateDelta
  4069.  
  4070.  $delta=&ParseDateDelta(\@args)
  4071.  $delta=&ParseDateDelta($string)
  4072.  $delta=&ParseDateDelta(\$string)
  4073.  
  4074. This takes an array and shifts a valid delta date (an amount of time)
  4075. from the array.  Recognized deltas are of the form:
  4076.   +Yy +Mm +Ww +Dd +Hh +MNmn +Ss
  4077.       examples:
  4078.          +4 hours +3mn -2second
  4079.          + 4 hr 3 minutes -2
  4080.          4 hour + 3 min -2 s
  4081.   +Y:+M:+D:+H:+MN:+S
  4082.       examples:
  4083.          0:0:0:4:3:-2
  4084.          +4:3:-2
  4085.   mixed format
  4086.       examples:
  4087.          4 hour 3:-2
  4088.  
  4089. A field in the format +Yy is a sign, a number, and a string specifying
  4090. the type of field.  The sign is "+", "-", or absent (defaults to the
  4091. next larger element).  The valid strings specifying the field type
  4092. are:
  4093.    y:  y, yr, year, years
  4094.    m:  m, mon, month, months
  4095.    w:  w, wk, ws, wks, week, weeks
  4096.    d:  d, day, days
  4097.    h:  h, hr, hour, hours
  4098.    mn: mn, min, minute, minutes
  4099.    s:  s, sec, second, seconds
  4100.  
  4101. Also, the "s" string may be omitted.  The sign, number, and string may
  4102. all be separated from each other by any number of whitespaces.
  4103.  
  4104. In the date, all fields must be given in the order: y m d h mn s.  Any
  4105. number of them may be omitted provided the rest remain in the correct
  4106. order.  In the 2nd (colon) format, from 2 to 6 of the fields may be given.
  4107. For example +D:+H:+MN:+S may be given to specify only four of the fields.
  4108. In any case, both the MN and S field may be present.  No spaces may be
  4109. present in the colon format.
  4110.  
  4111. Deltas may also be given as a combination of the two formats.  For example,
  4112. the following is valid: +Yy +D:+H:+MN:+S.  Again, all fields must be given
  4113. in the correct order.
  4114.  
  4115. The word "in" may be prepended to the delta ("in 5 years") and the word
  4116. "ago" may be appended ("6 months ago").  The "in" is completely ignored.
  4117. The "ago" has the affect of reversing all signs that appear in front of the
  4118. components of the delta.  I.e. "-12 yr 6 mon ago" is identical to "+12yr
  4119. +6mon" (don't forget that there is an impled minus sign in front of the 6
  4120. because when no sign is explicitely given, it carries the previously
  4121. entered sign).
  4122.  
  4123. The "week" field does not occur in the colon separated delta.  The reason
  4124. for this is to maintain backward compatibility with previous versions of
  4125. Date::Manip.  Parsing of weeks was only added in version 5.07.  At this
  4126. point, rather than change the internal format of the delta to
  4127. "Y:M:W:D:H:MN:S", I simply added the weeks to the days (1 week = 7 days) in
  4128. order to be compatible with previous versions.  So, they are not parsed in
  4129. the colon format, only in the first format.  Hopefully, this will not
  4130. result in too much confusion.
  4131.  
  4132. One thing is worth noting.  The year/month and day/hour/min/sec parts are
  4133. returned in a "normalized" form.  That is, the signs are adjusted so as to
  4134. be all positive or all negative.  For example, "+ 2 day - 2hour" does not
  4135. return "0:0:2:-2:0:0".  It returns "+0:0:1:22:0:0" (1 day 22 hours which is
  4136. equivalent).  I find (and I think most others agree) that this is a more
  4137. useful form.
  4138.  
  4139. Since the year/month and day/hour/min/sec parts must be normalized
  4140. separately there is the possibility that the sign of the two parts will be
  4141. different.  So, the delta "+ 2years -10 months - 2 days + 2 hours" produces
  4142. the delta "+1:2:-1:22:0:0".
  4143.  
  4144. For backwards compatibility, it is possible to include a sign for all
  4145. elements that is output.  See the configuration variable DeltaSigns below.
  4146.  
  4147. =item DateCalc
  4148.  
  4149.  $d=&DateCalc($d1,$d2,\$err [,$mode])
  4150.  
  4151. This takes two dates, deltas, or one of each and performs the appropriate
  4152. calculation with them.  Dates must be in the format given by &ParseDate and
  4153. or must be a string which can be parsed as a date.  Deltas must be in the
  4154. format returned by &ParseDateDelta or must be a string that can be parsed
  4155. as a delta.  Two deltas add together to form a third delta.  A date and a
  4156. delta returns a 2nd date.  Two dates return a delta (the difference between
  4157. the two dates).
  4158.  
  4159. Note that in many cases, it is somewhat ambiguous what the delta actually
  4160. refers to.  Although it is ALWAYS known how many months in a year, hours in
  4161. a day, etc., it is NOT known how many days form a month.  As a result, the
  4162. part of the delta containing month/year and the part with sec/min/hr/day
  4163. must be treated separately.  For example, "Mar 31, 12:00:00" plus a delta
  4164. of 1month 2days would yield "May 2 12:00:00".  The year/month is first
  4165. handled while keeping the same date.  Mar 31 plus one month is Apr 31 (but
  4166. since Apr only has 30 days, it becomes Apr 30).  Apr 30 + 2 days is May 2.
  4167. As a result, in the case where two dates are entered, the resulting delta
  4168. can take on two different forms.  By default ($mode=0), an absolutely
  4169. correct delta (ignoring daylight savings time) is returned in days, hours,
  4170. minutes, and seconds.
  4171.  
  4172. If $mode is 1, the math is done using an approximate mode where a delta is
  4173. returned using years and months as well.  The year and month part is
  4174. calculated first followed by the rest.  For example, the two dates "Mar 12
  4175. 1995" and "Apr 13 1995" would have an exact delta of "31 days" but in the
  4176. approximate mode, it would be returned as "1 month 1 day".  Also, "Mar 31"
  4177. and "Apr 30" would have deltas of "30 days" or "1 month" (since Apr 31
  4178. doesn't exist, it drops down to Apr 30).  Approximate mode is a more human
  4179. way of looking at things (you'd say 1 month and 2 days more often then 33
  4180. days), but it is less meaningful in terms of absolute time.  In approximate
  4181. mode $d1 and $d2 must be dates.  If either or both is a delta, the
  4182. calculation is done in exact mode.
  4183.  
  4184. If $mode is 2, a business mode is used.  That is, the calculation is done
  4185. using business days, ignoring holidays, weekends, etc.  In order to
  4186. correctly use this mode, a config file must exist which contains the
  4187. section defining holidays (see documentation on the config file below).
  4188. The config file can also define the work week and the hours of the work
  4189. day, so it is possible to have different config files for different
  4190. businesses.
  4191.  
  4192. For example, if a config file defines the workday as 08:00 to 18:00, a
  4193. workweek consisting of Mon-Sat, and the standard (American) holidays, then
  4194. from Tuesday at 12:00 to the following Monday at 14:00 is 5 days and 2
  4195. hours.  If the "end" of the day is reached in a calculation, it
  4196. autmoatically switches to the next day.  So, Tuesday at 12:00 plus 6 hours
  4197. is Wednesday at 08:00 (provided Wed is not a holiday).  Also, a date that
  4198. is not during a workday automatically becomes the start of the next
  4199. workday.  So, Sunday 12:00 and Monday at 03:00 both automatically becomes
  4200. Monday at 08:00 (provided Monday is not a holiday).  In business mode, any
  4201. combination of date and delta may be entered, but a delta should not
  4202. contain a year or month field (weeks are fine though).
  4203.  
  4204. See below for some additional comments about business mode calculations.
  4205.  
  4206. Any other non-nil value of $mode is treated as $mode=1 (approximate mode).
  4207.  
  4208. The mode can be automatically set in the dates/deltas passed by including a
  4209. key word somewhere in it.  For example, in English, if the word
  4210. "approximately" is found in either of the date/delta arguments, approximate
  4211. mode is forced.  Likewise, if the word "business" or "exactly" appears,
  4212. business/exact mode is forced (and $mode is ignored).  So, the two
  4213. following are equivalent:
  4214.  
  4215.    $date=&DateCalc("today","+ 2 business days",\$err);
  4216.    $date=&DateCalc("today","+ 2 days",\$err,2);
  4217.  
  4218. Note that if the keyword method is used instead of passing in $mode, it is
  4219. important that the keyword actually appear in the argument passed in to
  4220. DateCalc.  The following will NOT work:
  4221.  
  4222.    $delta=&ParseDateDelta("+ 2 business days");
  4223.    $today=&ParseDate("today");
  4224.    $date=&DateCalc($today,$delta,\$err);
  4225.  
  4226. because the mode keyword is removed from a date/delta by the parse routines,
  4227. and the mode is reset each time a parse routine is called.  Since DateCalc
  4228. parses both of its arguments, whatever mode was previously set is ignored.
  4229.  
  4230. $err is set to:
  4231.    1 is returned if $d1 is not a delta or date
  4232.    2 is returned if $d2 is not a delta or date
  4233.    3 is returned if the date is outside the years 1000 to 9999
  4234.  
  4235. Nothing is returned if an error occurs.
  4236.  
  4237. When a delta is returned, the signs such that it is strictly positive or
  4238. strictly negative ("1 day - 2 hours" would never be returned for example).
  4239. The only time when this cannot be enforced is when two deltas with a
  4240. year/month component are entered.  In this case, only the signs on the
  4241. day/hour/min/sec part are standardized.
  4242.  
  4243. =item Date_SetTime
  4244.  
  4245.  $date=&Date_SetTime($date,$hr,$min,$sec)
  4246.  $date=&Date_SetTime($date,$time)
  4247.  
  4248. This takes a date sets the time in that date.  For example, to get
  4249. the time for 7:30 tomorrow, use the lines:
  4250.  
  4251.    $date=&ParseDate("tomorrow")
  4252.    $date=&Date_SetTime($date,"7:30")
  4253.  
  4254. =item Date_GetPrev
  4255.  
  4256.  $date=&Date_GetPrev($date,$dow, $curr [,$hr,$min,$sec])
  4257.  $date=&Date_GetPrev($date,$dow, $curr [,$time])
  4258.  $date=&Date_GetPrev($date,undef,$curr,$hr,$min,$sec)
  4259.  $date=&Date_GetPrev($date,undef,$curr,$time)
  4260.  
  4261. If $dow is defined, it is a day of week (a string such as "Fri" or a number
  4262. from 0 to 6).  The date of the previous $dow is returned.  If $date falls
  4263. on this day of week, the date returned will be $date (if $curr is non-zero)
  4264. or a week earlier (if $curr is 0).  If a time is passed in (either as
  4265. separate hours, minutes, seconds or as a time in HH:MM:SS or HH:MM format),
  4266. the time on this date is set to it.  The following examples should
  4267. illustrate the use of Date_GetPrev:
  4268.  
  4269.     date                   dow    curr  time            returns
  4270.     Fri Nov 22 18:15:00    Thu    0     12:30           Thu Nov 21 12:30:00
  4271.     Fri Nov 22 18:15:00    Fri    0     12:30           Fri Nov 15 12:30:00
  4272.     Fri Nov 22 18:15:00    Fri    1     12:30           Fri Nov 22 12:30:00
  4273.  
  4274. If $dow is undefined, then a time must be entered, and the date returned is
  4275. the previous occurence of this time.  If $curr is non-zero, the current
  4276. time is returned if it matches the criteria passed in.  In other words, the
  4277. time returned is the last time that a digital clock (in 24 hour mode) would
  4278. have displayed the time you pass in.  If you define hours, minutes and
  4279. seconds default to 0 and you might jump back as much as an entire day.  If
  4280. hours are undefined, you are looking for the last time the minutes/seconds
  4281. appeared on the digital clock, so at most, the time will jump back one hour.
  4282.  
  4283.     date               curr  hr     min    sec      returns
  4284.     Nov 22 18:15:00    0/1   18     undef  undef    Nov 22 18:00:00
  4285.     Nov 22 18:15:00    0/1   18     30     0        Nov 21 18:30:00
  4286.     Nov 22 18:15:00    0     18     15     undef    Nov 21 18:15:00
  4287.     Nov 22 18:15:00    1     18     15     undef    Nov 22 18:15:00
  4288.     Nov 22 18:15:00    0     undef  15     undef    Nov 22 17:15:00
  4289.     Nov 22 18:15:00    1     undef  15     undef    Nov 22 18:15:00
  4290.  
  4291.  
  4292. =item Date_GetNext
  4293.  
  4294.  $date=&Date_GetNext($date,$dow, $curr [,$hr,$min,$sec])
  4295.  $date=&Date_GetNext($date,$dow, $curr [,$time])
  4296.  $date=&Date_GetNext($date,undef,$curr,$hr,$min,$sec)
  4297.  $date=&Date_GetNext($date,undef,$curr,$time)
  4298.  
  4299. Similar to Date_GetPrev.
  4300.  
  4301. =item Date_DayOfWeek
  4302.  
  4303.  $day=&Date_DayOfWeek($m,$d,$y);
  4304.  
  4305. Returns the day of the week (0 for Sunday, 6 for Saturday).  Dec 31, 0999
  4306. was Tuesday.
  4307.  
  4308. =item Date_SecsSince1970
  4309.  
  4310.  $secs=&Date_SecsSince1970($m,$d,$y,$h,$mn,$s)
  4311.  
  4312. Returns the number of seconds since Jan 1, 1970 00:00 (negative if date is
  4313. earlier).
  4314.  
  4315. =item Date_SecsSince1970GMT
  4316.  
  4317.  $secs=&Date_SecsSince1970GMT($m,$d,$y,$h,$mn,$s)
  4318.  
  4319. Returns the number of seconds since Jan 1, 1970 00:00 GMT (negative if date
  4320. is earlier).  If CurrTZ is "IGNORE", the number will be identical to
  4321. Date_SecsSince1970 (i.e. the date given will be treated as being in GMT).
  4322.  
  4323. =item Date_DaysSince999
  4324.  
  4325.  $days=&Date_DaysSince999($m,$d,$y)
  4326.  
  4327. Returns the number of days since Dec 31, 0999.
  4328.  
  4329. =item Date_DayOfYear
  4330.  
  4331.  $day=&Date_DayOfYear($m,$d,$y);
  4332.  
  4333. Returns the day of the year (001 to 366)
  4334.  
  4335. =item Date_DaysInYear
  4336.  
  4337.  $days=&Date_DaysInYear($y);
  4338.  
  4339. Returns the number of days in the year (365 or 366)
  4340.  
  4341. =item Date_WeekOfYear
  4342.  
  4343.  $wkno=&Date_WeekOfYear($m,$d,$y,$first);
  4344.  
  4345. Figure out week number.  $first is the first day of the week which is
  4346. usually 0 (Sunday) or 1 (Monday), but could be any number between 0 and 6
  4347. in practice.
  4348.  
  4349. =item Date_LeapYear
  4350.  
  4351.  $flag=&Date_LeapYear($y);
  4352.  
  4353. Returns 1 if the argument is a leap year
  4354. Written by David Muir Sharnoff <muir@idiom.com>
  4355.  
  4356. =item Date_DaySuffix
  4357.  
  4358.  $day=&Date_DaySuffix($d);
  4359.  
  4360. Add `st', `nd', `rd', `th' to a date (ie 1st, 22nd, 29th).  Works for
  4361. international dates.
  4362.  
  4363. =item Date_TimeZone
  4364.  
  4365.  $tz=&Date_TimeZone
  4366.  
  4367. This returns a timezone.  It looks in the following places for a timezone
  4368. in the following order:
  4369.  
  4370.    $ENV{TZ}
  4371.    $main::TZ
  4372.    unix 'date' command
  4373.    /etc/TIMEZONE
  4374.  
  4375. If it's not found in any of those places, an error occurs:
  4376.  
  4377.    ERROR: Date::Manip unable to determine TimeZone.
  4378.  
  4379. Date_TimeZone is able to read zones of the format PST8PDT (see TIMEZONES
  4380. documentation below).
  4381.  
  4382. =item Date_ConvTZ
  4383.  
  4384.  $date=&Date_ConvTZ($date,$from)
  4385.  $date=&Date_ConvTZ($date,$from,$to)
  4386.  
  4387. This converts a date (which MUST be in the format returned by ParseDate)
  4388. from one timezone to another.  The behavior of Date_ConvTZ depends on
  4389. whether it is called with 2 or 3 arguments.
  4390.  
  4391. If it is called with 2 arguments, $date is assumed to be in timezone given
  4392. in $from and it is converted to the timzone specified by the config
  4393. variable ConvTZ.  If ConvTZ is set to "IGNORE", no conversion is done and
  4394. $date is returned unmodified (see documentation on ConvTZ below).  This
  4395. form is most often used internally by the Date::Manip module.  The 3
  4396. argument form is of more use to most users.
  4397.  
  4398. If Date_ConvTZ is called with 3 arguments, the config variable ConvTZ is
  4399. ignored and $date is given in the timezone $from and is converted to the
  4400. timzone $to.  If $from is not given, it defaults to the working timezone.
  4401. NOTE: As in all other cases, the $date returned from Date_ConvTZ has no
  4402. timezone information included as part of it, so calling UnixDate with the
  4403. "%z" format will return the timezone that Date::Manip is working in
  4404. (usually the local timezone).
  4405.  
  4406. Example:  To convert 2/2/96 noon PST to CST (regardless of what timezone
  4407. you are in, do the following:
  4408.  
  4409.  $date=&ParseDate("2/2/96 noon");
  4410.  $date=&Date_ConvTZ($date,"PST","CST");
  4411.  
  4412. Both timezones MUST be in one of the formst listed below in the section
  4413. TIMEZONES.
  4414.  
  4415. =item Date_Init
  4416.  
  4417.  $flag=&Date_Init();
  4418.  $flag=&Date_Init("VAR=VAL","VAR=VAL",...);
  4419.  
  4420. Normally, it is not necessary to explicitely call Date_Init.  The first
  4421. time any of the other routines are called, Date_Init will be called to set
  4422. everything up.  If for some reason you want to change the configuration of
  4423. Date::Manip, you can pass the appropriate string or strings into Date_Init
  4424. to reinitizize things.
  4425.  
  4426. The strings to pass in are of the form "VAR=VAL".  Any number may be
  4427. included and they can come in any order.  VAR may be any configuration
  4428. variable.  A list of all configuaration variables is given in the section
  4429. CUSTOMIZING DATE::MANIP below.  VAL is any allowed value for that variable.
  4430. For example, to switch from English to French and use non-US format (so
  4431. that 12/10 is Oct 12), do the following:
  4432.  
  4433.   &Date_Init("Language=French","DateFormat=nonUS");
  4434.  
  4435. Note that the usage of Date_Init changed with version 5.07.  The old
  4436. calling convention is allowed but is depreciated.
  4437.  
  4438. If you change timezones in the middle of using Date::Manip, comparing dates
  4439. from before the switch to dates from after the switch will produce incorrect
  4440. results.
  4441.  
  4442. =item Date_IsWorkDay
  4443.  
  4444.   $flag=&Date_IsWorkDay($date [,$flag]);
  4445.  
  4446. This returns 1 if $date is a work day.  If $flag is non-zero, the time is
  4447. checked to see if it falls within work hours.
  4448.  
  4449. =item Date_NextWorkDay
  4450.  
  4451.   $date=&Date_NextWorkDay($date,$off [,$time]);
  4452.  
  4453. Finds the day $off work days from now.  If $time is passed in, we must also
  4454. take into account the time of day.
  4455.  
  4456. If $time is not passed in, day 0 is today (if today is a workday) or the
  4457. next work day if it isn't.  In any case, the time of day is unaffected.
  4458.  
  4459. If $time is passed in, day 0 is now (if now is part of a workday) or the
  4460. start of the very next work day.
  4461.  
  4462. =item Date_PrevWorkDay
  4463.  
  4464.   $date=&Date_PrevWorkDay($date,$off [,$time]);
  4465.  
  4466. Similar to Date_NextWorkDay.
  4467.  
  4468. =item DateManipVersion
  4469.  
  4470.   $version=&DateManipVersion
  4471.  
  4472. Returns the version of Date::Manip.
  4473.  
  4474. =back
  4475.  
  4476. =head1 TIMEZONES
  4477.  
  4478. The following timezone names are currently understood (and can be used in
  4479. parsing dates).  These are zones defined in RFC 822.
  4480.  
  4481.     Universal:  GMT, UT
  4482.     US zones :  EST, EDT, CST, CDT, MST, MDT, PST, PDT
  4483.     Military :  A to Z (except J)
  4484.     Other    :  +HHMM or -HHMM
  4485.  
  4486. In addition, the following timezone abbreviations are also accepted.  In a
  4487. few cases, the same abbreviation is used for two different timezones (for
  4488. example, NST stands for Newfoundland Standare -0330 and North Sumatra +0630).
  4489. In these cases, only 1 of the two is available.  The one preceded by a "#"
  4490. sign is NOT available but is documented here for completeness.  This list of
  4491. zones comes from the Time::Zone module by Graham Barr, David Muir Sharnoff,
  4492. and Paul Foley.
  4493.  
  4494.       IDLW    -1200    International Date Line West
  4495.       NT      -1100    Nome
  4496.       HST     -1000    Hawaii Standard
  4497.       CAT     -1000    Central Alaska
  4498.       AHST    -1000    Alaska-Hawaii Standard
  4499.       YST     -0900    Yukon Standard
  4500.       HDT     -0900    Hawaii Daylight
  4501.       YDT     -0800    Yukon Daylight
  4502.       PST     -0800    Pacific Standard
  4503.       PDT     -0700    Pacific Daylight
  4504.       MST     -0700    Mountain Standard
  4505.       MDT     -0600    Mountain Daylight
  4506.       CST     -0600    Central Standard
  4507.       CDT     -0500    Central Daylight
  4508.       EST     -0500    Eastern Standard
  4509.       EDT     -0400    Eastern Daylight
  4510.       AST     -0400    Atlantic Standard
  4511.      #NST     -0330    Newfoundland Standard       nst=North Sumatra    +0630
  4512.       NFT     -0330    Newfoundland
  4513.      #GST     -0300    Greenland Standard          gst=Guam Standard    +1000
  4514.       BST     -0300    Brazil Standard             bst=British Summer   +0100
  4515.       ADT     -0300    Atlantic Daylight
  4516.       NDT     -0230    Newfoundland Daylight
  4517.       AT      -0200    Azores
  4518.       WAT     -0100    West Africa
  4519.       GMT     +0000    Greenwich Mean
  4520.       UT      +0000    Universal (Coordinated)
  4521.       UTC     +0000    Universal (Coordinated)
  4522.       WET     +0000    Western European
  4523.       CET     +0100    Central European
  4524.       FWT     +0100    French Winter
  4525.       MET     +0100    Middle European
  4526.       MEWT    +0100    Middle European Winter
  4527.       SWT     +0100    Swedish Winter
  4528.      #BST     +0100    British Summer              bst=Brazil standard  -0300
  4529.       EET     +0200    Eastern Europe, USSR Zone 1
  4530.       FST     +0200    French Summer
  4531.       MEST    +0200    Middle European Summer
  4532.       SST     +0200    Swedish Summer              sst=South Sumatra    +0700
  4533.       BT      +0300    Baghdad, USSR Zone 2
  4534.       IT      +0330    Iran
  4535.       ZP4     +0400    USSR Zone 3
  4536.       ZP5     +0500    USSR Zone 4
  4537.       IST     +0530    Indian Standard
  4538.       ZP6     +0600    USSR Zone 5
  4539.       NST     +0630    North Sumatra               nst=Newfoundland Std -0330
  4540.       WAST    +0700    West Australian Standard
  4541.      #SST     +0700    South Sumatra, USSR Zone 6  sst=Swedish Summer   +0200
  4542.       JT      +0730    Java (3pm in Cronusland!)
  4543.       CCT     +0800    China Coast, USSR Zone 7
  4544.       WADT    +0800    West Australian Daylight
  4545.       JST     +0900    Japan Standard, USSR Zone 8
  4546.       CAST    +0930    Central Australian Standard
  4547.       EAST    +1000    Eastern Australian Standard
  4548.       GST     +1000    Guam Standard, USSR Zone 9  gst=Greenland Std    -0300
  4549.       CADT    +1030    Central Australian Daylight
  4550.       EADT    +1100    Eastern Australian Daylight
  4551.       IDLE    +1200    International Date Line East
  4552.       NZST    +1200    New Zealand Standard
  4553.       NZT     +1200    New Zealand
  4554.       NZDT    +1300    New Zealand Daylight
  4555.  
  4556. Others can be added in the future upon request.
  4557.  
  4558. DateManip needs to be able to determine the local timezone.  It can do this
  4559. by certain things such as the TZ environment variable (see Date_TimeZone
  4560. documentation above) or useing the TZ config variable (described below).
  4561. In either case, the timezone can be of the form STD#DST (for example
  4562. EST5EDT).  Both the standard and daylight savings time abbreviations must
  4563. be in the table above in order for this to work.  Also, this form may NOT
  4564. be used when parsing a date as there is no way to determine whether the
  4565. date is in daylight saving time or not.  The following forms are also
  4566. available and are treated similar to the STD#DST forms:
  4567.  
  4568.       US/Pacific
  4569.       US/Mountain
  4570.       US/Central
  4571.       US/Eastern
  4572.  
  4573. =head1 BUSINESS MODE
  4574.  
  4575. Anyone using business mode is going to notice a few quirks about it which
  4576. should be explained.  When I designed business mode, I had in mind what UPS
  4577. tells me when they say 2 day delivery, or what the local business which
  4578. promises 1 business day turnaround really means.
  4579.  
  4580. If you do a business day calculation (with the workday set to 9:00-5:00),
  4581. you will get the following:
  4582.  
  4583.    Saturday at noon + 1 business day = Tuesday at 9:00
  4584.    Saturday at noon - 1 business day = Friday at 9:00
  4585.  
  4586. What does this mean?
  4587.  
  4588. We have a business that works 9-5 and they have a drop box so I can drop
  4589. things off over the weekend and they promise 1 business day turnaround.  If
  4590. I drop something off Friday night, Saturday, or Sunday, it doesn't matter.
  4591. They're going to get started on it Monday morning.  It'll be 1 business day
  4592. to finish the job, so the earliest I can expect it to be done is around
  4593. 17:00 Monday or 9:00 Tuesday morning.  Unfortunately, there is some
  4594. ambiguity as to what day 17:00 really falls on, similar to the ambiguity
  4595. that occurs when you ask what day midnight falls on.  Although it's not the
  4596. only answer, Date::Manip treats midnight as the beginning of a day rather
  4597. than the end of one.  In the same way, 17:00 is equivalent to 9:00 the next
  4598. day and any time the date calculations encounter 17:00, it automatically
  4599. switch to 9:00 the next day.  Although this introduces some quirks, I think
  4600. this is justified.  You just have to treat 9:00 as being ambiguous (in the
  4601. same way you treat midnight as being ambiguous).
  4602.  
  4603. Equivalently, if I want a job to be finished on Saturday (despite the fact
  4604. that I cannot pick it up since the business is closed), I have to drop it
  4605. off no later than Friday at 9:00.  That gives them a full business day to
  4606. finish it off.  Of course, I could just as easily drop it off at 17:00
  4607. Thursday, or any time between then and 9:00 Friday.  Again, it's a matter
  4608. of treating 9:00 as ambiguous.
  4609.  
  4610. So, in case the business date calculations ever produce results that you
  4611. find confusing, I believe the solution is to write a wrapper which,
  4612. whenever it sees a date with the time of exactly 9:00, it treats it
  4613. specially (depending on what you want.
  4614.  
  4615. So Saturday + 1 business day = Tuesday at 9:00 (which means anything
  4616. from Monday 17:00 to Tuesday 9:00), but Monday at 9:01 + 1 business
  4617. day = Tuesday at 9:01 which is exact.
  4618.  
  4619. If this is not exactly what you have in mind, don't use the DateCalc
  4620. routine.  You can probably get whatever behavior you want using the
  4621. routines Date_IsWorkDay, Date_NextWorkDay, and Date_PrevWorkDay described
  4622. above.
  4623.  
  4624. =head1 CUSTOMIZING DATE::MANIP
  4625.  
  4626. There are a number of variables which can be used to customize the way
  4627. Date::Manip behaves.  There are also several ways to set these variables.
  4628.  
  4629. At the top of the Manip.pm file, there is a section which contains all
  4630. customization variables.  These provide the default values.
  4631.  
  4632. These can be overridden in a global config file if one is present (this
  4633. file is optional).  If the GlobalCnf variable is set in the Manip.pm file,
  4634. it contains the full path to a config file.  If the file exists, it's
  4635. values will override those set in the Manip.pm file.  A sample config file
  4636. is included with the Date::Manip distribution.  Modify it as appropriate
  4637. and copy it to some appropriate directory and set the GlobalCnf variable
  4638. in the Manip.pm file.
  4639.  
  4640. Each user can have a personal config file which is of the same form as
  4641. the global config file.  The variables PersonalCnf and PersonalCnfPath
  4642. set the name and search path for the personal config file.
  4643.  
  4644. Finally, any variables passed in through Date_Init override all other
  4645. values.
  4646.  
  4647. A config file can be composed of several sections (though only 2 of them
  4648. are currently used).  The first section sets configuration varibles.  Lines
  4649. in this section are of the form:
  4650.  
  4651.    VARIABLE = VALUE
  4652.  
  4653. For example, to make the default language French, include the line:
  4654.  
  4655.    Language = French
  4656.  
  4657. Only variables described below may be used.  Blank lines and lines beginning
  4658. with a pound sign (#) are ignored.  All spaces are optional and strings are
  4659. case insensitive.
  4660.  
  4661. A line which starts with an asterix (*) designates a new section.  The only
  4662. section currently used is the Holiday section.  All lines are of the form:
  4663.  
  4664.    DATE = HOLIDAY
  4665.  
  4666. HOLIDAY is the name of the holiday (or it can be blank in which case the
  4667. day will still be treated as a holiday... for example the day after
  4668. Thanksgiving or Christmas is often a work holiday though neither are
  4669. named).
  4670.  
  4671. DATE is a string which can be parsed to give a valid date in any year.  It
  4672. can be of the form
  4673.  
  4674.    Date
  4675.    Date + Delta
  4676.    Date - Delta
  4677.  
  4678. A valid holiday section would be:
  4679.  
  4680.    *Holiday
  4681.  
  4682.    1/1                             = New Year's Day
  4683.    third Monday in Feb             = Presidents' Day
  4684.    fourth Thu in Nov               = Thanksgiving
  4685.  
  4686.    # The Friday after Thanksgiving is an unnamed holiday most places
  4687.    fourth Thu in Nov + 1 day       =
  4688.  
  4689. In a Date + Delta or Date - Delta string, you can use business mode by
  4690. including the appropriate string (see documentation on DateCalc) in the
  4691. Date or Delta.  So (in English), the first workday before Christmas could
  4692. be defined as:
  4693.  
  4694.    12/25 - 1 business day          =
  4695.  
  4696. All Date::Manip variables which can be used are described in the following
  4697. section.
  4698.  
  4699. =over 4
  4700.  
  4701. =item IgnoreGlobalCnf
  4702.  
  4703. If this variable is used (any value is ignored), the global config file
  4704. is not read.  It must be present in the initial call to Date_Init or the
  4705. global config file will be read.
  4706.  
  4707. =item EraseHolidays
  4708.  
  4709. If this variable is used (any value is ignored), the current list of
  4710. defined holidays is erased.  A new set will be set the next time a
  4711. config file is read in.
  4712.  
  4713. =item PersonalCnf
  4714.  
  4715. This variable can be passed into Date_Init to read a different personal
  4716. configuration file.  It can also be included in the global config file
  4717. to define where personal config files live.
  4718.  
  4719. =item PersonalCnfPath
  4720.  
  4721. Used in the same way as the PersonalCnf option.  You can use tilde (~)
  4722. expansions when defining the path.
  4723.  
  4724. =item Language
  4725.  
  4726. Date::Manip can be used to parse dates in many different languages.
  4727. Currently, it is configured to read English, Swedish, and French dates,
  4728. but others can be added easily.  Language is set to the language used to
  4729. parse dates.
  4730.  
  4731. =item DateFormat
  4732.  
  4733. Different countries look at the date 12/10/96 as Dec 10 or Oct 12.  In the
  4734. United States, the first is most common, but this certainly doesn't hold
  4735. true for other countries.  Setting DateFormat to "US" forces the first
  4736. behavior (Dec 10).  Setting DateFormat to anything else forces the second
  4737. behavior (Oct 12).
  4738.  
  4739. =item TZ
  4740.  
  4741. Date::Manip is able to understand some timezones (and others will be added
  4742. in the future).  At the very least, all zones defined in RFC 822 are
  4743. supported.  Currently supported zones are listed in the TIMEZONES section
  4744. above and all timezones should be entered as one of them.
  4745.  
  4746. Date::Manip must be able to determine the timezone the user is in.  It does
  4747. this by looking in the following places:
  4748.  
  4749.    the environment variable TZ
  4750.    the variable $main::TZ
  4751.    the file /etc/TIMEZONE
  4752.    the 5th element of the unix "date" command (not available on NT machines)
  4753.  
  4754. At least one of these should contain a timezone in one of the supported
  4755. forms.  If it doesn't, the TZ variable must be set to contain the local
  4756. timezone in the appropriate form.
  4757.  
  4758. The TZ variable will override the other methods of determining the
  4759. timezone, so it should probably be left blank if any of the other methods
  4760. will work.  Otherwise, you will have to modify the variable every time you
  4761. switch to/from daylight savings time.
  4762.  
  4763. =item ConvTZ
  4764.  
  4765. All date comparisons and calculations must be done in a single time zone in
  4766. order for them to work correctly.  So, when a date is parsed, it should be
  4767. converted to a specific timezone.  This allows dates to easily be compared
  4768. and manipulated as if they are all in a single timezone.
  4769.  
  4770. The ConvTZ variable determines which timezone should be used to store dates
  4771. in.  If it is left blank, all dates are converted to the local timezone
  4772. (see the TZ variable above).  If it is set to one of the timezones listed
  4773. above, all dates are converted to this timezone.  Finally, if it is set to
  4774. the string "IGNORE", all timezone information is ignored as the dates are
  4775. read in (in this case, the two dates "1/1/96 12:00 GMT" and "1/1/96 12:00
  4776. EST" would be treated as identical).
  4777.  
  4778. =item Internal
  4779.  
  4780. When a date is parsed using ParseDate, that date is stored in an internal
  4781. format which is understood by the Date::Manip routines UnixDate and
  4782. DateCalc.  Originally, the format used to store the date internally was:
  4783.  
  4784.    YYYYMMDDHH:MN:SS
  4785.  
  4786. It has been suggested that I remove the colons (:) to shorten this to:
  4787.  
  4788.    YYYYMMDDHHMNSS
  4789.  
  4790. The main advantage of this is that some databases are colon delimited which
  4791. makes storing date from Date::Manip tedious.
  4792.  
  4793. In order to maintain backwards compatibility, the Internal varialbe was
  4794. introduced.  Set it to 0 (to use the old format) or 1 (to use the new
  4795. format).
  4796.  
  4797. =item FirstDay
  4798.  
  4799. It is sometimes necessary to know what day of week is regarded as first.
  4800. By default, this is set to sunday, but many countries and people will
  4801. prefer monday (and in a few cases, a different day may be desired).  Set
  4802. the FirstDay variable to be the first day of the week (0=sunday to
  4803. 6=saturday).  Incidentally, monday should be chosen as the default to be in
  4804. complete accordance with ISO 8601.
  4805.  
  4806. =item WorkWeekBeg, WorkWeekEnd
  4807.  
  4808. The first and last days of the work week.  By default, monday and friday.
  4809. WorkWeekBeg must come before WorkWeekEnd numerically.  The days are
  4810. numbered from 0 (sunday) to 6 (saturday).  There is no way to handle an odd
  4811. work week of Thu to Mon for example.
  4812.  
  4813. =item WorkDay24Hr
  4814.  
  4815. If this is non-nil, a work day is treated as being 24 hours long.  The
  4816. WorkDayBeg and WorkDayEnd variables are ignored in this case.
  4817.  
  4818. =item WorkDayBeg, WorkDayEnd
  4819.  
  4820. The times when the work day starts and ends.  WorkDayBeg must come before
  4821. WorkDayEnd (i.e. there is no way to handle the night shift where the work
  4822. day starts one day and ends another).  Also, the workday MUST be more than
  4823. one hour long (of course, if this isn't the case, let me know... I want a
  4824. job there!).
  4825.  
  4826. The time in both can be in any valid time format (including international
  4827. formats), but seconds will be ignored.
  4828.  
  4829. =item DeltaSigns
  4830.  
  4831. Prior to Date::Manip version 5.07, a negative delta would put negative
  4832. signs in front of every component (i.e. "0:0:-1:-3:0:-4").  By default,
  4833. 5.07 changes this behavior to print only 1 or two signs in front of the
  4834. year and day elements (even if these elements might be zero) and the sign
  4835. for year/month and day/hour/minute/second are the same.  Setting this
  4836. variable to non-zero forces deltas to be stored with a sign in front of
  4837. every element (including elements equal to 0).
  4838.  
  4839. =back
  4840.  
  4841. =head1 BACKWARDS INCOMPATIBILITIES
  4842.  
  4843. For the most part, Date::Manip has remained backward compatible at every
  4844. release.  There have been a few minor incompatibilities introduced at
  4845. various stages.
  4846.  
  4847. Version 5.07 introduced 2 minor incompatibilities.  In the UnixDate
  4848. command, the "%s" format changed.  In version 5.06, "%s" returned the
  4849. number of seconds since Jan 1, 1970 in the current timezone.  In 5.07,
  4850. it returns the number of seconds since Jan 1, 1970 GMT.  The "%o" format
  4851. was added to return what "%s" previously did.
  4852.  
  4853. Also in 5.07, the format for the deltas returned by ParseDateDelta changed.
  4854. Previously, each element of a delta had a sign attached to it
  4855. (+1:+2:+3:+4:+5:+6).  The new format removes all unnecessary signs by
  4856. default (+1:2:3:4:5:6).  Also, because of the way deltas are normalized
  4857. (see documentation on ParseDateDelta), at most two signs are included.
  4858. For backwards compatibility, the config variable DeltaSigns was added.  If
  4859. set to 1, all deltas include all 6 signs.
  4860.  
  4861. Finally, in 5.07 the format of the Date_Init calling arguments changed.  The
  4862. old method
  4863.  
  4864.   &Date_Init($language,$format,$tz,$convtz);
  4865.  
  4866. is still supported, but this support will likely disappear in the future.
  4867. Use the new calling format instead:
  4868.  
  4869.   &Date_Init("var=val","var=val",...);
  4870.  
  4871. One more important incompatibility is projected for ParseDate in the next
  4872. major release of Date::Manip.  The next release will support full ISO 8601
  4873. date formats including the format YY-MM-DD.  The current version of
  4874. ParseDate supports the format MM-DD-YY, which is commonly used in the US,
  4875. but is not part of any standard.  Unfortunately, there is no way to
  4876. unambiguously look at a date of the format XX-XX-XX and determine whether
  4877. it is YY-MM-DD or MM-DD-YY.  As a result, the MM-DD-YY format will no
  4878. longer be supported in favor of the YY-MM-DD format.  The MM/DD/YY and
  4879. MM-DD-YYYY formats WILL still be supported!
  4880.  
  4881. =head1 COMMON PROBLEMS
  4882.  
  4883. Perhaps the most common problem occurs when you get the error:
  4884.  
  4885.    Error: Date::Manip unable to determine TimeZone.
  4886.  
  4887. Date::Manip tries hard to determine the local timezone, but on some
  4888. machines, it cannot do this (especially those without a unix date
  4889. command... i.e. Microsoft Windows systems).  To fix this, just set the TZ
  4890. variable, either at the top of the Manip.pm file, or in the DateManip.cnf
  4891. file.  I suggest using the form "EST5EDT" so you don't have to change it
  4892. every 6 months when going to or from daylight savings time.
  4893.  
  4894. =head1 KNOWN PROBLEMS
  4895.  
  4896. =over 4
  4897.  
  4898. =item Daylight Savings Times
  4899.  
  4900. Date::Manip does not handle daylight savings time, though it does handle
  4901. timezones to a certain extent.  Converting from EST to PST works fine.
  4902. Going from EST to PDT is unreliable.
  4903.  
  4904. The following examples are run in the winter of the US East coast (i.e.
  4905. in the EST timezone).
  4906.  
  4907.     print UnixDate(ParseDate("6/1/97 noon"),"%u"),"\n";
  4908.         => Sun Jun  1 12:00:00 EST 1997
  4909.  
  4910. June 1 EST does not exist.  June 1st is during EDT.  It should print:
  4911.  
  4912.         => Sun Jun  1 00:00:00 EDT 1997
  4913.  
  4914. Even explicitely adding the timezone doesn't fix things (if anything, it
  4915. makes them worse):
  4916.  
  4917.     print UnixDate(ParseDate("6/1/97 noon EDT"),"%u"),"\n";
  4918.         => Sun Jun  1 11:00:00 EST 1997
  4919.  
  4920. Date::Manip converts everything to the current timezone (EST in this case).
  4921.  
  4922. Related problems occur when trying to do date calculations over a timezone
  4923. change.  These calculations may be off by an hour.
  4924.  
  4925. Also, if you are running a script which uses Date::Manip over a period of
  4926. time which starts in one time zone and ends in another (i.e. it switches
  4927. form Daylight Savings Time to Standard Time or vice versa), many things may
  4928. be wrong (especially elapsed time).
  4929.  
  4930. I hope to fix these problems in the next release so that it would convert
  4931. everything to the current zones (EST or EDT).
  4932.  
  4933. =item Sorting Problems
  4934.  
  4935. If you use Date::Manip to sort a number of dates, you must call Date_Init
  4936. either explicitely, or by way of some other Date::Manip routine before it
  4937. is used in the sort.  For example, the following code fails:
  4938.  
  4939.    use Date::Manip;
  4940.    # &Date_Init;
  4941.    sub sortDate {
  4942.        my($date1, $date2);
  4943.        $date1 = &ParseDate($a);
  4944.        $date2 = &ParseDate($b);
  4945.        return ($date1 cmp $date2);
  4946.    }
  4947.    @date = ("Fri 16 Aug 96",
  4948.             "Mon 19 Aug 96",
  4949.             "Thu 15 Aug 96");
  4950.    @i=sort sortDate @dates;
  4951.  
  4952. but if you uncomment the Date_Init line, it works.  The reason for this is
  4953. that the first time you call Date_Init, it initializes a number of items
  4954. used by Date::Manip.  Some of these are sorted.  It turns out that perl
  4955. (5.003 and earlier) has a bug in it which does not allow a sort within a
  4956. sort.  The next version (5.004) may fix this.  For now, the best thing to
  4957. do is to call Date_Init explicitely.  NOTE: This is an extremely
  4958. inefficient way to sort data.  Instead, you should translate the dates to
  4959. the Date::Manip internal format, sort them using a normal string
  4960. comparison, and then convert them back to the format desired using
  4961. UnixDate.
  4962.  
  4963. =item RCS Control
  4964.  
  4965. If you try to put Date::Manip under RCS control, you are going to have
  4966. problems.  Apparently, RCS replaces strings of the form "$Date...$" with
  4967. the current date.  This form occurs all over in Date::Manip.  Since very
  4968. few people will ever have a desire to do this (and I don't use RCS), I have
  4969. not worried about it.
  4970.  
  4971. =back
  4972.  
  4973. =head1 AUTHOR
  4974.  
  4975. Sullivan Beck (beck@qtp.ufl.edu)
  4976.  
  4977. =cut
  4978.