File Coverage

blib/lib/DateTimeX/Moment.pm
Criterion Covered Total %
statement 465 536 86.7
branch 200 268 74.6
condition 58 100 58.0
subroutine 139 160 86.8
pod 0 111 0.0
total 862 1175 73.3


line stmt bran cond sub pod time code
1             package DateTimeX::Moment;
2 35     35   4484460 use 5.008001;
  35         218  
3 35     35   159 use strict;
  35         57  
  35         622  
4 35     35   131 use warnings;
  35         62  
  35         1302  
5              
6             our $VERSION = "0.06";
7              
8 35     35   11966 use Time::Moment 0.38;
  35         39053  
  35         1004  
9 35     35   11985 use DateTimeX::Moment::Duration;
  35         85  
  35         958  
10 35     35   13674 use DateTime::Locale;
  35         5682842  
  35         1193  
11 35     35   16568 use DateTime::TimeZone;
  35         1047636  
  35         1132  
12 35     35   285 use Scalar::Util qw/blessed/;
  35         65  
  35         1659  
13 35     35   209 use Carp ();
  35         63  
  35         552  
14 35     35   168 use POSIX qw/floor/;
  35         65  
  35         270  
15 35     35   51011 use Class::Inspector;
  35         76  
  35         3172  
16              
17             use overload (
18 35         396 'fallback' => 1,
19             '<=>' => \&_compare_overload,
20             'cmp' => \&_string_compare_overload,
21             '""' => \&_stringify,
22             '-' => \&_subtract_overload,
23             '+' => \&_add_overload,
24             'eq' => \&_string_equals_overload,
25             'ne' => \&_string_not_equals_overload,
26 35     35   215 );
  35         64  
27              
28 35     35   20104 use Class::Accessor::Lite ro => [qw/time_zone locale formatter/];
  35         35762  
  35         235  
