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 }