In a spring project, I'd like to create a LocalDate from an #Autowired constructor parameter whose value is in a .properties file. Two things I'd like to do:
1. If the property file contains the property my.date, the parameter should be created by parsing the property value
When the property is set, and when I use the following:
#DateTimeFormat(pattern = "yyyy-MM-dd") #Value("${my.date}") LocalDate myDate,
...
I get this error:
java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'java.time.LocalDate': no matching editors or conversion strategy found
I have also used the iso = ... to use an ISO date with the same result.
2. If the property is not in the properties file, the parameter should be created using LocalDate.now()
I tried using a default value as such:
#Value("${my.date:#{T(java.time.LocalDate).now()}}") LocalDate myDate,
...
But I get the same error.
Forgive my ignorance with Spring, but how can I achieve the two objectives here?
I know two ways. One is generic for any object - to use #Value annotation on custom setter
#Component
public class Example {
private LocalDate localDate;
#Value("${property.name}")
private void setLocalDate(String localDateStr) {
if (localDateStr != null && !localDateStr.isEmpty()) {
localDate = LocalDate.parse(localDateStr);
}
}
}
The second is for LocalDate/LocalDateTime
public class Example {
#Value("#{T(java.time.LocalDate).parse('${property.name}')}")
private LocalDate localDate;
}
Sample property:
property.name=2018-06-20
Spring Boot 2.5, works perfect:
application.yaml
my.date: 2021-08-14
my.time: "11:00"
#Service
public class TestService {
#Value("${my.date}")
#DateTimeFormat(pattern = "yyyy-MM-dd")
LocalDate myDate;
#Value("${my.time}")
#DateTimeFormat(iso = DateTimeFormat.ISO.TIME)
LocalTime myTime;
}
If you want to specify the date format as well then use following on the field:
#Value("#{T(java.time.LocalDate).parse('${date.from.properties.file}', T(java.time.format.DateTimeFormatter).ofPattern('${date.format.from.properties.file}'))}")
Try to add this into your properties file:
spring.jackson.date-format=com.fasterxml.jackson.databind.util.ISO8601DateFormat
spring.jackson.time-zone=UTC
and remove #DateTimeFormat annotation
Concerning LocalDate.now() initialization. Try to use field injection this way:
#Value("${my.date}") LocalDate myDate = LocalDate.now();
As mentioned in other answer by Pavel there are two ways.
I am providing similar two ways with modification to handle 2nd Point by OP.
If the property is not in the properties file, the parameter should be
created using LocalDate.now()
#Component
public class Example {
private LocalDate localDate;
#Value("${property.name}")
private void setLocalDate(String localDateStr) {
if (localDateStr != null && !localDateStr.isEmpty()) {
localDate = LocalDate.parse(localDateStr, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
}else{
localDate = LocalDate.now();
}
}
}
I Prefere 2nd way though...
public class Example {
#Value("#{T(java.time.LocalDate).parse('${property.name}', T(java.time.format.DateTimeFormatter).ofPattern('yyyy-MM-dd')) ?: T(java.time.LocalDate).now()}")
private LocalDate localDate;
}
Edit:- Fixed 2nd Way
#Value("#{ !('${date:}'.equals('')) ? T(java.time.LocalDate).parse('${date:}', T(java.time.format.DateTimeFormatter).ofPattern('MM-dd-yyyy')) " +
":T(java.time.LocalDate).now()}")
private LocalDate asOfDate;
For the first bit, you could create a converter:
#Component
#ConfigurationPropertiesBinding
public class LocalDateConverter implements Converter<String, LocalDate> {
#Override
public LocalDate convert(String s) {
if (s == null) {
return null;
}
return LocalDate.parse(s);
}
}
Your config Class will automatically use this for conversion.
For the 2nd you can just do:
if(my.date == null) iso = LocalDate.now()
There's an example of initializing a LocalDateTime value using annotations and configuration properties. I've tested that it works in Spring Boot 2.4.
MyComponent.kt fragment:
#Value("\${my.date}")
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
val myDate: LocalDateTime
application.yml:
my:
date: "2023-01-23T00:00:00"
Related
I am using MongoDBTempalate with Springboot and trying to aggregate data basis LocalDateTime in which I am getting this error : org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.time.LocalDateTime] to type [java.util.Date]
I tried adding a custom convertor but it did not help, the code I added is :
`#Bean
public MongoCustomConversions customConversions(){
List<Converter<?,?>> converters = new ArrayList<>();
converters.add(DateToLocalDateTimeConverter.INSTANCE);
converters.add( LocalDateTimeToDateConverter.INSTANCE);
return new MongoCustomConversions(converters);
}
enum DateToLocalDateTimeConverter implements Converter<Date, LocalDateTime> {
INSTANCE;
#Override
public LocalDateTime convert(Date source) {
return ofInstant(source.toInstant(), systemDefault());
}
}
enum LocalDateTimeToDateConverter implements Converter<LocalDateTime, Date> {
INSTANCE;
#Override
public Date convert(LocalDateTime source) {
return Date.from(source.toInstant(ZoneOffset.UTC));
}
}`
Can someone tell me where have I gone wrong in creating the convertor, or is there some alternative apart from changing the LocalDateTime to Date in the code, as the occurance are very huge and refactoring might take a lot of time and effort
You can try this annotation on your entity or DTO, it will automatically format the date.
#Document(collection = "sample")
public class Foo{
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = 'yyyy-MM-dd HH:mm', locale = "en-PH")
private Date createdAt;
}
Hi I am seeking for some help,
previously I used to get my datetime field as 2022-06-30T22:39:22.235+0000 format but after adding one internal library(Org level) datetime fields are coming as 1656628762235 in json response.
I have tried to add #JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSSXXX") in my dto level but no luck also I tried adding below properties in yaml files.
spring.jackson.serialization.write-dates-as-timestamps: true
jackson-datatype-jsr310 version is : 2.9.10
jackson-databind : 2.9.10
spring-boot-2.1.11.RELEASE
please suggest what other option I can try to get the timestamp as in iso format.
#Setter
#Getter
public class TestRequestFormDto implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private UUID trfId;
private String testType;
private ItemDto item;
private List<RepItemsMappingDto> repItems;
private AdditionalQuestionsDto additionalQuestions;
private String hasRepItems;
private TestRequestInfoDto testRequestInfo;
private ItemInformationDto itemInformation;
private LabDto lab;
private Timestamp createdDate;
private String createdBy;
private String status;
private Timestamp modifiedDate;
private String modifiedBy;
private String pageLeftFrom;
private String referenceNumber;
private TrfVendorDto trfVendor;
private TrfFactoryDto trfFactory;
private String originCountry;
private String itemsVbu;
private String versionNumber;
}
previous date format:
{"createdDate": "2022-06-30T22:39:22.235+0000"}
current date format:
{"createdDate": 1656628762235}
Use modern Time API
java.util.Date and its subclasses are legacy.
Since Java 8 (which was released about 10 years ago) we have modern Time API which includes Instant, LocalDateTime and other classes from the java.time package.
java.sql.Timestamp as well as Date is obsolete and discouraged to be used.
You're advised to use java.time.Instant instead (if you have the ability to change the types in your DTO).
Seconds to Instant
The task would be simple if your timestamp were represented in seconds (unfortunately it's not, but for completeness I'll describe this option).
In this case, you would need to have a Bean of type JavaTimeModule in the Spring Contexts. Spring-Boot will do the rest for you. JacksonAutoConfiguration will grab the module and register it automatically.
#Configuration
public class JsonConfig {
#Bean
public JavaTimeModule javaTimeModule() {
return new JavaTimeModule();
}
}
Note: if the target field is of type Instant no extra annotations required.
Milliseconds to Instant
In case if your timestamp represented in milliseconds and autoconfiguration would not work (you can try it, the parsed date would be very far in the future).
To resolve this problem, we need to configure ObjectMapper manually by registering JavaTimeModule module and instructing mapper about the precision of the timestamp. For that, would need to place two Beans Jackson2ObjectMapperBuilder and ObjectMapper in the Spring's Context.
#Configuration
public class JsonConfig {
#Bean
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
return new Jackson2ObjectMapperBuilder();
}
#Bean
public ObjectMapper objectMapper() {
return jackson2ObjectMapperBuilder()
.build()
.registerModule(new JavaTimeModule())
.configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false);
}
}
And as I've said before, no additional steps and no extra annotations on fields required (unless the property name doesn't match).
Usage example:
Input JSON:
{"timestamp": 1656628762235}
Output:
[2022,6,30,22,39,22,235000000]
If you can't a change the data type
As the last resort if you're forced to use legacy date-time representations, here's a couple of possible solutions:
You can declare all-args constructor in your POJO and annotate each argument with #JsonProperty. The trick is to declare the corresponding arguments of type long and parse milliseconds to java.sql.Timestamp manually. But judging by the number of field you have, this approach doesn't look like the best option.
Another option would be to implement a custom Deserializer and annotate the fields of type Timestamp with #JsonDeserialize annotation.
I want to accept Arabic date in the query parameter
ex:
٢٠٢١-٠٧-٢٠ the English version of this date is 2021-07-20
I have found this soltion
DecimalStyle defaultDecimalStyle
= DateTimeFormatter.ISO_LOCAL_DATE.getDecimalStyle();
DateTimeFormatter arabicDateFormatter = DateTimeFormatter.ISO_LOCAL_DATE
.withDecimalStyle(defaultDecimalStyle.withZeroDigit('\u0660'));
String encodedArabicDateStr = "%D9%A2%D9%A0%D9%A1%D9%A9-%D9%A0%D9%A4-%D9%A1%D9%A5";
String arabicDateStr
= URLDecoder.decode(encodedArabicDateStr, StandardCharsets.UTF_8);
LocalDate date = LocalDate.parse(arabicDateStr, arabicDateFormatter);
System.out.println("Parsed date: " + date);
And it works as expected
now, in my Spring Boot application, I handle the LocalDate with #DateTimeFormat with pattern
#DateTimeFormat(pattern = "yyyy-MM-dd") val fromDate: LocalDate
How I can tell the Spring Boot to use a custom method to parse the date?
The original problem is when someone send Arabic date the application it will crash with the below error:
org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'dateRange' on field 'fromDate': rejected value [٢٠٢١-٠٧-٢٠];
codes [typeMismatch.dateRange.fromDate,typeMismatch.fromDate,typeMismatch.java.time.LocalDate,typeMismatch];
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [dateRange.fromDate,fromDate];
arguments [];
default message [fromDate]];
default message [Failed to convert value of type 'java.lang.String[]' to required type 'java.time.LocalDate';
nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [#org.springframework.format.annotation.DateTimeFormat java.time.LocalDate] for value '٢٠٢١-٠٧-٢٠'; nested exception is java.lang.IllegalArgumentException: Parse attempt failed for value [٢٠٢١-٠٧-٢٠]]
I have tried:
update request values by filter but it does not work.
FormattingConversionService like this sample.
You can create your own custom deserializer for LocalDate which will contain the format field.
Creating of custom deserializer for LocalDate
public class CustomDateDeserializer extends JsonDeserializer<LocalDate> {
#Override
public LocalDate deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
DecimalStyle defaultDecimalStyle = DateTimeFormatter.ISO_LOCAL_DATE.getDecimalStyle();
DateTimeFormatter arabicDateFormatter = DateTimeFormatter.ISO_LOCAL_DATE.withDecimalStyle(defaultDecimalStyle.withZeroDigit('\u0660'));
String arabicDateStr = URLDecoder.decode(jsonParser.getText(), StandardCharsets.UTF_8);
return LocalDate.parse(arabicDateStr, arabicDateFormatter);
}
}
Using the custom deserializer for LocalDate
#JsonSerialize(using = CustomDateDeserializer.class)
private LocalDate fromDate;
Since you need to parse the date from query parameter, you need to implement the logic as a Converter.
#Component
public class ArabicDateConverter implements Converter<String, LocalDate> {
private final DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE
.withDecimalStyle(DateTimeFormatter.ISO_LOCAL_DATE.getDecimalStyle().withZeroDigit('\u0660'));
#Override
public LocalDate convert(String source) {
return LocalDate.parse(source, formatter);
}
}
Note #Component, it's used to register the converter.
Keep in mind that this converts only arabic dates, it will fail for other formats. If you need to parse others, you may need to keep a list of possible formats to iterate over until something matches.
Thanks to this thread, I was able to register and use a custom Converter for org.joda.time.DateTime using JPA EclipseLink. Here is a sample use (only the relevant parts):
#Converter(name = "jodaTimeConverter", converterClass = JodaDateTimeConverter.class)
public class MyEntity{
#Column(name = "creationdate")
#Temporal(TemporalType.TIMESTAMP)
#Convert("jodaTimeConverter")
private DateTime creationdate;
}
I have many entity classes an most of them have a DateTime field. My question is thus: is it possible to register the converter once somewhere, so that all DateTime fields are automatically converted ?
I could obviously copy-paste the annotations everywhere, but a more DRY method would be appreciated.
What you're trying to use is a proprietary mechanism that would only work in EclipseLink, so leaving your code non-portable.
A better option, if using JPA 2.1, is to make use of AttributeConverter, and set the converter itself to "autoApply". This means that it will be applied to all fields of the specified type without having to annotate every field. And with that you get portability too
if you are using spring boot and the AttributeConverter.
in Application.java
#EntityScan(basePackageClasses = {Application.class, JpaConverters.class})
public class JpaConverters {
#Converter(
autoApply = true
)
public static class DateTimeOffsetToOffsetDateTimeConverter implements AttributeConverter<OffsetDateTime,
DateTimeOffset> {
#Override
public OffsetDateTime convertToEntityAttribute(DateTimeOffset dateTimeOffset) {
if (dateTimeOffset == null) {
return null;
}
OffsetDateTime utc = OffsetDateTime.ofInstant(dateTimeOffset.getTimestamp().toInstant(), UTC);
int offsetSeconds = Math.toIntExact(MINUTES.toSeconds(dateTimeOffset.getMinutesOffset()));
ZoneOffset offset = ZoneOffset.ofTotalSeconds(offsetSeconds);
OffsetDateTime offsetDateTime = utc.withOffsetSameInstant(offset);
return offsetDateTime;
}
#Override
public DateTimeOffset convertToDatabaseColumn(OffsetDateTime date) {
if (date == null) {
return null;
}
Timestamp timestamp = Timestamp.from(date.toInstant());
int offsetSeconds = date.getOffset().getTotalSeconds();
DateTimeOffset dateTimeOffset = DateTimeOffset.valueOf(timestamp, Math.toIntExact(SECONDS.toMinutes(offsetSeconds)));
return dateTimeOffset ;
}
}
}
I have an application that uses springs #PropertySource to create a bean parsed from my property-file.
Later I try to inject some of those values into a field using #Value.
property-file:
# StartDate in format yyyy-MM-dd
startDate=2013-12-05
# EndDate in format yyyy-MM-dd
endDate=2013-12-06
BeanConfig:
#Configuration
#PropertySource("classpath:wfmConfig.properties")
public class WfmConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
Injection:
#Value("#(Calendar.getInstance().setTime(new SimpleDateFormat(\"yyyy-MM- dd\").parse(\"$endDate}\"))}")
private Calendar endDate;
This spEL-parsing works wonderful for Date-objects as stated here.
Why doesnt this work? Is there any way to utilize Calendar here instead of Date?