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. 32 package toml 33 34 import ( 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. 43 type 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. 50 func 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. 57 func 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. 66 func (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. 71 func (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. 85 func (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. 91 func (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. 97 func (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. 105 func (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. 116 func (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(). 122 func (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. 128 func (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. 141 type 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. 150 func 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). 162 func 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. 173 func (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. 182 func (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(). 190 func (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. 196 func (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. 206 type 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. 214 func 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'. 227 func 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. 239 func (dt LocalDateTime) String() string { 240 return dt.Date.String() + "T" + dt.Time.String() 241 } 242 243 // IsValid reports whether the datetime is valid. 244 func (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. 261 func (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. 266 func (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. 271 func (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(). 277 func (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 283 func (dt *LocalDateTime) UnmarshalText(data []byte) error { 284 var err error 285 *dt, err = ParseLocalDateTime(string(data)) 286 return err 287 } 288