Steffen Beyer > Date-Pcalc-1.2 > Date::Pcalc

Download:
Date-Pcalc-1.2.tar.gz

Dependencies

Annotate this POD

CPAN RT

Open  1
View Bugs
Report a bug
Module Version: 1.2   Source  

NAME ^

Date::Pcalc - Gregorian calendar date calculations

PREFACE ^

This package consists of a Perl module 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)).

This module is a direct translation of Steffen Beyer's excellent Date::Calc module to Perl.

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.

Note that this package EXTRAPOLATES the Gregorian calendar BACK until the year 1 A.D. -- even though the Gregorian calendar was only adopted in 1582 by most (not all) European countries, in obedience to the corresponding decree of catholic pope Gregor I 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.

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

SYNOPSIS ^

  use Date::Pcalc qw(
      Days_in_Year
      Days_in_Month
      Weeks_in_Year
      leap_year
      check_date
      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
      Add_Delta_Days
      Add_Delta_DHMS
      Add_Delta_YMD
      System_Clock
      Today
      Now
      Today_and_Now
      Easter_Sunday
      Decode_Month
      Decode_Day_of_Week
      Decode_Language
      Decode_Date_EU
      Decode_Date_US
      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
  );

  use Date::Pcalc 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_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);

  Week_of_Year
      ($week,$year) = Week_of_Year($year,$month,$day);

  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);

  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_YMD
      ($year,$month,$day) =
      Add_Delta_YMD($year,$month,$day,
                    $Dy,$Dm,$Dd);

  System_Clock
      ($year,$month,$day, $hour,$min,$sec, $doy,$dow,$dst) =
      System_Clock();

  Today
      ($year,$month,$day) = Today();

  Now
      ($hour,$min,$sec) = Now();

  Today_and_Now
      ($year,$month,$day, $hour,$min,$sec) = Today_and_Now();

  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))

  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);

  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))

  Version
      $string = Date::Pcalc::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::Pcalc 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::Pcalc 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 wether a given date lies within a certain range of dates?

  use Date::Pcalc 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 verify wether someone has a certain age?

  use Date::Pcalc 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";
  }
4)

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::Pcalc qw( Today Day_of_Week );

  ($year,$month,$day) = Today();

  $week = int(($day + Day_of_Week($year,$month,1) - 2) / 7) + 1;
5)

How do I calculate wether 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::Pcalc 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.
6)

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

Solution #1:

  use Date::Pcalc 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::Pcalc 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::Pcalc qw( Standard_to_Business Today
                     Business_to_Standard );

  @business = Standard_to_Business(Today());

  $business[2] = 3; # 3 = Wednesday

  @date = Business_to_Standard(@business);
7)

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

  use Date::Pcalc 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);
8)

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

  use Date::Pcalc 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
9)

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

Solution #1 (holidays NOT taken into account):

  use Date::Pcalc 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::Pcalc 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; }
  }
10)

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

  use Date::Pcalc 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
11)

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::Pcalc 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!
  }
12)

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::Pcalc 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);

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

13)

How can I iterate through a range of dates?

  use Date::Pcalc 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.

14)

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

  use Date::Pcalc 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.

15)

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 ALSO ^

  "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 named $pcalc_Language.

Therefore, on systems where the "Date::Pcalc" module is a shared library, or as soon as Perl will be capable of multi-threading, this may cause undesired effects (of one process or thread always selecting the language for ALL OTHER processes or threads as well).

VERSION ^

This man page documents "Date::Pcalc" version 1.2.

AUTHOR ^

  J. David Eisenberg
  4604 Corrida Circle
  San Jose, California 95129
  USA

  mailto: nessus@best.com
  http://www.best.com/~nessus/date/pcalc.html

Please contact me by e-mail whenever possible!

All the mistakes in this implementation are caused by my translation of the original C code to perl.

Anything that works does so because it was written correctly by the original author:

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

COPYRIGHT ^

Copyright (c) 1999-2001 by J. David Eisenberg; portions Copyright (c) 1993-2001 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".

Please refer to the files "Artistic.txt" and "GNU_GPL.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.