| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | =head1 NAME | 
| 2 |  |  |  |  |  |  |  | 
| 3 |  |  |  |  |  |  | Time::UTC::Segment - segments of UTC definition | 
| 4 |  |  |  |  |  |  |  | 
| 5 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 6 |  |  |  |  |  |  |  | 
| 7 |  |  |  |  |  |  | use Time::UTC::Segment; | 
| 8 |  |  |  |  |  |  |  | 
| 9 |  |  |  |  |  |  | $seg = Time::UTC::Segment->start; | 
| 10 |  |  |  |  |  |  |  | 
| 11 |  |  |  |  |  |  | $tai = $seg->start_tai_instant; | 
| 12 |  |  |  |  |  |  | $tai = $seg->end_tai_instant; | 
| 13 |  |  |  |  |  |  | $len = $seg->length_in_tai_seconds; | 
| 14 |  |  |  |  |  |  |  | 
| 15 |  |  |  |  |  |  | $day = $seg->start_utc_day; | 
| 16 |  |  |  |  |  |  | $day = $seg->last_utc_day; | 
| 17 |  |  |  |  |  |  | $day = $seg->end_utc_day; | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  | $len = $seg->utc_second_length; | 
| 20 |  |  |  |  |  |  | $secs = $seg->leap_utc_seconds; | 
| 21 |  |  |  |  |  |  |  | 
| 22 |  |  |  |  |  |  | $secs = $seg->last_day_utc_seconds; | 
| 23 |  |  |  |  |  |  | $secs = $seg->length_in_utc_seconds; | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | $seg = $seg->prev; | 
| 26 |  |  |  |  |  |  | $seg = $seg->next; | 
| 27 |  |  |  |  |  |  |  | 
| 28 |  |  |  |  |  |  | if($seg->complete_p) { ... | 
| 29 |  |  |  |  |  |  | $seg->when_complete(\&do_stuff); | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | An object of this class represents a segment of the definition of UTC in | 
| 34 |  |  |  |  |  |  | terms of TAI.  Each segment is a period of time over which the relation | 
| 35 |  |  |  |  |  |  | between UTC and TAI is stable.  Each point where the relation changes | 
| 36 |  |  |  |  |  |  | is a boundary between segments. | 
| 37 |  |  |  |  |  |  |  | 
| 38 |  |  |  |  |  |  | Each segment consists of an integral number of consecutive UTC days. | 
| 39 |  |  |  |  |  |  | Within each segment, the length of the UTC second is fixed relative | 
| 40 |  |  |  |  |  |  | to the TAI second.  Also, every UTC day in the segment except for the | 
| 41 |  |  |  |  |  |  | last one contains exactly 86400 UTC seconds.  The last day of a segment | 
| 42 |  |  |  |  |  |  | commonly has some other length. | 
| 43 |  |  |  |  |  |  |  | 
| 44 |  |  |  |  |  |  | Because UTC is only defined a few months ahead, the description of UTC | 
| 45 |  |  |  |  |  |  | that is available at any particular time is necessarily incomplete. | 
| 46 |  |  |  |  |  |  | Nevertheless, this API gives the superficial appearance of completeness. | 
| 47 |  |  |  |  |  |  | The information-querying methods will C if asked for information | 
| 48 |  |  |  |  |  |  | that is not yet available.  There are additional methods to probe the | 
| 49 |  |  |  |  |  |  | availability of information. | 
| 50 |  |  |  |  |  |  |  | 
| 51 |  |  |  |  |  |  | =cut | 
| 52 |  |  |  |  |  |  |  | 
| 53 |  |  |  |  |  |  | package Time::UTC::Segment; | 
| 54 |  |  |  |  |  |  |  | 
| 55 | 11 |  |  | 11 |  | 21771 | { use 5.006; } | 
|  | 11 |  |  |  |  | 40 |  | 
|  | 11 |  |  |  |  | 475 |  | 
| 56 | 11 |  |  | 11 |  | 63 | use warnings; | 
|  | 11 |  |  |  |  | 27 |  | 
|  | 11 |  |  |  |  | 363 |  | 
| 57 | 11 |  |  | 11 |  | 65 | use strict; | 
|  | 11 |  |  |  |  | 26 |  | 
|  | 11 |  |  |  |  | 412 |  | 
| 58 |  |  |  |  |  |  |  | 
| 59 | 11 |  |  | 11 |  | 56 | use Carp qw(croak); | 
|  | 11 |  |  |  |  | 28 |  | 
|  | 11 |  |  |  |  | 674 |  | 
| 60 | 11 |  |  | 11 |  | 21026 | use Digest::SHA1 qw(sha1_hex); | 
|  | 11 |  |  |  |  | 9275 |  | 
|  | 11 |  |  |  |  | 699 |  | 
| 61 | 11 |  |  | 11 |  | 18716 | use HTTP::Tiny 0.016 (); | 
|  | 11 |  |  |  |  | 700408 |  | 
|  | 11 |  |  |  |  | 484 |  | 
| 62 | 11 |  |  | 11 |  | 1671 | use Math::BigRat 0.13; | 
|  | 11 |  |  |  |  | 107281 |  | 
|  | 11 |  |  |  |  | 136 |  | 
| 63 | 11 |  |  | 11 |  | 28110 | use Net::FTP 1.21 (); | 
|  | 11 |  |  |  |  | 273787 |  | 
|  | 11 |  |  |  |  | 513 |  | 
| 64 | 11 |  |  | 11 |  | 15796 | use Time::Unix 1.02 (); | 
|  | 11 |  |  |  |  | 5563 |  | 
|  | 11 |  |  |  |  | 6886 |  | 
| 65 |  |  |  |  |  |  |  | 
| 66 |  |  |  |  |  |  | our $VERSION = "0.008"; | 
| 67 |  |  |  |  |  |  |  | 
| 68 |  |  |  |  |  |  | @Time::UTC::Segment::Complete::ISA = qw(Time::UTC::Segment); | 
| 69 |  |  |  |  |  |  | @Time::UTC::Segment::Incomplete::ISA = qw(Time::UTC::Segment); | 
| 70 |  |  |  |  |  |  |  | 
| 71 |  |  |  |  |  |  | =head1 CONSTRUCTOR | 
| 72 |  |  |  |  |  |  |  | 
| 73 |  |  |  |  |  |  | Objects of this class are not created by users, but are generated | 
| 74 |  |  |  |  |  |  | internally.  New segments appear when updated UTC data is downloaded; | 
| 75 |  |  |  |  |  |  | this is done automatically as required.  Segments are accessed from each | 
| 76 |  |  |  |  |  |  | other by means of the C and C methods. | 
| 77 |  |  |  |  |  |  |  | 
| 78 |  |  |  |  |  |  | =over | 
| 79 |  |  |  |  |  |  |  | 
| 80 |  |  |  |  |  |  | =item Time::UTC::Segment->start | 
| 81 |  |  |  |  |  |  |  | 
| 82 |  |  |  |  |  |  | Returns the first segment of the UTC description. | 
| 83 |  |  |  |  |  |  |  | 
| 84 |  |  |  |  |  |  | =back | 
| 85 |  |  |  |  |  |  |  | 
| 86 |  |  |  |  |  |  | =cut | 
| 87 |  |  |  |  |  |  |  | 
| 88 |  |  |  |  |  |  | my $incomplete_segment; | 
| 89 |  |  |  |  |  |  |  | 
| 90 |  |  |  |  |  |  | { | 
| 91 |  |  |  |  |  |  | my $start_segment = $incomplete_segment = bless({ | 
| 92 |  |  |  |  |  |  | start_utc_day => Math::BigRat->new(1096), | 
| 93 |  |  |  |  |  |  | start_tai_instant => | 
| 94 |  |  |  |  |  |  | 1096*86400 + Math::BigRat->new("1.4228180"), | 
| 95 |  |  |  |  |  |  | utc_second_length => 1 + Math::BigRat->new("0.001296") / 86400, | 
| 96 |  |  |  |  |  |  | }, "Time::UTC::Segment::Incomplete"); | 
| 97 |  |  |  |  |  |  | sub start { | 
| 98 | 38 |  |  | 38 | 1 | 155 | return $start_segment; | 
| 99 |  |  |  |  |  |  | } | 
| 100 |  |  |  |  |  |  | } | 
| 101 |  |  |  |  |  |  |  | 
| 102 |  |  |  |  |  |  | sub _add_data($$$$$$) { | 
| 103 | 195 |  |  | 195 |  | 4781 | my($start_utc_day, $start_tai_instant, $utc_second_length, | 
| 104 |  |  |  |  |  |  | $end_utc_day, $end_tai_instant, $next_utc_second_length) = @_; | 
| 105 | 195 | 50 |  |  |  | 826 | die "backward UTC segment\n" if $end_utc_day <= $start_utc_day; | 
| 106 | 195 |  |  |  |  | 19757 | my $seg = $incomplete_segment; | 
| 107 | 195 | 50 |  |  |  | 718 | return if $end_utc_day <= $seg->start_utc_day; | 
| 108 | 195 | 50 |  |  |  | 17759 | die "unexpected gap in UTC knowledge\n" | 
| 109 |  |  |  |  |  |  | if $start_utc_day > $seg->start_utc_day; | 
| 110 | 195 |  |  |  |  | 18818 | my $overlap_days = $seg->start_utc_day - $start_utc_day; | 
| 111 | 195 |  |  |  |  | 37948 | $start_utc_day = $seg->start_utc_day; | 
| 112 | 195 |  |  |  |  | 602 | $start_tai_instant += $overlap_days * 86400 * $utc_second_length; | 
| 113 | 195 | 50 | 33 |  |  | 88455 | die "inconsistent UTC knowledge\n" | 
| 114 |  |  |  |  |  |  | unless $start_tai_instant == $seg->start_tai_instant && | 
| 115 |  |  |  |  |  |  | $utc_second_length == $seg->utc_second_length; | 
| 116 | 195 |  |  |  |  | 19114 | my $length_in_tai_seconds = $end_tai_instant - $start_tai_instant; | 
| 117 | 195 |  |  |  |  | 72608 | my $length_in_utc_seconds = $length_in_tai_seconds / | 
| 118 |  |  |  |  |  |  | $utc_second_length; | 
| 119 | 195 |  |  |  |  | 51622 | my $leap_utc_seconds = $length_in_utc_seconds - | 
| 120 |  |  |  |  |  |  | ($end_utc_day - $start_utc_day) * 86400; | 
| 121 | 195 | 50 |  |  |  | 128913 | die "UTC leap too large\n" | 
| 122 |  |  |  |  |  |  | if abs($leap_utc_seconds) >= 60; | 
| 123 | 195 |  |  |  |  | 44754 | $seg->{length_in_tai_seconds} = $length_in_tai_seconds; | 
| 124 | 195 |  |  |  |  | 525 | $seg->{length_in_utc_seconds} = $length_in_utc_seconds; | 
| 125 | 195 |  |  |  |  | 660 | $seg->{leap_utc_seconds} = $leap_utc_seconds; | 
| 126 | 195 |  |  |  |  | 666 | $seg->{last_utc_day} = $end_utc_day - 1; | 
| 127 | 195 |  |  |  |  | 64491 | $seg->{last_day_utc_seconds} = 86400 + $seg->{leap_utc_seconds}; | 
| 128 | 195 |  |  |  |  | 63194 | $seg->{next} = $incomplete_segment = bless({ | 
| 129 |  |  |  |  |  |  | start_utc_day => $end_utc_day, | 
| 130 |  |  |  |  |  |  | start_tai_instant => $end_tai_instant, | 
| 131 |  |  |  |  |  |  | utc_second_length => $next_utc_second_length, | 
| 132 |  |  |  |  |  |  | prev => $seg, | 
| 133 |  |  |  |  |  |  | }, "Time::UTC::Segment::Incomplete"); | 
| 134 | 195 |  |  |  |  | 798 | bless $seg, "Time::UTC::Segment::Complete"; | 
| 135 | 195 |  |  |  |  | 359 | foreach my $what (@{$seg->{when_complete}}) { | 
|  | 195 |  |  |  |  | 639 |  | 
| 136 | 352 |  |  |  |  | 602 | eval { local $SIG{__DIE__}; $what->(); }; | 
|  | 352 |  |  |  |  | 1360 |  | 
|  | 352 |  |  |  |  | 993 |  | 
| 137 |  |  |  |  |  |  | } | 
| 138 | 195 |  |  |  |  | 2127 | delete $seg->{when_complete}; | 
| 139 |  |  |  |  |  |  | } | 
| 140 |  |  |  |  |  |  |  | 
| 141 | 11 |  |  | 11 |  | 144 | use constant _JD_TO_MJD => Math::BigRat->new("2400000.5"); | 
|  | 11 |  |  |  |  | 28 |  | 
|  | 11 |  |  |  |  | 121 |  | 
| 142 |  |  |  |  |  |  |  | 
| 143 | 11 |  |  | 11 |  | 13226 | use constant _TAI_EPOCH_MJD => Math::BigRat->new(36204); | 
|  | 11 |  |  |  |  | 57 |  | 
|  | 11 |  |  |  |  | 70 |  | 
| 144 |  |  |  |  |  |  |  | 
| 145 |  |  |  |  |  |  | sub _add_data_from_tai_utc_dat($$) { | 
| 146 | 5 |  |  | 5 |  | 4342 | my($dat, $end_mjd) = @_; | 
| 147 | 5 |  |  |  |  | 11 | my $seg; | 
| 148 | 5 |  |  |  |  | 50 | while($dat =~ /\G([^\n]*\n)/g) { | 
| 149 | 195 |  |  |  |  | 706 | my $line = $1; | 
| 150 | 195 | 50 |  |  |  | 5228 | $line =~ /\A[\ \t]*[0-9]+[\ \t]*[A-Z]+[\ \t]*[0-9]+[\ \t]* | 
| 151 |  |  |  |  |  |  | =[\ \t]* | 
| 152 |  |  |  |  |  |  | JD[\ \t]*([0-9]+\.?[0-9]*)[\ \t]* | 
| 153 |  |  |  |  |  |  | TAI[\ \t]*-[\ \t]*UTC[\ \t]*=[\ \t]* | 
| 154 |  |  |  |  |  |  | (-?[0-9]+\.?[0-9]*)[\ \t]*S[\ \t]* | 
| 155 |  |  |  |  |  |  | ([\+\-])[\ \t]* | 
| 156 |  |  |  |  |  |  | \([\ \t]*MJD[\ \t]*([\+\-])[\ \t]* | 
| 157 |  |  |  |  |  |  | (-?[0-9]+\.?[0-9]*)[\ \t]*\)[\ \t]* | 
| 158 |  |  |  |  |  |  | X[\ \t]*(-?[0-9]+\.?[0-9]*)[\ \t]*S[\ \t]* | 
| 159 |  |  |  |  |  |  | \n\z/xi | 
| 160 |  |  |  |  |  |  | or die "bad TAI-UTC data\n"; | 
| 161 | 195 |  |  |  |  | 1581 | my($start_jd, $base_difference, $tweak_sign, $base_mjd_sign, | 
| 162 |  |  |  |  |  |  | $base_mjd, $day_tweak) = ($1, $2, $3, $4, $5, $6); | 
| 163 | 195 |  |  |  |  | 748 | my $start_mjd = Math::BigRat->new($start_jd) - _JD_TO_MJD; | 
| 164 | 195 | 50 |  |  |  | 133860 | die "bad UTC segment start date" unless $start_mjd->is_int; | 
| 165 | 195 |  |  |  |  | 3361 | $base_difference = Math::BigRat->new($base_difference); | 
| 166 | 195 |  |  |  |  | 67706 | $base_mjd = Math::BigRat->new($base_mjd); | 
| 167 | 195 |  |  |  |  | 46329 | $day_tweak = Math::BigRat->new($day_tweak); | 
| 168 | 195 | 50 |  |  |  | 68011 | $base_mjd = -$base_mjd if $base_mjd_sign eq "+"; | 
| 169 | 195 | 50 |  |  |  | 566 | $day_tweak = -$day_tweak if $tweak_sign eq "-"; | 
| 170 | 195 |  |  |  |  | 720 | my $nseg = { | 
| 171 |  |  |  |  |  |  | start_utc_day => $start_mjd - _TAI_EPOCH_MJD, | 
| 172 |  |  |  |  |  |  | utc_second_length => 1 + $day_tweak / 86400, | 
| 173 |  |  |  |  |  |  | }; | 
| 174 | 195 |  |  |  |  | 154827 | $nseg->{start_tai_instant} = | 
| 175 |  |  |  |  |  |  | $nseg->{start_utc_day} * 86400 | 
| 176 |  |  |  |  |  |  | + $base_difference | 
| 177 |  |  |  |  |  |  | + ($start_mjd - $base_mjd) * $day_tweak; | 
| 178 | 195 | 100 |  |  |  | 218132 | if(defined $seg) { | 
| 179 | 190 |  |  |  |  | 912 | _add_data($seg->{start_utc_day}, | 
| 180 |  |  |  |  |  |  | $seg->{start_tai_instant}, | 
| 181 |  |  |  |  |  |  | $seg->{utc_second_length}, | 
| 182 |  |  |  |  |  |  | $nseg->{start_utc_day}, | 
| 183 |  |  |  |  |  |  | $nseg->{start_tai_instant}, | 
| 184 |  |  |  |  |  |  | $nseg->{utc_second_length}); | 
| 185 |  |  |  |  |  |  | } | 
| 186 | 195 |  |  |  |  | 2870 | $seg = $nseg; | 
| 187 |  |  |  |  |  |  | } | 
| 188 | 5 | 50 |  |  |  | 28 | die "no TAI-UTC data\n" unless defined $seg; | 
| 189 |  |  |  |  |  |  | # Final segment: we have a minimal start date for the start of | 
| 190 |  |  |  |  |  |  | # the next real UTC segment ($end_mjd), but don't know what UTC | 
| 191 |  |  |  |  |  |  | # will be from that date onwards.  Consequently we don't know | 
| 192 |  |  |  |  |  |  | # the length of the preceding UTC day, and must knock off a day | 
| 193 |  |  |  |  |  |  | # from the segment that we build here. | 
| 194 | 5 |  |  |  |  | 21 | my $end_utc_day = $end_mjd - 1 - _TAI_EPOCH_MJD; | 
| 195 | 5 | 50 |  |  |  | 3129 | if($end_utc_day > $seg->{start_utc_day}) { | 
| 196 | 5 |  |  |  |  | 536 | _add_data($seg->{start_utc_day}, | 
| 197 |  |  |  |  |  |  | $seg->{start_tai_instant}, | 
| 198 |  |  |  |  |  |  | $seg->{utc_second_length}, | 
| 199 |  |  |  |  |  |  | $end_utc_day, | 
| 200 |  |  |  |  |  |  | $seg->{start_tai_instant} + | 
| 201 |  |  |  |  |  |  | (($end_utc_day - $seg->{start_utc_day}) * | 
| 202 |  |  |  |  |  |  | 86400) * | 
| 203 |  |  |  |  |  |  | $seg->{utc_second_length}, | 
| 204 |  |  |  |  |  |  | $seg->{utc_second_length}); | 
| 205 |  |  |  |  |  |  | } | 
| 206 |  |  |  |  |  |  | } | 
| 207 |  |  |  |  |  |  |  | 
| 208 |  |  |  |  |  |  | { | 
| 209 |  |  |  |  |  |  | my $init_dat = do { local $/ = undef;  }; | 
| 210 |  |  |  |  |  |  | close(DATA); | 
| 211 |  |  |  |  |  |  | sub _use_builtin_knowledge() { | 
| 212 | 5 | 50 |  | 5 |  | 2963 | $init_dat =~ s/^[\ \t]*[0-9]+[\ \t]*[A-Z]+[\ \t]*[0-9]+[\ \t]* | 
| 213 |  |  |  |  |  |  | =[\ \t]* | 
| 214 |  |  |  |  |  |  | JD[\ \t]*([0-9]+\.?[0-9]*)[\ \t]* | 
| 215 |  |  |  |  |  |  | unknown[\ \t]*\n\z//xim | 
| 216 |  |  |  |  |  |  | or die "broken built-in TAI-UTC data\n"; | 
| 217 | 5 |  |  |  |  | 27 | my $end_jd = $1; | 
| 218 | 5 |  |  |  |  | 306 | _add_data_from_tai_utc_dat($init_dat, $end_jd - _JD_TO_MJD); | 
| 219 | 5 |  |  |  |  | 66 | $init_dat = undef; | 
| 220 |  |  |  |  |  |  | } | 
| 221 |  |  |  |  |  |  | } | 
| 222 |  |  |  |  |  |  |  | 
| 223 | 11 |  |  | 11 |  | 11732 | use constant _UNIX_EPOCH_MJD => Math::BigRat->new(40586); | 
|  | 11 |  |  |  |  | 25 |  | 
|  | 11 |  |  |  |  | 71 |  | 
| 224 |  |  |  |  |  |  |  | 
| 225 |  |  |  |  |  |  | sub _download_tai_utc_dat() { | 
| 226 |  |  |  |  |  |  | # Annoyingly, TAI-UTC data is not published with any | 
| 227 |  |  |  |  |  |  | # indicator of the extent of its future validity. | 
| 228 |  |  |  |  |  |  | # The IERS never says "there will be no leap second | 
| 229 |  |  |  |  |  |  | # until at least 2005-06-30"; the latest TAI-UTC offset | 
| 230 |  |  |  |  |  |  | # is always valid "until further notice".  However, | 
| 231 |  |  |  |  |  |  | # leap seconds are supposed to be announced at least | 
| 232 |  |  |  |  |  |  | # eight weeks in advance, so here we assume validity of | 
| 233 |  |  |  |  |  |  | # the downloaded data seven weeks into the future. | 
| 234 |  |  |  |  |  |  | # For this reason we only do a direct get from USNO; | 
| 235 |  |  |  |  |  |  | # we do not use proxies which might serve old data. | 
| 236 | 0 |  |  | 0 |  | 0 | my $unix_time = Time::Unix::time(); | 
| 237 | 0 |  |  |  |  | 0 | my $httpresp = HTTP::Tiny->new->get( | 
| 238 |  |  |  |  |  |  | "http://maia.usno.navy.mil/ser7/tai-utc.dat"); | 
| 239 | 0 | 0 |  |  |  | 0 | unless($httpresp->{status} == 200) { | 
| 240 | 0 |  |  |  |  | 0 | die "failed to download tai-utc.dat: ". | 
| 241 | 0 |  |  |  |  | 0 | "@{[$httpresp->{status}]} @{[$httpresp->{reason}]}\n"; | 
|  | 0 |  |  |  |  | 0 |  | 
| 242 |  |  |  |  |  |  | } | 
| 243 | 11 |  |  | 11 |  | 2968 | use integer; | 
|  | 11 |  |  |  |  | 22 |  | 
|  | 11 |  |  |  |  | 103 |  | 
| 244 | 0 |  |  |  |  | 0 | my $now_mjd = $unix_time/86400 + _UNIX_EPOCH_MJD; | 
| 245 | 0 |  |  |  |  | 0 | _add_data_from_tai_utc_dat($httpresp->{content}, $now_mjd + 7*7); | 
| 246 |  |  |  |  |  |  | } | 
| 247 |  |  |  |  |  |  |  | 
| 248 | 11 |  |  | 11 |  | 1022 | use constant _NTP_EPOCH_MJD => Math::BigRat->new(15020); | 
|  | 11 |  |  |  |  | 25 |  | 
|  | 11 |  |  |  |  | 47 |  | 
| 249 |  |  |  |  |  |  |  | 
| 250 |  |  |  |  |  |  | sub _ntp_second_to_tai_day($) { | 
| 251 | 0 |  |  | 0 |  | 0 | my($ntp_sec_str) = @_; | 
| 252 | 0 |  |  |  |  | 0 | return Math::BigRat->new($ntp_sec_str) / 86400 | 
| 253 |  |  |  |  |  |  | + _NTP_EPOCH_MJD - _TAI_EPOCH_MJD; | 
| 254 |  |  |  |  |  |  | } | 
| 255 |  |  |  |  |  |  |  | 
| 256 | 11 |  |  | 11 |  | 2878 | use constant _BIGRAT_ONE => Math::BigRat->new(1); | 
|  | 11 |  |  |  |  | 21 |  | 
|  | 11 |  |  |  |  | 48 |  | 
| 257 |  |  |  |  |  |  |  | 
| 258 |  |  |  |  |  |  | sub _download_leap_seconds_list() { | 
| 259 | 0 | 0 |  | 0 |  | 0 | my $ftp = Net::FTP->new("utcnist2.colorado.edu") | 
| 260 |  |  |  |  |  |  | or die "failed to download leap-seconds.list: FTP error: $@\n"; | 
| 261 | 0 | 0 |  |  |  | 0 | $ftp->login("anonymous","-anonymous\@") | 
| 262 |  |  |  |  |  |  | or die "failed to download leap-seconds.list: FTP error: ". | 
| 263 |  |  |  |  |  |  | $ftp->message; | 
| 264 | 0 | 0 |  |  |  | 0 | $ftp->binary | 
| 265 |  |  |  |  |  |  | or die "failed to download leap-seconds.list: FTP error: ". | 
| 266 |  |  |  |  |  |  | $ftp->message; | 
| 267 | 0 | 0 |  |  |  | 0 | $ftp->cwd("pub") | 
| 268 |  |  |  |  |  |  | or die "failed to download leap-seconds.list: FTP error: ". | 
| 269 |  |  |  |  |  |  | $ftp->message; | 
| 270 | 0 | 0 |  |  |  | 0 | my $ftpd = $ftp->retr("leap-seconds.list") | 
| 271 |  |  |  |  |  |  | or die "failed to download leap-seconds.list: FTP error: ". | 
| 272 |  |  |  |  |  |  | $ftp->message; | 
| 273 | 0 |  |  |  |  | 0 | my $list = ""; | 
| 274 | 0 |  |  |  |  | 0 | while(1) { | 
| 275 | 0 |  |  |  |  | 0 | my $n = $ftpd->sysread($list, 4096, length($list)); | 
| 276 | 0 | 0 |  |  |  | 0 | defined $n or die "failed to download leap-seconds.list: $!\n"; | 
| 277 | 0 | 0 |  |  |  | 0 | last if $n == 0; | 
| 278 |  |  |  |  |  |  | } | 
| 279 |  |  |  |  |  |  | $ftpd->close | 
| 280 | 0 | 0 |  |  |  | 0 | or die "failed to download leap-seconds.list: FTP error: ". | 
| 281 |  |  |  |  |  |  | $ftp->message; | 
| 282 | 0 | 0 |  |  |  | 0 | die "malformed leap-seconds.list" unless $list =~ /\n\z/; | 
| 283 | 0 | 0 |  |  |  | 0 | $list =~ /^\#h([ \t0-9a-fA-F]+)$/m | 
| 284 |  |  |  |  |  |  | or die "no hash in leap-seconds.list"; | 
| 285 | 0 |  |  |  |  | 0 | (my $hash = $1) =~ tr/A-F \t/a-f/d; | 
| 286 | 0 |  |  |  |  | 0 | my $data_to_hash = ""; | 
| 287 | 0 |  |  |  |  | 0 | while($list =~ /^(?:\#[\$\@])?[ \t]*([0-9][^\#\n]*)[#\n]/mg) { | 
| 288 | 0 |  |  |  |  | 0 | $data_to_hash .= $1; | 
| 289 |  |  |  |  |  |  | } | 
| 290 | 0 |  |  |  |  | 0 | $data_to_hash =~ tr/0-9//cd; | 
| 291 | 0 | 0 |  |  |  | 0 | die "hash mismatch in leap-seconds.list" | 
| 292 |  |  |  |  |  |  | unless sha1_hex($data_to_hash) eq $hash; | 
| 293 | 0 |  |  |  |  | 0 | my($start_utc_day, $start_tai_instant); | 
| 294 | 0 |  |  |  |  | 0 | while($list =~ /^([^#\n][^\n]*)$/mg) { | 
| 295 | 0 |  |  |  |  | 0 | my $line = $1; | 
| 296 | 0 | 0 |  |  |  | 0 | $line =~ /\A[ \t]*([0-9]+)[ \t]+([0-9]+)[ \t]*(?:\#|\z)/ | 
| 297 |  |  |  |  |  |  | or die "malformed data line in leap-seconds.list"; | 
| 298 | 0 |  |  |  |  | 0 | my($next_start_ntp_sec, $ndiff) = ($1, $2); | 
| 299 | 0 |  |  |  |  | 0 | my $next_start_utc_day = | 
| 300 |  |  |  |  |  |  | _ntp_second_to_tai_day($next_start_ntp_sec); | 
| 301 | 0 | 0 |  |  |  | 0 | die "bad transition date in leap-seconds.list" | 
| 302 |  |  |  |  |  |  | unless $next_start_utc_day->is_int; | 
| 303 | 0 |  |  |  |  | 0 | my $next_start_tai_instant = | 
| 304 |  |  |  |  |  |  | $next_start_utc_day*86400 + Math::BigRat->new($ndiff); | 
| 305 | 0 | 0 |  |  |  | 0 | if(defined $start_utc_day) { | 
| 306 | 0 |  |  |  |  | 0 | _add_data($start_utc_day, | 
| 307 |  |  |  |  |  |  | $start_tai_instant, | 
| 308 |  |  |  |  |  |  | _BIGRAT_ONE, | 
| 309 |  |  |  |  |  |  | $next_start_utc_day, | 
| 310 |  |  |  |  |  |  | $next_start_tai_instant, | 
| 311 |  |  |  |  |  |  | _BIGRAT_ONE); | 
| 312 |  |  |  |  |  |  | } | 
| 313 | 0 |  |  |  |  | 0 | $start_utc_day = $next_start_utc_day; | 
| 314 | 0 |  |  |  |  | 0 | $start_tai_instant = $next_start_tai_instant; | 
| 315 |  |  |  |  |  |  | } | 
| 316 | 0 | 0 |  |  |  | 0 | $list =~ /^\#\@[ \t]*([0-9]+)[ \t]*$/m | 
| 317 |  |  |  |  |  |  | or die "no expiry date in leap-seconds.list"; | 
| 318 | 0 |  |  |  |  | 0 | my $expsec = $1; | 
| 319 | 0 |  |  |  |  | 0 | my $end_utc_day = _ntp_second_to_tai_day($expsec)->bfloor - 1; | 
| 320 | 0 | 0 |  |  |  | 0 | if(defined $start_utc_day) { | 
| 321 | 0 |  |  |  |  | 0 | _add_data($start_utc_day, | 
| 322 |  |  |  |  |  |  | $start_tai_instant, | 
| 323 |  |  |  |  |  |  | _BIGRAT_ONE, | 
| 324 |  |  |  |  |  |  | $end_utc_day, | 
| 325 |  |  |  |  |  |  | $start_tai_instant + | 
| 326 |  |  |  |  |  |  | ($end_utc_day - $start_utc_day) * 86400, | 
| 327 |  |  |  |  |  |  | _BIGRAT_ONE); | 
| 328 |  |  |  |  |  |  | } | 
| 329 |  |  |  |  |  |  | } | 
| 330 |  |  |  |  |  |  |  | 
| 331 |  |  |  |  |  |  | sub _download_latest_data() { | 
| 332 | 0 |  |  |  |  |  | eval { local $SIG{__DIE__}; _download_leap_seconds_list(); 1 } | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 333 | 0 | 0 |  | 0 |  |  | or eval { local $SIG{__DIE__}; _download_tai_utc_dat(); 1 }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 334 |  |  |  |  |  |  | } | 
| 335 |  |  |  |  |  |  |  | 
| 336 |  |  |  |  |  |  | { | 
| 337 |  |  |  |  |  |  | my $last_download = 0; | 
| 338 |  |  |  |  |  |  | my $wait_until = 0; | 
| 339 |  |  |  |  |  |  | sub _maybe_download_latest_data() { | 
| 340 | 18 |  |  | 18 |  | 60 | my $time = time; | 
| 341 | 18 | 100 | 66 |  |  | 155 | return unless $time >= $wait_until || $time < $last_download; | 
| 342 | 3 |  |  |  |  | 7 | $last_download = $time; | 
| 343 | 3 |  |  |  |  | 1153 | $wait_until = $last_download + 3600 + rand(3600); | 
| 344 | 3 | 50 |  |  |  | 61 | _download_latest_data() and | 
| 345 |  |  |  |  |  |  | $wait_until = $last_download + 20*86400 + rand(2*86400); | 
| 346 |  |  |  |  |  |  | } | 
| 347 |  |  |  |  |  |  | } | 
| 348 |  |  |  |  |  |  |  | 
| 349 |  |  |  |  |  |  | my $try_to_extend_knowledge = \&_use_builtin_knowledge; | 
| 350 |  |  |  |  |  |  |  | 
| 351 |  |  |  |  |  |  | =head1 METHODS | 
| 352 |  |  |  |  |  |  |  | 
| 353 |  |  |  |  |  |  | =head2 Information querying | 
| 354 |  |  |  |  |  |  |  | 
| 355 |  |  |  |  |  |  | Most methods merely query the segment data.  The data are strictly | 
| 356 |  |  |  |  |  |  | read-only. | 
| 357 |  |  |  |  |  |  |  | 
| 358 |  |  |  |  |  |  | The methods will C if information is requested that is not available. | 
| 359 |  |  |  |  |  |  | This happens when looking further ahead than UTC has been defined. | 
| 360 |  |  |  |  |  |  | UTC is defined only a few months in advance. | 
| 361 |  |  |  |  |  |  |  | 
| 362 |  |  |  |  |  |  | All numeric values are returned as C objects. | 
| 363 |  |  |  |  |  |  |  | 
| 364 |  |  |  |  |  |  | =over | 
| 365 |  |  |  |  |  |  |  | 
| 366 |  |  |  |  |  |  | =cut | 
| 367 |  |  |  |  |  |  |  | 
| 368 |  |  |  |  |  |  | sub _data_unavailable { | 
| 369 | 22 |  |  | 22 |  | 47 | my($self, $method) = @_; | 
| 370 | 22 | 50 |  |  |  | 69 | if(defined $try_to_extend_knowledge) { | 
| 371 | 22 |  |  |  |  | 34 | eval { local $SIG{__DIE__}; | 
|  | 22 |  |  |  |  | 98 |  | 
| 372 | 22 |  |  |  |  | 37 | my $ttek = $try_to_extend_knowledge; | 
| 373 | 22 |  |  |  |  | 31 | $try_to_extend_knowledge = undef; | 
| 374 | 22 |  |  |  |  | 51 | $ttek->(); | 
| 375 |  |  |  |  |  |  | }; | 
| 376 | 22 |  |  |  |  | 52 | $try_to_extend_knowledge = \&_maybe_download_latest_data; | 
| 377 | 22 | 100 |  |  |  | 86 | if(ref($self) eq "Time::UTC::Segment::Complete") { | 
| 378 | 4 |  |  |  |  | 28 | return $self->$method; | 
| 379 |  |  |  |  |  |  | } | 
| 380 |  |  |  |  |  |  | } | 
| 381 | 18 |  |  |  |  | 2478 | croak "data not available yet"; | 
| 382 |  |  |  |  |  |  | } | 
| 383 |  |  |  |  |  |  |  | 
| 384 |  |  |  |  |  |  | sub _data_unavailable_method($) { | 
| 385 | 88 |  |  | 88 |  | 147 | my($method) = @_; | 
| 386 | 88 |  |  | 22 |  | 406 | return sub { $_[0]->_data_unavailable($method) }; | 
|  | 22 |  |  |  |  | 4514 |  | 
| 387 |  |  |  |  |  |  | } | 
| 388 |  |  |  |  |  |  |  | 
| 389 |  |  |  |  |  |  | =item $seg->start_tai_instant | 
| 390 |  |  |  |  |  |  |  | 
| 391 |  |  |  |  |  |  | The instant at which this segment starts, in TAI form: a C | 
| 392 |  |  |  |  |  |  | giving the number of TAI seconds since the TAI epoch. | 
| 393 |  |  |  |  |  |  |  | 
| 394 |  |  |  |  |  |  | =cut | 
| 395 |  |  |  |  |  |  |  | 
| 396 |  |  |  |  |  |  | sub start_tai_instant { | 
| 397 | 754 |  |  | 754 | 1 | 6080 | $_[0]->{start_tai_instant} | 
| 398 |  |  |  |  |  |  | } | 
| 399 |  |  |  |  |  |  |  | 
| 400 |  |  |  |  |  |  | =item $seg->end_tai_instant | 
| 401 |  |  |  |  |  |  |  | 
| 402 |  |  |  |  |  |  | The instant at which this segment ends, in TAI form: a C | 
| 403 |  |  |  |  |  |  | giving the number of TAI seconds since the TAI epoch. | 
| 404 |  |  |  |  |  |  |  | 
| 405 |  |  |  |  |  |  | =cut | 
| 406 |  |  |  |  |  |  |  | 
| 407 |  |  |  |  |  |  | sub Time::UTC::Segment::Complete::end_tai_instant { | 
| 408 | 117 |  |  | 117 |  | 632 | $_[0]->{next}->{start_tai_instant} | 
| 409 |  |  |  |  |  |  | } | 
| 410 |  |  |  |  |  |  |  | 
| 411 |  |  |  |  |  |  | *Time::UTC::Segment::Incomplete::end_tai_instant = | 
| 412 |  |  |  |  |  |  | _data_unavailable_method("end_tai_instant"); | 
| 413 |  |  |  |  |  |  |  | 
| 414 |  |  |  |  |  |  | =item $seg->length_in_tai_seconds | 
| 415 |  |  |  |  |  |  |  | 
| 416 |  |  |  |  |  |  | The duration of this segment, measured in TAI seconds, as a | 
| 417 |  |  |  |  |  |  | C. | 
| 418 |  |  |  |  |  |  |  | 
| 419 |  |  |  |  |  |  | =cut | 
| 420 |  |  |  |  |  |  |  | 
| 421 |  |  |  |  |  |  | sub Time::UTC::Segment::Complete::length_in_tai_seconds { | 
| 422 | 78 |  |  | 78 |  | 32236 | $_[0]->{length_in_tai_seconds} | 
| 423 |  |  |  |  |  |  | } | 
| 424 |  |  |  |  |  |  |  | 
| 425 |  |  |  |  |  |  | *Time::UTC::Segment::Incomplete::length_in_tai_seconds = | 
| 426 |  |  |  |  |  |  | _data_unavailable_method("length_in_tai_seconds"); | 
| 427 |  |  |  |  |  |  |  | 
| 428 |  |  |  |  |  |  | =item $seg->start_utc_day | 
| 429 |  |  |  |  |  |  |  | 
| 430 |  |  |  |  |  |  | The first UTC day of this segment: a C giving the number | 
| 431 |  |  |  |  |  |  | of days since the TAI epoch. | 
| 432 |  |  |  |  |  |  |  | 
| 433 |  |  |  |  |  |  | =cut | 
| 434 |  |  |  |  |  |  |  | 
| 435 |  |  |  |  |  |  | sub start_utc_day { | 
| 436 | 2307 |  |  | 2307 | 1 | 211166 | $_[0]->{start_utc_day} | 
| 437 |  |  |  |  |  |  | } | 
| 438 |  |  |  |  |  |  |  | 
| 439 |  |  |  |  |  |  | =item $seg->last_utc_day | 
| 440 |  |  |  |  |  |  |  | 
| 441 |  |  |  |  |  |  | The last UTC day of this segment: a C giving the number | 
| 442 |  |  |  |  |  |  | of days since the TAI epoch. | 
| 443 |  |  |  |  |  |  |  | 
| 444 |  |  |  |  |  |  | =cut | 
| 445 |  |  |  |  |  |  |  | 
| 446 |  |  |  |  |  |  | sub Time::UTC::Segment::Complete::last_utc_day { | 
| 447 | 585 |  |  | 585 |  | 182550 | $_[0]->{last_utc_day} | 
| 448 |  |  |  |  |  |  | } | 
| 449 |  |  |  |  |  |  |  | 
| 450 |  |  |  |  |  |  | *Time::UTC::Segment::Incomplete::last_utc_day = | 
| 451 |  |  |  |  |  |  | _data_unavailable_method("last_utc_day"); | 
| 452 |  |  |  |  |  |  |  | 
| 453 |  |  |  |  |  |  | =item $seg->end_utc_day | 
| 454 |  |  |  |  |  |  |  | 
| 455 |  |  |  |  |  |  | The first UTC day after the end of this segment: a C | 
| 456 |  |  |  |  |  |  | giving the number of days since the TAI epoch. | 
| 457 |  |  |  |  |  |  |  | 
| 458 |  |  |  |  |  |  | =cut | 
| 459 |  |  |  |  |  |  |  | 
| 460 |  |  |  |  |  |  | sub Time::UTC::Segment::Complete::end_utc_day { | 
| 461 | 234 |  |  | 234 |  | 15362 | $_[0]->{next}->{start_utc_day} | 
| 462 |  |  |  |  |  |  | } | 
| 463 |  |  |  |  |  |  |  | 
| 464 |  |  |  |  |  |  | *Time::UTC::Segment::Incomplete::end_utc_day = | 
| 465 |  |  |  |  |  |  | _data_unavailable_method("end_utc_day"); | 
| 466 |  |  |  |  |  |  |  | 
| 467 |  |  |  |  |  |  | =item $seg->utc_second_length | 
| 468 |  |  |  |  |  |  |  | 
| 469 |  |  |  |  |  |  | The length of the UTC second in this segment, measured in TAI seconds, | 
| 470 |  |  |  |  |  |  | as a C. | 
| 471 |  |  |  |  |  |  |  | 
| 472 |  |  |  |  |  |  | =cut | 
| 473 |  |  |  |  |  |  |  | 
| 474 |  |  |  |  |  |  | sub utc_second_length { | 
| 475 | 235 |  |  | 235 | 1 | 22705 | $_[0]->{utc_second_length} | 
| 476 |  |  |  |  |  |  | } | 
| 477 |  |  |  |  |  |  |  | 
| 478 |  |  |  |  |  |  | =item $seg->leap_utc_seconds | 
| 479 |  |  |  |  |  |  |  | 
| 480 |  |  |  |  |  |  | The number of extra UTC seconds inserted at the end of the last day of | 
| 481 |  |  |  |  |  |  | this segment, as a C.  May be negative. | 
| 482 |  |  |  |  |  |  |  | 
| 483 |  |  |  |  |  |  | =cut | 
| 484 |  |  |  |  |  |  |  | 
| 485 |  |  |  |  |  |  | sub Time::UTC::Segment::Complete::leap_utc_seconds { | 
| 486 | 234 |  |  | 234 |  | 943 | $_[0]->{leap_utc_seconds} | 
| 487 |  |  |  |  |  |  | } | 
| 488 |  |  |  |  |  |  |  | 
| 489 |  |  |  |  |  |  | *Time::UTC::Segment::Incomplete::leap_utc_seconds = | 
| 490 |  |  |  |  |  |  | _data_unavailable_method("leap_utc_seconds"); | 
| 491 |  |  |  |  |  |  |  | 
| 492 |  |  |  |  |  |  | =item $seg->last_day_utc_seconds | 
| 493 |  |  |  |  |  |  |  | 
| 494 |  |  |  |  |  |  | The number of UTC seconds in the last day of this segment, as a | 
| 495 |  |  |  |  |  |  | C. | 
| 496 |  |  |  |  |  |  |  | 
| 497 |  |  |  |  |  |  | =cut | 
| 498 |  |  |  |  |  |  |  | 
| 499 |  |  |  |  |  |  | sub Time::UTC::Segment::Complete::last_day_utc_seconds { | 
| 500 | 117 |  |  | 117 |  | 50008 | $_[0]->{last_day_utc_seconds} | 
| 501 |  |  |  |  |  |  | } | 
| 502 |  |  |  |  |  |  |  | 
| 503 |  |  |  |  |  |  | *Time::UTC::Segment::Incomplete::last_day_utc_seconds = | 
| 504 |  |  |  |  |  |  | _data_unavailable_method("last_day_utc_seconds"); | 
| 505 |  |  |  |  |  |  |  | 
| 506 |  |  |  |  |  |  | =item $seg->length_in_utc_seconds | 
| 507 |  |  |  |  |  |  |  | 
| 508 |  |  |  |  |  |  | The duration of this segment, measured in UTC seconds, as a | 
| 509 |  |  |  |  |  |  | C. | 
| 510 |  |  |  |  |  |  |  | 
| 511 |  |  |  |  |  |  | =cut | 
| 512 |  |  |  |  |  |  |  | 
| 513 |  |  |  |  |  |  | sub Time::UTC::Segment::Complete::length_in_utc_seconds { | 
| 514 | 78 |  |  | 78 |  | 44340 | $_[0]->{length_in_utc_seconds} | 
| 515 |  |  |  |  |  |  | } | 
| 516 |  |  |  |  |  |  |  | 
| 517 |  |  |  |  |  |  | *Time::UTC::Segment::Incomplete::length_in_utc_seconds = | 
| 518 |  |  |  |  |  |  | _data_unavailable_method("length_in_utc_seconds"); | 
| 519 |  |  |  |  |  |  |  | 
| 520 |  |  |  |  |  |  | =item $seg->prev | 
| 521 |  |  |  |  |  |  |  | 
| 522 |  |  |  |  |  |  | The immediately preceding segment.  C if there is no preceding | 
| 523 |  |  |  |  |  |  | segment, because this segment covers the start of UTC service at the | 
| 524 |  |  |  |  |  |  | beginning of 1961. | 
| 525 |  |  |  |  |  |  |  | 
| 526 |  |  |  |  |  |  | =cut | 
| 527 |  |  |  |  |  |  |  | 
| 528 |  |  |  |  |  |  | sub prev { | 
| 529 | 39 |  |  | 39 | 1 | 151 | $_[0]->{prev} | 
| 530 |  |  |  |  |  |  | } | 
| 531 |  |  |  |  |  |  |  | 
| 532 |  |  |  |  |  |  | =item $seg->next | 
| 533 |  |  |  |  |  |  |  | 
| 534 |  |  |  |  |  |  | The immediately following segment.  In the event that UTC ever becomes | 
| 535 |  |  |  |  |  |  | fully defined, either by being defined for the entire future or by being | 
| 536 |  |  |  |  |  |  | withdrawn altogether, there will be a final segment for which this will | 
| 537 |  |  |  |  |  |  | be C. | 
| 538 |  |  |  |  |  |  |  | 
| 539 |  |  |  |  |  |  | =cut | 
| 540 |  |  |  |  |  |  |  | 
| 541 |  |  |  |  |  |  | sub Time::UTC::Segment::Complete::next { | 
| 542 | 784 |  |  | 784 |  | 142279 | $_[0]->{next} | 
| 543 |  |  |  |  |  |  | } | 
| 544 |  |  |  |  |  |  |  | 
| 545 |  |  |  |  |  |  | *Time::UTC::Segment::Incomplete::next = _data_unavailable_method("next"); | 
| 546 |  |  |  |  |  |  |  | 
| 547 |  |  |  |  |  |  | =back | 
| 548 |  |  |  |  |  |  |  | 
| 549 |  |  |  |  |  |  | =head2 Completeness | 
| 550 |  |  |  |  |  |  |  | 
| 551 |  |  |  |  |  |  | Segments can be classified as "complete" and "incomplete".  For complete | 
| 552 |  |  |  |  |  |  | segments, all information-querying methods give answers.  For incomplete | 
| 553 |  |  |  |  |  |  | segments, only a few data are available: the methods C, | 
| 554 |  |  |  |  |  |  | C, C, and C will give correct | 
| 555 |  |  |  |  |  |  | answers, but other methods will C. | 
| 556 |  |  |  |  |  |  |  | 
| 557 |  |  |  |  |  |  | An incomplete segment can become complete, as new information becomes | 
| 558 |  |  |  |  |  |  | available, when updated UTC data is (automatically) downloaded.  When this | 
| 559 |  |  |  |  |  |  | happens, the resulting complete segment cannot be distinguished from | 
| 560 |  |  |  |  |  |  | any other complete segment. | 
| 561 |  |  |  |  |  |  |  | 
| 562 |  |  |  |  |  |  | Only one incomplete segment exists at a time. | 
| 563 |  |  |  |  |  |  |  | 
| 564 |  |  |  |  |  |  | =over | 
| 565 |  |  |  |  |  |  |  | 
| 566 |  |  |  |  |  |  | =item $seg->complete_p | 
| 567 |  |  |  |  |  |  |  | 
| 568 |  |  |  |  |  |  | Returns a truth value indicating whether the segment is currently complete. | 
| 569 |  |  |  |  |  |  |  | 
| 570 |  |  |  |  |  |  | =cut | 
| 571 |  |  |  |  |  |  |  | 
| 572 | 0 |  |  | 0 |  | 0 | sub Time::UTC::Segment::Complete::complete_p { 1 } | 
| 573 |  |  |  |  |  |  |  | 
| 574 | 0 |  |  | 0 |  | 0 | sub Time::UTC::Segment::Incomplete::complete_p { 0 } | 
| 575 |  |  |  |  |  |  |  | 
| 576 |  |  |  |  |  |  | =item $seg->when_complete(WHAT) | 
| 577 |  |  |  |  |  |  |  | 
| 578 |  |  |  |  |  |  | I must be a reference to a function which takes no arguments. | 
| 579 |  |  |  |  |  |  | When the segment is complete, the function will be called.  If the | 
| 580 |  |  |  |  |  |  | segment is already complete then the function is called immediately; | 
| 581 |  |  |  |  |  |  | otherwise the function is noted and will be called when the segment | 
| 582 |  |  |  |  |  |  | becomes complete due to the availability of new information. | 
| 583 |  |  |  |  |  |  |  | 
| 584 |  |  |  |  |  |  | This is a one-shot operation.  To do something similar for all segments, | 
| 585 |  |  |  |  |  |  | see C in C. | 
| 586 |  |  |  |  |  |  |  | 
| 587 |  |  |  |  |  |  | =cut | 
| 588 |  |  |  |  |  |  |  | 
| 589 |  |  |  |  |  |  | sub Time::UTC::Segment::Complete::when_complete { | 
| 590 | 0 |  |  | 0 |  | 0 | my($self, $what) = @_; | 
| 591 | 0 |  |  |  |  | 0 | eval { local $SIG{__DIE__}; $what->(); }; | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 592 |  |  |  |  |  |  | } | 
| 593 |  |  |  |  |  |  |  | 
| 594 |  |  |  |  |  |  | sub Time::UTC::Segment::Incomplete::when_complete { | 
| 595 | 373 |  |  | 373 |  | 602 | my($self, $what) = @_; | 
| 596 | 373 |  |  |  |  | 474 | push @{$self->{when_complete}}, $what; | 
|  | 373 |  |  |  |  | 2379 |  | 
| 597 |  |  |  |  |  |  | } | 
| 598 |  |  |  |  |  |  |  | 
| 599 |  |  |  |  |  |  | =back | 
| 600 |  |  |  |  |  |  |  | 
| 601 |  |  |  |  |  |  | =head1 INVARIANTS | 
| 602 |  |  |  |  |  |  |  | 
| 603 |  |  |  |  |  |  | The following relations hold for all segments: | 
| 604 |  |  |  |  |  |  |  | 
| 605 |  |  |  |  |  |  | $seg->length_in_tai_seconds == | 
| 606 |  |  |  |  |  |  | $seg->end_tai_instant - $seg->start_tai_instant | 
| 607 |  |  |  |  |  |  |  | 
| 608 |  |  |  |  |  |  | $seg->last_utc_day + 1 == $seg->end_utc_day | 
| 609 |  |  |  |  |  |  |  | 
| 610 |  |  |  |  |  |  | $seg->last_day_utc_seconds == 86400 + $seg->leap_utc_seconds | 
| 611 |  |  |  |  |  |  |  | 
| 612 |  |  |  |  |  |  | $seg->length_in_utc_seconds == | 
| 613 |  |  |  |  |  |  | 86400 * ($seg->last_utc_day - $seg->start_utc_day) + | 
| 614 |  |  |  |  |  |  | $seg->last_day_utc_seconds | 
| 615 |  |  |  |  |  |  |  | 
| 616 |  |  |  |  |  |  | $seg->length_in_tai_seconds == | 
| 617 |  |  |  |  |  |  | $seg->length_in_utc_seconds * $seg->utc_second_length | 
| 618 |  |  |  |  |  |  |  | 
| 619 |  |  |  |  |  |  | $seg->next->prev == $seg | 
| 620 |  |  |  |  |  |  |  | 
| 621 |  |  |  |  |  |  | $seg->end_tai_instant == $seg->next->start_tai_instant | 
| 622 |  |  |  |  |  |  |  | 
| 623 |  |  |  |  |  |  | $seg->end_utc_day == $seg->next->start_utc_day | 
| 624 |  |  |  |  |  |  |  | 
| 625 |  |  |  |  |  |  | =head1 SEE ALSO | 
| 626 |  |  |  |  |  |  |  | 
| 627 |  |  |  |  |  |  | L | 
| 628 |  |  |  |  |  |  |  | 
| 629 |  |  |  |  |  |  | =head1 AUTHOR | 
| 630 |  |  |  |  |  |  |  | 
| 631 |  |  |  |  |  |  | Andrew Main (Zefram) | 
| 632 |  |  |  |  |  |  |  | 
| 633 |  |  |  |  |  |  | =head1 COPYRIGHT | 
| 634 |  |  |  |  |  |  |  | 
| 635 |  |  |  |  |  |  | Copyright (C) 2005, 2006, 2007, 2009, 2010, 2012 | 
| 636 |  |  |  |  |  |  | Andrew Main (Zefram) | 
| 637 |  |  |  |  |  |  |  | 
| 638 |  |  |  |  |  |  | =head1 LICENSE | 
| 639 |  |  |  |  |  |  |  | 
| 640 |  |  |  |  |  |  | This module is free software; you can redistribute it and/or modify it | 
| 641 |  |  |  |  |  |  | under the same terms as Perl itself. | 
| 642 |  |  |  |  |  |  |  | 
| 643 |  |  |  |  |  |  | =cut | 
| 644 |  |  |  |  |  |  |  | 
| 645 |  |  |  |  |  |  | 1; | 
| 646 |  |  |  |  |  |  |  | 
| 647 |  |  |  |  |  |  | __DATA__ |