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:
- Input and validation (lines 20–30): prompts for month and year, rejecting out-of-range values with looping
GO TOs. - Day-of-week calculation (lines 60–75): computes which weekday column the 1st of the month falls on.
- Days-in-month calculation (lines 80–100): sets
Cto the correct number of days, including Gregorian leap year logic. - 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)+…). RESTOREfollowed by aFOR/READ loop at line 250 loads month names into theDIM M$(12,9)array; the dimension of 9 accommodates “SEPTEMBER” (9 characters), the longest name.- Input validation uses
BEEP 1,-20on 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 withZ$; slicing withTO 2*D-2extracts exactly the needed blank prefix.- Line 290 uses two consecutive
STOPstatements; only the first is executed, the second is redundant. - The program uses
PAPER 6in the opening PRINT andBORDER 5for 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.
Content
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
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.

