0

I wrote the following test code.

SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss",Locale.JAPAN);
Date date1 = format.parse("08:00:01");
Date date2 = format.parse("23:00:05");
Date date = new Date(date2.getTime() - date1.getTime());
System.out.println(format.format(date));

But what amazes me is that the result is 00:00:04 , I cannot understand why. and if I change date2 to "08:00:01", the result will change to 09:00:00. how could this happen?

Luis
  • 77
  • 1
  • 10

4 Answers4

2

Date is a type for a time instant(時刻), not a time interval(時間).

So, if you want to caculate the time difference(time inverval) between two time instants(Date objects), you will get the time difference in another type, such as long(in seconds) or type in the external library.

Refer to http://www.mkyong.com/java/how-to-calculate-date-time-difference-in-java/

The first code does not use an external library, but you have to convert milliseconds into seconds, minutes, hours, and so on.

Here is the important part:

Date d1 = ...;
Date d2 = ...;

//in milliseconds
long diff = d2.getTime() - d1.getTime();

long diffSeconds = diff / 1000 % 60;
long diffMinutes = diff / (60 * 1000) % 60;
long diffHours = diff / (60 * 60 * 1000) % 24;
long diffDays = diff / (24 * 60 * 60 * 1000);

System.out.print(diffDays + " days, ");
System.out.print(diffHours + " hours, ");
System.out.print(diffMinutes + " minutes, ");
System.out.print(diffSeconds + " seconds.");

The second code does use an external library, Joda-Time, which is one of the most famous Java library for Time.

Here is the important part:

DateTime dt1 = new DateTime(d1);
DateTime dt2 = new DateTime(d2);

System.out.print(Days.daysBetween(dt1, dt2).getDays() + " days, ");
System.out.print(Hours.hoursBetween(dt1, dt2).getHours() % 24 + " hours, ");
System.out.print(Minutes.minutesBetween(dt1, dt2).getMinutes() % 60 + " minutes, ");
System.out.print(Seconds.secondsBetween(dt1, dt2).getSeconds() % 60 + " seconds.");

If you want to use Joda-Time library, you can also use another type for time invervals, Interval: http://joda-time.sourceforge.net/key_interval.html


EDIT: The Date class uses internal long variable to represent the time instant.

And the long value is the time difference in milliseconds between some time instant,
namely January 1, 1970, 00:00:00 GMT (which is specified in http://docs.oracle.com/javase/7/docs/api/java/util/Date.html),
which is January 1, 1970, 09:00:00 in Japan Standard Time because JST (UTC +09:00).

So, 23:00:05 - 08:00:01 will be 15:00:04, and will be converted into Japan Standard Time (UTC +09:00) = 24:00:04 (January 1, 1970), i.e 00:00:04 (January 2, 1970).

If you calculate 08:00:01 - 08:00:01, then it will be 1970-01-01 00:00:00 in UTC, which is 1970-01-01 09:00:00 in the Japan Standard time.


EDIT2: I made some mistake. I didn't use the Japan Standard Time when parsing. Anyway, the result is the same.
date1 = 08:00:01 in Japan = 23:00:01 in UTC
date2 = 23:00:05 in Japan = 14:00:05 in UTC
date = 14:00:05 in UTC - 23:00:01 in UTC = 15:00:04 in UTC
     = 23:00:05 in Japan - 08:00:01 in Japan = 15:00:04 in UTC (NOT in JAPAN)

Why? Because the calculation is done with the long value I mentioned earlier which is calculated in GMT (UTC +00:00, or just UTC). The long value is in UTC even if you specified you are using Japan time. Date stores it converted to UTC, so it will get the time you gave -09:00, and the internal process in Date is done with UTC. If you want to print it, Date just add +09:00 calculation.

You can also see it as:
23:00:05 with (+09:00) - 08:00:01 with (+09:00) = 15:00:04 with (+00:00)

anyway, 15:00:04 UTC is 00:00:04 JST, so if you print it, you will see the Japan Time 00:00:04 since you specified you are using Japan Standard Time.

Naetmul
  • 14,544
  • 8
  • 57
  • 81
  • I knew this, what I cannot understand is where does the result `00:00:04` mentioned in the question come from. – Luis Jan 11 '14 at 06:25
  • @Naetmul yes, I think that's the point. but the problem is I have specified the time zone to Locale.JAPAN, why it still use the DEFAULT one? – Luis Jan 11 '14 at 06:36
  • @Naetmul This answer is almost correct but not quite. The Locale affects the `using the given pattern and the default date format symbols for the given locale` but does not affect time zone. Another example… The result of running the question's exact source code here in time zone "America/Los_Angeles" with a time zone offset of 8 hours behind UTC results in a result of 07:00:04. That is 15:00:04 (on the first day of 1970, [Unix epoch](https://en.wikipedia.org/wiki/Unix_time)) offset by going back 08:00:00. – Basil Bourque Jan 11 '14 at 07:10
  • @Leng You assumed incorrectly that passing a Locale sets a time zone. Actually, a **Locale has *nothing* to do with time zone**. See [my answer](http://stackoverflow.com/a/21059490/642706) for details. – Basil Bourque Jan 12 '14 at 01:45
2

Your Problems

Problems with your code include:

  • You are trying to represent a span of time using a point in time object (java.util.Date). A Date is based on milliseconds since the Unix Epoch. By subtracting the difference, you got a result of 54,004,000 milliseconds which amounts to 15 hours and 4 seconds. When used to construct a Date instance, that means a few seconds after 3 PM on the first day of 1970 (1970-01-01T15:00:04.000Z). Then you printed using a format that automatically uses your local time zone offset to adjust the time rendered in the string. Running your same code in the US west coast (-08:00 from UTC), results in 07:00:04 on Jan 1, 1970. That is 15:00:04 adjusted back 8 hours.
  • You assumed that passing a Locale to java.text.SimpleDateFormat affects the time zone. It does not. A Locale is not a time zone. A Locale represents a country/culture and a language, and has nothing to do with a time zone. You should read the documentation for a method before using it. The doc says passing a Locale affects the given pattern and the default date format symbols.
  • You don't need a locale if you are merely working with times and no dates.
  • Generally a bad idea to work with only times and no dates and no time zone. You will be ignoring Daylight Savings Time (DST) and other anomalies that affect the outcome. You should only use time without date if you have some hypothetical situation as opposed to real business data.
  • You are using the java.util.Date class which should be avoided.
  • Trying to use a clock format (XX:XX:XX) to represent a span of time is awkward. It leads to confusion as it is easy to mistake for a time. It assumes the result will be less than 24 hours, which (practically speaking) can be a faulty assumption when put into use.

If you had bothered to search StackOverflow.com or other sources you would have found many examples and discussions. You should (a) read the documentation of the classes you are using, and (b) google/bing for words like date, time, interval, period, duration, span.

ISO 8601 Duration

The standard ISO 8601 defines a way to describe a span of time as a string in the Duration format of PnYnMnDTnHnMnS.

Joda-Time uses ISO 8601 for most of its defaults.

Joda-Time

The Joda-Time 2.3 library has multiple classes designed just for this purpose of representing a span of time: Period, Interval, and Duration.

The new java.time.* classes (inspired by Joda-Time) in the upcoming Java 8 has similar classes. These classes supplant the java.util.Date & Calendar classes currently bundled in Java.

Example Code

// © 2013 Basil Bourque. This source code may be used freely forever by anyone taking full responsibility for doing so.
// import org.joda.time.*;
// import org.joda.time.format.*;

// Better to specify a time zone explicitly rather than rely on default.
// Time Zone list… http://joda-time.sourceforge.net/timezones.html  (not quite up-to-date, read page for details)
DateTimeZone timeZone = DateTimeZone.forID( "Europe/Paris" );

DateTime dateTime1 = new DateTime( 2014, 1, 2, 8, 0, 1, timeZone );
DateTime dateTime2 = new DateTime( 2014, 1, 2, 23, 0, 5, timeZone );

// Calculate the span of time between them.
Period period = new Period( dateTime1, dateTime2 );

Convert to words, and dump to console…

System.out.println( "dateTime1: " + dateTime1 );
System.out.println( "dateTime2: " + dateTime2 );
System.out.println( "period in ISO Duration format: " + period );
System.out.println( "period in words: " + PeriodFormat.getDefault().print( period ) );
System.out.println( "period in words, en français: " + PeriodFormat.wordBased( Locale.FRANCE ).print( period ) );
System.out.println( "period in words, for Japan: " + PeriodFormat.wordBased( Locale.JAPAN ).print( period ) );

When run…

dateTime1: 2014-01-02T08:00:01.000+01:00
dateTime2: 2014-01-02T23:00:05.000+01:00
period in ISO Duration format: PT15H4S
period in words: 15 hours and 4 seconds
period in words, en français: 15 heures et 4 secondes
period in words, for Japan: 15時間4秒
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
1
Date date = new Date(date2.getTime() - date1.getTime());
  • date2.getTime() - date1.getTime() will always return same long value.
  • When you create Date object using long it to represent the specified number of milliseconds since the standard base time known as "the epoch", namely January 1, 1970, 00:00:00 GMT
  • When you ask SimpleDateFormatter to display time in terms of Japan TZ it adds 09:00:00 to your ans. (which answers your following question)

I cannot understand why. and if I change date2 to "08:00:01", the result will change to 09:00:00. how could this happen?

Add the following line to your code:

System.setProperty("user.timezone", "<your preferred timezone>");
Mitesh Pathak
  • 1,306
  • 10
  • 14
  • 1
    Your answer is correct, but risky. Setting that System property affects all the threads of all the apps. Using the [Joda-Time](http://www.joda.org/joda-time/) library, and avoiding the Date/Calendar classes, makes much more sense. – Basil Bourque Jan 13 '14 at 07:41
0

I'd use joda-time, but, from this code (just wrapping your code in a proper java class):

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class DateTest {
    public static void main (String[] args) throws Throwable{
SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss",Locale.JAPAN);
Date date1 = format.parse("08:00:01");
Date date2 = format.parse("23:00:05");
Date date = new Date(date2.getTime() - date1.getTime());
System.out.println(format.format(date));
}
}

I get:

07:00:04

this is on JDK 1.7.0_40 on Mac OS X Mavericks.

hd1
  • 33,938
  • 5
  • 80
  • 91
  • 1
    (date2 - date1) should result in 15:00:04 – Mitesh Pathak Jan 11 '14 at 06:10
  • What will you get if you change date2 to `08:00:01` which is the same with date1? – Luis Jan 11 '14 at 06:19
  • @MiteshPathak Your comment is correct for the Date instance, but incorrect for the result of call to `.format(date)`. The java.text.SimpleDateFormat class uses the JVM's default time zone by default. The answer's author (hd1) must be in a -08:00 time zone. If UTC time is 15 hours, going back with an offset of 8 hours results in 7 hours as the time. See [my answer](http://stackoverflow.com/a/21059490/642706) for details. – Basil Bourque Jan 13 '14 at 07:34
  • ^ yep actually that's the height of insanity... if you see my answer I have explained the same thing (see second comment in the trial); I should probably delete my comment ;) – Mitesh Pathak Jan 13 '14 at 17:47