File Coverage

blib/lib/Time/UTC/Segment.pm
Criterion Covered Total %
statement 125 176 71.0
branch 20 52 38.4
condition 3 6 50.0
subroutine 34 41 82.9
pod 5 5 100.0
total 187 280 66.7


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 12     12   92798 { use 5.006; }
  12         44  
56 12     12   65 use warnings;
  12         23  
  12         372  
57 12     12   63 use strict;
  12         28  
  12         356  
58              
59 12     12   58 use Carp qw(croak);
  12         24  
  12         686  
60 12     12   5514 use Digest::SHA1 qw(sha1_hex);
  12         7572  
  12         697  
61 12     12   927 use Math::BigRat 0.13;
  12         127620  
  12         69  
62 12     12   19197 use Time::Unix 1.02 ();
  12         35411  
  12         4753  
63              
64             our $VERSION = "0.009";
65              
66             @Time::UTC::Segment::Complete::ISA = qw(Time::UTC::Segment);
67             @Time::UTC::Segment::Incomplete::ISA = qw(Time::UTC::Segment);
68              
69             =head1 CONSTRUCTOR
70              
71             Objects of this class are not created by users, but are generated
72             internally. New segments appear when updated UTC data is downloaded;
73             this is done automatically as required. Segments are accessed from each
74             other by means of the C and C methods.
75              
76             =over
77              
78             =item Time::UTC::Segment->start
79              
80             Returns the first segment of the UTC description.
81              
82             =back
83              
84             =cut
85              
86             my $incomplete_segment;
87              
88             {
89             my $start_segment = $incomplete_segment = bless({
90             start_utc_day => Math::BigRat->new(1096),
91             start_tai_instant =>
92             1096*86400 + Math::BigRat->new("1.4228180"),
93             utc_second_length => 1 + Math::BigRat->new("0.001296") / 86400,
94             }, "Time::UTC::Segment::Incomplete");
95             sub start {
96 41     41 1 209 return $start_segment;
97             }
98             }
99              
100             sub _add_data($$$$$$) {
101 205     205   7700 my($start_utc_day, $start_tai_instant, $utc_second_length,
102             $end_utc_day, $end_tai_instant, $next_utc_second_length) = @_;
103 205 50       969 die "backward UTC segment\n" if $end_utc_day <= $start_utc_day;
104 205         20348 my $seg = $incomplete_segment;
105 205 50       1087 return if $end_utc_day <= $seg->start_utc_day;
106 205 50       14386 die "unexpected gap in UTC knowledge\n"
107             if $start_utc_day > $seg->start_utc_day;
108 205         15383 my $overlap_days = $seg->start_utc_day - $start_utc_day;
109 205         38416 $start_utc_day = $seg->start_utc_day;
110 205         703 $start_tai_instant += $overlap_days * 86400 * $utc_second_length;
111 205 50 33     213985 die "inconsistent UTC knowledge\n"
112             unless $start_tai_instant == $seg->start_tai_instant &&
113             $utc_second_length == $seg->utc_second_length;
114 205         16756 my $length_in_tai_seconds = $end_tai_instant - $start_tai_instant;
115 205         93919 my $length_in_utc_seconds = $length_in_tai_seconds /
116             $utc_second_length;
117 205         68824 my $leap_utc_seconds = $length_in_utc_seconds -
118             ($end_utc_day - $start_utc_day) * 86400;
119 205 50       264630 die "UTC leap too large\n"
120             if abs($leap_utc_seconds) >= 60;
121 205         159290 $seg->{length_in_tai_seconds} = $length_in_tai_seconds;
122 205         659 $seg->{length_in_utc_seconds} = $length_in_utc_seconds;
123 205         802 $seg->{leap_utc_seconds} = $leap_utc_seconds;
124 205         793 $seg->{last_utc_day} = $end_utc_day - 1;
125 205         178902 $seg->{last_day_utc_seconds} = 86400 + $seg->{leap_utc_seconds};
126 205         180424 $seg->{next} = $incomplete_segment = bless({
127             start_utc_day => $end_utc_day,
128             start_tai_instant => $end_tai_instant,
129             utc_second_length => $next_utc_second_length,
130             prev => $seg,
131             }, "Time::UTC::Segment::Incomplete");
132 205         700 bless $seg, "Time::UTC::Segment::Complete";
133 205         468 foreach my $what (@{$seg->{when_complete}}) {
  205         863  
134 370         688 eval { local $SIG{__DIE__}; $what->(); };
  370         1516  
  370         1244  
135             }
136 205         2423 delete $seg->{when_complete};
137             }
138              
139 12     12   121 use constant _JD_TO_MJD => Math::BigRat->new("2400000.5");
  12         28  
  12         107  
