--
chansen
Just because Time::Piece is in core doesn't mean it's stable! Time::Piece has several quirks:
Time::Piece->strptime() parses any given string as being in the UTC/GMT regardless if the format string contains the conversion specifiers %z or %Z.
Time::Piece->strptime() is hardcoded to use the C locale while Time:Piece->strftime() subjective to the locale.
If you still want to use Time::Piece as a base, I encourage you to use Time::Piece internally and delegate to it instead of inheriting from it.
BTW, Stability is proven by your test units, not by the module that you chose as a base!
--
chansen
--
chansen
How do you intend to deal with an abbreviated zone like CST? Central Standard Time (North America / Central America), Cuba Standard Time, China Standard Time or Central Standard Time (Australia)? What about abbreviated zones that has recently changed, like MSK (Moscow Standard Time)?
How do you intend to deal with numerical only dates? DD/MM/YYYY, MM/DD/YYYY, YY/MM/DD, MM/DD/YY or DD/MM/YY?
It's seems to me that you your are trying to solve a problem that you have very little real experience with. Perhaps you should gain some real experience with temporal data before you try to solve it?
--
chansen
$tm = Time::Moment->now;
# Truncate to day
say $tm->at_midnight;
# Truncate to first day of month
say $tm->at_midnight
->with_day_of_month(1);
# Truncate to first day of quarter
say $tm->at_midnight
->with_day_of_quarter(1);
Time::Moment supported range is 0001-01-01T00:00:00.000000000Z to 9999-12-31T23:59:59.999999999Z, regardless if Perl is compiled with 32-bit or 64-bit integers.
P.S. I'm the author of Time::Moment.
--
chansen
Time::Moment comes with Time::Moment::Adjusters which currently only provides routines for navigating/finding the day of week.
The following is also available on CPAN, as us_federal_holidays.pl or on github.
#!/usr/bin/perl
use strict;
use warnings;
use Time::Moment;
use Time::Moment::Adjusters qw[ NthDayOfWeekInMonth ];
use enum qw[ Monday=1 Tuesday Wednesday Thursday Friday Saturday Sunday ];
use enum qw[ First=1 Second Third Fourth Last=-1 ];
use constant FirstMondayInMonth => NthDayOfWeekInMonth(First, Monday);
use constant SecondMondayInMonth => NthDayOfWeekInMonth(Second, Monday);
use constant ThirdMondayInMonth => NthDayOfWeekInMonth(Third, Monday);
use constant LastMondayInMonth => NthDayOfWeekInMonth(Last, Monday);
use constant FourthThursdayInMonth => NthDayOfWeekInMonth(Fourth, Thursday);
# Adjusts the date to the nearest workday
use constant NearestWorkday => sub {
my ($tm) = @_;
return $tm unless $tm->day_of_week > Friday;
return $tm->plus_days($tm->day_of_week == Saturday ? -1 : +1);
};
# Federal law 5 USC § 6103 - HOLIDAYS
# http://www.law.cornell.edu/uscode/text/5/6103
sub compute_us_federal_holidays {
@_ == 1 or @_ == 2 or die q;
my ($year, $inauguration) = @_;
my @dates;
my $tm = Time::Moment->new(year => $year);
# New Year's Day, January 1.
push @dates, $tm->with_month(1)
->with_day_of_month(1)
->with(NearestWorkday);
# Birthday of Martin Luther King, Jr., the third Monday in January.
push @dates, $tm->with_month(1)
->with(ThirdMondayInMonth);
# Inauguration Day, January 20 of each fourth year after 1965.
if ($inauguration && $year % 4 == 1) {
my $date = $tm->with_month(1)
->with_day_of_month(20);
# When January 20 falls on Sunday, the next succeeding day is selected.
$date = $date->plus_days(1)
if $date->day_of_week == Sunday;
push @dates, $date
unless $date->day_of_week == Saturday
or $date->is_equal($dates[-1]); # 1997, 2013, 2025 ...
}
# Washington's Birthday, the third Monday in February.
push @dates, $tm->with_month(2)
->with(ThirdMondayInMonth);
# Memorial Day, the last Monday in May.
push @dates, $tm->with_month(5)
->with(LastMondayInMonth);
# Independence Day, July 4.
push @dates, $tm->with_month(7)
->with_day_of_month(4)
->with(NearestWorkday);
# Labor Day, the first Monday in September.
push @dates, $tm->with_month(9)
->with(FirstMondayInMonth);
# Columbus Day, the second Monday in October.
push @dates, $tm->with_month(10)
->with(SecondMondayInMonth);
# Veterans Day, November 11.
push @dates, $tm->with_month(11)
->with_day_of_month(11)
->with(NearestWorkday);
# Thanksgiving Day, the fourth Thursday in November.
push @dates, $tm->with_month(11)
->with(FourthThursdayInMonth);
# Christmas Day, December 25.
push @dates, $tm->with_month(12)
->with_day_of_month(25)
->with(NearestWorkday);
return @dates;
}
# Test cases extracted from
my @tests = (
[ 1997, '1997-01-01', '1997-01-20', '1997-02-17', '1997-05-26', '1997-07-04',
'1997-09-01', '1997-10-13', '1997-11-11', '1997-11-27', '1997-12-25' ],
[ 1998, '1998-01-01', '1998-01-19', '1998-02-16', '1998-05-25', '1998-07-03',
'1998-09-07', '1998-10-12', '1998-11-11', '1998-11-26', '1998-12-25' ],
[ 1999, '1999-01-01', '1999-01-18', '1999-02-15', '1999-05-31', '1999-07-05',
'1999-09-06', '1999-10-11', '1999-11-11', '1999-11-25', '1999-12-24' ],
[ 2000, '1999-12-31', '2000-01-17', '2000-02-21', '2000-05-29', '2000-07-04',
'2000-09-04', '2000-10-09', '2000-11-10', '2000-11-23', '2000-12-25' ],
[ 2001, '2001-01-01', '2001-01-15', '2001-02-19', '2001-05-28', '2001-07-04',
'2001-09-03', '2001-10-08', '2001-11-12', '2001-11-22', '2001-12-25' ],
[ 2002, '2002-01-01', '2002-01-21', '2002-02-18', '2002-05-27', '2002-07-04',
'2002-09-02', '2002-10-14', '2002-11-11', '2002-11-28', '2002-12-25' ],
[ 2003, '2003-01-01', '2003-01-20', '2003-02-17', '2003-05-26', '2003-07-04',
'2003-09-01', '2003-10-13', '2003-11-11', '2003-11-27', '2003-12-25' ],
[ 2004, '2004-01-01', '2004-01-19', '2004-02-16', '2004-05-31', '2004-07-05',
'2004-09-06', '2004-10-11', '2004-11-11', '2004-11-25', '2004-12-24' ],
[ 2005, '2004-12-31', '2005-01-17', '2005-02-21', '2005-05-30', '2005-07-04',
'2005-09-05', '2005-10-10', '2005-11-11', '2005-11-24', '2005-12-26' ],
[ 2006, '2006-01-02', '2006-01-16', '2006-02-20', '2006-05-29', '2006-07-04',
'2006-09-04', '2006-10-09', '2006-11-10', '2006-11-23', '2006-12-25' ],
[ 2007, '2007-01-01', '2007-01-15', '2007-02-19', '2007-05-28', '2007-07-04',
'2007-09-03', '2007-10-08', '2007-11-12', '2007-11-22', '2007-12-25' ],
[ 2008, '2008-01-01', '2008-01-21', '2008-02-18', '2008-05-26', '2008-07-04',
'2008-09-01', '2008-10-13', '2008-11-11', '2008-11-27', '2008-12-25' ],
[ 2009, '2009-01-01', '2009-01-19', '2009-02-16', '2009-05-25', '2009-07-03',
'2009-09-07', '2009-10-12', '2009-11-11', '2009-11-26', '2009-12-25' ],
[ 2010, '2010-01-01', '2010-01-18', '2010-02-15', '2010-05-31', '2010-07-05',
'2010-09-06', '2010-10-11', '2010-11-11', '2010-11-25', '2010-12-24' ],
[ 2011, '2010-12-31', '2011-01-17', '2011-02-21', '2011-05-30', '2011-07-04',
'2011-09-05', '2011-10-10', '2011-11-11', '2011-11-24', '2011-12-26' ],
[ 2012, '2012-01-02', '2012-01-16', '2012-02-20', '2012-05-28', '2012-07-04',
'2012-09-03', '2012-10-08', '2012-11-12', '2012-11-22', '2012-12-25' ],
[ 2013, '2013-01-01', '2013-01-21', '2013-02-18', '2013-05-27', '2013-07-04',
'2013-09-02', '2013-10-14', '2013-11-11', '2013-11-28', '2013-12-25' ],
[ 2014, '2014-01-01', '2014-01-20', '2014-02-17', '2014-05-26', '2014-07-04',
'2014-09-01', '2014-10-13', '2014-11-11', '2014-11-27', '2014-12-25' ],
[ 2015, '2015-01-01', '2015-01-19', '2015-02-16', '2015-05-25', '2015-07-03',
'2015-09-07', '2015-10-12', '2015-11-11', '2015-11-26', '2015-12-25' ],
[ 2016, '2016-01-01', '2016-01-18', '2016-02-15', '2016-05-30', '2016-07-04',
'2016-09-05', '2016-10-10', '2016-11-11', '2016-11-24', '2016-12-26' ],
[ 2017, '2017-01-02', '2017-01-16', '2017-02-20', '2017-05-29', '2017-07-04',
'2017-09-04', '2017-10-09', '2017-11-10', '2017-11-23', '2017-12-25' ],
[ 2018, '2018-01-01', '2018-01-15', '2018-02-19', '2018-05-28', '2018-07-04',
'2018-09-03', '2018-10-08', '2018-11-12', '2018-11-22', '2018-12-25' ],
[ 2019, '2019-01-01', '2019-01-21', '2019-02-18', '2019-05-27', '2019-07-04',
'2019-09-02', '2019-10-14', '2019-11-11', '2019-11-28', '2019-12-25' ],
[ 2020, '2020-01-01', '2020-01-20', '2020-02-17', '2020-05-25', '2020-07-03',
'2020-09-07', '2020-10-12', '2020-11-11', '2020-11-26', '2020-12-25' ],
);
use Test::More 0.88;
foreach my $test (@tests) {
my ($year, @exp) = @$test;
my @got = map {
$_->strftime('%Y-%m-%d')
} compute_us_federal_holidays($year);
is_deeply([@got], [@exp], "U.S. federal holidays for year $year");
}
done_testing();
#!/usr/bin/perl
use strict;
use warnings;
use Carp qw[];
use Time::Moment 0.19 qw[];
sub YEAR () { 365.2425 }
sub MONTH () { YEAR / 12 }
sub DAY () { 1 }
sub HOUR () { DAY / 24 }
sub MINUTE () { HOUR / 60 }
sub SECOND () { MINUTE / 60 }
sub ago {
@_ == 1 or Carp::croak(q/Usage: ago(moment)/);
my ($moment) = @_;
my $now = Time::Moment->now;
($now->compare($moment) >= 0)
or Carp::croak(q/Given moment is in the future/);
my $d = $now->mjd - $moment->mjd;
if ($d < 0.75 * DAY) {
if ($d < 0.75 * MINUTE) {
return 'a few seconds ago';
}
elsif ($d < 1.5 * MINUTE) {
return 'a minute ago';
}
elsif ($d < 0.75 * HOUR) {
return sprintf '%d minutes ago', $d / MINUTE + 0.5;
}
elsif ($d < 1.5 * HOUR) {
return 'an hour ago';
}
else {
return sprintf '%d hours ago', $d / HOUR + 0.5;
}
}
else {
if ($d < 1.5 * DAY) {
return 'a day ago';
}
elsif ($d < 0.75 * MONTH) {
return sprintf '%d days ago', $d / DAY + 0.5;
}
elsif ($d < 1.5 * MONTH) {
return 'a month ago';
}
elsif ($d < 0.75 * YEAR) {
return sprintf '%d months ago', $d / MONTH + 0.5;
}
elsif ($d < 1.5 * YEAR) {
return 'a year ago';
}
else {
return sprintf '%d years ago', $d / YEAR + 0.5;
}
}
}
my @tests = (
[ SECOND * 10, 'a few seconds ago' ],
[ MINUTE * 1, 'a minute ago' ],
[ SECOND * 75, 'a minute ago' ],
[ MINUTE * 30, '30 minutes ago' ],
[ HOUR * 1, 'an hour ago' ],
[ HOUR * 2, '2 hours ago' ],
[ DAY * 1, 'a day ago' ],
[ DAY * 20, '20 days ago' ],
[ MONTH * 1, 'a month ago' ],
[ MONTH * 2, '2 months ago' ],
[ MONTH * 13, 'a year ago' ],
[ YEAR * 1, 'a year ago' ],
[ YEAR * 2, '2 years ago' ],
[ YEAR * 10, '10 years ago' ],
[ YEAR * 100, '100 years ago' ],
);
# Time::Moment 0.25 is required to prove the tests as the
# ->from_mjd constructor was an addition in 0.25. The ->mjd
# accessor was added in 0.19.
use Time::Moment 0.25;
use Test::More 0.88;
my $now = Time::Moment->now;
foreach my $test (@tests) {
my ($duration, $expected) = @$test;
my $tm = Time::Moment->from_mjd($now->mjd - $duration);
is(ago($tm), $expected, "$tm ($duration)");
}
done_testing();
The above code is also available on gist.github.com.
Before deciding on using the above code, you should review the following code/modules to see if they fits your need better:
https://metacpan.org/pod/Time::Duration https://metacpan.org/pod/DateTime::Format::Human::Duration https://metacpan.org/pod/DateTimeX::Format::Ago https://github.com/mla/time-ago
]]>I did consider to add Panda::Date until I reviewed the source code. Your code isn't thread safe and you require 5.18 to compile it, and your naïve implementation of a date with a time representation due to the broken down representation is just a wasteful! An instance of Time::Moment requires 16 bytes plus the overhead of a scalar allocation and is mostly faster than your implementation that doesn't even support a fractional representation!
Time::Moment implements a subset of ISO 8601 (known as 4.3 Date and time of day, 4.3.2 Complete representations), Wikipedia has a good article regarding ISO 8601, but it's not an authoritative source for the ISO 8601:2004(E) standard.
Time::Moment is capable of parsing any string that purports to be formatted in ISO 8601:2004(E), 4.3.2 Complete representation.
Such formats includes the following:
Combinations of calendar date and time of day:
Basic format:
20140808T003146+0200
20140808T003146.705971+0200
20140808T0031+0200
Extended format:
2014-08-08T00:31:46+02:00
2014-08-08T00:31:46.705971+02:00
2014-08-08T00:31+02:00
Combinations of ordinal date and time of day:
Basic format:
2014220T003146+0200
2014220T003146.705971+0200
2014220T0031+0200
Extended format:
2014-220T00:31:46+02:00
2014-220T00:31:46.705971+02:00
2014-220T00:31+02:00
Combinations of week date and time of day:
Basic format:
2014W325T003146+0200
2014W325T003146.705971+0200
2014W325T0031+0200
Extended format:
2014-W32-5T00:31:46+02:00
2014-W32-5T00:31:46.705971+02:00
2014-W32-5T00:31+02:00
All of the above ISO 8601 formats can be round-trip'ed with excellent performance using Time::Moment->from_string()
Benchmarking parsing: '2013-12-24T12:34:56.123456+02:00'
Rate DT::F::ISO8601 DT::F::RFC3339 Time::Moment
DT::F::ISO8601 6938/s -- -37% -100%
DT::F::RFC3339 11019/s 59% -- -100%
Time::Moment 2715276/s 39037% 24542% --
It's worth noting thatTime::Moment->from_string(Time::Moment->now) round-trip without loss off precision!
]]>Time::Moment can never be "backwards" comparability with DateTime.pm due to DateTime.pm's design.
$duration = $tm1->minus($tm2);
$tm2 = $tm1->plus($duration);
$tm2 = $tm1->minus($duration);
$duration = $tm1 - $tm2;
$tm2 = $tm1 + $duration;
$tm2 = $tm1 - $duration;
$duration = $duration + $duration;
$duration = $duration - $duration;
If you would like to add any input please add a comment on the thread at the github issue 2
]]>In this blog post I'll talk a bit of the design decisions I made for Time::Moment.
Time::Moment supports a finite set of timestamps, the range is 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z with nanosecond precision regardless of architecture (32/64 bit) as long as the compiler provides support for 64 bit integers (I'm not aware of a architecture where it would be feasible to run Perl that doesn't provide a compiler which doesn't support 64 bit integers, cpan testers agree). Externally (the documented API) Time::Moment is constrained by 32 bit or the mantissa of NV (doubles) 53 bit. Time::Moment doesn't trust Storable and thus takes care of marshalling and unmarshalling the state when serializing/unserializing using Storable. Time::Moment guarantees a marshalled object on a Perl compiled 64 bit platform is unmarshalled correctly on a Perl compiled 32 bit platform and vice versa.
I'm a firm believer of standards and when it comes to timestamps, there is only one standard that is without legacy, it's ISO 8601. Time::Moment implements a subset of the ISO 8601:2004(E) standard. In my experience, when you mention ISO 8601, most developers think of RFC 3339, which is a superset of ISO 8601 and only accept one of the three calendar representations defined by ISO 8601.
Time::Moment supports three different calendars in two different formats, as specified by ISO 8601:2004(E). If you haven't read the wikipedia article covering ISO 8601, I suggest you do it know=)
Time::Moment->from_string is capable of parsing the following compliant timestamps (ISO 8601:2004 4.3 Date and time of day)
Combinations of calendar date and time of day:
Basic format:
20140808T003146+0200
20140808T003146.705971+0200
20140808T0031+0200
Extended format:
2014-08-08T00:31:46+02:00
2014-08-08T00:31:46.705971+02:00
2014-08-08T00:31+02:00
Combinations of ordinal date and time of day:
Basic format:
2014220T003146+0200
2014220T003146.705971+0200
2014220T0031+0200
Extended format:
2014-220T00:31:46+02:00
2014-220T00:31:46.705971+02:00
2014-220T00:31+02:00
Combinations of week date and time of day:
Basic format:
2014W325T003146+0200
2014W325T003146.705971+0200
2014W325T0031+0200
Extended format:
2014-W32-5T00:31:46+02:00
2014-W32-5T00:31:46.705971+02:00
2014-W32-5T00:31+02:00
For comparability reasons, Time::Moment stringifies to a Calendar date and time of day in extended format.
Because we (the common developers that need to deal with shitty implementations of ISO 8601 and RFC 3339) have to deal with real world implementations that purport to be in either ISO 8601 or RFC 3339 but in reality is neither, I had to implement a lenient mode in Time::Moment->from_string().
]]>