Jak znaleźć różnicę w dniach między dwiema datami?


84

A = „2002-20-10”
B = „2003-22-11”

Jak znaleźć różnicę w dniach między dwiema datami?


5
Jak wyjaśniono poniżej, ale daty są niepoprawne: muszą być rrrr-mm-dd
Peter Flynn

Wiele odpowiedzi używa opcji -d daty (GNU-). Chcę dodać, że NIE jest to część daty POSIX, dlatego jest mniej przenośna. Dopóki OP nie działa na dystrybucjach Uniksa, takich jak Solaris, a tylko na „zwykłym Linuksie”, powinien być dobry. Odpowiedni tag pomógłby imho.
Cadoiz

Odpowiedzi:


34

Jeśli masz GNU date, to pozwala wydrukować reprezentację dowolnej daty ( -dopcja). W tym przypadku zamień daty na sekundy od EPOCH, odejmij i podziel przez 24 * 3600.

Czy potrzebujesz przenośnego sposobu?


1
@Tree: Uważam, że nie ma przenośnego sposobu na odejmowanie dat poza samodzielnym wdrożeniem - nie jest to takie trudne, jedyną interesującą rzeczą są lata przestępne. Ale w dzisiejszych czasach każdy chce odejmować daty, jak się wydaje: spójrz na unix.stackexchange.com/questions/1825/… :)

15
Przez wzgląd na leniuchowanie myślę, że to właśnie miał na myśli user332325:echo "( `date -d $B +%s` - `date -d $A +%s`) / (24*3600)" | bc -l
Pablo Mendes

2
Co w tym kontekście oznacza przenośny?
htellez

@htellez coś, co działa na wielu platformach (Windows, Linux itp.)
Sumudu

Aby uzyskać informacje o bash one liner w oparciu o tę odpowiedź, zobacz: stackoverflow.com/a/49728059/1485527
jschnasse

64

Sposób basha - przekonwertuj daty do formatu% y% m% d, a następnie możesz to zrobić bezpośrednio z wiersza poleceń:

echo $(( ($(date --date="031122" +%s) - $(date --date="021020" +%s) )/(60*60*24) ))

9
Nie jest to konieczne, aby daty były zgodne z %y%m%dformatem. Np. Data gnu zaakceptuje %Y-%m-%dformat używany przez PO. Ogólnie rzecz biorąc, ISO-8601 jest dobrym wyborem (a format PO jest jednym z takich formatów). Lepiej unikać formatów z latami dwucyfrowymi.
mc0e

2
Za różnicę od dzisiaj można by skorzystać days_diff=$(( (`date -d $B +%s` - `date -d "00:00" +%s`) / (24*3600) )). zauważ, że $days_diffbędzie to liczba całkowita (tj. bez miejsc po przecinku)
Julien

1
Zależy to od wariantu datezainstalowanego. Działa dla GNU date. Nie działa z POSIX date. Prawdopodobnie nie stanowi to problemu dla większości czytelników, ale warto to zauważyć.
mc0e

Jeśli naprawdę chcesz przenośności POSIX, sprawdź to: unix.stackexchange.com/a/7220/43835
mc0e

1
Aby zwalczyć nieporozumienia: format OP nie jest %Y-%m-%d, dopóki nie wprowadzimy August 2.0i October 2.0format OP nie jest %Y-%d-%m. Odpowiednie xkcd: xkcd.com/1179
confetti

22

tl; dr

date_diff=$(( ($(date -d "2015-03-11 UTC" +%s) - $(date -d "2015-03-05 UTC" +%s)) / (60*60*24) ))

Uważaj! Wiele z rozwiązań bash tutaj jest uszkodzonych dla zakresów dat, które obejmują datę rozpoczęcia czasu letniego (w stosownych przypadkach). Dzieje się tak, ponieważ konstrukcja $ ((math)) wykonuje operację „piętra” / obcięcia na wynikowej wartości, zwracając tylko liczbę całkowitą. Pozwólcie, że zilustruję:

Czas letni rozpoczął się 8 marca tego roku w Stanach Zjednoczonych, więc użyjmy zakresu dat obejmującego:

start_ts=$(date -d "2015-03-05" '+%s')
end_ts=$(date -d "2015-03-11" '+%s')

Zobaczmy, co otrzymamy za pomocą podwójnych nawiasów:

echo $(( ( end_ts - start_ts )/(60*60*24) ))

Zwraca wartość „5”.

Wykonanie tego przy użyciu „bc” z większą dokładnością daje inny wynik:

echo "scale=2; ( $end_ts - $start_ts )/(60*60*24)" | bc

Zwraca wartość „5,95” - brakujące 0,05 oznacza utraconą godzinę od przejścia na czas letni.

Jak więc należy to zrobić poprawnie?
Sugerowałbym użycie tego zamiast tego:

printf "%.0f" $(echo "scale=2; ( $end_ts - $start_ts )/(60*60*24)" | bc)

Tutaj „printf” zaokrągla dokładniejszy wynik obliczony przez „bc”, dając nam prawidłowy zakres dat wynoszący „6”.

Edycja: zaznaczenie odpowiedzi w komentarzu od @ hank-schultz poniżej, którego ostatnio używałem:

date_diff=$(( ($(date -d "2015-03-11 UTC" +%s) - $(date -d "2015-03-05 UTC" +%s) )/(60*60*24) ))

Powinno to być również bezpieczne, o ile zawsze odejmujesz wcześniejszą datę od późniejszej, ponieważ sekundy przestępne tylko dodają różnicę - obcięcie skutecznie zaokrągla w dół do prawidłowego wyniku.


6
Jeśli zamiast tego określisz strefę czasową zarówno dla początku, jak i końca (i upewnij się, że są takie same), to również nie masz już tego problemu, na przykład:, echo $(( ($(date --date="2015-03-11 UTC" +%s) - $(date --date="2015-03-05 UTC" +%s) )/(60*60*24) ))co zwraca 6 zamiast 5.
Hank Schultz

1
Ach, jeden z odpowiadających pomyślał o nieścisłościach podstawowego rozwiązania powtarzanego przez innych w wariantach. Kciuki w górę! A co z sekundami przestępnymi? Czy jest bezpieczny w sekundę przestępną? Istnieją precedensy polegające na zwracaniu przez oprogramowanie wyników jednego dnia, jeśli jest uruchamiane o określonych porach, co odnosi się do problemów związanych z sekundą przestępną .
Stéphane Gourichon

Ups, "niewłaściwe" obliczenie, które podasz jako przykład, poprawnie zwraca tutaj 6 (Ubuntu 15.04 AMD64, data GNU w wersji 8.23). Być może Twój przykład zależy od strefy czasowej? Moja strefa czasowa to „Europa / Paryż”.
Stéphane Gourichon

Ten sam dobry wynik dla „złego” obliczenia z datą GNU 2.0 na MSYS, w tej samej strefie czasowej.
Stéphane Gourichon

1
@ StéphaneGourichon, wygląda na to, że zmiana czasu letniego w Twojej strefie czasowej nastąpiła 29 marca 2015 r. - więc wypróbuj zakres dat, który to obejmuje.
evan_b

21

I w Pythonie

$python -c "from datetime import date; print (date(2003,11,22)-date(2002,10,20)).days"
398

2
@mouviciel nie bardzo. Python nie zawsze jest obecny.
mc0e

1
@ mc0e w tym kontekście nic nie jest zawsze obecne
Sumudu,

5
@Anubis OP określił tagi jako bashi shell, więc myślę, że możemy założyć, że mówimy o systemie * nix. W takich systemach echoi datesą niezawodnie obecne, ale Python nie.
mc0e

2
@ mc0e tak, to ma sens. Mimo że python2 jest dostępny w większości systemów * nix, jeśli chodzi o urządzenia z niższej półki (wbudowane itp.), Będziemy musieli przetrwać tylko z podstawowymi narzędziami.
Sumudu

13

To działa dla mnie:

A="2002-10-20"
B="2003-11-22"
echo $(( ($(date -d $B +%s) - $(date -d $A +%s)) / 86400 )) days

Wydruki

398 days

Co się dzieje?

  1. Podaj prawidłowy ciąg czasu w A i B.
  2. Posługiwać się date -d do obsługi ciągów czasowych
  3. Służy date %sdo konwersji ciągów czasowych na sekundy od 1970 (unix epoche)
  4. Użyj rozszerzenia parametrów bash aby odjąć sekundy
  5. podziel przez sekundy dziennie (86400 = 60 * 60 * 24), aby otrzymać różnicę w dniach
  6. ! Czas letni nie jest brany pod uwagę! Zobacz tę odpowiedź na unix.stackexchange !

7

Jeśli opcja -d działa w twoim systemie, oto inny sposób, aby to zrobić. Jest zastrzeżenie, że nie uwzględniłoby to lat przestępnych, ponieważ rozważałem 365 dni w roku.

date1yrs=`date -d "20100209" +%Y`
date1days=`date -d "20100209" +%j`
date2yrs=`date +%Y`
date2days=`date +%j`
diffyr=`expr $date2yrs - $date1yrs`
diffyr2days=`expr $diffyr \* 365`
diffdays=`expr $date2days - $date1days`
echo `expr $diffyr2days + $diffdays`

7

Oto wersja MAC OS X dla Twojej wygody.

$ A="2002-20-10"; B="2003-22-11";
$ echo $(((`date -jf %Y-%d-%m $B +%s` - `date -jf %Y-%d-%m $A +%s`)/86400))

nJoy!


1
-bash: (-) / 86400: błąd składni: oczekiwany operand (token błędu to „) / 86400”)
kawalkada

1
@cavalcade - to dlatego, że tego nie zrobiłeśA="2002-20-10"; B="2003-22-11"
RAM237

Dzięki, mimo że działa w tym konkretnym przypadku, sugerowałbym użycie echo $(((`date -jf "%Y-%d-%m" "$B" +%s` - `date -jf "%Y-%d-%m" "$A" +%s`)/86400)), czyli zawijanie obu formatów i zmiennych w podwójne cudzysłowy, inaczej może się nie udać na innym formacie daty (np %b %d, %Y/ Jul 5, 2017)
RAM237

@ RAM237 To jest to, co dostaję za to, że nie zwracam uwagi na uderzenie głową Dobrze zauważone, dzięki
kawalkada

5

Nawet jeśli nie masz daty GNU, prawdopodobnie będziesz mieć zainstalowany Perl:

use Time::Local;
sub to_epoch {
  my ($t) = @_; 
  my ($y, $d, $m) = ($t =~ /(\d{4})-(\d{2})-(\d{2})/);
  return timelocal(0, 0, 0, $d+0, $m-1, $y-1900);
}
sub diff_days {
  my ($t1, $t2) = @_; 
  return (abs(to_epoch($t2) - to_epoch($t1))) / 86400;
}
print diff_days("2002-20-10", "2003-22-11"), "\n";

Zwraca to 398.041666666667- 398 dni i jedną godzinę ze względu na czas letni.


Pytanie wróciło do mojego kanału. Oto bardziej zwięzła metoda wykorzystująca dołączony moduł Perla

