localtime.go 9.9 KB

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