New cassandra bound statement getDate method - java

In a somewhat legacy project we were using the version 2 of the cassandra driver in Spring application.
This version, and in particular the class BoundStatement exposed a method getDate which returns a java Date. We all know that the old java date api was pretty horrible, but when used with caution it did the job.
Now, because of some necessity we decided to upgrade the cassandra driver to version 3.4. The first thing that was noted was that in this version, the same method getDate now returns a date of type LocalDate which a class that datastax team implemented to repleace the java's one. The interesting thing about this class is noted in documentation:
A date with no time components, no time zone, in the ISO 8601
calendar. Note that ISO 8601 has a number of differences with the
default gregorian calendar used in Java: it uses a proleptic gregorian
calendar, meaning that it's gregorian indefinitely back in the past
(there is no gregorian change); there is a year 0. This class
implements these differences, so that year/month/day fields match
exactly the ones in CQL string literals.
So basically, this class truncates the time information. This change caused some failures in the unit tests that were based on date comparison and it required some test modification. To me it seems strange actually, but I guess there must be a good reason for such choice by the datastax team. I would be happy to hear the opinion of someone who knows more in regard.

getDate from driver 2 was moved to getTimestamp in driver 3.0, as explained in the upgrade guide:
Getters and setters have been added to “data-container” classes for new CQL types:
getByte/setByte for the TINYINT type
getShort/setShort for the SMALLINT type
getTime/setTime for the TIME type
getDate/setDate for the DATE type
The methods for the TIMESTAMP CQL type have been renamed to getTimestamp and setTimestamp.
This affects Row, BoundStatement, TupleValue and UDTValue.
The main justification for this was the addition of a date type in Cassandra 3.0. To prevent future confusion, we moved the existing getDate to getTimestamp so the get methods match their cql type name.

Related

Is it possible to choose which overridden method to use in Java?

I have a java.sql.Date instance and I want to use the java.util.Date.toInstant() method instead of java.sql.Date.toInstant(), for example, is it possible?
java.time and JDBC 4.2
I recommend that you stick to java.time, the modern Java date and time API to which Instant belongs and forget about the two Date classes mentioned in the question. They are both poorly designed and both long outdated. Since JDBC 4.2 we can retrieve java.time types from a ResultSet.
I am assuming that your SQL query is returning SQL datatype date.
PreparedStatement stmt = yourDatabaseConnection
.prepareStatement("select your_date_column from your_table;");
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
LocalDate date = rs.getObject("your_date_column", LocalDate.class);
// do something with date
}
You want to convert the date to an Instant? That conversion doesn’t readily make sense. An SQL date usually is a calendar date defined by year, month and day of month, without time zone. An Instant is a point in time, a completely different beast. If you can decide on a time of day and a time zone to use, a conversion is possible. One option is:
Instant inst = date.atStartOfDay(ZoneId.systemDefault()).toInstant();
If you cannot avoid getting a java.sql.Date
Having a java.sql.Date, again we need a time of day and a time zone if we want to convert to Instant. There is a reason why java.sql.Date.toInstant() unconditionally throws UnsupportedOperationException (which I guess caused you to ask the question). I would convert the Date to a LocalDate and then proceed as before:
java.sql.Date oldfashionedSqlDate = getFromSomewhere();
Instant inst = oldfashionedSqlDate.toLocalDate()
.atStartOfDay(ZoneId.systemDefault())
.toInstant();
A shorter but more low-level and cryptic alternative is:
Instant inst = Instant.ofEpochMilli(oldfashionedSqlDate.getTime());
The two ways may not always give the same result, which should be your first guidance for choosing. In case the Date contrary to the specification holds a time of day other than the start of day, the latter method will give you that time, whereas the former will give you the start of the day as the code says. BTW the latter is what java.util.Date.toInstant() does.
In general is it possible to circumvent the method implementation in the subclass and call the superclass method directly?
Is it possible to choose which overridden method to use in Java?
No, that is not possible in the language. Java doesn’t offer any syntax for such a trick. Holger’s comment under this answer seems to suggest that it is possible through reflection under some circumstances such as you being able to open the java.base module. See the last link at the bottom for more details.
Links
Related question: Insert & fetch java.time.LocalDate objects to/from an SQL database such as H2
Oracle tutorial: Date Time explaining how to use java.time.
Related question: I want to print hi GrandFather;but it seems to print hi father
To answer your question as asked:
It is not possible for your code that uses a java.sql.Date to call an method in java.util.Date which java.sql.Date overrides.
That would break data abstraction, and the Java language doesn't allow it. You can't even do it reflectively.
(As other answers and comments indicate, there are alternative approaches that avoid this problem. Some more practical than others1.)
1 - The idea of creating a custom subtype of java.sql.Date is not practical. You don't want to be modifying JDBC drivers to try to solve this.

