; =============================================================================
;
;                              Litos8 date and time
;
; =============================================================================

; -----------------------------------------------------------------------------
; Exported user functions (47):
;	PackBCDByte - pack 1 byte to BCD format
;	PackBCD2Byte - pack 2 bytes to BCD format
;	PackBCD4Byte - pack 4 bytes to BCD format
;	UnpackBCDByte - unpack 1 byte from BCD format
;	UnpackBCD2Byte - unpack 2 bytes from BCD format
;	UnpackBCD4Byte - unpack 4 bytes from BCD format
;	ReadDT - read registers from DATETIME structure
;	WriteDT - write registers to DATETIME structure
;	PackDOSDate - pack date do DOS file format
;	UnpackDOSDate - unpack date from DOS file format
;	PackDOSTime - pack time to DOS file format
;	UnpackDOSTime - unpack time from DOS file format
;	DateValidate - limit date to valid values
;	TimeValidate - limit time to valid values
;	DateTimeValidate - limit date and time to valid values
;	DateTimeValidateDT - limit date and time to valid values with DATETIME
;	ClockValidate - limit clock timer value
;	PackUnixTime - pack date and time to Unix format
;	UnpackUnixTime - unpack date and time from Unix format
;	DayToUnix - convert days to Unix format
;	UnixToDay - covert Unix format to days
;	GetTime - get current time
;	GetDate - get current date
;	GetDateTime - get current date and time
;	GetDateTimeDT - get current date and time with DATETIME structure
;	GetTimeUnix - get current time in Unix format
;	GetDateUnix - get current date in Unix format
;	GetDateTimeUnix - get current date and time in Unix format
;	Clock2Unix - convert clock timer to Unix time
;	Clock2Time - convert clock timer to time
;	Time2Clock - convert time to clock timer
;	GetClockTimer - get clock timer
;	SetClockTimer - set clock timer
;	GetRTCTime - get time from RTC (CMOS) clock
;	GetRTCDate - get date from RTC (CMOS) clock
;	GetRTCDateTime - get date and time from RTC (CMOS) clock
;	GetRTCDateTimeDT - get date and time from RTC clock with DATETIME
;	SetTime - set new time
;	SetDate - set new date
;	SetDateTime - set new date and time
;	SetDateTimeDT - set new date and time with DATETIME
;	ReinitDateTime - reinitialize date and time from RTC (CMOS) clock
;	GetSysTimer - get system timer
;	SysTimerToUs - recalc system timer interval to microseconds
;	UsToSysTimer - recalc microseconds to system timer interval
;	WaitUs - wait short time in microseconds
;	WaitMs - wait given time in milliseconds
; -----------------------------------------------------------------------------

; PC Real-time clock (clock timer, counter 0):
;	Crystal oscilator generates 14.31818 MHz, which is divided by 12.
;	Hardware clock generator: 1.193180 MHz (correct value is 1.193181667MHz)
;	Timer divisor: 65536
;	Interrupt frequency: 18.20648193 Hz (correct value 18.206507365 Hz)
;	Interrupt interval: 54.92549322 ms (correct value 54.925416498 ms)
;	Ticks per day: 1573040, 1800B0h (correct value 1573042, 1800B2h)
;	Hundredths second per day: 8640000
;	Recalc counter to hundredth second: 100*65536/1193180 = 5*65536/59659

		CODEINIT_SECTION

; -----------------------------------------------------------------------------
;                      INIT: Initialize date and time service
; -----------------------------------------------------------------------------

; ------------- get clock timer to update date -> DX:AX

InitDateTime:	call	GetClockTimer	; get clock timer

; ------------- get date and time from RTC (CMOS) clock

		call	GetRTCDateTime	; get date and time from RTC clock
		mov	bl,0		; BL <- 0, hundredth

; ------------- set date

		call	PackUnixTime	; pack date to Unix format
		call	UnixToDay	; conver Unix format to day
		mov	[Date],ax	; store current date
		ret


		CODE_SECTION

; -----------------------------------------------------------------------------
;               Reinitialize date and time from RTC (CMOS) clock
; -----------------------------------------------------------------------------
; NOTES:	Can be used after changing TIMER0 (Int 08h) frequency.
; -----------------------------------------------------------------------------

; ------------- Push registers

ReinitDateTime:	push	ax		; push AX
		push	bx		; push BX
		push	cx		; push CX
		push	dx		; push DX
		push	si		; push SI

; ------------- get date and time from RTC (CMOS) clock

		call	GetRTCDateTime	; get date and time from RTC clock
		mov	bl,0		; BL <- 0, hundredth

; ------------- set clock timer

		push	dx		; push DX
		call	Time2Clock	; convert time to clock timer
		call	SetClockTimer	; set clock timer
		pop	dx		; pop DX

; ------------- set date

		call	PackUnixTime	; pack date to Unix format
		call	UnixToDay	; conver Unix format to day
		mov	[Date],ax	; store current date

; ------------- pop registers

		pop	si		; pop SI
		pop	dx		; pop DX
		pop	cx		; pop CX
		pop	bx		; pop BX
		pop	ax		; pop AX
		ret

; -----------------------------------------------------------------------------
;                         Pack 4 bytes to BCD format
; -----------------------------------------------------------------------------
; INPUT:	DH,DL,AH,AL = binary numbers (0..99)
; OUTPUT:	DH,DL,AH,AL = BCD numbers (00h..99h)
; -----------------------------------------------------------------------------

PackBCD4Byte:	xchg	ax,dx		; DH,DL <- high BIN, AH,AL <- low BIN
		call	PackBCD2Byte	; pack bytes in AH,AL to BCD format
		xchg	ax,dx		; DH,DL <- low BIN, AH,AL <- high BCD

; === PackBCD2Byte function must follow!

; -----------------------------------------------------------------------------
;                           Pack 2 bytes to BCD format
; -----------------------------------------------------------------------------
; INPUT:	AH,AL = binary numbers (0..99)
; OUTPUT:	AH,AL = BCD numbers (00h..99h)
; -----------------------------------------------------------------------------

PackBCD2Byte:	xchg	al,ah		; AL <- high BIN, AH <- low BIN
		call	PackBCDByte	; pack byte in AL to BCD format
		xchg	al,ah		; AL <- low BIN, AH <- high BCD

; === PackBCDByte function must follow!

; -----------------------------------------------------------------------------
;                           Pack 1 byte to BCD format
; -----------------------------------------------------------------------------
; INPUT:	AL = binary number (0..99)
; OUTPUT:	AL = BCD number (00h..99h)
; -----------------------------------------------------------------------------

; ------------- push registers

PackBCDByte:	push	cx		; push CX
		mov	cx,ax		; CH <- push registers AH

; ------------- pack number in AL

		aam			; AH <- tens, AL <- ones
		mov	cl,4		; CL <- number of rotations
		shl	ah,cl		; AH << 4
		or	al,ah		; AL <- BCD number

; ------------- pop registers

		mov	ah,ch		; pop register AH
		pop	cx		; pop CX
		ret

; -----------------------------------------------------------------------------
;                         Unpack 4 bytes from BCD format
; -----------------------------------------------------------------------------
; INPUT:	DH,DL,AH,AL = BCD numbers (00h..99h)
; OUTPUT:	DH,DL,AH,AL = binary numbers (0..99)
; -----------------------------------------------------------------------------

UnpackBCD4Byte:	xchg	ax,dx		; DH,DL <- high BCD, AH,AL <- low BCD
		call	UnpackBCD2Byte	; unpack bytes in AH,AL from BCD format
		xchg	ax,dx		; DH,DL <- low BCD, AH,AL <- high BIN

; === UnpackBCD2Byte function must follow!

; -----------------------------------------------------------------------------
;                          Unpack 2 bytes from BCD format
; -----------------------------------------------------------------------------
; INPUT:	AH,AL = BCD numbers (00h..99h)
; OUTPUT:	AH,AL = binary numbers (0..99)
; -----------------------------------------------------------------------------

UnpackBCD2Byte:	xchg	al,ah		; AL <- high BCD, AH <- low BCD
		call	UnpackBCDByte	; unpack byte in AL from BCD format
		xchg	al,ah		; AL <- low BCD, AH <- high BIN

; === UnpackBCDByte function must follow!

; -----------------------------------------------------------------------------
;                           Unpack 1 byte from BCD format
; -----------------------------------------------------------------------------
; INPUT:	AL = BCD number (00h..99h)
; OUTPUT:	AL = binary number (0..99)
; -----------------------------------------------------------------------------

; ------------- push registers

UnpackBCDByte:	push	cx		; push CX
		mov	cx,ax		; CH <- push registers AH

; ------------- unack number in AL

		mov	cl,4		; CL <- number of rotations
		mov	ah,al		; AH <- number to unpack
		shr	ah,cl		; AH <- BCD digit HIGH
		and	al,0fh		; AL <- BCD digit LOW
		aad			; AL <- 10*AH + AL

; ------------- pop registers

		mov	ah,ch		; pop register AH
		pop	cx		; pop CX
		ret

