Visual2000 · Архив статей Колесова & Павловой

Visual Basic и "Проблема 2000": особенности работы с датами в VB

Андрей Колесов

Загрузить программные приложения (2 Кб, CONVY.zip)

© 1999, Андрей Колесов
Авторский вариант. Статья была опубликована c незначительной литературной правкой в журнале "КомпьютерПресс" № 7/99 и размещена на прилагаемой к нему компакт-диске в виде файла VB2000.htm. Файл, с которым вы работаете сейчас, не является копией VB2000.htm ни по содержанию, ни по дизайну.


В какой степени Visual Basic

В какой степени Visual Basic соответствует требованиям 2000 года? Ответ Microsoft такой:

  1. 32-разрядные версии (4.0 — 6.0) — соответствуют с незначительными проблемами;
  2. 16-разрядные версии (1.0 — 4.0) — не соответствуют;
  3. версии для DOS (QB 4.5, PDS 7.1) — не будут тестироваться.

Мое мнение несколько иное: Microsoft сильное преувеличивает опасность проблем в VB. На мой взгляд их просто нет во всех перечисленных версиях, но нужно знать особенности разных версий и соблюдать определенные правила при написании программного кода.

В начало статьи

Преобразование из двухзначного в четырехзначное представление

Главное проблемой (по мнению Microsoft) является различия правилах преобразования года из двухзначного в четырехзначное представление. Впрочем, эти правила она сама же и меняет — четыре раза за последние пять лет.

Все версии Visual Basic for Windows до версии 3.0 включительно (а также более ранние DOS-овские варианты: VB 1.0 for DOS, QB 4.5 и PDS 7.1) двузначное представление года всегда преобразовывали в четырехзначное простым добавлением 1900. Программы такого преобразования встроены в состав внутренних библиотек продуктов и поэтому не зависят от версии операционной системы и значения текущей даты.

Отмечу от себя, что в версиях QB и PDS проблема преобразования года вообще никогда не была актуальной. Ведь единственным оператором языка, где использовалось это правило, в то время был только DATE$ — программная запись системной даты в MS-DOS. Такая функция в обычных приложениях практически не применялась. Для обычной работы с датами нужно было писать собственные процедуры, и их качество уже зависело от программиста (об этом будет разговор дальше).

В период создания VB 4.0 появился первый вариант VBA (в составе Excel 4.0) и механизм OLE Automation. В этой связи основные функции преобразования дат были переданы библиотекам OLE Automation (а не внутренним библиотекам пакетов), которым пользовались VB 4.0 и VBA. Исключением была только функция DateSerial, которая осталась внутри самого VB 4.0. (для ее реализации возможностей библиотек OLE Automation в те времена не хватало). Тогда же было изменено правило преобразования года: для двухзначного формата использовалось значение текущей системной даты. То есть сегодня при работе в VB 4.0 (16 бит) 99 сегодня будет означать 1999, а через год — 2099.

Первоначальный 32-разрядный вариант VB 4.0 использовал тот же алгоритм, что и 16-разрядный. В нем использовалась 32-разрядная Automation библиотека OleAut32.dll версии 2.10, которая входила в состав пакета. Однако позднее Microsoft решила изменить правило, введя окно в диапазоне 1930-2029 гг.: года 0-29 стали относиться к 21 веку, 30-99 — к 20-му. Однако такое изменения было выполнено только для 32-разрядного варианта OleAut32.dll (начиная с версии 2.20.4049). Таким образом VB 4.0 с новой библиотекой стал обрабатывать двухзначное представление года по новым правилам, кроме (ВНИМАНИЕ!) встроенной функции DateSerial, которая по-прежнему ориентировалась на текущее значение системной даты.

В VB и VBA (Office 97) 5.0 функция DateSerial работает уже по новым правилам (1930-2029), так как здесь используется не внутренняя библиотека пакета, а единая OleAut32.dll. Эта версия библиотеки входит в состав многих продуктов (не только Microsoft), выпущенных в 1996 году и позднее.

В сентябре прошлого года Microsoft выпустила VB 6.0, в котором был реализован тот же механизм преобразования года, что и в VB 5.0. Но не расслабляйтесь: это относится только к первым рабочим вариантам (в частности, как сообщает Microsoft, с датой до 1 августа 1998 года). В состав уже осенних релизов VB 6.0 будет включена новая версия библиотек OLE Automation, где алгоритм вычисления года опять изменен.

Но эта информация, приведенная на Web-узле Microsoft по состоянию на 6 марта 1999 года, не очень точна. Лично у меня установлен коробочный VB 6.0, приобретенный в октябре 1998 года с номером версии 6.00.8176 и датой 25.06.98. В его состав входит OleAut32.dll версии 2.30.4261 с той же датой и она уже работает по новым правилам: диапазон 100-летнего промежутка определяется установками в диалоговом окне "Control Panel|Regional Settings|Date" в Windows 98 (рис. 1 — установлен диапазон 1937-2036). Так же работает стандартная библиотека, входящая в состав Windows 98. Новая библиотека OleAut32.dll входит в состав многих новых продуктов, но для Windows 95 она может использовать диапазон, установленный по умолчанию — 1930-2029.

