MenoData / Time4J

Advanced date, time and interval library for Java with sun/moon-astronomy and calendars like Chinese, Coptic, Ethiopian, French Republican, Hebrew, Hijri, Historic Christian, Indian National, Japanese, Julian, Korean, Minguo, Persian, Thai, Vietnamese
GNU Lesser General Public License v2.1
438 stars 64 forks source link

Duration between Moments in differents units #921

Closed mel4tonin4 closed 3 years ago

mel4tonin4 commented 3 years ago

Hello. I'm trying to compute a duration between Moments in differents units, but I can't compile my code because of type errors.

The following code


    public void f2() {
        Moment t1 = SystemClock.currentMoment();
        Moment t2 = t1.plus(4646, TimeUnit.DAYS);

        TimeMetric<IsoUnit, Duration<IsoUnit>> m =
                Duration.in(CalendarUnit.YEARS, CalendarUnit.MONTHS, CalendarUnit.DAYS, ClockUnit.HOURS, ClockUnit.MINUTES);

        m.between(t1, t2);
    }

generates

error: method between in interface TimeMetric<U,P> cannot be applied to given types;
                Duration.in(CalendarUnit.YEARS, CalendarUnit.MONTHS, CalendarUnit.DAYS, ClockUnit.HOURS, ClockUnit.MINUTES).between(t1, t2);
                                                                                                                           ^
  required: T,T
  found: Moment,Moment
  reason: inference variable T has incompatible bounds
    lower bounds: TimePoint<? super INT#1,T>
    lower bounds: Moment
  where T,P,U are type-variables:
    T extends TimePoint<? super INT#1,T> declared in method <T>between(T,T)
    P extends Object declared in interface TimeMetric
    U extends Object declared in interface TimeMetric
  where INT#1,INT#2 are intersection types:
    INT#1 extends Enum<? extends INT#2>,IsoUnit
    INT#2 extends Enum<?>,IsoUnit

despite Moment extending extends TimePoint<TimeUnit, Moment>.

What am I doing wrong?

Thanks.

MenoData commented 3 years ago

Well, the compiler is right to reject the code above because a Moment extending TimePoint<TimeUnit, Moment> uses TimeUnit while a net.time4j.Duration uses IsoUnit (or more concrete: CalendarUnit resp. ClockUnit).

The key to understand the nature of the problem is: Moment and TimeUnit are global types in universal time (UT), but the units (and duration) you wish - CalendarUnit resp. ClockUnit - work in context of a time zone.

If you insist on moments (UT) then the appropriate timespan type is usually MachineTime which will satisfy the compiler because it works with TimeUnit, too. There is one limitation however. TimeUnit only supports days or smaller units but not months or years. Days in UT are always 24 hours long, but days in a time zone might be longer or shorter. The same with months or years. Just think of what happens around a switch from summer to winter time for example. Time4J is simply strict in making a difference here.

If you wish to support years or months, too, and also wish to support moments (at least indirectly) then I recommend first to do a conversion from Moment to PlainTimestamp using the appropriate time zone and then to construct a duration like following:

Moment t1 = SystemClock.currentMoment();
Moment t2 = t1.plus(4646, TimeUnit.DAYS);
Timezone tz = Timezone.ofSystem();  // or choose an explicit zone
PlainTimestamp tsp1 = t1.toZonalTimestamp(tz.getID());
PlainTimestamp tsp2 = t2.toZonalTimestamp(tz.getID());

TimeMetric<IsoUnit,Duration<IsoUnit>> zonedMetric =
  Duration.in(
    tz,
    CalendarUnit.YEARS, CalendarUnit.MONTHS, CalendarUnit.DAYS, 
    ClockUnit.HOURS, ClockUnit.MINUTES);
Duration<IsoUnit> duration = zonedMetric.between(tsp1, tsp2);

After this step, you are free to normalize or to print the duration.

mel4tonin4 commented 3 years ago

@MenoData Thank you very much, Meno!

mel4tonin4 commented 3 years ago

I adopted Time4J for my application.

At the moment I'm doing a very basic usage of Time4J: only as a final stage when displaying the time elapsed from the creation of various database objects, using this class to format the description:

sealed class ElapsedTimeDescription(
    val tz: Timezone = Timezone.ofSystem()
) {
    companion object : ElapsedTimeDescription() {
    }

    val zonedMetric = Duration.`in`(
        tz,
        CalendarUnit.YEARS, CalendarUnit.MONTHS, CalendarUnit.DAYS,
        ClockUnit.HOURS, ClockUnit.MINUTES
    )

    fun format(a: Moment, b: Moment, locale: Locale = Locale.US, textWidth: TextWidth = TextWidth.WIDE) : String {
        val tsp1 = a.toZonalTimestamp(tz.id)
        val tsp2 = b.toZonalTimestamp(tz.id)

        val d = zonedMetric.between(tsp1, tsp2)

        return PrettyTime.of(locale).print(d, textWidth)
    }

    fun format(a: Instant, b: Instant = Instant.now(), locale: Locale = Locale.US, textWidth: TextWidth = TextWidth.WIDE): String =
        format(
            TemporalType.INSTANT.translate(a),
            TemporalType.INSTANT.translate(b),
            locale,
            textWidth
        )
}

and some JavaFX Text, updated with Kotlin coroutines, to display the description.

Thanks again!

MenoData commented 3 years ago

thanks for feedback and happy coding.