Is Hibernate Using java.sql.Date Wrong?

I am reaching out to the community here because either I found an issue with Hibernate or I just don't understand how to use java.sql.Date and java.time.LocalDate.
I'm running into a problem where my DB is in UTC time zone while my client is in EST. I have a field in the DB called ETA of type DATE that for example is set to 2019-09-10 on a record. When I read the date in EST it becomes 2019-09-09. The DATE field according to documentation has no timezone information.
When Hibernate reads the value, it uses the DateTypeDescriptor.java class. However, the problem with that class is that it will first try to read the value as a java.sql.Date (in the rs.getDate( name ) part) and then call java.sql.Date.toLocalDate() in the javaTypeDescriptor.wrap() part, which is implemented poorly because what it does is it strips off the timezone offset information and just plainly returns a date. This makes 2019-09-09T20:00:00.000-0400 (which is 2019-09-10 in the database) to become 2019-09-09 without the timezone offset part.
What I consider the problem is the part where Hibernate calls rs.getDate() because that must return java.sql.Date. Now, the MySQL driver contains a method public LocalDate getLocalDate(int columnIndex) to obtain a LocalDate, so I don't understand why Hibernate isn't using that method.
I found that someone has already brought up this same problem with them but they don't seem to consider it an issue.
Therefore, I'm reaching out here to understand - is there a bug with Hibernate or am I just not understanding correctly how to convert between DATE (DB) and LocalDate (Java) types.
PS: I use latest Hibernate 5.4.5 and latest JPA 2.2.
Try Hibernate 5 and the configuration option hibernate.jdbc.time_zone. That's an official way to force it to use JDBC APIs which use an Calendar instance in the UTC timezone.
That said, I've found that there are a lot of problems in this area. JDBC drivers and people using databases often get this subtly wrong. That's why we eventually gave up and just put the milliseconds (in UTC) into the database. So the column type is always NUMBER(16) for all three temporal types.
That allows us to write special converters which return predictable results, independent of JDBC drivers, database/VM/OS time zones.
Programmers can then concentrate on trying to understand why timezones don't work they way they think they should :-)

Java Calendar/Date Minvalue Supported on DB/Rest

I'm using Spring Boot, and HSQLDB file,
When i use:
calendar.setTimeInMillis(-9223372036854775808L);
calendar.setTime(new Date(Long.MIN_VALUE));
and and store the model,
after i call rest client, i get invalid date, when i check it, it returns some positive value instead of negative one :| what should i use as min_Value.
i thought it may be SQL issue, i changed temporary variable type from timespan to Date, but didn't worked again, i no longer sure what is the case of this issue, and what nuber i should use, every one talk about min value, some one talked about around -8.......L too that work in JS, but it didn't worked here too :|
Avoid ancient date-time values
Many reasons to not use that minimum number as a date value. As commented, standard SQL does not permit such ancient dates. No database implementation I know of supports that value. And using a date-time for historical values is fraught with problems and issues, and ill-advised.
Use epoch as a flag value
If you are looking for an arbitrary value to use as a flag such as "no value intended" while avoiding the use of nulls, then I suggest using the Java and Unix epoch of first moment of 1970 in UTC. If you know your system will never store any date-time that far back as a valid value, this will work well. And 1970-01-01T00:00:00Z is easily recognized by many programmers, DBAs, and SysAdmins as the common epoch and therefore likely to be a special value.
java.time
Avoid using the Date and Calendar classes. These troublesome classes and their siblings are now legacy, supplanted by the java.time classes.
These classes include a constant for that epoch value: Instant.EPOCH
Similar Question: Minimum date in Java

