Spring MVC REST Date input and timezones - spring-mvc

I'm working with Spring MVC and I have a custom date/datetime parser:
#Bean
public FormattingConversionService mvcConversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);
DateTimeFormatterRegistrar dateTimeRegistrar = new DateTimeFormatterRegistrar();
dateTimeRegistrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
dateTimeRegistrar.setDateTimeFormatter(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
dateTimeRegistrar.registerFormatters(conversionService);
DateFormatterRegistrar dateRegistrar = new DateFormatterRegistrar();
dateRegistrar.setFormatter(new DateFormatter("yyyy-MM-dd"));
dateRegistrar.registerFormatters(conversionService);
return conversionService;
}
I have some trouble with timezone and #PathVariable or #RequestBody inputs.
#GetMapping("/report/{date}")
public void testPath(#DateTimeFormat(pattern = "yyyy-MM-dd") Date date){
...
}
#PostMapping("/report")
public void testBody(#RequestBody Date filter){
...
}
Calling this URL http://localhost/report/2022-06-01 the first controller parses the date to 2022-06-01 00.00 CEST
Calling this URL http://localhost/report with this body "2022-06-01" the second controller parses the date to 2022-06-01 +02.00 CEST (adjusting the time adding +2h).
Is there any way to force the FormattingConversionService to consider the date to local timezone?

I have the same problem.
With JsonFormat you can get the same date if you set timezone to your local.
Unfortunately, there is no way to set timezone dynamically.
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd", timezone = "Europe/Warsaw")
private Date date;
In my case, it only worked for POST RequestBody parameter. I couldn't change timezone of GET path parameter.

Related

How to set default datetime format for .net core 2.0 webapi

Currently in my local. The API is expecting the same formart as in SQL Server database which is yyyy-mm-dd.
However when i am deploying the application into production server. The API is expecting the datetime format as yyyy-dd-mm.
Is there any way to configure my .net core web api to accept yyyy-mm-dd as default format in every enviroment?
Please show some code. You can try the following in the AddJsonOptions() pipeline in ConfigureServices()
services
.AddMvc()
.AddJsonOptions(options =>
{
//Set date configurations
//options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
options.SerializerSettings.DateFormatString = "yyyy-MM-dd"; // month must be capital. otherwise it gives minutes.
});
100% of the time, If I am using DateTime, I create an interface for it. It just makes life a lot easier when it's time for testing. I believe this would work for you as well.
There's a couple of reasons for this method.
It's testable.
It abstracts the dependency of DateTime out of your business logic.
If other systems in your app may need a different format, just create a new MyAppDateTimeProvider
public interface IDateTimeProvider
{
DateTime Now { get; }
string GetDateString(int year, int month, int day);
DateTime TryParse(string sqlDateString);
}
public class SqlDateTimeProvider : IDateTimeProvider
{
public DateTime Now => DateTime.UtcNow;
public string GetDateString(int year, int month, int day)
{
return new DateTime(year, month, day).ToString("yyyy-MM-dd");
}
public DateTime TryParse(string sqlDateString)
{
var result = new DateTime();
DateTime.TryParse(sqlDateString, out result);
return result;
}
}

Convert asp.net / MS proprietary json Dateformat to java8 LocalDateTime with jackson while deserializing json to object

