File Coverage

blib/lib/DateTimeX/Lite.pm
Criterion Covered Total %
statement 371 378 98.1
branch 129 142 90.8
condition 84 104 80.7
subroutine 106 109 97.2
pod 70 78 89.7
total 760 811 93.7


line stmt bran cond sub pod time code
1             package DateTimeX::Lite;
2 56     56   1374635 use strict;
  56         147  
  56         8783  
3 56     56   326 use warnings;
  56         112  
  56         2478  
4 56     56   1544 use 5.008;
  56         261  
  56         5968  
5             use constant +{
6 56 50       10077 INFINITY => (9 ** 9 ** 9),
7             NEG_INFINITY => -1 * (9 ** 9 ** 9),
8             SECONDS_PER_DAY => 86400,
9             MAX_NANOSECONDS => 1_000_000_000, # 1E9 = almost 32 bits
10             LOCALE_SKIP => $ENV{DATETIMEX_LITE_LOCALE_SKIP} ? 1 : 0,
11 56     56   351 };
  56         167  
12              
13 56     56   333 use constant NAN => INFINITY - INFINITY;
  56         132  
  56         2834  
14              
15 56     56   311 use Carp ();
  56         196  
  56         1112  
16 56     56   70592 use DateTimeX::Lite::Duration;
  56         192  
  56         2079  
17 56     56   40113 use DateTimeX::Lite::Infinite;
  56         267  
  56         3040  
18 56     56   354 use DateTimeX::Lite::TimeZone;
  56         112  
  56         2318  
19 56     56   49337 use DateTimeX::Lite::LeapSecond;
  56         178  
  56         2714  
20 56     56   502 use DateTimeX::Lite::Util;
  56         112  
  56         2332  
21 56     56   329 use Scalar::Util qw(blessed);
  56         110  
  56         8434  
22              
23             BEGIN {
24 56     56   144 if (LOCALE_SKIP) {
25             warn "We're skipping locale handling. You shouldn't be doing this unless you're generating locale data";
26             } else {
27 56         46995 require DateTimeX::Lite::Locale;
28             }
29             }
30             our $VERSION = '0.00004';
31              
32             BEGIN {
33 56     56   288 my @local_c_comp = qw(year month day hour minute second quarter);
34 56         160 foreach my $comp (@local_c_comp) {
35 56     56   568 no strict 'refs';
  56         142  
  56         4506  
36 392     128911   1215 *{$comp} = sub { $_[0]->{local_c}{$comp} };
  392         244223  
  128911         455829  
37             }
38             }
39              
40             our $DefaultLocale = 'en_US';
41              
42             sub import {
43 55     55   595 my $class = shift;
44 55         61776 foreach my $component (@_) {
45 32         4611 eval "require DateTimeX::Lite::$component";
46 32 50       72847 die "DateTimeX::Lite failed to load $component component: $@" if $@;
47             }
48             }
49              
50 8275     8275 1 10875 sub utc_rd_values { @{ $_[0] }{ 'utc_rd_days', 'utc_rd_secs', 'rd_nanosecs' } }
  8275         29146  
51 12     12 1 4310 sub local_rd_values { @{ $_[0] }{ 'local_rd_days', 'local_rd_secs', 'rd_nanosecs' } }
  12         93  
52              
53             # NOTE: no nanoseconds, no leap seconds
54 4859     4859 1 21521 sub utc_rd_as_seconds { ( $_[0]->{utc_rd_days} * SECONDS_PER_DAY ) + $_[0]->{utc_rd_secs} }
55              
56             # NOTE: no nanoseconds, no leap seconds
57 270     270 1 1038 sub local_rd_as_seconds { ( $_[0]->{local_rd_days} * SECONDS_PER_DAY ) + $_[0]->{local_rd_secs} }
58              
59             # RD 1 is JD 1,721,424.5 - a simple offset
60             sub jd
61             {
62 24     24 1 32 my $self = shift;
63              
64 24         46 my $jd = $self->{utc_rd_days} + 1_721_424.5;
65              
66 24         89 my $day_length = DateTimeX::Lite::LeapSecond::day_length( $self->{utc_rd_days} );
67              
68 24         258 return ( $jd +
69             ( $self->{utc_rd_secs} / $day_length ) +
70             ( $self->{rd_nanosecs} / $day_length / MAX_NANOSECONDS )
71             );
72             }
73              
74 10     10 1 53 sub mjd { $_[0]->jd - 2_400_000.5 }
75              
76 5649     5649 1 46270 sub clone { bless { %{ $_[0] } }, ref $_[0] }
  5649         65401  
77              
78             sub to_datetime {
79 0     0 1 0 eval {
80 0         0 require DateTime;
81             };
82 0 0       0 if ($@) {
83 0         0 Carp::croak("Could not load DateTime: $@");
84             }
85 0         0 return DateTime->from_object(object => $_[0]);
86             }
87              
88             sub set_time_zone {
89 6468     6468 1 38936 my ( $self, $tz ) = @_;
90              
91             # This is a bit of a hack but it works because time zone objects
92             # are singletons, and if it doesn't work all we lose is a little
93             # bit of speed.
94 6468 100       26177 return $self if $self->{tz} eq $tz;
95              
96 5847         20254 my $was_floating = $self->{tz}->is_floating;
97              
98 5847 100       17980 $self->{tz} = ref $tz ? $tz : DateTimeX::Lite::TimeZone->load( name => $tz );
99              
100 5847         13076 $self->_handle_offset_modifier( $self->second, 1 );
101              
102             # if it either was or now is floating (but not both)
103 5847 100 75     18642 if ( $self->{tz}->is_floating xor $was_floating )
    50          
104             {
105 5598         19895 $self->_calc_utc_rd;
106             }
107             elsif ( ! $was_floating )
108             {
109 249         625 $self->_calc_local_rd;
110             }
111              
112 5846         12186 return $self;
113             }
114              
115              
116             sub new {
117 27757     27757 1 732139 my ($class, %p) = @_;
118              
119             # give default values, first...
120             {
121 27757         39371 my %spec = (
  27757         433693  
122             day => { default => 1, range => [1, 31] },
123             month => { default => 1, range => [1, 12] },
124             year => {default => 1},
125             hour => {default => 0, range => [0, 23]},
126             minute => {default => 0, range => [0, 59]},
127             second => {default => 0, range => [0, 61]},
128             nanosecond => {default => 0, range => [0,undef]}
129             );
130              
131 27757         125488 while (my ($key, $spec) = each %spec) {
132 194251         306886 my $default = $spec->{default};
133 194251 100       436074 $p{$key} = $default unless defined $p{$key};
134              
135 194251 100       569679 if (my $range = $spec->{range}) {
136 166505         208197 my $v = $p{$key};
137 166505 100 66     1885627 if ( (defined $range->[0] && $v < $range->[0]) ||
      100        
      66        
138             (defined $range->[1] && $v > $range->[1]) ) {
139 20         3500 Carp::croak(qq|The '$key' parameter ("$p{$key}") to DateTimeX::Lite::new did not pass the range test|); # hmm, almost
140             }
141             }
142             }
143             }
144 27737         43647 my $day = $p{day};
145 27737         38739 my $month = $p{month};
146 27737         40043 my $year = $p{year};
147 27737         42167 my $hour = $p{hour};
148 27737         42440 my $minute = $p{minute};
149 27737         37572 my $second = $p{second};
150 27737         34538 my $nanosecond = $p{nanosecond};
151              
152 27737 100       90305 if ($day > DateTimeX::Lite::Util::month_length($year, $month)) {
153 2         335 Carp::croak("Invalid day of month (day = $day - month = $month - year = $year\n");
154             }
155              
156 27735         90004 my $self = bless {}, $class;
157              
158 27735   100     151029 my $locale = delete $p{language} || delete $p{locale};
159 27735 100       74288 $locale = $DefaultLocale unless defined $locale;
160 27735   100     81444 my $time_zone = $p{time_zone} || 'floating';
161              
162 27735         61165 $self->{offset_modifier} = 0;
163              
164             # XXX This only happens when we're generating the locales
165 27735         35760 if (! LOCALE_SKIP) {
166 27735 100       148523 $self->{locale} = blessed $locale ?
167             $locale : DateTimeX::Lite::Locale->load($locale);
168             }
169              
170 27735 100       137063 $self->{tz} = blessed $time_zone ?
171             $time_zone : DateTimeX::Lite::TimeZone->load(name => $time_zone);
172 27735         89221 $self->{local_rd_days} = DateTimeX::Lite::Util::ymd2rd($year, $month, $day);
173 27735         90880 $self->{local_rd_secs} = DateTimeX::Lite::Util::time_as_seconds($hour, $minute, $second);
174 27735         56535 $self->{offfset_modifier} = 0;
175 27735         41633 $self->{rd_nanosecs} = $nanosecond;
176 27735         83521 $self->{formatter} = $p{formatter};
177              
178 27735         106167 DateTimeX::Lite::Util::normalize_nanoseconds($self->{local_rd_secs}, $self->{rd_nanosecs});
179              
180 27735         53494 $self->{utc_year} = $year + 1;
181 27735         69783 $self->_calc_utc_rd;
182 27730         91609 $self->_handle_offset_modifier($second);
183 27730         67504 $self->_calc_local_rd;
184              
185 27730 100       81580 if ($second > 59) {
186 46 100 100     180 if ($self->{tz}->is_floating || $self->{utc_rd_secs} - SECONDS_PER_DAY + 1 < $second - 59) {
187 3         719 Carp::croak("Invalid second value ($second)\n");
188             }
189             }
190              
191 27727         163944 return $self;
192             }
193              
194             sub _calc_utc_rd {
195 37393     37393   52888 my $self = shift;
196 37393         60725 delete $self->{utc_c};
197              
198 37393         60147 my $time_zone = $self->{tz};
199 37393 100 100     123116 if ($time_zone->is_utc || $time_zone->is_floating) {
200 37207         78795 $self->{utc_rd_days} = $self->{local_rd_days};
201 37207         67590 $self->{utc_rd_secs} = $self->{local_rd_secs};
202             } else {
203 186         558 my $offset = $self->_offset_for_local_datetime;
204 178         416 $offset += $self->{offset_modifier};
205              
206 178         379 $self->{utc_rd_days} = $self->{local_rd_days};
207 178         413 $self->{utc_rd_secs} = $self->{local_rd_secs} - $offset;
208             }
209              
210             # We account for leap seconds in the new() method and nowhere else
211             # except date math.
212 37385         141301 DateTimeX::Lite::Util::normalize_tai_seconds( $self->{utc_rd_days}, $self->{utc_rd_secs} );
213             }
214              
215             sub _handle_offset_modifier
216             {
217 39830     39830   60877 my $self = shift;
218              
219 39830         76293 $self->{offset_modifier} = 0;
220              
221 39830 100       132081 return if $self->{tz}->is_floating;
222              
223 25178         40299 my $second = shift;
224 25178         30219 my $utc_is_valid = shift;
225              
226 25178         40831 my $utc_rd_days = $self->{utc_rd_days};
227              
228 25178 100       69704 my $offset = $utc_is_valid ? $self->offset : $self->_offset_for_local_datetime;
229              
230 25178 100 100     129822 if ( $offset >= 0
    100 100        
231             && $self->{local_rd_secs} >= $offset
232             )
233             {
234 24857 100 100     172456 if ( $second < 60 && $offset > 0 )
    100 66        
      66        
235             {
236 52         401 $self->{offset_modifier} =
237             DateTimeX::Lite::LeapSecond::day_length( $utc_rd_days - 1 ) - SECONDS_PER_DAY;
238              
239 52         559 $self->{local_rd_secs} += $self->{offset_modifier};
240             }
241             elsif ( $second == 60
242             &&
243             ( ( $self->{local_rd_secs} == $offset
244             && $offset > 0 )
245             ||
246             ( $offset == 0
247             && $self->{local_rd_secs} > 86399 ) )
248             )
249             {
250 39         143 my $mod = DateTimeX::Lite::LeapSecond::day_length( $utc_rd_days - 1 ) - SECONDS_PER_DAY;
251              
252 39 100       107 unless ( $mod == 0 )
253             {
254 38         64 $self->{utc_rd_secs} -= $mod;
255              
256 38         110 DateTimeX::Lite::Util::normalize_seconds($self);
257             }
258             }
259             }
260             elsif ( $offset < 0
261             && $self->{local_rd_secs} >= SECONDS_PER_DAY + $offset )
262             {
263 38 100 66     138 if ( $second < 60 )
    100          
264             {
265 31         170 $self->{offset_modifier} =
266             DateTimeX::Lite::LeapSecond::day_length( $utc_rd_days - 1 ) - SECONDS_PER_DAY;
267 31         117 $self->{local_rd_secs} += $self->{offset_modifier};
268             }
269             elsif ( $second == 60 && $self->{local_rd_secs} == SECONDS_PER_DAY + $offset )
270             {
271 5         28 my $mod = DateTimeX::Lite::LeapSecond::day_length( $utc_rd_days - 1 ) - SECONDS_PER_DAY;
272              
273 5 50       21 unless ( $mod == 0 )
274             {
275 5         11 $self->{utc_rd_secs} -= $mod;
276              
277 5         21 DateTimeX::Lite::Util::normalize_seconds($self);
278             }
279             }
280             }
281             }
282              
283             sub _calc_local_rd
284             {
285 27983     27983   41012 my $self = shift;
286              
287 27983         42655 delete $self->{local_c};
288              
289             # We must short circuit for UTC times or else we could end up with
290             # loops between DateTime.pm and DateTimeX::Lite::TimeZone
291 27983 100 100     109325 if ( $self->{tz}->is_utc || $self->{tz}->is_floating )
292             {
293 27667         72637 $self->{local_rd_days} = $self->{utc_rd_days};
294 27667         73764 $self->{local_rd_secs} = $self->{utc_rd_secs};
295             }
296             else
297 316         875 { my $offset = $self->offset;
298              
299 315         920 $self->{local_rd_days} = $self->{utc_rd_days};
300 315         8537 $self->{local_rd_secs} = $self->{utc_rd_secs} + $offset;
301              
302             # intentionally ignore leap seconds here
303 315         1101 DateTimeX::Lite::Util::normalize_tai_seconds( $self->{local_rd_days}, $self->{local_rd_secs} );
304              
305 315         645 $self->{local_rd_secs} += $self->{offset_modifier};
306             }
307              
308 27982         73077 $self->_calc_local_components;
309             }
310              
311             sub _calc_local_components
312             {
313 27978     27978   36905 my $self = shift;
314              
315 27978         89203 @{ $self->{local_c} }{ qw( year month day day_of_week
  27978         188749  
316             day_of_year quarter day_of_quarter) } =
317             DateTimeX::Lite::Util::rd2ymd( $self->{local_rd_days}, 1 );
318              
319 27978         131894 @{ $self->{local_c} }{ qw( hour minute second ) } =
  27978         153619  
320             DateTimeX::Lite::Util::seconds_as_components
321             ( $self->{local_rd_secs}, $self->{utc_rd_secs}, $self->{offset_modifier} );
322             }
323              
324             sub from_object {
325 6322     6322 1 22074 my ($class, %p) = @_;
326 6322         11910 my $object = delete $p{object};
327              
328 6322         15726 my ( $rd_days, $rd_secs, $rd_nanosecs ) = $object->utc_rd_values;
329              
330             # A kludge because until all calendars are updated to return all
331             # three values, $rd_nanosecs could be undef
332 6322   100     31191 $rd_nanosecs ||= 0;
333              
334             # This is a big hack to let _seconds_as_components operate naively
335             # on the given value. If the object _is_ on a leap second, we'll
336             # add that to the generated seconds value later.
337 6322         7947 my $leap_seconds = 0;
338 6322 100 100     44060 if ( $object->can('time_zone') && ! $object->time_zone->is_floating
      100        
      66        
339             && $rd_secs > 86399 && $rd_secs <= DateTimeX::Lite::LeapSecond::day_length($rd_days) )
340             {
341 6         11 $leap_seconds = $rd_secs - 86399;
342 6         13 $rd_secs -= $leap_seconds;
343             }
344              
345 6322         9438 my %args;
346 6322         19340 @args{ qw( year month day ) } = DateTimeX::Lite::Util::rd2ymd($rd_days);
347 6322         20597 @args{ qw( hour minute second ) } =
348             DateTimeX::Lite::Util::seconds_as_components($rd_secs);
349 6322         12296 $args{nanosecond} = $rd_nanosecs;
350              
351 6322         9601 $args{second} += $leap_seconds;
352              
353 6322         41091 my $new = $class->new( %p, %args, time_zone => 'UTC' );
354              
355 6322 100       31739 if ( $object->can('time_zone') )
356             {
357 6319         12992 $new->set_time_zone( $object->time_zone );
358             }
359             else
360             {
361 3         14 $new->set_time_zone( 'floating' );
362             }
363              
364 6321         33029 return $new;
365             }
366              
367              
368             sub last_day_of_month {
369 74     74 1 14663 my ($class, %p) = @_;
370 74 100 100     560 if ($p{month} > 12 || $p{month} < 1) {
371 2         283 Carp::croak(qq|The 'month' parameter ("$p{month}") to DateTimeX::Lite::last_day_of_month did not pass the 'is between 1 and 12' callback|);
372             }
373              
374 72         357 return $class->new(%p, day => DateTimeX::Lite::Util::month_length($p{year}, $p{month}));
375             }
376              
377 589     589 1 2579 sub offset { $_[0]->{tz}->offset_for_datetime( $_[0] ) }
378 25109     25109   100839 sub _offset_for_local_datetime { $_[0]->{tz}->offset_for_local_datetime( $_[0] ) }
379              
380              
381 17220     17220 1 47523 sub nanosecond { $_[0]->{rd_nanosecs} }
382 3     3 1 12 sub fractional_second { $_[0]->second + $_[0]->nanosecond / MAX_NANOSECONDS }
383              
384 8     8 1 666 sub millisecond { _round( $_[0]->{rd_nanosecs} / 1000000 ) }
385              
386 7     7 1 546 sub microsecond { _round( $_[0]->{rd_nanosecs} / 1000 ) }
387              
388             sub _round
389             {
390 15     15   29 my $val = shift;
391 15         123 my $int = int $val;
392              
393 15 100       121 return $val - $int >= 0.5 ? $int + 1 : $int;
394             }
395              
396             sub ce_year {
397 20     20 1 54 my $year = $_[0]->{local_c}{year};
398 20 100       120 return $year <= 0 ? $year - 1 : $year
399             }
400              
401 2     2 0 16 sub era_name { $_[0]->{locale}->era_wide->[ $_[0]->_era_index() ] }
402              
403 10     10 0 56 sub era_abbr { $_[0]->{locale}->era_abbreviated->[ $_[0]->_era_index() ] }
404              
405 13 100   13   107 sub _era_index { $_[0]->{local_c}{year} <= 0 ? 0 : 1 }
406              
407 4 100   4 0 13 sub christian_era { $_[0]->ce_year > 0 ? 'AD' : 'BC' }
408 4 100   4 0 13 sub secular_era { $_[0]->ce_year > 0 ? 'CE' : 'BCE' }
409              
410 2     2 0 11 sub year_with_era { (abs $_[0]->ce_year) . $_[0]->era_abbr }
411 2     2 0 12 sub year_with_christian_era { (abs $_[0]->ce_year) . $_[0]->christian_era }
412 2     2 0 9 sub year_with_secular_era { (abs $_[0]->ce_year) . $_[0]->secular_era }
413              
414              
415 6     6 1 42 sub month_name { $_[0]->{locale}->month_format_wide->[ $_[0]->month() - 1] }
416              
417 10     10 1 69 sub month_abbr { $_[0]->{locale}->month_format_abbreviated->[ $_[0]->month() - 1] }
418              
419 56     56 1 1741 sub weekday_of_month { use integer; ( ( $_[0]->day - 1 ) / 7 ) + 1 }
  56     4   128  
  56         555  
  4         29  
