Sottrai il tempo usando la data e il bash


16

Tutte le altre domande sulla rete SE riguardano scenari in cui si presume che la data sia now( Q ) o in cui è specificata solo una data ( Q ).

Quello che voglio fare è fornire una data e un'ora, quindi sottrarre un orario da quello.
Ecco cosa ho provato prima:

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

Ciò si traduce in 2018-12-10 06:39:55: ha aggiunto 7 ore. Quindi sottratto 20:05 minuti.

Dopo aver letto la pagina mane infodi date, ho pensato di averlo corretto con questo:

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

Ma stesso risultato. Da dove arriva anche le 7 ore?

Ho provato anche altre date perché pensavo che forse quel giorno avremmo avuto 7200 secondi da saltare, chissà che lol. Ma stessi risultati.

Alcuni altri esempi:

$ 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

Ma qui diventa interessante. Se ometto il tempo di input, funziona bene:

$ 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

Cosa mi sto perdendo?

Aggiornamento: ho aggiunto Za alla fine e ha cambiato il comportamento:

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

Sono comunque confuso. Non c'è molto al riguardo nella pagina di informazioni GNU sulla data.

Immagino che questo sia un problema di fuso orario, ma citando The Calendar Wiki su ISO 8601 :

Se non viene fornita alcuna informazione sulla relazione UTC con una rappresentazione dell'ora, si presume che l'ora sia l'ora locale.

Qual è quello che voglio. Anche la mia ora locale è impostata correttamente. Non sono sicuro del perché la data rovinerebbe affatto il fuso orario in questo semplice caso in cui fornivo un datetime e volevo sottrarre qualcosa da esso. Non dovrebbe sottrarre prima le ore dalla stringa della data? Anche se prima lo converte in una data e poi fa la sottrazione, se tralascio qualsiasi sottrazione ottengo esattamente quello che voglio:

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

Quindi se questo è davvero un problema di fuso orario, da dove viene quella follia?


Risposte:


20

Quell'ultimo esempio dovrebbe averti chiarito le cose: i fusi orari .

$ 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

Poiché l'output varia chiaramente in base al fuso orario, sospetto che alcuni valori di default non ovvi vengano presi per una stringa temporale senza un fuso orario specificato. Testando un paio di valori, sembra essere UTC-05: 00 , anche se non sono sicuro di cosa si tratti.

$ 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

Viene utilizzato solo quando si esegue l'aritmetica della data.


Sembra che il problema qui è che - 2 hoursè non è preso come l'aritmetica, ma come un identificatore di fuso orario :

# 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

Quindi, non solo non viene eseguita alcuna aritmetica, ma sembra esserci una regolazione dell'ora legale di 1 ora sull'orario, che porta a un tempo un po 'insensato per noi.

Questo vale anche per l'aggiunta:

# 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

Il debug un po 'di più, l'analisi sembra essere: 2019-01-19T05:00:00 - 2( -2essendo il fuso orario) e hours(= 1 ora), con un'aggiunta implicita. Diventa più facile vedere se usi invece i minuti:

# 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

Quindi, beh, l'aritmetica della data è in corso, ma non quella che abbiamo chiesto. ¯ \ (ツ) / ¯


1
@confetti lo fa davvero; Penso che questo fuso orario predefinito sia usato solo durante l'aggiunta / sottrazione (confronto 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
Potrebbe essere un possibile bug date, secondo lo standard ISO 8601, se non viene fornito alcun fuso orario, si dovrebbe assumere l'ora locale (zona), che in caso di un'operazione aritmetica non lo è. Un problema davvero strano per me.
coriandoli

1
@confetti ha riscontrato il problema: - 2 hoursviene utilizzato come identificatore del fuso orario qui.
Olorin

2
Wow. Ora che in qualche modo ha senso. Beh, almeno lo spiega. Grazie mille per averlo capito. Per me sembra che il loro parser abbia bisogno di un aggiornamento, dato che chiaramente con un formato e uno spazio bianco come questo non si desidera specificare il fuso orario in `- 2 ore` e sicuramente causa confusione. Se non vogliono aggiornare il parser, almeno il manuale dovrebbe avere una nota a riguardo.
coriandoli

1
Oggi ho saputo --debugdell'opzione data ! Ottima spiegazione
Jeff Schaller

6

Funziona correttamente quando si converte prima la data di input in 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

Grazie, funziona davvero, ma c'è qualche spiegazione per questo? Ho aggiunto alcune ulteriori informazioni sulla cosa ISO 8601 alla mia domanda, e non vedo davvero dove daterovinerebbe tutto questo senza alcuna sottrazione fornita, il fuso orario rimane intatto e tutto è come previsto senza dover fornire alcun informazioni sul fuso orario o conversione.
coriandoli

Non posso dirlo, scusa. Se qualcun altro potesse rispondere a questo, sarei felice perché me lo stavo chiedendo anch'io.
pumo

Anche io sarei felice, ma lo accetterò per ora perché risolve il mio problema!
coriandoli

3
Questo funziona perché date -Igenera anche l' identificatore del fuso orario (ad es. 2018-12-10T00:00:00+02:00) Che risolve il problema descritto nella risposta di
Olorin

6

TLDR: questo non è un bug. Hai appena trovato uno dei comportamenti sottili ma documentati di date. Quando si esegue il tempo aritmetica con date, utilizzare un formato fuso orario indipendente (come il tempo Unix) o leggere molto attentamente la documentazione per sapere come usare correttamente questo comando.


