File Coverage

blib/lib/Time/UTC.pm
Criterion Covered Total %
statement 166 184 90.2
branch 67 76 88.1
condition 44 48 91.6
subroutine 33 35 94.2
pod 21 21 100.0
total 331 364 90.9


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