% weekschedule.cls - A LaTeX class for creating weekly schedule calendars % Usage: % \documentclass{weekschedule} % \scheduletitle{Weekly Schedule - Spring 2026} % \scheduleauthor{Dr. Smith} % \timefrom{8:00} % \timeto{18:00} % \twelvehourtime % or \twentyfourhourtime % \eventclass{Courses}{173,216,230} % RGB values % \event{Courses}{MATH 101}{Monday,Wednesday,Friday}{9:00}{10:00} % \begin{document} % \printschedule % \end{document} \NeedsTeXFormat{LaTeX2e} \ProvidesClass{weekschedule}[2026/01/11 Weekly Schedule Class] % Load base class \LoadClass[landscape,11pt]{article} % Required packages \RequirePackage[margin=0.5in]{geometry} \RequirePackage{tikz} \RequirePackage{xcolor} \RequirePackage{pgfmath} \pagestyle{empty} % ============================================ % Configuration variables with defaults % ============================================ \newcommand{\@scheduletitle}{Weekly Schedule} \newcommand{\@scheduleauthor}{} \newcommand{\@starthour}{8} \newcommand{\@endhour}{18} \newif\if@usetwentyfourhour \@usetwentyfourhourfalse % Layout parameters \newcommand{\@daywidth}{4.5} \newcommand{\@hourheight}{1.5} \newcommand{\@padding}{0.08} % Week configuration \newcommand{\@weektype}{workweek} % workweek, fullweeksunday, fullweekmonday \newcommand{\@weekdaylist}{Monday,Tuesday,Wednesday,Thursday,Friday} \newcommand{\@weekdaydisplay}{Monday,Tuesday,Wednesday,Thursday,Friday} \newcommand{\@daycount}{5} % Minute line configuration \newcommand{\@minutelineinterval}{0} % 0=off, 30=half-hour, 15=quarter-hour % ============================================ % User-facing configuration commands % ============================================ \newcommand{\scheduletitle}[1]{\renewcommand{\@scheduletitle}{#1}} \newcommand{\scheduleauthor}[1]{\renewcommand{\@scheduleauthor}{#1}} % Parse time string like "8:00" or "13:30" and store hour \newcommand{\timefrom}[1]{% \@parsetime{#1}% \pgfmathtruncatemacro{\@temph}{\@parsedtime}% \edef\@starthour{\@temph}% } \newcommand{\timeto}[1]{% \@parsetime{#1}% \pgfmathtruncatemacro{\@temph}{\@parsedtime}% \edef\@endhour{\@temph}% } % Time format toggles \newcommand{\twentyfourhourtime}{\@usetwentyfourhourtrue} \newcommand{\twelvehourtime}{\@usetwentyfourhourfalse} % Week configuration toggles \newcommand{\weekworkweek}{% \renewcommand{\@weektype}{workweek}% \renewcommand{\@weekdaylist}{Monday,Tuesday,Wednesday,Thursday,Friday}% \renewcommand{\@weekdaydisplay}{Monday,Tuesday,Wednesday,Thursday,Friday}% \renewcommand{\@daycount}{5}% } \newcommand{\weekfullsunday}{% \renewcommand{\@weektype}{fullweeksunday}% \renewcommand{\@weekdaylist}{Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday}% \renewcommand{\@weekdaydisplay}{Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday}% \renewcommand{\@daycount}{7}% } \newcommand{\weekfullmonday}{% \renewcommand{\@weektype}{fullweekmonday}% \renewcommand{\@weekdaylist}{Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday}% \renewcommand{\@weekdaydisplay}{Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday}% \renewcommand{\@daycount}{7}% } % Layout customization \newcommand{\setdaywidth}[1]{\renewcommand{\@daywidth}{#1}} \newcommand{\sethourheight}[1]{\renewcommand{\@hourheight}{#1}} \newcommand{\setpadding}[1]{\renewcommand{\@padding}{#1}} % Minute line configuration \newcommand{\minutelinesoff}{\renewcommand{\@minutelineinterval}{0}} \newcommand{\minutelinesthirty}{\renewcommand{\@minutelineinterval}{30}} \newcommand{\minutelinesfifteen}{\renewcommand{\@minutelineinterval}{15}} % ============================================ % Internal time parsing % ============================================ % Parse time to decimal (e.g., "9:30" -> 9.5) \def\@parsetime#1{\@@parsetime#1\@nil} \def\@@parsetime#1:#2\@nil{% \pgfmathparse{#1 + #2/60}% \edef\@parsedtime{\pgfmathresult}% } % ============================================ % Event class definitions (colors) % ============================================ \newcounter{@numclasses} \setcounter{@numclasses}{0} \newcounter{@daypos} % Define an event class with RGB color % Usage: \eventclass{ClassName}{R,G,B} \newcommand{\eventclass}[2]{% \definecolor{class@#1}{RGB}{#2}% \stepcounter{@numclasses}% \expandafter\def\csname @classname\the@numclasses\endcsname{#1}% } % ============================================ % Event storage % ============================================ \newcounter{@numevents} \setcounter{@numevents}{0} % Store an event % Usage: \event{ClassName}{Event Name}{Days}{Start}{End} % Days can be: Monday, Tuesday, Wednesday, Thursday, Friday (comma-separated) \newcommand{\event}[5]{% \stepcounter{@numevents}% \expandafter\def\csname @eventclass\the@numevents\endcsname{#1}% \expandafter\def\csname @eventname\the@numevents\endcsname{#2}% \expandafter\def\csname @eventdays\the@numevents\endcsname{#3}% \expandafter\def\csname @eventstart\the@numevents\endcsname{#4}% \expandafter\def\csname @eventend\the@numevents\endcsname{#5}% } % ============================================ % Main schedule rendering % ============================================ \newcommand{\printschedule}{% % Title and author \begin{center} \LARGE\textbf{\@scheduletitle}% \ifx\@scheduleauthor\empty\else\\[0.2cm]\Large\@scheduleauthor\fi% \end{center} \vspace{0.05cm} \centering \begin{tikzpicture}[x=1cm, y=1cm] % Calculate dynamic day width based on number of days % Reserve 1.5cm for time labels, divide remaining width by day count \pgfmathsetmacro{\availablewidth}{(\textwidth/1cm) - 1.5} \pgfmathsetmacro{\daywidth}{\availablewidth / \@daycount} % Store other parameters \pgfmathsetmacro{\hourheight}{\@hourheight} \pgfmathsetmacro{\padding}{\@padding} \pgfmathtruncatemacro{\starthour}{\@starthour} \pgfmathtruncatemacro{\endhour}{\@endhour} % Calculate grid width (used later for drawing) \pgfmathsetmacro{\gridwidth}{\@daycount*\daywidth} % Calculate shift to center the grid properly % \centering centers the bounding box, which includes time labels extending left. % The bounding box center is approximately at (-1.5 + gridwidth)/2. % The grid center is at gridwidth/2. % To align grid center with bounding box center (which is at page center), % shift right by: gridwidth/2 - (-1.5 + gridwidth)/2 = 1.5/2 = 0.75cm \pgfmathsetmacro{\timelabelreserved}{1.5} \pgfmathsetmacro{\xshift}{\timelabelreserved/2} % Shift everything to align grid center with page center \begin{scope}[xshift=\xshift cm] % Draw column headers (days) - iterate through configured day list \setcounter{@daypos}{0} \@for\@currentday:=\@weekdaylist\do{% \node[anchor=south, font=\large\bfseries] at (\the@daypos*\daywidth+\daywidth/2, -\starthour*\hourheight+0.2) {\@currentday}; \stepcounter{@daypos}% } % Draw row headers (times) \foreach \hour in {\starthour,...,\endhour} { \pgfmathtruncatemacro{\displayhour}{mod(\hour-1,12)+1} \if@usetwentyfourhour \node[anchor=east, font=\small] at (-0.1, -\hour*\hourheight) {\hour:00}; \else \ifnum\hour<12 \def\@ampm{AM} \else \def\@ampm{PM} \fi \node[anchor=east, font=\small] at (-0.1, -\hour*\hourheight) {\displayhour:00 \@ampm}; \fi } % Draw vertical lines (day separators) - dynamically based on week type \foreach \day in {0,...,\@daycount} { \draw[gray] (\day*\daywidth, -\starthour*\hourheight) -- (\day*\daywidth, -\endhour*\hourheight); } % Draw horizontal lines (hour separators) \pgfmathsetmacro{\gridwidth}{\@daycount*\daywidth} \foreach \hour in {\starthour,...,\endhour} { \draw[gray] (0, -\hour*\hourheight) -- (\gridwidth, -\hour*\hourheight); } % Draw minute lines (dashed) based on configuration \pgfmathtruncatemacro{\lasthour}{\endhour-1} \pgfmathtruncatemacro{\minuteinterval}{\@minutelineinterval} \ifnum\minuteinterval=30 % Draw half-hour lines \foreach \hour in {\starthour,...,\lasthour} { \draw[gray, dashed, line width=0.25pt] (0, -\hour*\hourheight-0.5*\hourheight) -- (\gridwidth, -\hour*\hourheight-0.5*\hourheight); } \fi \ifnum\minuteinterval=15 % Draw quarter-hour lines \foreach \hour in {\starthour,...,\lasthour} { \draw[gray, dashed, line width=0.25pt] (0, -\hour*\hourheight-0.25*\hourheight) -- (\gridwidth, -\hour*\hourheight-0.25*\hourheight); \draw[gray, dashed, line width=0.25pt] (0, -\hour*\hourheight-0.5*\hourheight) -- (\gridwidth, -\hour*\hourheight-0.5*\hourheight); \draw[gray, dashed, line width=0.25pt] (0, -\hour*\hourheight-0.75*\hourheight) -- (\gridwidth, -\hour*\hourheight-0.75*\hourheight); } \fi % Draw all events \foreach \evnum in {1,...,\the@numevents} { \@drawevent{\evnum} } \end{scope} \end{tikzpicture} \par \vspace{0.2cm} % Legend \begin{center} \begin{tikzpicture} \foreach \clnum in {1,...,\the@numclasses} { \pgfmathsetmacro{\xpos}{(\clnum-1)*3.5} \edef\@clname{\csname @classname\clnum\endcsname} \fill[class@\@clname] (\xpos,0) rectangle (\xpos+0.4,0.3); \node[anchor=west] at (\xpos+0.5,0.15) {\@clname}; } \end{tikzpicture} \end{center} } % ============================================ % Event drawing helper % ============================================ \newcommand{\@drawevent}[1]{% \edef\@evclass{\csname @eventclass#1\endcsname}% \edef\@evname{\csname @eventname#1\endcsname}% \edef\@evdays{\csname @eventdays#1\endcsname}% \edef\@evstart{\csname @eventstart#1\endcsname}% \edef\@evend{\csname @eventend#1\endcsname}% % Parse times \expandafter\@parsetime\expandafter{\@evstart}% \let\@startdec\@parsedtime \expandafter\@parsetime\expandafter{\@evend}% \let\@enddec\@parsedtime % Draw for each day \@drawforeachday{\@evclass}{\@evname}{\@evdays}{\@startdec}{\@enddec}% } \newcounter{@evdaypos} \newcommand{\@drawforeachday}[5]{% % #1=class, #2=name, #3=event days, #4=start, #5=end % Iterate through the week's day list and draw event if it's on that day \setcounter{@evdaypos}{0} \@for\@weekday:=\@weekdaylist\do{% \@checkandrawday{#1}{#2}{#3}{#4}{#5}{\@weekday}{\the@evdaypos}% \stepcounter{@evdaypos}% }% } \newcommand{\@checkandrawday}[7]{% % #1=class, #2=name, #3=event days string, #4=start, #5=end, #6=dayname, #7=daypos % Check if this day is in the event's day list \@checkdayinlist{#3}{#6}% \if@daymatched \@draweventbox{#1}{#2}{#7}{#4}{#5}% \fi } \newif\if@daymatched \newcommand{\@checkdayinlist}[2]{% % #1 = comma-separated day list, #2 = day to check \@daymatchedfalse \edef\@daytocheck{#2}% \@for\@testday:=#1\do{% \edef\@trimmedday{\@testday}% \ifnum\pdfstrcmp{\@trimmedday}{\@daytocheck}=0 \@daymatchedtrue \fi }% } \newcommand{\@draweventbox}[5]{% % #1 = class, #2 = name, #3 = day number, #4 = start decimal, #5 = end decimal % Note: daywidth, hourheight, padding are already set in parent scope % Clamp start and end times to calendar bounds \pgfmathsetmacro{\startdec}{max(#4, \@starthour)} \pgfmathsetmacro{\enddec}{min(#5, \@endhour)} \pgfmathsetmacro{\xstart}{#3*\daywidth+\padding} \pgfmathsetmacro{\xend}{#3*\daywidth+\daywidth-\padding} \pgfmathsetmacro{\ystart}{-\startdec*\hourheight-\padding} \pgfmathsetmacro{\yend}{-\enddec*\hourheight+\padding} \pgfmathsetmacro{\ymid}{(\ystart+\yend)/2} \pgfmathsetmacro{\xcenter}{#3*\daywidth+\daywidth/2} % Draw the box \fill[class@#1, rounded corners] (\xstart, \ystart) rectangle (\xend, \yend); % Draw the label \node[font=\small, align=center] at (\xcenter, \ymid) {#2}; } \endinput