This program displays a monthly calendar for any month and year from 1752 onward, the year Britain adopted the Gregorian calendar. The month is entered as a full name (matched by comparing the first three characters against a packed lookup string), and the year of adoption is enforced with a lower-bound check at line 110. Day-of-week calculation uses the Zeller-style formula at lines 300–320, adjusting for January and February falling in the previous year’s cycle. Display positioning is handled entirely with PRINT AT and column-counting via PEEK 16441 (the ZX81’s system variable holding the current print column), which is also used at line 230 to detect when a date would overflow the last row and STOP the output.
Program Analysis
Program Structure
The program is divided into three logical phases:
- Input and validation (lines 15–110): prompts for month name and year, validates both.
- Display setup (lines 120–190): clears screen, prints heading and day-name row, and determines the starting column for day 1.
- Date printing loop (lines 200–250): iterates from 1 to 31, printing each date with spacing, wrapping to new lines as needed.
The subroutine at lines 260–330 implements the day-of-week calculation and is called twice: once for the current month (to find the starting weekday) and once after incrementing M (line 170), though the second call’s result is only used for the starting-column print at line 160 — a subtle ordering quirk discussed below.
Month Name Lookup
Line 20 defines A$ as a single packed string containing all 12 three-character month abbreviations followed by seven three-character day abbreviations:
"CDJANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDECSUN MON TUE WED THU FRI SAT"
The first two characters CD are padding so that the 1-based month index M maps correctly: month M occupies characters A$(3*M TO 3*M+2). The user’s input is matched by comparing B$(TO 3) (first three characters) against each month slice in the FOR loop at lines 50–70. If no match is found, control returns to line 40 to re-prompt.
Day-of-Week Subroutine (Lines 260–330)
The subroutine implements a variant of Zeller’s congruence. January and February are treated as months 13 and 14 of the previous year, achieved by setting X=1 at line 270 when M=1 OR M=2. The adjusted year is stored in L and the adjusted month in X. The formula at line 310 is:
Z = INT(13*(X+1)/5) + INT(5*L/4) + INT(P/4) - P
where P = INT(L/100) is the century term. The result is then reduced modulo 7 at line 320. The return value in Z represents the day of week (0=Sunday through 6=Saturday, matching the day-name order in A$).
Display Mechanics and PEEK 16441
The program uses PEEK 16441 throughout to read the ZX81 system variable S_POSN_X (the current print column). This is used in two ways:
- At line 240:
IF PEEK 16441=5 THEN PRINT ,,,— when the column counter reaches 5 (i.e., after the last day of the week column), three commas advance to a new line, implementing the weekly row wrap. - At line 230:
IF I>27 AND 33-PEEK 16441=Z*4 THEN STOP— for dates beyond the 27th, this checks whether the column position matches the expected position for day 1 of the next month (i.e., the calendar has wrapped back to the starting column), halting output to avoid printing beyond the actual month length.
The starting column for day 1 is set by PRINT AT 8,Z*4; at line 160, positioning the cursor 4 characters per weekday column (each date occupies 4 characters as printed by lines 210–220).
Control Flow Anomaly: Subroutine Call Order
There is a subtle ordering issue in the display setup. The subroutine is first called at line 150 (GOSUB 260) to compute Z for the current month, but then M is incremented at line 170 before the result is used at line 160’s PRINT AT 8,Z*4. The second GOSUB 260 at line 180 then overwrites Z with the next month’s starting day — which is not actually used. In practice the PRINT AT at line 160 executes after the GOSUB at 150 and before the increment at 170, so Z correctly holds the current month’s starting weekday at that point. The second subroutine call at line 180 is therefore redundant and its result in Z is immediately corrected at line 190 (IF Z=0 THEN LET Z=7), which adjusts a Sunday result — but since the second call’s Z is never used for display, lines 180–190 appear to be vestigial or left from an earlier version.
Year Validation
Line 110 rejects years before 1752, the year Britain and its colonies adopted the Gregorian calendar, making the Zeller formula valid. This is a historically informed guard that prevents meaningless results for earlier dates.
Key Variables
| Variable | Purpose |
|---|---|
A$ | Packed month and day-name lookup string |
B$ | User-entered month name |
M | Month number (1–12) |
Y | Year (≥1752) |
Z | Day-of-week result from subroutine (0–6) |
L | Adjusted year for Zeller formula |
X | Adjusted month / century scratch variable |
P | Century term INT(L/100) |
Content
Source Code
10 SAVE "1007%9"
15 PRINT "**** CALENDAR ****"
20 LET A$="CDJANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDECSUN MON TUE WED THU FRI SAT"
30 PRINT "ENTER MONTH IE JANUARY"
40 INPUT B$
50 FOR M=1 TO 12
60 IF B$( TO 3)=A$(3*M TO 3*M+2) THEN GOTO 90
70 NEXT M
80 GOTO 40
90 PRINT "ENTER YEAR IE 1984"
100 INPUT Y
110 IF Y<1752 THEN GOTO 100
120 CLS
130 PRINT AT 3,8;B$;" ";Y
140 PRINT AT 6,0;A$(39 TO )
150 GOSUB 260
160 PRINT AT 8,Z*4;
170 LET M=M+1
180 GOSUB 260
190 IF Z=0 THEN LET Z=7
200 FOR I=1 TO 31
210 PRINT " ";I;" ";
220 IF I<10 THEN PRINT " ";
230 IF I>27 AND 33-PEEK 16441=Z*4 THEN STOP
240 IF PEEK 16441=5 THEN PRINT ,,,
250 NEXT I
260 LET X=0
270 IF M=1 OR M=2 THEN LET X=1
280 LET L=Y-X
290 LET X=M+X*12
300 LET P=INT (L/100)
310 LET Z=INT (13*(X+1)/5)+INT (5*L/4)+INT (P/4)-P
320 LET Z=Z-7*INT (Z/7)
330 RETURN
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
