Showing posts with label timezone. Show all posts
Showing posts with label timezone. Show all posts

Thursday, October 11, 2018

Hibernate saves/retrieves date minus day if application uses another timezone than MySQL

Leave a Comment

I have an application started on tomcat on MACHINE_A with timezone GMT+3.

I use remote mysql server started on MACHINE_B with timezone UTC.

We use spring-data-jpa for persistence.

As an example of the problem, I will show the repository:

public interface MyRepository extends JpaRepository<MyInstance, Long> {     Optional<MyInstance> findByDate(LocalDate localDate); } 

If I pass localDate for 2018-09-06, I get entities where the date is 2018-09-05(previous day)

In the logs I see:

2018-09-06 18:17:27.783 TRACE 13676 --- [nio-8080-exec-3] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [DATE] - [2018-09-06] 

I googled that question a lot and found a several articles with the same content(for example https://moelholm.com/2016/11/09/spring-boot-controlling-timezones-with-hibernate/)

So, I have the following application.yml:

spring:   datasource:     url: jdbc:mysql://localhost:3306/MYDB?useUnicode=true&characterEncoding=utf8&useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC     username: root     password: *****   jpa:     hibernate:       naming:         physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl     properties:       hibernate:         show_sql: true         use_sql_comments: true         format_sql: true         type: trace         jdbc:           time_zone: UTC 

But it doesn't help.

We use the following connector:

<dependency>     <groupId>mysql</groupId>     <artifactId>mysql-connector-java</artifactId>     <version>8.0.12</version> </dependency> 

How can I resolve my problem?

P.S.

I tried to run both applications with the same time zone. In this case everything works as expected.

P.S.2

I tried to use mysql driver 6.0.6 version but it doesn't change anything.

6 Answers

Answers 1

If you're using LocalDate in Java, you should use a DATE column in MySQL. This way the problem will be solved.

If you use LocalDateTime, try setting the property like this in Spring Boot:

spring.jpa.properties.hibernate.jdbc.time_zone=UTC 

For a more detailed explanation, check out this article. You can find a test case in my High-Performance Java Persistence GitHub repository which works just fine.

Answers 2

Ideally, your both servers should be in same time zone and preferred one be in UTC time zone. And to show correct time to user in his timezone; you parse it in browser itself. And while retrieving data from DB; you use UTC time. This way you will not have issue while fetching data from DB

Answers 3

In MySQL...

TIMESTAMP internally stores UTC, but converts to/from the server's timezone based on two settings. Check those settings via SHOW VARIABLES LIKE '%zone%'; Properly configured, the reader may see a different time than the writer (based on tz settings).

DATE and DATETIME take whatever you give it. There is no tz conversion between the string in the client and what is stored in the table. Think of it a storing a picture of a clock. The reader will see the same time string that the writer wrote.

Answers 4

I faced similar issues while creating some integration tests for a spring-boot application using hibernate. The database I used here was postgreSQL.

As another answer correctly points out, you can set the hibernate.jdbc.time_zone=UTC property like discribed. Nevermind this didn't solve my issues, so I had to set the JVM default time zone with the help of the following in my spring-boot applications main class:

@PostConstruct public void init(){     TimeZone.setDefault(TimeZone.getTimeZone("UTC"));   // It will set UTC timezone     System.out.println("Spring boot application running in UTC timezone :"+new Date());   // It will print UTC timezone } 

This should also solve your problems. You can gather more informations here.

Reason

I guess your problem (retrieving date - 1 day) comes from your specific setup. If your application is running in UTC and requesting timestamps from a database in GMT+3 it resolves in a earlier date, because the applications context (JVM and Hibernate are responsible here) is 3 hours behind the database context in UTC. Simple example:

2018-12-02 00:00:00 - 3hours = 2018-12-01 21:00:00

As you are only looking to the dates: 2018-12-02 - 3hours = 2018-12-01

Answers 5

spring.jpa.properties.hibernate.jdbc.time_zone=UTC

It's used when you are working TimeZoned Date, but from your logs it seems you are not passing TimeZone:

binding parameter [1] as [DATE] - [2018-09-06]

Try to remote property:

spring.jpa.properties.hibernate.jdbc.time_zone=UTC

Answers 6

If you add the following parsing to your HQL query, it will return a date without any time zone format or time of day. This is a quick workaround to your issue.

select DATE_FORMAT(date,'%Y-%m-%d') from Entity 
Read More

Saturday, September 15, 2018

Hibernate saves/retrieves date minus day if application uses another timezone than MySQL

Leave a Comment

I have application started on tomcat on MACHINE_A with timzone GMT+3

I use remote mysql server started on MASHINE_B with time zone UTC.

We use spring-data-jpa for pesistence.

As example of problem I will demonstrate repository:

    public interface MyRepository extends JpaRepository<MyInstance, Long> {         Optional<MyInstance> findByDate(LocalDate localDate); } 

if I pass localDate for 2018-09-06 I get entities where date is 2018-09-05(previous day)

In logs I see:

2018-09-06 18:17:27.783 TRACE 13676 --- [nio-8080-exec-3] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [DATE] - [2018-09-06] 

I googled that question a lot and found a several articles with the same content(for example https://moelholm.com/2016/11/09/spring-boot-controlling-timezones-with-hibernate/)

So I have following application.yml:

spring:   datasource:     url: jdbc:mysql://localhost:3306/MYDB?useUnicode=true&characterEncoding=utf8&useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC     username: root     password: *****   jpa:     hibernate:       naming:         physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl     properties:       hibernate:         show_sql: true         use_sql_comments: true         format_sql: true         type: trace         jdbc:           time_zone: UTC 

But it doesn't help anyway.

We use following connector:

<dependency>     <groupId>mysql</groupId>     <artifactId>mysql-connector-java</artifactId>     <version>8.0.12</version> </dependency> 

How can I resolve my problem?

P.S.

I tried to run both applications with the same timezone. At this case averything works as expected

4 Answers

Answers 1

If you're using LocalDate in Java, you should use a DATE column in MySQL. This way the problem will be solved.

If you use LocalDateTime, try setting the property like this in Spring Boot:

spring.jpa.properties.hibernate.jdbc.time_zone=UTC 

For a more detailed explanation, check out this article. You can find a test case in my High-Performance Java Persistence GitHub repository which works just fine.

Answers 2

Ideally, your both servers should be in same time zone and preferred one be in UTC time zone. And to show correct time to user in his timezone; you parse it in browser itself. And while retrieving data from DB; you use UTC time. This way you will not have issue while fetching data from DB

Answers 3

In MySQL...

TIMESTAMP internally stores UTC, but converts to/from the server's timezone based on two settings. Check those settings via SHOW VARIABLES LIKE '%zone%'; Properly configured, the reader may see a different time than the writer (based on tz settings).

DATE and DATETIME take whatever you give it. There is no tz conversion between the string in the client and what is stored in the table. Think of it a storing a picture of a clock. The reader will see the same time string that the writer wrote.

Answers 4

I faced similar issues while creating some integration tests for a spring-boot application using hibernate. The database I used here was postgreSQL.

As another answer correctly points out, you can set the hibernate.jdbc.time_zone=UTC property like discribed. Nevermind this didn't solve my issues, so I had to set the JVM default time zone with the help of the following in my spring-boot applications main class:

@PostConstruct public void init(){     TimeZone.setDefault(TimeZone.getTimeZone("UTC"));   // It will set UTC timezone     System.out.println("Spring boot application running in UTC timezone :"+new Date());   // It will print UTC timezone } 

This should also solve your problems. You can gather more informations here.

Reason

I guess your problem (retrieving date - 1 day) comes from your specific setup. If your application is running in UTC and requesting timestamps from a database in GMT+3 it resolves in a earlier date, because the applications context (JVM and Hibernate are responsible here) is 3 hours behind the database context in UTC. Simple example:

2018-12-02 00:00:00 - 3hours = 2018-12-01 21:00:00

As you are only looking to the dates: 2018-12-02 - 3hours = 2018-12-01

Read More

Monday, July 16, 2018

Does `serverTimezone` param change @@session.time_zone in MySQL?

Leave a Comment

If I'm making a connection to MySQL with serverTimezone=UTC in my connection URL, will that change the @@session.time_zone variable for my connection to MySQL? Or is the only way to alter @@session.time_zone through issuing a SET time_zone... statement? I was lead to believe that the combination of serverTimezone=UTC and useLegacyDateTimeCode=false would set the @@session.time_zone to UTC (or whatever timezone I passed in as an argument to serverTimezone) but testing this behaviour with MySQL Connector/J seems to indicate it does not.

1 Answers

Answers 1

If you want your MySQL to be configured UTC :

SET @@global.time_zone = '+00:00'; 

So every automatic update of datetime and everytime you connect to the database, @@session.time_zone will be set to @@global.time_zone.

Reboot and reconnection of the DB could be needed.

If you're just interested by the modification of the timezone of your session :

SET @@session.time_zone = '+00:00'; 

You need to do this after every connection.

Read More

Thursday, March 17, 2016

Saving datetime in UTC isn't accurate sometimes

Leave a Comment

In general, best practice when dealing with dates is to store them in UTC and convert back to whatever the user expects within the application layer.

That doesn't necessarily work with future dates, particularly where the date/time is specific to the user in their timezone.

In my instance,there’s one attribute that’s a timestamp containing the start_time of a future event, compared to everything else that's now or in the past (including the created_at and updated_at timestamps).

Why

This particular field is the timestamp of a future event where the user selects the time.

For future events, it seems best practice is not to store UTC.

Instead of saving the time in UTC along with the time zone, developers can save what the user expects us to save: the wall time.

When the user chooses 10am, it needs to stay 10am even when the user’s offset from UTC changes between creation and the event date due to daylight savings.

So, in June 2016, if a user creates an event for 1st Jan 2017 at midnight in Sydney, that timestamp will be stored in the database as 2017-01-01 00:00. The offset at time of creation would be +10:00, but at the time of the event, it’d be +11:00.. unless government decides to change that in the meantime.

Like wise, I’d expect a separate event that I create for 1 Jan 2016 at midnight in Brisbane to also be stored as 2017-01-01 00:00. I store the timezone i.e. Australia/Brisbane in a separate field.

What’s a best practice way to do this in Rails?

I’ve tried lots of options with no success:

1. Skip conversion

Problem, this only skips conversion on read, not writing.

self.skip_time_zone_conversion_for_attributes = [:start_time] 

2. Change the whole app configuration to use config.default_timestamp :local

To do this, I set:

config/application.rb

config.active_record.default_timezone = :local config.time_zone = 'UTC' 

app/model/event.rb

... self.skip_time_zone_conversion_for_attributes = [:start_time] before_save :set_timezone_to_location after_save :set_timezone_to_default  def set_timezone_to_location   Time.zone = location.timezone end  def set_timezone_to_default   Time.zone = 'UTC' end ... 

To be frank, I’m not sure what this is doing.. but not what I want.

I thought it was working as my Brisbane event was stored as 2017-01-01 00:00 but when I created a new event for Sydney, it was stored as 2017-01-01 01:00even though it displays as midnight correctly in the view.

That being the case, I’m concerned that still have the same problem with the Sydney event that I’m trying to avoid.

3. Override the getter and setter for the model to store as integer

I’ve tried to also store the event start_time as an integer in the database.

I tried doing this by monkey patching the Time class and adding a before_validates callback to do the conversion.

config/initializers/time.rb

class Time   def time_to_i     self.strftime('%Y%m%d%H%M').to_i   end end 

app/model/event.rb

before_validation :change_start_time_to_integer def change_start_time_to_integer   start_time = start_time.to_time if start_time.is_a? String   start_time = start_time.time_to_i end  # read value from DB # TODO: this freaks out with an error currently def start_time   #take integer YYYYMMDDHHMM and convert it to timestamp   st = self[:start_time]   Time.new(     st / 100000000,     st / 1000000 % 100,     st / 10000 % 100,     st / 100 % 100,     st % 100,     0,     offset(true)   ) end 

Ideal Solution

I’d like to be able to store a timestamp in its natural datatype in the database so queries don’t get messy in my controllers, but I can’t figure out how to store “wall time” that doesn’t convert.

Second best, I’d settle for the integer option if I have to.

How do others deal with this? What am I missing? Particularly with the "integer conversion" option above, I'm making things far more complicated than they need to be.

3 Answers

Answers 1

I propose that you still use the first option but with a little hack: in essence, you can switch off the time zone conversion for the desired attribute and use a custom setter to overcome the conversion during attribute writes.

The trick saves the time as a fake UTC time. Although technically it has an UTC zone (as all the times are saved in db in UTC) but by definition it shall be interpreted as local time, regardless of the current time zone.

class Model < ActiveRecord::Base   self.skip_time_zone_conversion_for_attributes = [:start_time]    def start_time=(time)     write_attribute(:start_time, time ? time + time.utc_offset : nil)   end end 

Let's test this in rails console:

$ rails c >> future_time = Time.local(2020,03,30,11,55,00) => 2020-03-30 11:55:00 +0200  >> Model.create(start_time: future_time) D, [2016-03-15T00:01:09.112887 #28379] DEBUG -- :    (0.1ms)  BEGIN D, [2016-03-15T00:01:09.114785 #28379] DEBUG -- :   SQL (1.4ms)  INSERT INTO `models` (`start_time`) VALUES ('2020-03-30 11:55:00') D, [2016-03-15T00:01:09.117749 #28379] DEBUG -- :    (2.7ms)  COMMIT => #<Model id: 6, start_time: "2020-03-30 13:55:00"> 

Note that Rails saved the time as a 11:55, in a "fake" UTC zone.

Also note that the time in the object returned from create is wrong because the zone is converted from the "UTC" in this case. You would have to count with that and reload the object every time after setting the start_time attribute, so that the zone conversion skipping can take place:

>> m = Model.create(start_time: future_time).reload D, [2016-03-15T00:08:54.129926 #28589] DEBUG -- :    (0.2ms)  BEGIN D, [2016-03-15T00:08:54.131189 #28589] DEBUG -- :   SQL (0.7ms)  INSERT INTO `models` (`start_time`) VALUES ('2020-03-30 11:55:00') D, [2016-03-15T00:08:54.134002 #28589] DEBUG -- :    (2.5ms)  COMMIT D, [2016-03-15T00:08:54.141720 #28589] DEBUG -- :   Model Load (0.3ms)  SELECT  `models`.* FROM `models` WHERE `models`.`id` = 10 LIMIT 1 => #<Model id: 10, start_time: "2020-03-30 11:55:00">  >> m.start_time => 2020-03-30 11:55:00 UTC 

After loading the object, the start_time attribute is correct and can be manually interpreted as local time regardless of the actual time zone.

I really don't get it why Rails behaves the way it does regarding the skip_time_zone_conversion_for_attributes configuration option...

Update: adding a reader

We can also add a reader so that we automatically interpret the saved "fake" UTC time in local time, without shifting the time due to timezone change:

class Model < ActiveRecord::Base   # interprets time stored in UTC as local time without shifting time   # due to time zone change   def start_time     t = read_attribute(:start_time)     t ? Time.local(t.year, t.month, t.day, t.hour, t.min, t.sec) : nil   end end 

Test in rails console:

>> m = Model.create(start_time: future_time).reload D, [2016-03-15T08:10:54.889871 #28589] DEBUG -- :    (0.1ms)  BEGIN D, [2016-03-15T08:10:54.890848 #28589] DEBUG -- :   SQL (0.4ms)  INSERT INTO `models` (`start_time`) VALUES ('2020-03-30 11:55:00') D, [2016-03-15T08:10:54.894413 #28589] DEBUG -- :    (3.1ms)  COMMIT D, [2016-03-15T08:10:54.895531 #28589] DEBUG -- :   Model Load (0.3ms)  SELECT  `models`.* FROM `models` WHERE `models`.`id` = 12 LIMIT 1 => #<Model id: 12, start_time: "2020-03-30 11:55:00">  >> m.start_time => 2020-03-30 11:55:00 +0200 

I.e. the start_time is correctly interpreted in local time, even though it was stored as the same hour and minute, but in UTC.

Answers 2

I vote the simplest route and that's saving to UTC. Create a column for country of origin, set up a google alert for "daylight savings time", and if Chile decides to stop using daylight savings or alter it in some crazy way, you can adapt by querying your database for Chilean users and adjusting their dates accordingly with a script. Then, you can use Time.parse with the date, time and timezone. To illustrate, here are the results the day before daylight savings and after daylight savings on 3/13/2016:

Time.parse("2016-03-12 12:00:00 Pacific Time (US & Canada)").utc => 2016-03-12 20:00:00 UTC Time.parse("2016-03-14 12:00:00 Pacific Time (US & Canada)").utc => 2016-03-14 19:00:00 UTC 

This will get you a list of the accepted time zone names:

timezones = ActiveSupport::TimeZone.zones_map.values.collect{|tz| tz.name} 

Answers 3

This may sound a bit out there, but I have dealt with similar issues with a recent application I was tasked with - but on the opposite side - when I run an ETL to load data for the application, dates from the source are stored in EST. Rails believes that it is UTC when serving the data, so for that, I converted the dates back to UTC using P/SQL. I did not want these dates to be different than the other date fields within the app.

Option A In this case, could you capture the user timezone at creation, and send that back as a hidden field in the form? I am still learning RoR, so am not sure on the "proper" way to do this, but right now I would do something like this:

Example (I tested this, and it will submit the offset (minutes) in a hidden field):

<div class="field">   <%= f.hidden_field :offset %> </div>  <script>   utcDiff = new Date().getTimezoneOffset();   dst_field = document.getElementById('timepage_offset');   dst_field.value = utcDiff; </script> 

If you then send utcDiff along with the user selected date, you could calculate the UTC date before storing. I suppose you could add that to the model as well if that data is necessary to know at a later date.

I think that no matter how this is done, there will always be slight area for confusion, unless the user is capable of providing the proper information, which leads me to...

Option B: You could, instead of a hidden field, provide a select list (and to be friendly, default it to the users' local offset), to allow them to provide the zone for which their date is specified in.

Update - TimeZone select I've done some research, and it looks like there is already a form helper for a time zone select box.

Read More