MySQL DATETIME and TIMESTAMP to java.sql.Timestamp to ZonedDateTime - java

I am wondering how the conversion works in this. MySQL server (5.6) treats TIMESTAMP as zone-adjusted (and internally stored in/retrieved from UTC). It also treats DATETIME as having no zone.
On the Java side, I am recommended to read into java.sql.Timestamp in either case. Is there a zone-type conversion taking place (when going through MySQL-connector 5.1.37) from MySQL's DATETIME to java.sql.Timestamp (such as to apply the client system zone) ?
In the end, there is only one zone for my server and clients, and so I maintain a specific ZoneId (in app code) to get to ZonedDateTime. But I would like to work with ZonedDateTime, going back and forth to the database stored as DATETIME. A simple example of conversion will be appreciated!

Let's address each question you have. First: Is there a zone-type conversion taking place (when going through MySQL-connector 5.1.37) from MySQL's DATETIME to java.sql.Timestamp (such as to apply the client system zone)?
First off, I presume that you are using the getTimestamp(int) method from the connector. I could not find an official source that showed me an enlightening answer; however, there was this question in which the answer stated: When you call getTimestamp(), MySQL JDBC driver converts the time from GMT into default timezone if the type is timestamp. It performs no such conversion for other types.
However, in this version of the method, it uses an underlying Calendar to convert the Timestamp to the TimeZone specified, if the underlying database doesn't store time zone information. This may be the solution to your second question, as long as you knew the time zone at which the value was stored (which you do). But if it is not, it seems that with the first method there is no conversion taking place, at least when it retrieves the DATETIME. Speaking about your second question:But I would like to work with ZonedDateTime, going back and forth to the database stored as DATETIME.
It makes me think that there is a way to do this as long as you knew which time zone you are converting from. As we have previously stated, you and your clients are only working with one ZoneId, which is totally fine. However, this answer is provided to work with more time zones. Multiple ZoneId's can be achieved if you were to store the ZoneId of the connection in the database; retrieving it as well as the DATETIME and finally processing these values into a ZonedDateTime. You could store the ZoneIds into the database using the ID's of the ZoneId class (if you wanted to).
Timestamp t = resultSet.getTimestamp(timestampColumnId);
ZoneId zoneId = ZoneId.of(resultSet.getString(zoneColumnId), ZoneId.SHORT_IDS);
ZonedDateTime d = ZonedDateTime.ofInstant(t.toInstant(), zoneId);
Or, you could just store the DATETIME as a TIMESTAMP in the database as ZZ Coder suggests in his answer stated above. But, you could just use the ZoneId you have hard-coded as such:
Timestamp t = resultSet.getTimestamp(timestampColumnId);
ZonedDateTime d = ZonedDateTime.ofInstant(t.toInstant(), zoneId);
EDIT
Looking at the source code, on get or set calls using the getTimestamp(int, Calendar) or the setTimestamp(int, Timestamp, Calendar) function, the timezone of the Calendar is used. However, in some cases with TIMESTAMP, when a Calendar is not used, the JDBC then uses the time zone of the server. And according to the original poster, it worked (see comment below).

Related

How to elegantly convert from MSSQL Datetime2 to java.time.Instant

