Как вы храните диапазоны дат, которые на самом деле являются временными метками

StackOverflow https://stackoverflow.com/questions/156032

Вопрос

Java и Oracle имеют временная метка введите так называемую Дату.Разработчики склонны манипулировать ими так, как если бы они были Календарь даты, которые, как я видел, вызывают неприятные одноразовые ошибки.

  1. Для базового количества дат вы можете просто обрезать временную часть при вводе, т. е. уменьшить точность.Но если вы сделаете это с диапазоном дат, (например: 9/29-9/30), разница между этими двумя значениями составляет 1 день, а не 2.Кроме того, для сравнения диапазонов требуется либо 1) операция усечения: start < trunc(now) <= end, или 2) арифметика: start < now < (end + 24hrs).Не ужасно, но и не СУХОЙ.

  2. Альтернативой является использование истинных временных меток: 9/29 00:00:00 - 10/1 00:00:00.(с полуночи до полуночи, поэтому не включает в себя какую-либо часть октября).Теперь значения длительности изначально корректны, а сравнение диапазонов стало проще: start <= now < end.Конечно, более чистый для внутренней обработки, однако даты окончания необходимо преобразовать при первоначальном вводе (+ 1) и для вывода (-1), предполагая метафору календарной даты на уровне пользователя.

Как вы обрабатываете диапазоны дат в своем проекте?Есть ли другие альтернативы?Меня особенно интересует, как вы справляетесь с этим как на Java, так и на Oracle сторонах уравнения.

Это было полезно?

Решение

Вот как мы это делаем.

  1. Используйте временные метки.

  2. Используйте полуоткрытые интервалы для сравнения: start <= now < end.

Не обращайте внимания на нытиков, которые настаивают на том, что BETWEEN каким-то образом необходим для успешного SQL.

Благодаря этому действительно легко провести аудит ряда диапазонов дат.Значение базы данных для 9/30 to 10/1 охватить один день (9/30).Начало следующего интервала должно совпадать с окончанием предыдущего интервала.Это interval[n-1].end == interval[n].start правило удобно для аудита.

При отображении, если вы хотите, вы можете отобразить отформатированное start и end-1.Оказывается, вы может учите людей понимать, что "конец" - это на самом деле первый день, когда правило перестает действовать.Таким образом, "с 9/30 по 10/1" означает "действительный начиная с 9/30, больше не действительный начиная с 10/1".

Другие советы

У Oracle есть Тип данных ВРЕМЕННОЙ МЕТКИ.В нем хранятся год, месяц и день типа данных DATE, а также значения часов, минут, секунд и долей секунды.

Вот такой резьба на asktom.oracle.com об арифметике дат.

Я поддерживаю то, что объяснил С.Лотт.У нас есть набор продуктов, который широко использует диапазоны дат и времени, и это был один из наших уроков работы с подобными диапазонами.Кстати, мы называем дату окончания Эксклюзив дата окончания, если она больше не входит в диапазон (IOW, полуоткрытый интервал).Напротив, это инклюзивный конечная дата, если она считается частью диапазона, что имеет смысл только в том случае, если нет временной части.

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

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

Я использую тип данных Oracle date и обучаю разработчиков проблеме временных компонентов, влияющих на граничные условия.

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

Например, ПРОВЕРКА ограничения (MY_DATE=TRUNC(MY_DATE)) предотвращает помещение значения со временем, отличным от 00:00:00, в столбец my_date, а также позволяет Oracle сделать вывод, что такой предикат, как MY_DATE = TO_DATE('2008-09-12 15:00:00') никогда не будет иметь значения true, и, следовательно, никакие строки не будут возвращены из таблицы, поскольку она может быть расширена до:

MY_DATE = TO_DATE('2008-09-12 15:00:00') AND
TO_DATE('2008-09-12 15:00:00') = TRUNC(TO_DATE('2008-09-12 15:00:00'))

Конечно, это автоматически равно false.

Хотя иногда возникает соблазн хранить даты в виде чисел, таких как 20080915, это может вызвать проблемы с оптимизацией запросов.Например, сколько юридических значений существует между 20,071,231 и 20,070,101?Как насчет промежутка между датами 31 декабря 2007 года и 01 января 2008 года?Он также позволяет вводить недопустимые значения, такие как 20070100.

Итак, если у вас есть даты без временных компонентов, то определить диапазон становится проще простого:

select ...
from   ...
where  my_date Between date '2008-01-01' and date '2008-01-05'

При наличии временного компонента вы можете выполнить одно из следующих действий:

