From 725c2d7e1a53b384754d810f8b1323b5e27a44f2 Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Mon, 21 Oct 2024 14:13:08 +0200 Subject: [PATCH 01/27] feat: simplify time pkg --- gnovm/stdlibs/time/time.gno | 814 +++++------------------------------- 1 file changed, 115 insertions(+), 699 deletions(-) diff --git a/gnovm/stdlibs/time/time.gno b/gnovm/stdlibs/time/time.gno index f3395142d1d..0498418ef1d 100644 --- a/gnovm/stdlibs/time/time.gno +++ b/gnovm/stdlibs/time/time.gno @@ -1,157 +1,18 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - // Package time provides functionality for measuring and displaying time. // // The calendrical calculations always assume a Gregorian calendar, with // no leap seconds. -// -// # Monotonic Clocks -// -// Operating systems provide both a “wall clock,” which is subject to -// changes for clock synchronization, and a “monotonic clock,” which is -// not. The general rule is that the wall clock is for telling time and -// the monotonic clock is for measuring time. Rather than split the API, -// in this package the Time returned by time.Now contains both a wall -// clock reading and a monotonic clock reading; later time-telling -// operations use the wall clock reading, but later time-measuring -// operations, specifically comparisons and subtractions, use the -// monotonic clock reading. -// -// For example, this code always computes a positive elapsed time of -// approximately 20 milliseconds, even if the wall clock is changed during -// the operation being timed: -// -// start := time.Now() -// ... operation that takes 20 milliseconds ... -// t := time.Now() -// elapsed := t.Sub(start) -// -// Other idioms, such as time.Since(start), time.Until(deadline), and -// time.Now().Before(deadline), are similarly robust against wall clock -// resets. -// -// The rest of this section gives the precise details of how operations -// use monotonic clocks, but understanding those details is not required -// to use this package. -// -// The Time returned by time.Now contains a monotonic clock reading. -// If Time t has a monotonic clock reading, t.Add adds the same duration to -// both the wall clock and monotonic clock readings to compute the result. -// Because t.AddDate(y, m, d), t.Round(d), and t.Truncate(d) are wall time -// computations, they always strip any monotonic clock reading from their results. -// Because t.In, t.Local, and t.UTC are used for their effect on the interpretation -// of the wall time, they also strip any monotonic clock reading from their results. -// The canonical way to strip a monotonic clock reading is to use t = t.Round(0). -// -// If Times t and u both contain monotonic clock readings, the operations -// t.After(u), t.Before(u), t.Equal(u), and t.Sub(u) are carried out -// using the monotonic clock readings alone, ignoring the wall clock -// readings. If either t or u contains no monotonic clock reading, these -// operations fall back to using the wall clock readings. -// -// On some systems the monotonic clock will stop if the computer goes to sleep. -// On such a system, t.Sub(u) may not accurately reflect the actual -// time that passed between t and u. -// -// Because the monotonic clock reading has no meaning outside -// the current process, the serialized forms generated by t.GobEncode, -// t.MarshalBinary, t.MarshalJSON, and t.MarshalText omit the monotonic -// clock reading, and t.Format provides no format for it. Similarly, the -// constructors time.Date, time.Parse, time.ParseInLocation, and time.Unix, -// as well as the unmarshalers t.GobDecode, t.UnmarshalBinary. -// t.UnmarshalJSON, and t.UnmarshalText always create times with -// no monotonic clock reading. -// -// The monotonic clock reading exists only in Time values. It is not -// a part of Duration values or the Unix times returned by t.Unix and -// friends. -// -// Note that the Go == operator compares not just the time instant but -// also the Location and the monotonic clock reading. See the -// documentation for the Time type for a discussion of equality -// testing for Time values. -// -// For debugging, the result of t.String does include the monotonic -// clock reading if present. If t != u because of different monotonic clock readings, -// that difference will be visible when printing t.String() and u.String(). + package time import ( "errors" ) -// A Time represents an instant in time with nanosecond precision. -// -// Programs using times should typically store and pass them as values, -// not pointers. That is, time variables and struct fields should be of -// type time.Time, not *time.Time. -// -// A Time value can be used by multiple goroutines simultaneously except -// that the methods GobDecode, UnmarshalBinary, UnmarshalJSON and -// UnmarshalText are not concurrency-safe. -// -// Time instants can be compared using the Before, After, and Equal methods. -// The Sub method subtracts two instants, producing a Duration. -// The Add method adds a Time and a Duration, producing a Time. -// -// The zero value of type Time is January 1, year 1, 00:00:00.000000000 UTC. -// As this time is unlikely to come up in practice, the IsZero method gives -// a simple way of detecting a time that has not been initialized explicitly. -// -// Each Time has associated with it a Location, consulted when computing the -// presentation form of the time, such as in the Format, Hour, and Year methods. -// The methods Local, UTC, and In return a Time with a specific location. -// Changing the location in this way changes only the presentation; it does not -// change the instant in time being denoted and therefore does not affect the -// computations described in earlier paragraphs. -// -// Representations of a Time value saved by the GobEncode, MarshalBinary, -// MarshalJSON, and MarshalText methods store the Time.Location's offset, but not -// the location name. They therefore lose information about Daylight Saving Time. -// -// In addition to the required “wall clock” reading, a Time may contain an optional -// reading of the current process's monotonic clock, to provide additional precision -// for comparison or subtraction. -// See the “Monotonic Clocks” section in the package documentation for details. -// -// Note that the Go == operator compares not just the time instant but also the -// Location and the monotonic clock reading. Therefore, Time values should not -// be used as map or database keys without first guaranteeing that the -// identical Location has been set for all values, which can be achieved -// through use of the UTC or Local method, and that the monotonic clock reading -// has been stripped by setting t = t.Round(0). In general, prefer t.Equal(u) -// to t == u, since t.Equal uses the most accurate comparison available and -// correctly handles the case when only one of its arguments has a monotonic -// clock reading. -type Time struct { - // wall and ext encode the wall time seconds, wall time nanoseconds, - // and optional monotonic clock reading in nanoseconds. - // - // From high to low bit position, wall encodes a 1-bit flag (hasMonotonic), - // a 33-bit seconds field, and a 30-bit wall time nanoseconds field. - // The nanoseconds field is in the range [0, 999999999]. - // If the hasMonotonic bit is 0, then the 33-bit field must be zero - // and the full signed 64-bit wall seconds since Jan 1 year 1 is stored in ext. - // If the hasMonotonic bit is 1, then the 33-bit field holds a 33-bit - // unsigned wall seconds since Jan 1 year 1885, and ext holds a - // signed 64-bit monotonic clock reading, nanoseconds since process start. - wall uint64 - ext int64 - - // loc specifies the Location that should be used to - // determine the minute, hour, month, day, and year - // that correspond to this Time. - // The nil location means UTC. - // All UTC times are represented with loc==nil, never loc==&utcLoc. - loc *Location -} +type Time int64 const ( hasMonotonic = 1 << 63 - maxWall = wallToInternal + (1<<33 - 1) // year 2157 - minWall = wallToInternal // year 1885 nsecMask = 1<<30 - 1 nsecShift = 30 ) @@ -160,121 +21,52 @@ const ( // take pointer receivers, even when they don't modify the time, // to make them cheaper to call. -// nsec returns the time's nanoseconds. -func (t *Time) nsec() int32 { - return int32(t.wall & nsecMask) +// nsec returns the nanoseconds component of the time. +func (t Time) nsec() int32 { + return int32(t % 1e9) } -// sec returns the time's seconds since Jan 1 year 1. -func (t *Time) sec() int64 { - if t.wall&hasMonotonic != 0 { - return wallToInternal + int64(t.wall<<1>>(nsecShift+1)) - } - return t.ext +// sec returns the time's seconds since the Unix epoch (Jan 1, 1970). +func (t Time) sec() int64 { + return int64(t / 1e9) } // unixSec returns the time's seconds since Jan 1 1970 (Unix time). -func (t *Time) unixSec() int64 { return t.sec() + internalToUnix } +func (t Time) unixSec() int64 { return t.sec() } // addSec adds d seconds to the time. func (t *Time) addSec(d int64) { - if t.wall&hasMonotonic != 0 { - sec := int64(t.wall << 1 >> (nsecShift + 1)) - dsec := sec + d - if 0 <= dsec && dsec <= 1<<33-1 { - t.wall = t.wall&nsecMask | uint64(dsec)< 0 && delta > 0 && int64(*t) > int64(1<<63-1)-delta) || + (*t < 0 && delta < 0 && int64(*t) < int64(-1<<63)-delta) { + // Handle overflow or underflow cases + if delta > 0 { + *t = Time(int64(1<<63 - 1)) // Max int64 value + } else { + *t = Time(int64(-1 << 63)) // Min int64 value } - // Wall second now out of range for packed field. - // Move to ext. - t.stripMono() - } - - // Check if the sum of t.ext and d overflows and handle it properly. - sum := t.ext + d - if (sum > t.ext) == (d > 0) { - t.ext = sum - } else if d > 0 { - t.ext = 1<<63 - 1 } else { - t.ext = -(1<<63 - 1) - } -} - -// setLoc sets the location associated with the time. -func (t *Time) setLoc(loc *Location) { - if loc == &utcLoc { - loc = nil - } - t.stripMono() - t.loc = loc -} - -// stripMono strips the monotonic clock reading in t. -func (t *Time) stripMono() { - if t.wall&hasMonotonic != 0 { - t.ext = t.sec() - t.wall &= nsecMask - } -} - -// setMono sets the monotonic clock reading in t. -// If t cannot hold a monotonic clock reading, -// because its wall time is too large, -// setMono is a no-op. -func (t *Time) setMono(m int64) { - if t.wall&hasMonotonic == 0 { - sec := t.ext - if sec < minWall || maxWall < sec { - return - } - t.wall |= hasMonotonic | uint64(sec-minWall)< u.ext - } - ts := t.sec() - us := u.sec() - return ts > us || ts == us && t.nsec() > u.nsec() + return t > u } // Before reports whether the time instant t is before u. func (t Time) Before(u Time) bool { - if t.wall&u.wall&hasMonotonic != 0 { - return t.ext < u.ext - } - ts := t.sec() - us := u.sec() - return ts < us || ts == us && t.nsec() < u.nsec() + return t < u } -// Equal reports whether t and u represent the same time instant. -// Two times can be equal even if they are in different locations. -// For example, 6:00 +0200 and 4:00 UTC are Equal. -// See the documentation on the Time type for the pitfalls of using == with -// Time values; most code should use Equal instead. +// Equal reports whether the time instant t is equal to u. func (t Time) Equal(u Time) bool { - if t.wall&u.wall&hasMonotonic != 0 { - return t.ext == u.ext - } - return t.sec() == u.sec() && t.nsec() == u.nsec() + return t == u } // A Month specifies a month of the year (January = 1, ...). @@ -328,154 +120,10 @@ func (d Weekday) String() string { return "%!Weekday(" + string(buf[n:]) + ")" } -// Computations on time. -// -// The zero value for a Time is defined to be -// January 1, year 1, 00:00:00.000000000 UTC -// which (1) looks like a zero, or as close as you can get in a date -// (1-1-1 00:00:00 UTC), (2) is unlikely enough to arise in practice to -// be a suitable "not set" sentinel, unlike Jan 1 1970, and (3) has a -// non-negative year even in time zones west of UTC, unlike 1-1-0 -// 00:00:00 UTC, which would be 12-31-(-1) 19:00:00 in New York. -// -// The zero Time value does not force a specific epoch for the time -// representation. For example, to use the Unix epoch internally, we -// could define that to distinguish a zero value from Jan 1 1970, that -// time would be represented by sec=-1, nsec=1e9. However, it does -// suggest a representation, namely using 1-1-1 00:00:00 UTC as the -// epoch, and that's what we do. -// -// The Add and Sub computations are oblivious to the choice of epoch. -// -// The presentation computations - year, month, minute, and so on - all -// rely heavily on division and modulus by positive constants. For -// calendrical calculations we want these divisions to round down, even -// for negative values, so that the remainder is always positive, but -// Go's division (like most hardware division instructions) rounds to -// zero. We can still do those computations and then adjust the result -// for a negative numerator, but it's annoying to write the adjustment -// over and over. Instead, we can change to a different epoch so long -// ago that all the times we care about will be positive, and then round -// to zero and round down coincide. These presentation routines already -// have to add the zone offset, so adding the translation to the -// alternate epoch is cheap. For example, having a non-negative time t -// means that we can write -// -// sec = t % 60 -// -// instead of -// -// sec = t % 60 -// if sec < 0 { -// sec += 60 -// } -// -// everywhere. -// -// The calendar runs on an exact 400 year cycle: a 400-year calendar -// printed for 1970-2369 will apply as well to 2370-2769. Even the days -// of the week match up. It simplifies the computations to choose the -// cycle boundaries so that the exceptional years are always delayed as -// long as possible. That means choosing a year equal to 1 mod 400, so -// that the first leap year is the 4th year, the first missed leap year -// is the 100th year, and the missed missed leap year is the 400th year. -// So we'd prefer instead to print a calendar for 2001-2400 and reuse it -// for 2401-2800. -// -// Finally, it's convenient if the delta between the Unix epoch and -// long-ago epoch is representable by an int64 constant. -// -// These three considerations—choose an epoch as early as possible, that -// uses a year equal to 1 mod 400, and that is no more than 2⁶³ seconds -// earlier than 1970—bring us to the year -292277022399. We refer to -// this year as the absolute zero year, and to times measured as a uint64 -// seconds since this year as absolute times. -// -// Times measured as an int64 seconds since the year 1—the representation -// used for Time's sec field—are called internal times. -// -// Times measured as an int64 seconds since the year 1970 are called Unix -// times. -// -// It is tempting to just use the year 1 as the absolute epoch, defining -// that the routines are only valid for years >= 1. However, the -// routines would then be invalid when displaying the epoch in time zones -// west of UTC, since it is year 0. It doesn't seem tenable to say that -// printing the zero time correctly isn't supported in half the time -// zones. By comparison, it's reasonable to mishandle some times in -// the year -292277022399. -// -// All this is opaque to clients of the API and can be changed if a -// better implementation presents itself. - -const ( - // The unsigned zero year for internal calculations. - // Must be 1 mod 400, and times before it will not compute correctly, - // but otherwise can be changed at will. - absoluteZeroYear = -292277022399 - - // The year of the zero Time. - // Assumed by the unixToInternal computation below. - internalYear = 1 - - // Offsets to convert between internal and absolute or Unix times. - absoluteToInternal int64 = (absoluteZeroYear - internalYear) * 365.2425 * secondsPerDay - internalToAbsolute = -absoluteToInternal - - unixToInternal int64 = (1969*365 + 1969/4 - 1969/100 + 1969/400) * secondsPerDay - internalToUnix int64 = -unixToInternal - - wallToInternal int64 = (1884*365 + 1884/4 - 1884/100 + 1884/400) * secondsPerDay -) - // IsZero reports whether t represents the zero time instant, // January 1, year 1, 00:00:00 UTC. func (t Time) IsZero() bool { - return t.sec() == 0 && t.nsec() == 0 -} - -// abs returns the time t as an absolute time, adjusted by the zone offset. -// It is called when computing a presentation property like Month or Hour. -func (t Time) abs() uint64 { - l := t.loc - // Avoid function calls when possible. - if l == nil || l == &localLoc { - l = l.get() - } - sec := t.unixSec() - if l != &utcLoc { - if l.cacheZone != nil && l.cacheStart <= sec && sec < l.cacheEnd { - sec += int64(l.cacheZone.offset) - } else { - _, offset, _, _, _ := l.lookup(sec) - sec += int64(offset) - } - } - return uint64(sec + (unixToInternal + internalToAbsolute)) -} - -// locabs is a combination of the Zone and abs methods, -// extracting both return values from a single zone lookup. -func (t Time) locabs() (name string, offset int, abs uint64) { - l := t.loc - if l == nil || l == &localLoc { - l = l.get() - } - // Avoid function call if we hit the local time cache. - sec := t.unixSec() - if l != &utcLoc { - if l.cacheZone != nil && l.cacheStart <= sec && sec < l.cacheEnd { - name = l.cacheZone.name - offset = l.cacheZone.offset - } else { - name, offset, _, _, _ = l.lookup(sec) - } - sec += int64(offset) - } else { - name = "UTC" - } - abs = uint64(sec + (unixToInternal + internalToAbsolute)) - return + return t == 0 } // Date returns the year, month, and day in which t occurs. @@ -504,16 +152,11 @@ func (t Time) Day() int { // Weekday returns the day of the week specified by t. func (t Time) Weekday() Weekday { - return absWeekday(t.abs()) -} - -// absWeekday is like Weekday but operates on an absolute time. -func absWeekday(abs uint64) Weekday { - // January 1 of the absolute year, like January 1 of 2001, was a Monday. - sec := (abs + uint64(Monday)*secondsPerDay) % secondsPerWeek - return Weekday(int(sec) / secondsPerDay) + daysSinceEpoch := t.sec() / secondsPerDay + return Weekday((daysSinceEpoch + int64(Thursday)) % 7) // January 1, 1970 was a Thursday } +// TODO: TEST // ISOWeek returns the ISO 8601 year and week number in which t occurs. // Week ranges from 1 to 53. Jan 01 to Jan 03 of year n might belong to // week 52 or 53 of year n-1, and Dec 29 to Dec 31 might belong to week 1 @@ -529,26 +172,25 @@ func (t Time) ISOWeek() (year, week int) { // 1 2 3 4 5 6 7 // +3 +2 +1 0 -1 -2 -3 // the offset to Thursday - abs := t.abs() - d := Thursday - absWeekday(abs) - // handle Sunday + // Get the current day of the week + d := Thursday - t.Weekday() + + // Handle Sunday (ISOWeek counts Monday as the first day of the week) if d == 4 { d = -3 } - // find the Thursday of the calendar week - abs += uint64(d) * secondsPerDay - year, _, _, yday := absDate(abs, false) - return year, yday/7 + 1 + + t += Time(d * secondsPerDay * 1e9) // Convert days to nanoseconds and adjust + + year, _, _, yday := t.date(false) + week = yday/7 + 1 + + return year, week } // Clock returns the hour, minute, and second within the day specified by t. func (t Time) Clock() (hour, min, sec int) { - return absClock(t.abs()) -} - -// absClock is like clock but operates on an absolute time. -func absClock(abs uint64) (hour, min, sec int) { - sec = int(abs % secondsPerDay) + sec = int(t % secondsPerDay) hour = sec / secondsPerHour sec -= hour * secondsPerHour min = sec / secondsPerMinute @@ -558,17 +200,17 @@ func absClock(abs uint64) (hour, min, sec int) { // Hour returns the hour within the day specified by t, in the range [0, 23]. func (t Time) Hour() int { - return int(t.abs()%secondsPerDay) / secondsPerHour + return int(t%secondsPerDay) / secondsPerHour } // Minute returns the minute offset within the hour specified by t, in the range [0, 59]. func (t Time) Minute() int { - return int(t.abs()%secondsPerHour) / secondsPerMinute + return int(t%secondsPerHour) / secondsPerMinute } // Second returns the second offset within the minute specified by t, in the range [0, 59]. func (t Time) Second() int { - return int(t.abs() % secondsPerMinute) + return int(t % secondsPerMinute) } // Nanosecond returns the nanosecond offset within the second specified by t, @@ -833,26 +475,22 @@ func (d Duration) Abs() Duration { // Add returns the time t+d. func (t Time) Add(d Duration) Time { dsec := int64(d / 1e9) - nsec := t.nsec() + int32(d%1e9) + dnsec := int64(d % 1e9) + tsec := t.sec() + tnsec := t.nsec() + + nsec := int64(tnsec) + dnsec + sec := tsec + dsec + if nsec >= 1e9 { - dsec++ + sec++ nsec -= 1e9 } else if nsec < 0 { - dsec-- + sec-- nsec += 1e9 } - t.wall = t.wall&^nsecMask | uint64(nsec) // update nsec - t.addSec(dsec) - if t.wall&hasMonotonic != 0 { - te := t.ext + int64(d) - if d < 0 && te > t.ext || d > 0 && te < t.ext { - // Monotonic clock reading now out of range; degrade to wall-only. - t.stripMono() - } else { - t.ext = te - } - } - return t + + return Time(sec*1e9 + nsec) } // Sub returns the duration t-u. If the result exceeds the maximum (or minimum) @@ -860,54 +498,28 @@ func (t Time) Add(d Duration) Time { // will be returned. // To compute t-d for a duration d, use t.Add(-d). func (t Time) Sub(u Time) Duration { - if t.wall&u.wall&hasMonotonic != 0 { - te := t.ext - ue := u.ext - d := Duration(te - ue) - if d < 0 && te > ue { - return maxDuration // t - u is positive out of range - } - if d > 0 && te < ue { - return minDuration // t - u is negative out of range - } - return d + diff := int64(t) - int64(u) + + if diff > int64(maxDuration) { + return maxDuration } - d := Duration(t.sec()-u.sec())*Second + Duration(t.nsec()-u.nsec()) - // Check for overflow or underflow. - switch { - case u.Add(d).Equal(t): - return d // d is correct - case t.Before(u): - return minDuration // t - u is negative out of range - default: - return maxDuration // t - u is positive out of range + if diff < int64(minDuration) { + return minDuration } + + return Duration(diff) } // Since returns the time elapsed since t. // It is shorthand for time.Now().Sub(t). func Since(t Time) Duration { - var now Time - if t.wall&hasMonotonic != 0 { - // Common case optimization: if t has monotonic time, then Sub will use only it. - now = Time{hasMonotonic, runtimeNano() - startNano, nil} - } else { - now = Now() - } - return now.Sub(t) + return Now().Sub(t) } // Until returns the duration until t. // It is shorthand for t.Sub(time.Now()). func Until(t Time) Duration { - var now Time - if t.wall&hasMonotonic != 0 { - // Common case optimization: if t has monotonic time, then Sub will use only it. - now = Time{hasMonotonic, runtimeNano() - startNano, nil} - } else { - now = Now() - } - return t.Sub(now) + return t.Sub(Now()) } // AddDate returns the time corresponding to adding the @@ -921,7 +533,7 @@ func Until(t Time) Duration { func (t Time) AddDate(years int, months int, days int) Time { year, month, day := t.Date() hour, min, sec := t.Clock() - return Date(year+years, month+Month(months), day+days, hour, min, sec, int(t.nsec()), t.Location()) + return Date(year+years, month+Month(months), day+days, hour, min, sec, int(t.nsec())) } const ( @@ -937,13 +549,7 @@ const ( // date computes the year, day of year, and when full=true, // the month and day in which t occurs. func (t Time) date(full bool) (year int, month Month, day int, yday int) { - return absDate(t.abs(), full) -} - -// 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 + d := t / secondsPerDay // Account for 400 year cycles. n := d / daysPer400Years @@ -975,7 +581,7 @@ func absDate(abs uint64, full bool) (year int, month Month, day int, yday int) { y += n d -= 365 * n - year = int(int64(y) + absoluteZeroYear) + year = int(int64(y) + 1970) yday = int(d) if !full { @@ -1041,121 +647,38 @@ func daysIn(m Month, year int) int { } // daysSinceEpoch takes a year and returns the number of days from -// the absolute epoch to the start of that year. -// This is basically (year - zeroYear) * 365, but accounting for leap days. -func daysSinceEpoch(year int) uint64 { - y := uint64(int64(year) - absoluteZeroYear) +// the Unix epoch (January 1, 1970) to the start of that year, +// accounting for leap years. +func daysSinceEpoch(year int) int64 { + y := year - 1970 // Calculate how many years from 1970 (Unix epoch) - // Add in days from 400-year cycles. + // Add days for 400-year cycles. n := y / 400 y -= 400 * n - d := daysPer400Years * n + d := int64(daysPer400Years * n) - // Add in 100-year cycles. + // Add days for 100-year cycles. n = y / 100 y -= 100 * n - d += daysPer100Years * n + d += int64(daysPer100Years * n) - // Add in 4-year cycles. + // Add days for 4-year cycles. n = y / 4 y -= 4 * n - d += daysPer4Years * n + d += int64(daysPer4Years * n) - // Add in non-leap years. - n = y - d += 365 * n + // Add days for the remaining years. + d += int64(365 * y) return d } func now() (sec int64, nsec int32, mono int64) // injected -// runtimeNano returns the current value of the runtime clock in nanoseconds. -func runtimeNano() int64 { - _, _, mono := now() - return mono -} - -// Monotonic times are reported as offsets from startNano. -// We initialize startNano to runtimeNano() - 1 so that on systems where -// monotonic time resolution is fairly low (e.g. Windows 2008 -// which appears to have a default resolution of 15ms), -// we avoid ever reporting a monotonic time of 0. -// (Callers may want to use 0 as "time not set".) -var startNano int64 = runtimeNano() - 1 - // Now returns the current local time. func Now() Time { - sec, nsec, mono := now() - mono -= startNano - sec += unixToInternal - minWall - if uint64(sec)>>33 != 0 { - return Time{uint64(nsec), sec + minWall, Local} - } - return Time{hasMonotonic | uint64(sec)< 32767 { - return nil, errors.New("Time.MarshalBinary: unexpected zone offset") - } - offsetMin = int16(offset) - } - + // Get seconds and nanoseconds sec := t.sec() nsec := t.nsec() + + // Encode seconds and nanoseconds into a byte slice enc := []byte{ - version, // byte 0 : version - byte(sec >> 56), // bytes 1-8: seconds + byte(sec >> 56), // bytes 0-7: seconds byte(sec >> 48), byte(sec >> 40), byte(sec >> 32), @@ -1235,15 +740,10 @@ func (t Time) MarshalBinary() ([]byte, error) { byte(sec >> 16), byte(sec >> 8), byte(sec), - byte(nsec >> 24), // bytes 9-12: nanoseconds + byte(nsec >> 24), // bytes 8-11: nanoseconds byte(nsec >> 16), byte(nsec >> 8), byte(nsec), - byte(offsetMin >> 8), // bytes 13-14: zone offset in minutes - byte(offsetMin), - } - if version == timeBinaryVersionV2 { - enc = append(enc, byte(offsetSec)) } return enc, nil @@ -1251,48 +751,19 @@ func (t Time) MarshalBinary() ([]byte, error) { // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. func (t *Time) UnmarshalBinary(data []byte) error { - buf := data - if len(buf) == 0 { - return errors.New("Time.UnmarshalBinary: no data") - } - - version := buf[0] - if version != timeBinaryVersionV1 && version != timeBinaryVersionV2 { - return errors.New("Time.UnmarshalBinary: unsupported version") - } - - wantLen := /*version*/ 1 + /*sec*/ 8 + /*nsec*/ 4 + /*zone offset*/ 2 - if version == timeBinaryVersionV2 { - wantLen++ - } - if len(buf) != wantLen { + if len(data) != 12 { return errors.New("Time.UnmarshalBinary: invalid length") } - buf = buf[1:] - sec := int64(buf[7]) | int64(buf[6])<<8 | int64(buf[5])<<16 | int64(buf[4])<<24 | - int64(buf[3])<<32 | int64(buf[2])<<40 | int64(buf[1])<<48 | int64(buf[0])<<56 - - buf = buf[8:] - nsec := int32(buf[3]) | int32(buf[2])<<8 | int32(buf[1])<<16 | int32(buf[0])<<24 - - buf = buf[4:] - offset := int(int16(buf[1])|int16(buf[0])<<8) * 60 - if version == timeBinaryVersionV2 { - offset += int(buf[2]) - } + // Decode seconds (int64) from the first 8 bytes + sec := int64(data[7]) | int64(data[6])<<8 | int64(data[5])<<16 | int64(data[4])<<24 | + int64(data[3])<<32 | int64(data[2])<<40 | int64(data[1])<<48 | int64(data[0])<<56 - *t = Time{} - t.wall = uint64(nsec) - t.ext = sec + // Decode nanoseconds (int32) from the next 4 bytes + nsec := int32(data[11]) | int32(data[10])<<8 | int32(data[9])<<16 | int32(data[8])<<24 - if offset == -1*60 { - t.setLoc(&utcLoc) - } else if _, localoff, _, _, _ := Local.lookup(t.unixSec()); offset == localoff { - t.setLoc(Local) - } else { - t.setLoc(FixedZone("", offset)) - } + // Combine seconds and nanoseconds into Time (nanoseconds since Unix epoch) + *t = Time(sec*1e9 + int64(nsec)) return nil } @@ -1375,7 +846,7 @@ func Unix(sec int64, nsec int64) Time { sec-- } } - return unixTime(sec, int32(nsec)) + return Time(sec*1e9 + nsec) } // UnixMilli returns the local Time corresponding to the given Unix time, @@ -1390,12 +861,6 @@ func UnixMicro(usec int64) Time { return Unix(usec/1e6, (usec%1e6)*1e3) } -// IsDST reports whether the time in the configured location is in Daylight Savings Time. -func (t Time) IsDST() bool { - _, _, _, _, isDST := t.loc.lookup(t.Unix()) - return isDST -} - func isLeap(year int) bool { return year%4 == 0 && (year%100 != 0 || year%400 == 0) } @@ -1419,28 +884,10 @@ func norm(hi, lo, base int) (nhi, nlo int) { } // Date returns the Time corresponding to -// -// yyyy-mm-dd hh:mm:ss + nsec nanoseconds -// -// in the appropriate zone for that time in the given location. -// +// yyyy-mm-dd hh:mm:ss + nsec nanoseconds in UTC. // The month, day, hour, min, sec, and nsec values may be outside // their usual ranges and will be normalized during the conversion. -// For example, October 32 converts to November 1. -// -// A daylight savings time transition skips or repeats times. -// For example, in the United States, March 13, 2011 2:15am never occurred, -// while November 6, 2011 1:15am occurred twice. In such cases, the -// choice of time zone, and therefore the time, is not well-defined. -// Date returns a time that is correct in one of the two zones involved -// in the transition, but it does not guarantee which. -// -// Date panics if loc is nil. -func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time { - if loc == nil { - panic("time: missing Location in call to Date") - } - +func Date(year int, month Month, day, hour, min, sec, nsec int) Time { // Normalize month, overflowing into year. m := int(month) - 1 year, m = norm(year, m, 12) @@ -1452,53 +899,30 @@ func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) T hour, min = norm(hour, min, 60) day, hour = norm(day, hour, 24) - // Compute days since the absolute epoch. - d := daysSinceEpoch(year) + // Compute days since the Unix epoch (January 1, 1970). + d := int64(daysSinceEpoch(year)) // Add in days before this month. - d += uint64(daysBefore[month-1]) - if isLeap(year) && month >= March { - d++ // February 29 + d += int64(daysBefore[month-1]) + if isLeap(year) && month > February { + d++ // Add leap day } // Add in days before today. - d += uint64(day - 1) - - // Add in time elapsed today. - abs := d * secondsPerDay - abs += uint64(hour*secondsPerHour + min*secondsPerMinute + sec) - - unix := int64(abs) + (absoluteToInternal + internalToUnix) - - // Look for zone offset for expected time, so we can adjust to UTC. - // The lookup function expects UTC, so first we pass unix in the - // hope that it will not be too close to a zone transition, - // and then adjust if it is. - _, offset, start, end, _ := loc.lookup(unix) - if offset != 0 { - utc := unix - int64(offset) - // If utc is valid for the time zone we found, then we have the right offset. - // If not, we get the correct offset by looking up utc in the location. - if utc < start || utc >= end { - _, offset, _, _, _ = loc.lookup(utc) - } - unix -= int64(offset) - } + d += int64(day - 1) + + // Calculate the total seconds for the date. + totalSeconds := d*secondsPerDay + int64(hour*secondsPerHour+min*secondsPerMinute+sec) + + // Convert total seconds to nanoseconds. + totalNanoseconds := totalSeconds*1e9 + int64(nsec) - t := unixTime(unix, int32(nsec)) - t.setLoc(loc) - return t + // Return the computed Time in nanoseconds since the Unix epoch. + return Time(totalNanoseconds) } -// Truncate returns the result of rounding t down to a multiple of d (since the zero time). -// If d <= 0, Truncate returns t stripped of any monotonic clock reading but otherwise unchanged. -// -// Truncate operates on the time as an absolute duration since the -// zero time; it does not operate on the presentation form of the -// time. Thus, Truncate(Hour) may return a time with a non-zero -// minute, depending on the time's Location. +// Truncate returns the result of rounding t down to a multiple of d. func (t Time) Truncate(d Duration) Time { - t.stripMono() if d <= 0 { return t } @@ -1506,16 +930,8 @@ func (t Time) Truncate(d Duration) Time { return t.Add(-r) } -// Round returns the result of rounding t to the nearest multiple of d (since the zero time). -// The rounding behavior for halfway values is to round up. -// If d <= 0, Round returns t stripped of any monotonic clock reading but otherwise unchanged. -// -// Round operates on the time as an absolute duration since the -// zero time; it does not operate on the presentation form of the -// time. Thus, Round(Hour) may return a time with a non-zero -// minute, depending on the time's Location. +// Round returns the result of rounding t to the nearest multiple of d. func (t Time) Round(d Duration) Time { - t.stripMono() if d <= 0 { return t } From 0160c5aec1c2410cde06cf1f86d36dd94988ef51 Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Mon, 21 Oct 2024 14:55:21 +0200 Subject: [PATCH 02/27] feat: adapt format time file --- gnovm/stdlibs/time/format.gno | 419 ++++------------------------------ 1 file changed, 41 insertions(+), 378 deletions(-) diff --git a/gnovm/stdlibs/time/format.gno b/gnovm/stdlibs/time/format.gno index 61a9eb3301b..af0e5b7ac34 100644 --- a/gnovm/stdlibs/time/format.gno +++ b/gnovm/stdlibs/time/format.gno @@ -509,32 +509,7 @@ func formatNano(b []byte, nanosec uint, std int) []byte { // representation, use t.MarshalText, t.MarshalBinary, or t.Format // with an explicit format string. func (t Time) String() string { - s := t.Format("2006-01-02 15:04:05.999999999 -0700 MST") - - // Format monotonic clock reading as m=±ddd.nnnnnnnnn. - if t.wall&hasMonotonic != 0 { - m2 := uint64(t.ext) - sign := byte('+') - if t.ext < 0 { - sign = '-' - m2 = -m2 - } - m1, m2 := m2/1e9, m2%1e9 - m0, m1 := m1/1e9, m1%1e9 - buf := make([]byte, 0, 24) - buf = append(buf, " m="...) - buf = append(buf, sign) - wid := 0 - if m0 != 0 { - buf = appendInt(buf, int(m0), 0) - wid = 9 - } - buf = appendInt(buf, int(m1), wid) - buf = append(buf, '.') - buf = appendInt(buf, int(m2), 9) - s += string(buf) - } - return s + return t.Format("2006-01-02 15:04:05.999999999 UTC") } // GoString implements fmt.GoStringer and formats t to be printed in Go source @@ -563,31 +538,6 @@ func (t Time) GoString() string { buf = append(buf, ", "...) buf = appendInt(buf, t.Nanosecond(), 0) buf = append(buf, ", "...) - switch loc := t.Location(); loc { - case UTC, nil: - buf = append(buf, "time.UTC"...) - case Local: - buf = append(buf, "time.Local"...) - default: - // there are several options for how we could display this, none of - // which are great: - // - // - use Location(loc.name), which is not technically valid syntax - // - use LoadLocation(loc.name), which will cause a syntax error when - // embedded and also would require us to escape the string without - // importing fmt or strconv - // - try to use FixedZone, which would also require escaping the name - // and would represent e.g. "America/Los_Angeles" daylight saving time - // shifts inaccurately - // - use the pointer format, which is no worse than you'd get with the - // old fmt.Sprintf("%#v", t) format. - // - // Of these, Location(loc.name) is the least disruptive. This is an edge - // case we hope not to hit too often. - buf = append(buf, `time.Location(`...) - buf = append(buf, []byte(quote(loc.name))...) - buf = append(buf, `)`...) - } buf = append(buf, ')') return string(buf) } @@ -616,16 +566,15 @@ func (t Time) Format(layout string) string { // representation to b and returns the extended buffer. func (t Time) AppendFormat(b []byte, layout string) []byte { var ( - name, offset, abs = t.locabs() - - year int = -1 + year int month Month day int - yday int - hour int = -1 + hour int min int sec int + yday int ) + // Each iteration generates one std value. for layout != "" { prefix, std, suffix := nextStdChunk(layout) @@ -638,39 +587,35 @@ func (t Time) AppendFormat(b []byte, layout string) []byte { layout = suffix // Compute year, month, day if needed. - if year < 0 && std&stdNeedDate != 0 { - year, month, day, yday = absDate(abs, true) - yday++ + if std&stdNeedDate != 0 && year == 0 { + year, month, day = t.Date() + + // Calculate day of the year (yday) if needed. + yday = t.YearDay() } // Compute hour, minute, second if needed. - if hour < 0 && std&stdNeedClock != 0 { - hour, min, sec = absClock(abs) + if std&stdNeedClock != 0 && hour == 0 { + hour, min, sec = t.Clock() } switch std & stdMask { case stdYear: - y := year - if y < 0 { - y = -y - } - b = appendInt(b, y%100, 2) + b = appendInt(b, year%100, 2) case stdLongYear: b = appendInt(b, year, 4) case stdMonth: b = append(b, month.String()[:3]...) case stdLongMonth: - m := month.String() - b = append(b, m...) + b = append(b, month.String()...) case stdNumMonth: b = appendInt(b, int(month), 0) case stdZeroMonth: b = appendInt(b, int(month), 2) case stdWeekDay: - b = append(b, absWeekday(abs).String()[:3]...) + b = append(b, t.Weekday().String()[:3]...) case stdLongWeekDay: - s := absWeekday(abs).String() - b = append(b, s...) + b = append(b, t.Weekday().String()...) case stdDay: b = appendInt(b, day, 0) case stdUnderDay: @@ -693,14 +638,12 @@ func (t Time) AppendFormat(b []byte, layout string) []byte { case stdHour: b = appendInt(b, hour, 2) case stdHour12: - // Noon is 12PM, midnight is 12AM. hr := hour % 12 if hr == 0 { hr = 12 } b = appendInt(b, hr, 0) case stdZeroHour12: - // Noon is 12PM, midnight is 12AM. hr := hour % 12 if hr == 0 { hr = 12 @@ -726,54 +669,6 @@ func (t Time) AppendFormat(b []byte, layout string) []byte { } else { b = append(b, "am"...) } - case stdISO8601TZ, stdISO8601ColonTZ, stdISO8601SecondsTZ, stdISO8601ShortTZ, stdISO8601ColonSecondsTZ, stdNumTZ, stdNumColonTZ, stdNumSecondsTz, stdNumShortTZ, stdNumColonSecondsTZ: - // Ugly special case. We cheat and take the "Z" variants - // to mean "the time zone as formatted for ISO 8601". - if offset == 0 && (std == stdISO8601TZ || std == stdISO8601ColonTZ || std == stdISO8601SecondsTZ || std == stdISO8601ShortTZ || std == stdISO8601ColonSecondsTZ) { - b = append(b, 'Z') - break - } - zone := offset / 60 // convert to minutes - absoffset := offset - if zone < 0 { - b = append(b, '-') - zone = -zone - absoffset = -absoffset - } else { - b = append(b, '+') - } - b = appendInt(b, zone/60, 2) - if std == stdISO8601ColonTZ || std == stdNumColonTZ || std == stdISO8601ColonSecondsTZ || std == stdNumColonSecondsTZ { - b = append(b, ':') - } - if std != stdNumShortTZ && std != stdISO8601ShortTZ { - b = appendInt(b, zone%60, 2) - } - - // append seconds if appropriate - if std == stdISO8601SecondsTZ || std == stdNumSecondsTz || std == stdNumColonSecondsTZ || std == stdISO8601ColonSecondsTZ { - if std == stdNumColonSecondsTZ || std == stdISO8601ColonSecondsTZ { - b = append(b, ':') - } - b = appendInt(b, absoffset%60, 2) - } - - case stdTZ: - if name != "" { - b = append(b, name...) - break - } - // No time zone known for this time, but we must print one. - // Use the -0700 format. - zone := offset / 60 // convert to minutes - if zone < 0 { - b = append(b, '-') - zone = -zone - } else { - b = append(b, '+') - } - b = appendInt(b, zone/60, 2) - b = appendInt(b, zone%60, 2) case stdFracSecond0, stdFracSecond9: b = formatNano(b, uint(t.Nanosecond()), std) } @@ -916,80 +811,27 @@ func skip(value, prefix string) (string, error) { return value, nil } -// Parse parses a formatted string and returns the time value it represents. -// See the documentation for the constant called Layout to see how to -// represent the format. The second argument must be parseable using -// the format string (layout) provided as the first argument. -// -// The example for Time.Format demonstrates the working of the layout string -// in detail and is a good reference. -// -// When parsing (only), the input may contain a fractional second -// field immediately after the seconds field, even if the layout does not -// signify its presence. In that case either a comma or a decimal point -// followed by a maximal series of digits is parsed as a fractional second. -// Fractional seconds are truncated to nanosecond precision. -// -// Elements omitted from the layout are assumed to be zero or, when -// zero is impossible, one, so parsing "3:04pm" returns the time -// corresponding to Jan 1, year 0, 15:04:00 UTC (note that because the year is -// 0, this time is before the zero Time). -// Years must be in the range 0000..9999. The day of the week is checked -// for syntax but it is otherwise ignored. -// -// For layouts specifying the two-digit year 06, a value NN >= 69 will be treated -// as 19NN and a value NN < 69 will be treated as 20NN. -// -// The remainder of this comment describes the handling of time zones. -// -// In the absence of a time zone indicator, Parse returns a time in UTC. -// -// When parsing a time with a zone offset like -0700, if the offset corresponds -// to a time zone used by the current location (Local), then Parse uses that -// location and zone in the returned time. Otherwise it records the time as -// being in a fabricated location with time fixed at the given zone offset. -// -// When parsing a time with a zone abbreviation like MST, if the zone abbreviation -// has a defined offset in the current location, then that offset is used. -// The zone abbreviation "UTC" is recognized as UTC regardless of location. -// If the zone abbreviation is unknown, Parse records the time as being -// in a fabricated location with the given zone abbreviation and a zero offset. -// This choice means that such a time can be parsed and reformatted with the -// same layout losslessly, but the exact instant used in the representation will -// differ by the actual zone offset. To avoid such problems, prefer time layouts -// that use a numeric zone offset, or use ParseInLocation. +// Parse parses a formatted string and returns the time value it represents in UTC. +// The second argument must be parseable using the format string (layout) provided as the first argument. func Parse(layout, value string) (Time, error) { - return parse(layout, value, UTC, Local) + return parse(layout, value) } -// ParseInLocation is like Parse but differs in two important ways. -// First, in the absence of time zone information, Parse interprets a time as UTC; -// ParseInLocation interprets the time as in the given location. -// Second, when given a zone offset or abbreviation, Parse tries to match it -// against the Local location; ParseInLocation uses the given location. -func ParseInLocation(layout, value string, loc *Location) (Time, error) { - return parse(layout, value, loc, loc) -} - -func parse(layout, value string, defaultLocation, local *Location) (Time, error) { +func parse(layout, value string) (Time, error) { alayout, avalue := layout, value - rangeErrString := "" // set if a value is out of range - amSet := false // do we need to subtract 12 from the hour for midnight? - pmSet := false // do we need to add 12 to the hour? + rangeErrString := "" + amSet := false // do we need to subtract 12 from the hour for midnight? + pmSet := false // do we need to add 12 to the hour? // Time being constructed. var ( - year int - month int = -1 - day int = -1 - yday int = -1 - hour int - min int - sec int - nsec int - z *Location - zoneOffset int = -1 - zoneName string + year int + month int = -1 + day int = -1 + hour int + min int + sec int + nsec int ) // Each iteration processes one std value. @@ -999,11 +841,11 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error) stdstr := layout[len(prefix) : len(layout)-len(suffix)] value, err = skip(value, prefix) if err != nil { - return Time{}, &ParseError{alayout, avalue, prefix, value, ""} + return Time(0), &ParseError{alayout, avalue, prefix, value, ""} } if std == 0 { if len(value) != 0 { - return Time{}, &ParseError{alayout, avalue, "", value, ": extra text: " + quote(value)} + return Time(0), &ParseError{alayout, avalue, "", value, ": extra text: " + quote(value)} } break } @@ -1020,7 +862,7 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error) year, err = atoi(p) if err != nil { value = hold - } else if year >= 69 { // Unix time starts Dec 31 1969 in some time zones + } else if year >= 69 { year += 1900 } else { year += 2000 @@ -1043,27 +885,14 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error) if err == nil && (month <= 0 || 12 < month) { rangeErrString = "month" } - case stdWeekDay: + case stdWeekDay, stdLongWeekDay: // Ignore weekday except for error checking. - _, value, err = lookup(shortDayNames, value) - case stdLongWeekDay: _, value, err = lookup(longDayNames, value) case stdDay, stdUnderDay, stdZeroDay: if std == stdUnderDay && len(value) > 0 && value[0] == ' ' { value = value[1:] } day, value, err = getnum(value, std == stdZeroDay) - // Note that we allow any one- or two-digit day here. - // The month, day, year combination is validated after we've completed parsing. - case stdUnderYearDay, stdZeroYearDay: - for i := 0; i < 2; i++ { - if std == stdUnderYearDay && len(value) > 0 && value[0] == ' ' { - value = value[1:] - } - } - yday, value, err = getnum3(value, std == stdZeroYearDay) - // Note that we allow any one-, two-, or three-digit year-day here. - // The year-day, year combination is validated after we've completed parsing. case stdHour: hour, value, err = getnum(value, false) if hour < 0 || 24 <= hour { @@ -1085,16 +914,13 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error) rangeErrString = "second" break } - // Special case: do we have a fractional second but no - // fractional second in the format? + // Special case: Handle fractional seconds even if not in layout. if len(value) >= 2 && commaOrPeriod(value[0]) && isDigit(value, 1) { _, std, _ = nextStdChunk(layout) std &= stdMask if std == stdFracSecond0 || std == stdFracSecond9 { - // Fractional second in the layout; proceed normally break } - // No fractional second in the layout but we have one in the input. n := 2 for ; n < len(value) && isDigit(value, n); n++ { } @@ -1129,85 +955,7 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error) default: err = errBad } - case stdISO8601TZ, stdISO8601ColonTZ, stdISO8601SecondsTZ, stdISO8601ShortTZ, stdISO8601ColonSecondsTZ, stdNumTZ, stdNumShortTZ, stdNumColonTZ, stdNumSecondsTz, stdNumColonSecondsTZ: - if (std == stdISO8601TZ || std == stdISO8601ShortTZ || std == stdISO8601ColonTZ) && len(value) >= 1 && value[0] == 'Z' { - value = value[1:] - z = UTC - break - } - var sign, hour, min, seconds string - if std == stdISO8601ColonTZ || std == stdNumColonTZ { - if len(value) < 6 { - err = errBad - break - } - if value[3] != ':' { - err = errBad - break - } - sign, hour, min, seconds, value = value[0:1], value[1:3], value[4:6], "00", value[6:] - } else if std == stdNumShortTZ || std == stdISO8601ShortTZ { - if len(value) < 3 { - err = errBad - break - } - sign, hour, min, seconds, value = value[0:1], value[1:3], "00", "00", value[3:] - } else if std == stdISO8601ColonSecondsTZ || std == stdNumColonSecondsTZ { - if len(value) < 9 { - err = errBad - break - } - if value[3] != ':' || value[6] != ':' { - err = errBad - break - } - sign, hour, min, seconds, value = value[0:1], value[1:3], value[4:6], value[7:9], value[9:] - } else if std == stdISO8601SecondsTZ || std == stdNumSecondsTz { - if len(value) < 7 { - err = errBad - break - } - sign, hour, min, seconds, value = value[0:1], value[1:3], value[3:5], value[5:7], value[7:] - } else { - if len(value) < 5 { - err = errBad - break - } - sign, hour, min, seconds, value = value[0:1], value[1:3], value[3:5], "00", value[5:] - } - var hr, mm, ss int - hr, err = atoi(hour) - if err == nil { - mm, err = atoi(min) - } - if err == nil { - ss, err = atoi(seconds) - } - zoneOffset = (hr*60+mm)*60 + ss // offset is in seconds - switch sign[0] { - case '+': - case '-': - zoneOffset = -zoneOffset - default: - err = errBad - } - case stdTZ: - // Does it look like a time zone? - if len(value) >= 3 && value[0:3] == "UTC" { - z = UTC - value = value[3:] - break - } - n, ok := parseTimeZone(value) - if !ok { - err = errBad - break - } - zoneName, value = value[:n], value[n:] - case stdFracSecond0: - // stdFracSecond0 requires the exact number of digits as specified in - // the layout. ndigit := 1 + digitsLen(std) if len(value) < ndigit { err = errBad @@ -1215,14 +963,10 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error) } nsec, rangeErrString, err = parseNanoseconds(value, ndigit) value = value[ndigit:] - case stdFracSecond9: if len(value) < 2 || !commaOrPeriod(value[0]) || value[1] < '0' || '9' < value[1] { - // Fractional second omitted. break } - // Take any number of digits, even more than asked for, - // because it is what the stdSecond case would do. i := 0 for i < 9 && i+1 < len(value) && '0' <= value[i+1] && value[i+1] <= '9' { i++ @@ -1231,107 +975,26 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error) value = value[1+i:] } if rangeErrString != "" { - return Time{}, &ParseError{alayout, avalue, stdstr, value, ": " + rangeErrString + " out of range"} + return Time(0), &ParseError{alayout, avalue, stdstr, value, ": " + rangeErrString + " out of range"} } if err != nil { - return Time{}, &ParseError{alayout, avalue, stdstr, value, ""} + return Time(0), &ParseError{alayout, avalue, stdstr, value, ""} } } + if pmSet && hour < 12 { hour += 12 } else if amSet && hour == 12 { hour = 0 } - // Convert yday to day, month. - if yday >= 0 { - var d int - var m int - if isLeap(year) { - if yday == 31+29 { - m = int(February) - d = 29 - } else if yday > 31+29 { - yday-- - } - } - if yday < 1 || yday > 365 { - return Time{}, &ParseError{alayout, avalue, "", value, ": day-of-year out of range"} - } - if m == 0 { - m = (yday-1)/31 + 1 - if int(daysBefore[m]) < yday { - m++ - } - d = yday - int(daysBefore[m-1]) - } - // If month, day already seen, yday's m, d must match. - // Otherwise, set them from m, d. - if month >= 0 && month != m { - return Time{}, &ParseError{alayout, avalue, "", value, ": day-of-year does not match month"} - } - month = m - if day >= 0 && day != d { - return Time{}, &ParseError{alayout, avalue, "", value, ": day-of-year does not match day"} - } - day = d - } else { - if month < 0 { - month = int(January) - } - if day < 0 { - day = 1 - } - } - // Validate the day of the month. if day < 1 || day > daysIn(Month(month), year) { - return Time{}, &ParseError{alayout, avalue, "", value, ": day out of range"} - } - - if z != nil { - return Date(year, Month(month), day, hour, min, sec, nsec, z), nil - } - - if zoneOffset != -1 { - t := Date(year, Month(month), day, hour, min, sec, nsec, UTC) - t.addSec(-int64(zoneOffset)) - - // Look for local zone with the given offset. - // If that zone was in effect at the given time, use it. - name, offset, _, _, _ := local.lookup(t.unixSec()) - if offset == zoneOffset && (zoneName == "" || name == zoneName) { - t.setLoc(local) - return t, nil - } - - // Otherwise create fake zone to record offset. - t.setLoc(FixedZone(zoneName, zoneOffset)) - return t, nil - } - - if zoneName != "" { - t := Date(year, Month(month), day, hour, min, sec, nsec, UTC) - // Look for local zone with the given offset. - // If that zone was in effect at the given time, use it. - offset, ok := local.lookupName(zoneName, t.unixSec()) - if ok { - t.addSec(-int64(offset)) - t.setLoc(local) - return t, nil - } - - // Otherwise, create fake zone with unknown offset. - if len(zoneName) > 3 && zoneName[:3] == "GMT" { - offset, _ = atoi(zoneName[3:]) // Guaranteed OK by parseGMT. - offset *= 3600 - } - t.setLoc(FixedZone(zoneName, offset)) - return t, nil + return Time(0), &ParseError{alayout, avalue, "", value, ": day out of range"} } - // Otherwise, fall back to default. - return Date(year, Month(month), day, hour, min, sec, nsec, defaultLocation), nil + // Return the parsed time in UTC. + return Date(year, Month(month), day, hour, min, sec, nsec), nil } // parseTimeZone parses a time zone string and returns its length. Time zones From 27ffb4261c1e25f2b71787174b49d34e749a19ef Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Mon, 21 Oct 2024 15:01:19 +0200 Subject: [PATCH 03/27] feat: remove tz logic --- gnovm/stdlibs/time/timezoneinfo.gno | 656 +-------------------------- gnovm/stdlibs/time/zoneinfo_read.gno | 353 -------------- 2 files changed, 6 insertions(+), 1003 deletions(-) delete mode 100644 gnovm/stdlibs/time/zoneinfo_read.gno diff --git a/gnovm/stdlibs/time/timezoneinfo.gno b/gnovm/stdlibs/time/timezoneinfo.gno index 826c2988a8c..704fd790fc3 100644 --- a/gnovm/stdlibs/time/timezoneinfo.gno +++ b/gnovm/stdlibs/time/timezoneinfo.gno @@ -1,671 +1,27 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - package time import ( "errors" - // "sync" XXX - // "syscall" XXX ) -//go:generate env ZONEINFO=$GOROOT/lib/time/zoneinfo.zip go run genzabbrs.go -output zoneinfo_abbrs_windows.go - -// A Location maps time instants to the zone in use at that time. -// Typically, the Location represents the collection of time offsets -// in use in a geographical area. For many Locations the time offset varies -// depending on whether daylight savings time is in use at the time instant. -type Location struct { - name string - zone []zone - tx []zoneTrans - - // The tzdata information can be followed by a string that describes - // how to handle DST transitions not recorded in zoneTrans. - // The format is the TZ environment variable without a colon; see - // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html. - // Example string, for America/Los_Angeles: PST8PDT,M3.2.0,M11.1.0 - extend string - - // Most lookups will be for the current time. - // To avoid the binary search through tx, keep a - // static one-element cache that gives the correct - // zone for the time when the Location was created. - // if cacheStart <= t < cacheEnd, - // lookup can return cacheZone. - // The units for cacheStart and cacheEnd are seconds - // since January 1, 1970 UTC, to match the argument - // to lookup. - cacheStart int64 - cacheEnd int64 - cacheZone *zone -} - -// A zone represents a single time zone such as CET. -type zone struct { - name string // abbreviated name, "CET" - offset int // seconds east of UTC - isDST bool // is this zone Daylight Savings Time? -} - -// A zoneTrans represents a single time zone transition. -type zoneTrans struct { - when int64 // transition time, in seconds since 1970 GMT - index uint8 // the index of the zone that goes into effect at that time - isstd, isutc bool // ignored - no idea what these mean -} - -// alpha and omega are the beginning and end of time for zone -// transitions. -const ( - alpha = -1 << 63 // math.MinInt64 - omega = 1<<63 - 1 // math.MaxInt64 -) - -// UTC represents Universal Coordinated Time (UTC). var UTC *Location = &utcLoc -// utcLoc is separate so that get can refer to &utcLoc -// and ensure that it never returns a nil *Location, -// even if a badly behaved client has changed UTC. var utcLoc = Location{name: "UTC"} -// Local represents the system's local time zone. -// On Unix systems, Local consults the TZ environment -// variable to find the time zone to use. No TZ means -// use the system default /etc/localtime. -// TZ="" means use UTC. -// TZ="foo" means use file foo in the system timezone directory. -var Local *Location = &localLoc - -// localLoc is separate so that initLocal can initialize -// it even if a client has changed Local. -var localLoc Location - -// XXX var localOnce sync.Once - -func (l *Location) get() *Location { - if l == nil { - return &utcLoc - } - if l == &localLoc { - // XXX localOnce.Do(initLocal) - } - return l +type Location struct { + name string } -// String returns a descriptive name for the time zone information, -// corresponding to the name argument to LoadLocation or FixedZone. func (l *Location) String() string { - return l.get().name -} - -// FixedZone returns a Location that always uses -// the given zone name and offset (seconds east of UTC). -func FixedZone(name string, offset int) *Location { - l := &Location{ - name: name, - zone: []zone{{name, offset, false}}, - tx: []zoneTrans{{alpha, 0, false, false}}, - cacheStart: alpha, - cacheEnd: omega, - } - l.cacheZone = &l.zone[0] - return l -} - -// lookup returns information about the time zone in use at an -// instant in time expressed as seconds since January 1, 1970 00:00:00 UTC. -// -// The returned information gives the name of the zone (such as "CET"), -// the start and end times bracketing sec when that zone is in effect, -// the offset in seconds east of UTC (such as -5*60*60), and whether -// the daylight savings is being observed at that time. -func (l *Location) lookup(sec int64) (name string, offset int, start, end int64, isDST bool) { - l = l.get() - - if len(l.zone) == 0 { - name = "UTC" - offset = 0 - start = alpha - end = omega - isDST = false - return - } - - if zone := l.cacheZone; zone != nil && l.cacheStart <= sec && sec < l.cacheEnd { - name = zone.name - offset = zone.offset - start = l.cacheStart - end = l.cacheEnd - isDST = zone.isDST - return - } - - if len(l.tx) == 0 || sec < l.tx[0].when { - zone := &l.zone[l.lookupFirstZone()] - name = zone.name - offset = zone.offset - start = alpha - if len(l.tx) > 0 { - end = l.tx[0].when - } else { - end = omega - } - isDST = zone.isDST - return - } - - // Binary search for entry with largest time <= sec. - // Not using sort.Search to avoid dependencies. - tx := l.tx - end = omega - lo := 0 - hi := len(tx) - for hi-lo > 1 { - m := lo + (hi-lo)/2 - lim := tx[m].when - if sec < lim { - end = lim - hi = m - } else { - lo = m - } - } - zone := &l.zone[tx[lo].index] - name = zone.name - offset = zone.offset - start = tx[lo].when - // end = maintained during the search - isDST = zone.isDST - - // If we're at the end of the known zone transitions, - // try the extend string. - if lo == len(tx)-1 && l.extend != "" { - if ename, eoffset, estart, eend, eisDST, ok := tzset(l.extend, end, sec); ok { - return ename, eoffset, estart, eend, eisDST - } - } - - return -} - -// lookupFirstZone returns the index of the time zone to use for times -// before the first transition time, or when there are no transition -// times. -// -// The reference implementation in localtime.c from -// https://www.iana.org/time-zones/repository/releases/tzcode2013g.tar.gz -// implements the following algorithm for these cases: -// 1. If the first zone is unused by the transitions, use it. -// 2. Otherwise, if there are transition times, and the first -// transition is to a zone in daylight time, find the first -// non-daylight-time zone before and closest to the first transition -// zone. -// 3. Otherwise, use the first zone that is not daylight time, if -// there is one. -// 4. Otherwise, use the first zone. -func (l *Location) lookupFirstZone() int { - // Case 1. - if !l.firstZoneUsed() { - return 0 - } - - // Case 2. - if len(l.tx) > 0 && l.zone[l.tx[0].index].isDST { - for zi := int(l.tx[0].index) - 1; zi >= 0; zi-- { - if !l.zone[zi].isDST { - return zi - } - } - } - - // Case 3. - for zi := range l.zone { - if !l.zone[zi].isDST { - return zi - } - } - - // Case 4. - return 0 -} - -// firstZoneUsed reports whether the first zone is used by some -// transition. -func (l *Location) firstZoneUsed() bool { - for _, tx := range l.tx { - if tx.index == 0 { - return true - } - } - return false -} - -// tzset takes a timezone string like the one found in the TZ environment -// variable, the end 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, initEnd, 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, initEnd, 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 -} - -// lookupName returns information about the time zone with -// the given name (such as "EST") at the given pseudo-Unix time -// (what the given time of day would be in UTC). -func (l *Location) lookupName(name string, unix int64) (offset int, ok bool) { - l = l.get() - - // First try for a zone with the right name that was actually - // in effect at the given time. (In Sydney, Australia, both standard - // and daylight-savings time are abbreviated "EST". Using the - // offset helps us pick the right one for the given time. - // It's not perfect: during the backward transition we might pick - // either one.) - for i := range l.zone { - zone := &l.zone[i] - if zone.name == name { - nam, offset, _, _, _ := l.lookup(unix - int64(zone.offset)) - if nam == zone.name { - return offset, true - } - } - } - - // Otherwise fall back to an ordinary name match. - for i := range l.zone { - zone := &l.zone[i] - if zone.name == name { - return zone.offset, true - } + if l == nil { + return "UTC" } - - // Otherwise, give up. - return + return l.name } -// NOTE(rsc): Eventually we will need to accept the POSIX TZ environment -// syntax too, but I don't feel like implementing it today. - -var errLocation = errors.New("time: invalid location name") - -var zoneinfo *string - -func loadFromEmbeddedTZData(name string) ([]byte, bool) // injected - -// XXX var zoneinfoOnce sync.Once - -// LoadLocation returns the Location with the given name. -// -// If the name is "" or "UTC", LoadLocation returns UTC. -// If the name is "Local", LoadLocation returns Local. -// -// Otherwise, the name is taken to be a location name corresponding to a file -// in the IANA Time Zone database, such as "America/New_York". -// -// LoadLocation looks for the IANA Time Zone database in the following -// locations in order: -// -// - the directory or uncompressed zip file named by the ZONEINFO environment variable -// - on a Unix system, the system standard installation location -// - $GOROOT/lib/time/zoneinfo.zip -// - the time/tzdata package, if it was imported func LoadLocation(name string) (*Location, error) { if name == "" || name == "UTC" { return UTC, nil } - if name == "Local" { - return Local, nil - } - if containsDotDot(name) || name[0] == '/' || name[0] == '\\' { - // No valid IANA Time Zone name contains a single dot, - // much less dot dot. Likewise, none begin with a slash. - return nil, errLocation - } - - return loadLocation(name) -} - -// containsDotDot reports whether s contains "..". -func containsDotDot(s string) bool { - if len(s) < 2 { - return false - } - for i := 0; i < len(s)-1; i++ { - if s[i] == '.' && s[i+1] == '.' { - return true - } - } - return false + return nil, errors.New("time: unsupported location; only UTC is available") } diff --git a/gnovm/stdlibs/time/zoneinfo_read.gno b/gnovm/stdlibs/time/zoneinfo_read.gno deleted file mode 100644 index 2621b2b6631..00000000000 --- a/gnovm/stdlibs/time/zoneinfo_read.gno +++ /dev/null @@ -1,353 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Parse "zoneinfo" time zone file. -// This is a fairly standard file format used on OS X, Linux, BSD, Sun, and others. -// See tzfile(5), https://en.wikipedia.org/wiki/Zoneinfo, -// and ftp://munnari.oz.au/pub/oldtz/ - -package time - -import ( - "errors" -) - -// Simple I/O interface to binary blob of data. -type dataIO struct { - p []byte - error bool -} - -func (d *dataIO) read(n int) []byte { - if len(d.p) < n { - d.p = nil - d.error = true - return nil - } - p := d.p[0:n] - d.p = d.p[n:] - return p -} - -func (d *dataIO) big4() (n uint32, ok bool) { - p := d.read(4) - if len(p) < 4 { - d.error = true - return 0, false - } - return uint32(p[3]) | uint32(p[2])<<8 | uint32(p[1])<<16 | uint32(p[0])<<24, true -} - -func (d *dataIO) big8() (n uint64, ok bool) { - n1, ok1 := d.big4() - n2, ok2 := d.big4() - if !ok1 || !ok2 { - d.error = true - return 0, false - } - return (uint64(n1) << 32) | uint64(n2), true -} - -func (d *dataIO) byte() (n byte, ok bool) { - p := d.read(1) - if len(p) < 1 { - d.error = true - return 0, false - } - return p[0], true -} - -// rest returns the rest of the data in the buffer. -func (d *dataIO) rest() []byte { - r := d.p - d.p = nil - return r -} - -// Make a string by stopping at the first NUL -func byteString(p []byte) string { - for i := 0; i < len(p); i++ { - if p[i] == 0 { - return string(p[0:i]) - } - } - return string(p) -} - -var errBadData = errors.New("malformed time zone information") - -// LoadLocationFromTZData returns a Location with the given name -// initialized from the IANA Time Zone database-formatted data. -// The data should be in the format of a standard IANA time zone file -// (for example, the content of /etc/localtime on Unix systems). -func LoadLocationFromTZData(name string, data []byte) (*Location, error) { - d := dataIO{data, false} - - // 4-byte magic "TZif" - if magic := d.read(4); string(magic) != "TZif" { - return nil, errBadData - } - - // 1-byte version, then 15 bytes of padding - var version int - var p []byte - if p = d.read(16); len(p) != 16 { - return nil, errBadData - } else { - switch p[0] { - case 0: - version = 1 - case '2': - version = 2 - case '3': - version = 3 - default: - return nil, errBadData - } - } - - // six big-endian 32-bit integers: - // number of UTC/local indicators - // number of standard/wall indicators - // number of leap seconds - // number of transition times - // number of local time zones - // number of characters of time zone abbrev strings - const ( - NUTCLocal = iota - NStdWall - NLeap - NTime - NZone - NChar - ) - var n [6]int - for i := 0; i < 6; i++ { - nn, ok := d.big4() - if !ok { - return nil, errBadData - } - if uint32(int(nn)) != nn { - return nil, errBadData - } - n[i] = int(nn) - } - - // If we have version 2 or 3, then the data is first written out - // in a 32-bit format, then written out again in a 64-bit format. - // Skip the 32-bit format and read the 64-bit one, as it can - // describe a broader range of dates. - - is64 := false - if version > 1 { - // Skip the 32-bit data. - skip := n[NTime]*4 + - n[NTime] + - n[NZone]*6 + - n[NChar] + - n[NLeap]*8 + - n[NStdWall] + - n[NUTCLocal] - // Skip the version 2 header that we just read. - skip += 4 + 16 - d.read(skip) - - is64 = true - - // Read the counts again, they can differ. - for i := 0; i < 6; i++ { - nn, ok := d.big4() - if !ok { - return nil, errBadData - } - if uint32(int(nn)) != nn { - return nil, errBadData - } - n[i] = int(nn) - } - } - - size := 4 - if is64 { - size = 8 - } - - // Transition times. - txtimes := dataIO{d.read(n[NTime] * size), false} - - // Time zone indices for transition times. - txzones := d.read(n[NTime]) - - // Zone info structures - zonedata := dataIO{d.read(n[NZone] * 6), false} - - // Time zone abbreviations. - abbrev := d.read(n[NChar]) - - // Leap-second time pairs - d.read(n[NLeap] * (size + 4)) - - // Whether tx times associated with local time types - // are specified as standard time or wall time. - isstd := d.read(n[NStdWall]) - - // Whether tx times associated with local time types - // are specified as UTC or local time. - isutc := d.read(n[NUTCLocal]) - - if d.error { // ran out of data - return nil, errBadData - } - - var extend string - rest := d.rest() - if len(rest) > 2 && rest[0] == '\n' && rest[len(rest)-1] == '\n' { - extend = string(rest[1 : len(rest)-1]) - } - - // Now we can build up a useful data structure. - // First the zone information. - // utcoff[4] isdst[1] nameindex[1] - nzone := n[NZone] - if nzone == 0 { - // Reject tzdata files with no zones. There's nothing useful in them. - // This also avoids a panic later when we add and then use a fake transition (golang.org/issue/29437). - return nil, errBadData - } - zones := make([]zone, nzone) - for i := range zones { - var ok bool - var n uint32 - if n, ok = zonedata.big4(); !ok { - return nil, errBadData - } - if uint32(int(n)) != n { - return nil, errBadData - } - zones[i].offset = int(int32(n)) - var b byte - if b, ok = zonedata.byte(); !ok { - return nil, errBadData - } - zones[i].isDST = b != 0 - if b, ok = zonedata.byte(); !ok || int(b) >= len(abbrev) { - return nil, errBadData - } - zones[i].name = byteString(abbrev[b:]) - } - - // Now the transition time info. - tx := make([]zoneTrans, n[NTime]) - for i := range tx { - var n int64 - if !is64 { - if n4, ok := txtimes.big4(); !ok { - return nil, errBadData - } else { - n = int64(int32(n4)) - } - } else { - if n8, ok := txtimes.big8(); !ok { - return nil, errBadData - } else { - n = int64(n8) - } - } - tx[i].when = n - if int(txzones[i]) >= len(zones) { - return nil, errBadData - } - tx[i].index = txzones[i] - if i < len(isstd) { - tx[i].isstd = isstd[i] != 0 - } - if i < len(isutc) { - tx[i].isutc = isutc[i] != 0 - } - } - - if len(tx) == 0 { - // Build fake transition to cover all time. - // This happens in fixed locations like "Etc/GMT0". - tx = append(tx, zoneTrans{when: alpha, index: 0}) - } - - // 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, - } - } - } - } - break - } - } - - return l, nil -} - -func findZone(zones []zone, name string, offset int, isDST bool) int { - for i, z := range zones { - if z.name == name && z.offset == offset && z.isDST == isDST { - return i - } - } - return -1 -} - -// get4 returns the little-endian 32-bit value in b. -func get4(b []byte) int { - if len(b) < 4 { - return 0 - } - return int(b[0]) | int(b[1])<<8 | int(b[2])<<16 | int(b[3])<<24 -} - -// get2 returns the little-endian 16-bit value in b. -func get2(b []byte) int { - if len(b) < 2 { - return 0 - } - return int(b[0]) | int(b[1])<<8 -} - -// loadLocation returns the Location with the given name from the -// embedded data.The first timezone data matching the given name -// that is successfully loaded and parsed is returned as a Location. -func loadLocation(name string) (*Location, error) { - zoneData, ok := loadFromEmbeddedTZData(name) - if !ok { - return nil, errors.New("unknown time zone " + name) - } - - if loc, err := LoadLocationFromTZData(name, zoneData); err == nil { - return loc, nil - } - - return nil, errors.New("unknown time zone " + name) -} From df848c66d737ef957c8f34c5921566c419886c13 Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Wed, 23 Oct 2024 16:28:10 +0200 Subject: [PATCH 04/27] chore: garbage files --- gnovm/stdlibs/time/format.go | 1285 ++++++++++++++++++++++++++++++++++ gnovm/stdlibs/time/time2.go | 865 +++++++++++++++++++++++ 2 files changed, 2150 insertions(+) create mode 100644 gnovm/stdlibs/time/format.go create mode 100644 gnovm/stdlibs/time/time2.go diff --git a/gnovm/stdlibs/time/format.go b/gnovm/stdlibs/time/format.go new file mode 100644 index 00000000000..5b53333d594 --- /dev/null +++ b/gnovm/stdlibs/time/format.go @@ -0,0 +1,1285 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package time2 + +import "errors" + +// These are predefined layouts for use in Time.Format and time.Parse. +// The reference time used in these layouts is the specific time stamp: +// +// 01/02 03:04:05PM '06 -0700 +// +// (January 2, 15:04:05, 2006, in time zone seven hours west of GMT). +// That value is recorded as the constant named Layout, listed below. As a Unix +// time, this is 1136239445. Since MST is GMT-0700, the reference would be +// printed by the Unix date command as: +// +// Mon Jan 2 15:04:05 MST 2006 +// +// It is a regrettable historic error that the date uses the American convention +// of putting the numerical month before the day. +// +// The example for Time.Format demonstrates the working of the layout string +// in detail and is a good reference. +// +// Note that the RFC822, RFC850, and RFC1123 formats should be applied +// only to local times. Applying them to UTC times will use "UTC" as the +// time zone abbreviation, while strictly speaking those RFCs require the +// use of "GMT" in that case. +// In general RFC1123Z should be used instead of RFC1123 for servers +// that insist on that format, and RFC3339 should be preferred for new protocols. +// RFC3339, RFC822, RFC822Z, RFC1123, and RFC1123Z are useful for formatting; +// when used with time.Parse they do not accept all the time formats +// permitted by the RFCs and they do accept time formats not formally defined. +// The RFC3339Nano format removes trailing zeros from the seconds field +// and thus may not sort correctly once formatted. +// +// Most programs can use one of the defined constants as the layout passed to +// Format or Parse. The rest of this comment can be ignored unless you are +// creating a custom layout string. +// +// To define your own format, write down what the reference time would look like +// formatted your way; see the values of constants like ANSIC, StampMicro or +// Kitchen for examples. The model is to demonstrate what the reference time +// looks like so that the Format and Parse methods can apply the same +// transformation to a general time value. +// +// Here is a summary of the components of a layout string. Each element shows by +// example the formatting of an element of the reference time. Only these values +// are recognized. Text in the layout string that is not recognized as part of +// the reference time is echoed verbatim during Format and expected to appear +// verbatim in the input to Parse. +// +// Year: "2006" "06" +// Month: "Jan" "January" "01" "1" +// Day of the week: "Mon" "Monday" +// Day of the month: "2" "_2" "02" +// Day of the year: "__2" "002" +// Hour: "15" "3" "03" (PM or AM) +// Minute: "4" "04" +// Second: "5" "05" +// AM/PM mark: "PM" +// +// Numeric time zone offsets format as follows: +// +// "-0700" ±hhmm +// "-07:00" ±hh:mm +// "-07" ±hh +// "-070000" ±hhmmss +// "-07:00:00" ±hh:mm:ss +// +// Replacing the sign in the format with a Z triggers +// the ISO 8601 behavior of printing Z instead of an +// offset for the UTC zone. Thus: +// +// "Z0700" Z or ±hhmm +// "Z07:00" Z or ±hh:mm +// "Z07" Z or ±hh +// "Z070000" Z or ±hhmmss +// "Z07:00:00" Z or ±hh:mm:ss +// +// Within the format string, the underscores in "_2" and "__2" represent spaces +// that may be replaced by digits if the following number has multiple digits, +// for compatibility with fixed-width Unix time formats. A leading zero represents +// a zero-padded value. +// +// The formats __2 and 002 are space-padded and zero-padded +// three-character day of year; there is no unpadded day of year format. +// +// A comma or decimal point followed by one or more zeros represents +// a fractional second, printed to the given number of decimal places. +// A comma or decimal point followed by one or more nines represents +// a fractional second, printed to the given number of decimal places, with +// trailing zeros removed. +// For example "15:04:05,000" or "15:04:05.000" formats or parses with +// millisecond precision. +// +// Some valid layouts are invalid time values for time.Parse, due to formats +// such as _ for space padding and Z for zone information. +const ( + Layout = "01/02 03:04:05PM '06 -0700" // The reference time, in numerical order. + ANSIC = "Mon Jan _2 15:04:05 2006" + UnixDate = "Mon Jan _2 15:04:05 MST 2006" + RubyDate = "Mon Jan 02 15:04:05 -0700 2006" + RFC822 = "02 Jan 06 15:04 MST" + RFC822Z = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone + RFC850 = "Monday, 02-Jan-06 15:04:05 MST" + RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST" + RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone + RFC3339 = "2006-01-02T15:04:05Z07:00" + RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00" + Kitchen = "3:04PM" + // Handy time stamps. + Stamp = "Jan _2 15:04:05" + StampMilli = "Jan _2 15:04:05.000" + StampMicro = "Jan _2 15:04:05.000000" + StampNano = "Jan _2 15:04:05.000000000" + DateTime = "2006-01-02 15:04:05" + DateOnly = "2006-01-02" + TimeOnly = "15:04:05" +) + +const ( + _ = iota + stdLongMonth = iota + stdNeedDate // "January" + stdMonth // "Jan" + stdNumMonth // "1" + stdZeroMonth // "01" + stdLongWeekDay // "Monday" + stdWeekDay // "Mon" + stdDay // "2" + stdUnderDay // "_2" + stdZeroDay // "02" + stdUnderYearDay // "__2" + stdZeroYearDay // "002" + stdHour = iota + stdNeedClock // "15" + stdHour12 // "3" + stdZeroHour12 // "03" + stdMinute // "4" + stdZeroMinute // "04" + stdSecond // "5" + stdZeroSecond // "05" + stdLongYear = iota + stdNeedDate // "2006" + stdYear // "06" + stdPM = iota + stdNeedClock // "PM" + stdpm // "pm" + stdTZ = iota // "MST" + stdISO8601TZ // "Z0700" // prints Z for UTC + stdISO8601SecondsTZ // "Z070000" + stdISO8601ShortTZ // "Z07" + stdISO8601ColonTZ // "Z07:00" // prints Z for UTC + stdISO8601ColonSecondsTZ // "Z07:00:00" + stdNumTZ // "-0700" // always numeric + stdNumSecondsTz // "-070000" + stdNumShortTZ // "-07" // always numeric + stdNumColonTZ // "-07:00" // always numeric + stdNumColonSecondsTZ // "-07:00:00" + stdFracSecond0 // ".0", ".00", ... , trailing zeros included + stdFracSecond9 // ".9", ".99", ..., trailing zeros omitted + + stdNeedDate = 1 << 8 // need month, day, year + stdNeedClock = 2 << 8 // need hour, minute, second + stdArgShift = 16 // extra argument in high bits, above low stdArgShift + stdSeparatorShift = 28 // extra argument in high 4 bits for fractional second separators + stdMask = 1<= i+3 && layout[i:i+3] == "Jan" { + if len(layout) >= i+7 && layout[i:i+7] == "January" { + return layout[0:i], stdLongMonth, layout[i+7:] + } + if !startsWithLowerCase(layout[i+3:]) { + return layout[0:i], stdMonth, layout[i+3:] + } + } + + case 'M': // Monday, Mon, MST + if len(layout) >= i+3 { + if layout[i:i+3] == "Mon" { + if len(layout) >= i+6 && layout[i:i+6] == "Monday" { + return layout[0:i], stdLongWeekDay, layout[i+6:] + } + if !startsWithLowerCase(layout[i+3:]) { + return layout[0:i], stdWeekDay, layout[i+3:] + } + } + if layout[i:i+3] == "MST" { + return layout[0:i], stdTZ, layout[i+3:] + } + } + + case '0': // 01, 02, 03, 04, 05, 06, 002 + if len(layout) >= i+2 && '1' <= layout[i+1] && layout[i+1] <= '6' { + return layout[0:i], std0x[layout[i+1]-'1'], layout[i+2:] + } + if len(layout) >= i+3 && layout[i+1] == '0' && layout[i+2] == '2' { + return layout[0:i], stdZeroYearDay, layout[i+3:] + } + + case '1': // 15, 1 + if len(layout) >= i+2 && layout[i+1] == '5' { + return layout[0:i], stdHour, layout[i+2:] + } + return layout[0:i], stdNumMonth, layout[i+1:] + + case '2': // 2006, 2 + if len(layout) >= i+4 && layout[i:i+4] == "2006" { + return layout[0:i], stdLongYear, layout[i+4:] + } + return layout[0:i], stdDay, layout[i+1:] + + case '_': // _2, _2006, __2 + if len(layout) >= i+2 && layout[i+1] == '2' { + //_2006 is really a literal _, followed by stdLongYear + if len(layout) >= i+5 && layout[i+1:i+5] == "2006" { + return layout[0 : i+1], stdLongYear, layout[i+5:] + } + return layout[0:i], stdUnderDay, layout[i+2:] + } + if len(layout) >= i+3 && layout[i+1] == '_' && layout[i+2] == '2' { + return layout[0:i], stdUnderYearDay, layout[i+3:] + } + + case '3': + return layout[0:i], stdHour12, layout[i+1:] + + case '4': + return layout[0:i], stdMinute, layout[i+1:] + + case '5': + return layout[0:i], stdSecond, layout[i+1:] + + case 'P': // PM + if len(layout) >= i+2 && layout[i+1] == 'M' { + return layout[0:i], stdPM, layout[i+2:] + } + + case 'p': // pm + if len(layout) >= i+2 && layout[i+1] == 'm' { + return layout[0:i], stdpm, layout[i+2:] + } + + case '-': // -070000, -07:00:00, -0700, -07:00, -07 + if len(layout) >= i+7 && layout[i:i+7] == "-070000" { + return layout[0:i], stdNumSecondsTz, layout[i+7:] + } + if len(layout) >= i+9 && layout[i:i+9] == "-07:00:00" { + return layout[0:i], stdNumColonSecondsTZ, layout[i+9:] + } + if len(layout) >= i+5 && layout[i:i+5] == "-0700" { + return layout[0:i], stdNumTZ, layout[i+5:] + } + if len(layout) >= i+6 && layout[i:i+6] == "-07:00" { + return layout[0:i], stdNumColonTZ, layout[i+6:] + } + if len(layout) >= i+3 && layout[i:i+3] == "-07" { + return layout[0:i], stdNumShortTZ, layout[i+3:] + } + + case 'Z': // Z070000, Z07:00:00, Z0700, Z07:00, + if len(layout) >= i+7 && layout[i:i+7] == "Z070000" { + return layout[0:i], stdISO8601SecondsTZ, layout[i+7:] + } + if len(layout) >= i+9 && layout[i:i+9] == "Z07:00:00" { + return layout[0:i], stdISO8601ColonSecondsTZ, layout[i+9:] + } + if len(layout) >= i+5 && layout[i:i+5] == "Z0700" { + return layout[0:i], stdISO8601TZ, layout[i+5:] + } + if len(layout) >= i+6 && layout[i:i+6] == "Z07:00" { + return layout[0:i], stdISO8601ColonTZ, layout[i+6:] + } + if len(layout) >= i+3 && layout[i:i+3] == "Z07" { + return layout[0:i], stdISO8601ShortTZ, layout[i+3:] + } + + case '.', ',': // ,000, or .000, or ,999, or .999 - repeated digits for fractional seconds. + if i+1 < len(layout) && (layout[i+1] == '0' || layout[i+1] == '9') { + ch := layout[i+1] + j := i + 1 + for j < len(layout) && layout[j] == ch { + j++ + } + // String of digits must end here - only fractional second is all digits. + if !isDigit(layout, j) { + code := stdFracSecond0 + if layout[i+1] == '9' { + code = stdFracSecond9 + } + std := stdFracSecond(code, j-(i+1), c) + return layout[0:i], std, layout[j:] + } + } + } + } + return layout, 0, "" +} + +var longDayNames = []string{ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", +} + +var shortDayNames = []string{ + "Sun", + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", +} + +var shortMonthNames = []string{ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", +} + +var longMonthNames = []string{ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", +} + +// match reports whether s1 and s2 match ignoring case. +// It is assumed s1 and s2 are the same length. +func match(s1, s2 string) bool { + for i := 0; i < len(s1); i++ { + c1 := s1[i] + c2 := s2[i] + if c1 != c2 { + // Switch to lower-case; 'a'-'A' is known to be a single bit. + c1 |= 'a' - 'A' + c2 |= 'a' - 'A' + if c1 != c2 || c1 < 'a' || c1 > 'z' { + return false + } + } + } + return true +} + +func lookup(tab []string, val string) (int, string, error) { + for i, v := range tab { + if len(val) >= len(v) && match(val[0:len(v)], v) { + return i, val[len(v):], nil + } + } + return -1, val, errBad +} + +// appendInt appends the decimal form of x to b and returns the result. +// If the decimal form (excluding sign) is shorter than width, the result is padded with leading 0's. +// Duplicates functionality in strconv, but avoids dependency. +func appendInt(b []byte, x int, width int) []byte { + u := uint(x) + if x < 0 { + b = append(b, '-') + u = uint(-x) + } + + // Assemble decimal in reverse order. + var buf [20]byte + i := len(buf) + for u >= 10 { + i-- + q := u / 10 + buf[i] = byte('0' + u - q*10) + u = q + } + i-- + buf[i] = byte('0' + u) + + // Add 0-padding. + for w := len(buf) - i; w < width; w++ { + b = append(b, '0') + } + + return append(b, buf[i:]...) +} + +// Never printed, just needs to be non-nil for return by atoi. +var atoiError = errors.New("time: invalid number") + +// Duplicates functionality in strconv, but avoids dependency. +func atoi(s string) (x int, err error) { + neg := false + if s != "" && (s[0] == '-' || s[0] == '+') { + neg = s[0] == '-' + s = s[1:] + } + q, rem, err := leadingInt(s) + x = int(q) + if err != nil || rem != "" { + return 0, atoiError + } + if neg { + x = -x + } + return x, nil +} + +// The "std" value passed to formatNano contains two packed fields: the number of +// digits after the decimal and the separator character (period or comma). +// These functions pack and unpack that variable. +func stdFracSecond(code, n, c int) int { + // Use 0xfff to make the failure case even more absurd. + if c == '.' { + return code | ((n & 0xfff) << stdArgShift) + } + return code | ((n & 0xfff) << stdArgShift) | 1<> stdArgShift) & 0xfff +} + +func separator(std int) byte { + if (std >> stdSeparatorShift) == 0 { + return '.' + } + return ',' +} + +// formatNano appends a fractional second, as nanoseconds, to b +// and returns the result. +func formatNano(b []byte, nanosec uint, std int) []byte { + var ( + n = digitsLen(std) + separator = separator(std) + trim = std&stdMask == stdFracSecond9 + ) + u := nanosec + var buf [9]byte + for start := len(buf); start > 0; { + start-- + buf[start] = byte(u%10 + '0') + u /= 10 + } + + if n > 9 { + n = 9 + } + if trim { + for n > 0 && buf[n-1] == '0' { + n-- + } + if n == 0 { + return b + } + } + b = append(b, separator) + return append(b, buf[:n]...) +} + +// String returns the time formatted using the format string +// +// "2006-01-02 15:04:05.999999999 -0700 MST" +// +// If the time has a monotonic clock reading, the returned string +// includes a final field "m=±", where value is the monotonic +// clock reading formatted as a decimal number of seconds. +// +// The returned string is meant for debugging; for a stable serialized +// representation, use t.MarshalText, t.MarshalBinary, or t.Format +// with an explicit format string. +func (t Time) String() string { + return t.Format("2006-01-02 15:04:05.999999999 UTC") +} + +// GoString implements fmt.GoStringer and formats t to be printed in Go source +// code. +func (t Time) GoString() string { + buf := make([]byte, 0, 70) + buf = append(buf, "time.Date("...) + buf = appendInt(buf, t.Year(), 0) + month := t.Month() + if January <= month && month <= December { + buf = append(buf, ", time."...) + buf = append(buf, t.Month().String()...) + } else { + // It's difficult to construct a time.Time with a date outside the + // standard range but we might as well try to handle the case. + buf = appendInt(buf, int(month), 0) + } + buf = append(buf, ", "...) + buf = appendInt(buf, t.Day(), 0) + buf = append(buf, ", "...) + buf = appendInt(buf, t.Hour(), 0) + buf = append(buf, ", "...) + buf = appendInt(buf, t.Minute(), 0) + buf = append(buf, ", "...) + buf = appendInt(buf, t.Second(), 0) + buf = append(buf, ", "...) + buf = appendInt(buf, t.Nanosecond(), 0) + buf = append(buf, ", "...) + buf = append(buf, ')') + return string(buf) +} + +// Format returns a textual representation of the time value formatted according +// to the layout defined by the argument. See the documentation for the +// constant called Layout to see how to represent the layout format. +// +// The executable example for Time.Format demonstrates the working +// of the layout string in detail and is a good reference. +func (t Time) Format(layout string) string { + const bufSize = 64 + var b []byte + max := len(layout) + 10 + if max < bufSize { + var buf [bufSize]byte + b = buf[:0] + } else { + b = make([]byte, 0, max) + } + b = t.AppendFormat(b, layout) + return string(b) +} + +// AppendFormat is like Format but appends the textual +// representation to b and returns the extended buffer. +func (t Time) AppendFormat(b []byte, layout string) []byte { + var ( + year int + month Month + day int + hour int + min int + sec int + yday int + ) + + // Each iteration generates one std value. + for layout != "" { + prefix, std, suffix := nextStdChunk(layout) + if prefix != "" { + b = append(b, prefix...) + } + if std == 0 { + break + } + layout = suffix + + // Compute year, month, day if needed. + if std&stdNeedDate != 0 && year == 0 { + year, month, day = t.Date() + + // Calculate day of the year (yday) if needed. + yday = t.YearDay() + } + + // Compute hour, minute, second if needed. + if std&stdNeedClock != 0 && hour == 0 { + hour, min, sec = t.Clock() + } + + switch std & stdMask { + case stdYear: + b = appendInt(b, year%100, 2) + case stdLongYear: + b = appendInt(b, year, 4) + case stdMonth: + b = append(b, month.String()[:3]...) + case stdLongMonth: + b = append(b, month.String()...) + case stdNumMonth: + b = appendInt(b, int(month), 0) + case stdZeroMonth: + b = appendInt(b, int(month), 2) + case stdWeekDay: + b = append(b, t.Weekday().String()[:3]...) + case stdLongWeekDay: + b = append(b, t.Weekday().String()...) + case stdDay: + b = appendInt(b, day, 0) + case stdUnderDay: + if day < 10 { + b = append(b, ' ') + } + b = appendInt(b, day, 0) + case stdZeroDay: + b = appendInt(b, day, 2) + case stdUnderYearDay: + if yday < 100 { + b = append(b, ' ') + if yday < 10 { + b = append(b, ' ') + } + } + b = appendInt(b, yday, 0) + case stdZeroYearDay: + b = appendInt(b, yday, 3) + case stdHour: + b = appendInt(b, hour, 2) + case stdHour12: + hr := hour % 12 + if hr == 0 { + hr = 12 + } + b = appendInt(b, hr, 0) + case stdZeroHour12: + hr := hour % 12 + if hr == 0 { + hr = 12 + } + b = appendInt(b, hr, 2) + case stdMinute: + b = appendInt(b, min, 0) + case stdZeroMinute: + b = appendInt(b, min, 2) + case stdSecond: + b = appendInt(b, sec, 0) + case stdZeroSecond: + b = appendInt(b, sec, 2) + case stdPM: + if hour >= 12 { + b = append(b, "PM"...) + } else { + b = append(b, "AM"...) + } + case stdpm: + if hour >= 12 { + b = append(b, "pm"...) + } else { + b = append(b, "am"...) + } + case stdFracSecond0, stdFracSecond9: + b = formatNano(b, uint(t.Nanosecond()), std) + } + } + return b +} + +var errBad = errors.New("bad value for field") // placeholder not passed to user + +// ParseError describes a problem parsing a time string. +type ParseError struct { + Layout string + Value string + LayoutElem string + ValueElem string + Message string +} + +// These are borrowed from unicode/utf8 and strconv and replicate behavior in +// that package, since we can't take a dependency on either. +const ( + lowerhex = "0123456789abcdef" + runeSelf = 0x80 + runeError = '\uFFFD' +) + +func quote(s string) string { + buf := make([]byte, 1, len(s)+2) // slice will be at least len(s) + quotes + buf[0] = '"' + for i, c := range s { + if c >= runeSelf || c < ' ' { + // This means you are asking us to parse a time.Duration or + // time.Location with unprintable or non-ASCII characters in it. + // We don't expect to hit this case very often. We could try to + // reproduce strconv.Quote's behavior with full fidelity but + // given how rarely we expect to hit these edge cases, speed and + // conciseness are better. + var width int + if c == runeError { + width = 1 + if i+2 < len(s) && s[i:i+3] == string(runeError) { + width = 3 + } + } else { + width = len(string(c)) + } + for j := 0; j < width; j++ { + buf = append(buf, `\x`...) + buf = append(buf, lowerhex[s[i+j]>>4]) + buf = append(buf, lowerhex[s[i+j]&0xF]) + } + } else { + if c == '"' || c == '\\' { + buf = append(buf, '\\') + } + buf = append(buf, string(c)...) + } + } + buf = append(buf, '"') + return string(buf) +} + +// Error returns the string representation of a ParseError. +func (e *ParseError) Error() string { + if e.Message == "" { + return "parsing time " + + quote(e.Value) + " as " + + quote(e.Layout) + ": cannot parse " + + quote(e.ValueElem) + " as " + + quote(e.LayoutElem) + } + return "parsing time " + + quote(e.Value) + e.Message +} + +// isDigit reports whether s[i] is in range and is a decimal digit. +func isDigit(s string, i int) bool { + if len(s) <= i { + return false + } + c := s[i] + return '0' <= c && c <= '9' +} + +// getnum parses s[0:1] or s[0:2] (fixed forces s[0:2]) +// as a decimal integer and returns the integer and the +// remainder of the string. +func getnum(s string, fixed bool) (int, string, error) { + if !isDigit(s, 0) { + return 0, s, errBad + } + if !isDigit(s, 1) { + if fixed { + return 0, s, errBad + } + return int(s[0] - '0'), s[1:], nil + } + return int(s[0]-'0')*10 + int(s[1]-'0'), s[2:], nil +} + +// getnum3 parses s[0:1], s[0:2], or s[0:3] (fixed forces s[0:3]) +// as a decimal integer and returns the integer and the remainder +// of the string. +func getnum3(s string, fixed bool) (int, string, error) { + var n, i int + for i = 0; i < 3 && isDigit(s, i); i++ { + n = n*10 + int(s[i]-'0') + } + if i == 0 || fixed && i != 3 { + return 0, s, errBad + } + return n, s[i:], nil +} + +func cutspace(s string) string { + for len(s) > 0 && s[0] == ' ' { + s = s[1:] + } + return s +} + +// skip removes the given prefix from value, +// treating runs of space characters as equivalent. +func skip(value, prefix string) (string, error) { + for len(prefix) > 0 { + if prefix[0] == ' ' { + if len(value) > 0 && value[0] != ' ' { + return value, errBad + } + prefix = cutspace(prefix) + value = cutspace(value) + continue + } + if len(value) == 0 || value[0] != prefix[0] { + return value, errBad + } + prefix = prefix[1:] + value = value[1:] + } + return value, nil +} + +// Parse parses a formatted string and returns the time value it represents in UTC. +// The second argument must be parseable using the format string (layout) provided as the first argument. +func Parse(layout, value string) (Time, error) { + return parse(layout, value) +} + +func parse(layout, value string) (Time, error) { + alayout, avalue := layout, value + rangeErrString := "" + amSet := false // do we need to subtract 12 from the hour for midnight? + pmSet := false // do we need to add 12 to the hour? + + // Time being constructed. + var ( + year int + month int = -1 + day int = -1 + hour int + min int + sec int + nsec int + ) + + // Each iteration processes one std value. + for { + var err error + prefix, std, suffix := nextStdChunk(layout) + stdstr := layout[len(prefix) : len(layout)-len(suffix)] + value, err = skip(value, prefix) + if err != nil { + return Time(0), &ParseError{alayout, avalue, prefix, value, ""} + } + if std == 0 { + if len(value) != 0 { + return Time(0), &ParseError{alayout, avalue, "", value, ": extra text: " + quote(value)} + } + break + } + layout = suffix + var p string + switch std & stdMask { + case stdYear: + if len(value) < 2 { + err = errBad + break + } + hold := value + p, value = value[0:2], value[2:] + year, err = atoi(p) + if err != nil { + value = hold + } else if year >= 69 { + year += 1900 + } else { + year += 2000 + } + case stdLongYear: + if len(value) < 4 || !isDigit(value, 0) { + err = errBad + break + } + p, value = value[0:4], value[4:] + year, err = atoi(p) + case stdMonth: + month, value, err = lookup(shortMonthNames, value) + month++ + case stdLongMonth: + month, value, err = lookup(longMonthNames, value) + month++ + case stdNumMonth, stdZeroMonth: + month, value, err = getnum(value, std == stdZeroMonth) + if err == nil && (month <= 0 || 12 < month) { + rangeErrString = "month" + } + case stdWeekDay, stdLongWeekDay: + // Ignore weekday except for error checking. + _, value, err = lookup(longDayNames, value) + case stdDay, stdUnderDay, stdZeroDay: + if std == stdUnderDay && len(value) > 0 && value[0] == ' ' { + value = value[1:] + } + day, value, err = getnum(value, std == stdZeroDay) + case stdHour: + hour, value, err = getnum(value, false) + if hour < 0 || 24 <= hour { + rangeErrString = "hour" + } + case stdHour12, stdZeroHour12: + hour, value, err = getnum(value, std == stdZeroHour12) + if hour < 0 || 12 < hour { + rangeErrString = "hour" + } + case stdMinute, stdZeroMinute: + min, value, err = getnum(value, std == stdZeroMinute) + if min < 0 || 60 <= min { + rangeErrString = "minute" + } + case stdSecond, stdZeroSecond: + sec, value, err = getnum(value, std == stdZeroSecond) + if sec < 0 || 60 <= sec { + rangeErrString = "second" + break + } + // Special case: Handle fractional seconds even if not in layout. + if len(value) >= 2 && commaOrPeriod(value[0]) && isDigit(value, 1) { + _, std, _ = nextStdChunk(layout) + std &= stdMask + if std == stdFracSecond0 || std == stdFracSecond9 { + break + } + n := 2 + for ; n < len(value) && isDigit(value, n); n++ { + } + nsec, rangeErrString, err = parseNanoseconds(value, n) + value = value[n:] + } + case stdPM: + if len(value) < 2 { + err = errBad + break + } + p, value = value[0:2], value[2:] + switch p { + case "PM": + pmSet = true + case "AM": + amSet = true + default: + err = errBad + } + case stdpm: + if len(value) < 2 { + err = errBad + break + } + p, value = value[0:2], value[2:] + switch p { + case "pm": + pmSet = true + case "am": + amSet = true + default: + err = errBad + } + case stdFracSecond0: + ndigit := 1 + digitsLen(std) + if len(value) < ndigit { + err = errBad + break + } + nsec, rangeErrString, err = parseNanoseconds(value, ndigit) + value = value[ndigit:] + case stdFracSecond9: + if len(value) < 2 || !commaOrPeriod(value[0]) || value[1] < '0' || '9' < value[1] { + break + } + i := 0 + for i < 9 && i+1 < len(value) && '0' <= value[i+1] && value[i+1] <= '9' { + i++ + } + nsec, rangeErrString, err = parseNanoseconds(value, 1+i) + value = value[1+i:] + } + if rangeErrString != "" { + return Time(0), &ParseError{alayout, avalue, stdstr, value, ": " + rangeErrString + " out of range"} + } + if err != nil { + return Time(0), &ParseError{alayout, avalue, stdstr, value, ""} + } + } + + if pmSet && hour < 12 { + hour += 12 + } else if amSet && hour == 12 { + hour = 0 + } + + // Validate the day of the month. + if day < 1 || day > daysIn(Month(month), year) { + return Time(0), &ParseError{alayout, avalue, "", value, ": day out of range"} + } + + // Return the parsed time in UTC. + return Date(year, Month(month), day, hour, min, sec, nsec), nil +} + +// parseTimeZone parses a time zone string and returns its length. Time zones +// are human-generated and unpredictable. We can't do precise error checking. +// On the other hand, for a correct parse there must be a time zone at the +// beginning of the string, so it's almost always true that there's one +// there. We look at the beginning of the string for a run of upper-case letters. +// If there are more than 5, it's an error. +// If there are 4 or 5 and the last is a T, it's a time zone. +// If there are 3, it's a time zone. +// Otherwise, other than special cases, it's not a time zone. +// GMT is special because it can have an hour offset. +func parseTimeZone(value string) (length int, ok bool) { + if len(value) < 3 { + return 0, false + } + // Special case 1: ChST and MeST are the only zones with a lower-case letter. + if len(value) >= 4 && (value[:4] == "ChST" || value[:4] == "MeST") { + return 4, true + } + // Special case 2: GMT may have an hour offset; treat it specially. + if value[:3] == "GMT" { + length = parseGMT(value) + return length, true + } + // Special Case 3: Some time zones are not named, but have +/-00 format + if value[0] == '+' || value[0] == '-' { + length = parseSignedOffset(value) + ok := length > 0 // parseSignedOffset returns 0 in case of bad input + return length, ok + } + // How many upper-case letters are there? Need at least three, at most five. + var nUpper int + for nUpper = 0; nUpper < 6; nUpper++ { + if nUpper >= len(value) { + break + } + if c := value[nUpper]; c < 'A' || 'Z' < c { + break + } + } + switch nUpper { + case 0, 1, 2, 6: + return 0, false + case 5: // Must end in T to match. + if value[4] == 'T' { + return 5, true + } + case 4: + // Must end in T, except one special case. + if value[3] == 'T' || value[:4] == "WITA" { + return 4, true + } + case 3: + return 3, true + } + return 0, false +} + +// parseGMT parses a GMT time zone. The input string is known to start "GMT". +// The function checks whether that is followed by a sign and a number in the +// range -23 through +23 excluding zero. +func parseGMT(value string) int { + value = value[3:] + if len(value) == 0 { + return 3 + } + + return 3 + parseSignedOffset(value) +} + +// parseSignedOffset parses a signed timezone offset (e.g. "+03" or "-04"). +// The function checks for a signed number in the range -23 through +23 excluding zero. +// Returns length of the found offset string or 0 otherwise +func parseSignedOffset(value string) int { + sign := value[0] + if sign != '-' && sign != '+' { + return 0 + } + x, rem, err := leadingInt(value[1:]) + + // fail if nothing consumed by leadingInt + if err != nil || value[1:] == rem { + return 0 + } + if x > 23 { + return 0 + } + return len(value) - len(rem) +} + +func commaOrPeriod(b byte) bool { + return b == '.' || b == ',' +} + +func parseNanoseconds(value string, nbytes int) (ns int, rangeErrString string, err error) { + if !commaOrPeriod(value[0]) { + err = errBad + return + } + if nbytes > 10 { + value = value[:10] + nbytes = 10 + } + if ns, err = atoi(value[1:nbytes]); err != nil { + return + } + if ns < 0 { + rangeErrString = "fractional second" + return + } + // We need nanoseconds, which means scaling by the number + // of missing digits in the format, maximum length 10. + scaleDigits := 10 - nbytes + for i := 0; i < scaleDigits; i++ { + ns *= 10 + } + return +} + +var errLeadingInt = errors.New("time: bad [0-9]*") // never printed + +// leadingInt consumes the leading [0-9]* from s. +func leadingInt(s string) (x uint64, rem string, err error) { + i := 0 + for ; i < len(s); i++ { + c := s[i] + if c < '0' || c > '9' { + break + } + if x > 1<<63/10 { + // overflow + return 0, "", errLeadingInt + } + x = x*10 + uint64(c) - '0' + if x > 1<<63 { + // overflow + return 0, "", errLeadingInt + } + } + return x, s[i:], nil +} + +// leadingFraction consumes the leading [0-9]* from s. +// It is used only for fractions, so does not return an error on overflow, +// it just stops accumulating precision. +func leadingFraction(s string) (x uint64, scale float64, rem string) { + i := 0 + scale = 1 + overflow := false + for ; i < len(s); i++ { + c := s[i] + if c < '0' || c > '9' { + break + } + if overflow { + continue + } + if x > (1<<63-1)/10 { + // It's possible for overflow to give a positive number, so take care. + overflow = true + continue + } + y := x*10 + uint64(c) - '0' + if y > 1<<63 { + overflow = true + continue + } + x = y + scale *= 10 + } + return x, scale, s[i:] +} + +var unitMap = map[string]uint64{ + "ns": uint64(Nanosecond), + "us": uint64(Microsecond), + "µs": uint64(Microsecond), // U+00B5 = micro symbol + "μs": uint64(Microsecond), // U+03BC = Greek letter mu + "ms": uint64(Millisecond), + "s": uint64(Second), + "m": uint64(Minute), + "h": uint64(Hour), +} + +// ParseDuration parses a duration string. +// A duration string is a possibly signed sequence of +// decimal numbers, each with optional fraction and a unit suffix, +// such as "300ms", "-1.5h" or "2h45m". +// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". +func ParseDuration(s string) (Duration, error) { + // [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+ + orig := s + var d uint64 + neg := false + + // Consume [-+]? + if s != "" { + c := s[0] + if c == '-' || c == '+' { + neg = c == '-' + s = s[1:] + } + } + // Special case: if all that is left is "0", this is zero. + if s == "0" { + return 0, nil + } + if s == "" { + return 0, errors.New("time: invalid duration " + quote(orig)) + } + for s != "" { + var ( + v, f uint64 // integers before, after decimal point + scale float64 = 1 // value = v + f/scale + ) + + var err error + + // The next character must be [0-9.] + if !(s[0] == '.' || '0' <= s[0] && s[0] <= '9') { + return 0, errors.New("time: invalid duration " + quote(orig)) + } + // Consume [0-9]* + pl := len(s) + v, s, err = leadingInt(s) + if err != nil { + return 0, errors.New("time: invalid duration " + quote(orig)) + } + pre := pl != len(s) // whether we consumed anything before a period + + // Consume (\.[0-9]*)? + post := false + if s != "" && s[0] == '.' { + s = s[1:] + pl := len(s) + f, scale, s = leadingFraction(s) + post = pl != len(s) + } + if !pre && !post { + // no digits (e.g. ".s" or "-.s") + return 0, errors.New("time: invalid duration " + quote(orig)) + } + + // Consume unit. + i := 0 + for ; i < len(s); i++ { + c := s[i] + if c == '.' || '0' <= c && c <= '9' { + break + } + } + if i == 0 { + return 0, errors.New("time: missing unit in duration " + quote(orig)) + } + u := s[:i] + s = s[i:] + unit, ok := unitMap[u] + if !ok { + return 0, errors.New("time: unknown unit " + quote(u) + " in duration " + quote(orig)) + } + if v > 1<<63/unit { + // overflow + return 0, errors.New("time: invalid duration " + quote(orig)) + } + v *= unit + if f > 0 { + // float64 is needed to be nanosecond accurate for fractions of hours. + // v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit) + v += uint64(float64(f) * (float64(unit) / scale)) + if v > 1<<63 { + // overflow + return 0, errors.New("time: invalid duration " + quote(orig)) + } + } + d += v + if d > 1<<63 { + return 0, errors.New("time: invalid duration " + quote(orig)) + } + } + if neg { + return -Duration(d), nil + } + if d > 1<<63-1 { + return 0, errors.New("time: invalid duration " + quote(orig)) + } + return Duration(d), nil +} diff --git a/gnovm/stdlibs/time/time2.go b/gnovm/stdlibs/time/time2.go new file mode 100644 index 00000000000..ddfe3021443 --- /dev/null +++ b/gnovm/stdlibs/time/time2.go @@ -0,0 +1,865 @@ +package time2 + +import "errors" + +const ( + minWall = wallToInternal // year 1885 + + // The year of the zero Time. + // Assumed by the unixToInternal computation below. + internalYear = 1 + + // Offsets to convert between internal and absolute or Unix times. + absoluteToInternal int64 = (absoluteZeroYear - internalYear) * 365.2425 * secondsPerDay + internalToAbsolute = -absoluteToInternal + unixToInternal int64 = (1969*365 + 1969/4 - 1969/100 + 1969/400) * secondsPerDay + internalToUnix int64 = -unixToInternal + wallToInternal int64 = (1884*365 + 1884/4 - 1884/100 + 1884/400) * secondsPerDay + + // The unsigned zero year for internal calculations. + // Must be 1 mod 400, and times before it will not compute correctly, + // but otherwise can be changed at will. + absoluteZeroYear = -292277022399 + + secondsPerMinute = 60 + secondsPerHour = 60 * secondsPerMinute + secondsPerDay = 24 * secondsPerHour + secondsPerWeek = 7 * secondsPerDay + daysPer400Years = 365*400 + 97 + daysPer100Years = 365*100 + 24 + daysPer4Years = 365*4 + 1 +) + +type Time struct { + sec int64 + nsec int32 +} + +func (t *Time) unixSec() int64 { return t.sec + internalToUnix } + +func (t Time) After(u Time) bool { + return t.sec > u.sec || t.sec == u.sec && t.nsec > u.nsec +} + +func (t Time) Before(u Time) bool { + return t.sec < u.sec || t.sec == u.sec && t.nsec < u.nsec +} + +func (t Time) Equal(u Time) bool { + return t.sec == u.sec && t.nsec == u.nsec +} + +type Month int + +const ( + January Month = 1 + iota + February + March + April + May + June + July + August + September + October + November + December +) + +func (m Month) String() string { + if January <= m && m <= December { + return longMonthNames[m-1] + } + buf := make([]byte, 20) + n := fmtInt(buf, uint64(m)) + return "%!Month(" + string(buf[n:]) + ")" +} + +type Weekday int + +const ( + Sunday Weekday = iota + Monday + Tuesday + Wednesday + Thursday + Friday + Saturday +) + +func (d Weekday) String() string { + if Sunday <= d && d <= Saturday { + return longDayNames[d] + } + buf := make([]byte, 20) + n := fmtInt(buf, uint64(d)) + return "%!Weekday(" + string(buf[n:]) + ")" +} + +func (t Time) IsZero() bool { + return t.sec == 0 && t.nsec == 0 +} + +// Date returns the year, month, and day in which t occurs. +func (t Time) Date() (year int, month Month, day int) { + year, month, day, _ = t.date(true) + return +} + +// Year returns the year in which t occurs. +func (t Time) Year() int { + year, _, _, _ := t.date(false) + return year +} + +// Month returns the month of the year specified by t. +func (t Time) Month() Month { + _, month, _, _ := t.date(false) + return month +} + +// Day returns the day of the month specified by t. +func (t Time) Day() int { + _, _, day, _ := t.date(true) + return day +} + +func (t Time) Weekday() Weekday { + return internalWeekday(t.internal()) +} + +func internalWeekday(it uint64) Weekday { + sec := (it + uint64(Monday)*secondsPerDay) % secondsPerWeek + return Weekday(sec / secondsPerDay) +} + +// ISOWeek returns the ISO 8601 year and week number in which t occurs. +// Week ranges from 1 to 53. Jan 01 to Jan 03 of year n might belong to +// week 52 or 53 of year n-1, and Dec 29 to Dec 31 might belong to week 1 +// of year n+1. +func (t Time) ISOWeek() (year, week int) { + // According to the rule that the first calendar week of a calendar year is + // the week including the first Thursday of that year, and that the last one is + // the week immediately preceding the first calendar week of the next calendar year. + // See https://www.iso.org/obp/ui#iso:std:iso:8601:-1:ed-1:v1:en:term:3.1.1.23 for details. + + // weeks start with Monday + // Monday Tuesday Wednesday Thursday Friday Saturday Sunday + // 1 2 3 4 5 6 7 + // +3 +2 +1 0 -1 -2 -3 + // the offset to Thursday + it := t.internal() + d := Thursday - internalWeekday(it) + // handle Sunday + if d == 4 { + d -= 3 + } + // find the Thursday of the calendar week + it += uint64(d) * secondsPerDay + year, _, _, yday := internalDate(it, false) + return year, yday/7 + 1 +} + +// Clock returns the hour, minute, and second within the day specified by t. +func (t Time) Clock() (hour, min, sec int) { + return internalClock(t.internal()) +} + +// internalClock is like clock but operates on an internal time. +func internalClock(it uint64) (hour, min, sec int) { + sec = int(it % secondsPerDay) + hour = sec / secondsPerHour + sec -= hour * secondsPerHour + min = sec / secondsPerMinute + sec -= min * secondsPerMinute + return +} + +// Hour returns the hour within the day specified by t, in the range [0, 23]. +func (t Time) Hour() int { + return int(t.internal()%secondsPerDay) / secondsPerHour +} + +// Minute returns the minute offset within the hour specified by t, in the range [0, 59]. +func (t Time) Minute() int { + return int(t.internal()%secondsPerHour) / secondsPerMinute +} + +// Second returns the second offset within the minute specified by t, in the range [0, 59]. +func (t Time) Second() int { + return int(t.internal() % secondsPerMinute) +} + +// Nanosecond returns the nanosecond offset within the second specified by t, +// in the range [0, 999999999]. +func (t Time) Nanosecond() int { + return int(t.nsec) +} + +// YearDay returns the day of the year specified by t, in the range [1,365] for non-leap years, +// and [1,366] in leap years. +func (t Time) YearDay() int { + _, _, _, yday := t.date(false) + return yday + 1 +} + +// AddDate returns the time corresponding to adding the +// given number of years, months, and days to t. +// For example, AddDate(-1, 2, 3) applied to January 1, 2011 +// returns March 4, 2010. +// +// AddDate normalizes its result in the same way that Date does, +// so, for example, adding one month to October 31 yields +// December 1, the normalized form for November 31. +func (t Time) AddDate(years int, months int, days int) Time { + year, month, day := t.Date() + hour, min, sec := t.Clock() + return Date(year+years, month+Month(months), day+days, hour, min, sec, int(t.nsec)) +} + +// rename of the "abs" method in the original time.go +// since we removed the location & zoneinfo related code +func (t Time) internal() uint64 { + sec := t.unixSec() + return uint64(sec + (unixToInternal + internalToAbsolute)) +} + +func (t Time) date(full bool) (year int, month Month, day int, yday int) { + return internalDate(t.internal(), full) +} + +func internalDate(it uint64, full bool) (year int, month Month, day int, yday int) { + d := it / 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) { + 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). +var daysBefore = [...]int32{ + 0, + 31, + 31 + 28, + 31 + 28 + 31, + 31 + 28 + 31 + 30, + 31 + 28 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, +} + +// daysSinceEpoch takes a year and returns the number of days from +// the absolute epoch to the start of that year. +// This is basically (year - zeroYear) * 365, but accounting for leap days. +func daysSinceEpoch(year int) uint64 { + y := uint64(int64(year) - absoluteZeroYear) + + // Add in days from 400-year cycles. + n := y / 400 + y -= 400 * n + d := daysPer400Years * n + + // Add in 100-year cycles. + n = y / 100 + y -= 100 * n + d += daysPer100Years * n + + // Add in 4-year cycles. + n = y / 4 + y -= 4 * n + d += daysPer4Years * n + + // Add in non-leap years. + n = y + d += 365 * n + + return d +} + +const ( + Nanosecond Duration = 1 + Microsecond = 1000 * Nanosecond + Millisecond = 1000 * Microsecond + Second = 1000 * Millisecond + Minute = 60 * Second + Hour = 60 * Minute +) + +// A Duration represents the elapsed time between two instants +// as an int64 nanosecond count. The representation limits the +// largest representable duration to approximately 290 years. +type Duration int64 + +const ( + minDuration Duration = -1 << 63 + maxDuration Duration = 1<<63 - 1 +) + +// String returns a string representing the duration in the form "72h3m0.5s". +// Leading zero units are omitted. As a special case, durations less than one +// second format use a smaller unit (milli-, micro-, or nanoseconds) to ensure +// that the leading digit is non-zero. The zero duration formats as 0s. +func (d Duration) String() string { + // Largest time is 2540400h10m10.000000000s + var buf [32]byte + w := len(buf) + + u := uint64(d) + neg := d < 0 + if neg { + u = -u + } + + if u < uint64(Second) { + // Special case: if duration is smaller than a second, + // use smaller units, like 1.2ms + var prec int + w-- + buf[w] = 's' + w-- + switch { + case u == 0: + return "0s" + case u < uint64(Microsecond): + // print nanoseconds + prec = 0 + buf[w] = 'n' + case u < uint64(Millisecond): + // print microseconds + prec = 3 + // U+00B5 'µ' micro sign == 0xC2 0xB5 + w-- // Need room for two bytes. + copy(buf[w:], "µ") + default: + // print milliseconds + prec = 6 + buf[w] = 'm' + } + w, u = fmtFrac(buf[:w], u, prec) + w = fmtInt(buf[:w], u) + } else { + w-- + buf[w] = 's' + + w, u = fmtFrac(buf[:w], u, 9) + + // u is now integer seconds + w = fmtInt(buf[:w], u%60) + u /= 60 + + // u is now integer minutes + if u > 0 { + w-- + buf[w] = 'm' + w = fmtInt(buf[:w], u%60) + u /= 60 + + // u is now integer hours + // Stop at hours because days can be different lengths. + if u > 0 { + w-- + buf[w] = 'h' + w = fmtInt(buf[:w], u) + } + } + } + + if neg { + w-- + buf[w] = '-' + } + + return string(buf[w:]) +} + +// Nanoseconds returns the duration as an integer nanosecond count. +func (d Duration) Nanoseconds() int64 { return int64(d) } + +// Microseconds returns the duration as an integer microsecond count. +func (d Duration) Microseconds() int64 { return int64(d) / 1e3 } + +// Milliseconds returns the duration as an integer millisecond count. +func (d Duration) Milliseconds() int64 { return int64(d) / 1e6 } + +// These methods return float64 because the dominant +// use case is for printing a floating point number like 1.5s, and +// a truncation to integer would make them not useful in those cases. +// Splitting the integer and fraction ourselves guarantees that +// converting the returned float64 to an integer rounds the same +// way that a pure integer conversion would have, even in cases +// where, say, float64(d.Nanoseconds())/1e9 would have rounded +// differently. + +// Seconds returns the duration as a floating point number of seconds. +func (d Duration) Seconds() float64 { + sec := d / Second + nsec := d % Second + return float64(sec) + float64(nsec)/1e9 +} + +// Minutes returns the duration as a floating point number of minutes. +func (d Duration) Minutes() float64 { + min := d / Minute + nsec := d % Minute + return float64(min) + float64(nsec)/(60*1e9) +} + +// Hours returns the duration as a floating point number of hours. +func (d Duration) Hours() float64 { + hour := d / Hour + nsec := d % Hour + return float64(hour) + float64(nsec)/(60*60*1e9) +} + +// Truncate returns the result of rounding d toward zero to a multiple of m. +// If m <= 0, Truncate returns d unchanged. +func (d Duration) Truncate(m Duration) Duration { + if m <= 0 { + return d + } + return d - d%m +} + +// lessThanHalf reports whether x+x < y but avoids overflow, +// assuming x and y are both positive (Duration is signed). +func lessThanHalf(x, y Duration) bool { + return uint64(x)+uint64(x) < uint64(y) +} + +// Round returns the result of rounding d to the nearest multiple of m. +// The rounding behavior for halfway values is to round away from zero. +// If the result exceeds the maximum (or minimum) +// value that can be stored in a Duration, +// Round returns the maximum (or minimum) duration. +// If m <= 0, Round returns d unchanged. +func (d Duration) Round(m Duration) Duration { + if m <= 0 { + return d + } + r := d % m + if d < 0 { + r = -r + if lessThanHalf(r, m) { + return d + r + } + if d1 := d - m + r; d1 < d { + return d1 + } + return minDuration // overflow + } + if lessThanHalf(r, m) { + return d - r + } + if d1 := d + m - r; d1 > d { + return d1 + } + return maxDuration // overflow +} + +// Add returns the time t+d +func (t Time) Add(d Duration) Time { + dsec := int64(d / 1e9) + nsec := t.nsec + int32(d%1e9) + if nsec >= 1e9 { + dsec++ + nsec -= 1e9 + } else if nsec < 0 { + dsec-- + nsec += 1e9 + } + return Time{t.sec + dsec, nsec} +} + +// Sub returns the duration t-u. If the result exceeds the maximum (or minimum) +// value that can be stored in a Duration, the maximum (or minimum) duration +// will be returned. +// To compute t-d for a duration d, use t.Add(-d). +func (t Time) Sub(u Time) Duration { + sec := t.sec - u.sec + nsec := t.nsec - u.nsec + if sec > 0 && nsec < 0 { + sec-- + nsec += 1e9 + } else if sec < 0 && nsec > 0 { + sec++ + nsec -= 1e9 + } + if sec > int64(maxDuration) { + return maxDuration + } + if sec < int64(minDuration) { + return minDuration + } + return Duration(sec*1e9 + int64(nsec)) +} + +// Since returns the time elapsed since t. +// It is shorthand for time.Now().Sub(t). +func Since(t Time) Duration { + return Now().Sub(t) +} + +// Until returns the duration until t. +// It is shorthand for t.Sub(time.Now()). +func Until(t Time) Duration { + return t.Sub(Now()) +} + +// Date returns the Time corresponding to +// +// yyyy-mm-dd hh:mm:ss + nsec nanoseconds +// +// The month, day, hour, min, sec, and nsec values may be outside +// their usual ranges and will be normalized during the conversion. +// For example, October 32 converts to November 1. +func Date(year int, month Month, day, hour, min, sec, nsec int) Time { + // Normalize month, overflowing into year. + m := int(month) - 1 + year, m = norm(year, m, 12) + month = Month(m) + 1 + + // Normalize nsec, sec, min, hour, overflowing into day. + sec, nsec = norm(sec, nsec, 1e9) + min, sec = norm(min, sec, 60) + hour, min = norm(hour, min, 60) + day, hour = norm(day, hour, 24) + + // Compute days since the absolute epoch. + d := daysSinceEpoch(year) + + // Add in days before this month. + d += uint64(daysBefore[month-1]) + if isLeap(year) && month >= March { + d++ // February 29 + } + + // Add in days before today. + d += uint64(day - 1) + + // Add in time elapsed today. + abs := d * secondsPerDay + abs += uint64(hour*secondsPerHour + min*secondsPerMinute + sec) + + unix := int64(abs) + (absoluteToInternal + internalToUnix) + t := unixTime(unix, int32(nsec)) + return t +} + +// Abs returns the absolute value of d. +// As a special case, math.MinInt64 is converted to math.MaxInt64. +func (d Duration) Abs() Duration { + switch { + case d >= 0: + return d + case d == minDuration: + return maxDuration + default: + return -d + } +} + +func now() (sec int64, nsec int32, mono int64) // injected by runtime + +func Now() Time { + sec, nsec, _ := now() + sec += unixToInternal - minWall + return Time{sec, nsec} +} + +// Unix returns t as a Unix time, the number of seconds elapsed +// since January 1, 1970 UTC. The result does not depend on the +// location associated with t. +// Unix-like operating systems often record time as a 32-bit +// count of seconds, but since the method here returns a 64-bit +// value it is valid for billions of years into the past or future. +func (t Time) Unix() int64 { + return t.unixSec() +} + +// UnixMilli returns t as a Unix time, the number of milliseconds elapsed since +// January 1, 1970 UTC. The result is undefined if the Unix time in +// milliseconds cannot be represented by an int64 (a date more than 292 million +// years before or after 1970). The result does not depend on the +// location associated with t. +func (t Time) UnixMilli() int64 { + return t.unixSec()*1e3 + int64(t.nsec)/1e6 +} + +// UnixMicro returns t as a Unix time, the number of microseconds elapsed since +// January 1, 1970 UTC. The result is undefined if the Unix time in +// microseconds cannot be represented by an int64 (a date before year -290307 or +// after year 294246). The result does not depend on the location associated +// with t. +func (t Time) UnixMicro() int64 { + return t.unixSec()*1e6 + int64(t.nsec)/1e3 +} + +// UnixNano returns t as a Unix time, the number of nanoseconds elapsed +// since January 1, 1970 UTC. The result is undefined if the Unix time +// in nanoseconds cannot be represented by an int64 (a date before the year +// 1678 or after 2262). Note that this means the result of calling UnixNano +// on the zero Time is undefined. The result does not depend on the +// location associated with t. +func (t Time) UnixNano() int64 { + return (t.unixSec())*1e9 + int64(t.nsec) +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface. +func (t Time) MarshalBinary() ([]byte, error) { + sec := t.sec + nsec := t.nsec + enc := []byte{ + //encode seconds (int64) / bytes 0 to 7 + byte(sec >> 56), + byte(sec >> 48), + byte(sec >> 40), + byte(sec >> 32), + byte(sec >> 24), + byte(sec >> 16), + byte(sec >> 8), + byte(sec), + //encode nanoseconds (int32) / bytes 8 to 11 + byte(nsec >> 24), + byte(nsec >> 16), + byte(nsec >> 8), + byte(nsec), + } + return enc, nil +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. +func (t *Time) UnmarshalBinary(data []byte) error { + buf := data + if len(buf) == 0 { + return errors.New("Time.UnmarshalBinary: no data") + } + if len(buf) != 12 { // 8 bytes for sec (int64) + 4 bytes for nsec (int32) + return errors.New("Time.UnmarshalBinary: invalid length") + } + sec := int64(buf[7]) | int64(buf[6])<<8 | int64(buf[5])<<16 | int64(buf[4])<<24 | + int64(buf[3])<<32 | int64(buf[2])<<40 | int64(buf[1])<<48 | int64(buf[0])<<56 + + buf = buf[8:] + nsec := int32(buf[3]) | int32(buf[2])<<8 | int32(buf[1])<<16 | int32(buf[0])<<24 + + *t = Time{} + t.sec = sec + t.nsec = nsec + + return nil +} + +// MarshalJSON implements the json.Marshaler interface. +// The time is a quoted string in RFC 3339 format, with sub-second precision added if present. +func (t Time) MarshalJSON() ([]byte, error) { + if y := t.Year(); y < 0 || y >= 10000 { + // RFC 3339 is clear that years are 4 digits exactly. + // See golang.org/issue/4556#c15 for more discussion. + return nil, errors.New("Time.MarshalJSON: year outside of range [0,9999]") + } + + b := make([]byte, 0, len(RFC3339Nano)+2) + b = append(b, '"') + b = t.AppendFormat(b, RFC3339Nano) + b = append(b, '"') + return b, nil +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +// The time is expected to be a quoted string in RFC 3339 format. +func (t *Time) UnmarshalJSON(data []byte) error { + // Ignore null, like in the main JSON package. + if string(data) == "null" { + return nil + } + // Fractional seconds are handled implicitly by Parse. + var err error + *t, err = Parse(`"`+RFC3339+`"`, string(data)) + return err +} + +// MarshalText implements the encoding.TextMarshaler interface. +// The time is formatted in RFC 3339 format, with sub-second precision added if present. +func (t Time) MarshalText() ([]byte, error) { + if y := t.Year(); y < 0 || y >= 10000 { + return nil, errors.New("Time.MarshalText: year outside of range [0,9999]") + } + + b := make([]byte, 0, len(RFC3339Nano)) + return t.AppendFormat(b, RFC3339Nano), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface. +// The time is expected to be in RFC 3339 format. +func (t *Time) UnmarshalText(data []byte) error { + // Fractional seconds are handled implicitly by Parse. + var err error + *t, err = Parse(RFC3339, string(data)) + return err +} + +// Unix returns the local Time corresponding to the given Unix time, +// sec seconds and nsec nanoseconds since January 1, 1970 UTC. +// It is valid to pass nsec outside the range [0, 999999999]. +// Not all sec values have a corresponding time value. One such +// value is 1<<63-1 (the largest int64 value). +func Unix(sec int64, nsec int64) Time { + if nsec < 0 || nsec >= 1e9 { + n := nsec / 1e9 + sec += n + nsec -= n * 1e9 + if nsec < 0 { + nsec += 1e9 + sec-- + } + } + return unixTime(sec, int32(nsec)) +} + +// UnixMilli returns the local Time corresponding to the given Unix time, +// msec milliseconds since January 1, 1970 UTC. +func UnixMilli(msec int64) Time { + return Unix(msec/1e3, (msec%1e3)*1e6) +} + +// UnixMicro returns the local Time corresponding to the given Unix time, +// usec microseconds since January 1, 1970 UTC. +func UnixMicro(usec int64) Time { + return Unix(usec/1e6, (usec%1e6)*1e3) +} + +func unixTime(sec int64, nsec int32) Time { + return Time{sec + unixToInternal, nsec} +} + +func isLeap(year int) bool { + return year%4 == 0 && (year%100 != 0 || year%400 == 0) +} + +// fmtFrac formats the fraction of v/10**prec (e.g., ".12345") into the +// tail of buf, omitting trailing zeros. It omits the decimal +// point too when the fraction is 0. It returns the index where the +// output bytes begin and the value v/10**prec. +func fmtFrac(buf []byte, v uint64, prec int) (nw int, nv uint64) { + // Omit trailing zeros up to and including decimal point. + w := len(buf) + isprint := false + for i := 0; i < prec; i++ { + digit := v % 10 + isprint = isprint || digit != 0 + if isprint { + w-- + buf[w] = byte(digit) + '0' + } + v /= 10 + } + if isprint { + w-- + buf[w] = '.' + } + return w, v +} + +// add v at the end of buf and return the index where the number starts in buf +func fmtInt(buf []byte, v uint64) int { + w := len(buf) + if v == 0 { + w-- + buf[w] = '0' + } else { + for v > 0 { + w-- + buf[w] = byte(v%10) + '0' + v /= 10 + } + } + return w +} + +// norm returns nhi, nlo such that +// +// hi * base + lo == nhi * base + nlo +// 0 <= nlo < base +func norm(hi, lo, base int) (nhi, nlo int) { + if lo < 0 { + n := (-lo-1)/base + 1 + hi -= n + lo += n * base + } + if lo >= base { + n := lo / base + hi += n + lo -= n * base + } + return hi, lo +} From 134d5945629d0e560dfe8073e17843fe5292d2be Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Wed, 23 Oct 2024 16:38:15 +0200 Subject: [PATCH 05/27] feat: adapt format to UTC based & without mono clock --- gnovm/stdlibs/time/format.go | 357 +++++++++++++++++++++++++++++++---- 1 file changed, 316 insertions(+), 41 deletions(-) diff --git a/gnovm/stdlibs/time/format.go b/gnovm/stdlibs/time/format.go index 5b53333d594..ef2a5fa2fc2 100644 --- a/gnovm/stdlibs/time/format.go +++ b/gnovm/stdlibs/time/format.go @@ -509,7 +509,8 @@ func formatNano(b []byte, nanosec uint, std int) []byte { // representation, use t.MarshalText, t.MarshalBinary, or t.Format // with an explicit format string. func (t Time) String() string { - return t.Format("2006-01-02 15:04:05.999999999 UTC") + s := t.Format("2006-01-02 15:04:05.999999999 -0700 MST") + return s } // GoString implements fmt.GoStringer and formats t to be printed in Go source @@ -538,6 +539,7 @@ func (t Time) GoString() string { buf = append(buf, ", "...) buf = appendInt(buf, t.Nanosecond(), 0) buf = append(buf, ", "...) + buf = append(buf, "time.UTC"...) buf = append(buf, ')') return string(buf) } @@ -566,15 +568,16 @@ func (t Time) Format(layout string) string { // representation to b and returns the extended buffer. func (t Time) AppendFormat(b []byte, layout string) []byte { var ( - year int + it = t.internal() + + year int = -1 month Month day int - hour int + yday int + hour int = -1 min int sec int - yday int ) - // Each iteration generates one std value. for layout != "" { prefix, std, suffix := nextStdChunk(layout) @@ -587,35 +590,39 @@ func (t Time) AppendFormat(b []byte, layout string) []byte { layout = suffix // Compute year, month, day if needed. - if std&stdNeedDate != 0 && year == 0 { - year, month, day = t.Date() - - // Calculate day of the year (yday) if needed. - yday = t.YearDay() + if year < 0 && std&stdNeedDate != 0 { + year, month, day, yday = internalDate(it, true) + yday++ } // Compute hour, minute, second if needed. - if std&stdNeedClock != 0 && hour == 0 { - hour, min, sec = t.Clock() + if hour < 0 && std&stdNeedClock != 0 { + hour, min, sec = internalClock(it) } switch std & stdMask { case stdYear: - b = appendInt(b, year%100, 2) + y := year + if y < 0 { + y = -y + } + b = appendInt(b, y%100, 2) case stdLongYear: b = appendInt(b, year, 4) case stdMonth: b = append(b, month.String()[:3]...) case stdLongMonth: - b = append(b, month.String()...) + m := month.String() + b = append(b, m...) case stdNumMonth: b = appendInt(b, int(month), 0) case stdZeroMonth: b = appendInt(b, int(month), 2) case stdWeekDay: - b = append(b, t.Weekday().String()[:3]...) + b = append(b, internalWeekday(it).String()[:3]...) case stdLongWeekDay: - b = append(b, t.Weekday().String()...) + s := internalWeekday(it).String() + b = append(b, s...) case stdDay: b = appendInt(b, day, 0) case stdUnderDay: @@ -638,12 +645,14 @@ func (t Time) AppendFormat(b []byte, layout string) []byte { case stdHour: b = appendInt(b, hour, 2) case stdHour12: + // Noon is 12PM, midnight is 12AM. hr := hour % 12 if hr == 0 { hr = 12 } b = appendInt(b, hr, 0) case stdZeroHour12: + // Noon is 12PM, midnight is 12AM. hr := hour % 12 if hr == 0 { hr = 12 @@ -669,6 +678,40 @@ func (t Time) AppendFormat(b []byte, layout string) []byte { } else { b = append(b, "am"...) } + case stdISO8601TZ, stdISO8601ColonTZ, stdISO8601SecondsTZ, stdISO8601ShortTZ, stdISO8601ColonSecondsTZ, stdNumTZ, stdNumColonTZ, stdNumSecondsTz, stdNumShortTZ, stdNumColonSecondsTZ: + // Ugly special case. We cheat and take the "Z" variants + // to mean "the time zone as formatted for ISO 8601". + if std == stdISO8601TZ || std == stdISO8601ColonTZ || std == stdISO8601SecondsTZ || std == stdISO8601ShortTZ || std == stdISO8601ColonSecondsTZ { + b = append(b, 'Z') + break + } + b = append(b, '+') + b = appendInt(b, 0, 2) + if std == stdISO8601ColonTZ || std == stdNumColonTZ || std == stdISO8601ColonSecondsTZ || std == stdNumColonSecondsTZ { + b = append(b, ':') + } + if std != stdNumShortTZ && std != stdISO8601ShortTZ { + b = appendInt(b, 0, 2) + } + + // append seconds if appropriate + if std == stdISO8601SecondsTZ || std == stdNumSecondsTz || std == stdNumColonSecondsTZ || std == stdISO8601ColonSecondsTZ { + if std == stdNumColonSecondsTZ || std == stdISO8601ColonSecondsTZ { + b = append(b, ':') + } + b = appendInt(b, 0, 2) + } + + case stdTZ: + if name != "" { + b = append(b, name...) + break + } + // No time zone known for this time, but we must print one. + // Use the -0700 format. + b = append(b, '+') + b = appendInt(b, 0, 2) + b = appendInt(b, 0, 2) case stdFracSecond0, stdFracSecond9: b = formatNano(b, uint(t.Nanosecond()), std) } @@ -811,27 +854,80 @@ func skip(value, prefix string) (string, error) { return value, nil } -// Parse parses a formatted string and returns the time value it represents in UTC. -// The second argument must be parseable using the format string (layout) provided as the first argument. +// Parse parses a formatted string and returns the time value it represents. +// See the documentation for the constant called Layout to see how to +// represent the format. The second argument must be parseable using +// the format string (layout) provided as the first argument. +// +// The example for Time.Format demonstrates the working of the layout string +// in detail and is a good reference. +// +// When parsing (only), the input may contain a fractional second +// field immediately after the seconds field, even if the layout does not +// signify its presence. In that case either a comma or a decimal point +// followed by a maximal series of digits is parsed as a fractional second. +// Fractional seconds are truncated to nanosecond precision. +// +// Elements omitted from the layout are assumed to be zero or, when +// zero is impossible, one, so parsing "3:04pm" returns the time +// corresponding to Jan 1, year 0, 15:04:00 UTC (note that because the year is +// 0, this time is before the zero Time). +// Years must be in the range 0000..9999. The day of the week is checked +// for syntax but it is otherwise ignored. +// +// For layouts specifying the two-digit year 06, a value NN >= 69 will be treated +// as 19NN and a value NN < 69 will be treated as 20NN. +// +// The remainder of this comment describes the handling of time zones. +// +// In the absence of a time zone indicator, Parse returns a time in UTC. +// +// When parsing a time with a zone offset like -0700, if the offset corresponds +// to a time zone used by the current location (Local), then Parse uses that +// location and zone in the returned time. Otherwise it records the time as +// being in a fabricated location with time fixed at the given zone offset. +// +// When parsing a time with a zone abbreviation like MST, if the zone abbreviation +// has a defined offset in the current location, then that offset is used. +// The zone abbreviation "UTC" is recognized as UTC regardless of location. +// If the zone abbreviation is unknown, Parse records the time as being +// in a fabricated location with the given zone abbreviation and a zero offset. +// This choice means that such a time can be parsed and reformatted with the +// same layout losslessly, but the exact instant used in the representation will +// differ by the actual zone offset. To avoid such problems, prefer time layouts +// that use a numeric zone offset, or use ParseInLocation. func Parse(layout, value string) (Time, error) { - return parse(layout, value) + return parse(layout, value, UTC, Local) +} + +// ParseInLocation is like Parse but differs in two important ways. +// First, in the absence of time zone information, Parse interprets a time as UTC; +// ParseInLocation interprets the time as in the given location. +// Second, when given a zone offset or abbreviation, Parse tries to match it +// against the Local location; ParseInLocation uses the given location. +func ParseInLocation(layout, value string, loc *Location) (Time, error) { + return parse(layout, value, loc, loc) } -func parse(layout, value string) (Time, error) { +func parse(layout, value string, defaultLocation, local *Location) (Time, error) { alayout, avalue := layout, value - rangeErrString := "" - amSet := false // do we need to subtract 12 from the hour for midnight? - pmSet := false // do we need to add 12 to the hour? + rangeErrString := "" // set if a value is out of range + amSet := false // do we need to subtract 12 from the hour for midnight? + pmSet := false // do we need to add 12 to the hour? // Time being constructed. var ( - year int - month int = -1 - day int = -1 - hour int - min int - sec int - nsec int + year int + month int = -1 + day int = -1 + yday int = -1 + hour int + min int + sec int + nsec int + z *Location + zoneOffset int = -1 + zoneName string ) // Each iteration processes one std value. @@ -841,11 +937,11 @@ func parse(layout, value string) (Time, error) { stdstr := layout[len(prefix) : len(layout)-len(suffix)] value, err = skip(value, prefix) if err != nil { - return Time(0), &ParseError{alayout, avalue, prefix, value, ""} + return Time{}, &ParseError{alayout, avalue, prefix, value, ""} } if std == 0 { if len(value) != 0 { - return Time(0), &ParseError{alayout, avalue, "", value, ": extra text: " + quote(value)} + return Time{}, &ParseError{alayout, avalue, "", value, ": extra text: " + quote(value)} } break } @@ -862,7 +958,7 @@ func parse(layout, value string) (Time, error) { year, err = atoi(p) if err != nil { value = hold - } else if year >= 69 { + } else if year >= 69 { // Unix time starts Dec 31 1969 in some time zones year += 1900 } else { year += 2000 @@ -885,14 +981,27 @@ func parse(layout, value string) (Time, error) { if err == nil && (month <= 0 || 12 < month) { rangeErrString = "month" } - case stdWeekDay, stdLongWeekDay: + case stdWeekDay: // Ignore weekday except for error checking. + _, value, err = lookup(shortDayNames, value) + case stdLongWeekDay: _, value, err = lookup(longDayNames, value) case stdDay, stdUnderDay, stdZeroDay: if std == stdUnderDay && len(value) > 0 && value[0] == ' ' { value = value[1:] } day, value, err = getnum(value, std == stdZeroDay) + // Note that we allow any one- or two-digit day here. + // The month, day, year combination is validated after we've completed parsing. + case stdUnderYearDay, stdZeroYearDay: + for i := 0; i < 2; i++ { + if std == stdUnderYearDay && len(value) > 0 && value[0] == ' ' { + value = value[1:] + } + } + yday, value, err = getnum3(value, std == stdZeroYearDay) + // Note that we allow any one-, two-, or three-digit year-day here. + // The year-day, year combination is validated after we've completed parsing. case stdHour: hour, value, err = getnum(value, false) if hour < 0 || 24 <= hour { @@ -914,13 +1023,16 @@ func parse(layout, value string) (Time, error) { rangeErrString = "second" break } - // Special case: Handle fractional seconds even if not in layout. + // Special case: do we have a fractional second but no + // fractional second in the format? if len(value) >= 2 && commaOrPeriod(value[0]) && isDigit(value, 1) { _, std, _ = nextStdChunk(layout) std &= stdMask if std == stdFracSecond0 || std == stdFracSecond9 { + // Fractional second in the layout; proceed normally break } + // No fractional second in the layout but we have one in the input. n := 2 for ; n < len(value) && isDigit(value, n); n++ { } @@ -955,7 +1067,85 @@ func parse(layout, value string) (Time, error) { default: err = errBad } + case stdISO8601TZ, stdISO8601ColonTZ, stdISO8601SecondsTZ, stdISO8601ShortTZ, stdISO8601ColonSecondsTZ, stdNumTZ, stdNumShortTZ, stdNumColonTZ, stdNumSecondsTz, stdNumColonSecondsTZ: + if (std == stdISO8601TZ || std == stdISO8601ShortTZ || std == stdISO8601ColonTZ) && len(value) >= 1 && value[0] == 'Z' { + value = value[1:] + z = UTC + break + } + var sign, hour, min, seconds string + if std == stdISO8601ColonTZ || std == stdNumColonTZ { + if len(value) < 6 { + err = errBad + break + } + if value[3] != ':' { + err = errBad + break + } + sign, hour, min, seconds, value = value[0:1], value[1:3], value[4:6], "00", value[6:] + } else if std == stdNumShortTZ || std == stdISO8601ShortTZ { + if len(value) < 3 { + err = errBad + break + } + sign, hour, min, seconds, value = value[0:1], value[1:3], "00", "00", value[3:] + } else if std == stdISO8601ColonSecondsTZ || std == stdNumColonSecondsTZ { + if len(value) < 9 { + err = errBad + break + } + if value[3] != ':' || value[6] != ':' { + err = errBad + break + } + sign, hour, min, seconds, value = value[0:1], value[1:3], value[4:6], value[7:9], value[9:] + } else if std == stdISO8601SecondsTZ || std == stdNumSecondsTz { + if len(value) < 7 { + err = errBad + break + } + sign, hour, min, seconds, value = value[0:1], value[1:3], value[3:5], value[5:7], value[7:] + } else { + if len(value) < 5 { + err = errBad + break + } + sign, hour, min, seconds, value = value[0:1], value[1:3], value[3:5], "00", value[5:] + } + var hr, mm, ss int + hr, err = atoi(hour) + if err == nil { + mm, err = atoi(min) + } + if err == nil { + ss, err = atoi(seconds) + } + zoneOffset = (hr*60+mm)*60 + ss // offset is in seconds + switch sign[0] { + case '+': + case '-': + zoneOffset = -zoneOffset + default: + err = errBad + } + case stdTZ: + // Does it look like a time zone? + if len(value) >= 3 && value[0:3] == "UTC" { + z = UTC + value = value[3:] + break + } + n, ok := parseTimeZone(value) + if !ok { + err = errBad + break + } + zoneName, value = value[:n], value[n:] + case stdFracSecond0: + // stdFracSecond0 requires the exact number of digits as specified in + // the layout. ndigit := 1 + digitsLen(std) if len(value) < ndigit { err = errBad @@ -963,10 +1153,14 @@ func parse(layout, value string) (Time, error) { } nsec, rangeErrString, err = parseNanoseconds(value, ndigit) value = value[ndigit:] + case stdFracSecond9: if len(value) < 2 || !commaOrPeriod(value[0]) || value[1] < '0' || '9' < value[1] { + // Fractional second omitted. break } + // Take any number of digits, even more than asked for, + // because it is what the stdSecond case would do. i := 0 for i < 9 && i+1 < len(value) && '0' <= value[i+1] && value[i+1] <= '9' { i++ @@ -975,26 +1169,107 @@ func parse(layout, value string) (Time, error) { value = value[1+i:] } if rangeErrString != "" { - return Time(0), &ParseError{alayout, avalue, stdstr, value, ": " + rangeErrString + " out of range"} + return Time{}, &ParseError{alayout, avalue, stdstr, value, ": " + rangeErrString + " out of range"} } if err != nil { - return Time(0), &ParseError{alayout, avalue, stdstr, value, ""} + return Time{}, &ParseError{alayout, avalue, stdstr, value, ""} } } - if pmSet && hour < 12 { hour += 12 } else if amSet && hour == 12 { hour = 0 } + // Convert yday to day, month. + if yday >= 0 { + var d int + var m int + if isLeap(year) { + if yday == 31+29 { + m = int(February) + d = 29 + } else if yday > 31+29 { + yday-- + } + } + if yday < 1 || yday > 365 { + return Time{}, &ParseError{alayout, avalue, "", value, ": day-of-year out of range"} + } + if m == 0 { + m = (yday-1)/31 + 1 + if int(daysBefore[m]) < yday { + m++ + } + d = yday - int(daysBefore[m-1]) + } + // If month, day already seen, yday's m, d must match. + // Otherwise, set them from m, d. + if month >= 0 && month != m { + return Time{}, &ParseError{alayout, avalue, "", value, ": day-of-year does not match month"} + } + month = m + if day >= 0 && day != d { + return Time{}, &ParseError{alayout, avalue, "", value, ": day-of-year does not match day"} + } + day = d + } else { + if month < 0 { + month = int(January) + } + if day < 0 { + day = 1 + } + } + // Validate the day of the month. if day < 1 || day > daysIn(Month(month), year) { - return Time(0), &ParseError{alayout, avalue, "", value, ": day out of range"} + return Time{}, &ParseError{alayout, avalue, "", value, ": day out of range"} + } + + if z != nil { + return Date(year, Month(month), day, hour, min, sec, nsec, z), nil + } + + if zoneOffset != -1 { + t := Date(year, Month(month), day, hour, min, sec, nsec, UTC) + t.addSec(-int64(zoneOffset)) + + // Look for local zone with the given offset. + // If that zone was in effect at the given time, use it. + name, offset, _, _, _ := local.lookup(t.unixSec()) + if offset == zoneOffset && (zoneName == "" || name == zoneName) { + t.setLoc(local) + return t, nil + } + + // Otherwise create fake zone to record offset. + t.setLoc(FixedZone(zoneName, zoneOffset)) + return t, nil + } + + if zoneName != "" { + t := Date(year, Month(month), day, hour, min, sec, nsec, UTC) + // Look for local zone with the given offset. + // If that zone was in effect at the given time, use it. + offset, ok := local.lookupName(zoneName, t.unixSec()) + if ok { + t.addSec(-int64(offset)) + t.setLoc(local) + return t, nil + } + + // Otherwise, create fake zone with unknown offset. + if len(zoneName) > 3 && zoneName[:3] == "GMT" { + offset, _ = atoi(zoneName[3:]) // Guaranteed OK by parseGMT. + offset *= 3600 + } + t.setLoc(FixedZone(zoneName, offset)) + return t, nil } - // Return the parsed time in UTC. - return Date(year, Month(month), day, hour, min, sec, nsec), nil + // Otherwise, fall back to default. + return Date(year, Month(month), day, hour, min, sec, nsec, defaultLocation), nil } // parseTimeZone parses a time zone string and returns its length. Time zones From 6dd23a6861899e78fe4f0e444563a5e2a3375ab5 Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Wed, 23 Oct 2024 16:55:53 +0200 Subject: [PATCH 06/27] feat: adapt time pkg --- gnovm/stdlibs/time/format.gno | 212 +++- gnovm/stdlibs/time/format.go | 1560 --------------------------- gnovm/stdlibs/time/time.gno | 845 ++++++--------- gnovm/stdlibs/time/time2.go | 865 --------------- gnovm/stdlibs/time/timezoneinfo.gno | 630 ++++++++++- 5 files changed, 1147 insertions(+), 2965 deletions(-) delete mode 100644 gnovm/stdlibs/time/format.go delete mode 100644 gnovm/stdlibs/time/time2.go diff --git a/gnovm/stdlibs/time/format.gno b/gnovm/stdlibs/time/format.gno index af0e5b7ac34..fc92b6eec49 100644 --- a/gnovm/stdlibs/time/format.gno +++ b/gnovm/stdlibs/time/format.gno @@ -509,7 +509,8 @@ func formatNano(b []byte, nanosec uint, std int) []byte { // representation, use t.MarshalText, t.MarshalBinary, or t.Format // with an explicit format string. func (t Time) String() string { - return t.Format("2006-01-02 15:04:05.999999999 UTC") + s := t.Format("2006-01-02 15:04:05.999999999 -0700 MST") + return s } // GoString implements fmt.GoStringer and formats t to be printed in Go source @@ -538,6 +539,7 @@ func (t Time) GoString() string { buf = append(buf, ", "...) buf = appendInt(buf, t.Nanosecond(), 0) buf = append(buf, ", "...) + buf = append(buf, "time.UTC"...) buf = append(buf, ')') return string(buf) } @@ -566,15 +568,16 @@ func (t Time) Format(layout string) string { // representation to b and returns the extended buffer. func (t Time) AppendFormat(b []byte, layout string) []byte { var ( - year int + it = t.internal() + + year int = -1 month Month day int - hour int + yday int + hour int = -1 min int sec int - yday int ) - // Each iteration generates one std value. for layout != "" { prefix, std, suffix := nextStdChunk(layout) @@ -587,35 +590,39 @@ func (t Time) AppendFormat(b []byte, layout string) []byte { layout = suffix // Compute year, month, day if needed. - if std&stdNeedDate != 0 && year == 0 { - year, month, day = t.Date() - - // Calculate day of the year (yday) if needed. - yday = t.YearDay() + if year < 0 && std&stdNeedDate != 0 { + year, month, day, yday = internalDate(it, true) + yday++ } // Compute hour, minute, second if needed. - if std&stdNeedClock != 0 && hour == 0 { - hour, min, sec = t.Clock() + if hour < 0 && std&stdNeedClock != 0 { + hour, min, sec = internalClock(it) } switch std & stdMask { case stdYear: - b = appendInt(b, year%100, 2) + y := year + if y < 0 { + y = -y + } + b = appendInt(b, y%100, 2) case stdLongYear: b = appendInt(b, year, 4) case stdMonth: b = append(b, month.String()[:3]...) case stdLongMonth: - b = append(b, month.String()...) + m := month.String() + b = append(b, m...) case stdNumMonth: b = appendInt(b, int(month), 0) case stdZeroMonth: b = appendInt(b, int(month), 2) case stdWeekDay: - b = append(b, t.Weekday().String()[:3]...) + b = append(b, internalWeekday(it).String()[:3]...) case stdLongWeekDay: - b = append(b, t.Weekday().String()...) + s := internalWeekday(it).String() + b = append(b, s...) case stdDay: b = appendInt(b, day, 0) case stdUnderDay: @@ -638,12 +645,14 @@ func (t Time) AppendFormat(b []byte, layout string) []byte { case stdHour: b = appendInt(b, hour, 2) case stdHour12: + // Noon is 12PM, midnight is 12AM. hr := hour % 12 if hr == 0 { hr = 12 } b = appendInt(b, hr, 0) case stdZeroHour12: + // Noon is 12PM, midnight is 12AM. hr := hour % 12 if hr == 0 { hr = 12 @@ -669,6 +678,32 @@ func (t Time) AppendFormat(b []byte, layout string) []byte { } else { b = append(b, "am"...) } + case stdISO8601TZ, stdISO8601ColonTZ, stdISO8601SecondsTZ, stdISO8601ShortTZ, stdISO8601ColonSecondsTZ, stdNumTZ, stdNumColonTZ, stdNumSecondsTz, stdNumShortTZ, stdNumColonSecondsTZ: + // Ugly special case. We cheat and take the "Z" variants + // to mean "the time zone as formatted for ISO 8601". + if std == stdISO8601TZ || std == stdISO8601ColonTZ || std == stdISO8601SecondsTZ || std == stdISO8601ShortTZ || std == stdISO8601ColonSecondsTZ { + b = append(b, 'Z') + break + } + b = append(b, '+') + b = appendInt(b, 0, 2) + if std == stdISO8601ColonTZ || std == stdNumColonTZ || std == stdISO8601ColonSecondsTZ || std == stdNumColonSecondsTZ { + b = append(b, ':') + } + if std != stdNumShortTZ && std != stdISO8601ShortTZ { + b = appendInt(b, 0, 2) + } + + // append seconds if appropriate + if std == stdISO8601SecondsTZ || std == stdNumSecondsTz || std == stdNumColonSecondsTZ || std == stdISO8601ColonSecondsTZ { + if std == stdNumColonSecondsTZ || std == stdISO8601ColonSecondsTZ { + b = append(b, ':') + } + b = appendInt(b, 0, 2) + } + + case stdTZ: + b = append(b, "UTC"...) case stdFracSecond0, stdFracSecond9: b = formatNano(b, uint(t.Nanosecond()), std) } @@ -811,23 +846,64 @@ func skip(value, prefix string) (string, error) { return value, nil } -// Parse parses a formatted string and returns the time value it represents in UTC. -// The second argument must be parseable using the format string (layout) provided as the first argument. +// Parse parses a formatted string and returns the time value it represents. +// See the documentation for the constant called Layout to see how to +// represent the format. The second argument must be parseable using +// the format string (layout) provided as the first argument. +// +// The example for Time.Format demonstrates the working of the layout string +// in detail and is a good reference. +// +// When parsing (only), the input may contain a fractional second +// field immediately after the seconds field, even if the layout does not +// signify its presence. In that case either a comma or a decimal point +// followed by a maximal series of digits is parsed as a fractional second. +// Fractional seconds are truncated to nanosecond precision. +// +// Elements omitted from the layout are assumed to be zero or, when +// zero is impossible, one, so parsing "3:04pm" returns the time +// corresponding to Jan 1, year 0, 15:04:00 UTC (note that because the year is +// 0, this time is before the zero Time). +// Years must be in the range 0000..9999. The day of the week is checked +// for syntax but it is otherwise ignored. +// +// For layouts specifying the two-digit year 06, a value NN >= 69 will be treated +// as 19NN and a value NN < 69 will be treated as 20NN. +// +// The remainder of this comment describes the handling of time zones. +// +// In the absence of a time zone indicator, Parse returns a time in UTC. +// +// When parsing a time with a zone offset like -0700, if the offset corresponds +// to a time zone used by the current location (Local), then Parse uses that +// location and zone in the returned time. Otherwise it records the time as +// being in a fabricated location with time fixed at the given zone offset. +// +// When parsing a time with a zone abbreviation like MST, if the zone abbreviation +// has a defined offset in the current location, then that offset is used. +// The zone abbreviation "UTC" is recognized as UTC regardless of location. +// If the zone abbreviation is unknown, Parse records the time as being +// in a fabricated location with the given zone abbreviation and a zero offset. +// This choice means that such a time can be parsed and reformatted with the +// same layout losslessly, but the exact instant used in the representation will +// differ by the actual zone offset. To avoid such problems, prefer time layouts +// that use a numeric zone offset, or use ParseInLocation. func Parse(layout, value string) (Time, error) { return parse(layout, value) } func parse(layout, value string) (Time, error) { alayout, avalue := layout, value - rangeErrString := "" - amSet := false // do we need to subtract 12 from the hour for midnight? - pmSet := false // do we need to add 12 to the hour? + rangeErrString := "" // set if a value is out of range + amSet := false // do we need to subtract 12 from the hour for midnight? + pmSet := false // do we need to add 12 to the hour? // Time being constructed. var ( year int month int = -1 day int = -1 + yday int = -1 hour int min int sec int @@ -841,11 +917,11 @@ func parse(layout, value string) (Time, error) { stdstr := layout[len(prefix) : len(layout)-len(suffix)] value, err = skip(value, prefix) if err != nil { - return Time(0), &ParseError{alayout, avalue, prefix, value, ""} + return Time{}, &ParseError{alayout, avalue, prefix, value, ""} } if std == 0 { if len(value) != 0 { - return Time(0), &ParseError{alayout, avalue, "", value, ": extra text: " + quote(value)} + return Time{}, &ParseError{alayout, avalue, "", value, ": extra text: " + quote(value)} } break } @@ -862,7 +938,7 @@ func parse(layout, value string) (Time, error) { year, err = atoi(p) if err != nil { value = hold - } else if year >= 69 { + } else if year >= 69 { // Unix time starts Dec 31 1969 in some time zones year += 1900 } else { year += 2000 @@ -885,14 +961,27 @@ func parse(layout, value string) (Time, error) { if err == nil && (month <= 0 || 12 < month) { rangeErrString = "month" } - case stdWeekDay, stdLongWeekDay: + case stdWeekDay: // Ignore weekday except for error checking. + _, value, err = lookup(shortDayNames, value) + case stdLongWeekDay: _, value, err = lookup(longDayNames, value) case stdDay, stdUnderDay, stdZeroDay: if std == stdUnderDay && len(value) > 0 && value[0] == ' ' { value = value[1:] } day, value, err = getnum(value, std == stdZeroDay) + // Note that we allow any one- or two-digit day here. + // The month, day, year combination is validated after we've completed parsing. + case stdUnderYearDay, stdZeroYearDay: + for i := 0; i < 2; i++ { + if std == stdUnderYearDay && len(value) > 0 && value[0] == ' ' { + value = value[1:] + } + } + yday, value, err = getnum3(value, std == stdZeroYearDay) + // Note that we allow any one-, two-, or three-digit year-day here. + // The year-day, year combination is validated after we've completed parsing. case stdHour: hour, value, err = getnum(value, false) if hour < 0 || 24 <= hour { @@ -914,13 +1003,16 @@ func parse(layout, value string) (Time, error) { rangeErrString = "second" break } - // Special case: Handle fractional seconds even if not in layout. + // Special case: do we have a fractional second but no + // fractional second in the format? if len(value) >= 2 && commaOrPeriod(value[0]) && isDigit(value, 1) { _, std, _ = nextStdChunk(layout) std &= stdMask if std == stdFracSecond0 || std == stdFracSecond9 { + // Fractional second in the layout; proceed normally break } + // No fractional second in the layout but we have one in the input. n := 2 for ; n < len(value) && isDigit(value, n); n++ { } @@ -955,7 +1047,22 @@ func parse(layout, value string) (Time, error) { default: err = errBad } + case stdISO8601TZ, stdISO8601ColonTZ, stdISO8601SecondsTZ, stdISO8601ShortTZ, stdISO8601ColonSecondsTZ, stdNumTZ, stdNumShortTZ, stdNumColonTZ, stdNumSecondsTz, stdNumColonSecondsTZ: + if (std == stdISO8601TZ || std == stdISO8601ShortTZ || std == stdISO8601ColonTZ) && len(value) >= 1 && value[0] == 'Z' { + value = value[1:] + break + } + case stdTZ: + // Does it look like a time zone? + if len(value) >= 3 && value[0:3] == "UTC" { + value = value[3:] + break + } + err = errBad + case stdFracSecond0: + // stdFracSecond0 requires the exact number of digits as specified in + // the layout. ndigit := 1 + digitsLen(std) if len(value) < ndigit { err = errBad @@ -963,10 +1070,14 @@ func parse(layout, value string) (Time, error) { } nsec, rangeErrString, err = parseNanoseconds(value, ndigit) value = value[ndigit:] + case stdFracSecond9: if len(value) < 2 || !commaOrPeriod(value[0]) || value[1] < '0' || '9' < value[1] { + // Fractional second omitted. break } + // Take any number of digits, even more than asked for, + // because it is what the stdSecond case would do. i := 0 for i < 9 && i+1 < len(value) && '0' <= value[i+1] && value[i+1] <= '9' { i++ @@ -975,25 +1086,64 @@ func parse(layout, value string) (Time, error) { value = value[1+i:] } if rangeErrString != "" { - return Time(0), &ParseError{alayout, avalue, stdstr, value, ": " + rangeErrString + " out of range"} + return Time{}, &ParseError{alayout, avalue, stdstr, value, ": " + rangeErrString + " out of range"} } if err != nil { - return Time(0), &ParseError{alayout, avalue, stdstr, value, ""} + return Time{}, &ParseError{alayout, avalue, stdstr, value, ""} } } - if pmSet && hour < 12 { hour += 12 } else if amSet && hour == 12 { hour = 0 } + // Convert yday to day, month. + if yday >= 0 { + var d int + var m int + if isLeap(year) { + if yday == 31+29 { + m = int(February) + d = 29 + } else if yday > 31+29 { + yday-- + } + } + if yday < 1 || yday > 365 { + return Time{}, &ParseError{alayout, avalue, "", value, ": day-of-year out of range"} + } + if m == 0 { + m = (yday-1)/31 + 1 + if int(daysBefore[m]) < yday { + m++ + } + d = yday - int(daysBefore[m-1]) + } + // If month, day already seen, yday's m, d must match. + // Otherwise, set them from m, d. + if month >= 0 && month != m { + return Time{}, &ParseError{alayout, avalue, "", value, ": day-of-year does not match month"} + } + month = m + if day >= 0 && day != d { + return Time{}, &ParseError{alayout, avalue, "", value, ": day-of-year does not match day"} + } + day = d + } else { + if month < 0 { + month = int(January) + } + if day < 0 { + day = 1 + } + } + // Validate the day of the month. if day < 1 || day > daysIn(Month(month), year) { - return Time(0), &ParseError{alayout, avalue, "", value, ": day out of range"} + return Time{}, &ParseError{alayout, avalue, "", value, ": day out of range"} } - // Return the parsed time in UTC. return Date(year, Month(month), day, hour, min, sec, nsec), nil } diff --git a/gnovm/stdlibs/time/format.go b/gnovm/stdlibs/time/format.go deleted file mode 100644 index ef2a5fa2fc2..00000000000 --- a/gnovm/stdlibs/time/format.go +++ /dev/null @@ -1,1560 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package time2 - -import "errors" - -// These are predefined layouts for use in Time.Format and time.Parse. -// The reference time used in these layouts is the specific time stamp: -// -// 01/02 03:04:05PM '06 -0700 -// -// (January 2, 15:04:05, 2006, in time zone seven hours west of GMT). -// That value is recorded as the constant named Layout, listed below. As a Unix -// time, this is 1136239445. Since MST is GMT-0700, the reference would be -// printed by the Unix date command as: -// -// Mon Jan 2 15:04:05 MST 2006 -// -// It is a regrettable historic error that the date uses the American convention -// of putting the numerical month before the day. -// -// The example for Time.Format demonstrates the working of the layout string -// in detail and is a good reference. -// -// Note that the RFC822, RFC850, and RFC1123 formats should be applied -// only to local times. Applying them to UTC times will use "UTC" as the -// time zone abbreviation, while strictly speaking those RFCs require the -// use of "GMT" in that case. -// In general RFC1123Z should be used instead of RFC1123 for servers -// that insist on that format, and RFC3339 should be preferred for new protocols. -// RFC3339, RFC822, RFC822Z, RFC1123, and RFC1123Z are useful for formatting; -// when used with time.Parse they do not accept all the time formats -// permitted by the RFCs and they do accept time formats not formally defined. -// The RFC3339Nano format removes trailing zeros from the seconds field -// and thus may not sort correctly once formatted. -// -// Most programs can use one of the defined constants as the layout passed to -// Format or Parse. The rest of this comment can be ignored unless you are -// creating a custom layout string. -// -// To define your own format, write down what the reference time would look like -// formatted your way; see the values of constants like ANSIC, StampMicro or -// Kitchen for examples. The model is to demonstrate what the reference time -// looks like so that the Format and Parse methods can apply the same -// transformation to a general time value. -// -// Here is a summary of the components of a layout string. Each element shows by -// example the formatting of an element of the reference time. Only these values -// are recognized. Text in the layout string that is not recognized as part of -// the reference time is echoed verbatim during Format and expected to appear -// verbatim in the input to Parse. -// -// Year: "2006" "06" -// Month: "Jan" "January" "01" "1" -// Day of the week: "Mon" "Monday" -// Day of the month: "2" "_2" "02" -// Day of the year: "__2" "002" -// Hour: "15" "3" "03" (PM or AM) -// Minute: "4" "04" -// Second: "5" "05" -// AM/PM mark: "PM" -// -// Numeric time zone offsets format as follows: -// -// "-0700" ±hhmm -// "-07:00" ±hh:mm -// "-07" ±hh -// "-070000" ±hhmmss -// "-07:00:00" ±hh:mm:ss -// -// Replacing the sign in the format with a Z triggers -// the ISO 8601 behavior of printing Z instead of an -// offset for the UTC zone. Thus: -// -// "Z0700" Z or ±hhmm -// "Z07:00" Z or ±hh:mm -// "Z07" Z or ±hh -// "Z070000" Z or ±hhmmss -// "Z07:00:00" Z or ±hh:mm:ss -// -// Within the format string, the underscores in "_2" and "__2" represent spaces -// that may be replaced by digits if the following number has multiple digits, -// for compatibility with fixed-width Unix time formats. A leading zero represents -// a zero-padded value. -// -// The formats __2 and 002 are space-padded and zero-padded -// three-character day of year; there is no unpadded day of year format. -// -// A comma or decimal point followed by one or more zeros represents -// a fractional second, printed to the given number of decimal places. -// A comma or decimal point followed by one or more nines represents -// a fractional second, printed to the given number of decimal places, with -// trailing zeros removed. -// For example "15:04:05,000" or "15:04:05.000" formats or parses with -// millisecond precision. -// -// Some valid layouts are invalid time values for time.Parse, due to formats -// such as _ for space padding and Z for zone information. -const ( - Layout = "01/02 03:04:05PM '06 -0700" // The reference time, in numerical order. - ANSIC = "Mon Jan _2 15:04:05 2006" - UnixDate = "Mon Jan _2 15:04:05 MST 2006" - RubyDate = "Mon Jan 02 15:04:05 -0700 2006" - RFC822 = "02 Jan 06 15:04 MST" - RFC822Z = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone - RFC850 = "Monday, 02-Jan-06 15:04:05 MST" - RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST" - RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone - RFC3339 = "2006-01-02T15:04:05Z07:00" - RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00" - Kitchen = "3:04PM" - // Handy time stamps. - Stamp = "Jan _2 15:04:05" - StampMilli = "Jan _2 15:04:05.000" - StampMicro = "Jan _2 15:04:05.000000" - StampNano = "Jan _2 15:04:05.000000000" - DateTime = "2006-01-02 15:04:05" - DateOnly = "2006-01-02" - TimeOnly = "15:04:05" -) - -const ( - _ = iota - stdLongMonth = iota + stdNeedDate // "January" - stdMonth // "Jan" - stdNumMonth // "1" - stdZeroMonth // "01" - stdLongWeekDay // "Monday" - stdWeekDay // "Mon" - stdDay // "2" - stdUnderDay // "_2" - stdZeroDay // "02" - stdUnderYearDay // "__2" - stdZeroYearDay // "002" - stdHour = iota + stdNeedClock // "15" - stdHour12 // "3" - stdZeroHour12 // "03" - stdMinute // "4" - stdZeroMinute // "04" - stdSecond // "5" - stdZeroSecond // "05" - stdLongYear = iota + stdNeedDate // "2006" - stdYear // "06" - stdPM = iota + stdNeedClock // "PM" - stdpm // "pm" - stdTZ = iota // "MST" - stdISO8601TZ // "Z0700" // prints Z for UTC - stdISO8601SecondsTZ // "Z070000" - stdISO8601ShortTZ // "Z07" - stdISO8601ColonTZ // "Z07:00" // prints Z for UTC - stdISO8601ColonSecondsTZ // "Z07:00:00" - stdNumTZ // "-0700" // always numeric - stdNumSecondsTz // "-070000" - stdNumShortTZ // "-07" // always numeric - stdNumColonTZ // "-07:00" // always numeric - stdNumColonSecondsTZ // "-07:00:00" - stdFracSecond0 // ".0", ".00", ... , trailing zeros included - stdFracSecond9 // ".9", ".99", ..., trailing zeros omitted - - stdNeedDate = 1 << 8 // need month, day, year - stdNeedClock = 2 << 8 // need hour, minute, second - stdArgShift = 16 // extra argument in high bits, above low stdArgShift - stdSeparatorShift = 28 // extra argument in high 4 bits for fractional second separators - stdMask = 1<= i+3 && layout[i:i+3] == "Jan" { - if len(layout) >= i+7 && layout[i:i+7] == "January" { - return layout[0:i], stdLongMonth, layout[i+7:] - } - if !startsWithLowerCase(layout[i+3:]) { - return layout[0:i], stdMonth, layout[i+3:] - } - } - - case 'M': // Monday, Mon, MST - if len(layout) >= i+3 { - if layout[i:i+3] == "Mon" { - if len(layout) >= i+6 && layout[i:i+6] == "Monday" { - return layout[0:i], stdLongWeekDay, layout[i+6:] - } - if !startsWithLowerCase(layout[i+3:]) { - return layout[0:i], stdWeekDay, layout[i+3:] - } - } - if layout[i:i+3] == "MST" { - return layout[0:i], stdTZ, layout[i+3:] - } - } - - case '0': // 01, 02, 03, 04, 05, 06, 002 - if len(layout) >= i+2 && '1' <= layout[i+1] && layout[i+1] <= '6' { - return layout[0:i], std0x[layout[i+1]-'1'], layout[i+2:] - } - if len(layout) >= i+3 && layout[i+1] == '0' && layout[i+2] == '2' { - return layout[0:i], stdZeroYearDay, layout[i+3:] - } - - case '1': // 15, 1 - if len(layout) >= i+2 && layout[i+1] == '5' { - return layout[0:i], stdHour, layout[i+2:] - } - return layout[0:i], stdNumMonth, layout[i+1:] - - case '2': // 2006, 2 - if len(layout) >= i+4 && layout[i:i+4] == "2006" { - return layout[0:i], stdLongYear, layout[i+4:] - } - return layout[0:i], stdDay, layout[i+1:] - - case '_': // _2, _2006, __2 - if len(layout) >= i+2 && layout[i+1] == '2' { - //_2006 is really a literal _, followed by stdLongYear - if len(layout) >= i+5 && layout[i+1:i+5] == "2006" { - return layout[0 : i+1], stdLongYear, layout[i+5:] - } - return layout[0:i], stdUnderDay, layout[i+2:] - } - if len(layout) >= i+3 && layout[i+1] == '_' && layout[i+2] == '2' { - return layout[0:i], stdUnderYearDay, layout[i+3:] - } - - case '3': - return layout[0:i], stdHour12, layout[i+1:] - - case '4': - return layout[0:i], stdMinute, layout[i+1:] - - case '5': - return layout[0:i], stdSecond, layout[i+1:] - - case 'P': // PM - if len(layout) >= i+2 && layout[i+1] == 'M' { - return layout[0:i], stdPM, layout[i+2:] - } - - case 'p': // pm - if len(layout) >= i+2 && layout[i+1] == 'm' { - return layout[0:i], stdpm, layout[i+2:] - } - - case '-': // -070000, -07:00:00, -0700, -07:00, -07 - if len(layout) >= i+7 && layout[i:i+7] == "-070000" { - return layout[0:i], stdNumSecondsTz, layout[i+7:] - } - if len(layout) >= i+9 && layout[i:i+9] == "-07:00:00" { - return layout[0:i], stdNumColonSecondsTZ, layout[i+9:] - } - if len(layout) >= i+5 && layout[i:i+5] == "-0700" { - return layout[0:i], stdNumTZ, layout[i+5:] - } - if len(layout) >= i+6 && layout[i:i+6] == "-07:00" { - return layout[0:i], stdNumColonTZ, layout[i+6:] - } - if len(layout) >= i+3 && layout[i:i+3] == "-07" { - return layout[0:i], stdNumShortTZ, layout[i+3:] - } - - case 'Z': // Z070000, Z07:00:00, Z0700, Z07:00, - if len(layout) >= i+7 && layout[i:i+7] == "Z070000" { - return layout[0:i], stdISO8601SecondsTZ, layout[i+7:] - } - if len(layout) >= i+9 && layout[i:i+9] == "Z07:00:00" { - return layout[0:i], stdISO8601ColonSecondsTZ, layout[i+9:] - } - if len(layout) >= i+5 && layout[i:i+5] == "Z0700" { - return layout[0:i], stdISO8601TZ, layout[i+5:] - } - if len(layout) >= i+6 && layout[i:i+6] == "Z07:00" { - return layout[0:i], stdISO8601ColonTZ, layout[i+6:] - } - if len(layout) >= i+3 && layout[i:i+3] == "Z07" { - return layout[0:i], stdISO8601ShortTZ, layout[i+3:] - } - - case '.', ',': // ,000, or .000, or ,999, or .999 - repeated digits for fractional seconds. - if i+1 < len(layout) && (layout[i+1] == '0' || layout[i+1] == '9') { - ch := layout[i+1] - j := i + 1 - for j < len(layout) && layout[j] == ch { - j++ - } - // String of digits must end here - only fractional second is all digits. - if !isDigit(layout, j) { - code := stdFracSecond0 - if layout[i+1] == '9' { - code = stdFracSecond9 - } - std := stdFracSecond(code, j-(i+1), c) - return layout[0:i], std, layout[j:] - } - } - } - } - return layout, 0, "" -} - -var longDayNames = []string{ - "Sunday", - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday", -} - -var shortDayNames = []string{ - "Sun", - "Mon", - "Tue", - "Wed", - "Thu", - "Fri", - "Sat", -} - -var shortMonthNames = []string{ - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", -} - -var longMonthNames = []string{ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", -} - -// match reports whether s1 and s2 match ignoring case. -// It is assumed s1 and s2 are the same length. -func match(s1, s2 string) bool { - for i := 0; i < len(s1); i++ { - c1 := s1[i] - c2 := s2[i] - if c1 != c2 { - // Switch to lower-case; 'a'-'A' is known to be a single bit. - c1 |= 'a' - 'A' - c2 |= 'a' - 'A' - if c1 != c2 || c1 < 'a' || c1 > 'z' { - return false - } - } - } - return true -} - -func lookup(tab []string, val string) (int, string, error) { - for i, v := range tab { - if len(val) >= len(v) && match(val[0:len(v)], v) { - return i, val[len(v):], nil - } - } - return -1, val, errBad -} - -// appendInt appends the decimal form of x to b and returns the result. -// If the decimal form (excluding sign) is shorter than width, the result is padded with leading 0's. -// Duplicates functionality in strconv, but avoids dependency. -func appendInt(b []byte, x int, width int) []byte { - u := uint(x) - if x < 0 { - b = append(b, '-') - u = uint(-x) - } - - // Assemble decimal in reverse order. - var buf [20]byte - i := len(buf) - for u >= 10 { - i-- - q := u / 10 - buf[i] = byte('0' + u - q*10) - u = q - } - i-- - buf[i] = byte('0' + u) - - // Add 0-padding. - for w := len(buf) - i; w < width; w++ { - b = append(b, '0') - } - - return append(b, buf[i:]...) -} - -// Never printed, just needs to be non-nil for return by atoi. -var atoiError = errors.New("time: invalid number") - -// Duplicates functionality in strconv, but avoids dependency. -func atoi(s string) (x int, err error) { - neg := false - if s != "" && (s[0] == '-' || s[0] == '+') { - neg = s[0] == '-' - s = s[1:] - } - q, rem, err := leadingInt(s) - x = int(q) - if err != nil || rem != "" { - return 0, atoiError - } - if neg { - x = -x - } - return x, nil -} - -// The "std" value passed to formatNano contains two packed fields: the number of -// digits after the decimal and the separator character (period or comma). -// These functions pack and unpack that variable. -func stdFracSecond(code, n, c int) int { - // Use 0xfff to make the failure case even more absurd. - if c == '.' { - return code | ((n & 0xfff) << stdArgShift) - } - return code | ((n & 0xfff) << stdArgShift) | 1<> stdArgShift) & 0xfff -} - -func separator(std int) byte { - if (std >> stdSeparatorShift) == 0 { - return '.' - } - return ',' -} - -// formatNano appends a fractional second, as nanoseconds, to b -// and returns the result. -func formatNano(b []byte, nanosec uint, std int) []byte { - var ( - n = digitsLen(std) - separator = separator(std) - trim = std&stdMask == stdFracSecond9 - ) - u := nanosec - var buf [9]byte - for start := len(buf); start > 0; { - start-- - buf[start] = byte(u%10 + '0') - u /= 10 - } - - if n > 9 { - n = 9 - } - if trim { - for n > 0 && buf[n-1] == '0' { - n-- - } - if n == 0 { - return b - } - } - b = append(b, separator) - return append(b, buf[:n]...) -} - -// String returns the time formatted using the format string -// -// "2006-01-02 15:04:05.999999999 -0700 MST" -// -// If the time has a monotonic clock reading, the returned string -// includes a final field "m=±", where value is the monotonic -// clock reading formatted as a decimal number of seconds. -// -// The returned string is meant for debugging; for a stable serialized -// representation, use t.MarshalText, t.MarshalBinary, or t.Format -// with an explicit format string. -func (t Time) String() string { - s := t.Format("2006-01-02 15:04:05.999999999 -0700 MST") - return s -} - -// GoString implements fmt.GoStringer and formats t to be printed in Go source -// code. -func (t Time) GoString() string { - buf := make([]byte, 0, 70) - buf = append(buf, "time.Date("...) - buf = appendInt(buf, t.Year(), 0) - month := t.Month() - if January <= month && month <= December { - buf = append(buf, ", time."...) - buf = append(buf, t.Month().String()...) - } else { - // It's difficult to construct a time.Time with a date outside the - // standard range but we might as well try to handle the case. - buf = appendInt(buf, int(month), 0) - } - buf = append(buf, ", "...) - buf = appendInt(buf, t.Day(), 0) - buf = append(buf, ", "...) - buf = appendInt(buf, t.Hour(), 0) - buf = append(buf, ", "...) - buf = appendInt(buf, t.Minute(), 0) - buf = append(buf, ", "...) - buf = appendInt(buf, t.Second(), 0) - buf = append(buf, ", "...) - buf = appendInt(buf, t.Nanosecond(), 0) - buf = append(buf, ", "...) - buf = append(buf, "time.UTC"...) - buf = append(buf, ')') - return string(buf) -} - -// Format returns a textual representation of the time value formatted according -// to the layout defined by the argument. See the documentation for the -// constant called Layout to see how to represent the layout format. -// -// The executable example for Time.Format demonstrates the working -// of the layout string in detail and is a good reference. -func (t Time) Format(layout string) string { - const bufSize = 64 - var b []byte - max := len(layout) + 10 - if max < bufSize { - var buf [bufSize]byte - b = buf[:0] - } else { - b = make([]byte, 0, max) - } - b = t.AppendFormat(b, layout) - return string(b) -} - -// AppendFormat is like Format but appends the textual -// representation to b and returns the extended buffer. -func (t Time) AppendFormat(b []byte, layout string) []byte { - var ( - it = t.internal() - - year int = -1 - month Month - day int - yday int - hour int = -1 - min int - sec int - ) - // Each iteration generates one std value. - for layout != "" { - prefix, std, suffix := nextStdChunk(layout) - if prefix != "" { - b = append(b, prefix...) - } - if std == 0 { - break - } - layout = suffix - - // Compute year, month, day if needed. - if year < 0 && std&stdNeedDate != 0 { - year, month, day, yday = internalDate(it, true) - yday++ - } - - // Compute hour, minute, second if needed. - if hour < 0 && std&stdNeedClock != 0 { - hour, min, sec = internalClock(it) - } - - switch std & stdMask { - case stdYear: - y := year - if y < 0 { - y = -y - } - b = appendInt(b, y%100, 2) - case stdLongYear: - b = appendInt(b, year, 4) - case stdMonth: - b = append(b, month.String()[:3]...) - case stdLongMonth: - m := month.String() - b = append(b, m...) - case stdNumMonth: - b = appendInt(b, int(month), 0) - case stdZeroMonth: - b = appendInt(b, int(month), 2) - case stdWeekDay: - b = append(b, internalWeekday(it).String()[:3]...) - case stdLongWeekDay: - s := internalWeekday(it).String() - b = append(b, s...) - case stdDay: - b = appendInt(b, day, 0) - case stdUnderDay: - if day < 10 { - b = append(b, ' ') - } - b = appendInt(b, day, 0) - case stdZeroDay: - b = appendInt(b, day, 2) - case stdUnderYearDay: - if yday < 100 { - b = append(b, ' ') - if yday < 10 { - b = append(b, ' ') - } - } - b = appendInt(b, yday, 0) - case stdZeroYearDay: - b = appendInt(b, yday, 3) - case stdHour: - b = appendInt(b, hour, 2) - case stdHour12: - // Noon is 12PM, midnight is 12AM. - hr := hour % 12 - if hr == 0 { - hr = 12 - } - b = appendInt(b, hr, 0) - case stdZeroHour12: - // Noon is 12PM, midnight is 12AM. - hr := hour % 12 - if hr == 0 { - hr = 12 - } - b = appendInt(b, hr, 2) - case stdMinute: - b = appendInt(b, min, 0) - case stdZeroMinute: - b = appendInt(b, min, 2) - case stdSecond: - b = appendInt(b, sec, 0) - case stdZeroSecond: - b = appendInt(b, sec, 2) - case stdPM: - if hour >= 12 { - b = append(b, "PM"...) - } else { - b = append(b, "AM"...) - } - case stdpm: - if hour >= 12 { - b = append(b, "pm"...) - } else { - b = append(b, "am"...) - } - case stdISO8601TZ, stdISO8601ColonTZ, stdISO8601SecondsTZ, stdISO8601ShortTZ, stdISO8601ColonSecondsTZ, stdNumTZ, stdNumColonTZ, stdNumSecondsTz, stdNumShortTZ, stdNumColonSecondsTZ: - // Ugly special case. We cheat and take the "Z" variants - // to mean "the time zone as formatted for ISO 8601". - if std == stdISO8601TZ || std == stdISO8601ColonTZ || std == stdISO8601SecondsTZ || std == stdISO8601ShortTZ || std == stdISO8601ColonSecondsTZ { - b = append(b, 'Z') - break - } - b = append(b, '+') - b = appendInt(b, 0, 2) - if std == stdISO8601ColonTZ || std == stdNumColonTZ || std == stdISO8601ColonSecondsTZ || std == stdNumColonSecondsTZ { - b = append(b, ':') - } - if std != stdNumShortTZ && std != stdISO8601ShortTZ { - b = appendInt(b, 0, 2) - } - - // append seconds if appropriate - if std == stdISO8601SecondsTZ || std == stdNumSecondsTz || std == stdNumColonSecondsTZ || std == stdISO8601ColonSecondsTZ { - if std == stdNumColonSecondsTZ || std == stdISO8601ColonSecondsTZ { - b = append(b, ':') - } - b = appendInt(b, 0, 2) - } - - case stdTZ: - if name != "" { - b = append(b, name...) - break - } - // No time zone known for this time, but we must print one. - // Use the -0700 format. - b = append(b, '+') - b = appendInt(b, 0, 2) - b = appendInt(b, 0, 2) - case stdFracSecond0, stdFracSecond9: - b = formatNano(b, uint(t.Nanosecond()), std) - } - } - return b -} - -var errBad = errors.New("bad value for field") // placeholder not passed to user - -// ParseError describes a problem parsing a time string. -type ParseError struct { - Layout string - Value string - LayoutElem string - ValueElem string - Message string -} - -// These are borrowed from unicode/utf8 and strconv and replicate behavior in -// that package, since we can't take a dependency on either. -const ( - lowerhex = "0123456789abcdef" - runeSelf = 0x80 - runeError = '\uFFFD' -) - -func quote(s string) string { - buf := make([]byte, 1, len(s)+2) // slice will be at least len(s) + quotes - buf[0] = '"' - for i, c := range s { - if c >= runeSelf || c < ' ' { - // This means you are asking us to parse a time.Duration or - // time.Location with unprintable or non-ASCII characters in it. - // We don't expect to hit this case very often. We could try to - // reproduce strconv.Quote's behavior with full fidelity but - // given how rarely we expect to hit these edge cases, speed and - // conciseness are better. - var width int - if c == runeError { - width = 1 - if i+2 < len(s) && s[i:i+3] == string(runeError) { - width = 3 - } - } else { - width = len(string(c)) - } - for j := 0; j < width; j++ { - buf = append(buf, `\x`...) - buf = append(buf, lowerhex[s[i+j]>>4]) - buf = append(buf, lowerhex[s[i+j]&0xF]) - } - } else { - if c == '"' || c == '\\' { - buf = append(buf, '\\') - } - buf = append(buf, string(c)...) - } - } - buf = append(buf, '"') - return string(buf) -} - -// Error returns the string representation of a ParseError. -func (e *ParseError) Error() string { - if e.Message == "" { - return "parsing time " + - quote(e.Value) + " as " + - quote(e.Layout) + ": cannot parse " + - quote(e.ValueElem) + " as " + - quote(e.LayoutElem) - } - return "parsing time " + - quote(e.Value) + e.Message -} - -// isDigit reports whether s[i] is in range and is a decimal digit. -func isDigit(s string, i int) bool { - if len(s) <= i { - return false - } - c := s[i] - return '0' <= c && c <= '9' -} - -// getnum parses s[0:1] or s[0:2] (fixed forces s[0:2]) -// as a decimal integer and returns the integer and the -// remainder of the string. -func getnum(s string, fixed bool) (int, string, error) { - if !isDigit(s, 0) { - return 0, s, errBad - } - if !isDigit(s, 1) { - if fixed { - return 0, s, errBad - } - return int(s[0] - '0'), s[1:], nil - } - return int(s[0]-'0')*10 + int(s[1]-'0'), s[2:], nil -} - -// getnum3 parses s[0:1], s[0:2], or s[0:3] (fixed forces s[0:3]) -// as a decimal integer and returns the integer and the remainder -// of the string. -func getnum3(s string, fixed bool) (int, string, error) { - var n, i int - for i = 0; i < 3 && isDigit(s, i); i++ { - n = n*10 + int(s[i]-'0') - } - if i == 0 || fixed && i != 3 { - return 0, s, errBad - } - return n, s[i:], nil -} - -func cutspace(s string) string { - for len(s) > 0 && s[0] == ' ' { - s = s[1:] - } - return s -} - -// skip removes the given prefix from value, -// treating runs of space characters as equivalent. -func skip(value, prefix string) (string, error) { - for len(prefix) > 0 { - if prefix[0] == ' ' { - if len(value) > 0 && value[0] != ' ' { - return value, errBad - } - prefix = cutspace(prefix) - value = cutspace(value) - continue - } - if len(value) == 0 || value[0] != prefix[0] { - return value, errBad - } - prefix = prefix[1:] - value = value[1:] - } - return value, nil -} - -// Parse parses a formatted string and returns the time value it represents. -// See the documentation for the constant called Layout to see how to -// represent the format. The second argument must be parseable using -// the format string (layout) provided as the first argument. -// -// The example for Time.Format demonstrates the working of the layout string -// in detail and is a good reference. -// -// When parsing (only), the input may contain a fractional second -// field immediately after the seconds field, even if the layout does not -// signify its presence. In that case either a comma or a decimal point -// followed by a maximal series of digits is parsed as a fractional second. -// Fractional seconds are truncated to nanosecond precision. -// -// Elements omitted from the layout are assumed to be zero or, when -// zero is impossible, one, so parsing "3:04pm" returns the time -// corresponding to Jan 1, year 0, 15:04:00 UTC (note that because the year is -// 0, this time is before the zero Time). -// Years must be in the range 0000..9999. The day of the week is checked -// for syntax but it is otherwise ignored. -// -// For layouts specifying the two-digit year 06, a value NN >= 69 will be treated -// as 19NN and a value NN < 69 will be treated as 20NN. -// -// The remainder of this comment describes the handling of time zones. -// -// In the absence of a time zone indicator, Parse returns a time in UTC. -// -// When parsing a time with a zone offset like -0700, if the offset corresponds -// to a time zone used by the current location (Local), then Parse uses that -// location and zone in the returned time. Otherwise it records the time as -// being in a fabricated location with time fixed at the given zone offset. -// -// When parsing a time with a zone abbreviation like MST, if the zone abbreviation -// has a defined offset in the current location, then that offset is used. -// The zone abbreviation "UTC" is recognized as UTC regardless of location. -// If the zone abbreviation is unknown, Parse records the time as being -// in a fabricated location with the given zone abbreviation and a zero offset. -// This choice means that such a time can be parsed and reformatted with the -// same layout losslessly, but the exact instant used in the representation will -// differ by the actual zone offset. To avoid such problems, prefer time layouts -// that use a numeric zone offset, or use ParseInLocation. -func Parse(layout, value string) (Time, error) { - return parse(layout, value, UTC, Local) -} - -// ParseInLocation is like Parse but differs in two important ways. -// First, in the absence of time zone information, Parse interprets a time as UTC; -// ParseInLocation interprets the time as in the given location. -// Second, when given a zone offset or abbreviation, Parse tries to match it -// against the Local location; ParseInLocation uses the given location. -func ParseInLocation(layout, value string, loc *Location) (Time, error) { - return parse(layout, value, loc, loc) -} - -func parse(layout, value string, defaultLocation, local *Location) (Time, error) { - alayout, avalue := layout, value - rangeErrString := "" // set if a value is out of range - amSet := false // do we need to subtract 12 from the hour for midnight? - pmSet := false // do we need to add 12 to the hour? - - // Time being constructed. - var ( - year int - month int = -1 - day int = -1 - yday int = -1 - hour int - min int - sec int - nsec int - z *Location - zoneOffset int = -1 - zoneName string - ) - - // Each iteration processes one std value. - for { - var err error - prefix, std, suffix := nextStdChunk(layout) - stdstr := layout[len(prefix) : len(layout)-len(suffix)] - value, err = skip(value, prefix) - if err != nil { - return Time{}, &ParseError{alayout, avalue, prefix, value, ""} - } - if std == 0 { - if len(value) != 0 { - return Time{}, &ParseError{alayout, avalue, "", value, ": extra text: " + quote(value)} - } - break - } - layout = suffix - var p string - switch std & stdMask { - case stdYear: - if len(value) < 2 { - err = errBad - break - } - hold := value - p, value = value[0:2], value[2:] - year, err = atoi(p) - if err != nil { - value = hold - } else if year >= 69 { // Unix time starts Dec 31 1969 in some time zones - year += 1900 - } else { - year += 2000 - } - case stdLongYear: - if len(value) < 4 || !isDigit(value, 0) { - err = errBad - break - } - p, value = value[0:4], value[4:] - year, err = atoi(p) - case stdMonth: - month, value, err = lookup(shortMonthNames, value) - month++ - case stdLongMonth: - month, value, err = lookup(longMonthNames, value) - month++ - case stdNumMonth, stdZeroMonth: - month, value, err = getnum(value, std == stdZeroMonth) - if err == nil && (month <= 0 || 12 < month) { - rangeErrString = "month" - } - case stdWeekDay: - // Ignore weekday except for error checking. - _, value, err = lookup(shortDayNames, value) - case stdLongWeekDay: - _, value, err = lookup(longDayNames, value) - case stdDay, stdUnderDay, stdZeroDay: - if std == stdUnderDay && len(value) > 0 && value[0] == ' ' { - value = value[1:] - } - day, value, err = getnum(value, std == stdZeroDay) - // Note that we allow any one- or two-digit day here. - // The month, day, year combination is validated after we've completed parsing. - case stdUnderYearDay, stdZeroYearDay: - for i := 0; i < 2; i++ { - if std == stdUnderYearDay && len(value) > 0 && value[0] == ' ' { - value = value[1:] - } - } - yday, value, err = getnum3(value, std == stdZeroYearDay) - // Note that we allow any one-, two-, or three-digit year-day here. - // The year-day, year combination is validated after we've completed parsing. - case stdHour: - hour, value, err = getnum(value, false) - if hour < 0 || 24 <= hour { - rangeErrString = "hour" - } - case stdHour12, stdZeroHour12: - hour, value, err = getnum(value, std == stdZeroHour12) - if hour < 0 || 12 < hour { - rangeErrString = "hour" - } - case stdMinute, stdZeroMinute: - min, value, err = getnum(value, std == stdZeroMinute) - if min < 0 || 60 <= min { - rangeErrString = "minute" - } - case stdSecond, stdZeroSecond: - sec, value, err = getnum(value, std == stdZeroSecond) - if sec < 0 || 60 <= sec { - rangeErrString = "second" - break - } - // Special case: do we have a fractional second but no - // fractional second in the format? - if len(value) >= 2 && commaOrPeriod(value[0]) && isDigit(value, 1) { - _, std, _ = nextStdChunk(layout) - std &= stdMask - if std == stdFracSecond0 || std == stdFracSecond9 { - // Fractional second in the layout; proceed normally - break - } - // No fractional second in the layout but we have one in the input. - n := 2 - for ; n < len(value) && isDigit(value, n); n++ { - } - nsec, rangeErrString, err = parseNanoseconds(value, n) - value = value[n:] - } - case stdPM: - if len(value) < 2 { - err = errBad - break - } - p, value = value[0:2], value[2:] - switch p { - case "PM": - pmSet = true - case "AM": - amSet = true - default: - err = errBad - } - case stdpm: - if len(value) < 2 { - err = errBad - break - } - p, value = value[0:2], value[2:] - switch p { - case "pm": - pmSet = true - case "am": - amSet = true - default: - err = errBad - } - case stdISO8601TZ, stdISO8601ColonTZ, stdISO8601SecondsTZ, stdISO8601ShortTZ, stdISO8601ColonSecondsTZ, stdNumTZ, stdNumShortTZ, stdNumColonTZ, stdNumSecondsTz, stdNumColonSecondsTZ: - if (std == stdISO8601TZ || std == stdISO8601ShortTZ || std == stdISO8601ColonTZ) && len(value) >= 1 && value[0] == 'Z' { - value = value[1:] - z = UTC - break - } - var sign, hour, min, seconds string - if std == stdISO8601ColonTZ || std == stdNumColonTZ { - if len(value) < 6 { - err = errBad - break - } - if value[3] != ':' { - err = errBad - break - } - sign, hour, min, seconds, value = value[0:1], value[1:3], value[4:6], "00", value[6:] - } else if std == stdNumShortTZ || std == stdISO8601ShortTZ { - if len(value) < 3 { - err = errBad - break - } - sign, hour, min, seconds, value = value[0:1], value[1:3], "00", "00", value[3:] - } else if std == stdISO8601ColonSecondsTZ || std == stdNumColonSecondsTZ { - if len(value) < 9 { - err = errBad - break - } - if value[3] != ':' || value[6] != ':' { - err = errBad - break - } - sign, hour, min, seconds, value = value[0:1], value[1:3], value[4:6], value[7:9], value[9:] - } else if std == stdISO8601SecondsTZ || std == stdNumSecondsTz { - if len(value) < 7 { - err = errBad - break - } - sign, hour, min, seconds, value = value[0:1], value[1:3], value[3:5], value[5:7], value[7:] - } else { - if len(value) < 5 { - err = errBad - break - } - sign, hour, min, seconds, value = value[0:1], value[1:3], value[3:5], "00", value[5:] - } - var hr, mm, ss int - hr, err = atoi(hour) - if err == nil { - mm, err = atoi(min) - } - if err == nil { - ss, err = atoi(seconds) - } - zoneOffset = (hr*60+mm)*60 + ss // offset is in seconds - switch sign[0] { - case '+': - case '-': - zoneOffset = -zoneOffset - default: - err = errBad - } - case stdTZ: - // Does it look like a time zone? - if len(value) >= 3 && value[0:3] == "UTC" { - z = UTC - value = value[3:] - break - } - n, ok := parseTimeZone(value) - if !ok { - err = errBad - break - } - zoneName, value = value[:n], value[n:] - - case stdFracSecond0: - // stdFracSecond0 requires the exact number of digits as specified in - // the layout. - ndigit := 1 + digitsLen(std) - if len(value) < ndigit { - err = errBad - break - } - nsec, rangeErrString, err = parseNanoseconds(value, ndigit) - value = value[ndigit:] - - case stdFracSecond9: - if len(value) < 2 || !commaOrPeriod(value[0]) || value[1] < '0' || '9' < value[1] { - // Fractional second omitted. - break - } - // Take any number of digits, even more than asked for, - // because it is what the stdSecond case would do. - i := 0 - for i < 9 && i+1 < len(value) && '0' <= value[i+1] && value[i+1] <= '9' { - i++ - } - nsec, rangeErrString, err = parseNanoseconds(value, 1+i) - value = value[1+i:] - } - if rangeErrString != "" { - return Time{}, &ParseError{alayout, avalue, stdstr, value, ": " + rangeErrString + " out of range"} - } - if err != nil { - return Time{}, &ParseError{alayout, avalue, stdstr, value, ""} - } - } - if pmSet && hour < 12 { - hour += 12 - } else if amSet && hour == 12 { - hour = 0 - } - - // Convert yday to day, month. - if yday >= 0 { - var d int - var m int - if isLeap(year) { - if yday == 31+29 { - m = int(February) - d = 29 - } else if yday > 31+29 { - yday-- - } - } - if yday < 1 || yday > 365 { - return Time{}, &ParseError{alayout, avalue, "", value, ": day-of-year out of range"} - } - if m == 0 { - m = (yday-1)/31 + 1 - if int(daysBefore[m]) < yday { - m++ - } - d = yday - int(daysBefore[m-1]) - } - // If month, day already seen, yday's m, d must match. - // Otherwise, set them from m, d. - if month >= 0 && month != m { - return Time{}, &ParseError{alayout, avalue, "", value, ": day-of-year does not match month"} - } - month = m - if day >= 0 && day != d { - return Time{}, &ParseError{alayout, avalue, "", value, ": day-of-year does not match day"} - } - day = d - } else { - if month < 0 { - month = int(January) - } - if day < 0 { - day = 1 - } - } - - // Validate the day of the month. - if day < 1 || day > daysIn(Month(month), year) { - return Time{}, &ParseError{alayout, avalue, "", value, ": day out of range"} - } - - if z != nil { - return Date(year, Month(month), day, hour, min, sec, nsec, z), nil - } - - if zoneOffset != -1 { - t := Date(year, Month(month), day, hour, min, sec, nsec, UTC) - t.addSec(-int64(zoneOffset)) - - // Look for local zone with the given offset. - // If that zone was in effect at the given time, use it. - name, offset, _, _, _ := local.lookup(t.unixSec()) - if offset == zoneOffset && (zoneName == "" || name == zoneName) { - t.setLoc(local) - return t, nil - } - - // Otherwise create fake zone to record offset. - t.setLoc(FixedZone(zoneName, zoneOffset)) - return t, nil - } - - if zoneName != "" { - t := Date(year, Month(month), day, hour, min, sec, nsec, UTC) - // Look for local zone with the given offset. - // If that zone was in effect at the given time, use it. - offset, ok := local.lookupName(zoneName, t.unixSec()) - if ok { - t.addSec(-int64(offset)) - t.setLoc(local) - return t, nil - } - - // Otherwise, create fake zone with unknown offset. - if len(zoneName) > 3 && zoneName[:3] == "GMT" { - offset, _ = atoi(zoneName[3:]) // Guaranteed OK by parseGMT. - offset *= 3600 - } - t.setLoc(FixedZone(zoneName, offset)) - return t, nil - } - - // Otherwise, fall back to default. - return Date(year, Month(month), day, hour, min, sec, nsec, defaultLocation), nil -} - -// parseTimeZone parses a time zone string and returns its length. Time zones -// are human-generated and unpredictable. We can't do precise error checking. -// On the other hand, for a correct parse there must be a time zone at the -// beginning of the string, so it's almost always true that there's one -// there. We look at the beginning of the string for a run of upper-case letters. -// If there are more than 5, it's an error. -// If there are 4 or 5 and the last is a T, it's a time zone. -// If there are 3, it's a time zone. -// Otherwise, other than special cases, it's not a time zone. -// GMT is special because it can have an hour offset. -func parseTimeZone(value string) (length int, ok bool) { - if len(value) < 3 { - return 0, false - } - // Special case 1: ChST and MeST are the only zones with a lower-case letter. - if len(value) >= 4 && (value[:4] == "ChST" || value[:4] == "MeST") { - return 4, true - } - // Special case 2: GMT may have an hour offset; treat it specially. - if value[:3] == "GMT" { - length = parseGMT(value) - return length, true - } - // Special Case 3: Some time zones are not named, but have +/-00 format - if value[0] == '+' || value[0] == '-' { - length = parseSignedOffset(value) - ok := length > 0 // parseSignedOffset returns 0 in case of bad input - return length, ok - } - // How many upper-case letters are there? Need at least three, at most five. - var nUpper int - for nUpper = 0; nUpper < 6; nUpper++ { - if nUpper >= len(value) { - break - } - if c := value[nUpper]; c < 'A' || 'Z' < c { - break - } - } - switch nUpper { - case 0, 1, 2, 6: - return 0, false - case 5: // Must end in T to match. - if value[4] == 'T' { - return 5, true - } - case 4: - // Must end in T, except one special case. - if value[3] == 'T' || value[:4] == "WITA" { - return 4, true - } - case 3: - return 3, true - } - return 0, false -} - -// parseGMT parses a GMT time zone. The input string is known to start "GMT". -// The function checks whether that is followed by a sign and a number in the -// range -23 through +23 excluding zero. -func parseGMT(value string) int { - value = value[3:] - if len(value) == 0 { - return 3 - } - - return 3 + parseSignedOffset(value) -} - -// parseSignedOffset parses a signed timezone offset (e.g. "+03" or "-04"). -// The function checks for a signed number in the range -23 through +23 excluding zero. -// Returns length of the found offset string or 0 otherwise -func parseSignedOffset(value string) int { - sign := value[0] - if sign != '-' && sign != '+' { - return 0 - } - x, rem, err := leadingInt(value[1:]) - - // fail if nothing consumed by leadingInt - if err != nil || value[1:] == rem { - return 0 - } - if x > 23 { - return 0 - } - return len(value) - len(rem) -} - -func commaOrPeriod(b byte) bool { - return b == '.' || b == ',' -} - -func parseNanoseconds(value string, nbytes int) (ns int, rangeErrString string, err error) { - if !commaOrPeriod(value[0]) { - err = errBad - return - } - if nbytes > 10 { - value = value[:10] - nbytes = 10 - } - if ns, err = atoi(value[1:nbytes]); err != nil { - return - } - if ns < 0 { - rangeErrString = "fractional second" - return - } - // We need nanoseconds, which means scaling by the number - // of missing digits in the format, maximum length 10. - scaleDigits := 10 - nbytes - for i := 0; i < scaleDigits; i++ { - ns *= 10 - } - return -} - -var errLeadingInt = errors.New("time: bad [0-9]*") // never printed - -// leadingInt consumes the leading [0-9]* from s. -func leadingInt(s string) (x uint64, rem string, err error) { - i := 0 - for ; i < len(s); i++ { - c := s[i] - if c < '0' || c > '9' { - break - } - if x > 1<<63/10 { - // overflow - return 0, "", errLeadingInt - } - x = x*10 + uint64(c) - '0' - if x > 1<<63 { - // overflow - return 0, "", errLeadingInt - } - } - return x, s[i:], nil -} - -// leadingFraction consumes the leading [0-9]* from s. -// It is used only for fractions, so does not return an error on overflow, -// it just stops accumulating precision. -func leadingFraction(s string) (x uint64, scale float64, rem string) { - i := 0 - scale = 1 - overflow := false - for ; i < len(s); i++ { - c := s[i] - if c < '0' || c > '9' { - break - } - if overflow { - continue - } - if x > (1<<63-1)/10 { - // It's possible for overflow to give a positive number, so take care. - overflow = true - continue - } - y := x*10 + uint64(c) - '0' - if y > 1<<63 { - overflow = true - continue - } - x = y - scale *= 10 - } - return x, scale, s[i:] -} - -var unitMap = map[string]uint64{ - "ns": uint64(Nanosecond), - "us": uint64(Microsecond), - "µs": uint64(Microsecond), // U+00B5 = micro symbol - "μs": uint64(Microsecond), // U+03BC = Greek letter mu - "ms": uint64(Millisecond), - "s": uint64(Second), - "m": uint64(Minute), - "h": uint64(Hour), -} - -// ParseDuration parses a duration string. -// A duration string is a possibly signed sequence of -// decimal numbers, each with optional fraction and a unit suffix, -// such as "300ms", "-1.5h" or "2h45m". -// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". -func ParseDuration(s string) (Duration, error) { - // [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+ - orig := s - var d uint64 - neg := false - - // Consume [-+]? - if s != "" { - c := s[0] - if c == '-' || c == '+' { - neg = c == '-' - s = s[1:] - } - } - // Special case: if all that is left is "0", this is zero. - if s == "0" { - return 0, nil - } - if s == "" { - return 0, errors.New("time: invalid duration " + quote(orig)) - } - for s != "" { - var ( - v, f uint64 // integers before, after decimal point - scale float64 = 1 // value = v + f/scale - ) - - var err error - - // The next character must be [0-9.] - if !(s[0] == '.' || '0' <= s[0] && s[0] <= '9') { - return 0, errors.New("time: invalid duration " + quote(orig)) - } - // Consume [0-9]* - pl := len(s) - v, s, err = leadingInt(s) - if err != nil { - return 0, errors.New("time: invalid duration " + quote(orig)) - } - pre := pl != len(s) // whether we consumed anything before a period - - // Consume (\.[0-9]*)? - post := false - if s != "" && s[0] == '.' { - s = s[1:] - pl := len(s) - f, scale, s = leadingFraction(s) - post = pl != len(s) - } - if !pre && !post { - // no digits (e.g. ".s" or "-.s") - return 0, errors.New("time: invalid duration " + quote(orig)) - } - - // Consume unit. - i := 0 - for ; i < len(s); i++ { - c := s[i] - if c == '.' || '0' <= c && c <= '9' { - break - } - } - if i == 0 { - return 0, errors.New("time: missing unit in duration " + quote(orig)) - } - u := s[:i] - s = s[i:] - unit, ok := unitMap[u] - if !ok { - return 0, errors.New("time: unknown unit " + quote(u) + " in duration " + quote(orig)) - } - if v > 1<<63/unit { - // overflow - return 0, errors.New("time: invalid duration " + quote(orig)) - } - v *= unit - if f > 0 { - // float64 is needed to be nanosecond accurate for fractions of hours. - // v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit) - v += uint64(float64(f) * (float64(unit) / scale)) - if v > 1<<63 { - // overflow - return 0, errors.New("time: invalid duration " + quote(orig)) - } - } - d += v - if d > 1<<63 { - return 0, errors.New("time: invalid duration " + quote(orig)) - } - } - if neg { - return -Duration(d), nil - } - if d > 1<<63-1 { - return 0, errors.New("time: invalid duration " + quote(orig)) - } - return Duration(d), nil -} diff --git a/gnovm/stdlibs/time/time.gno b/gnovm/stdlibs/time/time.gno index 0498418ef1d..620069b211c 100644 --- a/gnovm/stdlibs/time/time.gno +++ b/gnovm/stdlibs/time/time.gno @@ -1,75 +1,54 @@ -// Package time provides functionality for measuring and displaying time. -// -// The calendrical calculations always assume a Gregorian calendar, with -// no leap seconds. - package time -import ( - "errors" -) - -type Time int64 +import "errors" const ( - hasMonotonic = 1 << 63 - nsecMask = 1<<30 - 1 - nsecShift = 30 -) + minWall = wallToInternal // year 1885 -// These helpers for manipulating the wall and monotonic clock readings -// take pointer receivers, even when they don't modify the time, -// to make them cheaper to call. + // The year of the zero Time. + // Assumed by the unixToInternal computation below. + internalYear = 1 -// nsec returns the nanoseconds component of the time. -func (t Time) nsec() int32 { - return int32(t % 1e9) -} + // Offsets to convert between internal and absolute or Unix times. + absoluteToInternal int64 = (absoluteZeroYear - internalYear) * 365.2425 * secondsPerDay + internalToAbsolute = -absoluteToInternal + unixToInternal int64 = (1969*365 + 1969/4 - 1969/100 + 1969/400) * secondsPerDay + internalToUnix int64 = -unixToInternal + wallToInternal int64 = (1884*365 + 1884/4 - 1884/100 + 1884/400) * secondsPerDay -// sec returns the time's seconds since the Unix epoch (Jan 1, 1970). -func (t Time) sec() int64 { - return int64(t / 1e9) -} + // The unsigned zero year for internal calculations. + // Must be 1 mod 400, and times before it will not compute correctly, + // but otherwise can be changed at will. + absoluteZeroYear = -292277022399 -// unixSec returns the time's seconds since Jan 1 1970 (Unix time). -func (t Time) unixSec() int64 { return t.sec() } - -// addSec adds d seconds to the time. -func (t *Time) addSec(d int64) { - // Convert seconds to nanoseconds - delta := d * 1e9 - - // Check if the sum overflows or underflows - if (*t > 0 && delta > 0 && int64(*t) > int64(1<<63-1)-delta) || - (*t < 0 && delta < 0 && int64(*t) < int64(-1<<63)-delta) { - // Handle overflow or underflow cases - if delta > 0 { - *t = Time(int64(1<<63 - 1)) // Max int64 value - } else { - *t = Time(int64(-1 << 63)) // Min int64 value - } - } else { - // Safe to add delta to t - *t += Time(delta) - } + secondsPerMinute = 60 + secondsPerHour = 60 * secondsPerMinute + secondsPerDay = 24 * secondsPerHour + secondsPerWeek = 7 * secondsPerDay + daysPer400Years = 365*400 + 97 + daysPer100Years = 365*100 + 24 + daysPer4Years = 365*4 + 1 +) + +type Time struct { + sec int64 + nsec int32 } -// After reports whether the time instant t is after u. +func (t *Time) unixSec() int64 { return t.sec + internalToUnix } + func (t Time) After(u Time) bool { - return t > u + return t.sec > u.sec || t.sec == u.sec && t.nsec > u.nsec } -// Before reports whether the time instant t is before u. func (t Time) Before(u Time) bool { - return t < u + return t.sec < u.sec || t.sec == u.sec && t.nsec < u.nsec } -// Equal reports whether the time instant t is equal to u. func (t Time) Equal(u Time) bool { - return t == u + return t.sec == u.sec && t.nsec == u.nsec } -// A Month specifies a month of the year (January = 1, ...). type Month int const ( @@ -87,7 +66,6 @@ const ( December ) -// String returns the English name of the month ("January", "February", ...). func (m Month) String() string { if January <= m && m <= December { return longMonthNames[m-1] @@ -97,7 +75,6 @@ func (m Month) String() string { return "%!Month(" + string(buf[n:]) + ")" } -// A Weekday specifies a day of the week (Sunday = 0, ...). type Weekday int const ( @@ -110,7 +87,6 @@ const ( Saturday ) -// String returns the English name of the day ("Sunday", "Monday", ...). func (d Weekday) String() string { if Sunday <= d && d <= Saturday { return longDayNames[d] @@ -120,10 +96,8 @@ func (d Weekday) String() string { return "%!Weekday(" + string(buf[n:]) + ")" } -// IsZero reports whether t represents the zero time instant, -// January 1, year 1, 00:00:00 UTC. func (t Time) IsZero() bool { - return t == 0 + return t.sec == 0 && t.nsec == 0 } // Date returns the year, month, and day in which t occurs. @@ -140,7 +114,7 @@ func (t Time) Year() int { // Month returns the month of the year specified by t. func (t Time) Month() Month { - _, month, _, _ := t.date(true) + _, month, _, _ := t.date(false) return month } @@ -150,13 +124,15 @@ func (t Time) Day() int { return day } -// Weekday returns the day of the week specified by t. func (t Time) Weekday() Weekday { - daysSinceEpoch := t.sec() / secondsPerDay - return Weekday((daysSinceEpoch + int64(Thursday)) % 7) // January 1, 1970 was a Thursday + return internalWeekday(t.internal()) +} + +func internalWeekday(it uint64) Weekday { + sec := (it + uint64(Monday)*secondsPerDay) % secondsPerWeek + return Weekday(sec / secondsPerDay) } -// TODO: TEST // ISOWeek returns the ISO 8601 year and week number in which t occurs. // Week ranges from 1 to 53. Jan 01 to Jan 03 of year n might belong to // week 52 or 53 of year n-1, and Dec 29 to Dec 31 might belong to week 1 @@ -172,25 +148,26 @@ func (t Time) ISOWeek() (year, week int) { // 1 2 3 4 5 6 7 // +3 +2 +1 0 -1 -2 -3 // the offset to Thursday - // Get the current day of the week - d := Thursday - t.Weekday() - - // Handle Sunday (ISOWeek counts Monday as the first day of the week) + it := t.internal() + d := Thursday - internalWeekday(it) + // handle Sunday if d == 4 { - d = -3 + d -= 3 } - - t += Time(d * secondsPerDay * 1e9) // Convert days to nanoseconds and adjust - - year, _, _, yday := t.date(false) - week = yday/7 + 1 - - return year, week + // find the Thursday of the calendar week + it += uint64(d) * secondsPerDay + year, _, _, yday := internalDate(it, false) + return year, yday/7 + 1 } // Clock returns the hour, minute, and second within the day specified by t. func (t Time) Clock() (hour, min, sec int) { - sec = int(t % secondsPerDay) + return internalClock(t.internal()) +} + +// internalClock is like clock but operates on an internal time. +func internalClock(it uint64) (hour, min, sec int) { + sec = int(it % secondsPerDay) hour = sec / secondsPerHour sec -= hour * secondsPerHour min = sec / secondsPerMinute @@ -200,23 +177,23 @@ func (t Time) Clock() (hour, min, sec int) { // Hour returns the hour within the day specified by t, in the range [0, 23]. func (t Time) Hour() int { - return int(t%secondsPerDay) / secondsPerHour + return int(t.internal()%secondsPerDay) / secondsPerHour } // Minute returns the minute offset within the hour specified by t, in the range [0, 59]. func (t Time) Minute() int { - return int(t%secondsPerHour) / secondsPerMinute + return int(t.internal()%secondsPerHour) / secondsPerMinute } // Second returns the second offset within the minute specified by t, in the range [0, 59]. func (t Time) Second() int { - return int(t % secondsPerMinute) + return int(t.internal() % secondsPerMinute) } // Nanosecond returns the nanosecond offset within the second specified by t, // in the range [0, 999999999]. func (t Time) Nanosecond() int { - return int(t.nsec()) + return int(t.nsec) } // YearDay returns the day of the year specified by t, in the range [1,365] for non-leap years, @@ -226,28 +203,149 @@ func (t Time) YearDay() int { return yday + 1 } -// A Duration represents the elapsed time between two instants -// as an int64 nanosecond count. The representation limits the -// largest representable duration to approximately 290 years. -type Duration int64 +// AddDate returns the time corresponding to adding the +// given number of years, months, and days to t. +// For example, AddDate(-1, 2, 3) applied to January 1, 2011 +// returns March 4, 2010. +// +// AddDate normalizes its result in the same way that Date does, +// so, for example, adding one month to October 31 yields +// December 1, the normalized form for November 31. +func (t Time) AddDate(years int, months int, days int) Time { + year, month, day := t.Date() + hour, min, sec := t.Clock() + return Date(year+years, month+Month(months), day+days, hour, min, sec, int(t.nsec)) +} -const ( - minDuration Duration = -1 << 63 - maxDuration Duration = 1<<63 - 1 -) +// rename of the "abs" method in the original time.go +// since we removed the location & zoneinfo related code +func (t Time) internal() uint64 { + sec := t.unixSec() + return uint64(sec + (unixToInternal + internalToAbsolute)) +} + +func (t Time) date(full bool) (year int, month Month, day int, yday int) { + return internalDate(t.internal(), full) +} + +func internalDate(it uint64, full bool) (year int, month Month, day int, yday int) { + d := it / 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) { + 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). +var daysBefore = [...]int32{ + 0, + 31, + 31 + 28, + 31 + 28 + 31, + 31 + 28 + 31 + 30, + 31 + 28 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, +} + +// daysSinceEpoch takes a year and returns the number of days from +// the absolute epoch to the start of that year. +// This is basically (year - zeroYear) * 365, but accounting for leap days. +func daysSinceEpoch(year int) uint64 { + y := uint64(int64(year) - absoluteZeroYear) + + // Add in days from 400-year cycles. + n := y / 400 + y -= 400 * n + d := daysPer400Years * n + + // Add in 100-year cycles. + n = y / 100 + y -= 100 * n + d += daysPer100Years * n + + // Add in 4-year cycles. + n = y / 4 + y -= 4 * n + d += daysPer4Years * n + + // Add in non-leap years. + n = y + d += 365 * n + + return d +} -// Common durations. There is no definition for units of Day or larger -// to avoid confusion across daylight savings time zone transitions. -// -// To count the number of units in a Duration, divide: -// -// second := time.Second -// fmt.Print(int64(second/time.Millisecond)) // prints 1000 -// -// To convert an integer number of units to a Duration, multiply: -// -// seconds := 10 -// fmt.Print(time.Duration(seconds)*time.Second) // prints 10s const ( Nanosecond Duration = 1 Microsecond = 1000 * Nanosecond @@ -257,6 +355,16 @@ const ( Hour = 60 * Minute ) +// A Duration represents the elapsed time between two instants +// as an int64 nanosecond count. The representation limits the +// largest representable duration to approximately 290 years. +type Duration int64 + +const ( + minDuration Duration = -1 << 63 + maxDuration Duration = 1<<63 - 1 +) + // String returns a string representing the duration in the form "72h3m0.5s". // Leading zero units are omitted. As a special case, durations less than one // second format use a smaller unit (milli-, micro-, or nanoseconds) to ensure @@ -334,47 +442,6 @@ func (d Duration) String() string { return string(buf[w:]) } -// fmtFrac formats the fraction of v/10**prec (e.g., ".12345") into the -// tail of buf, omitting trailing zeros. It omits the decimal -// point too when the fraction is 0. It returns the index where the -// output bytes begin and the value v/10**prec. -func fmtFrac(buf []byte, v uint64, prec int) (nw int, nv uint64) { - // Omit trailing zeros up to and including decimal point. - w := len(buf) - isprint := false - for i := 0; i < prec; i++ { - digit := v % 10 - isprint = isprint || digit != 0 - if isprint { - w-- - buf[w] = byte(digit) + '0' - } - v /= 10 - } - if isprint { - w-- - buf[w] = '.' - } - return w, v -} - -// fmtInt formats v into the tail of buf. -// It returns the index where the output begins. -func fmtInt(buf []byte, v uint64) int { - w := len(buf) - if v == 0 { - w-- - buf[w] = '0' - } else { - for v > 0 { - w-- - buf[w] = byte(v%10) + '0' - v /= 10 - } - } - return w -} - // Nanoseconds returns the duration as an integer nanosecond count. func (d Duration) Nanoseconds() int64 { return int64(d) } @@ -459,38 +526,18 @@ func (d Duration) Round(m Duration) Duration { return maxDuration // overflow } -// Abs returns the absolute value of d. -// As a special case, math.MinInt64 is converted to math.MaxInt64. -func (d Duration) Abs() Duration { - switch { - case d >= 0: - return d - case d == minDuration: - return maxDuration - default: - return -d - } -} - -// Add returns the time t+d. +// Add returns the time t+d func (t Time) Add(d Duration) Time { dsec := int64(d / 1e9) - dnsec := int64(d % 1e9) - tsec := t.sec() - tnsec := t.nsec() - - nsec := int64(tnsec) + dnsec - sec := tsec + dsec - + nsec := t.nsec + int32(d%1e9) if nsec >= 1e9 { - sec++ + dsec++ nsec -= 1e9 } else if nsec < 0 { - sec-- + dsec-- nsec += 1e9 } - - return Time(sec*1e9 + nsec) + return Time{t.sec + dsec, nsec} } // Sub returns the duration t-u. If the result exceeds the maximum (or minimum) @@ -498,16 +545,22 @@ func (t Time) Add(d Duration) Time { // will be returned. // To compute t-d for a duration d, use t.Add(-d). func (t Time) Sub(u Time) Duration { - diff := int64(t) - int64(u) - - if diff > int64(maxDuration) { + sec := t.sec - u.sec + nsec := t.nsec - u.nsec + if sec > 0 && nsec < 0 { + sec-- + nsec += 1e9 + } else if sec < 0 && nsec > 0 { + sec++ + nsec -= 1e9 + } + if sec > int64(maxDuration) { return maxDuration } - if diff < int64(minDuration) { + if sec < int64(minDuration) { return minDuration } - - return Duration(diff) + return Duration(sec*1e9 + int64(nsec)) } // Since returns the time elapsed since t. @@ -522,163 +575,65 @@ func Until(t Time) Duration { return t.Sub(Now()) } -// AddDate returns the time corresponding to adding the -// given number of years, months, and days to t. -// For example, AddDate(-1, 2, 3) applied to January 1, 2011 -// returns March 4, 2010. +// Date returns the Time corresponding to // -// AddDate normalizes its result in the same way that Date does, -// so, for example, adding one month to October 31 yields -// December 1, the normalized form for November 31. -func (t Time) AddDate(years int, months int, days int) Time { - year, month, day := t.Date() - hour, min, sec := t.Clock() - return Date(year+years, month+Month(months), day+days, hour, min, sec, int(t.nsec())) -} - -const ( - secondsPerMinute = 60 - secondsPerHour = 60 * secondsPerMinute - secondsPerDay = 24 * secondsPerHour - secondsPerWeek = 7 * secondsPerDay - daysPer400Years = 365*400 + 97 - daysPer100Years = 365*100 + 24 - daysPer4Years = 365*4 + 1 -) - -// date computes the year, day of year, and when full=true, -// the month and day in which t occurs. -func (t Time) date(full bool) (year int, month Month, day int, yday int) { - d := t / 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 +// yyyy-mm-dd hh:mm:ss + nsec nanoseconds +// +// The month, day, hour, min, sec, and nsec values may be outside +// their usual ranges and will be normalized during the conversion. +// For example, October 32 converts to November 1. +func Date(year int, month Month, day, hour, min, sec, nsec int) Time { + // Normalize month, overflowing into year. + m := int(month) - 1 + year, m = norm(year, m, 12) + month = Month(m) + 1 - year = int(int64(y) + 1970) - yday = int(d) + // Normalize nsec, sec, min, hour, overflowing into day. + sec, nsec = norm(sec, nsec, 1e9) + min, sec = norm(min, sec, 60) + hour, min = norm(hour, min, 60) + day, hour = norm(day, hour, 24) - if !full { - return - } + // Compute days since the absolute epoch. + d := daysSinceEpoch(year) - 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 - } + // Add in days before this month. + d += uint64(daysBefore[month-1]) + if isLeap(year) && month >= March { + d++ // February 29 } - // 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]) - } + // Add in days before today. + d += uint64(day - 1) - month++ // because January is 1 - day = day - begin + 1 - return -} + // Add in time elapsed today. + abs := d * secondsPerDay + abs += uint64(hour*secondsPerHour + min*secondsPerMinute + sec) -// 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). -var daysBefore = [...]int32{ - 0, - 31, - 31 + 28, - 31 + 28 + 31, - 31 + 28 + 31 + 30, - 31 + 28 + 31 + 30 + 31, - 31 + 28 + 31 + 30 + 31 + 30, - 31 + 28 + 31 + 30 + 31 + 30 + 31, - 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, - 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, - 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, - 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, - 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, + unix := int64(abs) + (absoluteToInternal + internalToUnix) + t := unixTime(unix, int32(nsec)) + return t } -func daysIn(m Month, year int) int { - if m == February && isLeap(year) { - return 29 +// Abs returns the absolute value of d. +// As a special case, math.MinInt64 is converted to math.MaxInt64. +func (d Duration) Abs() Duration { + switch { + case d >= 0: + return d + case d == minDuration: + return maxDuration + default: + return -d } - return int(daysBefore[m] - daysBefore[m-1]) -} - -// daysSinceEpoch takes a year and returns the number of days from -// the Unix epoch (January 1, 1970) to the start of that year, -// accounting for leap years. -func daysSinceEpoch(year int) int64 { - y := year - 1970 // Calculate how many years from 1970 (Unix epoch) - - // Add days for 400-year cycles. - n := y / 400 - y -= 400 * n - d := int64(daysPer400Years * n) - - // Add days for 100-year cycles. - n = y / 100 - y -= 100 * n - d += int64(daysPer100Years * n) - - // Add days for 4-year cycles. - n = y / 4 - y -= 4 * n - d += int64(daysPer4Years * n) - - // Add days for the remaining years. - d += int64(365 * y) - - return d } -func now() (sec int64, nsec int32, mono int64) // injected +func now() (sec int64, nsec int32, mono int64) // injected by runtime -// Now returns the current local time. func Now() Time { sec, nsec, _ := now() - return Time(sec*1e9 + int64(nsec)) + sec += unixToInternal - minWall + return Time{sec, nsec} } // Unix returns t as a Unix time, the number of seconds elapsed @@ -697,7 +652,7 @@ func (t Time) Unix() int64 { // years before or after 1970). The result does not depend on the // location associated with t. func (t Time) UnixMilli() int64 { - return t.unixSec()*1e3 + int64(t.nsec())/1e6 + return t.unixSec()*1e3 + int64(t.nsec)/1e6 } // UnixMicro returns t as a Unix time, the number of microseconds elapsed since @@ -706,7 +661,7 @@ func (t Time) UnixMilli() int64 { // after year 294246). The result does not depend on the location associated // with t. func (t Time) UnixMicro() int64 { - return t.unixSec()*1e6 + int64(t.nsec())/1e3 + return t.unixSec()*1e6 + int64(t.nsec)/1e3 } // UnixNano returns t as a Unix time, the number of nanoseconds elapsed @@ -716,23 +671,16 @@ func (t Time) UnixMicro() int64 { // on the zero Time is undefined. The result does not depend on the // location associated with t. func (t Time) UnixNano() int64 { - return (t.unixSec())*1e9 + int64(t.nsec()) + return (t.unixSec())*1e9 + int64(t.nsec) } -const ( - timeBinaryVersionV1 byte = iota + 1 // For general situation - timeBinaryVersionV2 // For LMT only -) - // MarshalBinary implements the encoding.BinaryMarshaler interface. func (t Time) MarshalBinary() ([]byte, error) { - // Get seconds and nanoseconds - sec := t.sec() - nsec := t.nsec() - - // Encode seconds and nanoseconds into a byte slice + sec := t.sec + nsec := t.nsec enc := []byte{ - byte(sec >> 56), // bytes 0-7: seconds + //encode seconds (int64) / bytes 0 to 7 + byte(sec >> 56), byte(sec >> 48), byte(sec >> 40), byte(sec >> 32), @@ -740,48 +688,37 @@ func (t Time) MarshalBinary() ([]byte, error) { byte(sec >> 16), byte(sec >> 8), byte(sec), - byte(nsec >> 24), // bytes 8-11: nanoseconds + //encode nanoseconds (int32) / bytes 8 to 11 + byte(nsec >> 24), byte(nsec >> 16), byte(nsec >> 8), byte(nsec), } - return enc, nil } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. func (t *Time) UnmarshalBinary(data []byte) error { - if len(data) != 12 { + buf := data + if len(buf) == 0 { + return errors.New("Time.UnmarshalBinary: no data") + } + if len(buf) != 12 { // 8 bytes for sec (int64) + 4 bytes for nsec (int32) return errors.New("Time.UnmarshalBinary: invalid length") } + sec := int64(buf[7]) | int64(buf[6])<<8 | int64(buf[5])<<16 | int64(buf[4])<<24 | + int64(buf[3])<<32 | int64(buf[2])<<40 | int64(buf[1])<<48 | int64(buf[0])<<56 - // Decode seconds (int64) from the first 8 bytes - sec := int64(data[7]) | int64(data[6])<<8 | int64(data[5])<<16 | int64(data[4])<<24 | - int64(data[3])<<32 | int64(data[2])<<40 | int64(data[1])<<48 | int64(data[0])<<56 - - // Decode nanoseconds (int32) from the next 4 bytes - nsec := int32(data[11]) | int32(data[10])<<8 | int32(data[9])<<16 | int32(data[8])<<24 + buf = buf[8:] + nsec := int32(buf[3]) | int32(buf[2])<<8 | int32(buf[1])<<16 | int32(buf[0])<<24 - // Combine seconds and nanoseconds into Time (nanoseconds since Unix epoch) - *t = Time(sec*1e9 + int64(nsec)) + *t = Time{} + t.sec = sec + t.nsec = nsec return nil } -// TODO(rsc): Remove GobEncoder, GobDecoder, MarshalJSON, UnmarshalJSON in Go 2. -// The same semantics will be provided by the generic MarshalBinary, MarshalText, -// UnmarshalBinary, UnmarshalText. - -// GobEncode implements the gob.GobEncoder interface. -func (t Time) GobEncode() ([]byte, error) { - return t.MarshalBinary() -} - -// GobDecode implements the gob.GobDecoder interface. -func (t *Time) GobDecode(data []byte) error { - return t.UnmarshalBinary(data) -} - // MarshalJSON implements the json.Marshaler interface. // The time is a quoted string in RFC 3339 format, with sub-second precision added if present. func (t Time) MarshalJSON() ([]byte, error) { @@ -846,7 +783,7 @@ func Unix(sec int64, nsec int64) Time { sec-- } } - return Time(sec*1e9 + nsec) + return unixTime(sec, int32(nsec)) } // UnixMilli returns the local Time corresponding to the given Unix time, @@ -861,10 +798,54 @@ func UnixMicro(usec int64) Time { return Unix(usec/1e6, (usec%1e6)*1e3) } +func unixTime(sec int64, nsec int32) Time { + return Time{sec + unixToInternal, nsec} +} + func isLeap(year int) bool { return year%4 == 0 && (year%100 != 0 || year%400 == 0) } +// fmtFrac formats the fraction of v/10**prec (e.g., ".12345") into the +// tail of buf, omitting trailing zeros. It omits the decimal +// point too when the fraction is 0. It returns the index where the +// output bytes begin and the value v/10**prec. +func fmtFrac(buf []byte, v uint64, prec int) (nw int, nv uint64) { + // Omit trailing zeros up to and including decimal point. + w := len(buf) + isprint := false + for i := 0; i < prec; i++ { + digit := v % 10 + isprint = isprint || digit != 0 + if isprint { + w-- + buf[w] = byte(digit) + '0' + } + v /= 10 + } + if isprint { + w-- + buf[w] = '.' + } + return w, v +} + +// add v at the end of buf and return the index where the number starts in buf +func fmtInt(buf []byte, v uint64) int { + w := len(buf) + if v == 0 { + w-- + buf[w] = '0' + } else { + for v > 0 { + w-- + buf[w] = byte(v%10) + '0' + v /= 10 + } + } + return w +} + // norm returns nhi, nlo such that // // hi * base + lo == nhi * base + nlo @@ -883,152 +864,16 @@ func norm(hi, lo, base int) (nhi, nlo int) { return hi, lo } -// Date returns the Time corresponding to -// yyyy-mm-dd hh:mm:ss + nsec nanoseconds in UTC. -// The month, day, hour, min, sec, and nsec values may be outside -// their usual ranges and will be normalized during the conversion. -func Date(year int, month Month, day, hour, min, sec, nsec int) Time { - // Normalize month, overflowing into year. - m := int(month) - 1 - year, m = norm(year, m, 12) - month = Month(m) + 1 - - // Normalize nsec, sec, min, hour, overflowing into day. - sec, nsec = norm(sec, nsec, 1e9) - min, sec = norm(min, sec, 60) - hour, min = norm(hour, min, 60) - day, hour = norm(day, hour, 24) - - // Compute days since the Unix epoch (January 1, 1970). - d := int64(daysSinceEpoch(year)) - - // Add in days before this month. - d += int64(daysBefore[month-1]) - if isLeap(year) && month > February { - d++ // Add leap day - } - - // Add in days before today. - d += int64(day - 1) - - // Calculate the total seconds for the date. - totalSeconds := d*secondsPerDay + int64(hour*secondsPerHour+min*secondsPerMinute+sec) - - // Convert total seconds to nanoseconds. - totalNanoseconds := totalSeconds*1e9 + int64(nsec) - - // Return the computed Time in nanoseconds since the Unix epoch. - return Time(totalNanoseconds) -} - -// Truncate returns the result of rounding t down to a multiple of d. -func (t Time) Truncate(d Duration) Time { - if d <= 0 { - return t - } - _, r := div(t, d) - return t.Add(-r) -} - -// Round returns the result of rounding t to the nearest multiple of d. -func (t Time) Round(d Duration) Time { - if d <= 0 { - return t - } - _, r := div(t, d) - if lessThanHalf(r, d) { - return t.Add(-r) - } - return t.Add(d - r) -} - -// div divides t by d and returns the quotient parity and remainder. -// We don't use the quotient parity anymore (round half up instead of round to even) -// but it's still here in case we change our minds. -func div(t Time, d Duration) (qmod2 int, r Duration) { - neg := false - nsec := t.nsec() - sec := t.sec() - if sec < 0 { - // Operate on absolute value. - neg = true - sec = -sec - nsec = -nsec - if nsec < 0 { - nsec += 1e9 - sec-- // sec >= 1 before the -- so safe - } - } - - switch { - // Special case: 2d divides 1 second. - case d < Second && Second%(d+d) == 0: - qmod2 = int(nsec/int32(d)) & 1 - r = Duration(nsec % int32(d)) - - // Special case: d is a multiple of 1 second. - case d%Second == 0: - d1 := int64(d / Second) - qmod2 = int(sec/d1) & 1 - r = Duration(sec%d1)*Second + Duration(nsec) - - // General case. - // This could be faster if more cleverness were applied, - // but it's really only here to avoid special case restrictions in the API. - // No one will care about these cases. - default: - // Compute nanoseconds as 128-bit number. - sec := uint64(sec) - tmp := (sec >> 32) * 1e9 - u1 := tmp >> 32 - u0 := tmp << 32 - tmp = (sec & 0xFFFFFFFF) * 1e9 - u0x, u0 := u0, u0+tmp - if u0 < u0x { - u1++ - } - u0x, u0 = u0, u0+uint64(nsec) - if u0 < u0x { - u1++ - } - - // Compute remainder by subtracting r<>63 != 1 { - d1 <<= 1 - } - d0 := uint64(0) - for { - qmod2 = 0 - if u1 > d1 || u1 == d1 && u0 >= d0 { - // subtract - qmod2 = 1 - u0x, u0 = u0, u0-d0 - if u0 > u0x { - u1-- - } - u1 -= d1 - } - if d1 == 0 && d0 == uint64(d) { - break - } - d0 >>= 1 - d0 |= (d1 & 1) << 63 - d1 >>= 1 +func daysIn(m Month, year int) int { + if m == February { + if isLeap(year) { + return 29 } - r = Duration(u0) + return 28 } - - if neg && r != 0 { - // If input was negative and not an exact multiple of d, we computed q, r such that - // q*d + r = -t - // But the right answers are given by -(q-1), d-r: - // q*d + r = -t - // -q*d - r = t - // -(q-1)*d + (d - r) = t - qmod2 ^= 1 - r = d - r - } - return + // With the special case of February eliminated, the pattern is + // 31 30 31 30 31 30 31 31 30 31 30 31 + // Adding m&1 produces the basic alternation; + // adding (m>>3)&1 inverts the alternation starting in August. + return 30 + int((m+m>>3)&1) } diff --git a/gnovm/stdlibs/time/time2.go b/gnovm/stdlibs/time/time2.go deleted file mode 100644 index ddfe3021443..00000000000 --- a/gnovm/stdlibs/time/time2.go +++ /dev/null @@ -1,865 +0,0 @@ -package time2 - -import "errors" - -const ( - minWall = wallToInternal // year 1885 - - // The year of the zero Time. - // Assumed by the unixToInternal computation below. - internalYear = 1 - - // Offsets to convert between internal and absolute or Unix times. - absoluteToInternal int64 = (absoluteZeroYear - internalYear) * 365.2425 * secondsPerDay - internalToAbsolute = -absoluteToInternal - unixToInternal int64 = (1969*365 + 1969/4 - 1969/100 + 1969/400) * secondsPerDay - internalToUnix int64 = -unixToInternal - wallToInternal int64 = (1884*365 + 1884/4 - 1884/100 + 1884/400) * secondsPerDay - - // The unsigned zero year for internal calculations. - // Must be 1 mod 400, and times before it will not compute correctly, - // but otherwise can be changed at will. - absoluteZeroYear = -292277022399 - - secondsPerMinute = 60 - secondsPerHour = 60 * secondsPerMinute - secondsPerDay = 24 * secondsPerHour - secondsPerWeek = 7 * secondsPerDay - daysPer400Years = 365*400 + 97 - daysPer100Years = 365*100 + 24 - daysPer4Years = 365*4 + 1 -) - -type Time struct { - sec int64 - nsec int32 -} - -func (t *Time) unixSec() int64 { return t.sec + internalToUnix } - -func (t Time) After(u Time) bool { - return t.sec > u.sec || t.sec == u.sec && t.nsec > u.nsec -} - -func (t Time) Before(u Time) bool { - return t.sec < u.sec || t.sec == u.sec && t.nsec < u.nsec -} - -func (t Time) Equal(u Time) bool { - return t.sec == u.sec && t.nsec == u.nsec -} - -type Month int - -const ( - January Month = 1 + iota - February - March - April - May - June - July - August - September - October - November - December -) - -func (m Month) String() string { - if January <= m && m <= December { - return longMonthNames[m-1] - } - buf := make([]byte, 20) - n := fmtInt(buf, uint64(m)) - return "%!Month(" + string(buf[n:]) + ")" -} - -type Weekday int - -const ( - Sunday Weekday = iota - Monday - Tuesday - Wednesday - Thursday - Friday - Saturday -) - -func (d Weekday) String() string { - if Sunday <= d && d <= Saturday { - return longDayNames[d] - } - buf := make([]byte, 20) - n := fmtInt(buf, uint64(d)) - return "%!Weekday(" + string(buf[n:]) + ")" -} - -func (t Time) IsZero() bool { - return t.sec == 0 && t.nsec == 0 -} - -// Date returns the year, month, and day in which t occurs. -func (t Time) Date() (year int, month Month, day int) { - year, month, day, _ = t.date(true) - return -} - -// Year returns the year in which t occurs. -func (t Time) Year() int { - year, _, _, _ := t.date(false) - return year -} - -// Month returns the month of the year specified by t. -func (t Time) Month() Month { - _, month, _, _ := t.date(false) - return month -} - -// Day returns the day of the month specified by t. -func (t Time) Day() int { - _, _, day, _ := t.date(true) - return day -} - -func (t Time) Weekday() Weekday { - return internalWeekday(t.internal()) -} - -func internalWeekday(it uint64) Weekday { - sec := (it + uint64(Monday)*secondsPerDay) % secondsPerWeek - return Weekday(sec / secondsPerDay) -} - -// ISOWeek returns the ISO 8601 year and week number in which t occurs. -// Week ranges from 1 to 53. Jan 01 to Jan 03 of year n might belong to -// week 52 or 53 of year n-1, and Dec 29 to Dec 31 might belong to week 1 -// of year n+1. -func (t Time) ISOWeek() (year, week int) { - // According to the rule that the first calendar week of a calendar year is - // the week including the first Thursday of that year, and that the last one is - // the week immediately preceding the first calendar week of the next calendar year. - // See https://www.iso.org/obp/ui#iso:std:iso:8601:-1:ed-1:v1:en:term:3.1.1.23 for details. - - // weeks start with Monday - // Monday Tuesday Wednesday Thursday Friday Saturday Sunday - // 1 2 3 4 5 6 7 - // +3 +2 +1 0 -1 -2 -3 - // the offset to Thursday - it := t.internal() - d := Thursday - internalWeekday(it) - // handle Sunday - if d == 4 { - d -= 3 - } - // find the Thursday of the calendar week - it += uint64(d) * secondsPerDay - year, _, _, yday := internalDate(it, false) - return year, yday/7 + 1 -} - -// Clock returns the hour, minute, and second within the day specified by t. -func (t Time) Clock() (hour, min, sec int) { - return internalClock(t.internal()) -} - -// internalClock is like clock but operates on an internal time. -func internalClock(it uint64) (hour, min, sec int) { - sec = int(it % secondsPerDay) - hour = sec / secondsPerHour - sec -= hour * secondsPerHour - min = sec / secondsPerMinute - sec -= min * secondsPerMinute - return -} - -// Hour returns the hour within the day specified by t, in the range [0, 23]. -func (t Time) Hour() int { - return int(t.internal()%secondsPerDay) / secondsPerHour -} - -// Minute returns the minute offset within the hour specified by t, in the range [0, 59]. -func (t Time) Minute() int { - return int(t.internal()%secondsPerHour) / secondsPerMinute -} - -// Second returns the second offset within the minute specified by t, in the range [0, 59]. -func (t Time) Second() int { - return int(t.internal() % secondsPerMinute) -} - -// Nanosecond returns the nanosecond offset within the second specified by t, -// in the range [0, 999999999]. -func (t Time) Nanosecond() int { - return int(t.nsec) -} - -// YearDay returns the day of the year specified by t, in the range [1,365] for non-leap years, -// and [1,366] in leap years. -func (t Time) YearDay() int { - _, _, _, yday := t.date(false) - return yday + 1 -} - -// AddDate returns the time corresponding to adding the -// given number of years, months, and days to t. -// For example, AddDate(-1, 2, 3) applied to January 1, 2011 -// returns March 4, 2010. -// -// AddDate normalizes its result in the same way that Date does, -// so, for example, adding one month to October 31 yields -// December 1, the normalized form for November 31. -func (t Time) AddDate(years int, months int, days int) Time { - year, month, day := t.Date() - hour, min, sec := t.Clock() - return Date(year+years, month+Month(months), day+days, hour, min, sec, int(t.nsec)) -} - -// rename of the "abs" method in the original time.go -// since we removed the location & zoneinfo related code -func (t Time) internal() uint64 { - sec := t.unixSec() - return uint64(sec + (unixToInternal + internalToAbsolute)) -} - -func (t Time) date(full bool) (year int, month Month, day int, yday int) { - return internalDate(t.internal(), full) -} - -func internalDate(it uint64, full bool) (year int, month Month, day int, yday int) { - d := it / 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) { - 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). -var daysBefore = [...]int32{ - 0, - 31, - 31 + 28, - 31 + 28 + 31, - 31 + 28 + 31 + 30, - 31 + 28 + 31 + 30 + 31, - 31 + 28 + 31 + 30 + 31 + 30, - 31 + 28 + 31 + 30 + 31 + 30 + 31, - 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, - 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, - 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, - 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, - 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, -} - -// daysSinceEpoch takes a year and returns the number of days from -// the absolute epoch to the start of that year. -// This is basically (year - zeroYear) * 365, but accounting for leap days. -func daysSinceEpoch(year int) uint64 { - y := uint64(int64(year) - absoluteZeroYear) - - // Add in days from 400-year cycles. - n := y / 400 - y -= 400 * n - d := daysPer400Years * n - - // Add in 100-year cycles. - n = y / 100 - y -= 100 * n - d += daysPer100Years * n - - // Add in 4-year cycles. - n = y / 4 - y -= 4 * n - d += daysPer4Years * n - - // Add in non-leap years. - n = y - d += 365 * n - - return d -} - -const ( - Nanosecond Duration = 1 - Microsecond = 1000 * Nanosecond - Millisecond = 1000 * Microsecond - Second = 1000 * Millisecond - Minute = 60 * Second - Hour = 60 * Minute -) - -// A Duration represents the elapsed time between two instants -// as an int64 nanosecond count. The representation limits the -// largest representable duration to approximately 290 years. -type Duration int64 - -const ( - minDuration Duration = -1 << 63 - maxDuration Duration = 1<<63 - 1 -) - -// String returns a string representing the duration in the form "72h3m0.5s". -// Leading zero units are omitted. As a special case, durations less than one -// second format use a smaller unit (milli-, micro-, or nanoseconds) to ensure -// that the leading digit is non-zero. The zero duration formats as 0s. -func (d Duration) String() string { - // Largest time is 2540400h10m10.000000000s - var buf [32]byte - w := len(buf) - - u := uint64(d) - neg := d < 0 - if neg { - u = -u - } - - if u < uint64(Second) { - // Special case: if duration is smaller than a second, - // use smaller units, like 1.2ms - var prec int - w-- - buf[w] = 's' - w-- - switch { - case u == 0: - return "0s" - case u < uint64(Microsecond): - // print nanoseconds - prec = 0 - buf[w] = 'n' - case u < uint64(Millisecond): - // print microseconds - prec = 3 - // U+00B5 'µ' micro sign == 0xC2 0xB5 - w-- // Need room for two bytes. - copy(buf[w:], "µ") - default: - // print milliseconds - prec = 6 - buf[w] = 'm' - } - w, u = fmtFrac(buf[:w], u, prec) - w = fmtInt(buf[:w], u) - } else { - w-- - buf[w] = 's' - - w, u = fmtFrac(buf[:w], u, 9) - - // u is now integer seconds - w = fmtInt(buf[:w], u%60) - u /= 60 - - // u is now integer minutes - if u > 0 { - w-- - buf[w] = 'm' - w = fmtInt(buf[:w], u%60) - u /= 60 - - // u is now integer hours - // Stop at hours because days can be different lengths. - if u > 0 { - w-- - buf[w] = 'h' - w = fmtInt(buf[:w], u) - } - } - } - - if neg { - w-- - buf[w] = '-' - } - - return string(buf[w:]) -} - -// Nanoseconds returns the duration as an integer nanosecond count. -func (d Duration) Nanoseconds() int64 { return int64(d) } - -// Microseconds returns the duration as an integer microsecond count. -func (d Duration) Microseconds() int64 { return int64(d) / 1e3 } - -// Milliseconds returns the duration as an integer millisecond count. -func (d Duration) Milliseconds() int64 { return int64(d) / 1e6 } - -// These methods return float64 because the dominant -// use case is for printing a floating point number like 1.5s, and -// a truncation to integer would make them not useful in those cases. -// Splitting the integer and fraction ourselves guarantees that -// converting the returned float64 to an integer rounds the same -// way that a pure integer conversion would have, even in cases -// where, say, float64(d.Nanoseconds())/1e9 would have rounded -// differently. - -// Seconds returns the duration as a floating point number of seconds. -func (d Duration) Seconds() float64 { - sec := d / Second - nsec := d % Second - return float64(sec) + float64(nsec)/1e9 -} - -// Minutes returns the duration as a floating point number of minutes. -func (d Duration) Minutes() float64 { - min := d / Minute - nsec := d % Minute - return float64(min) + float64(nsec)/(60*1e9) -} - -// Hours returns the duration as a floating point number of hours. -func (d Duration) Hours() float64 { - hour := d / Hour - nsec := d % Hour - return float64(hour) + float64(nsec)/(60*60*1e9) -} - -// Truncate returns the result of rounding d toward zero to a multiple of m. -// If m <= 0, Truncate returns d unchanged. -func (d Duration) Truncate(m Duration) Duration { - if m <= 0 { - return d - } - return d - d%m -} - -// lessThanHalf reports whether x+x < y but avoids overflow, -// assuming x and y are both positive (Duration is signed). -func lessThanHalf(x, y Duration) bool { - return uint64(x)+uint64(x) < uint64(y) -} - -// Round returns the result of rounding d to the nearest multiple of m. -// The rounding behavior for halfway values is to round away from zero. -// If the result exceeds the maximum (or minimum) -// value that can be stored in a Duration, -// Round returns the maximum (or minimum) duration. -// If m <= 0, Round returns d unchanged. -func (d Duration) Round(m Duration) Duration { - if m <= 0 { - return d - } - r := d % m - if d < 0 { - r = -r - if lessThanHalf(r, m) { - return d + r - } - if d1 := d - m + r; d1 < d { - return d1 - } - return minDuration // overflow - } - if lessThanHalf(r, m) { - return d - r - } - if d1 := d + m - r; d1 > d { - return d1 - } - return maxDuration // overflow -} - -// Add returns the time t+d -func (t Time) Add(d Duration) Time { - dsec := int64(d / 1e9) - nsec := t.nsec + int32(d%1e9) - if nsec >= 1e9 { - dsec++ - nsec -= 1e9 - } else if nsec < 0 { - dsec-- - nsec += 1e9 - } - return Time{t.sec + dsec, nsec} -} - -// Sub returns the duration t-u. If the result exceeds the maximum (or minimum) -// value that can be stored in a Duration, the maximum (or minimum) duration -// will be returned. -// To compute t-d for a duration d, use t.Add(-d). -func (t Time) Sub(u Time) Duration { - sec := t.sec - u.sec - nsec := t.nsec - u.nsec - if sec > 0 && nsec < 0 { - sec-- - nsec += 1e9 - } else if sec < 0 && nsec > 0 { - sec++ - nsec -= 1e9 - } - if sec > int64(maxDuration) { - return maxDuration - } - if sec < int64(minDuration) { - return minDuration - } - return Duration(sec*1e9 + int64(nsec)) -} - -// Since returns the time elapsed since t. -// It is shorthand for time.Now().Sub(t). -func Since(t Time) Duration { - return Now().Sub(t) -} - -// Until returns the duration until t. -// It is shorthand for t.Sub(time.Now()). -func Until(t Time) Duration { - return t.Sub(Now()) -} - -// Date returns the Time corresponding to -// -// yyyy-mm-dd hh:mm:ss + nsec nanoseconds -// -// The month, day, hour, min, sec, and nsec values may be outside -// their usual ranges and will be normalized during the conversion. -// For example, October 32 converts to November 1. -func Date(year int, month Month, day, hour, min, sec, nsec int) Time { - // Normalize month, overflowing into year. - m := int(month) - 1 - year, m = norm(year, m, 12) - month = Month(m) + 1 - - // Normalize nsec, sec, min, hour, overflowing into day. - sec, nsec = norm(sec, nsec, 1e9) - min, sec = norm(min, sec, 60) - hour, min = norm(hour, min, 60) - day, hour = norm(day, hour, 24) - - // Compute days since the absolute epoch. - d := daysSinceEpoch(year) - - // Add in days before this month. - d += uint64(daysBefore[month-1]) - if isLeap(year) && month >= March { - d++ // February 29 - } - - // Add in days before today. - d += uint64(day - 1) - - // Add in time elapsed today. - abs := d * secondsPerDay - abs += uint64(hour*secondsPerHour + min*secondsPerMinute + sec) - - unix := int64(abs) + (absoluteToInternal + internalToUnix) - t := unixTime(unix, int32(nsec)) - return t -} - -// Abs returns the absolute value of d. -// As a special case, math.MinInt64 is converted to math.MaxInt64. -func (d Duration) Abs() Duration { - switch { - case d >= 0: - return d - case d == minDuration: - return maxDuration - default: - return -d - } -} - -func now() (sec int64, nsec int32, mono int64) // injected by runtime - -func Now() Time { - sec, nsec, _ := now() - sec += unixToInternal - minWall - return Time{sec, nsec} -} - -// Unix returns t as a Unix time, the number of seconds elapsed -// since January 1, 1970 UTC. The result does not depend on the -// location associated with t. -// Unix-like operating systems often record time as a 32-bit -// count of seconds, but since the method here returns a 64-bit -// value it is valid for billions of years into the past or future. -func (t Time) Unix() int64 { - return t.unixSec() -} - -// UnixMilli returns t as a Unix time, the number of milliseconds elapsed since -// January 1, 1970 UTC. The result is undefined if the Unix time in -// milliseconds cannot be represented by an int64 (a date more than 292 million -// years before or after 1970). The result does not depend on the -// location associated with t. -func (t Time) UnixMilli() int64 { - return t.unixSec()*1e3 + int64(t.nsec)/1e6 -} - -// UnixMicro returns t as a Unix time, the number of microseconds elapsed since -// January 1, 1970 UTC. The result is undefined if the Unix time in -// microseconds cannot be represented by an int64 (a date before year -290307 or -// after year 294246). The result does not depend on the location associated -// with t. -func (t Time) UnixMicro() int64 { - return t.unixSec()*1e6 + int64(t.nsec)/1e3 -} - -// UnixNano returns t as a Unix time, the number of nanoseconds elapsed -// since January 1, 1970 UTC. The result is undefined if the Unix time -// in nanoseconds cannot be represented by an int64 (a date before the year -// 1678 or after 2262). Note that this means the result of calling UnixNano -// on the zero Time is undefined. The result does not depend on the -// location associated with t. -func (t Time) UnixNano() int64 { - return (t.unixSec())*1e9 + int64(t.nsec) -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface. -func (t Time) MarshalBinary() ([]byte, error) { - sec := t.sec - nsec := t.nsec - enc := []byte{ - //encode seconds (int64) / bytes 0 to 7 - byte(sec >> 56), - byte(sec >> 48), - byte(sec >> 40), - byte(sec >> 32), - byte(sec >> 24), - byte(sec >> 16), - byte(sec >> 8), - byte(sec), - //encode nanoseconds (int32) / bytes 8 to 11 - byte(nsec >> 24), - byte(nsec >> 16), - byte(nsec >> 8), - byte(nsec), - } - return enc, nil -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. -func (t *Time) UnmarshalBinary(data []byte) error { - buf := data - if len(buf) == 0 { - return errors.New("Time.UnmarshalBinary: no data") - } - if len(buf) != 12 { // 8 bytes for sec (int64) + 4 bytes for nsec (int32) - return errors.New("Time.UnmarshalBinary: invalid length") - } - sec := int64(buf[7]) | int64(buf[6])<<8 | int64(buf[5])<<16 | int64(buf[4])<<24 | - int64(buf[3])<<32 | int64(buf[2])<<40 | int64(buf[1])<<48 | int64(buf[0])<<56 - - buf = buf[8:] - nsec := int32(buf[3]) | int32(buf[2])<<8 | int32(buf[1])<<16 | int32(buf[0])<<24 - - *t = Time{} - t.sec = sec - t.nsec = nsec - - return nil -} - -// MarshalJSON implements the json.Marshaler interface. -// The time is a quoted string in RFC 3339 format, with sub-second precision added if present. -func (t Time) MarshalJSON() ([]byte, error) { - if y := t.Year(); y < 0 || y >= 10000 { - // RFC 3339 is clear that years are 4 digits exactly. - // See golang.org/issue/4556#c15 for more discussion. - return nil, errors.New("Time.MarshalJSON: year outside of range [0,9999]") - } - - b := make([]byte, 0, len(RFC3339Nano)+2) - b = append(b, '"') - b = t.AppendFormat(b, RFC3339Nano) - b = append(b, '"') - return b, nil -} - -// UnmarshalJSON implements the json.Unmarshaler interface. -// The time is expected to be a quoted string in RFC 3339 format. -func (t *Time) UnmarshalJSON(data []byte) error { - // Ignore null, like in the main JSON package. - if string(data) == "null" { - return nil - } - // Fractional seconds are handled implicitly by Parse. - var err error - *t, err = Parse(`"`+RFC3339+`"`, string(data)) - return err -} - -// MarshalText implements the encoding.TextMarshaler interface. -// The time is formatted in RFC 3339 format, with sub-second precision added if present. -func (t Time) MarshalText() ([]byte, error) { - if y := t.Year(); y < 0 || y >= 10000 { - return nil, errors.New("Time.MarshalText: year outside of range [0,9999]") - } - - b := make([]byte, 0, len(RFC3339Nano)) - return t.AppendFormat(b, RFC3339Nano), nil -} - -// UnmarshalText implements the encoding.TextUnmarshaler interface. -// The time is expected to be in RFC 3339 format. -func (t *Time) UnmarshalText(data []byte) error { - // Fractional seconds are handled implicitly by Parse. - var err error - *t, err = Parse(RFC3339, string(data)) - return err -} - -// Unix returns the local Time corresponding to the given Unix time, -// sec seconds and nsec nanoseconds since January 1, 1970 UTC. -// It is valid to pass nsec outside the range [0, 999999999]. -// Not all sec values have a corresponding time value. One such -// value is 1<<63-1 (the largest int64 value). -func Unix(sec int64, nsec int64) Time { - if nsec < 0 || nsec >= 1e9 { - n := nsec / 1e9 - sec += n - nsec -= n * 1e9 - if nsec < 0 { - nsec += 1e9 - sec-- - } - } - return unixTime(sec, int32(nsec)) -} - -// UnixMilli returns the local Time corresponding to the given Unix time, -// msec milliseconds since January 1, 1970 UTC. -func UnixMilli(msec int64) Time { - return Unix(msec/1e3, (msec%1e3)*1e6) -} - -// UnixMicro returns the local Time corresponding to the given Unix time, -// usec microseconds since January 1, 1970 UTC. -func UnixMicro(usec int64) Time { - return Unix(usec/1e6, (usec%1e6)*1e3) -} - -func unixTime(sec int64, nsec int32) Time { - return Time{sec + unixToInternal, nsec} -} - -func isLeap(year int) bool { - return year%4 == 0 && (year%100 != 0 || year%400 == 0) -} - -// fmtFrac formats the fraction of v/10**prec (e.g., ".12345") into the -// tail of buf, omitting trailing zeros. It omits the decimal -// point too when the fraction is 0. It returns the index where the -// output bytes begin and the value v/10**prec. -func fmtFrac(buf []byte, v uint64, prec int) (nw int, nv uint64) { - // Omit trailing zeros up to and including decimal point. - w := len(buf) - isprint := false - for i := 0; i < prec; i++ { - digit := v % 10 - isprint = isprint || digit != 0 - if isprint { - w-- - buf[w] = byte(digit) + '0' - } - v /= 10 - } - if isprint { - w-- - buf[w] = '.' - } - return w, v -} - -// add v at the end of buf and return the index where the number starts in buf -func fmtInt(buf []byte, v uint64) int { - w := len(buf) - if v == 0 { - w-- - buf[w] = '0' - } else { - for v > 0 { - w-- - buf[w] = byte(v%10) + '0' - v /= 10 - } - } - return w -} - -// norm returns nhi, nlo such that -// -// hi * base + lo == nhi * base + nlo -// 0 <= nlo < base -func norm(hi, lo, base int) (nhi, nlo int) { - if lo < 0 { - n := (-lo-1)/base + 1 - hi -= n - lo += n * base - } - if lo >= base { - n := lo / base - hi += n - lo -= n * base - } - return hi, lo -} diff --git a/gnovm/stdlibs/time/timezoneinfo.gno b/gnovm/stdlibs/time/timezoneinfo.gno index 704fd790fc3..a8678f855ea 100644 --- a/gnovm/stdlibs/time/timezoneinfo.gno +++ b/gnovm/stdlibs/time/timezoneinfo.gno @@ -1,27 +1,639 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package time import ( "errors" + // "sync" XXX + // "syscall" XXX +) + +//go:generate env ZONEINFO=$GOROOT/lib/time/zoneinfo.zip go run genzabbrs.go -output zoneinfo_abbrs_windows.go + +// A Location maps time instants to the zone in use at that time. +// Typically, the Location represents the collection of time offsets +// in use in a geographical area. For many Locations the time offset varies +// depending on whether daylight savings time is in use at the time instant. +type Location struct { + name string + zone []zone + tx []zoneTrans + + // The tzdata information can be followed by a string that describes + // how to handle DST transitions not recorded in zoneTrans. + // The format is the TZ environment variable without a colon; see + // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html. + // Example string, for America/Los_Angeles: PST8PDT,M3.2.0,M11.1.0 + extend string + + // Most lookups will be for the current time. + // To avoid the binary search through tx, keep a + // static one-element cache that gives the correct + // zone for the time when the Location was created. + // if cacheStart <= t < cacheEnd, + // lookup can return cacheZone. + // The units for cacheStart and cacheEnd are seconds + // since January 1, 1970 UTC, to match the argument + // to lookup. + cacheStart int64 + cacheEnd int64 + cacheZone *zone +} + +// A zone represents a single time zone such as CET. +type zone struct { + name string // abbreviated name, "CET" + offset int // seconds east of UTC + isDST bool // is this zone Daylight Savings Time? +} + +// A zoneTrans represents a single time zone transition. +type zoneTrans struct { + when int64 // transition time, in seconds since 1970 GMT + index uint8 // the index of the zone that goes into effect at that time + isstd, isutc bool // ignored - no idea what these mean +} + +// alpha and omega are the beginning and end of time for zone +// transitions. +const ( + alpha = -1 << 63 // math.MinInt64 + omega = 1<<63 - 1 // math.MaxInt64 ) +// UTC represents Universal Coordinated Time (UTC). var UTC *Location = &utcLoc +// utcLoc is separate so that get can refer to &utcLoc +// and ensure that it never returns a nil *Location, +// even if a badly behaved client has changed UTC. var utcLoc = Location{name: "UTC"} -type Location struct { - name string +// Local represents the system's local time zone. +// On Unix systems, Local consults the TZ environment +// variable to find the time zone to use. No TZ means +// use the system default /etc/localtime. +// TZ="" means use UTC. +// TZ="foo" means use file foo in the system timezone directory. +var Local *Location = &localLoc + +// localLoc is separate so that initLocal can initialize +// it even if a client has changed Local. +var localLoc Location + +// XXX var localOnce sync.Once + +func (l *Location) get() *Location { + if l == nil { + return &utcLoc + } + if l == &localLoc { + // XXX localOnce.Do(initLocal) + } + return l } +// String returns a descriptive name for the time zone information, +// corresponding to the name argument to LoadLocation or FixedZone. func (l *Location) String() string { - if l == nil { - return "UTC" + return l.get().name +} + +// FixedZone returns a Location that always uses +// the given zone name and offset (seconds east of UTC). +func FixedZone(name string, offset int) *Location { + l := &Location{ + name: name, + zone: []zone{{name, offset, false}}, + tx: []zoneTrans{{alpha, 0, false, false}}, + cacheStart: alpha, + cacheEnd: omega, + } + l.cacheZone = &l.zone[0] + return l +} + +// lookup returns information about the time zone in use at an +// instant in time expressed as seconds since January 1, 1970 00:00:00 UTC. +// +// The returned information gives the name of the zone (such as "CET"), +// the start and end times bracketing sec when that zone is in effect, +// the offset in seconds east of UTC (such as -5*60*60), and whether +// the daylight savings is being observed at that time. +func (l *Location) lookup(sec int64) (name string, offset int, start, end int64, isDST bool) { + l = l.get() + + if len(l.zone) == 0 { + name = "UTC" + offset = 0 + start = alpha + end = omega + isDST = false + return + } + + if zone := l.cacheZone; zone != nil && l.cacheStart <= sec && sec < l.cacheEnd { + name = zone.name + offset = zone.offset + start = l.cacheStart + end = l.cacheEnd + isDST = zone.isDST + return + } + + if len(l.tx) == 0 || sec < l.tx[0].when { + zone := &l.zone[l.lookupFirstZone()] + name = zone.name + offset = zone.offset + start = alpha + if len(l.tx) > 0 { + end = l.tx[0].when + } else { + end = omega + } + isDST = zone.isDST + return + } + + // Binary search for entry with largest time <= sec. + // Not using sort.Search to avoid dependencies. + tx := l.tx + end = omega + lo := 0 + hi := len(tx) + for hi-lo > 1 { + m := lo + (hi-lo)/2 + lim := tx[m].when + if sec < lim { + end = lim + hi = m + } else { + lo = m + } + } + zone := &l.zone[tx[lo].index] + name = zone.name + offset = zone.offset + start = tx[lo].when + // end = maintained during the search + isDST = zone.isDST + + // If we're at the end of the known zone transitions, + // try the extend string. + if lo == len(tx)-1 && l.extend != "" { + if ename, eoffset, estart, eend, eisDST, ok := tzset(l.extend, end, sec); ok { + return ename, eoffset, estart, eend, eisDST + } + } + + return +} + +// lookupFirstZone returns the index of the time zone to use for times +// before the first transition time, or when there are no transition +// times. +// +// The reference implementation in localtime.c from +// https://www.iana.org/time-zones/repository/releases/tzcode2013g.tar.gz +// implements the following algorithm for these cases: +// 1. If the first zone is unused by the transitions, use it. +// 2. Otherwise, if there are transition times, and the first +// transition is to a zone in daylight time, find the first +// non-daylight-time zone before and closest to the first transition +// zone. +// 3. Otherwise, use the first zone that is not daylight time, if +// there is one. +// 4. Otherwise, use the first zone. +func (l *Location) lookupFirstZone() int { + // Case 1. + if !l.firstZoneUsed() { + return 0 + } + + // Case 2. + if len(l.tx) > 0 && l.zone[l.tx[0].index].isDST { + for zi := int(l.tx[0].index) - 1; zi >= 0; zi-- { + if !l.zone[zi].isDST { + return zi + } + } + } + + // Case 3. + for zi := range l.zone { + if !l.zone[zi].isDST { + return zi + } + } + + // Case 4. + return 0 +} + +// firstZoneUsed reports whether the first zone is used by some +// transition. +func (l *Location) firstZoneUsed() bool { + for _, tx := range l.tx { + if tx.index == 0 { + return true + } + } + return false +} + +// tzset takes a timezone string like the one found in the TZ environment +// variable, the end 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, initEnd, 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, initEnd, 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 } - return l.name + 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 := internalDate(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 } -func LoadLocation(name string) (*Location, error) { - if name == "" || name == "UTC" { - return UTC, nil +// 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 +} + +// lookupName returns information about the time zone with +// the given name (such as "EST") at the given pseudo-Unix time +// (what the given time of day would be in UTC). +func (l *Location) lookupName(name string, unix int64) (offset int, ok bool) { + l = l.get() + + // First try for a zone with the right name that was actually + // in effect at the given time. (In Sydney, Australia, both standard + // and daylight-savings time are abbreviated "EST". Using the + // offset helps us pick the right one for the given time. + // It's not perfect: during the backward transition we might pick + // either one.) + for i := range l.zone { + zone := &l.zone[i] + if zone.name == name { + nam, offset, _, _, _ := l.lookup(unix - int64(zone.offset)) + if nam == zone.name { + return offset, true + } + } + } + + // Otherwise fall back to an ordinary name match. + for i := range l.zone { + zone := &l.zone[i] + if zone.name == name { + return zone.offset, true + } + } + + // Otherwise, give up. + return +} + +// NOTE(rsc): Eventually we will need to accept the POSIX TZ environment +// syntax too, but I don't feel like implementing it today. + +var errLocation = errors.New("time: invalid location name") + +var zoneinfo *string + +func loadFromEmbeddedTZData(name string) ([]byte, bool) // injected + +// XXX var zoneinfoOnce sync.Once +// containsDotDot reports whether s contains "..". +func containsDotDot(s string) bool { + if len(s) < 2 { + return false + } + for i := 0; i < len(s)-1; i++ { + if s[i] == '.' && s[i+1] == '.' { + return true + } } - return nil, errors.New("time: unsupported location; only UTC is available") + return false } From 780fd5ba744ff1d7c7e753aad4d496dcabac3c72 Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Wed, 23 Oct 2024 17:26:29 +0200 Subject: [PATCH 07/27] feat: fix wrong compute in const --- dbeug/time.gno | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 dbeug/time.gno diff --git a/dbeug/time.gno b/dbeug/time.gno new file mode 100644 index 00000000000..1e5bc34f63a --- /dev/null +++ b/dbeug/time.gno @@ -0,0 +1,12 @@ +package main + +import ( + "time" +) + +func main() { + time.Now() +} + +// Output: +// 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001 From 6db8cb0598041a32a6a1a54fcf5e0ee319f8db74 Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Wed, 23 Oct 2024 17:26:38 +0200 Subject: [PATCH 08/27] feat: fix wrong compute in const --- gnovm/stdlibs/time/time.gno | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/gnovm/stdlibs/time/time.gno b/gnovm/stdlibs/time/time.gno index 620069b211c..ecd39b719e7 100644 --- a/gnovm/stdlibs/time/time.gno +++ b/gnovm/stdlibs/time/time.gno @@ -5,6 +5,11 @@ import "errors" const ( minWall = wallToInternal // year 1885 + // The unsigned zero year for internal calculations. + // Must be 1 mod 400, and times before it will not compute correctly, + // but otherwise can be changed at will. + absoluteZeroYear = -292277022399 + // The year of the zero Time. // Assumed by the unixToInternal computation below. internalYear = 1 @@ -12,14 +17,11 @@ const ( // Offsets to convert between internal and absolute or Unix times. absoluteToInternal int64 = (absoluteZeroYear - internalYear) * 365.2425 * secondsPerDay internalToAbsolute = -absoluteToInternal - unixToInternal int64 = (1969*365 + 1969/4 - 1969/100 + 1969/400) * secondsPerDay - internalToUnix int64 = -unixToInternal - wallToInternal int64 = (1884*365 + 1884/4 - 1884/100 + 1884/400) * secondsPerDay - // The unsigned zero year for internal calculations. - // Must be 1 mod 400, and times before it will not compute correctly, - // but otherwise can be changed at will. - absoluteZeroYear = -292277022399 + unixToInternal int64 = (1969*365 + 1969/4 - 1969/100 + 1969/400) * secondsPerDay + internalToUnix int64 = -unixToInternal + + wallToInternal int64 = (1884*365 + 1884/4 - 1884/100 + 1884/400) * secondsPerDay secondsPerMinute = 60 secondsPerHour = 60 * secondsPerMinute From b3ed5bae8b33a547f422fbaf808946668d226702 Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Wed, 23 Oct 2024 18:12:44 +0200 Subject: [PATCH 09/27] feat: add data about time pkg --- dbeug/time.gno | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dbeug/time.gno b/dbeug/time.gno index 1e5bc34f63a..7eef5913c6d 100644 --- a/dbeug/time.gno +++ b/dbeug/time.gno @@ -5,7 +5,10 @@ import ( ) func main() { - time.Now() + year, month, day := time.Now().Date() + println("year", year, "month", month, "day", day) + it := time.Now().InternalDebug() + println("InternalDebug", it) } // Output: From 933396853e0f405598222cbfb60499d6bdbd6ac4 Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Wed, 23 Oct 2024 18:13:23 +0200 Subject: [PATCH 10/27] feat: add data about time pkg --- gnovm/stdlibs/time/time.gno | 85 ++++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 39 deletions(-) diff --git a/gnovm/stdlibs/time/time.gno b/gnovm/stdlibs/time/time.gno index ecd39b719e7..e692a29a729 100644 --- a/gnovm/stdlibs/time/time.gno +++ b/gnovm/stdlibs/time/time.gno @@ -2,41 +2,13 @@ package time import "errors" -const ( - minWall = wallToInternal // year 1885 - - // The unsigned zero year for internal calculations. - // Must be 1 mod 400, and times before it will not compute correctly, - // but otherwise can be changed at will. - absoluteZeroYear = -292277022399 - - // The year of the zero Time. - // Assumed by the unixToInternal computation below. - internalYear = 1 - - // Offsets to convert between internal and absolute or Unix times. - absoluteToInternal int64 = (absoluteZeroYear - internalYear) * 365.2425 * secondsPerDay - internalToAbsolute = -absoluteToInternal - - unixToInternal int64 = (1969*365 + 1969/4 - 1969/100 + 1969/400) * secondsPerDay - internalToUnix int64 = -unixToInternal - - wallToInternal int64 = (1884*365 + 1884/4 - 1884/100 + 1884/400) * secondsPerDay - - secondsPerMinute = 60 - secondsPerHour = 60 * secondsPerMinute - secondsPerDay = 24 * secondsPerHour - secondsPerWeek = 7 * secondsPerDay - daysPer400Years = 365*400 + 97 - daysPer100Years = 365*100 + 24 - daysPer4Years = 365*4 + 1 -) - type Time struct { sec int64 nsec int32 } +const minWall = wallToInternal // year 1885 + func (t *Time) unixSec() int64 { return t.sec + internalToUnix } func (t Time) After(u Time) bool { @@ -98,10 +70,37 @@ func (d Weekday) String() string { return "%!Weekday(" + string(buf[n:]) + ")" } +const ( + // The unsigned zero year for internal calculations. + // Must be 1 mod 400, and times before it will not compute correctly, + // but otherwise can be changed at will. + absoluteZeroYear = -292277022399 + + // The year of the zero Time. + // Assumed by the unixToInternal computation below. + internalYear = 1 + + // Offsets to convert between internal and absolute or Unix times. + absoluteToInternal int64 = (absoluteZeroYear - internalYear) * 365.2425 * secondsPerDay + internalToAbsolute = -absoluteToInternal + + unixToInternal int64 = (1969*365 + 1969/4 - 1969/100 + 1969/400) * secondsPerDay + internalToUnix int64 = -unixToInternal + + wallToInternal int64 = (1884*365 + 1884/4 - 1884/100 + 1884/400) * secondsPerDay +) + func (t Time) IsZero() bool { return t.sec == 0 && t.nsec == 0 } +// rename of the "abs" method in the original time.go +// since we removed the location & zoneinfo related code +func (t Time) internal() uint64 { + sec := t.unixSec() + return uint64(sec + (unixToInternal + internalToAbsolute)) +} + // Date returns the year, month, and day in which t occurs. func (t Time) Date() (year int, month Month, day int) { year, month, day, _ = t.date(true) @@ -132,7 +131,7 @@ func (t Time) Weekday() Weekday { func internalWeekday(it uint64) Weekday { sec := (it + uint64(Monday)*secondsPerDay) % secondsPerWeek - return Weekday(sec / secondsPerDay) + return Weekday(int(sec) / secondsPerDay) } // ISOWeek returns the ISO 8601 year and week number in which t occurs. @@ -182,6 +181,10 @@ func (t Time) Hour() int { return int(t.internal()%secondsPerDay) / secondsPerHour } +func (t Time) InternalDebug() uint64 { + return t.internal() +} + // Minute returns the minute offset within the hour specified by t, in the range [0, 59]. func (t Time) Minute() int { return int(t.internal()%secondsPerHour) / secondsPerMinute @@ -219,12 +222,15 @@ func (t Time) AddDate(years int, months int, days int) Time { return Date(year+years, month+Month(months), day+days, hour, min, sec, int(t.nsec)) } -// rename of the "abs" method in the original time.go -// since we removed the location & zoneinfo related code -func (t Time) internal() uint64 { - sec := t.unixSec() - return uint64(sec + (unixToInternal + internalToAbsolute)) -} +const ( + secondsPerMinute = 60 + secondsPerHour = 60 * secondsPerMinute + secondsPerDay = 24 * secondsPerHour + secondsPerWeek = 7 * secondsPerDay + daysPer400Years = 365*400 + 97 + daysPer100Years = 365*100 + 24 + daysPer4Years = 365*4 + 1 +) func (t Time) date(full bool) (year int, month Month, day int, yday int) { return internalDate(t.internal(), full) @@ -272,12 +278,13 @@ func internalDate(it uint64, full bool) (year int, month Month, day int, yday in day = yday if isLeap(year) { + // Leap year switch { case day > 31+29-1: - // After leap day; pretend it wasn't there + // After leap day; pretend it wasn't there. day-- case day == 31+29-1: - // Leap day + // Leap day. month = February day = 29 return From 1fe6e3cc80271bc9f81c75c864a7f9481362af83 Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Wed, 23 Oct 2024 18:39:13 +0200 Subject: [PATCH 11/27] fix: remove minWall sub to time injected by syscall --- dbeug/time.gno | 5 +---- gnovm/stdlibs/time/time.gno | 8 +------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/dbeug/time.gno b/dbeug/time.gno index 7eef5913c6d..d9f7e64d958 100644 --- a/dbeug/time.gno +++ b/dbeug/time.gno @@ -5,10 +5,7 @@ import ( ) func main() { - year, month, day := time.Now().Date() - println("year", year, "month", month, "day", day) - it := time.Now().InternalDebug() - println("InternalDebug", it) + println(time.Now()) } // Output: diff --git a/gnovm/stdlibs/time/time.gno b/gnovm/stdlibs/time/time.gno index e692a29a729..4bc5d217bf5 100644 --- a/gnovm/stdlibs/time/time.gno +++ b/gnovm/stdlibs/time/time.gno @@ -7,8 +7,6 @@ type Time struct { nsec int32 } -const minWall = wallToInternal // year 1885 - func (t *Time) unixSec() int64 { return t.sec + internalToUnix } func (t Time) After(u Time) bool { @@ -181,10 +179,6 @@ func (t Time) Hour() int { return int(t.internal()%secondsPerDay) / secondsPerHour } -func (t Time) InternalDebug() uint64 { - return t.internal() -} - // Minute returns the minute offset within the hour specified by t, in the range [0, 59]. func (t Time) Minute() int { return int(t.internal()%secondsPerHour) / secondsPerMinute @@ -641,7 +635,7 @@ func now() (sec int64, nsec int32, mono int64) // injected by runtime func Now() Time { sec, nsec, _ := now() - sec += unixToInternal - minWall + sec += unixToInternal return Time{sec, nsec} } From 0b33b7796825e8622900c2ffde67ab3022b50fd8 Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Wed, 23 Oct 2024 19:11:28 +0200 Subject: [PATCH 12/27] tests: fix text --- gnovm/tests/files/time0_stdlibs.gno | 2 +- gnovm/tests/files/time13_stdlibs.gno | 4 ++-- gnovm/tests/files/time1_stdlibs.gno | 2 +- gnovm/tests/files/time2_stdlibs.gno | 2 +- gnovm/tests/files/time3_stdlibs.gno | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/gnovm/tests/files/time0_stdlibs.gno b/gnovm/tests/files/time0_stdlibs.gno index d9f7e64d958..da1db306396 100644 --- a/gnovm/tests/files/time0_stdlibs.gno +++ b/gnovm/tests/files/time0_stdlibs.gno @@ -9,4 +9,4 @@ func main() { } // Output: -// 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001 +// 2009-02-13 23:31:30 +0000 UTC diff --git a/gnovm/tests/files/time13_stdlibs.gno b/gnovm/tests/files/time13_stdlibs.gno index 1c2d1d868d1..25e6816d207 100644 --- a/gnovm/tests/files/time13_stdlibs.gno +++ b/gnovm/tests/files/time13_stdlibs.gno @@ -6,10 +6,10 @@ import ( var dummy = 1 -var t time.Time = time.Date(2007, time.November, 10, 23, 4, 5, 0, time.UTC) +var t time.Time = time.Date(2007, time.November, 10, 23, 4, 5, 0) func main() { - t = time.Date(2009, time.November, 10, 23, 4, 5, 0, time.UTC) + t = time.Date(2009, time.November, 10, 23, 4, 5, 0) println(t.Clock()) } diff --git a/gnovm/tests/files/time1_stdlibs.gno b/gnovm/tests/files/time1_stdlibs.gno index 2250bb5bdaf..0c530bb5353 100644 --- a/gnovm/tests/files/time1_stdlibs.gno +++ b/gnovm/tests/files/time1_stdlibs.gno @@ -5,7 +5,7 @@ import ( ) func main() { - t := time.Date(2009, time.November, 10, 23, 4, 5, 0, time.UTC) + t := time.Date(2009, time.November, 10, 23, 4, 5, 0) m := t.Minute() println(t, m) } diff --git a/gnovm/tests/files/time2_stdlibs.gno b/gnovm/tests/files/time2_stdlibs.gno index 054a67c18d8..8a5c7a37cbe 100644 --- a/gnovm/tests/files/time2_stdlibs.gno +++ b/gnovm/tests/files/time2_stdlibs.gno @@ -5,7 +5,7 @@ import ( ) func main() { - t := time.Date(2009, time.November, 10, 23, 4, 5, 0, time.UTC) + t := time.Date(2009, time.November, 10, 23, 4, 5, 0) h, m, s := t.Clock() println(h, m, s) } diff --git a/gnovm/tests/files/time3_stdlibs.gno b/gnovm/tests/files/time3_stdlibs.gno index b2d0dc5b123..24a61aca4bb 100644 --- a/gnovm/tests/files/time3_stdlibs.gno +++ b/gnovm/tests/files/time3_stdlibs.gno @@ -6,7 +6,7 @@ import ( // FIXME related to named returns func main() { - t := time.Date(2009, time.November, 10, 23, 4, 5, 0, time.UTC) + t := time.Date(2009, time.November, 10, 23, 4, 5, 0) println(t.Clock()) } From 25b963395688f517503bc7b2a66213d0ee2a4a38 Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Thu, 24 Oct 2024 10:32:46 +0200 Subject: [PATCH 13/27] feat: convert tz date into UTC in parse function --- gnovm/stdlibs/time/format.gno | 120 ++++++++++++++++++++++++++++++---- gnovm/stdlibs/time/time.gno | 13 +++- 2 files changed, 119 insertions(+), 14 deletions(-) diff --git a/gnovm/stdlibs/time/format.gno b/gnovm/stdlibs/time/format.gno index fc92b6eec49..5f83642fbff 100644 --- a/gnovm/stdlibs/time/format.gno +++ b/gnovm/stdlibs/time/format.gno @@ -568,8 +568,7 @@ func (t Time) Format(layout string) string { // representation to b and returns the extended buffer. func (t Time) AppendFormat(b []byte, layout string) []byte { var ( - it = t.internal() - + it = t.internal() year int = -1 month Month day int @@ -889,10 +888,19 @@ func skip(value, prefix string) (string, error) { // differ by the actual zone offset. To avoid such problems, prefer time layouts // that use a numeric zone offset, or use ParseInLocation. func Parse(layout, value string) (Time, error) { - return parse(layout, value) + return parse(layout, value, UTC, Local) +} + +// ParseInLocation is like Parse but differs in two important ways. +// First, in the absence of time zone information, Parse interprets a time as UTC; +// ParseInLocation interprets the time as in the given location. +// Second, when given a zone offset or abbreviation, Parse tries to match it +// against the Local location; ParseInLocation uses the given location. +func ParseInLocation(layout, value string, loc *Location) (Time, error) { + return parse(layout, value, loc, loc) } -func parse(layout, value string) (Time, error) { +func parse(layout, value string, defaultLocation, local *Location) (Time, error) { alayout, avalue := layout, value rangeErrString := "" // set if a value is out of range amSet := false // do we need to subtract 12 from the hour for midnight? @@ -900,14 +908,16 @@ func parse(layout, value string) (Time, error) { // Time being constructed. var ( - year int - month int = -1 - day int = -1 - yday int = -1 - hour int - min int - sec int - nsec int + year int + month int = -1 + day int = -1 + yday int = -1 + hour int + min int + sec int + nsec int + zoneOffset int = -1 + zoneName string ) // Each iteration processes one std value. @@ -1052,13 +1062,74 @@ func parse(layout, value string) (Time, error) { value = value[1:] break } + var sign, hour, min, seconds string + if std == stdISO8601ColonTZ || std == stdNumColonTZ { + if len(value) < 6 { + err = errBad + break + } + if value[3] != ':' { + err = errBad + break + } + sign, hour, min, seconds, value = value[0:1], value[1:3], value[4:6], "00", value[6:] + } else if std == stdNumShortTZ || std == stdISO8601ShortTZ { + if len(value) < 3 { + err = errBad + break + } + sign, hour, min, seconds, value = value[0:1], value[1:3], "00", "00", value[3:] + } else if std == stdISO8601ColonSecondsTZ || std == stdNumColonSecondsTZ { + if len(value) < 9 { + err = errBad + break + } + if value[3] != ':' || value[6] != ':' { + err = errBad + break + } + sign, hour, min, seconds, value = value[0:1], value[1:3], value[4:6], value[7:9], value[9:] + } else if std == stdISO8601SecondsTZ || std == stdNumSecondsTz { + if len(value) < 7 { + err = errBad + break + } + sign, hour, min, seconds, value = value[0:1], value[1:3], value[3:5], value[5:7], value[7:] + } else { + if len(value) < 5 { + err = errBad + break + } + sign, hour, min, seconds, value = value[0:1], value[1:3], value[3:5], "00", value[5:] + } + var hr, mm, ss int + hr, err = atoi(hour) + if err == nil { + mm, err = atoi(min) + } + if err == nil { + ss, err = atoi(seconds) + } + zoneOffset = (hr*60+mm)*60 + ss // offset is in seconds + switch sign[0] { + case '+': + case '-': + zoneOffset = -zoneOffset + default: + err = errBad + } case stdTZ: // Does it look like a time zone? if len(value) >= 3 && value[0:3] == "UTC" { value = value[3:] break } - err = errBad + n, ok := parseTimeZone(value) + if !ok { + err = errBad + break + } + zoneName, value = value[:n], value[n:] case stdFracSecond0: // stdFracSecond0 requires the exact number of digits as specified in @@ -1144,6 +1215,29 @@ func parse(layout, value string) (Time, error) { return Time{}, &ParseError{alayout, avalue, "", value, ": day out of range"} } + if zoneOffset != -1 { + t := Date(year, Month(month), day, hour, min, sec, nsec) + t.addSec(-int64(zoneOffset)) + + // Look for local zone with the given offset. + // If that zone was in effect at the given time, use it. + return t, nil + } + + if zoneName != "" { + t := Date(year, Month(month), day, hour, min, sec, nsec) + // Look for local zone with the given offset. + // If that zone was in effect at the given time, use it. + offset, ok := local.lookupName(zoneName, t.unixSec()) + if ok { + t.addSec(-int64(offset)) + return t, nil + } + + return t, nil + } + + // Otherwise, fall back to default. return Date(year, Month(month), day, hour, min, sec, nsec), nil } diff --git a/gnovm/stdlibs/time/time.gno b/gnovm/stdlibs/time/time.gno index 4bc5d217bf5..3ad85270354 100644 --- a/gnovm/stdlibs/time/time.gno +++ b/gnovm/stdlibs/time/time.gno @@ -1,4 +1,4 @@ -package time +package garbage import "errors" @@ -216,6 +216,17 @@ func (t Time) AddDate(years int, months int, days int) Time { return Date(year+years, month+Month(months), day+days, hour, min, sec, int(t.nsec)) } +func (t *Time) addSec(d int64) { + sum := t.sec + d + if (sum > t.sec) == (d > 0) { + t.sec = sum + } else if d > 0 { + t.sec = 1<<63 - 1 + } else { + t.sec = -(1<<63 - 1) + } +} + const ( secondsPerMinute = 60 secondsPerHour = 60 * secondsPerMinute From 39b9d832602068abb103a40fd3d4f68fb1e86dc7 Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Thu, 24 Oct 2024 10:58:36 +0200 Subject: [PATCH 14/27] feat: simplify time pkg, remove mono clock & location --- dbeug/time.gno | 12 ------------ gnovm/stdlibs/time/time.gno | 6 +++--- 2 files changed, 3 insertions(+), 15 deletions(-) delete mode 100644 dbeug/time.gno diff --git a/dbeug/time.gno b/dbeug/time.gno deleted file mode 100644 index d9f7e64d958..00000000000 --- a/dbeug/time.gno +++ /dev/null @@ -1,12 +0,0 @@ -package main - -import ( - "time" -) - -func main() { - println(time.Now()) -} - -// Output: -// 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001 diff --git a/gnovm/stdlibs/time/time.gno b/gnovm/stdlibs/time/time.gno index 3ad85270354..136f5d55954 100644 --- a/gnovm/stdlibs/time/time.gno +++ b/gnovm/stdlibs/time/time.gno @@ -1,4 +1,4 @@ -package garbage +package time import "errors" @@ -693,7 +693,7 @@ func (t Time) MarshalBinary() ([]byte, error) { sec := t.sec nsec := t.nsec enc := []byte{ - //encode seconds (int64) / bytes 0 to 7 + // encode seconds (int64) / bytes 0 to 7 byte(sec >> 56), byte(sec >> 48), byte(sec >> 40), @@ -702,7 +702,7 @@ func (t Time) MarshalBinary() ([]byte, error) { byte(sec >> 16), byte(sec >> 8), byte(sec), - //encode nanoseconds (int32) / bytes 8 to 11 + // encode nanoseconds (int32) / bytes 8 to 11 byte(nsec >> 24), byte(nsec >> 16), byte(nsec >> 8), From 88e12e75148b515f4a19e70e26498d6992260681 Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Thu, 24 Oct 2024 11:13:10 +0200 Subject: [PATCH 15/27] feat: add Round & Zone method to time --- gnovm/stdlibs/time/time.gno | 108 ++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/gnovm/stdlibs/time/time.gno b/gnovm/stdlibs/time/time.gno index 136f5d55954..f038f9b6c03 100644 --- a/gnovm/stdlibs/time/time.gno +++ b/gnovm/stdlibs/time/time.gno @@ -642,6 +642,17 @@ func (d Duration) Abs() Duration { } } +func (t Time) Round(d Duration) Time { + if d <= 0 { + return t + } + _, r := div(t, d) + if lessThanHalf(r, d) { + return t.Add(-r) + } + return t.Add(d - r) +} + func now() (sec int64, nsec int32, mono int64) // injected by runtime func Now() Time { @@ -782,6 +793,12 @@ func (t *Time) UnmarshalText(data []byte) error { return err } +// Zone is here to avoid breaking changes, but return UTC and offset 0. +func (t Time) Zone() (name string, offset int) { + name, offset = "UTC", 0 + return +} + // Unix returns the local Time corresponding to the given Unix time, // sec seconds and nsec nanoseconds since January 1, 1970 UTC. // It is valid to pass nsec outside the range [0, 999999999]. @@ -891,3 +908,94 @@ func daysIn(m Month, year int) int { // adding (m>>3)&1 inverts the alternation starting in August. return 30 + int((m+m>>3)&1) } + +// div divides t by d and returns the quotient parity and remainder. +// We don't use the quotient parity anymore (round half up instead of round to even) +// but it's still here in case we change our minds. +func div(t Time, d Duration) (qmod2 int, r Duration) { + neg := false + nsec := t.nsec + sec := t.sec + if sec < 0 { + // Operate on absolute value. + neg = true + sec = -sec + nsec = -nsec + if nsec < 0 { + nsec += 1e9 + sec-- // sec >= 1 before the -- so safe + } + } + + switch { + // Special case: 2d divides 1 second. + case d < Second && Second%(d+d) == 0: + qmod2 = int(nsec/int32(d)) & 1 + r = Duration(nsec % int32(d)) + + // Special case: d is a multiple of 1 second. + case d%Second == 0: + d1 := int64(d / Second) + qmod2 = int(sec/d1) & 1 + r = Duration(sec%d1)*Second + Duration(nsec) + + // General case. + // This could be faster if more cleverness were applied, + // but it's really only here to avoid special case restrictions in the API. + // No one will care about these cases. + default: + // Compute nanoseconds as 128-bit number. + sec := uint64(sec) + tmp := (sec >> 32) * 1e9 + u1 := tmp >> 32 + u0 := tmp << 32 + tmp = (sec & 0xFFFFFFFF) * 1e9 + u0x, u0 := u0, u0+tmp + if u0 < u0x { + u1++ + } + u0x, u0 = u0, u0+uint64(nsec) + if u0 < u0x { + u1++ + } + + // Compute remainder by subtracting r<>63 != 1 { + d1 <<= 1 + } + d0 := uint64(0) + for { + qmod2 = 0 + if u1 > d1 || u1 == d1 && u0 >= d0 { + // subtract + qmod2 = 1 + u0x, u0 = u0, u0-d0 + if u0 > u0x { + u1-- + } + u1 -= d1 + } + if d1 == 0 && d0 == uint64(d) { + break + } + d0 >>= 1 + d0 |= (d1 & 1) << 63 + d1 >>= 1 + } + r = Duration(u0) + } + + if neg && r != 0 { + // If input was negative and not an exact multiple of d, we computed q, r such that + // q*d + r = -t + // But the right answers are given by -(q-1), d-r: + // q*d + r = -t + // -q*d - r = t + // -(q-1)*d + (d - r) = t + qmod2 ^= 1 + r = d - r + } + return +} From 80cd87cec729e9752c8ef3799e46fe55256ac47e Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Thu, 24 Oct 2024 11:36:17 +0200 Subject: [PATCH 16/27] test: delete monotonic clock precision on z filetest --- .../gno.land/r/demo/boards/z_4_filetest.gno | 892 ------------------ .../gno.land/r/demo/groups/z_1_a_filetest.gno | 4 +- .../gno.land/r/demo/groups/z_2_a_filetest.gno | 2 +- 3 files changed, 3 insertions(+), 895 deletions(-) delete mode 100644 examples/gno.land/r/demo/boards/z_4_filetest.gno diff --git a/examples/gno.land/r/demo/boards/z_4_filetest.gno b/examples/gno.land/r/demo/boards/z_4_filetest.gno deleted file mode 100644 index f0620c28c9d..00000000000 --- a/examples/gno.land/r/demo/boards/z_4_filetest.gno +++ /dev/null @@ -1,892 +0,0 @@ -// PKGPATH: gno.land/r/demo/boards_test -package boards_test - -// SEND: 200000000ugnot - -import ( - "strconv" - - "gno.land/r/demo/boards" - "gno.land/r/demo/users" -) - -var ( - bid boards.BoardID - pid boards.PostID -) - -func init() { - users.Register("", "gnouser", "my profile") - - bid = boards.CreateBoard("test_board") - boards.CreateThread(bid, "First Post (title)", "Body of the first post. (body)") - pid = boards.CreateThread(bid, "Second Post (title)", "Body of the second post. (body)") - rid := boards.CreateReply(bid, pid, pid, "Reply of the second post") - println(rid) -} - -func main() { - rid2 := boards.CreateReply(bid, pid, pid, "Second reply of the second post") - println(rid2) - println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) -} - -// Output: -// 3 -// 4 -// # Second Post (title) -// -// Body of the second post. (body) -// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=2&body.type=textarea)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=2&title.type=textarea&body.type=textarea&dstBoardID.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] -// -// > Reply of the second post -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3&body.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] -// -// > Second reply of the second post -// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=4&body.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=4)] - -// Realm: -// switchrealm["gno.land/r/demo/users"] -// switchrealm["gno.land/r/demo/boards"] -// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111]={ -// "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111", -// "ModTime": "123", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123", -// "RefCount": "1" -// }, -// "Value": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "68663c8895d37d479e417c11e21badfe21345c61", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:112" -// } -// } -// } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "0000000004" -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/boards.Post" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "64" -// } -// }, -// { -// "N": "AQAAAAAAAAA=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// } -// ], -// "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125", -// "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124", -// "RefCount": "1" -// } -// } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124]={ -// "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124", -// "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123", -// "RefCount": "1" -// }, -// "Value": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "3f34ac77289aa1d5f9a2f8b6d083138325816fb0", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125" -// } -// } -// } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "0000000004" -// } -// }, -// {}, -// { -// "N": "AQAAAAAAAAA=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "64" -// } -// }, -// { -// "N": "AgAAAAAAAAA=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "94a6665a44bac6ede7f3e3b87173e537b12f9532", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "bc8e5b4e782a0bbc4ac9689681f119beb7b34d59", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124" -// }, -// "Index": "0", -// "TV": null -// } -// } -// ], -// "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123", -// "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122", -// "RefCount": "1" -// } -// } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122]={ -// "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122", -// "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106", -// "RefCount": "1" -// }, -// "Value": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "9957eadbc91dd32f33b0d815e041a32dbdea0671", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123" -// } -// } -// } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// } -// ], -// "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128", -// "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127", -// "RefCount": "1" -// } -// } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// } -// ], -// "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129", -// "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127", -// "RefCount": "1" -// } -// } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// } -// ], -// "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130", -// "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127", -// "RefCount": "1" -// } -// } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131]={ -// "Fields": [ -// { -// "N": "AAAAgJSeXbo=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "65536" -// } -// }, -// { -// "N": "AbSNdvQQIhE=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "1024" -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "time.Location" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "336074805fc853987abe6f7fe3ad97a6a6f3077a:2" -// }, -// "Index": "182", -// "TV": null -// } -// } -// ], -// "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131", -// "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127", -// "RefCount": "1" -// } -// } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "65536" -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "1024" -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "time.Location" -// } -// } -// } -// ], -// "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132", -// "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127", -// "RefCount": "1" -// } -// } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/boards.Board" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "N": "BAAAAAAAAAA=", -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/boards.PostID" -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "std.Address" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "" -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "Second reply of the second post" -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Tree" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "f91e355bd19240f0f3350a7fa0e6a82b72225916", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128" -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Tree" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "9ee9c4117be283fc51ffcc5ecd65b75ecef5a9dd", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129" -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Tree" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "eb768b0140a5fe95f9c58747f0960d647dacfd42", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130" -// } -// }, -// { -// "N": "AgAAAAAAAAA=", -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/boards.PostID" -// } -// }, -// { -// "N": "AgAAAAAAAAA=", -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/boards.PostID" -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/boards.BoardID" -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "time.Time" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "0fd3352422af0a56a77ef2c9e88f479054e3d51f", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131" -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "time.Time" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "bed4afa8ffdbbf775451c947fc68b27a345ce32a", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132" -// } -// } -// ], -// "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127", -// "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126", -// "RefCount": "1" -// } -// } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126]={ -// "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126", -// "IsEscaped": true, -// "ModTime": "0", -// "RefCount": "2" -// }, -// "Value": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/boards.Post" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "c45bbd47a46681a63af973db0ec2180922e4a8ae", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127" -// } -// } -// } -// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120]={ -// "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120", -// "ModTime": "134", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134", -// "RefCount": "1" -// }, -// "Value": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "dc1f011553dc53e7a846049e08cc77fa35ea6a51", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:121" -// } -// } -// } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "0000000004" -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/boards.Post" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Escaped": true, -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "64" -// } -// }, -// { -// "N": "AQAAAAAAAAA=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// } -// ], -// "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136", -// "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135", -// "RefCount": "1" -// } -// } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135]={ -// "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135", -// "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134", -// "RefCount": "1" -// }, -// "Value": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "96b86b4585c7f1075d7794180a5581f72733a7ab", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136" -// } -// } -// } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "0000000004" -// } -// }, -// {}, -// { -// "N": "AQAAAAAAAAA=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "64" -// } -// }, -// { -// "N": "AgAAAAAAAAA=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "32274e1f28fb2b97d67a1262afd362d370de7faa", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120" -// }, -// "Index": "0", -// "TV": null -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "c2cfd6aec36a462f35bf02e5bf4a127aa1bb7ac2", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135" -// }, -// "Index": "0", -// "TV": null -// } -// } -// ], -// "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134", -// "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133", -// "RefCount": "1" -// } -// } -// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133]={ -// "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133", -// "ModTime": "0", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107", -// "RefCount": "1" -// }, -// "Value": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "5cb875179e86d32c517322af7a323b2a5f3e6cc5", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134" -// } -// } -// } -// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85]={ -// "Fields": [ -// { -// "N": "AQAAAAAAAAA=", -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/boards.BoardID" -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "/r/demo/boards:test_board" -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "test_board" -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "std.Address" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Tree" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "a416a751c3a45a1e5cba11e737c51340b081e372", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:86" -// } -// }, -// { -// "N": "BAAAAAAAAAA=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "65536" -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "time.Time" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "36299fccbc13f2a84c4629fad4cb940f0bd4b1c6", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:87" -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Tree" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "af6ed0268f99b7f369329094eb6dfaea7812708b", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:88" -// } -// } -// ], -// "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85", -// "ModTime": "121", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84", -// "RefCount": "1" -// } -// } -// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "9809329dc1ddc5d3556f7a8fa3c2cebcbf65560b", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122" -// }, -// "Index": "0", -// "TV": null -// } -// } -// ], -// "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106", -// "ModTime": "121", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105", -// "RefCount": "1" -// } -// } -// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": { -// "@type": "/gno.RefValue", -// "Hash": "ceae9a1c4ed28bb51062e6ccdccfad0caafd1c4f", -// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133" -// }, -// "Index": "0", -// "TV": null -// } -// } -// ], -// "ObjectInfo": { -// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107", -// "ModTime": "121", -// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105", -// "RefCount": "1" -// } -// } -// switchrealm["gno.land/r/demo/boards"] -// switchrealm["gno.land/r/demo/users"] -// switchrealm["gno.land/r/demo/users"] -// switchrealm["gno.land/r/demo/users"] -// switchrealm["gno.land/r/demo/boards"] -// switchrealm["gno.land/r/demo/boards_test"] diff --git a/examples/gno.land/r/demo/groups/z_1_a_filetest.gno b/examples/gno.land/r/demo/groups/z_1_a_filetest.gno index aeff9ab7774..c752441e38a 100644 --- a/examples/gno.land/r/demo/groups/z_1_a_filetest.gno +++ b/examples/gno.land/r/demo/groups/z_1_a_filetest.gno @@ -69,10 +69,10 @@ func main() { // // Group Creator: gnouser0 // -// Group createdAt: 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001 +// Group createdAt: 2009-02-13 23:31:30 +0000 UTC // // Group Last MemberID: 0000000001 // // Group Members: // -// [0000000000, g1vahx7atnv4erxh6lta047h6lta047h6ll85gpy, 32, i am from UAE, 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001], +// [0000000000, g1vahx7atnv4erxh6lta047h6lta047h6ll85gpy, 32, i am from UAE, 2009-02-13 23:31:30 +0000 UTC], diff --git a/examples/gno.land/r/demo/groups/z_2_a_filetest.gno b/examples/gno.land/r/demo/groups/z_2_a_filetest.gno index d1cc53d612f..7cece2d3712 100644 --- a/examples/gno.land/r/demo/groups/z_2_a_filetest.gno +++ b/examples/gno.land/r/demo/groups/z_2_a_filetest.gno @@ -71,7 +71,7 @@ func main() { // // Group Creator: gnouser0 // -// Group createdAt: 2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001 +// Group createdAt: 2009-02-13 23:31:30 +0000 UTC // // Group Last MemberID: 0000000001 // From 8f54e28105cd212b4e65aeb5c0de095efd03c8c8 Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Thu, 24 Oct 2024 11:36:49 +0200 Subject: [PATCH 17/27] test: delete monotonic clock precision on z filetest --- examples/gno.land/r/gnoland/monit/monit_test.gno | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/gno.land/r/gnoland/monit/monit_test.gno b/examples/gno.land/r/gnoland/monit/monit_test.gno index fc9b394b8ed..f09b14e7b73 100644 --- a/examples/gno.land/r/gnoland/monit/monit_test.gno +++ b/examples/gno.land/r/gnoland/monit/monit_test.gno @@ -23,7 +23,7 @@ status=KO` Incr() { expected := `counter=3 -last update=2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001 +last update=2009-02-13 23:31:30 +0000 UTC last caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm status=OK` got := Render("") @@ -35,7 +35,7 @@ status=OK` use std.TestSkipTime(time.Hour) { expected := `counter=3 - last update=2009-02-13 22:31:30 +0000 UTC m=+1234564290.000000001 + last update=2009-02-13 22:31:30 +0000 UTC last caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm status=KO` got := Render("") @@ -46,7 +46,7 @@ status=OK` Incr() { expected := `counter=4 - last update=2009-02-13 23:31:30 +0000 UTC m=+1234567890.000000001 + last update=2009-02-13 23:31:30 +0000 UTC last caller=g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm status=OK` got := Render("") From 5008c6bf80e30ad77797973102c0cd52ac6992a2 Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Thu, 24 Oct 2024 14:02:30 +0200 Subject: [PATCH 18/27] test: fix test remove tz logic --- examples/gno.land/r/gnoland/events/events.gno | 6 ------ examples/gno.land/r/gnoland/events/events_test.gno | 3 --- 2 files changed, 9 deletions(-) diff --git a/examples/gno.land/r/gnoland/events/events.gno b/examples/gno.land/r/gnoland/events/events.gno index 0984edf75a9..9ff246ddf51 100644 --- a/examples/gno.land/r/gnoland/events/events.gno +++ b/examples/gno.land/r/gnoland/events/events.gno @@ -189,11 +189,5 @@ func parseTimes(startTime, endTime string) (time.Time, time.Time, error) { return time.Time{}, time.Time{}, ErrEndBeforeStart } - _, stOffset := st.Zone() - _, etOffset := et.Zone() - if stOffset != etOffset { - return time.Time{}, time.Time{}, ErrStartEndTimezonemMismatch - } - return st, et, nil } diff --git a/examples/gno.land/r/gnoland/events/events_test.gno b/examples/gno.land/r/gnoland/events/events_test.gno index 357857352d8..a8f6c6baef1 100644 --- a/examples/gno.land/r/gnoland/events/events_test.gno +++ b/examples/gno.land/r/gnoland/events/events_test.gno @@ -156,9 +156,6 @@ func TestParseTimes(t *testing.T) { _, _, err = parseTimes("2009-02-13T23:30:30Z", "2009-02-13T21:30:30Z") uassert.ErrorContains(t, err, ErrEndBeforeStart.Error()) - - _, _, err = parseTimes("2009-02-10T23:30:30+02:00", "2009-02-13T21:30:33+05:00") - uassert.ErrorContains(t, err, ErrStartEndTimezonemMismatch.Error()) } func TestRenderEventWidget(t *testing.T) { From 2edfd58ad41c4c9490b66edb86f96562911eb15c Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Thu, 24 Oct 2024 14:33:33 +0200 Subject: [PATCH 19/27] fix: add zone method to avoid breaking changes --- gnovm/stdlibs/time/time.gno | 663 +++++++++++++++++++++--------------- 1 file changed, 389 insertions(+), 274 deletions(-) diff --git a/gnovm/stdlibs/time/time.gno b/gnovm/stdlibs/time/time.gno index f038f9b6c03..175696ff74d 100644 --- a/gnovm/stdlibs/time/time.gno +++ b/gnovm/stdlibs/time/time.gno @@ -7,16 +7,35 @@ type Time struct { nsec int32 } +// unixSec returns the time's seconds since Jan 1 1970 (Unix time). func (t *Time) unixSec() int64 { return t.sec + internalToUnix } +// addSec adds d seconds to the time. +func (t *Time) addSec(d int64) { + // Check if the sum of t.sec and d overflows and handle it properly. + sum := t.sec + d + if (sum > t.sec) == (d > 0) { + t.sec = sum + } else if d > 0 { + t.sec = 1<<63 - 1 + } else { + t.sec = -(1<<63 - 1) + } +} + +// After reports whether the time instant t is after u. func (t Time) After(u Time) bool { return t.sec > u.sec || t.sec == u.sec && t.nsec > u.nsec } +// Before reports whether the time instant t is before u. func (t Time) Before(u Time) bool { return t.sec < u.sec || t.sec == u.sec && t.nsec < u.nsec } +// Equal reports whether t and u represent the same time instant. +// See the documentation on the Time type for the pitfalls of using == with +// Time values; most code should use Equal instead. func (t Time) Equal(u Time) bool { return t.sec == u.sec && t.nsec == u.nsec } @@ -38,6 +57,7 @@ const ( December ) +// String returns the English name of the month ("January", "February", ...). func (m Month) String() string { if January <= m && m <= December { return longMonthNames[m-1] @@ -47,6 +67,7 @@ func (m Month) String() string { return "%!Month(" + string(buf[n:]) + ")" } +// A Weekday specifies a day of the week (Sunday = 0, ...). type Weekday int const ( @@ -59,6 +80,7 @@ const ( Saturday ) +// String returns the English name of the day ("Sunday", "Monday", ...). func (d Weekday) String() string { if Sunday <= d && d <= Saturday { return longDayNames[d] @@ -68,6 +90,86 @@ func (d Weekday) String() string { return "%!Weekday(" + string(buf[n:]) + ")" } +// Computations on time. +// +// The zero value for a Time is defined to be +// January 1, year 1, 00:00:00.000000000 UTC +// which (1) looks like a zero, or as close as you can get in a date +// (1-1-1 00:00:00 UTC), (2) is unlikely enough to arise in practice to +// be a suitable "not set" sentinel, unlike Jan 1 1970, and (3) has a +// non-negative year even in time zones west of UTC, unlike 1-1-0 +// 00:00:00 UTC, which would be 12-31-(-1) 19:00:00 in New York. +// +// The zero Time value does not force a specific epoch for the time +// representation. For example, to use the Unix epoch internally, we +// could define that to distinguish a zero value from Jan 1 1970, that +// time would be represented by sec=-1, nsec=1e9. However, it does +// suggest a representation, namely using 1-1-1 00:00:00 UTC as the +// epoch, and that's what we do. +// +// The Add and Sub computations are oblivious to the choice of epoch. +// +// The presentation computations - year, month, minute, and so on - all +// rely heavily on division and modulus by positive constants. For +// calendrical calculations we want these divisions to round down, even +// for negative values, so that the remainder is always positive, but +// Go's division (like most hardware division instructions) rounds to +// zero. We can still do those computations and then adjust the result +// for a negative numerator, but it's annoying to write the adjustment +// over and over. Instead, we can change to a different epoch so long +// ago that all the times we care about will be positive, and then round +// to zero and round down coincide. These presentation routines already +// have to add the zone offset, so adding the translation to the +// alternate epoch is cheap. For example, having a non-negative time t +// means that we can write +// +// sec = t % 60 +// +// instead of +// +// sec = t % 60 +// if sec < 0 { +// sec += 60 +// } +// +// everywhere. +// +// The calendar runs on an exact 400 year cycle: a 400-year calendar +// printed for 1970-2369 will apply as well to 2370-2769. Even the days +// of the week match up. It simplifies the computations to choose the +// cycle boundaries so that the exceptional years are always delayed as +// long as possible. That means choosing a year equal to 1 mod 400, so +// that the first leap year is the 4th year, the first missed leap year +// is the 100th year, and the missed missed leap year is the 400th year. +// So we'd prefer instead to print a calendar for 2001-2400 and reuse it +// for 2401-2800. +// +// Finally, it's convenient if the delta between the Unix epoch and +// long-ago epoch is representable by an int64 constant. +// +// These three considerations—choose an epoch as early as possible, that +// uses a year equal to 1 mod 400, and that is no more than 2⁶³ seconds +// earlier than 1970—bring us to the year -292277022399. We refer to +// this year as the absolute zero year, and to times measured as a uint64 +// seconds since this year as absolute times. +// +// Times measured as an int64 seconds since the year 1—the representation +// used for Time's sec field—are called internal times. +// +// Times measured as an int64 seconds since the year 1970 are called Unix +// times. +// +// It is tempting to just use the year 1 as the absolute epoch, defining +// that the routines are only valid for years >= 1. However, the +// routines would then be invalid when displaying the epoch in time zones +// west of UTC, since it is year 0. It doesn't seem tenable to say that +// printing the zero time correctly isn't supported in half the time +// zones. By comparison, it's reasonable to mishandle some times in +// the year -292277022399. +// +// All this is opaque to clients of the API and can be changed if a +// better implementation presents itself. + const ( // The unsigned zero year for internal calculations. // Must be 1 mod 400, and times before it will not compute correctly, @@ -88,6 +190,8 @@ const ( wallToInternal int64 = (1884*365 + 1884/4 - 1884/100 + 1884/400) * secondsPerDay ) +// IsZero reports whether t represents the zero time instant, +// January 1, year 1, 00:00:00 UTC. func (t Time) IsZero() bool { return t.sec == 0 && t.nsec == 0 } @@ -123,10 +227,12 @@ func (t Time) Day() int { return day } +// Weekday returns the day of the week specified by t. func (t Time) Weekday() Weekday { return internalWeekday(t.internal()) } +// internalWeekday is like Weekday but operates on an internal time. func internalWeekday(it uint64) Weekday { sec := (it + uint64(Monday)*secondsPerDay) % secondsPerWeek return Weekday(int(sec) / secondsPerDay) @@ -202,164 +308,28 @@ func (t Time) YearDay() int { return yday + 1 } -// AddDate returns the time corresponding to adding the -// given number of years, months, and days to t. -// For example, AddDate(-1, 2, 3) applied to January 1, 2011 -// returns March 4, 2010. -// -// AddDate normalizes its result in the same way that Date does, -// so, for example, adding one month to October 31 yields -// December 1, the normalized form for November 31. -func (t Time) AddDate(years int, months int, days int) Time { - year, month, day := t.Date() - hour, min, sec := t.Clock() - return Date(year+years, month+Month(months), day+days, hour, min, sec, int(t.nsec)) -} - -func (t *Time) addSec(d int64) { - sum := t.sec + d - if (sum > t.sec) == (d > 0) { - t.sec = sum - } else if d > 0 { - t.sec = 1<<63 - 1 - } else { - t.sec = -(1<<63 - 1) - } -} +// A Duration represents the elapsed time between two instants +// as an int64 nanosecond count. The representation limits the +// largest representable duration to approximately 290 years. +type Duration int64 const ( - secondsPerMinute = 60 - secondsPerHour = 60 * secondsPerMinute - secondsPerDay = 24 * secondsPerHour - secondsPerWeek = 7 * secondsPerDay - daysPer400Years = 365*400 + 97 - daysPer100Years = 365*100 + 24 - daysPer4Years = 365*4 + 1 + minDuration Duration = -1 << 63 + maxDuration Duration = 1<<63 - 1 ) -func (t Time) date(full bool) (year int, month Month, day int, yday int) { - return internalDate(t.internal(), full) -} - -func internalDate(it uint64, full bool) (year int, month Month, day int, yday int) { - d := it / 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). -var daysBefore = [...]int32{ - 0, - 31, - 31 + 28, - 31 + 28 + 31, - 31 + 28 + 31 + 30, - 31 + 28 + 31 + 30 + 31, - 31 + 28 + 31 + 30 + 31 + 30, - 31 + 28 + 31 + 30 + 31 + 30 + 31, - 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, - 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, - 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, - 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, - 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, -} - -// daysSinceEpoch takes a year and returns the number of days from -// the absolute epoch to the start of that year. -// This is basically (year - zeroYear) * 365, but accounting for leap days. -func daysSinceEpoch(year int) uint64 { - y := uint64(int64(year) - absoluteZeroYear) - - // Add in days from 400-year cycles. - n := y / 400 - y -= 400 * n - d := daysPer400Years * n - - // Add in 100-year cycles. - n = y / 100 - y -= 100 * n - d += daysPer100Years * n - - // Add in 4-year cycles. - n = y / 4 - y -= 4 * n - d += daysPer4Years * n - - // Add in non-leap years. - n = y - d += 365 * n - - return d -} - +// Common durations. There is no definition for units of Day or larger +// to avoid confusion across daylight savings time zone transitions. +// +// To count the number of units in a Duration, divide: +// +// second := time.Second +// fmt.Print(int64(second/time.Millisecond)) // prints 1000 +// +// To convert an integer number of units to a Duration, multiply: +// +// seconds := 10 +// fmt.Print(time.Duration(seconds)*time.Second) // prints 10s const ( Nanosecond Duration = 1 Microsecond = 1000 * Nanosecond @@ -369,16 +339,6 @@ const ( Hour = 60 * Minute ) -// A Duration represents the elapsed time between two instants -// as an int64 nanosecond count. The representation limits the -// largest representable duration to approximately 290 years. -type Duration int64 - -const ( - minDuration Duration = -1 << 63 - maxDuration Duration = 1<<63 - 1 -) - // String returns a string representing the duration in the form "72h3m0.5s". // Leading zero units are omitted. As a special case, durations less than one // second format use a smaller unit (milli-, micro-, or nanoseconds) to ensure @@ -456,6 +416,46 @@ func (d Duration) String() string { return string(buf[w:]) } +// fmtFrac formats the fraction of v/10**prec (e.g., ".12345") into the +// tail of buf, omitting trailing zeros. It omits the decimal +// point too when the fraction is 0. It returns the index where the +// output bytes begin and the value v/10**prec. +func fmtFrac(buf []byte, v uint64, prec int) (nw int, nv uint64) { + // Omit trailing zeros up to and including decimal point. + w := len(buf) + isprint := false + for i := 0; i < prec; i++ { + digit := v % 10 + isprint = isprint || digit != 0 + if isprint { + w-- + buf[w] = byte(digit) + '0' + } + v /= 10 + } + if isprint { + w-- + buf[w] = '.' + } + return w, v +} + +// add v at the end of buf and return the index where the number starts in buf +func fmtInt(buf []byte, v uint64) int { + w := len(buf) + if v == 0 { + w-- + buf[w] = '0' + } else { + for v > 0 { + w-- + buf[w] = byte(v%10) + '0' + v /= 10 + } + } + return w +} + // Nanoseconds returns the duration as an integer nanosecond count. func (d Duration) Nanoseconds() int64 { return int64(d) } @@ -540,6 +540,19 @@ func (d Duration) Round(m Duration) Duration { return maxDuration // overflow } +// Abs returns the absolute value of d. +// As a special case, math.MinInt64 is converted to math.MaxInt64. +func (d Duration) Abs() Duration { + switch { + case d >= 0: + return d + case d == minDuration: + return maxDuration + default: + return -d + } +} + // Add returns the time t+d func (t Time) Add(d Duration) Time { dsec := int64(d / 1e9) @@ -589,78 +602,182 @@ func Until(t Time) Duration { return t.Sub(Now()) } -// Date returns the Time corresponding to -// -// yyyy-mm-dd hh:mm:ss + nsec nanoseconds +// AddDate returns the time corresponding to adding the +// given number of years, months, and days to t. +// For example, AddDate(-1, 2, 3) applied to January 1, 2011 +// returns March 4, 2010. // -// The month, day, hour, min, sec, and nsec values may be outside -// their usual ranges and will be normalized during the conversion. -// For example, October 32 converts to November 1. -func Date(year int, month Month, day, hour, min, sec, nsec int) Time { - // Normalize month, overflowing into year. - m := int(month) - 1 - year, m = norm(year, m, 12) - month = Month(m) + 1 +// AddDate normalizes its result in the same way that Date does, +// so, for example, adding one month to October 31 yields +// December 1, the normalized form for November 31. +func (t Time) AddDate(years int, months int, days int) Time { + year, month, day := t.Date() + hour, min, sec := t.Clock() + return Date(year+years, month+Month(months), day+days, hour, min, sec, int(t.nsec)) +} - // Normalize nsec, sec, min, hour, overflowing into day. - sec, nsec = norm(sec, nsec, 1e9) - min, sec = norm(min, sec, 60) - hour, min = norm(hour, min, 60) - day, hour = norm(day, hour, 24) +const ( + secondsPerMinute = 60 + secondsPerHour = 60 * secondsPerMinute + secondsPerDay = 24 * secondsPerHour + secondsPerWeek = 7 * secondsPerDay + daysPer400Years = 365*400 + 97 + daysPer100Years = 365*100 + 24 + daysPer4Years = 365*4 + 1 +) - // Compute days since the absolute epoch. - d := daysSinceEpoch(year) +// date computes the year, day of year, and when full=true, +// the month and day in which t occurs. +func (t Time) date(full bool) (year int, month Month, day int, yday int) { + return internalDate(t.internal(), full) +} - // Add in days before this month. - d += uint64(daysBefore[month-1]) - if isLeap(year) && month >= March { - d++ // February 29 +// internalDate is like date but operates on an internal time. +func internalDate(it uint64, full bool) (year int, month Month, day int, yday int) { + d := it / 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 } - // Add in days before today. - d += uint64(day - 1) + 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 + } + } - // Add in time elapsed today. - abs := d * secondsPerDay - abs += uint64(hour*secondsPerHour + min*secondsPerMinute + sec) + // 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]) + } - unix := int64(abs) + (absoluteToInternal + internalToUnix) - t := unixTime(unix, int32(nsec)) - return t + month++ // because January is 1 + day = day - begin + 1 + return } -// Abs returns the absolute value of d. -// As a special case, math.MinInt64 is converted to math.MaxInt64. -func (d Duration) Abs() Duration { - switch { - case d >= 0: - return d - case d == minDuration: - return maxDuration - default: - return -d - } +// 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). +var daysBefore = [...]int32{ + 0, + 31, + 31 + 28, + 31 + 28 + 31, + 31 + 28 + 31 + 30, + 31 + 28 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, } -func (t Time) Round(d Duration) Time { - if d <= 0 { - return t - } - _, r := div(t, d) - if lessThanHalf(r, d) { - return t.Add(-r) +func daysIn(m Month, year int) int { + if m == February && isLeap(year) { + return 29 } - return t.Add(d - r) + return int(daysBefore[m] - daysBefore[m-1]) +} + +// daysSinceEpoch takes a year and returns the number of days from +// the absolute epoch to the start of that year. +// This is basically (year - zeroYear) * 365, but accounting for leap days. +func daysSinceEpoch(year int) uint64 { + y := uint64(int64(year) - absoluteZeroYear) + + // Add in days from 400-year cycles. + n := y / 400 + y -= 400 * n + d := daysPer400Years * n + + // Add in 100-year cycles. + n = y / 100 + y -= 100 * n + d += daysPer100Years * n + + // Add in 4-year cycles. + n = y / 4 + y -= 4 * n + d += daysPer4Years * n + + // Add in non-leap years. + n = y + d += 365 * n + + return d } func now() (sec int64, nsec int32, mono int64) // injected by runtime +// Now returns the current UTC time. func Now() Time { sec, nsec, _ := now() sec += unixToInternal return Time{sec, nsec} } +func unixTime(sec int64, nsec int32) Time { + return Time{sec + unixToInternal, nsec} +} + +// This method is here to avoid breaking changes but always return UTC and offset 0 +func (t Time) Zone() (name string, offset int) { + name, offset = "UTC", 0 + return +} + // Unix returns t as a Unix time, the number of seconds elapsed // since January 1, 1970 UTC. The result does not depend on the // location associated with t. @@ -793,12 +910,6 @@ func (t *Time) UnmarshalText(data []byte) error { return err } -// Zone is here to avoid breaking changes, but return UTC and offset 0. -func (t Time) Zone() (name string, offset int) { - name, offset = "UTC", 0 - return -} - // Unix returns the local Time corresponding to the given Unix time, // sec seconds and nsec nanoseconds since January 1, 1970 UTC. // It is valid to pass nsec outside the range [0, 999999999]. @@ -829,54 +940,10 @@ func UnixMicro(usec int64) Time { return Unix(usec/1e6, (usec%1e6)*1e3) } -func unixTime(sec int64, nsec int32) Time { - return Time{sec + unixToInternal, nsec} -} - func isLeap(year int) bool { return year%4 == 0 && (year%100 != 0 || year%400 == 0) } -// fmtFrac formats the fraction of v/10**prec (e.g., ".12345") into the -// tail of buf, omitting trailing zeros. It omits the decimal -// point too when the fraction is 0. It returns the index where the -// output bytes begin and the value v/10**prec. -func fmtFrac(buf []byte, v uint64, prec int) (nw int, nv uint64) { - // Omit trailing zeros up to and including decimal point. - w := len(buf) - isprint := false - for i := 0; i < prec; i++ { - digit := v % 10 - isprint = isprint || digit != 0 - if isprint { - w-- - buf[w] = byte(digit) + '0' - } - v /= 10 - } - if isprint { - w-- - buf[w] = '.' - } - return w, v -} - -// add v at the end of buf and return the index where the number starts in buf -func fmtInt(buf []byte, v uint64) int { - w := len(buf) - if v == 0 { - w-- - buf[w] = '0' - } else { - for v > 0 { - w-- - buf[w] = byte(v%10) + '0' - v /= 10 - } - } - return w -} - // norm returns nhi, nlo such that // // hi * base + lo == nhi * base + nlo @@ -895,18 +962,66 @@ func norm(hi, lo, base int) (nhi, nlo int) { return hi, lo } -func daysIn(m Month, year int) int { - if m == February { - if isLeap(year) { - return 29 - } - return 28 +// Date returns the Time corresponding to +// +// yyyy-mm-dd hh:mm:ss + nsec nanoseconds +// +// The month, day, hour, min, sec, and nsec values may be outside +// their usual ranges and will be normalized during the conversion. +// For example, October 32 converts to November 1. +func Date(year int, month Month, day, hour, min, sec, nsec int) Time { + // Normalize month, overflowing into year. + m := int(month) - 1 + year, m = norm(year, m, 12) + month = Month(m) + 1 + + // Normalize nsec, sec, min, hour, overflowing into day. + sec, nsec = norm(sec, nsec, 1e9) + min, sec = norm(min, sec, 60) + hour, min = norm(hour, min, 60) + day, hour = norm(day, hour, 24) + + // Compute days since the absolute epoch. + d := daysSinceEpoch(year) + + // Add in days before this month. + d += uint64(daysBefore[month-1]) + if isLeap(year) && month >= March { + d++ // February 29 } - // With the special case of February eliminated, the pattern is - // 31 30 31 30 31 30 31 31 30 31 30 31 - // Adding m&1 produces the basic alternation; - // adding (m>>3)&1 inverts the alternation starting in August. - return 30 + int((m+m>>3)&1) + + // Add in days before today. + d += uint64(day - 1) + + // Add in time elapsed today. + abs := d * secondsPerDay + abs += uint64(hour*secondsPerHour + min*secondsPerMinute + sec) + + unix := int64(abs) + (absoluteToInternal + internalToUnix) + t := unixTime(unix, int32(nsec)) + return t +} + +// Truncate returns the result of rounding t down to a multiple of d (since the zero time). +func (t Time) Truncate(d Duration) Time { + if d <= 0 { + return t + } + _, r := div(t, d) + return t.Add(-r) +} + +// Round returns the result of rounding t to the nearest multiple of d (since the zero time). +// The rounding behavior for halfway values is to round up. +func (t Time) Round(d Duration) Time { + if d <= 0 { + return t + } + _, r := div(t, d) + if lessThanHalf(r, d) { + return t.Add(-r) + } + return t.Add(d - r) } // div divides t by d and returns the quotient parity and remainder. From 305a587af307d86ea7ec8e1a2c7934eb104ad923 Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Thu, 24 Oct 2024 15:13:39 +0200 Subject: [PATCH 20/27] test: fix adapt number of parameters expected --- gnovm/tests/files/fun22.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/tests/files/fun22.gno b/gnovm/tests/files/fun22.gno index 671f2d1a1ed..0ad30a352ac 100644 --- a/gnovm/tests/files/fun22.gno +++ b/gnovm/tests/files/fun22.gno @@ -7,4 +7,4 @@ func main() { } // Error: -// main/files/fun22.gno:6:2: wrong argument count in call to time.Date; want 8 got 0 +// main/files/fun22.gno:6:2: wrong argument count in call to time.Date; want 7 got 0 From 4377e1d20b19b6dbd68e2819e4fb2af671139012 Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Thu, 24 Oct 2024 15:21:06 +0200 Subject: [PATCH 21/27] test: fix adapt number of parameters expected --- gnovm/tests/files/fun22.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/tests/files/fun22.gno b/gnovm/tests/files/fun22.gno index 0ad30a352ac..671f2d1a1ed 100644 --- a/gnovm/tests/files/fun22.gno +++ b/gnovm/tests/files/fun22.gno @@ -7,4 +7,4 @@ func main() { } // Error: -// main/files/fun22.gno:6:2: wrong argument count in call to time.Date; want 7 got 0 +// main/files/fun22.gno:6:2: wrong argument count in call to time.Date; want 8 got 0 From 7d8787856f15753258f0eba4b28ab01a58165c8c Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Thu, 24 Oct 2024 15:25:45 +0200 Subject: [PATCH 22/27] test: transform Date to keep the same number of parameters but don't use location param --- gnovm/stdlibs/time/time.gno | 3 ++- gnovm/tests/files/time13_stdlibs.gno | 4 ++-- gnovm/tests/files/time1_stdlibs.gno | 2 +- gnovm/tests/files/time2_stdlibs.gno | 2 +- gnovm/tests/files/time3_stdlibs.gno | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/gnovm/stdlibs/time/time.gno b/gnovm/stdlibs/time/time.gno index 175696ff74d..51419116c03 100644 --- a/gnovm/stdlibs/time/time.gno +++ b/gnovm/stdlibs/time/time.gno @@ -969,7 +969,8 @@ func norm(hi, lo, base int) (nhi, nlo int) { // The month, day, hour, min, sec, and nsec values may be outside // their usual ranges and will be normalized during the conversion. // For example, October 32 converts to November 1. -func Date(year int, month Month, day, hour, min, sec, nsec int) Time { +// Location is always UTC, regardless of the given time zone. +func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time { // Normalize month, overflowing into year. m := int(month) - 1 year, m = norm(year, m, 12) diff --git a/gnovm/tests/files/time13_stdlibs.gno b/gnovm/tests/files/time13_stdlibs.gno index 25e6816d207..1c2d1d868d1 100644 --- a/gnovm/tests/files/time13_stdlibs.gno +++ b/gnovm/tests/files/time13_stdlibs.gno @@ -6,10 +6,10 @@ import ( var dummy = 1 -var t time.Time = time.Date(2007, time.November, 10, 23, 4, 5, 0) +var t time.Time = time.Date(2007, time.November, 10, 23, 4, 5, 0, time.UTC) func main() { - t = time.Date(2009, time.November, 10, 23, 4, 5, 0) + t = time.Date(2009, time.November, 10, 23, 4, 5, 0, time.UTC) println(t.Clock()) } diff --git a/gnovm/tests/files/time1_stdlibs.gno b/gnovm/tests/files/time1_stdlibs.gno index 0c530bb5353..2250bb5bdaf 100644 --- a/gnovm/tests/files/time1_stdlibs.gno +++ b/gnovm/tests/files/time1_stdlibs.gno @@ -5,7 +5,7 @@ import ( ) func main() { - t := time.Date(2009, time.November, 10, 23, 4, 5, 0) + t := time.Date(2009, time.November, 10, 23, 4, 5, 0, time.UTC) m := t.Minute() println(t, m) } diff --git a/gnovm/tests/files/time2_stdlibs.gno b/gnovm/tests/files/time2_stdlibs.gno index 8a5c7a37cbe..054a67c18d8 100644 --- a/gnovm/tests/files/time2_stdlibs.gno +++ b/gnovm/tests/files/time2_stdlibs.gno @@ -5,7 +5,7 @@ import ( ) func main() { - t := time.Date(2009, time.November, 10, 23, 4, 5, 0) + t := time.Date(2009, time.November, 10, 23, 4, 5, 0, time.UTC) h, m, s := t.Clock() println(h, m, s) } diff --git a/gnovm/tests/files/time3_stdlibs.gno b/gnovm/tests/files/time3_stdlibs.gno index 24a61aca4bb..b2d0dc5b123 100644 --- a/gnovm/tests/files/time3_stdlibs.gno +++ b/gnovm/tests/files/time3_stdlibs.gno @@ -6,7 +6,7 @@ import ( // FIXME related to named returns func main() { - t := time.Date(2009, time.November, 10, 23, 4, 5, 0) + t := time.Date(2009, time.November, 10, 23, 4, 5, 0, time.UTC) println(t.Clock()) } From 350df9bf0e68154e5209ba7a1e37eb679cf8e653 Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Thu, 24 Oct 2024 15:30:04 +0200 Subject: [PATCH 23/27] test: adapt Date to take location even if not used to keep same API as go --- gnovm/stdlibs/time/format.gno | 10 +++-- gnovm/stdlibs/time/time.gno | 2 +- gnovm/test.txt | 83 +++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 gnovm/test.txt diff --git a/gnovm/stdlibs/time/format.gno b/gnovm/stdlibs/time/format.gno index 5f83642fbff..a0e32626dc2 100644 --- a/gnovm/stdlibs/time/format.gno +++ b/gnovm/stdlibs/time/format.gno @@ -4,7 +4,9 @@ package time -import "errors" +import ( + "errors" +) // These are predefined layouts for use in Time.Format and time.Parse. // The reference time used in these layouts is the specific time stamp: @@ -1216,7 +1218,7 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error) } if zoneOffset != -1 { - t := Date(year, Month(month), day, hour, min, sec, nsec) + t := Date(year, Month(month), day, hour, min, sec, nsec, UTC) t.addSec(-int64(zoneOffset)) // Look for local zone with the given offset. @@ -1225,7 +1227,7 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error) } if zoneName != "" { - t := Date(year, Month(month), day, hour, min, sec, nsec) + t := Date(year, Month(month), day, hour, min, sec, nsec, UTC) // Look for local zone with the given offset. // If that zone was in effect at the given time, use it. offset, ok := local.lookupName(zoneName, t.unixSec()) @@ -1238,7 +1240,7 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error) } // Otherwise, fall back to default. - return Date(year, Month(month), day, hour, min, sec, nsec), nil + return Date(year, Month(month), day, hour, min, sec, nsec, UTC), nil } // parseTimeZone parses a time zone string and returns its length. Time zones diff --git a/gnovm/stdlibs/time/time.gno b/gnovm/stdlibs/time/time.gno index 51419116c03..d30a5e349fe 100644 --- a/gnovm/stdlibs/time/time.gno +++ b/gnovm/stdlibs/time/time.gno @@ -613,7 +613,7 @@ func Until(t Time) Duration { func (t Time) AddDate(years int, months int, days int) Time { year, month, day := t.Date() hour, min, sec := t.Clock() - return Date(year+years, month+Month(months), day+days, hour, min, sec, int(t.nsec)) + return Date(year+years, month+Month(months), day+days, hour, min, sec, int(t.nsec), UTC) } const ( diff --git a/gnovm/test.txt b/gnovm/test.txt new file mode 100644 index 00000000000..0abebb16ee5 --- /dev/null +++ b/gnovm/test.txt @@ -0,0 +1,83 @@ +=== RUN TestFilesNative + file_test.go:26: skipping test l2_long.gno in short mode. + file_test.go:26: skipping test l3_long.gno in short mode. + file_test.go:26: skipping test l4_long.gno in short mode. + file_test.go:26: skipping test l5_long.gno in short mode. + file_test.go:26: skipping test xfactor_long.gno in short mode. + file_test.go:26: skipping test xfib_long.gno in short mode. + file_test.go:26: skipping test zsolitaire_long.gno in short mode. +=== RUN TestFilesNative/time0_native.gno +=== RUN TestFilesNative/time11_native.gno +=== RUN TestFilesNative/time12_native.gno +=== RUN TestFilesNative/time13_native.gno +=== RUN TestFilesNative/time14_native.gno +=== RUN TestFilesNative/time15.gno +=== RUN TestFilesNative/time16_native.gno +OUTPUT: + +=== RUN TestFilesNative/time17_native.gno +Machine.RunMain() panic: time: missing Location in call to Time.In +Machine State:Machine: + CheckTypes: false + Op: [OpHalt OpPopResults OpExec OpBody OpPopResults] + Values: (len: 1) + #0 (main func()()) + Exprs: + Stmts: + #1 bodyStmt[0/0/2]=(end) + #0 return + Blocks: + @(76) Block(ID:0000000000000000000000000000000000000000:0,Addr:0xc0005b9680,Source:func main() { now ...,Parent:0xc0005b92c0) + now: (gonative{1970-01-01 00:00:00 +0000 UTC} gonative{time.Time}) + (s vals) @(76) Block(ID:0000000000000000000000000000000000000000:0,Addr:0xc0002ab030,Source:func main() { now ...,Parent:0xc0005b4630) + now: (nil gonative{time.Time}) + (s typs) @(76) [gonative{time.Time}] + Blocks (other): + Frames: + #1 [FRAME GOFUNC:0x4f8540 RECV:(undefined) (1 args) 5/1/0/2/2] + #0 [FRAME FUNC:main RECV:(undefined) (0 args) 2/0/0/0/1 LASTPKG:main LASTRLM:Realm(nil)] + Exceptions: + +Stacktrace: now.In(gonative{*time.Location}) + gofunction:func(*time.Location) time.Time +main() + main/files/time17_native.gno:10 + +OUTPUT: + +=== RUN TestFilesNative/time1_native.gno +=== RUN TestFilesNative/time2_native.gno +=== RUN TestFilesNative/time3_native.gno +=== RUN TestFilesNative/time4_native.gno +=== RUN TestFilesNative/time6_native.gno +=== RUN TestFilesNative/time7_native.gno +=== RUN TestFilesNative/time8.gno +=== RUN TestFilesNative/time9_native.gno +--- PASS: TestFilesNative (0.01s) + --- PASS: TestFilesNative/time0_native.gno (0.00s) + --- PASS: TestFilesNative/time11_native.gno (0.00s) + --- PASS: TestFilesNative/time12_native.gno (0.00s) + --- PASS: TestFilesNative/time13_native.gno (0.00s) + --- PASS: TestFilesNative/time14_native.gno (0.00s) + --- PASS: TestFilesNative/time15.gno (0.00s) + --- PASS: TestFilesNative/time16_native.gno (0.00s) + --- PASS: TestFilesNative/time17_native.gno (0.00s) + --- PASS: TestFilesNative/time1_native.gno (0.00s) + --- PASS: TestFilesNative/time2_native.gno (0.00s) + --- PASS: TestFilesNative/time3_native.gno (0.00s) + --- PASS: TestFilesNative/time4_native.gno (0.00s) + --- PASS: TestFilesNative/time6_native.gno (0.00s) + --- PASS: TestFilesNative/time7_native.gno (0.00s) + --- PASS: TestFilesNative/time8.gno (0.00s) + --- PASS: TestFilesNative/time9_native.gno (0.00s) +=== RUN TestFiles + file_test.go:32: skipping test l2_long.gno in short mode. + file_test.go:32: skipping test l3_long.gno in short mode. + file_test.go:32: skipping test l4_long.gno in short mode. + file_test.go:32: skipping test l5_long.gno in short mode. + file_test.go:32: skipping test xfactor_long.gno in short mode. + file_test.go:32: skipping test xfib_long.gno in short mode. + file_test.go:32: skipping test zsolitaire_long.gno in short mode. +=== RUN TestFiles/time0_stdlibs.gno +signal: interrupt +FAIL command-line-arguments 0.077s From c0df26f00484d3e7356bb08b5469f0a3d13a53ff Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Thu, 24 Oct 2024 15:32:27 +0200 Subject: [PATCH 24/27] chore: remove tmp file test.txt --- gnovm/test.txt | 83 -------------------------------------------------- 1 file changed, 83 deletions(-) delete mode 100644 gnovm/test.txt diff --git a/gnovm/test.txt b/gnovm/test.txt deleted file mode 100644 index 0abebb16ee5..00000000000 --- a/gnovm/test.txt +++ /dev/null @@ -1,83 +0,0 @@ -=== RUN TestFilesNative - file_test.go:26: skipping test l2_long.gno in short mode. - file_test.go:26: skipping test l3_long.gno in short mode. - file_test.go:26: skipping test l4_long.gno in short mode. - file_test.go:26: skipping test l5_long.gno in short mode. - file_test.go:26: skipping test xfactor_long.gno in short mode. - file_test.go:26: skipping test xfib_long.gno in short mode. - file_test.go:26: skipping test zsolitaire_long.gno in short mode. -=== RUN TestFilesNative/time0_native.gno -=== RUN TestFilesNative/time11_native.gno -=== RUN TestFilesNative/time12_native.gno -=== RUN TestFilesNative/time13_native.gno -=== RUN TestFilesNative/time14_native.gno -=== RUN TestFilesNative/time15.gno -=== RUN TestFilesNative/time16_native.gno -OUTPUT: - -=== RUN TestFilesNative/time17_native.gno -Machine.RunMain() panic: time: missing Location in call to Time.In -Machine State:Machine: - CheckTypes: false - Op: [OpHalt OpPopResults OpExec OpBody OpPopResults] - Values: (len: 1) - #0 (main func()()) - Exprs: - Stmts: - #1 bodyStmt[0/0/2]=(end) - #0 return - Blocks: - @(76) Block(ID:0000000000000000000000000000000000000000:0,Addr:0xc0005b9680,Source:func main() { now ...,Parent:0xc0005b92c0) - now: (gonative{1970-01-01 00:00:00 +0000 UTC} gonative{time.Time}) - (s vals) @(76) Block(ID:0000000000000000000000000000000000000000:0,Addr:0xc0002ab030,Source:func main() { now ...,Parent:0xc0005b4630) - now: (nil gonative{time.Time}) - (s typs) @(76) [gonative{time.Time}] - Blocks (other): - Frames: - #1 [FRAME GOFUNC:0x4f8540 RECV:(undefined) (1 args) 5/1/0/2/2] - #0 [FRAME FUNC:main RECV:(undefined) (0 args) 2/0/0/0/1 LASTPKG:main LASTRLM:Realm(nil)] - Exceptions: - -Stacktrace: now.In(gonative{*time.Location}) - gofunction:func(*time.Location) time.Time -main() - main/files/time17_native.gno:10 - -OUTPUT: - -=== RUN TestFilesNative/time1_native.gno -=== RUN TestFilesNative/time2_native.gno -=== RUN TestFilesNative/time3_native.gno -=== RUN TestFilesNative/time4_native.gno -=== RUN TestFilesNative/time6_native.gno -=== RUN TestFilesNative/time7_native.gno -=== RUN TestFilesNative/time8.gno -=== RUN TestFilesNative/time9_native.gno ---- PASS: TestFilesNative (0.01s) - --- PASS: TestFilesNative/time0_native.gno (0.00s) - --- PASS: TestFilesNative/time11_native.gno (0.00s) - --- PASS: TestFilesNative/time12_native.gno (0.00s) - --- PASS: TestFilesNative/time13_native.gno (0.00s) - --- PASS: TestFilesNative/time14_native.gno (0.00s) - --- PASS: TestFilesNative/time15.gno (0.00s) - --- PASS: TestFilesNative/time16_native.gno (0.00s) - --- PASS: TestFilesNative/time17_native.gno (0.00s) - --- PASS: TestFilesNative/time1_native.gno (0.00s) - --- PASS: TestFilesNative/time2_native.gno (0.00s) - --- PASS: TestFilesNative/time3_native.gno (0.00s) - --- PASS: TestFilesNative/time4_native.gno (0.00s) - --- PASS: TestFilesNative/time6_native.gno (0.00s) - --- PASS: TestFilesNative/time7_native.gno (0.00s) - --- PASS: TestFilesNative/time8.gno (0.00s) - --- PASS: TestFilesNative/time9_native.gno (0.00s) -=== RUN TestFiles - file_test.go:32: skipping test l2_long.gno in short mode. - file_test.go:32: skipping test l3_long.gno in short mode. - file_test.go:32: skipping test l4_long.gno in short mode. - file_test.go:32: skipping test l5_long.gno in short mode. - file_test.go:32: skipping test xfactor_long.gno in short mode. - file_test.go:32: skipping test xfib_long.gno in short mode. - file_test.go:32: skipping test zsolitaire_long.gno in short mode. -=== RUN TestFiles/time0_stdlibs.gno -signal: interrupt -FAIL command-line-arguments 0.077s From d12e53d65c15f605aaeb2ed724a0dd70c7b0f071 Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Thu, 24 Oct 2024 15:41:27 +0200 Subject: [PATCH 25/27] test: remove tz_location file test --- gnovm/tests/files/tz_locations.gno | 117 ----------------------------- 1 file changed, 117 deletions(-) delete mode 100644 gnovm/tests/files/tz_locations.gno diff --git a/gnovm/tests/files/tz_locations.gno b/gnovm/tests/files/tz_locations.gno deleted file mode 100644 index 83ba201bbeb..00000000000 --- a/gnovm/tests/files/tz_locations.gno +++ /dev/null @@ -1,117 +0,0 @@ -package main - -import "time" - -func main() { - const layout = "2006-01-02 15:04:05 -0700 MST" - - loc, err := time.LoadLocation("America/New_York") - if err != nil { - panic(err) - } - - // US eastern war time - t := time.Date(1944, time.August, 15, 0, 0, 0, 0, loc) - println(t.Format(layout)) - - // Us eastern peace time - t = t.Add(24 * time.Hour * 30 * 13) - println(t.Format(layout)) - - loc, err = time.LoadLocation("America/Chicago") - if err != nil { - panic(err) - } - - // US central time - t = time.Date(1935, time.April, 5, 11, 11, 11, 0, loc) - println(t.Format(layout)) - - // US eastern time for a bit - t = t.Add(24 * time.Hour * 365) - println(t.Format(layout)) - - // They didn't like it -- stayed light too late -- back to central - t = t.Add(24 * time.Hour * 365) - println(t.Format(layout)) - - loc, err = time.LoadLocation("Asia/Kathmandu") - if err != nil { - panic(err) - } - - // Nepalese time -- :30 off the hour - t = time.Date(1985, time.September, 17, 12, 12, 12, 0, loc) - println(t.Format(layout)) - - // :30 off the hour is too hard so let's change it to on the hour. - // Wait, no, let's switch to :45 off the hour, for convenience :) - t = t.Add(24 * time.Hour * 365) - println(t.Format(layout)) - - loc, err = time.LoadLocation("Pacific/Kwajalein") - if err != nil { - panic(err) - } - - // Marshall Islands -- where the world's day ends - t = time.Date(1993, time.July, 4, 8, 0, 0, 0, loc) - println(t.Format(layout)) - - // They didn't like that. They want to be where the world's day begins. - t = t.Add(24 * time.Hour * 60) - println(t.Format(layout)) - - loc, err = time.LoadLocation("Pacific/Guam") - if err != nil { - panic(err) - } - - // Guam - t = time.Date(1999, time.December, 25, 12, 0, 0, 0, loc) - println(t.Format(layout)) - - // Sometimes you want to change your timezone abbreviation for the sake of national identity. - // A merry Christmas indeed! - t = t.Add(24 * time.Hour * 365) - println(t.Format(layout)) - - loc, err = time.LoadLocation("Europe/Paris") - if err != nil { - panic(err) - } - - // Paris -- days of yore -- local mean time, determined by longitude - t = time.Date(1891, time.February, 14, 9, 0, 0, 0, loc) - println(t.Format(layout)) - - // Paris mean time - t = t.Add(24 * time.Hour * 365) - println(t.Format(layout)) - - // Paris in the present -- CET -- a month earlier than the original date - // due to 130 years worth of leap days. - t = t.Add(24 * time.Hour * 365 * 130) - println(t.Format(layout)) - - // Paris in the summer -- CEST - t = t.Add(24 * time.Hour * 30 * 5) - println(t.Format(layout)) -} - -// Output: -// 1944-08-15 00:00:00 -0400 EWT -// 1945-09-09 00:00:00 -0400 EPT -// 1935-04-05 11:11:11 -0600 CST -// 1936-04-04 12:11:11 -0500 EST -// 1937-04-04 11:11:11 -0600 CST -// 1985-09-17 12:12:12 +0530 +0530 -// 1986-09-17 12:27:12 +0545 +0545 -// 1993-07-04 08:00:00 -1200 -12 -// 1993-09-03 08:00:00 +1200 +12 -// 1999-12-25 12:00:00 +1000 GST -// 2000-12-24 12:00:00 +1000 ChST -// 1891-02-14 09:00:00 +0009 LMT -// 1892-02-14 09:00:00 +0009 PMT -// 2022-01-13 09:50:39 +0100 CET -// 2022-06-12 10:50:39 +0200 CEST From fc130f607144deca9d05fa39088e0d43cfacec7e Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Thu, 24 Oct 2024 19:26:32 +0200 Subject: [PATCH 26/27] test: fix test --- .../gno.land/r/demo/boards/z_4_filetest.gno | 863 ++++++++++++++++++ 1 file changed, 863 insertions(+) create mode 100644 examples/gno.land/r/demo/boards/z_4_filetest.gno diff --git a/examples/gno.land/r/demo/boards/z_4_filetest.gno b/examples/gno.land/r/demo/boards/z_4_filetest.gno new file mode 100644 index 00000000000..56b11a4a231 --- /dev/null +++ b/examples/gno.land/r/demo/boards/z_4_filetest.gno @@ -0,0 +1,863 @@ +// PKGPATH: gno.land/r/demo/boards_test +package boards_test + +// SEND: 200000000ugnot + +import ( + "strconv" + + "gno.land/r/demo/boards" + "gno.land/r/demo/users" +) + +var ( + bid boards.BoardID + pid boards.PostID +) + +func init() { + users.Register("", "gnouser", "my profile") + + bid = boards.CreateBoard("test_board") + boards.CreateThread(bid, "First Post (title)", "Body of the first post. (body)") + pid = boards.CreateThread(bid, "Second Post (title)", "Body of the second post. (body)") + rid := boards.CreateReply(bid, pid, pid, "Reply of the second post") + println(rid) +} + +func main() { + rid2 := boards.CreateReply(bid, pid, pid, "Second reply of the second post") + println(rid2) + println(boards.Render("test_board/" + strconv.Itoa(int(pid)))) +} + +// Output: +// 3 +// 4 +// # Second Post (title) +// +// Body of the second post. (body) +// \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=2&body.type=textarea)] \[[repost](/r/demo/boards?help&__func=CreateRepost&bid=1&postid=2&title.type=textarea&body.type=textarea&dstBoardID.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] +// +// > Reply of the second post +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/3) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3&body.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] +// +// > Second reply of the second post +// > \- [@gnouser](/r/demo/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/demo/boards:test_board/2/4) \[[reply](/r/demo/boards?help&__func=CreateReply&bid=1&threadid=2&postid=4&body.type=textarea)] \[[x](/r/demo/boards?help&__func=DeletePost&bid=1&threadid=2&postid=4)] + +// Realm: +// switchrealm["gno.land/r/demo/users"] +// switchrealm["gno.land/r/demo/boards"] +// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111]={ +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111", +// "ModTime": "123", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "68663c8895d37d479e417c11e21badfe21345c61", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:112" +// } +// } +// } +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "0000000004" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/boards.Post" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126" +// }, +// "Index": "0", +// "TV": null +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "64" +// } +// }, +// { +// "N": "AQAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125", +// "ModTime": "0", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124", +// "RefCount": "1" +// } +// } +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124]={ +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124", +// "ModTime": "0", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "3f34ac77289aa1d5f9a2f8b6d083138325816fb0", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:125" +// } +// } +// } +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "0000000004" +// } +// }, +// {}, +// { +// "N": "AQAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "64" +// } +// }, +// { +// "N": "AgAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "94a6665a44bac6ede7f3e3b87173e537b12f9532", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:111" +// }, +// "Index": "0", +// "TV": null +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "bc8e5b4e782a0bbc4ac9689681f119beb7b34d59", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:124" +// }, +// "Index": "0", +// "TV": null +// } +// } +// ], +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123", +// "ModTime": "0", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122", +// "RefCount": "1" +// } +// } +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122]={ +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122", +// "ModTime": "0", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "9957eadbc91dd32f33b0d815e041a32dbdea0671", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:123" +// } +// } +// } +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128", +// "ModTime": "0", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127", +// "RefCount": "1" +// } +// } +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129", +// "ModTime": "0", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127", +// "RefCount": "1" +// } +// } +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130", +// "ModTime": "0", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127", +// "RefCount": "1" +// } +// } +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131]={ +// "Fields": [ +// { +// "N": "0vknwQ4AAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "1024" +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "512" +// } +// } +// ], +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131", +// "ModTime": "0", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127", +// "RefCount": "1" +// } +// } +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "1024" +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "512" +// } +// } +// ], +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132", +// "ModTime": "0", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127", +// "RefCount": "1" +// } +// } +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/boards.Board" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84" +// }, +// "Index": "0", +// "TV": null +// } +// }, +// { +// "N": "BAAAAAAAAAA=", +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/boards.PostID" +// } +// }, +// { +// "T": { +// "@type": "/gno.RefType", +// "ID": "std.Address" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "" +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "Second reply of the second post" +// } +// }, +// { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Tree" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "f91e355bd19240f0f3350a7fa0e6a82b72225916", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:128" +// } +// }, +// { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Tree" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "9ee9c4117be283fc51ffcc5ecd65b75ecef5a9dd", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:129" +// } +// }, +// { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Tree" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "eb768b0140a5fe95f9c58747f0960d647dacfd42", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:130" +// } +// }, +// { +// "N": "AgAAAAAAAAA=", +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/boards.PostID" +// } +// }, +// { +// "N": "AgAAAAAAAAA=", +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/boards.PostID" +// } +// }, +// { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/boards.BoardID" +// } +// }, +// { +// "T": { +// "@type": "/gno.RefType", +// "ID": "time.Time" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "55cc9b7009f66b35f15d34ceed3fa25c9e36d54d", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:131" +// } +// }, +// { +// "T": { +// "@type": "/gno.RefType", +// "ID": "time.Time" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "d8d66a5855b0427c262cfce9891c88d469ab2351", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:132" +// } +// } +// ], +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127", +// "ModTime": "0", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126", +// "RefCount": "1" +// } +// } +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126]={ +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126", +// "IsEscaped": true, +// "ModTime": "0", +// "RefCount": "2" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/boards.Post" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "6b2cd34f216499f59540555be3fe11f40a67baa1", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:127" +// } +// } +// } +// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120]={ +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120", +// "ModTime": "134", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "dc1f011553dc53e7a846049e08cc77fa35ea6a51", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:121" +// } +// } +// } +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "0000000004" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/boards.Post" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:126" +// }, +// "Index": "0", +// "TV": null +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "64" +// } +// }, +// { +// "N": "AQAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136", +// "ModTime": "0", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135", +// "RefCount": "1" +// } +// } +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135]={ +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135", +// "ModTime": "0", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "96b86b4585c7f1075d7794180a5581f72733a7ab", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:136" +// } +// } +// } +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "0000000004" +// } +// }, +// {}, +// { +// "N": "AQAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "64" +// } +// }, +// { +// "N": "AgAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "32274e1f28fb2b97d67a1262afd362d370de7faa", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:120" +// }, +// "Index": "0", +// "TV": null +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "c2cfd6aec36a462f35bf02e5bf4a127aa1bb7ac2", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:135" +// }, +// "Index": "0", +// "TV": null +// } +// } +// ], +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134", +// "ModTime": "0", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133", +// "RefCount": "1" +// } +// } +// c[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133]={ +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133", +// "ModTime": "0", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107", +// "RefCount": "1" +// }, +// "Value": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "5cb875179e86d32c517322af7a323b2a5f3e6cc5", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:134" +// } +// } +// } +// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85]={ +// "Fields": [ +// { +// "N": "AQAAAAAAAAA=", +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/r/demo/boards.BoardID" +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "/r/demo/boards:test_board" +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "test_board" +// } +// }, +// { +// "T": { +// "@type": "/gno.RefType", +// "ID": "std.Address" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" +// } +// }, +// { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Tree" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "ed9d8105a90a05bc8d953538dd8810201e26ec6d", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:86" +// } +// }, +// { +// "N": "BAAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "65536" +// } +// }, +// { +// "T": { +// "@type": "/gno.RefType", +// "ID": "time.Time" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "ef78d14dccddfaa802727058e543d3ecc87157f0", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:87" +// } +// }, +// { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Tree" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "af6ed0268f99b7f369329094eb6dfaea7812708b", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:88" +// } +// } +// ], +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:85", +// "ModTime": "121", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:84", +// "RefCount": "1" +// } +// } +// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "9809329dc1ddc5d3556f7a8fa3c2cebcbf65560b", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:122" +// }, +// "Index": "0", +// "TV": null +// } +// } +// ], +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:106", +// "ModTime": "121", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105", +// "RefCount": "1" +// } +// } +// u[f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": { +// "@type": "/gno.RefValue", +// "Hash": "ceae9a1c4ed28bb51062e6ccdccfad0caafd1c4f", +// "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:133" +// }, +// "Index": "0", +// "TV": null +// } +// } +// ], +// "ObjectInfo": { +// "ID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:107", +// "ModTime": "121", +// "OwnerID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:105", +// "RefCount": "1" +// } +// } +// switchrealm["gno.land/r/demo/boards"] +// switchrealm["gno.land/r/demo/users"] +// switchrealm["gno.land/r/demo/users"] +// switchrealm["gno.land/r/demo/users"] +// switchrealm["gno.land/r/demo/boards"] +// switchrealm["gno.land/r/demo/boards_test"] From 411f022032a0b8ab4b4cb7aa9e61d8f819467986 Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Fri, 25 Oct 2024 18:56:34 +0200 Subject: [PATCH 27/27] fix: put true to compute month from date function --- gnovm/stdlibs/time/time.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/stdlibs/time/time.gno b/gnovm/stdlibs/time/time.gno index d30a5e349fe..60a4dd3181a 100644 --- a/gnovm/stdlibs/time/time.gno +++ b/gnovm/stdlibs/time/time.gno @@ -217,7 +217,7 @@ func (t Time) Year() int { // Month returns the month of the year specified by t. func (t Time) Month() Month { - _, month, _, _ := t.date(false) + _, month, _, _ := t.date(true) return month }