The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Date::Easy::Datetime - easy datetime class

VERSION

This document describes version 0.09_01 of Date::Easy::Datetime.

SYNOPSIS

    use Date::Easy::Datetime ':all';

    # default timezone is your local zone
    my $dt = datetime("3/31/2012 7:38am");

    # addition and subtraction work in increments of seconds
    my $this_time_yesterday = now - 60*60*24;
    my $after_30_minutes = now + 30 * 60;
    say "$dt was ", now - $dt, " seconds ago";
    # or can add or subtract months
    my $next_month = now->add_months(1);
    my $last_month = now->add_months(-1);

    # if you prefer UTC
    my $utc = datetime(UTC => "2016-03-07 01:22:16PST-0800");

    # or UTC for all your objects
    use Date::Easy::Datetime 'UTC';
    say datetime("Jan 1 2000 midnight")->time_zone;
    # prints "UTC"

    my $yr = $dt->year;
    my $mo = $dt->month;
    my $da = $dt->day;
    my $hr = $dt->hour;
    my $mi = $dt->minute;
    my $sc = $dt->second;
    my $ep = $dt->epoch;
    my $zo = $dt->time_zone;
    my $qr = $dt->quarter;
    my $dw = $dt->day_of_week;

    say $dt->strftime("%Y-%m-%dT%H:%M:%S%z");
    say $dt->iso8601;
    say $dt->as("/Ymd");

    my $tp = $dt->as('Time::Piece');

DESCRIPTION

A Date::Easy::Datetime object contains a Time::Piece object and provides a slightly different UI to access it. In typical usage, you will either use the datetime constructor to convert a human-readable string to a datetime, or the now function to return the current datetime (i.e. the datetime object corresponding to time()). Both are exported with the :all tag; nothing is exported by default.

Arithmetic operators (plus and minus) either add or subtract seconds to or from the datetime object. Accessor methods use the naming conventions of DateTime (rather than those of Time::Piece).

Datetime objects are immutable.

See Date::Easy for more general usage notes.

USAGE

Zone Specifiers

There are three zone specifiers that Date::Easy::Datetime understands:

local

'local' means to use the local timezone, however that is determined (often this is via the $TZ environment variable, but your system may differ). That is, under 'local' Date::Easy::Datetime will use localtime and timelocal (technically, timelocal_modern, from Time::Local) to deal with epoch seconds.

UTC

'UTC' means to use the UTC timzeone, which essentialy means to ignore timezone altogether. That is, under 'UTC' Date::Easy::Datetime will use gmtime and timegm (technically, timegm_modern, from Time::Local) to deal with epoch seconds.

GMT

As far as Date::Easy::Datetime is concerned, 'GMT' is always exactly equivalent to 'UTC'. It's just an alias for people who prefer that term.

Import Parameters

After the use Date::Easy::Datetime statement, you can add parameters. These can be one of three things, in any order.

Function names

These are passed on to Exporter to export only certain function names. The only names currently recognized are now and datetime.

Exporter tags

These are also passed on to Exporter. The only tag currently recognized is :all, which means to import all the names above.

Zone specifier

These change the default zone specifier for all datetime objects. If you specify more than one, the last one wins (but don't do that). There is only one default zone specifier, so don't do this in a module.

Possible values are listed under "Zone Specifiers". 'local' is the default, so it's redundant to pass that in, but you may just wish to be explicit. As always, 'UTC' and 'GMT' are equivalent.

Constructors

Date::Easy::Datetime->new

Returns the same as "now".

Date::Easy::Datetime->new($e)

Takes the given epoch seconds and turns it into a datetime using the default zone specifier.

Date::Easy::Datetime->new($zone_spec => $e)

Takes the given epoch seconds and turns it into a datetime using the given zone specifier.

Date::Easy::Datetime->new($y, $m, $d, $hr, $mi, $sc)

Takes the given year, month, day, hours, minutes, and seconds, and turns them into a datetime object, using the default zone specifier. Month and day are human-centric (i.e., 1-based, not 0-based). Year should be a 4-digit year; if you pass in a 2-digit year, you get a year bewteen 1900 and 1999, even if you use the last 2 digits of the current year.

Date::Easy::Datetime->new($zone_spec => $y, $m, $d, $hr, $mi, $sc)

Takes the given year, month, day, hours, minutes, and seconds, and turns them into a datetime object, using the given zone specifier. Month and day are human-centric (i.e., 1-based, not 0-based). Year should be a 4-digit year; if you pass in a 2-digit year, you get a year bewteen 1900 and 1999, even if you use the last 2 digits of the current year.

Date::Easy::Datetime->new($obj)

If the sole argument to new is a blessed object, attempts to convert that object to a datetime. Currently the only type of object that can be successfully converted is a Time::Piece.

now

Returns the current datetime (using the default zone specifier).

datetime($string)

datetime($zone_spec => $string)

Takes the human-readable string and converts it to a datetime using the given zone specifier, if passed (or the default zone specifier if not), using the following heuristics:

  • If the string consists of nothing but digits (including an optional leading negative sign), treats it as a number of epoch seconds and passes it to new.

  • Otherwise passes it to Date::Parse's str2time function. If the zone specifier is 'GMT' or 'UTC', this is passed to str2time as its second argument. If the result is defined, pass it as epoch seconds to new.

  • Otherwise if the result of calling str2time is undefined, passes it to Time::ParseDate's parsedate function. If the result is defined, passes the resulting epoch seconds to new.

  • If parsedate returns undef, throws an "Illegal date" exception.

This is designed to be a DWIMmy method which will most of the time just do what you meant so you don't need to think about it.

Accessors

Names of accessors match the DateTime class. Ranges generally match what DateTime uses as well.

is_local

Returns true if the datetime is in the current timezone.

is_utc

Returns true if the datetime is in UTC.

is_gmt

Alias for is_utc.

year

Returns the year (4-digit).

month

Returns the month as a number (1 - 12).

day

Returns the day as a number (1 - 31).

hour

Returns the hour (0 - 23).

minute

Returns the minute (0 - 59).

second

Returns the second (0 - 59).

epoch

Returns the datetime as a number of epoch seconds.

Other Methods

time_zone

Same as strftime('%Z').

day_of_week

Returns the day of the week from 1 (Monday) to 7 (Sunday).

day_of_year

Returns the day of the year from 1 (January 1st) to either 365 (December 31st) for a non-leap year, or 366 for a leap year.

quarter

Returns the quarter of the year, based on the month (1 - 4).

strftime($fmt)

Calls Time::Piece's strftime. See those docs for full details.

iso8601

Calls Time::Piece's datetime, which produces an ISO 8601 formatted datetime.

iso

Alias for "iso8601", in case you can never remember the exact digits (like me).

split

Returns a list consisting of the year, month, day, hours, minutes, and seconds, in that order, in the same ranges as returned by the "Accessors". Doesn't return anything useful in scalar context, so don't do that. Calling split in scalar context may eventually be changed to throw a warning or fatal error.

as($conv_spec)

Tries to convert the datetime according to the supplied conversion specification. There are two possible formats for the spec:

  • If the spec consists of a non-letter followed by one or more letters (and nothing else), as will convert this to a time format to be passed to "strftime". For instance, the spec "-Ymd" is converted to "%Y-%m-%d", and ":HMS" is converted to "%H:%M:%S". This allows conversion to a string using much more compact formats, such as "/mdy" or even " abdYZ".

  • Otherwise, the spec is expected to be a classname, and as tries to convert the datetime to the given class. Currently, the only acceptable classname is Time::Piece. (Since a Date::Easy::Datetime is stored internally as a Time::Piece object, this is a trivial lookup.)

Overloaded Operators

Addition

You can add an integer value to a datetime object. It adds that number of seconds and returns a new datetime object. The original datetime is not modified.

Subtraction

You can subtract an integer value from a datetime object. It subtracts that number of seconds and returns a new datetime object. The original datetime is not modified.

You can subtract one datetime from another; the result is the number of seconds you would have to add to the right-hand operand to get the left-hand operand (therefore, the result is positive when the left-hand side is a later datetime, and negative when the left-hand side is earlier). Currently the result of attempting to subtract a date from a datetime is undefined.

Math Methods

add_seconds($num)

Same as adding $num directly to the datetime.

add_minutes($num)

Same as adding $num * 60 directly to the datetime.

add_hours($num)

Same as adding $num * 60 * 60 directly to the datetime.

add_days($num)

Same as adding $num * 60 * 60 * 24 directly to the datetime.

add_weeks($num)

Same as calling add_days($num * 7) on the datetime.

add_months($num)

Calls Time::Piece's add_months to add a given number of months and return a new datetime object. The original datetime is not modified. See the Time::Piece docs for full details, especially as regards what happens when you try to add months to dates at the ends of months.

add_years($num)

Calls Time::Piece's add_years to add a given number of years and return a new datetime object. The original datetime is not modified. See the Time::Piece docs for full details. (Though the Time::Piece documentation isn't clear on this point, adding a year to Feb 29th of a leap year acts correspondingly to adding a month to Jan 29th of a non-leap year.)

