Швидко обчисліть різниці в датах


84

Мені часто хочеться зробити кілька швидких підрахунків дат, таких як:

  • Яка різниця між цими двома датами?
  • Яка дата через n тижнів після цієї дати?

Зазвичай я відкриваю календар і рахую дні, але думаю, що має бути програма / сценарій, з допомогою якого я можу робити такі обчислення. Будь-які пропозиції?


3
Дивіться також Інструмент в UNIX для віднімання дат, коли дата GNU недоступна.
Жиль

Відповіді:


106

"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 , один з днів триватиме лише 23 години; ви можете компенсувати, додавши до суми ½ дня.

echo $(( (((d1-d2) > 0 ? (d1-d2) : (d2-d1)) + 43200) / 86400 )) days

40

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

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

3
+1 ваш інструмент рок (хоча dateadd -i '%m%d%Y' 01012015 +1dце, здається, не працює, він просто висить там нескінченно ... він працює, якщо специфікації дати розділені знаком, будь-яким символом ... будь-яка ідея, що не так?)
don_crissti

2
@don_crissti Аналізатор не зміг розрізнити дати та тривалість, що стосуються лише цифр, це зафіксовано в поточному головному (d0008f98)
hroptatyr

Чи є у вас бінарне розповсюдження датників?
мош

@mosh ні, і я не маю можливості спробувати.
hroptatyr

Пізно на вечірку тут, але датаутилі на cygwin, якщо вам потрібно використовувати в Windows.
gregb212

30

Приклад пітона для обчислення кількості днів, якими я ходив по планеті:

$ 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 "від дати імпорту date date як 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аргумент може передувати @вказати «секунди з початком епохи» формату.


7
З датою GNU date -d @1234567890перетворюється за секунди з епохи до будь-якого формату дати, який ви вказали.
Жиль

1
@Gilles: це геніально. Неможливо знайти це на сторінці сторінки. Де ви це дізналися?
MattBianco

1
@MattBianco, див. info date, Особливо Секунди після вузла " Епоха ": "Якщо ви передуєте цифрі за допомогою" @ ", це відображає внутрішню
позначку

8

Це з’явилося під час використання date -d "$death_date - $y years - $m months - $d days"дати народження (для генеалогії). Ця команда WRONG. Місяці не всі однакові, так що (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виклику не можна, бо високосні роки, що змінюють тривалість лютого.)


7

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

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

щоб отримати кількість днів (140, оскільки 1900 рік не був високосним) між датами, але, звичайно, ви також можете зробити те ж саме:

17:12:45 − 08:45:12

дають 8.4591667годинник або, якщо ви встановите вихід на час форматування 8:27:33.


3
Це здорово, і навіть більше, тому що qalculate робить є CLI. Спробуйте qalc, значить help days.
Sparhawk

qcalcприклад: qalc -t 'days(1900-05-21, 1900-01-01)'-> 140
sierrasdetandil

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.)


3

Іншим способом обчислити різницю між двома датами одного і того ж календарного року ви можете скористатися цим:

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 присвоюється значення від 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 з двома аргументами. Спробуйте це: 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.
Шість

Вам не потрібно $всередині арифметичного вираження: $((DATElastnum - DATEfirstnum))також буде працювати.
Руслан

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 - дата)


1

dateiff.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

1

відповідь Кемха стосується більшості його, але ми можемо покращити справу з округленням, часовими поясами тощо, плюс ми отримаємо додаткову точність та можливість вибору наших одиниць:

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змінює дату-час на секунду.наносекунд з 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 дня як розумне наближення.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.