views:

9011

answers:

4

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 :)

+2  A: 

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.
Missing two ))'s at the end.
Michael Myers
I'd like an exact result, please.
Aaron Digulla
Thanks for the effort; I've posted an exact solution but boy, that API is a *mess*
Aaron Digulla
If you have milliseconds in the timestamp, your result will be wrong plus 1s == 1,000,000,000ns (10^9).
Aaron Digulla
A: 

(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
To convert to nanoseconds you need to multiply by 1000000. The question is whether you will hit a long overflow at some point.
Richard
@Richard: Yes and yes, it seems.
Michael Myers
Just saw the edit to the question. Ouch. :P
Michael Myers
@mmyers: Stuff like that ought to be more simple ... ;) See my solution.
Aaron Digulla
Umm, it is pretty simple. What looks complicated?
Michael Myers
Something else found (from the javadoc) that could be a nasty gotcha if you really need to work with nanos: "the hashcode method uses the underlying java.util.Date implementation and therefore does not include nanos in its computation."
Steve B.
@SteveB: this just gets better by the minute ;)
Aaron Digulla
@mmyers: Your code will overflow 2262-04-12 01:47:16.854 :) Seeing how long COBOL is around, I fear that Java might still be around then ... I think we really have to use either BigInteger or my solution.
Aaron Digulla
Then you should have better unit tests. ;)
Michael Myers
+1  A: 

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
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
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
+1  A: 

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);
    }
}
TofuBeer
+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
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