Spec-Zone.ru › Julia 0.6

Дата и DateTime

Модуль Dates предоставляет два типа для работы с датами: Date и DateTime, представляющие точность дня и миллисекунд соответственно; оба являются подтипами абстрактного TimeType. Мотивация для отдельных типов проста: некоторые операции намного проще, как с точки зрения кода, так и с точки зрения умственного рассуждения, когда не нужно иметь дело со сложностями большей точности. Например, поскольку тип Date разрешается только до точности одной даты (т.е. без часов, минут или секунд), обычные соображения по часовым поясам, переходу на летнее/зимнее время и високосным секундам не нужны и избегаются.

Оба Date и DateTime в основном являются неизменяемыми обёртками над Int64. Единственное поле instant любого типа фактически является типом UTInstant{P}, который представляет непрерывно возрастающую временную шкалу машины, основанную на секунде UT [1]. Тип DateTime не учитывает часовые пояса (наивный, по терминологии Python), аналогично LocalDateTime в Java 8. Дополнительные функции часовых поясов могут быть добавлены с помощью пакета TimeZones.jl, который компилирует базу данных часовых поясов IANA. И Date, и DateTime основаны на стандарте ISO 8601, который следует пролептическому григорианскому календарю. Следует отметить, что стандарт ISO 8601 является специфичным в отношении дат до н.э./до нашей эры. В общем, последний день эры до н.э./до нашей эры, 1-12-31 до н.э./до н.э., следовал за 1-1-1 н.э./н.э., поэтому нулевого года нет. Однако, стандарт ISO указывает, что 1 до н.э./до нашей эры — это нулевой год, поэтому 0000-12-31 — это день перед 0001-01-01, и год -0001 (да, минус один для года) — это 2 до н.э./до н.э., год -0002 — это 3 до н.э./до н.э. и т. д.

[1]

Понятие секунды UT на самом деле довольно фундаментально. В основном существуют два разных понятия времени, которые обычно принимаются, одно основано на физическом вращении Земли (один полный оборот = 1 день), другое основано на секунде СИ (фиксированное, постоянное значение). Это радикально отличается! Подумайте об этом, "секунда UT", как определено относительно вращения Земли, может иметь разную абсолютную длину в зависимости от дня! В любом случае тот факт, что Date и DateTime основаны на секундах UT, представляет собой упрощающее, но честное предположение, чтобы избежать таких вещей, как високосные секунды и всей их сложности. Эта основа времени формально называется UT или UT1. Основание типов на секунде UT в основном означает, что каждая минута имеет 60 секунд, а каждый день имеет 24 часа, что приводит к более естественным вычислениям при работе с календарными датами.

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

Date и DateTime типы могут быть созданы с использованием целых чисел или Period типов, с помощью парсинга или через корректировщики (подробнее об этом позже):

julia> DateTime(2013)
2013-01-01T00:00:00

julia> DateTime(2013,7)
2013-07-01T00:00:00

julia> DateTime(2013,7,1)
2013-07-01T00:00:00

julia> DateTime(2013,7,1,12)
2013-07-01T12:00:00

julia> DateTime(2013,7,1,12,30)
2013-07-01T12:30:00

julia> DateTime(2013,7,1,12,30,59)
2013-07-01T12:30:59

julia> DateTime(2013,7,1,12,30,59,1)
2013-07-01T12:30:59.001

julia> Date(2013)
2013-01-01

julia> Date(2013,7)
2013-07-01

julia> Date(2013,7,1)
2013-07-01

julia> Date(Dates.Year(2013),Dates.Month(7),Dates.Day(1))
2013-07-01

julia> Date(Dates.Month(7),Dates.Year(2013))
2013-07-01

Date или DateTime парсинг выполняется с помощью строк формата. Строки формата работают, определяя разграниченные или фиксированные "слоты", содержащие период для парсинга, и передавая текст для парсинга и строку формата конструктору Date или DateTime в форме Date("2015-01-01","y-m-d") или DateTime("20150101","yyyymmdd").

Разграниченные слоты отмечаются указанием разделителя, которого парсер должен ожидать между двумя последующими периодами; так "y-m-d" сообщает парсеру, что между первым и вторым слотами в строке даты, такой как "2014-07-16", он должен найти символ -.

Символы y, m, и d сообщают парсеру, какие периоды следует парсить в каждом слоте.

Фиксированные слоты задаются повторением символа периода количество раз, соответствующее ширине без разделителя между символами. Так "yyyymmdd" соответствует строке даты, такой как "20140716". Парсер различает фиксированный слот по отсутствию разделителя, отмечая переход "yyyymm" от одного символа периода к следующему.

Поддержка парсинга месяца в текстовой форме также поддерживается символами u и U, для сокращенных и полных названий месяцев соответственно. По умолчанию поддерживаются только английские названия месяцев, поэтому u соответствует "Янв", "Фев", "Мар" и т.д. А U соответствует "Январь", "Февраль", "Март" и т.д. Аналогично другим функциям сопоставления имя=>значение dayname() и monthname(), пользовательские языковые локали могут быть загружены путем передачи отображения locale=>Dict{String,Int} в словари MONTHTOVALUEABBR и MONTHTOVALUE для сокращенных и полных названий месяцев соответственно.

Примечание о производительности парсинга: использование функции Date(date_string,format_string) приемлемо, если она вызывается несколько раз. Однако, если нужно обработать много строк дат с аналогичным форматом, намного эффективнее сначала создать Dates.DateFormat и передать его вместо исходной строки формата.

julia> df = DateFormat("y-m-d");

julia> dt = Date("2015-01-01",df)
2015-01-01

julia> dt2 = Date("2015-01-02",df)
2015-01-02

Вы также можете использовать макрос строк dateformat"". Этот макрос создаёт объект DateFormat один раз при расширении макроса и использует тот же объект DateFormat даже если фрагмент кода выполняется несколько раз.

julia> for i = 1:10^5
           Date("2015-01-01", dateformat"y-m-d")
       end

Полный набор тестов и примеров парсинга и форматирования доступен в tests/dates/io.jl.

Продолжительности/Сравнения

Определение длительности между двумя Date или DateTime проста, учитывая их внутреннее представление как UTInstant{Day} и UTInstant{Millisecond} соответственно. Разница между Date возвращается в количестве Day, а DateTime — в количестве Millisecond.

Аналогично, сравнение TimeType сводится к простому сравнению базовых мгновений машины (что в свою очередь сравнивает внутренние значения Int64).

julia> dt = Date(2012,2,29)
2012-02-29

julia> dt2 = Date(2000,2,1)
2000-02-01

julia> dump(dt)
Date
  instant: Base.Dates.UTInstant{Base.Dates.Day}
    periods: Base.Dates.Day
      value: Int64 734562

julia> dump(dt2)
Date
  instant: Base.Dates.UTInstant{Base.Dates.Day}
    periods: Base.Dates.Day
      value: Int64 730151

julia> dt > dt2
true

julia> dt != dt2
true

julia> dt + dt2
ERROR: MethodError: no method matching +(::Date, ::Date)
[...]

julia> dt * dt2
ERROR: MethodError: no method matching *(::Date, ::Date)
[...]

julia> dt / dt2
ERROR: MethodError: no method matching /(::Date, ::Date)
[...]

julia> dt - dt2
4411 days

julia> dt2 - dt
-4411 days

julia> dt = DateTime(2012,2,29)
2012-02-29T00:00:00

julia> dt2 = DateTime(2000,2,1)
2000-02-01T00:00:00

julia> dt - dt2
381110400000 milliseconds

Функции-акцессоры

Поскольку типы Date и DateTime хранятся как одиночные значения Int64, части или поля даты могут быть извлечены с помощью функций-акцессоров. Функции-акцессоры с маленькими буквами возвращают поле в виде целого числа:

julia> t = Date(2014, 1, 31)
2014-01-31

julia> Dates.year(t)
2014

julia> Dates.month(t)
1

julia> Dates.week(t)
5

julia> Dates.day(t)
31

В то время как функции с заглавными буквами возвращают то же значение в соответствующем типе Period:

julia> Dates.Year(t)
2014 years

julia> Dates.Day(t)
31 days

Предоставляются составные методы, так как они обеспечивают определённую эффективность, если в один и тот же момент нужны несколько полей:

julia> Dates.yearmonth(t)
(2014, 1)

julia> Dates.monthday(t)
(1, 31)

julia> Dates.yearmonthday(t)
(2014, 1, 31)

Также можно получить доступ к базовому значению UTInstant или целому числу:

julia> dump(t)
Date
  instant: Base.Dates.UTInstant{Base.Dates.Day}
    periods: Base.Dates.Day
      value: Int64 735264

julia> t.instant
Base.Dates.UTInstant{Base.Dates.Day}(735264 days)

julia> Dates.value(t)
735264

Функции запроса

Функции запроса предоставляют календарную информацию о TimeType. В их число входит информация о дне недели:

julia> t = Date(2014, 1, 31)
2014-01-31

julia> Dates.dayofweek(t)
5

julia> Dates.dayname(t)
"Friday"

julia> Dates.dayofweekofmonth(t) # 5th Friday of January
5

Месяц года:

julia> Dates.monthname(t)
"January"

julia> Dates.daysinmonth(t)
31

А также информация о году и квартале TimeType:

julia> Dates.isleapyear(t)
false

julia> Dates.dayofyear(t)
31

julia> Dates.quarterofyear(t)
1

julia> Dates.dayofquarter(t)
31

Методы dayname() и monthname() также могут принимать необязательный ключевой параметр locale, который может быть использован для возвращения названия дня или месяца года для других языков/локалей. Также существуют версии этих функций, возвращающие сокращенные названия, а именно dayabbr() и monthabbr(). Сначала отображение загружается в переменную LOCALES.

julia> french_months = ["janvier", "février", "mars", "avril", "mai", "juin",
                        "juillet", "août", "septembre", "octobre", "novembre", "décembre"];

julia> french_monts_abbrev = ["janv","févr","mars","avril","mai","juin",
                              "juil","août","sept","oct","nov","déc"];

julia> french_days = ["lundi","mardi","mercredi","jeudi","vendredi","samedi","dimanche"];

julia> Dates.LOCALES["french"] = Dates.DateLocale(french_months, french_monts_abbrev, french_days, [""]);

Затем упомянутые выше функции могут быть использованы для выполнения запросов:

julia> Dates.dayname(t;locale="french")
"vendredi"

julia> Dates.monthname(t;locale="french")
"janvier"

julia> Dates.monthabbr(t;locale="french")
"janv"

Поскольку сокращенные названия дней не загружены, попытка использовать функцию dayabbr() вызовет ошибку.

julia> Dates.dayabbr(t;locale="french")
ERROR: BoundsError: attempt to access 1-element Array{String,1} at index [5]
Stacktrace:
 [1] #dayabbr#6(::String, ::Function, ::Int64) at ./dates/query.jl:114
 [2] (::Base.Dates.#kw##dayabbr)(::Array{Any,1}, ::Base.Dates.#dayabbr, ::Int64) at ./<missing>:0 (repeats 2 times)

Арифметика TimeType-Period

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

END_OF_DOCUMENT_MARKER

Подход модуля Dates пытается следовать простому принципу минимальных изменений при выполнении арифметики дат Period. Этот подход также часто известен как календарная арифметика или то, что вы, вероятно, предположили бы, если бы вас попросили выполнить такой же расчет в разговоре. Зачем все эти сложности? Давайте рассмотрим классический пример: прибавим 1 месяц к 31 января 2014 года. Какой ответ? Javascript скажет 3 марта (предполагается 31 день). PHP скажет 2 марта (предполагается 30 дней). Фактически, правильного ответа нет. В модуле Dates он возвращает 28 февраля. Как он это определяет? Мне нравится представлять это как классическую азартную игру 7-7-7 в казино.

Теперь представьте, что вместо 7-7-7 слоты содержат Год-Месяц-День или, в нашем примере, 2014-01-31. При запросе добавления 1 месяца к этой дате, слот месяца увеличивается, и теперь у нас есть 2014-02-31. Затем проверяется, больше ли число дня, чем последнее допустимое число дня в новом месяце; если это так (как в случае выше), число дня корректируется до последнего допустимого числа (28). Каковы последствия этого подхода? Попробуйте добавить еще один месяц к нашей дате, 2014-02-28 + Month(1) == 2014-03-28. Что? Вы ожидали последнее число марта? Нет, извините, помните слоты 7-7-7. Изменяется как можно меньше слотов, поэтому мы сначала увеличиваем слот месяца на 1, 2014-03-28, и готово, так как это допустимая дата. С другой стороны, если мы добавим 2 месяца к нашей исходной дате 2014-01-31, то получим 2014-03-31, как ожидалось. Другим следствием этого подхода является потеря ассоциативности, когда принудительно задается определенный порядок (то есть, добавление в разных порядках приводит к разным результатам). Например:

julia> (Date(2014,1,29)+Dates.Day(1)) + Dates.Month(1)
2014-02-28

julia> (Date(2014,1,29)+Dates.Month(1)) + Dates.Day(1)
2014-03-01

Что происходит в этом примере? В первой строке мы добавляем 1 день к 29 января, что приводит к 2014-01-30; затем мы добавляем 1 месяц, и получаем 2014-02-30, которое затем корректируется до 2014-02-28. Во втором примере мы сначала добавляем 1 месяц, получаем 2014-02-29, которое корректируется до 2014-02-28, а затем добавляем 1 день, что приводит к 2014-03-01. Один из принципов проектирования, который помогает в этом случае, заключается в том, что в присутствии нескольких периодов операции будут упорядочены по типам периодов, а не по их значению или порядковому номеру; это означает, что Year всегда добавляется первым, затем Month, затем Week и так далее. Таким образом, следующее действительно приводит к ассоциативности и просто работает:

julia> Date(2014,1,29) + Dates.Day(1) + Dates.Month(1)
2014-03-01

julia> Date(2014,1,29) + Dates.Month(1) + Dates.Day(1)
2014-03-01

Сложно? Возможно. Что должен делать простой пользователь Dates? В конечном итоге нужно понимать, что явное принуждение к определенной ассоциативности при работе с месяцами может привести к некоторым неожиданным результатам, но в противном случае все должно работать как ожидается. К счастью, это практически исчерпывает все странные случаи в арифметике дат-периодов при работе со временем в UT (избегая «радостей» работы с переходом на летнее время, високосных секунд и т. д.).

В качестве бонуса все объекты арифметики периодов работают напрямую с диапазонами:

julia> dr = Date(2014,1,29):Date(2014,2,3)
2014-01-29:1 day:2014-02-03

julia> collect(dr)
6-element Array{Date,1}:
 2014-01-29
 2014-01-30
 2014-01-31
 2014-02-01
 2014-02-02
 2014-02-03

julia> dr = Date(2014,1,29):Dates.Month(1):Date(2014,07,29)
2014-01-29:1 month:2014-07-29

julia> collect(dr)
7-element Array{Date,1}:
 2014-01-29
 2014-02-28
 2014-03-29
 2014-04-29
 2014-05-29
 2014-06-29
 2014-07-29

Функции корректировки

Насколько удобной ни была арифметика дат-периодов, часто типы вычислений, необходимых для дат, имеют календарный или временной характер, а не фиксированное число периодов. Праздники — прекрасный пример; большинство из них подчиняются правилам, таким как «День памяти = Последний понедельник мая» или «День благодарения = Четвертый четверг ноября». Эти виды временных выражений связаны с правилами, относящимися к календарю, такими как первое или последнее число месяца, следующий вторник или первый и третий среды и т. д.

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

julia> Dates.firstdayofweek(Date(2014,7,16)) # Adjusts the input to the Monday of the input's week
2014-07-14

julia> Dates.lastdayofmonth(Date(2014,7,16)) # Adjusts to the last day of the input's month
2014-07-31

julia> Dates.lastdayofquarter(Date(2014,7,16)) # Adjusts to the last day of the input's quarter
2014-09-30

Два последующих метода высшего порядка, tonext() и toprev(), обобщают работу с временными выражениями, принимая DateFunction в качестве первого аргумента вместе с начальным TimeType. DateFunction — это просто функция, обычно анонимная, которая принимает один TimeType в качестве входных данных и возвращает Bool, true указывающий на выполнение критерия корректировки. Например:

julia> istuesday = x->Dates.dayofweek(x) == Dates.Tuesday # Returns true if the day of the week of x is Tuesday
(::#1) (generic function with 1 method)

julia> Dates.tonext(istuesday, Date(2014,7,13)) # 2014-07-13 is a Sunday
2014-07-15

julia> Dates.tonext(Date(2014,7,13), Dates.Tuesday) # Convenience method provided for day of the week adjustments
2014-07-15

Это полезно при использовании синтаксиса блока do для более сложных временных выражений:

julia> Dates.tonext(Date(2014,7,13)) do x
           # Return true on the 4th Thursday of November (Thanksgiving)
           Dates.dayofweek(x) == Dates.Thursday &&
           Dates.dayofweekofmonth(x) == 4 &&
           Dates.month(x) == Dates.November
       end
2014-11-27

Метод Base.filter() может использоваться для получения всех допустимых дат/моментов в заданном диапазоне:

# Pittsburgh street cleaning; Every 2nd Tuesday from April to November
# Date range from January 1st, 2014 to January 1st, 2015
julia> dr = Dates.Date(2014):Dates.Date(2015);

julia> filter(dr) do x
           Dates.dayofweek(x) == Dates.Tue &&
           Dates.April <= Dates.month(x) <= Dates.Nov &&
           Dates.dayofweekofmonth(x) == 2
       end
8-element Array{Date,1}:
 2014-04-08
 2014-05-13
 2014-06-10
 2014-07-08
 2014-08-12
 2014-09-09
 2014-10-14
 2014-11-11

Дополнительные примеры и тесты доступны в test/dates/adjusters.jl.

Типы периодов

Периоды — это человеческое представление о дискретных, иногда нерегулярных временных интервалах. Рассмотрим 1 месяц; он может представлять собой, в днях, значение 28, 29, 30 или 31 в зависимости от года и контекста месяца. Или год может представлять 365 или 366 дней в случае високосного года. Типы Period — это простые обертки Int64, и они создаются путем обертывания любого типа, преобразуемого в Int64, т. е. Year(1) или Month(3.0). Арифметические операции между Period одного типа ведут себя как целые числа, и доступна ограниченная арифметика Period-Real.

julia> y1 = Dates.Year(1)
1 year

julia> y2 = Dates.Year(2)
2 years

julia> y3 = Dates.Year(10)
10 years

julia> y1 + y2
3 years

julia> div(y3,y2)
5

julia> y3 - y2
8 years

julia> y3 % y2
0 years

julia> div(y3,3) # mirrors integer division
3 years

Округление

Date и DateTime значения могут быть округлены до заданного разрешения (например, 1 месяц или 15 минут) с помощью floor(), ceil() или round():

julia> floor(Date(1985, 8, 16), Dates.Month)
1985-08-01

julia> ceil(DateTime(2013, 2, 13, 0, 31, 20), Dates.Minute(15))
2013-02-13T00:45:00

julia> round(DateTime(2016, 8, 6, 20, 15), Dates.Day)
2016-08-07T00:00:00

В отличие от числового метода round(), который по умолчанию разрывает связи в сторону четного числа, метод TimeTyperound() использует режим округления RoundNearestTiesUp. (Трудно предположить, что означало бы разрыв связей в сторону ближайшего «четного» TimeType). Более подробные сведения об имеющихся режимах RoundingMode можно найти в справочнике API.

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

Округление эпохи

Во многих случаях разрешение, указанное для округления (например, Dates.Second(30) ), делит без остатка следующий наибольший период (в данном случае, Dates.Minute(1)). Но поведение округления в тех случаях, когда это не так, может привести к путанице. Какой ожидаемый результат округления DateTime до ближайших 10 часов?

julia> round(DateTime(2016, 7, 17, 11, 55), Dates.Hour(10))
2016-07-17T12:00:00

Это может показаться запутанным, учитывая, что час (12) не делится на 10 без остатка. Причина, по которой было выбрано 2016-07-17T12:00:00, заключается в том, что это 17 676 660 часов после 0000-01-01T00:00:00, а 17 676 660 делится на 10 без остатка.

Поскольку значения Julia Date и DateTime представлены в соответствии со стандартом ISO 8601, 0000-01-01T00:00:00 был выбран в качестве базы (или «эпохи округления»), от которой начинается подсчёт дней (и миллисекунд), используемых в расчётах округления. (Обратите внимание, что это немного отличается от внутреннего представления Julia Date с использованием обозначения Rata Die; но поскольку стандарт ISO 8601 наиболее виден конечному пользователю, 0000-01-01T00:00:00 был выбран в качестве эпохи округления, а не 0000-12-31T00:00:00 используемого во внутренней работе, чтобы свести к минимуму путаницу.)

Единственным исключением из использования 0000-01-01T00:00:00 в качестве эпохи округления является округление до недель. Округление до ближайшей недели всегда возвращает понедельник (первый день недели, как указано в стандарте ISO 8601). По этой причине мы используем 0000-01-03T00:00:00 (первый день первой недели года 0000, как определено в стандарте ISO 8601) в качестве базы при округления до определенного числа недель.

Вот ещё один случай, в котором ожидаемое поведение необязательно очевидно: что происходит, когда мы округляем до ближайшего P(2), где P — это тип Period? В некоторых случаях (в частности, когда P <: Dates.TimePeriod ) ответ ясен:

julia> round(DateTime(2016, 7, 17, 8, 55, 30), Dates.Hour(2))
2016-07-17T08:00:00

julia> round(DateTime(2016, 7, 17, 8, 55, 30), Dates.Minute(2))
2016-07-17T08:56:00

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

julia> round(DateTime(2016, 7, 17, 8, 55, 30), Dates.Month(2))
2016-07-01T00:00:00

Почему округляется до первого числа июля, даже если это месяц 7 (нечётное число)? Ключевым моментом является то, что месяцы индексируются с 1 (первому месяцу присваивается 1), в отличие от часов, минут, секунд и миллисекунд (первому из которых присваивается 0).

Это означает, что округление DateTime до чётного кратного секунд, минут, часов или лет (потому что спецификация ISO 8601 включает год ноль) приведёт к DateTime с чётным значением в этом поле, тогда как округление DateTime до чётного кратного месяцев приведёт к тому, что поле месяца будет содержать нечётное значение. Поскольку и месяцы, и годы могут содержать неравное число дней, нет гарантии, что округление до чётного числа дней приведёт к чётному значению в поле дней.

См. справочник API для дополнительной информации о методах, экспортированных из модуля Dates.

© 2009–2016 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/release-0.6/manual/dates/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API