summaryrefslogtreecommitdiff
path: root/src/pkg/time
diff options
context:
space:
mode:
authorRob Pike <r@golang.org>2010-01-07 10:32:48 +1100
committerRob Pike <r@golang.org>2010-01-07 10:32:48 +1100
commitb9ad5bc3ca9350325334cc9e93681df390bb6b09 (patch)
tree8c30acc4bdf76a97e1de4e72020d746069557d9c /src/pkg/time
parent0edbb8d60f2a1eff0ee6421568deaeb97e13b499 (diff)
downloadgolang-b9ad5bc3ca9350325334cc9e93681df390bb6b09.tar.gz
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 http://codereview.appspot.com/181130
Diffstat (limited to 'src/pkg/time')
-rw-r--r--src/pkg/time/Makefile1
-rw-r--r--src/pkg/time/format.go227
-rw-r--r--src/pkg/time/time.go152
-rw-r--r--src/pkg/time/time_test.go34
4 files changed, 259 insertions, 155 deletions
diff --git a/src/pkg/time/Makefile b/src/pkg/time/Makefile
index ac4415069..f73fc8878 100644
--- a/src/pkg/time/Makefile
+++ b/src/pkg/time/Makefile
@@ -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
index 000000000..52d62a908
--- /dev/null
+++ b/src/pkg/time/format.go
@@ -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) }
diff --git a/src/pkg/time/time.go b/src/pkg/time/time.go
index ff696e0ac..d84807e83 100644
--- a/src/pkg/time/time.go
+++ b/src/pkg/time/time.go
@@ -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") }
diff --git a/src/pkg/time/time_test.go b/src/pkg/time/time_test.go
index 97787f30b..4dfdea445 100644
--- a/src/pkg/time/time_test.go
+++ b/src/pkg/time/time_test.go
@@ -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)
}
}
}