【问题标题】:SimpleDateFormat vs ZonedDateTime with timezoneSimpleDateFormat 与 ZonedDateTime 与时区
【发布时间】:2020-03-05 19:39:39
【问题描述】:

我正在尝试将 PST 时间戳转换为纪元时间。

我尝试的第一种方法是通过将美国/洛杉矶作为输入时区来使用分区日期时间。

    public static void changeStringDateFormatToEpoch(String oldDate, String format) throws ParseException {
        DateTimeFormatter dtf  = DateTimeFormatter.ofPattern(format);
        LocalDateTime dt = LocalDateTime.parse(oldDate, dtf);
        ZonedDateTime zdtzone = dt.atZone(ZoneId.of("America/Los_Angeles"));
        System.out.println("When date: "+ oldDate + " is in format "+ format + " --> "  + zdtzone.toEpochSecond());
    }

在此之后,我尝试运行下面的代码,它使用 SimpleDateFormat 来做同样的事情

 public static void changeStringDateFormatToEpochSimpleDate(String oldDate, String format) throws ParseException{
        SimpleDateFormat sdf = new SimpleDateFormat(format);
        Date dt = sdf.parse(oldDate);
        long epoch = dt. getTime();
        System.out.println("When date: "+ oldDate + " is in format "+ format + " --> "  + epoch);
    }

两个输出是如何相似的,在第二种情况下,我什至没有指定输入日期的时区。

由于输入时区是 PST(America/Los Angeles),纪元时间不应该受到影响吗?

样本输入

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;

class Scratch {
    public static void main(String[] args) {
        String date = "2019-11-27 04:32:41.000-0800"; //yyyy-MM-dd HH:mm:ss.SSSZ
        String date2 = "2019-11-27 04:32:41"; // yyyy-MM-dd HH:mm:ss


        try {
            changeStringDateFormatToEpoch(date, "yyyy-MM-dd HH:mm:ss.SSSZ");
            changeStringDateFormatToEpoch(date2, "yyyy-MM-dd HH:mm:ss");

            changeStringDateFormatToEpochSimpleDate(date, "yyyy-MM-dd HH:mm:ss.SSSZ");
            changeStringDateFormatToEpochSimpleDate(date2, "yyyy-MM-dd HH:mm:ss");


        } catch (ParseException e) {
            e.printStackTrace();
        }
    }

    public static void changeStringDateFormatToEpoch(String oldDate, String format) throws ParseException {
        DateTimeFormatter dtf  = DateTimeFormatter.ofPattern(format);
        LocalDateTime dt = LocalDateTime.parse(oldDate, dtf);
        ZonedDateTime zdtzone = dt.atZone(ZoneId.of("America/Los_Angeles"));
        System.out.println("When date: "+ oldDate + " is in format "+ format + " --> "  + zdtzone.toEpochSecond());
    }

    public static void changeStringDateFormatToEpochSimpleDate(String oldDate, String format) throws ParseException{
        SimpleDateFormat sdf = new SimpleDateFormat(format);
        Date dt = sdf.parse(oldDate);
        long epoch = dt. getTime();
        System.out.println("SimpleDate : When date: "+ oldDate + " is in format "+ format + " --> "  + epoch);
    }
}

输出

When date: 2019-11-27 04:32:41.000-0800 is in format yyyy-MM-dd HH:mm:ss.SSSZ --> 1574857961
When date: 2019-11-27 04:32:41 is in format yyyy-MM-dd HH:mm:ss --> 1574857961
SimpleDate : When date: 2019-11-27 04:32:41.000-0800 is in format yyyy-MM-dd HH:mm:ss.SSSZ --> 1574857961000
SimpleDate : When date: 2019-11-27 04:32:41 is in format yyyy-MM-dd HH:mm:ss --> 1574857961000

【问题讨论】:

  • 您将什么format 值传递给这两种方法? JVM的默认时区是什么?请提供Minimal, Reproducible Example,例如而不是只显示String date2 = "2019-11-27 04:32:41";,而是向我们展示实际调用changeStringDateFormatToEpoch("2019-11-27 04:32:41", "...") 以及你得到的输出。
  • 您无法将"2019-11-27 04:32:41" 解析为ZonedDateTime,因此您的问题毫无意义。
  • 我已经添加了示例函数调用和我得到的输出。 @OleV.V.我在太平洋标准时间工作。这就是为什么它在不指定时区的情况下转换为正确的纪元时间的原因吗?
  • @Andreas 你能解释一下为什么 ZonedDateTime 无法解析吗?
  • @MhnCodes 我认为错误信息说明了一切:ZonedDateTime.parse("2019-11-27 04:32:41", DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss")) throws DateTimeException: 无法获取 ZoneId,因为没有时区在输入字符串中指定,ZonedDateTime 需要时区(因此得名)。 --- 我复制/粘贴了问题 as-is 中的代码以及方法调用,我得到了那个错误。您未能为您的输出提供 reproducible 示例。

标签: java java-8 simpledateformat java-time


【解决方案1】:

您的基于SimpleDateFormat 的代码能够按预期工作,只是因为您在太平洋时区的机器上运行它。 SimpleDateFormat实例使用系统默认时区初始化,但可以更改。

要使您的代码健壮且可移植,请使用第一种方法。解析LocalDateTime,然后将其与显式ZoneId 组合,而不是推断默认时区。

您的示例中解析的某些日期时间使用偏移日期时间。这很有帮助;受夏令时影响的地区的许多开发人员忽略了在存储本地时间戳时需要包括一些指示当前夏令时是否有效的必要性。没有它,在秋季过渡到标准时间期间,解析时间就会出现歧义。

无论如何,您应该知道,也可以将LocalDateTimeZoneOffset 组合起来产生OffsetDateTime,并由此产生一个纪元时间。

【讨论】:

    【解决方案2】:

    编辑你的changeStringDateFormatToEpoch() 后有一个新问题:它现在忽略了字符串中给定的偏移量-0800。只要恰好与您在代码中硬编码提供的时区一致,结果就OK;但如果偏移量不同,您将获得不正确的结果。即使对于美国/洛杉矶时区,您也会在夏令时 (DST) 结束时的重叠处得到不正确的结果,时钟时间会重复,而偏移量是您区分的唯一机会。

    使用SimpleDateFormat 的代码可以说有相反的问题:在不存在时区或偏移的情况下,它使用 JVM 的默认时区,这可能是也可能不是预期的。 JVM 时区设置可以随时被您的程序的另一部分或在同一 JVM 中运行的另一个程序更改,这进一步损害了机会。

    如果您知道美国/洛杉矶时区将用于没有时区或偏移量的字符串,则解决方案是以下变体:

    /** @throws DateTimeParseException if oldDate is not in the format given */
    public static void changeStringDateFormatToEpoch(String oldDate, String format) {
        // Time zone to use if neither time zone nor UTC offset is given in the string
        ZoneId defaultZone = ZoneId.of("America/Los_Angeles");
        DateTimeFormatter dtf  = DateTimeFormatter.ofPattern(format).withZone(defaultZone);
        ZonedDateTime zdt = ZonedDateTime.parse(oldDate, dtf);
        System.out.println("When date: " + oldDate + " is in format " + format
                            + " --> "  + zdt.toEpochSecond());
    }
    

    让我们用你自己的代码来试试吧:

        String date = "2019-11-27 04:32:41.000-0800"; //yyyy-MM-dd HH:mm:ss.SSSZ
        String date2 = "2019-11-27 04:32:41"; // yyyy-MM-dd HH:mm:ss
    
        changeStringDateFormatToEpoch(date, "yyyy-MM-dd HH:mm:ss.SSSZ");
        changeStringDateFormatToEpoch(date2, "yyyy-MM-dd HH:mm:ss");
    

    即使在我的时区欧洲/哥本哈根运行,结果也是一致的:

    When date: 2019-11-27 04:32:41.000-0800 is in format yyyy-MM-dd HH:mm:ss.SSSZ --> 1574857961
    When date: 2019-11-27 04:32:41 is in format yyyy-MM-dd HH:mm:ss --> 1574857961
    

    回答您的问题

    两种输出是如何相似的,而在第二种情况下我不是 甚至指定输入日期的时区。

    在第二种情况下,SimpleDateFormat 使用 JVM 的时区设置。仅因为这是 America/Los_Angeles(或在 11 月 27 日使用偏移量 -08:00 的另一个时区),输出才会一致。在不同的时区运行时,它们不会。

    由于输入时区是,纪元时间不应该受到影响吗 PST(美国/洛杉矶)?

    输出受 JVM 的默认时区影响。除非设置了默认值,否则它不使用 UTC 或其他默认值。

    不要使用SimpleDateFormat

    SimpleDateFormat 类是出了名的麻烦和过时。它给你一个令人惊讶的结果并不奇怪。请使用 java.time,这是现代 Java 日期和时间 API,ZonedDateTime 是您的日期和时间工作的一部分。它往往会带来更少的惊喜和更自然的代码。

    【讨论】:

      【解决方案3】:
          ZonedDateTime zonedDateTime = LocalDateTime.now().atZone(ZoneId.of("Asia/Hong_Kong"));
          DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("MM/dd/yyyy - hh:mm:ss zzz");
          String formattedString = zonedDateTime.format(dateTimeFormatter);
          System.out.println(String.format("ZonedDateTime [%s], Formatted ZonedDateTime [%s]", zonedDateTime, formattedString));
      

      输出为 - ZonedDateTime [2022-03-17T17:09:27.471+08:00[Asia/Hong_Kong]],格式化 ZonedDateTime [03/17/2022 - 05:09:27 HKT]

      但是当我这样做时

      SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd/mm/yyyy - hh:mm:ss zzz");
          String format = simpleDateFormat.format(zonedDateTime);
          System.out.println(format);
      

      抛出错误

      无法将给定的对象格式化为日期

      这意味着 SimpleDateFormat 不支持 ZonedDateTime ?

      【讨论】:

        猜你喜欢
        • 2019-03-12
        • 2021-07-25
        • 1970-01-01
        • 2022-01-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多