140              
141 12     12   19193 use constant _TAI_EPOCH_MJD => Math::BigRat->new(36204);
  12         35  
  12         58  
142              
143             sub _add_data_from_tai_utc_dat($$) {
144 5     5   7596 my($dat, $end_mjd) = @_;
145 5         15 my $seg;
146 5         47 while($dat =~ /\G([^\n]*\n)/g) {
147 205         852 my $line = $1;
148 205 50       4218 $line =~ /\A[\ \t]*[0-9]+[\ \t]*[A-Z]+[\ \t]*[0-9]+[\ \t]*
149             =[\ \t]*
150             JD[\ \t]*([0-9]+\.?[0-9]*)[\ \t]*
151             TAI[\ \t]*-[\ \t]*UTC[\ \t]*=[\ \t]*
152             (-?[0-9]+\.?[0-9]*)[\ \t]*S[\ \t]*
153             ([\+\-])[\ \t]*
154             \([\ \t]*MJD[\ \t]*([\+\-])[\ \t]*
155             (-?[0-9]+\.?[0-9]*)[\ \t]*\)[\ \t]*
156             X[\ \t]*(-?[0-9]+\.?[0-9]*)[\ \t]*S[\ \t]*
157             \n\z/xi
158             or die "bad TAI-UTC data\n";
159 205         1726 my($start_jd, $base_difference, $tweak_sign, $base_mjd_sign,
160             $base_mjd, $day_tweak) = ($1, $2, $3, $4, $5, $6);
161 205         929 my $start_mjd = Math::BigRat->new($start_jd) - _JD_TO_MJD;
162 205 50       233052 die "bad UTC segment start date" unless $start_mjd->is_int;
163 205         4275 $base_difference = Math::BigRat->new($base_difference);
164 205         160451 $base_mjd = Math::BigRat->new($base_mjd);
165 205         135974 $day_tweak = Math::BigRat->new($day_tweak);
166 205 50       166224 $base_mjd = -$base_mjd if $base_mjd_sign eq "+";
167 205 50       749 $day_tweak = -$day_tweak if $tweak_sign eq "-";
168 205         824 my $nseg = {
169             start_utc_day => $start_mjd - _TAI_EPOCH_MJD,
170             utc_second_length => 1 + $day_tweak / 86400,
171             };
172             $nseg->{start_tai_instant} =
173 205         402584 $nseg->{start_utc_day} * 86400
174             + $base_difference
175             + ($start_mjd - $base_mjd) * $day_tweak;
176 205 100       373163 if(defined $seg) {
177             _add_data($seg->{start_utc_day},
178             $seg->{start_tai_instant},
179             $seg->{utc_second_length},
180             $nseg->{start_utc_day},
181             $nseg->{start_tai_instant},
182 200         1202 $nseg->{utc_second_length});
183             }
184 205         3638 $seg = $nseg;
185             }
186 5 50       23 die "no TAI-UTC data\n" unless defined $seg;
187             # Final segment: we have a minimal start date for the start of
188             # the next real UTC segment ($end_mjd), but don't know what UTC
189             # will be from that date onwards. Consequently we don't know
190             # the length of the preceding UTC day, and must knock off a day
191             # from the segment that we build here.
192 5         20 my $end_utc_day = $end_mjd - 1 - _TAI_EPOCH_MJD;
193 5 50       4505 if($end_utc_day > $seg->{start_utc_day}) {
194             _add_data($seg->{start_utc_day},
195             $seg->{start_tai_instant},
196             $seg->{utc_second_length},
197             $end_utc_day,
198             $seg->{start_tai_instant} +
199             (($end_utc_day - $seg->{start_utc_day}) *
200             86400) *
201             $seg->{utc_second_length},
202 5         456 $seg->{utc_second_length});
203             }
204             }
205              
206             {
207             my $init_dat = do { local $/ = undef; };
208             close(DATA);
209             sub _use_builtin_knowledge() {
210 5 50   5   1619 $init_dat =~ s/^[\ \t]*[0-9]+[\ \t]*[A-Z]+[\ \t]*[0-9]+[\ \t]*
211             =[\ \t]*
212             JD[\ \t]*([0-9]+\.?[0-9]*)[\ \t]*
213             unknown[\ \t]*\n\z//xim
214             or die "broken built-in TAI-UTC data\n";
215 5         22 my $end_jd = $1;
216 5         169 _add_data_from_tai_utc_dat($init_dat, $end_jd - _JD_TO_MJD);
217 5         73 $init_dat = undef;
218             }
219             }
220              
221 12     12   16700 use constant _UNIX_EPOCH_MJD => Math::BigRat->new(40586);
  12         34  
  12         98  
222              
223             sub _download_tai_utc_dat() {
224 0     0   0 require Net::HTTP::Tiny;
225 0         0 Net::HTTP::Tiny->VERSION(0.001);
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 my $unix_time = Time::Unix::time();
237 12     12   10031 use integer;
  12         32  
  12         86  
238 0         0 my $now_mjd = $unix_time/86400 + _UNIX_EPOCH_MJD;
239 0         0 _add_data_from_tai_utc_dat(
240             Net::HTTP::Tiny::http_get(
241             "http://maia.usno.navy.mil/ser7/tai-utc.dat"),
242             $now_mjd + 7*7);
243             }
244              
245 12     12   1296 use constant _NTP_EPOCH_MJD => Math::BigRat->new(15020);
  12         28  
  12         68  
246              
247             sub _ntp_second_to_tai_day($) {
248 0     0   0 my($ntp_sec_str) = @_;
249 0         0 return Math::BigRat->new($ntp_sec_str) / 86400
250             + _NTP_EPOCH_MJD - _TAI_EPOCH_MJD;
251             }
252              
253 12     12   10153 use constant _BIGRAT_ONE => Math::BigRat->new(1);
  12         39  
  12         59  
254              
255             sub _download_leap_seconds_list() {
256 0     0   0 require Net::FTP::Tiny;
257 0         0 Net::FTP::Tiny->VERSION(0.001);
258 0         0 my $list = Net::FTP::Tiny::ftp_get(
259             "ftp://utcnist2.colorado.edu/pub/leap-seconds.list");
260 0 0       0 die "malformed leap-seconds.list" unless $list =~ /\n\z/;
261 0 0       0 $list =~ /^\#h[\ \t]*
262             ([0-9a-fA-F]{1,8}(?:[\ \t]+[0-9a-fA-F]{1,8}){4})
263             [\ \t]*$/xm
264             or die "no hash in leap-seconds.list";
265 0         0 (my $hash = $1) =~ tr/A-F/a-f/;
266 0         0 $hash =~ s/([0-9a-f]+)/("0" x (8 - length($1))).$1/eg;
  0         0  
267 0         0 $hash =~ tr/ \t//d;
268 0         0 my $data_to_hash = "";
269 0         0 while($list =~ /^(?:\#[\$\@])?[ \t]*([0-9][^\#\n]*)[#\n]/mg) {
270 0         0 $data_to_hash .= $1;
271             }
272 0         0 $data_to_hash =~ tr/0-9//cd;
273 0 0       0 die "hash mismatch in leap-seconds.list"
274             unless sha1_hex($data_to_hash) eq $hash;
275 0         0 my($start_utc_day, $start_tai_instant);
276 0         0 while($list =~ /^([^#\n][^\n]*)$/mg) {
277 0         0 my $line = $1;
278 0 0       0 $line =~ /\A[ \t]*([0-9]+)[ \t]+([0-9]+)[ \t]*(?:\#|\z)/
279             or die "malformed data line in leap-seconds.list";
280 0         0 my($next_start_ntp_sec, $ndiff) = ($1, $2);
281 0         0 my $next_start_utc_day =
282             _ntp_second_to_tai_day($next_start_ntp_sec);
283 0 0       0 die "bad transition date in leap-seconds.list"
284             unless $next_start_utc_day->is_int;
285 0         0 my $next_start_tai_instant =
286             $next_start_utc_day*86400 + Math::BigRat->new($ndiff);
287 0 0       0 if(defined $start_utc_day) {
288 0         0 _add_data($start_utc_day,
289             $start_tai_instant,
290             _BIGRAT_ONE,
291             $next_start_utc_day,
292             $next_start_tai_instant,
293             _BIGRAT_ONE);
294             }
295 0         0 $start_utc_day = $next_start_utc_day;
296 0         0 $start_tai_instant = $next_start_tai_instant;
297             }
298 0 0       0 $list =~ /^\#\@[ \t]*([0-9]+)[ \t]*$/m
299             or die "no expiry date in leap-seconds.list";
300 0         0 my $expsec = $1;
301 0         0 my $end_utc_day = _ntp_second_to_tai_day($expsec)->bfloor - 1;
302 0 0       0 if(defined $start_utc_day) {
303 0         0 _add_data($start_utc_day,
304             $start_tai_instant,
305             _BIGRAT_ONE,
306             $end_utc_day,
307             $start_tai_instant +
308             ($end_utc_day - $start_utc_day) * 86400,
309             _BIGRAT_ONE);
310             }
311             }
312              
313             sub _download_latest_data() {
314 0           eval { local $SIG{__DIE__}; _download_leap_seconds_list(); 1 }
  0            
  0            
315 0 0   0     or eval { local $SIG{__DIE__}; _download_tai_utc_dat(); 1 };
  0            
  0            
  0            
316             }
317              
318             {
319             my $last_download = 0;
320             my $wait_until = 0;
321             sub _maybe_download_latest_data() {
322 18     18   26 my $time = time;
323 18 100 66     92 return unless $time >= $wait_until || $time < $last_download;
324 3         7 $last_download = $time;
325 3         154 $wait_until = $last_download + 3600 + rand(3600);
326 3 50       27 _download_latest_data() and
327             $wait_until = $last_download + 20*86400 + rand(2*86400);
328             }
329             }
330              
331             my $try_to_extend_knowledge = \&_use_builtin_knowledge;
332              
333             =head1 METHODS
334              
335             =head2 Information querying
336              
337             Most methods merely query the segment data. The data are strictly
338             read-only.
339              
340             The methods will C if information is requested that is not available.
341             This happens when looking further ahead than UTC has been defined.
342             UTC is defined only a few months in advance.
343              
344             All numeric values are returned as C objects.
345              
346             =over
347              
348             =cut
349              
350             sub _data_unavailable {
351 22     22   51 my($self, $method) = @_;
352 22 50       52 if(defined $try_to_extend_knowledge) {
353 22         30 eval { local $SIG{__DIE__};
  22         76  
354 22         37 my $ttek = $try_to_extend_knowledge;
355 22         30 $try_to_extend_knowledge = undef;
356 22         51 $ttek->();
357             };
358 22         52 $try_to_extend_knowledge = \&_maybe_download_latest_data;
359 22 100       72 if(ref($self) eq "Time::UTC::Segment::Complete") {
360 4         23 return $self->$method;
361             }
362             }
363 18         1472 croak "data not available yet";
364             }
365              
366             sub _data_unavailable_method($) {
367 96     96   198 my($method) = @_;
368 96     22   384 return sub { $_[0]->_data_unavailable($method) };
  22         5055  
369             }
370              
371             =item $seg->start_tai_instant
372              
373             The instant at which this segment starts, in TAI form: a C
374             giving the number of TAI seconds since the TAI epoch.
375              
376             =cut
377              
378             sub start_tai_instant {
379             $_[0]->{start_tai_instant}
380 798     798 1 5403 }
381              
382             =item $seg->end_tai_instant
383              
384             The instant at which this segment ends, in TAI form: a C
385             giving the number of TAI seconds since the TAI epoch.
386              
387             =cut
388              
389             sub Time::UTC::Segment::Complete::end_tai_instant {
390             $_[0]->{next}->{start_tai_instant}
391 123     123   580 }
392              
393             *Time::UTC::Segment::Incomplete::end_tai_instant =
394             _data_unavailable_method("end_tai_instant");
395              
396             =item $seg->length_in_tai_seconds
397              
398             The duration of this segment, measured in TAI seconds, as a
399             C.
400              
401             =cut
402              
403             sub Time::UTC::Segment::Complete::length_in_tai_seconds {
404             $_[0]->{length_in_tai_seconds}
405 82     82   32320 }
406              
407             *Time::UTC::Segment::Incomplete::length_in_tai_seconds =
408             _data_unavailable_method("length_in_tai_seconds");
409              
410             =item $seg->start_utc_day
411              
412             The first UTC day of this segment: a C giving the number
413             of days since the TAI epoch.
414              
415             =cut
416              
417             sub start_utc_day {
418             $_[0]->{start_utc_day}
419 2430     2430 1 314735 }
420              
421             =item $seg->last_utc_day
422              
423             The last UTC day of this segment: a C giving the number
424             of days since the TAI epoch.
425              
426             =cut
427              
428             sub Time::UTC::Segment::Complete::last_utc_day {
429             $_[0]->{last_utc_day}
430 615     615   266857 }
431              
432             *Time::UTC::Segment::Incomplete::last_utc_day =
433             _data_unavailable_method("last_utc_day");
434              
435             =item $seg->end_utc_day
436              
437             The first UTC day after the end of this segment: a C
438             giving the number of days since the TAI epoch.
439              
440             =cut
441              
442             sub Time::UTC::Segment::Complete::end_utc_day {
443             $_[0]->{next}->{start_utc_day}
444 246     246   54234 }
445              
446             *Time::UTC::Segment::Incomplete::end_utc_day =
447             _data_unavailable_method("end_utc_day");
448              
449             =item $seg->utc_second_length
450              
451             The length of the UTC second in this segment, measured in TAI seconds,
452             as a C.
453              
454             =cut
455              
456             sub utc_second_length {
457             $_[0]->{utc_second_length}
458 247     247 1 21531 }
459              
460             =item $seg->leap_utc_seconds
461              
462             The number of extra UTC seconds inserted at the end of the last day of
463             this segment, as a C. May be negative.
464              
465             =cut
466              
467             sub Time::UTC::Segment::Complete::leap_utc_seconds {
468             $_[0]->{leap_utc_seconds}
469 246     246   850 }
470              
471             *Time::UTC::Segment::Incomplete::leap_utc_seconds =
472             _data_unavailable_method("leap_utc_seconds");
473              
474             =item $seg->last_day_utc_seconds
475              
476             The number of UTC seconds in the last day of this segment, as a
477             C.
478              
479             =cut
480              
481             sub Time::UTC::Segment::Complete::last_day_utc_seconds {
482             $_[0]->{last_day_utc_seconds}
483 123     123   103431 }
484              
485             *Time::UTC::Segment::Incomplete::last_day_utc_seconds =
486             _data_unavailable_method("last_day_utc_seconds");
487              
488             =item $seg->length_in_utc_seconds
489              
490             The duration of this segment, measured in UTC seconds, as a
491             C.
492              
493             =cut
494              
495             sub Time::UTC::Segment::Complete::length_in_utc_seconds {
496             $_[0]->{length_in_utc_seconds}
497 82     82   66005 }
498              
499             *Time::UTC::Segment::Incomplete::length_in_utc_seconds =
500             _data_unavailable_method("length_in_utc_seconds");
501              
502             =item $seg->prev
503              
504             The immediately preceding segment. C if there is no preceding
505             segment, because this segment covers the start of UTC service at the
506             beginning of 1961.
507              
508             =cut
509              
510             sub prev {
511             $_[0]->{prev}
512 41     41 1 187 }
513              
514             =item $seg->next
515              
516             The immediately following segment. In the event that UTC ever becomes
517             fully defined, either by being defined for the entire future or by being
518             withdrawn altogether, there will be a final segment for which this will
519             be C.
520              
521             =cut
522              
523             sub Time::UTC::Segment::Complete::next {
524             $_[0]->{next}
525 824     824   114944 }
526              
527             *Time::UTC::Segment::Incomplete::next = _data_unavailable_method("next");
528              
529             =back
530              
531             =head2 Completeness
532              
533             Segments can be classified as "complete" and "incomplete". For complete
534             segments, all information-querying methods give answers. For incomplete
535             segments, only a few data are available: the methods C,
536             C, C, and C will give correct
537             answers, but other methods will C.
538              
539             An incomplete segment can become complete, as new information becomes
540             available, when updated UTC data is (automatically) downloaded. When this
541             happens, the resulting complete segment cannot be distinguished from
542             any other complete segment.
543              
544             Only one incomplete segment exists at a time.
545              
546             =over
547              
548             =item $seg->complete_p
549              
550             Returns a truth value indicating whether the segment is currently complete.
551              
552             =cut
553              
554 0     0   0 sub Time::UTC::Segment::Complete::complete_p { 1 }
555              
556 0     0   0 sub Time::UTC::Segment::Incomplete::complete_p { 0 }
557              
558             =item $seg->when_complete(WHAT)
559              
560             I must be a reference to a function which takes no arguments.
561             When the segment is complete, the function will be called. If the
562             segment is already complete then the function is called immediately;
563             otherwise the function is noted and will be called when the segment
564             becomes complete due to the availability of new information.
565              
566             This is a one-shot operation. To do something similar for all segments,
567             see C in C.
568              
569             =cut
570              
571             sub Time::UTC::Segment::Complete::when_complete {
572 0     0   0 my($self, $what) = @_;
573 0         0 eval { local $SIG{__DIE__}; $what->(); };
  0         0  
  0         0  
574             }
575              
576             sub Time::UTC::Segment::Incomplete::when_complete {
577 393     393   952 my($self, $what) = @_;
578 393         606 push @{$self->{when_complete}}, $what;
  393         1780  
579             }
580              
581             =back
582              
583             =head1 INVARIANTS
584              
585             The following relations hold for all segments:
586              
587             $seg->length_in_tai_seconds ==
588             $seg->end_tai_instant - $seg->start_tai_instant
589              
590             $seg->last_utc_day + 1 == $seg->end_utc_day
591              
592             $seg->last_day_utc_seconds == 86400 + $seg->leap_utc_seconds
593              
594             $seg->length_in_utc_seconds ==
595             86400 * ($seg->last_utc_day - $seg->start_utc_day) +
596             $seg->last_day_utc_seconds
597              
598             $seg->length_in_tai_seconds ==
599             $seg->length_in_utc_seconds * $seg->utc_second_length
600              
601             $seg->next->prev == $seg
602              
603             $seg->end_tai_instant == $seg->next->start_tai_instant
604              
605             $seg->end_utc_day == $seg->next->start_utc_day
606              
607             =head1 SEE ALSO
608              
609             L
610              
611             =head1 AUTHOR
612              
613             Andrew Main (Zefram)
614              
615             =head1 COPYRIGHT
616              
617             Copyright (C) 2005, 2006, 2007, 2009, 2010, 2012, 2017
618             Andrew Main (Zefram)
619              
620             =head1 LICENSE
621              
622             This module is free software; you can redistribute it and/or modify it
623             under the same terms as Perl itself.
624              
625             =cut
626              
627             1;
628              
629             __DATA__