In most web APIs nanoseconds are represented as fractions of milliseconds. Why was a pair of integers chosen here? Is it possible to make floating-point milliseconds the only representation instead, or does that lose too much precision? If the latter, it'd be great to have a section explaining.
Relatedly, I'm wondering what new Instant(123456.7890, 1234.532) does.
@domenic do any of the other web APIs actually include nanoseconds in wall clock time? Most things I'm familiar with include microsecond support in datetime APIs and only include nanoseconds in monotonic high-resolution timing APIs that aren't meant to relate to wall clocks.
They are all relative to page load time, instead of to the epoch, if that's what you mean.
Yeah, those fall into the "monotonic high-resolution timer" category.. I guess I'd want to generally know whether there was a use case / motivation to have nanosecond resolution wall clock time since numbers at that resolution don't really have any practical meaning for comparing between common computers.
There are a few considerations:
Nanosecond precision seems to be what other languages' APIs are leaning towards. If we want JavaScript to be able to compete then we should somehow support nanos. I think this matters greatly for things like high-frequency trading, scientific computing, and other areas. Even if we went with micros, we'd still have the same set of concerns - so we might as well support nanos.
We need to support at least the same range as Date, which is -864e13 to 864e13 milliseconds around the Unix epoch. To extend precision to nanoseconds in a single number, that would be -864e19 to 864e19, which is well beyond the range of Number.MIN_SAFE_INTEGER -
Number.MAX_SAFE_INTEGER. So we'd have to take a dependency on the bigint proposal, and support input that is either Number or BigInt. I'm not sure if this would be a good API or not. Maybe @littledan has some ideas about this.
I think many people will continue to work in milliseconds, since that's the precision we have currently with Date. If nanoseconds were the only field, we'd probably see math in usage, like new Instant(timestamp * 1e6). That can be error prone. That's why I went for millis as one field, and nanos (within millis) as a separate, optional field.
As far as the question of what new Instant(123456.7890, 1234.532) does, I see two options:
Truncate decimals on both fields, giving the same value as new Instant(123456, 1234)
Truncate decimals on the nanoseconds field only, and carry over decimals in the milliseconds field (up to 6 places). In that case, this would be the same as new Instant(123456, 789000 + 1234), or new Instant(123456, 790234)
Which would you expect?
@mj1856 I think it might be a fallacy though.. Financial market timestamps were just standardized recently by MiFID 2 regs and HFT activity (the top-tier requirement) is required to have 100 microsecond accuracy and 1 microsecond granularity. Most people dealing with high-resolution wall clock requirements nowadays need microseconds. What other language APIs were you referring to? I'm curious what the documentation states. I think if JS handed people a number of nanoseconds, they would incorrectly assume they can send that number around and compare it across other systems like they do for millis-since-epoch + NTP synchronized systems. It just doesn't work that way...
A quick search shows nanoseconds are supported by:
Instant - Java 8PlainTimeStamp, Moment - Time4JInstant - Noda TimeTime - Rubynanotime - Pythonstd::chrono::duration - C++Probably a lot more. PHP and a few other languages/libraries support only microseconds, but I like I said, if we support anything finer than milliseconds we might as well support nanoseconds.
As currently spec'd, though we support nanoseconds, you're not going to get them back in a single number. We'd need BigInt for that. You would get them back as part of an ISO8601 string representation, but it's already a fact that precision loss can occur during serialization/deserialization. If anything, this would help that problem.
Consider .NET which gives 7 decimals of precision ("ticks"). It's quite common to see a DateTime.UtcNow value serialized as ISO8601 and round-tripped from server to client and back, losing 4 decimals of precision. With support for nanoseconds on the client, those digits would be persisted. (Note also that newer .NET now can give now values with precision of under one microsecond, via the changes in dotnet/coreclr#9736)
I was more concerned if there was a push to try to make the "current datetime" API return a sub-micro resolution value, which is separate from making the data model support working with nanoseconds. (e.g., AFAIK on Linux, Java 8 only returns milli precision and Java 9 ups that to micro)
Sorry for digressing there for a min.
Maybe there's a third option.. Throw an error if the numbers aren't integers?
J8 Instant uses nanoseconds everywhere. It's not platform dependent.
https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html
I still feel like nobody's answered my question. This probably means the answer is obvious to people here, but it's not to me, and presumably not to others coming across this proposal. So it would be good to get it recorded in the README.
To repeat my question:
Why do we need to use [integer milliseconds since epoch, integer nanoseconds since designated millisecond] instead of floating-point milliseconds since epoch?
Because of the range issues I described in point 2 of this comment. Moving the decimal place doesn't help. We'd still have floating point conversion issues at the finer parts of the scale.
@mj1856 Instant uses nanoseconds (what I was referring to as the data model) but when you ask J8 "what time is it?" it only gives you milli precision. When you ask J9 that question, it gives you micro precision (in both, the nanos portion will always be 0).
Why is supporting -864e13 to 864e13 milliseconds around the epoch (at nanosecond precision, not just e.g. millisecond precision) a requirement?
@apaprocki - Let's move that discussion to #28 please. Here let's focus on what the objects can represent and how the parameters are passed.
@domenic - That's the range of Date. A smaller range would likely lead to compatibility issues. A slightly larger range is possible within the range of Number, but not enough to be meaningful. Do you have a better idea?
Date does not support nanosecond resolution at that range.
Yes, I get that. It supports millisecond resolution at that range. Are you suggesting we have different ranges of support for millisecond resolution than for nanosecond resolution?
I am suggesting you interpret the input as a floating point number of milliseconds. Then you have support for as much resolution as a 64-bit float centered around the epoch gives you.
Like Date, the resolution will then vary across the range. (E.g. Date stops giving millisecond resolution around Number.MAX_SAFE_INTEGER, and starts giving 2- or 4- millisecond resolution.)
Can you work out what that looks like for nanoseconds? Clearly we wouldn't have 1ns resolution across the entire range.
Well, I believe it would look like:
etc. Note that in between you of course have varying range, e.g. within +/- Number.MAX_SAFE_INTEGER / 1e5 you have 1e-8-second resolution, etc.
I'd appreciate if someone would check my math though, as I'm largely basing this off of your above statements, and not deriving it from first principles.
The "same as Date" lines above make the statement "a smaller range would likely lead to compatibility issues" especially puzzling, so perhaps I am missing something.
Date doesn't let you go larger that +/- 100,000,000 standard-days around the epoch, and maintains 1ms resolution throughout. It's slightly less than MAX_SAFE_INTEGER in terms of milliseconds, and you can't choose seconds as the resolution to get up to * 1e3.
My point about compatibility was that if you have a milliseconds-based timestamp coming from elsewhere that you feed to a Date object, and you want to move to this API instead, you'd still want to be able to pass it in to Instant without loosing anything. So at minimum, we have to support the range and precision that the Date object supports.
On the rest of it, do we really want to state that at finer precision we only support a reduced range? I can see how that can technically work, but I think it will be quite confusing for users. Just my opinion.
To be clear, I do think we should allow the milliseconds parameter to support decimals. I'm just not in favor of removing the nanoseconds optional parameter.
My point about compatibility was that if you have a milliseconds-based timestamp coming from elsewhere that you feed to a
Dateobject, and you want to move to this API instead, you'd still want to be able to pass it in toInstantwithout loosing anything. So at minimum, we have to support the range and precision that theDateobject supports.
As far as I can tell my proposal supports this. Can you give a concrete example where it would fail?
On the rest of it, do we really want to state that at finer precision we only support a reduced range?
That seems very natural to me, since that's how numbers work in JavaScript; I would expect it to also be how dates work.
@mj1856 About using BigInt here: It would seem reasonable to me to have a single BigInt as the parameter for the constructor, but I'm not sure you want to introduce that as a dependency (maybe this proposal will zip through standardization faster than BigInt, as optimistic as I want to be). Using a pair of Numbers feels rather odd to me, but I can understand the motivation for supporting tons and tons of dates. Is it really better to take these numbers as arguments rather than an ISO 8601 string?
@apaprocki Does this proposal involve changing the data model for Date? That seems like a separate question. Also, sorry for my ignorance here, but does MiFID 2 apply globally forever, or is this a European regulation and other jurisdictions may have tighter timestamps (now or in the future)?
@littledan You can disregard that discussion on here. I don't think anyone is proposing that Date will be modified, and will stay represented in milliseconds.
@domenic closing this since it has already been handled (see https://github.com/tc39/proposal-temporal/blob/master/examples.md#object-instant and https://github.com/std-proposal/temporal/blob/main/lib/instant.mjs#L12)
@ryzokuken, your links have rotten ☹
@ByteEater-pl I think the links are not as relevant anymore anyway, the issue was about the Temporal.Absolute constructor taking a pair of integers (absolute time in milliseconds, nanoseconds ≤ 999) but now it takes the absolute time in nanoseconds as a bigint.