2

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

jlr
  • 928
  • 6
  • 16
  • 1
    What you are looking for is the *global duration type* [MachineTime](http://time4j.net/javadoc-en/net/time4j/range/MachineTime.html) which can be combined with class `Moment` (needs the range-module). The type `net.time4j.Duration` only works with units on the local timeline (CalendarUnit and ClockUnit). I can give more details as answer in the evening or tomorrow. – Meno Hochschild Jul 11 '17 at 12:03
  • Thanks for the quick reply, really appreciate it! Yes I'd like more details. Have you considered the new documentation feature of stackoverflow? (https://stackoverflow.com/documentation) We could help building on top of your insights. =) – jlr Jul 11 '17 at 14:31
  • Meanwhile, the SO-documentation site has been shutdown. But sure, the project Time4J could get much more documentation - also because there are a lot of features and details. However, this is also a question of where to place the documentation (until now: the incomplete [tutorial](http://time4j.net/tutorial/overview.html) and the javadoc) and working capabilities. – Meno Hochschild Dec 18 '17 at 09:04

1 Answers1

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.

  • Every calendar or temporal type with a timeline has its own unit type which also characterizes the timeline of the temporal type in a distinct way so even the compiler will tell you not to mix up different unit types of different entities like Moment (counterpart to java.time.Instant) or PlainTimestamp (counterpart to LocalDateTime).
  • This distinction is coded by design as generic type parameter U in the super class TimePoint. For concrete classes: Moment works primarily with java.util.concurrent.TimeUnit while PlainTimestamp works with any implementation of IsoUnit, especially with the enums CalendarUnit and ClockUnit.
  • The generalized duration concept is described by the interface TimeSpan which is also specified by the unit type U to be used. You can only add a timespan to a moment if the unit type is 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.
  • Special temporal entities have their own sets of suitable units. For example, the unit for eras does only exist in the Japanese calendar but not for other calendars which have either zero, one or only two eras.

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.

Meno Hochschild
  • 38,305
  • 7
  • 88
  • 115
  • Thanks a lot for the answer. And sorry for the long wait for accepted answer, I only came back to the hobby project where I use time4j, which I am growing to like. – jlr Dec 17 '17 at 22:50
  • @jlr Appreciate your feedback. By the way, I am also thinking about moving `MachineTime` to the main package so it can be discovered in an easier way (for a future [major release](https://github.com/MenoData/Time4J/issues/609)). But I am also open to alternatives or other improvements. – Meno Hochschild Dec 18 '17 at 09:00