1 // Copyright 2019 CUE Authors 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 time defines time-related types. 16 // 17 // In CUE time values are represented as a string of the format 18 // time.RFC3339Nano. 19 package time 20 21 import ( 22 "fmt" 23 "time" 24 ) 25 26 // These are predefined layouts for use in Time.Format and time.Parse. 27 // The reference time used in the layouts is the specific time: 28 // 29 // Mon Jan 2 15:04:05 MST 2006 30 // 31 // which is Unix time 1136239445. Since MST is GMT-0700, 32 // the reference time can be thought of as 33 // 34 // 01/02 03:04:05PM '06 -0700 35 // 36 // To define your own format, write down what the reference time would look 37 // like formatted your way; see the values of constants like ANSIC, 38 // StampMicro or Kitchen for examples. The model is to demonstrate what the 39 // reference time looks like so that the Format and Parse methods can apply 40 // the same transformation to a general time value. 41 // 42 // Some valid layouts are invalid time values for time.Parse, due to formats 43 // such as _ for space padding and Z for zone information. 44 // 45 // Within the format string, an underscore _ represents a space that may be 46 // replaced by a digit if the following number (a day) has two digits; for 47 // compatibility with fixed-width Unix time formats. 48 // 49 // A decimal point followed by one or more zeros represents a fractional 50 // second, printed to the given number of decimal places. A decimal point 51 // followed by one or more nines represents a fractional second, printed to 52 // the given number of decimal places, with trailing zeros removed. 53 // When parsing (only), the input may contain a fractional second 54 // field immediately after the seconds field, even if the layout does not 55 // signify its presence. In that case a decimal point followed by a maximal 56 // series of digits is parsed as a fractional second. 57 // 58 // Numeric time zone offsets format as follows: 59 // 60 // -0700 ±hhmm 61 // -07:00 ±hh:mm 62 // -07 ±hh 63 // 64 // Replacing the sign in the format with a Z triggers 65 // the ISO 8601 behavior of printing Z instead of an 66 // offset for the UTC zone. Thus: 67 // 68 // Z0700 Z or ±hhmm 69 // Z07:00 Z or ±hh:mm 70 // Z07 Z or ±hh 71 // 72 // The recognized day of week formats are "Mon" and "Monday". 73 // The recognized month formats are "Jan" and "January". 74 // 75 // Text in the format string that is not recognized as part of the reference 76 // time is echoed verbatim during Format and expected to appear verbatim 77 // in the input to Parse. 78 // 79 // The executable example for Time.Format demonstrates the working 80 // of the layout string in detail and is a good reference. 81 // 82 // Note that the RFC822, RFC850, and RFC1123 formats should be applied 83 // only to local times. Applying them to UTC times will use "UTC" as the 84 // time zone abbreviation, while strictly speaking those RFCs require the 85 // use of "GMT" in that case. 86 // In general RFC1123Z should be used instead of RFC1123 for servers 87 // that insist on that format, and RFC3339 should be preferred for new protocols. 88 // RFC3339, RFC822, RFC822Z, RFC1123, and RFC1123Z are useful for formatting; 89 // when used with time.Parse they do not accept all the time formats 90 // permitted by the RFCs. 91 // The RFC3339Nano format removes trailing zeros from the seconds field 92 // and thus may not sort correctly once formatted. 93 const ( 94 ANSIC = "Mon Jan _2 15:04:05 2006" 95 UnixDate = "Mon Jan _2 15:04:05 MST 2006" 96 RubyDate = "Mon Jan 02 15:04:05 -0700 2006" 97 RFC822 = "02 Jan 06 15:04 MST" 98 RFC822Z = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone 99 RFC850 = "Monday, 02-Jan-06 15:04:05 MST" 100 RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST" 101 RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone 102 RFC3339 = "2006-01-02T15:04:05Z07:00" 103 RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00" 104 RFC3339Date = "2006-01-02" 105 Kitchen = "3:04PM" 106 Kitchen24 = "15:04" 107 ) 108 109 const ( 110 January = 1 111 February = 2 112 March = 3 113 April = 4 114 May = 5 115 June = 6 116 July = 7 117 August = 8 118 September = 9 119 October = 10 120 November = 11 121 December = 12 122 ) 123 124 const ( 125 Sunday = 0 126 Monday = 1 127 Tuesday = 2 128 Wednesday = 3 129 Thursday = 4 130 Friday = 5 131 Saturday = 6 132 ) 133 134 // Time validates a RFC3339 date-time. 135 // 136 // Caveat: this implementation uses the Go implementation, which does not 137 // accept leap seconds. 138 func Time(s string) (bool, error) { 139 return timeFormat(s, time.RFC3339Nano) 140 } 141 142 func timeFormat(value, layout string) (bool, error) { 143 _, err := time.ParseInLocation(layout, value, time.UTC) 144 if err != nil { 145 // Use our own error, the time package's error as the Go error is too 146 // confusing within this context. 147 return false, fmt.Errorf("invalid time %q", value) 148 } 149 return true, nil 150 } 151 152 // Format defines a type string that must adhere to a certain layout. 153 // 154 // See Parse for a description on layout strings. 155 func Format(value, layout string) (bool, error) { 156 return timeFormat(value, layout) 157 } 158 159 // FormatString returns a textual representation of the time value. 160 // The formatted value is formatted according to the layout defined by the 161 // argument. See Parse for more information on the layout string. 162 func FormatString(layout, value string) (string, error) { 163 t, err := time.Parse(time.RFC3339Nano, value) 164 if err != nil { 165 return "", err 166 } 167 return t.Format(layout), nil 168 } 169 170 // Parse parses a formatted string and returns the time value it represents. 171 // The layout defines the format by showing how the reference time, 172 // defined to be 173 // 174 // Mon Jan 2 15:04:05 -0700 MST 2006 175 // 176 // would be interpreted if it were the value; it serves as an example of 177 // the input format. The same interpretation will then be made to the 178 // input string. 179 // 180 // Predefined layouts ANSIC, UnixDate, RFC3339 and others describe standard 181 // and convenient representations of the reference time. For more information 182 // about the formats and the definition of the reference time, see the 183 // documentation for ANSIC and the other constants defined by this package. 184 // Also, the executable example for Time.Format demonstrates the working 185 // of the layout string in detail and is a good reference. 186 // 187 // Elements omitted from the value are assumed to be zero or, when 188 // zero is impossible, one, so parsing "3:04pm" returns the time 189 // corresponding to Jan 1, year 0, 15:04:00 UTC (note that because the year is 190 // 0, this time is before the zero Time). 191 // Years must be in the range 0000..9999. The day of the week is checked 192 // for syntax but it is otherwise ignored. 193 // 194 // In the absence of a time zone indicator, Parse returns a time in UTC. 195 // 196 // When parsing a time with a zone offset like -0700, if the offset corresponds 197 // to a time zone used by the current location (Local), then Parse uses that 198 // location and zone in the returned time. Otherwise it records the time as 199 // being in a fabricated location with time fixed at the given zone offset. 200 // 201 // Parse currently does not support zone abbreviations like MST. All are 202 // interpreted as UTC. 203 func Parse(layout, value string) (string, error) { 204 // TODO: should we support locations? The result will be non-hermetic. 205 // See comments on github.com/cue-lang/cue/issues/1522. 206 t, err := time.ParseInLocation(layout, value, time.UTC) 207 if err != nil { 208 return "", err 209 } 210 return t.UTC().Format(time.RFC3339Nano), nil 211 } 212 213 // Unix returns the Time, in UTC, corresponding to the given Unix time, 214 // sec seconds and nsec nanoseconds since January 1, 1970 UTC. 215 // It is valid to pass nsec outside the range [0, 999999999]. 216 // Not all sec values have a corresponding time value. One such 217 // value is 1<<63-1 (the largest int64 value). 218 func Unix(sec int64, nsec int64) string { 219 t := time.Unix(sec, nsec) 220 return t.UTC().Format(time.RFC3339Nano) 221 } 222 223 // Parts holds individual parts of a parsed time stamp. 224 type Parts struct { 225 Year int `json:"year"` 226 Month int `json:"month"` 227 Day int `json:"day"` 228 Hour int `json:"hour"` 229 Minute int `json:"minute"` 230 231 // Second is equal to div(Nanosecond, 1_000_000_000) 232 Second int `json:"second"` 233 Nanosecond int `json:"nanosecond"` 234 } 235 236 // Split parses a time string into its individual parts. 237 func Split(t string) (*Parts, error) { 238 st, err := time.Parse(time.RFC3339Nano, t) 239 if err != nil { 240 return nil, err 241 } 242 year, month, day := st.Date() 243 return &Parts{ 244 Year: year, 245 Month: int(month), 246 Day: day, 247 Hour: st.Hour(), 248 Minute: st.Minute(), 249 250 Second: st.Second(), 251 Nanosecond: st.Nanosecond(), 252 }, nil 253 } 254