How to use SQL Server AT TIME ZONE clause with JPQL? - java

I have to convert date fields in where clause of query from one time zone(server time zone) to another (client time zone). I am able to achieve this for MySQL database using CONVERT_TZ function, but for Microsoft SQL Server, AT TIME ZONE clause throwing error in below JPQL.
select (PR.requestDate AT TIME ZONE 'Central European Time' AT TIME ZONE 'India Standard Time'),count(*) FROM com.grc.pam.model.entity.PrivilegeRequest PR where 1 = 1 and (PR.requestDate AT TIME ZONE 'Central European Time' AT TIME ZONE 'India Standard Time') >= ?1 and (PR.requestDate AT TIME ZONE 'Central European Time' AT TIME ZONE 'India Standard Time') < ?2 GROUP BY (PR.requestDate AT TIME ZONE 'Central European Time' AT TIME ZONE 'India Standard Time')
Error:
18:13:50.006 [http-nio-8080-exec-10] ERROR org.hibernate.hql.internal.ast.ErrorTracker - line 1:24: unexpected token: AT
antlr.NoViableAltException: unexpected token: AT at org.hibernate.hql.internal.antlr.HqlBaseParser.expressionOrVector(HqlBaseParser.java:5226) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
Does this AT TIME ZONE clause works in JPQL? or is there any other way to achieve this?
Update:
Query with CONVERT funtion:
select (CONVERT(datetime,PR.requestDate,120) AT TIME ZONE 'India Standard Time'),count(*) FROM com.grc.pam.model.entity.PrivilegeRequest PR where 1 = 1 and (CONVERT(datetime,PR.requestDate,120) AT TIME ZONE 'India Standard Time') >= ?1 and (CONVERT(datetime,PR.requestDate,120) AT TIME ZONE 'India Standard Time') < ?2 GROUP BY (CONVERT(datetime,PR.requestDate,120) AT TIME ZONE 'India Standard Time')
Error:
14:23:06.303 [http-nio-8080-exec-4] ERROR
org.hibernate.hql.internal.ast.ErrorTracker - line 1:46: unexpected
token: AT
14:23:06.319 [http-nio-8080-exec-4] ERROR
org.hibernate.hql.internal.ast.ErrorTracker - line 1:46: unexpected
token: AT
antlr.NoViableAltException: unexpected token: AT
Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException:
unexpected token: AT near line 1, column 46 [select
(CONVERT(datetime,PR.requestDate,120) AT TIME ZONE 'India Standard
Time'),count(*) FROM com.grc.pam.model.entity.PrivilegeRequest PR
where 1 = 1 and (CONVERT(datetime,PR.requestDate,120) AT TIME ZONE
'India Standard Time') >= ?1 and
(CONVERT(datetime,PR.requestDate,120) AT TIME ZONE 'India Standard
Time') < ?2 GROUP BY (CONVERT(datetime,PR.requestDate,120) AT TIME
ZONE 'India Standard Time')]

After did some research on this, found that we can not call/use this SQL Server "AT TIME ZONE" clause directly in JPQL.
As explained in MetadataBuilderContributor, used MetadataBuilderContributor utility to customize the MetadataBuilder like below:
public class SqlTimeZoneMetadataBuilderContributor implements MetadataBuilderContributor {
#Override
public void contribute(MetadataBuilder metadataBuilder) {
metadataBuilder.applySqlFunction("CONVERT_TIMEZONE", new SQLFunctionTemplate(StandardBasicTypes.TIMESTAMP,
"CONVERT(datetime, ?1 AT TIME ZONE ?2 AT TIME ZONE ?3)"));
}
}
Specified this to jpaProperties property of org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean as property.
<prop key="hibernate.metadata_builder_contributor">com.grc.common.SqlTimeZoneMetadataBuilderContributor</prop>
Usage in JPQL:
and (CONVERT_TIMEZONE(PR.requestDate, '"+serverDisplayName+"', '"+clientDisplayName+"')) >= ?

Related

Why Oracle DATE type storing with time stamp and how to store only date

My Oracle table define with date type for one of the date column requestedDate.
While persisting, I am setting the value like below
.setRequestedDate(Date.valueOf(LocalDate.now())))
And JPA entity defined like below,
#Column(name = "REQ_DT")
#Temporal(TemporalType.DATE)
private Date requestedDate;
I am expecting value like "2022-05-12", but it gets stored like "2022-05-12 00:00:00.0"? I want to get rid of the timestamp when it gets inserted into the database.
Do I need to change the date definition in Oracle DB like creating view to truncate the timestamp?
The Oracle DATE data type always includes a time component. If you don't specify one, then midnight is assumed. If you don't want the time portion to be displayed, then use the to_char function or some other native function in your programming language of choice in your application to control the display when the date is retrieved at runtime:
select to_char(sysdate,'YYYY-MM-DD') from dual;
TO_CHAR(SY
----------
2022-11-24
Oracle DATE = SQL standard TIMESTAMP WITHOUT TIME ZONE
The DATE type in Oracle database in misnamed. As others noted, that type represents a date with a time-of-day. So values in a column of this type always have a time-of-day.
Furthermore, that type lacks the context of a time zone or offset-from-UTC.
You said:
I want to get rid of the timestamp when it gets inserted into the database.
You cannot.
JDBC
This Oracle DATE type is equivalent to the SQL standard type TIMESTAMP WITHOUT TIME ZONE.
While I do not use Oracle Database, I expect the following code works when using JDBC 4.2 or later.
LocalDateTime
The Oracle type DATE matching in Java is java.time.LocalDateTime.
LocalDateTime ldt = myResultSet.getObject( … , LocalDateTime.class ) ;
LocalDate
Extract your date-only value as a java.time.LocalDate.
LocalDate ld = ldt.toLocalDate() ;
Today
Note that a time zone is involved in determining the current date. For any given moment, the date varies around the globe by time zone, “tomorrow” in Tokyo while simultaneously “yesterday” in Toledo.
ZoneId z = ZoneId.of( "Asia/Tokyo" ) ; // Or ZoneId.systemDefault()
LocalDate ld = LocalDate.now( z ) ;
Write today’s date to Oracle DATE column.
LocalDateTime ldt = ld.atStartOfDay() ; // Assign a time-of-day of 00:00:00 to the date-only `LocalDate` object, returning a `LocalDateTime` object.
myPreparedStatement.setObject( … , ldt ) ;
If you want to insert value without time component, then truncate it. You can't get rid of it because DATE datatype - in Oracle - always contains both date and time
Have a look at the following example:
SQL> create table test (id number, datum date);
Table created.
Altering the session, just to display full format while selecting DATE datatype values:
SQL> alter session set nls_date_format = 'dd.mm.yyyy hh24:mi:ss';
Session altered.
SYSDATE is a function that returns current date and time; as you can see, DATE_ONLY contains date component, while time is set to midnight (but it is still here):
SQL> select sysdate value_with_date_and_time,
2 trunc(sysdate) date_only
3 from dual;
VALUE_WITH_DATE_AND DATE_ONLY
------------------- -------------------
25.11.2022 07:11:21 25.11.2022 00:00:00
If you select such values into a table:
SQL> insert into test (id, datum)
2 select 1, sysdate from dual union all
3 select 2, trunc(sysdate) from dual;
2 rows created.
Select from it:
SQL> select * from test order by id;
ID DATUM
---------- -------------------
1 25.11.2022 07:11:48 --> both date and time
2 25.11.2022 00:00:00 --> also date AND time, but time is set to midnight
If you want to display values without time, either alter session and set another format model, or use to_char function; once again: this will just display date only, but value - stored in the table - will still have both date and time:
SQL> select id, to_char(datum, 'dd.mm.yyyy') display_date
2 from test order by id;
ID DISPLAY_DA
---------- ----------
1 25.11.2022
2 25.11.2022
SQL>

How to convert PostgreSQL timestamp with time zone to Java Instant or OffSetDateTime?

How to convert PostgreSQL timestamp with time zone to Java Instant or OffSetDateTime?
PostgreSQL timestamp with time zone format: 2020-06-18 16:15:38+05:30
Getting the following exception in Java 11.7 - Ubuntu for Instant.parse("2020-06-18 16:15:38+05:30".replace(" ", "T"))
Exception in thread "main" java.time.format.DateTimeParseException: Text '2020-06-18T16:15:38+05:30' could not be parsed at index 19
at java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:2046)
at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1948)
at java.base/java.time.Instant.parse(Instant.java:395)
at OffSetDateTimeExample.main(OffSetDateTimeExample.java:9)
but it works in Java 13.
Any help to make it work in Java 11
Split the range type value
tstzrange is a range type in Postgres.
Split the PostgreSQL tstzrange in query by calling the lower  and upper functions.
select
*,
lower(tstzrange) as lower_tstzrange,
upper(tstzrange) as upper_tstzrange
from announcement
;
and use it in Resultset as OffsetDateTime
TstzRange.builder()
.startDateTime(rs.getObject("lower_tstzrange", OffsetDateTime.class))
.endDateTime(rs.getObject("upper_tstzrange", OffsetDateTime.class))
.build()
Thanks to a_horse_with_no_name and Arvind Kumar Avinash for saving my day & learnt splitting range datatypes.

JDBC Mysql Timezone issue with Day Light saving

I am facing an issue with MySql Select statement for date field with Day Light Saving.
Cause: java.sql.SQLException: HOUR_OF_DAY: 2 -> 3
The only solution worked for is setting serverTimezone=GMT-6 in MySql Connection String. But this I will need to change when daylight saving is not there.
If I am trying to set serverTimezone=America/Denver then it give same error.
Cause: java.sql.SQLException: HOUR_OF_DAY: 2 -> 3
My Database time zone is America/Denver
My project is Jersey project with Java 8 and Mybatis.
I tried multiple suggested solution but nothing is working concrete. Please guide me on this.
You should be tracking moments in your database in a column of type TIMESTAMP in MySQL version 8. This type is akin to the SQL standard type TIMESTAMP WITH TIME ZONE.
Using JDBC 4.2 or later, exchange java.time objects with your database.
ZoneId z = ZoneId.of( "America/Denver" ) ;
LocalDate ld = LocalDate.of( 2020 , Month.JANUARY , 23 ) ;
LocalTime lt = LocalTime.of( 2 , 0 ) ;
ZonedDateTime zdt = ZonedDateTime.of( ld , lt , z ) ;
The ZonedDateTime class automatically adjusts for anomalies such as Daylight Saving Time (DST). If 2 AM does not exist on a certain date on a certain date with a “Spring ahead” DST cutover, the time-of-day is adjusted to 3 AM.
Oddly, JDBC 4.2 requires support for OffsetDateTime but not the more commonly used Instant and ZonedDateTime. So convert.
myPreparedStatement.setObject( … , zdt.toOffsetDateTime() ) ;
Retrieval.
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
ZonedDateTime zdt = odt.withZoneSameInstant( z ) ;
Notice that using this approach means we don’t care about the current default time zone on The server or the client, nor do we care about the current default time zone in your session.

Map timezone in query from postgresql to jpql

I would like to map my postgresql query to jpql query in java. And I can't map "created AT time zone 'UTC' AT time zone 'Europe/Paris'" .
My postgresql query:
SELECT count(*), to_char(created AT time zone 'UTC' AT time zone 'Europe/Paris','DD-MM-YYYY')
from my_table GROUP BY to_char(created AT time zone 'UTC' AT time zone 'Europe/Paris','DD-MM-YYYY');
My jpql query:
Query query = em.createQuery("select count(z), to_char(z.created, 'DD-MM-YYYY'), z.formType from MyTableEntity z where z.created >= :startFrom and z.created <= :endTo GROUP BY to_char(z.created, 'DD-MM-YYYY'), z.formType ");
How can I group by my created field in timestamp 'Europe/Paris'? In database created field is saved in timestamp. AT time zone in jpql doesn't work.
You shouldn't use to_char to convert dates to a string at all. Let JPA handle the conversion. Also PostgreSQL can store a timestamp, but it does not store a time zone. It only stores the offset of the timezone at the given moment.
What you can do to get these results by date is casting to date, rather than using formatting. Here's how it would look as a native query:
SELECT count(*), cast(created AS date), form_type from my_table
GROUP BY cast(created AS date), form_type;
And in JPQL:
SELECT count(z), cast(z.created as date), z.formType from MyTableEntity z
where z.created between :startFrom and :endTo
GROUP BY cast(z.created as date), z.formType
The a between x and y statement is equivalent to a >= x and a <= y.
And if your database runs on a server who's default timezone is Europe/Paris, you don't need that timezone conversion. Alternatively you can run a native query to set the timezone for your current connection https://www.postgresql.org/docs/9.1/sql-set.html and then run your other queries.
SET SESSION TIME ZONE 'Europe/Paris'

How to disable conversion to UTC when storing OffsetDateTime in PostgreSQL

Given this table:
CREATE TABLE a(
t TIMESTAMP WITH TIME ZONE
);
And this simple JDBC code snippet:
DriverManager.getConnection(
"jdbc:postgresql://localhost:5432/dbname", "user", "password"
).use { connection ->
val nowSomeTimeZone = OffsetDateTime.now(ZoneOffset.of("+4"))
connection.prepareStatement("insert into a(t) values (?)").use { insertStmt ->
insertStmt.setObject(1, nowSomeTimeZone)
insertStmt.executeUpdate()
}
connection.createStatement().use { stmt ->
stmt.executeQuery("select * from a").use { resultSet ->
resultSet.next()
val t = resultSet.getObject(1, OffsetDateTime::class.java)
println("$nowSomeTimeZone -> $t")
}
}
}
Somewhere inside the JDBC stack an automatic conversion from +04:00 to UTC must be happening, because this is the println output:
2018-08-30T10:35:33.594+04:00 -> 2018-08-30T06:35:33.594Z
What's even more weird, when I look into the table using the psql console client, it shows me the timestamp in yet another time zone (which is my local time zone):
$ psql -h localhost -U username
dbname=> select * from a;
t
----------------------------
2018-08-30 08:35:33.594+02
Why does this conversion happen, and how can I disable it?
Disabling the conversion is not possible, because the PostgreSQL server strips the time zone information and stores timestamps always in UTC, even when you are explicitly using the type TIMESTAMP WITH TIME ZONE.
Quoting the PostgreSQL documentation:
For timestamp with time zone, the internally stored value is always in UTC (Universal Coordinated Time, traditionally known as Greenwich Mean Time, GMT). An input value that has an explicit time zone specified is converted to UTC using the appropriate offset for that time zone. If no time zone is stated in the input string, then it is assumed to be in the time zone indicated by the system's TimeZone parameter, and is converted to UTC using the offset for the timezone zone.
Moreover, the documentation states:
We do not recommend using the type time with time zone (though it is supported by PostgreSQL for legacy applications and for compliance with the SQL standard). PostgreSQL assumes your local time zone for any type containing only date or time.
The weird behaviour described in the question is because
The JDBC driver always returns the timestamp in UTC
The psql console client converts the timestamp to the user's local time zone before displaying it, in this case German Time (+02:00)
Thanks to #RobbyCornelissen for the insights.

Categories