File Coverage

blib/lib/Time/TAI.pm
Criterion Covered Total %
statement 133 323 41.1
branch 0 116 0.0
condition 0 23 0.0
subroutine 45 62 72.5
pod 3 3 100.0
total 181 527 34.3


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Time::TAI - International Atomic Time and realisations
4              
5             =head1 SYNOPSIS
6              
7             use Time::TAI qw(tai_instant_to_mjd tai_mjd_to_instant);
8              
9             $mjd = tai_instant_to_mjd($instant);
10             $instant = tai_mjd_to_instant($mjd);
11              
12             use Time::TAI qw(tai_realisation);
13              
14             $rln = tai_realisation("npl");
15             $instant = $rln->to_tai($npl_instant);
16              
17             =head1 DESCRIPTION
18              
19             International Atomic Time (TAI) is a time scale produced by an ensemble
20             of atomic clocks around Terra. It attempts to tick at the rate of proper
21             time on the Terran geoid (i.e., at sea level), and thus is the principal
22             realisation of Terrestrial Time (TT). It is the frequency standard
23             underlying Coordinated Universal Time (UTC), and so is indirectly the
24             basis for Terran civil timekeeping.
25              
26             This module represents instants on the TAI time scale as a scalar number
27             of TAI seconds since an epoch. This is an appropriate form for all manner
28             of calculations. The TAI scale is defined with a well-known point at UT2
29             instant 1958-01-01T00:00:00.0 as calculated by the United States Naval
30             Observatory. That instant is assigned the scalar value zero exactly,
31             making it the epoch for this linear seconds count. This matches the
32             convention used by C for instants on the TT scale.
33              
34             There is also a conventional way to represent TAI instants using day-based
35             notations associated with planetary rotation `time' scales. The `day'
36             of TAI is a nominal period of exactly 86400 TAI seconds, which is
37             slightly shorter than an actual Terran day. The well-known point at UT2
38             instant 1958-01-01T00:00:00.0 is assigned the label 1958-01-01T00:00:00.0
39             (MJD 36204.0). Because TAI is not connected to Terran rotation, and so
40             has no inherent concept of a day, it is somewhat misleading to use such
41             day-based notations. Conversion between this notation and the linear
42             count of seconds is supported by this module. This notation does not
43             match the similar day-based notation used for TT.
44              
45             Because TAI is canonically defined only in retrospect, real-time time
46             signals can only approximate it. To achieve microsecond accuracy it
47             is necessary to take account of this process. This module supports
48             conversion of times between different realisations of TAI.
49              
50             =cut
51              
52             package Time::TAI;
53              
54 1     1   31579 { use 5.006; }
  1         4  
  1         38  
55 1     1   5 use warnings;
  1         2  
  1         51  
56 1     1   11 use strict;
  1         7  
  1         37  
57              
58 1     1   5 use Carp qw(croak);
  1         2  
  1         87  
59 1     1   1450 use Math::BigRat 0.04;
  1         84643  
  1         8  
60              
61             our $VERSION = "0.003";
62              
63 1     1   2635 use parent "Exporter";
  1         404  
  1         4  
64             our @EXPORT_OK = qw(tai_instant_to_mjd tai_mjd_to_instant tai_realisation);
65              
66             =head1 FUNCTIONS
67              
68             =over
69              
70             =item tai_instant_to_mjd(INSTANT)
71              
72             Converts from a count of seconds to a Modified Julian Date in the manner
73             conventional for TAI. The MJD can be further converted to other forms of
74             day-based date using other modules. The input must be a C
75             object, and the result is the same type.
76              
77             =cut
78              
79 1     1   72 use constant TAI_EPOCH_MJD => Math::BigRat->new(36204);
  1         1  
  1         6  
80              
81             sub tai_instant_to_mjd($) {
82 4     4 1 5455 my($tai) = @_;
83 4         18 return TAI_EPOCH_MJD + ($tai / 86400);
84             }
85              
86             =item tai_mjd_to_instant(MJD)
87              
88             Converts from a Modified Julian Date, interpreted in the manner
89             conventional for TAI, to a count of seconds. The input must be a
90             C object, and the result is the same type.
91              
92             =cut
93              
94             sub tai_mjd_to_instant($) {
95 4     4 1 4756 my($mjd) = @_;
96 4         16 return ($mjd - TAI_EPOCH_MJD) * 86400;
97             }
98              
99             =item tai_realisation(NAME)
100              
101             Looks up and returns an object representing a named realisation of TAI.
102             The object returned is of the class C; see the
103             documentation of that class for its interface.
104              
105             The name, recognised case-insensitively, may be of these forms:
106              
107             =over
108              
109             =item "" (the empty string)
110              
111             TAI itself, as defined retrospectively.
112              
113             =item B
114              
115             TAI(NPL), supplied in real time by the National Physical Laboratory in
116             the UK. Other real-time estimates of TAI are named similarly using an
117             abbreviation of the name of the supplying agency. The names recognised
118             are:
119              
120             aos cnm ftz inti lt nimb nrc pknm smu tug
121             apl cnmp glo ipq lv nimt nrl pl snt ua
122             asmw crl gps it mike nis nrlm psb so ume
123             aus csao gum jatc mkeh nist ntsc ptb sp usno
124             bev csir hko jv msl nmc omh rc sta vmi
125             bim dlr ien kim nao nmij onba roa su vsl
126             birm dmdm ifag kris naom nml onrj scl tao yuzm
127             by dpt igma ksri naot nmls op sg tcc za
128             cao dtag igna kz nict npl orb siq tl zipe
129             ch eim inpl lds nim npli pel smd tp zmdm
130              
131             See L for expansions of these abbreviations.
132              
133             Some pairs of these names refer to the same time scale, due to renaming
134             of the underlying agency or transfer of responsibility for a time scale.
135             It is possible that some names that should be aliases are treated
136             as separate time scales, due to uncertainty of this module's author;
137             see L.
138              
139             The relationships between these scales and TAI are defined by isolated
140             data points, so conversions in general involve interpolation. The process
141             is by its nature inexact.
142              
143             =back
144              
145             Other names may be recognised in the future, as more TAI(k) time scales
146             are defined.
147              
148             =cut
149              
150             #
151             # general
152             #
153              
154 1     1   371 use constant MJD_1990_01 => 47892;
  1         2  
  1         50  
