Відніміть час, використовуючи дату та башти


18

Усі інші запитання в мережі SE стосуються сценаріїв, коли дата або вважається рівною now( Q ), або де вказана лише дата ( Q ).

Я хочу зробити дату та час, а потім відняти час.
Ось що я спробував спочатку:

date -d "2018-12-10 00:00:00 - 5 hours - 20 minutes - 5 seconds"

Це призводить до 2018-12-10 06:39:55- Це додало 7 годин. Потім відняли 20:05 хвилин.

Прочитавши manта infoсторінку date, я подумав, що це виправлено:

date -d "2018-12-10T00:00:00 - 5 hours - 20 minutes - 5 seconds"

Але, той же результат. Звідки це взагалі 7 годин?

Я спробував і інші побачення, тому що я подумав, що, можливо, у цей день у нас було 7200 високосних секунд, хто знає, хаха. Але ті ж результати.

Ще кілька прикладів:

$ date -d "2018-12-16T00:00:00 - 24 hours" +%Y-%m-%d_%H:%M:%S
2018-12-17_02:00:00

$ date -d "2019-01-19T05:00:00 - 2 hours - 5 minutes" +%Y-%m-%d_%H:%M:%S
2019-01-19_08:55:00

Але тут стає цікаво. Якщо я пропущу час на введення, він працює добре:

$ date -d "2018-12-16 - 24 hours" +%Y-%m-%d_%H:%M:%S
2018-12-15_00:00:00

$ date -d "2019-01-19 - 2 hours - 5 minutes" +%Y-%m-%d_%H:%M:%S
2019-01-18_21:55:00

$ date --version
date (GNU coreutils) 8.30

Що я пропускаю?

Оновлення: я додав в Zкінці, і це змінило поведінку:

$ date -d "2019-01-19T05:00:00Z - 2 hours" +%Y-%m-%d_%H:%M:%S
2019-01-19_04:00:00

Я все ще розгублений. На інформаційній сторінці GNU про дату не так багато .

Я здогадуюсь, що це проблема часового поясу, але цитую The Wiki Wiki за ISO 8601 :

Якщо інформація про відношення UTC не подана з поданням часу, час вважається місцевим часом.

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

$ date -d "2019-01-19T05:00:00" +%Y-%m-%d_%H:%M:%S
2019-01-19_05:00:00

Тож якщо це справді питання часового поясу, звідки це безумство?


Відповіді:


20

Цей останній приклад мав би вам прояснити речі: часові пояси .

$ TZ=UTC date -d "2019-01-19T05:00:00Z - 2 hours" +%Y-%m-%d_%H:%M:%S
2019-01-19_03:00:00
$ TZ=Asia/Colombo date -d "2019-01-19T05:00:00Z - 2 hours" +%Y-%m-%d_%H:%M:%S 
2019-01-19_08:30:00

Оскільки вихідний сигнал чітко змінюється залежно від часового поясу, я б підозрював деякі неочевидні за замовчуванням, прийняті для часового рядка без вказаного часового поясу. Випробовуючи пару значень, здається, це UTC-05: 00 , хоча я не впевнений, що це таке.

$ TZ=UTC date -d "2019-01-19T05:00:00 - 2 hours" +%Y-%m-%d_%H:%M:%S%Z
2019-01-19_08:00:00UTC
$ TZ=UTC date -d "2019-01-19T05:00:00Z - 2 hours" +%Y-%m-%d_%H:%M:%S%Z
2019-01-19_03:00:00UTC
$ TZ=UTC date -d "2019-01-19T05:00:00" +%Y-%m-%d_%H:%M:%S%Z           
2019-01-19_05:00:00UTC

Він використовується лише при виконанні арифметики дати.


Здається, проблема полягає в тому, що - 2 hoursвона не сприймається як арифметична, а як специфікатор часового поясу :

# TZ=UTC date -d "2019-01-19T05:00:00 - 2 hours" +%Y-%m-%d_%H:%M:%S%Z --debug
date: parsed datetime part: (Y-M-D) 2019-01-19 05:00:00 UTC-02
date: parsed relative part: +1 hour(s)
date: input timezone: parsed date/time string (-02)
date: using specified time as starting value: '05:00:00'
date: starting date/time: '(Y-M-D) 2019-01-19 05:00:00 TZ=-02'
date: '(Y-M-D) 2019-01-19 05:00:00 TZ=-02' = 1547881200 epoch-seconds
date: after time adjustment (+1 hours, +0 minutes, +0 seconds, +0 ns),
date:     new time = 1547884800 epoch-seconds
date: timezone: TZ="UTC" environment value
date: final: 1547884800.000000000 (epoch-seconds)
date: final: (Y-M-D) 2019-01-19 08:00:00 (UTC)
date: final: (Y-M-D) 2019-01-19 08:00:00 (UTC+00)
2019-01-19_08:00:00UTC

