Java 8 Time API - ZonedDateTime - specify default ZoneId when parsing
我正在尝试编写一个泛型方法来返回
如果
它可以用
这里的问题是使用固定的时区。 我将格式指定为参数。 日期及其格式都是
代码和输出如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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")); } } |
产量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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 |
因此,您需要检查是否可以构建
一种替代方法是首先尝试创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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; } |
在上面的代码中,我使用的是
API使用IANA时区名称(始终采用
避免使用3个字母的缩写(如
您可以通过调用
如果要使用特定时区,只需使用
另一种方法是使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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; } |
在这种情况下,我只使用
然后我检查返回的类型是什么,并相应地执行操作。
您可以添加所需的任何类型(所有主要类型,例如
夏令时
使用
我将使用我所居住的时区(
在S?o Paulo,DST于2016年10月16日开始:在午夜,时钟从午夜向上移动1小时到凌晨1点(偏移从
1 2 3 4 5 6 | 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] |
DST结束时:2017年2月19日午夜时钟,时钟从18点的午夜到晚上23点向后移动1小时(偏移量从
默认情况下,它使用DST结束前的偏移量,但您可以使用
1 2 3 4 5 6 7 8 9 10 | // 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] |
请注意,DST结束前后的日期具有不同的偏移量(
根据Java 8 ZonedDateTime实现,您无法在ZonedDateTime中解析没有区域的日期。
为了满足给定的问题,你必须把try catch放在它将考虑默认时区的任何异常的情况下。
请找到修改后的程序如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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")); } } |
有关即将推出的Java功能的更多详细信息,请参考http://www.codenuclear.com/java-8-date-time-intro
我建议如果你的日期字符串不包含Zone - 它是
我的主要建议是,如果您知道该模式没有区域信息,则解析为本地日期时间。
但是,如果你真的必须,这是另一种方法来做你想要的(一种不使用异常来控制流的替代解决方案):
1 2 3 4 5 | TemporalAccessor parsed = f.parse(string); if (parsed.query(TemporalQueries.zone()) == null) { parsed = f.withZone(ZoneId.systemDefault()).parse(string); } return ZonedDateTime.from(parsed); |
这里我们使用中间解析结果来确定字符串是否包含区域信息,如果没有,我们再次解析(使用相同的字符串,但不同的打印机解析器),这次它将包含一个区域。
或者,您可以创建此类,这将使您免于解析第二次,并且应该允许您解析分区日期时间,假设所有其他字段都在那里:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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); } } |
如果没有
编辑:要获得系统的默认值
1 2 3 4 5 6 7 8 9 10 11 12 | 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); } } |
输出:
1 2 | 2017-09-14T15:00+05:30 2017-09-14T15:00+02:00 |
可以使用
1 | DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat).withZone("+0530"); |
只是从我拥有的项目中复制此解决方案:
1 | formatter = DateTimeFormatter.ofPattern(dateFormat).withZone(ZONE_UTC); |
编译格式化程序后,可以调用