MANIP(1)

NAME
Date::Manip - powerful date manipulation routines

SYNOPSIS

 use DateManip;
 $date=&ParseDate(\@args)
 $date=&ParseDate($string)
 $date=&ParseDate(\$string)
 @date=&UnixDate($date,@format)
 $date=&UnixDate($date,@format)
 $delta=&ParseDateDelta(\@args)
 $delta=&ParseDateDelta($string)
 $delta=&ParseDateDelta(\$string)
 $d=&DateCalc($d1,$d2,$errref,$del)
 $date=&Date_SetTime($date,$hr,$min,$sec)
 $date=&Date_SetTime($date,$time)
 $date=&Date_GetPrev($date,$dow,$today,$hr,$min,$sec)
 $date=&Date_GetPrev($date,$dow,$today,$time)
 $date=&Date_GetNext($date,$dow,$today,$hr,$min,$sec)
 $date=&Date_GetNext($date,$dow,$today,$time)
 &Date_Init($lang,$format)
 The following routines are used by the above routines (though they can
 also be called directly).  Make sure that $y is entered as the full 4
 digit year... 2 digit years may give wrong results.
 $day=&Date_DayOfWeek($m,$d,$y)
 $secs=&Date_SecsSince1970($m,$d,$y,$h,$mn,$s)
 $days=&Date_DaysSince999($m,$d,$y)
 $day=&Date_DayOfYear($m,$d,$y)
 $days=&Date_DaysInYear($y)
 $wkno=&Date_WeekOfYear($m,$d,$y,$first)
 $flag=&Date_LeapYear($y)
 $day=&Date_DaySuffix($d)
 $tz=&Date_TimeZone()

DESCRIPTION
This is a set of routines to work with the Gregorian calendar (the one currently in use). The Julian calendar defined leap years as every 4th year. The Gregorian calendar improved this by making every 100th year NOT a leap year, unless it was also the 400th year. The Gregorian calendar has been extrapolated back to the year 1000 AD and forward to the year 9999 AD. Note that in historical context, the Julian calendar was in use until 1582 when the Gregorian calendar was adopted by the Catholic church. Protestant countries did not accept it until later; Germany and Netherlands in 1698, British Empire in 1752, Russia in 1918.

Note that the Gregorian calendar is itself imperfect. Each year is on average 26 seconds too long, which means that every 3,323 years, a day should be removed from the calendar. No attempt is made to correct for that.

Among other things, these routines allow you to:

1. Enter a date and be able to choose any format conveniant

2. Compare two dates, entered in widely different formats to determine
    which is earlier

3. Extract any information you want from ANY date using a format string
    similar to the Unix date command

4. Determine the amount of time between two dates

5. Add a time offset to a date to get a second date (i.e. determine the
    date 132 days ago or 2 years and 3 months after Jan 2, 1992)

6. Work with dates with dates using international formats (foreign month
    names, 12-10-95 referring to October rather than December, etc.).

Each of these tasks is trivial (one or two lines at most) with this package.

Although the word date is used extensively here, it is actually somewhat misleading. This package works with the full date AND time (year, month, day, hour, minute, second).

In the documentation below, US formats are used, but in most cases, a non-English equivalent will work equally well.

EXAMPLES
1. Parsing a date from any conveniant format

  $date=&ParseDate("today");
  $date=&ParseDate("1st thursday in June 1992");
  $date=&ParseDate("05-10-93");
  $date=&ParseDate("12:30 Dec 12th 1880");
  $date=&ParseDate("8:00pm december tenth");
  if (! $date) {
    # Error in the date
  }
2. Compare two dates

  $date1=&ParseDate($string1);
  $date2=&ParseDate($string2);
  if ($date1 lt $date2) {
    # date1 is earlier
  } else {
    # date2 is earlier (or the two dates are identical)
  }
3. Extract information from a date.

  print &UnixDate("today","The time is now %T on %b %f, %Y.");
  =>  "The time is now 13:24:08 on Feb  3, 1996."
4. The amount of time between two dates.

  $date1=&ParseDate($string1);
  $date2=&ParseDate($string2);
  $delta=&DateCalc($date1,$date2,\$err);
  => 0:0:DD:HH:MM:SS   the days, hours, minutes, and seconds between the two
  $delta=&DateCalc($date1,$date2,\$err,1);
  => YY:MM:DD:HH:MM:SS  the years, months, etc. between the two
  Read the documentation below for an explanation of the difference.
5. To determine a date a given offset from another.

  $date=&DateCalc("today","+ 3hours 12minutes 6 seconds",\$err);
  $date=&DateCalc("12 hours ago","12:30 6Jan90",\$err);
6. To work with dates in another language.

  &Date_Init("French","non-US");
  $date=&ParseDate("1er decembre 1990");

 $date=&ParseDate(\@args)
 $date=&ParseDate($string)
 $date=&ParseDate(\$string)
This takes an array or a string containing a date and parses it. When the date is included as an array (for example, the arguments to a program) the array should contain a valid date in the first one or more elements (elements after a valid date are ignored). Elements containing a valid date are shifted from the array. The largest possible number of elements which can be correctly interpreted as a valid date are always used. If a string is entered rather than an array, that string is tested for a valid date. The string is unmodified, even if passed in by reference.

When a part ofq the date is not given, defaults are used: year defaults to current year; hours, minutes, seconds to 00.

Times may be written as:
  1) HH:MN
     HH:MN:SS
     HH:MN am
     HH:MN:SS am
  2) hh:MN
     hh:MN:SS
     hh:MN am
     hh:MN:SS am

Valid formats for a full date and time (and examples of how Dec 10, 1965 at 9:00 pm might appear) are:
  DateTime
     Date=YYMMDD 1965121021:00:00 65121021:00
     Time=format 1

  Date Time
  Date%Time
    Date=mm%dd, mm%dd%YY     12/10/65 21:00    12 10 1965 9:00pm
    Date=mmm%dd, mmm%dd%YY   December-10-65-9:00:00pm
    Date=dd%mmm, dd%mmm%YY   10/December/65 9:00:00pm
  Date Time
    Date=mmmdd, mmmdd YY, mmmDDYY, mmm DDYY
                             Dec10 65 9:00:00 pm    December 10 1965 9:00pm
    Date=ddmmm, ddmmm YY, ddmmmYY, dd mmmYY
                             10Dec65 9:00:00 pm     10 December 1965 9:00pm
  TimeDate
  Time Date
  Time%Date
    Date=mm%dd, mm%dd%YY     9:00pm 12.10.65      21:00 12/10/1965
    Date=mmm%dd, mmm%dd%YY   9:00pm December/10/65
    Date=dd%mmm, dd%mmm%YY   9:00pm 10-December-65  21:00/10/Dec/65
  TimeDate
  Time Date
    Date=mmmdd, mmmdd YY, mmmDDYY
                             21:00:00DeCeMbEr10
    Date=ddmmm, ddmmm YY, ddmmmYY, dd mmmYY
                             21:00 10Dec95
  which dofw in mmm at time
  which dofw in mmm YY at time  "first sunday in june 1996"
In addition, the following strings are recognized:
  today
  now (synonym for today)
  yesterday (exactly 24 hours before now)
  tomorrow (exactly 24 hours from now)

 %       One of the valid date separators: - . / or whitespace (the same
         character must be used for all occurences of a single date)
         example: mm%dd%YY works for 1-1-95, 1 1 95, or 1/1/95
 YY      year in 2 or 4 digit format
 MM      two digit month (01 to 12)
 mm      one or two digit month (1 to 12 or 01 to 12)
 mmm     month name or 3 character abbreviation
 DD      two digit day (01 to 31)
 dd      one or two digit day (1 to 31 or 01 to 31)
 HH      two digit hour in 12 or 24 hour mode (00 to 23)
 hh      one or two digit hour in 12 or 24 hour mode (0 to 23 or 00 to 23)
 MN      two digit minutes (00 to 59)
 SS      two digit seconds (00 to 59)
 which   one of the strings (first-fifth, 1st-5th, or last)
 dofw    either the 3 character abbreviation or full name of a day of
         the week
In the above, the mm%dd formats can be switched to dd%mm by calling Date_Init and telling it to use a non-US date format.

All "Date Time" and "DateTime" type formats allow the word "at" in them (i.e. Jan 12 at 12:00) (and at can replace the space). So the following are both acceptable: "Jan 12at12:00" and "Jan 12 at 12:00". Also, the day of the week can be given practically anywhere in the date. If it is given, it is checked to see if it is correct. So, the string "Tue Jun 25 1996" works but "Mon Jun 25 1996" doesn't. Note that depending on where the weekday comes, it may give unexpected results when used in array context. For example, the date ("Jun","25","Sun","1990") would return June 25 of the current year since only Jun 25, 1990 is not Sunday.

Any time you have HH:MM or HH:MM:SS, it can be followed by an am or pm to force have it in 12 hour mode (it defaults to 24 hour mode).

The year may be entered as 2 or 4 digits. If entered as 2 digits, it is taken to be the year in the range CurrYear-89 to CurrYear+10. So, if the current year is 1996, the range is [1907 to 2006] so entering the year 00 crefers to 2000, 05 to 2005, but 07 refers to 1907.

When entered as a single element, the different parts of the date may be separated by any number of whitespaces including spaces and tabs.

The date returned is YYYYMMDDHH:MM:SS. The advantage of this time format is that two times can be compared using simple string comparisons to find out which is later.

Dates are checked to make sure they are valid.

The elements containing a valid date are removed from the array! If no valid date is found, the array is unmodified and nothing returned.

In all of the formats, the day of week ("Friday") can be entered anywhere in the date and it will be checked for accuracy. In other words,
  "Tue Jul 16 1996 13:17:00" will work but
  "Jul 16 1996 Wednesday 13:17:00" will not (because Jul 16, 1996 is Tuesday, not Wednesday).

 @date=&UnixDate($date,@format)
 $date=&UnixDate($date,@format)
This takes a date and a list of strings containing formats roughly identical to the format strings used by the UNIX date(1) command. Each format is parsed and an array of strings corresponding to each format is returned.

$date must be of the form produced by &ParseDate.

The format options are:

 Year
     %y     year                     - 00 to 99
     %Y     year                     - 0001 to 9999
 Month, Week
     %m     month of year            - 01 to 12
     %f     month of year            - " 1" to "12"
     %b,%h  month abbreviation       - Jan to Dec
     %B     month name               - January to December
     %U     week of year, Sunday
            as first day of week     - 00 to 53
     %W     week of year, Monday
            as first day of week     - 00 to 53
 Day
     %j     day of the year          - 001 to 366
     %d     day of month             - 01 to 31
     %e     day of month             - " 1" to "31"
     %v     weekday abbreviation     - " S"," M"," T"," W","Th"," F","Sa"
     %a     weekday abbreviation     - Sun to Sat
     %A     weekday name             - Sunday to Saturday
     %w     day of week              - 0 (Sunday) to 6
     %E     day of month with suffix - 1st, 2nd, 3rd...
 Hour
     %H     hour                     - 00 to 23
     %k     hour                     - " 0" to "23"
     %i     hour                     - " 1" to "12"
     %I     hour                     - 01 to 12
     %p     AM or PM
 Minute, Second, Timezone
     %M     minute                   - 00 to 59
     %S     second                   - 00 to 59
     %s     seconds from Jan 1, 1970 - negative if before 1/1/1970
     %z,%Z  timezone (3 characters)  - "EDT"
 Date, Time
     %c     %a %b %e %H:%M:%S %Y     - Fri Apr 28 17:23:15 1995
     %C,%u  %a %b %e %H:%M:%S %z %Y  - Fri Apr 28 17:25:57 EDT 1995
     %D,%x  %m/%d/%y                 - 04/28/95
     %l     date in ls(1) format
              %b %e $H:$M            - Apr 28 17:23  (if within 6 months)
              %b %e  %Y              - Apr 28  1993  (otherwise)
     %r     %I:%M:%S %p              - 05:39:55 PM
     %R     %H:%M                    - 17:40
     %T,%X  %H:%M:%S                 - 17:40:58
     %V     %m%d%H%M%y               - 0428174095
     %F     %A, %B %e, %Y            - Sunday, January  1, 1996
 Other formats
     %n     insert a newline character
     %t     insert a tab character
     %%     insert a `%' character
     %+     insert a `+' character
 All other formats insert the character following the %.  If a lone
 percent is the final character in a format, it is ignored.