subtract_seconds($num)

subtract_minutes($num)

subtract_hours($num)

subtract_days($num)

subtract_weeks($num)

subtract_months($num)

subtract_years($num)

The same as calling the equivalent add_ method, but with -$num.

BUGS, CAVEATS and NOTES

If you try to pass a zone specifier to new along with a Time::Piece object, it is ignored. That means that this code:

        use Time::Piece;
        use Date::Easy::Datetime;

        my $tp = localtime;
        my $dt = Date::Easy::Datetime->new(UTC => $tp);

is not going to do what you thought it would do. (Although, honestly, I'm not sure what you thought it was going to do.)

There is a bug in Time::ParseDate which causes epoch seconds to be one hour off in certain specific circumstances. Please note that you will not hit this bug if the string you pass to "datetime($string)" has any of the following characteristicts:

  • If you use one of the UTC "Zone Specifiers".

  • If your string never makes it to Time::ParseDate, either because it's a number of epoch seconds, or because it's parseable by Date::Parse.

  • If your string contains a time zone, in any format.

  • If your string is a relative time, such as "next week" or "+3 minutes".

  • If your local timezone doesn't use DST (e.g. Ecuador, Kenya, Nepal, Saudi Arabia, etc).

  • If the DST flag (i.e. the condition of either being on daylight savings or not) of the time in your string matches the current DST flag, in your local timezone.

Hopefully this means hitting this bug will be rare. An upstream bug has been filed.

If your local timezone contains leap seconds, you will likely get funky results with UTC datetimes, such as this being true:

    $dt->second != $dt->strftime("%S")

in all cases except, of course, datetimes from before the first leap second was added (i.e. prior to 30-Jun-1972 23:59:60). Weirdly, this isn't a problem with local datetimes. An upstream bug has been filed, although there is still some ongoing discussion about whether this is a bug or not, and whether it's fixable even if it is.

If you pass a 2-digit year to `datetime`, it will always come back in the 20th century:

    say datetime("2/1/17"); # Thu Feb  1 00:00:00 1917

Avoiding this is simple: always use 4-digit dates (which is a good habit to get into anyway). This could be considered a bug, since Time::Local uses a 50-year sliding window, which might be considered to be more correct behavior. However, by suffering this "bug," we avoid a bigger one (see RT/53413 and RT/105031).

See also "Limitations" in Date::Easy.

AUTHOR

Buddy Burden <barefootcoder@gmail.com>

COPYRIGHT AND LICENSE

This software is Copyright (c) 2020 by Buddy Burden.

This is free software, licensed under:

  The Artistic License 2.0 (GPL Compatible)