[hibernate-dev] Supporting timezone in Timestamp Type

Gunnar Morling gunnar at hibernate.org
Thu Sep 1 06:13:44 EDT 2016


> Saving in "timezoned" instants is prone to several bugs: you're then
> exposed to ambiguities as time doesn't flow linearly in all zones.

I see it the other way around: the indirection you suggest is prone to
errors.

Rules about TZ constantly change (e.g. when switching to/from DST),
locations change time zones more often than one would expect. Saving the
then valid TZ "next to" an UTC timestamp is much easier and more reliable
then trying to reconstruct that info from some historical TZ DB.


2016-09-01 11:59 GMT+02:00 Sanne Grinovero <sanne at hibernate.org>:

> To be fair I would have mapped that differently.
> Your Shipment arrives at a certain *point in time* (long, UTC based)
> in a certain Port (mapped with Foreign Key).
> A Port would have a reference to a local Timezone, and UI could decide
> to use that (or a different one) for rendering, based on other
> aspects, like the location of the user actually looking at the UI, or
> even a switchable user option.
>
> Saving in "timezoned" instants is prone to several bugs: you're then
> exposed to ambiguities as time doesn't flow linearly in all zones.
>
> Storing a timezoned instant only makes sense when you're quoting a
> political/legal source; i.e. you'ave passed under OCR some legal
> paperwork and you need to store the date printed on the header in a
> dedicated field, in Java8 time type you'd never use an Instant for
> this though, there are dedicated types which make the limitations of
> this representation clear. AFAIR some kinds of comparisons are made
> impossible.
>
> I'd not spend time with improvements on Calendar: the "old Date types"
> should be deprecated.
>
>
> On 1 September 2016 at 07:46, Gunnar Morling <gunnar at hibernate.org> wrote:
> >> it is a huge mistake to save the specific TZ differences to the DB
> >
> > I once was working on a globally deployed shipping management application
> > where that was a requirement.
> >
> > Say there is a shipment from Austin (one TZ) to London (another TZ). In
> > that application an operator based in Hamburg (yet another TZ) should be
> > able to know when the shipment was brought in in Austin (the local time
> in
> > Austin at that point) and when it was delivered in Hamburg (again the
> local
> > time there at that point).
> >
> > So we stored all dates in UTC, but also the TZ (in a *different column*;
> I
> > don't quite remember whether it was just a string with the TZ (offset) or
> > again the entire date-time using TIMESTAMP_WITH_TZ). Storing it in UTC
> > allowed for comparisons whereas the other column allowed to show each
> date
> > in its local TZ. So an operator in Hamburg (at CET) could see a delivery
> > was brought in in Austin at 8:32 CST.
> >
> > Something like that would still be my preference for handling TZ-bound
> > types such as ZonedDateTime: store the UTC timestamp in one column and
> the
> > TZ info (either id or offset from UTC) in another.
> >
> > Relying on the JVM TZ or something like a single "application TZ" won't
> cut
> > it for cases like the one above: the operator in Hamburg wants to see
> dates
> > from Austin and London, using their respective time zones. Instead, the
> TZ
> > of the incoming ZonedDateTime would have to be persisted so it's
> available
> > when reading it back.
> >
> >
> >
> >
> > 2016-08-31 20:51 GMT+02:00 Steve Ebersole <steve at hibernate.org>:
> >
> >> We discussed this on HipChat, but for the benefit of all on this
> >> discussion...
> >>
> >> Part of this (the original report) speaks to a difference in how we map
> >> (org.hibernate.type.Type) java.time temporal versus a java.util
> temporal -
> >> specifically java.util.Calendar.  When we are passed a Calendar (the
> model
> >> defines its attribute as Calendar) we pass along that Calendar
> reference to
> >> the JDBC driver which forces the driver to use that Calendar's TZ.  For
> >> java.time temporal types, we always resolve them to UTC values mainly
> >> because of how we get to the java.sql.Timestamp value from the java.time
> >> value.  For example, for ZonedDateTime we call:
> >>
> >>     return (X) Timestamp.from( zonedDateTime.toInstant() );
> >>
> >> The call to #toInstant there essentially forces the value to UTC
> because it
> >> converted directly to epoch-based numeric.
> >>
> >> Thinking about this some more and "stepping back", the real problem is
> that
> >> there are a few different timezones in play here that need to be
> balanced
> >> and handled:
> >>
> >>    1. The database default TZ
> >>    2. The stored TZ
> >>    3. The JVM/JDBC TZ
> >>    4. The application TZ
> >>
> >> 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
> >>
> >>    1. when binding a Timestamp (without passing a Calendar)
> >>    2. reading a Timestamp (without passing a Calendar).
> >>
> >> Specifically this would mean setting the JVM TZ, or for JDBC drivers
> that
> >> support it setting the "default TZ" on the driver.  If we start storing
> >> values in the TZ specific to the Calendar we really have no idea how to
> >> properly read those values back because we'd lose context to that TZ.
> For
> >> that reason I suggest we drop passing along the Calender and instead
> >> perform all these conversions in memory and pass the converted
> Timestamp to
> >> JDBC.  When reading back, that should mean the driver would return us
> >> Timestamps relative to that same TZ.
> >>
> >> The last piece is "application TZ" which would be an optional setting
> >> indicating the TZ zone-based values should be returned in.
> >>
> >> E.g., let's say a user maps a ZonedDataTime using
> @Temporal(TIMESTAMP).  If
> >> following my suggestions, when saving these values we would:
> >>
> >>    1. convert the ZonedDateTime to a ZonedDateTime in the "JVM/JDBC TZ"
> (if
> >>    different from the incoming ZonedDateTime's TZ)
> >>    2. use that to construct a Timestamp to bind to JDBC
> >>
> >> When reading back these values, we would:
> >>
> >>    1. get back the Timestamp from JDBC
> >>    2. assume the value is relative to the "JVM/JDBC TZ" and build the
> >>    ZonedDateTime via `ZonedDateTime.ofInstant( timestamp.toInstant(),
> >>    jvmJdbcZoneId )`
> >>    3. convert that built ZonedDateTime to the application TZ:
> >>    `builtZonedDateTime.withZoneSameInstant( applicationZoneId )` - it
> >> might
> >>    be possible to combine this step with the previous, not sure...
> >>
> >>
> >> Some if this is type-specific.  Assuming the suggestions above:
> >>
> >>    - Instant - inherently epoch-based.
> >>       - to convert this to a Timestamp for binding we would use
> >> `Timestamp#from(Instant).
> >>       Because of the above assumptions, the driver would interpret
> >> this relative
> >>       to JVM/JDBC TZ
> >>       - Reading back we simply use `Timestamp#toInstant()`
> >>    - LocalDateTime - is the relative idea of a date+time outside the
> >>    context of any specific TZ.
> >>       - When binding we would resolve this into the JVM/JDBC TZ (using
> >>       LocalDateTime#atZone(jvmJdbcZoneId), convert to an Instant and
> >> create a
> >>       Timestamp and bind that Timestamp to JDBC.
> >>       - When reading back we'd get the Instant from Timestamp and use
> that
> >>       in `LocalDateTime#ofInstant` using the application TZ
> >>    - LocalDate - relative idea of a date outside of any specific TZ.  Of
> >>    course this could also be mapped to a JDBC DATE which circumvents
> some
> >> of
> >>    this, but mapped to a TIMESTAMP...
> >>       - When binding we'd convert to a LocalDateTime using
> >>       `LocalDate#atStartOfDay` and treat that LocalDateTime as above.
> >>       - When reading back we would create a LocalDateTime using
> >> `LocalDateTime.ofInstant(timestamp.toInstant(),
> >>       applicationZoneId )`
> >>    - LocalTime - relative idea of a time outside any specific TZ.  Could
> >>    also be mapped to a JDBC TIME...
> >>       - When binding we'd create a LocalDateTime using
> `LocalTime.atDate(
> >>       LocalDate.of( 1970, 1, 1 ) )`, converting that to a ZonedDateTime
> >> using
> >>       `LocalDateTime#atZone( jvmJdbcZoneId )` and finally converting
> that
> >> to a
> >>       Timestamp
> >>       - When reading we would call `LocalDateTime.ofInstant(
> >>       timestamp.toInstant(), applicationZoneId ).toLocalTime()`
> >>    - ZonedDateTime - like a Calendar bundles a date+time with a TZ, and
> so
> >>    is directly convertible to an epoch-based value (Instant, Timestamp,
> >> etc).
> >>       - When binding we would convert this to an Instant and then to a
> >>       Timestamp
> >>       - When reading we would convert the Timestamp to an Instant and
> then
> >>       convert the Instant to a ZonedDateTime using
> >> `ZonedDateTime.ofInstant(
> >>       timestamp.toInstant(), applicationZoneId )`
> >>    - OffsetDateTime - handled similar to ZonedDateTime.
> >>       - When binding we would convert the value to a ZonedDateTime using
> >>       `OffsetDateTime#atZoneSameInstant( jvmJdbcZoneId )` and then
> follows
> >>       ZonedDateTime
> >>       - When reading we would use `OffsetDateTime.ofInstant(
> >>       timestamp.toInstant(), applicationZoneId )`
> >>    - OffsetTime - atm we just drop the ZoneOffset
> >>    - for java.util.Date and java.sql.Timstamp mapping we have to decide
> >>    what those concepts model in regards to their value relative to a TZ.
> >> Are
> >>    they a LocalDateTime relative to the system TimeZone?  Are they a
> >>    LocalDateTime relative to UTC?  At the moment how we handled these
> >> "legacy
> >>    Java temporal types" ends up answering this question as them being "a
> >>    LocalDateTime relative to the system TimeZone".
> >>    - what about java.sql.Time?  Is this a LocalTime?  An OffsetTime
> >>    relative to UTC (+0)?
> >>    - and java.sql.Date?
> >>
> >> Lot of moving parts when you actually start looking deep at "supporting
> >> timezones".
> >>
> >>
> >> [1]
> >> http://stackoverflow.com/questions/18447995/postgresql-
> >> 9-2-jdbc-driver-uses-client-time-zone,
> >> etc
> >>
> >> On Wed, Aug 31, 2016 at 9:10 AM Vlad Mihalcea <mihalcea.vlad at gmail.com>
> >> wrote:
> >>
> >> > Hi,
> >> >
> >> > While reviewing the Pull Request for this issue:
> >> > https://github.com/hibernate/hibernate-orm/pull/1536
> >> >
> >> > I realized that we can improve the default TimestampType as follows:
> >> >
> >> > 1. We could make it parameterizable so that it can also take a custom
> >> > timezone (UTC) during mapping.
> >> > 2. We could also define a default timezone so that we don't rely on
> the
> >> JVM
> >> > one. This might be desirable when the UI requires to display the time
> in
> >> a
> >> > certain timezone, while we want to save all timestamps in UTC.
> >> >
> >> > Let me know what you think.
> >> >
> >> > Vlad
> >> > _______________________________________________
> >> > hibernate-dev mailing list
> >> > hibernate-dev at lists.jboss.org
> >> > https://lists.jboss.org/mailman/listinfo/hibernate-dev
> >> >
> >> _______________________________________________
> >> hibernate-dev mailing list
> >> hibernate-dev at lists.jboss.org
> >> https://lists.jboss.org/mailman/listinfo/hibernate-dev
> >>
> > _______________________________________________
> > hibernate-dev mailing list
> > hibernate-dev at lists.jboss.org
> > https://lists.jboss.org/mailman/listinfo/hibernate-dev
>


More information about the hibernate-dev mailing list