Soustraire l'heure en utilisant la date et bash

18

Toutes les autres questions sur le réseau SE traitent de scénarios où soit la date est supposée être now( Q ), soit où seule une date est spécifiée ( Q ).

Ce que je veux faire, c'est fournir une date et une heure, puis soustraire une heure à cela.
Voici ce que j'ai essayé en premier:

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

Il en résulte 2018-12-10 06:39:55- Il a ajouté 7 heures. Puis soustrait 20:05 minutes.

Après avoir lu la page manet infode date, je pensais l'avoir corrigé avec ceci:

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

Mais, même résultat. D'où vient-il même les 7 heures?

J'ai également essayé d'autres dates parce que je pensais que nous avions peut-être 7200 secondes intercalaires ce jour-là, qui sait lol. Mais mêmes résultats.

Quelques exemples supplémentaires:

$ 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

Mais ici, cela devient intéressant. Si j'omets le temps en entrée, cela fonctionne très bien:

$ 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

Qu'est-ce que je rate?

Mise à jour: j'ai ajouté un Zà la fin, et cela a changé le comportement:

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

Je suis toujours confus cependant. Il n'y a pas grand-chose à ce sujet dans la page d'informations GNU sur la date.

Je suppose que c'est un problème de fuseau horaire, mais en citant le calendrier Wiki sur ISO 8601 :

Si aucune information de relation UTC n'est donnée avec une représentation temporelle, l'heure est supposée être en heure locale.

C'est ce que je veux. Mon heure locale est également réglée correctement. Je ne sais pas pourquoi la date dérangerait le fuseau horaire dans ce cas simple de moi fournissant une date et voulant en soustraire quelque chose. Ne devrait-il pas soustraire les heures de la chaîne de date en premier? Même s'il la convertit d'abord en date, puis effectue la soustraction, si je laisse de côté toutes les soustractions, j'obtiens exactement ce que je veux:

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

Donc , SI c'est vraiment un problème de fuseau horaire, où est - ce que la folie vient?

confettis
la source

Réponses:

20

Ce dernier exemple aurait dû clarifier les choses pour vous: les fuseaux horaires .

$ 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

Comme la sortie varie clairement selon le fuseau horaire, je soupçonne une valeur par défaut non évidente prise pour une chaîne de temps sans fuseau horaire spécifié. En testant quelques valeurs, cela semble être UTC-05: 00 , bien que je ne sois pas sûr de ce que c'est.

$ 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

Il n'est utilisé que lors de l'exécution de l'arithmétique des dates.


Il semble que le problème ici ne- 2 hours soit pas pris comme arithmétique, mais comme spécificateur de fuseau horaire :

# 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

Donc, non seulement pas être arithmétique fait, il semble y avoir une heure d'été ajustement de 1 heure sur le temps, ce qui conduit à un temps un peu de sens pour nous.

Cela vaut également pour l'addition:

# 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

Débogage un peu plus, l'analyse semble être: 2019-01-19T05:00:00 - 2( -2étant le fuseau horaire), et hours(= 1 heure), avec un ajout implicite. Il devient plus facile de voir si vous utilisez des minutes à la place:

# 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

Donc, bien, l'arithmétique des dates est en cours, mais pas celle que nous avons demandée. ¯ \ (ツ) / ¯

Olorin
la source
1
@confetti c'est effectivement le cas; Je pense que ce fuseau horaire par défaut est utilisé uniquement lors de l'ajout / la soustraction (comparaison 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
Cela pourrait être un bogue possible dans datela norme ISO 8601, si aucun fuseau horaire n'est fourni, l'heure locale (fuseau) doit être supposée, ce qui n'est pas le cas en cas d'opération arithmétique. Un problème très étrange pour moi.
confettis
1
@confetti a trouvé le problème: - 2 hoursest pris ici comme spécificateur de fuseau horaire.
Olorin
2
Sensationnel. Maintenant, cela a un sens. Eh bien, au moins ça l'explique. Merci beaucoup d'avoir compris cela. Pour moi, il semble que leur analyseur ait besoin d'une mise à jour, comme clairement avec un format et des espaces comme celui-ci, vous ne voulez pas spécifier le fuseau horaire par le `- 2 heures 'et cela cause sûrement de la confusion. S'ils ne veulent pas mettre à jour l'analyseur, au moins le manuel devrait obtenir une note à ce sujet.
confettis
1
Aujourd'hui, j'ai appris l' --debugoption de la date ! Grande explication.
Jeff Schaller
6

Cela fonctionne correctement lorsque vous convertissez d'abord la date d'entrée en 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
la source
Merci, cela fonctionne vraiment, mais y a-t-il une explication à cela? J'ai ajouté plus d'informations sur la chose ISO 8601 à ma question, et je ne vois vraiment pas où datecela gâcherait car sans aucune soustraction fournie, le fuseau horaire reste intact et tout est comme prévu sans avoir à fournir de informations sur le fuseau horaire ou conversion.
confettis
Je ne peux pas le dire, désolé. Si quelqu'un d'autre pouvait répondre à cela, je serais heureux parce que je me demandais aussi.
pLumo
Je serais heureux aussi mais je l'accepterai pour l'instant car cela résout mon problème!
confettis
3
Cela fonctionne car date -I2018-12-10T00:00:00+02:00
génère
6

TLDR: Ce n'est pas un bug. Vous venez de découvrir l'un des comportements subtils mais documentés de date. Lorsque vous effectuez l'arithmétique temporelle avec date, utilisez un format indépendant du fuseau horaire (comme l'heure Unix) ou lisez très attentivement la documentation pour savoir comment utiliser correctement cette commande.


GNU dateutilise vos paramètres système (la TZvariable d'environnement ou, s'il n'est pas défini, les paramètres système par défaut) pour déterminer le fuseau horaire de la date alimentée avec l' option -d/ --dateet la date signalée par l' +formatargument. L' --dateoption vous permet également de remplacer le fuseau horaire pour son propre argument d'option, mais elle ne remplace pas le fuseau horaire de +format. C'est la racine de la confusion, à mon humble avis.

Étant donné que mon fuseau horaire est UTC-6, comparez les commandes suivantes:

$ 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

Le premier utilise mon fuseau horaire pour les deux -det +format. Le second utilise UTC pour -dmais mon fuseau horaire pour +format. Le troisième utilise UTC pour les deux.

Maintenant, comparez les opérations simples suivantes:

$ 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

Même si l'heure Unix me dit la même chose, l'heure "Normal" diffère en raison de mon propre fuseau horaire.

Si je voulais faire la même opération mais en utilisant exclusivement mon fuseau horaire:

$ 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
la source
4
A voté pour avoir mentionné l'époque / le temps Unix, ce qui est la seule façon sensée de faire de l'arithmétique temporelle
Rui F Ribeiro
1
TL; DR Si vous connaissez simplement votre décalage horaire local par rapport à l'heure zoulou (par exemple, la côte ouest des États-Unis est de -8 heures ou -08:00), ajoutez simplement cela pour entrer la date-heure ... rien d'autre à manipuler. Exemple: date -d '2019-02-28 14:05:36-08:00 +2 days 4 hours 3 seconds' +'local: %F %T'donne local: 2019-03-02 18:05:36
Couche B
1
@BLayer Vous avez raison, cela fonctionne comme prévu dans ce scénario. Mais imaginez la situation où un script donné utilisant cette approche est exécuté dans un endroit avec un fuseau horaire différent. La sortie, bien que techniquement correcte, peut sembler incorrecte selon les informations fournies par l' +formatargument. Par exemple, sur mon système, la même commande affiche: local: 2019-03-02 20:05:39(si j'ajoute %:zà +format, il devient évident que les informations sont correctes et que l'écart est dû aux fuseaux horaires).
nxnev
@BLayer IMHO, une meilleure approche générale serait d'utiliser le même fuseau horaire pour les deux -det +formatou le temps Unix, comme je l'ai dit dans la réponse, pour éviter des résultats inattendus.
nxnev
1
@nxnev D'accord, si vous écrivez un script à publier / partager. Les mots d'ouverture "si vous connaissez votre propre fuseau horaire", en plus d'avoir un sens littéral, sont censés impliquer une utilisation occasionnelle / personnelle. Quelqu'un qui publie un script en se fondant sur quelque chose d'aussi fragile que de ne connaître que le tz de son propre système ne devrait probablement pas être dans le jeu de partage de script. :) D'un autre côté, j'utilise / utiliserais ma propre commande sur tous mes environnements personnels.
B Layer
4

GNU dateprend en charge l'arithmétique simple des dates, bien que les calculs d'époque comme indiqué dans la réponse de @sudodus soient parfois plus clairs (et plus portables).

L'utilisation de +/- quand aucun fuseau horaire n'est spécifié dans l'horodatage déclenche une tentative de correspondance avec un fuseau horaire avant, avant toute autre analyse.

Voici une façon de le faire, utilisez "il y a" au lieu de "-":

$ 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

ou

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

(Bien que vous ne puissiez pas utiliser arbitrairement "Z", cela fonctionne dans ma zone, mais cela en fait un horodatage de zone UTC / GMT - utilisez votre propre zone, ou% z /% Z en l'ajoutant ${TZ:-$(date +%z)}à l'horodatage à la place.)

L'ajout de termes de temps supplémentaires à ces formulaires ajuste le temps:

  • "Il y a 5 heures" soustrayez 5 heures
  • "4 heures" ajouter (implicitement) 4 heures
  • "3 heures d'ici" ajouter (explicite) 3 heures (non pris en charge dans les anciennes versions)

De nombreux ajustements complexes, dans n'importe quel ordre, peuvent être utilisés (bien que des termes relatifs et variables comme "14 semaines donc lundi dernier" demandent des ennuis ;-)

(Il y a un autre petit ourson ici aussi, datedonnera toujours une date valide, donc date -d "2019-01-31 1 month"donne 2019-03-03, comme le "mois prochain")

Compte tenu de la grande variété de formats d'heure et de date pris en charge, l'analyse du fuseau horaire est nécessairement bâclée: il peut s'agir d'un suffixe à une ou plusieurs lettres, d'un décalage d'une heure ou d'une heure: minute, d'un nom "America / Denver" (ou même d'un nom de fichier dans le cas de la TZvariable).

Votre 2018-12-10T00:00:00version ne fonctionne pas parce que "T" est juste un délimiteur, pas un fuseau horaire, l'ajout de "Z" à la fin rend ce travail (sous réserve de l'exactitude de la zone choisie) comme prévu aussi.

Voir: https://www.gnu.org/software/tar/manual/html_node/Date-input-formats.html et en particulier la section 7.7.

Mr Spuratic
la source
1
D'autres options incluent:, date -d "2018-12-10 00:00:00 now -5 hoursou todayou 0 hourau lieu de now, tout ce qui indique dateque le jeton après l'heure n'est pas un décalage de fuseau horaire.
Stéphane Chazelas
1
Ou, par souci de rigueur, ne rien laisser après la date-heure en réorganisant les arguments: date -d "-5 heures 2018-12-10 00:00:00" `
B Layer
2

Cette solution est facile à comprendre, mais un peu plus compliquée, je la présente donc sous forme de script shell.

  • convertir en 'secondes depuis le 01-01-1970 00:00:00 UTC'
  • ajouter ou soustraire la différence
  • reconvertir en un format lisible par l'homme avec une dateligne de commande 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"
sudodus
la source