Поддержка даты и времени

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

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



Интерпретация ввода даты и времени

Строки ввода даты/времени распознаются по следующей схеме.

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

    1. Если числовая синтаксическая единица содержит двоеточие (:), то это строка времени. Включаются все последующие цифры и двоеточия.

    2. Если числовая синтаксическая единица содержит тире (-), слэш (/) или две или более точек (.), то это строка даты, которая может содержать название месяца. Если единица даты уже встречалась, то она интерпретируется как название часового пояса (например, America/New_York).

    3. Если синтаксическая единица состоит только из чисел, то это либо отдельное поле, либо объединенная дата ISO 8601 (например, 19990113 для 13 января 1999 года) или время (например, 141516 для 14:15:16).

    4. Если синтаксическая единица начинается с плюса (+) или минуса (-), то это либо числовой часовой пояс, либо специальное поле.

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

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

    2. Если соответствие не найдено, проверить по внутренней таблице, не совпадает ли синтаксическая единица со специальной строкой (например, today), днем недели (например, Thursday), месяцем (например, January) или пропускаемым словом (например, at, on).

    3. Если соответствие все же не найдено, вывести ошибку.

  3. Когда синтаксическая единица является числом или числовым полем:

    1. Если получено восемь или шесть цифр и если никакие другие поля даты не были ранее прочитаны, интерпретировать их как «объединенную дату» (например 19990118 или 990118). Интерпретация такой даты — ГГГГММДД или ГГММДД.

    2. Если синтаксическая единица состоит из трех цифр и год уже был прочитан, интерпретировать ее как день года.

    3. Если это четыре или шесть цифр и год уже был прочитан, интерпретировать их как время (ЧЧММ или ЧЧММСС).

    4. Если это три или более цифр и поля даты еще не были найдены, интерпретировать их как год (это приводит к установке порядка гг-мм-дд для оставшихся полей даты).

    5. В противном случае предполагается, что порядок полей даты будет следовать настройке DateStyle: мм-дд-гг, дд-мм-гг или гг-мм-дд. Если поле месяца или дня окажется за пределами диапазона, выдать ошибку.

  4. Если был указан год до н. э. (BC), отнять год и добавить единицу для внутреннего хранения. (В григорианском календаре нет нулевого года, поэтому 1 год до н. э. становится нулевым.)

  5. Если год до н. э. не был указан и поле года было двухзначным, исправить запись года на четырехзначную. Если поле меньше 70, добавить 2000, в противном случае добавить 1900.

Совет
Годы с 1 по 99 н. э. по григорианскому календарю можно ввести, используя четырехзначное число с ведущими нулями (например, 0099 — это 99 год н. э.).


Обработка недопустимых или неоднозначных временных меток

Обычно если строка даты/времени является синтаксически допустимой, но содержит поля со значениями вне диапазона, выдается ошибка. Например, при введении даты 31 февраля строка будет отклонена.

При переходе на летнее время допустимая на первый взгляд строка даты/времени может отображать несуществующую или неоднозначную метку времени. В таких случаях строка не отклоняется; неоднозначность решается путем определения того, какое смещение UTC применить. Например, предположим, что для параметра TimeZone установлено значение America/New_York, и рассмотрим следующий пример:

=> SELECT ’2018-03-11 02:30’::timestamptz;
timestamptz
------------------------
2018-03-11 03:30:00-04
(1 row)

Поскольку в этом часовом поясе в этот день стрелки часов переводились вперед, по гражданскому времени момента 2:30 не существовало; часы перескочили вперед с 2:00 по восточному стандартному времени (EST) на 3:00 по летнему восточному времени (EDT). QHB воспринимает данное время, как если бы оно было стандартным временем (часовой пояс UTC-5), которое затем преобразовалось в 3:30 EDT (часовой пояс UTC-4).

И, наоборот, рассмотрим поведение во время перевода времени назад:

=> SELECT ’2018-11-04 02:30’::timestamptz;
timestamptz
------------------------
2018-11-04 02:30:00-05
(1 row)

