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//     time.Date(1955, time.May, 1, 0, 0, 0, 0, loc)
70// and
71//     civil.Date{Year: 1955, Month: time.May, Day: 1}.In(loc)
72// return 23:00:00 on April 30, 1955.
73//
74// In panics if loc is nil.
75func (d Date) In(loc *time.Location) time.Time {
76	return time.Date(d.Year, d.Month, d.Day, 0, 0, 0, 0, loc)
77}
78
79// AddDays returns the date that is n days in the future.
80// n can also be negative to go into the past.
81func (d Date) AddDays(n int) Date {
82	return DateOf(d.In(time.UTC).AddDate(0, 0, n))
83}
84
85// DaysSince returns the signed number of days between the date and s, not including the end day.
86// This is the inverse operation to AddDays.
87func (d Date) DaysSince(s Date) (days int) {
88	// We convert to Unix time so we do not have to worry about leap seconds:
89	// Unix time increases by exactly 86400 seconds per day.
90	deltaUnix := d.In(time.UTC).Unix() - s.In(time.UTC).Unix()
91	return int(deltaUnix / 86400)
92}
93
94// Before reports whether d1 occurs before d2.
95func (d1 Date) Before(d2 Date) bool {
96	if d1.Year != d2.Year {
97		return d1.Year < d2.Year
98	}
99	if d1.Month != d2.Month {
100		return d1.Month < d2.Month
101	}
102	return d1.Day < d2.Day
103}
104
105// After reports whether d1 occurs after d2.
106func (d1 Date) After(d2 Date) bool {
107	return d2.Before(d1)
108}
109
110// MarshalText implements the encoding.TextMarshaler interface.
111// The output is the result of d.String().
112func (d Date) MarshalText() ([]byte, error) {
113	return []byte(d.String()), nil
114}
115
116// UnmarshalText implements the encoding.TextUnmarshaler interface.
117// The date is expected to be a string in a format accepted by ParseDate.
118func (d *Date) UnmarshalText(data []byte) error {
119	var err error
120	*d, err = ParseDate(string(data))
121	return err
122}
123
124// A Time represents a time with nanosecond precision.
125//
126// This type does not include location information, and therefore does not
127// describe a unique moment in time.
128//
129// This type exists to represent the TIME type in storage-based APIs like BigQuery.
130// Most operations on Times are unlikely to be meaningful. Prefer the DateTime type.
131type Time struct {
132	Hour       int // The hour of the day in 24-hour format; range [0-23]
133	Minute     int // The minute of the hour; range [0-59]
134	Second     int // The second of the minute; range [0-59]
135	Nanosecond int // The nanosecond of the second; range [0-999999999]
136}
137
138// TimeOf returns the Time representing the time of day in which a time occurs
139// in that time's location. It ignores the date.
140func TimeOf(t time.Time) Time {
141	var tm Time
142	tm.Hour, tm.Minute, tm.Second = t.Clock()
143	tm.Nanosecond = t.Nanosecond()
144	return tm
145}
146
147// ParseTime parses a string and returns the time value it represents.
148// ParseTime accepts an extended form of the RFC3339 partial-time format. After
149// the HH:MM:SS part of the string, an optional fractional part may appear,
150// consisting of a decimal point followed by one to nine decimal digits.
151// (RFC3339 admits only one digit after the decimal point).
152func ParseTime(s string) (Time, error) {
153	t, err := time.Parse("15:04:05.999999999", s)
154	if err != nil {
155		return Time{}, err
156	}
157	return TimeOf(t), nil
158}
159
160// String returns the date in the format described in ParseTime. If Nanoseconds
161// is zero, no fractional part will be generated. Otherwise, the result will
162// end with a fractional part consisting of a decimal point and nine digits.
163func (t Time) String() string {
164	s := fmt.Sprintf("%02d:%02d:%02d", t.Hour, t.Minute, t.Second)
165	if t.Nanosecond == 0 {
166		return s
167	}
168	return s + fmt.Sprintf(".%09d", t.Nanosecond)
169}
170
171// IsValid reports whether the time is valid.
172func (t Time) IsValid() bool {
173	// Construct a non-zero time.
174	tm := time.Date(2, 2, 2, t.Hour, t.Minute, t.Second, t.Nanosecond, time.UTC)
175	return TimeOf(tm) == t
176}
177
178// MarshalText implements the encoding.TextMarshaler interface.
179// The output is the result of t.String().
180func (t Time) MarshalText() ([]byte, error) {
181	return []byte(t.String()), nil
182}
183
184// UnmarshalText implements the encoding.TextUnmarshaler interface.
185// The time is expected to be a string in a format accepted by ParseTime.
186func (t *Time) UnmarshalText(data []byte) error {
187	var err error
188	*t, err = ParseTime(string(data))
189	return err
190}
191
192// A DateTime represents a date and time.
193//
194// This type does not include location information, and therefore does not
195// describe a unique moment in time.
196type DateTime struct {
197	Date Date
198	Time Time
199}
200
201// Note: We deliberately do not embed Date into DateTime, to avoid promoting AddDays and Sub.
202
203// DateTimeOf returns the DateTime in which a time occurs in that time's location.
204func DateTimeOf(t time.Time) DateTime {
205	return DateTime{
206		Date: DateOf(t),
207		Time: TimeOf(t),
208	}
209}
210
211// ParseDateTime parses a string and returns the DateTime it represents.
212// ParseDateTime accepts a variant of the RFC3339 date-time format that omits
213// the time offset but includes an optional fractional time, as described in
214// ParseTime. Informally, the accepted format is
215//     YYYY-MM-DDTHH:MM:SS[.FFFFFFFFF]
216// where the 'T' may be a lower-case 't'.
217func ParseDateTime(s string) (DateTime, error) {
218	t, err := time.Parse("2006-01-02T15:04:05.999999999", s)
219	if err != nil {
220		t, err = time.Parse("2006-01-02t15:04:05.999999999", s)
221		if err != nil {
222			return DateTime{}, err
223		}
224	}
225	return DateTimeOf(t), nil
226}
227
228// String returns the date in the format described in ParseDate.
229func (dt DateTime) String() string {
230	return dt.Date.String() + "T" + dt.Time.String()
231}
232
233// IsValid reports whether the datetime is valid.
234func (dt DateTime) IsValid() bool {
235	return dt.Date.IsValid() && dt.Time.IsValid()
236}
237
238// In returns the time corresponding to the DateTime in the given location.
239//
240// If the time is missing or ambigous at the location, In returns the same
241// result as time.Date. For example, if loc is America/Indiana/Vincennes, then
242// both
243//     time.Date(1955, time.May, 1, 0, 30, 0, 0, loc)
244// and
245//     civil.DateTime{
246//         civil.Date{Year: 1955, Month: time.May, Day: 1}},
247//         civil.Time{Minute: 30}}.In(loc)
248// return 23:30:00 on April 30, 1955.
249//
250// In panics if loc is nil.
251func (dt DateTime) In(loc *time.Location) time.Time {
252	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)
253}
254
255// Before reports whether dt1 occurs before dt2.
256func (dt1 DateTime) Before(dt2 DateTime) bool {
257	return dt1.In(time.UTC).Before(dt2.In(time.UTC))
258}
259
260// After reports whether dt1 occurs after dt2.
261func (dt1 DateTime) After(dt2 DateTime) bool {
262	return dt2.Before(dt1)
263}
264
265// MarshalText implements the encoding.TextMarshaler interface.
266// The output is the result of dt.String().
267func (dt DateTime) MarshalText() ([]byte, error) {
268	return []byte(dt.String()), nil
269}
270
271// UnmarshalText implements the encoding.TextUnmarshaler interface.
272// The datetime is expected to be a string in a format accepted by ParseDateTime
273func (dt *DateTime) UnmarshalText(data []byte) error {
274	var err error
275	*dt, err = ParseDateTime(string(data))
276	return err
277}
278