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.
Related
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;
}
}
I Usig SpringBoot and try to convert this return to my object
...
{
"data": "2015-05-29",
"codigo": 618393,
"apresentante": null,
"total": 6,
"desconto": 0,
"pago": 6
},
...
so I have one object with
private Date data;
and I have one SimpleDateFormat:
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
System.out.println(sdf.format(myObjet.getData()));
And print
28/05/2015, but the correct are 29/05/2015
my application.properties have this lines:
spring.jackson.date-format=yyyy-MM-dd
spring.jackson.time-zone=America/Sao_Paulo
anyone know why this happen?
tks
First, you suspect the Jackson configuration interferes with the SimpleDateFormat parsing/formatting, but it is wrong: although Jackson uses SimpleDateFormat to parse/format code, the configuration of spring.jackson.* will not affect each and every SimpleDateFormat instance you create in the application. They are two separate things.
You must use an ObjectMapper, and #Autowired, to use some context configurations; although I suspect, as I observed, spring.jackson.time-zone config does not affect the result of parsing/formatting of json/Date, i.e., Jackson internally does not use this value in serialization/deserialization.
My conclusion is that neither spring.jackson.time-zone either mapper.setTimezone() will affect the processing of timezone conversion in the (de)serialization, when we set #JsonFormat(timezone = "xxx"); the latter seems to override the former two; and, the default timezone of application will be the "target" timezone when converting.
I suggest you to attach another short but complete test case to show what is working and what is not.
Check this test of mine, and try to post a complete test like this:
#Test
public void testSimpleDateFormat() throws Exception {
System.out.println("The default timezone is: " + TimeZone.getDefault().getDisplayName());
SimpleDateFormat f = new SimpleDateFormat("yyyy/MM/dd");
String ds = "2015/05/29";
Date d = f.parse(ds);
System.out.println(d);
}
Be sure to begin with easy tests and add more variables into it little by little.
Use LocalDate and add the zone you need
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
String date = "16/08/2016";
LocalDate localDate= ZonedDateTime.of(LocalDateTime.parse(date,
formatter), ZoneId.of("Europe/Helsinki")).toLocalDate();
I'm trying to convert this string: 2014-01-01 00:00:00 to Joda DateTime
I have tried the following:
public static DateTime SimpleIso8601StringToDateTime(String date) {
DateTimeFormatter df = DateTimeFormat.forPattern(CONSTS_APP_GENERAL.SIMPLE_DATE_FORMAT);
return df.parseDateTime(date);
}
And also the following:
public static DateTime SimpleIso8601StringToDateTime(String date) {
DateTime dtDate = DateTime.parse(date, DateTimeFormat.forPattern(CONSTS_APP_GENERAL.SIMPLE_DATE_FORMAT));
return dtDate;
}
Where
public static final String SIMPLE_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS";
However, using the debugger I get to the formatting line and then while trying to process it the program cursor never comes back.
I should mention that this is an Android project.
Any ideas what might be the problem?
It’s because 2014-01-01 00:00:00 doesn’t match the pattern yyyy-MM-dd HH:mm:ss.SSS—there’s no fractional part on the seconds in your input.
The result is that an unhandled exception gets raised—I’m not familiar with how Android handles those, the thread probably just dies unless you set a handler. But putting the parse() call inside a try block should let you recover.
I am executing a query
select level as VERSION,
name as DESCRIPTION,code as CODE,created as RELEASEDATE
,SUBSTR(name, INSTR(name, '.',-1)+1) as TYPE
from mytable
where code = 'HX56UO'
which gives result like
Type Version Description Release Date
.exe 3.0.2 MPC560xB MCAL Autosar 3.x 15sep2011_00:00:00
I need release date like this Sept 15th 2011 and i need to set this result value in a bean set method.
public void setLastDate(java.util.Calendar newLastPubDate) {
lastDate = newLastDate;
}
what type casting i need to do in setting the above value from result set
since the set method is of calender.
i tried like setting the value like this
if (rs.getDate("RELEASEDATE") != null ) {
Bean.setLastPubDate(rs.getdate"RELEASEDATE"));
}
But it is not date format,its of calender how to set the value.
Please help me solving this.
Thanks.
if (rs.getDate("RELEASEDATE") != null ) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(rs.getDate("RELEASEDATE"));
Bean.setLastPubDate(calendar);
}
EDIT:
When you later want to convert the calendar to a string (to display it), you can use a SimpleDateFormat.
String formatted = new SimpleDateFormat("dd MMMM yyyy").format(bean.getLastPubDate().getTime());
As your created column is a VARCHAR2 - very bad practice to not store a date as a DATE but we'll skip that for now - you need to convert it to a date type somewhere, and in the query is possibly simplest:
select level as VERSION,
name as DESCRIPTION,
code as CODE,
TO_DATE(created,'DDMONYYYY_HH24:MI:SS') as RELEASEDATE,
SUBSTR(name, INSTR(name, '.',-1)+1) as TYPE
from mytable
where code = 'HX56UO'
You can still use getDate() from the resultset, and need to convert that Date to a Calendar object as you call your bean method:
Bean.setLastPubDate(Calendar.getInstance().setTime(rs.getDate("RELEASEDATE"));
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