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; |