File Coverage

blib/lib/Time/Piece.pm
Criterion Covered Total %
statement 333 357 93.2
branch 125 150 83.3
condition 39 54 72.2
subroutine 77 77 100.0
pod 1 47 2.1
total 575 685 83.9


line stmt bran cond sub pod time code
1             package Time::Piece;
2              
3 13     13   708005 use strict;
  13         117  
  13         419  
4              
5 13     13   71 use XSLoader ();
  13         29  
  13         214  
6 13     13   5630 use Time::Seconds;
  13         32  
  13         813  
7 13     13   91 use Carp;
  13         26  
  13         732  
8 13     13   6668 use Time::Local;
  13         29344  
  13         771  
9 13     13   96 use Scalar::Util qw/ blessed /;
  13         23  
  13         792  
10              
11 13     13   76 use Exporter ();
  13         28  
  13         2443  
12              
13             our @EXPORT = qw(
14             localtime
15             gmtime
16             );
17              
18             our %EXPORT_TAGS = (
19             ':override' => 'internal',
20             );
21              
22             our $VERSION = '1.3401';
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 13         12320 '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 13     13   106 };
  13         22  
50              
51             sub localtime {
52 27 100   27 0 1363 unshift @_, __PACKAGE__ unless eval { $_[0]->isa('Time::Piece') };
  27         158  
53 27         62 my $class = shift;
54 27         44 my $time = shift;
55 27 100       64 $time = time if (!defined $time);
56 27         80 $class->_mktime($time, 1);
57             }
58              
59             sub gmtime {
60 33 100   33 0 52048 unshift @_, __PACKAGE__ unless eval { $_[0]->isa('Time::Piece') };
  33         255  
61 33         80 my $class = shift;
62 33         54 my $time = shift;
63 33 100       76 $time = time if (!defined $time);
64 33         89 $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 450 100   450   1169 return 1 if ref($_[1]) eq 'ARRAY';
75 329 100 100     890 return 1 if blessed($_[1]) && $_[1]->isa('Time::Piece');
76              
77 321         681 return 0;
78             }
79              
80              
81             sub new {
82 13     13 0 1851 my $class = shift;
83 13         38 my ($time) = @_;
84              
85 13         22 my $self;
86              
87 13 100 66     34 if ($class->_is_time_struct($time)) {
    100          
    100          
88 4 100       30 $self = $time->[c_islocal] ? $class->localtime($time) : $class->gmtime($time);
89             }
90             elsif (defined($time)) {
91 6         41 $self = $class->localtime($time);
92             }
93             elsif (ref($class) && $class->isa(__PACKAGE__)) {
94 1         5 $self = $class->_mktime($class->epoch, $class->[c_islocal]);
95             }
96             else {
97 2         8 $self = $class->localtime();
98             }
99              
100 13   66     76 return bless $self, ref($class) || $class;
101             }
102              
103             sub parse {
104 4     4 0 100 my $proto = shift;
105 4   100     18 my $class = ref($proto) || $proto;
106 4         9 my @components;
107              
108 4         248 warnings::warnif("deprecated",
109             "parse() is deprecated, use strptime() instead.");
110              
111 4 100       15 if (@_ > 1) {
112 2         5 @components = @_;
113             }
114             else {
115 2         37 @components = shift =~ /(\d+)$DATE_SEP(\d+)$DATE_SEP(\d+)(?:(?:T|\s+)(\d+)$TIME_SEP(\d+)(?:$TIME_SEP(\d+)))/;
116 2         9 @components = reverse(@components[0..5]);
117             }
118 4         16 return $class->new( timelocal(@components ));
119             }
120              
121             sub _mktime {
122 437     437   1077 my ($class, $time, $islocal) = @_;
123              
124 437   66     1577 $class = blessed($class) || $class;
125              
126 437 100       1122 if ($class->_is_time_struct($time)) {
127 125         266 my @new_time = @$time;
128 125         314 my @tm_parts = (@new_time[c_sec .. c_mon], $new_time[c_year]+1900);
129              
130 125 100       460 $new_time[c_epoch] = $islocal ? timelocal(@tm_parts) : timegm(@tm_parts);
131              
132 125 50       5170 return wantarray ? @new_time : bless [@new_time[0..9], $islocal], $class;
133             }
134 312         6967 _tzset();
135 312 100       2853 my @time = $islocal ?
136             CORE::localtime($time)
137             :
138             CORE::gmtime($time);
139 312 50       2465 wantarray ? @time : bless [@time, $time, $islocal], $class;
140             }
141              
142             my %_special_exports = (
143 14     14   1796 localtime => sub { my $c = $_[0]; sub { $c->localtime(@_) } },
144 19     19   3064 gmtime => sub { my $c = $_[0]; sub { $c->gmtime(@_) } },
145             );
146              
147             sub export {
148 14     14 0 49 my ($class, $to, @methods) = @_;
149 14         35 for my $method (@methods) {
150 28 50       70 if (exists $_special_exports{$method}) {
151 13     13   104 no strict 'refs';
  13         32  
  13         463  
152 13     13   80 no warnings 'redefine';
  13         36  
  13         23636  
153 28         63 *{$to . "::$method"} = $_special_exports{$method}->($class);
  28         8062  
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 14     14   309 my $class = shift;
163 14         23 my %params;
164 14         79 map($params{$_}++,@_,@EXPORT);
165 14 50       55 if (delete $params{':override'}) {
166 0         0 $class->export('CORE::GLOBAL', keys %params);
167             }
168             else {
169 14         67 $class->export(scalar caller, keys %params);
170             }
171             }
172              
173             ## Methods ##
174              
175             sub sec {
176 63     63 0 93 my $time = shift;
177 63         185 $time->[c_sec];
178             }
179              
180             *second = \&sec;
181              
182             sub min {
183 63     63 0 86 my $time = shift;
184 63         122 $time->[c_min];
185             }
186              
187             *minute = \&min;
188              
189             sub hour {
190 63     63 0 103 my $time = shift;
191 63         125 $time->[c_hour];
192             }
193              
194             sub mday {
195 71     71 0 113 my $time = shift;
196 71         171 $time->[c_mday];
197             }
198              
199             *day_of_month = \&mday;
200              
201             sub mon {
202 57     57 0 99 my $time = shift;
203 57         247 $time->[c_mon] + 1;
204             }
205              
206             sub _mon {
207 54     54   80 my $time = shift;
208 54         126 $time->[c_mon];
209             }
210              
211             sub month {
212 5     5 0 18 my $time = shift;
213 5 100       16 if (@_) {
    50          
214 1         5 return $_[$time->[c_mon]];
215             }
216             elsif (@MON_LIST) {
217 4         57 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       7 if (@_) {
    50          
229 0         0 return $_[$time->[c_mon]];
230             }
231             elsif (@FULLMON_LIST) {
232 1         6 return $FULLMON_LIST[$time->[c_mon]];
233             }
234             else {
235 0         0 return $time->strftime('%B');
236             }
237             }
238              
239             sub year {
240 366     366 0 1743 my $time = shift;
241 366         1734 $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 7 my $time = shift;
251 2         7 my $res = $time->[c_year] % 100;
252 2 100       13 return $res > 9 ? $res : "0$res";
253             }
254              
255             sub wday {
256 3     3 0 7 my $time = shift;
257 3         16 $time->[c_wday] + 1;
258             }
259              
260             sub _wday {
261 10     10   23 my $time = shift;
262 10         35 $time->[c_wday];
263             }
264              
265             *day_of_week = \&_wday;
266              
267             sub wdayname {
268 5     5 0 14 my $time = shift;
269 5 100       20 if (@_) {
    50          
270 1         5 return $_[$time->[c_wday]];
271             }
272             elsif (@DAY_LIST) {
273 4         19 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 5 my $time = shift;
284 2 50       8 if (@_) {
    50          
285 0         0 return $_[$time->[c_wday]];
286             }
287             elsif (@FULLDAY_LIST) {
288 2         8 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 11 my $time = shift;
297 5         18 $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 19 my $time = shift;
312              
313 3 50       10 return Time::Seconds->new(0) unless $time->[c_islocal];
314              
315 3         8 my $epoch = $time->epoch;
316              
317             my $j = sub {
318              
319 6     6   16 my ($s,$n,$h,$d,$m,$y) = @_; $m += 1; $y += 1900;
  6         10  
  6         9  
320              
321 6         31 $time->_jd($y, $m, $d, $h, $n, $s);
322              
323 3         19 };
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         22 my $delta = 24 * ($j->(_crt_localtime($epoch)) - $j->(_crt_gmtime($epoch)));
330              
331             # Return value in seconds rounded to nearest minute.
332 3 50       38 return Time::Seconds->new( int($delta * 60 + ($delta >= 0 ? 0.5 : -0.5)) * 60 );
333             }
334              
335             sub epoch {
336 707     707 1 1728 my $time = shift;
337 707 50       1460 if (defined($time->[c_epoch])) {
338 707         5738 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 27 my $time = shift;
352 13 100       36 my $sep = @_ ? shift(@_) : $TIME_SEP;
353 13         109 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 28 my $time = shift;
360 15 100       51 my $sep = @_ ? shift(@_) : $DATE_SEP;
361 15         52 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 7 my $time = shift;
368 3 100       14 my $sep = @_ ? shift(@_) : $DATE_SEP;
369 3         11 sprintf("%02d$sep%02d$sep%d", $time->mon, $time->[c_mday], $time->year);
370             }
371              
372             sub dmy {
373 3     3 0 8 my $time = shift;
374 3 100       12 my $sep = @_ ? shift(@_) : $DATE_SEP;
375 3         12 sprintf("%02d$sep%02d$sep%d", $time->[c_mday], $time->mon, $time->year);
376             }
377              
378             sub datetime {
379 7     7 0 1339 my $time = shift;
380 7         31 my %seps = (date => $DATE_SEP, T => 'T', time => $TIME_SEP, @_);
381 7         25 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 518 my $time = shift;
390             # Correct for localtime
391 25 100       96 $time = $time->gmtime( $time->epoch ) if $time->[c_islocal];
392              
393             # Calculate the Julian day itself
394 25         60 my $jd = $time->_jd( $time->year, $time->mon, $time->mday,
395             $time->hour, $time->min, $time->sec);
396              
397 25         168 return $jd;
398             }
399              
400             # MJD is defined as JD - 2400000.5 days
401             sub mjd {
402 13     13 0 57 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   47 my $self = shift;
414 31         56 my ($y, $m, $d, $h, $n, $s) = @_;
415              
416             # Adjust input parameters according to the month
417 31 100       67 $y = ( $m > 2 ? $y : $y - 1);
418 31 100       63 $m = ( $m > 2 ? $m - 3 : $m + 9);
419              
420             # Calculate the Julian Date (assuming Julian calendar)
421 31         104 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         66 my $G = 38 - int( 0.75 * int(49+($y/100)));
429              
430             # Calculate the actual Julian Date
431 31         53 my $JD = $J + $G;
432              
433             # Modify to include hours/mins/secs in floating portion.
434 31         93 return $JD + ($h + ($n + $s / 60) / 60) / 24;
435             }
436              
437             sub week {
438 11     11 0 19 my $self = shift;
439              
440 11         33 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       27 $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 13     13   7032 use integer;
  13         194  
  13         69  
453 11         31 my $d4 = ((($J + 31741 - ($J % 7)) % 146097) % 36524) % 1461;
454 11         20 my $L = $d4 / 1460;
455 11         18 my $d1 = (($d4 - $L) % 365) + $L;
456 11         55 return $d1 / 7 + 1;
457             }
458              
459             sub _is_leap_year {
460 6     6   12 my $year = shift;
461 6 100 100     63 return (($year %4 == 0) && !($year % 100 == 0)) || ($year % 400 == 0)
462             ? 1 : 0;
463             }
464              
465             sub is_leap_year {
466 2     2 0 7 my $time = shift;
467 2         9 my $year = $time->year;
468 2         10 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         7 my $year = $time->year;
476 2         10 my $_mon = $time->_mon;
477 2 100       17 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 284     284 0 23232 my $time = shift;
579 284 100       635 my $format = @_ ? shift(@_) : '%a, %d %b %Y %H:%M:%S %Z';
580 284         577 $format = _translate_format($format, $strftime_trans_map, $time);
581              
582 284 100       773 return $format unless $format =~ /%/; #if translate removes everything
583              
584 262         549 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 68     68 0 2213 my $time = shift;
593 68         108 my $string = shift;
594 68 50       161 my $format = @_ ? shift(@_) : "%a, %d %b %Y %H:%M:%S %Z";
595 68 100       173 my $islocal = (ref($time) ? $time->[c_islocal] : 0);
596 68   66     164 my $locales = $LOCALE || &Time::Piece::_default_locale();
597 68         122 $format = _translate_format($format, $strptime_trans_map);
598 68         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 68         224 return scalar $time->_mktime(\@vals, $islocal);
601             }
602              
603             sub day_list {
604 5 100 66 5 0 37 shift if ref($_[0]) && $_[0]->isa(__PACKAGE__); # strip first if called as a method
605 5         18 my @old = @DAY_LIST;
606 5 100       29 if (@_) {
607 3         8 @DAY_LIST = @_;
608 3         54 &Time::Piece::_default_locale();
609             }
610 5         16 return @old;
611             }
612              
613             sub mon_list {
614 4 100 66 4 0 656 shift if ref($_[0]) && $_[0]->isa(__PACKAGE__); # strip first if called as a method
615 4         15 my @old = @MON_LIST;
616 4 100       10 if (@_) {
617 2         9 @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 28 shift if ref($_[0]) && $_[0]->isa(__PACKAGE__);
625 5         9 my $old = $TIME_SEP;
626 5 100       10 if (@_) {
627 2         4 $TIME_SEP = $_[0];
628             }
629 5         31 return $old;
630             }
631              
632             sub date_separator {
633 5 100 66 5 0 33 shift if ref($_[0]) && $_[0]->isa(__PACKAGE__);
634 5         9 my $old = $DATE_SEP;
635 5 100       12 if (@_) {
636 2         3 $DATE_SEP = $_[0];
637             }
638 5         19 return $old;
639             }
640              
641 13         156 use overload '""' => \&cdate,
642             'cmp' => \&str_compare,
643 13     13   21051 'fallback' => undef;
  13         36  
644              
645             sub cdate {
646 26     26 0 2367 my $time = shift;
647 26 100       96 if ($time->[c_islocal]) {
648 4         12 return scalar(CORE::localtime($time->epoch));
649             }
650             else {
651 22         53 return scalar(CORE::gmtime($time->epoch));
652             }
653             }
654              
655             sub str_compare {
656 5     5 0 687 my ($lhs, $rhs, $reverse) = @_;
657              
658 5 50 33     90 if (blessed($rhs) && $rhs->isa('Time::Piece')) {
659 5         16 $rhs = "$rhs";
660             }
661 5 50       19 return $reverse ? $rhs cmp $lhs->cdate : $lhs->cdate cmp $rhs;
662             }
663              
664             use overload
665 13         120 '-' => \&subtract,
666 13     13   3419 '+' => \&add;
  13         27  
667              
668             sub subtract {
669 204     204 0 98716 my $time = shift;
670 204         325 my $rhs = shift;
671              
672 204 100       466 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         3 return $rhs - "$time";
681             }
682              
683 203 100 100     710 if (blessed($rhs) && $rhs->isa('Time::Piece')) {
684 1         4 return Time::Seconds->new($time->epoch - $rhs->epoch);
685             }
686             else {
687             # rhs is seconds.
688 202         430 return $time->_mktime(($time->epoch - $rhs), $time->[c_islocal]);
689             }
690             }
691              
692             sub add {
693 53     53 0 25364 my $time = shift;
694 53         81 my $rhs = shift;
695              
696 53         128 return $time->_mktime(($time->epoch + $rhs), $time->[c_islocal]);
697             }
698              
699             use overload
700 13     13   3339 '<=>' => \&compare;
  13         38  
  13         78  
701              
702             sub get_epochs {
703 22     22 0 52 my ($lhs, $rhs, $reverse) = @_;
704 22 100 66     146 unless (blessed($rhs) && $rhs->isa('Time::Piece')) {
705 3         8 $rhs = $lhs->new($rhs);
706             }
707 22 50       51 if ($reverse) {
708 0         0 return $rhs->epoch, $lhs->epoch;
709             }
710 22         49 return $lhs->epoch, $rhs->epoch;
711             }
712              
713             sub compare {
714 22     22 0 2385 my ($lhs, $rhs) = get_epochs(@_);
715 22         203 return $lhs <=> $rhs;
716             }
717              
718             sub add_months {
719 35     35 0 3790 my ($time, $num_months) = @_;
720              
721 35 100       239 croak("add_months requires a number of months") unless defined($num_months);
722              
723 34         67 my $final_month = $time->_mon + $num_months;
724 34         51 my $num_years = 0;
725 34 100 100     126 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     28 if ($final_month < 0 && $final_month % 12 == 0) {
729 2         7 $num_years = int($final_month / 12) + 1;
730             }
731             else {
732 4         11 $num_years = int($final_month / 12);
733             }
734 6 100       15 $num_years-- if ($final_month < 0);
735              
736 6         9 $final_month = $final_month % 12;
737             }
738              
739 34         71 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         97 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 1354 my ($time, %params) = @_;
752 23 100       65 return $time unless exists $params{to};
753             #if ($params{to} eq 'week') { return $time->_truncate_week; }
754 21         73 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         34 my $to = $units{$params{to}};
764 21 100       285 croak "Invalid value of 'to' parameter: $params{to}" unless defined $to;
765 19         26 my $start_month = 0;
766 19 100       38 if ($params{to} eq 'quarter') {
767 13         24 $start_month = int( $time->_mon / 3 ) * 3;
768             }
769 19         36 my @down_to = (0, 0, 0, 1, $start_month, $time->year);
770 19         75 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 352     352   715 my ( $format, $trans_map, $time ) = @_;
778              
779 352         746 $format =~ s/%%/\e\e/g; #escape the escape
780 352         591 my $lexer = _build_format_lexer($format);
781              
782 352         680 while(my $flag = $lexer->() ){
783 1564 100       3591 next unless exists $trans_map->{$flag};
784 315         616 $format = $trans_map->{$flag}($format, $time);
785             }
786              
787 352         675 $format =~ s/\e\e/%%/g;
788 352         1181 return $format;
789             }
790              
791             sub _build_format_lexer {
792 352     352   501 my $format = shift();
793              
794             #Higher Order Perl p.359 (or thereabouts)
795             return sub {
796             LABEL: {
797 1916 100   1916   2320 return $1 if $format =~ m/\G%([a-zA-Z])/gc; #return single char flags
  3421         9385  
798              
799 1857 100       3644 redo LABEL if $format =~ m/\G(.)/gc;
800 352         745 return; #return at empty string
801             }
802 352         1470 };
803             }
804              
805             sub use_locale {
806             #get locale month/day names from posix strftime (from Piece.xs)
807 1     1 0 29 my $locales = _get_localization();
808              
809             #If AM and PM are the same, set both to ''
810 1 50 33     11 if ( !$locales->{PM}
      33        
811             || !$locales->{AM}
812             || ( $locales->{PM} eq $locales->{AM} ) )
813             {
814 0         0 $locales->{PM} = '';
815 0         0 $locales->{AM} = '';
816             }
817              
818 1         6 $locales->{pm} = lc $locales->{PM};
819 1         4 $locales->{am} = lc $locales->{AM};
820             #should probably figure out how to get a
821             #region specific format for %c someday
822 1         3 $locales->{c_fmt} = '';
823              
824             #Set globals. If anything is
825             #weird just use original
826 1 50       1 if( @{$locales->{weekday}} < 7 ){
  1         4  
827 0         0 @{$locales->{weekday}} = @FULLDAY_LIST;
  0         0  
828             }
829             else {
830 1         2 @FULLDAY_LIST = @{$locales->{weekday}};
  1         5  
831             }
832              
833 1 50       2 if( @{$locales->{wday}} < 7 ){
  1         3  
834 0         0 @{$locales->{wday}} = @DAY_LIST;
  0         0  
835             }
836             else {
837 1         2 @DAY_LIST = @{$locales->{wday}};
  1         4  
838             }
839              
840 1 50       2 if( @{$locales->{month}} < 12 ){
  1         3  
841 0         0 @{$locales->{month}} = @FULLMON_LIST;
  0         0  
842             }else {
843 1         2 @FULLMON_LIST = @{$locales->{month}};
  1         4  
844             }
845              
846 1 50       1 if( @{$locales->{mon}} < 12 ){
  1         5  
847 0         0 @{$locales->{mon}} = @MON_LIST;
  0         0  
848             }
849             else{
850 1         2 @MON_LIST= @{$locales->{mon}};
  1         17  
851             }
852              
853 1         8 $LOCALE = $locales;
854             }
855              
856             #$Time::Piece::LOCALE is used by strptime and thus needs to be
857             #in sync with what ever users change to via day_list() and mon_list().
858             #Should probably deprecate this use of gloabl state, but oh well...
859             sub _default_locale {
860 9     9   33 my $locales = {};
861              
862 9         20 @{ $locales->{weekday} } = @FULLDAY_LIST;
  9         40  
863 9         18 @{ $locales->{wday} } = @DAY_LIST;
  9         29  
864 9         15 @{ $locales->{month} } = @FULLMON_LIST;
  9         36  
865 9         20 @{ $locales->{mon} } = @MON_LIST;
  9         35  
866 9         22 $locales->{alt_month} = $locales->{month};
867              
868 9         21 $locales->{PM} = 'PM';
869 9         21 $locales->{AM} = 'AM';
870 9         28 $locales->{pm} = 'pm';
871 9         17 $locales->{am} = 'am';
872 9         20 $locales->{c_fmt} = '';
873              
874 9         35 $LOCALE = $locales;
875             }
876              
877             sub _locale {
878 10     10   42 return $LOCALE;
879             }
880              
881              
882             1;
883             __END__