Як порівняти дві дати в оболонці?


88

Як можна порівняти дві дати в оболонці?

Ось приклад того, як я хотів би використовувати це, хоча це не працює як є:

todate=2013-07-18
cond=2013-07-15

if [ $todate -ge $cond ];
then
    break
fi                           

Як я можу досягти бажаного результату?


Для чого ти хочеш циклічити? Ви маєте на увазі умовний, а не циклічний?
Мат

Чому ви позначили файли ? Чи справді ці дати файли?
манатура

Перевірте це на stackoverflow: stackoverflow.com/questions/8116503/…
slm

Відповіді:


107

Правильна відповідь все ще відсутня:

todate=$(date -d 2013-07-18 +%s)
cond=$(date -d 2014-08-19 +%s)

if [ $todate -ge $cond ];
then
    break
fi  

Зауважте, що для цього потрібна дата GNU. Еквівалентний dateсинтаксис для BSD date(як, наприклад, знайдено в OSX за замовчуванням) єdate -j -f "%F" 2014-08-19 +"%s"


10
Не знаю, чому хтось спростував це, це досить точно і не займається об'єднанням потворних струн. Перетворіть дату в часову позначку unix (у секундах), порівняйте часові позначки unix.
Ніколі

Думаю, головна моя перевага цього рішення полягає в тому, що ви можете легко робити додавання чи віднімання. Наприклад, я хотів мати тайм-аут x секунд, але моя перша ідея ( timeout=$(`date +%Y%m%d%H%M%S` + 500)), очевидно, неправильна.
iGEL

Ви можете включити точність наносекунд за допомогоюdate -d 2013-07-18 +%s%N
typelogic

32

Вам не вистачає формату дати для порівняння:

#!/bin/bash

todate=$(date -d 2013-07-18 +"%Y%m%d")  # = 20130718
cond=$(date -d 2013-07-15 +"%Y%m%d")    # = 20130715

if [ $todate -ge $cond ]; #put the loop where you need it
then
 echo 'yes';
fi

Вам також не вистачає петельних структур, як ви плануєте отримати більше дат?


Це не працює з dateпостачанням з macOS.
Flimm

date -dє нестандартним і не працює в типовому UNIX. У BSD він навіть намагається встановити значення kenel DST.
schily

32

Можна використовувати стандартне порівняння рядків для порівняння хронологічного впорядкування [рядків у форматі рік, місяць, день].

date_a=2013-07-18
date_b=2013-07-15

if [[ "$date_a" > "$date_b" ]] ;
then
    echo "break"
fi

На щастя, коли [рядки, які використовують формат YYYY-MM-DD], сортуються * в алфавітному порядку, вони також сортуються * у хронологічному порядку.

(* - відсортовано або порівняно)

Нічого фантазії в цьому випадку не потрібно. так!


Де тут ще інша галузь?
Володимир Деспотович

Це єдине рішення, яке працювало на мене на Sierra MacOS
Володимир Деспотович

