Commit e05b381e authored by Rob Pike's avatar Rob Pike

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
parent 9c9c89c0
......@@ -6,6 +6,7 @@ include ../../Make.$(GOARCH)
TARG=time
GOFILES=\
format.go \
sleep.go\
tick.go\
time.go\
......
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)
}
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment