Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;

import org.apache.commons.validator.GenericValidator;

Expand All @@ -38,6 +39,9 @@ public abstract class AbstractCalendarValidator extends AbstractFormatValidator

private static final long serialVersionUID = -1410008585975827379L;

/** Number of milliseconds in a week, beyond which two instants cannot share a week. */
private static final long MILLIS_PER_WEEK = TimeUnit.DAYS.toMillis(7);

/**
* The date style to use for Locale validation.
*/
Expand Down Expand Up @@ -117,17 +121,20 @@ protected int compare(final Calendar value, final Calendar compare, final int fi

int result;

// Week of Year and Week of Month numbers repeat across the boundaries they reset on, and a
// week can belong to a different calendar year or month than its number suggests (for
// example 31 December may fall in week 1 of the following year), so the week is compared by
// day distance and week number rather than by comparing the calendar year first.
if (field == Calendar.WEEK_OF_YEAR || field == Calendar.WEEK_OF_MONTH) {
return compareWeek(value, compare, field);
}

// Compare Year
result = calculateCompareResult(value, compare, Calendar.YEAR);
if (result != 0 || field == Calendar.YEAR) {
return result;
}

// Compare Week of Year
if (field == Calendar.WEEK_OF_YEAR) {
return calculateCompareResult(value, compare, Calendar.WEEK_OF_YEAR);
}

// Compare Day of the Year
if (field == Calendar.DAY_OF_YEAR) {
return calculateCompareResult(value, compare, Calendar.DAY_OF_YEAR);
Expand All @@ -139,11 +146,6 @@ protected int compare(final Calendar value, final Calendar compare, final int fi
return result;
}

// Compare Week of Month
if (field == Calendar.WEEK_OF_MONTH) {
return calculateCompareResult(value, compare, Calendar.WEEK_OF_MONTH);
}

// Compare Date
result = calculateCompareResult(value, compare, Calendar.DATE);
if (result != 0 || field == Calendar.DATE ||
Expand Down Expand Up @@ -416,4 +418,25 @@ protected Object parse(String value, final String pattern, final Locale locale,
*/
@Override
protected abstract Object processParsedValue(Object value, Format formatter);

/**
* Compares the week two calendars fall in, ordering by the actual week rather than by the
* {@code WEEK_OF_YEAR} or {@code WEEK_OF_MONTH} number alone. Those numbers repeat across the
* boundaries they reset on (for example 31 December may be week 1 of the following year, and
* the first week of a month can hold days carried over from the previous month), so the gap
* between the two instants is checked first: dates a week or more apart are always in different
* weeks, and nearer dates share a week only when the week number also matches.
*
* @param value The Calendar value.
* @param compare The {@link Calendar} to check the value against.
* @param field {@code Calendar.WEEK_OF_YEAR} or {@code Calendar.WEEK_OF_MONTH}.
* @return Zero if both calendars are in the same week, -1 or +1 otherwise.
*/
private int compareWeek(final Calendar value, final Calendar compare, final int field) {
final long millis = value.getTimeInMillis() - compare.getTimeInMillis();
if (Math.abs(millis) >= MILLIS_PER_WEEK || calculateCompareResult(value, compare, field) != 0) {
return Long.signum(millis);
}
return 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,52 @@ void testCompare() {
assertEquals("Invalid field: -1", e.getMessage(), "check message");
}

/**
* Test compareWeeks() across the year boundary, where WEEK_OF_YEAR repeats: 31 December can be
* week 1 of the next year while 1 January of the same calendar year is also week 1.
*/
@Test
@DefaultLocale(country = "US", language = "en")
void testCompareWeeksAcrossYearBoundary() {
final int noon = 120000;
// US locale: week starts Sunday, so Sun 30 Dec 2018 to Sat 5 Jan 2019 is a single week
final Calendar dec30y2018 = createCalendar(TimeZones.GMT, 20181230, noon);
final Calendar dec31y2018 = createCalendar(TimeZones.GMT, 20181231, noon);
final Calendar jan01y2018 = createCalendar(TimeZones.GMT, 20180101, noon);
final Calendar jan01y2019 = createCalendar(TimeZones.GMT, 20190101, noon);
final Calendar jan05y2019 = createCalendar(TimeZones.GMT, 20190105, noon);

// both are calendar year 2018 and WEEK_OF_YEAR 1, but lie ~52 weeks apart
assertEquals(1, calValidator.compareWeeks(dec31y2018, jan01y2018), "Dec 31 2018 is weeks after Jan 1 2018");
assertEquals(-1, calValidator.compareWeeks(jan01y2018, dec31y2018), "Jan 1 2018 is weeks before Dec 31 2018");

// different calendar years but the same week
assertEquals(0, calValidator.compareWeeks(dec31y2018, jan01y2019), "Dec 31 2018 is the same week as Jan 1 2019");

// the whole Sun 30 Dec 2018 to Sat 5 Jan 2019 span is one week
assertEquals(0, calValidator.compareWeeks(dec30y2018, jan05y2019), "Dec 30 2018 is the same week as Jan 5 2019");
}

/**
* Test WEEK_OF_MONTH comparison across a month/year boundary, where the week number resets and
* the first week of a month can hold days carried over from the previous month.
*/
@Test
@DefaultLocale(country = "US", language = "en")
void testCompareWeekOfMonthAcrossBoundary() {
final int noon = 120000;
final Calendar dec30y2018 = createCalendar(TimeZones.GMT, 20181230, noon);
final Calendar dec31y2018 = createCalendar(TimeZones.GMT, 20181231, noon);
final Calendar jan01y2019 = createCalendar(TimeZones.GMT, 20190101, noon);

// 30 and 31 Dec 2018 fall in the same week of December
assertEquals(0, calValidator.compare(dec30y2018, dec31y2018, Calendar.WEEK_OF_MONTH), "Dec 30 and 31 2018 are the same week of month");

// 31 Dec 2018 and 1 Jan 2019 are one day apart but lie in different weeks of their months
assertEquals(-1, calValidator.compare(dec31y2018, jan01y2019, Calendar.WEEK_OF_MONTH), "Dec 31 2018 is before Jan 1 2019 by week of month");
assertEquals(1, calValidator.compare(jan01y2019, dec31y2018, Calendar.WEEK_OF_MONTH), "Jan 1 2019 is after Dec 31 2018 by week of month");
}

Comment thread
sebbASF marked this conversation as resolved.
/**
* Test Date/Time style Validator (there isn't an implementation for this)
*/
Expand Down
Loading