【问题标题】:Past date increased by one hour when converted to Europe/Moscow timezone转换为欧洲/莫斯科时区时,过去日期增加一小时
【发布时间】:2021-01-27 16:54:06
【问题描述】:

当我将它转换为莫斯科时区时,有人可以解释为什么过去的日期增加了一小时吗?

我使用的是 JDK 1.6.0_12 版本。

2011-04-02T11:39:46+0300 --> Sat Apr 02 12:39:46 MSK 2011     // 11:39 --> 12:39

我当前的系统时区是“欧洲/莫斯科”UTC+3。

另外请注意,过去的日期是在 DST(夏令时)时区时间 UTC+4 中,早在俄罗斯使用。 2014 年 10 月,俄罗斯时区定义发生了立法变化。从那时起,俄罗斯全年使用 UTC+3。

我已经检查过了 this old post of 2014 .

但我认为这个问题看起来不同。

我们的开发人员希望每个过去的日期(如 "2011-04-02T11:39:46+0300" 并且在 DST 期间)应该包含当前时区偏移值,即 +0300 ,而不是 +0400 。他们认为 JRE 将其错误地转换为 UTC+4 ,尽管“默认时区偏移”在此处显示 +3 。这种处理过去日期的时区偏移值的方式是否正确?

JRE 1.8 上给出了相同的输出,我认为这是一个更新版本,JRE 1.8 中的 TZ 定义应该没有任何问题。

提前致谢!

Java 代码:

import java.text.SimpleDateFormat;
import java.util.TimeZone;
import java.util.Date;

public class HelloWorld{
    public static void main(String []args)
    {
        String dateInString = "2011-04-02T11:39:46+0300";
        System.out.println(dateInString);

        try {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
            Date date = dateFormat.parse(dateInString);
            System.out.println(date);
        } catch (Exception e) {
            System.out.println(e);
        }

        final TimeZone tzone = TimeZone.getDefault();
        System.out.println("Default Time Zone ID - " + tzone.getID());
        System.out.println("Default Time Zone Offset - (" + (tzone.getRawOffset() / 60 / 60 / 1000) + ") hour.");
    }
}

输出:

2011-04-02T11:39:46+0300
Sat Apr 02 12:39:46 MSK 2011
Default Time Zone ID - Europe/Moscow
Default Time Zone Offset - (3) hour.

【问题讨论】:

  • 即使立法发生了变化;它们仅在应用之日后生效,因此对 UTC+3 的更改不应追溯应用于 2014 年之前的日期。
  • 我建议你不要使用SimpleDateFormatTimeZoneDate。这些类设计不佳且早已过时,尤其是第一个类是出了名的麻烦。而是使用OffsetDateTimeZonedDateTimeZoneIdDateTimeFormatter,均来自java.time, the modern Java date and time API
  • 这是一个经过深思熟虑的好问题。您已经解释了您在搜索中找到的内容以及它如何没有解决您的问题,并且您提供了一个小而完整的代码示例。非常喜欢。

标签: java date datetime timezone simpledateformat


【解决方案1】:

12:39 是正确的时间

你得到了正确的结果。在您的字符串2011-04-02T11:39:46+0300 中,结尾的+0300 是与UTC 的偏移量。所以时间点与2011-04-02T08:39:46+00:00(UTC)相同。正如您自己所说,从 2011 年 3 月 27 日到 2014 年 10 月 26 日,莫斯科的 UTC 偏移量为 +04:00。因此,要获得莫斯科 Java 的正确时间,需要在字符串中的小时数上增加 1 小时。或 4 小时到 08:39:46 的 UTC 小时。无论如何,此时莫斯科的时间是 12:39:46。

或者回答你的问题:

…为什么过去的日期增加了一小时,当我转换它时 到莫斯科时区?

因为该日期的莫斯科比字符串中的时间早 1 小时。

java.time

也就是说,我同意那些推荐现代 Java 日期和时间 API 的 java.time 来完成这项工作。 SimpleDateFormat 是一个臭名昭著的班级麻烦制造者,DateTimeZone 的设计也很糟糕且令人困惑。一切都已经过时了。现代 API 更易于使用。

