Sachinthana Aluvihare
Sachinthana Aluvihare

Reputation: 273

How to change Java Date timezone from server local timezone to a different timezone for a spring boot native query?

I have records in a db with a column created_date saved as timestamp without time zone. Front-end users are based in +05.30 timezone. Back-end is hosted in Singapore. So the created_date date values are 5.30 hours behind the user's time.

How do i properly select a set of records that got created within a given day.

Eg: if the user is requesting data on 16-oct-2021, server needs to return the data with the created_date between 2021-10-16 00:00:00 and 2021-10-16 23:59:59:999 in their local time which is +05.30

I have a native query written for this as below,

select * from records rr where rr.created_date at time zone 'utc' at time zone 'Asia/Calcutta' between ?1 and ?2

I get the date 1 and 2 with the following code, which is giving the required data set only after 05.30am in users's timezone (+05.30)

Calendar startDate = (Calendar) Calendar.getInstance();
startDate.set(Calendar.HOUR_OF_DAY, 0);
startDate.set(Calendar.MINUTE, 0);
startDate.set(Calendar.SECOND, 0);
startDate.set(Calendar.MILLISECOND, 0);
                        
Calendar endDate = (Calendar) startDate.clone();
endDate.set(Calendar.HOUR_OF_DAY, 23);
endDate.set(Calendar.MINUTE, 59);
endDate.set(Calendar.SECOND, 59);
endDate.set(Calendar.MILLISECOND, 999);

Date fromDate = startDate.getTime();
Date toDate = endDate.getTime();

So, Assuming that i need to convert the startDate and endDate to the user's local timezone before passing as parameters for the query, i did the following,

Calendar startDate = (Calendar) Calendar.getInstance();
startDate.setTimeZone(TimeZone.getTimeZone("Asia/Calcutta")); // convert from server timezone to user's local timezone
startDate.set(Calendar.HOUR_OF_DAY, 0);
startDate.set(Calendar.MINUTE, 0);
startDate.set(Calendar.SECOND, 0);
startDate.set(Calendar.MILLISECOND, 0);   
                        
Calendar endDate = (Calendar) startDate.clone();
endDate.set(Calendar.HOUR_OF_DAY, 23);
endDate.set(Calendar.MINUTE, 59);
endDate.set(Calendar.SECOND, 59);
endDate.set(Calendar.MILLISECOND, 999);

Date fromDate = startDate.getTime();
Date toDate = endDate.getTime();

This still doesn't return me the correct data set. If i directly run the below query on pgadmin i can get the results

select * from records rr where rr.created_date at time zone 'utc' at time zone 'Asia/Calcutta' between '2021-10-16 00:00:00' and '2021-10-16 23:59:59' 

How do i get the same data set with my code? Any help would be much appreciated. TIA

Upvotes: 0

Views: 1147

Answers (1)

Basil Bourque
Basil Bourque

Reputation: 338574

You said:

column created_date saved as timestamp without time zone

That is the wrong data type for recording moments. Specific points on the timeline must be written to a column of the type TIMESTAMP WITH TIME ZONE, not WITHOUT.

Postgres uses the offset-from-UTC or time zone info supplied with any input to adjust to UTC. Values in a TIMESTAMP WITH TIME ZONE column in Postgres are always in UTC, always.

You said:

run the below query on pgadmin

Unfortunately, many tools including pgAdmin have the anti-feature where they apply some default time zone to adjust the value retrieved from the database. This gives the false illusion of the value having been stored with a particular time zone. But as I said, values in a TIMESTAMP WITH TIME ZONE column in Postgres are always in UTC, always. I suggest always setting the pgAdmin session default time zone to UTC.

Never use Calendar or Date. Use only java.time classes with JDBC 4.2 or later.

The time zone Asia/Calcutta has been renamed to Asia/Kolkata.

If you want the start and end of a day as seen in modern India 🇮🇳:

ZoneId z = ZoneId.of( "Asia/Kolkata" ) ;
LocalDate today = LocalDate.now( z ) ;
ZonedDateTime start = today.atStartOfDay( z ) ;
ZonedDateTime end = today.plusDays( 1 ).atStartOfDay( z ) ;

We are using the Half-Open approach to defining a span of time, usually best for date-time work.

Convert to OffsetDateTime before passing to your PreparedStatement, to use a data type appropriate to SQL.

OffsetDateTime startOdt = start.toOffsetDateTime() ;

You wrote:

Calendar startDate = (Calendar) Calendar.getInstance();

No need to cast here. That method already returns a Calendar object, so casting is pointless. (And, never use Calendar.)

I’ve been brief here, as this topic has already been addressed many many times on Stack Overflow. Search to learn more.

Upvotes: 1

Related Questions