Is there a way to set the default DateFormat class used for parsing strings into dates?
My background: I get exceptions reading date values from JDBC because the date string is not in the expected format.
(text added on 2011-07-22):
Seems I need to precise my question description: I use a foreign, proprietary database together with a proprietary JDBC driver. There is no possibility to know or even change the column type on database side. When I try to read the ResultSet columns via ResultSet.getDate() or ResultSet.getObject() some exception is triggered inside the JDBC driver like "10 Jul 1999 is not a valid date". What I want to achieve is to avoid this internal exception by setting some appropriate global default date format. Maybe I would need to implement some custom Locale first and the install that Locale globally?
There should be totally no need for this.
Dates should be stored in DB as a DATE, DATETIME or TIMESTAMP field, depending on the DB used and the information you'd like to store (e.g. date only or date and time combined), not as a VARCHAR or something. Such a date-specific field stores the value under the covers basically as an integer/long with the epoch time as value.
Assuming that you're using a date+time field type such as DATETIME or TIMESTAMP, then you should be saving it in the DB using PreparedStatement#setTimestamp(). Here's an example, assuming that the date variable is of a java.util.Date type:
preparedStatement.setTimestamp(1, new Timestamp(date.getTime()));
And you should be retrieving them from the DB using ResultSet#getTimestamp() which returns a Timestamp which in turn is a subclass of java.util.Date, so you could just safely upcast it:
Date date = resultSet.getTimestamp("columnname");
As to parsing/formatting the java.util.Date object from/into a human readable String format, this should technically happen in the view side, not in the persistence layer. How exactly to do this in turn depends on the view/UI technology/framework used, such as Swing, JSP, JSF, Struts2, Spring-MVC, etcetera. As it's not clear from your question which one you're using, it's not possible to give a suitable answer. In general, they all use SimpleDateFormat API under the covers. You could even use it in raw form.
You can set your default Locale:
Locale.setDefault(Locale.US);
Alternatively, you can use one of the DateFormat static methods which accepts a Locale to just get the format for the Locale you're interested in. If your date format doesn't match one of the standard ones, you'll need to create your own SimpleDateFormat (based on a pattern) and make sure you always use that one instead of the default one.
Related
I am using hibernate + spring and want to store/load timestamps in UTC. I've read that I should add a property, so I added this to my application.properties
spring.jpa.properties[hibernate.jdbc.time_zone]=UTC
This worked for one part of the problem - now dates are saved in utc in the database. But when I retrieve timestamps, they are transformed into default timezone. How can I fix this without setting default time zone to UTC?
The property of the entity has type LocalDateTime.
I ran the code, and noticed that the proper result set method is used during get(the one that accepts calendar) with instance that has zone info storing UTC. But after setting calendar's values to the one retrieved from the database, the calendar is transformed into Timestamp with this code
Timestamp ts = new Timestamp(c.getTimeInMillis());
In debug mode, I see that ts stores cdate field with value of timestamp in default time zone(not UTC).
First of all, if we are talking about Hibernate 5 (5.2.3 - 5.6.x if to be precise) the purpose of hibernate.jdbc.time_zone setting is not to give the ability for application developer to implement some kind of sophisticated date/time logic, but to synchronize persistence provider with underlying database, that is clearly stated in the corresponding CR:
Currently my database has implicit date times in UTC. No zoned data is appended to the end of the string (e.g. "2013-10-14 04:00:00").
When Hibernate reads this as a ZonedDateTime, it incorrectly reads it in as EST, as that is the TimeZone of the JVM.
It would be nice to be able to specify the TimeZone of a field by an annotation perhaps.
basically: you definitely need to set up hibernate.jdbc.time_zone if (mine: and only if) SQL statement like SELECT SYSDATE FROM DUAL (SELECT LOCALTIMESTAMP for PostgreSQL, etc) returns something, what you do not expect, in that case Hibernate will start adjusting non-timezone-aware JDBC data to something more or less reliable for application - that is exactly what you are observing (when I retrieve timestamps, they are transformed into default timezone)
At second, any speculations around JSR-310 and JDBC 4.2 (like for timezone-aware java types you need to define DB columns as timestamp with time zone), are not correct in case of Hibernate 5, that is mentioned in the corresponding CR as well:
The whole idea of "stored TZ" really depends on how the database/driver treats TIMESTAMP and whether it supports a "TIMESTAMP_WITH_TIMEZONE" type. I personally think it is a huge mistake to save the specific TZ differences to the DB, so I would personally continue to not support TIMESTAMP_WITH_TIMEZONE types. This would mean we never have to bind the Calendar because we could simply convert the value to to the JVM/JDBC TZ ourselves. Specifically I would suggest that we (continue to) assume that the driver has been set up such that the same TZ is used when ...
And indeed, if you try to find usage of java.sql.Types#TIMESTAMP_WITH_TIMEZONE in Hibernate 5 sources you will find nothing, just because that time Hibernate developers didn't get a common opinion about how timezone conversions should work in cases of different Java versions, Java types, DB engines and JDBC drivers (they are developing the most popular (mine: the only one) JPA implementation, that is definitely not the same as develop microservice), however, there are a lot of related changes in Hibernate 6 (check TimeZoneStorageType for example). In Hibernate 5 all timezone conversion logic passes through TimestampTypeDescriptor:
#Override
protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException {
return options.getJdbcTimeZone() != null ?
javaTypeDescriptor.wrap( rs.getTimestamp( name, Calendar.getInstance( options.getJdbcTimeZone() ) ), options ) :
javaTypeDescriptor.wrap( rs.getTimestamp( name ), options );
}
and as you can see, Hibernate 5 just gives a hint to JDBC driver, how the last should process #getTimestamp call:
Retrieves the value of a JDBC TIMESTAMP parameter as a java.sql.Timestamp object, using the given Calendar object to construct the Timestamp object. With a Calendar object, the driver can calculate the timestamp taking into account a custom timezone and locale. If no Calendar object is specified, the driver uses the default timezone and locale.
in regard to your case:
you either need to use timezone-aware java types (ZonedDateTime/OffsetDateTime, or even Instant) or code your own Hibernate type, which will handle timezone conversions - that is not so hard as it might seem.
we can also set it up` per-session basis:
session = HibernateUtil.getSessionFactory().withOptions()
.jdbcTimeZone(TimeZone.getTimeZone("UTC"))
.openSession();
My database timezone was in UTC and in my application timezone I solved this problem by having both the Entity and the table have a date in UTC so that there will need to be no conversion between them. Then I did the conversions between timestamps in code in the getters and setters. Then I think you do it manually.
Setter and getter for that field:
public void setCreatedDate(LocalDateTime createdAt)
{
this.createdAt = createdAt.atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime();
}
public LocalDateTime getCreatedDate()
{
return createdAt.atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime();
}
As alluded to be Andrey in his answer, in Hibernate 6 the way to normalize dates/times to UTC is to use the java.time types OffsetDateTime or ZonedDateTime, and either:
annotate the field #TimeZoneStorage(NORMALIZE_UTC), or
set the property hibernate.timezone.default_storage=NORMALIZE_UTC.
I'm not very certain what you mean about using LocalDateTime here. What would it even mean to normalize a local datetime to UTC? That statement just doesn't really make sense: you can't move a local datetime to a new time zone because it doesn't have an associated time zone to begin with.
I think what you mean is that your "local" date times are actually zoned datetimes in the current JVM time zone. But if that's the case, it's very easy to use localDateTime.atZone(ZoneId.systemDefault()) to represent that situation correctly.
My application requires the following.
In my application(in struts), we need to support Persian Calendar.
When we submit a form, date is coming as String in Action class. We need to save this date in Persian format in DB. We have configured DB for Persian Calendar. And while retrieving data from DB user should be able to see the date in Persian format only.
Also, the user can switch in 2 languages(English, Persian). So, application should support both type of calendars(Gregorian and Persian). If user logged-in in English, Gregorian calendar should be visible. If user logged-in in Persian language, then Persian Calendar should be visible.
For date conversion from Gregorian to Persian I am using below:
http://www.dailyfreecode.com/forum/converter-gregorian-date-jalali-date-20288.aspx
In above requirement, I am facing 2 issues:
While submitting a form, how can we save date(which is in String format in Action class) in Persian format in DB?
While retrieving data from DB, it should come in Persian format. As of now, the JDBC client is retrieving the date in Gregorian Calender.
I am passing java.sql.Date(today's date) which is getting saved in persian format in DB. Using below code.
java.sql.Date sqlDate = null;
java.util.Date utilDate = new Date();
sqlDate = new java.sql.Date(utilDate.getTime());
PreparedStatement psmtInsert = conn.prepareStatement(insertQuery);
psmtInsert.setDate(1, sqlDate));
psmtInsert.executeUpdate();
For retrieving:
PreparedStatement psmtSelect = conn.prepareStatement("select dateOfJoining from EMPLOYEE");
ResultSet resultSet = psmtSelect.executeQuery();
while (resultSet.next()) {
System.out.println(resultSet.getDate(1));
}
But it is returning date in Gregorian type.
Do we have any setting in Tomcat/JVM/JDBC client which converts date returned from DB into Persian Format itself(Like we have NLS_CALENDAR ,NLS_DATE_FORMAT in Oracle)?
For 1st issue, if I am passing date in Persian format then In DB it is saving incorrectly. PFB my code:
java.sql.Date sqlDate = null;
java.util.Date utilDate = new Date("1397/02/04");
sqlDate = new java.sql.Date(utilDate.getTime());
PreparedStatement psmtInsert = conn.prepareStatement(insertQuery);
psmtInsert.setDate(1, sqlDate));
psmtInsert.executeUpdate();
Above is inserting as 0777/09/13 in DB.
How can we overcome the above issues?
Thanks in advance.
There is no calendar type in java.sql.Date so it is impossible to force it to Persian. All date/time values in JDBC are normalized in a neutral form and Oracle Database uses a neutral form as well for storage and processing. When NLS_CALENDAR is PERSIAN, Oracle converts the value to and from the neutral form and it is just "presenting" it in the Persian calendar convention while it is handling in the neutral form internally.
Usually it is optimal to use a neutral form consistently for all values in the backend. Typically a converter is used in the UI layer as part of the localization to tailor to the preferred locale for individual users. If localization in Java in the middle tier is needed, the java.time.chrono package that Douglas is suggesting above would be a clean solution.
While submitting a form, how can we save date(which is in String format in Action class) in Persian format in DB?
Store the date as a DATE data type and when you want to insert it into the table use TO_DATE with the NLS calendar parameter for Persian to convert it from a Persion formatted string to a date:
SQL Fiddle
Query 1:
SELECT TO_DATE(
'1397/02/05',
'yyyy/mm/dd',
'nls_calendar=persian'
)
FROM DUAL
Results:
| TO_DATE('1397/02/05','YYYY/MM/DD','NLS_CALENDAR=PERSIAN') |
|-----------------------------------------------------------|
| 2018-04-25T00:00:00Z |
While retrieving data from DB, it should come in Persian format. As of now, the JDBC client is retrieving the date in Gregorian Calender.
When you output the value, just specify the calendar you want to use to format it. So for Persian, use TO_CHAR with the NLS calendar parameter for Persian:
Query 2:
SELECT TO_CHAR(
DATE '2018-04-25',
'yyyy/mm/dd',
'nls_calendar=persian'
)
FROM DUAL
Results:
| TO_CHAR(DATE'2018-04-25','YYYY/MM/DD','NLS_CALENDAR=PERSIAN') |
|---------------------------------------------------------------|
| 1397/02/05 |
Do we have any way using which JDBC client retrieve date in Persian format.
This is a common misconception. A DATE data type stored in Oracle tables as 7 bytes containing year (2 bytes), month, day, hours, minutes and seconds (1 byte each). It does not have any "format" (but it is effectively stored in the Gregorian calendar).
JDBC does not transfer a formatted date, it just transfers those 7 bytes and stores it in a java.sql.Date class (which also just stores those bytes).
If you want to format a DATE data type then you need to convert it to another data type; typically a string for which you want to use TO_CHAR in the Oracle database (or some other method to format Dates in Java).
Just to provide the inputs for those who still are looking how to implement the Persian calendar in the application.
My application should support Gregorian and Persian calendar, also Oracle and PostgreSQL DBs should be supported.
To implement the Persian calendar with minimum efforts performed the below steps:
Converting every date coming from UI into Gregorian format in validate() of form using the implementation suggested in below link
http://www.dailyfreecode.com/forum/converter-gregorian-date-jalali-date-20288.aspx
Once the dates are converted to Gregorian the whole application will keep running as it was running earlier with Gregorian dates.
While saving the date in DB, I decided to store the date in Gregorian format only as I need to support PostgreSQL DB as well and it was not supporting the Persian Calendar.
While fetching the data to UI, the date will be retrieved from DB and again I am converting the date in Persian format.
To show the Persian calendar on UI, refer the below link. It has other calendars implementation as well
http://keith-wood.name/calendars.html
The above approach can help implementing most of the different calendars.
java.sql.Date is defined to be in UTC which is Gregorian. Getting a DATE from the database as a java.sql.Date is not likely to do anything useful. The right answer would be to define a PersianChronology which extends java.time.chrono.AbstractChronology and PersianLocalDate which implements java.time.chrono.ChronoLocalDate. Then get the value from the database as a PersianLocalDate. That's a lot of work but it is in theory the right thing to do.
PersianLocalDate per = rs.getObject(col, PersianLocalDate.class);
I think this could be made to work. Not easy but possible. If your PersianLocalDate class defines the following method
public static PersianLocalDate of(oracle.sql.DATE date) { ... }
then Oracle Database JDBC would use that method to construct a PersianLocalDate in the getObject call above. The fun part would be implementating the of(DATE) method. All of the oracle.sql.DATE methods assume a Gregorian calendar. Your best bet is to call DATE.getBytes and interpret the bytes yourself. Bytes 0 and 1 are the year, 2 is the month and 3 is the day of the month. TIMESTAMP.getJavaYear will convert those two bytes into an int year. It doesn't know anything about calendars; it's just doing arithmetic. Depending on what the database does in sending a Persian DATE as a query result that should let you construct a PersianLocalDate. If the database is converting to Gregorian, you'll have to convert back to Persian. Your PersianChronology should help with that.
Going the other way, sending a PersianLocalDate to the database is going to be more interesting. The Oracle Database JDBC drivers have no capability of converting an unknown class to a database type like DATE, nothing equivalent to the of method hook described above. You could try making PersianLocalDate extend oracle.sql.ORAData. The toDatum method would have to return an oracle.sql.DATE with the same bytes that the database sent as a query result.
A simpler approach, maybe, would be to send Persian dates back and forth to the database as VARCHARs/Strings. The drivers would call a static of(String) method on PersianLocalDate so getting a PersianLocalDate would be easy. If the database does the right thing with PersianLocalDate.toString results then calling setString makes sending the values easy. If not then define PersianLocalDate.toDatabaseString and do the conversion yourself.
This is a use case that we talked about when we implemented support for java.time but we simply did not have the resources required to do anything. And we didn't know how common it would be so it was hard to justify doing the work. If you have a support contract I'd encourage you to file an SR and ask for an enhancement request. If you can provide a PersianChronology and PersianLocalDate it would be easier for me to get some resources allocated to do something. Maybe nothing more than a hook to make setObject work, but at least that. I wish I could be more help.
In my Java app I must save data to Oracle 11g with object created date and time and for this I convert java.util.Date() to java.sql.Date() in format as new java.sql.Date(new java.util.Date().getTime()). But I noticed, that after the data inserted, oracle truncate the time part of date and I get something like 16/09/2015. but I need format like this: 16/09/2015 9:55:44. the second format created by oracle's sysdate() procedure. How I can get the second format from java code?
From memory (it's been a while), java.sql.Date is used to hold dates (no time) only, if you want time information as well, you need to use java.sql.TimeStamp instead.
Do not use java.sql.Date if you want to store date and time. Just use java.util.Date without any conversion. Simple and easy. There is no need for java.sql.TimeStamp either.
And make sure your NLS settings (e.g. in SQL Developer) are such that they display both date and time as Oracle does not distinguish between dates with and without time.
I guess Oracle does not truncate. For example, if you use Oracle SQL Developer, you have to update NLS parameter "Date Format" to see a time.
By default it just equal "DD-MON-RR"
I am loading data into TPC-H tables for Oracle using the sqlldr load functionality. For example, the orders.ctl file has the following.
load data
INFILE 'orders.tbl'
INTO TABLE ORDERS
FIELDS TERMINATED BY '|'
(O_ORDERKEY, O_CUSTKEY, O_ORDERSTATUS, O_TOTALPRICE, O_ORDERDATE DATE
'YYYY-MM-DD', O_ORDERPRIORITY, O_CLERK, O_SHIPPRIORITY, O_COMMENT)
After loading the data into Orders table, I find that the DATE format is not in 'YYYY-MM-DD' but in the format 'DD-MON-YY'. Why is Oracle not using the format I had mentioned?
Any suggestions?
Thanks!
EDIT : Adding java tag as it involves converting the date value passed as string to a java method to convert it back to Date format. See comment to #Justin Cave answer.
A date column in Oracle does not have a format. A string representing a date has a format. A date is always stored in a binary format that is not particularly human readable. Note that an Oracle date also always contains a time component even if your client doesn't display it.
My guess is that you are saying that when you open up SQL*Plus on a machine where you have done a default US-English install of the Oracle client, connect to the database, and do a
SELECT o_orderdate
FROM orders
that the date that is displayed is in the format DD-MON-RR. This is because the client needs to convert the date to a string representation in order to display it. If you rely on implicit data type conversion, the client will use your session's NLS_DATE_FORMAT to do the conversion. The default for an English language install in the United States is to have an NLS_DATE_FORMAT of "DD-MON-RR". Each session has its own NLS_DATE_FORMAT because each user potentially wants to display dates in a different format.
If you want to display the string representation of the date in a particular format, you'd want to use explicit data type conversions using the to_char function
SELECT to_char( o_orderdate, 'YYYY-MM-DD' )
FROM orders
Our java class calls PLSQL proc which returns date in default format which is defined by NLS_DATE_FORMAT. Our application sets its own Locale for internationalization but I want the date format to remain just 'DD-MON-RR' which is en_US Locale NLS_DATE_FORMAT. Due to the change in locale oracle's fetched Date string differs and subsequent TO_DATE() function calls are failing. I tried fixing this by changing Locale to Locale.setDefault(new Locale("en","US")); //"en_US" in java class and it works fine, but the internationalization part wont work anymore. I am in Singapore so my Locale is "en_SG" and the date format that oracle assumes after setting NLS_TERRITORY: SINGAPORE is NLS_DATE_FORMAT:'DD/MM/RR'. I queries server for V$NLS_PARAMETERS and there the default date format is 'DD-MON-RR'. So my question is, can i set the NLS_DATE_FORMAT without affecting Locale settings of application. Or can I make jdbc driver to ignore NLS settings of client altogether?
Yo can use to_char function to format the date as you want, also you can include the third parameter in to_char function to denote locale used:
select to_char(sysdate, 'DY-MM-YYYY', 'NLS_DATE_LANGUAGE=AMERICAN') from dual;
More info about date formats can be found in SQL Reference.
I am kind of finalizing on the i18n stuff for my application and my design is based on below assumptions)
1) Locale.setDefault(new Locale("en","US")); is bad as you are changing the default locale for the JVM and not just for your app. So it is better to pass the locale around the app on a per request basis (may be threadlocal)
2) Handle all i18n formatting/parsing at the application level. DB should only handle comparisions (optionally), sorting (optionally) and storage. So just set the NLS_CHARACTERSET, NLS_COMP, NLS_SORT and NLS_LENGTH_SEMANTICS. By the way comparision and sorting at the db level means creating locale specific indexes thus slowing down the inserts/updates