Date time format for query parameter in rest url - java

I am using joda DateTime as parameter.
Use Custom Date Time format for request query parameter in spring REST
I have tried with #DateTimeFormat annotation.
I have tried with custom converter.
#GetMapping("/users/time")
public ResponseEntity<User> findByTimeRange(final DateTime from, final DateTime to) {
}
Spring REST only support "2019-09-01T7:32:56.235-05:30" but i want to use "2019-09-01T7:32:56"

Basically, you would need a custom formatter and pass parameters as Strings and parse them with it. Here is the working code using Joda Time and tested with your input:
private static final DateTimeFormatter FORMATTER =
DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss");
#GetMapping("/users/time")
public ResponseEntity<User> findByTimeRange(#RequestParam("from") String fromParam,
#RequestParam("to") String toParam) {
DateTime from = FORMATTER.parseDateTime(fromParam);
DateTime to = FORMATTER.parseDateTime(toParam);
// Do your search part...
}

Related

Spring MVC: Convert String Date into Date over REST endpoint

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.

Spring Boot change date format of request param field as DTO

My controller has a GET endpoint which accept lots of query parameters. So, instead of having multiple #QueryParam I created a CriteriaDTO in order to perform dynamic queries to our Mongo database
My controller endpoint :
#GetMapping
public List<MyObject> getAll(#Valid CriteriaDTO criteriaDTO){
return myObjectService.findAll(criteriaDTO);
}
public class CriteriaDTO {
private int offset = 0
private int limit = 20
#DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate minDate
// getters, setters ...
}
And, I want to pass the minDate is the URL with the following format yyyy-MM-dd but I need to convert it to the following format yyyy-MM-dd'T'HH:mm:ss.SSS.
My question is : Is there any annotation or something else which accepts the first format yyyy-MM-dd and automatically convert it to another ?
To be clear if I make the following call :
http://localhost:8080/api/myobject?minDate=2020-01-01
And then criteriaDTO.getminDate() will return 2020-01-01'T'00:00:00.000
Thanks for your help :)
You can do it in a more simple way than searching an annotation-magic solution.
Just add to your CriteriaDTO an additional getter for LocalDateTime:
public LocalDateTime getMinDateTime() {
return this.minDate.atTime(0, 0, 0, 0);
}
and use it wherever you need time instead of date.
Define setter and parse with SimpleDateFormat
public void setMinDate() {
if(!minDate.empty()) {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
this.minDate = formatter.parse(minDate)
}
}
I would recommend to use atStartOfDay instead of converting this 2020-01-01 to 2020-01-01'T'00:00:00.000 using custom deserializer. And also since you are manipulating the input data i would recommend to do it as separate operation
LocalDateTime date = criteriaDTO.getminDate().atStartOfDay() //2020-01-01'T'00:00
And you can also add formatter DateTimeFormatter to get required output
date.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) //2020-01-01'T'00:00:00
You have several options. Check what you exactly need,
LocalDate date = LocalDate.now();
LocalDateTime dateTime = LocalDateTime.of(date, LocalTime.MIDNIGHT);
System.out.println(dateTime); //2020-02-04T00:00:00
System.out.println(DateTimeFormatter.ISO_DATE_TIME.format(dateTime)); //2020-02-04T00:00:00
System.out.println(date.atStartOfDay()); ////2020-02-04T00:00
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS");
System.out.println(formatter.format(dateTime)); //2020-02-04T00:00:00.000
You need to modify the getter in dto to format it, for example:
class CriteriaDTO {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS");
private int offset = 0
private int limit = 20
#DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate minDate
public String getMinDate() {
return formatter.format(LocalDateTime.of(minDate, LocalTime.MIDNIGHT));
}
// getters, setters ...
}

Rest API date parameter change when run junit test

I have made a Rest API with Spring frame work, the API will take a date and find a record from database and return the record, in the database it will have a start date and end date columns, based on the given date, it should return all records that given date is in between.
I am using oracle database java 8 and spring frome work boot 2.1.2 release, the problem is when I make a junit test with Mokito to test the REST api i made, when I pass in a date, when my method receives the date it changed the date, I have found that seems when it receives the date it converted to different timezone (+1 timezone), and my local computer timeZone is set to -6 timeZone, if I covert the date to +1, the test will be success, if I just pass in the date object, it will auto convert it to +1 timezone which will be miss match.
I would like to know why is that since I testing them in the same place, how come date still get changed, and with understanding I prefer a solution of it.
so I will have a table in data base with
product_category_id(long), start_date(date), end_date(date)
and second table with
Product_id(long), type_code(short), price(double), product_category_id
and they are associated with product_category_id
here is the method in controller file
#GetMapping("/{startDate}")
public ResponseEntity<List<ProductCategoryResource>> findByDate(
#PathVariable("startDate") #DateTimeFormat(iso = DateTimeFormat.ISO.DATE) final Date startDate)
{
final List<ProductCategory> productCategoryList =
ProductCategoryService.findByDate(startDate);
final List<ProductCategoryResource> productCategoryResourceList = new ArrayList<>();
for (final ProductCategory productCategory: productCategoryList)
{
productCategoryResourceList.add(new ProductCategoryResource(productCategory));
}
return new ResponseEntity<>(ProductCategoryResourceList, HttpStatus.OK);
}
here is service
public List<ProductCategory> findByDate(final Date searchDate)
{
return ProductCategoryRepository
.findByStartDateLessThanEqualAndEndDateGreaterThanEqual(searchDate,
searchDate);
}
and repository is just an default interface extends CrudRepository, with findByStartDateLessThanEqualAndEndDateGreaterThanEqual method inside.
here is my junit test.
public void findByDate() throws Exception{
final String pattern = "yyyy-MM-dd";
final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern);
final Date searchDate = simpleDateFormat.parse("2018-05-22");
//Here mock all return values and add to result list
BDDMockito.given(ProductCategoryService.findByDate(searchDate)).willReturn(resultList);
mvc.perform(MockMvcRequestBuilders.get("/productCategory/2018-05-22")
.contentType(MediaType.APPLICATION_JSON)).andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$[0].startDate", Matchers
.startsWith(simpleDateFormat.format(resultList.get(0).getProductCategoryStartDate()))));
}
so this will give an error $[0].startDate is null, which it did not return the mocked result list, I debug and found that when it hits the method in controller, the date changed to Mon May 21 18:00:00 CST 2018, so the date was not matching to the date I give in BDDMockito.given. but if I change my local timezone to +1, it will be exactly the same
I expected no matter which timezone I run the build of the project, the test never fails, it should always send same date due to it actually passed by url.

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;
}
}

How do I control the formatting of dates in a URL with Play?

I have a Play application that should list out purchases within a given date interval. If the user does not give any date interval, it should default to showing purchases within the last year. This is done with these two methods in my controller:
public static void show(String id) {
Date today = new Date();
Calendar calendar = Calendar.getInstance();
calendar.setTime(today);
calendar.add(Calendar.YEAR, -1);
Date oneYearAgo = calendar.getTime();
showWithInterval(id, oneYearAgo, today);
}
public static void showWithInterval(String id, Date fromDate, Date toDate) {
List<Purchase> purchases= Purchase.find(id, fromDate, toDate);
render(purchases, fromDate, toDate);
}
However, this produces a url looking like this: http://localhost:9000/purchases/showwithinterval/10076430719?fromDate=ISO8601:2010-01-17T19:41:20%2B0100&toDate=ISO8601:2011-01-17T19:41:20%2B0100
This does not adhere to the date format I have specified with the date.format property in application.conf. This format is simply not usable, as I want to be able to print the dates (using ${params.fromDate}) and let my users edit them to show other intervals. I cannot format them in the view, since they are strings.
Does anyone know how to fix this?
Edit: Fixed a typo
Add fromDate et toDate to your render method :
render(purchases,fromDate,toDate);
and format them :
${fromDate.format()}
Play will format the date with your format configuration in application.conf
there are several ways to influence the format of date parametes in URLS.
play.data.binding.As Annotation
since Play 1.1 you can influence the route and data binding with this annotation. Simply add an #As annotation as follows to your date parameter:
public static void submit(#As("dd/MM/yyyy")Date myDate)
{
Logger.info("date %s", new SimpleDateFormat("dd-MM-yyyy").format(myDate));
}
more information about the annotation can be found here http://www.playframework.org/documentation/1.1/releasenotes-1.1
application.conf
take a look at the i18n/DateFormat section

Categories

Resources