diff --git a/.changeset/tough-drinks-bathe.md b/.changeset/tough-drinks-bathe.md new file mode 100644 index 000000000..b6ba38cd2 --- /dev/null +++ b/.changeset/tough-drinks-bathe.md @@ -0,0 +1,5 @@ +--- +'@modern-kit/utils': minor +--- + +feat(utils): getAge 신규 함수 추가 - @ssi02014 diff --git a/.changeset/weak-colts-love.md b/.changeset/weak-colts-love.md new file mode 100644 index 000000000..10c97d5dd --- /dev/null +++ b/.changeset/weak-colts-love.md @@ -0,0 +1,5 @@ +--- +'@modern-kit/utils': minor +--- + +feat(utils): isUnderAge 신규 함수 추가 - @ssi02014 diff --git a/docs/docs/utils/date/getAge.md b/docs/docs/utils/date/getAge.md new file mode 100644 index 000000000..afe319d49 --- /dev/null +++ b/docs/docs/utils/date/getAge.md @@ -0,0 +1,25 @@ +# getAge + +주어진 `생년월일`을 기준으로 `현재 나이`를 계산합니다. + +
+ +## Code +[🔗 실제 구현 코드 확인](https://github.com/modern-agile-team/modern-kit/blob/main/packages/utils/src/validator/getAge/index.ts) + +## Interface +```ts title="typescript" +function getAge(birthDate: string | number | Date): number +``` + +## Usage + +```ts title="typescript" +import { getAge } from '@modern-kit/utils'; + +// 현재 날짜가 2025년 1월 1일 00:00:00 일 때 +getAge(new Date('2006-01-01')); // 19 + +// 문자열 포맷도 허용합니다. +getAge('2006-01-01'); // 19 +``` \ No newline at end of file diff --git a/docs/docs/utils/date/isUnderAge.md b/docs/docs/utils/date/isUnderAge.md new file mode 100644 index 000000000..1f8e896ee --- /dev/null +++ b/docs/docs/utils/date/isUnderAge.md @@ -0,0 +1,52 @@ +# isUnderAge + +주어진 생년월일을 기준으로 특정 나이보다 어린지 확인합니다. + +`inclusive` 값을 기준으로 기준 나이를 포함할지 여부를 결정합니다. + +
+ +## Code +[🔗 실제 구현 코드 확인](https://github.com/modern-agile-team/modern-kit/blob/main/packages/utils/src/validator/isUnderAge/index.ts) + +## Interface +```ts title="typescript" +interface IsUnderAgeParams { + birthDate: string | number | Date; + targetAge: number; + inclusive?: boolean; +} +``` +```ts title="typescript" +function isUnderAge({ + birthDate, + targetAge, + inclusive = false, +}: IsUnderAgeParams): boolean; +``` + +## Usage +### without inclusive +```ts title="typescript" +import { isUnderAge } from '@modern-kit/utils'; + +// 현재 날짜 2025년 1월 1일 기준 +isUnderAge({ birthDate: new Date('2006-01-02'), targetAge: 19 }); // true +isUnderAge({ birthDate: new Date('2006-01-01'), targetAge: 19 }); // false, 정확히 만 19세 +isUnderAge({ birthDate: new Date('2005-12-31'), targetAge: 19 }); // false + +isUnderAge({ birthDate: '2006-01-02', targetAge: 19 }); // true +isUnderAge({ birthDate: '2006-01-01', targetAge: 19 }); // false, 정확히 만 19세 +isUnderAge({ birthDate: '2005-12-31', targetAge: 19 }); // false +``` + +### with inclusive +- `inclusive` 값을 기준으로 기준 나이를 포함할지 여부를 결정합니다. (기본값: `false`) +- `inclusive` 값이 `true`일 경우, 기준 나이를 포함하며, `false`일 경우, 기준 나이를 포함하지 않습니다. + +```ts title="typescript" +// 2025년 01월 01일 기준 +// inclusive 값을 기준으로 기준 나이를 포함할지 여부를 결정합니다. +isUnderAge({ birthDate: '2006-01-01', targetAge: 19, inclusive: true }); // true +isUnderAge({ birthDate: '2006-01-01', targetAge: 19, inclusive: false }); // false +``` \ No newline at end of file diff --git a/packages/utils/src/date/getAge/getAge.spec.ts b/packages/utils/src/date/getAge/getAge.spec.ts new file mode 100644 index 000000000..bf0c28c09 --- /dev/null +++ b/packages/utils/src/date/getAge/getAge.spec.ts @@ -0,0 +1,45 @@ +import { describe, expect, it, beforeEach, vi, afterEach } from 'vitest'; +import { getAge } from '.'; + +beforeEach(() => { + /** + * 테스트를 위해 2025년 01월 01일로 날짜 고정 + * + * 1. 2005년 01월 01일: 만 20세 + * 2. 2006년 01월 01일: 정확히 만 19세 (기대값: false) + * 3. 2007년 01월 01일: 만 18세 + */ + vi.setSystemTime(new Date('2025-01-01')); +}); + +afterEach(() => { + vi.useRealTimers(); +}); + +describe('getAge', () => { + it('Date 객체로 나이를 정확히 계산해야 함', () => { + expect(getAge(new Date('2007-01-01'))).toBe(18); + + expect(getAge(new Date('2006-06-01'))).toBe(18); // 생일이 지나지 않은 경우 + expect(getAge(new Date('2006-01-01'))).toBe(19); + expect(getAge(new Date('2005-12-31'))).toBe(19); // 생일이 지난 경우 + + expect(getAge(new Date('2005-01-01'))).toBe(20); + }); + + it('날짜 문자열로 나이를 정확히 계산해야 함', () => { + expect(getAge('2007-01-01')).toBe(18); + + expect(getAge('2006-06-01')).toBe(18); // 생일이 지나지 않은 경우 + expect(getAge('2006-01-01')).toBe(19); + expect(getAge('2005-12-31')).toBe(19); // 생일이 지난 경우 + + expect(getAge('2005-01-01')).toBe(20); + }); + + it('잘못된 날짜 형식에 대해 에러를 발생시켜야 함', () => { + expect(() => getAge('invalid-date')).toThrow( + '유효하지 않은 날짜 형식입니다.' + ); + }); +}); diff --git a/packages/utils/src/date/getAge/index.ts b/packages/utils/src/date/getAge/index.ts new file mode 100644 index 000000000..e8c753f47 --- /dev/null +++ b/packages/utils/src/date/getAge/index.ts @@ -0,0 +1,35 @@ +/** + * @description 주어진 생년월일을 기준으로 현재 나이를 계산합니다. + * + * @param {string | number | Date} birthDate - 확인하고자 하는 생년월일 + * @returns {number} 현재 나이 (만 나이) + * + * @example + * // 현재 날짜 2025년 01월 01일 기준 + * getAge(new Date('2006-01-01')); // 19 + * + * @example + * // 2025년 01월 01일 기준 + * getAge('2006-01-01'); // 19 + * + */ +export function getAge(birthDate: string | number | Date): number { + const birthDateTime = new Date(birthDate); + + if (isNaN(birthDateTime.getTime())) { + throw new Error('유효하지 않은 날짜 형식입니다.'); + } + + const today = new Date(); + + const age = today.getFullYear() - birthDateTime.getFullYear(); + const monthDiff = today.getMonth() - birthDateTime.getMonth(); + const dayDiff = today.getDate() - birthDateTime.getDate(); + + // 만약, 생일이 지나지 않았다면 나이에서 1을 뺀다. + if (monthDiff < 0 || (monthDiff === 0 && dayDiff < 0)) { + return age - 1; + } + + return age; +} diff --git a/packages/utils/src/date/index.ts b/packages/utils/src/date/index.ts index 67795ec34..cbf3019d6 100644 --- a/packages/utils/src/date/index.ts +++ b/packages/utils/src/date/index.ts @@ -1 +1,6 @@ +export * from './getAge'; export * from './getDDay'; +export * from './isAfterDate'; +export * from './isBeforeDate'; +export * from './isDateInRange'; +export * from './isUnderAge'; diff --git a/packages/utils/src/date/isUnderAge/index.ts b/packages/utils/src/date/isUnderAge/index.ts new file mode 100644 index 000000000..c9e6c416f --- /dev/null +++ b/packages/utils/src/date/isUnderAge/index.ts @@ -0,0 +1,48 @@ +import { getAge } from '../getAge'; + +interface IsUnderAgeParams { + birthDate: string | number | Date; + targetAge: number; + inclusive?: boolean; +} + +/** + * @description 주어진 생년월일을 기준으로 특정 나이보다 어린지 확인합니다. + * + * `inclusive` 값을 기준으로 기준 나이를 포함할지 여부를 결정합니다. + * + * @param {IsUnderAgeParams} params - 확인하고자 하는 생년월일 + * @param {string | number| Date} params.birthDate - 확인하고자 하는 생년월일 + * @param {number} params.targetAge - 비교할 기준 나이 + * @param {boolean} params.inclusive - 기준 나이에 포함 여부 + * @returns {boolean} 대상이 기준 나이보다 어리면 true, 아니면 false + * @throws {Error} 유효하지 않은 날짜 형식이 입력된 경우 + * + * @example + * // 2025년 01월 01일 기준 + * isUnderAge({ birthDate: new Date('2006-01-02'), targetAge: 19 }); // true + * isUnderAge({ birthDate: new Date('2006-01-01'), targetAge: 19 }); // false, 정확히 만 19세 + * isUnderAge({ birthDate: new Date('2005-12-31'), targetAge: 19 }); // false + * + * isUnderAge({ birthDate: '2006-01-02', targetAge: 19 }); // true + * isUnderAge({ birthDate: '2006-01-01', targetAge: 19 }); // false, 정확히 만 19세 + * isUnderAge({ birthDate: '2005-12-31', targetAge: 19 }); // false + * + * @example + * // 2025년 01월 01일 기준 + * // inclusive 값을 기준으로 기준 나이를 포함할지 여부를 결정합니다. + * isUnderAge({ + * birthDate: '2006-01-01', + * targetAge: 19, + * inclusive: true + * }); // true + */ +export function isUnderAge({ + birthDate, + targetAge, + inclusive = false, +}: IsUnderAgeParams): boolean { + const age = getAge(birthDate); + + return inclusive ? age <= targetAge : age < targetAge; +} diff --git a/packages/utils/src/date/isUnderAge/isUnderAge.spec.ts b/packages/utils/src/date/isUnderAge/isUnderAge.spec.ts new file mode 100644 index 000000000..5483cb67b --- /dev/null +++ b/packages/utils/src/date/isUnderAge/isUnderAge.spec.ts @@ -0,0 +1,71 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { isUnderAge } from '.'; + +beforeEach(() => { + /** + * 테스트를 위해 2025년 01월 01일로 날짜 고정 + * + * 1. 2006년 01월 01일: 정확히 만 19세 (기대값: false) + * 2. 2006년 01월 02일: 만 19세보다 어림 (기대값: true) + * 3. 2005년 12월 31일: 만 19세보다 많음 (기대값: false) + */ + vi.setSystemTime(new Date('2025-01-01')); +}); + +afterEach(() => { + vi.useRealTimers(); +}); + +describe('isUnderAge', () => { + it('만 19세 미만인 경우 true를 반환해야 합니다.', () => { + // Date 객체 + expect( + isUnderAge({ birthDate: new Date('2006-01-02'), targetAge: 19 }) + ).toBeTruthy(); + + // 문자열 + expect(isUnderAge({ birthDate: '2006-01-02', targetAge: 19 })).toBeTruthy(); + }); + + it('정확히 만 19세인 경우 false를 반환해야 합니다.', () => { + // Date 객체 + expect( + isUnderAge({ birthDate: new Date('2006-01-01'), targetAge: 19 }) + ).toBeFalsy(); + + // 문자열 + expect(isUnderAge({ birthDate: '2006-01-01', targetAge: 19 })).toBeFalsy(); + }); + + it('만 19세가 지난 경우 false를 반환해야 합니다.', () => { + // Date 객체 + expect( + isUnderAge({ birthDate: new Date('2005-12-31'), targetAge: 19 }) + ).toBeFalsy(); + + // 문자열 + expect(isUnderAge({ birthDate: '2005-12-31', targetAge: 19 })).toBeFalsy(); + }); + + it('inclusive 값을 기준으로 기준 나이를 포함할지 여부를 결정해야 한다.', () => { + // Date 객체 + expect( + isUnderAge({ + birthDate: new Date('2006-01-01'), + targetAge: 19, + inclusive: true, + }) + ).toBeTruthy(); + + // 문자열 + expect( + isUnderAge({ birthDate: '2006-01-01', targetAge: 19, inclusive: false }) + ).toBeFalsy(); + }); + + it('잘못된 날짜 형식에 대해 에러를 던진다', () => { + expect(() => + isUnderAge({ birthDate: 'invalid-date', targetAge: 19 }) + ).toThrow('유효하지 않은 날짜 형식입니다.'); + }); +});