File Coverage

blib/lib/Date/MSD.pm
Criterion Covered Total %
statement 139 139 100.0
branch 18 30 60.0
condition 1 3 33.3
subroutine 81 81 100.0
pod 72 72 100.0
total 311 325 95.6


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Date::MSD - conversion between flavours of Mars Sol Date
4              
5             =head1 SYNOPSIS
6              
7             use Date::MSD qw(js_to_msd msd_to_cmsdnf cmsdn_to_js);
8              
9             $msd = js_to_msd($js);
10             ($cmsdn, $cmsdf) = msd_to_cmsdnf($msd, $tz);
11             $js = cmsdn_to_js($cmsdn, $cmsdf, $tz);
12              
13             # and 69 other conversion functions
14              
15             =head1 DESCRIPTION
16              
17             For date and time calculations it is convenient to represent dates by
18             a simple linear count of days, rather than in a particular calendar.
19             This module performs conversions between different flavours of linear
20             count of Martian solar days ("sols").
21              
22             Among Martian day count systems there are also some non-trivial
23             differences of concept. There are systems that count only complete days,
24             and those that count fractional days also. There are some that are fixed
25             to Airy Mean Time (time on the Martian prime meridian), and others that
26             are interpreted according to a timezone. The functions of this module
27             appropriately handle the semantics of all the non-trivial conversions.
28              
29             The day count systems supported by this module are Mars Sol Date,
30             Julian Sol, and Chronological Mars Solar Date, each in both integral
31             and fractional forms.
32              
33             =head2 Flavours of day count
34              
35             In the interests of orthogonality, all flavours of day count come in
36             both integral and fractional varieties. Generally, there is a quantity
37             named "XYZ" which is a real count of days since a particular epoch (an
38             integer plus a fraction) and a corresponding quantity named "XYZN" ("XYZ
39             Number") which is a count of complete days since the same epoch. XYZN is
40             the integral part of XYZ. There is also a quantity named "XYZF" ("XYZ
41             Fraction") which is a count of fractional days since the XYZN changed
42             (at midnight). XYZF is the fractional part of XYZ, in the range [0, 1).
43              
44             This quantity naming pattern is derived from the naming of Terran day
45             counts, particularly JD (Julian Date) and JDN (Julian Day Number) which
46             have the described correspondence. The "XYZF" name type is a neologism,
47             invented for L.
48              
49             All calendar dates given are in the Darian calendar for Mars. An hour
50             number is appended to each date, separated by a "T"; hour 00 is midnight
51             at the start of the day. An appended "Z" indicates that the date is to
52             be interpreted in the timezone of the prime meridian (Airy Mean Time),
53             and so is absolute; where any other timezone is to be used then this is
54             explicitly noted.
55              
56             =over
57              
58             =item MSD (Mars Sol Date)
59              
60             days elapsed since 0140-19-26T00Z (approximately MJD 5521.50
61             in Terrestrial Time). This epoch is the most recent near
62             coincidence of midnight on the Martian prime meridian with noon
63             on the Terran prime meridian. MSD is defined by the paper at
64             L.
65              
66             =item JS (Julian Sol)
67              
68             days elapsed since 0000-01-01T00Z (MSD -94129.0) (approximately
69             MJD -91195.22 in Terrestrial Time). This epoch is an Airy
70             midnight approximating the last northward equinox prior to
71             the first telescopic observations of Mars. The same epoch is
72             used for the Darian calendar for Mars. JS is defined (but not
73             explicitly) by the document describing the Darian calendar, at
74             L.
75              
76             =item CMSD (Chronological Mars Solar Date)
77              
78             days elapsed since -0608-23-20T00 in the timezone of interest.
79             CMSD = MSD + 500000.0 + Zoff, where Zoff is the timezone
80             offset in fractional days. CMSD is defined by the memo at
81             L.
82              
83             =back
84              
85             =head2 Meaning of the day
86              
87             A day count has meaning only in the context of a particular definition
88             of "day". Potentially several time scales could be expressed in terms
89             of a day count, just as Terran day counts such as MJD are used in the
90             timescales UT1, UT2, UTC, TAI, TT, TCG, and others. For a day number
91             to be meaningful it is necessary to be aware of which kind of day it
92             is counting. Conversion between the different time scales is out of
93             scope for this module.
94              
95             =cut
96              
97             package Date::MSD;
98              
99 1     1   86024 { use 5.006; }
  1         7  
100 1     1   8 use warnings;
  1         3  
  1         43  
101 1     1   7 use strict;
  1         9  
  1         47  
102              
103 1     1   8 use Carp qw(croak);
  1         3  
  1         90  
104              
105             our $VERSION = "0.005";
106              
107 1     1   422 use parent "Exporter";
  1         669  
  1         7  
108             our @EXPORT_OK;
109              
110             my %msd_flavours = (
111             msd => { epoch_msd => 0 },
112             js => { epoch_msd => -94129.0 },
113             cmsd => { epoch_msd => -500000.0, zone => 1 },
114             );
115              
116             =head1 FUNCTIONS
117              
118             Day counts in this API may be native Perl numbers or C
119             objects. Both are acceptable for all parameters, in any combination.
120             In all conversion functions, the result is of the same type as the
121             input, provided that the inputs are of consistent type. If native Perl
122             numbers are supplied then the conversion is subject to floating point
123             rounding, and possible overflow if the numbers are extremely large.
124             The use of C is recommended to avoid these problems.
125             With C the results are exact.
126              
127             There are conversion functions between all pairs of day count systems.
128             This is a total of 72 conversion functions (including 12 identity
129             functions).
130              
131             When converting between timezone-relative counts (CMSD) and absolute
132             counts (MSD, JS), the timezone that is being used must be specified.
133             It is given in a ZONE argument as a fractional number of days offset
134             from the base time scale (typically Airy Mean Time). Beware of
135             floating point rounding when the offset does not have a terminating
136             binary representation; use of C avoids this problem.
137             A ZONE parameter is not used when converting between absolute day counts
138             (e.g., between MSD and JS) or between timezone-relative counts (e.g.,
139             between CMSD and CMSDN).
140              
141             =over
142              
143             =item msd_to_msd(MSD)
144              
145             =item msd_to_js(MSD)
146              
147             =item msd_to_cmsd(MSD, ZONE)
148              
149             =item js_to_msd(JS)
150              
151             =item js_to_js(JS)
152              
153             =item js_to_cmsd(JS, ZONE)
154              
155             =item cmsd_to_msd(CMSD, ZONE)
156              
157             =item cmsd_to_js(CMSD, ZONE)
158              
159             =item cmsd_to_cmsd(CMSD)
160              
161             These functions convert from one continuous day count to another.
162             This principally involve a change of epoch. The input identifies a
163             point in time, as a continuous day count of input flavour. The function
164             returns the same point in time, represented as a continuous day count
165             of output flavour.
166              
167             =item msd_to_msdnn(MSD)
168              
169             =item msd_to_jsnn(MSD)
170              
171             =item msd_to_cmsdnn(MSD, ZONE)
172              
173             =item js_to_msdnn(JS)
174              
175             =item js_to_jsnn(JS)
176              
177             =item js_to_cmsdnn(JS, ZONE)
178              
179             =item cmsd_to_msdnn(CMSD, ZONE)
180              
181             =item cmsd_to_jsnn(CMSD, ZONE)
182              
183             =item cmsd_to_cmsdnn(CMSD)
184              
185             These functions convert from a continuous day count to an integral day
186             count. The input identifies a point in time, as a continuous day count
187             of input flavour. The function returns the day number of output flavour
188             that applies at that instant. The process throws away information about
189             the time of (output-flavour) day.
190              
191             =item msd_to_msdnf(MSD)
192              
193             =item msd_to_jsnf(MSD)
194              
195             =item msd_to_cmsdnf(MSD, ZONE)
196              
197             =item js_to_msdnf(JS)
198              
199             =item js_to_jsnf(JS)
200              
201             =item js_to_cmsdnf(JS, ZONE)
202              
203             =item cmsd_to_msdnf(CMSD, ZONE)
204              
205             =item cmsd_to_jsnf(CMSD, ZONE)
206              
207             =item cmsd_to_cmsdnf(CMSD)
208              
209             These functions convert from a continuous day count to an integral day
210             count with separate fraction. The input identifies a point in time,
211             as a continuous day count of input flavour. The function returns a
212             list of two items: the day number and fractional day of output flavour,
213             which together identify the same point in time as the input.
214              
215             =item msd_to_msdn(MSD)
216              
217             =item msd_to_jsn(MSD)
218              
219             =item msd_to_cmsdn(MSD, ZONE)
220              
221             =item js_to_msdn(JS)
222              
223             =item js_to_jsn(JS)
224              
225             =item js_to_cmsdn(JS, ZONE)
226              
227             =item cmsd_to_msdn(CMSD, ZONE)
228              
229             =item cmsd_to_jsn(CMSD, ZONE)
230              
231             =item cmsd_to_cmsdn(CMSD)
232              
233             These functions convert from a continuous day count to an integral day
234             count, possibly with separate fraction. The input identifies a point in
235             time, as a continuous day count of input flavour. If called in scalar
236             context, the function returns the day number of output flavour that
237             applies at that instant, throwing away information about the time of
238             (output-flavour) day. If called in list context, the function returns a
239             list of two items: the day number and fractional day of output flavour,
240             which together identify the same point in time as the input.
241              
242             These functions are not recommended, because the context-sensitive
243             return convention makes their use error-prone. They are retained for
244             backward compatibility. You should prefer to use the more specific
245             functions shown above.
246              
247             =item msdn_to_msd(MSDN, MSDF)
248              
249             =item msdn_to_js(MSDN, MSDF)
250              
251             =item msdn_to_cmsd(MSDN, MSDF, ZONE)
252              
253             =item jsn_to_msd(JSN, JSF)
254              
255             =item jsn_to_js(JSN, JSF)
256              
257             =item jsn_to_cmsd(JSN, JSF, ZONE)
258              
259             =item cmsdn_to_msd(CMSDN, CMSDF, ZONE)
260              
261             =item cmsdn_to_js(CMSDN, CMSDF, ZONE)
262              
263             =item cmsdn_to_cmsd(CMSDN, CMSDF)
264              
265             These functions convert from an integral day count with separate fraction
266             to a continuous day count. The input identifies a point in time, as
267             an integral day number of input flavour plus day fraction in the range
268             [0, 1). The function returns the same point in time, represented as a
269             continuous day count of output flavour.
270              
271             =item msdn_to_msdnn(MSDN[, MSDF])
272              
273             =item msdn_to_jsnn(MSDN[, MSDF])
274              
275             =item msdn_to_cmsdnn(MSDN, MSDF, ZONE)
276              
277             =item jsn_to_msdnn(JSN[, JSF])
278              
279             =item jsn_to_jsnn(JSN[, JSF])
280              
281             =item jsn_to_cmsdnn(JSN, JSF, ZONE)
282              
283             =item cmsdn_to_msdnn(CMSDN, CMSDF, ZONE)
284              
285             =item cmsdn_to_jsnn(CMSDN, CMSDF, ZONE)
286              
287             =item cmsdn_to_cmsdnn(CMSDN[, CMSDF])
288              
289             These functions convert from an integral day count with separate fraction
290             to an integral day count. The input identifies a point in time, as an
291             integral day number of input flavour plus day fraction in the range
292             [0, 1). The function returns the day number of output flavour that
293             applies at that instant. The process throws away information about
294             the time of (output-flavour) day. If converting between systems that
295             delimit days identically (e.g., between JS and MSD), the day fraction
296             makes no difference and may be omitted from the input.
297              
298             =item msdn_to_msdnf(MSDN, MSDF)
299              
300             =item msdn_to_jsnf(MSDN, MSDF)
301              
302             =item msdn_to_cmsdnf(MSDN, MSDF, ZONE)
303              
304             =item jsn_to_msdnf(JSN, JSF)
305              
306             =item jsn_to_jsnf(JSN, JSF)
307              
308             =item jsn_to_cmsdnf(JSN, JSF, ZONE)
309              
310             =item cmsdn_to_msdnf(CMSDN, CMSDF, ZONE)
311              
312             =item cmsdn_to_jsnf(CMSDN, CMSDF, ZONE)
313              
314             =item cmsdn_to_cmsdnf(CMSDN, CMSDF)
315              
316             These functions convert from one integral day count with separate
317             fraction to another. The input identifies a point in time, as an
318             integral day number of input flavour plus day fraction in the range
319             [0, 1). The function returns a list of two items: the day number and
320             fractional day of output flavour, which together identify the same point
321             in time as the input.
322              
323             =item msdn_to_msdn(MSDN[, MSDF])
324              
325             =item msdn_to_jsn(MSDN[, MSDF])
326              
327             =item msdn_to_cmsdn(MSDN, MSDF, ZONE)
328              
329             =item jsn_to_msdn(JSN[, JSF])
330              
331             =item jsn_to_jsn(JSN[, JSF])
332              
333             =item jsn_to_cmsdn(JSN, JSF, ZONE)
334              
335             =item cmsdn_to_msdn(CMSDN, CMSDF, ZONE)
336              
337             =item cmsdn_to_jsn(CMSDN, CMSDF, ZONE)
338              
339             =item cmsdn_to_cmsdn(CMSDN[, CMSDF])
340              
341             These functions convert from an integral day count with separate fraction
342             to an integral day count, possibly with separate fraction. The input
343             identifies a point in time, as an integral day number of input flavour
344             plus day fraction in the range [0, 1). If called in scalar context, the
345             function returns the day number of output flavour that applies at that
346             instant, throwing away information about the time of (output-flavour) day.
347             If called in list context, the function returns a list of two items:
348             the day number and fractional day of output flavour, which together
349             identify the same point in time as the input.
350              
351             If converting between systems that delimit days identically (e.g.,
352             between JS and MSD), the day fraction makes no difference to the integral
353             day number of the output, and may be omitted from the input. If the day
354             fraction is extracted from the output when it wasn't supplied as input,
355             it will default to zero.
356              
357             These functions are not recommended, because the context-sensitive
358             return convention makes their use error-prone. They are retained for
359             backward compatibility. You should prefer to use the more specific
360             functions shown above.
361              
362             =cut
363              
364             eval { local $SIG{__DIE__};
365             require POSIX;
366             *_floor = \&POSIX::floor;
367             };
368             if($@ ne "") {
369             *_floor = sub($) {
370             my $i = int($_[0]);
371             return $i == $_[0] || $_[0] > 0 ? $i : $i - 1;
372             }
373             }
374              
375             sub _check_dn($$) {
376 504 100   504   2650 croak "purported day number $_[0] is not an integer"
    50          
377             unless ref($_[0]) ? $_[0]->is_int : $_[0] == int($_[0]);
378 504 50 33     10910 croak "purported day fraction $_[1] is out of range [0, 1)"
379             unless $_[1] >= 0 && $_[1] < 1;
380             }
381              
382             sub _ret_dnn($) {
383 720 100   720   266006 my $dn = ref($_[0]) eq "Math::BigRat" ?
384             $_[0]->copy->bfloor : _floor($_[0]);
385 720         25352 return $dn;
386             }
387              
388             sub _ret_dnf($) {
389 288     288   129921 my $dn = &_ret_dnn;
390 288         1246 return ($dn, $_[0] - $dn);
391             }
392              
393             sub _ret_dn($) {
394 288 100   288   258877 return wantarray ? &_ret_dnf : &_ret_dnn;
395             }
396              
397             foreach my $src (keys %msd_flavours) { foreach my $dst (keys %msd_flavours) {
398             my $ediff = $msd_flavours{$src}->{epoch_msd} -
399             $msd_flavours{$dst}->{epoch_msd};
400             my $ediffh = $ediff == int($ediff) ? 0 : 0.5;
401             my $src_zone = !!$msd_flavours{$src}->{zone};
402             my $dst_zone = !!$msd_flavours{$dst}->{zone};
403             my($zp, $z1, $z2);
404             if($src_zone == $dst_zone) {
405             $zp = $z1 = $z2 = "";
406             } else {
407             $zp = "\$";
408             my $zsign = $src_zone ? "-" : "+";
409             $z1 = "$zsign \$_[1]";
410             $z2 = "$zsign \$_[2]";
411             }
412 16     16 1 26979 eval "sub ${src}_to_${dst}(\$${zp}) { \$_[0] + (${ediff}) ${z1} }";
  16     16 1 38639  
  16     16 1 35677  
  16     16 1 32551  
  16     16 1 29107  
  16     16 1 26084  
  16     16 1 37534  
  16     16 1 32880  
  16     16 1 136450  
413             push @EXPORT_OK, "${src}_to_${dst}";
414 16     16 1 10521 eval "sub ${src}_to_${dst}nn(\$${zp}) {
  16     16 1 20421  
  16     16 1 16699  
  16     16 1 15351  
  16     16 1 11721  
  16     16 1 12582  
  16     16 1 19553  
  16     16 1 13921  
  16     16 1 11826  
415             _ret_dnn(\$_[0] + (${ediff}) ${z1})
416             }";
417             push @EXPORT_OK, "${src}_to_${dst}nn";
418 8     8 1 6507 eval "sub ${src}_to_${dst}nf(\$${zp}) {
  8     8 1 9150  
  8     8 1 7437  
  8     8 1 8504  
  8     8 1 7264  
  8     8 1 8013  
  8     8 1 9767  
  8     8 1 8499  
  8     8 1 7547  
419             _ret_dnf(\$_[0] + (${ediff}) ${z1})
420             }";
421             push @EXPORT_OK, "${src}_to_${dst}nf";
422 16     16 1 21788 eval "sub ${src}_to_${dst}n(\$${zp}) {
  16     16 1 28237  
  16     16 1 23631  
  16     16 1 24844  
  16     16 1 23403  
  16     16 1 26566  
  16     16 1 30180  
  16     16 1 27311  
  16     16 1 28236  
423             _ret_dn(\$_[0] + (${ediff}) ${z1})
424             }";
425             push @EXPORT_OK, "${src}_to_${dst}n";
426 16     16 1 20788 eval "sub ${src}n_to_${dst}(\$\$${zp}) {
  16     16 1 13680  
  16     16 1 28463  
  16     16 1 14044  
  16     16 1 24479  
  16     16 1 13613  
  16     16 1 28210  
  16     16 1 14089  
  16     16 1 18784  
  16         11895  
  16         19994  
  16         14159  
  16         30920  
  16         16139  
  16         22739  
  16         14084  
  16         23748  
  16         14574  
427             _check_dn(\$_[0], \$_[1]);
428             \$_[0] + \$_[1] + (${ediff}) ${z2}
429             }";
430             push @EXPORT_OK, "${src}n_to_${dst}";
431             my($tp, $tc);
432             if($ediffh == 0 && $src_zone == $dst_zone) {
433             $tp = ";";
434             $tc = "push \@_, 0 if \@_ == 1;";
435             } else {
436             $tp = $tc = "";
437             }
438 16 50   16 1 16837 eval "sub ${src}n_to_${dst}nn(\$${tp}\$${zp}) { $tc
  16 50   16 1 79  
  16 50   16 1 13862  
  16 50   16 1 26868  
  16 50   16 1 14584  
  16     16 1 22395  
  16     16 1 15289  
  16     16 1 22135  
  16     16 1 13935  
  16         15982  
  16         81  
  16         12664  
  16         17889  
  16         79  
  16         14395  
  16         22870  
  16         14271  
  16         16572  
  16         89  
  16         14402  
  16         17217  
  16         80  
  16         14564  
439             _check_dn(\$_[0], \$_[1]);
440             _ret_dnn(\$_[0] + \$_[1] + ($ediff) ${z2})
441             }";
442             push @EXPORT_OK, "${src}n_to_${dst}nn";
443 8     8 1 7177 eval "sub ${src}n_to_${dst}nf(\$\$${zp}) {
  8     8 1 6309  
  8     8 1 8771  
  8     8 1 6434  
  8     8 1 7946  
  8     8 1 6641  
  8     8 1 8689  
  8     8 1 6803  
  8     8 1 7855  
  8         7614  
  8         7130  
  8         5748  
  8         8360  
  8         7358  
  8         8704  
  8         6870  
  8         7940  
  8         7272  
444             _check_dn(\$_[0], \$_[1]);
445             _ret_dnf(\$_[0] + \$_[1] + ($ediff) ${z2})
446             }";
447             push @EXPORT_OK, "${src}n_to_${dst}nf";
448 16 50   16 1 14379 eval "sub ${src}n_to_${dst}n(\$${tp}\$${zp}) { $tc
  16 50   16 1 76  
  16 50   16 1 16103  
  16 50   16 1 15795  
  16 50   16 1 11732  
  16     16 1 13707  
  16     16 1 13874  
  16     16 1 14191  
  16     16 1 13080  
  16         15848  
  16         81  
  16         13472  
  16         14257  
  16         68  
  16         13921  
  16         15403  
  16         14542  
  16         15900  
  16         87  
  16         14818  
  16         15881  
  16         79  
  16         12640  
449             _check_dn(\$_[0], \$_[1]);
450             _ret_dn(\$_[0] + \$_[1] + ($ediff) ${z2})
451             }";
452             push @EXPORT_OK, "${src}n_to_${dst}n";
453             } }
454              
455             =back
456              
457             =head1 SEE ALSO
458              
459             L,
460             L
461              
462             =head1 AUTHOR
463              
464             Andrew Main (Zefram)
465              
466             =head1 COPYRIGHT
467              
468             Copyright (C) 2007, 2009, 2010, 2012, 2017
469             Andrew Main (Zefram)
470              
471             =head1 LICENSE
472              
473             This module is free software; you can redistribute it and/or modify it
474             under the same terms as Perl itself.
475              
476             =cut
477              
478             1;