Рис. 1

Обновление OleAut32.dll соответственно изменяет работу с датами в VB 4.0 и VB/VBA 5.0.

В начало статьи

Очевидное и простое решение

Вы еще успеваете следить за изменениями алгоритма преобразования даты? Короче говоря, чтобы определить, каким образом VB-приложение делает из двухзначного года его четырехзначное представление нужно знать версию VB (для 4.0 еще и разрядность — 16 или 32), версию библиотеки OleAut32.dll и версию Windows (нужно знать еще и региональные установки, но об этом поговорим позднее). Убедиться, как же работает это преобразование на вашем компьютере, можно с помощью одной строчки кода:

Debug.Print Year(Выражение с датой)

Решение проблемы очевидно — нужно сделать собственную простую подпрограмму преобразования формата даты и не зависеть ни прихотей Microsoft. Для примера создайте форму, на которой разместите окно ввода Text1, метку Label1 и командную кнопку Command1 (рис. 2). Напишите программный код для процедур Command1_Click и ConvertYear (листинг 1). Запустите приложение. Далее вводите исходную дату в текстовом окне и после щелчка кнопки вы увидите в поле метки преобразованный вариант (в данном случае в диапазоне 1951-2050 гг.)

Рис. 2

Процедуру ConverYear можно разместить в BAS-модуле или даже в классе (поместив потом в OLE-сервер) и использовать в любом месте вашего приложения. Переменную YearWindow% можно объявить глобальной или передавать в качестве параметра и менять ее значение динамически в процессе работы приложения.

В начало статьи

Срок работоспособности программ

В материалах Microsoft указано, что срок работоспособности VB (включая последнюю версию 6.0) — до 2030 года, а не до 2035 года, как это записано в требованиях 2000 корпорации (см. "Проблема Y2K и продукты Microsoft"). Именно поэтому VB 5.0 и 6.0 причислены к категории "соответствуют с незначительными проблемами". В чем причина таких временных границ, мне выяснить не удалось (даже после обращения с этим вопросом в Microsoft). Однако работа в тестовом режиме с QB 4.5 и VB 3.0-6.0 в течение недели с датой "март 2079 года" не выявила никаких проблем.

В начало статьи

Работа с датами

Начиная с первой версии, в VB была введен понятие типа данных для хранения даты и тогда же появились специальные операции для работы с ними. Однако до версии 3.0 для этого просто использовался тип Variant (подтип 7) и только, начиная с 4.0, был введен специальный тип Date.

Переменная типа Date физически представляет собой вещественное число двойной точности (восемь байтов) формата IEEE, в котором хранится дата для диапазона от 1 января 100 года до 31 декабря 9999 года и время от 0:00:00 до 23:59:59. Сама дата равна целой части этого числа и соответствует порядковому номеру суток, начиная от 30 декабря 1899 года. (0 — это 30.12.1899, значения до и после этой даты имеют соответственно отрицательное и положительное значение.) Времени соответствует дробная часть числа: полночь — 0,0, а полдень — 0,5.

Если вы запустите на выполнение код (для российских региональных установок):

Dim D1 As Double, D2 As Date
D2 = "2 апреля 1999 15:51:56"
D1 = D2
Print D1; D2
D2 = Clng(D2) + 100
Print D2
D2 = 100
Print D2

то получите такой результат:

36252.6610648148  02.04.99  15:51:56
12.07.99
09.04.1900

ПРИМЕЧАНИЕ. Возможность использования алгоритма, применяемого Microsoft для расчета дат и дней недели, до 15 октября 1582 года вызывает серьезные возражения. Подробнее — см. статью "Y2K: как вести календарь?"

Для определения даты можно использовать любые символьные или числовые выражения, а также литералы (выражения заключенный в скобки #), которые могут быть интерпретированы как корректное обозначение даты, например:

"2 апреля 1999", #1 Jan 93#, "02.04.99", 100

Но надо иметь в виду, что попытка присвоения некорректного значения даты вызывает ошибку и останов программы. Поэтому при вводе даты пользователем, перед присвоением ее значения переменной следует проверить ее на правильность с помощью функции IsDate (см. листинг 1).

Для работы с датами в VB имеется целый ряд специальный функций. Версия 5.0 включает такой набор: Now, CDate, CVDate, DateValue, Date, Date$, Format, DateAdd, DateDiff, DatePart, IsDate, Day, Month, Weekday, Year. В VB6 добавлены еще несколько дополнительных функций, которые позволяют преобразовывать дату и отдельные ее элементы в строковое выражение: MonthName, WeekdayName, FormatDateTime. (Следует иметь в виду, что по умолчанию отсчет дней недели в VB ведется, начиная с воскресенья.)

В начало статьи

Национальные особенности

При работе с датой нужно учитывать влияние региональных установок. Проблема заключается в том, что при использовании национальных параметров (в нашем случае российских) VB в одних случаях использует эти установки, в других — исходные, американские. Поэтому не стоит удивляться, что некоторые примеры, приведенные в справочной системе будут работать на вашем компьютере не так, как это сказано в их описании.

Мы рассмотрим здесь только несколько примеров, которые должны показать, то при работе с датами нужно внимательно изучить их специфику и, в частности, учесть возможное влияние изменения региональных установок на работу программы. Сейчас разговор будет идти на примере, международной Windows 98 в российскими параметрами.

Вывод даты всегда выполняется в формате, определенным в региональных установках (для России — ДД.ММ.ГГ), но с их вводом есть проблемы. Для примера введите:

Dim D As Date
D = "2 апреля 1999": Print D
D = "2.04.99": Print D
D = "2/04/99": Print D
D = "2 Apr 99": Print D

В первых трех случаях будет напечатано одно и тоже — 02.04.99. А в четвертом будет ошибка программы — английское строковое выражение является недействительным. В варианте США первые два выражения будут ошибочными, а двух последних будут выдано — 02/04/99 (2 февраля!) и 04/02/99 (2 апреля).

Еще больше неожиданностей ждет тех, кто предпочитает иметь дело с литералами. Дело в том, что VB при работе с ними использует только встроенный американский вариант, игнорируя региональные установки. Попытка ввести D = #2 апреля 1999# или D = #2.04.99# сразу же вызовет сообщение об ошибке. В то же время при работе с российскими (!) установками строки

D = #04/02/99# или D = #2 Apr 99#

cразу будут преобразованы в

D = #4/2/99#

что будет соответствовать 2 апреля (а не 4 февраля). Для примера выполните такой код:

D1 = #2/4/99#: D2 = "02/04/99"
Print D1, D2

В русском варианте будет напечатано:

04.02.99  02.04.99  ' 4 февраля и 2 апреля

В американском варианте будет напечатано:

02/04/99  02/04/99  ' 4 февраля и 4 февраля

Еще один вопрос: использование значения текущей даты, которое определяется с помощью функций Date и Date$. Первая возвращает значение даты в числовом виде, вторая — в символьном, который имеет формат "MM-DD-YYYY" независимо от региональных установок. Выполните такой код (для текущей даты 2 апреля):

D1 = Date
D2 = Date$
Print D1; D2; Date$

В русском варианте будет напечатано:

02.04.99  04.02.99  04-02-1999

В американском варианте будет напечатано:

04/02/99  04/02/99  04-02-1999

В начало статьи

Возможные проблемы

Ключевая проблема заключается в том, что работы с датами часто приходится использовать, кроме специального типа данных Date, еще и символьный формат строковых переменные. Тип String обычно используется для ввода данных, и часто применяется в пользовательских процедурах для какой-то специальной обработки. Строковый формат наверняка понадобится, если вы захотите работать с датами юлианского календаря (см. "Y2K: как вести календарь?"). Типичной проблемы является аварийное завершение программы в момент преобразования даты (неверного формата) из символьного представления в тип Date.

С учетом всего сказанного сформулируем следующие СОВЕТЫ:

В начало статьи

Листинг 1. Утилита преобразования даты

Private Sub Command1_Click()
  Dim TextDate$

  TextDate$ = Text1.Text
  Call ConvertYear(TextDate$)
  If TextDate$ <> "" Then ' правильная дата
    Label1.Caption = "Дата:  " & TextDate$
  Else
    Label1.Caption = "Неверный формат даты"
  End If
End Sub

Public Sub ConvertYear(TextDate$)
  'преобразование даты:
  ' двухзначное представление года в четырехзначное
  '
  ' Подразумевается российский стандарт даты: День.Месяц.Год
  Const YearWindow% = 50 ' год смены веков
  Dim strYear$, iPoint%
  iPoint% = InStr(TextDate$, ".")
  If iPoint% > 0 Then
    iPoint% = InStr(iPoint% + 1, TextDate$, ".")
    If iPoint% > 0 Then ' найдено поле года
      strYear$ = Mid$(TextDate$, iPoint% + 1)
      If Len(strYear$) <= 2 And strYear$ <> "" Then
        ' преобразование формата года<
        If Len(strYear$) = 1 Then strYear$ = "0" & strYear$
        If CInt(strYear$) <= YearWindow% Then
          strYear$ = "20" & strYear$
        Else
          strYear$ = "19" & strYear$
        End If
        TextDate$ = Left$(TextDate$, iPoint%) + strYear$
      End If
    End If
  End If
  ' проверка на правильность даты:
  If Not IsDate(TextDate$) Then TextDate$ = ""
End Sub

В начало