File Coverage

blib/lib/Time/TAI/Now.pm
Criterion Covered Total %
statement 27 54 50.0
branch 0 12 0.0
condition n/a
subroutine 9 12 75.0
pod 3 3 100.0
total 39 81 48.1


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(now_tai_rat now_tai_gsna now_tai_flt);
8              
9             ($instant, $bound) = now_tai_rat;
10             ($instant, $bound) = now_tai_rat(1);
11             ($instant, $bound) = now_tai_gsna;
12             ($instant, $bound) = now_tai_gsna(1);
13             ($instant, $bound) = now_tai_flt;
14             ($instant, $bound) = now_tai_flt(1);
15              
16             =head1 DESCRIPTION
17              
18             This module is one answer to the question "what time is it?".
19             It determines the current time on the TAI scale, and puts a bound on how
20             inaccurate it could be. It is designed to interoperate with L,
21             which knows all about the TAI time scale.
22              
23             TAI (International Atomic Time) is a time scale produced by an ensemble of
24             atomic clocks around Terra. It attempts to tick at the rate of proper
25             time on the Terran geoid (i.e., at sea level). It is the frequency
26             standard underlying Coordinated Universal Time (UTC).
27              
28             TAI is not connected to planetary rotation, and so has no inherent
29             concept of a "day" or of "time of day". (There is nevertheless a
30             convention for how to represent TAI times using day-based notations,
31             for which see L.) This module represents instants on the
32             TAI time scale as a scalar number of TAI seconds since its epoch, which
33             was at 1958-01-01T00:00:00.0 UT2 as calculated by the United States
34             Naval Observatory. This matches the convention used by C.
35              
36             =cut
37              
38             package Time::TAI::Now;
39              
40 1     1   20790 { use 5.006; }
  1         3  
  1         36  
41 1     1   5 use warnings;
  1         1  
  1         30  
42 1     1   5 use strict;
  1         1  
  1         42  
43              
44 1     1   904 use Data::Float 0.008 qw(significand_step float_parts mult_pow2);
  1         9463  
  1         109  
45 1     1   904 use Math::BigRat 0.10;
  1         78560  
  1         6  
46 1     1   2048 use Time::UTC 0.005 qw(utc_to_tai);
  1         129003  
  1         109  
47 1     1   1158 use Time::UTC::Now 0.007 qw(now_utc_rat now_utc_sna now_utc_flt);
  1         1282  
  1         104  
48              
49             our $VERSION = "0.003";
50              
51 1     1   7 use parent "Exporter";
  1         2  
  1         5  
52             our @EXPORT_OK = qw(now_tai_rat now_tai_gsna now_tai_flt);
53              
54 1     1   77 use constant BIGRAT_ZERO => Math::BigRat->new(0);
  1         1  
  1         8  