В этот день возможны две интерпретации времени 2:30; было 2:30 в часовом поясе EDT, а затем, через час после возврата к стандартному времени, наступило 2:30 в часовом поясе EST. Опять же, QHB воспринимает данное время, как если бы оно было стандартным (часовой пояс UTC-5). Можно форсировать ситуацию, указав часовой пояс летнего времени:

=> SELECT ’2018-11-04 02:30 EDT’::timestamptz;
timestamptz
------------------------
2018-11-04 01:30:00-05
(1 row)

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

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


Ключевые слова даты и времени

В Таблице 1 перечислены синтаксические единицы, которые распознаются как названия месяцев.

Таблица 1. Названия месяцев

МесяцАббревиатуры
JanuaryJan
FebruaryFeb
MarchMar
AprilApr
May
JuneJun
JulyJul
AugustAug
SeptemberSep, Sept
OctoberOct
NovemberNov
DecemberDec

В Таблице 2 показаны синтаксические единицы, которые распознаются как названия дней недели.

Таблица 2. Названия дней недели

ДеньАббревиатуры
SundaySun
MondayMon
TuesdayTue, Tues
WednesdayWed, Weds
ThursdayThu, Thur, Thurs
FridayFri
SaturdaySat

В Таблице 3 показаны синтаксические единицы, которые выполняют различные функции модификаторов.

Таблица 3. Модификаторы поля даты/времени

ИдентификаторОписание
AMВремя до 12:00
ATИгнорируется
JULIAN , JD , JСледующее поле — юлианская дата
ONИгнорируется
PMВремя 12:00 и более позднее
TСледующее поле — время

Файлы конфигурации даты/времени

Поскольку аббревиатуры часовых поясов недостаточно стандартизированы, QHB предоставляет средства для самостоятельного определения набора аббревиатур, принимаемых сервером. Параметр времени выполнения timezone_abbreviations определяет активный набор аббревиатур. Хотя этот параметр может изменить любой пользователь базы данных, возможные значения для него контролируются администратором базы данных и на самом деле являются именами файлов конфигурации, хранящихся в .../share/timezonesets/ каталога установки. Добавляя или изменяя файлы в этом каталоге, администратор может назначить локальную политику для аббревиатур часовых поясов.

В timezone_abbreviations можно установить любое имя файла, найденное в .../share/timezonesets/, если то полностью состоит из букв. (Запрет на использование небуквенных символов в timezone_abbreviations препятствует чтению файлов, находящихся вне заданного каталога, а также чтению резервных файлов редактора и других посторонних файлов.)

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

аббревиатура_пояса смещение
аббревиатура_пояса смещение D
аббревиатура_пояса имя_часового_пояса
@INCLUDE имя_файла
@OVERRIDE

аббревиатура_пояса просто задает определяемую аббревиатуру. смещение — это целое число, задающее эквивалентное смещение от UTC в секундах: положительное — к востоку от Гринвича, а отрицательное — к западу. Например, -18000 означало бы пять часов к западу от Гринвича или североамериканское восточное стандартное время. D указывает, что название зоны представляет местное летнее, а не стандартное время.

Как вариант, можно задать имя_часового_пояса, ссылающееся на имя пояса, определенное в базе данных часовых поясов IANA. Определение пояса позволяет узнать, используется или использовалась ли аббревиатура в этом пояса, и если да, то выбирается соответствующее значение, то есть значение, действовавшее в указанный момент времени, или действовавшее непосредственно перед ним, если текущее не было определено, или самое старое значение, если оно начало действовать только после этого момент. Такое поведение важно для работы с аббревиатурами, значение которых менялось в ходе истории. Также разрешается определять аббревиатуру через имя часового пояса, в котором этой аббревиатуры нет; в таком случае использование аббревиатуры равнозначно написанию просо имени часового пояса.

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

Синтаксис @INCLUDE позволяет включать другой файл из каталога .../share/timezonesets/. Включение может быть вложенным до ограниченной глубины.

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

При установке без изменений файл Default содержит все не конфликтующие аббревиатуры часовых поясов для большей части мира. Дополнительные файлы Australia и India предоставляются для данных регионов: эти файлы сначала включают файл Default, а затем добавляют или изменяют аббревиатуры по мере необходимости.

В справочных целях стандартная установка также содержит файлы Africa.txt, America.txt и т. д., содержащие информацию о каждой используемой, согласно базе данных часовых поясов IANA, аббревиатуре часового пояса. Определения имен часовых поясов, находящихся в этих файлах, можно копировать и вставлять в пользовательский файл конфигурации по мере необходимости. Обратите внимание, что эти файлы нельзя напрямую указывать как значение параметра timezone_abbreviations, поскольку их имена содержат точку.

Примечание
Если при чтении набора аббревиатур часовых поясов возникает ошибка, новое значение не применяется и сохраняется старый набор. Если ошибка возникает при запуске базы данных, происходит сбой запуска.

ВНИМАНИЕ!
Аббревиатуры часовых поясов, определенные в файле конфигурации, переопределяют не относящиеся к часовым поясам значения, встроенные в QHB. Например, файл конфигурации Australia определяет SAT (для южно-австралийского стандартного времени, South Australian Standard Time). Когда этот файл активен, SAT не будет распознаваться как сокращение слова «суббота».

ВНИМАНИЕ!
Если вы изменяете файлы в .../share/timezonesets/, резервное копирование остается на ваше усмотрение — обычный дамп базы данных не будет включать этот каталог.


Указание часовых поясов в стиле POSIX

QHB может принимать указания часовых поясов, записанные в соответствии с правилами стандарта POSIX для переменной среды TZ. Указания часовых поясов в стиле POSIX не подходят для решения вопросов, связанных со сложной историей часовых поясов в реальном мире, но иногда их использование бывает оправданным.

Указание часового пояса в стиле POSIX имеет следующую форму:

STD смещение [ DST [ смещение_летнего_времени ] [ , правило ] ]

(Для удобства восприятия между полями добавлены пробелы, но на практике их использовать не следует.) В указании имеются следующие поля:

  • STD — аббревиатура, используемая для стандартного часового пояса.

  • смещение — смещение стандартного времени часового пояса от UTC.

  • DST — аббревиатура часового пояса при переходе на летнее время. Если это поле и следующие за ним опущены, для часового пояса используется фиксированное смещение UTC без учета перехода на летнее время.

  • смещение_летнего_времени — смещение часового пояса от UTC при переходе на летнее время. Обычно это поле опускается, поскольку по умолчанию такое смещение на один час меньше стандартного смещения, что, как правило, и происходит на практике.

  • правило определяет, когда вступает в силу переход на летнее время; более подробно это описывается ниже.

В таком синтаксисе аббревиатура часового пояса может представлять собой строку букв, например EST, или произвольную строку, заключенную в угловые скобки, например <UTC-05>. Обратите внимание, что показанные здесь аббревиатуры часовых поясов используются только для вывода и только в некоторых выходных форматах метки времени. Определения аббревиатур, распознаваемых во входных значениях меток времени, описаны в подразделе Файлы конфигурации даты/времени.

В полях смещений задается разница с UTC в часах и, при необходимости, в минутах и секундах. Они записываются в формате чч[:мм[:сс]] и могут иметь спереди знак (+ or -). Положительный знак используется для часовых поясов к западу от Гринвича. (Обратите внимание, что это противоположно соглашению о знаках ISO-8601, применяемому в других областях QHB.) Элемент чч может состоять из одной или двух цифр; мм и сс (если они задаются) должны состоять из двух цифр.

Поле, задающее правило перехода на летнее время, имеет следующий формат:

дата_перехода [ / время_перехода ] , дата_возврата [ / время_возврата ]

(Как и ранее, на практике пробелов здесь быть не должно.) Поля дата_перехода и время_перехода определяют момент перехода на летнее время, а поля дата_возврата и время_возврата определяют момент возврата к стандартному времени. (В некоторых случаях, в частности, в Южном полушарии, первая дата в календаре может идти после второй.) Поля даты задаются в одном из следующих форматов:

n
Простое целое число, обозначающее день года, с нумерацией от нуля до 364, или до 365 в високосном году.

Jn
В этой форме n обозначает номер от 1 до 365, а 29 февраля не считается, даже когда присутствует. (Таким образом, смены часового пояса, происходящие 29 февраля, таким способом задать нельзя. Однако после февраля дни имеют одинаковые номера, независимо от того, високосный это год или нет, так что для записи фиксированных дат перевода часов эта форма более полезна, чем простое целое.)

Mm.n.d
Эта форма задает переход, который всегда происходит в один и тот же месяц и день недели. m обозначает номер месяца от 1 до 12. n обозначает номер вхождения в этом месяце дня недели, заданного полем d. Значение n задается числом от 1 до 4 либо числом 5, означающим последнее вхождение этого дня недели в этом месяце (фактически оно может быть четвертым или пятым). Значение d задается числом от 0 до 6, где 0 обозначает воскресенье. Например, запись M3.2.0 означает «второе воскресенье марта».

Примечание
Для описания многих распространенных правил перехода на летнее время формата M вполне достаточно. Но примите к сведению, что ни один из этих вариантов не позволяет отразить изменения в этих правилах, поэтому на практике для правильной интерпретации меток времени, относящихся к прошлому, необходимы исторические данные, хранящиеся в привязке к именованным часовым поясам (в базе данных часовых поясов IANA).

Поля времени в правиле перехода имеют тот же формат, что и описанные ранее поля смещения, за исключением того, что они не могут содержать знака. Они определяют текущее местное время, в которое производится перевод часов. Если эти поля опущены, по умолчанию их значением считается 02:00:00.

Если аббревиатура для летнего времени задается, но правило перехода опускается, в качестве альтернативы используется правило M3.2.0,M11.1.0, соответствующее порядку, принятому в США в 2020 г. (то есть часы переводятся вперед во второе воскресенье марта, а назад — в первое воскресенье ноября, в 2:00 по действующему времени). Обратите внимание, что это правило не дает правильные даты перехода в США ранее 2007 г.

Например, запись CET-1CEST,M3.5.0,M10.5.0/3 описывает действующий в 2020 г. порядок перевода часов в Париже. В этом указании говорится, что стандартное время имеет аббревиатуру CET и смещено на час вперед (к востоку) от UTC; летнее время имеет аббревиатуру CEST и смещено (неявно) на два часа вперед от UTC; летнее время начинается в последнее воскресенье марта в 2:00 CET и заканчивается в последнее воскресенье октября в 3:00 CEST.

Названия четырех часовых поясов EST5EDT, CST6CDT, MST7MDT и PST8PDT выглядят как указания часовых поясов в стиле POSIX. Однако в действительности они воспринимаются как именованные часовые пояса, поскольку (по историческим причинам) в базе данных часовых поясов IANA есть файлы с такими именами. Практическое следствие этого состоит в том, что для этих имен часовых поясов будут выдаваться исторические сведения о действовавших правилах перехода на летнее время в США, тогда как обычное указание в стиле POSIX этого не сделает.

Не стоит забывать, что ошибиться при указании часового пояса в стиле POSIX очень легко, поскольку допустимость аббревиатуры не проверяется. К примеру, команда SET TIMEZONE TO FOOBAR0 выполнится успешно, и по сути система будет использовать довольно своеобразную аббревиатуру для UTC.


История единиц измерения времени

Стандарт SQL гласит, что «В определении "литерала даты и времени" "значения даты и времени" ограничены естественными правилами для дат и времени согласно григорианскому календарю». Следуя примеру стандарта SQL, QHB считает даты исключительно в григорианском календаре, в том числе те годы, когда этот календарь не использовался. Это правило известно как пролептический григорианский календарь.

Юлианский календарь был введен Юлием Цезарем в 45 году до н. э. Он широко использовался в западном мире вплоть до 1582 года, когда страны начали переходить на григорианский календарь. В юлианском календаре тропический год равен примерно 365 1/4 дням = 365,25 дням. В результате каждые 128 лет накапливается приблизительно 1 лишний день.

