Foundation 2 / 2.1. Дата и временные интервалы



Мир слов и оправданий совершенен, и его невозможно подставить под удар. И поэтому мудрец избегает его. Не сходи в мир оправданий за отпущением грехов. Когда ты выходишь из области произносимого, ты не можешь быть ни в чём уверен. Усилия безумца — само общество, но только если они записаны. Мудрец может замещать один закон другим, пусть бессвязно, но утверждая, что у него есть метод. Это правда о речи, простирающаяся на все писания...

Мудрец

В этом уроке


Дата и время

Мы уже знакомы немного с классами Date или TimeInterval. Попробуем разобраться с ними подробнее.

Класс дат отражает отдельную точку времени, независимую от конкретной временной зоны (или часового пояса) и календаря. По своей природе объект даты отражает собой интервал, прошедший от фиксированной даты. Date является структурой, корреспондирующим же классом выступаем NSDate.

Введём две важных даты:

Давайте поиграемся с конструкторами это системы:

let date1 = Date()

Как мы помним, это конструктор создаёт объект текущей даты.

Создадим объекты от обеих заданных дат:

let date2 = Date(timeIntervalSinceReferenceDate: 10000) // "1 Jan 2001 at 07:46"
let date3 = Date(timeIntervalSince1970: 10000) //"1 Jan 1970 at 07:46"

Также можно создать даты, считая от текущей даты и от определённой даты:

let date4 = Date(timeIntervalSinceNow: 1000000)
var date5 = Date(timeInterval: 100000, since: date3) //"2 Jan 1970 at 11:33"

Так же можно получить даты, соответствующие далёкому прошлому и будущему:

Date.distantFuture //"1 Jan 4001 at 05:00"
Date.distantPast //"30 Dec 1 at 04:02"

Как видите, этого класса хватит до 4001 года и если Вы отправитесь в прошлое, то до начала нашей эры.

Даты можно сравнивать:

date3 > date2 //false
date3 == date2 //false
date3 != date2 //true
date3 < date2 //true
date3 >= date2 //false
date3 <= date2 //true

Временные интервалы

Временные интервалы, как мы помним, выражаются в секундах

Вы можете получить интервал времени от изначальной даты, а также между ней и 1970-ым:

Date.timeIntervalSinceReferenceDate
Date.timeIntervalBetween1970AndReferenceDate //978307200

Также можно получить разницу между текущей датой и:

date2.timeIntervalSinceReferenceDate
date2.timeIntervalSince1970
date2.timeIntervalSinceNow

Вы можете складывать даты с временными интервалами, также доступны операторы составного присваивания:

date5 + 10000 //"2 Jan 1970 at 14:20"
date5 - 10000 //"2 Jan 1970 at 08:46"
date5 += 10000 //"2 Jan 1970 at 14:20"
date5 -= 10000 //"2 Jan 1970 at 11:33"

Даты можно сравнивать и с помощью функции compare(_:), которая вернёт объект ComparisonResult. Сам по себе он не равен истине или лжи в Swift, однако с помощью rawValue можно узнать результаты сравнения:

Как альтернативу можно использовать методы:

  1. Мутирующий addTimeInterval(_:)
  2. Немутирующий addingTimeInterval(_:)
date5.compare(date4).rawValue //-1

Вы можете создавать интервалы дат, которые отражают интервалы между датами. Вы можете использовать как обычные операторы ... (и тому подобное), а также специальную структуру DateInterval и корреспондирующий класс NSDateInterval. Их можно сравнивать, находить пересечения и тому подобное.


Локализации

Заметьте, что вывод с правой стороны отличается от использования свойства description или функции print;

date5.description //"1970-01-02 06:33:20 +0000"
print(date5) //"1970-01-02 06:33:20 +0000"

Каждая система обладает своей локализацией. Это важно для приложений, которые хотят выйти более, чем на одном языке или в одном регионе.

Для работы с локализацией используется тип Locale (ссылочный тип - NSLocale).

Чтобы получить текущую локализацию пользователя следует воспользоваться статическими свойствами типа current и autoupdatingCurrent:

import Foundation

Locale.current //en_US
let locale = Locale.autoupdatingCurrent //en_US

Их разница состоит в том, что вторая отслеживает изменение текущей локализации.

Если изменить объект, отслеживающий текущую локализацию, то он перестанет это делать.

Чтобы создать локализацию, нужно передать в неё идентификатор. Для российского региона, это ru_RU:

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

let ruLocale = Locale(identifier: "ru_RU") //ru_RU

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

//Все доступные идентификаторы
print(Locale.availableIdentifiers.count) //790

//Все доступные коды регионов
print(Locale.isoRegionCodes.count) //256

//Коды языков
print(Locale.isoLanguageCodes.count) ///594

//Коды валют
print(Locale.isoCurrencyCodes.count) //299

//Коды распространённых валют
print(Locale.commonISOCurrencyCodes.count) //161

Чтобы получить список языков, предпочитаемых пользователем, можно сделать так:

print(Locale.preferredLanguages) //["en"]

У каждого языка выделяют направление письменности и направление строк:

print(Locale.characterDirection(forLanguage: "ru"))
print(Locale.lineDirection(forLanguage: "ru"))
let arrayOfDirections: [Locale.LanguageDirection] = [
    .leftToRight, .bottomToTop, .rightToLeft, .topToBottom
]

При этом возможны все направления. Для английского и русского языков такие символы пишутся слева направо, а строчки сверху вниз.


Свойства локализации для дат

Стандартные свойства локализации:

//Идентификатор
print(ruLocale.identifier) //ru_RU

//Код региона
print(ruLocale.regionCode) //Optional(RU)

//Код яызка
print(ruLocale.languageCode) //Optional(ru)

При изучении дат мы узнали, что их объекты не зависят от календаря, а представляют собой точку во времени. Выделяют множество календарей, однако Европа и Америка чаще всего использует Григорианский календарь:

print(ruLocale.calendar) //gregorian

Несколько дополнительных методов

Вы можете получить канонические идентификаторы (то есть вида ru_RU) из строки:

print(Locale.canonicalIdentifier(from: "ru_ru")) //ru_RU
print(Locale.canonicalLanguageIdentifier(from: "ru_ru")) //ru_RU

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

Вы можете получить словарь с компонентами идентификатора

let components = Locale.components(fromIdentifier: "ru_RU")
    for (key, value) in components {
    print("\(key): \(value)")
}

//kCFLocaleLanguageCodeKey: ru
//kCFLocaleCountryCodeKey: RU

Обратите внимание на формат ключей - такой тип данных используется в чистом C и Objective-C - первая буква k обозначает константу, а далее идёт блок наподобие перечисления CFLocale, а далее название самой константы. Это связано с тем, что перечисления в C не умели принимать объекты, отличные от чисел, а потому требовалось вводить данные константы. Большинство высокоуровневых фреймворков в Swift умеют импортировать их в качестве кейсов перечислений, однако многие вещи остались в стиле Objective-C.

Из компонентов можно собрать идентификатор назад:

let idFromComponents = Locale.identifier(fromComponents: components) //ru_RU

В Windows используется другая система локализаций, а потому может быть полезным взаимно конвертировать их:

// AppleOS -> Windows
if let windowsLocaleCode = Locale.windowsLocaleCode(fromIdentifier: idFromComponents)  {
    print(windowsLocaleCode) //1049
    //Windows -> AppleOS
    print(Locale.identifier(fromWindowsLocaleCode: windowsLocaleCode)) //ru_RU
}

Другие штучки

Можно получить значения основных кавычек (и главной альтернтивы им) для текущей локализации:

print(ruLocale.quotationBeginDelimiter) //"Optional("«")\n"
print(ruLocale.quotationEndDelimiter) //"Optional("»")\n"
print(ruLocale.alternateQuotationBeginDelimiter) //"Optional("„")\n"
print(ruLocale.alternateQuotationEndDelimiter) //"Optional("“")\n"

В странах так же отличаются символы для десятичного разделителя:

print(ruLocale.decimalSeparator) // ","

В России это запятая, тогда как в большинстве стран - точка.

Так же выделяют символ разделителя для разрядов в числах:

print(ruLocale.groupingSeparator) // " "

В России это пробел.

Помимо этого можно получить символ и название валюты:

print(ruLocale.currencySymbol) //"Optional("₽")\n"
let ruCurrencyCode = ruLocale.currencyCode
print(ruCurrencyCode) //Optional("RUB")
print(ruLocale.localizedString(forCurrencyCode: ruCurrencyCode!)) //"Optional("Российский рубль")\n"

Последним методом для некоторых типов данных Вы можете получить их локализованное описание.

Можно узнать, использует ли локализация метрическую систему измерений:

print(ruLocale.usesMetricSystem) //true

Можно получить набор базовых символов локализации для сравнения с:

print(ruLocale.exemplarCharacterSet)

Теперь посмотрим коды письменности:

let ruVariantCode = ruLocale.variantCode
print(ruVariantCode) //nil
print(ruLocale.localizedString(forLanguageCode: ruVariantCode ?? "")) //nil

Код вариантов позволяет узнать подвид языка. Для русского в международной системе его нет. Пример языка с вариантами - китайский, есть как китайский традиционный, так и упрощенный.

Также можно узнать код письменности:

let ruScriptCode = ruLocale.scriptCode
print(ruScriptCode) //nil
print(ruLocale.localizedString(forScriptCode: ruScriptCode ?? "")) //nil

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

let ruCollatorIdentifier = ruLocale.collatorIdentifier
print(ruCollatorIdentifier) //Optional("ru_RU")
print(ruLocale.localizedString(forCollatorIdentifier: ruCollatorIdentifier!)) //

let ruCollationIdentifier = ruLocale.collationIdentifier
print(ruCollationIdentifier) //nil
print(ruLocale.localizedString(forCollationIdentifier: ruCollationIdentifier ?? ""))

Узнать о разных тонкостях локализации можно на проекте ICU