420              
421 2     2 1 17 sub quarter_name { $_[0]->{locale}->quarter_format_wide->[ $_[0]->quarter() - 1] }
422 2     2 1 16 sub quarter_abbr { $_[0]->{locale}->quarter_format_abbreviated->[ $_[0]->quarter() - 1] }
423              
424 11938     11938 1 62933 sub day_of_week { $_[0]->{local_c}{day_of_week} }
425              
426             sub local_day_of_week
427             {
428 4     4 1 7 my $self = shift;
429              
430 4         10 my $day = $self->day_of_week();
431              
432 4         22 my $local_first_day = $self->{locale}->first_day_of_week();
433              
434 4         12 my $d = ( ( 8 - $local_first_day ) + $day ) % 7;
435              
436 4 50       21 return $d == 0 ? 7 : $d;
437             }
438              
439              
440 5 100   5 1 53 sub hour_1 { $_[0]->{local_c}{hour} == 0 ? 24 : $_[0]->{local_c}{hour} }
441              
442 30 100   30 1 96 sub hour_12 { my $h = $_[0]->hour % 12; return $h ? $h : 12 }
  30         245  
443              
444 6     6 1 44 sub day_name { $_[0]->{locale}->day_format_wide->[ $_[0]->day_of_week() - 1 ] }
445              
446 8     8 1 56 sub day_abbr { $_[0]->{locale}->day_format_abbreviated->[ $_[0]->day_of_week() - 1] }
447              
448 8     8 1 52 sub day_of_quarter { $_[0]->{local_c}{day_of_quarter} }
449              
450 70     70 1 333 sub day_of_year { $_[0]->{local_c}{day_of_year} }
451              
452 23 100   23 1 113 sub am_or_pm { $_[0]->{locale}->am_pm_abbreviated->[ $_[0]->hour() < 12 ? 0 : 1 ] }
453              
454             # ISO says that the first week of a year is the first week containing
455             # a Thursday. Extending that says that the first week of the month is
456             # the first week containing a Thursday. ICU agrees.
457             sub week_of_month
458             {
459 4     4 1 11 my $self = shift;
460              
461 4         18 my $thu = $self->day + 4 - $self->day_of_week;
462 4         30 return int( ( $thu + 6 ) / 7 );
463             }
464              
465             sub week
466             {
467 37     37 1 141 my $self = shift;
468              
469 37 100       118 unless ( defined $self->{local_c}{week_year} )
470             {
471             # This algorithm was taken from Date::Calc's DateCalc.c file
472 31         251 my $jan_one_dow_m1 =
473             ( ( DateTimeX::Lite::Util::ymd2rd( $self->year, 1, 1 ) + 6 ) % 7 );
474              
475 31         88 $self->{local_c}{week_number} =
476             int( ( ( $self->day_of_year - 1 ) + $jan_one_dow_m1 ) / 7 );
477 31 100       90 $self->{local_c}{week_number}++ if $jan_one_dow_m1 < 4;
478              
479 31 100 100     199 if ( $self->{local_c}{week_number} == 0 )
    100          
480             {
481 4         10 $self->{local_c}{week_year} = $self->year - 1;
482 4         14 $self->{local_c}{week_number} =
483             $self->_weeks_in_year( $self->{local_c}{week_year} );
484             }
485             elsif ( $self->{local_c}{week_number} == 53 &&
486             $self->_weeks_in_year( $self->year ) == 52 )
487             {
488 5         10 $self->{local_c}{week_number} = 1;
489 5         11 $self->{local_c}{week_year} = $self->year + 1;
490             }
491             else
492             {
493 22         52 $self->{local_c}{week_year} = $self->year;
494             }
495             }
496              
497 37         55 return @{ $self->{local_c} }{ 'week_year', 'week_number' }
  37         161  
498             }
499              
500             # Also from DateCalc.c
501             sub _weeks_in_year
502             {
503 11     11   15 my $self = shift;
504 11         15 my $year = shift;
505              
506 11         32 my $dow = DateTimeX::Lite::Util::ymd2rd($year, 1, 1) % 7;
507            
508             # Tears starting with a Thursday and leap years starting with a Wednesday
509             # have 53 weeks.
510 11 100 66     84 return ( $dow == 4 || ( $dow == 3 && DateTimeX::Lite::Util::is_leap_year( $year ) ) )
511             ? 53
512             : 52;
513             }
514              
515 5     5 1 13 sub week_year { ($_[0]->week)[0] }
516 6     6 1 31 sub week_number { ($_[0]->week)[1] }
517              
518             sub ymd
519             {
520 730     730 1 5520 my ( $self, $sep ) = @_;
521 730 100       1741 $sep = '-' unless defined $sep;
522              
523 730         2140 return sprintf( "%0.4d%s%0.2d%s%0.2d",
524             $self->year, $sep,
525             $self->{local_c}{month}, $sep,
526             $self->{local_c}{day} );
527             }
528              
529             sub mdy
530             {
531 3     3 1 10 my ( $self, $sep ) = @_;
532 3 100       14 $sep = '-' unless defined $sep;
533              
534 3         17 return sprintf( "%0.2d%s%0.2d%s%0.4d",
535             $self->{local_c}{month}, $sep,
536             $self->{local_c}{day}, $sep,
537             $self->year );
538             }
539              
540             sub dmy
541             {
542 3     3 1 13 my ( $self, $sep ) = @_;
543 3 100       11 $sep = '-' unless defined $sep;
544              
545 3         16 return sprintf( "%0.2d%s%0.2d%s%0.4d",
546             $self->{local_c}{day}, $sep,
547             $self->{local_c}{month}, $sep,
548             $self->year );
549             }
550              
551             sub hms
552             {
553 178     178 1 300 my ( $self, $sep ) = @_;
554 178 100       432 $sep = ':' unless defined $sep;
555              
556 178         1871 return sprintf( "%0.2d%s%0.2d%s%0.2d",
557             $self->{local_c}{hour}, $sep,
558             $self->{local_c}{minute}, $sep,
559             $self->{local_c}{second} );
560             }
561              
562 174     174 1 2818 sub iso8601 { join 'T', $_[0]->ymd('-'), $_[0]->hms(':') }
563              
564 2     2 1 17 sub is_leap_year { DateTimeX::Lite::Util::is_leap_year( $_[0]->year ) }
565              
566 31865     31865 1 178324 sub time_zone { $_[0]->{tz} }
567              
568              
569 56     56 1 687 sub is_dst { $_[0]->{tz}->is_dst_for_datetime( $_[0] ) }
570              
571 3     3 1 21 sub time_zone_long_name { $_[0]->{tz}->name }
572 8     8 1 79 sub time_zone_short_name { $_[0]->{tz}->short_name_for_datetime( $_[0] ) }
573              
574 17083     17083 1 46328 sub locale { $_[0]->{locale} }
575              
576             # This method exists for the benefit of internal methods which create
577             # a new object based on the current object, like set() and truncate().
578             sub _new_from_self
579             {
580 17073     17073   27781 my $self = shift;
581              
582 17073         31917 my %old = map { $_ => $self->$_() }
  153657         342226  
583             qw( year month day hour minute second nanosecond
584             locale time_zone );
585 17073 50       57741 $old{formatter} = $self->formatter()
586             if defined $self->formatter();
587              
588 17073         90844 return (ref $self)->new( %old, @_ );
589             }
590              
591             sub set
592             {
593 17055     17055 1 1186472 my ($self, %p) = @_;
594              
595 17055         63245 my $new_dt = $self->_new_from_self(%p);
596              
597 17045         241450 %$self = %$new_dt;
598              
599 17045         118727 return $self;
600             }
601              
602 1     1 1 11 sub set_year { $_[0]->set( year => $_[1] ) }
603 1     1 1 8 sub set_month { $_[0]->set( month => $_[1] ) }
604 1     1 1 6 sub set_day { $_[0]->set( day => $_[1] ) }
605 1     1 1 8 sub set_hour { $_[0]->set( hour => $_[1] ) }
606 1     1 1 9 sub set_minute { $_[0]->set( minute => $_[1] ) }
607 1     1 1 7 sub set_second { $_[0]->set( second => $_[1] ) }
608 1     1 1 6 sub set_nanosecond { $_[0]->set( nanosecond => $_[1] ) }
609              
610 1     1 1 8 sub set_locale { $_[0]->set( locale => $_[1] ) }
611              
612 0     0 1 0 sub set_formatter { $_[0]->{formatter} = $_[1] }
613            
614              
615 17073     17073 1 58041 sub formatter { $_[0]->{formatter} }
616              
617             sub from_epoch
618             {
619 28     28 1 4816 my ($class, %p) = @_;
620              
621 28         50 my %args;
622              
623             # Because epoch may come from Time::HiRes
624 28         84 my $fraction = $p{epoch} - int( $p{epoch} );
625 28 100       105 $args{nanosecond} = int( $fraction * MAX_NANOSECONDS )
626             if $fraction;
627              
628             # Note, for very large negative values this may give a
629             # blatantly wrong answer.
630 28         471 @args{ qw( second minute hour day month year ) } =
631             ( gmtime( int delete $p{epoch} ) )[ 0..5 ];
632 28         84 $args{year} += 1900;
633 28         49 $args{month}++;
634              
635 28         163 my $self = $class->new( %p, %args, time_zone => 'UTC' );
636              
637 28 100       138 $self->set_time_zone( $p{time_zone} ) if exists $p{time_zone};
638              
639 28         148 return $self;
640             }
641              
642             sub _utc_ymd
643             {
644 16     16   28 my $self = shift;
645              
646 16 50       90 $self->_calc_utc_components unless exists $self->{utc_c}{year};
647              
648 16         34 return @{ $self->{utc_c} }{ qw( year month day ) };
  16         64  
649             }
650              
651             sub _utc_hms
652             {
653 16     16   29 my $self = shift;
654              
655 16 50       60 $self->_calc_utc_components unless exists $self->{utc_c}{hour};
656              
657 16         26 return @{ $self->{utc_c} }{ qw( hour minute second ) };
  16         59  
658             }
659              
660             # use scalar time in case someone's loaded Time::Piece
661 17     17 1 8320 sub now { shift->from_epoch( epoch => (scalar CORE::time), @_ ) }
662              
663 2     2 1 495 sub today { shift->now(@_)->truncate( to => 'day' ) }
664              
665             my %TruncateDefault = (
666             month => 1,
667             day => 1,
668             hour => 0,
669             minute => 0,
670             second => 0,
671             nanosecond => 0,
672             );
673              
674             sub truncate {
675 26     26 1 97 my ($self, %p) = @_;
676              
677 26         32 my %new;
678 26 100       71 if ( $p{to} eq 'week' )
679             {
680 8         20 my $day_diff = $self->day_of_week - 1;
681              
682 8 100       21 if ($day_diff)
683             {
684 7         24 $self->add( days => -1 * $day_diff );
685             }
686              
687 8         42 return $self->truncate( to => 'day' );
688             }
689             else
690             {
691 18         23 my $truncate;
692 18         44 foreach my $f ( qw( year month day hour minute second nanosecond ) ) {
693 126 100       327 $new{$f} = $truncate ? $TruncateDefault{$f} : $self->$f();
694              
695 126 100       363 $truncate = 1 if $p{to} eq $f;
696             }
697             }
698              
699 18         103 my $new_dt = $self->_new_from_self(%new);
700              
701 18         216 %$self = %$new_dt;
702              
703 18         166 return $self;
704             }
705              
706              
707             sub epoch
708             {
709 17     17 1 54 my $self = shift;
710              
711 17 100       75 return $self->{utc_c}{epoch}
712             if exists $self->{utc_c}{epoch};
713              
714 16         3654 require Time::Local;
715 16         6127 my ( $year, $month, $day ) = $self->_utc_ymd;
716 16         55 my @hms = $self->_utc_hms;
717              
718 16         81 $self->{utc_c}{epoch} =
719             Time::Local::timegm_nocheck( ( reverse @hms ),
720             $day,
721             $month - 1,
722             $year,
723             );
724              
725 16         507 return $self->{utc_c}{epoch};
726             }
727              
728             sub hires_epoch
729             {
730 1     1 1 3 my $self = shift;
731              
732 1         3 my $epoch = $self->epoch;
733              
734 1 50       4 return undef unless defined $epoch;
735              
736 1         5 my $nano = $self->{rd_nanosecs} / MAX_NANOSECONDS;
737              
738 1         6 return $epoch + $nano;
739             }
740              
741 0     0 1 0 sub is_finite { 1 }
742 6318     6318 1 20448 sub is_infinite { 0 }
743              
744             # added for benefit of DateTime::TimeZone
745 15     15 0 56 sub utc_year { $_[0]->{utc_year} }
746              
747              
748             sub leap_seconds
749             {
750 3     3 1 10 my $self = shift;
751              
752 3 100       12 return 0 if $self->{tz}->is_floating;
753              
754 2         73 return DateTimeX::Lite::LeapSecond::leap_seconds( $self->{utc_rd_days} );
755             }
756              
757             sub _calc_utc_components
758             {
759 16     16   26 my $self = shift;
760              
761 16 50       62 die "Cannot get UTC components before UTC RD has been calculated\n"
762             unless defined $self->{utc_rd_days};
763              
764 16         70 @{ $self->{utc_c} }{ qw( year month day ) } =
  16         71  
765             DateTimeX::Lite::Util::rd2ymd( $self->{utc_rd_days} );
766              
767 16         68 @{ $self->{utc_c} }{ qw( hour minute second ) } =
  16         60  
768             DateTimeX::Lite::Util::seconds_as_components( $self->{utc_rd_secs} );
769             }
770              
771             sub compare
772             {
773 966     966 1 2534 shift->_compare( @_, 0 );
774             }
775              
776             sub compare_ignore_floating
777             {
778 1     1 1 5 shift->_compare( @_, 1 );
779             }
780              
781             sub _compare
782             {
783 967 100   967   2562 my ( $class, $dt1, $dt2, $consistent ) = ref $_[0] ? ( undef, @_ ) : @_;
784              
785 967 50       2021 return undef unless defined $dt2;
786              
787 967 100 100     2165 if ( ! ref $dt2 && ( $dt2 == INFINITY || $dt2 == NEG_INFINITY ) )
      66        
788             {
789 6         42 return $dt1->{utc_rd_days} <=> $dt2;
790             }
791              
792 961 100 33     12046 unless ( (blessed $dt1 && $dt1->can( 'utc_rd_values' )) &&
      100        
      33        
793             (blessed $dt2 && $dt2->can( 'utc_rd_values' ) ))
794             {
795 2         5 my $dt1_string = overload::StrVal($dt1);
796 2         14 my $dt2_string = overload::StrVal($dt2);
797              
798 2         269 Carp::croak( "A DateTimeX::Lite object can only be compared to"
799             . " another DateTimeX::Lite object ($dt1_string, $dt2_string)." );
800             }
801              
802 959 100 33     11924 if ( ! $consistent &&
      66        
      66        
      33        
803             (blessed $dt1 && $dt1->can( 'time_zone' )) &&
804             (blessed $dt2 && $dt2->can( 'time_zone' ))
805             )
806             {
807 956         1957 my $is_floating1 = $dt1->time_zone->is_floating;
808 956         2100 my $is_floating2 = $dt2->time_zone->is_floating;
809              
810 956 100 100     6910 if ( $is_floating1 && ! $is_floating2 )
    100 100        
811             {
812 2         17 $dt1 = $dt1->clone->set_time_zone( $dt2->time_zone );
813             }
814             elsif ( $is_floating2 && ! $is_floating1 )
815             {
816 3         10 $dt2 = $dt2->clone->set_time_zone( $dt1->time_zone );
817             }
818             }
819              
820 959         2304 my @dt1_components = $dt1->utc_rd_values;
821 959         2107 my @dt2_components = $dt2->utc_rd_values;
822              
823 959         2167 foreach my $i ( 0..2 )
824             {
825 1103 100       5955 return $dt1_components[$i] <=> $dt2_components[$i]
826             if $dt1_components[$i] != $dt2_components[$i]
827             }
828              
829 57         374 return 0;
830             }
831              
832             sub from_day_of_year
833             {
834 1122     1122 1 17919 my ($class, %p) = @_;
835              
836 1122         4108 my $is_leap_year = DateTimeX::Lite::Util::is_leap_year( $p{year} );
837              
838 1122 100 100     4462 Carp::croak( "$p{year} is not a leap year.\n" )
839             if $p{day_of_year} == 366 && ! $is_leap_year;
840              
841 1121         1512 my $month = 1;
842 1121         2055 my $day = delete $p{day_of_year};
843              
844 1121   66     5175 while ( $month <= 12 && $day > DateTimeX::Lite::Util::month_length( $p{year}, $month ) )
845             {
846 6195         18848 $day -= DateTimeX::Lite::Util::month_length( $p{year}, $month );
847 6195         26716 $month++;
848             }
849              
850 1121         5090 return $class->new( %p,
851             month => $month,
852             day => $day,
853             );
854             }
855              
856              
857             1;
858              
859             __END__