Java Programmer
Java Programmer

Reputation: 1413

Week numbers between two dates in Java

My intention is to get the week numbers between two date ranges. The date 24th fall in the week number 34 and 26th fall in week number 35. Now the problem is if I put 2018-08-22T12:18:06,166 as the start date, i am getting 34,35,36. I am not expecting 36 here because the end date does not fall into week 36. Can anyone help me. This question is different from the solution provided here Week numbers from start date to end date Java . The solution has a problem which I detected recently

The below is the code for getting it :

public static void main(String[] args) {
    DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss','SSS");
    LocalDateTime startDate = LocalDateTime.parse("2018-08-24T12:18:06,166", format);
    LocalDateTime endDate = LocalDateTime.parse("2018-08-26T12:19:06,188", format);

    numberOfWeeks(startDate, endDate);

}

public static void numberOfWeeks(LocalDateTime startDate, LocalDateTime endDate) {
    int addWeek = 0;

    TemporalField tf = WeekFields.SUNDAY_START.weekOfYear();
    if (startDate.get(tf) < endDate.get(tf)) {
        addWeek = 1;
    }
    long weeks = WEEKS.between(startDate, endDate) + addWeek;
    List<String> numberWeeks = new ArrayList<>();
    if (weeks >= 0) {
        int week = 0;
        do {
            //Get the number of week
            LocalDateTime dt = startDate.plusWeeks(week);
            int weekNumber = dt.get(tf);
            numberWeeks.add(String.format("%d-W%d", dt.getYear(), weekNumber));
            week++;
        } while (week <= weeks);
    }
    System.out.println(numberWeeks);
}

Upvotes: 0

Views: 2844

Answers (2)

Basil Bourque
Basil Bourque

Reputation: 338266

tl;dr

LocalDateTime.parse( "2018-08-24T12:18:06,166".replace( "," , "." ) ).toLocalDate()
.with( TemporalAdjusters.previousOrSame( DayOfWeek.SUNDAY ) )
.datesUntil(
    LocalDateTime.parse( "2018-08-26T12:19:06,188".replace( "," , "." ) ).toLocalDate()
    .with( TemporalAdjusters.previousOrSame( DayOfWeek.SUNDAY ) )
    .plusWeeks( 1 ) 
    ,
    Period.ofWeeks( 1 )
)
.map( localDate -> localDate.get( WeekFields.SUNDAY_START.weekOfWeekBasedYear() ) )
.collect( Collectors.toList() )
.toString()

[34, 35]

Streams

Let’s take the idea of WeekFields shown in correct Answer by Ole V.V. but shorten the code using Java Stream technology. While interesting, I do not necessarily recommend this approach.

First parse your input strings to get LocalDate objects. The LocalDate class represents a date-only value without time-of-day and without time zone.

Unfortunately, the java.time classes fail to support the comma as the fractional second delimiter, and instead expect a period (FULL STOP). This runs contrary to the ISO 8601 standard which allows both and actually prefers comma. This is one of the few flaws I have found in the otherwise excellent java.time classes, presumably due to the bias of programmers from the United States. To get around this flaw, we substitute a FULL STOP for the comma.

LocalDate inputDateStart = 
    LocalDateTime.parse( 
        "2018-08-24T12:18:06,166".replace( "," , "." )  // Unfortunately, the *java.time* classes fail to support the comma and instead only period. This runs contrary to the ISO 8601 standard which allows both and prefers comma.
    )
    .toLocalDate()
;  
LocalDate inputDateStop = 
    LocalDateTime.parse( 
        "2018-08-26T12:19:06,188".replace( "," , "." ) 
    )
    .toLocalDate()
;

You want to work with weeks defined as starting on Sunday. So adjust from your input dates to the Sunday on or before that date.

Note that here we are adding a week to the stop to accommodate the needs of the Question. More commonly we would not do this addition, to follow the Half-Open approach to defining a span-of-time where beginning is inclusive while the ending is exclusive. In contrast to Half-Open, the Question apparently wants Fully-Closed approach where both the beginning and ending are inclusive (I do not recommend this).

LocalDate start = inputDateStart.with( 
    TemporalAdjusters.previousOrSame( DayOfWeek.SUNDAY ) 
);
LocalDate stop = inputDateStop.with( 
    TemporalAdjusters.previousOrSame( DayOfWeek.SUNDAY ) 
)
.plusWeeks( 1 )  // Add one to suit the Question, whereas commonly in date-time work we would have used Half-Open logic.
;  

Define a series of dates as a Stream< LocalDate >. We jump a week at a time by passing a Period of one week.

Stream< LocalDate > stream = 
    startDate
    .datesUntil( 
        stopDate , 
        Period.ofWeeks( 1 ) 
    )
;

If you want, you can see those dates by collecting them from the stream into a list. But note that this exhausts the stream. You'll need to re-establish the stream to continue our code further down.

List< LocalDate > dates = stream.collect( Collectors.toList() );
System.out.println( dates );

[2018-08-19, 2018-08-26]

Run through that series of dates in the stream. On each LocalDate object, get the week number. Collect each returned week number as a Integer object, all collected in a List.

List< Integer > weekNumbers = 
    stream
    .map( 
        localDate -> localDate.get( WeekFields.SUNDAY_START.weekOfWeekBasedYear() ) 
    )
    .collect( Collectors.toList() )
;

Dump to console.

System.out.println( weekNumbers );

[34, 35]

One-liner

If you really want to go crazy with brevity, we can do all this in one line of code. I do not recommend this, but it is fun to try.

System.out.println(
    LocalDateTime.parse( "2018-08-24T12:18:06,166".replace( "," , "." ) ).toLocalDate()
    .with( TemporalAdjusters.previousOrSame( DayOfWeek.SUNDAY ) )
    .datesUntil(
        LocalDateTime.parse( "2018-08-26T12:19:06,188".replace( "," , "." ) ).toLocalDate()
        .with( TemporalAdjusters.previousOrSame( DayOfWeek.SUNDAY ) )
        .plusWeeks( 1 ) 
        ,
        Period.ofWeeks( 1 )
    )
    .map( localDate -> localDate.get( WeekFields.SUNDAY_START.weekOfWeekBasedYear() ) )
    .collect( Collectors.toList() )
);

[34, 35]

Upvotes: 4

Anonymous
Anonymous

Reputation: 86203

public static void numberOfWeeks(LocalDateTime startDateTime, LocalDateTime endDateTime) {
    if (startDateTime.isAfter(endDateTime)) {
        throw new IllegalArgumentException("End date must not be before start date");
    }

    LocalDate endDate = endDateTime.toLocalDate();
    List<String> numberWeeks = new ArrayList<>();
    LocalDate currentDate = startDateTime.toLocalDate();
    while (currentDate.isBefore(endDate)) {
        numberWeeks.add(formatWeek(currentDate));
        currentDate = currentDate.plusWeeks(1);
    }
    // Now currentDate is on or after endDate, but are they in the same week?
    if (currentDate.get(WeekFields.SUNDAY_START.weekOfWeekBasedYear()) 
            == endDate.get(WeekFields.SUNDAY_START.weekOfWeekBasedYear())) {
        numberWeeks.add(formatWeek(currentDate));
    }

    System.out.println(numberWeeks);
}

public static String formatWeek(LocalDate currentDate) {
    return String.format("%d-W%d", 
            currentDate.get(WeekFields.SUNDAY_START.weekBasedYear()), 
            currentDate.get(WeekFields.SUNDAY_START.weekOfWeekBasedYear()));
}

With the methods above your main method from the question prints:

[2018-W34, 2018-W35]

I see that you have ignored the other answer in the linked question, the one using YearWeek from the ThreeTen Extra library. So I assumed you didn’t want to do that. So I am using LocalDate for the weeks.

While a couple of users have failed to reproduce your exact issue, I do agree that your code in the question is flawed.

Upvotes: 4

Related Questions