The central role of a universal temporal data type in other libraries
First design goal of other date-time-libraries was always to achieve some kind of simplicity by unifying all features in one class
although the whole subject of date and time is very complex. Any libraries (also in other programming languages) introduced a general
all-purpose data type for date and time, even including timezones and/or offsets. The same with Java: The first such data type was
java.util.GregorianCalendar
. It even contains a reference to a Locale
to lookup localized week-model-informations.
What was/is the alternative to this data type? Not really. The only alternative java.util.Date
is just a global data type
like Moment
in Time4J. Especially local/plain data types were missing. The result of this situation which lasted for years
until 2014 is that most Java developers are trained to only work with a universal temporal type and not with local/plain types.
Introducing the JSR-310 alias the package java.time
and its sub-packages, the situation changes a little bit. Now we find
classes like java.time.LocalDate
. But still there is a general data type named java.time.ZonedDateTime
. It is the
de-facto successor of the old class java.util.GregorianCalendar
. It also tries to be all-in-one. The new class
ZonedDateTime
enables serialization, implements the interface Comparable
and defines a temporal arithmetic using common time units like months, days or hours and seconds.
Why is net.time4j.ZonalDateTime
limited?
The Time4J-equivalent net.time4j.ZonalDateTime
is quite different in design. It is not a basic temporal type due to following
constraints:
- Not serializable: Implementing the interface
Serializable
is not difficult but it would not be very useful because the result would be heavy-weight. Any instance ofnet.time4j.ZonalDateTime
carries the full timezone data so serialization would necessarily be slow. For comparison: The JSR-310-classZonedDateTime
only stores a pointer to the timezone data so the serialization speed is somehow reasonable and improves the speed, but: It is extremely complex to understand. Consider for example if the timezone data in the receiver VM are different or have changed in the mean-time by political decisions. It would be a pretty tough task to document the serialization behaviour. And surprisingly: There is NO official documentation in JSR-310 what will really happen in detail if you try to deserialize an instance ofZonedDateTime
(there are only some remarks in the mailing list of JSR-310 - hard to find). Regarding the two implementation strategies of serialization users only get the choice between a complex undocumented behaviour or slow serialization speed. Time4J has therefore dropped the direct serialization and forces you to think twice before you manually serialize an instance ofZonalDateTime
via a specialized method. - Not comparable: It is not completely clear how to implement this feature because there is not just the reference
to the UTC-axis only. Users might also wish to compare first by local timestamp representations and then by UTC. So there is some source
of confusion in the class
ZonedDateTime
if you don't look carefully at the documentation. Therefore Time4J does not know a natural order innet.time4j.ZonalDateTime
. Instead two different comparing methods are offered: One for the preference of the local timeline, and one for the UTC-axis. Note also that the comparison behaviour of the old classjava.util.GregorianCalendar
is even inconsistent withequals()
. - No temporal arithmetic or manipulations: This looks first like an oversight but is intentional. Consider how
you define the temporal distance in hours around a daylight saving change from winter to summer time. Should the gap (usually one hour)
be taken into account or not? The JSR-310 has decided to say: Yes for time units and no for calendrical units. But Time4J does not want
to force users to think so. Sometimes users have good reasons to hide the winter-summer-gap - dependent on their use-case. Otherwise
consider the case of dateline-border-switch of Samoa in year 2011. The JSR-310 has decided to hide it when counting days, see here:
ChronoUnit.DAYS.between(ZonedDateTime.parse("2011-12-28T00:00-10:00[Pacific/Apia]"), ZonedDateTime.parse("2011-12-31T00:00+14:00[Pacific/Apia]").
This code counts 3 days although we know that one day (2011-12-30) was completely left out. Regarding these non-trivial implications Time4J has decided to offer another strategy as replacement for the feature of temporal arithmetic. Even manipulations were dropped because they often refer to the underlying time axis (and there is not a unique time axis innet.time4j.ZonalDateTime
). Keep also in mind that repeated addition of any count of days toZonedDateTime
in a loop can lead to hidden bugs because daylight-savings can cause a shift on local times not just once (during the winter-summer-transition) but for ever (even when long time after last winter-summer-transition). - No timezone-related manipulations: Methods like
withZoneSameInstant()
don't exist. They are simply not necessary because Time4J offers alternative even more expressive and shorter solutions (details see below).
This list is missing an important item. net.time4j.ZonalDateTime
is not storable but the alternatives like
java.time.ZonedDateTime
or java.util.GregorianCalendar
are not storable, too! All standard relational databases
have NO such similar data type. Even the SQL-type TIMESTAMP_WITH_TIMEZONE is only offset-related and does not refer to general timezones including
data about winter-to-summer-change. If a relational database had such a type then it would not even be a good feature because it blows up the complexity
of transferring a universal temporal type from the user to the database or reverse. Consider for example the frightening scenario if the database
had a different timezone version than the Java application server or the client machine.
Conclusion: The non-storable-property of such all-in-one temporal types finally makes clear that such a type is far less useful than most users traditionally assume. The design decision of Time4J is somehow brave to break old traditions but is also consequent.
What is the equivalent replacement in Time4J for the missing features?
Solutions usually involve type conversions using ZonalDateTime.toMoment()
or ZonalDateTime.toTimestamp()
. Users don't need
to be afraid of any performance issues in these cases because those methods just query already constructed finished data. It is safe and quick.
Serialization
You can serialize/deserialize a ZonalDateTime
via two specialized write()
/read()
-methods (since v3.1):
ObjectOutputStream oos = ...; ZonalDateTime zdt = ...; zdt.write(oos); ObjectInputStream ois = ...; ZonalDateTime zdt = ZonalDateTime.read(ois);
But before you consider serialization ask yourself if you really want to serialize the whole timezone data. You might also consider and have the
freedom just to serialize the associated moment and the timezone id (if you can cope with the complexity of possibly different timezone versions
in that scenario). In the deserialization phase, you can then use moment.inZonalView(tzid)
.
Comparison on global or local time axis
ZonalDateTime zdt1 = ...; ZonalDateTime zdt2 = ...; int utcComparison = zdt1.toMoment().compareTo(zdt2.toMoment()); // on physical UTC-axis int localComparison = zdt1.toTimestamp().compareTo(zdt2.toTimestamp()); // on local time-axis
Temporal arithmetic (example)
ChronoFormatter<Moment> f = ChronoFormatter.ofMomentPattern("uuuu-MM-dd'T'HH:mm'['VV']'", PatternType.CLDR, Locale.ROOT, ZonalOffset.UTC); ZonalDateTime zdt1 = ZonalDateTime.parse("2011-12-28T00:00[Pacific/Apia]", f); ZonalDateTime zdt2 = ZonalDateTime.parse("2011-12-31T00:00[Pacific/Apia]", f); long utcDays = zdt1.toMoment().until(zdt2.toMoment(), TimeUnit.DAYS); // 2 long localDays = CalendarUnit.DAYS.between(zdt1.toTimestamp(), zdt2.toTimestamp()); // 3
This expressive example clearly demonstrates that Time4J gives the users more freedom in defining the temporal arithmetic either on the global UTC-axis or on the local timestamp axis. And the different unit types in use stress that point.
Timezone-related manipulations (example)
// Java-8-code LocalDateTime germany = LocalDateTime.of(2015, 6, 17, 14, 45); LocalDateTime india = germany.atZone(ZoneId.of("Europe/Berlin")).withZoneSameInstant(ZoneId.of("Asia/Kolkata")).toLocalDateTime(); // Time4J-replacement PlainTimestamp germany = PlainTimestamp.of(2015, 6, 17, 14, 45); PlainTimestamp india = germany.inTimezone("Europe/Berlin").toZonalTimestamp("Asia/Kolkata");
Is net.time4j.ZonalDateTime
necessary at all?
If only Time4J itself is considered the anwer will be NO in most cases. There is only one exceptional case if you want to parse a complete
date-time-zone-representation. In that case it is more convenient to just use the parse()
-method of ZonalDateTime
in order to determine which timezone or offset had been parsed (although the parsed timezone/offset can be achieved by evaluating the parse log).
Within the context of Time4J, the four basic types PlainDate
, PlainTime
, PlainTimestamp
and Moment
are usually sufficient.
However, Time4J cannot ignore the fact that outside there are libraries which heavily use similar data types
(like java.time.ZonedDateTime
in JSR-310). Herein is the real justification of the class ZonalDateTime
.
It serves as a type conversion bridge, see also the appropriate constant in the class TemporalType
.