File Coverage

blib/lib/Class/Date.pm
Criterion Covered Total %
statement 243 338 71.8
branch 73 144 50.6
condition 66 118 55.9
subroutine 63 90 70.0
pod 11 56 19.6
total 456 746 61.1


line stmt bran cond sub pod time code
1             package Class::Date;
2             # $Id: Date.pm,v 8dbcc6b6035d 2008/11/23 00:41:11 dlux $
3              
4 7     7   168631 use 5.006;
  7         29  
  7         297  
5              
6 7     7   41 use strict;
  7         14  
  7         372  
7 7         1330 use vars qw(
8             @EXPORT_OK %EXPORT_TAGS @ISA
9             $DATE_FORMAT $DST_ADJUST $MONTH_BORDER_ADJUST $RANGE_CHECK
10             @NEW_FROM_SCALAR @ERROR_MESSAGES $WARNINGS
11             $DEFAULT_TIMEZONE $LOCAL_TIMEZONE $GMT_TIMEZONE
12             $NOTZ_TIMEZONE $RESTORE_TZ
13 7     7   37 );
  7         19  
14 7     7   38 use Carp;
  7         13  
  7         700  
15              
16 7     7   62 use Exporter;
  7         17  
  7         283  
17 7     7   36 use DynaLoader;
  7         11  
  7         192  
18 7     7   18150 use Time::Local;
  7         29795  
  7         525  
19 7     7   4998 use Class::Date::Const;
  7         14  
  7         1886  
20 7     7   46 use Scalar::Util qw(blessed);
  7         13  
  7         741  
21              
22 7     7   4142 use Class::Date::Rel;
  7         23  
  7         271  
23 7     7   6804 use Class::Date::Invalid;
  7         15  
  7         1785  
24              
25             BEGIN {
26 7 50   7   39 $WARNINGS = 1 if !defined $WARNINGS;
27 7 50       40 if ($] > 5.006) {
28 7         21 *timelocal = *Time::Local::timelocal_nocheck;
29 7         18 *timegm = *Time::Local::timegm_nocheck;
30             } else {
31 0         0 *timelocal = *Time::Local::timelocal;
32 0         0 *timegm = *Time::Local::timegm;
33             }
34              
35 7         156 @ISA=qw(DynaLoader Exporter);
36 7         24 %EXPORT_TAGS = ( errors => $Class::Date::Const::EXPORT_TAGS{errors});
37 7         32 @EXPORT_OK = (qw( date localdate gmdate now @ERROR_MESSAGES),
38 7         12 @{$EXPORT_TAGS{errors}});
39              
40 7         13 our $VERSION = '1.1.15';
41 7         13 eval { Class::Date->bootstrap($VERSION); };
  7         3827  
42 7 50       2688 if ($@) {
43 0 0       0 warn "Cannot find the XS part of Class::Date, \n".
44             " using strftime, tzset and tzname from POSIX module.\n"
45             if $WARNINGS;
46 0         0 require POSIX;
47 0         0 *strftime_xs = *POSIX::strftime;
48 0         0 *tzset_xs = *POSIX::tzset;
49 0         0 *tzname_xs = *POSIX::tzname;
50             }
51             }
52              
53             $GMT_TIMEZONE = 'GMT';
54             $DST_ADJUST = 1;
55             $MONTH_BORDER_ADJUST = 0;
56             $RANGE_CHECK = 0;
57             $RESTORE_TZ = 1;
58             $DATE_FORMAT="%Y-%m-%d %H:%M:%S";
59              
60 1106     1106   1613 sub _set_tz { my ($tz) = @_;
61 1106         2068 my $lasttz = $ENV{TZ};
62 1106 100 100     4990 if (!defined $tz || $tz eq $NOTZ_TIMEZONE) {
63             # warn "_set_tz: deleting TZ\n";
64 827         3612 delete $ENV{TZ};
65 827 50       2371 Env::C::unsetenv('TZ') if exists $INC{"Env/C.pm"};
66             } else {
67             # warn "_set_tz: setting TZ to $tz\n";
68 279         1671 $ENV{TZ} = $tz;
69 279 50       945 Env::C::setenv('TZ', $tz) if exists $INC{"Env/C.pm"};
70             }
71 1106         65314 tzset_xs();
72 1106         2163 return $lasttz;
73             }
74              
75 546     546   1028 sub _set_temp_tz { my ($tz, $sub) = @_;
76 546         1010 my $lasttz = _set_tz($tz);
77 546         1031 my $retval = eval { $sub->(); };
  546         4524  
78 546 50       9647 _set_tz($lasttz) if $RESTORE_TZ;
79 546 50       1094 die $@ if $@;
80 546         2229 return $retval;
81             }
82              
83             tzset_xs();
84             $LOCAL_TIMEZONE = $DEFAULT_TIMEZONE = local_timezone();
85             {
86             my $last_tz = _set_tz(undef);
87             $NOTZ_TIMEZONE = local_timezone();
88             _set_tz($last_tz);
89             }
90             # warn "LOCAL: $LOCAL_TIMEZONE, NOTZ: $NOTZ_TIMEZONE\n";
91              
92             # this method is used to determine what is the package name of the relative
93             # time class. It is used at the operators. You only need to redefine it if
94             # you want to derive both Class::Date and Class::Date::Rel.
95             # Look at the Class::Date::Rel::ClassDate also.
96 7     7   58 use constant ClassDateRel => "Class::Date::Rel";
  7         18  
  7         560  
