3 =head1 NAME $RCSfile: DateUtils.pm,v $
5 Simple date/time utilities
13 Andrew DeFaria <Andrew@ClearSCM.com>
21 Thu Jan 5 11:06:49 PST 2006
25 $Date: 2013/02/21 05:01:17 $
31 Simple date and time utilities for often used date/time functionality.
35 my $timestamp = timestamp;
39 Often you just want to simply and quickly get date or date and time in
40 a YMD or YMDHM format. Note the YMDHM format defined here is YMD\@H:M
41 and is not well suited for a filename. The timestamp routine returns
46 The following routines are exported:
72 SQLDatetime2UnixDatetime
75 UnixDatetime2SQLDatetime
100 my $SECS_IN_MIN = 60;
101 my $SECS_IN_HOUR = $SECS_IN_MIN * 60;
102 my $SECS_IN_DAY = $SECS_IN_HOUR * 24;
105 sub Today2SQLDatetime ();
124 ) = localtime ($time);
132 # Zero preface month, day, hour and minute
133 $mon = '0' . $mon if $mon < 10;
134 $mday = '0' . $mday if $mday < 10;
135 $hour = '0' . $hour if $hour < 10;
136 $min = '0' . $min if $min < 10;
137 $sec = '0' . $sec if $sec < 10;
139 return $year, $mon, $mday, $hour, $min, $sec;
143 my ($year, $month, $day) = @_;
149 last if $m >= $month;
157 sub _is_leap_year ($) {
160 return 0 if $year % 4;
161 return 1 if $year % 100;
162 return 0 if $year % 400;
168 my ($datetime, %parms) = @_;
172 =head2 Add ($datetime, %parms)
178 =for html <blockquote>
184 Datetime in SQLDatetime format to manipulate.
188 Hash of parms. Acceptable values are of the following format:
196 Note that month will simply increment the month number, adjusting for overflow
197 of year if appropriate. Therefore a date of 2/28/2001 would increase by 1 month
198 to yield 3/28/2001. And, unfortunately, an increase of 1 month to 1/30/2011
199 would incorrectly yeild 2/30/2011!
203 =for html </blockquote>
207 =for html <blockquote>
215 =for html </blockquote>
227 foreach (keys %parms) {
228 unless (InArray ($_, @validKeys)) {
229 croak "Invalid key in DateUtils::Add: $_";
233 my $epochTime = DateToEpoch $datetime;
237 $parms{seconds} ||= 0;
238 $parms{minutes} ||= 0;
242 $amount += $parms{days} * $SECS_IN_DAY;
243 $amount += $parms{hours} * $SECS_IN_HOUR;
244 $amount += $parms{minutes} * $SECS_IN_MIN;
245 $amount += $parms{seconds};
247 $epochTime += $amount;
249 $datetime = EpochToDate $epochTime;
252 my $years = $parms{month} / 12;
253 my $months = $parms{month} % 12;
255 my $month = substr $datetime, 5, 2;
257 $years += ($month + $months) / 12;
258 substr ($datetime, 5, 2) = ($month + $months) % 12;
260 substr ($datetime, 0, 4) = substr ($datetime, 0, 4) + $years;
267 my ($timestamp) = @_;
271 =head2 Age ($timestamp)
273 Determines how old something is given a timestamp
277 =for html <blockquote>
283 Timestamp to age from (Assumed to be earlier than today)
287 =for html </blockquote>
291 =for html <blockquote>
295 =item Number of days between $timestamp and today
299 =for html </blockquote>
303 my $today = Today2SQLDatetime;
304 my $today_year = substr $today, 0, 4;
305 my $month = substr $today, 5, 2;
306 my $day = substr $today, 8, 2;
307 my $today_days = julian $today_year, $month, $day;
309 my $timestamp_year = substr $timestamp, 0, 4;
310 $month = substr $timestamp, 5, 2;
311 $day = substr $timestamp, 8, 2;
312 my $timestamp_days = julian $timestamp_year, $month, $day;
314 if ($timestamp_year > $today_year or
315 ($timestamp_days > $today_days and $timestamp_year == $today_year)) {
320 for (my $i = $timestamp_year; $i < $today_year; $i++) {
322 $leap_days++ if $i % 4 == 0;
325 $today_days += 365 * ($today_year - $timestamp_year) + $leap_days;
326 return $today_days - $timestamp_days;
331 my ($date1, $date2) = @_;
335 =head2 Compare ($date2, $date2)
337 Compares two datetimes returning -1 if $date1 < $date2, 0 if equal or 1 if
342 =for html <blockquote>
356 =for html </blockquote>
360 =for html <blockquote>
364 =item -1 if $date1 < $date2, 0 if equal or 1 if $date1 > $date2
368 =for html </blockquote>
372 return DateToEpoch ($date1) <=> DateToEpoch ($date2);
375 sub DateToEpoch ($) {
380 =head2 DateToEpoch ($datetime)
382 Converts a datetime to epoch
386 =for html <blockquote>
392 Datetime to convert to an epoch
396 =for html </blockquote>
400 =for html <blockquote>
408 =for html </blockquote>
412 my $year = substr $date, 0, 4;
413 my $month = substr $date, 5, 2;
414 my $day = substr $date, 8, 2;
415 my $hour = substr $date, 11, 2;
416 my $minute = substr $date, 14, 2;
417 my $seconds = substr $date, 17, 2;
421 for (my $i = 1970; $i < $year; $i++) {
422 $days += _is_leap_year ($i) ? 366 : 365;
440 $days += $monthDays[$month - 1];
443 if _is_leap_year ($year) and $month > 2;
447 return ($days * $SECS_IN_DAY)
448 + ($hour * $SECS_IN_HOUR)
449 + ($minute * $SECS_IN_MIN)
453 sub EpochToDate ($) {
458 =head2 EpochToDate ($epoch)
460 Converts an epoch to a datetime
464 =for html <blockquote>
470 Epoch to convert to a datetime
474 =for html </blockquote>
478 =for html <blockquote>
486 =for html </blockquote>
491 my ($month, $day, $hour, $minute, $seconds);
492 my $leapYearSecs = 366 * $SECS_IN_DAY;
493 my $yearSecs = $leapYearSecs - $SECS_IN_DAY;
496 my $amount = _is_leap_year ($year) ? $leapYearSecs : $yearSecs;
505 my $leapYearAdjustment = _is_leap_year ($year) ? 1 : 0;
507 if ($epoch >= (334 + $leapYearAdjustment) * $SECS_IN_DAY) {
509 $epoch -= (334 + $leapYearAdjustment) * $SECS_IN_DAY;
510 } elsif ($epoch >= (304 + $leapYearAdjustment) * $SECS_IN_DAY) {
512 $epoch -= (304 + $leapYearAdjustment) * $SECS_IN_DAY;
513 } elsif ($epoch >= (273 + $leapYearAdjustment) * $SECS_IN_DAY) {
515 $epoch -= (273 + $leapYearAdjustment) * $SECS_IN_DAY;
516 } elsif ($epoch >= (243 + $leapYearAdjustment) * $SECS_IN_DAY) {
518 $epoch -= (243 + $leapYearAdjustment) * $SECS_IN_DAY;
519 } elsif ($epoch >= (212 + $leapYearAdjustment) * $SECS_IN_DAY) {
521 $epoch -= (212 + $leapYearAdjustment) * $SECS_IN_DAY;
522 } elsif ($epoch >= (181 + $leapYearAdjustment) * $SECS_IN_DAY) {
524 $epoch -= (181 + $leapYearAdjustment) * $SECS_IN_DAY;
525 } elsif ($epoch >= (151 + $leapYearAdjustment) * $SECS_IN_DAY) {
527 $epoch -= (151 + $leapYearAdjustment) * $SECS_IN_DAY;
528 } elsif ($epoch >= (120 + $leapYearAdjustment) * $SECS_IN_DAY) {
530 $epoch -= (120 + $leapYearAdjustment) * $SECS_IN_DAY;
531 } elsif ($epoch >= (90 + $leapYearAdjustment) * $SECS_IN_DAY) {
533 $epoch -= (90 + $leapYearAdjustment) * $SECS_IN_DAY;
534 } elsif ($epoch >= (59 + $leapYearAdjustment) * $SECS_IN_DAY) {
536 $epoch -= (59 + $leapYearAdjustment) * $SECS_IN_DAY;
537 } elsif ($epoch >= 31 * $SECS_IN_DAY) {
539 $epoch -= 31 * $SECS_IN_DAY;
544 $day = int (($epoch / $SECS_IN_DAY) + 1);
545 $epoch = $epoch % $SECS_IN_DAY;
546 $hour = int ($epoch / $SECS_IN_HOUR);
547 $epoch = $epoch % $SECS_IN_HOUR;
548 $minute = int ($epoch / $SECS_IN_MIN);
549 $seconds = $epoch % $SECS_IN_MIN;
551 $day = "0$day" if $day < 10;
552 $hour = "0$hour" if $hour < 10;
553 $minute = "0$minute" if $minute < 10;
554 $seconds = "0$seconds" if $seconds < 10;
556 return "$year-$month-$day $hour:$minute:$seconds";
564 =head2 UTCTime ($epoch)
566 Converts an epoch to UTC Time
570 =for html <blockquote>
576 Epoch to convert to a datetime
580 =for html </blockquote>
584 =for html <blockquote>
592 =for html </blockquote>
596 my @localtime = localtime;
597 my ($sec, $min, $hour, $mday, $mon, $year) = gmtime (
598 DateToEpoch ($datetime) - (timegm (@localtime) - timelocal (@localtime))
604 $sec = '0' . $sec if $sec < 10;
605 $min = '0' . $min if $min < 10;
606 $hour = '0' . $hour if $hour < 10;
607 $mon = '0' . $mon if $mon < 10;
608 $mday = '0' . $mday if $mday < 10;
610 return "$year-$mon-${mday}T$hour:$min:${sec}Z";
613 sub UTC2Localtime ($) {
614 my ($utcdatetime) = @_;
616 # If the field does not look like a UTC time then just return it.
617 return $utcdatetime unless $utcdatetime =~ /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/;
619 $utcdatetime =~ s/T/ /;
620 $utcdatetime =~ s/Z//;
622 my @localtime = localtime;
625 DateToEpoch ($utcdatetime) + (timegm (@localtime) - timelocal (@localtime))
634 =head2 FormatDate ($date)
640 =for html <blockquote>
650 =for html </blockquote>
654 =for html <blockquote>
658 =item Returns a date in MM/DD/YYYY format
662 =for html </blockquote>
666 return substr ($date, 4, 2)
668 . substr ($date, 6, 2)
670 . substr ($date, 0, 4);
678 =head2 FormatTime ($time)
684 =for html <blockquote>
690 Time in in HH:MM format (24 hour format)
694 =for html </blockquote>
698 =for html <blockquote>
702 =item Time in HH:MM [Am|Pm] format
706 =for html </blockquote>
710 my $hours = substr $time, 0, 2;
711 my $minutes = substr $time, 3, 2;
712 my $AmPm = $hours > 12 ? "Pm" : "Am";
714 $hours = $hours - 12 if $hours > 12;
716 $hours = "0$hours" if length $hours == 1;
718 return "$hours:$minutes $AmPm";
728 Returns MM/DD/YYYY for $time
732 =for html <blockquote>
738 Time in Unix time format (Default: current time)
742 =for html </blockquote>
746 =for html <blockquote>
750 =item Date in MM/DD/YYYY
754 =for html </blockquote>
758 my ($year, $mon, $mday) = ymdhms $time;
760 return "$mon/$mday/$year";
763 sub SQLDatetime2UnixDatetime ($) {
764 my ($sqldatetime) = @_;
768 =head2 SQLDatetime2UnixDatetime ($sqldatetime)
770 Converts an SQL formatted date to a Unix (localtime) formatted date)
774 =for html <blockquote>
780 Date and time stamp in SQL format
784 =for html </blockquote>
788 =for html <blockquote>
792 =item Returns a Unix formated date and time (a la localtime)
796 =for html </blockquote>
815 my $year = substr $sqldatetime, 0, 4;
816 my $month = substr $sqldatetime, 5, 2;
817 my $day = substr $sqldatetime, 8, 2;
818 my $time = FormatTime (substr $sqldatetime, 11);
820 return $months{$month} . " $day, $year \@ $time";
821 } # SQLDatetime2UnixDatetime
823 sub SubtractDays ($$) {
824 my ($timestamp, $nbr_of_days) = @_;
828 =head2 SubtractDays ($timestamp, $nbr_of_days)
830 Subtracts $nbr_of_days from $timestamp
834 =for html <blockquote>
840 Timestamp to subtract days from
850 Number of days to subtract from $timestamp
852 =for html </blockquote>
856 =for html <blockquote>
860 =item SQL format date $nbr_of_days ago
864 =for html </blockquote>
868 my $year = substr $timestamp, 0, 4;
869 my $month = substr $timestamp, 5, 2;
870 my $day = substr $timestamp, 8, 2;
873 my $days = julian $year, $month, $day;
875 # Subtract $nbr_of_days
876 $days -= $nbr_of_days;
878 # Compute $days_in_year
881 # Adjust if crossing year boundary
884 $days_in_year = (($year % 4) == 0) ? 366 : 365;
885 $days = $days_in_year + $days;
887 $days_in_year = (($year % 4) == 0) ? 366 : 365;
894 # If remaining days is less than the current month then last
895 last if ($days <= $months[$month]);
897 # Subtract off the number of days in this month
898 $days -= $months[$month++];
901 # Prefix month with 0 if necessary
904 $month = "0" . $month;
907 # Prefix days with 0 if necessary
910 } elsif ($days < 10) {
914 return $year . "-" . $month . "-" . $days . substr $timestamp, 10;
917 sub Today2SQLDatetime () {
921 =head2 Today2SQLDatetime ($datetime)
923 Returns today's date in an SQL format
927 =for html <blockquote>
935 =for html </blockquote>
939 =for html <blockquote>
943 =item SQL formated time stamp for today
947 =for html </blockquote>
951 return UnixDatetime2SQLDatetime (scalar (localtime));
952 } # Today2SQLDatetime
954 sub UnixDatetime2SQLDatetime ($) {
959 =head2 UnixDatetime2SQLDatetime ($datetime)
961 Converts a Unix (localtime) date/time stamp to an SQL formatted
966 =for html <blockquote>
972 Unix formated date time stamp
976 =for html </blockquote>
980 =for html <blockquote>
984 =item SQL formated time stamp
988 =for html </blockquote>
992 my $orig_datetime = $datetime;
1008 # Some mailers neglect to put the leading day of the week field in.
1009 # Check for this and compensate.
1010 my $dow = substr $datetime, 0, 3;
1012 if ($dow ne 'Mon' and
1019 $datetime = 'XXX, ' . $datetime;
1022 # Some mailers have day before month. We need to correct this
1023 my $day = substr $datetime, 5, 2;
1025 if ($day =~ /\d /) {
1026 $day = '0' . (substr $day, 0, 1);
1027 $datetime = (substr $datetime, 0, 5) . $day . (substr $datetime, 6);
1030 if ($day !~ /\d\d/) {
1031 $day = substr $datetime, 8, 2;
1034 # Check for 1 digit date
1035 if ((substr $day, 0, 1) eq ' ') {
1036 $day = '0' . (substr $day, 1, 1);
1037 $datetime = (substr $datetime, 0, 8) . $day . (substr $datetime, 10);
1038 } elsif ((substr $day, 1, 1) eq ' ') {
1039 $day = '0' . (substr $day, 0, 1);
1040 $datetime = (substr $datetime, 0, 8) . $day . (substr $datetime, 9);
1043 my $year = substr $datetime, 20, 4;
1045 if ($year !~ /\d\d\d\d/) {
1046 $year = substr $datetime, 12, 4;
1047 if ($year !~ /\d\d\d\d/) {
1048 $year = substr $datetime, 12, 2;
1052 # Check for 2 digit year. Argh!
1053 if (length $year == 2 or (substr $year, 2, 1) eq ' ') {
1054 $year = '20' . (substr $year, 0, 2);
1055 $datetime = (substr $datetime, 0, 12) . '20' . (substr $datetime, 12);
1058 my $month_name = substr $datetime, 4, 3;
1060 unless ($months{$month_name}) {
1061 $month_name = substr $datetime, 8, 3;
1064 my $month = $months{$month_name};
1065 my $time = substr $datetime, 11, 8;
1067 if ($time !~ /\d\d:\d\d:\d\d/) {
1068 $time = substr $datetime, 17, 8
1072 warning "Year undefined for $orig_datetime\nReturning today's date";
1073 return Today2SQLDatetime;
1077 warning "Month undefined for $orig_datetime\nReturning today's date";
1078 return Today2SQLDatetime;
1082 warning "Day undefined for $orig_datetime\nReturning today's date";
1083 return Today2SQLDatetime;
1087 warning "Time undefined for $orig_datetime\nReturning today's date";
1088 return Today2SQLDatetime;
1091 return "$year-$month-$day $time";
1092 } # UnixDatetime2SQLDatetime
1101 Returns the YMD in a format of YYYYMMDD
1105 =for html <blockquote>
1111 Time to convert to YYYYMMDD (Default: Current time)
1115 =for html </blockquote>
1119 =for html <blockquote>
1123 =item Date in YYYYMMDD format
1127 =for html </blockquote>
1131 my ($year, $mon, $mday) = ymdhms $time;
1133 return "$year$mon$mday";
1141 =head2 YMDHM ($time)
1143 Returns the YMD in a format of YYYYMMDD@HH:MM
1147 =for html <blockquote>
1153 Time to convert to YYYYMMDD@HH:MM (Default: Current time)
1157 =for html </blockquote>
1161 =for html <blockquote>
1165 =item Date in YYYYMMDD@HH:MM format
1169 =for html </blockquote>
1173 my ($year, $mon, $mday, $hour, $min) = ymdhms $time;
1175 return "$year$mon$mday\@$hour:$min";
1183 =head2 YMDHMS ($time)
1185 Returns the YMD in a format of YYYYMMDD@HH:MM:SS
1189 =for html <blockquote>
1195 Time to convert to YYYYMMDD@HH:MM:SS (Default: Current time)
1199 =for html </blockquote>
1203 =for html <blockquote>
1207 =item Date in YYYYMMDD@HH:MM:SS format
1211 =for html </blockquote>
1215 my ($year, $mon, $mday, $hour, $min, $sec) = ymdhms $time;
1217 return "$year$mon$mday\@$hour:$min:$sec";
1220 sub timestamp (;$) {
1225 =head2 timestamp ($time)
1227 Returns the YMD in a format of YYYYMMDD_HHMM
1231 =for html <blockquote>
1237 Time to convert to YYYYMMDD_HHMMSS (Default: Current time)
1239 =for html </blockquote>
1243 =for html <blockquote>
1247 =item Date in YYYYMMDD_HHMMSS format
1251 =for html </blockquote>
1255 my ($year, $mon, $mday, $hour, $min, $sec) = ymdhms $time;
1257 return "$year$mon${mday}_$hour$min$sec";
1266 =for html <p><a href="/php/scm_man.php?file=lib/Display.pm">Display</a></p>
1268 =head1 INCOMPATABILITIES
1272 =head1 BUGS AND LIMITATIONS
1274 There are no known bugs in this module.
1276 Please report problems to Andrew DeFaria <Andrew@ClearSCM.com>.
1278 =head1 LICENSE AND COPYRIGHT
1280 This Perl Module is freely available; you can redistribute it and/or
1281 modify it under the terms of the GNU General Public License as
1282 published by the Free Software Foundation; either version 2 of the
1283 License, or (at your option) any later version.
1285 This Perl Module is distributed in the hope that it will be useful,
1286 but WITHOUT ANY WARRANTY; without even the implied warranty of
1287 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1288 General Public License (L<http://www.gnu.org/copyleft/gpl.html>) for more
1291 You should have received a copy of the GNU General Public License
1292 along with this Perl Module; if not, write to the Free Software Foundation,
1293 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.