Gusti Arya
Gusti Arya

Reputation: 1301

Iterate between two date range using Joda

In my code I need to iterate between a range of dates using Joda, and I already tried this:

for(LocalDate currentdate = startDate; currenDate.isBefore(endDate); currenDate= currenDate.plusDays(1)){
    System.out.println(currentdate);
}

The above code is working, but the iteration stops when currenDate reaches the day before endDate. What I want to achieve is that the iteration stops when currentDate is exactly the same as endDate.

for(Date currentdate = startDate; currentdate <= endDate; currentdate++){
    System.out.println(currentdate );
}

I know the code above is impossible, but I do it to make clear what I'd want.

Upvotes: 7

Views: 7168

Answers (6)

Boris Churzin
Boris Churzin

Reputation: 1203

Evevntually went for this solution:

Range.inclusive(3, 0).map(i => LocalDate.now.minusDays(i)).foreach()

Upvotes: 0

Basil Bourque
Basil Bourque

Reputation: 338326

Using java.time

The Joda-Time project is now in maintenance mode, with the team advising migration to the java.time classes. See Tutorial by Oracle.

The LocalDate class represents a date-only value without time-of-day and without time zone.

LocalDate start = LocalDate.of( 2017 , Month.JANUARY , 23 ) ;
LocalDate stop = LocalDate.of( 2017 , Month.FEBRUARY , 2 ) ;

By the way, you might want to add a sanity-check to verify that the ending is not before the beginning.

Not After

I believe the logic you are looking for, to include the ending date, is “not after“. The LocalDate class includes a isAfter method, to which you can add a logical “NOT” (!).

Also, a while loop seems more appropriate and self-explanatory in this situation than a for loop.

LocalDate ld = start ;
List<LocalDate> dates = new ArrayList<>() ;
while ( ! ld.isAfter( stop ) ) {
    dates.add( ld ); // Collect this date.
    ld = ld.plusDays( 1 ) ;  // Setup the next loop.
}

See this code run live at IdeOne.com.

dates: [2017-01-23, 2017-01-24, 2017-01-25, 2017-01-26, 2017-01-27, 2017-01-28, 2017-01-29, 2017-01-30, 2017-01-31, 2017-02-01, 2017-02-02]

Half-Open

the iteration stops when currentDate reaches the day before endDate

This is actually desirable. Known as Half-Open, the common approach in date-time handling is to consider the beginning as inclusive while the ending is exclusive. So a lunch break starts at 12:00:00 (noon) and runs up to, but does not include, 13:00:00 (1 pm). The month of January starts on January 1 and runs up to, but does not include, February 1. A week starts on a Monday and runs up to, but does not include the following Monday. Most usefully, this approach avoids the problem of determining the last split second of a date-time where some systems use milliseconds (x.999), some (x.999999), same nanoseconds( x.999999999 ), and others use variations such as 5 decimal places (x.99999). Instead we go up to, but not include, the first moment of the next hour or day etc.

I find that using Half-Open approach consistently throughout my code makes the code easier to read, easier to comprehend, and much less likely to result in off-by-one bugs. I have been caught in countless financial mystery problems that turned out to be confusion or misunderstandings about a report covering date for with inclusive vs exclusive dates. So if possible, train your users to think the Half-Open way consistently. If not feasible, then adjust your code so your logic and loops are using Half-Open internally at least.

Here is code similar to above, but using isBefore rather than NOT isAfter, to use Half-Open approach. The ending is Feb. 3 instead of Feb. 2.

LocalDate start = LocalDate.of( 2017 , Month.JANUARY , 23 ) ;
LocalDate stop = LocalDate.of( 2017 , Month.FEBRUARY , 3 ) ;  // Third instead of the Second of February, to be half-open.

LocalDate ld = start ;
List<LocalDate> dates = new ArrayList<>() ;
while ( ld.isBefore( stop ) ) {  // Using "isBefore" for Half-Open approach.
    dates.add( ld ); // Collect this date.
    ld = ld.plusDays( 1 ) ;  // Setup the next loop.
}

See this code run live at IdeOne.com.

start: 2017-01-23 | stop: 2017-02-03

