Java: ResultSet getString() differs between environments - java

I have a SQL query that is returning an oracle Date object.
e.g.:
SELECT sysdate FROM DUAL
There is code currently that does the following:
String s = rs.getString("sysdate");
The problem is, this returns different date format on different environments (database is the same).
One environment will return:
2011-01-31 12:59:59.0
The other will return something weirder:
2011-1-31 12.15.32.0 (the time is separated by decimals)
Maybe this has something do with Locale... one machine is "English (Canada)" as reported by java, the other is "English (United States)".
What I'm interested in is, when the resultset transforms the date object into a string, where is that format coming from?

From Oracle's site:
At database connection time, the JDBC
Class Library sets the server
NLS_LANGUAGE and NLS_TERRITORY
parameters to correspond to the locale
of the Java VM that runs the JDBC
driver
So yes, the difference in the response is because the machines have a different locale specified. The correct solution should be to use getDate() or getTimestamp() or have the database server return the date as a string in a specific format as mentioned earlier.

I suggest not calling getString at all. The value you've asked for isn't a string, it's a date or timestamp. So call getDate or getTimestamp instead. (I don't know which of them is most appropriate offhand; it depends on the exact semantics of sysdate.)
If you then need to format it, you can do so in an appropriate manner.
Fundamentally, the fewer text conversions you introduce between your code and the database, the better. That's one reason to use parameterized queries - you don't have to care about how the database will parse numeric values or dates and times; you just provide the value. This is the same sort of thing, but in reverse.

In the 11g drivers the format seems to be hard-coded. Calling getString() on the result set ultimately calls this:
oracle.sql.TIMESTAMPTZ.toString(int year, int month, int day, int hours, int minutes, int seconds, int nanos, String regionName)
For dates, oracle.jdbc.driver.DateAccessor.getString() calls this with nanos=-1 and the result uses the format "yyyy-mm-dd HH:MM:SS"
For Timestamps, the format is "yyyy-mm-dd HH:MM:SS.S". Up to 9 digits of nanoseconds will be included.
Actually another interesting result is that this has changed significantly from 10g to 11g:
10.2.0.5.0:
select a DATE value and use getString to read it
2009-04-20 00:00:00.0
select a TIMESTAMP value and use getString to read it
2010-10-15.10.16. 16. 709928000
11.2.0.2.0:
select a DATE value and use getString to read it
2009-04-20 00:00:00
select a TIMESTAMP value and use getString to read it
2010-10-15 10:16:16.709928
Could your environments be using different versions of the Oracle JDBC drivers?

If you are really wanting it as a String you are better off using to_char in the query.
SELECT to_char(sysdate, 'MM/DD/YYYY') FROM DUAL;
This will be consistent.

Don't use getString, use getDate (or getTimestamp). That way it will won't rely on the database engine to convert it to a string.

Related

Convert oracle.sql.timestamptz to postgresql timestamp with timezone

Sorry, but i`m noob and i need your advices.
I have some result set from oracle with timestamptz, i get string value of this timestamptz which like
2014-1-10 13.47.56.0 7:0
and then I need put it in postgresql-request in some function which takes timestamp with timezone. How can I convert this this string to timestamp with timezone in java? I tried do something look like
TO_TIMESTAMP_TZ("2014-1-10 13.47.32.0 7:0","YYYY-MM-DD HH24:MI:SS.FF TZH:TZM") but it didn`t work for me. Help me, please.
I don't know of a function TO_TIMESTAMP_TZ() in Postgres. You probably mean to_timestamp().
I also don't know of a template patterns TZH:TZM. You could use the AT TIME ZONE construct.
And data is quoted with single quotes; double quotes are for identifiers.
This works for me:
SELECT to_timestamp('2014-1-10 13.47.32.0', 'YYYY-MM-DD HH24:MI:SS.MS')::timestamp
AT TIME ZONE '-7:0'; -- shift back
It would be smarter to output timestamps in ISO 8601 format with TZR (time zone region) in Oracle, which is less ambiguous. Details in the Oracle manual.
Or better yet, UTC timestamps (without 0 offset), which you can cast to timestamptz directly in Postgres:
SELECT '2014-01-10 06:47:32+0'::timestamptz;
Or UNIX epochs, which can be fed to the second form of to_timestamp().
SELECT to_timestamp(1389336452);
Details about timestamps in Postgres:
Ignoring time zones altogether in Rails and PostgreSQL

ORA-01843: not a valid month

I want to insert record in oracle11g database table. I am calling a stored procedure from java.
While executing stored procedure I am getting following error.
ORA-01843: not a valid month.
I am converting my date from String to Date in java as follows
date=new SimpleDateFormat("MM/dd/yyyy").parse(visitDate);
In my stored procedure I have following insert query
create or replace PROCEDURE CREATE_VISIT(p_visit_date varchar2)
insert into visit(VISIT_ID,visit_date) values
(frc.SEQ_VISIT.nextval,nvl(to_date(p_visit_date, 'mm/dd/yyyy hh:mi am'),
sysdate));
END CREATE_VISIT;
You have a String in Java. You convert that to a Date in Java. So far, presumably, so good.
Your procedure, however, accepts a varchar2. So at some point the Date that you constructed is being explicitly or implicitly cast to a varchar2. You don't say where that is happening (in Java or in PL/SQL) or whether it is being done explicitly or implicitly. If the conversion is being done implicitly in PL/SQL, then that would use your session's NLS_DATE_FORMAT which may be different for different sessions in the same database. In a default US-based install of the database and of the client, that would be DD-MON-RR.
Within your procedure, you then call to_date on the string that was passed in using an explicit format mask. If the string that is passed in is not in the format mm/dd/yyyy hh:mi am you'll either get an error or, worse, you'll get an incorrect result (this is the old question of whether the string 01/02/03 represents January 2, 2003 or February 1, 2003 or February 3, 1901).
If we guess that the Date in Java is being implicitly cast to a varchar2 using your session's default NLS_DATE_FORMAT of DD-MON-YYYY, then we can see why your to_date conversion would fail. If you have a Date of May 30, 2014, for example, that would be implicitly cast to the string "30-MAY-14". If you then try to cast that back to a date using to_date and a format mask of mm/dd/yyyy hh:mi am, Oracle would try to convert the 30 from your string to a month and raise an error since there is no 30th month.
Realistically, you should be using the proper data type throughout and you should be avoiding implicit casts like the plague. If you declare your procedure to take a date parameter, then you can pass a Date from your Java code and omit the to_date call in your procedure. The Date will be passed from Java to PL/SQL without needing to be converted to a varchar2 and then converted back to a date and you won't have to worry about making sure that all your format masks align throughout your code base.
create or replace PROCEDURE CREATE_VISIT(p_visit_date date)
AS
BEGIN
insert into visit(VISIT_ID,visit_date)
values (frc.SEQ_VISIT.nextval,nvl(p_visit_date, sysdate);
END CREATE_VISIT;

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

Insert string(containing timezone) into datetimeoffset column in SQL server 2008 through Java program

I have to insert data(which is also containing the timezone, i.e. 2013-01-19 00:00:00 +0530) which is in String form and the datatype of the column is DATETIMEOFFSET. I have tried both java.util.date and sql.date but could not find any solution.
If you're using the Microsoft JDBC driver, you can use the DateTimeOffset class, constructing instances with the valueOf method.
You'll need to parse the value out into local time and offset (in order to pass the two parts separately) but that shouldn't be too bad using SimpleDateFormat. (The Z format specifier in SimpleDateTimeFormat will handle offsets like +0530.) Alternatively, use Joda Time which will make life easier still, as it will allow you to parse to a DateTime which lets you get the offset as well as the local time in one go. I would personally use Joda Time and create a method to convert from a DateTime to a DateTimeOffset.

How do I know the offset of a time zone from GMT by using its id in joda?

I have a MySQL database in which one column is of type DATETIME and stores the values in IST (as my server time zone is IST).
I am getting a two string in EST from the user for which I need to match the date part in my database with that column.
I am using this query :
SELECT * FROM my_table where date(convert_tz(`myDate`,'+05:30','-05:00'))
between '2012-12-01' and '2012-12-02';
Note: That in my database mysql.time_zone is empty meaning I cannot use strings likeGMTetc in my query, they instructed me to download a package, but I don't want to download it, its fine for me to use00:05` as I will be framing my query in Java.
This query runs fine as I have hardcoded the the time zone offset (for EST.
But I am worried that this query will give wrong data for the dates that fall in Daylight timings i.e. for EDT.
So how do I get the time zone difference (meaning it should return '-05:00' for EST and '-04:00 for EDT ) so that I can directly use them when building my query string in java.
My query string may look like :
SELECT * FROM my_table where date(convert_tz(`myDate`,'+05:30','???'))
between '2012-12-01' and '2012-12-02';
where I want the value at ??? to be dynamically allocated using a prepared statement.
I am using Joda Time API, but don't have much knowledge of it, whether it has something that can return me the timezone offser for a given timezone string.
I would suggest that:
You start storing your values as UTC, for the sake of sanity. (The data shouldn't depend on the server's location.)
You compute the UTC value for the start/end points within Java (using a DateTime set in the right time zone, and then converting to UTC)
You pass the UTC start/end directly into the query via SQL parameters (don't include the values in the SQL text) and avoid the database doing any conversion
If you really, really can't change the database to use UTC instead, you should convert your "target timezone" values into IST values instead, and pass those to the database. Again, the query doesn't need to do conversion: convert your inputs beforehand instead.

Categories

Resources