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

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)

Эту временную метку можно корректно представить как 2:30 в UTC-4 или 1:30 в UTC-5; код вывода метки времени выбирает второй вариант.

Точное правило, применяемое в таких случаях, состоит в том, что недопустимая временная метка, которая предположительно попадает в момент перевода часов вперед на летнее время, привязывается к смещению 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/, резервное копирование остается на ваше усмотрение — обычный дамп базы данных не будет включать этот каталог.

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

Стандарт 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)