Skip to content

Conversation

@glen-84
Copy link
Contributor

@glen-84 glen-84 commented Nov 27, 2025

Closes #23

Note: I've made the T case insensitive to match the DateTime scalar, although I'm not sure if these relaxations are necessary.

Copy link
Contributor

@martinbonnin martinbonnin left a 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
Copy link
Contributor

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?

Copy link
Collaborator

@andimarek andimarek Nov 27, 2025

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.

Copy link
Collaborator

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.

Copy link
Contributor

@martinbonnin martinbonnin Nov 28, 2025

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.

Copy link
Collaborator

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.

Copy link
Contributor Author

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).

Copy link
Contributor

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.

Copy link
Contributor Author

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:45Z and 2025-12-01T10:30:45.123456Z would 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:

  • Date only 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 to 2025-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-fns and dayjs all use Date under the hood, so they inherit the same limitation

On 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:

  1. Keep timestamps as strings in JavaScript and only parse when necessary for display
  2. Use bigint for epoch microseconds - store as 1733053845123456n instead of Date objects. Less ergonomic but preserves precision.
  3. 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.

Copy link
Contributor Author

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, and 2025-12-01T10:30:45.123456789Z are 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.Time provides 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.123Z vs .123000Z tells 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's DateTimeType, 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +27 to +28
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".
Copy link
Contributor

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

Copy link
Contributor Author

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?

Copy link
Contributor

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.

Comment on lines +30 to +31
Because this scalar depends on the ISO-8601 calendar it is not recommended to
use for dates before the year 1582.
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

@martinbonnin martinbonnin Dec 1, 2025

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.

@martinbonnin
Copy link
Contributor

ping @graphql/tsc for review 👀

@martinbonnin martinbonnin requested a review from a team November 27, 2025 15:33
@andimarek
Copy link
Collaborator

andimarek commented Nov 28, 2025

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.
This is why I went of rfc 3339 for the date time. Can we do the same for local date time?

@andimarek
Copy link
Collaborator

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.

@glen-84
Copy link
Contributor Author

glen-84 commented Nov 28, 2025

I'm off today, but will dig into this again next week.

@martinbonnin
Copy link
Contributor

martinbonnin commented Nov 28, 2025

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.

If this is a Chillicream contribution, I think it makes sense to acknowledge that. I opened #33 for further discussion.

@andimarek
Copy link
Collaborator

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)

@martinbonnin martinbonnin mentioned this pull request Nov 28, 2025
@glen-84
Copy link
Contributor Author

glen-84 commented Dec 1, 2025

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.
This is why I went of rfc 3339 for the date time. Can we do the same for local date time?

But you did for the LocalDate scalar. 😄

This spec was based on that.

Happy to align things if/when that spec is updated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add LocalDateTime scalar?

3 participants