Wednesday, August 10, 2005

more headaches with localizing dates

Per the previous post we have decided upon normalizing dates into standard UTC/GMT times (at midnight of the day they occurred). Using the code generation tools that we have it was simply a matter of adding the necessary logic in the setters and getters of those fields on the domain model objects. So, if a date field is a GMT date we add in:

this._dateValue = new java.sql.Timestamp(setDateToUtcHourFromTimeZone((java.util.Date)dateValue).getTime());

Where setDateToUtcHourFromTimeZone is going to find the TimeZone in the session, modify the date value to midnight at that TimeZone and then translate the midnight date value to real UTC/GMT time.

Likewise fo the getter:

return new java.sql.Timestamp(getTimeZoneDate((java.util.Date)_dateValue).getTime());

Where getTimeZoneDate will translate the Date (which is GMT) back into the correct TimeZone date.

So, what's the problem with this? The problem is Hibernate calls the getters and setters. It's just fine and dandy when you set the value via the object. But, once that DMO gets into Hibernate's clutches it calls the getter on it which effectively negates the intended GMT normalization! And again, when Hibernate creates the object and calls the setter the GMT date retrieved from the database is then retranslated into a different value rather than the desired the translated local TimeZone date ).

What's the solution? If you're using Oracle , evaluate the Timestamp with timezone datatype. Same thing with PostgreSql. If not, what now? Use a varchar and muck with the values through the data access code? That's nasty and then you lose all major benefits of the RDBS datatype operations. Varchar operations could be more CPU intensive compared to numerical datatypes, especially as record sizes ramp. If I find out anything more I'll update.


So it's not going to be too difficult to fix this issue. The changes will be as follows:
1) generated DMO code will not hose with the getter, it will return the value as-is.

2) generated DMO code will have additional method for obtaining the local date value given a TimeZone.

3) generated DMO code will still use the above function but its signature will change to use a LocalizedTimestamp object.

4) LocalizedTimestamp object to be created. Has a Timestamp value and a TimeZone value. Default TimeZone is GMT.

Implementation details

The setter calls setDateToUtcHourFromTimeZone and passes in the LocalizedTimestamp. Internal logic evaluates that this is either a GMT Timestamp or not. If it is, translate, if not, keep original value. This way the code is smart enough to not hose the value if it is already GMT.

As for the getter, we'll toss on a new method (getLocalDateValue) that will require a TimeZone and will translate the value for the caller.

Thus, we satisfy the setter/getter issue for Hibernate operations and still provide necessary functionality for the additional callers.

No comments: