File Coverage

blib/lib/Time/UTC.pm
Criterion Covered Total %
statement 165 183 90.1
branch 67 76 88.1
condition 45 48 93.7
subroutine 33 35 94.2
pod 21 21 100.0
total 331 363 91.1


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Time::UTC - manipulation of UTC in terms of TAI
4              
5             =head1 SYNOPSIS
6              
7             use Time::UTC qw(
8             utc_start_segment
9             foreach_utc_segment_when_complete
10             utc_start_tai_instant utc_start_utc_day
11             utc_segment_of_tai_instant utc_segment_of_utc_day);
12              
13             $seg = utc_start_segment;
14             foreach_utc_segment_when_complete { ... $_[0] ... };
15              
16             $instant = utc_start_tai_instant;
17             $day = utc_start_utc_day;
18              
19             $seg = utc_segment_of_tai_instant($instant);
20             $seg = utc_segment_of_utc_day($day);
21              
22             use Time::UTC qw(
23             utc_day_leap_seconds utc_day_seconds utc_check_instant);
24              
25             $secs = utc_day_leap_seconds($day);
26             $secs = utc_day_seconds($day);
27             utc_check_instant($day, $secs);
28              
29             use Time::UTC qw(tai_to_utc utc_to_tai);
30              
31             ($day, $secs) = tai_to_utc($instant);
32             $instant = utc_to_tai($day, $secs);
33              
34             use Time::UTC qw(
35             utc_secs_to_hms utc_hms_to_secs
36             utc_day_to_ymd utc_ymd_to_day
37             utc_instant_to_ymdhms utc_ymdhms_to_instant);
38              
39             ($hr, $mi, $sc) = utc_secs_to_hms($secs);
40             $secs = utc_hms_to_secs($hr, $mi, $sc);
41              
42             ($yr, $mo, $dy) = utc_day_to_ymd($day);
43             $day = utc_ymd_to_day($yr, $mo, $dy);
44              
45             ($yr, $mo, $dy, $hr, $mi, $sc) =
46             utc_instant_to_ymdhms($day, $secs);
47             ($day, $secs) = utc_ymdhms_to_instant(
48             $yr, $mo, $dy, $hr, $mi, $sc);
49              
50             use Time::UTC qw(
51             utc_day_to_mjdn utc_mjdn_to_day
52             utc_day_to_cjdn utc_cjdn_to_day);
53              
54             $mjdn = utc_day_to_mjdn($day);
55             $day = utc_mjdn_to_day($mjdn);
56              
57             $cjdn = utc_day_to_cjdn($day);
58             $day = utc_cjdn_to_day($cjdn);
59              
60             =head1 DESCRIPTION
61              
62             This module encapsulates knowledge about the structure of the UTC time
63             scale, including the leap seconds of the current incarnation. This
64             information is useful in manipulating times stored in a UTC-based format,
65             or in converting between UTC and TAI (the underlying atomic time scale).
66             It automatically downloads new UTC data as required to keep up to date.
67             This is a low-level module, intended for use by other modules that need
68             to know about UTC. This module aims to be comprehensive and rigorous.
69              
70             =head1 HISTORY OF UTC
71              
72             Until the middle of the twentieth century, the passage of time was
73             measured primarily against the astronomical motions of the Earth and
74             other bodies. These motions are very regular, and indeed were the
75             most temporally regular phenomena available to pre-industrial society.
76             After the invention of the caesium-based atomic clock, a gradual
77             transition from astronomic to atomic timekeeping began. The hyperfine
78             transition of caesium is more regular than the Earth's motion, and so
79             makes a better time standard. Unfortunately, this means that during the
80             transition phase there are two disagreeing time standards in use, and we
81             must jump through hoops to accommodate both. UTC is one of these hoops.
82              
83             =head2 Solar timekeeping
84              
85             Each revolution of the Earth relative to the Sun (i.e., each day) has
86             traditionally been divided into units of hours, minutes, and seconds.
87             These are defined such that there are exactly 86400 seconds in a day.
88             Since these units are measuring the rotation of the Earth, rather than
89             the passage of time per se, it makes more sense to view these as measures
90             of I than of time. Thus, the hour refers to a rotation of exactly
91             15 degrees, regardless of how much time that rotation takes, and so on.
92              
93             Because the Earth's rotation is non-uniform, each day is a slightly
94             different length, and so the duration of the second, as defined above,
95             also varies over time. This is not good in a time standard. In order
96             to make the time as stable as possible, the non-uniformities of the
97             Earth's rotation need to be accounted for. The use of I
98             time> rather than I smooths out variation in the
99             apparent daily motion of the Sun over the course of the year that are
100             due to the non-circularity of the Earth's orbit. The mean solar time
101             at Greenwich is known as I, and specifically as I.
102             I, I, and I are smoothed versions of Universal Time,
103             removing periodic seasonal and tidal variations.
104              
105             But however smoothed these scales get, they remain fundamentally measures
106             of angle rather than time. They are not uniform over time.
107              
108             =head2 Atomic timekeeping
109              
110             It has been long recognised that the Earth's rotation is non-uniform,
111             and so that the scales based on the Earth's rotation are not stable
112             measures of time. Scientists have therefore defined units of time that
113             are unrelated to the Earth's current motions. Confusingly, the unit
114             so defined is called the "second", and is arranged to have a duration
115             similar to that of the traditional angle-based second, despite being
116             fundamentally different in intent.
117              
118             The second in this sense was originally defined as 1/86400 of the mean
119             duration of a solar day. In 1956 the second was redefined in terms of the
120             length of the tropical year 1900 (the "ephemeris second"), in recognition
121             of the non-uniformity of the Earth's rotation. This definition was
122             superseded in 1967 by a definition based on the hyperfine transition
123             of caesium, following a decade of experience with early caesium clocks.
124             That definition was refined in 1997, and further refinements may happen
125             in the future.
126              
127             The important aspects of atomic timekeeping, for our purposes, are that
128             it is more stable than the Earth's spin; it is independent of the Earth's
129             current spin; and it confusingly uses much of the same terminology as
130             measurement of the Earth's spin.
131              
132             =head2 TAI
133              
134             Time started to be measured using atomic clocks in 1955, and the first
135             formal atomic time scale started at the beginning of 1958. In 1961
136             an international effort constructed a new time scale, synchronised
137             with the first one, which eventually (in 1971) came to be known as
138             I or I. TAI is strictly a measure of
139             time as determined by atomic clocks, and is entirely independent of
140             the Earth's daily revolutions. However, it uses the terminology and
141             superficial appearance of the time scales that went before it, which is
142             to say the angle scales. Thus a point on the TAI scale is conventionally
143             referred to by specifying a date and a time of day, the latter composed
144             of hours, minutes, and seconds.
145              
146             Like the pure measures of rotation, TAI has exactly 86400 seconds per day.
147             Completely unlike those measures, TAI's seconds are, as far as possible,
148             of identical duration, the duration with which the second was defined
149             in 1967. TAI, through its predecessor atomic time scale, was initially
150             synchronised with Universal Time, so that TAI and UT2 describe the same
151             instant as 1958-01-01T00:00:00.0 (at least, according to the United States
152             Naval Observatory's determination of UT2). TAI now runs independently
153             of UT, and at the time of writing (early 2005) TAI is about 32.5 seconds
154             ahead of UT1.
155              
156             =head2 UTC
157              
158             Over the long term, the world is switching from basing civil time on UT1
159             (i.e., the revolution of the Earth) to basing civil time on TAI (i.e.,
160             atomic clocks). In the short term, however, a clean switch is not such
161             a popular idea. There is a demand for a hybrid system which is based
162             on atomic clocks but which also maintains synchronisation with the
163             Earth's spin. UTC is that system.
164              
165             UTC is defined in terms of TAI, and is in that sense an atomic time
166             standard. However, the relation between UTC and TAI is determined only
167             a few months in advance. The relation changes over time, so that UTC
168             remains an approximation of UT1.
169              
170             This concept behind UTC originates with the WWV radio time signal station
171             in the USA. Up until 1956 it had, like all time signal stations at
172             the time, transmitted the closest achievable approximation of UT1.
173             In 1956, with atomic clocks now available, the National Bureau of
174             Standards started to base WWV's signals on atomic frequency standards.
175             Rather than continuously adjust the frequency to track UT1, as had been
176             previously done, they set the frequency once to match the rate of UT1
177             and then let it diverge by accurately maintaining the same frequency.
178             When the divergence grew too large, the time signals were stepped by 20
179             ms at a time to keep the magnitude of the difference within chosen limits.
180              
181             This new system, deliberately accepting a visible difference between
182             signalled time and astronomical time, was initially controversial, but
183             soon caught on. Other time signal stations operated by other bodies,
184             such as the National Physical Laboratory in the UK, started to use the
185             same type of scheme. This raised the problem of keeping the time signals
186             synchronised, so international agreement became necessary.
187              
188             In 1960, with the frequency of the caesium hyperfine transition now
189             established (though it did not become the SI standard until 1967),
190             a frequency offset for time signals was internationally agreed,
191             chosen to match the then-current rate of UT2. It was decided that
192             the International Time Bureau (BIH, Bureau International de l'Heure)
193             would henceforth determine what frequency offset to use, changing it if
194             necessary at each year end, and also coordinate the necessary time steps
195             to closely approximate UT2. Thus was international synchronisation of
196             time signals achieved.
197              
198             From the beginning of 1961 this system was formalised as Coordinated
199             Universal Time (UTC). Time steps, both forward and backward, were always
200             introduced at midnight, achieved by making a UTC day have a length other
201             than 86400 UTC seconds. The time steps of 20 ms having been found to be
202             inconveniently frequent, it was decided to use steps of 50 ms instead.
203             This was soon increased to 100 ms. This arrangement lasted until the
204             end of 1971.
205              
206             The frequency offsets, which when correctly chosen avoided the need for
207             many time steps, were found to be inconvenient. Radio time signals
208             commonly provided per-second pulses that were phase-locked to the
209             carrier signal, and maintaining that relation meant that the frequency
210             offset to make atomic time match UT2 had to be applied to the carrier
211             frequency also. This made the carrier unreliable as a frequency standard,
212             which was a secondary use made of it.
213              
214             To maintain the utility of time signals as frequency standards, from
215             the beginning of 1972 the frequency offset was permanently set to zero.
216             Henceforth the UTC second is identical in duration to the TAI second.
217             The size of the time steps was increased again, to one second, to make the
218             steps less frequent and to avoid phase shifts in per-second pulse signals.
219             An irregular time step was used to bring UTC to an integral number of
220             seconds offset from TAI, where it henceforth remains.
221              
222             Because of the zero frequency offset, the new form of UTC has only had
223             backward jumps (by having an 86401 s UTC day). Forward jumps are also
224             theoretically possible, but unlikely to ever occur.
225              
226             Notice that the new form of UTC is more similar to TAI than the old
227             form was. This appears to be part of the gradual switch from solar
228             time to atomic time. It has been proposed (controversially) that in
229             the near future the system of irregularities in UTC will terminate,
230             resulting in a purely atomic time scale.
231              
232             =head1 STRUCTURE OF UTC
233              
234             UTC is a time scale derived from TAI. UTC divides time up into days,
235             and each day into seconds. Most UTC days are exactly 86400 UTC seconds
236             long, but they can be up to a second shorter or longer. The UTC second
237             is in general slightly different from the TAI second; it stays stable
238             most of the time, occasionally undergoing an instantaneous change.
239             Since 1972 the UTC second has been equal to the TAI second, and it will
240             remain so in the future.
241              
242             The details of the day lengths, and until 1972 the length of the UTC
243             second, are published by the International Earth Rotation and Reference
244             Systems Service (IERS). They are announced only a few months in advance,
245             so it is not possible to convert between TAI and UTC for times more than
246             a few months ahead.
247              
248             UTC is not defined for dates prior to 1961.
249              
250             =cut
251              
252             package Time::UTC;
253              
254 11     11   775555 { use 5.006; }
  11         46  
255 11     11   67 use warnings;
  11         25  
  11         354  
256 11     11   61 use strict;
  11         33  
  11         383  
257              
258 11     11   75 use Carp qw(croak);
  11         25  
  11         683  
259 11     11   8433 use Math::BigRat 0.08;
  11         904350  
  11         76  
260 11     11   19045 use Time::UTC::Segment 0.007;
  11         604  
  11         722  
261              
262             our $VERSION = "0.009";
263              
264 11     11   117 use parent "Exporter";
  11         29  
  11         88  
265             our @EXPORT_OK = qw(
266             utc_start_segment foreach_utc_segment_when_complete
267             utc_start_tai_instant utc_start_utc_day
268             utc_segment_of_utc_day utc_segment_of_tai_instant
269             utc_day_leap_seconds utc_day_seconds utc_check_instant
270             tai_to_utc utc_to_tai
271             utc_secs_to_hms utc_hms_to_secs utc_day_to_ymd utc_ymd_to_day
272             utc_instant_to_ymdhms utc_ymdhms_to_instant
273             utc_day_to_mjdn utc_mjdn_to_day
274             utc_day_to_cjdn utc_cjdn_to_day
275             );
276              
277             =head1 FUNCTIONS
278              
279             Because UTC is defined purely in terms of TAI, these interfaces make
280             frequent use of the TAI epoch, 1958-01-01T00:00:00.0. Instants on the
281             TAI scale are identified by a scalar number of TAI seconds since the TAI
282             epoch; this is a perfectly linear scale with no discontinuities. The TAI
283             seconds count can be trivially split into the conventional units of days,
284             hours, minutes, and seconds for display (TAI days contain exactly 86400
285             TAI seconds each).
286              
287             Because UTC days have differing lengths, instants on the UTC scale are
288             identified by the combination of an integral number of days since the
289             TAI epoch and a number of UTC seconds since midnight within the day.
290             In some interfaces the day number is used alone. The conversion of
291             the number of seconds within a day into hours, minutes, and seconds for
292             display is idiosyncratic; the function C handles this.
293              
294             All numbers in this API are C objects. All numeric function
295             arguments must be Cs, and all numeric values returned are
296             likewise Cs.
297              
298             =head2 Description of UTC
299              
300             This module contains a machine-manipulable description of the relation
301             between UTC and TAI. Most users of this module do not need to examine
302             this directly, and will be better served by the higher-level functions
303             described later. However, users with unusual requirements have access
304             to the description if necessary. The functions in this section deal
305             with this.
306              
307             The internal description is composed of C objects.
308             Each segment object describes a period of time over which the relation
309             between UTC and TAI is stable. See L for details of
310             how to use these objects. More segments can appear during the course
311             of a program's execution: updated UTC data is automatically downloaded
312             as required.
313              
314             =over
315              
316             =item utc_start_segment
317              
318             Returns the first segment of the UTC description. The later segments can
319             be accessed from the first one. This function is intended for programs
320             that will walk through the entire description.
321              
322             =cut
323              
324 40     40 1 4311 sub utc_start_segment() { Time::UTC::Segment->start }
325              
326             =item foreach_utc_segment_when_complete(WHAT)
327              
328             =item foreach_utc_segment_when_complete BLOCK
329              
330             I must be a reference to a function which takes one argument;
331             it may be specified as a bare BLOCK in the function call. The function
332             is called for each segment of the UTC description in turn, passing the
333             segment as an argument to the function. This call takes place, for each
334             segment, when it is complete, as described in L.
335             The function is immediately called for already-complete segments.
336              
337             To do this for only one segment, see the C method on
338             C.
339              
340             =cut
341              
342             sub foreach_utc_segment_when_complete(&) {
343 23     23 1 71 my($what) = @_;
344 23         49 my $setup_for_segment;
345             $setup_for_segment = sub($) {
346 392     392   854 my($seg) = @_;
347             $seg->when_complete(sub() {
348 369         590 eval { local $SIG{__DIE__}; $what->($seg); };
  369         1282  
  369         1207  
349 369         973 $setup_for_segment->($seg->next);
350 392         2764 });
351 23         86 };
352 23         59 $setup_for_segment->(utc_start_segment());
353             }
354              
355             my @segments = (utc_start_segment());
356             foreach_utc_segment_when_complete {
357             push @segments, $_[0]->next;
358             };
359              
360             =item utc_start_tai_instant
361              
362             Identifies the instant at which the UTC service started. This instant
363             was the start of the first UTC day.
364              
365             =cut
366              
367 1     1 1 87 sub utc_start_tai_instant() { $segments[0]->start_tai_instant }
368              
369             =item utc_start_utc_day
370              
371             Identifies the first day of UTC service.
372              
373             =cut
374              
375 1     1 1 10 sub utc_start_utc_day() { $segments[0]->start_utc_day }
376              
377             =item utc_segment_of_tai_instant(INSTANT)
378              
379             Returns the segment of the UTC description that pertains to the specified
380             TAI instant. Cs if the specified instant precedes the start of
381             UTC or if the relevant segment hasn't been defined yet.
382              
383             =cut
384              
385             sub utc_segment_of_tai_instant($) {
386 85     85 1 48022 my($instant) = @_;
387 85         142 my $min = 0;
388 85         140 TRY_AGAIN:
389             my $final = @segments - 1;
390 85         152 my $max = $final;
391 85         224 while($max > $min + 1) {
392 11     11   4157 use integer;
  11         25  
  11         63  
393 458         681 my $try = ($min + $max) / 2;
394 458 100       1032 if($instant >= $segments[$try]->start_tai_instant) {
395 254         26845 $min = $try;
396             } else {
397 204         21201 $max = $try;
398             }
399             }
400 85 100 100     251 if($min == 0 && $instant < $segments[0]->start_tai_instant) {
401 1         165 croak "instant $instant precedes the start of UTC";
402             }
403 84 100 100     429 if($max == $final &&
404             $instant >= $segments[$final]->start_tai_instant) {
405 2         187 eval { local $SIG{__DIE__}; $segments[$final]->next; };
  2         6  
  2         6  
406 2 50       45 goto TRY_AGAIN if @segments != $final + 1;
407 2         7 croak "instant $instant has no UTC definition yet";
408             }
409 82         505 return $segments[$min];
410             }
411              
412             =item utc_segment_of_utc_day(DAY)
413              
414             Returns the segment of the UTC description that pertains to the specified
415             day number. Cs if the specified day precedes the start of UTC or
416             if the relevant segment hasn't been defined yet.
417              
418             =cut
419              
420             sub utc_segment_of_utc_day($) {
421 85     85 1 2177 my($day) = @_;
422 85 50       297 croak "non-integer day $day is invalid" unless $day->is_int;
423 85         1462 my $min = 0;
424 86         177 TRY_AGAIN:
425             my $final = @segments - 1;
426 86         132 my $max = $final;
427 86         208 while($max > $min + 1) {
428 11     11   2582 use integer;
  11         124  
  11         50  
429 458         686 my $try = ($min + $max) / 2;
430 458 100       1047 if($day >= $segments[$try]->start_utc_day) {
431 254         16361 $min = $try;
432             } else {
433 204         12604 $max = $try;
434             }
435             }
436 86 100 100     238 if($min == 0 && $day < $segments[0]->start_utc_day) {
437 1         87 croak "day $day precedes the start of UTC";
438             }
439 85 100 100     440 if($max == $final && $day >= $segments[$final]->start_utc_day) {
440 3         197 eval { local $SIG{__DIE__}; $segments[$final]->next; };
  3         11  
  3         10  
441 3 100       64 goto TRY_AGAIN if @segments != $final + 1;
442 2         8 croak "day $day has no UTC definition yet";
443             }
444 82         424 return $segments[$min];
445             }
446              
447             =back
448              
449             =head2 Shape of UTC
450              
451             =over
452              
453             =item utc_day_leap_seconds(DAY)
454              
455             Returns the number of extra UTC seconds inserted at the end of the day
456             specified by number. The number is returned as a C and
457             may be negative. Cs if the specified day precedes the start of
458             UTC or if UTC for the day has not yet been defined.
459              
460             =item utc_day_seconds(DAY)
461              
462             Returns the length, in UTC seconds, of the day specified by number.
463             The number is returned as a C. Cs if the specified day
464             precedes the start of UTC or if UTC for the day has not yet been defined.
465              
466             =cut
467              
468             {
469             my $bigrat_0 = Math::BigRat->new(0);
470             my $bigrat_86400 = Math::BigRat->new(86400);
471             my $end_day = $segments[0]->start_utc_day;
472             my(%day_leap_seconds, %day_seconds);
473             foreach_utc_segment_when_complete {
474             my($seg) = @_;
475             my $day = $seg->last_utc_day;
476             $day = "$day";
477             my $ls = $seg->leap_utc_seconds;
478             $day_leap_seconds{$day} = $ls;
479             $day_seconds{$day} = $bigrat_86400 + $ls;
480             $end_day = $seg->end_utc_day;
481             };
482             sub _utc_day_value($$$) {
483 667     667   1311 my($day, $hash, $default) = @_;
484 667 50       2150 croak "non-integer day $day is invalid" unless $day->is_int;
485 667 100       10709 croak "day $day precedes the start of UTC"
486             if $day < $segments[0]->start_utc_day;
487 664 100       54101 if($day >= $end_day) {
488 8         447 eval { local $SIG{__DIE__}; $segments[-1]->next; };
  8         27  
  8         31  
489 8 100       158 if($day >= $end_day) {
490 6         414 croak "day $day has no UTC definition yet";
491             }
492             }
493 658         49930 my $val = $hash->{$day};
494 658 100       20646 return defined($val) ? $val : $default;
495             }
496             sub utc_day_leap_seconds($) {
497 167     167 1 83421 my($day) = @_;
498 167         572 return _utc_day_value($day, \%day_leap_seconds, $bigrat_0);
499             }
500             sub utc_day_seconds($) {
501 500     500 1 83247 my($day) = @_;
502 500         1243 return _utc_day_value($day, \%day_seconds, $bigrat_86400);
503             }
504             }
505              
506             =item utc_check_instant(DAY, SECS)
507              
508             Checks that a day/seconds combination is valid. Cs if UTC is not
509             defined for the specified day or if the number of seconds is out of
510             range for that day.
511              
512             =cut
513              
514             sub utc_check_instant($$) {
515 331     331 1 200255 my($day, $secs) = @_;
516 331         771 my $day_len = utc_day_seconds($day);
517 328 100 100     2192 croak "$secs seconds is out of range for a $day_len second day"
518             if $secs->is_negative || $secs >= $day_len;
519             }
520              
521             =back
522              
523             =head2 Conversion between UTC and TAI
524              
525             =over
526              
527             =item tai_to_utc(INSTANT)
528              
529             Translates a TAI instant into UTC. The function returns a list of two
530             values: the integral number of days since the TAI epoch and the number
531             of UTC seconds within the day. Cs if the specified instant precedes
532             the start of UTC or if UTC is not yet defined for the instant.
533              
534             =cut
535              
536             sub tai_to_utc($) {
537 0     0 1 0 my($instant) = @_;
538 0         0 my $seg = utc_segment_of_tai_instant($instant);
539 0         0 my $tai_offset = $instant - $seg->start_tai_instant;
540 0         0 my $utc_offset = $tai_offset / $seg->utc_second_length;
541 0         0 my $day_offset = ($utc_offset / 86400)->bfloor;
542 0         0 my $secs = $utc_offset % 86400;
543 0         0 my $day = $seg->start_utc_day + $day_offset;
544 0 0       0 if($day == $seg->end_utc_day) {
545 0         0 $day--;
546 0         0 $secs += 86400;
547             }
548 0         0 return ($day, $secs);
549             }
550              
551             =item utc_to_tai(DAY, SECS)
552              
553             Translates a UTC instant into TAI. Cs if the specified instant
554             precedes the start of UTC or if UTC is not yet defined for the instant,
555             or if the number of seconds is out of range for the day.
556              
557             =cut
558              
559             sub utc_to_tai($$) {
560 0     0 1 0 my($day, $secs) = @_;
561 0         0 my $seg = utc_segment_of_utc_day($day);
562 0 0       0 my $day_len = $day == $seg->last_utc_day ?
563             $seg->last_day_utc_seconds : 86400;
564 0 0 0     0 croak "$secs seconds is out of range for a $day_len second day"
565             if $secs->is_negative || $secs >= $day_len;
566 0         0 my $utc_offset = ($day - $seg->start_utc_day) * 86400 + $secs;
567 0         0 my $tai_offset = $utc_offset * $seg->utc_second_length;
568 0         0 return $seg->start_tai_instant + $tai_offset;
569             }
570              
571             =back
572              
573             =head2 Display formatting
574              
575             =over
576              
577             =item utc_secs_to_hms(SECS)
578              
579             When a UTC day is longer than 86400 seconds, it is divided into hours
580             and minutes in an idiosyncratic manner. Rather than times more than
581             86400 seconds after midnight being displayed as 24 hours and a fraction
582             of a second, they are displayed as 23 hours, 59 minutes, and more than
583             60 seconds. This means that each UTC day contains the usual 1440 minutes;
584             where leap seconds occur, the last minute of the day has a non-standard
585             length. This arrangement is essential to make timezones work with UTC.
586              
587             This function takes a number of seconds since midnight and returns a list
588             of hours, minutes, and seconds values, in the UTC manner. It Cs
589             if given a negative number of seconds. It places no upper limit on the
590             number of seconds, because the length of UTC days varies.
591              
592             =cut
593              
594             {
595             my $bigrat_23 = Math::BigRat->new(23);
596             my $bigrat_59 = Math::BigRat->new(59);
597              
598             sub utc_secs_to_hms($) {
599 7     7 1 26723 my($secs) = @_;
600 7 100       60 croak "can't have negative seconds in a day"
601             if $secs->is_negative;
602 6 100       247 if($secs >= 86400-60) {
603 3         1873 return ($bigrat_23, $bigrat_59, $secs - (86400-60));
604             } else {
605 3         1959 return (($secs / 3600)->bfloor,
606             (($secs % 3600) / 60)->bfloor,
607             $secs % 60);
608             }
609             }
610             }
611              
612             =item utc_hms_to_secs(HR, MI, SC)
613              
614             This performs the reverse of the translation that C does.
615             It takes numbers of hours, minutes, and seconds, and returns the number of
616             seconds since midnight. It Cs if the numbers provided are invalid.
617             It does not impose an upper limit on the time that may be specified,
618             because the length of UTC days varies.
619              
620             =cut
621              
622             sub utc_hms_to_secs($$$) {
623 18     18 1 49572 my($hr, $mi, $sc) = @_;
624 18 100 100     78 croak "invalid hour number $hr"
      100        
625             unless $hr->is_int && !$hr->is_negative && $hr < 24;
626 15 100 100     10349 croak "invalid minute number $mi"
      100        
627             unless $mi->is_int && !$mi->is_negative && $mi < 60;
628 9 100 100     6174 croak "invalid second number $sc"
      100        
629             unless !$sc->is_negative &&
630             (($hr == 23 && $mi == 59) || $sc < 60);
631 6         7812 return 3600*$hr + 60*$mi + $sc;
632             }
633              
634             =item utc_day_to_ymd(DAY)
635              
636             Although UTC is compatible with any means of labelling days, and the
637             scalar day numbering used in this API can be readily converted into
638             whatever form is required, it is conventional to label UTC days using the
639             Gregorian calendar. Even when using some other calendar, the Gregorian
640             calendar may be a convenient intermediate form, because of its prevalence.
641              
642             This function takes a number of days since the TAI epoch and returns a
643             list of a year, month, and day, in the Gregorian calendar. It places no
644             bounds on the permissible day numbers; it is not limited to days for which
645             UTC is defined. All year numbers generated are in the Common Era, and
646             may be zero or negative if a sufficiently negative day number is supplied.
647              
648             =cut
649              
650             {
651             my @nonleap_monthstarts =
652             (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365);
653             my @leap_monthstarts =
654             (0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366);
655             sub _monthstarts($) {
656 49     49   125 my($yr) = @_;
657 49 100       189 my $isleap = $yr % 4 == 0 ? $yr % 100 == 0 ?
    100          
    100          
658             $yr % 400 == 0 ? 1 : 0 : 1 : 0;
659 49 100       125260 return $isleap ? \@leap_monthstarts : \@nonleap_monthstarts;
660             }
661             }
662              
663             sub utc_day_to_ymd($) {
664 18     18 1 48313 my($day) = @_;
665 18 100       91 croak "non-integer day $day is invalid" unless $day->is_int;
666 17         321 $day += 365*358 + 87;
667 17         12287 my $qcents = ($day / (365*400 + 97))->bfloor;
668 17         15216 $day -= $qcents * (365*400 + 97);
669 17         13810 my $yr = ($day / 366)->bfloor;
670 17         14604 my $leaps = (($yr + 3) / 4)->bfloor;
671 17 100       26619 $leaps -= (($leaps - 1) / 25)->bfloor unless $leaps->is_zero;
672 17         26167 $day -= 365 * $yr + $leaps;
673 17         17012 my $monthstarts = _monthstarts($yr);
674 17 100       78 if($day >= $monthstarts->[12]) {
675 12         7155 $day -= $monthstarts->[12];
676 12         8020 $yr++;
677 12         1175 $monthstarts = _monthstarts($yr);
678             }
679 17         3485 my $mo = 1;
680 17         64 while($day >= $monthstarts->[$mo]) {
681 42         25924 $mo++;
682             }
683 17         10926 my $dy = Math::BigRat->new(1 + $day - $monthstarts->[$mo - 1]);
684 17         27793 return (1600 + $qcents * 400 + $yr, Math::BigRat->new($mo), $dy);
685             }
686              
687             =item utc_ymd_to_day(YR, MO, DY)
688              
689             This performs the reverse of the translation that C does.
690             It takes year, month, and day numbers, and returns the number of days
691             since the TAI epoch. It Cs if the numbers provided are invalid.
692             It does not impose any limit on the range of years.
693              
694             =cut
695              
696             sub utc_ymd_to_day($$$) {
697 24     24 1 74021 my($yr, $mo, $dy) = @_;
698 24 100       128 croak "invalid year number $yr"
699             unless $yr->is_int;
700 23 100 100     444 croak "invalid month number $mo"
      100        
701             unless $mo->is_int && $mo >= 1 && $mo <= 12;
702 20         26716 $mo = $mo->numify;
703 20         479 my $monthstarts = _monthstarts($yr);
704 20 100 100     83 croak "invalid day number $dy"
      100        
705             unless $dy->is_int && $dy >= 1 &&
706             $dy <= $monthstarts->[$mo] - $monthstarts->[$mo - 1];
707 17         22097 $yr -= 1600;
708 17         13401 my $qcents = ($yr / 400)->bfloor;
709 17         14683 my $day = Math::BigRat->new(-(365*358 + 87)) +
710             $qcents * (365*400 + 97);
711 17         22655 $yr -= $qcents * 400;
712 17         13967 $day += 365 * $yr;
713 17         15762 my $leaps = (($yr + 3) / 4)->bfloor;
714 17 100       27576 $leaps -= (($leaps - 1) / 25)->bfloor unless $leaps->is_zero;
715 17         29222 $day += $leaps;
716 17         2612 $day += $monthstarts->[$mo - 1];
717 17         13765 $day += $dy - 1;
718 17         15463 return $day;
719             }
720              
721             =item utc_instant_to_ymdhms(DAY, SECS)
722              
723             =item utc_ymdhms_to_instant(YR, MO, DY, HR, MI, SC)
724              
725             As a convenience, these two functions package together the corresponding
726             pairs of display formatting functions described above. They do nothing
727             extra that the underlying functions do not; they do not check that the
728             instant specified is actually a valid UTC time.
729              
730             =cut
731              
732             sub utc_instant_to_ymdhms($$) {
733 1     1 1 1930 my($day, $secs) = @_;
734 1         5 return (utc_day_to_ymd($day), utc_secs_to_hms($secs));
735             }
736              
737             sub utc_ymdhms_to_instant($$$$$$) {
738 1     1 1 12672 my($yr, $mo, $dy, $hr, $mi, $sc) = @_;
739 1         7 return (utc_ymd_to_day($yr, $mo, $dy), utc_hms_to_secs($hr, $mi, $sc));
740             }
741              
742             =back
743              
744             =head2 Calendar conversion
745              
746             =over
747              
748             =item utc_day_to_mjdn(DAY)
749              
750             This function takes a number of days since the TAI epoch and returns
751             the corresponding Modified Julian Day Number (a number of days since
752             1858-11-17 UT). MJDN is a standard numbering for days in Universal Time.
753             There is no bound on the permissible day numbers; the function is not
754             limited to days for which UTC is defined.
755              
756             =cut
757              
758 11     11   16285 use constant _TAI_EPOCH_MJDN => Math::BigRat->new(36204);
  11         26  
  11         58  
759              
760             sub utc_day_to_mjdn($) {
761 4     4 1 6113 my($day) = @_;
762 4 100       11 croak "non-integer day $day is invalid" unless $day->is_int;
763 3         38 return _TAI_EPOCH_MJDN + $day;
764             }
765              
766             =item utc_mjdn_to_day(MJDN)
767              
768             This performs the reverse of the translation that C does.
769             It takes a Modified Julian Day Number and returns the number of days
770             since the TAI epoch. It does not impose any limit on the range.
771              
772             =cut
773              
774             sub utc_mjdn_to_day($) {
775 4     4 1 3472 my($mjdn) = @_;
776 4 100       9 croak "invalid MJDN $mjdn" unless $mjdn->is_int;
777 3         42 return $mjdn - _TAI_EPOCH_MJDN;
778             }
779              
780             =item utc_day_to_cjdn(DAY)
781              
782             This function takes a number of days since the TAI epoch and returns
783             the corresponding Chronological Julian Day Number (a number of days
784             since -4713-11-24). CJDN is a standard day numbering that is useful as
785             an interchange format between implementations of different calendars.
786             There is no bound on the permissible day numbers; the function is not
787             limited to days for which UTC is defined.
788              
789             =cut
790              
791 11     11   9424 use constant _TAI_EPOCH_CJDN => Math::BigRat->new(2436205);
  11         27  
  11         42  
792              
793             sub utc_day_to_cjdn($) {
794 4     4 1 5958 my($day) = @_;
795 4 100       14 croak "non-integer day $day is invalid" unless $day->is_int;
796 3         40 return _TAI_EPOCH_CJDN + $day;
797             }
798              
799             =item utc_cjdn_to_day(CJDN)
800              
801             This performs the reverse of the translation that C does.
802             It takes a Chronological Julian Day Number and returns the number of
803             days since the TAI epoch. It does not impose any limit on the range.
804              
805             =cut
806              
807             sub utc_cjdn_to_day($) {
808 4     4 1 3608 my($cjdn) = @_;
809 4 100       12 croak "invalid CJDN $cjdn" unless $cjdn->is_int;
810 3         43 return $cjdn - _TAI_EPOCH_CJDN;
811             }
812              
813             =back
814              
815             =head1 SEE ALSO
816              
817             L,
818             L,
819             L,
820             L,
821             L,
822             L
823              
824             =head1 AUTHOR
825              
826             Andrew Main (Zefram)
827              
828             =head1 COPYRIGHT
829              
830             Copyright (C) 2005, 2006, 2007, 2009, 2010, 2012, 2017
831             Andrew Main (Zefram)
832              
833             =head1 LICENSE
834              
835             This module is free software; you can redistribute it and/or modify it
836             under the same terms as Perl itself.
837              
838             =cut
839              
840             1;