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