; -----------------------------------------------------------------------------
;                   read registers from DATETIME structure
; -----------------------------------------------------------------------------
; INPUT:	DI = pointer to DATETIME structure
; OUTPUT:	BL = hundredth (0..99)
;		BH = second (0..59)
;		CL = minute (0..59)
; 		CH = hour (0..23)
;		DL = day (1..31)
;		DH = month (1..12)
;		SI = year (1970..2106)
; -----------------------------------------------------------------------------

ReadDT:		mov	bx,[di+DATETIME_Hundr] ; BL <- hundredth, BH <- second
		mov	cx,[di+DATETIME_Min] ; CL <- minute, CH <- hour
		mov	dx,[di+DATETIME_Day] ; DL <- day, DH <- month
		mov	si,[di+DATETIME_Year] ; SI <- year
		ret

; -----------------------------------------------------------------------------
;                   write registers to DATETIME structure
; -----------------------------------------------------------------------------
; INPUT:	DI = pointer to DATETIME structure
; 		BL = hundredth (0..99)
;		BH = second (0..59)
;		CL = minute (0..59)
; 		CH = hour (0..23)
;		DL = day (1..31)
;		DH = month (1..12)
;		SI = year (1970..2106)
; -----------------------------------------------------------------------------

WriteDT:	mov	[di+DATETIME_Hundr],bx ; write hundredth and second
		mov	[di+DATETIME_Min],cx ; write minute and hour
		mov	[di+DATETIME_Day],dx ; write day and month
		mov	[di+DATETIME_Year],si ; write year
		ret

; -----------------------------------------------------------------------------
;                       Pack date to DOS file format
; -----------------------------------------------------------------------------
; INPUT:	DL = day (1..31, can be 0..31)
;		DH = month (1..12, can be 0..15)
;		SI = year (1980..2117)
; OUTPUT:	AX = date in DOS file format
;			bit 0..4: day (1..31, can be 0..31)
;			bit 5..8: month (1..12, can be 0..15)
;			bit 9..15: year - 1980 (0..127 = 1980..2117)
; -----------------------------------------------------------------------------

; ------------- push registers

PackDOSDate:	push	cx		; push CX

; ------------- pack year -> CH

		mov	cx,si		; CX <- year
		sub	cx,1980		; CX <- offset from 1980
		shl	cx,1		; CL <- year - 1980
		mov	ch,cl		; CH <- year - 1980

; ------------- pack month

		mov	al,dh		; AL <- month
		cbw			; AX <- month
		mov	cl,5		; CL <- 5 number of rotations
		shl	ax,cl		; shift month to bit 5

; ------------- add year and day

		or	ah,ch		; AH <- add year
		or	al,dl		; add day

; ------------- pop registers

		pop	cx		; pop CX
		ret

; -----------------------------------------------------------------------------
;                       Unpack date from DOS file format
; -----------------------------------------------------------------------------
; INPUT:	AX = date in DOS file format
;			bit 0..4: day (1..31, can be 0..31)
;			bit 5..8: month (1..12, can be 0..15)
;			bit 9..15: year - 1980 (0..127 = 1980..2117)
; OUTPUT:	DL = day (1..31, can be 0..31)
;		DH = month (1..12, can be 0..15)
;		SI = year (1980..2117)
; -----------------------------------------------------------------------------

; ------------- push registers

UnpackDOSDate:	push	ax		; push AX
		push	cx		; push CX

; ------------- unpack day -> DL and month -> DH

		mov	dx,ax		; DX <- date
		mov	cl,3		; CL <- rotations to shift B5 -> B8
		shl	dx,cl		; DH <- month, DL <- day << 3
		and	dh,0fh		; DH <- month
		shr	dl,cl		; DL <- day

; ------------- unpack year -> CX

		mov	cl,9		; CL <- rotations for year
		shr	ax,cl		; AX <- year offset
		add	ax,1980		; add base
		xchg	ax,si		; SI <- year	

; ------------- pop registers

		pop	cx		; pop CX
		pop	ax		; pop AX
		ret

; -----------------------------------------------------------------------------
;                       Pack time to DOS file format
; -----------------------------------------------------------------------------
; INPUT:	BH = second (0..58, can be 0..62, converted to even second)
;		CL = minute (0..59, can be 0..63)
;		CH = hour (0..23, can be 0..31)
; OUTPUT:	AX = time in DOS file format
;			bit 0..4: second/2 (value 0..29 = 0..58 seconds,
;					can be 0..31 = 0..62 seconds)
;			bit 5..10: minute (0..59, can be 0..63)
;			bit 11..15: hour (0..23, can be 0..31)
; -----------------------------------------------------------------------------

; ------------- push registers

PackDOSTime:	push	cx		; push CX

; ------------- pack minute -> AX

		mov	al,cl		; AL <- minute
		cbw			; AX <- minute
		mov	cl,5		; CL <- number of rotations
		shl	ax,cl		; AX <- shift minute to position

; ------------- pack hour -> AH

		mov	cl,11-8		; CL <- number of rotations
		shl	ch,cl		; CH <- hour in right position
		or	ah,ch		; AH <- add hour

; ------------- add second -> AL

		mov	ch,bh		; CH <- second
		shr	ch,1		; CH <- second / 2
		or	al,ch		; AL <- add second

; ------------- pop registers

		pop	cx		; pop CX
		ret

; -----------------------------------------------------------------------------
;                       Unpack time from DOS file format
; -----------------------------------------------------------------------------
; INPUT:	AX = time in DOS file format
;			bit 0..4: second/2 (value 0..29 = 0..58 seconds,
;					can be 0..31 = 0..62 seconds)
;			bit 5..10: minute (0..59, can be 0..63)
;			bit 11..15: hour (0..23, can be 0..31)
; OUTPUT:	BH = second (0..58, can be 0..62, only even second)
;		CL = minute (0..59, can be 0..63)
;		CH = hour (0..23, can be 0..31)
; -----------------------------------------------------------------------------

; ------------- push registers

UnpackDOSTime:	push	ax		; push AX

; ------------- unpack second -> BH

		mov	bh,al		; BH <- time LOW
		and	bh,1fh		; BH <- second / 2
		shl	bh,1		; BH <- second

; ------------- unpack hour -> CH and minute -> CL

		mov	cl,3		; CL <- rotations to shift hour
		shr	ax,cl		; AH <- hour, AL <- minute << 2
		mov	cl,2		; CL <- rotations to shift minute
		shr	al,cl		; AL <- minute
		xchg	ax,cx		; CH <- hour, CL <- minute

; ------------- pop registers

		pop	ax		; pop AX
		ret

; -----------------------------------------------------------------------------
;                        INTERNAL: Convert year to days
; -----------------------------------------------------------------------------
; INPUT:	CX = year - 1970 (value 0..136 = year 1970..2106)
; OUTPUT:	AX = days
; DESTROYS:	DX
; -----------------------------------------------------------------------------
; days = year*365 + (year+1)/4; if (year > 130) days -= 1

YearToDays:	mov	ax,365		; AX <- number of days per year
		mul	cx		; AX <- days (result 0..49640)
		mov	dx,cx		; DX <- year
		inc	dx		; DX += 1
		shr	dx,1		; DX <- (year+1)/2
		shr	dx,1		; DX <- (year+1)/4
		add	ax,dx		; AX <- add leap years (0..49674)
		cmp	cl,130		; year > 2100 ?
		jbe	short YearToDays2 ; year <= 2100
		dec	ax		; leap correction
YearToDays2:	ret

; -----------------------------------------------------------------------------
;                         Limit date to valid values
; -----------------------------------------------------------------------------
; INPUT:	DL = day (1..31)
;		DH = month (1..12)
;		SI = year (1970..2106)
; OUTPUT:	values can be corrected to valid range
; -----------------------------------------------------------------------------

; ------------- push registers

DateValidate:	push	bx		; push BX

; ------------- validate month

		cmp	dh,1		; minimal value
		ja	short DateValidate2 ; value is OK
		mov	dh,1		; DH <- limit to minimal value
DateValidate2:	cmp	dh,12		; maximal value
		jb	short DateValidate3 ; valule is OK
		mov	dh,12		; DH <- limit to maximal value

; ------------- validate year

DateValidate3:	cmp	si,1970		; minimal value
		ja	short DateValidate4 ; year is OK
		mov	si,1970		; SI <- limit to minimal value
DateValidate4:	cmp	si,2106		; maximal value
		jb	short DateValidate5 ; value is OK
		mov	si,2106		; SI <- limit to maximal value

; ------------- limit year 2106 to 2/7/2106 (6:28:15)

		cmp	dh,2		; maximal month
		jb	short DateValidate5 ; value is OK
		mov	dh,2		; DH <- limit to maximal value
		cmp	dl,7		; maximal day
		jb	short DateValidate5 ; value is OK
		mov	dl,7		; DL <- limit to maximal value

; ------------- validate day

DateValidate5:	cmp	dl,1		; minimal value
		ja	short DateValidate6 ; value is OK
		mov	dl,1		; DL <- limit to minimal value
DateValidate6:	mov	bl,dh		; BL <- month
		dec	bx		; BX <- correction - 1
		mov	bh,0		; BX <- month - 1
		add	bx,DaysInMonth	; BX <- table of days
		test	si,3		; leap year?
		jnz	short DateValidate7 ; not leap year
		cmp	si,2100		; year 2100 ?
		je	short DateValidate7 ; not leap year
		add	bx,DaysInMonthL-DaysInMonth ; leap year
DateValidate7:	cmp	dl,[bx]		; check day
		jbe	short DateValidate8 ; day is OK
		mov	dl,[bx]		; DL <- limit day

; ------------- pop registers

DateValidate8:	pop	bx		; pop BX
		ret

; -----------------------------------------------------------------------------
;                         Limit time to valid values
; -----------------------------------------------------------------------------
; INPUT:	BL = hundredth (0..99)
;		BH = second (0..59)
;		CL = minute (0..59)
;		CH = hour (0..23)
; OUTPUT:	values can be corrected to valid range
; -----------------------------------------------------------------------------

; ------------- validate hundredth

TimeValidate:	or	bl,bl		; negative?
		jns	short TimeValidate2 ; not negative
		mov	bl,0		; BL <- limit to minimal value
TimeValidate2:	cmp	bl,99		; maximal value
		jb	short TimeValidate3 ; value is OK
		mov	bl,99		; BL <- limit to maximal value

; ------------- validate second

TimeValidate3:	or	bh,bh		; negative?
		jns	short TimeValidate4 ; not negative
		mov	bh,0		; BH <- limit to minimal value
TimeValidate4:	cmp	bh,59		; maximal value
		jb	short TimeValidate5 ; value is OK
		mov	bh,59		; BH <- limit to maximal value

; ------------- validate minute

TimeValidate5:	or	cl,cl		; negative?
		jns	short TimeValidate6 ; not negative
		mov	cl,0		; CL <- limit to minimal value
TimeValidate6:	cmp	cl,59		; maximal value
		jb	short TimeValidate7 ; value is OK
		mov	cl,59		; CL <- limit to maximal value

; ------------- validate hour

TimeValidate7:	or	ch,ch		; negative?
		jns	short TimeValidate8 ; not negative
		mov	ch,0		; CH <- limit to minimal value
TimeValidate8:	cmp	ch,23		; maximal value
		jb	short TimeValidate9 ; value is OK
		mov	ch,23		; CH <- limit to maximal value
TimeValidate9:	ret

; -----------------------------------------------------------------------------
;                        Limit date and time to valid values
; -----------------------------------------------------------------------------
; INPUT:	BL = hundredth (0..99)
;		BH = second (0..59)
;		CL = minute (0..59)
;		CH = hour (0..23)
; 		DL = day (1..31)
;		DH = month (1..12)
;		SI = year (1970..2106)
; OUTPUT:	values can be corrected to valid range
; -----------------------------------------------------------------------------

; ------------- date and time validate

DateTimeValidate:
		call	DateValidate	; date validate
		call	TimeValidate	; time validate

; ------------- limit to 2/7/2106 6:28:15

		cmp	si,2106		; maximal year 2106
		jne	short DateTimeValid4 ; not 2106
		cmp	dx,207h		; maximal month and day 2/7/2106
		jne	short DateTimeValid4 ; not 2/7/2106
		cmp	ch,6		; maximal hour
		jb	short DateTimeValid4 ; value is OK
		mov	ch,6		; CH <- limit to maximal value
		cmp	cl,28		; maximal minute
		jb	short DateTimeValid4 ; value is OK
		mov	cl,28		; CL <- limit to maximal value
		cmp	bh,15		; maximal second
		jb	short DateTimeValid4 ; value is OK
		mov	bh,15		; BH <- limit to maximal value
DateTimeValid4:	ret

; -----------------------------------------------------------------------------
;           Limit date and time to valid values with DATETIME structure
; -----------------------------------------------------------------------------
; INPUT:	DI = pointer to DATETIME structure
; OUTPUT:	values in DATETIME can be corrected to valid range
; -----------------------------------------------------------------------------

; ------------- push registers

DateTimeValidateDT:
		push	bx		; push BX
		push	cx		; push CX
		push	dx		; push DX
		push	si		; push SI

;-------------- limit values

		call	ReadDT		; read registers from DATETIME
		call	DateTimeValidate ; limit date and time
		call	WriteDT		; write registers to DATETIME

; ------------- pop registers

		pop	si		; pop SI
		pop	dx		; pop DX
		pop	cx		; pop CX
		pop	bx		; pop BX
		ret

; -----------------------------------------------------------------------------
;                       Limit clock timer value
; -----------------------------------------------------------------------------
; INPUT:	DX:AX = clock timer (0..1800AFh, interval 54.92549322ms)
; OUTPUT:	value can be corrected to valid range
; -----------------------------------------------------------------------------

ClockValidate:	cmp	dx,18h		; maximal value HIGH
		jb	short ClockValidate2 ; value is OK
		mov	dx,18h		; DX <- limit to maximal value
		cmp	ax,0afh		; maximal value LOW
		jb	short ClockValidate2 ; value is OK
		mov	ax,0afh		; AX <- limit to maximal value
ClockValidate2:	ret

; -----------------------------------------------------------------------------
;                       Pack date and time to Unix format
; -----------------------------------------------------------------------------
; INPUT:	BH = second (0..59)
;		CL = minute (0..59)
; 		CH = hour (0..23)
;		DL = day (1..31)
;		DH = month (1..12)
;		SI = year (1970..2106)
; OUTPUT:	DX:AX = number of seconds since 1/1/1970 0:00:00 Thursday
; NOTES:	Maxim date/time: 2/7/2106 6:28:15 (result 0ffffh:0ffffh)
; -----------------------------------------------------------------------------

; Julian date 1/1/1970:2440587, 2/7/2106:2490297, difference 49710, 4294944000s
; leap years (februar is 29): 1972, 1976, 1980, 1984, 1988... but not 2100

; ------------- push registers

PackUnixTime:	push	bx		; push BX
		push	cx		; push CX
		push	si		; push SI
		push	di		; push DI

; ------------- limit date and time values

		call	DateTimeValidate ; limit date and time values

; ------------- prepare year -> CX (0..136)
; BH = second, CL = minute, CH = hour, DL = day, DH = month, SI = year
; year = year - 1970

		mov	al,bh		; AL <- second
		mov	bx,cx		; BL <- minute, BH <- hour
		mov	cx,si		; CX <- year
		sub	cx,1970		; CX <- offset from 1970 (0..136)

; ------------- convert time to seconds -> DI:SI (0..86399)
; AL = second, BL = minute, BH = hour, CX = year, DL = day, DH = month
; time = (hour*60 + minute)*60 + second

		push	dx		; push DX, day and month

		cbw			; AX <- second
		xchg	ax,si		; SI <- second
		mov	al,60		; AL <- number of minutes per hour
		mul	bh		; AX <- recalc hour to minutes
		mov	bh,0		; BX = minute
		add	ax,bx		; AX <- add minute (result 0..1439)
		mov	dx,60		; DX <- number of seconds per minute
		mul	dx		; DX:AX <- recalc minutes to seconds
		add	si,ax		; add seconds (result 0..86399)
		adc	dx,byte 0	; DX:SI <- seconds from time
		mov	di,dx		; DI <- time in seconds HIGH

; ------------- convert year to days -> AX (0..49674)
; CX = year, DI:SI = time in seconds, [sp] = day+month
; days = year*365 + (year+1)/4; if (year > 130) days -= 1

		call	YearToDays	; convert year CX to days->AX, destr.DX

		pop	dx		; pop DX, day and month

; ------------- add days in month from start of year -> AX (0..50008)
; AX = year in days, CX = year, DL = day, DH = month, DI:SI = time in seconds
; days = days + DaysInYear[month-1]

		mov	bl,dh		; BL <- month
		mov	bh,0		; BX <- month
		shl	bx,1		; BX <- offset in table
		add	ax,[bx+DaysInYear-2]; AX <- add days from start of year

