Spring MVC: Convert String Date into Date over REST endpoint - java

I'm working on Spring boot project and I want to convert a String date coming from a post request
D,100000001028686,BA0884,72087000000176,N,2,147568593,DEPOSITREFERENCE,2020-08-05
20:17:33.32691,
601123,ZAR,2500,57,24,i10c=0,i20c=2,i50c=5,iR1=2,iR2=5,iR5=8,iR10=200,iR20=1,iR50=55,iR100=60,iR200=82,0,0,0,0,000
The date that I want to convert is in Bold and need to convert that part from a #PostMapping method request parameter into one of the java.time Objects.
After searching I found some solution for the data if self without using Spring but it did not work for me and used java.util.Date, here the code I wrote so far
class Main {
public static void main(String[] args) throws ParseException {
String date = "2020-08-05 20:18:33.32692";
System.out.println(covertDate(date)); //Wed Aug 05 20:19:05 UTC 2020
}
public static Date covertDate(String date) throws ParseException {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSS");
return formatter.parse(date);
}
}
The response I got is not what I'm looking for, is there any way to solve the problem

Here the solution I found after searching for future
I used Java 8 API to solve it
class Main {
public static void main(String[] args) throws ParseException {
String sDate6 = "2020-08-05 11:50:55.555";
System.out.println(covertDate(sDate6)); //2020-08-05T11:50:55.555
}
public static LocalDateTime covertDate(String date) throws ParseException {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.ENGLISH);
LocalDateTime dateTime = LocalDateTime.parse(date,formatter);
return dateTime;
}
}

In JShell (Java Version 14) I ran your code and was able to get the similar results (Granted it is in GMT and not UTC; however, the seconds are offset by the same amount as your current output):
If the question is about UTC:
I would suggest to use Instant as it avoids many of the issues that LocalDateTime has presented over the years. As mentioned in the comments is it generally best to avoid using java.util.Date and to use Instant instead (or you could use the more traditional LocalDateTime).
If you are talking about Spring's annotated #PostMapping method to parse out the date automatically you could use something like:
#PostMapping
public String postDate(#RequestParam #DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Long dateReq) {
Instant date = Instant.ofEpochMilli(dateReq);
System.out.println(date);
}
If you wanted to use your custom formatter the you could do #RequestParam #DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSSSS" LocalDateTime date) as the parameter of the postDate method.
Please note that Spring's docs state that the pattern field of #DateTimeFormat uses the same patterns as java.text.SimpleDateFormat. So that could be of use.

Related

Unable to parse string into Java 8 LocalDateTime [duplicate]

This question already has answers here:
Is java.time failing to parse fraction-of-second?
(3 answers)
Closed 2 years ago.
Running this gives me the following error, what am I missing ?
public static void main(String[] args) {
DateTimeFormatter _timestampFomatGMT = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
LocalDateTime localDateTime = LocalDateTime.parse("20200331094118137",_timestampFomatGMT);
System.out.println(localDateTime);
}
Gives me the following exception. What am I missing ?
Exception in thread "main" java.time.format.DateTimeParseException: Text '20200331094118137' could not be parsed at index 0
at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)
at java.time.LocalDateTime.parse(LocalDateTime.java:492)
at cotown.lib.common.util.JavaTimeUtil.main(JavaTimeUtil.java:90)
Java does not accept a plain Date value as DateTime.
Try using LocalDate,
public static void main(String[] args) {
DateTimeFormatter _timestampFomatGMT = DateTimeFormatter.ofPattern("yyyyMMdd");
LocalDate localDateTime = LocalDate.parse("20200331",_timestampFomatGMT);
System.out.println(localDateTime);
}
or if you really have to use LocalDateTime, then try
LocalDateTime time = LocalDate.parse("20200331", _timestampFomatGMT).atStartOfDay();
EDIT
there was a bug for this already raised https://bugs.openjdk.java.net/browse/JDK-8031085.
It is fixed in JDK 9.
You cannot parse a date-only String into a LocalDateTime without passing a time value in addition.
What you can do is use a date-only class like LocalDate similar to your code:
public static void main(String[] args) {
DateTimeFormatter _timestampFomatGMT = DateTimeFormatter.ofPattern("yyyyMMdd");
LocalDate localDate = LocalDate.parse("20200331",_timestampFomatGMT);
System.out.println(localDate);
}
That would simply output
2020-03-31
If you really need to have a LocalDateTime and the String to be parsed cannot be adjusted to include time, then pass an additional time of 0 hours and minutes with an intermediate operation like this (but keep in mind that the output will include the time information as well):
public static void main(String[] args) {
DateTimeFormatter _timestampFomatGMT = DateTimeFormatter.ofPattern("yyyyMMdd");
LocalDate localDate = LocalDate.parse("20200331",_timestampFomatGMT);
LocalDateTime localDateTime = LocalDateTime.of(localDate, LocalTime.of(0, 0));
System.out.println(localDateTime);
}
Or use LocalDateTime time = LocalDate.parse("20200331", _timestampFomatGMT).atStartOfDay(); as suggested by #Shubham.
Output would be:
2020-03-31T00:00
For outputting the date only, change the last line of the last example to
System.out.println(localDateTime.format(DateTimeFormatter.ISO_DATE));
which will only output the date part of the LocalDateTime in an ISO representation:
2020-03-31
EDIT
Targeting your latest question update, this might help:
public static void main(String[] args) {
DateTimeFormatter timestampFomatGMT = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
LocalDateTime localDateTime = LocalDateTime.parse("20200331094118137", timestampFomatGMT);
System.out.println(localDateTime);
}
Output:
2020-03-31T09:41:18.137

LocalDateTime parses invalid dates to valid date and does not throw any exception

I am using LocalDateTime in the request body of my API in Spring.
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-mm-dd HH:mm:ss")
#DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
#JsonDeserialize(using = LocalDateTimeDeserializer.class)
#JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime createdAt;
When I put an invalid date in request such as "2020-02-31 00:00:00" it is automatically converted to "2020-02-29 00:00:00". I want to throw Exception in case of an invalid date. It is mentioned in the official documentation that it converts to previous valid date .
In some cases, changing the specified field can cause the resulting date-time to become invalid,
such as changing the month from 31st January to February would make the day-of-month invalid.
In cases like this, the field is responsible for resolving the date.
Typically it will choose the previous valid date,
which would be the last valid day of February in this example.
You need to write a custom serializer for that.
class CustomLocalDateTimeSerializer extends StdSerializer<LocalDateTime> {
private static final DateTimeFormatter FORMATTER
= DateTimeFormatter.ofPattern("yyyy-mm-dd HH:mm:ss");
...
#Override
public void serialize(LocalDateTime value, JsonGenerator generator, SerializerProvider provider)
throws IOException, JsonProcessingException {
// Do your validation using FORMATTER.
// Serialize the value using generator and provider.
}
}
Then you can just use it in your annotation.
#JsonSerialize(using = CustomLocalDateTimeSerializer.class)
Note that DateTimeFormatter throws an exception when formatting/parsing invalid values.
Check out the source of LocalDateTimeSerializer to know what has to be done. Check out Jackson Date - 10. Serialize Java 8 Date Without Any Extra Dependency for examples of writing a custom serializer. This is done analogous for a custom deserializer.

is it possible to make certain part of the JsonFormat datetime optional?

I have a field which is defined with the seconds part, (i.e.: "ss") as follows
#JsonProperty("date")
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss z")
private Date date;
Is it possible to make the seconds part optional? so that both the following date String will work:
"2020-02-13T16:02:11 EST" will be parsed to "Thu Feb 13 16:02:11 EST 2020"
"2020-02-13T16:02 EST" will be parsed to "Thu Feb 13 16:02 EST 2020"
I am thinking about somethinglike (this does not work though...):
"yyyy-MM-dd'T'HH:mm[:ss] z"
thanks.
I got the answer.
add a customerized Deserializer class 'DateDeserializer.class'
the DateDeserializer.class implement both pattern with and without the second part.
code sample is attched:
#JsonDeserialize(using = DateDeserializer.class)
#JsonProperty("date")
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss z")
public class DateDeserializer extends StdDeserializer<Date> {
private static final SimpleDateFormat withSeconds = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss z");
private static final SimpleDateFormat withoutSeconds = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm z");
public DateDeserializer() {
this(null);
}
public DateDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String dateString = p.getText();
if (dateString.isEmpty()) {
//handle empty strings however you want,
//but I am setting the Date objects null
return null;
}
try {
return withSeconds.parse(dateString);
} catch (ParseException e) {
try {
return withoutSeconds.parse(dateString);
} catch (ParseException e1) {
throw new RuntimeException("Unable to parse date", e1);
}
}
}
}
Since the SimpleDateFormat class is notoriously troublesome and long outdated and also not thread-safe, I thought I’d show you how to solve your task with java.time, the modern Java date and time API. For now I am assuming that you cannot change the type of your date field, it needs to be Date, and also that you cannot change the format/s. I rewrote the class from your own answer in this way:
public class DateDeserializer extends StdDeserializer<Date> {
private static final DateTimeFormatter formatter
= DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm[:ss] z", Locale.ENGLISH);
public DateDeserializer() {
this(null);
}
public DateDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String dateString = p.getText();
if (dateString.isEmpty()) {
//handle empty strings however you want,
//but I am setting the Date objects null
return null;
}
Instant parsedInstant = formatter.parse(dateString, Instant::from);
return Date.from(parsedInstant);
}
}
In the format pattern string I am using square brackets, [], around the optional seconds of the minute. DateTimeFormatter is thread-safe (opposite SimpleDateFormat), so it is safe to have just one static instance even if used from several threads. DateTimeFormatter.parse() throws a runtime exception (unchecked exception) if it cannot parse the string, so I didn’t find any reason to make this explicit in our own code.
If you could make any of the following changes, it would improve matters still:
Educate the source of your date-time strings to use ISO 8601 format, for example 2020-02-13T16:02:11-05:00. The format they are using now is a peculiar mix of ISO 8601 and non-ISO, so you and they are really neither getting the advantages of the standard nor the full potential advantages of a more human-readable format. Formatters for ISO 8601 are built into java.time, so using the standard would save us from defining any formatter ourselves. And seconds are optional in ISO 8601, so they could still send strings with and without seconds.
Throw away the Date class. It is poorly designed and long outdated. Use Instant or another class from java.time and save the conversion that I am doing in the code.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Wikipedia article: ISO 8601

What is the Standard way to Parse different Dates passed as Query-Params to the REST API?

I am working on a REST API which supports Date as a query param. Since it is Query param it will be String. Now the Date can be sent in the following formats in the QueryParams:
yyyy-mm-dd[(T| )HH:MM:SS[.fff]][(+|-)NNNN]
It means following are valid dates:
2017-05-05 00:00:00.000+0000
2017-05-05 00:00:00.000
2017-05-05T00:00:00
2017-05-05+0000
2017-05-05
Now to parse all these different date-times i am using Java8 datetime api. The code is as shown below:
DateTimeFormatter formatter = new DateTimeFormatterBuilder().parseCaseInsensitive()
.append(DateTimeFormatter.ofPattern("yyyy-MM-dd[[ ][['T'][ ]HH:mm:ss[.SSS]][Z]"))
.toFormatter();
LocalDateTime localDateTime = null;
LocalDate localDate = null;
ZoneId zoneId = ZoneId.of(ZoneOffset.UTC.getId());
Date date = null;
try {
localDateTime = LocalDateTime.parse(datetime, formatter);
date = Date.from(localDateTime.atZone(zoneId).toInstant());
} catch (Exception exception) {
System.out.println("Inside Excpetion");
localDate = LocalDate.parse(datetime, formatter);
date = Date.from(localDate.atStartOfDay(zoneId).toInstant());
}
As can be seens from the code I am using DateTimeFormatter and appending a pattern. Now I am first trying to parse date as LocalDateTime in the try-block and if it throws an exception for cases like 2017-05-05 as no time is passed, I am using a LocalDate in the catch block.
The above approach is giving me the solution I am looking for but my questions are that is this the standard way to deal with date sent as String and is my approach is in line with those standards?
Also, If possible what is the other way I can parse the different kinds of date (shown as the Valid dates above) except some other straightforward solutions like using an Array list and putting all the possible formats and then using for-loop trying to parse the date?
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
// time is optional
.optionalStart()
.parseCaseInsensitive()
.appendPattern("[ ]['T']")
.append(DateTimeFormatter.ISO_LOCAL_TIME)
.optionalEnd()
// offset is optional
.appendPattern("[xx]")
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.parseDefaulting(ChronoField.OFFSET_SECONDS, 0)
.toFormatter();
for (String queryParam : new String[] {
"2017-05-05 00:00:00.000+0000",
"2017-05-05 00:00:00.000",
"2017-05-05T00:00:00",
"2017-05-05+0000",
"2017-05-05",
"2017-05-05T11:20:30.643+0000",
"2017-05-05 16:25:09.897+0000",
"2017-05-05 22:13:55.996",
"2017-05-05t02:24:01"
}) {
Instant inst = OffsetDateTime.parse(queryParam, formatter).toInstant();
System.out.println(inst);
}
The output from this snippet is:
2017-05-05T00:00:00Z
2017-05-05T00:00:00Z
2017-05-05T00:00:00Z
2017-05-05T00:00:00Z
2017-05-05T00:00:00Z
2017-05-05T11:20:30.643Z
2017-05-05T16:25:09.897Z
2017-05-05T22:13:55.996Z
2017-05-05T02:24:01Z
The tricks I am using include:
Optional parts may be included in either optionalStart/optionalEnd or in [] in a pattern. I use both, each where I find it easier to read, and you may prefer differently.
There are already predefined formatters for date and time of day, so I reuse those. In particular I take advantage of the fact that DateTimeFormatter.ISO_LOCAL_TIME already handles optional seconds and fraction of second.
For parsing into an OffsetDateTime to work we need to supply default values for the parts that may be missing in the query parameter. parseDefaulting does this.
In your code you are converting to a Date. The java.util.Date class is long outdated and has a number of design problems, so avoid it if you can. Instant will do fine. If you do need a Date for a legacy API that you cannot change or don’t want to change just now, convert in the same way as you do in the question.
EDIT: Now defaulting HOUR_OF_DAY, not MILLI_OF_DAY. The latter caused a conflict when only the millis were missing, but it seems the formatter is happy with just default hour of day when the time is missing.
I usually use the DateUtils.parseDate which belongs to commons-lang.
This method looks like this:
public static Date parseDate(String str,
String... parsePatterns)
throws ParseException
Here is the description:
Parses a string representing a date by trying a variety of different parsers.
The parse will try each parse pattern in turn. A parse is only deemed successful if it parses the whole of the input string. If no parse patterns match, a ParseException is thrown.
The parser will be lenient toward the parsed date.
#Configuration
public class DateTimeConfig extends WebMvcConfigurationSupport {
/**
* https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#format-configuring-formatting-globaldatetimeformat
* #return
*/
#Bean
#Override
public FormattingConversionService mvcConversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);
conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
// Register JSR-310 date conversion with a specific global format
DateTimeFormatterRegistrar dateTimeRegistrar = new DateTimeFormatterRegistrar();
dateTimeRegistrar.setDateTimeFormatter(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
dateTimeRegistrar.setDateTimeFormatter(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
dateTimeRegistrar.setDateTimeFormatter(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'"));
dateTimeRegistrar.registerFormatters(conversionService);
// Register date conversion with a specific global format
DateFormatterRegistrar dateRegistrar = new DateFormatterRegistrar();
dateRegistrar.setFormatter(new DateFormatter("yyyy-MM-dd"));
dateRegistrar.setFormatter(new DateFormatter("yyyy-MM-dd HH:mm:ss"));
dateRegistrar.setFormatter(new DateFormatter("yyyy-MM-dd'T'HH:mm:ss'Z'"));
dateRegistrar.registerFormatters(conversionService);
return conversionService;
}
}

Date difference in standalone java vs tomcat server

I'm having issues converting the date into UTC, I'm able to get this working on a standalone java program, however, while running the same method on a server gives me a different timestamp.
Here is the method I've to convert a String date into XMLGregorianCalendar in UTC.
public static XMLGregorianCalendar convertDateToXMLGregorianCalendarInUTC(String date, String dateFormat) throws ParseException, DatatypeConfigurationException {
if (StringUtils.isBlank(date))
return null;
TimeZone timeZoneInUTC = TimeZone.getTimeZone("GMT0:00");
GregorianCalendar gregorianCalendar = new GregorianCalendar(timeZoneInUTC);
DateFormat dateFormat = new SimpleDateFormat(dateFormat);
Date dateUtil = null;
XMLGregorianCalendar xmlGregorianCalendar = null;
dateUtil = dateFormat.parse(date);
gregorianCalendar.setTime(dateUtil);
xmlGregorianCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(gregorianCalendar);
return xmlGregorianCalendar;
}
With the above method when I run it on standAlone java prog. It converts the timestamp into UTC by adding 5 hours to the date passed.
public static void main(String[] args) throws ParseException, DatatypeConfigurationException {
System.out.println("DateUtil xmlGC in UTC: "+DateUtil.convertDateToXMLGregorianCalendarInUTC("03/06/2017 05:47:37", "MM/dd/yyyy HH:mm:ss"));
}
OutPut: DateUtil xmlGC in UTC: 2017-03-06T10:47:37.000Z.
However, while using the same method on tomcat - application server on the same machine, it not converting the date into UTC. Am I missing something here.,
Can someone please help me on this. Thanks in Advance.
Set Tomcat timezone
-Duser.timezone=GMT+05

Categories

Resources