select ...
from   ...
where  my_date >= date '2008-01-01' and
       my_date  < date '2008-01-06'

или

select ...
from   ...
where  my_date Between date '2008-01-01'
                   and date '2008-01-05'-(1/24/60/60)

Обратите внимание на использование (1/24/60/60) вместо магического числа.В Oracle довольно часто выполняется арифметика даты путем добавления определенных долей дня ...3/24 в течение трех часов, 27/24/60 в течение 27 минут.Математика Oracle этого типа является точной и не допускает ошибок округления, поэтому:

select 27/24/60 from dual;

...дает 0,01875, а не 0,01874999999999 или что-то еще.

Я еще не вижу опубликованных интервальных типов данных.

У Oracle также есть типы данных для вашего конкретного сценария.В Oracle также существуют типы данных С ИНТЕРВАЛОМ ОТ ГОДА До МЕСЯЦА и С ИНТЕРВАЛОМ ОТ ДНЯ До СЕКУНДЫ.

Из документов 10gR2.

ИНТЕРВАЛ ОТ ГОДА До МЕСЯЦА хранит период времени, используя поля ГОД и МЕСЯЦ дата-время.Этот тип данных полезен для представления разница между двумя значениями datetime, когда только год и месяц значения значительное.

ИНТЕРВАЛ ОТ ГОДА [(year_precision)] До МЕСЯЦА

где year_precision - это количество цифр в поле Дата-время ГОДА. Значение year_precision по умолчанию равно 2.

ИНТЕРВАЛ ОТ ДНЯ ДО ВТОРОГО типа данных

ИНТЕРВАЛ ОТ ДНЯ До СЕКУНДЫ хранит период времени в виде дней, часов, минут и секунд.Этот тип данных полезен для представления точной разницы между двумя значениями datetime .

Укажите этот тип данных следующим образом:

ИНТЕРВАЛ DAY [(day_precision)] Для СЕКУНДА [(фракционная точность)]

где

day_precision - это количество цифр в поле ДЕНЬ, дата и время.Допустимые значения от 0 до 9.Значение по умолчанию равно 2.

fractional_seconds_precision - это количество цифр в дробной части ВТОРОГО поля даты и времени.Допустимые значения - от 0 до 9.Значение по умолчанию равно 6.

У вас есть большая гибкость при указании значений интервалов в виде литералов.Пожалуйста, обратитесь к разделу "Литералы интервала " для получения подробной информации о том, как указывать значения интервала в виде литералов.Также смотрите "Примеры даты, времени и интервала " для примера использования интервалов.

Основываясь на моем опыте, есть четыре основных способа сделать это:

1) Преобразуйте дату в целое число эпохи (секунды с 1 января 1970 года) и сохраните ее в базе данных как целое число.

2) Преобразуйте дату в целое число YYYYMMDDHHMMSS и сохраните ее в базе данных как целое число.

3) Сохраните его как дату

4) Сохраните его в виде строки

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

Основываясь на вашем первом предложении, вы натыкаетесь на одну из скрытых "особенностей" (т.е.ошибки) Java: java.util.Date должно было быть неизменяемым, но это не так.(Java 7 обещает исправить это с помощью нового API даты / времени.) Почти каждое корпоративное приложение рассчитывает на различные временные паттерны, и в какой-то момент вам нужно будет произвести арифметические расчеты по дате и времени.

В идеале вы могли бы использовать Время Джоды, который используется Google Calendar.Если вы не можете этого сделать, я предполагаю, что это API, который состоит из оболочки вокруг java.util.Date с вычислительными методами, аналогичными Grails / Rails, и диапазоном вашей оболочки (т.е.упорядоченной пары, указывающей начало и конец периода времени) будет достаточно.

В моем текущем проекте (приложение для учета рабочего времени HR) мы пытаемся привести все наши даты к одному и тому же часовому поясу как для Oracle, так и для Java.К счастью, наши требования к локализации невелики (= достаточно 1 часового пояса).Когда постоянному объекту не требуется более высокая точность, чем сутки, мы используем временную метку, начиная с полуночи.Я бы пошел дальше и настаивал на том, чтобы потратить дополнительные миллисекунды на максимально грубую детализацию, которую может выдержать постоянный объект (это упростит вашу обработку).

Все даты могут быть однозначно сохранены в виде временных меток GMT (т. е.отсутствие часового пояса или проблем с переходом на летнее время) путем сохранения результата Время получения() как длинное целое число.

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

Алан прав - время Джоды - это здорово.java.util.Дата и календарь - это просто позор.

Если вам нужны временные метки, используйте тип даты oracle с указанием времени, назовите столбец каким-нибудь суффиксом, например _tmst .Когда вы считываете данные в java, преобразуйте их в объект joda time DateTime.чтобы убедиться в правильности часового пояса, учтите, что в oracle существуют определенные типы данных, которые будут хранить временные метки вместе с часовым поясом.Или вы можете создать другой столбец в таблице для хранения идентификатора часового пояса.Значения для идентификатора часового пояса должны быть стандартными полное имя ИДЕНТИФИКАТОР для часовых поясов см. http://java.sun.com/j2se/1.4.2/docs/api/java/util/TimeZone.html#getTimeZone%28java.lang .Строка%29 .Если вы используете другой столбец для TZ dta, то при чтении данных в java используйте объект DateTime, но установите часовой пояс в объекте DateTime, используя .withZoneRetainFields для установки часового пояса.

Если вам нужны только данные о дате (без метки времени), тогда используйте тип даты в базе данных без указания времени.еще раз назовите это хорошо.в этом случае используйте объект DateMidnight из jodatime.

итог:используйте систему типов базы данных и язык, который вы используете.Изучите их и воспользуйтесь преимуществами наличия выразительного api и языкового синтаксиса для решения вашей проблемы.

Обновить:Проект Joda-Time сейчас находится в режиме технического обслуживания.Его команда консультирует по вопросам миграции в java.время классы, встроенные в Java.

Джода-Время

Joda-Time предлагает 3 класса для представления определенного периода времени:Интервал, Продолжительность и периодичность.

Стандарт ISO 8601 определяет, как форматировать строки, представляющие Продолжительность и еще Интервал.Joda-Time как анализирует, так и генерирует такие строки.

Часовой пояс является важным фактором.Ваша база данных должна хранить свои значения даты и времени в формате UTC.Но в вашей бизнес-логике может потребоваться учитывать часовые пояса.Начало "дня" зависит от часового пояса.Кстати, используйте правильные названия часовых поясов вместо 3-х или 4-х буквенных кодов.

Тот Самый правильный ответ С. Лотта мудро советует использовать полуоткрытую логику, поскольку это обычно лучше всего работает для работы с датой и временем.Началом определенного отрезка времени является инклюзивный в то время как финал таков Эксклюзив.Joda-Time использует полуоткрытую логику в своих методах.

diagram defining a week as greater than or equal to Day 1 and less than Day 8

DateTimeZone timeZone_NewYork = DateTimeZone.forID( "America/New_York" );
DateTime start = new DateTime( 2014, 9, 29, 15, 16, 17, timeZone_NewYork );
DateTime stop = new DateTime( 2014, 9, 30, 1, 2, 3, timeZone_NewYork );

int daysBetween = Days.daysBetween( start, stop ).getDays();

Period period = new Period( start, stop );

Interval interval = new Interval( start, stop );
Interval intervalWholeDays = new Interval( start.withTimeAtStartOfDay(), stop.plusDays( 1 ).withTimeAtStartOfDay() );

DateTime lateNight29th = new DateTime( 2014, 9, 29, 23, 0, 0, timeZone_NewYork );
boolean containsLateNight29th = interval.contains( lateNight29th );

Сброс на консоль…

System.out.println( "start: " + start );
System.out.println( "stop: " + stop );
System.out.println( "daysBetween: " + daysBetween );
System.out.println( "period: " + period ); // Uses format: PnYnMnDTnHnMnS
System.out.println( "interval: " + interval );
System.out.println( "intervalWholeDays: " + intervalWholeDays );
System.out.println( "lateNight29th: " + lateNight29th );
System.out.println( "containsLateNight29th: " + containsLateNight29th );

Когда запускаешь…

start: 2014-09-29T15:16:17.000-04:00
stop: 2014-09-30T01:02:03.000-04:00
daysBetween: 0
period: PT9H45M46S
interval: 2014-09-29T15:16:17.000-04:00/2014-09-30T01:02:03.000-04:00
intervalWholeDays: 2014-09-29T00:00:00.000-04:00/2014-10-01T00:00:00.000-04:00
lateNight29th: 2014-09-29T23:00:00.000-04:00
containsLateNight29th: true

Я сохраняю все даты в миллисекундах.Я вообще не использую поля timestamps / datetime.

Итак, я должен манипулировать им как можно дольше.Это означает, что я не использую ключевые слова "до", "после", "сейчас" в своих sql-запросах.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top