155 1     1   10 use constant MJD_1991_01 => 48257;
  1         1  
  1         31  
156 1     1   4 use constant MJD_1992_01 => 48622;
  1         2  
  1         38  
157 1     1   3 use constant MJD_1993_01 => 48988;
  1         1  
  1         32  
158 1     1   4 use constant MJD_1994_01 => 49353;
  1         3  
  1         45  
159 1     1   4 use constant MJD_1995_01 => 49718;
  1         2  
  1         65  
160 1     1   15 use constant MJD_1996_01 => 50083;
  1         1  
  1         46  
161 1     1   5 use constant MJD_1997_01 => 50449;
  1         1  
  1         47  
162 1     1   5 use constant MJD_1998_01 => 50814;
  1         1  
  1         41  
163 1     1   4 use constant MJD_1999_01 => 51179;
  1         1  
  1         29  
164 1     1   4 use constant MJD_2000_01 => 51544;
  1         1  
  1         32  
165 1     1   4 use constant MJD_2001_01 => 51910;
  1         1  
  1         26  
166 1     1   5 use constant MJD_2001_07 => 52091;
  1         1  
  1         32  
167 1     1   10 use constant MJD_2002_01 => 52275;
  1         1  
  1         37  
168 1     1   5 use constant MJD_2003_01 => 52640;
  1         1  
  1         27  
169 1     1   4 use constant MJD_2003_04 => 52730;
  1         2  
  1         32  
170 1     1   3 use constant MJD_2004_01 => 53005;
  1         2  
  1         26  
171 1     1   4 use constant MJD_2005_01 => 53371;
  1         2  
  1         40  
172              
173 1     1   4 use constant UTC_1989_07 => Math::BigRat->new( 993945624);
  1         1  
  1         5  
174 1     1   125 use constant UTC_1990_07 => Math::BigRat->new(1025481625);
  1         2  
  1         2  
175 1     1   170 use constant UTC_1991_07 => Math::BigRat->new(1057017626);
  1         1  
  1         3  
176 1     1   173 use constant UTC_1992_07 => Math::BigRat->new(1088640027);
  1         2  
  1         6  
177 1     1   180 use constant UTC_1993_07 => Math::BigRat->new(1120176028);
  1         3  
  1         4  
178 1     1   168 use constant UTC_1994_07 => Math::BigRat->new(1151712029);
  1         3  
  1         3  
179 1     1   184 use constant UTC_1995_07 => Math::BigRat->new(1183248029);
  1         2  
  1         6  
180 1     1   177 use constant UTC_1996_07 => Math::BigRat->new(1214870430);
  1         2  
  1         5  
181 1     1   169 use constant UTC_1997_07 => Math::BigRat->new(1246406431);
  1         2  
  1         3  
182 1     1   171 use constant UTC_1998_07 => Math::BigRat->new(1277942431);
  1         22  
  1         4  
183 1     1   184 use constant UTC_1999_07 => Math::BigRat->new(1309478432);
  1         2  
  1         4  
184 1     1   199 use constant UTC_2000_07 => Math::BigRat->new(1341100832);
  1         2  
  1         3  
185 1     1   145 use constant UTC_2001_07 => Math::BigRat->new(1372636832);
  1         1  
  1         9  
186 1     1   133 use constant UTC_2002_07 => Math::BigRat->new(1404172832);
  1         1  
  1         3  
187 1     1   152 use constant UTC_2003_02 => Math::BigRat->new(1422748832);
  1         2  
  1         3  
188 1     1   179 use constant UTC_2003_07 => Math::BigRat->new(1435708832);
  1         1  
  1         4  
189 1     1   168 use constant UTC_2004_07 => Math::BigRat->new(1467331232);
  1         2  
  1         4  
190 1     1   181 use constant UTC_2005_07 => Math::BigRat->new(1498867232);
  1         2  
  1         4  
191              
192             sub _get_bipm_file($) {
193 0     0     my($fn) = @_;
194 0           require LWP;
195 0           LWP->VERSION(5.53_94);
196 0           require LWP::UserAgent;
197 0           my $response = LWP::UserAgent->new
198             ->get("ftp://ftp2.bipm.fr/pub/tai/$fn");
199 0 0         croak "can't download $fn: ".$response->message
200             unless $response->code == 200;
201 0           return $response->content;
202             }
203              
204             my $nl_rx = qr/\r?\n(?:\ *\r?\n)*/;
205              
206             #
207             # UTC(k) data from utc-* files
208             #
209              
210             sub _parse_utck_file($$$) {
211 0     0     my($content, $min_mjd, $max_mjd) = @_;
212 0 0         $content =~ /\A\ *MJD\ +\[UTC-UTC\([A-Z]+\ *\)\]\/ns
213             (?:\ [^\n]*)?${nl_rx}
214             (?>\ *[0-9]+\ +(?:-|-?[0-9]+(?:\.[0-9]+)?)
215             (?:\ [^\n]*)?${nl_rx})*
216             \x{1a}?\z/xo
217             or die "doesn't look like a UTC-UTC(k) file\n";
218 0           require Time::TT::OffsetKnot;
219 0           Time::TT::OffsetKnot->VERSION(0.004);
220 0           my @data;
221 0           my $last_mjd = 0;
222 0           my $last_nonzero_mjd = 0;
223 0           my $consecutive_zeroes = 0;
224 0           while($content =~ /^\ *([0-9]+)\ +([-+]?[0-9]+(?:\.[0-9]+)?)
225             [\ \r\n]/xmg) {
226 0           my($mjd, $offset_ns) = ($1, $2);
227 0 0         die "data out of order at mjd=$mjd" unless $mjd > $last_mjd;
228 0           $last_mjd = $mjd;
229 0 0 0       next unless $mjd >= $min_mjd &&
      0        
230             (!defined($max_mjd) || $mjd < $max_mjd);
231 0           push @data, Time::TT::OffsetKnot->new($mjd, $offset_ns, 9);
232 0 0         if($offset_ns =~ /\A-?0+(?:\.0+)?\z/) {
233 0           $consecutive_zeroes++;
234             } else {
235 0           $consecutive_zeroes = 0;
236 0           $last_nonzero_mjd = $last_mjd;
237             }
238             }
239             # A couple of files have been seen with lots of bogus zero entries
240             # at the end.
241 0 0         splice @data, -$consecutive_zeroes if $consecutive_zeroes != 0;
242 0           return (\@data, $last_nonzero_mjd);
243             }
244              
245             sub _utck_file_source($$$;$);
246             sub _utck_file_source($$$;$) {
247 0     0     my($k, $rep_date, $min_mjd, $rpt) = @_;
248 0           my $max_mjd;
249 0 0         if(!defined($rpt)) {
    0          
250 0           $rpt = { last_downloaded => 0, wait_until => 0 };
251             } elsif(ref($rpt) eq "") {
252 0           $max_mjd = $rpt;
253 0           $rpt = undef;
254             }
255 0           require Math::Interpolator::Source;
256             return Math::Interpolator::Source->new(
257             sub () {
258 0 0   0     if(defined $rpt) {
259 0           my $time = time;
260 0 0 0       croak "no more data for TT(TAI(".uc($k).
261             ")) available"
262             unless $time >= $rpt->{wait_until} ||
263             $time < $rpt->{last_downloaded};
264 0           $rpt->{last_downloaded} = $time;
265 0           $rpt->{wait_until} =
266             $time + 86400 + rand(86400);
267             }
268 0           my($data, $last_mjd) =
269             _parse_utck_file(
270             _get_bipm_file("publication/utc-$k"),
271             $min_mjd, $max_mjd);
272 0 0         croak "no more data for TT(TAI(".uc($k).")) available"
273             unless @$data;
274 0 0         push @$data, _utck_file_source($k,
275             $data->[-1]->x + 1000000,
276             $last_mjd + 1, $rpt)
277             if defined $rpt;
278 0           return $data;
279             },
280 0           $rep_date, $rep_date);
281             }
282              
283             #
284             # UTC(k) data from utc.?? and utc??.ar files
285             #
286              
287             sub _parse_utcyr_file($$$) {
288 0     0     my($content, $min_mjd, $max_mjd) = @_;
289 0 0         $content =~ /\A\ *Values\ of\ UTC-UTC\(laboratory\)\ for
290             (?>[^\n]+\n)+\n
291             ((?>(?>\ {5}(?:\ {4}[A-Z\ ]{4}){8}\n)+))
292             (?>[0-9]{5}
293             (?:[\ \-\+][\ \-\+0-9]{3}\.(?:[0-9]{3}|0\ \ )
294             |\ {8}){8}\n)+
295             \z/x
296             or die "doesn't look like a bulk UTC-UTC(k) file\n";
297 0           my @labs = map { [ map { lc } split ] } split(/\n/, $1);
  0            
  0            
298 0           require Time::TT::OffsetKnot;
299 0           Time::TT::OffsetKnot->VERSION(0.004);
300 0           my %data;
301 0           $content =~ /\n\n/g;
302 0           my $last_mjd = 0;
303 0           while($content =~ /^([0-9]{5})(.{64})\n/msg) {
304 0           my($mjd, $numbers) = ($1, $2);
305 0 0         die "data out of order at mjd=$mjd" unless $mjd > $last_mjd;
306 0           $last_mjd = $mjd;
307 0           for(my $line = 0; $line != @labs; $line++) {
308 0 0         unless($line == 0) {
309 0 0         $content =~ /^([0-9]{5})(.{64})\n/msg
310             or die "incomplete data group\n";
311 0           ($mjd, $numbers) = ($1, $2);
312 0 0         die "inconsistent data group\n"
313             unless $mjd eq $last_mjd;
314             }
315 0 0 0       next unless $mjd >= $min_mjd && $mjd < $max_mjd;
316 0           for(my $i = 0; $i != 8; $i++) {
317 0           my $num = substr($numbers, 8*$i, 8);
318 0           my $lab = $labs[$line]->[$i];
319 0 0         if(!defined($lab)) {
320 0 0         die "extraneous data\n"
321             unless $num eq " ";
322 0           next;
323             }
324 0 0         next if $num eq " 0.0 ";
325 0 0         die "malformed number\n"
326             unless $num =~ /\A\ *([-+]?[0-9]+
327             \.[0-9]+)\z/x;
328 0           push @{$data{$lab}},
  0            
329             Time::TT::OffsetKnot
330             ->new($last_mjd, $1, 6);
331             }
332             }
333             }
334 0           return \%data;
335             }
336              
337             sub _parse_utcyrar_file($$$) {
338 0     0     my($content, $min_mjd, $max_mjd) = @_;
339 0 0         $content =~ /\A[\ \t\n]*[^\n]*\ local\ representations\ of\ utc[\ :].*
340             [\ \t\n]unit\ is\ one\ (micr|nan)osecond\./xsi
341             or die "doesn't look like a bulk UTC-UTC(k) file\n";
342 0 0         my $unit = $1 =~ /\Amicr\z/i ? 6 : 9;
343 0           require Time::TT::OffsetKnot;
344 0           Time::TT::OffsetKnot->VERSION(0.004);
345 0           my %data;
346             my @labs;
347 0           while($content =~ /^\ *0h\ UTC((?:\ +[A-Z]{1,4})+)\ *[\r\n]
348             |^\ *[A-Z][a-z]{2}\ +[0-9]+\ +([0-9]+)
349             ((?:\ +(?:-|[-+]?[0-9]+(?:\.[0-9]+)?))+)
350             \ *[\r\n]/xmg) {
351 0           my($labs, $mjd, $offsets) = ($1, $2, $3);
352 0 0         if(defined $labs) {
353 0           @labs = map { lc } split(" ", $labs);
  0            
354 0           foreach my $lab (@labs) {
355 0 0         next if exists $data{$lab};
356 0           $data{$lab} = {
357             last_mjd => 0,
358             points => [],
359             };
360             }
361 0           next;
362             }
363 0 0         die "data without heading\n" unless @labs;
364 0 0 0       next unless $mjd >= $min_mjd && $mjd < $max_mjd;
365 0           my @offsets = split(" ", $offsets);
366 0 0         die "malformed table\n" unless @offsets == @labs;
367 0           for(my $i = @labs; $i--; ) {
368 0           my $lab = $labs[$i];
369 0 0         unless($mjd > $data{$lab}->{last_mjd}) {
370             # there is a repeated table in utc98.ar
371 0 0         next if $data{$lab}->{last_mjd} == 50994;
372 0           die "data out of order at mjd=$mjd";
373             }
374 0           $data{$lab}->{last_mjd} = $mjd;
375 0           my $offset = $offsets[$i];
376 0 0         push @{$data{$lab}->{points}},
  0            
377             Time::TT::OffsetKnot->new($mjd, $offset, $unit)
378             unless $offset eq "-";
379             }
380             }
381 0           foreach my $lab (keys %data) {
382 0           $data{$lab} = $data{$lab}->{points};
383             }
384 0           return \%data;
385             }
386              
387             #
388             # UTC(GPS) & UTC(GLO) data from utcg(ps|lo)??.ar files
389             #
390              
391             sub _parse_gpsyr_file($$$) {
392 0     0     my($content, $min_mjd, $max_mjd) = @_;
393 0 0         $content =~ /\A[\ \t\n]*[^\n]*
394             \[\ *(?:tai|utc)\ *-\ *(?:gps|glonass)\ time\]/xi
395             or die "doesn't look like a GPS file\n";
396 0 0         my $unit = $content =~ /\(Unit is one microsecond\)/ ? 6 : 9;
397 0           require Time::TT::OffsetKnot;
398 0           Time::TT::OffsetKnot->VERSION(0.004);
399 0           my @data;
400 0           my $last_mjd = 0;
401             # in some cases adjacent data lines are separated by a large number
402             # of spaces instead of by a newline character
403 0           while($content =~ /(?:^|\ {30})\ *[A-Z][a-z]{2}\ +[0-9]+\ +([0-9]+)
404             \ +([-+]?[0-9]+(?:\.[0-9]+)?)
405             (?:\ +(?:-|[-+]?[0-9]+(?:\.[0-9]+)?)){1,2}
406             (?:\ *[\r\n]|\ {30})/xmg) {
407 0           my($mjd, $offset) = ($1, $2);
408 0 0         unless($mjd > $last_mjd) {
409             # there are two data for one day in utcgps94.ar
410             # (they give different C0 values, no idea which is
411             # better)
412 0 0         next if $mjd == 49709;
413 0           die "data out of order at mjd=$mjd";
414             }
415 0           $last_mjd = $mjd;
416 0 0 0       next unless $mjd >= $min_mjd && $mjd < $max_mjd;
417 0           push @data, Time::TT::OffsetKnot->new($mjd, $offset, $unit);
418             }
419 0           return \@data;
420             }
421              
422             my %gpsyr_year = (
423             "93" => {
424             min_mjd => MJD_1993_01, max_mjd => MJD_1994_01,
425             rep_date => UTC_1993_07,
426             },
427             "94" => {
428             min_mjd => MJD_1994_01, max_mjd => MJD_1995_01,
429             rep_date => UTC_1994_07,
430             },
431             "95" => {
432             min_mjd => MJD_1995_01, max_mjd => MJD_1996_01,
433             rep_date => UTC_1995_07,
434             },
435             "96" => {
436             min_mjd => MJD_1996_01, max_mjd => MJD_1997_01,
437             rep_date => UTC_1996_07,
438             },
439             "97" => {
440             min_mjd => MJD_1997_01, max_mjd => MJD_1998_01,
441             rep_date => UTC_1997_07,
442             },
443             "98" => {
444             min_mjd => MJD_1998_01, max_mjd => MJD_1999_01,
445             rep_date => UTC_1998_07,
446             },
447             "99" => {
448             min_mjd => MJD_1999_01, max_mjd => MJD_2000_01,
449             rep_date => UTC_1999_07,
450             },
451             "00" => {
452             min_mjd => MJD_2000_01, max_mjd => MJD_2001_01,
453             rep_date => UTC_2000_07,
454             },
455             "01" => {
456             min_mjd => MJD_2001_01, max_mjd => MJD_2002_01,
457             rep_date => UTC_2001_07,
458             },
459             "02" => {
460             min_mjd => MJD_2002_01, max_mjd => MJD_2003_01,
461             rep_date => UTC_2002_07,
462             },
463             "03" => {
464             min_mjd => MJD_2003_01, max_mjd => MJD_2003_04,
465             rep_date => UTC_2003_02,
466             },
467             );
468              
469             sub _gpsyr_file_source($$) {
470 0     0     my($k, $yr) = @_;
471 0           my $year = $gpsyr_year{$yr};
472 0 0         die "GPS-style data requested for unknown year `$yr'"
473             unless defined $year;
474 0           require Math::Interpolator::Source;
475             return Math::Interpolator::Source->new(
476             sub () {
477 0     0     return _parse_gpsyr_file(
478             _get_bipm_file("scale/utc$k$yr.ar"),
479             $year->{min_mjd}, $year->{max_mjd});
480             },
481 0           $year->{rep_date}, $year->{rep_date});
482             }
483              
484             #
485             # UTC(GPS) & UTC(GLO) data from utcgpsglo??.ar files
486             #
487              
488             sub _parse_gpsgloyr_file($$$) {
489 0     0     my($content, $min_mjd, $max_mjd) = @_;
490 0 0         $content =~ /\A[\ \t\n]*Relations\ of\ UTC\ and\ TAI\ with
491             \ GPS\ time\ and\ GLONASS\ time[\ \t\n]/x
492             or die "doesn't look like a GPS/GLONASS file\n";
493 0           require Time::TT::OffsetKnot;
494 0           Time::TT::OffsetKnot->VERSION(0.004);
495 0           my(@gps, @glo);
496 0           my $last_mjd = 0;
497 0           while($content =~ /^\ *[A-Z]{3}\ +[0-9]+\ +([0-9]+)
498             \ +(-|[-+]?[0-9]+(?:\.[0-9]+)?)\ +[0-9]+
499             \ +(-|[-+]?[0-9]+(?:\.[0-9]+)?)\ +[0-9]+
500             \ *[\r\n]/xmg) {
501 0           my($mjd, $gps_offset_ns, $glo_offset_ns) = ($1, $2, $3);
502 0 0         die "data out of order at mjd=$mjd" unless $mjd > $last_mjd;
503 0           $last_mjd = $mjd;
504 0 0 0       next unless $mjd >= $min_mjd && $mjd < $max_mjd;
505 0 0         push @gps, Time::TT::OffsetKnot->new($mjd, $gps_offset_ns, 9)
506             unless $gps_offset_ns eq "-";
507 0 0         push @glo, Time::TT::OffsetKnot->new($mjd, $glo_offset_ns, 9)
508             unless $glo_offset_ns eq "-";
509             }
510 0           return { gps => \@gps, glo => \@glo };
511             }
512              
513             #
514             # mechanism for multi-scale files
515             #
516              
517             my %multiscale = (
518             u90 => {
519             filename => "scale/utc.90",
520             parser => \&_parse_utcyr_file,
521             min_mjd => MJD_1990_01, max_mjd => MJD_1991_01,
522             rep_date => UTC_1990_07,
523             },
524             u91 => {
525             filename => "scale/utc.91",
526             parser => \&_parse_utcyr_file,
527             min_mjd => MJD_1991_01, max_mjd => MJD_1992_01,
528             rep_date => UTC_1991_07,
529             },
530             u92 => {
531             filename => "scale/utc.92",
532             parser => \&_parse_utcyr_file,
533             min_mjd => MJD_1992_01, max_mjd => MJD_1993_01,
534             rep_date => UTC_1992_07,
535             },
536             u93 => {
537             filename => "scale/utc93.ar",
538             parser => \&_parse_utcyrar_file,
539             min_mjd => MJD_1993_01, max_mjd => MJD_1994_01,
540             rep_date => UTC_1993_07,
541             },
542             u94 => {
543             filename => "scale/utc94.ar",
544             parser => \&_parse_utcyrar_file,
545             min_mjd => MJD_1994_01, max_mjd => MJD_1995_01,
546             rep_date => UTC_1994_07,
547             },
548             u95 => {
549             filename => "scale/utc95.ar",
550             parser => \&_parse_utcyrar_file,
551             min_mjd => MJD_1995_01, max_mjd => MJD_1996_01,
552             rep_date => UTC_1995_07,
553             },
554             u96 => {
555             filename => "scale/utc96.ar",
556             parser => \&_parse_utcyrar_file,
557             min_mjd => MJD_1996_01, max_mjd => MJD_1997_01,
558             rep_date => UTC_1996_07,
559             },
560             u97 => {
561             filename => "scale/utc97.ar",
562             parser => \&_parse_utcyrar_file,
563             min_mjd => MJD_1997_01, max_mjd => MJD_1998_01,
564             rep_date => UTC_1997_07,
565             },
566             u98 => {
567             filename => "scale/utc98.ar",
568             parser => \&_parse_utcyrar_file,
569             min_mjd => MJD_1998_01, max_mjd => MJD_1999_01,
570             rep_date => UTC_1998_07,
571             },
572             gg03 => {
573             filename => "scale/utcgpsglo03.ar",
574             parser => \&_parse_gpsgloyr_file,
575             min_mjd => MJD_2003_04, max_mjd => MJD_2004_01,
576             rep_date => UTC_2003_07,
577             },
578             gg04 => {
579             filename => "scale/utcgpsglo04.ar",
580             parser => \&_parse_gpsgloyr_file,
581             min_mjd => MJD_2004_01, max_mjd => MJD_2005_01,
582             rep_date => UTC_2004_07,
583             },
584             );
585              
586             sub _multiscale_source($$) {
587 0     0     my($k, $source) = @_;
588 0           my $metadata = $multiscale{$source};
589 0 0         die "multi-scale data requsted from unknown source `$source'\n"
590             unless defined $metadata;
591 0           require Math::Interpolator::Source;
592             return Math::Interpolator::Source->new(
593             sub () {
594 0     0     my $data = $metadata->{data};
595 0 0         unless(defined $data) {
596 0           $data = $metadata->{parser}->(
597             _get_bipm_file($metadata->{filename}),
598             $metadata->{min_mjd},
599             $metadata->{max_mjd});
600 0           $metadata->{data} = $data;
601             }
602 0   0       return $data->{$k} || [];
603             },
604 0           $metadata->{rep_date}, $metadata->{rep_date});
605             }
606              
607             #
608             # permanently-broken sources to represent missing data
609             #
610              
611             sub _bad_start_source($) {
612 0     0     my($k) = @_;
613 0           $k = uc($k);
614 0           require Math::Interpolator::Source;
615             return Math::Interpolator::Source->new(
616             sub () {
617 0     0     croak "earlier data for TT(TAI($k)) is missing";
618             },
619 0           UTC_1989_07, UTC_1989_07);
620             }
621              
622             sub _bad_end_source($) {
623 0     0     my($k) = @_;
624 0           $k = uc($k);
625 0           require Math::Interpolator::Source;
626             return Math::Interpolator::Source->new(
627             sub () {
628 0     0     croak "later data for TT(TAI($k)) is missing";
629             },
630 0           UTC_2005_07, UTC_2005_07);
631             }
632              
633             #
634             # overall structure of realisations
635             #
636              
637             #
638             # These recipes detail where to find data on each time scale. This is
639             # necessary because the data are split up between several files, and
640             # there are redundancies and renamings. The recipe string contains the
641             # following items:
642             #
643             # "u90".."u98": utc.?? and utc??.ar files, which each contain data on
644             # many UTC(k) time scales for a single year
645             # "u": utc-* files, which each contain data on a single time scale
646             # from 1998 onwards
647             # "u*gum": special case: utc-gum has data only up to 2001-07
648             # "u*pl": special case: utc-pl has data from 2001-07 onwards
649             # "g93".."g03": utcg(ps|lo)??.ar files, which each contain data on either
650             # GPS or GLONASS for a single year
651             # "gg03".."gg04": utcgpsglo??.ar files, which each contain GPS and GLONASS
652             # data for a single year
653             # "<": data missing at start
654             # ">": data missing at end
655             # ":dtag": change of name
656             # "!": following data source has only blanks for this scale
657             # "?": following data source has only redundant data for this scale
658             #
659             # or the recipe may consist entirely of:
660             #
661             # "=dtag": alias of referenced scale
662             # "*tai": special case for TAI itself
663             #
664              
665             my %realisation = (
666             "" => "*tai",
667             # not a real realisation: amc => "!u",
668             aos => "< u90 u91 u92 u93 u94 u95 u96 u97 ?u98 u",
669             apl => "< u90 u91 u92 u93 u94 u95 u96 u97 ?u98 u",
670             asmw => "< u90 >",
671             aus => "< u90 u91 u92 u93 u94 u95 u96 u97 ?u98 u",
672             bev => "< u90 u91 u92 u93 u94 u95 u96 !u97 ?u98 u",
673             bim => "< :nmc u91 u92 u93 !u94 ?u :bim u",
674             birm => "u95 u96 u97 ?u98 u",
675             by => "u",
676             cao => "< u90 u91 u92 u93 u94 u95 u96 u97 ?u98 u",
677             ch => "< u90 u91 u92 u93 u94 u95 u96 u97 ?u98 u",
678             cnm => "u96 u97 ?u98 u",
679             cnmp => "u",
680             crl => "=nict",
681             csao => "=ntsc",
682             csir => "=za",
683             dlr => "u96 u97 ?u98 u",
684             dmdm => "u",
685             dpt => "=za",
686             dtag => "< :ftz u90 u91 u92 u93 u94 u95 :dtag u96 u97 ?u98 u",
687             eim => "u",
688             ftz => "=dtag",
689             glo => "< g93 g94 g95 g96 g97 g98 g99 g00 g01 g02 g03 gg03 gg04 >",
690             gps => "< g93 g94 g95 g96 g97 g98 g99 g00 g01 g02 g03 gg03 gg04 >",
691             gum => "=pl",
692             hko => "u",
693             ien => "=it",
694             ifag => "< u90 u91 u92 u93 u94 u95 u96 u97 ?u98 u",
695             igma => "=igna",
696             igna => "< :igma u90 u91 u92 u93 u94 u95 u96 u97 ?u98 :igna u",
697             inpl => "< u90 u91 u92 u93 u94 u95 u96 u97 ?u98 u",
698             inti => "u",
699             ipq => "u95 u96 u97 ?u98 u",
700             it => "< :ien u90 u91 u92 u93 u94 u95 u96 u97 ?u98 ?u :it u",
701             jatc => "< u90 u91 u92 u93 u94 u95 u96 u97 ?u98 u",
702             jv => "u",
703             kim => "u",
704             kris => "< :ksri u90 :kris u91 u92 u93 u94 u95 u96 u97 ?u98 u",
705             ksri => "=kris",
706             kz => "u",
707             lds => "< u90 u91 u92 u93 u94 u95 u96 u97 ?u98 u",
708             lt => "u",
709             lv => "u",
710             mike => "u",
711             mkeh => "< :omh u90 u91 u92 u93 u94 u95 u96 u97 ?u98 ?u :mkeh u",
712             msl => "< :pel u90 u91 :msl u92 u93 u94 u95 u96 u97 ?u98 u",
713             nao => "< :naom u90 u91 u92 u93 u94 u95 u96 :nao u97 ?u98 u",
714             naom => "=nao",
715             naot => "< u92 u93 u94 u95 u96 >",
716             nict => "< :crl u90 u91 u92 u93 u94 u95 u96 u97 ?u98 ?u :nict u",
717             nim => "< u90 u91 u92 u93 u94 u95 u96 u97 ?u98 u",
718             nimb => "u",
719             nimt => "u",
720             nis => "u",
721             nist => "< u90 u91 u92 u93 u94 u95 u96 u97 ?u98 u",
722             nmc => "=bim",
723             nmij => "< :nrlm u90 u91 u92 u93 u94 u95 u96 u97 ?u98 ?u :nmij u",
724             nml => "u97 u98",
725             nmls => "u",
726             npl => "< u90 u91 u92 u93 u94 u95 u96 u97 ?u98 u",
727             npli => "< u90 u91 u92 u93 u94 !u95 !u96 ?u98 u",
728             nrc => "< u90 u91 u92 u93 u94 u95 u96 u97 ?u98 u",
729             nrl => "u",
730             nrlm => "=nmij",
731             ntsc => "< :csao u90 u91 u92 u93 u94 u95 u96 u97 ?u98 ?u :ntsc u",
732             omh => "=mkeh",
733             onba => "< u92 u93 u94 u95 u96 u97 ?u98 u",
734             onrj => "< u90 u91 u92 u93 u94 u95 u96 u97 ?u98 u",
735             op => "< u90 u91 u92 u93 u94 u95 u96 u97 ?u98 u",
736             orb => "< u90 u91 u92 u93 u94 u95 u96 u97 ?u98 u",
737             pel => "=msl",
738             pknm => "=pl",
739             pl => "< :pknm u90 u91 u92 u93 :gum u94 u95 u96 u97 ?u98 u*gum :pl u*pl",
740             psb => "=sg",
741             ptb => "< u90 u91 u92 u93 u94 u95 u96 u97 ?u98 u",
742             rc => "< u90 u91 u92 u93 u94 u95 u96 >",
743             roa => "< u90 u91 u92 u93 u94 u95 u96 u97 ?u98 u",
744             scl => "< u92 u93 u94 u95 u96 u97 ?u98 u",
745             sg => ":psb u97 ?u98 ?u :sg u",
746             siq => "u",
747             smd => "u",
748             smu => "?u98 u",
749             snt => "< u91 u92 u93 u94 u95 >",
750             so => "< u90 u91 u92 u93 u94 u95 u96 u97 ?u98 u",
751             sp => "u96 u97 ?u98 u",
752             sta => "< u90 >",
753             su => "< u90 u91 u92 u93 u94 u95 u96 u97 ?u98 u",
754             tao => "< u90 u91 >",
755             tcc => "u",
756             tl => "< u90 u91 u92 u93 u94 u95 u96 u97 ?u98 u",
757             tp => "< u90 u91 u92 u93 u94 u95 u96 u97 ?u98 u",
758             tug => "< u90 u91 u92 u93 u94 u95 u96 u97 ?u98 u",
759             ua => "u",
760             ume => "u94 u95 u96 u97 ?u98 u",
761             usno => "< u90 u91 u92 u93 u94 u95 u96 u97 ?u98 u",
762             vmi => "u",
763             vsl => "< u90 u91 u92 u93 u94 u95 u96 u97 ?u98 u",
764             yuzm => "< u90 u91 !u92 >",
765             za => "< :dpt u90 u91 u92 :csir u93 u94 u95 u96 u97 ?u98 ?u :za u",
766             zipe => "< u90 u91 >",
767             zmdm => "=dmdm",
768             );
769              
770             sub tai_realisation($);
771             sub tai_realisation($) {
772 0     0 1   my($name) = @_;
773 0           $name = lc($name);
774 0           my $r = $realisation{$name};
775 0 0         croak "no realisation TT(TAI(".uc($name).")) known" unless defined $r;
776 0 0         if(ref($r) eq "") {
777 0 0         if($r =~ /\A=([a-z]+)\z/) {
    0          
778 0           $r = tai_realisation($1);
779             } elsif($r eq "*tai") {
780 0           require Time::TAI::Realisation_TAI;
781 0           $r = Time::TAI::Realisation_TAI->new;
782             } else {
783 0           my @parts;
784 0           my $k = $name;
785 0           foreach my $ingredient (split(/ /, $r)) {
786 0 0         if($ingredient =~ /\A[!?]/) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
787             # ignore this data
788             } elsif($ingredient =~
789             /\A(?:u|gg)[0-9][0-9]\z/) {
790 0           push @parts, _multiscale_source($k,
791             $ingredient);
792             } elsif($ingredient eq "u") {
793 0           push @parts, _utck_file_source($k,
794             UTC_1998_07, MJD_1998_01);
795             } elsif($ingredient eq "u*gum") {
796 0           push @parts, _utck_file_source($k,
797             UTC_1998_07,
798             MJD_1998_01, MJD_2001_07),
799             } elsif($ingredient eq "u*pl") {
800 0           push @parts, _utck_file_source($k,
801             UTC_2002_07, MJD_2001_07);
802             } elsif($ingredient =~ /\Ag([0-9][0-9])\z/) {
803 0           push @parts,
804             _gpsyr_file_source($k, $1);
805             } elsif($ingredient eq "<") {
806 0           push @parts, _bad_start_source($k);
807             } elsif($ingredient eq ">") {
808 0           push @parts, _bad_end_source($k);
809             } elsif($ingredient =~ /\A:([a-z]+)\z/) {
810 0           $k = $1;
811             } else {
812 0           die "unrecognised ingredient ".
813             "`$ingredient'";
814             }
815             }
816 0           require Math::Interpolator::Robust;
817 0           $r = Math::Interpolator::Robust->new(@parts);
818 0           require Time::TT::InterpolatingRealisation;
819 0           $r = Time::TT::InterpolatingRealisation->new($r);
820             }
821 0           $realisation{$name} = $r;
822             }
823 0           return $r;
824             }
825              
826             =back
827              
828             =head1 BUGS
829              
830             For a few of the named realisations of TAI for which there is data, the
831             author of this module was unable to determine conclusively whether they
832             were renamed at some point. This affects particularly the names "naot",
833             "snt", "sta", "tao".
834              
835             Time scale data only goes back to the beginning of 1990. GPS and GLONASS
836             data only goes back to the beginning of 1993, and forward to the end
837             of 2004.
838              
839             If you can supply more information about any of the time scales for
840             which data is missing then please mail the author.
841              
842             Time steps and frequency shifts are not noted in the time scale data
843             available to this module. The smooth interpolation will therefore produce
844             inaccurate results in the immediate vicinity of such discontinuities.
845              
846             =head1 SEE ALSO
847              
848             L,
849             L,
850             L,
851             L,
852             L,
853             L,
854             L
855              
856             =head1 AUTHOR
857              
858             Andrew Main (Zefram)
859              
860             =head1 COPYRIGHT
861              
862             Copyright (C) 2006, 2007, 2010 Andrew Main (Zefram)
863              
864             =head1 LICENSE
865              
866             This module is free software; you can redistribute it and/or modify it
867             under the same terms as Perl itself.
868              
869             =cut
870              
871             1;