Источник Хабрахабр.ru, Москва
Заголовок ДАТская арифметика високосного года в базе данных Oracle
Дата 20240702

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

============= Обработанный текст:
ДАТская арифметика високосного года в базе данных• Информационные технологии » Информационно-коммуникационные технологии » Информационные технологии и телекоммуникации » Базы данных

• Высокие технологии » Информационные технологии и телекоммуникации » Базы данных
Oracle• Информационные технологии » Информационно-коммуникационные технологии » Информационные технологии и телекоммуникации » Базы данных » Oracle

• Высокие технологии » Информационные технологии и телекоммуникации » Базы данных » Oracle

ДАТская арифметика високосного года в базе данных• Информационные технологии » Информационно-коммуникационные технологии » Информационные технологии и телекоммуникации » Базы данных

• Высокие технологии » Информационные технологии и телекоммуникации » Базы данных
Oracle• Информационные технологии » Информационно-коммуникационные технологии » Информационные технологии и телекоммуникации » Базы данных » Oracle

• Высокие технологии » Информационные технологии и телекоммуникации » Базы данных » Oracle

Уровень сложности

Средний

Время на прочтение

7 мин

Количество просмотров

69

Блог компании Ингосстрах• Страхование » Страховые компании » Страховые компании России » МСГ ИНГО » Ингосстрах

• Страхование » Страховые компании » Страховые компании России » Ингосстрах

• Объект организация » Организации по алфавиту » Организации на Ин » МСГ ИНГО » Ингосстрах

• Объект организация » Организации по алфавиту » Организации на Ин » Ингосстрах
Oracle• Информационные технологии » Информационно-коммуникационные технологии » Информационные технологии и телекоммуникации » Базы данных » Oracle

• Высокие технологии » Информационные технологии и телекоммуникации » Базы данных » Oracle
* SQL *

Кейс

Начнем с теории

Начнем с теории

В ФЗ-107 «Об исчислении времени» Статья 2 указано:

5) календарный годпериод времени с 1 января по 31 декабря
продолжительностью триста шестьдесят пять либо триста шестьдесят шесть• Отдых, развлечения » Развлекательные игры » Настольные игры » Карточные Игры » Шестьдесят шесть
(високосный год) календарных дней. Календарный год имеет порядковый номер в
соответствии с григорианским календарем;

Логично и очевидно, когда рассматривается период с начала по конец года.

А вот сколько должно быть дней в интервале (365/366), который начинается не
просто 1 января, а затрагивает февраль високосного года?

Самый близкий по смыслу ответ нашелся в комментариях к Статье 16 ФЗ-229 «Об
исполнительном производстве• Государство » Законы и право » Правовая система России » Нормативные акты » Федеральные законы РФ » Закон "Об Исполнительном Производстве"
» (если у кого-то есть более точные данные,
напишите в комментариях):

срок, исчисляемый годами, оканчивается в соответствующие месяц и день
последнего года установленного срока (ч. 1);

срок, исчисляемый месяцами , оканчивается в соответствующий день последнего
месяца установленного срока. Если окончание срока, исчисляемого месяцами,
приходится на месяц , который соответствующего числа не имеет , то срок
оканчивается в последний день этого месяца (ч. 2; например, месячный срок,
исчисляемый с 29, 30 или 31 января, истечет 28 либо 29 февраля в високосный
год
);

Из написанного тоже не следует какая должна быть логика• Философия » Логика на граничных условиях.
Есть только один вывод• Философия » Логика » Вывод (рассуждение): если в новой дате нет необходимого дня, то берется
ближайший к нему предыдущий.

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

Неутешительный вывод• Философия » Логика » Вывод (рассуждение) из вышенаписанного – не существует задокументированных
методических правил датской арифметики в високосном году. Описан только один
случай преобразования 29 февраля високосного года в 28 февраля не високосного.

А при чем здесь страхование• Страхование?

Казалось бы, проблема не зависит от отрасли применения: високосный год он и в
Африке• Физико-географические регионы » Африка
високосный год .

И тут начинает портить жизнь многолетние договоры страхования• Страхование » Договор страхования – ведь за свой
период действия они (договоры) могут неоднократно попадать на високосный год.
И это еще пол беды, когда клиенту (допустим) не важны даты начала/окончания
периодов страхования• Страхование договора.

Баги встают в очередь, когда необходимо правильно разбить интервалы
страхования• Страхование погодично (финансовый учет идет в разрезе календарных лет) да еще
и в разных системах (фронты, бэк, , отчетность и т.п.). Мы ведь помним, что
на фронтах клиент не выбирает дату окончания руками – он ставит количество
месяцев/лет действия договора. То есть, системе необходимо каким-то образом
преобразовать интервал в конечное число, которое и будет зафиксировано в
учетной системе.

Потупим?

Вот, простой вопрос: если договор, длительностью год, начал действовать
28.02.2023, то, когда он закончится – 27.02.2024 или 28.02.2024?

По приведенной ранее выдержке из ФЗ, да и по тривиальной программерской
логике• Философия » Логика, следует, что правильным будет 27 число. Этому правилу следуют

Класс• Информационные технологии » Информационно-коммуникационные технологии » Информационные технологии и телекоммуникации » Программирование » Программное обеспечение » Языки программирования » Типы данных » Структуры Данных » Класс (программирование)

• Информационные технологии » Информатика » Программирование » Программное обеспечение » Языки программирования » Типы данных » Структуры Данных » Класс (программирование)

• Информационные технологии » Информационно-коммуникационные технологии » Информационные технологии и телекоммуникации » Программирование » Парадигмы программирования » Объектно-ориентированное программирование » Класс (программирование)

• Информационные технологии » Информатика » Программирование » Парадигмы программирования » Объектно-ориентированное программирование » Класс (программирование)

• Высокие технологии » Информационные технологии и телекоммуникации » Программирование » Программное обеспечение » Языки программирования » Типы данных » Структуры Данных » Класс (программирование)

• Высокие технологии » Информационные технологии и телекоммуникации » Программирование » Парадигмы программирования » Объектно-ориентированное программирование » Класс (программирование)
Calendar в Java

Подкласс dateutil.relativedelta Python

Тип данных• Информационные технологии » Информатика » Программирование » Программное обеспечение » Языки программирования » Типы данных

• Информационные технологии » Информационно-коммуникационные технологии » Информационные технологии и телекоммуникации » Программирование » Программное обеспечение » Языки программирования » Типы данных

• Высокие технологии » Информационные технологии и телекоммуникации » Программирование » Программное обеспечение » Языки программирования » Типы данных
Interval Postgres

Т.е. решение можно считать негласным стандартом )

Вариант 1 (дата окончания 27 февраля 2024)

Строим цепочку периодов для договора на 5 лет с датой начала 28 февраля не
високосного года:

Начало периода Конец периода Количество дней День месяца День месяца
начала периода конца периода
28.02.2023 27 .02.2024 365 Последний Пред-
предпоследний
28. 02.2024 27.02.2025 36 6 Предпоследний Предпоследний
28.02.2025 27.02.2026 365 Последний Предпоследний
28.02.2026 27.02.2027 365 Последний Предпоследний
28.02.2027 27 .02.2028 365 Последний Пред-
предпоследний
Т.е. 2 периода страхования• Страхование, затрагивающие високосный год, выбиваются из логики• Философия » Логика
«Последний день – предпоследний день». И увеличенный период (366 дней)
происходит только при пересечении 29 февраля.

Безобразно, но однообразно. И согласуется с логикой ФЗ.

Вариант 2 (дата окончания 28 февраля 2024)

Та же самая цепочка периодов теперь выглядит более однородно:

Начало периода Конец периода Количество дней День месяца День месяца
начала периода конца периода
28.02.2023 28 .02.2024 36 6 Последний Предпоследний
29. 02.2024 27.02.2025 365 Последний Предпоследний
28.02.2025 27.02.2026 365 Последний Предпоследний
28.02.2026 27.02.2027 365 Последний Предпоследний
28.02.2027 28 .02.2028 36 6 Последний Предпоследний
Выделяются снова те же два периода, соблюдая логику• Философия » Логика «Последний день
предпоследний день». Увеличение периода (366 дней) происходит уже в другом
месте – расширяется интервал, предшествующий 29 февраля.

В итоге, встает методологический вопрос: а как правильно?

Если вы ограничены ТЗ до безусловного следования букве закона, вам остается
только первый вариант (27 февраля).

Если вам не безразличен потребитель ваших услуг и его психическое спокойствие
(объяснение с налоговой по поводу потерянного дня удовольствие малоприятное),
то разумно воспользоваться вторым вариантом (28 февраля). Дополнительным
поводом использовать вариант является уже реализованная в DB Oracle• Информационные технологии » Информационно-коммуникационные технологии » Информационные технологии и телекоммуникации » Базы данных » Oracle

• Высокие технологии » Информационные технологии и телекоммуникации » Базы данных » Oracle
функция
Add_months , учитывающая подобное поведение на границе високосного года.

Решение существует!

Круто! У нас есть как минимум 2 варианта решения. И даже встроенный метод
расчета. Можно с чистой совестью закодить и расслабиться… И словить уже другой
баг, менее очевидный и более заковыристый: а какова же дата окончания
договора, начинающегося 28.02.2024?

Используем Add_months DB Oracle• Информационные технологии » Информационно-коммуникационные технологии » Информационные технологии и телекоммуникации » Базы данных » Oracle

• Высокие технологии » Информационные технологии и телекоммуникации » Базы данных » Oracle
и получаем удивительную картину:

Начало периода Конец периода Количество дней День месяца День месяца
начала периода конца периода
28 .02.2024 27.02.2025 36 6 Предпоследний Предпоследний
28.02.2025 27.02.2026 365 Последний Предпоследний
28.02.2026 27.02.2027 365 Последний Предпоследний
28.02.2027 28 .02.2028 36 6 Последний Предпоследний
29 .02.2028 27.02.2029 365 Последний Предпоследний
Спустя 4 года, дата начала периода сдвигается на 29.02.

Чем же руководствоваться при решении такого бага, а может целой фичи?

Вот здесь уже всерьез придется включить голову.

Вариант решения «в лоб» – использовать логику• Философия » Логика «Предпоследний день
месяца-пред-предпоследний день месяца» – порождает встречный вопрос. А до
какой глубины от даты конца месяца использовать принцип «пред-пред-***»? До 27
дня, 26… 15 или вообще первого числа месяца?

А может вообще, количество дней в месяце должно быть фиксировано и равно 30?
Что-то слишком сложно выходит.

Поэтому делаем вывод• Философия » Логика » Вывод (рассуждение), что логика• Философия » Логика «Предпоследний день месяца-пред-предпоследний
день месяца» здесь не применима и единственным исключением датской арифметики
является «Последний день – предпоследний день».

Таким образом, корректировке подлежат последние 2 периода для обеспечения
одной даты начала действия договора:

Начало периода Конец периода Количество дней День месяца День месяца
начала периода конца периода
28 .02.2024 27.02.2025 36 6 Предпоследний Предпоследний
28.02.2025 27.02.2026 365 Последний Предпоследний
28.02.2026 27.02.2027 365 Последний Предпоследний
28.02.2027 27 .02.2028 365 Последний Пред-
предпоследний
28 .02.2028 27.02.2029 36 6 Предпоследний Предпоследний
Обращаю внимание, что в этом конкретном кейсе для обеспечения
детерминистичности функции расчета дат необходимо передавать еще дату начала
действия договора. Идеальный вариант, когда метод централизованно считает все
периоды сразу, вместо рекурсивного вызова для каждого периода. Пример кода,
рассчитывающий периоды (автор концепта - Денис Грачев):

DECLARE TYPE TPERIOD IS RECORD ( pbeg DATE, pend DATE ); vPeriod TPERIOD; TYPE
TPERIODTABLE IS TABLE OF TPERIOD; FUNCTION CalcPeriods(pBegin IN DATE, pEnd IN
DATE) RETURN TPERIODTABLE IS vResult TPERIODTABLE := TPERIODTABLE(); vBegin
DATE := TRUNC(pBegin); vEnd DATE := TRUNC(pEnd); vPeriod TPERIOD; vMonths
NUMBER := GREATEST(months_between(vEnd, vBegin), 1); -- Необходимо
корректировать 1 день в феврале високосного года NeedCorrectLeapYear BOOLEAN
:= (TO_CHAR(vBegin,'dd.mm') = '28.02') AND (TO_CHAR(vBegin,'mm') =
TO_CHAR(vBegin + 1,'mm')); BEGIN FOR cMonth IN 1..vMonths LOOP IF MOD(cMonth -
1, 12) = 0 THEN vPeriod.pbeg := add_months(vBegin, cMonth - 1); vPeriod.pend
:= add_months(vPeriod.pbeg, 12) - 1; IF vPeriod.pend > pEnd THEN vPeriod.pend
:= pEnd; end if; -- Исправление ошибки разбивки периодов многолетних договоров
IF (NeedCorrectLeapYear AND (TO_CHAR(vPeriod.pend,'dd.mm') = '28.02')) THEN
vPeriod.pend := vPeriod.pend - 1; END IF; vResult.extend;
vResult(vResult.last) := vPeriod; END IF; END LOOP; RETURN vResult; END
CalcPeriods; BEGIN NULL; END;

Это был последний прикол. Правда же?

Что ж, приветствуем на сцене тип данных• Информационные технологии » Информатика » Программирование » Программное обеспечение » Языки программирования » Типы данных

• Информационные технологии » Информационно-коммуникационные технологии » Информационные технологии и телекоммуникации » Программирование » Программное обеспечение » Языки программирования » Типы данных

• Высокие технологии » Информационные технологии и телекоммуникации » Программирование » Программное обеспечение » Языки программирования » Типы данных
INTERVAL Oracle• Информационные технологии » Информационно-коммуникационные технологии » Информационные технологии и телекоммуникации » Базы данных » Oracle

• Высокие технологии » Информационные технологии и телекоммуникации » Базы данных » Oracle
любезно падающий
ошибкой ORA-01843 на простом запросе:

SQL> Select TO_DATE('29-FEB-2024','DD-MON-YYYY') + TO_YMINTERVAL('1-0') As Res
From Dual; ORA-01843: месяц неверен

На этом примере видно, что даже в рамках одного диалекта языка
программирования
, для функций работы с датами, может быть совершенно разная
логика• Философия » Логика под капотом. Что уж говорить о не родственных языках, например
JavaScript и SQLпостроение непротиворечивого расчета дат потребует либо
высокой компетенции в обоих языках, либо качественных Unit-тестов.

А теперь-то все?

К сожалению, прямое и обратное преобразование периода в дату тоже не всегда
однородно.

Например, Months_Between дает разные значения в зависимости от месяцев,
которые сравниваются (запрос приведен для части месяца, для случая годовых
интервалов эту особенность игнорируют):

SQL> Select Months_between(TO_DATE('15.02.2023','DD.MM.YYYY'),
TO_DATE('01.02.2023','DD.MM.YYYY')) As Res From Dual; RES ----------
0,45161290 SQL> Select Months_between(TO_DATE('01.03.2023','DD.MM.YYYY'),
TO_DATE('16.02.2023','DD.MM.YYYY')) As Res From Dual; RES ----------
0,51612903

Бонусы

Для дочитавших до конца прикладываю годовые интервалы, которые портируются в
Unit-тесты. Таблица интервалов

Начало периода Конец периода Количество дней Дата начала Категория теста
договора високосного
года

