№ 100. Вычисление дней между датами
Сколько дней вы живете? Сколько дней прошло с того момента, как ваши родители встретились? Подобные вопросы, связанные с определением разности между датами, возникают часто, но вычислить ответ на них обычно бывает непросто. И снова на помощь нам приходит утилита GNU date.
Идея обоих сценариев, № 100 и № 101, заключается в том, что суммируются дни от первой даты до конца года, дни от начала года до второй даты и число дней во всех промежуточных годах. Один и тот же подход можно использовать для вычисления количества дней, прошедших с некоторой даты в прошлом (этот сценарий), и количества дней, оставшихся до некоторой даты в будущем (сценарий № 101).
Листинг 15.3 довольно длинный. Готовы?
Листинг 15.3.Сценарий daysago
··#!/bin/bash
··# daysago — получая дату в формате месяц/день/год, вычисляет количество
··#·· дней, прошедших от нее до текущего дня, учитывая високосные годы, и пр.
··# Если вы используете Linux, замените "$(which gdate)" на "$(which date)".
··#·· Если вы используете OS X, установите пакет coreutils с помощью brew или
··#·· из исходного кода, чтобы получить команду gdate.
··date="$(which gdate)"
··function daysInMonth
··{
····case $1 in
······1|3|5|7|8|10|12) dim=31;; # Постоянное значение
······4|6|9|11········) dim=30;;
······2··············) dim=29;; # Зависит от года: високосный/невисокосный
······*··············) dim=-1;; # Неизвестный месяц
····esac
··}
··function isleap
··{
····# Возвращает ненулевое значение в $leapyear, если $1 — високосный год.
····leapyear=$($date −d 12/31/$1 +%j | grep 366)
··}
··#######################
··#### ОСНОВНОЙ БЛОК
··#######################
··if [$# −ne 3]; then
····echo "Usage: $(basename $0) mon day year"
····echo " with just numerical values (ex: 7 7 1776)"
····exit 1
··fi
··$date −version > /dev/null 2>&1 # Отбросить сообщение об ошибке, если появится.
··if [$? -ne 0]; then
····echo "Sorry, but $(basename $0) can't run without GNU date." >&2
····exit 1
··fi
··eval $($date "+thismon=%m;thisday=%d;thisyear=%Y;dayofyear=%j")
··startmon=$1; startday=$2; startyear=$3
··daysInMonth $startmon # Инициализирует глобальную переменную dim.
··if [$startday −lt 0 −o $startday −gt $dim]; then
····echo "Invalid: Month #$startmon only has $dim days." >&2
····exit 1
··fi
··if [$startmon −eq 2 −a $startday −eq 29]; then
····isleap $startyear
····if [-z "$leapyear"]; then
······echo "Invalid: $startyear wasn't a leap year; February had 28 days." >&2
······exit 1
····fi
··fi
··#######################
··#### ВЫЧИСЛЕНИЕ КОЛИЧЕСТВА ДНЕЙ
··#######################
··#### ДНЕЙ В НАЧАЛЬНОМ ГОДУ
··# Собрать строку формата с начальной датой.
··startdatefmt="$startmon/$startday/$startyear"
··calculate="$((10#$($date −d "12/31/$startyear" +%j))) \
····-$((10#$($date −d $startdatefmt +%j)))"
··daysleftinyear=$(($calculate))
··#### ДНЕЙ В ПРОМЕЖУТОЧНЫХ ГОДАХ
··daysbetweenyears=0
··tempyear=$(($startyear + 1))
··while [$tempyear −lt $thisyear]; do
····daysbetweenyears=$(($daysbetweenyears + \
····$((10#$($date −d "12/31/$tempyear" +%j)))))
····tempyear=$(($tempyear + 1))
··done
··#### ДНЕЙ В ТЕКУЩЕМ ГОДУ
··dayofyear=$($date +%j) # Это просто!
··#### ТЕПЕРЬ СЛОЖИТЬ ВСЕ ВМЕСТЕ
··totaldays=$(($((10#$daysleftinyear)) + \
····$((10#$daysbetweenyears)) + \
····$((10#$dayofyear))))
··/bin/echo −n "$totaldays days have elapsed between "
··/bin/echo −n "$startmon/$startday/$startyear "
··echo "and today, day $dayofyear of $thisyear."
··exit 0
Сценарий получился довольно длинным, но не слишком сложным. Функция определения високосного года
достаточно простая — она всего лишь проверяет, равно ли количество дней в году 366.
В строке
выполняется интересная проверка наличия GNU-версии date в системе перед продолжением работы.
Оператор перенаправления отбрасывает любой вывод и сообщения об ошибках, а возвращаемый код проверяется на неравенство нулю, которое свидетельствует об ошибке при попытке разобрать параметр −version. В OS X, например, устанавливается версия date с минимальными возможностями — она не поддерживает параметра −version и многих других.
Читать дальше