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.
26package toml
27
28import (
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.
37type 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.
44func 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.
51func 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.
60func (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.
65func (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.
79func (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.
85func (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.
91func (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.
99func (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.
110func (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().
116func (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.
122func (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.
135type 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.
144func 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).
156func 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.
167func (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.
176func (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().
184func (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.
190func (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.
200type 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.
208func 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'.
221func 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.
233func (dt LocalDateTime) String() string {
234	return dt.Date.String() + "T" + dt.Time.String()
235}
236
237// IsValid reports whether the datetime is valid.
238func (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.
255func (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.
260func (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.
265func (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().
271func (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
277func (dt *LocalDateTime) UnmarshalText(data []byte) error {
278	var err error
279	*dt, err = ParseLocalDateTime(string(data))
280	return err
281}
282