29              
30             BEGIN {
31 35     35   4545 local $@;
32 35 50       78 if (eval { require Data::Util; 1 }) {
  35         13832  
  35         23007  
33 35         240041 *is_instance = \&Data::Util::is_instance;
34             }
35             else {
36 0 0       0 *is_instance = sub { blessed($_[0]) && $_[0]->isa($_[1]) };
  0         0  
37             }
38             }
39              
40             my $_DEFAULT_LOCALE = DateTime::Locale->load('en_US');
41             my $_FLOATING_TIME_ZONE = DateTime::TimeZone->new(name => 'floating');
42             my $_UTC_TIME_ZONE = DateTime::TimeZone->new(name => 'UTC');
43 846     846   2059 sub _default_locale { $_DEFAULT_LOCALE }
44 1712     1712   3586 sub _default_formatter { undef }
45 97     97   431 sub _default_time_zone { $_FLOATING_TIME_ZONE }
46              
47             sub _inflate_locale {
48 855     855   1649 my ($class, $locale) = @_;
49 855 50       1640 return $class->_default_locale unless defined $locale;
50 855 100       1654 return $locale if _isa_locale($locale);
51 24         120 return DateTime::Locale->load($locale);
52             }
53              
54             sub _inflate_formatter {
55 852     852   4772 my ($class, $formatter) = @_;
56 852 100       1811 return $class->_default_formatter unless defined $formatter;
57 5 100       13 return $formatter if _isa_formatter($formatter);
58 1         229 Carp::croak 'formatter should can format_datetime.';
59             }
60              
61             sub _inflate_time_zone {
62 843     843   1391 my ($class, $time_zone) = @_;
63 843 50       1369 return $class->_default_time_zone unless defined $time_zone;
64 843 100       1345 return $time_zone if _isa_time_zone($time_zone);
65 751         2470 return DateTime::TimeZone->new(name => $time_zone);
66             }
67              
68             sub isa {
69 335     335 0 12061 my ($invocant, $a) = @_;
70 335 100       654 return !!1 if $a eq 'DateTime';
71 326         1522 return $invocant->SUPER::isa($a);
72             }
73              
74             sub _moment_resolve_instant {
75 134     134   271 my ($moment, $time_zone) = @_;
76 134 100       477 if ($time_zone->is_floating) {
77 8         53 return $moment->with_offset_same_local(0);
78             }
79             else {
80 126         604 my $offset = $time_zone->offset_for_datetime($moment) / 60;
81 126         14313050 return $moment->with_offset_same_instant($offset);
82             }
83             }
84              
85             sub _moment_resolve_local {
86 680     680   1059 my ($moment, $time_zone) = @_;
87 680 100       1416 if ($time_zone->is_floating) {
88 76         368 return $moment->with_offset_same_local(0);
89             }
90             else {
91 604         2164 my $offset = $time_zone->offset_for_local_datetime($moment) / 60;
92 600         13214042 return $moment->with_offset_same_local($offset);
93             }
94             }
95              
96             sub new {
97 823     823 0 1765556 my $class = shift;
98 823 50 33     3838 my %args = (@_ == 1 && ref $_[0] eq 'HASH') ? %{$_[0]} : @_;
  0         0  
99              
100 823 100       1845 $args{locale} = delete $args{language} if exists $args{language};
101 823   66     2463 my $locale = delete $args{locale} || $class->_default_locale;
102 823   66     1987 my $formatter = delete $args{formatter} || $class->_default_formatter;
103 823   66     1796 my $time_zone = delete $args{time_zone} || $class->_default_time_zone;
104              
105 823         1298 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
106              
107 823         5083 my $self = bless {
108             _moment => Time::Moment->new(%args),
109             locale => $class->_inflate_locale($locale),
110             formatter => $class->_inflate_formatter($formatter),
111             time_zone => $class->_inflate_time_zone($time_zone),
112             } => $class;
113 810         534694 return $self->_adjust_to_current_offset();
114             }
115              
116             sub now {
117 20     20 0 205221 my $class = shift;
118 20 50 33     120 my %args = (@_ == 1 && ref $_[0] eq 'HASH') ? %{$_[0]} : @_;
  0         0  
119              
120 20 50       71 $args{locale} = delete $args{language} if exists $args{language};
121 20   66     112 my $locale = delete $args{locale} || $class->_default_locale;
122 20   33     122 my $formatter = delete $args{formatter} || $class->_default_formatter;
123 20 100       60 my $time_zone = exists $args{time_zone} ? $class->_inflate_time_zone(delete $args{time_zone}) : $_UTC_TIME_ZONE;
124 20 50       3271 if (%args) {
125 0         0 my $msg = 'Invalid args: '.join ',', keys %args;
126 0         0 Carp::croak $msg;
127             }
128              
129 20         48 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
130              
131 20         857 return bless {
132             _moment => _moment_resolve_instant(Time::Moment->now, $time_zone),
133             locale => $class->_inflate_locale($locale),
134             formatter => $class->_inflate_formatter($formatter),
135             time_zone => $time_zone,
136             } => $class;
137             }
138              
139             sub from_object {
140 7     7 0 206873 my $class = shift;
141 7 50 33     46 my %args = (@_ == 1 && ref $_[0] eq 'HASH') ? %{$_[0]} : @_;
  0         0  
142             my $object = delete $args{object}
143 7 50       36 or Carp::croak 'object is required.';
144              
145 7 50       21 $args{locale} = delete $args{language} if exists $args{language};
146 7   66     32 my $locale = delete $args{locale} || $class->_default_locale;
147 7   66     34 my $formatter = delete $args{formatter} || $class->_default_formatter;
148 7 100       58 my $time_zone = $object->can('time_zone') ? $object->time_zone : $_FLOATING_TIME_ZONE;
149 7 50       45 if (%args) {
150 0         0 my $msg = 'Invalid args: '.join ',', keys %args;
151 0         0 Carp::croak $msg;
152             }
153              
154 7 100       27 if ($object->isa(__PACKAGE__)) {
155 4         42 my $self = $object->clone;
156 4         17 $self->set_locale($locale);
157 4 100       22 $self->set_formatter($formatter) if $formatter;
158 4         20 return $self;
159             }
160              
161 3         8 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
162              
163 3         5 my $moment;
164 3 50       7 if (_isa_moment_convertable($object)) {
165 3         20 $moment = Time::Moment->from_object($object);
166             }
167             else {
168 0         0 require DateTime; # fallback
169 0         0 my $object = DateTime->from_object(object => $object);
170 0 0       0 if ($object->time_zone->is_floating) {
171 0         0 $time_zone = $object->time_zone;
172 0         0 $object->set_time_zone($_UTC_TIME_ZONE);
173             }
174 0         0 $moment = Time::Moment->from_object($object);
175             }
176              
177 3         58 return bless {
178             _moment => _moment_resolve_instant($moment, $time_zone),
179             locale => $class->_inflate_locale($locale),
180             formatter => $class->_inflate_formatter($formatter),
181             time_zone => $time_zone,
182             } => $class;
183             }
184              
185             sub from_epoch {
186 19     19 0 10171 my $class = shift;
187 19 50 33     87 my %args = (@_ == 1 && ref $_[0] eq 'HASH') ? %{$_[0]} : @_;
  0         0  
188 19 50       47 Carp::croak 'epoch is required.' unless exists $args{epoch};
189              
190 19         39 my $epoch = delete $args{epoch};
191              
192 19 50       42 $args{locale} = delete $args{language} if exists $args{language};
193 19   66     62 my $locale = delete $args{locale} || $class->_default_locale;
194 19   66     70 my $formatter = delete $args{formatter} || $class->_default_formatter;
195 19 100       42 my $time_zone = exists $args{time_zone} ? $class->_inflate_time_zone(delete $args{time_zone}) : $_UTC_TIME_ZONE;
196 19 50       237 if (%args) {
197 0         0 my $msg = 'Invalid args: '.join ',', keys %args;
198 0         0 Carp::croak $msg;
199             }
200              
201 19         35 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
202              
203 19         23 my $moment = do {
204 19     3   107 local $SIG{__WARN__} = sub { die @_ };
  3         23  
205 19         252 Time::Moment->from_epoch($epoch);
206             };
207              
208 16         85 return bless {
209             _moment => _moment_resolve_instant($moment, $time_zone),
210             locale => $class->_inflate_locale($locale),
211             formatter => $class->_inflate_formatter($formatter),
212             time_zone => $time_zone,
213             } => $class;
214             }
215              
216 2     2 0 529 sub today { shift->now(@_)->truncate(to => 'day') }
217              
218             sub last_day_of_month {
219 28     28 0 12564 my $class = shift;
220 28 50 33     137 my %args = (@_ == 1 && ref $_[0] eq 'HASH') ? %{$_[0]} : @_;
  0         0  
221 28         54 for my $key (qw/year month/) {
222 56 50       116 Carp::croak "Parameter: $key is required." unless defined $args{$key};
223             }
224 28 100 66     248 Carp::croak q{Parameter 'month' is out of the range [1, 12]} if 0 > $args{month} || $args{month} > 12;
225              
226 27         50 my ($year, $month) = @args{qw/year month/};
227 27         60 my $day = _month_length($year, $month);
228              
229 27         36 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
230 27         83 return $class->new(%args, day => $day);
231             }
232              
233             my @_MONTH_LENGTH = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
234             sub _month_length {
235 27     27   43 my ($year, $month) = @_;
236 27         46 my $day = $_MONTH_LENGTH[$month-1];
237 27 100 100     62 $day++ if $month == 2 && _is_leap_year($year);
238 27         41 return $day;
239             }
240              
241             sub _is_leap_year {
242 11     11   2296 my $year = shift;
243 11 100       37 return 0 if $year % 4;
244 8 100       22 return 1 if $year % 100;
245 6 100       17 return 0 if $year % 400;
246 3         12 return 1;
247             }
248              
249             sub from_day_of_year {
250 26     26 0 12854 my $class = shift;
251 26 50 33     121 my %args = (@_ == 1 && ref $_[0] eq 'HASH') ? %{$_[0]} : @_;
  0         0  
252 26         52 for my $key (qw/year day_of_year/) {
253 52 50       96 Carp::croak "Parameter: $key is required." unless defined $args{$key};
254             }
255              
256 26         40 my $day_of_year = delete $args{day_of_year};
257              
258 26         37 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
259 26         75 my $self = $class->new(%args);
260 26         130 $self->{_moment} = $self->{_moment}->with_day_of_year($day_of_year);
261 25         46 return $self->_adjust_to_current_offset();
262             }
263              
264             sub _adjust_to_current_offset {
265 835     835   1121 my $self = shift;
266 835 100       2901 return $self if $self->{time_zone}->is_floating;
267              
268 745         3354 my $offset = $self->{time_zone}->offset_for_local_datetime($self->{_moment}) / 60;
269 743         7836 $self->{_moment} = $self->{_moment}->with_offset_same_local($offset);
270              
271 743         2521 return $self;
272             }
273              
274 100     100 0 38691 sub clone { bless { %{$_[0]} }, ref $_[0] }
  100         625  
