Moon Calendar computes and displays a month-at-a-glance lunar phase calendar for any year between 1950 and 2050. For each day of the selected month, it calculates an approximate moon phase using a 29.73-day synodic period and draws a small crescent or gibbous arc using the CIRCLE and DRAW-with-angle commands on a color-coded grid. The day-of-week calculation uses a modified Julian Day Number formula (lines 220–240), and the month-end day count is derived from a compact encoded string “202121221212” with a leap-year correction for February. The screen layout divides the display into a 7-row by 6-column grid with horizontal and vertical lines drawn via PLOT/DRAW, and the lower channel prompts the user to print via COPY, re-enter a date, or quit.
Program Structure
The program is organized into clearly labeled sections using REM comments with inverse-video highlighting. The main flow is:
- Lines 10–30: Initialization and title string setup.
- Lines 50–100: Screen setup, user input of year and month, validation, and header printing.
- Lines 110–160: Drawing the calendar grid using PLOT/DRAW loops.
- Lines 180–300: Computing month length, determining the first day of the week via a Julian Day formula, and printing day-of-week labels.
- Lines 320–510: Main loop over each day of the month — moon phase calculation and arc drawing.
- Lines 530–570: Lower-channel prompt and keypress handling (COPY, re-run, quit).
- Lines 600–9998: SAVE command and optional loader stubs in REMs.
Input Validation
Line 80 enforces that the year is within 1950–2050, the month number does not exceed 12, and that the month is an integer (INT M <> M catches fractional input). Any failure loops back to line 70 for re-entry.
Month-End and Leap Year Calculation
Line 180 defines the string C$="202121221212", where each character encodes the number of days beyond 29 for each month (e.g., ‘2’ for 31-day months, ‘0’ for February’s base of 29). Line 200 extracts the relevant digit with VAL C$(M) and subtracts 1 if the year is not divisible by 4 and the month is February, implementing a simplified leap-year rule. This compact encoding avoids a DATA/READ structure or a long IF chain.
Day-of-Week Formula
Lines 220–240 implement a Julian Day Number variant. Line 220 computes a Julian-like integer J using the standard astronomical formula with century and 400-year corrections. Line 240 reduces J modulo 7 to get the day-of-week index Q for the first of the month. Lines 250–300 then walk through the 7 grid rows, printing three-letter day abbreviations from D$="SATSUNMONTUEWEDTHUFRI". The wrapping from Saturday back through the week is handled by the conditional at line 270.
Moon Phase Calculation
The phase engine in lines 350–390 uses a synodic period of 29.73 days (MO) and a per-month offset table encoded as "02022446789A" at line 360, where VAL of each character gives a numeric day offset. The epoch is anchored to 1930. The phase value PH is the fractional remainder of days since a known new moon, expressed in days within the current lunation.
Line 360 uses VAL "02022446789A"(M) — note that VAL "A" returns 0 in Sinclair BASIC since ‘A’ is not a valid number prefix, effectively treating November (M=11) as offset 0. This is a latent anomaly that may affect November phase accuracy slightly.
Moon Phase Drawing
For each day, two arcs are drawn using DRAW 0,20,P and DRAW 0,-20,angle (lines 460–470), forming a lens or crescent shape. The first arc is always a semicircle (P = PI or -PI). The second arc’s angle is computed as 2.5 * ATN((PI/180) * C * 25), where C is the phase deviation from quarter-moon, giving a smooth transition from new (flat) to full (opposite semicircle). INK 7 (white) is used for the waxing half and INK 5 (cyan) for the waning half, providing a visual color cue for the phase direction.
Screen Layout
The calendar grid is drawn as 8 horizontal lines spaced 24 pixels apart (line 140) and 5 vertical lines spaced 48 pixels apart (line 150), creating a 7-row × 6-column grid. Coordinates XX and YY (initialized to 40 and 20) advance down each column and then right across the screen as days are iterated (line 490).
User Interaction and Output
After drawing completes, line 530 prints a prompt in the lower channel (stream #0). The PAUSE 0 / INKEY$ idiom at lines 540–570 waits efficiently for a keypress without busy-looping at full CPU speed. Pressing C triggers COPY for a hard-copy printout, D re-runs the program to enter a new date, and Q jumps to line 9998 (a REM, effectively ending execution).
Notable Techniques
- Compact month-length encoding in a string (
C$) accessed by index withVAL. - Per-month lunar offset table similarly encoded as a 12-character string read with
VAL. - Use of
DRAWwith an angle parameter to render curved arcs for moon phases. - Day-of-week labels packed into a single string
D$and sliced by 3-character index. - Boolean arithmetic:
(1 AND M=2 AND Y/4 <> INT(Y/4))for leap-year correction without an IF statement. - Month name printed by slicing a 36-character concatenated string at line 90.
Anomalies and Limitations
- The leap year rule at line 200 only checks divisibility by 4, omitting the century correction (years divisible by 100 but not 400 are not leap years). This means year 2000 is handled correctly but 1900 (outside the valid range) and 2100 would not be.
- As noted,
VAL "A"at line 360 for M=11 evaluates to 0, which may introduce a small systematic error in November moon phases. - Line 565 jumps to 9998 which is a REM line, silently ending the program — this is intentional as a quit mechanism.
Source Code
10 \{20}\{1} REM \{20}\{0}
20 LET N$="MOON CALENDAR"
30 REM Maurice Gavin FRAS \* copyright 1984
40 \{20}\{1} REM INPUT PRINT DATE
50 \{20}\{0} BORDER 0:PAPER 0:CLS
60 BRIGHT 1:INK 9:POKE 23658,8
70 INPUT "year ";Y,"MTH NO ";M
80 IF Y<1950 OR Y>2050 OR M>12 OR INT M <>M THEN GO TO 70
90 PRINT "JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC"(M*3-2 TO M*3);
100 PRINT " ";Y,N$
110 CIRCLE 243,172,3
120 CIRCLE 247,172,3
130 REM \{20}\{1}CALENDAR GRID \{20}\{0}
140 INK 2:FOR F=0 TO 7:PLOT 0,F*24-1:DRAW 255,0:NEXT F
150 FOR F=1 TO 5:PLOT F*48+15,0:DRAW 0,166:NEXT F
160 PLOT 0,0:DRAW 0,166:INK 9
170 REM \{20}\{1}CALC CALENDAR\{20}\{0}
180 LET C$="202121221212"
190 LET D$="SATSUNMONTUEWEDTHUFRI"
200 LET MOEND=29+ VAL C$(M)-(1 AND M=2 AND Y/4 <> INT (Y/4))
210 REM --------------------
220 LET J= INT (365.25*(Y-(M<3)))+ INT (30.6001*(M+1+12*(M<3)))- INT (Y/100)+ INT (Y/400)
230 REM \{20}\{1}PRINT DAY OF THE WEEK\{20}\{0}
240 LET Q=J-7* INT (J/7)+1
250 INK 6:FOR F=1 TO 7
260 LET QF=Q+F
270 IF QF>7 THEN LET QF=QF-7
280 PRINT AT F*3,0;
290 PRINT D$(3*QF-2 TO 3*QF)
300 NEXT F
310 REM \{20}\{1}MAIN LOOP\{20}\{0}\{20}\{1} \{20}\{0}
320 LET XX=40:LET YY=20
330 FOR D=1 TO MOEND
340 REM \{20}\{1}CALC MOON PHASE \{20}\{0}
350 LET A=10:LET MO=29.73
360 LET N= VAL "02022446789A"(M)
370 LET PH=(Y-1930)*11-150+N+D-1
380 LET PHMO=PH/MO
390 LET PH=(PHMO- INT PHMO)*MO
400 REM \{20}\{1}DRAW PHASE IN COLOUR \{20}\{0}
410 LET P= PI:INK 7
420 IF PH>MO/2 THEN LET P=-P:INK 5
430 PRINT AT YY/8-1,XX/8-3;(" " AND D<10);D
440 LET MO4=MO/4:LET C=PH-MO4
450 IF C>MO4 THEN LET C=C-MO/2
460 PLOT XX+5,165-YY:DRAW 0,20,P
470 DRAW 0,-20,2.5* ATN ((PI/180)*C*25)
480 REM \{20}\{1}SCREEN FORMAT \{20}\{0}
490 LET YY=YY+24:IF YY>170 THEN LET YY=20:LET XX=XX+48
500 REM \{20}\{1}END MAIN LOOP \{20}\{0}
510 NEXT D
520 REM \{20}\{1}\{20}\{0}---------------------
530 PRINT #0;"PRESS \{20}\{1}C\{20}\{0}OPY/\{20}\{1}D\{20}\{0}ATE"
540 BEEP .1,10:PAUSE 0
550 IF INKEY$="C" THEN COPY
560 IF INKEY$="D" THEN RUN
565 IF INKEY$="Q" THEN GO TO 9998
570 GO TO 540
580 REM \{20}\{1} \{20}\{0} ECLIPSEsoft;79 Ardrossan GardensWorcester Park, Surrey KT4 7AX,
590 REM \{20}\{1} \{20}\{0}
600 SAVE "MOONCAL" LINE 1
610 REM Iowa Sinclair BBS 1-515-282-4169 300bps 8/n/1 24h/day
9992 REM RANDOMIZE USR 100:SAVE "MOONCL.B1" LINE 1
9998 REM RANDOMIZE USR 100:LOAD "L.B1"
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
