	PAGE	66,132
;****************************************************************************
;
; PROGRAM: CLOCK
;
; DESCRIPTION: PRINT DATE AND TIME ON SCREEN
;
; INPUT: NONE
;
; OUTPUT: CURRENT DATE AND TIME ON SCREEN
;
; TYPE OF FILE: .COM FILE
;
; COMMENTS: THIS PROGRAM WILL PICK UP THE CURRENT DATE AND TIME AND
;     DISPLAY DAY, DATE AND TIME ON THE SCREEN.  DATE IS FORMATTED OUT
;     TO MONTH NAME, DAY AND YEAR, TIME IS HH:MM:SS.  THIS PROGRAM WILL
;     LOOP UNTIL CONTROL BREAK IS INTERCEPTED.
;
;  TO INCREASE READABILITY OF THE DISPLAY, IT WILL SAVE THE CURRENT
;     CURSOR TYPE, AND ERASE THE CURSOR.  WHEN CONTROL-BREAK IS DETECTED,
;     THE PROGRAM WILL INTERCEPT THE INTERRUPT AT ROUTINE TERMIN, RESTORE
;     THE CURSOR AND TERMINATE.
;
;  THIS PROGRAM ALSO CHECKS FOR DOS 2.0 OR ABOVE, AND IF IT IS NOT
;     PRESENT, TERMINATE IMMEDIATELY
;
;  THIS PROGRAM USES THE EQUATIONS FROM WES MEIER'S DFACT.BAS TO COMPUTE
;     THE DAY OF THE WEEK.  THEY ARE:
;
;	F = 365 * YEAR + DAY + (31 * (MONTH - 1))
;
;	IF MONTH < 3 THEN F = F + INT ((YEAR - 1) / 4) -
;	   INT ((INT ((YEAR / 100) + 1)) * 3 / 4)
;
;	IF MONTH >= 3 THEN F = F + INT ((MONTH + 23) * 4 / 10) +
;	   INT (YEAR / 4) - INT ((INT (YEAR / 100) + 1) * 3 / 4)
;
;	DAY NUMBER = F MOD 7 (REMAINDER OF F / 7) AND IS FIGURED AS
;
;	WHERE 0 = SATURDAY, 1 = SUNDAY, ETC.
;
;	NOTE: THE DAY NUMBER IS FIGURED AS (F MOD 7000) MOD 7 TO
;	   PREVENT AN OVERFLOW CAUSED BY DIVIDING A LARGE NUMBER
;	   BY A SMALL ONE.
;
;  THIS PROGRAM IS MADE TO RUN AS A .COM FILE, AND MUST BE RUN THROUGH
;     EXE2BIN.EXE IN ORDER TO OPERATE CORRECTLY
;
;****************************************************************************;
	IF1					;DURING PASS 1
	INCLUDE ASSEMBLE.MAC			;INCLUDE MACRO LIBRARY
	ENDIF
;
COLUMNS EQU	80				;NUMBER OF COLUMNS ON SCREEN
;
BDATES	STRUC					;STRUCTURE FOR BINARY DATE
MONTH	DB	0				;BYTE MONTH
DAY	DB	0				;BYTE DAY
YEAR	DW	0				;WORD YEAR
BDATES	ENDS
;
DATETIME SEGMENT PARA PUBLIC 'CODE'
	ASSUME	CS:DATETIME, DS:DATETIME, ES:DATETIME, SS:NOTHING
	ORG	100H				;SET ORIGIN TO XXXX:0100H
ENTRY	LABEL	NEAR				;ENTRY POINT
	JMP	CODE_START			;JUMP AROUND DATA AREAS
	DB	'CLOCK.ASM'                     ;COPYRIGHT NOTICE
	DB	'COPYRIGHT (C) 1983'
	DB	'JERRY D. STUCKLE'
	DB	'PUBLIC DOMAIN SOFTWARE'        ;PROGRAM FOR PUBLIC DOMAIN
CSRSAVE DW	?				;CURSOR MODE SAVE AREA
WRKAREA DB	11 DUP (?)			;WORK AREA
TIME	DB	8 DUP (0),'$'                   ;AREA FOR ASCII TIME
BDATE	BDATES	<,,>				;BINARY DATE AREA
CURRDAY DB	10 DUP (?)			;AREA FOR ASCII DAY
DATEOUT DB	20 DUP (?)			;AREA FOR ASCII DATE
DOSERR	DB	'DOS 2.0 OR ABOVE REQUIRED.  PROGRAM TERMINATED.',0AH,0DH
;
MO_TBL	LABEL	WORD				;POINTER TO MONTH NAMES
	DW	OFFSET JAN			;JANUARY
	DW	OFFSET FEB			;FEBRUARY
	DW	OFFSET MAR			;MARCH
	DW	OFFSET APR			;APRIL
	DW	OFFSET MAY			;MAY
	DW	OFFSET JUN			;JUNE
	DW	OFFSET JUL			;JULY
	DW	OFFSET AUG			;AUGUST
	DW	OFFSET SEP			;SEPTEMBER
	DW	OFFSET OCT			;OCTOBER
	DW	OFFSET NOV			;NOVEMBER
	DW	OFFSET DEC			;DECEMBER
;						;ASCIIZ STRINGS FO MONTH NAMES
JAN	DB	'JANUARY ',0
FEB	DB	'FEBRUARY ',0
MAR	DB	'MARCH ',0
APR	DB	'APRIL ',0
MAY	DB	'MAY ',0
JUN	DB	'JUNE ',0
JUL	DB	'JULY ',0
AUG	DB	'AUGUST ',0
SEP	DB	'SEPTEMBER ',0
OCT	DB	'OCTOBER ',0
NOV	DB	'NOVEMBER ',0
DEC	DB	'DECEMBER ',0
;
;
DAY_NAM LABEL	WORD				;POINTER TO DAY NAMES
	DW	OFFSET SAT			;SATURDAY
	DW	OFFSET SUN			;SUNDAY
	DW	OFFSET MON			;MONDAY
	DW	OFFSET TUE			;TUESDAY
	DW	OFFSET WED			;WEDNESDAY
	DW	OFFSET THU			;THURSDAY
	DW	OFFSET FRI			;FRIDAY
;						;ASCIIZ STRINGS OF DAY NAMES
SAT	DB	'SATURDAY',0
SUN	DB	'SUNDAY',0
MON	DB	'MONDAY',0
TUE	DB	'TUESDAY',0
WED	DB	'WEDNESDAY',0
THU	DB	'THURSDAY',0
FRI	DB	'FRIDAY',0
;
CODE_START LABEL NEAR
;
;	FIRST, WE CLEAR THE SCREEN.  THEN, IF NOT AT AT LEAST DOS 2.0,
;	WE TERMINATE THE PROGRAM.
;
	CLS
	DOSCALL 30H				;GET DOS VERSION NUMBER
	CMP	AL,2				;AT LEAST DOS 2.0?
	JGE	CONT				;YES - CONTINUE
	DISPLAY DOSERR				;NO - DISPLAY DOS ERROR MESSAGE
	INT	20H				;AND TERMINATE
;
;	SAVE THE CURSOR MODE, THEN ERASE IT
;	ALSO, SET THE CONTROL-BREAK INTERRUPT VECTOR
;
CONT:						;DOS VERSION GOOD
	CURSOR	SAVE,CSRSAVE			;SAVE CURSOR MODE
	CURSOR	ERASE				;ERASE CURSOR
	SETINT	23H,TERMIN			;SET CNTL-BREAK VECTOR
	CALL	PROCESS_DATE			;GET THE DATE
