From 188ec6ea1d7c8fee614ea021a5e757f004331fad Mon Sep 17 00:00:00 2001 From: xushiwei Date: Wed, 26 Jun 2024 20:41:34 +0800 Subject: [PATCH] patch time: time.Format --- internal/lib/time/format.go | 340 ++++++++++++++-------------- internal/lib/time/format_rfc3339.go | 188 +++++++++++++++ internal/lib/time/time.go | 149 ++++++++++++ internal/lib/time/zoneinfo.go | 35 +++ 4 files changed, 539 insertions(+), 173 deletions(-) create mode 100644 internal/lib/time/format_rfc3339.go diff --git a/internal/lib/time/format.go b/internal/lib/time/format.go index 2145c89b..0e40c9c7 100644 --- a/internal/lib/time/format.go +++ b/internal/lib/time/format.go @@ -611,7 +611,7 @@ func (t Time) GoString() string { buf = append(buf, ')') return string(buf) */ - panic("todo") + panic("todo: time.GoString") } // Format returns a textual representation of the time value formatted according @@ -637,189 +637,183 @@ func (t Time) Format(layout string) string { // 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 { - /* - // Optimize for RFC3339 as it accounts for over half of all representations. - switch layout { - case RFC3339: - return t.appendFormatRFC3339(b, false) - case RFC3339Nano: - return t.appendFormatRFC3339(b, true) - default: - return t.appendFormat(b, layout) - } - */ - panic("todo") + // Optimize for RFC3339 as it accounts for over half of all representations. + switch layout { + case RFC3339: + return t.appendFormatRFC3339(b, false) + case RFC3339Nano: + return t.appendFormatRFC3339(b, true) + default: + return t.appendFormat(b, layout) + } } func (t Time) appendFormat(b []byte, layout string) []byte { - /* - var ( - name, offset, abs = t.locabs() + var ( + name, offset, abs = t.locabs() - year int = -1 - month Month - day int - yday int - hour int = -1 - min int - sec int - ) + 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...) + // 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 = absDate(abs, true) + yday++ + } + + // Compute hour, minute, second if needed. + if hour < 0 && std&stdNeedClock != 0 { + hour, min, sec = absClock(abs) + } + + switch std & stdMask { + case stdYear: + y := year + if y < 0 { + y = -y } - if std == 0 { + 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, absWeekday(abs).String()[:3]...) + case stdLongWeekDay: + s := absWeekday(abs).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 offset == 0 && (std == stdISO8601TZ || std == stdISO8601ColonTZ || std == stdISO8601SecondsTZ || std == stdISO8601ShortTZ || std == stdISO8601ColonSecondsTZ) { + b = append(b, 'Z') break } - layout = suffix - - // Compute year, month, day if needed. - if year < 0 && std&stdNeedDate != 0 { - year, month, day, yday = absDate(abs, true) - yday++ + 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) } - // Compute hour, minute, second if needed. - if hour < 0 && std&stdNeedClock != 0 { - hour, min, sec = absClock(abs) - } - - 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, absWeekday(abs).String()[:3]...) - case stdLongWeekDay: - s := absWeekday(abs).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 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 { + // append seconds if appropriate + if std == stdISO8601SecondsTZ || std == stdNumSecondsTz || std == stdNumColonSecondsTZ || std == stdISO8601ColonSecondsTZ { + if std == stdNumColonSecondsTZ || std == stdISO8601ColonSecondsTZ { 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 = appendNano(b, t.Nanosecond(), std) + 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 = appendNano(b, t.Nanosecond(), std) } - return b - */ - panic("todo") + } + return b } var errBad = errors.New("bad value for field") // placeholder not passed to user @@ -1023,7 +1017,7 @@ func Parse(layout, value string) (Time, error) { } return parse(layout, value, UTC, Local) */ - panic("todo") + panic("todo: time.Parse") } // ParseInLocation is like Parse but differs in two important ways. @@ -1041,7 +1035,7 @@ func ParseInLocation(layout, value string, loc *Location) (Time, error) { } return parse(layout, value, loc, loc) */ - panic("todo") + panic("todo: time.ParseInLocation") } func parse(layout, value string, defaultLocation, local *Location) (Time, error) { @@ -1413,7 +1407,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, defaultLocation), nil */ - panic("todo") + panic("todo: time.parse") } // parseTimeZone parses a time zone string and returns its length. Time zones diff --git a/internal/lib/time/format_rfc3339.go b/internal/lib/time/format_rfc3339.go new file mode 100644 index 00000000..1151666c --- /dev/null +++ b/internal/lib/time/format_rfc3339.go @@ -0,0 +1,188 @@ +// Copyright 2022 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" + +// RFC 3339 is the most commonly used format. +// +// It is implicitly used by the Time.(Marshal|Unmarshal)(Text|JSON) methods. +// Also, according to analysis on https://go.dev/issue/52746, +// RFC 3339 accounts for 57% of all explicitly specified time formats, +// with the second most popular format only being used 8% of the time. +// The overwhelming use of RFC 3339 compared to all other formats justifies +// the addition of logic to optimize formatting and parsing. + +func (t Time) appendFormatRFC3339(b []byte, nanos bool) []byte { + _, offset, abs := t.locabs() + + // Format date. + year, month, day, _ := absDate(abs, true) + b = appendInt(b, year, 4) + b = append(b, '-') + b = appendInt(b, int(month), 2) + b = append(b, '-') + b = appendInt(b, day, 2) + + b = append(b, 'T') + + // Format time. + hour, min, sec := absClock(abs) + b = appendInt(b, hour, 2) + b = append(b, ':') + b = appendInt(b, min, 2) + b = append(b, ':') + b = appendInt(b, sec, 2) + + if nanos { + std := stdFracSecond(stdFracSecond9, 9, '.') + b = appendNano(b, t.Nanosecond(), std) + } + + if offset == 0 { + return append(b, 'Z') + } + + // Format zone. + 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 = append(b, ':') + b = appendInt(b, zone%60, 2) + return b +} + +func (t Time) appendStrictRFC3339(b []byte) ([]byte, error) { + n0 := len(b) + b = t.appendFormatRFC3339(b, true) + + // Not all valid Go timestamps can be serialized as valid RFC 3339. + // Explicitly check for these edge cases. + // See https://go.dev/issue/4556 and https://go.dev/issue/54580. + num2 := func(b []byte) byte { return 10*(b[0]-'0') + (b[1] - '0') } + switch { + case b[n0+len("9999")] != '-': // year must be exactly 4 digits wide + return b, errors.New("year outside of range [0,9999]") + case b[len(b)-1] != 'Z': + c := b[len(b)-len("Z07:00")] + if ('0' <= c && c <= '9') || num2(b[len(b)-len("07:00"):]) >= 24 { + return b, errors.New("timezone hour outside of range [0,23]") + } + } + return b, nil +} + +func parseRFC3339[bytes []byte | string](s bytes, local *Location) (Time, bool) { + // parseUint parses s as an unsigned decimal integer and + // verifies that it is within some range. + // If it is invalid or out-of-range, + // it sets ok to false and returns the min value. + ok := true + parseUint := func(s bytes, min, max int) (x int) { + for _, c := range []byte(s) { + if c < '0' || '9' < c { + ok = false + return min + } + x = x*10 + int(c) - '0' + } + if x < min || max < x { + ok = false + return min + } + return x + } + + // Parse the date and time. + if len(s) < len("2006-01-02T15:04:05") { + return Time{}, false + } + year := parseUint(s[0:4], 0, 9999) // e.g., 2006 + month := parseUint(s[5:7], 1, 12) // e.g., 01 + day := parseUint(s[8:10], 1, daysIn(Month(month), year)) // e.g., 02 + hour := parseUint(s[11:13], 0, 23) // e.g., 15 + min := parseUint(s[14:16], 0, 59) // e.g., 04 + sec := parseUint(s[17:19], 0, 59) // e.g., 05 + if !ok || !(s[4] == '-' && s[7] == '-' && s[10] == 'T' && s[13] == ':' && s[16] == ':') { + return Time{}, false + } + s = s[19:] + + // Parse the fractional second. + var nsec int + if len(s) >= 2 && s[0] == '.' && isDigit(s, 1) { + n := 2 + for ; n < len(s) && isDigit(s, n); n++ { + } + nsec, _, _ = parseNanoseconds(s, n) + s = s[n:] + } + + // Parse the time zone. + t := Date(year, Month(month), day, hour, min, sec, nsec, UTC) + if len(s) != 1 || s[0] != 'Z' { + if len(s) != len("-07:00") { + return Time{}, false + } + hr := parseUint(s[1:3], 0, 23) // e.g., 07 + mm := parseUint(s[4:6], 0, 59) // e.g., 00 + if !ok || !((s[0] == '-' || s[0] == '+') && s[3] == ':') { + return Time{}, false + } + zoneOffset := (hr*60 + mm) * 60 + if s[0] == '-' { + zoneOffset *= -1 + } + t.addSec(-int64(zoneOffset)) + + // Use local zone with the given offset if possible. + if _, offset, _, _, _ := local.lookup(t.unixSec()); offset == zoneOffset { + t.setLoc(local) + } else { + t.setLoc(FixedZone("", zoneOffset)) + } + } + return t, true +} + +func parseStrictRFC3339(b []byte) (Time, error) { + t, ok := parseRFC3339(b, Local) + if !ok { + t, err := Parse(RFC3339, string(b)) + if err != nil { + return Time{}, err + } + + // The parse template syntax cannot correctly validate RFC 3339. + // Explicitly check for cases that Parse is unable to validate for. + // See https://go.dev/issue/54580. + num2 := func(b []byte) byte { return 10*(b[0]-'0') + (b[1] - '0') } + switch { + // TODO(https://go.dev/issue/54580): Strict parsing is disabled for now. + // Enable this again with a GODEBUG opt-out. + case true: + return t, nil + case b[len("2006-01-02T")+1] == ':': // hour must be two digits + return Time{}, &ParseError{RFC3339, string(b), "15", string(b[len("2006-01-02T"):][:1]), ""} + case b[len("2006-01-02T15:04:05")] == ',': // sub-second separator must be a period + return Time{}, &ParseError{RFC3339, string(b), ".", ",", ""} + case b[len(b)-1] != 'Z': + switch { + case num2(b[len(b)-len("07:00"):]) >= 24: // timezone hour must be in range + return Time{}, &ParseError{RFC3339, string(b), "Z07:00", string(b[len(b)-len("Z07:00"):]), ": timezone hour out of range"} + case num2(b[len(b)-len("00"):]) >= 60: // timezone minute must be in range + return Time{}, &ParseError{RFC3339, string(b), "Z07:00", string(b[len(b)-len("Z07:00"):]), ": timezone minute out of range"} + } + default: // unknown error; should not occur + return Time{}, &ParseError{RFC3339, string(b), RFC3339, string(b), ""} + } + } + return t, nil +} diff --git a/internal/lib/time/time.go b/internal/lib/time/time.go index 734bf0be..8f201a33 100644 --- a/internal/lib/time/time.go +++ b/internal/lib/time/time.go @@ -409,6 +409,50 @@ 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 +} + // Date returns the Time corresponding to // // yyyy-mm-dd hh:mm:ss + nsec nanoseconds @@ -481,6 +525,111 @@ func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) T return t } +/* TODO(xsw): +// 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(true) + return month +} + +// Day returns the day of the month specified by t. +func (t Time) Day() int { + _, _, day, _ := t.date(true) + return day +} +*/ + +// 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) +} + +// 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 + abs := t.abs() + d := Thursday - absWeekday(abs) + // handle Sunday + 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 +} + +// 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) + 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.abs()%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 +} + +// 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) +} + +// 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 + */ + panic("todo: Time.YearDay") +} + func unixTime(sec int64, nsec int32) Time { return Time{uint64(nsec), sec + unixToInternal, Local} } diff --git a/internal/lib/time/zoneinfo.go b/internal/lib/time/zoneinfo.go index 069a36c8..89c6983b 100644 --- a/internal/lib/time/zoneinfo.go +++ b/internal/lib/time/zoneinfo.go @@ -94,6 +94,41 @@ func (l *Location) String() string { return l.get().name } +var unnamedFixedZones []*Location +var unnamedFixedZonesOnce sync.Once + +// FixedZone returns a Location that always uses +// the given zone name and offset (seconds east of UTC). +func FixedZone(name string, offset int) *Location { + // Most calls to FixedZone have an unnamed zone with an offset by the hour. + // Optimize for that case by returning the same *Location for a given hour. + const hoursBeforeUTC = 12 + const hoursAfterUTC = 14 + hour := offset / 60 / 60 + if name == "" && -hoursBeforeUTC <= hour && hour <= +hoursAfterUTC && hour*60*60 == offset { + unnamedFixedZonesOnce.Do(func() { + unnamedFixedZones = make([]*Location, hoursBeforeUTC+1+hoursAfterUTC) + for hr := -hoursBeforeUTC; hr <= +hoursAfterUTC; hr++ { + unnamedFixedZones[hr+hoursBeforeUTC] = fixedZone("", hr*60*60) + } + }) + return unnamedFixedZones[hour+hoursBeforeUTC] + } + return fixedZone(name, offset) +} + +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. //