5

I develop a local calendar for my application. but there is an issue with monthly repeat event (day of week).

When i create an event starting on 16-9-2016(16 SEP 2016 FRIDAY) and repeating Third Friday of each month. but next month it create on second Friday 14-10-2016 (This is the issue). next month it will be on third Friday.

my code is

public Date nthWeekdayOfMonth(int dayOfWeek, int month, int year, int week, TimeZone timeZone) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeZone(timeZone);
        calendar.set(Calendar.DAY_OF_WEEK, dayOfWeek);
        calendar.set(Calendar.WEEK_OF_MONTH, week);
        calendar.set(Calendar.MONTH, month);
        calendar.set(Calendar.YEAR, year);
        return calendar.getTime();
    }

I know the issue. but i don`t know how to fix it.. is there any way to fix it ?

Ajmal Muhammad
  • 685
  • 7
  • 25
  • Hint: if possible, use the Java8 Date/Time Apis instead of the good ol' Calendar. Calendar just s***s big time. – GhostCat Sep 16 '16 at 09:47
  • @GhostCat sorry our system using Java7. is there any way to fix it by using Java7 calendar – Ajmal Muhammad Sep 16 '16 at 10:00
  • If you are on Java7, you could try [Joda Time](http://www.joda.org/joda-time/) and then [this answer](http://stackoverflow.com/questions/20527998/get-all-fridays-in-a-date-range-in-java) contains a sample code. – DVarga Sep 16 '16 at 10:01
  • Where are you facing issues? I just tried this out with the following parameters and it works for me: `nthWeekdayOfMonth(6, 9, 2016, 3, TimeZone.getTimeZone("Europe/London"));`, returns: `Fri Oct 21 10:59:09 BST 2016` unless, I've done a parameter wrong... – px06 Sep 16 '16 at 10:01

9 Answers9

4

You code seems to be working completely fine, there is nothing that is going wrong from what I can see, it may be that your parameters are wrong.

It is important to note that MONTH and DAY are 0-based so, 0 = January and 0 = Sunday so your parameters for getting the third friday should look like the following:

nthWeekdayOfMonth(6, 9, 2016, 3, TimeZone.getTimeZone("Europe/London"));

Which returns the following output:

Fri Oct 21 11:06:33 BST 2016

To break it down:

  1. Day of week is 6, because Sunday = 0.
  2. Month is 9 - i.e. October
  3. Year is normal - 2016
  4. Week is NOT 0-based so 3rd week will be index 3
  5. TimeZone as normal

Please see the Calendar documentation for reference.


EDIT

So for some reason, it works on my machine but it doesn't on others; I don't know what the issue could be with that but using DAY_OF_WEEK_IN_MONTH seems to be a better option for this:

public static Date nthWeekdayOfMonth(int dayOfWeek, int month, int year, int week, TimeZone timeZone) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTimeZone(timeZone);
    calendar.set(Calendar.DAY_OF_WEEK, dayOfWeek);
    //calendar.set(Calendar.WEEK_OF_MONTH, week);
    calendar.set(Calendar.DAY_OF_WEEK_IN_MONTH, week);
    calendar.set(Calendar.MONTH, month);
    calendar.set(Calendar.YEAR, year);
    return calendar.getTime();
}

I usually use GregorianCalendar but Calendar should work just fine.

This should (hopefully) work for the most part, I've tested it on other machines and ideone.

px06
  • 2,256
  • 1
  • 27
  • 47
  • Fri Oct 14 20:16:10 IST 2016 i have getting – Ajmal Muhammad Sep 16 '16 at 10:17
  • i am using java7 calendar. and timezone INDIAN timezone (GMT +5:30) – Ajmal Muhammad Sep 16 '16 at 10:18
  • @AjmalMuhammad How are you setting your TimeZone? What TimeZone are you using? – px06 Sep 16 '16 at 10:19
  • @AjmalMuhammad I just set `TimeZone.getTimeZone("IST");` and also `TimeZone.getTimeZone("Asia/Kolkata");` and it still works totally fine, can you post some more code so I can see where exactly you're going wrong... – px06 Sep 16 '16 at 10:30
  • From the docs, the first week of the month is “defined by `getFirstDayOfWeek()` and `getMinimalDaysInFirstWeek()`”. So you need to know what these are to control what your code does. – Ole V.V. Sep 16 '16 at 10:34
  • @OleV.V. I don't believe that's the case by just doing trail and error here, the Calendar API seems to know where the `FirstDayOfWeek` is for a given month, and counts from there, I don't see any issues with OPs code at all, I've tried it in several different ways and it gives correct output. I'm using Java8 but with the native Calendar library which shouldn't make any difference. – px06 Sep 16 '16 at 10:37
  • @OleV.V. Here is the running code for you to see that it's working: https://ideone.com/DFpkW8 – px06 Sep 16 '16 at 10:41
  • On my Mac with Java 8 `System.out.println(nthWeekdayOfMonth(6, 9, 2016, 3, TimeZone.getTimeZone("Europe/London")));` prints `Fri Oct 14 13:46:30 CEST 2016`. It does your other example dates correctly. `calendar.getFirstDayOfWeek());` and `calendar.getMinimalDaysInFirstWeek()` both return 1. – Ole V.V. Sep 16 '16 at 10:48
  • @px06 please check your output it will print Fri Oct 14 05:12:37 GMT 2016 – Ajmal Muhammad Sep 16 '16 at 10:49
  • @OleV.V. This is extremely weird, and I have no idea what could be causing this issue... I don't think I've set any date specifically for java or anything, in any case I've updated the answer to include a better solution that doesn't require much change. Could you test that and let me know your findings? – px06 Sep 16 '16 at 10:56
  • @px06, For the 15 coming months (the 6 from your previous example and the following 9) the new version of your code gives correct results on my Mac. – Ole V.V. Sep 16 '16 at 11:11
  • @OleV.V. thanks! In that case I would assume this should work for OP and also _should_ work in the future. – px06 Sep 16 '16 at 11:12
  • Agree, Upvoted. PS `DAY_OF_WEEK` and `DAY_OF_WEEK_IN_MONTH` are both declared in `Calendar`, so I’d find it nicer to avoid using `GregorianCalendar` in the code (even though the instance will be of `GregorianCalendar`). – Ole V.V. Sep 16 '16 at 11:20
  • @OleV.V. Yes you're right, I've updated the answer to look a little more clean. – px06 Sep 16 '16 at 11:26
3

I could propose next decision:

public Date nthWeekdayOfMonth(int dayOfWeek, int month, int year, int week, TimeZone timeZone) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTimeZone(timeZone);
    calendar.set(Calendar.YEAR, year);
    calendar.set(Calendar.MONTH, month);
    calendar.set(Calendar.DAY_OF_MONTH, 1);
    // add +1 to week if first weekday of mounth > dayOfWeek
    int localWeek = week;
    if (calendar.get(calendar.DAY_OF_WEEK) > dayOfWeek) {
        localWeek++;
    }
    calendar.set(Calendar.WEEK_OF_MONTH, localWeek);
    calendar.set(Calendar.DAY_OF_WEEK, dayOfWeek);
    return calendar.getTime();
}

for:

System.out.println(nthWeekdayOfMonth(Calendar.FRIDAY, Calendar.SEPTEMBER, 2016, 3, TimeZone.getTimeZone("Europe/London")));
System.out.println(nthWeekdayOfMonth(Calendar.FRIDAY, Calendar.OCTOBER, 2016, 3, TimeZone.getTimeZone("Europe/London")));
System.out.println(nthWeekdayOfMonth(Calendar.FRIDAY, Calendar.NOVEMBER, 2016, 3, TimeZone.getTimeZone("Europe/London")));

it returns:

Fri Sep 16 19:41:23 YEKT 2016
Fri Oct 21 19:41:23 YEKT 2016
Fri Nov 18 20:41:23 YEKT 2016
mv200580
  • 702
  • 1
  • 6
  • 14
3

Java 8

LocalDate thirdFriday = java.time.LocalDate.now()
                        .with(TemporalAdjusters.firstDayOfMonth())
                        .with(TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY))
                        .plusDays(14)
alex314159
  • 3,159
  • 2
  • 20
  • 28
3

java.time

java.time, the modern Java date and time API, has a built-in adjuster for that:

public LocalDate nthWeekdayOfMonth(DayOfWeek dayOfWeek, Month month, int year, int week) {
    return LocalDate.of(year, month, 15)
            .with(TemporalAdjusters.dayOfWeekInMonth(week, dayOfWeek));
}

Try it out:

    System.out.println(nthWeekdayOfMonth(DayOfWeek.FRIDAY, Month.OCTOBER, 2016, 3));

Output:

2016-10-21

Please also note that the arguments that I pass to the method are much more telling.

Link: Oracle tutorial: Date Time explaining how to use java.time.

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

This is a functioning Java 8 implementation. The example from KayV did not work for September 2017, but it helped me to head in the right direction.

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.Month;
import java.time.temporal.TemporalAdjusters;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class OptionExpirationDates {

    public static void main(String[] args) {

        LocalDate startDate = LocalDate.of(2017, Month.FEBRUARY, 15);
        List<LocalDate> optionExDates = optionExpirationDates(startDate, 20);

        for (LocalDate temp : optionExDates) {
            System.out.println(temp);
        } 
    }

    public static List<LocalDate> optionExpirationDates(LocalDate startDate, int limit) {
        return Stream.iterate(startDate, date -> date.plusDays(1))
                .map(LocalDate -> LocalDate.with(TemporalAdjusters.firstDayOfMonth()).minusDays(1)
                        .with(TemporalAdjusters.next(DayOfWeek.FRIDAY)).plusWeeks(2))
                .distinct()
                .limit(limit)
                .collect(Collectors.toList());
    }
}

Perhaps we should also mention that this code is to calculate an option expiration date, so that the search engine can pick it up.

sdittmar
  • 365
  • 2
  • 14
1

Java 8 way of doing this is as follows:

LocalDate thirdFriday = LocalDate
                            .now()
                            .with(lastDayOfMonth())
                            .with(previous(DayOfWeek.FRIDAY)).minusDays(7);
KayV
  • 12,987
  • 11
  • 98
  • 148
  • `import static java.time.temporal.TemporalAdjusters.lastDayOfMonth; import static java.time.temporal.TemporalAdjusters.previous;` – riddle_me_this Jul 22 '20 at 02:12
0

To take a different approach. If the first day of the month is a Saturday, then the third Friday is the 21st of that month. Extend this for the seven possible days:

  • Saturday 1st -> Friday 21st.
  • Sunday 1st -> Friday 20th
  • Monday 1st -> Friday 19th
  • Tuesday 1st -> Friday 18th
  • Wednesday 1st -> Friday 17th
  • Thursday 1st -> Friday 16th
  • Friday 1st -> Friday 15th

You just need to check what day of the week the first of the month is.

rossum
  • 15,344
  • 1
  • 24
  • 38
0

Below function can be used to calculate third friday of month using joda time. The function is verbose for sake of clarity on logic.

   public static DateTime thirdFridayOfMonth(int year, int month) {
         DateTime firstDayOfMonth = new DateTime(year, month, 1, 0, 0);
         MutableDateTime mFirstDayOfMonth = new MutableDateTime(firstDayOfMonth);

         //Now calculate days to 1st friday from 1st day of month
         int daysToFirstFridayOfMonth = mFirstDayOfMonth.dayOfWeek().get() <= 5 ? (5 - mFirstDayofMonth.dayOfWeek().get()) : (7 - mFirstDayofMonth.dayOfWeek().get() + 5);

         //move to first 1st friday of month
         mFirstDayOfMonth.addDays(daysToFirstFridayOfMonth);

        //move to 3rd friday of month
        mFirstDayOfMonth.addWeeks(2);
        return mFirstDayOfMonth.toDateTime();
    }
shagun akarsh
  • 159
  • 2
  • 14
0

with Java LocalDateTime

LocalDateTime firstDayOfMonth = LocalDateTime.of(year, Month.of(month), 1, 0, 0);
// Returns 1-7 (NOT 0-6)
int firstDayValue = firstDayOfMonth.getDayOfWeek().getValue();
int thirdFriday = 20 + firstDayValue / 6 * 7 - firstDayValue;    
return LocalDateTime.of(year, Month.of(month), thirdFriday, 0, 0);