Odejmij czas za pomocą daty i bash

18

Wszystkie pozostałe pytania w sieci SE dotyczą scenariuszy, w których zakłada się, że data to now( Q ) lub w których określono tylko datę ( Q ).

Chcę podać datę i godzinę, a następnie odjąć czas od tego.
Oto, co próbowałem najpierw:

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

Powoduje to 2018-12-10 06:39:55- Dodano 7 godzin. Następnie odjęto 20:05 minut.

Po przeczytaniu mani infostronie date, myślałem, że to naprawić z tym:

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

Ale ten sam wynik. Skąd bierze się nawet 7 godzin?

Próbowałem również innych dat, ponieważ myślałem, że może mieliśmy 7200 sekund przestępnych tego dnia, kto zna lol. Ale te same wyniki.

Kilka innych przykładów:

$ 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

Ale tutaj robi się ciekawie. Jeśli pominę czas na wejściu, działa dobrze:

$ 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

czego mi brakuje?

Aktualizacja: dodałem Zna końcu i zmieniło to zachowanie:

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

Nadal jestem zdezorientowany. Na stronie informacyjnej GNU o dacie nie ma wiele na ten temat.

Zgaduję, że jest to problem związany ze strefą czasową, ale cytuję Wiki Wiki na temat ISO 8601 :

Jeśli nie podano informacji o relacji UTC z reprezentacją czasu, zakłada się, że czas jest czasem lokalnym.

Właśnie tego chcę. Mój czas lokalny jest również ustawiony poprawnie. Nie jestem pewien, dlaczego data miałaby w ogóle zadzierać ze strefą czasową w tym prostym przypadku, w którym dostarczam datę i godzinę i chcę ją odjąć. Czy nie powinien najpierw odjąć godzin od ciągu daty? Nawet jeśli najpierw przekształci go w datę, a następnie odejmie, jeśli pominę jakieś odejmowania, otrzymam dokładnie to, czego chcę:

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

Więc IF to naprawdę jest to kwestia stref czasowych, gdzie ma to szaleństwo pochodzi?

konfetti
źródło

Odpowiedzi:

20

Ten ostatni przykład powinien był dla ciebie wyjaśnić: strefy czasowe .

$ 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

Ponieważ dane wyjściowe wyraźnie różnią się w zależności od strefy czasowej, podejrzewam, że niektóre nieoczywiste wartości domyślne zostały przyjęte dla ciągu czasowego bez określonej strefy czasowej. Testując kilka wartości, wydaje się, że jest to UTC-05: 00 , chociaż nie jestem pewien, co to jest.

$ 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

Jest używany tylko podczas wykonywania arytmetyki dat.


Wydaje się, że problemem jest to, że nie- 2 hours jest traktowany jako arytmetyka, ale jako specyfikator strefy czasowej :

# 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

Tak więc nie tylko nie wykonuje się arytmetyki, ale wydaje się, że korekta czasu zajmuje 1 godzinę czasu dziennego , co prowadzi do nieco nonsensownego czasu dla nas.

Dotyczy to również dodania:

# 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

Trochę więcej debugowania wygląda na to, że parsowanie to: 2019-01-19T05:00:00 - 2( -2strefa czasowa) i hours(= 1 godzina), z domniemanym dodatkiem. Łatwiej jest sprawdzić, czy zamiast tego używasz minut:

# 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

No cóż, arytmetyka dat jest wykonywana, ale nie ta, o którą prosiliśmy. ¯ \ (ツ) / ¯

Olorin
źródło
1
@confetti rzeczywiście tak jest; Myślę, że ta domyślna strefa czasowa jest używana tylko podczas dodawania / odejmowania (porównywania TZ=UTC date -d "2019-01-19T05:00:00Z - 2 hours" +%Y-%m-%d_%H:%M:%Svs TZ=UTC date -d "2019-01-19T05:00:00 - 2 hours" +%Y-%m-%d_%H:%M:%S)
Olorin
2
Może to być możliwy błąd date, zgodnie z normą ISO 8601, jeśli nie podano strefy czasowej, należy założyć czas lokalny (strefę), co w przypadku operacji arytmetycznej tak nie jest. Jednak bardzo dziwny problem.
konfetti
1
@confetti znalazł problem: - 2 hoursjest brany jako specyfikator strefy czasowej tutaj.
Olorin
2
Łał. Teraz to w jakiś sposób ma sens. Cóż, przynajmniej to wyjaśnia. Dziękuję bardzo za zrozumienie tego. Dla mnie brzmi to tak, jakby ich parser wymaga aktualizacji, ponieważ wyraźnie przy takim formacie i białej spacji nie chcesz określać strefy czasowej przez `- 2 godziny` i to z pewnością powoduje zamieszanie. Jeśli nie chcą aktualizować analizatora składni, przynajmniej instrukcja powinna otrzymać informację na ten temat.
konfetti
1
Dzisiaj dowiedziałem się o --debugopcji randki ! Świetne wyjaśnienie.
Jeff Schaller
6

Działa poprawnie, jeśli najpierw przekonwertujesz datę wejścia na 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
pLumo
źródło
Dziękuję, to rzeczywiście działa, ale czy jest na to jakieś wytłumaczenie? Dodałem więcej informacji o ISO 8601 do mojego pytania i naprawdę nie widzę, gdzie dateby to zepsuło, ponieważ bez dostarczonych żadnych odejmowań, strefa czasowa pozostaje nietknięta i wszystko jest zgodne z oczekiwaniami bez konieczności podawania żadnych informacje o strefie czasowej lub konwersja.
konfetti
Przepraszam, nie umiem powiedzieć. Gdyby ktoś inny mógł na to odpowiedzieć, byłbym szczęśliwy, bo też się zastanawiałem.
pLumo,
Też byłbym zadowolony, ale na razie to zaakceptuję, ponieważ naprawia to mój problem!
konfetti
3
Działa to, ponieważ wyświetla date -Irównież specyfikator strefy czasowej (np. 2018-12-10T00:00:00+02:00), Który rozwiązuje problem opisany w odpowiedzi
Olorina
6

TLDR: To nie jest błąd. Właśnie znalazłeś jedno z subtelnych, ale udokumentowanych zachowań date. Wykonując arytmetykę czasu date, użyj formatu niezależnego od strefy czasowej (takiego jak czas uniksowy) lub bardzo czytaj uważnie przeczytaj dokumentację, aby dowiedzieć się, jak poprawnie używać tego polecenia.


GNU datewykorzystuje ustawienia systemowe ( TZzmienna środowiskowa lub, jeśli nie jest ustawiona, domyślne ustawienia systemowe), aby określić strefę czasową zarówno daty podanej w opcji -d/ --date, jak i daty podanej przez +formatargument. Opcja pozwoli również zastąpić strefę czasową dla własnej opcji-argumentu, ale nie zastępują strefie czasowej .--date+formatTo jest przyczyna zamieszania, IMHO.

Biorąc pod uwagę, że moja strefa czasowa to UTC-6, porównaj następujące polecenia:

$ 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

Pierwszy używa mojej strefy czasowej zarówno dla, jak -di dla +format. Drugi używa UTC tylko dla -dmojej strefy czasowej +format. Trzeci używa UTC dla obu.

Teraz porównaj następujące proste operacje:

$ 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

Nawet jeśli czas uniksowy mówi mi to samo, czas „normalny” różni się z powodu mojej własnej strefy czasowej.

Gdybym chciał wykonać tę samą operację, ale używając wyłącznie mojej strefy czasowej:

$ 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
nxnev
źródło
4
Wybitny za wzmiankę o epoce / czasie uniksowym, który to imo jest jedynym rozsądnym sposobem wykonywania arytmetyki czasu
Rui F Ribeiro
1
TL; DR Jeśli znasz tylko swój lokalny czas przesunięty w stosunku do czasu Zulu (np. Zachodnie wybrzeże USA wynosi -8 godzin lub -08:00), po prostu dołącz to, aby wprowadzić datę i godzinę ... nic innego, jak się bawić. Przykład: date -d '2019-02-28 14:05:36-08:00 +2 days 4 hours 3 seconds' +'local: %F %T'podaje lokalnie: 2019-03-02 18:05:36
Warstwa B
1
@BLayer Masz rację, działa zgodnie z oczekiwaniami w tym scenariuszu. Ale wyobraź sobie sytuację, w której dany skrypt wykorzystujący to podejście jest wykonywany w miejscu o innej strefie czasowej. Dane wyjściowe, chociaż technicznie poprawne, mogą wyglądać nieprawidłowo w zależności od informacji dostarczonych przez +formatargument. Na przykład w moim systemie te same polecenia generują: local: 2019-03-02 20:05:39(jeśli dodam %:zdo +format, staje się oczywiste, że informacje są poprawne, a rozbieżność wynika ze stref czasowych).
nxnev
@ BLayer IMHO, lepszym ogólnym podejściem byłoby użycie tej samej strefy czasowej dla obu -di / +formatlub czasu uniksowego, jak powiedziałem w odpowiedzi, aby uniknąć nieoczekiwanych rezultatów.
nxnev
1
@nxnev Zgoda, jeśli piszesz skrypt do opublikowania / udostępnienia. Początkowe słowa „jeśli znasz swoją strefę czasową”, oprócz tego, że mają dosłowne znaczenie, powinny sugerować swobodny / osobisty użytek. Ktoś publikujący skrypt polegający na czymś tak marnym jak znajomość tylko tz własnego systemu prawdopodobnie nie powinien być w grze z udostępnianiem skryptów. :) Z drugiej strony używam / używałbym własnego polecenia we wszystkich moich osobistych środowiskach.
B Warstwa
4