;
;	DATE PROCESSING IS COMPLETE.  PROCESS THE TIME
;	NOTE: WHEN DX IS LOADED, THE 2 CHARACTERS ARE SWAPPED, SO THE
;	COMPARE '32' IS FOR THE 23'RD HOUR.
;
PROCESS_TIME:
	GETTIME WRKAREA,CHAR			;GET CURRENT TIME IN ASCII
	MOV	DX,WORD PTR TIME		;GET OLD HOUR IN DH
	MOVE	TIME,WRKAREA,8			;MOVE IT INTO THE TIME FIELD
	CMP	DX,WORD PTR TIME		;SAME AS NEW AREA?
	JE	SHOW_TIME			;YES - NO NEED TO DISPLAY DATE
	CMP	DX,'32'                         ;WAS IT 23:XX:XX?
	JNE	SHOW_TIME			;NO - DON'T DISPLAY THE DATE
	CALL	PROCESS_DATE			;DAY HAS CHANGED - DISPLAY IT
SHOW_TIME:
	LOCATE	15,(COLUMNS-8)/2		;LOCATE THE CURSOR
	DISPLAY TIME				;AND DISPLAY IT
	JMP	PROCESS_TIME			;AND GO PROCESS THE TIME AGAIN
;
;	GET THE CURRENT DATE AND PROCESS IT
;
PROCESS_DATE PROC NEAR				;PROCESS DATE
	GETDATE BDATE,BIN			;GET CURRENT DATE IN BINARY
;
;	   F = 365 * YEAR + DAY + (31 * (MONTH - 1))
;
	MOV	AX,BDATE.YEAR			;GET CURRENT YEAR
	SAVE	AX				;SAVE ON STACK
	MOV	BX,365				;NUMBER OF DAYS IN A YEAR
	MUL	BX				;MULTIPLY
	CLEAR	BH				;CLEAR BH
	MOV	BL,BDATE.DAY			;GET THE DAY
	ADD	AX,BX				;ADD TO AX
	ADC	DX,0				;ADD ANY CARRY TO DX
	MOV	WORD PTR WRKAREA,AX		;SAVE IN WORK AREA
	MOV	WORD PTR WRKAREA+2,DX		;SAVE HIGH ORDER, TOO.
	CLEAR	AH				;CLEAR AH
	MOV	AL,BDATE.MONTH			;GET THE MONTH
	DEC	AL				;DECREMENT
	MOV	BL,31				;MAX DAYS IN MONTH
	MUL	BL				;MULTIPLY
	ADD	WORD PTR WRKAREA,AX		;ADD TO WRKAREA
	ADC	WORD PTR WRKAREA+2,0		;ADD ANY CARRY
;NOW WE MUST USE ONE OF TWO DIFFERENT ROUTINES
	CMP	BDATE.MONTH,3			;JANUARY OR FEBRUARY?
	JGE	PROCESS_DATE_2			;NO - GO TO SECOND ROUTINE
;
;	IF MONTH < 3 THEN F = F + INT ((YEAR - 1) / 4) -
;	   INT ((INT ((YEAR / 100) + 1)) * 3 / 4)
;
	RESTORE AX				;GET YEAR BACK
	DEC	AX				;DECREMENT UEAR
	SAVE	AX				;AND SAVE IT AGAIN
	SHR	AX,1				;DIVIDE BY 2...
	SHR	AX,1				;AND AGAIN.
	ADD	WORD PTR WRKAREA,AX		;ADD TO WORK AREA
	ADC	WORD PTR WRKAREA+2,0		;ADD ANY CARRY
	RESTORE AX				;GET (YEAR-1) BACK
	MOV	BL,100				;100 TO DL
	DIV	BL				;AND DIVIDE
	INC	AL				;INCREMENT IT
	MOV	BL,3				;MULTIPLY BY 3...
	MUL	BL				;DO IT
	SHR	AX,1				;DIVIDE BY 2...
	SHR	AX,1				;AND THEN AGAIN.
	CLEAR	AH				;THROW AWAY REMAINDER
	SUB	WORD PTR WRKAREA,AX		;SUBTRACT NUMBER OF LEAP DAYS
	SBB	WORD PTR WRKAREA,0		;AND ANY BORROW REQUIRED.
	JMP	PROCESS_DATE_3			;GO BACK TO COMMON ROUTINE
PROCESS_DATE_2:
;
;	IF MONTH >= 3 THEN F = F + INT ((MONTH + 23) * 4 / 10) +
;	   INT (YEAR / 4) - INT ((INT (YEAR / 100) + 1) * 3 / 4)
;
	MOV	AL,BDATE.MONTH			;GET THE MONTH IN AL
	CLEAR	AH				;CLEAR HIGH ORDER BYTE
	SHL	AX,1				;MULTIPLY BY 2...
	SHL	AX,1				;AND AGAIN BY 2
	ADD	AX,23				;ADD 23
	MOV	BL,10				;MOVE 10 TO BL
	DIV	BL				;AND DIVIDE
	CLEAR	AH				;THROW OUT REMAINDER
	SUB	WORD PTR WRKAREA,AX		;SUBTRACT FROM WORK AREA
	SBB	WORD PTR WRKAREA+2,0		;AND SUBTRACT ANY BORROW.
	RESTORE AX				;GET YEAR BACK
	SAVE	AX				;SAVE IT AGAIN
	SHR	AX,1				;DIVIDE BY 2
	SHR	AX,1				;AND DO IT AGAIN
	ADD	WORD PTR WRKAREA,AX		;ADD TO WORK AREA
	ADC	WORD PTR WRKAREA+2,0		;ADD ANY CARRY
	RESTORE AX				;GET (YEAR-1) BACK
	MOV	BL,100				;100 TO DL
	DIV	BL				;AND DIVIDE
	INC	AL				;INCREMENT IT
	MOV	BL,3				;MULTIPLY BY 3...
	MUL	BL				;DO IT
	SHR	AX,1				;DIVIDE BY 2...
	SHR	AX,1				;AND THEN AGAIN.
	CLEAR	AH				;THROW AWAY REMAINDER
	SUB	WORD PTR WRKAREA,AX		;SUBTRACT NUMBER OF LEAP DAYS
	SBB	WORD PTR WRKAREA,0		;AND ANY BORROW REQUIRED.
PROCESS_DATE_3:
;
;	DAY NUMBER = (F MOD 7000) MOD 7
;
	MOV	AX,WORD PTR WRKAREA		;GET TOTAL IN AX...
	MOV	DX,WORD PTR WRKAREA+2		;AND HIGH ORDER IN DX
	MOV	BX,7000 			;DIVIDE BY 7000 SO WE...
	DIV	BX				;...DON'T GET AN OVERFLOW
	MOV	AX,DX				;REMAINDER TO AX
	CLEAR	DX				;CLEAR HIGH ORDER WORD
	MOV	BX,7				;NOW DIVIDE BY 7 IN 32...
	DIV	BX				;BIT MODE SO WE DON'T OVERFLOW.
	MOV	BX,DX				;GET DAY NUMBER IN BL
	SHL	BX,1				;SHIFT FOR TABLE LOOKUP
	MOV	SI,WORD PTR DAY_NAM [BX]	;GET DAY NAME
	LEA	DI,CURRDAY			;GET ADDRESS OF DAY AREA
	CLD					;SET INCREMENT
;
;	WE HAVE THE CURRENT DAY.  MOVE IT INTO THE WORK AREA AND DISPLAY
;	IT, CENTERED. FORMULA FOR CENTERING IS (COLUMNS+1-LENGTH)/2
;	AND IS FIGURED BY ((START ADDRESS+COLUMNS+1)-END ADDRESS)/2
;
DAY_LOOP:
	MOVSB					;MOVE A BYTE
	CMP	BYTE PTR [SI],0 		;END OF STRING?
	JNE	DAY_LOOP			;NO - WE'RE NOT DONE
	MOV	BYTE PTR [DI],'$'               ;MOVE IN A '$'
	LEA	BX,CURRDAY+(COLUMNS+1)		;START ADDRESS + LINE LENGTH
	SUB	BX,DI				;SUBTRACE END ADDRESS
	SHR	BL,1				;AND DIVIDE BY 2
	CLS					;CLEAR THE SCEEN
	LOCATE	11,BL				;LOCAT THE CURSOR
	DISPLAY CURRDAY 			;AND DISPLAY THE DAY
