Created
June 30, 2025 02:35
-
-
Save dladukedev/7df7f497492b28810431674a2ee65e85 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Calendar</title> | |
<style> | |
* { | |
margin: 0; | |
padding: 0; | |
font-family: 'Arial'; | |
box-sizing: border-box; | |
} | |
@media print { | |
.no-print { | |
display: none; | |
} | |
} | |
.calendar-header { | |
height: .5in; | |
} | |
body { | |
padding: .2in .5in; | |
} | |
#header { | |
font-size: .4in; | |
} | |
table, | |
th, | |
td { | |
border: 1px solid black; | |
} | |
table { | |
border-collapse: collapse; | |
} | |
th { | |
height: .2in; | |
} | |
td { | |
font-weight: bold; | |
padding: .05in; | |
width: 1.4in; | |
height: 1.2in; | |
vertical-align: top; | |
} | |
td.other-month { | |
color: darkgrey; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="calendar-header"> | |
<div class="month-year" id="header"></div> | |
</div> | |
<table id="calendar"> | |
<tr class="day-header"> | |
<th>Sunday</th> | |
<th>Monday</th> | |
<th>Tuesday</th> | |
<th>Wednesday</th> | |
<th>Thursday</th> | |
<th>Friday</th> | |
<th>Saturday</th> | |
</tr> | |
<tbody id="calendarBody"></tbody> | |
</table> | |
<script> | |
const getCalendarMonthName = (calendar) => { | |
return new Date(calendar.year, calendar.month) | |
.toLocaleString('default', {month: 'long'}); | |
} | |
const getEligibleDates = (calendar) => { | |
const firstOfMonth = new Date(calendar.year, calendar.month) | |
const dayOfStart = firstOfMonth.getDay() | |
const start = new Date(calendar.year, calendar.month, 1 - dayOfStart) | |
return [...new Array(42).keys()].map(offset => { | |
const date = new Date(start.getFullYear(), start.getMonth(), start.getDate() + offset) | |
return { | |
date: date.getDate(), | |
isCalendarMonth: date.getMonth() === calendar.month, | |
} | |
}) | |
} | |
const removeExtraWeek = (dates) => { | |
const firstWeek = dates.slice(0, 7) | |
const lastWeek = dates.slice(-7) | |
const rest = dates.slice(7, -7) | |
const firstInMonthCount = firstWeek.filter(day => day.isCalendarMonth).length | |
const lastInMonthCount = lastWeek.filter(day => day.isCalendarMonth).length | |
return lastInMonthCount === 0 || firstInMonthCount > 3 | |
? [...firstWeek, ...rest] | |
: [...rest, ...lastWeek] | |
} | |
const splitWeeks = (dates) => { | |
return [ | |
dates.slice(0, 7), | |
dates.slice(7, 14), | |
dates.slice(14, 21), | |
dates.slice(21, 28), | |
dates.slice(28, 35), | |
] | |
} | |
const buildCalendarModel = (calendar) => { | |
const eligibleDates = getEligibleDates(calendar) | |
const dates = removeExtraWeek(eligibleDates) | |
const weeks = splitWeeks(dates) | |
return { | |
monthName: getCalendarMonthName(calendar), | |
year: calendar.year.toString(), | |
weeks, | |
} | |
} | |
const asDateHtml = (day) => { | |
const classes = day.isCalendarMonth ? 'date' : 'date other-month' | |
return `<td class="${classes}">${day.date}</td>` | |
} | |
const asWeekHtml = (week) => { | |
const weekHtml = week | |
.map(asDateHtml) | |
.join('') | |
return `<tr>${weekHtml}</tr>` | |
} | |
const buildCalendarHtml = (calendarModel) => { | |
const headerHtml = `${calendarModel.monthName} ${calendarModel.year}` | |
const weeksHtml = calendarModel.weeks | |
.map(asWeekHtml) | |
.join('') | |
return { | |
headerHtml, | |
weeksHtml, | |
} | |
} | |
const updateCalendar = (getCalendar, updateCalendarUi) => { | |
const calendar = getCalendar() | |
const calendarModel = buildCalendarModel(calendar) | |
const calendarHtml = buildCalendarHtml(calendarModel) | |
updateCalendarUi(calendarHtml) | |
} | |
const parseMonth = (month) => { | |
if (!month) return null | |
const parsedMonth = parseInt(month) | |
const isValid = !isNaN(parsedMonth) && parsedMonth <= 12 && parsedMonth >= 1 | |
return isValid ? parsedMonth - 1 : null | |
} | |
const parseYear = (year) => { | |
if (!year) return null | |
const parsedYear = parseInt(year) | |
const isValid = !isNaN(parsedYear) && parsedYear >= 0 | |
return isValid ? parsedYear : null | |
} | |
const getCalendarFromParams = () => { | |
const now = new Date() | |
const urlParams = new URLSearchParams(window.location.search); | |
const monthParam = urlParams.get('m'); | |
const yearParam = urlParams.get('y'); | |
const parsedMonth = parseMonth(monthParam) | |
const parsedYear = parseYear(yearParam) | |
const [month, year] = parsedMonth && parsedYear ? [parsedMonth, parsedYear] : [now.getMonth(), now.getFullYear()] | |
return {month, year} | |
} | |
const updateCalendarUi = (calendarHtml) => { | |
document.getElementById('header').innerHTML = calendarHtml.headerHtml | |
document.getElementById('calendarBody').innerHTML = calendarHtml.weeksHtml | |
} | |
updateCalendar(getCalendarFromParams, updateCalendarUi) | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment