Working with time scales

Table Of Contents

Areas of application
Which classes use time scales?
Value space and lexical space
Can TAI replace UTC?
TAI: How to format or parse a timestamp?
TAI: How to interprete clocks?
TAI: How to store a timestamp?

Areas of application

  1. UTC is the civil legal standard scale including leap seconds. However, Time4J handles all times before 1972 as UT1 (mean solar time).
  2. POSIX is a wide-spread scale mostly used in IT-industry. It is almost identical to UTC with the exception of leap seconds.
  3. Scientific laboratories sometimes use TAI, TT etc. Astronomical calculations need a bridge between civil time (UTC) and other scales.
  4. GPS is similar to TAI but uses a different epoch and might be useable on some mobile devices.
  5. There are also some technical standards and proposals which mandate TAI or smeared leap seconds with the motivation to completely avoid the special leap second value 60.

The currently supported time scales can be looked up in the enum net.time4j.scale.TimeScale.

Which classes use time scales?

All implementations of the interface UniversalTime support time scales. The most important implementation is the class Moment. Its internal state reflects pure UTC. Various methods exist to convert values and representations from or to other scales. The support for leap seconds and time scales can also be switched off by setting a special system property.

The local types PlainTime and PlainTimestamp do not model leap seconds and time scales and can therefore not realize UTC which requires knowledge of time zone or offset. So a simple conversion from Moment to these local types might be lossy. Users who wish to work with time scales should take care to avoid those local types and always work with objects of type Moment.

Value space and lexical space

When many users think of time scale support in Java they are often accustomed to think of moments only in value space. That means they interprete moments as elapsed time since an epoch. Traditionally just as elapsed milliseconds since UNIX epoch. Regarding this background, they often misinterprete the phrase of discontinuity of UTC-scale. Concretely, they think the insertion of leap seconds happens in the value space. This would mean, the value space shows either gaps or duplicated numbers as it usually happens in POSIX. But this view contradicts the character of UTC being an atomic scale counting SI-seconds which have constant length. Every SI-second coming from an atomic clock must be counted (equal if normal second or leap second) and cannot just be left out. This is the same behaviour as on TAI-scale. So the value space of UTC forms continuous numbers.

So where to find the discontinuity of UTC? It happens in the lexical space. A leap second shows up with the special label "60" in timestamp representations. No other scale has this behaviour. That also means: If we partition minutes into seconds then those minutes having leap seconds have a non-standard-length, either 61 or 59 (in case of hypothetical negative leap seconds). By the way, the concept of minutes, hours etc. only exist in lexical space. It has no meaning in the value space which only counts seconds.

Behaviour of class Moment for some time scales around a leap second event
time scale epoch
Moment.of(0, scale)
value space
getElapsedTime(scale)
lexical space
toString(scale)
POSIX *) 1970-01-01T00:00:00.000Z 1483228799
1483228799
1483228800
2016-12-31T23:59:59.000Z
2016-12-31T23:59:59.000Z
2017-01-01T00:00:00.000Z
UTC 1972-01-01T00:00:00.000Z 1420156825
1420156826
1420156827
2016-12-31T23:59:59.000Z
2016-12-31T23:59:60.000Z
2017-01-01T00:00:00.000Z
TAI 1971-12-31T23:59:50.000Z **) 1420156835
1420156836
1420156837
2017-01-01T00:00:35.000Z
2017-01-01T00:00:36.000Z
2017-01-01T00:00:37.000Z
GPS 1980-01-06T00:00:00.000Z 1167264016
1167264017
1167264018
2017-01-01T00:00:16.000Z
2017-01-01T00:00:17.000Z
2017-01-01T00:00:18.000Z

*) POSIX is always discontinous around a leap second because latter one is not supported at all. Officially POSIX does not define the exact behaviour. Time4J models a positive leap second just as repeated timestamp at the end of day. Other practical workarounds might be possible and valid (like freezing clock or repeating one second later) but are not modelled for sake of simplicity.

**) This value is only virtual since TAI is really supported first at UTC epoch (10 seconds later). The choice of TAI-epoch is somehow arbitrary but fits best the original relationship in year 1972 (the official introduction of UTC in broadcast signals), namely TAI = UTC + 10. Note that the delta between TAI and UTC increases in the lexical space with every positive leap second.

