Арифметика дат в сценариях оболочки 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

но теперь мне нужно сделать более сложную арифметику.

Какой это лучший и более совместимый способ сделать это?

8.08.2008 23:24:02
14 ОТВЕТОВ
РЕШЕНИЕ

Я написал скрипт bash для преобразования дат, выраженных на английском языке, в обычные даты в формате мм / дд / гггг. Это называется ComputeDate .

Вот несколько примеров его использования. Для краткости я поместил вывод каждого вызова в ту же строку, что и вызов, разделенный двоеточием (:). Приведенные ниже кавычки не нужны при запуске 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

Я включил этот сценарий как ответ на эту проблему, потому что он иллюстрирует, как выполнять арифметику дат с помощью набора функций 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 $*

Этот сценарий широко использует другой сценарий, который я написал (называемый col ... много извинений тем, кто использует стандартный col, поставляемый с Linux). Эта версия col упрощает извлечение столбцов из стандартного ввода. Таким образом,

$ echo a b c d e | col 5 3 2

печать

e c b

Вот это сценарий col :

#!/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=
    
15
26.06.2010 19:04:41

Предполагая, что у вас есть дата GNU , вот так:

date --date='1 days ago' '+%a'

И похожие фразы .

71
11.02.2015 22:14:15
Появляется для работы на Linux. Я не удивлюсь, если это расширение GNU. Это даже работает для замещения произвольных сумм от произвольных дат, например, "date --date = '2012-01-27 - 30 дней'"
Joseph Garvin 27.02.2012 21:12:36
Определенно не работает с датой BSD, которая установлена ​​с OS X. Установка coreutils с чем-то вроде homebrew делает версию GNU, которая добавляет эти расширения, доступной как gdate.
sj26 12.11.2014 02:10:58
Так приятно обнаружить полезную функциональность, которая спасает день, в обычной мирской команде. Unix командная строка навсегда.
Thalis K. 24.07.2015 14:52:25
date --date='1 days ago' '+%a'

Это не очень совместимое решение. Это будет работать только в Linux. По крайней мере, это не сработало в Aix и Solaris.

Работает в RHEL:

date --date='1 days ago' '+%Y%m%d'
20080807
4
5.09.2014 04:10:47

Почему бы не написать свои сценарии с использованием языка, такого как perl или python, который более естественным образом поддерживает обработку сложных дат? Конечно, вы можете делать все это в bash, но я думаю, вы также получите большую согласованность между платформами, использующими, например, python, при условии, что вы можете убедиться, что Perl или Python установлены.

Я должен добавить, что довольно легко связать скрипты на python и perl в содержащий скрипт оболочки.

4
8.08.2008 23:48:23
Я согласен с Джастином и рекомендую использовать лучший инструмент / язык для вашего сценария. Я бы, вероятно, пошел с Perl, так как он установлен на большинстве систем Unix из коробки. Python, хотя и более современный, требует, чтобы вы сначала установили его, что может быть болезненным или даже политически невозможным.
markus_b 3.05.2009 13:05:47

Если посмотреть дальше, я думаю, что вы можете просто использовать дату. Я попробовал следующее на OpenBSD: я взял дату 29 февраля 2008 года и случайный час (в виде 080229301535) и добавил +1 к дневной части, вот так:

$ date -j 0802301535
Sat Mar  1 15:35:00 EST 2008

Как видите, дата правильно отформатировала время ...

НТН

3
9.08.2008 12:37:19

Если у вас работает версия даты из GNU, почему бы вам не взять исходный код и не скомпилировать его в AIX и Solaris?

http://www.gnu.org/software/coreutils/

В любом случае, источник должен помочь вам получить правильную арифметику даты, если вы собираетесь написать свой собственный код.

Кроме того, комментарии типа «это решение хорошо, но, конечно, вы можете заметить, что оно не так хорошо, как могло бы быть. Кажется, никто не думал возиться с датами при создании Unix». на самом деле нас никуда не доставит. Я нашел, что каждое из предложений до сих пор было очень полезным и целевым.

0
15.08.2008 22:13:52

Чтобы выполнить арифметику с датами в UNIX, вы получите дату в виде числа секунд, начиная с эпохи UNIX, проведите некоторые вычисления и затем вернитесь обратно в формат даты для печати. Команда date должна быть в состоянии дать вам секунды с начала эпохи и преобразовать это число обратно в печатную дату. Моя локальная команда даты делает это,

% date -n
1219371462
% date 1219371462
Thu Aug 21 22:17:42 EDT 2008
% 

Смотрите вашу местную date(1)справочную страницу. Чтобы увеличить день, добавьте 86400 секунд.

6
23.11.2009 23:48:07

Я сталкивался с этим пару раз. Мои мысли:

  1. Арифметика даты - это всегда боль
  2. Это немного проще при использовании формата даты EPOCH
  3. дата в Linux конвертируется в EPOCH, но не в Solaris
  4. Для переносимого решения вам необходимо выполнить одно из следующих действий:
    1. Установите GNU Date на Solaris (уже упоминалось, для завершения требуется взаимодействие с человеком)
    2. Используйте 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);
}
4
16.09.2008 09:34:20

Если вы хотите продолжить с 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]
     }
}
3
16.09.2008 12:43:15
но, как я только что обнаружил, mktime - это gawk, а не в Solaris awk / nawk :(
Joe Watkins 20.02.2009 16:26:03

Книга Криса Ф. А. Джонсона «Рецепты сценариев оболочки: подход к решению проблемы» (ISBN: 978-1-59059-471-1) содержит библиотеку функций даты, которая может оказаться полезной. Исходный код доступен по адресу http://apress.com/book/downloadfile/2146 (функции даты находятся в Chapter08 / data-funcs-sh в файле tar).

3
16.09.2008 20:17:13

Вот простой способ вычисления даты в сценариях оболочки.

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-tgotis-date.html http://www.cyberciti.biz/faq/linux-unix-formatting-dates-for-display/

Это сработало для меня на RHEL.

17
31.05.2012 13:36:22
+1 Это то, что мне нужно было сделать арифметику. Очень просто и понятно. Я исправил заказ м / д / у.
Erick Robertson 31.05.2012 13:36:39

Вот мои две копейки стоит - обертка сценария, использующая 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
0
3.05.2012 14:56:26

Для совместимости с 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

Смотрите man-страницу для более подробной информации.

8
12.11.2014 02:45:14

Это работает для меня:

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'`
-1
13.07.2016 22:22:28