ORCALib/time.asm

1572 lines
43 KiB
NASM

keep obj/time
mcopy time.macros
case on
****************************************************************
*
* Time - Time and date libraries for C
*
* This code implements the tables and subroutines needed to
* support the standard C library TIME.
*
* January 1989
* Mike Westerfield
*
* Copyright 1989
* Byte Works, Inc.
*
* Note: Portions of this library appear in SysFloat
*
****************************************************************
*
Time private dummy segment
; struct tm fields
tm_sec gequ 0 seconds 0..59
tm_min gequ tm_sec+2 minutes 0..59
tm_hour gequ tm_min+2 hours 0..23
tm_mday gequ tm_hour+2 day 1..31
tm_mon gequ tm_mday+2 month 0..11
tm_year gequ tm_mon+2 year 69..205 (1900=0)
tm_wday gequ tm_year+2 day of week 0..6 (Sun = 0)
tm_yday gequ tm_wday+2 day of year 0..365
tm_isdst gequ tm_yday+2 daylight savings? 1 = yes, 0 = no
end
****************************************************************
*
* TimeCommon - common variables for the time library
*
****************************************************************
*
TimeCommon privdata
;
; For conversion to/from seconds since 13 Nov 1969
;
year ds 4 year (years since 1900)
month ds 4 month 0..11
day ds 4 day 1..31
hour ds 4 hour 0..23
minute ds 4 minute 0..59
second ds 4 second 0..59
count ds 8 seconds since 13 Nov 1969
t1 ds 4 work variable
t2 ds 4 work variable
lasttime ds 4 last time_t value returned by time()
lastDST dc i2'-1' tm_isdst value for lasttime
end
****************************************************************
*
* clock_t __clocks_per_sec()
*
* Outputs:
* X-A - the number of clock ticks per second (50 or 60)
*
****************************************************************
*
__clocks_per_sec start
LANGSEL equ $E1C02B LANGSEL soft switch
short I,M
ldy #60
ldx #0
lda >LANGSEL
and #$10 test NTSC/PAL bit of LANGSEL
beq lb1
ldy #50
lb1 long I,M
tya
rtl
end
****************************************************************
*
* char *asctime(struct tm *ts)
*
* Inputs:
* ts - time record to create string for
*
* Outputs:
* returns a pointer to the time string
*
****************************************************************
*
asctime start
csubroutine (4:ts),0
phb
phk
plb
brl ~ctime2
end
****************************************************************
*
* clock_t clock()
*
* Outputs:
* X-A - tick count
*
****************************************************************
*
clock start
pha
pha
_GetTick
pla
plx
rtl
end
****************************************************************
*
* char *ctime(timeptr)
* time_t *timptr;
*
* Inputs:
* timeptr - time to create string for
*
* Outputs:
* returns a pointer to the time string
*
****************************************************************
*
ctime start
tm_sec equ 0 displacements into the time record
tm_min equ 2
tm_hour equ 4
tm_mday equ 6
tm_mon equ 8
tm_year equ 10
tm_wday equ 12
csubroutine (4:timeptr),0
phb
phk
plb
ph4 <timeptr convert to a time record
jsl localtime
sta timeptr
stx timeptr+2
~ctime2 entry
ldy #tm_wday convert the week day to a string
lda [timeptr],Y
asl a
asl a
tax
lda weekDay,X
sta str
lda weekDay+1,X
sta str+1
ldy #tm_mon convert the month to a string
lda [timeptr],Y
asl a
asl a
tax
lda monthStr,X
sta str+4
lda monthStr+1,X
sta str+5
ldy #tm_mday convert the day to a string
lda [timeptr],Y
jsr mkstr
bit #$00CF check for leading '0'
bne lb1
and #$FFEF convert leading '0' to ' '
lb1 sta str+8
ldy #tm_hour convert the hour to a string
lda [timeptr],Y
jsr mkstr
sta str+11
ldy #tm_min convert minutes to a string
lda [timeptr],Y
jsr mkstr
sta str+14
ldy #tm_sec convert seconds to a string
lda [timeptr],Y
jsr mkstr
sta str+17
ldy #tm_year convert the year to a string
lda [timeptr],Y
ldy #19
sec
yr1 iny
sbc #100
bpl yr1
clc
yr2 dey
adc #100
bmi yr2
jsr mkstr
sta str+22
tya
jsr mkstr
sta str+20
lla timeptr,str
plb
creturn 4:timeptr
weekDay dc c'Sun Mon Tue Wed Thu Fri Sat'
monthStr dc c'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'
str dc c'Sun Jan 00 00:00:00 1900',i1'10,0'
;
; mkstr - convert a number to a string
;
mkstr ldx #-1
mk1 inx
sec
sbc #10
bcs mk1
clc
adc #10
xba
pha
txa
ora 1,S
ora #$3030
plx
rts
end
****************************************************************
*
* factor - compute the seconds since 13 Nov 1969 from date
*
* factor_second32 - alt entry point taking second as a
* signed 32-bit input value
*
* Inputs:
* year,month,day,hour,minute,second - time to convert
* (each treated as a signed 16-bit value, with the
* exception of second when using factor_second32)
*
* Outputs:
* count - seconds since 13 Nov 1969 (signed 64-bit value)
*
* Note: Input values outside their normal ranges are allowed.
*
****************************************************************
*
factor private
using TimeCommon
;
; sign-extend time components to 4 bytes
;
stz second+2
lda second
bpl lb0
dec second+2
factor_second32 entry
lb0 stz year+2
lda year
bpl lb0a
dec year+2
lb0a stz month+2
stz day+2
lda day
bpl lb0b
dec day+2
lb0b stz hour+2
lda hour
bpl lb0c
dec hour+2
lb0c stz minute+2
lda minute
bpl lb0e
dec minute+2
;
; adjust for out-of-range month values
;
lb0e lda month
bpl lb0f
clc
adc #12
sta month
dec4 year
bra lb0e
lb0f sec
sbc #12
bmi lb0x
sta month
inc4 year
bra lb0e
;
; compute the # of days since 13 Nov 1969
;
lb0x mul4 year,#365,count count := 365*year + day + 31*month
add4 count,day
mul4 month,#31,t1
add4 count,t1
add4 year,#32800,t2 t2 := year + 32800 (so it is positive)
lda month if January or February then
cmp #2
bge lb1
dec4 t2 year := year-1
bra lb2 else
lb1 mul4 month,#4,t1 count := count - (month*4+27) div 10
add4 t1,#27
div4 t1,#10
sub4 count,t1
lb2 div4 t2,#4,t1 count := count + (year+32800) div 4
add4 count,t1
add4 t2,#300 count := count -
div4 t2,#100 ((300+year+32800) div 100+1)*3 div 4
inc4 t2
mul4 t2,#3
div4 t2,#4
sub4 count,t2
sub4 count,#25518-2+7954 subtract off days between 1 Jan 1900
! and 13 Nov 1969, minus 2 to adjust for
! skipped leap days in 1700 and 1800,
! plus 7954 to adjust for leap days in
! an additional 32800 years
;
; Convert to seconds and add in time of day in seconds
;
lda count+2 convert to 64-bit count of seconds
pha
bpl lb3 if count is negative, negate it
sub4 #0,count,count
lb3 tsc compute count*24*60*60
sec
sbc #8
tcs
ph4 count
ph4 #24*60*60
_LongMul
pla
sta count
pla
sta count+2
pla
sta count+4
pla
sta count+6
pla
bpl lb4 if count was negative, negate result
negate8 count
lb4 mul4 hour,#3600,t1 add in hours*3600
jsr add_t1_to_count
mul4 minute,#60,t1 add in minutes*60
jsr add_t1_to_count
move4 second,t1 add in seconds
;
; Add t1 (4 bytes) to count (8 bytes).
; (This is called as a subroutine and also run at the end of factor.)
;
add_t1_to_count anop
clc
lda t1
adc count
sta count
lda t1+2
tax
adc count+2
sta count+2
lda #0
txy
bpl ad1
dec a
ad1 tay
adc count+4
sta count+4
tya
adc count+6
sta count+6
rts
end
****************************************************************
*
* ~get_tz_offset - get current time zone offset from UTC
*
* Outputs:
* A-X - time zone offset from UTC
*
****************************************************************
*
~get_tz_offset private
lda >__useTimeTool if not using time tool
beq no_tz assume we have no TZ offset
pha make space for TZ prefs
pha
pea 1 get one record element only (TZ offset)
tsc get time zone preference
inc a
pea 0
pha
_tiGetTimePrefs
pla
bcc have_tz
pla
pla
lda #0 assume 0 offset if TZ info not available
no_tz tax
rts
have_tz pha determine if it's daylight savings
ph2 #$5E
_ReadBParam
pla
lsr a
lsr a
bcs ret
; clc
lda #60*60 adjust for DST (+1 hour) if needed
adc 1,s
sta 1,s
lda #0
adc 3,s
sta 3,s
ret pla return offset value
plx
rts
end
****************************************************************
*
* gmlocaltime_tm - struct tm used by gmtime and localtime
*
****************************************************************
*
gmlocaltime_tm private
ds 9*2
end
****************************************************************
*
* struct tm *gmtime(t)
* time_t *t;
*
* Inputs:
* t - pointer to # of seconds since 13 Nov 1969
*
* Outputs:
* returns a pointer to a time record for UTC time
*
****************************************************************
*
gmtime start
t equ 6
phd
tsc
tcd
ldy #2 dereference the pointer to time_t
lda [t],Y
tax
lda [t]
tay
pld
phb
pla move return address
sta 3,s
pla
sta 3,s
plb
ph4 #gmlocaltime_tm push address of struct tm to use
pea 0 push tm_isdst value (no DST for UTC)
phx push time_t value to convert
phy
jsr ~get_tz_offset push time zone offset
phx
pha
doit jsl ~gmlocaltime use common gmtime/localtime code
rtl
end
****************************************************************
*
* struct tm *localtime(t)
* time_t *t;
*
* Inputs:
* t - pointer to # of seconds since 13 Nov 1969
*
* Outputs:
* returns a pointer to a time record for local time
*
****************************************************************
*
localtime start
using TimeCommon
t equ 6
phd
tsc
tcd
ldy #2 dereference the pointer to time_t
lda [t],Y
tax
lda [t]
tay
pld
phb
pla move return address
sta 3,s
pla
sta 3,s
lda #-1 default DST setting = -1 (unknown)
cpy lasttime determine DST setting, if we can
bne lb1
cpx lasttime+2
bne lb1
lda lastDST
lb1 plb
ph4 #gmlocaltime_tm push address of struct tm to use
pha push tm_isdst value
phx push time_t value to convert
phy
pea 0 no time zone offset
pea 0
jsl ~gmlocaltime use common gmtime/localtime code
rtl
end
****************************************************************
*
* ~gmlocaltime - common code for gmtime and localtime
*
* Inputs:
* tz_offset - offset of local time from desired time zone
* t - time_t value (# of seconds since 13 Nov 1969)
* isdst - value for tm_isdst flag
* tm - pointer to struct tm for result
*
* Outputs:
* returns a pointer to a time record
*
****************************************************************
*
~gmlocaltime private
using TimeCommon
csubroutine (4:tz_offset,4:t,2:isdst,4:tm),0
phb
phk
plb
lda #69 find the year
sta year
lda #1
sta day
stz month
stz hour
stz minute
lda tz_offset
sta second
lda tz_offset+2
sta second+2
lb1 inc year
jsr factor_second32
lda count+4
bne lb1b
lda count+2
cmp t+2
bne lb1a
lda count
cmp t
lb1a ble lb1
lb1b dec year
lb2 inc month find the month
jsr factor_second32
lda count+4
bmi lb2
bne lb2b
lda count+2
cmp t+2
bne lb2a
lda count
cmp t
lb2a ble lb2
lb2b dec month
jsr factor_second32 recompute the factor
lda year set the year
ldy #tm_year
sta [tm],y
lda month set the month
ldy #tm_mon
sta [tm],y
ph4 <t save original t value
sub4 t,count find the number of seconds
move4 t,t1
div4 t,#60
mul4 t,#60,t2
sub4 t1,t2
lda t1
ldy #tm_sec
sta [tm],y
move4 t,t1 find the number of minutes
div4 t,#60
mul4 t,#60,t2
sub4 t1,t2
lda t1
ldy #tm_min
sta [tm],y
move4 t,t1 find the number of hours
div4 t,#24
mul4 t,#24,t2
sub4 t1,t2
lda t1
ldy #tm_hour
sta [tm],y
lda t set the day
inc A
ldy #tm_mday
sta [tm],y
pl4 t restore original t value
stz month compute the days since the start of the
jsr factor_second32 year (in desired time zone)
sub4 t,count,count
div4 count,#60*60*24
ldy #tm_yday set the day of year
lda count
sta [tm],y
lb3 cmpl t,#7*3000*60*60*24 compute the day of week
blt lb3a
sub4 t,#7*3000*60*60*24
bra lb3
lb3a add4 t,#4*60*60*24
sec (adjust for time zone)
lda t
sbc tz_offset
sta t
lda t+2
sbc tz_offset+2
sta t+2
div4 t,#60*60*24
mod4 t,#7
lda t set the day of week
ldy #tm_wday
sta [tm],y
lda isdst set the DST flag
ldy #tm_isdst
sta [tm],y
plb
creturn 4:tm
end
****************************************************************
*
* time_t mktime(tmptr)
* struct tm *tmptr
*
* Inputs:
* tmptr - pointer to a time record
*
* Outputs:
* tmptr->wday - day of week
* tmptr->yday - day of year
* returns the ime in seconds since 13 Nov 1969
*
****************************************************************
*
mktime start
using TimeCommon
temp equ 1 temp variable
temp2 equ 5 temp variable
csubroutine (4:tmptr),8
phb
phk
plb
ldy #tm_year set time parameters
lda [tmptr],Y
sta year
dey
dey
lda [tmptr],Y
sta month
dey
dey
lda [tmptr],Y
sta day
dey
dey
lda [tmptr],Y
sta hour
dey
dey
lda [tmptr],Y
sta minute
lda [tmptr]
sta second
jsr factor compute seconds since 13 Nov 1969
lda count+4 if time is unrepresentable
ora count+6
beq lb0
lda #-1 return -1
sta temp
sta temp+2
brl lb1
lb0 move4 count,temp save the value for later return
ph4 <tmptr recompute struct tm values
ldy #tm_isdst
lda [tmptr],y
pha
ph4 <temp
ph4 #0
jsl ~gmlocaltime
lb1 plb
creturn 4:temp
end
****************************************************************
*
* time_t time(tptr)
* time_t *tptr;
*
* Outputs:
* tptr - if non-null, the value it points to is set
* time - returns the value
*
****************************************************************
*
time start
using TimeCommon
csubroutine (4:tptr),0
phb
phk
plb
;
; get the current time
;
pha get the current time
pha
pha
pha
_ReadTimeHex
lda 5,S set the day
and #$00FF
inc A
sta day
lda 6,S set the month
and #$00FF
sta month
lda 4,S set the year
and #$00FF
sta year
lda 3,S set the hour
and #$00FF
sta hour
lda 2,S set the minute
and #$00FF
sta minute
pla set the second
and #$00FF
sta second
pla clean up the stack
pla
pla
jsr factor convert the seconds
lda count+4 if time is unrepresentable
ora count+6
beq lb0
lda #-1 set return value to -1
sta count
sta count+2
lb0 lda tptr if tptr <> nil then
ora tptr+2
beq lb1
ldy #2 place the result there
lda count
sta [tptr]
lda count+2
sta [tptr],Y
lb1 lda count
sta tptr
sta lasttime
lda count+2
sta tptr+2
sta lasttime+2
pha determine if it's daylight savings
ph2 #$5E
_ReadBParam
pla
lsr A
and #$0001
eor #$0001
sta lastDST
plb
creturn 4:tptr
end
****************************************************************
*
* int timespec_get(struct timespec *ts, int base);
*
* Inputs:
* ts - pointer to structure for result
* base - requested time base
*
* Outputs:
* *tptr - the requested time (if successful)
* returns base if successful, or 0 otherwise
*
****************************************************************
*
timespec_get start
using TimeCommon
tz_offset equ 1 time zone offset from UTC
current_time equ 5 current time
TIME_UTC equ 1 UTC time base
tv_sec equ 0 struct timespec members
tv_nsec equ 4
csubroutine (4:ts,2:base),8
lda base
cmp #TIME_UTC
bne err
ph4 #0 get current time (in count)
jsl time
sta current_time
stx current_time+2
and current_time+2 if time is not available
inc a
beq err report error
jsr ~get_tz_offset get time zone offset
sta tz_offset
stx tz_offset+2
sec adjust for time zone & store result
lda current_time
sbc tz_offset
sta [ts]
lda current_time+2
sbc tz_offset+2
ldy #tv_sec+2
sta [ts],y
ldy #tv_nsec ts->tv_nsec = 0
lda #0
sta [ts],y
iny
iny
sta [ts],y
bra ret
err stz base unsupported base: return 0
ret creturn 2:base
end
****************************************************************
*
* size_t strftime(
* char * restrict s,
* size_t maxsize,
* const char * restrict format,
* const struct tm * restrict timeptr);
*
* Inputs:
* s - pointer to output buffer
* maxsize - max number of bytes to write
* format - format string
* timeptr - the time/date
*
* Outputs:
* s - formatted string representation of the time/date
* returns length of s (not including terminating null),
* or 0 if maxsize is too small
*
****************************************************************
*
strftime start
csubroutine (4:s,4:maxsize,4:format,4:timeptr),14
substfmt equ 1 substitute format str (used if non-null)
s_orig equ substfmt+2 original s pointer (start of output str)
overflow equ s_orig+4 overflow flag
numstr equ overflow+2 string representation of a number
numstr_len equ 6 length of numstr
tm_sec equ 0 displacements into the time record
tm_min equ 2
tm_hour equ 4
tm_mday equ 6
tm_mon equ 8
tm_year equ 10
tm_wday equ 12
tm_yday equ 14
tm_isdst equ 16
phb set data bank = program bank
phk
plb
;initialization of local variables
stz substfmt substfmt = 0
lda s s_orig = s
sta s_orig
lda s+2
sta s_orig+2
stz overflow overflow = false
;main loop to process the format string
fmtloop jsr nextch get next character in format
cmp #'%' if it is not '%'
beq dosubst
nonfmt jsr writech write it to the output
bra fmtloop continue format loop
dosubst jsr nextch get next character in format
cmp #'E' if it is 'E' or 'O'
beq skipalt
cmp #'O'
bne dofmt
skipalt jsr nextch skip it
dofmt cmp #'%' if format character is '%'
beq nonfmt write it like an ordinary character
cmp #'@' if fmt chr is outside valid range
blt fmtloop skip it
cmp #'z'+1
bge fmtloop
and #$003f if we are here, fmt chr is in ['@'..'z']
asl a convert to jump table position
asl a
tax
lda fmttbl+2,x if there is a substitution
beq fmtcall
sta substfmt do the substitution
bra fmtloop
fmtcall jsr (fmttbl,x) otherwise, call the format routine
bra fmtloop continue format loop
;subroutine to get next character in format string (call only from main loop)
;returns with character in a, or exits via strftime_return if character is 0
nextch lda substfmt if there is a substitute format string
beq nosubst
lda (substfmt) get next character from it
inc substfmt advance subst string pointer
and #$00FF
bne retchar if at end of substitute format string
stz substfmt go back to using main format string
nosubst lda [format] get next character from main fmt string
and #$00FF
beq strftime_return if char is '\0', return from strftime
inc4 format advance fmt string pointer
retchar rts return from nextch
;code to return from strftime
strftime_return anop
jsr writech write '\0' to output
pla discard nextch return address
lda overflow if there was an overflow
beq ret_good
stz maxsize maxsize = 0
stz maxsize+2
bra ret
ret_good clc else
lda s maxsize = s - s_orig - 1
sbc s_orig
sta maxsize
lda s+2
sbc s_orig+2
sta maxsize+2
ret plb restore program bank
creturn 4:maxsize return maxsize
;subroutine to write a character to the output
;input: character in low-order byte of a (high-order byte is ignored)
;leaves x unchanged
writech ldy maxsize if remaining size is 0
bne writeok
ldy maxsize+2
bne writeok
lda #1 set overflow flag
sta overflow
rts return
writeok short M write the character to s
sta [s]
long M
inc4 s s++
dec4 maxsize maxsize--
rts return
;table of formatting routines or substitutions for the conversion specifiers
;first ptr is a routine, second is a subst string - only one should be non-zero
fmttbl anop
dc a2'fmt_invalid,0' @
dc a2'fmt_A,0' A
dc a2'fmt_B,0' B
dc a2'fmt_C,0' C
dc a2'0,subst_D' D
dc a2'fmt_invalid,0' E
dc a2'0,subst_F' F
dc a2'fmt_G,0' G
dc a2'fmt_H,0' H
dc a2'fmt_I,0' I
dc a2'fmt_invalid,0' J
dc a2'fmt_invalid,0' K
dc a2'fmt_invalid,0' L
dc a2'fmt_M,0' M
dc a2'fmt_invalid,0' N
dc a2'fmt_invalid,0' O
dc a2'fmt_invalid,0' P
dc a2'fmt_invalid,0' Q
dc a2'0,subst_R' R
dc a2'fmt_S,0' S
dc a2'0,subst_T' T
dc a2'fmt_U,0' U
dc a2'fmt_V,0' V
dc a2'fmt_W,0' W
dc a2'0,subst_X' X
dc a2'fmt_Y,0' Y
dc a2'fmt_Z,0' Z
dc a2'fmt_invalid,0' [
dc a2'fmt_invalid,0' \
dc a2'fmt_invalid,0' ]
dc a2'fmt_invalid,0' ^
dc a2'fmt_invalid,0' _
dc a2'fmt_invalid,0' `
dc a2'fmt_a,0' a
dc a2'fmt_b,0' b
dc a2'0,subst_c' c
dc a2'fmt_d,0' d
dc a2'fmt_e,0' e
dc a2'fmt_invalid,0' f
dc a2'fmt_g,0' g
dc a2'fmt_h,0' h
dc a2'fmt_invalid,0' i
dc a2'fmt_j,0' j
dc a2'fmt_invalid,0' k
dc a2'fmt_invalid,0' l
dc a2'fmt_m,0' m
dc a2'fmt_n,0' n
dc a2'fmt_invalid,0' o
dc a2'fmt_p,0' p
dc a2'fmt_invalid,0' q
dc a2'0,subst_r' r
dc a2'fmt_invalid,0' s
dc a2'fmt_t,0' t
dc a2'fmt_u,0' u
dc a2'fmt_invalid,0' v
dc a2'fmt_w,0' w
dc a2'0,subst_x' x
dc a2'fmt_y,0' y
dc a2'fmt_z,0' z
;%a - abbreviated weekday name
fmt_a ldy #tm_wday
lda [timeptr],y
asl a
tay
ldx weekdays,y
lda |0,x
jsr writech
lda |1,x
jsr writech
lda |2,x
brl writech
;%A - full weekday name
fmt_A ldy #tm_wday
lda [timeptr],y
asl a
tay
ldx weekdays,y
A_loop lda |0,x
and #$00FF
beq A_ret
jsr writech
inx
bra A_loop
A_ret rts
;%b - abbreviated month name
fmt_b ldy #tm_mon
lda [timeptr],y
asl a
tay
ldx months,y
lda |0,x
jsr writech
lda |1,x
jsr writech
lda |2,x
brl writech
;%B - full month name
fmt_B ldy #tm_mon
lda [timeptr],y
asl a
tay
ldx months,y
B_loop lda |0,x
and #$00FF
beq A_ret
jsr writech
inx
bra A_loop
B_ret rts
;%c - date and time
subst_c dc c'%a %b %e %H:%M:%S %Y',i1'0'
;%C - century
fmt_C jsr format_year
ldx #0
C_loop lda numstr,x
and #$00FF
cmp #' '
beq C_skip
jsr writech
C_skip inx
cpx #numstr_len-2
blt C_loop
rts
;%d - day of the month (01-31)
fmt_d ldy #tm_mday
brl print2digits_of_field
;%D - equivalent to %m/%d/%y
subst_D dc c'%m/%d/%y',i1'0'
;%e - day of the month (1-31, padded with space if a single digit)
fmt_e ldy #tm_mday
lda [timeptr],y
ldy #2
cmp #10
bge e_print
tax
lda #' '
jsr writech
txa
ldy #1
e_print brl printdigits
;%F - equivalent to %Y-%m-%d
subst_F dc c'%Y-%m-%d',i1'0'
;%g - last two digits of week-based year
fmt_g jsr week_number_V
jsr format_year_altbase
brl write_year_2digit
;%G - week-based year
fmt_G jsr week_number_V
jsr format_year_altbase
brl write_year
;%h - equivalent to %b
fmt_h brl fmt_b
;%H - hour (24-hour clock, 00-23)
fmt_H ldy #tm_hour
brl print2digits_of_field
;%I - hour (12-hour clock, 01-12)
fmt_I ldy #tm_hour
lda [timeptr],y
bne I_adjust
lda #12
I_adjust cmp #12+1
blt I_print
sbc #12
I_print brl print2digits
;%j - day of the year (001-366)
fmt_j ldy #tm_yday
lda [timeptr],y
inc a
ldy #3
brl printdigits
;%m - month number
fmt_m ldy #tm_mon
lda [timeptr],y
inc a
brl print2digits
;%M - minute
fmt_M ldy #tm_min
brl print2digits_of_field
;%n - new-line character
fmt_n lda #$0A
brl writech
;%p - AM/PM
fmt_p ldy #tm_hour
lda [timeptr],y
cmp #12
bge p_pm
lda #'A'
bra p_write
p_pm lda #'P'
p_write jsr writech
lda #'M'
brl writech
;%r - time (using 12-hour clock)
subst_r dc c'%I:%M:%S %p',i1'0'
;%R - equivalent to %H:%M
subst_R dc c'%H:%M',i1'0'
;%S - seconds
fmt_S ldy #tm_sec
brl print2digits_of_field
;%t - horizontal tab character
fmt_t lda #$09
brl writech
;%T - equivalent to %H:%M:%S
subst_T dc c'%H:%M:%S',i1'0'
;%u - weekday number (1-7, Monday=1)
fmt_u ldy #tm_wday
lda [timeptr],y
bne u_print
lda #7
u_print ldy #1
brl printdigits
;%U - week number of the year (first Sunday starts week 01)
fmt_U ldy #tm_yday
lda [timeptr],y
clc
adc #7
sec
ldy #tm_wday
sbc [timeptr],y
jsr div7
tya
brl print2digits
;%V - ISO 8601 week number
fmt_V jsr week_number_V
txa
brl print2digits
;%w - weekday number (0-6, 0=Sunday)
fmt_w ldy #tm_wday
lda [timeptr],y
ldy #1
brl printdigits
;%W - week number of the year (first Monday starts week 01)
fmt_W jsr week_number_W
tya
brl print2digits
;%x - date
subst_x dc c'%m/%d/%y',i1'0'
;%X - time
subst_X dc c'%T',i1'0'
;%y - last two digits of year
fmt_y jsr format_year
write_year_2digit anop
lda numstr+4
jsr writech
lda numstr+5
brl writech
;%Y - year
fmt_Y jsr format_year
write_year anop
ldx #0
Y_loop lda numstr,x
and #$00FF
cmp #' '
beq Y_skip
jsr writech
Y_skip inx
cpx #numstr_len
blt Y_loop
rts
;%z - offset from UTC, if available
;%Z - time zone name or abbreviation, if available
;we print the numeric offset for both, or nothing if time zone is not available
fmt_z anop
fmt_Z lda >__useTimeTool if not using Time Tool
beq z_ret write nothing
pea 0 push pointer to string buffer
tdc
clc
adc #numstr
pha
pha make space for TZ preferences record
pha
pea 1 get one record element only (TZ offset)
tsc get time zone preference
inc a
pea 0
pha
_tiGetTimePrefs
pla
bcc z_dst
z_bail pla bail out in case of error
pla
pla
pla
z_ret rts
z_dst ldy #tm_isdst adjust for DST (+1 hour) if needed
lda [timeptr],y
bmi z_bail bail out if DST is unknown
beq z_fmtstr
; clc
pla
adc #60*60
tay
pla
adc #0
pha
phy
z_fmtstr pea 0 no DST mangling
_tiOffset2TimeZoneString get TZ offset string
bcs z_ret
ldx #1
z_loop lda numstr,x print the digits
jsr writech
inx
cpx #5+1
blt z_loop
rts
fmt_invalid rts
;get decimal representation of the year in numstr
;the string is adjusted to have at least four digits
format_year anop
lda #1900
format_year_altbase anop alt entry point using year base in a
ldx #1 default to signed
clc
ldy #tm_year
adc [timeptr],y
bvc year_ok
ldx #0 use unsigned if signed value overflows
year_ok jsr int2dec
short M,I
ldx #4
yr_adjlp lda numstr,x adjust year to have >= 4 digits
cmp #'-'
bne yr_adj1
sta numstr-1,x
bra yr_adj2
yr_adj1 cmp #' '
bne yr_adj3
yr_adj2 lda #'0'
sta numstr,x
yr_adj3 dex
cpx #2
bge yr_adjlp
long M,I
rts
;get the week number as for %W (first Monday starts week 1)
;output: week number in y
week_number_W anop
ldy #tm_wday
lda [timeptr],y
beq W_yday
sec
lda #7
sbc [timeptr],y
W_yday sec
ldy #tm_yday
adc [timeptr],y
brl div7
;get the ISO 8601 week number (as for %V) and corresponding year adjustment
;output: week number in x, adjusted year base in a (1900-1, 1900, or 1900+1)
week_number_V anop
jsr week_number_W get %W-style week number (kept in x)
tyx
ldy #tm_wday calculate wday for Jan 1 (kept in a)
lda [timeptr],y
sec
ldy #tm_yday
sbc [timeptr],y
clc
adc #53*7
jsr div7
cmp #2 if Jan 1 was Tue/Wed/Thu
blt V_adjust
cmp #4+1
bge V_adjust
inx inc week (week 1 started in last year)
V_adjust txy
bne V_not0 week 0 is really 52 or 53 of last year:
ldx #52 assume 52
cmp #5 if Jan 1 is Fri
bne V_0notfr
inx last year had week 53
bra V_0done
V_0notfr cmp #6 else if Jan 1 is Sat
bne V_0done
ldy #tm_year
lda [timeptr],y
dec a
jsr leapyear if last year was a leap year
bne V_0done
inx last year had week 53
V_0done lda #-1+1900 year adjustment is -1
bra V_done
V_not0 cpx #53 week 53 might be week 1 of next year:
bne V_noadj
cmp #4 if Jan 1 was Thu
beq V_noadj it is week 53
cmp #3 else if Jan 1 was Wed
bne V_53is1
ldy #tm_year
lda [timeptr],y
jsr leapyear and this is a leap year
beq V_noadj it is week 53
V_53is1 ldx #1 otherwise, it is really week 1
lda #1+1900 and year adjustment is +1
rts
V_noadj lda #0+1900 if we get here, year adjustment is 0
V_done rts
;check if a year is a leap year
;input: tm_year value in a
;output: z flag set if a leap year, clear if not; x,y unmodified
leapyear and #$0003 not multiple of 4 => not leap year
bne ly_done
clc calculate year mod 400
adc #1900-1600
bpl ly_lp400
clc
adc #32800
sec
ly_lp400 sbc #400
bcs ly_lp400
adc #400
beq ly_done multiple of 400 => leap year
sec
ly_lp100 sbc #100
bcs ly_lp100
cmp #-100
bne ly_leap
dec a other multiple of 100 => not leap year
rts
ly_leap lda #0 other multiple of 4 => leap year
ly_done rts
;divide a number (treated as unsigned) by 7
;input: dividend in a
;output: quotient in y, remainder in a, x unmodified
div7 ldy #-1
sec
sublp iny
sbc #7
bcs sublp
adc #7
rts
;print the low-order two digits of a field of struct tm
;(with leading zeros, if any)
;input: offset of field in y
print2digits_of_field anop
lda [timeptr],y load the field
;print the low-order two digits of a number (with leading zeros, if any)
;input: number in a
print2digits anop
ldy #2 print two digits
;print the low-order digits of a number (with leading zeros, if any)
;input: number in a, how many digits to print in y
printdigits anop
pd1 phy save number of digits to print
ldx #0 treat as signed
jsr int2dec convert to decimal string
sec calculate where to print from
lda #numstr_len
sbc 1,s
ply
tax
pd_loop lda numstr,x print the digits
and #$00FF
cmp #' ' change padding spaces to zeros
bne pd_write
lda #'0'
pd_write jsr writech
inx
cpx #numstr_len
blt pd_loop
rts
;get decimal representation of a number, placed in numstr
;input: number in a, signed flag in y
int2dec pha number to convert
pea 0000 pointer to string buffer
tdc
clc
adc #numstr
pha
pea numstr_len length of string buffer
phx signed flag
_Int2Dec
rts
weekdays dc a2'sun,mon,tue,wed,thu,fri,sat'
sun dc c'Sunday',i1'0'
mon dc c'Monday',i1'0'
tue dc c'Tuesday',i1'0'
wed dc c'Wednesday',i1'0'
thu dc c'Thursday',i1'0'
fri dc c'Friday',i1'0'
sat dc c'Saturday',i1'0'
months dc a2'jan,feb,mar,apr,may,jun,jul,aug,sep,oct,nov,dec'
jan dc c'January',i1'0'
feb dc c'February',i1'0'
mar dc c'March',i1'0'
apr dc c'April',i1'0'
may dc c'May',i1'0'
jun dc c'June',i1'0'
jul dc c'July',i1'0'
aug dc c'August',i1'0'
sep dc c'September',i1'0'
oct dc c'October',i1'0'
nov dc c'November',i1'0'
dec dc c'December',i1'0'
end