【问题标题】:Storing a "fake" timestamp into a database将“假”时间戳存储到数据库中
【发布时间】:2015-09-25 13:27:14
【问题描述】:

这是我要解决的问题:从数据库 A 读取字符串,将字符串转换为 Date 对象,将 Date 对象存储到数据库 B。

EX) 数据库A:从数据库A读入日期字符串“2015-03-08 02:00:00”,转换成Date对象,存回数据库B。

这里出现的问题是因为 2:00 AM 是美国中部时间 DST 的开始,所以 Data 对象将 2:00 AM 直接转换为 3:00 AM,这意味着 3:00 AM 被存储到数据库 B.

有什么办法可以解决这个问题吗?如有必要,我不反对使用 Joda Time。

我正在努力关注上述日期,2015-03-08 02:00:00

这是我正在使用的代码:

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S");
    sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
    String date = "2015-03-08 02:00:00.0";



    try 
    {
        d = sdf.parse(date);
        sdf.format(d);

        //Insert into database here
        // ---
        //
    } 
    catch (ParseException e) 
    {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

【问题讨论】:

  • 为什么?您是否试图在不考虑任何特定时区的情况下存储该日期凌晨 2 点的想法?一个例子是“圣诞节在 12 月 25 日午夜过后开始”,这意味着每当午夜在 任何 时区敲响。让我换一种说法,您使用的是什么数据库以及究竟列的数据类型是什么?
  • @BasilBourque 我正在从 Oracle 数据库中读取数据,对新读取的信息执行 Java 处理,并尝试将其存储到 Java 内存数据库中。
  • 你的 exact 两个数据库中日期时间列的数据类型呢?

标签: java date calendar timezone jodatime


【解决方案1】:

您有多个问题交织在一起。

您不应该从数据库中读取日期时间值的字符串,而应该读取日期时间对象。 StackOverflow 上有很多关于从/向数据库读取/写入日期时间值的问题,因此无需在此重复。

如果您确实有一个字符串,例如“2015-03-08 02:00:00”,请注意缺少任何时区或偏移量指示符。如果您想假设该字符串表示特定于美国中部时间的时间,那么您必须接受这样一个事实,即没有这样的日期时间,因为夏令时 (DST) 将其定义为凌晨 3 点。在凌晨 2 点的行程中,时间标记跳到凌晨 2 点。因此,尝试获取这样一个不存在的日期时间是没有意义的。

使用正确的时区名称

日期时间工作的重要提示:避免将时区视为“中部时间”和“CST”等 3-4 个字母代码。这些都不是标准化的,也不是唯一的(许多重复),并且进一步混淆了夏令时的混乱。使用proper time zone,以“continent/majorCityOrRegion”的模式。

本地日期时间

也许您的意思是我们所说的“本地时间”,其中日期时间并不特定于任何一个时区。例如,“圣诞节从 2015 年 12 月 25 日午夜开始”。这意味着每个特定时区的不同时刻。例如,巴黎的圣诞节比蒙特利尔早。

乔达时间

让我们在 Joda-Time 中将该字符串解释为 LocalDateTime。首先,为方便起见,我们将 SPACE 替换为“T”,以利用 Joda-Time 针对ISO 8601 格式的内置解析器。

String input = "2015-03-08 02:00:00";
String inputStandardized = input.replace( " ", "T" );  // For convenience, convert input text to comply with ISO 8601 standard’s canonical format. Replace SPACE between date & time portions with "T".

接下来我们解析那个标准化的字符串。

LocalDateTime localDateTime = LocalDateTime.parse( inputStandardized );

转储到控制台。

System.out.println( "inputStandardized: " + inputStandardized );
System.out.println( "localDateTime: " + localDateTime );

运行时。

inputStandardized: 2015-03-08T02:00:00
localDateTime: 2015-03-08T02:00:00.000

可以使用SQL type TIMESTAMP WITHOUT TIME ZONE 将此本地日期时间存储在 SQL 数据库中。这种类型意味着在获取 (SELECT) 或放置 (INSERT / UPDATE) 数据库值时不会对 UTC 时区进行调整。有关这些 SQL 类型的更多信息,请参阅Postgres doc

分区日期时间

如果您要表示特定时区中的特定时刻,例如America/Chicago,则我们需要分配该时区。对于这种特定于时区的值,在您的数据库中,您将使用数据类型TIMESTAMP WITH TIME ZONE。该类型名称具有误导性——它表示尊重时区,因为它将传入数据调整为 UTC。数据的原始时区随即丢失。

不幸的是,这是 Joda-Time 让我们失望的少数情况之一。 Joda-Time 没有进行调整,而是拒绝,抛出异常。 ☹

自己看吧……让我们将以下代码添加到上面的示例代码中。

DateTimeZone zone = DateTimeZone.forID( "America/Chicago" );
DateTime dateTimeChicago = localDateTime.toDateTime( zone ); // If the input lacks an offset, then Joda-Time *assigns* the value the specified time zone. If the input has an offset, Joda-Time *adjusts* the value to the specified zone.

转储到控制台。

System.out.println( "zone: " + zone );
System.out.println( "dateTime: " + dateTimeChicago );

运行时。

Exception in thread "main" org.joda.time.IllegalInstantException: Illegal instant due to time zone offset transition (daylight savings time 'gap'): 2015-03-08T02:00:00.000 (America/Chicago
…

似乎没有好的通用解决方法,只有 hacks。基本上,如果您期望某个时区,您可以自己进行调整。请参阅 thisthisthisthe Joda-Time FAQ 等讨论。

java.time

在 Java 8 及更高版本中,我们在 java.time package (Tutorial) 中有新的内置日期时间框架。该框架受到 Joda-Time 的启发,与 Joda-Time 相比具有一些优势。这些优势之一是处理这个 DST 不存在值问题。

String input = "2015-03-08 02:00:00";
String inputStandardized = input.replace( " ", "T" );  

LocalDateTime localDateTime = LocalDateTime.parse( inputStandardized );

让我们调整本地日期时间以分配特定时区。 java.time 框架检测到不存在的日期时间并自动将时间向前滑动以遵守 DST 转换。

ZoneId zone = ZoneId.of( "America/Chicago" );
ZonedDateTime zdt = ZonedDateTime.of( localDateTime, zone );

转储到控制台。

System.out.println("inputStandardized: " + inputStandardized );
System.out.println("localDateTime: " + localDateTime );
System.out.println("zone: " + zone );
System.out.println("zdt: " + zdt );

运行时。

inputStandardized: 2015-03-08T02:00:00
localDateTime: 2015-03-08T02:00
zone: America/Chicago
zdt: 2015-03-08T03:00-05:00[America/Chicago]

SQL

如上所述,您可以搜索 StackOveflow 以获取有关将日期时间进出数据库的更多信息。

理想情况下,使用 java.time,您可以直接将 LocalDateTimeZonedDateTime 提供给您的 JDBC 驱动程序。但大多数驱动程序尚未更新以处理 java.time 类型。在您的驱动程序更新之前,请使用 java.sql.* classes。在与 Java 捆绑的新旧类中都可以找到方便的转换方法。

java.sql.Timestamp ts = java.sql.Timestamp.valueOf( localDateTime );

……或者……

Instant instant = zdt.toInstant();
java.sql.Timestamp ts = java.sql.Timestamp.from( instant );

【讨论】:

  • 很好的答案——尤其是关于 Java 8 的 java.time 的部分。
【解决方案2】:

通常最好将日期存储为自纪元以来的毫秒数。这样,您可以使用 long 将数字存储在数据库中,当您需要格式化日期时,您可以使用 Joda Time 的 DateTime(long) 构造函数或仅使用内置的 Date(long) 构造函数。

【讨论】:

  • 我不同意。如果您的数据库具有日期时间数据类型,请使用它们。如果您的程序有不错的日期时间框架,请使用它。尝试使用 count-from-epoch 进行日期时间工作容易出错、难以调试且令人沮丧。
  • 我认为这真的取决于用例,我们并不完全确定。 OP 想要一个时间戳,并且为了比较不同时区的时间,它不会比仅使用整数更快/更简单,而且我使用的每个日期时间框架都支持它作为输入。但同样,这取决于几个因素:例如,您需要 1970 年之前的日期,还是将日期显示给用户?两者都有用例,但说“通常更好”可能是不正确的,您可能是对的。
猜你喜欢
  • 2012-10-04
  • 2016-08-24
  • 2017-02-02
  • 1970-01-01
  • 2016-12-12
  • 2014-01-28
  • 1970-01-01
  • 2012-04-04
  • 2012-01-30
相关资源
最近更新 更多