Design and role of the class ZonalDateTime

Table Of Contents

The central role of a universal temporal data type in other libraries
Why is net.time4j.ZonalDateTime limited?
What is the equivalent replacement in Time4J for the missing features?
Is net.time4j.ZonalDateTime necessary at all?

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:

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.


You can serialize/deserialize a ZonalDateTime via two specialized write()/read()-methods (since v3.1):

	ObjectOutputStream oos = ...;
	ZonalDateTime zdt = ...;

	ObjectInputStream ois = ...;
	ZonalDateTime zdt =;

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.