| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258 |
- package Time::ParseDate;
- require 5.000;
- use Carp;
- use Time::Timezone;
- use Time::JulianDay;
- require Exporter;
- @ISA = qw(Exporter);
- @EXPORT = qw(parsedate);
- @EXPORT_OK = qw(pd_raw %mtable %umult %wdays);
- use strict;
- #use diagnostics;
- # constants
- use vars qw(%mtable %umult %wdays $VERSION);
- $VERSION = 2013.1113;
- # globals
- use vars qw($debug);
- # dynamically-scoped
- use vars qw($parse);
- my %mtable;
- my %umult;
- my %wdays;
- my $y2k;
- CONFIG: {
- %mtable = qw(
- Jan 1 Jan. 1 January 1
- Feb 2 Feb. 2 February 2
- Mar 3 Mar. 3 March 3
- Apr 4 Apr. 4 April 4
- May 5
- Jun 6 Jun. 6 June 6
- Jul 7 Jul. 7 July 7
- Aug 8 Aug. 8 August 8
- Sep 9 Sep. 9 September 9 Sept 9
- Oct 10 Oct. 10 October 10
- Nov 11 Nov. 11 November 11
- Dec 12 Dec. 12 December 12 );
- %umult = qw(
- sec 1 second 1
- min 60 minute 60
- hour 3600
- day 86400
- week 604800
- fortnight 1209600);
- %wdays = qw(
- sun 0 sunday 0
- mon 1 monday 1
- tue 2 tuesday 2
- wed 3 wednesday 3
- thu 4 thursday 4
- fri 5 friday 5
- sat 6 saturday 6
- );
- $y2k = 946684800; # turn of the century
- }
- my $break = qr{(?:\s+|\Z|\b(?![-:.,/]\d))};
- sub parsedate
- {
- my ($t, %options) = @_;
- my ($y, $m, $d); # year, month - 1..12, day
- my ($H, $M, $S); # hour, minute, second
- my $tz; # timezone
- my $tzo; # timezone offset
- my ($rd, $rs); # relative days, relative seconds
- my $rel; # time&|date is relative
- my $isspec;
- my $now = defined($options{NOW}) ? $options{NOW} : time;
- my $passes = 0;
- my $uk = defined($options{UK}) ? $options{UK} : 0;
- local $parse = ''; # will be dynamically scoped.
- if ($t =~ s#^ ([ \d]\d)
- / (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)
- / (\d\d\d\d)
- : (\d\d)
- : (\d\d)
- : (\d\d)
- (?:
- [ ]
- ([-+] \d\d\d\d)
- (?: \("?(?:(?:[A-Z]{1,4}[TCW56])|IDLE)\))?
- )?
- $break
- ##xi) { #"emacs
- # [ \d]/Mon/yyyy:hh:mm:ss [-+]\d\d\d\d
- # This is the format for www server logging.
- ($d, $m, $y, $H, $M, $S, $tzo) = ($1, $mtable{"\u\L$2"}, $3, $4, $5, $6, $7 ? &mkoff($7) : ($tzo || undef));
- $parse .= " ".__LINE__ if $debug;
- } elsif ($t =~ s#^(\d\d)/(\d\d)/(\d\d)\.(\d\d)\:(\d\d)($break)##) {
- # yy/mm/dd.hh:mm
- # I support this format because it's used by wbak/rbak
- # on Apollo Domain OS. Silly, but historical.
- ($y, $m, $d, $H, $M, $S) = ($1, $2, $3, $4, $5, 0);
- $parse .= " ".__LINE__ if $debug;
- } else {
- while(1) {
- if (! defined $m and ! defined $rd and ! defined $y
- and ! ($passes == 0 and $options{'TIMEFIRST'}))
- {
- # no month defined.
- if (&parse_date_only(\$t, \$y, \$m, \$d, $uk)) {
- $parse .= " ".__LINE__ if $debug;
- next;
- }
- }
- if (! defined $H and ! defined $rs) {
- if (&parse_time_only(\$t, \$H, \$M, \$S,
- \$tz, %options))
- {
- $parse .= " ".__LINE__ if $debug;
- next;
- }
- }
- next if $passes == 0 and $options{'TIMEFIRST'};
- if (! defined $y) {
- if (&parse_year_only(\$t, \$y, $now, %options)) {
- $parse .= " ".__LINE__ if $debug;
- next;
- }
- }
- if (! defined $tz and ! defined $tzo and ! defined $rs
- and (defined $m or defined $H))
- {
- if (&parse_tz_only(\$t, \$tz, \$tzo)) {
- $parse .= " ".__LINE__ if $debug;
- next;
- }
- }
- if (! defined $H and ! defined $rs) {
- if (&parse_time_offset(\$t, \$rs, %options)) {
- $rel = 1;
- $parse .= " ".__LINE__ if $debug;
- next;
- }
- }
- if (! defined $m and ! defined $rd and ! defined $y) {
- if (&parse_date_offset(\$t, $now, \$y,
- \$m, \$d, \$rd, \$rs, %options))
- {
- $rel = 1;
- $parse .= " ".__LINE__ if $debug;
- next;
- }
- }
- if (defined $M or defined $rd) {
- if ($t =~ s/^\s*(?:at|\@|\+)($break)//x) {
- $rel = 1;
- $parse .= " ".__LINE__ if $debug;
- next;
- }
- }
- last;
- } continue {
- $passes++;
- &debug_display($tz, $tzo, $H, $M, $S, $m, $d, $y, $rs, $rd, $rel, $passes, $parse, $t) if $debug;
- }
- if ($passes == 0) {
- print "nothing matched\n" if $debug;
- return (undef, "no match on time/date")
- if wantarray();
- return undef;
- }
- }
- &debug_display($tz, $tzo, $H, $M, $S, $m, $d, $y, $rs, $rd, $rel, $passes, $parse, $t) if $debug;
- $t =~ s/^\s+//;
- if ($t ne '') {
- # we didn't manage to eat the string
- print "NOT WHOLE\n" if $debug;
- if ($options{WHOLE}) {
- return (undef, "characters left over after parse")
- if wantarray();
- return undef
- }
- }
- # define a date if there isn't one already
- if (! defined $y and ! defined $m and ! defined $rd) {
- print "no date defined, trying to find one." if $debug;
- if (defined $rs or defined $H) {
- # we do have a time.
- if ($options{DATE_REQUIRED}) {
- return (undef, "no date specified")
- if wantarray();
- return undef;
- }
- if (defined $rs) {
- print "simple offset: $rs\n" if $debug;
- my $rv = $now + $rs;
- return ($rv, $t) if wantarray();
- return $rv;
- }
- $rd = 0;
- } else {
- print "no time either!\n" if $debug;
- return (undef, "no time specified")
- if wantarray();
- return undef;
- }
- }
- if ($options{TIME_REQUIRED} && ! defined($rs)
- && ! defined($H) && ! defined($rd))
- {
- return (undef, "no time found")
- if wantarray();
- return undef;
- }
- my $secs;
- my $jd;
- if (defined $rd) {
- if (defined $rs || ! (defined($H) || defined($M) || defined($S))) {
- print "fully relative\n" if $debug;
- my ($j, $in, $it);
- my $definedrs = defined($rs) ? $rs : 0;
- my ($isdst_now, $isdst_then);
- my $r = $now + $rd * 86400 + $definedrs;
- #
- # It's possible that there was a timezone shift
- # during the time specified. If so, keep the
- # hours the "same".
- #
- $isdst_now = (localtime($r))[8];
- $isdst_then = (localtime($now))[8];
- if (($isdst_now == $isdst_then) || $options{GMT})
- {
- return ($r, $t) if wantarray();
- return $r
- }
-
- print "localtime changed DST during time period!\n" if $debug;
- }
- print "relative date\n" if $debug;
- $jd = $options{GMT}
- ? gm_julian_day($now)
- : local_julian_day($now);
- print "jd($now) = $jd\n" if $debug;
- $jd += $rd;
- } else {
- unless (defined $y) {
- if ($options{PREFER_PAST}) {
- my ($day, $mon011);
- ($day, $mon011, $y) = (&righttime($now))[3,4,5];
- print "calc year -past $day-$d $mon011-$m $y\n" if $debug;
- $y -= 1 if ($mon011+1 < $m) ||
- (($mon011+1 == $m) && ($day < $d));
- } elsif ($options{PREFER_FUTURE}) {
- print "calc year -future\n" if $debug;
- my ($day, $mon011);
- ($day, $mon011, $y) = (&righttime($now))[3,4,5];
- $y += 1 if ($mon011 >= $m) ||
- (($mon011+1 == $m) && ($day > $d));
- } else {
- print "calc year -this\n" if $debug;
- $y = (localtime($now))[5];
- }
- $y += 1900;
- }
- $y = expand_two_digit_year($y, $now, %options)
- if $y < 100;
- if ($options{VALIDATE}) {
- require Time::DaysInMonth;
- my $dim = Time::DaysInMonth::days_in($y, $m);
- if ($y < 1000 or $m < 1 or $d < 1
- or $y > 9999 or $m > 12 or $d > $dim)
- {
- return (undef, "illegal YMD: $y, $m, $d")
- if wantarray();
- return undef;
- }
- }
- $jd = julian_day($y, $m, $d);
- print "jd($y, $m, $d) = $jd\n" if $debug;
- }
- # put time into HMS
- if (! defined($H)) {
- if (defined($rd) || defined($rs)) {
- ($S, $M, $H) = &righttime($now, %options);
- print "HMS set to $H $M $S\n" if $debug;
- }
- }
- my $carry;
- print "before ", (defined($rs) ? "$rs" : ""),
- " $jd $H $M $S\n"
- if $debug;
- #
- # add in relative seconds. Do it this way because we want to
- # preserve the localtime across DST changes.
- #
- $S = 0 unless $S; # -w
- $M = 0 unless $M; # -w
- $H = 0 unless $H; # -w
- if ($options{VALIDATE} and
- ($S < 0 or $M < 0 or $H < 0 or $S > 59 or $M > 59 or $H > 23))
- {
- return (undef, "illegal HMS: $H, $M, $S") if wantarray();
- return undef;
- }
- $S += $rs if defined $rs;
- $carry = int($S / 60) - ($S < 0 && $S % 60 && 1);
- $S -= $carry * 60;
- $M += $carry;
- $carry = int($M / 60) - ($M < 0 && $M % 60 && 1);
- $M %= 60;
- $H += $carry;
- $carry = int($H / 24) - ($H < 0 && $H % 24 && 1);
- $H %= 24;
- $jd += $carry;
- print "after rs $jd $H $M $S\n" if $debug;
- $secs = jd_secondsgm($jd, $H, $M, $S);
- print "jd_secondsgm($jd, $H, $M, $S) = $secs\n" if $debug;
- #
- # If we see something link 3pm CST then and we want to end
- # up with a GMT seconds, then we convert the 3pm to GMT and
- # subtract in the offset for CST. We subtract because we
- # are converting from CST to GMT.
- #
- my $tzadj;
- if ($tz) {
- $tzadj = tz_offset($tz, $secs);
- if (defined $tzadj) {
- print "adjusting secs for $tz: $tzadj\n" if $debug;
- $tzadj = tz_offset($tz, $secs-$tzadj);
- $secs -= $tzadj;
- } else {
- print "unknown timezone: $tz\n" if $debug;
- undef $secs;
- undef $t;
- }
- } elsif (defined $tzo) {
- print "adjusting time for offset: $tzo\n" if $debug;
- $secs -= $tzo;
- } else {
- unless ($options{GMT}) {
- if ($options{ZONE}) {
- $tzadj = tz_offset($options{ZONE}, $secs) || 0;
- $tzadj = tz_offset($options{ZONE}, $secs-$tzadj);
- unless (defined($tzadj)) {
- return (undef, "could not convert '$options{ZONE}' to time offset")
- if wantarray();
- return undef;
- }
- print "adjusting secs for $options{ZONE}: $tzadj\n" if $debug;
- $secs -= $tzadj;
- } else {
- $tzadj = tz_local_offset($secs);
- print "adjusting secs for local offset: $tzadj\n" if $debug;
- #
- # Just in case we are very close to a time
- # change...
- #
- $tzadj = tz_local_offset($secs-$tzadj);
- $secs -= $tzadj;
- }
- }
- }
- print "returning $secs.\n" if $debug;
- return ($secs, $t) if wantarray();
- return $secs;
- }
- sub mkoff
- {
- my($offset) = @_;
- if (defined $offset and $offset =~ s#^([-+])(\d\d):?(\d\d)$##) {
- return ($1 eq '+' ?
- 3600 * $2 + 60 * $3
- : -3600 * $2 + -60 * $3 );
- }
- return undef;
- }
- sub parse_tz_only
- {
- my($tr, $tz, $tzo) = @_;
- $$tr =~ s#^\s+##;
- my $o;
- if ($$tr =~ s#^
- ([-+]\d\d:?\d\d)
- \s+
- \(
- "?
- (?:
- (?:
- [A-Z]{1,4}[TCW56]
- )
- |
- IDLE
- )
- \)
- $break
- ##x) { #"emacs
- $$tzo = &mkoff($1);
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- } elsif ($$tr =~ s#^GMT\s*([-+]\d{1,2})($break)##x) {
- $o = $1;
- if ($o < 24 and $o !~ /^0/) {
- # probably hours.
- printf "adjusted at %d. ($o 00)\n", __LINE__ if $debug;
- $o = "${o}00";
- }
- $o =~ s/\b(\d\d\d)/0$1/;
- $$tzo = &mkoff($o);
- printf "matched at %d. ($$tzo, $o)\n", __LINE__ if $debug;
- return 1;
- } elsif ($$tr =~ s#^(?:GMT\s*)?([-+]\d\d:?\d\d)($break)##x) {
- $o = $1;
- $$tzo = &mkoff($o);
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- } elsif ($$tr =~ s#^"?((?:[A-Z]{1,4}[TCW56])|IDLE)$break##x) { #"
- $$tz = $1;
- $$tz .= " DST"
- if $$tz eq 'MET' && $$tr =~ s#^DST$break##x;
- printf "matched at %d: '$$tz'.\n", __LINE__ if $debug;
- return 1;
- }
- return 0;
- }
- sub parse_date_only
- {
- my ($tr, $yr, $mr, $dr, $uk) = @_;
- $$tr =~ s#^\s+##;
- if ($$tr =~ s#^(\d\d\d\d)([-./])(\d\d?)\2(\d\d?)(T|$break)##) {
- # yyyy/mm/dd
- ($$yr, $$mr, $$dr) = ($1, $3, $4);
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- } elsif ($$tr =~ s#^(\d\d?)([-./])(\d\d?)\2(\d\d\d\d?)($break)##) {
- # mm/dd/yyyy - is this safe? No.
- # -- or dd/mm/yyyy! If $1>12, then it's unambiguous.
- # Otherwise check option UK for UK style date.
- if ($uk || $1>12) {
- ($$yr, $$mr, $$dr) = ($4, $3, $1);
- } else {
- ($$yr, $$mr, $$dr) = ($4, $1, $3);
- }
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- } elsif ($$tr =~ s#^(\d\d\d\d)/(\d\d?)$break##x) {
- # yyyy/mm
- ($$yr, $$mr, $$dr) = ($1, $2, 1);
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- } elsif ($$tr =~ s#^(?xi)
- (?:
- (?:Mon|Monday|Tue|Tuesday|Wed|Wednesday|
- Thu|Thursday|Fri|Friday|
- Sat|Saturday|Sun|Sunday),?
- \s+
- )?
- (\d\d?)
- (\s+ | - | \. | /)
- (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\.?
- (?:
- \2
- (\d\d (?:\d\d)? )
- )?
- $break
- ##) {
- # [Dow,] dd Mon [yy[yy]]
- ($$yr, $$mr, $$dr) = ($4, $mtable{"\u\L$3"}, $1);
- printf "%d: %s - %s - %s\n", __LINE__, $1, $2, $3 if $debug;
- print "y undef\n" if ($debug && ! defined($$yr));
- return 1;
- } elsif ($$tr =~ s#^(?xi)
- (?:
- (?:Mon|Monday|Tue|Tuesday|Wed|Wednesday|
- Thu|Thursday|Fri|Friday|
- Sat|Saturday|Sun|Sunday),?
- \s+
- )?
- (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\.?
- ((\s)+ | - | \. | /)
-
- (\d\d?)
- ,?
- (?:
- (?: \2|\3+)
- (\d\d (?: \d\d)?)
- )?
- $break
- ##) {
- # [Dow,] Mon dd [yyyy]
- # [Dow,] Mon d, [yy]
- ($$yr, $$mr, $$dr) = ($5, $mtable{"\u\L$1"}, $4);
- printf "%d: %s - %s - %s\n", __LINE__, $1, $2, $4 if $debug;
- print "y undef\n" if ($debug && ! defined($$yr));
- return 1;
- } elsif ($$tr =~ s#^(?xi)
- (January|Jan\.?|February|Feb\.?|March|Mar\.?|April|Apr\.?|May|
- June|Jun\.?|July|Jul\.?|August|Aug\.?|September|Sep\.?|
- October|Oct\.?|November|Nov\.?|December|Dec\.?)
- \s+
- (\d+)
- (?:st|nd|rd|th)?
- \,?
- (?:
- \s+
- (?:
- (\d\d\d\d)
- |(?:\' (\d\d))
- )
- )?
- $break
- ##) {
- # Month day{st,nd,rd,th}, 'yy
- # Month day{st,nd,rd,th}, year
- # Month day, year
- # Mon. day, year
- ($$yr, $$mr, $$dr) = ($3 || $4, $mtable{"\u\L$1"}, $2);
- printf "%d: %s - %s - %s - %s\n", __LINE__, $1, $2, $3, $4 if $debug;
- print "y undef\n" if ($debug && ! defined($$yr));
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- } elsif ($$tr =~ s#^(\d\d?)([-/.])(\d\d?)\2(\d\d?)($break)##x) {
- if ($1 > 31 || (!$uk && $1 > 12 && $4 < 32)) {
- # yy/mm/dd
- ($$yr, $$mr, $$dr) = ($1, $3, $4);
- } elsif ($1 > 12 || $uk) {
- # dd/mm/yy
- ($$yr, $$mr, $$dr) = ($4, $3, $1);
- } else {
- # mm/dd/yy
- ($$yr, $$mr, $$dr) = ($4, $1, $3);
- }
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- } elsif ($$tr =~ s#^(\d\d?)/(\d\d?)($break)##x) {
- if ($1 > 31 || (!$uk && $1 > 12)) {
- # yy/mm
- ($$yr, $$mr, $$dr) = ($1, $2, 1);
- } elsif ($2 > 31 || ($uk && $2 > 12)) {
- # mm/yy
- ($$yr, $$mr, $$dr) = ($2, $1, 1);
- } elsif ($1 > 12 || $uk) {
- # dd/mm
- ($$mr, $$dr) = ($2, $1);
- } else {
- # mm/dd
- ($$mr, $$dr) = ($1, $2);
- }
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- } elsif ($$tr =~ s#^(\d\d)(\d\d)(\d\d)($break)##x) {
- if ($1 > 31 || (!$uk && $1 > 12)) {
- # YYMMDD
- ($$yr, $$mr, $$dr) = ($1, $2, $3);
- } elsif ($1 > 12 || $uk) {
- # DDMMYY
- ($$yr, $$mr, $$dr) = ($3, $2, $1);
- } else {
- # MMDDYY
- ($$yr, $$mr, $$dr) = ($3, $1, $2);
- }
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- } elsif ($$tr =~ s#^(?xi)
- (\d{1,2})
- (\s+ | - | \. | /)
- (January|Jan\.?|February|Feb\.?|March|Mar\.?|April|Apr\.?|May|
- June|Jun\.?|July|Jul\.?|August|Aug\.?|September|Sep\.?|
- October|Oct\.?|November|Nov\.?|December|Dec\.?)
- (?:
- \2
- (
- \d\d
- (?:\d\d)?
- )
- )
- $break
- ##) {
- # dd Month [yr]
- ($$yr, $$mr, $$dr) = ($4, $mtable{"\u\L$3"}, $1);
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- } elsif ($$tr =~ s#^(?xi)
- (\d+)
- (?:st|nd|rd|th)?
- \s+
- (January|Jan\.?|February|Feb\.?|March|Mar\.?|April|Apr\.?|May|
- June|Jun\.?|July|Jul\.?|August|Aug\.?|September|Sep\.?|
- October|Oct\.?|November|Nov\.?|December|Dec\.?)
- (?:
- \,?
- \s+
- (\d\d\d\d)
- )?
- $break
- ##) {
- # day{st,nd,rd,th}, Month year
- ($$yr, $$mr, $$dr) = ($3, $mtable{"\u\L$2"}, $1);
- printf "%d: %s - %s - %s - %s\n", __LINE__, $1, $2, $3, $4 if $debug;
- print "y undef\n" if ($debug && ! defined($$yr));
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- }
- return 0;
- }
- sub parse_time_only
- {
- my ($tr, $hr, $mr, $sr, $tzr, %options) = @_;
- $$tr =~ s#^\s+##;
- if ($$tr =~ s!^(?x)
- (?:
- (?:
- ([012]\d) (?# $1)
- (?:
- ([0-5]\d) (?# $2)
- (?:
- ([0-5]\d) (?# $3)
- )?
- )
- \s*
- ([apAP][mM])? (?# $4)
- ) | (?:
- (\d{1,2}) (?# $5)
- (?:
- \:
- (\d\d) (?# $6)
- (?:
- \:
- (\d\d) (?# $7)
- (
- (?# don't barf on database sub-second timings)
- [:.,]
- \d+
- )? (?# $8)
- )?
- )
- \s*
- ([apAP][mM])? (?# $9)
- ) | (?:
- (\d{1,2}) (?# $10)
- ([apAP][mM]) (?# ${11})
- )
- )
- (?:
- \s+
- "?
- ( (?# ${12})
- (?: [A-Z]{1,4}[TCW56] )
- |
- IDLE
- )
- )?
- $break
- !!) { #"emacs
- # HH[[:]MM[:SS]]meridian [zone]
- my $ampm;
- $$hr = $1 || $5 || $10 || 0; # 10 is undef, but 5 is defined..
- $$mr = $2 || $6 || 0;
- $$sr = $3 || $7 || 0;
- if (defined($8) && exists($options{SUBSECOND}) && $options{SUBSECOND}) {
- my($frac) = $8;
- substr($frac,0,1) = '.';
- $$sr += $frac;
- }
- print "S = $$sr\n" if $debug;
- $ampm = $4 || $9 || $11 || '';
- $$tzr = $12;
- $$hr += 12 if $ampm and "\U$ampm" eq "PM" && $$hr != 12;
- $$hr = 0 if $$hr == 12 && "\U$ampm" eq "AM";
- printf "matched at %d, rem = %s.\n", __LINE__, $$tr if $debug;
- return 1;
- } elsif ($$tr =~ s#^noon$break##ix) {
- # noon
- ($$hr, $$mr, $$sr) = (12, 0, 0);
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- } elsif ($$tr =~ s#^midnight$break##ix) {
- # midnight
- ($$hr, $$mr, $$sr) = (0, 0, 0);
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- }
- return 0;
- }
- sub parse_time_offset
- {
- my ($tr, $rsr, %options) = @_;
- $$tr =~ s/^\s+//;
- return 0 if $options{NO_RELATIVE};
- if ($$tr =~ s{^(?xi)
- (?:
- (-) (?# 1)
- |
- [+]
- )?
- \s*
- (?:
- (\d+(?:\.\d+)?) (?# 2)
- |
- (?:(\d+)\s+(\d+)/(\d+)) (?# 3 4/5)
- )
- \s*
- (sec|second|min|minute|hour)s? (?# 6)
- (
- \s+
- ago (?# 7)
- )?
- $break
- }{}) {
- # count units
- $$rsr = 0 unless defined $$rsr;
- return 0 if defined($5) && $5 == 0;
- my $num = defined($2)
- ? $2
- : $3 + $4/$5;
- $num = -$num if $1;
- $$rsr += $umult{"\L$6"} * $num;
- $$rsr = -$$rsr if $7 ||
- $$tr =~ /\b(day|mon|month|year)s?\s*ago\b/;
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- }
- return 0;
- }
- #
- # What to you do with a date that has a two-digit year?
- # There's not much that can be done except make a guess.
- #
- # Some example situations to handle:
- #
- # now year
- #
- # 1999 01
- # 1999 71
- # 2010 71
- # 2110 09
- #
- sub expand_two_digit_year
- {
- my ($yr, $now, %options) = @_;
- return $yr if $yr > 100;
- my ($y) = (&righttime($now, %options))[5];
- $y += 1900;
- my $century = int($y / 100) * 100;
- my $within = $y % 100;
- my $r = $yr + $century;
- if ($options{PREFER_PAST}) {
- if ($yr > $within) {
- $r = $yr + $century - 100;
- }
- } elsif ($options{PREFER_FUTURE}) {
- # being strict here would be silly
- if ($yr < $within-20) {
- # it's 2019 and the date is '08'
- $r = $yr + $century + 100;
- }
- } elsif ($options{UNAMBIGUOUS}) {
- # we really shouldn't guess
- return undef;
- } else {
- # prefer the current century in most cases
- if ($within > 80 && $within - $yr > 60) {
- $r = $yr + $century + 100;
- }
- if ($within < 30 && $yr - $within > 59) {
- $r = $yr + $century - 100;
- }
- }
- print "two digit year '$yr' expanded into $r\n" if $debug;
- return $r;
- }
- sub calc
- {
- my ($rsr, $yr, $mr, $dr, $rdr, $now, $units, $count, %options) = @_;
- confess unless $units;
- $units = "\L$units";
- print "calc based on $units\n" if $debug;
- if ($units eq 'day') {
- $$rdr = $count;
- } elsif ($units eq 'week') {
- $$rdr = $count * 7;
- } elsif ($umult{$units}) {
- $$rsr = $count * $umult{$units};
- } elsif ($units eq 'mon' || $units eq 'month') {
- ($$yr, $$mr, $$dr) = &monthoff($now, $count, %options);
- $$rsr = 0 unless $$rsr;
- } elsif ($units eq 'year') {
- ($$yr, $$mr, $$dr) = &monthoff($now, $count * 12, %options);
- $$rsr = 0 unless $$rsr;
- } else {
- carp "interal error";
- }
- print "calced rsr $$rsr rdr $$rdr, yr $$yr mr $$mr dr $$dr.\n" if $debug;
- }
- sub monthoff
- {
- my ($now, $months, %options) = @_;
- # months are 0..11
- my ($d, $m11, $y) = (&righttime($now, %options)) [ 3,4,5 ] ;
- $y += 1900;
- print "m11 = $m11 + $months, y = $y\n" if $debug;
- $m11 += $months;
- print "m11 = $m11, y = $y\n" if $debug;
- if ($m11 > 11 || $m11 < 0) {
- $y -= 1 if $m11 < 0 && ($m11 % 12 != 0);
- $y += int($m11/12);
- # this is required to work around a bug in perl 5.003
- no integer;
- $m11 %= 12;
- }
- print "m11 = $m11, y = $y\n" if $debug;
- #
- # What is "1 month from January 31st?"
- # I think the answer is February 28th most years.
- #
- # Similarly, what is one year from February 29th, 1980?
- # I think it's February 28th, 1981.
- #
- # If you disagree, change the following code.
- #
- if ($d > 30 or ($d > 28 && $m11 == 1)) {
- require Time::DaysInMonth;
- my $dim = Time::DaysInMonth::days_in($y, $m11+1);
- print "dim($y,$m11+1)= $dim\n" if $debug;
- $d = $dim if $d > $dim;
- }
- return ($y, $m11+1, $d);
- }
- sub righttime
- {
- my ($time, %options) = @_;
- if ($options{GMT}) {
- return gmtime($time);
- } else {
- return localtime($time);
- }
- }
- sub parse_year_only
- {
- my ($tr, $yr, $now, %options) = @_;
- $$tr =~ s#^\s+##;
- if ($$tr =~ s#^(\d\d\d\d)$break##) {
- $$yr = $1;
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- } elsif ($$tr =~ s#\'(\d\d)$break##) {
- $$yr = expand_two_digit_year($1, $now, %options);
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- }
- return 0;
- }
- sub parse_date_offset
- {
- my ($tr, $now, $yr, $mr, $dr, $rdr, $rsr, %options) = @_;
- return 0 if $options{NO_RELATIVE};
- # now - current seconds_since_epoch
- # yr - year return
- # mr - month return
- # dr - day return
- # rdr - relative day return
- # rsr - relative second return
- my $j;
- my $wday = (&righttime($now, %options))[6];
- $$tr =~ s#^\s+##;
- if ($$tr =~ s#^(?xi)
- \s*
- (\d+)
- \s*
- (day|week|month|year)s?
- (
- \s+
- ago
- )?
- $break
- ##) {
- my $amt = $1 + 0;
- my $units = $2;
- $amt = -$amt if $3 ||
- $$tr =~ m#\b(sec|second|min|minute|hour)s?\s*ago\b#;
- &calc($rsr, $yr, $mr, $dr, $rdr, $now, $units,
- $amt, %options);
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- } elsif ($$tr =~ s#^(?xi)
- (?:
- (?:
- now
- \s+
- )?
- (\+ | \-)
- \s*
- )?
- (\d+)
- \s*
- (day|week|month|year)s?
- $break
- ##) {
- my $one = $1 || '';
- my $two = $2 || '';
- my $amt = "$one$two"+0;
- &calc($rsr, $yr, $mr, $dr, $rdr, $now, $3,
- $amt, %options);
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- } elsif ($$tr =~ s#^(?xi)
- (Mon|Tue|Wed|Thu|Fri|Sat|Sun|Monday|Tuesday
- |Wednesday|Thursday|Friday|Saturday|Sunday)
- \s+
- after
- \s+
- next
- $break
- ##) {
- # Dow "after next"
- $$rdr = $wdays{"\L$1"} - $wday + ( $wdays{"\L$1"} > $wday ? 7 : 14);
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- } elsif ($$tr =~ s#^(?xi)
- (Mon|Tue|Wed|Thu|Fri|Sat|Sun|Monday|Tuesday
- |Wednesday|Thursday|Friday|Saturday|Sunday)
- \s+
- before
- \s+
- last
- $break
- ##) {
- # Dow "before last"
- $$rdr = $wdays{"\L$1"} - $wday - ( $wdays{"\L$1"} < $wday ? 7 : 14);
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- } elsif ($$tr =~ s#^(?xi)
- next\s+
- (Mon|Tue|Wed|Thu|Fri|Sat|Sun|Monday|Tuesday
- |Wednesday|Thursday|Friday|Saturday|Sunday)
- $break
- ##) {
- # "next" Dow
- $$rdr = $wdays{"\L$1"} - $wday
- + ( $wdays{"\L$1"} > $wday ? 0 : 7);
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- } elsif ($$tr =~ s#^(?xi)
- last\s+
- (Mon|Tue|Wed|Thu|Fri|Sat|Sun|Monday|Tuesday
- |Wednesday|Thursday|Friday|Saturday|Sunday)
- $break##) {
- # "last" Dow
- printf "c %d - %d + ( %d < %d ? 0 : -7 \n", $wdays{"\L$1"}, $wday, $wdays{"\L$1"}, $wday if $debug;
- $$rdr = $wdays{"\L$1"} - $wday + ( $wdays{"\L$1"} < $wday ? 0 : -7);
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- } elsif ($options{PREFER_PAST} and $$tr =~ s#^(?xi)
- (Mon|Tue|Wed|Thu|Fri|Sat|Sun|Monday|Tuesday
- |Wednesday|Thursday|Friday|Saturday|Sunday)
- $break##) {
- # Dow
- printf "c %d - %d + ( %d < %d ? 0 : -7 \n", $wdays{"\L$1"}, $wday, $wdays{"\L$1"}, $wday if $debug;
- $$rdr = $wdays{"\L$1"} - $wday + ( $wdays{"\L$1"} < $wday ? 0 : -7);
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- } elsif ($options{PREFER_FUTURE} and $$tr =~ s#^(?xi)
- (Mon|Tue|Wed|Thu|Fri|Sat|Sun|Monday|Tuesday
- |Wednesday|Thursday|Friday|Saturday|Sunday)
- $break
- ##) {
- # Dow
- $$rdr = $wdays{"\L$1"} - $wday
- + ( $wdays{"\L$1"} > $wday ? 0 : 7);
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- } elsif ($$tr =~ s#^today$break##xi) {
- # today
- $$rdr = 0;
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- } elsif ($$tr =~ s#^tomorrow$break##xi) {
- $$rdr = 1;
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- } elsif ($$tr =~ s#^yesterday$break##xi) {
- $$rdr = -1;
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- } elsif ($$tr =~ s#^last\s+(week|month|year)$break##xi) {
- &calc($rsr, $yr, $mr, $dr, $rdr, $now, $1, -1, %options);
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- } elsif ($$tr =~ s#^next\s+(week|month|year)$break##xi) {
- &calc($rsr, $yr, $mr, $dr, $rdr, $now, $1, 1, %options);
- printf "matched at %d.\n", __LINE__ if $debug;
- return 1;
- } elsif ($$tr =~ s#^now $break##x) {
- $$rdr = 0;
- return 1;
- }
- return 0;
- }
- sub debug_display
- {
- my ($tz, $tzo, $H, $M, $S, $m, $d, $y, $rs, $rd, $rel, $passes, $parse, $t) = @_;
- print "---------<<\n";
- print defined($tz) ? "tz: $tz.\n" : "no tz\n";
- print defined($tzo) ? "tzo: $tzo.\n" : "no tzo\n";
- print "HMS: ";
- print defined($H) ? "$H, " : "no H, ";
- print defined($M) ? "$M, " : "no M, ";
- print defined($S) ? "$S\n" : "no S.\n";
- print "mdy: ";
- print defined($m) ? "$m, " : "no m, ";
- print defined($d) ? "$d, " : "no d, ";
- print defined($y) ? "$y\n" : "no y.\n";
- print defined($rs) ? "rs: $rs.\n" : "no rs\n";
- print defined($rd) ? "rd: $rd.\n" : "no rd\n";
- print $rel ? "relative\n" : "not relative\n";
- print "passes: $passes\n";
- print "parse:$parse\n";
- print "t: $t.\n";
- print "--------->>\n";
- }
- 1;
- __END__
- =head1 NAME
- Time::ParseDate -- date parsing both relative and absolute
- =head1 SYNOPSIS
- use Time::ParseDate;
- $seconds_since_jan1_1970 = parsedate("12/11/94 2pm", NO_RELATIVE => 1)
- $seconds_since_jan1_1970 = parsedate("12/11/94 2pm", %options)
- =head1 OPTIONS
- Date parsing can also use options. The options are as follows:
- FUZZY -> it's okay not to parse the entire date string
- NOW -> the "current" time for relative times (defaults to time())
- ZONE -> local timezone (defaults to $ENV{TZ})
- WHOLE -> the whole input string must be parsed
- GMT -> input time is assumed to be GMT, not localtime
- UK -> prefer UK style dates (dd/mm over mm/dd)
- DATE_REQUIRED -> do not default the date
- TIME_REQUIRED -> do not default the time
- NO_RELATIVE -> input time is not relative to NOW
- TIMEFIRST -> try parsing time before date [not default]
- PREFER_PAST -> when year or day of week is ambigueous, assume past
- PREFER_FUTURE -> when year or day of week is ambigueous, assume future
- SUBSECOND -> parse fraction seconds
- VALIDATE -> only accept normal values for HHMMSS, YYMMDD. Otherwise
- days like -1 might give the last day of the previous month.
- =head1 DATE FORMATS RECOGNIZED
- =head2 Absolute date formats
- Dow, dd Mon yy
- Dow, dd Mon yyyy
- Dow, dd Mon
- dd Mon yy
- dd Mon yyyy
- Month day{st,nd,rd,th}, year
- Month day{st,nd,rd,th}
- Mon dd yyyy
- yyyy/mm/dd
- yyyy-mm-dd (usually the best date specification syntax)
- yyyy/mm
- mm/dd/yy
- mm/dd/yyyy
- mm/yy
- yy/mm (only if year > 12, or > 31 if UK)
- yy/mm/dd (only if year > 12 and day < 32, or year > 31 if UK)
- dd/mm/yy (only if UK, or an invalid mm/dd/yy or yy/mm/dd)
- dd/mm/yyyy (only if UK, or an invalid mm/dd/yyyy)
- dd/mm (only if UK, or an invalid mm/dd)
- =head2 Relative date formats:
- count "days"
- count "weeks"
- count "months"
- count "years"
- Dow "after next"
- Dow "before last"
- Dow (requires PREFER_PAST or PREFER_FUTURE)
- "next" Dow
- "tomorrow"
- "today"
- "yesterday"
- "last" dow
- "last week"
- "now"
- "now" "+" count units
- "now" "-" count units
- "+" count units
- "-" count units
- count units "ago"
- =head2 Absolute time formats:
- hh:mm:ss[.ddd]
- hh:mm
- hh:mm[AP]M
- hh[AP]M
- hhmmss[[AP]M]
- "noon"
- "midnight"
- =head2 Relative time formats:
- count "minutes" (count can be franctional "1.5" or "1 1/2")
- count "seconds"
- count "hours"
- "+" count units
- "+" count
- "-" count units
- "-" count
- count units "ago"
- =head2 Timezone formats:
- [+-]dddd
- GMT[+-]d+
- [+-]dddd (TZN)
- TZN
- =head2 Special formats:
- [ d]d/Mon/yyyy:hh:mm:ss [[+-]dddd]
- yy/mm/dd.hh:mm
- =head1 DESCRIPTION
- This module recognizes the above date/time formats. Usually a
- date and a time are specified. There are numerous options for
- controlling what is recognized and what is not.
- The return code is always the time in seconds since January 1st, 1970
- or undef if it was unable to parse the time.
- If a timezone is specified it must be after the time. Year specifications
- can be tacked onto the end of absolute times.
- If C<parsedate()> is called from array context, then it will return two
- elements. On successful parses, it will return the seconds and what
- remains of its input string. On unsuccessful parses, it will return
- C<undef> and an error string.
- =head1 EXAMPLES
- $seconds = parsedate("Mon Jan 2 04:24:27 1995");
- $seconds = parsedate("Tue Apr 4 00:22:12 PDT 1995");
- $seconds = parsedate("04.04.95 00:22", ZONE => PDT);
- $seconds = parsedate("Jan 1 1999 11:23:34.578", SUBSECOND => 1);
- $seconds = parsedate("122212 950404", ZONE => PDT, TIMEFIRST => 1);
- $seconds = parsedate("+3 secs", NOW => 796978800);
- $seconds = parsedate("2 months", NOW => 796720932);
- $seconds = parsedate("last Tuesday");
- $seconds = parsedate("Sunday before last");
- ($seconds, $remaining) = parsedate("today is the day");
- ($seconds, $error) = parsedate("today is", WHOLE=>1);
- =head1 LICENSE
- Copyright (C) 1996-2010 David Muir Sharnoff.
- Copyright (C) 2011 Google, Inc.
- License hereby
- granted for anyone to use, modify or redistribute this module at
- their own risk. Please feed useful changes back to [email protected].
|