251
|
1 |
// Implementation of TOML's local date/time. |
|
2 |
// Copied over from https://github.com/googleapis/google-cloud-go/blob/master/civil/civil.go |
|
3 |
// to avoid pulling all the Google dependencies. |
|
4 |
// |
|
5 |
// Copyright 2016 Google LLC |
|
6 |
// |
|
7 |
// Licensed under the Apache License, Version 2.0 (the "License"); |
|
8 |
// you may not use this file except in compliance with the License. |
|
9 |
// You may obtain a copy of the License at |
|
10 |
// |
|
11 |
// http://www.apache.org/licenses/LICENSE-2.0 |
|
12 |
// |
|
13 |
// Unless required by applicable law or agreed to in writing, software |
|
14 |
// distributed under the License is distributed on an "AS IS" BASIS, |
|
15 |
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
16 |
// See the License for the specific language governing permissions and |
|
17 |
// limitations under the License. |
|
18 |
|
|
19 |
// Package civil implements types for civil time, a time-zone-independent |
|
20 |
// representation of time that follows the rules of the proleptic |
|
21 |
// Gregorian calendar with exactly 24-hour days, 60-minute hours, and 60-second |
|
22 |
// minutes. |
|
23 |
// |
|
24 |
// Because they lack location information, these types do not represent unique |
|
25 |
// moments or intervals of time. Use time.Time for that purpose. |
|
26 |
package toml |
|
27 |
|
|
28 |
import ( |
|
29 |
"fmt" |
|
30 |
"time" |
|
31 |
) |
|
32 |
|
|
33 |
// A LocalDate represents a date (year, month, day). |
|
34 |
// |
|
35 |
// This type does not include location information, and therefore does not |
|
36 |
// describe a unique 24-hour timespan. |
|
37 |
type LocalDate struct { |
|
38 |
Year int // Year (e.g., 2014). |
|
39 |
Month time.Month // Month of the year (January = 1, ...). |
|
40 |
Day int // Day of the month, starting at 1. |
|
41 |
} |
|
42 |
|
|
43 |
// LocalDateOf returns the LocalDate in which a time occurs in that time's location. |
|
44 |
func LocalDateOf(t time.Time) LocalDate { |
|
45 |
var d LocalDate |
|
46 |
d.Year, d.Month, d.Day = t.Date() |
|
47 |
return d |
|
48 |
} |
|
49 |
|
|
50 |
// ParseLocalDate parses a string in RFC3339 full-date format and returns the date value it represents. |
|
51 |
func ParseLocalDate(s string) (LocalDate, error) { |
|
52 |
t, err := time.Parse("2006-01-02", s) |
|
53 |
if err != nil { |
|
54 |
return LocalDate{}, err |
|
55 |
} |
|
56 |
return LocalDateOf(t), nil |
|
57 |
} |
|
58 |
|
|
59 |
// String returns the date in RFC3339 full-date format. |
|
60 |
func (d LocalDate) String() string { |
|
61 |
return fmt.Sprintf("%04d-%02d-%02d", d.Year, d.Month, d.Day) |
|
62 |
} |
|
63 |
|
|
64 |
// IsValid reports whether the date is valid. |
|
65 |
func (d LocalDate) IsValid() bool { |
|
66 |
return LocalDateOf(d.In(time.UTC)) == d |
|
67 |
} |
|
68 |
|
|
69 |
// In returns the time corresponding to time 00:00:00 of the date in the location. |
|
70 |
// |
|
71 |
// In is always consistent with time.LocalDate, even when time.LocalDate returns a time |
|
72 |
// on a different day. For example, if loc is America/Indiana/Vincennes, then both |
|
73 |
// time.LocalDate(1955, time.May, 1, 0, 0, 0, 0, loc) |
|
74 |
// and |
|
75 |
// civil.LocalDate{Year: 1955, Month: time.May, Day: 1}.In(loc) |
|
76 |
// return 23:00:00 on April 30, 1955. |
|
77 |
// |
|
78 |
// In panics if loc is nil. |
|
79 |
func (d LocalDate) In(loc *time.Location) time.Time { |
|
80 |
return time.Date(d.Year, d.Month, d.Day, 0, 0, 0, 0, loc) |
|
81 |
} |
|
82 |
|
|
83 |
// AddDays returns the date that is n days in the future. |
|
84 |
// n can also be negative to go into the past. |
|
85 |
func (d LocalDate) AddDays(n int) LocalDate { |
|
86 |
return LocalDateOf(d.In(time.UTC).AddDate(0, 0, n)) |
|
87 |
} |
|
88 |
|
|
89 |
// DaysSince returns the signed number of days between the date and s, not including the end day. |
|
90 |
// This is the inverse operation to AddDays. |
|
91 |
func (d LocalDate) DaysSince(s LocalDate) (days int) { |
|
92 |
// We convert to Unix time so we do not have to worry about leap seconds: |
|
93 |
// Unix time increases by exactly 86400 seconds per day. |
|
94 |
deltaUnix := d.In(time.UTC).Unix() - s.In(time.UTC).Unix() |
|
95 |
return int(deltaUnix / 86400) |
|
96 |
} |
|
97 |
|
|
98 |
// Before reports whether d1 occurs before d2. |
|
99 |
func (d1 LocalDate) Before(d2 LocalDate) bool { |
|
100 |
if d1.Year != d2.Year { |
|
101 |
return d1.Year < d2.Year |
|
102 |
} |
|
103 |
if d1.Month != d2.Month { |
|
104 |
return d1.Month < d2.Month |
|
105 |
} |
|
106 |
return d1.Day < d2.Day |
|
107 |
} |
|
108 |
|
|
109 |
// After reports whether d1 occurs after d2. |
|
110 |
func (d1 LocalDate) After(d2 LocalDate) bool { |
|
111 |
return d2.Before(d1) |
|
112 |
} |
|
113 |
|
|
114 |
// MarshalText implements the encoding.TextMarshaler interface. |
|
115 |
// The output is the result of d.String(). |
|
116 |
func (d LocalDate) MarshalText() ([]byte, error) { |
|
117 |
return []byte(d.String()), nil |
|
118 |
} |
|
119 |
|
|
120 |
// UnmarshalText implements the encoding.TextUnmarshaler interface. |
|
121 |
// The date is expected to be a string in a format accepted by ParseLocalDate. |
|
122 |
func (d *LocalDate) UnmarshalText(data []byte) error { |
|
123 |
var err error |
|
124 |
*d, err = ParseLocalDate(string(data)) |
|
125 |
return err |
|
126 |
} |
|
127 |
|
|
128 |
// A LocalTime represents a time with nanosecond precision. |
|
129 |
// |
|
130 |
// This type does not include location information, and therefore does not |
|
131 |
// describe a unique moment in time. |
|
132 |
// |
|
133 |
// This type exists to represent the TIME type in storage-based APIs like BigQuery. |
|
134 |
// Most operations on Times are unlikely to be meaningful. Prefer the LocalDateTime type. |
|
135 |
type LocalTime struct { |
|
136 |
Hour int // The hour of the day in 24-hour format; range [0-23] |
|
137 |
Minute int // The minute of the hour; range [0-59] |
|
138 |
Second int // The second of the minute; range [0-59] |
|
139 |
Nanosecond int // The nanosecond of the second; range [0-999999999] |
|
140 |
} |
|
141 |
|
|
142 |
// LocalTimeOf returns the LocalTime representing the time of day in which a time occurs |
|
143 |
// in that time's location. It ignores the date. |
|
144 |
func LocalTimeOf(t time.Time) LocalTime { |
|
145 |
var tm LocalTime |
|
146 |
tm.Hour, tm.Minute, tm.Second = t.Clock() |
|
147 |
tm.Nanosecond = t.Nanosecond() |
|
148 |
return tm |
|
149 |
} |
|
150 |
|
|
151 |
// ParseLocalTime parses a string and returns the time value it represents. |
|
152 |
// ParseLocalTime accepts an extended form of the RFC3339 partial-time format. After |
|
153 |
// the HH:MM:SS part of the string, an optional fractional part may appear, |
|
154 |
// consisting of a decimal point followed by one to nine decimal digits. |
|
155 |
// (RFC3339 admits only one digit after the decimal point). |
|
156 |
func ParseLocalTime(s string) (LocalTime, error) { |
|
157 |
t, err := time.Parse("15:04:05.999999999", s) |
|
158 |
if err != nil { |
|
159 |
return LocalTime{}, err |
|
160 |
} |
|
161 |
return LocalTimeOf(t), nil |
|
162 |
} |
|
163 |
|
|
164 |
// String returns the date in the format described in ParseLocalTime. If Nanoseconds |
|
165 |
// is zero, no fractional part will be generated. Otherwise, the result will |
|
166 |
// end with a fractional part consisting of a decimal point and nine digits. |
|
167 |
func (t LocalTime) String() string { |
|
168 |
s := fmt.Sprintf("%02d:%02d:%02d", t.Hour, t.Minute, t.Second) |
|
169 |
if t.Nanosecond == 0 { |
|
170 |
return s |
|
171 |
} |
|
172 |
return s + fmt.Sprintf(".%09d", t.Nanosecond) |
|
173 |
} |
|
174 |
|
|
175 |
// IsValid reports whether the time is valid. |
|
176 |
func (t LocalTime) IsValid() bool { |
|
177 |
// Construct a non-zero time. |
|
178 |
tm := time.Date(2, 2, 2, t.Hour, t.Minute, t.Second, t.Nanosecond, time.UTC) |
|
179 |
return LocalTimeOf(tm) == t |
|
180 |
} |
|
181 |
|
|
182 |
// MarshalText implements the encoding.TextMarshaler interface. |
|
183 |
// The output is the result of t.String(). |
|
184 |
func (t LocalTime) MarshalText() ([]byte, error) { |
|
185 |
return []byte(t.String()), nil |
|
186 |
} |
|
187 |
|
|
188 |
// UnmarshalText implements the encoding.TextUnmarshaler interface. |
|
189 |
// The time is expected to be a string in a format accepted by ParseLocalTime. |
|
190 |
func (t *LocalTime) UnmarshalText(data []byte) error { |
|
191 |
var err error |
|
192 |
*t, err = ParseLocalTime(string(data)) |
|
193 |
return err |
|
194 |
} |
|
195 |
|
|
196 |
// A LocalDateTime represents a date and time. |
|
197 |
// |
|
198 |
// This type does not include location information, and therefore does not |
|
199 |
// describe a unique moment in time. |
|
200 |
type LocalDateTime struct { |
|
201 |
Date LocalDate |
|
202 |
Time LocalTime |
|
203 |
} |
|
204 |
|
|
205 |
// Note: We deliberately do not embed LocalDate into LocalDateTime, to avoid promoting AddDays and Sub. |
|
206 |
|
|
207 |
// LocalDateTimeOf returns the LocalDateTime in which a time occurs in that time's location. |
|
208 |
func LocalDateTimeOf(t time.Time) LocalDateTime { |
|
209 |
return LocalDateTime{ |
|
210 |
Date: LocalDateOf(t), |
|
211 |
Time: LocalTimeOf(t), |
|
212 |
} |
|
213 |
} |
|
214 |
|
|
215 |
// ParseLocalDateTime parses a string and returns the LocalDateTime it represents. |
|
216 |
// ParseLocalDateTime accepts a variant of the RFC3339 date-time format that omits |
|
217 |
// the time offset but includes an optional fractional time, as described in |
|
218 |
// ParseLocalTime. Informally, the accepted format is |
|
219 |
// YYYY-MM-DDTHH:MM:SS[.FFFFFFFFF] |
|
220 |
// where the 'T' may be a lower-case 't'. |
|
221 |
func ParseLocalDateTime(s string) (LocalDateTime, error) { |
|
222 |
t, err := time.Parse("2006-01-02T15:04:05.999999999", s) |
|
223 |
if err != nil { |
|
224 |
t, err = time.Parse("2006-01-02t15:04:05.999999999", s) |
|
225 |
if err != nil { |
|
226 |
return LocalDateTime{}, err |
|
227 |
} |
|
228 |
} |
|
229 |
return LocalDateTimeOf(t), nil |
|
230 |
} |
|
231 |
|
|
232 |
// String returns the date in the format described in ParseLocalDate. |
|
233 |
func (dt LocalDateTime) String() string { |
|
234 |
return dt.Date.String() + "T" + dt.Time.String() |
|
235 |
} |
|
236 |
|
|
237 |
// IsValid reports whether the datetime is valid. |
|
238 |
func (dt LocalDateTime) IsValid() bool { |
|
239 |
return dt.Date.IsValid() && dt.Time.IsValid() |
|
240 |
} |
|
241 |
|
|
242 |
// In returns the time corresponding to the LocalDateTime in the given location. |
|
243 |
// |
|
244 |
// If the time is missing or ambigous at the location, In returns the same |
|
245 |
// result as time.LocalDate. For example, if loc is America/Indiana/Vincennes, then |
|
246 |
// both |
|
247 |
// time.LocalDate(1955, time.May, 1, 0, 30, 0, 0, loc) |
|
248 |
// and |
|
249 |
// civil.LocalDateTime{ |
|
250 |
// civil.LocalDate{Year: 1955, Month: time.May, Day: 1}}, |
|
251 |
// civil.LocalTime{Minute: 30}}.In(loc) |
|
252 |
// return 23:30:00 on April 30, 1955. |
|
253 |
// |
|
254 |
// In panics if loc is nil. |
|
255 |
func (dt LocalDateTime) In(loc *time.Location) time.Time { |
|
256 |
return time.Date(dt.Date.Year, dt.Date.Month, dt.Date.Day, dt.Time.Hour, dt.Time.Minute, dt.Time.Second, dt.Time.Nanosecond, loc) |
|
257 |
} |
|
258 |
|
|
259 |
// Before reports whether dt1 occurs before dt2. |
|
260 |
func (dt1 LocalDateTime) Before(dt2 LocalDateTime) bool { |
|
261 |
return dt1.In(time.UTC).Before(dt2.In(time.UTC)) |
|
262 |
} |
|
263 |
|
|
264 |
// After reports whether dt1 occurs after dt2. |
|
265 |
func (dt1 LocalDateTime) After(dt2 LocalDateTime) bool { |
|
266 |
return dt2.Before(dt1) |
|
267 |
} |
|
268 |
|
|
269 |
// MarshalText implements the encoding.TextMarshaler interface. |
|
270 |
// The output is the result of dt.String(). |
|
271 |
func (dt LocalDateTime) MarshalText() ([]byte, error) { |
|
272 |
return []byte(dt.String()), nil |
|
273 |
} |
|
274 |
|
|
275 |
// UnmarshalText implements the encoding.TextUnmarshaler interface. |
|
276 |
// The datetime is expected to be a string in a format accepted by ParseLocalDateTime |
|
277 |
func (dt *LocalDateTime) UnmarshalText(data []byte) error { |
|
278 |
var err error |
|
279 |
*dt, err = ParseLocalDateTime(string(data)) |
|
280 |
return err |
|
281 |
} |