Skip to content
Open
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,7 +23,9 @@
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.MonthDay;
import java.time.OffsetDateTime;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
Expand Down Expand Up @@ -474,4 +476,29 @@ public static LocalDateTime convertDateTimeStringToLocalDateTime(String dateTime
public static LocalDate min(@NonNull LocalDate date1, @NonNull LocalDate date2) {
return date1.isBefore(date2) ? date1 : date2;
}

/**
* Builds a {@link MonthDay} from month and day, clamping the day to the last valid day of the month for the current
* business year if necessary. Use when reading (month, day) from storage (e.g. fee_on_month, fee_on_day) where the
* combination may be invalid (e.g. day 30 for February).
* <p>
* The year is derived from {@link #getBusinessLocalDate()}. This makes February sensitive to leap years:
* <ul>
* <li>In a leap year, February allows 29 (Feb 30/31 are clamped to 29).</li>
* <li>In a non-leap year, February is clamped to 28 (Feb 29/30/31 are clamped to 28).</li>
* </ul>
*
* @param month
* month 1–12
* @param day
* day of month (may exceed month length; will be clamped)
* @return valid MonthDay (day clamped to month length for the current business year)
*/
public static MonthDay safeMonthDay(int month, int day) {
LocalDate businessDate = getBusinessLocalDate();
int year = businessDate.getYear();
int maxDay = YearMonth.of(year, month).lengthOfMonth();
int safeDay = Math.min(day, maxDay);
return MonthDay.of(month, safeDay);
Comment on lines +497 to +502
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having Feb 29 is just as bad as having feb 30 or feb 31... leap years has only feb 29 only... so from 4 years 3 has feb 28 only, i dont see any improvement here...

Am I missing something here?

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -439,9 +439,8 @@ public StandingInstructionData mapRow(final ResultSet rs, @SuppressWarnings("unu
MonthDay recurrenceOnMonthDay = null;
final Integer recurrenceOnDay = JdbcSupport.getInteger(rs, "recurrenceOnDay");
final Integer recurrenceOnMonth = JdbcSupport.getInteger(rs, "recurrenceOnMonth");
if (recurrenceOnDay != null) {
recurrenceOnMonthDay = MonthDay.now(DateUtils.getDateTimeZoneOfTenant()).withMonth(recurrenceOnMonth)
.withDayOfMonth(recurrenceOnDay);
if (recurrenceOnDay != null && recurrenceOnMonth != null) {
recurrenceOnMonthDay = DateUtils.safeMonthDay(recurrenceOnMonth, recurrenceOnDay);
}

final Integer transferType = rs.getInt("transferType");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ public ChargeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final i
final Integer feeOnMonth = JdbcSupport.getInteger(rs, "feeOnMonth");
final Integer feeOnDay = JdbcSupport.getInteger(rs, "feeOnDay");
if (feeOnDay != null && feeOnMonth != null) {
feeOnMonthDay = MonthDay.now(DateUtils.getDateTimeZoneOfTenant()).withDayOfMonth(feeOnDay).withMonth(feeOnMonth);
feeOnMonthDay = DateUtils.safeMonthDay(feeOnMonth, feeOnDay);
}
final BigDecimal minCap = rs.getBigDecimal("minCap");
final BigDecimal maxCap = rs.getBigDecimal("maxCap");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public SavingsAccountChargeData mapRow(final ResultSet rs, @SuppressWarnings("un
final Integer feeOnMonth = JdbcSupport.getInteger(rs, "feeOnMonth");
final Integer feeOnDay = JdbcSupport.getInteger(rs, "feeOnDay");
if (feeOnDay != null && feeOnMonth != null) {
feeOnMonthDay = MonthDay.now(DateUtils.getDateTimeZoneOfTenant()).withMonth(feeOnMonth).withDayOfMonth(feeOnDay);
feeOnMonthDay = DateUtils.safeMonthDay(feeOnMonth, feeOnDay);
}

final int chargeCalculation = rs.getInt("chargeCalculation");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.MonthDay;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
Expand Down Expand Up @@ -91,4 +92,69 @@ public void isDateInTheFuture() {
public void getBusinesLocalDate() {
assertTrue(DateUtils.isEqualBusinessDate(LocalDate.of(2022, 6, 12)));
}

// --- safeMonthDay (clamps day to last valid day of month for current business year) ---

@Test
public void safeMonthDay_validDay_returnsSameMonthDay() {
assertEquals(MonthDay.of(1, 15), DateUtils.safeMonthDay(1, 15));
assertEquals(MonthDay.of(3, 31), DateUtils.safeMonthDay(3, 31));
assertEquals(MonthDay.of(4, 30), DateUtils.safeMonthDay(4, 30));
assertEquals(MonthDay.of(12, 31), DateUtils.safeMonthDay(12, 31));
}

@Test
public void safeMonthDay_february29_inNonLeapYear_clampedTo28() {
// business date initialized to 2022-06-12 (non-leap year) in @BeforeEach
assertEquals(MonthDay.of(2, 28), DateUtils.safeMonthDay(2, 29));
}

@Test
public void safeMonthDay_february30_inNonLeapYear_clampedTo28() {
assertEquals(MonthDay.of(2, 28), DateUtils.safeMonthDay(2, 30));
}

@Test
public void safeMonthDay_february31_inNonLeapYear_clampedTo28() {
assertEquals(MonthDay.of(2, 28), DateUtils.safeMonthDay(2, 31));
}

@Test
public void safeMonthDay_february29_inLeapYear_preserved() {
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.of(2024, 6, 12))));
assertEquals(MonthDay.of(2, 29), DateUtils.safeMonthDay(2, 29));
}

@Test
public void safeMonthDay_february30_inLeapYear_clampedTo29() {
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.of(2024, 6, 12))));
assertEquals(MonthDay.of(2, 29), DateUtils.safeMonthDay(2, 30));
}

@Test
public void safeMonthDay_february31_inLeapYear_clampedTo29() {
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.of(2024, 6, 12))));
assertEquals(MonthDay.of(2, 29), DateUtils.safeMonthDay(2, 31));
}

@Test
public void safeMonthDay_april31_clampedTo30() {
assertEquals(MonthDay.of(4, 30), DateUtils.safeMonthDay(4, 31));
}

@Test
public void safeMonthDay_june31_clampedTo30() {
assertEquals(MonthDay.of(6, 30), DateUtils.safeMonthDay(6, 31));
}

@Test
public void safeMonthDay_november31_clampedTo30() {
assertEquals(MonthDay.of(11, 30), DateUtils.safeMonthDay(11, 31));
}

@Test
public void safeMonthDay_firstDayOfMonth_preserved() {
assertEquals(MonthDay.of(2, 1), DateUtils.safeMonthDay(2, 1));
assertEquals(MonthDay.of(7, 1), DateUtils.safeMonthDay(7, 1));
}
}