-
Notifications
You must be signed in to change notification settings - Fork 2.5k
/
Copy pathparser.go
182 lines (150 loc) · 5.46 KB
/
parser.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package timeutils // import "github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal/timeutils"
import (
"errors"
"fmt"
"regexp"
"strings"
"time"
"github.com/elastic/lunes"
strptime "github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal/timeutils/internal/ctimefmt"
)
var invalidFractionalSecondsGoTime = regexp.MustCompile(`[^.,9]9+`)
func StrptimeToGotime(layout string) (string, error) {
return strptime.ToNative(layout)
}
func ParseStrptime(layout string, value any, location *time.Location) (time.Time, error) {
goLayout, err := strptime.ToNative(layout)
if err != nil {
return time.Time{}, err
}
return ParseGotime(goLayout, value, location)
}
// ParseLocalizedStrptime is like ParseLocalizedGotime, but instead of using the native Go time layout,
// it uses the ctime-like format.
func ParseLocalizedStrptime(layout string, value any, location *time.Location, language string) (time.Time, error) {
goLayout, err := strptime.ToNative(layout)
if err != nil {
return time.Time{}, err
}
return ParseLocalizedGotime(goLayout, value, location, language)
}
func GetLocation(location *string, layout *string) (*time.Location, error) {
if location != nil && *location != "" {
// If location is specified, it must be in the local timezone database
loc, err := time.LoadLocation(*location)
if err != nil {
return nil, fmt.Errorf("failed to load location %s: %w", *location, err)
}
return loc, nil
}
if layout != nil && strings.HasSuffix(*layout, "Z") {
// If a timestamp ends with 'Z', it should be interpreted at Zulu (UTC) time
return time.UTC, nil
}
return time.Local, nil
}
// ParseLocalizedGotime is like ParseGotime, but instead of parsing a formatted time in
// English, it parses a value in foreign language, and returns the [time.Time] it represents.
// The language argument must be a well-formed BCP 47 language tag (e.g.: "en", "en-US"), and
// a known CLDR locale.
func ParseLocalizedGotime(layout string, value any, location *time.Location, language string) (time.Time, error) {
stringValue, err := convertParsingValue(value)
if err != nil {
return time.Time{}, err
}
translatedVal, err := lunes.Translate(layout, stringValue, language)
if err != nil {
return time.Time{}, err
}
return ParseGotime(layout, translatedVal, location)
}
func ParseGotime(layout string, value any, location *time.Location) (time.Time, error) {
timeValue, err := parseGotime(layout, value, location)
if err != nil {
return time.Time{}, err
}
return SetTimestampYear(timeValue), nil
}
func parseGotime(layout string, value any, location *time.Location) (time.Time, error) {
str, err := convertParsingValue(value)
if err != nil {
return time.Time{}, err
}
result, err := time.ParseInLocation(layout, str, location)
// Depending on the timezone database, we may get a pseudo-matching timezone
// This is apparent when the zone is not "UTC", but the offset is still 0
zone, offset := result.Zone()
if offset != 0 || zone == "UTC" {
return result, err
}
// Manually look up the location based on the zone
loc, locErr := time.LoadLocation(zone)
if locErr != nil {
// can't correct offset, just return what we have
return result, fmt.Errorf("failed to load location %s: %w", zone, locErr)
}
// Reparse the timestamp, with the location
resultLoc, locErr := time.ParseInLocation(layout, str, loc)
if locErr != nil {
// can't correct offset, just return original result
return result, err
}
return resultLoc, locErr
}
func convertParsingValue(value any) (string, error) {
var str string
switch v := value.(type) {
case string:
str = v
case []byte:
str = string(v)
default:
return "", fmt.Errorf("type %T cannot be parsed as a time", value)
}
return str, nil
}
// SetTimestampYear sets the year of a timestamp to the current year.
// This is needed because year is missing from some time formats, such as rfc3164.
func SetTimestampYear(t time.Time) time.Time {
if t.Year() > 0 {
return t
}
n := Now()
d := time.Date(n.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location())
// Assume the timestamp is from last year if its month and day are
// more than 7 days past the current date.
// i.e. If today is January 1, but the timestamp is February 1, it's safe
// to assume the timestamp is from last year.
if d.After(n.AddDate(0, 0, 7)) {
d = d.AddDate(-1, 0, 0)
}
return d
}
// ValidateStrptime checks the given strptime layout and returns an error if it detects any known issues
// that prevent it from being parsed.
func ValidateStrptime(layout string) error {
return strptime.Validate(layout)
}
func ValidateGotime(layout string) error {
if match := invalidFractionalSecondsGoTime.FindString(layout); match != "" {
return fmt.Errorf("invalid fractional seconds directive: '%s'. must be preceded with '.' or ','", match)
}
return nil
}
// ValidateLocale checks the given locale and returns an error if the language tag
// is not supported by the localized parser functions.
func ValidateLocale(locale string) error {
_, err := lunes.NewDefaultLocale(locale)
if err == nil {
return nil
}
var e *lunes.ErrUnsupportedLocale
if errors.As(err, &e) {
return fmt.Errorf("unsupported locale '%s', value must be a supported BCP 47 language tag", locale)
}
return fmt.Errorf("invalid locale '%s': %w", locale, err)
}
// Allows tests to override with deterministic value
var Now = time.Now