NAME

Date::Gregorian - dates on the Gregorian Calendar

SYNOPSIS

  # note: -DateParse uses Date::Parse and requires Perl5 embedding.
  use Date::Gregorian qw(date localdate gmdate now -DateParse);

  # creating non-timezones date object ('floating' time)
  $date = Date::Gregorian.new($year,$month,$day,$hour,$min,$sec);
  $date = date [$year,$month,$day,$hour,$min,$sec];
  $date = date ( :year($year), :month($month), :day($day),
                 :hour($hour), :min($min),     :sec($sec) );

  $date = date "2001-11-12 07:13:12";  # or any ISO-8601 format

  $date = date 1e9;  # Unix epoch time if an Int
  $date = date 0.0;  # Perl6 date if a Float

  # add a month to it:
  $date += "1M";
  $date += "P1M";         # ISO-8601 Periods supported
  $date += [0,1,0,0,0,0]; # constructor formats supported
  $date += 31*24*60*60;   # seconds

  # intuitive arithmetic between dates via Duration::Gregorian
  $days_between = ( date('2001-11-12')
                    - date('2001-07-04') ).days; # prints 131

  # creating date object in local timezone
  $date = localdate "2001-12-11";      # force local time
  $date = now;                         # the same as date(time)

  # creating absolute date object (UTC)
  $date = Date::Gregorian.new([$yyyy,$mm,$dd,$HH,$MM,$SS], 'UTC');
  $date = gmdate "2001-11-12 17:13";

  # creating absolute date object in any other timezone
  $date = date [$year,$month,$day,$hour,$min,$sec], 'Iceland';
  $date = date "2001-11-12 17:13", 'Iceland';

  # getting parts out - comprehensive methods available
  my $year = $date.year;     # full
  my $year_C = $date._year;  # year - 1900 (or 1904 on Mac?)
  my $month = $date.mon;     # 1..12
  my $month_C = $date._mon;  # 0..11
  ($year,$month,$day,$hour,$min,$sec)=$date.array;
  print date([2001,12,11,4,5,6]).truncate;
                               # will print "2001-12-11"

  # modifying date properties
  $date.set( :year<1977>, :sec<14> );
  $date.year = 1978;
  $date.sec  = 15;

  # constructing new date based on an existing one:
  $new_date = $date.clone;
  $new_date.clone( :year<1977>, :sec<14> );

  # constructing a new date, which is the same absolute time as
  # the original, but expressed in another timezone:
  $new_date = $date.as_tz('Iceland');

  # comparison between absolute dates
  say $date1 > $date2 ?? "I am older" :: "I am younger";

  # comparison between relative dates
  say $reldate1 > $reldate2 ?? "I am faster" :: "I am slower";

  # Adding / Subtracting months and years are sometimes
  # tricky; in general be sure whether you mean "one month"
  # or "30 days", etc:
  say date("2001-01-29") + '1M' - '1M'; # gives "2001-02-01"
  say date("2000-02-29") + '1Y' - '1Y'; # gives "2000-03-01"

DESCRTIPTION

Date::Gregorian represents dates on the Gregorian calendar as Perl 6 objects.

You can use the +, -, < and > operators as with native perl data types.

OVERVIEW

Date::Gregorian operations fall into these categories:

  • creating a new date object from some representation of a date

  • performing operations on a date object (+, -, comparison)

  • getting results, or parts of results back

Creating a new Date::Gregorian object

You can create a date object by the (exported by default) "date", "localdate" or "gmdate" functions, or by calling the Date::Gregorian.new() constructor.

date() and Date::Gregorian.new() are equivalent, both have two arguments (each of which may be composed of multiple parts): The date and (optionally) the timezone.

  $date1 = date [2000,11,12];
  $date2 = Date::Gregorian->new([2000,06,11,13,11,22],'GMT');
  $date2 = $date1->new([2000,06,11,13,11,22]);

You can also supply the date information in any of the following forms:

  $date = date("2003-12-31 12:56:59");
  $date = date("2003-12-31 12:56:59 +12:00");
  $date = date([$year,$month,$day,$hour,$min,$sec]);
  $date = date( :year(2003), :month(12), :day(31),
                :hour(12),   :min(56),   :sec(59),
                :tz<Pacific/Auckland> );

See "Valid Date Formats" for more information on the allowable formats of the date.

Dates may be cloned using the constructor, or the clone() method:

  $date2 = $date.new();
  $date2 = $date.clone();
  $date2 = $date.clone( :year($newyear), :sec($newsec) );

How the Timezone is determined

When constructing a new date from scratch, if the timezone information is omitted, then the date is "floating" (unless you set $Date::Gregorian::DEFAULT_TIMEZONE).

If the date is being extracted from the system clock, then it always has a time zone set.

You can override this behaviour to always assume the local time zone, by setting $Date::Gregorian::DEFAULT_TIMEZONE to $Date::Gregorian::LOCAL_TIMEZONE.

localdate $x is equivalent to date $x, $Date::Gregorian::LOCAL_TIMEZONE, and gmdate $x is equivalent to date $x, 'UTC'

  $date1 = localdate [2000,11,12];
  $date2 = gmdate [2000,4,2,3,33,33];

  $date = localdate(time);

The time offset can also be specified in ISO-8601 notation; however the time zone of the resulting date is fixed to that offset, and not to any country's 'dynamic' time zone:

  $date = date("2001-01-17 10:10:00 +12:00");
  print $date->tz;   # prints "+12:00";

Such dates may freely be converted to a local time zone using the .clone(:tz($timezone)) copy constructor, or the .tz($new_timezone)) mutator (or any of its equivalent variants):

  $date = date("2001-01-17 10:10:00 +12:00")
             ->to_tz("Pacific/Auckland");

Note that if you want to merely change the timezone without adjusting the time, you must first convert the date to floating, by setting the timezone to the undefined value or "Floating".

Dates that have a timezone attached are converted according to the interpretation of the TimeZone you passed; 'floating' dates simply have the timezone attached.

Valid Date Formats

ISO-8601 formats (String)

All ISO-8601 formats are supported for the scalar use; for example:

Dates:

  YY
  -MM
  --DD
  YY-MM
  YYYY
  YYDDD
  YY-DDD
  YYYYMM
  YYYY-MM
  YYYYDDD
  YYYYMMDD
  YY-MM-DD
  YYYY-MM-DD
  YYYYWNN
  YYYYWNND

(note: the W is a literal W or w, and is for specifying the week of the year).

These formats must be strings. Int inputs will be interpreted as Unix epoch seconds, and Float inputs as Perl epoch seconds.

Times (must be preceded/seperated from the date with a literal T or t where ambiguous with the above forms, or follow a complete date with a space):

  HH
  HH.HHH...
  HH,HHH...
  HH:MM
  HHMM
  HH:MM.MMM...
  HH:MM,MMM...
  HH:MM:SS
  HHMMSS
  HH:MM:SS.SSS...
  HH:MM:SS,SSS...
Unix Dates: Integers

Valid integers are interpreted as a unix epoch times. Some systems may not handle epoch times outside of the signed 32 bit range.

Note that this is ambiguous with compact ISO-8601 date forms above when the type of the scalar is a junction of Str and Int compatible types. To force the intended behaviour in this case, it is recommended that you explicitly force integer convertion by prepending 0+ to the time (or using int()).

These are the recommended methods for unix date input:

  $date = date(0+$myEpochTime);
  $date = date(int($myEpochTime));
  $date = date(:epoch($myEpochTime));
  $date = epoch($myEpochTime);

This form of construction implies a local timezone by default.

Perl Dates: Floats
  $date = date(time());

Perl dates are returned as floating point seconds since 1999. 0.0 means midnight on the First of January, 2005, UTC.

This form of construction implies a local timezone by default.

[$year, $month, $day, $hour, $min, $sec, $tz]

An array reference with 7 or fewer elements. Individual elements may be undefined, in which case they simply remain unspecified and do not default to any particular time, like midnight or the 1st of January 1900 or anything like that.

However, such implication of default values may happen when performing arithmetic with absent units; in which case the lower unit always defaults to the lowest value, while upper units remain unspecified.

$year, $month, $day, $hour, $min, $sec, $tz

An un-adverbed list with 7 or fewer elements. Individual elements may be undefined.

:year($year), :month($month) ...

Adverb form of list constructor input.

additional input formats

You can specify -DateParse as an import parameter, e.g:

  use Date::Gregorian qw(date -DateParse);

With this, the module will try to load Date::Parse Perl 5 module, and if it find it then all these formats can be used as an input. See Date::Parse for more.

Date Operations

Be sure to read carefully "CAVEATS WORKING WITH DATES" near the end of this document before making you write really bad code with these operators :-). Note that subclasses of Date::Gregorian are free to use their own semantics for these operations which may or may not remain compatible on the finer details.

addition

You can add the following to a Date::Gregorian object:

  • a valid Duration::Gregorian object

  • any object that .does(Duration) and can marshall itself to any Gregorian calendar unit (eventually)

  • any valid Duration::Gregorian constructor arguments

It means that you don't need to create a new Duration::Gregorian object every time when you add something to the Date::Gregorian object, it creates them automatically:

  $date = date('2001-12-11') + Duration::Gregorian.new('3Y');

can be:

  $date = date('2001-12-11') + '3Y';   # 2004-12-11

See Duration::Gregorian for a full list of constructors.

Overflows never propagate into smaller fields; the extra time is simply discarded;

  $date = date('2001-12-31') + '6M';   # 2002-06-30
subtraction

Subtraction from a Date::Gregorian object returns a new object that represents a date that is the amount of time you specified in the past;

  $date = date('2001-12-11') - '3Y';   # 1998-12-11

Overflows never propagate into smaller fields; the extra time is simply discarded;

  $date = date('2001-12-31') - '6M';   # 2001-06-30
difference

Subtracting two dates from one another returns a Duration::Gregorian object that represents a specific interval.

  # will become 1998-12-11/P3Y or a close analog
  $interval = date('2001-12-11') - date('1998-12-11');

  # will become 1998-12-11/-P3Y or a close analog
  $interval = date('1998-12-11') - date('2001-12-11');

You can't add two Date::Gregorian objects together.

comparison

You can compare two Date::Gregorian objects, or one Date::Gregorian object and another valid Date::Gregorian constructor argument list.

It means that you don't need to bless both objects, one of them can be a simple string, array ref, hash ref, etc (see how to create a date object).

  if ( date('2001-11-12') > date('2000-11-11') ) { ... }

or

  if ( date('2001-11-12') > '2000-11-11' ) { ... }
copying

Dates can be copied, either by adding 0 to them or by calling .new() or .clone():

  my $date = date('2001-11-12');

  my $date_copy = $date + 0;
  $date_copy = $date.clone(:hour(12)); # can set while copying
  $date_copy = $date.new(:hour(12));   # if you prefer

Accessing data from a Date::Gregorian object

(to be reviewed, from Class::Date)

  $date.year;        # year, e.g: 2001
  $date._year;       # C year, e.g. 101
  $date.yr;          # 2-digit year 0-99, e.g 1
  $date.mon;         # month 1..12
  $date.month;       # same as prev.
  $date._mon;        # C month; 0..11
  $date._month;      # same as prev.
  $date.day;         # day of month
  $date.mday;        # day of month
  $date.day_of_month;# same as prev.
  $date.hour;
  $date.min;
  $date.minute;      # same as prev.
  $date.sec;
  $date.second;      # same as prev.
  $date.wday;        # 1 = Sunday
  $date._wday;       # C weekday; 0 = Sunday
  $date.day_of_week; # same as prev.
  $date.yday;
  $date.day_of_year; # same as prev.
  $date.isdst;       # DST?
  $date.daylight_savings; # same as prev.
  $date.epoch;       # Perl epoch seconds
  $date.time_t;      # UNIX time_t
  $date.unix;        # same as above
  $date.mon_name;    # name of month, eg: March
  $date.month_name;  # same as prev.
  $date.wday_name;   # Thursday
  $date.day_of_week_name # same as prev.
  $date.hms          # 01:23:45
  $date.ymd          # 2000-02-29
  $date.mdy          # 02-29-2000
  $date.dmy          # 29-02-2000
  $date.string       # Print as ISO-8601; eg 2000-02-29T12:21:11Z
  "$date"            # same as prev.
  $date.tzoffset     # timezone-offset, eg: +12
  $date.strftime($format) # POSIX strftime
  $date.tz_code      # returns the current timezone code, eg: CET
  $date.tz_base      # returns the non-daylight saving code, eg: CET
  $date.tz_dst       # returns the daylight savings code, eg: CEST
                     # or "undef" if no daylight savings

Setting values of a individual date fields will never cause the date to "roll over" to the next major unit; however, negative integers indicate distance from the "end" of the maximum value of the field.

  $date.days = 60;    # sets it to 28, 29, 30 or 31
  $date.days = -2;    # sets it to 27..30

When multiple values are set (as via .clone()), they are processed in order of major unit to minor unit.

Note the following:

  $date = date(:month(2), :day(29));   # valid, -02-29
  $date.year = 2000;                   # 2000-02-29
  $date.year = 2000;                   # 2000-02-29

  $date = date(:day(-2));              # valid, --30
  $date.month = 2;                     # now -02-28
  $date.year  = 2001;                  # now 2001-02-27

  $date = date(:day(31));              # valid, --31
  $date.month = 2;                     # now -02-29
  $date.year  = 2001;                  # now 2001-02-28

