The Answer by Aaron is good, but it ignores any seconds or fraction-of-second that may be present in the inputs. Also, I can expand a bit more, and apply time zone.
Truncate
You can truncate the LocalDateTime
objects eliminate any whole or fractional seconds as you increment the hours.
LocalDateTime ldtTruncated = ldt.truncatedTo( ChronoUnit.MINUTES ); // Lop off seconds and nanoseconds.
…or…
LocalDateTime ldtTruncated = ldt.truncatedTo( ChronoUnit.HOURS ); // Lop off minutes, seconds, and nanoseconds.
Time zone
A crucial issue is time zone. These values lack any indicator of offset-from-UTC or time zone. So as correctly shown by Aaron, we should initially parse them as LocalDateTime
.
LocalDateTime ldtStart = LocalDateTime.parse ( "2017-03-07 06:30:00".replace ( " " , "T" ) );
LocalDateTime ldtStop = LocalDateTime.parse ( "2017-03-07 11:35:00".replace ( " " , "T" ) );
But I assume from the wording of the Question that we want to work with specific moments. A LocalDateTime
by definition is not a specific moment, only a vague idea of potential moments. You must add the context of a time zone (or offset) to determine actual moment. So if you know the zone intended for these values, apply it.
The ZonedDateTime
class makes adjustments for input values that are invalid for your particular time zone because of anomalies such as Daylight Saving Time (DST).
ZoneId z = ZoneId.of ( "America/Montreal" );
ZonedDateTime zdtStart = ldtStart.atZone ( z );
ZonedDateTime zdtStop = ldtStop.atZone ( z );
Make sure the inputs make sense.
// Validate the inputs.
if ( zdtStop.isBefore ( zdtStart ) ) {
// perhaps throw exception
System.out.println ( "ERROR - stop is befor start." );
return;
}
Date-time range as a pair of ZonedDateTime
objects
Now we can increment hour-by-hour to generate your desired ranges. We could use the Interval
class from the ThreeTen-Extra project (see below) which track a pair of Instant
objects which are always in UTC. But that may not be useful for us as we really want to work with the zoned hours (or so I assume). So instead let's keep track of each date-time range as a pair of ZonedDateTime
classes. We could create our own class for the pair, but instead we re-purpose the AbstractMap.SimpleImmutableEntry class. That class tracks a pair of objects as a key and a value.
Do not let the name of that pairing class confuse you. The original intent of that class was to be used with a Map
, but is not itself a map. That class is meant to refer to a key and a value from a Map
, but we will use in a manner where its “key” is our starting moment and its “value” is our stopping moment.
We collect each calculated time range in a List
of that AbstractMap.SimpleImmutableEntry
type.
int initialCapacity = ( ( int ) ChronoUnit.HOURS.between ( zdtStart , zdtStop ) ) + 2; // Plus two for good measure. (not sure if needed).
List<AbstractMap.SimpleImmutableEntry<ZonedDateTime , ZonedDateTime>> ranges = new ArrayList<> ( initialCapacity );
Set up our loop with the starting value, and loop. On each loop we want to truncate the value to a whole hour (to lop off seconds and fractional-second). After truncation, we add an hour to get to next hour. We only need that truncation on the first loop, so you could re-write this code to be more efficient by avoiding the needless truncation on successive loops. I would not bother unless you are processing very large numbers of hours.
ZonedDateTime zdt = zdtStart;
while ( zdt.isBefore ( zdtStop ) ) {
ZonedDateTime zdt2 = zdt.truncatedTo ( ChronoUnit.HOURS ).plusHours ( 1 ); // Truncate to whole hour, and add one.
if ( zdt2.isAfter ( zdtStop ) ) { // Oops, went too far. Clip this range to end at `zdtStop`.
zdt2 = zdtStop;
}
AbstractMap.SimpleImmutableEntry<ZonedDateTime , ZonedDateTime> range = new AbstractMap.SimpleImmutableEntry<> ( zdt , zdt2 );
ranges.add ( range );
// Prepare for next loop.
zdt = zdt2;
}
Dump to console.
System.out.println ( "ldtStart/ldtStop: " + ldtStart + "/" + ldtStop );
System.out.println ( "zdtStart/zdtStop: " + zdtStart + "/" + zdtStart );
System.out.println ( "ranges: " + ranges );
When run.
ldtStart/ldtStop: 2017-03-07T06:30/2017-03-07T11:35
zdtStart/zdtStop: 2017-03-07T06:30-05:00[America/Montreal]/2017-03-07T06:30-05:00[America/Montreal]
ranges: [2017-03-07T06:30-05:00[America/Montreal]=2017-03-07T07:00-05:00[America/Montreal], 2017-03-07T07:00-05:00[America/Montreal]=2017-03-07T08:00-05:00[America/Montreal], 2017-03-07T08:00-05:00[America/Montreal]=2017-03-07T09:00-05:00[America/Montreal], 2017-03-07T09:00-05:00[America/Montreal]=2017-03-07T10:00-05:00[America/Montreal], 2017-03-07T10:00-05:00[America/Montreal]=2017-03-07T11:00-05:00[America/Montreal], 2017-03-07T11:00-05:00[America/Montreal]=2017-03-07T11:35-05:00[America/Montreal]]
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.