28.02.2023 28 .02.2024 36 6 28.02.2023 Последний день
невисокосного
29. 02.2024 27.02.2025 365 28.02.2023 Последний день
високосного
28.02.2025 27.02.2026 365 28.02.2023/24 Последний день
невисокосного
28.02.2024 27.02.2025 36 6 28.02.2023/24 Предпоследний
день
високосного
28.02.2027 27.02.2028 365 28.02.2024 Последний день
невисокосного
при начале
периода в
високосном году
Внимательный читатель заметит, что первая и последняя строки таблицы дают
различный результат, если не привязываться к дате начала действия договора.
Так же, к обычным тестам на даты, целесообразно добавить еще и тесты на
непрерывность периодов.

А как оно в PostgreSQL?

С Oracle• Информационные технологии » Информационно-коммуникационные технологии » Информационные технологии и телекоммуникации » Базы данных » Oracle

• Высокие технологии » Информационные технологии и телекоммуникации » Базы данных » Oracle
понятно, есть вариант решения, есть баги, есть тесты – можно пилить
свою логику• Философия » Логика. А что делать тем, с кем случилось импортозамещение? И не простое,
а на PostgreSQL.

Здесь нам поможет уже привычный Extension OraFce – в котором есть реализация
Oracle• Информационные технологии » Информационно-коммуникационные технологии » Информационные технологии и телекоммуникации » Базы данных » Oracle

• Высокие технологии » Информационные технологии и телекоммуникации » Базы данных » Oracle
.Add_months со всей своей спецификой.

Что, конечно, не отменяет возможности написать свое собственное решение,
вытянуть его в микро/нано сервис и подвергнуть рефакторингу всю систему.

Итог

Мы посмотрели только вершину айсберга датской арифметикиработу с годовыми
интервалами. Для месячных интервалов нюансов больше (вспоминаем
Months_Between). И там одним простым костылем не обойтись.

В сухом остатке• Филология » Лингвистика » Русский язык » Русские фразеологизмы » Сухой Остаток, датская арифметика требует от исполнителя скрупулезности в
реализации и хорошего знания используемых методов языка программирования• Информационные технологии » Информатика » Программирование » Программное обеспечение » Языки программирования

• Информационные технологии » Информационно-коммуникационные технологии » Информационные технологии и телекоммуникации » Программирование » Программное обеспечение » Языки программирования

• Высокие технологии » Информационные технологии и телекоммуникации » Программирование » Программное обеспечение » Языки программирования
по
работе с датами. Хитрость на этом пути – использование UT. Если забить на
что-то из этого, то максимум через 4 года зеленое насекомое снова разбудит
тебя ночью падающим продом )

============= Итог: 3,7500 ; Философия#Логика#Вывод (рассуждение) 3,6875 ; Страхование#Ингосстрах 2,3438 ; Страхование#Договор страхования 3,3930 ; Информационные технологии#Информационно-коммуникационные технологии #Информационные технологии и телекоммуникации#Программирование #Программное обеспечение#Языки программирования#Типы данных #Структуры Данных#Класс (программирование) 2,4196 ; Информационные технологии#Информационно-коммуникационные технологии #Информационные технологии и телекоммуникации#Базы данных#Oracle 1,3330 ; Государство#Законы и право#Правовая система России#Нормативные акты #Федеральные законы РФ#Закон "Об Исполнительном Производстве" 1,3320 ; Отдых, развлечения#Развлекательные игры#Настольные игры#Карточные Игры #Шестьдесят шесть 1,3320 ; Филология#Лингвистика#Русский язык#Русские фразеологизмы#Сухой Остаток 1,2500 ; Физико-географические регионы#Африка

============= Объекты: законы Закон "Об Исполнительном Производстве" организации Ингосстрах МСГ ИНГО Страховые компании России

============= Связи: Страхование # ассоциации # Ингосстрах


Copyright © 2007-2024 ООО «RelTeam»