« Все записи

Маленькие чудеса C#/.NET: DateTime с дополнительными преимуществами

Продолжаю переводить "Маленькие чудеса...". После успешной трилогии - сериал. Оригинал статьи.

Еще раз позвольте окунуться в "Маленькие чудеса .NET" - эти маленькие "штучки" в языках платформы .NET и классах BCL, которые делают разработку проще за счет повышения читаемости кода, сопровождаемости или производительности.

Сегодня я собираюсь сосредоточиться на System.DateTime. Эта небольшая структура была в BCL с самого начала, она широко используется для представления экземпляров даты и времени. В ней есть много замечательных свойств, методов и операторов, которые, возможно, не столь широко известны, но могут дать вам множество преимуществ.

Маленькое замечание о конструкторе по умолчанию

Одна из первых особенностей, которую замечают при переходе c Java на C# является то, что конструктор по умолчанию для DateTime возвращает не текущее время, а минимальное.

// Каковы дата и время по умолчанию?
var someTime = new DateTime();

// Дата и время: 01.01.0001 00:00:00
Console.WriteLine("Дата и время: " + someTime);

Почему это происходит? Почему бы не возвращать текущие дату и время? Ключевым моментом здесь является то, что DateTime является структурой. Структура была выбрана из соображений производительности, чтобы  сделать экземпляры DateTime очень легковесными, но одним из многочисленных последствий этого является то, что вы не можете определить поведение структуры в конструкторе по умолчанию. Конструктор по умолчанию структуры всегда инициализирует все поля текущими значениями (ноль для числовых типов, null для ссылочных типов, и т.д.).

Для DateTime это означает, что единственное поле ее экземпляра (dateData) инициализируется нулем. Так что же такое dateData? Это длинное целое, два старших бита которого указывают тип DateTime (Unspecfied - не задан, UTC (всемирное координированное время), Local - местное время), а остальные биты (62) - это количество тактов с полуночи 1 января 0001 года нашей эпохи.

Поскольку текущими значениями всех полей структуры являются их значения по умолчанию, поэтому используемый при создании DateTime конструктор по умолчанию дает вам случай, когда значение dateData равно нулю. Это означает, что количество тактов (младшие 62 бита) равны нулю, и тип (старшие 2 бита) также равен нулю. Это нулевое смещение от минимального значения DateTime и тип Unspecified.

Текущее время, но какое?

Если вы хотите получить текущие местные время и дату, самый простой способ состоит в использовании статического поля DateTime.Now.

// CurrentTime будет содержать текущую дату и время
vаг CurrentTime = DateTime.Now;

Это широко используется для получения местных времени и даты, но если мы работаем в нескольких часовых поясах, мы можем захотеть использовать время UTC, чтобы иметь независимость от размещения. В этом случае мы должны вызвать:

// CurrentTime будет содержать текущую дату и время UTC
vаг CurrentTime = DateTime.UtcNow;

DateTime.UtcNow возвращает текущие дату и время, но вместо использования локального часового пояса, оно представляет их как время UTC. Важно, какое из свойств вы будете использовать, потому что оно установит два старших бита в dateData, которые определяют тип DateTime. Мы можем легко запросить эти два страших бита с помощью свойства Kind:

// Будет выведено: Local
Console.WriteLine(DateTime.Now.Kind);

// Будет выведено: Utc
Console.WriteLien(DateTime.UtcNow.Kind);

Конструкторы с DateTimeKind

Используя Now или UtcNow, мы можем установить страшие биты dateData соответствующим образом, но что, если вы хотите сами определить дату и время (скажем, для представления определенных праздников или ключевых календарных событий)? Многие конструкторы перегружены так, чтобы обеспечить дополнительный параметр DateTimeKind:

// создает 18.11.2010 17:30 - Тип не определен (Unspecified)
var someTime = new DateTime(2010, 11, 18, 17, 30, 0);

// создает 18.11.2010 17:30 - Местное время
var localTime = new DateTime(2010, 11, 18, 17, 30, 0, DateTimeKind.Local);

// создает 18.11.2010 17:30 - Всемирное координированное время (UTC)
var utcTime = new DateTime(2010, 11, 18, 17, 30, 0, DateTimeKind.Utc);

Следует отметить, что конструктор, который принимает только год, месяц и день не имеет параметра DateTimeKind. Если вы хотите определить экземпляр DateTime, содержащий только дату (без значения времени) в контексте UTC или местного времени, то лучше указать это явно:

// unspecifiedTime будет содержать определенную дату в полночь без определенного типа
var unspecifiedTime = new DateTime(2010, 11, 18);

// localTime будет содержать определенную дату в полночь по местному времени
var localTime = new DateTime(2010, 11, 18, 0, 0, 0, DateTimeKind.Local);

// utcTime будет содержать определенную дату на момент полуночи UTC
var utcTime = new DateTime(2010, 11, 18, 0, 0, 0, DateTimeKind.Utc);

Когда вам нужна дата или время, но не то и другое вместе

Часто при разработке нам необходим только компонент даты или компонент времени. Существует несколько полезных свойств, которые могут помочь вам быстро добраться до них.

Нет необходимости вручную конструировать новые экземпляры DateTime:

var now = DateTime.Now;

// не делайте так, используйте статическое свойство Today
var today = new DateTime(now.Year, now.Month, now.Day, 0, 0, 0, DateTimeKind.Local);

// 18.11.2010 17:30:00
var someTime = new DateTime(2010, 11, 18, 17, 30, 0, DateTimeKind.Local);

// не делайте так, используйте свойство экземпляра Date
var justDate = new DateTime(someTime.Year, someTime.Month, someTime.Day, 0, 0, 0, DateTimeKind.Local);

// не делайте так, используйте свойство экземпляра TimeOfDay
var justTime = new TimeSpan(someTime.Hour, someTime.Minute, someTime.Second);

Во-первых, есть статическое свойство Today, которое дает нам DateTime на текущую дату в полночь по местному времени. Или, если у вас есть экземпляр DateTime, и вы хотите получить только компонент даты или времени, вы можете использовать свойство Date или TimeOfDay соответственно.

Свойство Date возвращает новый экземпляр, содержащий первоначальную дату на момент полуночи (ноль часов, минут и секунд), свойство TimeOfDay возвращает экземпляр TimeSpan, содержащий время оригинального экземпляра (вы можете представить его, как смещение от полуночи оригинального экземпляра).

// возвращает текущий день по местному времени (полночь).
var today = DateTime.Today;

// 18.11.2010 17:30
var someTime = new DateTime(2010, 11, 18, 17, 30, 0, DateTimeKind.Local);

// 18.10.2010 00:00:00
var justDate = someTime.Date;

// 17:30:00
var justTime = someTime.TimeOfDay;

Операции с DateTime

Большинство из вас знают, что мы можем сравнивать экземпляры DateTime с помощью традиционных операторов сравнения (<, <=,>,> =, ==,! =) или с помощью метода CompareTo(). Но мы также можем складывать и вычитать экземпляры DateTime.

Имейте в виду, что DateTime является неизменяемым, что означает, что каждый раз при операциях с DateTime создается новый экземпляр. Но так как DateTime - это структура с одним полем, то связанные с этим расходы крайне малы, и вы не должны учитывать этот фактор.

Очевидно, что сложение двух DateTime концептуально не имеет смысла. Как добавить 31.12.2010 03:50:00 к 19.11.2010 7:30:00? Это просто не имеет смысла, но вы можете добавить TimeSpan к дате и времени. Например, добавление 02:00:00 к 19.11.2010 07:30:00 даст 19.11.2010 09:30:00.

Добавление экземпляра TimeSpan к DateTime возможно с помощью метода Add() и оператора +. TimeSpan может быть положительным или отрицательным (добавление отрицательного TimeSpan эквивалентно вычитанию положительного TimeSpan, и наоборот).

Если вам не нужен TimeSpan полностью и вы просто хотите добавить (или вычесть) отдельные компоненты, вы можете использовать вспомогательные методы Add...() для дней, часов, лет и т.д.

// полночь текущей даты
var today = DateTime.Today;

// 5 часов, 30 минут, 10 секунд
var interval = new TimeSpan(5, 30, 10);

// newTime - сегодня в 05:30:10
var newTime = today + interval;

// tomorrowNewTime - завтра в 05:30:10
var tomorrowNewTime = newTime.AddDays(1);

// yesterdayNewTime - вчера в 05:30:10
var yesterdayNewTime = newTime.AddDays(-1);

// seeYouNextDecard - 10 лет от полуночи текущей даты
var seeYouNextDecade = today.AddYears(10);

Console.WriteLine(tomorrowNewTime);
Console.WriteLine(yesterdayNewTime);
Console.WriteLine(seeYouNextDecade);

В отличие от Add(), вы можете осмысленно вычесть одну дату из другой с помощью метода Substract() и получить интервал между ними. Например, 19.11.2010 09:30:00 - 19.11.2010 07:30:00 возвращает TimeSpan, представляющий два часа. Также можно вычитать TimeSpan из DateTime.

Заметим, что методы Add...() является родственными методам Substract...(). В случае методов Add...(), если вам нужно вычитать дни, часы, месяцы и т.д. нужно  просто добавить отрицательное значение.

// праздник Новый год
var newYears2010 = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Local);

// День Благодарения
var thanksgiving2010 = new DateTime(2010, 11, 25, 0, 0, 0, DateTimeKind.Local);

// аналогично thanksgiving2010.Subtract(newYears2010)
var timeFromNewYearsToThanksgiving = thanksgiving2010 - newYears2010;

// 328 дней
Console.WriteLine(timeFromNewYearsToThanksgiving);

Выводы

Cтруктура DateTime имеет много преимуществ. Несмотря на то что она очень легкая и производительная, в то же время её мощь обеспечивается с помощью операций, которые она поддерживает.

Progg it

comments powered by Disqus