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

Define timestamp with more specificity #1858

Merged
merged 1 commit into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 165 additions & 0 deletions designs/defining-timestamps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# Defining Smithy Timestamps

* **Author**: Michael Dowling
* **Created**: 2023-07-14

# Abstract

This document defines the missing details of Smithy timestamp shapes that will
help ensure consistent implementations across programming languages, including
the temporal resolution and range of allowable dates.

## Motivation

The timestamp shape is a protocol agnostic instant in time; its data type in
code does not change based on the protocol bindings or serialized format of the
timestamp. Smithy's timestamp shape does not currently define critical details
that implementations need to consistently implement Smithy across programming
languages, including the temporal resolution of a timestamp or the supported
date range. This lack of specification risks incompatibilities across
implementations.

## Proposal

A timestamp represents an instant in time in the proleptic Gregorian calendar,
independent of local times or timezones. Timestamps support an allowable date
range between midnight January 1, 0001 CE to 23:59:59.999 on
December 31, 9999 CE, with a temporal resolution of 1 millisecond. This
resolution and range ensures broad support across programming languages and
guarantees compatibility with [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339.html).

### Rationale

Determining the temporal resolution and allowable range of a Smithy timestamp is
difficult because we want to ensure broad compatibility across programming
languages. Smithy protocols today support an undefined fractional precision for
many timestamp formats, which in practice when calling AWS APIs is millisecond
precision. This implies that Smithy timestamps should at least support
millisecond precision, but it does not imply a minimum and maximum date. To
determine an appropriate range, we observe the following programming language
date/time types:

