Reputation: 5287
I created the following code to calculate the duration between two timestamps which can come in two different formats:
public class dummyTime {
public static void main(String[] args) {
try {
convertDuration("2008-01-01 01:00 pm - 01:56 pm");
convertDuration("2008-01-01 8:30 pm - 2008-01-02 09:30 am");
} catch (Exception e) {
e.printStackTrace();
}
}
private static String convertDuration(String time) throws Exception {
String ts[] = time.split(" - ");
SimpleDateFormat formatNew = new SimpleDateFormat("HH:mm");
Date beg, end;
String duration = null;
beg = getDateTime(ts[0]);
end = getDateTime(ts[1], beg);
duration = formatNew.format(end.getTime() - beg.getTime());
System.out.println(duration + " /// " + time + " /// " + beg + " /// "
+ end);
return duration;
}
private static Date getDateTime(String dateTime) throws ParseException {
DateFormat formatOldDateTime = new SimpleDateFormat(
"yyyy-MM-dd hh:mm aa");
DateFormat formatOldTimeOnly = new SimpleDateFormat("hh:mm aa");
Date date = null;
try {
date = formatOldDateTime.parse(dateTime);
} catch (ParseException e) {
date = formatOldTimeOnly.parse(dateTime);
}
return date;
}
private static Date getDateTime(String dateTime, Date orig)
throws ParseException {
Date end = getDateTime(dateTime);
if (end.getYear() == 70) {
end.setYear(orig.getYear());
end.setMonth(orig.getMonth());
end.setDate(orig.getDate());
}
return end;
}
}
The output it generates is:
01:56 /// 2008-01-01 01:00 pm - 01:56 pm /// Tue Jan 01 13:00:00 CET 2008 /// Tue Jan 01 13:56:00 CET 2008
14:00 /// 2008-01-01 8:30 pm - 2008-01-02 09:30 am /// Tue Jan 01 20:30:00 CET 2008 /// Wed Jan 02 09:30:00 CET 2008
My questions are:
Many many thanks, this issue has been driving me crazy for several hours.
Upvotes: 1
Views: 10794
Reputation: 340230
First, your example strings are not consistent: 8:30 pm
lacks a padding zero. I will assume that is a typo, and should have been 08:30 pm
.
By the way, these input string formats are not desirable.
- A much better way is to use standard ISO 8601 formats.
- 12-hour clocks with AM/PM are troublesome. The standard formats use 24-hour clock, with hours 0-23.
- The standard notation for an interval is the pair of date-time strings separated by a slash: 2008-01-01T13:00/2008-01-01T13:56
.
Your input strings have another serious problem: No indication of offset-from-UTC or time zone. Without an offset or time zone, we must fall back to assuming generic 24-hour days. This ignores anomalies such as Daylight Saving Time (DST) that can result in 23 or 25 hour long days.
If you know the time zone intended for the incoming strings, pass that as a second argument to get a correct result.
This Question is quite old. Since then Java has supplanted the troublesome old date-time classes (Date
, Calendar
, etc.) with modern java.time classes. We use java.time in the example code below.
Here is a complete class for processing these strings as given in your Question. A Duration
is produced.
package javatimestuff;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Locale;
/**
*
* @author Basil Bourque
*/
public class DurationProcessor {
static final int SHORT = 30;
static final int LONG = 41;
static final DateTimeFormatter FORMATTER_LOCALDATETIME = DateTimeFormatter.ofPattern ( "uuuu-MM-dd hh:mm a" );
static final DateTimeFormatter FORMATTER_LOCALTIME = DateTimeFormatter.ofPattern ( "hh:mm a" );
static public Duration process ( String input ) {
return DurationProcessor.process ( input , ZoneOffset.UTC );
}
static public Duration process ( String input , ZoneId zoneId ) {
Duration d = Duration.ZERO; // Or maybe null. To be generated by the bottom of this code.
if ( null == input ) {
// …
System.out.println ( "ERROR - Passed null argument." );
return d;
}
if ( input.length () == 0 ) {
// …
System.out.println ( "ERROR - Passed empty string as argument." );
return d;
}
String inputModified = input.toUpperCase ( Locale.ENGLISH ); // Change `am` `pm` to `AM` `PM` for parsing.
String[] parts = inputModified.split ( " - " );
String inputStart = parts[ 0 ]; // A date-time sting.
String inputStop = parts[ 1 ]; // Either a date-time string or a time-only string (assume the same date).
ZonedDateTime start = null; // To be generated in this block of code.
try {
LocalDateTime ldt = LocalDateTime.parse ( inputStart , DurationProcessor.FORMATTER_LOCALDATETIME );
start = ldt.atZone ( zoneId );
} catch ( DateTimeParseException e ) {
// …
System.out.println ( "ERROR - The start failed to parse. inputStart: " + inputStart );
return d;
}
ZonedDateTime stop = null; // To be generated in this block of code.
switch ( input.length () ) {
case DurationProcessor.SHORT: // Example: "2008-01-01 01:00 pm - 01:56 pm"
try {
LocalTime stopTime = LocalTime.parse ( inputStop , DurationProcessor.FORMATTER_LOCALTIME );
stop = ZonedDateTime.of ( start.toLocalDate () , stopTime , zoneId );
} catch ( DateTimeParseException e ) {
// …
System.out.println ( "ERROR - The stop time failed to parse." );
return d;
}
break;
case DurationProcessor.LONG: // "2008-01-01 8:30 pm - 2008-01-02 09:30 am"
try {
LocalDateTime ldt = LocalDateTime.parse ( inputStop , DurationProcessor.FORMATTER_LOCALDATETIME );
stop = ldt.atZone ( zoneId );
} catch ( DateTimeParseException e ) {
// …
System.out.println ( "ERROR - The stop date-time failed to parse." );
return d;
}
break;
default:
// …
System.out.println ( "ERROR - Input string is of unexpected length: " + input.length () );
break;
}
d = Duration.between ( start , stop );
return d;
}
public static void main ( String[] args ) {
// Run with out time zone (assumes UTC).
Duration dShort = DurationProcessor.process ( "2008-01-01 01:00 pm - 01:56 pm" );
System.out.println ( "dShort: " + dShort );
Duration dLong = DurationProcessor.process ( "2008-01-01 08:30 pm - 2008-01-02 09:30 am" );
System.out.println ( "dLong: " + dLong );
// Run with specified time zone.
ZoneId z = ZoneId.of ( "America/Montreal" );
Duration dShortZoned = DurationProcessor.process ( "2008-01-01 01:00 pm - 01:56 pm" , z );
System.out.println ( "dShortZoned: " + dShortZoned );
Duration dLongZoned = DurationProcessor.process ( "2008-01-01 08:30 pm - 2008-01-02 09:30 am" , z );
System.out.println ( "dLongZoned: " + dLongZoned );
}
}
Note the main
method within the class for example usages.
First a pair of calls without specifying a time zone. So UTC and 24-hour days will be used.
Duration dShort = DurationProcessor.process ( "2008-01-01 01:00 pm - 01:56 pm" );
System.out.println ( "dShort: " + dShort );
Duration dLong = DurationProcessor.process ( "2008-01-01 08:30 pm - 2008-01-02 09:30 am" );
System.out.println ( "dLong: " + dLong );
Another pair of calls where we do specify the intended time zone.
ZoneId z = ZoneId.of ( "America/Montreal" );
Duration dShortZoned = DurationProcessor.process ( "2008-01-01 01:00 pm - 01:56 pm" , z );
System.out.println ( "dShortZoned: " + dShortZoned );
Duration dLongZoned = DurationProcessor.process ( "2008-01-01 08:30 pm - 2008-01-02 09:30 am" , z );
System.out.println ( "dLongZoned: " + dLongZoned );
See this class run in live code in IdeOne.com.
dShort: PT56M
dLong: PT13H
dShortZoned: PT56M
dLongZoned: PT13H
As noted elsewhere on this page, your output format using time-of-day style such as 00:56
is ambiguous and confusing, and should be avoided. The Duration
class instead uses standard ISO 8601 format for durations. Above, we see results of fifty-six minutes and thirteen minutes.
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date
, Calendar
, & SimpleDateFormat
.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
Where to obtain the java.time classes?
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval
, YearWeek
, YearQuarter
, and more.
Upvotes: 0
Reputation: 39606
Simple answer: it's inappropriate to use SimpleDateFormat to format values that represent time of day without date.
Longer answer: Java time values are a count of milliseconds since the "epoch": midnight, January 1 1970, UTC.
SimpleDateFormat assumes that you're giving it a valid timestamp, and applies a localized conversion to a date and time. I suspect that your locale is one hour off GMT (continental Europe), so that's why you're seeing results that are one hour off.
While you could fool SimpleDateFormat by setting the timezone GMT, you're probably better off displaying durations using explicit math:
int duration = 90;
System.out.printf("%02d:%02d", duration / 60, duration % 60);
Upvotes: 1
Reputation: 28696
At my computer this is off by 2 hours, because I'm at GMT+2, and you're probably at GMT+1. Note that formatNew.format(end.getTime() - beg.getTime());
receives date, i.e. treats your 56 minutes as 1970-01-01-00:56:00 GMT+1. To fix this quickly, call formatNew.setTimeZone( TimeZone.getTimeZone( "GMT" ) );
For the 2nd item, you can check if format-yyyy-MM-dd failed (you catch a parse error), and this is how you know that there's no year.
Upvotes: 2
Reputation: 147164
You are formatting time of day, not number of hours and minutes. As you are in the CET timezone [Central European Time] in winter, that is one hour different from UTC ("GMT").
You probably want to be using Calendar
instead of Date
. Or Joda-Time.
Upvotes: 4