ISO 8601
We must parse your strings. The java.time classes use standard ISO 8601 formats by default when parsing/generating strings. Your inputs are close to complying; we just need to replace the SPACE in the middle with a T
.
String inputStart = "2017-01-23 05:25:00".replace ( " " , "T" );
String inputStop = "2018-03-20 07:29:50".replace ( " " , "T" );
LocalDateTime
Your inputs lack any indication of offset-from-UTC or time zone. So they do not represent actual moments on the timeline, only vague idea of possible moments on the timeline. So we will parse this as LocalDateTime
objects. The math we will perform to determine spans of time will be based on generic 24-hour days, and will ignore anomalies such as Daylight Saving Time (DST).
LocalDateTime start = LocalDateTime.parse ( inputStart );
LocalDateTime stop = LocalDateTime.parse ( inputStop );
Period
& Duration
The java.time classes define a span of time unattached to the timeline as two pieces. The Period
class represents years-months-days, while the Duration
class represents hours-minutes-seconds.
Breaking a span of time into these two pieces avoids certain issues, issues which we must address to solve your request. How exactly do you want to handle the possibly partial day on the first day, and the possibly partial day at the end? Is noon on one day to noon on the next day a single day or 24 hours? If we sum the hours of the first day with the hours of the last day, should we squeeze out a whole day if that number is more than 24 hours?
Calculating
Here I take the approach that we count the number of whole days, ignoring the first day and the last day. Then I sum the number of hours in the first day with the number of hours in the last day. As we see with your example data, this may result in more than 24 hours in our Duration
. This approach may or may not meet your unstated intentions/definitions.
Period p = Period.between ( start.toLocalDate () , stop.toLocalDate () ).minusDays ( 1 ); // Subtract one, as we account for hours of first day.
// Get the Duration of first day's hours-minutes-seconds.
LocalDateTime startNextDay = start.toLocalDate ().plusDays ( 1 ).atStartOfDay ();
Duration dStart = Duration.between ( start , startNextDay );
// Get the Duration of first day's hours-minutes-seconds.
LocalDateTime stopStartOfDay = stop.toLocalDate ().atStartOfDay ();
Duration dStop = Duration.between ( stopStartOfDay , stop );
// Combine the pair of partial days into a single Duration.
Duration d = dStart.plus ( dStop );
Dump to console. The toString
methods on the Period
& Duration
class generates strings in the standard durations format of ISO 8601: PnYnMnDTnHnMnS
where P
marks the beginning and T
separates the years-month-days from the hours-minutes-seconds.
System.out.println ( "start/stop: " + start + "/" + stop );
System.out.println ( "p: " + p );
System.out.println ( "dStart: " + dStart + " and dStop: " + dStop );
System.out.println ( "d: " + d );
start/stop: 2017-01-23T05:25/2018-03-20T07:29:50
p: P1Y1M24D
dStart: PT18H35M and dStop: PT7H29M50S
d: PT26H4M50S
To get your desired textual output, you need to ask for each part of the Period
and each part of the Duration
.
int years = p.getYears ();
int months = p.getMonths ();
int days = p.getDays ();
Java 8 inexplicably lacks methods to conveniently get each hours or minutes or seconds part from a Duration
. Java 9 adds such methods. See this Question for more discussion.
long durationDays = d.toDaysPart();
long hours = d.toHoursPart();
long minutes = d.toMinutesPart();
long seconds = d.toSecondsPart();
int nanos = d.toNanosPart();
Live code
See this example code working live in IdeOne.com.
ZonedDateTime
The discussion above was for generic 24-hour days, ignoring the issue of offset-from-UTC/time zone.
If instead you meant to track actual moments on the timeline, you must apply the time zone intended for those date-time strings.
ZoneId z = ZoneId.of( "America/Montreal" );
ZonedDateTime zdtStart = start.atZone( z );
…
The rest of the code would be similar, but pass the ZoneId
to the atStartOfDay
calls to generate a ZonedDateTime
object rather than a LocalDateTime
object.
Keep in mind that you may well get different results when using ZonedDateTime
objects rather than LocalDateTime
objects.
See the example code live in IdeOne.com.
String inputStart = "2017-01-23 05:25:00".replace ( " " , "T" );
String inputStop = "2018-03-20 07:29:50".replace ( " " , "T" );
LocalDateTime ldtStart = LocalDateTime.parse ( inputStart ); // LocalDateTime used only briefly here, to parse inputs. Then we switch to `ZonedDateTime`.
LocalDateTime ldtStop = LocalDateTime.parse ( inputStop );
ZoneId z = ZoneId.of( "America/Montreal" );
ZonedDateTime zdtStart = ldtStart.atZone( z ); // Invalid values may be adjusted, such as during the hour of a "Spring-forward" Daylight Saving Time (DST) switch-over.
ZonedDateTime zdtStop = ldtStop.atZone( z );
Period p = Period.between ( zdtStart.toLocalDate () , zdtStop.toLocalDate () ).minusDays ( 1 ); // Subtract one, as we account for hours of first day.
// Get the Duration of first day's hours-minutes-seconds.
ZonedDateTime zdtStartNextDay = zdtStart.toLocalDate ().plusDays ( 1 ).atStartOfDay ( z );
Duration dStart = Duration.between ( zdtStart , zdtStartNextDay );
// Get the Duration of first day's hours-minutes-seconds.
ZonedDateTime zdtStopStartOfDay = zdtStop.toLocalDate ().atStartOfDay ( z );
Duration dStop = Duration.between ( zdtStopStartOfDay , zdtStop );
// Combine the pair of partial days into a single Duration.
Duration d = dStart.plus ( dStop );
System.out.println ( "zdtStart: " + zdtStart );
System.out.println ( "zdtStop: " + zdtStop );
System.out.println ( "p: " + p );
System.out.println ( "dStart: " + dStart + " and dStop: " + dStop );
System.out.println ( "d: " + d );
int years = p.getYears ();
int months = p.getMonths ();
int days = p.getDays ();
System.out.println( "Years: " + years + "\nMonths: " + months + "\nDays: " + days );
// Enable if in Java 9 or later (but not in Java 8)
// long durationDays = d.toDaysPart();
// long hours = d.toHoursPart();
// long minutes = d.toMinutesPart();
// long seconds = d.toSecondsPart();
// int nanos = d.toNanosPart();
Tip: The ThreeTen-Extra library offers a class Interval
to track a pair of Instant
objects. An Instant
is moment on the timeline in UTC. You may extract an Instant
from a ZonedDateTime
by calling toInstant
.
About java.time
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?
- Java SE 8 and SE 9 and later
- Built-in.
- Part of the standard Java API with a bundled implementation.
- Java 9 adds some minor features and fixes.
- Java SE 6 and SE 7
- Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
- Android
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.