MySQL - Passing UTC timestamps to sprocs via JDBC - mysql

I have a MySQL Server set to UTC (##global.time_zone = '+00:00') and a table with a DATETIME column in which I store dates in UTC. I'm having problems getting UTC dates to come through when I call a stored procedure via JDBC. Example:
java.util.Date now = new java.util.Date();
sproc = conn.prepareCall("{call TzTestInsert(?)}");
sproc.setTimestamp(1, new java.sql.Timestamp(now.getTime()), Calendar.getInstance(TimeZone.getTimeZone("GMT+00:00")));
sproc.execute();
The TzTestInsert sproc simply takes a DATETIME and inserts it into the table.
I'd expect the database to now hold my current time in UTC, but in fact it holds the current time for my timezone.
If I change the sproc to take a string it works...
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
...
sproc.setString(1, dateFormat.format(now));
but I'd rather use the correct type in the sproc.
Also works if I bypass the sproc, but again not my preferred solution...
String sql = "INSERT INTO TzTest VALUES('" + dateFormat.format(now) + "') ;
With the original sproc I have the same issue if I use a TIMESTAMP datatype in the sproc and table, which isn't surprising with the server in UTC since any timezone conversions specific to MySQL TIMESTAMP should be noops.
Calling the sproc from a MySQL Workbench connection works fine, e.g.
CALL TzTestInsert(UTC_TIMESTAMP());
Seems like the problem is in JDBC. I've looked at the various timezone connection parameters and haven't found any that make a difference.
I must be missing something basic - lots of people do this, right?

Solution was to pass the JDBC driver "useLegacyDatetimeCode=false". See mysql bug http://bugs.mysql.com/bug.php?id=15604
Looks like they left the old code in the driver for backwards compatibility.

Related

how to find out the timezone of given time from mysql db in node js

I am quite new to Node js.
I want to know what timezone the given datetime value, which comes from mysql db, it is .
For example
const conference = await mysqlHelper.query(conferenceSql);
// select query
const time = conference[0].created_datetime
// I wonder how to get the timezone from this time variable here.
console.log(time)
// console.log returns '2021-02-23T01:30:00.000Z' to the terminal
mysql value type: datetime
I know I could directly look up the db table instead of figuring out in node js. However, this is a required task for validation in my server.
Thanks a lot !!
Mysql datetime does not store time zone information.
2021-02-23T01:30:00.000Z is displayed in an ISO 8601 format, being in UTC.

MySql Timezone JDBC issue

I am trying to insert a date value in MySql table name person and column name regdate with data type = datetime. I am setting a value e.g. '2019-08-21 20:25:20' but after saving +5:30 hours get added and value which gets stored is '2019-08-22 03:55:20'. Generating the date value using below Java code
Timestamp curDate = Timestamp.valueOf(Instant.now().atZone(ZoneId.of("Asia/Kolkata")).toLocalDateTime());
and then using .setTimestamp(1, curdate); in INSERT query.
I have checked that the timezone of MySql is set to IST (GMT+0530). App Server timezone is also set to IST. But I am not able to understand why +5:30 hours are getting added even if I explictly setting the date value.
I have tried setting timezone in connection string as ?serverTimezone=Asia/Kolkata but didn't work.
But if I run the same code using my local machine connecting same MySql instance, I get no problem and same value gets stored without addition of 5:30 hours. I checked App Server timezone and it is IST.
MySql version - 5.7.17-log
mysql-connector-java - 8.0.15
Am I missing something?
You have a few problems here.
Avoid legacy date-time classes
First of all, you are mixing the terrible legacy date-time classes (java.sql.Timestamp) with the modern java.time classes. Don’t. Use only classes from the java.time packages.
LocalDateTime cannot represent a moment
You are using LocalDateTime to track a moment, which it cannot. By definition, that class does not represent a point on the time line. That class has a date and a time-of-day but intentionally lacks the context of a time zone or offset-from-UTC. Calling toLocalDateTime strips away vital information about zone/offset.
Tools lie
You are likely getting confused by the well-intentioned but unfortunate behavior of many tools to dynamically apply a time zone while generating text to represent the date-time value retrieved from the database. You can avoid this by using Java & JDBC to get the pure unadulterated value from the database.
TIMESTAMP WITH TIME ZONE
You failed to disclose the exact data type of your column in your database. If you are trying to track a moment, use a data type akin to the SQL-standard type TIMESTAMP WITH TIME ZONE. In MySQL 8 that would, apparently, be the TIMESTAMP type according to this doc. (I am a Postgres guy, not a MySQL user.)
In JDBC 4.2 and later, we can exchange java.time objects with the database. So no need to over touch java.sql.Timestamp again.
Unfortunately, the JDBC spec oddly chose to not require support for Instant (a moment in UTC) nor forZonedDateTime(a moment as seen in some particular time zone). The spec does require support for [OffsetDateTime`]2.
Tip: Learn to work in UTC for the most part. Adjust into a time zone only when required by business logic or for presentation to the user.
OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC ) ; // Capture current moment in UTC.
Write to the database via a prepared statement.
myPreparedStatement.setObject( … , odt ) ;
Retrieval.
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
View that moment through the wall-clock time used by the people of some particular region (a time zone).
ZoneId z = ZoneId.of( "Asia/Kolkata" ) ;
ZonedDateTime zdt = odt.atZoneSameInstant( z ) ;

MySQL/Hibernate: getting value +1H on insert type Time

I'm working on a java web project that uses:
Hibernate 5.2.2/JPA 2.0+ MySQL5InnoDBDialect
MySQL 5.6.15-innoDB (on EasyPHP/PHPMyAdmin) + JDBC connector 6.0.4
Joda time API 2.9.4 + Fasterxml jackson API 2.8.3
I'm facing a problem on inserting Time data on database. everytime i put a row, i get a +1H value on time column!
Attribute on Java:
#JsonFormat(shape=JsonFormat.Shape.STRING, pattern="HH:mm")
#Column(name = "RES_DUREE", nullable = false)
#Temporal(TemporalType.TIME) private Date resDuree;
Attribute on SQL:
RES_DUREE TIME NOT NULL;
EDIT (After Adrian Shum's Comment):
Connection line:
jdbc.url =
jdbc:mysql://localhost:3306/mydb?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
I do use UTC but it still 1H+ .
Any suggestion will help, thanks.
Normally it is caused by server and DB time zone mismatch.
In brief, java.util.Date does not contain Timezone information. Conceptually you can treat it as simply representing Date + Time (similar to what JODA/JSR310 LocalDateTime is doing). Therefore if your app server is UTC+10, and your DB is UTC+2, when you save a date of "2016-10-13 10:11:12", although your app server is treating it as "2016-10-13 10:11:12 +10:00", it is simply passing "2016-10-13 10:11:12" to DB. Given DB is UTC+2, it is thinking the time actually means "2016-10-13 10:11:12 +02:00". Situation become more messy if your JDBC connection is claimed to be "UTC+10", most DB is going to "smartly" translate "2016-10-13 10:11:12 +02:00" to "2016-10-13 18:11:12 +10:00" which caused weird time stored and retrieved.
You may diagnose by tracing the SQL (and the actual value used) for corresponding inserts and select. You should see discrepancies between the values, vs the value stored in table. Such tracing can be done by misc way, e.g.
Older version of Hibernate can show the parameter used in prepared statement by turning on proper logger
You may use tools like JdbcDsLog (Disclaimer: I am maintainer for a fork of JbdcDsLog at http://github.com/adrianshum/jdbcdslog)
There is probably tools in DBMS side to trace incoming queries.
Best way to solve is to make sure everything is in the same timezone, and the most rational choice for timezone is UTC.

Bypass MySQL automatic GMT conversion when selecting TIMESTAMP fields

There's lots of stuff on the internet about converting MySQL timestamps, how it works etc. But what I need is the opposite: knowing that MySQL stores every datetime data as UTC in TIMESTAMP fields, all I want is to direclty retrieve the stored UTC value without MySQL messing around the datetime with system/server/connection timezones.
You see, we've built a simple node.js feeder which reads from several third-part MySQL databases (so I can't change their timezone settings) and save the gathered data to a Elasticsearch, as a "denormalization process". As the original data comes from different timezones, I need to store them in UTC, so I can easily coordinate further GETs and aggregations.
I know I can set the connection timezone on the fly and I know I can change every timestamp field fetched in my node application, but since MySQL engine already stores timestamps in UTC, why should I add any other step if I could simply get it directly, without converting functions or costly data processings?
In a nutshell, I'd like to know: is there a way to bypass MySQL automatic GMT conversion?
MySQL provides a UNIX_TIMESTAMP function which returns a raw integer value. And that isn't subject to timezone conversions at all.
SELECT UNIX_TIMESTAMP( timestamp_col ) ...
But that returns a raw integer, not a datetime. The client would need to do the conversion into a "datetime" type object, if that's needed.
Another option would be to use the CONVERT_TZ function to convert to UTC from the session time_zone setting.
SELECT CONVERT_TZ( timestamp_col, ##session.time_zone,'+00:00')
But, that doesn't really "bypass" timezone conversion. One downside of this approach is if the session time_zone is affected by daylight saving time changes, there's ambiguity with a one hour period each year when the clock "falls back" one hour. e.g. Sunday, Nov 1 2015 2AM CDT transition to Sunday Nov 1 2015 1AM CST. (Converting back from UTC, if we get 1:30 AM in the session time_zone, we don't "know" if that's CDT or CST. And the conversion back to UTC doesn't know which it is either.)
Another option (which I think you already mentioned) is changing the session time_zone to UTC. Then you could just return the timestamp_col value as UTC. You could save the current time_zone setting, and set it back when you are done, e.g.
SET #save_session_time_zone := ##session.time_zone ;
SET time_zone = '+00:00' ;
SELECT timestamp_col ...
SET time_zone = #save_session_time_zone ;
But your client Connector might do some not-so-helpful conversions when the time_zone of the MySQL database session doesn't match the time_zone of the client, like the funky shenanigans the JDBC driver (MySQL Connector/J) does. (That concern isn't limited to returning UTC; that's a concern whenever the time_zone of the client doesn't match the time_zone of the database session.)
It looks like there's no way to get the original UTC value from a MySQL field; every single function uses the timezone setting, be that SYSTEM or any other you configure.
The way MySQL forces you to use a date conversion is, at least, very constraining. For example, say you have a MySQL server set to a timezone with GMT -03:00 and GMT/DST -02:00 and you store a datetime like '2016-07-01 10:00:00'. If you select this value after the DST has ended, you'll get '2016-07-01 09:00:00'.
You can't tell what time it is for sure unless you store the GMT offset separately or you previously know what timezone the server was when it was stored.
We used the second approach. We saved the server timezone and used it to calculate the offset and return an ISO datetime, so future calculations can be made easily.
DROP FUNCTION IF EXISTS `iso_datetime`;;
CREATE FUNCTION `iso_datetime` (
p_datetime TIMESTAMP
) RETURNS VARCHAR(25)
READS SQL DATA
BEGIN
DECLARE _timezone VARCHAR(255) DEFAULT NULL;
DECLARE _offset VARCHAR(6) DEFAULT NULL;
SET _timezone = (SELECT timezone FROM network);
SET _offset = (SELECT SUBSTRING(TIMEDIFF(p_datetime,CONVERT_TZ(p_datetime, _timezone,'UTC')), 1,6));
RETURN CONCAT(DATE_FORMAT(p_datetime, '%Y-%m-%dT%H:%i:%S'), _offset);
END;
In order to do so, you have to load timezone info into MySQL, so the server can calculate the tz offset of the date for you.

Comparing string with SmallDateTime used to work

I'm trying to debug an old VB6 program at my job. It's full of bugs and hardcoded stuff.
The program is connecting on a SQL Server 2005 database to read and write info using stored procedures.
Since the program is still being used while I'm working on it, I made a backup of the database to test some things and restored it in my version of SQL Server 2008. Everything was working fine.
When I came in the next morning, I got an error I didn't have the previous night even though the program wasn't used in the night.
The problem :
"exec dbo.sp_Get_Wait_Data '" & DateEx & "'"
DateEx is a string containing "2012/06/14"
The stored procedure :
[sp_Get_Wait_Data] #Datest as char(10)
AS
SELECT
(A lot of column names here)
FROM
Fiche
LEFT JOIN
voyage ON fcid = vofiche
LEFT JOIN
client on fcaccount = cusnov
WHERE
fcdate = #Datest
AND (void is null or (void > 0 and (void <> 999 and void <> 1000 and void <> 998)))
AND ((fcremarques NOT LIKE '%SYSANNULATION%' OR
fcremarques IS NULL)
AND fcrettime IS NOT NULL)
ORDER BY
FcTime, FcOrgSite, fcdessite
The error message :
The conversion of a varchar data type to a smalldatetime data type resulted in an out-of-range value
So the error is here fcdate=#Datest in the stored procedure. I tried adding this line in the stored procedure
SELECT convert(datetime, #Datest, 120)
which worked like a charm for the convertion within the query but caused a few hundred other errors within the program when other queries tried to access this variable so it is not an option.
Any idea why this query was working perfectly yesterday and now it's giving me this error? Thanks in advance.
You probably have set dateformat dmy when connecting to the copy of your database. The date format is set implicitly by the language used. set language.
You could change the format of the parameter to YYYYMMDD which will be safe regardless of set dateformat or even better, change your parameter to datetime.
If that is not an option you can rewrite your query using where fcdate=convert(datetime, #Datest, 111)