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 $hours = "0$hours" if length $hours == 1;
717 return "$hours:$minutes $AmPm";
727 Returns MM/DD/YYYY for $time
731 =for html <blockquote>
737 Time in Unix time format (Default: current time)
741 =for html </blockquote>
745 =for html <blockquote>
749 =item Date in MM/DD/YYYY
753 =for html </blockquote>
757 my ($year, $mon, $mday) = ymdhms $time;
759 return "$mon/$mday/$year";
762 sub SQLDatetime2UnixDatetime ($) {
763 my ($sqldatetime) = @_;
767 =head2 SQLDatetime2UnixDatetime ($sqldatetime)
769 Converts an SQL formatted date to a Unix (localtime) formatted date)
773 =for html <blockquote>
779 Date and time stamp in SQL format
783 =for html </blockquote>
787 =for html <blockquote>
791 =item Returns a Unix formated date and time (a la localtime)
795 =for html </blockquote>
814 my $year = substr $sqldatetime, 0, 4;
815 my $month = substr $sqldatetime, 5, 2;
816 my $day = substr $sqldatetime, 8, 2;
817 my $time = FormatTime (substr $sqldatetime, 11);
819 return $months{$month} . " $day, $year \@ $time";
820 } # SQLDatetime2UnixDatetime
822 sub SubtractDays ($$) {
823 my ($timestamp, $nbr_of_days) = @_;
827 =head2 SubtractDays ($timestamp, $nbr_of_days)
829 Subtracts $nbr_of_days from $timestamp
833 =for html <blockquote>
839 Timestamp to subtract days from
849 Number of days to subtract from $timestamp
851 =for html </blockquote>
855 =for html <blockquote>
859 =item SQL format date $nbr_of_days ago
863 =for html </blockquote>
867 my $year = substr $timestamp, 0, 4;
868 my $month = substr $timestamp, 5, 2;
869 my $day = substr $timestamp, 8, 2;
872 my $days = julian $year, $month, $day;
874 # Subtract $nbr_of_days
875 $days -= $nbr_of_days;
877 # Compute $days_in_year
880 # Adjust if crossing year boundary
883 $days_in_year = (($year % 4) == 0) ? 366 : 365;
884 $days = $days_in_year + $days;
886 $days_in_year = (($year % 4) == 0) ? 366 : 365;
893 # If remaining days is less than the current month then last
894 last if ($days <= $months[$month]);
896 # Subtract off the number of days in this month
897 $days -= $months[$month++];
900 # Prefix month with 0 if necessary
903 $month = "0" . $month;
906 # Prefix days with 0 if necessary
909 } elsif ($days < 10) {
913 return $year . "-" . $month . "-" . $days . substr $timestamp, 10;
916 sub Today2SQLDatetime () {
920 =head2 Today2SQLDatetime ($datetime)
922 Returns today's date in an SQL format
926 =for html <blockquote>
934 =for html </blockquote>
938 =for html <blockquote>
942 =item SQL formated time stamp for today
946 =for html </blockquote>
950 return UnixDatetime2SQLDatetime (scalar (localtime));
951 } # Today2SQLDatetime
953 sub UnixDatetime2SQLDatetime ($) {
958 =head2 UnixDatetime2SQLDatetime ($datetime)
960 Converts a Unix (localtime) date/time stamp to an SQL formatted
965 =for html <blockquote>
971 Unix formated date time stamp
975 =for html </blockquote>
979 =for html <blockquote>
983 =item SQL formated time stamp
987 =for html </blockquote>
991 my $orig_datetime = $datetime;
1007 # Some mailers neglect to put the leading day of the week field in.
1008 # Check for this and compensate.
1009 my $dow = substr $datetime, 0, 3;
1011 if ($dow ne 'Mon' and
1018 $datetime = 'XXX, ' . $datetime;
1021 # Some mailers have day before month. We need to correct this
1022 my $day = substr $datetime, 5, 2;
1024 if ($day =~ /\d /) {
1025 $day = '0' . (substr $day, 0, 1);
1026 $datetime = (substr $datetime, 0, 5) . $day . (substr $datetime, 6);
1029 if ($day !~ /\d\d/) {
1030 $day = substr $datetime, 8, 2;
1033 # Check for 1 digit date
1034 if ((substr $day, 0, 1) eq ' ') {
1035 $day = '0' . (substr $day, 1, 1);
1036 $datetime = (substr $datetime, 0, 8) . $day . (substr $datetime, 10);
1037 } elsif ((substr $day, 1, 1) eq ' ') {
1038 $day = '0' . (substr $day, 0, 1);
1039 $datetime = (substr $datetime, 0, 8) . $day . (substr $datetime, 9);
1042 my $year = substr $datetime, 20, 4;
1044 if ($year !~ /\d\d\d\d/) {
1045 $year = substr $datetime, 12, 4;
1046 if ($year !~ /\d\d\d\d/) {
1047 $year = substr $datetime, 12, 2;
1051 # Check for 2 digit year. Argh!
1052 if (length $year == 2 or (substr $year, 2, 1) eq ' ') {
1053 $year = '20' . (substr $year, 0, 2);
1054 $datetime = (substr $datetime, 0, 12) . '20' . (substr $datetime, 12);
1057 my $month_name = substr $datetime, 4, 3;
1059 unless ($months{$month_name}) {
1060 $month_name = substr $datetime, 8, 3;
1063 my $month = $months{$month_name};
1064 my $time = substr $datetime, 11, 8;
1066 if ($time !~ /\d\d:\d\d:\d\d/) {
1067 $time = substr $datetime, 17, 8
1071 warning "Year undefined for $orig_datetime\nReturning today's date";
1072 return Today2SQLDatetime;
1076 warning "Month undefined for $orig_datetime\nReturning today's date";
1077 return Today2SQLDatetime;
1081 warning "Day undefined for $orig_datetime\nReturning today's date";
1082 return Today2SQLDatetime;
1086 warning "Time undefined for $orig_datetime\nReturning today's date";
1087 return Today2SQLDatetime;
1090 return "$year-$month-$day $time";
1091 } # UnixDatetime2SQLDatetime
1100 Returns the YMD in a format of YYYYMMDD
1104 =for html <blockquote>
1110 Time to convert to YYYYMMDD (Default: Current time)
1114 =for html </blockquote>
1118 =for html <blockquote>
1122 =item Date in YYYYMMDD format
1126 =for html </blockquote>
1130 my ($year, $mon, $mday) = ymdhms $time;
1132 return "$year$mon$mday";
1140 =head2 YMDHM ($time)
1142 Returns the YMD in a format of YYYYMMDD@HH:MM
1146 =for html <blockquote>
1152 Time to convert to YYYYMMDD@HH:MM (Default: Current time)
1156 =for html </blockquote>
1160 =for html <blockquote>
1164 =item Date in YYYYMMDD@HH:MM format
1168 =for html </blockquote>
1172 my ($year, $mon, $mday, $hour, $min) = ymdhms $time;
1174 return "$year$mon$mday\@$hour:$min";
1182 =head2 YMDHMS ($time)
1184 Returns the YMD in a format of YYYYMMDD@HH:MM:SS
1188 =for html <blockquote>
1194 Time to convert to YYYYMMDD@HH:MM:SS (Default: Current time)
1198 =for html </blockquote>
1202 =for html <blockquote>
1206 =item Date in YYYYMMDD@HH:MM:SS format
1210 =for html </blockquote>
1214 my ($year, $mon, $mday, $hour, $min, $sec) = ymdhms $time;
1216 return "$year$mon$mday\@$hour:$min:$sec";
1219 sub timestamp (;$) {
1224 =head2 timestamp ($time)
1226 Returns the YMD in a format of YYYYMMDD_HHMM
1230 =for html <blockquote>
1236 Time to convert to YYYYMMDD_HHMMSS (Default: Current time)
1238 =for html </blockquote>
1242 =for html <blockquote>
1246 =item Date in YYYYMMDD_HHMMSS format
1250 =for html </blockquote>
1254 my ($year, $mon, $mday, $hour, $min, $sec) = ymdhms $time;
1256 return "$year$mon${mday}_$hour$min$sec";
1265 =for html <p><a href="/php/scm_man.php?file=lib/Display.pm">Display</a></p>
1267 =head1 INCOMPATABILITIES
1271 =head1 BUGS AND LIMITATIONS
1273 There are no known bugs in this module.
1275 Please report problems to Andrew DeFaria <Andrew@ClearSCM.com>.
1277 =head1 LICENSE AND COPYRIGHT
1279 This Perl Module is freely available; you can redistribute it and/or
1280 modify it under the terms of the GNU General Public License as
1281 published by the Free Software Foundation; either version 2 of the
1282 License, or (at your option) any later version.
1284 This Perl Module is distributed in the hope that it will be useful,
1285 but WITHOUT ANY WARRANTY; without even the implied warranty of
1286 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1287 General Public License (L<http://www.gnu.org/copyleft/gpl.html>) for more
1290 You should have received a copy of the GNU General Public License
1291 along with this Perl Module; if not, write to the Free Software Foundation,
1292 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.