;       SCCSID = @(#)time.asm	6.1 90/11/17
SCCSID	equ	"@(#)time.asm	1.18 90/09/17"

TITLE	TIME - time and date functions
NAME	TIME
PAGE ,132
;
; ****************************************************************************
; *									     *
; *			  IBM/Microsoft Confidential			     *
; *									     *
; *		    Copyright (c) IBM Corporation  1987, 1990		     *
; *		    Copyright (c) Microsoft Corp.  1987, 1990		     *
; *			      All Rights Reserved			     *
; *									     *
; ****************************************************************************
;

; System Calls and low level routines for DATE and TIME
;
;
;   w_SetDateTime
;   Date16
;   ReadTime
;   DSUM
;   CheckTime
;   Calc_Date
;   DemWriteClock
;
;   Revisions:
;	danno 22-Nov-1988
;	    Removed $GET/$SET routines
;	danno 16-Feb-1989
;	    Added DemWriteClock and Calc_Date far16 entry points

INCL_ERROR2_H	EQU	1		; for error2.inc EQUates
.xlist
include dosseg.inc
include basemaca.inc
include osmaca.inc
include fnotask.inc
include infoseg.inc
include bseerr.inc
.xcref
include devsym.inc
include syscalls.inc
include mi.inc
include pmode.inc
include struc.inc
include	timlib.inc
.cref
.list

	ModName , FNO_TIME_ASM
	CpuMode 386

	ReferGlobal	Day,BYTE
	ReferGlobal	Month,BYTE
	ReferGlobal	Year,WORD
	ReferGlobal	WeekDay,BYTE
	ReferGlobal	BClock,DWORD
	ReferGlobal	DayCnt,WORD
	ReferGlobal	YRTAB,BYTE
	ReferGlobal	MONTAB,BYTE
	ReferGlobal	SISData,BYTE
	ReferGlobal	_Sis2,BYTE

ifdef SECURITY
        ReferFlat       _fSec,BYTE                                      ;@SEC
        EXTRNFAR        SecSetDateTime                              ;@SEC
endif

	EXTRNFAR	FuBuff

	EXTRN		SetRead:NEAR
	EXTRN		SetWrite:NEAR
	EXTRN		DevIOCall2:NEAR
	EXTRN		FreeReqPacket:NEAR


	FOURYEARS = 3*365 + 366
	TIMBUFSIZ = 6			; size of buffers used by BCLOCK

SUBTTL DATE AND TIME - SYSTEM CALLS 42,43,44,45
PAGE

	FARCODE LOW
CODE	SEGMENT
    ASSUME  SS:TASKAREA

;******* START OF SOURCE FILE SPECIFICATIONS ********************
;*
;* SUBROUTINE NAME:	w_SetDateTime
;*
;* FUNCTION:	Changes the time of day to the value in structure.
;*		This routine is the worker for the DOSSETDATETIME system call.
;*
;* ENTRY POINT: w_SetDateTime
;*    LINKAGE:	HYBRID
;*
;* INPUT:	BX:SI = DateTime structure
;*
;* EXIT-NORMAL:
;*	System time and date are set according to data in DateTime structure.
;*	Carry clear (no error)
;*	    AX = 0
;*
;* EXIT-ERROR:
;*	Carry set (error)
;*	    AX = ERROR_TS_DATETIME
;*
;* EFFECTS:	AX, CX, DX, DS, ES
;*
;* INTERNAL REFERENCES:
;*    ROUTINES:		CheckTime, Calc_Date
;*
;* EXTERNAL REFERENCES:
;*    ROUTINES:		SetWrite, DevIOCall2, FreeReqPacket, FuBuff
;*
;* Change History
;* --------------
;* 02/10/95 LRN - added Security callout hook.                         @SEC
;*
;*********************** END OF SPECIFICATIONS *********************
;**********************START OF PSEUDO CODE ************************
;
; Call FuBuff to put the user's DateTime on the system buffer
; if FuBuff fails then return to the caller
; Call CheckTime to examine the time is valid or not
; if the time is not valid
;    then jump to SetDTError to handle the error
; if user's specified timezone > 720 or user's specified timezone < -720
;    then jump to SetDTError to handle the error
; Call Calc_Date( year, month, day ) to check date, set date in global data,
;      and calculate date count from 1980-1-1
; if Calc_Date failed
;    then jump to SetDTError to handle the error
; set system time zone as the user's specified time zone
; Call SetWrite to get request packet for set date & time
; set up DS:SI => clock device header
; call DevIOCall2 to set date and time
; if DevIOCall2 failed
;    internal error
; call FreeReqPacket to free request packet
; if FreeReqPacket failed
;    internal error
; set AX=0 as good return code
; return
;
; SetDTError:
;  set AX = ERROR_TS_DATETIME
;  return to caller
;
;*********************** END OF PSEUDO CODE ************************

Procedure   w_SetDateTime,<near,faronly,far16>
	assume ds:nothing,es:nothing,ss:TASKAREA
LocalVar TimeBuf,TIMBUFSIZ		; buffer for read/write calls
LocalVar DTbuff,<SIZE time>		; allocate local DateTime buffer

	EnterProc
					; put users date & time in kernel buffer
					; (BX:SI) -> callers DateTime structure
	TaskContext <es>,di		; (ES:DI) -> local DTbuff
	lea	di,DTbuff		;
	mov	cx,SIZE time		; (CX) = number of bytes to copy

; BX:SI -> pointer to source buffer (may be in user space)
; ES:DI -> target buffer (in kernel space)
; CX	=  number of bytes to copy
	CALLFAR FuBuff			; Get callers DateTime structure
	jc	SetDT_leave
					; if GP fault then exit.  AX=error
					; put time in registers

ifdef SECURITY
        FlatContext <ds>                                                ;@SEC
        cmp     _fSec,0                                                 ;@SEC
        jz      set10                   ; no security. skip the callout.;@SEC

	TaskContext <es>,di		                                ;@SEC
	lea	di,DTbuff		; (ES:DI) -> kernel DTbuff      ;@SEC

        CALLFAR SecSetDateTime                                          ;@SEC
        jc      SetDT_leave             ; jump if callout rejects api   ;@SEC
set10:                                                                  ;@SEC
endif

; Carry clear
; SI, DI, both advanced by rep movsb
;;;	mov	ch,[DTbuff].Thours	; hour
;;;	mov	cl,[DTbuff].Tminutes	; minute
	mov	cx,word ptr [DTbuff].Thours
	xchg	cl,ch
	.errnz	(Tminutes - Thours - 1)
;;;	mov	dh,[DTbuff].Tseconds	; second
;;;	mov	dl,[DTbuff].Thundredths	; hundreth
	mov	dx,word ptr [DTbuff].Tseconds
	xchg	dl,dh
	.errnz	(Thundredths - Tseconds - 1)
	call	CheckTime		; see if time is valid
	jnc	SetDTError		; no -- jump to handle error
	mov	bx,[DTbuff].Ttimezone	; get timezone
	cmp	bx,-720d		; check user specified timezone
	jl	SetDTError		; and +12 hours (in minutes)
	cmp	bx,720d			; check user specified timezone
	jg	SetDTError		;
	mov	word ptr [TimeBuf+2],cx ; save valid time
	mov	word ptr [TimeBuf+4],dx
					; put date in registers
;;;	mov	dl,[DTbuff].Tday	; dl=day, dh=month
;;;	mov	dh,[DTbuff].Tmonth
	mov	dx,word ptr [DTbuff].Tday
	.errnz	(Tmonth - Tday - 1)
	mov	cx,[DTbuff].Tyear	; year
	call	Calc_Date		; get day count
	cmp	al, 0FFh		; error on calculating date?
	jne	set20			; ok, DX=day count
	jmp	SetDTError		; fail, jump

set20:
	mov	word ptr [TimeBuf],dx	; set date
	mov	bx,[DTbuff].Ttimezone	; get timezone
	DosContext DS
	mov	SISData.SIS_TimeZone,bx ; set timezone
	mov	fs,[_Sis2].is_sel	; update infoseg copy
	mov	fs:SIS_TimeZone,bx

; set DS:BX=> Xfer addr == TimeBuf
	TaskContext <ds>,bx
	lea	BX,TimeBuf		; (ds:bx) = time buffer

;******* START OF SOURCE FILE SPECIFICATIONS ********************
;*
;* SUBROUTINE NAME:	common_write_clock
;*
;* FUNCTION:	Set the current date and time on hardware.
;*
;* ENTRY POINT: common_write_clock
;*    LINKAGE:	NEAR
;*
;* INPUT:	DS:BX = address of transfer buffer
;*
;* EXIT-NORMAL: AX = 0	set clock successfully
;*
;* EXIT-ERROR:
;*
;* EFFECTS:	BX, CX, DX, SI, DS, ES, flags
;*
;* INTERNAL REFERENCES:
;*    ROUTINES:
;*
;* EXTERNAL REFERENCES:
;*    ROUTINES:		SetWrite, DevIOCall2, FreeReqPacket
;*
;*********************** END OF SPECIFICATIONS *********************
;**********************START OF PSEUDO CODE ************************
;
; Call SetWrite to get request packet for set date & time
; set up DS:SI => clock device header
; call DevIOCall2 to set date and time
; if DevIOCall2 failed
;    internal error
; call FreeReqPacket to free request packet
; if FreeReqPacket failed
;    internal error
; set AX=0 as good return code
; return
;
;*********************** END OF PSEUDO CODE ************************

Entry	common_write_clock,near,local	; Provide entry for INT 21 functions

	MOV	CX,TIMBUFSIZ		; CX=SIZE
	XOR	DX,DX			; DX=0=STARTING RECORD
	XOR	AX,AX			; AH=0=MEDIA BYTE,AL=0=UNIT CODE

; DS:BX=> Xfer addr == TimeBuf
; CX=record count
; DX=starting record=0
; AH=media byte=0
; AL=unit=0

	call	SetWrite		; get ES:BX=> tiled request packet
	mov	es:[bx].IOSFN,ax	; dummy SFN

; set up DS:SI=> clock device header
	DosContext <DS>,ax
	mov	si,word ptr BClock	; clock dev. header offset
	mov	ax,word ptr BClock+2	; clock dev. header selector
	whatmode
	.if nc
	  mov	ax,word ptr BClock+4	; clock dev. header segment
	.endif
    ASSUME	DS:NOTHING
	mov	ds,ax
	xor	ax,ax			; restore ax value

; DS:SI=> clock device header
; ES:BX=> tiled request packet
	call	DevIOCall2		;Set the date
	test	word ptr es:[bx].PktStatus,STERR ; Is status=0?
	jnz	int_err
	call	FreeReqPacket		; free packet in ES:BX
	jc	int_err			; failed, internal err
	xor	ax,ax			; set good return code
SetDT_leave:
	LeaveProc
	RETP

SetDTError:
	mov	ax,ERROR_TS_DATETIME	; error code on failure in 0FFFFh
	stc				; set carry to indicate error
	jmp	SetDT_leave

int_err: InternalError <time: write clock failed> ; yes, halt the system

EndProc w_SetDateTime


SUBTTL Date16, ReadTime -- GUTS OF TIME AND DATE
PAGE

;*********************************************************************
; Function: Get the current date in AX, the current time in DX
;
; Input:	nothing
;
; Output:
;   AX = the current date = YYYYYYYMMMMDDDDD  years months days
;   DX = the current time = HHHHHMMMMMMSSSSS  hours minutes seconds/2
;
; Registers modified: none
;

Procedure   Date16,<near,faronly,far16>
	assume ds:DOSGROUP,es:nothing,ss:nothing
	push	ds
	PUSH	CX
	DosContext <DS>,cx
	CALL	ReadTime
	SHL	CL,2			;Minutes to left part of byte
	SHL	CX,3			;Push hours and minutes to left end
	SHR	DH,1			;Count every two seconds
	OR	CL,DH			;Combine seconds with hours and minutes
	MOV	DX,CX
;
; WARNING!  Month and Year must be adjacently allocated
;
	MOV	AX,WORD PTR [Month]	;Fetch month and year
	SHL	AL,4			;Push month left to make room for day
	SHL	AX,1
	OR	AL,[Day]
	POP	CX
	pop	ds
	RETP
EndProc Date16

;******* START OF SOURCE FILE SPECIFICATIONS ********************
;*
;* SUBROUTINE NAME:	ReadTime
;*
;* FUNCTION:	Get time in CX:DX, and update new date for
;*		Year, Day, Month, DayCnt, and WeekDay
;*
;* ENTRY POINT: ReadTime
;*    LINKAGE:	HYBRID
;*
;* INPUT:	DS:DOSGROUP
;*
;* EXIT-NORMAL:
;*	CH  =  Hour (0 - 23)
;*	CL  =  Minutes (0-59)
;*	DH  =  Seconds (0-59)
;*	DL  =  Hundredths (0-99)
;*
;* EXIT-ERROR:
;*
;* EFFECTS:	AX
;*
;* INTERNAL REFERENCES:
;*    ROUTINES:		Get_day_count, getFeb
;*
;* EXTERNAL REFERENCES:
;*    ROUTINES:
;*
;*********************** END OF SPECIFICATIONS *********************
;**********************START OF PSEUDO CODE ************************
;
; disable interrupts
; get year, month, day form SysInfoSeg
; get hour, minute, second, and hundredths form SysInfoSeg
; enable interrupts
; call getFeb to set # of days in Feburary
; Call get_day_count( year, month, day ) to calculate day count
;      from 1980-1-1, and update Year, Day, Month, DayCnt, and WeekDay
;
;*********************** END OF PSEUDO CODE ************************

Procedure   ReadTime,HYBRID
	assume ds:DOSGROUP,es:nothing,ss:nothing
	PUSH	SI
	PUSH	BX
; get year, month, and day from SysInfoSeg
	pushf
	cli				; disable interrupts
	mov	CX,SISData.SIS_YrsDate	; CX = Year (1980 - 2099)

; WARNING!!!! Day and Month must be adjacently allocated!
	MOV	DX,WORD PTR SISData.SIS_DayDate ; fetch both day and month
					; DH = Month  (1 - 12)
					; DL = Day  (1 - 31)
; WARNING!!!! HOURS and MINUTES must be adjacently allocated!
;	      SECONDS and HUNDREDTHS must be adjacently allocated!
	MOV	bx,WORD PTR SISData.SIS_SecTime ; Get Secs/Hundths
	MOV	si,WORD PTR SISData.SIS_HrsTime ; Get Hrs/Mins
	popf				; restore interrupt flag
	push	bx
	push	si

; update Year, Day, Month, DAYCNT, WeekDay
	call	getFeb			; set # of days in Feburary
	SUB	CX,1980			;Fix bias in year
	call	get_day_count		; set Year,Day,DayCnt,WeekDay

	pop	cx
	pop	dx
	xchg	ch,cl
	xchg	dh,dl
	POP	BX
	POP	SI
	ret
EndProc ReadTime

;*********************************************************************
; Function:	Add data in DS:SI to DX
; Input:	DS:SI=>source data
;		CX = # of entries in source data
; Output:	DX
; Effect:	ax, cx
;
Procedure   DSUM,NEAR
	assume ds:nothing,es:nothing,ss:nothing
	XOR	AH,AH
	JCXZ	dsumx
dsum1:
	LODSB
	ADD	DX,AX
	LOOP	dsum1
dsumx:	ret
EndProc DSUM


;*********************************************************************
;
; CheckTime checks to see if the time in CX and DX represents a valid time
; of day.
;
; Input:
;	CH	Hours		Valid range: 0 - 23
;	CL	Minutes		Valid range: 0 - 59
;	DH	Seconds		Valid range: 0 - 59
;	DL	1/100's of seconds  Valid range: 0 - 99
;
; Output:
;	Carry set if valid time
;	Carry clear if invalid time (e.g. CH == 35)
;
; Registers used:
;	Flags
; Calls:
;	None
;
Procedure   CheckTime,NEAR
	assume ds:nothing,es:nothing,ss:nothing
	cmp	CH,24			;Check hours
	jae	CheckTime1
	cmp	CL,60			;Check minutes
	jae	CheckTime1
	cmp	DH,60			;Check seconds
	jae	CheckTime1
	cmp	DL,100			;Check 1/100's
CheckTime1:
	ret
EndProc CheckTime


;*********************************************************************
; Function:	Set the number of days in February at the specified year
; Input:	DS=DOSGROUP
;		CL = current year
; Output:	MONTAB = # of days in February at the specified year
; Effect:	ax
;
getFeb	proc
	assume ds:DOSGROUP,es:nothing,ss:nothing
; if leap year, set FEB = 29
;	   else set FEB = 28
	TEST	CL,3			;Check for leap year
	MOV	AL,28
	JNZ	SetFEB			;28 days if no leap year
	INC	AL			;Add leap day

SetFEB: MOV	[MONTAB+1],AL		;Store for February
	ret
getFeb	endp

;******* START OF SOURCE FILE SPECIFICATIONS ********************
;*
;* SUBROUTINE NAME:	Calc_Date
;*
;* FUNCTION:	Check if the date is legal. If not, return to caller.
;*		If the date is ok, calculate current date from 1980 1-1,
;*		and update Year, Day, Month, DayCnt, and WeekDay.
;*
;* ENTRY POINT: Calc_Date
;*    LINKAGE:	near
;*
;* Called from:
;*	w_SETDATETIME, DemSetDate
;*
;* INPUT:	CX:DX valid date
;*		 CX  =	Year  (1980 - 2099)
;*		 DH  =	Month  (1 - 12)
;*		 DL  =	Day  (1 - 31)
;*
;* EXIT-NORMAL:
;*	    AL = 0
;*	    DX = date count
;*
;* EXIT-ERROR:
;*	    AL = -1
;*
;* EFFECTS:	BX, CX, SI, DS, flags
;*
;* INTERNAL REFERENCES:
;*    ROUTINES:		DSUM, WKDAY, getFeb
;*
;* EXTERNAL REFERENCES:
;*    ROUTINES:
;*
;*********************** END OF SPECIFICATIONS *********************
;**********************START OF PSEUDO CODE ************************
;
; if year < 1980 or year > 2080 or month = 0 or day = 0 or month > 12
;    return with AL = -1
; if leap year, set FEB = 29
;	   else set FEB = 28
; if day is not correct for the specified month
;    process error
; save Year and Day
; calculate the total days from 1980 1-1, and save in DayCnt
; call WKDAY to get day of week, and save in WeekDay
; return with AL = 0
;
;*********************** END OF PSEUDO CODE ************************

Procedure	Calc_Date,<faronly,far16>
	assume ds:DOSGROUP,es:nothing,ss:nothing

	MOV	AL,-1			;Be ready to flag error

; if date is invalid
;    return with carry set
	SUB	CX,1980			;Fix bias in year
	jc	dodax			;Error if not big enough
	CMP	CX,99			;Year must be less than 2080
	JA	dodax
	OR	DH,DH
	jz	dodax
	OR	DL,DL
	jz	dodax			;Error if either month or day is 0
	CMP	DH,12			;Check against max. month
	JA	dodax

	DosContext <DS>, ax
	call	getFeb

; if day is not correct for the specified month
;    process error
	MOV	AL,DH
	MOV	BX,OFFSET DOSGROUP:MONTAB-1
	XLAT				;Look up days in month
	CMP	AL,DL
	MOV	AL,-1			;Restore error flag, just in case
	jc	dodax			; error if too many days

;******* START OF SOURCE FILE SPECIFICATIONS ********************
;*
;* SUBROUTINE NAME:	get_day_count
;*
;* FUNCTION:	Calculate current date from 1980 1-1,
;*		and update Year, Day, Month, DayCnt, and WeekDay.
;*
;* ENTRY POINT: get_day_count
;*    LINKAGE:	near
;*
;* Called from:
;*	ReadTime
;*
;* INPUT:	CX:DX valid date
;*		 CX  =	Year  (00 - 119) = the current year - 1980
;*		 DH  =	Month  (1 - 12)
;*		 DL  =	Day  (1 - 31)
;*		DS->DOSGROUP
;*
;* EXIT-NORMAL:
;*	    AL = 0
;*	    DX = date count
;*
;* EXIT-ERROR:
;*
;* EFFECTS:	AX, BX, CX, DX, SI, DS
;*
;* INTERNAL REFERENCES:
;*    ROUTINES:		DSUM, WKDAY
;*
;* EXTERNAL REFERENCES:
;*    ROUTINES:
;*
;*********************** END OF SPECIFICATIONS *********************
;**********************START OF PSEUDO CODE ************************
;
; save Year and Day
; calculate the total days from 1980 1-1, and save in DayCnt
; call WKDAY to get day of week, and save in WeekDay
; return with AL = 0
;
;*********************** END OF PSEUDO CODE ************************

Entry	get_day_count,near
	MOV	BYTE PTR [Year],CL

; WARNING!  Day and Month must be adjacently allocated
	MOV	WORD PTR [Day],DX	;Set both day and month

	SHR	CX,1			;CX=years/4
	SHR	CX,1

	MOV	AX,FOURYEARS		;BH=month BL=day
	MOV	BX,DX
	MUL	CX			;AX=days in 4 years group

	MOV	CL,BYTE PTR [Year]	;CL <= 3 years
	AND	CL,3

	MOV	SI,OFFSET DOSGROUP:YRTAB
	MOV	DX,AX
	SHL	CX,1			;Two entries per year, so double count
	CALL	DSUM			;Add up the days in each year

	MOV	CL,BH			;Month of year
	MOV	SI,OFFSET DOSGROUP:MONTAB
	DEC	CX			;Account for months starting with one
	CALL	DSUM			;Add up days in each month

	MOV	CL,BL			;Day of month
	DEC	CX			;Account for days starting with one
	ADD	DX,CX			;Add in to day total

	XOR	AL,AL			;Flag OK
	cmp	dx,[DayCnt]		; Is the same day count?
	je	dodax
	MOV	[DayCnt],DX

;*********************************************************************
; Calculate day of week based on DayCnt.
; The formula is ( total days from 1-1-1980 + 2 ) % 7.
;
	MOV	AX,dx
	XOR	DX,DX
	MOV	CX,7
	INC	AX
	INC	AX			;First day was Tuesday
	DIV	CX			;Compute day of week
	MOV	[WeekDay],DL

	XOR	AL,AL			; restore al
	mov	dx,[DayCnt]		; restore day count
dodax:
	RETP
EndProc Calc_Date


;******* START OF SOURCE FILE SPECIFICATIONS ********************
;*
;* SUBROUTINE NAME:	DemWriteClock
;*
;* FUNCTION:	Thunk for calling common_write_clock entry in
;*		w_SetDateTime from 32-bit code
;*
;* ENTRY POINT: DemWriteClock
;*    LINKAGE:	far16
;*
;* Called from:
;*	DemSetDate, DemSetTime
;*
;* INPUT:	DS:BX = address of transfer buffer
;*
;* EXIT-NORMAL:
;*	    AX = 0  set clock successfully
;*
;* EXIT-ERROR:
;*
;* EFFECTS:	BX, CX, DX, SI, DS, ES, flags
;*
;* INTERNAL REFERENCES:
;*    ROUTINES:		common_write_clock
;*
;* EXTERNAL REFERENCES:
;*    ROUTINES:
;*
;*********************** END OF SPECIFICATIONS *********************
;**********************START OF PSEUDO CODE ************************
;
; call common_write_clock
;
;*********************** END OF PSEUDO CODE ************************

Procedure   DemWriteClock,far16
	call	common_write_clock	    ; Call local routine
	RETP
EndProc	    DemWriteClock

CODE	ENDS
	END