Це простіше, ніж моє рішення if [ $(sed -e s/-//g <<< $todate) -ge $(sed -e s/-//g <<< $cond) ];… Цей формат дати був розроблений для сортування за допомогою стандартного алфавітного числового сортування, або для того, -щоб бути викресленим і відсортованим чисельно.
ctrl-alt-delor

Це далеко-далеко найрозумніша відповідь тут
Ед Рендалл,

7

Це не проблема циклічного циклу, а типів даних.

Ці дати ( todateі cond) є рядками, а не числами, тому ви не можете використовувати оператор "-ge" test. (Пам'ятайте, що позначення квадратних дужок еквівалентно команді test.)

Що ви можете зробити, це використовувати інше позначення для своїх дат, щоб вони були цілими числами. Наприклад:

date +%Y%m%d

буде видано ціле число, як 20130715, для 15 липня 2013 року. Потім ви можете порівняти дати з "-ge" та еквівалентними операторами.

Оновлення: якщо дати вказані (наприклад, ви читаєте їх з файлу у форматі 2013-07-13), тоді ви можете попередньо обробити їх легко tr.

$ echo "2013-07-15" | tr -d "-"
20130715

6

Оператор -geпрацює лише з цілими числами, а ваші дати - ні.

Якщо ваш скрипт - це bash, або ksh або zsh, ви можете скористатися <оператором. Цей оператор недоступний у тире або інших оболонках, які не надто перевищують стандарт POSIX.

if [[ $cond < $todate ]]; then break; fi

У будь-якій оболонці ви можете перетворити рядки в цифри, дотримуючись черговості дат, просто видаливши тире.

if [ "$(echo "$todate" | tr -d -)" -ge "$(echo "$cond" | tr -d -)" ]; then break; fi

Крім того, ви можете перейти традиційно і скористатися exprутилітою.

if expr "$todate" ">=" "$cond" > /dev/null; then break; fi

Оскільки виклик підпроцесів у циклі може бути повільним, ви можете віддати перевагу перетворенню за допомогою конструкцій обробки рядкових оболонок.

todate_num=${todate%%-*}${todate#*-}; todate_num=${todate_num%%-*}${todate_num#*-}
cond_num=${cond%%-*}${cond#*-}; cond_num=${cond_num%%-*}${cond_num#*-}
if [ "$todate_num" -ge "$cond_num" ]; then break; fi

Звичайно, якщо ви зможете в першу чергу отримати дати без дефісів, ви зможете порівняти їх -ge.


Де ще інша галузь у цьому базі?
Володимир Деспотович

2
Це здається найбільш правильною відповіддю. Дуже корисний!
Мойтаба Резайан

1

Я перетворюю рядки в unix-часові позначки (секунди з 1.1.1970 0: 0: 0). Їх можна порівняти легко

unix_todate=$(date -d "${todate}" "+%s")
unix_cond=$(date -d "${cond}" "+%s")
if [ ${unix_todate} -ge ${unix_cond} ]; then
   echo "over condition"
fi

Це не працює з тим, dateщо постачається з macOS.
Flimm

Здається, це те саме, що unix.stackexchange.com/a/170982/100397, який був розміщений за деякий час до вашого
roaima

1

Існує також цей метод із статті під назвою: Просте підрахунок дати та часу в БАШ від unix.com.

Ці функції - уривок із сценарію в цій темі!

date2stamp () {
    date --utc --date "$1" +%s
}

dateDiff (){
    case $1 in
        -s)   sec=1;      shift;;
        -m)   sec=60;     shift;;
        -h)   sec=3600;   shift;;
        -d)   sec=86400;  shift;;
        *)    sec=86400;;
    esac
    dte1=$(date2stamp $1)
    dte2=$(date2stamp $2)
    diffSec=$((dte2-dte1))
    if ((diffSec < 0)); then abs=-1; else abs=1; fi
    echo $((diffSec/sec*abs))
}

Використання

# calculate the number of days between 2 dates
    # -s in sec. | -m in min. | -h in hours  | -d in days (default)
    dateDiff -s "2006-10-01" "2006-10-31"
    dateDiff -m "2006-10-01" "2006-10-31"
    dateDiff -h "2006-10-01" "2006-10-31"
    dateDiff -d "2006-10-01" "2006-10-31"
    dateDiff  "2006-10-01" "2006-10-31"

Це не працює з тим, dateщо постачається з macOS.
Flimm

Якщо ви встановите gdate на MacOS за допомогою варіння, це можна легко змінити для роботи, змінивши dateна gdate.
slm

1
Ця відповідь дуже допомогла в моєму випадку. Це повинно оцінювати!
Мойтаба Резайан

1

конкретну відповідь

Оскільки я люблю зменшувати вилки і дозволяють багато хитрощів, є моя мета:

todate=2013-07-18
cond=2013-07-15

Ну а тепер:

{ read todate; read cond ;} < <(date -f - +%s <<<"$todate"$'\n'"$cond")

Це повторно заповнить обидві змінні $todateта $cond, використовуючи лише одну вилку, з урахуванням того, date -f -що візьме stdio для читання однієї дати за рядком.

Нарешті, ви можете зламати петлю

((todate>=cond))&&break

Або як функція :

myfunc() {
    local todate cond
    { read todate
      read cond
    } < <(
      date -f - +%s <<<"$1"$'\n'"$2"
    )
    ((todate>=cond))&&return
    printf "%(%a %d %b %Y)T older than %(%a %d %b %Y)T...\n" $todate $cond
}

Використовуючи вбудований файл , який printfміг би відображати час з секундами з епохи (див man bash;-)

У цьому сценарії використовується лише одна вилка.

Альтернатива з обмеженими вилками та функцією зчитування дат

Це створить виділений підпроцес (лише одна вилка):

mkfifo /tmp/fifo
exec 99> >(exec stdbuf -i 0 -o 0 date -f - +%s >/tmp/fifo 2>&1)
exec 98</tmp/fifo
rm /tmp/fifo

Оскільки вхід і вихід відкриті, запис "FIFO" можна буде видалити.

Функція:

myDate() {
    local var="${@:$#}"
    shift
    echo >&99 "${@:1:$#-1}"
    read -t .01 -u 98 $var
}

Nota Для того, щоб запобігти марним вилкам типу todate=$(myDate 2013-07-18), змінна повинна встановлюватися самою функцією. І щоб дозволити вільний синтаксис (із лапочками або без лапок), ім'я змінної повинно бути останнім аргументом.

