From 137e93319e7865d4d250ce35df7be51d8c215a4b Mon Sep 17 00:00:00 2001 From: xushiwei Date: Wed, 26 Jun 2024 19:36:39 +0800 Subject: [PATCH] c/time; patch: time --- c/c.go | 23 +- c/pthread/sync/sync.go | 4 +- c/time/time.go | 98 ++++++++- internal/lib/time/time.go | 74 +++++++ internal/lib/time/zoneinfo.go | 339 +++++++++++++++++++++++++++++ internal/lib/time/zoneinfo_read.go | 70 +++--- 6 files changed, 562 insertions(+), 46 deletions(-) diff --git a/c/c.go b/c/c.go index e66406f7..9b7e9854 100644 --- a/c/c.go +++ b/c/c.go @@ -17,6 +17,9 @@ package c // typedef unsigned int uint; +// typedef unsigned long ulong; +// typedef unsigned long long ulonglong; +// typedef long long longlong; import "C" import "unsafe" @@ -25,20 +28,22 @@ const ( ) type ( - Char = int8 - Long = int32 - Ulong = uint32 - LongLong = int64 - UlongLong = uint64 - Float = float32 - Double = float64 - Pointer = unsafe.Pointer - FilePtr = unsafe.Pointer + Char = int8 + Float = float32 + Double = float64 + Pointer = unsafe.Pointer + FilePtr = unsafe.Pointer ) type ( Int C.int Uint C.uint + + Long C.long + Ulong C.ulong + + LongLong C.longlong + UlongLong C.ulonglong ) type integer interface { diff --git a/c/pthread/sync/sync.go b/c/pthread/sync/sync.go index 16664719..33679a54 100644 --- a/c/pthread/sync/sync.go +++ b/c/pthread/sync/sync.go @@ -143,10 +143,10 @@ func (a *CondAttr) Init(attr *CondAttr) c.Int { return 0 } func (a *CondAttr) Destroy() {} // llgo:link (*CondAttr).SetClock C.pthread_condattr_setclock -func (a *CondAttr) SetClock(clock time.ClockID) c.Int { return 0 } +func (a *CondAttr) SetClock(clock time.ClockidT) c.Int { return 0 } // llgo:link (*CondAttr).GetClock C.pthread_condattr_getclock -func (a *CondAttr) GetClock(clock *time.ClockID) c.Int { return 0 } +func (a *CondAttr) GetClock(clock *time.ClockidT) c.Int { return 0 } // ----------------------------------------------------------------------------- diff --git a/c/time/time.go b/c/time/time.go index e650c383..cac6aa8d 100644 --- a/c/time/time.go +++ b/c/time/time.go @@ -21,6 +21,8 @@ import "C" import ( _ "unsafe" + + "github.com/goplus/llgo/c" ) const ( @@ -29,10 +31,102 @@ const ( // ----------------------------------------------------------------------------- -type Timespec C.struct_timespec +type TimeT C.time_t + +//go:linkname Time C.time +func Time(timer *TimeT) TimeT + +//go:linkname Mktime C.mktime +func Mktime(timer *Tm) TimeT + +//go:linkname Ctime C.ctime +func Ctime(timer *TimeT) string + +//go:linkname Difftime C.difftime +func Difftime(end, start TimeT) float64 // ----------------------------------------------------------------------------- -type ClockID C.clockid_t +type Tm struct { + Sec c.Int + Min c.Int + Hour c.Int + Mday c.Int + Mon c.Int + Year c.Int + Wday c.Int + Yday c.Int + Isdst c.Int + Gmtoff c.Long + Zone *c.Char +} + +//go:linkname Gmtime C.gmtime +func Gmtime(timer *TimeT) *Tm + +//go:linkname Localtime C.localtime +func Localtime(timer *TimeT) *Tm + +//go:linkname Strftime C.strftime +func Strftime(buf *c.Char, bufSize uintptr, format *c.Char, timeptr *Tm) uintptr + +// ----------------------------------------------------------------------------- + +type ClockT C.clock_t + +//go:linkname Clock C.clock +func Clock() ClockT + +// ----------------------------------------------------------------------------- + +type ClockidT C.clockid_t + +const ( + // the system's real time (i.e. wall time) clock, expressed as the amount of time since the Epoch. + // This is the same as the value returned by gettimeofday + CLOCK_REALTIME = ClockidT(C.CLOCK_REALTIME) + + // clock that increments monotonically, tracking the time since an arbitrary point, and will continue + // to increment while the system is asleep. + CLOCK_MONOTONIC = ClockidT(C.CLOCK_MONOTONIC) + + // clock that increments monotonically, tracking the time since an arbitrary point like CLOCK_MONOTONIC. + // However, this clock is unaffected by frequency or time adjustments. It should not be compared to + // other system time sources. + CLOCK_MONOTONIC_RAW = ClockidT(C.CLOCK_MONOTONIC_RAW) + + // like CLOCK_MONOTONIC_RAW, but reads a value cached by the system at context switch. This can be + // read faster, but at a loss of accuracy as it may return values that are milliseconds old. + CLOCK_MONOTONIC_RAW_APPROX = ClockidT(C.CLOCK_MONOTONIC_RAW_APPROX) + + // clock that increments monotonically, in the same manner as CLOCK_MONOTONIC_RAW, but that does + // not increment while the system is asleep. The returned value is identical to the result of + // mach_absolute_time() after the appropriate mach_timebase conversion is applied. + CLOCK_UPTIME_RAW = ClockidT(C.CLOCK_UPTIME_RAW) + + // like CLOCK_UPTIME_RAW, but reads a value cached by the system at context switch. This can be read + // faster, but at a loss of accuracy as it may return values that are milliseconds old. + CLOCK_UPTIME_RAW_APPROX = ClockidT(C.CLOCK_UPTIME_RAW_APPROX) + + // clock that tracks the amount of CPU (in user- or kernel-mode) used by the calling process. + CLOCK_PROCESS_CPUTIME_ID = ClockidT(C.CLOCK_PROCESS_CPUTIME_ID) + + // clock that tracks the amount of CPU (in user- or kernel-mode) used by the calling thread. + CLOCK_THREAD_CPUTIME_ID = ClockidT(C.CLOCK_THREAD_CPUTIME_ID) +) + +type Timespec struct { + Sec TimeT // seconds + Nsec c.Long // and nanoseconds +} + +//go:linkname ClockGettime C.clock_gettime +func ClockGettime(clkId ClockidT, tp *Timespec) c.Int + +//go:linkname ClockSettime C.clock_settime +func ClockSettime(clkId ClockidT, tp *Timespec) c.Int + +//go:linkname ClockGetres C.clock_getres +func ClockGetres(clkId ClockidT, res *Timespec) c.Int // ----------------------------------------------------------------------------- diff --git a/internal/lib/time/time.go b/internal/lib/time/time.go index 61f27ec7..734bf0be 100644 --- a/internal/lib/time/time.go +++ b/internal/lib/time/time.go @@ -255,6 +255,80 @@ const ( daysPer4Years = 365*4 + 1 ) +// absDate is like date but operates on an absolute time. +func absDate(abs uint64, full bool) (year int, month Month, day int, yday int) { + // Split into time and day. + d := abs / secondsPerDay + + // Account for 400 year cycles. + n := d / daysPer400Years + y := 400 * n + d -= daysPer400Years * n + + // Cut off 100-year cycles. + // The last cycle has one extra leap year, so on the last day + // of that year, day / daysPer100Years will be 4 instead of 3. + // Cut it back down to 3 by subtracting n>>2. + n = d / daysPer100Years + n -= n >> 2 + y += 100 * n + d -= daysPer100Years * n + + // Cut off 4-year cycles. + // The last cycle has a missing leap year, which does not + // affect the computation. + n = d / daysPer4Years + y += 4 * n + d -= daysPer4Years * n + + // Cut off years within a 4-year cycle. + // The last year is a leap year, so on the last day of that year, + // day / 365 will be 4 instead of 3. Cut it back down to 3 + // by subtracting n>>2. + n = d / 365 + n -= n >> 2 + y += n + d -= 365 * n + + year = int(int64(y) + absoluteZeroYear) + yday = int(d) + + if !full { + return + } + + day = yday + if isLeap(year) { + // Leap year + switch { + case day > 31+29-1: + // After leap day; pretend it wasn't there. + day-- + case day == 31+29-1: + // Leap day. + month = February + day = 29 + return + } + } + + // Estimate month on assumption that every month has 31 days. + // The estimate may be too low by at most one month, so adjust. + month = Month(day / 31) + end := int(daysBefore[month+1]) + var begin int + if day >= end { + month++ + begin = end + } else { + begin = int(daysBefore[month]) + } + + month++ // because January is 1 + day = day - begin + 1 + return +} + // daysBefore[m] counts the number of days in a non-leap year // before month m begins. There is an entry for m=12, counting // the number of days before January of next year (365). diff --git a/internal/lib/time/zoneinfo.go b/internal/lib/time/zoneinfo.go index b43d2ccc..069a36c8 100644 --- a/internal/lib/time/zoneinfo.go +++ b/internal/lib/time/zoneinfo.go @@ -172,3 +172,342 @@ func (l *Location) lookup(sec int64) (name string, offset int, start, end int64, */ panic("todo: Location.lookup") } + +// tzset takes a timezone string like the one found in the TZ environment +// variable, the time of the last time zone transition expressed as seconds +// since January 1, 1970 00:00:00 UTC, and a time expressed the same way. +// We call this a tzset string since in C the function tzset reads TZ. +// The return values are as for lookup, plus ok which reports whether the +// parse succeeded. +func tzset(s string, lastTxSec, sec int64) (name string, offset int, start, end int64, isDST, ok bool) { + var ( + stdName, dstName string + stdOffset, dstOffset int + ) + + stdName, s, ok = tzsetName(s) + if ok { + stdOffset, s, ok = tzsetOffset(s) + } + if !ok { + return "", 0, 0, 0, false, false + } + + // The numbers in the tzset string are added to local time to get UTC, + // but our offsets are added to UTC to get local time, + // so we negate the number we see here. + stdOffset = -stdOffset + + if len(s) == 0 || s[0] == ',' { + // No daylight savings time. + return stdName, stdOffset, lastTxSec, omega, false, true + } + + dstName, s, ok = tzsetName(s) + if ok { + if len(s) == 0 || s[0] == ',' { + dstOffset = stdOffset + secondsPerHour + } else { + dstOffset, s, ok = tzsetOffset(s) + dstOffset = -dstOffset // as with stdOffset, above + } + } + if !ok { + return "", 0, 0, 0, false, false + } + + if len(s) == 0 { + // Default DST rules per tzcode. + s = ",M3.2.0,M11.1.0" + } + // The TZ definition does not mention ';' here but tzcode accepts it. + if s[0] != ',' && s[0] != ';' { + return "", 0, 0, 0, false, false + } + s = s[1:] + + var startRule, endRule rule + startRule, s, ok = tzsetRule(s) + if !ok || len(s) == 0 || s[0] != ',' { + return "", 0, 0, 0, false, false + } + s = s[1:] + endRule, s, ok = tzsetRule(s) + if !ok || len(s) > 0 { + return "", 0, 0, 0, false, false + } + + year, _, _, yday := absDate(uint64(sec+unixToInternal+internalToAbsolute), false) + + ysec := int64(yday*secondsPerDay) + sec%secondsPerDay + + // Compute start of year in seconds since Unix epoch. + d := daysSinceEpoch(year) + abs := int64(d * secondsPerDay) + abs += absoluteToInternal + internalToUnix + + startSec := int64(tzruleTime(year, startRule, stdOffset)) + endSec := int64(tzruleTime(year, endRule, dstOffset)) + dstIsDST, stdIsDST := true, false + // Note: this is a flipping of "DST" and "STD" while retaining the labels + // This happens in southern hemispheres. The labelling here thus is a little + // inconsistent with the goal. + if endSec < startSec { + startSec, endSec = endSec, startSec + stdName, dstName = dstName, stdName + stdOffset, dstOffset = dstOffset, stdOffset + stdIsDST, dstIsDST = dstIsDST, stdIsDST + } + + // The start and end values that we return are accurate + // close to a daylight savings transition, but are otherwise + // just the start and end of the year. That suffices for + // the only caller that cares, which is Date. + if ysec < startSec { + return stdName, stdOffset, abs, startSec + abs, stdIsDST, true + } else if ysec >= endSec { + return stdName, stdOffset, endSec + abs, abs + 365*secondsPerDay, stdIsDST, true + } else { + return dstName, dstOffset, startSec + abs, endSec + abs, dstIsDST, true + } +} + +// tzsetName returns the timezone name at the start of the tzset string s, +// and the remainder of s, and reports whether the parsing is OK. +func tzsetName(s string) (string, string, bool) { + if len(s) == 0 { + return "", "", false + } + if s[0] != '<' { + for i, r := range s { + switch r { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ',', '-', '+': + if i < 3 { + return "", "", false + } + return s[:i], s[i:], true + } + } + if len(s) < 3 { + return "", "", false + } + return s, "", true + } else { + for i, r := range s { + if r == '>' { + return s[1:i], s[i+1:], true + } + } + return "", "", false + } +} + +// tzsetOffset returns the timezone offset at the start of the tzset string s, +// and the remainder of s, and reports whether the parsing is OK. +// The timezone offset is returned as a number of seconds. +func tzsetOffset(s string) (offset int, rest string, ok bool) { + if len(s) == 0 { + return 0, "", false + } + neg := false + if s[0] == '+' { + s = s[1:] + } else if s[0] == '-' { + s = s[1:] + neg = true + } + + // The tzdata code permits values up to 24 * 7 here, + // although POSIX does not. + var hours int + hours, s, ok = tzsetNum(s, 0, 24*7) + if !ok { + return 0, "", false + } + off := hours * secondsPerHour + if len(s) == 0 || s[0] != ':' { + if neg { + off = -off + } + return off, s, true + } + + var mins int + mins, s, ok = tzsetNum(s[1:], 0, 59) + if !ok { + return 0, "", false + } + off += mins * secondsPerMinute + if len(s) == 0 || s[0] != ':' { + if neg { + off = -off + } + return off, s, true + } + + var secs int + secs, s, ok = tzsetNum(s[1:], 0, 59) + if !ok { + return 0, "", false + } + off += secs + + if neg { + off = -off + } + return off, s, true +} + +// ruleKind is the kinds of rules that can be seen in a tzset string. +type ruleKind int + +const ( + ruleJulian ruleKind = iota + ruleDOY + ruleMonthWeekDay +) + +// rule is a rule read from a tzset string. +type rule struct { + kind ruleKind + day int + week int + mon int + time int // transition time +} + +// tzsetRule parses a rule from a tzset string. +// It returns the rule, and the remainder of the string, and reports success. +func tzsetRule(s string) (rule, string, bool) { + var r rule + if len(s) == 0 { + return rule{}, "", false + } + ok := false + if s[0] == 'J' { + var jday int + jday, s, ok = tzsetNum(s[1:], 1, 365) + if !ok { + return rule{}, "", false + } + r.kind = ruleJulian + r.day = jday + } else if s[0] == 'M' { + var mon int + mon, s, ok = tzsetNum(s[1:], 1, 12) + if !ok || len(s) == 0 || s[0] != '.' { + return rule{}, "", false + + } + var week int + week, s, ok = tzsetNum(s[1:], 1, 5) + if !ok || len(s) == 0 || s[0] != '.' { + return rule{}, "", false + } + var day int + day, s, ok = tzsetNum(s[1:], 0, 6) + if !ok { + return rule{}, "", false + } + r.kind = ruleMonthWeekDay + r.day = day + r.week = week + r.mon = mon + } else { + var day int + day, s, ok = tzsetNum(s, 0, 365) + if !ok { + return rule{}, "", false + } + r.kind = ruleDOY + r.day = day + } + + if len(s) == 0 || s[0] != '/' { + r.time = 2 * secondsPerHour // 2am is the default + return r, s, true + } + + offset, s, ok := tzsetOffset(s[1:]) + if !ok { + return rule{}, "", false + } + r.time = offset + + return r, s, true +} + +// tzsetNum parses a number from a tzset string. +// It returns the number, and the remainder of the string, and reports success. +// The number must be between min and max. +func tzsetNum(s string, min, max int) (num int, rest string, ok bool) { + if len(s) == 0 { + return 0, "", false + } + num = 0 + for i, r := range s { + if r < '0' || r > '9' { + if i == 0 || num < min { + return 0, "", false + } + return num, s[i:], true + } + num *= 10 + num += int(r) - '0' + if num > max { + return 0, "", false + } + } + if num < min { + return 0, "", false + } + return num, "", true +} + +// tzruleTime takes a year, a rule, and a timezone offset, +// and returns the number of seconds since the start of the year +// that the rule takes effect. +func tzruleTime(year int, r rule, off int) int { + var s int + switch r.kind { + case ruleJulian: + s = (r.day - 1) * secondsPerDay + if isLeap(year) && r.day >= 60 { + s += secondsPerDay + } + case ruleDOY: + s = r.day * secondsPerDay + case ruleMonthWeekDay: + // Zeller's Congruence. + m1 := (r.mon+9)%12 + 1 + yy0 := year + if r.mon <= 2 { + yy0-- + } + yy1 := yy0 / 100 + yy2 := yy0 % 100 + dow := ((26*m1-2)/10 + 1 + yy2 + yy2/4 + yy1/4 - 2*yy1) % 7 + if dow < 0 { + dow += 7 + } + // Now dow is the day-of-week of the first day of r.mon. + // Get the day-of-month of the first "dow" day. + d := r.day - dow + if d < 0 { + d += 7 + } + for i := 1; i < r.week; i++ { + if d+7 >= daysIn(Month(r.mon), year) { + break + } + d += 7 + } + d += int(daysBefore[r.mon-1]) + if isLeap(year) && r.mon > 2 { + d++ + } + s = d * secondsPerDay + } + + return s + r.time - off +} diff --git a/internal/lib/time/zoneinfo_read.go b/internal/lib/time/zoneinfo_read.go index 60e11b0c..c32d1a7c 100644 --- a/internal/lib/time/zoneinfo_read.go +++ b/internal/lib/time/zoneinfo_read.go @@ -13,6 +13,8 @@ import ( "errors" "runtime" "syscall" + + "github.com/goplus/llgo/c/time" ) // registerLoadFromEmbeddedTZData is called by the time/tzdata package, @@ -313,46 +315,42 @@ func LoadLocationFromTZData(name string, data []byte) (*Location, error) { tx = append(tx, zoneTrans{when: alpha, index: 0}) } - /* - // Committed to succeed. - l := &Location{zone: zones, tx: tx, name: name, extend: extend} + // Committed to succeed. + l := &Location{zone: zones, tx: tx, name: name, extend: extend} - // Fill in the cache with information about right now, - // since that will be the most common lookup. - sec, _, _ := now() - for i := range tx { - if tx[i].when <= sec && (i+1 == len(tx) || sec < tx[i+1].when) { - l.cacheStart = tx[i].when - l.cacheEnd = omega - l.cacheZone = &l.zone[tx[i].index] - if i+1 < len(tx) { - l.cacheEnd = tx[i+1].when - } else if l.extend != "" { - // If we're at the end of the known zone transitions, - // try the extend string. - if name, offset, estart, eend, isDST, ok := tzset(l.extend, l.cacheStart, sec); ok { - l.cacheStart = estart - l.cacheEnd = eend - // Find the zone that is returned by tzset to avoid allocation if possible. - if zoneIdx := findZone(l.zone, name, offset, isDST); zoneIdx != -1 { - l.cacheZone = &l.zone[zoneIdx] - } else { - l.cacheZone = &zone{ - name: name, - offset: offset, - isDST: isDST, - } + // Fill in the cache with information about right now, + // since that will be the most common lookup. + sec := now() + for i := range tx { + if tx[i].when <= sec && (i+1 == len(tx) || sec < tx[i+1].when) { + l.cacheStart = tx[i].when + l.cacheEnd = omega + l.cacheZone = &l.zone[tx[i].index] + if i+1 < len(tx) { + l.cacheEnd = tx[i+1].when + } else if l.extend != "" { + // If we're at the end of the known zone transitions, + // try the extend string. + if name, offset, estart, eend, isDST, ok := tzset(l.extend, l.cacheStart, sec); ok { + l.cacheStart = estart + l.cacheEnd = eend + // Find the zone that is returned by tzset to avoid allocation if possible. + if zoneIdx := findZone(l.zone, name, offset, isDST); zoneIdx != -1 { + l.cacheZone = &l.zone[zoneIdx] + } else { + l.cacheZone = &zone{ + name: name, + offset: offset, + isDST: isDST, } } } - break } + break } + } - return l, nil - */ - _ = extend - panic("todo") + return l, nil } func findZone(zones []zone, name string, offset int, isDST bool) int { @@ -606,3 +604,9 @@ func gorootZoneSource(goroot string) (string, bool) { } return goroot + "/lib/time/zoneinfo.zip", true } + +func now() (sec int64) { + var tv time.Timespec + time.ClockGettime(0, &tv) + return int64(tv.Sec) +}