Spring Controller converts time to it's Local TimeZone - spring

I have spring rest service project and I am using JPA hibernate and I am facing a strange issue in my controller. Below is code snippet:
controller:
#RequestMapping(method = RequestMethod.POST, value=PATH_SEPERATOR+ CREATE_OR_UPDATE_EVENT_METHOD, headers = ACCEPT_APPLICATION_JSON, produces = APPLICATION_JSON)
#ResponseStatus(HttpStatus.CREATED)
#ResponseBody
ResponseBean createOrUpdateEvent(#RequestBody Event eventBean)
{
ResponseBean response = new ResponseBean();
try {
String sysId = eventService.createOrUpdateEvent(eventBean);
response.setStatus(OK);
response.setData(sysId);
} catch(Exception ex) {
response = handleException(CREATE_OR_UPDATE_EVENT_METHOD, ex);
return response;
}
return response;
}
Event.java
#Entity
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "sysId", scope = Event.class)
#Table(name = "event")
public class Event {
#Column(name = "date_time")
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss")
private Date dateTime;
public Date getDateTime() {
return dateTime;
}
public void setDateTime(Date dateTime) {
this.dateTime = dateTime;
}
}
When I pass date to Event bean in createOrUpdateEvent method as String "2014-04-17T17:15:56" which is in IST timezone, the controller convert it to Date with datetime "2014-04-17T22:45:56" IST considering previous time as UTC time. I don't understand this behaviour of auto conversion. I assume that it's because I am accepting parameter as bean, where bean is JPA Entity. Please help me to fix it.

There are several things you must take into consideration. First and foremost you are lacking a time zone information in the provided JSON serialization format "yyyy-MM-dd'T'HH:mm:ss". There's a format character that adds it - Z. Using it should be something like "yyyy-MM-dd'T'HH:mm:ssZ" depending on your preferences. Another thing you should consider is the fact that java.util.Date is not TimeZone aware and when you are creating a new Date(long) it always assumes that the passed date is in the current time zone.
So in order to fix this issue you have to add (and pass) the time zone as I told you and the Json parser will do the rest.

Related

LastModifiedDate is not getting updated for an entity using spring-data-elasticsearch

// Entity model definition
#Builder.Default
#LastModifiedDate
#Field(type = FieldType.Date, name = "modified_dt", format = DateFormat.basic_date_time)
private Instant modifiedDate = Instant.now();
// update attribute call
protected void updateAttribute(String id, Document document) throws Exception {
UpdateQuery updateQuery = UpdateQuery.builder(id).withDocument(document).build();
UpdateResponse updateResponse = operations.update(updateQuery, indexCoordinates());
Result result = updateResponse.getResult();
if (!result.equals(Result.UPDATED)) {
throw new Exception();
}
}
From version 7 the date format is different since yyyy has been replaced by uuuu, maybe it's related to this:
https://www.elastic.co/guide/en/elasticsearch/reference/current/migrate-to-java-time.html#java-time-migration-incompatible-date-formats
Also the Lombok annotation #Builder.Default won't work for parameterless constructors, so maybe it's also interfering.

dealing with exceptions in #JsonFormat pattern

I am using the #JsonFormat annotation from the fasterxml.jackson library:
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private Date endDate;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private Date startDateInUtc;
When the date format isn't correct, I don't get an exception, but instead, get the wrong date object (e.g. month 13 becomes January).
Based on my research, I came across two possible solutions:
Playing with the ObjectMapper. (with the setDateFormat function)
Creating my own Json Deserializer class, that will throw an error when the format is not valid:
public class JsonDateDeserializer extends JsonDeserializer<Date> {
public static final String DATE_FORMAT = "yyyy-MM-dd";
#Override
public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT);
format.setLenient(false);
String dateString = jsonParser.getText();
try {
return format.parse(dateString);
} catch (ParseException e) {
throw new InvalidFormatException(String.format("Date format should be %s", DATE_FORMAT), dateString, Date.class);
}
}
}
With neither solution can I specify a different format for different fields.
Although I can define multiple deserializers, it looks to me like a pretty ugly way to do this.
I thought that the #JsonFormat annotation was designed to deal with different formats of dates in different fields, but as I said, there is no exception when an invalid format is entered.
Could someone suggest an elegant solution to this problem?

Spring boot - date in response is not well formatted

