Арифметика даты в сценариях оболочки Unix
Вопрос
Мне нужно выполнить арифметику даты в сценариях оболочки Unix, которые я использую для управления выполнением сторонних программ.
Я использую функцию для увеличения числа дней и другую для уменьшения:
IncrementaDia(){
echo $1 | awk '
BEGIN {
diasDelMes[1] = 31
diasDelMes[2] = 28
diasDelMes[3] = 31
diasDelMes[4] = 30
diasDelMes[5] = 31
diasDelMes[6] = 30
diasDelMes[7] = 31
diasDelMes[8] = 31
diasDelMes[9] = 30
diasDelMes[10] = 31
diasDelMes[11] = 30
diasDelMes[12] = 31
}
{
anio=substr($1,1,4)
mes=substr($1,5,2)
dia=substr($1,7,2)
if((anio % 4 == 0 && anio % 100 != 0) || anio % 400 == 0)
{
diasDelMes[2] = 29;
}
if( dia == diasDelMes[int(mes)] ) {
if( int(mes) == 12 ) {
anio = anio + 1
mes = 1
dia = 1
} else {
mes = mes + 1
dia = 1
}
} else {
dia = dia + 1
}
}
END {
printf("%04d%02d%02d", anio, mes, dia)
}
'
}
if [ $# -eq 1 ]; then
tomorrow=$1
else
today=$(date +"%Y%m%d")
tomorrow=$(IncrementaDia $hoy)
fi
но теперь мне нужно выполнить более сложную арифметику.
Какой это лучший и более совместимый способ сделать это?
Решение
Я написал скрипт bash для преобразования дат, выраженных на английском языке, в обычные даты мм / дд / гггг.Это называется Вычисляемый параметр.
Вот несколько примеров его использования.Для краткости я разместил выходные данные каждого вызова в той же строке, что и вызов, разделенный двоеточием (:).Ниже приведены следующие цитаты нет необходимо при запуске Вычисляемый параметр:
$ 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
Я включил этот скрипт в качестве ответа на эту проблему, потому что он иллюстрирует, как выполнять арифметику даты с помощью набора функций bash, и эти функции могут оказаться полезными для других.Он правильно обрабатывает високосные годы и високосные столетия:
#! /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 $*
Этот скрипт широко использует другой скрипт, который я написал (называется кол ...приношу свои извинения тем, кто использует стандарт кол поставляется вместе с Linux).Эта версия кол упрощает извлечение столбцов из stdin.Таким образом,
$ echo a b c d e | col 5 3 2
С принтами
e c b
Вот оно самое кол сценарий:
#!/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}"
Он также использует направления печати для распечатки инструкций при неправильном вызове скрипта:
#!/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.$$
Чтобы использовать это, поместите три скрипта в файлы Вычисляемый параметр, кол, и направления печати, соответственно.Поместите файл в каталог, названный по вашему ПУТИ, обычно ~/bin.Затем сделайте их исполняемыми с помощью:
$ chmod a+x ComputeDate col printdirections
Проблемы?Пришлите мне какое-нибудь электронное письмо:морелл В cs.atu.edu Плейс Вычисляемый параметр в теме.
Другие советы
Вот простой способ выполнения вычислений даты в сценариях командной оболочки.
meetingDate='12/31/2011' # MM/DD/YYYY Format
reminderDate=`date --date=$meetingDate'-1 day' +'%m/%d/%Y'`
echo $reminderDate
Ниже приведены дополнительные варианты вычисления даты, которые могут быть достигнуты с помощью date
полезность.http://www.cyberciti.biz/tips/linux-unix-get-yesterdays-tomorrows-date.html
http://www.cyberciti.biz/faq/linux-unix-formatting-dates-for-display/
Это сработало у меня на RHEL.
Для совместимости с BSD / OS X вы также можете использовать утилиту date с -j
и -v
чтобы посчитать даты.Смотрите на Справочная страница FreeBSD для указания даты.Вы могли бы объединить предыдущие ответы Linux с этим ответом, который мог бы обеспечить вам достаточную совместимость.
На BSD, как и в Linux, работает date
сообщит вам текущую дату:
$ date
Wed 12 Nov 2014 13:36:00 AEDT
Теперь, имея дату BSD, вы можете выполнять математические вычисления с помощью -v
, например, с указанием завтрашней даты (+1d
является плюс один день):
$ date -v +1d
Thu 13 Nov 2014 13:36:34 AEDT
Вы можете использовать существующую дату в качестве основы и при необходимости указать формат синтаксического анализа с использованием strftime, и убедитесь, что вы используете -j
таким образом, вы не меняете свою системную дату:
$ date -j -f "%a %b %d %H:%M:%S %Y %z" "Sat Aug 09 13:37:14 2014 +1100"
Sat 9 Aug 2014 12:37:14 AEST
И вы можете использовать это в качестве основы для расчета дат:
$ date -v +1d -f "%a %b %d %H:%M:%S %Y %z" "Sat Aug 09 13:37:14 2014 +1100"
Sun 10 Aug 2014 12:37:14 AEST
Обратите внимание , что -v
подразумевает -j
.
Несколько регулировок могут быть выполнены последовательно:
$ date -v +1m -v -1w
Fri 5 Dec 2014 13:40:07 AEDT
Смотрите справочную страницу для получения более подробной информации.
Чтобы выполнить арифметику с датами в UNIX, вы получаете дату как количество секунд, прошедшее с эпохи UNIX, производите некоторые вычисления, затем конвертируете обратно в свой формат даты для печати.Команда date должна быть способна как указывать секунды, прошедшие с момента начала эпохи, так и преобразовывать это число обратно в дату для печати.Моя локальная команда date выполняет это,
% date -n
1219371462
% date 1219371462
Thu Aug 21 22:17:42 EDT 2008
%
Посмотрите свой местный date(1)
справочная страница.Чтобы увеличить продолжительность дня, добавьте 86400 секунд.
Почему бы вместо этого не писать свои скрипты, используя такой язык, как perl или python, который более естественно поддерживает сложную обработку дат?Уверен, что ты может делайте все это в bash, но я думаю, вы также добьетесь большей согласованности между платформами, например, используя python, если сможете убедиться, что установлен perl или python.
Я должен добавить, что довольно легко подключить скрипты на python и perl к содержащему их сценарию оболочки.
Я сталкивался с этим пару раз.Мои мысли таковы:
- Арифметика дат - это всегда мучение
- Это немного проще при использовании формата даты ЭПОХИ
- дата в Linux преобразуется в ЭПОХУ, но не в Solaris
- Для создания портативного решения вам необходимо выполнить одно из следующих действий:
- Установите gnu date на solaris (уже упоминалось, для завершения требуется взаимодействие с человеком )
- Используйте perl для части с датой (большинство установки unix включают perl, поэтому я бы вообще предположил, что это действие выполняет нет требуют дополнительной работы).
Пример скрипта (проверяет возраст определенных пользовательских файлов, чтобы узнать, можно ли удалить учетную запись):
#!/usr/local/bin/perl
$today = time();
$user = $ARGV[0];
$command="awk -F: '/$user/ {print \$6}' /etc/passwd";
chomp ($user_dir = `$command`);
if ( -f "$user_dir/.sh_history" ) {
@file_dates = stat("$user_dir/.sh_history");
$sh_file_date = $file_dates[8];
} else {
$sh_file_date = 0;
}
if ( -f "$user_dir/.bash_history" ) {
@file_dates = stat("$user_dir/.bash_history");
$bash_file_date = $file_dates[8];
} else {
$bash_file_date = 0;
}
if ( $sh_file_date > $bash_file_date ) {
$file_date = $sh_file_date;
} else {
$file_date = $bash_file_date;
}
$difference = $today - $file_date;
if ( $difference >= 3888000 ) {
print "User needs to be disabled, 45 days old or older!\n";
exit (1);
} else {
print "OK\n";
exit (0);
}
date --date='1 days ago' '+%a'
Это не очень совместимое решение.Это будет работать только в Linux.По крайней мере, в Эксе и Солярисе это не сработало.
Это работает в RHEL:
date --date='1 days ago' '+%Y%m%d'
20080807
Рассматривая это подробнее, я думаю, вы можете просто использовать date .Я пробовал следующее в OpenBSD:Я взял дату февраля.29 числа 2008 года и случайный час (в форме 080229301535) и добавлено +1 к дневной части, вот так:
$ date -j 0802301535
Sat Mar 1 15:35:00 EST 2008
Как вы можете видеть, дата правильно отформатировала время...
HTH
Если вы хотите продолжить работу с awk, то вам пригодятся функции mktime и strftime:
BEGIN { dateinit }
{ newdate=daysadd(OldDate,DaysToAdd)}
# daynum: convert DD-MON-YYYY to day count
#-----------------------------------------
function daynum(date, d,m,y,i,n)
{
y=substr(date,8,4)
m=gmonths[toupper(substr(date,4,3))]
d=substr(date,1,2)
return mktime(y" "m" "d" 12 00 00")
}
#numday: convert day count to DD-MON-YYYY
#-------------------------------------------
function numday(n, y,m,d)
{
m=toupper(substr(strftime("%B",n),1,3))
return strftime("%d-"m"-%Y",n)
}
# daysadd: add (or subtract) days from date (DD-MON-YYYY), return new date (DD-MON-YYYY)
#------------------------------------------
function daysadd(date, days)
{
return numday(daynum(date)+(days*86400))
}
#init variables for date calcs
#-----------------------------------------
function dateinit( x,y,z)
{
# Stuff for date calcs
split("JAN:1,FEB:2,MAR:3,APR:4,MAY:5,JUN:6,JUL:7,AUG:8,SEP:9,OCT:10,NOV:11,DEC:12", z)
for (x in z)
{
split(z[x],y,":")
gmonths[y[1]]=y[2]
}
}
Книга "Рецепты сценариев оболочки:Подход к решению проблем" (ISBN:978-1-59059-471-1) Криса Ф.А.У Джонсона есть библиотека функций даты, которая может оказаться полезной.Исходный код доступен по адресу http://apress.com/book/downloadfile/2146 (функции даты приведены в главе 08/data-funcs-sh в файле tar).
Если вам подходит GNU-версия date, почему бы вам не взять исходный код и не скомпилировать его на AIX и Solaris?
http://www.gnu.org/software/coreutils/
В любом случае, исходный код должен помочь вам правильно вычислить дату, если вы собираетесь писать свой собственный код.
В качестве отступления, комментарии типа "это решение хорошее, но, конечно, вы можете заметить, что оно не так хорошо, как могло бы быть.Похоже, никому не приходило в голову возиться с датами при создании Unix ". на самом деле это ни к чему нас не приведет.Я нахожу, что каждое из предложенных до сих пор предложений было очень полезным и соответствовало цели.
Вот мои два пенни, которые стоят - оболочка скрипта, использующая date
и grep
.
Пример Использования
> sh ./datecalc.sh "2012-08-04 19:43:00" + 1s
2012-08-04 19:43:00 + 0d0h0m1s
2012-08-04 19:43:01
> sh ./datecalc.sh "2012-08-04 19:43:00" - 1s1m1h1d
2012-08-04 19:43:00 - 1d1h1m1s
2012-08-03 18:41:59
> sh ./datecalc.sh "2012-08-04 19:43:00" - 1d2d1h2h1m2m1s2sblahblah
2012-08-04 19:43:00 - 1d1h1m1s
2012-08-03 18:41:59
> sh ./datecalc.sh "2012-08-04 19:43:00" x 1d
Bad operator :-(
> sh ./datecalc.sh "2012-08-04 19:43:00"
Missing arguments :-(
> sh ./datecalc.sh gibberish + 1h
date: invalid date `gibberish'
Invalid date :-(
Сценарий
#!/bin/sh
# Usage:
#
# datecalc "<date>" <operator> <period>
#
# <date> ::= see "man date", section "DATE STRING"
# <operator> ::= + | -
# <period> ::= INTEGER<unit> | INTEGER<unit><period>
# <unit> ::= s | m | h | d
if [ $# -lt 3 ]; then
echo "Missing arguments :-("
exit; fi
date=`eval "date -d \"$1\" +%s"`
if [ -z $date ]; then
echo "Invalid date :-("
exit; fi
if ! ([ $2 == "-" ] || [ $2 == "+" ]); then
echo "Bad operator :-("
exit; fi
op=$2
minute=$[60]
hour=$[$minute*$minute]
day=$[24*$hour]
s=`echo $3 | grep -oe '[0-9]*s' | grep -m 1 -oe '[0-9]*'`
m=`echo $3 | grep -oe '[0-9]*m' | grep -m 1 -oe '[0-9]*'`
h=`echo $3 | grep -oe '[0-9]*h' | grep -m 1 -oe '[0-9]*'`
d=`echo $3 | grep -oe '[0-9]*d' | grep -m 1 -oe '[0-9]*'`
if [ -z $s ]; then s=0; fi
if [ -z $m ]; then m=0; fi
if [ -z $h ]; then h=0; fi
if [ -z $d ]; then d=0; fi
ms=$[$m*$minute]
hs=$[$h*$hour]
ds=$[$d*$day]
sum=$[$s+$ms+$hs+$ds]
out=$[$date$op$sum]
formattedout=`eval "date -d @$out +\"%Y-%m-%d %H:%M:%S\""`
echo $1 $2 $d"d"$h"h"$m"m"$s"s"
echo $formattedout
Это работает для меня:
TZ=GMT+6;
export TZ
mes=`date --date='2 days ago' '+%m'`
dia=`date --date='2 days ago' '+%d'`
anio=`date --date='2 days ago' '+%Y'`
hora=`date --date='2 days ago' '+%H'`