NAME

Date::Calc - Gregorian calendar date calculations

MOTTO

Keep it small, fast and simple

PREFACE

This package consists of a C library and a Perl module (which uses the C library, internally) for all kinds of date calculations based on the Gregorian calendar (the one used in all western countries today), thereby complying with all relevant norms and standards: ISO/R 2015-1971, DIN 1355 and, to some extent, ISO 8601 (where applicable).

(See also http://www.engelschall.com/u/sb/download/Date-Calc/DIN1355/ for a scan of part of the "DIN 1355" document (in German)).

The module of course handles year numbers of 2000 and above correctly ("Year 2000" or "Y2K" compliance) -- actually all year numbers from 1 to the largest positive integer representable on your system (which is at least 32767) can be dealt with.

This is not true, however, for the import/export functions in this package which are an interface to the internal POSIX date and time functions of your system, which can only cover dates in the following ranges:

 01-Jan-1970 00:00:00 GMT .. 19-Jan-2038 03:14:07 GMT [Unix etc.]
 01-Jan-1904 00:00:00 LT  .. 06-Feb-2040 06:28:15 LT  [MacOS Classic]
 (LT = local time)

Note that this package projects the Gregorian calendar back until the year 1 A.D. -- even though the Gregorian calendar was only adopted in 1582, mostly by the Catholic European countries, in obedience to the corresponding decree of Pope Gregory XIII in that year.

Some (mainly protestant) countries continued to use the Julian calendar (used until then) until as late as the beginning of the 20th century.

Finally, note that this package is not intended to do everything you could ever imagine automagically for you; it is rather intended to serve as a toolbox (in the best of UNIX spirit and traditions) which should, however, always get you where you want to go.

See the section "RECIPES" at the bottom of this document for solutions to common problems!

If nevertheless you can't figure out how to solve a particular problem, please let me know! (See e-mail address at the end of this document.)

SYNOPSIS

  use Date::Calc qw(
      Days_in_Year
      Days_in_Month
      Weeks_in_Year
      leap_year
      check_date
      check_time
      check_business_date
      Day_of_Year
      Date_to_Days
      Day_of_Week
      Week_Number
      Week_of_Year
      Monday_of_Week
      Nth_Weekday_of_Month_Year
      Standard_to_Business
      Business_to_Standard
      Delta_Days
      Delta_DHMS
      Delta_YMD
      Delta_YMDHMS
      Normalize_DHMS
      Add_Delta_Days
      Add_Delta_DHMS
      Add_Delta_YM
      Add_Delta_YMD
      Add_Delta_YMDHMS
      System_Clock
      Today
      Now
      Today_and_Now
      This_Year
      Gmtime
      Localtime
      Mktime
      Timezone
      Date_to_Time
      Time_to_Date
      Easter_Sunday
      Decode_Month
      Decode_Day_of_Week
      Decode_Language
      Decode_Date_EU
      Decode_Date_US
      Fixed_Window
      Moving_Window
      Compress
      Uncompress
      check_compressed
      Compressed_to_Text
      Date_to_Text
      Date_to_Text_Long
      English_Ordinal
      Calendar
      Month_to_Text
      Day_of_Week_to_Text
      Day_of_Week_Abbreviation
      Language_to_Text
      Language
      Languages
      Decode_Date_EU2
      Decode_Date_US2
      Parse_Date
      ISO_LC
      ISO_UC
  );
  use Date::Calc qw(:all);
  Days_in_Year
      $days = Days_in_Year($year,$month);
  Days_in_Month
      $days = Days_in_Month($year,$month);
  Weeks_in_Year
      $weeks = Weeks_in_Year($year);
  leap_year
      if (leap_year($year))
  check_date
      if (check_date($year,$month,$day))
  check_time
      if (check_time($hour,$min,$sec))
  check_business_date
      if (check_business_date($year,$week,$dow))
  Day_of_Year
      $doy = Day_of_Year($year,$month,$day);
  Date_to_Days
      $days = Date_to_Days($year,$month,$day);
  Day_of_Week
      $dow = Day_of_Week($year,$month,$day);
  Week_Number
      $week = Week_Number($year,$month,$day);          # DEPRECATED
  Week_of_Year
      ($week,$year) = Week_of_Year($year,$month,$day); # RECOMMENDED
      $week = Week_of_Year($year,$month,$day);         # DANGEROUS
  Monday_of_Week
      ($year,$month,$day) = Monday_of_Week($week,$year);
  Nth_Weekday_of_Month_Year
      if (($year,$month,$day) =
      Nth_Weekday_of_Month_Year($year,$month,$dow,$n))
  Standard_to_Business
      ($year,$week,$dow) =
      Standard_to_Business($year,$month,$day);
  Business_to_Standard
      ($year,$month,$day) =
      Business_to_Standard($year,$week,$dow);
  Delta_Days
      $Dd = Delta_Days($year1,$month1,$day1,
                       $year2,$month2,$day2);
  Delta_DHMS
      ($Dd,$Dh,$Dm,$Ds) =
      Delta_DHMS($year1,$month1,$day1, $hour1,$min1,$sec1,
                 $year2,$month2,$day2, $hour2,$min2,$sec2);
  Delta_YMD
      ($Dy,$Dm,$Dd) =
      Delta_YMD($year1,$month1,$day1,
                $year2,$month2,$day2);
  Delta_YMDHMS
      ($D_y,$D_m,$D_d, $Dh,$Dm,$Ds) =
      Delta_YMDHMS($year1,$month1,$day1, $hour1,$min1,$sec1,
                   $year2,$month2,$day2, $hour2,$min2,$sec2);
  Normalize_DHMS
      ($Dd,$Dh,$Dm,$Ds) =
      Normalize_DHMS($Dd,$Dh,$Dm,$Ds);
  Add_Delta_Days
      ($year,$month,$day) =
      Add_Delta_Days($year,$month,$day,
                     $Dd);
  Add_Delta_DHMS
      ($year,$month,$day, $hour,$min,$sec) =
      Add_Delta_DHMS($year,$month,$day, $hour,$min,$sec,
                     $Dd,$Dh,$Dm,$Ds);
  Add_Delta_YM
      ($year,$month,$day) =
      Add_Delta_YM($year,$month,$day,
                   $Dy,$Dm);
  Add_Delta_YMD
      ($year,$month,$day) =
      Add_Delta_YMD($year,$month,$day,
                    $Dy,$Dm,$Dd);
  Add_Delta_YMDHMS
      ($year,$month,$day, $hour,$min,$sec) =
      Add_Delta_YMDHMS($year,$month,$day, $hour,$min,$sec,
                       $D_y,$D_m,$D_d, $Dh,$Dm,$Ds);
  System_Clock
      ($year,$month,$day, $hour,$min,$sec, $doy,$dow,$dst) =
      System_Clock([$gmt]);
  Today
      ($year,$month,$day) = Today([$gmt]);
  Now
      ($hour,$min,$sec) = Now([$gmt]);
  Today_and_Now
      ($year,$month,$day, $hour,$min,$sec) = Today_and_Now([$gmt]);
  This_Year
      $year = This_Year([$gmt]);
  Gmtime
      ($year,$month,$day, $hour,$min,$sec, $doy,$dow,$dst) =
      Gmtime([time]);
  Localtime
      ($year,$month,$day, $hour,$min,$sec, $doy,$dow,$dst) =
      Localtime([time]);
  Mktime
      $time = Mktime($year,$month,$day, $hour,$min,$sec);
  Timezone
      ($D_y,$D_m,$D_d, $Dh,$Dm,$Ds, $dst) = Timezone([time]);
  Date_to_Time
      $time = Date_to_Time($year,$month,$day, $hour,$min,$sec);
  Time_to_Date
      ($year,$month,$day, $hour,$min,$sec) = Time_to_Date([time]);
  Easter_Sunday
      ($year,$month,$day) = Easter_Sunday($year);
  Decode_Month
      if ($month = Decode_Month($string))
  Decode_Day_of_Week
      if ($dow = Decode_Day_of_Week($string))
  Decode_Language
      if ($lang = Decode_Language($string))
  Decode_Date_EU
      if (($year,$month,$day) = Decode_Date_EU($string))
  Decode_Date_US
      if (($year,$month,$day) = Decode_Date_US($string))
  Fixed_Window
      $year = Fixed_Window($yy);
  Moving_Window
      $year = Moving_Window($yy);
  Compress
      $date = Compress($year,$month,$day);
  Uncompress
      if (($century,$year,$month,$day) = Uncompress($date))
  check_compressed
      if (check_compressed($date))
  Compressed_to_Text
      $string = Compressed_to_Text($date);
  Date_to_Text
      $string = Date_to_Text($year,$month,$day);
  Date_to_Text_Long
      $string = Date_to_Text_Long($year,$month,$day);
  English_Ordinal
      $string = English_Ordinal($number);
  Calendar
      $string = Calendar($year,$month[,$orthodox]);
  Month_to_Text
      $string = Month_to_Text($month);
  Day_of_Week_to_Text
      $string = Day_of_Week_to_Text($dow);
  Day_of_Week_Abbreviation
      $string = Day_of_Week_Abbreviation($dow);
  Language_to_Text
      $string = Language_to_Text($lang);
  Language
      $lang = Language();
      Language($lang);
      $oldlang = Language($newlang);
  Languages
      $max_lang = Languages();
  Decode_Date_EU2
      if (($year,$month,$day) = Decode_Date_EU2($string))
  Decode_Date_US2
      if (($year,$month,$day) = Decode_Date_US2($string))
  Parse_Date
      if (($year,$month,$day) = Parse_Date($string))
  ISO_LC
      $lower = ISO_LC($string);
  ISO_UC
      $upper = ISO_UC($string);
  Version
      $string = Date::Calc::Version();

IMPORTANT NOTES

(See the section "RECIPES" at the bottom of this document for solutions to common problems!)

DESCRIPTION

RECIPES

1)

How do I compare two dates?

Solution #1:

  use Date::Calc qw( Date_to_Days );
  if (Date_to_Days($year1,$month1,$day1)  <
      Date_to_Days($year2,$month2,$day2))
  if (Date_to_Days($year1,$month1,$day1)  <=
      Date_to_Days($year2,$month2,$day2))
  if (Date_to_Days($year1,$month1,$day1)  >
      Date_to_Days($year2,$month2,$day2))
  if (Date_to_Days($year1,$month1,$day1)  >=
      Date_to_Days($year2,$month2,$day2))
  if (Date_to_Days($year1,$month1,$day1)  ==
      Date_to_Days($year2,$month2,$day2))
  if (Date_to_Days($year1,$month1,$day1)  !=
      Date_to_Days($year2,$month2,$day2))
  $cmp = (Date_to_Days($year1,$month1,$day1)  <=>
          Date_to_Days($year2,$month2,$day2));

Solution #2:

  use Date::Calc qw( Delta_Days );
  if (Delta_Days($year1,$month1,$day1,
                 $year2,$month2,$day2) > 0)
  if (Delta_Days($year1,$month1,$day1,
                 $year2,$month2,$day2) >= 0)
  if (Delta_Days($year1,$month1,$day1,
                 $year2,$month2,$day2) < 0)
  if (Delta_Days($year1,$month1,$day1,
                 $year2,$month2,$day2) <= 0)
  if (Delta_Days($year1,$month1,$day1,
                 $year2,$month2,$day2) == 0)
  if (Delta_Days($year1,$month1,$day1,
                 $year2,$month2,$day2) != 0)
2)

How do I check whether a given date lies within a certain range of dates?

  use Date::Calc qw( Date_to_Days );
  $lower = Date_to_Days($year1,$month1,$day1);
  $upper = Date_to_Days($year2,$month2,$day2);
  $date = Date_to_Days($year,$month,$day);
  if (($date >= $lower) && ($date <= $upper))
  {
      # ok
  }
  else
  {
      # not ok
  }
3)

How do I compare two dates with times? How do I check whether two dates and times lie more or less than a given time interval apart?

Solution #1:

  use Date::Calc qw( Add_Delta_DHMS Date_to_Days );
  @date1 = (2002,8,31,23,59,1);
  @date2 = (2002,9,1,11,30,59); # ==> less than 12 hours
  #@date1 = (2002,8,31,22,59,1);
  #@date2 = (2002,9,1,11,30,59); # ==> more than 12 hours
  # Omit the next line if you just want to compare the two dates
  # (and change @date3 and @d3 to @date1 and @d1, respectively):
  @date3 = Add_Delta_DHMS(@date1, 0,12,0,0); # ==> is the difference within 12 hours?
  @d2 = ( Date_to_Days(@date2[0..2]), ($date2[3]*60+$date2[4])*60+$date2[5] );
  @d3 = ( Date_to_Days(@date3[0..2]), ($date3[3]*60+$date3[4])*60+$date3[5] );
  @diff = ( $d2[0]-$d3[0], $d2[1]-$d3[1] );
  if ($diff[0] > 0 and $diff[1] < 0) { $diff[0]--; $diff[1] += 86400; }
  if ($diff[0] < 0 and $diff[1] > 0) { $diff[0]++; $diff[1] -= 86400; }
  if (($diff[0] || $diff[1]) >= 0) { print "More than 12 hours.\n"; }
  else                             { print "Less than 12 hours.\n"; }

