=head1 NAME
Date::Gregorian - dates on the Gregorian Calendar
=head1 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"
=head1 DESCRTIPTION
B represents dates on the Gregorian calendar as Perl
6 objects.
You can use the C, C, C> and C> operators as with
native perl data types.
=head2 OVERVIEW
Date::Gregorian operations fall into these categories:
=over
=item *
creating a new date object from some representation of a date
=item *
performing operations on a date object (+, -, comparison)
=item *
getting results, or parts of results back
=back
=head1 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
C constructor.
C and C 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 );
See L for more information on the allowable
formats of the date.
Dates may be I using the constructor, or the C
method:
$date2 = $date.new();
$date2 = $date.clone();
$date2 = $date.clone( :year($newyear), :sec($newsec) );
=head2 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
C).
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 C to
C.
C is equivalent to C, and C is equivalent to
C
$date1 = localdate [2000,11,12];
$date2 = gmdate [2000,4,2,3,33,33];
$date = localdate(time);
The I can also be specified in ISO-8601 notation; however
the I 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
C copy constructor, or the
C 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 "C".
Dates that have a timezone attached are converted according to the
interpretation of the TimeZone you passed; 'floating' dates simply
have the timezone attached.
=head2 Valid Date Formats
=over
=item 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 C or C, and is for specifying the week
of the year).
These formats B. B inputs will be interpreted
as Unix epoch seconds, and B inputs as Perl epoch seconds.
Times (must be preceded/seperated from the date with a literal C or
C 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...
=item 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 C and C
compatible types. To force the intended behaviour in this case, it is
recommended that you explicitly force integer convertion by prepending
C<0+> to the time (or using C).
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.
=item 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.
=item [$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.
=item $year, $month, $day, $hour, $min, $sec, $tz
An un-adverbed list with 7 or fewer elements. Individual elements may
be undefined.
=item :year($year), :month($month) ...
Adverb form of list constructor input.
=item additional input formats
You can specify C 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
L for more.
=back
=back
=head1 Date Operations
Be sure to read carefully L near the end
of this document before making you write really bad code with these
operators :-). Note that subclasses of C are free to
use their own semantics for these operations which may or may not
remain compatible on the finer details.
=over 4
=item addition
You can add the following to a Date::Gregorian object:
=over
=item
a valid Duration::Gregorian object
=item
any object that C and can marshall itself to any
Gregorian calendar unit (eventually)
=item
any valid Duration::Gregorian constructor arguments
=back
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 L 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
=item 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
=item 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.
=item 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' ) { ... }
=item copying
Dates can be copied, either by adding 0 to them or by calling
C or C:
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
=back
=head1 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 C), 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
=head1 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.
=head2 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 C 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, C ends up as "C<20040228>", and both intervals
are the same number of days.
=head2 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) ...
=head2 ADJUSTING FOR MONTH LENGTH
The C setting in C 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.
=head2 ADJUSTING FOR LEAP YEARS
... (text and example to follow) ...
=cut