Java 中不同时区的日期格式

Date Format with different Timezone in Java

我对 Java 中的时区转换感到困惑。我有几个案例,我将在下面列出。

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
   // sdf.setTimeZone(TimeZone.getTimeZone("Asia/kolkata"));
    Date date1 = sdf.parse("2021-01-31");

    System.out.println(date1);   //. O/P - Sun Jan 31 00:00:00 IST 2021

现在让我们取消时区部分的注释并查看时差

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    sdf.setTimeZone(TimeZone.getTimeZone("Asia/kolkata"));
    Date date1 = sdf.parse("2021-01-31");

    System.out.println(date1);  // O/P - Sun Jan 31 05:30:00 IST 2021 

现在让我们将时区设置为 IST 并查看时差

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    sdf.setTimeZone(TimeZone.getTimeZone("IST"));
    Date date1 = sdf.parse("2021-01-31");

    System.out.println(date1);   // O/P - Sun Jan 31 00:00:00 IST 2021

现在让我们将时区设置为 UTC 并查看时差

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
    Date date1 = sdf.parse("2021-01-31");

    System.out.println(date1);  // O/P - Sun Jan 31 05:30:00 IST 2021
  1. 谁能解释一下为什么当我更改时区时会发生这种时间变化 (+- 5:30)?
  2. 对于 IST 和 Asia/Kolkata,时间应该保持不变,因为它们是相同的时区,但为什么要转变?
  3. 为什么使用 UTC 时区时,时间会增加 5:30 小时?我的理解是 IST 比 UTC 早 5:30 小时,因此转换为 UTC 应该可以将时间减少 5:30 小时
  4. 为什么即使在转换为 UTC 后,我的时间仍显示 IST 2021?

我这里还有点迷糊

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
    Date date1 = sdf.parse("2021-01-31");
    System.out.println(date1.getTime()); // 1612051200000


    SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd");
    sdf1.setTimeZone(TimeZone.getTimeZone("Asia/Kolkata"));
    Date date2 = sdf1.parse("2021-01-31");
    System.out.println(date2.getTime());  // 1612031400000

为什么 UTC 中的时刻大于 Asia/Kolkata 中的时刻?

您需要注意以下几点:

  • 当打印 Date 时,它将按照您计算机的本地时区进行格式化(Date.toString 就是这样做的)。据推测,您的计算机处于 Asia/Kolkata 时区,因此输出始终显示为该时区的日期和时间。

  • 一个Date表示一个时间点(即瞬间)。它是不是年月日时分秒时区的元组

  • 由于你输入的字符串中没有时间,解析时使用00:00:00时间

  • 仅日期和时间不足以生成时间点。您还需要一个时区来指定时间点。由于您的输入字符串中没有时区,因此使用您计算机的本地时区,或者如果您已设置,则使用 sdf.getTimeZone()

  • 尽管在解析日期时使用了时区,但时区 不是 Date 对象的一部分。

Can anybody please explain me why this shift in time is happening (+- 5:30) when I change the Timezone?

当您使用 "IST" 时区(第一个和第三个代码片段)时,sdf 获得以下信息:

  • 日期:2021-01-31
  • 时间:00:00:00
  • 时区:Asia/Kolkata

利用这些信息,它可以产生一个时间点,用自 Java 纪元 - 1970-01-01 00:00:00 UTC 以来的毫秒数表示。 这是 Date 对象。然后你打印 Date 对象,它被格式化为你当地的时区。您当地的时区恰好与 sdf 提供的时区相同,因此您会看到 Sun Jan 31 00:00:00 IST 2021.

当您使用 UTC(第二个和第四个代码片段)时,这些信息将提供给 sdf:

  • 日期:2021-01-31
  • 时间:00:00:00
  • 时区:UTC

这代表了与加尔各答 2021-01-31T00:00:00 不同的时间点。有何不同? UTC 的 2021-01-31T00:00:00 比加尔各答的 2021-01-31T00:00:00 整整晚了 5 个半小时。回想一下,要将 UTC 时间转换为加尔各答时间,您需要添加 5 个半小时。

For IST and Asia/Kolkata, time should have remain same because they are same Timezone, but why the shift?

因为你拼错了Asia/Kolkata。 "Kolkata" 中的第一个 "K" 应该大写。 TimeZone class 将未知区域 ID 视为 UTC。这就是您应该迁移到新的 java.time classes 的原因。 ZoneId 如果您为其提供未知区域 ID,则会抛出异常。

Why When using the UTC Timezone, time gets increased by 5:30 hours? What I understand is IST is 5:30 hrs ahead of UTC, so converting to UTC should have decreased the time by 5:30 hrs

您正在考虑 格式化 日期,而不是解析,因为请记住时区不是 Date 的一部分,而是 SimpleDateFormat 的一部分。您的代码不格式化 Date,只解析它们。如果没有格式化,Dates 将始终以您当地的时区打印。

要使用 SimpleDateFormat 查看您想要的行为,您首先要解析日期字符串 once,然后 format它使用不同时区的 SimpleDateFormats。


不过真的,你应该改成 java.time。使用 API,您的区域更改代码可以这样写:

ZonedDateTime zdt = LocalDate.parse("2021-01-31")
        .atStartOfDay()
        .atZone(ZoneId.of("Asia/Kolkata"));
System.out.println(zdt);
ZonedDateTime utcDateTime = zdt.withZoneSameInstant(ZoneId.of("UTC"));
System.out.println(utcDateTime);

// output:
// 2021-01-31T00:00+05:30[Asia/Kolkata]
// 2021-01-30T18:30Z[UTC]

Java getTimeZone

的文档

ID - the ID for a TimeZone, either an abbreviation such as "PST", a full name such as "America/Los_Angeles", or a custom ID such as "GMT-8:00". Note that the support of abbreviations is for JDK 1.1.x compatibility only and full names should be used.

不支持时区缩写。所以你不能使用 IST

并且在 Three-letter time zone IDs

的时区文档中

For compatibility with JDK 1.1.x, some other three-letter time zone IDs (such as "PST", "CTT", "AST") are also supported. However, their use is deprecated because the same abbreviation is often used for multiple time zones (for example, "CST" could be U.S. "Central Standard Time" and "China Standard Time"), and the Java platform can then only recognize one of them.

问题是 IST 缩写用于多个时区,例如爱尔兰标准时间、以色列标准时间、印度标准时间。您将 Asia/Kolkata 误输入为 Asia/kolkata

因此,如果无法从 TimeZone.getTimeZone()

理解给定的 ID,则 GMT 区域将 return

java 时间

我建议你使用 java.time,现代 Java 日期和时间 API,作为你的约会工作

    LocalDate date = LocalDate.parse("2021-01-31");
    System.out.println(date);

输出为:

2021-01-31

A LocalDate 是一个没有时间和时区或 UTC 偏移的日期,因此使用它可以让您完全摆脱所有时区的麻烦。此外,我们不需要任何明确的格式化程序。您的字符串采用 ISO 8601 格式,LocalDate 将最常见的 ISO 8601 变体解析为默认值。如您所见,当我们打印它时,它也打印回相同的 ISO 8601 格式,隐式调用其 toString 方法。

你的代码出了什么问题?

您使用的 SimpleDateFormatTimeZoneDate class 设计不佳且早已过时。难怪他们的行为让你感到困惑。

我假设 Asia/Kolkata(或 Asia/Colombo 或 Asia/Calcutta)是您的 JVM 的默认时区。在您的第一个示例中,SimpleDateFormat 使用您的默认时区并将字符串解析为该时区当天的第一时刻。

在你的第二个例子中,正如 Elavya 所发现的那样,你在 Asia/kolkata 中有一个小写字母 k,这导致 TimeZone 无法识别预期的时区。这就是 TimeZone 在糟糕的设计中表现出色的地方:它只是默认给你 GMT。接下来 Date class 的设计也很糟糕,仍然以 JVM 的默认时区打印时间,给人一种 Date 对象包含时区的错觉。这让很多人感到困惑。 GMT 中一天的开始时间与 05:30:00 IST 相同,所以这就是你得到的。

在您的第三个和第四个示例中,即使不推荐使用三个字母的时区缩写,IST(与 Eklavya 所说的相反)被解释为 Asia/Kolkata,UTC 被解释为 Etc/UTC。尽管正如 Eklavya 所说,IST 是模棱两可的。

简而言之:

  1. 发生这种变化是因为一天的开始时间在不同的时区是不同的时间点。
  2. 由于您在 Asia/kolkata 中的错字。时区 ID 区分大小写。
  3. 您没有转换为 UTC。您正在解析 UTC,从而将 UTC 转换为 Date.toString() 进一步将 转换为 Asia/Kolkata (IST) 作为输出还说。
  4. 因为 Date 对象没有时区并且因为 Date.toString() 获取 JVM 的默认时区并使用它来呈现要返回的字符串。

链接

作为已接受答案的补充,针对您问题的最后一部分;

Why instant of time in UTC is greater than instant of time in Asia/Kolkata in below code?

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
Date date1 = sdf.parse("2021-01-31");
System.out.println(date1.getTime()); // 1612051200000


SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd");
sdf1.setTimeZone(TimeZone.getTimeZone("Asia/Kolkata"));
Date date2 = sdf1.parse("2021-01-31");
System.out.println(date2.getTime());  // 1612031400000
  • 首先,无论时区如何,您的时间点为 T。在我们的示例中,T=2021-01-31 00:00:00。
  • 当我们将时区设置为 UTC 并使用 java.util.Date.getTime() 方法打印时间时,它将打印自 Unix 纪元以来的毫秒数,该纪元发生在 1970 年 1 月 1 日午夜,UTC。所以它将打印 1612051200000。如您所见,时间和我们的日期具有相同的时区,即 UTC。所以直接打印时间,不需要调整时区。
  • 现在,当我们将时区设置为 Asia/Kolkata 时,在 SimpleDateFormat.parse 期间,时区信息将添加到日期中。这意味着 +5:30h(19800000ms) 将添加到时间 T。因此我们的时间 T 增加了 19800000ms。 但是 T 必须指向同一时间点。我们如何解决这个问题?它通过从时间 1612051200000ms 中减去 19800000ms 固定在 SimpleDateFormat.parse 方法上,这样 getTime() 方法现在将显示 1612031400000ms,这样我们的实际时间 T 仍将显示相同的时间点(即 1612051200000ms ) 因为在这个日期对象中我们有一个额外的 19800000ms 来自时区。