我如何检查 joda-time 是给定日期的第一周,第二周,......还是一个月的最后一周?
How can I check with joda-time is it first, second, ... , or last week of a month by given date?
例如:
5 February 2016
- 第一周,
12 February 2016
- 第二周,
28 February 2016
- 上周
在 jdk 8 中,您可以实现自己的 TemporalAdjusters
并与 LocalDate.with()
一起使用
嗯,您不太清楚如何定义一个月中的星期几。对于下面的讨论,我假设你说的是从星期一开始的几周(如 ISO-8601 标准和大多数欧洲国家/地区)。
有两种可能的方式来定义如何在计算周数时处理月的开始和结束。
由于星期一的开始不一定与一个月的第一天相同,一周可以从上个月开始,也可以属于下个月.
JDK-类 SimpleDateFormat
及其字段模式符号 w(以及新的 JSR-310 字段 WeekFields.weekOfMonth())使用以下策略:
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.
与该定义相反,CLDR 日期-时间-模式规范和 ISO-8601 几乎没有提及月中周上下文中的细节。然而,这些标准并没有对他们描述另一种策略的一年中的哪一周保持沉默。并且 CLDR explicitly says about week-of-month(第 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.
在 2016 年 2 月 29 日应用的两种策略之间的差异将是:
- 根据 JDK
第 5 周
- 第 1 周根据 CLDR/ISO
现在我提出一个 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());
}
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 解决方案。
由于以下关于主题的评论而更新:
所以整个事情是关于 一个月中的星期几。 Joda-Time 也不支持开箱即用的 element/field。对不起。但是您也许可以研究 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- 8 使用 java.time.LocalDate
.
例如:
5 February 2016
- 第一周,12 February 2016
- 第二周,28 February 2016
- 上周
在 jdk 8 中,您可以实现自己的 TemporalAdjusters 并与 LocalDate.with()
一起使用嗯,您不太清楚如何定义一个月中的星期几。对于下面的讨论,我假设你说的是从星期一开始的几周(如 ISO-8601 标准和大多数欧洲国家/地区)。
有两种可能的方式来定义如何在计算周数时处理月的开始和结束。
由于星期一的开始不一定与一个月的第一天相同,一周可以从上个月开始,也可以属于下个月.
JDK-类 SimpleDateFormat
及其字段模式符号 w(以及新的 JSR-310 字段 WeekFields.weekOfMonth())使用以下策略:
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.
与该定义相反,CLDR 日期-时间-模式规范和 ISO-8601 几乎没有提及月中周上下文中的细节。然而,这些标准并没有对他们描述另一种策略的一年中的哪一周保持沉默。并且 CLDR explicitly says about week-of-month(第 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.
在 2016 年 2 月 29 日应用的两种策略之间的差异将是:
- 根据 JDK 第 5 周
- 第 1 周根据 CLDR/ISO
现在我提出一个 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());
}
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 解决方案。
由于以下关于主题的评论而更新:
所以整个事情是关于 一个月中的星期几。 Joda-Time 也不支持开箱即用的 element/field。对不起。但是您也许可以研究 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- 8 使用 java.time.LocalDate
.