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 ;
}
}
}
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.
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"
I am attempting to persist a java.time.LocalDateTime object in my Cassandra database and keep it timezone agnostic. I am using Spring Data Cassandra to do this.
The problem is that somewhere along the line, something is treating these LocalDateTime objects as if they are in the timezone of my server, and offsetting them to UTC time when it stores them in the database.
Is this a bug or a feature? Can I work around it in some way?
Configuration:
#Configuration
#EnableCassandraRepositories(
basePackages = "my.base.package")
public class CassandraConfig extends AbstractCassandraConfiguration{
#Override
protected String getKeyspaceName() {
return "keyspacename";
}
#Bean
public CassandraClusterFactoryBean cluster() {
CassandraClusterFactoryBean cluster =
new CassandraClusterFactoryBean();
cluster.setContactPoints("127.0.0.1");
cluster.setPort(9142);
return cluster;
}
#Bean
public CassandraMappingContext cassandraMapping()
throws ClassNotFoundException {
return new BasicCassandraMappingContext();
}
}
Booking record I wish to persist:
#Table("booking")
public class BookingRecord {
#PrimaryKeyColumn(
ordinal = 0,
type = PrimaryKeyType.PARTITIONED
)
private UUID bookingId = null;
#PrimaryKeyColumn(
ordinal = 1,
type = PrimaryKeyType.CLUSTERED,
ordering = Ordering.ASCENDING
)
private LocalDateTime startTime = null;
...
}
Simple Repository Interface:
#Repository
public interface BookingRepository extends CassandraRepository<BookingRecord> { }
Save Call:
...
#Autowired
BookingRepository bookingRepository;
...
public void saveBookingRecord(BookingRecord bookingRecord) {
bookingRepository.save(bookingRecord);
}
Here is the string used to populate the starttime in BookingRecord:
"startTime": "2017-06-10T10:00:00Z"
And here is the output from cqlsh after the timestamp has been persisted:
cqlsh:keyspacename> select * from booking ;
bookingid | starttime
--------------------------------------+--------------------------------
8b640c30-4c94-11e7-898b-6dab708ec5b4 | 2017-06-10 15:00:00.000000+0000
Cassandra stores a Date (timestamp) as milliseconds since epoch without a specific timezone information. Timezone data is handled in the layers above Cassandra.
LocalDate/LocalDateTime represent a point in time relative to your local time. Before the date/time can be saved, it needs to be enhanced with a timezone to calculate the generic representation, which can be saved.
Spring Data uses your system-default timezone (Date.from(source.atZone(systemDefault()).toInstant())).
If you need timezone precision and want to omit any implicit timezone conversions, use java.util.Date directly which corresponds with Cassandra's (well, it's the Datastax Driver to be precise) storage format representation.
I do actually want to use LocalDateTime and LocalDate in my project, rather than java.util.Date, since they are newer and have more attractive functionality.
After much searching I have found a workaround.
First, you must create custom implementations of Spring's Converter interface as follows:
One for Date to LocalDateTime:
public class DateToLocalDateTime implements Converter<Date, LocalDateTime> {
#Override
public LocalDateTime convert(Date source) {
return source == null ? null : LocalDateTime.ofInstant(source.toInstant(), ZoneOffset.UTC);
}
}
And one for LocalDateTime to Date:
public class LocalDateTimeToDate implements Converter<LocalDateTime, Date> {
#Override
public Date convert(LocalDateTime source) {
return source == null ? null : Date.from(source.toInstant(ZoneOffset.UTC));
}
}
Finally, you must override the customConversions method in CassandraConfig as follows:
#Configuration
#EnableCassandraRepositories(basePackages = "my.base.package")
public class CassandraConfig extends AbstractCassandraConfiguration{
#Override
protected String getKeyspaceName() {
return "keyspacename";
}
#Override
public CustomConversions customConversions() {
List<Converter> converters = new ArrayList<>();
converters.add(new DateToLocalDateTime());
converters.add(new LocalDateTimeToDate());
return new CustomConversions(converters);
}
#Bean
public CassandraClusterFactoryBean cluster() {
CassandraClusterFactoryBean cluster =
new CassandraClusterFactoryBean();
cluster.setContactPoints("127.0.0.1");
cluster.setPort(9142);
return cluster;
}
#Bean
public CassandraMappingContext cassandraMapping()
throws ClassNotFoundException {
return new BasicCassandraMappingContext();
}
}
Thanks to mp911de for putting me in the ballpark of where to look for the solution!
I want my auditable (#CreatedDate and #LastModifiedDate) MongoDB document to work with ZonedDateTime fields.
Apparently this type is not supported by Spring Data (have a look at org.springframework.data.auditing.AnnotationAuditingMetadata).
Framework version: Spring Boot 2.0.0 and Spring Data MongoDB 2.0.0
Spring Data auditing error:
java.lang.IllegalArgumentException: Invalid date type for member <MEMBER NAME>!
Supported types are [org.joda.time.DateTime, org.joda.time.LocalDateTime, java.util.Date, java.lang.Long, long].
Mongo configuration:
#Configuration
#EnableMongoAuditing
public class MongoConfiguration {
}
The auditable entity:
public abstract class BaseDocument {
#CreatedDate
private ZonedDateTime createdDate;
#LastModifiedDate
private ZonedDateTime lastModifiedDate;
}
Things I tried
I also tried creating a custom converter for ZonedDateTime, but it is not considered by Spring Data. The class DateConvertingAuditableBeanWrapper has a ConversionService which is configured in the constructor method with JodaTimeConverters, Jsr310Converters and ThreeTenBackPortConverters.
Custom converter:
#Component
public class LocalDateTimeToZonedDateTimeConverter implements Converter<LocalDateTime, ZonedDateTime> {
#Override
public ZonedDateTime convert(LocalDateTime source) {
return source.atZone(ZoneId.systemDefault());
}
}
Spring Data DateConvertingAuditableBeanWrapper:
class DefaultAuditableBeanWrapperFactory implements AuditableBeanWrapperFactory {
abstract static class DateConvertingAuditableBeanWrapper implements AuditableBeanWrapper {
private final ConversionService conversionService;
}
}
Is it possible to audit ZonedDateTime fields?
How can I register a converter?
Create a DateTimeProvider to provide the current time to be used when auditing:
#Component("dateTimeProvider")
public class CustomDateTimeProvider implements DateTimeProvider {
#Override
public Optional<TemporalAccessor> getNow() {
return Optional.of(ZonedDateTime.now());
}
}
And then:
Reference the DateTimeProvider component in the #EnableMongoAuditing annotation;
Create Converters for Date and ZonedDateTime;
Add the Converter instances to a MongoCustomConversions instance;
Expose the MongoCustomConversions instance as a #Bean.
#Configuration
#EnableMongoAuditing(dateTimeProviderRef = "dateTimeProvider")
public class MongoConfiguration {
#Bean
public MongoCustomConversions customConversions() {
List<Converter<?, ?>> converters = new ArrayList<>();
converters.add(new DateToZonedDateTimeConverter());
converters.add(new ZonedDateTimeToDateConverter());
return new MongoCustomConversions(converters);
}
class DateToZonedDateTimeConverter implements Converter<Date, ZonedDateTime> {
#Override
public ZonedDateTime convert(Date source) {
return source == null ? null :
ZonedDateTime.ofInstant(source.toInstant(), ZoneId.systemDefault());
}
}
class ZonedDateTimeToDateConverter implements Converter<ZonedDateTime, Date> {
#Override
public Date convert(ZonedDateTime source) {
return source == null ? null : Date.from(source.toInstant());
}
}
}
I wouldn't, however, use ZonedDateTime for this purpose. I would stick to OffsetDateTime:
OffsetDateTime, ZonedDateTime and Instant all store an instant on the time-line to nanosecond precision. Instant is the simplest, simply representing the instant. OffsetDateTime adds to the instant the offset from UTC/Greenwich, which allows the local date-time to be obtained. ZonedDateTime adds full time-zone rules.
It is intended that ZonedDateTime or Instant is used to model data in simpler applications. This class may be used when modeling date-time concepts in more detail, or when communicating to a database or in a network protocol.