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. 22 package civil 23 24 import ( 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. 33 type 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. 40 func 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. 47 func 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. 56 func (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. 61 func (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 // 70 // time.Date(1955, time.May, 1, 0, 0, 0, 0, loc) 71 // 72 // and 73 // 74 // civil.Date{Year: 1955, Month: time.May, Day: 1}.In(loc) 75 // 76 // return 23:00:00 on April 30, 1955. 77 // 78 // In panics if loc is nil. 79 func (d Date) 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. 85 func (d Date) AddDays(n int) Date { 86 return DateOf(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. 91 func (d Date) DaysSince(s Date) (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 d occurs before d2. 99 func (d Date) Before(d2 Date) bool { 100 if d.Year != d2.Year { 101 return d.Year < d2.Year 102 } 103 if d.Month != d2.Month { 104 return d.Month < d2.Month 105 } 106 return d.Day < d2.Day 107 } 108 109 // After reports whether d occurs after d2. 110 func (d Date) After(d2 Date) bool { 111 return d2.Before(d) 112 } 113 114 // IsZero reports whether date fields are set to their default value. 115 func (d Date) IsZero() bool { 116 return (d.Year == 0) && (int(d.Month) == 0) && (d.Day == 0) 117 } 118 119 // MarshalText implements the encoding.TextMarshaler interface. 120 // The output is the result of d.String(). 121 func (d Date) MarshalText() ([]byte, error) { 122 return []byte(d.String()), nil 123 } 124 125 // UnmarshalText implements the encoding.TextUnmarshaler interface. 126 // The date is expected to be a string in a format accepted by ParseDate. 127 func (d *Date) UnmarshalText(data []byte) error { 128 var err error 129 *d, err = ParseDate(string(data)) 130 return err 131 } 132 133 // A Time represents a time with nanosecond precision. 134 // 135 // This type does not include location information, and therefore does not 136 // describe a unique moment in time. 137 // 138 // This type exists to represent the TIME type in storage-based APIs like BigQuery. 139 // Most operations on Times are unlikely to be meaningful. Prefer the DateTime type. 140 type Time struct { 141 Hour int // The hour of the day in 24-hour format; range [0-23] 142 Minute int // The minute of the hour; range [0-59] 143 Second int // The second of the minute; range [0-59] 144 Nanosecond int // The nanosecond of the second; range [0-999999999] 145 } 146 147 // TimeOf returns the Time representing the time of day in which a time occurs 148 // in that time's location. It ignores the date. 149 func TimeOf(t time.Time) Time { 150 var tm Time 151 tm.Hour, tm.Minute, tm.Second = t.Clock() 152 tm.Nanosecond = t.Nanosecond() 153 return tm 154 } 155 156 // ParseTime parses a string and returns the time value it represents. 157 // ParseTime accepts an extended form of the RFC3339 partial-time format. After 158 // the HH:MM:SS part of the string, an optional fractional part may appear, 159 // consisting of a decimal point followed by one to nine decimal digits. 160 // (RFC3339 admits only one digit after the decimal point). 161 func ParseTime(s string) (Time, error) { 162 t, err := time.Parse("15:04:05.999999999", s) 163 if err != nil { 164 return Time{}, err 165 } 166 return TimeOf(t), nil 167 } 168 169 // String returns the date in the format described in ParseTime. If Nanoseconds 170 // is zero, no fractional part will be generated. Otherwise, the result will 171 // end with a fractional part consisting of a decimal point and nine digits. 172 func (t Time) String() string { 173 s := fmt.Sprintf("%02d:%02d:%02d", t.Hour, t.Minute, t.Second) 174 if t.Nanosecond == 0 { 175 return s 176 } 177 return s + fmt.Sprintf(".%09d", t.Nanosecond) 178 } 179 180 // IsValid reports whether the time is valid. 181 func (t Time) IsValid() bool { 182 // Construct a non-zero time. 183 tm := time.Date(2, 2, 2, t.Hour, t.Minute, t.Second, t.Nanosecond, time.UTC) 184 return TimeOf(tm) == t 185 } 186 187 // IsZero reports whether time fields are set to their default value. 188 func (t Time) IsZero() bool { 189 return (t.Hour == 0) && (t.Minute == 0) && (t.Second == 0) && (t.Nanosecond == 0) 190 } 191 192 // Before reports whether t occurs before t2. 193 func (t Time) Before(t2 Time) bool { 194 if t.Hour != t2.Hour { 195 return t.Hour < t2.Hour 196 } 197 if t.Minute != t2.Minute { 198 return t.Minute < t2.Minute 199 } 200 if t.Second != t2.Second { 201 return t.Second < t2.Second 202 } 203 204 return t.Nanosecond < t2.Nanosecond 205 } 206 207 // After reports whether t occurs after t2. 208 func (t Time) After(t2 Time) bool { 209 return t2.Before(t) 210 } 211 212 // MarshalText implements the encoding.TextMarshaler interface. 213 // The output is the result of t.String(). 214 func (t Time) MarshalText() ([]byte, error) { 215 return []byte(t.String()), nil 216 } 217 218 // UnmarshalText implements the encoding.TextUnmarshaler interface. 219 // The time is expected to be a string in a format accepted by ParseTime. 220 func (t *Time) UnmarshalText(data []byte) error { 221 var err error 222 *t, err = ParseTime(string(data)) 223 return err 224 } 225 226 // A DateTime represents a date and time. 227 // 228 // This type does not include location information, and therefore does not 229 // describe a unique moment in time. 230 type DateTime struct { 231 Date Date 232 Time Time 233 } 234 235 // Note: We deliberately do not embed Date into DateTime, to avoid promoting AddDays and Sub. 236 237 // DateTimeOf returns the DateTime in which a time occurs in that time's location. 238 func DateTimeOf(t time.Time) DateTime { 239 return DateTime{ 240 Date: DateOf(t), 241 Time: TimeOf(t), 242 } 243 } 244 245 // ParseDateTime parses a string and returns the DateTime it represents. 246 // ParseDateTime accepts a variant of the RFC3339 date-time format that omits 247 // the time offset but includes an optional fractional time, as described in 248 // ParseTime. Informally, the accepted format is 249 // 250 // YYYY-MM-DDTHH:MM:SS[.FFFFFFFFF] 251 // 252 // where the 'T' may be a lower-case 't'. 253 func ParseDateTime(s string) (DateTime, error) { 254 t, err := time.Parse("2006-01-02T15:04:05.999999999", s) 255 if err != nil { 256 t, err = time.Parse("2006-01-02t15:04:05.999999999", s) 257 if err != nil { 258 return DateTime{}, err 259 } 260 } 261 return DateTimeOf(t), nil 262 } 263 264 // String returns the date in the format described in ParseDate. 265 func (dt DateTime) String() string { 266 return dt.Date.String() + "T" + dt.Time.String() 267 } 268 269 // IsValid reports whether the datetime is valid. 270 func (dt DateTime) IsValid() bool { 271 return dt.Date.IsValid() && dt.Time.IsValid() 272 } 273 274 // In returns the time corresponding to the DateTime in the given location. 275 // 276 // If the time is missing or ambigous at the location, In returns the same 277 // result as time.Date. For example, if loc is America/Indiana/Vincennes, then 278 // both 279 // 280 // time.Date(1955, time.May, 1, 0, 30, 0, 0, loc) 281 // 282 // and 283 // 284 // civil.DateTime{ 285 // civil.Date{Year: 1955, Month: time.May, Day: 1}}, 286 // civil.Time{Minute: 30}}.In(loc) 287 // 288 // return 23:30:00 on April 30, 1955. 289 // 290 // In panics if loc is nil. 291 func (dt DateTime) In(loc *time.Location) time.Time { 292 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) 293 } 294 295 // Before reports whether dt occurs before dt2. 296 func (dt DateTime) Before(dt2 DateTime) bool { 297 return dt.In(time.UTC).Before(dt2.In(time.UTC)) 298 } 299 300 // After reports whether dt occurs after dt2. 301 func (dt DateTime) After(dt2 DateTime) bool { 302 return dt2.Before(dt) 303 } 304 305 // IsZero reports whether datetime fields are set to their default value. 306 func (dt DateTime) IsZero() bool { 307 return dt.Date.IsZero() && dt.Time.IsZero() 308 } 309 310 // MarshalText implements the encoding.TextMarshaler interface. 311 // The output is the result of dt.String(). 312 func (dt DateTime) MarshalText() ([]byte, error) { 313 return []byte(dt.String()), nil 314 } 315 316 // UnmarshalText implements the encoding.TextUnmarshaler interface. 317 // The datetime is expected to be a string in a format accepted by ParseDateTime 318 func (dt *DateTime) UnmarshalText(data []byte) error { 319 var err error 320 *dt, err = ParseDateTime(string(data)) 321 return err 322 } 323