Solution #2:

This solution is only feasible if your dates are guaranteed to lie within the range given by your system's epoch and overflow date and time!

     Unix:    1-Jan-1970 00:00:00  to  19-Jan-2038 03:14:07
     MacOS:   1-Jan-1904 00:00:00  to   6-Feb-2040 06:28:15
  use Date::Calc qw( Date_to_Time );
  @date1 = (2002,8,31,23,59,1);
  @date2 = (2002,9,1,11,30,59); # ==> less than 12 hours
  #@date1 = (2002,8,31,22,59,1);
  #@date2 = (2002,9,1,11,30,59); # ==> more than 12 hours
  $d1 = Date_to_Time(@date1);
  $d2 = Date_to_Time(@date2);
  if ($d1 <= $d2) { print "The two dates are in chronological order.\n"; }
  else            { print "The two dates are in reversed order.\n"; }
  if ($d1 + 12*60*60 <= $d2) { print "More than 12 hours.\n"; }
  else                       { print "Less than 12 hours.\n"; }
4)

How do I verify whether someone has a certain age?

  use Date::Calc qw( Decode_Date_EU Today leap_year Delta_Days );
  $date = <STDIN>; # get birthday
  ($year1,$month1,$day1) = Decode_Date_EU($date);
  ($year2,$month2,$day2) = Today();
  if (($day1 == 29) && ($month1 == 2) && !leap_year($year2))
      { $day1--; }
  if ( (($year2 - $year1) >  18) ||
     ( (($year2 - $year1) == 18) &&
     (Delta_Days($year2,$month1,$day1, $year2,$month2,$day2) >= 0) ) )
  {
      print "Ok - you are over 18.\n";
  }
  else
  {
      print "Sorry - you aren't 18 yet!\n";
  }
  Or, alternatively (substituting the last "if" statement above):
  if (($year1+18 <=> $year2 || $month1 <=> $month2 || $day1 <=> $day2) <= 0)
      { print "Ok - you are over 18.\n"; }
  else
      { print "Sorry - you aren't 18 yet!\n"; }
5)

How do I calculate the number of the week of month the current date lies in?

For example:

            April 1998
    Mon Tue Wed Thu Fri Sat Sun
              1   2   3   4   5  =  week #1
      6   7   8   9  10  11  12  =  week #2
     13  14  15  16  17  18  19  =  week #3
     20  21  22  23  24  25  26  =  week #4
     27  28  29  30              =  week #5

Solution:

  use Date::Calc qw( Today Day_of_Week );
  ($year,$month,$day) = Today();
  $week = int(($day + Day_of_Week($year,$month,1) - 2) / 7) + 1;
6)

How do I calculate whether a given date is the 1st, 2nd, 3rd, 4th or 5th of that day of week in the given month?

For example:

           October 2000
    Mon Tue Wed Thu Fri Sat Sun
                              1
      2   3   4   5   6   7   8
      9  10  11  12  13  14  15
     16  17  18  19  20  21  22
     23  24  25  26  27  28  29
     30  31

Is Sunday, the 15th of October 2000, the 1st, 2nd, 3rd, 4th or 5th Sunday of that month?

