【问题标题】:ZonedDateTime parses successfully but output string is differentZonedDateTime 解析成功但输出字符串不同
【发布时间】:2016-09-01 06:07:25
【问题描述】:

我正在使用ZonedDateTime根据时区解析并获取当前时间。

当我解析以下String 时,解析成功但输出String 不同。这是为什么呢?

String dateTimeString = "2016-05-04T12:58:22+01:00[Europe/Paris]";
ZonedDateTime dateTime = ZonedDateTime.parse(dateTimeString, ISO_ZONED_DATE_TIME);
System.out.println(dateTimeString);
System.out.println(dateTime.toString());

输出

2016-05-04T12:58:22+01:00[Europe/Paris]
2016-05-04T12:58:22+02:00[Europe/Paris]

它在什么时候决定将+1 更改为+2,为什么没有抛出异常?

我知道括号中的参数[Europe/Paris] 是可选的,但这里它优先于偏移量。

另一方面,下面的代码

String dateTimeString = "2016-05-04T12:58:22+01:00";
ZonedDateTime dateTime = ZonedDateTime.parse(dateTimeString, ISO_ZONED_DATE_TIME);
System.out.println(dateTimeString);
System.out.println(dateTime.toString());

产生输出

2016-05-04T12:58:22+01:00
2016-05-04T12:58:22+01:00

【问题讨论】:

    标签: java java-8 zoneddatetime


    【解决方案1】:

    特征;不是错误。

    ZonedDateTime#parse 给予ZoneId 的优先级高于ZoneOffsetdocumentation 明确提及:

    在设计方面,这个类应该主要被视为 LocalDateTimeZoneId 的组合。 ZoneOffset 是一个 重要但次要的信息,用于确保 类代表一个瞬间,尤其是在夏令时 重叠。

    通过查看反编译的源码可以进一步理解:

    类 ZonedDateTime

    public static ZonedDateTime parse(CharSequence text) {
        return parse(text, DateTimeFormatter.ISO_ZONED_DATE_TIME);
    }
    
    public static ZonedDateTime parse(CharSequence text, DateTimeFormatter formatter) {
        Objects.requireNonNull(formatter, "formatter");
        return formatter.parse(text, ZonedDateTime::from);
    }
    
    public static ZonedDateTime from(TemporalAccessor temporal) {
        if (temporal instanceof ZonedDateTime) {
            return (ZonedDateTime) temporal;
        }
        try {
            ZoneId zone = ZoneId.from(temporal);
            if (temporal.isSupported(INSTANT_SECONDS)) {
                long epochSecond = temporal.getLong(INSTANT_SECONDS);
                int nanoOfSecond = temporal.get(NANO_OF_SECOND);
                return create(epochSecond, nanoOfSecond, zone);
            } else {
                LocalDate date = LocalDate.from(temporal);
                LocalTime time = LocalTime.from(temporal);
                return of(date, time, zone);
            }
        } catch (DateTimeException ex) {
            throw new DateTimeException("Unable to obtain ZonedDateTime from TemporalAccessor: " +
                    temporal + " of type " + temporal.getClass().getName(), ex);
        }
    }
    

    类 ZoneId

    public static ZoneId from(TemporalAccessor temporal) {
        ZoneId obj = temporal.query(TemporalQueries.zone());
        if (obj == null) {
            throw new DateTimeException("Unable to obtain ZoneId from TemporalAccessor: " +
                    temporal + " of type " + temporal.getClass().getName());
        }
        return obj;
    }
    

    类 TemporalQuery

    public static TemporalQuery<ZoneId> zone() {
        return TemporalQueries.ZONE;
    }
    
    static final TemporalQuery<ZoneId> ZONE = new TemporalQuery<>() {
        @Override
        public ZoneId queryFrom(TemporalAccessor temporal) {
            ZoneId zone = temporal.query(ZONE_ID);
            return (zone != null ? zone : temporal.query(OFFSET));// <----- Look at this line
        }
    
        @Override
        public String toString() {
            return "Zone";
        }
    };
    

    另外,ZonedDateTime 的全部意义在于您将ZoneId 包含在其中,以便考虑到 夏令时/夏令时,它可以自动为您提供正确的日期时间,例如在没有ZoneId 信息的情况下,以下代码将为您提供两个打印语句的相同输出:

    import java.time.ZonedDateTime;
    
    public class Main {
        public static void main(String[] args) {
            String dateTimeString = "2016-05-04T12:58:22+01:00";
            ZonedDateTime dateTime = ZonedDateTime.parse(dateTimeString);
            System.out.println(dateTimeString);
            System.out.println(dateTime.toString());
        }
    }
    

    输出:

    2016-05-04T12:58:22+01:00
    2016-05-04T12:58:22+01:00
    

    再次强调,这是一项功能;不是错误,因为您要求系统使用 fixed ZoneOffset 信息解析日期时间字符串。做一个简单的类比,你可以认为ZoneOffset 是按字面意思计算的,而ZoneId 是可变计算的。

    几件事可能对您和未来的访问者有用:

    1. 日期时间字符串2016-05-04T12:58:22+01:00[Europe/Paris] 已经采用ZonedDateTime#parse 使用的默认格式,因此您无需将DateTimeFormatter 作为参数传递给它。
    2. System.out.println 自动打印参数对象的toString 方法返回的字符串,因此您不需要显式调用toString

    因此你的代码,

    import static java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME;
    
    import java.time.ZonedDateTime;
    
    public class Main {
        public static void main(String[] args) {
            String dateTimeString = "2016-05-04T12:58:22+01:00[Europe/Paris]";
            ZonedDateTime dateTime = ZonedDateTime.parse(dateTimeString, ISO_ZONED_DATE_TIME);
            System.out.println(dateTime.toString());
        }
    }
    

    可以写成

    import java.time.ZonedDateTime;
    
    public class Main {
        public static void main(String[] args) {
            String dateTimeString = "2016-05-04T12:58:22+01:00[Europe/Paris]";
            ZonedDateTime dateTime = ZonedDateTime.parse(dateTimeString);
            System.out.println(dateTime);
        }
    }
    

    【讨论】:

      【解决方案2】:

      问题是,2016-05-04T12:58:22+01:00[Europe/Paris] 不是正确的时间,因为我们在 5 月有 CEST(中欧夏令时,夏令时),从 3 月的最后一个星期日开始。与 UTC 相比,它是 +2 小时。所以确实2016-05-04T12:58:22+02:00[Europe/Paris] 是正确的。

      正如你所说,[Europe/Paris] 似乎占了上风。不确定规范方面是否应该有例外,但我对此表示怀疑。

      换一种说法2016-05-04T12:58:22+01:00不能在时区Europe/Paris

      【讨论】:

      • 感谢您的回复。我明白这是一个错误的日期,为什么它解析成功?这是一个设计缺陷。
      • 我猜规范中有一个定义的优先级。但我没有检查。
      猜你喜欢
      • 2016-09-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-16
      相关资源
      最近更新 更多