-
Notifications
You must be signed in to change notification settings - Fork 9
Add LocalDateTime scalar #32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
martinbonnin
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few comments. Nothing blocking except maybe the fractional second thing.
| This scalar represents a date and time without a time-zone in the | ||
| [ISO-8601](https://en.wikipedia.org/wiki/ISO_8601) calendar system. | ||
|
|
||
| The pattern is "YYYY-MM-DDThh:mm:ss" with "YYYY" representing the year, "MM" the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would include a potential factional second here. RFC3339 supports this and Java too (with nanosecond precision).
The existing DateTime supports only 3 digits. Ping @andimarek, is there any context behind this decision?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because I decided to err on the side of clarification and simplification vs ambiguity. Always providing 3 milli seconds seemed the obvious and clear choice.
For example with a fixed length parsing and validation of it is much easier than not.
Also: what does micro 1500 ms for example mean? This should be more one more second and just 500 ms.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also: RFC3339 is just underspecified in some details. For example secfrac is never explained in detail I believe. Is it ms or nano or something else? This is why we clarified it more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand RFC3339 as specifying an arbitrary fractional second. The unit is always second, not ms or nano. 1.5 is 1.5 second or 1500 milliseconds or 1500000000 nanoseconds.
This could be represented as 1.500 or 1.5, I think we should allow all of them.
In 2025 I think it's OK to allow variable length parsing. Java will happily parse 1.5 or 1.500 and the results will be the same. I expect most language to do the same.
The main question IMO is whether we limit the precision because some languages do not have an easy way to represent high precision dates. JavaScript for an example, has only millisecond precision. If we care about the future, nanosecond would make sense IMO.
As someone doing Kotlin mostly, I would love more precision:
time-secfrac = "." 1*9DIGIT (Meaning between 1 and 9 DIGIT)
But I also realize that a large portion of the ecosystem is on JavaScript so I'm fine with millisecond precision:
time-secfrac = "." 1*3DIGIT (Meaning between 1 and 3 DIGIT)
And a note explaining why this choice was made.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem with allowing different fraction sizes is that you need to switch between ms and tenths of seconds for example.
So you need to deal with different units which is not great and error prone imho. Requiring always 3 means it is always milliseconds and you have a fixed unit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I interpret the spec the same way that Martin does.
.1 is not deciseconds, it's ".1 of a second". It's fractions of a second, and the unit doesn't change depending on the number of digits that are included.
For example, .100000000 could be formatted as either 100 ms or 100,000,000 ns.
For Hot Chocolate, we actually diverged from the DateTime spec and went with fractional seconds of 0-7 digits, as opposed to exactly 3.
This issue has been brought up before: andimarek/graphql-scalars.com#2.
In my opinion, DateTime could be simplified by removing the parts that differ from RFC 3339 – I don't see strong enough reasons for the differences.
To the original question, we should probably finalize the discussion regarding DateTime first, but I'm not opposed to adding fractional seconds. I did notice this in RFC 3339 though, but maybe it's okay since it's optional anyway.
Finally, I fundamentally disagree with the structure of this repository, but I've opened an issue for that (#39).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
0-7 digits,
That sounds oddly specific. Why not 0-6 (microseconds) or 0-9 (nanoseconds)?
In my opinion, DateTime could be simplified by removing the parts that differ from RFC 3339 – I don't see strong enough reasons for the differences.
I think there is some value in limiting precision to make it easier on the implementation side. Just like Int is Int32 and not BigDecimal by defaut because in most languages, it's a lot easier to deal with Int32 than it is to deal with arbitrary precision integers.
I would probably limit precision to 0-3 digits (JavaScript) or 0-9 digits (Java), not 100% sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds oddly specific. Why not 0-6 (microseconds) or 0-9 (nanoseconds)?
Ya, it's for C#, which has 100 nanosecond precision. Perhaps 9 (or arbitrary) would have been a better choice.
From Claude:
- Nanosecond precision (9 digits): Java 8+, Go, Rust, Ruby, Oracle TIMESTAMP
- Microsecond precision (6 digits): Python, PHP, PostgreSQL, MySQL 5.6.4+
- 100-nanosecond precision (7 digits): C# DateTime/DateTimeOffset, SQL Server datetime2
- Millisecond precision (3 digits): JavaScript, older Java Date, MongoDB, SQLite
Then I asked:
If you were designing a DateTime scalar for GraphQL, what fractional second precision would you opt for?
With the response:
I'd go with microsecond precision (6 decimal places) for a DateTime scalar in GraphQL.
Here's my reasoning:
Microseconds hit the sweet spot between practical utility and reasonable overhead. Most database systems (PostgreSQL, SQL Server, MySQL) natively support microsecond precision for timestamps, so you get natural round-tripping without data loss. It's also sufficient for most application-level timing needs - logging, event sequencing, distributed system coordination.
Nanoseconds (9 places) are overkill for the vast majority of use cases. While .NET's DateTime and DateTimeOffset support 100-nanosecond ticks internally, that precision rarely matters at the application layer. You'd be transmitting and parsing extra digits that provide no real value in typical GraphQL scenarios. The main exception would be high-frequency trading or scientific instrumentation, which are edge cases.
Milliseconds (3 places) are too coarse for modern systems. You'll run into ambiguity with event ordering, especially in high-throughput scenarios or when dealing with multiple events within the same millisecond. Microseconds give you 1000x more resolution for minimal cost.
Implementation-wise, I'd follow ISO 8601 format with optional fractional seconds - so both
2025-12-01T10:30:45Zand2025-12-01T10:30:45.123456Zwould be valid. This gives consumers flexibility while maintaining precision when needed.The one nuance: I'd make sure the serialization preserves whatever precision the input provides (up to 6 places), rather than always padding to 6 digits. Keeps the wire format cleaner when full precision isn't needed.
I followed up with:
What would the practical considerations be for JavaScript servers and clients, if 6 digits of precision were specified?
To which it responded:
Good question - JavaScript's Date object is actually the main friction point here.
JavaScript Date limitations:
Dateonly supports millisecond precision (3 digits). It's backed by a single number representing milliseconds since epoch.- If you serialize a Date with microseconds like
2025-12-01T10:30:45.123456Z, JavaScript will parse it fine but silently truncate to2025-12-01T10:30:45.123Z. Those last 3 digits just vanish.- You can't round-trip microsecond data through a Date object without loss.
Practical impacts:
On the server side (Node.js), this creates some awkwardness:
- If your backend (say, PostgreSQL) stores microsecond timestamps, you lose precision when hydrating into Date objects
- For most GraphQL mutations this is fine - you're typically just passing timestamps through to the DB
- But if you need to do timestamp math or comparisons in JavaScript, you're working with degraded precision
- Libraries like
date-fnsanddayjsall use Date under the hood, so they inherit the same limitationOn the client side (browsers), it's similar:
- Clients can display microsecond timestamps just fine as strings
- But the moment they parse into Date for any manipulation (sorting, filtering, relative time display), those microseconds disappear
- For UI purposes this rarely matters - humans can't perceive microsecond differences anyway
Workarounds if you actually need the precision:
- Keep timestamps as strings in JavaScript and only parse when necessary for display
- Use bigint for epoch microseconds - store as
1733053845123456ninstead of Date objects. Less ergonomic but preserves precision.- Temporal API (Stage 3 proposal) will support nanosecond precision natively, but it's not yet widely available
My take: If your GraphQL schema specifies microseconds, I'd still do it. The precision loss in JavaScript is unfortunate but acceptable for most applications. Document it clearly, and structure your resolvers to pass database timestamps through without unnecessary Date conversions. The places where you actually need sub-millisecond precision in JavaScript logic are pretty rare.
The alternative - specifying only milliseconds to match JavaScript's capability - feels like letting the weakest link dictate the standard, which hamstrings other ecosystems unnecessarily.
That last paragraph really resonates with me, especially given the fact that Temporal will support nanosecond precision.
I'm currently leaning toward 0-9 digits.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or alternatively, not specifying the precision, to align with RFC 3339.
What about simply not specifying the precision, to align with RFC 3339?
That's actually the cleanest approach and what I'd recommend in practice.
RFC 3339 explicitly allows variable precision:
- The spec says fractional seconds are optional and doesn't mandate a specific number of digits
2025-12-01T10:30:45Z,2025-12-01T10:30:45.1Z, and2025-12-01T10:30:45.123456789Zare all valid- This gives you "precision on demand" - use what you need, no more
Benefits of not specifying:
Flexibility across platforms - Each layer can use its native precision without translation:
- JavaScript naturally produces milliseconds
- .NET can emit its full 7-digit precision (100ns ticks)
- PostgreSQL sends microseconds
- Go's
time.Timeprovides nanoseconds- No impedance mismatch, no forced padding or truncation
Simpler parsing - Your scalar just validates RFC 3339 format and passes values through. You don't need logic to enforce "must be exactly 6 digits" or pad zeros.
Better for observability - When debugging, seeing
2025-12-01T10:30:45.123Zvs.123000Ztells you which system generated it. The precision itself carries information.Forward compatible - If you later need nanosecond precision for some new use case, the schema doesn't need to change.
HotChocolate precedent:
Looking at HotChocolate'sDateTimeType, I believe it already follows this pattern - it parses ISO 8601/RFC 3339 without enforcing specific fractional digits. The scalar just validates format and lets the underlying DateTime/DateTimeOffset handle precision naturally.The one tradeoff: You lose strict predictability in your API contract. A client can't assume "all timestamps will have exactly 6 digits." But in practice, this is rarely a real issue - consumers should parse timestamps as temporal values, not do string manipulation on them.
So yeah, don't specify precision at all. Just document "RFC 3339 format with optional fractional seconds" and let implementations use what makes sense for them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, JSON Schema simply references RFC 3339:
https://json-schema.org/understanding-json-schema/reference/type#built-in-formats
| The prefix "Local" comes from the fact that without a time-zone it is not a | ||
| specific point in time, but rather expresses a "local point of view". |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, it'd be nice to refer to RFC 3339 section 5.6, like DateTime is doing. I think this proposal is
local-date-time = full-date "T" patial-time
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had aligned this spec with the LocalDate spec, which doesn't mention RFC 3339.
RFC 3339 mentions in its introduction:
All times expressed have a stated relationship (offset) to Coordinated Universal Time (UTC).
Date and time expressions indicate an instant in time.
So it may not make sense to reference it for a local date/time?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, I think we're bumping into the sad reality that is the mess of internet RFCs. I don't have a strong opinion whether we should use RFC 3339 (with this weird wording) or ISO 8601 (not publicly available). If anything, I would aim for consistency.
| Because this scalar depends on the ISO-8601 calendar it is not recommended to | ||
| use for dates before the year 1582. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TIL.
This is not mentioned in https://scalars.graphql.org/andimarek/date-time.html so I would either remove it here or add it there for consistency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned, this spec was based on LocalDate, which includes this paragraph.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would remove it there then ^^.
Or put some more details, for an example from the JavaDoc: https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html
The ISO-8601 calendar system is the modern civil calendar system used today in most of the world. It is equivalent to the proleptic Gregorian calendar system, in which today's rules for leap years are applied for all time. For most applications written today, the ISO-8601 rules are entirely suitable. However, any application that makes use of historical dates, and requires them to be accurate will find the ISO-8601 approach unsuitable.
(Assuming we're OK with ISO 8601)
My logaf is low though so your call.
|
ping @graphql/tsc for review 👀 |
|
General comment: I am not a big fan of referencing iso 8601 because the standard is not public (you have to pay for it) and huge. |
|
I think the namespace should be changed: it should be under @glen-84 name and not chili cream to identify uniquely the author. That was at least the original idea. |
|
I'm off today, but will dig into this again next week. |
|
Just to clarify: the actual spec is fine and can be merged whenever @glen-84 is happy with it (completely under his authority). BUT: the namespace issue need to be sorted out before that and we need to decide that before it can be merged. Therefore it is blocked until #33 is resolved (or @glen-84 changes the path to match his username, then we can merge anytime) |
But you did for the This spec was based on that. Happy to align things if/when that spec is updated. |
Closes #23
Note: I've made the
Tcase insensitive to match theDateTimescalar, although I'm not sure if these relaxations are necessary.