dates: [2017-01-23, 2017-01-24, 2017-01-25, 2017-01-26, 2017-01-27, 2017-01-28, 2017-01-29, 2017-01-30, 2017-01-31, 2017-02-01, 2017-02-02]


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?

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: 1

Matt York
Matt York

Reputation: 301

If you want the loop to be inclusive of the endDate you can use !currentDate.isAfter( endDate ). This is logically equivalent to currentDate.isBefore(endDate) || currentDate.equals(endDate).

The following example will print 6/1/2017 through 6/10/2017.

LocalDate startDate = new LocalDate( 2017, 6, 1 );
LocalDate endDate = new LocalDate( 2017, 6, 10 );
for ( LocalDate currentDate = startDate; !currentDate.isAfter( endDate ); currentDate = currentDate.plusDays( 1 ) )
{
    System.out.println( currentDate );
}

Upvotes: 2

javaMaster
javaMaster

Reputation: 316

Actually there's a simple way around to your original code you posted, see my implementation below, just modified your for loop implementation:

    //test data
    LocalDate startDate = LocalDate.now(); //get current date
    LocalDate endDate = startDate.plusDays(5); //add 5 days to current date

    System.out.println("startDate : " + startDate);
    System.out.println("endDate : " + endDate);

    for(LocalDate currentdate = startDate; 
            currentdate.isBefore(endDate) || currentdate.isEqual(endDate); 
            currentdate= currentdate.plusDays(1)){
        System.out.println(currentdate);
    }

Below is the Output (with respect to my localDate):

startDate : 2015-03-26
endDate : 2015-03-31
2015-03-26
2015-03-27
2015-03-28
2015-03-29
2015-03-30
2015-03-31

Hope this helps! Cheers. :)

Upvotes: 10

Jimmy
Jimmy

Reputation: 16428

If you want your loop to stop when the date your iterating is the same as todays date, you can use an equality check for that.

Have a look at .equals() on LocalDate

Here is a quick example:

public class DateIterator {

    public static void main(String[] args) {

        LocalDate lastMonth = LocalDate.now().minusMonths(1);
        LocalDate lastWeek = LocalDate.now().minusWeeks(1);
        LocalDate yesterday = LocalDate.now().minusDays(1);
        LocalDate today = LocalDate.now();
        LocalDate tomorrow = LocalDate.now().plusDays(1);

        List<LocalDate> dates = Arrays.asList(lastMonth, lastWeek, yesterday, today, tomorrow);

        for (LocalDate date : dates) {
            if (date.isEqual(today)) {
                System.out.println("Date is equal to todays date! Break out, or do something else here");
            } else if (date.isBefore(today)) {
                System.out.println("The date " + date.toString() + " is in the past");
            } else {
                System.out.println("The date " + date.toString() + " is in the future");
            }
        }
    }
}

Output is:

The date 2015-02-25 is in the past
The date 2015-03-18 is in the past
The date 2015-03-24 is in the past
Date is equal to todays date! Break out, or do something else here
The date 2015-03-26 is in the future

Obviously, if that equality check passes, you'll need to break out of the loop etc.

Heres another that uses a specific date and increments 1 day at a time, which I think is a bit more like what you want

public class DateIterator {

    public static void main(String[] args) {

        LocalDate specificDate = LocalDate.now().minusWeeks(1);
        LocalDate today = LocalDate.now();

        boolean matchFound = false;
        while (!matchFound) {
            if (!specificDate.isEqual(today)) {
                System.out.println(specificDate.toString() + " is in the past, incrementing day and checking again...");
                specificDate = specificDate.plusDays(1);
            } else {
                System.out.println("Date matches today!");
                matchFound = true;
            }
        }
    }
}

Output:

2015-03-18 is in the past, incrementing day and checking again...
2015-03-19 is in the past, incrementing day and checking again...
2015-03-20 is in the past, incrementing day and checking again...
2015-03-21 is in the past, incrementing day and checking again...
2015-03-22 is in the past, incrementing day and checking again...
2015-03-23 is in the past, incrementing day and checking again...
2015-03-24 is in the past, incrementing day and checking again...
Date matches today!

Upvotes: 4

Jigar Joshi
Jigar Joshi

Reputation: 240870

Not sure with joda-type but you could iterate at the interval (second, minute, hour, day, month, year) with Calendar API, here & here are examples

Upvotes: 0

Related Questions