fix(backend): 행사 목록에서 끝을 생략한 없는 시작일이 500 이 나던 것 수정

from=2030-13-01 처럼 없는 시작일에 to 를 생략하면 끝 날짜를 계산하다 RangeError 가 나서
VAL_001(400) 대신 SYS_001(500)이 나갔다. from=9999-12-31 은 DB 가 읽지 못하는 끝 날짜를 만들었다.
날짜 계산을 shared/utils/iso-date.ts 순수 함수로 옮기고, 없는 날짜와 네 자리 연도를 넘는 결과는
null 로 알려 VAL_001 로 응답한다. 앱은 항상 두 날짜를 보내므로 사용자에게는 보이지 않았다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-04 23:58:27 +09:00
parent ff56d78022
commit 785afcc53f
5 changed files with 76 additions and 21 deletions
+34
View File
@@ -0,0 +1,34 @@
import { addDaysIso, daysBetweenIso } from './iso-date';
describe('daysBetweenIso', () => {
it('두 날짜 사이 일수를 센다(달·윤년 경계 포함)', () => {
expect(daysBetweenIso('2026-11-04', '2026-12-04')).toBe(30);
expect(daysBetweenIso('2026-02-28', '2026-03-01')).toBe(1);
expect(daysBetweenIso('2028-02-28', '2028-03-01')).toBe(2);
expect(daysBetweenIso('2026-11-08', '2026-11-07')).toBe(-1);
});
it.each(['2026-13-01', '2026-00-10', '2026-02-30', '2026-1-1', ''])(
'없는 날짜 "%s" 가 끼면 NaN',
(bad) => {
expect(daysBetweenIso(bad, '2026-11-04')).toBeNaN();
expect(daysBetweenIso('2026-11-04', bad)).toBeNaN();
},
);
});
describe('addDaysIso', () => {
it('일수를 더한 날짜를 돌려준다', () => {
expect(addDaysIso('2026-11-04', 30)).toBe('2026-12-04');
expect(addDaysIso('2026-12-31', 1)).toBe('2027-01-01');
});
it('없는 날짜는 예외 없이 null', () => {
expect(addDaysIso('2026-13-01', 30)).toBeNull();
expect(addDaysIso('2026-02-30', 1)).toBeNull();
});
it('결과가 네 자리 연도를 넘으면 null — DB 가 읽지 못하는 날짜를 만들지 않는다', () => {
expect(addDaysIso('9999-12-31', 30)).toBeNull();
});
});
+32
View File
@@ -0,0 +1,32 @@
/**
* 'YYYY-MM-DD' 날짜 계산 (순수 함수) — 행사 기간 조건에 쓴다.
* 없는 날짜("2030-13-01", "2030-02-30")는 예외를 던지지 않고 null·NaN 으로 알린다. 호출부가 VAL_001 로 응답한다.
*/
const DAY_MS = 86_400_000;
const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
/** 실제로 있는 날짜면 그날 0시(UTC) 시각, 아니면 null */
function utcMidnight(iso: string): number | null {
if (!ISO_DATE.test(iso)) return null;
const time = Date.parse(`${iso}T00:00:00Z`);
// '2030-02-30' 처럼 다른 날로 넘어가거나 해석이 안 되는 날짜를 거른다
if (Number.isNaN(time)) return null;
return new Date(time).toISOString().slice(0, 10) === iso ? time : null;
}
/** 두 날짜 사이 일수(b − a). 어느 하나라도 없는 날짜면 NaN */
export function daysBetweenIso(a: string, b: string): number {
const ta = utcMidnight(a);
const tb = utcMidnight(b);
if (ta === null || tb === null) return Number.NaN;
return Math.round((tb - ta) / DAY_MS);
}
/** 날짜에 일수를 더한 'YYYY-MM-DD'. 없는 날짜이거나 결과가 네 자리 연도를 벗어나면 null */
export function addDaysIso(iso: string, days: number): string | null {
const time = utcMidnight(iso);
if (time === null) return null;
const next = new Date(time + days * DAY_MS).toISOString().slice(0, 10);
return ISO_DATE.test(next) ? next : null;
}