DOS keeps one timestamp per file, the date and time of the last write, in two 16-bit words of the directory entry. INT 21h AH=57h reads them (AL = 0) or replaces them (AL = 1) through an open handle; unlike INT 21h AH=43h it does not take a name.

The packed format

Time (CX) bitsHold
15-11hours, 0 to 23
10-5minutes, 0 to 59
4-0seconds ÷ 2, 0 to 29
Date (DX) bitsHold
15-9year − 1980, 0 to 127 (1980 to 2107)
8-5month, 1 to 12
4-0day, 1 to 31

So the time word is hours × 2048 + minutes × 32 + seconds ÷ 2, and the date word (year − 1980) × 512 + month × 32 + day. Seconds have a resolution of two: an odd second cannot be stored. The same two words appear in the record that find first / next fill in (INT 21h AH=4Eh: time at offset 16h, date at 18h). Unpacking is shifts and masks: SHR by 9 for the year, by 5 and AND 0Fh for the month, AND 1Fh for the day.

prints two lines: "packed: time 63C0 date 1ECF" and "1995-06-15 12:30:00"
MOV AH, 3Ch ; DOS: create TEST.TXT
XOR CX, CX ; CX = 0: normal attributes
MOV DX, OFFSET FNAME
INT 21h
CALL CHECK ; stop if CF = 1
MOV BX, AX ; BX = the handle
MOV AX, 5701h ; DOS: set the date and time
MOV CX, 63C0h ; 12:30:00 = 12 x 2048 + 30 x 32 + 0
MOV DX, 1ECFh ; 1995-06-15 = 15 x 512 + 6 x 32 + 15
INT 21h
CALL CHECK
MOV AH, 3Eh ; close: the stamp is written now
INT 21h
MOV AX, 3D00h ; open the file again
MOV DX, OFFSET FNAME
INT 21h
CALL CHECK
MOV BX, AX
MOV AX, 5700h ; DOS: get the date and time
INT 21h
CALL CHECK
MOV SI, DX ; SI = date
MOV DI, CX ; DI = time
MOV AH, 3Eh ; close the handle
INT 21h
MOV AH, 41h ; delete TEST.TXT
MOV DX, OFFSET FNAME
INT 21h
MOV DX, OFFSET M1
MOV AH, 09h ; DOS: print the string at DS:DX
INT 21h
MOV AX, DI
CALL PHEX
MOV DX, OFFSET M2
MOV AH, 09h ; DOS: print the string at DS:DX
INT 21h
MOV AX, SI
CALL PHEX
MOV DX, OFFSET CRLF
MOV AH, 09h ; DOS: print the string at DS:DX
INT 21h
MOV AX, SI ; year: bits 15-9, + 1980
MOV CL, 9
SHR AX, CL
ADD AX, 1980
CALL PDEC
MOV DL, "-"
MOV AH, 02h ; DOS: print DL
INT 21h
MOV AX, SI ; month: bits 8-5
MOV CL, 5
SHR AX, CL
AND AX, 0Fh
CALL P2
MOV DL, "-"
MOV AH, 02h ; DOS: print DL
INT 21h
MOV AX, SI ; day: bits 4-0
AND AX, 1Fh
CALL P2
MOV DL, " "
MOV AH, 02h ; DOS: print DL
INT 21h
MOV AX, DI ; hours: bits 15-11
MOV CL, 11
SHR AX, CL
CALL P2
MOV DL, ":"
MOV AH, 02h ; DOS: print DL
INT 21h
MOV AX, DI ; minutes: bits 10-5
MOV CL, 5
SHR AX, CL
AND AX, 3Fh
CALL P2
MOV DL, ":"
MOV AH, 02h ; DOS: print DL
INT 21h
MOV AX, DI ; seconds: bits 4-0, times 2
AND AX, 1Fh
SHL AX, 1
CALL P2
MOV DX, OFFSET CRLF
MOV AH, 09h ; DOS: print the string at DS:DX
INT 21h
FNAME DB "TEST.TXT", 0
M1 DB "packed: time $"
M2 DB " date $"
CRLF DB 13, 10, "$"
CHECK PROC NEAR ; CF = 1: print the error code in AL and exit
JNC CHECK1
PUSH AX
MOV DX, OFFSET FAILMSG
MOV AH, 09h
INT 21h
POP AX
CALL PHEXB
MOV AX, 4C01h ; exit with code 1
INT 21h
CHECK1:
RET
CHECK ENDP
FAILMSG DB "failed: error $"
PHEXB PROC NEAR ; print AL as two hex digits
PUSH CX
PUSH DX
MOV DH, AL ; keep the byte
MOV CL, 4
SHR AL, CL ; the high digit first
MOV CX, 2
PHEXB1:
AND AL, 0Fh
ADD AL, "0"
CMP AL, "9"
JBE PHEXB2
ADD AL, 7 ; 10..15 -> "A".."F"
PHEXB2:
MOV DL, AL
MOV AH, 02h ; DOS: print DL
INT 21h
MOV AL, DH ; then the low digit
LOOP PHEXB1
POP DX
POP CX
RET
PHEXB ENDP
PHEX PROC NEAR ; print AX as four hex digits
PUSH AX
MOV AL, AH
CALL PHEXB
POP AX
CALL PHEXB
RET
PHEX ENDP
PDEC PROC NEAR ; print AX in decimal
PUSH BX
PUSH CX
PUSH DX
MOV BX, 10
XOR CX, CX ; CX = digits pushed
PDEC1:
XOR DX, DX
DIV BX ; AX = AX / 10, DX = the last digit
PUSH DX
INC CX
OR AX, AX
JNZ PDEC1
PDEC2:
POP DX ; the digits come back first to last
ADD DL, "0"
MOV AH, 02h
INT 21h
LOOP PDEC2
POP DX
POP CX
POP BX
RET
PDEC ENDP
P2 PROC NEAR ; print AX (0..99) as two digits
MOV DL, 10
DIV DL ; AL = tens, AH = ones
ADD AX, 3030h ; both to characters
PUSH AX
MOV DL, AL
MOV AH, 02h
INT 21h
POP AX
MOV DL, AH
MOV AH, 02h
INT 21h
RET
P2 ENDP

When a set stamp sticks

DOS keeps the new stamp with the open file and writes it to the directory when the file is closed. A write through the handle after the set replaces it with the current date and time again, so set the stamp last, after all writes. Archivers and copy programs use this to give files their original dates.

In DOSBox-X, on a host folder, the stamp becomes the host file's modification time when the file is closed, and a get always reads the host file. A value just set therefore shows up only after closing and reopening, which is what the example does.