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>
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++) {
321 $leap_days++ if $i % 4 == 0;
324 $today_days += 365 * ($today_year - $timestamp_year) + $leap_days;
325 return $today_days - $timestamp_days;
330 my ($date1, $date2) = @_;
334 =head2 Compare ($date1, $date2)
336 Compares two datetimes returning -1 if $date1 < $date2, 0 if equal or 1 if
341 =for html <blockquote>
355 =for html </blockquote>
359 =for html <blockquote>
363 =item -1 if $date1 < $date2, 0 if equal or 1 if $date1 > $date2
367 =for html </blockquote>
371 return DateToEpoch ($date1) <=> DateToEpoch ($date2);
379 =head2 DateToEpoch ($datetime)
381 Converts a datetime to epoch
385 =for html <blockquote>
391 Datetime to convert to an epoch
395 =for html </blockquote>
399 =for html <blockquote>
407 =for html </blockquote>
411 my $year = substr $date, 0, 4;
412 my $month = substr $date, 5, 2;
413 my $day = substr $date, 8, 2;
414 my $hour = substr $date, 11, 2;
415 my $minute = substr $date, 14, 2;
416 my $seconds = substr $date, 17, 2;
420 for (my $i = 1970; $i < $year; $i++) {
421 $days += _is_leap_year ($i) ? 366 : 365;
439 $days += $monthDays[$month - 1];
441 $days++ if _is_leap_year ($year) and $month > 2;
445 return ($days * $SECS_IN_DAY)
446 + ($hour * $SECS_IN_HOUR)
447 + ($minute * $SECS_IN_MIN)
456 =head2 EpochToDate ($epoch)
458 Converts an epoch to a datetime
462 =for html <blockquote>
468 Epoch to convert to a datetime
472 =for html </blockquote>
476 =for html <blockquote>
484 =for html </blockquote>
488 my ($month, $day, $hour, $minute, $seconds);
491 my $leapYearSecs = 366 * $SECS_IN_DAY;
492 my $yearSecs = $leapYearSecs - $SECS_IN_DAY;
495 my $amount = _is_leap_year ($year) ? $leapYearSecs : $yearSecs;
497 last if $amount > $epoch;
503 my $leapYearAdjustment = _is_leap_year ($year) ? 1 : 0;
505 if ($epoch >= (334 + $leapYearAdjustment) * $SECS_IN_DAY) {
507 $epoch -= (334 + $leapYearAdjustment) * $SECS_IN_DAY;
508 } elsif ($epoch >= (304 + $leapYearAdjustment) * $SECS_IN_DAY) {
510 $epoch -= (304 + $leapYearAdjustment) * $SECS_IN_DAY;
511 } elsif ($epoch >= (273 + $leapYearAdjustment) * $SECS_IN_DAY) {
513 $epoch -= (273 + $leapYearAdjustment) * $SECS_IN_DAY;
514 } elsif ($epoch >= (243 + $leapYearAdjustment) * $SECS_IN_DAY) {
516 $epoch -= (243 + $leapYearAdjustment) * $SECS_IN_DAY;
517 } elsif ($epoch >= (212 + $leapYearAdjustment) * $SECS_IN_DAY) {
519 $epoch -= (212 + $leapYearAdjustment) * $SECS_IN_DAY;
520 } elsif ($epoch >= (181 + $leapYearAdjustment) * $SECS_IN_DAY) {
522 $epoch -= (181 + $leapYearAdjustment) * $SECS_IN_DAY;
523 } elsif ($epoch >= (151 + $leapYearAdjustment) * $SECS_IN_DAY) {
525 $epoch -= (151 + $leapYearAdjustment) * $SECS_IN_DAY;
526 } elsif ($epoch >= (120 + $leapYearAdjustment) * $SECS_IN_DAY) {
528 $epoch -= (120 + $leapYearAdjustment) * $SECS_IN_DAY;
529 } elsif ($epoch >= (90 + $leapYearAdjustment) * $SECS_IN_DAY) {
531 $epoch -= (90 + $leapYearAdjustment) * $SECS_IN_DAY;
532 } elsif ($epoch >= (59 + $leapYearAdjustment) * $SECS_IN_DAY) {
534 $epoch -= (59 + $leapYearAdjustment) * $SECS_IN_DAY;
535 } elsif ($epoch >= 31 * $SECS_IN_DAY) {
537 $epoch -= 31 * $SECS_IN_DAY;
542 $day = int (($epoch / $SECS_IN_DAY) + 1);
543 $epoch = $epoch % $SECS_IN_DAY;
544 $hour = int ($epoch / $SECS_IN_HOUR);
545 $epoch = $epoch % $SECS_IN_HOUR;
546 $minute = int ($epoch / $SECS_IN_MIN);
547 $seconds = $epoch % $SECS_IN_MIN;
549 $day = "0$day" if $day < 10;
550 $hour = "0$hour" if $hour < 10;
551 $minute = "0$minute" if $minute < 10;
552 $seconds = "0$seconds" if $seconds < 10;
554 return "$year-$month-$day $hour:$minute:$seconds";
562 =head2 UTCTime ($epoch)
564 Converts an epoch to UTC Time
568 =for html <blockquote>
574 Epoch to convert to a datetime
578 =for html </blockquote>
582 =for html <blockquote>
590 =for html </blockquote>
594 my @localtime = localtime;
595 my ($sec, $min, $hour, $mday, $mon, $year) = gmtime (
596 DateToEpoch ($datetime) - (timegm (@localtime) - timelocal (@localtime))
602 $sec = '0' . $sec if $sec < 10;
603 $min = '0' . $min if $min < 10;
604 $hour = '0' . $hour if $hour < 10;
605 $mon = '0' . $mon if $mon < 10;
606 $mday = '0' . $mday if $mday < 10;
608 return "$year-$mon-${mday}T$hour:$min:${sec}Z";
611 sub UTC2Localtime($) {
612 my ($utcdatetime) = @_;
614 # If the field does not look like a UTC time then just return it.
615 return $utcdatetime unless $utcdatetime =~ /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/;
617 $utcdatetime =~ s/T/ /;
618 $utcdatetime =~ s/Z//;
620 my @localtime = localtime;
623 DateToEpoch ($utcdatetime) + (timegm (@localtime) - timelocal (@localtime))
627 sub FormatDate($;$) {
628 my ($date, $separator) = @_;
632 =head2 FormatDate ($date, $separator)
638 =for html <blockquote>
648 If specified, indicates that $date has separators (e.g. 2021-07-04).
652 =for html </blockquote>
656 =for html <blockquote>
660 =item Returns a date in MM/DD/YYYY format
664 =for html </blockquote>
668 unless ($separator) {
669 return substr($date, 4, 2) . '/'
670 . substr($date, 6, 2) . '/'
671 . substr($date, 0, 4);
673 return substr($date, 5, 2)
675 . substr($date, 8, 2)
677 . substr($date, 0, 4);
686 =head2 FormatTime ($time)
692 =for html <blockquote>
698 Time in in HH:MM format (24 hour format)
702 =for html </blockquote>
706 =for html <blockquote>
710 =item Time in HH:MM [Am|Pm] format
714 =for html </blockquote>
718 my $hours = substr $time, 0, 2;
719 my $minutes = substr $time, 3, 2;
720 my $AmPm = $hours > 12 ? "Pm" : "Am";
722 $hours = $hours - 12 if $hours > 12;
724 $hours = "0$hours" if length $hours == 1;
726 return "$hours:$minutes $AmPm";
736 Returns MM/DD/YYYY for $time
740 =for html <blockquote>
746 Time in Unix time format (Default: current time)
750 =for html </blockquote>
754 =for html <blockquote>
758 =item Date in MM/DD/YYYY
762 =for html </blockquote>
766 my ($year, $mon, $mday) = ymdhms $time;
768 return "$mon/$mday/$year";
771 sub SQLDatetime2UnixDatetime($) {
772 my ($sqldatetime) = @_;
776 =head2 SQLDatetime2UnixDatetime ($sqldatetime)
778 Converts an SQL formatted date to a Unix (localtime) formatted date)
782 =for html <blockquote>
788 Date and time stamp in SQL format
792 =for html </blockquote>
796 =for html <blockquote>
800 =item Returns a Unix formated date and time (a la localtime)
804 =for html </blockquote>
823 my $year = substr $sqldatetime, 0, 4;
824 my $month = substr $sqldatetime, 5, 2;
825 my $day = substr $sqldatetime, 8, 2;
826 my $time = FormatTime (substr $sqldatetime, 11);
828 return $months{$month} . " $day, $year \@ $time";
829 } # SQLDatetime2UnixDatetime
831 sub SubtractDays($$) {
832 my ($timestamp, $nbr_of_days) = @_;
836 =head2 SubtractDays ($timestamp, $nbr_of_days)
838 Subtracts $nbr_of_days from $timestamp
842 =for html <blockquote>
848 Timestamp to subtract days from
858 Number of days to subtract from $timestamp
860 =for html </blockquote>
864 =for html <blockquote>
868 =item SQL format date $nbr_of_days ago
872 =for html </blockquote>
876 my $year = substr $timestamp, 0, 4;
877 my $month = substr $timestamp, 5, 2;
878 my $day = substr $timestamp, 8, 2;
880 # We are not properly accounting for leap days but this is just a rough
882 if ($nbr_of_days > 365) {
883 $year -= int $nbr_of_days / 365;
885 $nbr_of_days = $nbr_of_days % 365;
889 my $days = julian $year, $month, $day;
891 # Subtract $nbr_of_days
892 $days -= $nbr_of_days;
894 # Compute $days_in_year
897 # Adjust if crossing year boundary
900 $days_in_year = (($year % 4) == 0) ? 366 : 365;
901 $days = $days_in_year + $days;
903 $days_in_year = (($year % 4) == 0) ? 366 : 365;
910 # If remaining days is less than the current month then last
911 last if ($days <= $months[$month]);
913 # Subtract off the number of days in this month
914 $days -= $months[$month++];
917 # Prefix month with 0 if necessary
920 $month = "0" . $month;
923 # Prefix days with 0 if necessary
926 } elsif ($days < 10) {
930 return $year . "-" . $month . "-" . $days . substr $timestamp, 10;
933 sub Today2SQLDatetime() {
937 =head2 Today2SQLDatetime ($datetime)
939 Returns today's date in an SQL format
943 =for html <blockquote>
951 =for html </blockquote>
955 =for html <blockquote>
959 =item SQL formated time stamp for today
963 =for html </blockquote>
967 return UnixDatetime2SQLDatetime(scalar localtime);
968 } # Today2SQLDatetime
970 sub UnixDatetime2SQLDatetime($) {
975 =head2 UnixDatetime2SQLDatetime ($datetime)
977 Converts a Unix (localtime) date/time stamp to an SQL formatted
982 =for html <blockquote>
988 Unix formated date time stamp
992 =for html </blockquote>
996 =for html <blockquote>
1000 =item SQL formated time stamp
1004 =for html </blockquote>
1008 my $orig_datetime = $datetime;
1024 # Some mailers neglect to put the leading day of the week field in.
1025 # Check for this and compensate.
1026 my $dow = substr $datetime, 0, 3;
1028 if ($dow ne 'Mon' and
1035 $datetime = 'XXX, ' . $datetime;
1038 # Some mailers have day before month. We need to correct this
1039 my $day = substr $datetime, 5, 2;
1041 if ($day =~ /\d /) {
1042 $day = '0' . (substr $day, 0, 1);
1043 $datetime = (substr $datetime, 0, 5) . $day . (substr $datetime, 6);
1046 if ($day !~ /\d\d/) {
1047 $day = substr $datetime, 8, 2;
1050 # Check for 1 digit date
1051 if ((substr $day, 0, 1) eq ' ') {
1052 $day = '0' . (substr $day, 1, 1);
1053 $datetime = (substr $datetime, 0, 8) . $day . (substr $datetime, 10);
1054 } elsif ((substr $day, 1, 1) eq ' ') {
1055 $day = '0' . (substr $day, 0, 1);
1056 $datetime = (substr $datetime, 0, 8) . $day . (substr $datetime, 9);
1059 my $year = substr $datetime, 20, 4;
1061 if ($year !~ /\d\d\d\d/) {
1062 $year = substr $datetime, 12, 4;
1063 if ($year !~ /\d\d\d\d/) {
1064 $year = substr $datetime, 12, 2;
1068 # Check for 2 digit year. Argh!
1069 if (length $year == 2 or (substr $year, 2, 1) eq ' ') {
1070 $year = '20' . (substr $year, 0, 2);
1071 $datetime = (substr $datetime, 0, 12) . '20' . (substr $datetime, 12);
1074 my $month_name = substr $datetime, 4, 3;
1076 unless ($months{$month_name}) {
1077 $month_name = substr $datetime, 8, 3;
1080 my $month = $months{$month_name};
1081 my $time = substr $datetime, 11, 8;
1083 if ($time !~ /\d\d:\d\d:\d\d/) {
1084 $time = substr $datetime, 17, 8
1088 warning "Year undefined for $orig_datetime\nReturning today's date";
1089 return Today2SQLDatetime;
1093 warning "Month undefined for $orig_datetime\nReturning today's date";
1094 return Today2SQLDatetime;
1098 warning "Day undefined for $orig_datetime\nReturning today's date";
1099 return Today2SQLDatetime;
1103 warning "Time undefined for $orig_datetime\nReturning today's date";
1104 return Today2SQLDatetime;
1107 return "$year-$month-$day $time";
1108 } # UnixDatetime2SQLDatetime
1117 Returns the YMD in a format of YYYYMMDD
1121 =for html <blockquote>
1127 Time to convert to YYYYMMDD (Default: Current time)
1131 =for html </blockquote>
1135 =for html <blockquote>
1139 =item Date in YYYYMMDD format
1143 =for html </blockquote>
1147 my ($year, $mon, $mday) = ymdhms $time;
1149 return "$year$mon$mday";
1157 =head2 YMDHM ($time)
1159 Returns the YMD in a format of YYYYMMDD@HH:MM
1163 =for html <blockquote>
1169 Time to convert to YYYYMMDD@HH:MM (Default: Current time)
1173 =for html </blockquote>
1177 =for html <blockquote>
1181 =item Date in YYYYMMDD@HH:MM format
1185 =for html </blockquote>
1189 my ($year, $mon, $mday, $hour, $min) = ymdhms $time;
1191 return "$year$mon$mday\@$hour:$min";
1199 =head2 YMDHMS ($time)
1201 Returns the YMD in a format of YYYYMMDD@HH:MM:SS
1205 =for html <blockquote>
1211 Time to convert to YYYYMMDD@HH:MM:SS (Default: Current time)
1215 =for html </blockquote>
1219 =for html <blockquote>
1223 =item Date in YYYYMMDD@HH:MM:SS format
1227 =for html </blockquote>
1231 my ($year, $mon, $mday, $hour, $min, $sec) = ymdhms $time;
1233 return "$year$mon$mday\@$hour:$min:$sec";
1241 =head2 timestamp ($time)
1243 Returns the YMD in a format of YYYYMMDD_HHMM
1247 =for html <blockquote>
1253 Time to convert to YYYYMMDD_HHMMSS (Default: Current time)
1255 =for html </blockquote>
1259 =for html <blockquote>
1263 =item Date in YYYYMMDD_HHMMSS format
1267 =for html </blockquote>
1271 my ($year, $mon, $mday, $hour, $min, $sec) = ymdhms $time;
1273 return "$year$mon${mday}_$hour$min$sec";
1282 =for html <p><a href="/php/scm_man.php?file=lib/Display.pm">Display</a></p>
1284 =head1 INCOMPATABILITIES
1288 =head1 BUGS AND LIMITATIONS
1290 There are no known bugs in this module.
1292 Please report problems to Andrew DeFaria <Andrew@ClearSCM.com>.
1294 =head1 LICENSE AND COPYRIGHT
1296 This Perl Module is freely available; you can redistribute it and/or
1297 modify it under the terms of the GNU General Public License as
1298 published by the Free Software Foundation; either version 2 of the
1299 License, or (at your option) any later version.
1301 This Perl Module is distributed in the hope that it will be useful,
1302 but WITHOUT ANY WARRANTY; without even the implied warranty of
1303 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1304 General Public License (L<http://www.gnu.org/copyleft/gpl.html>) for more
1307 You should have received a copy of the GNU General Public License
1308 along with this Perl Module; if not, write to the Free Software Foundation,
1309 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.