kapex
kapex

Reputation: 29999

javax.xml.datatype.Duration to nanoseconds

I have a javax.xml.datatype.Duration that contains a fraction of millisecond, e.g. a duration of 1.5ms (1500 microseconds):

Duration duration = DatatypeFactory.newInstance().newDuration("PT0.0015S");

I need to get this value in nanoseconds but the smallest unit of time that Duration is providing seems to be getTimeInMillis, which returns a long and cuts of anything smaller than a millisecond.

How do I get the duration in nanoseconds?

Upvotes: 1

Views: 1043

Answers (2)

hasnae
hasnae

Reputation: 2183

You can parse the duration to java.time.Duration. Then call toNanos() method.

Duration duration = DatatypeFactory.newInstance().newDuration("PT0.0015S");
java.time.Duration.parse(duration.toString()).toNanos();

Upvotes: 3

kapex
kapex

Reputation: 29999

The Duration API was slightly surprising to me here because you have to get the seconds part of the duration with Duration.getField(Field). That method returns a Number, which is either a BigInteger (for days, hours, minutes. etc) or a BigDecimal (only for seconds).

So to get fractions of a second you can use getField(DatatypeConstants.SECONDS) and then convert the value:

Duration duration = DatatypeFactory.newInstance().newDuration("PT0.0015S");
BigDecimal seconds = (BigDecimal) duration.getField(DatatypeConstants.SECONDS);
// Note that `getField` will return `null` if the field is not defined.
if(seconds != null) {
    System.out.println(seconds + "s");                    // 0.0015s
    System.out.println(seconds.movePointRight(9) + "ns"); // 1500000ns
}

But these are not the total seconds of the duration. This is only the value of the seconds field and other fields are ignored. For a duration like P1M0.0015 (1 minute and 1.5 milliseconds) this would ignore the minute and would just return 1.5ms.

Converting all other fields of the duration to seconds and summing them up would work. Or by using getTimeInMillis, which returns the total milliseconds of the duration:

Duration duration = DatatypeFactory.newInstance().newDuration("PT1M0.0015S");
BigDecimal seconds = (BigDecimal) duration.getField(DatatypeConstants.SECONDS);

// only keep the fractional part
BigDecimal fractionalSeconds = seconds.remainder(BigDecimal.ONE);

long totalMillis = duration.getTimeInMillis(new Date(0));
// convert total millis to whole seconds (removes fractional part)
BigDecimal totalIntegerSeconds = 
    new BigDecimal(totalMillis).movePointLeft(3).setScale(0, RoundingMode.FLOOR);

// add both to get total seconds 
BigDecimal totalSeconds = totalIntegerSeconds.add(fractionalSeconds);

System.out.println(totalSeconds + "s");                     // 60.0015s
System.out.println(totalSeconds.movePointRight(9) + "ns");  // 60001500000ns

This seems to work but is way too complicated. There must be an easier way to do this.

@hasnae's answer solution looks much better if you are using Java 1.8. I wonder what the API designer's intended way to get nanosecond precision was though, since java.time.Duration was introduced years later than javax.xml.datatype.Duration.

Upvotes: 1

Related Questions