How do I persist the original timezone in JPA - java

Specifically in SQL server (and I'm assuming others), there is a datetimeoffset type that can store the timestamp with an offset other than GMT. (Looks like this "1998-01-01 15:00:00.0000000 +06:00")
But trying to persist a Calendar to the database automatically converts it to GMT (although I can't tell who's doing it JPA, Hibernate, or sqljdbc) so it looks like this "1998-01-01 9:00:00.0000000 +00:00"
Is there a way to prevent this for certain attributes?

Altough there might be other solutions using SQL databases, I always follow this approach:
In a data base or file I always store times in UTC, with no exception.
If the timezone has to be preserved, e.g for UI, then I store the UTC offset in an extra field.
This way I have the correct time, and if needded the time zone is preserved.

Found a way to do it
package testers.jpa.beans;
import java.io.Serializable;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.persistence.Transient;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
#Entity
#Table(name = "dbo.tester")
//Ignore the actual calendar object here because the jsonner strips the timezone info from it
#JsonIgnoreProperties(value = { "date" })
#NamedQuery(name = "NameAndOffset.purge", query = "DELETE FROM NameAndOffset")
public class NameAndOffset implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private Calendar date;
public NameAndOffset() {
}
public NameAndOffset(String name, Calendar date) {
this.name = name;
this.date = date;
}
#Id
#Basic
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* Return a string representation of the {#link NameAndOffset#date} object.<br/>
* This will be used by JPA and the Jsonner to prevent losing the timezone
* information<br/>
* <br/>
* For this to work properly you must tell both the Json mapper and JPA to
* ignore anything else that relates to the {#link NameAndOffset#date} field
*
* #return A String representation of the {#link NameAndOffset#date} field,
* formatted for SQL Server's datetimeoffset data type
*/
#Basic
#Column(name = "date")
public String getDateTimeOffset() {
return new OffsetDateFormat().formatCalendar(date);
}
public void setDateTimeOffset(String date) throws ParseException {
this.date = new OffsetDateFormat().parseCalendar(date);
}
//Ignore the actual calendar object here because JPA strips the timezone info from it
#Transient
public Calendar getDate() {
return date;
}
public void setDate(Calendar date) throws ParseException {
this.date = date;
}
class OffsetDateFormat extends SimpleDateFormat {
private static final long serialVersionUID = 1L;
private static final String OFFSET_FORMAT = "yyyy-MM-dd HH:mm:ss.S Z";
public OffsetDateFormat() {
super(OFFSET_FORMAT);
}
public Calendar parseCalendar(String source) throws ParseException {
//pull out the colon in the offset
int timeZoneColon = source.lastIndexOf(":");
String nocolon = source.substring(0, timeZoneColon) + source.substring(timeZoneColon + 1);
Calendar cal = Calendar.getInstance();
cal.setTime(parse(nocolon));
//after parsing, the timezone of this DateFormatter changes to whatever was represented in the string
//make sure the new calendar reflects this
cal.setTimeZone(getTimeZone());
return cal;
}
public String formatCalendar(Calendar calendar) {
setTimeZone(calendar.getTimeZone());
String nocolon = format(calendar.getTime());
//add the colon
StringBuffer sb = new StringBuffer(nocolon.substring(0, nocolon.length() - 2)).append(":").append(nocolon.substring(nocolon.length() - 2));
return sb.toString();
}
}
}

Related

Converting JSON long to a date in Java via Jackson

I have a JSON Data Object class as follows:
public class Plugins {
private String id;
private String name;
#JsonProperty("created_at")
private long createdAt;
}
Where createdAt is the long timestamp of the creation date. I use this class to back up a Jackson ObjectMapper object parsing JSON data from an external API call. I was wondering if it is possible to have Jackson convert created_at automatically to a readable date format and store in Java as a String or Date flavour?
if it is possible to have Jackson convert created_at automatically to a readable date format and store in Java as a String or Date flavour?
Date is obsolete and discouraged to be used.
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.
You can change your POJO to make it store date-time information properly without the need to change the JSON payload. I.e. created_at can be received as a long value like 1665148545 and translated into ZonedDateTime (or other date-time representations like Istant, LocalDateTime).
public class Plugins {
private String id;
private String name;
private ZonedDateTime createdAt;
public Plugins(#JsonProperty("id") String id,
#JsonProperty("name") String name,
#JsonProperty("created_at") long createdAt) {
this.id = id;
this.name = name;
this.createdAt = Instant.ofEpochSecond(createdAt)
.atZone(ZoneId.of("UTC"));
}
// getters, toString(), etc.
}
Usage example:
String json = """
{
"id": "1",
"name": "someName",
"created_at": 1665148545
}""";
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(json, Plugins.class));
Output:
lugins{id='1', name='someName', createdAt=2022-10-07T13:15:45}
You just need to register JavaTimeModule module and use required type from Java-8 time package. Take a look on below example:
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.Instant;
public class DateApp {
private final static JsonMapper JSON_MAPPER = JsonMapper.builder()
.enable(SerializationFeature.INDENT_OUTPUT)
.addModule(new JavaTimeModule())
.build();
public static void main(String[] args) throws Exception {
String json = "{\"id\": \"1\",\"name\":\"someName\",\"created_at\": 1665148545}";
Plugins plugins = JSON_MAPPER.readValue(json, Plugins.class);
System.out.println(plugins);
}
}
#Data
#NoArgsConstructor
#AllArgsConstructor
class Plugins {
private String id;
private String name;
#JsonProperty("created_at")
private Instant createdAt;
}
Above code prints:
Plugins(id=1, name=someName, createdAt=2022-10-07T13:15:45Z)
Using Custom Deserialiser in jackson
You can achieve the date conversion from long to String or Date by using the custom deserialiser. This custom deserialiser will convert the long value from the json into the defined date format(either Date or String).
Please Note: Here, I have converted the epoch value into the String datatype. In case if Date datatype is needed, you can change the implementation of the deserialize method of CustomDateSerializer class accordingly.
You need to use the below annotation to the fields on which custom deserialisation is required.
#JsonDeserialize(using = CustomDateSerializer.class)
Please find the code below:
Plugins.java
public class Plugins {
private String id;
private String name;
#JsonDeserialize(using = CustomDateSerializer.class)
#JsonProperty("created_at")
private String createdAt;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCreatedAt() {
return createdAt;
}
public void setCreatedAt(String createdAt) {
this.createdAt = createdAt;
}
#Override
public String toString() {
return "Plugins{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", createdAt='" + createdAt + '\'' +
'}';
}
}
CustomDateSerializer.java
public class CustomDateSerializer extends StdDeserializer<String> {
public static String pattern = "dd MMM yyyy hh:mm:ss";
public CustomDateSerializer() {
this(StdDeserializer.class);
}
protected CustomDateSerializer(Class<?> c) {
super(c);
}
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
SimpleDateFormat formatter = new SimpleDateFormat(pattern);
return formatter.format(new Date(jsonParser.getLongValue()));//change the implementation of deserialise method if date format is needed.
}
}
Test.java
public class Test {
public static void main(String[] args) throws JsonProcessingException {
//For sample input json, here i have used Text Blocks feature available from JDK 15 to have the string in readable format.
String json = """
{
"id":"1",
"name":"test",
"created_at":1665158083000
}
""";
ObjectMapper mapper = new ObjectMapper();
Plugins test = mapper.readValue(json,Plugins.class);
System.out.println(test);
}
}
Output:
Plugins{id='1', name='test', createdAt='07 Oct 2022 09:24:43'}

How to convert Date format to dd-MM-yyyy in Java which return a Date object [duplicate]

This question already has answers here:
Java : Cannot format given Object as a Date
(7 answers)
Closed 1 year ago.
I am Making an API which handle date in dd-MM-yyyy format. But Using Date object I get yyyy-MM-dd format. I tried to change Date format By many way like this code -
package com.example.internshala.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
public class Ship {
private String loadingPoint;
private String unloadingPoint;
private String productType;
private String truckType;
private int noOfTrucks;
private int weight;
private String comment;
private UUID shipperId;
private String date;
//--------------------------------------
SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy");
//--------------------------------------
public String getLoadingPoint() {
return loadingPoint;
}
public String getUnloadingPoint() {
return unloadingPoint;
}
public String getProductType() {
return productType;
}
public String getTruckType() {
return truckType;
}
public int getNoOfTrucks() {
return noOfTrucks;
}
public int getWeight() {
return weight;
}
public String getComment() {
return comment;
}
public UUID getShipperId() {
return shipperId;
}
public String getDate() {
return date;
}
public Ship(#JsonProperty("loadingPoint") String loadingPoint,
#JsonProperty("unloadingPoint") String unloadingPoint,
#JsonProperty("productType") String productType,
#JsonProperty("truckType") String truckType,
#JsonProperty("noOfTrucks") int noOfTrucks,
#JsonProperty("weight") int weight,
#JsonProperty("comment") String comment,
#JsonProperty("shipperId") UUID shipperId,
#JsonProperty("Date") Date date) {
this.loadingPoint = loadingPoint;
this.unloadingPoint = unloadingPoint;
this.productType = productType;
this.truckType = truckType;
this.noOfTrucks = noOfTrucks;
this.weight = weight;
this.comment = comment;
this.shipperId = shipperId;
String newDate=date.toString();
this.date=formatter.format(newDate);
}
}
I also Applied it to direct Date object as Constructor parameter but It give error --com.fasterxml.jackson.databind.exc.ValueInstantiationException
I can't think of any good reason to not save a date in anything other than a date object. (As mentioned Date is outdated, so LocalDate is a better choice.)
From what I see in your code you are using jackson to read/write a file. Instead of changing your own class so it will output what you expect in the file you change the library you are using, in this case jackson, to write and read the values in the format you want them to be.
You have many different ways to do it. For example you can set your format as the default format like this:
DateFormat df = new SimpleDateFormat("dd-MM-yyyy");
objectMapper.setDateFormat(df);
Or you only change it for one attribute
public class Ship {
// Code
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy")
private Date date;
// Code
}
You should change
String newDate=date.toString();
this.date=formatter.format(newDate);
to
this.date=formatter.format(date);
import java.text.SimpleDateFormat;
import java.util.Date;
public class SamDateFormat
{
public static void main(String[] args)
{
Date date = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy");
String currDate= formatter.format(date);
System.out.println(currDate);
}
}

LocalDate with JPA does not work correctly

I do request localhost:8080/history/world/2020-02-08
Entity:
public class DailyStatistic {
...
#Column(columnDefinition = "DATE")
private LocalDate date;
...
Controller:
#GetMapping("/world/{date}")
public ResponseEntity<List<DailyStatistic>> getWorldStatByDate(#PathVariable String date) {
List<DailyStatistic> worldStatList = null;
try {
worldStatList = dataProvider.getWorldStatByDate(LocalDate.parse(date));
...
Invoked dataProvider method:
public List<DailyStatistic> getWorldStatByDate(LocalDate date) throws NoDataException {
List<DailyStatistic> dailyStatisticList = repository.findAllByDate(date);
...
Invoked repository method:
#Repository
public interface DailyStatRepository extends JpaRepository<DailyStatistic, Long> {
List<DailyStatistic> findAllByDate(LocalDate date);
Json answer:
{
...
"date": "2020-02-07",
...
}
Remind input: localhost:8080/history/world/2020-02-08
So I get a wrong resultSet. Anybody knows why it happens and how it solve?
Try with this way in your entity
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.time.LocalDateTime;
#JsonSerialize(converter = LocalDateTimeToStringConverter.class)
#JsonDeserialize(converter = StringToLocalDatetimeConverter.class)
private LocalDateTime date;
See this
Or simply this way
#JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate date;

Hazelcast: Issue while adding index

I have a hazelcast instance whose key is of type MyObject and value is an enum.
Let's say one of the attributes of MyObject class is date which is of type java.sql.Date.
class MyObject {
private Date date;
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date
}
}
public enum MyEnum {
TEST_ENUM;
}
Also I am using predicate to filter on the keys retrieve the enum value.
For ex:
EntryObject entryObject = new PredicateBuilder().getEntryObject();
PredicateBuiler predicateBuilder = entryObject.key.get(date).isNull;
This is how I am trying to add index:
IMap<MyObject, MyEnum> map = hazelcastInstance.getMap("test");
map.addIndex("date", true)
But as soon as this gets executed an exception is being thrown:
com.hazelcast.query.QueryException: java.lang.IllegalArgumentException: There is no suitable accessor for 'date' on class 'class com.main.constants.myEnum'
at com.hazelcast.query.impl.getters.ReflectionHelper.createGetter(ReflectionHelper.java:176)
at com.hazelcast.query.impl.getters.Extractors.instantiateGetter(Extractors.java:88)
at com.hazelcast.query.impl.getters.Extractors.getGetter(Extractors.java:73)
at com.hazelcast.query.impl.getters.Extractors.extract(Extractors.java:57)
at com.hazelcast.query.impl.QueryableEntry.extractAttributeValueFromTargetObject(QueryableEntry.java:156)
at com.hazelcast.query.impl.QueryableEntry.extractAttributeValue(QueryableEntry.java:82)
at com.hazelcast.query.impl.QueryableEntry.getAttributeValue(QueryableEntry.java:48)
at com.hazelcast.query.impl.QueryableEntry.getConverter(QueryableEntry.java:67)
at com.hazelcast.query.impl.IndexImpl.saveEntryIndex(IndexImpl.java:67)
at com.hazelcast.map.impl.operation.AddIndexOperation.run(AddIndexOperation.java:75)
I understand it's trying to find the index attribute in the value class
How do I get this thing working i.e. add the index on the Key rather than on the value.
While writing a test I actually found your problem :-)
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
import com.hazelcast.query.EntryObject;
import com.hazelcast.query.PredicateBuilder;
import java.io.Serializable;
import java.sql.Date;
public class Test
{
public static void main(String[] args)
{
HazelcastInstance hz = Hazelcast.newHazelcastInstance();
IMap<MyObject, MyEnum> map = hz.getMap("test");
EntryObject entryObject = new PredicateBuilder().getEntryObject();
PredicateBuilder builder = entryObject.key().get("date").isNull();
map.addIndex("__key#date", true);
map.put(new MyObject(), MyEnum.TEST_ENUM);
}
public static class MyObject implements Serializable
{
private Date date;
public Date getDate()
{
return date;
}
public void setDate(Date date)
{
this.date = date;
}
}
public static enum MyEnum {
TEST_ENUM;
}
}
The trick is to create the index based on the map-key and not the value which is taken by default. You already did it in your query entryObject.key() but missed it on the index definition __key.date.

Play! framework bootstrap(Fixtures.loadModels("initial-data.yaml)), injecting "random" String value in model's setter

I have a model class with a persisted DateTime field that is only interacted via getters/setters for Transient properties, String date; and String time;. The do some very specific formatting to create the DateTime object that will be persisted or retrieved when need be.
The problem is that when my model's loaded from the yaml file, the setter for the time field receives a String value that doesn't correspond at all to anything in my project/code.
Here's the class with only relevant members:
package models;
import javax.persistence.*;
import org.hibernate.annotations.*;
import org.joda.time.*;
import org.joda.time.format.*;
import play.db.jpa.*;
#javax.persistence.Entity
public class Booking extends Model {
#Column
#Type(type = "org.joda.time.contrib.hibernate.PersistentDateTime")
public DateTime datetime;
public Integer duration;
#Transient
public String date;
#Transient
public String time;
//default constructor called by play's model loader that sets default values that are required for the getters and setters to work.
public Booking() {
DateTimeFormatter fmt = DateTimeFormat.forPattern("'ISO8601':yyyy-MM-dd'T'HH:mm:ssZ");
this.datetime = fmt.parseDateTime("ISO8601:1970-01-01T00:00:00+0200");
//this.datetime = fmt.parseDateTime(this.date+"T"+this.time);
}
public void setDate(String dateStr) {
this.date = dateStr;
if (dateStr.contains("ISO")) {
DateTimeFormatter dt = DateTimeFormat.forPattern("'ISO8601':yyyy-MM-dd'T'HH:mm:ssZ");
DateTime tmp = dt.parseDateTime(dateStr);
this.datetime = toDateTime(tmp.toString("yyyy-MM-dd"), getTime());
} else {
this.datetime = toDateTime(dateStr, getTime());
}
}
public void setTime(String timeStr) {
this.time = timeStr; //timeStr = "780" for some reason?!
if (timeStr.contains("ISO")) {
DateTimeFormatter dt = DateTimeFormat.forPattern("'ISO8601':yyyy-MM-dd'T'HH:mm:ssZ");
DateTime tmp = dt.parseDateTime(timeStr);
this.datetime = toDateTime(getDate(), tmp.toString("HH:mm"));
}
this.datetime = toDateTime(getDate(), timeStr);
}
public String getDate() {
DateTimeFormatter format = DateTimeFormat.forPattern("yyyy-MM-dd");
return this.datetime.toString(format);
}
public String getTime() {
DateTimeFormatter format = DateTimeFormat.forPattern("HH:mm");
return this.datetime.toString(format);
}
private DateTime toDateTime(String dateStr, String timeStr) {
DateTimeFormatter fmt = ISODateTimeFormat.dateHourMinute();
DateTime dt = fmt.parseDateTime(dateStr + "T" + timeStr);
return dt;
}
When I run through the debugger, the timeStr parameter that setTime receives when it's first called is "780". There is no such value in my yaml file as the model is injected like this:
Booking(bobBooking):
date: 2011-09-16
time: 13:00
duration: 30
headcount: 10
room: b
user: bob
description: Bob's Booking.
The additional fields are omitted.
Try using quotes for time value in yaml file. There could be some issue in parsing colon fields using SnakeYAML parser (which is the default in Play)
YAML 1.1 defines 13:00 as a sexagesimal value (which is not what you expect)
http://yaml.org/type/int.html
Use single or double quotes to specify a string value. ('13:00', "13:00")

Categories