Skip to content

Commit

Permalink
feat: expose lunar util functions
Browse files Browse the repository at this point in the history
  • Loading branch information
kabeep committed Nov 16, 2024
1 parent 122ce7c commit 340335d
Show file tree
Hide file tree
Showing 13 changed files with 188 additions and 34 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { BASE_YEAR, LUNAR_DATA } from './constants';
import { BASE_YEAR, LUNAR_DATA } from '../_lib/constants';
import { getLeapMonth } from '../get-leap-month';

/**
* Returns days in a lunar leap month if there is a leap month in the year otherwise return 0
Expand All @@ -13,8 +14,8 @@ import { BASE_YEAR, LUNAR_DATA } from './constants';
* // => 0
* getLeapMonthDays(2024)
*/
function getLeapMonthDays(year: number): number {
export function getLeapMonthDays(year: number): number {
if (!getLeapMonth(year)) return 0;

return LUNAR_DATA[year - BASE_YEAR] & 0x1_00_00 ? 30 : 29;
}

export default getLeapMonthDays;
35 changes: 35 additions & 0 deletions src/get-leap-month-days/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { type Assertion, describe, expect, it } from 'vitest';
import { getLeapMonthDays } from './index';

const expectFunction = (input?: unknown): Assertion => expect(getLeapMonthDays(input as number));

const testFunction = (year: number, expected: number) => {
expectFunction(year).toBe(expected);
};

describe('Leap Month', () => {
it.each([
[2023, 29],
[2025, 29],
])('%i -> %i', testFunction);
});

describe('Non-leap Month', () => {
it.each([
[2024, 0],
[2026, 0],
])('%i -> %i', testFunction);
});

describe('Exception Case', () => {
it('return 0 if the given year is invalid', () => {
expectFunction().toBe(0);
expectFunction(null).toBe(0);
expectFunction(Number.NaN).toBe(0);
});

it('return 0 if the given year is out of boundary', () => {
expectFunction(1899).toBe(0);
expectFunction(2101).toBe(0);
});
});
6 changes: 2 additions & 4 deletions src/_lib/get-leap-month.ts → src/get-leap-month/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BASE_YEAR, LUNAR_DATA } from './constants';
import { BASE_YEAR, LUNAR_DATA } from '../_lib/constants';

/**
* Return the lunar leap month in the year otherwise return 0
Expand All @@ -13,8 +13,6 @@ import { BASE_YEAR, LUNAR_DATA } from './constants';
* // => 0
* getLeapMonth(2024)
*/
function getLeapMonth(year: number): number {
export function getLeapMonth(year: number): number {
return LUNAR_DATA[year - BASE_YEAR] & 0xf;
}

export default getLeapMonth;
35 changes: 35 additions & 0 deletions src/get-leap-month/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { type Assertion, describe, expect, it } from 'vitest';
import { getLeapMonth } from './index';

const expectFunction = (input?: unknown): Assertion => expect(getLeapMonth(input as number));

const testFunction = (year: number, expected: number) => {
expectFunction(year).toBe(expected);
};

describe('Leap Year', () => {
it.each([
[2023, 2],
[2025, 6],
])('%i -> %i', testFunction);
});

describe('Non-leap Year', () => {
it.each([
[2022, 0],
[2024, 0],
])('%i -> %i', testFunction);
});

describe('Exception Case', () => {
it('return 0 if the given year is invalid', () => {
expectFunction(Number.NaN).toBe(0);
expectFunction(null).toBe(0);
expectFunction().toBe(0);
});

it('return 0 if the given year is out of boundary', () => {
expectFunction(1899).toBe(0);
expectFunction(2101).toBe(0);
});
});
14 changes: 10 additions & 4 deletions src/_lib/get-month-days.ts → src/get-month-days/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BASE_YEAR, LUNAR_DATA } from './constants';
import { BASE_YEAR, LUNAR_DATA, MAXIMUM_LUNAR_YEAR } from '../_lib/constants';

/**
* Returns the number of days in a non-leap month of lunar
Expand All @@ -13,9 +13,15 @@ import { BASE_YEAR, LUNAR_DATA } from './constants';
* @example
* // => 30
* getLeapMonth(2025, 1)
*
* @example
* // => -1
* getLeapMonth(1899, 13)
*/
function getMonthDays(year: number, month: number): number {
export function getMonthDays(year: number, month: number): number {
if (!year || year < BASE_YEAR || year > MAXIMUM_LUNAR_YEAR || !month || month > 12 || month < 1) {
return -1;
}

return LUNAR_DATA[year - BASE_YEAR] & (0x1_00_00 >> month) ? 30 : 29;
}

export default getMonthDays;
31 changes: 31 additions & 0 deletions src/get-month-days/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { type Assertion, describe, expect, it } from 'vitest';
import { getMonthDays } from './index';

const expectFunction = (year?: unknown, month?: unknown): Assertion =>
expect(getMonthDays(year as number, month as number));

const testFunction = (year: number, month: number, expected: number) => {
expectFunction(year, month).toBe(expected);
};

describe('Non-leap Month', () => {
it.each([
[2024, 1, 29],
[2025, 1, 30],
[2026, 1, 30],
])('%i-%i -> %i', testFunction);
});

describe('Exception Case', () => {
it('return -1 if the given year is invalid', () => {
expectFunction().toBe(-1);
expectFunction(null).toBe(-1);
expectFunction({}).toBe(-1);
expectFunction(Number.NaN, Number.NaN).toBe(-1);
});

it('return -1 if the given year is out of boundary', () => {
expectFunction(1899).toBe(-1);
expectFunction(2101).toBe(-1);
});
});
16 changes: 10 additions & 6 deletions src/_lib/get-year-days.ts → src/get-year-days/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BASE_YEAR, LUNAR_DATA } from './constants';
import getLeapMonth from './get-leap-month';
import getLeapMonthDays from './get-leap-month-days';
import { BASE_YEAR, LUNAR_DATA, MAXIMUM_LUNAR_YEAR } from '../_lib/constants';
import { getLeapMonth } from '../get-leap-month';
import { getLeapMonthDays } from '../get-leap-month-days';

