简体   繁体   中英

Java: How do I get the date of x day in a month ( e.g. Third Monday in February 2012)

I am somewhat struggling with this.

I want to setup my Calendar to let's say: Third Monday in February 2012. And I didn't find any way of doing this using Java.

For example, if I wanted to set my calendar for Christmas 2011, I can do this easily, this way:

Calendar when = Calendar.getInstance();
when.set (Calendar.MONTH, Calendar.DECEMBER);
when.set (Calendar.DAY_OF_MONTH, 25)
when.set (Calendar.YEAR, 2011);

But I am lost as to how to set it up for let's say Memorial Day 2012, which is the last Monday of May. This is my code, but it's obviously wrong, because I simply can't assume that the last Monday of May will be in the 4th week of May that year:

Calendar when = Calendar.getInstance ();
when.set (Calendar.DAY_OF_WEEK,Calendar.MONDAY);
when.set (Calendar.MONTH, Calendar.MAY);
when.set (Calendar.WEEK_OF_MONTH, 4)
when.set (Calendar.YEAR, 2012);

Any suggestions as to how I can programatically find out, in which week of the month of May 2012 (in example above) is the last Monday? Assuming I can get that information, I should be able to get my code above to work.

I need something which would basically work for any other examples. Something which could give an exact day for the same scenarios. Examples:

Which date is:

  • 3rd Thursday of May 2015
  • 1st Monday of June 2050
  • 4th Tuesday of December 2012
  • 2nd Wednesday of July 2000

I really need this for my project and I am sure it's simple, but I am breaking my head on this without any real results to show for :) And also couldn't find anything on the net.


Added:

Ok, this is where I've got for the last Monday in a month:

when.set (GregorianCalendar.MONTH, GregorianCalendar.MAY);
when.set (GregorianCalendar.DAY_OF_WEEK, Calendar.MONDAY);
when.set (GregorianCalendar.DAY_OF_WEEK_IN_MONTH, -1);
when.set (Calendar.YEAR, 2012);

But I am not sure how would I go about doing for example seconds Monday in the same month, like this?

when.set (GregorianCalendar.DAY_OF_WEEK_IN_MONTH, 2);

Any suggestions?

To do date arithmetic in Java (and in general, to do anything with datetimes, except for the most trivial things) Joda-Time is the answer:

public static LocalDate getNDayOfMonth(int dayweek,int nthweek,int month,int year)  {
   LocalDate d = new LocalDate(year, month, 1).withDayOfWeek(dayweek);
   if(d.getMonthOfYear() != month) d = d.plusWeeks(1);
   return d.plusWeeks(nthweek-1);
}

public static LocalDate getLastWeekdayOfMonth(int dayweek,int month,int year) {
   LocalDate d = new LocalDate(year, month, 1).plusMonths(1).withDayOfWeek(dayweek);
   if(d.getMonthOfYear() != month) d = d.minusWeeks(1);
  return d;
}

public static void main(String[] args) {
   // second wednesday of oct-2011
   LocalDate d = getNDayOfMonth( DateTimeConstants.WEDNESDAY, 2, 10, 2011);
   System.out.println(d);
   // last wednesday of oct-2011
   LocalDate dlast = getLastWeekdayOfMonth( DateTimeConstants.WEDNESDAY,  10, 2011);
   System.out.println(dlast);
}

Edit : Since Java 8 (2014) the new Date API (package java.time ), which is inspired by/similar to Jodatime, should be preferred .

The following code was successfully tested for all holidays in 2013 and 2014. I realize that this doesn't really answer the original question, but I think it might be useful for people who come across this post in hopes of figuring out how to work with holidays using Calendar.

public static boolean isMajorHoliday(java.util.Date date) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);

    // check if New Year's Day
    if (cal.get(Calendar.MONTH) == Calendar.JANUARY
        && cal.get(Calendar.DAY_OF_MONTH) == 1) {
        return true;
    }

    // check if Christmas
    if (cal.get(Calendar.MONTH) == Calendar.DECEMBER
        && cal.get(Calendar.DAY_OF_MONTH) == 25) {
        return true;
    }

    // check if 4th of July
    if (cal.get(Calendar.MONTH) == Calendar.JULY
        && cal.get(Calendar.DAY_OF_MONTH) == 4) {
        return true;
    }

    // check Thanksgiving (4th Thursday of November)
    if (cal.get(Calendar.MONTH) == Calendar.NOVEMBER
        && cal.get(Calendar.DAY_OF_WEEK_IN_MONTH) == 4
        && cal.get(Calendar.DAY_OF_WEEK) == Calendar.THURSDAY) {
        return true;
    }

    // check Memorial Day (last Monday of May)
    if (cal.get(Calendar.MONTH) == Calendar.MAY
        && cal.get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY
        && cal.get(Calendar.DAY_OF_MONTH) > (31 - 7) ) {
        return true;
    }

    // check Labor Day (1st Monday of September)
    if (cal.get(Calendar.MONTH) == Calendar.SEPTEMBER
        && cal.get(Calendar.DAY_OF_WEEK_IN_MONTH) == 1
        && cal.get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY) {
        return true;
    }

    // check President's Day (3rd Monday of February)
    if (cal.get(Calendar.MONTH) == Calendar.FEBRUARY
        && cal.get(Calendar.DAY_OF_WEEK_IN_MONTH) == 3
        && cal.get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY) {
        return true;
    }

    // check Veterans Day (November 11)
    if (cal.get(Calendar.MONTH) == Calendar.NOVEMBER
        && cal.get(Calendar.DAY_OF_MONTH) == 11) {
        return true;
    }

    // check MLK Day (3rd Monday of January)
    if (cal.get(Calendar.MONTH) == Calendar.JANUARY
        && cal.get(Calendar.DAY_OF_WEEK_IN_MONTH) == 3
        && cal.get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY) {
        return true;
    }

    return false;

}

