You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

277 lines
9.0 KiB

  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. // Package civil implements types for civil time, a time-zone-independent
  15. // representation of time that follows the rules of the proleptic
  16. // Gregorian calendar with exactly 24-hour days, 60-minute hours, and 60-second
  17. // minutes.
  18. //
  19. // Because they lack location information, these types do not represent unique
  20. // moments or intervals of time. Use time.Time for that purpose.
  21. package civil
  22. import (
  23. "fmt"
  24. "time"
  25. )
  26. // A Date represents a date (year, month, day).
  27. //
  28. // This type does not include location information, and therefore does not
  29. // describe a unique 24-hour timespan.
  30. type Date struct {
  31. Year int // Year (e.g., 2014).
  32. Month time.Month // Month of the year (January = 1, ...).
  33. Day int // Day of the month, starting at 1.
  34. }
  35. // DateOf returns the Date in which a time occurs in that time's location.
  36. func DateOf(t time.Time) Date {
  37. var d Date
  38. d.Year, d.Month, d.Day = t.Date()
  39. return d
  40. }
  41. // ParseDate parses a string in RFC3339 full-date format and returns the date value it represents.
  42. func ParseDate(s string) (Date, error) {
  43. t, err := time.Parse("2006-01-02", s)
  44. if err != nil {
  45. return Date{}, err
  46. }
  47. return DateOf(t), nil
  48. }
  49. // String returns the date in RFC3339 full-date format.
  50. func (d Date) String() string {
  51. return fmt.Sprintf("%04d-%02d-%02d", d.Year, d.Month, d.Day)
  52. }
  53. // IsValid reports whether the date is valid.
  54. func (d Date) IsValid() bool {
  55. return DateOf(d.In(time.UTC)) == d
  56. }
  57. // In returns the time corresponding to time 00:00:00 of the date in the location.
  58. //
  59. // In is always consistent with time.Date, even when time.Date returns a time
  60. // on a different day. For example, if loc is America/Indiana/Vincennes, then both
  61. // time.Date(1955, time.May, 1, 0, 0, 0, 0, loc)
  62. // and
  63. // civil.Date{Year: 1955, Month: time.May, Day: 1}.In(loc)
  64. // return 23:00:00 on April 30, 1955.
  65. //
  66. // In panics if loc is nil.
  67. func (d Date) In(loc *time.Location) time.Time {
  68. return time.Date(d.Year, d.Month, d.Day, 0, 0, 0, 0, loc)
  69. }
  70. // AddDays returns the date that is n days in the future.
  71. // n can also be negative to go into the past.
  72. func (d Date) AddDays(n int) Date {
  73. return DateOf(d.In(time.UTC).AddDate(0, 0, n))
  74. }
  75. // DaysSince returns the signed number of days between the date and s, not including the end day.
  76. // This is the inverse operation to AddDays.
  77. func (d Date) DaysSince(s Date) (days int) {
  78. // We convert to Unix time so we do not have to worry about leap seconds:
  79. // Unix time increases by exactly 86400 seconds per day.
  80. deltaUnix := d.In(time.UTC).Unix() - s.In(time.UTC).Unix()
  81. return int(deltaUnix / 86400)
  82. }
  83. // Before reports whether d1 occurs before d2.
  84. func (d1 Date) Before(d2 Date) bool {
  85. if d1.Year != d2.Year {
  86. return d1.Year < d2.Year
  87. }
  88. if d1.Month != d2.Month {
  89. return d1.Month < d2.Month
  90. }
  91. return d1.Day < d2.Day
  92. }
  93. // After reports whether d1 occurs after d2.
  94. func (d1 Date) After(d2 Date) bool {
  95. return d2.Before(d1)
  96. }
  97. // MarshalText implements the encoding.TextMarshaler interface.
  98. // The output is the result of d.String().
  99. func (d Date) MarshalText() ([]byte, error) {
  100. return []byte(d.String()), nil
  101. }
  102. // UnmarshalText implements the encoding.TextUnmarshaler interface.
  103. // The date is expected to be a string in a format accepted by ParseDate.
  104. func (d *Date) UnmarshalText(data []byte) error {
  105. var err error
  106. *d, err = ParseDate(string(data))
  107. return err
  108. }
  109. // A Time represents a time with nanosecond precision.
  110. //
  111. // This type does not include location information, and therefore does not
  112. // describe a unique moment in time.
  113. //
  114. // This type exists to represent the TIME type in storage-based APIs like BigQuery.
  115. // Most operations on Times are unlikely to be meaningful. Prefer the DateTime type.
  116. type Time struct {
  117. Hour int // The hour of the day in 24-hour format; range [0-23]
  118. Minute int // The minute of the hour; range [0-59]
  119. Second int // The second of the minute; range [0-59]
  120. Nanosecond int // The nanosecond of the second; range [0-999999999]
  121. }
  122. // TimeOf returns the Time representing the time of day in which a time occurs
  123. // in that time's location. It ignores the date.
  124. func TimeOf(t time.Time) Time {
  125. var tm Time
  126. tm.Hour, tm.Minute, tm.Second = t.Clock()
  127. tm.Nanosecond = t.Nanosecond()
  128. return tm
  129. }
  130. // ParseTime parses a string and returns the time value it represents.
  131. // ParseTime accepts an extended form of the RFC3339 partial-time format. After
  132. // the HH:MM:SS part of the string, an optional fractional part may appear,
  133. // consisting of a decimal point followed by one to nine decimal digits.
  134. // (RFC3339 admits only one digit after the decimal point).
  135. func ParseTime(s string) (Time, error) {
  136. t, err := time.Parse("15:04:05.999999999", s)
  137. if err != nil {
  138. return Time{}, err
  139. }
  140. return TimeOf(t), nil
  141. }
  142. // String returns the date in the format described in ParseTime. If Nanoseconds
  143. // is zero, no fractional part will be generated. Otherwise, the result will
  144. // end with a fractional part consisting of a decimal point and nine digits.
  145. func (t Time) String() string {
  146. s := fmt.Sprintf("%02d:%02d:%02d", t.Hour, t.Minute, t.Second)
  147. if t.Nanosecond == 0 {
  148. return s
  149. }
  150. return s + fmt.Sprintf(".%09d", t.Nanosecond)
  151. }
  152. // IsValid reports whether the time is valid.
  153. func (t Time) IsValid() bool {
  154. // Construct a non-zero time.
  155. tm := time.Date(2, 2, 2, t.Hour, t.Minute, t.Second, t.Nanosecond, time.UTC)
  156. return TimeOf(tm) == t
  157. }
  158. // MarshalText implements the encoding.TextMarshaler interface.
  159. // The output is the result of t.String().
  160. func (t Time) MarshalText() ([]byte, error) {
  161. return []byte(t.String()), nil
  162. }
  163. // UnmarshalText implements the encoding.TextUnmarshaler interface.
  164. // The time is expected to be a string in a format accepted by ParseTime.
  165. func (t *Time) UnmarshalText(data []byte) error {
  166. var err error
  167. *t, err = ParseTime(string(data))
  168. return err
  169. }
  170. // A DateTime represents a date and time.
  171. //
  172. // This type does not include location information, and therefore does not
  173. // describe a unique moment in time.
  174. type DateTime struct {
  175. Date Date
  176. Time Time
  177. }
  178. // Note: We deliberately do not embed Date into DateTime, to avoid promoting AddDays and Sub.
  179. // DateTimeOf returns the DateTime in which a time occurs in that time's location.
  180. func DateTimeOf(t time.Time) DateTime {
  181. return DateTime{
  182. Date: DateOf(t),
  183. Time: TimeOf(t),
  184. }
  185. }
  186. // ParseDateTime parses a string and returns the DateTime it represents.
  187. // ParseDateTime accepts a variant of the RFC3339 date-time format that omits
  188. // the time offset but includes an optional fractional time, as described in
  189. // ParseTime. Informally, the accepted format is
  190. // YYYY-MM-DDTHH:MM:SS[.FFFFFFFFF]
  191. // where the 'T' may be a lower-case 't'.
  192. func ParseDateTime(s string) (DateTime, error) {
  193. t, err := time.Parse("2006-01-02T15:04:05.999999999", s)
  194. if err != nil {
  195. t, err = time.Parse("2006-01-02t15:04:05.999999999", s)
  196. if err != nil {
  197. return DateTime{}, err
  198. }
  199. }
  200. return DateTimeOf(t), nil
  201. }
  202. // String returns the date in the format described in ParseDate.
  203. func (dt DateTime) String() string {
  204. return dt.Date.String() + "T" + dt.Time.String()
  205. }
  206. // IsValid reports whether the datetime is valid.
  207. func (dt DateTime) IsValid() bool {
  208. return dt.Date.IsValid() && dt.Time.IsValid()
  209. }
  210. // In returns the time corresponding to the DateTime in the given location.
  211. //
  212. // If the time is missing or ambigous at the location, In returns the same
  213. // result as time.Date. For example, if loc is America/Indiana/Vincennes, then
  214. // both
  215. // time.Date(1955, time.May, 1, 0, 30, 0, 0, loc)
  216. // and
  217. // civil.DateTime{
  218. // civil.Date{Year: 1955, Month: time.May, Day: 1}},
  219. // civil.Time{Minute: 30}}.In(loc)
  220. // return 23:30:00 on April 30, 1955.
  221. //
  222. // In panics if loc is nil.
  223. func (dt DateTime) In(loc *time.Location) time.Time {
  224. 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)
  225. }
  226. // Before reports whether dt1 occurs before dt2.
  227. func (dt1 DateTime) Before(dt2 DateTime) bool {
  228. return dt1.In(time.UTC).Before(dt2.In(time.UTC))
  229. }
  230. // After reports whether dt1 occurs after dt2.
  231. func (dt1 DateTime) After(dt2 DateTime) bool {
  232. return dt2.Before(dt1)
  233. }
  234. // MarshalText implements the encoding.TextMarshaler interface.
  235. // The output is the result of dt.String().
  236. func (dt DateTime) MarshalText() ([]byte, error) {
  237. return []byte(dt.String()), nil
  238. }
  239. // UnmarshalText implements the encoding.TextUnmarshaler interface.
  240. // The datetime is expected to be a string in a format accepted by ParseDateTime
  241. func (dt *DateTime) UnmarshalText(data []byte) error {
  242. var err error
  243. *dt, err = ParseDateTime(string(data))
  244. return err
  245. }