GNU dateutilizza le impostazioni di sistema (la TZvariabile di ambiente o, se non impostata, le impostazioni predefinite del sistema) per determinare il fuso orario sia della data alimentata con l' opzione -d/ --datesia della data riportata +formatdall'argomento. L' --dateopzione consente anche di sovrascrivere il fuso orario per il proprio argomento-opzione, ma non sovrascrive il fuso orario di +format. Questa è la radice della confusione, IMHO.

Considerando che il mio fuso orario è UTC-6, confronta i seguenti comandi:

$ 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

Il primo utilizza il mio fuso orario per entrambi -de +format. Il secondo usa UTC per -dma il mio fuso orario per +format. Il terzo utilizza UTC per entrambi.

Ora confronta le seguenti semplici operazioni:

$ 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

Anche se l'ora di Unix mi sta dicendo la stessa cosa, l'ora "Normale" differisce a causa del mio fuso orario.

Se volessi fare la stessa operazione ma utilizzando esclusivamente il mio fuso orario:

$ 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
Votato per aver menzionato l'epoca / tempo Unix, che imo è l'unico modo sano per fare l'aritmetica del tempo
Rui F Ribeiro,

1
TL; DR Se conosci solo il tuo fuso orario locale rispetto all'ora Zulu (ad esempio, la costa occidentale degli Stati Uniti è di -8 ore o -08:00), aggiungi semplicemente quello per inserire la data-ora ... nient'altro con cui giocherellare. Esempio: date -d '2019-02-28 14:05:36-08:00 +2 days 4 hours 3 seconds' +'local: %F %T'locale: 2019-03-02 18:05:36
B Layer

1
@BLayer Hai ragione, funziona come previsto in quello scenario. Ma immagina la situazione in cui un determinato script che utilizza quell'approccio viene eseguito in un luogo con un fuso orario diverso. L'output, sebbene tecnicamente corretto, potrebbe apparire errato a seconda delle informazioni fornite +formatdall'argomento. Ad esempio, sul mio sistema, viene emesso lo stesso comando: local: 2019-03-02 20:05:39(se aggiungo %:za +format, diventa ovvio che le informazioni sono corrette e la discrepanza è dovuta ai fusi orari).
nxnev,

@BLayer IMHO, un approccio generale migliore sarebbe quello di utilizzare lo stesso fuso orario per entrambi -de +formato per il tempo Unix, come ho detto nella risposta, per evitare risultati imprevisti.
nxnev,

1
@nxnev D'accordo, se stai scrivendo una sceneggiatura da pubblicare / condividere. Le parole iniziali "se conosci il tuo fuso orario", oltre ad avere un significato letterale, dovrebbero implicare un uso casuale / personale. Qualcuno che pubblica uno script basandosi su qualcosa di così fragile come sapere solo il proprio sistema tz probabilmente non dovrebbe essere nel gioco di condivisione degli script. :) D'altra parte faccio / userei il mio comando su tutti i miei ambienti personali.
B Layer

3

GNU datesupporta la semplice aritmetica della data, sebbene i calcoli dell'epoca come mostrato nella risposta di @sudodus siano talvolta più chiari (e più portabili).

L'uso di +/- quando non è specificato alcun fuso orario nel timestamp avvia un tentativo di abbinare un fuso orario successivo, prima che venga analizzato qualsiasi altro elemento.

Ecco un modo per farlo, usa "ago" invece di "-":

$ 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

o

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

(Anche se non puoi usare arbitrariamente "Z", funziona nella mia zona, ma questo lo rende un timestamp di zona UTC / GMT - usa la tua zona, o% z /% Z aggiungendo invece ${TZ:-$(date +%z)}al timestamp.)

L'aggiunta di termini supplementari di questi moduli regola il tempo:

  • "5 ore fa" sottrae 5 ore
  • "4 ore" aggiungono (implicite) 4 ore
  • Le "3 ore da qui" aggiungono (esplicito) 3 ore (non supportato nelle versioni precedenti)

Possono essere usati molti aggiustamenti complessi, in qualsiasi ordine (sebbene termini relativi e variabili come "14 settimane da lunedì scorso" chiedano problemi ;-)

(C'è anche un altro piccolo beartrap qui, datefornirà sempre una data valida, quindi date -d "2019-01-31 1 month"dà il 03-03-2019, così come il "prossimo mese")

Data l'ampia varietà di formati di data e ora supportati, l'analisi del fuso orario è necessariamente sciatta: può essere un suffisso a una o più lettere, un'ora o un'ora: offset dei minuti, un nome "America / Denver" (o anche un nome di file nel caso della TZvariabile).

La tua 2018-12-10T00:00:00versione non funziona perché "T" è solo un delimitatore, non un fuso orario, l'aggiunta di "Z" alla fine rende tale lavoro (soggetto alla correttezza della zona scelta) come previsto.

Vedi: https://www.gnu.org/software/tar/manual/html_node/Date-input-formats.html e in particolare la sezione 7.7.


1
Altre opzioni includono: date -d "2018-12-10 00:00:00 now -5 hourso todayo 0 houral posto di now, qualsiasi cosa che dice dateche il token dopo che il tempo non è un offset fuso orario.
Stéphane Chazelas,

1
Oppure, per motivi di completezza, non lasciare nulla dopo la data e l'ora riordinando args: date -d "-5 ore 2018-12-10 00:00:00" `
B Layer

2

Questa soluzione è facile da capire, ma un po 'più complicata, quindi la mostro come shellscript.

  • convertire in "secondi dal 1970-01-01 00:00:00 UTC"
  • aggiungere o sottrarre la differenza
  • riconvertire in un formato leggibile dall'uomo con una dateriga di comando finale

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"
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.