7

I need to schedule a task in my code at a fixed datetime. For that I'm using a ScheduledExecutorService with the method schedule(Runnable command, long delay, TimeUnit unit);

How can I compute this delay according to leap seconds ?

For the moment I use Duration.between() but it doesn't seem aware of leap seconds:

ZonedDateTime date1 = ZonedDateTime.of(2015, 06, 30, 23, 59, 59, 000000, ZoneOffset.UTC);
ZonedDateTime date2 = ZonedDateTime.of(2015, 07, 01, 00, 00, 00, 000000, ZoneOffset.UTC);
ZonedDateTime date3 = ZonedDateTime.of(2015, 07, 01, 00, 00, 01, 000000, ZoneOffset.UTC);
System.out.println(Duration.between(date1, date2)); // PT1S
System.out.println(Duration.between(date2, date3)); // PT1S
assylias
  • 321,522
  • 82
  • 660
  • 783
Zycho
  • 298
  • 3
  • 13
  • 1
    Is your computer's clock really accurate enough that this practically matters? – Andy Turner Nov 13 '15 at 17:39
  • I don't think that [leap seconds are supported](http://stackoverflow.com/a/16022428/1079354) by JSR-310. [Another source](https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html) – Makoto Nov 13 '15 at 17:41
  • I don't think you can get proper accounting of leap seconds - see also: http://stackoverflow.com/a/33478391/829571 – assylias Nov 13 '15 at 17:41
  • 4
    Also: writing time/date components with leading zeros is not the best idea. An int literal with a leading zero is interpreted in octal, so you can't write 08, 09 etc, and writing 012345 isn't the same as 12345. – Andy Turner Nov 13 '15 at 17:42
  • 6
    The leap seconds are [hidden from you](https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html) via [UTC-SLS](https://www.cl.cam.ac.uk/~mgk25/time/utc-sls/) smoothing. Basically, around the leap seconds, "Java seconds" are either a bit shorter or a bit longer than actual UTC seconds. `Duration` is measuring in Java time, so you won't ever see leap seconds. – dhke Nov 13 '15 at 17:44
  • 1
    It can not be supported in general ... since we don't know all the leap seconds in future ... or in distant past. It is unpredictable. You may compile and maintain a list of known leap seconds (only 36 as of today) and add them yourselves. – ZhongYu Nov 13 '15 at 17:57
  • @dhke Well, your statement is based on the spec of JSR-310 (especially in the javadoc of classes `Instant` and `Clock`). One correction though: UTC-SLS smoothing is here only theoretical. It does not happen in practice! Why? Because the JDK does not contain any leap second table so smoothing is simply not possible. Furthermore: Smoothing is not even mandated if you carefully read the spec. The spec only says, you shall interprete `Instant` as being UTC-SLS, but if you convert any `java.util.Date` or any kind of timestamps (defined usually in POSIX-style) then no smoothing (conversion is 1:1)! – Meno Hochschild Jan 15 '16 at 14:52
  • @dhke My previous comment might appear as splitting a tiny hair. However, if you have timestamps with literal values like "2015-06-30T23:59:59" (interpreted as POSIX like most people do) and create/convert to `Instant` the result in term of calculated duration is just the same like in case of UTC-SLS-smoothing - unaware of leap seconds (not counted). – Meno Hochschild Jan 15 '16 at 14:56
  • @MenoHochschild AFAIR timekeeping requires to hair splitting correctness, because the subtle details are probably done wrong everywhere. Do you have a link with more information about the JDK leap second handling? Because tzdata (which is used by the JDK) does contain leap second information. – dhke Jan 15 '16 at 15:12
  • 1
    @dhke Here an offical confirmation for the fact that the JDK does NOT contain any leap second information: https://github.com/threeten/threeten/issues/197 Yes, the original tzdb hosted by IANA contains such infos but not the distribution of Oracle or OpenJDK when they extract/evaluate the tzdb into their own format. – Meno Hochschild Jan 15 '16 at 15:19

3 Answers3

5

To support leap seconds you need the ThreeTen-Extra extension jar file. It has dedicated classes for UTC and TAI plus the table of leap seconds.

To calculate the duration, use the dedicated durationUntil() method.

JodaStephen
  • 60,927
  • 15
  • 95
  • 117
  • This is an intresting answer. But I already use threetenbp (the project is in Java 7, cannot be migrated on java8), and Threetenbp-extra seems inactive and not supported :( – Zycho Nov 14 '15 at 07:16
2

Using my library Time4J (v3.x works on Java 7 - or Time4A on Android) will give you a complete solution including extensive support for formatting and parsing in different timezones. Examples:

// Time4J - transformation from old world
Moment m1 = TemporalType.JAVA_UTIL_DATE.translate(d1);
Moment m2 = TemporalType.JAVA_UTIL_DATE.translate(d2);
System.out.println("Time4J - from j.u.Date: " + SI.SECONDS.between(m1, m2)); // 601

// Time4J - programmatical construction of timestamps (like in your example)
m1 = PlainTimestamp.of(2012, 6, 30, 23, 50, 0).atUTC();
m2 = PlainTimestamp.of(2012, 7, 1, 0, 0, 0).atUTC();
System.out.println("Time4J - from utc-timestamps: " + SI.SECONDS.between(m1, m2)); // 601

// Time4J - parsing zoned inputs
ChronoFormatter<Moment> cf =
    ChronoFormatter.ofMomentPattern(
      "d. MMM uuuu HH:mm:ss[XXX]", // optional offset
      PatternType.CLDR, 
      Locale.ENGLISH, 
      EUROPE.PARIS); // only used if the offset is missing in input
m1 = cf.parse("1. Jul 2015 01:50:00");
m2 = cf.parse("1. Jul 2015 09:00:00+09:00");
System.out.println("Time4J - from offsets or zones: " + SI.SECONDS.between(m1, m2)); // 601

// the leap second itself can also be parsed
Moment ls = cf.parse("1. Jul 2015 08:59:60+09:00"); // offset of Tokyo
System.out.println("leap second in 2015: " + ls);
// 2015-06-30T23:59:60Z

There is even more, namely support for clocks being aware of leap seconds and transfer of leap second data from IANA-TZDB. Examples see my article on DZone. Note that Time4J can fully replace ThreetenBP, but is also interoperable with Java-8 (just change the version to v4.x) and offers many more features which are out of scope of the question here.


Side note: Most people obviously choose ThreetenBP in order to make a future migration to Java-8 easier (just changing some import statements). But the problem in your case is that

  • Java-standard (any version) itself does not know anything about leap seconds (not even the necessary data).

  • The proposed other 3rd-party library Threeten-Extra is not working on Java-7. It requires Java-8.

After migration to Java-8, Threeten-Extra seems to present a solution. However, I have now done my own tests and would advise against using that library, see following code:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
Date d1 = sdf.parse("2012-06-30T23:50:00Z");
Date d2 = sdf.parse("2012-07-01T00:00:00Z");
System.out.println(
  "Old world in Posix - ignorant of leap seconds: " 
  + (d2.getTime() - d1.getTime()) / 1000);
// 600 (OK, SI-seconds without counting leap second, a limitation of POSIX)

Instant instant1 = d1.toInstant();
Instant instant2 = d2.toInstant();
System.out.println(
  "Java 8 without Threeten-Extra: " 
  + (instant2.getEpochSecond() - instant1.getEpochSecond()));
// 600 (obviously using more or less fuzzy "rubber seconds", not SI-seconds)
// internally a simple 1:1-mapping of POSIX is applied within 'toInstant()'

UtcInstant utc1 = UtcInstant.of(instant1);
UtcInstant utc2 = UtcInstant.of(instant2);
System.out.println(
  "Threeten-Extra-impl of UTC-SLS: " 
  + utc1.durationUntil(utc2).getSeconds()); // pitfall, see next output!
// 600 (??? - where is the leap second, should be 601 because of original POSIX-input?!)
System.out.println(
  "Threeten-Extra-impl of UTC-SLS: " 
  + utc1.durationUntil(utc2)); // <= printing the duration object
// PT10M0.600600601S (OK, here the UTC-SLS-specific fraction of second appears
// Reason: Smoothing starts 1000s before leap second)

// only offset "Z=UTC+00:00" can be parsed
utc1 = UtcInstant.parse("2012-06-30T23:50:00Z");
utc2 = UtcInstant.parse("2012-07-01T00:00:00Z");
System.out.println(
  "Threeten-Extra-impl of UTC-SLS: " + utc1.durationUntil(utc2).getSeconds());
// 601 (expected, the only way where seconds are really SI-seconds)

Leaving aside some obvious limitations in formatting and parsing, the main problem seems to be the confusing handling of time scale definitions. The definition of a second depends here on the context. For example the result "PT10M0.600600601S" is okay if you only consider the transformation using UTC-SLS, but not okay if you consider the whole transformation from POSIX via UTC-SLS to UTC. And as said before, POSIX time is well defined 10 minutes before leap second so any fuzzyness of POSIX during the leap second is no excuse.

Keep in mind that really NOBODY outside of java.time-world talks about UTC-SLS which has been an (officially expired) proposal of Markus Kuhn addressing the internal implementation of NTP time servers or OS-kernels. Instead other people and enterprises like Google introduce their own different leap smear implementations. I don't see any future for UTC-SLS.

And since java.util.Date has nothing to do with UTC-SLS (is much older) but is also well defined for normal timestamps (following UTC-definition of Si-second with the limitation of the leap seconds which are not handled at all), we see here an interoperability problem between outer IT-world (which does not want to know anything about leap seconds or UTC-SLS) and Threeten-Extra which can cause unexpected differences in calculated durations.

Community
  • 1
  • 1
Meno Hochschild
  • 42,708
  • 7
  • 104
  • 126
-2

Consider using Quartz with CronTrigger

jnr
  • 790
  • 1
  • 7
  • 9
  • Using Quartz does not add anything in term of leap second ... Or if it does, you need to provide a bit more context as what it brings... – Guillaume Nov 13 '15 at 20:13