days=$(perl -MDateTime -le '
    sub parse_date { 
        @f = split /-/, shift;
        return DateTime->new(year=>$f[0], month=>$f[2], day=>$f[1]); 
    }
    print parse_date(shift)->delta_days(parse_date(shift))->in_units("days");
' $A $B)
echo $days   # => 398


2

Oto najprostsze, jakie udało mi się uzyskać podczas pracy na Centos 7:

OLDDATE="2018-12-31"
TODAY=$(date -d $(date +%Y-%m-%d) '+%s')
LINUXDATE=$(date -d "$OLDDATE" '+%s')
DIFFDAYS=$(( ($TODAY - $LINUXDATE) / (60*60*24) ))

echo $DIFFDAYS

2

Oto moje podejście do pracy przy użyciu zsh. Testowane na OSX:

# Calculation dates
## A random old date
START_DATE="2015-11-15"
## Today's date
TODAY=$(date +%Y-%m-%d)

# Import zsh date mod
zmodload zsh/datetime

# Calculate number of days
DIFF=$(( ( $(strftime -r %Y-%m-%d $TODAY) - $(strftime -r %Y-%m-%d $START_DATE) ) / 86400 ))
echo "Your value: " $DIFF

Wynik:

Your value:  1577

Zasadniczo używamy funkcji strftimereverse ( -r), aby przekształcić nasz ciąg z datą z powrotem na znacznik czasu, a następnie wykonujemy obliczenia.


1

Przedstawiłbym inne możliwe rozwiązanie w Rubim. Wygląda na to, że jest najmniejszy i najczystszy do tej pory:

A=2003-12-11
B=2002-10-10
DIFF=$(ruby -rdate -e "puts Date.parse('$A') - Date.parse('$B')")
echo $DIFF

2
Szuka sposobu na bash.
Cojones,

2
Nie ma przenośnego sposobu, aby zrobić to w samej powłoce. Wszystkie alternatywy używają określonych programów zewnętrznych (np. GNU datelub jakiegoś języka skryptowego) i szczerze uważam, że Ruby to dobry sposób, aby tu przejść. To rozwiązanie jest bardzo krótkie i nie wykorzystuje żadnych niestandardowych bibliotek ani innych zależności. W rzeczywistości uważam, że istnieje większe prawdopodobieństwo zainstalowania Rubiego niż zainstalowania daty GNU.
GreyCat

1

Inna wersja Pythona:

python -c "from datetime import date; print date(2003, 11, 22).toordinal() - date(2002, 10, 20).toordinal()"


1

na Uniksie powinieneś mieć zainstalowane daty GNU. nie musisz odchodzić od bash. tutaj jest rozwlekłe rozwiązanie uwzględniające dni, tylko po to, aby pokazać kroki. można go uprościć i rozszerzyć na pełne daty.

DATE=$(echo `date`)
DATENOW=$(echo `date -d "$DATE" +%j`)
DATECOMING=$(echo `date -d "20131220" +%j`)
THEDAY=$(echo `expr $DATECOMING - $DATENOW`)

echo $THEDAY 

1

Zakłada się, że miesiąc to 1/12 roku:

#!/usr/bin/awk -f
function mktm(datespec) {
  split(datespec, q, "-")
  return q[1] * 365.25 + q[3] * 365.25 / 12 + q[2]
}
BEGIN {
  printf "%d\n", mktm(ARGV[2]) - mktm(ARGV[1])
}


0

Załóżmy, że ręcznie rsyncujemy kopie zapasowe Oracle DB na trzeci dysk. Następnie chcemy usunąć stare kopie zapasowe z tego dysku. Oto mały skrypt basha:

#!/bin/sh

for backup_dir in {'/backup/cmsprd/local/backupset','/backup/cmsprd/local/autobackup','/backup/cfprd/backupset','/backup/cfprd/autobackup'}
do

    for f in `find $backup_dir -type d -regex '.*_.*_.*' -printf "%f\n"`
    do

        f2=`echo $f | sed -e 's/_//g'`
        days=$(((`date "+%s"` - `date -d "${f2}" "+%s"`)/86400))

        if [ $days -gt 30 ]; then
            rm -rf $backup_dir/$f
        fi

    done

done

Zmień katalogi i okres przechowywania („30 dni”) zgodnie ze swoimi potrzebami.


Oracle umieszcza zestawy kopii zapasowych w obszarze odzyskiwania Flash przy użyciu formatu takiego jak „YYYY_MM_DD”, więc usuwamy podkreślenia w celu przekazania go do „date -d”
Alex Cherkas,

0

Dla MacOS sierra (może z Mac OS X yosemate),

Aby uzyskać czas epoki (sekundy od 1970 roku) z pliku i zapisać go w zmiennej: old_dt=`date -j -r YOUR_FILE "+%s"`

Aby uzyskać aktualny czas epoki new_dt=`date -j "+%s"`

Aby obliczyć różnicę czasu powyżej dwóch epok (( diff = new_dt - old_dt ))

Aby sprawdzić, czy różnica jest większa niż 23 dni (( new_dt - old_dt > (23*86400) )) && echo Is more than 23 days


0
echo $(date +%d/%h/%y) date_today
echo " The other date is 1970/01/01"
TODAY_DATE="1970-01-01"
BEFORE_DATE=$(date +%d%h%y)
echo "THE DIFFERNS IS " $(( ($(date -d $BEFORE_DATE +%s) - $(date -d $TODAY_DATE +%s)) )) SECOUND

użyj go, aby uzyskać dzisiejszą datę i sekundę od wybranej daty. jeśli chcesz to z dniami, po prostu podziel je przez 86400

echo $(date +%d/%h/%y) date_today
echo " The other date is 1970/01/01"
TODAY_DATE="1970-01-01"
BEFORE_DATE=$(date +%d%h%y)
echo "THE DIFFERNS IS " $(( ($(date -d $BEFORE_DATE +%s) - $(date -d $TODAY_DATE +%s))/ 86400)) SECOUND
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.