Calendar

Date: 198x
Type: Program
Platform(s): TS 1000

This program generates a perpetual calendar for any date between 1801 and 2399, displaying the day of the week and a formatted monthly calendar page with the target date highlighted using inverse video characters. A comma-delimited string is used as a compact data store for day names, month names, and month lengths, parsed at startup by a reusable GOSUB 1000 tokenizer routine. The day-of-week calculation uses a Zeller-style formula adjusted for century boundaries, with special cases for years 1900–1999, year 2000, and other centuries. Leap year detection at line 480 correctly handles the Gregorian calendar’s century exception, though the year-2000 special case is hardcoded separately rather than derived from the general rule.


Program Structure

The program divides cleanly into five phases:

  1. Initialisation (lines 10–240): Arrays D$(7,10) (day names), M$(12,9) (month names), and L(12) (month lengths) are populated by repeatedly calling the string-tokenizer at line 1000, which walks the comma-delimited master string I$.
  2. Input (lines 250–450): The user types a date in MM,DD,YYYY format; the same tokenizer routine re-uses I$ (now overwritten with the user’s input) to extract month, day, and year.
  3. Validation (lines 460–490): Checks month range, day range against L(M), and year range 1801–2399. Leap year is computed and stored in L(2) at line 480.
  4. Day-of-week calculation (lines 500–542): A Zeller-style formula computes FOM (first-of-month offset) and DOW.
  5. Display (lines 550–830): Prints the month/year header, abbreviated day-of-week row, and the calendar grid. The queried date is highlighted in inverse video before the next-date prompt is shown.

String Tokenizer (GOSUB 1000)

Lines 1000–1060 implement a simple comma-delimiter parser. Two pointers P1 and P2 are maintained globally. The routine scans forward from P2 until a comma is found, extracts I$(P1 TO P2-1) into R$, then advances both pointers past the comma ready for the next call. This single subroutine serves double duty: first during initialisation (parsing the built-in data string) and again during input parsing (applied to the user-supplied date string).

Array Length Encoding

Because ZX81 string arrays have fixed-width rows, variable-length strings are padded with spaces. The actual string length is stored as a single character in the last cell of each row: D$(I,10)=CHR$(LEN R$) and M$(I,9)=CHR$(LEN R$). To retrieve the string, the program reads back the length with CODE D$(DOW,10) or CODE M$(M,9) and uses a TO slice, e.g. D$(DOW,1 TO CODE D$(DOW,10)). This avoids trailing-space problems without needing a separate length array.

Day-of-Week Formula

Lines 500–542 use a modified Zeller congruence. January and February are treated as months 13 and 14 of the previous year (lines 500–510). The core formula at line 520 is:

FOM = INT(Y*1.25) + (Y<1900) + (Y>2000)*INT((Y-2000)/100) + INT((M-2)*2.59)

The term (Y<1900) adds 1 for 19th-century years and (Y>2000)*INT((Y-2000)/100) adjusts for each completed century beyond 2000, compensating for century non-leap years. DOW is then computed modulo 7. After that, FOM is separately reduced modulo 7 and incremented by 1 to give the 1-based column for the first day of the month.

Leap Year Detection

Line 480 computes February’s length:

L(2) = 28 + ((Y=INT(Y/4)*4 AND Y<>INT(Y/100)*100) OR Y=2000)

This correctly identifies leap years divisible by 4 but not 100, with year 2000 hardcoded as an exception (it is divisible by 400). However, other years divisible by 400 within the supported range (2400 is excluded; 1600 is below the lower bound) are not handled by the general rule—for this program’s stated range of 1801–2399 the logic is correct.

Inverse-Video Date Highlighting

Lines 680–720 convert the day number string for the selected date to inverse video by adding 128 to each character code:

LET P$(J) = CHR$(CODE P$(J) + 128)

This exploits the ZX81’s character set, where codes 128–255 are the inverse-video equivalents of codes 0–127, providing highlighting without any additional PRINT control codes.

Calendar Grid Layout

The grid uses PRINT AT VP, HP+(I<10) (line 730) to right-align single-digit dates within their four-character column. HP starts at (FOM-1)*4+2 and advances by 4 each day; when it reaches or exceeds 30, both VP is incremented by 2 and HP resets to 2 (lines 750–760).

Notable Idioms and Anomalies

  • FAST/SLOW are used strategically: array initialisation and calculation run in FAST mode; input and display run in SLOW mode.
  • Line 785 prints a row of block graphics (zmakebas \'' sequences) as a visual separator before the quit prompt.
  • Line 2000 (SAVE "CALENDA%R") and line 3000 (GOTO 10) are utility lines, not part of the normal execution path—the % in the filename is unusual and may be a typo for the intended name CALENDAR.
  • Line 1800 contains an unreachable STOP; it appears to be a leftover stub.
  • The re-use of I$ for both the initialisation data string and the user’s date input means that after line 340, the original day/month data is overwritten. This is intentional—the arrays have already been populated by then—but it is a subtle side-effect that could confuse maintenance.
  • The tokenizer advances P2 by 2 and sets P1=P2-1 (lines 1040–1050), which correctly positions P1 one character ahead of the just-consumed comma, ready for the next token.

Variable Summary

VariablePurpose
I$Master data string (init), then user date input
P1, P2Tokenizer start/end pointers into I$
R$Token returned by GOSUB 1000
D$(7,10)Day names; column 10 stores name length as CHR$
M$(12,9)Month names; column 9 stores name length as CHR$
L(12)Days in each month (L(2) updated for leap year)
M, D, YInput month, day, year
FOMFirst-of-month day offset (1–7)
DOWDay of week for the input date (1–7)
VP, HPVertical/horizontal print position for calendar grid
P$String form of day number, optionally inverse-video

Content

Appears On

One individual’s cassette containing a number of programs.

Related Products

Related Articles

Related Content

Image Gallery

Calendar