Can TAI replace UTC?

There are only two reasons why UTC might be replaced by TAI in your app. First reason is the fear that some external interfaces cannot process timestamps which contain the special second-value 60. However, within the context of Time4J only, leap seconds do not present any specific problem as long as you work with class Moment only, not even in any kind of moment-related manipulation. Second reason is if your app is supposed to follow a specific standard (like ETSI) which mandates the usage of TAI. But then you have to cope with the fact that TAI-timestamps deviate from UTC by 37 seconds in year 2017 (and this delta is growing in the future). This might be an interoperability problem with the external world, too.

TAI: How to format or parse a timestamp?

The simplest possibility for formatting was until version v3.27/4.23 only using the moment.toString(scale). Parsing was not supported at all. However, since version v3.28/4.24, this gap has been closed by introducing the new format attribute Attributes.TIME_SCALE. This enables using the expert format engine of Time4J. So you can construct any kind of ChronoFormatter as usual and finally apply the new format attribute. Example:

           assertThat(
             ChronoFormatter.ofMomentPattern(
               "uuuu-MM-dd'T'HH:mm:ssX",
               PatternType.CLDR,
               Locale.ROOT,
               ZonalOffset.UTC)
             .with(Attributes.TIME_SCALE, TimeScale.TAI)
             .parse("2012-07-01T00:00:34Z"),
             is(PlainTimestamp.of(2012, 1, 1, 0, 0).atUTC().with(Moment.nextLeapSecond()))); 
             // UTC=2012-06-30T23:59:60Z
             // 35 seconds delta between UTC and TAI

This example imitates a TAI-timestamp in an ISO-like notation. However, users can apply any kind of format patterns or time zones. The same can be said for printing moments to TAI. Just apply the expression with(Attributes.TIME_SCALE, TimeScale.TAI) on your formatter before printing or parsing.

TAI: How to interprete clocks?

Here you don't need to take any special action after you have got an object of type Moment from your clock. But: You might reconsider your clock choice.

Most OS-related clocks ignore leap seconds and usually apply any kind of clock reset or even smearing. The POSIX-scale of Time4J handles this somehow (with limitations in precision). As long as you are sufficiently far away from a leap second event, it is okay to use such a clock.

For higher precision requirements, you might also consider SystemClock.MONOTONIC or alternative clocks. The misc-module of Time4J offers some alternatives. For example a configurable SNTP-client (new SntpConnector("ptbtime1.ptb.de") - NTP-server in Germany). Such an SNTP-client would yield a monotonic time between two connecting actions. But you should not connect to network time servers during or near a leap second.

TAI: How to store a timestamp?

As long as your Moment-timestamp is no leap second, there is nothing special about storing such timestamps. Any kind of standard storing strategies might be applicable, for example net.time4j.sql.JDBCAdapter.SQL_TIMESTAMP_WITH_ZONE. Of course, if you choose a database as storage medium then you will have to take into account the possible precision of the database. Not every database can handle nanoseconds.

When storing leap seconds: This topic is also relevant for TAI because a Moment-object is still pure UTC. The standard database storing strategies can no longer be recommended due to risk of loosing data. Here you should also strictly avoid code which makes usage of storing a moment as a number via getPosixTime(). But you could store a (possibly leap-second-)moment in two database columns using getElapsedTime(TimeScale.TAI) and getNanosecond(). This value-space-strategy is possible because such numbers count leap seconds, too. Storage a leap second is also possible as String (into a (VAR)CHAR-column), best in a format similar to ISO-8601 (see formatting code in previous section).

Serialization: If you transfer a leap-second-moment to another JVM by serialization where the leapsecond-table is not up-to-date (old version of Time4J) then it will be handled there as one second off. If you serialize it back to a properly updated JVM or if the receiver-JVM is properly updated later then the leapsecond will be shown again. Of course, if you directly serialize TAI-timestamps in String-format then there is nothing special around leap seconds because TAI-timestamps don't contain leap-second-values (i.e. not showing the value 60).

What so ever strategy you choose, make sure that your apps are using the newest leap second tables (i.e. using the newest versions of Time4J).