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 __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 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 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