He escrito un script bash para convertir las fechas expresadas en inglés a las fechas convencionales mm/dd/aaaa. Se llama ComputeDate.
Aquí hay algunos ejemplos de su uso. Para abreviar, he colocado la salida de cada invocación en la misma línea que la invocación, separada por dos puntos (:). Las cotizaciones se muestran a continuación son no necesaria cuando se ejecuta ComputeDate:
$ ComputeDate 'yesterday': 03/19/2010
$ ComputeDate 'yes': 03/19/2010
$ ComputeDate 'today': 03/20/2010
$ ComputeDate 'tod': 03/20/2010
$ ComputeDate 'now': 03/20/2010
$ ComputeDate 'tomorrow': 03/21/2010
$ ComputeDate 'tom': 03/21/2010
$ ComputeDate '10/29/32': 10/29/2032
$ ComputeDate 'October 29': 10/1/2029
$ ComputeDate 'October 29, 2010': 10/29/2010
$ ComputeDate 'this monday': 'this monday' has passed. Did you mean 'next monday?'
$ ComputeDate 'a week after today': 03/27/2010
$ ComputeDate 'this satu': 03/20/2010
$ ComputeDate 'next monday': 03/22/2010
$ ComputeDate 'next thur': 03/25/2010
$ ComputeDate 'mon in 2 weeks': 03/28/2010
$ ComputeDate 'the last day of the month': 03/31/2010
$ ComputeDate 'the last day of feb': 2/28/2010
$ ComputeDate 'the last day of feb 2000': 2/29/2000
$ ComputeDate '1 week from yesterday': 03/26/2010
$ ComputeDate '1 week from today': 03/27/2010
$ ComputeDate '1 week from tomorrow': 03/28/2010
$ ComputeDate '2 weeks from yesterday': 4/2/2010
$ ComputeDate '2 weeks from today': 4/3/2010
$ ComputeDate '2 weeks from tomorrow': 4/4/2010
$ ComputeDate '1 week after the last day of march': 4/7/2010
$ ComputeDate '1 week after next Thursday': 4/1/2010
$ ComputeDate '2 weeks after the last day of march': 4/14/2010
$ ComputeDate '2 weeks after 1 day after the last day of march': 4/15/2010
$ ComputeDate '1 day after the last day of march': 4/1/2010
$ ComputeDate '1 day after 1 day after 1 day after 1 day after today': 03/24/2010
He incluido este script como una respuesta a este problema, ya que ilustra cómo a hacer aritmética de fechas a través de un conjunto de funciones de bash y éstos las funciones pueden resultar útiles para otros. Se ocupa de los años bisiestos y siglos bisiestos correctamente:
#! /bin/bash
# ConvertDate -- convert a human-readable date to a MM/DD/YY date
#
# Date ::= Month/Day/Year
# | Month/Day
# | DayOfWeek
# | [this|next] DayOfWeek
# | DayofWeek [of|in] [Number|next] weeks[s]
# | Number [day|week][s] from Date
# | the last day of the month
# | the last day of Month
#
# Month ::= January | February | March | April | May | ... | December
# January ::= jan | january | 1
# February ::= feb | january | 2
# ...
# December ::= dec | december | 12
# Day ::= 1 | 2 | ... | 31
# DayOfWeek ::= today | Sunday | Monday | Tuesday | ... | Saturday
# Sunday ::= sun*
# ...
# Saturday ::= sat*
#
# Number ::= Day | a
#
# Author: Larry Morell
if [ $# = 0 ]; then
printdirections $0
exit
fi
# Request the value of a variable
GetVar() {
Var=$1
echo -n "$Var= [${!Var}]: "
local X
read X
if [ ! -z $X ]; then
eval $Var="$X"
fi
}
IsLeapYear() {
local Year=$1
if [ $[20$Year % 4] -eq 0 ]; then
echo yes
else
echo no
fi
}
# AddToDate -- compute another date within the same year
DayNames=(mon tue wed thu fri sat sun) # To correspond with 'date' output
Day2Int() {
ErrorFlag=
case $1 in
-e)
ErrorFlag=-e; shift
;;
esac
local dow=$1
n=0
while [ $n -lt 7 -a $dow != "${DayNames[n]}" ]; do
let n++
done
if [ -z "$ErrorFlag" -a $n -eq 7 ]; then
echo Cannot convert $dow to a numeric day of wee
exit
fi
echo $[n+1]
}
Months=(31 28 31 30 31 30 31 31 30 31 30 31)
MonthNames=(jan feb mar apr may jun jul aug sep oct nov dec)
# Returns the month (1-12) from a date, or a month name
Month2Int() {
ErrorFlag=
case $1 in
-e)
ErrorFlag=-e; shift
;;
esac
M=$1
Month=${M%%/*} # Remove /...
case $Month in
[a-z]*)
Month=${Month:0:3}
M=0
while [ $M -lt 12 -a ${MonthNames[M]} != $Month ]; do
let M++
done
let M++
esac
if [ -z "$ErrorFlag" -a $M -gt 12 ]; then
echo "'$Month' Is not a valid month."
exit
fi
echo $M
}
# Retrieve month,day,year from a legal date
GetMonth() {
echo ${1%%/*}
}
GetDay() {
echo $1 | col/2
}
GetYear() {
echo ${1##*/}
}
AddToDate() {
local Date=$1
local days=$2
local Month=`GetMonth $Date`
local Day=`echo $Date | col/2` # Day of Date
local Year=`echo $Date | col/3` # Year of Date
local LeapYear=`IsLeapYear $Year`
if [ $LeapYear = "yes" ]; then
let Months[1]++
fi
Day=$[Day+days]
while [ $Day -gt ${Months[$Month-1]} ]; do
Day=$[Day - ${Months[$Month-1]}]
let Month++
done
echo "$Month/$Day/$Year"
}
# Convert a date to normal form
NormalizeDate() {
Date=`echo "$*" | sed 'sX *X/Xg'`
local Day=`date +%d`
local Month=`date +%m`
local Year=`date +%Y`
#echo Normalizing Date=$Date > /dev/tty
case $Date in
*/*/*)
Month=`echo $Date | col/1 `
Month=`Month2Int $Month`
Day=`echo $Date | col/2`
Year=`echo $Date | col/3`
;;
*/*)
Month=`echo $Date | col/1 `
Month=`Month2Int $Month`
Day=1
Year=`echo $Date | col/2 `
;;
[a-z]*) # Better be a month or day of week
Exp=${Date:0:3}
case $Exp in
jan|feb|mar|apr|may|june|jul|aug|sep|oct|nov|dec)
Month=$Exp
Month=`Month2Int $Month`
Day=1
#Year stays the same
;;
mon|tue|wed|thu|fri|sat|sun)
# Compute the next such day
local DayOfWeek=`date +%u`
D=`Day2Int $Exp`
if [ $DayOfWeek -le $D ]; then
Date=`AddToDate $Month/$Day/$Year $[D-DayOfWeek]`
else
Date=`AddToDate $Month/$Day/$Year $[7+D-DayOfWeek]`
fi
# Reset Month/Day/Year
Month=`echo $Date | col/1 `
Day=`echo $Date | col/2`
Year=`echo $Date | col/3`
;;
*) echo "$Exp is not a valid month or day"
exit
;;
esac
;;
*) echo "$Date is not a valid date"
exit
;;
esac
case $Day in
[0-9]*);; # Day must be numeric
*) echo "$Date is not a valid date"
exit
;;
esac
[0-9][0-9][0-9][0-9]);; # Year must be 4 digits
[0-9][0-9])
Year=20$Year
;;
esac
Date=$Month/$Day/$Year
echo $Date
}
# NormalizeDate jan
# NormalizeDate january
# NormalizeDate jan 2009
# NormalizeDate jan 22 1983
# NormalizeDate 1/22
# NormalizeDate 1 22
# NormalizeDate sat
# NormalizeDate sun
# NormalizeDate mon
ComputeExtension() {
local Date=$1; shift
local Month=`GetMonth $Date`
local Day=`echo $Date | col/2`
local Year=`echo $Date | col/3`
local ExtensionExp="$*"
case $ExtensionExp in
*w*d*) # like 5 weeks 3 days or even 5w2d
ExtensionExp=`echo $ExtensionExp | sed 's/[a-z]/ /g'`
weeks=`echo $ExtensionExp | col 1`
days=`echo $ExtensionExp | col 2`
days=$[7*weeks+days]
Due=`AddToDate $Month/$Day/$Year $days`
;;
*d) # Like 5 days or 5d
ExtensionExp=`echo $ExtensionExp | sed 's/[a-z]/ /g'`
days=$ExtensionExp
Due=`AddToDate $Month/$Day/$Year $days`
;;
*)
Due=$ExtensionExp
;;
esac
echo $Due
}
# Pop -- remove the first element from an array and shift left
Pop() {
Var=$1
eval "unset $Var[0]"
eval "$Var=(\${$Var[*]})"
}
ComputeDate() {
local Date=`NormalizeDate $1`; shift
local Expression=`echo $* | sed 's/^ *a /1 /;s/,/ /' | tr A-Z a-z `
local Exp=(`echo $Expression `)
local Token=$Exp # first one
local Ans=
#echo "Computing date for ${Exp[*]}" > /dev/tty
case $Token in
*/*) # Regular date
M=`GetMonth $Token`
D=`GetDay $Token`
Y=`GetYear $Token`
if [ -z "$Y" ]; then
Y=$Year
elif [ ${#Y} -eq 2 ]; then
Y=20$Y
fi
Ans="$M/$D/$Y"
;;
yes*)
Ans=`AddToDate $Date -1`
;;
tod*|now)
Ans=$Date
;;
tom*)
Ans=`AddToDate $Date 1`
;;
the)
case $Expression in
*day*after*) #the day after Date
Pop Exp; # Skip the
Pop Exp; # Skip day
Pop Exp; # Skip after
#echo Calling ComputeDate $Date ${Exp[*]} > /dev/tty
Date=`ComputeDate $Date ${Exp[*]}` #Recursive call
#echo "New date is " $Date > /dev/tty
Ans=`AddToDate $Date 1`
;;
*last*day*of*th*month|*end*of*th*month)
M=`date +%m`
Day=${Months[M-1]}
if [ $M -eq 2 -a `IsLeapYear $Year` = yes ]; then
let Day++
fi
Ans=$Month/$Day/$Year
;;
*last*day*of*)
D=${Expression##*of }
D=`NormalizeDate $D`
M=`GetMonth $D`
Y=`GetYear $D`
# echo M is $M > /dev/tty
Day=${Months[M-1]}
if [ $M -eq 2 -a `IsLeapYear $Y` = yes ]; then
let Day++
fi
Ans=$[M]/$Day/$Y
;;
*)
echo "Unknown expression: " $Expression
exit
;;
esac
;;
next*) # next DayOfWeek
Pop Exp
dow=`Day2Int $DayOfWeek` # First 3 chars
tdow=`Day2Int ${Exp:0:3}` # First 3 chars
n=$[7-dow+tdow]
Ans=`AddToDate $Date $n`
;;
this*)
Pop Exp
dow=`Day2Int $DayOfWeek`
tdow=`Day2Int ${Exp:0:3}` # First 3 chars
if [ $dow -gt $tdow ]; then
echo "'this $Exp' has passed. Did you mean 'next $Exp?'"
exit
fi
n=$[tdow-dow]
Ans=`AddToDate $Date $n`
;;
[a-z]*) # DayOfWeek ...
M=${Exp:0:3}
case $M in
jan|feb|mar|apr|may|june|jul|aug|sep|oct|nov|dec)
ND=`NormalizeDate ${Exp[*]}`
Ans=$ND
;;
mon|tue|wed|thu|fri|sat|sun)
dow=`Day2Int $DayOfWeek`
Ans=`NormalizeDate $Exp`
if [ ${#Exp[*]} -gt 1 ]; then # Just a DayOfWeek
#tdow=`GetDay $Exp` # First 3 chars
#if [ $dow -gt $tdow ]; then
#echo "'this $Exp' has passed. Did you mean 'next $Exp'?"
#exit
#fi
#n=$[tdow-dow]
#else # DayOfWeek in a future week
Pop Exp # toss monday
Pop Exp # toss in/off
if [ $Exp = next ]; then
Exp=2
fi
n=$[7*(Exp-1)] # number of weeks
n=$[n+7-dow+tdow]
Ans=`AddToDate $Date $n`
fi
;;
esac
;;
[0-9]*) # Number weeks [from|after] Date
n=$Exp
Pop Exp;
case $Exp in
w*) let n=7*n;;
esac
Pop Exp; Pop Exp
#echo Calling ComputeDate $Date ${Exp[*]} > /dev/tty
Date=`ComputeDate $Date ${Exp[*]}` #Recursive call
#echo "New date is " $Date > /dev/tty
Ans=`AddToDate $Date $n`
;;
esac
echo $Ans
}
Year=`date +%Y`
Month=`date +%m`
Day=`date +%d`
DayOfWeek=`date +%a |tr A-Z a-z`
Date="$Month/$Day/$Year"
ComputeDate $Date $*
Este script hace un amplio uso de otro guión que escribí (llamado col ... muchas disculpas a aquellos que utilizan el estándar col suministrado con Linux).Esta versión de col simplifica la extracción de columnas del stdin. Por lo tanto,
$ echo a b c d e | col 5 3 2
impresiones
e c b
Aquí la col guión:
#!/bin/sh
# col -- extract columns from a file
# Usage:
# col [-r] [c] col-1 col-2 ...
# where [c] if supplied defines the field separator
# where each col-i represents a column interpreted according to the presence of -r as follows:
# -r present : counting starts from the right end of the line
# -r absent : counting starts from the left side of the line
Separator=" "
Reverse=false
case "$1" in
-r) Reverse=true; shift;
;;
[0-9]*)
;;
*)Separator="$1"; shift;
;;
esac
case "$1" in
-r) Reverse=true; shift;
;;
[0-9]*)
;;
*)Separator="$1"; shift;
;;
esac
# Replace each col-i with $i
Cols=""
for f in $*
do
if [ $Reverse = true ]; then
Cols="$Cols \$(NF-$f+1),"
else
Cols="$Cols \$$f,"
fi
done
Cols=`echo "$Cols" | sed 's/,$//'`
#echo "Using column specifications of $Cols"
awk -F "$Separator" "{print $Cols}"
También utiliza printdirections para imprimir las direcciones cuando el script se invoca indebidamente:
#!/bin/sh
#
# printdirections -- print header lines of a shell script
#
# Usage:
# printdirections path
# where
# path is a *full* path to the shell script in question
# beginning with '/'
#
# To use printdirections, you must include (as comments at the top
# of your shell script) documentation for running the shell script.
if [ $# -eq 0 -o "$*" = "-h" ]; then
printdirections $0
exit
fi
# Delete the command invocation at the top of the file, if any
# Delete from the place where printdirections occurs to the end of the file
# Remove the # comments
# There is a bizarre oddity here.
sed '/#!/d;/.*printdirections/,$d;/ *#/!d;s/# //;s/#//' $1 > /tmp/printdirections.$$
# Count the number of lines
numlines=`wc -l /tmp/printdirections.$$ | awk '{print $1}'`
# Remove the last line
numlines=`expr $numlines - 1`
head -n $numlines /tmp/printdirections.$$
rm /tmp/printdirections.$$
Para utilizar este lugar los tres guiones en los archivos ComputeDate, col, y printdirections, respectivamente. Coloque el archivo en el directorio nombrado por su RUTA, generalmente, ~/bin. A continuación, hágalos ejecutables con:
$ chmod a+x ComputeDate col printdirections
¿Problemas? Envíeme un correo electrónico: morell AT cs.atu.edu Lugar ComputeDate en el asunto.
Vea mi respuesta para la solución más simple –