Issue in saving current date with UTC time in sql server - java

I want save date with UTC time zone in sql server 2014. I have used ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of("UTC")) to get the current date time and persisting this to db using Hibernate session object. While debugging I can see that in Java program date is fine like this 2019-09-25T13:22:29.573Z[UTC], but after saving in database column it is something like this 2019-09-25 18:53:23.3630000. It's automatically converting the time part according to system time. Can anyone please suggest what is the issue? I have used datetime2 datatype while creating this column in database.

Wrong type
You are using the wrong data type for your column.
The type datetime2 cannot represent a moment. That type stores only a date and a time-of-day, but lacks the context of a time zone or offset-from-UTC. So if you store “noon on the 23rd of January in 2020”, we cannot know if you meant noon in Tokyo Japan, noon in Tunis Tunisia, or noon in Toledo Ohio US, three different moments several hours apart. This wrong type is akin to the SQL standard type TIMESTAMP WITHOUT TIME ZONE. The equivalent class in Java is LocalDateTime.
If you already have data stored, you will need to refactor your database. Basically, add a new column of the correct type, copy data over for each row, converting as you go. Of course, this only works if you know the intended time zone that was absent from each value saved.
Right type
While I don’t use Microsoft SQL Server, according to the doc you should be using a column of type datetimeoffset to record a moment. This type adjusts any submitted value into UTC for queries and sorting, while also recording the offset. This type is akin to the SQL standard type TIMESTAMP WITH TIME ZONE.
Generally best to store your moments in UTC, an offset of zero hours-minutes-seconds.
The Instant class represents a moment in UTC.
Instant instant = Instant.now() ;
If you have a ZonedDateTime, you can extract an Instant.
Instant instant = zdt.toInstant() ;
We would like to save that Instant directly to the database. Unfortunately the JDBC 4.2 soec does not require support for either of the two most commonly used java.time classes: Instant and ZonedDateTime. The spec does require support for OffsetDateTime. So we convert.
OffsetDateTime odt = instant.atOffset( ZoneOffset.UTC ) ;
Submit to database.
myPreparedStatement.setObject( … , odt ) ;
Retrieve.
OffsetDateTime odt = MyResultSet.getObject( … , OffsetDateTime.class ) ;
Extract an Instant to make clear in your code that you want UTC.
Instant instant = odt.toInstant() ;
See this moment through the wall-clock-time used by the people of a particular region (a time zone).
ZoneId z = ZoneId.of( "Asia/Kolkata" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;

Related

Does mybatis change time zone while inserting into a DB?

While inserting a record into a table in Oracle, the time stamp in the created date column is 4 pm UTC(in the complete format eg...2020-05-20 10:30:20.15 UTC). That is verified even just before calling the save method of Mybatis. After the save method is called, the created date column in the new record in the DB does not have UTC as the time. It shows 4 pm Asia/Calcutta. The numeric part of the time stamp is correct but the timezone got changed.
Our code is in Spring framework -
mybatisMapper.save(events) .. the created_date column in events POJO is of java.util.Date type.
We are trying to insert a record in DB hosted in Montreal.
select dbtimezone from dual;
DBtimezone of the sever - -04:00 means American/Montreal time zone(hosted in America/Montreal)
select sessiontimezone from dual;
session time zone - Asia/Calcutta.(because the application is running in India).
Data type of the created date column is TIME STAMP WITH TIMEZONE.
Is mybatis changing the time zone by any chance or is it dropping it altogether and letting Oracle decide the timezone by itself?
Never use java.util.Date. That terrible class was supplanted years ago by the modern java.time classes defined in JSR 310.
Among the problems with Date is that its toString method dynamically applies the JVM‘s current default time zone while generating its text. This creates the illusion of that zone being contained within the object while actually a Date (despite its misnomer) represents a moment in UTC. One of many reasons to avoid this class.
Since you did not post any code, I can only guess that your problem is that you are being misled by the lie told by Date::toString.
As of JDBC 4.2 we can exchange java.time objects with the database. Specifically, use OffsetDateTime class to exchange values stored in a column of a type akin to the SQL-standard type TIMESTAMP WITH TIME ZONE. Instant and ZonedDateTime might also work depending on your JDBC driver, but oddly JDBC 4.2 does not require support for those two more commonly used classes. I generally suggest you store an Instant on your class, converting to/from OffsetDateTime.
FYI, the time zone Asia/Calcutta has been renamed Asia/Kolkata.
Example code
String input = "2020-05-20T10:30:20.15Z" ;
Instant instant = Instant.parse( input ) ;
OffsetDateTime odt = instant.atOffset( ZoneOffset.UTC ) ;
ZonedDateTime zdt = instant.atZone( ZoneId.of( "Asia/Kolkata" ) ) ;
Write to database.
…
myPreparedStatement.setObject( … , odt ) ;
Retrieval from database.
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
ZonedDateTime zdt = odt.atZoneSameInstant( ZoneId.of( "Asia/Kolkata" ) ) ;
This has been covered many many times already on Stack Overflow. Search to learn more.

Convert server date timestamp stored in central time based on user timezone

The dates with timestamps are inserted/saved into the database with datatype datetime in Central Time (CT) regardless of the timezone. When I access the application in eastern timezone and retrieve those dates from my time zone, which is Eastern Time (ET), I would like the dates to render in my Eastern Time zone and not central time. For example, if the stored date is 2018-11-19 13:08:44 in the database in Central Time, I need to get it back as 2018-11-19 14:08:44 if I connect from Eastern or 2018-11-19 12:08:44 if I connect from Mountain Time Zone. I have a Java bean and I need to set the retrieved date converted to my timezone and display the date in a csv file. How do I accomplish this? Java is the server side language. Thank you very much.
You have not provided enough detail to give you precise help. But here are some general guidelines.
 Data type of column
To record moments, your database table column must be defined with a type akin to the SQL standard TIMESTAMP WITH TIME ZONE. The equivalent in MySQL seems to be TIMESTAMP.
Beware of tools with an anti-feature of dynamically applying a default time zone to the value extracted from the database. This creates the illusion of the stored value having that particular time zone. This is certainly not the case in MySQL 8 where TIMESTAMP is documented as always being stored in UTC.
Do not use the type TIMESTAMP WITHOUT TIME ZONE as that records only the date and time-of-day but no context of time zone or offset-from-UTC. The equivalent in MySQL seems to be DATETIME.
UTC is your friend
Generally best to track moments as seen through UTC, that is, with an offset-from-UTC of zero hours-minutes-seconds. You should learn to think of UTC as the One True Time, all other zones and offsets are but mere variations. All programmers and sysadmins should be working in UTC for their logging, debugging, data storage, and data exchange. Apply a time zone only as required by business logic or for presentation to the user.
inserted/saved into the database with datatype datetime in Central Time (CT) regardless of the timezone
I have no idea what that means.
If you want to track moments, specific points on the timeline, you must have a time zone or offset-from-UTC. As I suggested, using UTC itself is strongly recommended.
In java.time, the Instant class represents a moment in UTC, always in UTC.
Instant instant = Instant.now() ; // Capture current moment in UTC.
Oddly, the JDBC 4.2 standard does not require support for the two most commonly used java.time classes: Instant and ZonedDateTime. The third class for moments, OffsetDateTime, is required. No matter, we can easily convert. Specify an offset of zero using the constant ZoneOffset.UTC.
OffsetDateTime odt = instant.toOffset( ZoneOffset.UTC ) ;
Or skip the Instant.
OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC ) ;
Send to the database.
myPreparedStatement.setObject( … , odt ) ;
Retrieve from the database.
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
Notice that all this code is working in UTC exclusively. No time zones involved, just an offset of zero hours-minutes-seconds.
Time zones
Central Time (CT) …Eastern Time (ET) …
These are not time zones. These are pseudo-zones, commonly used in the media, but never to be used in your programming.
Specify a proper time zone name in the format of Continent/Region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 2-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).
ZoneId
While ZoneOffset is for mere offsets (a number of hours-minutes-seconds), the ZoneId class is for time zones (a history of past, present, and future changes to the offset used by the people of a particular region).
ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime
We can apply a ZoneId to an Instant or OffsetDateTime to view that moment through the wall-clock time (offset) used by the people of that region.
ZonedDateTime zdt = odt.atZoneSameInstant( z ) ;
Text
To create strings in standard ISO 8601 format representing the value of our java.time objects, simply call toString.
To generate text in other custom formats, or even to automatically localize, see the DateTimeFormatter and DateTimeFormatterBuilder classes. Search Stack Overflow as this has been covered many many times already.
Refactoring
You mentioned "datetime" in your Question. If you meant that your column is literally of type DATETIME, you have a mess on your hands. You should refactor your database to use proper types.
For that refactoring, extract the values from that column, retrieving as the Java type LocalDateTime. This class is for a date and time-of-day but lacking the context of a zone or offset. As such, this value is not a moment, is not a point on the timeline.
LocalDateTime ldt = myResultSet.getObject( … , LocalDateTime.class ) ; // Retrieving value from a MySQL 8 column of type `DATETIME`.
You claim to know the time zone intended, but not recorded, for those values. Keep in mind that you are only guessing. You do not really know for sure what the original zone/offset was, as that value was discarded.
But if you are certain of the intended zone, specify as a ZoneId. By "Central Time (CT)" I will assume you meant a time zone such as America/Chicago.
ZoneId zChicago = ZoneId.of( "America/Chicago" ) ;
Apply this ZoneId object to each retrieved LocalDateTime object.
ZonedDateTime zdt = ldt.atZone( zChicago ) ;
Convert our ZonedDateTime to an OffsetDateTime given the support required by the JDBC 4.2 spec. (Your JDBC driver may optionally support ZonedDateTime, so you could try that if you like.)
OffsetDateTime odt = zdt.toOffsetDateTime() ; // Stripping away the rich time zone information, trading it for simply a number of hours-minutes-seconds ahead/behind UTC.
Send to the database in a new column of type TIMESTAMP, as should have originally been done when the database was designed.
myPreparedStatement.setObject( … , odt ) ;
Notice that the OffsetDateTime object we are sending is not in UTC. That object will carry the offset used by the Chicago-region at that time-of-day on that date (several hours behind UTC). Of course that offset varies because of politician-imposed anomalies such as Daylight Saving Time (DST).
But you should not have to worry about the offset in the OffsetDateTime. Your MySQL-specific JDBC driver should take care of converting to UTC per the requirements of that type as defined in MySQL. You should do a bit of testing to be sure this is working properly, and according to your understanding. If while debugging you want to see the moment of this OffsetDateTime in UTC, convert to an Instant: odt.toInstant().toString().
When you have finished populating the new column of type TIMESTAMP, drop the old column of type DATETIME.

How to store a date in ET with offset when the java code executes in UTC

We store a date in a sqlserver db table as varchar.
When this is read in the java code as a String and then parsed to a Date, it gets read as UTC (java code is in servers that are in UT). And on reconverting the date to ET, it goes 4 hours behind. How do I handle storing the date in ET in this db column so it gets read as ET in the java code.
We are researching around offsets, but not understanding exactly what to do.
Varchar date in table 03/29/2019 23:23:03 //we want this date to be in ET
SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
Date beginDate = sdf.parse("03/29/2019 23:23:03");
//The problem is when this code executes, the server is in UTC. So beginDate //is read as 03/29/2019 23:23:03 UTC instead of 03/29/2019 23:23:03 ET
Expected 03/29/2019 23:23:03 ET
Actual 03/29/2019 23:23:03 UTC
First, you need to be aware that a Date object doesn't have a time zone at all. It's just an instant in time. So even when you've parsed the value correctly, it'll represent the right instant in time, but you may need to convert it back to Eastern Time later on.
Second, you need to be aware that storing values like this introduces ambiguity - if you store a value of (say) 11/03/2019 01:30, that local time occurs twice - once before the daylight saving transition and once afterwards. If you're always storing times in the past, you should at least consider storing UTC instead - although that's not always the right answer, particularly not if you're storing future date/time values.
For the parsing part, you just need to set the time zone in the Calendar used by SimpleDateFormat. For example:
SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("America/New_York");
Date beginDate = sdf.parse("03/29/2019 23:23:03");
Finally, I'd strongly advise you to start migrating your code to use java.time if at all possible. It's a much nicer API than the java.util.Date etc API.
Use a datetime datatype of your database engine (DBMS), not varchar for date and/or time. With many DBMSs, the timestamp with timezone type defined in the SQL standard is the correct type to use. I don’t know SQL Server well enough to tell precisely what you should use there. Make sure you store date and time in UTC.
If you cannot get around the requirement to store as varchar, store in ISO 8601 format in UTC. For example 2019-03-30T03:23:03Z.
If you also cannot get around the requirement to store in Eastern Time, make sure you are clear whether North American Eastern Time or Australian Eastern Time is intended. Store date and time with UTC offset, for example 2019-03-29T23:23:03-04:00.
If you also cannot get around the requirement to store in the format 03/29/2019 23:23:03 (without offset), be aware that in the fall when summer time (DST) ends and the clocks are moved back, you are storing ambiguous times.
Under all circumstances prefer java.time, the modern Java date and time API. The answer by Basil Bourque shows you how, I don’t need to repeat that.
java.time
The Answer by Jon Skeet is correct. As he mentioned, you should be using the java.time classes defined by JSR 310. These modern classes years ago supplanted the terrible date-time classes such as Date and SimpleDateFormat. Here is some example code in that direction.
Varchar date in table 03/29/2019 23:23:03 //we want this date to be in ET
Parse the string as a LocalDateTime because our input lacks an indicator of offset-from-UTC or time zone.
Define a formatting pattern to match the input.
DateTimeFormatter f = DateTimeFormatter.ofPattern( "MM/dd/uuuu HH:mm:ss" ) ;
Parse.
LocalDateTime ldt = LocalDateTime.parse( input , f ) ;
If you are absolutely certain this date and time was intended for a particular time zone, apply a ZoneId to get a ZonedDateTime.
ZoneId z = ZoneId.of( "America/New_York" ) ;
ZonedDateTime zdt = ldt.atZone( z ) ;
Adjust to UTC by extracting an Instant.
Instant instant = zdt.toInstant() ;
Your JDBC driver may not accept a Instant. So convert to an OffsetDateTime where the offset is set to zero hours-minutes-seconds (in other words, UTC itself).
OffsetDateTime odt = instant.atOffset( ZoneOffset.UTC ) ;
Write to a column of type TIMESTAMP WITH TIME ZONE in your database. As of JDBC 4.2 and later, we can directly exchange java.time objects with a database.
myPreparedStatement.setObject( … , odt ) ;
And retrieval.
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
Adjust to your desired time zone.
ZonedDateTime zdt = odt.atZoneSameInstant( z ) ;
Now you have the pieces needed to do some database-refactoring, to replace that varchar column with a proper TIMESTAMP WITH TIME ZONE column.

Mysql server saves LocalDateTime with 3 hours beyond OS time

During the process of the service writing the LocaDateTime to the database, in my case, in a spring boot API with java 8, when saving the attribute configured as TIMESTAMP on the database side and LocaDateTime in the API, that date is being saved with 3 hours beyond the current operating system date.
Suppose I try to do this exactly at 10 in the morning, the date saved to the database should be 11 hours, but in my case it is not working that way ...
private LocalDateTime dataLimite;
#PrePersist
public void prepareToSave() {
String str = "1986-04-08 10:00";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime dateTime = LocalDateTime.parse(str, formatter);
this.dataLimite = dateTime.plusHours(1);
}
Apparently it may be noted that it is a problem on the side of the Mysql configuration, but when I test ...
SELECT NOW();
The result is exactly the date and time of the operating system, so what is the real problem that is occurring? How to fix this issue?
LocalDateTime
Read the documentation more closely.
This class does not store or represent a time-zone. Instead, it is a description of the date, as used for birthdays, combined with the local time as seen on a wall clock. It cannot represent an instant on the time-line without additional information such as an offset or time-zone.
The LocalDateTime class in Java cannot be used to represent a moment. It represents potential moments along a range of about 26-27 hours, the range of time zones around the globe.
This class contains a date and a time-of-day, but purposely lacks any concept of time zone or offset-from-UTC. So if you store, for example, noon on the 23rd of January this year, we do not know if you meant noon in Tokyo, Kolkata, Paris, or Montréal… all different moments, hours apart, happening earlier in the east and later in the west.
So you are using the wrong class. For a moment, use Instant, OffsetDateTime, or ZonedDateTime.
TIMESTAMP
Read the documentation more carefully.
The TIMESTAMP type in MySQL 8 is akin to the SQL-standard type TIMESTAMP WITH TIME ZONE. This type represents a moment, a specific point on the timeline.
The documentation explains that a moment submitted to the database is adjusted to UTC for storage.
MySQL converts TIMESTAMP values from the current time zone to UTC for storage, and back from UTC to the current time zone for retrieval.
This explains your problem. You were implicitly relying on the current default time zone to be assigned to an object of the wrong type in Java that lacked any time zone. Never write code that relies on the current default time zone (or locale) of your server, as that lies outside your control as a programmer. Instead, always specify explicitly your desired/expected time zone. Or better, just work in UTC whenever possible.
For best results stick to UTC when exchanging values with the database.
OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC ) ;
myPreparedStatement.setObject( … , odt ) ;
Retrieval.
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
To view that moment through the wall-clock time used by the people of a particular region, apply a ZoneId to get a ZonedDateTime.
ZoneId z = ZoneId.of( "Australia/Sydney" ) ;
ZonedDateTime zdt = odt.atZoneSameInstant( z ) ;