I do not know the "easy" way but I can suggest you the following.

  1. Set calendar to the first day of the month.
  2. Retrieve its day of the week
  3. Calculate the date of the first Monday of the month.
  4. Add 14 days using calendar.add() method. You will get the third Monday.

tl;dr

LocalDate thirdMondayInFebruary2012 = 
    YearMonth.of( 2012 , Month.FEBRUARY )
             .atDay( 1 )
             .with( TemporalAdjusters.dayOfWeekInMonth( 3 , DayOfWeek.MONDAY ) );

…and…

LocalDate lastMondayInMay2012 = 
    YearMonth.of( 2012 , Month.MAY )
             .atDay( 1 );
             .with( TemporalAdjusters.lastInMonth( DayOfWeek.MONDAY ) );

java.time

Very easy to do using the java.time classes that now supplant both Joda-Time and the troublesome old date-time classes bundled with the earliest versions of Java.

First we need to specify the desired month. The YearMonth class can do that. From there we get a LocalDate , a date without a time-of-day and without a time zone.

YearMonth ym = YearMonth.of( 2012 , Month.FEBRUARY ); // Or pass '2' for 'February'.
LocalDate ld = ym.atDay( 1 );

The TemporalAdjuster interface provides for adjusting a date-time value into another date-time value. Implementations provided by the TemporalAdjusters class (notice the plural 's'). Specify a day of week using the handy DayOfWeek enum.

int ordinal = 3 ; // Use '3' for 'third occurrence in month' such as '3rd Monday'.
LocalDate thirdMondayInFebruary2012 = ld.with( TemporalAdjusters.dayOfWeekInMonth( ordinal , DayOfWeek.MONDAY ) );

Tip: Rather than pass mere integers for year and month across your code base, pass objects such as YearMonth , Year , Month , and DayOfWeek . Doing so eliminates ambiguity, makes your code more self-documenting, provides types-safety , and ensures valid values.


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 .

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 .

All you need is a loop:

public class CalculateDate {

public static void main( String ... args ) {

    Calendar c = Calendar.getInstance();
    c.set( Calendar.YEAR, 2012 );
    c.set( Calendar.MONTH , Calendar.MAY);
    c.set( Calendar.DAY_OF_MONTH, 0 );
    c.add( Calendar.DAY_OF_MONTH, -1 );

    System.out.println( c.getTime() );

    int mondaysCount = 0;

    while ( mondaysCount != 4 ) {
        c.add( Calendar.DAY_OF_MONTH, 1 );
        if ( c.get( Calendar.DAY_OF_WEEK ) == Calendar.MONDAY ) {
            mondaysCount++; 
        }       
    }

    System.out.printf( "The fourth monday of may is %s", c.getTime() );     

}

}
public String nDow(int year, int month, int nweek, int nday)
{
    Calendar cdt = Calendar.getInstance();
    cdt.set(year, month -1, 1);
    return year + "-" + month + "-" + (getPosOfWeekday(cdt.get(Calendar.DAY_OF_WEEK), nday) + ((nweek - 1) *7));
}

private int getPosOfWeekday(int startday, int nday)
{
    nday = weekDayValue(nday);
    return constructCircularArray(startday).indexOf(nday) + 1;
}

private ArrayList<Integer> constructCircularArray(int weekday)
{
    ArrayList<Integer> circularArray = new ArrayList<Integer>();
    for(int i = 0; i < 7; i++)
    {
        circularArray.add(i, weekDayValue(weekday++));
    }
    return circularArray;
}

private int weekDayValue(int x)
{
    return ((x-1) % 7) + 1;
}

Any chances you are using Quartz scheduler ?

public Date date(String cronExpression) {
    return new org.quartz.CronExpression(cronExpression).
        getNextValidTimeAfter(new Date(0));
}

System.out.println(date("0 0 0 ? May Thu#3 2015"));
System.out.println(date("0 0 0 ? Jun Mon#1 2050"));
System.out.println(date("0 0 0 ? Dec Tue#4 2012"));
System.out.println(date("0 0 0 ? Jul Wed#2 2000"));

This simple code prints correct (?) results:

Thu May 21 00:00:00 CEST 2015
Mon Jun 06 00:00:00 CEST 2050
Tue Dec 25 00:00:00 CET 2012
Wed Jul 12 00:00:00 CEST 2000

The required CronExpression doesn't have any dependencies on the rest of Quartz, so you might consider copying it to your project (watch out for license!)

Side note: the internal implementation of getNextValidTimeAfter() is 400 lines of code...

Here is an alternative. What it does is: Get which week you want (n), and the other parameters, and return the date of the day in that week. Since Calendar gives the date of the previous month (for example 29th of February instead of 7th of March, since the 1st week of March collides with last week of Feb), the function computes the 2nd week if the date goes beyond 7 or multiples of it for each week thereof. Hope that helps.

public static int getNthWeekDay (int n, int day, int month, int year) {
    Calendar calendar = Calendar.getInstance();

    calendar.set(Calendar.DAY_OF_WEEK, day);
    calendar.set(Calendar.MONTH, month);
    calendar.set(Calendar.WEEK_OF_MONTH,n);
    calendar.set(Calendar.YEAR, year);
    if (calendar.get(Calendar.DATE) > n * 7) {
        calendar.set(Calendar.DAY_OF_WEEK,day);
        calendar.set(Calendar.MONTH, month);
        calendar.set(Calendar.WEEK_OF_MONTH,day+1);

    }
    return calendar.get(Calendar.DATE);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM