我们如何存储日期范围,它实际上是时间戳

时间:2020-03-06 14:57:32  来源:igfitidea点击:

Java和Oracle都具有一个称为Date的时间戳类型。开发人员倾向于将它们当作日历日期来处理,我已经看到它们会引起讨厌的一次性错误。

  • 对于基本日期量,我们可以在输入时简单地截去时间部分,即降低精度。但是,如果我们使用日期范围(例如:9 / 29-9 / 30)执行此操作,则这两个值之间的差为1天而不是2天。此外,范围比较需要以下两者之一:1)截断操作: <trunc(now)<= end,或者2)算术:start <now <(end + 24hrs)`。不可怕,但不干燥。
  • 另一种选择是使用真实时间戳记:9/29 00:00:00-10/1 00:00:00。 (午夜至午夜,因此不包括十月的任何部分)。现在持续时间本质上是正确的,并且范围比较更简单:start &lt;= now &lt;end。当然,对于内部处理而言,它更干净,但是,结束日期的确需要在初始输入(+1)和输出(-1)时进行转换,假定在用户级别具有日历日期隐喻。

我们如何处理项目的日期范围?还有其他选择吗?我对我们如何在等式的Java和Oracle方面都特别感兴趣。

解决方案

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

理想情况下,我们可以使用Google日历使用的Joda时间。如果我们不能做到这一点,我猜想一个API包括一个围绕着java.util.Date的包装器,该包装器具有类似于Grails / Rails的计算方法,并且包含一个范围的包装器(即指示开始和结束的有序对时间段结束)就足够了。

在我当前的项目(HR计时应用程序)上,我们尝试将Oracle和Java的所有日期标准化为相同的时区。幸运的是,我们的本地化要求是轻量级的(= 1个时区就足够了)。当持久对象不需要比一天更好的精度时,我们使用从午夜开始的时间戳。我会走得更远,坚持要把持久对象可以忍受的最粗粒度扔掉额外的毫秒(这会使处理变得更简单)。

我使用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年12月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或者任何其他值。

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

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

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

3)将其存储为日期

4)将其存储为字符串

我一直坚持使用1和2,因为它使我们可以对日期进行快速而简单的算术运算,而不必依赖基础数据库功能。

Oracle具有TIMESTAMP数据类型。它存储DATE数据类型的年,月和日,以及时,分,秒和小数秒值。

这是asktom.oracle.com上有关日期算术的一个线程。

我存储所有日期(以毫秒为单位)。我根本不使用时间戳记/日期时间字段。

因此,我必须操纵它很长时间。这意味着我在SQL查询中不使用"之前","之后","现在"关键字。

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

在数据库查询中需要进行日,周,月等操作的情况下,并且当查询性能至关重要时,可以将时间戳(规范化为比毫秒更高的粒度)链接到具有当天列的日期细分表,星期,月份等值,这样就不必在查询中使用昂贵的日期/时间函数。

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

如果我们需要时间戳记,请在时间中使用oracle日期类型,并使用诸如_tmst之类的后缀来命名该列。当我们将数据读入java时,将其放入joda时间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设置Datezone的DateTime对象上的时区。

如果只需要日期数据(没有时间戳),则可以无时间使用数据库中的日期类型。再次命名。在这种情况下,请使用jodatime中的DateMidnight对象。

最重要的是:利用数据库的类型系统和我们使用的语言。学习它们,并获得具有表达性的api和语言语法来解决问题的好处。

这是我们的方法。

  • 使用时间戳。
  • 使用半开间隔进行比较:"开始<=现在<结束"。

忽略那些坚持认为BETWEEN对于成功的SQL必不可少的抱怨。

有了这个一系列的日期范围,真的很容易审核。 " 9/30到10/1"的数据库值包含一天(9/30)。下一个间隔的开始必须等于上一个间隔的结束。该" interval [n-1] .end == interval [n] .start"规则对于审核很方便。

显示时,如果需要,可以显示格式化的"开始"和"结束" -1. 事实证明,我们可以教育人们以了解"结束"实际上是规则不再成立的第一天。因此," 9/30到10/1"表示"有效的起始9/30,不再有效的起始10/1"。

我还没有看到Interval数据类型。

Oracle还具有适合我们实际情况的数据类型。 Oracle中也有INTERVAL YEAR TO MONTH和INTERVAL DAYTO SECOND数据类型。

来自10gR2文档。

INTERVAL YEAR TO MONTH stores a period
  of time using the YEAR and MONTH
  datetime fields. This datatype is
  useful for representing the difference
  between two datetime values when only
  the year and month values are
  significant.
  
  INTERVAL YEAR [(year_precision)] TO
  MONTH
  
  where year_precision is the number of
  digits in the YEAR datetime field. The
  default value of year_precision is 2.
  
  INTERVAL DAY TO SECOND Datatype
  
  INTERVAL DAY TO SECOND stores a period
  of time in terms of days, hours,
  minutes, and seconds. This datatype is
  useful for representing the precise
  difference between two datetime
  values.
  
  Specify this datatype as follows:
  
  INTERVAL DAY [(day_precision)]     TO
  SECOND
  [(fractional_seconds_precision)]
  
  where
  
  day_precision is the number of digits
  in the DAY datetime field. Accepted
  values are 0 to 9. The default is 2.
  
  fractional_seconds_precision is the
  number of digits in the fractional
  part of the SECOND datetime field.
  Accepted values are 0 to 9. The
  default is 6.
  
  You have a great deal of flexibility
  when specifying interval values as
  literals. Please refer to "Interval
  Literals" for detailed information on
  specify interval values as literals.
  Also see "Datetime and Interval
  Examples" for an example using
  intervals.

我赞同S.Lott的解释。我们有一个产品套件,可以广泛使用日期时间范围,这是我们学习过的与这样的范围一起使用的经验教训之一。顺便说一句,如果不再是结束日期(IOW,半开放时间间隔),我们将其称为结束日期专有结束日期。相反,如果它算作范围的一部分,则是一个包容性的结束日期,只有在没有时间部分的情况下才有意义。

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

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