        .TITLE  TERVEHDYS
        .IDENT  /V1.4/

        .LIBRARY /SYS$LIBRARY:STARLET.MLB/

        $SSDEF
        $DSCDEF

        .PSECT  DATA,NOEXE,WRT,LONG

UTC_TIME:       .BLKQ   1
UTCNUM:         .BLKW   7
FINNUM:         .BLKW   7

DAY:            .BLKL   1
YEAR:           .BLKL   1
HOUR:           .BLKL   1
MINUTE:         .BLKL   1
SECOND:         .BLKL   1

MAR_LAST:       .BLKL   1
OCT_LAST:       .BLKL   1
MONTH_PTR:      .BLKL   1
OFFSET_HOURS:   .BLKL   1
MONTH_LEN:      .BLKL   1

OUTBUF:         .BLKB   132
OUTLEN:         .BLKW   1

OUT_DSC:
        .WORD   132
        .BYTE   DSC$K_DTYPE_T
        .BYTE   DSC$K_CLASS_S
        .ADDRESS OUTBUF

FMT_DATE:       .ASCID  /!UL. !AS !UL !2ZL:!2ZL:!2ZL Suomen aikaa/

MON01:          .ASCID  /tammikuuta/
MON02:          .ASCID  /helmikuuta/
MON03:          .ASCID  /maaliskuuta/
MON04:          .ASCID  /huhtikuuta/
MON05:          .ASCID  /toukokuuta/
MON06:          .ASCID  /kesakuuta/
MON07:          .ASCID  /heinakuuta/
MON08:          .ASCID  /elokuuta/
MON09:          .ASCID  /syyskuuta/
MON10:          .ASCID  /lokakuuta/
MON11:          .ASCID  /marraskuuta/
MON12:          .ASCID  /joulukuuta/

MONTH_TABLE:
        .ADDRESS MON01,MON02,MON03,MON04,MON05,MON06
        .ADDRESS MON07,MON08,MON09,MON10,MON11,MON12

MDAYS:
        .LONG   31,28,31,30,31,30,31,31,30,31,30,31

MSG_TIME:       .ASCID  /Paivamaara ja aika:/
GREET_MORN:     .ASCID  /Hyvaa huomenta!/
GREET_DAY:      .ASCID  /Hyvaa paivaa!/
GREET_EVE:      .ASCID  /Hyvaa iltaa!/
GREET_NIGHT:    .ASCID  /Hyvaa yota!/

        .PSECT  CODE,EXE,NOWRT,LONG

        .ENTRY  START,^M<>

        $GETTIM_S TIMADR=UTC_TIME
        BLBS    R0,10$
        JMP     ERROR
10$:
        $NUMTIM_S TIMBUF=UTCNUM,TIMADR=UTC_TIME
        BLBS    R0,20$
        JMP     ERROR
20$:

        ; Find last Sunday of March and October in the UTC year.
        MOVZWL  UTCNUM+0,R0
        MOVL    #3,R1
        MOVL    #31,R2
        BSBW    LAST_SUNDAY
        MOVL    R0,MAR_LAST

        MOVZWL  UTCNUM+0,R0
        MOVL    #10,R1
        MOVL    #31,R2
        BSBW    LAST_SUNDAY
        MOVL    R0,OCT_LAST

        ; Default: Finnish winter time, UTC+2.
        MOVL    #2,OFFSET_HOURS

        ; Finnish summer time: last Sunday of March 01:00 UTC
        ; through last Sunday of October before 01:00 UTC.
        MOVZWL  UTCNUM+2,R0          ; UTC month

        CMPL    R0,#3
        BLSS    HAVE_OFFSET          ; Jan-Feb

        CMPL    R0,#10
        BGTR    HAVE_OFFSET          ; Nov-Dec

        CMPL    R0,#3
        BEQL    CHECK_MARCH

        CMPL    R0,#10
        BEQL    CHECK_OCTOBER

        MOVL    #3,OFFSET_HOURS     ; Apr-Sep
        BRW     HAVE_OFFSET

CHECK_MARCH:
        MOVZWL  UTCNUM+4,R0          ; UTC day
        CMPL    R0,MAR_LAST
        BLSS    HAVE_OFFSET
        BGTR    MARCH_DST

        MOVZWL  UTCNUM+6,R0          ; UTC hour
        CMPL    R0,#1
        BLSS    HAVE_OFFSET

MARCH_DST:
        MOVL    #3,OFFSET_HOURS
        BRW     HAVE_OFFSET

CHECK_OCTOBER:
        MOVL    #3,OFFSET_HOURS     ; October is DST until cutoff
        MOVZWL  UTCNUM+4,R0
        CMPL    R0,OCT_LAST
        BLSS    HAVE_OFFSET
        BGTR    OCT_STD

        MOVZWL  UTCNUM+6,R0
        CMPL    R0,#1
        BLSS    HAVE_OFFSET

OCT_STD:
        MOVL    #2,OFFSET_HOURS

HAVE_OFFSET:
        ; Copy UTC numeric time to FINNUM.
        MOVW    UTCNUM+0,FINNUM+0
        MOVW    UTCNUM+2,FINNUM+2
        MOVW    UTCNUM+4,FINNUM+4
        MOVW    UTCNUM+6,FINNUM+6
        MOVW    UTCNUM+8,FINNUM+8
        MOVW    UTCNUM+10,FINNUM+10
        MOVW    UTCNUM+12,FINNUM+12

        ; Add 2 or 3 hours.
        MOVZWL  FINNUM+6,R0
        ADDL2   OFFSET_HOURS,R0
        CMPL    R0,#24
        BLSS    STORE_HOUR

        SUBL2   #24,R0
        MOVW    R0,FINNUM+6
        BSBW    INC_DATE
        BRW     AFTER_HOUR

STORE_HOUR:
        MOVW    R0,FINNUM+6

AFTER_HOUR:
        MOVZWL  FINNUM+0,YEAR
        MOVZWL  FINNUM+4,DAY
        MOVZWL  FINNUM+6,HOUR
        MOVZWL  FINNUM+8,MINUTE
        MOVZWL  FINNUM+10,SECOND

        MOVZWL  FINNUM+2,R1
        DECL    R1
        MULL2   #4,R1
        MOVL    MONTH_TABLE(R1),MONTH_PTR

        PUSHL   SECOND
        PUSHL   MINUTE
        PUSHL   HOUR
        PUSHL   YEAR
        PUSHL   MONTH_PTR
        PUSHL   DAY
        PUSHAQ  OUT_DSC
        PUSHAL  OUTLEN
        PUSHAQ  FMT_DATE
        CALLS   #9,G^LIB$SYS_FAO
        BLBS    R0,30$
        JMP     ERROR
30$:
        MOVW    OUTLEN,OUT_DSC

        PUSHAQ  MSG_TIME
        CALLS   #1,G^LIB$PUT_OUTPUT

        PUSHAQ  OUT_DSC
        CALLS   #1,G^LIB$PUT_OUTPUT

        CMPW    FINNUM+6,#6
        BLSS    SAY_NIGHT

        CMPW    FINNUM+6,#12
        BLSS    SAY_MORN

        CMPW    FINNUM+6,#18
        BLSS    SAY_DAY

        CMPW    FINNUM+6,#22
        BLSS    SAY_EVE

SAY_NIGHT:
        PUSHAQ  GREET_NIGHT
        BRB     PRINT_GREET

SAY_MORN:
        PUSHAQ  GREET_MORN
        BRB     PRINT_GREET

SAY_DAY:
        PUSHAQ  GREET_DAY
        BRB     PRINT_GREET

SAY_EVE:
        PUSHAQ  GREET_EVE

PRINT_GREET:
        CALLS   #1,G^LIB$PUT_OUTPUT
        MOVL    #SS$_NORMAL,R0
        RET

ERROR:
        PUSHL   R0
        CALLS   #1,G^LIB$STOP
        RET

; Increment FINNUM date by one day.

INC_DATE:
        MOVZWL  FINNUM+0,R0          ; year
        MOVZWL  FINNUM+2,R1          ; month
        BSBW    GET_MONTH_LENGTH
        MOVL    R0,MONTH_LEN

        MOVZWL  FINNUM+4,R0          ; day
        INCL    R0
        CMPL    R0,MONTH_LEN
        BGTR    NEXT_MONTH

        MOVW    R0,FINNUM+4
        RSB

NEXT_MONTH:
        MOVW    #1,FINNUM+4

        MOVZWL  FINNUM+2,R0
        INCL    R0
        CMPL    R0,#12
        BGTR    NEXT_YEAR

        MOVW    R0,FINNUM+2
        RSB

NEXT_YEAR:
        MOVW    #1,FINNUM+2
        MOVZWL  FINNUM+0,R0
        INCL    R0
        MOVW    R0,FINNUM+0
        RSB

; Input:
;   R0 = year
;   R1 = month
; Output:
;   R0 = length of month

GET_MONTH_LENGTH:
        DECL    R1
        MULL2   #4,R1
        MOVL    MDAYS(R1),R2

        ; February: check leap year.
        CMPL    R1,#4                ; original month 2 -> index 1 -> offset 4
        BNEQ    MONTH_LEN_DONE

        BSBW    IS_LEAP
        TSTL    R0
        BEQL    MONTH_LEN_DONE
        MOVL    #29,R2

MONTH_LEN_DONE:
        MOVL    R2,R0
        RSB

; Input:
;   R0 = year
; Output:
;   R0 = 1 if leap year, 0 otherwise

IS_LEAP:
        MOVL    R0,R3

        MOVL    R3,R4
        DIVL2   #400,R4
        MULL2   #400,R4
        CMPL    R3,R4
        BEQL    LEAP_YES

        MOVL    R3,R4
        DIVL2   #100,R4
        MULL2   #100,R4
        CMPL    R3,R4
        BEQL    LEAP_NO

        MOVL    R3,R4
        DIVL2   #4,R4
        MULL2   #4,R4
        CMPL    R3,R4
        BEQL    LEAP_YES

LEAP_NO:
        CLRL    R0
        RSB

LEAP_YES:
        MOVL    #1,R0
        RSB

; Input:
;   R0 = year
;   R1 = month, 3 or 10
;   R2 = last day of month
; Output:
;   R0 = day number of last Sunday in that month

LAST_SUNDAY:
        MOVL    R0,R3
        MOVL    R1,R4
        MOVL    R2,R5

        ADDL3   #1,R4,R6
        MULL2   #13,R6
        DIVL2   #5,R6

        ADDL3   R5,R6,R7
        ADDL2   R3,R7

        MOVL    R3,R8
        DIVL2   #4,R8
        ADDL2   R8,R7

        MOVL    R3,R8
        DIVL2   #100,R8
        SUBL2   R8,R7

        MOVL    R3,R8
        DIVL2   #400,R8
        ADDL2   R8,R7

        MOVL    R7,R8
        DIVL2   #7,R8
        MULL2   #7,R8
        SUBL2   R8,R7

        ADDL2   #6,R7
        MOVL    R7,R8
        DIVL2   #7,R8
        MULL2   #7,R8
        SUBL2   R8,R7

        SUBL3   R7,R5,R0
        RSB

        .END    START
