web-dev-qa-db-fra.com

Calculez rapidement les différences de date

Je veux souvent faire des calculs de date rapides, tels que:

  • Quelle est la différence entre ces deux dates?
  • Quelle est la date n semaines après cette autre date?

J'ouvre habituellement un calendrier et compte les jours, mais je pense qu'il devrait y avoir un programme/script que je peux utiliser pour faire ce genre de calculs. Aucune suggestion?

93
daniel kullmann

Les "n semaines après une date" sont faciles avec GNU date (1):

$ date -d 'now + 3 weeks'
Tue Dec  6 23:58:04 EST 2011
$ date -d 'Aug 4 + 3 weeks'
Thu Aug 25 00:00:00 EST 2011
$ date -d 'Jan 1 1982 + 11 weeks'
Fri Mar 19 00:00:00 EST 1982

Je ne connais pas de moyen simple de calculer la différence entre deux dates, mais vous pouvez encapsuler un peu de logique autour de date (1) avec une fonction Shell.

datediff() {
    d1=$(date -d "$1" +%s)
    d2=$(date -d "$2" +%s)
    echo $(( (d1 - d2) / 86400 )) days
}
$ datediff '1 Nov' '1 Aug'
91 days

Échanger d1 et d2 si vous voulez le calcul de la date dans l'autre sens, ou soyez un peu plus sophistiqué pour que cela ne soit pas important. De plus, dans le cas où il y a une transition non DST vers DST dans l'intervalle, l'un des jours ne durera que 23 heures; vous pouvez compenser en ajoutant ½ journée à la somme.

echo $(( (((d1-d2) > 0 ? (d1-d2) : (d2-d1)) + 43200) / 86400 )) days
113
camh

Pour un ensemble d'outils portables, essayez mon propre dateutils . Vos deux exemples se résumeraient à une seule ligne:

ddiff 2011-11-15 2012-04-11
=>
  148

ou en semaines et jours:

ddiff 2011-11-15 2012-04-11 -f '%w %d'
=>
  21 1

et

dadd 2011-11-15 21w
=>
  2012-04-10

Remarque importante concernant l'installation d'Ubuntu:

Ces mêmes dateutils sont disponibles en tant que paquet Ubuntu, et donc installables via Sudo apt install dateutils Cependant, les commandes doivent être précédées d'un dateutils. préfixe comme dans dateutils.ddiff 2019-03-28 2019-05-16

43
hroptatyr

Un exemple python pour calculer le nombre de jours que j'ai parcouru la planète:

$ python
>>> from datetime import date as D
>>> print (D.today() - D(1980, 6, 14)).days
11476
32
Daniel Näslund

Je préfère généralement avoir l'heure/la date au format unix utime (nombre de secondes depuis l'époque, lorsque les années 70 ont commencé, UTC). De cette façon, cela se résume toujours à une simple soustraction ou addition de secondes.

Le problème devient généralement la transformation d'une date/heure dans ce format.

Dans un environnement/script Shell, vous pouvez l'obtenir avec date '+%s' Au moment de la rédaction, l'heure actuelle est 1321358027.

Pour comparer avec le 2011-11-04 (mon anniversaire), date '+%s' -d 2011-11-04, donnant 1320361200. Soustraire: expr 1321358027 - 1320361200 donne 996827 secondes, soit expr 996827 / 86400 = Il y a 11 jours.

La conversion d'utime (format 1320361200) en une date est très simple à faire par exemple en C, PHP ou Perl, en utilisant des bibliothèques standard. Avec GNU date, le -d l'argument peut être précédé de @ pour indiquer le format "Secondes depuis l'époque".

14
MattBianco

Si un outil graphique vous convient, je vous recommande vivement qalculate (une calculatrice mettant l'accent sur les conversions d'unités, elle est livrée avec une interface GTK et KDE, IIRC). Là, vous pouvez dire par exemple.

days(1900-05-21, 1900-01-01)

