Sending an object containing a Date field as JSON to an API the time zone seems to be changed and the time "change"

huangapple go评论48阅读模式
英文:

Sending an object containing a Date field as JSON to an API the time zone seems to be changed and the time "change"

问题

我正在处理两个Spring Boot项目,它们之间进行通信(第一个项目调用第二个项目上定义的POST API,并向其发送JSON格式的DTO对象。目前,这两个项目在同一台计算机上运行,因此它们应该具有相同的时区(我认为...)

我在正确发送日期格式方面遇到了问题。我将尝试解释我正在做的事情以及我遇到的问题。

在第一个项目中,我有这个DTO对象:

import java.util.Date;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;

@JsonPropertyOrder(alphabetic=true)
public class OneRowReadTrain1DTO {
    
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = JsonFormat.DEFAULT_TIMEZONE)
    @JsonProperty("Time_Stamp")
    private Date timeStamp;
    
    private Double v1;
    private Double v2;
    // ......................................
    // ......................................
    // ......................................
    // 构造函数和Getter和Setter方法
}

如您所见,我有一个带有此注释的timeStamp字段,以将字段转换为JSON:

@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = JsonFormat.DEFAULT_TIMEZONE)

它还应该设置默认时区。

然后,在这个第一个项目的一个类中,我有这个执行POST请求到第二个项目中定义的API的方法:

@Scheduled(fixedRate=90000)
public void insertOneRowReadTrain1Job() {
    System.out.println("COUNTER: " + counter);
    
    OneRowReadTrain1DTO currentElement = excelRowAsDtoList.get(counter);
    System.out.println("CURRENT DTO: " + currentElement);
    
    String insertApiUrl = baseUrl + "oneRowReadTrain1/";
    try {
        uri = new URI(insertApiUrl);
    } catch (URISyntaxException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    
    ResponseEntity<Integer> result = restTemplate.postForEntity(uri, currentElement, Integer.class);
    
    System.out.println("result: " + result);
    System.out.println("--------------------------------------------------------------------------------------------"); 
    counter++;
}

如您所见,我通过这行执行API调用:

ResponseEntity<Integer> result = restTemplate.postForEntity(uri, currentElement, Integer.class);

使用调试器,timeStamp字段的值似乎是正确的,事实上,这个字段的值是:

OneRowReadTrain1DTO [timeStamp=Sat Oct 17 06:00:14 PDT 2009, v1=6.5718506, v2=538.47812, ......]

如您所见,这个Date字段的值是timeStamp=Sat Oct 17 06:00:14,这是预期的值。

然后,调用第二个项目中定义的API,在这里,我对这个字段的值获得了奇怪的行为。

在第二个项目中,调用的API如下:

@PostMapping(value = "/train/{trainName}", consumes = "application/json")
@CrossOrigin(origins = "*", allowedHeaders = "*")
public int insertTrend(@PathVariable("trainName") String trainName, 
                       @RequestBody Map<String, Object> jsonTrainInfo) throws IOException {
    
    int result = 0;
    
    System.out.println("trainName: " + trainName);
    System.out.println("JSON: " + jsonTrainInfo);
    
    result = trainService.insertOneRowReadTrain(trainName, jsonTrainInfo);
    
    return result;
}

如您所见,获得的有效负载位于此方法参数中:

@RequestBody Map<String, Object> jsonTrainInfo

问题是我得到了类似于以下的内容:

{Time_Stamp=2009-10-17 13:00:14, v1=6.5718506,.....}

如您所见,这个字段的值是Time_Stamp=2009-10-17 13:00:14,其中日期部分(2009-10-17)是正确的,但时间部分完全错误,事实上,获得的时间部分是13:00:14,而不是预期的06:00:14(发送对象中存在的时间)。

根据我所知,6:00 PDT等于13:00 GMT,但为什么会出现这个问题?我需要接收到的日期与相同的时区(或者我漏掉了什么?)

为什么当它收到Map<String, Object> jsonTrainInfo时,时区似乎已经改变了?

出了什么问题?我漏掉了什么?我该如何尝试解决这个问题?我都要疯了。

英文:

I am working on 2 Spring Boot projects that comunicate between them (the first project call a POST API defined on the second project and send to it a DTO object in JSON format. Both the projects at the moment runs on the same machine so they should have the same timezone (I suppose...)

I have a problem sending correctly a Date format. I will try to explain what I am doing and what problem I am facing.

In the first project I have this DTO object:

import java.util.Date;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;

@JsonPropertyOrder(alphabetic=true)
public class OneRowReadTrain1DTO {
    
    @JsonFormat(pattern=&quot;yyyy-MM-dd HH:mm:ss&quot;, timezone = JsonFormat.DEFAULT_TIMEZONE)
    @JsonProperty(&quot;Time_Stamp&quot;)
    private Date timeStamp;
    
    private Double v1;
    private Double v2;
    ........................................
    ........................................
    ........................................
    CONSTRUCTOR AND GETTER AND SETTER METHODS
}

As you can see I have this timeStamp field that is annotated with this annotation to convert the field in JSON:

@JsonFormat(pattern=&quot;yyyy-MM-dd HH:mm:ss&quot;, timezone = JsonFormat.DEFAULT_TIMEZONE)

it should also set the default timezone

Then into a class of this first project I have this method that perform the POST request to the API defined in the second project:

@Scheduled(fixedRate=90000)
public void insertOneRowReadTrain1Job() {
    System.out.println(&quot;COUNTER: &quot; + counter);
    
    OneRowReadTrain1DTO currentElement = excelRowAsDtoList.get(counter);
    System.out.println(&quot;CURRENT DTO: &quot; + currentElement);
    
    String insertApiUrl = baseUrl + &quot;oneRowReadTrain1/&quot;;
    try {
        uri = new URI(insertApiUrl);
    } catch (URISyntaxException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    
    ResponseEntity&lt;Integer&gt; result = restTemplate.postForEntity(uri, currentElement, Integer.class);
    
    System.out.println(&quot;result: &quot; + result);
    System.out.println(&quot;--------------------------------------------------------------------------------------------&quot;); 
    counter++;
    
}

As you can see I am performing the API call by this line:

ResponseEntity&lt;Integer&gt; result = restTemplate.postForEntity(uri, currentElement, Integer.class);

Using the debugger the timeStamp field value seems to be correct, infact this field value is:

OneRowReadTrain1DTO [timeStamp=Sat Oct 17 06:00:14 PDT 2009, v1=6.5718506, v2=538.47812, ......]

As you can see the value of this Date field is timeStamp=Sat Oct 17 06:00:14 and it is the expected value.

Then the API defined in the second project is called and here I am obtaining a strange behavior with this field value.

In the second project the called API is:

@PostMapping(value = &quot;/train/{trainName}&quot;, consumes = &quot;application/json&quot;)
@CrossOrigin(origins = &quot;*&quot;, allowedHeaders = &quot;*&quot;)
public int insertTrend(@PathVariable(&quot;trainName&quot;) String trainName, 
                       @RequestBody Map&lt;String, Object&gt; jsonTrainInfo) throws IOException {
    
    int result = 0;
    
    System.out.println(&quot;trainName: &quot; + trainName);
    System.out.println(&quot;JSON: &quot; + jsonTrainInfo);
    
    result = trainService.insertOneRowReadTrain(trainName, jsonTrainInfo);
    
    return result;
}

As you can see the obtained payload is into this method parameter:

@RequestBody Map&lt;String, Object&gt; jsonTrainInfo

The problem is that I am obtaining something like this:

{Time_Stamp=2009-10-17 13:00:14, v1=6.5718506,.....}

As you can see the value of this field is: Time_Stamp=2009-10-17 13:00:14 where the date section (2009-10-17) is correct but the time section is totally wrong, infact the obtained time section is 13:00:14 and not the expected 06:00:14 (the one that is present into the sent object).

Now from what I know 6:00 PDT equals 13:00 GMT but why have I this problem? I need that the received date is in the same time zone (or am I missing something?)

Why when it is received into the Map<String, Object> jsonTrainInfo the timezone seems to be changed?

What is wrong? What am I missing? How can I try to fix this issue? I am going crazy

答案1

得分: 1

Date

在你的第一个项目中,你将时间戳存储为java.util.Date,它以毫秒为单位存储信息,从"纪元"(1970年1月1日,00:00:00 GMT)开始计算。由于我没有你的整个代码,我假设你在调试器中看到的是你的类的toString()表示。在这里,你看到了正确的输出,因为java.util.Date在内部使用系统本地时区,但背后的值只是一个瞬间,没有任何关于时区的信息。

Serialization

你的OneRowReadTrain1DTO类使用以下注解指定日期格式:
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = JsonFormat.DEFAULT_TIMEZONE),快速查看时区属性的文档:

用于序列化的时区(如果需要)。可以使用DEFAULT_TIMEZONE的特殊值表示"只使用默认时区",其中默认时区由序列化上下文指定,默认为系统默认时区(UTC),除非明确设置为另一个时区。
默认值:"##default"

然后,查看文档对DEFAULT_TIMEZONE的说明:

表示应使用默认时区(来自反序列化或序列化上下文)的值:注解不定义要使用的值。
注意:这里的默认值不是指JVM默认值,而是Jackson数据绑定的默认值,通常为UTC,但可以在ObjectMapper上进行更改。

配置 Jackson

默认情况下,Spring Boot使用Jackson来序列化/反序列化对象,具体来说是MappingJackson2HttpMessageConverter。它使用Jackson的ObjectMapper来读取/写入数据。当你将时区设置为JsonFormat.DEFAULT_TIMEZONE时,它实际上是UTC,因此你在其他应用程序中收到的时间戳位于不同的时区。

如果你的数据应始终包含在"PDT"时区的日期,你可以在DTO的注解中设置它,就像这样:
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "America/Los_Angeles")
(如果你想知道为什么我使用America/Los_Angeles时区,请参考这里)。
另一件你可以尝试的事情是将spring.jackson.time-zone=America/Los_Angeles添加到你的application.properties中,这将在整个应用程序中设置JacksonProperties中的时区。

最后说明

提供的解决方案应该适用于java.util.Date,但请注意,不鼓励使用这个类,考虑使用Java 8的java.time包中的类。

英文:

Date

In your first project, you store timestamp as java.util.Date which stores information in milliseconds since "the epoch" (January 1, 1970, 00:00:00 GMT). Since I don't have your entire code I'm assuming that what you see in the debugger is the toString() representation of your class. Here you see the correct output, because java.util.Date uses internally system local timezone, but the value behind is just an instant, without any information about timezones.

Serialization

Your OneRowReadTrain1DTO class specifies date format with the:
@JsonFormat(pattern=&quot;yyyy-MM-dd HH:mm:ss&quot;, timezone = JsonFormat.DEFAULT_TIMEZONE) annotation, a quick look into the documentation of timezone property:

> TimeZone to use for serialization (if needed). Special value of DEFAULT_TIMEZONE can be used to mean "just use the default", where default is specified by the serialization context, which in turn defaults to system default (UTC) unless explicitly set to another timezone.
Default:
"##default"

And further, see what documentation says about DEFAULT_TIMEZONE:

> Value that indicates that default TimeZone (from deserialization or serialization context) should be used: annotation does not define value to use.
NOTE: default here does NOT mean JVM defaults but Jackson databindings default, usually UTC, but may be changed on ObjectMapper.

Configuring Jackson

By default spring boot uses Jackson to serialize/deserialize objects and specifically MappingJackson2HttpMessageConverter. It uses Jackson's ObjectMapper to write/read data. When you set timezone to JsonFormat.DEFAULT_TIMEZONE, it turns out to be UTC, hence you receive in the other app timestamp in the different timezone.

If your data should always contain dates in the PDT "timezone", you could set it in the annotation in your DTO, like this:
@JsonFormat(pattern = &quot;yyyy-MM-dd HH:mm:ss&quot;, timezone = &quot;America/Los_Angeles&quot;)
(If you are wondering why do I use America/Los_Angeles timezone it's explained here).
One other thing you could try is to add spring.jackson.time-zone=America/Los_Angeles to your application.properties, which will set timezone in JacksonProperties used in your entire application.

Final Note

The provided solution should work with java.util.Date, note however that it is discouraged to use this class, consider using classes from the java 8 java.time package instead.

huangapple
  • 本文由 发表于 2020年7月29日 06:21:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/63143631.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定