MyTetra Share
Делитесь знаниями!
Как вычислить интервал между двумя датами, измеряемый в разных единицах
Время создания: 31.07.2019 22:37
Раздел: !Закладки - VBA
Запись: xintrea/mytetra_db_adgaver_new/master/base/15201090111rdifnbp65/text.html на raw.githubusercontent.com

Как вычислить интервал между двумя датами, измеряемый в разных единицах

Как вы уже наверняка знаете, для вычисления интервала между двумя датами можно использовать встроенную в 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 хотелось бы обратить внимание еще на три используемые нами конструкции:

  1. Для передачи возвращаемых параметров мы используем массив Param() с ключевым словом ParamArray. Такая конструкция применима только в конце списка аргументов подпрограммы и указывает, что данный аргумент является массивом типа Optional элементов типа Variant. С его помощью можно задать произвольное число аргументов. Кроме того, ParamArray нельзя использовать вместе с ключевыми словами ByVal, ByRef, или Optional.

    В принципе, можно было бы просто зарезервировать в вызывающей подпрограмме массив Param (0 To 5) и использовать непосредственно его. Но в данном случае подпрограмма выполнила бы расчет для всех элементов этого массива. Применение ParamArray позволяет нам пропускать "ненужные" параметры. Например, в нашем обращении мы получим результат в полных годах и весь остаток интервала — в секундах:

    Call DateIntervals("28.02.2000", "01.03.2001", Years, , , , ,Secs)



  2. Для выбора нужного значения из двух вариантов мы используем функцию:

    MyVal = IIf(expr, truepart, falsepart)



    которая равнозначна такому варианту:

    If expr Then

    MyVal = truepart

    Else

    MyVal = falsepart

    End If



  3. При коррекции временного интервала мы использовали такую конструкцию:

    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). Мы, со своей стороны, настоятельно не рекомендуем пользоваться неявными преобразованиями типов данных.

Так же в этом разделе:
 
MyTetra Share v.0.59
Яндекс индекс цитирования