ParseDate.pm 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258
  1. package Time::ParseDate;
  2. require 5.000;
  3. use Carp;
  4. use Time::Timezone;
  5. use Time::JulianDay;
  6. require Exporter;
  7. @ISA = qw(Exporter);
  8. @EXPORT = qw(parsedate);
  9. @EXPORT_OK = qw(pd_raw %mtable %umult %wdays);
  10. use strict;
  11. #use diagnostics;
  12. # constants
  13. use vars qw(%mtable %umult %wdays $VERSION);
  14. $VERSION = 2013.1113;
  15. # globals
  16. use vars qw($debug);
  17. # dynamically-scoped
  18. use vars qw($parse);
  19. my %mtable;
  20. my %umult;
  21. my %wdays;
  22. my $y2k;
  23. CONFIG: {
  24. %mtable = qw(
  25. Jan 1 Jan. 1 January 1
  26. Feb 2 Feb. 2 February 2
  27. Mar 3 Mar. 3 March 3
  28. Apr 4 Apr. 4 April 4
  29. May 5
  30. Jun 6 Jun. 6 June 6
  31. Jul 7 Jul. 7 July 7
  32. Aug 8 Aug. 8 August 8
  33. Sep 9 Sep. 9 September 9 Sept 9
  34. Oct 10 Oct. 10 October 10
  35. Nov 11 Nov. 11 November 11
  36. Dec 12 Dec. 12 December 12 );
  37. %umult = qw(
  38. sec 1 second 1
  39. min 60 minute 60
  40. hour 3600
  41. day 86400
  42. week 604800
  43. fortnight 1209600);
  44. %wdays = qw(
  45. sun 0 sunday 0
  46. mon 1 monday 1
  47. tue 2 tuesday 2
  48. wed 3 wednesday 3
  49. thu 4 thursday 4
  50. fri 5 friday 5
  51. sat 6 saturday 6
  52. );
  53. $y2k = 946684800; # turn of the century
  54. }
  55. my $break = qr{(?:\s+|\Z|\b(?![-:.,/]\d))};
  56. sub parsedate
  57. {
  58. my ($t, %options) = @_;
  59. my ($y, $m, $d); # year, month - 1..12, day
  60. my ($H, $M, $S); # hour, minute, second
  61. my $tz; # timezone
  62. my $tzo; # timezone offset
  63. my ($rd, $rs); # relative days, relative seconds
  64. my $rel; # time&|date is relative
  65. my $isspec;
  66. my $now = defined($options{NOW}) ? $options{NOW} : time;
  67. my $passes = 0;
  68. my $uk = defined($options{UK}) ? $options{UK} : 0;
  69. local $parse = ''; # will be dynamically scoped.
  70. if ($t =~ s#^ ([ \d]\d)
  71. / (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)
  72. / (\d\d\d\d)
  73. : (\d\d)
  74. : (\d\d)
  75. : (\d\d)
  76. (?:
  77. [ ]
  78. ([-+] \d\d\d\d)
  79. (?: \("?(?:(?:[A-Z]{1,4}[TCW56])|IDLE)\))?
  80. )?
  81. $break
  82. ##xi) { #"emacs
  83. # [ \d]/Mon/yyyy:hh:mm:ss [-+]\d\d\d\d
  84. # This is the format for www server logging.
  85. ($d, $m, $y, $H, $M, $S, $tzo) = ($1, $mtable{"\u\L$2"}, $3, $4, $5, $6, $7 ? &mkoff($7) : ($tzo || undef));
  86. $parse .= " ".__LINE__ if $debug;
  87. } elsif ($t =~ s#^(\d\d)/(\d\d)/(\d\d)\.(\d\d)\:(\d\d)($break)##) {
  88. # yy/mm/dd.hh:mm
  89. # I support this format because it's used by wbak/rbak
  90. # on Apollo Domain OS. Silly, but historical.
  91. ($y, $m, $d, $H, $M, $S) = ($1, $2, $3, $4, $5, 0);
  92. $parse .= " ".__LINE__ if $debug;
  93. } else {
  94. while(1) {
  95. if (! defined $m and ! defined $rd and ! defined $y
  96. and ! ($passes == 0 and $options{'TIMEFIRST'}))
  97. {
  98. # no month defined.
  99. if (&parse_date_only(\$t, \$y, \$m, \$d, $uk)) {
  100. $parse .= " ".__LINE__ if $debug;
  101. next;
  102. }
  103. }
  104. if (! defined $H and ! defined $rs) {
  105. if (&parse_time_only(\$t, \$H, \$M, \$S,
  106. \$tz, %options))
  107. {
  108. $parse .= " ".__LINE__ if $debug;
  109. next;
  110. }
  111. }
  112. next if $passes == 0 and $options{'TIMEFIRST'};
  113. if (! defined $y) {
  114. if (&parse_year_only(\$t, \$y, $now, %options)) {
  115. $parse .= " ".__LINE__ if $debug;
  116. next;
  117. }
  118. }
  119. if (! defined $tz and ! defined $tzo and ! defined $rs
  120. and (defined $m or defined $H))
  121. {
  122. if (&parse_tz_only(\$t, \$tz, \$tzo)) {
  123. $parse .= " ".__LINE__ if $debug;
  124. next;
  125. }
  126. }
  127. if (! defined $H and ! defined $rs) {
  128. if (&parse_time_offset(\$t, \$rs, %options)) {
  129. $rel = 1;
  130. $parse .= " ".__LINE__ if $debug;
  131. next;
  132. }
  133. }
  134. if (! defined $m and ! defined $rd and ! defined $y) {
  135. if (&parse_date_offset(\$t, $now, \$y,
  136. \$m, \$d, \$rd, \$rs, %options))
  137. {
  138. $rel = 1;
  139. $parse .= " ".__LINE__ if $debug;
  140. next;
  141. }
  142. }
  143. if (defined $M or defined $rd) {
  144. if ($t =~ s/^\s*(?:at|\@|\+)($break)//x) {
  145. $rel = 1;
  146. $parse .= " ".__LINE__ if $debug;
  147. next;
  148. }
  149. }
  150. last;
  151. } continue {
  152. $passes++;
  153. &debug_display($tz, $tzo, $H, $M, $S, $m, $d, $y, $rs, $rd, $rel, $passes, $parse, $t) if $debug;
  154. }
  155. if ($passes == 0) {
  156. print "nothing matched\n" if $debug;
  157. return (undef, "no match on time/date")
  158. if wantarray();
  159. return undef;
  160. }
  161. }
  162. &debug_display($tz, $tzo, $H, $M, $S, $m, $d, $y, $rs, $rd, $rel, $passes, $parse, $t) if $debug;
  163. $t =~ s/^\s+//;
  164. if ($t ne '') {
  165. # we didn't manage to eat the string
  166. print "NOT WHOLE\n" if $debug;
  167. if ($options{WHOLE}) {
  168. return (undef, "characters left over after parse")
  169. if wantarray();
  170. return undef
  171. }
  172. }
  173. # define a date if there isn't one already
  174. if (! defined $y and ! defined $m and ! defined $rd) {
  175. print "no date defined, trying to find one." if $debug;
  176. if (defined $rs or defined $H) {
  177. # we do have a time.
  178. if ($options{DATE_REQUIRED}) {
  179. return (undef, "no date specified")
  180. if wantarray();
  181. return undef;
  182. }
  183. if (defined $rs) {
  184. print "simple offset: $rs\n" if $debug;
  185. my $rv = $now + $rs;
  186. return ($rv, $t) if wantarray();
  187. return $rv;
  188. }
  189. $rd = 0;
  190. } else {
  191. print "no time either!\n" if $debug;
  192. return (undef, "no time specified")
  193. if wantarray();
  194. return undef;
  195. }
  196. }
  197. if ($options{TIME_REQUIRED} && ! defined($rs)
  198. && ! defined($H) && ! defined($rd))
  199. {
  200. return (undef, "no time found")
  201. if wantarray();
  202. return undef;
  203. }
  204. my $secs;
  205. my $jd;
  206. if (defined $rd) {
  207. if (defined $rs || ! (defined($H) || defined($M) || defined($S))) {
  208. print "fully relative\n" if $debug;
  209. my ($j, $in, $it);
  210. my $definedrs = defined($rs) ? $rs : 0;
  211. my ($isdst_now, $isdst_then);
  212. my $r = $now + $rd * 86400 + $definedrs;
  213. #
  214. # It's possible that there was a timezone shift
  215. # during the time specified. If so, keep the
  216. # hours the "same".
  217. #
  218. $isdst_now = (localtime($r))[8];
  219. $isdst_then = (localtime($now))[8];
  220. if (($isdst_now == $isdst_then) || $options{GMT})
  221. {
  222. return ($r, $t) if wantarray();
  223. return $r
  224. }
  225. print "localtime changed DST during time period!\n" if $debug;
  226. }
  227. print "relative date\n" if $debug;
  228. $jd = $options{GMT}
  229. ? gm_julian_day($now)
  230. : local_julian_day($now);
  231. print "jd($now) = $jd\n" if $debug;
  232. $jd += $rd;
  233. } else {
  234. unless (defined $y) {
  235. if ($options{PREFER_PAST}) {
  236. my ($day, $mon011);
  237. ($day, $mon011, $y) = (&righttime($now))[3,4,5];
  238. print "calc year -past $day-$d $mon011-$m $y\n" if $debug;
  239. $y -= 1 if ($mon011+1 < $m) ||
  240. (($mon011+1 == $m) && ($day < $d));
  241. } elsif ($options{PREFER_FUTURE}) {
  242. print "calc year -future\n" if $debug;
  243. my ($day, $mon011);
  244. ($day, $mon011, $y) = (&righttime($now))[3,4,5];
  245. $y += 1 if ($mon011 >= $m) ||
  246. (($mon011+1 == $m) && ($day > $d));
  247. } else {
  248. print "calc year -this\n" if $debug;
  249. $y = (localtime($now))[5];
  250. }
  251. $y += 1900;
  252. }
  253. $y = expand_two_digit_year($y, $now, %options)
  254. if $y < 100;
  255. if ($options{VALIDATE}) {
  256. require Time::DaysInMonth;
  257. my $dim = Time::DaysInMonth::days_in($y, $m);
  258. if ($y < 1000 or $m < 1 or $d < 1
  259. or $y > 9999 or $m > 12 or $d > $dim)
  260. {
  261. return (undef, "illegal YMD: $y, $m, $d")
  262. if wantarray();
  263. return undef;
  264. }
  265. }
  266. $jd = julian_day($y, $m, $d);
  267. print "jd($y, $m, $d) = $jd\n" if $debug;
  268. }
  269. # put time into HMS
  270. if (! defined($H)) {
  271. if (defined($rd) || defined($rs)) {
  272. ($S, $M, $H) = &righttime($now, %options);
  273. print "HMS set to $H $M $S\n" if $debug;
  274. }
  275. }
  276. my $carry;
  277. print "before ", (defined($rs) ? "$rs" : ""),
  278. " $jd $H $M $S\n"
  279. if $debug;
  280. #
  281. # add in relative seconds. Do it this way because we want to
  282. # preserve the localtime across DST changes.
  283. #
  284. $S = 0 unless $S; # -w
  285. $M = 0 unless $M; # -w
  286. $H = 0 unless $H; # -w
  287. if ($options{VALIDATE} and
  288. ($S < 0 or $M < 0 or $H < 0 or $S > 59 or $M > 59 or $H > 23))
  289. {
  290. return (undef, "illegal HMS: $H, $M, $S") if wantarray();
  291. return undef;
  292. }
  293. $S += $rs if defined $rs;
  294. $carry = int($S / 60) - ($S < 0 && $S % 60 && 1);
  295. $S -= $carry * 60;
  296. $M += $carry;
  297. $carry = int($M / 60) - ($M < 0 && $M % 60 && 1);
  298. $M %= 60;
  299. $H += $carry;
  300. $carry = int($H / 24) - ($H < 0 && $H % 24 && 1);
  301. $H %= 24;
  302. $jd += $carry;
  303. print "after rs $jd $H $M $S\n" if $debug;
  304. $secs = jd_secondsgm($jd, $H, $M, $S);
  305. print "jd_secondsgm($jd, $H, $M, $S) = $secs\n" if $debug;
  306. #
  307. # If we see something link 3pm CST then and we want to end
  308. # up with a GMT seconds, then we convert the 3pm to GMT and
  309. # subtract in the offset for CST. We subtract because we
  310. # are converting from CST to GMT.
  311. #
  312. my $tzadj;
  313. if ($tz) {
  314. $tzadj = tz_offset($tz, $secs);
  315. if (defined $tzadj) {
  316. print "adjusting secs for $tz: $tzadj\n" if $debug;
  317. $tzadj = tz_offset($tz, $secs-$tzadj);
  318. $secs -= $tzadj;
  319. } else {
  320. print "unknown timezone: $tz\n" if $debug;
  321. undef $secs;
  322. undef $t;
  323. }
  324. } elsif (defined $tzo) {
  325. print "adjusting time for offset: $tzo\n" if $debug;
  326. $secs -= $tzo;
  327. } else {
  328. unless ($options{GMT}) {
  329. if ($options{ZONE}) {
  330. $tzadj = tz_offset($options{ZONE}, $secs) || 0;
  331. $tzadj = tz_offset($options{ZONE}, $secs-$tzadj);
  332. unless (defined($tzadj)) {
  333. return (undef, "could not convert '$options{ZONE}' to time offset")
  334. if wantarray();
  335. return undef;
  336. }
  337. print "adjusting secs for $options{ZONE}: $tzadj\n" if $debug;
  338. $secs -= $tzadj;
  339. } else {
  340. $tzadj = tz_local_offset($secs);
  341. print "adjusting secs for local offset: $tzadj\n" if $debug;
  342. #
  343. # Just in case we are very close to a time
  344. # change...
  345. #
  346. $tzadj = tz_local_offset($secs-$tzadj);
  347. $secs -= $tzadj;
  348. }
  349. }
  350. }
  351. print "returning $secs.\n" if $debug;
  352. return ($secs, $t) if wantarray();
  353. return $secs;
  354. }
  355. sub mkoff
  356. {
  357. my($offset) = @_;
  358. if (defined $offset and $offset =~ s#^([-+])(\d\d):?(\d\d)$##) {
  359. return ($1 eq '+' ?
  360. 3600 * $2 + 60 * $3
  361. : -3600 * $2 + -60 * $3 );
  362. }
  363. return undef;
  364. }
  365. sub parse_tz_only
  366. {
  367. my($tr, $tz, $tzo) = @_;
  368. $$tr =~ s#^\s+##;
  369. my $o;
  370. if ($$tr =~ s#^
  371. ([-+]\d\d:?\d\d)
  372. \s+
  373. \(
  374. "?
  375. (?:
  376. (?:
  377. [A-Z]{1,4}[TCW56]
  378. )
  379. |
  380. IDLE
  381. )
  382. \)
  383. $break
  384. ##x) { #"emacs
  385. $$tzo = &mkoff($1);
  386. printf "matched at %d.\n", __LINE__ if $debug;
  387. return 1;
  388. } elsif ($$tr =~ s#^GMT\s*([-+]\d{1,2})($break)##x) {
  389. $o = $1;
  390. if ($o < 24 and $o !~ /^0/) {
  391. # probably hours.
  392. printf "adjusted at %d. ($o 00)\n", __LINE__ if $debug;
  393. $o = "${o}00";
  394. }
  395. $o =~ s/\b(\d\d\d)/0$1/;
  396. $$tzo = &mkoff($o);
  397. printf "matched at %d. ($$tzo, $o)\n", __LINE__ if $debug;
  398. return 1;
  399. } elsif ($$tr =~ s#^(?:GMT\s*)?([-+]\d\d:?\d\d)($break)##x) {
  400. $o = $1;
  401. $$tzo = &mkoff($o);
  402. printf "matched at %d.\n", __LINE__ if $debug;
  403. return 1;
  404. } elsif ($$tr =~ s#^"?((?:[A-Z]{1,4}[TCW56])|IDLE)$break##x) { #"
  405. $$tz = $1;
  406. $$tz .= " DST"
  407. if $$tz eq 'MET' && $$tr =~ s#^DST$break##x;
  408. printf "matched at %d: '$$tz'.\n", __LINE__ if $debug;
  409. return 1;
  410. }
  411. return 0;
  412. }
  413. sub parse_date_only
  414. {
  415. my ($tr, $yr, $mr, $dr, $uk) = @_;
  416. $$tr =~ s#^\s+##;
  417. if ($$tr =~ s#^(\d\d\d\d)([-./])(\d\d?)\2(\d\d?)(T|$break)##) {
  418. # yyyy/mm/dd
  419. ($$yr, $$mr, $$dr) = ($1, $3, $4);
  420. printf "matched at %d.\n", __LINE__ if $debug;
  421. return 1;
  422. } elsif ($$tr =~ s#^(\d\d?)([-./])(\d\d?)\2(\d\d\d\d?)($break)##) {
  423. # mm/dd/yyyy - is this safe? No.
  424. # -- or dd/mm/yyyy! If $1>12, then it's unambiguous.
  425. # Otherwise check option UK for UK style date.
  426. if ($uk || $1>12) {
  427. ($$yr, $$mr, $$dr) = ($4, $3, $1);
  428. } else {
  429. ($$yr, $$mr, $$dr) = ($4, $1, $3);
  430. }
  431. printf "matched at %d.\n", __LINE__ if $debug;
  432. return 1;
  433. } elsif ($$tr =~ s#^(\d\d\d\d)/(\d\d?)$break##x) {
  434. # yyyy/mm
  435. ($$yr, $$mr, $$dr) = ($1, $2, 1);
  436. printf "matched at %d.\n", __LINE__ if $debug;
  437. return 1;
  438. } elsif ($$tr =~ s#^(?xi)
  439. (?:
  440. (?:Mon|Monday|Tue|Tuesday|Wed|Wednesday|
  441. Thu|Thursday|Fri|Friday|
  442. Sat|Saturday|Sun|Sunday),?
  443. \s+
  444. )?
  445. (\d\d?)
  446. (\s+ | - | \. | /)
  447. (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\.?
  448. (?:
  449. \2
  450. (\d\d (?:\d\d)? )
  451. )?
  452. $break
  453. ##) {
  454. # [Dow,] dd Mon [yy[yy]]
  455. ($$yr, $$mr, $$dr) = ($4, $mtable{"\u\L$3"}, $1);
  456. printf "%d: %s - %s - %s\n", __LINE__, $1, $2, $3 if $debug;
  457. print "y undef\n" if ($debug && ! defined($$yr));
  458. return 1;
  459. } elsif ($$tr =~ s#^(?xi)
  460. (?:
  461. (?:Mon|Monday|Tue|Tuesday|Wed|Wednesday|
  462. Thu|Thursday|Fri|Friday|
  463. Sat|Saturday|Sun|Sunday),?
  464. \s+
  465. )?
  466. (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\.?
  467. ((\s)+ | - | \. | /)
  468. (\d\d?)
  469. ,?
  470. (?:
  471. (?: \2|\3+)
  472. (\d\d (?: \d\d)?)
  473. )?
  474. $break
  475. ##) {
  476. # [Dow,] Mon dd [yyyy]
  477. # [Dow,] Mon d, [yy]
  478. ($$yr, $$mr, $$dr) = ($5, $mtable{"\u\L$1"}, $4);
  479. printf "%d: %s - %s - %s\n", __LINE__, $1, $2, $4 if $debug;
  480. print "y undef\n" if ($debug && ! defined($$yr));
  481. return 1;
  482. } elsif ($$tr =~ s#^(?xi)
  483. (January|Jan\.?|February|Feb\.?|March|Mar\.?|April|Apr\.?|May|
  484. June|Jun\.?|July|Jul\.?|August|Aug\.?|September|Sep\.?|
  485. October|Oct\.?|November|Nov\.?|December|Dec\.?)
  486. \s+
  487. (\d+)
  488. (?:st|nd|rd|th)?
  489. \,?
  490. (?:
  491. \s+
  492. (?:
  493. (\d\d\d\d)
  494. |(?:\' (\d\d))
  495. )
  496. )?
  497. $break
  498. ##) {
  499. # Month day{st,nd,rd,th}, 'yy
  500. # Month day{st,nd,rd,th}, year
  501. # Month day, year
  502. # Mon. day, year
  503. ($$yr, $$mr, $$dr) = ($3 || $4, $mtable{"\u\L$1"}, $2);
  504. printf "%d: %s - %s - %s - %s\n", __LINE__, $1, $2, $3, $4 if $debug;
  505. print "y undef\n" if ($debug && ! defined($$yr));
  506. printf "matched at %d.\n", __LINE__ if $debug;
  507. return 1;
  508. } elsif ($$tr =~ s#^(\d\d?)([-/.])(\d\d?)\2(\d\d?)($break)##x) {
  509. if ($1 > 31 || (!$uk && $1 > 12 && $4 < 32)) {
  510. # yy/mm/dd
  511. ($$yr, $$mr, $$dr) = ($1, $3, $4);
  512. } elsif ($1 > 12 || $uk) {
  513. # dd/mm/yy
  514. ($$yr, $$mr, $$dr) = ($4, $3, $1);
  515. } else {
  516. # mm/dd/yy
  517. ($$yr, $$mr, $$dr) = ($4, $1, $3);
  518. }
  519. printf "matched at %d.\n", __LINE__ if $debug;
  520. return 1;
  521. } elsif ($$tr =~ s#^(\d\d?)/(\d\d?)($break)##x) {
  522. if ($1 > 31 || (!$uk && $1 > 12)) {
  523. # yy/mm
  524. ($$yr, $$mr, $$dr) = ($1, $2, 1);
  525. } elsif ($2 > 31 || ($uk && $2 > 12)) {
  526. # mm/yy
  527. ($$yr, $$mr, $$dr) = ($2, $1, 1);
  528. } elsif ($1 > 12 || $uk) {
  529. # dd/mm
  530. ($$mr, $$dr) = ($2, $1);
  531. } else {
  532. # mm/dd
  533. ($$mr, $$dr) = ($1, $2);
  534. }
  535. printf "matched at %d.\n", __LINE__ if $debug;
  536. return 1;
  537. } elsif ($$tr =~ s#^(\d\d)(\d\d)(\d\d)($break)##x) {
  538. if ($1 > 31 || (!$uk && $1 > 12)) {
  539. # YYMMDD
  540. ($$yr, $$mr, $$dr) = ($1, $2, $3);
  541. } elsif ($1 > 12 || $uk) {
  542. # DDMMYY
  543. ($$yr, $$mr, $$dr) = ($3, $2, $1);
  544. } else {
  545. # MMDDYY
  546. ($$yr, $$mr, $$dr) = ($3, $1, $2);
  547. }
  548. printf "matched at %d.\n", __LINE__ if $debug;
  549. return 1;
  550. } elsif ($$tr =~ s#^(?xi)
  551. (\d{1,2})
  552. (\s+ | - | \. | /)
  553. (January|Jan\.?|February|Feb\.?|March|Mar\.?|April|Apr\.?|May|
  554. June|Jun\.?|July|Jul\.?|August|Aug\.?|September|Sep\.?|
  555. October|Oct\.?|November|Nov\.?|December|Dec\.?)
  556. (?:
  557. \2
  558. (
  559. \d\d
  560. (?:\d\d)?
  561. )
  562. )
  563. $break
  564. ##) {
  565. # dd Month [yr]
  566. ($$yr, $$mr, $$dr) = ($4, $mtable{"\u\L$3"}, $1);
  567. printf "matched at %d.\n", __LINE__ if $debug;
  568. return 1;
  569. } elsif ($$tr =~ s#^(?xi)
  570. (\d+)
  571. (?:st|nd|rd|th)?
  572. \s+
  573. (January|Jan\.?|February|Feb\.?|March|Mar\.?|April|Apr\.?|May|
  574. June|Jun\.?|July|Jul\.?|August|Aug\.?|September|Sep\.?|
  575. October|Oct\.?|November|Nov\.?|December|Dec\.?)
  576. (?:
  577. \,?
  578. \s+
  579. (\d\d\d\d)
  580. )?
  581. $break
  582. ##) {
  583. # day{st,nd,rd,th}, Month year
  584. ($$yr, $$mr, $$dr) = ($3, $mtable{"\u\L$2"}, $1);
  585. printf "%d: %s - %s - %s - %s\n", __LINE__, $1, $2, $3, $4 if $debug;
  586. print "y undef\n" if ($debug && ! defined($$yr));
  587. printf "matched at %d.\n", __LINE__ if $debug;
  588. return 1;
  589. }
  590. return 0;
  591. }
  592. sub parse_time_only
  593. {
  594. my ($tr, $hr, $mr, $sr, $tzr, %options) = @_;
  595. $$tr =~ s#^\s+##;
  596. if ($$tr =~ s!^(?x)
  597. (?:
  598. (?:
  599. ([012]\d) (?# $1)
  600. (?:
  601. ([0-5]\d) (?# $2)
  602. (?:
  603. ([0-5]\d) (?# $3)
  604. )?
  605. )
  606. \s*
  607. ([apAP][mM])? (?# $4)
  608. ) | (?:
  609. (\d{1,2}) (?# $5)
  610. (?:
  611. \:
  612. (\d\d) (?# $6)
  613. (?:
  614. \:
  615. (\d\d) (?# $7)
  616. (
  617. (?# don't barf on database sub-second timings)
  618. [:.,]
  619. \d+
  620. )? (?# $8)
  621. )?
  622. )
  623. \s*
  624. ([apAP][mM])? (?# $9)
  625. ) | (?:
  626. (\d{1,2}) (?# $10)
  627. ([apAP][mM]) (?# ${11})
  628. )
  629. )
  630. (?:
  631. \s+
  632. "?
  633. ( (?# ${12})
  634. (?: [A-Z]{1,4}[TCW56] )
  635. |
  636. IDLE
  637. )
  638. )?
  639. $break
  640. !!) { #"emacs
  641. # HH[[:]MM[:SS]]meridian [zone]
  642. my $ampm;
  643. $$hr = $1 || $5 || $10 || 0; # 10 is undef, but 5 is defined..
  644. $$mr = $2 || $6 || 0;
  645. $$sr = $3 || $7 || 0;
  646. if (defined($8) && exists($options{SUBSECOND}) && $options{SUBSECOND}) {
  647. my($frac) = $8;
  648. substr($frac,0,1) = '.';
  649. $$sr += $frac;
  650. }
  651. print "S = $$sr\n" if $debug;
  652. $ampm = $4 || $9 || $11 || '';
  653. $$tzr = $12;
  654. $$hr += 12 if $ampm and "\U$ampm" eq "PM" && $$hr != 12;
  655. $$hr = 0 if $$hr == 12 && "\U$ampm" eq "AM";
  656. printf "matched at %d, rem = %s.\n", __LINE__, $$tr if $debug;
  657. return 1;
  658. } elsif ($$tr =~ s#^noon$break##ix) {
  659. # noon
  660. ($$hr, $$mr, $$sr) = (12, 0, 0);
  661. printf "matched at %d.\n", __LINE__ if $debug;
  662. return 1;
  663. } elsif ($$tr =~ s#^midnight$break##ix) {
  664. # midnight
  665. ($$hr, $$mr, $$sr) = (0, 0, 0);
  666. printf "matched at %d.\n", __LINE__ if $debug;
  667. return 1;
  668. }
  669. return 0;
  670. }
  671. sub parse_time_offset
  672. {
  673. my ($tr, $rsr, %options) = @_;
  674. $$tr =~ s/^\s+//;
  675. return 0 if $options{NO_RELATIVE};
  676. if ($$tr =~ s{^(?xi)
  677. (?:
  678. (-) (?# 1)
  679. |
  680. [+]
  681. )?
  682. \s*
  683. (?:
  684. (\d+(?:\.\d+)?) (?# 2)
  685. |
  686. (?:(\d+)\s+(\d+)/(\d+)) (?# 3 4/5)
  687. )
  688. \s*
  689. (sec|second|min|minute|hour)s? (?# 6)
  690. (
  691. \s+
  692. ago (?# 7)
  693. )?
  694. $break
  695. }{}) {
  696. # count units
  697. $$rsr = 0 unless defined $$rsr;
  698. return 0 if defined($5) && $5 == 0;
  699. my $num = defined($2)
  700. ? $2
  701. : $3 + $4/$5;
  702. $num = -$num if $1;
  703. $$rsr += $umult{"\L$6"} * $num;
  704. $$rsr = -$$rsr if $7 ||
  705. $$tr =~ /\b(day|mon|month|year)s?\s*ago\b/;
  706. printf "matched at %d.\n", __LINE__ if $debug;
  707. return 1;
  708. }
  709. return 0;
  710. }
  711. #
  712. # What to you do with a date that has a two-digit year?
  713. # There's not much that can be done except make a guess.
  714. #
  715. # Some example situations to handle:
  716. #
  717. # now year
  718. #
  719. # 1999 01
  720. # 1999 71
  721. # 2010 71
  722. # 2110 09
  723. #
  724. sub expand_two_digit_year
  725. {
  726. my ($yr, $now, %options) = @_;
  727. return $yr if $yr > 100;
  728. my ($y) = (&righttime($now, %options))[5];
  729. $y += 1900;
  730. my $century = int($y / 100) * 100;
  731. my $within = $y % 100;
  732. my $r = $yr + $century;
  733. if ($options{PREFER_PAST}) {
  734. if ($yr > $within) {
  735. $r = $yr + $century - 100;
  736. }
  737. } elsif ($options{PREFER_FUTURE}) {
  738. # being strict here would be silly
  739. if ($yr < $within-20) {
  740. # it's 2019 and the date is '08'
  741. $r = $yr + $century + 100;
  742. }
  743. } elsif ($options{UNAMBIGUOUS}) {
  744. # we really shouldn't guess
  745. return undef;
  746. } else {
  747. # prefer the current century in most cases
  748. if ($within > 80 && $within - $yr > 60) {
  749. $r = $yr + $century + 100;
  750. }
  751. if ($within < 30 && $yr - $within > 59) {
  752. $r = $yr + $century - 100;
  753. }
  754. }
  755. print "two digit year '$yr' expanded into $r\n" if $debug;
  756. return $r;
  757. }
  758. sub calc
  759. {
  760. my ($rsr, $yr, $mr, $dr, $rdr, $now, $units, $count, %options) = @_;
  761. confess unless $units;
  762. $units = "\L$units";
  763. print "calc based on $units\n" if $debug;
  764. if ($units eq 'day') {
  765. $$rdr = $count;
  766. } elsif ($units eq 'week') {
  767. $$rdr = $count * 7;
  768. } elsif ($umult{$units}) {
  769. $$rsr = $count * $umult{$units};
  770. } elsif ($units eq 'mon' || $units eq 'month') {
  771. ($$yr, $$mr, $$dr) = &monthoff($now, $count, %options);
  772. $$rsr = 0 unless $$rsr;
  773. } elsif ($units eq 'year') {
  774. ($$yr, $$mr, $$dr) = &monthoff($now, $count * 12, %options);
  775. $$rsr = 0 unless $$rsr;
  776. } else {
  777. carp "interal error";
  778. }
  779. print "calced rsr $$rsr rdr $$rdr, yr $$yr mr $$mr dr $$dr.\n" if $debug;
  780. }
  781. sub monthoff
  782. {
  783. my ($now, $months, %options) = @_;
  784. # months are 0..11
  785. my ($d, $m11, $y) = (&righttime($now, %options)) [ 3,4,5 ] ;
  786. $y += 1900;
  787. print "m11 = $m11 + $months, y = $y\n" if $debug;
  788. $m11 += $months;
  789. print "m11 = $m11, y = $y\n" if $debug;
  790. if ($m11 > 11 || $m11 < 0) {
  791. $y -= 1 if $m11 < 0 && ($m11 % 12 != 0);
  792. $y += int($m11/12);
  793. # this is required to work around a bug in perl 5.003
  794. no integer;
  795. $m11 %= 12;
  796. }
  797. print "m11 = $m11, y = $y\n" if $debug;
  798. #
  799. # What is "1 month from January 31st?"
  800. # I think the answer is February 28th most years.
  801. #
  802. # Similarly, what is one year from February 29th, 1980?
  803. # I think it's February 28th, 1981.
  804. #
  805. # If you disagree, change the following code.
  806. #
  807. if ($d > 30 or ($d > 28 && $m11 == 1)) {
  808. require Time::DaysInMonth;
  809. my $dim = Time::DaysInMonth::days_in($y, $m11+1);
  810. print "dim($y,$m11+1)= $dim\n" if $debug;
  811. $d = $dim if $d > $dim;
  812. }
  813. return ($y, $m11+1, $d);
  814. }
  815. sub righttime
  816. {
  817. my ($time, %options) = @_;
  818. if ($options{GMT}) {
  819. return gmtime($time);
  820. } else {
  821. return localtime($time);
  822. }
  823. }
  824. sub parse_year_only
  825. {
  826. my ($tr, $yr, $now, %options) = @_;
  827. $$tr =~ s#^\s+##;
  828. if ($$tr =~ s#^(\d\d\d\d)$break##) {
  829. $$yr = $1;
  830. printf "matched at %d.\n", __LINE__ if $debug;
  831. return 1;
  832. } elsif ($$tr =~ s#\'(\d\d)$break##) {
  833. $$yr = expand_two_digit_year($1, $now, %options);
  834. printf "matched at %d.\n", __LINE__ if $debug;
  835. return 1;
  836. }
  837. return 0;
  838. }
  839. sub parse_date_offset
  840. {
  841. my ($tr, $now, $yr, $mr, $dr, $rdr, $rsr, %options) = @_;
  842. return 0 if $options{NO_RELATIVE};
  843. # now - current seconds_since_epoch
  844. # yr - year return
  845. # mr - month return
  846. # dr - day return
  847. # rdr - relative day return
  848. # rsr - relative second return
  849. my $j;
  850. my $wday = (&righttime($now, %options))[6];
  851. $$tr =~ s#^\s+##;
  852. if ($$tr =~ s#^(?xi)
  853. \s*
  854. (\d+)
  855. \s*
  856. (day|week|month|year)s?
  857. (
  858. \s+
  859. ago
  860. )?
  861. $break
  862. ##) {
  863. my $amt = $1 + 0;
  864. my $units = $2;
  865. $amt = -$amt if $3 ||
  866. $$tr =~ m#\b(sec|second|min|minute|hour)s?\s*ago\b#;
  867. &calc($rsr, $yr, $mr, $dr, $rdr, $now, $units,
  868. $amt, %options);
  869. printf "matched at %d.\n", __LINE__ if $debug;
  870. return 1;
  871. } elsif ($$tr =~ s#^(?xi)
  872. (?:
  873. (?:
  874. now
  875. \s+
  876. )?
  877. (\+ | \-)
  878. \s*
  879. )?
  880. (\d+)
  881. \s*
  882. (day|week|month|year)s?
  883. $break
  884. ##) {
  885. my $one = $1 || '';
  886. my $two = $2 || '';
  887. my $amt = "$one$two"+0;
  888. &calc($rsr, $yr, $mr, $dr, $rdr, $now, $3,
  889. $amt, %options);
  890. printf "matched at %d.\n", __LINE__ if $debug;
  891. return 1;
  892. } elsif ($$tr =~ s#^(?xi)
  893. (Mon|Tue|Wed|Thu|Fri|Sat|Sun|Monday|Tuesday
  894. |Wednesday|Thursday|Friday|Saturday|Sunday)
  895. \s+
  896. after
  897. \s+
  898. next
  899. $break
  900. ##) {
  901. # Dow "after next"
  902. $$rdr = $wdays{"\L$1"} - $wday + ( $wdays{"\L$1"} > $wday ? 7 : 14);
  903. printf "matched at %d.\n", __LINE__ if $debug;
  904. return 1;
  905. } elsif ($$tr =~ s#^(?xi)
  906. (Mon|Tue|Wed|Thu|Fri|Sat|Sun|Monday|Tuesday
  907. |Wednesday|Thursday|Friday|Saturday|Sunday)
  908. \s+
  909. before
  910. \s+
  911. last
  912. $break
  913. ##) {
  914. # Dow "before last"
  915. $$rdr = $wdays{"\L$1"} - $wday - ( $wdays{"\L$1"} < $wday ? 7 : 14);
  916. printf "matched at %d.\n", __LINE__ if $debug;
  917. return 1;
  918. } elsif ($$tr =~ s#^(?xi)
  919. next\s+
  920. (Mon|Tue|Wed|Thu|Fri|Sat|Sun|Monday|Tuesday
  921. |Wednesday|Thursday|Friday|Saturday|Sunday)
  922. $break
  923. ##) {
  924. # "next" Dow
  925. $$rdr = $wdays{"\L$1"} - $wday
  926. + ( $wdays{"\L$1"} > $wday ? 0 : 7);
  927. printf "matched at %d.\n", __LINE__ if $debug;
  928. return 1;
  929. } elsif ($$tr =~ s#^(?xi)
  930. last\s+
  931. (Mon|Tue|Wed|Thu|Fri|Sat|Sun|Monday|Tuesday
  932. |Wednesday|Thursday|Friday|Saturday|Sunday)
  933. $break##) {
  934. # "last" Dow
  935. printf "c %d - %d + ( %d < %d ? 0 : -7 \n", $wdays{"\L$1"}, $wday, $wdays{"\L$1"}, $wday if $debug;
  936. $$rdr = $wdays{"\L$1"} - $wday + ( $wdays{"\L$1"} < $wday ? 0 : -7);
  937. printf "matched at %d.\n", __LINE__ if $debug;
  938. return 1;
  939. } elsif ($options{PREFER_PAST} and $$tr =~ s#^(?xi)
  940. (Mon|Tue|Wed|Thu|Fri|Sat|Sun|Monday|Tuesday
  941. |Wednesday|Thursday|Friday|Saturday|Sunday)
  942. $break##) {
  943. # Dow
  944. printf "c %d - %d + ( %d < %d ? 0 : -7 \n", $wdays{"\L$1"}, $wday, $wdays{"\L$1"}, $wday if $debug;
  945. $$rdr = $wdays{"\L$1"} - $wday + ( $wdays{"\L$1"} < $wday ? 0 : -7);
  946. printf "matched at %d.\n", __LINE__ if $debug;
  947. return 1;
  948. } elsif ($options{PREFER_FUTURE} and $$tr =~ s#^(?xi)
  949. (Mon|Tue|Wed|Thu|Fri|Sat|Sun|Monday|Tuesday
  950. |Wednesday|Thursday|Friday|Saturday|Sunday)
  951. $break
  952. ##) {
  953. # Dow
  954. $$rdr = $wdays{"\L$1"} - $wday
  955. + ( $wdays{"\L$1"} > $wday ? 0 : 7);
  956. printf "matched at %d.\n", __LINE__ if $debug;
  957. return 1;
  958. } elsif ($$tr =~ s#^today$break##xi) {
  959. # today
  960. $$rdr = 0;
  961. printf "matched at %d.\n", __LINE__ if $debug;
  962. return 1;
  963. } elsif ($$tr =~ s#^tomorrow$break##xi) {
  964. $$rdr = 1;
  965. printf "matched at %d.\n", __LINE__ if $debug;
  966. return 1;
  967. } elsif ($$tr =~ s#^yesterday$break##xi) {
  968. $$rdr = -1;
  969. printf "matched at %d.\n", __LINE__ if $debug;
  970. return 1;
  971. } elsif ($$tr =~ s#^last\s+(week|month|year)$break##xi) {
  972. &calc($rsr, $yr, $mr, $dr, $rdr, $now, $1, -1, %options);
  973. printf "matched at %d.\n", __LINE__ if $debug;
  974. return 1;
  975. } elsif ($$tr =~ s#^next\s+(week|month|year)$break##xi) {
  976. &calc($rsr, $yr, $mr, $dr, $rdr, $now, $1, 1, %options);
  977. printf "matched at %d.\n", __LINE__ if $debug;
  978. return 1;
  979. } elsif ($$tr =~ s#^now $break##x) {
  980. $$rdr = 0;
  981. return 1;
  982. }
  983. return 0;
  984. }
  985. sub debug_display
  986. {
  987. my ($tz, $tzo, $H, $M, $S, $m, $d, $y, $rs, $rd, $rel, $passes, $parse, $t) = @_;
  988. print "---------<<\n";
  989. print defined($tz) ? "tz: $tz.\n" : "no tz\n";
  990. print defined($tzo) ? "tzo: $tzo.\n" : "no tzo\n";
  991. print "HMS: ";
  992. print defined($H) ? "$H, " : "no H, ";
  993. print defined($M) ? "$M, " : "no M, ";
  994. print defined($S) ? "$S\n" : "no S.\n";
  995. print "mdy: ";
  996. print defined($m) ? "$m, " : "no m, ";
  997. print defined($d) ? "$d, " : "no d, ";
  998. print defined($y) ? "$y\n" : "no y.\n";
  999. print defined($rs) ? "rs: $rs.\n" : "no rs\n";
  1000. print defined($rd) ? "rd: $rd.\n" : "no rd\n";
  1001. print $rel ? "relative\n" : "not relative\n";
  1002. print "passes: $passes\n";
  1003. print "parse:$parse\n";
  1004. print "t: $t.\n";
  1005. print "--------->>\n";
  1006. }
  1007. 1;
  1008. __END__
  1009. =head1 NAME
  1010. Time::ParseDate -- date parsing both relative and absolute
  1011. =head1 SYNOPSIS
  1012. use Time::ParseDate;
  1013. $seconds_since_jan1_1970 = parsedate("12/11/94 2pm", NO_RELATIVE => 1)
  1014. $seconds_since_jan1_1970 = parsedate("12/11/94 2pm", %options)
  1015. =head1 OPTIONS
  1016. Date parsing can also use options. The options are as follows:
  1017. FUZZY -> it's okay not to parse the entire date string
  1018. NOW -> the "current" time for relative times (defaults to time())
  1019. ZONE -> local timezone (defaults to $ENV{TZ})
  1020. WHOLE -> the whole input string must be parsed
  1021. GMT -> input time is assumed to be GMT, not localtime
  1022. UK -> prefer UK style dates (dd/mm over mm/dd)
  1023. DATE_REQUIRED -> do not default the date
  1024. TIME_REQUIRED -> do not default the time
  1025. NO_RELATIVE -> input time is not relative to NOW
  1026. TIMEFIRST -> try parsing time before date [not default]
  1027. PREFER_PAST -> when year or day of week is ambigueous, assume past
  1028. PREFER_FUTURE -> when year or day of week is ambigueous, assume future
  1029. SUBSECOND -> parse fraction seconds
  1030. VALIDATE -> only accept normal values for HHMMSS, YYMMDD. Otherwise
  1031. days like -1 might give the last day of the previous month.
  1032. =head1 DATE FORMATS RECOGNIZED
  1033. =head2 Absolute date formats
  1034. Dow, dd Mon yy
  1035. Dow, dd Mon yyyy
  1036. Dow, dd Mon
  1037. dd Mon yy
  1038. dd Mon yyyy
  1039. Month day{st,nd,rd,th}, year
  1040. Month day{st,nd,rd,th}
  1041. Mon dd yyyy
  1042. yyyy/mm/dd
  1043. yyyy-mm-dd (usually the best date specification syntax)
  1044. yyyy/mm
  1045. mm/dd/yy
  1046. mm/dd/yyyy
  1047. mm/yy
  1048. yy/mm (only if year > 12, or > 31 if UK)
  1049. yy/mm/dd (only if year > 12 and day < 32, or year > 31 if UK)
  1050. dd/mm/yy (only if UK, or an invalid mm/dd/yy or yy/mm/dd)
  1051. dd/mm/yyyy (only if UK, or an invalid mm/dd/yyyy)
  1052. dd/mm (only if UK, or an invalid mm/dd)
  1053. =head2 Relative date formats:
  1054. count "days"
  1055. count "weeks"
  1056. count "months"
  1057. count "years"
  1058. Dow "after next"
  1059. Dow "before last"
  1060. Dow (requires PREFER_PAST or PREFER_FUTURE)
  1061. "next" Dow
  1062. "tomorrow"
  1063. "today"
  1064. "yesterday"
  1065. "last" dow
  1066. "last week"
  1067. "now"
  1068. "now" "+" count units
  1069. "now" "-" count units
  1070. "+" count units
  1071. "-" count units
  1072. count units "ago"
  1073. =head2 Absolute time formats:
  1074. hh:mm:ss[.ddd]
  1075. hh:mm
  1076. hh:mm[AP]M
  1077. hh[AP]M
  1078. hhmmss[[AP]M]
  1079. "noon"
  1080. "midnight"
  1081. =head2 Relative time formats:
  1082. count "minutes" (count can be franctional "1.5" or "1 1/2")
  1083. count "seconds"
  1084. count "hours"
  1085. "+" count units
  1086. "+" count
  1087. "-" count units
  1088. "-" count
  1089. count units "ago"
  1090. =head2 Timezone formats:
  1091. [+-]dddd
  1092. GMT[+-]d+
  1093. [+-]dddd (TZN)
  1094. TZN
  1095. =head2 Special formats:
  1096. [ d]d/Mon/yyyy:hh:mm:ss [[+-]dddd]
  1097. yy/mm/dd.hh:mm
  1098. =head1 DESCRIPTION
  1099. This module recognizes the above date/time formats. Usually a
  1100. date and a time are specified. There are numerous options for
  1101. controlling what is recognized and what is not.
  1102. The return code is always the time in seconds since January 1st, 1970
  1103. or undef if it was unable to parse the time.
  1104. If a timezone is specified it must be after the time. Year specifications
  1105. can be tacked onto the end of absolute times.
  1106. If C<parsedate()> is called from array context, then it will return two
  1107. elements. On successful parses, it will return the seconds and what
  1108. remains of its input string. On unsuccessful parses, it will return
  1109. C<undef> and an error string.
  1110. =head1 EXAMPLES
  1111. $seconds = parsedate("Mon Jan 2 04:24:27 1995");
  1112. $seconds = parsedate("Tue Apr 4 00:22:12 PDT 1995");
  1113. $seconds = parsedate("04.04.95 00:22", ZONE => PDT);
  1114. $seconds = parsedate("Jan 1 1999 11:23:34.578", SUBSECOND => 1);
  1115. $seconds = parsedate("122212 950404", ZONE => PDT, TIMEFIRST => 1);
  1116. $seconds = parsedate("+3 secs", NOW => 796978800);
  1117. $seconds = parsedate("2 months", NOW => 796720932);
  1118. $seconds = parsedate("last Tuesday");
  1119. $seconds = parsedate("Sunday before last");
  1120. ($seconds, $remaining) = parsedate("today is the day");
  1121. ($seconds, $error) = parsedate("today is", WHOLE=>1);
  1122. =head1 LICENSE
  1123. Copyright (C) 1996-2010 David Muir Sharnoff.
  1124. Copyright (C) 2011 Google, Inc.
  1125. License hereby
  1126. granted for anyone to use, modify or redistribute this module at
  1127. their own risk. Please feed useful changes back to [email protected].