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