55              
56             =head1 FUNCTIONS
57              
58             =over
59              
60             =item now_tai_rat([DEMAND_ACCURACY])
61              
62             Returns a list of two values. The first value identifies a current TAI
63             instant, in the form of a number of seconds since the epoch. The second
64             value is an inaccuracy bound, as a number of seconds, or C if no
65             accurate answer could be determined.
66              
67             If an inaccuracy bound is returned then this function is claiming to have
68             answered correctly, to within the specified margin. That is, some instant
69             during the execution of C is within the specified margin of
70             the instant identified. (This semantic differs from older current-time
71             interfaces that are content to return an instant that has already passed.)
72              
73             The inaccuracy bound is measured in TAI seconds; that is, in SI seconds
74             on the Terran geoid as realised by atomic clocks. This differs from SI
75             seconds at the computer's location, but the difference is only apparent
76             if the computer hardware is significantly time dilated with respect to
77             the geoid.
78              
79             If C is returned instead of an inaccuracy bound then this function
80             could not find a trustable answer. Either the clock available was
81             not properly synchronised or its accuracy could not be established.
82             Whatever time could be found is returned, but this function makes
83             no claim that it is accurate. It should be treated with suspicion.
84             In practice, clocks of this nature are especially likely to misbehave
85             around UTC leap seconds.
86              
87             The function Cs if it could not find a plausible time at all.
88             If DEMAND_ACCURACY is supplied and true then it will also die if it
89             could not find an accurate answer, instead of returning with C
90             for the inaccuracy bound.
91              
92             Both return values are in the form of C objects. This
93             retains full resolution, is future-proof, and is easy to manipulate,
94             but beware that C is currently rather slow. If performance
95             is a problem then consider using one of the functions below that return
96             the results in other formats.
97              
98             =cut
99              
100             my $rat_last_dayno = BIGRAT_ZERO;
101             my $rat_mn_s = BIGRAT_ZERO;
102              
103             sub now_tai_rat(;$) {
104 0     0 1   my($dayno, $secs, $bound) = now_utc_rat($_[0]);
105 0 0         if($dayno != $rat_last_dayno) {
106 0           $rat_mn_s = utc_to_tai($dayno, BIGRAT_ZERO);
107 0           $rat_last_dayno = $dayno;
108             }
109 0           return ($rat_mn_s + $secs, $bound);
110             }
111              
112             =item now_tai_gsna([DEMAND_ACCURACY])
113              
114             This performs exactly the same operation as C, but
115             returns the results in a different form. The time since the epoch
116             and the inaccuracy bound (if present) are each returned in the form
117             of a four-element array, giving a high-resolution fixed-point number
118             of seconds. The first element is the integral number of gigaseconds,
119             the second is an integral number of seconds in the range [0, 1000000000),
120             the third is an integral number of nanoseconds in the same range, and
121             the fourth is an integral number of attoseconds in the same range.
122              
123             This form of return value is fairly efficient. It is convenient for
124             decimal output, but awkward to do arithmetic with. Its resolution is
125             adequate for the foreseeable future, but could in principle be obsoleted
126             some day.
127              
128             The number of gigaseconds will exceed 1000000000, thus violating
129             the intent of the number format, one exasecond after the epoch,
130             when the universe is around three times the age it had at the epoch.
131             Terra (and thus TAI) might still exist then, depending on how much
132             its orbital radius increases before Sol enters its red giant phase.
133             In that situation the number of gigaseconds will simply continue to
134             increase, ultimately overflowing if native integer formats don't grow,
135             though it's a good bet that they will.
136              
137             The inaccuracy bound describes the actual time represented in the
138             return value, not an internal value that was rounded to generate the
139             return value.
140              
141             =cut
142              
143             my $gsna_last_dayno = 0;
144             my($gsna_mn_g, $gsna_mn_s) = (0, 0);
145              
146             sub now_tai_gsna(;$) {
147 0     0 1   my($dayno, $secs, $bound) = now_utc_sna($_[0]);
148 0 0         if($dayno != $gsna_last_dayno) {
149 0           my $midnight = utc_to_tai(Math::BigRat->new($dayno),
150             BIGRAT_ZERO);
151 0           $gsna_mn_g = ($midnight / 1000000000)->bfloor->numify;
152 0           $gsna_mn_s = ($midnight % 1000000000)->numify;
153 0           $gsna_last_dayno = $dayno;
154             }
155 0           my($g, $s) = ($gsna_mn_g, $gsna_mn_s);
156 0           $s += $secs->[0];
157 0 0         if($s >= 1000000000) {
158 0           $g++;
159 0           $s -= 1000000000;
160             }
161 0 0         $bound = [ 0, @$bound ] if defined $bound;
162 0           return ([ $g, $s, @{$secs}[1, 2] ], $bound);
  0            
163             }
164              
165             =item now_tai_flt([DEMAND_ACCURACY])
166              
167             This performs exactly the same operation as C, but returns
168             the results as Perl floating point numbers. This form of return value
169             is very efficient and easy to manipulate. However, its resolution is
170             limited, rendering it already obsolete for high-precision applications
171             at the time of writing.
172              
173             The inaccuracy bound describes the actual time represented in the
174             return value, not an internal value that was rounded to generate the
175             return value.
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             =back
207              
208             =head1 SEE ALSO
209              
210             L,
211             L
212              
213             =head1 AUTHOR
214              
215             Andrew Main (Zefram)
216              
217             =head1 COPYRIGHT
218              
219             Copyright (C) 2006, 2009, 2010 Andrew Main (Zefram)
220              
221             =head1 LICENSE
222              
223             This module is free software; you can redistribute it and/or modify it
224             under the same terms as Perl itself.
225              
226             =cut
227              
228             1;