Таким чином, не тільки не робиться арифметика, але, здається, щоденне коригування часу на 1 годину на час, що веде до дещо безглуздого для нас часу.

Це також стосується додатку:

# TZ=UTC date -d "2019-01-19T05:00:00 + 5:30 hours" +%Y-%m-%d_%H:%M:%S%Z --debug
date: parsed datetime part: (Y-M-D) 2019-01-19 05:00:00 UTC+05:30
date: parsed relative part: +1 hour(s)
date: input timezone: parsed date/time string (+05:30)
date: using specified time as starting value: '05:00:00'
date: starting date/time: '(Y-M-D) 2019-01-19 05:00:00 TZ=+05:30'
date: '(Y-M-D) 2019-01-19 05:00:00 TZ=+05:30' = 1547854200 epoch-seconds
date: after time adjustment (+1 hours, +0 minutes, +0 seconds, +0 ns),
date:     new time = 1547857800 epoch-seconds
date: timezone: TZ="UTC" environment value
date: final: 1547857800.000000000 (epoch-seconds)
date: final: (Y-M-D) 2019-01-19 00:30:00 (UTC)
date: final: (Y-M-D) 2019-01-19 00:30:00 (UTC+00)
2019-01-19_00:30:00UTC

Відладжуючи трохи більше, аналіз здається таким: 2019-01-19T05:00:00 - 2( -2будучи часовим поясом) та hours(= 1 година), маючи на увазі додавання. Це стає легше побачити, якщо ви замість цього використовуєте хвилини:

# TZ=UTC date -d "2019-01-19T05:00:00 - 2 minutes" +%Y-%m-%d_%H:%M:%S%Z --debug
date: parsed datetime part: (Y-M-D) 2019-01-19 05:00:00 UTC-02
date: parsed relative part: +1 minutes
date: input timezone: parsed date/time string (-02)
date: using specified time as starting value: '05:00:00'
date: starting date/time: '(Y-M-D) 2019-01-19 05:00:00 TZ=-02'
date: '(Y-M-D) 2019-01-19 05:00:00 TZ=-02' = 1547881200 epoch-seconds
date: after time adjustment (+0 hours, +1 minutes, +0 seconds, +0 ns),
date:     new time = 1547881260 epoch-seconds
date: timezone: TZ="UTC" environment value
date: final: 1547881260.000000000 (epoch-seconds)
date: final: (Y-M-D) 2019-01-19 07:01:00 (UTC)
date: final: (Y-M-D) 2019-01-19 07:01:00 (UTC+00)
2019-01-19_07:01:00UTC

Отже, ну арифметика дати проводиться, тільки не та, про яку ми просили. ¯ \ (ツ) / ¯


1
@confetti це справді; Я думаю, що цей часовий пояс за замовчуванням використовується лише для додавання / віднімання (порівняння TZ=UTC date -d "2019-01-19T05:00:00Z - 2 hours" +%Y-%m-%d_%H:%M:%Sпроти TZ=UTC date -d "2019-01-19T05:00:00 - 2 hours" +%Y-%m-%d_%H:%M:%S)
Олорін

2
Можливо, можлива помилка date, згідно зі стандартом ISO 8601, якщо не вказаний часовий пояс, слід припустити місцевий час (пояс), що у випадку арифметичної операції це не так. Дуже дивне питання для мене, хоча.
конфетті

1
@confetti знайшов проблему: - 2 hoursтут приймається як специфікатор часового поясу.
Олорін

2
Ого. Тепер це якось має сенс. Ну, принаймні це пояснює. Дуже дякую, що вияснили це. Мені це здається, що їх аналізатор потребує оновлення, хоча так само чітко з таким форматом і пробілом, що ви не хочете вказувати часовий пояс на "2 години", і це, безумовно, викликає плутанину. Якщо вони не хочуть оновлювати парсер, принаймні посібник повинен отримати примітку про це.
конфетті

1
Сьогодні я дізнався про --debugваріант побачення ! Чудове пояснення.
Jeff Schaller

6

Працює правильно, коли ви перетворюєте дату введення в ISO 8601 спочатку:

$ date -d "$(date -Iseconds -d "2018-12-10 00:00:00") - 5 hours - 20 minutes - 5 seconds"
So 9. Dez 18:39:55 CET 2018

Дякую, це справді працює, але чи є це пояснення? Я додав ще більше інформації про річ ISO 8601 до свого запитання, і я дійсно не бачу, де можна dateбуло б це зіпсувати, як без жодних віднімань, часовий пояс залишається недоторканим, і все так само, як очікувалося, без необхідності подавати інформація про часовий пояс або конверсія.
конфетті

Не можу сказати, вибачте. Якщо хтось інший міг би відповісти на це, я був би радий, бо мені теж цікаво.
pLumo

Я також буду радий, але зараз це прийму, бо це вирішує мою проблему!
конфетті

3
Це працює, тому що date -Iтакож виводиться специфікатор часового поясу (наприклад 2018-12-10T00:00:00+02:00), який виправляє проблему, описану у відповіді
Олоріна

6

TLDR: Це не помилка. Ви щойно знайшли одне з найтонших, але задокументованих способів поведінки date. Під час виконання арифметики за часом dateвикористовуйте формат, незалежний від часового поясу (наприклад, час Unix) або дуже уважно читайте документацію, щоб знати, як правильно використовувати цю команду.


GNU dateвикористовує ваші системні налаштування ( TZзмінна середовище або, якщо вона не встановлена, система за замовчуванням), щоб визначити часовий пояс як дати, що подається з параметром -d/, так --dateі дати, повідомленої +formatаргументом. Варіант також дозволяє перевизначити часовий пояс для свого власного опціонного-аргументу , але це не скасовує часовий пояс .--date+formatОсь корінь плутанини, ІМХО.

Враховуючи, що мій часовий пояс UTC-6, порівняйте наступні команди:

$ date -d '1970-01-01 00:00:00' '+Normal: %F %T %:z%nUnix: %s'
Normal: 1970-01-01 00:00:00 -06:00
Unix: 21600
$ date -d '1970-01-01 00:00:00 UTC' '+Normal: %F %T %:z%nUnix: %s'
Normal: 1969-12-31 18:00:00 -06:00
Unix: 0
$ TZ='UTC0' date -d '1970-01-01 00:00:00 UTC' '+Normal: %F %T %:z%nUnix: %s'
Normal: 1970-01-01 00:00:00 +00:00
Unix: 0

Перший використовує мій часовий пояс і для, -dі для +format. У другому використовується UTC для, -dале мій часовий пояс для +format. Третій використовує UTC для обох.

Тепер порівняйте такі прості операції:

$ date -d '1970-01-01 00:00:00 UTC +1 day' '+Normal: %F %T %:z%nUnix: %s'
Normal: 1970-01-01 18:00:00 -06:00
Unix: 86400
$ TZ='UTC0' date -d '1970-01-01 00:00:00 UTC +1 day' '+Normal: %F %T %:z%nUnix: %s'
Normal: 1970-01-02 00:00:00 +00:00
Unix: 86400

Навіть якщо час Unix каже мені те саме, час "Normal" відрізняється через мій власний часовий пояс.

Якщо я хотів зробити ту саму операцію, але використовуючи виключно свій часовий пояс:

$ TZ='CST+6' date -d '1970-01-01 00:00:00 -06:00 +1 day' '+Normal: %F %T %:z%nUnix: %s'
Normal: 1970-01-02 00:00:00 -06:00
Unix: 108000

4
Запропоновано згадати епоху / час Unix, який imo є єдиним розумним способом зробити арифметику часу
Rui F Ribeiro

1
TL; DR Якщо ви просто знаєте, що ваш місцевий час зміщений із зулуського часу (наприклад, західне узбережжя США становить -8 годин або -08:00), просто додайте його до введення дати-часу ... нічого іншого не можна поспішати. Приклад: date -d '2019-02-28 14:05:36-08:00 +2 days 4 hours 3 seconds' +'local: %F %T'дає місцеві: 2019-03-02 18:05:36
B Layer

1
@BLayer Ви маєте рацію, це працює так, як очікувалося в тому сценарії. Але уявіть ситуацію, коли даний сценарій із використанням цього підходу виконується в місці з іншим часовим поясом. Хоча технічно правильний результат може виглядати неправильним залежно від інформації, наданої +formatаргументом. Наприклад, в моїй системі, то ж команда виводить: local: 2019-03-02 20:05:39(якщо додати %:zдо +format, то стає очевидним , що інформація вірна і розбіжність пов'язана з часових поясів).
nxnev

@BLayer IMHO, кращим загальним підходом було б використання одного і того ж часового поясу як для, так -dі для +formatUnix часу, як я вже сказав у відповіді, щоб уникнути несподіваних результатів.
nxnev

1
@ nxnev Погодився, якщо ви пишете сценарій для публікації / спільного використання. Вступні слова "якщо ти знаєш свій власний часовий пояс", крім того, що мають буквальне значення, повинні означати випадкове / особисте використання. Хтось, хто публікує сценарій, покладається на щось настільки надумане, як, що знає лише тз своєї власної системи, ймовірно, не повинен бути в грі спільного використання сценаріїв. :) З іншого боку, я / використовую власну команду у всіх своїх особистих умовах.
B Layer

4

GNU date підтримує просту арифметику дати, хоча епохальні розрахунки часу, як показано у відповіді @sudodus, іноді зрозуміліші (і більш портативні).

Використання +/-, коли в часовій марці немає часового поясу, запускає спробу встановлення відповідності часовому поясу наступним чином, перш ніж щось інше буде проаналізовано.

Ось один із способів зробити це: використовувати "тому" замість "-":

$ date -d "2018-12-10 00:00:00 5 hours ago 20 minutes ago 5 seconds ago"
Sun Dec  9 18:39:55 GMT 2018

або

$ date -d "2018-12-10 00:00:00Z -5 hours -20 minutes -5 seconds"
Sun Dec  9 18:39:55 GMT 2018

(Хоча ви не можете довільно використовувати "Z", він працює у моїй зоні, але це робить часову позначку зони UTC / GMT - використовуйте власну зону або% z /% Z шляхом додавання ${TZ:-$(date +%z)} замість цього позначку часу.)

Додавання додаткових термінів у ці форми регулює час:

  • «5 годин тому» відняти 5 годин
  • "4 години" додати (неявно) 4 години
  • "3 години звідси" додати (явно) 3 години (не підтримується у старих версіях)

Можна використовувати безліч складних коригувань у будь-якому порядку (хоча відносні та змінні терміни, наприклад, "14 тижнів, отже, минулого понеділка", вимагають неприємностей ;-)

(Тут є ще один невеликий бортрап, dateвін завжди дасть дійсну дату, тому date -d "2019-01-31 1 month"дає 2019-03-03, як і "наступний місяць")

Враховуючи велику різноманітність форматів часу та дати, які підтримуються, розбір часових поясів обов'язково неохайний: це може бути суфікс з однією чи кількома літерами, година або година: зміщення хвилини, назва "Америка / Денвер" (або навіть ім'я файлу у випадку з TZ змінної).

Ваша 2018-12-10T00:00:00 версія не працює, тому що "T" - це лише роздільник, а не часовий пояс, додавання "Z" в кінці робить цю роботу (за умови правильності вибраної зони), як і очікувалося.

Дивіться: https://www.gnu.org/software/tar/manual/html_node/Date-input-formats.html та зокрема розділ 7.7.


1
Інші параметри включають: date -d "2018-12-10 00:00:00 now -5 hoursабо todayабо 0 hourзамість цього nowвсе, що говорить про dateте, що маркер після часу не є зміщенням часового поясу.
Стефан Шазелас

1
Або, заради ґрунтовності, не залишайте нічого після дати, переставляючи аргументи: дата -d "-5 годин 2018-12-10 00:00:00" `
B Layer

2

Це рішення легко зрозуміти, але трохи складніше, тому я показую це як оболонку.

  • перетворити на 'секунди з 1970-01-01 00:00:00 UTC'
  • додавати чи віднімати різницю
  • перетворити назад у людський читаний формат із заключним dateкомандним рядком

Оболонка:

#!/bin/bash

startdate="2018-12-10 00:00:00"

ddif="0"          # days
diff="-5:-20:-5"  # hours:minutes:seconds

#-----------------------------------------------------------------------------

ss1970in=$(date -d "$startdate" "+%s")  # seconds since 1970-01-01 00:00:00 UTC
printf "%11s\n" "$ss1970in"

h=${diff%%:*}
m=${diff#*:}
m=${m%:*}
s=${diff##*:}
difs=$(( (((ddif*24+h)*60)+m)*60+s ))
printf "%11s\n" "$difs"

ss1970ut=$((ss1970in + difs))  # add/subtract the time difference
printf "%11s\n" "$ss1970ut"

date -d "@$ss1970ut" "+%Y-%m-%d %H:%M:%S"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.