97 7     7   59 use constant ClassDateInvalid => "Class::Date::Invalid";
  7         33  
  7         608  
98              
99             use overload
100 7         44 '""' => "string",
101             '-' => "subtract",
102             '+' => "add",
103             '<=>' => "compare",
104             'cmp' => "compare",
105 7     7   37 fallback => 1;
  7         14  
106              
107 36     36 1 1344 sub date ($;$) { my ($date,$tz)=@_;
108 36         144 return __PACKAGE__ -> new($date,$tz);
109             }
110              
111 1     1 0 16 sub now () { date(time); }
112              
113 8   33 8 0 3722 sub localdate ($) { date($_[0] || time, $LOCAL_TIMEZONE) }
114              
115 20   33 20 0 9588 sub gmdate ($) { date($_[0] || time, $GMT_TIMEZONE) }
116              
117             sub import {
118 7     7   73 my $package=shift;
119 7         15 my @exported;
120 7         28 foreach my $symbol (@_) {
121 12 50       55 if ($symbol eq '-DateParse') {
    50          
122 0 0       0 if (!$Class::Date::DateParse++) {
123 0 0       0 if ( eval { require Date::Parse } ) {
  0         0  
124 0         0 push @NEW_FROM_SCALAR,\&new_from_scalar_date_parse;
125             } else {
126 0 0       0 warn "Date::Parse is not available, although it is requested by Class::Date\n"
127             if $WARNINGS;
128             }
129             }
130             } elsif ($symbol eq '-EnvC') {
131 0 0       0 if (!$Class::Date::EnvC++) {
132 0 0       0 if ( !eval { require Env::C } ) {
  0         0  
133 0 0       0 warn "Env::C is not available, although it is requested by Class::Date\n"
134             if $WARNINGS;
135             }
136             }
137             } else {
138 12         37 push @exported,$symbol;
139             }
140             };
141 7         30366 $package->export_to_level(1,$package,@exported);
142             }
143              
144 134     134 1 4115 sub new { my ($proto,$time,$tz)=@_;
145 134   66     507 my $class = ref($proto) || $proto;
146            
147             # if the prototype is an object, not a class, then the timezone will be
148             # the same
149 134 100 100     1423 $tz = $proto->[c_tz]
      100        
      66        
150             if defined($time) && !defined $tz && blessed($proto) && $proto->isa( __PACKAGE__ );
151              
152             # Default timezone is used if the timezone cannot be determined otherwise
153 134 100       305 $tz = $DEFAULT_TIMEZONE if !defined $tz;
154              
155 134 100       266 return $proto->new_invalid(E_UNDEFINED,"") if !defined $time;
156 129 100 66     1531 if (blessed($time) && $time->isa( __PACKAGE__ ) ) {
    50 33        
    100          
    50          
    100          
157 2         9 return $class->new_copy($time,$tz);
158             } elsif (blessed($time) && $time->isa('Class::Date::Rel')) {
159 0         0 return $class->new_from_scalar($time,$tz);
160             } elsif (ref($time) eq 'ARRAY') {
161 13         48 return $class->new_from_array($time,$tz);
162             } elsif (ref($time) eq 'SCALAR') {
163 0         0 return $class->new_from_scalar($$time,$tz);
164             } elsif (ref($time) eq 'HASH') {
165 8         31 return $class->new_from_hash($time,$tz);
166             } else {
167 106         290 return $class->new_from_scalar($time,$tz);
168             }
169             }
170              
171 38     38 0 63 sub new_copy { my ($s,$input,$tz)=@_;
172 38         177 my $new_object=[ @$input ];
173             # we don't mind $isgmt!
174 38   66     191 return bless($new_object, ref($s) || $s);
175             }
176              
177 123     123 0 193 sub new_from_array { my ($s,$time,$tz) = @_;
178 123         364 my ($y,$m,$d,$hh,$mm,$ss,$dst) = @$time;
179 123   100     1364 my $obj= [
      100        
      100        
      100        
      100        
      100        
180             ($y||2000)-1900, ($m||1)-1, $d||1,
181             $hh||0 , $mm||0 , $ss||0
182             ];
183 123         309 $obj->[c_tz]=$tz;
184 123   66     504 bless $obj, ref($s) || $s;
185 123         286 $obj->_recalc_from_struct;
186 123         562 return $obj;
187             }
188              
189 8     8 0 16 sub new_from_hash { my ($s,$time,$tz) = @_;
190 8         21 $s->new_from_array(_array_from_hash($time),$tz);
191             }
192              
193 29     29   47 sub _array_from_hash { my ($val)=@_;
194             [
195 29 100 66     568 $val->{year} || ($val->{_year} ? $val->{_year} + 1900 : 0 ),
    100 66        
      66        
196             $val->{mon} || $val->{month} || ( $val->{_mon} ? $val->{_mon} + 1 : 0 ),
197             $val->{day} || $val->{mday} || $val->{day_of_month},
198             $val->{hour},
199             exists $val->{min} ? $val->{min} : $val->{minute},
200             exists $val->{sec} ? $val->{sec} : $val->{second},
201             ];
202             }
203              
204 124     124 0 202 sub new_from_scalar { my ($s,$time,$tz)=@_;
205 124         349 for (my $i=0;$i<@NEW_FROM_SCALAR;$i++) {
206 124         300 my $ret=$NEW_FROM_SCALAR[$i]->($s,$time,$tz);
207 124 100       814 return $ret if defined $ret;
208             }
209 1         5 return $s->new_invalid(E_UNPARSABLE,$time);
210             }
211              
212 124     124 0 178 sub new_from_scalar_internal { my ($s,$time,$tz) = @_;
213 124 50       248 return undef if !$time;
214              
215 124 100       1830 if ($time eq 'now') {
    100          
    100          
    100          
216             # now string
217 1   33     7 my $obj=bless [], ref($s) || $s;
218 1         3 $obj->[c_epoch]=time;
219 1         3 $obj->[c_tz]=$tz;
220 1         4 $obj->_recalc_from_epoch;
221 1         7 return $obj;
222             } elsif ($time =~ /^\s*(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)\d*\s*$/) {
223             # mysql timestamp
224 2         13 my ($y,$m,$d,$hh,$mm,$ss)=($1,$2,$3,$4,$5,$6);
225 2         11 return $s->new_from_array([$y,$m,$d,$hh,$mm,$ss],$tz);
226             } elsif ($time =~ /^\s*( \-? \d+ (\.\d+ )? )\s*$/x) {
227             # epoch secs
228 22   66     110 my $obj=bless [], ref($s) || $s;
229 22         210 $obj->[c_epoch]=$1;
230 22         48 $obj->[c_tz]=$tz;
231 22         57 $obj->_recalc_from_epoch;
232 22         250 return $obj;
233             } elsif ($time =~ m{ ^\s* ( \d{0,4} ) - ( \d\d? ) - ( \d\d? )
234             ( (?: T|\s+ ) ( \d\d? ) : ( \d\d? ) ( : ( \d\d? ) (\.\d+)?)? )? }x) {
235 98         739 my ($y,$m,$d,$hh,$mm,$ss)=($1,$2,$3,$5,$6,$8);
236             # ISO(-like) date
237 98         481 return $s->new_from_array([$y,$m,$d,$hh,$mm,$ss],$tz);
238             } else {
239 1         3 return undef;
240             }
241             }
242              
243             push @NEW_FROM_SCALAR,\&new_from_scalar_internal;
244              
245 0     0 0 0 sub new_from_scalar_date_parse { my ($s,$date,$tz)=@_;
246 0         0 my $lt;
247 0         0 my ($ss, $mm, $hh, $day, $month, $year, $zone) =
248             Date::Parse::strptime($date);
249 0 0       0 $zone = $tz if !defined $zone;
250 0 0       0 if ($zone eq $GMT_TIMEZONE) {
251             _set_temp_tz($zone, sub {
252 0 0 0 0   0 $ss = ($lt ||= [ gmtime ])->[0] if !defined $ss;
253 0 0 0     0 $mm = ($lt ||= [ gmtime ])->[1] if !defined $mm;
254 0 0 0     0 $hh = ($lt ||= [ gmtime ])->[2] if !defined $hh;
255 0 0 0     0 $day = ($lt ||= [ gmtime ])->[3] if !defined $day;
256 0 0 0     0 $month = ($lt ||= [ gmtime ])->[4] if !defined $month;
257 0 0 0     0 $year = ($lt ||= [ gmtime ])->[5] if !defined $year;
258 0         0 });
259             } else {
260             _set_temp_tz($zone, sub {
261 0 0 0 0   0 $ss = ($lt ||= [ localtime ])->[0] if !defined $ss;
262 0 0 0     0 $mm = ($lt ||= [ localtime ])->[1] if !defined $mm;
263 0 0 0     0 $hh = ($lt ||= [ localtime ])->[2] if !defined $hh;
264 0 0 0     0 $day = ($lt ||= [ localtime ])->[3] if !defined $day;
265 0 0 0     0 $month = ($lt ||= [ localtime ])->[4] if !defined $month;
266 0 0 0     0 $year = ($lt ||= [ localtime ])->[5] if !defined $year;
267 0         0 });
268             }
269 0         0 return $s->new_from_array( [$year+1900, $month+1, $day,
270             $hh, $mm, $ss], $zone);
271             }
272              
273 332     332   578 sub _check_sum { my ($s) = @_;
274 332   100     533 my $sum=0; $sum += $_ || 0 foreach @{$s}[c_year .. c_sec];
  332         487  
  332         4369  
275 332         1086 return $sum;
276             }
277              
278             sub _recalc_from_struct {
279 166     166   206 my $s = shift;
280 166         800 $s->[c_isdst] = -1;
281 166         213 $s->[c_wday] = 0;
282 166         328 $s->[c_yday] = 0;
283 166         181 $s->[c_epoch] = 0; # these are required to suppress warinngs;
284 166         333 eval {
285 166     0   1397 local $SIG{__WARN__} = sub { };
  0         0  
286 166 100       558 my $timecalc = $s->[c_tz] eq $GMT_TIMEZONE ?
287             \&timegm : \&timelocal;
288             _set_temp_tz($s->[c_tz],
289             sub {
290 166         926 $s->[c_epoch] = $timecalc->(
291 166     166   230 @{$s}[c_sec,c_min,c_hour,c_day,c_mon],
292             $s->[c_year] + 1900);
293             }
294 166         768 );
295             };
296 166 50       837 return $s->_set_invalid(E_INVALID,$@) if $@;
297 166         577 my $sum = $s->_check_sum;
298 166         525 $s->_recalc_from_epoch;
299 166 100       853 @$s[c_error,c_errmsg] = (($s->_check_sum != $sum ? E_RANGE : 0), "");
300 166 100 100     458 return $s->_set_invalid(E_RANGE,"") if $RANGE_CHECK && $s->[c_error];
301 163         218 return 1;
302             }
303              
304 191     191   463 sub _recalc_from_epoch { my ($s) = @_;
305             _set_temp_tz($s->[c_tz],
306             sub {
307 191 100   191   2479 @{$s}[c_year..c_isdst] =
  191         1027  
308             ($s->[c_tz] eq $GMT_TIMEZONE ?
309             gmtime($s->[c_epoch]) : localtime($s->[c_epoch]))
310             [5,4,3,2,1,0,6,7,8];
311             }
312             )
313 191         1690 }
314              
315             my $SETHASH = {
316             year => sub { shift->[c_year] = shift() - 1900 },
317             _year => sub { shift->[c_year] = shift },
318             month => sub { shift->[c_mon] = shift() - 1 },
319             _month => sub { shift->[c_mon] = shift },
320             day => sub { shift->[c_day] = shift },
321             hour => sub { shift->[c_hour] = shift },
322             min => sub { shift->[c_min] = shift },
323             sec => sub { shift->[c_sec] = shift },
324             tz => sub { shift->[c_tz] = shift },
325             };
326             $SETHASH->{mon} = $SETHASH->{month};
327             $SETHASH->{_mon} = $SETHASH->{_month};
328             $SETHASH->{mday} = $SETHASH->{day_of_month} = $SETHASH->{day};
329             $SETHASH->{minute} = $SETHASH->{min};
330             $SETHASH->{second} = $SETHASH->{sec};
331              
332             sub clone {
333 23     23 1 53 my $s = shift;
334 23         64 my $new_date = $s->new_copy($s);
335 23         77 while (@_) {
336 24         39 my $key = shift;
337 24         36 my $value = shift;
338 24         93 $SETHASH->{$key}->($value,$new_date);
339             };
340 23         59 $new_date->_recalc_from_struct;
341 23         183 return $new_date;
342             }
343              
344             *set = *clone; # compatibility
345              
346 8     8 1 52 sub year { shift->[c_year] +1900 }
347 0     0   0 sub _year { shift->[c_year] }
348 0     0 0 0 sub yr { shift->[c_year] % 100 }
349 6     6 0 36 sub mon { shift->[c_mon] +1 }
350             *month = *mon;
351 0     0   0 sub _mon { shift->[c_mon] }
352             *_month = *_mon;
353 11     11 1 168 sub day { shift->[c_day] }
354             *day_of_month= *mday = *day;
355 4     4 1 21 sub hour { shift->[c_hour] }
356 3     3 1 14 sub min { shift->[c_min] }
357             *minute = *min;
358 3     3 1 14 sub sec { shift->[c_sec] }
359             *second = *sec;
360 0     0 0 0 sub wday { shift->[c_wday] + 1 }
361 0     0   0 sub _wday { shift->[c_wday] }
362             *day_of_week = *_wday;
363 0     0 0 0 sub yday { shift->[c_yday] }
364             *day_of_year = *yday;
365 0     0 0 0 sub isdst { shift->[c_isdst] }
366             *daylight_savings = \&isdst;
367 184     184 0 862 sub epoch { shift->[c_epoch] }
368             *as_sec = *epoch; # for compatibility
369 4     4 0 1016 sub tz { shift->[c_tz] }
370 2     2 0 5 sub tzdst { shift->strftime("%Z") }
371              
372 0     0 0 0 sub monname { shift->strftime('%B') }
373             *monthname = *monname;
374 0     0 0 0 sub wdayname { shift->strftime('%A') }
375             *day_of_weekname= *wdayname;
376              
377 0     0 0 0 sub error { shift->[c_error] }
378 0     0 0 0 sub errmsg { my ($s) = @_;
379 0         0 sprintf $ERROR_MESSAGES[ $s->[c_error] ]."\n", $s->[c_errmsg]
380             }
381             *errstr = *errmsg;
382              
383 6     6 0 10 sub new_invalid { my ($proto,$error,$errmsg) = @_;
384 6   66     36 bless([],ref($proto) || $proto)->_set_invalid($error,$errmsg);
385             }
386              
387 9     9   15 sub _set_invalid { my ($s,$error,$errmsg) = @_;
388 9         40 bless($s,$s->ClassDateInvalid);
389 9         105 @$s = ();
390 9         26 @$s[ci_error, ci_errmsg] = ($error,$errmsg);
391 9         25 return $s;
392             }
393              
394 0     0 0 0 sub ampm { my ($s) = @_;
395 0 0       0 return $s->[c_hour] < 12 ? "AM" : "PM";
396             }
397              
398 0     0 0 0 sub meridiam { my ($s) = @_;
399 0         0 my $hour = $s->[c_hour] % 12;
400 0 0       0 if( $hour == 0 ) { $hour = 12; }
  0         0  
401 0         0 sprintf('%02d:%02d %s', $hour, $s->[c_min], $s->ampm);
402             }
403              
404 0     0 0 0 sub hms { sprintf('%02d:%02d:%02d', @{ shift() }[c_hour,c_min,c_sec]) }
  0         0  
