]> Cypherpunks repositories - gostls13.git/commitdiff
New time formatter, time.Format(formatString)
authorRob Pike <r@golang.org>
Wed, 6 Jan 2010 23:32:48 +0000 (10:32 +1100)
committerRob Pike <r@golang.org>
Wed, 6 Jan 2010 23:32:48 +0000 (10:32 +1100)
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

src/pkg/time/Makefile
src/pkg/time/format.go [new file with mode: 0644]
src/pkg/time/time.go
src/pkg/time/time_test.go

index ac441506972bfae7a397294ed14deaea24357c8b..f73fc8878c9365101e9ff7d0d6c2958b63a6629a 100644 (file)
@@ -6,6 +6,7 @@ include ../../Make.$(GOARCH)
 
 TARG=time
 GOFILES=\
+       format.go \
        sleep.go\
        tick.go\
        time.go\
diff --git a/src/pkg/time/format.go b/src/pkg/time/format.go
new file mode 100644 (file)
index 0000000..52d62a9
--- /dev/null
@@ -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) }
index ff696e0ac0a938733051d8f6837816850e6c3346..d84807e836e1fbcd104bf06229688f04e0d20997 100644 (file)
@@ -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") }
index 97787f30bc2a0b2c06a81d9e91a59df03194f3eb..4dfdea4456879ad8e58f07b916c22017195462b5 100644 (file)
@@ -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)
                }
        }
 }