File Coverage

blib/lib/Time/FFI/tm.pm
Criterion Covered Total %
statement 74 96 77.0
branch 15 34 44.1
condition 2 6 33.3
subroutine 19 22 86.3
pod 9 12 75.0
total 119 170 70.0


line stmt bran cond sub pod time code
1             package Time::FFI::tm;
2              
3 3     3   501990 use strict;
  3         19  
  3         93  
4 3     3   17 use warnings;
  3         6  
  3         76  
5 3     3   15 use Carp ();
  3         5  
  3         39  
6 3     3   1718 use FFI::Platypus::Record ();
  3         23527  
  3         77  
7 3     3   1893 use Module::Runtime ();
  3         5561  
  3         76  
8 3     3   1201 use Time::Local ();
  3         3414  
  3         223  
9              
10             our $VERSION = '2.002';
11              
12             my @tm_members = qw(sec min hour mday mon year wday yday isdst);
13              
14             FFI::Platypus::Record::record_layout_1(
15             (map { (int => $_) } @tm_members),
16             long => 'gmtoff',
17             string => 'zone',
18             );
19              
20             {
21 3     3   28 no strict 'refs';
  3         8  
  3         4348  
22             *{"tm_$_"} = \&$_ for @tm_members, 'gmtoff', 'zone';
23             }
24              
25             sub from_list {
26 4     4 1 193 my ($class, @args) = @_;
27 4         16 my %attr = map { ($tm_members[$_] => $args[$_]) } 0..$#tm_members;
  36         78  
28 4         31 return $class->new(\%attr);
29             }
30              
31             sub from_object {
32 4     4 1 15921 my ($class, $obj) = @_;
33 4 100 33     51 if ($obj->isa('Time::Piece')) {
    50          
    50          
    50          
34 2         13 return $class->new(
35             year => $obj->year - 1900,
36             mon => $obj->mon - 1,
37             mday => $obj->mday,
38             hour => $obj->hour,
39             min => $obj->min,
40             sec => $obj->sec,
41             isdst => -1,
42             );
43             } elsif ($obj->isa('Time::Moment')) {
44 0         0 return $class->new(
45             year => $obj->year - 1900,
46             mon => $obj->month - 1,
47             mday => $obj->day_of_month,
48             hour => $obj->hour,
49             min => $obj->minute,
50             sec => $obj->second,
51             isdst => -1,
52             );
53             } elsif ($obj->isa('DateTime')) {
54 0         0 return $class->new(
55             year => $obj->year - 1900,
56             mon => $obj->month - 1,
57             mday => $obj->day,
58             hour => $obj->hour,
59             min => $obj->minute,
60             sec => $obj->second,
61             isdst => -1,
62             );
63             } elsif ($obj->isa('Time::FFI::tm') or $obj->isa('Time::tm')) {
64 2         56 my %attr = map { ($_ => $obj->$_) } qw(sec min hour mday mon year wday yday isdst);
  18         347  
65 2         29 return $class->new(\%attr);
66             } else {
67 0         0 my $class = ref $obj;
68 0         0 Carp::croak "Cannot convert from unrecognized object class $class";
69             }
70             }
71              
72             sub to_list {
73 2     2 1 5 my ($self) = @_;
74 2         7 return map { $self->$_ } @tm_members;
  18         50  
75             }
76              
77             sub to_object {
78 0     0 0 0 Carp::carp '->to_object is deprecated; use ->to_object_as_local or ->to_object_as_utc';
79 0         0 return _to_object(@_);
80             }
81              
82             sub to_object_as_local {
83 3     3 1 130 my ($self, $class) = @_;
84 3         10 return _to_object($self, $class, 1);
85             }
86              
87             sub to_object_as_utc {
88 2     2 1 68 my ($self, $class) = @_;
89 2         6 return _to_object($self, $class, 0);
90             }
91              
92             sub _to_object {
93 5     5   12 my ($self, $class, $islocal) = @_;
94 5         24 Module::Runtime::require_module $class;
95 5 100 33     278 if ($class->isa('Time::Piece')) {
    50          
    50          
    50          
96 3 100       12 my ($epoch) = $islocal ? _mktime($self) : _timegm($self);
97 3 100       77 return $islocal ? scalar $class->localtime($epoch) : scalar $class->gmtime($epoch);
98             } elsif ($class->isa('Time::Moment')) {
99 0         0 my $tm = $self;
100 0         0 my $epoch;
101 0 0       0 ($epoch, $tm) = _mktime($self) if $islocal;
102 0         0 my $moment = $class->new(
103             year => $tm->year + 1900,
104             month => $tm->mon + 1,
105             day => $tm->mday,
106             hour => $tm->hour,
107             minute => $tm->min,
108             second => $tm->sec,
109             );
110 0 0       0 return $islocal ? $moment->with_offset_same_local(($moment->epoch - $epoch) / 60) : $moment;
111             } elsif ($class->isa('DateTime')) {
112 0         0 my $tm = $self;
113 0 0       0 (undef, $tm) = _mktime($self) if $islocal;
114 0 0       0 return $class->new(
115             year => $tm->year + 1900,
116             month => $tm->mon + 1,
117             day => $tm->mday,
118             hour => $tm->hour,
119             minute => $tm->min,
120             second => $tm->sec,
121             time_zone => $islocal ? 'local' : 'UTC',
122             );
123             } elsif ($class->isa('Time::FFI::tm') or $class->isa('Time::tm')) {
124 2         6 my %attr = map { ($_ => $self->$_) } qw(sec min hour mday mon year wday yday isdst);
  18         47  
125 2         54 return $class->new(%attr);
126             } else {
127 0         0 Carp::croak "Cannot convert to unrecognized object class $class";
128             }
129             }
130              
131             sub epoch {
132 0     0 0 0 my ($self, $islocal) = @_;
133 0         0 Carp::carp '->epoch is deprecated; use ->epoch_as_local or ->epoch_as_utc';
134 0 0       0 my ($epoch) = $islocal ? _mktime($self) : _timegm($self);
135 0         0 return $epoch;
136             }
137              
138             sub epoch_as_local {
139 4     4 1 18170 my ($self) = @_;
140 4         13 my ($epoch) = _mktime($self);
141 4         25 return $epoch;
142             }
143              
144             sub epoch_as_utc {
145 4     4 1 9253 my ($self) = @_;
146 4         13 my ($epoch) = _timegm($self);
147 4         148 return $epoch;
148             }
149              
150             sub normalized {
151 0     0 0 0 my ($self, $islocal) = @_;
152 0         0 Carp::carp '->normalized is deprecated; use ->normalized_as_local or ->normalized_as_utc';
153 0 0       0 return $islocal ? $self->normalized_as_local : $self->normalized_as_utc;
154             }
155             *with_extra = \&normalized;
156              
157             sub normalized_as_local {
158 1     1 1 2892 my ($self) = @_;
159 1         4 my (undef, $new) = _mktime($self);
160 1         4 return $new;
161             }
162              
163             sub normalized_as_utc {
164 1     1 1 2487 my ($self) = @_;
165 1         5 my ($epoch) = _timegm($self);
166 1         39 require Time::FFI;
167 1         3 my $new = Time::FFI::gmtime($epoch);
168 1         3 bless $new, ref $self;
169 1         3 return $new;
170             }
171              
172             sub _mktime {
173 7     7   15 my ($self) = @_;
174 7         1114 require Time::FFI;
175 7         32 my %attr = map { ($_ => $self->$_) } qw(sec min hour mday mon year);
  42         118  
176 7         18 $attr{isdst} = -1;
177 7         28 my $new = (ref $self)->new(\%attr);
178 7         340 return (Time::FFI::mktime($new), $new);
179             }
180              
181             sub _timegm {
182 6     6   12 my ($self) = @_;
183 6         21 my $year = $self->year;
184 6 50       21 $year += 1900 if $year >= 0; # avoid timegm year heuristic
185 6         14 my @vals = ((map { $self->$_ } qw(sec min hour mday mon)), $year);
  30         72  
186 6         23 return scalar Time::Local::timegm(@vals);
187             }
188              
189             1;
190              
191             =head1 NAME
192              
193             Time::FFI::tm - POSIX tm record structure
194              
195             =head1 SYNOPSIS
196              
197             use Time::FFI::tm;
198              
199             my $tm = Time::FFI::tm->new(
200             year => 95, # years since 1900
201             mon => 0, # 0 == January
202             mday => 1,
203             hour => 13,
204             min => 25,
205             sec => 59,
206             isdst => -1, # allow DST status to be determined by the system
207             );
208             $tm->mday($tm->mday + 1); # add a day
209              
210             my $in_local = $tm->normalized_as_local;
211             say $in_local->isdst; # now knows if DST is active
212              
213             my $tm = Time::FFI::tm->from_list(CORE::localtime(time));
214              
215             my $epoch = POSIX::mktime($tm->to_list);
216             my $epoch = $tm->epoch_as_local;
217              
218             my $tm = Time::FFI::tm->from_object(Time::Moment->now);
219             my $datetime = $tm->to_object_as_local('DateTime');
220              
221             =head1 DESCRIPTION
222              
223             This L class represents the C struct defined by
224             F and used by functions such as L and L. This
225             is used by L to provide access to such structures.
226              
227             The structure does not store an explicit time zone, so you must specify whether
228             to interpret it as local or UTC time whenever rendering it to an actual
229             datetime.
230              
231             =head1 ATTRIBUTES
232              
233             The integer components of the C struct are stored as settable attributes
234             that default to 0.
235              
236             Note that 0 is out of the standard range for the C value (often
237             indicating the last day of the previous month), and C should be set to a
238             negative value if unknown, so these values should always be specified
239             explicitly.
240              
241             Each attribute also has a corresponding alias starting with C to match the
242             standard C struct member names.
243              
244             =head2 sec
245              
246             Seconds [0,60].
247              
248             =head2 min
249              
250             Minutes [0,59].
251              
252             =head2 hour
253              
254             Hour [0,23].
255              
256             =head2 mday
257              
258             Day of month [1,31].
259              
260             =head2 mon
261              
262             Month of year [0,11].
263              
264             =head2 year
265              
266             Years since 1900.
267              
268             =head2 wday
269              
270             Day of week [0,6] (Sunday =0).
271              
272             =head2 yday
273              
274             Day of year [0,365].
275              
276             =head2 isdst
277              
278             Daylight Savings flag. (0: off, positive: on, negative: unknown)
279              
280             =head2 gmtoff
281              
282             Seconds east of UTC. (May not be available on all systems)
283              
284             =head2 zone
285              
286             Timezone abbreviation. (Read only string, may not be available on all systems)
287              
288             =head1 METHODS
289              
290             =head2 new
291              
292             my $tm = Time::FFI::tm->new;
293             my $tm = Time::FFI::tm->new(year => $year, ...);
294             my $tm = Time::FFI::tm->new({year => $year, ...});
295              
296             Construct a new B object representing a C struct.
297              
298             =head2 from_list
299              
300             my $tm = Time::FFI::tm->from_list($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst);
301              
302             Construct a new B object from the passed list of time
303             attributes, in the same order returned by L. Missing or
304             undefined values will be interpreted as the default of 0, but see
305             L.
306              
307             =head2 from_object
308              
309             my $tm = Time::FFI::tm->from_object($obj);
310              
311             I
312              
313             Construct a new B object from the passed datetime object's local
314             datetime components. Currently L, L, L,
315             L, and L objects (and subclasses) are recognized. The
316             original time zone and any fractional seconds will not be represented in the
317             resulting structure.
318              
319             =head2 to_list
320              
321             my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = $tm->to_list;
322              
323             Return the list of time attributes in the structure, in the same order returned
324             by L.
325              
326             =head2 to_object_as_local
327              
328             =head2 to_object_as_utc
329              
330             my $piece = $tm->to_object_as_local('Time::Piece');
331             my $moment = $tm->to_object_as_utc('Time::Moment');
332              
333             I
334              
335             Return an object of the specified class. Currently L,
336             L, and L (or subclasses) are recognized. Depending on
337             the method called, the time attributes are interpreted in the local time zone
338             or in UTC.
339              
340             When interpreted as a local time, values outside the standard ranges are
341             accepted; this is not currently supported for UTC times.
342              
343             You may also specify L or L (or subclasses), in which
344             case C and C produce the same result with
345             the time attributes copied as-is.
346              
347             =head2 epoch_as_local
348              
349             =head2 epoch_as_utc
350              
351             my $epoch = $tm->epoch_as_local;
352             my $epoch = $tm->epoch_as_utc
353              
354             I
355              
356             Translate the time structure into a Unix epoch timestamp (seconds since
357             1970-01-01 UTC). Depending on the method called, the time attributes are
358             interpreted in the local time zone or in UTC.
359              
360             When interpreted as a local time, values outside the standard ranges are
361             accepted; this is not currently supported for UTC times.
362              
363             =head2 normalized_as_local
364              
365             =head2 normalized_as_utc
366              
367             my $new = $tm->normalized_as_local;
368             my $new = $tm->normalized_as_utc;
369              
370             I
371              
372             Return a new B object representing the same time, but with
373             C, C, C, and (if supported) C and C set
374             appropriately. Depending on the method called, the time attributes are
375             interpreted in the local time zone or in UTC.
376              
377             When interpreted as a local time, values outside the standard ranges will also
378             be normalized; this is not currently supported for UTC times.
379              
380             =head1 BUGS
381              
382             Report any issues on the public bugtracker.
383              
384             =head1 AUTHOR
385              
386             Dan Book
387              
388             =head1 COPYRIGHT AND LICENSE
389              
390             This software is Copyright (c) 2019 by Dan Book.
391              
392             This is free software, licensed under:
393              
394             The Artistic License 2.0 (GPL Compatible)
395              
396             =head1 SEE ALSO
397              
398             L, L
399              
400             =for Pod::Coverage with_extra to_object epoch normalized tm_sec tm_min tm_hour tm_mday tm_mon tm_year tm_wday tm_yday tm_isdst tm_gmtoff tm_zone