INT 21h AH=2Ah returns the date DOS keeps: the year as a full number in CX (2026, not 26), the month in DH and the day in DL, all binary rather than BCD, and the day of the week in AL, counted from 0 for Sunday. DOS works out the weekday itself; it is ready for picking a name from a table.
MOV AH, 2Ah ; DOS: get the dateINT 21h ; CX = year, DH = month, DL = day, AL = weekdayPUSH AX ; keep the weekdayPUSH DX ; and the month and dayMOV AX, CXCALL PDEC1 ; the yearMOV DL, "-"MOV AH, 02h ; DOS: print the character in DLINT 21hPOP DXPUSH DXMOV AL, DHCALL P2D1 ; the monthMOV DL, "-"MOV AH, 02h ; DOS: print the character in DLINT 21hPOP DXMOV AL, DLCALL P2D1 ; the dayMOV DL, " "MOV AH, 02h ; DOS: print the character in DLINT 21hPOP AX ; AL = the weekday, 0 = SundayMOV AH, 3MUL AH ; AX = weekday * 3: its name in the tableADD AX, OFFSET DAYS1MOV DX, AXMOV CX, 3 ; three lettersMOV BX, 1 ; to standard outputMOV AH, 40h ; DOS: write to a handleINT 21hDAYS1 DB "SunMonTueWedThuFriSat"PDEC1 PROC NEAR ; print AX in decimalMOV BX, 10XOR CX, CX ; digits so farPD1:XOR DX, DXDIV BX ; AX = AX / 10, DX = the last digitPUSH DXINC CXTEST AX, AXJNZ PD1PD2:POP DX ; the digits come back first to lastADD DL, "0"MOV AH, 02hINT 21hLOOP PD2RETPDEC1 ENDPP2D1 PROC NEAR ; print AL (0-99) as two digitsAAM ; AH = AL / 10, AL = AL mod 10ADD AX, 3030h ; both digits to charactersPUSH AXMOV DL, AH ; the tensMOV AH, 02hINT 21hPOP DX ; DL = the unitsMOV AH, 02hINT 21hRETP2D1 ENDP
The year goes through a small decimal printer (divide by 10, push the digits, pop them in reverse), the month and day through AAM, which splits a value below 100 into its two decimal digits. The weekday's name is written with INT 21h AH=40h, which takes a length, since the table has no "$" between the names.
Setting the date
INT 21h AH=2Bh takes the date in the same registers. It checks them first: a month outside 1–12, a day the month does not have (30 February, 31 April, 29 February outside a leap year) or a year before 1980 gives AL = FFh and changes nothing; AL = 00h means the date is set. MS-DOS also refuses years after 2099. DOS 3.3 and later write the new date into the CMOS clock of an AT as well, so it survives a reboot.
MOV AH, 2Ah ; DOS: get the dateINT 21hPUSH DX ; today, for laterPUSH CXMOV DX, 021Eh ; DH = 2, DL = 30: 30 FebruaryMOV AH, 2Bh ; DOS: set the dateINT 21h ; AL = FFh: refused, nothing changedCALL HEX2MOV DL, " "MOV AH, 02h ; DOS: print the character in DLINT 21hPOP CX ; today againPOP DXMOV AH, 2BhINT 21h ; the date it already was: AL = 00hCALL HEX2HEX2 PROC NEAR ; print AL as two hex digitsPUSH AXMOV CL, 4SHR AL, CL ; the high digit firstCALL NIB1POP AXAND AL, 0Fh ; then the low oneCALL NIB1RETHEX2 ENDPNIB1 PROC NEAR ; print AL (0-15) as one hex digitADD AL, "0"CMP AL, "9"JBE NIB2ADD AL, 7 ; 10-15 become "A"-"F"NIB2:MOV DL, ALMOV AH, 02hINT 21hRETNIB1 ENDP
The example leaves the clock alone: it offers a date that does not exist, which is refused, and then sets today's date again. In DOSBox-X a successful set of the date or the time also switches off the "synchronize time" option, which keeps the clock in step with the host, if it was on.
Pitfalls
The date advances only while something reads the clock through DOS. The BIOS counts timer ticks and sets a "midnight passed" flag when the count wraps; DOS adds a day when it sees the flag. A program that reads the tick count with INT 1Ah AH=00h clears that flag itself, and DOS misses the change of day. The DOS date and the CMOS clock (INT 1Ah AH=04h) can therefore disagree: DOS reads the CMOS once at boot and counts on its own after that. A program reading the date and then the time just before midnight can get the old day with the new time; read the date again after the time and start over if it changed.