Рекомендации по написанию кода с использованием даты и времени в платформа .NET Framework
Дэн Роджерс
Microsoft Corporation
Февраль 2004 г.
Применяется к
Microsoft® платформа .NET Framework
Веб-службы Microsoft® ASP.NET
сериализация XML
Сводка: Для создания программ, которые хранят, выполняют вычисления и сериализуют значения времени с помощью типа DateTime в Microsoft платформа .NET Framework, необходимо ознакомиться с различными проблемами, связанными с представлениями времени, доступными в Windows и .NET. В этой статье рассматриваются ключевые сценарии тестирования и разработки, требующие времени, и определяются рекомендации по написанию программ, использующих тип DateTime в Microsoft . Приложения и сборки на основе NET. (18 печатных страниц)
Содержимое
Историческая справка
Что такое DateTime, в любом случае?
Правила
Стратегии хранения
Рекомендации 1
Рекомендации 2
Выполнение вычислений
Больше не обманывайте
Рекомендации 3
Методы сортировки даты и времени
Особый случай XML
Рекомендации 4
Рекомендация No 5
Класс Coders Quandary
Рекомендации 6
Работа с переходом на летнее время
Рекомендация 7
Форматирование и анализ значений User-Ready
Будущие рекомендации
Проблемы с методом DateTime.Now()
Рекомендации 8
Пара малоизвестных статистов
Заключение
Историческая справка
Многие программисты сталкиваются с назначениями, которые требуют от них точного хранения и обработки данных, содержащих сведения о дате и времени. На первый взгляд для этих задач идеально подходит тип данных среды CLR DateTime . Однако не редкость для программистов, но, скорее всего, тест-инженеров, встречаются случаи, когда программа просто теряет правильные значения времени. В этой статье рассматриваются проблемы, связанные с логикой, связанной с DateTime, и при этом рассматриваются рекомендации по написанию и тестированию программ, которые собирают, хранят, извлекают и передают сведения о дате и времени.
Что такое DateTime, в любом случае?
При просмотре документации по библиотеке классов NET Framework мы видим, что "Тип значения CLR System.DateTime представляет даты и время в диапазоне от 12:00:00 полуночи, 1 января 0001 г. AD до 23:59:59, 31 декабря 9999 г." Ознакомившись с дополнительными сведениями, неудивительно, что значение DateTime представляет собой момент времени и что распространенной практикой является запись значений точки во времени в формате универсального скоординированного времени (UCT), более известного как среднее время по Гринвичу (GMT).
На первый взгляд программист обнаруживает, что тип DateTime очень хорош при хранении значений времени, которые могут возникнуть в текущих проблемах программирования, например в бизнес-приложениях. С такой уверенностью многие не подозревающие программисты начинают писать код, уверенные в том, что они могут узнать столько, сколько им нужно, чтобы примерно время, как они идти вперед. Этот подход "учиться по мере использования" может привести к возникновению нескольких проблем, поэтому давайте приступим к их выявлению. Они варьируются от проблем в документации до поведения, которые необходимо учитывать при разработке программы.
Документация версий 1.0 и 1.1 для System.DateTime содержит несколько обобщений, которые могут покинуть подозревающий программист. Например, в документации по-прежнему говорится, что методы и свойства класса DateTime всегда используют предположение, что значение представляет локальный часовой пояс локального компьютера при вычислениях или сравнениях. Такое обобщение не соответствует действительности, так как существуют определенные типы вычислений даты и времени, которые предполагают GMT, а другие — представление местного часового пояса. Эти области описаны далее в этой статье.
Итак, давайте приступим к изучению типа DateTime, изложив ряд правил и рекомендаций, которые помогут вам обеспечить правильную работу кода в первый раз.
Правила
- Вычисления и сравнения экземпляров DateTime имеют смысл только в том случае, если сравниваемые или используемые экземпляры представляют собой представления точек во времени с той же точки зрения часового пояса.
- Разработчик отвечает за отслеживание сведений о часовом поясе, связанных со значением DateTime, с помощью какого-то внешнего механизма. Обычно это достигается путем определения другого поля или переменной, которые используются для записи сведений о часовом поясе при хранении типа значения DateTime. Этот подход (хранение значения часового пояса вместе со значением DateTime) является наиболее точным и позволяет различным разработчикам в разные моменты жизненного цикла программы всегда четко понимать значение значения DateTime. Другой распространенный подход заключается в том, чтобы сделать "правилом" в структуре, что все значения времени хранятся в определенном контексте часового пояса. Этот подход не требует дополнительного хранилища для сохранения представления пользователя о контексте часового пояса, но представляет риск того, что значение времени будет неправильно истолковано или неправильно сохранено разработчиком, который не знает о правиле.
- Выполнение вычислений даты и времени для значений, представляющих локальное время компьютера, не всегда дает правильный результат. При выполнении вычислений значений времени в контекстах часовых поясов, которые практикуют летнее время, следует преобразовать значения в универсальные представления времени перед выполнением арифметических вычислений даты. Конкретный список операций и соответствующие контексты часовых поясов см. в таблице в разделе Сортировка методов даты и времени.
- Вычисление на экземпляре значения DateTime не изменяет значение экземпляра , поэтому вызов MyDateTime.ToLocalTime() не изменяет значение экземпляра DateTime. Методы, связанные с классами Date (в Visual Basic®) и DateTime (в среде CLR .NET), возвращают новые экземпляры, представляющие результат вычисления или операции.
- При использовании платформа .NET Framework версий 1.0 и 1.1 НЕ отправляйте значение DateTime, представляющее время UCT черезSystem.XML. Сериализация. Это значения даты, времени и даты и времени. Для веб-служб и других форм сериализации в XML с использованием System.DateTime всегда убедитесь, что значение в значении DateTime представляет текущее локальное время компьютера. Сериализатор правильно декодирует определенное схемой XML значение DateTime, закодированное в формате GMT (значение смещения = 0), но декодирует его в точку представления времени локального компьютера.
- Как правило, если вы имеете дело с абсолютным затраченным временем, например с измерением времени ожидания, выполнением арифметических операций или сравнением различных значений DateTime, следует попробовать использовать универсальное значение времени, если это возможно, чтобы обеспечить наилучшую точность без влияния часового пояса и (или) летнего времени.
- При работе с высокоуровневыми понятиями, такими как планирование, и вы можете с уверенностью предположить, что каждый день имеет 24 часа с точки зрения пользователя, можно противостоять правилу 6, выполняя арифметические действия и т. д. по местному времени.
В этой статье этот простой список правил служит основой для набора рекомендаций по написанию и тестированию приложений, которые обрабатывают даты.
К настоящему моменту некоторые из вас уже просматривают свой код и говорят: "О, черт, это не делает то, что я ожидал сделать", что и является целью этой статьи. Для тех из нас, у кого еще не было прозрения от чтения так далеко, давайте рассмотрим проблемы, связанные с обработкой значений DateTime (с этого момента я просто сокрачу это до "даты") в . Приложения на основе NET.
Стратегии хранения
В соответствии с приведенными выше правилами вычисления значений даты имеют смысл только в том случае, если вы понимаете сведения о часовом поясе, связанные со значением даты, которое вы обрабатываете. Это означает, что независимо от того, храните ли вы значение временно в переменной-члене класса или хотите сохранить значения, собранные в базе данных или файле, вы как программист несете ответственность за применение стратегии, которая позволяет понять связанные сведения о часовом поясе позже.
Рекомендации 1
При написании кода сохраните сведения о часовом поясе, связанные с типом DateTime, в дополнительной переменной.
Альтернативная, но менее надежная стратегия заключается в том, чтобы создать надежное правило, согласно которому сохраненные даты всегда будут преобразованы в определенный часовой пояс, например GMT, перед хранением. Это может показаться разумным, и многие команды могут заставить его работать. Однако отсутствие прямого сигнала о том, что определенный столбец DateTime в таблице базы данных находится в определенном часовом поясе, неизменно приводит к ошибкам в интерпретации в последующих итерациях проекта.
Общая стратегия, увидаемая в неофициальном обзоре различных . Приложения на основе NET — это желание всегда иметь даты, представленные в универсальном (GMT) времени. Я говорю "желание", потому что это не всегда практично. Определенный случай возникает при сериализации класса с переменной-членом DateTime через веб-службу. Причина заключается в том, что тип значения DateTime сопоставляется с типом XSD:DateTime (как и ожидалось), а тип XSD поддерживает представление точек во времени в любом часовом поясе. Мы обсудим случай XML позже. Что еще более интересно, значительная часть этих проектов на самом деле не достигла своей цели и хранила сведения о дате в часовом поясе сервера, не осознавая этого.
В этих случаях интересный факт заключается в том, что тестировщики не видели проблем с преобразованием времени, поэтому никто не заметил, что код, который должен был преобразовать локальные данные даты в время UCT, завершился сбоем. В этих конкретных случаях данные позже сериализуются с помощью XML и преобразовываются должным образом, так как сведения о дате находились в локальном времени компьютера для начала.
Давайте рассмотрим код , который не работает:
Dim d As DateTime
d = DateTime.Parse("Dec 03, 2003 12:00:00 PM") 'date assignment
d.ToUniversalTime()
Приведенная выше программа принимает значение в переменной d и сохраняет его в базе данных, ожидая, что сохраненное значение будет представлять UCT-представление времени. В этом примере показано, что метод Parse отображает результат в локальное время, если в качестве необязательного аргумента для семейства методов Parse не используется какой-то язык и региональные параметры, отличные от языка и региональных параметров по умолчанию.
Показанному выше коду фактически не удается преобразовать значение в переменной DateTime d в универсальное время в третьей строке, так как образец, как написано, нарушает правило 4 (методы класса DateTime не преобразуют базовое значение). Примечание. Этот код был замечен в фактическом приложении, которое было протестировано.
Как это прошло? Приложениям удалось успешно сравнить сохраненные даты, так как во время тестирования все данные поступают с компьютеров, заданных в одном часовом поясе, поэтому правило 1 было выполнено (все сравниваемые и вычисляемые даты локализованы в одном часовом поясе). Ошибка в этом коде представляет собой тип, который трудно обнаружить— оператор, который выполняется, но ничего не делает (подсказка: последний оператор в примере является оператором no-op, как написано).
Рекомендации 2
При тестировании проверка, чтобы увидеть, что хранимые значения представляют собой значение на определенный момент времени в заданном часовом поясе.
Исправить пример кода очень просто:
Dim d As DateTime
d = DateTime.Parse("Dec 03, 2003 12:00:00 PM").ToUniversalTime()
Так как методы вычисления, связанные с типом значения DateTime, никогда не влияют на базовое значение, а возвращают результат вычисления, программа должна помнить о том, чтобы сохранить преобразованное значение (если это необходимо, конечно). Далее мы рассмотрим, как даже это, казалось бы, правильное вычисление может не достичь ожидаемых результатов в определенных обстоятельствах, связанных с переходом на летнее время.
Выполнение вычислений
На первый взгляд, функции вычислений, которые поставляются с классом System.DateTime, действительно полезны. Поддерживается добавление интервалов к значениям времени, выполнение арифметических операций со значениями времени и даже преобразование значений времени .NET в соответствующий тип значения, подходящий для вызовов API Win32®, а также вызовов OLE-автоматизации. Взгляд на методы поддержки, окружающие тип DateTime, вызывает ностальгический взгляд на различные способы развития MS-DOS® и Windows® для работы с временем и метками времени на протяжении многих лет.
Тот факт, что все эти компоненты по-прежнему присутствуют в различных частях операционной системы, связан с требованиями обратной совместимости, которые поддерживает корпорация Майкрософт. Для программиста это означает, что если вы перемещаете данные, представляющие метки времени в файлах, каталогах, или выполняете COM/OLE-взаимодействие со значениями date и DateTime, вам придется освоить работу с преобразованиями между различными поколениями времени, которые присутствуют в Windows.
Больше не обманывайте
Предположим, вы приняли стратегию "Мы храним все во времени UCT", предположительно, чтобы избежать издержек, связанных с сохранением смещения часового пояса (и, возможно, с учетом взгляда пользователя часового пояса, такого как тихоокеанское стандартное время или PST). Выполнение вычислений с использованием UCT-времени имеет ряд преимуществ. Главным из них является тот факт, что при представлении в универсальном времени каждый день имеет фиксированную длину, и нет смещения часовых поясов, с которыми можно было бы справиться.
Если вы были удивлены тем, что день может иметь разную длину, имейте в виду, что в любом часовом поясе, который позволяет переходить на летнее время, два дня в году (обычно) дни имеют разную длину. Таким образом, даже если вы используете значение местного времени, например тихоокеанское стандартное время (PST), если вы попытаетесь добавить интервал времени к определенному значению экземпляра DateTime, вы можете не получить результат, который вы считаете нужным, если добавляемый интервал проходит мимо времени изменения в дату начала или окончания летнего времени.
Рассмотрим пример кода, который не работает в тихоокеанском часовом поясе в США:
Dim d As DateTime
d = DateTime.Parse("Oct 26, 2003 12:00:00 AM") 'date assignment
d = d.AddHours(3.0)
' - displays 10/26/2003 03:00:00 AM – an ERROR!
MsgBox(d.ToString)
Результат, отображаемый в результате этого вычисления, может показаться правильным на первый взгляд; однако, 26 октября 2003 года, через минуту после 1:59 по зимнему времени, изменение летнего времени вступило в силу. Правильный ответ должен был быть 26.10.2003, 02:00:00, поэтому вычисление, основанное на значении местного времени, не дало правильный результат. Но если мы вернемся к Правилу No 3, у нас, кажется, есть противоречие, но мы этого не делаем. Давайте просто назовем это особым случаем для использования методов Add/Subtract в часовых поясах, которые празднуют летнее время.
Рекомендации 3
При написании кода будьте осторожны, если вам нужно выполнить вычисления DateTime (сложение или вычитание) для значений, представляющих часовые пояса, которые практикуют летнее время. Могут возникнуть непредвиденные ошибки вычислений. Вместо этого преобразуйте значение локального времени в универсальное время, выполните вычисление и обратное преобразование для достижения максимальной точности..
Исправить этот неисправный код очень просто:
Dim d As DateTime
d = DateTime.Parse("Oct 26, 2003 12:00:00 AM") 'date assignment
d = d.ToUniversalTime().AddHours(3.0).ToLocalTime()
' - displays 10/26/2003 02:00:00 AM – Correct!
MsgBox(d.ToString)
Самый простой способ надежного добавления интервалов времени — преобразовать значения на основе локального времени в универсальное время, выполнить вычисления, а затем преобразовать значения обратно.
Методы сортировки даты и времени
В этой статье рассматриваются различные методы класса System.DateTime. Некоторые из них дают правильный результат, когда базовый экземпляр представляет местное время, некоторые из них представляют универсальное время, а другие по-прежнему не требуют никакого базового экземпляра. Кроме того, некоторые из них полностью не зависят от часового пояса (например, AddYear, AddMonth). Чтобы упростить общее понимание предположений, лежащих в основе наиболее часто встречающихся методов поддержки DateTime, приведена следующая таблица.
Чтобы прочитать таблицу, рассмотрите начальную (входную) и конечную (возвращаемое значение) точки зрения. Во всех случаях конечное состояние вызова метода возвращается методом . Преобразование в базовый экземпляр данных не выполняется. Также предоставляются предупреждения, описывающие исключения или полезные рекомендации.
Имя метода | Начальная точка просмотра | Конечная точка просмотра | Предупреждения |
---|---|---|---|
ToUniversalTime | Местное время | Формат UTC. | Не вызывайте для экземпляра DateTime, который уже представляет универсальное время |
ToLocalTime | Формат UTC. | Местное время | Не вызывайте для экземпляра DateTime, который уже представляет местное время |
ToFileTime | Местное время | Метод возвращает int64, представляющий время файла Win32 (UCT). | |
FromFileTime | Местное время | Статический метод — экземпляр не требуется. Принимает время UCT INT64 в качестве входных данных | |
ToFileTimeUtc (только версия 1.1) |
Формат UTC. | Метод возвращает INT64, представляющий время файла Win32 (время UCT). | |
FromFileTimeUtc (только версия 1.1) |
Формат UTC. | Метод преобразует время файла INT64 Win32 в экземпляр UCT DateTime | |
Сейчас | Местное время | Статический метод — экземпляр не требуется. Возвращает значение DateTime, представляющее текущее время в локальном времени компьютера. | |
UtcNow | Формат UTC. | Статический метод — экземпляр не требуется | |
IsLeapYear | Местное время | Возвращает логическое значение, указывающее значение true, если часть года экземпляра местного времени является високосным годом. | |
Сегодня | Местное время | Статический метод — экземпляр не требуется. Возвращает значение Типа DateTime, заданное в полночь текущего дня в локальном машинном времени. |
Особый случай XML
У нескольких пользователей, с которыми я недавно говорил, была цель разработки сериализации значений времени в веб-службах таким образом, чтобы XML-код, представляющий dateTime, был бы отформатирован в формате GMT (например, с нулевым смещением). Хотя я слышал различные причины, начиная от желания просто проанализировать поле в виде текстовой строки для отображения в клиенте до сохранения предположений "stored in UCT", которые существуют на сервере, и вызывающих веб-служб, я не был убежден, что есть веские основания для управления форматом маршалинга на проводе до такой степени. Почему? Просто потому, что кодировка XML для типа DateTime идеально подходит для представления момента во времени, а XML-сериализатор, встроенный в платформа .NET Framework, выполняет тонкую работу по управлению проблемами сериализации и десериализации, связанными со значениями времени.
Далее оказывается, что принудительное System.XML. Сериализатор сериализации для кодирования значения даты в GMT по сети невозможно в .NET, по крайней мере сегодня. Как программист, дизайнер или руководитель проекта ваша работа будет следить за тем, чтобы данные, передаваемые в приложении, выполнялись точно с минимальными затратами.
Некоторые из групп, с которыми я говорил в исследованиях, которые вошли в эту статью, приняли стратегию определения специальных классов и написания собственных сериализаторов XML, чтобы они имели полный контроль над тем, как значения DateTime на проводе выглядели в их XML. Хотя я восхищаюсь щипкой, что разработчики имеют, делая скачок в этом смелом начинании, будьте уверены, что нюансы работы с переходом на летнее время и преобразование часовых поясов сами по себе должны сделать хороший менеджер сказать: "Нет в пути", особенно когда механизмы, предоставляемые в платформа .NET Framework делать совершенно точную работу сериализации значений времени уже.
Существует только один трюк, о которых вы должны знать, и как конструктор вы должны понимать это и придерживаться правила (см. Правило 5).
Код, который не работает:
Давайте сначала определим простой класс XML с переменной-членом DateTime. Для полноты этот класс является упрощенным эквивалентом рекомендуемого подхода, показанного далее в этой статье.
<XmlType(TypeName:="timeTestDef", _
Namespace:= "http://tempuri.org/Timetester.xsd")>), _
XmlRoot(), Serializable()> _
Public Class timeTestDef
Private __timeVal As DateTime
<XmlIgnore()> _
Public timeValSpecified As Boolean
<XmlElement(ElementName:="timeVal", IsNullable:=False, _
Form:=XmlSchemaForm.Qualified, DataType:="dateTime", _
Namespace:="http://tempuri.org/Timetester.xsd")> _
Public Property timeVal() As DateTime
Get
timeVal = __timeVal
End Get
Set(ByVal Value As DateTime)
__timeVal = Value
timeValSpecified = True
End Set
End Property
End Class
Теперь давайте используем этот класс для записи XML-кода в файл.
' write out to the file
Dim t As Xml.XmlTextWriter
Dim ser As XmlSerializer
Dim tt As New timeTest ' a class that has a DateTime variable
' set the fields in your class
tt.timeVal = DateTime.Parse("12/12/2003 12:01:02 PM")
tt.timeVal = tt.TimeVal.ToUniversalTime()
' get a serializer for the root type, and serialize this UTC time
ser = New XmlSerializer(GetType(timeTest))
t = New Xml.XmlTextWriter("c:\timetest.xml", System.Text.Encoding.UTF8)
ser.Serialize(t, tt)
t.Close()
t = Nothing
tt = Nothing
При выполнении этого кода XML-код, сериализованный в выходной файл, содержит xml-представление DateTime следующим образом:
<timeVal>2003-12-12T20:01:02.0000000-08:00</timeVal>
Это ошибка: значение, закодированное в XML, отключено на восемь часов! Так как это происходит смещение часового пояса моего текущего компьютера, мы должны быть подозрительными. Если взглянуть на сам XML, дата правильная, и дата 20:01:02 соответствует часовому времени в Лондоне для моего полудня, но часть смещения не является правильной для часов в Лондоне. Если XML-код выглядит как лондонское время, смещение также должно представлять точку зрения Лондона, чего этот код не достигает.
Сериализатор XML всегда предполагает, что сериализуемые значения DateTime представляют локальное машинное время, поэтому он применяет смещение локального часового пояса компьютера в качестве части смещения закодированного ВРЕМЕНИ XML. При десериализации на другом компьютере исходное смещение вычитается из анализируемого значения и добавляется смещение часового пояса текущего компьютера.
Когда мы начинаем с местного времени, результат сериализации (кодирование в XML DateTime с последующим декодированием в локальное машинное время) всегда является правильным, но только в том случае, если начальное значение DateTime представляет местное время при начале сериализации. В случае с этим неработающим примером кода мы уже настроили значение DateTime в переменной-члене timeVal на время UCT, поэтому при сериализации и десериализации результат отключается на количество часов, равное смещения часового пояса исходного компьютера. Это плохо.
Рекомендации 4
При тестировании вычислите значение, которое должно отображаться в XML-строке, сериализуемой с помощью представления локального времени компьютера для проверяемой точки во времени. Если XML-код в потоке сериализации отличается, зайдите в журнал ошибку!
Исправить этот код очень просто. Закомментируйте строку, которая вызывает ToUniversalTime().
Рекомендации 5
При написании кода для сериализации классов, имеющих переменные-члены DateTime, значения должны представлять местное время. Если они не содержат локального времени, настройте их перед любым этапом сериализации, включая передачу или возврат типов, содержащих значения DateTime в веб-службах.
Кодировщики классов затруднительно
Ранее мы рассмотрели довольно неискушенный класс, предоставляющий свойство DateTime. В этом классе мы просто сериализовали то, что храним в DateTime, независимо от того, представляет ли значение локальную или универсальную точку представления времени. Давайте рассмотрим более сложный подход, который предлагает программистам выбор того, какие предположения часового пояса они хотят, при этом всегда правильно сериализуя.
При написании кода класса, который будет иметь переменную-член типа DateTime, программист может сделать переменную-член общедоступной или написать логику свойства для упаковки переменной-члена с помощью операций get/set . Выбор общедоступного типа имеет несколько недостатков, которые в случае типов DateTime могут иметь последствия, которые не находятся под контролем разработчика класса.
Используя то, что мы узнали до сих пор, рассмотрите возможность предоставления двух свойств для каждого типа DateTime.
В следующем примере показан рекомендуемый подход к управлению переменными-членами DateTime:
<XmlType(TypeName:="timeTestDef", _
Namespace:= "http://tempuri.org/Timetester.xsd")>), _
XmlRoot(), Serializable(), _
EditorBrowsable(EditorBrowsableState.Advanced)> _
Public Class timeTestDef
Private __timeVal As DateTime
<XmlIgnore()> _
Public timeValSpecified As Boolean
<XmlElement(ElementName:="timeVal", IsNullable:=False, _
Form:=XmlSchemaForm.Qualified, DataType:="dateTime", _
Namespace:="http://tempuri.org/Timetester.xsd")> _
Public Property timeVal() As DateTime
Get
timeVal = __timeVal.ToLocalTime()
End Get
Set(ByVal Value As DateTime)
__timeVal = Value.ToUniversalTime()
timeValSpecified = True
End Set
End Property
<XmlIgnore()> _
Public Property timeValUTC() As DateTime
Get
timeValUTC = __timeVal
End Get
Set(ByVal Value As DateTime)
__timeVal = Value
timeValSpecified = True
End Set
End Property
End Class
Этот пример является исправленным эквивалентом предыдущего примера сериализации класса. В обоих примерах классов (в этом и более ранних) классы представляют собой реализации, описанные в следующей схеме:
<?xml version="1.0" encoding="utf-8" ?>
<xs:schema id="Timetester"
targetNamespace="http://tempuri.org/Timetester.xsd"
elementFormDefault="qualified"
xmlns="http://tempuri.org/Timetester.xsd"
xmlns:mstns="http://tempuri.org/Timetester.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="timeTest" type="timeTestDef"/>
<xs:complexType name="timeTestDef">
<xs:sequence>
<xs:element name="timeVal" type="xs:dateTime"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
В этой схеме и в любых реализациях класса мы определяем переменную-член, представляющую необязательное значение времени. В нашем рекомендуемом примере мы предоставили два свойства с параметрами получения и заданиями: одно для универсального времени и одно для местного времени. Атрибуты, заключенные в угловые скобки, которые вы видите в коде, сообщают сериализатору XML использовать версию локального времени для сериализации и, как правило, делают реализацию класса результатом в соответствии со схемой. Чтобы класс правильно справились с необязательным отсутствием выражения, если в экземпляре не задано значение, переменная timeValSpecified и связанная логика в методе задания свойств определяет, выражается ли XML-элемент во время сериализации. Это необязательное поведение использует функцию в подсистеме сериализации, которая была разработана для поддержки необязательного XML-содержимого.
Используя этот подход к управлению значениями DateTime в классах .NET, вы получаете лучшие возможности обоих миров. Вы получаете доступ к хранилищу на основе универсального времени, чтобы вычисления были точными, а также правильной сериализации представлений локального времени.
Рекомендация 6
При написании кода сделайте переменные-члены DateTime частными и предоставьте два свойства для управления элементами DateTime в локальное или универсальное время. Смещение хранилища в частном члене в качестве времени UCT путем управления логикой в методах получения и заданиях. Добавьте атрибуты XML-сериализации в объявление свойства местного времени, чтобы убедиться, что значение локального времени является сериализованным (см. пример).
Предостережения по этому подходу
Рекомендуемый подход к управлению DateTime в универсальном времени в частных переменных-членах является обоснованным, а также рекомендуется предоставить двойные свойства, чтобы позволить кодировщикам работать с версиями времени, которые им наиболее удобны. Одной из проблем, которые разработчик использует этот или любой другой подход, который предоставляет программе любое местное время, по-прежнему является проблема 25-часового дня, связанная с переходом на летнее время. Это по-прежнему будет проблемой для программ, использующих среду CLR версий 1.0 и 1.1, поэтому необходимо знать, относится ли ваша программа к этому особому случаю (добавленный или отсутствующий час для представленного времени), и настраивать вручную. Для тех, кто не допускает одночасового периода проблем в год, текущая рекомендация заключается в том, чтобы хранить даты в виде строк или какой-либо другой самостоятельно управляемый подход. (Хороший вариант — длинные целые числа Unix.)
Для среды CLR версии 2.0 (доступной в предстоящем выпуске Visual Studio® с кодом Whidbey) в платформа .NET Framework добавляется осведомленность о том, содержит ли dateTime местное время или универсальное значение времени. На этом этапе рекомендуемый шаблон будет по-прежнему работать, но для программ, взаимодействующих с переменными-членами с помощью свойств UTC, эти ошибки в период отсутствия или дополнительного часа будут устранены. По этой причине настоятельно рекомендуется использовать для написания кода двойные свойства, чтобы ваши программы были полностью перенесены в среду CLR версии 2.0.
Работа с переходом на летнее время
По мере того, как мы готовимся закрыть и оставить тему программирования и тестирования для значений DateTime, остается один особый случай, который необходимо изучить. Этот случай включает в себя неоднозначность, связанную с переходом на летнее время, и повторяющуюся проблему с одним часом в год. Эта проблема в первую очередь затрагивает только приложения, которые собирают значения времени из введенных пользователем данных.
Для тех из вас, в стране-подсчет большинства, этот случай является тривиальным, потому что в большинстве стран летнее время не практикуется. Но для тех из вас, кто находится в пострадавших программах большинство (то есть, все вы, кто имеет приложения, которые должны иметь дело со временем, которое может быть представлено в или источнике в местах, которые DO практикуют летнее время), вы должны знать, что эта проблема существует и учитывать ее.
В районах мира, которые практикуют летнее время, Есть один час каждую осень и весну, где время, казалось бы, идет сеней. В ночь, когда время часы смещается со стандартного времени на летнее время, время прыгает вперед на час. Это происходит весной. Осенью года, в одну ночь, часы местного времени прыгают назад на час.
В эти дни вы можете столкнуться с условиями, когда день составляет 23 или 25 часов. Таким образом, если вы добавляете или вычитаете интервалы времени из значений даты, а диапазон пересекает эту странную точку во времени, когда часы переключаются, код должен выполнить корректировку вручную.
Для логики, которая использует метод DateTime.Parse() для вычисления значения DateTime на основе введенных пользователем данных конкретной даты и времени, необходимо определить, что определенные значения являются недопустимыми (в 23-часовой день), а некоторые значения имеют два значения, так как определенный час повторяется (в 25-часовой день). Для этого необходимо знать даты и искать эти часы. Может быть полезно проанализировать и повторно отобразить интерпретируемые сведения о дате, когда пользователь выходит из полей, используемых для ввода дат. Как правило, не указывайте пользователям летнее время во входных данных.
Мы уже рассмотрели рекомендации по вычислениям временных интервалов. Преобразовав представления местного времени в универсальное время перед выполнением вычислений, вы будете учитывать проблемы с точностью времени. Труднее управлять случаем неоднозначности, связанным с анализом ввода данных пользователем, который происходит в этот волшебный час весной и осенью.
В настоящее время невозможно проанализировать строку, представляющую представление пользователя о времени и точно назначив ей универсальное значение времени. Причина в том, что люди, которые испытывают летнее время, не живут в местах, где часовой пояс гринвичского среднего времени. Таким образом, вполне возможно, что кто-то, живущий на восточном побережье США типа типа "26 октября 2003 01:10:00 AM".
В это конкретное утро, в 2:00, местные часы сбрасываются до 1:00, создавая 25-часовой день. Так как все значения часового времени между 1:00 и 2:00 поступают дважды в это конкретное утро— по крайней мере в большинстве США и Канады. Компьютер действительно не может узнать, какой 1:10 имел в виду: тот, который происходит до переключения, или тот, который происходит через 10 минут после перехода на летнее время.
Аналогичным образом, ваши программы должны иметь дело с проблемой, которая происходит в весеннее время, когда, в определенное утро, нет такого времени, как 2:10 утра. Причина в том, что в 2:00 этого конкретного утра время на местных часах вдруг меняется на 3:00. Все 2:00 никогда не происходит в этот 23-часовой день.
Ваши программы должны иметь дело с этими случаями, возможно, предлагая пользователю при обнаружении неоднозначности. Если вы не собираете строки даты и времени у пользователей и не анализируете их, скорее всего, у вас нет этих проблем. Программы, которые должны определить, попадает ли определенное время в летнее время, могут использовать следующее:
Timezone.CurrentTimeZone.IsDaylightSavingTime(DateTimeInstance)
или
DateTimeInstance.IsDaylightSavingTime
Рекомендации 7
При тестировании, если ваши программы принимают пользовательский ввод с указанием значений даты и времени, обязательно проверьте потерю данных в 23- и 25-часовой день. Кроме того, обязательно протестируйте даты, собранные на компьютере в одном часовом поясе и сохраненные на компьютере в другом часовом поясе.
Форматирование и анализ значений User-Ready
Для программ, которые принимают от пользователей сведения о дате и времени и нуждаются в преобразовании этих данных пользователем в значения DateTime, Платформа обеспечивает поддержку анализа строк, отформатированных определенными способами. Как правило, методы DateTime.Parse и ParseExact полезны для преобразования строк, содержащих даты и время, в значения DateTime. И наоборот, методы ToString, ToLongDateString, ToLongTimeString, ToShortDateString и ToShortTimeString полезны для отрисовки значений DateTime в удобочитаемых строках.
Две main проблемы, влияющие на синтаксический анализ, — это язык и региональные параметры и строка формата. В разделе Часто задаваемые вопросы о дате и времени рассматриваются основные вопросы, связанные с языком и региональными параметрами, поэтому здесь мы сосредоточимся на рекомендациях по форматированию строк, влияющих на синтаксический анализ DateTime.
Рекомендуемые строки формата для преобразования даты и времени в строки:
'yyyy'-'MM'-'dd'T'HH': 'mm': 'ss.fffffff'Z' — для значений UCT
'yyyy'-'MM'-'dd'T'HH': 'mm': 'ss.fffffff'zzz' — для локальных значений
'yyyy'-'MM'-'dd'T'HH': 'mm': 'ss.fffffff' — для абстрактных значений времени
Это строковые значения формата, которые будут переданы методу DateTime.ToString , если требуется получить выходные данные, совместимые со спецификацией типа XML DateTime. Кавычки заверяют, что локальные параметры даты и времени на компьютере не переопределяют параметры форматирования. Если необходимо указать другие макеты, можно передать другие строки формата для довольно гибкой отрисовки даты, но следует соблюдать осторожность, чтобы использовать только нотацию Z для отрисовки строк из UCT-значений, а также использовать нотацию zzz для значений местного времени.
Синтаксический анализ строк и их преобразование в значения DateTime можно выполнить с помощью методов DateTime.Parse и ParseExact. Для большинства из нас parse достаточно, так как ParseExact требует предоставления собственного экземпляра объекта Formatter . Синтаксический анализ является довольно гибким и может точно преобразовать большинство строк, содержащих даты и время.
Наконец, важно всегда вызывать методы Parse и ToString только после того, как для параметра CultureInfo потока задано значение CultureInfo.InvariantCulture.
Будущие рекомендации
Одна вещь, которую вы не можете легко сделать в настоящее время с помощью DateTime.ToString, — это форматирование значения DateTime в произвольный часовой пояс. Эта функция рассматривается для будущих реализаций платформа .NET Framework. Если необходимо определить, что строка "12:00:00 EST" эквивалентна "11:00:00 EDT", вам придется обрабатывать преобразование и сравнение самостоятельно.
Проблемы с методом DateTime.Now()
Существует несколько проблем при работе с методом Now. Для разработчиков Visual Basic, которые читают это, это относится и к функции Visual Basic Now . Разработчики, которые регулярно используют метод Now, знают, что он обычно используется для получения текущего времени. Значение, возвращаемое методом Now, находится в контексте текущего часового пояса компьютера и не может рассматриваться как неизменяемое значение. Распространенной практикой является преобразование времени, которое будет храниться или передаваться между компьютерами, в универсальное (UCT) время.
При переходе на летнее время существует одна практика программирования, которую следует избегать. Рассмотрим следующий код, который может привести к трудно обнаруживаемой ошибке:
Dim timeval As DateTime
timeval = DateTime.Now().ToUniversalTime()
Значение, полученное в результате выполнения этого кода, будет отключено на час при вызове в течение дополнительного часа, который происходит во время перехода на летнее время осенью. (Это относится только к компьютерам, которые находятся в часовых поясах, использующих летнее время.) Так как дополнительный час попадает в то место, где одно и то же значение, например 1:10:00 утра, возникает дважды утром, возвращаемое значение может не совпадать с нужным значением.
Чтобы устранить эту проблему, рекомендуется вызывать Метод DateTime.UtcNow() вместо вызова DateTime.Now, а затем преобразовать в универсальное время.
Dim timeval As DateTime
timeval = DateTime.UtcNow()
Этот код всегда будет иметь правильную 24-часовую перспективу и может быть безопасно преобразован в местное время.
Рекомендации 8
При написании кода и желании хранить текущее время, представленное как универсальное время, избегайте вызова Метода DateTime.Now() и преобразования в универсальное время. Вместо этого вызовите функцию DateTime.UtcNow напрямую.
Предостережение. Если вы собираетесь сериализовать класс, содержащий значение DateTime, убедитесь, что сериализуемое значение не представляет универсальное время. Сериализация XML не будет поддерживать сериализацию UCT до выпуска Visual Studio Whidbey.
Пара малоизвестных статистов
Иногда, когда вы начинаете нырять в часть API, вы находите скрытый драгоценный камень — то, что помогает достичь цели, но если вы не узнаете об этом, вы не раскрываете в повседневных путешествиях. Тип значения DateTime в .NET имеет несколько таких драгоценных камней, которые могут помочь вам добиться более согласованного использования универсального времени.
Во-первых, перечисление DateTimeStyles находится в пространстве имен System.Globalization . Перечисление управляет поведением функций DateTime.Parse() и ParseExact, которые используются для преобразования пользовательских входных данных и других форм входных строковых представлений в значения DateTime.
В следующей таблице выделены некоторые функции, которые включает перечисление DateTimeStyles.
Константы перечисления | Назначение | Предупреждения |
---|---|---|
AdjustToUniversal | При передаче в составе метода Parse или ParseExact этот флаг приводит к тому, что значение возвращается как универсальное время. | Документация неоднозначна, но она работает как с Parse, так и с ParseExact. |
NoCurrentDateDefault | Подавляет предположение о том, что строки, анализируемые без компонентов даты, будут возвращать значение DateTime, которое является временем текущей даты. | Если используется этот параметр, возвращаемое значение DateTime — это время, указанное в григорианской дате 1 января года 1. |
AllowWhiteSpaces AllowTrailingWhite AllowLeadingWhite AllowInnerWhite |
Все эти параметры обеспечивают допустимость добавленных пробелов перед, сзади и в середине анализируемых строк даты. | Нет |
Другие интересные функции поддержки находятся в классе System.Timezone . Обязательно проверка их, если вы хотите определить, влияет ли летнее время на значение DateTime, или если вы хотите программно определить текущее смещение часового пояса для локального компьютера.
Заключение
Класс платформа .NET Framework DateTime предоставляет полнофункциональный интерфейс для написания программ, которые имеют дело со временем. Понимание нюансов работы с классом выходит за рамки того, что вы можете почерпнуть из Intellisense®. Здесь мы рассмотрели рекомендации по написанию кода и тестированию программ, которые имеют дело с датами и временем. Удачного программирования!