7

Is there any fast way to create DateTime instance and set minutes\seconds\millis to 0? At this moment I am using the following code:

private DateTime createDateTime(java.util.Date date, org.joda.time.Chronology chronology) {
    DateTime dateTime = new DateTime(date, chronology);
    dateTime = dateTime.withMinuteOfHour(0);
    dateTime = dateTime.withSecondOfMinute(0);
    dateTime = dateTime.withMillisOfSecond(0);
    return dateTime;
}

But when it invokes about 200.000 times, dateTime.with***(0); takes a lot of time. Probably there is more correct solution?

Slam
  • 177
  • 1
  • 8

5 Answers5

8

Maybe like this?

// Truncate minutes/seconds/milliseconds from the date's timestamp
long truncatedTimestamp = date.getTime() - date.getTime() % 3600000;
DateTime dateTime = new DateTime(truncatedTimestamp, chronology);    

Why is this faster?

  • My solution uses fast integer arithmetic (negligible) and 1 unix timestamp normalisation (expensive) in the DateTime constructor
  • Your solution uses 1 unix timestamp normalisation (expensive) in the DateTime constructor and 3 more normalisations (expensive), every time you set some date part to 0
  • Other solutions may need less lines of code, but when you look at JodaTime sources, they require even more than 3 normalisations (expensive)

Hence, you probably can't beat modulo. As others pointed out, this might lead to incorrect results in very remote corner-cases where hours don't count 60 seconds (e.g. due to leap seconds), although I fail to see how, as the unix timestamp can always be truncated to zero, to get the beginning of a calendar hour (examples welcome).

Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
  • There aren't a fixed number of seconds in a minute, so on some occasions this would be wrong. – Dunes Apr 03 '12 at 11:27
  • Is arithmetic, especially Modulo calculation faster than 3 set method? Also, date.getTime(), u did that twice, so 2 getters and a calculation, is that really faster than 3 setters? And since he is settting hours to 0, shouldn't it be also, 3600000*24 – Churk Apr 03 '12 at 11:28
  • @Churk: Don't worry about these minor things. What's expensive in the `with` method is the complete date-time arithmetic. Note, the OP is not setting hours to `0` – Lukas Eder Apr 03 '12 at 11:29
  • @Dunes: True in some remote cases. But if that has to be taken into account, then it's hard to speed things up massively... – Lukas Eder Apr 03 '12 at 11:30
  • @LukasEder Why are u asking me this question when I didn't say truncating minutes would leads to wrong results. I pointed out that you need to do this for the hours also since the OP wanted a date, not date with hours. – Churk Apr 05 '12 at 10:39
  • @Dunes: Actually, can you provide an example where truncating minutes on a unix timestamp leads to wrong results? I fail to see how this can happen... Leap seconds are "stolen" or "added" by removing, repeating some unix timestamps in the "time-continuum" – Lukas Eder Apr 05 '12 at 11:21
  • Sorry @Churk. I meant to ask Dunes. Still, the OP is not setting hours to `0`, only minutes... – Lukas Eder Apr 05 '12 at 11:22
  • Nevermind, I made a mistake. The unix epoch does not represent leap seconds, so the problem would never occur. – Dunes Apr 05 '12 at 12:00
5

Just tried the code below - it looks like method 1 (yours) takes about 320ms on my pc, vs method 2 (mine) 390ms, vs method 3 (Lukas's) 15ms, vs method 4 (MutableDateTime) 310ms... Now the modulo might (?) lead to incorrect results.

public class Test {

    private static int NUM_RUN;

    public static void main(String[] args) {

        Date date = new Date();
        List<Runnable> list = new ArrayList<>();

        list.add(method3Withs(date));
        list.add(method1With(date));
        list.add(methodModulo(date));
        list.add(methodMutable(date));

        NUM_RUN = 100_000;
        for (Runnable r : list) {
            long start = System.nanoTime();
            r.run();
            long end = System.nanoTime();
            System.out.println((end - start) / 1000000);
        }

        NUM_RUN = 10_000_000;
        for (Runnable r : list) {
            long start = System.nanoTime();
            r.run();
            long end = System.nanoTime();
            System.out.println((end - start) / 1000000);
        }
    }

    private static Runnable method3Withs(final Date date) {
        return new Runnable() {

            @Override
            public void run() {
                DateTime d2 = null;
                for (int i = 0; i < NUM_RUN; i++) {
                    d2 = new DateTime(date);
                    d2 = d2.withMinuteOfHour(0);
                    d2 = d2.withSecondOfMinute(0);
                    d2 = d2.withMillisOfSecond(0);
                }
                System.out.println(d2);
            }
        };
    }

    private static Runnable method1With(final Date date) {
        return new Runnable() {

            @Override
            public void run() {
                DateTime d2 = null;
                for (int i = 0; i < NUM_RUN; i++) {
                    d2 = new DateTime(date);
                    d2 = d2.withTime(d2.getHourOfDay(), 0, 0, 0);
                }
                System.out.println(d2);
            }
        };
    }
    private static Runnable methodModulo(final Date date) {
        return new Runnable() {

            @Override
            public void run() {
                DateTime d2 = null;
                for (int i = 0; i < NUM_RUN; i++) {
                    long truncatedTimestamp = date.getTime() - date.getTime() % 3600000;
                    d2 = new DateTime(truncatedTimestamp);
                }
                System.out.println(d2);
            }
        };
    }

    private static Runnable methodMutable(final Date date) {
        return new Runnable() {

            @Override
            public void run() {
                MutableDateTime m = null;
                for (int i = 0; i < NUM_RUN; i++) {
                    m = new MutableDateTime(date);
                    m.setMinuteOfHour(0);
                    m.setSecondOfMinute(0);
                    m.setMillisOfSecond(0);
                }
                System.out.println(m);
            }
        };
    }
}

EDIT
I made it 10 million runs after a warm up round of 100,000:

3037
4068
88
2864

The modulo method wins by a large margin, so it seems safe to think it will perform much better in most situations.

assylias
  • 321,522
  • 82
  • 660
  • 783
3

Try creating a MutableDateTime: http://joda-time.sourceforge.net/userguide.html#Using_a_MutableDateTime, then call toDateTime() on it

artbristol
  • 32,010
  • 5
  • 70
  • 103
3

The method dateTime.hourOfDay().roundFloorCopy() should round the minute, second and milli to zero in a single method call (and is the recommended way if performance is not an issue). However, it is unlikely to be faster than calling modulo on the millis directly.

JodaStephen
  • 60,927
  • 15
  • 95
  • 117
1

How about using the withTime() method of class DateTime, so that you only have to do one call?

private DateTime createDateTime(java.util.Date date, org.joda.time.Chronology chronology) {
    DateTime dateTime = new DateTime(date, chronology);
    return dateTime.withTime(dateTime.getHourOfDay(), 0, 0, 0);
}
Jesper
  • 202,709
  • 46
  • 318
  • 350
  • @assylias Ah yes, I see that's one of your test cases. That's really strange, especially if you look at the source code of Joda Time to see what exactly all those `with...()` methods do. – Jesper Apr 03 '12 at 11:56
  • Or maybe due to the way I test it which is a bit simplistic! – assylias Apr 03 '12 at 11:57
  • It's quite intuitive. Check out the sources: https://github.com/JodaOrg/joda-time/blob/master/src/main/java/org/joda/time/DateTime.java#L736. This runs two date time manipulation operations more than the OP's code – Lukas Eder Apr 03 '12 at 12:01