0

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);
}
Youcef LAIDANI
  • 55,661
  • 15
  • 90
  • 140
Java Programmer
  • 1,347
  • 3
  • 12
  • 31
  • 1
    Could you please remove the duplicate , because this question is asked for an enhancement purpose @Henry – Java Programmer Aug 29 '18 at 17:27
  • @Henry the OP need more details about my answer that I can't give, I can't understand the logic, my answer for the dup based on the first inputs, now the OP need another logic I suggest to open the question so others can help the OP and me also – Youcef LAIDANI Aug 29 '18 at 17:33
  • 2
    Your first paragraph is confusing. Can you state the business rules more plainly? And give simple examples: input->output. And eliminate the parsing of strings into `LocalDateTime` as that is not relevant to your issue here. – Basil Bourque Aug 29 '18 at 17:48
  • 1
    Since you obviously know how to get the week number of a date (34 and 35 in your example), why don't you just loop from start-week to end-week, instead of that flawed number-of-weeks logic? – Andreas Aug 29 '18 at 17:52
  • 1
    Unable to reproduce. *"if I put `2018-08-22T12:18:06,166` as the start date, i am getting 34,35,36"* I don't. I get `[2018-W34, 2018-W35]`. Running with locale `en_US` and zone id `America/New_York`. – Andreas Aug 29 '18 at 18:12
  • 1
    `2018-08-22T12:18:06,166` as the start date still gets `[2018-W34, 2018-W35]`, https://repl.it/@downshift/ComplicatedBigDeskscan – chickity china chinese chicken Aug 29 '18 at 18:16
  • Same here, output is `[2018-W34, 2018-W35]`. – Joakim Danielson Aug 29 '18 at 18:18
  • 1
    Also, you do know that you are using wrong year and week field combination, right? If you use start `2015-12-20` and end `2016-01-10`, result is `[2015-W52, 2015-W53, 2016-W2, 2016-W3]`. What happened to week 1? You need to use `weekBasedYear()` and `weekOfWeekBasedYear()` to build ISO week strings, as described in [`ISO_WEEK_DATE`](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#ISO_WEEK_DATE). – Andreas Aug 29 '18 at 18:21
  • Define week. Your example dates of `2018-08-24` and `2018-08-26` are both in ISO 8601 standard week `2018-W34`. – Basil Bourque Aug 29 '18 at 18:26
  • 1
    @BasilBourque ISO 8601 standard week starts on Monday. Code specifically uses `WeekFields.SUNDAY_START`, i.e. US weeks, not ISO weeks. – Andreas Aug 29 '18 at 18:27
  • @Andreas Got it, thanks. Deleting my Answer. – Basil Bourque Aug 29 '18 at 18:41
  • @BasilBourque I recognize that "About java.time" section in your now-deleted answer from [this answer](https://stackoverflow.com/a/39215127/5221149) :-) – Andreas Aug 29 '18 at 18:46

2 Answers2

4
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.

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
4

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]

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • Another elegant solution. Really enjoyable. Thanks. – Ole V.V. Aug 29 '18 at 19:46
  • Works. Thank you @Basil – Java Programmer Aug 29 '18 at 21:39
  • your first and last code block has low readability and it makes it hard for reusing it in the future. can you make it more readable so it would have a better impact on other developers? here is something you can do: since you are calling different functions after each other, it is better to write some comments and explain what you are doing. thank you :) – Mohammad Hossein Shojaeinia Mar 12 '19 at 05:17
  • @MohammadHosseinShojaeinia The first and last code blocks are not intended to be explanatory, nor are they intended to be used in production. I would *never* write such code in an app. Thus the cautionary labels [*tl;dr*](https://en.wikipedia.org/wiki/Wikipedia:Too_long;_didn%27t_read) and *go crazy with brevity*. The one-liners provide a quick overview and review for those familiar with the relevant classes and methods. All the stuff I wrote in between the first and last code blocks *is* the explanation. If a point in the middle is not clear, point it out and I will try to clarify. – Basil Bourque Mar 15 '19 at 20:11
  • @BasilBourque thanks for your response, I did not notice the line you said you don't recommend the one-liner. – Mohammad Hossein Shojaeinia Mar 16 '19 at 07:13