As I am converting code using the java.time implementation to time4j, I want to add a Duration
to a Moment
but I get a compilation error. Under java.time, I would do the following:
val startTime: ZonedDateTime = ...
val duration: TemporalAmount = ...
val endTime: ZonedDateTime = startTime.plus(duration)
Using time4j though, the same does not work:
val startTime: Moment = ...
val duration: Duration[ClockUnit] = ...
val endTime: Moment = startTime.plus(duration)
The compiler complains about the generics interaction between the Duration
and the Moment
. No matter which way I create a Duration
(at least that I found), it would need to have an associated generic of java.util.concurrent.TimeUnit
, because Moment
implements TimePoint[java.util.concurrent.TimeUnit]
and the Moment#plus
method hence needs a Duration[java.util.concurrent.TimeUnit]
with the same associated generic as Moment
for the time unit.
This surprises me that java.util.concurrent.TimeUnit
is used as this is not a time4j type. Am I missing the finer details around this choice? I have the impression that this was decided by design.
One way that works is if I use a PlainTimestamp
and adds a Duration[ClockUnit | CalendarUnit | IsoUnit]
, as the former type implements TimePoint[IsoUnit, PlainTime]
and overloads extra supported units. Then I can convert the PlainTimestamp
to whatever timezone I need afterward. Is this the intended design?
(And still:) What's the proper Duration
type to use with Moment.plus
method?
I am using time4j version 4.27.2
Short answer how to migrate zonedDateTime.plus(Duration.ofHours(24))
:
Moment.from(zonedDateTime.toInstant()).plus(MachineTime.of(24, TimeUnit.HOURS));
See also the API of the global duration type MachineTime.
Long answer in detail:
In contrast to the java.time
-API which only knows only one single class ChronoUnit for representing the most used temporal units applicable on arbitrary temporal entities (and the general interface TemporalUnit
), the unit and duration design of the library Time4J is much more fine-grained.
Moment
(counterpart to java.time.Instant
) or PlainTimestamp
(counterpart to LocalDateTime
). Moment
works primarily with java.util.concurrent.TimeUnit
while PlainTimestamp
works with any implementation of IsoUnit
, especially with the enums CalendarUnit
and ClockUnit
.java.util.concurrent.TimeUnit
. This is offered by the special implementation class MachineTime
. Furthermore, the other duration implementation, called net.time4j.Duration
is only compatible with local types like PlainTimestamp
.Why different unit types for Moment
and PlainTimestamp
?
Partially this is already answered by the last item about suitable sets of units. Example, months and years do not make much sense for machine-like types as Moment
. Therefore the chosen enum java.util.concurrent.TimeUnit
covers what is needed as units for Moment
.
In addition, the different unit types of Time4J help to differentiate. A net.time4j.Duration<ClockUnit>
is calculated in a local context while a MachineTime<TimeUnit>
is calculated as global duration. This is not only true for clock-related units like hours but also for calendrical units. A year is not simply a year. We have ISO-calendar years (corresponding to gregorian years). We have ISO-week-based-years (length of 364 or 371 days). We have islamic years (354 or 355 days) and so on. Therefore Time4J knows a lot of different calendar units (watch out the API of the calendar-module). So Time4J finally adopted a design to prevent comparisons of durations with different unit types (which would be like comparing apples and oranges).
Following example for the rare case of changing the international dateline in Samoa (2011-12-30 was left out) demonstrates how important the distinction of unit types might be:
In Time4J, we just use different unit types to express if the arithmetic happens on the local or on the global timeline. Conclusion: In Java-8, we have to carefully study the context, in Time4J the unit types give valuable extra information.
ZonedDateTime zdt = ZonedDateTime.of(2011, 12, 29, 0, 0, 0, 0, "Pacific/Apia");
Moment m1 = Moment.from(zdt.toInstant());
Moment m2 = m1.plus(MachineTime.of(24, TimeUnit.HOURS));
assertThat(m2.isSimultaneous(m1.plus(MachineTime.of(1, TimeUnit.DAYS))), is(true));
System.out.println(m2); // 2011-12-30T10:00:00Z
System.out.println(m2.toZonalTimestamp(PACIFIC.APIA)); // 2011-12-31T00
System.out.println(m1.toZonalTimestamp(PACIFIC.APIA).plus(2, CalendarUnit.DAYS)); // 2011-12-31T00
The objects TimeUnit.DAYS
and CalendarUnit.DAYS
are obviously not the same. They require even different amounts (1 versus 2) to create the same results.
Side note:
I have now shortened the first version of my answer - mainly leaving out the Java-8-related stuff because I think it is easily possible to write too much about the topic of unit/duration-design in the narrow context of your question (and I have not even given any kind of complete answer). A tutorial page or extra documentation page like here on SO would indeed be a better place.
But at least two other points not mentioned in my answer might be interesting for you, too (concerns net.time4j.Duration
): Sign handling and specialized timezone-metric. Last one can even be an alternative for MachineTime
in some cases.