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