Вычтите время, используя дату и bash


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 Calendar 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)
OLORIN

2
В dateсоответствии со стандартом ISO 8601 может быть возможной ошибкой , если часовой пояс не указан, следует использовать местное время (часовой пояс), а в случае арифметической операции это не так. Очень странная проблема для меня, хотя.
конфетти

1
@confetti обнаружил проблему: - 2 hoursздесь указывается часовой пояс.
Олорин

2
Вау. Теперь это как-то имеет смысл. Ну, по крайней мере, это объясняет это. Большое спасибо за то, что выяснили это. Для меня это звучит так, как будто их парсер нуждается в обновлении, хотя с таким форматом и пробелами, как этот, вы не хотите указывать часовой пояс через `- 2 часа`, и это, безусловно, вызывает путаницу. Если они не хотят обновлять парсер, по крайней мере, руководство должно получить примечание об этом.
конфетти

1
Сегодня я узнал о --debugвыборе даты ! Отличное объяснение.
Джефф Шаллер

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 говорит мне то же самое, «Нормальное» время отличается из-за моего собственного часового пояса.

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

$ 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, которое является единственным разумным способом сделать арифметику времени
Руи Ф. Рибейро

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

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

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

1
@nxnev Согласен, если вы пишете сценарий для публикации / публикации. Вводные слова «если вы знаете свой часовой пояс», помимо буквального значения, подразумевают случайное / личное использование. Кто-то, кто публикует сценарий, полагаясь на что-то столь же хрупкое, как зная только tz своей собственной системы, вероятно, не должен быть в игре совместного использования сценариев. :) С другой стороны, я делаю / буду использовать свою собственную команду во всех моих личных средах.
Слой

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 недель, следовательно, последний понедельник», вызывают проблемы ;-)

(Здесь есть еще один маленький beartrap, 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
Или, ради тщательности, ничего не оставляйте после даты и времени, переупорядочивая аргументы: date -d "-5 часов 2018-12-10 00:00:00" `
B Layer

2

Это решение легко понять, но немного сложнее, поэтому я показываю его в виде шелл-скрипта.

  • преобразовать в «секунды с 1970-01-01 00:00:00 UTC»
  • добавить или вычесть разницу
  • преобразовать обратно в удобочитаемый формат с помощью последней dateкомандной строки

Shellscript:

#!/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.