Быстро рассчитать разницу дат

84

Я часто хочу сделать некоторые быстрые вычисления даты, такие как:

  • В чем разница между этими двумя датами?
  • Какая дата n недель после этой другой даты?

Я обычно открываю календарь и считаю дни, но я думаю, что должна быть программа / скрипт, который я мог бы использовать для таких вычислений. Какие-либо предложения?

Даниэль Куллманн
источник
3
См. Также Инструмент в UNIX для вычитания дат, когда дата GNU недоступна.
Жиль

Ответы:

107

«N недель после даты» легко с датой GNU (1):

$ date -d 'now + 3 weeks'
Tue Dec  6 23:58:04 EST 2011
$ date -d 'Aug 4 + 3 weeks'
Thu Aug 25 00:00:00 EST 2011
$ date -d 'Jan 1 1982 + 11 weeks'
Fri Mar 19 00:00:00 EST 1982

Я не знаю простого способа вычисления разницы между двумя датами, но вы можете немного обдумать дату (1) с помощью функции оболочки.

datediff() {
    d1=$(date -d "$1" +%s)
    d2=$(date -d "$2" +%s)
    echo $(( (d1 - d2) / 86400 )) days
}
$ datediff '1 Nov' '1 Aug'
91 days

Поменяйте местами d1и, d2если вы хотите, чтобы вычисление даты происходило иначе, или сделайте немного интереснее, чтобы это не имело значения. Кроме того, в случае перехода не-DST к DST в этом интервале один из дней будет длиться всего 23 часа; Вы можете компенсировать, добавив ½ дня к сумме.

echo $(( (((d1-d2) > 0 ? (d1-d2) : (d2-d1)) + 43200) / 86400 )) days
CAMH
источник
42

Для набора портативных инструментов попробуйте мой собственный dateutils . Ваши два примера будут сводиться к одной строке:

ddiff 2011-11-15 2012-04-11
=>
  148

или в неделях и днях:

ddiff 2011-11-15 2012-04-11 -f '%w %d'
=>
  21 1

а также

dadd 2011-11-15 21w
=>
  2012-04-10
hroptatyr
источник
3
+1 ваши инструменты качаются (хотя dateadd -i '%m%d%Y' 01012015 +1d, кажется, не работает, он просто висит там неопределенно долго ... он работает, если спецификации даты разделены символом, любым символом ... есть идеи, что случилось?)
don_crissti
2
@don_crissti Синтаксический анализатор не может различить даты и длительности только для цифр, он исправлен в текущем мастере (d0008f98)
hroptatyr
У вас есть бинарный дистрибутив окна dateutils?
мош
@ нет, и у меня нет возможности попробовать.
Гроптатыр,
Поздно здесь, но dateutils на Cygwin, если вам нужно использовать на Windows.
gregb212
30

Пример Python для расчета количества дней, которые я прошел по планете:

$ python
>>> from datetime import date as D
>>> print (D.today() - D(1980, 6, 14)).days
11476
Даниэль Нэслунд
источник
2
На всякий случай, если кто-то захочет, чтобы это велось как единая команда, вместо ввода интерактивного переводчика: ychaouche@ychaouche-PC ~ $ python -c "from datetime import date as d; print (d.today() - d(1980, 6, 14)).days" 12813 ychaouche@ychaouche-PC ~ $
ychaouche
1
это работает для меня python3 -c "из даты и времени импорта даты как d; print (d.today () - d (2016, 1, 9))" дни в конце не требуются
Kiran K Telukunta
13

Я обычно предпочитаю иметь время / дату в формате unix utime (количество секунд с начала эпохи семидесятых до UTC). Таким образом, это всегда сводится к простому вычитанию или сложению секунд.

Обычно проблема заключается в преобразовании даты / времени в этот формат.

В оболочке / скрипте вы можете получить его с помощью date '+%s' На момент написания, текущее время 1321358027.

Сравнивать с 2011-11-04 (мой день рождения) date '+%s' -d 2011-11-04, уступая 1320361200. Вычесть: expr 1321358027 - 1320361200дает 996827секунды, что expr 996827 / 86400= 11 дней назад.

Преобразование из utime (формат 1320361200) в дату очень просто сделать, например, в C, PHP или perl, используя стандартные библиотеки. В GNU dateк -dаргументу можно добавить префикс @«секунды с начала эпохи».

MattBianco
источник
7
С GNU date date -d @1234567890конвертирует из секунд с начала эпохи в любой формат даты, который вы укажете.
Жиль
1
@ Жиль: это великолепно. Не могу найти это на странице руководства. Где ты это узнал?
MattBianco
1
@MattBianco, см. info date, В частности, секунды, прошедшие с узла « Эпоха» : «Если перед числом стоит` @ ', он представляет
метку
8

Если вам подходит графический инструмент, я искренне рекомендую qalculate(калькулятор с акцентом на преобразование единиц, он поставляется с интерфейсом GTK и KDE, IIRC). Там вы можете сказать, например,

days(1900-05-21, 1900-01-01)

чтобы получить количество дней (140, с 1900 года не было високосным годом) между датами, но, конечно, вы можете сделать то же самое для времен:

17:12:45 − 08:45:12

дают 8.4591667часы или, если вы установите выход на время форматирования 8:27:33.

quazgar
источник
3
Это здорово, и даже больше, потому что qalculate делает есть CLI. Попробуй qalcтогда help days.
Sparhawk
qcalcпример: qalc -t 'days(1900-05-21, 1900-01-01)'-> 140
sierrasdetandil
8

Это возникло при использовании, date -d "$death_date - $y years - $m months - $d days"чтобы получить дату рождения (для генеалогии). Эта команда НЕПРАВИЛЬНА. Месяцы не имеют одинаковую длину, поэтому (date + offset) - offset != date. Возраст в году / месяце / дне - это показатели, идущие вперед от даты рождения.

$ date --utc -d 'mar 28 1867 +72years +11months +2days'
Fri Mar  1 00:00:00 UTC 1940

$ date --utc -d 'mar 1 1940 -72years -11months -2days'
Sat Mar 30 00:00:00 UTC 1867
# (2 days later than our starting point)

Дата дает правильный вывод в обоих случаях, но во втором случае вы задавали неправильный вопрос. Это имеет значение, КОТОРЫЕ 11 месяцев в году охватывают +/- 11, прежде чем добавлять / вычитать дни. Например:

$ date --utc -d 'mar 31 1939  -1month'
Fri Mar  3 00:00:00 UTC 1939
$ date --utc -d 'mar 31 1940  -1month' # leap year
Sat Mar  2 00:00:00 UTC 1940
$ date --utc -d 'jan 31 1940  +1month' # leap year
Sat Mar  2 00:00:00 UTC 1940

Чтобы вычитание было обратной операцией сложения, порядок операций должен быть обратным. Добавление добавляет лет, ТОГДА месяцев, ТОГДА дней. Если вычитание использовало противоположный порядок, то вы вернетесь к исходной точке. Это не так, так что нет, если смещение дней пересекает границу месяца в другом месяце длины.

Если вам нужно работать в обратном направлении с конечной даты и возраста, вы можете сделать это с помощью нескольких вызовов date. Сначала вычтите дни, затем месяцы, а затем годы. (Я не думаю, что безопасно объединять годы и месяцы в одном dateвызове, потому что високосные годы меняют продолжительность февраля.)

Питер Кордес
источник
4

Другой способ рассчитать разницу между двумя датами одного и того же календарного года, вы можете использовать это:

date_difference.sh
1  #!/bin/bash
2  DATEfirstnum=`date -d "2014/5/14" +"%j"`
3  DATElastnum=`date -d "12/31/14" +"%j"`
4  DAYSdif=$(($DATElastnum - $DATEfirstnum))
5  echo "$DAYSdif"
  • Строка 1 объявляет оболочке, какой интерпретатор использовать.
  • Строка 2 присваивает значение out из dateпеременной DATEfirstnum. -dФлаг отображает строку в формате времени в этом случае 14 мая 2014 года и +"%j"говорит dateформатировать вывод только на день года (1-365).
  • Строка 3 такая же, как и строка 2, но с другой датой и другим форматом для строки, 31 декабря 2014 г.
  • Строка 4 присваивает значение DAYSdifразности двух дней.
  • В строке 5 отображается значение DAYSdif.

Это работает с версией GNU date, но не с версией PC-BSD / FreeBSD. Я установил coreutilsиз дерева портов и использовал команду /usr/local/bin/gdateвместо этого.

Джастин Холкомб
источник
1
Этот скрипт не будет работать. В последней строке приведена опечатка, которая содержит пробелы вокруг присваивания переменных, поэтому bash пытается запустить программу с именем DATEfirst name с двумя аргументами. Попробуйте это: DATEfirstnum=$(date -d "$1" +%s) DATElastnum=$(date -d "$2" +%s) Кроме того, этот скрипт не сможет рассчитать разницу между двумя разными годами. +%jотносится к дню года (001..366), поэтому ./date_difference.sh 12/31/2001 12/30/2014выводит -1. Как уже отмечалось в других ответах, вам нужно конвертировать обе даты в секунды с 1970-01-01 00:00:00 UTC.
6
Вам не нужно $внутри арифметического выражения: $((DATElastnum - DATEfirstnum))также будет работать.
Руслан
3

Я часто использую SQL для вычисления даты. Например, MySQL , PostgreSQL или SQLite :

bash-4.2$ mysql <<< "select datediff(current_date,'1980-06-14')"
datediff(current_date,'1980-06-14')
11477

bash-4.2$ psql <<< "select current_date-'1980-06-14'"
 ?column? 
----------
    11477
(1 row)

bash-4.2$ sqlite2 <<< "select julianday('now')-julianday('1980-06-14');"
11477.3524537035

В других случаях я просто в настроении для JavaScript. Например, SpiderMonkey , WebKit , Seed или Node.js :

bash-4.2$ js -e 'print((new Date()-new Date(1980,5,14))/1000/60/60/24)'
11477.477526192131

bash-4.2$ jsc-1 -e 'print((new Date()-new Date(1980,5,14))/1000/60/60/24)'
11477.47757960648

bash-4.2$ seed -e '(new Date()-new Date(1980,5,14))/1000/60/60/24'
11477.4776318287

bash-4.2$ node -pe '(new Date()-new Date(1980,5,14))/1000/60/60/24'
11624.520061481482

(Будьте осторожны при передаче месяца в Dateконструктор объекта JavaScript . Начинается с 0.)

manatwork
источник
3

С помощью решений dannas это можно сделать в одну строку с помощью следующего кода:

python -c "from datetime import date as d; print(d.today() - d(2016, 7, 26))"

(Работает как в Python 2.x, так и в Python 3.)

Киран К Телукунта
источник
1
Вы можете редактировать свой пост вместо комментариев
Стивен Раух
3

dateи bash может делать различия даты (показаны параметры OS X). Поместите последнюю дату первой.

echo $((($(date -jf%D "04/03/16" +%s) - $(date -jf%D "03/02/16" +%s)) / 86400))
# 31
Дейв
источник
1

Также есть расчеты времени блока GNU в сочетании с датой GNU:

$ gunits $(gdate +%s)sec-$(gdate +%s -d -1234day)sec 'yr;mo;d;hr;min;s'
        3 yr + 4 mo + 16 d + 12 hr + 37 min + 26.751072 s
$ gunits $(gdate +%s -d '2015-1-2 3:45:00')sec-$(gdate +%s -d '2013-5-6 7:43:21')sec 'yr;mo;d;hr;min;s'
        1 yr + 7 mo + 27 d + 13 hr + 49 min + 26.206759 s

(gunits это единицы в Linux, gdate это дата)

Larry
источник
1

datediff.sh на github: gist

#!/bin/bash
#Simplest calculator two dates difference. By default in days

# Usage:
# ./datediff.sh first_date second_date [-(s|m|h|d) | --(seconds|minutes|hours|days)]

first_date=$(date -d "$1" "+%s")
second_date=$(date -d "$2" "+%s")

case "$3" in
"--seconds" | "-s") period=1;;
"--minutes" | "-m") period=60;;
"--hours" | "-h") period=$((60*60));;
"--days" | "-d" | "") period=$((60*60*24));;
esac

datediff=$(( ($first_date - $second_date)/($period) ))
echo $datediff
user178063
источник
1

Ответ camh заботится о большей части этого, но мы можем улучшить работу с округлением, часовыми поясами и т. д., плюс мы получаем дополнительную точность и возможность выбирать наши юниты:

datediff() {
#convert dates to decimal seconds since 1970-01-01 00:00:00 UTC
date1seconds=$(date +%s.%N -d "$date1")
date2seconds=$(date +%s.%N -d "$date2")

#Calculate time difference in various time units
timeseconds=$(printf "%0.8f\n" $(bc <<<"scale=9; ($date2sec-$date1sec)"))
timeminutes=$(printf "%0.8f\n" $(bc <<<"scale=9; ($date2sec-$date1sec)/60"))
  timehours=$(printf "%0.8f\n" $(bc <<<"scale=9; ($date2sec-$date1sec)/3600"))
   timedays=$(printf "%0.8f\n" $(bc <<<"scale=9; ($date2sec-$date1sec)/86400"))
  timeweeks=$(printf "%0.8f\n" $(bc <<<"scale=9; ($date2sec-$date1sec)/604800"))
}

-dговорит, dateчто мы предоставляем дату-время для конвертации. +%s.%Nизменяет дату и время на seconds.nanoseconds с 1970-01-01 00:00:00 UTC. bcвычисляет разницу между двумя числами, а коэффициент деления дает нам различные единицы. printfдобавляет 0 перед десятичной запятой, если необходимо ( bcне делает), и обеспечивает округление до ближайшего ( bcтолько усекает). Вы также можете использовать awk.

Теперь давайте запустим это с помощью классного теста,

date1='Tue Jul  9 10:18:04.031 PST 2020'
date2='Wed May  8 15:19:34.447 CDT 2019'
datediff "$date1" "$date2"

echo $timeseconds seconds
-36971909.584000000 seconds

echo $timeminutes minutes
-616198.493066667 minutes

echo $timehours hours
-10269.974884444 hours

echo $timedays days
-427.915620185 days

echo $timeweeks weeks
-61.130802884 weeks

Обратите внимание, что, поскольку длина месяца или года не всегда одинакова, здесь нет ни одного «правильного» ответа, хотя в настоящее время можно использовать 365,24 дня в качестве разумного приближения.

ТТТ
источник
0

Вы можете использовать библиотеку awk Velor :

velour -n 'print t_secday(t_utc("2017-4-12") - t_utc("2017-4-5"))'

Или же:

velour -n 'print t_secday(t_utc(ARGV[1]) - t_utc(ARGV[2]))' 2017-4-12 2017-4-5

Результат:

7
Стивен Пенни
источник