Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

OpenLMIS v3 has certain data that stores dates ( and times (timestamps). There is a question about how that data should be stored in the database, used in the backend, exposed through the API and used by clients (UI). There are two main types of data that would be stored using dates (and times)timestamps:

  1. Dates that represent an instant in time - timezone applies. In this document, these will be referred to as instant dates.
    1. An example here would be the created submitted date field of a requisition. This is the specific point in time where the requisition was created and storedsubmitted for authorization/approval.
  2. Dates that do not represent an instant in time - timezone does not apply. only after appropriate timezone is added - In this document, these will be referred to as general business dates.
    1. An example here would be the expiration start date of a drugprogram. This is a date , but it represents when this drug generally expiresthat represents when a program was started. It only becomes an instant in time when an appropriate timezone is added (in this case, an implementation's default timezone).

In v2, most dates in the system were not timezone-aware. As a result, the system is not able to support a country/region with multiple timezonesassumptions about timezone would be made, usually some system default that would be different in different contexts (database, Java, browser). There could also be confusion about exactly "when" something occurred, without timezone data.

...

Since we are currently using Postgres as our backend database, Postgres' timestamp with time zone data type should be used for instant dates, and date text data type for general business dates.

Java backend code

Java 8's ZonedDateTime should be used when dealing with instant dates (and generally be in UTC), and LocalDate should be used when dealing with general business dates.

Custom attribute converters should be defined for both to convert between Java and the database. If necessary, these converters should "translate" dates into UTC timezone before persisting.

Translation details:

  • For an instant date, a UTC version of it would be created, which would be the one persisted to the database.
  • For a business date, a UTC ZonedDateTime object would be created based on it, then it would be converted into a string, which would be persisted to the database.

Note: a ZonedDateTimeAttributeConverter has already been implemented in the Reference Data Service for use. This converts between ZonedDateTime and java.sql.Timestamp. However, the instant date fields that are of type ZonedDateTime will also need a JPA annotation to explicitly define the column as "timestamp with time zone". See https://github.com/OpenLMIS/openlmis-referencedata/blob/master/src/main/java/org/openlmis/referencedata/domain/SupportedProgram.java's startDate field for an example.

...

During serialization (returning dates through the API), dates :

  • Dates should be serialized into a

...

  • ISO-8601 formatted string. This is because when Jackson serializes date classes, it turns them into an array of values, which is not as useful or readable.

...

During deserialization (when dates are provided by a client to an API call):

  • Instant dates should have timezone information added. Because we cannot depend on clients (browsers) to understand complex timezone scenarios (sending a date in UTC that’s for a facility in UTC+1 for a browser that’s in Seattle running UTC-8), we should assume that instant dates are passed in without timezones and the backend code will do a timezone "translation". Rough steps for this translation:
  • Get the timestamp from the client
  • Ignore/strip any timezone information
  • Instant dates should return a timestamp string with timezone UTC.
  • Business dates should return a timestamp string with an appropriate timezone. Rough steps to add the appropriate timezone (this is to avoid using a default timezone from LocalDate):
    1. Make a "best guess" about the timezone to use
      1. If the API call is respective to a user, use the timezone in the user's profile
      2. If respective to a facility, use the facility's timezone (Note: this would be a new feature, as facility timezone profiles were not in v2)
      3. If neither, may have use an implementation default timezone (although this may not work with an implementation that spans multiple timezones)
      Append
      1. some configuration setting in the system)
    2. Create a new ZonedDateTime with the business date and the "best guess" timezone to the timestamp , and use serialize that in the backend and database
  • General dates should be in ISO-8601 format, deserialize into a LocalDate object properly and should be left alone.
    1. API response

During deserialization (when dates are provided by a client to an API call):

  • Instant dates should be in timezone UTC, which would deserialize into a ZonedDateTime object. If it is not in UTC, it would be "translated" into UTC.
  • Business dates should not have timezone information, and would deserialize into a LocalDate object.

Frontend Client Usage (i.e. AngularJS UI)

Clients displaying timestamps to user

  • For instant dates, the client would know the user (or browser's) timezone and would "translate" the UTC timestamp into a local time for the user (or browser).
  • For business dates, since the timestamp has already been set to the appropriate timezone, the client would simply display the timestamp to the user.

Clients making API calls

  • For instant dates, the client should send timestamps in UTC timezone.
  • For business dates, the client should send timestamps with no timezone information.

Appendix: Survey of Usage

Places where dates and times are being used currently (as of 2016-12-19 / 2016-12-20):

...