Changed behaviour of java.sql.Date after OJBC client upgrade

After an upgrade of the OJDBC client from version 11.2.0 to 12.1.0, I experience a different behavior in binding a java.sql.Date object to a PreparedStatement.
In the prepared statement, a host variable "f.plan_date = ?" should be binded with the value of a java.util.Date object, being an input obtained elsewhere in the code. The column data type in the Oracle table is "DATE" and only the date part should be taken into account - time is irrelevant.
I translated the java.util.Date object in a java.sql.Date object in the following way:
statementRegisterJobs.setDate(3, new java.sql.Date(planDate.getTime()));
This worked fine with the 11.2.0 client. However, things tend to go wrong after the upgrade towards 12.1.0. No records are retrieved anymore. After hours of debugging, I found out that the issue was related to the date variable. The following way of working gives me my records back:
statementRegisterJobs.setDate(3, java.sql.Date.valueOf("2014-08-21"));
Could someone clarify this behaviour? The java.util.Date object can eventually have a time component, and I have the undefined feeling that this could be related to the problem somehow. On the other hand, the following items should argue that a time component is neglected in a java.sql.Date, no matter how the object was constructed...
In the Java 6 API for java.sql.Date, I found the following statement: "This method is deprecated and should not be used because SQL Date values do not have a time component." (method 'getHours()'). So this would mean that the time aspect is neglected when converting the java.util.Date into a java.sql.Date.
This is confirmed by the information in the constructor documentation: "Constructs a Date object using the given milliseconds time value. If the given milliseconds value contains time information, the driver will set the time components to the time in the default time zone (the time zone of the Java virtual machine running the application) that corresponds to zero GMT."
Moreover, I'm not able to get a possible time aspect out of the java.sql.Date object: toString() gives me only the date, getHours() throws an exception.
And how can this be related to an update in a JDBC client?
Any thoughts are appreciated :) Thank you very much in advance.
Opposed to what the Java API states, when creating a java.sql.Date object by passing a milliseconds time value the time aspect seems to be stored in the object and is not defaulted to zero when using the 12.1.0 OJDBC driver.
This is the test I set up:
java.util.Date utilDate = new Date();
java.sql.Date sqlDate1 = new java.sql.Date(utilDate.getTime());
java.sql.Date sqlDate2 = java.sql.Date.valueOf("2014-08-27");
I prepared the following statement (SELECT to_char(?, 'YYYY-MM-DD HH24:MI:SS') testDate FROM dual), binded both sqlDate1 and sqlDate2 and got the following results.
With driver version 11.2.0
sqlDate1: 2014-08-27 00:00:00
sqlDate2: 2014-08-27 00:00:00
With driver version 12.1.0
sqlDate1: 2014-08-27 14:47:29
sqlDate2: 2014-08-27 00:00:00
This is not in line with the documentation in the API:
If the given milliseconds value contains time information, the driver
will set the time components to the time in the default time zone (the
time zone of the Java virtual machine running the application) that
corresponds to zero GMT.
However, knowing this I can fix the issue by forcing the time information of the sql date object to be midnight.

Oracle / JDBC: retrieving TIMESTAMP WITH TIME ZONE value in ISO 8601 format

A lot have been said (and written on SO) on parts of the subject, but not in a comprehensive, complete way, so we can have one "ultimate, covering-it-all" solution for everyone to use.
I have an Oracle DB where I store date+time+timezone of global events, so original TZ must be preserved, and delivered to the client side upon request. Ideally, it could work nicely by using standard ISO 8601 "T" format which can be nicely stored in Oracle using "TIMESTAMP WITH TIME ZONE" column type ("TSTZ").
Something like '2013-01-02T03:04:05.060708+09:00'
All I need to do is to retrieve the above value from DB and send it to client without any manipulations.
The problem is that Java lacks support of ISO 8601 (or any other date+time+nano+tz data type) and the situation is even worse, because Oracle JDBC driver (ojdbc6.jar) has even less support of TSTZ (as opposed to Oracle DB itself where it's well supported).
Specifically, here's what I shouldn't or cannot do:
Any mapping from TSTZ to java Date, Time, Timestamp (e.g. via JDBC getTimestamp() calls) won't work because I lose TZ.
Oracle JDBC driver doesn't provide any method to map TSTZ to java Calendar object (this could be a solution, but it isn't there)
JDBC getString() could work, but Oracle JDBC driver returns string in format '2013-01-02 03:04:05.060708 +9:00', which is not compliant with ISO 8601 (no "T", no trailing 0 in TZ, etc.). Moreover, this format is hard-coded (!) inside Oracle JDBC driver implementation, which also ignores JVM locale settings and Oracle session formatting settings (i.e. it ignores NLS_TIMESTAMP_TZ_FORMAT session variable).
JDBC getObject(), or getTIMESTAMPTZ(), both return Oracle's TIMESTAMPTZ object, which is practically useless, because it doesn't have any conversion to Calendar (only Date, Time and Timestamp), so again, we lose TZ information.
So, here are the options I'm left with:
Use JDBC getString(), and string-manipulate it to fix and make ISO 8601 compliant. This is easy to do, but there's a danger to die if Oracle changes internal hard-coded getString() formatting. Also, by looking at the getString() source code, seems like using getString() would also result in some performance penalty.
Use Oracle DB "toString" conversion: "SELECT TO_CHAR(tstz...) EVENT_TIME ...". This works fine, but has 2 major disadvatages:
Each SELECT now has to include TO_CHAR call which is a headache to remember and write
Each SELECT now has to add EVENT_TIME column "alias" (needed e.g. to serialize the result to Json automatically)
.
Use Oracle's TIMESTAMPTZ java class and extract relevant value manually from its internal (documented) byte array structure (i.e. implement my own toString() method which Oracle forgot to implement there). This is risky if Oracle changes internal structure (unlikely) and demands relatively complicated function to implement and maintain.
I hope there's 4th, great option, but from looking all over the web and SO - I can't see any.
Ideas? Opinions?
UPDATE
A lot of ideas have been given below, but it looks like there is no proper way to do it. Personally, I think using method #1 is the shortest and the most readable way (and maintains decent performance, without losing sub-milliseconds or SQL time-based query capabilities).
This is what I eventually decided to use:
String iso = rs.getString(col).replaceFirst(" ", "T");
Thanks for good answers everyone,
B.
JDBC getObject(), or getTIMESTAMPTZ(), both return Oracle's TIMESTAMPTZ object, which is practically useless, because it doesn't have any conversion to Calendar (only Date, Time and Timestamp), so again, we lose TZ information.
That would be my recommendation as the only reliable way to get the information you seek.
If you are on Java SE 8 and have ojdbc8 then you can use getObject(int, OffsetDateTime.class). Be aware that when you use getObject(int, ZonedDateTime.class) you may be affected by bug 25792016.
Use Oracle's TIMESTAMPTZ java class and extract relevant value manually from its internal (documented) byte array structure (i.e. implement my own toString() method which Oracle forgot to implement there). This is risky if Oracle changes internal structure (unlikely) and demands relatively complicated function to implement and maintain.
This is what we ultimately went with until bug free JSR-310 support is available in the Oracle JDBC driver. We determined this was the only reliable way to get the information we want.
A slight improvement to #2:
CREATE OR REPLACE PACKAGE FORMAT AS
FUNCTION TZ(T TIMESTAMP WITH TIME ZONE) RETURN VARCHAR2;
END;
/
CREATE OR REPLACE PACKAGE BODY FORMAT AS
FUNCTION TZ(T TIMESTAMP WITH TIME ZONE) RETURN VARCHAR2
AS
BEGIN
RETURN TO_CHAR(T,'YYYYMMDD"T"HH24:MI:SS.FFTZHTZM');
END;
END;
/
The in SQL this becomes:
SELECT FORMAT.TZ(tstz) EVENT_TIME ...
It's more readable.
If you ever need to change it, it's 1 place.
The downside is it is an extra function call.
you need two values: time utc in millis since 1970 and timezone offset fom utc.
So store them as a pair and forward them as a pair.
class DateWithTimeZone {
long timestampUtcMillis;
// offset in seconds
int tzOffsetUtcSec;
}
A Date is a pair of numbers. It is not a String. So a machine interface should not contain a date represented by a iso string, although that is handy to debug.
If even java cannot parse that iso date, how do you think that your clients can do?
If you design an interface to your clients, think how they can parse that. And in advance write a code that shows that.
This is untested, but seems like it ought to be a workable approach. I'm not sure about parsing the TZ name out, but just treating the two parts of the TZTZ object as separate inputs to Calendar seems like the was to go.
I'm not sure whether longValue() will return the value in local or GMT/UCT. If it's not GMT, you should be able to load a calendar as UTC and ask it for a Calendar converted to local TZ.
public Calendar toCalendar(oracle.sql.TIMESTAMPTZ myOracleTime) throws SQLException {
byte[] bytes = myOracleTime.getBytes();
String tzId = "GMT" + ArrayUtils.subarray(bytes, ArrayUtils.lastIndexOf(bytes, (byte) ' '), bytes.length);
TimeZone tz = TimeZone.getTimeZone(tzId);
Calendar cal = Calendar.getInstance(tz);
cal.setTimeInMillis(myOracleTime.longValue());
return cal;
}
Do you really care about sub-millisecond precision? If not converting from a UTC millisecond + timezone-offset to your required string is a one-liner using joda-time:
int offsetMillis = rs.getInt(1);
Date date = rs.getTimestamp(2);
String iso8601String =
ISODateTimeFormat
.dateTime()
.withZone(DateTimeZone.forOffsetMillis(offsetMillis))
.print(date.getTime());
Prints, for example (current time in +9:00):
2013-07-18T13:05:36.551+09:00
Regarding the database: Two columns, one for the offset, one for the date. The date column could be an actual date type (thus making many, timezone-independent anyway, db date functions available). For time-zone dependent queries (such as the mentioned global hourly histogram) perhaps a view could expose columns: local_hour_of_day, local_minute_of_hour, etc.
This is likely how one would have to do it if no TSTZ datatype was available--which, considering Oralce's poor support, is the nearly the case for practical purposes. Who wants to use an Oracle specific features anyway! :-)
Since it looks like there's no magical way of doing this right, the simplest and the shortest method would be #1. Specifically, this is all the code needed:
// convert Oracle's hard-coded: '2013-01-02 03:04:05.060708 +9:00'
// to properly formatted ISO 8601: '2013-01-02T03:04:05.060708 +9:00'
String iso = rs.getString(col).replaceFirst(" ", "T");
it seems that just adding 'T' is enough, although a perfectionist would probably put more cosmetics (regex can optimized, of course), e.g.: rs.getString(col).replaceFirst(" ", "T").replaceAll(" ", "").replaceFirst("\+([0-9])\:", "+0$1:");
B.
The solution with oracle is SELECT SYSTIMESTAMP FROM DUAL

Categories

Resources