web-dev-qa-db-fra.com

Soustraire l'heure en utilisant la date et bash

Toutes les autres questions sur le réseau SE concernent des scénarios où la date est supposée être now ( Q ) ou où seule une date est spécifié ( 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"

Cela se traduit par 2018-12-10 06:39:55 - Il a ajouté 7 heures. Puis soustrait 20:05 minutes.

Après avoir lu la page man et info de 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 de saut 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'information GN sur la date.

Je suppose que c'est un problème de fuseau horaire, mais en citant The Calendar Wiki on 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/heure 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é 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, d'où vient cette folie?

20
confetti

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

$ 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, il semble que ce soit TC-05: , 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 est que - 2 hours est pas pris comme arithmétique, mais comme un 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 aucune arithmétique n'est effectuée, il semble y avoir un l'heure d'été 1 heure d'ajustement de l'heure, ce qui nous amène à une heure quelque peu absurde.

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, eh bien, l'arithmétique des dates est en cours, mais pas celle que nous avons demandée. ¯\(ツ)/¯

20
Olorin

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
6
pLumo

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 pour utiliser correctement cette commande.


GNU date utilise vos paramètres système (la variable d'environnement TZ ou, si elle n'est pas définie, les valeurs par défaut du système) pour déterminer le fuseau horaire de la date alimentée avec le -d/--date option et la date indiquée par le +format argument. Le --date L'option 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 -d et +format. Le second utilise UTC pour -d mais 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
5
nxnev

GNU date prend en charge l'arithmétique de date simple, bien que les calculs de temps Epoch 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 ajoutant ${TZ:-$(date +%z)} à l'horodatage au lieu.)

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 beartrap ici aussi, date donnera 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 variable TZ).

Votre version 2018-12-10T00:00:00 Ne fonctionne pas car "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.

4
mr.spuratic

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 ligne de commande finale 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"
1
sudodus