title 'MP/M-80 V2.0 Time of Day Conversion Procedure' name 'todcnv' cseg ; todcnv: ; do; ; /***************************************************** ; Time & Date ASCII Conversion Code ; *****************************************************/ dseg ; declare lit literally 'literally', ; forever lit 'while 1', ; word lit 'address', ; true lit '0ffh', ; false lit '0'; ; declare tod$adr address; tod$adr: ds 2 ; declare tod based tod$adr structure ( ; opcode byte, ; date address, ; hrs byte, ; min byte, ; sec byte, ; ASCII (21) byte ); ; declare string$adr address; string$adr: ds 2 ; declare string based string$adr (1) byte; ; declare index byte; index: ds 1 ; declare chr byte; chr: ds 1 ; declare ; base$year lit '78', /* base year for computations */ ; base$day lit '0', /* starting day for base$year 0..6 */ ; month$size (*) byte data ; /* jan feb mar apr may jun jul aug sep oct nov dec */ ; ( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31), cseg month$size: db 31,28,31,30,31,30,31,31,30,31,30,31 ; month$days (*) word data ; /* jan feb mar apr may jun jul aug sep oct nov dec */ ; ( 000,031,059,090,120,151,181,212,243,273,304,334); month$days: dw 000,031,059,090,120,151,181,212,243,273,304,334 dseg ; declare word$value word; word$value: ds 2 ; declare (month, day, year, hrs, min, sec) byte; month: ds 1 day: ds 1 year: ds 1 hrs: ds 1 min: ds 1 sec: ds 1 ; declare ; week$day byte, /* day of week 0 ... 6 */ weekday: ds 1 ; day$list (*) byte data ; ('Sun$Mon$Tue$Wed$Thu$Fri$Sat$'), cseg day$list: db 'Sun$Mon$Tue$Wed$Thu$Fri$Sat$' dseg ; leap$bias byte; /* bias for feb 29 */ leapbias: ds 1 cseg ; error$exit: procedure; ERROREXIT: ; declare rtn byte; ; /* pop off two returns */ ; stack$ptr = stack$ptr + 6; ; /* set A reg = 0 */ LXI D,6H LXI H,0 ; 0 DAD SP DAD D SPHL ; rtn = false; xra a ; end error$exit; RET ; emitchar: procedure(c$1); EMITCHAR: ; declare c$1 byte; ; string(index := index + 1) = c$1; lxi h,index inr m mov e,m mvi d,0 LHLD STRINGADR dad d mov m,c ; end emitchar; RET ; emitn: procedure(a$1); EMITN: ; declare a$1 address; ; declare c based a$1 byte; ; do while c <> '$'; @25: ;BC = .source ldax b CPI 24H rz ; string(index := index + 1) = c; lxi h,index inr m mov e,m mvi d,0 lhld stringadr dad d mov m,a inx h ; a$1 = a$1 + 1; inx b ; end; JMP @25 ; end emitn; ; RET ; emit$bcd$pair: procedure(b$1); EMITBCDPAIR: ; declare b$1 byte; ; call emit$char('0'+shr(b$1,4)); push b mov a,c ani 0f0h RAR RAR RAR RAR ADI '0' MOV C,A CALL EMITCHAR ; call emit$char('0'+(b$1 and 0fh)); pop b mov a,c ANI 0FH ADI '0' MOV C,A jmp EMITCHAR ; end emit$bcd$pair; ; RET ; emit$colon: procedure(b$2); EMITCOLON: ; declare b$2 byte; ; call emit$bcd$pair(b$2); CALL EMITBCDPAIR ; call emitchar(':'); MVI C,3AH jmp EMITCHAR ; end emit$colon; ; RET ; emit$bin$pair: procedure(b$3); EMITBINPAIR: ; declare b$3 byte; ; call emit$char('0'+b$3/10); mov e,c mvi d,0 LXI H,0AH CALL @P0029 push h mvi a,'0' add e mov c,a CALL EMITCHAR ; call emit$char('0'+b$3 mod 10); pop b mov a,c adi '0' mov c,a jmp EMITCHAR ; end emit$bin$pair; ; RET ; emit$slant: procedure(b$4); EMITSLANT: ; declare b$4 byte; ; call emit$bin$pair(b$4); CALL EMITBINPAIR ; call emitchar('/'); MVI C,2FH jmp EMITCHAR ; end emit$slant; ; RET ; gnc: procedure; GNC: ; /* get next command byte */ ; if chr = 0 then return; LDA CHR ora a rz ; if index = 20 then LDA INDEX sui 14H jz @2 ; do; ; chr = 0; ; return; ; end; ; chr = string(index := index + 1); lxi h,index inr m mov e,m mvi d,0 LHLD STRINGADR DAD d MOV A,M @2: STA CHR ; end gnc; RET ; deblank: procedure; DEBLANK: ; do while chr = ' '; @27: LDA CHR CPI 20H rnz ; call gnc; CALL GNC ; end; JMP @27 ; end deblank; ; RET ; numeric: procedure byte; NUMERIC: ; /* test for numeric */ ; return (chr - '0') < 10; LDA CHR SUI 30H SUI 0AH SBB A RET ; end numeric; ; scan$numeric: procedure(lb$1,ub$1) byte; SCANNUMERIC: LXI H,UB1 MOV M,E DCX H MOV M,C ; declare (lb$1,ub$1) byte; dseg lb1: ds 1 ub1: ds 1 ; declare b$5 byte; b$5: ds 1 cseg ; b$5 = 0; LXI H,B5 MVI M,0H ; call deblank; CALL DEBLANK ; if not numeric then call error$exit; CALL NUMERIC RAR cnc ERROREXIT ; do while numeric; @29: CALL NUMERIC RAR JNC @30 ; if (b$5 and 1110$0000b) <> 0 then call error$exit; LDA B5 ANI 0E0H CPI 0H cnz ERROREXIT ; b$5 = shl(b$5,3) + shl(b$5,1); /* b$5 = b$5 * 10 */ LDA B5 ADD A mov c,a ADD A ADD A ADD C STA B5 ; if carry then call error$exit; SBB A RAR cc ERROREXIT ; b$5 = b$5 + (chr - '0'); LDA CHR SUI 30H LXI H,B5 ADD M MOV M,A ; if carry then call error$exit; SBB A RAR cc ERROREXIT ; call gnc; CALL GNC ; end; JMP @29 @30: ; if (b$5 < lb$1) or (b$5 > ub$1) then call error$exit; LXI H,LB1 LDA B5 SUB M SBB A INX H PUSH PSW ; 1 MOV A,M LXI H,B5 SUB M SBB A POP B ; 1 MOV C,B ORA C RAR cc ERROREXIT ; return b$5; LDA B5 RET ; end scan$numeric; ; scan$delimiter: procedure(d$1,lb$2,ub$2) byte; SCANDELIMITER: LXI H,UB2 MOV M,E DCX H MOV M,C DCX H POP D POP B MOV M,C PUSH D ; declare (d$1,lb$2,ub$2) byte; dseg d$1: ds 1 lb2: ds 1 ub2: ds 1 cseg ; call deblank; CALL DEBLANK ; if chr <> d$1 then call error$exit; LXI H,D1 LDA CHR CMP M JZ @8 CALL ERROREXIT @8: ; call gnc; CALL GNC ; return scan$numeric(lb$2,ub$2); LHLD LB2 MOV C,L LHLD UB2 XCHG jmp SCANNUMERIC ; RET ; end scan$delimiter; ; leap$days: procedure(y,m$1) byte; LEAPDAYS: LXI H,M1 MOV M,E DCX H MOV M,C ; declare (y,m$1) byte; dseg y: ds 1 m1: ds 1 cseg ; /* compute days accumulated by leap years */ ; declare yp byte; dseg yp: ds 1 cseg ; yp = shr(y,2); /* yp = y/4 */ LDA Y ANI 254 RAR RAR STA YP ; if (y and 11b) = 0 and month$days(m$1) < 59 then LDA Y ANI 3H SUI 0H SUI 1 SBB A LHLD M1 MVI H,0 LXI B,MONTHDAYS DAD H DAD B XCHG PUSH PSW ; 1 MVI A,3BH CALL @P0101 SBB A POP B ; 1 MOV C,B ANA C RAR JNC @9 ; /* y not 00, y mod 4 = 0, before march, so not leap yr */ ; return yp - 1; LDA YP DCR A RET @9: ; /* otherwise, yp is the number of accumulated leap days */ ; return yp; LDA YP RET ; end leap$days; ; bcd: BCD: LXI H,VAL MOV M,C ; procedure (val) byte; ; declare val byte; dseg val: ds 1 cseg ; return shl((val/10),4) + val mod 10; LHLD VAL MVI H,0 XCHG LXI H,0AH CALL @P0029 XCHG DAD H DAD H DAD H DAD H PUSH H ; 1 LHLD VAL MVI H,0 XCHG CALL @P0030 POP B ; 1 DAD B MOV A,L RET ; end bcd; ; set$date$time: procedure; SETDATETIME: ; declare ; (i, leap$flag) byte; /* temporaries */ dseg i: ds 1 leap$flag: ds 1 cseg ; month = scan$numeric(1,12) - 1; ; /* may be feb 29 */ MVI E,0CH MVI C,1H CALL SCANNUMERIC DCR A STA MONTH ; if (leap$flag := month = 1) then i = 29; LDA MONTH SUI 1H SUI 1 SBB A STA LEAPFLAG RAR JNC @10 LXI H,I MVI M,1DH JMP @11 @10: ; else i = month$size(month); LHLD MONTH MVI H,0 LXI B,MONTHSIZE DAD B MOV A,M STA I @11: ; day = scan$delimiter('/',1,i); MVI C,2FH PUSH B ; 1 LHLD I XCHG MVI C,1H CALL SCANDELIMITER STA DAY ; year = scan$delimiter('/',base$year,99); ; /* ensure that feb 29 is in a leap year */ MVI C,2FH PUSH B ; 1 MVI E,63H MVI C,4EH CALL SCANDELIMITER STA YEAR ; if leap$flag and day = 29 and (year and 11b) <> 0 then LDA DAY SUI 1DH SUI 1 SBB A LXI H,LEAPFLAG ANA M PUSH PSW ; 1 LDA YEAR ANI 3H SUI 0H ADI 255 SBB A POP B ; 1 MOV C,B ANA C RAR JNC @12 ; /* feb 29 of non-leap year */ call error$exit; CALL ERROREXIT @12: ; /* compute total days */ ; tod.date = month$days(month) ; + 365 * (year - base$year) ; + day ; - leap$days(base$year,0) ; + leap$days(year,month); LHLD MONTH MVI H,0 LXI B,MONTHDAYS DAD H DAD B LDA YEAR SUI 4EH MOV E,A MVI D,0 PUSH H ; 1 LXI H,16DH CALL @P0034 POP D ; 1 CALL @P0017 PUSH H ; 1 LHLD DAY MVI H,0 POP B ; 1 DAD B PUSH H ; 1 MVI E,0H MVI C,4EH CALL LEAPDAYS POP D ; 1 CALL @P0096 PUSH H ; 1 LHLD YEAR MOV C,L LHLD MONTH XCHG CALL LEAPDAYS MOV E,A MVI D,0 POP H ; 1 DAD D PUSH H ; 1 LHLD TODADR INX H POP B ; 1 MOV M,C INX H MOV M,B ; tod.hrs = bcd (scan$numeric(0,23)); MVI E,17H MVI C,0H CALL SCANNUMERIC MOV C,A CALL BCD LXI B,3H LHLD TODADR DAD B MOV M,A ; tod.min = bcd (scan$delimiter(':',0,59)); MVI C,3AH PUSH B ; 1 MVI E,3BH MVI C,0H CALL SCANDELIMITER MOV C,A CALL BCD LXI B,4H LHLD TODADR DAD B MOV M,A ; if tod.opcode = 2 then LHLD TODADR MOV A,M CPI 2H JNZ @13 ; /* date, hours and minutes only */ ; do; ; if chr = ':' LDA CHR CPI 3AH JNZ @14 ; then i = scan$delimiter (':',0,59); MVI C,3AH PUSH B ; 1 MVI E,3BH MVI C,0H CALL SCANDELIMITER STA I @14: ; tod.sec = 0; LXI B,5H LHLD TODADR DAD B MVI M,0H ; end; ret @13: ; /* include seconds */ ; else tod.sec = bcd (scan$delimiter(':',0,59)); MVI C,3AH PUSH B ; 1 MVI E,3BH MVI C,0H CALL SCANDELIMITER MOV C,A CALL BCD LXI B,5H LHLD TODADR DAD B MOV M,A ; end set$date$time; RET ; compute$year: procedure; COMPUTEYEAR: ; /* compute year from number of days in word$value */ ; declare year$length word; dseg year$length: ds 2 cseg ; year = base$year; LXI H,YEAR MVI M,4EH ; do forever; @31: ; year$length = 365; LXI H,16DH SHLD YEARLENGTH ; if (year and 11b) = 0 then /* leap year */ LDA YEAR ANI 3H JNZ @16 ; year$length = 366; LXI H,16EH SHLD YEARLENGTH @16: ; if word$value <= year$length then LXI D,YEARLENGTH LXI B,WORDVALUE CALL @P0098 rnc ; return; ; word$value = word$value - year$length; LXI B,YEARLENGTH LXI D,WORDVALUE CALL @P0098 XCHG DCX H MOV M,E INX H MOV M,D ; year = year + 1; LXI H,YEAR INR M ; end; JMP @31 ; end compute$year; ; RET ; compute$month: procedure; COMPUTEMONTH: ; month = 12; LXI H,MONTH MVI M,0CH ; do while month > 0; @33: LXI H,MONTH mov a,m ora a rz ; if (month := month - 1) < 2 then /* jan or feb */ DCR A mov m,a CPI 2H JNC @18 ; leapbias = 0; LXI H,LEAPBIAS MVI M,0H @18: ; if month$days(month) + leap$bias < word$value then return; LHLD MONTH MVI H,0 LXI B,MONTHDAYS DAD H DAD B LDA LEAPBIAS CALL @P0015 XCHG LXI H,WORDVALUE CALL @P0104 rc ; end; JMP @33 ; end compute$month; ; RET ; get$date$time: procedure; GETDATETIME: ; /* get date and time */ ; hrs = tod.hrs; LXI B,3H LHLD TODADR DAD B MOV A,M STA HRS ; min = tod.min; LHLD TODADR INX B DAD B MOV A,M STA MIN ; sec = tod.sec; LHLD TODADR INX B DAD B MOV A,M STA SEC ; word$value = tod.date; ; /* word$value contains total number of days */ LHLD TODADR INX H MOV E,M INX H MOV D,M XCHG SHLD WORDVALUE ; week$day = (word$value + base$day - 1) mod 7; DCX H XCHG LXI H,7H CALL @P0029 XCHG LXI H,WEEKDAY MOV M,E ; call compute$year; ; /* year has been set, word$value is remainder */ CALL COMPUTEYEAR ; leap$bias = 0; LXI H,LEAPBIAS MVI M,0H ; if (year and 11b) = 0 and word$value > 59 then LDA YEAR ANI 3H SUI 0H SUI 1 SBB A PUSH PSW ; 1 MVI A,3BH LXI H,WORDVALUE CALL @P0103 SBB A POP B ; 1 MOV C,B ANA C RAR JNC @20 ; /* after feb 29 on leap year */ leap$bias = 1; LXI H,LEAPBIAS MVI M,1H @20: ; call compute$month; CALL COMPUTEMONTH ; day = word$value - (month$days(month) + leap$bias); LHLD MONTH MVI H,0 LXI B,MONTHDAYS DAD H DAD B LDA LEAPBIAS CALL @P0015 LXI D,WORDVALUE CALL @P0102 XCHG LXI H,DAY MOV M,E ; month = month + 1; DCX H INR M ; end get$date$time; RET ; emit$date$time: procedure; EMITDATETIME: ; call emitn(.day$list(shl(week$day,2))); LDA WEEKDAY ADD A ADD A MOV C,A MVI B,0 LXI H,DAYLIST DAD B MOV B,H MOV C,L CALL EMITN ; call emitchar(' '); MVI C,20H CALL EMITCHAR ; call emit$slant(month); LHLD MONTH MOV C,L CALL EMITSLANT ; call emit$slant(day); LHLD DAY MOV C,L CALL EMITSLANT ; call emit$bin$pair(year); LHLD YEAR MOV C,L CALL EMITBINPAIR ; call emitchar(' '); MVI C,20H CALL EMITCHAR ; call emit$colon(hrs); LHLD HRS MOV C,L CALL EMITCOLON ; call emit$colon(min); LHLD MIN MOV C,L CALL EMITCOLON ; call emit$bcd$pair(sec); LHLD SEC MOV C,L jmp EMITBCDPAIR ; end emit$date$time; ; RET ; tod$cnv: TODCNV: public todcnv mov h,b mov l,c ; procedure (parameter) byte; ; declare parameter address; ; tod$adr = parameter; SHLD TODADR ; string$adr = .tod.ASCII; xchg LXI h,6H DAD d SHLD STRINGADR ; if tod.opcode = 0 then ldax d ora a JNZ @21 ; do; ; call get$date$time; CALL GETDATETIME ; index = -1; LXI H,INDEX MVI M,0FFH ; call emit$date$time; CALL EMITDATETIME ; end; JMP @22 @21: ; else ; do; ; if (tod.opcode = 1) or LHLD TODADR MOV A,M SUI 1H SUI 1 SBB A PUSH PSW ; 1 MOV A,M SUI 2H SUI 1 SBB A POP B ; 1 MOV C,B ORA C RAR JNC @23 ; (tod.opcode = 2) then ; do; ; chr = string(index:=0); LXI H,INDEX MVI M,0H LHLD STRINGADR MOV A,M STA CHR ; call set$date$time; CALL SETDATETIME ; end; JMP @24 @23: ; else ; do; ; return false; xra a RET ; end; @24: ; end; @22: ; return true; MVI A,0FFH RET ; end tod$ASCII; ; end todcnv; @P0015: mov e,a mvi d,0 xchg @P0017: LDAX D ADD L MOV L,A INX D LDAX D ADC H MOV H,A RET @P0029: MOV B,H MOV C,L @P0030: LXI H,0 MVI A,16 PUSH PSW DAD H XCHG SUB A DAD H XCHG ADC L SUB C MOV L,A MOV A,H SBB B MOV H,A INX D JNC $+5H DAD B DCX D POP PSW DCR A JNZ $-14H RET @P0034: MOV B,H MOV C,L LXI H,0 MVI A,16 DAD H XCHG DAD H XCHG JNC $+4H DAD B DCR A JNZ $-9H RET @P0096: MOV C,A MVI B,0 MOV A,E SUB C MOV L,A MOV A,D SBB B MOV H,A RET @P0098: MOV L,C MOV H,B MOV C,M INX H MOV B,M LDAX D SUB C MOV L,A INX D LDAX D SBB B MOV H,A RET @P0101: MOV L,A MVI H,0 @P0102: LDAX D SUB L MOV L,A INX D LDAX D SBB H MOV H,A RET @P0103: MOV E,A MVI D,0 @P0104: MOV A,E SUB M MOV E,A MOV A,D INX H SBB M MOV D,A XCHG RET END