Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/spring-mvc/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
为什么Spring在绑定带有@DateTimeFormat(iso=DateTimeFormat.iso.DATE\U time)注释的LocalDateTime时会忽略输入字符串的时间偏移?_Spring_Spring Mvc_Datetime_Java Time - Fatal编程技术网

为什么Spring在绑定带有@DateTimeFormat(iso=DateTimeFormat.iso.DATE\U time)注释的LocalDateTime时会忽略输入字符串的时间偏移?

为什么Spring在绑定带有@DateTimeFormat(iso=DateTimeFormat.iso.DATE\U time)注释的LocalDateTime时会忽略输入字符串的时间偏移?,spring,spring-mvc,datetime,java-time,Spring,Spring Mvc,Datetime,Java Time,我有一个带有以下字段的对象活动: @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) private LocalDateTime start; 我正在通过发送以下表单将此对象绑定到控制器方法: @RequestMapping(value = "update", method = RequestMethod.POST) public String submitUpdateActivityForm(Activity activity) {

我有一个带有以下字段的对象
活动

@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private LocalDateTime start;
我正在通过发送以下表单将此对象绑定到控制器方法:

@RequestMapping(value = "update", method = RequestMethod.POST)
public String submitUpdateActivityForm(Activity activity) {
      activityRepository.save(activity);
      return "successPage;
}
我使用的是Spring Boot 1.5.1、Spring MVC 4.3.6,在我的web应用程序中,我希望从任何时区的客户端接收时间,但始终保持
LocalDateTime
为UTC。但当Spring将表单中的对象绑定到控制器中的请求参数时,它会完全忽略类型为
LocalDateTime
的属性的输入字符串的时间偏移量

我想,根据
@DateTimeFormat(iso=DateTimeFormat.iso.DATE\u TIME)的文档,
Spring将在字符串中找到偏移量,并将输入的日期时间从给定的时区转换为我的服务器的时区(UTC),然后绑定

例如:我希望字符串
2017-05-31T12:00-03:00
转换为
LocalDateTime'2017-05-31T15:00'
2017-05-31T12:00Z
被解释为
LocalDateTime'2017-05-31T12:00',
。不幸的是,无论时间偏移量是多少,我总是得到
LocalDateTime

@DateTimeFormat
是正确的行为还是我做错了什么?我应该实现Spring
转换器
还是扩展
属性编辑器支持


这是处理时间的正确方法吗?我想接受任何类型的日期,但将它们保留在
LocalDateTime
中的UTC中,因为这样我就可以轻松地将它们发送到客户端,在那里我可以将它们从UTC转换为客户端的本地时区并显示。

LocalDateTime
没有任何时区/偏移信息。因此我怀疑问题在于(尽管我没有在Spring环境中进行测试):Spring将字符串解析为
OffsetDateTime
(因为格式包含偏移量,比如
-03:00
)并获取其本地日期时间部分(去掉偏移量)。或者做其他类似的事情,但忽略偏移量

执行此操作时,不会进行时间转换。因此,我认为最好的解决方案是将字段更改为
OffsetDateTime
Instant
(请参阅下面的更多详细信息)。如果不可能,您可以使用以下代码将此
OffsetDateTime
转换为
LocalDateTime

// convert OffsetDateTime to LocalDateTime (converting the time to UTC)
LocalDateTime localDateTime = OffsetDateTime.parse("2017-05-31T12:00-03:00")
                                  // change to UTC ("sameInstant" converts the time)
                                  .withOffsetSameInstant(ZoneOffset.UTC)
                                  // get only localdatetime part (without offset)
                                  .toLocalDateTime();
System.out.println(localDateTime);
withOffsetSameInstant(ZoneOffset.UTC)
将时间转换为UTC。因此,上述代码的输出为:

2017-05-31T15:00

您还可以使用相同的代码解析UTC字符串:

localDateTime = OffsetDateTime.parse("2017-05-31T12:00Z")
                    .withOffsetSameInstant(ZoneOffset.UTC)
                    .toLocalDateTime();
System.out.println(localDateTime);
请注意,在这种情况下,
withOffsetSameInstant(ZoneOffset.UTC)
是多余的,因为字符串已经在UTC()中。但是你可以毫无问题地离开它。 输出将是:

2017-05-31T12:00


错误原因可能是什么 请注意,如果不将
与OffsetSameInstant一起使用,则会得到不正确的结果:

localDateTime = OffsetDateTime.parse("2017-05-31T12:00-03:00").toLocalDateTime();
System.out.println(localDateTime); // 2017-05-31T12:00 (12h instead of 15h)
我怀疑Spring就是这么做的。或者,它可能正在使用忽略偏移量的解析器解析
LocalDateTime
——与此非常类似:

System.out.println(LocalDateTime.parse("2017-05-31T12:00-03:00",
                                       DateTimeFormatter.ISO_DATE_TIME));
// output is 2017-05-31T12:00
无论如何,Spring忽略了偏移量。您可以尝试编写转换器(使用上面描述的代码)或使用下面描述的方法


处理时区时的更好方法 在我看来,在处理可以处理多个时区和偏移的日期/时间时,使用
LocalDateTime
并不是最好的方法。这是因为
LocalDateTime
没有任何时区/偏移量信息,无法正确处理

我认为在这种情况下最好的方法是使用
OffsetDateTime
Instant
。基于以下原因,我认为
Instant
是最好的选择

如果选择将字段类型更改为
OffsetDateTime
,则可以使用
withOffsetSameInstant(ZoneOffset.UTC)
将其转换为UTC:

要将此
OffsetDateTime
转换为另一个时区并返回UTC,您可以执行以下操作:

OffsetDateTime utcOffset = OffsetDateTime.parse("2017-05-31T12:00-03:00").withOffsetSameInstant(ZoneOffset.UTC);
System.out.println(utcOffset); // 2017-05-31T15:00Z

// convert to London timezone
ZonedDateTime z = utcOffset.atZoneSameInstant(ZoneId.of("Europe/London"));
System.out.println(z); // 2017-05-31T16:00+01:00[Europe/London]

// convert back to UTC
System.out.println(z.withZoneSameInstant(ZoneOffset.UTC)); // 2017-05-31T15:00Z
请注意,UTC的15小时在伦敦是16小时,因为5月份是英国的夏季,API会自动处理


如果选择使用
即时
(UTC即时,独立于时区/偏移),则可以通过以下方式对其进行解析:

// 2017-05-31T15:00:00Z
System.out.println(OffsetDateTime.parse("2017-05-31T12:00-03:00").toInstant());

// 2017-05-31T12:00:00Z
System.out.println(OffsetDateTime.parse("2017-05-31T12:00Z").toInstant());
如果您想明确说明日期/时间是UTC,我相信
Instant
是最佳选择。如果使用
LocalDateTime
,则不清楚它在哪个时区(实际上,因为此类没有此类信息,从技术上讲,它不在任何时区),并且必须记住(或将此信息存储在其他任何地方)。使用
Instant
,UTC的用法是明确的

使用
Instant
,从另一个时区到另一个时区的转换非常容易:

// UTC instant (2017-05-31T15:00:00Z)
Instant instant = OffsetDateTime.parse("2017-05-31T12:00-03:00").toInstant();

// converts to London timezone
ZonedDateTime london = instant.atZone(ZoneId.of("Europe/London"));
System.out.println(london); // 2017-05-31T16:00+01:00[Europe/London] 
// ** note that 15h in UTC is 16h in London, because in May it's British's Summer Time

// converts back to UTC instant
System.out.println(london.toInstant()); // 2017-05-31T15:00:00Z
请注意,伦敦的夏季时间也适用

Instant
转换为
OffsetDateTime
也很简单:

// converts to offset +05:00
OffsetDateTime odt = instant.atOffset(ZoneOffset.ofHours(5));
System.out.println(odt); // 2017-05-31T20:00+05:00

// converts back to UTC instant
System.out.println(odt.toInstant()); // 2017-05-31T15:00:00Z

但如果您使用
LocalDateTime
,则在处理某些情况时并不明显:

// LocalDateTime (2017-05-31T15:00)
LocalDateTime dt = OffsetDateTime.parse("2017-05-31T12:00-03:00")
                      .withOffsetSameInstant(ZoneOffset.UTC)
                      .toLocalDateTime();

// converts to London timezone (wrong way)
ZonedDateTime wrongLondon = dt.atZone(ZoneId.of("Europe/London"));
System.out.println(wrongLondon); // 2017-05-31T15:00+01:00[Europe/London] (hour is 15 instead of 16)

// converts to London timezone (right way: first convert to UTC, then to London)
ZonedDateTime correctLondon = dt.atZone(ZoneOffset.UTC).withZoneSameInstant(ZoneId.of("Europe/London"));
System.out.println(correctLondon); // 2017-05-31T16:00+01:00[Europe/London]
PS:当我说“错误的方式”时,我的意思是“你的情况是错误的”。如果我的本地时间是10小时,并且我想创建一个表示“伦敦时区10小时”的对象,
localDateTime.atZone()。但在您的情况下,10小时是UTC时间。但由于对象是本地对象,因此在转换到另一时区之前,需要将其转换为UTC。这就是为什么
LocalDateTime
不适合您的情况

当转换回
LocalDateTime
时,在获取本地部分之前,必须注意转换为UTC:

// wrong: 2017-05-31T16:00
System.out.println(correctLondon.toLocalDateTime());

// correct: 2017-05-31T15:00
System.out.println(correctLondon.withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime());

因此,IMO使用
Instant
更为直接,因为UTC的使用是明确的,并且与其他时区的转换也很容易。但是如果您不能更改字段的类型,您可以转换
Loc
// wrong: 2017-05-31T16:00
System.out.println(correctLondon.toLocalDateTime());

// correct: 2017-05-31T15:00
System.out.println(correctLondon.withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime());