aboutsummaryrefslogtreecommitdiff
path: root/src/time/format.go
diff options
context:
space:
mode:
authorRuss Cox <rsc@golang.org>2018-07-09 22:52:22 -0400
committerRuss Cox <rsc@golang.org>2019-03-08 01:21:33 +0000
commit6a9da69147a3b32e48f8dfea8c82f5975af9cc62 (patch)
tree9cbe7e0ddca6c3ce1bcf07b951d712fb13b12508 /src/time/format.go
parent9586c093a2e65cb8edd73a4dd0a6a18823249cf4 (diff)
downloadgo-6a9da69147a3b32e48f8dfea8c82f5975af9cc62.tar.gz
go-6a9da69147a3b32e48f8dfea8c82f5975af9cc62.zip
time: add support for day-of-year in Format and Parse
Day of year is 002 or __2, in contrast to day-in-month 2 or 02 or _2. This means there is no way to print a variable-width day-of-year, but that's probably OK. Fixes #25689. Change-Id: I1425d412cb7d2d360e9b3bf74e89566714e2477a Reviewed-on: https://go-review.googlesource.com/c/go/+/122876 Run-TryBot: Russ Cox <rsc@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rob Pike <r@golang.org>
Diffstat (limited to 'src/time/format.go')
-rw-r--r--src/time/format.go105
1 files changed, 96 insertions, 9 deletions
diff --git a/src/time/format.go b/src/time/format.go
index 2adbbe0770..d8e295f696 100644
--- a/src/time/format.go
+++ b/src/time/format.go
@@ -48,6 +48,10 @@ import "errors"
// The recognized day of week formats are "Mon" and "Monday".
// The recognized month formats are "Jan" and "January".
//
+// The formats 2, _2, and 02 are unpadded, space-padded, and zero-padded
+// day of month. The formats __2 and 002 are space-padded and zero-padded
+// three-character day of year; there is no unpadded day of year format.
+//
// Text in the format string that is not recognized as part of the reference
// time is echoed verbatim during Format and expected to appear verbatim
// in the input to Parse.
@@ -96,6 +100,8 @@ const (
stdDay // "2"
stdUnderDay // "_2"
stdZeroDay // "02"
+ stdUnderYearDay // "__2"
+ stdZeroYearDay // "002"
stdHour = iota + stdNeedClock // "15"
stdHour12 // "3"
stdZeroHour12 // "03"
@@ -170,10 +176,13 @@ func nextStdChunk(layout string) (prefix string, std int, suffix string) {
}
}
- case '0': // 01, 02, 03, 04, 05, 06
+ case '0': // 01, 02, 03, 04, 05, 06, 002
if len(layout) >= i+2 && '1' <= layout[i+1] && layout[i+1] <= '6' {
return layout[0:i], std0x[layout[i+1]-'1'], layout[i+2:]
}
+ if len(layout) >= i+3 && layout[i+1] == '0' && layout[i+2] == '2' {
+ return layout[0:i], stdZeroYearDay, layout[i+3:]
+ }
case '1': // 15, 1
if len(layout) >= i+2 && layout[i+1] == '5' {
@@ -187,7 +196,7 @@ func nextStdChunk(layout string) (prefix string, std int, suffix string) {
}
return layout[0:i], stdDay, layout[i+1:]
- case '_': // _2, _2006
+ case '_': // _2, _2006, __2
if len(layout) >= i+2 && layout[i+1] == '2' {
//_2006 is really a literal _, followed by stdLongYear
if len(layout) >= i+5 && layout[i+1:i+5] == "2006" {
@@ -195,6 +204,9 @@ func nextStdChunk(layout string) (prefix string, std int, suffix string) {
}
return layout[0:i], stdUnderDay, layout[i+2:]
}
+ if len(layout) >= i+3 && layout[i+1] == '_' && layout[i+2] == '2' {
+ return layout[0:i], stdUnderYearDay, layout[i+3:]
+ }
case '3':
return layout[0:i], stdHour12, layout[i+1:]
@@ -503,6 +515,7 @@ func (t Time) AppendFormat(b []byte, layout string) []byte {
year int = -1
month Month
day int
+ yday int
hour int = -1
min int
sec int
@@ -520,7 +533,8 @@ func (t Time) AppendFormat(b []byte, layout string) []byte {
// Compute year, month, day if needed.
if year < 0 && std&stdNeedDate != 0 {
- year, month, day, _ = absDate(abs, true)
+ year, month, day, yday = absDate(abs, true)
+ yday++
}
// Compute hour, minute, second if needed.
@@ -560,6 +574,16 @@ func (t Time) AppendFormat(b []byte, layout string) []byte {
b = appendInt(b, day, 0)
case stdZeroDay:
b = appendInt(b, day, 2)
+ case stdUnderYearDay:
+ if yday < 100 {
+ b = append(b, ' ')
+ if yday < 10 {
+ b = append(b, ' ')
+ }
+ }
+ b = appendInt(b, yday, 0)
+ case stdZeroYearDay:
+ b = appendInt(b, yday, 3)
case stdHour:
b = appendInt(b, hour, 2)
case stdHour12:
@@ -688,7 +712,7 @@ func isDigit(s string, i int) bool {
return '0' <= c && c <= '9'
}
-// getnum parses s[0:1] or s[0:2] (fixed forces the latter)
+// getnum parses s[0:1] or s[0:2] (fixed forces s[0:2])
// as a decimal integer and returns the integer and the
// remainder of the string.
func getnum(s string, fixed bool) (int, string, error) {
@@ -704,6 +728,20 @@ func getnum(s string, fixed bool) (int, string, error) {
return int(s[0]-'0')*10 + int(s[1]-'0'), s[2:], nil
}
+// getnum3 parses s[0:1], s[0:2], or s[0:3] (fixed forces s[0:3])
+// as a decimal integer and returns the integer and the remainder
+// of the string.
+func getnum3(s string, fixed bool) (int, string, error) {
+ var n, i int
+ for i = 0; i < 3 && isDigit(s, i); i++ {
+ n = n*10 + int(s[i]-'0')
+ }
+ if i == 0 || fixed && i != 3 {
+ return 0, s, errBad
+ }
+ return n, s[i:], nil
+}
+
func cutspace(s string) string {
for len(s) > 0 && s[0] == ' ' {
s = s[1:]
@@ -792,8 +830,9 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error)
// Time being constructed.
var (
year int
- month int = 1 // January
- day int = 1
+ month int = -1
+ day int = -1
+ yday int = -1
hour int
min int
sec int
@@ -861,10 +900,17 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error)
value = value[1:]
}
day, value, err = getnum(value, std == stdZeroDay)
- if day < 0 {
- // Note that we allow any one- or two-digit day here.
- rangeErrString = "day"
+ // Note that we allow any one- or two-digit day here.
+ // The month, day, year combination is validated after we've completed parsing.
+ case stdUnderYearDay, stdZeroYearDay:
+ for i := 0; i < 2; i++ {
+ if std == stdUnderYearDay && len(value) > 0 && value[0] == ' ' {
+ value = value[1:]
+ }
}
+ yday, value, err = getnum3(value, std == stdZeroYearDay)
+ // Note that we allow any one-, two-, or three-digit year-day here.
+ // The year-day, year combination is validated after we've completed parsing.
case stdHour:
hour, value, err = getnum(value, false)
if hour < 0 || 24 <= hour {
@@ -1044,6 +1090,47 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error)
hour = 0
}
+ // Convert yday to day, month.
+ if yday >= 0 {
+ var d int
+ var m int
+ if isLeap(year) {
+ if yday == 31+29 {
+ m = int(February)
+ d = 29
+ } else if yday > 31+29 {
+ yday--
+ }
+ }
+ if yday < 1 || yday > 365 {
+ return Time{}, &ParseError{alayout, avalue, "", value, ": day-of-year out of range"}
+ }
+ if m == 0 {
+ m = yday/31 + 1
+ if int(daysBefore[m]) < yday {
+ m++
+ }
+ d = yday - int(daysBefore[m-1])
+ }
+ // If month, day already seen, yday's m, d must match.
+ // Otherwise, set them from m, d.
+ if month >= 0 && month != m {
+ return Time{}, &ParseError{alayout, avalue, "", value, ": day-of-year does not match month"}
+ }
+ month = m
+ if day >= 0 && day != d {
+ return Time{}, &ParseError{alayout, avalue, "", value, ": day-of-year does not match day"}
+ }
+ day = d
+ } else {
+ if month < 0 {
+ month = int(January)
+ }
+ if day < 0 {
+ day = 1
+ }
+ }
+
// Validate the day of the month.
if day < 1 || day > daysIn(Month(month), year) {
return Time{}, &ParseError{alayout, avalue, "", value, ": day out of range"}