civil.go

  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}