Java 8 Time API - ZonedDateTime - 解析时指定默认 ZoneId

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/46218958/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 09:07:06  来源:igfitidea点击:

Java 8 Time API - ZonedDateTime - specify default ZoneId when parsing

javatimezonejava-timedatetime-parsingzoneddatetime

提问by RuntimeException

I am trying to write a generic method to return a ZonedDateTimegiven a date as Stringand its format.

我正在尝试编写一个通用方法来返回ZonedDateTime给定的日期String及其格式。

How do we make ZonedDateTimeto use the default ZoneIdif it is not specified in the date String?

如果日期中未指定,我们如何ZonedDateTime使用默认值?ZoneIdString

It can be done with java.util.Calendar, but I want to use the Java 8 time API.

可以用 来完成java.util.Calendar,但我想使用 Java 8 时间 API。

This question hereis using a fixed time zone. I am specifying the format as an argument. Both the date and its format are Stringarguments. More generic.

这里的问题使用固定时区。我将格式指定为参数。日期及其格式都是String参数。比较通用。

Code and output below:

代码和输出如下:

public class DateUtil {
    /** Convert a given String to ZonedDateTime. Use default Zone in string does not have zone.  */
    public ZonedDateTime parseToZonedDateTime(String date, String dateFormat) {
        //use java.time from java 8
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);
        ZonedDateTime zonedDateTime = ZonedDateTime.parse(date, formatter);
        return zonedDateTime;
    }

    public static void main(String args[]) {
        DateUtil dateUtil = new DateUtil();
        System.out.println(dateUtil.parseToZonedDateTime("2017-09-14 15:00:00+0530", "yyyy-MM-dd HH:mm:ssZ"));
        System.out.println(dateUtil.parseToZonedDateTime("2017-09-14 15:00:00", "yyyy-MM-dd HH:mm:ss"));
    }
}

Output

输出

2017-09-14T15:00+05:30
Exception in thread "main" java.time.format.DateTimeParseException: Text '2017-09-14 15:00:00' could not be parsed: Unable to obtain ZonedDateTime from TemporalAccessor: {},ISO resolved to 2017-09-14T15:00 of type java.time.format.Parsed
    at java.time.format.DateTimeFormatter.createError(DateTimeFormatter.java:1920)
    at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1855)
    at java.time.ZonedDateTime.parse(ZonedDateTime.java:597)
    at com.nam.sfmerchstorefhs.util.DateUtil.parseToZonedDateTime(DateUtil.java:81)
    at com.nam.sfmerchstorefhs.util.DateUtil.main(DateUtil.java:97)
Caused by: java.time.DateTimeException: Unable to obtain ZonedDateTime from TemporalAccessor: {},ISO resolved to 2017-09-14T15:00 of type java.time.format.Parsed
    at java.time.ZonedDateTime.from(ZonedDateTime.java:565)
    at java.time.format.Parsed.query(Parsed.java:226)
    at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)
    ... 3 more
Caused by: java.time.DateTimeException: Unable to obtain ZoneId from TemporalAccessor: {},ISO resolved to 2017-09-14T15:00 of type java.time.format.Parsed
    at java.time.ZoneId.from(ZoneId.java:466)
    at java.time.ZonedDateTime.from(ZonedDateTime.java:553)
    ... 5 more

回答by

A ZonedDateTimeneeds a timezone or an offset to be built, and the second input doesn't have it. (It contains only a date and time).

AZonedDateTime需要构建时区或偏移量,而第二个输入没有。(它只包含日期和时间)。

So you need to check if it's possible to build a ZonedDateTime, and if it's not, you'll have to choose an arbitrary zone for it (as the input has no indication about what's the timezone being used, you must choose one to be used).

因此,您需要检查是否可以构建一个ZonedDateTime,如果不能,则必须为其选择一个任意区域(因为输入没有指示正在使用的时区,您必须选择一个要使用的时区) .

One alternative is to first try to create a ZonedDateTimeand if it's not possible, then create a LocalDateTimeand convert it to a timezone:

一种替代方法是首先尝试创建一个ZonedDateTime,如果不可能,则创建一个LocalDateTime并将其转换为时区:

public ZonedDateTime parseToZonedDateTime(String date, String dateFormat) {
    // use java.time from java 8
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);
    ZonedDateTime zonedDateTime = null;
    try {
        zonedDateTime = ZonedDateTime.parse(date, formatter);
    } catch (DateTimeException e) {
        // couldn't parse to a ZoneDateTime, try LocalDateTime
        LocalDateTime dt = LocalDateTime.parse(date, formatter);

        // convert to a timezone
        zonedDateTime = dt.atZone(ZoneId.systemDefault());
    }
    return zonedDateTime;
}

In the code above, I'm using ZoneId.systemDefault(), which gets the JVM default timezone, but this can be changed without notice, even at runtime, so it's better to always make it explicit which one you're using.

在上面的代码中,我正在使用ZoneId.systemDefault(),它获取JVM 默认时区,但是这可以在没有通知的情况下更改,即使在运行时也是如此,因此最好始终明确说明您正在使用哪个时区。

The API uses IANA timezones names(always in the format Region/City, like America/Sao_Pauloor Europe/Berlin). Avoid using the 3-letter abbreviations (like CSTor PST) because they are ambiguous and not standard.

API使用IANA时区的名称(总是在格式Region/City,如America/Sao_PauloEurope/Berlin)。避免使用 3 个字母的缩写(如CSTPST),因为它们含糊不清且不标准

You can get a list of available timezones (and choose the one that fits best your system) by calling ZoneId.getAvailableZoneIds().

您可以通过调用获取可用时区列表(并选择最适合您系统的时区)ZoneId.getAvailableZoneIds()

If you want to use a specific timezone, just use ZoneId.of("America/New_York")(or any other valid name returned by ZoneId.getAvailableZoneIds(), New York is just an example) instead of ZoneId.systemDefault().

如果您想使用特定的时区,只需使用ZoneId.of("America/New_York")(或由 返回的任何其他有效名称ZoneId.getAvailableZoneIds(),纽约只是一个示例)而不是ZoneId.systemDefault()



Another alternative is to use parseBest()method, that tries to create a suitable date object (using a list of TemporalQuery's) until it creates the type you want:

另一种选择是使用parseBest()method,它尝试创建一个合适的日期对象(使用TemporalQuery's的列表),直到它创建您想要的类型:

public ZonedDateTime parseToZonedDateTime(String date, String dateFormat) {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);

    // try to create a ZonedDateTime, if it fails, try LocalDateTime
    TemporalAccessor parsed = formatter.parseBest(date, ZonedDateTime::from, LocalDateTime::from);

    // if it's a ZonedDateTime, return it
    if (parsed instanceof ZonedDateTime) {
        return (ZonedDateTime) parsed;
    }
    if (parsed instanceof LocalDateTime) {
        // convert LocalDateTime to JVM default timezone
        LocalDateTime dt = (LocalDateTime) parsed;
        return dt.atZone(ZoneId.systemDefault());
    }

    // if it can't be parsed, return null or throw exception?
    return null;
}

In this case, I just used ZonedDateTime::fromand LocalDateTime::from, so the formatter will try to first create a ZonedDateTime, and if it's not possible, then it tries to create a LocalDateTime.

在这种情况下,我只使用了ZonedDateTime::fromand LocalDateTime::from,因此格式化程序将尝试首先创建一个ZonedDateTime,如果不可能,那么它会尝试创建一个LocalDateTime.

Then I check what was the type returned and do the actions accordingly. You can add as many types you want (all main types, such as LocalDate, LocalTime, OffsetDateTimeand so on, have a frommethod that works with parseBest- you can also create your own custom TemporalQueryif you want, but I think the built-in methods are enough for this case).

然后我检查返回的类型并相应地执行操作。您可以添加你想要的多种类型(所有主要类型,如LocalDateLocalTimeOffsetDateTime等等,有一个from方法与作品parseBest-你也可以创建自己的自定义TemporalQuery,如果你想要的,但我觉得内置的方法是不够的这个案例)。



Daylight Saving Time

夏令时

When converting a LocalDateTimeto a ZonedDateTimeusing the atZone()method, there are some tricky cases regarding Daylight Saving Time(DST).

使用该方法将 a 转换LocalDateTime为 a 时,有一些关于夏令时(DST) 的棘手情况。ZonedDateTimeatZone()

I'm going to use the timezone I live in (America/Sao_Paulo) as example, but this can happen at any timezone with DST.

我将使用我居住的时区 ( America/Sao_Paulo) 作为示例,但这可能发生在 DST 的任何时区。

In S?o Paulo, DST started at October 16th2016: at midnight, clocks shifted 1 hour forwardfrom midnight to 1 AM (and the offset changes from -03:00to -02:00). So all local times between 00:00 and 00:59 didn't exist in this timezone (you can also think that clocks changed from 23:59:59.999999999 directly to 01:00). If I create a local date in this interval, it's adjusted to the next valid moment:

在圣保罗,夏令时于2016 年10 月 16开始:午夜时分,时钟从午夜向前移动 1 小时到凌晨 1 点(偏移量从-03:00变为-02:00)。因此,00:00 和 00:59 之间的所有本地时间都不存在于该时区(您也可以认为时钟从 23:59:59.999999999 直接更改为 01:00)。如果我在这个时间间隔内创建一个本地日期,它会被调整到下一个有效时刻:

ZoneId zone = ZoneId.of("America/Sao_Paulo");

// October 16th 2016 at midnight, DST started in Sao Paulo
LocalDateTime d = LocalDateTime.of(2016, 10, 16, 0, 0, 0, 0);
ZonedDateTime z = d.atZone(zone);
System.out.println(z);// adjusted to 2017-10-15T01:00-02:00[America/Sao_Paulo]

When DST ends: in February 19th2017 at midnight, clocks shifted back1 hour, from midnight to 23 PM of 18th(and the offset changes from -02:00to -03:00). So all local times from 23:00 to 23:59 existed twice(in both offsets: -03:00and -02:00), and you must decide which one you want. By default, it uses the offset before DST ends, but you can use the withLaterOffsetAtOverlap()method to get the offset after DST ends:

夏令时结束时:2017年 2 月 19午夜,时钟向后移动1 小时,从午夜到18晚上 23 点(偏移量从-02:00变为-03:00)。因此,从 23:00 到 23:59 的所有本地时间都存在两次(在偏移量:-03:00和 中-02:00),您必须决定您想要哪一个。默认情况下,它使用夏令时结束前的偏移量,但您可以使用该withLaterOffsetAtOverlap()方法获取夏令时结束后的偏移量:

// February 19th 2017 at midnight, DST ends in Sao Paulo
// local times from 23:00 to 23:59 at 18th exist twice
LocalDateTime d = LocalDateTime.of(2017, 2, 18, 23, 0, 0, 0);
// by default, it gets the offset before DST ends
ZonedDateTime beforeDST = d.atZone(zone);
System.out.println(beforeDST); // before DST end: 2018-02-17T23:00-02:00[America/Sao_Paulo]

// get the offset after DST ends
ZonedDateTime afterDST = beforeDST.withLaterOffsetAtOverlap();
System.out.println(afterDST); // after DST end: 2018-02-17T23:00-03:00[America/Sao_Paulo]

Note that the dates before and after DST ends have different offsets (-02:00and -03:00). If you're working with a timezone that has DST, keep in mind that those corner-cases can happen.

请注意,DST 结束前后的日期具有不同的偏移量 (-02:00-03:00)。如果您正在使用具有 DST 的时区,请记住这些极端情况可能会发生。

回答by M. Prokhorov

There are next to no defaults in java.timelibrary, which is mostly a good thing - what you're seeing is what you get, period.

java.time库中几乎没有默认值,这主要是一件好事 - 你所看到的就是你得到的,时期。

I would suggest that if your date string doesn't contain Zone - it is a LocalDateTime, and can't be a ZonedDateTime, which is the meaning of exception you're getting (even if verbiage suffered due to overly flexible code structure).

我建议,如果您的日期字符串不包含 Zone - 它是 a LocalDateTime,并且不能是 a ZonedDateTime,这就是您得到的异常的含义(即使由于过于灵活的代码结构而导致冗长)。

My main suggestion is to parse into local date time if you know that pattern has no Zone information.

如果您知道该模式没有区域信息,我的主要建议是解析为本地日期时间。

However, if you really must, here is another way to do what you want (an alternative solution that does not use exceptions for controlling the flow):

但是,如果您真的必须这样做,这里有另一种方式来做您想做的事情(一种不使用异常来控制流程的替代解决方案):

TemporalAccessor parsed = f.parse(string);
if (parsed.query(TemporalQueries.zone()) == null) {
  parsed = f.withZone(ZoneId.systemDefault()).parse(string);
}
return ZonedDateTime.from(parsed);

Here we use intermediate parsing result to determine if the string contained Zone information, and if not, we parse again (using same string, but different printer-parser) so that it will contain a zone this time.

这里我们使用中间解析结果来判断字符串是否包含Zone信息,如果没有,我们再次解析(使用相同的字符串,但使用不同的printer-parser),以便它这次包含一个区域。

Alternatively, you can make this class, which will save you from parsing second time and shouldallow you parse zoned date time, assuming all other fields are there:

或者,您可以创建这个类,这将使您免于解析第二次,并且应该允许您解析分区日期时间,假设所有其他字段都在那里:

class TemporalWithZone implements TemporalAccessor {
  private final ZoneId zone;
  private final TemporalAccessor delegate;
  public TemporalWithZone(TemporalAccessor delegate, ZoneId zone) {
    this.delegate = requireNonNull(delegate);
    this.zone = requireNonNull(zone);
  }

  <delegate methods: isSupported(TemporalField), range(TemporalField), getLong(TemporalField)>

  public <R> R query(TemporalQuery<R> query) {
    if (query == TemporalQueries.zone() || query == TemporalQueries.zoneId()) {
      return (R) zone;
    }
    return delegate.query(query);
  }
}

回答by vivek_vara

As per Java 8 ZonedDateTime implementation, You can not parse date without zone in ZonedDateTime.

根据 Java 8 ZonedDateTime 实现,您无法在 ZonedDateTime 中解析没有区域的日期。

To cater given problem you must have to put try catch in case any exception it will consider default time zone.

为了满足给定的问题,您必须放置 try catch 以防万一它会考虑默认时区。

Please find revised program as below:

请找到修改后的程序如下:

public class DateUtil {
     /** Convert a given String to ZonedDateTime. Use default Zone in string does not have zone.  */
    public ZonedDateTime parseToZonedDateTime(String date, String dateFormat) {
        //use java.time from java 8
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);
        ZonedDateTime zonedDateTime = null;
        try {
            zonedDateTime = ZonedDateTime.parse(date, formatter);
        } catch (DateTimeException e) {
            // If date doesn't contains Zone then parse with LocalDateTime 
            LocalDateTime localDateTime = LocalDateTime.parse(date, formatter);
            zonedDateTime = localDateTime.atZone(ZoneId.systemDefault());
        }
        return zonedDateTime;
    }

    public static void main(String args[]) {
        DateUtil dateUtil = new DateUtil();
        System.out.println(dateUtil.parseToZonedDateTime("2017-09-14 15:00:00+0530", "yyyy-MM-dd HH:mm:ssZ"));
        System.out.println(dateUtil.parseToZonedDateTime("2017-09-14 15:00:00", "yyyy-MM-dd HH:mm:ss"));
    }
}

You case refer http://www.codenuclear.com/java-8-date-time-introfor more details on upcoming java features

您的情况请参阅http://www.codenuclear.com/java-8-date-time-intro有关即将推出的 Java 功能的更多详细信息

回答by Flown

You can simply add a default value in a DateTimeFormatterBuilderif there is no OFFSET_SECOND:

DateTimeFormatterBuilder如果没有,您可以简单地在 a 中添加一个默认值OFFSET_SECOND



EDIT: To get the system's default ZoneOffsetyou have to apply the ZoneRulesto the current Instant. The result looks like this:

编辑:要获得系统的默认值,ZoneOffset您必须将 应用ZoneRules到当前的Instant. 结果如下所示:

class DateUtil {
  public ZonedDateTime parseToZonedDateTime(String date, String dateFormat) {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);
    LocalDateTime localDateTime = LocalDateTime.parse(date, formatter);
    ZoneOffset defaultOffset =  ZoneId.systemDefault().getRules().getOffset(localDateTime);
    DateTimeFormatter dateTimeFormatter = new DateTimeFormatterBuilder()
            .append(formatter)
            .parseDefaulting(ChronoField.OFFSET_SECONDS, defaultOffset.getTotalSeconds())
            .toFormatter();
    return ZonedDateTime.parse(date, dateTimeFormatter);
  }
}

Output:

输出:

2017-09-14T15:00+05:30
2017-09-14T15:00+02:00

回答by RSloeserwij

The ZoneIdcan be specified by using the withZone method in the DateTimeFormatter:

ZoneId可以通过使用在所述withZone方法来指定DateTimeFormatter

DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat).withZone("+0530");

回答by coladict

Just copying this solution from a project I have:

只需从我拥有的项目中复制此解决方案:

formatter = DateTimeFormatter.ofPattern(dateFormat).withZone(ZONE_UTC);

Once the formatter is compiled, you can call withZone(ZoneId)to create a new formatter with a set timezone.

编译格式化程序后,您可以调用withZone(ZoneId)以创建具有设置时区的新格式化程序。