
; Time and date routines for english territory.

 MACRO
        CDATT   $mnemonic, $stackoffset, $digits
        ASSERT  (:LEN: "$mnemonic") = 2
        ASSERT  ((CDAT$mnemonic-(CDATBranch+8)) :AND: &FFFFFC03) = 0
        ASSERT  ($stackoffset >=0) :LAND: ($stackoffset < 64)
        LCLA    digits
        [       "$digits"=""
digits  SETA    2
        |
digits  SETA    $digits
        ]
        ASSERT  (digits >= 1) :LAND: (digits <= 3)

        DCB     digits :OR: ($stackoffset :SHL: 2)
        DCB     (CDAT$mnemonic-(CDATBranch+8)) :SHR: 2
        =       "$mnemonic"
        MEND

tickspersecond  * 100
ticksperminute  * tickspersecond * 60
ticksperhour    * ticksperminute * 60
ticksperday     * ticksperhour   * 24
ticksperyear    * ticksperday    * 365  ; &BBF81E00


;--------------------------------------------------------------
; MessageLookup
; In: 
;     R4 -> token
; Out:
;     R4 -> Translated message.
;
MessageLookup

        Push    "r0-r7,LR"
        
        BL      open_messages_file
        Pull    "r0-r7,pc",VS
        ADR     R0,message_file_block+4
        MOV     R1,R4
        ADR     R2,scratch_buffer
        MOV     R3,#&100
        MOV     R4,#0
        MOV     R5,#0
        MOV     R6,#0
        MOV     R7,#0
        SWI     XMessageTrans_Lookup
        STRVS   r0,[sp]
        Pull    "r0-r7,PC",VS

        ADR     R4,scratch_buffer
        STR     R4,[SP,#4*4]
        
        Pull    "r0-r7,PC"

;******************************************************************************************
; GetTimeValues
; In:
;     R1 -> UTC Time value
; Out:
;     R0   =   Day of month       ; All values are LOCAL time.
;     R1   =   Month
;     R2   =   Week of year
;     R3   =   Day of year
;     R4   =   Day of week
;     R5   =   Year (HI)
;     R6   =   Year (LO)
;     R7   =   Hours
;     R8   =   Minutes
;     R10  =   Seconds
;     R9   =   Centiseconds
GetTimeValues
        Push    "R14"
        MOV     R0,R1

        LDRB    R4, [R0, #0]            ; read the 5 byte value to convert
        LDRB    R5, [R0, #1]
        ORR     R4, R4, R5, LSL #8
        LDRB    R5, [R0, #2]
        ORR     R4, R4, R5, LSL #16
        LDRB    R5, [R0, #3]
        ORR     R4, R4, R5, LSL #24     ; R4 contains bottom 4 bytes
        LDRB    R5, [R0, #4]            ; R5 contains 5th byte

; Add current time offset from GMT

        Push    "R1"
        SWI     XTerritory_ReadCurrentTimeZone
        ADDVS   SP,SP,#4
        Pull    "PC",VS
        STR     R0,TimeZoneName
        MOV     R0,R1,ASR #31           ; Sign extend to 2 words. 
        ADDS    R4,R4,R1
        ADC     R5,R5,R0
        Pull    "R1"

        MOV     R6, R4, LSR #8
        ORR     R6, R6, R5, LSL #24     ; R6 := centiseconds DIV 256
        LDR     R7, =ticksperday/256    ; (ticksperday is a multiple of 256)
        DivRem  R8, R6, R7, R9          ; R8 = number of days since 1900
        AND     R4, R4, #&FF            ; R4 := centiseconds MOD 256
        ORR     R6, R4, R6, LSL #8      ; R6 := centiseconds today

; first work out bits from R6

        LDR     R7, =ticksperhour
        DivRem  R4, R6, R7, R9          ; R4 := hours
        LDR     R7, =ticksperminute
        DivRem  R5, R6, R7, R9          ; R5 := minutes
        LDR     R7, =tickspersecond
        DivRem  R10, R6, R7, R9         ; R10 := seconds
                                        ; R6 := centiseconds
        Push    "R4,R5,R6,R10"

; now work out bits from R8

        ADD     R11, R8, #1             ; R11 := fudged copy of days since 1900
        MOV     R5, #7
        DivRem  R6, R11, R5, R7         ; R11 := days MOD 7 (ie day of week)
        ADD     R11, R11, #1            ; make in range 1..7

        MOV     R5, #00                 ; units of years = 00
        MOV     R4, #19                 ; hundreds of years = 19
10
        MOV     R6, #0                  ; 0 if not a leap year
        TST     R5, #3                  ; if not divis by 4 then not leap year
        BNE     %FT30
        TEQ     R5, #0                  ; elif not divis by 100 then leap
        BNE     %FT20
        TST     R4, #3                  ; elif not divis by 400 then not leap
        BNE     %FT30
20
        MOV     R6, #1                  ; 1 if leap year
30
        LDR     R7, =365                ; normally take off 365 days per year
        ADD     R7, R7, R6              ; extra day if leap year

        SUBS    R8, R8, R7              ; try taking off 365 or 366 days
        BCC     %FT40                   ; [failed the subtract]
        ADD     R5, R5, #1              ; increment year if successful
        CMP     R5, #100
        MOVEQ   R5, #0
        ADDEQ   R4, R4, #1
        B       %BT10

40
        ADD     R8, R8, R7              ; add back on if we couldn't do it
                                        ; R8 is day of year (0..365)
        Push    "R4,R5"                 ; push yearhi, yearlo

        ADD     R7, R8, #1              ; R7 = day number in range 1-366
        Push    "R7, R11"               ; push d-o-y, d-o-w

; now compute week number

        SUBS    R7, R11, #2             ; dow (Sun=-1, Mon=0,... ,Sat=5)
        ADDCC   R7, R7, #7              ; dow (Mon=0,... ,Sun=6)
        SUB     R7, R8, R7              ; day-of-year no. of start of week

        ADD     R7, R7, #6              ; work out week number as if
                                        ; 1st part week is week 0
        MOV     R10, #7
        DivRem  R11, R7, R10, R9        ; R11 = week number (0..53)
                                        ; R7 (remainder) indicates dayofweek
                                        ; of start of year (Mon=6,Tue=5..Sun=0)
        CMP     R7, #3                  ; if year starts on Mon..Thu
        ADDCS   R11, R11, #1            ; then 1st part week is week 1

        TEQ     R7, #4                  ; if a Wednesday
        TEQEQ   R6, #1                  ; in a leap year
        TEQNE   R7, #3                  ; or a Thursday in any year
        MOVEQ   R9, #53                 ; then allow 53 weeks in year
        MOVNE   R9, #52                 ; else only 52 weeks
        CMP     R11, R9                 ; if more than this
        MOVHI   R11, #1                 ; then week 1 of next year

        TEQ     R11, #0                 ; if not week 0
        BNE     %FT45                   ; then finished

        CMP     R7, #1                  ; HI => Fri, EQ => Sat, CC => Sun
        ADC     R11, R11, #52           ; Fri => 53, Sun => 52, Sat => dunno
        BNE     %FT45

; 1st day of year is Saturday
; if previous year was leap, then is week 53, else is week 52

        SUBS    R5, R5, #1              ; decrement year
        MOVCC   R5, #99
        SUBCC   R4, R4, #1

        TST     R5, #3                  ; if not divis by 4 then not leap year
        BNE     %FT42
        TEQ     R5, #0                  ; elif not divis by 100 then leap
        BNE     %FT45
        TST     R4, #3                  ; elif not divis by 400 then not leap
42
        MOVNE   R11, #52                ; not leap, so must be week 52
45
        Push    "R11"                   ; push weekno

        ADRL    R7, MonthLengths+1      ; R7 -> Jan(31) (Feb is stored as 29)
        EOR     R6, R6, #1              ; R6 = 1 <=> not a leap year
        MOV     R9, #1                  ; month number (1 = Jan)
50
        LDRB    R10, [R7], #1           ; get next month
        CMP     R9, #2                  ; if we're trying for Feb
        SUBEQ   R10, R10, R6            ; and not leap then subtract a day
        SUBS    R8, R8, R10             ; subtract off month value
        ADDCS   R9, R9, #1              ; if successful month +:= 1
        BCS     %BT50

        ADD     R8, R8, R10             ; add the month back on if we failed
        ADD     R8, R8, #1              ; day of month in range 1..31

        Push    "R8,R9"                 ; push d-o-m, month

        Pull    "R0-R10,PC",,^


;---------------------------------------------------------------
; Read Time in local format.                                
;ConvertDateAndTime
;In:
; R1 = Pointer to 5 byte UTC time block
; R2 = Pointer to buffer for resulting string
; R3 = Size of buffer
; R4 = Pointer to format string (null terminated)
;Out:
; R0 = Pointer to buffer (R2 on entry)
; R1 = Pointer to terminating 0 in buffer.
; R2 = Number of bytes free in buffer.
; R3 = R4 on entry. 
; R4 - Preserved.
;           
; The fields for the above are now:
;                       
;  CS - Centi-seconds
;  SE - Seconds
;  MI - Minutes
;  12 - Hours in 12 hour format
;  24 - Hours in 24 hour format
;  AM - AM or PM indicator in local language (may NOT be 2 characters)
;  PM - AM or PM indicator in local language (may NOT be 2 characters)
;  
;  WE - Weekday in full in local language.
;  W3 - Short form of weekday name (May not be 3 characters)
;  WN - Weekday as a number
;  
;  DY - Day of the month (may not be 2 characters, and may not even be
;                         numeric)
;  ST - Ordinal pre/suffix in local language (may be null)
;  MO - Month name in full
;  M3 - Short form of month name (May not be 3 characters)
;  MN - Month number.
;  CE - Century
;  YR - Year within century
;  WK - Week of the year (As calculated in the territory, may not be Mon to
;       Sun)
;  DN - Day of the year.
;  TZ - TIme zone currently in effect.
;  0    Insert an ASCII 0 byte
;  %    Insert a '%'
ConvertDateAndTime ROUT

        Push    "R1-R11, R14"

        BL      GetTimeValues
        Pull    "R1-R11,PC",VS
        Push    "r0-r10"
        ADD     R14,SP,#4*11
        LDMIA   R14,{r0-r4}             ; R0 - R4 are R1..R5 on entry.

CDATMainLoop
        SUBS    R2, R2, #1              ; decrement buffer size
        BCC     CDATBufferError         ; error: buffer too small
        LDRB    R0, [R3], #1            ; get byte from format string
        TEQ     R0, #"%"                ; is it a escape sequence ?
        BEQ     %FT65                   ; yes, then do specially
        STRB    R0, [R1], #1            ; no, then store in output buffer
        TEQ     R0, #0                  ; end of format string ?
        BNE     CDATMainLoop            ; no, then loop

; end of format string, so finish

        SUB     R1, R1, #1              ; make R1 point to 0 byte

        ADD     R13, R13, #11*4         ; junk dom,month,woy,doy
                                        ; junk dow,yearhi,yearlo,hours
                                        ; junk mins,secs,centisecs
        ADD     SP,SP,#4                ; junk input r1
        Pull    "R0,R3"                 ; R0 := input R2 , junk input R3
        LDR     R3,[SP]
        Pull    "R4-R11, PC"            ; restore other registers

; come here if run out of buffer space for string

CDATBufferError
        ADR     R0, ErrorBlock_CDATBufferOverflow ; point R0 to error block
CDATError
        ADD     R13, R13, #11*4         ; junk dom,month,woy,doy
                                        ; junk dow,yearhi,yearlo,hours
                                        ; junk mins,secs,centisecs
        Pull    "R1-R11,R14"            ; restore all registers apart from R0
        B       message_errorlookup

        MakeInternatErrorBlock CDATBufferOverflow, BOvFlow

        MakeInternatErrorBlock CDATBadField, BadFld

; process "%" escape sequences

65
        LDRB    R0, [R3], #1            ; get next character
        TEQ     R0, #"0"                ; if char = "0"
        MOVEQ   R0, #0                  ; convert to <0>
        TEQNE   R0, #"%"                ; or if char = "%", store "%"
        STREQB  R0, [R1], #1            ; store <0> or "%"
        BEQ     CDATMainLoop            ; and loop

        UpperCase R0, R5                ; convert character to upper case
        EORS    R7, R0, #"Z"            ; zero if we have "Z" specifier
        BNE     %FT67                   ; not "Z"

        LDRB    R0, [R3], #1            ; is "Z", so get another char
        UpperCase R0, R5                ; convert character to upper case
67
        TEQ     R0, #0                  ; are they a wally!
        BEQ     CDATFieldError          ; yes, then bomb out (avoid data abort)

        LDRB    R4, [R3], #1            ; get next char
        UpperCase R4, R5                ; and convert that to upper case

        ORR     R0, R0, R4, LSL #8      ; 2 chars in bottom two bytes

        ADR     R4, CDATEscTab          ; point to table
        ADR     R5, CDATEscTabEnd       ; end of table
70
        TEQ     R4, R5                  ; are we at end of table
CDATFieldError
        ADREQ   R0, ErrorBlock_CDATBadField
        BEQ     CDATError               ; yes, then invalid escape sequence
        LDR     R6, [R4], #4            ; chars in top two bytes
        EOR     R6, R6, R0, LSL #16     ; if match, then must be < 1:SHL:16
        CMP     R6, #(1 :SHL: 16)
        BCS     %BT70                   ; no match, so loop

; found mnemonic match

        AND     R0, R6, #&03            ; R0 = number of digits to print
        AND     R4, R6, #&FC            ; R4 = stack offset
        LDR     R4, [R13, R4]           ; R4 = data item
        MOV     R6, R6, LSR #8          ; R6 = code offset in words
CDATBranch
        ADD     PC, PC, R6, LSL #2      ; go to routine

CDATEscTab
        CDATT   DY, 0
        CDATT   ST, 0
        CDATT   MN, 1
        CDATT   MO, 1
        CDATT   M3, 1
        CDATT   WK, 2
        CDATT   DN, 3, 3
        CDATT   WN, 4, 1
        CDATT   W3, 4, 1
        CDATT   WE, 4, 1
        CDATT   CE, 5
        CDATT   YR, 6
        CDATT   24, 7
        CDATT   12, 7
        CDATT   AM, 7
        CDATT   PM, 7
        CDATT   MI, 8
        CDATT   CS, 9
        CDATT   SE, 10
        CDATT   TZ, 0
CDATEscTabEnd

; routine to print R0 digits of the number held in R4,
; R7=0 <=> suppress leading zeroes

CDATDY
CDATMN
CDATWK
CDATDN
CDATWN
CDATCE
CDATYR
CDAT24
CDATMI
CDATCS
CDATSE
CDATDecR4 ROUT
        ADD     R2, R2, #1              ; undo initial subtract
        ADR     R6, PowersOfTen
10
        MOV     R5, #0
        TEQ     R0, #1                  ; if on last digit
        MOVEQ   R7, #1                  ; definitely don't suppress
20
        LDRB    R8, [R6, R0]            ; get power of ten to subtract
        SUBS    R4, R4, R8              ; subtract value
        ADDCS   R5, R5, #1
        BCS     %BT20
        ADD     R4, R4, R8              ; undo failed subract

        ORRS    R7, R7, R5              ; Z => suppress it
        BEQ     %FT30                   ; [suppressing]

        ORR     R5, R5, #"0"            ; convert to ASCII digit
        SUBS    R2, R2, #1              ; one less space in buffer
        BCC     CDATBufferError
        STRB    R5, [R1], #1            ; store character
30
        SUBS    R0, R0, #1              ; next digit
        BNE     %BT10                   ; [another digit to do]
        B       CDATMainLoop

PowersOfTen
        =       0, 1, 10, 100
        ALIGN

; Hours in 12 hour format

CDAT12
        CMP     R4, #12                 ; if in range 12..23
        SUBCS   R4, R4, #12             ; then make in range 00..11
        TEQ     R4, #0                  ; if 00
        MOVEQ   R4, #12                 ; then make 12
        B       CDATDecR4

; AM or PM indication

CDATAM
CDATPM
        CMP     R4, #12                 ; if earlier than 12 o'clock
        ADRCC   R4, CDATamstr           ; then am
        ADRCS   R4, CDATpmstr           ; else pm
CDATdostr    
        BL      MessageLookup           ; R4 - Pointer to string, lookup as message
        BVS     CDATError
CDATdostr1
        LDRB    R0, [R4], #1            ; get byte from string
        TEQ     R0, #0                  ; if zero, then end of string
        BEQ     CDATMainLoop            ; so loop
CDATdostrloop
        STRB    R0, [R1], #1            ; we know there's room for one char
CDATdostr2
        LDRB    R0, [R4], #1            ; get byte from string
        TEQ     R0, #0                  ; if zero, then end of string
        BEQ     CDATMainLoop            ; so loop
        SUBS    R2, R2, #1              ; dec R2 for next char
        BCS     CDATdostrloop           ; OK to do another char
        B       CDATBufferError         ; ran out of buffer space

CDATST
        TEQ     R4, #1
        TEQNE   R4, #21
        TEQNE   R4, #31
        ADREQ   R4, CDATststr
        BEQ     CDATdostr
        TEQ     R4, #2
        TEQNE   R4, #22
        ADREQ   R4, CDATndstr
        BEQ     CDATdostr
        TEQ     R4, #3
        TEQNE   R4, #23
        ADREQ   R4, CDATrdstr
        ADRNE   R4, CDATthstr
        B       CDATdostr

CDATW3
        ADRL    R0, DayNameTable-4      ; Sun is 1
        B       CDATdo3
CDATM3
        ADRL    R0, MonthNameTable-4    ; Jan is month 1
CDATdo3
        ADD     R4, R0, R4, LSL #2      ; point to short month name
        B       CDATdostr               ; then do copy

CDATWE
        ADD     R4, R4, #12             ; skip months
CDATMO
        ADR     R0, LongMonthTable-1    ; Jan is month 1
        LDRB    R4, [R0, R4]            ; get offset to month string
        ADD     R4, R0, R4              ; point to start of string
        B       CDATdostr
CDATTZ
        LDR     R4,TimeZoneName
        B       CDATdostr1              ; No need to look it up. 

CDATamstr
        =       "am", 0
CDATpmstr
        =       "pm", 0
CDATststr
        =       "st", 0
CDATndstr
        =       "nd", 0
CDATrdstr
        =       "rd", 0
CDATthstr
        =       "th", 0

LongMonthTable
        =       LongJan-(LongMonthTable-1)
        =       LongFeb-(LongMonthTable-1)
        =       LongMar-(LongMonthTable-1)
        =       LongApr-(LongMonthTable-1)
        =       LongMay-(LongMonthTable-1)
        =       LongJun-(LongMonthTable-1)
        =       LongJul-(LongMonthTable-1)
        =       LongAug-(LongMonthTable-1)
        =       LongSep-(LongMonthTable-1)
        =       LongOct-(LongMonthTable-1)
        =       LongNov-(LongMonthTable-1)
        =       LongDec-(LongMonthTable-1)
        =       LongSun-(LongMonthTable-1)
        =       LongMon-(LongMonthTable-1)
        =       LongTue-(LongMonthTable-1)
        =       LongWed-(LongMonthTable-1)
        =       LongThu-(LongMonthTable-1)
        =       LongFri-(LongMonthTable-1)
        =       LongSat-(LongMonthTable-1)

LongJan =       "M01L", 0
LongFeb =       "M02L", 0
LongMar =       "M03L", 0
LongApr =       "M04L", 0
LongMay =       "M05L", 0
LongJun =       "M06L", 0
LongJul =       "M07L", 0
LongAug =       "M08L", 0
LongSep =       "M09L", 0
LongOct =       "M10L", 0
LongNov =       "M11L", 0
LongDec =       "M12L", 0
LongSun =       "D01L", 0
LongMon =       "D02L", 0
LongTue =       "D03L", 0
LongWed =       "D04L", 0
LongThu =       "D05L", 0
LongFri =       "D06L", 0
LongSat =       "D07L", 0
        
; *****************************************************************************

MonthNameTable
 = "M01",0
 = "M02",0
 = "M03",0
 = "M04",0
 = "M05",0
 = "M06",0
 = "M07",0
 = "M08",0
 = "M09",0
 = "M10",0
 = "M11",0
 = "M12",0

DayNameTable
 = "D01",0
 = "D02",0
 = "D03",0
 = "D04",0
 = "D05",0
 = "D06",0
 = "D07",0

MonthLengths
 ;  F  J  F  M  A  M  J  J  A  S  O  N  D
 = 28,31,29,31,30,31,30,31,31,30,31,30,31

         ALIGN
STMonths
        &       &00000000       ; Jan
        &       &0FF6EA00       ; Feb
        &       &1E625200       ; Mar
        &       &2E593C00       ; Apr
        &       &3DCC5000       ; May
        &       &4DC33A00       ; Jun
        &       &5D364E00       ; Jul
        &       &6D2D3800       ; Aug
        &       &7D242200       ; Sep
        &       &8C973600       ; Oct
        &       &9C8E2000       ; Nov
        &       &AC013400       ; Dec
        &       &F0000000       ; terminator, must be less than this (+1)

         ALIGN

;---------------------------------------------------------------
; Read Time in local format.                                
;ConvertTimeToOrdinals
;In:
; R1 = Pointer to 5 byte UTC time block
; R2 -> Word alligned buffer to hold data
;Out:
; R1 Preserved
; R2 Preserved
;   [R2]    = CS.                     ; all values are for LOCAL time
;   [R2+4]  = Second
;   [R2+8]  = Minute
;   [R2+12] = Hour (out of 24)
;   [R2+16] = Day number in month.
;   [R2+20] = Month number in year.
;   [R2+24] = Year number.
;   [R2+28] = Day of week.
;   [R2+32] = Day of year
ConvertTimeToOrdinals
        Push    "r0-r11,LR"

        BL      GetTimeValues
        STRVS   r0,[sp]
        Pull    "r0-r11,PC",VS

        LDR     R14,[SP,#2*4]
        
        STR     R9, [R14],#4
        STR     R10,[R14],#4
        STR     R8 ,[R14],#4
        STR     R7 ,[R14],#4
        STR     R0 ,[R14],#4
        STR     R1 ,[R14],#4
        MOV     R1,#100
        MUL     R0 ,R5,R1
        ADD     R0 ,R0,R6
        STR     R0 ,[R14],#4
        STR     R4,[R14],#4
        STR     R3,[R14]

        Pull    "r0-r11,PC"

; *****************************************************************************
;ConvertOrdinalsToTime
;In:
;   r1 -> Block to contain 5 byte time.
;   r2 -> Block containing:
;      [R2]    = CS.                     ; all values are for LOCAL time
;      [R2+4]  = Second
;      [R2+8]  = Minute
;      [R2+12] = Hour (out of 24)
;      [R2+16] = Day number in month.
;      [R2+20] = Month number in year.
;      [R2+24] = Year number.
;Out:
;  r1,r2 preserved.
;  [r1] 5 byte UTC time for given time.
ConvertOrdinalsToTime
        Push    "r0-r11,LR"
           
        MOV     R11,R2
        LDR     R0,[R11,#12]
        LDR     R1,[R11,#8 ]
        LDR     R2,[R11,#16]
        LDR     R3,[R11,#20]
        LDR     R4,[R11,#24]
        MOV     R6,#100
        DivRem  R5,R4,R6,R14
        LDR     R6,[R11,#4]
        LDR     R7,[R11]

; R0 := hours, R1 := minutes
; R2 := days, R3 := months
; R4 := year(lo), R5 := year(hi)
; R6 := seconds, R7 := centiseconds

        MOV     R9, #24
        SUB     R2, R2, #1              ; decrement day (day=1 => nowt to add)
        MLA     R0, R9, R2, R0          ; R0 = hours + day*24
        MOV     R9, #60
        MLA     R1, R0, R9, R1          ; R1 = mins + hours*60
        MLA     R6, R1, R9, R6          ; R6 = secs + mins*60
        MOV     R9, #100
        MLA     R7, R6, R9, R7          ; R7 = centisecs + secs*100

        ADR     R0, STMonths-4          ; Point to table (month = 1..12)
        LDR     R1, [R0, R3, LSL #2]    ; get word of offset
        ADD     R7, R7, R1              ; add to total

; if not had leap day in this year yet, then exclude this year from the
; leap day calculations

        CMP     R3, #3                  ; if month >= 3
        SBCS    R0, R4, #0              ; then R0,R1 = R4,R5
        MOVCC   R0, #99                 ; else R0,R1 = R4,R5 -1
        SBC     R1, R5, #0

; want (yl+100*yh) DIV 4 - (yl+100*yh) DIV 100 + (yl+100*yh) DIV 400
; = (yl DIV 4)+ (25*yh) - yh + (yh DIV 4)
; = (yl >> 2) + 24*yh + (yh >> 2)

        MOV     R0, R0, LSR #2          ; yl >> 2
        ADD     R0, R0, R1, LSR #2      ; + yh >> 2
        ADD     R0, R0, R1, LSL #4      ; + yh * 16
        ADD     R0, R0, R1, LSL #3      ; + yh * 8

; now subtract off the number of leap days in first 1900 years = 460

        SUBS    R0, R0, #460
        BCC     BadYear                 ; before 1900, so bad
        CMP     R0, #86                 ; if more than 86 days, then it's
        BCS     BadYear                 ; after 2248, so bad

        LDR     R9, =ticksperday        ; multiply by ticksperday and add to
        MLA     R7, R9, R0, R7          ; total (no overflow possible as this
                                        ; can never be more than 85+31 days)

; now add on (year-1900)*ticksperyear

        SUBS    R5, R5, #19             ; subtract off 1900
        BCC     BadYear
        MOV     R9, #100
        MLA     R4, R9, R5, R4          ; R4 = year-1900

        LDR     R0, =ticksperyear       ; lo word of amount to add on
        MOV     R1, #0                  ; hi word of amount to add on
        MOV     R8, #0                  ; hi word of result
10
        MOVS    R4, R4, LSR #1
        BCC     %FT15

        ADDS    R7, R7, R0              ; if bit set then add on amount
        ADCS    R8, R8, R1
        BCS     BadYear                 ; overflow => bad time value
15
        ADDS    R0, R0, R0              ; shift up amount
        ADCS    R1, R1, R1
        TEQ     R4, #0                  ; if still bits to add in
        BNE     %BT10                   ; then loop

; So far this is local time, now Make it UTC

        MOV     R0,#-1
        SWI     XTerritory_ReadCurrentTimeZone
        ADDVS   SP,SP,#4
        Pull    "r1-r11,PC",VS
        MOV     R0,R1,ASR #31               ; Sign extend to 5 bytes. 
        SUBS    R7,R7,R1
        SBC     R8,R8,R0

        CMP     R8, #&100               ; R8 must only be a byte
        BCS     BadYear

        LDR     R14,[SP,#1*4]
        STRB    R7,[R14],#1
        MOV     R7,R7,LSR #8
        STRB    R7,[R14],#1
        MOV     R7,R7,LSR #8
        STRB    R7,[R14],#1
        MOV     R7,R7,LSR #8
        STRB    R7,[R14],#1
        STRB    R8,[R14]

        Pull    "R0-R11,PC",,^

BadYear
        ADR     R0,ErrorBlock_BadTimeBlock
        BL      message_errorlookup
        ADD     SP,SP,#4
        Pull    "r1-r11,PC"

ErrorBlock_BadTimeBlock
        DCD     0
        DCB     "BadTime",0
        ALIGN

; ----------------------------------------------------------------------
; CheckSeparator
;
; in:   r11 = allowable character for a separator
;       r2 points to 2nd char in string
;       r9 = 1st char in string
;
; out:  r2 updated to point after 1st non space char after an optional
;          separator
;       r9 = 1st non space char

CheckSeparator
        STMDB   sp!, {lr}
        BL      SkipSpaces1
        CMP     r9, r11
        BLEQ    SkipSpaces
        LDMIA   sp!, {pc}^

; ----------------------------------------------------------------------
; SkipSpaces
;
; in:   R2 = pointer to string
;
; out:  R2 updated to point to 1st char after 1st non space
;       R9 = 1st non space char
;
; SkipSpaces1
;
; in:   R9 = 1st char in string
;       R2 = pointer to 2nd char in string
;
; out:  R2 updated to point to 1st char after 1st non space
;       R9 = 1st non space char

SkipSpaces
        LDRB    r9, [r2], #1
SkipSpaces1
        CMP     r9, #' '
        BEQ     SkipSpaces
        MOV     pc, lr

; ----------------------------------------------------------------------
;
;       GetInteger - Read integer from string
;
; in:   R9  = 1st char of string
;       R2 -> 2nd char of string
;       R11 = Upper limit of value
;
; out:  BadTimeString error if invalid string
;       R1 = value read
;       R9 = Terminating character
;       Z set if result was 0

GetInteger
        SUB     r10, r9, #'0'
        CMP     r10, #10
        BCS     BadTimeString

        MOV     r1, #0
01
        ADD     r1, r1, r1, LSL #2
        ADD     r1, r10, r1, LSL #1
        LDRB    r9, [r2], #1
        SUB     r10, r9, #'0'
        CMP     r10, #10
        BCC     %B01
        CMP     r11, r1
        BCC     BadTimeString
        CMP     r1, #0

        MOV     pc, lr

;------------------------------------------------------------------------
; ConvertTimeStringToOrdinals
; In:
;     R1 = Reason code:
;            1   - String is %24:%MI:%SE
;            2   - String is %W3, %DY-%M3-%CE%YR
;            3   - String is %W3, %DY-%M3-%CE%YR.%24:%MI:%SE
;     R2 -> String
;     R3 -> Word aligned buffer to contain result.
; Out:
;   R1-R3  Preserved.
;   [R3]    = CS                        ; all values are for LOCAL time
;   [R3+4]  = Seconds                   ; Values that are not present in the string are set to -1
;   [R3+8]  = Minutes
;   [R3+12] = Hours (out of 24)
;   [R3+16] = Day number in month.
;   [R3+20] = Month number in year.
;   [R3+24] = Year number.
;
ConvertTimeStringToOrdinals

        Push    "R1-R11,LR"

        DebugS  dt,"String is ",R2

; Skip leading spaces

        BL      SkipSpaces

; first do the date part of the string

        TST     R1,#2
        MOVEQ   R0,#-1                  ; If no date, set fields to -1
        STREQ   R0,[R3,#16]
        STREQ   R0,[R3,#20]
        STREQ   R0,[R3,#24]
        BEQ     %FT50

        BL      open_messages_file

; Test if 1st non blank char is a digit

        SUB     r10, r9, #'0'
        CMP     r10, #10
        BCC     %F02                    ; Yes => must be day of month

; Skip to after first ',' (ignoring day of week)

01      CMP     r9, #' '
        BCC     BadTimeString
        CMP     r9, #','
        LDRNEB  r9, [r2], #1
        BNE     %B01

; Skip spaces before day of month

        BL      SkipSpaces

; Read day of month
02
        MOV     r11, #31
        BL      GetInteger
        BEQ     BadTimeString           ; 0 for DOM illegal
        MOV     r5, r1

; Allow ' ' or '-' as day and month separator

        MOV     r11, #'-'
        BL      CheckSeparator

; R2 now points to month name.

        SUB     R6,R2,#1                ; R6 -> month
                   
; Get all names from messages file

        ADR     R0,message_file_block+4
        MOV     R4,#0                   ; First call
        MOV     R7,#1                   ; First month.

01
        ADRL    R1,month_token
        SUB     SP,SP,#8                ; We know tokens aren't long.
        MOV     R2,SP
        MOV     R3,#8
        SWI     XMessageTrans_EnumerateTokens
        ADDVS   SP,SP,#8                ; 
        Pull    "R1-R11,PC",VS
        CMP     R2,#0
        ADDEQ   SP,SP,#8
        BEQ     BadTimeString           ; Not found !

; Get Message

        DebugS  dt,"Next token is ",R2
        MOV     R1,R2                   ; Token.
        MOV     R2,#0                   ; Don't copy message !
        SWI     XMessageTrans_Lookup
        ADD     SP,SP,#8
        Pull    "R1-R11,PC",VS
        
; Got message, now compare with month name in string.

        DebugS  dt,"Got month name: ",R2,4
        DebugS  dt,"String pointer is now on: ",R6
        MOV     R1,R6
02
        LDRB    r9,[R2],#1
        CMP     r9,#10
        BEQ     %FT03                   ; End of token
        ORR     r9,r9,#&20

        LDRB    R10,[R1],#1
        CMP     R10,#' '
        BCC     BadTimeString
        ORR     R10,R10,#&20

        CMP     r9,R10
        BEQ     %BT02                   ; Try next character.
        ADD     R7,R7,#1
        B       %BT01                   ; Try next month.

; Skip remainder of chars in month (eg "uary" in "January")
; Allow ' ' or '-' as month - year separators
03
        LDRB    R9,[R1],#1
        CMP     R9,#' '
        BCC     BadTimeString
        CMPNE   r9,#'-'
        BNE     %B03

        MOV     r2, r1
        MOV     r11, #'-'
        BL      CheckSeparator

; Save pointer so we can work out length of year later to see whether it
; is 2 digit or 4 digit format

        MOV     r4, r2

        MOV     r11, #-1
        BL      GetInteger
        MOV     r3, r1

; Check no. of days in month

        ADRL    r0, MonthLengths
        LDRB    r10, [r0, r7]
        CMP     r7, #2          ; Table has 29 days for Feb
        MOVEQ   r10, #28        ; Make 28
        TSTEQ   r3, #3          ; Leap year, ignore 2100
        ADDEQ   r10, r10, #1    ; Make 29
        CMP     r10, r5
        BCC     BadTimeString

; Test if a 2 digit year is given, if so we must decide whether it is
; 19XX or 20XX. Dates on or after 26-Jan-66 are taken to be 19XX otherwise
; it is 20XX.

        SUB     r4, r2, r4      ; No. of digits in year
        CMP     r4, #3
        BCS     %F20            ; 3 or more digits
        CMP     r3, #66
        CMPEQ   r7, #1
        CMPEQ   r5, #26         ; CS if >= 26-Jan-66
        ADD     r3, r3, #2000   ; = &7D0
        SUBCS   r3, r3, #100

; Save date in ordinal struct
20
        LDR     r10, [sp, #2*4]         ; Get entry R3
        STR     r5, [r10, #16]          ; Save day no.
        STR     r7, [r10, #20]          ; Save month no.
        STR     r3, [r10, #24]          ; Save year

        MOV     r11, #'.'
        BL      CheckSeparator

        Debug   dt,"Year is ",R3
        LDR     R1,[SP]                 ; Get reason code back.
        LDR     r3, [sp, #2*4]
50
        TST     R1, #1
        MOVEQ   R0, #-1                 ; if not doing time, set hours,
        STREQ   R0,[R3]                 ; minutes seconds and cs to -1
        STREQ   R0,[R3,#4]              ;
        STREQ   R0,[R3,#8]              ;
        STREQ   R0,[R3,#12]             ;
        BEQ     %FT80                   ; [not doing time part]

; Allow ' ' or '.' for date - time separator

        MOV     R0,#0
        STR     R0,[R3]                 ; Zero the centiseconds

; Read hour

        MOV     r11, #23
        BL      GetInteger
        STR     r1, [r3, #12]

; Allow ':' or ' ' for hour - minute separator

        MOV     r11, #':'
        BL      CheckSeparator

; Read minute

        MOV     r11, #59
        BL      GetInteger
        STR     r1, [r3, #8]

; Allow ':' or ' ' for minute - second separator

        MOV     r11, #':'
        BL      CheckSeparator

; Read second

        MOV     r11, #61
        BL      GetInteger
        STR     r1, [r3, #4]

; No errors, buffer now contains values !
80
        Pull    "R1-R11,PC",,^

BadTimeString
        ADR     R0,ErrorBlock_BadTimeString
        BL      message_errorlookup
        Pull    "R1-R11,PC"

ErrorBlock_BadTimeString

        DCD     0
        DCB     "BadStr",0
month_token     DCB     "M??",0        
        ALIGN

;---------------------------------------------------------------------------
; ConvertStandardDate
; Entry:
;       R1 -> 5 byte UTC time block
;       R2 -> Buffer
;       R3 = Size of buffer.
; Exit:
; out:  V=0 => successful conversion
;       R0 = input value of R2
;       R1 = updated pointer to buffer
;       R2 = updated size of buffer
;
;       V=1 => failed conversion
;       R0 -> error block
;       R1 = input value of R1
;       R2 = input value of R2
;       R3 = input value of R3
ConvertStandardDate
        Push    "R3,R4,LR"

        Debug   xx,"Territory number is ",r0

        ADRL    R4,StandardDateFormat
        SWI     XTerritory_ConvertDateAndTime

        Pull    "R3,R4,PC"

;---------------------------------------------------------------------------
; ConvertStandardTime
; Entry:
;       R1 -> 5 byte UTC time block
;       R2 -> Buffer
;       R3 = Size of buffer.
; Exit:
; out:  V=0 => successful conversion
;       R0 = input value of R2
;       R1 = updated pointer to buffer
;       R2 = updated size of buffer
;
;       V=1 => failed conversion
;       R0 -> error block
;       R1 = input value of R1
;       R2 = input value of R2
;       R3 = input value of R3
ConvertStandardTime
        Push    "R3,R4,LR"

        Debug   xx,"Territory number is ",r0

        ADRL    R4,StandardTimeFormat
        SWI     XTerritory_ConvertDateAndTime

        Pull    "R3,R4,PC"


;---------------------------------------------------------------------------
; ConvertStandardDateAndTime
; Entry:
;       R1 -> 5 byte UTC time block
;       R2 -> Buffer
;       R3 = Size of buffer.
; Exit:
; out:  V=0 => successful conversion
;       R0 = input value of R2
;       R1 = updated pointer to buffer
;       R2 = updated size of buffer
;
;       V=1 => failed conversion
;       R0 -> error block
;       R1 = input value of R1
;       R2 = input value of R2
;       R3 = input value of R3
ConvertStandardDateAndTime
        Push    "R3,R4,LR"

        Debug   xx,"Territory number is ",r0

        ADRL    R4,StandardDateAndTimeFormat
        SWI     XTerritory_ConvertDateAndTime

        Pull    "R3,R4,PC"

        LNK     Sources.Transform
