Reputation: 217
I have a string that is usually in format "HH:mm:ss", but can also appear as "x days HH:mm:ss". I have to parse it to Date object and I want to calculate what Date will it be after this time from string. How can it be done?
Date now = new Date();
Date delay = new SimpleDateFormat("HH:mm:ss").parse(myString);
Date after = new Date(now.getTime() + delay.getTime());
But unfortunately it doesn't work. It adds, but very small value as "delay".
Upvotes: -2
Views: 233
Reputation: 13
Use java.time, the modern Java date and time API, for all of your date and time work. I will show you that this is much simpler than the accepted answer.
Edit:
To add days correctly to the current point in time taking summer time transitions and other time anomalies into account, we need to add 4 days to today’s date. After that (or before that, depending on exact requirements) we can add the hours, minutes and seconds.
ZoneId
, ZonedDateTime
and Duration
Your string in the format HH:mm:ss
denotes an amount of time, neither a date nor a time of day. Therefore parse it into a Duration
object, not into a date-time object.
The Duration
class can parse strings in ISO 8601 format only. So my trick is to convert your string into that format. ISO 8601 durations go like for example PT15H14M15S
for 15 hours 14 minutes 15 seconds. Think P
for period and T
marking the start of the time-based part (as opposed to any date-based part of years, months, weeks and/or days).
I am using the following method to add the contents of your string to a ZonedDateTime
. A ZonedDateTime
holds the date and time in a specific time zone.
public static ZonedDateTime addTimeFromString(ZonedDateTime dateTime, String timeString) {
if (timeString.length() > 8) { // x days HH:mm:ss
String[] daysTime = timeString.split(" days ");
dateTime = dateTime.plusDays(Integer.parseInt(daysTime[0]));
timeString = daysTime[1];
}
// Now timeString is HH:mm:ss
var isoDurationString = timeString.replaceFirst(
"^(\\d{2}):(\\d{2}):(\\d{2})$", "PT$1H$2M$3S");
var duration = Duration.parse(isoDurationString);
return dateTime.plus(duration);
}
Let’s try this method out.
var now = ZonedDateTime.now(ZoneId.of("America/Asuncion"));
System.out.println("Time now: " + now);
System.out.println("Time after 14:21:15: "
+ addTimeFromString(now, "14:21:15"));
System.out.println("Time after 4 days 15:14:15: "
+ addTimeFromString(now, "4 days 15:14:15"));
Please substitute your desired time zone instead of America/Asuncion. To use the JVM’s default time zone specify ZoneId.systemDefault()
.
When I ran the above snippet just now, the output was:
Time now: 2024-12-14T06:48:16.216672420-03:00[America/Asuncion]
Time after 14:21:15: 2024-12-14T21:09:31.216672420-03:00[America/Asuncion]
Time after 4 days 15:14:15: 2024-12-18T22:02:31.216672420-03:00[America/Asuncion]
Original code follows.
public static Duration parseDuration(String durationString) {
String isoDurationString;
if (durationString.length() <= 8) { // HH:mm:ss
isoDurationString
= durationString.replaceFirst("^(\\d{2}):(\\d{2}):(\\d{2})$", "PT$1H$2M$3S");
} else { // x days HH:mm:ss
isoDurationString
= durationString.replaceFirst("^(\\d+) days (\\d{2}):(\\d{2}):(\\d{2})$",
"P$1DT$2H$3M$4S");
}
return Duration.parse(isoDurationString);
}
Let’s try this method out.
ZonedDateTime now = ZonedDateTime.now(ZoneId.of("America/Asuncion"));
System.out.println("Time now: " + now);
Duration duration1 = parseDuration("14:21:15");
System.out.println("Delay: " + duration1);
System.out.println("Time after: " + now.plus(duration1));
Duration duration2 = parseDuration("4 days 15:14:15");
System.out.println("Delay: " + duration2);
System.out.println("Time after: " + now.plus(duration2));
Again please substitute your desired time zone.
When I ran the above snippet just now, the output was:
Time now: 2024-12-11T17:08:21.290431517-03:00[America/Asuncion]
Delay: PT14H21M15S
Time after: 2024-12-12T07:29:36.290431517-03:00[America/Asuncion]
Delay: PT111H14M15S
Time after: 2024-12-16T08:22:36.290431517-03:00[America/Asuncion]
The latter delay is printed as PT111H14M15S
, that is, 111 hours 14 minutes 15 seconds. This is how Duration.toString()
renders it, which for our purpose isn’t perfect. Calculating backwards the 111 hours equals 4 days 15 hours (assuming a day is 24 hours), so the duration is correct.
The Duration
class calculates a day as always 24 hours, which isn’t always correct. To take summer time transitions and other time anomalies into account you would need to add 4 days to today’s date, not just a Duration
of 4 days, which will always be taken to be 96 hours.
The Java™ Tutorials: Trail: Date Time explaining how to use java.time
Upvotes: 1
Reputation: 340200
Ideally, communicate the span of time in standard format rather than your custom format.
Instant
.now()
.plus(
Duration.parse( "PT1H30M" )
)
Otherwise, hack it a bit to parse.
Instant
.now()
.plus(
Duration
.ofDays (
Integer.parseInt( input.split( " days " )[0] )
)
.plus(
Duration.between (
LocalTime.MIN ,
LocalTime.parse( input.split( " days " )[1] )
)
)
)
You seem to be abusing a moment value, contorting it to serve as a span-of-time.
Also, you are using terribly-flawed legacy classes that were supplanted by the modern java.time classes defined in JSR 310, and built into Java 8+. Never use Date
, Calendar
, nor SimpleDateFormat
.
Fortunately java.time provides classes for both concepts:
Instant
, OffsetDateTime
, or ZonedDateTime
.Duration
for scale of hours-minutes-seconds-nanos, and Period
for scale of years-months-days.You said:
I have a string that is usually in format "HH:mm:ss", but can also appear as "x days HH:mm:ss".
Educate the publisher of your data about the ISO 8601 standard for text expressing a span-of-time: PnYnMnDTnHnMnS
. The P
marks the beginning. The T
separates the years-months-days from the hours-minutes-seconds. So an hour and a half would be PT1H30M
.
You can directly parse ISO 8601 text by default with the java.time classes.
Duration d = Duration.parse( "PT1H30M" ) ;
To parse your custom format rather than standard format, we must do a bit of hacking.
String[] parts = input.split( " days " ) ;
int days = Integer.parseInt( parts[0] );
LocalTime lt = LocalTime.parse( parts[1] ) ; // Not really a time-of-day, but we will fake it.
Combine those into a Duration
.
Duration duration = Duration.ofDays ( days ) ;
duration = duration.plus ( Duration.between ( LocalTime.MIN , lt ) ) ;
Capture the current moment.
Instant now = Instant.now() ;
Add your span-of-time.
Instant later = now.plus ( duration ) ;
Upvotes: 2
Reputation: 38338
Highlights:
String.split
to determine if you have a time or a duration and a time.Calendar
for date stuff.Calendar.add(Calendar.DAY_OF_YEAR, xxx)
gives the end date.Try something like this:
public class LearnDate
{
private static DateFormat timeFormat = new SimpleDateFormat("HH:mm:ss");
public static void main(final String[] arguments)
throws ParseException
{
final String input1 = "14:21:15";
final String input2 = "4 days 15:14:15";
printDateInfo(input1);
printDateInfo(input2);
}
private static String formatCalendar(final Calendar input)
{
return
"" +
input.get(Calendar.DAY_OF_MONTH) +
monthName(input.get(Calendar.MONTH)) +
input.get(Calendar.YEAR) +
" " +
timeFormat.format(input.getTime());
}
private static String monthName(final int month)
{
switch (month)
{
case Calendar.JANUARY:
return "Jan";
case Calendar.FEBRUARY:
return "Feb";
case Calendar.MARCH:
return "Mar";
case Calendar.APRIL:
return "Apr";
case Calendar.MAY:
return "May";
case Calendar.JUNE:
return "Jun";
case Calendar.JULY:
return "Jul";
default:
return "blah";
}
}
private static void printDateInfo(final String input)
throws ParseException
{
String[] parsedInput = input.split(" ");
Date time = null;
if (parsedInput != null)
{
switch (parsedInput.length)
{
case 1:
time = timeFormat.parse(parsedInput[0]);
System.out.println("Just Time: " + timeFormat.format(time));
break;
case 3:
{
int duration;
Calendar end = Calendar.getInstance();
Calendar start = Calendar.getInstance();
Calendar timeValue = Calendar.getInstance();
time = timeFormat.parse(parsedInput[2]);
System.out.println("Time: " + timeFormat.format(time));
System.out.println("Duration: " + parsedInput[0] + " days.");
timeValue.setTime(time);
transferTime(start, timeValue);
transferTime(end, timeValue);
duration = Integer.parseInt(parsedInput[0]) - 1;
end.add(Calendar.DAY_OF_YEAR, duration);
System.out.println("Start: " + formatCalendar(start));
System.out.println(" End: " + formatCalendar(end));
}
break;
default:
System.out.println("Unrecognized format: " + input);
break;
}
}
}
private static void transferTime(
final Calendar destination,
final Calendar source)
{
destination.set(Calendar.HOUR_OF_DAY, source.get(Calendar.HOUR_OF_DAY));
destination.set(Calendar.MINUTE, source.get(Calendar.MINUTE));
destination.set(Calendar.SECOND, source.get(Calendar.SECOND));
}
}
Upvotes: 1
Reputation: 36940
You can't add two Date
s directly using the +
operator. You can, however, add a certain number of hours or minutes, or add their timestamps.
However, there seems to be a general consensus that the JDK Time API is broken. They recommend using Joda Time, which provides additional features, such as adding a Period
or Duration
to a DateTime
:
DateTime dt = new DateTime(2005, 3, 26, 12, 0, 0, 0);
DateTime plusPeriod = dt.plus(Period.days(1));
DateTime plusDuration = dt.plus(new Duration(24L*60L*60L*1000L));
For more information see http://joda-time.sourceforge.net/quickstart.html
Upvotes: 3
Reputation: 17057
You'll want to try something like this:
Date now = new Date();
Date delay = new SimpleDateFormat("HH:mm:ss").parse("02:15:30");
Date after = new Date(now.getTime() + delay.getTime());
Upvotes: 1