嗯,您如何定义月份中的星期还不是很清楚。对于以下讨论,我假设您谈论的是从星期一开始的几周(如 ISO-8601 标准和大多数欧洲国家/地区)。
有两种可能的方法来定义如何在计算周数时处理月份的开始和结束。
由于周一的一周开始不一定与每月的第一天相同,一周可以从上个月开始,也可以属于下个月。
带有字段模式符号 w 的 JDK 类 SimpleDateFormat(以及新的 JSR-310 字段 WeekFields.weekOfMonth())使用以下策略:
如果一个月的第一天是星期一到星期四,那么
相关周在本月至少有 4 天,将是
计为第 1 周,否则计为第 0 周(零)。始终是最后一个
一个月中的某天将始终使用递增的数字,即使它属于
到下个月的第一周。
与该定义相反,CLDR 日期-时间-模式规范和 ISO-8601 几乎没有提及月周上下文中的细节。然而,这些标准并没有对他们描述另一种策略的一年中的一周保持沉默。和CLDR explicitly says about week-of-month(第 8.4 节):
一年中的 8.4 周
为“一年中的周”字段计算的值范围为 1 到 53
公历(它们可能有不同的范围
日历)。一年的第 1 周是第一周至少包含
从该年起指定的最少天数。几周之间
一年的第 1 周和下一年的第 1 周有编号
依次从 2 到 52 或 53(如果需要)。例如,1 月 1 日,
1998 年是一个星期四。如果一周的第一天是星期一,并且
一周中的最少天数为 4(这些是反映 ISO 8601 的值
和许多国家标准),然后 1998 年的第 1 周从 12 月开始
1997 年 1 月 29 日,并于 1998 年 1 月 4 日结束。但是,如果
星期是星期天,然后 1998 年的第 1 周从 1998 年 1 月 4 日开始,并且
结束于 1998 年 1 月 10 日。然后是 1998 年的前三天
1997 年第 53 周。
值的计算方法与月份的一周类似。
在 2016 年 2 月 29 日应用的两种策略之间的区别是:
- 根据 JDK 的第 5 周
- 根据 CLDR/ISO 的第 1 周
现在我提出一个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;
}
希望对你来说不要太复杂。
顺便说一下,如果您有兴趣在早于 Java-8 的平台上适用的简单替代方案是什么样的:
Time4J(我的图书馆)
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());
}
Old JDK:
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);
}
如您所见,使用这些替代方案很容易将基础周模型更改为非 ISO 案例(如美国周)。如果您希望在 Joda-Time 中这样做,那么我将任务留给您重写所提供的 Joda-solution。
由于以下关于该主题的评论而更新:
所以整个事情都是关于每月的星期几。 Joda-Time 也不支持开箱即用的这个元素/字段。对不起。但是您也许可以研究 necessary algorithm 以了解其他库中使用的此类字段。
Time4J 中的一个演示示例,用于对评论中提到的 rfc2445 规则进行建模:
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]
在 Java-8 中,还通过 specialized adjuster 提供支持,因此您可以使用 java.time.LocalDate 轻松地将给定的演示示例转移到 Java-8。