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_HOUR = $SECS_IN_MIN * 60;
101 my $SECS_IN_DAY = $SECS_IN_HOUR * 24;
104 sub Today2SQLDatetime ();
123 ) = localtime ($time);
131 # Zero preface month, day, hour and minute
132 $mon = '0' . $mon if $mon < 10;
133 $mday = '0' . $mday if $mday < 10;
134 $hour = '0' . $hour if $hour < 10;
135 $min = '0' . $min if $min < 10;
136 $sec = '0' . $sec if $sec < 10;
138 return $year, $mon, $mday, $hour, $min, $sec;
142 my ($year, $month, $day) = @_;
148 last if $m >= $month;
156 sub _is_leap_year ($) {
159 return 0 if $year % 4;
160 return 1 if $year % 100;
161 return 0 if $year % 400;
167 my ($datetime, %parms) = @_;
171 =head2 Add ($datetime, %parms)
177 =for html <blockquote>
183 Datetime in SQLDatetime format to manipulate.
187 Hash of parms. Acceptable values are of the following format:
195 Note that month will simply increment the month number, adjusting for overflow
196 of year if appropriate. Therefore a date of 2/28/2001 would increase by 1 month
197 to yield 3/28/2001. And, unfortunately, an increase of 1 month to 1/30/2011
198 would incorrectly yeild 2/30/2011!
202 =for html </blockquote>
206 =for html <blockquote>
214 =for html </blockquote>
226 foreach (keys %parms) {
227 unless (InArray ($_, @validKeys)) {
228 croak "Invalid key in DateUtils::Add: $_";
232 my $epochTime = DateToEpoch $datetime;
236 $parms{seconds} ||= 0;
237 $parms{minutes} ||= 0;
241 $amount += $parms{days} * $SECS_IN_DAY;
242 $amount += $parms{hours} * $SECS_IN_HOUR;
243 $amount += $parms{minutes} * $SECS_IN_MIN;
244 $amount += $parms{seconds};
246 $epochTime += $amount;
248 $datetime = EpochToDate $epochTime;
251 my $years = $parms{month} / 12;
252 my $months = $parms{month} % 12;
254 my $month = substr $datetime, 5, 2;
256 $years += ($month + $months) / 12;
257 substr ($datetime, 5, 2) = ($month + $months) % 12;
259 substr ($datetime, 0, 4) = substr ($datetime, 0, 4) + $years;
266 my ($timestamp) = @_;
270 =head2 Age ($timestamp)
272 Determines how old something is given a timestamp
276 =for html <blockquote>
282 Timestamp to age from (Assumed to be earlier than today)
286 =for html </blockquote>
290 =for html <blockquote>
294 =item Number of days between $timestamp and today
298 =for html </blockquote>
302 my $today = Today2SQLDatetime;
303 my $today_year = substr $today, 0, 4;
304 my $month = substr $today, 5, 2;
305 my $day = substr $today, 8, 2;
306 my $today_days = julian $today_year, $month, $day;
308 my $timestamp_year = substr $timestamp, 0, 4;
309 $month = substr $timestamp, 5, 2;
310 $day = substr $timestamp, 8, 2;
311 my $timestamp_days = julian $timestamp_year, $month, $day;
313 if ($timestamp_year > $today_year or
314 ($timestamp_days > $today_days and $timestamp_year == $today_year)) {
319 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 ($date2, $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);
374 sub DateToEpoch ($) {
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];
442 if _is_leap_year ($year) and $month > 2;
446 return ($days * $SECS_IN_DAY)
447 + ($hour * $SECS_IN_HOUR)
448 + ($minute * $SECS_IN_MIN)
452 sub EpochToDate ($) {
457 =head2 EpochToDate ($epoch)
459 Converts an epoch to a datetime
463 =for html <blockquote>
469 Epoch to convert to a datetime
473 =for html </blockquote>
477 =for html <blockquote>
485 =for html </blockquote>
490 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;
504 my $leapYearAdjustment = _is_leap_year ($year) ? 1 : 0;
506 if ($epoch >= (334 + $leapYearAdjustment) * $SECS_IN_DAY) {
508 $epoch -= (334 + $leapYearAdjustment) * $SECS_IN_DAY;
509 } elsif ($epoch >= (304 + $leapYearAdjustment) * $SECS_IN_DAY) {
511 $epoch -= (304 + $leapYearAdjustment) * $SECS_IN_DAY;
512 } elsif ($epoch >= (273 + $leapYearAdjustment) * $SECS_IN_DAY) {
514 $epoch -= (273 + $leapYearAdjustment) * $SECS_IN_DAY;
515 } elsif ($epoch >= (243 + $leapYearAdjustment) * $SECS_IN_DAY) {
517 $epoch -= (243 + $leapYearAdjustment) * $SECS_IN_DAY;
518 } elsif ($epoch >= (212 + $leapYearAdjustment) * $SECS_IN_DAY) {
520 $epoch -= (212 + $leapYearAdjustment) * $SECS_IN_DAY;
521 } elsif ($epoch >= (181 + $leapYearAdjustment) * $SECS_IN_DAY) {
523 $epoch -= (181 + $leapYearAdjustment) * $SECS_IN_DAY;
524 } elsif ($epoch >= (151 + $leapYearAdjustment) * $SECS_IN_DAY) {
526 $epoch -= (151 + $leapYearAdjustment) * $SECS_IN_DAY;
527 } elsif ($epoch >= (120 + $leapYearAdjustment) * $SECS_IN_DAY) {
529 $epoch -= (120 + $leapYearAdjustment) * $SECS_IN_DAY;
530 } elsif ($epoch >= (90 + $leapYearAdjustment) * $SECS_IN_DAY) {
532 $epoch -= (90 + $leapYearAdjustment) * $SECS_IN_DAY;
533 } elsif ($epoch >= (59 + $leapYearAdjustment) * $SECS_IN_DAY) {
535 $epoch -= (59 + $leapYearAdjustment) * $SECS_IN_DAY;
536 } elsif ($epoch >= 31 * $SECS_IN_DAY) {
538 $epoch -= 31 * $SECS_IN_DAY;
543 $day = int (($epoch / $SECS_IN_DAY) + 1);
544 $epoch = $epoch % $SECS_IN_DAY;
545 $hour = int ($epoch / $SECS_IN_HOUR);
546 $epoch = $epoch % $SECS_IN_HOUR;
547 $minute = int ($epoch / $SECS_IN_MIN);
548 $seconds = $epoch % $SECS_IN_MIN;
550 $day = "0$day" if $day < 10;
551 $hour = "0$hour" if $hour < 10;
552 $minute = "0$minute" if $minute < 10;
553 $seconds = "0$seconds" if $seconds < 10;
555 return "$year-$month-$day $hour:$minute:$seconds";
563 =head2 UTCTime ($epoch)
565 Converts an epoch to UTC Time
569 =for html <blockquote>
575 Epoch to convert to a datetime
579 =for html </blockquote>
583 =for html <blockquote>
591 =for html </blockquote>
595 my @localtime = localtime;
596 my ($sec, $min, $hour, $mday, $mon, $year) = gmtime (
597 DateToEpoch ($datetime) - (timegm (@localtime) - timelocal (@localtime))
603 $sec = '0' . $sec if $sec < 10;
604 $min = '0' . $min if $min < 10;
605 $hour = '0' . $hour if $hour < 10;
606 $mon = '0' . $mon if $mon < 10;
607 $mday = '0' . $mday if $mday < 10;
609 return "$year-$mon-${mday}T$hour:$min:${sec}Z";
612 sub UTC2Localtime ($) {
613 my ($utcdatetime) = @_;
615 # If the field does not look like a UTC time then just return it.
616 return $utcdatetime unless $utcdatetime =~ /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/;
618 $utcdatetime =~ s/T/ /;
619 $utcdatetime =~ s/Z//;
621 my @localtime = localtime;
624 DateToEpoch ($utcdatetime) + (timegm (@localtime) - timelocal (@localtime))
633 =head2 FormatDate ($date)
639 =for html <blockquote>
649 =for html </blockquote>
653 =for html <blockquote>
657 =item Returns a date in MM/DD/YYYY format
661 =for html </blockquote>
665 return substr ($date, 4, 2)
667 . substr ($date, 6, 2)
669 . substr ($date, 0, 4);
677 =head2 FormatTime ($time)
683 =for html <blockquote>
689 Time in in HH:MM format (24 hour format)
693 =for html </blockquote>
697 =for html <blockquote>
701 =item Time in HH:MM [Am|Pm] format
705 =for html </blockquote>
709 my $hours = substr $time, 0, 2;
710 my $minutes = substr $time, 3, 2;
711 my $AmPm = $hours > 12 ? "Pm" : "Am";
713 $hours = $hours - 12 if $hours > 12;
715 return "$hours:$minutes $AmPm";
725 Returns MM/DD/YYYY for $time
729 =for html <blockquote>
735 Time in Unix time format (Default: current time)
739 =for html </blockquote>
743 =for html <blockquote>
747 =item Date in MM/DD/YYYY
751 =for html </blockquote>
755 my ($year, $mon, $mday) = ymdhms $time;
757 return "$mon/$mday/$year";
760 sub SQLDatetime2UnixDatetime ($) {
761 my ($sqldatetime) = @_;
765 =head2 SQLDatetime2UnixDatetime ($sqldatetime)
767 Converts an SQL formatted date to a Unix (localtime) formatted date)
771 =for html <blockquote>
777 Date and time stamp in SQL format
781 =for html </blockquote>
785 =for html <blockquote>
789 =item Returns a Unix formated date and time (a la localtime)
793 =for html </blockquote>
812 my $year = substr $sqldatetime, 0, 4;
813 my $month = substr $sqldatetime, 5, 2;
814 my $day = substr $sqldatetime, 8, 2;
815 my $time = FormatTime (substr $sqldatetime, 11);
817 return $months{$month} . " $day, $year \@ $time";
818 } # SQLDatetime2UnixDatetime
820 sub SubtractDays ($$) {
821 my ($timestamp, $nbr_of_days) = @_;
825 =head2 SubtractDays ($timestamp, $nbr_of_days)
827 Subtracts $nbr_of_days from $timestamp
831 =for html <blockquote>
837 Timestamp to subtract days from
847 Number of days to subtract from $timestamp
849 =for html </blockquote>
853 =for html <blockquote>
857 =item SQL format date $nbr_of_days ago
861 =for html </blockquote>
865 my $year = substr $timestamp, 0, 4;
866 my $month = substr $timestamp, 5, 2;
867 my $day = substr $timestamp, 8, 2;
870 my $days = julian $year, $month, $day;
872 # Subtract $nbr_of_days
873 $days -= $nbr_of_days;
875 # Compute $days_in_year
878 # Adjust if crossing year boundary
881 $days_in_year = (($year % 4) == 0) ? 366 : 365;
882 $days = $days_in_year + $days;
884 $days_in_year = (($year % 4) == 0) ? 366 : 365;
891 # If remaining days is less than the current month then last
892 last if ($days <= $months[$month]);
894 # Subtract off the number of days in this month
895 $days -= $months[$month++];
898 # Prefix month with 0 if necessary
901 $month = "0" . $month;
904 # Prefix days with 0 if necessary
907 } elsif ($days < 10) {
911 return $year . "-" . $month . "-" . $days . substr $timestamp, 10;
914 sub Today2SQLDatetime () {
918 =head2 Today2SQLDatetime ($datetime)
920 Returns today's date in an SQL format
924 =for html <blockquote>
932 =for html </blockquote>
936 =for html <blockquote>
940 =item SQL formated time stamp for today
944 =for html </blockquote>
948 return UnixDatetime2SQLDatetime (scalar (localtime));
949 } # Today2SQLDatetime
951 sub UnixDatetime2SQLDatetime ($) {
956 =head2 UnixDatetime2SQLDatetime ($datetime)
958 Converts a Unix (localtime) date/time stamp to an SQL formatted
963 =for html <blockquote>
969 Unix formated date time stamp
973 =for html </blockquote>
977 =for html <blockquote>
981 =item SQL formated time stamp
985 =for html </blockquote>
989 my $orig_datetime = $datetime;
1005 # Some mailers neglect to put the leading day of the week field in.
1006 # Check for this and compensate.
1007 my $dow = substr $datetime, 0, 3;
1009 if ($dow ne 'Mon' and
1016 $datetime = 'XXX, ' . $datetime;
1019 # Some mailers have day before month. We need to correct this
1020 my $day = substr $datetime, 5, 2;
1022 if ($day =~ /\d /) {
1023 $day = '0' . (substr $day, 0, 1);
1024 $datetime = (substr $datetime, 0, 5) . $day . (substr $datetime, 6);
1027 if ($day !~ /\d\d/) {
1028 $day = substr $datetime, 8, 2;
1031 # Check for 1 digit date
1032 if ((substr $day, 0, 1) eq ' ') {
1033 $day = '0' . (substr $day, 1, 1);
1034 $datetime = (substr $datetime, 0, 8) . $day . (substr $datetime, 10);
1035 } elsif ((substr $day, 1, 1) eq ' ') {
1036 $day = '0' . (substr $day, 0, 1);
1037 $datetime = (substr $datetime, 0, 8) . $day . (substr $datetime, 9);
1040 my $year = substr $datetime, 20, 4;
1042 if ($year !~ /\d\d\d\d/) {
1043 $year = substr $datetime, 12, 4;
1044 if ($year !~ /\d\d\d\d/) {
1045 $year = substr $datetime, 12, 2;
1049 # Check for 2 digit year. Argh!
1050 if (length $year == 2 or (substr $year, 2, 1) eq ' ') {
1051 $year = '20' . (substr $year, 0, 2);
1052 $datetime = (substr $datetime, 0, 12) . '20' . (substr $datetime, 12);
1055 my $month_name = substr $datetime, 4, 3;
1057 unless ($months{$month_name}) {
1058 $month_name = substr $datetime, 8, 3;
1061 my $month = $months{$month_name};
1062 my $time = substr $datetime, 11, 8;
1064 if ($time !~ /\d\d:\d\d:\d\d/) {
1065 $time = substr $datetime, 17, 8
1069 warning "Year undefined for $orig_datetime\nReturning today's date";
1070 return Today2SQLDatetime;
1074 warning "Month undefined for $orig_datetime\nReturning today's date";
1075 return Today2SQLDatetime;
1079 warning "Day undefined for $orig_datetime\nReturning today's date";
1080 return Today2SQLDatetime;
1084 warning "Time undefined for $orig_datetime\nReturning today's date";
1085 return Today2SQLDatetime;
1088 return "$year-$month-$day $time";
1089 } # UnixDatetime2SQLDatetime
1098 Returns the YMD in a format of YYYYMMDD
1102 =for html <blockquote>
1108 Time to convert to YYYYMMDD (Default: Current time)
1112 =for html </blockquote>
1116 =for html <blockquote>
1120 =item Date in YYYYMMDD format
1124 =for html </blockquote>
1128 my ($year, $mon, $mday) = ymdhms $time;
1130 return "$year$mon$mday";
1138 =head2 YMDHM ($time)
1140 Returns the YMD in a format of YYYYMMDD@HH:MM
1144 =for html <blockquote>
1150 Time to convert to YYYYMMDD@HH:MM (Default: Current time)
1154 =for html </blockquote>
1158 =for html <blockquote>
1162 =item Date in YYYYMMDD@HH:MM format
1166 =for html </blockquote>
1170 my ($year, $mon, $mday, $hour, $min) = ymdhms $time;
1172 return "$year$mon$mday\@$hour:$min";
1180 =head2 YMDHMS ($time)
1182 Returns the YMD in a format of YYYYMMDD@HH:MM:SS
1186 =for html <blockquote>
1192 Time to convert to YYYYMMDD@HH:MM:SS (Default: Current time)
1196 =for html </blockquote>
1200 =for html <blockquote>
1204 =item Date in YYYYMMDD@HH:MM:SS format
1208 =for html </blockquote>
1212 my ($year, $mon, $mday, $hour, $min, $sec) = ymdhms $time;
1214 return "$year$mon$mday\@$hour:$min:$sec";
1217 sub timestamp (;$) {
1222 =head2 timestamp ($time)
1224 Returns the YMD in a format of YYYYMMDD_HHMM
1228 =for html <blockquote>
1234 Time to convert to YYYYMMDD_HHMMSS (Default: Current time)
1236 =for html </blockquote>
1240 =for html <blockquote>
1244 =item Date in YYYYMMDD_HHMMSS format
1248 =for html </blockquote>
1252 my ($year, $mon, $mday, $hour, $min, $sec) = ymdhms $time;
1254 return "$year$mon${mday}_$hour$min$sec";
1263 =for html <p><a href="/php/cvs_man.php?file=lib/Display.pm">Display</a></p>
1265 =head1 INCOMPATABILITIES
1269 =head1 BUGS AND LIMITATIONS
1271 There are no known bugs in this module.
1273 Please report problems to Andrew DeFaria <Andrew@ClearSCM.com>.
1275 =head1 LICENSE AND COPYRIGHT
1277 This Perl Module is freely available; you can redistribute it and/or
1278 modify it under the terms of the GNU General Public License as
1279 published by the Free Software Foundation; either version 2 of the
1280 License, or (at your option) any later version.
1282 This Perl Module is distributed in the hope that it will be useful,
1283 but WITHOUT ANY WARRANTY; without even the implied warranty of
1284 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1285 General Public License (L<http://www.gnu.org/copyleft/gpl.html>) for more
1288 You should have received a copy of the GNU General Public License
1289 along with this Perl Module; if not, write to the Free Software Foundation,
1290 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.