Subtrahieren Sie die Zeit mit Datum und Bash

18

Alle anderen Fragen im SE-Netzwerk beziehen sich auf Szenarien, in denen entweder das Datum als now( Q ) angenommen wird oder nur ein Datum angegeben wird ( Q ).

Ich möchte ein Datum und eine Uhrzeit angeben und dann eine Uhrzeit davon abziehen.
Folgendes habe ich zuerst versucht:

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

Dies führt zu 2018-12-10 06:39:55- Es wurden 7 Stunden hinzugefügt. Dann werden 20:05 Minuten abgezogen.

Nachdem ich die manund infoSeite von gelesen hatte date, dachte ich, ich hätte es folgendermaßen behoben:

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

Aber das gleiche Ergebnis. Woher kommen die 7 Stunden überhaupt?

Ich habe auch andere Daten ausprobiert, weil ich dachte, wir hätten an diesem Tag vielleicht 7200 Schaltsekunden, wer weiß, lol. Aber die gleichen Ergebnisse.

Noch ein paar Beispiele:

$ 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

Aber hier wird es interessant. Wenn ich die Zeit bei der Eingabe weglasse, funktioniert es gut:

$ 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

Was vermisse ich?

Update: Ich habe ein Zam Ende hinzugefügt , und es hat das Verhalten geändert:

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

Ich bin immer noch verwirrt. Darüber gibt es auf der GNU-Infoseite zum Datum nicht viel .

Ich vermute, dies ist ein Problem mit der Zeitzone, zitiere aber das Kalender-Wiki zu ISO 8601 :

Wenn keine UTC-Beziehungsinformationen mit einer Zeitdarstellung angegeben werden, wird angenommen, dass die Zeit in Ortszeit ist.

Welches ist, was ich will. Meine Ortszeit ist auch richtig eingestellt. Ich bin nicht sicher, warum Datum mit der Zeitzone in diesem einfachen Fall, in dem ich eine Datums- / Uhrzeitangabe liefere und etwas davon subtrahieren möchte, in Konflikt geraten würde. Sollte es nicht zuerst die Stunden vom Datumsstring subtrahieren? Auch wenn es es zuerst in ein Datum konvertiert und dann die Subtraktion durchführt, bekomme ich genau das, was ich will, wenn ich keine Subtraktionen auslasse:

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

So IF dies wirklich eine Zeitzone Frage ist, woher kommt der Wahnsinn gekommen?

Konfetti
quelle

Antworten:

20

Das letzte Beispiel hätte die Dinge für Sie klarstellen sollen: Zeitzonen .

$ 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

Da die Ausgabe je nach Zeitzone deutlich variiert, würde ich vermuten, dass für eine Zeitzeichenfolge ohne Zeitzone ein nicht offensichtlicher Standardwert verwendet wurde. Wenn ich ein paar Werte teste, scheint es UTC-05: 00 zu sein , obwohl ich nicht sicher bin, was das ist.

$ 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

Es wird nur verwendet, wenn Datumsarithmetik ausgeführt wird.


Es scheint , das hier Problem ist , dass - 2 hourssich nicht als arithmetisches genommen, sondern als eine Zeitzone Spezifizierer :

# 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

Es wird also nicht nur keine Arithmetik ausgeführt, es scheint auch eine Sommerzeitumstellung von 1 Stunde für die Zeit zu geben, was für uns zu einer etwas unsinnigen Zeit führt.

Dies gilt auch für die Ergänzung:

# 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

Wenn Sie ein bisschen mehr debuggen, scheint das Parsing wie folgt zu sein: 2019-01-19T05:00:00 - 2( -2als Zeitzone) und hours(= 1 Stunde), mit einem impliziten Zusatz. Es wird einfacher zu erkennen, ob Sie stattdessen Minuten verwenden:

# 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

Nun, es wird gerade eine Datumsberechnung durchgeführt, nur nicht die, nach der wir gefragt haben. ¯ \ (ツ) / ¯

Olorin
quelle
1
@confetti tut es in der Tat; Ich denke, diese Standard-Zeitzone wird nur beim Addieren / Subtrahieren (Vergleichen 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) verwendet
Olorin
2
Könnte ein möglicher Fehler dategemäß der Norm ISO 8601 sein, wenn keine Zeitzone angegeben wird, sollte die Ortszeit (Zone) angenommen werden, was im Falle einer arithmetischen Operation nicht der Fall ist. Sehr seltsames Problem für mich.
Konfetti
1
@confetti hat das Problem gefunden: - 2 hourswird hier als Zeitzonenbezeichner verwendet.
Olorin
2
Beeindruckend. Jetzt macht das irgendwie Sinn. Nun, zumindest erklärt es das. Vielen Dank, dass Sie das herausgefunden haben. Für mich klingt es so, als ob ihr Parser ein Update benötigt, da Sie bei einem Format und einem Leerzeichen wie diesem die Zeitzone nicht mit "- 2 Stunden" angeben möchten und dies sicherlich Verwirrung stiftet. Wenn sie den Parser nicht aktualisieren möchten, sollte zumindest das Handbuch einen Hinweis dazu erhalten.
Konfetti
1
Heute habe ich über die --debugOption des Datums erfahren ! Tolle Erklärung.
Jeff Schaller
6

Es funktioniert korrekt, wenn Sie das Eingabedatum zuerst in ISO 8601 konvertieren:

$ 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
quelle
Danke, das funktioniert zwar, aber gibt es eine Erklärung dafür? Ich habe meiner Frage einige weitere Informationen zur ISO 8601-Sache hinzugefügt, und ich sehe wirklich nicht, wo datedies zu Problemen führen würde, da ohne Subtraktionen die Zeitzone unangetastet bleibt und auch alles wie erwartet ist, ohne dass eine angegeben werden muss Zeitzoneninformationen oder Konvertierung.
Konfetti
Ich kann es nicht sagen, sorry. Wenn jemand anderes antworten könnte, wäre ich glücklich, weil ich mich auch wunderte.
pLumo
Ich wäre auch froh, aber ich werde das jetzt akzeptieren, weil es mein Problem behebt!
Konfetti
3
Dies funktioniert, da date -Iauch der Zeitzonenspezifizierer (z. B. 2018-12-10T00:00:00+02:00) ausgegeben wird, der
ilkkachu
6

TLDR: Dies ist kein Fehler. Sie haben gerade eines der subtilen, aber dokumentierten Verhaltensweisen von gefunden date. Wenn mit der Zeit Arithmetik date, verwenden Sie eine Zeitzone unabhängigen Format (wie Unix Zeit) oder lesen sehr sorgfältig in der Dokumentation zu wissen , wie man richtig diesen Befehl verwenden.


GNU dateverwendet Ihre Systemeinstellungen (die TZUmgebungsvariable oder, falls nicht festgelegt, die Standardeinstellungen des Systems), um die Zeitzone sowohl des mit der Option -d/ --dateeingegebenen Datums als auch des vom +formatArgument angegebenen Datums zu bestimmen . Mit der --dateOption können Sie auch die Zeitzone für ein eigenes Optionsargument außer Kraft setzen, aber die Zeitzone von wird nicht außer Kraft gesetzt +format. Das ist die Wurzel der Verwirrung, IMHO.

Wenn meine Zeitzone UTC-6 ist, vergleichen Sie die folgenden Befehle:

$ 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

Der erste verwendet meine Zeitzone für beide -dund +format. Die zweite verwendet UTC für -daber meine Zeitzone für +format. Der dritte verwendet UTC für beide.

Vergleichen Sie nun die folgenden einfachen Operationen:

$ 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

Auch wenn mir die Unix-Zeit dasselbe sagt, unterscheidet sich die "normale" Zeit aufgrund meiner eigenen Zeitzone.

Wenn ich den gleichen Vorgang ausführen möchte, aber ausschließlich meine Zeitzone benutze:

$ 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
quelle
4
Upvoted für die Erwähnung der Epoche / Unix-Zeit, die imo der einzig vernünftige Weg ist, um Zeit zu rechnen
Rui F Ribeiro
1
TL; DR Wenn Sie nur wissen, dass Ihre Ortszeit von der Zulu-Zeit abweicht (z. B. die Westküste der USA beträgt -8 Stunden oder -08:00), hängen Sie dies einfach an die Eingabe der Datums- / Uhrzeit an. Beispiel: date -d '2019-02-28 14:05:36-08:00 +2 days 4 hours 3 seconds' +'local: %F %T'gibt local: 2019-03-02 18:05:36
B Layer
1
@BLayer Du hast recht, es funktioniert wie erwartet in diesem Szenario. Stellen Sie sich jedoch die Situation vor, in der ein bestimmtes Skript, das diesen Ansatz verwendet, an einem Ort mit einer anderen Zeitzone ausgeführt wird. Obwohl die Ausgabe technisch korrekt ist, kann sie je nach den durch das +formatArgument bereitgestellten Informationen falsch aussehen . Auf meinem System wird zum Beispiel derselbe Befehl ausgegeben: local: 2019-03-02 20:05:39(Wenn ich ihn hinzufüge %:z, +formatwird es offensichtlich, dass die Informationen korrekt sind und die Diskrepanz auf die Zeitzonen zurückzuführen ist.)
nxnev
@BLayer IMHO, ein besserer allgemeiner Ansatz wäre, die gleiche Zeitzone für beide -dund / +formatoder Unix-Zeit zu verwenden, wie ich in der Antwort sagte, um unerwartete Ergebnisse zu vermeiden.
nxnev
1
@nxnev Einverstanden, wenn Sie ein Skript schreiben, das veröffentlicht / freigegeben werden soll. Die einleitenden Worte "wenn Sie Ihre eigene Zeitzone kennen" haben nicht nur eine wörtliche Bedeutung, sondern implizieren auch einen gelegentlichen / persönlichen Gebrauch. Jemand, der ein Skript veröffentlicht, das sich auf etwas so Fadenscheiniges stützt, als nur das tz seines eigenen Systems zu kennen, sollte wahrscheinlich nicht im Spiel zur gemeinsamen Nutzung von Skripten dabei sein. :) Andererseits verwende ich meinen eigenen Befehl in allen meinen persönlichen Umgebungen.
B Layer
4

GNU dateunterstützt einfache Datumsberechnungen, obwohl Berechnungen der Epochenzeit, wie in @sudodus 'Antwort gezeigt, manchmal klarer (und portabler) sind.

Die Verwendung von +/-, wenn im Zeitstempel keine Zeitzone angegeben ist, löst den Versuch aus, als Nächstes eine Zeitzone zuzuordnen, bevor etwas anderes analysiert wird.

Hier ist eine Möglichkeit, dies zu tun: Verwenden Sie "ago" anstelle von "-":

$ 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

oder

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

(Obwohl Sie "Z" nicht willkürlich verwenden können, funktioniert es in meiner Zone, aber das macht es zu einem UTC / GMT-Zonen-Zeitstempel. Verwenden Sie Ihre eigene Zone oder% z /% Z, indem Sie ${TZ:-$(date +%z)}stattdessen den Zeitstempel anhängen .)

Durch Hinzufügen zusätzlicher Zeitbegriffe für diese Formulare wird die Zeit angepasst:

  • "Vor 5 Stunden" subtrahieren 5 Stunden
  • "4 Stunden" addieren (implizit) 4 Stunden
  • "3 Stunden danach" addiere (explizit) 3 Stunden (in älteren Versionen nicht unterstützt)

Viele komplexe Anpassungen, in beliebiger Reihenfolge, können verwendet werden (obwohl relative und variable Begriffe wie "14 Wochen ab dem letzten Montag" Probleme bereiten ;-)

(Es gibt auch hier eine andere kleine Bärenfalle, datedie immer ein gültiges Datum date -d "2019-01-31 1 month"angibt, also gibt sie 2019-03-03 an, ebenso wie "nächsten Monat".)

Angesichts der Vielzahl der unterstützten Zeit- und Datumsformate ist das Parsen von Zeitzonen zwangsläufig schlampig: Es kann sich um ein Einzel- oder Mehrfachbuchstaben-Suffix, einen Stunden- oder Stunden-Minuten-Versatz, einen Namen "America / Denver" (oder sogar einen Dateinamen) handeln im Fall der TZVariablen).

Ihre 2018-12-10T00:00:00Version funktioniert nicht, weil "T" nur ein Begrenzer und keine Zeitzone ist. Wenn Sie am Ende "Z" hinzufügen, funktioniert dies (abhängig von der Richtigkeit der ausgewählten Zone) ebenfalls wie erwartet.

Siehe: https://www.gnu.org/software/tar/manual/html_node/Date-input-formats.html und insbesondere Abschnitt 7.7.

mr.spuratic
quelle
1
Weitere Optionen sind: date -d "2018-12-10 00:00:00 now -5 hoursoder todayoder 0 hourstatt now, irgendetwas , das sagt , datedass das Token nach der Zeit ist keine Zeitzone gegenüber .
Stéphane Chazelas
1
Oder lassen Sie der Gründlichkeit halber nichts nach der Datumszeit, indem Sie args neu anordnen: date -d "-5 hours 2018-12-10 00:00:00" `
B Layer
2

Diese Lösung ist leicht zu verstehen, aber etwas komplizierter, weshalb ich sie als Shellscript zeige.

  • Umrechnen in 'Sekunden seit 1970-01-01 00:00:00 UTC'
  • Addiere oder subtrahiere die Differenz
  • konvertiere zurück in ein lesbares Format mit einer letzten dateKommandozeile

Shell-Skript:

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