How to remember offset when save json-dto using #DateTimeFormat? - java

There is rest-controller in spring applcation
#GetMapping
public String findByParams(#RequestParam("date") #DateTimeFormat(pattern = DATETIME_FORMAT) ZonedDateTime date) {...}
So, ZonedDateTime does't save my +03:00, +04:00 offsets and shift all times to Z
How to customize it to save my offset in repsonse?
for example, I saved date like this
2020-05-07T17:10:45.001+03:00
but #DateTimeFormat parsed it like
2020-05-07T14:10:45.001Z
But I need in +03:00 offset with time, how to fix it.
The issue is save not only for request params, but for dto fields too..

for example, I saved date like this
2020-05-07T17:10:45.001+03:00
but #DateTimeFormat parsed it like
2020-05-07T14:10:45.001Z
And that is perfectly valid as it describes THE SAME INSTANT but represented in different timezones. Having this you are able to present the exact same instant of time to users in different timezones in their relative (local) time while having consistent timeline of events (eg in the datebase). I would leave it as is because most of integration with your API will work out of the box. 2020-05-07T14:10:45.001Z is equal to 2020-05-07T14:10:45.001+00:00 but it is a matter of convinience to have it shorter way :)
so as to
How to remember offset when save json-dto using #DateTimeFormat?
You don't. You are free to output data in any timezone (including the one required)
Here is how to do "hardcoded version'
https://stackoverflow.com/a/46653797/1527544

Related

Hibernate retrieve timestamps in UTC

I am using hibernate + spring and want to store/load timestamps in UTC. I've read that I should add a property, so I added this to my application.properties
spring.jpa.properties[hibernate.jdbc.time_zone]=UTC
This worked for one part of the problem - now dates are saved in utc in the database. But when I retrieve timestamps, they are transformed into default timezone. How can I fix this without setting default time zone to UTC?
The property of the entity has type LocalDateTime.
I ran the code, and noticed that the proper result set method is used during get(the one that accepts calendar) with instance that has zone info storing UTC. But after setting calendar's values to the one retrieved from the database, the calendar is transformed into Timestamp with this code
Timestamp ts = new Timestamp(c.getTimeInMillis());
In debug mode, I see that ts stores cdate field with value of timestamp in default time zone(not UTC).
First of all, if we are talking about Hibernate 5 (5.2.3 - 5.6.x if to be precise) the purpose of hibernate.jdbc.time_zone setting is not to give the ability for application developer to implement some kind of sophisticated date/time logic, but to synchronize persistence provider with underlying database, that is clearly stated in the corresponding CR:
Currently my database has implicit date times in UTC. No zoned data is appended to the end of the string (e.g. "2013-10-14 04:00:00").
When Hibernate reads this as a ZonedDateTime, it incorrectly reads it in as EST, as that is the TimeZone of the JVM.
It would be nice to be able to specify the TimeZone of a field by an annotation perhaps.
basically: you definitely need to set up hibernate.jdbc.time_zone if (mine: and only if) SQL statement like SELECT SYSDATE FROM DUAL (SELECT LOCALTIMESTAMP for PostgreSQL, etc) returns something, what you do not expect, in that case Hibernate will start adjusting non-timezone-aware JDBC data to something more or less reliable for application - that is exactly what you are observing (when I retrieve timestamps, they are transformed into default timezone)
At second, any speculations around JSR-310 and JDBC 4.2 (like for timezone-aware java types you need to define DB columns as timestamp with time zone), are not correct in case of Hibernate 5, that is mentioned in the corresponding CR as well:
The whole idea of "stored TZ" really depends on how the database/driver treats TIMESTAMP and whether it supports a "TIMESTAMP_WITH_TIMEZONE" type. I personally think it is a huge mistake to save the specific TZ differences to the DB, so I would personally continue to not support TIMESTAMP_WITH_TIMEZONE types. This would mean we never have to bind the Calendar because we could simply convert the value to to the JVM/JDBC TZ ourselves. Specifically I would suggest that we (continue to) assume that the driver has been set up such that the same TZ is used when ...
And indeed, if you try to find usage of java.sql.Types#TIMESTAMP_WITH_TIMEZONE in Hibernate 5 sources you will find nothing, just because that time Hibernate developers didn't get a common opinion about how timezone conversions should work in cases of different Java versions, Java types, DB engines and JDBC drivers (they are developing the most popular (mine: the only one) JPA implementation, that is definitely not the same as develop microservice), however, there are a lot of related changes in Hibernate 6 (check TimeZoneStorageType for example). In Hibernate 5 all timezone conversion logic passes through TimestampTypeDescriptor:
#Override
protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException {
return options.getJdbcTimeZone() != null ?
javaTypeDescriptor.wrap( rs.getTimestamp( name, Calendar.getInstance( options.getJdbcTimeZone() ) ), options ) :
javaTypeDescriptor.wrap( rs.getTimestamp( name ), options );
}
and as you can see, Hibernate 5 just gives a hint to JDBC driver, how the last should process #getTimestamp call:
Retrieves the value of a JDBC TIMESTAMP parameter as a java.sql.Timestamp object, using the given Calendar object to construct the Timestamp object. With a Calendar object, the driver can calculate the timestamp taking into account a custom timezone and locale. If no Calendar object is specified, the driver uses the default timezone and locale.
in regard to your case:
you either need to use timezone-aware java types (ZonedDateTime/OffsetDateTime, or even Instant) or code your own Hibernate type, which will handle timezone conversions - that is not so hard as it might seem.
we can also set it up` per-session basis:
session = HibernateUtil.getSessionFactory().withOptions()
.jdbcTimeZone(TimeZone.getTimeZone("UTC"))
.openSession();
My database timezone was in UTC and in my application timezone I solved this problem by having both the Entity and the table have a date in UTC so that there will need to be no conversion between them. Then I did the conversions between timestamps in code in the getters and setters. Then I think you do it manually.
Setter and getter for that field:
public void setCreatedDate(LocalDateTime createdAt)
{
this.createdAt = createdAt.atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime();
}
public LocalDateTime getCreatedDate()
{
return createdAt.atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime();
}
As alluded to be Andrey in his answer, in Hibernate 6 the way to normalize dates/times to UTC is to use the java.time types OffsetDateTime or ZonedDateTime, and either:
annotate the field #TimeZoneStorage(NORMALIZE_UTC), or
set the property hibernate.timezone.default_storage=NORMALIZE_UTC.
I'm not very certain what you mean about using LocalDateTime here. What would it even mean to normalize a local datetime to UTC? That statement just doesn't really make sense: you can't move a local datetime to a new time zone because it doesn't have an associated time zone to begin with.
I think what you mean is that your "local" date times are actually zoned datetimes in the current JVM time zone. But if that's the case, it's very easy to use localDateTime.atZone(ZoneId.systemDefault()) to represent that situation correctly.

How do I convert from MySQL DATETIME to java.time.Instant and display to user in system default timezone?

I'm working on a project for school and I'm at a total loss. Here is the background:
I am working on a JavaFX appointment app that will be run locally, so I don't need to worry about the ramifications of server-side timezone being different from actual user time. I cannot use any 3rd party libraries. The app currently uses a DatePicker and two ComboBox controls for the user to enter the Date, Hour, and Minute values. These are stored on an Appointment object in an Instant variable (start) with traditional get/set methods. I'm successfully writing to the DB from the controls and my results appear as I would expect. If I pick 06/31/2019 and 12:00, then write it to the DB, it shows up in the DB, stored in the Start column (which has a Datatype of DATETIME) as 2019-12-31 17:00:00. I am in CST, so my UTC offset is -5 hours. Up to this point, I believe everything is peachy.
From here, something goes south. I map each of the DB appointments to an ObservableList object using something like this on each item in the ResultSet
Instant start = i_rs.getTimestamp("start").toInstant();
Return newAppointment(start);
A value check on the Instant start variable gives me:
2019-12-31T17:00
as I would expect. However, I cannot seem to convert this time successfully to the user's timezone. The closest I have come is with this:
ZonedDateTime localStartDate = currentAppointment
.getStart()
.atZone(ZoneId.systemDefault());
But that returns
2019-12-31T17:00-05:00
I've also tried
ZonedDateTime zdt = date.atZone(ZoneOffset.systemDefault());
But it winds up converting my time in the wrong direction, leaving me with
2019-12-31T22:00
I've been working on this small "easy" part for over a day. Any help would be greatly appreciated.
ZonedDateTime localStartDate = currentAppointment
.getStart()
.atZone(ZoneId.systemDefault());
Here you say "I have some timestamp, treat it as CST" It is why it becomes "17:00-05:00"
It seems you store your timestamps in UTC (if you do not then I advise you change your writing code to always store in UTC) so you should tell Java about it
ZonedDateTime localStartDate = currentAppointment
.getStart()
.atZone(ZoneId.of("UTC")) // my instant is UTC
.withZoneSameInstant(ZoneId.systemDefault()) // convert to my local zone

Copying TIMESTAMP to DATETIME on MySQL with Hibernate

I have these two classes
class Source {
// mapped to TIMESTAMP
#Version
#Column(columnDefinition="TIMESTAMP(3) DEFAULT '2016-01-01'")
Instant myInstant;
}
class Destination {
// mapped to DATETIME
#Basic(optional=true)
Instant myInstant;
}
When using Hibernate, I assign
destination.myInstant = source.myInstant;
and then the stored value is smaller by one hour than the original - both according to the command line MySQL client and in Java. My current timezone is UTC+1, so the reason is obviously a timezone conversion.
There are a few places where this can be fixed, but I'm looking for the best practice. The server should work world-wide, so it should continue to use UTC internally, right?
Should I just change the column type to TIMESTAMP? Then, why does Instant by default map to DATETIME?
According to this article, Instant does map to TIMESTAMP, but in my case it did not. Why?
If you want to work with timezones and Java 8 I would recommend using ZonedDateTime or OffsetTimeZone (the latter being prefered when working with Hibernate). For older versions use Calendar.
When you instance it should go by default with the timezone of your computer.
Check if the database is timestamp with or without timezone.
The default you set is also without timezone, and if it is "with timezone" it should automatically add the database's offset.
I hope some of this works. Here's how I did in one of my projects.
#Column(name = "registration_time")
private OffsetDateTime registrationTime;
[...]
subscriber.setRegistrationTime(OffsetDateTime.now());
In MySQL 5 & above, TIMESTAMP values are converted from the current time zone to UTC for storage, and converted back from UTC to the current time zone for retrieval. This occurs only for the TIMESTAMP data type but not for DATETIME. This is the reason you are seeing the difference while assigning a TIMESTAMP to DATETIME. So, having both the columns of same type should work. Hibernate by default maps InstantType to database TIMESTAMP type. Though you could use it for both TIMESTAMP and DATETIME in MYSQL, they are handled differently.

Best strategy to persist ISO8601

I have been reading different articles on the said question yet i am unable to figure out what should be the best strategy to store the date in db.
I will be receiving the ISO8601 date via path-param in a rest call. What I have decided
Use Joda-Time to parse the date.
Extract UTC-0 time out of the date and the hours offset
Store UTC-0 in DateTime datatype in mysql db and store offset in varchar(5).
When I have to search something based on the date (an exposed rest api). I will use the search criteria (input date) extract the UTC-0 time and hours offset and compare the two columns in the db i.e. where table.dateInUTC0 = :inputDateInUTC0 AND table.hoursOffset = :inputHoursOffset
I am not sure about step 4. Am i doing i right ?
I am not sure about step 4. Am i doing i right ?
Really, it depends on what you are trying to do.
If you want the search to only match if the searcher is using the same timezone as the original data, then you are doing it right.
If you don't want that, you are doing it wrong.
Ask yourself this: If you enter the same date / time in your local time zone and UTC (or some other time zone), do they mean the same thing for clients of your server? Should they?

How to detect the timezone of a client?

How to get client/request timezone in jsp?
Unfortunately this information is not passed in HTTP headers.
Usually you need cooperating JavaScript to fetch it for you.
Web is full of examples, here is one http://www.coderanch.com/t/486127/JSP/java/Query-timezone
you cannot get timezone, but you can get current time from client side.i.e. through javascript and than post back. On server side, you can convert that time to GMT/UTC. The UTC shows the TimeZone.
If you just need the local timezone in order to display local times to the user, I recommend representing all times in your service in UTC and rendering them in browsers as local times using Moment.js.
My general rule is to handle and store times in UTC everywhere except at the interface with the user, where you convert to/from local time. The advantage of UTC is that you never have to worry about daylight-saving adjustments.
Note that if you want to show the age of something (e.g. "posted 3 hours ago") you just need to compare the UTC timestamp with the current UTC time; no need to convert to local times at all.
Best solution for me is sending date/time as a string, and then parse with server's timezone to get a timestamp. Timestamps are always UTC (or supposed to be) so you will not need client's TimeZone.
For example, sending "10/07/2018 12:45" can be parsed like:
SimpleDateFormat oD = new SimpleDateFormat();
oD.applyPattern("dd/MM/yyyy HH:mm");
oD.setTimeZone(TimeZone.getDefault()); // ;)
Date oDate = oD.parse(request.getParameter("time"));
Obviously you can set your specific date/time format.

Categories