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.
19package time
20
21import (
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.
93const (
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
109const (
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
124const (
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.
138func Time(s string) (bool, error) {
139 return timeFormat(s, time.RFC3339Nano)
140}
141
142func 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.
155func 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.
162func 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.
203func 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).
218func 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.
224type 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.
237func 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}