例如:

    ZoneId zone = ZoneId.of("Europe/Moscow");
    ZonedDateTime zdt = ZonedDateTime.of(2011, 4, 2, 11, 39, 46, 0, zone); 
    System.out.println(zdt);

输出:

2011-04-02T11:39:46+04:00[欧洲/莫斯科]

您还可以从输出中看到,Java 知道莫斯科当时的偏移量为 +04:00。

您的问题很好地说明了为什么 java.time(与旧的 TimeZone 类相反)区分时区和偏移量。时区包括与 UTC 的所有历史、现在和所有已知的未来偏移量。这就是您正确代表莫斯科历史时期所需要的。在 java.time 中,时区由 ZoneId 对象标识并服从 ZoneRules 对象(大多数情况下,我们不需要关心后者,只需要相信 Java 就可以进行正确的转换)。 UTC 偏移量由 ZoneOffset 对象表示。

问题:如何在 Java 1.6 中使用 java.time?

这是你的幸运日。 java.time 至少需要 Java 6

  • 在 Java 8 及更高版本以及更新的 Android 设备(从 API 级别 26 起)中,现代 API 是内置的。
  • 在非 Android 的 Java 6 和 7 中,获取 ThreeTen Backport,这是现代类的后向端口(对于 JSR 310,ThreeTen;请参阅底部的链接)。
  • 在较旧的 Android 上,请使用脱糖或 Android 版本的 ThreeTen Backport。它被称为 ThreeTenABP。在后一种情况下,请确保从 org.threeten.bp 导入日期和时间类以及子包。

链接

【讨论】:

  • 哇!除了很好的答案,我最喜欢的是标题:12:39 is the correct time,它设置了正确的上下文。
【解决方案2】:

现代 Java 日期/时间 API 和旧版(用于 jdk1.6)都依赖于系统 unix 时间和与 JRE 捆绑的 tzdata file。看起来开发人员是对的,您的 java 使用的是非常旧的 tzdata 版本,而您的开发人员是对的。

此外,tzdata 保留有关法律更改的信息,如果您尝试转换过去的日期/时间,它将应用当时相关的转换规则。

关于 JDK 1.8:在 8u101 中更新了俄罗斯时区信息,因此您应该至少使用 8u101 以获得更好的时区转换。

如果您确实需要使用旧的 Java,最好的决定是使用现代 Java 或手动更新您的 JRE tzdata。

【讨论】:

  • 感谢您的回复。但是我使用最新的 JRE 8u261 编译并执行了该程序。它包含最新的 tzdata 。您可以查看此站点 -->oracle.com/java/technologies/javase/8all-relnotes.html 。上述程序的输出是相同的。仍然有1小时的差异。这就是为什么我问用过去的日期字符串而不是 +0400 来获取当前时区偏移量(+0300)是否正确。
【解决方案3】:

您需要将时区设置为SimpleDateFormat,如下所示:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

public class Main {
    public static void main(String[] args) throws ParseException {
        String dateInString = "2011-04-02T11:39:46+0300";
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
        dateFormat.setTimeZone(TimeZone.getTimeZone("Europe/Moscow"));// Set time-zone
        Date date = dateFormat.parse(dateInString);
        System.out.println(dateFormat.format(date));
    }
}

输出:

2011-04-02T12:39:46+0400

请注意,java.util.Date 没有时区信息。它只是标准 Java 纪元 1970-01-01T00:00:00Z 的毫秒数,其中 Z 代表 UTC(0 小时偏移),也称为 Zulu 时区。在任何给定时刻,您将在位于单词任何部分的 JVM 上获得相同的毫秒数。当您尝试打印java.util.Date 的对象时,JVM 时区的日期时间字符串是根据此毫秒值计算的,并显示相同的值。如果要获取特定时区的日期时间String,则需要将其显式设置为SimpleDateFormat,并使用它来格式化java.util.Date

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-18
    • 2020-05-24
    • 2017-03-24
    • 2018-10-07
    相关资源
    最近更新 更多