File Coverage

blib/lib/DateTime/Precise.pm
Criterion Covered Total %
statement 581 673 86.3
branch 230 338 68.0
condition 42 99 42.4
subroutine 64 69 92.7
pod 32 53 60.3
total 949 1232 77.0


line stmt bran cond sub pod time code
1             # DateTime::Precise -*- Perl -*-
2             #
3             # This code is a heavily modified version of Greg Fast's
4             # (gdfast@usgs.gov) DateTime.pm package. This version includes
5             # subsecond precision on all calculations and a whole bunch of
6             # additional method calls.
7             #
8             # Latest author: Blair Zajac (blair@orcaware.com).
9             # Original author: Greg Fast (gdfast@usgs.gov).
10              
11             package DateTime::Precise;
12              
13             require 5.004_04;
14 1     1   9607 use strict;
  1         2  
  1         57  
15 1     1   5 use Carp qw(carp cluck croak confess);
  1         2  
  1         104  
16 1     1   6 use Exporter;
  1         6  
  1         131  
17             require 'DateTime/Math/bigfloat.pl';
18              
19             # Try to load the Time::HiRes module to get the high resolution
20             # version of time.
21             BEGIN {
22 1     1   2 eval {
23 1         2 my $module = 'Time::HiRes';
24 1         2 my $package = "$module.pm";
25 1         4 $package =~ s#::#/#g;
26 1         1031 require $package;
27 1         1983 import $module qw(time);
28             };
29             }
30              
31 1         438 use vars qw(@ISA @EXPORT_OK %EXPORT_TAGS
32             $AUTOLOAD
33             $VERSION
34             $TZ @LC_AMPM
35             %SET_MASK %SET_START_VALUE %SET_MULTIPLER_VALUE
36             $USGSMidnight
37             $is_internal_format_re
38             @MonthDays @MonthName @MonthAbbrev @WeekName @WeekAbbrev
39             %_month_name
40 1     1   215 $Days_per_5_months $Days_per_4_years $Days_per_400_years);
  1         2  
41              
42             # Definitions for overloaded operators:
43             # Overloaded operators: +/-, <=>, cmp, stringify.
44             # Addition handles seconds, subtraction handles secs or date
45             # differences. Comparisons also work.
46             use overload
47 0     0   0 'neg' => sub { cluck "neg is an invalid operator for " . ref($_[0]); $_[0] },
  0         0  
48             '""' => 'stringify',
49             '+' => 'ovld_add',
50 58 50   58   419 '-' => sub { $_[2] ? &ovld_sub($_[1],$_[0]) : &ovld_sub; },
51 67 100   67   361 '<=>' => sub { $_[2] ? DateTime::Math::fcmp("$_[1]","$_[0]") :
52             DateTime::Math::fcmp("$_[0]","$_[1]") },
53 1 50   1   1727 'cmp' => sub { $_[2] ? ("$_[1]" cmp "$_[0]") : ("$_[0]" cmp "$_[1]") };
  1     27   1072  
  1         14  
  27         120  