275              
276             # Date / Calendar
277 85     85 0 2340 sub year { $_[0]->{_moment}->year }
278 0     0 0 0 sub year_0 { $_[0]->{_moment}->year - 1 }
279 10     10 0 116 sub month_0 { $_[0]->{_moment}->month - 1 }
280 84     84 0 3592 sub month { $_[0]->{_moment}->month }
281 10     10 0 800 sub day_of_week { $_[0]->{_moment}->day_of_week }
282 11     11 0 113 sub day_of_week_0 { $_[0]->{_moment}->day_of_week - 1 }
283 100     100 0 3196 sub day_of_month { $_[0]->{_moment}->day_of_month }
284 4     4 0 42 sub day_of_month_0 { $_[0]->{_moment}->day_of_month - 1 }
285 6     6 0 40 sub day_of_quarter { $_[0]->{_moment}->day_of_quarter }
286 2     2 0 13 sub day_of_quarter_0 { $_[0]->{_moment}->day_of_quarter - 1 }
287 27     27 0 142 sub day_of_year { $_[0]->{_moment}->day_of_year }
288 3     3 0 19 sub day_of_year_0 { $_[0]->{_moment}->day_of_year - 1 }
289 5     5 0 62 sub quarter { $_[0]->{_moment}->quarter }
290 2     2 0 23 sub quarter_0 { $_[0]->{_moment}->quarter - 1 }
291 3     3 0 34 sub weekday_of_month { int(($_[0]->{_moment}->day_of_month + 6) / 7) }
292 26     26 0 86 sub week_number { $_[0]->{_moment}->week }
293 26     26 0 138 sub week_year { $_[0]->{_moment}->strftime('%G') + 0 }
294              
295             sub week {
296 26     26 0 76 return ($_[0]->week_year, $_[0]->week_number);
297             }
298             sub week_of_month {
299 3     3 0 8 my $moment = shift->{_moment};
300 3         16 my $thu = $moment->day_of_month + 4 - $moment->day_of_week;
301 3         14 return int(($thu + 6) / 7);
302             }
303              
304 2     2 0 23 sub is_leap_year { $_[0]->{_moment}->is_leap_year + 0 }
305              
306             # Time of Day
307 33     33 0 2857 sub hour { $_[0]->{_moment}->hour }
308 4 100   4 0 38 sub hour_1 { $_[0]->{_moment}->hour || 24 }
309 3 100   3 0 12 sub hour_12 { $_[0]->hour_12_0 || 12 }
310 6     6 0 44 sub hour_12_0 { $_[0]->{_moment}->hour % 12 }
311 28     28 0 2814 sub minute { $_[0]->{_moment}->minute }
312 25     25 0 4065 sub second { $_[0]->{_moment}->second }
313 20     20 0 2899 sub nanosecond { $_[0]->{_moment}->nanosecond }
314 5     5 0 30 sub millisecond { $_[0]->{_moment}->millisecond }
315 5     5 0 31 sub microsecond { $_[0]->{_moment}->microsecond }
316              
317             sub fractional_second {
318 1     1 0 3 my $moment = $_[0]->{_moment};
319 1         12 return $moment->second + $moment->nanosecond / 1_000_000_000;
320             }
321              
322 0     0 0 0 sub leap_seconds { 0 }
323 0     0 0 0 sub is_finite { 1 }
324 0     0 0 0 sub is_infinite { 0 }
325              
326             # Absolute values
327 17     17 0 153 sub epoch { $_[0]->{_moment}->epoch }
328              
329             sub hires_epoch {
330 1     1 0 3 my $moment = $_[0]->{_moment};
331 1         8 return $moment->epoch + $moment->nanosecond / 1_000_000_000;
332             }
333              
334 7     7 0 90 sub mjd { $_[0]->{_moment}->mjd }
335 7     7 0 56 sub jd { $_[0]->{_moment}->jd }
336 0     0 0 0 sub rd { $_[0]->{_moment}->rd }
337 110     110 0 653 sub utc_rd_values { $_[0]->{_moment}->utc_rd_values }
338 2     2 0 2929 sub local_rd_values { $_[0]->{_moment}->local_rd_values }
339 2     2 0 35 sub utc_rd_as_seconds { $_[0]->{_moment}->utc_rd_as_seconds }
340 0     0 0 0 sub local_rd_as_seconds { $_[0]->{_moment}->local_rd_as_seconds }
341              
342             # Time zone
343 6     6 0 569 sub offset { $_[0]->{_moment}->offset * 60 }
344 1     1 0 12 sub is_dst { $_[0]->{time_zone}->is_dst_for_datetime($_[0]) }
345 0     0 0 0 sub time_zone_long_name { $_[0]->{time_zone}->name }
346 1     1 0 12 sub time_zone_short_name { $_[0]->{time_zone}->short_name_for_datetime($_[0]) }
347              
348 0     0 0 0 sub utc_year { $_[0]->{_moment}->utc_year }
349              
350             # Locale
351 6     6 0 36 sub ce_year { $_[0]->{_moment}->year }
352 1     1 0 452 sub era_name { $_[0]->{locale}->era_wide->[1] }
353 5     5 0 493 sub era_abbr { $_[0]->{locale}->era_abbreviated->[1] }
354 2     2 0 454 sub christian_era { 'AD' }
355 2     2 0 9 sub secular_era { 'CE' }
356              
357             sub year_with_era {
358 1     1 0 552 $_[0]->ce_year . $_[0]->era_abbr;
359             }
360              
361             sub year_with_christian_era {
362 1     1 0 3 $_[0]->ce_year . $_[0]->christian_era;
363             }
364              
365             sub year_with_secular_era {
366 1     1 0 3 $_[0]->ce_year . $_[0]->secular_era;
367             }
368              
369             sub month_name {
370 5     5 0 25 $_[0]->{locale}->month_format_wide->[ $_[0]->month_0 ];
371             }
372             sub month_abbr {
373 4     4 0 22 $_[0]->{locale}->month_format_abbreviated->[ $_[0]->month_0 ];
374             }
375             sub day_name {
376 4     4 0 24 $_[0]->{locale}->day_format_wide->[ $_[0]->day_of_week_0];
377             }
378             sub day_abbr {
379 4     4 0 26 $_[0]->{locale}->day_format_abbreviated->[ $_[0]->day_of_week_0 ];
380             }
381             sub am_or_pm {
382 20 100   20 0 71 $_[0]->{locale}->am_pm_abbreviated->[ $_[0]->{_moment}->hour < 12 ? 0 : 1 ];
383             }
384             sub quarter_name {
385 1     1 0 6 $_[0]->{locale}->quarter_format_wide->[ $_[0]->quarter_0 ];
386             }
387             sub quarter_abbr {
388 1     1 0 461 $_[0]->{locale}->quarter_format_abbreviated->[ $_[0]->quarter_0 ];
389             }
390              
391             sub local_day_of_week {
392 0     0 0 0 my $moment = $_[0]->{_moment};
393 0         0 return 1 + ($moment->day_of_week - $_[0]->{locale}->first_day_of_week) % 7;
394             }
395              
396             sub _escape_pct {
397 8     8   26 (my $string = $_[0]) =~ s/%/%%/g; $string;
  8         23  
398             }
399              
400             sub ymd {
401 525     525 0 3019 my $moment = shift->{_moment};
402 525   66     1133 my $hyphen = !defined $_[0] || $_[0] eq '-';
403 525 100       896 my $format = $hyphen ? '%Y-%m-%d' : join(_escape_pct($_[0]), qw(%Y %m %d));
404 525         2848 return $moment->strftime($format);
405             }
406              
407             sub mdy {
408 3     3 0 8 my $moment = shift->{_moment};
409 3   66     15 my $hyphen = !defined $_[0] || $_[0] eq '-';
410 3 100       11 my $format = $hyphen ? '%m-%d-%Y' : join(_escape_pct($_[0]), qw(%m %d %Y));
411 3         22 return $moment->strftime($format);
412             }
413              
414             sub dmy {
415 3     3 0 9 my $moment = shift->{_moment};
416 3   66     16 my $hyphen = !defined $_[0] || $_[0] eq '-';
417 3 100       10 my $format = $hyphen ? '%d-%m-%Y' : join(_escape_pct($_[0]), qw(%d %m %Y));
418 3         21 return $moment->strftime($format);
419             }
420              
421             sub hms {
422 4     4 0 9 my $moment = shift->{_moment};
423 4   66     20 my $colon = !defined $_[0] || $_[0] eq ':';
424 4 100       11 my $format = $colon ? '%H:%M:%S' : join(_escape_pct($_[0]), qw(%H %M %S));
425 4         27 return $moment->strftime($format);
426             }
427              
428             sub iso8601 {
429 105     105 0 2920 return $_[0]->{_moment}->strftime('%Y-%m-%dT%H:%M:%S');
430             }
431              
432             sub subtract_datetime {
433 44     44 0 4755 my ($lhs, $rhs) = @_;
434 44         75 my $class = ref $lhs;
435              
436             # normalize
437 44 50       77 $rhs = $class->from_object(object => $rhs) unless $rhs->isa($class);
438 44 100       127 $rhs = $rhs->clone->set_time_zone($lhs->time_zone) unless $lhs->time_zone->name eq $rhs->time_zone->name;
439              
440 44         530 my ($lhs_moment, $rhs_moment) = map { $_->{_moment} } ($lhs, $rhs);
  88         166  
441              
442 44 100       254 my $sign = $lhs_moment < $rhs_moment ? -1 : 1;
443 44 100       99 ($lhs_moment, $rhs_moment) = ($rhs_moment, $lhs_moment) if $sign == -1;
444              
445 44         132 my $months = $rhs_moment->delta_months($lhs_moment);
446 44         128 my $days = $lhs_moment->day_of_month - $rhs_moment->day_of_month;
447 44         100 my $minutes = $lhs_moment->minute_of_day - $rhs_moment->minute_of_day;
448 44         94 my $seconds = $lhs_moment->second - $rhs_moment->second;
449 44         90 my $nanoseconds = $lhs_moment->nanosecond - $rhs_moment->nanosecond;
450              
451 44         71 my $time_zone = $lhs->{time_zone};
452 44 100       110 if ($time_zone->has_dst_changes) {
453 19         87 my $lhs_dst = $time_zone->is_dst_for_datetime($lhs_moment);
454 19         1467 my $rhs_dst = $time_zone->is_dst_for_datetime($rhs_moment);
455              
456 19 100       1227 if ($lhs_dst != $rhs_dst) {
457 11         14 my $previous = eval {
458 11         51 _moment_resolve_local($lhs_moment->minus_days(1), $time_zone);
459             };
460              
461 11 100       154 if (defined $previous) {
462 10         18 my $previous_dst = $time_zone->is_dst_for_datetime($previous);
463 10 100       618 if ($lhs_dst) {
464 4 50       14 $minutes -= 60 if !$previous_dst;
465             }
466             else {
467 6 100       15 $minutes += 60 if $previous_dst;
468             }
469             }
470             }
471             }
472              
473 44 100       155 if ($nanoseconds < 0) {
474 6         11 $nanoseconds += 1_000_000_000;
475 6         8 $seconds--;
476             }
477 44 100       75 if ($seconds < 0) {
478 5         5 $seconds += 60;
479 5         6 $minutes--;
480             }
481 44 100       83 if ($minutes < 0) {
482 8         15 $minutes += 24 * 60;
483 8         10 $days--;
484             }
485 44 100       72 if ($days < 0) {
486 9         26 $days += $rhs_moment->length_of_month;
487 9         23 $months -= $lhs_moment->day_of_month > $rhs_moment->day_of_month;
488             }
489              
490 44         193 return DateTimeX::Moment::Duration->new(
491             months => $sign * $months,
492             days => $sign * $days,
493             minutes => $sign * $minutes,
494             seconds => $sign * $seconds,
495             nanoseconds => $sign * $nanoseconds,
496             );
497             }
498              
499             sub subtract_datetime_absolute {
500 4     4 0 17 my ($lhs, $rhs) = @_;
501 4         9 my $class = ref $lhs;
502              
503 4 50       11 $rhs = $class->from_object(object => $rhs)
504             unless $rhs->isa($class);
505              
506 4         11 my ($lhs_moment, $rhs_moment) = ($lhs->{_moment}, $rhs->{_moment});
507              
508 4         17 my $seconds = $rhs_moment->delta_seconds($lhs_moment);
509 4         16 my $nanoseconds = $rhs_moment->plus_seconds($seconds)
510             ->delta_nanoseconds($lhs_moment);
511              
512 4         17 return DateTimeX::Moment::Duration->new(
513             seconds => $seconds,
514             nanoseconds => $nanoseconds,
515             );
516             }
517              
518             sub _stringify {
519 63     63   1429 my $self = shift;
520 63 100       181 return $self->iso8601 unless defined $self->{formatter};
521 10         24 return $self->{formatter}->format_datetime($self);
522             }
523              
524             sub _compare_overload {
525 15     15   2119 my ($lhs, $rhs, $flip) = @_;
526 15 100       71 return undef unless defined $rhs;
527 13 50       42 return $flip ? -$lhs->compare($rhs) : $lhs->compare($rhs);
528             }
529              
530             sub _string_compare_overload {
531 15     15   1732 my ($lhs, $rhs, $flip) = @_;
532 15 50       31 return undef unless defined $rhs;
533 15 100       27 goto \&_compare_overload if _isa_datetime_compareble($rhs);
534              
535             # One is a DateTimeX::Moment object, one isn't. Just stringify and compare.
536 12 100       24 my $sign = $flip ? -1 : 1;
537 12         19 return $sign * ("$lhs" cmp "$rhs");
538             }
539              
540 6     6   500 sub _string_not_equals_overload { !_string_equals_overload(@_) }
541             sub _string_equals_overload {
542 12 50   12   754 my ($class, $lhs, $rhs) = ref $_[0] ? (ref $_[0], @_) : @_;
543 12 50       24 return undef unless defined $rhs;
544 12 100       23 return !$class->compare($lhs, $rhs) if _isa_datetime_compareble($rhs);
545 10         20 return "$lhs" eq "$rhs";
546             }
547              
548             sub _add_overload {
549 6     6   1168 my ($dt, $dur, $flip) = @_;
550 6 50       18 ($dur, $dt) = ($dt, $dur) if $flip;
551              
552 6 100       17 unless (_isa_duration($dur)) {
553 2         4 my $class = ref $dt;
554 2         8 Carp::croak("Cannot add $dur to a $class object ($dt).\n"
555             . ' Only a DateTime::Duration object can '
556             . " be added to a $class object.");
557             }
558              
559 4         13 return $dt->clone->add_duration($dur);
560             }
561              
562             sub _subtract_overload {
563 12     12   1061 my ($date1, $date2, $flip) = @_;
564 12 50       24 ($date2, $date1) = ($date1, $date2) if $flip;
565              
566 12 100       24 if (_isa_duration($date2)) {
    100          
567 1         3 my $new = $date1->clone;
568 1         4 $new->add_duration($date2->inverse);
569 1         5 return $new;
570             }
571             elsif (_isa_datetime($date2)) {
572 9         20 return $date1->subtract_datetime($date2);
573             }
574              
575 2         21 my $class = ref $date1;
576 2         10 Carp::croak(
577             "Cannot subtract $date2 from a $class object ($date1).\n"
578             . ' Only a DateTime::Duration or DateTimeX::Moment object can '
579             . " be subtracted from a $class object." );
580             }
581              
582 75     75 0 266 sub compare { shift->_compare(@_, 0) }
583 2     2 0 6 sub compare_ignore_floating { shift->_compare(@_, 1) }
584              
585             sub _compare {
586 77 100   77   210 my ($class, $lhs, $rhs, $consistent) = ref $_[0] ? (__PACKAGE__, @_) : @_;
587 77 50       135 return undef unless defined $rhs;
588              
589 77 100 66     164 if (!_isa_datetime_compareble($lhs) || !_isa_datetime_compareble($rhs)) {
590 2         6 Carp::croak("A DateTimeX::Moment object can only be compared to another DateTimeX::Moment object ($lhs, $rhs).");
591             }
592              
593 75 100 66     428 if (!$consistent && $lhs->can('time_zone') && $rhs->can('time_zone')) {
      100        
594 71         214 my $is_floating1 = $lhs->time_zone->is_floating;
595 71         509 my $is_floating2 = $rhs->time_zone->is_floating;
596              
597 71 50 66     573 if ($is_floating1 && !$is_floating2) {
    100 100        
598 0         0 $lhs = $lhs->clone->set_time_zone($rhs->time_zone);
599             }
600             elsif ($is_floating2 && !$is_floating1) {
601 3         9 $rhs = $rhs->clone->set_time_zone($lhs->time_zone);
602             }
603             }
604              
605 75 100 66     159 if ($lhs->isa(__PACKAGE__) && $rhs->isa(__PACKAGE__)) {
606 73         465 return $lhs->{_moment}->compare($rhs->{_moment});
607             }
608              
609 2         12 my @lhs_components = $lhs->utc_rd_values;
610 2         5 my @rhs_components = $rhs->utc_rd_values;
611              
612 2         11 for my $i (0 .. 2) {
613 4 100       20 return $lhs_components[$i] <=> $rhs_components[$i] if $lhs_components[$i] != $rhs_components[$i];
614             }
615              
616 0         0 return 0;
617             }
618              
619             sub set {
620 41     41 0 118 my $self = shift;
621 41 50 33     181 my %args = (@_ == 1 && ref $_[0] eq 'HASH') ? %{$_[0]} : @_;
  0         0  
622              
623 41         123 my $moment = $self->{_moment};
624 41         522 my %params = (
625             year => $moment->year,
626             month => $moment->month,
627             day => $moment->day_of_month,
628             hour => $moment->hour,
629             minute => $moment->minute,
630             second => $moment->second,
631             nanosecond => $moment->nanosecond,
632             );
633 41         129 for my $component (keys %args) {
634 69 50       127 next unless exists $params{$component};
635 69         128 $params{$component} = delete $args{$component};
636             }
637 41 50       86 if (%args) {
638 0         0 my $msg = 'Invalid args: '.join ',', keys %args;
639 0         0 Carp::croak $msg;
640             }
641              
642 41         317 my $result = Time::Moment->new(%params, offset => $moment->offset);
643 31 50       163 if (!$moment->is_equal($result)) {
644 31         84 $self->{_moment} = _moment_resolve_local($result, $self->{time_zone});
645             }
646 31         120 return $self;
647             }
648              
649 1     1 0 6 sub set_year { $_[0]->set(year => $_[1]) }
650 1     1 0 4 sub set_month { $_[0]->set(month => $_[1]) }
651 1     1 0 4 sub set_day { $_[0]->set(day => $_[1]) }
652 1     1 0 4 sub set_hour { $_[0]->set(hour => $_[1]) }
653 1     1 0 3 sub set_minute { $_[0]->set(minute => $_[1]) }
654 1     1 0 4 sub set_second { $_[0]->set(second => $_[1]) }
655 1     1 0 3 sub set_nanosecond { $_[0]->set(nanosecond => $_[1]) }
656              
657             sub set_time_zone {
658 31     31 0 194990 my ($self, $time_zone) = @_;
659 31 50       120 Carp::croak 'required time_zone' if @_ != 2;
660              
661 31         83 $time_zone = $self->_inflate_time_zone($time_zone);
662 31 100       73653 return $self if $time_zone == $self->{time_zone};
663 29 50       134 return $self if $time_zone->name eq $self->{time_zone}->name;
664              
665 29         204 $self->{_moment} = do {
666 29 100       82 if ($self->{time_zone}->is_floating) {
667 5         41 _moment_resolve_local($self->{_moment}, $time_zone)
668             }
669             else {
670 24         115 _moment_resolve_instant($self->{_moment}, $time_zone);
671             }
672             };
673 28         62 $self->{time_zone} = $time_zone;
674 28         88 return $self;
675             }
676              
677             sub set_locale {
678 6     6 0 18 my ($self, $locale) = @_;
679 6 50       19 Carp::croak 'required locale' if @_ != 2;
680 6         35 $self->{locale} = $self->_inflate_locale($locale);
681 6         1182 return $self;
682             }
683              
684             sub set_formatter {
685 3     3 0 74 my ($self, $formatter) = @_;
686 3         6 $self->{formatter} = $self->_inflate_formatter($formatter);
687 2         4 return $self;
688             }
689              
690 632     632 0 2181 sub add { shift->_calc_date(plus => @_) }
691 24     24 0 70 sub subtract { shift->_calc_date(minus => @_) }
692              
693             sub _calc_date {
694 656     656   842 my $self = shift;
695 656         788 my $type = shift;
696 656 50 33     1464 return $self->_calc_duration($type => @_) if @_ == 1 && _isa_duration($_[0]);
697              
698 656 50 33     1909 my %args = (@_ == 1 && ref $_[0] eq 'HASH') ? %{$_[0]} : @_;
  0         0  
699              
700 656         905 my $moment = $self->{_moment};
701              
702             {
703 656 50 66     1960 if (exists $args{years} && exists $args{months}) {
704 0 0       0 my $factor = ($type eq 'plus') ? 12 : -12;
705 0         0 $args{months} += delete($args{years}) * $factor;
706             }
707              
708 656         767 my $result = $moment;
709 656         1082 for my $unit (qw/weeks days months years/) {
710 2624 100       4077 next unless exists $args{$unit};
711 670         1280 my $method = $type.'_'.$unit;
712 670         3310 $result = $result->$method(delete $args{$unit});
713             }
714              
715 656 100       1799 if (!$moment->is_equal($result)) {
716 602         1171 $moment = _moment_resolve_local($result, $self->{time_zone});
717             }
718             }
719              
720             {
721 656         758 my $result = $moment;
  655         827  
  655         746  
722 655         1010 for my $unit (qw/nanoseconds seconds minutes hours/) {
723 2620 100       3894 next unless exists $args{$unit};
724 204         336 my $method = $type.'_'.$unit;
725 204         581 $result = $result->$method(delete $args{$unit});
726             }
727              
728 655 100       1369 if (!$moment->is_equal($result)) {
729 71         153 $moment = _moment_resolve_instant($result, $self->{time_zone});
730             }
731             }
732              
733 655 50       1251 if (%args) {
734 0         0 my $msg = 'Invalid args: '.join ',', keys %args;
735 0         0 Carp::croak $msg;
736             }
737              
738 655         1116 $self->{_moment} = $moment;
739 655         1291 return $self;
740             }
741              
742             sub delta_md {
743 4     4 0 2725 my ($lhs, $rhs) = @_;
744 4         8 my $class = ref $lhs;
745              
746 4 50       10 $rhs = $class->from_object(object => $rhs)
747             unless $rhs->isa($class);
748              
749 4         12 my ($lhs_moment, $rhs_moment) = ($lhs->{_moment}, $rhs->{_moment});
750              
751 4 100       33 if ($lhs_moment->rd < $rhs_moment->rd) {
752 1         4 ($lhs_moment, $rhs_moment) = ($rhs_moment, $lhs_moment);
753             }
754              
755 4         27 my $months = $rhs_moment->delta_months($lhs_moment);
756 4         17 my $days = $lhs_moment->day_of_month - $rhs_moment->day_of_month;
757              
758 4 50       12 if ($days < 0) {
759 0         0 $days += $rhs_moment->length_of_month;
760 0         0 $months -= $lhs_moment->day_of_month > $rhs_moment->day_of_month;
761             }
762              
763 4         20 return DateTimeX::Moment::Duration->new(
764             months => $months,
765             days => $days,
766             );
767             }
768              
769             sub delta_days {
770 5     5 0 2284 my ($lhs, $rhs) = @_;
771 5         12 my $class = ref $lhs;
772              
773 5 50       14 $rhs = $class->from_object(object => $rhs)
774             unless $rhs->isa($class);
775              
776             return DateTimeX::Moment::Duration->new(
777 5         38 days => abs($lhs->{_moment}->delta_days($rhs->{_moment}))
778             );
779             }
780              
781             sub delta_ms {
782 3     3 0 15 my ($lhs, $rhs) = reverse sort { $a <=> $b } @_;
  3         9  
783 3         37 my $days = floor($lhs->{_moment}->jd - $rhs->{_moment}->jd);
784 3         10 my $duration = $lhs->subtract_datetime($rhs);
785 3         10 return DateTimeX::Moment::Duration->new(
786             hours => $duration->hours + ($days * 24),
787             minutes => $duration->minutes,
788             seconds => $duration->seconds,
789             );
790             }
791              
792 0     0 0 0 sub delta_years { shift->_delta(years => @_) }
793 0     0 0 0 sub delta_months { shift->_delta(months => @_) }
794 0     0 0 0 sub delta_weeks { shift->_delta(weeks => @_) }
795             #sub delta_days { shift->_delta(days => @_) }
796 0     0 0 0 sub delta_hours { shift->_delta(hours => @_) }
797 0     0 0 0 sub delta_minutes { shift->_delta(minutes => @_) }
798 0     0 0 0 sub delta_seconds { shift->_delta(seconds => @_) }
799 0     0 0 0 sub delta_milliseconds { shift->_delta(milliseconds => @_) }
800 0     0 0 0 sub delta_microseconds { shift->_delta(microseconds => @_) }
801 0     0 0 0 sub delta_nanoseconds { shift->_delta(nanoseconds => @_) }
802              
803             sub _delta {
804 0     0   0 my ($self, $unit, $another) = @_;
805 0         0 my $lhs = $self->{_moment};
806 0 0       0 my $rhs = $another->isa(__PACKAGE__) ? $another->{_moment} : Time::Moment->from_object($another);
807 0 0       0 $rhs = Time::Moment->from_object($rhs) unless _isa_moment($rhs);
808              
809 0         0 my $method = "delta_$unit";
810 0 0       0 my $diff = $lhs > $rhs ? $rhs->$method($lhs) : $lhs->$method($rhs);
811              
812             # normalize
813 0 0       0 if ($unit eq 'milliseconds') {
    0          
814 0         0 $unit = 'nanoseconds';
815 0         0 $diff *= 1_000_000;
816             }
817             elsif ($unit eq 'microseconds') {
818 0         0 $unit = 'nanoseconds';
819 0         0 $diff *= 1_000;
820             }
821              
822 0         0 return DateTimeX::Moment::Duration->new($unit => $diff);
823             }
824              
825             # strftime
826             {
827             my %CUSTOM_HANDLER = (
828             a => sub { $_[0]->day_abbr },
829             A => sub { $_[0]->day_name },
830             b => sub { $_[0]->month_abbr },
831             B => sub { $_[0]->month_name },
832             c => sub { $_[0]->format_cldr($_[0]->{locale}->datetime_format_default()) },
833             p => sub { $_[0]->am_or_pm },
834             P => sub { lc $_[0]->am_or_pm },
835             r => sub { $_[0]->strftime('%I:%M:%S %p') },
836             x => sub { $_[0]->format_cldr($_[0]->{locale}->date_format_default()) },
837             X => sub { $_[0]->format_cldr($_[0]->{locale}->time_format_default()) },
838             Z => sub { $_[0]->{time_zone}->short_name_for_datetime($_[0]) },
839             );
840              
841             my $CUSTOM_HANDLER_REGEXP = '(?:(?<=[^%])((?:%%)*)|\A)%(['.(join '', keys %CUSTOM_HANDLER).'])';
842              
843             sub strftime {
844 164     164 0 282547 my ($self, @formats) = @_;
845 164         307 my $moment = $self->{_moment};
846              
847 164         194 my @ret;
848 164         251 for my $format (@formats) {
849             # XXX: follow locale/time_zone
850 165   50     852 $format =~ s/$CUSTOM_HANDLER_REGEXP/($1||'').$CUSTOM_HANDLER{$2}->($self)/omsge;
  41         248  
851 165 100 50     3060 $format =~ s/(?:(?<=[^%])((?:%%)*)|\A)%\{(\w+)\}/($1||'').($self->can($2) ? $self->$2 : "%{$2}")/omsge;
  7         73  
852              
853 165         744 my $ret = $moment->strftime($format);
854 165 100       987 return $ret unless wantarray;
855              
856 2         5 push @ret => $ret;
857             }
858              
859 1         4 return @ret;
860             }
861             }
862              
863             sub format_cldr {
864 105     105 0 112490 my $self = shift;
865              
866             # fallback
867 105         524 require DateTime;
868             return DateTime->from_object(
869             object => $self,
870             locale => $self->{locale},
871 105         341 )->format_cldr(@_);
872             }
873              
874             sub truncate :method {
875 40     40 0 2294 my $self = shift;
876 40 50 33     157 my %args = (@_ == 1 && ref $_[0] eq 'HASH') ? %{$_[0]} : @_;
  0         0  
877              
878             my $to = delete $args{to}
879 40 50       102 or Carp::croak "Parameter: to is required.";
880 40 50       78 if (%args) {
881 0         0 my $msg = 'Invalid args: '.join ',', keys %args;
882 0         0 Carp::croak $msg;
883             }
884              
885 40         86 my $moment = $self->{_moment};
886 40         46 my $result = do {
887 40 100       153 if ($to eq 'year') {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
888 1         7 $moment->with_day_of_year(1)
889             ->at_midnight;
890             }
891             elsif ($to eq 'month') {
892 2         17 $moment->with_day_of_month(1)
893             ->at_midnight;
894             }
895             elsif ($to eq 'week') {
896 9         41 $moment->with_day_of_week(1)
897             ->at_midnight;
898             }
899             elsif ($to eq 'local_week') {
900 16         38 my $dow = $self->{locale}->first_day_of_week;
901 16         109 $moment->minus_days(($moment->day_of_week - $dow) % 7)
902             ->at_midnight;
903             }
904             elsif ($to eq 'day') {
905 4         33 $moment->at_midnight;
906             }
907             elsif ($to eq 'hour') {
908 1         5 $moment->with_precision(-2);
909             }
910             elsif ($to eq 'minute') {
911 2         10 $moment->with_precision(-1);
912             }
913             elsif ($to eq 'second') {
914 1         10 $moment->with_precision(0);
915             }
916             else {
917 4         336 Carp::croak "The 'to' parameter '$to' is unsupported.";
918             }
919             };
920              
921 36 100       112 if (!$moment->is_equal($result)) {
922 31         59 $self->{_moment} = _moment_resolve_local($result, $self->{time_zone});
923             }
924 35         95 return $self;
925             }
926              
927             my %CALC_DURATION_METHOD = (plus => 'add_duration', minus => 'subtract_duration');
928             sub _calc_duration {
929 0     0   0 my ($self, $type, $duration) = @_;
930 0         0 my $method = $CALC_DURATION_METHOD{$type};
931 0         0 return $self->$method($duration);
932             }
933              
934 17     17 0 66 sub subtract_duration { $_[0]->add_duration($_[1]->inverse) }
935             sub add_duration {
936 50     50 0 743 my ($self, $duration) = @_;
937 50 50       94 Carp::croak 'required duration object' unless _isa_duration($duration);
938              
939             # simple optimization
940 50 50       134 return $self if $duration->is_zero;
941              
942 50 50       186 if (!$duration->is_limit_mode) {
943 0         0 Carp::croak 'DateTimeX::Moment supports limit mode only.';
944             }
945              
946 50         104 return $self->add($duration->deltas);
947             }
948              
949             # internal utilities
950 855 100   855   5741 sub _isa_locale { is_instance($_[0] => 'DateTime::Locale::FromData') || is_instance($_[0] => 'DateTime::Locale::Base') }
951 5 100   5   12 sub _isa_formatter { _isa_invocant($_[0]) && $_[0]->can('format_datetime') }
952 843     843   2528 sub _isa_time_zone { is_instance($_[0] => 'DateTime::TimeZone') }
953 11     11   37 sub _isa_datetime { is_instance($_[0] => 'DateTime') }
954 181 100   181   1067 sub _isa_datetime_compareble { blessed($_[0]) && $_[0]->can('utc_rd_values') }
955 68     68   424 sub _isa_duration { is_instance($_[0] => 'DateTime::Duration') }
956 0     0   0 sub _isa_moment { is_instance($_[0] => 'Time::Moment') }
957 3 50   3   25 sub _isa_moment_convertable { blessed($_[0]) && $_[0]->can('__as_Time_Moment') }
958 5 100   5   59 sub _isa_invocant { blessed $_[0] || Class::Inspector->loaded("$_[0]") }
959              
960             # define aliases
961             {
962             my %aliases = (
963             month => [qw/mon/],
964             day_of_month => [qw/day mday/],
965             day_of_month_0 => [qw/day_0 mday_0/],
966             day_of_week => [qw/wday dow/],
967             day_of_week_0 => [qw/wday_0 dow_0/],
968             day_of_quarter => [qw/doq/],
969             day_of_quarter_0 => [qw/doq_0/],
970             day_of_year => [qw/doy/],
971             day_of_year_0 => [qw/doy_0/],
972             ymd => [qw/date/],
973             hms => [qw/time/],
974             iso8601 => [qw/datetime/],
975             minute => [qw/min/],
976             second => [qw/sec/],
977             locale => [qw/language/],
978             era_abbr => [qw/era/],
979             );
980              
981             for my $src (keys %aliases) {
982             my $code = do {
983 35     35   377 no strict qw/refs/;
  35         92  
  35         2470  
984             \&{$src};
985             };
986              
987             for my $dst (@{ $aliases{$src} }) {
988 35     35   256 no strict qw/refs/;
  35         73  
  35         2251  
989             *{$dst} = $code;
990             }
991             }
992             }
993              
994             1;
995             __END__
996              
997             =encoding utf-8
998              
999             =head1 NAME
1000              
1001             DateTimeX::Moment - EXPERIMENTAL DateTime like interface for Time::Moment
1002              
1003             =head1 SYNOPSIS
1004              
1005             use DateTimeX::Moment;
1006              
1007             $dt = DateTimeX::Moment->new(
1008             year => 1964,
1009             month => 10,
1010             day => 16,
1011             hour => 16,
1012             minute => 12,
1013             second => 47,
1014             nanosecond => 500000000,
1015             time_zone => 'Asia/Taipei',
1016             );
1017              
1018             $dt = DateTimeX::Moment->from_epoch( epoch => $epoch );
1019             $dt = DateTimeX::Moment->now; # same as ( epoch => time() )
1020              
1021             $year = $dt->year;
1022             $month = $dt->month; # 1-12
1023              
1024             $day = $dt->day; # 1-31
1025              
1026             $dow = $dt->day_of_week; # 1-7 (Monday is 1)
1027              
1028             $hour = $dt->hour; # 0-23
1029             $minute = $dt->minute; # 0-59
1030              
1031             $second = $dt->second; # 0-61 (leap seconds!)
1032              
1033             $doy = $dt->day_of_year; # 1-366 (leap years)
1034              
1035             $doq = $dt->day_of_quarter; # 1..
1036              
1037             $qtr = $dt->quarter; # 1-4
1038              
1039             # all of the start-at-1 methods above have corresponding start-at-0
1040             # methods, such as $dt->day_of_month_0, $dt->month_0 and so on
1041              
1042             $ymd = $dt->ymd; # 2002-12-06
1043             $ymd = $dt->ymd('/'); # 2002/12/06
1044              
1045             $mdy = $dt->mdy; # 12-06-2002
1046             $mdy = $dt->mdy('/'); # 12/06/2002
1047              
1048             $dmy = $dt->dmy; # 06-12-2002
1049             $dmy = $dt->dmy('/'); # 06/12/2002
1050              
1051             $hms = $dt->hms; # 14:02:29
1052             $hms = $dt->hms('!'); # 14!02!29
1053              
1054             $is_leap = $dt->is_leap_year;
1055              
1056             # these are localizable, see Locales section
1057             $month_name = $dt->month_name; # January, February, ...
1058             $month_abbr = $dt->month_abbr; # Jan, Feb, ...
1059             $day_name = $dt->day_name; # Monday, Tuesday, ...
1060             $day_abbr = $dt->day_abbr; # Mon, Tue, ...
1061              
1062             # May not work for all possible datetime, see the docs on this
1063             # method for more details.
1064             $epoch_time = $dt->epoch;
1065              
1066             $rhs = $dt + $duration_object;
1067              
1068             $dt3 = $dt - $duration_object;
1069              
1070             $duration_object = $dt - $rhs;
1071              
1072             $dt->set( year => 1882 );
1073              
1074             $dt->set_time_zone( 'America/Chicago' );
1075              
1076             $dt->set_formatter( $formatter );
1077              
1078             =head1 BENCHMARK
1079              
1080             C<author/benchmark.pl>:
1081              
1082             new()
1083             Benchmark: timing 100000 iterations of datetime, moment...
1084             datetime: 4 wallclock secs ( 4.06 usr + 0.01 sys = 4.07 CPU) @ 24570.02/s (n=100000)
1085             moment: 1 wallclock secs ( 0.62 usr + 0.01 sys = 0.63 CPU) @ 158730.16/s (n=100000)
1086             Rate datetime moment
1087             datetime 24570/s -- -85%
1088             moment 158730/s 546% --
1089             ----------------------------------------
1090             now()
1091             Benchmark: timing 100000 iterations of datetime, moment...
1092             datetime: 4 wallclock secs ( 4.38 usr + 0.01 sys = 4.39 CPU) @ 22779.04/s (n=100000)
1093             moment: 1 wallclock secs ( 0.59 usr + 0.00 sys = 0.59 CPU) @ 169491.53/s (n=100000)
1094             Rate datetime moment
1095             datetime 22779/s -- -87%
1096             moment 169492/s 644% --
1097             ----------------------------------------
1098             from_epoch()
1099             Benchmark: timing 100000 iterations of datetime, moment...
1100             datetime: 4 wallclock secs ( 4.27 usr + 0.01 sys = 4.28 CPU) @ 23364.49/s (n=100000)
1101             moment: 1 wallclock secs ( 0.63 usr + 0.00 sys = 0.63 CPU) @ 158730.16/s (n=100000)
1102             Rate datetime moment
1103             datetime 23364/s -- -85%
1104             moment 158730/s 579% --
1105             ----------------------------------------
1106             calculate()
1107             Benchmark: timing 100000 iterations of datetime, moment...
1108             datetime: 20 wallclock secs (20.30 usr + 0.04 sys = 20.34 CPU) @ 4916.42/s (n=100000)
1109             moment: 1 wallclock secs ( 1.07 usr + 0.00 sys = 1.07 CPU) @ 93457.94/s (n=100000)
1110             Rate datetime moment
1111             datetime 4916/s -- -95%
1112             moment 93458/s 1801% --
1113             ----------------------------------------
1114              
1115             =head1 DESCRIPTION
1116              
1117             TODO: write it
1118              
1119             =head1 METHODS
1120              
1121             TODO: write it
1122              
1123             =head1 LICENSE
1124              
1125             Copyright (C) karupanerura.
1126              
1127             This is free software, licensed under:
1128             The Artistic License 2.0 (GPL Compatible)
1129              
1130             =head1 AUTHOR
1131              
1132             karupanerura E<lt>karupa@cpan.orgE<gt>
1133              
1134             =cut
1135