Solution:

  use Date::Calc qw( Day_of_Week Delta_Days
                     Nth_Weekday_of_Month_Year
                     Date_to_Text_Long English_Ordinal
                     Day_of_Week_to_Text Month_to_Text );
  ($year,$month,$day) = (2000,10,15);
  $dow = Day_of_Week($year,$month,$day);
  $n = int( Delta_Days(
            Nth_Weekday_of_Month_Year($year,$month,$dow,1),
            $year,$month,$day)
            / 7) + 1;
  printf("%s is the %s %s in %s %d.\n",
      Date_to_Text_Long($year,$month,$day),
      English_Ordinal($n),
      Day_of_Week_to_Text($dow),
      Month_to_Text($month),
      $year);

This prints:

  Sunday, October 15th 2000 is the 3rd Sunday in October 2000.
7)

How do I calculate the date of the Wednesday of the same week as the current date?

Solution #1:

  use Date::Calc qw( Today Day_of_Week Add_Delta_Days );
  $searching_dow = 3; # 3 = Wednesday
  @today = Today();
  $current_dow = Day_of_Week(@today);
  @date = Add_Delta_Days(@today, $searching_dow - $current_dow);

Solution #2:

  use Date::Calc qw( Today Add_Delta_Days
                     Monday_of_Week Week_of_Year );
  $searching_dow = 3; # 3 = Wednesday
  @today = Today();
  @date = Add_Delta_Days( Monday_of_Week( Week_of_Year(@today) ),
                          $searching_dow - 1 );

Solution #3:

  use Date::Calc qw( Standard_to_Business Today
                     Business_to_Standard );
  @business = Standard_to_Business(Today());
  $business[2] = 3; # 3 = Wednesday
  @date = Business_to_Standard(@business);
8)

How can I add a week offset to a business date (including across year boundaries)?

  use Date::Calc qw( Business_to_Standard Add_Delta_Days
                     Standard_to_Business );
  @temp = Business_to_Standard($year,$week,$dow);
  @temp = Add_Delta_Days(@temp, $week_offset * 7);
  ($year,$week,$dow) = Standard_to_Business(@temp);
9)

How do I calculate the last and the next Saturday for any given date?

  use Date::Calc qw( Today Day_of_Week Add_Delta_Days
                     Day_of_Week_to_Text Date_to_Text );
  $searching_dow = 6; # 6 = Saturday
  @today = Today();
  $current_dow = Day_of_Week(@today);
  if ($searching_dow == $current_dow)
  {
      @prev = Add_Delta_Days(@today,-7);
      @next = Add_Delta_Days(@today,+7);
  }
  else
  {
      if ($searching_dow > $current_dow)
      {
          @next = Add_Delta_Days(@today,
                    $searching_dow - $current_dow);
          @prev = Add_Delta_Days(@next,-7);
      }
      else
      {
          @prev = Add_Delta_Days(@today,
                    $searching_dow - $current_dow);
          @next = Add_Delta_Days(@prev,+7);
      }
  }
  $dow = Day_of_Week_to_Text($searching_dow);
  print "Today is:      ", ' ' x length($dow),
                               Date_to_Text(@today), "\n";
  print "Last $dow was:     ", Date_to_Text(@prev),  "\n";
  print "Next $dow will be: ", Date_to_Text(@next),  "\n";

This will print something like:

  Today is:              Sun 12-Apr-1998
  Last Saturday was:     Sat 11-Apr-1998
  Next Saturday will be: Sat 18-Apr-1998
10)

How can I calculate the last business day (payday!) of a month?

Solution #1 (holidays NOT taken into account):

  use Date::Calc qw( Days_in_Month Day_of_Week Add_Delta_Days );
  $day = Days_in_Month($year,$month);
  $dow = Day_of_Week($year,$month,$day);
  if ($dow > 5)
  {
      ($year,$month,$day) =
          Add_Delta_Days($year,$month,$day, 5-$dow);
  }

Solution #2 (holidays taken into account):

This solution expects a multi-dimensional array "@holiday", which contains all holidays, as follows: "$holiday[$year][$month][$day] = 1;".

(See the description of the function "Easter_Sunday()" further above for how to calculate the moving (variable) christian feast days!)

Days which are not holidays remain undefined or should have a value of zero in this array.

  use Date::Calc qw( Days_in_Month Add_Delta_Days Day_of_Week );
  $day = Days_in_Month($year,$month);
  while (1)
  {
      while ($holiday[$year][$month][$day])
      {
          ($year,$month,$day) =
              Add_Delta_Days($year,$month,$day, -1);
      }
      $dow = Day_of_Week($year,$month,$day);
      if ($dow > 5)
      {
          ($year,$month,$day) =
              Add_Delta_Days($year,$month,$day, 5-$dow);
      }
      else { last; }
  }

Solution #3 (holidays taken into account, more comfortable, but requires Date::Calendar(3) and Date::Calc::Object(3)):

  use Date::Calc::Object qw( Today Add_Delta_YM Date_to_Text_Long );
  use Date::Calendar::Profiles qw($Profiles);
  use Date::Calendar;
  $calendar = Date::Calendar->new( $Profiles->{'DE-BW'} );
  @today = Today();
  @nextmonth = Add_Delta_YM(@today[0,1],1, 0,1);
  $workaround = $calendar->add_delta_workdays(@nextmonth,+1);
  $payday     = $calendar->add_delta_workdays($workaround,-2);
  print "Pay day = ", Date_to_Text_Long($payday->date()), "\n";

The "workaround" is necessary due to a bug in the method "add_delta_workdays()" when adding a negative number of workdays.

11)

How do I convert a MS Visual Basic "DATETIME" value into its date and time constituents?

  use Date::Calc qw( Add_Delta_DHMS Date_to_Text );
  $datetime = "35883.121653";
  ($Dd,$Dh,$Dm,$Ds) = ($datetime =~ /^(\d+)\.(\d\d)(\d\d)(\d\d)$/);
  ($year,$month,$day, $hour,$min,$sec) =
      Add_Delta_DHMS(1900,1,1, 0,0,0, $Dd,$Dh,$Dm,$Ds);
  printf("The given date is %s %02d:%02d:%02d\n",
      Date_to_Text($year,$month,$day), $hour, $min, $sec);

This prints:

  The given date is Tue 31-Mar-1998 12:16:53

Since I do not have or use Visual Basic, I can't guarantee that the number format assumed here is really the one used by Visual Basic - but you get the general idea. :-)

Moreover, consider the following:

Morten Sickel <Morten.Sickel@nrpa.no> wrote:

I discovered a bug in Excel (2000): Excel thinks that 1900 was a leap year. Users should use 31-Dec-1899 as the date to add an Excel date value to in order to get the correct date.

I found out on the web that this bug originated in Lotus 123, which made 29-Feb-1900 an "industrial standard". MS chose to keep the bug in order to be compatible with Lotus 123. But they have not mentioned anything about it in the help files.

12)

How can I send a reminder to members of a group on the day before a meeting which occurs every first Friday of a month?

  use Date::Calc qw( Today Date_to_Days Add_Delta_YMD
                     Nth_Weekday_of_Month_Year );
  ($year,$month,$day) = Today();
  $tomorrow = Date_to_Days($year,$month,$day) + 1;
  $dow = 5; # 5 = Friday
  $n   = 1; # 1 = First of that day of week
  $meeting_this_month = Date_to_Days(
      Nth_Weekday_of_Month_Year($year,$month,$dow,$n) );
  ($year,$month,$day) = Add_Delta_YMD($year,$month,$day, 0,1,0);
  $meeting_next_month = Date_to_Days(
      Nth_Weekday_of_Month_Year($year,$month,$dow,$n) );
  if (($tomorrow == $meeting_this_month) ||
      ($tomorrow == $meeting_next_month))
  {
      # Send reminder e-mail!
  }
13)

How can I print a date in a different format than provided by the functions "Date_to_Text()", "Date_to_Text_Long()" or "Compressed_to_Text()"?

  use Date::Calc qw( Today Day_of_Week_to_Text
                     Day_of_Week Month_to_Text
                     English_Ordinal );
  ($year,$month,$day) = Today();

For example with leading zeros for the day: "Fri 03-Jan-1964"

  printf("%.3s %02d-%.3s-%d\n",
      Day_of_Week_to_Text(Day_of_Week($year,$month,$day)),
      $day,
      Month_to_Text($month),
      $year);

For example in U.S. american format: "April 12th, 1998"

  $string = sprintf("%s %s, %d",
                Month_to_Text($month),
                English_Ordinal($day),
                $year);

