File Coverage

blib/lib/Time/Piece.pm
Criterion Covered Total %
statement 334 356 93.8
branch 124 148 83.7
condition 39 52 75.0
subroutine 77 77 100.0
pod 1 47 2.1
total 575 680 84.5


line stmt bran cond sub pod time code
1             package Time::Piece;
2              
3 12     12   554255 use strict;
  12         105  
  12         303  
4              
5 12     12   55 use XSLoader ();
  12         18  
  12         169  
6 12     12   4303 use Time::Seconds;
  12         25  
  12         675  
7 12     12   77 use Carp;
  12         19  
  12         623  
8 12     12   5286 use Time::Local;
  12         23929  
  12         630  
9 12     12   82 use Scalar::Util qw/ blessed /;
  12         18  
  12         655  
10              
11 12     12   61 use Exporter ();
  12         20  
  12         1881  
12              
13             our @EXPORT = qw(
14             localtime
15             gmtime
16             );
17              
18             our %EXPORT_TAGS = (
19             ':override' => 'internal',
20             );
21              
22             our $VERSION = '1.34';
23              
24             XSLoader::load( 'Time::Piece', $VERSION );
25              
26             my $DATE_SEP = '-';
27             my $TIME_SEP = ':';
28             my @MON_LIST = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
29             my @FULLMON_LIST = qw(January February March April May June July
30             August September October November December);
31             my @DAY_LIST = qw(Sun Mon Tue Wed Thu Fri Sat);
32             my @FULLDAY_LIST = qw(Sunday Monday Tuesday Wednesday Thursday Friday Saturday);
33             my $IS_WIN32 = ($^O =~ /Win32/);
34              
35             my $LOCALE;
36              
37             use constant {
38 12         9502 'c_sec' => 0,
39             'c_min' => 1,
40             'c_hour' => 2,
41             'c_mday' => 3,
42             'c_mon' => 4,
43             'c_year' => 5,
44             'c_wday' => 6,
45             'c_yday' => 7,
46             'c_isdst' => 8,
47             'c_epoch' => 9,
48             'c_islocal' => 10,
49 12     12   89 };
  12         20  
50              
51             sub localtime {
52 27 100   27 0 1033 unshift @_, __PACKAGE__ unless eval { $_[0]->isa('Time::Piece') };
  27         149  
53 27         54 my $class = shift;
54 27         44 my $time = shift;
55 27 100       57 $time = time if (!defined $time);
56 27         71 $class->_mktime($time, 1);
57             }
58              
59             sub gmtime {
60 31 100   31 0 42497 unshift @_, __PACKAGE__ unless eval { $_[0]->isa('Time::Piece') };
  31         193  
61 31         70 my $class = shift;
62 31         48 my $time = shift;
63 31 100       66 $time = time if (!defined $time);
64 31         78 $class->_mktime($time, 0);
65             }
66              
67              
68             # Check if the supplied param is either a normal array (as returned from
69             # localtime in list context) or a Time::Piece-like wrapper around one.
70             #
71             # We need to differentiate between an array ref that we can interrogate and
72             # other blessed objects (like overloaded values).
73             sub _is_time_struct {
74 204 100   204   535 return 1 if ref($_[1]) eq 'ARRAY';
75 77 100 100     276 return 1 if blessed($_[1]) && $_[1]->isa('Time::Piece');
76              
77 69         158 return 0;
78             }
79              
80              
81             sub new {
82 13     13 0 1605 my $class = shift;
83 13         25 my ($time) = @_;
84              
85 13         17 my $self;
86              
87 13 100 66     34 if ($class->_is_time_struct($time)) {
    100          
    100          
88 4 100       20 $self = $time->[c_islocal] ? $class->localtime($time) : $class->gmtime($time);
89             }
90             elsif (defined($time)) {
91 6         36 $self = $class->localtime($time);
92             }
93             elsif (ref($class) && $class->isa(__PACKAGE__)) {
94 1         4 $self = $class->_mktime($class->epoch, $class->[c_islocal]);
95             }
96             else {
97 2         8 $self = $class->localtime();
98             }
99              
100 13   66     67 return bless $self, ref($class) || $class;
101             }
102              
103             sub parse {
104 4     4 0 86 my $proto = shift;
105 4   100     15 my $class = ref($proto) || $proto;
106 4         5 my @components;
107              
108 4         216 warnings::warnif("deprecated",
109             "parse() is deprecated, use strptime() instead.");
110              
111 4 100       13 if (@_ > 1) {
112 2         5 @components = @_;
113             }
114             else {
115 2         30 @components = shift =~ /(\d+)$DATE_SEP(\d+)$DATE_SEP(\d+)(?:(?:T|\s+)(\d+)$TIME_SEP(\d+)(?:$TIME_SEP(\d+)))/;
116 2         8 @components = reverse(@components[0..5]);
117             }
118 4         11 return $class->new( timelocal(@components ));
119             }
120              
121             sub _mktime {
122 191     191   609 my ($class, $time, $islocal) = @_;
123              
124 191   66     677 $class = blessed($class) || $class;
125              
126 191 100       433 if ($class->_is_time_struct($time)) {
127 131         232 my @new_time = @$time;
128 131         279 my @tm_parts = (@new_time[c_sec .. c_mon], $new_time[c_year]+1900);
129             #TODO: what happens here for data below 1970?
130 131 100       364 $new_time[c_epoch] = $islocal ? timelocal(@tm_parts) : timegm(@tm_parts);
131              
132 131 50       4740 return wantarray ? @new_time : bless [@new_time[0..9], $islocal], $class;
133             }
134 60         1251 _tzset();
135 60 100       664 my @time = $islocal ?
136             CORE::localtime($time)
137             :
138             CORE::gmtime($time);
139 60 50       543 wantarray ? @time : bless [@time, $time, $islocal], $class;
140             }
141              
142             my %_special_exports = (
143 14     14   473 localtime => sub { my $c = $_[0]; sub { $c->localtime(@_) } },
144 17     17   1270 gmtime => sub { my $c = $_[0]; sub { $c->gmtime(@_) } },
145             );
146              
147             sub export {
148 13     13 0 45 my ($class, $to, @methods) = @_;
149 13         29 for my $method (@methods) {
150 26 50       62 if (exists $_special_exports{$method}) {
151 12     12   99 no strict 'refs';
  12         20  
  12         388  
152 12     12   60 no warnings 'redefine';
  12         27  
  12         18766  
153 26         53 *{$to . "::$method"} = $_special_exports{$method}->($class);
  26         7347  
154             } else {
155 0         0 $class->Exporter::export($to, $method);
156             }
157             }
158             }
159              
160             sub import {
161             # replace CORE::GLOBAL localtime and gmtime if passed :override
162 13     13   309 my $class = shift;
163 13         24 my %params;
164 13         74 map($params{$_}++,@_,@EXPORT);
165 13 50       84 if (delete $params{':override'}) {
166 0         0 $class->export('CORE::GLOBAL', keys %params);
167             }
168             else {
169 13         63 $class->export(scalar caller, keys %params);
170             }
171             }
172              
173             ## Methods ##
174              
175             sub sec {
176 63     63 0 84 my $time = shift;
177 63         176 $time->[c_sec];
178             }
179              
180             *second = \&sec;
181              
182             sub min {
183 63     63 0 74 my $time = shift;
184 63         108 $time->[c_min];
185             }
186              
187             *minute = \&min;
188              
189             sub hour {
190 63     63 0 94 my $time = shift;
191 63         112 $time->[c_hour];
192             }
193              
194             sub mday {
195 71     71 0 107 my $time = shift;
196 71         143 $time->[c_mday];
197             }
198              
199             *day_of_month = \&mday;
200              
201             sub mon {
202 57     57 0 84 my $time = shift;
203 57         228 $time->[c_mon] + 1;
204             }
205              
206             sub _mon {
207 54     54   74 my $time = shift;
208 54         100 $time->[c_mon];
209             }
210              
211             sub month {
212 5     5 0 16 my $time = shift;
213 5 100       18 if (@_) {
    50          
214 1         6 return $_[$time->[c_mon]];
215             }
216             elsif (@MON_LIST) {
217 4         50 return $MON_LIST[$time->[c_mon]];
218             }
219             else {
220 0         0 return $time->strftime('%b');
221             }
222             }
223              
224             *monname = \&month;
225              
226             sub fullmonth {
227 1     1 0 3 my $time = shift;
228 1 50       5 if (@_) {
    50          
229 0         0 return $_[$time->[c_mon]];
230             }
231             elsif (@FULLMON_LIST) {
232 1         4 return $FULLMON_LIST[$time->[c_mon]];
233             }
234             else {
235 0         0 return $time->strftime('%B');
236             }
237             }
238              
239             sub year {
240 114     114 0 725 my $time = shift;
241 114         444 $time->[c_year] + 1900;
242             }
243              
244             sub _year {
245 2     2   6 my $time = shift;
246 2         8 $time->[c_year];
247             }
248              
249             sub yy {
250 2     2 0 6 my $time = shift;
251 2         7 my $res = $time->[c_year] % 100;
252 2 100       14 return $res > 9 ? $res : "0$res";
253             }
254              
255             sub wday {
256 3     3 0 7 my $time = shift;
257 3         15 $time->[c_wday] + 1;
258             }
259              
260             sub _wday {
261 10     10   16 my $time = shift;
262 10         33 $time->[c_wday];
263             }
264              
265             *day_of_week = \&_wday;
266              
267             sub wdayname {
268 5     5 0 12 my $time = shift;
269 5 100       20 if (@_) {
    50          
270 1         6 return $_[$time->[c_wday]];
271             }
272             elsif (@DAY_LIST) {
273 4         16 return $DAY_LIST[$time->[c_wday]];
274             }
275             else {
276 0         0 return $time->strftime('%a');
277             }
278             }
279              
280             *day = \&wdayname;
281              
282             sub fullday {
283 2     2 0 4 my $time = shift;
284 2 50       8 if (@_) {
    50          
285 0         0 return $_[$time->[c_wday]];
286             }
287             elsif (@FULLDAY_LIST) {
288 2         9 return $FULLDAY_LIST[$time->[c_wday]];
289             }
290             else {
291 0         0 return $time->strftime('%A');
292             }
293             }
294              
295             sub yday {
296 5     5 0 12 my $time = shift;
297 5         20 $time->[c_yday];
298             }
299              
300             *day_of_year = \&yday;
301              
302             sub isdst {
303 5     5 0 11 my $time = shift;
304 5         19 $time->[c_isdst];
305             }
306              
307             *daylight_savings = \&isdst;
308              
309             # Thanks to Tony Olekshy for this algorithm
310             sub tzoffset {
311 3     3 0 14 my $time = shift;
312              
313 3 50       9 return Time::Seconds->new(0) unless $time->[c_islocal];
314              
315 3         16 my $epoch = $time->epoch;
316              
317             my $j = sub {
318              
319 6     6   15 my ($s,$n,$h,$d,$m,$y) = @_; $m += 1; $y += 1900;
  6         8  
  6         8  
320              
321 6         12 $time->_jd($y, $m, $d, $h, $n, $s);
322              
323 3         15 };
324              
325             # Compute floating offset in hours.
326             #
327             # Note use of crt methods so the tz is properly set...
328             # See: http://perlmonks.org/?node_id=820347
329 3         19 my $delta = 24 * ($j->(_crt_localtime($epoch)) - $j->(_crt_gmtime($epoch)));
330              
331             # Return value in seconds rounded to nearest minute.
332 3 50       29 return Time::Seconds->new( int($delta * 60 + ($delta >= 0 ? 0.5 : -0.5)) * 60 );
333             }
334              
335             sub epoch {
336 499     499 1 1195 my $time = shift;
337 499 50       831 if (defined($time->[c_epoch])) {
338 499         4898 return $time->[c_epoch];
339             }
340             else {
341             my $epoch = $time->[c_islocal] ?
342 0         0 timelocal(@{$time}[c_sec .. c_mon], $time->[c_year]+1900)
343             :
344 0 0       0 timegm(@{$time}[c_sec .. c_mon], $time->[c_year]+1900);
  0         0  
345 0         0 $time->[c_epoch] = $epoch;
346 0         0 return $epoch;
347             }
348             }
349              
350             sub hms {
351 13     13 0 24 my $time = shift;
352 13 100       31 my $sep = @_ ? shift(@_) : $TIME_SEP;
353 13         99 sprintf("%02d$sep%02d$sep%02d", $time->[c_hour], $time->[c_min], $time->[c_sec]);
354             }
355              
356             *time = \&hms;
357              
358             sub ymd {
359 15     15 0 25 my $time = shift;
360 15 100       42 my $sep = @_ ? shift(@_) : $DATE_SEP;
361 15         45 sprintf("%d$sep%02d$sep%02d", $time->year, $time->mon, $time->[c_mday]);
362             }
363              
364             *date = \&ymd;
365              
366             sub mdy {
367 3     3 0 8 my $time = shift;
368 3 100       11 my $sep = @_ ? shift(@_) : $DATE_SEP;
369 3         12 sprintf("%02d$sep%02d$sep%d", $time->mon, $time->[c_mday], $time->year);
370             }
371              
372             sub dmy {
373 3     3 0 7 my $time = shift;
374 3 100       11 my $sep = @_ ? shift(@_) : $DATE_SEP;
375 3         15 sprintf("%02d$sep%02d$sep%d", $time->[c_mday], $time->mon, $time->year);
376             }
377              
378             sub datetime {
379 7     7 0 915 my $time = shift;
380 7         33 my %seps = (date => $DATE_SEP, T => 'T', time => $TIME_SEP, @_);
381 7         26 return join($seps{T}, $time->date($seps{date}), $time->time($seps{time}));
382             }
383              
384              
385              
386             # Julian Day is always calculated for UT regardless
387             # of local time
388             sub julian_day {
389 25     25 0 501 my $time = shift;
390             # Correct for localtime
391 25 100       91 $time = $time->gmtime( $time->epoch ) if $time->[c_islocal];
392              
393             # Calculate the Julian day itself
394 25         100 my $jd = $time->_jd( $time->year, $time->mon, $time->mday,
395             $time->hour, $time->min, $time->sec);
396              
397 25         141 return $jd;
398             }
399              
400             # MJD is defined as JD - 2400000.5 days
401             sub mjd {
402 13     13 0 51 return shift->julian_day - 2_400_000.5;
403             }
404              
405             # Internal calculation of Julian date. Needed here so that
406             # both tzoffset and mjd/jd methods can share the code
407             # Algorithm from Hatcher 1984 (QJRAS 25, 53-55), and
408             # Hughes et al, 1989, MNRAS, 238, 15
409             # See: http://adsabs.harvard.edu/cgi-bin/nph-bib_query?bibcode=1989MNRAS.238.1529H&db_key=AST
410             # for more details
411              
412             sub _jd {
413 31     31   37 my $self = shift;
414 31         58 my ($y, $m, $d, $h, $n, $s) = @_;
415              
416             # Adjust input parameters according to the month
417 31 100       58 $y = ( $m > 2 ? $y : $y - 1);
418 31 100       52 $m = ( $m > 2 ? $m - 3 : $m + 9);
419              
420             # Calculate the Julian Date (assuming Julian calendar)
421 31         94 my $J = int( 365.25 *( $y + 4712) )
422             + int( (30.6 * $m) + 0.5)
423             + 59
424             + $d
425             - 0.5;
426              
427             # Calculate the Gregorian Correction (since we have Gregorian dates)
428 31         65 my $G = 38 - int( 0.75 * int(49+($y/100)));
429              
430             # Calculate the actual Julian Date
431 31         45 my $JD = $J + $G;
432              
433             # Modify to include hours/mins/secs in floating portion.
434 31         75 return $JD + ($h + ($n + $s / 60) / 60) / 24;
435             }
436              
437             sub week {
438 11     11 0 23 my $self = shift;
439              
440 11         24 my $J = $self->julian_day;
441             # Julian day is independent of time zone so add on tzoffset
442             # if we are using local time here since we want the week day
443             # to reflect the local time rather than UTC
444 11 50       24 $J += ($self->tzoffset/(24*3600)) if $self->[c_islocal];
445              
446             # Now that we have the Julian day including fractions
447             # convert it to an integer Julian Day Number using nearest
448             # int (since the day changes at midday we convert all Julian
449             # dates to following midnight).
450 11         18 $J = int($J+0.5);
451              
452 12     12   5765 use integer;
  12         154  
  12         56  
453 11         27 my $d4 = ((($J + 31741 - ($J % 7)) % 146097) % 36524) % 1461;
454 11         19 my $L = $d4 / 1460;
455 11         18 my $d1 = (($d4 - $L) % 365) + $L;
456 11         46 return $d1 / 7 + 1;
457             }
458              
459             sub _is_leap_year {
460 6     6   12 my $year = shift;
461 6 100 100     61 return (($year %4 == 0) && !($year % 100 == 0)) || ($year % 400 == 0)
462             ? 1 : 0;
463             }
464              
465             sub is_leap_year {
466 2     2 0 5 my $time = shift;
467 2         5 my $year = $time->year;
468 2         8 return _is_leap_year($year);
469             }
470              
471             my @MON_LAST = qw(31 28 31 30 31 30 31 31 30 31 30 31);
472              
473             sub month_last_day {
474 2     2 0 5 my $time = shift;
475 2         6 my $year = $time->year;
476 2         6 my $_mon = $time->_mon;
477 2 100       14 return $MON_LAST[$_mon] + ($_mon == 1 ? _is_leap_year($year) : 0);
478             }
479              
480             my $trans_map_common = {
481              
482             'c' => sub {
483             my ( $format ) = @_;
484             if($LOCALE->{PM} && $LOCALE->{AM}){
485             $format =~ s/%c/%a %d %b %Y %I:%M:%S %p/;
486             }
487             else{
488             $format =~ s/%c/%a %d %b %Y %H:%M:%S/;
489             }
490             return $format;
491             },
492             'r' => sub {
493             my ( $format ) = @_;
494             if($LOCALE->{PM} && $LOCALE->{AM}){
495             $format =~ s/%r/%I:%M:%S %p/;
496             }
497             else{
498             $format =~ s/%r/%H:%M:%S/;
499             }
500             return $format;
501             },
502             'X' => sub {
503             my ( $format ) = @_;
504             if($LOCALE->{PM} && $LOCALE->{AM}){
505             $format =~ s/%X/%I:%M:%S %p/;
506             }
507             else{
508             $format =~ s/%X/%H:%M:%S/;
509             }
510             return $format;
511             },
512             };
513              
514             my $strftime_trans_map = {
515             %{$trans_map_common},
516              
517             'e' => sub {
518             my ( $format, $time ) = @_;
519             $format =~ s/%e/%d/ if $IS_WIN32;
520             return $format;
521             },
522             'D' => sub {
523             my ( $format, $time ) = @_;
524             $format =~ s/%D/%m\/%d\/%y/;
525             return $format;
526             },
527             'F' => sub {
528             my ( $format, $time ) = @_;
529             $format =~ s/%F/%Y-%m-%d/;
530             return $format;
531             },
532             'R' => sub {
533             my ( $format, $time ) = @_;
534             $format =~ s/%R/%H:%M/;
535             return $format;
536             },
537             's' => sub {
538             #%s not portable if time parts are from gmtime since %s will
539             #cause a call to native mktime (and thus uses local TZ)
540             my ( $format, $time ) = @_;
541             $format =~ s/%s/$time->[c_epoch]/;
542             return $format;
543             },
544             'T' => sub {
545             my ( $format, $time ) = @_;
546             $format =~ s/%T/%H:%M:%S/ if $IS_WIN32;
547             return $format;
548             },
549             'u' => sub {
550             my ( $format, $time ) = @_;
551             $format =~ s/%u/%w/ if $IS_WIN32;
552             return $format;
553             },
554             'V' => sub {
555             my ( $format, $time ) = @_;
556             my $week = sprintf( "%02d", $time->week() );
557             $format =~ s/%V/$week/ if $IS_WIN32;
558             return $format;
559             },
560             'x' => sub {
561             my ( $format, $time ) = @_;
562             $format =~ s/%x/%a %d %b %Y/;
563             return $format;
564             },
565             'z' => sub { #%[zZ] not portable if time parts are from gmtime
566             my ( $format, $time ) = @_;
567             $format =~ s/%z/+0000/ if not $time->[c_islocal];
568             return $format;
569             },
570             'Z' => sub {
571             my ( $format, $time ) = @_;
572             $format =~ s/%Z/UTC/ if not $time->[c_islocal];
573             return $format;
574             },
575             };
576              
577             sub strftime {
578 314     314 0 22381 my $time = shift;
579 314 100       605 my $format = @_ ? shift(@_) : '%a, %d %b %Y %H:%M:%S %Z';
580 314         514 $format = _translate_format($format, $strftime_trans_map, $time);
581              
582 314 100       722 return $format unless $format =~ /%/; #if translate removes everything
583              
584 292         506 return _strftime($format, $time->epoch, $time->[c_islocal]);
585             }
586              
587             my $strptime_trans_map = {
588             %{$trans_map_common},
589             };
590              
591             sub strptime {
592 74     74 0 1531 my $time = shift;
593 74         93 my $string = shift;
594 74 50       160 my $format = @_ ? shift(@_) : "%a, %d %b %Y %H:%M:%S %Z";
595 74 100       174 my $islocal = (ref($time) ? $time->[c_islocal] : 0);
596 74   66     145 my $locales = $LOCALE || &Time::Piece::_default_locale();
597 74         150 $format = _translate_format($format, $strptime_trans_map);
598 74         829 my @vals = _strptime($string, $format, $islocal, $locales);
599             # warn(sprintf("got vals: %d-%d-%d %d:%d:%d\n", reverse(@vals[c_sec..c_year])));
600 74         234 return scalar $time->_mktime(\@vals, $islocal);
601             }
602              
603             sub day_list {
604 5 100 66 5 0 34 shift if ref($_[0]) && $_[0]->isa(__PACKAGE__); # strip first if called as a method
605 5         17 my @old = @DAY_LIST;
606 5 100       30 if (@_) {
607 3         9 @DAY_LIST = @_;
608 3         27 &Time::Piece::_default_locale();
609             }
610 5         14 return @old;
611             }
612              
613             sub mon_list {
614 4 100 66 4 0 640 shift if ref($_[0]) && $_[0]->isa(__PACKAGE__); # strip first if called as a method
615 4         14 my @old = @MON_LIST;
616 4 100       11 if (@_) {
617 2         8 @MON_LIST = @_;
618 2         5 &Time::Piece::_default_locale();
619             }
620 4         13 return @old;
621             }
622              
623             sub time_separator {
624 5 100 66 5 0 30 shift if ref($_[0]) && $_[0]->isa(__PACKAGE__);
625 5         8 my $old = $TIME_SEP;
626 5 100       12 if (@_) {
627 2         4 $TIME_SEP = $_[0];
628             }
629 5         12 return $old;
630             }
631              
632             sub date_separator {
633 5 100 66 5 0 32 shift if ref($_[0]) && $_[0]->isa(__PACKAGE__);
634 5         10 my $old = $DATE_SEP;
635 5 100       12 if (@_) {
636 2         3 $DATE_SEP = $_[0];
637             }
638 5         13 return $old;
639             }
640              
641 12         168 use overload '""' => \&cdate,
642             'cmp' => \&str_compare,
643 12     12   16414 'fallback' => undef;
  12         29  
644              
645             sub cdate {
646 26     26 0 2025 my $time = shift;
647 26 100       93 if ($time->[c_islocal]) {
648 4         9 return scalar(CORE::localtime($time->epoch));
649             }
650             else {
651 22         48 return scalar(CORE::gmtime($time->epoch));
652             }
653             }
654              
655             sub str_compare {
656 5     5 0 539 my ($lhs, $rhs, $reverse) = @_;
657              
658 5 50 33     37 if (blessed($rhs) && $rhs->isa('Time::Piece')) {
659 5         10 $rhs = "$rhs";
660             }
661 5 50       15 return $reverse ? $rhs cmp $lhs->cdate : $lhs->cdate cmp $rhs;
662             }
663              
664             use overload
665 12         83 '-' => \&subtract,
666 12     12   2634 '+' => \&add;
  12         34  
667              
668             sub subtract {
669 4     4 0 1354 my $time = shift;
670 4         8 my $rhs = shift;
671              
672 4 100       14 if (shift)
673             {
674             # SWAPED is set (so someone tried an expression like NOTDATE - DATE).
675             # Imitate Perl's standard behavior and return the result as if the
676             # string $time resolves to was subtracted from NOTDATE. This way,
677             # classes which override this one and which have a stringify function
678             # that resolves to something that looks more like a number don't need
679             # to override this function.
680 1         4 return $rhs - "$time";
681             }
682              
683 3 100 100     39 if (blessed($rhs) && $rhs->isa('Time::Piece')) {
684 1         5 return Time::Seconds->new($time->epoch - $rhs->epoch);
685             }
686             else {
687             # rhs is seconds.
688 2         8 return $time->_mktime(($time->epoch - $rhs), $time->[c_islocal]);
689             }
690             }
691              
692             sub add {
693 3     3 0 881 my $time = shift;
694 3         6 my $rhs = shift;
695              
696 3         14 return $time->_mktime(($time->epoch + $rhs), $time->[c_islocal]);
697             }
698              
699             use overload
700 12     12   2359 '<=>' => \&compare;
  12         32  
  12         1733  
701              
702             sub get_epochs {
703 22     22 0 37 my ($lhs, $rhs, $reverse) = @_;
704 22 100 66     127 unless (blessed($rhs) && $rhs->isa('Time::Piece')) {
705 3         6 $rhs = $lhs->new($rhs);
706             }
707 22 50       41 if ($reverse) {
708 0         0 return $rhs->epoch, $lhs->epoch;
709             }
710 22         43 return $lhs->epoch, $rhs->epoch;
711             }
712              
713             sub compare {
714 22     22 0 1982 my ($lhs, $rhs) = get_epochs(@_);
715 22         164 return $lhs <=> $rhs;
716             }
717              
718             sub add_months {
719 35     35 0 3142 my ($time, $num_months) = @_;
720              
721 35 100       237 croak("add_months requires a number of months") unless defined($num_months);
722              
723 34         56 my $final_month = $time->_mon + $num_months;
724 34         39 my $num_years = 0;
725 34 100 100     104 if ($final_month > 11 || $final_month < 0) {
726             # these two ops required because we have no POSIX::floor and don't
727             # want to load POSIX.pm
728 6 100 100     22 if ($final_month < 0 && $final_month % 12 == 0) {
729 2         5 $num_years = int($final_month / 12) + 1;
730             }
731             else {
732 4         11 $num_years = int($final_month / 12);
733             }
734 6 100       12 $num_years-- if ($final_month < 0);
735              
736 6         8 $final_month = $final_month % 12;
737             }
738              
739 34         72 my @vals = _mini_mktime($time->sec, $time->min, $time->hour,
740             $time->mday, $final_month, $time->year - 1900 + $num_years);
741             # warn(sprintf("got %d vals: %d-%d-%d %d:%d:%d [%d]\n", scalar(@vals), reverse(@vals), $time->[c_islocal]));
742 34         79 return scalar $time->_mktime(\@vals, $time->[c_islocal]);
743             }
744              
745             sub add_years {
746 1     1 0 4 my ($time, $years) = @_;
747 1         4 $time->add_months($years * 12);
748             }
749              
750             sub truncate {
751 23     23 0 1105 my ($time, %params) = @_;
752 23 100       47 return $time unless exists $params{to};
753             #if ($params{to} eq 'week') { return $time->_truncate_week; }
754 21         64 my %units = (
755             second => 0,
756             minute => 1,
757             hour => 2,
758             day => 3,
759             month => 4,
760             quarter => 5,
761             year => 5
762             );
763 21         31 my $to = $units{$params{to}};
764 21 100       252 croak "Invalid value of 'to' parameter: $params{to}" unless defined $to;
765 19         20 my $start_month = 0;
766 19 100       33 if ($params{to} eq 'quarter') {
767 13         33 $start_month = int( $time->_mon / 3 ) * 3;
768             }
769 19         31 my @down_to = (0, 0, 0, 1, $start_month, $time->year);
770 19         70 return $time->_mktime([@down_to[0..$to-1], @$time[$to..c_isdst]],
771             $time->[c_islocal]);
772             }
773              
774             #Given a format and a translate map, replace format flags in
775             #accordance with the logic from the translation map subroutines
776             sub _translate_format {
777 388     388   611 my ( $format, $trans_map, $time ) = @_;
778              
779 388         678 $format =~ s/%%/\e\e/g; #escape the escape
780 388         506 my $lexer = _build_format_lexer($format);
781              
782 388         636 while(my $flag = $lexer->() ){
783 1852 100       3698 next unless exists $trans_map->{$flag};
784 345         600 $format = $trans_map->{$flag}($format, $time);
785             }
786              
787 388         584 $format =~ s/\e\e/%%/g;
788 388         1120 return $format;
789             }
790              
791             sub _build_format_lexer {
792 388     388   496 my $format = shift();
793              
794             #Higher Order Perl p.359 (or thereabouts)
795             return sub {
796             LABEL: {
797 2240 100   2240   2496 return $1 if $format =~ m/\G%([a-zA-Z])/gc; #return single char flags
  4105         9420  
798              
799 2253 100       3754 redo LABEL if $format =~ m/\G(.)/gc;
800 388         676 return; #return at empty string
801             }
802 388         1238 };
803             }
804              
805             sub use_locale {
806             #get locale month/day names from posix strftime (from Piece.xs)
807 6     6 0 2151 my $locales = _get_localization();
808              
809 6   50     23 $locales->{PM} ||= '';
810 6   50     20 $locales->{AM} ||= '';
811              
812 6         27 $locales->{pm} = lc $locales->{PM};
813 6         16 $locales->{am} = lc $locales->{AM};
814             #should probably figure out how to get a
815             #region specific format for %c someday
816 6         12 $locales->{c_fmt} = '';
817              
818             #Set globals. If anything is
819             #weird just use original
820 6 50       10 if( @{$locales->{weekday}} < 7 ){
  6         19  
821 0         0 @{$locales->{weekday}} = @FULLDAY_LIST;
  0         0  
822             }
823             else {
824 6         12 @FULLDAY_LIST = @{$locales->{weekday}};
  6         70  
825             }
826              
827 6 50       11 if( @{$locales->{wday}} < 7 ){
  6         20  
828 0         0 @{$locales->{wday}} = @DAY_LIST;
  0         0  
829             }
830             else {
831 6         8 @DAY_LIST = @{$locales->{wday}};
  6         26  
832             }
833              
834 6 50       11 if( @{$locales->{month}} < 12 ){
  6         18  
835 0         0 @{$locales->{month}} = @FULLMON_LIST;
  0         0  
836             }else {
837 6         8 @FULLMON_LIST = @{$locales->{month}};
  6         37  
838             }
839              
840 6 50       13 if( @{$locales->{mon}} < 12 ){
  6         20  
841 0         0 @{$locales->{mon}} = @MON_LIST;
  0         0  
842             }
843             else{
844 6         9 @MON_LIST= @{$locales->{mon}};
  6         31  
845             }
846              
847 6         44 $LOCALE = $locales;
848             }
849              
850             #$Time::Piece::LOCALE is used by strptime and thus needs to be
851             #in sync with what ever users change to via day_list() and mon_list().
852             #Should probably deprecate this use of gloabl state, but oh well...
853             sub _default_locale {
854 9     9   50 my $locales = {};
855              
856 9         23 @{ $locales->{weekday} } = @FULLDAY_LIST;
  9         37  
857 9         18 @{ $locales->{wday} } = @DAY_LIST;
  9         42  
858 9         17 @{ $locales->{month} } = @FULLMON_LIST;
  9         38  
859 9         17 @{ $locales->{mon} } = @MON_LIST;
  9         38  
860 9         21 $locales->{alt_month} = $locales->{month};
861              
862 9         17 $locales->{PM} = 'PM';
863 9         20 $locales->{AM} = 'AM';
864 9         24 $locales->{pm} = 'pm';
865 9         15 $locales->{am} = 'am';
866 9         19 $locales->{c_fmt} = '';
867              
868 9         32 $LOCALE = $locales;
869             }
870              
871             sub _locale {
872 10     10   42 return $LOCALE;
873             }
874              
875              
876             1;
877             __END__