Сегодня в СУБД Oracle есть несколько типов данных для хранения дат и времени. Самый старый из них - тип DATE - совершенно точно был еще в Oracle 7 (с более ранними версиями СУБД я не работал). Тогда ввести значение типа DATE можно было только с помощью функции to_date
. В версии 9 появился литерал для значений типа DATE (а также новые типы для дат и времени TIMESTAMP, TIMESTAMP WITH LOCAL TIME ZONE и TIMESTAMP WITH TIME ZONE). Рассмотрим сегодня некоторые особенности типа DATE.
Тип DATE позволяет работать с датами и временем с точностью до 1 секунды в диапазоне от date '-4712-01-01'
(1 января 4713 года до нашей эры (это не ошибка, читайте дальше)) до date '9999-12-31'
(31 декабря 9999 нашей эры). Проверим это:
SQL> alter session set nls_date_format = 'yyyy-mm-dd hh24:mi:ss AD';
Session altered.
SQL> select date '-4712-01-01', date '9999-01-01' from dual;
DATE'-4712-01-01' DATE'9999-01-01'
---------------------- ----------------------
4712-01-01 00:00:00 BC 9999-01-01 00:00:00 AD
Днем раньше или позже:
SQL> select date '-4712-01-01'-1 from dual;
select date '-4712-01-01'-1 from dual
*
ERROR at line 1:
ORA-01841: (full) year must be between -4713 and +9999, and not be 0
SQL> select date '9999-12-31'+1 from dual;
select date '9999-12-31'+1 from dual
*
ERROR at line 1:
ORA-01841: (full) year must be between -4713 and +9999, and not be 0
Как видим, попытка выйти за границы разрешенного диапазона не удалась. Однако, сообщение об ошибке утверждает, что минимальным допустимым годом является -4713, а не -4712, и что недопустимо указывать нулевой год 0000. На самом деле, попытка работать с -4713 годом привела нас к ошибке, а вот литерал с годом 0000 работает успешно (чего не скажешь о функции to_date
):
SQL> select date '0000-01-01' from dual;
DATE'0000-01-01'
----------------------
0000-01-01 00:00:00 AD
SQL> select to_date('0000-01-01', 'yyyy-mm-dd') from dual;
select to_date('0000-01-01', 'yyyy-mm-dd') from dual
*
ERROR at line 1:
ORA-01841: (full) year must be between -4713 and +9999, and not be 0
В чем здесь дело?
Как известно, существует соглашение о счете лет от рождения Иисуса Христа. По этому соглашению, 1-й год нашей эры (0001 AD) - это 1-й год жизни Иисуса. Год, предшествующий 1-му году нашей эры, считается 1-м годом до нашей эры (0001 BC). Между 1 января 1 года до н. э. и 1 января 1 года н. э. прошел один год. Нулевого года в традиционном летоисчислении просто нет.
Логично, что новая эра начинается с 1-го года. Ведь и для новорожденного сразу начинается 1-ый год жизни, а когда ему исполняется 1 год - начинается его второй год жизни. Когда 1 января 2001 года наступил XXI век, то сразу же начался 1-ый год нового века, правильно? Правильно.
Однако, отсутствие нулевого года не согласуется с арифметикой. Если 1 год до н. э. принять за -1, то разность между числами, представляющими два соседних года в календаре, оказывается равна двум: 1 - (-1) = 2. Другой казус состоит в том, что 1 год до н. э. должен быть високосным, поскольку он случился на 4 года раньше, чем 4-й год н. э., который является високосным. Но -1 не делится на 4 без остатка и потому не удовлетворяет правилу високосного года!
Чтобы устранить эти неудобства и упростить астрономические расчеты, уже давным-давно астрономы ввели в обращение нулевой год. Астрономический нулевой год
- соответствует 1-му году до н. э. в традиционном летоисчислении,
- арифметически отстоит от следующего за ним года на 1: 1 - 0 = 1,
- является високосным по правилу определения високосного года.
1-й и последующий астрономические годы соответствуют 1-му и последующим годам н. э., а 0-ой, минус 1-ый и предшествующие отрицательные годы соответствуют 1-му, 2-му и предшествующим годам до н. э., соответственно (со сдвигом на 1).
Так вот, в СУБД Oracle реализован астрономический счет лет:
SQL> select date '0001-01-01' - 1 from dual;
DATE'0001-01-01'-1
----------------------
0000-12-31 00:00:00 AD
SQL> select date '0001-01-01' - date '0000-01-01' from dual;
DATE'0001-01-01'-DATE'0000-01-01'
---------------------------------
366
SQL> select date '0000-01-01' - 1 from dual;
DATE'0000-01-01'-1
----------------------
0001-12-31 00:00:00 BC
Однако, индикатор BC
(до нашей эры) в последнем примере создает впечатление, что перед нами не астрономическая дата, а дата традиционного христианского летоисчисления. Это некорректно со стороны Oracle, ведь на самом деле дата 0001-12-31 00:00:00 BC
из последнего запроса соответствует 31 декабря 2 года до н. э.
Поэтому, имея дело в СУБД Oracle с датами до нашей эры, лучше избегать индикатора BC
(и помнить об астрономическом летоисчислении):
SQL> alter session set nls_date_format = 'syyyy-mm-dd hh24:mi:ss';
Session altered.
SQL> select date '0000-01-01' - 1 from dual;
DATE'0000-01-01'-1
--------------------
-0001-12-31 00:00:00
Если вернуться к нашему первому запросу, то, в свете сказанного становится понятно, что литерал date '-4712-01-01'
, задающий минимальное допустимое значение типа DATE, соответствует 1 января 4713 до нашей эры.
SQL> select date '-4712-01-01', date '9999-01-01' from dual;
DATE'-4712-01-01' DATE'9999-01-01'
-------------------- --------------------
-4712-01-01 00:00:00 9999-01-01 00:00:00
Встретившееся ранее сообщение об ошибке
"ORA-01841: (full) year must be between -4713 and +9999, and not be 0"
исходит из того, что даты в СУБД Oracle имеют традиционное, а не астрономическое представление, что, как мы видим, расходится с практикой.
Минимально допустимая дата 1 января 4713 г. до н. э. в СУБД Oracle не случайна. Это день начала цикла Скалигера, с которого астрономы ведут отсчет так называемых "юлианских дней". На эту тему см. статью в Википедии.
Oracle позволяет преобразовывать даты в юлианские дни, и наоборот. Вот крайние даты диапазона значений DATE, выраженные в юлианских днях, и некоторые круглые юлианские дни, выраженные в традиционных датах:
SQL> select to_char(date '-4712-01-01', 'j'),
2 to_char(date '9999-12-31', 'j')
3 from dual;
TO_CHAR(DATE'-4712-01-01','J TO_CHAR(DATE'9999-12-31','J'
---------------------------- ----------------------------
0000001 5373484
SQL> select to_date(1000000, 'j'),
2 to_date(2000000, 'j'),
3 to_date(3000000, 'j')
4 from dual;
TO_DATE(1000000,'J') TO_DATE(2000000,'J') TO_DATE(3000000,'J')
-------------------- -------------------- --------------------
-1975-11-07 00:00:00 0763-09-14 00:00:00 3501-08-15 00:00:00
Какой сегодня у нас юлианский день?
SQL> select to_char(sysdate, 'j') from dual;
TO_CHAR(SYSDATE,'J')
----------------------------
2457861
Возможность преобразований между юлианскими днями и традиционными датами означает, в частности, что даты с точностью до суток можно хранить в СУБД Oracle как число - номер юлианского дня. Арифметика с юлианскими днями, очевидно, соответствует арифметике с датами в СУБД Oracle, за исключением того, что даты складывать нельзя, а юлианские дни - можно (например, чтобы найти середину диапазона):
SQL> select (date '2017-01-01' + date '2017-06-30')/2 from dual;
select (date '2017-01-01' + date '2017-06-30')/2 from dual
*
ERROR at line 1:
ORA-00975: date + date not allowed
SQL> select to_date(
2 (to_char(date '2017-01-01','j') + to_char(date '2017-06-30','j'))/2,
3 'j')
4 from dual;
TO_DATE((TO_CHAR(DAT
--------------------
2017-04-01 00:00:00
С помощью юлианских дней легко определить день недели (0 - понедельник, 1 - вторник, и т.д.):
SQL> select mod(to_char(trunc(sysdate),'j'), 7) from dual;
MOD(TO_CHAR(TRUNC(SYSDATE),'J'),7)
----------------------------------
0
Календарь - вещь не менее увлекательная, чем карта. Да он и есть карта времени.
Сегодня повсеместно принят григорианский календарь (иногда наряду с национальными календарями, как, например, в Японии), а ему исторически предшествовал юлианский календарь. Отличие юлианского и григорианского календарей - в правиле определения високосного года.
В юлианском календаре, установленном Юлием Цезарем в 46 году до н.э., правило определения високосного года такое: если число, представляющее год, делится на 4 без остатка, значит, год високосный и содержит 366 дней, иначе год не високосный и содержит 365 дней. Таким образом, в юлианском календаре каждый 4-й год високосный.
Применим это правило к годам с 1895 по 1904:
SQL> select to_char(lvl, '0000') year,
2 case when mod(lvl, 4) = 0 then 'yes' else null end is_leap_year
3 from (select level+1894 lvl from dual connect by level <= 10)
4 ;
YEAR IS_LEAP_YEAR
-------------------- ------------
1895
1896 yes
1897
1898
1899
1900 yes
1901
1902
1903
1904 yes
10 rows selected.
В григорианском календаре, впервые введенном папой Григорием XIII в XVI веке, правило високосного года изменилось из-за того, что были получены уточненные данные о продолжительности астрономического года. Оказалось, что астрономический год на 11 минут короче года, положенного в основу юлианского календаря и равного 365.25 дней. Поэтому правило определения високосного года стало таким: если число, представляющее год, делится на 4 без остатка и (не делится на 100 или делится на 400), значит, год високосный и содержит 366 дней, иначе год не високосный и содержит 365 дней. Таким образом, в григорианском календаре високосным является каждый 4-й год, за исключением кратных 100 и при этом не кратных 400.
Применим это правило к годам с 1895 по 1904:
SQL> select to_char(lvl, '0000') year,
2 case when mod(lvl, 4) = 0 and (mod(lvl, 100) != 0 or mod(lvl, 400) = 0) then 'yes' else null end is_leap_year
3 from (select level+1894 lvl from dual connect by level <= 10)
4 ;
YEAR IS_LEAP_YEAR
-------------------- ------------
1895
1896 yes
1897
1898
1899
1900
1901
1902
1903
1904 yes
10 rows selected.
Переход от юлианского к григорианскому календарю в разных странах имел место в разное время и растянулся с XVI по XX века. Первыми на григорианский календарь перешли несколько католических стран Европы в октябре 1582 года; при этом за 4-тым октября сразу последовало 15-е. Британия и ее колонии, включая североамериканские, перешли на григорианский календарь в сентябре 1752 года. Одной из последних на григорианский календарь перешла Россия в 1918 году: после 31 января наступило 14 февраля.
Следующие запросы демонстрируют, что в СУБД Oracle
- для дат ранее XVI века работает юлианское правило високосного года,
- в октябре 1582 реализован переход от юлианского к григорианскому календарю,
- для дат позднее XVI века работает григорианское правило високосного года.
SQL> select date '1200-02-29' from dual;
DATE'1200-02-29'
--------------------
1200-02-29 00:00:00
SQL> select date '1300-02-29' from dual;
DATE'1300-02-29'
--------------------
1300-02-29 00:00:00
SQL> select date '1400-02-29' from dual;
DATE'1400-02-29'
--------------------
1400-02-29 00:00:00
SQL> select date '1500-02-29' from dual;
DATE'1500-02-29'
--------------------
1500-02-29 00:00:00
SQL> select date '1582-10-04', date '1582-10-04'+1 from dual;
DATE'1582-10-04' DATE'1582-10-04'+1
-------------------- --------------------
1582-10-04 00:00:00 1582-10-15 00:00:00
SQL> select date '1582-10-31' - date '1582-09-30' from dual;
DATE'1582-10-31'-DATE'1582-09-30'
---------------------------------
21
SQL> select date '1600-02-29' from dual;
DATE'1600-02-29'
--------------------
1600-02-29 00:00:00
SQL> select date '1700-02-29' from dual;
select date '1700-02-29' from dual
*
ERROR at line 1:
ORA-01847: day of month must be between 1 and last day of month
SQL> select date '1800-02-29' from dual;
select date '1800-02-29' from dual
*
ERROR at line 1:
ORA-01847: day of month must be between 1 and last day of month
SQL> select date '1900-02-29' from dual;
select date '1900-02-29' from dual
*
ERROR at line 1:
ORA-01847: day of month must be between 1 and last day of month
SQL> select date '2000-02-29' from dual;
DATE'2000-02-29'
--------------------
2000-02-29 00:00:00
Любопытно, что согласно утилите cal
в Linux переход к григорианскому календарю происходит не в октябре 1582 (вместе с католическими странами Европы), а в сентябре 1752 года (вместе с Британией и колониями):
[ay@tsuki ~]$ cal 10 1582
October 1582
Su Mo Tu We Th Fr Sa
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
[ay@tsuki ~]$ cal 9 1752
September 1752
Su Mo Tu We Th Fr Sa
1 2 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
[ay@tsuki ~]$ cal 2 1700
February 1700
Su Mo Tu We Th Fr Sa
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29
[ay@tsuki ~]$ cal 2 1900
February 1900
Su Mo Tu We Th Fr Sa
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28
В заключение разговора о типе DATE, пара этюдов практического характера.
Значение типа DATE с точностью до секунд и сегодня нельзя задать с помощью литерала. Как и прежде, сделать это можно с помощью функции to_date
:
SQL> select date '2017-04-17 20:15:41' from dual;
select date '2017-04-17 20:15:41' from dual
*
ERROR at line 1:
ORA-01861: literal does not match format string
SQL> select to_date('2017-04-17 20:15:41', 'yyyy-mm-dd hh24-mi-ss') from dual;
TO_DATE('2017-04-172
--------------------
2017-04-17 20:15:41
Функция sysdate
возвращает текущие системные дату и время, а функция current_date
- текущие дату и время в часовом поясе, в котором работает пользователь:
SQL> select sysdate, current_date from dual;
SYSDATE CURRENT_DATE
-------------------- --------------------
2017-04-17 20:52:53 2017-04-17 20:52:53
Совпадение результатов говорит о том, что часовой пояс, в котором работает сервер СУБД Oracle, и часовой пояс пользователя совпадают. Изменим часовой пояс пользователя и увидим разницу:
SQL> select sessiontimezone from dual;
SESSIONTIMEZONE
--------------------------------------------------------------------------------
+10:00
SQL> alter session set time_zone = '+3:00';
Session altered.
SQL> select sessiontimezone from dual;
SESSIONTIMEZONE
--------------------------------------------------------------------------------
+03:00
SQL> select sysdate, current_date from dual;
SYSDATE CURRENT_DATE
-------------------- --------------------
2017-04-17 20:56:33 2017-04-17 13:56:33
Комментариев нет:
Отправить комментарий