Skip to main content
Version: 8.2411.x.x RR

Appendix G - Date handling changes introduced with 7.2311.0.x

As part of the Java 17 modernization, Nevis is adapting interfaces to the java.time API originally introduced in Java 8.

Change overview

Legacy Java date API removal

The usage of the legacy date API is eliminated where possible. The following classes form the Java legacy date API:

  • java.util.Date
  • java.util.Calendar
  • java.util.GregorianCalendar
  • java.text.SimpleDateFormat
  • java.util.TimeZone
  • java.sql.Date
  • java.sql.Time
  • java.sql.Timestamp

The only place where the old legacy API usage remains is in the nevisAuth codebase, where those types are used by third party dependencies (for example Nimbus).

Joda-time removed

We removed the dependency and any usage of Joda-time. As per the official readme of joda-time, the library is no longer maintained actively in favor of the java.time API.

Time precision increased

Noteworthy change comes from Java 17 itself where the Instant precision was increased from Miroseconds to Nanoseconds. In practice, this affects:

  • Increased precision when converting to String.
  • Databases are using microsecond precision and will round to Microseconds. To avoid rounding issues nevisAuth truncates Instants to Microseconds when written into timestamp database fields.
  • In tests when comparing instants created very closely to each other, previously green test can fail due to the nanoseconds difference.

UTC

Now all date-time information assumed to be in UTC, and enforced by the usage of certain Java types. In practice this has very limited effect as it was mostly the case already, it is just more explicit and consistant now.

What is affected?

  • nevisAuth Java API relaeted to
    • Session
    • OOCD
    • Utilities
  • EL expressions in the esauth4.xml
    • nevisAuth API changes.
    • The following functionality in the EL expressions are no longer available:
      • AuthDateUtils
      • DateFormatUtils
      • DateUtils
      • DateTimeZone
      • DateTime
    • Most of the java.time classes mentioned below are bound to EL expressions.
  • ScriptState
    • Usage of Joda-time is no longer possible (for example DateTimeZone, DateTime).
    • AuthDateUtils is removed.
    • nevisAuth API changes.
    • Most of the java.time classes mentioned below are automatically imported.
  • Ninja / jcan-sectoken API
  • Sectoken validity time is now in UTC only.

What is not affected?

  • We do not restrict what can be done in ScriptStates so using the legacy date API will still work, except for interface changes. Though the usage of the legacy date API is not recommended.
  • Date and duration formats in the esauth4.xml configuration were not changed.

Summary

  • Do not use legacy date API classes and Joda-time if possible.
  • Do not mix the legacy API with the java.time API in formatting.
  • Use java.time.Instant instead of java.util.Date.
  • Use UTC everywhere unless required otherwise.
  • Use standardized Date formats to make life easy.
  • Use java.time.Duration instead of long for time difference and use java.time.Instant for epoch seconds or millis to not worry about time units.

Details

An overview of the java.time API is available in the official documenation.

Types used in nevisAuth

The main type used overall in nevisAuth is the java.time.Instant which stores the date-time in UTC. Replacing the java.util.Date type. Note that the java.util.Date type also stored the date-time in UTC, however in certain operations like the toString() it used the timezone of the system to display the actual value, which can be confusing.

nevisAuth now uses java.time.Instant in most places. In version 4.40.0.10 we also fixed that the database now consistently stores dates in UTC. Therefore, in most cases it will not be necessary to use any other type. This is important because the java.time API can be overwhelming.

The other major legacy Java type used was java.util.Calendar. In our case, this was not used much and even in the case where it was used didn't have any meaningful timezone related info, so those were also changed to java.time.Instant. In case you are using this type in a custom auth state, you have to decide if you can replace it to java.time.Instant or java.time.ZonedDateTime if required.

Parsing

The most common use case to use other Java types will be presented if you have a date-time infomation originating from an external system or configuration expression in a non-standard String format. Parsing can be relevant as the old AuthDateUtils which parsed many different formats is no longer available.

For parsing we have to distinguish 5 different case:

  • UTC
  • zone-offset
  • timezone
  • no zone indication
  • epoch time

Standard date formats handled by Java can be found in the official documentation, including formatting.

info

The following examples use double quotes on String literals which is suitable for Java and Groovy. For EL expressions, use single quotes. (You have to change the quotes in the examples.)

UTC

For cases where the date-time is received in a UTC format you can use:

  • The type java.time.Instant can directly parse a string when the time is in the ISO-8601 instant format, for example: 2023-04-26T09:33:40.459Z where Z notes Zulu / UTC.

    ISO-8601 parsing example
    Instant.parse("2023-04-26T09:33:40.459Z")
  • In case of other formats you can parse it by providing a formatter pattern. It is important to use the X in the pattern to recognize Z as the zone = UTC. When using 'Z' in the pattern, it causes Java to not know the zone, so it fails with an error.

    Generic parsing example
    Instant.from(DateTimeFormatter.ofPattern("yyyyMMddHHmmssX").parse("20230619105508Z"))
UTC information

It is very important that using UTC shows in the format. It is not enough to "know" it to be UTC. An indication of that is necessary in the input from the Java parsing point of view.

Zone offset

Zone offset has slightly reduced information compared to timezones. Daylight saving time (DST) causes offsets to vary during the year, also DST rules in countries can change over time. Therefore, only knowing the zone offset can actually result in a resolution of non unique timezone especially for dates in the distant future. Therefore, using the zone offset format is not recommended.

  • When the ISO-8601 instant format is used dates can be parsed directly:

    OffsetDateTime.parse("2024-03-31T01:33:40.459+02:00")
  • When using other formats you can parse it with a formatter pattern:

    OffsetDateTime.from(DateTimeFormatter.ofPattern("yyyyMMddHHmmssX").parse("20240331033340+0200"))

To convert these to instant you call the toInstant() method.

Timezone

Time zone information present in the date format.

  • When the ISO-8601 instant format is used:

    ZonedDateTime.parse("2024-03-31T03:33:40.459+02:00[Europe/Budapest]")
  • When using other formats you can parse it it with a formatter pattern:

    Parsing the output of Date.toString()
    ZonedDateTime.from(DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss zzz yyyy").parse("Wed Oct 18 11:42:43 CEST 2023"))

To convert these to instant you call the toInstant() method.

Local time

When no timezone or zone-offset information is available you have to resort to using types which are not using those information and assume the timezone manually.

  • When the ISO-8601 instant format is used parse it like this (assuming it to be UTC):

    LocalDateTime.parse("2023-06-19T10:55:08").toInstant(ZoneOffset.UTC)
  • In case of other formats assuming UTC:

    LocalDateTime.from(DateTimeFormatter.ofPattern("yyyyMMddHHmmss").parse("20230619105508")).toInstant(ZoneOffset.UTC)
  • In case of other format but in system timezone:

    LocalDateTime.from(DateTimeFormatter.ofPattern("yyyyMMddHHmmss").parse("20230619105508")).atZone(ZoneId.systemDefault()).toInstant()

Epoch seconds / millis

To:

  • Instant.now().toEpochMilli()
  • Instant.now().getEpochSecond()

From:

  • Instant.ofEpochMilli(1697631668660L)
  • Instant.ofEpochSecond(1697631681L)

Operations

The java.time.Instant type has a couple of very precise plus and minus operations with exact function names like plusSecond(). For bigger precisions like minute, hour and so on you can use the ChronoUnit constants. Though be aware that for example java.time.Instant does not support all big precisions. (for example ChronoUnit.YEARS)

  • Adding 10 seconds: Instant.now().plusSeconds(10)
  • Adding 10 days: Instant.now().plus(10, ChronoUnit.DAYS)

It is also a good practice to use the Duration class to construct any value you want to add or substract and provide that in the plus or minus function.

  • Substracting 10 days: Instant.now().minus(Duration.ofDays(10))

For more about these check the official documentation.

Immutability

It is important to mention that the java.time API classes are immutable. Therefore, when doing any operation on a variable the original variable remains untouched and the returned new value will have the changes.

Comparisons

  • Check the different between two instants: Duration.between(Instant.now(), Instant.now().plusSeconds(10))
  • Check before or after:
    • Instant.now().isBefore(Instant.now().plusSeconds(1))
    • Instant.now().plusSeconds(1).isAfter(Instant.now())

Interfacing with the legacy date API

The old date API has functions "to" and "from" to provide a gateway between the two API. For more see the official legacy documentation.