Skip to content

Commit

Permalink
move getYearMonthDay to own file and test
Browse files Browse the repository at this point in the history
  • Loading branch information
Liam-Tait committed Jul 22, 2024
1 parent 5ca9b94 commit 10a3470
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 71 deletions.
20 changes: 20 additions & 0 deletions src/getYearMonthDay.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { daysInMonth } from "./daysInMonth.js";
import { extractLeapYear, extractOrdinal, extractYear } from "./yold.js";

/**
* Get the year, month, and day from a YOLD date.
* @param yold The YOLD date.
* @returns The year, month, and day as a tuple.
*/

export function getYearMonthDay(yold: number): [number, number, number] {
const year = extractYear(yold);
let day = extractOrdinal(yold);
const leapYear = extractLeapYear(yold);
let month = 1;
while (day > daysInMonth(month, leapYear)) {
day -= daysInMonth(month, leapYear);
month++;
}
return [year, month, day];
}
2 changes: 1 addition & 1 deletion src/iso.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getYearMonthDay } from "./getYearMonthDay.js";
import { fromYMD } from "./yold.js";
import { getYearMonthDay } from "./yold.js";

/**
* Convert an ISO date string to a YOLD date.
Expand Down
52 changes: 15 additions & 37 deletions src/yold.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { daysInMonth } from "./daysInMonth.js";
import { getLastDayOfYearDayOfWeek } from "./getLastDayOfYearDayOfWeek.js";
import { isLeapYear } from "./isLeapYear.js";

/**
import { isLeapYear } from "./isLeapYear.js"; /**
* Mask a year, ordinal, leap year, and day of week into a single number.
*
* The year is a 13-bit number, which allows for years from 1 to 9999.
Expand All @@ -18,8 +16,8 @@ import { isLeapYear } from "./isLeapYear.js";
* Negative years are not supported.
*
* @param year The year, from 1 to 9999.
* @param ordinal The ordinal, from 1 to 366.
* @param leapYear 1 if year is a leap year, 0 otherwise.
* @param ordinal The ordinal days, from 1 to 366.
* @param leapYear 1 if year is a leap year, 0 if year is a common year.
* @param dayOfWeek Day of the week for the last day of the year before this year. 1 for Monday, 7 for Sunday.
*/
export function mask(
Expand All @@ -30,12 +28,13 @@ export function mask(
): number {
if (year < 1 || year > 9999) {
throw new Error(
`Invalid year: year must be greater than 0 but got ${year}`,
`Invalid year: year must be greater than 0 and less than 9999 but got ${year}`,
);
}
if (ordinal < 1 || ordinal > 366) {
const daysInYear = leapYear ? 366 : 365;
if (ordinal < 1 || ordinal > daysInYear) {
throw new Error(
`Invalid ordinal: ordinal must be between 1 and 366 but got ${ordinal}`,
`Invalid ordinal: ordinal must be between 1 and ${daysInYear} for ${year} but got ${ordinal}`,
);
}
if (dayOfWeek < 1 || dayOfWeek > 7) {
Expand All @@ -45,15 +44,10 @@ export function mask(
}
if (leapYear !== 0 && leapYear !== 1) {
throw new Error(
"Invalid leap year: leap year must be 0 or 1 but got ${leapYear}",
`Invalid leap year: leap year must be 0 or 1 but got ${leapYear}`,
);
}
const yearBits = year << 13;
const ordinalBits = ordinal << 4;
const leapYearBits = leapYear << 3;
const dayOfWeekBits = dayOfWeek;
const yold = yearBits | ordinalBits | leapYearBits | dayOfWeekBits;
return yold;
return (year << 13) | (ordinal << 4) | (leapYear << 3) | dayOfWeek;
}

/**
Expand Down Expand Up @@ -110,23 +104,6 @@ export function extractDayOfWeekOfDayBefore(yold: number): number {
return dayOfWeek;
}

/**
* Get the year, month, and day from a YOLD date.
* @param yold The YOLD date.
* @returns The year, month, and day as a tuple.
*/
export function getYearMonthDay(yold: number): [number, number, number] {
const year = extractYear(yold);
let day = extractOrdinal(yold);
const leapYear = extractLeapYear(yold);
let month = 1;
while (day > daysInMonth(month, leapYear)) {
day -= daysInMonth(month, leapYear);
month++;
}
return [year, month, day];
}

/**
* Create a YOLD date from a year, month, and day.
* @param year The year, from 1 to 9999.
Expand All @@ -135,15 +112,16 @@ export function getYearMonthDay(yold: number): [number, number, number] {
* @returns The YOLD date.
*/
export function fromYMD(year: number, month: number, day: number): number {
if (year < 1) {
throw new Error("Invalid year");
}
if (month < 1 || month > 12) {
throw new Error("Invalid month");
throw new Error(
`Invalid month: month must be between 1 and 12 but got ${month}`,
);
}
const leapYear = isLeapYear(year) ? 1 : 0;
if (day < 1 || day > daysInMonth(month, leapYear)) {
throw new Error("Invalid date");
throw new Error(
`Invalid day: day must be between 1 and ${daysInMonth(month, leapYear)} for ${month}/${year} but got ${day}`,
);
}
let ordinal = 0;
while (month > 1) {
Expand Down
22 changes: 11 additions & 11 deletions test/daysInMonth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { daysInMonth } from "../src/daysInMonth.js";

describe("daysInMonth", () => {
test("returns 31 for January", () => {
assert.strictEqual(daysInMonth(1, 0), 31);
assert.strictEqual(daysInMonth(1, 0), 31);
assert.strictEqual(daysInMonth(1, 1), 31);
});
test("returns 29 for February in a leap year", () => {
Expand All @@ -14,43 +14,43 @@ describe("daysInMonth", () => {
assert.strictEqual(daysInMonth(2, 0), 28);
});
test("returns 31 for March", () => {
assert.strictEqual(daysInMonth(3, 0), 31);
assert.strictEqual(daysInMonth(3, 0), 31);
assert.strictEqual(daysInMonth(3, 1), 31);
});
test("returns 30 for April", () => {
assert.strictEqual(daysInMonth(4, 0), 30);
assert.strictEqual(daysInMonth(4, 0), 30);
assert.strictEqual(daysInMonth(4, 1), 30);
});
test("returns 31 for May", () => {
assert.strictEqual(daysInMonth(5, 0), 31);
assert.strictEqual(daysInMonth(5, 0), 31);
assert.strictEqual(daysInMonth(5, 1), 31);
});
test("returns 30 for June", () => {
assert.strictEqual(daysInMonth(6, 0), 30);
assert.strictEqual(daysInMonth(6, 0), 30);
assert.strictEqual(daysInMonth(6, 1), 30);
});
test("returns 31 for July", () => {
assert.strictEqual(daysInMonth(7, 0), 31);
assert.strictEqual(daysInMonth(7, 0), 31);
assert.strictEqual(daysInMonth(7, 1), 31);
});
test("returns 31 for August", () => {
assert.strictEqual(daysInMonth(8, 0), 31);
assert.strictEqual(daysInMonth(8, 0), 31);
assert.strictEqual(daysInMonth(8, 1), 31);
});
test("returns 30 for September", () => {
assert.strictEqual(daysInMonth(9, 0), 30);
assert.strictEqual(daysInMonth(9, 0), 30);
assert.strictEqual(daysInMonth(9, 1), 30);
});
test("returns 31 for October", () => {
assert.strictEqual(daysInMonth(10, 0), 31);
assert.strictEqual(daysInMonth(10, 0), 31);
assert.strictEqual(daysInMonth(10, 1), 31);
});
test("returns 30 for November", () => {
assert.strictEqual(daysInMonth(11, 0), 30);
assert.strictEqual(daysInMonth(11, 0), 30);
assert.strictEqual(daysInMonth(11, 1), 30);
});
test("returns 31 for December", () => {
assert.strictEqual(daysInMonth(12, 0), 31);
assert.strictEqual(daysInMonth(12, 1), 31);
assert.strictEqual(daysInMonth(12, 1), 31);
});
});
25 changes: 25 additions & 0 deletions test/getYearMonthDay.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import assert from "node:assert/strict";
import { describe, test } from "node:test";
import { daysInMonth } from "../src/daysInMonth.js";
import { getYearMonthDay } from "../src/getYearMonthDay.js";
import { isLeapYear } from "../src/isLeapYear.js";
import { fromYMD } from "../src/yold.js";

describe("getYearMonthDay", () => {
test("returns the same year, month, and day as the input fromYMD", () => {
for (let year = 1; year <= 9999; year++) {
const leapYear = isLeapYear(year) ? 1 : 0;
for (let month = 1; month <= 12; month++) {
const maxDay = daysInMonth(month, leapYear);
for (let day = 1; day <= maxDay; day++) {
const yold = fromYMD(year, month, day);
const [extractedYear, extractedMonth, extractedDay] =
getYearMonthDay(yold);
assert.strictEqual(extractedYear, year);
assert.strictEqual(extractedMonth, month);
assert.strictEqual(extractedDay, day);
}
}
}
});
});
26 changes: 13 additions & 13 deletions test/isLeapYear.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@ import { isLeapYear } from "../src/isLeapYear.js";
describe("isLeapYear", () => {
test("returns true for leap years", () => {
assert.strictEqual(isLeapYear(2000), true);
assert.strictEqual(isLeapYear(2004), true);
assert.strictEqual(isLeapYear(2008), true);
assert.strictEqual(isLeapYear(2012), true);
assert.strictEqual(isLeapYear(2016), true);
assert.strictEqual(isLeapYear(2020), true);
assert.strictEqual(isLeapYear(2024), true);
assert.strictEqual(isLeapYear(2004), true);
assert.strictEqual(isLeapYear(2008), true);
assert.strictEqual(isLeapYear(2012), true);
assert.strictEqual(isLeapYear(2016), true);
assert.strictEqual(isLeapYear(2020), true);
assert.strictEqual(isLeapYear(2024), true);
});
test("returns false for non-leap years", () => {
assert.strictEqual(isLeapYear(2001), false);
assert.strictEqual(isLeapYear(2002), false);
assert.strictEqual(isLeapYear(2003), false);
assert.strictEqual(isLeapYear(2005), false);
assert.strictEqual(isLeapYear(2006), false);
assert.strictEqual(isLeapYear(2007), false);
assert.strictEqual(isLeapYear(2009), false);
assert.strictEqual(isLeapYear(2001), false);
assert.strictEqual(isLeapYear(2002), false);
assert.strictEqual(isLeapYear(2003), false);
assert.strictEqual(isLeapYear(2005), false);
assert.strictEqual(isLeapYear(2006), false);
assert.strictEqual(isLeapYear(2007), false);
assert.strictEqual(isLeapYear(2009), false);
});
});
3 changes: 2 additions & 1 deletion test/nextDay.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import assert from "node:assert/strict";
import { describe, test } from "node:test";
import { getYearMonthDay } from "../src/getYearMonthDay.js";
import { nextDay } from "../src/nextDay.js";
import { extractYear, fromYMD, getYearMonthDay } from "../src/yold.js";
import { fromYMD } from "../src/yold.js";

describe("nextDay", () => {
test("returns the next day", () => {
Expand Down
16 changes: 8 additions & 8 deletions test/yold.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import assert from "node:assert/strict";
import { describe, test } from "node:test";
import { getLastDayOfYearDayOfWeek } from "../src/getLastDayOfYearDayOfWeek.js";
import { isLeapYear } from "../src/isLeapYear.js";
import {
mask,
extractYear,
extractOrdinal,
extractLeapYear,
extractDayOfWeekOfDayBefore,
extractLeapYear,
extractOrdinal,
extractYear,
mask,
} from "../src/yold.js";
import { isLeapYear } from "../src/isLeapYear.js";
import { getLastDayOfYearDayOfWeek } from "../src/getLastDayOfYearDayOfWeek.js";

describe("mask & extract", () => {
test("extracts the same year, ordinal, leapYear, dow as masked", () => {
Expand All @@ -17,15 +17,15 @@ describe("mask & extract", () => {
for (let year = 1; year <= 9999; year++) {
const leapYear = isLeapYear(year) ? 1 : 0;
const dow = getLastDayOfYearDayOfWeek(year);
const maxOrdinal = leapYear ? 366 : 365;
const maxOrdinal = leapYear ? 366 : 365;
for (let ordinal = 1; ordinal <= maxOrdinal; ordinal++) {
const date = mask(year, ordinal, leapYear, dow);
const extractedYear = extractYear(date);
const extractedOrdinal = extractOrdinal(date);
const extractedLeapYear = extractLeapYear(date);
const extractedDow = extractDayOfWeekOfDayBefore(date);

assert.strictEqual(typeof date, "number");
assert.strictEqual(typeof date, "number");
assert.strictEqual(extractedYear, year);
assert.strictEqual(extractedOrdinal, ordinal);
assert.strictEqual(extractedLeapYear, leapYear);
Expand Down

0 comments on commit 10a3470

Please sign in to comment.