I call a webservice from a Spring Boot App, using jackson-jsr-310 as maven dependency for being able to make use of LocalDateTime:
RestTemplate restTemplate = new RestTemplate();
HttpHeaders httpHeaders = this.createHeaders();
ResponseEntity<String> response;
response = restTemplate.exchange(uri,HttpMethod.GET,new HttpEntity<Object>(httpHeaders),String.class);
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
BusinessPartner test = mapper.readValue(response.getBody(), BusinessPartner.class);
My problem is in the last line, the code produces this error:
java.time.format.DateTimeParseException: Text '/Date(591321600000)/' could not be parsed at index 0
The resulting JSON in response.getBody() looks like this:
{
"d":{
...
"Address":{...},
"FirstName":"asd",
"LastName":"asd",
"BirthDate":"\/Date(591321600000)\/",
}
}
And in my model class, I have the following member:
#JsonProperty("BirthDate")
private LocalDateTime birthDate;
So, after a bit of searching here I found out that this /Date(...)/ seems to be a Microsoft-proprietary Dateformat, which Jackson cannot deserialize into an object per default.
Some questions advise to create a custom SimpleDateFormat and apply it to the opbject mapper, which I tried to do, but then I think I miss the right syntax for mapper.setDateFormat(new SimpleDateFormat("..."));
I tried with e.g. mapper.setDateFormat(new SimpleDateFormat("/Date(S)/"));
or at the end even mapper.setDateFormat(new SimpleDateFormat("SSSSSSSSSSSS)"));
but it seems this does not work, too, so I am out of ideas for now and hope some people here could help me out.
edit 1:
further investigated, it seems one way to go is to write a custom DateDeSerializer for jackson. So I tried this:
#Component
public class JsonDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
private DateTimeFormatter formatter;
private JsonDateTimeDeserializer() {
this(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
public JsonDateTimeDeserializer(DateTimeFormatter formatter) {
this.formatter = formatter;
}
#Override
public LocalDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException
{
if (parser.hasTokenId(JsonTokenId.ID_STRING)) {
String unixEpochString = parser.getText().trim();
unixEpochString = unixEpochString.replaceAll("[^\\d.]", "");
long unixTime = Long.valueOf(unixEpochString);
if (unixEpochString.length() == 0) {
return null;
}
LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(unixTime), ZoneId.systemDefault());
localDateTime.format(formatter);
return localDateTime;
}
return null;
}
}
which actually returns nearly what I want, annotating my fields in the model using
#JsonDeserialize(using = JsonDateTimeDeserializer.class)
but not exactly:
This code returns a LocalDateTime of value: 1988-09-27T01:00.
But in the thirdparty system, the xmlvalue is 1988-09-27T00:00:00.
As it is obvious, the ZoneId here:
LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(unixTime), ZoneId.systemDefault());
is the Problem, apart from a wrong dateformat.
So could someone here please help me out in how to switch to always use zeros for the time-part and to get my dateformat right? Would be great!
I'm assuming that the number 591321600000 is the epoch milli (number of milliseconds from 1970-01-01T00:00:00Z).
If that's the case, I think that SimpleDateFormat can't help you (at least I couldn't find a way to parse a date from the epoch milli using this class). The pattern S (according to javadoc) is used to format or parse the milliseconds field of a time (so its maximum value is 999) and won't work for your case.
The only way I could make it work is creating a custom deserializer.
First, I created this class:
public class SimpleDateTest {
#JsonProperty("BirthDate")
private LocalDateTime birthDate;
// getter and setter
}
Then I created the custom deserializer and added it to a custom module:
// I'll explain all the details below
public class CustomDateDeserializer extends JsonDeserializer<LocalDateTime> {
#Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String s = p.getText(); // s is "/Date(591321600000)/"
// assuming the format is always /Date(number)/
long millis = Long.parseLong(s.replaceAll("\\/Date\\((\\d+)\\)\\/", "$1"));
Instant instant = Instant.ofEpochMilli(millis); // 1988-09-27T00:00:00Z
// instant is in UTC (no timezone assigned to it)
// to get the local datetime, you must provide a timezone
// I'm just using system's default, but you must use whatever timezone your system uses
return instant.atZone(ZoneId.systemDefault()).toLocalDateTime();
}
}
public class CustomDateModule extends SimpleModule {
public CustomDateModule() {
addDeserializer(LocalDateTime.class, new CustomDateDeserializer());
}
}
Then I added this module to my mapper and it worked:
// using reduced JSON with only the relevant field
String json = "{ \"BirthDate\": \"\\/Date(591321600000)\\/\" }";
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
// add my custom module
mapper.registerModule(new CustomDateModule());
SimpleDateTest value = mapper.readValue(json, SimpleDateTest.class);
System.out.println(value.getBirthDate()); // 1988-09-26T21:00
Now some comments about the deserializer method.
First I converted the millis 591321600000 to an Instant (a class that represents a UTC instant). 591321600000 in millis is equivalent to 1988-09-27T00:00:00Z.
But that's the UTC date/time. To get the local date and time, you must know in what timezone you are, because in every timezone it's a different date and time (everybody in the world are at the same instant, but their local date/time might be different, depending on where they are).
In my example, I just used ZoneId.systemDefault(), which gets the default timezone of my system. But if you don't want to depend on the default and want to use a specific timezone, use the ZoneId.of("timezone name") method (you can get the list of all available timezones names with ZoneId.getAvailableZoneIds() - this method returns all valid names accepted by the ZoneId.of() method).
As my default timezone is America/Sao_Paulo, this code sets the birthDate to 1988-09-26T21:00.
If you don't want to convert to a specific timezone, you can use the ZoneOffset.UTC. So, in the deserializer method, the last line will be:
return instant.atZone(ZoneOffset.UTC).toLocalDateTime();
Now the local date will be 1988-09-27T00:00 - as we're using UTC offset, there's no timezone conversion and the local date/time is not changed.
PS: if you need to convert the birthDate back to MS's custom format, you can write a custom serializer and add to the custom module as well. To convert a LocalDateTime to that format, you can do:
LocalDateTime birthDate = value.getBirthDate();
// you must know in what zone you are to convert it to epoch milli (using default as an example)
Instant instant = birthDate.atZone(ZoneId.systemDefault()).toInstant();
String msFormat = "/Date(" + instant.toEpochMilli() + ")/";
System.out.println(msFormat); // /Date(591321600000)/
Note that, to convert a LocalDateTime to Instant, you must know in what timezone you are. In this case, I recommend to use the same timezone for serializing and deserializing (in your case, you can use ZoneOffset.UTC instead of ZoneId.systemDefault().
Here's some Groovy code I wrote that also handles the timezone offset: https://gist.github.com/jeffsheets/938733963c03208afd74927fb6130884
class JsonDotNetLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
#Override
LocalDateTime deserialize(JsonParser parser, DeserializationContext ctxt) {
convertDotNetDateToJava(parser.text.trim())
}
/**
* Returns a Java LocalDateTime when given a .Net Date String
* /Date(1535491858840-0500)/
*/
static LocalDateTime convertDotNetDateToJava(String dotNetDate) {
// Strip the prefix and suffix to just 1535491858840-0500
String epochAndOffset = dotNetDate[6..-3]
// 1535491858840
String epoch = epochAndOffset[0..-6]
// -0500 Note, keep the negative/positive indicator
String offset = epochAndOffset[-5..-1]
ZoneId zoneId = ZoneId.of("UTC${offset}")
LocalDateTime.ofInstant(Instant.ofEpochMilli(epoch.toLong()), zoneId)
}
}

