--- title: "CAL" id: 56668 type: "computer_media" slug: "cal" url: "http://localhost/computer_media/cal/" markdown_url: "http://localhost/computer_media/cal.md" published_at: "2024-09-22T23:31:36+00:00" modified_at: "2026-04-03T07:59:03+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/09/Cal0.png" excerpt: "Enter any year and watch a full 12-month calendar appear in a compact 4×3 grid, calculated from scratch using a clever packed day-count string." 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 1000" slug: "ts1000" taxonomy: "post_tag" url: "http://localhost/tag/ts1000/" - name: "UAS" slug: "uas" taxonomy: "post_tag" url: "http://localhost/tag/uas/" model: - name: "Timex/Sinclair 1000" slug: "ts-1000" taxonomy: "model" url: "http://localhost/model/ts-1000/" genre: - name: "Home" slug: "home" taxonomy: "genre" url: "http://localhost/type/home/" media_type: "Cassette" download_url: "https://archive.org/download/timex-sinclair-software-archive/CAL%20%28198x%29%28UAS%29%28TS1000%29%28US%29%28Program%29.zip" mediadate: "198x" producer_company: - id: 11418 title: "UAS" type: "company" url: "http://localhost/company/uas/" images: - url: "http://localhost/wp-content/uploads/2024/09/Cal0.png" - url: "http://localhost/wp-content/uploads/2024/09/Cal1.png" - url: "http://localhost/wp-content/uploads/2024/09/Tape-5.jpg" media_type_tags: "Home" --- # CAL This program prints a complete 12-month calendar for any user-specified year on a single ZX81/TS1000 screen, laid out in a 4×3 grid of months. It calculates the day-of-week for January 1st of the requested year using a cumulative day-count formula anchored to 1900, then advances the starting day through each month using a packed string (“312831303130313130313031”) to store the days-per-month. Leap year detection adjusts February to 29 days and corrects the day-count accumulation. The introductory subroutine at line 1030 draws a decorative box using PLOT commands and displays a title screen with a 200-frame pause before clearing to the calendar. *** ## Program Analysis ### Program Structure The program is divided into three logical phases: 1. **Title screen** (lines 5, 1030–1210): A decorative PLOT-drawn border with a “CAL” title and UAS attribution, displayed for about 3 seconds before clearing. 2. **Setup and layout** (lines 6–200): Warns about the 6th-week limitation, prompts for a year, draws the static calendar grid (day headers, month labels, year), and stores days-per-month in a packed string. 3. **Date filling** (lines 200–600): Decodes days-per-month into array `A()`, calculates the starting day-of-week for January 1st, and iterates through all 12 months printing dates into the correct screen positions. ### Days-per-Month Encoding Rather than using a `DATA`/READ approach, the program stores month lengths as the string literal `"312831303130313130313031"` in `A$`. Lines 210–230 parse this two characters at a time using `VAL (A$(N))+VAL (A$(N+1))*10` to populate the numeric array `A(1..12)`. Note that the formula at line 220 reverses the expected digit order: it computes `10*VAL(A$(N)) + VAL(A$(N+1))`, so the leading digit is the tens place. The packed string encodes months as: 31,28,31,30,31,30,31,31,30,31,30,31. ### Day-of-Week Calculation The starting day-of-week for January 1st is derived in lines 240–265: - Line 240: `W = 366 + ((YEAR-1900)*365)` — a cumulative day count from a reference point. - Line 250: Adds one day per leap year elapsed since 1900: `INT((YEAR-1900)/4)`. - Line 255: If the target year itself is a leap year, subtracts 1 (January 1st precedes the leap day). - Lines 260–265: Reduces modulo 7; if the result is 0 it is set to 7, mapping Sunday=7 in the SM-TW-TF-S column layout. The guard at line 245 skips the leap-year correction for years ≤ 1904, avoiding an off-by-one around the 1900 non-leap-year boundary (1900 is divisible by 4 but not a leap year; this is a partial workaround). ### Screen Layout The 12 months are arranged in a 4-column, 3-row grid. Month labels are printed at fixed AT positions (rows 1, 8, 15) with 8-column spacing per column. The “SMTWTFS” header row is printed in inverse video. Each month occupies 8 character columns. Dates are filled starting at screen rows 3, 10, 17 (variable `L` steps by 7) and columns offset by `K*8`. ### Date-Filling Logic The subroutine at line 470 fills one month’s dates into the grid: - The first partial row (lines 470–520) prints day 1 starting at column `W1` (the day-of-week) through column 7. - Four subsequent rows (lines 530–570) fill the remaining days, wrapping columns 1–7. - Variable `C` tracks days elapsed in the month; when `C = A(M+1)+1` the month is exhausted and the subroutine exits early via `GOTO 580`. - A tens-digit suppression trick at line 546 resets `D` to 0 when it reaches 10, so `PRINT D` outputs only the units digit — effectively displaying 10 as “0”. This is a ZX81 width-saving technique but means the tens digit of dates 10–19+ is never printed; only the units digit appears for all dates. ### Carry-Forward Between Months After each month, the starting column for the next month is computed by accumulating total days elapsed (line 585: `W = W + A(M)`) and taking the fractional day-of-week from the running total (lines 591–593). The `INT(W1 + 0.5)` rounding at line 592 guards against floating-point remainder errors. ### Notable Bugs and Anomalies | Line(s) | Issue | | --- | --- | | 405–406 | `W1=W` is set at line 405, then immediately overwritten by `M=0` at line 406 (a separate statement). However, `W1` is correctly initialised here for January; `M` being reset to 0 is intentional so the first `M+1` reference inside the subroutine indexes `A(1)`. | | 546 | The tens-digit suppression (`IF D=10 THEN LET D=0`) causes dates 10–19, 20–29, 30–31 to display only their units digit (e.g., “10” prints as “0”). This is a deliberate space-saving measure but makes the calendar hard to read. | | 232 | Leap year detection uses only the divide-by-4 rule; century years (e.g., 1900, 2100) that are not leap years are not handled beyond the partial guard at line 245. | | 6 | The note about “6th week in month will not print” is correct — the grid only allocates 5 date rows per month (one partial + four full), so months starting late in the week can have dates beyond row 5 silently dropped. | ### Subroutine at Lines 1030–1210 This title-screen subroutine uses `PLOT` in two loops to draw a rectangular border: horizontal lines at Y=2 and Y=43 (lines 1030–1060), and vertical lines at X=0 and X=63 (lines 1070–1100). It then uses `PRINT AT` to place asterisk-bordered text and the program title inside the box. A `PAUSE 200` (approximately 3.3 seconds at 60 Hz) gives the user time to read the title before `CLS` clears to the calendar. ## Source Code ``` 5 GOSUB 1030 6 PRINT "%N%O%T%E% %6%T%H% %W%E%E%K% %I%N% % %M%O%N%T%H% %W%I%L%L% %N%O%T% %P%R%I%N%T% %D%U%E% %T%O% %S%P%A%C%E% %L%I%M%I%T%A%T%I%O%N%S% " 8 PRINT AT 5,0;"IT WILL TAKE ABOUT ONE MINUTE TO PRINT CALENDARS" 10 PRINT AT 15,5;"ENTER YEAR DESIRED XXXX" 11 INPUT YEAR 12 LET Z=YEAR-1899 13 DIM A(12) 15 DIM A$(24) 16 CLS 19 FOR L=2 TO 16 STEP 7 20 FOR K=0 TO 3 30 PRINT AT L,(K*8)+1;"%S%M%T%W%T%F%S" 40 NEXT K 50 NEXT L 55 PRINT AT 0,13;YEAR 60 PRINT AT 1,0;"*JAN" 70 PRINT AT 1,8;"*FEB" 80 PRINT AT 1,16;"*MAR" 90 PRINT AT 1,24;"*APR" 100 PRINT AT 8,0;"*MAY" 110 PRINT AT 8,8;"*JUN" 120 PRINT AT 8,16;"*JUL" 130 PRINT AT 8,24;"*AUG" 140 PRINT AT 15,0;"*SEP" 150 PRINT AT 15,8;"*OCT" 160 PRINT AT 15,16;"*NOV" 170 PRINT AT 15,24;"*DEC" 200 LET A$="312831303130313130313031" 205 LET N=1 210 FOR K=1 TO 12 220 LET A(K)=10*VAL (A$(N))+VAL (A$(N+1)) 225 LET N=N+2 230 NEXT K 232 IF (YEAR/4)-INT (YEAR/4)=0 THEN LET A(2)=29 240 LET W=366+((YEAR-1900)*365) 245 IF YEAR<=1904 THEN GOTO 260 250 LET W=W+INT ((YEAR-1900)/4) 255 IF (YEAR/4)-INT (YEAR/4)=0 THEN LET W=W-1 260 LET W=W-(INT (W/7)*7) 265 IF W=0 THEN LET W=7 335 LET M=1 405 LET W1=W 406 LET M=0 410 FOR L=3 TO 17 STEP 7 420 FOR K=0 TO 3 426 LET D=1 428 LET C=1 430 GOSUB 470 440 NEXT K 450 NEXT L 460 STOP 470 FOR R=W1 TO 7 500 PRINT AT L,(K*8)+R;D 510 LET D=D+1 515 LET C=C+1 520 NEXT R 530 FOR S=1 TO 4 540 FOR R=1 TO 7 545 IF C=A(M+1)+1 THEN GOTO 580 546 IF D=10 THEN LET D=0 550 PRINT AT L+S,(K*8)+R;D 555 LET D=D+1 556 LET C=C+1 560 NEXT R 570 NEXT S 580 LET M=1+M 585 LET W=W+A(M) 591 LET W1=((W/7)-INT (W/7))*7 592 LET W1=INT (W1+.5) 593 IF W1=0 THEN LET W1=7 599 LET C=1 600 RETURN 1030 FOR K=0 TO 63 1040 PLOT K,43 1050 PLOT K,2 1060 NEXT K 1070 FOR K=2 TO 43 1080 PLOT 0,K 1090 PLOT 63,K 1100 NEXT K 1110 PRINT AT 4,11;"***********" 1120 PRINT AT 8,11;"***********" 1130 FOR K=1 TO 3 1140 PRINT AT 4+K,11;"*" 1150 PRINT AT 4+K,21;"*" 1160 NEXT K 1170 PRINT AT 6,14;"C A L" 1180 PRINT AT 21,0;"UAS BOX612 HADDONFIELD,N.J.08033" 1190 PAUSE 200 1200 CLS 1210 RETURN 1220 SAVE "CA%L" 1230 GOTO 5 ```