我正在尝试编写一个泛型方法来返回ZonedDateTime给定日期为String及其格式。
如果String未在日期String中指定,我们如何使ZonedDateTime使用默认ZoneId?
它可以用java.util.Calendar完成,但我想使用Java 8时间API。
这里的问题是使用固定的时区。 我将格式指定为参数。 日期及其格式都是String参数。 更通用。
代码和输出如下:
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("-09-14 15:00:00+0530","yyyy-MM-dd HH:mm:ssZ"));
System.out.println(dateUtil.parseToZonedDateTime("-09-14 15:00:00","yyyy-MM-dd HH:mm:ss"));
}
}
产量
-09-14T15:00+05:30
Exception in thread"main" java.time.format.DateTimeParseException: Text '-09-14 15:00:00' could not be parsed: Unable to obtain ZonedDateTime from TemporalAccessor: {},ISO resolved to -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 -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 -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
如何使用默认区域解析ZonedDateTime可能重复?
谢谢各位的回应。 很有帮助。 不幸的是,我只能接受一个答案。
ZonedDateTime需要构建时区或偏移量,第二个输入不需要它。 (它只包含日期和时间)。
因此,您需要检查是否可以构建ZonedDateTime,如果不是,则必须为其选择任意区域(因为输入没有指示正在使用的时区,您必须选择一个使用)。
一种替代方法是首先尝试创建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;
}
在上面的代码中,我使用的是ZoneId.systemDefault(),它获取了JVM默认时区,但是即使在运行时也可以在不事先通知的情况下进行更改,因此最好始终明确指出您正在使用的是哪一个。
API使用IANA时区名称(始终采用Region/City格式,如America/Sao_Paulo或Europe/Berlin)。
避免使用3个字母的缩写(如CST或PST),因为它们不明确且不标准。
您可以通过调用ZoneId.getAvailableZoneIds()获取可用时区列表(并选择最适合您系统的时区)。
如果要使用特定时区,只需使用ZoneId.of("America/New_York")(或ZoneId.getAvailableZoneIds()返回的任何其他有效名称,纽约只是一个示例)而不是ZoneId.systemDefault()。
另一种方法是使用parseBest()方法,尝试创建一个合适的日期对象(使用TemporalQuery的列表),直到它创建所需的类型:
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;
}
在这种情况下,我只使用ZonedDateTime::from和LocalDateTime::from,因此格式化程序将尝试首先创建ZonedDateTime,如果不可能,则尝试创建LocalDateTime。
然后我检查返回的类型是什么,并相应地执行操作。
您可以添加所需的任何类型(所有主要类型,例如LocalDate,LocalTime,OffsetDateTime等),使用from方法与parseBest一起使用 - 您还可以创建自己的自定义TemporalQuery如果你愿意,但我认为内置方法足以满足这种情况)。
夏令时
使用atZone()方法将LocalDateTime转换为ZonedDateTime时,有一些关于夏令时(DST)的棘手案例。
我将使用我所居住的时区(America/Sao_Paulo)作为示例,但这可能发生在使用DST的任何时区。
在S?o Paulo,DST于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 at midnight, DST started in Sao Paulo
LocalDateTime d = LocalDateTime.of(, 10, 16, 0, 0, 0, 0);
ZonedDateTime z = d.atZone(zone);
System.out.println(z);// adjusted to -10-15T01:00-02:00[America/Sao_Paulo]
DST结束时:2月19日午夜时钟,时钟从18点的午夜到晚上23点向后移动1小时(偏移量从-02:00变为-03:00)。所以从23:00到23:59的所有当地时间都存在两次(在两个偏移中:-03:00和-02:00),你必须决定你想要哪一个。
默认情况下,它使用DST结束前的偏移量,但您可以使用withLaterOffsetAtOverlap()方法在DST结束后获取偏移量:
// February 19th at midnight, DST ends in Sao Paulo
// local times from 23:00 to 23:59 at 18th exist twice
LocalDateTime d = LocalDateTime.of(, 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: -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: -02-17T23:00-03:00[America/Sao_Paulo]
请注意,DST结束前后的日期具有不同的偏移量(-02:00和-03:00)。如果您正在使用具有DST的时区,请记住这些角落情况可能会发生。
根据Java 8 ZonedDateTime实现,您无法在ZonedDateTime中解析没有区域的日期。
为了满足给定的问题,你必须把try catch放在它将考虑默认时区的任何异常的情况下。
请找到修改后的程序如下:
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("-09-14 15:00:00+0530","yyyy-MM-dd HH:mm:ssZ"));
System.out.println(dateUtil.parseToZonedDateTime("-09-14 15:00:00","yyyy-MM-dd HH:mm:ss"));
}
}
有关即将推出的Java功能的更多详细信息,请参考/java-8-date-time-intro
java.time库中没有默认设置,这是一件好事 - 你所看到的就是你得到的东西,句号。
我建议如果你的日期字符串不包含Zone - 它是LocalDateTime,并且不能是ZonedDateTime,这就是你得到的异常的含义(即使由于过于灵活而导致措辞受到影响代码结构)。
我的主要建议是,如果您知道该模式没有区域信息,则解析为本地日期时间。
但是,如果你真的必须,这是另一种方法来做你想要的(一种不使用异常来控制流的替代解决方案):
TemporalAccessor parsed = f.parse(string);
if (parsed.query(TemporalQueries.zone()) == null) {
parsed = f.withZone(ZoneId.systemDefault()).parse(string);
}
return ZonedDateTime.from(parsed);
这里我们使用中间解析结果来确定字符串是否包含区域信息,如果没有,我们再次解析(使用相同的字符串,但不同的打印机解析器),这次它将包含一个区域。
或者,您可以创建此类,这将使您免于解析第二次,并且应该允许您解析分区日期时间,假设所有其他字段都在那里:
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);
}
public R query(TemporalQuery query) {
if (query == TemporalQueries.zone() || query == TemporalQueries.zoneId()) {
return (R) zone;
}
return delegate.query(query);
}
}
如果没有OFFSET_SECOND,您只需在DateTimeFormatterBuilder中添加默认值:
编辑:要获得系统的默认值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);
}
}
输出:
-09-14T15:00+05:30
-09-14T15:00+02:00
关于夏令时,使用Instant.now()的偏移量有一个棘手的情况。 示例:在我的JVM中,默认区域为America/Sao_Paulo。 冬季偏移为-03:00,夏令时(10月开始)偏移为-02:00。 如果我用params "-11-14 15:00:00","yyyy-MM-dd HH:mm:ss"调用你的方法,它将在11月11日15:00和当前偏移量(-03:00)得到一个日期,但正确的(IMO)应该是当地日期有效的偏移量 对应于输入(在这种情况下,在十一月,当它已经是DST时,因此偏移应该是-02:00)。
然后查看API并选择正确的方法:ZoneRules::getOffset vs. ZoneRules::getStandardOffset。 OP并非那么具体。
可以使用DateTimeFormatter中的withZone方法指定ZoneId:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat).withZone("+0530");
只是从我拥有的项目中复制此解决方案:
formatter = DateTimeFormatter.ofPattern(dateFormat).withZone(ZONE_UTC);
编译格式化程序后,可以调用withZone(ZoneId)创建具有设置时区的新格式化程序。
这将忽略第一个输入中的偏移+0530
然后尝试OffsetDateTime,因为Zoned需要知道区域规则。
好吧,OP要求ZonedDateTime和默认时区(不是UTC)。