1// Copyright 2016 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Package civil implements types for civil time, a time-zone-independent
16// representation of time that follows the rules of the proleptic
17// Gregorian calendar with exactly 24-hour days, 60-minute hours, and 60-second
18// minutes.
19//
20// Because they lack location information, these types do not represent unique
21// moments or intervals of time. Use time.Time for that purpose.
22package civil
23
24import (
25 "fmt"
26 "time"
27)
28
29// A Date represents a date (year, month, day).
30//
31// This type does not include location information, and therefore does not
32// describe a unique 24-hour timespan.
33type Date struct {
34 Year int // Year (e.g., 2014).
35 Month time.Month // Month of the year (January = 1, ...).
36 Day int // Day of the month, starting at 1.
37}
38
39// DateOf returns the Date in which a time occurs in that time's location.
40func DateOf(t time.Time) Date {
41 var d Date
42 d.Year, d.Month, d.Day = t.Date()
43 return d
44}
45
46// ParseDate parses a string in RFC3339 full-date format and returns the date value it represents.
47func ParseDate(s string) (Date, error) {
48 t, err := time.Parse("2006-01-02", s)
49 if err != nil {
50 return Date{}, err
51 }
52 return DateOf(t), nil
53}
54
55// String returns the date in RFC3339 full-date format.
56func (d Date) String() string {
57 return fmt.Sprintf("%04d-%02d-%02d", d.Year, d.Month, d.Day)
58}
59
60// IsValid reports whether the date is valid.
61func (d Date) IsValid() bool {
62 return DateOf(d.In(time.UTC)) == d
63}
64
65// In returns the time corresponding to time 00:00:00 of the date in the location.
66//
67// In is always consistent with time.Date, even when time.Date returns a time
68// on a different day. For example, if loc is America/Indiana/Vincennes, then both
69//
70// time.Date(1955, time.May, 1, 0, 0, 0, 0, loc)
71//
72// and
73//
74// civil.Date{Year: 1955, Month: time.May, Day: 1}.In(loc)
75//
76// return 23:00:00 on April 30, 1955.
77//
78// In panics if loc is nil.
79func (d Date) 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.
85func (d Date) AddDays(n int) Date {
86 return DateOf(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.
91func (d Date) DaysSince(s Date) (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 d occurs before d2.
99func (d Date) Before(d2 Date) bool {
100 if d.Year != d2.Year {
101 return d.Year < d2.Year
102 }
103 if d.Month != d2.Month {
104 return d.Month < d2.Month
105 }
106 return d.Day < d2.Day
107}
108
109// After reports whether d occurs after d2.
110func (d Date) After(d2 Date) bool {
111 return d2.Before(d)
112}
113
114// Compare compares d and d2. If d is before d2, it returns -1;
115// if d is after d2, it returns +1; otherwise it returns 0.
116func (d Date) Compare(d2 Date) int {
117 if d.Before(d2) {
118 return -1
119 } else if d.After(d2) {
120 return +1
121 }
122 return 0
123}
124
125// IsZero reports whether date fields are set to their default value.
126func (d Date) IsZero() bool {
127 return (d.Year == 0) && (int(d.Month) == 0) && (d.Day == 0)
128}
129
130// MarshalText implements the encoding.TextMarshaler interface.
131// The output is the result of d.String().
132func (d Date) MarshalText() ([]byte, error) {
133 return []byte(d.String()), nil
134}
135
136// UnmarshalText implements the encoding.TextUnmarshaler interface.
137// The date is expected to be a string in a format accepted by ParseDate.
138func (d *Date) UnmarshalText(data []byte) error {
139 var err error
140 *d, err = ParseDate(string(data))
141 return err
142}
143
144// A Time represents a time with nanosecond precision.
145//
146// This type does not include location information, and therefore does not
147// describe a unique moment in time.
148//
149// This type exists to represent the TIME type in storage-based APIs like BigQuery.
150// Most operations on Times are unlikely to be meaningful. Prefer the DateTime type.
151type Time struct {
152 Hour int // The hour of the day in 24-hour format; range [0-23]
153 Minute int // The minute of the hour; range [0-59]
154 Second int // The second of the minute; range [0-59]
155 Nanosecond int // The nanosecond of the second; range [0-999999999]
156}
157
158// TimeOf returns the Time representing the time of day in which a time occurs
159// in that time's location. It ignores the date.
160func TimeOf(t time.Time) Time {
161 var tm Time
162 tm.Hour, tm.Minute, tm.Second = t.Clock()
163 tm.Nanosecond = t.Nanosecond()
164 return tm
165}
166
167// ParseTime parses a string and returns the time value it represents.
168// ParseTime accepts an extended form of the RFC3339 partial-time format. After
169// the HH:MM:SS part of the string, an optional fractional part may appear,
170// consisting of a decimal point followed by one to nine decimal digits.
171// (RFC3339 admits only one digit after the decimal point).
172func ParseTime(s string) (Time, error) {
173 t, err := time.Parse("15:04:05.999999999", s)
174 if err != nil {
175 return Time{}, err
176 }
177 return TimeOf(t), nil
178}
179
180// String returns the date in the format described in ParseTime. If Nanoseconds
181// is zero, no fractional part will be generated. Otherwise, the result will
182// end with a fractional part consisting of a decimal point and nine digits.
183func (t Time) String() string {
184 s := fmt.Sprintf("%02d:%02d:%02d", t.Hour, t.Minute, t.Second)
185 if t.Nanosecond == 0 {
186 return s
187 }
188 return s + fmt.Sprintf(".%09d", t.Nanosecond)
189}
190
191// IsValid reports whether the time is valid.
192func (t Time) IsValid() bool {
193 // Construct a non-zero time.
194 tm := time.Date(2, 2, 2, t.Hour, t.Minute, t.Second, t.Nanosecond, time.UTC)
195 return TimeOf(tm) == t
196}
197
198// IsZero reports whether time fields are set to their default value.
199func (t Time) IsZero() bool {
200 return (t.Hour == 0) && (t.Minute == 0) && (t.Second == 0) && (t.Nanosecond == 0)
201}
202
203// Before reports whether t occurs before t2.
204func (t Time) Before(t2 Time) bool {
205 if t.Hour != t2.Hour {
206 return t.Hour < t2.Hour
207 }
208 if t.Minute != t2.Minute {
209 return t.Minute < t2.Minute
210 }
211 if t.Second != t2.Second {
212 return t.Second < t2.Second
213 }
214
215 return t.Nanosecond < t2.Nanosecond
216}
217
218// After reports whether t occurs after t2.
219func (t Time) After(t2 Time) bool {
220 return t2.Before(t)
221}
222
223// Compare compares t and t2. If t is before t2, it returns -1;
224// if t is after t2, it returns +1; otherwise it returns 0.
225func (t Time) Compare(t2 Time) int {
226 if t.Before(t2) {
227 return -1
228 } else if t.After(t2) {
229 return +1
230 }
231 return 0
232}
233
234// MarshalText implements the encoding.TextMarshaler interface.
235// The output is the result of t.String().
236func (t Time) MarshalText() ([]byte, error) {
237 return []byte(t.String()), nil
238}
239
240// UnmarshalText implements the encoding.TextUnmarshaler interface.
241// The time is expected to be a string in a format accepted by ParseTime.
242func (t *Time) UnmarshalText(data []byte) error {
243 var err error
244 *t, err = ParseTime(string(data))
245 return err
246}
247
248// A DateTime represents a date and time.
249//
250// This type does not include location information, and therefore does not
251// describe a unique moment in time.
252type DateTime struct {
253 Date Date
254 Time Time
255}
256
257// Note: We deliberately do not embed Date into DateTime, to avoid promoting AddDays and Sub.
258
259// DateTimeOf returns the DateTime in which a time occurs in that time's location.
260func DateTimeOf(t time.Time) DateTime {
261 return DateTime{
262 Date: DateOf(t),
263 Time: TimeOf(t),
264 }
265}
266
267// ParseDateTime parses a string and returns the DateTime it represents.
268// ParseDateTime accepts a variant of the RFC3339 date-time format that omits
269// the time offset but includes an optional fractional time, as described in
270// ParseTime. Informally, the accepted format is
271//
272// YYYY-MM-DDTHH:MM:SS[.FFFFFFFFF]
273//
274// where the 'T' may be a lower-case 't'.
275func ParseDateTime(s string) (DateTime, error) {
276 t, err := time.Parse("2006-01-02T15:04:05.999999999", s)
277 if err != nil {
278 t, err = time.Parse("2006-01-02t15:04:05.999999999", s)
279 if err != nil {
280 return DateTime{}, err
281 }
282 }
283 return DateTimeOf(t), nil
284}
285
286// String returns the date in the format described in ParseDate.
287func (dt DateTime) String() string {
288 return dt.Date.String() + "T" + dt.Time.String()
289}
290
291// IsValid reports whether the datetime is valid.
292func (dt DateTime) IsValid() bool {
293 return dt.Date.IsValid() && dt.Time.IsValid()
294}
295
296// In returns the time corresponding to the DateTime in the given location.
297//
298// If the time is missing or ambigous at the location, In returns the same
299// result as time.Date. For example, if loc is America/Indiana/Vincennes, then
300// both
301//
302// time.Date(1955, time.May, 1, 0, 30, 0, 0, loc)
303//
304// and
305//
306// civil.DateTime{
307// civil.Date{Year: 1955, Month: time.May, Day: 1}},
308// civil.Time{Minute: 30}}.In(loc)
309//
310// return 23:30:00 on April 30, 1955.
311//
312// In panics if loc is nil.
313func (dt DateTime) In(loc *time.Location) time.Time {
314 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)
315}
316
317// Before reports whether dt occurs before dt2.
318func (dt DateTime) Before(dt2 DateTime) bool {
319 return dt.In(time.UTC).Before(dt2.In(time.UTC))
320}
321
322// After reports whether dt occurs after dt2.
323func (dt DateTime) After(dt2 DateTime) bool {
324 return dt2.Before(dt)
325}
326
327// Compare compares dt and dt2. If dt is before dt2, it returns -1;
328// if dt is after dt2, it returns +1; otherwise it returns 0.
329func (dt DateTime) Compare(dt2 DateTime) int {
330 return dt.In(time.UTC).Compare(dt2.In(time.UTC))
331}
332
333// IsZero reports whether datetime fields are set to their default value.
334func (dt DateTime) IsZero() bool {
335 return dt.Date.IsZero() && dt.Time.IsZero()
336}
337
338// MarshalText implements the encoding.TextMarshaler interface.
339// The output is the result of dt.String().
340func (dt DateTime) MarshalText() ([]byte, error) {
341 return []byte(dt.String()), nil
342}
343
344// UnmarshalText implements the encoding.TextUnmarshaler interface.
345// The datetime is expected to be a string in a format accepted by ParseDateTime
346func (dt *DateTime) UnmarshalText(data []byte) error {
347 var err error
348 *dt, err = ParseDateTime(string(data))
349 return err
350}