Why Instant does not support operations with ChronoUnit.YEARS?
Asked Answered
T

2

71

This was unexpected to me:

> Clock clock = Clock.systemUTC();

> Instant.now(clock).minus(3, ChronoUnit.DAYS);
java.time.Instant res4 = 2016-10-04T00:57:20.840Z

> Instant.now(clock).minus(3, ChronoUnit.YEARS);
java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Years

As a workaround I have to do this:

> Instant.now(clock).atOffset(ZoneOffset.UTC).minus(3, ChronoUnit.YEARS).toInstant();
java.time.Instant res11 = 2013-10-07T01:02:56.361Z

I am curios why Instant does not support YEARS. Did developers just give up on it?

(In my actual code I tried to subtract a Period.ofYears(3) but the quoted Instant methods are the ones being called in the end).

Tound answered 7/10, 2016 at 1:15 Comment(20)
My actual question would be, why ChronoUnit.DAYS is supported. That’s inconsistent…Subjunctive
Inconsistent with what? My expectation was that if a method takes a Temporal unit (Period) then it knows what to do with it, so it was surprising when it failed.Tound
The length of days is not constant, when measuring using exact units of higher precision, i.e. seconds, microseconds or even nanoseconds. As soon as you start supporting days, assuming them to be exactly 86400 seconds, you are giving up the actual meaning of, e.g. seconds, microseconds and nanoseconds, as well as the units that are build as unambiguous multiples of seconds, i.e. minutes and hours. So it’s strange to have a class supporting nanos, micros, millis, seconds, minutes, hours, and days where the support for days turns all former into pseudo units.Subjunctive
The documentation of Instant.minus directs to Instant.plus which lists all supported units, further, the method Instant.isSupported(TemporalUnit) can be queried in advance and its documentation lists the supported ChronoUnits.Subjunctive
Of course, the Instant would support any TemporalUnit whose addTo method does the job, so you could implement an alternative YEAR unit performing the work, though this raises the question why to pass the request through instant.minus(…, YourYearUnit) instead of invoking addTo(instant, -…) on your custom unit directly…Subjunctive
Interesting that there is no answer to this question yet. I am sure there must be a reason, but I am unable to find an explanation anywhere. Only advice on what to use instead...Frederigo
@Blackman The same is true for minutes and hours since a minute can have 60, 59 or 61 seconds.Cicelycicenia
@Subjunctive in the docs for instant. The Java Time-Scale divides each calendar day into exactly 86400 subdivisions, known as seconds. These seconds may differ from the SI second. It closely matches the de facto international civil time scale, the definition of which changes from time to time. Blackman
@jens I hadn't heard that, and if you read the javadoc for Instant they don't mention that possibility. They do however mention that days are not always 86400 seconds, but java defines a day as being 86400 'seconds'Blackman
@Blackman Oh, I didn't know that. I assumed (and I think I read it quite often) that Instant is UTC and thus assumed that it uses leap seconds. Thanks for pointing that out. The doc also says that if there is a day with a leap second, it will be spread equally over the seconds of the day, keeping the number of seconds per day of 86400.Cicelycicenia
@Blackman that’s exactly what I said, “As soon as you start supporting days, assuming them to be exactly 86400 seconds, you are giving up the actual meaning of, e.g. seconds, …”.Subjunctive
@Subjunctive I was just pointing out the docs explicitly say it. You seemed to say it as though there was some debate to how java was handling it.Blackman
@Blackman I clearly said, my question would be “why” not “how”…Subjunctive
@Subjunctive You didn't clearly distinguish this. Further, what is your point? I added some information by pointing out what you are claiming is in the javadoc. Right now you are either arguing for the sake of arguing, or you are trying to prove something relevant. I suspect it is the forming but maybe I am just missing the point.Blackman
@Blackman my point has been stated right in first comment, more than a year ago. I didn’t want to discuss this further, at least not with someone who can’t answer that question. I’m not “arguing for the sake of arguing”; it was you who used @Holger to tell me nothing new. We know what has been decided, but the question was why.Subjunctive
@Subjunctive I was adding to your second comment, because the way you had made that comment using "As soon as you..." made it sound as though you were speculating about there choice. It didn't come across that you were describing what was in the javadoc. Sorry to have disturbed you.Blackman
@Blackman that phrase was used to indicate that this will happen always when making such a design decision, regardless of which way you go. As modelling a day with a fixed number of seconds is not compatible with real seconds. Granted, that could be misinterpreted.Subjunctive
The answer to this question is most likely as already pointed out: Instant is according to the documentation an implementation of the 'Java time-scale' and in that system. a day is the longest temporal unit defined (always as exactly 86.400 seconds). So the real question is most likely rather: Why was the 'Java time-scale' defined in such an arbitrary way with an obviously very incorrect definition of a day.Ranket
The often mentioned leap second is a rather theoretical problem, since it is not supported anyway on any commonly used systems, but in all countries using DST, there are two days each year with 23 and 25 hours. Adding a Java day to 2018-03-24 12:00 in central Europe will result in 2018-03-25 13:00, which is most definitely not what most developers or users would expect.Ranket
"I clearly said, my question would be “why”" - The developers made a choice. Unless you can get an answer from the developers, the question is "opinion-based", no?Afterimage
B
55

I'm taking a stab at it in what looks to me like something very logical.

Here is the code for the method plus(long, TemporalUnit) (which is used in minus(...)):

@Override
public Instant plus(long amountToAdd, TemporalUnit unit) {
    if (unit instanceof ChronoUnit) {
        switch ((ChronoUnit) unit) {
            case NANOS: return plusNanos(amountToAdd);
            case MICROS: return plus(amountToAdd / 1000_000, (amountToAdd % 1000_000) * 1000);
            case MILLIS: return plusMillis(amountToAdd);
            case SECONDS: return plusSeconds(amountToAdd);
            case MINUTES: return plusSeconds(Math.multiplyExact(amountToAdd, SECONDS_PER_MINUTE));
            case HOURS: return plusSeconds(Math.multiplyExact(amountToAdd, SECONDS_PER_HOUR));
            case HALF_DAYS: return plusSeconds(Math.multiplyExact(amountToAdd, SECONDS_PER_DAY / 2));
            case DAYS: return plusSeconds(Math.multiplyExact(amountToAdd, SECONDS_PER_DAY));
        }
        throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);
    }
    return unit.addTo(this, amountToAdd);
}

We can see that the results are calculated by multiplying seconds representation of units, a year cannot be logically and consistently represented by seconds for obvious reasons.


Addition

I can see another obvious reason why : constants used in the method above come from java.time.LocalTime. The constants only define units up to days. No constant above days are defined (in LocalDate and LocalDateTime neither).

Baudelaire answered 13/3, 2018 at 9:27 Comment(16)
@AlexeyRomanov Instant defines it's own Java time scale. In this time scale, each day has 86400 seconds no matter what. These seconds do not match the SI second all the time. As long as you stay in this system, a day is precisely defined.Cicelycicenia
@Cicelycicenia when you are defining your own time scale, where each day has 86400 seconds no matter what, what is stopping you from defining further that each year has 365 days, no matter what? That’s the whole point, since this time scale does already uses pseudo-seconds, this is not explaining why the YEARS unit is not supported.Subjunctive
Also, it is possible to add a year to a LocalDateTime, this logic could have been applied to an Instant as well?Frederigo
@Subjunctive I never said this explains why it doesn't support years. I was just pointing out that the problem is not with different number of seconds in days, hours or minutes, and that it has nothing to do with resolution of time measurements. Maybe it is because a different definition of year would be much more different from the usual solar year than the definition of seconds?Cicelycicenia
@Subjunctive Leap years bring a lot of difference in seconds compared to differences between days with more or less seconds. The difference in leap years is too big;Baudelaire
@Frederigo You can indeed add a year to a Temporal but it just adds the year logically, it does not add a number of units representing a year.Baudelaire
@YassinHajaj one could add a year logically to an Instant... but I guess this it is actually the difference between Instant and LocalDateTime. Instant is something "real", while LocalDateTime is more of a subjective interpretation of time. For Instant it is safer to just throw an exception as the exact behaviour is undefined, while LocalDateTime just implements the behaviour most people would need/expect.Frederigo
@YassinHajaj then again, why can't you add a WEEK to an Instant? It's a day multiplied by 7, there is no much room for interpretation in this case...Frederigo
@Frederigo The constant week does not existBaudelaire
@YassinHajaj ChronoUnit.WEEKS? (docs.oracle.com/javase/8/docs/api/java/time/temporal/…)Cicelycicenia
@YassinHajaj In locations where daylight saving time is observed, you have two days in the year with 23 and 25 hours. That is a 4,2% deviation twice a year from the 86.400 seconds as implemented in the code you are quoting. Using the same logic to define a year as 31.536.000 seconds would only give a deviation of 0.27% less than once every four years. Your explanation does not hold.Ranket
@Ranket As you know probably, an Instant is not the same as a ZonedDateTime because it is based on UTC. I'm not sure the earth turns 1 hour faster anywhere in the world :). I think you should review YOUR explaination.Baudelaire
@YassinHajaj No, I probably don't know that, because you are wrong. Instant is a model of the 'Java time-scale' (others have already tried to explain that), which is currently based on UTC-SLS. If you want to nitpick, believe me, I can do it better. It is also completely irrelevant for my objection if the data model observes time zones and/or DST. My objection against your argumentation is that whatever many people intuitively understand as 'one day' is often not even nearly exactly 24 hours.Ranket
@Cicelycicenia LocalTime does not have such constantBaudelaire
@YassinHajaj LocalTime also doesn't have a constant SECONDS or MINUTES (or any other time unit constant). They are defined in the enum ChronoUnit, but LocalTime e.g. supports only a subset. Surprisingly, this subset includes HALF_DAYS but not DAYS for LocalTime. But since the question was about Instant, does it matter which units LocalTime supports and which constants it has?Cicelycicenia
@YassinHajaj The constants e.g. SECONDS_PER_MINUTE are defined in LocalTime and statically imported. But this is just an implementation detail of Instant. What has stopped the developers from defining SECONDS_PER_YEAR etc? Especially when you consider that ChronoUnit contains YEARS?Cicelycicenia
I
8

I guess it happens because Instant does not contain information about time zone. It means that same Instant can be interpreted as different date-time value in different time zones. Let's assume we have Instant which is is represented as 2016.01.01 00:30:00 in, let's say, UTC+2 time zone. The same Instant means 2015.12.31 23:30:00 in UTC+1 time zone. 2016 is a leap year, it's length is 366 days, so in order to get Instant minus 1 year, we have to subtract 366 days from it. But 2015 is not a leap year, it's length is 365 days, so we have to subtract 365 days from Instant. This ambiguity causes the fact that Instant does not support ChronoUnit.YEARS. Similar issue causes Instant to not support ChronoUnit.MONTHS. And probably absence of DST information causes Instant to not support ChronoUnit.WEEKS.

Increscent answered 12/7, 2019 at 14:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.