Convert Unix timestamp to Java Date, Spring RequestParam

Following is a request fullcalendar js send to the server.
http://localhost:8080/NVB/rest/calendar/events?start=1425168000&end=1428796800 400
How to specify Date pattern (#DateTimeFormat) in Spring Request Param to convert this time to a Date object. I tried different patterns but getting 405 Bad Request.
#RequestMapping(value = "/events", method = RequestMethod.GET)
public #ResponseBody List<EventDto> addOrder(#RequestParam(value = "start") #DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Date start,
#RequestParam(value = "end") #DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Date end) {
LOGGER.info("Requesting event from [{}] to [{}]", start, end);
return new LinkedList<EventDto>();
}
Since timestamps aren't a formatted date (going by Java's SimpleDateFormat options), but more a numeric value: I would recommend making a custom data-binder for Date objects if you're doing this more often than this single instance. See http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#portlet-ann-webdatabinder
As a one-off solution you can bind them to Long parameters and create your own Date object with new Date(start).
Using #InitBinder and WebDataBinder:
#RestController
public class SimpleController {
//... your handlers here...
#InitBinder
public void initBinder(final WebDataBinder webdataBinder) {
webdataBinder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
#Override
public void setAsText(String text) throws IllegalArgumentException {
setValue(new Date(Long.valueOf(text)));
}
});
}
}

How to return ISO.DATE_TIME formatted value from variable defined as java.sql.Timestamp using Spring MVC

If I want to persist into database a date as a timestamp I have to define the entity class using the java.sql.Timestamp type for the date variable as shown below.
But doing so result in the controller returning an epoch time format in the json message sent back to the browser.
If I want it in the format of yyyy-MM-dd'T'HH:mm:ss.SSS'Z' in the json message sent back to the browser, how can I do this? I tried using the #DateTimeFormat annotation on the entity class's date variable but it doesn't make any differences.
#Entity
public class Party implements Serializable {
#Column(nullable=false)
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#ManyToOne(cascade={CascadeType.ALL},targetEntity=Organizer.class)
#JoinColumn(name="id_Organizer",referencedColumnName="id",nullable=false,updatable=false)
private Organizer id_Organizer;
#Column(name="startDate")
#DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
private Timestamp start;
}
Controller looks like this
#RequestMapping(value = "/createtestarray", method=RequestMethod.POST, produces = "application/json")
#ResponseBody
public List<Party> createParty(Model model) {
List<Party> PartyList = new ArrayList<>();
Party Party = Generator.createParty();
PartyList.add(Party);
return PartyList;
}
There are actually 2 ways of doing this.
Method 1
Annotate with #JsonFormat as suggested and show below for the fields wanted to be printed in a specific format.
#Column(name = "startDate")
#Basic
#JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
private Timestamp start;
Method 2
Customize the getters to return a String value formatted in the format wanted, rather than using the default ones which returns Timestamp.

spring boot Joda DateTime Serialisation

I'm trying to serialize Joda DateTime properties as ISO-8601 using Spring Boot v1.2.0.BUILD-SNAPSHOT
Here is my very simple REST Application.
#RestController
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class Application {
class Info{
private DateTime dateTime;
public Info(){
dateTime = new DateTime();
}
public DateTime getDateTime() {
return dateTime;
}
public void setDateTime(DateTime dateTime) {
this.dateTime = dateTime;
}
}
#RequestMapping("/info")
Info info() {
return new Info();
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public Module getModule(){
return new JodaModule();
}
}
The dateTime is being serialized as a timestamp e.g. {"dateTime":1415954873412}
I've tried adding
#Bean
#Primary
public ObjectMapper getObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,
false);
return objectMapper;
}
but that didn't help either. So how do I configure Jackson in Spring Boot to serialize using the ISO-8601 format?
BTW: I only added the following dependencies to my Gradle build
compile("joda-time:joda-time:2.4")
compile("org.jadira.usertype:usertype.jodatime:2.0.1")
compile("com.fasterxml.jackson.datatype:jackson-datatype-joda:2.4.2");
Since you're using Spring Boot 1.2 you should be able to simply add the following to your application.properties file:
spring.jackson.serialization.write_dates_as_timestamps=false
This will give output in the form:
{
"dateTime": "2014-11-18T19:01:38.352Z"
}
If you need a custom format you can configure the JodaModule directly, for example to drop the time part:
#Bean
public JodaModule jacksonJodaModule() {
JodaModule module = new JodaModule();
DateTimeFormatterFactory formatterFactory = new DateTimeFormatterFactory();
formatterFactory.setIso(ISO.DATE);
module.addSerializer(DateTime.class, new DateTimeSerializer(
new JacksonJodaFormat(formatterFactory.createDateTimeFormatter()
.withZoneUTC())));
return module;
}
With Spring Boot 1.2 you can use a fluent builder for building ObjectMapper instance:
#Bean
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
return builder
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.build();
}
As for JodaModule, it will be autoconfigured when com.fasterxml.jackson.datatype:jackson-datatype-joda is on classpath.
Pass a new JodaModule() to the constructor of your object mapper.
Annotate your Info methods with the ISO pattern
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ")
or I think you can use this if you're using spring
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DateTimeFormat.ISO.DATE_TIME)
Add this to your application.* in your resources. (I use yamel so it's .yml for me, but should be .properties by default)
spring.jackson.date-format: yyyy-MM-dd'T'HH:mm:ssZ
Or whatever format you want.
There is also a joda-date-time-format property (I think this property appeared for the first time in Spring boot 1.3.x versions) that you can set in your application.properties which will work for jackson serialization/deserialization:
From: https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
spring.jackson.joda-date-time-format= # Joda date time format string. If not configured, "date-format" will be used as a fallback if it is configured with a format string.
So if you want to use the ISO format you can set it like this:
spring.jackson.joda-date-time-format=yyyy-MM-dd'T'HH:mm:ss.SSSZ
You can have different number of 'Z' which changes the way the time zone id or offset hours are shown, from the joda time documentation (http://joda-time.sourceforge.net/apidocs/org/joda/time/format/DateTimeFormat.html):
Zone: 'Z' outputs offset without a colon, 'ZZ' outputs the offset with a colon, 'ZZZ' or more outputs the zone id.

Resources