Как вы храните диапазоны дат, которые на самом деле являются временными метками - PullRequest
7 голосов
/ 01 октября 2008

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

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

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

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

Ответы [ 11 ]

7 голосов
/ 01 октября 2008

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

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

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

Не обращайте внимания на нытиков, которые настаивают на том, что МЕЖДУ как-то необходимо для успешного 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».

4 голосов
/ 01 октября 2008

Oracle имеет тип данных TIMESTAMP . В нем хранятся год, месяц и день типа данных DATE, а также значения часов, минут, секунд и доли секунды.

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

3 голосов
/ 16 ноября 2008

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

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

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

2 голосов
/ 01 октября 2008

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

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

Например, ограничение CHECK (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'))

Это автоматически ложно, конечно.

Хотя иногда бывает заманчиво хранить даты в виде чисел, например 20080915, это может вызвать проблемы с оптимизацией запросов. Например, сколько допустимых значений существует между 20 071 231 и 20 070 101? Как насчет дат 31 декабря 2007 года и 1 января 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 или что-либо еще.

1 голос
/ 01 октября 2008

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

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

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

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

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

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

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

INTERVAL DAY TO SECOND хранит период времени в днях, часах, минуты и секунды. Этот тип данных полезно для представления точного разница между двумя датами и временем значения.

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

ИНТЕРВАЛЬНЫЙ ДЕНЬ [(day_precision)] TO ВТОРОЙ [(Fractional_seconds_precision)]

, где

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

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

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

0 голосов
/ 23 марта 2014

ОБНОВЛЕНИЕ: проект Joda-Time сейчас находится в режиме обслуживания. Его команда советует перейти на классы java.time , встроенные в Java.

Joda-Time

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

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

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

Правильный ответ от S.Lott мудро советует использовать полуоткрытую логику, поскольку она обычно лучше всего подходит для работы с датой и временем. Начало промежутка времени включительно , а окончание исключительно . 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
0 голосов
/ 01 октября 2008

Алан прав - время у Йоды отличное. java.util.Date и Calendar - просто позор.

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

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

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

0 голосов
/ 01 октября 2008

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

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

0 голосов
/ 01 октября 2008

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

Итак, я должен манипулировать этим как долго. Это означает, что я не использую ключевые слова «до», «после», «сейчас» в моих SQL-запросах.

0 голосов
/ 01 октября 2008

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

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

2) Преобразовать дату в целое число ГГГГММДДЧЧММСС и сохранить ее в базе данных как целое число.

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

4) Сохранить как строку

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

...