Skip to content
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

[RFC] Support DateTime scalar #315

Closed

Conversation

excitement-engineer
Copy link

Following the discussions in graphql-js repo (graphql/graphql-js#557) I would like to submit a proposal for adding a DateTime scalar to the GraphQL specification. Adding this to the specification allows us to get some standard behaviour regarding date and time representations in GraphQL.

The DateTime scalar conforms to the RFC 3339 profile of the ISO 8601 standard. The RFC 3339 standard is very simple to understand and lightweight in contrast to the full ISO 8601 standard, I think it is therefore a good fit for graphql. Quoting the RFC 3339 standard document:

”The complete set of date and time formats specified in ISO 8601
[ISO8601] is quite complex in an attempt to provide multiple
representations and partial representations. Appendix A contains an
attempt to translate the complete syntax of ISO 8601 into ABNF.
Internet protocols have somewhat different requirements and
simplicity has proved to be an important characteristic. In
addition, Internet protocols usually need complete specification of
data in order to achieve true interoperability. Therefore, the
complete grammar for ISO 8601 is deemed too complex for most Internet
protocols.

See the updated PR (graphql/graphql-js#557) in the graphql reference implementation for an implementation of the proposed DateTime scalar.

Issue with leap seconds
Note, the RFC 3339 profile allows for leap seconds, this is problematic because leap seconds cannot be known in advance and therefore the code will have to be continuously updated over time to take leap seconds into account. The Date class in javascript doesn’t accept leap seconds for example in order to avoid these issues. Should we do the same in the specification and explicitly mention that leap seconds are not supported?

Thoughts on a Date scalar
Next to a DateTime scalar I have also had many use cases for a Date scalar (used to represent a birthdate for example). The RFC 3339 standard provides the format YYYY-MM-DD for representing dates. I am curious to hear your thoughts whether there is a place in the spec for a Date scalar as well?

excitement-engineer added a commit to excitement-engineer/graphql-js that referenced this pull request May 26, 2017
@leebyron
Copy link
Collaborator

Thanks for opening this!

My primary concern with adding this to the spec is just seeing the broad array of types, options, and opinions brought up in graphql/graphql-js#557. If we add this type would we later regret doing so? Would most people simply ignore it in favor of their own preferred time type?

I'd like to avoid a situation where a built-in scalar for time fails to handle the cases people expect and results in a multitude of options that explode the common scalar set (like graphql/graphql-js#557 (comment))

James Gorman in that thread had a poignant comment (graphql/graphql-js#557 (comment)) "My personal view that GraphQL should either do nothing or go the whole hog and specify a semantically complete set of temporal types." - I'm curious what you think about this. Should the GraphQL core specification itself just do nothing, and leave the definition of times up to the application/user domain?

Thoughts on a Date scalar

My understanding is that a Date scalar would simply be a subset of DateTime. That may be an argument for using a less strict profile than 3339 which allows for the T<time> portion to be omitted to describe a timeless Date? It could also be that one scalar may suffice for both, but application knowledge of timeless Date may be contextual (e.g. a birthday is known to be a timeless date, even if a native date object suggested it was at T00:00:00)

@leebyron
Copy link
Collaborator

leebyron commented Jun 16, 2017

To answer your question brought up on the other thread about how times are handled at Facebook: we have a handful of different time representations, but each one is fairly specific to a different application-domain problem.

Some examples:

The most common is just a unix timestamp. We use these for timezone-less universal time, typically (but not always) for content where the time is not user-visible but instead used for behavior. Though it turns out that knowing a GMT unix timestamp and someone's local time zone can get you pretty far. It's also preferred since it's supported with no additional effort on every platform.

Second most common is an ISO 8601 time, though we've ended up building some custom client infrastructure to handle these. These are often used for Facebook Events, or other things where we're not describing time in terms of a physical reality but in terms of a human experience. "Happy hour starts at 5pm" or "Noon" or "Tomorrow" can all be coerced to a specific time, but there is context involved and sometimes not specifying seconds or a timezone, or a date (time only) is actually the desired intent. If this sounds complicated, it is.

Third most common is an object type, like type Date = { year: Int, month: Int, day: Int }. I'm not sure this is explicitly better than ISO 8601, it's at least more constrained in what it can represent which reduces complexity. We use this for showing people's birthdays on their Facebook profiles. Having a struct is important because your profile privacy rules can cause different fields to have access blocked (and return null) so you may only see someone's birth year, or only the month and day but no year.

There are a couple other long-tail representations that are used in rare cases. They're usually very specific to a certain application problem.

@rmosolgo
Copy link

Thanks for all the exploration in this area! It's really interesting to hear different approaches.

Given the range of possible solutions to DateTime/Date/Time issues, I don't think adding a scalar to the spec is worth the complexity. There's such a variety of needs and platform-specific constraints that plenty of folks will roll their own anyways.

For my own case, I used an object type so that I could include a timeAgo field that use Rails' time formatting functions, something like like:

type DateTime {
  year: Int!
  month: Int!
  # ...
  timeAgo: String!
  strftime(format: String!): String!
}

@stubailo
Copy link
Contributor

I agree that this might add more complexity than it reduces.

@excitement-engineer
Copy link
Author

excitement-engineer commented Jun 26, 2017

@leebyron thanks for explaining how Facebook handles times in more depth. It's great to see so many people sharing their use cases both in this thread and in the graphql-js PR. It does indeed show that the use cases differ greatly between different domains.

James Gorman in that thread had a poignant comment (graphql/graphql-js#557 (comment)) "My personal view that GraphQL should either do nothing or go the whole hog and specify a semantically complete set of temporal types." - I'm curious what you think about this. Should the GraphQL core specification itself just do nothing, and leave the definition of times up to the application/user domain?

This is a good question. I think that the discussions have identified 3 possible ways of looking at this:

  1. Try and cover every possible use case in the spec using a complete set of temporal types.
  2. Leave the definition of times completely up to the user domain and do not include any temporal types in the specification.
  3. Create a simple DateTime representation that covers 80%-90% of the most common use cases.

Option 1 has the advantage of being 100% complete, however it may also give a lot of overhead to the specification. I think that this will result in the picture you painted @leebyron: "I'd like to avoid a situation where a built-in scalar for time fails to handle the cases people expect and results in a multitude of options that explode the common scalar set".

Option 2 is the most flexible. It allows users to define the set of temporal types that fit their use cases exactly. However, I can see a downside in that developers will often have to "reinvent the wheel" when it comes to defining some basic date/time functionality. Developers may inadvertently represent date-time as strings or integers in which case the API does not define a clear contract that client applications can uphold.

Option 3 is thee middle-way; prevent the GraphQL specification from becoming bloated with options for representing time by introducing a simple to use DateTime scalar that follows a clear standard. @leebyron I think that your comment in the graphql-js pull request made a good point.

"Also I should point out that if we were to write specification for DateTime, it would not remove your ability to use additional types like MonthYear in your servers if that was the right choice for your API."

If the default scalar does not cover a specific use case then there is always an escape hatch that allows the user to create a custom scalar if the need arises. But the majority of the developers won't have to think about it and can simply use the default. The question is of course, what DateTime scalar definition would cover 80-90% of the use cases? Does such a scalar even exist or are there so many different domains/use cases that it is impossible to create such a scalar? As was mentioned by @rmosolgo: "There's such a variety of needs and platform-specific constraints that plenty of folks will roll their own anyways." This is a good point highlighting that creating a generic scalar may not be feasible.

I think that the discussions in the PRs have given a lot of valuable information about what kinds of issues people are running into. I do agree that adding this to the spec is risky, if specified wrongly then people will simply work around it and use their own solutions which is undesirable, therefore we have to tread carefully!

@leebyron what do you think about the above point? Do you see the same 3 options and if so do which do you think is most appropriate for graphql?

To clarify, I decided to go with the RFC 3339 profile in this PR because I think it is an example of a simple format that is widely applicable and sets some clear guidelines as to what users can expect to receive; the tradeoff for this simplicity is that it does not cover every use case. I agree that it is worth investigating whether a less constrained profile that includes things like dates (without times) etc. is more appropriate. I think that this is an interesting space to keep investigating given the valuable information contributed by everyone in the discussions over the previous months.

@leebyron
Copy link
Collaborator

leebyron commented Oct 2, 2018

I'm closing this PR since it's been quite a long time and the explorations which started from this led to the realization that defining a single format for DateTime as a base scalar wasn't able to get consensus due to different requirements.

This could be reconsidered in the future if anything changes, but I want to ensure the current set of PRs are being actively addressed.

@andimarek
Copy link
Contributor

andimarek commented Apr 25, 2019

I started a new RFC about date/time scalar: #579

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🗑 Rejected (RFC X) RFC Stage X (See CONTRIBUTING.md)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants