-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathoffsetdatetime.go
More file actions
276 lines (237 loc) · 9.36 KB
/
Copy pathoffsetdatetime.go
File metadata and controls
276 lines (237 loc) · 9.36 KB
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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
package goda
import (
"database/sql"
"database/sql/driver"
"encoding"
"encoding/json"
"fmt"
"time"
)
// OffsetDateTime represents a date-time with a time-zone offset from UTC,
// such as 2024-03-15T14:30:45.123456789+01:00.
//
// OffsetDateTime stores an offset from UTC, but does not store or use a time-zone.
// Use ZonedDateTime when you need full time-zone support including DST transitions.
//
// OffsetDateTime is comparable and can be used as a map key.
// The zero value represents an unset date-time and IsZero returns true for it.
//
// OffsetDateTime implements sql.Scanner and driver.Valuer for database operations,
// encoding.TextMarshaler and encoding.TextUnmarshaler for text serialization,
// and json.Marshaler and json.Unmarshaler for JSON serialization.
//
// Format: yyyy-MM-ddTHH:mm:ss[.nnnnnnnnn]±HH:mm (e.g., "2024-03-15T14:30:45.123456789+01:00").
type OffsetDateTime struct {
datetime LocalDateTime
offset ZoneOffset
}
// LocalDateTime returns the local date-time part without the offset.
func (odt OffsetDateTime) LocalDateTime() LocalDateTime {
return odt.datetime
}
// LocalDate returns the date part of this date-time.
func (odt OffsetDateTime) LocalDate() LocalDate {
return odt.datetime.LocalDate()
}
// LocalTime returns the time part of this date-time.
func (odt OffsetDateTime) LocalTime() LocalTime {
return odt.datetime.LocalTime()
}
// Offset returns the zone offset.
func (odt OffsetDateTime) Offset() ZoneOffset {
return odt.offset
}
// Year returns the year component.
func (odt OffsetDateTime) Year() Year {
return odt.datetime.Year()
}
// Month returns the month component.
func (odt OffsetDateTime) Month() Month {
return odt.datetime.Month()
}
// DayOfMonth returns the day-of-month component.
func (odt OffsetDateTime) DayOfMonth() int {
return odt.datetime.DayOfMonth()
}
// DayOfWeek returns the day-of-week.
func (odt OffsetDateTime) DayOfWeek() DayOfWeek {
return odt.datetime.DayOfWeek()
}
// DayOfYear returns the day-of-year.
func (odt OffsetDateTime) DayOfYear() int {
return odt.datetime.DayOfYear()
}
// Hour returns the hour component (0-23).
func (odt OffsetDateTime) Hour() int {
return odt.datetime.Hour()
}
// Minute returns the minute component (0-59).
func (odt OffsetDateTime) Minute() int {
return odt.datetime.Minute()
}
// Second returns the second component (0-59).
func (odt OffsetDateTime) Second() int {
return odt.datetime.Second()
}
// Millisecond returns the millisecond component (0-999).
func (odt OffsetDateTime) Millisecond() int {
return odt.datetime.Millisecond()
}
// Nanosecond returns the nanosecond component (0-999999999).
func (odt OffsetDateTime) Nanosecond() int {
return odt.datetime.Nanosecond()
}
// IsZero returns true if this is the zero value.
func (odt OffsetDateTime) IsZero() bool {
return odt.datetime.IsZero() && odt.offset.IsZero()
}
// IsLeapYear returns true if the year is a leap year.
func (odt OffsetDateTime) IsLeapYear() bool {
return odt.datetime.IsLeapYear()
}
// IsSupportedField returns true if the field is supported by OffsetDateTime.
// OffsetDateTime supports all fields from LocalDateTime plus FieldOffsetSeconds and FieldInstantSeconds.
func (odt OffsetDateTime) IsSupportedField(field Field) bool {
return odt.datetime.IsSupportedField(field) || odt.offset.IsSupportedField(field) || field == FieldInstantSeconds
}
// GetField returns the value of the specified field as a TemporalValue.
// This method queries the date-time for the value of the specified field.
// The returned value may be unsupported if the field is not supported by OffsetDateTime.
//
// If the date-time is zero (IsZero() returns true), an unsupported TemporalValue is returned.
//
// OffsetDateTime supports all fields from LocalDateTime plus:
// - FieldOffsetSeconds: the offset in seconds
// - FieldInstantSeconds: the epoch seconds (Unix timestamp)
func (odt OffsetDateTime) GetField(field Field) TemporalValue {
if odt.IsZero() {
return TemporalValue{v: 0, unsupported: true}
}
if odt.offset.IsSupportedField(field) {
return odt.offset.GetField(field)
}
// Handle offset-specific fields
switch field {
case FieldInstantSeconds:
v, o := odt.epochSecondOverflow()
return TemporalValue{v: v, overflow: o}
case FieldOffsetSeconds:
return TemporalValue{v: int64(odt.Offset().totalSeconds)}
}
// Delegate to LocalDateTime for date and time fields
return odt.datetime.GetField(field)
}
// GoTime converts this offset date-time to a time.Time.
// Returns time.Time{} (zero) for zero value.
func (odt OffsetDateTime) GoTime() time.Time {
if odt.IsZero() {
return time.Time{}
}
// Create a fixed location with the offset
loc := time.FixedZone("", odt.offset.TotalSeconds())
return time.Date(int(odt.Year()), time.Month(odt.Month()), odt.DayOfMonth(), odt.Hour(), odt.Minute(), odt.Second(), odt.Nanosecond(), loc)
}
// EpochSecond returns the number of seconds since Unix epoch (1970-01-01T00:00:00Z).
func (odt OffsetDateTime) EpochSecond() int64 {
i, _ := odt.epochSecondOverflow()
return i
}
func (odt OffsetDateTime) epochSecondOverflow() (i int64, overflow bool) {
if odt.IsZero() {
return 0, false
}
epochDay := odt.datetime.LocalDate().UnixEpochDays()
secondsOfDay := odt.datetime.LocalTime().GetField(FieldSecondOfDay).Int64()
i, overflow = addExactly(epochDay*86400+secondsOfDay, -int64(odt.offset.TotalSeconds()))
overflow = overflow || odt.LocalDateTime().Compare(localDateTimeMinEpochSecond) < 0 || odt.LocalDateTime().Compare(localDateTimeMaxEpochSecond) > 0
return
}
// Compare compares this offset date-time with another.
// The comparison is based on the instant then on the local date-time.
// Returns -1 if this is before other, 0 if equal, 1 if after.
func (odt OffsetDateTime) Compare(other OffsetDateTime) int {
return doCompare(odt, other, compareZero, comparing(OffsetDateTime.EpochSecond), comparing(OffsetDateTime.Nanosecond), comparing1(OffsetDateTime.LocalDateTime))
}
// IsBefore returns true if this offset date-time is before the specified offset date-time.
func (odt OffsetDateTime) IsBefore(other OffsetDateTime) bool {
return doCompare(odt, other, compareZero, comparing(OffsetDateTime.EpochSecond), comparing(OffsetDateTime.Nanosecond)) < 0
}
// IsAfter returns true if this offset date-time is after the specified offset date-time.
func (odt OffsetDateTime) IsAfter(other OffsetDateTime) bool {
return doCompare(odt, other, compareZero, comparing(OffsetDateTime.EpochSecond), comparing(OffsetDateTime.Nanosecond)) > 0
}
func (odt OffsetDateTime) Chain() (chain OffsetDateTimeChain) {
chain.value = odt
return
}
// OffsetDateTimeOf creates a new OffsetDateTime from individual components.
// Returns an error if any component is invalid.
func OffsetDateTimeOf(year Year, month Month, day int, hour int, minute int, second int, nanosecond int, offset ZoneOffset) (OffsetDateTime, error) {
dt, err := LocalDateTimeOf(year, month, day, hour, minute, second, nanosecond)
if err != nil {
return OffsetDateTime{}, err
}
return OffsetDateTime{
datetime: dt,
offset: offset,
}, nil
}
// MustOffsetDateTimeOf creates a new OffsetDateTime from individual components.
// Panics if any component is invalid.
func MustOffsetDateTimeOf(year Year, month Month, day int, hour int, minute int, second int, nanosecond int, offset ZoneOffset) OffsetDateTime {
return mustValue(OffsetDateTimeOf(year, month, day, hour, minute, second, nanosecond, offset))
}
// OffsetDateTimeNow returns the current date-time with offset in the system's local time zone.
func OffsetDateTimeNow() OffsetDateTime {
return OffsetDateTimeOfGoTime(time.Now())
}
// OffsetDateTimeNowUTC returns the current date-time with offset in UTC.
func OffsetDateTimeNowUTC() OffsetDateTime {
return OffsetDateTimeOfGoTime(time.Now().UTC())
}
// OffsetDateTimeOfGoTime creates an OffsetDateTime from a time.Time.
// Returns zero value if t.IsZero().
func OffsetDateTimeOfGoTime(t time.Time) OffsetDateTime {
if t.IsZero() {
return OffsetDateTime{}
}
_, offsetSeconds := t.Zone()
offset := MustZoneOffsetOfSeconds(offsetSeconds)
return LocalDateTimeOfGoTime(t).AtOffset(offset)
}
// OffsetDateTimeParse parses a date-time string in RFC3339-compatible format.
// The format is yyyy-MM-ddTHH:mm:ss[.nnnnnnnnn]±HH:mm[:ss] or with 'Z' for UTC.
//
// Examples:
//
// odt, err := OffsetDateTimeParse("2024-03-15T14:30:45.123456789+01:00")
// odt, err := OffsetDateTimeParse("2024-03-15T14:30:45Z")
// odt, err := OffsetDateTimeParse("2024-03-15T14:30:45+05:30")
func OffsetDateTimeParse(s string) (OffsetDateTime, error) {
var odt OffsetDateTime
err := odt.UnmarshalText([]byte(s))
return odt, err
}
// MustOffsetDateTimeParse parses a date-time string with offset.
// Panics if the string is invalid.
func MustOffsetDateTimeParse(s string) OffsetDateTime {
odt, err := OffsetDateTimeParse(s)
if err != nil {
panic(err)
}
return odt
}
// Compile-time interface checks
var (
_ encoding.TextAppender = (*OffsetDateTime)(nil)
_ fmt.Stringer = (*OffsetDateTime)(nil)
_ encoding.TextMarshaler = (*OffsetDateTime)(nil)
_ encoding.TextUnmarshaler = (*OffsetDateTime)(nil)
_ json.Marshaler = (*OffsetDateTime)(nil)
_ json.Unmarshaler = (*OffsetDateTime)(nil)
_ driver.Valuer = (*OffsetDateTime)(nil)
_ sql.Scanner = (*OffsetDateTime)(nil)
)
// Compile-time check that OffsetDateTime is comparable
func _assertOffsetDateTimeIsComparable[T comparable](t T) {}
var _ = _assertOffsetDateTimeIsComparable[OffsetDateTime]