Timestamp with timezone in PostgreSQL + Java?

I have a column in the database (PostgreSQL).
I want to insert the current time in GMT in this column.
When getting the current time and inserting it into the DB
it's inserted in the server timezone GMT-5 although that time was in GMT+0.
Any ideas how to insert this time in the database in GMT timezone?
Always UTC in Postgres
Understand that Postgres always stores values in a column of type TIMESTAMP WITH TIME ZONE in UTC, that is, with an offset from UTC of zero hours-minutes-seconds.
So the name is bit of a misnomer, in that no time zone is actually being stored. Instead, any time zone or offset indicator arriving with an input is used by Postgres to adjust to UTC. After adjustment, the UTC value is stored to the table while the zone/offset indicator is discarded. If you care about the original time zone, you must write that yourself to a second column.
What makes things confusing is tooling and middleware such as the psql console client app. These often are opinionated, choosing to inject some default time zone adjustment onto retrieved values. So while the Postgres database always retrieves a date-time in UTC from any TIMESTAMP WITH TIME ZONE column, you may see otherwise on your receiving end. Such a feature, while well-intentioned, is an unfortunate anti-feature to my mind.
Fortunately, I have not seen any JDBC driver that performs such an adjustment. You should always get a UTC value when retrieving an OffsetDateTime from a TIMESTAMP WITH TIME ZONE column (described below). But test your particular JDBC driver to be sure.
JDBC and the java.time classes
The modern solution for date-time handling in Java is the java.time classes.
To capture the current moment, use Instant. This class represents a moment as seen in UTC.
Instant instant = Instant.now() ; // Capture the current moment as seen in UTC.
However, despite Instant being the most basic and probably the most commonly used java.time class, the JDBC 4.2 team made the inexplicable decision to not require its support. (Nor is ZonedDateTime support required, by the way.)
Instead, the JDBC 4.2 spec requires support for OffsetDateTime. Fortunately, conversion between Instant and OffsetDateTime is trivial.
OffsetDateTime odt = instant.atOffset( ZoneOffset.UTC ) ;
Or you can skip the Instant class is this case.
OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC ) ;
Write to the database.
myPreparedStatement.setObject( … , odt ) ;
Retrieve from the database.
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
This OffsetDateTime object will carry an assigned offset from UTC of zero hours-minutes-seconds — commonly called just “UTC” as an abbreviation.
Extract an Instant if needed.
Instant instant = odt.toInstant() ;
Or adjust into a time zone.
ZoneId z = ZoneId.of( "Asia/Tokyo" ) ;
ZonedDateTime zdt = odt.atZoneSameInstant( z ) ;
A caution regarding possible data-loss: The resolution in java.time is nanoseconds. Capturing the current moment is likely limited to microseconds because today’s commonly-used hardware clocks go no finer. Nevertheless, a java.time object such as OffsetDateTime may carry nanoseconds. Meanwhile, Postgres date-time values have a resolution of microseconds. So if using a java.time object with any nanos present, writing that value with Postgres will truncate the nanos, resulting in a different value to be retrieved later.
I think paragraph 8.5.1.2 of the manual might be enlightening. It states that by default time is assumed to be without timezone and if one is given it is silently ignored.
In order to make things clear I think it is best to explicitely cast the time :
pti=> select timestamp with time zone '20100610T180000-5';
timestamptz
------------------------
2010-06-11 01:00:00+02
(1 row)
pti=> select timestamp with time zone '20100610T180000PST';
timestamptz
------------------------
2010-06-11 04:00:00+02
(1 row)
As is evident the time with time zone is properly converted from localtime to server time.
SELECT current_timestamp AT TIME ZONE 'gmt-5';

Categories

Resources