В мире есть два типа разработчиков: те, у кого глаза дергаются при упоминании часовых поясов, и те, кто все еще думает, что в сутках всегда 24 часа.

Время и часовые пояса, пожалуй, одна из самых обманчиво простых, но в то же время невероятно сложных вещей, с которыми вам придется иметь дело как разработчику. В качестве примера можно привести этот прекрасный урок истории из документации Apple по их NSCalendar классу.

Григорианский календарь был впервые введен в 1582 году как замена юлианскому календарю. Согласно юлианскому календарю к февралю любого года добавляется високосный день с числом, кратным 4, что приводит к годовому расхождению в 11 минут или 1 день каждые 128 лет. Григорианский календарь пересмотрел правила расчета високосных дней, пропустив високосные дни для любого года с числом, кратным 100, если только этот год не делится на 400, в результате чего годовая разница составляет всего 26 секунд, или 1 день каждые 3323 года.

Для перехода от юлианского календаря к григорианскому календарю из григорианского календаря были исключены 10 дней (5–14 октября).

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

В этом посте мы рассмотрим:

  • Введение в классы Apple Date и Calendar и то, как они работают вместе для моделирования времени.
  • Как управлять датами и часовыми поясами с помощью DateComponents (вкратце о DateFormatter).
  • Как и почему вы можете использовать эти классы.
  • Некоторые проблемы, с которыми вы можете столкнуться.

Примечание. Классы Date, Calendar и DateComponents - это классы Swift, которые соединяются с классами Objective-C NSDate, NSCalendar и NSDateComponents соответственно. В этом посте будут использоваться классы Swift, однако на момент написания документация Objective-C была более подробной, поэтому ссылки на документацию Apple будут относиться к классам Objective-C.

Как Apple моделирует время

Фреймворк Apple использует два класса для работы с концепцией времени: Date и Calendar.

Date

Класс Date назван ужасно - на самом деле он не соответствует «дате», например 8 августа 2014 года. Вместо этого он представляет момент времени, определяемый временным интервалом от абсолютной ссылки. точка. Вы можете думать об этом как о количестве секунд, прошедших с (или до) этой контрольной точки.

Date не зависит от календарных систем или часовых поясов. Чтобы сопоставить момент времени с конкретным «datetime» (комбинация даты и времени, например, 28 января 2017 г., 14:45), нам нужно ввести календарную систему.

"Календарь"

Календарь помещает вехи на временную шкалу, определяя что-то вроде «1982 года» как интервал между двумя конкретными моментами времени. Дата имеет смысл только в контексте календаря.

Apple представляет календари с классом Calendar. Calendar экземпляры создаются с использованием предопределенных identifiers, объявляющих использование календарной системы. Например, Calendar(identifier: .gregorian) даст вам григорианский календарь.

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

Чтобы привязать календарную систему к месту, мы должны указать часовой пояс. Согласно документации Apple:

По умолчанию NSCalendar использует часовой пояс по умолчанию для приложения или процесса при создании объекта календаря. Если часовой пояс по умолчанию не был установлен иным образом, это часовой пояс, установленный в Системных настройках.

Другими словами, Calendar объекты автоматически инициализируются часовым поясом по умолчанию, и этот часовой пояс по умолчанию, скорее всего, является часовым поясом в системных настройках пользователя.

Однако вы можете изменить это значение по умолчанию, явно установив свойство Calendar .timeZone самостоятельно. Apple представляет часовые пояса с помощью класса TimeZone. Подобно Calendar, они инициализируются идентификаторами.

Совет: вы можете использовать Calendar.current, чтобы получить текущий календарь пользователя. Согласно документации Apple:

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

Совет 2: вы можете просмотреть список всех TimeZone идентификаторов, перебирая TimeZone.knownTimeZoneIdentifiers. Я не понимаю, почему этого где-то нет в документации.

Управление датой и временем

Управление Date якобы так же просто, как прибавление или вычитание количества секунд (также известного как TimeInterval) к одному моменту времени, чтобы получить другой момент времени.

Однако для наших целей этой возможности зачастую недостаточно. Например, мы могли бы захотеть перейти с первого дня одного месяца на первый день следующего месяца. В зависимости от месяца и года добавляемое количество секунд будет отличаться. Вот тут-то и пригодится Calendar. Calendar будет знать количество секунд, необходимое для перехода от одной календарной даты и времени к другой.

С Date и Calendar (установлен с TimeZone) у нас есть инструменты, необходимые для управления датами относительно единиц календарного времени (например, месяцев или лет). Мы выполняем эти задачи, переводя не зависящие от календаря Date объекты в зависящие от календаря DateComponents объекты и обратно.

Компоненты даты

В отличие от Date, DateComponents представляет именно то, что можно было ожидать. Он инкапсулирует компоненты даты, такие как год, месяц, день, час, минута и секунда, относительно Calendar.