Source Code

   1 REM "CALENDAR"
  10 FAST 
  20 DIM D$(7,10)
  30 DIM M$(12,9)
  40 DIM L(12)
  49 REM --INITIALIZE VARIABLES      AND ARRAYS--
  50 REM 
  51 LET P1=1
  60 LET P2=7
  70 LET I$="SUNDAY,MONDAY,TUESDAY,WEDNESDAY,"
  80 LET I$=I$+"THURSDAY,FRIDAY,SATURDAY,"
  90 LET I$=I$+"JANUARY,31,FEBRUARY,28,MARCH,31,"
 100 LET I$=I$+"APRIL,30,MAY,31,JUNE,30,JULY,31,"
 110 LET I$=I$+"AUGUST,31,SEPTEMBER,30,OCTOBER,31,"
 120 LET I$=I$+"NOVEMBER,30,DECEMBER,31,"
 130 FOR I=1 TO 7
 140 GOSUB 1000
 150 LET D$(I)=R$
 160 LET D$(I,10)=CHR$ (LEN R$)
 170 NEXT I
 180 FOR I=1 TO 12
 190 GOSUB 1000
 200 LET M$(I)=R$
 210 LET M$(I,9)=CHR$ (LEN R$)
 220 GOSUB 1000
 230 LET L(I)=VAL (R$)
 240 NEXT I
 249 REM --ASK FOR,ACCEPT,AND                 CHECK INPUT--
 250 SLOW 
 260 CLS 
 270 PRINT AT 0,7;"PERPETUAL CALENDAR"
 280 PRINT AT 1,0;"TYPE IN DATE IN ANY YEAR"
 290 PRINT "AFTER 1800 AND BEFORE 2400;"
 300 PRINT "THEN PRESS <ENTER>."
 310 PRINT AT 5,0;"USE THIS FORMAT:"
 320 PRINT AT 7,0;"12,22,1984"
 330 PRINT AT 9,0;"DATE? ";
 340 INPUT I$
 350 PRINT I$
 360 LET I$=I$+","
 370 LET P1=1
 380 LET P2=2
 390 GOSUB 1000
 400 LET M=VAL R$
 410 IF M<1 OR M>12 THEN GOTO        260
 420 GOSUB 1000
 430 LET D=VAL R$
 440 GOSUB 1000
 450 LET Y=VAL R$
 460 FAST 
 470 CLS 
 480 LET L(2)=28+((Y=INT (Y/4)*4 AND Y<>INT (Y/100)*100) OR Y=2000)
 490 IF D<1 OR D>L(M) OR Y<1801 OR Y>2399 THEN GOTO 250
 495 REM 
 496 REM ***********************
 498 REM --COMPUTE WHAT DAY THE            DATE FALLS ON (DOW)-
 499 REM ***********************
 500 IF M<3 THEN LET Y=Y-1
 510 IF M<3 THEN LET M=M+12
 520 LET FOM=INT (Y*1.25)+(Y<1900)+(Y>2000)*INT ((Y-2000)/100)+INT ((M-2)*2.59)
 530 LET DOW=FOM+D-INT ((FOM+D-1)/7)*7
 537 REM 
 538 REM ***********************
 539 REM --FOM IS DAY THAT FIRST           OF MONTH M FALLS ON--
 540 REM ***********************
 541 REM 
 542 LET FOM=FOM-INT (FOM/7)*7+1
 550 IF M>12 THEN LET Y=Y+1
 560 IF M>12 THEN LET M=M-12
 565 REM 
 566 REM ***********************
 567 REM --PRINT DAY OF WEEK AND         TOP OF CALENDAR PAGE--
 568 REM ***********************
 569 REM 
 570 PRINT M$(M,1 TO CODE M$(M,9));" ";D;", ";Y;", IS A"
 580 PRINT D$(DOW,1 TO CODE D$(DOW,10));"."
 590 PRINT AT 4,(25-CODE M$(M,9))/2;M$(M,1 TO CODE M$(M,9));" ";Y
 600 PRINT AT 6,2;
 610 FOR I=1 TO 7
 620 PRINT D$(I,1 TO 3);" ";
 630 NEXT I
 640 PRINT 
 650 LET VP=8
 660 LET HP=(FOM-1)*4+2
 670 FOR I=1 TO L(M)
 680 LET P$=STR$ (I)
 690 IF I<>D THEN GOTO 730
 700 FOR J=1 TO LEN P$
 710 LET P$(J)=CHR$ (CODE P$(J)+128)
 720 NEXT J
 730 PRINT AT VP,HP+(I<10);P$
 740 LET HP=HP+4
 750 IF HP>=30 THEN LET VP=VP+2
 760 IF HP>=30 THEN LET HP=2
 770 NEXT I
 780 SLOW 
 781 REM 
 782 REM **********************
 783 REM -- ANOTHER DATE OR STOP
 784 REM **********************
 785 PRINT AT 19,0;"\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''\''"
 790 PRINT AT 20,3;"PRESS <Q> TO QUIT OR ANY";AT 21,0;"OTHER KEY TO TRY ANOTHER DATE."
 800 LET K$=INKEY$
 810 IF K$="" THEN GOTO 800
 820 IF K$<>"Q" THEN GOTO 260
 830 STOP 
\n1000 IF I$(P2)="," THEN GOTO 1030
\n1010 LET P2=P2+1
\n1020 GOTO 1000
\n1030 LET R$=I$(P1 TO P2-1)
\n1040 LET P2=P2+2
\n1050 LET P1=P2-1
\n1060 RETURN 
\n1800 STOP 
\n2000 SAVE "CALENDA%R"
\n3000 GOTO 10

Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.

People

No people associated with this content.

Scroll to Top