-
Notifications
You must be signed in to change notification settings - Fork 80
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Multiple longstanding bugs in java.sql.{Date,Time,Timestamp} conversions #200
Comments
Fix a regression in 1.5.1 that was not caught in pre-release testing, and could leave conversions between PostgreSQL date and java.sql.Date off by one day in certain timezones and times of the year (#199). Other issues in date/time conversion have also been uncovered that are of longer standing, not recent regressions. They are detailed in #200 and are not fixed in this PR, but can be addressed in another, later release.
The second issue, out-of-range values passed to Consider the following, "corrected" behavior (that is, without the deliberate "bug" added in 25fd6df), in an
The value has been converted for Java and roundtripped back "correctly", but the By contrast, with the 'fix' added in 25fd6df, the milliseconds value used to construct the To be sure, the 'bug' solved by that 'fix' crops up in very narrow circumstances: when the SQL value is a In the general case, the consistency that feels lacking there can't be expected at all, anyway. Consider the case where the original value does not just have a zone offset defaulted from the current session (as by
The weird behaviors are intrinsic to the ill-conceived need for shoehorning the data into this misbegotten Java type at all. Have I mentioned one should be using (Even on that point, to be honest, absolute strictness is hard to defend: Java itself, for example via the deprecated hours/minutes/seconds constructor, can install a milliseconds value outside of There is no problem with moving ahead and 'fixing' (that is, removing the 25fd6df fix) the But in the At the same time, the existing behavior can have its own perplexing cases, such as a time that can't be represented if the current day happens to be a transition to summer time and the correct time value falls in the gap. That's just wrong; there should be no way for a complication like that to arise in the mere representation of a time with no date. That could be avoided by adding or subtracting one or more whole days during conversion, if the in-the-gap case is detected, to find a day that has no gap and the desired zone offset. But that would add even more complexity to a problem whose real answer is: hey, PL/Java already supports |
The thing that needs to happen when a PostgreSQL value with no time zone is mapped to one of the java.sql.{Date,Time,Timestamp} classes (which all implicitly have a time zone) could be described as 'masquerading' the zoneless PostgreSQL value as a UTC value that will be rendered by Java as similarly as possible to the original. The 'unmasking' operation needed in the reverse direction is similar but simpler; both can be combined in one method. The code formerly doing that appeared in a few places without very clearly conveying what was going on, and was using the wrong time zone to do it (the PostgreSQL session time zone when it needs to be the Java runtime's default time zone, see issue #200, though of course the two are often the same). Replace that code with a single new method utcMasquerade with more explicit comments explaining the need for, constraints on, and limitations of the 'masquerade'.
Almost! But does the "out-of-range values passed to java.sql.Time constructor" turn out to be NOTABUG? Consider passing current_time to Java, with the PG session and Java default time zones the same, but the current status of summer time being different from that of 1970-Jan-1. The right mSecs value gets set and things are good, but the java.sql.Time object won't report the same hour as PG. What a mess. No regression test added yet, owing to the fiddliness of changing default timezones ... can be revisited.
The bug/notabug debate in issue #200 and the commit notes for 4a0bb84 is hereby resolved by executive decision: bug. The old approach technically violated the contract of java.sql.Time, and differed from pgJDBC's behavior in the same case. A back-compatibility mode would not be infeasible if requested, but would be a bit of work to do well (the former behavior could avoidably fail on days with time zone rule transitions), too much work to invest in advance of a request for it, on a deprecated mapping where clients really ought to migrate to java.time.
There's another complication affecting
(0) has the benefit of a strong argument from principle: the argument being supplied is in UTC, and carries with it no information to identify any other time zone, so (0) is the only conversion that doesn't involve blatantly making something up. But, (1) has the benefit of being what pgJDBC does, and what the Java API docs specify (at least for the (2) is what PL/Java has been doing. Changing that to (1) should be an invisible change in cases where the PG session time zone and the Java default time zone are the same. And once again, application code would be best advised to use the |
Analysis for issue #199 reveals that there are several other issues involving the pre-(JSR 310, JDBC 4.2) date, time, timestamp conversions to and from the
java.sql.{Date,Time,Timestamp}
classes. Unlike issue #199, these are not regressions in 1.5.1, but older historical behavior; they will be left unchanged for 1.5.2 and fixed in some later release.Unsurprisingly, they all have to do with the timezone manipulations that the driver has to do, even when the PG type involves no timezone, simply because the
java.sql
classes inherit fromjava.util.Date
, which assumes knowledge of a timezone.Plug: The Java 8, JDBC 4.2 / JSR 310
java.time
types are free of these issuesThese bugs in the legacy
java.sql
mappings can be fixed, and will be in a future PL/Java release, but this is a good time for another plug that PL/Java 1.5.1 supports the JDBC 4.2 mappings to Java 8's JSR 310java.time
types, which already are free of these issues, and migration to them is encouraged wherever practical.The following issues are with the legacy mappings to
java.sql.{Date,Time,Timestamp}
classes.Errors when PG and Java timezones do not match
For the PG types without timezone, the driver has to apply an offset corresponding to the local timezone. Historically, it has been using PG's local timezone. This gives correct results only when the PG
timezone
setting and the Java runtime's default timezone happen to be the same. That isn't guaranteed: the PG timezone can be set per session, possibly to match the location of a connected client, and the Java timezone typically defaults from the server OS settings (unless it is explicitly set in Java code, or with-Duser.timezone=...
inpljava.vmoptions
).A future PL/Java version might automatically set
user.timezone
to match the PG session timezone (if it is not explicitly set inpljava.vmoptions
), but the correctness of these conversions should not be left to depend on that. They are already querying PG for its timezone offset; they should simply be querying Java for its offset instead. This is explicit in the API spec forjava.sql.Date
:(emphasis mine). Although there are no similarly explicit statements in the API docs for
Time
andTimestamp
, it turns out that the principle holds. Using Java's timezone consistently for exactly the adjustments that are currently using PG's timezone will produce correct mappings whether or not the PG and Java zones happen to match.Out-of-range values passed to
java.sql.Time
constructorThe
java.sql.Time
class is meant to represent a time of day only, and per spec is initialized the same as ajava.util.Date
representing that time of day on 1 January 1970 in UTC. The PG driver will currently pass a value representing the current date. The Java class does not (at present!) detect or reject that, and itstoString
method produces the expected value (the date part is simply ignored), but any Java code that looked at the underlying milliseconds value would find it out of the expected range.Such
Time
objects round-trip back to PG successfully (but for the next issue), because PL/Java also does not (at present!) detect or reject an out-of-range value, but simplymod
s out any date part.However:
Sign problems in modular arithmetic,
java.sql.Time
to PGtime with time zone
This is reminiscent of the 'does
%
round or floor?' issue in #155 (C before C99 doesn't specify!):(the example ensures PG and Java are set to the same timezone, a simple one-hour offset west of Greenwhich. The problem can be observed in other timezones, but this makes a simple reproducible test.)
Bogus results for
timestamp without time zone
near start and end of summer timeIn this example, midnight before the start of summer time maps to Java correctly and roundtrips correctly. 01:00 fails to map or roundtrip correctly, despite an hour still remaining before anything interesting should happen. 02:00 is technically an invalid local timestamp (part of the hour that doesn't exist from 02:00 to 03:00). It could either be mapped ahead to 03:00 with or without a warning, or flagged as an error (Java 8's JSR 310 types offer those behaviors). To map it an hour earlier, as seen here, is in no interpretation correct! By 03:00, the bogosity is passed, and matching Java and roundtrip values are again produced.
At the end of summer time, midnight again is handled correctly, and correctness returns for 02:00 onward, but for the hour starting at 01:00, the Java and roundtrip values are incorrect. (There is an ambiguity at the end of summer time such that local times in the hour starting at 02:00 could represent two different actual instants, but that ends up being mostly transparent; unlike the start of summer time, this is an overlap, not a gap where some times are invalid. But the behavior here is messing up the hour preceding the overlap, which is plainly wrong.)
The choice of
Europe/Prague
as the timezone is not necessary to see the problem, but simply used to make this demonstration reproducible. The details of the errors vary if the zone is west rather than east of Greenwich, and the errors' duration varies with the magnitude of the offset: Prague is only one hour off GMT, and for other zones, far more than one or two hours can be in error on the morning of a transition.The text was updated successfully, but these errors were encountered: