Appendix J - 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 intotimestamp
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 exampleDateTimeZone
,DateTime
). AuthDateUtils
is removed.- nevisAuth API changes.
- Most of the
java.time
classes mentioned below are automatically imported.
- Usage of
- 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 ofjava.util.Date
. - Use UTC everywhere unless required otherwise.
- Use standardized Date formats to make life easy.
- Use
java.time.Duration
instead oflong
for time difference and usejava.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.
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
whereZ
notes Zulu / UTC.ISO-8601 parsing exampleInstant.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 exampleInstant.from(DateTimeFormatter.ofPattern("yyyyMMddHHmmssX").parse("20230619105508Z"))
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.