I have a simple spring boot REST API application, using plain jdbc to fetch data from a MSSQL DB. I am trying to figure out how best to retrieve a DATETIME2 column from the DB (which stores no timezone info), and serialize it as a UTC timestamp (and treat it as such in general in code).
My DB server timezone is set to UTC. I know that everything stored to this column is stored as UTC and I cannot change the column type unfortunately. It's a bit of a legacy DB, so all the dates will need to fetch will have this same problem, hence looking for a clean neat and tidy solution.
Ideally in my Java app, I would ideally like all my "date" fields to be of type java.time.Instant, since it is easy to handle and will serialize to json looking something like "someDate": "2022-05-30T15:04:06.559896Z".
The options as I see them are:
Use a custom RowMapper to do something like myModel.setDate(rs.getTimestamp("Date").toLocalDateTime().toInstant(ZoneOffset.UTC));, but this just seems verbose. I suppose I could tuck it away in some utility class static function?
Use LocalDateTime everywhere and do myModel.setDate(rs.getTimestamp("Date").toLocalDateTime()). But then Jackson will serialize it without timezone information.
Set the whole app timezone to UTC on startup. But this could be changed by other code, and from what I read is generally a bad idea.
Caveat: I am not a user of Spring.
moment versus not-a-moment
You need to get clear on one fundamental issue with date-time handling: moment versus not-a-moment.
By “moment” I mean a specific point on the timeline. Without even thinking about time zones and such, we all know that time flows forward, one moment at a time. Each moment is simultaneous for everyone around the world (sticking with Newtonian time, ignoring Einstein Relativity here 😉). To track a moment in Java, use Instant, OffsetDateTime, or ZonedDateTime. These are three different ways to represent a specific point on the timeline.
By “not-a-moment” I mean a date with a time-of-day, but lacking the context of a time zone or offset-from-UTC. If I were to say to you, “Call me at noon tomorrow” without the context of a time zone, you would have no way of knowing if you should call at noon time in Tokyo Japan, noon time in Toulouse France, or noon time in Toledo Ohio US — three very different moments, several hours apart. For not-a-moment, use LocalDateTime.
So never mix LocalDateTime with the other three classes, Instant, OffsetDateTime, or ZonedDateTime. You would be mixing your apples with your oranges.
You said:
I would ideally like all my "date" fields to be of type java.time.Instant
Yes, I would agree on generally using Instant as the member field on any Java object tracking a moment. This is generally a good idea — but only for moments. For not-a-moment, as discussed above, you should use LocalDateTime instead.
TIMESTAMP WITH TIME ZONE
Another issue, Instant was not mapped in JDBC 4.2 and later. Some JDBC drivers may optionally handle an Instant object, but doing so is not required.
So convert your Instant to a OffsetDateTime. The OffsetDateTime class is mapped in JDBC to a database column of a type akin to the SQL-standard type TIMESTAMP WITH TIME ZONE.
OffsetDateTime odt = instant.atOffset( Offset.UTC ) ;
Writing to database.
myPreparedStatement.setObject( … , odt ) ; // Pass your `OffsetDateTime` object.
Retrieval.
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
TIMESTAMP WITHOUT TIME ZONE
For database columns of a type akin to the SQL-standard type TIMESTAMP WITHOUT TIME ZONE, use LocalDateTime class.
Writing to database.
myPreparedStatement.setObject( … , ldt ) ; // Pass your `LocalDateTime` object.
Retrieval.
LocalDateTime ldt = myResultSet.getObject( … , LocalDateTime.class ) ;
Specify time zone
You said:
My DB server timezone is set to UTC.
That should be irrelevant. Always write your Java code in such as way as to not rely on the JVM’s current default time zone, the host OS’ current default time zone, or the database’s current default time zone. All of those lay outside your control as a programmer.
Specify your desired/expected time zone explicitly.
Retrieve a moment from the database, and adjust into a desired time zone.
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = odt.atZoneSameInstant( z ) ;
Generate text localized to the user's preferred locale.
Locale locale = Locale.JAPAN ;
DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime( FormatStyle.LONG ).withLocale( locale ) ;
String output = zdt.format( f ) ;
DATETIME2 in MS SQL Server
The type DATETIME2 type in MS SQL Server stores a date with time-of-day, but lacks the context of a time zone or offset-from-UTC.
That is exactly the wrong type to use for storing a moment. As discussed above, that type is akin to the SQL standard type TIMESTAMP WITHOUT TIME ZONE, and maps to the Java class LocalDateTime.
You seem to understand that fact given your comment:
I know that everything stored to this column is stored as UTC and I cannot change the column type unfortunately. It's a bit of a legacy DB …
Let me point out that you do not know the values in that column are intended to represent a moment as seen with an offset of zero. You can expect that, and hope so. But without using the protection of the database’s type system, you cannot be certain. Every user, every DBA, and every SysAdmin must have always been aware of this unfortunate scenario, and must have always done the right thing. You’ll need lots of luck with that.
I must mention that the ideal solution is to refactor your database, to correct this wrong choice of data type for that column. But I understand this could be a burdensome and challenging fix.
So given this unfortunate scenario without a fix being feasible, what to do?
Options 1, 2, & 3 you listed
Option 1
Regarding your option # 1, yes that makes sense to me. Except two things:
I would change the name of your model method to be more precise: setInstant. Or use a descriptive business name such as setInstantWhenContractGoesIntoEffect.
Never use the awful legacy date-time classes in Java such as Timestamp. Change this:
myModel.setDate(rs.getTimestamp("Date").toLocalDateTime().toInstant(ZoneOffset.UTC));
… to:
myModel
.setInstantWhenContractGoesIntoEffect
(
resultSet
.getObject( "Date" , LocalDateTime.class ) // Returns a `LocalDateTime` object.
.toInstant( ZoneOffset.UTC ) // Returns an `Instant` object.
)
;
Option 2
As for your option # 2, I am not quite sure what you have in mind. But my impression is that would be the wrong way to go. I believe the best approach, for long-term maintenance without "technical debt", and for avoiding confusing and mishaps, is to “tell the truth”. Do not pretend to have a zebra when you actually have donkey. So:
On the database side, be clear and explicit that you have a date with time but lack the context of an offset. Add lots of documentation to explain that this is based on a faulty design, and that we are intend to store moments as seen in UTC.
On the app side, the Java side, deal only with Instant, OffsetDateTime, and ZonedDateTime objects, because within the data model we are representing moments. So use classes that represent a moment. So no use of LocalDateTime where you really mean a specific point on the timeline.
Obviously, there is some kind of a dividing line between your database side and your app side. Crossing that line is where you must convert between your Java type for a moment and your database type faking it as a moment. Where you draw that line, that transition zone, is up to you.
Option 3
As for your option # 3, yes that would be a very bad idea.
Setting such a default is not reliable. Any SysAdmin, or even an unfortunate OS update, could change the OS’s current default time zone. Like wise for the database’s current default time zone. And likewise for the JVM’s current default time zone.
So you end up three default time zones that could be changing, with each affecting various parts of your environment. And changing the current default time zone in any of those places immediately affects all other software depending on that default, not just your particular app.
As mentioned above, I recommend just the opposite: Code without any reliance on default time zones anywhere.
The one place for accessing a default time zone is maybe for presentation to the user. But even then, if the context in crucial, you must confirm the desired/expected time zone with the user. And where you do make use of a current default time zone, do so explicitly rather than implicitly. That is, make explicit calls such as ZoneId.getSystemDefault() rather than using omitted optional arguments.
I'm not sure I see a problem.
Instant values are UTC by definition, and java.sql.Timestamps have no zone other than the one implied by the database setting. You know the database is strictly UTC. This is lucky for you since it eliminates one error-prone conversion. Then, reading java.sql.Timestamps and keeping them as Instants at runtime is trivial, given java.sql.Timestamp#toInstant(). DON'T convert through LocalDateTime.
This has nothing to do with setting any "default" timezone in your application. Design and write your code so that internally (i.e. runtime memory and database) you deal ONLY with UTC (i.e. instants). The only point at which you should convert instants to anything local is at external interface points... i.e.
when outputting date/time values, either for human consumption or for other software that expects a specific timezone.
when reading date/time values from the user or another program (for which you will need to know any implied zone if it's not explicit)
Leave your "default" timezone as whatever is given to you by your environment. Then, no matter where your code is running, it will produce meaningful local dates/times.
Establish a strict rule that you deal only with UTC internally. This will make reasoning about your code MUCH simpler in the long run.
I guess the only real stumbling block is realizing that things depending on local conditions, such as day boundaries, have to be done in the local zone... but write your code to "think" UTC internally.

Convert Timestamp as per TimeZone in java

I have a Timestamp object and a TimeZone object
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
TimeZone userTimeZone = preferenceService.getPreferredTimezone();
I want a function which modifies timestamp as per the userTimeZone . The function should return Timestamp. Could someone help ?
Your question is non-sensical. A Timestamp instance represent an instant in time. It doesn't have timezone info and isn't a timezoned stamp.
Instead, when showing a Timestamp to a user, in the step of 'convert this computer-time-kept concept to something to show the user', you'd do the job there. You wouldn't do that by first 'converting the timestamp to this timezone', you'd either do that in one go, or better yet, get to the java.time package as fast as possible because all other time libraries (including those in almost all DB engines) are non-sensical.
In java.time terms, you'd get to a ZonedDateTime, and hten you can convert that to another zone, and from there, potentially via a LocalDateTime, to rendering it to the user.
Given that you said the function must return a Timestamp, the code that uses this method is broken, so fix it there.

Convert from joda.Datetime to time.LocalDateTime is the right way?

I need timestamp format for my dates in database.
For now i have joda.Datetime in database , but also in my restApi application.
I tried to create a new column , and converted the existing joda.Datetime in the other column time.LocalDateTime. Also I replaced in all code joda.DateTime with time.LocalDateTime.
It works, but when i make a get call in postman, i received a json like:
{
seconds: x1,
minutes: x2,
hours: x3,
days: x4,
........
}
I think i need a convertor, to show the timestamp as "dd-mm-yy hh-mm-ss"
I want to have timestamp format in database to be able to execute SQL standard operation and named queries on time.
In my database I have bytea type for dates. I use PostgreSQL with DBeaver.
Is this the right way, or you could recommend me another option?
Is this the right way, or you could recommend me another option?
Without experience with PostgreSQL I should say that bytea is the wrong datatype for your dates or timestamps. timestamp with time zone is good and supports SQL operations and queries. It further has the advantage that you can store OffsetDateTime (perhaps even Instant, I am not sure) directly, so you avoid formatting your timestamp for storing it. That’ll be recommended.
For a time stamp to be a time stamp date and time of day is not enough. Date and time of day will be interpreted differently in different time zones (and is even ambiguous in the fall when summer time ends and the clocks are turned backward). As far as I have understood timestamp with time zone will make sure that time stamps are stored in UTC, so will be unambiguous points in time. In Java the Instant class represents a point in time independently of time zone, so is good for timestamps. Some JDBC drivers allow you to store an Instant directly into a timestamp with time zone column, others require you to convert to OffsetDateTime first. In the latter case use
OffsetDateTime dateTimeForDatabase = yourInstant.atOffset(ZoneOffset.UTC);
Edit: Please note that the with time zone bit is a bit of a lie, as #Jon Skeet points out in a comment. The database doesn’t store a time zone or offset, it only makes sure that dates and times are stored in UTC for removing ambiguity about the point in time.
Link: Date/Time Types in the PostgreSQL docs

Save TimeZone with Date in mongodb

I have java.util.Date field in my document class.
E:g:
#Document(collection = "testdoc")
public class TestDoc {
#Id
String id;
Date startDate;
}
Even if I set the Date with UTC and IST, it always save in my collection as below,
"startDate" : ISODate("2015-08-21T18:30:00.000Z")
How can I save the time zone also in mongo collection? What does Z stand in this case?
The 'Z' signifies that the time is stored in UTC. Mongo internally converts all local representations of time in UTC before storing. However, one suggestion would be to store the time along with the timezone which is received from your application. You can later reconstruct the local time from the UTC time and the timezone in your application logic.
Please go through this link. They have given an example on how to model local time data using JavaScript.
https://docs.mongodb.com/v3.2/tutorial/model-time-data/
Do the conversion before storing and save as UTC always. Then reconvert it in the timezone you want before displaying.
If you desperately want to store the timezone with the offset you may have to deal with it as a separate string in db, but it cannot go with date field for MongoDB.
As currently MongoDB doesn't allow saving of timezone.
Below is the open JIRA issue or the same.
https://jira.mongodb.org/browse/SERVER-6310
Dates in MongoDB are stored in UTC. There isn't timestamp with a timezone data type like in some relational databases. Applications that need to access and modify timestamps, based on local time, should store the timezone offset together with the date and offset dates on an application level.
In the MongoDB shell, this could be done using the following format with JavaScript:
let now = new Date();
db.page_views.save({date: now,
offset: now.getTimezoneOffset()});
Then you need to apply the saved offset to reconstruct the original local time, as in the following example:
let record = db.page_views.findOne();
let localNow = new Date( record.date.getTime() - ( record.offset * 60000 ) );
I guess here you'll find a good guideline how to handle timestamps in different scenarios for different data language independent.
As it's recommendet in the document, ALWAYS use UTC to save the data, even it is local timezone. If necessary, save the time zone in a seperate field ( JSON or xml) and follw the format guideline ISO 8601. ( like you did but there are many possible representations)
As far as I know, correct me if i'm wrong, JSOn does not deal with specific date formats but JS does. Microsoft docs recommends the followind format bases on ECMA YYYY-MM-DDTHH:mm:ss.sssZ this
The Z in the Timestamp shows that it's UTC format with zero offset +00:00
if nothing else is added.
If you want to use the "Z" notation, you have to add or substract the offset within the timestamp instead of writing the zero-offset and add the offset at the end.
I recommend you to follw the w3c guideline because it covers different scenatios for different time saving usecases.
Hope this helps

best way to convert datetimes in globally synchronised system architecture

We are working on a Customer Data Integration project (using Java 8), which has a central database that is kept synchronised with local databases in other countries.
When a new or updated contact request comes from a local system to our central system, a modifiedAt value is passed (which is the local datetime stamp value in their time zone)
We convert this into UTC and store it in our database. (To do this we store the time zone offsets for each system). When any system requests that contact object, we convert the stored modifiedAt value from UTC into their local datetime.
Is this the best way to do this? What about issues with daylight savings times? Does the central system need to keep track of when DST starts and stops for each of the local systems?
Thanks in advance
Don't store the timezone offset. Store the timezone itself.
The offset of "Europe/Paris" is different in the winter and in the summer, due to DST.
But if I know that the timezone is "Europe/Paris", I'm able to reliably convert any French local date to a UTC timestamp, because I can find the right offset for that local date.
(actually, I can convert almost any date reliably, because some local dates are ambiguous, when the time goes back from 3AM to 2AM).
Why don't the local systems provide a UTC timestamp directly, instead of providing a local datetime?
Totally agree with JB Nizet's answer: you should store the time zone instead of just the offset. In Java 8, you can use the ZonedDateTime class to accomplish this.
It contains methods such as:
public static ZonedDateTime of(LocalDateTime localDateTime, ZoneId zone)
which allow to easily convert a local dateTime to a zoned dateTime. Then, you can move this zoned dateTime to UTC with similar methods, i.e:
public ZonedDateTime withZoneSameInstant(ZoneId zone)
You are looking at storing two information Instant and ZoneId.
In your database, you store the time as Instant. Whenever any of your server asks for the time, you convert that to ZonedDateTime using Instant value stored in database, and ZoneId either passed by server, or stored in database as well.
This allows you to easily query databases in cases you want "All objects that were updated in last hour".
Instant to ZonedDateTime
Instant instant = Instant.now();
// Japan = UTC+9
ZonedDateTime zonedDateTime = instant.atZone(ZoneId.of("Asia/Tokyo"));
ZonedDateTime to Instant
zonedDateTime.toInstant();

Categories

Resources