Як знайти різницю в днях між двома датами?


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
Заради ледачого, я думаю, саме це означав user332325:echo "( `date -d $B +%s` - `date -d $A +%s`) / (24*3600)" | bc -l
Пабло Мендес,

2
Що в цьому контексті означає портативний?
htellez

@htellez щось, що працює на кількох платформах (Windows, Linux тощо.)
Сумуду

Для Баша один вкладиш на основі цієї відповіді дивіться: stackoverflow.com/a/49728059/1485527
jschnasse

64

Баш-спосіб - перетворіть дати у формат% y% m% d, а потім ви можете зробити це прямо з командного рядка:

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

9
Для цього не обов’язково дати дати у %y%m%dформаті. Наприклад, дата gnu приймає %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.03.18

Якщо ви дійсно хочете переносити POSIX, перевірте це: unix.stackexchange.com/a/7220/43835
mc0e

1
Просто бойової плутанини: формат OP є НЕ %Y-%m-%d , поки ми не введемо August 2.0і October 2.0формат OP є %Y-%d-%m. Відповідний xkcd: xkcd.com/1179
конфетті

22

tl; д-р

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

Стережись! Багато баш-рішень тут розбиті на діапазони дат, які охоплюють дату, коли починається перехід на літній час (де це можливо). Це пов’язано з тим, що конструкція $ ((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) ))

Це також повинно бути другим безпечним стрибком, якщо ви завжди віднімаєте попередню дату від пізнішої, оскільки високосні секунди лише збільшують різницю - скорочення фактично округлюється до правильного результату.


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, версія 8.23). Можливо, ваш приклад залежить від часового поясу? Мій часовий пояс - "Європа / Париж".
Стефан Гурішон,

Той самий хороший результат для "неправильного" обчислення з датою GNU 2.0 на MSYS, той самий часовий пояс.
Стефан Гурішон,

1
@ StéphaneGourichon, зміна переходу на літній час у вашому часовому поясі, здається, відбулася 29 березня 2015 року, тому спробуйте діапазон дат, який включає це.
evan_b

21

І в python

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

2
@mouviciel не дуже. Python присутній не завжди.
mc0e

1
@ mc0e, у цьому контексті нічого не завжди присутнє
Сумуду,

5
@Anubis OP вказав теги як bashі shell, тому, я думаю, ми можемо припустити, що мова йде про систему * nix. У таких системах, echoі dateдостовірно присутній, але пітон немає.
mc0e 01.03.18

2
@ mc0e так, це має сенс. Незважаючи на те, що python2 доступний у більшості систем * nix, коли мова заходить про пристрої низького класу (вбудовані тощо), нам доведеться виживати лише за допомогою основних інструментів.
Сумуду

13

Це працює для мене:

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

Відбитки

398 days

Що відбувається?

  1. Надайте дійсний часовий рядок в А і В
  2. Використовуйте date -dдля обробки рядків часу
  3. Використовується date %sдля перетворення часових рядків у секунди з 1970 року (епоха Unix)
  4. Використовуйте розширення параметра bash, щоб відняти секунди
  5. розділити на секунди на день (86400 = 60 * 60 * 24), щоб отримати різницю як дні
  6. ! Літній час не враховується! Дивіться цю відповідь на unix.stackexchange !

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))

радість!


1
-bash: (-) / 86400: синтаксична помилка: очікується операнд (маркер помилки ") / 86400")
кавалькада

1
@cavalcade - це тому, що ви цього ще не зробилиA="2002-20-10"; B="2003-22-11"
RAM237

Дякую, незважаючи на те, що це працює в даному конкретному випадку, я б запропонував використовувати echo $(((`date -jf "%Y-%d-%m" "$B" +%s` - `date -jf "%Y-%d-%m" "$A" +%s`)/86400)), тобто загортання як форматів, так і змінних у подвійні лапки, інакше може не вдатися в іншому форматі дати (наприклад %b %d, %Y/ Jul 5, 2017)
RAM237

@ RAM237 Ось що я отримую за те, що не звертаю уваги на хедмейк Добре помічений, дякую
кавалькада

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

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

2
Він шукає дорогу в Баш.
Койонес

2
Немає портативного способу зробити це в самій оболонці. Всі альтернативи використовують певні зовнішні програми (тобто GNU dateабо якусь мову сценаріїв), і я, чесно кажучи, вважаю, що Ruby - це хороший спосіб піти сюди. Це рішення дуже коротке і не використовує нестандартних бібліотек та інших залежностей. Насправді, я думаю, що шанси встановити Ruby вищі, ніж у GNU.
GreyCat

1

Інша версія Python:

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

1

Використовуйте функції оболонки з http://cfajohnson.com/shell/ssr/ssr-scripts.tar.gz ; вони працюють в будь-якій стандартній оболонці Unix.

date1=2012-09-22
date2=2013-01-31
. date-funcs-sh
_date2julian "$date1"
jd1=$_DATE2JULIAN
_date2julian "$date2"
echo $(( _DATE2JULIAN - jd1 ))

Дивіться документацію за адресою http://cfajohnson.com/shell/ssr/08-The-Dating-Game.shtml


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

Припустимо, ми вручну синхронізуємо резервні копії 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 із використанням такого формату, як `` YYYY_MM_DD '', тому ми видаляємо символи підкреслення для передачі їх на 'date -d'
Алекс Черкас,

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


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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.