mirror of
https://github.com/hanzoai/traces.git
synced 2026-10-30 18:20:41 +00:00
* Update Prometheus to v0.307.2 * Update otel collector * Update OTel collector
296 lines
6.1 KiB
Go
296 lines
6.1 KiB
Go
// Package iso8601 is a utility for parsing ISO8601 datetime strings into native Go times.
|
|
// The standard library's RFC3339 reference layout can be too strict for working with 3rd party APIs,
|
|
// especially ones written in other languages.
|
|
//
|
|
// Use the provided `Time` structure instead of the default `time.Time` to provide ISO8601 support for JSON responses.
|
|
package iso8601
|
|
|
|
import (
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
year uint = iota
|
|
month
|
|
day
|
|
hour
|
|
minute
|
|
second
|
|
millisecond
|
|
)
|
|
|
|
const (
|
|
// charStart is the binary position of the character `0`
|
|
charStart uint = '0'
|
|
)
|
|
|
|
// ParseISOZone parses the 5 character zone information in an ISO8601 date string.
|
|
// This function expects input that matches:
|
|
//
|
|
// Z, z (UTC)
|
|
// -0100
|
|
// +0100
|
|
// +01:00
|
|
// -01:00
|
|
// +01
|
|
// +01:45
|
|
// +0145
|
|
func ParseISOZone(inp []byte) (*time.Location, error) {
|
|
if len(inp) == 0 {
|
|
return nil, ErrZoneCharacters
|
|
}
|
|
|
|
var neg bool
|
|
switch inp[0] {
|
|
case 'Z', 'z':
|
|
if len(inp) != 1 {
|
|
return nil, ErrRemainingData
|
|
}
|
|
return time.UTC, nil
|
|
case '+':
|
|
case '-':
|
|
neg = true
|
|
default:
|
|
return nil, newUnexpectedCharacterError(inp[0])
|
|
}
|
|
|
|
if len(inp) < 3 || len(inp) > 6 {
|
|
return nil, ErrZoneCharacters
|
|
}
|
|
|
|
var offset int
|
|
|
|
var z uint
|
|
var multiplier = uint(3600) // start with initial multiplier of hours
|
|
for i := 1; i < len(inp); i++ {
|
|
if i == 3 { // next multiplier
|
|
offset = int(z * multiplier)
|
|
multiplier = 60 // multiplier for minutes
|
|
z = 0
|
|
} else { // next digit
|
|
z = z * 10
|
|
}
|
|
|
|
switch inp[i] {
|
|
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
|
z += uint(inp[i]) - charStart
|
|
case ':':
|
|
if i != 3 {
|
|
return nil, newUnexpectedCharacterError(inp[i])
|
|
}
|
|
default:
|
|
return nil, newUnexpectedCharacterError(inp[i])
|
|
}
|
|
|
|
}
|
|
|
|
offset += int(z * multiplier)
|
|
|
|
if neg {
|
|
offset = -offset
|
|
}
|
|
if neg && offset == 0 {
|
|
return nil, ErrInvalidZone
|
|
}
|
|
return time.FixedZone("", offset), nil
|
|
}
|
|
|
|
// Parse parses an ISO8601 compliant date-time byte slice into a time.Time object.
|
|
// If any component of an input date-time is not within the expected range then an *iso8601.RangeError is returned.
|
|
func Parse(inp []byte) (time.Time, error) {
|
|
return ParseInLocation(inp, time.UTC)
|
|
}
|
|
|
|
// ParseInLocation parses an ISO8601 compliant date-time byte slice into a time.Time object.
|
|
// If the input does not have timezone information, it will use the given location.
|
|
func ParseInLocation(inp []byte, loc *time.Location) (time.Time, error) {
|
|
var (
|
|
Y uint
|
|
M uint
|
|
d uint
|
|
h uint
|
|
m uint
|
|
s uint
|
|
fraction int
|
|
nfraction = 1 //counts amount of precision for the second fraction
|
|
)
|
|
|
|
var c uint
|
|
var p = year
|
|
|
|
var i int
|
|
|
|
parse:
|
|
for ; i < len(inp); i++ {
|
|
switch inp[i] {
|
|
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
|
c = c * 10
|
|
c += uint(inp[i]) - charStart
|
|
|
|
if p == millisecond {
|
|
nfraction++
|
|
}
|
|
case '-':
|
|
if p < hour {
|
|
switch p {
|
|
case year:
|
|
Y = c
|
|
case month:
|
|
M = c
|
|
default:
|
|
return time.Time{}, newUnexpectedCharacterError(inp[i])
|
|
}
|
|
p++
|
|
c = 0
|
|
continue
|
|
}
|
|
fallthrough
|
|
case '+', 'Z':
|
|
if i == 0 {
|
|
// The ISO8601 technically allows signed year components.
|
|
// Go does not allow negative years, but let's allow a positive sign to be more compatible with the spec.
|
|
// It must be the very first character of the input (#11).
|
|
continue
|
|
}
|
|
|
|
switch p {
|
|
case hour:
|
|
h = c
|
|
case minute:
|
|
m = c
|
|
case second:
|
|
s = c
|
|
case millisecond:
|
|
fraction = int(c)
|
|
default:
|
|
return time.Time{}, newUnexpectedCharacterError(inp[i])
|
|
}
|
|
c = 0
|
|
var err error
|
|
loc, err = ParseISOZone(inp[i:])
|
|
if err != nil {
|
|
return time.Time{}, err
|
|
}
|
|
break parse
|
|
case 'T', ' ':
|
|
if p != day {
|
|
return time.Time{}, newUnexpectedCharacterError(inp[i])
|
|
}
|
|
d = c
|
|
c = 0
|
|
p++
|
|
case ':':
|
|
switch p {
|
|
case hour:
|
|
h = c
|
|
case minute:
|
|
m = c
|
|
case second:
|
|
m = c
|
|
default:
|
|
return time.Time{}, newUnexpectedCharacterError(inp[i])
|
|
}
|
|
c = 0
|
|
p++
|
|
case '.':
|
|
if p != second {
|
|
return time.Time{}, newUnexpectedCharacterError(inp[i])
|
|
}
|
|
s = c
|
|
c = 0
|
|
p++
|
|
default:
|
|
return time.Time{}, newUnexpectedCharacterError(inp[i])
|
|
}
|
|
}
|
|
|
|
// Capture remaining data
|
|
// Sometimes a date can end without a non-integer character
|
|
if c > 0 {
|
|
switch p {
|
|
case year:
|
|
Y = c
|
|
M = 1
|
|
d = 1
|
|
case month:
|
|
M = c
|
|
d = 1
|
|
case day:
|
|
d = c
|
|
case hour:
|
|
h = c
|
|
case minute:
|
|
m = c
|
|
case second:
|
|
s = c
|
|
case millisecond:
|
|
fraction = int(c)
|
|
}
|
|
}
|
|
|
|
// Get the seconds fraction as nanoseconds
|
|
if fraction < 0 || 1e9 <= fraction {
|
|
return time.Time{}, ErrPrecision
|
|
}
|
|
scale := 10 - nfraction
|
|
for i := 0; i < scale; i++ {
|
|
fraction *= 10
|
|
}
|
|
|
|
switch {
|
|
case M < 1 || M > 12: // Month 1-12
|
|
return time.Time{}, &RangeError{
|
|
Value: string(inp),
|
|
Element: "month",
|
|
Given: int(M),
|
|
Min: 1,
|
|
Max: 12,
|
|
}
|
|
case d < 1 || int(d) > daysIn(time.Month(M), int(Y)): // Day 1-daysIn(month, year)
|
|
return time.Time{}, &RangeError{
|
|
Value: string(inp),
|
|
Element: "day",
|
|
Given: int(d),
|
|
Min: 1,
|
|
Max: daysIn(time.Month(M), int(Y)),
|
|
}
|
|
case h > 23: // Hour 0-23
|
|
return time.Time{}, &RangeError{
|
|
Value: string(inp),
|
|
Element: "hour",
|
|
Given: int(h),
|
|
Min: 0,
|
|
Max: 23,
|
|
}
|
|
case m > 59: // Minute 0-59
|
|
return time.Time{}, &RangeError{
|
|
Value: string(inp),
|
|
Element: "minute",
|
|
Given: int(m),
|
|
Min: 0,
|
|
Max: 59,
|
|
}
|
|
case s > 59: // Second 0-59
|
|
return time.Time{}, &RangeError{
|
|
Value: string(inp),
|
|
Element: "second",
|
|
Given: int(s),
|
|
Min: 0,
|
|
Max: 59,
|
|
}
|
|
}
|
|
|
|
return time.Date(int(Y), time.Month(M), int(d), int(h), int(m), int(s), fraction, loc), nil
|
|
}
|
|
|
|
// ParseString parses an ISO8601 compliant date-time string into a time.Time object.
|
|
func ParseString(inp string) (time.Time, error) {
|
|
return Parse([]byte(inp))
|
|
}
|
|
|
|
// ParseStringInLocation parses an ISO8601 compliant date-time string into a time.Time object.
|
|
// If the input does not have timezone information, it will use the given location.
|
|
func ParseStringInLocation(inp string, loc *time.Location) (time.Time, error) {
|
|
return ParseInLocation([]byte(inp), loc)
|
|
}
|