/**
* Return the number of days in this lunar year
Expand All @@ -14,8 +14,14 @@ import getLeapMonthDays from './get-leap-month-days';
* @example
* // => 384
* getYearDays(2025)
*
* @example
* // => -1
* getYearDays(1899)
*/
function getYearDays(year: number): number {
export function getYearDays(year: number): number {
if (!year || year < BASE_YEAR || year > MAXIMUM_LUNAR_YEAR) return -1;

let sum = 348;
const yearData = LUNAR_DATA[year - BASE_YEAR];
for (let i = 0x80_00; i > 0x8; i >>= 1) {
Expand All @@ -24,5 +30,3 @@ function getYearDays(year: number): number {

return sum + (getLeapMonth(year) ? getLeapMonthDays(year) : 0);
}

export default getYearDays;
35 changes: 35 additions & 0 deletions src/get-year-days/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { type Assertion, describe, expect, it } from 'vitest';
import { getYearDays } from './index';

const expectFunction = (year?: unknown): Assertion => expect(getYearDays(year as number));

const testFunction = (year: number, expected: number) => {
expectFunction(year).toBe(expected);
};

describe('Leap Year', () => {
it.each([
[2023, 384],
[2025, 384],
])('%i -> %i', testFunction);
});

describe('Non-leap Year', () => {
it.each([
[2024, 354],
[2026, 354],
])('%i -> %i', testFunction);
});

describe('Exception Case', () => {
it('return -1 if the given year is invalid', () => {
expectFunction().toBe(-1);
expectFunction(null).toBe(-1);
expectFunction(Number.NaN).toBe(-1);
});

it('return -1 if the given year is out of boundary', () => {
expectFunction(1899).toBe(-1);
expectFunction(2101).toBe(-1);
});
});
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export * from './get-leap-month';
export * from './get-leap-month-days';
export * from './get-month-days';
export * from './get-year-days';
export * from './is-date';
export * from './is-valid-date';
export * from './is-valid-lunar';
Expand Down
13 changes: 8 additions & 5 deletions src/is-valid-lunar/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { BASE_MONTH, BASE_YEAR, MAXIMUM_LUNAR_DAY, MAXIMUM_LUNAR_MONTH, MAXIMUM_LUNAR_YEAR } from '../_lib/constants';
import getLeapMonth from '../_lib/get-leap-month';
import getLeapMonthDays from '../_lib/get-leap-month-days';
import getMonthDays from '../_lib/get-month-days';
import { getLeapMonth } from '../get-leap-month';
import { getLeapMonthDays } from '../get-leap-month-days';
import { getMonthDays } from '../get-month-days';
import { isPlainObject } from '../is-plain-object';
import type { LunarDate } from '../types';

Expand Down Expand Up @@ -41,8 +41,11 @@ export function isValidLunar(lunar: unknown): lunar is LunarDate {
return false;
}

const isLeapMonth = getLeapMonth(lunar.year) === lunar.month;
if (lunar.day > (isLeapMonth ? getLeapMonthDays(lunar.year) : getMonthDays(lunar.year, lunar.month))) {
const isLeapMonth = Boolean(lunar.isLeapMonth);
if (
(isLeapMonth && getLeapMonth(lunar.year) !== lunar.month) ||
lunar.day > (isLeapMonth ? getLeapMonthDays(lunar.year) : getMonthDays(lunar.year, lunar.month))
) {
return false;
}

Expand Down
8 changes: 4 additions & 4 deletions src/to-lunar/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { BASE_YEAR, MAXIMUM_YEAR } from '../_lib/constants';
import getBaseDate from '../_lib/get-base-date';
import getLeapMonth from '../_lib/get-leap-month';
import getLeapMonthDays from '../_lib/get-leap-month-days';
import getMonthDays from '../_lib/get-month-days';
import getYearDays from '../_lib/get-year-days';
import { getLeapMonth } from '../get-leap-month';
import { getLeapMonthDays } from '../get-leap-month-days';
import { getMonthDays } from '../get-month-days';
import { getYearDays } from '../get-year-days';
import { isValidSolar } from '../is-valid-solar';
import { toGMTDate } from '../to-gmt-date';
import type { LunarDate } from '../types';
Expand Down
14 changes: 7 additions & 7 deletions src/to-solar/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { BASE_YEAR } from '../_lib/constants';
import getBaseDate from '../_lib/get-base-date';
import getLeapMonth from '../_lib/get-leap-month';
import getLeapMonthDays from '../_lib/get-leap-month-days';
import getMonthDays from '../_lib/get-month-days';
import getYearDays from '../_lib/get-year-days';
import { getLeapMonth } from '../get-leap-month';
import { getLeapMonthDays } from '../get-leap-month-days';
import { getMonthDays } from '../get-month-days';
import { getYearDays } from '../get-year-days';
import { isValidLunar } from '../is-valid-lunar';
import type { LunarDate, Optionalize } from '../types';
import type { LunarDate } from '../types';

/**
* Converts a given lunar date to a solar (Gregorian) date. The function calculates the corresponding solar year,
Expand All @@ -21,7 +21,7 @@ import type { LunarDate, Optionalize } from '../types';
* // => -1
* toSolar({ year: 1900, month: 1, day: 30, isLeapMonth: false })
*/
export function toSolar(lunar: Optionalize<LunarDate, 'isLeapMonth'>): Date | -1 {
export function toSolar(lunar: LunarDate): Date | -1 {
if (!isValidLunar(lunar)) return -1;

let offset = 0;
Expand All @@ -41,7 +41,7 @@ export function toSolar(lunar: Optionalize<LunarDate, 'isLeapMonth'>): Date | -1
}
}

if (leapMonth === lunar.month) {
if (lunar.isLeapMonth) {
offset += getMonthDays(lunar.year, lunar.month);
}

Expand Down
2 changes: 2 additions & 0 deletions src/to-solar/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ describe('Exception Case', () => {
});

it('return -1 if the given lunar date is incorrect', () => {
// Not a leap month
expectFunction({ year: 2025, month: 1, day: 1, isLeapMonth: true }).toBe(-1);
// Is a 29-day leap month
expectFunction({ year: 2025, month: 6, day: 30, isLeapMonth: true }).toBe(-1);
// Is a 29-day month
Expand Down

0 comments on commit 340335d

Please sign in to comment.