I have the following entity column definition:
#Column(name= "time")
#Temporal(TemporalType.TIMESTAMP)
private java.util.Calendar time;
When I query and return my data as JSON:
modules = this.moduleStatsRepository.findAll();
JsonArray modulesArray = Application.gson.fromJson(Application.gson.toJson(modules), JsonArray.class);
JsonObject modulesJson = new JsonObject();
modulesJson.add("modules", modulesArray);
modulesJson.addProperty("triggerTimeShortSec", configurationManager.startupConfig.get("stats_trigger_time_sec"));
modulesJson.addProperty("triggerTimeLongSec", Integer.parseInt(configurationManager.startupConfig.get("stats_trigger_time_sec")) * 3);
return Application.gson.toJson(modulesJson);
the time is returned as an object, not really ideal:
Is there any way to customize gson settings to parse dates as ISO 8601?
Many of these things come out of the box with Jackson. With Gson it doesn't seem that there is an option to configure ISO 8601 timestamps, so you'll have to write it yourself by registering a JsonSerializer<Calendar> and perhaps also a JsonDeserializer<Calendar>.
For example, a simplified ISO 8601 string to calendar converter could look like this:
public class CalendarISO8601Serializer implements JsonSerializer<Calendar>, JsonDeserializer<Calendar> {
private static final SimpleDateFormat FORMATTER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
#Override
public Calendar deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
try {
Calendar instance = Calendar.getInstance();
instance.setTime(FORMATTER.parse(jsonElement.getAsString()));
return instance;
} catch (ParseException e) {
throw new JsonParseException(e);
}
}
#Override
public JsonElement serialize(Calendar calendar, Type type, JsonSerializationContext jsonSerializationContext) {
return new JsonPrimitive(FORMATTER.format(calendar.getTime()));
}
}
This means you can no longer rely on the default Gson object created by Spring boot since I don't think it will automatically pick up the serializer as a type adapter. To solve this, you need to create your own Gson bean and add the serializer:
#Bean
public Gson gson() {
return new GsonBuilder()
.registerTypeHierarchyAdapter(Calendar.class, new CalendarISO8601Serializer())
.create();
}
But considering that you're using a public static field (Application.gson), you may want to see for yourself how you want to register that adapter.
just use java.util.Date as Type in your Entity:
#Temporal(TemporalType.TIMESTAMP)
private Date date;

How do I make a spring-boot request method accept an iso formatted date param?

I'm trying to write a controller request method that accepts a date parameter that is sent as an iso formatted date time string. It looks like you can specify a format manually, annotating the method parameter with #DateTimeFormat(pattern="yyyy-MM-dd") but I want to use the iso
setting. I.e. #DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME). Using the ISO Date Time format I can't even get it to parse the example date in the documentation. I'm wondering if I'm doing something wrong.
Here is my controller class:
#RestController
public class DateController {
#RequestMapping(path = "/echoIsoDate", method = RequestMethod.GET)
public ResponseEntity<String> echoIsoDate(#RequestParam("date") #DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date date){
return new ResponseEntity<>(date.toString(), HttpStatus.OK);
}
#RequestMapping(path = "/echoDumbDate", method = RequestMethod.GET)
public ResponseEntity<String> echoDumbDate(#RequestParam("date") #DateTimeFormat(pattern = "yyyy-MM-dd") Date date){
return new ResponseEntity<>(date.toString(), HttpStatus.OK);
}
}
When I try to call this controller with the date I actually want to parse it doesn't work:
http://localhost:8080/echoIsoDate?date=2015-12-30T00:00:00.000Z
When I try to call this controller with the example date from the documentation it doesn't work:
http://localhost:8080/echoIsoDate?date=2000-10-31%2001:30:00.000-05:00
The second controller method does work. e.g calling http://localhost:8080/echoDumbDate?date=1970-01-01 returns Thu Jan 01 00:00:00 CST 1970 (But then it's in CST, which presumably is in my system timezone).
Questions:
What am I doing wrong in echoIsoDate()? Or is there a bug in Spring?
For echoDumbDate() is there a way to specify the timezone I want, so that it will always use UTC?
try this instead
#RequestMapping(path = "/echoIsoDate", method = RequestMethod.GET)
public ResponseEntity<String> echoIsoDate(#RequestParam("date") #DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") Date date){
return new ResponseEntity<>(date.toString(), HttpStatus.OK);
}

How to handle a request with multiple parameters on Spring-MVC

I am sending following request that need to be handled by Spring-MVC but it does not.
http://localhost:2001/MyProject/flights/find?origin=LAX&destination=WA&departure=&arrival=&return=false
Code
#Controller
#RequestMapping("/flights")
public class FlightController {
#RequestMapping(value = "/find?origin={origin}&destination={destination}&departure={departure}&arrival={arrival}&return={return}", method = RequestMethod.GET)
public String findFlight(#PathVariable String origin,
String destination, Date departure, Date arrival, boolean return) {
That is not the correct way (or place) to use #PathVariable. You need to use #RequestParam.
#Controller
#RequestMapping("/flights")
public class FlightController {
#RequestMapping("/find")
public String findFlight(#RequestParam String origin
, #RequestParam String destination
, #RequestParam(required = false) Date departure
, #RequestParam(required = false) Date arrival
, #RequestParam(defaultValue = "false", required = false, value = "return") Boolean ret) { ... }
}
Note that return is a keyword in Java so you cannot use it as a method parameter name.
You will also have to add a java.beans.PropertyEditor for reading the dates because the dates will (presumably) be in a specific format.
Try this, may be it works:
#RequestMapping("/find")
public String findFlight(#RequestParam("origin") String origin
, #RequestParam("destination") String destination,....

Resources