mirror of
https://github.com/golang/go
synced 2024-11-25 03:27:58 -07:00
New time formatter, time.Format(formatString)
The model is that formatString is a a representation of a standard time, and that Format converts the time to that representation. Standard representaitons are defined for ANSIC, RFC850, RFC1123, and ISO8601. There's also a humane Kitchen fomat: 3:04PM. R=rsc, benolive, cw CC=golang-dev https://golang.org/cl/181130
This commit is contained in:
parent
9c9c89c013
commit
e05b381e79
@ -6,6 +6,7 @@ include ../../Make.$(GOARCH)
|
|||||||
|
|
||||||
TARG=time
|
TARG=time
|
||||||
GOFILES=\
|
GOFILES=\
|
||||||
|
format.go \
|
||||||
sleep.go\
|
sleep.go\
|
||||||
tick.go\
|
tick.go\
|
||||||
time.go\
|
time.go\
|
||||||
|
227
src/pkg/time/format.go
Normal file
227
src/pkg/time/format.go
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
package time
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
numeric = iota
|
||||||
|
alphabetic
|
||||||
|
separator
|
||||||
|
)
|
||||||
|
|
||||||
|
// These are predefined layouts for use in Time.Format.
|
||||||
|
// The standard time used in the layouts is:
|
||||||
|
// Mon Jan 2 15:04:05 PST 2006 (PST is GMT-0800)
|
||||||
|
// which is Unix time 1136243045.
|
||||||
|
const (
|
||||||
|
ANSIC = "Mon Jan 2 15:04:05 2006"
|
||||||
|
UnixDate = "Mon Jan 2 15:04:05 PST 2006"
|
||||||
|
RFC850 = "Monday, 02-Jan-06 15:04:05 PST"
|
||||||
|
RFC1123 = "Mon, 02 Jan 2006 15:04:05 PST"
|
||||||
|
Kitchen = "3:04PM"
|
||||||
|
// Special case: use Z to get the time zone formatted according to ISO 8601,
|
||||||
|
// which is -0800 or Z for UTC
|
||||||
|
ISO8601 = "2006-01-02T15:04:05Z"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
stdLongMonth = "January"
|
||||||
|
stdMonth = "Jan"
|
||||||
|
stdNumMonth = "1"
|
||||||
|
stdZeroMonth = "01"
|
||||||
|
stdLongWeekDay = "Monday"
|
||||||
|
stdWeekDay = "Mon"
|
||||||
|
stdDay = "2"
|
||||||
|
stdZeroDay = "02"
|
||||||
|
stdHour = "15"
|
||||||
|
stdHour12 = "3"
|
||||||
|
stdZeroHour12 = "03"
|
||||||
|
stdMinute = "4"
|
||||||
|
stdZeroMinute = "04"
|
||||||
|
stdSecond = "5"
|
||||||
|
stdZeroSecond = "05"
|
||||||
|
stdLongYear = "2006"
|
||||||
|
stdYear = "06"
|
||||||
|
stdZulu = "1504"
|
||||||
|
stdPM = "PM"
|
||||||
|
stdpm = "pm"
|
||||||
|
stdTZ = "PST"
|
||||||
|
stdISO8601TZ = "Z"
|
||||||
|
)
|
||||||
|
|
||||||
|
var longDayNames = []string{
|
||||||
|
"Sunday",
|
||||||
|
"Monday",
|
||||||
|
"Tuesday",
|
||||||
|
"Wednesday",
|
||||||
|
"Thursday",
|
||||||
|
"Friday",
|
||||||
|
"Saturday",
|
||||||
|
}
|
||||||
|
|
||||||
|
var shortDayNames = []string{
|
||||||
|
"Sun",
|
||||||
|
"Mon",
|
||||||
|
"Tue",
|
||||||
|
"Wed",
|
||||||
|
"Thu",
|
||||||
|
"Fri",
|
||||||
|
"Sat",
|
||||||
|
}
|
||||||
|
|
||||||
|
var shortMonthNames = []string{
|
||||||
|
"---",
|
||||||
|
"Jan",
|
||||||
|
"Feb",
|
||||||
|
"Mar",
|
||||||
|
"Apr",
|
||||||
|
"May",
|
||||||
|
"Jun",
|
||||||
|
"Jul",
|
||||||
|
"Aug",
|
||||||
|
"Sep",
|
||||||
|
"Oct",
|
||||||
|
"Nov",
|
||||||
|
"Dec",
|
||||||
|
}
|
||||||
|
|
||||||
|
var longMonthNames = []string{
|
||||||
|
"---",
|
||||||
|
"January",
|
||||||
|
"February",
|
||||||
|
"March",
|
||||||
|
"April",
|
||||||
|
"May",
|
||||||
|
"June",
|
||||||
|
"July",
|
||||||
|
"August",
|
||||||
|
"September",
|
||||||
|
"October",
|
||||||
|
"November",
|
||||||
|
"December",
|
||||||
|
}
|
||||||
|
|
||||||
|
func charType(c uint8) int {
|
||||||
|
switch {
|
||||||
|
case '0' <= c && c <= '9':
|
||||||
|
return numeric
|
||||||
|
case 'a' <= c && c < 'z', 'A' <= c && c <= 'Z':
|
||||||
|
return alphabetic
|
||||||
|
}
|
||||||
|
return separator
|
||||||
|
}
|
||||||
|
|
||||||
|
func pieces(s string) []string {
|
||||||
|
p := make([]string, 20)
|
||||||
|
i := 0
|
||||||
|
// Each iteration generates one piece
|
||||||
|
for n := range p {
|
||||||
|
if i >= len(s) {
|
||||||
|
p = p[0:n]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
start := i
|
||||||
|
c := s[i]
|
||||||
|
pieceType := charType(c)
|
||||||
|
for i < len(s) && charType(s[i]) == pieceType {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
p[n] = s[start:i]
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func zeroPad(i int) string {
|
||||||
|
s := strconv.Itoa(i)
|
||||||
|
if i < 10 {
|
||||||
|
s = "0" + s
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format returns a textual representation of the time value formatted
|
||||||
|
// according to layout. The layout defines the format by showing the
|
||||||
|
// representation of a standard time, which is then used to describe
|
||||||
|
// the time to be formatted. Predefined layouts ANSIC, UnixDate,
|
||||||
|
// ISO8601 and others describe standard representations.
|
||||||
|
func (t *Time) Format(layout string) string {
|
||||||
|
pc := pieces(layout)
|
||||||
|
s := ""
|
||||||
|
for _, p := range pc {
|
||||||
|
switch p {
|
||||||
|
case stdYear:
|
||||||
|
p = strconv.Itoa64(t.Year % 100)
|
||||||
|
case stdLongYear:
|
||||||
|
p = strconv.Itoa64(t.Year)
|
||||||
|
case stdMonth:
|
||||||
|
p = shortMonthNames[t.Month]
|
||||||
|
case stdLongMonth:
|
||||||
|
p = longMonthNames[t.Month]
|
||||||
|
case stdNumMonth:
|
||||||
|
p = strconv.Itoa(t.Month)
|
||||||
|
case stdZeroMonth:
|
||||||
|
p = zeroPad(t.Month)
|
||||||
|
case stdWeekDay:
|
||||||
|
p = shortDayNames[t.Weekday]
|
||||||
|
case stdLongWeekDay:
|
||||||
|
p = longDayNames[t.Weekday]
|
||||||
|
case stdDay:
|
||||||
|
p = strconv.Itoa(t.Day)
|
||||||
|
case stdZeroDay:
|
||||||
|
p = zeroPad(t.Day)
|
||||||
|
case stdHour:
|
||||||
|
p = zeroPad(t.Hour)
|
||||||
|
case stdHour12:
|
||||||
|
p = strconv.Itoa(t.Hour % 12)
|
||||||
|
case stdZeroHour12:
|
||||||
|
p = zeroPad(t.Hour % 12)
|
||||||
|
case stdMinute:
|
||||||
|
p = strconv.Itoa(t.Minute)
|
||||||
|
case stdZeroMinute:
|
||||||
|
p = zeroPad(t.Minute)
|
||||||
|
case stdSecond:
|
||||||
|
p = strconv.Itoa(t.Second)
|
||||||
|
case stdZeroSecond:
|
||||||
|
p = zeroPad(t.Second)
|
||||||
|
case stdZulu:
|
||||||
|
p = zeroPad(t.Hour) + zeroPad(t.Minute)
|
||||||
|
case stdISO8601TZ:
|
||||||
|
// Rather ugly special case, required because the time zone is too broken down
|
||||||
|
// in this format to recognize easily. We cheat and take "Z" to mean "the time
|
||||||
|
// zone as formatted for ISO 8601".
|
||||||
|
if t.ZoneOffset == 0 {
|
||||||
|
p = "Z"
|
||||||
|
} else {
|
||||||
|
zone := t.ZoneOffset / 60 // minutes
|
||||||
|
if zone < 0 {
|
||||||
|
p = "-"
|
||||||
|
zone = -zone
|
||||||
|
} else {
|
||||||
|
p = "+"
|
||||||
|
}
|
||||||
|
p += zeroPad(zone / 60)
|
||||||
|
p += zeroPad(zone % 60)
|
||||||
|
}
|
||||||
|
case stdPM:
|
||||||
|
if t.Hour >= 12 {
|
||||||
|
p = "PM"
|
||||||
|
} else {
|
||||||
|
p = "AM"
|
||||||
|
}
|
||||||
|
case stdpm:
|
||||||
|
if t.Hour >= 12 {
|
||||||
|
p = "pm"
|
||||||
|
} else {
|
||||||
|
p = "am"
|
||||||
|
}
|
||||||
|
case stdTZ:
|
||||||
|
p = t.Zone
|
||||||
|
}
|
||||||
|
s += p
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a Unix-style representation of the time value.
|
||||||
|
func (t *Time) String() string { return t.Format(UnixDate) }
|
@ -227,155 +227,3 @@ func (t *Time) Seconds() int64 {
|
|||||||
sec -= int64(t.ZoneOffset)
|
sec -= int64(t.ZoneOffset)
|
||||||
return sec
|
return sec
|
||||||
}
|
}
|
||||||
|
|
||||||
var longDayNames = []string{
|
|
||||||
"Sunday",
|
|
||||||
"Monday",
|
|
||||||
"Tuesday",
|
|
||||||
"Wednesday",
|
|
||||||
"Thursday",
|
|
||||||
"Friday",
|
|
||||||
"Saturday",
|
|
||||||
}
|
|
||||||
|
|
||||||
var shortDayNames = []string{
|
|
||||||
"Sun",
|
|
||||||
"Mon",
|
|
||||||
"Tue",
|
|
||||||
"Wed",
|
|
||||||
"Thu",
|
|
||||||
"Fri",
|
|
||||||
"Sat",
|
|
||||||
}
|
|
||||||
|
|
||||||
var shortMonthNames = []string{
|
|
||||||
"---",
|
|
||||||
"Jan",
|
|
||||||
"Feb",
|
|
||||||
"Mar",
|
|
||||||
"Apr",
|
|
||||||
"May",
|
|
||||||
"Jun",
|
|
||||||
"Jul",
|
|
||||||
"Aug",
|
|
||||||
"Sep",
|
|
||||||
"Oct",
|
|
||||||
"Nov",
|
|
||||||
"Dec",
|
|
||||||
}
|
|
||||||
|
|
||||||
func copy(dst []byte, s string) {
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
dst[i] = s[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func decimal(dst []byte, n int) {
|
|
||||||
if n < 0 {
|
|
||||||
n = 0
|
|
||||||
}
|
|
||||||
for i := len(dst) - 1; i >= 0; i-- {
|
|
||||||
dst[i] = byte(n%10 + '0')
|
|
||||||
n /= 10
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func addString(buf []byte, bp int, s string) int {
|
|
||||||
n := len(s)
|
|
||||||
copy(buf[bp:bp+n], s)
|
|
||||||
return bp + n
|
|
||||||
}
|
|
||||||
|
|
||||||
// Just enough of strftime to implement the date formats below.
|
|
||||||
// Not exported.
|
|
||||||
func format(t *Time, fmt string) string {
|
|
||||||
buf := make([]byte, 128)
|
|
||||||
bp := 0
|
|
||||||
|
|
||||||
for i := 0; i < len(fmt); i++ {
|
|
||||||
if fmt[i] == '%' {
|
|
||||||
i++
|
|
||||||
switch fmt[i] {
|
|
||||||
case 'A': // %A full weekday name
|
|
||||||
bp = addString(buf, bp, longDayNames[t.Weekday])
|
|
||||||
case 'a': // %a abbreviated weekday name
|
|
||||||
bp = addString(buf, bp, shortDayNames[t.Weekday])
|
|
||||||
case 'b': // %b abbreviated month name
|
|
||||||
bp = addString(buf, bp, shortMonthNames[t.Month])
|
|
||||||
case 'd': // %d day of month (01-31)
|
|
||||||
decimal(buf[bp:bp+2], t.Day)
|
|
||||||
bp += 2
|
|
||||||
case 'e': // %e day of month ( 1-31)
|
|
||||||
if t.Day >= 10 {
|
|
||||||
decimal(buf[bp:bp+2], t.Day)
|
|
||||||
} else {
|
|
||||||
buf[bp] = ' '
|
|
||||||
buf[bp+1] = byte(t.Day + '0')
|
|
||||||
}
|
|
||||||
bp += 2
|
|
||||||
case 'H': // %H hour 00-23
|
|
||||||
decimal(buf[bp:bp+2], t.Hour)
|
|
||||||
bp += 2
|
|
||||||
case 'M': // %M minute 00-59
|
|
||||||
decimal(buf[bp:bp+2], t.Minute)
|
|
||||||
bp += 2
|
|
||||||
case 'm': // %m month 01-12
|
|
||||||
decimal(buf[bp:bp+2], t.Month)
|
|
||||||
bp += 2
|
|
||||||
case 'S': // %S second 00-59
|
|
||||||
decimal(buf[bp:bp+2], t.Second)
|
|
||||||
bp += 2
|
|
||||||
case 'Y': // %Y year 2008
|
|
||||||
decimal(buf[bp:bp+4], int(t.Year))
|
|
||||||
bp += 4
|
|
||||||
case 'y': // %y year 08
|
|
||||||
decimal(buf[bp:bp+2], int(t.Year%100))
|
|
||||||
bp += 2
|
|
||||||
case 'z': // %z tz in the form -0500
|
|
||||||
if t.ZoneOffset == 0 {
|
|
||||||
bp = addString(buf, bp, "Z")
|
|
||||||
} else if t.ZoneOffset < 0 {
|
|
||||||
bp = addString(buf, bp, "-")
|
|
||||||
decimal(buf[bp:bp+2], -t.ZoneOffset/3600)
|
|
||||||
decimal(buf[bp+2:bp+4], (-t.ZoneOffset%3600)/60)
|
|
||||||
bp += 4
|
|
||||||
} else {
|
|
||||||
bp = addString(buf, bp, "+")
|
|
||||||
decimal(buf[bp:bp+2], t.ZoneOffset/3600)
|
|
||||||
decimal(buf[bp+2:bp+4], (t.ZoneOffset%3600)/60)
|
|
||||||
bp += 4
|
|
||||||
}
|
|
||||||
case 'Z':
|
|
||||||
bp = addString(buf, bp, t.Zone)
|
|
||||||
default:
|
|
||||||
buf[bp] = '%'
|
|
||||||
buf[bp+1] = fmt[i]
|
|
||||||
bp += 2
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
buf[bp] = fmt[i]
|
|
||||||
bp++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return string(buf[0:bp])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Asctime formats the parsed time value in the style of
|
|
||||||
// ANSI C asctime: Sun Nov 6 08:49:37 1994
|
|
||||||
func (t *Time) Asctime() string { return format(t, "%a %b %e %H:%M:%S %Y") }
|
|
||||||
|
|
||||||
// RFC850 formats the parsed time value in the style of
|
|
||||||
// RFC 850: Sunday, 06-Nov-94 08:49:37 UTC
|
|
||||||
func (t *Time) RFC850() string { return format(t, "%A, %d-%b-%y %H:%M:%S %Z") }
|
|
||||||
|
|
||||||
// RFC1123 formats the parsed time value in the style of
|
|
||||||
// RFC 1123: Sun, 06 Nov 1994 08:49:37 UTC
|
|
||||||
func (t *Time) RFC1123() string { return format(t, "%a, %d %b %Y %H:%M:%S %Z") }
|
|
||||||
|
|
||||||
// ISO8601 formats the parsed time value in the style of
|
|
||||||
// ISO 8601: 1994-11-06T08:49:37Z
|
|
||||||
func (t *Time) ISO8601() string { return format(t, "%Y-%m-%dT%H:%M:%S%z") }
|
|
||||||
|
|
||||||
// String formats the parsed time value in the style of
|
|
||||||
// date(1): Sun Nov 6 08:49:37 UTC 1994
|
|
||||||
func (t *Time) String() string { return format(t, "%a %b %e %H:%M:%S %Z %Y") }
|
|
||||||
|
@ -114,10 +114,38 @@ var iso8601Formats = []TimeFormatTest{
|
|||||||
|
|
||||||
func TestISO8601Conversion(t *testing.T) {
|
func TestISO8601Conversion(t *testing.T) {
|
||||||
for _, f := range iso8601Formats {
|
for _, f := range iso8601Formats {
|
||||||
if f.time.ISO8601() != f.formattedValue {
|
if f.time.Format(ISO8601) != f.formattedValue {
|
||||||
t.Error("ISO8601():")
|
t.Error("ISO8601:")
|
||||||
t.Errorf(" want=%+v", f.formattedValue)
|
t.Errorf(" want=%+v", f.formattedValue)
|
||||||
t.Errorf(" have=%+v", f.time.ISO8601())
|
t.Errorf(" have=%+v", f.time.Format(ISO8601))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FormatTest struct {
|
||||||
|
name string
|
||||||
|
format string
|
||||||
|
result string
|
||||||
|
}
|
||||||
|
|
||||||
|
var formatTests = []FormatTest{
|
||||||
|
FormatTest{"ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010"},
|
||||||
|
FormatTest{"UnixDate", UnixDate, "Thu Feb 4 21:00:57 PST 2010"},
|
||||||
|
FormatTest{"RFC850", RFC850, "Thursday, 04-Feb-10 21:00:57 PST"},
|
||||||
|
FormatTest{"RFC1123", RFC1123, "Thu, 04 Feb 2010 21:00:57 PST"},
|
||||||
|
FormatTest{"ISO8601", ISO8601, "2010-02-04T21:00:57-0800"},
|
||||||
|
FormatTest{"Kitchen", Kitchen, "9:00PM"},
|
||||||
|
FormatTest{"am/pm", "3pm", "9pm"},
|
||||||
|
FormatTest{"AM/PM", "3PM", "9PM"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormat(t *testing.T) {
|
||||||
|
// The numeric time represents Thu Feb 4 21:00:57 EST 2010
|
||||||
|
time := SecondsToLocalTime(1265346057)
|
||||||
|
for _, test := range formatTests {
|
||||||
|
result := time.Format(test.format)
|
||||||
|
if result != test.result {
|
||||||
|
t.Errorf("%s expected %q got %q", test.name, test.result, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user