;
;	NOW WE MUST MOVE THE CURRENT MONTH INTO THE STRING
;
	MOV	BL,BDATE.MONTH			;GET CURRENT MONTH
	DEC	BL				;DECREMENT FOR TABLE LOOKUP
	SHL	BX,1				;MULTIPLY BY 2
	MOV	SI,MO_TBL[BX]			;GET ADDRESS OF MONTH NAME
	LEA	DI,DATEOUT			;GET TO STRING ADDRESS
	CLD					;INCREMENT ADDRESSES
MONTH_LOOP:					;MOVE THE MONTH NAME
	MOVSB					;NO - MOVE THE BYTE
	CMP	BYTE PTR [SI],0 		;IS THE NEXT BYTE 0?
	JNE	MONTH_LOOP			;AND GO CHECK THE NEXT BYTE
;
;	CURRENT MONTH NAME IS READY.  LET'S GET THE DAY NEXT, CONVERT IT
;	TO ASCII, AND PUT IT IN THE STRING
;
MOVE_DAY:
	MOV	AL,BDATE.DAY			;GET THE DAY IN AL
	AAM					;CONVERT TO UNPACKED DECIMAL
	OR	AX,3030H			;CONVERT TO ASCII
	CMP	AH,'0'                          ;IS THE FIRST CHARACTER 0?
	JE	MOVE_LOW_DATE			;YES - DO NOT MOVE IT
	MOV	BYTE PTR [DI],AH		;NO - MOVE IT
	INC	DI				;AND INCREMENT DESTINATION
MOVE_LOW_DATE:
	MOV	BYTE PTR [DI],AL		;MOVE IN LOW DATE
	INC	DI				;POINT TO NEXT BYTE
	MOV	BYTE PTR [DI],','               ;MOVE IN A COMMA
	INC	DI				;POINT TO NEXT BYTE
	MOV	BYTE PTR [DI],' '               ;A SPACE GOES HERE
	INC	DI				;AND POINT AT NEXT BYTE
;
;	DAY PROCESSING IS COMPLETE, NOW DO THE SAME TO THE YEAR
;
	MOV	AX,BDATE.YEAR			;GET CURRENT YEAR
	SAVE	DI				;SAVE DESTINATION ADDRESS
	CVD	WRKAREA,BDATE.YEAR		;CONVERT THE YEAR TO ASCII
	RESTORE DI				;RESTORE DESTINATION ADDRESS
	MOVE	[DI],WRKAREA,4			;MOVE YEAR TO DESTINATION
	MOV	BYTE PTR [DI],'$'               ;SET A '$' AT THE END
;
;	STRING IS BUILT.  CENTER IT USING THE SAME EQUATION AS THE DAY.
;
	LEA	BX,DATEOUT+COLUMNS		;START ADDRESS + LINE LENGTH
	SUB	BX,DI				;SUBTRACE END ADDRESS
	SHR	BX,1				;AND DIVIDE BY 2
	LOCATE	13,BL			       ;LOCATE THE CURSOR
	DISPLAY DATEOUT 			;AND DISPLAY THE DATE
	RET					;RETURN TO CALLER
PROCESS_DATE ENDP
TERMIN	PROC	NEAR				;TERMINATION PROCEDURE
	CURSOR	SET,CSRSAVE			;RESTORE CURSOR
	CLS					;CLEAR THE SCREEN
	CLEAR	AX				;RETURN CODE 0
	DOSCALL 4CH				;AND TERMINATE
TERMIN	ENDP
DATETIME ENDS
	END	ENTRY

