17

Please include the nanos, otherwise it would be trivial:

long diff = Math.abs(t1.getTime () - t2.getTime ());

[EDIT] I want the most precise result, so no doubles; only integer/long arithmetic. Also, the result must be positive. Pseudo code:

Timestamp result = abs (t1 - t2);

Examples:

t1 = (time=1001, nanos=1000000), t2 = (time=999, nanos=999000000)
 -> diff = (time=2, nanos=2000000)

Yes, milliseconds in java.sql.Timestamp are duplicated in the time and the nanos par, so 1001 milliseconds means 1 second (1000) and 1 milli which is in the time part and the nanos part because 1 millisecond = 1000000 nanoseconds). This is much more devious than it looks.

I suggest not to post an answer without actually testing the code or having a working code sample ready :)

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • So do you have a solution that you're going to post if no one gets it? – Michael Myers Feb 24 '09 at 16:23
  • 1
    According to the comments in the sourcecode for Timestamp, the millis part is not actually duplicated in the nanos. Instead, just upto the seconds are stored in the super class of java.lang.Date, and the nanos provides all the rest of the time. – Richard Feb 24 '09 at 16:34
  • @mmyers: I was working on one but I hoped that someone had a working example saving my poor brain ... but I should have guessed that no one has when Google didn't turn anything up but t1.getTime()-t2.getTime() – Aaron Digulla Feb 24 '09 at 16:46
  • @Richard: Yes. The devious thing is that they do the right thing *internally*. If you call the API, it will mix the millis into both fields; see my unit tests how weird that looks. – Aaron Digulla Feb 24 '09 at 16:47
  • Damn! this crap took me ONE HOUR! =8*O – Aaron Digulla Feb 24 '09 at 16:51
  • Interesting - looking at the code for JDK1.4, JDK1.5 and JDK1.6 versions of Timestamp it very definitely rounds to seconds for the superclass of Date. It is also explicitly mentioned in the javadocs. Perhaps alternative Java implementations have it different? – Richard Feb 24 '09 at 16:59
  • @Richard: No. You must look at the constructor *and* getTime(). In the constructor, it rounds to seconds and it "undos" that in getTime(). So internally, "time % 1000 == 0" but when you call getTime(), it adds the millis part of the nanos field, so outside, you get a mix. – Aaron Digulla Feb 25 '09 at 08:14

5 Answers5

11

After one hour and various unit tests, I came up with this solution:

public static Timestamp diff (java.util.Date t1, java.util.Date t2)
{
    // Make sure the result is always > 0
    if (t1.compareTo (t2) < 0)
    {
        java.util.Date tmp = t1;
        t1 = t2;
        t2 = tmp;
    }

    // Timestamps mix milli and nanoseconds in the API, so we have to separate the two
    long diffSeconds = (t1.getTime () / 1000) - (t2.getTime () / 1000);
    // For normals dates, we have millisecond precision
    int nano1 = ((int) t1.getTime () % 1000) * 1000000;
    // If the parameter is a Timestamp, we have additional precision in nanoseconds
    if (t1 instanceof Timestamp)
        nano1 = ((Timestamp)t1).getNanos ();
    int nano2 = ((int) t2.getTime () % 1000) * 1000000;
    if (t2 instanceof Timestamp)
        nano2 = ((Timestamp)t2).getNanos ();

    int diffNanos = nano1 - nano2;
    if (diffNanos < 0)
    {
        // Borrow one second
        diffSeconds --;
        diffNanos += 1000000000;
    }

    // mix nanos and millis again
    Timestamp result = new Timestamp ((diffSeconds * 1000) + (diffNanos / 1000000));
    // setNanos() with a value of in the millisecond range doesn't affect the value of the time field
    // while milliseconds in the time field will modify nanos! Damn, this API is a *mess*
    result.setNanos (diffNanos);
    return result;
}

Unit tests:

    Timestamp t1 = new Timestamp (0);
    Timestamp t3 = new Timestamp (999);
    Timestamp t4 = new Timestamp (5001);
    // Careful here; internally, Java has set nanos already!
    t4.setNanos (t4.getNanos () + 1);

    // Show what a mess this API is...
    // Yes, the milliseconds show up in *both* fields! Isn't that fun?
    assertEquals (999, t3.getTime ());
    assertEquals (999000000, t3.getNanos ());
    // This looks weird but t4 contains 5 seconds, 1 milli, 1 nano.
    // The lone milli is in both results ...
    assertEquals (5001, t4.getTime ());
    assertEquals (1000001, t4.getNanos ());

    diff = DBUtil.diff (t1, t4);
    assertEquals (5001, diff.getTime ());
    assertEquals (1000001, diff.getNanos ());

    diff = DBUtil.diff (t4, t3);
    assertEquals (4002, diff.getTime ());
    assertEquals (2000001, diff.getNanos ());
Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • After much trying, I can't get the BigInteger version of my code to be less than three times slower than your code. This deserves an upvote. – Michael Myers Feb 26 '09 at 22:36
  • Thanks :) The advantage of the BigInt code is that it's a single line (and more easy to understand). That's why I gave it a +1. – Aaron Digulla Feb 27 '09 at 09:03
7

I use this method to get difference between 2 java.sql.Timestmap

/**
 * Get a diff between two timestamps.
 *
 * @param oldTs The older timestamp
 * @param newTs The newer timestamp
 * @param timeUnit The unit in which you want the diff
 * @return The diff value, in the provided time unit.
 */
public static long getDateDiff(Timestamp oldTs, Timestamp newTs, TimeUnit timeUnit) {
    long diffInMS = newTs.getTime() - oldTs.getTime();
    return timeUnit.convert(diffInMS, TimeUnit.MILLISECONDS);
}

// Examples:
// long diffMinutes = getDateDiff(oldTs, newTs, TimeUnit.MINUTES);
// long diffHours = getDateDiff(oldTs, newTs, TimeUnit.HOURS);
AechoLiu
  • 17,522
  • 9
  • 100
  • 118
3

In what units? your diff above will give milliseconds, Timestamp.nanos() returns an int, which would be in (millionths?) of a millisecond.So do you mean e.g.

(t1.getTime () + (.000001*t1.getNanos()) - (t2.getTime () + (.000001*t2.getNanos())

or am I missing something? Another question is do you need this level of precision? AFAIK the JVM isn't guaranteed to be precise at this level, I don't think it'd matter unless you're sure your datasource is that precise.

Steve B.
  • 55,454
  • 12
  • 93
  • 132
1

Building on mmyers code...

import java.math.BigInteger;
import java.sql.Timestamp;


public class Main
{
    // 1s == 1000ms == 1,000,000us == 1,000,000,000ns (1 billion ns)
    public final static BigInteger ONE_BILLION = new BigInteger ("1000000000");
    public static void main(String[] args) throws InterruptedException 
    {
        final Timestamp t1;
        final Timestamp t2;
        final BigInteger firstTime;
        final BigInteger secondTime;
        final BigInteger diffTime;

        t1 = new Timestamp(System.currentTimeMillis());
        Thread.sleep(20);
        t2 = new Timestamp(System.currentTimeMillis());

        System.out.println(t1);
        System.out.println(t2);
        firstTime  = BigInteger.valueOf(t1.getTime() / 1000 * 1000).multiply(ONE_BILLION ).add(BigInteger.valueOf(t1.getNanos()));
        secondTime = BigInteger.valueOf(t2.getTime() / 1000 * 1000).multiply(ONE_BILLION ).add(BigInteger.valueOf(t2.getNanos()));
        diffTime   = firstTime.subtract(secondTime);
        System.out.println(firstTime);
        System.out.println(secondTime);
        System.out.println(diffTime);
    }
}
Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
TofuBeer
  • 60,850
  • 18
  • 118
  • 163
  • +1: Probably not the fastest possible solution but definitely the most simple to code. Try to run the code with my unit tests, though. I think it will mess up the milliseconds as soon as nanos % 1000000 != 0. – Aaron Digulla Feb 24 '09 at 16:50
  • Performance hint: This takes about three times as long to run as my solution but roughly 1/6 of the time to code. – Aaron Digulla Feb 25 '09 at 08:36
0

(old code removed to shorten answer)

EDIT 2: New code:

public class ArraySizeTest {
    public static void main(String[] args) throws InterruptedException {
        Timestamp t1 = new Timestamp(System.currentTimeMillis());
        t1.setNanos(t1.getNanos() + 60);
        Thread.sleep(20);
        Timestamp t2 = new Timestamp(System.currentTimeMillis());
        t2.setNanos(t2.getNanos() + 30);
        System.out.println(t1);
        System.out.println(t2);
        // The actual diff...
        long firstTime = (getTimeNoMillis(t1) * 1000000) + t1.getNanos();
        long secondTime = (getTimeNoMillis(t2) * 1000000) + t2.getNanos();
        long diff = Math.abs(firstTime - secondTime); // diff is in nanos
        System.out.println(diff);
        System.out.println(Math.abs(t1.getTime() - t2.getTime()));
    }
    private static long getTimeNoMillis(Timestamp t) {
        return t.getTime() - (t.getNanos()/1000000);
    }
}

Output:

2009-02-24 10:35:15.56500006
2009-02-24 10:35:15.59600003
30999970
31

Edit 3: If you'd prefer something that returns a Timestamp, use this:

public static Timestamp diff(Timestamp t1, Timestamp t2) {
    long firstTime = (getTimeNoMillis(t1) * 1000000) + t1.getNanos();
    long secondTime = (getTimeNoMillis(t2) * 1000000) + t2.getNanos();
    long diff = Math.abs(firstTime - secondTime); // diff is in nanoseconds
    Timestamp ret = new Timestamp(diff / 1000000);
    ret.setNanos((int) (diff % 1000000000));
    return ret;
}
private static long getTimeNoMillis(Timestamp t) {
    return t.getTime() - (t.getNanos()/1000000);
}

This code passes your unit tests.

Michael Myers
  • 188,989
  • 46
  • 291
  • 292