Потім порівняння дат:

myDate 2013-07-18        todate
myDate Mon Jul 15 2013   cond
(( todate >= cond )) && {
    printf "To: %(%c)T > Cond: %(%c)T\n" $todate $cond
    break
}

може надавати:

To: Thu Jul 18 00:00:00 2013 > Cond: Mon Jul 15 00:00:00 2013
bash: break: only meaningful in a `for', `while', or `until' loop

якщо поза петлею.

Або скористайтеся функцією bash-connector bash:

wget https://github.com/F-Hauri/Connector-bash/raw/master/shell_connector.bash

або

wget https://f-hauri.ch/vrac/shell_connector.sh

(Які не зовсім такі: .shмістять повний тестовий скрипт, якщо вони не отримані)

source shell_connector.sh
newConnector /bin/date '-f - +%s' @0 0

myDate 2013-07-18        todate
myDate "Mon Jul 15 2013"   cond
(( todate >= cond )) && {
    printf "To: %(%c)T > Cond: %(%c)T\n" $todate $cond
    break
}

Профільний баш містить хорошу демонстрацію того, як використання date -f -може зменшити потребу в ресурсах.
Ф. Хаурі

0

дати - це рядки, а не цілі числа; ви не можете порівняти їх зі стандартними арифметичними операторами.

один підхід, який ви можете використовувати, якщо роздільник гарантовано буде -:

IFS=-
read -ra todate <<<"$todate"
read -ra cond <<<"$cond"
for ((idx=0;idx<=numfields;idx++)); do
  (( todate[idx] > cond[idx] )) && break
done
unset IFS

Це працює так само, як і раніше bash 2.05b.0(1)-release.


0

Ви також можете використовувати вбудовану функцію mysql для порівняння дат. Це дає результат за 'дні'.

from_date="2015-01-02"
date_today="2015-03-10"
diff=$(mysql -u${DBUSER} -p${DBPASSWORD} -N -e"SELECT DATEDIFF('${date_today}','${from_date}');")

Просто вставте значення $DBUSERта $DBPASSWORDзмінні відповідно до ваших.


0

FWIW: На Mac, здається, що введення дати, як-от "2017-05-05" в дату, додасть поточний час до дати, і ви отримаєте інше значення щоразу, коли перетворюєте його в епоху. щоб отримати більш послідовний час епохи для фіксованих дат, включайте фіктивні значення для годин, хвилин і секунд:

date -j -f "%Y-%m-%d %H:%M:%S" "2017-05-05 00:00:00" +"%s"

Це може бути дивна версія, обмежена версією дати, що постачається з macOS .... тестована на macOS 10.12.6.


0

Відповідь @Gilles ("Оператор -ge працює лише з цілими числами"), ось приклад.

curr_date=$(date +'%Y-%m-%d')
echo "$curr_date"
2019-06-20

old_date="2019-06-19"
echo "$old_date"
2019-06-19

datetimeцілих конверсій на відповідь epochза відповідь @ mike-q на https://stackoverflow.com/questions/10990949/convert-date-time-string-to-epoch-in-bash :

curr_date_int=$(date -d "${curr_date}" +"%s")
echo "$curr_date_int"
1561014000

old_date_int=$(date -d "${old_date}" +"%s")
echo "$old_date_int"
1560927600

"$curr_date"є більшим, ніж (останнім часом) "$old_date", але це вираз неправильно оцінюється як False:

if [[ "$curr_date" -ge "$old_date" ]]; then echo 'foo'; fi

... показано явно, тут:

if [[ "$curr_date" -ge "$old_date" ]]; then echo 'foo'; else echo 'bar'; fi
bar

Порівняння цілих чисел:

if [[ "$curr_date_int" -ge "$old_date_int" ]]; then echo 'foo'; fi
foo

if [[ "$curr_date_int" -gt "$old_date_int" ]]; then echo 'foo'; fi
foo

if [[ "$curr_date_int" -lt "$old_date_int" ]]; then echo 'foo'; fi

if [[ "$curr_date_int" -lt "$old_date_int" ]]; then echo 'foo'; else echo 'bar'; fi
bar

0

Причиною, чому це порівняння не працює добре з дефісом рядка дати, - оболонка передбачає число з провідним 0 - восьмеричним числом, у цьому випадку місяць "07". Запропоновано різні рішення, але найшвидше і найпростіше - викреслити дефіс. Bash має функцію підстановки рядків, яка робить це швидко і просто, тоді порівняння може бути виконано як арифметичний вираз:

todate=2013-07-18
cond=2013-07-15

if (( ${todate//-/} > ${cond//-/} ));
then
    echo larger
fi
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.