pour obtenir le nombre de jours (140, puisque 1900 n'était pas une année bissextile) entre les dates, mais bien sûr, vous pouvez également faire de même pour les temps:

17:12:45 − 08:45:12

donne 8.4591667 heures ou, si vous définissez la sortie sur le formatage de l'heure, 8:27:33.

8
quazgar

Cela s'est produit lors de l'utilisation de date -d "$death_date - $y years - $m months - $d days" pour obtenir une date de naissance (pour la généalogie). Cette commande est FAUX. Les mois ne sont pas tous de la même longueur, donc (date + offset) - offset != date. L'âge, en année/mois/jour, est une mesure qui progresse à partir de la date de naissance.

$ date --utc -d 'mar 28 1867 +72years +11months +2days'
Fri Mar  1 00:00:00 UTC 1940

$ date --utc -d 'mar 1 1940 -72years -11months -2days'
Sat Mar 30 00:00:00 UTC 1867
# (2 days later than our starting point)

La date donne la sortie correcte dans les deux cas, mais dans le second cas, vous posiez la mauvaise question. Il importe QUELS 11 mois de l'année la couverture +/- 11, avant d'ajouter/soustraire des jours. Par exemple:

$ date --utc -d 'mar 31 1939  -1month'
Fri Mar  3 00:00:00 UTC 1939
$ date --utc -d 'mar 31 1940  -1month' # leap year
Sat Mar  2 00:00:00 UTC 1940
$ date --utc -d 'jan 31 1940  +1month' # leap year
Sat Mar  2 00:00:00 UTC 1940

Pour que la soustraction soit l'opération inverse de l'addition, l'ordre des opérations devrait être inversé. L'ajout ajoute des années, PUIS des mois, PUIS des jours. Si la soustraction utilise l'ordre inverse, vous revenez à votre point de départ. Ce n'est pas le cas, donc vous ne le faites pas, si le décalage des jours traverse une limite de mois dans un mois de longueur différente.

Si vous devez travailler en arrière à partir d'une date et d'un âge de fin, vous pouvez le faire avec plusieurs appels de date. Soustrayez d'abord les jours, puis les mois, puis les années. (Je ne pense pas qu'il soit sûr de combiner les années et les mois en une seule invocation date, car les années bissextiles modifient la longueur de février.)

8
Peter Cordes

Avec l'aide des solutions dannas, cela peut être fait en une seule ligne avec le code suivant:

python -c "from datetime import date as d; print(d.today() - d(2016, 7, 26))"

(Fonctionne dans les deux Python 2.x et Python 3.)

4
Kiran K Telukunta

Une autre façon de calculer la différence entre deux dates de la même année civile, vous pouvez utiliser ceci:

date_difference.sh
1  #!/bin/bash
2  DATEfirstnum=`date -d "2014/5/14" +"%j"`
3  DATElastnum=`date -d "12/31/14" +"%j"`
4  DAYSdif=$(($DATElastnum - $DATEfirstnum))
5  echo "$DAYSdif"
  • La ligne 1 déclare au shell quel interprète utiliser.
  • La ligne 2 affecte la valeur de la sortie de date à la variable DATEfirstnum. Le -d flag affiche la chaîne dans un format d'heure dans ce cas le 14 mai 2014 et +"%j" indique à date de formater la sortie au jour de l'année (1-365).
  • La ligne 3 est la même que la ligne 2 mais avec une date et un format différents pour la chaîne, le 31 décembre 2014.
  • La ligne 4 affecte la valeur DAYSdif à la différence des deux jours.
  • La ligne 5 affiche la valeur de DAYSdif.

Cela fonctionne avec la version GNU de date, mais pas sur la version PC-BSD/FreeBSD. J'ai installé coreutils depuis l'arborescence des ports et utilisé la commande /usr/local/bin/gdate au lieu.

4
Justin Holcomb

J'utilise fréquemment SQL pour les calculs de date. Par exemple MySQL , PostgreSQL ou SQLite :

bash-4.2$ mysql <<< "select datediff(current_date,'1980-06-14')"
datediff(current_date,'1980-06-14')
11477

bash-4.2$ psql <<< "select current_date-'1980-06-14'"
 ?column? 
----------
    11477
(1 row)

bash-4.2$ sqlite2 <<< "select julianday('now')-julianday('1980-06-14');"
11477.3524537035

D'autres fois, je me sens juste d'humeur pour JavaScript. Par exemple SpiderMonkey , WebKit , Seed ou Node.js :

bash-4.2$ js -e 'print((new Date()-new Date(1980,5,14))/1000/60/60/24)'
11477.477526192131

bash-4.2$ jsc-1 -e 'print((new Date()-new Date(1980,5,14))/1000/60/60/24)'
11477.47757960648

bash-4.2$ seed -e '(new Date()-new Date(1980,5,14))/1000/60/60/24'
11477.4776318287

bash-4.2$ node -pe '(new Date()-new Date(1980,5,14))/1000/60/60/24'
11624.520061481482

(Attention lorsque vous passez le mois au constructeur de l'objet JavaScript Date. Commence par 0.)

3
manatwork

date et bash peuvent faire des différences de date (options OS X montrées). Placez la dernière date en premier.

echo $((($(date -jf%D "04/03/16" +%s) - $(date -jf%D "03/02/16" +%s)) / 86400))
# 31
3
Dave

C'est un fil plutôt ancien, mais j'ai découvert quelque chose d'intéressant. Je suis bloqué sur un système embarqué avec BusyBox (v1.19) et j'ai été un peu déçu que le super maniable now - 10 days la notation échoue là-bas. En fait, un ensemble très limité de formats d'entrée est disponible pour le -d option.

Recognized TIME formats:
    hh:mm[:ss]
    [YYYY.]MM.DD-hh:mm[:ss]
    YYYY-MM-DD hh:mm[:ss]
    [[[[[YY]YY]MM]DD]hh]mm[.ss]

Cependant, certains calculs peuvent être effectués à une date d'entrée.

Compte tenu de ces formats, j'ai trouvé que le réglage du jour de la date à zéro vous donne le dernier jour du mois précédent:

$ date -d 2020.03.00-12:00
Sat Feb 29 12:00:00 EST 2020 

Et la saisie de nombres négatifs remonte encore plus loin:

$ date -d 2020.03.-10-12:00
Wed Feb 19 12:00:00 EST 2020

Quelqu'un peut-il confirmer si cela fonctionne également le GNU date? (Bien que le now - 10 days est beaucoup mieux)

1
Nate

réponse de camh s'occupe de la plupart, mais nous pouvons nous améliorer pour gérer l'arrondi, les fuseaux horaires, etc., en plus nous obtenons une précision supplémentaire et la possibilité de choisir nos unités:

datediff() {
#convert dates to decimal seconds since 1970-01-01 00:00:00 UTC
date1seconds=$(date +%s.%N -d "$date1")
date2seconds=$(date +%s.%N -d "$date2")

#Calculate time difference in various time units
timeseconds=$(printf "%0.8f\n" $(bc <<<"scale=9; ($date2sec-$date1sec)"))
timeminutes=$(printf "%0.8f\n" $(bc <<<"scale=9; ($date2sec-$date1sec)/60"))
  timehours=$(printf "%0.8f\n" $(bc <<<"scale=9; ($date2sec-$date1sec)/3600"))
   timedays=$(printf "%0.8f\n" $(bc <<<"scale=9; ($date2sec-$date1sec)/86400"))
  timeweeks=$(printf "%0.8f\n" $(bc <<<"scale=9; ($date2sec-$date1sec)/604800"))
}

-d indique à date que nous fournissons une date-heure à convertir. +%s.%N change la date-heure en secondes. nanosecondes depuis le 1970-01-01 00:00:00 UTC. bc calcule la différence entre les deux nombres, et le facteur de division nous donne les différentes unités. printf ajoute un 0 avant la décimale si nécessaire (bc ne le fait pas) et s'assure que l'arrondi va au plus proche (bc tronque juste). Vous pouvez également utiliser awk.

Maintenant, exécutons cela avec un cas de test génial,

date1='Tue Jul  9 10:18:04.031 PST 2020'
date2='Wed May  8 15:19:34.447 CDT 2019'
datediff "$date1" "$date2"

echo $timeseconds seconds
-36971909.584000000 seconds

echo $timeminutes minutes
-616198.493066667 minutes

echo $timehours hours
-10269.974884444 hours

echo $timedays days
-427.915620185 days

echo $timeweeks weeks
-61.130802884 weeks

Notez que comme la durée d'un mois ou d'une année n'est pas toujours la même, il n'y a pas une seule réponse "correcte", bien que l'on puisse utiliser 365,24 jours comme approximation raisonnable de nos jours.

1
TTT

Il y a aussi GNU calculs de temps de l'unité combinés avec GNU date:

$ gunits $(gdate +%s)sec-$(gdate +%s -d -1234day)sec 'yr;mo;d;hr;min;s'
        3 yr + 4 mo + 16 d + 12 hr + 37 min + 26.751072 s
$ gunits $(gdate +%s -d '2015-1-2 3:45:00')sec-$(gdate +%s -d '2013-5-6 7:43:21')sec 'yr;mo;d;hr;min;s'
        1 yr + 7 mo + 27 d + 13 hr + 49 min + 26.206759 s

(gunits est des unités sous Linux, gdate est la date)

1
Larry

dateiff.sh sur github: Gist

#!/bin/bash
#Simplest calculator two dates difference. By default in days

# Usage:
# ./datediff.sh first_date second_date [-(s|m|h|d) | --(seconds|minutes|hours|days)]

first_date=$(date -d "$1" "+%s")
second_date=$(date -d "$2" "+%s")

case "$3" in
"--seconds" | "-s") period=1;;
"--minutes" | "-m") period=60;;
"--hours" | "-h") period=$((60*60));;
"--days" | "-d" | "") period=$((60*60*24));;
esac