; ------------- februar correction -> AX
; AX=year+month in days, CX=year, DL=day, DH=month, DI:SI=time in seconds
; if ((month > 2) && (year != 130) && (((year+2) & 3) == 0) days++

		cmp	dh,2		; februar?
		jbe	short PackUnixTime4 ; no correction
		cmp	cx,130		; year 2100 ?
		je	short PackUnixTime4 ; year 2100
		inc	cx		; DI <- year + 1
		inc	cx		; DI <- year + 2
		test	cl,3		; overlapped year?
		jnz	short PackUnixTime4 ; not overlapped year
		inc	ax		; correction for februar
		
; ------------- add day in month -> AX (0..50038)
; AX=year+month in days, CX=year, DL=day, DH=month, DI:SI=time in seconds
; days = days + day - 1

PackUnixTime4:	mov	dh,0		; DX = day
		dec	dx		; DX <- day 0..30
		add	ax,dx		; AX <- add days in month
		
; ------------- convert days to seconds -> DX:AX
; AX=date in days, CX=year, DI:SI=time in seconds
; time = (days * (24*60*60/2)) * 2

		mov	dx,24*60*60/2	; DX <- seconds per day / 2 (43200)
		mul	dx		; DX:AX <- convert days to second/2
		shl	ax,1		; AX *= 2
		rcl	dx,1		; DX:AX <- days converted to seconds

; ------------- add time -> DX:AX
; DX:AX = date in seconds, DI:SI = time in seconds

		add	ax,si		; AX <- add time LOW
		adc	dx,di		; DX <- add time HIGH

; ------------- pop registers

		pop	di		; pop DI
		pop	si		; pop SI
		pop	cx		; pop CX
		pop	bx		; pop BX
		ret

; -----------------------------------------------------------------------------
;                       Unpack date and time from Unix format
; -----------------------------------------------------------------------------
; INPUT:	DX:AX = number of seconds since 1/1/1970 0:00:00 Thursday
; OUTPUT:	AL = day of week (1..7, 1=Monday...7=Sunday)
;		BH = second (0..59)
;		CL = minute (0..59)
; 		CH = hour (0..23)
;		DL = day (1..31)
;		DH = month (1..12)
;		SI = year (1970..2106)
; NOTES:	Maxim date/time: 2/7/2106 6:28:15 Sunday (result 0ffffh:0ffffh)
; -----------------------------------------------------------------------------

; ------------- push registers

UnpackUnixTime:	push	di		; push DI
		push	ax		; push AX
		push	bx		; push BX

; ------------- split to second+minute -> DI, and hours -> DX:AX (0..1193046)
; DX:AX = seconds since 1/1/1970

		mov	di,60*60	; DI <- number of seconds per hour
		xchg	ax,si		; SI <- time LOW
		xor	ax,ax		; AX <- 0
		xchg	ax,dx		; AX <- time HIGH, DX <- 0
		div	di		; AX <- hours HIGH, DX <- rest
		xchg	ax,si		; AX <- time LOW, SI <- hours HIGH
		div	di		; AX <- hours LOW,DX <- second+minute
		mov	di,dx		; DI <- second+minute
		mov	dx,si		; DX <- hours HIGH

; ------------- split to hour -> BH, and days -> SI (0..49710)
; DX:AX = hours, DI = second+minute

		mov	cx,24		; CX <- number of hours per day
		div	cx		; DX <- hours, AX <- days
		mov	bh,dl		; BH <- hours
		xchg	ax,si		; SI <- days

; ------------- split second+minute to second -> DI and minute -> BL
; BH = hour, SI = days, DI = second+minute

		xchg	ax,di		; AX <- seconds with minutes
		mov	bl,60		; BL <- number of seconds per minute
		div	bl		; AL <- minute, AH <- second
		mov	bl,al		; BL <- minute
		mov	al,ah		; AL <- second
		cbw			; AX = second
		xchg	ax,di		; DI <- second

; ------------- get day of week -> DI HIGH (DI LOW = second)
; BH = hour, BL = minute, SI = days, DI = second

		mov	ax,si		; AX <- days
		add	ax,3		; AX <- add correction (0 -> Thursday)
		xor	dx,dx		; DX <- 0
		mov	cl,7		; CX <- days per week
		div	cx		; DL <- day of week
		inc	dx		; DX <- day of week correction 1..
		xchg	dh,dl		; DH <- day of week, DL <- 0
		or	di,dx		; DI <- second, day of week 
	
; ------------- get year - 1970 -> CX (0..136)
; BH = hour, BL = minute, SI = days, DI = second+day of week

		xor	cx,cx		; CX <- year 1970
		mov	ax,si		; AX <- days
		add	ax,365+366	; AX <- shift to base 1968
		xor	dx,dx		; DX <- 0
		shl	ax,1		; AX <- days*2
		rcl	dx,1		; DX <- carry
		shl	ax,1		; AX <- days*4
		rcl	dx,1		; DX <- carry
		mov	cx,365*3+366	; CX <- number of days per 4 years
		div	cx		; AX <- year
		dec	ax		; AX <- year - 1
		dec	ax		; AX <- back to base 1970
		xchg	ax,cx		; CX <- year

; ------------- get days in year -> SI, correct year -> CX (1970..2106)
; BH = hour, BL = minute, CX = year, SI = days, DI = second+day of week

UnpackUnixTime2:call	YearToDays	; convert year CX to days->AX, destr.DX
		sub	si,ax		; SI <- days in year
		cmp	cl,130		; year >= 2100?
		jb	short UnpackUnixTime3 ; year < 2100
		cmp	si,366		; 12/32/2104 error
		je	short UnpackUnixTim24 ; invalid date
		cmp	cl,134		; year 2104?
		je	short UnpackUnixTime3 ; year 2104
		cmp	si,365		; invalid 32th Dec ?
		jne	short UnpackUnixTime3 ; no
UnpackUnixTim24:inc	cx		; CL <- year + 1
		xor	si,si		; SI <- day 1st Jan
UnpackUnixTime3:add	cx,1970		; CX <- year 1970..2106

; ------------- day (-> DL) and month (-> DH) on 29th Febr leap year
; BH = hour, BL = minute, CX = year, SI = days in year, DI = second+day of week

		test	cl,3		; leap year?
		jnz	short UnpackUnixTime6 ; not leap year
		cmp	cx,2100		; year 2100?
		je	short UnpackUnixTime6 ; not leap year
		cmp	si,31+28	; 29th Febr ?
		jb	short UnpackUnixTime6 ; not leap day
		ja	short UnpackUnixTime5 ; later than 29th Febr
		mov	dx,2*256 + 29	; DX <- date 29th Febr
		jmp	short UnpackUnixTime8

; ------------- split days to day -> DL, and month -> DH
; BH = hour, BL = minute, CX = year, SI = days in year, DI = second+day of week

UnpackUnixTime5:dec	si		; SI <- day - 1 (Februar correction)
UnpackUnixTime6:mov	ax,si		; AX <- days in year
		mov	dl,31		; DL <- days in longest month
		div	dl		; AL <- month
		inc	ax		; AL <- month + 1
		mov	dh,al		; DH <- month
		cbw			; AX <- month
		shl	ax,1		; AX <- month*2
		xchg	ax,si		; SI <- month*2, AX <- days
		cmp	ax,[DaysInYear+si] ; check month
		jb	short UnpackUnixTime7 ; month is OK
		inc	dh		; DH <- month correction
		inc	si		; SI += 1
		inc	si		; SI <- pointer to next month
UnpackUnixTime7:sub	ax,[DaysInYear+si-2] ; AX <- day
		inc	ax		; AX <- day + 1
		mov	dl,al		; DL <- day

; ------------- get output values
; BH=hour, BL=minute, CX=year, DH=month, DL=day, DI=second+day of week

UnpackUnixTime8:mov	si,cx		; SI <- year
		mov	cx,bx		; CL <- minute, CH <- hour
		xchg	ax,di		; AL <- second, AH <- day of week
		mov	di,sp		; DI <- stack
		mov	[di+1],al	; BH <- second
		mov	[di+2],ah	; AL <- day of week

; ------------- pop registers

		pop	bx		; pop BX (with second)
		pop	ax		; pop AX (with day of week)
		pop	di		; pop DI
		ret

; -----------------------------------------------------------------------------
;         Convert days (since 1/1/1970) to Unix format (time 0:00:00)
; -----------------------------------------------------------------------------
; INPUT:	AX = days since 1/1/1970
; OUTPUT:	DX:AX = number of seconds since 1/1/1970 0:00:00 Thursday
; NOTES:	Maximum date/time: 2/7/2106 6:28:15 (result 0ffffh:0ffffh)
; -----------------------------------------------------------------------------

DayToUnix:	mov	dx,24*60*60/2	; DX <- seconds per day / 2 (43200)
		mul	dx		; DX:AX <- convert days to second/2
		shl	ax,1		; AX *= 2
		rcl	dx,1		; DX:AX <- days converted to seconds
		ret

; -----------------------------------------------------------------------------
;         Convert Unix format (time ignored) to days (since 1/1/1970)
; -----------------------------------------------------------------------------
; INPUT:	DX:AX = number of seconds since 1/1/1970 0:00:00 Thursday
; OUTPUT:	AX = days since 1/1/1970
; NOTES:	Maximum date/time: 2/7/2106 6:28:15 (result 0ffffh:0ffffh)
; -----------------------------------------------------------------------------

UnixToDay:	push	dx		; push DX
		push	cx		; push CX

		shr	dx,1		; DX:AX /= 2
		rcr	ax,1		; DX:AX = seconds / 2
		mov	cx,24*60*60/2	; CX <- seconds per day / 2 (43200)
		div	cx		; AX <- days (0..49710)

		pop	cx		; pop CX
		pop	dx		; pop DX
		ret

; -----------------------------------------------------------------------------
;                              get current time
; -----------------------------------------------------------------------------
; OUTPUT:	BL = hundredth (0..99)
;		BH = second (0..59)
;		CL = minute (0..59)
;		CH = hour (0..23)
; -----------------------------------------------------------------------------

GetTime:	push	ax		; push AX
		push	dx		; push DX

		call	GetClockTimer	; get clock timer
		call	Clock2Time	; convert clock timer to time

		pop	dx		; pop DX
		pop	ax		; pop AX
		ret

; -----------------------------------------------------------------------------
;                           get current date and time
; -----------------------------------------------------------------------------
; OUTPUT:	AL = day of week (1..7, 1=Monday...7=Sunday)
;		BL = hundredth (0..99)
;		BH = second (0..59)
;		CL = minute (0..59)
; 		CH = hour (0..23)
;		DL = day (1..31)
;		DH = month (1..12)
;		SI = year (1970..2106)
; -----------------------------------------------------------------------------

GetDateTime:	call	GetTime		; get current time

; === GetDate must follow !

; -----------------------------------------------------------------------------
;                              get current date
; -----------------------------------------------------------------------------
; OUTPUT:	AL = day of week (1..7, 1=Monday...7=Sunday)
;		DL = day
;		DH = month
;		SI = year
; -----------------------------------------------------------------------------

; ------------- push registers

GetDate:	push	bx		; push BX
		push	cx		; push CX
		push	ax		; push AX

; ------------- get clock timer to update date -> DX:AX

		call	GetClockTimer	; get clock timer

; ------------- get current date

		mov	ax,[Date]	; AX <- current date
		call	DayToUnix	; convert date to Unix format
		call	UnpackUnixTime	; unpack date from Unix format

; ------------- pop registers

		pop	bx		; pop AX
		mov	ah,bh		; AH <- pop AH
		pop	cx		; pop CX
		pop	bx		; pop BX
		ret

; -----------------------------------------------------------------------------
;                get current date and time with DATETIME structure
; -----------------------------------------------------------------------------
; INPUT:	DI = pointer to destination DATETIME structure
; OUTPUT:	AL = day of week (1..7, 1=Monday...7=Sunday)
; -----------------------------------------------------------------------------

; ------------- push registers

GetDateTimeDT:	push	bx		; push BX
		push	ax		; push AX
		push	cx		; push CX
		push	dx		; push DX
		push	si		; push SI

; ------------- get date and time

		call	GetDateTime	; get current date and time
		call	WriteDT		; write registers to DATETIME structure

; ------------- pop registers

		pop	si		; pop SI
		pop	dx		; pop DX
		pop	cx		; pop CX
		pop	bx		; BX <- pop AX
		mov	ah,bh		; AH <- pop AH (AL=day of week)
		pop	bx		; pop BX
		ret

; -----------------------------------------------------------------------------
;                get current date in Unix format (time is 0:00:00)
; -----------------------------------------------------------------------------
; OUTPUT:	DX:AX = number of seconds since 1/1/1970 0:00:00 Thursday
; -----------------------------------------------------------------------------

GetDateUnix:	mov	ax,[Date]	; AX <- current date
		call	DayToUnix	; convert date to Unix format
		ret

; -----------------------------------------------------------------------------
;                    get current time in Unix format
; -----------------------------------------------------------------------------
; OUTPUT:	DX:AX = number of seconds (0..86399)
; -----------------------------------------------------------------------------

GetTimeUnix:	push	cx		; push CX
		call	GetClockTimer	; get clock timer -> DX:AX
		call	Clock2Unix	; convert clock timer to Unix
		pop	cx		; pop CX
		ret

; -----------------------------------------------------------------------------
;                      get current date and time in Unix format
; -----------------------------------------------------------------------------
; OUTPUT:	DX:AX = number of seconds since 1/1/1970 0:00:00 Thursday
; -----------------------------------------------------------------------------

; ------------- push registers

GetDateTimeUnix:push	bx		; push BX
		push	cx		; push CX

; ------------- get time and time in Unix format

		call	GetTimeUnix	; get current time in Unix format
		xchg	ax,cx		; CX <- time LOW
		mov	bx,dx		; BX <- time HIGHT
		call	GetDateUnix	; get current date in Unix format
		add	ax,cx		; AX <- date and time LOW
		adc	dx,bx		; DX <- date and time HIGH

; ------------- pop registers

		pop	cx		; pop CX
		pop	bx		; pop BX
		ret

; -----------------------------------------------------------------------------
;           Convert clock timer to Unix time (= number of seconds)
; -----------------------------------------------------------------------------
; INPUT:	DX:AX = clock timer (0..1800AFh, interval 54.92549322ms)
; OUTPUT:	DX:AX = number of seconds (0..86399)
;		CL = hundredth (0..99)
; -----------------------------------------------------------------------------

; ------------- push registers

Clock2Unix:	push	bx		; push BX
		push	cx		; push CX

; ------------- limit value

		call	ClockValidate	; limit clock timer value

; ------------- multiple clock timer * 5 -> DX:AX (0..7865195)
; Recalc counter to hundredth second: *100*65536/1193180 = *5*65536/59659

		mov	bx,5		; BX <- 5
		xchg	ax,cx		; CX <- timer LOW
		xchg	ax,dx		; DX <- timer HIGH
		mul	bx		; AX <- timer HIGH*5
		xchg	ax,cx		; AX <- timer LOW, CX <- timer HIGH*5
		mul	bx		; DX:AX <- timer LOW*5
		add	dx,cx		; DX <- add timer HIGH

; ------------- calculate hundredths DX:AX/59659*65536 -> CX:AX (0..8639994)

		mov	bx,59659	; BX <- 59659
		div	bx		; AX <- quotient, DX <- remainder
		xchg	cx,ax		; CX <- hundredths HIGH
		xor	ax,ax		; AX <- 0, DX:AX = remainder * 65536
		div	bx		; AX <- hundredths LOW

; ------------- calculate seconds -> DX:AX (0..86399), hundredths -> CL (0..99)

		xchg	ax,cx		; AX <- hundredths HIGH, CX <- LOW
		xor	dx,dx		; DX <- 0
		mov	bx,100		; BX <- divisor
		div	bx		; AX <- seconds HIGH, DX <- reaminder
		xchg	ax,cx		; AX <- hundredths LOW, CX <- sec. HIGH
		div	bx		; AX <- seconds LOW, DX <- hundredths
		xchg	dx,cx		; DX <- seconds HIGH, CL <- hundredths

; ------------- pop registers

		pop	bx		; BX <- pop CX
		mov	ch,bh		; CH <- pop CH
		pop	bx		; pop BX
		ret

; -----------------------------------------------------------------------------
;                          Convert clock timer to time
; -----------------------------------------------------------------------------
; INPUT:	DX:AX = clock timer (0..1800AFh, interval 54.92549322ms)
; OUTPUT:	BL = hundredth (0..99)
;		BH = second (0..59)
;		CL = minute (0..59)
;		CH = hour (0..23)
; -----------------------------------------------------------------------------

; ------------- push registers

Clock2Time:	push	ax		; push AX
		push	dx		; push DX

; ------------- calculate seconds -> DX:AX (0..86399), hundredths -> CL (0..99)

		call	Clock2Unix	; convert to Unix time

; ------------- calculate second -> CH (0..59), minutes -> AX (0..1439)

		mov	bx,60		; BX <- divisor
		div	bx		; AX <- minutes, DX <- second
		mov	ch,dl		; CH <- second

; ------------- calculate hour (0 to 23) and minute (0 to 59)

		div	bl		; AL <- hour, AH <- minute
		xchg	ah,al		; AL <- minute, AH <- hour
		xchg	ax,cx		; CL<-min,CH<-hour,AL<-hund,AH<-sec
		xchg	ax,bx		; BL <- hundreth, BH <- second

; ------------- pop registers

		pop	dx		; pop DX
		pop	ax		; pop AX
		ret

; -----------------------------------------------------------------------------
;                        convert time to clock timer
; -----------------------------------------------------------------------------
; INPUT:	BL = hundredth (0..99)
;		BH = second (0..59)
;		CL = minute (0..59)
;		CH = hour (0..23)
; OUTPUT:	DX:AX = clock timer (0..1800AFh, interval 54.92549322ms)
; -----------------------------------------------------------------------------

; ------------- push registers

Time2Clock:	push	bx		; push BX
		push	cx		; push CX

; ------------- limit time values

		call	TimeValidate	; limit time values

; ------------- merge second with hundredth -> BX (0..5999 hundredths)

		mov	al,100		; AL <- 100 hundredths per second
		mul	bh		; AX <- second in hundredths
		mov	bh,0		; BX = hundredth
		add	bx,ax		; DX <- second*100 + hundredth

; ------------- merge minute with hour -> AX (0..1439 minutes)

		mov	al,60		; AL <- 60 minutes per hour
		mul	ch		; AX <- recalc hour to minutes
		mov	ch,0		; CX = minute
		add	ax,cx		; AX <- hour*60 + minute

; ------------- merge all items -> DX:BX (0..8639999 hundredths)

		mov	dx,6000		; DX <- 6000 hundredths per minute
		mul	dx		; DX:AX <- hour & minute in hundredths
		add	bx,ax		; BX <- hundredths LOW
		adc	dx,byte 0	; DX <- hundredths HIGH

; ------------- calculate DX:CX*59659/65536 -> DX:AX (0..7865199)
; Recalc hundredth second to counter: *1193180/100/65536 = *59659/5/65536

		xchg	ax,dx		; AX <- hundredths HIGH
		xchg	ax,bx		; AX <- hundredths LOW, BX <- HIGH
		mov	cx,59659	; CX <- 59659
		mul	cx		; DX <- result LOW (e.g. / 65536)
		xchg	ax,dx		; AX <- result LOW
		xchg	ax,bx		; BX <- result LOW, AX <- hundr. HIGH
		mul	cx		; DX:AX <- result
		add	ax,bx		; AX <- result LOW
		adc	dx,byte 0	; DX <- result HIGH

; ------------- calculate DX:AX/5 -> DX:AX (0..1573039)

		mov	bx,5		; BX <- divisor
		xchg	ax,dx		; AX <- HIGH (0..24), DX <- LOW
		div	bl		; AL <- result HIGH, AH <- remainder
		xchg	ax,cx		; CL <- result HIGH, CH <- remainder
		mov	al,ch		; AL <- remainder
		mov	ch,0		; CX = result HIGH
		cbw			; AX = remainder
		xchg	ax,dx		; AX <- LOW, DX <- HIGH
		div	bx		; AX <- calculate result LOW
		xchg	dx,cx		; DX <- result HIGH

; ------------- pop registers

		pop	cx		; pop CX
		pop	bx		; pop BX
		ret

; -----------------------------------------------------------------------------
;                            get clock timer
; -----------------------------------------------------------------------------
; OUTPUT:	DX:AX = clock timer (0..1800AFh, interval 54.92549322ms)
; -----------------------------------------------------------------------------

; ------------- push registers

GetClockTimer:	push	cx		; push CX

; ------------- get clock timer

		mov	ah,0		; AH <- function code
		int	1ah		; read clock timer
		and	ax,byte 1	; AX <- mask 24-hour flag
		add	[Date],ax	; increase current date
		xchg	ax,dx		; AX <- clock timer LOW
		mov	dx,cx		; DX <- clock timer HIGH

; ------------- limit value

		call	ClockValidate	; limit clock timer value

; ------------- pop registers

		pop	cx		; pop CX
		ret

; -----------------------------------------------------------------------------
;                set clock timer (without updating RTC clock)
; -----------------------------------------------------------------------------
; INPUT:	DX:AX = clock timer (0..1800AFh, interval 54.92549322ms)
; -----------------------------------------------------------------------------

; ------------- push registers

SetClockTimer:	push	ax		; push AX
		push	cx		; push CX
		push	dx		; push DX

; ------------- limit clock timer value

		call	ClockValidate	; limit clock timer value

; ------------- get clock timer to update date

		push	ax		; push AX
		push	dx		; push DX
		call	GetClockTimer	; get clock timer
		pop	dx		; pop DX
		pop	ax		; pop AX

; ------------- Set new time to clock timer CX:DX

		xchg	ax,dx		; DX <- timer LOW, AX <- timer HIGH
		xchg	ax,cx		; CX <- timer HIGH
		mov	ah,1		; AH <- function code
		int	1ah		; set new time

; ------------- pop registry

		pop	dx		; pop DX
		pop	cx		; pop CX
		pop	ax		; pop AX
		ret

; -----------------------------------------------------------------------------
;                      get time from RTC (CMOS) clock
; -----------------------------------------------------------------------------
; OUTPUT:	BH = second (0..59)
;		CL = minute (0..59)
;		CH = hour (0..23)
; -----------------------------------------------------------------------------

; ------------- push registers

GetRTCTime:	push	ax		; push AX
		push	dx		; push DX

; ------------- get RTC time (BCD), second -> DH, minute -> CL, hour -> CH

		mov	ah,2		; AH <- function code
		int	1ah		; get RTC time

; ------------- unpack from BCD to BIN format

		xchg	ax,cx		; AX <- minute and hour		
		call	UnpackBCD4Byte	; unpack DX:AX from BCD to BIN format
		xchg	ax,cx		; CL <- minute, CH <- hour
		mov	bh,dh		; BH <- second

; ------------- limit time values

		mov	al,bl		; AL <- push BL
		call	TimeValidate	; limit time values
		mov	bl,al		; BL <- pop BL

; ------------- pop registers

		pop	dx		; pop DX
		pop	ax		; pop AX
		ret

; -----------------------------------------------------------------------------
;                      get date from RTC (CMOS) clock
; -----------------------------------------------------------------------------
; OUTPUT:	DL = day (1..31)
;		DH = month (1..12)
;		SI = year (1970..2106)
; -----------------------------------------------------------------------------

; ------------- push registers

GetRTCDate:	push	ax		; push AX
		push	cx		; push CX

; ------------- get RTC date (BCD), month -> DH, day -> DL, year -> CH:CL

		mov	ah,4		; AH <- function code
		int	1ah		; get RTC time

; ------------- unpack from BCD to BIN format

		xchg	ax,cx		; AX <- year
		call	UnpackBCD4Byte	; unpack DX:AX from BCD to BIN format
		xchg	ax,cx		; CX <- year
		mov	al,100		; AL <- number of years per century
		mul	ch		; AX <- convert century to year
		add	al,cl		; AL <- add year
		adc	ah,0		; AX <- year
		xchg	ax,si		; SI <- year

; ------------- limit date values

		call	DateValidate	; limit date values

; ------------- pop registers

		pop	cx		; pop CX
		pop	ax		; pop AX
		ret

; -----------------------------------------------------------------------------
;                   get date and time from RTC (CMOS) clock
; -----------------------------------------------------------------------------
; OUTPUT:	BH = second (0..59)
;		CL = minute (0..59)
;		CH = hour (0..23)
; 		DL = day (1..31)
;		DH = month (1..12)
;		SI = year (1970..2106)
; -----------------------------------------------------------------------------

GetRTCDateTime:	call	GetRTCTime	; get time from RTC (CMOS) clock
		call	GetRTCDate	; get date from RTC (CMOS) clock
		push	ax		; push AX
		mov	al,bl		; AL <- push BL
		call	DateTimeValidate ; limit date and time values
		mov	bl,al		; BL <- pop BL
		pop	ax		; pop AX
		ret

; -----------------------------------------------------------------------------
;      get date and time from RTC (CMOS) clock with DATETIME structure
; -----------------------------------------------------------------------------
; INPUT:	DI = pointer to DATETIME structure
; -----------------------------------------------------------------------------

; ------------- push registers

GetRTCDateTimeDT:
		push	ax		; push AX
		push	bx		; push BX
		push	cx		; push CX
		push	dx		; push DX
		push	si		; push SI

; ------------- get date and time

		call	GetRTCDateTime	; get date and time from RTC clock
		mov	bl,0		; BL <- 0, hundrendth
		call	WriteDT		; write registers to DATETIME structure

; ------------- pop registers

		pop	si		; pop SI
		pop	dx		; pop DX
		pop	cx		; pop CX
		pop	bx		; pop BX
		pop	ax		; pop AX
		ret

; -----------------------------------------------------------------------------
;                              set new time
; -----------------------------------------------------------------------------
; INPUT:	BL = hundredth (0..99)
;		BH = second (0..59)
;		CL = minute (0..59)
;		CH = hour (0..23)
; -----------------------------------------------------------------------------

; ------------- push registers

SetTime:	push	ax		; push AX
		push	bx		; push BX
		push	cx		; push CX
		push	dx		; push DX

; ------------- limit time values 

		call	TimeValidate	; limit time values

; ------------- set time to RTC (CMOS) clock

		push	cx		; push CX
		mov	dx,bx		; DH <- second
		xchg	ax,cx		; AX <- minute and hour
		call	PackBCD4Byte	; pack to BCD format
		xchg	ax,cx		; CX <- minute and hour
		mov	dl,0		; DL <- don't use day save time
		mov	ah,3		; AH <- function code
		int	1ah		; set RTC (CMOS) clock
		pop	cx		; pop CX

; ------------- get clock timer to update date -> DX:AX

		call	GetClockTimer	; get clock timer

; ------------- convert time to clock timer -> DX:AX

		call	Time2Clock	; convert time to clock timer

; ------------- Set new time to clock timer CX:DX

		xchg	ax,dx		; DX <- timer LOW, AX <- timer HIGH
		xchg	ax,cx		; CX <- timer HIGH
		mov	ah,1		; AH <- function code
		int	1ah		; set new time

; ------------- pop registers

		pop	dx		; pop DX
		pop	cx		; pop CX
		pop	bx		; pop BX
		pop	ax		; pop AX
		ret

; -----------------------------------------------------------------------------
;                              set new date
; -----------------------------------------------------------------------------
; INPUT:	DL = day (1..31)
;		DH = month (1..12)
;		SI = year (1970..2106)
; -----------------------------------------------------------------------------

; ------------- push registers

SetDate:	push	ax		; push AX
		push	bx		; push BX
		push	cx		; push CX
		push	dx		; push DX
		push	si		; push SI

; ------------- limit date values

		call	DateValidate	; limit date values

; ------------- set time to RTC (CMOS) clock

		push	dx		; push DX
		mov	ax,si		; AX <- year
		mov	cl,100		; CL <- 100, divider
		div	cl		; AL <- century, AH <- year
		xchg	al,ah		; AL <- year, AH <- century
		call	PackBCD4Byte	; pack to BCD format
		xchg	ax,cx		; CH <- century, CL <- year
		mov	dl,0		; DL <- don't use day save time
		mov	ah,5		; AH <- function code
		int	1ah		; set RTC (CMOS) clock

; ------------- get clock timer to update date -> DX:AX

		call	GetClockTimer	; get clock timer
		pop	dx		; pop DX

; ------------- set date

		xor	bx,bx		; BH <- 0, second
		xor	cx,cx		; CX <- 0, minute and hour
		call	PackUnixTime	; pack date to Unix format
		call	UnixToDay	; conver Unix format to day
		mov	[Date],ax	; store current date

; ------------- pop registers

		pop	si		; pop SI
		pop	dx		; pop DX
		pop	cx		; pop CX
		pop	bx		; pop BX
		pop	ax		; pop AX
		ret

; -----------------------------------------------------------------------------
;                           set new date and time
; -----------------------------------------------------------------------------
; INPUT:	BL = hundredth (0..99)
;		BH = second (0..59)
;		CL = minute (0..59)
;		CH = hour (0..23)
;		DL = day (1..31)
;		DH = month (1..12)
;		SI = year (1970..2106)
; -----------------------------------------------------------------------------

; ------------- push registers

SetDateTime:	push	bx		; push BX
		push	cx		; push CX
		push	dx		; push DX
		push	si		; push SI

; ------------- limit values of date and time

		call	DateTimeValidate ; limit values of date and time

; ------------- set date and time

		call	SetDate		; set date
		call	SetTime		; set time

; ------------- pop registers

		pop	si		; pop SI
		pop	dx		; pop DX
		pop	cx		; pop CX
		pop	bx		; pop BX
		ret

; -----------------------------------------------------------------------------
;                  set new date and time with DATETIME structure
; -----------------------------------------------------------------------------
; INPUT:	DI = pointer to DATETIME structure
; -----------------------------------------------------------------------------

; ------------- push registers

SetDateTimeDT:	push	bx		; push BX
		push	cx		; push CX
		push	dx		; push DX
		push	si		; push SI

; ------------- read registers from DATETIME

		call	ReadDT		; read registers from DATETIME

; ------------- limit values of date and time

		call	DateTimeValidate ; limit values of date and time

; ------------- set date and time

		call	SetDate		; set date
		call	SetTime		; set time

; ------------- pop registers

		pop	si		; pop SI
		pop	dx		; pop DX
		pop	cx		; pop CX
		pop	bx		; pop BX
		ret

; -----------------------------------------------------------------------------
;                            Get system timer
; -----------------------------------------------------------------------------
; OUTPUT:	AX = system timer (increasing 0..0ffffh)
; NOTES:	Timer counts in frequency 1.193182 MHz, resolution 0.838095 us.
;		It takes aprox. 7 us.
; -----------------------------------------------------------------------------

; ------------- push registers

GetSysTimer:	push	dx		; push DX

; ------------- get system timer
	
		mov	dl,0		; DL <- counter index
		call	TimerGetVal	; get counter 0 value -> AX
		neg	ax		; AX <- system timer LOW

; ------------- pop registers

		pop	dx		; pop DX
		ret

; -----------------------------------------------------------------------------
;                  Recalc system timer interval to microseconds
; -----------------------------------------------------------------------------
; INPUT:	AX = system timer interval in ticks (0..0ffffh)
; OUTPUT:	AX = time interval in microseconds (0..54924)
; NOTES:	Timer counts in frequency 1.193182 MHz, resolution 0.838095 us.
; -----------------------------------------------------------------------------

SysTimerToUs:	push	dx		; push DX
		mov	dx,54925	; DX <- multiplier 65535/1.193182
		mul	dx		; DX <- time in microseconds
		xchg	ax,dx		; AX <- time in microseconds
		pop	dx		; pop DX
		ret

; -----------------------------------------------------------------------------
;                Recalc microseconds to system timer interval
; -----------------------------------------------------------------------------
; INPUT:	AX = time interval in microseconds (0..54924)
; OUTPUT:	AX = system timer interval in ticks (0..0ffffh)
; NOTES:	Timer counts in frequency 1.193182 MHz, resolution 0.838095 us.
; -----------------------------------------------------------------------------

UsToSysTimer:	push	dx		; push DX
		mov	dx,39097	; DX <- multiplier/2 (1.193182*65536/2)
		mul	dx		; DX <- system timer / 2
		shl	dx,1		; DX <- system timer
		jnc	short UsToSysTimer2 ; no overflow
		xor	dx,dx		; DX <- 0
		dec	dx		; DX <- 0ffffh, limit value
UsToSysTimer2:	xchg	ax,dx		; AX <- system timer value
		pop	dx		; pop DX
		ret

; -----------------------------------------------------------------------------
;                          Wait short time in microseconds
; -----------------------------------------------------------------------------
; INPUT:	AX = required period in us (0..50000)
; NOTES:	Interrupt need not be enabled.
;		It takes minimal aprox. 15 us, granularity is aprox. 7 us.
; -----------------------------------------------------------------------------

; ------------- push registers

WaitUs:		push	ax		; push AX
		push	cx		; push CX
		push	dx		; push DX

; ------------- limit value (to avoid overflow or ambiguous results on bound)

		cmp	ax,50000	; check maximal value
		jbe	short WaitUs3	; value is OK
		mov	ax,50000	; limit maximal value

; ------------- recalc time to ticks -> DX

WaitUs3:	call	UsToSysTimer	; recalc time to system timer
		xchg	ax,dx		; DX <- required time

; ------------- save starting value

		call	GetSysTimer	; get system timer
		xchg	ax,cx		; CX <- save old value

; ------------- wait for a given period

WaitUs4:	call	GetSysTimer	; get system timer
		sub	ax,cx		; AX <- difference
		cmp	ax,dx		; time elapsed?
		jb	short WaitUs4	; continue waiting

; ------------- pop registers

		pop	dx		; pop DX
		pop	cx		; pop CX
		pop	ax		; pop AX
		ret

; -----------------------------------------------------------------------------
;                        Wait given time in milliseconds
; -----------------------------------------------------------------------------
; INPUT:	AX = required period in ms (0..65535)
; NOTES:	Interrupt need not be enabled.
; -----------------------------------------------------------------------------

; ------------- push registers

WaitMs:		push	ax		; push AX
		push	cx		; push CX
		push	dx		; push DX

; ------------- save required time -> CX

		xchg	ax,cx		; CX <- required period
		jcxz	WaitMs8		; no period required

; ------------- prepare time for one wait -> AX

WaitMs2:	mov	ax,50		; AX <- required time 50 ms
		cmp	cx,50		; check remaining time
		jae	short WaitMs4	; longer time
		mov	ax,cx		; AX <- limit time
WaitMs4:	sub	cx,ax		; CX <- decrease counter

; ------------- one wait

		mov	dx,1000		; DX <- us per ms
		mul	dx		; AX <- time in us
		call	WaitUs		; wait 1000 us
		or	cx,cx		; all time elapsed?
		jnz	short WaitMs2	; next wait

; ------------- pop registers

WaitMs8:	pop	dx		; pop DX
		pop	cx		; pop CX
		pop	ax		; pop AX
		ret

; -----------------------------------------------------------------------------
;                       Debug check Unix time
; -----------------------------------------------------------------------------

%ifdef DEB_CHECKUNIXTIME

; ------------- mesage

CheckUnixTime:	mov	bx,DebUnixTimeMsg ; BX <- message text string
		call	DispConTextStr	; display text string

; ------------- initialize pointers

		xor	ax,ax		; AX <- 0
		mov	[DebUnixTimeUnix],ax ; Unix time LOW
		mov	[DebUnixTimeUnix+2],ax ; Unix time HIGH
		mov	si,1970		; year
		mov	dx,101h		; month and day
		xor	cx,cx		; hour and minute
		xor	bx,bx		; second and hundredth
		mov	di,DebUnixDatetime ; DATETIME structure
		call	WriteDT		; write registers
		mov	byte [DebUnixTimeOld],0ffh ; last displayed day
		mov	byte [DebUnixTimeDayW],4 ; day of week

; ------------- display new year

CheckUnixTime2:	mov	al,[di+DATETIME_Day] ; AL <- day
		cmp	al,[DebUnixTimeOld] ; check last displayed day
		je	short CheckUnixTime3 ; day already displayed
		mov	[DebUnixTimeOld],al ; store new displayer day
		mov	bx,DebUnixTimeMsgY ; BX <- message text string
		call	DispConTextStr	; display text string

		mov	ah,DATEFORM_JAPAN ; AH<-date format Japan (YYYY-MM-DD)
		call	ReadDT		; read registers from DATETIME
		call	DispConDate	; display date
		call	DispConSpc	; display space

; ------------- check unpack from UNIX format

CheckUnixTime3:	mov	ax,[DebUnixTimeUnix] ; AX <- Unix time LOW
		mov	dx,[DebUnixTimeUnix+2] ; DX <- Unix time HIGH
		call	UnpackUnixTime	; unpack Unix time
		cmp	al,[DebUnixTimeDayW] ; check day of week
		jne	short CheckUnixTime32 ; error
		cmp	bh,[di+DATETIME_Sec] ; check second
		jne	short CheckUnixTime32 ; error
		cmp	cl,[di+DATETIME_Min] ; check minute
		jne	short CheckUnixTime32 ; error
		cmp	ch,[di+DATETIME_Hour] ; check hour
		jne	short CheckUnixTime32 ; error
		cmp	dl,[di+DATETIME_Day] ; check day
		jne	short CheckUnixTime32 ; error
		cmp	dh,[di+DATETIME_Month] ; check month
		jne	short CheckUnixTime32 ; error
		cmp	si,[di+DATETIME_Year] ; check year
		je	short CheckUnixTime4 ; all is OK

; ------------- error in function UnpackUnixTime

CheckUnixTime32:call	CheckUnixTime8	; display current date and time

		call	DispConByte	; display day of week
		call	DispConSpc	; display space

		mov	ah,DATEFORM_JAPAN ;AL <- date format JAPAN (YYYY-MM-DD)
		call	DispConDate	; display date
		call	DispConSpc	; display space

		call	DispConTime	; display time
		call	DispConSpc	; display space

CheckUnixTime34:jmp	CheckUnixTime34	; halt

; ------------- check pack to UNIX format

CheckUnixTime4:	call	ReadDT		; load registers from DATETIME
		call	PackUnixTime	; pack Unix time
		cmp	ax,[DebUnixTimeUnix] ; check Unix time LOW
		jne	CheckUnixTime42	; error
		cmp	dx,[DebUnixTimeUnix+2] ; check Unix time HIGH
		je	short CheckUnixTime5 ; all is ok

; ------------- error in function PackUnixTime

CheckUnixTime42:call	CheckUnixTime8	; display current date and time

		call	DispConDWord	; display counter
		call	DispConSpc	; display space

CheckUnixTime44:jmp	CheckUnixTime44	; halt

; ------------- increase Unix time

CheckUnixTime5:	add	word [DebUnixTimeUnix],byte 1 ; increase time LOW
		adc	word [DebUnixTimeUnix+2],byte 0 ; increase time HIGH
		jnc	short CheckUnixTime52 ; continue test

; ------------- all is OK

		mov	bx,DebUnixTimeMsg0 ; BX <- message OK
		call	DispConTextStr	; display message
		ret

; ------------- increase time

CheckUnixTime52:inc	byte [di+DATETIME_Sec] ; increase second
		cmp	byte [di+DATETIME_Sec],60 ; overflow?
		jne	short CheckUnixTime7 ; no
		mov	byte [di+DATETIME_Sec],0 ; reset second to 0
		inc	byte [di+DATETIME_Min] ; increase minute
		cmp	byte [di+DATETIME_Min],60 ; overflow?
		jne	short CheckUnixTime7 ; no
		mov	byte [di+DATETIME_Min],0 ; reset minute to 0
		inc	byte [di+DATETIME_Hour] ; increase hour
		cmp	byte [di+DATETIME_Hour],24 ; overflow?
		jne	short CheckUnixTime7 ; no
		mov	byte [di+DATETIME_Hour],0 ; reset hour to 0

; ------------- increase day of week

		inc	byte [DebUnixTimeDayW] ; increase day of week
		cmp	byte [DebUnixTimeDayW],8 ; overflow?
		jne	short CheckUnixTime6 ; no
		mov	byte [DebUnixTimeDayW],1 ; reset day of week to 1

; ------------- increase date

CheckUnixTime6:	inc	byte [di+DATETIME_Day] ; increase date
		mov	bl,[di+DATETIME_Month] ; BL <- month
		dec	bx		; BX <- correction - 1
		mov	bh,0		; BX <- month
		add	bx,DaysInMonth	; BX <- table of days
		test	word [di+DATETIME_Year],3 ; leap year?
		jnz	short CheckUnixTime62 ; not leap year
		cmp	word [di+DATETIME_Year],2100 ; year 2100?
		je	short CheckUnixTime62 ; not leap year
		add	bx,DaysInMonthL - DaysInMonth ; BX <- leap table
CheckUnixTime62:mov	al,[bx]		; AL <- number of days in this month
		cmp	al,[di+DATETIME_Day] ; check day
		jae	short CheckUnixTime7 ; no overflow
		mov	byte [di+DATETIME_Day],1 ; reset day to 1
		inc	byte [di+DATETIME_Month] ; increase month
		cmp	byte [di+DATETIME_Month],13 ; overflow?
		jne	short CheckUnixTime7 ; no overflow
		mov	byte [di+DATETIME_Month],1 ; reset month to 1
		inc	word [di+DATETIME_Year] ; increase year

; ------------- continue test

CheckUnixTime7:	jmp	CheckUnixTime2	; next test

; ------------- display current date and time

CheckUnixTime8:	push	ax		; push AX
		push	bx		; push BX
		push	cx		; push CX
		push	dx		; push DX
		push	si		; push SI

		mov	bx,DebUnixTimeMsgU ; BX <- error message
		call	DispConTextStr	; display error message

		mov	al,[DebUnixTimeDayW] ; AL <- day of week
		call	DispConByte	; display day of week
		call	DispConSpc	; display space

		mov	ah,DATEFORM_JAPAN; AH <- date format Japan (YYYY-MM-DD)
		call	ReadDT		; read registers from DATETIME
		call	DispConDate	; display date
		call	DispConSpc	; display space
		call	DispConTime	; display time
		call	DispConSpc	; display space

		mov	ax,[DebUnixTimeUnix] ; AX <- Unix time LOW
		mov	dx,[DebUnixTimeUnix+2] ; DX <- Unix time HIGH
		call	DispConDWord	; display Unix time
		call	DispConSpc	; display space

		mov	al,"-"		; AL <- separator
		call	DispConChar	; display separator
		call	DispConSpc	; display space

		pop	si		; pop SI
		pop	dx		; pop DX
		pop	cx		; pop CX
		pop	bx		; pop BX
		pop	ax		; pop AX
		ret
%endif

; -----------------------------------------------------------------------------
;                            Constant data
; -----------------------------------------------------------------------------

		CONST_SECTION

; ------------- debug check Unix time

%ifdef DEB_CHECKUNIXTIME
DebUnixTimeMsg:	CTEXT	13,10,"DEBUG check Unix time conversions...",13,10
DebUnixTimeMsgY:CTEXT	13,"Checking date "
DebUnixTimeMsgU:CTEXT	13,"Error in function UnpackUnixTime!",13,10
DebUnixTimeMsgP:CTEXT	13,"Error in function PackUnixTime!",13,10
DebUnixTimeMsg0:CTEXT	13,"Unix time functions are all OK",13,10
%endif

; ------------- days in month

DaysInMonth:	db	31,28,31,30,31,30,31,31,30,31,30,31	; normal year
DaysInMonthL:	db	31,29,31,30,31,30,31,31,30,31,30,31	; leap year

; ------------- days in months from start of year

DaysInYear:	dw	0					; 1 (0)
		dw	31					; 2 (31)
		dw	31+28					; 3 (59)
		dw	31+28+31				; 4 (90)
		dw	31+28+31+30				; 5 (120)
		dw	31+28+31+30+31				; 6 (151)
		dw	31+28+31+30+31+30			; 7 (181)
		dw	31+28+31+30+31+30+31			; 8 (212)
		dw	31+28+31+30+31+30+31+31			; 9 (243)
		dw	31+28+31+30+31+30+31+31+30		; 10 (273)
		dw	31+28+31+30+31+30+31+31+30+31		; 11 (304)
		dw	31+28+31+30+31+30+31+31+30+31+30	; 12 (334)

; -----------------------------------------------------------------------------
;                            Uninitialised data
; -----------------------------------------------------------------------------

		DATA_SECTION

; ------------- debug check Unix time

		align	4, resb 1
%ifdef DEB_CHECKUNIXTIME
DebUnixDatetime:resb	DATETIME_size	; current date and time
		align	4, resb 1
DebUnixTimeUnix:resd	1		; Unix time
DebUnixTimeDayW:resb	1		; day of week
DebUnixTimeOld:	resb	1		; last displayed day
%endif

; ------------- current date

		align	2, resb 1
Date:		resw	1		; current date (days from 1/1/1970)
