Často chci provést několik rychlých výpočtů data, například:
Obvykle otevírajím kalendář a počítám dny, ale myslím, že by měl existovat program/skript, který mohu použít k provádění těchto druhů výpočtů. Nějaké návrhy?
„N týdnů po datu“ je snadné s GNU datum (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
Nevím o jednoduchém způsobu výpočtu rozdílu mezi dvěma daty, ale pomocí funkce Shell můžete zabalit malou logiku kolem data (1).
datediff() {
d1=$(date -d "$1" +%s)
d2=$(date -d "$2" +%s)
echo $(( (d1 - d2) / 86400 )) days
}
$ datediff '1 Nov' '1 Aug'
91 days
Zaměnit d1
a d2
pokud chcete výpočet data opačným způsobem, nebo si nechejte trochu fantazii, aby na tom nezáleželo. Dále, v případě, že dojde k přechodu mimo DST na DST v intervalu, bude jeden ze dnů dlouhý pouze 23 hodin; můžete kompenzovat přidáním ½ dne k částce.
echo $(( (((d1-d2) > 0 ? (d1-d2) : (d2-d1)) + 43200) / 86400 )) days
Pro sadu přenosných nástrojů zkuste své vlastní dateutils . Vaše dva příklady by se scvrkly na jednoplášťové:
ddiff 2011-11-15 2012-04-11
=>
148
nebo v týdnech a dnech:
ddiff 2011-11-15 2012-04-11 -f '%w %d'
=>
21 1
a
dadd 2011-11-15 21w
=>
2012-04-10
Důležitá poznámka týkající se instalace Ubuntu:
Tyto velmi stejné dateutils jsou k dispozici jako balíček Ubuntu, a proto je lze instalovat pomocí Sudo apt install dateutils Příkazům však musí předcházet dateutils. předpona jako v dateutils.ddiff 2019-03-28 2019-05-16
A python příklad pro výpočet počtu dní, které jsem prošel planetou:
$ python
>>> from datetime import date as D
>>> print (D.today() - D(1980, 6, 14)).days
11476
Obvykle dávám přednost času a datu v unixovém utime formátu (počet sekund od epochy, kdy se začalo sedmdesátá léta, UTC). Tímto způsobem se vždy scvrkává na obyčejné odčítání nebo sčítání sekund.
Problém, který se obvykle stává transformací data/času do tohoto formátu.
V prostředí/skriptu prostředí Shell to můžete získat pomocí date '+%s'
V době psaní je aktuální čas 1321358027
.
Pro srovnání s 2011-11-04 (moje narozeniny), date '+%s' -d 2011-11-04
, Což přináší 1320361200
. Odečíst: expr 1321358027 - 1320361200
Dává 996827
Sekund, což je expr 996827 / 86400
= Před 11 dny.
Převod z utime (formát 1320361200) na datum je velmi jednoduchý, například C, PHP nebo Perl, pomocí standardních knihoven.) GNU date
, argument -d
lze doplnit do @
a označit formát „Sekundy od epochy“.
Pokud je pro vás grafický nástroj v pořádku, vřele doporučuji qalculate
(kalkulačka s důrazem na převody jednotek, přichází s rozhraním GTK a KDE, IIRC). Zde můžete říci např.
days(1900-05-21, 1900-01-01)
pro získání počtu dní (140, protože rok 1900 nebyl přestupným rokem) mezi daty, ale samozřejmě můžete udělat i to samé pro časy:
17:12:45 − 08:45:12
výnosy 8.4591667
hodin nebo, pokud nastavíte výstup na formátování času, 8:27:33
.
Toto přišlo při použití date -d "$death_date - $y years - $m months - $d days"
získat datum narození (pro genealogii). Tento příkaz je nesprávný. Měsíce nejsou všechny stejné délky, takže (date + offset) - offset != date
. Věk v roce/měsíci/den jsou opatření směřující dopředu od data narození.
$ 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)
Datum dává správný výstup v obou případech, ale ve druhém případě jste se ptali na špatnou otázku. Záleží na tom, KTERÉ 11 měsíců v roce pokrývají +/- 11, před přidáním/odečtením dnů. Například:
$ 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
Aby bylo odečtením obrácená operace sčítání, musí být pořadí operací obráceno. Přidání přidá roky, THEN měsíce, THEN dny. Pokud by odečtení použilo opačné pořadí, vrátili byste se k výchozímu bodu. Ne, takže ne, pokud posun dnů překročí hranici měsíce v jiné délce měsíce.
Pokud potřebujete pracovat zpět od data a věku ukončení, můžete to provést pomocí více vyvolání date
. Nejprve odečtěte dny, pak měsíce, pak roky. (Nemyslím si, že je bezpečné spojit roky a měsíce do jediné date
vyvolání, protože přestupné roky mění délku února.)
S pomocí řešení dannas to lze provést v jednom řádku s následujícím kódem:
python -c "from datetime import date as d; print(d.today() - d(2016, 7, 26))"
(Pracuje v obou Python 2.x a Python 3.))
Další způsob výpočtu rozdílu mezi dvěma daty stejného kalendářního roku můžete použít:
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
proměnné DATEfirstnum. Příznak -d
Zobrazuje řetězec v časovém formátu, v tomto případě 14. května 2014 a +"%j"
Řekne date
, aby formátoval výstup pouze na den roku (1-365) ).DAYSdif
rozdílu dvou dnů.DAYSdif
.Toto funguje s verzí GNU ve verzi date
, ale ne ve verzi PC-BSD/FreeBSD. Nainstaloval jsem coreutils
ze stromu portů a místo toho jsem použil příkaz /usr/local/bin/gdate
.
Často používám SQL pro výpočty data. Například MySQL , PostgreSQL nebo 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
Jindy se cítím v náladě pro JavaScript. Například SpiderMonkey , WebKit , Seed nebo 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
(Dávejte pozor, když předáváte měsíc konstruktoru objektu Java Date
. Začíná 0.)
date
a bash umí provádět rozdíly v datech (zobrazené možnosti OS X). Nejdříve vložte druhé datum.
echo $((($(date -jf%D "04/03/16" +%s) - $(date -jf%D "03/02/16" +%s)) / 86400))
# 31
Toto je poněkud staré vlákno, ale objevil jsem něco zajímavého. Jsem zaseknutý na vestavěném systému s BusyBoxem (v1.19) a byl jsem trochu zklamaný, že super šikovný now - 10 days
notace tam selhala. Ve skutečnosti je pro -d
možnost.
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]
Některé matematické údaje však lze provést k datu vstupu.
S ohledem na tyto formáty jsem zjistil, že když nastavíte den zobrazení na nulu, dostanete poslední den předchozího měsíce:
$ date -d 2020.03.00-12:00
Sat Feb 29 12:00:00 EST 2020
A zadávání záporných čísel jde ještě dále:
$ date -d 2020.03.-10-12:00
Wed Feb 19 12:00:00 EST 2020
Může někdo potvrdit, jestli to funguje také na datum GNU datum?) Ačkoli now - 10 days
je mnohem lepší)
Camhova odpověď se o většinu postará, ale můžeme se zlepšit s řešením zaokrouhlování, časových pásem atd., navíc získáme extra přesnost a schopnost vybrat si naše jednotky:
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
řekne date
, že dodáváme datum a čas pro převod. +%s.%N
změní datum a čas na sekundy. Druhé sekundy od 1970-01-01 00:00:00 UTC. bc
vypočítá rozdíl mezi těmito dvěma čísly a faktor dělení nám dá různé jednotky. printf
v případě potřeby přidá 0 před desetinné místo (bc
ne) a zajistí zaokrouhlování na nejbližší (bc
jen zkrátí). Můžete také použít awk
.
Nyní to pojďme s funky testovacím případem,
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
Všimněte si, že protože délka měsíce nebo roku není vždy stejná, neexistuje jediná „správná“ odpověď, i když dnes lze jako rozumnou aproximaci použít 365,24 dní.
K dispozici je také GNU časové výpočty jednotek v kombinaci s GNU datum:
$ 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 jsou jednotky v Linuxu, gdate je datum)
#!/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
Toto je poněkud staré vlákno, ale objevil jsem něco zajímavého v BusyBox, alespoň co se týče přidávání a odečítání N jednotek času od známého data. Není to ale tak moc, že byste si mohli určit interval mezi dvěma známými daty.
Jsem zaseknutý na vestavěném systému s BusyBoxem a super šikovným now - 10 days
notace tam selhala. Ve skutečnosti je pro -d
možnost.
$ 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]
Některé matematické údaje však lze provést k datu vstupu. Vzhledem k těmto formátům jsem zjistil, že když nastavíte číslo dne vstupního data na nulu, dostanete poslední den předchozího měsíce:
$ date -d 2020.03.00-12:00
Sat Feb 29 12:00:00 EST 2020
Zadávání záporných čísel dále odečítá zpět, což jsem určitě neočekával:
$ date -d 2020.03.-10-12:00
Wed Feb 19 12:00:00 EST 2020
Takže můžete udělat nějakou rychlou matematiku ke svému známému datu a vygenerovat hodnoty, které se budou posílat do -d
volba v BusyBoxu date
volání:
#!/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
Výstup:
2018-05-25
Může někdo potvrdit, zda to funguje také s GNU, BSD nebo MacOS date
? Určitě oceňuji, že GNU now - 10 days
je mnohem lepší, ale mám zájem o to, aby to bylo co nejpřenosnější. Potvrdil jsem, že Android je velmi odlišný (žádný přístup tam nefunguje).
date -d
je specifické pro GNU Datum a není definováno POSIX:
https://pubs.opengroup.org/onlinepubs/9699919799/utilities/date.html
Jak již uvedli ostatní, je to něco, co by lépe vyhovovalo správnému programovacímu jazyku. Například PHP:
<?php
$o1 = date_create('2020-02-26');
$o2 = date_create('2020-02-16');
$o3 = date_diff($o2, $o1);
echo 'days: ', $o3->d, "\n";
Výsledek:
days: 10