portfolio-webpage/src/content/blog/doomsday-algorithm/DynamicDoomsdayDisplay.astro

246 lines
8.4 KiB
Plaintext

---
import { cn } from "@/lib/utils";
export interface Props {
display:
| "weekday"
| "weekday-number"
| "weekday-of"
| "weekday-number-of"
| "year"
| "january-day"
| "february-day"
| "if-leap-year";
prefix?: string;
suffix?: string;
offset?: number;
resultModulo?: number;
value?: string;
valueAlt?: string;
valueClass?: string;
}
const {
display,
prefix,
suffix,
offset,
resultModulo,
valueClass: customClass,
value,
valueAlt,
} = Astro.props;
---
<span class={cn(`doomsday-display doomsday-${display}`)}
>{prefix}<span
class={cn("doomsday-value", customClass)}
data-offset={offset}
data-resultmodulo={resultModulo}
data-value={value}
data-valuealt={valueAlt}><slot /></span
>{suffix}</span
>
<script>
function isLeapYear(year: number) {
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
}
function optionalModulo(value: number, modulo?: number): number {
if (modulo === undefined) {
return value;
}
return (value + modulo) % modulo;
}
function dateFromValueString(value: string, year: number): Date | null {
const parts = value.split(".");
if (parts.length !== 3) {
return null;
}
const [dayStr, monthStr, _] = parts;
const day = parseInt(dayStr);
const month = parseInt(monthStr) - 1;
if (isNaN(day) || isNaN(month) || month < 0 || month > 11) {
return null;
}
const date = new Date(year, month, day);
return date;
}
function handleOffset<T>(
element: HTMLElement,
fn: (offset: number, modulo?: number) => T,
): T {
const offset = parseInt(element.dataset.offset || "0");
const resultModulo = element.dataset.resultmodulo
? parseInt(element.dataset.resultmodulo)
: undefined;
return fn(offset, resultModulo);
}
const weekdays = [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
];
const currentYearInput = document.getElementById(
"doomsday-year",
) as HTMLInputElement | null;
let firstUpdate = true;
const url = new URL(window.location.href);
const yearParam = url.searchParams.get("year");
const parsedYearParam = yearParam ? parseInt(yearParam) : null;
const initialYear =
parsedYearParam && !isNaN(parsedYearParam) && parsedYearParam >= 1583
? parsedYearParam
: new Date().getFullYear();
function update() {
const selectedYear =
!firstUpdate && currentYearInput && currentYearInput.value
? parseInt(currentYearInput.value)
: initialYear;
firstUpdate = false;
console.log("Selected Year:", selectedYear);
const isSelectedYearLeap = isLeapYear(selectedYear);
const febDoomsdayDay = 28 + (isSelectedYearLeap ? 1 : 0);
// Display the selected year
const yearElements = document.querySelectorAll(
".doomsday-display.doomsday-year > .doomsday-value",
);
yearElements.forEach((element) => {
element.textContent = handleOffset(
element as HTMLElement,
(offset, modulo) =>
optionalModulo(selectedYear + offset, modulo).toString(),
);
});
// Display the doomsday weekday for the selected year
const doomsdayWeekdayElements = document.querySelectorAll(
".doomsday-display.doomsday-weekday > .doomsday-value",
);
const doomsdayDate = new Date(selectedYear, 2, febDoomsdayDay);
const doomsdayOfSelectedYear = doomsdayDate.getDay();
const doomsdayOfSelectedYearInText = weekdays[doomsdayOfSelectedYear];
doomsdayWeekdayElements.forEach((element) => {
element.textContent = handleOffset(
element as HTMLElement,
(offset) => {
if (offset === 0) {
return doomsdayOfSelectedYearInText;
} else {
const adjustedDoomsday = optionalModulo(
doomsdayOfSelectedYear + offset,
7,
);
return weekdays[adjustedDoomsday];
}
},
);
});
// Display the weekday number of the doomsday for the selected year
const doomsdayWeekdayNumberElements = document.querySelectorAll(
".doomsday-display.doomsday-weekday-number > .doomsday-value",
);
doomsdayWeekdayNumberElements.forEach((element) => {
element.textContent = handleOffset(
element as HTMLElement,
(offset) =>
optionalModulo(
doomsdayOfSelectedYear + offset,
7,
).toString(),
).toString();
});
// Display the doomsday date for January and February
const janDoomsdayDay = isSelectedYearLeap ? 4 : 3;
const janDoomsdayDayElements = document.querySelectorAll(
".doomsday-display.doomsday-january-day > .doomsday-value",
);
janDoomsdayDayElements.forEach((element) => {
element.textContent = handleOffset(
element as HTMLElement,
(offset, modulo) =>
optionalModulo(janDoomsdayDay + offset, modulo).toString(),
);
});
const febDoomsdayDayElements = document.querySelectorAll(
".doomsday-display.doomsday-february-day > .doomsday-value",
);
febDoomsdayDayElements.forEach((element) => {
element.textContent = handleOffset(
element as HTMLElement,
(offset, modulo) =>
optionalModulo(febDoomsdayDay + offset, modulo).toString(),
);
});
// Weekday of a specific date
const weekdayOfElements = document.querySelectorAll(
".doomsday-display.doomsday-weekday-of > .doomsday-value",
);
weekdayOfElements.forEach((element) => {
const dateStr = (element as HTMLElement).dataset.value || "";
const date = dateFromValueString(dateStr, selectedYear);
if (date === null) {
element.textContent = "INVALID DATE";
element.classList.add("text-red-500");
return;
}
const weekday = date.getDay();
element.textContent = handleOffset(
element as HTMLElement,
(offset) => {
const adjustedWeekday = optionalModulo(weekday + offset, 7);
return weekdays[adjustedWeekday];
},
);
});
const weekdayNumberOfElements = document.querySelectorAll(
".doomsday-display.doomsday-weekday-number-of > .doomsday-value",
);
weekdayNumberOfElements.forEach((element) => {
const dateStr = (element as HTMLElement).dataset.value || "";
const date = dateFromValueString(dateStr, selectedYear);
if (date === null) {
element.textContent = "INVALID DATE";
element.classList.add("text-red-500");
return;
}
const weekday = date.getDay();
element.textContent = handleOffset(
element as HTMLElement,
(offset, modulo) => {
const adjustedWeekday = optionalModulo(
weekday + offset,
modulo,
);
return adjustedWeekday.toString();
},
);
});
// Conditional on the leap year status
const ifLeapYearElements = document.querySelectorAll(
".doomsday-display.doomsday-if-leap-year > .doomsday-value",
);
ifLeapYearElements.forEach((element) => {
element.textContent = isSelectedYearLeap
? (element as HTMLElement).dataset.value || ""
: (element as HTMLElement).dataset.valuealt || "";
});
}
currentYearInput?.addEventListener("change", update);
update();
</script>