File Coverage

blib/lib/Time/Clock.pm
Criterion Covered Total %
statement 183 186 98.3
branch 86 92 93.4
condition 52 66 78.7
subroutine 28 28 100.0
pod 13 17 76.4
total 362 389 93.0


line stmt bran cond sub pod time code
1             package Time::Clock;
2              
3 4     4   116597 use strict;
  4         10  
  4         171  
4              
5 4     4   23 use Carp;
  4         8  
  4         587  
6              
7             our $VERSION = '1.03';
8              
9             use overload
10             (
11 17     17   245 '""' => sub { shift->as_string },
12 4         47 fallback => 1,
13 4     4   8440 );
  4         6331  
14              
15             our $Have_HiRes_Time;
16              
17             TRY:
18             {
19             local $@;
20             eval { require Time::HiRes };
21             $Have_HiRes_Time = $@ ? 0 : 1;
22             }
23              
24             # Allow an hour value of 24
25             our $Allow_Hour_24 = 0;
26              
27 4     4   568 use constant NANOSECONDS_IN_A_SECOND => 1_000_000_000;
  4         7  
  4         360  
28 4     4   20 use constant SECONDS_IN_A_MINUTE => 60;
  4         8  
  4         215  
29 4     4   20 use constant SECONDS_IN_AN_HOUR => SECONDS_IN_A_MINUTE * 60;
  4         7  
  4         197  
30 4     4   20 use constant SECONDS_IN_A_CLOCK => SECONDS_IN_AN_HOUR * 24;
  4         7  
  4         175  
31              
32 4     4   20 use constant DEFAULT_FORMAT => '%H:%M:%S%n';
  4         6  
  4         7135  
33              
34             our %Default_Format;
35              
36             __PACKAGE__->default_format(DEFAULT_FORMAT);
37              
38             sub default_format
39             {
40 164     164 1 195 my($invocant) = shift;
41              
42             # Called as object method
43 164 100       323 if(ref $invocant)
44             {
45 80 50       139 return $invocant->{'default_format'} = shift if(@_);
46 80         205 return ref($invocant)->default_format;
47             }
48              
49             # Called as class method
50 84 100       149 return $Default_Format{$invocant} = shift if(@_);
51 80   50     398 return $Default_Format{$invocant} ||= DEFAULT_FORMAT;
52             }
53              
54             sub new
55             {
56 8     8 1 1207 my($class) = shift;
57              
58 8         26 my $self = bless {}, $class;
59 8 100       40 @_ = (parse => @_) if(@_ == 1);
60 8         26 $self->init(@_);
61              
62 8         23 return $self;
63             }
64              
65             sub init
66             {
67 8     8 0 14 my($self) = shift;
68              
69 8         29 while(@_)
70             {
71 5         7 my $method = shift;
72 5         18 $self->$method(shift);
73             }
74             }
75              
76             sub hour
77             {
78 2078     2078 1 3638 my($self) = shift;
79              
80 2078 100       8903 if(@_)
81             {
82 583         806 my $hour = shift;
83              
84 583 100       827 if($Allow_Hour_24)
85             {
86 4 50 33     29 croak "hour must be between 0 and 24"
      33        
87             unless(!defined $hour || ($hour >= 0 && $hour <= 24));
88             }
89             else
90             {
91 579 100 100     3065 croak "hour must be between 0 and 23"
      33        
92             unless(!defined $hour || ($hour >= 0 && $hour <= 23));
93             }
94              
95 581         1127 return $self->{'hour'} = $hour;
96             }
97              
98 1495   100     5057 return $self->{'hour'} ||= 0;
99             }
100              
101             sub minute
102             {
103 1756     1756 1 2649 my($self) = shift;
104              
105 1756 100       2918 if(@_)
106             {
107 568         604 my $minute = shift;
108              
109 568 100 100     3183 croak "minute must be between 0 and 59"
      66        
110             unless(!defined $minute || ($minute >= 0 && $minute <= 59));
111              
112 566         882 return $self->{'minute'} = $minute;
113             }
114              
115 1188   100     3442 return $self->{'minute'} ||= 0;
116             }
117              
118             sub second
119             {
120 1283     1283 1 2215 my($self) = shift;
121              
122 1283 100       2148 if(@_)
123             {
124 568         568 my $second = shift;
125              
126 568 100 100     2692 croak "second must be between 0 and 59"
      66        
127             unless(!defined $second || ($second >= 0 && $second <= 59));
128              
129 566         895 return $self->{'second'} = $second;
130             }
131              
132 715   100     3697 return $self->{'second'} ||= 0;
133             }
134              
135             sub nanosecond
136             {
137 222     222 1 1107 my($self) = shift;
138              
139 222 100       388 if(@_)
140             {
141 95         104 my $nanosecond = shift;
142              
143 95 100 100     494 croak "nanosecond must be between 0 and ", (NANOSECONDS_IN_A_SECOND - 1)
      66        
144             unless(!defined $nanosecond || ($nanosecond >= 0 && $nanosecond < NANOSECONDS_IN_A_SECOND));
145              
146 93         184 return $self->{'nanosecond'} = $nanosecond;
147             }
148              
149 127         258 return $self->{'nanosecond'};
150             }
151              
152             sub ampm
153             {
154 297     297 1 401 my($self) = shift;
155              
156 297 100 100     742 if(@_ && defined $_[0])
157             {
158 13         18 my $ampm = shift;
159              
160 13 100       74 if($ampm =~ /^a\.?m\.?$/i)
    50          
161             {
162 4 100       10 if($self->hour > 12)
    50          
163             {
164 1         2 croak "Cannot set AM/PM to AM when hour is set to ", $self->hour;
165             }
166             elsif($self->hour == 12)
167             {
168 3         10 $self->hour(0);
169             }
170              
171 3         8 return 'am';
172             }
173             elsif($ampm =~ /^p\.?m\.?$/i)
174             {
175 9 100       18 if($self->hour < 12)
176             {
177 6         14 $self->hour($self->hour + 12);
178             }
179              
180 9         24 return 'pm';
181             }
182 0         0 else { croak "AM/PM value not understood: $ampm" }
183             }
184              
185 284 100       427 return ($self->hour >= 12) ? 'PM' : 'AM';
186             }
187              
188             sub as_string
189             {
190 80     80 1 126 my($self) = shift;
191 80         159 return $self->format($self->default_format);
192             }
193              
194             sub format
195             {
196 110     110 1 173 my($self, $format) = @_;
197              
198 110   33     210 $format ||= ref($self)->default_format;
199              
200 110         195 my $hour = $self->hour;
201 110 100       260 my $ihour = $hour > 12 ? ($hour - 12) : $hour == 0 ? 12 : $hour;
    100          
202 110         191 my $ns = $self->nanosecond;
203              
204 110         179 $ihour =~ s/^0//;
205              
206 110 100 100     422 my %formats =
207             (
208             'H' => sprintf('%02d', $hour),
209             'I' => sprintf('%02d', $ihour),
210             'i' => $ihour,
211             'k' => $hour,
212             'M' => sprintf('%02d', $self->minute),
213             'S' => sprintf('%02d', $self->second),
214             'N' => sprintf('%09d', $ns || 0),
215             'n' => defined $ns ? sprintf('.%09d', $ns) : '',
216             'p' => $self->ampm,
217             'P' => lc $self->ampm,
218             's' => $self->as_integer_seconds,
219             );
220              
221 110         318 $formats{'n'} =~ s/\.?0+$//;
222              
223 110         189 for($format)
224             {
225 110         226 s{ ((?:%%|[^%]+)*) %T }{$1%H:%M:%S}gx;
226              
227 110         966 s/%([HIikMSsNnpP])/$formats{$1}/g;
228              
229 4     4   30 no warnings 'uninitialized';
  4         7  
  4         2035  
230 110   50     225 s{ ((?:%%|[^%]+)*) % ([1-9]) N }{ $1 . substr(sprintf("%09d", $ns || 0), 0, $2) }gex;
  11         63  
231              
232 110 100       189 if(defined $ns)
233             {
234 53   50     126 s{ ((?:%%|[^%]+)*) % ([1-9]) n }{ "$1." . substr(sprintf("%09d", $ns || 0), 0, $2) }gex;
  9         86  
235             }
236             else
237             {
238 57         76 s{ ((?:%%|[^%]+)*) % ([1-9]) n }{$1}gx;
239             }
240              
241 110         285 s/%%/%/g;
242             }
243              
244 110         924 return $format;
245             }
246              
247             sub parse
248             {
249 75     75 1 1811 my($self, $time) = @_;
250              
251 75 100       583 if(my($hour, $min, $sec, $fsec, $ampm) = ($time =~
    50          
252             m{^
253             (\d\d?) # hour
254             (?::(\d\d)(?::(\d\d))?)?(?:\.(\d{0,9})\d*)? # min? sec? nanosec?
255             (?:\s*([aApP]\.?[mM]\.?))? # am/pm
256             $
257             }x))
258             {
259             # Special case to allow times of 24:00:00, which the Postgres
260             # database considers valid (presumably in order to account for
261             # leap seconds)
262 73 100       166 if($hour == 24)
263             {
264 4     4   67 no warnings 'uninitialized';
  4         9  
  4         5689  
265 7 100 100     57 if($min == 0 && $sec == 0 && $fsec == 0)
      100        
266             {
267 4         7 local $Allow_Hour_24 = 1;
268 4         10 $self->hour($hour);
269             }
270             else
271             {
272 3         425 croak "Could not parse time '$time' - an hour value of 24 is only ",
273             "allowed if minutes, seconds, and nanoseconds are all zero"
274             }
275             }
276 66         138 else { $self->hour($hour) }
277              
278 70         140 $self->minute($min);
279 70         136 $self->second($sec);
280 70         130 $self->ampm($ampm);
281              
282 70 100       141 if(defined $fsec)
283             {
284 19         33 my $len = length $fsec;
285              
286 19 100       53 if($len < 9)
    50          
287             {
288 5         17 $fsec .= ('0' x (9 - $len));
289             }
290             elsif($len > 9)
291             {
292 0         0 $fsec = substr($fsec, 0, 9);
293             }
294             }
295              
296 70         119 $self->nanosecond($fsec);
297             }
298             elsif($time eq 'now')
299             {
300 2 100       3 if($Have_HiRes_Time)
301             {
302 1         26 (my $fsecs = Time::HiRes::time()) =~ s/^.*\.//;
303 1         183 return $self->parse(sprintf("%d:%02d:%02d.$fsecs", (localtime(time))[2,1,0]));
304             }
305             else
306             {
307 1         31 return $self->parse(sprintf('%d:%02d:%02d', (localtime(time))[2,1,0]));
308             }
309             }
310             else
311             {
312 0         0 croak "Could not parse time '$time'";
313             }
314              
315 70         192 return $self;
316             }
317              
318             sub as_integer_seconds
319             {
320 605     605 1 637 my($self) = shift;
321              
322 605         990 return ($self->hour * SECONDS_IN_AN_HOUR) +
323             ($self->minute * SECONDS_IN_A_MINUTE) +
324             $self->second;
325             }
326              
327             sub delta_as_integer_seconds
328             {
329 493     493 0 789 my($self, %args) = @_;
330 493   100     3372 return (($args{'hours'} || 0) * SECONDS_IN_AN_HOUR) +
      100        
      100        
331             (($args{'minutes'} || 0) * SECONDS_IN_A_MINUTE) +
332             ($args{'seconds'} || 0);
333             }
334              
335             sub parse_delta
336             {
337 495     495 0 537 my($self) = shift;
338              
339 495 100       894 if(@_ == 1)
340             {
341 8         8 my $delta = shift;
342              
343 8 100       57 if(my($hour, $min, $sec, $fsec) = ($delta =~
344             m{^
345             (\d+) # hours
346             (?::(\d+))? # minutes
347             (?::(\d+))? # seconds
348             (?:\.(\d{0,9})\d*)? # nanoseconds
349             $
350             }x))
351             {
352 6 100       12 if(defined $fsec)
353             {
354 3         6 my $len = length $fsec;
355              
356 3 100       6 if($len < 9)
357             {
358 1         3 $fsec .= ('0' x (9 - $len));
359             }
360              
361 3         6 $fsec = $fsec + 0;
362             }
363              
364             return
365             (
366 6         33 hours => $hour,
367             minutes => $min,
368             seconds => $sec,
369             nanoseconds => $fsec,
370             );
371             }
372 2         308 else { croak "Time delta not understood: $delta" }
373             }
374              
375 487         1092 return @_;
376             }
377              
378             sub add
379             {
380 27     27 1 499 my($self) = shift;
381              
382 27         51 my %args = $self->parse_delta(@_);
383 25         57 my $secs = $self->as_integer_seconds + $self->delta_as_integer_seconds(%args);
384              
385 25 100       70 if(defined $args{'nanoseconds'})
386             {
387 9         12 my $ns_arg = $args{'nanoseconds'};
388 9   100     15 my $nsec = $self->nanosecond || 0;
389              
390 9 100       17 if($ns_arg + $nsec < NANOSECONDS_IN_A_SECOND)
391             {
392 5         18 $self->nanosecond($ns_arg + $nsec);
393             }
394             else
395             {
396 4         9 $secs += int(($ns_arg + $nsec) / NANOSECONDS_IN_A_SECOND);
397 4         11 $self->nanosecond(($ns_arg + $nsec) % NANOSECONDS_IN_A_SECOND);
398             }
399             }
400              
401 25         47 $self->init_with_seconds($secs);
402              
403 25         58 return;
404             }
405              
406             sub subtract
407             {
408 468     468 1 1499 my($self) = shift;
409              
410 468         808 my %args = $self->parse_delta(@_);
411 468         808 my $secs = $self->as_integer_seconds - $self->delta_as_integer_seconds(%args);
412              
413 468 100       1148 if(defined $args{'nanoseconds'})
414             {
415 8         12 my $ns_arg = $args{'nanoseconds'};
416 8   100     13 my $nsec = $self->nanosecond || 0;
417              
418 8 100       19 if($nsec - $ns_arg >= 0)
419             {
420 2         6 $self->nanosecond($nsec - $ns_arg);
421             }
422             else
423             {
424 6 100       16 if(abs($nsec - $ns_arg) >= NANOSECONDS_IN_A_SECOND)
425             {
426 3         7 $secs -= int($ns_arg / NANOSECONDS_IN_A_SECOND);
427             }
428             else
429             {
430 3         4 $secs--;
431             }
432              
433 6         15 $self->nanosecond(($nsec - $ns_arg) % NANOSECONDS_IN_A_SECOND);
434             }
435             }
436              
437 468 100       731 if($secs < 0)
438             {
439 13         15 $secs = $secs % SECONDS_IN_A_CLOCK;
440             }
441              
442 468         756 $self->init_with_seconds($secs);
443              
444 468         941 return;
445             }
446              
447             sub init_with_seconds
448             {
449 493     493 0 565 my($self, $secs) = @_;
450              
451 493 100       908 if($secs >= SECONDS_IN_A_CLOCK)
452             {
453 9         13 $secs = $secs % SECONDS_IN_A_CLOCK;
454             }
455              
456 493 100       735 if($secs >= SECONDS_IN_AN_HOUR)
457             {
458 471         965 $self->hour(int($secs / SECONDS_IN_AN_HOUR));
459 471         904 $secs -= $self->hour * SECONDS_IN_AN_HOUR;
460             }
461 22         39 else { $self->hour(0) }
462              
463 493 100       792 if($secs >= SECONDS_IN_A_MINUTE)
464             {
465 473         1149 $self->minute(int($secs / SECONDS_IN_A_MINUTE));
466 473         804 $secs -= $self->minute * SECONDS_IN_A_MINUTE;
467             }
468 20         32 else { $self->minute(0) }
469              
470 493         851 $self->second($secs);
471              
472 493         560 return;
473             }
474              
475             1;
476              
477             __END__