Как вычислить интервал между двумя датами, измеряемый в разных единицах
Как вы уже наверняка знаете, для вычисления интервала между двумя
датами можно использовать встроенную в VB функцию DateDiff. Но
работать с этой функцией нужно очень внимательно, с учетом входящих в
нее ограничений.
Отгадайте такую загадку. Заданы две даты в виде переменных DateStart и
DateFinish. Чтобы определить временной интервал между ними, мы
написали такую процедуру:
Print "Интервал в годах = "; DateDiff("yyyy", DateStart, DateFinish)
Print "Интервал в месяцах= "; DateDiff("m", DateStart, DateFinish)
Print "Интервал в днях = "; DateDiff("d", DateStart, DateFinish)
Print "Интервал в часах = "; DateDiff("h", DateStart, DateFinish)
Print "Интервал в минутах = "; DateDiff("n", DateStart, DateFinish)
Print "Интервал в секундах= "; DateDiff("s", DateStart, DateFinish)
И получили такой парадоксальный результат:
Интервал в годах = 1
Интервал в месяцах = 1
Интервал в днях = 1
Интервал в часах = 1
Интервал в минутах = 1
Интервал в секундах = 1
ВОПРОС. Почему так произошло и о каких датах шла речь?
ОТВЕТ. Дело в том, что функция DateDiff определяет временной интервал
элементарно — в соответствии с заданным первым параметром просто
отбрасывает значения даты "после этой точки". То есть если вы задали
"день", то отбрасываются часы (0 часов), если месяц — дни (первое
число месяца). В соответствии с этим алгоритмом получается, что между
31 мая 2000-го и 1 июня 2000-го в единицах "месяц" разница — один
месяц (что в определенном смысле совершенно верно).
В нашем же примере исходные значения даты были равны
DateS = "31.12.2000 23:59:59"
DateF = "01.01.2001"
Изменение показателя текущего момента на одну секунду привело к
изменению минут, часов, суток, месяца и года (и даже века и
тысячелетия). Очевидно, что самое точное определение интервала дается
в данном случае в секундах (этой точности вполне достаточно для
решения большинства бытовых и деловых проблем). Но как
интерпретировать величину типа 12 345 678 сек? Конечно, желательно
получить информацию в более привычных единицах — месяцах, днях,
минутах. В таких случаях вам поможет подпрограмма DateIntervals,
позволяющая передавать две даты и свои собственные переменные для
указанного интервала:
Public Sub DateIntervals(ByVal DateS _
As Date, ByVal DateF As Date, ParamArray Prams())
If UBound(Prams) < 0 Then Exit Sub
Dim i As Long, itr As String
'
' Если не задан день, то считаем его "сегодняшним"
If DateValue(DateS) = 0 Then DateS = DateS + DateValue(Now)
If DateValue(DateF) = 0 Then DateF = DateF + DateValue(Now)
'
For i = 0 To IIf(UBound(Prams) > 5, 5, UBound(Prams))
If Not IsMissing(Prams(i)) Then
If i = 0 Then
itr = "yyyy"
Else
itr = Mid$("mdhns", i, 1)
End If
Prams(i) = DateDiff(itr, DateS, DateF)
If DateAdd(itr, Prams(i), DateS) <= DateF Then _
Prams(i) = Prams(i) - 1
DateS = DateAdd(itr, Prams(i), DateS)
End If
Next i
End Sub
Подпрограмма DateIntervals возвращает наибольший полный интервал
указанного вами типа (год, месяц, день, час, минута, секунда) между
двумя датами. Например, чтобы получить интервал времени в часах и
минутах между 09:00 и 17:15, передайте в подпрограмму эти две даты, а
также две переменные, задающие размерность интервала. Используйте
запятые, чтобы пропустить более крупные ненужные интервалы:
Dim Hours As Variant, Minutes As Variant
Call DateIntervals(Now, "23.05.2000", , , , Hours, Minutes)
MsgBox "Часов = " & Hours & _
"Минут = " & Minutes
Подпрограмма вернет "Часов = 8, Минут = 15".
Однако здесь следует обратить внимание на такой любопытный момент. Если вы
выполните такое обращение к функции:
Call DateIntervals("28.02.2000", "01.03.2001", Years, , Days)
MsgBox Years & " " & Days
Call DateIntervals("29.02.2000", "01.03.2001", Years, , Days)
MsgBox Years & " " & Days
то получите для разных начальных дат один и тот же результат — 1 год
и 1 день. Казалось бы, в подпрограмме есть ошибка, но это не так.
Данный парадокс объясняется неопределенностью интервала в один год —
он может быть 365 и 366 дней (так же, как и в один месяц).
Соответственно в первом случае "год" является високосным
(366 дней), а во втором — обычным (365 дней). Чтобы представить
эту ситуацию, вообразите, что ваш знакомый говорит 31 января:
"Позвони мне ровно через месяц" (или 29 февраля 2000 года
— "ровно через год"). Когда же будет эта точная дата
намеченного звонка?
Отметим также, что алгоритм расчета интервала с использованием
привычных единиц можно выполнить и по-другому — сначала
определить интервал в секундах, а потом выделить из него минуты, часы
и сутки (с месяцами и годами тут возникнут те же проблемы). Но он
будет выглядеть не так изящно, как приведенная выше подпрограмма.
В процедуре DateIntervals хотелось бы обратить внимание еще на три
используемые нами конструкции:
- Для передачи возвращаемых параметров мы используем массив Param() с ключевым
словом ParamArray. Такая конструкция применима только в конце списка аргументов
подпрограммы и указывает, что данный аргумент является массивом типа Optional
элементов типа Variant. С его помощью можно задать произвольное число
аргументов. Кроме того, ParamArray нельзя использовать вместе с ключевыми
словами ByVal, ByRef, или Optional.
В принципе, можно было бы просто зарезервировать в вызывающей подпрограмме
массив Param (0 To 5) и использовать непосредственно его. Но в данном случае
подпрограмма выполнила бы расчет для всех элементов этого массива. Применение
ParamArray позволяет нам пропускать "ненужные" параметры. Например, в нашем
обращении мы получим результат в полных годах и весь остаток интервала
— в секундах:
Call DateIntervals("28.02.2000", "01.03.2001", Years, , , , ,Secs)
- Для выбора нужного значения из двух вариантов мы используем функцию:
MyVal = IIf(expr, truepart, falsepart)
которая равнозначна такому варианту:
If expr Then
MyVal = truepart
Else
MyVal = falsepart
End If
- При коррекции временного интервала мы использовали такую конструкцию:
If DateAdd(itr, Prams(i), DateS) <= DateF Then _
Prams(i) = Prams(i) - 1
Любители хитроумных преобразований данных могли бы предложить более
"изящный" вариант:
Prams(i) = Prams(i) + (DateAdd(itr, Prams(i), DateS) > DateF)
имея в виду, что арифметическое значение логического выражения будет
равно -1 (True) или 0 (False). Мы, со своей стороны, настоятельно не
рекомендуем пользоваться неявными преобразованиями типов данных.