Note that the ls format applies to date within the past OR future 6 months!

The following formats are currently unused but may be used in the future:
  goq GJKLNOPQ 1234567890 !@#$^&*()_|-=\`[];',./~{}:<>?

This routine is loosely based on date.pl (version 3.2) by Terry McGonigal. No code was used, but most of his formats were.

 $delta=&ParseDateDelta(\@args)
 $delta=&ParseDateDelta($string)
 $delta=&ParseDateDelta(\$string)
This takes an array and shifts a valid delta date (an amount of time) from the array. Recognized deltas are of the form:
  +Yy +Mm +Dd +Hh +MNmn +Ss
  +Y:+M:+D:+H:+MN:+S

A field in the format +Yy is a sign, a number, and a string specifying the type of field. The sign is "+", "-", or absent (defaults to the last sign given). The valid strings specifying the field type are:
   y: y, yr, year, years
   m: m, mon, month, months
   d: d, day, days
   h: h, hr, hour, hours
   mn: mn, min, minute, minutes
   s: s, sec, second, seconds

Also, the "s" string may be omitted. The sign, number, and string may all be separated from each other by any number of whitespaces.

In the date, all fields must be given in the order: y m d h mn s. Any number of them may be omitted provided the rest remain in the correct order. In the 2nd (colon) format, from 2 to 6 of the fields may be given. For example +D:+H:+MN:+S may be given to specify only four of the fields. In any case, both the MN and S field may be present. No spaces may be present in the colon format.

Deltas may also be given as a combination of the two formats. For example, the following is valid: +Yy +D:+H:+MN:+S. Again, all fields must be given in the correct order.

The word in may be prepended to the delta ("in 5 years") and the word ago may be appended ("6 months ago"). The "in" is completely ignored. The "ago" has the affect of reversing all signs that appear in front of the components of the delta. I.e. "-12 yr 6 mon ago" is identical to "+12yr +6mon" (don't forget that there is an impled minus sign in front of the 6 because when no sign is explicitely given, it carries the previously entered sign).

 $d=&DateCalc($d1,$d2,\$err,$del)
This takes two dates, deltas, or one of each and performs the appropriate calculation with them. Dates must be in the format given by &ParseDate and or must be a string which can be parsed as a date. Deltas must be in the format returned by &ParseDateDelta or must be a string that can be parsed as a delta. Two deltas add together to form a third delta. A date and a delta returns a 2nd date. Two dates return a delta (the difference between the two dates).

Note that in many cases, it is somewhat ambiguous what the delta actually refers to. Although it is ALWAYS known how many months in a year, hours in a day, etc., it is NOT known how many days form a month. As a result, the part of the delta containing month/year and the part with sec/min/hr/day must be treated separately. For example, "Mar 31, 12:00:00" plus a delta of 1month 2days would yield "May 2 12:00:00". The year/month is first handled while keeping the same date. Mar 31 plus one month is Apr 31 (but since Apr only has 30 days, it becomes Apr 30). Apr 30 + 2 days is May 2.

In the case where two dates are entered, the resulting delta can take on two different forms. By default, an absolutely correct delta (ignoring daylight savings time) is returned in days, hours, minutes, and seconds. If $del is non-nil, a delta is returned using years and months as well. The year and month part is calculated first followed by the rest. For example, the two dates "Mar 12 1995" and "Apr 10 1995" would have an absolutely correct delta of "29 days" but if $del is non-nil, it would be returned as "1 month - 2 days". Also, "Mar 31" and "Apr 30" would have deltas of "30 days" or "1 month" (since Apr 31 doesn't exist, it drops down to Apr 30).

$err is set to:
   1 is returned if $d1 is not a delta or date
   2 is returned if $d2 is not a delta or date
   3 is returned if the date is outside the years 1000 to 9999

Nothing is returned if an error occurs.

If $del is non-nil, both $d1 and $d2 must be dates.

 $date=&Date_SetTime($date,$hr,$min,$sec)
 $date=&Date_SetTime($date,$time)
This takes a date sets the time in that date. For example, to get the time for 7:30 tomorrow, use the lines:

   $date=&ParseDate("tomorrow")
   $date=&Date_SetTime($date,"7:30")

 $date=&Date_GetPrev($date,$dow,$today,$hr,$min,$sec)
 $date=&Date_GetPrev($date,$dow,$today,$time)
This takes a date and returns the date of the previous $day. For example, if $day is "Fri", it returns the date of the previous Friday. If $date is Friday, it will return either $date (if $today is non-zero) or the Friday a week before (if $today is 0). The time is also set according to the optional $hr,$min,$sec (or $time in the format HH:MM:SS).

 $date=&Date_GetNext($date,$dow,$today,$hr,$min,$sec)
 $date=&Date_GetNext($date,$dow,$today,$time)
Similar to Date_GetPrev.

 $day=&Date_DayOfWeek($m,$d,$y);
Returns the day of the week (0 for Sunday, 6 for Saturday). Dec 31, 0999 was Tuesday.

 $secs=&Date_SecsSince1970($m,$d,$y,$h,$mn,$s)
Returns the number of seconds since Jan 1, 1970 00:00 (negative if date is earlier).

 $days=&Date_DaysSince999($m,$d,$y)
Returns the number of days since Dec 31, 0999.

 $day=&Date_DayOfYear($m,$d,$y);
Returns the day of the year (001 to 366)

 $days=&Date_DaysInYear($y);
Returns the number of days in the year (365 or 366)

 $wkno=&Date_WeekOfYear($m,$d,$y,$first);
Figure out week number. $first is the first day of the week which is usually 0 (Sunday) or 1 (Monday), but could be any number between 0 and 6 in practice.

 $flag=&Date_LeapYear($y);
Returns 1 if the argument is a leap year Written by David Muir Sharnoff <muir@idiom.com>

 $day=&Date_DaySuffix($d);
Add `st', `nd', `rd', `th' to a date (ie 1st, 22nd, 29th). Works for international dates.

 $tz=&Date_TimeZone
This returns a timezone. It looks in the following places for a timezone in the following order:
   POSIX::tzname
   $ENV{TZ}
   $main::TZ
   /etc/TIMEZONE If it's not found in any of those places, GMT is returned. Obviously, this does not guarantee the correct timezone.

 $flag=&Date_Init()
 $flag=&Date_Init($lang,$format)
Normally, it is not necessary to explicitely call Date_Init. The first time any of the other routines are called, Date_Init will be called to set everything up. If for some reason you want to parse dates in multiple languages, you can pass in the language and format information and reinitialize everything for a different language.

Recognized values of $lang are "English" and "French". Others will be added in the future. $format should be "US" or any other string. Most US people look at the date 12/10/96 as MM/DD/YY or Dec 10, 1996. Many countries would regard this as Oct 12, 1996 instead. Setting the $form variable to "US" forces the first one. Anything else forces the 2nd.

AUTHOR
Sullivan Beck (beck@qtp.ufl.edu)