为什么 SimpleDateFormat 总是忽略连接的时区?

时间:2021-02-23 18:06:02

标签: java date datetime java-8 datetime-format

我在 Java 中遇到 Date 和 SimpleDateFormat 对象的一些问题。我知道我需要在 Java 8 中使用新的日期和时间 API 来切换它们,我想这样做,但我有一个问题。当我从数据库中检索我的日期时(它是 SimpleDateFormat 并且我无法更改它),并且当我想添加一些小时(例如下午 4 点),然后我需要在用户从下拉列表中选择的时区中(假设美国/加拉加斯),所以我希望美国/加拉加斯的结果是 16 小时,但在我的情况下是 16 小时 UTC + UTC 和美国/加拉加斯之间的差异。 这是我的代码:

Date startDate -> it's value is Tue Feb 23 20:00:00 CET 2021
Date date = new SimpleDateFormat("yyy-MM-dd").format( startDate + 4:00PM + America/Caracas);

但是,奇怪的是,这种格式化广告表示下午 4 点的值,但不知何故忽略了时区的那部分。所以,我在美国/加拉加斯有 20 小时,但在 UTC 时间有 20 小时。 有人可以帮助我吗?

2 个答案:

答案 0 :(得分:5)

java.time

你说:

<块引用>

需要在 Java 8 中使用新的日期和时间 API 来切换它们

是的,绝对。遗留的 DateCalendarSimpleDateFormat可怕:令人困惑和有缺陷。

你说:

<块引用>

当我从数据库中检索日期时(它是 SimpleDateFormat 并且我无法更改它)

如果您的日期时间值正确存储在数据库中的日期时间列中,则它没有“格式”,因为它不是文本。

存储在日期时间列中的日期时间值应作为日期时间对象而不是文本进行检索。如果您收到短信,您应该回溯数据的食物链以解决该问题。

分区

如果存储在类型类似于 SQL 标准 TIMESTAMP WITH TIME ZONE 的列中,则检索为 OffsetDateTime

OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;

你说:

<块引用>

我想增加一些时间(例如下午 4 点)

这是一个矛盾。你想add four hours吗?

OffsetDateTime odtFourHoursLater = odt.plusHours( 4 ) ;

或者您想将一天中的时间设置为下午 4 点吗?

OffsetDateTime odtSetToFourPm = odt.with( LocalTime.of ( 16 , 0 ) ) ;

你说:

<块引用>

那个结果我需要在用户从下拉列表中选择的时区(假设美国/加拉加斯),

JDBC 4.2 支持的 OffsetDateTime 类表示通过与 UTC 的偏移量(小时-分钟-秒数)看到的时刻。大多数数据库会以零时分秒的偏移量将这个对象交付给您。

了解时区不是偏移量。时区是特定地区人民使用的偏移量的过去、现在和未来变化的命名历史。时区的名称格式为 Continent/Region

要表示在时区中看到的时刻而不是偏移量,请使用 ZonedDateTime 类。

ZoneId zoneId = ZoneId.of( "America/Caracas" ) ;
ZonedDateTime zdt = odt.atZoneSameInstant( zoneId ) ;

我无法提供更具体的代码示例,因为您的问题令人困惑。您的确切目标尚不清楚。

未分区

如果存储在类型类似于 SQL 标准 TIMESTAMP WITHOUT TIME ZONE 的列中,则检索为 LocalDateTime

LocalDateTime ldt = myResultSet.getObject( … , LocalDateTime.class ) ;

这种类型不能代表一个时刻,时间轴上的一个点。缺乏时区的上下文或与 UTC 的偏移意味着我们不知道,例如,特定日期的中午是日本东京的中午、法国图卢兹的中午还是美国俄亥俄州托莱多的中午——所有不同的时刻,相隔几个小时。因此,该类型无法调整为任意时区。


关于java.time

java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

要了解更多信息,请参阅 Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为 JSR 310

现在位于 Joda-Timemaintenance mode 项目建议迁移到 java.time 类。

您可以直接与您的数据库交换 java.time 对象。使用符合 JDBC driver 或更高版本的 JDBC 4.2。不需要字符串,不需要 java.sql.* 类。 Hibernate 5 和 JPA 2.2 支持 java.time

从哪里获取 java.time 类?

答案 1 :(得分:1)

使用 JDBC 4.2(无 SimpleDateFormat)从数据库获取日期

我建议您使用 java.time,现代 Java 日期和时间 API,用于您的日期和时间工作。从 JDBC 4.2 开始,我们可以直接从 SQL 中检索像 LocalDate 这样的 java.time 类型。如果您在数据库中的数据类型是 date(推荐用于没有时间的日期):

    PreparedStatement selectStatement = yourDatabaseConnection.prepareStatement("select your_date from your_table where id = 4;");
    ResultSet rs = selectStatement.executeQuery();
    if (rs.next()) {
        LocalDate date = rs.getObject("your_date", LocalDate.class);
    }

如果 SQL 中的数据类型是 varcharchar(不推荐),则检索到 Java 中的 String 并解析它。以下示例假定为 ISO 8601 格式,例如 2021-02-25,但可以使用 DateTimeFormatter 定制为其他格式。

        LocalDate date = LocalDate.parse(rs.getString("your_date"));

添加时间和时区

通过添加时间和时区,我猜测您的意图如下:

        LocalTime timeOfDay = LocalTime.of(16, 0); // 4 PM
        ZoneId zone = ZoneId.of("America/Caracas");
        
        ZonedDateTime zdt = date.atTime(timeOfDay).atZone(zone);
        
        System.out.println(zdt);

如果 date 是 2021-02-25,输出是:

<块引用>

2021-02-25T16:00-04:00[美国/加拉加斯]

尽管名称过时,但过时的 java.util.Date 类仅代表一个时间点,即没有时区。另一方面,现代 ZonedDateTime 是时区中的日期和时间,正如名称所暗示的那样。因此,当您想要一个带有时间和时区的日期时,您需要什么。

我想再解释一下中心表达式,date.atTime(timeOfDay).atZone(zone)

        date                    // A LocalDate, that is, a date without time of day
             .atTime(timeOfDay) // A LocalDateTime, with specified time of day, still without time zone
             .atZone(zone)      // A ZonedDateTime. The same date and time, now in the specified time zone.

链接

Oracle tutorial: Date Time 解释如何使用 java.time。