#!/usr/bin/perl # Filename: dateCalc # Author: David Ljung Madison # See License: http://MarginalHacks.com/License/ # Description: Date math use strict; use Time::Local; ################################################## # Setup the variables ################################################## my $PROGNAME = $0; $PROGNAME =~ s|.*/||; my ($BASENAME,$PROGNAME) = ($0 =~ m|(.*)/(.+)|) ? ($1?$1:'/',$2) : ('.',$0); ################################################## # Usage ################################################## sub fatal { foreach my $msg (@_) { print STDERR "[$PROGNAME] ERROR: $msg\n"; } exit(-1); } sub usage { foreach my $msg (@_) { print STDERR "ERROR: $msg\n"; } print STDERR <[..] Performs date math -d Set debug mode -date Show result as date '12/3/71' (default if answer > 1 year) -time Show result as time '6d3h40s' (default if answer < 1 year) A date, such as 1/2/2010 or a time period, such as 10d3h Operations, currently + or - Dates can be in m/d/y or y/m/d format. Examples: % dateCalc today-6d3h2s % dateCalc 1/2/2010-12/3/1971 Note that differences may include daylight savings time, for example: % dateCalc 11/8/2010 - 11/7/2010 USAGE exit -1; } sub parseArgs { my $opt = {}; while (my $arg=shift(@ARGV)) { if ($arg =~ /^-h$/) { usage(); } if ($arg =~ /^-d$/) { $MAIN::DEBUG=1; next; } if ($arg =~ /^-date$/) { $opt->{date}=1; next; } if ($arg =~ /^-time$/) { $opt->{time}=1; next; } #if ($arg =~ /^-./) { usage("Unknown option: $arg"); } $opt->{calc}.=$arg; } usage("No dates/ops defined") unless $opt->{calc}; $opt; } sub debug { return unless $MAIN::DEBUG; foreach my $msg (@_) { print STDERR "[$PROGNAME] $msg\n"; } } my $YEAR = 365*24*60*60; sub date { my ($date) = @_; $date =~ s/^\s*//; $date =~ s/\s*$//; # Date if ($date =~ m|^(\d+)/(\d+)(?:/(\d+))?$|) { my ($m,$d,$y) = ($1,$2,$3); $y ||= 1900+(localtime(time))[5]; # Dates can be in m/d/y or y/m/d format. ($y,$m,$d) = ($m,$d,$y) if $m>$y; $m--; return timelocal(0,0,0,$d,$m,$y); } # 6d2h3s... if ($date =~ m|(\d+)[ywdmhs]|) { my $sec = 0; while ($date =~ s|^(\d+)([ywdmhs])||) { my ($num,$unit) = ($1,$2); $sec += $num if $unit eq 's'; $sec += 60*$num if $unit eq 'm'; $sec += 60*60*$num if $unit eq 'h'; $sec += 24*60*60*$num if $unit eq 'd'; $sec += 7*24*60*60*$num if $unit eq 'w'; $sec += $YEAR*$num if $unit eq 'y'; # Hacky. } usage("Couldn't understand rest of time period [$date]") if $date; return $sec; } # "today" return time if $date =~ /^(today|now)$/i; usage("Unknown date format [$date]"); } sub splitNum { my ($num,$base) = @_; my $rem = $num % $base; $num -= $rem; ($num / $base, $rem); } sub unsec_date { my ($sec) = @_; my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($sec); $mon++; $year += 1900; my $date = sprintf("%0.4d/%0.2d/%0.2d", $year,$mon,$mday); $date .= " ${hour}h" if $hour; $date .= " ${min}m" if $min; $date .= " ${sec}s" if $sec; $date; } sub unsec_time { my ($sec) = @_; my ($year,$week,$day,$hour,$min); ($min,$sec) = splitNum($sec,60); ($hour,$min) = splitNum($min,60); ($day,$hour) = splitNum($hour,24); ($year,$day) = splitNum($day,365); ($week,$day) = splitNum($day,7); my @date; push(@date, "${year}y") if $year; push(@date, "${week}w") if $week; push(@date, "${day}d") if $day; push(@date, "${hour}h") if $hour; push(@date, "${min}m") if $min; push(@date, "${sec}s") if $sec; return "0s" unless @date; join(' ',@date); } sub unsec { my ($opt,$sec) = @_; # Heuristic. It's a date if it's more than 1 year if (($sec>$YEAR && !$opt->{time}) || $opt->{date}) { print unsec_date($sec),"\n"; return unless $opt->{time}; } print unsec_time($sec),"\n"; } sub main { my $opt = parseArgs(); my @c = split(/\s*([+-])\s*/,$opt->{calc}); my $amt = date(shift(@c)); #print "START: $amt\n"; while (@c) { my ($op,$what) = (shift(@c),date(shift(@c))); usage("Op without value?") unless defined $what; $amt += $what if $op eq '+'; $amt -= $what if $op eq '-'; } unsec($opt,$amt); } main();