CAVEATS WORKING WITH DATES

The Gregorian calendar is like many calendars in that its units of measurement are not always the same length.

On the Gregorian calendar, months can be 28 (one year in four, 29), 30 or 31 days long. This leads to years being either 365 or 366 days long, determined by rules based on an old determination of the length of a Solar year.

Days in a particular time zone are always 24 hours long, but some countries will change time zones around times of Solar Equinox (usually called "Daylight Savings"), so that the "local" day will appear to be 23 hours long, 25 hours long or potentially any length.

Little known is that also minutes can have either 60, 61 or 59 seconds. These are called "leap seconds", and are used to counter for errors in the original estimations of the earth's orbital and rotational speeds, as well as variations caused by natural events not easily predictable, such as earthquakes and celestial impacts. Due to the "adjustment" nature of leap seconds, they are not considered by this module when performing any calculations, but it is possible to create dates with a second value of 60 by setting a special flag.

These factors lead to some people trying to derive time down to some perceived "atomic" unit, based on phenomena thought to be constant such as nanoseconds or the rate of radioactive decay of certain Caesium isotopes, then making all date calculations in terms of those units. However, this is not necessary for the vast majority of applications; you need simply always bear in mind that durations in a particular unit can't be expected to be the same when applied to different times.

If this simple engineering principle is kept in mind when dealing with dates, common date handling bugs can easily be circumvented without the prohibitive computational and engineering costs of counting caesium decay. Examples of save vs unsafe code follow.

In summary, this module represents the calendar, not the calendar's conformance with any particular body's idea of the "real" world.

SAFE VS UNSAFE DATE CODE

Here is a perfectly safe way to find two closely corresponding intervals of one month, one year apart:

  my $first_start = date("2005-01-01");
  my $second_start = $first_start - "P1Y";

  my $first_end = $first_start + "P1M";
  my $second_end = $second_start + "P1M";

Given that these dates start at the same time of year, you'd think that the two start and finish dates represent the same length of time.

However, this would not necessarily the case if a different start date, such as the 1st of February was picked instead;

  my $first_start = date("2005-02-01");
  my $second_start = $first_start - "P1Y";

  my $first_end = $first_start + "P1M";
  my $second_end = $second_start + "P1M";

  say "First interval is  {  ($first_end - $first_start).days  } days";
  say "Second interval is { ($second_end - $second_start).days } days";

This prints:

  First interval is 28 days
  Second interval is 29 days

This is fine; after all, you expressed the operations in months, so it makes sense that when you convert this to another unit that they might not be the same; after all, calendars are generally not regular.

If you wanted them to always be the same length (in days), you might have written:

  my $first_start = date("2005-02-01");
  my $second_start = $first_start - "P1Y";

  my $first_end = $first_start + "P1M";
  my $length_first = ($first_end - $first_start);

  my $second_end = $second_start + $length_first;

However, this is still not correct, because $length_first will be the ISO interval "20050201/20050301", or maybe "20050201/P1M", which is one month. So, the second interval will still be 29 days, longer than the first.

To get around this, you can convert intervals between units;

  my $second_end = $second_start + $length_first.in_days;

This time, $second_end ends up as "20040228", and both intervals are the same number of days.

ADJUSTING FOR DAYLIGHT SAVINGS

To adjust for dalight savings, make sure all timezones are in the local timezone and that all calculations are computed in hours.

 ... (example to follow) ...

ADJUSTING FOR MONTH LENGTH

The MONTH_BORDER_ADJUST setting in Class::Date has not been preserved, it can be achieved in other ways;

  # PERL 5 - Class::Date
  $Class::Date::MONTH_BORDER_ADJUST = 0; # this is the default
  print date("2001-01-31")+'1M'; # will print 2001-03-03 due to
                                 # overflow
  $Class::Date::MONTH_BORDER_ADJUST = 1;
  print date("2001-01-31")+'1M'; # will print 2001-02-28

  # Date::Gregorian
  say date("2001-01-31")+'1M'; # will print 2001-02-28
  say date("2001-01-31")+'31D'; # will print 2001-03-03

Something quite important is happening here; adding a "month" goes forward that number of months, then as 31 is not a valid day of the month for February, it sets the date to the highest possible value.

ADJUSTING FOR LEAP YEARS

 ... (text and example to follow) ...

1 POD Error

The following errors were encountered while parsing the POD:

Around line 293:

=back without =over