Contributor: WILBERT VAN LEIJEN       
{ COUNTRY.PAS -- Going native with Dos.  Do not use under DOS 2.xx.
  Written by Wilbert van Leijen and released into the Public Domain }
Unit Country;
Interface
uses Dos;
Type
  DelimType    = Record
                   thousands,
                   decimal,
                   date,
                   time        : Array[0..1] of Char;
                 end;
  CurrType     = (leads,               { symbol precedes value }
                  trails,              { value precedes symbol }
                  leads_,              { symbol, space, value }
                  _trails,             { value, space, symbol }
                  replace);            { replaced }
  CountryType  = Record
                   DateFormat  : Word;       { 0: USA, 1: Europe, 2: Japan }
                   CurrSymbol  : Array[0..4] of Char;
                   Delimiter   : DelimType;  { Separators }
                   CurrFormat  : CurrType;   { Way currency is formatted }
                   CurrDigits  : Byte;       { Digits in currency }
                   Clock24hrs  : Boolean;    { True if 24-hour clock }
                   CaseMapCall : Procedure;  { Lookup table for ASCII ò $80 }
                   DataListSep : Array[0..1] of Char;
                   CountryCode : Word;
                 end;
  UpCaseType   = Function(c : Char) : Char;
  UpCaseStrType = Procedure(Var s : String);
Var
  UpCase       : UpCaseType;       { To be determined at runtime }
  UpCaseStr    : UpCaseStrType;
  CountryOk    : Boolean;          { Could determine country code flag }
  CountryRec   : CountryType;
Procedure GetSysTime(Var Today : DateTime);
Procedure SetSysTime(Today : DateTime);
Function DateString(FileStamp : DateTime) : String;
Function TimeString(FileStamp : DateTime) : String;
Implementation
{$R-,S-,V- }
{ Country dependent character capitalisation for DOS 3 }
Function UpCase3(c : Char) : Char; Far; Assembler;
ASM
        MOV    AL, c
        CMP    AL, 'a'
        JB     @2
        CMP    AL, 'z'
        JA     @1
        AND    AL, 11011111b
        JMP    @2
@1:     CMP    AL, 80h
        JB     @2
        CALL   [CountryRec.CaseMapCall]
@2:
end;  { UpCase3 }
{ Country dependent string capitalisation for DOS 3 }
Procedure UpCaseStr3(Var s : String); Far; Assembler;
ASM
        CLD
        LES    DI, s
        XOR    AX, AX
        MOV    AL, ES:[DI]
        STOSB
        XCHG   AX, CX
        JCXZ   @4
@1:     MOV    AL, ES:[DI]
        CMP    AL, 'a'
        JB     @3
        CMP    AL, 'z'
        JA     @2
        AND    AL, 11011111b
        JMP    @3
@2:     CMP    AL, 80h
        JB     @3
        CALL   [CountryRec.CaseMapCall]
@3:     STOSB
        LOOP   @1
@4:
end;  { UpCaseStr3 }
{ Country dependent character capitalisation for DOS 4+ }
Function UpCase4(c : Char) : Char; Far; Assembler;
ASM
        MOV    DL, c
        MOV    AX, 6520h
        INT    21h
        MOV    AL, DL
end;  { UpCase4 }
{ Country dependent string capitalisation for DOS 4+ }
Procedure UpCaseStr4(Var s : String); Far; Assembler;
ASM
        PUSH   DS
        CLD
        XOR    AX, AX
        LDS    SI, s
        LODSB
        XCHG   AX, CX
        JCXZ   @1
        MOV    DX, SI
        MOV    AX, 6521h
        INT    21h
@1:     POP    DS
end;  { UpCaseStr4 }
{ Return system time in Today }
Procedure GetSysTime(Var Today : DateTime); Assembler;
ASM
        LES    DI, Today
        CLD
        MOV    AH, 2Ah
        INT    21h
        XCHG   AX, CX          { year }
        STOSW
        XOR    AH, AH
        MOV    AL, DH          { month }
        STOSW
        MOV    AL, DL          { day }
        STOSW
        MOV    AH, 2Ch
        INT    21h
        XOR    AH, AH
        MOV    AL, CH          { hours }
        STOSW
        MOV    AL, CL          { min }
        STOSW
        MOV    AL, DH          { seconds }
        STOSW
end;  { GetSysTime }
{ Set system time }
Procedure SetSysTime(Today : DateTime); Assembler;
ASM
        PUSH   DS
        CLD
        LDS    SI, Today
        LODSW
        MOV    CX, AX          { year }
        LODSW
        MOV    DH, AL          { month }
        LODSW
        MOV    DL, AL          { day }
        MOV    AH, 2Bh
        INT    21h
        LODSW                  
        MOV    CH, AL          { hour }
        LODSW
        MOV    CL, AL          { minutes }
        LODSW
        MOV    DH, AL          { seconds }
        XOR    DL, DL
        MOV    AH, 2Dh
        INT    21h
        POP    DS