* .NET
- [DateTime](https://learn.microsoft.com/en-us/dotnet/api/system.datetime?view=net-7.0):
Nanosecond precision (every 100 nanosecond "ticks"), with a range of
January 1, 0001 through December 31, 9999.
* C++: [chrono time_point](https://en.cppreference.com/w/cpp/chrono/time_point):
Seems to allow any resolution, calendar, or date range.
* Go: [Time](https://pkg.go.dev/time#Time): Nanosecond precision, using a
signed int to represent years.
* Java:
* [Instant](https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html):
Nanosecond precision, ranging from -1000000000-01-01 to 1000000000-12-31.
* [Date](https://docs.oracle.com/javase/8/docs/api/java/util/Date.html):
Millisecond precision with a range defined by a Long, allowing for dates
in the distant past and future.
* JavaScript: [Date](https://262.ecma-international.org/5.1/#sec-15.9.1.1):
Millisecond precision. Can store ±8,640,000,000,000,000 milliseconds, which is
based on and slightly less than the maximum integral number that can be safely
represented by a JavaScript number. This provides a range from April 20,
271821 BCE to September 13, 275760 CE.
* Kotlin: Java's Instant or [kotlinx.Instant](https://kotlinlang.org/api/kotlinx-datetime/kotlinx-datetime/kotlinx.datetime/-instant/):
Nanosecond precision, with an undocumented range.
* PHP: [DateTime](https://www.php.net/manual/en/intro.datetime.php):
Microsecond precision, "The range is from about 292 billion years in the past
to the same in the future".
* Python: [datetime](https://docs.python.org/3/library/datetime.html):
Microsecond precision, ranging from 0001-01-01T00:00:00Z to 9999-12-31T23:59:
59.999999Z.
* Ruby: [DateTime](https://ruby-doc.org/stdlib-2.6.1/libdoc/date/rdoc/DateTime.html):
Nanosecond precision, with an undocumented range.
* Swift: [Date](https://developer.apple.com/documentation/foundation/date)
/ [NSDate](https://developer.apple.com/documentation/foundation/nsdate):
"sub-millisecond" precision, with the underlying representation using a Double
to contain sections and fractions of a second.

Millisecond precision is the most broadly supported. Setting the default
precision of timestamps to nanoseconds rather than milliseconds would
immediately render the AWS SDK for PHP, JavaScript, Botocore, and many Amazon
services incompatible with the requirement. Languages without nanosecond
support would need to create custom data structures to represent timestamps
that are incompatible with the rest of their ecosystem.

Python and .NET's date ranges are the most limited. However, this range likely
captures most of the use cases developers would expect from Smithy timestamps
(i.e., typically representing nearby instants in time), and also ensures
timestamp values are compatible with
[RFC 3339 dates](https://www.ietf.org/rfc/rfc3339.txt).

### Protocol limitations

While Smithy timestamps can store timestamps with millisecond precision, not all
protocols support serializing timestamps with this precision. For example,
the `http-date` timestamp format does not support anything beyond second
precision, which can result in a loss of precision when transmitting a
timestamp. Modelers should be aware of the limitations of timestamp formats when
defining Smithy models.

This proposal will update the specification of Smithy's
existing [timestamp formats](https://smithy.io/2.0/spec/protocol-traits.html#timestampformat-trait)
to document the representable precision and behavior when a timestamp is too
granular.

* `http-date`: Second precision with no fractional seconds. A deserializer that
encounters an `http-date` timestamp with fractional precision SHOULD fail to
deserialize the value (for example, an HTTP server SHOULD return a 400 status
code).
* `date-time`: Millisecond precision. Values that are more granular than
millisecond precision SHOULD be truncated to fit millisecond precision.
* `epoch-seconds`: Millisecond precision. Values that are more granular than
millisecond precision SHOULD be truncated to fit millisecond precision.

#### Protocol limitations rationale

Because the resolution of a Smithy timestamp was previously undefined,
truncating timestamps that are too granular rather than failing to deserialize
them ensures that existing and new implementations can interoperate. Truncating
overly precise times also keeps the door open in case support for nanosecond
precision is needed in the future (e.g., using a hypothetical
`@resolution("nanos")` trait).

### Timestamp representation in code

Implementations MUST NOT directly expose the serialized value of a timestamp.
Timestamps are an abstraction to represent an instant in time, and the
serialization format of a timestamp can vary depending on the protocol
or `@timestampFormat` trait of the shape. Because Smithy is protocol agnostic,
changing the serialization format of a shape MUST NOT have any impact on the
representation of that shape in code.

When generating code, implementations MUST use a type for Smithy timestamp
shapes that can hold *at least* the minimum and maximum supported values using
millisecond precision. Implementations MAY use a type that represents dates with
a wider range or temporal resolution than Smithy's timestamp shape.
Implementations SHOULD use an idiomatic date/time data structure for the
target environment, though they may need to use an alternative data structure
to represent Smithy timestamps if they do not have access to a date/time type
with at least millisecond precision spanning the supported range.

Some programming language types offer more granularity than millisecond
precision (e.g., Java's `Instant`). Using such data types to represent a Smithy
timestamp is fine as long as the timestamp is serialized according to any
limitations defined in the protocol and any `@timestampFormat` trait applied to
the timestamp. Implementations SHOULD NOT refuse to serialize a timestamp due to
an overly granular value, and instead they should serialize the timestamp
according to the limitations of the protocol and model.

## Alternatives and tradeoffs

### Use nanosecond resolution

Standardizing on nanoseconds would make it more difficult for JavaScript (ms),
PHP (us), Python (us), and to an extent .NET to losslessly represent timestamps.
Smithy implementations for these programming languages would need to choose
whether they should use a lossy but idiomatic data type when working with
timestamps, or if they want to create their own data structure.

Languages without nanosecond support could provide a light-weight data structure
that easily converts to idiomatic date/time types. This kind of abstraction
SHOULD NOT be created for languages that already provide compatible date/time
types. A drawback of such a type is that a Smithy timestamp would be
incompatible with the rest of the language's date/time types (e.g, intervals).
Any date/time-based logic would have to be handled manually by end-users.

Finally, nanosecond precision is not currently supported by most of AWS, which
supports millisecond precision.
11 changes: 6 additions & 5 deletions docs/source-1.0/spec/core/model.rst
Original file line number Diff line number Diff line change
Expand Up @@ -334,11 +334,12 @@ Simple shapes
* - bigDecimal
- Arbitrary precision signed decimal number
* - timestamp
- Represents an instant in time with no UTC offset or timezone. The
serialization of a timestamp is an implementation detail that is
determined by a :ref:`protocol <protocolDefinition-trait>` and
MUST NOT have any effect on the types exposed by tooling to
represent a timestamp value.
- A timestamp represents an instant in time in the proleptic Gregorian
calendar, independent of local times or timezones. Timestamps support
an allowable date range between midnight January 1, 0001 CE to
23:59:59.999 on December 31, 9999 CE, with a temporal resolution of 1
millisecond. This resolution and range ensures broad support across
programming languages and guarantees compatibility with :rfc:`3339`.
* - document
- Represents protocol-agnostic open content that functions as a kind of
"any" type. Document types are represented by a JSON-like data model
Expand Down
19 changes: 12 additions & 7 deletions docs/source-1.0/spec/core/protocol-traits.rst
Original file line number Diff line number Diff line change
Expand Up @@ -302,19 +302,24 @@ Smithy defines the following built-in timestamp formats:
- Description
* - date-time
- Date time as defined by the ``date-time`` production in
`RFC3339 section 5.6 <https://www.rfc-editor.org/rfc/rfc3339#section-5.6>`_
with optional fractional precision but no UTC offset (for example,
``1985-04-12T23:20:50.52Z``).
*However*, offsets are parsed gracefully, but the datetime is normalized
to an offset of zero by converting to UTC.
:rfc:`3339#section-5.6` with optional millisecond precision but no
UTC offset (for example, ``1985-04-12T23:20:50.520Z``). Values that
are more granular than millisecond precision SHOULD be truncated to
fit millisecond precision. Deserializers SHOULD parse ``date-time``
values that contain offsets gracefully by normalizing them to UTC.
* - http-date
- An HTTP date as defined by the ``IMF-fixdate`` production in
:rfc:`7231#section-7.1.1.1` (for example,
``Tue, 29 Apr 2014 18:30:38 GMT``).
``Tue, 29 Apr 2014 18:30:38 GMT``). A deserializer that encounters an
``http-date`` timestamp with fractional precision SHOULD fail to
deserialize the value (for example, an HTTP server SHOULD return a 400
status code).
* - epoch-seconds
- Also known as Unix time, the number of seconds that have elapsed since
00:00:00 Coordinated Universal Time (UTC), Thursday, 1 January 1970,
with optional fractional precision (for example, ``1515531081.1234``).
with optional millisecond precision (for example, ``1515531081.123``).
Values that are more granular than millisecond precision SHOULD be
truncated to fit millisecond precision.

.. rubric:: Resolving timestamp formats

Expand Down
19 changes: 12 additions & 7 deletions docs/source-2.0/spec/protocol-traits.rst
Original file line number Diff line number Diff line change
Expand Up @@ -218,19 +218,24 @@ Smithy defines the following built-in timestamp formats:
- Description
* - date-time
- Date time as defined by the ``date-time`` production in
:rfc:`3339#section-5.6`
with optional fractional precision but no UTC offset (for example,
``1985-04-12T23:20:50.52Z``).
*However*, offsets are parsed gracefully, but the datetime is normalized
to an offset of zero by converting to UTC.
:rfc:`3339#section-5.6` with optional millisecond precision but no
UTC offset (for example, ``1985-04-12T23:20:50.520Z``). Values that
are more granular than millisecond precision SHOULD be truncated to
fit millisecond precision. Deserializers SHOULD parse ``date-time``
values that contain offsets gracefully by normalizing them to UTC.
* - http-date
- An HTTP date as defined by the ``IMF-fixdate`` production in
:rfc:`7231#section-7.1.1.1` (for example,
``Tue, 29 Apr 2014 18:30:38 GMT``).
``Tue, 29 Apr 2014 18:30:38 GMT``). A deserializer that encounters an
``http-date`` timestamp with fractional precision SHOULD fail to
deserialize the value (for example, an HTTP server SHOULD return a 400
status code).
* - epoch-seconds
- Also known as Unix time, the number of seconds that have elapsed since
00:00:00 Coordinated Universal Time (UTC), Thursday, 1 January 1970,
with optional fractional precision (for example, ``1515531081.1234``).
with optional millisecond precision (for example, ``1515531081.123``).
Values that are more granular than millisecond precision SHOULD be
truncated to fit millisecond precision.

.. rubric:: Resolving timestamp formats

Expand Down
30 changes: 25 additions & 5 deletions docs/source-2.0/spec/simple-types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -144,17 +144,37 @@ A bigDecimal is an arbitrary precision signed decimal number.
timestamp
=========

A timestamp represents an instant in time with no UTC offset or timezone.
The serialization of a timestamp is an implementation detail that is
determined by a :ref:`protocol <protocolDefinition-trait>` and MUST NOT
have any effect on the types exposed by tooling to represent a timestamp
value.
A timestamp represents an instant in time in the proleptic Gregorian calendar,
independent of local times or timezones. Timestamps support an allowable date
range between midnight January 1, 0001 CE to 23:59:59.999 on
December 31, 9999 CE, with a temporal resolution of 1 millisecond. This
resolution and range ensures broad support across programming languages and
guarantees compatibility with :rfc:`3339`.

.. code-block:: smithy

timestamp MyTimestamp


Timestamp serialization and deserialization
-------------------------------------------

The serialization format of a timestamp is an implementation detail that is
determined by a :ref:`protocol <protocolDefinition-trait>` and or
:ref:`timestampFormat-trait`. The format of a timestamp MUST NOT have any
effect on the types exposed by tooling to represent a timestamp value.

Protocols and ``timestampFormat`` traits MAY support temporal resolutions
other than 1 millisecond. For example, the ``http-date`` timestamp format
supports only seconds and forbids fractional precision. Modelers need to be
aware of these limitations when defining timestamps to avoid an unintended
loss of precision.

The use of timestamps outside the allowable range risk not interoperating
correctly across Smithy implementations; deserializers that encounter
timestamps outside the allowable range SHOULD fail to deserialize the value.


.. _document:

document
Expand Down