File Coverage

blib/lib/DateTime/Format/Epoch.pm
Criterion Covered Total %
statement 96 101 95.0
branch 40 50 80.0
condition 9 15 60.0
subroutine 13 13 100.0
pod 3 3 100.0
total 161 182 88.4


line stmt bran cond sub pod time code
1             package DateTime::Format::Epoch;
2              
3 19     19   739149 use 5.00503; #qr
  19         40  
  19         506  
4 19     19   60 use strict;
  19         20  
  19         458  
5              
6 19     19   61 use vars qw($VERSION);
  19         18  
  19         757  
7              
8             $VERSION = '0.15';
9              
10 19     19   693 use DateTime 0.22;
  19         86717  
  19         308  
11 19     19   7256 use DateTime::LeapSecond;
  19         18910  
  19         551  
12              
13 19 50   19   15087 use Math::BigInt ('lib' => $^O eq 'MSWin32' ? 'Pari,FastCalc' : 'GMP,Pari,FastCalc');
  19         253815  
  19         357  
14 19     19   198432 use Params::Validate qw/validate BOOLEAN OBJECT/;
  19         33  
  19         13031  
15              
16             sub _floor {
17 11     11   113 my $x = shift;
18 11         14 my $ix = int $x;
19 11 50       26 if ($ix <= $x) {
20 11         36 return $ix;
21             } else {
22 0         0 return $ix - 1;
23             }
24             }
25              
26             my %units_per_second = (
27             seconds => 1,
28             milliseconds => 1000,
29             microseconds => 1e6,
30             nanoseconds => 1e9,
31             );
32              
33             sub new {
34 57     57 1 1979 my $class = shift;
35             my %p = validate( @_,
36             { epoch => {type => OBJECT,
37             can => 'utc_rd_values'},
38             unit => {callbacks =>
39             {'valid unit' =>
40 48 100   48   1452 sub { exists $units_per_second{$_[0]}
41             or $_[0] > 0 }},
42 57         1416 default => 'seconds'},
43             type => {regex => qr/^(?:int|float|bigint)$/,
44             default => 0},
45             local_epoch => {type => BOOLEAN,
46             default => 0},
47             dhms => {type => BOOLEAN,
48             default => 0},
49             skip_leap_seconds => {type => BOOLEAN,
50             default => 1},
51             start_at => {default => 0},
52             } );
53              
54 57 50       1925 $p{epoch} = $p{epoch}->clone if $p{epoch}->can('clone');
55              
56 57   66     591 $p{unit} = $units_per_second{$p{unit}} || $p{unit};
57 57 100       108 $p{unit} = 1 if $p{dhms};
58              
59 57 100       87 if (!$p{type}) {
60 11 100       40 $p{type} = ($p{unit} > 1e6 ? 'bigint' : 'int');
61             }
62              
63 57         121 ($p{epoch_rd_days}, $p{epoch_rd_secs}) = $p{epoch}->utc_rd_values;
64 57         323 $p{epoch_class} = ref $p{epoch};
65              
66 57 100       91 if (!$p{skip_leap_seconds}) {
67 5         147 $p{leap_secs} =
68             DateTime::LeapSecond::leap_seconds( $p{epoch_rd_days} );
69             }
70              
71 57         138 my $self = bless \%p, $class;
72 57         123 return $self;
73             }
74              
75             sub format_datetime {
76 96     96 1 75756 my ($self, $dt) = @_;
77              
78 96 100       199 unless (ref $self) {
79 25         64 $self = $self->new;
80             }
81              
82 96 100 66     365 $dt = $dt->clone->set_time_zone('floating')
      66        
      33        
      66        
83             if $self->{local_epoch} &&
84             $self->{epoch}->can('time_zone') &&
85             $self->{epoch}->time_zone->is_floating &&
86             $dt->can('time_zone') &&
87             !$dt->time_zone->is_floating;
88              
89 96         917 my ($rd_days, $rd_secs) = $dt->utc_rd_values;
90 96         413 my $delta_days = $rd_days - $self->{epoch_rd_days};
91 96         97 my $delta_secs = $rd_secs - $self->{epoch_rd_secs};
92              
93 96         117 my $secs = $delta_days * 86_400 + $delta_secs;
94              
95 96 100       160 if (!$self->{skip_leap_seconds}) {
96 16         378 $secs += DateTime::LeapSecond::leap_seconds( $rd_days )
97             - $self->{leap_secs};
98             }
99              
100 96 100       267 if ($self->{type} eq 'bigint') {
101 21         64 $secs = Math::BigInt->new($secs);
102             }
103              
104 96         905 $secs *= $self->{unit};
105              
106 96 50       1872 if ($dt->can('nanosecond')) {
107 96         158 my $fraction = $dt->nanosecond / 1e9 * $self->{unit};
108 96 100       415 if ($self->{type} eq 'float') {
109 11         12 $secs += $fraction;
110             } else {
111 85         120 $secs += int $fraction;
112             }
113             }
114              
115 96         1995 $secs += $self->{start_at};
116              
117 96 100       1773 if ($self->{dhms}) {
118 5         16 my $mins = int($secs / 60);
119 5         6 $secs -= $mins * 60;
120 5         5 my $hours = int($mins / 60);
121 5         5 $mins -= $hours * 60;
122 5         23 my $days = int($hours / 24);
123 5         3 $hours -= $days * 24;
124              
125 5         32 return $days, $hours, $mins, $secs;
126             }
127              
128 91         376 return $secs;
129             }
130              
131             sub parse_datetime {
132 11     11 1 11682 my ($self, $str) = @_;
133              
134 11 100       21 unless (ref $self) {
135 7         15 $self = $self->new;
136             }
137              
138 11 50       24 if ($self->{dhms}) {
139 0         0 my (undef, $d, $h, $m, $s) = @_;
140 0         0 $str = (($d * 24 + $h) * 60 + $m) + $s;
141             }
142              
143 11         12 $str -= $self->{start_at};
144              
145 11         140 my $delta_days = _floor( $str / (86_400 * $self->{unit}) );
146 11         15 $str -= $delta_days * 86_400 * $self->{unit};
147              
148             # $str cannot be negative now, so int() instead of _floor()
149 11         188 my $delta_secs = int( $str / $self->{unit} );
150 11         82 $str -= $delta_secs * $self->{unit};
151              
152 11         130 my $delta_nano = $str / $self->{unit} * 1e9;
153              
154 11         141 my $rd_days = $self->{epoch_rd_days} + $delta_days;
155 11         77 my $rd_secs = $self->{epoch_rd_secs} + $delta_secs;
156              
157 11 100       78 if (!$self->{skip_leap_seconds}) {
158 1         22 $rd_secs -= DateTime::LeapSecond::leap_seconds( $rd_days )
159             - $self->{leap_secs};
160 1 50       352 if ($rd_secs >= DateTime::LeapSecond::day_length( $rd_days )) {
    0          
161 1         61 $rd_secs -= DateTime::LeapSecond::day_length( $rd_days );
162 1         91 $rd_days++;
163             } elsif ($rd_secs < 0) {
164 0         0 $rd_days--;
165 0         0 $rd_secs += DateTime::LeapSecond::day_length( $rd_days );
166             }
167             } else {
168 10 100       16 if ($rd_secs >= 86400) {
169 2         2 $rd_secs -= 86400;
170 2         2 $rd_days++;
171             }
172             }
173              
174 11 100       44 $rd_days = $rd_days->numify if UNIVERSAL::isa($rd_days, 'Math::BigInt');
175 11 100       37 $rd_secs = $rd_secs->numify if UNIVERSAL::isa($rd_secs, 'Math::BigInt');
176              
177 11         39 my $temp_dt = bless { rd_days => $rd_days, rd_secs => $rd_secs},
178             'DateTime::Format::Epoch::_DateTime';
179              
180 11         30 my $dt = $self->{epoch_class}->from_object( object => $temp_dt );
181              
182 11 50       2680 if (!$self->{local_epoch}) {
183 11 50       43 $dt->set_time_zone( 'UTC' ) if $dt->can('set_time_zone');
184             }
185              
186 11         843 return $dt;
187             }
188              
189             sub DateTime::Format::Epoch::_DateTime::utc_rd_values {
190 11     11   201 my $self = shift;
191              
192 11         33 return $self->{rd_days}, $self->{rd_secs};
193             }
194              
195             1;
196             __END__