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