The behavior is undefined because you frag the current DS which is basically killing the memory. You are also fragging your DS across interrupt 21h. DOS functions often change the DS to the internal DOS DS and they do not restore the DS at times - even though they should. So preserve the DS first before you change it to your data segment, and preserve it across interrupt 21h and then restore it likewise.
And yes you do have to move the value into AX first and then into DS. Placing values in segment registers is not allowed in x86 asm. Intel has an opcode which does this quite nicely in fewer cycles, however it is normally used to load a full pointer. Since your DS lies well within your 64Kb and since everything is near you wouldn't need to load a full pointer and probably are not allowed to. Also the LES and LDS opcodes are usually used in preparation for a copy from ES:DI to DS:SI so you are doing that part correctly.
I would say that if you add this it might do what you want. This is perhaps why the memory is not reading out correctly. But this is only conjecture as I see nothing else that sticks out in my mind as being the culprit. I'm too lazy right now to get my DOS 6.20 tech ref out but I seem to remember that you must preserve DS prior to invoking DOS interrupt 21h because some of the DOS functions alter the DS and so when those functions return, the DS no longer points to what you think it does.
This code will preserve the previous DS, your DS, call the DOS function and no matter if DOS changes the DS to it's DS, yours and the previous will be restored upon returning from DOS.
Now it is true that at first glance you might just say well my DS is what's in AX. However, interrupt 21h will alter AX and therefore if you do not preserve DS prior to the interrupt, you have to way to restore it to what it was prior to the interrupt.
EDIT: I've also noticed that you move a 16-bit value into AX and then an 8-bit value into AH. However the lower 8 bits of AX are still unaltered when you invoke interrupt 21h. Perhaps after doing this:
You should do a xor ax,ax to zero out ax completely. Even if the DOS function only uses 8 bits, I would ensure I didn't send it anything it didn't need. I don't think this would cause any issues, but it might.
Always preserve segment registers prior to changing their values, either through immediate values, pointers, function calls, or interrupt service routines. This will ensure that the segment registers are indeed pointing to what you want them to be pointing to.
DAYLEN EQU 10
dow db 'Sunday, '
db 'Monday, '
db 'Tuesday, '
db 'Thursday, '
db 'Friday, '
db 'Saturday, '
dowlen db 7,7,8,10,9,7,9
datestr db 30 dup('$')
main PROC ;everything near for now
mov ax, @data ;don't to do need this in a86 typically
push ds ;preserve previous DS
mov ds, ax
mov ah, 2ah
push ds ;preserve our current DS across int 21h
int 21h ;it is possible our DS will be fragged here and/or AX
pop ds ;Restore our DS
pop ds ;Restore DS to entry value
mov ax, 4c00h; Dos terminate program
Otherwise in DOS, you could cause major problems since there is NO protection mechanism. You could point the DS to the FORMAT function in DOS or anything else on the system. It spells disaster.
So the NOPs might not be from your code at all. Also are you sure it's just not tracing into the interrupt 21h service routine code? That would seem logical since that is what you called and that would alter CS:IP. Keep tracing it and it should get back to your code. It wouldn't be possible to check the address of the 21h sub-function 2Ah address since that is internal to the 21h handler, but you could trace it and read the 21h handler code. I'm sure you are just reading the very start of the Microsoft interrupt 21h handler code. No telling how large it is, but eventually as I said it should come back to your code.
If you ran this code in DOS under XP, it would crash. If you called this function from C in it's current state, it would crash.
And as always if you wish to use a function in asm that uses params and you want it to be callable from C, use this format.
TASM has a nice feature macro that allows you to specify arguments, local variables, etc, so you don't have to refer to them by [bp+4],[bp+8], etc. I'm sure MASM has a similar macro.
Gawd I love assembly!!
Hope this information helps.