There is a constant in the Calendar class called: UNDECIMBER. It describes the 13th month.
Is there a useful purpose for this constant? In Wikipedia it is written that it is for the lunar calendar. But there is no implementation for such calendar.
And does there exist any solutions for the 14th month (Duodecimber)?
I didn't found so much in the web, and I would like to find out more about this topic.
As already said, some lunar (and other ancient) calendars have 13 months. One example is the Coptic Calendar.
Although there are no implementations of calendars with 13 months that extends java.util.Calendar, in Java 8's new API there are some. With the introduction of the new java.time API, it was also created the ThreeTen Extra project, which contains an implementation for that.
The class is org.threeten.extra.chrono.CopticChronology, which extends the native java.time.chrono.Chronology. I've just made a sample code to create a date in this calendar and loop through its months:
// Coptic calendar
CopticChronology cal = CopticChronology.INSTANCE;
// range for month of year (from 1 to 13)
System.out.println("month range: " + cal.range(ChronoField.MONTH_OF_YEAR)); // 1 - 13
// getting a date in Coptic calendar and loop through the months
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd/MM/yyyy");
// September 11th is equivalent to 01/01 in Coptic calendar
CopticDate d = cal.date(LocalDate.of(2017, 9, 11));
for (int i = 0; i < 14; i++) {
System.out.println(fmt.format(d));
d = d.plus(1, ChronoUnit.MONTHS);
}
The output is:
month range: 1 - 13
01/01/1734
01/02/1734
01/03/1734
01/04/1734
01/05/1734
01/06/1734
01/07/1734
01/08/1734
01/09/1734
01/10/1734
01/11/1734
01/12/1734
01/13/1734
01/01/1735
Note that the year changed just after the 13th month.
The ThreeTen Extra project also has an implementation for the Ethiopian calendar, which has 13 months as well.
And, as an example of a calendar with 14 months, there's the PaxChronology class, which implements the Pax Calendar: a proposed reform calendar system, but not currently in use, as far as I know.
Quoting wikipedia:
The common year is divided into 13 months of 28 days each, whose names are the same as in the Gregorian calendar, except that a month called Columbus occurs between November and December. The first day of every week, month and year would be Sunday.
In leap years, a one-week month called Pax would be inserted after Columbus.
And according to javadoc:
Leap years occur in every year whose last two digits are divisible by 6, are 99, or are 00 and the year is not divisible by 400.
Example:
PaxChronology paxCal = PaxChronology.INSTANCE;
System.out.println("month range: " + paxCal.range(ChronoField.MONTH_OF_YEAR));
PaxDate pd = paxCal.date(1930, 1, 1);
for (int i = 0; i < 15; i++) {
// fmt is the same DateTimeFormatter from previous example
System.out.println(fmt.format(pd));
// adjusting for first day of next month - using TemporalAdjuster because
// adding 1 ChronoUnit.MONTHS throws an exception for 14th month (not sure why)
pd = pd.plus(30, ChronoUnit.DAYS).with(TemporalAdjusters.firstDayOfMonth());
}
Output:
month range: 1 - 13/14
01/01/1930
01/02/1930
01/03/1930
01/04/1930
01/05/1930
01/06/1930
01/07/1930
01/08/1930
01/09/1930
01/10/1930
01/11/1930
01/12/1930
01/13/1930
01/14/1930
01/01/1931
You can notice that the year changes after the 14th month.
The range is 1 - 13/14 because years can have 13 or 14 months, depending if it's a leap year or not.
The Calendar.UNDECIMBER is an additional constant in the Calendar class that is not typically used in the widely used Gregorian Calendar but certain Lunar calendars use a 13th month. That's the purpose of this field.
Refer to the Java Docs below:
https://docs.oracle.com/javase/7/docs/api/java/util/Calendar.html#UNDECIMBER
Wikipedia article for this:
https://en.wikipedia.org/wiki/Undecimber
There is also a mention of a 14th month - Duodecimber in the wiki. Unfortunately, Java does not (yet) support that.
Hope this helps!
Related
I'm having an issue with Date library current week. When you google it's week 11, but my function returns 12. In terms of location, I am in South Africa, but the same issue is reported by someone in Germany who is testing the application. I wrote below code to test this in a small piece of code and I see when I get calendar using new Date() library, it's returning 12, but when I use Localdate.now() it's returning 11.
I updated the Dockerfile jdk initially it was jdk8 and now it's openjdk11. Can this be the reason I am getting different results? Is new Date() no longer supported in Java 11?
Below is the code snippet. I would love if some can assist me on this.
package com.company;
import java.time.LocalDate;
import java.time.temporal.ChronoField;
import java.util.Calendar;
import java.util.Date;
public class CalenderLibrary {
private static Calendar cal = Calendar.getInstance();
public static void main(String[] args) {
Date date = new Date();
String calendarWeekFromEvent = getCalendarWeekFromEventWithDate(date);
System.out.println("Date: {} " + calendarWeekFromEvent);
LocalDate localDate = LocalDate.now();
String localDate1 = getCalendarWeekFromEventWithLocalDate(localDate);
System.out.println("LocalDate: {} " + localDate1);
}
private static String getCalendarWeekFromEventWithLocalDate(LocalDate date) {
return String.valueOf(date.get(ChronoField.ALIGNED_WEEK_OF_YEAR));
}
private static String getCalendarWeekFromEventWithDate(Date date) {
cal.setTime(date);
return String.valueOf(cal.get(Calendar.WEEK_OF_YEAR));
}
}
Results:
Date: {} 12
LocalDate: {} 11
java.util.Date and java.util.Calendar are not deprecated, and still work as they always have. However, how they work is difficult to use, which is why java.time classes are recommended instead.
What you are seeing is the difference between WEEK_OF_YEAR (which depends on the locale) and ALIGNED_WEEK_OF_YEAR (which is the same in all locales).
When setting or getting the WEEK_OF_MONTH or WEEK_OF_YEAR fields, Calendar must determine the first week of the month or year as a reference point. The first week of a month or year is defined as the earliest seven day period beginning on getFirstDayOfWeek() and containing at least getMinimalDaysInFirstWeek() days of that month or year. Weeks numbered ..., -1, 0 precede the first week; weeks numbered 2, 3,... follow it. Note that the normalized numbering returned by get() may be different. For example, a specific Calendar subclass may designate the week before week 1 of a year as week n of the previous year.
ALIGNED_WEEK_OF_YEAR represents concept of the count of weeks within the period of a year where the weeks are aligned to the start of the year. This field is typically used with ALIGNED_DAY_OF_WEEK_IN_YEAR.
For example, in a calendar systems with a seven day week, the first aligned-week-of-year starts on day-of-year 1, the second aligned-week starts on day-of-year 8, and so on. Thus, day-of-year values 1 to 7 are in aligned-week 1, while day-of-year values 8 to 14 are in aligned-week 2, and so on.
And for the locale difference:
// returns DayOfWeek.MONDAY
WeekFields.of(Locale.forLanguageTag("de-DE")).getFirstDayOfWeek();
// returns DayOfWeek.SUNDAY
WeekFields.of(Locale.forLanguageTag("en-ZA")).getFirstDayOfWeek();
To get the unaligned week of year using the current system local with java.time:
LocalDate.now().get(WeekFields.of(Locale.getDefault()).weekOfYear())
If you want to be locale-independent, then there is ISO.weekOfYear() if you want the week to start on a Monday, and SUNDAY_START.weekOfYear() for a Sunday.
The old, much-derided Date and Calendar classes have always been confusing and difficult to use properly, particularly in a multi-threaded context.Java 8’s JSR 310 implementation offers specific classes for:
The old date library included only a single time representation class – java.util.Date, which despite its name, is actually a timestamp. It only stores the number of milliseconds elapsed since the Unix epoch.
I am looking to build up a function that return an array with all week numbers of the previous months in a year that are the same week number of one particular month.
I am using as first day of week Monday and I am taking as first week of month week with the first Monday of current month.
Input: week of year and year. For example, 27 and 2019. The first week of July (7).
Output: array of week of months. For example, [2, 6, 10, 14, 19, 23, 27].
What I try:
private void getResult(int weekYear)
{
LocalDate date = LocalDate.now();
final int weekNumber = 27;
LocalDate newDate = date.with(IsoFields.WEEK_OF_WEEK_BASED_YEAR, weekNumber);
int month = newDate.getMonthValue();;
int weekMonth = LocalDate.from(newDate).get(WeekFields.ISO.weekOfMonth());
System.out.println(newDate);
System.out.println(month);
System.out.println(weekMonth);
ArrayList<Integer> list = new ArrayList<Integer>();
for (int i = 1; i <= month; i++)
{
LocalDate tempDate = date.withYear(2019).withMonth(i).with(WeekFields.ISO.weekOfMonth(), weekMonth);
int tempYear = LocalDate.from(tempDate).get(WeekFields.ISO.weekOfWeekBasedYear());
list.add(tempYear);
}
list.forEach((e) -> System.out.print(e + " "));
}
int weekYear = 27;
getResult(weekYear);
What I get: [1 6 10 14 18 23 27].
What I am looking for: I have two question:
First one: the results obtained are different from those expected. I think the problem is due to the fact that I didn't specify how to calculate the first week of the month (first Monday of the month). Is it right? How can I solve that?
Second one: What is a better solution?
The key here is understanding a few points:
You are numbering weeks in two different ways. For the week of year you are using ISO numbering: the first week of the year is the one that includes at least 4 days of the new year. For week of month you are counting the Mondays (you may say that the first week of the month is the one that includes seven days of the month, not four).
The week number may not always exist. If your starting point is in 0th or the 5th week of the month, a preceding month may not have that week in it.
the results obtained are different from those expected. I think the
problem is due to the fact that I didn't specify how to calculate the
first week of the month (first Monday of the month). Is it right? How
can I solve that?
You are correct. To count the Mondays of the month you may use:
LocalDate tempDate = date.withYear(2019)
.withMonth(i)
.with(ChronoField.DAY_OF_WEEK, DayOfWeek.MONDAY.getValue())
.with(ChronoField.ALIGNED_WEEK_OF_MONTH, weekMonth);
(DayOfWeek.MONDAY.getValue() is just of wordy way of saying 1, of course, but conveys the intention better, so I prefer it.)
With this change to your code the output is the expected:
2 6 10 14 19 23 27
The key is ChronoField.ALIGNED_WEEK_OF_MONTH. The aligned weeks of a month start from the 1st of the month and are always 7 days regardless of the days of the week. The first aligned week is from the 1st through the 7th of the month, the 2nd aligned week if from 8th through 14th, etc. Since we have set the day of week to Monday, setting the aligned week to 1 gives us the 1st Monday of the month, etc.
We’re not done yet. If I set weekNumber to 40, I get:
2 6 10 14 14 23 27 27 36 41
I had expected 40 to be the last number in the list, but it is not there. Week 40 of 2019 is from Monday September 30 through October 6, so if I understand correctly you want the 5th week of those months that have a 5th week. This brings us back to the issue of not all month having a week 5 (because they don’t have 5 Mondays). What happened was that since I ran your code on a Tuesday, it took Tuesday in week 40, which is October 1, as a starting point, and therefore gave me the 1st rather than the 5th week of every month.
are there better solutions? Can you suggest one?
I can’t really. What you’ve got is fine.
Only you’re not using the int weekYear parameter. You may want to use it in place of your weekNumber local variable. In any case you should delete one of them and use the other.
And this unrelated tip: Your use of LocalDate.from(someLocalDate) is redundant since it just gives you the same LocalDate again (either the same object or an equal one, I don’t know or care). Just use someLocalDate in those situations.
I was studying the old Calendar API to see how bad it was, and I found out that Calendar has a roll method. Unlike the add method, roll does not change the values of bigger calendar fields.
For example, the calendar instance c represents the date 2019-08-31. Calling c.roll(Calendar.MONTH, 13) adds 13 to the month field, but does not change the year, so the result is 2019-09-30. Note that the day of month changes, because it is a smaller field.
Related
I tried to find such a method in the modern java.time API. I thought such a method has to be in LocalDate or LocalDateTime, but I found nothing of the sort.
So I tried to write my own roll method:
public static LocalDateTime roll(LocalDateTime ldt, TemporalField unit, long amount) {
LocalDateTime newLdt = ldt.plus(amount, unit.getBaseUnit());
return ldt.with(unit, newLdt.get(unit));
}
However, this only works for some cases, but not others. For example, it does not work for the case described in the documentation here:
Consider a GregorianCalendar originally set to Sunday June 6, 1999.
Calling roll(Calendar.WEEK_OF_MONTH, -1) sets the calendar to Tuesday
June 1, 1999, whereas calling add(Calendar.WEEK_OF_MONTH, -1) sets the
calendar to Sunday May 30, 1999. This is because the roll rule imposes
an additional constraint: The MONTH must not change when the
WEEK_OF_MONTH is rolled. Taken together with add rule 1, the resultant
date must be between Tuesday June 1 and Saturday June 5. According to
add rule 2, the DAY_OF_WEEK, an invariant when changing the
WEEK_OF_MONTH, is set to Tuesday, the closest possible value to Sunday
(where Sunday is the first day of the week).
My code:
System.out.println(roll(
LocalDate.of(1999, 6, 6).atStartOfDay(),
ChronoField.ALIGNED_WEEK_OF_MONTH, -1
));
outputs 1999-07-04T00:00, whereas using Calendar:
Calendar c = new GregorianCalendar(1999, 5, 6);
c.roll(Calendar.WEEK_OF_MONTH, -1);
System.out.println(c.getTime().toInstant());
outputs 1999-05-31T23:00:00Z, which is 1999-06-01 in my timezone.
What is an equivalent of roll in the java.time API? If there isn't one, how can I write a method to mimic it?
First, I cannot remember having seen any useful application of Calendar.roll. Second, I don’t think that the functionality is very well specified in corner cases. And the corner cases would be the interesting ones. Rolling month by 13 months would not be hard without the rollmethod. It may be that similar observations are the reasons why this functionality is not offered by java.time.
Instead I believe that we would have to resort to more manual ways of rolling. For your first example:
LocalDate date = LocalDate.of(2019, Month.JULY, 22);
int newMonthValue = 1 + (date.getMonthValue() - 1 + 13) % 12;
date = date.with(ChronoField.MONTH_OF_YEAR, newMonthValue);
System.out.println(date);
Output:
2019-08-22
I am using the fact that in the ISO chronology there are always 12 months in the year. Since % always gives a 0-based result, I subtract 1 from the 1-based month value before the modulo operation and add it back in afterwards And I am assuming a positive roll. If the number of months to roll may be negative, it gets slightly more complicated (left to the reader).
For other fields I think that a similar approach will work for most cases: Find the smallest and the largest possible value of the field given the larger fields and do some modulo operation.
It may become a challenge in some cases. For example, when summer time (DST) ends and the clock is turned backward from 3 to 2 AM, so the day is 25 hours long, how would you roll 37 hours from 6 AM? I’m sure it can be done. And I am also sure that the functionality is not built in.
For your example with rolling the week of month, another difference between the old and the modern API comes into play: a GregorianCalendar not only defines a calendar day and time, it also defines a week scheme consisting of a first day of the week and a minimum number of days in the first week. In java.time the week scheme is defined by a WeekFields object instead. So while rolling the week of month may be unambiguous in GregorianCalendar, without knowing the week scheme it isn’t with LocalDate or LocalDateTime. An attempt may be to assume ISO weeks (start on Monday, and the first week is the on that has at least 4 days of the new month in it), but it may not always be what a user had intended.
Week of month and week of year are special since weeks cross month and year boundaries. Here’s my attempt to implement a roll of week of month:
private static LocalDate rollWeekOfMonth(LocalDate date, int amount, WeekFields wf) {
LocalDate firstOfMonth = date.withDayOfMonth(1);
int firstWeekOfMonth = firstOfMonth.get(wf.weekOfMonth());
LocalDate lastOfMonth = date.with(TemporalAdjusters.lastDayOfMonth());
int lastWeekOfMonth = lastOfMonth.get(wf.weekOfMonth());
int weekCount = lastWeekOfMonth - firstWeekOfMonth + 1;
int newWeekOfMonth = firstWeekOfMonth
+ (date.get(wf.weekOfMonth()) - firstWeekOfMonth
+ amount % weekCount + weekCount)
% weekCount;
LocalDate result = date.with(wf.weekOfMonth(), newWeekOfMonth);
if (result.isBefore(firstOfMonth)) {
result = firstOfMonth;
} else if (result.isAfter(lastOfMonth)) {
result = lastOfMonth;
}
return result;
}
Try it out:
System.out.println(rollWeekOfMonth(LocalDate.of(1999, Month.JUNE, 6), -1, WeekFields.SUNDAY_START));
System.out.println(rollWeekOfMonth(LocalDate.of(1999, Month.JUNE, 6), -1, WeekFields.ISO));
Output:
1999-06-01
1999-06-30
Explanation: The documentation you quote assumes that Sunday is the first day of the week (it ends “where Sunday is the first day of the week”; it was probably written in the USA) so there is a week before Sunday June 6. And rolling by -1 week should roll into this week before. My first line of code does that.
In the ISO week scheme, Sunday June 6 belong to the week from Monday May 31 through Sunday June 6, so in June there is no week before this week. Therefore my second line of code rolls into the last week of June, June 28 through July 4. Since we cannot go outside June, June 30 is chosen.
I have not tested whether it behaves the same as GregorianCalendar. For comparison,the GregorianCalendar.roll implementation uses 52 code lines to handle the WEEK_OF_MONTH case, compared to my 20 lines. Either I have left something out of consideration, or java.time once again shows it superiority.
Rather my suggestion for the real world is: make your requirements clear and implement them directly on top of java.time, ignoring how the old API behaved. As an academic exercise, your question is a fun and interesting one.
TL;DR
There is no equivalent.
Think about whether you really need the behavior of roll of java.util.Calendar:
/**
* Adds or subtracts (up/down) a single unit of time on the given time
* field without changing larger fields. For example, to roll the current
* date up by one day, you can achieve it by calling:
* roll(Calendar.DATE, true).
* When rolling on the year or Calendar.YEAR field, it will roll the year
* value in the range between 1 and the value returned by calling
* getMaximum(Calendar.YEAR).
* When rolling on the month or Calendar.MONTH field, other fields like
* date might conflict and, need to be changed. For instance,
* rolling the month on the date 01/31/96 will result in 02/29/96.
* When rolling on the hour-in-day or Calendar.HOUR_OF_DAY field, it will
* roll the hour value in the range between 0 and 23, which is zero-based.
*
* #param field the time field.
* #param up indicates if the value of the specified time field is to be
* rolled up or rolled down. Use true if rolling up, false otherwise.
* #see Calendar#add(int,int)
* #see Calendar#set(int,int)
*/
public void roll(int field, boolean up);
I am running the below code on 6/7/2018 in order to omit weekends from any dates returned. However the code seems to determine the below days as the weekend.
13/7/2018 - Friday & 14/7/2018 - Saturday
rather than
14/7/2018 - Saturday & 15/7/2018 - Sunday
I am updating the field indicated to increase / reduce the amount of days in the future I want to select.
If I input 5 days the date returned is 12/7/2018 and if I input 6 days the date returned is 15/7/2018.
Is there something obvious I am missing, any help would be much appreciated.
Date date=new Date();
Calendar calendar = Calendar.getInstance();
date=calendar.getTime();
SimpleDateFormat s;
s=new SimpleDateFormat("dd/MM/yyyy");
System.out.println(s.format(date));
int days = 5; //I am updating this value to increase and decrease days
for(int i=0;i<days;)
{
calendar.add(Calendar.DAY_OF_MONTH, 1);
//here even sat and sun are added
//but at the end it goes to the correct week day.
//because i is only increased if it is week day
if(calendar.get(Calendar.DAY_OF_WEEK)<=5)
{
i++;
}
}
date=calendar.getTime();
s=new SimpleDateFormat("dd/MM/yyyy");
System.out.println(s.format(date));
DateTimeFormatter dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
.withLocale(Locale.UK);
LocalDate date = LocalDate.now(ZoneId.of("Pacific/Truk"));
System.out.println(date.format(dateFormatter));
int days = 5;
int i = 0;
while (i < days) {
date = date.plusDays(1);
DayOfWeek day = date.getDayOfWeek();
if (! day.equals(DayOfWeek.SATURDAY) && ! day.equals(DayOfWeek.SUNDAY)) {
i++;
}
}
System.out.println(date.format(dateFormatter));
Output today (Sunday 8th July):
08/07/2018
13/07/2018
13th July is next Friday, so obviously it didn’t take Friday as weekend.
Is there something obvious I am missing(?)
It don’t think it’s that obvious: The Calendar class numbers the days of the week from 1 for Sunday through 7 for Saturday. This comes from an American understanding of weeks. So when your condition was that the day of week should be less than or equal to 5, you included Sunday (1) through Thursday (5) and filtered out Friday (6) and Saturday.
…if you could point me in the right direction to the documentation…
To find this information in the documentation you would have to look under each constant for day of week, SUNDAY, etc., and there follow the link Constant Field Values. See the links at the bottom of this answer.
The Calendar class has proved poorly designed (despite attempts to fix the problems with Date) and is now long outdated too. Instead I recommend that you use java.time, the modern Java date and time API. Which I of course do in the snippet above.
One of many problems with Calendar is the use of int for day of week (and other items that have names rather than being numbers). It’s unnatural and very easy to confuse. One may say that you reinforced the problem by comparing to 5 rather than to Calendar.FRIDAY, but because of the American numbering the latter wouldn’t have solved your issue either. java.time’s DayOfWeek is an enum and doesn’t invite for comparing using “less than” or “is before” (though you may, and it would work in your case). The code referring to named constants SATURDAY and SUNDAY is not only clearer to read, it is also less error-prone.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Documentation of LocalDate and DayOfWeek
Calendar.SUNDAYdocumentation
Constant Field Values documentation
I need to get the date by passing these parameters
year
week number (in a month) i.e. 1,2,3,4,5
day number (in a week) 0 (Sunday) to 6 (Saturday)
Month
I looked for a constructor in Calendar class but does not contain these parameters.
In spite of your tags I agree with Joe C’s comment, you should prefer the modern Java date and time API (AKA known as java.time or JSR-310) over the long outdated Calendar and friends. The modern classes are more programmer friendly and nicer to work with, they come with fewer surprises, lead to clearer and more natural code (at least when you get used to the fluent style) and are easier to debug should you make a bug.
I don’t know how you count your weeks of the month. I have assumed that the first week starts on the first of the month and lasts 7 days (so the second week starts on the 8th and the 5th week on the 29th day of the month). If you count differently, please see Hugo’s answer.
I suggest this method:
public static LocalDate weekNoAndDayToDate(Year year, Month month, int weekOfMonth, int dayOfWeek) {
// find day of week: convert from 0 (Sunday) to 6 (Saturday)
// to DayOfWeek, from 1 (Monday) to 7 (Sunday)
DayOfWeek dow;
if (dayOfWeek == 0) { // Sunday
dow = DayOfWeek.SUNDAY;
} else {
dow = DayOfWeek.of(dayOfWeek);
}
return year.atMonth(month)
.atDay(1)
.with(TemporalAdjusters.nextOrSame(dow))
.with(ChronoField.ALIGNED_WEEK_OF_MONTH, weekOfMonth);
}
With this we may do for example
LocalDate today = weekNoAndDayToDate(Year.of(2017), Month.JULY, 1, 1);
This yields
2017-07-03
If you need either a Date or a GregorianCalendar, for example for an old API that you cannot change, do one of the following:
Date oldfashiondDateObject
= Date.from(today.atStartOfDay(ZoneId.systemDefault()).toInstant());
GregorianCalendar oldfashionedCalendarObject
= GregorianCalendar.from(today.atStartOfDay(ZoneId.systemDefault()));
In both cases the result will be different in different time zones (one of the inherent troubles of the old classes). On my computer the first yields a Date of
Mon Jul 03 00:00:00 CEST 2017
The second yields a GregorianCalendar equal to the same point in time.
To create date from year, week number and week's day use java.util.Calendar instance:
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, 2017);
cal.set(Calendar.WEEK_OF_YEAR, 26);
cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
To convert from Calendar to java.util.Date :
Date date = cal.getTime();
To convert Date into java.time.LocalDateTime use :
LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
As #Ole V.V. explained, you need to define in what day the week starts, and how many days it must have to be considered the first week.
For example, the ISO-8601 standard considers:
Monday to be the first day-of-week.
The minimal number of days in the first week: the standard counts the first week as needing at least 4 days
The month is divided into periods where each period starts on the defined first day-of-week. The earliest period in the same month is referred to as week 0 if it has less than the minimal number of days and week 1 if it has at least the minimal number of days.
Depending on how you define those, you can have different results. Consider the calendar for July 2017:
July 2017
Su Mo Tu We Th Fr Sa
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
If we consider ISO's definition, we have:
week zero - 2017-07-01 to 2017-07-02
week 1: from 2017-07-03 to 2017-07-09
week 2: from 2017-07-10 to 2017-07-16
week 3: from 2017-07-17 to 2017-07-22
week 4: from 2017-07-23 to 2017-07-30
week 5: 2017-07-31
If we consider first day of week as Sunday and minimal number of days in the first week as 1, we have:
week 1: 2017-07-01
week 2: from 2017-07-02 to 2017-07-08
week 3: from 2017-07-09 to 2017-07-15
week 4: from 2017-07-16 to 2017-07-21
week 5: from 2017-07-22 to 2017-07-29
week 6: from 2017-07-30 to 2017-07-31
As a solution with Calendar was already posted, I'm gonna write one using the new API. If you're using Java 8, consider using the new java.time API. It's easier, less bugged and less error-prone than the old APIs.
If you're using Java <= 7, you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes. And for Android, there's the ThreeTenABP (more on how to use it here).
The code below works for both.
The only difference is the package names (in Java 8 is java.time and in ThreeTen Backport (or Android's ThreeTenABP) is org.threeten.bp), but the classes and methods names are the same.
I'm using a DateTimeFormatter because it takes all the fields (month, year, day of week and week of month) and resolves the day accordingly, creating a LocalDate (which has the day/month/year fields). I'm also using the WeekFields class, which can be configured to use different week definitions (first day and minimal number of days in first week)
There's also a little adjustment to consider Sunday as zero:
public LocalDate getDate(int weekOfMonth, int dayOfWeek, int month, int year) {
// you can customize your week definition (first day of week and mininum days in first week)
WeekFields wf = WeekFields.of(DayOfWeek.SUNDAY, 2);
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
// week of month, using week definition from WeekFields
.appendValue(wf.weekOfMonth()).appendPattern(" ")
// day of week
.appendValue(ChronoField.DAY_OF_WEEK)
// month and year
.appendPattern(" M/yyyy")
// create formatter
.toFormatter();
return LocalDate.parse(weekOfMonth + " " +
// Sunday is 0, adjusting value
(dayOfWeek == 0 ? 7 : dayOfWeek) + " " + month + "/" + year, fmt);
}
Using this code (week starts on Sunday, and 2 days are required to be considered the first week - otherwise week will be zero as in the first example above):
LocalDate d = getDate(1, 6, 7, 2017);
d will be 2017-07-08 (Saturday in the week 1 of July 2017).
If you want to use ISO 8601 definition, use the constant WeekFields.ISO instead of using WeekFields.of() method.
As #Ole V.V. suggested in the comments, it can also be done without creating a formatter: get the first dayOfWeek of the month and adjust it to the desired weekOfMonth:
public LocalDate getDate(int weekOfMonth, int dayOfWeek, int month, int year) {
// you can customize your week definition (first day of week and mininum days in first week)
WeekFields wf = WeekFields.of(DayOfWeek.SUNDAY, 2);
// Sunday is 0, adjusting value
DayOfWeek dow = DayOfWeek.of(dayOfWeek == 0 ? 7 : dayOfWeek);
// get the first weekday of the month
LocalDate first = LocalDate.of(year, month, 1).with(TemporalAdjusters.nextOrSame(dow));
// check in which week this date is
int week = first.get(wf.weekOfMonth());
// adjust to weekOfMonth
return first.plusWeeks(weekOfMonth - week);
}
This works exactly the same way, but without the need of a formatter - I've tested with dates from year 1600 to 2100 and it gets the same results.
PS: Calendar also has a similar configuration via the methods setFirstDayOfWeek() and setMinimalDaysInFirstWeek(). You can check the default configuration calling the respective get methods.