package Date::Manip::DM6; # Copyright (c) 1995-2017 Sullivan Beck. All rights reserved. # This program is free software; you can redistribute it and/or modify it # under the same terms as Perl itself. ########################################################################### ########################################################################### our (@ISA,@EXPORT); require 5.010000; require Exporter; @ISA = qw(Exporter); @EXPORT = qw( DateManipVersion Date_Init ParseDate ParseDateString ParseDateDelta ParseDateFormat ParseRecur Date_IsHoliday Date_IsWorkDay Date_Cmp DateCalc UnixDate Delta_Format Date_GetPrev Date_GetNext Date_SetTime Date_SetDateField Events_List Date_NextWorkDay Date_PrevWorkDay Date_NearestWorkDay Date_DayOfWeek Date_SecsSince1970 Date_SecsSince1970GMT Date_DaysSince1BC Date_DayOfYear Date_NthDayOfYear Date_DaysInMonth Date_DaysInYear Date_WeekOfYear Date_LeapYear Date_DaySuffix Date_ConvTZ Date_TimeZone ); use strict; use integer; use warnings; our $VERSION; $VERSION='6.60'; ########################################################################### our ($dmb,$dmt,$date,$delta,$recur,$date2,$dateUT); use Date::Manip::Date; $dateUT = new Date::Manip::Date; $dateUT->config('setdate','now,Etc/GMT'); $date = new Date::Manip::Date; $date2 = $date->new_date(); $delta = $date->new_delta(); $recur = $date->new_recur(); $dmb = $date->base(); $dmt = $date->tz(); ######################################################################## ######################################################################## # THESE ARE THE MAIN ROUTINES ######################################################################## ######################################################################## sub DateManipVersion { my($flag) = @_; return $date->version($flag); } sub Date_Init { my(@args) = @_; my(@args2); foreach my $arg (@args) { if ($arg =~ /^(\S+)\s*=\s*(.*)$/) { push(@args2,$1,$2); } else { warn "ERROR: invalid Date_Init argument: $arg\n"; } } $date->config(@args2); return $date->err(); } sub ParseDateString { my($string,@opts) = @_; $string = '' if (! defined($string)); my $err = $date->parse($string,@opts); return '' if ($err); my $ret = $date->value('local'); return $ret; } sub ParseDateFormat { my($format,$string) = @_; $string = '' if (! defined($string)); my $err = $date->parse_format($format,$string); return '' if ($err); my $ret = $date->value('local'); return $ret; } sub ParseDate { my($arg,@opts) = @_; $arg = '' if (! defined($arg)); my $ref = ref($arg); my $list = 0; my @args; if (! $ref) { @args = ($arg); } elsif ($ref eq 'ARRAY') { @args = @$arg; $list = 1; } elsif ($ref eq 'SCALAR') { @args = ($$arg); } else { print "ERROR: Invalid arguments to ParseDate.\n"; return ''; } while (@args) { my $string = join(' ',@args); my $err = $date->parse($string,@opts); if (! $err) { splice(@$arg,0,$#args+1) if ($list); my $ret = $date->value('local'); return $ret; } pop(@args); } return ''; } sub ParseDateDelta { my(@a) = @_; if (@a < 1 || @a > 2) { print "ERROR: Invalid number of arguments to ParseDateDelta.\n"; return ''; } my($args,$mode) = @_; $args = '' if (! defined($args)); $mode = '' if (! $mode); $mode = lc($mode); if ($mode && ($mode ne 'exact' && $mode ne 'semi' && $mode ne 'approx')) { print "ERROR: Invalid arguments to ParseDateDelta.\n"; return ''; } my @args; my $ref = ref($args); my $list = 0; if (! $ref) { @args = ($args); } elsif ($ref eq 'ARRAY') { @args = @$args; $list = 1; } elsif ($ref eq 'SCALAR') { @args = ($$args); } else { print "ERROR: Invalid arguments to ParseDateDelta.\n"; return ''; } while (@args) { my $string = join(' ',@args); my $err = $delta->parse($string); if (! $err) { $delta->convert($mode) if ($mode); splice(@$args,0,$#args+1) if ($list); my $ret = $delta->value('local'); return $ret; } pop(@args); } return ''; } sub UnixDate { my($string,@in) = @_; my(@ret); my $err = $date->parse($string); return () if ($err); foreach my $in (@in) { push(@ret,$date->printf($in)); } if (! wantarray) { return join(" ",@ret); } return @ret; } sub Delta_Format { my($string,@args) = @_; my $err = $delta->parse($string); return () if ($err); my($mode,$dec,@in); if (! defined($args[0])) { $mode = 'exact'; @in = @args; shift(@in); } elsif (lc($args[0]) eq 'exact' || lc($args[0]) eq 'approx' || lc($args[0]) eq 'semi') { ($mode,$dec,@in) = (@args); $mode = lc($mode); } elsif ($args[0] =~ /^\d+$/) { ($mode,$dec,@in) = ('exact',@args); } else { $mode = 'exact'; @in = @args; } $dec = 0 if (! $dec); @in = _Delta_Format_old($mode,$dec,@in); my @ret = (); foreach my $in (@in) { push(@ret,$delta->printf($in)); } if (! wantarray) { return join(" ",@ret); } return @ret; } sub _Delta_Format_old { my($mode,$dec,@in) = @_; my(@ret); my $business = $delta->type('business'); foreach my $in (@in) { my $out = ''; # This will look for old formats (%Xd, %Xh, %Xt) and turn them # into the new format: %XYZ while ($in) { if ($in =~ s/^([^%]+)//) { $out .= $1; } elsif ($in =~ /^%[yMwdhms][yMwdhms][yMwdhms]/) { # It's one of the new formats so don't modify it. $in =~ s/^%//; $out .= '%'; } elsif ($in =~ s/^%([yMwdhms])([dht])//) { my($field,$scope) = ($1,$2); $out .= '%'; if ($scope eq 'd') { if ($mode eq 'approx') { $out .= ".${dec}${field}${field}s"; } elsif ($field eq 'y' || $field eq 'M') { $out .= ".${dec}${field}${field}M"; } elsif ($mode eq 'semi') { $out .= ".${dec}${field}${field}s"; } elsif ($field eq 'w' && $business) { $out .= ".${dec}www"; } elsif (($field eq 'w' || $field eq 'd') && ! $business) { $out .= ".${dec}${field}${field}d"; } else { $out .= ".${dec}${field}${field}s"; } } elsif ($scope eq 'h') { if ($mode eq 'approx') { $out .= ".${dec}${field}y${field}"; } elsif ($field eq 'y' || $field eq 'M') { $out .= ".${dec}${field}y${field}"; } elsif ($mode eq 'semi') { $out .= ".${dec}${field}w${field}"; } elsif ($field eq 'w') { $out .= ".${dec}www"; } elsif ($field eq 'd' && ! $business) { $out .= ".${dec}dwd"; } elsif ($business) { $out .= ".${dec}${field}d${field}"; } else { $out .= ".${dec}${field}h${field}"; } } elsif ($scope eq 't') { if ($mode eq 'approx') { $out .= ".${dec}${field}ys"; } elsif ($field eq 'y' || $field eq 'M') { $out .= ".${dec}${field}yM"; } elsif ($mode eq 'semi') { $out .= ".${dec}${field}ws"; } elsif ($field eq 'w' && $business) { $out .= ".${dec}www"; } elsif (($field eq 'w' || $field eq 'd') && ! $business) { $out .= ".${dec}${field}wd"; } elsif ($business) { $out .= ".${dec}${field}ds"; } else { $out .= ".${dec}${field}hs"; } } } else { # It's one of the new formats so don't modify it. $in =~ s/^%//; $out .= '%'; } } push(@ret,$out); } return @ret; } sub DateCalc { my($d1,$d2,@args) = @_; # Handle \$err arg my($ref,$errref); if (@args && ref($args[0])) { $errref = shift(@args); $ref = 1; } else { $ref = 0; } # Parse $d1 and $d2 my ($obj1,$obj2,$err,$usemode); $usemode = 1; $obj1 = $date->new_date(); $err = $obj1->parse($d1,'nodelta'); if ($err) { $obj1 = $date->new_delta(); $err = $obj1->parse($d1); if ($err) { $$errref = 1 if ($ref); return ''; } $usemode = 0; } $obj2 = $date->new_date(); $err = $obj2->parse($d2,'nodelta'); if ($err) { $obj2 = $date->new_delta(); $err = $obj2->parse($d2); if ($err) { $$errref = 2 if ($ref); return ''; } $usemode = 0; } # Handle $mode my($mode); if (@args) { $mode = shift(@args); } if (@args) { $$errref = 3 if ($ref); return ''; } # Apply the $mode to any deltas if (defined($mode)) { if (ref($obj1) eq 'Date::Manip::Delta') { if ($$obj1{'data'}{'gotmode'}) { if ($mode == 2 || $mode == 3) { if (! $obj1->type('business')) { $$errref = 3 if ($ref); return ''; } } else { if ($obj1->type('business')) { $$errref = 3 if ($ref); return ''; } } } else { if ($mode == 2 || $mode == 3) { $obj1->set('mode','business'); } else { $obj1->set('mode','normal'); } } } if (ref($obj2) eq 'Date::Manip::Delta') { if ($$obj2{'data'}{'gotmode'}) { if ($mode == 2 || $mode == 3) { if (! $obj2->type('business')) { $$errref = 3 if ($ref); return ''; } } else { if ($obj2->type('business')) { $$errref = 3 if ($ref); return ''; } } } else { if ($mode ==2 || $mode == 3) { $obj2->set('mode','business'); } else { $obj2->set('mode','normal'); } } } } # Do the calculation my $obj3; if ($usemode) { $mode = 'exact' if (! $mode); my %tmp = ('0' => 'exact', '1' => 'approx', '2' => 'bapprox', '3' => 'business', 'exact' => 'exact', 'semi' => 'semi', 'approx' => 'approx', 'business'=> 'business', 'bsemi' => 'bsemi', 'bapprox' => 'bapprox', ); if (exists $tmp{$mode}) { $mode = $tmp{$mode}; } else { $$errref = 3 if ($ref); return ''; } $obj3 = $obj1->calc($obj2,$mode); } else { $obj3 = $obj1->calc($obj2); } my $ret = $obj3->value(); return $ret; } sub Date_GetPrev { my($string,$dow,$curr,@time) = @_; my $err = $date->parse($string); return '' if ($err); if (defined($dow)) { $dow = lc($dow); if (exists $$dmb{'data'}{'wordmatch'}{'day_char'}{$dow}) { $dow = $$dmb{'data'}{'wordmatch'}{'day_char'}{$dow}; } elsif (exists $$dmb{'data'}{'wordmatch'}{'day_abb'}{$dow}) { $dow = $$dmb{'data'}{'wordmatch'}{'day_abb'}{$dow}; } elsif (exists $$dmb{'data'}{'wordmatch'}{'day_name'}{$dow}) { $dow = $$dmb{'data'}{'wordmatch'}{'day_name'}{$dow}; } } if ($#time == 0) { @time = @{ $dmb->split('hms',$time[0]) }; } if (@time) { while ($#time < 2) { push(@time,0); } $date->prev($dow,$curr,\@time); } else { $date->prev($dow,$curr); } my $ret = $date->value(); return $ret; } sub Date_GetNext { my($string,$dow,$curr,@time) = @_; my $err = $date->parse($string); return '' if ($err); if (defined($dow)) { $dow = lc($dow); if (exists $$dmb{'data'}{'wordmatch'}{'day_char'}{$dow}) { $dow = $$dmb{'data'}{'wordmatch'}{'day_char'}{$dow}; } elsif (exists $$dmb{'data'}{'wordmatch'}{'day_abb'}{$dow}) { $dow = $$dmb{'data'}{'wordmatch'}{'day_abb'}{$dow}; } elsif (exists $$dmb{'data'}{'wordmatch'}{'day_name'}{$dow}) { $dow = $$dmb{'data'}{'wordmatch'}{'day_name'}{$dow}; } } if ($#time == 0) { @time = @{ $dmb->split('hms',$time[0]) }; } if (@time) { while ($#time < 2) { push(@time,0); } $date->next($dow,$curr,\@time); } else { $date->next($dow,$curr); } my $ret = $date->value(); return $ret; } sub Date_SetTime { my($string,@time) = @_; my $err = $date->parse($string); return '' if ($err); if ($#time == 0) { @time = @{ $dmb->split('hms',$time[0]) }; } while ($#time < 2) { push(@time,0); } $date->set('time',\@time); my $val = $date->value(); return $val; } sub Date_SetDateField { my($string,$field,$val) = @_; my $err = $date->parse($string); return '' if ($err); $date->set($field,$val); my $ret = $date->value(); return $ret; } sub Date_NextWorkDay { my($string,$n,$checktime) = @_; my $err = $date->parse($string); return '' if ($err); $date->next_business_day($n,$checktime); my $val = $date->value(); return $val; } sub Date_PrevWorkDay { my($string,$n,$checktime) = @_; my $err = $date->parse($string); return '' if ($err); $date->prev_business_day($n,$checktime); my $val = $date->value(); return $val; } sub Date_NearestWorkDay { my($string,$tomorrowfirst) = @_; my $err = $date->parse($string); return '' if ($err); $date->nearest_business_day($tomorrowfirst); my $val = $date->value(); return $val; } sub ParseRecur { my($string,@args) = @_; if ($#args == 3) { my($base,$d0,$d1,$flags) = @args; @args = (); push(@args,$flags) if ($flags); push(@args,$base,$d0,$d1); } my $err = $recur->parse($string,@args); return '' if ($err); if (wantarray) { my @dates = $recur->dates(); my @ret; foreach my $d (@dates) { my $val = $d->value(); push(@ret,$val); } return @ret; } my @int = @{ $$recur{'data'}{'interval'} }; my @rtime = @{ $$recur{'data'}{'rtime'} }; my @flags = @{ $$recur{'data'}{'flags'} }; my $start = $$recur{'data'}{'start'}; my $end = $$recur{'data'}{'end'}; my $base = $$recur{'data'}{'base'}; my $r; if (@int) { $r = join(':',@int); } if (@rtime) { my @rt; foreach my $rt (@rtime) { push(@rt,join(",",@$rt)); } $r .= '*' . join(':',@rt); } $r .= '*' . join(",",@flags); my $val = (defined($base) ? $base->value() : ''); $r .= "*$val"; $val = (defined($start) ? $start->value() : ''); $r .= "*$val"; $val = (defined($end) ? $end->value() : ''); $r .= "*$val"; return $r; } sub Events_List { my($datestr,@args) = @_; # First argument is always a date my $err = $date->parse($datestr); return [] if ($err); # Second argument is absent, a date, or 0. my @list; my $flag = 0; my ($date0,$date1); if (! @args) { # absent @list = $date->list_events('dates'); } else { # a date or 0 my $arg = shift(@args); $flag = shift(@args) if (@args); if (@args) { warn "ERROR: unknown argument list\n"; return []; } if (! $arg) { my($y,$m,$d) = $date->value(); $date2->set('date',[$y,$m,$d,23,59,59]); @list = $date->list_events(0, 'dates'); } else { $err = $date2->parse($arg); if ($err) { warn "ERROR: invalid argument: $arg\n"; return []; } @list = $date->list_events($date2, 'dates'); } } # Handle the flag if (! $flag) { my @ret = (); foreach my $e (@list) { my($d,@n) = @$e; my $v = $d->value(); push(@ret,$v,[@n]); } return \@ret; } push(@list,[$date2]); my %ret; if ($flag==1) { while ($#list > 0) { my($d0,@n) = @{ shift(@list) }; my $d1 = $list[0]->[0]; my $delta = $d0->calc($d1); foreach $flag (@n) { $flag = '' if (! defined($flag)); if (exists $ret{$flag}) { $ret{$flag} = $ret{$flag}->calc($delta); } else { $ret{$flag} = $delta; } } } } elsif ($flag==2) { while ($#list > 0) { my($d0,@n) = @{ shift(@list) }; my $d1 = $list[0]->[0]; my $delta = $d0->calc($d1); $flag = join("+",sort(@n)); if (exists $ret{$flag}) { $ret{$flag} = $ret{$flag}->calc($delta); } else { $ret{$flag} = $delta; } } } else { warn "ERROR: Invalid flag $flag\n"; return []; } foreach my $flag (keys %ret) { $ret{$flag} = $ret{$flag}->value(); } return \%ret; } ######################################################################## # ADDITIONAL ROUTINES ######################################################################## sub Date_DayOfWeek { my($m,$d,$y) = @_; return $dmb->day_of_week([$y,$m,$d]); } sub Date_SecsSince1970 { my($m,$d,$y,$h,$mn,$s) = @_; return $dmb->secs_since_1970([$y,$m,$d,$h,$mn,$s]); } sub Date_SecsSince1970GMT { my($m,$d,$y,$h,$mn,$s) = @_; $date->set('date',[$y,$m,$d,$h,$mn,$s]); return $date->secs_since_1970_GMT(); } sub Date_DaysSince1BC { my($m,$d,$y) = @_; return $dmb->days_since_1BC([$y,$m,$d]); } sub Date_DayOfYear { my($m,$d,$y) = @_; return $dmb->day_of_year([$y,$m,$d]); } sub Date_NthDayOfYear { my($y,$n) = @_; my @ret = @{ $dmb->day_of_year($y,$n) }; push(@ret,0,0,0) if ($#ret == 2); return @ret; } sub Date_DaysInMonth { my($m,$y) = @_; return $dmb->days_in_month($y,$m); } sub Date_DaysInYear { my($y) = @_; return $dmb->days_in_year($y); } sub Date_WeekOfYear { my($m,$d,$y,$first) = @_; my($yy,$ww) = $dmb->_week_of_year($first,[$y,$m,$d]); return 0 if ($yy<$y); return 53 if ($yy>$y); return $ww; } sub Date_LeapYear { my($y) = @_; return $dmb->leapyear($y); } sub Date_DaySuffix { my($d) = @_; return $$dmb{'data'}{'wordlist'}{'nth_dom'}[$d-1]; } sub Date_TimeZone { my($ret) = $dmb->_now('tz'); return $ret; } sub Date_ConvTZ { my($str,$from,$to) = @_; $from = $dmb->_now('tz') if (! $from); $to = $dmb->_now('tz') if (! $to); # Parse the date (ignoring timezone information): my $err = $dateUT->parse($str); return '' if ($err); my $d = [ $dateUT->value() ]; return '' if (! $d); # Get the timezone for $from. First, we'll assume that # the date matches exactly (so if the timezone is passed # in as an abbreviation, we'll try to get the timezone # that fits the date/abbrev combination). If we can't, # we'll just assume that the timezone is more generic # and try it without the date. my $tmp; $tmp = $dmt->zone($from,$d); if (! $tmp) { $tmp = $dmt->zone($from); return '' if (! $tmp); } $from = $tmp; $tmp = $dmt->zone($to,$d); if (! $tmp) { $tmp = $dmt->zone($to); return '' if (! $tmp); } $to = $tmp; ($err,$d) = $dmt->convert($d,$from,$to); return '' if ($err); return $dmb->join('date',$d); } sub Date_IsWorkDay { my($str,$checktime) = @_; my $err = $date->parse($str); return '' if ($err); return $date->is_business_day($checktime); } sub Date_IsHoliday { my($str) = @_; my $err = $date->parse($str); return undef if ($err); if (wantarray) { my @ret = $date->holiday(); return @ret; } else { my $ret = $date->holiday(); return $ret; } } sub Date_Cmp { my($str1,$str2) = @_; my $err = $date->parse($str1); return undef if ($err); $err = $date2->parse($str2); return undef if ($err); return $date->cmp($date2); } 1; # Local Variables: # mode: cperl # indent-tabs-mode: nil # cperl-indent-level: 3 # cperl-continued-statement-offset: 2 # cperl-continued-brace-offset: 0 # cperl-brace-offset: 0 # cperl-brace-imaginary-offset: 0 # cperl-label-offset: 0 # End: