Как найти разницу в днях между двумя датами?

84

A = "2002-20-10"
B = "2003-22-11"

Как найти разницу в днях между двумя датами?

Дерево
источник
5
Как объясняется ниже, но ваши даты неправильные: они должны быть гггг-мм-дд
Питер Флинн
Во многих ответах используется опция -d даты (GNU-). Я хочу добавить, что это НЕ является частью даты POSIX, поэтому менее переносимо. Пока OP работает не с такими дистрибутивами Unix, как Solaris, а только с «обычным Linux», он или она должны быть хороши. Соответствующий тег помог бы imho.
Кадоис,

Ответы:

34

Если у вас есть GNU date, он позволяет распечатать представление произвольной даты ( -dопция). В этом случае преобразуйте даты в секунды с момента EPOCH, вычтите и разделите на 24 * 3600.

Или нужен переносной способ?


источник
1
@Tree: Я считаю, что нет портативного способа вычитания дат, кроме как реализовать его самостоятельно - это не так уж сложно, единственное, что интересно, - это високосные годы. Но в настоящее время все хотят вычитать даты, как кажется: взгляните на unix.stackexchange.com/questions/1825/… :)
15
Ради lazyweb, я думаю, что user332325 имел в виду:echo "( `date -d $B +%s` - `date -d $A +%s`) / (24*3600)" | bc -l
Пабло Мендес
2
Что в этом контексте означает переносимый?
htellez
@htellez то, что работает на нескольких платформах (Windows, Linux и т. д.)
Sumudu
Для bash one liner, основанного на этом ответе, см .: stackoverflow.com/a/49728059/1485527
jschnasse
64

Способ bash - конвертируйте даты в формат% y% m% d, а затем вы можете сделать это прямо из командной строки:

echo $(( ($(date --date="031122" +%s) - $(date --date="021020" +%s) )/(60*60*24) ))
технарей
источник
9
Необязательно, чтобы даты были в %y%m%dформате. Например, gnu date примет %Y-%m-%dформат, используемый OP. В общем, ISO-8601 - хороший выбор (и формат OP является одним из таких форматов). Форматы с двузначным годом лучше избегать.
mc0e
2
Для отличия от сегодняшнего дня можно использовать days_diff=$(( (`date -d $B +%s` - `date -d "00:00" +%s`) / (24*3600) )). обратите внимание, что $days_diffэто будет целое число (т.е. без десятичных знаков)
Жюльен
1
Это зависит от варианта dateустановки. Это работает для GNU date. Он не работает с POSIX date. Вероятно, это не проблема для большинства читателей, но стоит отметить.
mc0e 01
Если вам действительно нужна переносимость POSIX, проверьте это: unix.stackexchange.com/a/7220/43835
mc0e 01
1
Просто боевой путаницы: формат OP является не %Y-%m-%d , пока мы не введем August 2.0и October 2.0формат OP является %Y-%d-%m. Соответствующий xkcd: xkcd.com/1179
конфетти
22

tl; dr

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

Осторожно! Многие из решений bash здесь не работают для диапазонов дат, которые охватывают дату начала летнего времени (где применимо). Это связано с тем, что конструкция $ ((math)) выполняет операцию «пол» / усечения над результирующим значением, возвращая только целое число. Позвольте мне проиллюстрировать:

Летнее время в США началось 8 марта этого года, поэтому давайте использовать диапазон дат, охватывающий следующие области:

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

Посмотрим, что получится с двойными скобками:

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

Возвращает «5».

Если сделать это с большей точностью с помощью bc, то получится другой результат:

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

Возвращает «5.95» - недостающие 0,05 - это час, потерянный после перехода на летнее время.

Так как же это сделать правильно?
Я бы предложил использовать вместо этого:

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

Здесь printf округляет более точный результат, вычисленный параметром bc, давая нам правильный диапазон дат - 6.

Изменить: выделение ответа в комментарии от @ hank-schultz ниже, который я использовал в последнее время:

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

Это также должно быть безопасным для секунды координации, если вы всегда вычитаете более раннюю дату из более поздней, поскольку секунды координации будут только увеличивать разницу - усечение эффективно округляется до правильного результата.

evan_b
источник
6
Если вместо этого вы укажете часовой пояс как для начала, так и для конца (и убедитесь, что они совпадают), тогда у вас также больше не будет этой проблемы, например:, echo $(( ($(date --date="2015-03-11 UTC" +%s) - $(date --date="2015-03-05 UTC" +%s) )/(60*60*24) ))которая возвращает 6 вместо 5.
Хэнк Шульц,
1
Ах, один из ответивших подумал о неточностях основного решения, повторенных другими в вариантах. Недурно! Как насчет дополнительных секунд? Это безопасно для секунды? Существуют прецеденты, когда программное обеспечение возвращало результат за один день при запуске в определенное время, ссылаясь на проблемы, связанные с дополнительной секундой .
Стефан Гуришон
Woops, "неправильное" вычисление, которое вы приводите в качестве примера, здесь правильно возвращает 6 (Ubuntu 15.04 AMD64, версия GNU date version 8.23). Возможно, ваш пример зависит от часового пояса? Мой часовой пояс - «Европа / Париж».
Стефан Гуришон
Тот же хороший результат для "неправильных" вычислений с GNU date 2.0 на MSYS, в том же часовом поясе.
Стефан Гуришон
1
@ StéphaneGourichon, изменение летнего времени в вашем часовом поясе, похоже, произошло 29 марта 2015 года, поэтому попробуйте диапазон дат, который включает это.
evan_b 04
21

И в питоне

$python -c "from datetime import date; print (date(2003,11,22)-date(2002,10,20)).days"
398
Фред Лоран
источник
2
@mouviciel не совсем. Python присутствует не всегда.
mc0e
1
@ mc0e в этом контексте ничего не присутствует всегда
Sumudu
5
@Anub - это теги, указанные OP как bashи shell, поэтому я думаю, мы можем предположить, что мы говорим о системе * nix. Внутри таких систем echoи dateнадежно присутствуют, а вот питона нет.
mc0e 01
2
@ mc0e Да, в этом есть смысл. Несмотря на то, что python2 доступен в большинстве систем * nix, когда дело доходит до устройств низкого уровня (встроенных и т. Д.), Нам придется выжить только с основными инструментами.
Sumudu 02
13

Это работает для меня:

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

Печать

398 days

Что происходит?

  1. Укажите действительную строку времени в A и B
  2. Использовать date -d для обработки строк времени
  3. Используется date %sдля преобразования строк времени в секунды с 1970 года (unix epoche)
  4. Используйте расширение параметра bash, чтобы вычесть секунды
  5. разделите на секунды в день (86400 = 60 * 60 * 24), чтобы получить разницу в днях
  6. ! Летнее время не учитывается! См. Этот ответ на unix.stackexchange !
jschnasse
источник
7

Если опция -d работает в вашей системе, вот еще один способ сделать это. Есть предостережение, что он не будет учитывать високосные годы, поскольку я считал 365 дней в году.

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

Вот версия MAC OS X для вашего удобства.

$ 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: синтаксическая ошибка: ожидается операнд (токен ошибки - ") / 86400")
cavalcade
1
@cavalcade - это потому, что вы еще не сделалиA="2002-20-10"; B="2003-22-11"
RAM237 05
Спасибо, несмотря на то, что это работает в этом конкретном случае, я бы предложил использовать echo $(((`date -jf "%Y-%d-%m" "$B" +%s` - `date -jf "%Y-%d-%m" "$A" +%s`)/86400)), то есть заключить оба формата и переменные в двойные кавычки, иначе может произойти сбой в другом формате даты (например, %b %d, %Y/ Jul 5, 2017)
RAM237
@ RAM237 Вот что я получаю за то, что не обращаю внимания на хедсмастер Хорошо замечено, спасибо
cavalcade
5

Даже если у вас нет даты GNU, у вас, вероятно, установлен 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";

Это возвращается 398.041666666667- 398 дней и один час из-за летнего времени.


Вопрос снова появился в моей ленте. Вот более краткий метод с использованием модуля Perl.

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

Это самое простое, что мне удалось поработать над 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
Gotxi
источник
2

Вот мой рабочий подход с использованием zsh. Проверено на 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

Результат:

Your value:  1577

По сути, мы используем функцию strftimereverse ( -r) для преобразования нашей строки даты обратно в метку времени, а затем производим наши вычисления.

Оливье Б.
источник
1

Я бы предложил другое возможное решение на Ruby. Похоже, это самый маленький и чистый на данный момент:

A=2003-12-11
B=2002-10-10
DIFF=$(ruby -rdate -e "puts Date.parse('$A') - Date.parse('$B')")
echo $DIFF
GreyCat
источник
2
Он ищет выход в баше.
Cojones
2
Нет переносимого способа сделать это в самой оболочке. Во всех альтернативах используются определенные внешние программы (например, GNU dateили какой-либо язык сценариев), и я искренне думаю, что Ruby - хороший вариант . Это очень короткое решение, в котором не используются нестандартные библиотеки или другие зависимости. На самом деле, я думаю, что вероятность того, что Ruby будет установлена, выше, чем дата GNU.
GreyCat 08
1

Другая версия Python:

python -c "from datetime import date; print date(2003, 11, 22).toordinal() - date(2002, 10, 20).toordinal()"
Ульф
источник
1

в unix у вас должны быть установлены даты GNU. вам не нужно отклоняться от bash. вот натянутое решение с учетом дней, просто чтобы показать шаги. его можно упростить и расширить до полных дат.

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

echo $THEDAY 
Маттимик
источник
1

Предполагается, что месяц равен 1/12 года:

#!/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

Попробуйте:

perl -e 'use Date::Calc qw(Delta_Days); printf "%d\n", Delta_Days(2002,10,20,2003,11,22);'
Деннис Уильямсон
источник
0

Предположим, мы вручную выполняем синхронизацию резервных копий Oracle DB на третичный диск. Затем мы хотим удалить старые резервные копии на этом диске. Итак, вот небольшой сценарий bash:

#!/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

Измените каталоги и срок хранения («30 дней») в соответствии с вашими потребностями.

Алексей Черкас
источник
Oracle помещает наборы резервных копий в Flash Recovery Area, используя такой формат, как 'YYYY_MM_DD', поэтому мы удаляем символы подчеркивания для передачи их на 'date -d'
Alex Cherkas
0

Для MacOS sierra (возможно, из Mac OS X yosemate),

Чтобы получить время эпохи (секунды с 1970 года) из файла и сохранить его в переменной: old_dt=`date -j -r YOUR_FILE "+%s"`

Чтобы узнать время текущего времени new_dt=`date -j "+%s"`

Чтобы вычислить разницу во времени двух эпох (( diff = new_dt - old_dt ))

Чтобы проверить, не превышает ли разница 23 дней (( new_dt - old_dt > (23*86400) )) && echo Is more than 23 days

osexp2003
источник
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

используйте его, чтобы получить дату сегодня и вашу секунду с нужной даты. если вы хотите, чтобы это было дни, просто разделите его на 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
Заед Казем
источник