File Coverage

blib/lib/Time/Moment/Epoch.pm
Criterion Covered Total %
statement 120 120 100.0
branch 16 30 53.3
condition 18 33 54.5
subroutine 37 37 100.0
pod 14 28 50.0
total 205 248 82.6


line stmt bran cond sub pod time code
1             #####################################################################
2             ## ABSTRACT: Convert various epoch times to Time::Moment times.
3             #####################################################################
4              
5              
6             package Time::Moment::Epoch;
7             our $VERSION = '1.002'; # VERSION
8              
9 1     1   425 use v5.10;
  1         3  
10 1     1   5 use strict;
  1         2  
  1         15  
11 1     1   3 use warnings;
  1         4  
  1         21  
12 1     1   839 use Math::BigInt try => 'GMP';
  1         18066  
  1         4  
13 1     1   12204 use Math::BigFloat;
  1         16763  
  1         6  
14 1     1   631 use Scalar::Util qw(looks_like_number);
  1         2  
  1         86  
15 1     1   483 use Time::Moment;
  1         1200  
  1         993  
16              
17             my $SECONDS_PER_DAY = 24 * 60 * 60;
18             my $NANOSECONDS_PER_DAY = $SECONDS_PER_DAY * 1e9;
19              
20              
21             # APFS time is in nanoseconds since the Unix epoch.
22             sub apfs {
23 1     1 1 206 my $num = shift;
24 1         5 _epoch2time($num, 1_000_000_000);
25             }
26             sub to_apfs {
27 1     1 0 1141 my $tm = shift;
28 1         4 _time2epoch($tm, 1_000_000_000);
29             }
30              
31              
32              
33             # Chrome time is the number of microseconds since 1601-01-01, which is
34             # 11,644,473,600 seconds before the Unix epoch.
35             #
36             sub chrome {
37 3     3 1 2855 my $num = shift;
38 3         8 _epoch2time($num, 1_000_000, -11_644_473_600);
39             }
40             sub to_chrome {
41 1     1 0 720 my $tm = shift;
42 1         5 _time2epoch($tm, 1_000_000, -11_644_473_600);
43             }
44              
45              
46             # Cocoa time is the number of seconds since 2001-01-01, which
47             # is 978,307,200 seconds after the Unix epoch.
48             sub cocoa {
49 2     2 1 2531 my $num = shift;
50 2         7 _epoch2time($num, 1, 978_307_200);
51             }
52             sub to_cocoa {
53 1     1 0 727 my $tm = shift;
54 1         4 _time2epoch($tm, 1, 978_307_200);
55             }
56              
57              
58             # DOS time uses bit fields to store dates between 1980-01-01 and
59             # 2107-12-31 (it fails outside that range).
60             sub dos {
61 1     1 1 1567 my $num = shift;
62              
63 1         4 my $year = ($num >> 25) & 0b1111111;
64              
65 1         2 my $month = ($num >> 21) & 0b1111;
66 1 50 33     8 return if $month < 1 or $month > 12;
67              
68 1         3 my $day = ($num >> 16) & 0b11111;
69 1 50 33     5 return if $day < 1 or $day > 31;
70              
71 1         3 my $hour = ($num >> 11) & 0b11111;
72 1 50 33     5 return if $hour < 0 or $hour > 23;
73              
74 1         3 my $minute = ($num >> 5) & 0b111111;
75 1 50 33     6 return if $minute < 0 or $minute > 60;
76              
77 1         2 my $second = ($num ) & 0b11111;
78 1 50 33     7 return if $second < 0 or $second > 60;
79              
80 1         10 Time::Moment->new(
81             year => 1980 + $year,
82             month => $month,
83             day => $day,
84             hour => $hour,
85             minute => $minute,
86             second => 2 * $second,
87             );
88              
89             }
90             sub to_dos {
91 1     1 0 499 my $tm = shift;
92              
93 1 50       5 if (ref $tm ne 'Time::Moment') {
94 1         15 $tm = Time::Moment->from_string($tm);
95             }
96              
97 1         18 ($tm->year - 1980 << 25) +
98             ($tm->month << 21) +
99             ($tm->day_of_month << 16) +
100             ($tm->hour << 11) +
101             ($tm->minute << 5) +
102             ($tm->second / 2);
103              
104             }
105              
106              
107             # Google Calendar time seems to count 32-day months from the day
108             # before the Unix epoch. @noppers worked out how to do this.
109             sub google_calendar {
110 1     1 1 513 my $n = shift;
111              
112 1 50       6 return unless looks_like_number $n;
113              
114 1         13 my $b = Math::BigInt->new($n);
115 1         53 my($total_days, $seconds) = $b->bdiv($SECONDS_PER_DAY);
116 1         256 my($months, $days) = $total_days->bdiv(32);
117              
118 1         208 Time::Moment
119             ->from_epoch(-$SECONDS_PER_DAY)
120             ->plus_days($days)
121             ->plus_months($months)
122             ->plus_seconds($seconds);
123             }
124             sub to_google_calendar {
125 1     1 0 600 my $tm = shift;
126              
127 1 50       5 if (ref $tm ne 'Time::Moment') {
128 1         6 $tm = Time::Moment->from_string($tm);
129             }
130              
131 1         13 ((((($tm->year - 1970 )*12
132             + ($tm->month - 1))*32
133             + $tm->day_of_month)*24
134             + $tm->hour )*60
135             + $tm->minute )*60
136             + $tm->second;
137             }
138              
139              
140             # ICQ time is the number of days since 1899-12-30, which is
141             # 2,209,161,600 seconds before the Unix epoch. Days can have a
142             # fractional part.
143             sub icq {
144 6   50 6 1 2017 my $days = shift // return;
145              
146 6 50       23 return unless looks_like_number $days;
147              
148 6         26 my $t = Time::Moment->from_epoch(-2_209_161_600);
149              
150 6         13 my $intdays = int($days);
151              
152             # Want the fractional part of the day in nanoseconds.
153 6         15 my $fracday = int(($days - $intdays) * $NANOSECONDS_PER_DAY);
154              
155 6         31 return $t->plus_days($days)->plus_nanoseconds($fracday);
156             }
157             sub to_icq {
158 3     3 0 990 my $tm = shift;
159              
160 3 50       16 if (ref $tm ne 'Time::Moment') {
161 3         20 $tm = Time::Moment->from_string($tm);
162             }
163              
164 3         13 my $t2 = Time::Moment->from_epoch(-2_209_161_600);
165              
166 3         19 $t2->delta_nanoseconds($tm) / $NANOSECONDS_PER_DAY;
167             }
168              
169              
170             # Java time is in milliseconds since the Unix epoch.
171             sub java {
172 2     2 1 1343 my $num = shift;
173 2         6 _epoch2time($num, 1000);
174             }
175             sub to_java {
176 1     1 0 746 my $tm = shift;
177 1         4 _time2epoch($tm, 1000);
178             }
179              
180              
181             # Mozilla time is in microseconds since the Unix epoch.
182             sub mozilla {
183 1     1 1 1605 my $num = shift;
184 1         4 _epoch2time($num, 1_000_000);
185             }
186             sub to_mozilla {
187 1     1 0 750 my $tm = shift;
188 1         4 _time2epoch($tm, 1_000_000);
189             }
190              
191              
192             # OLE time is the number of days since 1899-12-30, which is
193             # 2,209,161,600 seconds before the Unix epoch.
194             sub ole {
195 2   50 2 1 2136 my $bytes = shift // return;
196              
197 2 50       16 my $d_days = unpack('d', $bytes) or return;
198              
199 2 50       18 return if $d_days eq '-nan';
200              
201 2         7 return icq $d_days;
202             }
203             sub to_ole {
204 1   50 1 0 493 my $t = shift // return;
205              
206 1         7 my $icq = to_icq($t);
207              
208 1 50       8 my $epoch = pack('d', $icq) or return;
209              
210 1         3 return $epoch;
211             }
212              
213              
214             # Symbian time is the number of microseconds since the year 0, which
215             # is 62,167,219,200 seconds before the Unix epoch.
216             sub symbian {
217 1     1 1 525 my $num = shift;
218 1         8 _epoch2time($num, 1_000_000, -62_167_219_200);
219             }
220             sub to_symbian {
221 1     1 0 815 my $tm = shift;
222 1         4 _time2epoch($tm, 1_000_000, -62_167_219_200);
223             }
224              
225              
226             # Unix time is the number of seconds since 1970-01-01.
227             sub unix {
228 2     2 1 2660 my $num = shift;
229 2         6 _epoch2time($num);
230             }
231             sub to_unix {
232 2     2 0 2338 my $tm = shift;
233 2         7 _time2epoch($tm);
234             }
235              
236              
237             # UUID version 1 time (RFC 4122) is the number of hectonanoseconds
238             # (100 ns) since 1582-10-15, which is 12,219,292,800 seconds before
239             # the Unix epoch.
240             sub uuid_v1 {
241 2     2 1 2252 my $num = shift;
242 2         6 _epoch2time($num, 10_000_000, -12_219_292_800);
243             }
244             sub to_uuid_v1 {
245 2     2 0 2573 my $tm = shift;
246 2         7 _time2epoch($tm, 10_000_000, -12_219_292_800);
247             }
248              
249              
250             # Windows date time (e.g., .NET) is the number of hectonanoseconds
251             # (100 ns) since 0001-01-01, which is 62,135,596,800 seconds before
252             # the Unix epoch.
253             sub windows_date {
254 2     2 1 2504 my $num = shift;
255 2         7 _epoch2time($num, 10_000_000, -62_135_596_800);
256             }
257             sub to_windows_date {
258 1     1 0 747 my $tm = shift;
259 1         3 _time2epoch($tm, 10_000_000, -62_135_596_800);
260             }
261              
262              
263             # Windows file time (e.g., NTFS) is the number of hectonanoseconds
264             # (100 ns) since 1601-01-01, which is 11,644,473,600 seconds before
265             # the Unix epoch.
266             sub windows_file {
267 2     2 1 2621 my $num = shift;
268 2         7 _epoch2time($num, 10_000_000, -11_644_473_600);
269             }
270             sub to_windows_file {
271 1     1 0 755 my $tm = shift;
272 1         4 _time2epoch($tm, 10_000_000, -11_644_473_600);
273             }
274              
275            
276             sub _epoch2time {
277 18   50 18   60 my $num = shift // return;
278 18   100     52 my $q = shift // 1;
279 18   100     57 my $s = shift // 0;
280              
281 18 100       79 return unless looks_like_number $num;
282              
283 17         64 my($z, $m) = Math::BigInt->new($num)->bdiv($q);
284 17         5454 my $r = ($m * 1e9)->bdiv($q);
285 17         6443 Time::Moment->from_epoch($z + $s, $r);
286             }
287              
288             sub _time2epoch {
289 12   50 12   41 my $t = shift // return;
290 12   100     40 my $m = shift // 1;
291 12   100     40 my $s = shift // 0;
292              
293 12 50       41 if (ref $t ne 'Time::Moment') {
294 12         79 $t = Time::Moment->from_string($t);
295             }
296            
297 12         88 my $bf = Math::BigFloat->new($t->nanosecond)->bdiv(1e9);
298 12         7214 int $m*($t->epoch + $bf - $s);
299             }
300              
301             1;
302              
303             __END__