=head1 NAME

Date - extremely fast Date framework with timezones, microseconds, relative dates and intervals support.



=head1 SYNOPSIS

    use Date qw/date date_ymd rdate rdate_ymd tzset/;
    
    my $date = Date->new($epoch); # using server's local timezone
    $date = Date->new("2013-03-05 23:45:56.345");
    $date = Date->new_ymd($y,$m,$d,$h,$m,$s);
    $date = Date->new_ymd(year => $y, month => $m, day => $d, hour => $h, min => $m, sec => $s, mksec => $mks);
    $date = Date::now(); # same as Date->new(time()) but faster
    $date = Date::today(); # same as Date->new(time())->truncate but faster
    $date = Date::now_hires(); # current time with microseconds
    
    # create using function 'date'
    tzset('Europe/Moscow'); # using 'Europe/Moscow' as server's local timezone
    $date = date(123456789);
    $date = date("2001/11/12 07:13:12", 'America/New_York'); # $date operates in custom time zone
    
    # creating relative date object
    # (normally you don't need to create this object explicitly)
    $reldate = Date::Rel->new("3Y 1M 3D 6h 2m 4s");
    $reldate = Date::Rel->new("6Y");
    $reldate = Date::Rel->new($secs);
    $reldate = Date::Rel->new_ymd($year,$month,$day,$hour,$min,$sec);
    $reldate = Date::Rel->new_ymd(year => $year, month => $month, day => $day, hour => $hour, min => $min, sec => $sec);
    $reldate = rdate "-1M -3D 6h";
    $reldate = 3*MONTH; # "3M"
    $reldate = 2*YEAR + MONTH - 30*DAY; # "2Y 1M -30D"
    print $reldate/2; # 1Y 5h 14m 32s
    $reldate = YEAR/2 + HOUR/2; # 6M 30m
    
    $date;              # prints the date in default output format (ISO/SQL format)
    $date->epoch;       # unix timestamp
    $date->year;        # year, e.g: 2001
    $date->c_year;      # year - 1900, e.g. 101
    $date->yr;          # 2-digit year 0-99, e.g 1
    $date->month;       # month 1..12
    $date->mon;         # same as prev.
    $date->c_month;     # month 0..11
    $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->mksec;
    $date->wday;        # 1-7, Sunday-Saturday
    $date->day_of_week; # same as prev.
    $date->c_wday;      # 0-6, Sunday-Saturday
    $date->ewday;       # 1-7, Monday-Sunday
    $date->yday;        # day of year [1-366]
    $date->day_of_year; # same as prev.
    $date->c_yday;      # [0-365]
    $date->isdst;       # DST?
    $date->daylight_savings; # same as prev.
    $date->strftime($format);
    $date->month_name;  # March
    $date->month_sname; # Mar
    $date->wday_name;   # Thursday
    $date->wday_sname;  # Thu
    $date->to_string    # 2000-02-29 12:21:11
    "$date"             # same as prev.
    $date->to_string(Date::FORMAT_ISO8601); # 2000-02-29T12:21:11+03:00
    $date->gmtoff       # current offset from UTC (in seconds)
    $date->tzname       # returns the timezone name (EST, EET, etc)
    $date->tzlocal      # true if $date is in local time zone
    $date->timezone     # returns timezone object
    $date->timezone('GMT+5')  # changes $date's timezone (saving YMDhms)
    $date->to_timezone('UTC') # changes $date's timezone (saving epoch)
    
    ($year,$month,$day,$hour,$min,$sec) = $date->array;
    ($sec,$min,$hour,$day,$mon,$year,$wday,$yday,$isdst) = $date->struct;
    
    # constructing new date based on an existing one:
    $new_date = $date->clone;
    $new_date = $date->clone(year => 1977, sec => 14, tz => 'Australia/Melbourne');
    # valid keys: year, month, day, hour, min, sec, tz
    
    $date->month_begin   # First day of the month (date object)
    $date->month_end     # Last day of the month
    $date->days_in_month # 28..31
    
    # error handling
    $a = date($date_string);
    if ($a) { # valid date
      ...
    } else { # invalid date
      if ($a->error == Date::Error::parser_error) { ... }
      print $a->error->message;
    }
    
    # date range check
    Date::range_check(0); # this is the default
    print date("2001-02-31"); # will print 2001-03-03
    Date::range_check(1);
    print date("2001-02-31"); # will print nothing
    
    # getting values of a relative date object
    $reldate->year;
    $reldate->month;
    $reldate->day;
    $reldate->hour;
    $reldate->min;
    $reldate->sec;
    "$reldate";             # reldate in "1Y 2M 3D 4h 5m 6s" format
    $reldate->duration;     # duration in seconds
    int $reldate;           # same as prev
    $reldate->to_secs;      # same as prev
    $reldate->to_mins;      # relative date in minutes
    $reldate->to_hours;     # relative date in hours
    $reldate->to_days;      # relative date in days
    $reldate->to_months;    # relative date in months
    $reldate->to_years;     # relative date in years
     
    # arithmetic with dates:
    print date_ymd(2001,12,11,4,5,6)->truncate; # "2001-12-11"
    
    $new_date = $date + $reldate;
    $date2    = $date - '3Y 2D';   # 3 Years and 2 days
    $date3    = $date + rdate_ymd(1,2,3);   # $date plus 1 year, 2 months, 3 days
    
    # accurate relative dates
    $rel = $date1 - $date2;
    $rel->duration;              # accurate number of seconds in interval
    $rel->to_months;             # accurate number of months in interval
    $rel->includes("2013-01-01") # returns -1, 0, or 1
    
    $days_between = ($date1 - $date2)->to_days;
    
    # comparison between absolute dates
    print $date1 > $date2 ? "I am older" : "I am younger";
    
    # comparison between relative dates
    print $reldate1 > $reldate2 ? "I am faster" : "I am slower";
    
    # Adding / Subtracting months and years are sometimes tricky:
    print date("2001-01-29") + '1M' - '1M'; # gives "2001-02-01"
    print date("2000-02-29") + '1Y' - '1Y'; # gives "2000-03-01"

    #strict mode
    {
        use Date::strict;
        my $d = Date->new("invalid date"); # throws
        my $rel = Date::Rel->new("1X"); # throws
        $d += "1X"; # throws
    }



=head1 DESCRIPTION

Date is a port of C++ C<panda::date> framework which is complete feature-rich date framework, supporting timezones (not just offsets), microseconds,
relative dates and intervals. Timezones with leap seconds (right/*) are also supported.

L<Date> supports dates between -2147483648/01/01 00:00:00 and 2147483647/12/31 23:59:59 (on 64bit perls).

With L<Date> you can perform some operations even faster than in plain C program using stdlibc functions because it has its own efficient 
low-level time functions implementation.



=head1 CLASS METHODS

=head4 new([$epoch = 0], [$timezone = local])

Creates date from UNIX timestamp (number of seconds since 1970). C<$epoch> may be non-integer value in which case microseconds is set.


=head4 new($string, [$timezone = local], [$format = INPUT_FORMAT_ALL])

Creates date from string in one of the following formats:

=over

=item Default iso date format

    YYYY-MM-DD[ HH:MM[:SS[.ss]][±hh[:mm]|Z]]
    YYYY/MM/DD[ HH:MM[:SS[.ss]][±hh[:mm]|Z]]

    2019-12-11
    2019-12-11 14:56:23
    2019-12-11 14:56:23.123
    2019-12-11 14:56:23Z
    2019/12/11 14:56+03:00
    2019/12/11 14:56:23.123+03

C<.ss> is a microseconds part 1-6 digits long.

=item ISO 8601 most popular subset

    YYYY-MM[-DD[THH[:MM[:SS[.ss]]][±hh[:mm]|Z]]]]
    YYYYMMDD[THH[MM[SS[.ss]]][±hh[mm]|Z]]]
    YYYY-Wnn[-n]

    2019-12
    2019-12-11T14:56:23
    20191211T145623.123Z
    20191211T145623.123+0300
    2019-12-11T14:56:23.123-03:30
    2019-W35
    2019-W35-2

=item RFC 822/1123

    Tue, 10 Dec 2019 00:00:00 GMT
    10 Dec 2019 00:00 Z

Only C locale supported.

=item RFC 850

    Tuesday, 01-Jan-19 03:04:00 GMT

Only C locale supported.

=item ANSI C

    Sun Sep  1 03:04:00 2019

Only C locale supported.

=item Dotted format

    DD.MM.YYYY

=item Comman Log Format

    dd/MMM/yyyy:hh:mm:ss +-hhmm
    [dd/MMM/yyyy:hh:mm:ss +-hhmm]

Only C locale supported.

=back

If timezone offset designator is present inside string, the date is created in unnamed timezone with constant offset from GMT (no transition rules).
C<$timezone> parameter is ignored in this case.

You can limit the number of allowed formats by passing C<$format> argument, which is the bitmask of the following constants:
INPUT_FORMAT_ALL(the default), INPUT_FORMAT_ISO, INPUT_FORMAT_ISO8601, INPUT_FORMAT_RFC1123, INPUT_FORMAT_RFC850, INPUT_FORMAT_ANSI_C, INPUT_FORMAT_DOT, INPUT_FORMAT_CLF.

If C<$string> is an invalid date or a valid date but in format other than specified, then C<error()> is set to C<Date::Error::parser_error>.

If you want it to throw an exception on invalid inputs, see L<Date::strict>.


=head4 new_ymd(%ymdhms)

Creates date from named YMDHMS arguments.

    Date->new(
        year  => 2019
        month => 12,
        day   => 31,
        hour  => 23,
        min   => 59,
        sec   => 59,
        mksec => 123456,
        tz    => 'Europe/Moscow',
    );

    
the default values for year is 1970, month&day - 1, others - 0, timezone - local.

If the date represented by YMDHMS is ambigious (falls into DST->STD timezone transition, i.e. when clock goes 1 hour back and certain HMS appears twice),
the greater epoch is preferred. This behaviour can be altered with additional C<isdst> hash parameter. The default value is C<-1> (auto).
C<0> means 'prefer standart time', C<1> means 'prefer daylight savings time'. If specified date is not ambigious, C<isdst> parameter is completely ignored.

    
=head4 new_ymd($year, [$month = 1], [$day = 1], [$hour = 0], [$min = 0], [$sec = 0], [$mksec = 0], [$timezone = local])

Created date from unnamed YMDHMS arguments.


=head4 new($date_object)

If C<$date_object> is a Date object, then returns copy of the date as C<clone()>.
Otherwise stringifies C<$date_object> and create date as C<new($string)>.
If this object does not have stringification overload, the result is date with parsing error.


=head4 new* [all variants]

Common behaviour for any of constructor variants.

If $timezone is present, created object will operate as if C<tzset($timezone)> was called, but without calling C<tzset()>.

If $timezone is absent (or undef or ""), local timezone will be used.
Further changes of local timezone via C<tzset()> won't affect constructed object.

If there is any errors while creating an object, C<error()> will be set and the date is set to epoch=0.
The object itself will return false in boolean context, empty string in string context and so on.


=head1 OBJECT METHODS

=head4 set(...)

=head4 set_ymd(%ymdhms | @ymdhms)

Set another date for object. This is faster than creating new object. See C<new()> and C<new_ymd()> for arguments details.


=head4 epoch([$epoch])

Get or set UNIX timestamp (integer or double with mksecs)


=head4 epoch_sec ()

Get UNIX timestamp as integer (without microseconds even if they are present).


=head4 year([$year])

Get or set year [-2**31, 2**31-1]


=head4 c_year([$year])

Get or set year counted from 1900 (C-style)


=head4 yr([$yr])

Get or set 2-digits-style-year [0-99]


=head4 month([$mon]), mon

Get or set month [1-12]


=head4 c_month([$mon])

Get or set month [0-11] (C-style)


=head4 day([$day]), mday, day_of_month

Get or set day of month [1-31]


=head4 hour([$hour])

Get or set hours [0-23]


=head4 min([$min]), minute

Get or set minutes [0-59]


=head4 sec([$sec]), second

Get or set seconds [0-60]


=head4 mksec([$mksec])

Get or set microseconds [0-999999]


=head4 wday([$wday]), day_of_week

Day of week. 1 = Sunday, ... , 7 = Saturday.

If you pass an argument then another day of the same week will be set.


=head4 c_wday([$wday])

C-style day of week. 0 = Sunday, ... , 6 = Saturday.

If you pass an argument then another day of the same week will be set.


=head4 ewday([$wday])

Europe-friendly day of week. 1 = Monday, ..., 7 = Sunday.

If you pass an argument then another day of the same week will be set.


=head4 yday([$yday]), day_of_year

Day of the year [1-366].

If you pass an argument then another day of the same year will be set.


=head4 c_yday([$yday])

C-style day of the year [0-365].

If you pass an argument then another day of the same year will be set.


=head4 isdst(), daylight_savings()

Is daylight savings time in effect now (true/false).


=head4 month_name()

Full name of the month


=head4 month_sname()

Short name of the month


=head4 wday_name()

Full name of the day


=head4 wday_sname()

Short name of the day


=head4 ""

Same as C<to_string()>.


=head4 to_string($format = Date::FORMAT_ISO)

Stringifies date using C<$format>.

Available formats:

=over

=item Date::FORMAT_ISO

Default format

    2019-12-31 23:59:59.123

Microseconds part will be absent if mksec == 0.

=item Date::FORMAT_ISO_TZ

C<iso> with timezone offset

    2019-12-31 23:59:59.123+03:00

=item Date::FORMAT_ISO_DATE

Only date

    2019-12-31

=item Date::FORMAT_ISO8601

    2019-12-31T23:59:59.123+03:00

=item Date::FORMAT_ISO8601_NOTZ

Same as C<iso8601> but omits printing timezone offset

    2019-12-31T23:59:59.123

=item Date::FORMAT_RFC1123

    Tue, 10 Dec 2019 00:00:00 GMT
    
=item Date::FORMAT_COOKIE

same as rfc1123

=item Date::FORMAT_RFC850

    Tuesday, 01-Jan-19 03:04:00 GMT

=item Date::FORMAT_ANSI_C

    Sun Sep  1 03:04:00 2019

=item Date::FORMAT_YMD

    2019/12/31

=item Date::FORMAT_DOT

    31.12.2019

=item Date::FORMAT_HMS

    23:59:59

=item Date::FORMAT_CLF

    10/Oct/1999:21:15:05 +0500

=item Date::FORMAT_CLF_BRACKETS

    [10/Oct/1999:21:15:05 +0500]

=back


=head4 strftime($format)

Works like C<Date::strftime()> with current date's fields passed as arguments.


=head4 'bool', to_bool()

Called implicitly in boolean context

    if ($date)
    $date ? EXPR1 : EXPR2
    $date && $something
    
Returns TRUE if date has no errors (i.e. has no parsing or out of range errors, etc), otherwise FALSE


=head4 '0+', to_number()

Returns C<epoch()> in numeric context


=head4 gmtoff()

Returns current timezone offset from UTC in seconds - may change when the date changes isdst/nodst.


=head4 tzname()

Returns the name of the object's timezone (Europe/Moscow, America/New_York, etc).


=head4 tzabbr()

Returns timezone abbreviation (EST, EET, etc) - may change when the date changes isdst/nodst.


=head4 tzlocal()

Returns TRUE if this object's timezone is set as local.


=head4 tz([$newzone])

With no arguments returns L<Date::Timezone> object associated with the date.

With argument changes the timezone of current object to $newzone preserving YMDhms information (epoch may change) and 
returns nothing. C<$newzone> may be timezone object, name or rule.


=head4 to_tz($newzone)

Changes the timezone of current object to $newzone in a way that changed date still points to the same time moment (same epoch).
YMDhms info may change. Returns nothing. C<$newzone> may be timezone object, name or rule.


=head4 array()

Returns elements list - $year,$month,$day,$hour,$min,$sec,[$mksec].

$year is year() [2013=2013]

$month is month() [1-12]

If $mksec == 0 it will not be on the list


=head4 struct()

Returns elements list - $sec,$min,$hour,$day,$mon,$year,$wday,$yday,$isdst

$year is c_year()  [113 = 2013]

$month is c_month() [0-11]

$wday is c_wday() [0-6]

$yday is c_yday() [0-365]


=head4 clone(%diff | @diff)

Returns copy of the date.

If you pass a hash-list or array then date is cloned with changes described in the hash/array.

Hash keys: year (YYYY), month [1-12], day, hour, min, sec, mksec, tz

Array: [$year (YYYY), $month [1-12], $day, $hour, $min, $sec, $mksec, $tz]

If any values in hash or array are absent (or = undef or = -1) the appropriate field of source date is not changed.

If C<tz> parameter is absent (or undef or ""), newly created date will use current date's timezone.
Otherwise constructed date is converted to specified timezone preserving YMDhms information.


=head4 month_begin_new()

Returns the beggining of month. Only day of month is changed, HMS are preserved.


=head4 month_begin()

Same as C<month_begin_new()> but changes current object instead of cloning.


=head4 month_end_new()

Returns the end of month. Only day of month is changed, HMS are preserved.


=head4 month_end()

Same as C<month_end_new()> but changes current object instead of cloning.


=head4 days_in_month()

Returns the number of days in month


=head4 week_of_month()

Returns the week of the month [0..5]. The first week of the month is the first week that contains a Thursday.
A day in the week before the week with the first Thursday will be week 0.


=head4 weeks_in_year()

Returns total number of weeks in the current year (52 or 53).


=head4 week_of_year()

    my $week_number = $date->week_of_year();
    my ($week_year, $week_number) = $date->week_of_year();

Returns information about the calendar week which contains this date object [1..53].

The first week of the year is defined by ISO as the one which contains the fourth day of January, which is equivalent to saying that it's the first week to overlap the new year by at least four days.

Typically the week year will be the same as the year that the object is in, but dates at the very beginning of a calendar year often end up in the last week of the prior year, and similarly, the final few days of the year may be placed in the first week of the next year.


=head4 error()

Returns error occured during creating or cloning object (if any) as L<XS::STL::ErrorCode> object.


=head4 truncated()

Return copy of the current date with HMS set to 0. Same as C<clone(hour => 0, min => 0, sec => 0)>, but runs faster.


=head4 truncate()

Same as C<truncated()> but changes current object instead of cloning.


=head4 '<=>', 'cmp', compare($arg)

Converts C<$arg> to date, compares 2 dates and returns -1, 0 or 1.

    $date <=> $arg
    $arg  <=> $date
    
    $date <=> "2001-01-01 01:01:01"
    $date <=> 123456789; # epoch
    "01.02.2019" <=> $date
    $date1 <=> $date2


=head4 '+', sum($arg)

Converts C<$arg> to relative date, adds it to the date and returns new date as a result.

    $date + $arg
    $arg + $date
    
    $date + "1Y 2M";
    86400 + $date;
    $date + DAY;


=head4 '+=', add($arg)

Converts C<$arg> to relative date and adds it to existing date object.

    $date += $arg
    
    $date += 10;
    $date += YEAR;
    $date += "1h 2m 3s";


=head4 '-', difference($arg)

If second operand is a date object, then returns L<Date::Rel> object representing exact interval from right operand till left operand.

    $date_till - $date_from; # same as Date::Rel->new($date_from, $date_till)
    
    (date("2019-02-01") - date("2019-01-01"))->to_days == 31;
    
see L<Date::Rel>.

Otherwise converts right operand to relative date, subtracts it from the date object and returns new date as a result. Reverse is not allowed.

    $date - "1Y";
    $date - 86400;
    86400 - $date; # !not allowed!

=head4 '-=', subtract($arg)

Converts C<$arg> to relative date and subtracts it from existing date object. Reverse is not allowed.


=head1 FUNCTIONS

=head4 now()

Same as Date->new(time()) but runs faster.


=head4 now_hires()

Same as Date->new(Time::HiRes::time()) but runs faster.


=head4 today()

Same as Date->new(time())->truncate but runs faster.


=head4 today_epoch()

Same as today()->epoch but runs faster.


=head4 range_check([$true_false])

If parts of the date are invalid or the whole date is not valid, e.g. 2001-02-31 then:

when range_check is not set (the default), then these date values are automatically converted to a valid date (normalized): 2001-03-03

when range_check is set, then a date "2001-02-31" became invalid date and error() is set to C<Date::Error::out_of_range>.

One exception from this rule is when format has a weekday name (rfc1123, etc) and it doesn't match represented date, C<Date::Error::out_of_range> will be always set.


=head4 date(...)

Same as Date->new(...). See C<new()> for arguments details.

=head4 date_ymd(...)

Same as Date->new_ymd(...). See C<new_ymd()> for arguments details.


=head4 rdate(...)

Same as Date::Rel->new(...), see L<Date::Rel>

=head4 rdate_ymd(...)

Same as Date::Rel->new_ymd(...), see L<Date::Rel>

    
=head4 tzget([$zone])

Returns timezone object for timezone name C<$zone> (or local zone if $zone is not provided). The object can later be used for creating dates in this zone
without setting this zone as local via C<tzset()>.


=head4 tzset([$zone])

Sets $zone as localzone. If you don't provide $zone, default timezone will be set ($ENV{TZ}, /etc/localtime, or whatever your OS considers to be localzone).

Does NOT affect POSIX:tzset(). Only this module's functions and objects will follow this timezone.

    # change local zone to 'America/New_York'
    tzset('America/New_York');
    # or
    my $tz = tzget('America/New_York');
    tzset($tz);
    
    # the same (doesnt work in Windows)
    local $ENV{TZ} = 'America/New_York';
    tzset();
    
    # change localzone back to the default localzone (in case you didn't change $ENV{TZ})
    tzset(); # or tzset(undef) or tzset('')

If you don't want to change localzone, you don't have to call this function directly as it's called implicitly on-demand.

If you provide $zone and no such zone found in zones directory (or timezone file is corrupted), 'UTC0' is used.

$zone may be L<Date::Timezone> object.


=head4 tzname()

The name of localzone. Note that in some cases the real name of localzone is not known
(for example when localzone is retrieved from /etc/localtime file, tzname() will return ':/etc/localtime')


=head4 use_system_timezones()

Use your OS's timezones dir. This is default behaviour if your OS has /usr/share/zoneinfo DB. Otherwise embedded zones
are used by default (for example, on MS Windows).

If your OS doesn't have /usr/share/zoneinfo DB, this function warns and does nothing.


=head4 use_embed_timezones()

Use timezone files which come with this module (they may be newer or older than system ones).


=head4 tzdir([$newdir])

Sets or returns current timezones directory.
If there was an error (!exists, !readable, etc) returns false and leaves tzdir unchanged.

    say tzdir(); # prints /usr/share/zoneinfo (on UNIX)
    tzdir('/home/frank/myzones'); # use /home/frank/myzones as timezones DB
    say tzdir(); # prints /home/frank/myzones
    tzset('Europe/Moscow'); # set /home/frank/myzones/Europe/Moscow as localzone


=head4 available_timezones()

Returns list of all available timezones in tzdir().


=head4 strftime($format, $sec, $min, $hour, $mday, $mon, $year, [$isdst], [$timezone])

Works like POSIX's C<strftime()>, but runs much faster. Additionaly, supports timezone argument in which case it will interpret passed YMDHSD as date
in timezone C<$timezone> instead of local zone.

NOTE: C<$year> must be in natural form (2019, not 119). Month as in original C<strftime()>, 0-11.

=head4 strftime($format, $epoch, [$timezone])

Same as above, but receives UNIX timestamp instead of YMDHMS arguments.


=head4 gmtime($epoch)

The returned year is in human-readable form (not year-1900). Month is [0-11]. The same applies for all further time functions.

=head4 localtime($epoch)

=head4 anytime($epoch, $timezone)

Like C<localtime()> but runs calculations in timezone C<$timezone> instead of local zone

=head4 timegm($sec, $min, $hour, $day, $mon, $year, [$isdst])

=head4 timegmn($sec, $min, $hour, $day, $mon, $year)

Same as timegm() except for the arguments which have to be non-constant values because they are normalized during calculations.

=head4 timelocal($sec, $min, $hour, $day, $mon, $year, [$isdst])

=head4 timelocaln($sec, $min, $hour, $day, $mon, $year, [$isdst])

Same as timelocal() except for the arguments which have to be non-constant values because they are normalized during calculations.

=head4 timeany($sec, $min, $hour, $day, $mon, $year, [$isdst], [$timezone])

Like C<timelocal()> but runs calculations in timezone C<$timezone> instead of local zone

=head4 timeanyn($sec, $min, $hour, $day, $mon, $year, [$isdst], [$timezone])



=head1 CONSTANTS

=head4 Date::Error::parser_error

Wrong date or relative date string format

=head4 Date::Error::out_of_range

Invalid date (or date part) supplied when range_check() is in effect

=head4 YEAR

Constant for rdate("1Y"). These (YEAR...SEC) objects are constants (read-only).

If you try to change these objects they'll croak.

=head4 MONTH

Constant for rdate("1M")

=head4 DAY

Constant for rdate("1D")

=head4 WEEK

Constant for rdate("1W")

=head4 HOUR

Constant for rdate("1h")

=head4 MIN

Constant for rdate("1m")

=head4 SEC

Constant for rdate("1s")



=head1 CLONING/SERIALIZATION

C<Date> supports:

=over

=item Storable serialization/deserealization

You can freeze Date::* objects and thaw serialized data back without losing any date information.

If you serialize a date object which was created with personal timezone (second arg to constructor),
then it will be deserialized exactly in the same timezone.

If a date object is in local timezone, then it will be deserialized in local timezone too (which may differ on differrent servers), 
but it's guaranteed that those two dates will point to the same time moment (epoch is preserved).

For example:

    tzset('Europe/Moscow');
    my $date = date("2014-01-01");
    my $frozen = freeze $date;
    tzset('America/New_York');
    my $date2 = thaw $frozen;
    $date == $date2; # true, because $date->epoch == $date2->epoch
    say $date;  # 2014-01-01 00:00:00
    say $date2; # 2013-12-31 15:00:00

=item L<Data::Recursive>'s C<clone>

=item L<JSON> serialization with convert_blessed

Serializes to C<epoch()>

=back



=head1 PACKAGES

C<Date> (this package)

L<Date::Rel> - relative date object

L<Date::strict> - enable/disable strict mode



=head1 DATE RANGES

=head4 64bit OS + perl-64bit-int

from -2147483648/01/01 00:00:00 till 2147483647/12/31 23:59:59

=head4 32bit OS + perl-64bit-int

from -2147483648/01/01 00:00:00 till 2147483647/12/31 23:59:59

=head4 32bit OS + perl-32bit-int

from -285424812/02/20 18:53:48 till 285428751/11/12 11:36:32 


Please note, that these ranges means that Date object can make calculations and its API works correctly while date is in these ranges.

However, it won't be able to parse and to stringify such dates because no ISO/RFC standarts exist for those ranges.

The maximum range for parsing and stringification is 0000/01/01 00:00:00 - 9999/12/31 23:59:59


=head1 SUPPORTED OS

Tested on FreeBSD, Linux, MacOSX, Windows 2003-10.

I believe all of UNIX-like and Windows-like systems are supported.

Timezones are supported in Olson DB format (V1,2,3).



=head1 CAVEATS

Setting C<$ENV{TZ}> doesn't work in Windows. To set $zone as localzone, you should write

    Date::tzset($zone);

to produce platform-independent code.

Also please note that in some extremely rare cases the result of date calculations of this module and POSIX functions may not match
(this module's results are correct in this case, POSIX's - are not).

That's because POSIX's timegm/timelocal/etc functions have bugs on some certain time moments. For detailed info see L<Date::TimeImpl>.



=head1 PERFORMANCE

L<Date> runs 1.5 - 50x faster than Time::Moment. Tests were performed on Xeon-v3 3.0Ghz, Debian 10, perl 5.30.0

    new empty
                      Rate Time::Moment         Date
    Time::Moment 3619344/s           --         -34%
    Date         5450519/s          51%           --
    
    new with current time
                      Rate Time::Moment   Date_hires         Date
    Time::Moment  420102/s           --         -91%         -92%
    Date_hires   4812084/s        1045%           --         -13%
    Date         5519512/s        1214%          15%           --
    
    new from epoch
                      Rate Time::Moment         Date
    Time::Moment 3849666/s           --         -25%
    Date         5164206/s          34%           --

    new from string
                      Rate Time::Moment         Date
    Time::Moment 2682760/s           --         -29%
    Date         3780922/s          41%           --

    new from named YMDHMS
                      Rate Time::Moment         Date
    Time::Moment 2372854/s           --         -13%
    Date         2737056/s          15%           --
    
    today (now+truncate)
                      Rate Time::Moment         Date
    Time::Moment  430970/s           --         -91%
    Date         4721290/s         996%           --

    adding 1 relative
                      Rate Time::Moment         Date
    Time::Moment 3780922/s           --         -53%
    Date         7989875/s         111%           --

    adding many relatives
                      Rate Time::Moment         Date
    Time::Moment 1341380/s           --         -72%
    Date         4733156/s         253%           --    
    
    converting epoch<->YMDHMS with date operations
                      Rate Time::Moment         Date
    Time::Moment 1102769/s           --         -49%
    Date         2143658/s          94%           --

    using timezones
                      Rate Time::Moment         Date
    Time::Moment   71087/s           --         -98%
    Date         3674915/s        5070%           --
        
Benchmark code:

    my $time = -1;
    
    say "new empty";
    cmpthese($time, {
        Date           => sub { Date::date() },
        "Time::Moment" => sub { Time::Moment->new },
    });
    say;
    
    say "new with current time";
    cmpthese($time, {
        Date           => sub { Date::now() },
        Date_hires     => sub { Date::now_hires() },
        "Time::Moment" => sub { Time::Moment->now },
    });
    say;
    
    say "new from epoch";
    cmpthese($time, {
        Date           => sub { Date::date(1000000000) },
        "Time::Moment" => sub { Time::Moment->from_epoch(1000000000) },
    });
    say;
    
    say "new from string";
    cmpthese($time, {
        Date           => sub { Date::date("2019-01-01T23:59:59Z") },
        "Time::Moment" => sub { Time::Moment->from_epoch("2019-01-01T23:59:59Z") },
    });
    say;
    
    say "new from named YMDHMS";
    cmpthese($time, {
        Date           => sub { Date::date_ymd(year => 2019, month => 1, day => 1, hour => 23, min => 59, sec => 59, mksec => 123456) },
        "Time::Moment" => sub { Time::Moment->new(year => 2019, month => 1, day => 1, hour => 23, minute => 59, second => 59, nanosecond => 123456000) },
    });
    say;
    
    say "today (now+truncate)";
    {
        my $d = Date::now();
        my $tm = Time::Moment->now();
        cmpthese($time, {
            Date           => sub { Date::today() },
            "Time::Moment" => sub { Time::Moment->now->at_midnight },
        });
    }
    say;
    
    say "adding 1 relative";
    {
        my $d = Date::now();
        my $tm = Time::Moment->now();
        cmpthese($time, {
            Date           => sub { $d += HOUR },
            "Time::Moment" => sub { $tm = $tm->plus_hours(1) },
        });
    }
    say;
    
    say "adding many relatives";
    {
        my $d = Date::now();
        my $tm = Time::Moment->now();
        cmpthese($time, {
            Date           => sub { $d += "1D 1h 1m 1s" },
            "Time::Moment" => sub { $tm = $tm->plus_days(1)->plus_hours(1)->plus_minutes(1)->plus_seconds(1) },
        });
    }
    say;
    
    say "converting epoch<->YMDHMS with date operations";
    {
        my $d = Date::now();
        my $tm = Time::Moment->now();
        cmpthese($time, {
            Date           => sub { $d += 1; $d->year; $d += DAY; $d->epoch },
            "Time::Moment" => sub { $tm = $tm->plus_seconds(1); $tm->year; $tm = $tm->plus_days(1); $tm->epoch },
        });
    }
    say;
    
    say "using timezones";
    cmpthese($time, {
        Date           => sub { Date::date_ymd(2012, 12, 24, 15, 0, 0, 0, 'America/New_York') },
        "Time::Moment" => sub {
            my $tm = Time::Moment->new(
                year   => 2012,
                month  => 12,
                day    => 24,
                hour   => 15
            );
            my $zone   = DateTime::TimeZone->new(name => 'America/New_York');
            my $offset = $zone->offset_for_datetime($tm) / 60;
            $tm->with_offset_same_instant($offset);
        },
    });
    say;



=head1 AUTHOR

Pronin Oleg <syber@cpan.org>, Ivan Baidakou <i.baydakov@crazypanda.ru>, Crazy Panda LTD

=head1 LICENSE

You may distribute this code under the same terms as Perl itself.

=cut