Накапливающаяся календарная погрешность побудила Папу Григория XIII реформировать календарь в соответствии с предписаниями Трентского Собора. В григорианском календаре тропический год равен примерно 365 + 97/400 дням = 365,2425 дням. Таким образом, в григорианском календаре погрешность в один день тропического года накапливается примерно за 3300 лет.

Приблизительное число 365 + 97/400 достигается за счет того, что каждые 97 лет из 400 становятся високосными с помощью следующих правил:

Каждый год, кратный 4, является високосным.
Однако каждый год, кратный 100, не является високосным.
Однако каждый год, кратный 400, все-таки является високосным.

То есть 1700, 1800, 1900, 2100 и 2200 не являются високосными годами. Но 1600, 2000 и 2400 — високосные годы. А вот в старом юлианском календаре високосными являются все годы, кратные 4.

Папская булла, изданная в феврале 1582 года, постановила, что октябрь 1582 года должен быть на 10 дней короче, чтобы 15 октября следовало сразу за 4 октября. Этому предписанию последовали Италия, Польша, Португалия и Испания. Вскоре к ним присоединились и другие католические страны, но протестантские страны вводили эти изменения неохотно, а страны греческой православной церкви не проводили реформу до начала 20-го века. В 1752 году реформа была проведена в Великобритании и ее доминионах (включая территорию нынешних США). Таким образом, за 2 сентября 1752 года последовало 14 сентября 1752 года. Вот почему в системах Unix, имеющих программу cal, выводится следующее:

$ cal 9 1752
September 1752
S M Tu W Th F S
1 2 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30

Но, конечно, этот календарь действителен только для Великобритании и доминионов, а в других местах он не действует. Поскольку отслеживание реальных календарей, которые использовались в разных местах в разное время, сопряжено с трудностями и неясностями, QHB вместо этого применяет правила григорианского календаря ко всем датам, хотя этот метод и не является исторически достоверным.

Различные календари были разработаны в разных частях света, многие из которых появились до григорианской системы. Например, появление китайского календаря относится к 14 веку до н. э. Легенда гласит, что император Хуанди изобрел этот календарь в 2637 году до н. э. В Китайской Народной Республике григорианский календарь используется в гражданских целях. Китайский календарь используется для определения праздничных дат.


Юлианские даты

Система юлианских дат — это способ исчисления периодов времени. Он не имеет отношения к юлианскому календарю, хотя схожее название может сбить с толку. Система юлианских дат была изобретена французским ученым Жозефом Юстусом Скалигером (1540-

  1. и, вероятно, получила свое название в честь его отца, итальянского ученого Юлия Цезаря Скалигера (1484-1558).

В системе юлианских дат каждый день имеет порядковый номер, начинающийся с JD 0 (который иногда называют номером юлианской даты). JD 0 соответствует 1 января 4713 г. до н. э. по юлианскому календарю или 24 ноября 4714 г. до н. э. по григорианскому календарю. Исчисление по юлианским датам чаще всего используется астрономами для маркировки своих ночных наблюдений, и поэтому день длится с полудня до полудня UTC, а не с полуночи до полуночи: JD 0 (первый юлианский день) обозначает 24 часа с полудня UTC 24 ноября 4714 г. до н. э. до полудня UTC 25 ноября 4714 г. до н. э.

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

Однако при необходимости эта формулировка позволяет получить астрономическое определение, произведя вычисления в часовом поясе UTC+12. Например,

=> SELECT extract(julian from '2021-06-23 7:00:00-04'::timestamptz at time zone 'UTC+12');
     date_part
--------------------
 2459388.9583333335
(1 row)
=> SELECT extract(julian from '2021-06-23 8:00:00-04'::timestamptz at time zone 'UTC+12');
 date_part
-----------
   2459389
(1 row)
=> SELECT extract(julian from date '2021-06-23');
 date_part
-----------
   2459389
(1 row)