Code:
#!/usr/bin/perl
use POSIX;
use strict;
use warnings;
my $cmdstr="%Y-%m-%d %H:%M:%S";
my ($input, $arg, $sign, $file,$offset)=("", undef, 0, undef,0);
my $verbose=0;
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst);
my %days = ( "sun" => 0, "sunday" => 0,
"mon" => 1, "monday" => 1,
"tue" => 2, "tues" => 2, "tuesday" => 2,
"wed" => 3, "wednesday" => 3,
"thu" => 4, "thurs" => 4, "thursday" => 4,
"fri" => 5, "friday" => 5,
"sat" => 6, "saturday" => 6 );
my %month = ( "jan" => 0, "january" => 0,"feb" => 1, "february" => 1,
"mar" => 2, "march" => 2,"apr" => 3, "april" => 3,
"may" => 4,"jun" => 5, "june" => 5,
"jul" => 6, "july" => 6,"aug" => 7, "august" => 7,
"sep" => 8, "sept" => 8, "september" => 8,
"oct" => 9, "october" => 9,"nov" => 10, "november" => 10,
"dec" => 11, "december" => 11 );
sub set { # Sets the big mess of time variables from epoch input
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(shift);
}
set(time);
# Commandline parsing stuff
while(defined($arg=shift)) {
if($arg =~ /^--date=/) { $input=substr($arg, 7); }
elsif($arg eq "-v") { $verbose=1; }
elsif($arg eq "-d") { $input=shift; }
elsif($arg eq "-r") { $file=shift; }
elsif($arg =~ /^--reference=/) {$file=substr($arg, 12); }
elsif($arg =~/^\+/) { $cmdstr=substr($arg,1); }
elsif($arg =~/^-h|--help|--version$/) {
print STDERR <<"EOT";
date.pl v0.0.7, Tyler Montbriand, 2016. Free PERL date calc/converter.
Usage:
-d "time string" string like "YYYYMMDD", "YYYY/MM/DD",
"HHMMSS", "HH:MM:SS", "\@epoch", "- 3 days",
"Mar 3 2016 1:16:09 AM",
etc. You can string them together, like
"\@1343322750 - 3 days".
-r /path/to/file Show mtime of the given file, not current time.
+"formatstring" Give strftime this format string instead of the
default "%Y-%m-%d %H:%M:%S". See 'man strftime'
-v Verbose, warn when input formatting is ignored.
Examples:
date.pl # current time in YYYY-MM-DD HH:MM:SS
TZ="UTC" ./date.pl # Use an alternate time zone
date.pl +"%a %b %d %Y %r" # Like Thu Jan 16 2014 12:58:59 PM
date.pl -d "+ 3 days" # Current time plus three days
date.pl -d "\@1343322750" # exact time in epoch seconds
date.pl -d "2013/01/02 12:00:00"# exact time in YYYYMMDD HHMMSS
date.pl -r /etc/passwd # display mtime of /etc/passwd
date.pl -r /etc/passwd -d "12:00:00" # date of /etc/passwd, time of noon
EOT
exit(1);
}
else { die("unknown argument $arg, try --help"); }
}
if(defined($file)) { # stat file to get mtime
my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
$atime,$mtime,$ctime,$blksize,$blocks)
= stat($file);
defined($dev) || die("No such file $file");
set($mtime);
}
# Put the date string back into argv, split on any whitespace or grammar
unshift(@ARGV, split(/[ \r\n\t,]+/, $input));
while(defined($arg=shift))
{
# Need to split +1 into +1
if($arg =~ /^[+]/) { $sign=1; $arg=substr($arg,1); }
elsif($arg =~ /^-/) { $sign=-1; $arg=substr($arg,1); }
################## DATE FORMAT DETECTION ########################
# Month and date
if(exists($month{tolower($arg)}) && ($ARGV[0] =~ /^[0-9]+$/) )
{
$mon=$month{tolower($arg)};
$mday=shift;
$mday = $mday + 0;
next;
}
# Things like "Mon 12" are day of month
if(exists($days{tolower($arg)})) {
if($ARGV[0] =~ /^[0-9]+$/)
{
$mday=$ARGV[0] + 0;
shift;
}
next;
}
# A bare four digit number beginning with 19 or 20 is probably a year
if($arg =~ /^(19[0-9][0-9])|(2[0-9][0-9][0-9])$/)
{
$year=$arg - 1900;
next;
}
# @1234 means seconds in epoch time
if($arg =~ /^@([0-9]+)$/) { set($1+0); next; }
# Checks for YYYYMMDD or YYYY/MM/DD time
# TODO: Check for YYMMDD dates
# TODO: Check for YYYYDDMM dates (ugh)
if($arg =~ /^([0-9]{4})([\/-]?)([0-9]{2})\2([0-9]{2})/)
{
($year,$mon,$mday)=($1-1900,$3-1,$4+0);
($sec,$min,$hour)=(0,0,0);
next;
}
# HH:MM:SS times
if($arg =~ /([0-2]?[0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?$/)
{
($sec,$min,$hour)=($3+0, $2+0, $1+0);
# Handle time with PM in it
if(tolower($ARGV[0]) eq "pm")
{
shift;
if($hour >= 1) { $hour += 12; }
}
elsif(tolower($ARGV[0]) == "am")
{
shift;
if($hour == 12) { $hour -= 12; }
}
next;
}
# As last resort, assume its a pure number.
if($arg =~ /^([0-9]+)$/) {
($sign != 0) ||
die("offset without unit -- probably unrecognized format");
$offset=$1+0;
next;
}
if($arg =~ /^seconds?$/) { } # Just take seconds at face value
elsif($arg =~ /^minutes?$/) { $offset *= 60; }
elsif($arg =~ /^hours?/) { $offset *= 60*60; }
elsif($arg =~ /^days?/) { $offset *= 60*60*24; }
elsif($arg =~ /^weeks?/) { $offset *= 60*60*24*7; }
elsif($arg =~ /^months?$/) {
$mon += ($offset*$sign);
while($mon > 12)
{
$mon-=12;
$year++;
}
while($mon < 0)
{
$mon+=12;
$year--;
}
$sign=0;
$offset=0;
}
elsif ($arg =~ /^years?$/) {
$year += ($offset*$sign);
$sign=0;
$offset=0;
}
elsif($arg =~ /^now$/) { set(time); }
elsif(length($arg) == 0){ } # Empty string? Ignore
elsif($verbose) {
print STDERR "Unknown argument $arg\n";
}
}
# Convert the altered year, month, etc back into epoch time.
my $ref=mktime($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst);
# Print the calculated time plus offset
print strftime($cmdstr."\n", localtime($ref + ($sign*$offset)));
exit(0);