Java& Oracle都有一个名为Date的时间戳类型。开发人员倾向于操纵这些,就好像它们是 calendar 日期一样,我已经看到它会导致令人讨厌的一次性错误。
对于基本日期数量,您可以在输入时简单地切断时间部分,即降低精度。但如果您使用日期范围(例如: 9 / 29-9 / 30 ),则这两个值之间的差异是1天,而不是2.此外,范围比较需要1 )截断操作:start < trunc(now) <= end
,或2)算术:start < now < (end + 24hrs)
。不可怕,但不是DRY。
另一种方法是使用真正的时间戳: 9/29 00:00:00 - 10/1 00:00:00。 (午夜到午夜,因此不包括10月的任何部分)。现在持续时间本质上是正确的,范围比较更简单:start <= now < end
。对于内部处理当然更干净,但是在初始输入(+1)和输出(-1)时需要转换结束日期,假定用户级别的日历日期比喻。
您如何处理项目的日期范围?还有其他选择吗?我特别感兴趣的是如何在Java和Oracle方面处理这个问题。
答案 0 :(得分:7)
我们是如何做到的。
使用时间戳。
使用半开时间间隔进行比较: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”。
答案 1 :(得分:4)
Oracle拥有TIMESTAMP datatype。它存储DATE数据类型的年,月和日,加上小时,分钟,秒和小数秒值。
这是关于日期算术的thread on asktom.oracle.com。
答案 2 :(得分:3)
我是S.Lott解释的第二个。我们有一个广泛使用日期时间范围的产品套件,这是我们学习使用这样的范围的经验教训之一。顺便说一句,如果它不再是范围的一部分,我们称结束日期独占结束日期(IOW,半开放间隔)。相反,它是一个包含结束日期,如果它算作范围的一部分,只有在没有时间部分时才有意义。
用户通常期望包含日期范围的输入/输出。无论如何,请尽快将用户输入转换为独占结束日期范围,并在必须向用户显示时尽可能晚地转换任何日期范围。
在数据库中,始终存储独占结束日期范围。如果存在包含结束日期范围的旧数据,请尽可能在数据库上迁移它们,或者在读取数据时尽快转换为独占结束日期范围。
答案 3 :(得分:2)
我使用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或其他。
答案 4 :(得分:1)
我没有看到已发布的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。
你有很大的灵活性 将间隔值指定为 文字。请参阅“间隔 文字“有关详细信息 将间隔值指定为文字。 另请参阅“日期时间和间隔 示例“使用示例” 间隔。
答案 5 :(得分:0)
根据你的第一句话,你绊倒了Java的一个隐藏的“特征”(即错误):java.util.Date
本来应该是不可变的,但事实并非如此。 (Java 7承诺使用新的日期/时间API修复此问题。)几乎每个企业应用程序都依赖于各种temporal patterns,并且在某些时候您需要对日期和时间进行算术运算。
理想情况下,您可以使用Google日历使用的Joda time。如果你不能这样做,我想一个API包含java.util.Date
的包装器,其计算方法类似于Grails / Rails,以及一系列包装器(即一个指示开头和结尾的有序对)一段时间)就足够了。
在我目前的项目(HR计时应用程序)中,我们尝试将所有日期规范化为Oracle和Java的相同时区。幸运的是,我们的本地化要求是轻量级的(= 1个时区就足够了)。当持久对象不需要比一天更精确时,我们使用截止到午夜的时间戳。我会更进一步,并坚持将额外的毫秒丢弃到持久对象可以容忍的最粗糙的粒度(这将使您的处理更简单)。
答案 6 :(得分:0)
根据我的经验,有四种主要方法:
1)将日期转换为纪元整数(自1970年1月1日起的秒数),并将其作为整数存储在数据库中。
2)将日期转换为YYYYMMDDHHMMSS整数,并将其作为整数存储在数据库中。
3)将其存储为日期
4)将其存储为字符串
我一直坚持使用1和2,因为它使您能够使用日期执行快速简单的算术,而不依赖于底层数据库功能。
答案 7 :(得分:0)
通过将 getTime()的结果存储为长整数,可以将所有日期明确存储为GMT时间戳(即无时区或夏令时头痛)。
如果在数据库查询中需要进行日,周,月等操作,并且查询性能至关重要,则时间戳(标准化为更高的粒度而非毫秒)可以链接到具有列的日期分解表对于日,周,月等值,以便不必在查询中使用昂贵的日期/时间函数。
答案 8 :(得分:0)
如果需要时间戳,请将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和语言语法来处理问题的好处。
答案 9 :(得分:0)
更新:Joda-Time项目现在处于维护模式。它的团队建议迁移到Java内置的 java.time 类。
Joda-Time提供3个类来表示时间跨度:间隔,持续时间和周期。
ISO 8601标准规定了如何格式化表示Duration和Interval的字符串。 Joda-Time既解析又生成这样的字符串。
时区是一个至关重要的考虑因素。您的数据库应该以UTC格式存储其日期时间值。但是您的业务逻辑可能需要考虑时区。一天的开始&#34;取决于时区。顺便说一句,使用proper time zone names而不是3或4个字母代码。
correct answer by 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 );
转储到控制台...
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
答案 10 :(得分:-1)
我以毫秒为单位存储所有日期。我根本不使用timestamps / datetime字段。
所以,我必须操纵它多久。这意味着我不在我的SQL查询中使用'before','after','now'关键字。