Java <!> amp; Oracle都有一个名为Date的时间戳类型。开发人员倾向于操纵这些,就好像它们是 calendar 日期一样,我已经看到它会导致令人讨厌的一次性错误。

  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。 (午夜到午夜,因此不包括10月的任何部分)。现在持续时间本质上是正确的,范围比较更简单:start <= now < end。对于内部处理当然更干净,但是在初始输入(+1)和输出(-1)时需要转换结束日期,假定用户级别的日历日期比喻。

  3. 您如何处理项目的日期范围?还有其他选择吗?我特别感兴趣的是如何在Java和Oracle方面处理这个问题。

有帮助吗?

解决方案

我们是如何做到的。

  1. 使用时间戳。

  2. 使用半开间隔进行比较:start <= now < end

  3. 忽略那些坚持认为BETWEEN对SQL成功至关重要的抱怨者。

    通过这一系列日期范围非常容易审核。 9/30 to 10/1的数据库值包含一天(9/30)。下一个间隔的开始必须等于前一个间隔的结束。 interval[n-1].end == interval[n].start规则对审计很方便。

    当您显示时,如果需要,您可以显示格式化的startend - 1。事实证明,你可以教育人们理解<!> quot; end <!> quot;实际上是规则不再正确的第一天。所以<!>“9/30到10/1 <!>”;表示<!>有效;从9/30开始有效,不再有效,从10/1 <!>开始。

其他提示

我是S.Lott解释的第二个。我们有一个广泛使用日期时间范围的产品套件,这是我们学习使用这样的范围的经验教训之一。顺便说一句,如果它不再是范围的一部分,我们称结束日期独占结束日期(IOW,半开放间隔)。相反,它是一个包含结束日期,如果它算作范围的一部分,只有在没有时间部分时才有意义。

用户通常期望包含日期范围的输入/输出。无论如何,请尽快将用户输入转换为独占结束日期范围,并在必须向用户显示时尽可能晚地转换任何日期范围。

在数据库中,始终存储独占结束日期范围。如果存在包含结束日期范围的旧数据,请尽可能在数据库上迁移它们,或者在读取数据时尽快转换为独占结束日期范围。

我使用Oracle的日期数据类型,并教育开发人员了解影响边界条件的时间组件问题。

数据库约束还可以防止意外指定应该没有的列中的时间组件,并且还告诉优化器没有任何值具有时间组件。

例如,约束CHECK(MY_DATE = TRUNC(MY_DATE))会阻止将00:00:00以外的时间值放入my_date列,并且还允许Oracle推断出谓词如MY_DATE = TO_DATE('2008-09-12 15:00:00')将永远不会成立,因此不会从表中返回任何行,因为它可以扩展为:

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之间有多少合法值? 2007年1月31日至2008年1月1日期间如何?它还允许输入非法值,例如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或其他。

我没有看到已发布的Interval数据类型。

Oracle也有针对您的确切方案的数据类型。 Oracle中也有间隔年数和间隔日数到第二种数据类型。

来自10gR2文档。

  

每年的间隔期间会有一段时间   使用YEAR和MONTH的时间   日期时间字段。这个数据类型是   用于表示差异   仅在两个日期时间值之间   年和月的价值是   显著。

     

间隔年[(year_precision)] TO   个月

     

其中year_precision是数字   YEAR日期时间字段中的数字。该   year_precision的默认值为2。

     

INTERVAL DAY TO SECOND数据类型

     

INTERVAL DAY TO SECOND存储一段时间   时间,天,小时,   分钟和秒。这个数据类型是   用于表示精确的   两个日期时间之间的差异   值。

     

指定此数据类型,如下所示:

     

INTERVAL DAY [(day_precision)] TO   第二   [(小数秒)]

     

,其中

     

day_precision是位数   在DAY datetime字段中。公认   值为0到9.默认值为2.

     

fractional_seconds_precision就是   小数位数   SECOND日期时间字段的一部分。   可接受的值为0到9   默认值为6。

     

你有很大的灵活性   将间隔值指定为   文字。请参阅<!>“Interval   <!>文字QUOT;有关的详细信息   将间隔值指定为文字。   另请参阅<!> quot;日期时间和间隔   <!>实例QUOT;举个例子   间隔。

根据我的经验,有四种主要方法:

1)将日期转换为纪元整数(自1970年1月1日起的秒数),并将其作为整数存储在数据库中。

2)将日期转换为YYYYMMDDHHMMSS整数,并将其作为整数存储在数据库中。

3)将其存储为日期

4)将其存储为字符串

我一直坚持使用1和2,因为它使您能够使用日期执行快速简单的算术,而不依赖于底层数据库功能。

根据你的第一句话,你绊倒了一个隐藏的<!>“功能<!> Java的那些(即错误):java.util.Date应该是不可变的,但事实并非如此。 (Java 7承诺使用新的日期/时间API来解决这个问题。)几乎每个企业应用程序都依赖于各种 temporal模式,在某些时候你需要对日期和时间进行算术运算。

理想情况下,您可以使用Google日历使用的 Joda时间。如果你不能这样做,我想一个API包含一个围绕<=>的包装器,其计算方法类似于Grails / Rails,以及一系列包装器(即一个指示时间开始和结束的有序对)期间)就足够了。

在我目前的项目(HR计时应用程序)中,我们尝试将所有日期规范化为Oracle和Java的相同时区。幸运的是,我们的本地化要求是轻量级的(= 1个时区就足够了)。当持久对象不需要比一天更精确时,我们使用截止到午夜的时间戳。我会更进一步,并坚持将额外的毫秒丢弃到持久对象可以容忍的最粗糙的粒度(这将使您的处理更简单)。

通过将 getTime()的结果存储为长整数,可以将所有日期明确存储为GMT时间戳(即无时区或夏令时头痛)。

如果在数据库查询中需要进行日,周,月等操作,并且查询性能至关重要,则时间戳(标准化为更高的粒度而非毫秒)可以链接到具有列的日期分解表对于日,周,月等值,以便不必在查询中使用昂贵的日期/时间函数。

艾伦是对的 - 乔达时间很棒。 java.util.Date和Calendar只是一个耻辱。

如果需要时间戳,请将oracle日期类型与时间一起使用,将列命名为_tmst之类的后缀。当你将数据读入java时,将其转换为joda time DateTime对象。确保时区是正确的,考虑到oracle中存在特定的数据类型,它们将时间戳存储在时区中。或者,您可以在表中创建另一列以存储时区ID。时区ID的值应为时区的标准全名ID,请参阅 http://java.sun.com/j2se/1.4.2/docs/api/java/util/TimeZone.html#getTimeZone%28java.lang.String% 29 。如果您为TZ dta使用另一列,那么当您将数据读入java时,请使用DateTime对象,但使用.withZoneRetainFields在DateTime对象上设置时区以设置时区。

如果您只需要日期数据(没有时间戳),那么请立即使用数据库中的日期类型。再说一遍。在这种情况下,使用jodatime中的DateMidnight对象。

底线:利用数据库的类型系统和您正在使用的语言。了解它们并获得使用富有表现力的api和语言语法来处理问题的好处。

更新:Joda-Time项目现在处于维护模式。它的团队建议迁移到Java内置的 java.time 类。

约达时间

Joda-Time提供3个类来表示时间跨度:间隔,持续时间和周期。

ISO 8601标准规定了如何格式化代表持续时间的字符串和< a href =“https://en.wikipedia.org/wiki/ISO_8601#Time_intervals”rel =“nofollow noreferrer”>间隔。 Joda-Time既解析又生成这样的字符串。

时区是一个至关重要的考虑因素。您的数据库应该以UTC格式存储其日期时间值。但是您的业务逻辑可能需要考虑时区。 <!>“天的开始<!>取决于时区。顺便说一下,使用正确的时区名称而不是3或4个字母代码。

S.Lott的正确答案明智地建议使用半开逻辑,因为这通常最适合日期-兼职工作。一段时间的开始是包含,而结尾是独占。 Joda-Time在其方法中使用了半开逻辑。

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 );

转储到控制台<!>#8230;

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 );

运行<!>#8230;

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查询中使用'before','after','now'关键字。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top