将 "fake" 时间戳存储到数据库中

Storing a "fake" timestamp into a database

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

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

这里的问题是因为2:00上午是U.S夏令时的开始。中央时间,因此 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();
    }

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

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

您不应从数据库中读取字符串以获取日期时间值,您应该读取日期时间对象。 Whosebug 上有 许多 个关于 reading/writing 日期时间值 from/to 数据库的问题,因此无需在此重复。

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

使用正确的时区名称

日期时间工作的重要提示:避免将时区视为 "Central Time" 和 3-4 字母代码如 "CST"。这些不是标准化的,也不是唯一的(很多重复),进一步混淆了夏令时的混乱。在 "continent/majorCityOrRegion" 的模式中使用 proper time zone

本地日期时间

也许您的意思就是我们所说的 "local time",其中日期时间不特定于任何一个时区。例如,"Christmas starts at midnight on December 25th 2015"。这意味着每个特定时区的不同时刻。例如,巴黎的圣诞节黎明比蒙特利尔早。

乔达时间

让我们将该字符串解释为 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. This type means no adjustments to UTC time zone are to be made in either getting (SELECT) or putting (INSERT / UPDATE) database values. See Postgres doc 将此本地日期时间存储在 SQL 数据库中,以获取有关这些 SQL 类型的更多信息。

分区日期时间

如果您要表示特定时区的特定时刻,例如 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
…

似乎没有好的通用解决方法,只有 hack。基本上,如果您期望某个时区,您可以自己进行调整。查看 this, this, this, and the 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,您可以直接喂养 LocalDateTime or ZonedDateTime to your JDBC driver. But most drivers have not yet be updated to handle the java.time types. Until your driver is updated, fall back on the 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 );