AndreaNobili
AndreaNobili

Reputation: 43007

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

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="yyyy-MM-dd HH:mm:ss", timezone = JsonFormat.DEFAULT_TIMEZONE)
    @JsonProperty("Time_Stamp")
    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="yyyy-MM-dd HH:mm:ss", 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("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++;
    
}

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

ResponseEntity<Integer> 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 = "/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;
}

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

@RequestBody Map<String, Object> 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

Upvotes: 0

Views: 2611

Answers (1)

Eulodos
Eulodos

Reputation: 634

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="yyyy-MM-dd HH:mm:ss", 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 = "yyyy-MM-dd HH:mm:ss", timezone = "America/Los_Angeles") (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.

Upvotes: 1

Related Questions