Мы можем переводить между Date и DateComponents с помощью Calendar. После того, как Date был разбит на компоненты, мы можем манипулировать этими компонентами, прежде чем собирать их в новый Date.

В приведенном выше примере (Код 2) мы начинаем с произвольного Date (строка 2) и объекта григорианского Calendar, установленного на Нью-Йорк TimeZone (строки с 5 по 7). В строке 10 мы создаем DateComponents, интерпретируя Date в контексте Calendar. Если мы проверим эти компоненты, мы увидим, что они описывают datetime 27 сентября 2018 г., 15:34:53. В строке 13 мы берем эти компоненты и снова превращаем их в Date.

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

В приведенном выше примере (Код 3) мы начинаем с двух Date и Calendar (строки со 2 по 4). Затем мы вычисляем разницу между этими двумя датами в календарных днях (то есть количество дней в нашем Calendar между двумя нашими Dates). Проверка differenceInCalendarUnits показывает, что differenceInCalendarUnits.days равно 1157, что означает, что в нашем calendar промежутке между date1 и date2 1157 дней.

Боковое примечание: DateFormatter

В общем, DateComponents позволяет выполнять вычисления и изменять Date объекты. Если вы обнаружите, что у вас есть singleDate, который никогда не меняется, и ваша цель - просто отобразить этот Date по отношению к другому часовому поясу, тогда класс DateFormatter может удовлетворить ваши потребности. DateFormatter преобразует объект Date в представление String (и наоборот) и позволяет вам установить для него как Calendar, так и TimeZone (со свойствами .calendar и .timeZone соответственно). Вы можете использовать DateFormatter, чтобы отображать свой Date как TimeZone по вашему желанию.

Примеры использования

Вот несколько способов использования Date, Calendar и DateComponents.

14:00 сегодня в Калифорнии

Допустим, вы хотите создать Date, соответствующий 14:00 сегодняшнего дня по калифорнийскому времени.

Какой сейчас день / время в Тайбэе?

Допустим, вы хотите знать, какая сейчас дата и время в Тайбэе, Тайвань.

Альтернативно

Обратите внимание, что .dateComponents(in:, from:) игнорирует часовой пояс календаря, для которого вызывается метод, и вместо этого использует переданный часовой пояс. Он возвращает все компоненты.

Какой день / время сегодня 14:00 в Калифорнии по времени Тайбэя?

Просто объедините два приведенных выше примера.

В это же время на следующей неделе

Допустим, вы хотите создать Date, представляющий «это время на следующей неделе» (т.е. через неделю).

Альтернативно

На этот раз в следующий понедельник

Допустим, вы хотите создать Date, представляющий «в этот раз в следующий понедельник» (то есть в то же время, что и сейчас, но в следующий понедельник).

Обратите внимание, что понедельник обозначен цифрой 2. Согласно документации Apple:

Единицы дня недели - это числа от 1 до n, где n - количество дней в неделе. Например, в григорианском календаре n равно 7, а воскресенье представлено 1.

Обратный отсчет до нового года

Допустим, вы хотите знать, сколько месяцев, дней, часов, минут и секунд осталось до того, как мяч упадет на Таймс-сквер (Нью-Йорк).

И более!

Класс Calendar имеет массу других полезных функций. Я рекомендую либо проверить документацию, либо просто ввести . после экземпляра календаря и позволить автозаполнению показать вам все забавные вещи, которые вы можете сделать.

Исправление проблем

Перевод с Date на DateComponents и обратно приводит к другой дате

Если вы попытаетесь создать экземпляр Date из неполного DateComponents объекта, Apple попытается заполнить пробелы. Например, если ваш DateComponents указывает только час и минуту, Apple по умолчанию установит месяц и день на 1 января.

Возможно, когда вы разложили свой Date на DateComponents, вы указали только определенные компоненты для возврата (например, .hour и .minute). Если в этих компонентах недостаточно информации, когда вы пытаетесь превратить эти DateComponents обратно в Date, Apple заполняет недостающую информацию своими значениями по умолчанию, что приводит к другому Date.

При разложении Date на DateComponents попробуйте указать как минимум компоненты .year, .month, .day, .hour, .minute, .second и .nanosecond. Это должно дать вам достаточно информации, чтобы перекомпоновать ваш оригинальный Date.

Закрытие

В итоге:

  • Date представляет собой абсолютный момент времени.
  • Calendar определяет вехи (например, год, месяц и день) на временной шкале. Он закреплен на временной шкале с помощью TimeZone.
  • Используя Calendar, вы можете разложить Date на DateComponents и перекомпоновать DateComponents в Date.
  • DateComponents позволяют вам манипулировать Date объектами, используя календарные единицы (например, месяцы, дни).

Надеюсь, теперь вы можете управлять датой и временем по своему усмотрению! В следующий раз, когда кто-то упомянет часовые пояса, ваши глаза могут немного меньше подергиваться.

использованная литература

Документация Apple