54              
55             $VERSION = sprintf '%d.%02d', '$Revision: 1.05 $' =~ /(\d+)\.(\d+)/;
56             @ISA = qw(Exporter);
57             @EXPORT_OK = qw($USGSMidnight
58             @MonthDays @MonthName @MonthAbbrev @WeekName @WeekAbbrev
59             &Secs_per_week &Secs_per_day &Secs_per_hour &Secs_per_minute
60             &JANUARY_1_1970 &JANUARY_6_1980
61             &IsLeapYear &DaysInMonth);
62             %EXPORT_TAGS = (TimeVars => [qw(@MonthDays @MonthName @MonthAbbrev
63             @WeekName @WeekAbbrev
64             &Secs_per_week &Secs_per_day
65             &Secs_per_hour &Secs_per_minute
66             &JANUARY_1_1970 &JANUARY_6_1980)] );
67              
68             #
69             # Global, internal variables.
70             #
71              
72             # This is the regular expression to test if a string represents an
73             # internal representation of the time.
74             $is_internal_format_re = '^\d{14}(\.\d*)?$';
75              
76             # USGS, god knows why, likes midnight to be 24:00:00, not 00:00:00.
77             # If $USGSMidnight is set to 1, dprintf will always print midnight as
78             # 24:00:00. Time is always stored internally as real midnight.
79             $USGSMidnight = 0;
80              
81             # @MonthDays: days per month, 1-indexed (0=dec, 13=jan).
82             # @MonthName: Names of months, one-indexed.
83             # @MonthAbbrev: 3-letter abbrevs of months. one-indexed.
84             # @WeekName: Names of days of the week. zero-indexed.
85             # @WeekAbbrev: 3-letter abbrevs. zero-indexed.
86             @MonthDays = (31,31,28,31,30,31,30,31,31,30,31,30,31,31);
87             @MonthName = ('December','January','February','March','April','May','June','July','August','September','October','November','December','January');
88             @MonthAbbrev = ('Dec','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec','Jan');
89             @WeekName = ('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday');
90             @WeekAbbrev = ('Sun','Mon','Tue','Wed','Thu','Fri','Sat');
91              
92             # SDN is serial day number (the SDN conversion code isn't mine).
93             # SDN_Offset: deep magic from the dawn of time.
94             # Days_per_5_months: number of days in a five month block (mar-jul)
95             # Days_per_4_years: number of days in a leap year cycle
96             # Days_per_400_years: number of days in a *real* leap year cycle
97             sub SDN_Offset () { 32045; }
98             sub Days_per_5_months () { 153; }
99             sub Days_per_4_years () { 1461; }
100             sub Days_per_400_years () { 146097; }
101              
102             # Secs_per_week: number of seconds in one week (7 days)
103             # Secs_per_day: number of seconds in one day (24 hours)
104             # Secs_per_hour: number of seconds in one hour
105             # Secs_per_minute: number of seconds in one minute
106             sub Secs_per_week () { 604800; }
107             sub Secs_per_day () { 86400; }
108             sub Secs_per_hour () { 3600; }
109             sub Secs_per_minute () { 60; }
110              
111             # There's no portable way to find the system default timezone, so
112             # set it to GMT.
113             $TZ = 'GMT';
114             # These are locales specific variables. Change these to suit your
115             # local format.
116             @LC_AMPM = ('AM', 'PM');
117             # This time represents Unix time 0 of January 1, 1970 UTC.
118 21     21 0 78 sub JANUARY_1_1970 () { DateTime::Precise->new('1970.01.01 00:00:00'); }
119             # This time represents GPS time 0 of January 6, 1980.
120 40     40 0 133 sub JANUARY_6_1980 () { DateTime::Precise->new('1980.01.06 00:00:00'); }
121             # Modified Julian Day #0 is 40587 days after January 1, 1970 UTC.
122             sub MODIFIED_JULIAN_DAY () { 40587; }
123              
124             # These constants are used in the internal representation of the date
125             # and time, which is a reference to an array. These constants are
126             # indices into the appropriate location in the array to get the
127             # particular portion of the date/time.
128             sub YEAR () { 0; }
129             sub MONTH () { 1; }
130             sub DAY () { 2; }
131             sub HOUR () { 3; }
132             sub MINUTE () { 4; }
133             sub SECOND () { 5; }
134             sub FRACTION () { 6; }
135              
136             # %_unit_name: translate function names to component indices.
137             my %_unit_name = (second => SECOND,
138             sec => SECOND,
139             minute => MINUTE,
140             min => MINUTE,
141             hour => HOUR,
142             day => DAY,
143             month => MONTH,
144             mo => MONTH,
145             year => YEAR);
146              
147             # %_unit_name: which function names to allow (see AUTOLOADER).
148             my %_func_name = ('inc'=>1, 'dec'=>1, 'floor'=>1, 'ceil'=>1, 'round'=>1);
149              
150             # @_half_unit: when to round up.
151             my @_half_unit = (0, 6, 15, 12, 30, 30, 0.5);
152              
153             # @_full_unit: full size of unit.
154             my @_full_unit = map(2*$_, @_half_unit);
155              
156             my %_month_name;
157             foreach (1..12) {
158             $_month_name{lc $MonthName[$_]} = $_;
159             $_month_name{lc $MonthAbbrev[$_]} = $_;
160             }
161              
162             # These variables are used for setting the time using the set_time and
163             # new methods. Time is set using a template of key letters and an
164             # array containing any needed arguments for each key. Each letter
165             # represents a different method of setting the time. Associated with
166             # each key is a mask storred in %SET_MASK that identifies the
167             # propterties of the key. The keys are bitwise ANDed between four
168             # keys, MASK_ABSOLUTE, MASK_NO_ARG, MASK_FRACTIONAL_ARG, and
169             # MASK_MULTIPLIER_ARG. Key letters that set the time to an absolute
170             # value are marked with the MASK_ABSOLUTE flag. If the key does not
171             # have MASK__ABSOLUTE, then the time is set relative to the current
172             # time value. If the key does not use a argument, then MASK_NO_ARG is
173             # set. If non-integer arguments to the keys are allowed, then the
174             # MASK_FRACTIONAL_ARG is set. Finally, if the key needs a multipler
175             # value to convert the argument into seconds, then MASK_MULTIPLIER_ARG
176             # is set.
177             sub MASK_ABSOLUTE () { 1; }
178             sub MASK_NO_ARG () { 2; }
179             sub MASK_USES_PARTIAL () { 4; }
180             sub MASK_USES_MULTIPLIER () { 8; }
181              
182             # Define combinations of these flags.
183             %SET_MASK = (
184             # set time to now
185             'N' => MASK_ABSOLUTE | MASK_NO_ARG,
186             # set time to GPS time 0
187             'G' => MASK_ABSOLUTE | MASK_NO_ARG,
188             # set to beginning on year
189             'Y' => MASK_ABSOLUTE | MASK_USES_PARTIAL,
190             # set to modfied Julian date
191             'J' => MASK_ABSOLUTE | MASK_USES_PARTIAL,
192             # set to seconds since January 1, 1970 UTC
193             's' => MASK_ABSOLUTE | MASK_USES_PARTIAL,
194             # add month of year
195             'B' => MASK_USES_PARTIAL,
196             # add number of weeks
197             'W' => MASK_USES_PARTIAL | MASK_USES_MULTIPLIER,
198             # add number of days from 1
199             'D' => MASK_USES_PARTIAL | MASK_USES_MULTIPLIER,
200             # add number of days from 0
201             'd' => MASK_USES_PARTIAL | MASK_USES_MULTIPLIER,
202             # add hours
203             'H' => MASK_USES_PARTIAL | MASK_USES_MULTIPLIER,
204             # add minutes
205             'M' => MASK_USES_PARTIAL | MASK_USES_MULTIPLIER,
206             # add seconds
207             'S' => MASK_USES_PARTIAL | MASK_USES_MULTIPLIER,
208             );
209              
210             # These define the starting values for the different keys in SET_MASK.
211             %SET_START_VALUE = ('s' => 0,
212             'W' => 0,
213             'D' => 1,
214             'd' => 0,
215             'H' => 0,
216             'M' => 0,
217             'S' => 0);
218              
219             # These are the multipler from the key into seconds.
220             %SET_MULTIPLER_VALUE = ('s' => 1,
221             'W' => Secs_per_week,
222             'D' => Secs_per_day,
223             'd' => Secs_per_day,
224             'H' => Secs_per_hour,
225             'M' => Secs_per_minute,
226             'S' => 1);
227              
228             #----------------------------------------
229             # ARG1 $year: year
230             # RETVAL: true/false
231             # EXAMPLE: print "Yes!" if DateTime::Precise::IsLeapYear(2000);
232             # ACCESS: public nonmethod
233             sub IsLeapYear {
234 100     100 1 150 my $year = int($_[0]);
235 100 100 66     540 ((($year%4) == 0) && ((($year%100) != 0) || (($year%400) == 0)));
236             }
237             # IsLeapYear
238              
239             #----------------------------------------
240             # ARG1 $month: month in question
241             # ARG2 $year: year, for figuring leap years if it's feb.
242             # RETVAL: number of days in month
243             # ACCESS: public nonmethod
244             sub DaysInMonth {
245 1092     1092 1 1449 my $month = shift;
246 1092         1284 my $year = shift;
247 1092   100     3828 $MonthDays[$month] + ($month==2 && IsLeapYear($year));
248             }
249             # DaysInMonth
250              
251              
252             #
253             # Internal helper functions.
254             #
255              
256             #----------------------------------------
257             # NOTES: fix to 24:00:00 midnight.
258             # RETVAL: 1 if the date was modified, 0 otherwise
259             # ACCESS: method
260             sub USGSDumbMidnightFix {
261 0     0 0 0 my $self = shift;
262 0         0 my $modified_date = 0;
263 0         0 $self->_FixDate;
264 0 0 0     0 if ($self->[FRACTION] == 0 && $self->[SECOND] == 0 &&
      0        
      0        
265             $self->[MINUTE] == 0 && $self->[HOUR] == 0) {
266 0         0 $modified_date = 1;
267 0         0 $self->[HOUR] = '24';
268 0         0 $self->[DAY]--;
269 0 0       0 if ($self->[DAY] < 1) {
270 0         0 $self->[MONTH]--;
271 0         0 $self->[DAY] = DaysInMonth($self->[MONTH], $self->[YEAR]);
272 0 0       0 if ($self->[MONTH] < 1) {
273 0         0 $self->[MONTH] = 12;
274 0         0 $self->[YEAR]--;
275             }
276             }
277             }
278 0         0 $modified_date;
279             }
280             # USGSDumbMidnightFix
281              
282             #----------------------------------------
283             # NOTES: Check date for validity.
284             # NOTES: 24:00:00 is ok, but will be changed internally to 00:00:00.
285             # ARG1 @a: component array to check for real-ness
286             # RETVAL: true/false
287             # ACCESS: private nonmethod
288             sub IsOkDate {
289 3 50 33 3 0 33 ($_[MONTH] >= 1 && $_[MONTH] <= 12 &&
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
290             $_[DAY] >= 1 && $_[DAY] <= DaysInMonth($_[MONTH], $_[YEAR]) &&
291             $_[HOUR] >= 0 && $_[HOUR] <= 24 &&
292             $_[MINUTE] >= 0 && $_[MINUTE] <= 59 &&
293             $_[SECOND] >= 0 && $_[SECOND] <= 59 &&
294             $_[FRACTION] >= 0 && $_[FRACTION] < 1);
295             }
296             # IsOkDate
297              
298             #----------------------------------------
299             # NOTES: Fix overshoots or undershoots in component increments.
300             # ARG1 @a: component array
301             # RETVAL: component array
302             # ACCESS: private method
303             sub _FixDate {
304 193     193   318 my $self = shift;
305             # Fix fractions of seconds.
306 193 100 100     1461 if ($self->[FRACTION] < 0 ||
      66        
307             $self->[FRACTION] >= 1 ||
308             (int($self->[SECOND]) != $self->[SECOND])) {
309             # Get the integer and fractional part of the seconds. Add the
310             # integer part to the seconds field. Keep the remaining
311             # fractional seconds in the fractional seconds field. Remember
312             # the standard accuracy for the fraction.
313 32         104 my $total = DateTime::Math::fadd(@$self[SECOND, FRACTION]);
314 32         186 my $second = int($total);
315 32         117 my $fraction = 0 + DateTime::Math::fsub($total, $second);
316             # Handle when the fractional seconds are negative. Sometimes when
317             # very small negative fractional numbers are added by 1 the number
318             # becomes 1. In this case, subtract by 1 again.
319 32 100       190 if ($fraction < 0) {
320 31         43 --$second;
321 31         43 ++$fraction;
322             }
323 32 100       88 if ($fraction >= 1) {
324 31         49 ++$second;
325 31         62 --$fraction;
326             }
327 32         60 $self->[SECOND] = $second;
328 32         66 $self->[FRACTION] = $fraction;
329             }
330              
331             # Fix seconds.
332 193         487 while ($self->[SECOND] > 59) {
333 1         5 $self->[SECOND] -= 60;
334 1         3 $self->[MINUTE]++;
335             }
336 193         456 while ($self->[SECOND] < 0) {
337 0         0 $self->[SECOND] += 60;
338 0         0 $self->[MINUTE]--;
339             }
340              
341             # Fix minutes.
342 193         438 while ($self->[MINUTE] > 59) {
343 0         0 $self->[MINUTE] -= 60;
344 0         0 $self->[HOUR]++;
345             }
346 193         431 while ($self->[MINUTE] < 0) {
347 0         0 $self->[MINUTE] += 60;
348 0         0 $self->[HOUR]--;
349             }
350              
351             # Fix hours.
352 193         414 while ($self->[HOUR] > 23) {
353 0         0 $self->[HOUR] -= 24;
354 0         0 $self->[DAY]++;
355             }
356 193         748 while ($self->[HOUR] < 0) {
357 1         2 $self->[HOUR] += 24;
358 1         4 $self->[DAY]--;
359             }
360              
361             # Fixing the days and months is a little complicated. Because the
362             # number of days in the month is not constant and we're using a
363             # function to calculate the number of days in the month, be careful.
364             # Go into a loop, fix the month first, then fix the days. If
365             # anything gets fixed, redo the loop.
366             FIX_DAY_MONTH:
367             {
368             # Fix months.
369 193         200 while ($self->[MONTH] > 12) {
  614         1676  
370 34         45 $self->[MONTH] -= 12;
371 34         77 $self->[YEAR]++;
372             }
373 614         1276 while ($self->[MONTH] < 1) {
374 0         0 $self->[MONTH] += 12;
375 0         0 $self->[YEAR]--;
376             }
377              
378             # Fix days.
379 614 100       1393 if ($self->[DAY] > DaysInMonth($self->[MONTH], $self->[YEAR])) {
380 420         877 $self->[DAY] -= DaysInMonth($self->[MONTH], $self->[YEAR]);
381 420         537 $self->[MONTH]++;
382 420         522 redo FIX_DAY_MONTH;
383             }
384 194 100       426 if ($self->[DAY] < 1) {
385 1         2 $self->[MONTH]--;
386 1         3 $self->[DAY] += DaysInMonth($self->[MONTH], $self->[YEAR]);
387 1         3 redo FIX_DAY_MONTH;
388             }
389             }
390 193         589 $self;
391             }
392             # _FixDate
393              
394             # Parse the internal string of the form yyyymmddhmmss.fff.
395             sub InternalStringToInternal {
396 3     3 0 5 my $in = shift;
397 3         15 my @a = unpack('a4a2a2a2a2a2a*', $in);
398 3 50       10 $a[6] = 0 unless $a[6];
399 3 50       8 if (IsOkDate(@a)) {
400 3         24 return @a;
401             } else {
402             return
403 0         0 }
404             }
405              
406             #----------------------------------------
407             # NOTES: Convert a datetime string to the components of an array.
408             # ARG1 $in: datetime string ("19YY.MM.DD hh:mm:ss.sss")
409             # RETVAL: Return an array cleaned and validified or an empty list
410             # RETVAL: in a list context, an undefined value in a scalar context,
411             # RETVAL: or nothing in a void context if the datetime string does
412             # RETVAL: not pass muster.
413             # ACCESS: private nonmethod
414             sub DatetimeToInternal {
415 135     135 0 182 my $in = shift;
416              
417             # Restructure date time into a consistent fixed width format
418             # suitable for easy parsing. Need to handle formats like:
419             # 1974.11.02
420             # 1974/11/02
421             # 1974.11.02 12:33:44.538
422             # 19741102123344.538
423             # yyyymmddhhmmss.fff
424              
425             # The return array.
426 135         231 my @ret = ();
427              
428             # Try to match different patterns.
429 135 50       342 if ($in =~ /$is_internal_format_re/o) {
430 0         0 @ret = InternalStringToInternal($in);
431             } else {
432             # 1) Protect the fractional seconds period.
433 135         245 $in =~ s/(:\d+)\.(.*)/$1\200$2/;
434             # 2) Convert periods to spaces.
435 135         397 $in =~ s/\./ /g;
436             # 3) Convert back to the period for fractional seconds.
437 135         200 $in =~ s/\200/\./;
438              
439             # Cycle through the numbers and set each element of the object.
440 135         330 my @a = map { 0; } (YEAR..FRACTION);
  945         1826  
441 135         332 my $i = 0;
442 135   66     1059 while ($i<=FRACTION && $in =~ m/(\d+(\.\d*)?)/g) {
443 609         4038 $a[$i++] = $1;
444             }
445              
446             # We need to read in either 3 or 6 numbers.
447 135 50 66     524 return if ($i != 3 and $i != 6);
448              
449 135 100       303 if ($i == 6) {
450             # Split the seconds into the integer and the fractional part.
451             # Store only the normal accuracy for the fractional part.
452 68         120 my $sec = $a[SECOND];
453 68         125 $a[SECOND] = int($sec);
454 68         248 $a[FRACTION] = 0 + DateTime::Math::fsub($sec, $a[SECOND]);
455             }
456 135         872 @ret = @a;
457             }
458 135 50       272 if (@ret) {
459 135         972 return @ret;
460             } else {
461 0         0 return;
462             }
463             }
464             # DatetimeToInternal
465              
466             #----------------------------------------
467             # NOTES: Convert a (hh, mm, ss, fs) into fraction of a day.
468             # RETVAL: fraction of a day (0 <= f < 1) with very large precision.
469             # ACCESS: private nonmethod
470             sub HMSToFraction {
471 179     179 0 404 my ($h, $m, $s, $fs) = @_;
472 179 50       375 defined($fs) or $fs = 0;
473             # Do the math that doesn't require high precision.
474 179         399 $s += 60*($m+60*$h);
475             # Now take into account high precision math.
476 179         487 $s = DateTime::Math::fadd($s, $fs);
477 179         836 DateTime::Math::fdiv($s, Secs_per_day);
478             }
479              
480             #----------------------------------------
481             # NOTES: Convert a fraction of a day into (hh, mm, ss, fs).
482             # RETVAL: array of (hh, mm, ss, fs).
483             # ACCESS: private nonmethod
484             sub FractionToHMS {
485 72     72 0 139 my $number = shift;
486              
487             # Remove the integer part of the number.
488 72         422 my $fraction = DateTime::Math::fsub($number, int($number));
489              
490 72         441 $fraction = DateTime::Math::fmul($fraction, 24.0);
491 72         378 my $h = int($fraction);
492 72         198 $fraction = DateTime::Math::fsub($fraction, $h);
493 72         387 $fraction = DateTime::Math::fmul($fraction, 60.0);
494 72         1599 my $m = int($fraction);
495 72         217 $fraction = DateTime::Math::fsub($fraction, $m);
496 72         730 $fraction = DateTime::Math::fmul($fraction, 60.0);
497 72         453 my $s = int($fraction);
498 72         201 $fraction = 0+DateTime::Math::fsub($fraction, $s);
499 72         689 ($h, $m, $s, $fraction);
500             }
501             # FractionToHMS
502              
503             #----------------------------------------
504             # NOTES: Convert a time (hh:mm:ss:fs) to seconds since midnight.
505             # RETVAL: Seconds since midnight.
506             # ACCESS: private nonmethod
507             sub SecsSinceMidnight {
508 114     114 0 186 my ($h, $m, $s, $fs) = @_;
509 114 50       228 defined($fs) or $fs = 0;
510             # Do the fast calculation with normal precision.
511 114         168 $s += 60*($m + 60*$h);
512             # Do the slow, very precise calculation.
513 114         278 DateTime::Math::fadd($s, $fs);
514             }
515             # SecsSinceMidnight
516              
517             #----------------------------------------
518             # NOTES: Convert a Gregorian day (yr,mo,day) to a serial day number,
519             # NOTES: ie, return number of days since the beginning of time.
520             # NOTES: SDN 1 is 25 Nov 4714 B.C.
521             # NOTES: Negative input years are B.C.
522             # NOTES: Returns 0 on error.
523             # NOTES: This and SDNToDay were basically lifted whole-cloth
524             # NOTES: from Scott E. Lee... details to follow... someday...
525             # ARG1 $y: year
526             # ARG2 $mo: month
527             # ARG3 $d: day
528             # RETVAL: SDN
529             # ACCESS: private nonmethod
530             sub DayToSDN {
531 270     270 0 531 my ($y, $mo, $d) = @_;
532             # NOTES: This is internal, so I assume all inputs are valid. Caveat felis.
533              
534             # Make the year positive.
535 270         493 $y += 4800 + ($y<0);
536             # Adjust to nice start of year.
537 270 100       571 if ($mo > 2) {
538 99         146 $mo -= 3;
539             } else {
540 171         200 $mo += 9;
541 171         234 $y--;
542             }
543              
544             # Calculate sdn.
545 1     1   3937 use integer;
  1         12  
  1         6  
546 270         1286 (((($y/100)*Days_per_400_years)/4) +
547             ((($y%100)*Days_per_4_years) /4) +
548             ( ($mo*Days_per_5_months + 2) /5) + $d - SDN_Offset);
549             }
550             # DayToSDN
551              
552             #----------------------------------------
553             # NOTES: Convert a SDN day back to normal time (yr,mo,day).
554             # NOTES: See DayToSDN().
555             # ARG1 $sdn
556             # RETVAL: array of (yr,mo,day)
557             # ACCESS: private nonmethod
558             sub SDNToDay {
559 62     62 0 86 my $sdn = shift;
560              
561             # A mass of confused calculations.
562 1     1   103 use integer;
  1         1  
  1         4  
563 62         134 my $temp = ($sdn+SDN_Offset)*4-1;
564 62         119 my $cent = $temp/Days_per_400_years;
565 62         111 $temp = (($temp%Days_per_400_years) / 4) * 4 + 3;
566 62         164 my $y = ($cent*100)+($temp/Days_per_4_years);
567 62         103 my $doy = ($temp%Days_per_4_years)/4+1;
568 62         97 $temp = $doy*5-3;
569 62         99 my $m = $temp/Days_per_5_months;
570 62         100 my $d = ($temp%Days_per_5_months)/5+1;
571             # Convert to a real date.
572 62 50       385 if ($m < 10) {
573 62         126 $m += 3;
574             } else {
575 0         0 $m -= 9;
576 0         0 $y++;
577             }
578 62         78 $y -= 4800;
579 62 50       138 $y-- if ($y <= 0);
580 62         187 ($y, $m, $d);
581             }
582             # SDNToDay
583              
584             sub stringify {
585 361     361 0 536 my $self = shift;
586 361         607 my $sec = $self->[SECOND] + $self->[FRACTION];
587 361 100       714 if ($sec == int($sec)) {
588 342         2753 return sprintf('%04d%02d%02d%02d%02d%02d', @$self[0..SECOND]);
589             } else {
590 19         21 my $str;
591 19 100       31 if ($sec >= 10) {
592 2         22 $str = sprintf('%04d%02d%02d%02d%02d%f', @$self[0..MINUTE], $sec);
593             } else {
594 17         126 $str = sprintf('%04d%02d%02d%02d%02d0%f', @$self[0..MINUTE], $sec);
595             }
596             # Trim any trailing 0's.
597 19         161 $str =~ s/\.?0*$//;
598 19         84 return $str;
599             }
600             }
601              
602             #
603             # Public DateTime::Precise class methods
604             #
605              
606              
607             #----------------------------------------
608             # NOTES: Constructor.
609             # NOTES: Return blessed reference to a array. If the input is not
610             # NOTES: is not valid, then return an empty list in a list context, an
611             # NOTES: undefined value in a scalar context, or nothing in a void
612             # NOTES: context.
613             # ARG1 $dt: Initial date+time to set object to (optional)
614             # ACCESS: method
615             # EXAMPLE: $dt = DateTime::Precise->new('1998.03.25 20:25:30');
616             # EXAMPLE: $dt = DateTime::Precise->new('1974.11.02');
617             # EXAMPLE: $dt = DateTime::Precise->new('19741102123344');
618             # EXAMPLE: $dt = DateTime::Precise->new();
619             sub new {
620 144     144 1 585 my $proto = shift;
621 144   66     635 my $class = ref($proto) || $proto;
622              
623             # Create the blessed array with the correct number of elements.
624 144         566 my $self = bless [YEAR .. FRACTION], $class;
625              
626             # Parse the input arguments depending upon the number of arguments.
627 144 100       537 if (@_ == 0) {
    100          
    50          
628 8         29 $self->set_gmtime_from_epoch_time;
629             } elsif (@_ == 1) {
630             # If there is only one argument, it is either the Unix epoch time
631             # or a date string. First try to match the exact internal format
632             # and parse it using InternalStringToInternal. Otherwise, see if
633             # it is a number and treat it as an epoch time. Finally, treat
634             # the string as a gernal time/date format.
635 135         231 my $arg = shift;
636 135 100       910 if ($arg =~ /$is_internal_format_re/o) {
    100          
637 2         7 @$self = InternalStringToInternal($arg);
638 2 50       8 @$self or return;
639             } elsif ($arg =~ /^\d+(\.\d*)?$/) {
640 1         5 $self->set_gmtime_from_epoch_time($arg);
641             } else {
642 132         446 @$self = DatetimeToInternal($arg);
643 132 50       506 @$self or return;
644             }
645             } elsif (@_ > 1) {
646 1 50       7 $self->set_time(@_) or return;
647             }
648              
649 144         784 $self;
650             }
651             # new
652              
653             sub unix_seconds_since_epoch {
654 18     18 1 70 $_[0] - JANUARY_1_1970;
655             }
656              
657             sub gps_seconds_since_epoch {
658 37     37 1 115 $_[0] - JANUARY_6_1980;
659             }
660              
661             sub gps_week_seconds_day {
662 19     19 1 38 my $self = shift;
663 19         47 my $epoch_seconds = $self->gps_seconds_since_epoch;
664 19         120 my $week = int($epoch_seconds/Secs_per_week);
665 19         41 my $seconds = $epoch_seconds - $week*Secs_per_week;
666 19         39 my $day = int($seconds/Secs_per_day);
667 19         68 ($week, $seconds, $day);
668             }
669              
670             sub gps_week {
671 0     0 1 0 ($_[0]->gps_week_seconds_day)[0];
672             }
673              
674             sub gps_seconds {
675 0     0 1 0 ($_[0]->gps_week_seconds_day)[1];
676             }
677              
678             sub gps_day {
679 0     0 1 0 ($_[0]->gps_week_seconds_day)[2];
680             }
681              
682             sub asctime {
683 21     21 1 55 my $self = shift;
684              
685 21         77 sprintf("%s %s %2d %02d:%02d:%02d %s %4d",
686             $WeekAbbrev[$self->weekday],
687             $MonthAbbrev[$self->month],
688             $self->day,
689             $self->hours,
690             $self->minutes,
691             $self->seconds,
692             $TZ,
693             $self->year);
694             }
695              
696             sub strftime {
697 1     1 1 3 my $self = shift;
698 1         2 my $template = shift;
699 1 50       6 $template = '' unless defined $template;
700              
701             # Go through the template and substitute for all known patterns.
702             # Change %% to \200 to protect it and not have it attach itself to
703             # other characters.
704 1         4 $template =~ s/%%/\200/g;
705 1         2 my %strftime_values = %{$self->_strftime_values};
  1         5  
706 1         17 while (my ($key, $value) = each %strftime_values) {
707 40         440 $template =~ s/%$key/$value/g;
708             }
709 1         7 $template =~ s/\200/%/g;
710 1         18 return $template;
711             }
712              
713             sub set_time {
714 6     6 1 14 my $self = shift;
715 6         11 my $template = shift;
716 6         16 my @values = @_;
717              
718             # Make a copy of the current DateTime::Precise object to work on.
719 6         22 my $work = $self->copy;
720              
721             # If the input fails, then return an empty list in a list context, an
722             # undefined value in a scalar context, or nothing in a void context.
723              
724             # The template should not be empty.
725 6 50       18 return unless defined $template;
726              
727             # Split up the template into individual characters. There should be
728             # some keys.
729 6         27 my @keys = split(//, $template);
730 6 50       20 return unless @keys;
731              
732             # The first key must be an absolute time specifier.
733 6 50       22 return unless ($SET_MASK{$keys[0]} & MASK_ABSOLUTE);
734              
735             # The rest of the keys must be relative.
736 6         22 foreach my $key (@keys[1..$#keys]) {
737 20 50       59 return if ($SET_MASK{$key} & MASK_ABSOLUTE);
738             }
739              
740             # Go through each key and set the time from it.
741 6         14 foreach my $key (@keys) {
742             # Get the argument if the key requires it. Leave the subroutine
743             # if there is no value for the key.
744 26         29 my $arg = 0;
745 26 100       73 unless ($SET_MASK{$key} & MASK_NO_ARG) {
746 24 100       58 return unless @values;
747 23         35 $arg = shift(@values);
748             }
749              
750             # Arguments can either be numerical or month names.
751 25         31 my $partial = 0;
752 25 50       91 if ($arg !~ /[a-zA-Z]/) {
753             # Get the non-integer part of the argument.
754 25 100       63 $partial = ($arg - int($arg)) if ($SET_MASK{$key} & MASK_USES_PARTIAL);
755 25         31 $arg = int($arg);
756             }
757              
758             # These keys set the time completely.
759 25 100       57 if ($SET_MASK{$key} & MASK_ABSOLUTE) {
760             # Set time to now.
761 6 100       20 $key eq 'N' and $work->set_gmtime_from_epoch_time, next;
762             # Set time to GPS time 0.
763 4 50       12 $key eq 'G' and $work->clone(JANUARY_6_1980), next;
764             # Set time to seconds since January 1, 1970 UTC.
765 4 50       17 $key eq 's' and do {
766 0         0 $work->set_gmtime_from_unix_epoch($arg);
767 0         0 $work->addSec($partial);
768 0         0 next;
769             };
770             # Set time to year and fractional year.
771 4 50       12 $key eq 'Y' and do {
772 4         12 $work->year($arg);
773 4         14 $work->month(1);
774 4         12 $work->day(1);
775 4         13 $work->hours(0);
776 4         13 $work->minutes(0);
777 4         10 $work->seconds(0);
778 4 50       83 $work->addSec($partial * Secs_per_day *
779             (IsLeapYear($arg) ? 366 : 365));
780 4         10 next;
781             };
782             # Set time to modified fractional year.
783 0 0       0 $key eq 'J' and do {
784 0         0 my $time = ($arg + MODIFIED_JULIAN_DAY + $partial) * Secs_per_day;
785 0         0 $work->set_gmtime_from_unix_epoch($time);
786 0         0 next;
787             };
788 0         0 cluck "DateTime::Precise::set_time: unknown absolute key '$key'";
789 0         0 next;
790             }
791              
792             # The remaining keys set the time relative to the current time.
793 19 100       43 if ($SET_MASK{$key} & MASK_USES_MULTIPLIER) {
794             # If the key requires a multiplier, take care of it.
795 16         27 $arg -= $SET_START_VALUE{$key};
796 16         24 $arg *= $SET_MULTIPLER_VALUE{$key};
797 16         24 $partial *= $SET_MULTIPLER_VALUE{$key};
798 16         41 $work->addSec($arg + $partial);
799 16         39 next;
800             }
801              
802             # Otherwise we're using a special key.
803             # Set time to the month.
804 3 50       8 $key eq 'B' and do {
805 3         28 $work->inc_month($arg);
806 3         6 next;
807             };
808 0         0 cluck "DateTime::Precise::set_time: unknown relative key '$key'";
809             }
810              
811             # Set the real DateTime::Precise to the working one.
812 5         20 $self->clone($work);
813             }
814              
815             sub get_time {
816 1     1 1 4 my ($self, $template) = @_;
817              
818             # For each conversion, add one more value to an output array
819             # containing the requested value.
820 1         3 my %strftime_values = %{$self->_strftime_values};
  1         5  
821 1         8 my @values;
822 1         5 foreach my $char (split(//, $template)) {
823 3 50       13 push(@values, $strftime_values{$char}) if defined($strftime_values{$char});
824             }
825 1         17 @values;
826             }
827              
828             # Take in the day of the year, the year, the first day of the week (0
829             # = Sunday, 1 = Monday) and wether days before the first week of the
830             # year return as 0 or 53. The last option, if true, uses the ISO 8601
831             # standard that January 4th is in week1. Set the last two options to
832             # be true to get the %V behavior for strftime.
833             sub _week_of_year {
834 54     54   112 my ($doy, $year, $week_begin, $previous, $jan4week1) = @_;
835              
836             # Calculate the day of the week for January 1.
837 54         206 my $dow = DateTime::Precise->new("$year 1 1")->weekday;
838              
839             # Calculate number of days between Jan 1 and the beginning of the
840             # first week.
841 54         294 my $diff = $week_begin - $dow;
842 54 50       131 $diff < 0 and $diff += 7;
843              
844             # Calculate the day of the year for the beginning of the first week.
845 54         97 my $first_weekday = 1 + $diff;
846              
847             # If January 4th has to be in the first week and it currently isn't,
848             # then add 7 to the day of the year. January 4th isn't in the first
849             # week if the difference between the first day of the first week and
850             # January 1 is greater than 3.
851 54 100       153 if ($jan4week1) {
852 18 100       52 $diff > 3 and $doy += 7;
853             }
854              
855             # If the day of the year is less than the beginning of the first
856             # week, then either return 0 or 53.
857 54 100       228 return ($previous ? 53 : 0) if ($doy < $first_weekday);
    100          
858              
859             # Return the week.
860 46         903 ($doy - $first_weekday)/7 + 1;
861             }
862              
863              
864             sub _strftime_values {
865 18     18   41 my $self = shift;
866              
867             # These values are strings preceeded by 0 if they don't fill all of
868             # the space.
869 18         72 my $y = sprintf('%04d', $self->year);
870 18         74 my $mo = sprintf('%02d', $self->month);
871 18         70 my $d = sprintf('%02d', $self->day);
872 18         61 my $h = sprintf('%02d', $self->hours);
873 18         61 my $mn = sprintf('%02d', $self->minutes);
874 18         56 my $s = sprintf('%02d', $self->seconds);
875              
876             # These are numerical values.
877 18         54 my $week_day = $self->weekday;
878 18         136 my $day_of_year = $self->day_of_year;
879 18         152 my $gps_seconds_since_epoch = $self->gps_seconds_since_epoch;
880 18         267 my $unix_seconds_since_epoch = $self->unix_seconds_since_epoch;
881 18         104 my ($gps_week, $gps_seconds, $gps_day) = $self->gps_week_seconds_day;
882              
883             # These are the initial values for strftime. The remaining ones
884             # that get put togther with these are below.
885 18 100       140 my %values = (
    100          
    100          
886             # same as %
887             '%' => '%',
888              
889             # the abbreviated weekday name
890             'a' => $WeekAbbrev[$week_day],
891              
892             # the full weekday name
893             'A' => $WeekName[$week_day],
894              
895             # the abbreviated month name
896             'b' => $MonthAbbrev[$mo],
897              
898             # the full month name
899             'B' => $MonthName[$mo],
900              
901             # the appropriate date and time representation
902             'c' => $self->asctime,
903              
904             # century number; single digits are preceded by 0
905             'C' => sprintf('%02u', int($y/100)),
906              
907             # the day of month [1,31]; single digits are preceded by 0
908             'd' => $d,
909              
910             # the day of month [1,31]; single digits are preceded by a space
911             'e' => sprintf('%2s', $d+0),
912              
913             # the abbreviated month name
914             'h' => $MonthAbbrev[$mo],
915              
916             # hour (24-hour clock) [0,23]; single digits are preceded by 0
917             'H' => $h,
918              
919             # hour (12-hour clock) [1,12]; single digits are preceded by 0
920             'I' => sprintf('%02d', (($h % 12) == 0) ? 12 : ($h % 12)),
921              
922             # the day of year
923             'j' => sprintf('%03d', $day_of_year),
924              
925             # the hour (24-hour clock) [0,23]; single digits are preceded by a blank
926             'k' => sprintf('%2s', $h+0),
927              
928             # the hour (12-hour clock) [1,12]; single digits are preceded by a blank
929             'l' => sprintf('%2s', (($h % 12) == 0) ? 12 : ($h % 12)),
930              
931             # the month number [1,12]; single digits are preceded by 0
932             'm' => $mo,
933              
934             # the minute [00,59]
935             'M' => $mn,
936              
937             # insert a newline
938             'n' => "\n",
939              
940             # the equivalent of either a.m. or p.m.
941             'p' => $LC_AMPM[$h > 11],
942              
943             # the seconds [00,59]
944             'S' => $s,
945              
946             # insert a tab
947             't' => "\t",
948              
949             # the weekday as a decimal number [1,7] with Monday being 1
950             'u' => $week_day == 0 ? 7 : $week_day,
951              
952             # week number of year as a decimal number [00,53] with Sunday
953             # as the first day of week 1
954             'U' => sprintf('%02d', _week_of_year($day_of_year, $y, 0, 0, 0)),
955              
956             # week number of the year as a decimal number [01,53], with
957             # Monday as the first day of the week. If the week containing
958             # 1 January has four or more days in the new year, then it is
959             # considered week 1; otherwise, it is week 53 of the previous
960             # year, and the next week is week 1.
961             'V' => sprintf('%02d', _week_of_year($day_of_year, $y, 1, 1, 1)),
962              
963             # the weekday as a decimal number [0,6], with 0 representing Sunday
964             'w' => $week_day,
965              
966             # the week number of year as a decimal number [00,53], with Monday
967             # as the first day of week 1
968             'W' => sprintf('%02d', _week_of_year($day_of_year, $y, 1, 0, 0)),
969              
970             # year within century [00,99]
971             'y' => sprintf('%02d', $y % 100),
972              
973             # the year, including the century (for example 1998)
974             'Y' => sprintf('%04d', $y),
975              
976             # time zone name or abbreviation, or no bytes if no time zone
977             # information exists
978             'Z' => $TZ
979             );
980              
981             # These are values built up using the previous ones.
982              
983             # the date as %m/%d/%y
984 18         305 $values{'D'} = "$values{'m'}/$values{'d'}/$values{'y'}";
985             # appropriate time representation in 12-hour clock format with %p
986 18         90 $values{'r'} = "$values{'I'}:$values{'M'}:$values{'S'} $values{'p'}";
987             # time as %H:%M
988 18         98 $values{'R'} = "$values{'H'}:$values{'M'}";
989             # time as %H:%M:%S
990 18         204 $values{'T'} = "$values{'H'}:$values{'M'}:$values{'S'}",
991             # the appropriate date representation
992             $values{'x'} = "$values{'m'}/$values{'d'}/$values{'y'}";
993             # the appropriate time representation
994 18         46 $values{'X'} = $values{'T'};
995              
996             # Now add some nonstandard values.
997              
998             # seconds since UTC January 1, 1970
999 18         39 $values{'s'} = $unix_seconds_since_epoch;
1000             # the GPS week (4 digits with leading 0's)
1001 18         67 $values{'G'} = sprintf("%04d", $gps_week);
1002             # the GPS seconds into the GPS week with no leading zeros
1003 18         44 $values{'g'} = $gps_seconds;
1004             # the GPS day (1 digit)
1005 18         61 $values{'f'} = $gps_day;
1006             # the GPS day (1 digit)
1007 18         34 $values{'F'} = $gps_day + 1;
1008 18         227 \%values;
1009             }
1010              
1011             #-------------------------------------------
1012             # NOTES: Set this DateTime::Precise equal to another.
1013             # ARG2 $other: Other DateTime::Precise object to set by.
1014             # ACCESS: method
1015             # EXAMPLE: $dt->clone($other_dt);
1016             sub clone {
1017 11     11 1 24 @{$_[0]} = @{$_[1]};
  11         68  
  11         18  
1018             }
1019             # clone
1020              
1021             #-------------------------------------------
1022             # NOTES: Create a copy of this DateTime::Precise.
1023             # ACCESS: method
1024             # EXAMPLE: $t1 = $t2->copy;
1025             sub copy {
1026 13     13 1 17 bless [ @{$_[0]} ], ref($_[0]);
  13         104  
1027             }
1028              
1029              
1030             # NOTES: Set (if param), or return the stringified DateTime::Precise.
1031             # NOTES: See copy() for a better way to copy DateTime::Precises.
1032             # ARG2 $in: (optional) estring to set internal to.
1033             # RETVAL: estring
1034             # ACCESS: method
1035             # EXAMPLE: print $dt->internal('19980325202530'), " compressed\n";
1036             sub internal {
1037 134     134 0 265 my ($self, $in) = @_;
1038 134 100       344 if ($in) {
1039 1         19 my @a = InternalStringToInternal($in);
1040 1 50       9 @$self = @a if @a;
1041             }
1042 134         409 "$self";
1043             }
1044             # internal
1045              
1046             #----------------------------------------
1047             # some days have bouncers and won't let you in.
1048             # NOTES: Set date/time from passed datetime string.
1049             # ARG2 $dt: string in datetime format ("YYYY.MM.DD hh:mm:ss")
1050             # ACCESS: method
1051             # RETVAL: return 1 if the date was sucessfully set, an empty list in
1052             # RETVAL: a list context, an undefined value in a scalar context, or
1053             # RETVAL: nothing in a void context.
1054             # EXAMPLE: $dt->set_from_datetime("1998.03.23 16:58:11");
1055             sub set_from_datetime {
1056 3     3 1 7 my ($self, $dt, $ret) = @_;
1057 3 50       10 if (defined $dt) {
1058 3         9 my @a = DatetimeToInternal($dt);
1059 3 50       10 if (@a) {
1060 3         14 @$self = @a;
1061 3         9 $ret = 1;
1062             }
1063             }
1064 3 50       7 if ($ret) {
1065 3         6 return $self;
1066             } else {
1067 0         0 return;
1068             }
1069             }
1070             # set_from_datetime
1071              
1072             #----------------------------------------
1073             # NOTES: Set date/time from decimal day of year, where day 1 is
1074             # NOTES: midnight January 1.
1075             # ARG2 $j: day of year
1076             # ARG3 $y: year
1077             # RETVAL: 1 if the date was sucessfully set, an empty list in a list
1078             # RETVAL: context, an undefined value in a scalar context, or nothing
1079             # RETVAL: in a void context.
1080             # ACCESS: method
1081             # EXAMPLE: $dt->set_from_day_of_year(1998, 1.325);
1082             sub set_from_day_of_year {
1083 10     10 1 82 my $self = shift;
1084 10         16 my $y = shift;
1085 10         16 my $j = shift;
1086              
1087 10 50       30 unless (defined $y) {
1088 0         0 cluck "DateTime::Precise::set_from_day_of_year called without year parameter";
1089 0         0 return;
1090             }
1091 10         11 $y = int($y);
1092              
1093 10 50       27 unless (defined $j) {
1094 0         0 cluck "DateTime::Precise::set_from_day_of_year called without day of year parameter";
1095 0         0 return;
1096             }
1097              
1098 10         22 my $leap = IsLeapYear($y);
1099 10 50       24 return if ($j < 1);
1100 10 100       30 return if ($j >= ($leap ? 367 : 366));
    50          
1101              
1102 10         17 my @a = ($y);
1103 10         23 @a[HOUR..FRACTION] = FractionToHMS($j);
1104              
1105             # Calculate the month and the day. Shift the first value in the
1106             # MonthDays array since it represents the number of days in
1107             # December.
1108 10         39 my @days_in_month = @MonthDays;
1109 10 100       27 $leap and ++$days_in_month[2];
1110 10         12 shift(@days_in_month);
1111              
1112             # Count the number of number of months into the year this date is.
1113 10         19 my ($m, $d) = (0, 0);
1114 10         15 $j = int($j);
1115 10         24 while ($j>0) {
1116 40         44 $m++;
1117 40 100       78 if ($j <= $days_in_month[0]) {
1118 10         11 $d = int($j);
1119 10         22 $j = 0;
1120             } else {
1121 30         33 $j -= $days_in_month[0];
1122 30         49 shift(@days_in_month);
1123             }
1124             }
1125 10         15 $a[YEAR] = $y;
1126 10         16 $a[MONTH] = $m;
1127 10         14 $a[DAY] = $d;
1128 10         48 @$self = (@a);
1129 10         32 $self->_FixDate;
1130             }
1131             # set_from_day_of_year
1132              
1133             #----------------------------------------
1134             # NOTES: Returns the SDN representing the date, plus a fraction
1135             # NOTES: representing the time since midnight (ie, noon=0.5).
1136             # RETVAL: large, fractional number (eg, 2645455.075)
1137             # ACCESS: method
1138             sub serial_day {
1139 156     156 1 450 my $self = shift;
1140 156         622 DateTime::Math::fadd(DayToSDN(@$self), HMSToFraction(@$self[HOUR..FRACTION]));
1141             }
1142             # serial_day
1143              
1144             #----------------------------------------
1145             # NOTES: Set date/time from the serial day.
1146             # ARG1: serial day
1147             # RETVAL: 1 if the date was sucessfully set, an empty list in a list
1148             # RETVAL: context, an undefined value in a scalar context, or nothing
1149             # RETVAL: in a void context if the date was not set.
1150             # ACCESS: method
1151             # EXAMPLE: $dt->set_from_serial_day(4312343.325);
1152             sub set_from_serial_day {
1153 62     62 1 106 my $self = shift;
1154 62         98 my $sdn = shift;
1155              
1156 62 50       151 unless (defined $sdn) {
1157 0         0 cluck "DateTime::Precise::set_from_serial_day called without serial day parameter";
1158 0         0 return;
1159             }
1160              
1161             # Split the serial day into day and fraction of day.
1162 62         202 my $days = int($sdn);
1163 62         170 my @a = SDNToDay($days);
1164 62         173 @a[HOUR..FRACTION] = FractionToHMS($sdn);
1165 62         267 @$self = @a;
1166 62         225 $self->_FixDate;
1167             }
1168             # set_from_serial_day
1169              
1170             #----------------------------------------
1171             # NOTES: Set from epoch time (to local date/time).
1172             # ARG2 $epoch: seconds since 1904 (MacOS) or 1970 (most other systems, ie Unix)
1173             # RETVAL: 1 if the date was sucessfully set. If the date could not
1174             # RETVAL: be set, then it returns an empty list in a list context, an
1175             # RETVAL: undefined value in a scalar context, or nothing in a void
1176             # RETVAL: context.
1177             # ACCESS: method
1178             # EXAMPLE: $dt->set_localtime_from_epoch_time(time);
1179             sub set_localtime_from_epoch_time {
1180 1     1 1 3 my $self = shift;
1181 1         3 my $time = shift;
1182 1 50       4 $time = time unless defined $time;
1183 1         4 my $epoch = int($time);
1184 1         196 my @a = localtime($epoch);
1185 1         4 $self->[YEAR] = 1900 + $a[5];
1186 1         3 $self->[MONTH] = $a[4] + 1;
1187 1         2 $self->[DAY] = $a[3];
1188 1         3 $self->[HOUR] = $a[2];
1189 1         2 $self->[MINUTE] = $a[1];
1190 1         3 $self->[SECOND] = $a[0];
1191 1         2 $self->[FRACTION] = $time - $epoch;
1192 1         4 $self;
1193             }
1194             # set_localtime_from_epoch_time
1195              
1196             #----------------------------------------
1197             # NOTES: Set from epoch time (to local date/time).
1198             # ARG2 $epoch: seconds since 1904 (MacOS) or 1970 (most other systems, ie Unix)
1199             # RETVAL: 1 if the date was sucessfully set. If the date could not
1200             # RETVAL: be set, then it returns an empty list in a list context, an
1201             # RETVAL: undefined value in a scalar context, or nothing in a void
1202             # RETVAL: context.
1203             # ACCESS: method
1204             # EXAMPLE: $dt->set_gmtime_from_epoch_time(time);
1205             sub set_gmtime_from_epoch_time {
1206 13     13 1 23 my $self = shift;
1207 13         18 my $time = shift;
1208 13 100       67 $time = time unless defined $time;
1209 13         28 my $epoch = int($time);
1210 13         103 my @a = gmtime($epoch);
1211 13         162 $self->[YEAR] = 1900 + $a[5];
1212 13         26 $self->[MONTH] = $a[4] + 1;
1213 13         20 $self->[DAY] = $a[3];
1214 13         27 $self->[HOUR] = $a[2];
1215 13         19 $self->[MINUTE] = $a[1];
1216 13         63 $self->[SECOND] = $a[0];
1217 13         25 $self->[FRACTION] = $time - $epoch;
1218 13         34 $self;
1219             }
1220             # set_gmtime_from_epoch_time
1221              
1222             sub set_from_gps_week_seconds {
1223 1     1 1 2 my $self = shift;
1224 1         2 my $gps_week = shift;
1225 1         2 my $gps_seconds = shift;
1226              
1227 1 50       5 unless (defined $gps_week) {
1228 0         0 cluck "DateTime::Precise::set_from_gps_week_seconds called without gps_week parameter";
1229 0         0 return;
1230             }
1231              
1232 1 50       4 unless (defined $gps_seconds) {
1233 0         0 cluck "DateTime::Precise::set_from_gps_week_seconds called without gps_seconds parameter";
1234 0         0 return;
1235             }
1236              
1237 1         4 $self->clone(JANUARY_6_1980);
1238 1         7 $self->addSec($gps_week * 7, DAY);
1239 1         8 $self->addSec($gps_seconds);
1240              
1241 1         5 $self;
1242             }
1243              
1244             #----------------------------------------
1245             # NOTES: Return the day of the year including the fraction of the day.
1246             # ACCESS: method
1247             # EXAMPLE: $j = $dt->day_of_year;
1248             sub day_of_year {
1249 23     23 1 52 my $self = shift;
1250 23         57 my $y = $self->[YEAR];
1251 23         41 my $m = $self->[MONTH];
1252 23         37 my $d = $self->[DAY];
1253 23         78 for (my $i=1; $i<$m; ++$i) {
1254 48         79 $d += DaysInMonth($i, $y);
1255             }
1256 23         81 DateTime::Math::fadd($d, HMSToFraction(@$self[HOUR..FRACTION]));
1257             }
1258             # day_of_year
1259              
1260             #----------------------------------------
1261             # NOTES: Return the Julian day of the year including the fraction of
1262             # NOTES: the day.
1263             # ACCESS: method
1264             # EXAMPLE: $j = $dt->julian_day;
1265             sub julian_day {
1266 2     2 1 5 DateTime::Math::fsub($_[0]->day_of_year, 1);
1267             }
1268             # julian_day
1269              
1270             #----------------------------------------
1271             # NOTES: Return the year and optionally set it.
1272             # ACCESS: method
1273             # EXAMPLE: my $year = $dt->year(); $dt->year(1988);
1274             sub year {
1275 47     47 1 90 my $self = shift;
1276              
1277 47 100       114 if (@_) {
1278 5         12 $self->[YEAR] = int(shift);
1279             }
1280              
1281 47         580 $self->[YEAR];
1282             }
1283             # year
1284              
1285             #----------------------------------------
1286             # NOTES: Return the month and optionally set it.
1287             # ACCESS: method
1288             # EXAMPLE: my $month = $dt->month(); $dt->month(11);
1289             sub month {
1290 47     47 1 97 my $self = shift;
1291              
1292 47 100       123 if (@_) {
1293 5         21 $self->[MONTH] = int(shift);
1294 5         17 $self->_FixDate;
1295             }
1296              
1297 47         182 $self->[MONTH];
1298             }
1299             # month
1300              
1301             #----------------------------------------
1302             # NOTES: Return the day and optionally set it.
1303             # ACCESS: method
1304             # EXAMPLE: my $day = $dt->day(); $dt->day(21);
1305             sub day {
1306 47     47 1 77 my $self = shift;
1307              
1308 47 100       109 if (@_) {
1309 5         9 $self->[DAY] = int(shift);
1310 5         15 $self->_FixDate;
1311             }
1312              
1313 47         162 $self->[DAY];
1314             }
1315             # day
1316              
1317             #----------------------------------------
1318             # NOTES: Return the hours and optionally set them.
1319             # ACCESS: method
1320             # EXAMPLE: my $hours = $dt->hours(); $dt->hours(13);
1321             sub hours {
1322 47     47 1 82 my $self = shift;
1323              
1324 47 100       119 if (@_) {
1325 5         10 $self->[HOUR] = int(shift);
1326 5         15 $self->_FixDate;
1327             }
1328              
1329 47         152 $self->[HOUR];
1330             }
1331             # hours
1332              
1333             #----------------------------------------
1334             # NOTES: Return the minutes and optionally set them.
1335             # ACCESS: method
1336             # EXAMPLE: my $minutes = $dt->minutes(); $dt->minutes(49);
1337             sub minutes {
1338 47     47 1 93 my $self = shift;
1339              
1340 47 100       111 if (@_) {
1341 5         11 $self->[MINUTE] = int(shift);
1342 5         13 $self->_FixDate;
1343             }
1344              
1345 47         136 $self->[MINUTE];
1346             }
1347             # minutes
1348              
1349             #----------------------------------------
1350             # NOTES: Return the seconds and optionally set them.
1351             # ACCESS: method
1352             # EXAMPLE: my $seconds = $dt->seconds(); $dt->seconds(29);
1353             sub seconds {
1354 108     108 1 18716 my $self = shift;
1355              
1356 108 100       321 if (@_) {
1357 66         145 $self->[SECOND] = shift;
1358 66         118 $self->[FRACTION] = 0;
1359 66         164 $self->_FixDate;
1360             }
1361              
1362 108         343 $self->[SECOND] + $self->[FRACTION];
1363             }
1364             # seconds
1365              
1366             #----------------------------------------
1367             # NOTES: Returns the parameter string with substitutions:
1368             # see Note at Bottom (??)
1369             # NOTES: %x number zero-padded to 2 digits (ie, '02')
1370             # NOTES: %-x number space-padded to 2 digits (ie, ' 2')
1371             # NOTES: %^x unpadded number (ie, '2')
1372             # NOTES: %~x 3-letter abbrev corresponding to value (%M and %w only)
1373             # NOTES: %*x full name corresponding to value (%M and %w only)
1374             # NOTES: %% '%'
1375             #
1376             # NOTES: ...where x is one of: Y (year), M (month), D (day), h (hour),
1377             # NOTES: m (minutes), s (seconds), w (day of the week).
1378             # NOTES: Also supported are W (water year) and E (internal format).
1379             # i'm taking out %J now, since no one's using them.
1380             # ARG2 $form: format string (see notes)
1381             # RETVAL: string, formatted at requested.
1382             # ACCESS: method
1383             # EXAMPLE: print $dt->dprintf("%^Y.%M.%D %h:%m:%s"); # datetime
1384             # EXAMPLE: print $dt->dprintf("%~w %~M %-D %h:%m:%s CST %^Y"); # ctime
1385             sub dprintf {
1386 8     8 1 14 my $self = shift;
1387 8         15 my $form = shift;
1388              
1389             # Fix the date if the special USGS midnight treatment needs to be
1390             # applied.
1391 8         10 my $usgs_midnight_fix_applied = 0;
1392 8 50       17 if ($USGSMidnight) {
1393 0         0 $usgs_midnight_fix_applied = $self->USGSDumbMidnightFix;
1394             }
1395              
1396 8         96 my @form = split(//,$form); # make a list of all the chars in the format
1397 8         44 my ($y, $mo, $d, $h, $m, $s) = @$self[YEAR,MONTH,DAY,HOUR,MINUTE,SECOND];
1398 8         10 my @retn;
1399              
1400             # We shouldn't ever store in non-USGS midnight. Check each char in
1401             # the format for formatting.
1402 8         30 while (@form) {
1403 167         378 my $char = shift(@form);
1404 167 100       338 if ($char eq '%') { # found a format
1405             # the second char... mod becomes the formatting char (~^*-)
1406 49         64 my $mod = shift(@form);
1407 49 50       87 if ($mod eq '%') { # %%
1408             # only push one '%'
1409 0         0 push(@retn, '%');
1410             } else {
1411             # $type is the letter (field specifier)
1412 49         64 my $type = $mod;
1413 49 100       159 $type = shift(@form) unless ($mod=~/[a-zA-Z]/);
1414             # put the value to push into $field
1415 49         63 my $field = '';
1416 49 100       190 if ($type eq 's') {
    100          
    100          
    100          
    100          
    100          
    50          
    50          
    0          
1417 8         12 $field = $s;
1418             } elsif ($type eq 'm') {
1419 8         11 $field = $m;
1420             } elsif ($type eq 'h') {
1421 8         14 $field = $h;
1422             } elsif ($type eq 'D') {
1423 8         11 $field = $d;
1424             } elsif ($type eq 'M') {
1425 8         10 $field = $mo;
1426             } elsif ($type eq 'Y') {
1427 8         17 $field = $y;
1428             } elsif ($type eq 'W') {
1429             # This is water year.
1430 0         0 $field = $y;
1431 0 0       0 $field++ if ($mo > 9);
1432             } elsif ($type eq 'w') {
1433 1         11 $field = $self->weekday;
1434             } elsif ($type eq 'E') {
1435 0         0 $mod = '^';
1436 0         0 $field = "$self";
1437             }
1438              
1439             # Push an approprite char onto the return stack.
1440 49 50       164 if ($mod eq '*') { # %*
    100          
    100          
    100          
1441 0 0       0 push(@retn, $MonthName[$field]) if ($type eq 'M');
1442 0 0       0 push(@retn, $WeekName[$field]) if ($type eq 'w');
1443             } elsif ($mod eq '~') { # %~
1444 2 100       27 push(@retn, $MonthAbbrev[$field]) if ($type eq 'M');
1445 2 100       10 push(@retn, $WeekAbbrev[$field]) if ($type eq 'w');
1446             } elsif ($mod eq '^') { # %^
1447 8         59 push(@retn, $field);
1448             } elsif ($mod eq '-') { # %-
1449 1         5 push(@retn, sprintf("%2d",$field));
1450             } else {
1451 38 50       78 $field=~s/^\d{2}// if ($type eq 'Y');
1452 38         948 push(@retn, sprintf("%02d",$field));
1453             }
1454             }
1455             } else {
1456             # Just a plain character.
1457 118         277 push(@retn, $char);
1458             }
1459             }
1460              
1461             # If the USGS midnight fix was applied to the date, then undo it.
1462 8 50       20 if ($usgs_midnight_fix_applied) {
1463 0         0 $self->_FixDate;
1464             }
1465            
1466 8         107 return join('', @retn);
1467             }
1468             # dprintf
1469              
1470             #----------------------------------------
1471             # NOTES: Returns a reference to a tags hash, or a string containing
1472             # NOTES: an error message. Used by dprintf() and dscanf().
1473             # ARG2 $format: format string (see dprintf())
1474             # ARG3 $string: string to parse with $format
1475             # this will confuse you, but that's ok, you shouldn't be using it anyhow.
1476             # ACCESS: method private
1477             sub extract_format {
1478 7     7 0 13 my $format = shift;
1479 7         8 my $string = shift;
1480 7         8 my($regex, $arg, %tags);
1481 0         0 my($mod,$type,$x,@ghost,$i);
1482              
1483             # xform the format string into a handy regex
1484             # remember what $ns go with what ()s
1485 7         12 $arg = 0;
1486 7         8 $regex = '';
1487 7         11 $format .= ' '; # add trailing space for luck
1488 7         19 while ($format) {
1489             # munge $format one character (or two) at a time
1490 88 100       422 if ($format =~ s/^\s+//) { # all whitespace is equal
    100          
    50          
1491 28         64 $regex .= '\s+';
1492             } elsif ($format =~ s/^%(.)(.)//) { # %MT
1493 42         69 $mod = $1;
1494 42         57 $type = $2;
1495 42 100 100     189 if ($1 eq '*' or $1 eq '~') { # it's %*M or %~M
1496             # it better be
1497 3 50       8 return "error in format: '%$1$2'?" unless $type eq 'M';
1498 3 100       22 if ($mod eq '~') {
1499 2         5 $regex .= '(\w{3})';
1500             } else {
1501 1         2 $regex .= '(\w+)';
1502             }
1503 3         10 $tags{'M'} = $arg++; # remember which () this is
1504             } else {
1505 39 100       102 unless ($mod=~/\d/) { # no width spec?
1506 33         102 $format = $type . $format; # put it back
1507 33         46 $type = $mod; # and move things to the right place
1508 33         49 $mod = '';
1509             }
1510 39 100       90 if ($type eq 'c') { # chunk of random (non-ws) crap
    100          
1511 6 50       23 $regex .= ($mod ? "[^\\s]{$mod}" : '[^\s]+?');
1512             } elsif ($type eq 'p') { # ignore any width spec for %p
1513 1         2 $regex .= '([a|p]m?)';
1514 1         4 $tags{'p'} = $arg++;
1515             } else { # anything else is digits
1516 32 100       56 $regex .= ($mod ? "(\\d{$mod})" : '(\d+)');
1517 32         106 $tags{$type} = $arg++;
1518             }
1519             }
1520             } elsif ($format =~ s/^(.)//) { # it's not %MT
1521             #($x = $1) =~ s/([\Q^$\{}*+?-./[]|()\E])/\\$1/;
1522             # replace when you get the chance to test for typos
1523 18         66 ($x = $1) =~ s/([\^\$\\\{\}\*\+\?\-\.\/\[\]\|\(\)])/\\$1/; # sob
1524 18         46 $regex .= $x; # just toss it into the regex
1525             } else {
1526 0         0 return "I'm baffled by your format";
1527             }
1528             }
1529             # apply our nice new regex
1530 7         40 $regex =~ s/(.*\)).*$/$1/; # trim crap off the end
1531 7         193 @ghost = ($string =~ /$regex/);
1532 7 50       22 return "format does not match string" unless @ghost;
1533             # fill hash with matched values
1534 7         25 foreach $i (keys %tags) {
1535 36         75 $tags{$i} = $ghost[$tags{$i}];
1536             }
1537             # seconds aren't necessarily given, but should be defined.
1538 7 100       26 $tags{'s'} = 0 unless exists($tags{'s'});
1539             # return
1540 7         29 \%tags;
1541             }
1542             # extract_format
1543              
1544             #----------------------------------------
1545             # NOTES: Takes a format string, and uses it to suck the date and
1546             # NOTES: time fields from the supplied string. Current setting is
1547             # NOTES: unchanged if dscanf() fails.
1548             #
1549             # NOTES: All format characters recognized by dprintf() are valid.
1550             # NOTES: Unless exact characters are supplied or format characters are
1551             # NOTES: concatenated, will separate on non-matching chars.
1552             # ARG2 $format: format string
1553             # ARG3 $string: string to get date and time from
1554             # RETVAL: undef on success, string containing error message on failure.
1555             # ACCESS: method
1556             # EXAMPLE: # this is the same as $dt->set_from_datetime(...)
1557             # EXAMPLE: $dt->dscanf("%^Y.%M.%D %h:%m:%s", "1998.03.25 20:25:23");
1558             #
1559             # EXAMPLE: if ($msg = $dt->dscanf("%~M", $input)) {
1560             # EXAMPLE: print "Must enter a three-letter month abbrev.\n";
1561             # EXAMPLE: }
1562             sub dscanf {
1563 7     7 1 109 my $self = shift;
1564 7         11 my $format = shift;
1565 7         11 my $string = shift;
1566 7         8 my(@form, @source, @ret);
1567 0         0 my($char, $mod, $type, $i, $x);
1568 0         0 my($arg, %tags, $regex, @ghost);
1569 0         0 my($msg); # is good for you
1570              
1571 7         15 $msg = extract_format($format, $string);
1572 7 50       20 return $msg unless (ref($msg)); # there was an error, got a string.
1573 7         9 %tags = %{$msg};
  7         47  
1574              
1575             # put things in the right place
1576 7 50       33 if (exists $tags{'U'}) {
    100          
    50          
1577 0         0 $self->set_localtime_from_epoch_time($tags{U});
1578             } elsif (exists $tags{'u'}) {
1579 1         6 $self->set_gmtime_from_epoch_time($tags{u});
1580             } elsif (exists $tags{'E'}) {
1581 0 0       0 return 'bad %E format' unless ($tags{'E'} =~ /^\d{14}$/);
1582 0         0 my @a = DatetimeToInternal($tags{'E'});
1583 0 0       0 if (@a) {
1584 0         0 @$self = @a;
1585             } else {
1586 0         0 return 'bad %E format';
1587             }
1588             } else {
1589             # check for sanity
1590 6 50 33     35 return 'bad seconds' unless ($tags{'s'} >= 0 and $tags{'s'} < 60);
1591 6 50 33     35 return 'bad minutes' unless ($tags{'m'} >= 0 and $tags{'m'} < 60);
1592             # check am/pm, if given
1593 6 50 66     31 if (exists($tags{p}) and $tags{'p'}=~/p/i) { # pm
    100          
1594 0 0       0 $tags{'h'}+=12 unless $tags{'h'}==12; # noon is 1200
1595             } elsif ($tags{'h'}==12) { # midnight?
1596 2 100       7 $tags{'h'}=0 if defined $tags{'p'};
1597             }
1598 6 50 33     37 return 'bad hours' unless ($tags{'h'} >= 0 and $tags{'h'} <= 24);
1599              
1600             # translate month names/abbrevs
1601 6 100       26 $tags{'M'} = $_month_name{lc $tags{'M'}} if ($tags{'M'}=~/[^\d]/);
1602 6 50 33     65 return 'bad month' unless ($tags{'M'} >= 1 and $tags{'M'} <= 12);
1603              
1604 6 50       14 if (defined $tags{'W'}) { # water year?
1605 0 0       0 carp "overriding %Y with %W" if defined $tags{'Y'};
1606 0         0 $tags{'Y'} = $tags{'W'};
1607 0 0       0 $tags{'Y'}-- if ($tags{'M'} < 9);
1608             }
1609 6 100       20 if ($tags{'Y'} =~ /^\d\d$/) {
1610             # we'll assume that no dates under AD 100 will be entered :)
1611 1         3 $tags{'Y'}+=1900;
1612             } else {
1613 5 50 33     29 return 'bad year' unless ($tags{'Y'}>=100 and $tags{'Y'}<10000);
1614             }
1615              
1616 6 50 33     28 return 'bad days' unless
1617             ($tags{'D'} >= 1
1618             and $tags{'D'} <= DaysInMonth($tags{'M'},$tags{'Y'}));
1619              
1620 6 50 33     233 return 'no (or incomplete) date given'
      33        
1621             unless (defined $tags{D} && defined $tags{M} && defined $tags{Y});
1622              
1623 6         12 $self->[YEAR] = $tags{'Y'};
1624 6         10 $self->[MONTH] = $tags{'M'};
1625 6         12 $self->[DAY] = $tags{'D'};
1626 6         8 $self->[HOUR] = $tags{'h'};
1627 6         9 $self->[MINUTE] = $tags{'m'};
1628 6         11 $self->[SECOND] = $tags{'s'};
1629 6         9 $self->[FRACTION] = 0;
1630             }
1631             # return
1632 7         30 $self->_FixDate;
1633 7         54 return;
1634             }
1635             # dscanf
1636              
1637              
1638              
1639             #----------------------------------------
1640             # NOTES: return the day of the week, 0..6 (sun..sat).
1641             # NOTES: SDN 0 is a saturday. Used by dprintf().
1642             # ACCESS: method private
1643             sub weekday {
1644 94     94 0 356 ($_[0]->serial_day + 1) % 7;
1645             }
1646             # weekday
1647              
1648             #----------------------------------------
1649             # NOTES: Increment by addition of seconds. Requires conversion to and
1650             # NOTES: from SDN time.
1651             # NOTES: Used by inc_* and overloaded add.
1652             # ARG2 $secs: seconds
1653             # ARG3 $unit: units (5,4,3,2) = (s,m,h,d) (negative increments are ok)
1654             # ACCESS: method private
1655             sub addSec {
1656 28     28 0 41 my $self = shift;
1657 28         46 my $increment = shift;
1658 28         156 my $unit = shift;
1659 28 100       62 $unit = SECOND unless defined $unit;
1660              
1661 28 100       59 if ($increment == 0) {
1662 4         9 return $self;
1663             }
1664              
1665             # If the units are year or month then we cannot add the proper number
1666             # of seconds.
1667 24 50       55 cluck "DateTime::Precise::addSec cannot add with unit=$unit" if ($unit
1668              
1669             # Take the increment and subtract from it any larger units.
1670 24         60 for (my $i=DAY; $i<$unit; $i++) {
1671 69         77 my $factor = 1;
1672 69         153 for (my $j=$i+1; $j<=$unit; $j++) {
1673 138         383 $factor *= $_full_unit[$j];
1674             }
1675 69         103 my $inc = $increment/$factor;
1676 69 100       389 if (my $int = int($inc)) {
1677 17         27 $self->[$i] += $int;
1678 17         45 $increment -= $int*$factor;
1679             }
1680             }
1681              
1682             # Chop up $increment into units and fractions of units.
1683 24         61 for (my $i=$unit; $i
1684 24         34 my $int = int($increment);
1685 24         41 $self->[$i] += $int;
1686 24         34 my $frac = $increment - $int;
1687 24         34 $increment = $frac*$_full_unit[$i+1];
1688 24 100       63 last if ($frac == 0);
1689             }
1690              
1691             # Anything remaining is added to the fractional part.
1692 24         35 $self->[FRACTION] += $increment;
1693 24         58 $self->_FixDate;
1694             }
1695             # addSec
1696              
1697             #----------------------------------------
1698             # NOTES: Increment (or decrement) date.
1699             # inc-decs by looping, unless you want more than 10 increments, at
1700             # which point it's faster to break the date down and use addSec()
1701             # (this should be re-checked)
1702             # NOTES: This is generally called by AUTOLOAD, not by the end user (qv.)
1703             # ARG2 $unit: unit to increment by
1704             # ARG3 $increment: (opt, defaults to 1) number of units to inc, may be neg.
1705             # ACCESS: method private
1706             # EXAMPLE: $dt->inc(2, 13); # add 13 days
1707             # EXAMPLE: $dt->inc_day(13); # does the same thing. see AUTOLOAD().
1708             sub inc {
1709 3     3 0 5 my $self = shift;
1710 3         5 my $unit = shift;
1711 3         4 my $increment = shift;
1712              
1713 3 50       8 if (defined $increment) {
1714 3 50       7 if ($increment == 0) {
1715 0         0 return $self;
1716             }
1717             } else {
1718 0         0 $increment = 1;
1719             }
1720              
1721 3 50       10 if (!defined $unit) {
1722 0         0 $unit = SECOND;
1723 0         0 cluck "DateTime::Precise::inc Cannot increment without your unit";
1724             }
1725              
1726             # Just increment the appropriate unit. Even if the increment is
1727             # very large, addSed combined with _FixDate can handle it. If we're
1728             # incrementing the year or month, then just add the integer part of
1729             # the increment to the appropriate unit. Otherwise, use the general
1730             # addSec, which can add fractions of units.
1731 3 50 33     18 if ($unit == YEAR or $unit == MONTH) {
1732 3         7 $self->[$unit] += int($increment);
1733             } else {
1734 0         0 $self->addSec($increment, $unit);
1735             }
1736 3         8 $self->_FixDate;
1737             }
1738             # inc
1739              
1740             #----------------------------------------
1741             # NOTES: floor and ceil stuff
1742             # NOTES: this is typically called through AUTOLOAD, not by hand.
1743             # ARG2 $unit: unit to floor/ceil/round
1744             # ARG3 $function: what to do: 0=floor, 1=ceil, 2=round
1745             # ACCESS: method private
1746             sub floorceil {
1747 1     1 0 3 my $self = shift;
1748 1         2 my $unit = shift;
1749 1 50       10 cluck "DateTime::Precise::floorceil cannot floor or ceiling without a unit" unless defined $unit;
1750 1         3 my $function = shift; # 1 for ceil, 0 for floor, 2 for round
1751             # inc unit, so we play with the appropriate parts
1752 1         1 $unit++;
1753             # if round, redo function appropriately
1754 1 50       5 if ($function==2) {
1755 1 50       5 $function = ($self->[$unit] > $_half_unit[$unit]) ? 1 : 0;
1756             }
1757             # everything wants to be floored.
1758 1         4 foreach my $i ($unit..FRACTION) {
1759 1         4 $self->[$i] = 0 + ($i < HOUR);
1760             }
1761             # if ceil, inc the next 'greater' (lesser) unit
1762 1 50       5 if ($function==1) {
1763 0         0 $self->[$unit-1]++;
1764             }
1765 1         4 $self->_FixDate;
1766             }
1767             # floorceil
1768              
1769             #----------------------------------------
1770             # NOTES: Find the difference between two DateTime::Precises.
1771             # NOTES: diff $a $b returns "$a-$b", in seconds.
1772             # NOTES: Used by overloaded subtract.
1773             # ARG2 $other: ref to another DateTime::Precise
1774             # RETVAL: seconds of difference between $self and $other
1775             # ACCESS: method
1776             # EXAMPLE: $secstolunch = $lunch->diff($dt); # how much longer!@?@!?
1777             # EXAMPLE: $secstolunch = $lunch - $dt; # same thing
1778             sub diff {
1779 57     57 0 185 my $self = shift;
1780 57         71 my $other = shift;
1781 57         72 my $neg = 0; # want to sub lesser from greater.
1782 57 100       142 if ($self < $other) {
1783             # Swap $self and $other, and set $neg to 1.
1784 1         2 my $tmp = $self;
1785 1         2 $self = $other;
1786 1         3 $other = $tmp;
1787 1         2 $neg = 1;
1788             }
1789 57         765 my @top = (DayToSDN(@$self), SecsSinceMidnight(@$self[HOUR..FRACTION]));
1790 57         319 my @bot = (DayToSDN(@$other), SecsSinceMidnight(@$other[HOUR..FRACTION]));
1791             # Carry the seconds if need be.
1792 57 100       508 if ($bot[1] > $top[1]) {
1793 2         9 $top[1] = DateTime::Math::fadd($top[1], Secs_per_day);
1794 2         7 $top[0]--;
1795             }
1796             # Subtract and return seconds.
1797 57         122 my $diff = ($top[0] - $bot[0])*Secs_per_day;
1798 57         161 $diff = DateTime::Math::fadd($diff, DateTime::Math::fsub($top[1], $bot[1]));
1799 57 100       453 if ($neg) {
1800 1         5 $diff = DateTime::Math::fneg($diff);
1801             }
1802 57         284 $diff;
1803             }
1804             # diff
1805              
1806             #----------------------------------------
1807             # NOTES: AUTOLOAD - handle 'func_unit' sub names.
1808             # NOTES: Allowable 'func' parts are in %_func_names
1809             # NOTES: Allowable 'unit' parts are in %_unit_names
1810             #
1811             # NOTES: Provides the following functions:
1812             # NOTES: inc dec floor ceil round
1813             # NOTES: For the following units:
1814             # NOTES: second (or sec) minute (or min) hour day month (or mo) year
1815             #
1816             # NOTES: inc adds the specified number of units to the date.
1817             # NOTES: dec subtracts the units from the date.
1818             # NOTES: floor sets the date to the largest whole given unit less than the
1819             # NOTES: current date setting.
1820             # NOTES: ceil sets the date to the smallest whole given unit greater
1821             # NOTES: than the current date setting.
1822             # NOTES: round rounds the date to the closest whole given unit.
1823             # ACCESS: method private
1824             sub AUTOLOAD {
1825 4     4   8 my $self = shift;
1826 4   33     14 my $type = ref($self) || cluck "DateTime::Precise::AUTOLOAD $self is not an object ($AUTOLOAD)";
1827 4         9 my $name = $AUTOLOAD;
1828 4         22 $name =~ s/.*://; # strip qualifier(s)
1829 4         9 my $func = $name;
1830 4         6 my($unit,$increment);
1831 4 50       36 return if $func eq 'DESTROY';
1832 4         16 $func =~ /(\w+)_(\w+)/;
1833 4         12 ($func, $unit) = ($1,$2);
1834 4 50 33     30 unless (exists($_func_name{$func}) && exists($_unit_name{$unit})) {
1835 0         0 cluck "DateTime::Precise::AUTOLOAD $name is not a valid function for object $type";
1836             }
1837 4 100       21 if ($func eq 'inc') {
    50          
    50          
    50          
    50          
1838 3         14 $self->inc($_unit_name{$unit}, @_);
1839             } elsif ($func eq 'dec') {
1840 0         0 $increment = shift;
1841 0 0       0 $increment = 1 unless defined $increment;
1842 0         0 $self->inc($_unit_name{$unit}, -$increment);
1843             } elsif ($func eq 'floor') {
1844 0         0 $self->floorceil($_unit_name{$unit}, 0);
1845             } elsif ($func eq 'ceil') {
1846 0         0 $self->floorceil($_unit_name{$unit}, 1);
1847             } elsif ($func eq 'round') {
1848 1         11 $self->floorceil($_unit_name{$unit}, 2);
1849             } else {
1850 0         0 cluck "DateTime::Precise::AUTOLOAD seems to have fallen out the bottom using $name";
1851             }
1852             }
1853             # AUTOLOAD
1854              
1855              
1856             #
1857             # overloaded operator functions
1858             #
1859              
1860             #----------------------------------------
1861             # NOTES: add some seconds to a date
1862             # ARG1 $a: DateTime::Precise
1863             # ARG2 $n: number of seconds to add to $a
1864             # ACCESS: private
1865             sub ovld_add {
1866 5     5 0 3301 my $a = shift;
1867 5         9 my $n = shift;
1868 5 50       43 cluck "DateTime::Precise::ovld_add $n is really really huge (did you try to add two dates?)"
1869             if ("$n" > "10000000000");
1870 5         25 $a->copy->addSec($n);
1871             }
1872             # ovld_add
1873              
1874             #----------------------------------------
1875             # NOTES: subtract some time from a date, or two dates from each other
1876             # ARG1 $a: DateTime::Precise
1877             # ARG2 $n: DateTime::Precise, or number of seconds to subtract.
1878             # ACCESS: private
1879             sub ovld_sub {
1880 58     58 0 252 my $a = shift; # this be a DateTime::Precise or a subclass
1881 58         453 my $n = shift; # this may be a DateTime::Precise
1882 58 100       239 if ("$n" > "10000000000") { # subing two DateTime::Precises
1883 57         171 return $a->diff($n);
1884             } else {
1885 1         5 return $a->copy->addSec(-$n);
1886             }
1887             }
1888             # ovld_sub
1889              
1890             1;
1891              
1892             __END__