The Answer by Ole V.V. is correct, and wisely uses the modern java.time classes.
LocalDateRange
As a bonus, I will mention adding the excellent ThreeTen-Extra library to your project to access the LocalDateRange
class. This class represents a span of time as a pair of LocalDate
objects. That seems a match to your business problem.
LocalDate start = … ;
LocalDate stop = … ;
LocalDateRange range = LocalDateRange.of( start , stop ) ;
From this LocalDateRange
object you can obtain the Period
objects used in that Answer by Ole V.V.
Period period = range.toPeriod() ;
Notice that LocalDateRange
has handy methods for comparison, such as abuts
, contains
, overlaps
, and so on.
Half-Open
You may be confused about how to handle end-of-month/first-of-month.
Generally in date-time handling, it is best to represent a span-of-time using the Half-Open approach. In this approach, the beginning is inclusive while the ending is exclusive.
So a month starts on the first of the month and runs up to, but does not include the first of the next month.
LocalDateRange rangeOfMonthOfNovember2019 =
LocalDateRange.of(
LocalDate.of( 2019 , Month.NOVEMBER , 1 ) ,
LocalDate.of( 2019 , Month.DECEMBER , 1
)
;
You can ask each LocalDateRange
for days elapsed: LocalDateRange::lengthInDays
.
DateTimeFormatter f = DateTimeFormatter.ofPattern( "dd/MM/uuuu" );
List < LocalDateRange > ranges = new ArrayList <>( 4 );
ranges.add(
LocalDateRange.of(
LocalDate.parse( "31/03/2017" , f ) ,
LocalDate.parse( "30/09/2017" , f )
)
);
ranges.add(
LocalDateRange.of(
LocalDate.parse( "01/10/2017" , f ) ,
LocalDate.parse( "31/03/2018" , f )
)
);
ranges.add(
LocalDateRange.of(
LocalDate.parse( "01/04/2018" , f ) ,
LocalDate.parse( "30/09/2018" , f )
)
);
ranges.add(
LocalDateRange.of(
LocalDate.parse( "01/10/2018" , f ) ,
LocalDate.parse( "31/12/2019" , f )
)
);
// Sum the periods, one from each range.
Period period = Period.ZERO;
int days = 0;
for ( LocalDateRange range : ranges )
{
days = ( days + range.lengthInDays() );
period = period.plus( range.toPeriod() ).normalized();
}
Dump to console.
System.out.println( "ranges: " + ranges );
System.out.println( "days: " + days + " | pseudo-months: " + ( days / 30 ) + " and days: " + ( days % 30 ) );
System.out.println( "period: " + period );
ranges: [2017-03-31/2017-09-30, 2017-10-01/2018-03-31, 2018-04-01/2018-09-30, 2018-10-01/2019-12-31]
days: 1002 | pseudo-months: 33 and days: 12
period: P2Y5M119D
If you insist on fully-closed rather than half-open, LocalDateRange
can still help you with its ofClosed
method. But I strongly suggest you adopt half-open instead, as I believe you will find it makes your life easier to use one consistent approach across all your code. And educate your users. I have seen much confusion among office staff making incorrect assumptions about inclusive/exclusive dates. Your practice of tracking end-of-month to end-of-month seems likely to engender ever more confusion.
YearMonth
Another class you might find useful is built into Java: YearMonth
. This class represents a particular month as a whole unit.
YearMonth yearMonth = YearMonth.of( 2019 , Month.NOVEMBER ) ;
Notice methods such as atDay
to produce a LocalDate
from a YearMonth
.
ISO 8601
Tip: Make a habit of using only the ISO 8601 standard formats rather than localized formats when serializing date-time values as text.
So for a date, use YYYY-MM-DD.
The java.time classes use these formats by default when parsing/generating text. So no need to specify a formatting pattern.
LocalDate.parse( "2019-01-23" )
The standard ISO 8601 format for an entire month is YYYY-MM such as 2019-11
.
YearMonth yearMonth = YearMonth.parse( "2019-11" ) ; // Entire month of November 2019.
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
.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.*
classes.
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.