For example in one of the possible formats as specified by ISO 8601:

  @date = ($year,$month,$day,$hour,$min,$sec);
  $date = sprintf("%d-%02d-%02d %02d:%02d:%02d", @date);

(See also perlfunc(1)/printf and/or perlfunc(1)/sprintf!)

14)

How can I iterate through a range of dates?

  use Date::Calc qw( Delta_Days Add_Delta_Days );
  @start = (1999,5,27);
  @stop  = (1999,6,1);
  $j = Delta_Days(@start,@stop);
  for ( $i = 0; $i <= $j; $i++ )
  {
      @date = Add_Delta_Days(@start,$i);
      printf("%4d/%02d/%02d\n", @date);
  }

Note that the loop can be improved; see also the recipe below.

15)

How can I create a (Perl) list of dates in a certain range?

  use Date::Calc qw( Delta_Days Add_Delta_Days Date_to_Text );
  sub date_range
  {
      my(@date) = (@_)[0,1,2];
      my(@list);
      my($i);
      $i = Delta_Days(@_);
      while ($i-- >= 0)
      {
          push( @list, [ @date ] );
          @date = Add_Delta_Days(@date, 1) if ($i >= 0);
      }
      return(@list);
  }
  @range = &date_range(1999,11,3, 1999,12,24); # in chronological order
  foreach $date (@range)
  {
      print Date_to_Text(@{$date}), "\n";
  }

Note that you probably shouldn't use this one, because it is much more efficient to iterate through all the dates (as shown in the recipe immediately above) than to construct such an array and then to loop through it. Also, it is much more space-efficient not to create this array.

16)

How can I calculate the difference in days between dates, but without counting Saturdays and Sundays?

  sub Delta_Business_Days
  {
      my(@date1) = (@_)[0,1,2];
      my(@date2) = (@_)[3,4,5];
      my($minus,$result,$dow1,$dow2,$diff,$temp);
      $minus  = 0;
      $result = Delta_Days(@date1,@date2);
      if ($result != 0)
      {
          if ($result < 0)
          {
              $minus = 1;
              $result = -$result;
              $dow1 = Day_of_Week(@date2);
              $dow2 = Day_of_Week(@date1);
          }
          else
          {
              $dow1 = Day_of_Week(@date1);
              $dow2 = Day_of_Week(@date2);
          }
          $diff = $dow2 - $dow1;
          $temp = $result;
          if ($diff != 0)
          {
              if ($diff < 0)
              {
                  $diff += 7;
              }
              $temp -= $diff;
              $dow1 += $diff;
              if ($dow1 > 6)
              {
                  $result--;
                  if ($dow1 > 7)
                  {
                      $result--;
                  }
              }
          }
          if ($temp != 0)
          {
              $temp /= 7;
              $result -= ($temp << 1);
          }
      }
      if ($minus) { return -$result; }
      else        { return  $result; }
  }

This solution is probably of little practical value, however, because it doesn't take legal holidays into account.

See Date::Calendar(3) for how to do that.

17)

How can I "normalize" the output of the "Delta_YMDHMS()" (or "Delta_YMD()") function so that it contains only positive values?

I.e., how can I show a difference in date (and time) in a more human-readable form, for example in order to show how much time until (or since) the expiration of something (e.g. an account, a domain, a credit card, etc.) is left (has passed)?

a) Delta_YMDHMS():

  #!perl
  use strict;
  use Date::Calc qw(Today_and_Now Delta_YMDHMS Add_Delta_YMDHMS Delta_DHMS Date_to_Text);
  my $today = [Today_and_Now()];
  my $target = [2005,1,1,0,0,0];
  my $sign = "until";
  my $delta = Normalize_Delta_YMDHMS($today,$target);
  if ($delta->[0] < 0)
  {
      $sign = "since";
      $delta = Normalize_Delta_YMDHMS($target,$today);
  }
  printf("Today is %s %02d:%02d:%02d\n", Date_to_Text(@{$today}[0..2]), @{$today}[3..5]);
  printf
  (
      "%d year%s, %d month%s, %d day%s, %d hour%s, %d minute%s, %d second%s %s %s %02d:%02d:%02d\n",
      $delta->[0], (($delta->[0]==1)?'':'s'),
      $delta->[1], (($delta->[1]==1)?'':'s'),
      $delta->[2], (($delta->[2]==1)?'':'s'),
      $delta->[3], (($delta->[3]==1)?'':'s'),
      $delta->[4], (($delta->[4]==1)?'':'s'),
      $delta->[5], (($delta->[5]==1)?'':'s'),
      $sign,
      Date_to_Text(@{$target}[0..2]),
      @{$target}[3..5]
  );
  sub Normalize_Delta_YMDHMS
  {
      my($date1,$date2) = @_;
      my(@delta);
      @delta = Delta_YMDHMS(@$date1,@$date2);
      while ($delta[1] < 0 or
             $delta[2] < 0 or
             $delta[3] < 0 or
             $delta[4] < 0 or
             $delta[5] < 0)
      {
          if ($delta[1] < 0) { $delta[0]--; $delta[1] += 12; }
          if ($delta[2] < 0)
          {
              $delta[1]--;
              @delta[2..5] = (0,0,0,0);
              @delta[2..5] = Delta_DHMS(Add_Delta_YMDHMS(@$date1,@delta),@$date2);
          }
          if ($delta[3] < 0) { $delta[2]--; $delta[3] += 24; }
          if ($delta[4] < 0) { $delta[3]--; $delta[4] += 60; }
          if ($delta[5] < 0) { $delta[4]--; $delta[5] += 60; }
      }
      return \@delta;
  }

b) Delta_YMD():

  #!perl
  use strict;
  use Date::Calc qw(Today Delta_YMD Add_Delta_YM Delta_Days Date_to_Text);
  my($sign,$delta);
  my $today = [Today()];
  my $target = [2005,1,1];
  if (Delta_Days(@$today,@$target) < 0)
  {
      $sign = "since";
      $delta = Normalize_Delta_YMD($target,$today);
  }
  else
  {
      $sign = "until";
      $delta = Normalize_Delta_YMD($today,$target);
  }
  print "Today is ", Date_to_Text(@$today), "\n";
  printf
  (
      "%d year%s, %d month%s, %d day%s %s %s\n",
      $delta->[0], (($delta->[0]==1)?'':'s'),
      $delta->[1], (($delta->[1]==1)?'':'s'),
      $delta->[2], (($delta->[2]==1)?'':'s'),
      $sign,
      Date_to_Text(@$target)
  );
  sub Normalize_Delta_YMD
  {
      my($date1,$date2) = @_;
      my(@delta);
      @delta = Delta_YMD(@$date1,@$date2);
      while ($delta[1] < 0 or $delta[2] < 0)
      {
          if ($delta[1] < 0) { $delta[0]--; $delta[1] += 12; }
          if ($delta[2] < 0)
          {
              $delta[1]--;
              $delta[2] = Delta_Days(Add_Delta_YM(@$date1,@delta[0,1]),@$date2);
          }
      }
      return \@delta;
  }

Note that for normalizing just a time vector, you can use the built-in function "Normalize_DHMS()". However, this will yield either all positive OR all negative values, NOT all positive values as above.

SEE ALSO

Date::Calc::Object(3), Date::Calendar(3), Date::Calendar::Year(3), Date::Calendar::Profiles(3).

  "The Calendar FAQ":
  http://www.tondering.dk/claus/calendar.html
  by Claus Tondering <claus@tondering.dk>

LIMITATIONS

In the current implementation of this package, the selected language is stored in a global variable.

Therefore, when you are using a threaded Perl, this may cause undesired side effects (of one thread always selecting the language for ALL OTHER threads as well).

VERSION

This man page documents "Date::Calc" version 5.4.

AUTHOR

  Steffen Beyer
  mailto:sb@engelschall.com
  http://www.engelschall.com/u/sb/download/

COPYRIGHT

Copyright (c) 1995 - 2004 by Steffen Beyer. All rights reserved.

LICENSE

This package is free software; you can redistribute it and/or modify it under the same terms as Perl itself, i.e., under the terms of the "Artistic License" or the "GNU General Public License".

The C library at the core of this Perl module can additionally be redistributed and/or modified under the terms of the "GNU Library General Public License".

Please refer to the files "Artistic.txt", "GNU_GPL.txt" and "GNU_LGPL.txt" in this distribution for details!

DISCLAIMER

This package is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the "GNU General Public License" for more details.