datediff=$(( ($first_date - $second_date)/($period) ))
echo $datediff
1
user178063

C'est un fil plutôt ancien, mais j'ai découvert quelque chose d'intéressant dans BusyBox, concernant au moins l'ajout et la soustraction de N unités de temps d'une date connue. Pas tant pour aller dans l'autre sens que pour déterminer l'intervalle entre deux dates connues.

Je suis bloqué sur un système embarqué avec BusyBox et le super maniable now - 10 days la notation échoue là-bas. En fait, un ensemble très limité de formats d'entrée est disponible pour le -d option.

$ busybox date --help
BusyBox v1.19.0 (2019-07-14 10:52:19 UTC) multi-call binary.

Usage: date [OPTIONS] [+FMT] [TIME]

Display time (using +FMT), or set time

        [-s,--set] TIME Set time to TIME
        -u,--utc        Work in UTC (don't convert to local time)
        -R,--rfc-2822   Output RFC-2822 compliant date string
        -I[SPEC]        Output ISO-8601 compliant date string
                        SPEC='date' (default) for date only,
                        'hours', 'minutes', or 'seconds' for date and
                        time to the indicated precision
        -r,--reference FILE     Display last modification time of FILE
        -d,--date TIME  Display TIME, not 'now'
        -D FMT          Use FMT for -d TIME conversion

Recognized TIME formats:
        hh:mm[:ss]
        [YYYY.]MM.DD-hh:mm[:ss]
        YYYY-MM-DD hh:mm[:ss]
        [[[[[YY]YY]MM]DD]hh]mm[.ss]

Cependant, certains calculs peuvent être effectués à une date d'entrée. Compte tenu de ces formats, j'ai trouvé que le réglage du numéro de jour de la date d'entrée à zéro vous donne le dernier jour du mois précédent:

$ date -d 2020.03.00-12:00
Sat Feb 29 12:00:00 EST 2020 

De plus, la saisie de nombres négatifs continue de soustraire en arrière, ce à quoi je ne m'attendais certainement pas:

$ date -d 2020.03.-10-12:00
Wed Feb 19 12:00:00 EST 2020

Vous pouvez donc faire quelques calculs rapides à votre date connue pour générer des valeurs à alimenter dans le -d option dans l'appel BusyBox date:

#!/bin/bash
#Other shells not tested.

start_year=2020
start_month=1
start_day=20

offset_year=-2
offset_month=4
offset_day=5

new_year=$(( $start_year + $offset_year ))
new_month=$(( $start_month + $offset_month ))
new_day=$(( $start_day + $offset_day ))

new_date$( busybox date -d $new_year.$new_month.$new_day-12:00 +%Y-%m-%d )
echo $new_date

Production:

2018-05-25

Quelqu'un peut-il confirmer si cela fonctionne également avec GNU, BSD ou MacOS date? J'apprécie certainement que le GNU now - 10 days est beaucoup mieux, mais je souhaite que ce soit aussi portable que possible. J'ai confirmé que Android est très différent (aucune approche ne fonctionne là-bas).

0
Nate

date -d est spécifique à GNU Date et n'est pas défini par POSIX:

https://pubs.opengroup.org/onlinepubs/9699919799/utilities/date.html

Comme d'autres l'ont mentionné, c'est quelque chose qui pourrait être mieux adapté à un langage de programmation approprié. Par exemple PHP:

<?php
$o1 = date_create('2020-02-26');
$o2 = date_create('2020-02-16');
$o3 = date_diff($o2, $o1);
echo 'days: ', $o3->d, "\n";

Résultat:

days: 10
0
Steven Penny