Je veux souvent faire des calculs de date rapides, tels que:
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?
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
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
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
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".
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
.
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.)
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.)
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"
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).DAYSdif
à la différence des deux jours.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.
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.)
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
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)
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.
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)
#!/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
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).
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