File Coverage

blib/lib/Time/TAI/Now.pm
Criterion Covered Total %
statement 29 61 47.5
branch 0 14 0.0
condition n/a
subroutine 10 14 71.4
pod 4 4 100.0
total 43 93 46.2


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Time::TAI::Now - determine current time in TAI
4              
5             =head1 SYNOPSIS
6              
7             use Time::TAI::Now qw(
8             now_tai_rat now_tai_gsna now_tai_flt now_tai_dec);
9              
10             ($instant, $bound) = now_tai_rat;
11             ($instant, $bound) = now_tai_rat(1);
12             ($instant, $bound) = now_tai_gsna;
13             ($instant, $bound) = now_tai_gsna(1);
14             ($instant, $bound) = now_tai_flt;
15             ($instant, $bound) = now_tai_flt(1);
16             ($instant, $bound) = now_tai_dec;
17             ($instant, $bound) = now_tai_dec(1);
18              
19             =head1 DESCRIPTION
20              
21             This module is one answer to the question "what time is it?".
22             It determines the current time on the TAI scale, and puts a bound on how
23             inaccurate it could be. It is designed to interoperate with L,
24             which knows all about the TAI time scale.
25              
26             TAI (International Atomic Time) is a time scale produced by an ensemble of
27             atomic clocks around Terra. It attempts to tick at the rate of proper
28             time on the Terran geoid (i.e., at sea level). It is the frequency
29             standard underlying Coordinated Universal Time (UTC).
30              
31             TAI is not connected to planetary rotation, and so has no inherent
32             concept of a "day" or of "time of day". (There is nevertheless a
33             convention for how to represent TAI times using day-based notations,
34             for which see L.) This module represents instants on the
35             TAI time scale as a scalar number of TAI seconds since its epoch, which
36             was at 1958-01-01T00:00:00.0 UT2 as calculated by the United States
37             Naval Observatory. This matches the convention used by C.
38              
39             =cut
40              
41             package Time::TAI::Now;
42              
43 1     1   68350 { use 5.006; }
  1         4  
44 1     1   5 use warnings;
  1         1  
  1         27  
45 1     1   4 use strict;
  1         2  
  1         30  
46              
47 1     1   362 use Data::Float 0.008 qw(significand_step float_parts mult_pow2);
  1         6710  
  1         107  
48 1     1   468 use Math::BigRat 0.10;
  1         67029  
  1         7  
49 1     1   1536 use Math::Decimal 0.000 qw(dec_add);
  1         4212  
  1         71  
50 1     1   452 use Time::UTC 0.007 qw(utc_to_tai);
  1         21227  
  1         113  
51 1     1   466 use Time::UTC::Now 0.012 qw(now_utc_rat now_utc_sna now_utc_flt now_utc_dec);
  1         712  
  1         69  
52              
53             our $VERSION = "0.004";
54              
55 1     1   6 use parent "Exporter";
  1         2  
  1         3  
56             our @EXPORT_OK = qw(now_tai_rat now_tai_gsna now_tai_flt now_tai_dec);
57              
58 1     1   68 use constant BIGRAT_ZERO => Math::BigRat->new(0);
  1         2  
  1         5  
59              
60             =head1 FUNCTIONS
61              
62             Each of these functions determines the current TAI time and returns it.
63             They vary in the form in which the time is returned. In each case,
64             the function returns a list of two values. The first value identifies
65             a current TAI instant, in the form of a number of seconds since the TAI
66             epoch. The second value is an inaccuracy bound, as a number of seconds,
67             or C if no accurate answer could be determined.
68              
69             If an inaccuracy bound is returned then the function is claiming to have
70             answered correctly, to within the specified margin. That is, some instant
71             during the execution of the function is within the specified margin of
72             the instant identified. (This semantic differs from older current-time
73             interfaces that are content to return an instant that has already passed.)
74             The inaccuracy bound describes the actual time represented in the first
75             return value, not some internal value that was rounded to generate the
76             return value.
77              
78             The inaccuracy bound is measured in TAI seconds; that is, in SI seconds
79             on the Terran geoid as realised by atomic clocks. This differs from SI
80             seconds at the computer's location, but the difference is only apparent
81             if the computer hardware is significantly time dilated with respect to
82             the geoid.
83              
84             If C is returned instead of an inaccuracy bound then the function
85             could not find a trustable answer. Either the clock available was not
86             properly synchronised or its accuracy could not be established. Whatever
87             time could be found is returned, but the function makes no claim that it
88             is accurate. It should be treated with suspicion. In practice, clocks
89             of this nature are especially likely to misbehave around UTC leap seconds.
90              
91             Each function will C if it can't find a plausible time at all.
92             If the I parameter is supplied and true then it will
93             also die if it could not find an accurate answer, instead of returning
94             with C for the inaccuracy bound.
95              
96             =over
97              
98             =item now_tai_rat([DEMAND_ACCURACY])
99              
100             Both return values are in the form of C objects.
101              
102             This retains full resolution, is future-proof, and is easy to manipulate,
103             but beware that C is currently rather slow. If performance
104             is a problem then consider using one of the functions below that return
105             the results in other formats.
106              
107             =cut
108              
109             my $rat_last_dayno = BIGRAT_ZERO;
110             my $rat_mn_s = BIGRAT_ZERO;
111              
112             sub now_tai_rat(;$) {
113 0     0 1   my($dayno, $secs, $bound) = now_utc_rat($_[0]);
114 0 0         if($dayno != $rat_last_dayno) {
115 0           $rat_mn_s = utc_to_tai($dayno, BIGRAT_ZERO);
116 0           $rat_last_dayno = $dayno;
117             }
118 0           return ($rat_mn_s + $secs, $bound);
119             }
120              
121             =item now_tai_gsna([DEMAND_ACCURACY])
122              
123             The time since the epoch and the inaccuracy bound (if present) are each
124             returned in the form of a four-element array, giving a high-resolution
125             fixed-point number of seconds. The first element is the integral number
126             of gigaseconds, the second is an integral number of seconds in the range
127             [0, 1000000000), the third is an integral number of nanoseconds in the
128             same range, and the fourth is an integral number of attoseconds in the
129             same range.
130              
131             This form of return value is fairly efficient. It is convenient for
132             decimal output, but awkward to do arithmetic with. Its resolution is
133             adequate for the foreseeable future, but could in principle be obsoleted
134             some day.
135              
136             The number of gigaseconds will exceed 1000000000, thus violating
137             the intent of the number format, one exasecond after the epoch,
138             when the universe is around three times the age it had at the epoch.
139             Terra (and thus TAI) might still exist then, depending on how much
140             its orbital radius increases before Sol enters its red giant phase.
141             In that situation the number of gigaseconds will simply continue to
142             increase, ultimately overflowing if native integer formats don't grow,
143             though it's a good bet that they will.
144              
145             =cut
146              
147             my $gsna_last_dayno = 0;
148             my($gsna_mn_g, $gsna_mn_s) = (0, 0);
149              
150             sub now_tai_gsna(;$) {
151 0     0 1   my($dayno, $secs, $bound) = now_utc_sna($_[0]);
152 0 0         if($dayno != $gsna_last_dayno) {
153 0           my $midnight = utc_to_tai(Math::BigRat->new($dayno),
154             BIGRAT_ZERO);
155 0           $gsna_mn_g = ($midnight / 1000000000)->bfloor->numify;
156 0           $gsna_mn_s = ($midnight % 1000000000)->numify;
157 0           $gsna_last_dayno = $dayno;
158             }
159 0           my($g, $s) = ($gsna_mn_g, $gsna_mn_s);
160 0           $s += $secs->[0];
161 0 0         if($s >= 1000000000) {
162 0           $g++;
163 0           $s -= 1000000000;
164             }
165 0 0         $bound = [ 0, @$bound ] if defined $bound;
166 0           return ([ $g, $s, @{$secs}[1, 2] ], $bound);
  0            
167             }
168              
169             =item now_tai_flt([DEMAND_ACCURACY])
170              
171             Both return values are in the form of Perl floating point numbers.
172              
173             This form of return value is very efficient and easy to manipulate.
174             However, its resolution is limited, rendering it already obsolete for
175             high-precision applications at the time of writing.
176              
177             =cut
178              
179             my $flt_last_dayno = 0;
180             my $flt_mn_s = 0;
181             my $flt_add_bound = 0;
182              
183             sub now_tai_flt(;$) {
184 0     0 1   my($dayno, $secs, $bound) = now_utc_flt($_[0]);
185 0 0         if($dayno != $flt_last_dayno) {
186 0           $flt_mn_s = utc_to_tai(Math::BigRat->new($dayno), BIGRAT_ZERO)
187             ->numify;
188             # Part of the precision of the number of seconds within
189             # the day will be lost due to it being moved down the
190             # significand to line up with the seconds derived from
191             # the day number. Not trusting floating-point rounding,
192             # presume the maximum possible additional error to be 1
193             # ulp of the final value. That's 1 ulp of ($flt_mn_s +
194             # 86400) at the end of the day; possibly 0.5 ulp of that
195             # at the start of the day (if $flt_mn_s is just below an
196             # exponent boundary), but using the larger value all day
197             # will be fine.
198 0           my(undef, $mn_exp, undef) = float_parts($flt_mn_s + 86400.0);
199 0           $flt_add_bound = mult_pow2(significand_step, $mn_exp);
200 0           $flt_last_dayno = $dayno;
201             }
202 0 0         $bound += $flt_add_bound if defined $bound;
203 0           return ($flt_mn_s + $secs, $bound);
204             }
205              
206             =item now_tai_dec([DEMAND_ACCURACY])
207              
208             Each return value is in the form of a string expressing a number
209             as a decimal fraction. These strings are of the type processed
210             by L, and are always returned in L's
211             canonical form.
212              
213             This form of return value is fairly efficient and easy to manipulate.
214             It is convenient both for decimal output and (via implicit coercion to
215             floating point) for low-precision arithmetic. L can be
216             used for high-precision arithmetic. Its resolution is unlimited.
217              
218             =cut
219              
220             my $dec_last_dayno = "0";
221             my $dec_mn_s = "0";
222              
223             sub now_tai_dec(;$) {
224 0     0 1   my($dayno, $secs, $bound) = now_utc_dec($_[0]);
225 0 0         if($dayno ne $dec_last_dayno) {
226 0           $dec_mn_s = utc_to_tai(Math::BigRat->new($dayno), BIGRAT_ZERO)
227             ->bstr;
228 0           $dec_last_dayno = $dayno;
229             }
230 0           return (dec_add($dec_mn_s, $secs), $bound);
231             }
232              
233             =back
234              
235             =head1 SEE ALSO
236              
237             L,
238             L
239              
240             =head1 AUTHOR
241              
242             Andrew Main (Zefram)
243              
244             =head1 COPYRIGHT
245              
246             Copyright (C) 2006, 2009, 2010, 2017
247             Andrew Main (Zefram)
248              
249             =head1 LICENSE
250              
251             This module is free software; you can redistribute it and/or modify it
252             under the same terms as Perl itself.
253              
254             =cut
255              
256             1;