405              
406 0     0 0 0 sub ymd { my ($s)=@_;
407 0         0 sprintf('%04d/%02d/%02d', $s->year, $s->mon, $s->[c_day])
408             }
409              
410 0     0 0 0 sub mdy { my ($s)=@_;
411 0         0 sprintf('%02d/%02d/%04d', $s->mon, $s->[c_day], $s->year)
412             }
413              
414 0     0 0 0 sub dmy { my ($s)=@_;
415 0         0 sprintf('%02d/%02d/%04d', $s->[c_day], $s->mon, $s->year)
416             }
417              
418 3     3 1 844 sub array { my ($s)=@_;
419 3         8 my @return=@{$s}[c_year .. c_sec];
  3         13  
420 3         6 $return[c_year]+=1900;
421 3         5 $return[c_mon]+=1;
422 3         13 @return;
423             }
424              
425 1     1 0 4 sub aref { return [ shift()->array ] }
426             *as_array = *aref;
427              
428             sub struct {
429 189     189 0 2493 return ( @{ shift() }
  189         1915  
430             [c_sec,c_min,c_hour,c_day,c_mon,c_year,c_wday,c_yday,c_isdst] )
431             }
432              
433 0     0 0 0 sub sref { return [ shift()->struct ] }
434              
435 0     0 0 0 sub href { my ($s)=@_;
436 0         0 my @struct=$s->struct;
437 0         0 my $h={};
438 0         0 foreach my $key (qw(sec min hour day _month _year wday yday isdst)) {
439 0         0 $h->{$key}=shift @struct;
440             }
441 0         0 $h->{epoch} = $s->[c_epoch];
442 0         0 $h->{year} = 1900 + $h->{_year};
443 0         0 $h->{month} = $h->{_month} + 1;
444 0         0 $h->{minute} = $h->{min};
445 0         0 return $h;
446             }
447              
448             *as_hash=*href;
449              
450 0     0 1 0 sub hash { return %{ shift->href } }
  0         0  
451              
452             # Thanks to Tony Olekshy for this algorithm
453             # ripped from Time::Object by Matt Sergeant
454 0     0 0 0 sub tzoffset { my ($s)=@_;
455 0         0 my $epoch = $s->[c_epoch];
456             my $j = sub { # Tweaked Julian day number algorithm.
457 0     0   0 my ($s,$n,$h,$d,$m,$y) = @_; $m += 1; $y += 1900;
  0         0  
  0         0  
458             # Standard Julian day number algorithm without constant.
459 0 0       0 my $y1 = $m > 2 ? $y : $y - 1;
460 0 0       0 my $m1 = $m > 2 ? $m + 1 : $m + 13;
461 0         0 my $day = int(365.25 * $y1) + int(30.6001 * $m1) + $d;
462             # Modify to include hours/mins/secs in floating portion.
463 0         0 return $day + ($h + ($n + $s / 60) / 60) / 24;
464 0         0 };
465             # Compute floating offset in hours.
466             my $delta = _set_temp_tz($s->[c_tz],
467             sub {
468 0     0   0 24 * (&$j(localtime $epoch) - &$j(gmtime $epoch));
469             }
470 0         0 );
471             # Return value in seconds rounded to nearest minute.
472 0 0       0 return int($delta * 60 + ($delta >= 0 ? 0.5 : -0.5)) * 60;
473             }
474              
475 1     1 0 477 sub month_begin { my ($s) = @_;
476 1         4 my $aref = $s->aref;
477 1         3 $aref->[2] = 1;
478 1         4 return $s->new($aref);
479             }
480              
481 5     5 0 10 sub month_end { my ($s)=@_;
482 5         16 return $s->clone(day => 1)+'1M'-'1D';
483             }
484              
485             sub days_in_month {
486 4     4 0 15 shift->month_end->mday;
487             }
488              
489 2     2 0 3 sub is_leap_year { my ($s) = @_;
490 2         3 my $new_date;
491 2 50       2 eval {
492 2         5 $new_date = $s->new([$s->year, 2, 29],$s->tz);
493             } or return 0;
494 2         7 return $new_date->day == 29;
495             }
496              
497 189     189 0 249 sub strftime { my ($s,$format)=@_;
498 189   50     482 $format ||= "%a, %d %b %Y %H:%M:%S %Z";
499 189     189   880 my $fmt = _set_temp_tz($s->[c_tz], sub { strftime_xs($format,$s->struct) } );
  189         475  
500 189         1513 return $fmt;
501             }
502              
503 187     187 0 289 sub string { my ($s) = @_;
504 187         418 $s->strftime($DATE_FORMAT);
505             }
506              
507 15     15 0 41 sub subtract { my ($s,$rhs)=@_;
508 15 100 100     165 if (blessed($rhs) && $rhs->isa( __PACKAGE__ )) {
    100 66        
    50          
509 2         5 my $dst_adjust = 0;
510 2 50       11 $dst_adjust = 60*60*( $s->[c_isdst]-$rhs->[c_isdst] ) if $DST_ADJUST;
511 2         20 return $s->ClassDateRel->new($s->[c_epoch]-$rhs->[c_epoch]+$dst_adjust);
512             } elsif (blessed($rhs) && $rhs->isa("Class::Date::Rel")) {
513 4         18 return $s->add(-$rhs);
514             } elsif ($rhs) {
515 9         50 return $s->add($s->ClassDateRel->new($rhs)->neg);
516             } else {
517 0         0 return $s;
518             }
519             }
520              
521 31     31 0 925 sub add { my ($s,$rhs)=@_;
522 31         41 local $RANGE_CHECK;
523 31 100 66     277 $rhs=$s->ClassDateRel->new($rhs) unless blessed($rhs) && $rhs->isa('Class::Date::Rel');
524            
525 31 50 33     225 return $s unless blessed($rhs) && $rhs->isa('Class::Date::Rel');
526              
527             # adding seconds
528 31 100       188 my $retval= $rhs->[cs_sec] ?
529             $s->new_from_scalar($s->[c_epoch]+$rhs->[cs_sec],$s->[c_tz]) :
530             $s->new_copy($s);
531              
532             # adjust DST if necessary
533 31 50 33     179 if ( $DST_ADJUST && (my $dstdiff=$retval->[c_isdst]-$s->[c_isdst])) {
534 0         0 $retval->[c_epoch] -= $dstdiff*60*60;
535 0         0 $retval->_recalc_from_epoch;
536             }
537            
538             # adding months
539 31 100       72 if ($rhs->[cs_mon]) {
540 20         33 $retval->[c_mon]+=$rhs->[cs_mon];
541 20 100       117 my $year_diff= $retval->[c_mon]>0 ? # instead of POSIX::floor
542             int ($retval->[c_mon]/12) :
543             int (($retval->[c_mon]-11)/12);
544 20         33 $retval->[c_mon] -= 12*$year_diff;
545 20         29 my $expected_month = $retval->[c_mon];
546 20         26 $retval->[c_year] += $year_diff;
547 20         52 $retval->_recalc_from_struct;
548              
549             # adjust month border if necessary
550 20 50 66     66 if ($MONTH_BORDER_ADJUST && $retval && $expected_month != $retval->[c_mon]) {
      66        
551 2         5 $retval->[c_epoch] -= $retval->[c_day]*60*60*24;
552 2         6 $retval->_recalc_from_epoch;
553             }
554             }
555            
556             # sigh! We have finished!
557 31         177 return $retval;
558             }
559              
560 2     2 0 5 sub trunc { my ($s)=@_;
561 2         7 return $s->new_from_array([$s->year,$s->month,$s->day,0,0,0],$s->[c_tz]);
562             }
563              
564             *truncate = *trunc;
565              
566             sub get_epochs {
567 92     92 0 251 my ($lhs,$rhs,$reverse)=@_;
568 92 100 66     406 unless (blessed($rhs) && $rhs->isa( __PACKAGE__ )) {
569 85         209 $rhs = $lhs->new($rhs);
570             }
571 92 100       277 my $repoch= $rhs ? $rhs->epoch : 0;
572 92 100       217 return $repoch, $lhs->epoch if $reverse;
573 84         162 return $lhs->epoch, $repoch;
574             }
575              
576             sub compare {
577 92     92 0 13709 my ($lhs, $rhs) = get_epochs(@_);
578 92         1569 return $lhs <=> $rhs;
579             }
580              
581             sub local_timezone {
582 14     14 0 79 return (tzname_xs())[0];
583             }
584              
585 1     1 1 3 sub to_tz { my ($s, $tz) = @_;
586 1         4 return $s->new($s->epoch, $tz);
587             }
588             1;
589