package Panda::Date::Rel;
use 5.012;
=head1 NAME
Panda::Date::Rel - Relative date object.
=cut
use overload '""' => \&to_string,
'bool' => \&to_bool,
'0+' => \&to_number,
'neg' => \&negative,
'<=>' => \&compare, # based on to_sec()
'eq' => \&equals, # based on full equality only
'+' => \&add,
'+=' => \&add_me,
'-' => \&subtract,
'-=' => \&subtract_me,
'*' => \&multiply,
'*=' => \&multiply_me,
'/' => \÷,
'/=' => \÷_me,
fallback => 1;
=head1 DESCRIPTION
Relative date is a length of time not bound to any particular point in time and is used for date calculations.
Reldate consists of 6 units - seconds, minutes, hours, days, months and years. Some units convert to another inaccurate.
=head1 CLASS METHODS
=head4 new([$rel_string | $seconds | \@rel_array | \%rel_hash | $reldate])
Creates a date object using one of these source data types:
=over
=item 123456 or "123456"
Treated as a number of seconds
=item [$year, $month, $day, $hour, $min, $sec]
Missing elements are considered = 0.
=item {year => x, month => x, day => x, hour => x, min => x, sec => x}
Missing elements are considered = 0.
=item string "1Y 2M 3D 4h 5m 6s"
Any part can be absent or negative.
=item Another relative date object
Clones another object
=back
=head4 new($from, $till)
Creates relative date $rel so that $from + $rel == $till.
$from and $till can be L objects or any data supported by L constructor.
=head1 OBJECT METHODS
=head4 set_from($rel_string | $seconds | \@rel_array | \%rel_hash | $reldate)
=head4 set_from($from, $till)
Set relative date from data (data can be anything that constructor supports). This is much faster than creating new object.
=head4 year([$years]), years
Number of years in relative date
=head4 month([$months]), months, mon, mons
Number of month in relative date
=head4 day([$days]), days
Number of days in relative date
=head4 hour([$hours]), hours
Number of hours in relative date
=head4 min([$mins]), mins, minute, minutes
Number of minutes in relative date
=head4 sec([$secs]), secs, second, seconds
Number of seconds in relative date
=head4 to_sec(), to_second, to_secs, to_seconds, duration
Converts relative date to number of seconds.
If any of day/month/year are non-zero then this value can be inaccurate because one need to know exact dates
to calculate exact number of seconds.
For such calculations the following assumptions are made:
=over
=item 1M = 2_629_744s
=item 1M = 2_629_744/86400 D
=item 1D = 86400s
=back
=head4 to_min(), to_minute, to_mins, to_minutes
Converts relative date to number of minutes. If any of day/month/year are non-zero then this value can be inaccurate, see to_sec().
=head4 to_hour(), to_hours
Converts relative date to number of hours. If any of day/month/year are non-zero then this value can be inaccurate, see to_sec().
=head4 to_day(), to_days
Converts relative date to number of days. If any of sec/min/hour/month/year are non-zero then this value can be inaccurate, see to_sec().
=head4 to_month(), to_months, to_mon, to_mons
Converts relative date to number of months. If any of sec/min/hour/day are non-zero then this value can be inaccurate, see to_sec().
=head4 to_year(), to_years
Converts relative date to number of years. If any of sec/min/hour/day are non-zero then this value can be inaccurate, see to_sec().
=head4 "", to_string(), string(), as_string()
Returns string in "4M 15D 123s" format, any of YMDhms can be absent or negative. If all the parts YMDhms are 0, then "" is returned.
=head4 'bool', to_bool()
Called implicitly in boolean context.
Returns FALSE, if sec = 0 and min = 0 and .... year = 0, i.e. duration = 0. Otherwise TRUE.
=head4 '0+', to_number()
Returns to_sec().
=head4 '*', multiply($num)
Multiplies relative date by $num. $num can be fractional but the result is always integer.
Examples
$rel = "1M 1D";
print $rel * 2; # 2M 2D
print rdate("10h")->multiply(10); # 100h
Relative date can only be multiplied by number (scalar).
No normalization are made, i.e. 12h*2 = 24h is not normalized to 1D because that would be inaccurate (on DST border day for example, 1D is 25 or 23h)
=head4 '*=', multiply_me($num)
Same as multiply(), but changes current object instead of creating new one. It's almost 10x faster :-)
=head4 '/', divide($num)
Divides relative date by $num. $num can be fractional but the result is always integer.
System will denormalize values if in another way (rounding) precision loses are bigger,
for example "1Y" / 2 = "6M" (without denormalization it would be 0).
This applies even if units are not converted accurate. In this case assumptions mentioned in to_sec() are made.
Examples
$rel = "2Y";
print $rel/2; # 1Y
print $rel/4; # 6M
print rdate("1D")/3; # 8h
print (rdate("1D")/3)*3; # 24h
print MONTH/2; # "15D 5h 14m 32s"
P.S. Keep in mind that ($rel / N) * N is not always equals $rel, as well as ($rel * N) / N
=head4 '/=', divide_me($num)
Same as divide(), but changes current object instead of creating new one.
=head4 '+', add($reldate | $rel_string | $seconds | \@rel_array | \%rel_hash)
Adds another rel date to current. Another reldate can be Panda::Date::Rel object or any data valid for its constructor.
Examples
my $rel = 2*MONTH;
print $rel+MONTH; # 3M
print $rel+'30D'; # 2M 30D
print $rel+[1,2,3]; # 1Y 4M 3D
=head4 '+=', add_me($reldate | $rel_string | $seconds | \@rel_array | \%rel_hash)
Same as add(), but changes current object instead of creating new one.
Examples
my $rel = 2*MONTH;
$rel += '16h'; # 2M 16h
$rel += {sec => 10, min => 20}; # 2M 16h 20m 10s
$rel += $rel; # 4M 32h 40m 20s
=head4 '-', subtract($reldate | $rel_string | $seconds | \@rel_array | \%rel_hash)
Subtracts another reldate from current. Another reldate can be Panda::Date::Rel object or any data valid for its constructor.
Examples
my $rel = 2*MONTH;
print $rel-MONTH; # 1M
print $rel-'30D'; # 2M -30D
print $rel-[1,2,3]; # -1Y -3D
=head4 '-=', subtract_me($reldate | $rel_string | $seconds | \@rel_array | \%rel_hash)
Same as subtract(), but changes current object instead of creating new one.
=head4 'neg', negative() (unary '-')
Changes sign of YMDhms
=head4 negative_me()
Same as negative(), but changes current object instead of creating new one.
=head4 '<=>', compare($reldate | $rel_string | $seconds | \@rel_array | \%rel_hash)
Compares 2 relative dates and returns -1, 0 or 1. Another reldate can be Panda::Date::Rel object or any data valid for its constructor.
Dates are compared using to_sec(), therefore 2 dates can be equal even if they consist of different components.
If you want full equality test, use 'eq'.
Examples
MONTH > YEAR; # false
rdate("1Y 1M") > YEAR; #true
12*MONTH == YEAR; #true
12*MONTH eq YEAR; #false
=head4 'eq', equals($reldate | $rel_string | $seconds | \@rel_array | \%rel_hash)
Same as '==' but returns TRUE only if 2 reldates are fully identical.
$reldate = rdate("1Y 2M");
$reldate == "1Y 2M"; # true
$reldate == "14M"; # true
$reldate eq "1Y 2M"; # true
$reldate eq "14M"; # false
=head1 OPERATOR OVERLOAD RULES
See screenshot L
=head1 AUTHOR
Pronin Oleg , Crazy Panda, CP Decision LTD
=head1 LICENSE
You may distribute this code under the same terms as Perl itself.
=cut
1;