end;  { SetSysTime }
{ Convert a binary number to an unpacked decimal
  On entry:  AL <-- number ó 99
  On exit:   AX --> ASCII representation }
Procedure UnpackNumber; Assembler;
ASM
        AAM
        XCHG    AH, AL
        ADD     AX, '00'
end;  { UnpackNumber }
Function DateString(FileStamp : DateTime) : String; Assembler;
ASM
        PUSH   DS
        CLD
  { Set string length }
        LES    DI, @Result
        MOV    AL, 8
        STOSB
  { Store year, month and day in registers }
        LDS    SI, FileStamp
        LODSW
        SUB    AX, 1900
        CALL   UnpackNumber
        XCHG   AX, BX              { yy -> BX }
        LODSW
        CALL   UnpackNumber
        XCHG   AX, CX              { mm -> CX }
        LODSW
        CALL   UnpackNumber
        XCHG   AX, DX              { dd -> DX }
  {  Case date format of
       0 : USA standard       mm:dd:yy
       1 : Europe standard    dd:mm:yy
       2 : Japan standard     yy:mm:dd }
        POP    DS
        MOV    AL, Byte Ptr [CountryRec.DateFormat]
        OR     AL, AL
        JZ     @1
        DEC    AL
        JZ     @2
  { Japan }
        PUSH   DX
        PUSH   CX
        PUSH   BX
        JMP    @3
  { USA }
@1:     PUSH   BX
        PUSH   DX
        PUSH   CX
        JMP    @3
  { Europe }
@2:     PUSH   BX
        PUSH   CX
        PUSH   DX
  { Remove leading zero }
@3:     POP    AX
        CMP    AL, '0'
        JNE    @4
        MOV    AL, ' '
@4:     MOV    CL, Byte Ptr [CountryRec.Delimiter.date]
        STOSW
        MOV    AL, CL
        STOSB
        POP    AX
        STOSW
        MOV    AL, CL
        STOSB
        POP    AX
        STOSW
end;  { DateString }
Function TimeString(FileStamp : DateTime) : String; Assembler;
ASM
        PUSH   DS
        CLD
        MOV    BL, [CountryRec.Clock24Hrs]
        MOV    DX, [CountryRec.Delimiter.time]
        LDS    SI, FileStamp
        LES    DI, @Result
  { Set string length }
        MOV    AL, 5
        STOSB
  { Advance string index of FileStamp to hour field }
        ADD    SI, 6
        LODSW
  { Query time format }
        OR     BL, BL
        JNZ    @2
  { a.m. / p.m. clock format, set string length to 6 }
        INC    Byte Ptr ES:[DI-1]
        MOV    BL, 'a'
        CMP    AL, 12
        JBE    @1
        SUB    AL, 12
        MOV    BL, 'p'
@1:     MOV    Byte Ptr ES:[DI+5], BL
  { Convert to ASCII and remove leading zero }
@2:     CALL   UnpackNumber
        CMP    AL, '0'
        JNE    @3
        MOV    AL, ' '
@3:     STOSW
  { Write time separator }
        XCHG   AX, DX
        STOSB
  { Store minutes in string }
        LODSW
        CALL   UnpackNumber
        STOSW
        POP    DS
end;  { TimeString }
Begin  { Country }
ASM
   { Exit if Dos version < 3.0 }
        MOV    AH, 30h
        INT    21h
        CMP    AL, 3
        JB     @3
        JA     @1
   { Initialise pointers to DOS 3 capitalisation routines }
        MOV    Word Ptr [UpCase], Offset UpCase3
        MOV    Word Ptr [UpCaseStr], Offset UpCaseStr3
        JMP    @2
   { Initialise pointers to DOS 4 (or later) capitalisation routines }
@1:     MOV    Word Ptr [UpCase], Offset UpCase4
        MOV    Word Ptr [UpCaseStr], Offset UpCaseStr4
@2:     MOV    Word Ptr [UpCase+2], CS
        MOV    Word Ptr [UpCaseStr+2], CS
   { Call Dos 'Get country dependent information' function }
        MOV    AX, 3800h
        MOV    DX, Offset [CountryRec]
        INT    21h
        JC     @3
   { Add country code to the structure }
        MOV    [CountryRec.CountryCode], BX
        MOV    [CountryOk], True
        JMP    @4
@3:     MOV    [CountryOk], False
@4:
end;
end.  { Country }