--- title: "Perpetual Calendar 2068" id: 55039 type: "computer_media" slug: "perpetual-calendar-2068" url: "http://localhost/computer_media/perpetual-calendar-2068/" markdown_url: "http://localhost/computer_media/perpetual-calendar-2068.md" published_at: "2024-06-11T13:41:03+00:00" modified_at: "2026-03-30T21:44:39+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2019/02/20230809-041332.jpg" excerpt: "Enter any month and year from 1583 onward and watch a graphically drawn monthly calendar grid appear, fully calculated using Zeller's Congruence arithmetic." category: - name: "Archived Media" slug: "archived-media" taxonomy: "category" url: "http://localhost/category/archived-media/" post_tag: - name: "Downloadable" slug: "downloadable" taxonomy: "post_tag" url: "http://localhost/tag/downloadable/" - name: "TS 2068" slug: "ts2068" taxonomy: "post_tag" url: "http://localhost/tag/ts2068/" model: - name: "Timex/Sinclair 2068" slug: "ts-2068" taxonomy: "model" url: "http://localhost/model/ts-2068/" indiv: - name: "Imre Auersbacher" slug: "imre-auersbacher" taxonomy: "indiv" url: "http://localhost/indiv/imre-auersbacher/" genre: - name: "Calendar" slug: "calendar" taxonomy: "genre" url: "http://localhost/type/calendar/" media_contents: - id: 55036 title: "TS-2068 Computer Programs" type: "computer_media" url: "http://localhost/computer_media/ts-2068-computer-programs/" media_type: "Program" programmers: - name: "Imre Auersbacher" slug: "imre-auersbacher" taxonomy: "indiv" url: "http://localhost/indiv/imre-auersbacher/" mediadate: "1985" images: - url: "http://localhost/wp-content/uploads/2024/06/SCR-20260330-mlnm.png" - url: "http://localhost/wp-content/uploads/2024/06/SCR-20260330-mlky.png" article_media: - id: 44928 title: "Perpetual Calendar 2068" type: "article" url: "http://localhost/article/perpetual-calendar-2068/" - id: 44969 title: "Perpetual Calendar 2068" type: "article" url: "http://localhost/article/perpetual-calendar-2068-2/" - id: 46969 title: "Perpetual Calendar" type: "article" url: "http://localhost/article/perpetual-calendar-2/" media_type_tags: "Calendar" --- # Perpetual Calendar 2068 This program implements a perpetual calendar for dates ranging from 1583 to 9999 AD, using Zeller’s Congruence-style arithmetic to calculate the day of the week for any given month and year. The user inputs a month (1–12) and a four-digit year, and the program draws a bordered grid on screen representing a monthly calendar with day labels. Leap year detection handles the standard Gregorian rules: divisible by 4 but not 100, or divisible by 400. The calendar grid is drawn using PLOT and DRAW commands to create a 7-column, 6-row table, and dates are positioned by building a padded string from a pre-formatted date sequence stored in Z$. *** ## Program Analysis ### Program Structure The program is organized into four logical phases: 1. **Input and validation** (lines 20–30): prompts for month and year, rejecting out-of-range values with looping `GO TO`s. 2. **Day-of-week calculation** (lines 60–75): computes which weekday column the 1st of the month falls on. 3. **Days-in-month calculation** (lines 80–100): sets `C` to the correct number of days, including Gregorian leap year logic. 4. **Calendar rendering** (lines 200–290): draws the grid with PLOT/DRAW, prints column headers and month/year label, then places each date string into the correct cell. ### Day-of-Week Algorithm Lines 60–75 implement a variant of Zeller’s Congruence. The adjusted year `E` is decremented when the month is January or February (treating them as months 13 and 14 of the previous year). The adjusted month `C` (line 65) is offset by 12 for those months and incremented by 1. The day index `D` is then computed as: - `INT(E/400) - INT(E/100) + INT(1.25*E) + INT(2.6*C)` This combines the Gregorian century correction with the standard formula. Line 75 reduces `D` modulo 7 and adds 1, yielding a value from 1 (Sunday) to 7 (Saturday), representing the starting column of the first day. ### Date String Construction Line 15 defines `Z$` as a 74-character string containing two-character representations of the numbers 1 through 31, with single-digit numbers right-padded with a space (e.g., `" 1"`, `" 2"`, …, `"31"`). Line 264 constructs `C$` of length 74, then prefixes it with blank characters to shift the starting date by the computed day offset: `C$ = C$(TO 2*D-2) + Z$`. This effectively inserts `D-1` blank pairs before day 1, so date extraction in the print loop is straightforward. ### Leap Year Logic Line 100 uses a compound boolean expression typical of Sinclair BASIC. The condition `(B/4=INT(B/4))*(B/100<>INT(B/100))+(B/400=INT(B/400))` evaluates to non-zero (true) when the year is a leap year under Gregorian rules: divisible by 4 and not by 100, or divisible by 400. This correctly handles all Gregorian edge cases within the supported range. ### Grid Rendering Lines 200–240 draw the calendar border and internal grid lines using PLOT and DRAW. A 225×112 pixel rectangle is drawn first, followed by horizontal dividers (line 230) and vertical dividers (line 240). The seven column headers (S M T W Th F S) are printed at line 220. The inner grid has 6 row bands and 7 column bands. ### Date Placement Loop Lines 270–290 iterate `Z` from 1 to `C+D-1`, printing the two-character substring `C$(2*Z-1 TO 2*Z)` at screen position `(J, K)`. Column `K` advances by 4 each iteration. When `Z` is divisible by 7 (end of a week row), row `J` is incremented by 2 and `K` resets to 2. Starting the loop at `Z=1` with the pre-padded `C$` means the first `D-1` cells print blanks, correctly indenting the 1st of the month to its weekday column. ### Notable Techniques and Idioms - Boolean arithmetic is used throughout as a substitute for IF-THEN within expressions (e.g., `A+12*(A<3)`, `(A=4)+(A=6)+…`). - `RESTORE` followed by a `FOR`/READ loop at line 250 loads month names into the `DIM M$(12,9)` array; the dimension of 9 accommodates “SEPTEMBER” (9 characters), the longest name. - Input validation uses `BEEP 1,-20` on an invalid year as an audible error cue. - `DIM C$(74)` at line 264 initializes the string to 74 spaces before the concatenation that right-fills it with `Z$`; slicing with `TO 2*D-2` extracts exactly the needed blank prefix. - Line 290 uses two consecutive `STOP` statements; only the first is executed, the second is redundant. - The program uses `PAPER 6` in the opening PRINT and `BORDER 5` for screen coloring consistent with TS2068/Spectrum color attributes. ### Variable Summary | Variable | Role | | --- | --- | | `A` | Input month (1–12) | | `B` | Input year | | `C` | Days in the month; also reused as adjusted month in Zeller calculation | | `D` | Starting weekday column (1=Sunday … 7=Saturday) | | `E` | Adjusted year for Zeller’s formula | | `Z$` | Pre-formatted string of day numbers ” 1″ through “31” | | `C$` | Working string with blank prefix + day numbers for grid placement | | `M$()` | 12×9 array of month name strings | | `J`, `K` | Current print row and column within the grid | | `Z` | Loop counter for grid rendering and DATA read | ### Potential Anomaly The reuse of variable `C` is worth noting: it holds the adjusted month number (line 65) during the day-of-week calculation, then is overwritten with the days-in-month count (lines 80–100). This works correctly because the overwrite happens after `D` has been fully computed, but makes the code harder to follow at first glance. ## Source Code ``` 2 REM Perpetual Calendar 2068 4 REM \* 1984 I. Auersbacher 8 BORDER 5: DIM M$(12,9) 10 CLS : PRINT PAPER 6;"*** CALENDAR (1583-9999 AD) *** " 15 LET Z$=" 1 2 3 4 5 6 7 8 910111213141516171819202122232425262728293031": BEEP 0.05,20 20 INPUT "MONTH (1-12):";A: IF (A<1)+(A>12) THEN GO TO 20 30 INPUT "YEAR (YYYY):";B: IF B<1582 THEN BEEP 1,-20: GO TO 30 60 LET B=INT B: LET E=B-(A<3) 65 LET C=A+12*(A<3)+1 70 LET D=INT (E/400)-INT (E/100)+INT (1.25*E)+INT (2.6*C) 75 LET D=D-(7*INT (D/7))+1 80 IF (A=4)+(A=6)+(A=9)+(A=11) THEN LET C=30 85 IF (A=1)+(A=3)+(A=5)+(A=7)+(A=8)+(A=10)+(A=12) THEN LET C=31 90 IF A<>2 THEN GO TO 200 100 LET C=28: IF (B/4=INT (B/4))*(B/100<>INT (B/100))+(B/400=INT (B/400)) THEN LET C=29 200 PLOT 10,132: DRAW 225,0: DRAW 0,-112: DRAW -225,0: DRAW 0,112 210 PLOT 10,118: DRAW 225,0 220 PRINT AT 6,3;"S M T W Th F S": PRINT AT 3,0; 230 FOR Z=1 TO 6: PLOT 10,116-(Z-1)*16: DRAW 225,0: NEXT Z 240 FOR Z=1 TO 6: PLOT 40+(Z-1)*32,132: DRAW 0,-112: NEXT Z 250 RESTORE : FOR Z=1 TO 12: READ M$(Z): NEXT Z 252 PRINT TAB 8;M$(A);TAB 18;B 264 DIM C$(74): LET J=8: LET K=2: LET C$=C$( TO 2*D-2)+Z$ 270 FOR Z=1 TO C+D-1: PRINT AT J,K;C$(2*Z-1 TO 2*Z): LET K=K+4 280 IF (Z/7=INT (Z/7)) THEN LET J=J+2: LET K=2 290 NEXT Z: STOP : STOP 300 DATA "JANUARY","FEBRUARY","MARCH","APRIL","MAY","JUNE","JULY","AUGUST","SEPTEMBER","OCTOBER","NOVEMBER","DECEMBER" 999 SAVE "cal" LINE 8 ```