- Format your holidays like so:
-
- {exampleString}
-
+
+
+
+
+ Format your holidays like so:
+
+ {exampleString}
+
+
- {Object.keys(parsedText).map((k) => (
-
-
{k}
-
- {parsedText[k].map((day) => {
- const parsedDate = getDateFromStringOrThrow(
- day,
- "holiday preview display"
- );
- return
{dateToMarkdownString(parsedDate)}
;
- })}
-
-
- ))}
+
+
+
);
}
-const parseHolidays = (
- inputText: string
-): { [holidayName: string]: string[] } => {
- const holidays: { [holidayName: string]: string[] } = {};
-
- const lines = inputText.split("\n").filter(line => line.trim() !== "");
- let currentHoliday: string | null = null;
+function ParsedHolidaysDisplay({ value }: { value: string }) {
+ const [parsedHolidays, setParsedHolidays] = useState<{
+ [holidayName: string]: string[];
+ }>({});
+ const [error, setError] = useState("");
- lines.forEach(line => {
- if (line.includes(":")) {
- // It's a holiday name
- const holidayName = line.split(":")[0].trim();
- currentHoliday = holidayName;
- holidays[currentHoliday] = [];
- } else if (currentHoliday && line.startsWith("-")) {
- // It's a date under the current holiday
- const date = line.replace("-", "").trim();
- holidays[currentHoliday].push(date);
- }
- });
+ useEffect(() => {
+ try {
+ const parsed = parseHolidays(value);
+ setParsedHolidays(parsed);
+ setError("");
+ } catch (error: any) {
+ setError(error + "");
+ }
+ }, [value]);
- return holidays;
-};
+ return (
+
+
{error}
+ {Object.keys(parsedHolidays).map((k) => (
+
+
{k}
+
+ {parsedHolidays[k].map((day) => {
+ const date = getDateFromString(day);
+ return (
+
+ {date?.toLocaleDateString("en-us", {
+ weekday: "short",
+ year: "numeric",
+ month: "short",
+ day: "numeric",
+ })}
+
+ );
+ })}
+
+
+ ))}
+
+ );
+}
diff --git a/nextjs/src/app/course/[courseName]/settings/page.tsx b/nextjs/src/app/course/[courseName]/settings/page.tsx
index 152cac3..c10a59f 100644
--- a/nextjs/src/app/course/[courseName]/settings/page.tsx
+++ b/nextjs/src/app/course/[courseName]/settings/page.tsx
@@ -1,5 +1,4 @@
import React from "react";
-import { useCourseContext } from "../context/courseContext";
import StartAndEndDate from "./StartAndEndDate";
import SettingsHeader from "./SettingsHeader";
import DefaultDueTime from "./DefaultDueTime";
diff --git a/nextjs/src/models/local/settingsUtils.tsx b/nextjs/src/models/local/settingsUtils.tsx
new file mode 100644
index 0000000..4f813ca
--- /dev/null
+++ b/nextjs/src/models/local/settingsUtils.tsx
@@ -0,0 +1,40 @@
+import {
+ dateToMarkdownString,
+ getDateFromString,
+ getDateFromStringOrThrow,
+ getDateOnlyMarkdownString,
+} from "./timeUtils";
+
+export const parseHolidays = (
+ inputText: string
+): { [holidayName: string]: string[] } => {
+ const holidays: { [holidayName: string]: string[] } = {};
+
+ const lines = inputText.split("\n").filter((line) => line.trim() !== "");
+ let currentHoliday: string | null = null;
+
+ lines.forEach((line) => {
+ if (line.includes(":")) {
+ const holidayName = line.split(":")[0].trim();
+ currentHoliday = holidayName;
+ holidays[currentHoliday] = [];
+ } else if (currentHoliday && line.startsWith("-")) {
+ const date = line.replace("-", "").trim();
+ const dateObject = getDateFromStringOrThrow(date, "parsing holiday text");
+ holidays[currentHoliday].push(getDateOnlyMarkdownString(dateObject));
+ }
+ });
+
+ return holidays;
+};
+
+
+export const holidaysToString = (holidays: { [holidayName: string]: string[] })=> {
+ const entries = Object.keys(holidays).map(holiday => {
+ const title = holiday + ":\n"
+ const days = holidays[holiday].map(d => `- ${d}\n`)
+ return title + days.join("")
+ })
+
+ return entries.join("")
+}
\ No newline at end of file
diff --git a/nextjs/src/models/local/tests/testHolidayParsing.test.ts b/nextjs/src/models/local/tests/testHolidayParsing.test.ts
new file mode 100644
index 0000000..aac45d0
--- /dev/null
+++ b/nextjs/src/models/local/tests/testHolidayParsing.test.ts
@@ -0,0 +1,29 @@
+import { describe, it, expect } from "vitest";
+import { parseHolidays } from "../settingsUtils";
+
+describe("can parse holiday string", () => {
+ it("can parse empty list", () => {
+ const testString = `
+springBreak:
+`;
+ const output = parseHolidays(testString);
+ expect(output).toEqual({ springBreak: [] });
+ });
+ it("can parse list with date", () => {
+ const testString = `
+springBreak:
+- 10/12/2024
+`;
+ const output = parseHolidays(testString);
+ expect(output).toEqual({ springBreak: ["10/12/2024"] });
+ });
+ it("can parse list with two dates", () => {
+ const testString = `
+springBreak:
+- 10/12/2024
+- 10/13/2024
+`;
+ const output = parseHolidays(testString);
+ expect(output).toEqual({ springBreak: ["10/12/2024", "10/13/2024"] });
+ });
+});
diff --git a/nextjs/src/models/local/tests/timeUtils.test.ts b/nextjs/src/models/local/tests/timeUtils.test.ts
index 0b0e501..0103f44 100644
--- a/nextjs/src/models/local/tests/timeUtils.test.ts
+++ b/nextjs/src/models/local/tests/timeUtils.test.ts
@@ -61,4 +61,12 @@ describe("Can properly handle expected date formats", () => {
expect(updatedString).toBe("08/29/2024 17:00:00")
})
+ it("can handle date without time", () => {
+ const dateString = "8/29/2024";
+ const dateObject = getDateFromString(dateString);
+
+ expect(dateObject).not.toBeUndefined()
+ const updatedString = dateToMarkdownString(dateObject!)
+ expect(updatedString).toBe("08/29/2024 00:00:00")
+ })
});
diff --git a/nextjs/src/models/local/timeUtils.ts b/nextjs/src/models/local/timeUtils.ts
index 6da28a3..76547f4 100644
--- a/nextjs/src/models/local/timeUtils.ts
+++ b/nextjs/src/models/local/timeUtils.ts
@@ -36,11 +36,18 @@ const _getDateFromISO = (value: string): Date | undefined => {
return isNaN(date.getTime()) ? undefined : date;
};
+const _getDateFromDateOnly = (datePart: string): Date | undefined => {
+ const [month, day, year] = datePart.split("/").map(Number);
+ const date = new Date(year, month - 1, day);
+ return isNaN(date.getTime()) ? undefined : date;
+};
+
export const getDateFromString = (value: string): Date | undefined => {
const ampmDateRegex =
/^\d{1,2}\/\d{1,2}\/\d{4},? \d{1,2}:\d{2}:\d{2}\s{1}[APap][Mm]$/; //"M/D/YYYY h:mm:ss AM/PM" or "M/D/YYYY, h:mm:ss AM/PM"
const militaryDateRegex = /^\d{1,2}\/\d{1,2}\/\d{4} \d{1,2}:\d{2}:\d{2}$/; //"MM/DD/YYYY HH:mm:ss"
const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}((.\d+)|(Z))$/; //"2024-08-26T00:00:00.0000000"
+ const dateOnlyRegex = /^\d{1,2}\/\d{1,2}\/\d{4}$/; // "M/D/YYYY" or "MM/DD/YYYY"
if (isoDateRegex.test(value)) {
return _getDateFromISO(value);
@@ -50,6 +57,8 @@ export const getDateFromString = (value: string): Date | undefined => {
} else if (militaryDateRegex.test(value)) {
const [datePart, timePart] = value.split(" ");
return _getDateFromMilitary(datePart, timePart);
+ } if (dateOnlyRegex.test(value)) {
+ return _getDateFromDateOnly(value);
} else {
if (value) console.log("invalid date format", value);
return undefined;