mirror of
https://github.com/golang/go
synced 2024-11-12 09:20:22 -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
|
||||
GOFILES=\
|
||||
format.go \
|
||||
sleep.go\
|
||||
tick.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)
|
||||
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) {
|
||||
for _, f := range iso8601Formats {
|
||||
if f.time.ISO8601() != f.formattedValue {
|
||||
t.Error("ISO8601():")
|
||||
if f.time.Format(ISO8601) != f.formattedValue {
|
||||
t.Error("ISO8601:")
|
||||
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