GNU dateobsługuje prostą arytmetykę dat, chociaż obliczenia czasu epoki, jak pokazano w odpowiedzi @sudodus, są czasami jaśniejsze (i bardziej przenośne).

Użycie +/-, gdy nie ma określonej strefy czasowej w sygnaturze czasowej, powoduje próbę dopasowania strefy czasowej, zanim cokolwiek innego zostanie przeanalizowane.

Oto jeden ze sposobów, aby to zrobić, użyj „temu” zamiast „-”:

$ 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

lub

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

(Chociaż nie możesz arbitralnie użyć „Z”, działa w mojej strefie, ale to sprawia, że ​​jest to sygnatura czasowa strefy UTC / GMT - użyj własnej strefy lub% z /% Z, dołączając ${TZ:-$(date +%z)}zamiast niej sygnaturę czasową).

Dodając dodatkowe terminy tych formularzy dostosuj czas:

  • „5 godzin temu” odejmij 5 godzin
  • „4 godziny” dodać (domyślnie) 4 godziny
  • Dodaj „3 godziny stąd” (wyraźne) 3 godziny (nieobsługiwane w starszych wersjach)

Można zastosować wiele złożonych korekt, w dowolnej kolejności (choć terminy względne i zmienne, takie jak „14 tygodni od ostatniego poniedziałku”, wymagają kłopotów ;-)

(Jest tu także kolejny mały beartrap, datezawsze poda prawidłową datę, więc date -d "2019-01-31 1 month"podaje 2019-03-03, podobnie jak „następny miesiąc”)

Biorąc pod uwagę szeroką gamę obsługiwanych formatów czasu i daty, parsowanie strefy czasowej jest niekoniecznie niechlujne: może to być pojedynczy lub wieloliterowy sufiks, przesunięcie godziny lub godziny: minuty, nazwa „America / Denver” (lub nawet nazwa pliku w przypadku TZzmiennej).

Twoja 2018-12-10T00:00:00wersja nie działa, ponieważ „T” jest tylko separatorem, a nie strefą czasową, a dodanie „Z” na końcu powoduje, że działa (zależnie od poprawności wybranej strefy) zgodnie z oczekiwaniami.

Zobacz: https://www.gnu.org/software/tar/manual/html_node/Date-input-formats.html, w szczególności sekcja 7.7.

pan. spuratic
źródło
1
Inne opcje obejmują: date -d "2018-12-10 00:00:00 now -5 hoursalbo todayalbo 0 hourzamiast now, wszystkiego, co mówi date, że token po czasie nie jest przesunięcie stref czasowych.
Stéphane Chazelas
1
Lub, dla zachowania dokładności, nie pozostawiaj nic po dacie, zmieniając kolejność argumentów: data -d "-5 godzin 2018-12-10 00:00:00" '
B Layer
2

To rozwiązanie jest łatwe do zrozumienia, ale nieco bardziej skomplikowane, więc pokazuję je jako skrypt powłoki.

  • konwertuj na „sekundy od 1970-01-01 00:00:00 UTC”
  • dodaj lub odejmij różnicę
  • przekonwertować z powrotem do formatu czytelnego dla człowieka za pomocą ostatniego datewiersza poleceń

Skrypt powłoki:

#!/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"
sudodus
źródło