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.

prints the box's date, for example: 2026-10-23 Fri
MOV AH, 2Ah ; DOS: get the date
INT 21h ; CX = year, DH = month, DL = day, AL = weekday
PUSH AX ; keep the weekday
PUSH DX ; and the month and day
MOV AX, CX
CALL PDEC1 ; the year
MOV DL, "-"
MOV AH, 02h ; DOS: print the character in DL
INT 21h
POP DX
PUSH DX
MOV AL, DH
CALL P2D1 ; the month
MOV DL, "-"
MOV AH, 02h ; DOS: print the character in DL
INT 21h
POP DX
MOV AL, DL
CALL P2D1 ; the day
MOV DL, " "
MOV AH, 02h ; DOS: print the character in DL
INT 21h
POP AX ; AL = the weekday, 0 = Sunday
MOV AH, 3
MUL AH ; AX = weekday * 3: its name in the table
ADD AX, OFFSET DAYS1
MOV DX, AX
MOV CX, 3 ; three letters
MOV BX, 1 ; to standard output
MOV AH, 40h ; DOS: write to a handle
INT 21h
DAYS1 DB "SunMonTueWedThuFriSat"
PDEC1 PROC NEAR ; print AX in decimal
MOV BX, 10
XOR CX, CX ; digits so far
PD1:
XOR DX, DX
DIV BX ; AX = AX / 10, DX = the last digit
PUSH DX
INC CX
TEST AX, AX
JNZ PD1
PD2:
POP DX ; the digits come back first to last
ADD DL, "0"
MOV AH, 02h
INT 21h
LOOP PD2
RET
PDEC1 ENDP
P2D1 PROC NEAR ; print AL (0-99) as two digits
AAM ; AH = AL / 10, AL = AL mod 10
ADD AX, 3030h ; both digits to characters
PUSH AX
MOV DL, AH ; the tens
MOV AH, 02h
INT 21h
POP DX ; DL = the units
MOV AH, 02h
INT 21h
RET
P2D1 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.

prints: FF 00 (30 February refused, then today set to itself)
MOV AH, 2Ah ; DOS: get the date
INT 21h
PUSH DX ; today, for later
PUSH CX
MOV DX, 021Eh ; DH = 2, DL = 30: 30 February
MOV AH, 2Bh ; DOS: set the date
INT 21h ; AL = FFh: refused, nothing changed
CALL HEX2
MOV DL, " "
MOV AH, 02h ; DOS: print the character in DL
INT 21h
POP CX ; today again
POP DX
MOV AH, 2Bh
INT 21h ; the date it already was: AL = 00h
CALL HEX2
HEX2 PROC NEAR ; print AL as two hex digits
PUSH AX
MOV CL, 4
SHR AL, CL ; the high digit first
CALL NIB1
POP AX
AND AL, 0Fh ; then the low one
CALL NIB1
RET
HEX2 ENDP
NIB1 PROC NEAR ; print AL (0-15) as one hex digit
ADD AL, "0"
CMP AL, "9"
JBE NIB2
ADD AL, 7 ; 10-15 become "A"-"F"
NIB2:
MOV DL, AL
MOV AH, 02h
INT 21h
RET
NIB1 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.