Reputation: 5243
For example:
5 February 2016
- first week, 12 February 2016
- second week, 28 February 2016
- last weekUpvotes: 4
Views: 949
Reputation: 44061
Well, it is not quite clear how you define the week of month. For the following discussion, I assume you speak about weeks starting on Monday (like in ISO-8601-standard and in most European countries).
There are two possible ways of definition how to handle the start and end of month when counting the weeks.
Since the start of week on Monday is not necessarily the same as the first day of month, a week can start in previous month or belong to next month.
The JDK-classes SimpleDateFormat
with its field pattern symbol w (and also the new JSR-310-field WeekFields.weekOfMonth()) use following strategy:
If the first day of month is falling on Monday to Thursday then the associated week has at least 4 days in current month and will be counted as week 1 otherwise as week 0 (zero). Consistently the last day of month will always use an incrementing number even if it belongs to first week of next month.
In contrast to that definition, CLDR date-time-pattern specification and ISO-8601 are almost silent about the details in context of week-of-month. However, these standards are not silent about the week-of-year where they describe another strategy. And CLDR explicitly says about week-of-month (section 8.4):
8.4 Week of Year
Values calculated for the Week of Year field range from 1 to 53 for the Gregorian calendar (they may have different ranges for other calendars). Week 1 for a year is the first week that contains at least the specified minimum number of days from that year. Weeks between week 1 of one year and week 1 of the following year are numbered sequentially from 2 to 52 or 53 (if needed). For example, January 1, 1998 was a Thursday. If the first day of the week is MONDAY and the minimum days in a week is 4 (these are the values reflecting ISO 8601 and many national standards), then week 1 of 1998 starts on December 29, 1997, and ends on January 4, 1998. However, if the first day of the week is SUNDAY, then week 1 of 1998 starts on January 4, 1998, and ends on January 10, 1998. The first three days of 1998 are then part of week 53 of 1997.
Values are similarly calculated for the Week of Month.
The difference between both strategies applied on the date 2016-02-29 will be:
Now I present a solution for Joda-Time.
public static void main(String[] args) throws Throwable {
System.out.println(getWeekOfMonth(false)); // CLDR/ISO-spec
// 1 for today=2016-02-05
// 2 for today=2016-02-12
// 4 for today=2016-02-28
// 1 for today=2016-02-29
System.out.println(getWeekOfMonth(true)); // JDK-behaviour
// 1 for today=2016-02-05
// 2 for today=2016-02-12
// 4 for today=2016-02-28
// 5 for today=2016-02-29
}
private static int getWeekOfMonth(boolean bounded) {
int weekOfMonth;
LocalDate today = LocalDate.now();
LocalDate first = today.dayOfMonth().withMinimumValue();
int dowFirst = first.getDayOfWeek();
if (dowFirst <= DateTimeConstants.THURSDAY) {
// we are in week 1 and go to Monday as start of week
first = first.minusDays(dowFirst - DateTimeConstants.MONDAY);
// first try: we determine the week of current month
weekOfMonth = Days.daysBetween(first, today).getDays() / 7 + 1;
if (!bounded) {
// edge case: are we in first week of next month?
LocalDate next = first.plusMonths(1);
int dowNext = next.getDayOfWeek();
if (dowNext <= DateTimeConstants.THURSDAY) {
next = next.minusDays(dowNext - DateTimeConstants.MONDAY);
if (!next.isAfter(today)) {
weekOfMonth = 1;
}
}
}
} else if (bounded) {
weekOfMonth = 0;
} else {
// we are in last week of previous month so let's check the start of previous month
LocalDate previous = first.minusMonths(1);
int dowPrevious = previous.getDayOfWeek();
if (dowPrevious <= DateTimeConstants.THURSDAY) {
previous = previous.minusDays(dowPrevious - DateTimeConstants.MONDAY);
} else {
previous = previous.plusDays(DateTimeConstants.MONDAY - dowPrevious + 7);
}
weekOfMonth = Days.daysBetween(previous, today).getDays() / 7 + 1;
}
return weekOfMonth;
}
I hope it is not too complex for you.
By the way, if you are interested how simple alternatives applicable on platforms older than Java-8 look like:
Time4J (my library)
private static int time4j(boolean bounded) { // supports both definitions
PlainDate today = SystemClock.inLocalView().today(); // using system timezone
return today.get(
(bounded ? Weekmodel.ISO.boundedWeekOfMonth() : Weekmodel.ISO.weekOfMonth()));
}
Threeten-BP (backport of Java-8):
private static int threeten() { // only JDK-definition (code similar to Java-8)
org.threeten.bp.LocalDate today = org.threeten.bp.LocalDate.now();
return today.get(WeekFields.ISO.weekOfMonth());
}
private static int oldJDK() { // only JDK-definition
GregorianCalendar gcal = new GregorianCalendar();
gcal.setMinimalDaysInFirstWeek(4);
gcal.setFirstDayOfWeek(Calendar.MONDAY);
return gcal.get(Calendar.WEEK_OF_MONTH);
}
As you can see, it is very easy with these alternatives to change the underlying week models to non-ISO cases (like US-weeks). If you want that in Joda-Time then I leave the task for you to rewrite the presented Joda-solution.
Update due to comment below about the topic:
So the whole thing is about day-of-week-in-month. Joda-Time does not support this element/field out of the box, too. Sorry. But you might be able to study the necessary algorithm for such a field used in other libraries.
A demo example in Time4J for modelling the rfc2445-rule mentioned in comment:
PlainDate dtStart = PlainDate.of(2016, Month.FEBRUARY, 4);
int count = 5;
Weekday byday = Weekday.FRIDAY; // first
int interval = 1;
CalendarUnit frequency = CalendarUnit.MONTHS;
List<PlainDate> sequence = new ArrayList<>(count);
PlainDate wim = dtStart;
for (int i = 0; i < count; i++) {
wim = wim.with(PlainDate.WEEKDAY_IN_MONTH.setToFirst(byday));
sequence.add(wim);
wim = wim.with(PlainDate.DAY_OF_MONTH, 1).plus(interval, frequency);
}
if (!sequence.isEmpty() && !sequence.get(0).equals(dtStart)) {
sequence.remove(0); // Not quite sure - do you need another condition?
}
System.out.println(sequence); // [2016-03-04, 2016-04-01, 2016-05-06, 2016-06-03]
In Java-8, there is also support via a specialized adjuster so you can easily transfer given demo example to Java-8 using java.time.LocalDate
.
Upvotes: 1
Reputation: 3733
In jdk 8 you could implement your own TemporalAdjusters and use with LocalDate.with()
Upvotes: 0