Repository: emersion/go-imap
Pull request #60 (2016-09-30 22:32:18)

ENVELOPE should be able to parse the RFC1123Z time format.
RFC 3501 uses this format in its sample connection:

  https://tools.ietf.org/html/rfc3501#section-8

Seems like this format is inspired by RFC2822's specification:

  https://tools.ietf.org/html/rfc2822#section-3.3

FastMail and Gmail also the RFC1123Z format for ENVELOPE.


Patch:
diff --git a/commands/append.go b/commands/append.go
index 44d9b12..f6ce5a9 100644
--- a/commands/append.go
+++ b/commands/append.go
@@ -86,7 +86,7 @@ func (cmd *Append) Parse(fields []interface{}) (err error) {
 			if !ok {
 				return errors.New("Date must be a string")
 			}
-			if cmd.Date, err = imap.ParseDateTime(date); err != nil {
+			if cmd.Date, err = time.Parse(imap.DateTimeLayout, date); err != nil {
 				return err
 			}
 		}
diff --git a/date.go b/date.go
new file mode 100644
index 0000000..fc92a3b
--- /dev/null
+++ b/date.go
@@ -0,0 +1,68 @@
+package imap
+
+import (
+	"fmt"
+	"time"
+)
+
+// Date and time layouts.
+// Dovecot adds a leading zero to dates:
+//   https://github.com/dovecot/core/blob/4fbd5c5e113078e72f29465ccc96d44955ceadc2/src/lib-imap/imap-date.c#L166
+// Cyrus adds a leading space to dates:
+//   https://github.com/cyrusimap/cyrus-imapd/blob/1cb805a3bffbdf829df0964f3b802cdc917e76db/lib/times.c#L543
+const (
+	// Defined in RFC 3501 as date-text on page 83.
+	DateLayout = "_2-Jan-2006"
+	// Defined in RFC 3501 as date-time on page 83.
+	DateTimeLayout = "_2-Jan-2006 15:04:05 -0700"
+	// Defined in RFC 5322 section 3.3, mentioned as env-date in RFC 3501 page 84.
+	envelopeDateTimeLayout = "Mon, 02 Jan 2006 15:04:05 -0700"
+)
+
+// time.Time with a specific layout.
+type (
+	Date             time.Time
+	DateTime         time.Time
+	envelopeDateTime time.Time
+)
+
+// Permutations of the layouts defined in RFC 5322, section 3.3.
+var envelopeDateTimeLayouts = [...]string{
+	envelopeDateTimeLayout, // popular, try it first
+	"_2 Jan 2006 15:04:05 -0700",
+	"_2 Jan 2006 15:04:05 MST",
+	"_2 Jan 2006 15:04:05 -0700 (MST)",
+	"_2 Jan 2006 15:04 -0700",
+	"_2 Jan 2006 15:04 MST",
+	"_2 Jan 2006 15:04 -0700 (MST)",
+	"_2 Jan 06 15:04:05 -0700",
+	"_2 Jan 06 15:04:05 MST",
+	"_2 Jan 06 15:04:05 -0700 (MST)",
+	"_2 Jan 06 15:04 -0700",
+	"_2 Jan 06 15:04 MST",
+	"_2 Jan 06 15:04 -0700 (MST)",
+	"Mon, _2 Jan 2006 15:04:05 -0700",
+	"Mon, _2 Jan 2006 15:04:05 MST",
+	"Mon, _2 Jan 2006 15:04:05 -0700 (MST)",
+	"Mon, _2 Jan 2006 15:04 -0700",
+	"Mon, _2 Jan 2006 15:04 MST",
+	"Mon, _2 Jan 2006 15:04 -0700 (MST)",
+	"Mon, _2 Jan 06 15:04:05 -0700",
+	"Mon, _2 Jan 06 15:04:05 MST",
+	"Mon, _2 Jan 06 15:04:05 -0700 (MST)",
+	"Mon, _2 Jan 06 15:04 -0700",
+	"Mon, _2 Jan 06 15:04 MST",
+	"Mon, _2 Jan 06 15:04 -0700 (MST)",
+}
+
+// Try parsing the date based on the layouts defined in RFC 5322, section 3.3.
+// Inspired by https://github.com/golang/go/blob/master/src/net/mail/message.go
+func parseMessageDateTime(maybeDate string) (time.Time, error) {
+	for _, layout := range envelopeDateTimeLayouts {
+		parsed, err := time.Parse(layout, maybeDate)
+		if err == nil {
+			return parsed, nil
+		}
+	}
+	return time.Time{}, fmt.Errorf("date %s could not be parsed", maybeDate)
+}
diff --git a/message.go b/message.go
index 1fb9fc0..29a7dab 100644
--- a/message.go
+++ b/message.go
@@ -90,7 +90,7 @@ type Message struct {
 	BodyStructure *BodyStructure
 	// The message flags.
 	Flags []string
-	// The date the message was received by th server.
+	// The date the message was received by the server.
 	InternalDate time.Time
 	// The message size.
 	Size uint32
@@ -152,7 +152,7 @@ func (m *Message) Parse(fields []interface{}) error {
 				}
 			case InternalDateMsgAttr:
 				date, _ := f.(string)
-				m.InternalDate, _ = ParseDateTime(date)
+				m.InternalDate, _ = time.Parse(DateTimeLayout, date)
 			case SizeMsgAttr:
 				m.Size, _ = ParseNumber(f)
 			case UidMsgAttr:
@@ -520,7 +520,7 @@ func (e *Envelope) Parse(fields []interface{}) error {
 	}
 
 	if date, ok := fields[0].(string); ok {
-		e.Date, _ = ParseDateTime(date)
+		e.Date, _ = parseMessageDateTime(date)
 	}
 	if subject, ok := fields[1].(string); ok {
 		e.Subject = subject
@@ -556,7 +556,7 @@ func (e *Envelope) Parse(fields []interface{}) error {
 // Format an envelope to fields.
 func (e *Envelope) Format() (fields []interface{}) {
 	return []interface{}{
-		e.Date,
+		envelopeDateTime(e.Date),
 		e.Subject,
 		FormatAddressList(e.From),
 		FormatAddressList(e.Sender),
diff --git a/read.go b/read.go
index d81214e..a8649c2 100644
--- a/read.go
+++ b/read.go
@@ -5,7 +5,6 @@ import (
 	"io"
 	"strconv"
 	"strings"
-	"time"
 )
 
 const (
@@ -77,26 +76,6 @@ func ParseNumber(f interface{}) (uint32, error) {
 	return uint32(nbr), nil
 }
 
-func ParseDate(f interface{}) (t time.Time, err error) {
-	s, ok := f.(string)
-	if !ok {
-		err = newParseError("imap: date is not a string")
-		return
-	}
-
-	return time.Parse("2-Jan-2006", s)
-}
-
-func ParseDateTime(f interface{}) (t time.Time, err error) {
-	s, ok := f.(string)
-	if !ok {
-		err = newParseError("imap: date-time is not a string")
-		return
-	}
-
-	return time.Parse("2-Jan-2006 15:04:05 -0700", s)
-}
-
 // Convert a field list to a string list.
 func ParseStringList(fields []interface{}) ([]string, error) {
 	list := make([]string, len(fields))
diff --git a/search.go b/search.go
index 9487075..147a129 100644
--- a/search.go
+++ b/search.go
@@ -1,7 +1,7 @@
 package imap
 
 import (
-	"errors"
+	"fmt"
 	"strings"
 	"time"
 )
@@ -48,6 +48,15 @@ type SearchCriteria struct {
 	Unseen     bool
 }
 
+func maybeString(mystery interface{}) string {
+	s, ok := mystery.(string)
+	if ok {
+		return s
+	}
+
+	return ""
+}
+
 // Parse search criteria from fields.
 func (c *SearchCriteria) Parse(fields []interface{}) error {
 	// TODO: do not panic when criteria is malformed
@@ -55,7 +64,7 @@ func (c *SearchCriteria) Parse(fields []interface{}) error {
 	for i := 0; i < len(fields); i++ {
 		f, ok := fields[i].(string)
 		if !ok {
-			return errors.New("Invalid search criteria field")
+			return fmt.Errorf("Invalid search criteria field: %v", fields[i])
 		}
 
 		switch strings.ToUpper(f) {
@@ -68,7 +77,7 @@ func (c *SearchCriteria) Parse(fields []interface{}) error {
 			c.Bcc, _ = fields[i].(string)
 		case "BEFORE":
 			i++
-			c.Before, _ = ParseDate(fields[i])
+			c.Before, _ = time.Parse(DateLayout, maybeString(fields[i]))
 		case "BODY":
 			i++
 			c.Body, _ = fields[i].(string)
@@ -111,7 +120,7 @@ func (c *SearchCriteria) Parse(fields []interface{}) error {
 			c.Old = true
 		case "ON":
 			i++
-			c.On, _ = ParseDate(fields[i])
+			c.On, _ = time.Parse(DateLayout, maybeString(fields[i]))
 		case "OR":
 			i++
 			leftFields, _ := fields[i].([]interface{})
@@ -132,16 +141,16 @@ func (c *SearchCriteria) Parse(fields []interface{}) error {
 			c.Seen = true
 		case "SENTBEFORE":
 			i++
-			c.SentBefore, _ = ParseDate(fields[i])
+			c.SentBefore, _ = time.Parse(DateLayout, maybeString(fields[i]))
 		case "SENTON":
 			i++
-			c.SentOn, _ = ParseDate(fields[i])
+			c.SentOn, _ = time.Parse(DateLayout, maybeString(fields[i]))
 		case "SENTSINCE":
 			i++
-			c.SentSince, _ = ParseDate(fields[i])
+			c.SentSince, _ = time.Parse(DateLayout, maybeString(fields[i]))
 		case "SINCE":
 			i++
-			c.Since, _ = ParseDate(fields[i])
+			c.Since, _ = time.Parse(DateLayout, maybeString(fields[i]))
 		case "SMALLER":
 			i++
 			c.Smaller, _ = ParseNumber(fields[i])
@@ -196,7 +205,7 @@ func (c *SearchCriteria) Format() (fields []interface{}) {
 		fields = append(fields, "BCC", c.Bcc)
 	}
 	if !c.Before.IsZero() {
-		fields = append(fields, "BEFORE", FormatDate(c.Before))
+		fields = append(fields, "BEFORE", Date(c.Before))
 	}
 	if c.Body != "" {
 		fields = append(fields, "BODY", c.Body)
@@ -235,7 +244,7 @@ func (c *SearchCriteria) Format() (fields []interface{}) {
 		fields = append(fields, "OLD")
 	}
 	if !c.On.IsZero() {
-		fields = append(fields, "ON", FormatDate(c.On))
+		fields = append(fields, "ON", Date(c.On))
 	}
 	if c.Or[0] != nil && c.Or[1] != nil {
 		fields = append(fields, "OR", c.Or[0].Format(), c.Or[1].Format())
@@ -247,16 +256,16 @@ func (c *SearchCriteria) Format() (fields []interface{}) {
 		fields = append(fields, "SEEN")
 	}
 	if !c.SentBefore.IsZero() {
-		fields = append(fields, "SENTBEFORE", FormatDate(c.SentBefore))
+		fields = append(fields, "SENTBEFORE", Date(c.SentBefore))
 	}
 	if !c.SentOn.IsZero() {
-		fields = append(fields, "SENTON", FormatDate(c.SentOn))
+		fields = append(fields, "SENTON", Date(c.SentOn))
 	}
 	if !c.SentSince.IsZero() {
-		fields = append(fields, "SENTSINCE", FormatDate(c.SentSince))
+		fields = append(fields, "SENTSINCE", Date(c.SentSince))
 	}
 	if !c.Since.IsZero() {
-		fields = append(fields, "SINCE", FormatDate(c.Since))
+		fields = append(fields, "SINCE", Date(c.Since))
 	}
 	if c.Smaller != 0 {
 		fields = append(fields, "SMALLER", c.Smaller)
diff --git a/write.go b/write.go
index 9ede2e5..241dd7e 100644
--- a/write.go
+++ b/write.go
@@ -24,14 +24,6 @@ func formatNumber(num uint32) string {
 	return strconv.FormatUint(uint64(num), 10)
 }
 
-func FormatDate(t time.Time) string {
-	return t.Format("2-Jan-2006")
-}
-
-func FormatDateTime(t time.Time) string {
-	return t.Format("2-Jan-2006 15:04:05 -0700")
-}
-
 // Convert a string list to a field list.
 func FormatStringList(list []string) (fields []interface{}) {
 	fields = make([]interface{}, len(list))
@@ -98,11 +90,11 @@ func (w *Writer) writeAstring(s string) error {
 	return w.writeAtom(s)
 }
 
-func (w *Writer) writeDateTime(t time.Time) error {
+func (w *Writer) writeDateTime(t time.Time, layout string) error {
 	if t.IsZero() {
 		return w.writeAtom(nilAtom)
 	}
-	return w.writeQuoted(FormatDateTime(t))
+	return w.writeQuoted(t.Format(layout))
 }
 
 func (w *Writer) writeFields(fields []interface{}) error {
@@ -177,8 +169,14 @@ func (w *Writer) writeField(field interface{}) error {
 		return w.writeLiteral(field)
 	case []interface{}:
 		return w.writeList(field)
+	case envelopeDateTime:
+		return w.writeDateTime(time.Time(field), envelopeDateTimeLayout)
+	case Date:
+		return w.writeDateTime(time.Time(field), DateLayout)
+	case DateTime:
+		return w.writeDateTime(time.Time(field), DateTimeLayout)
 	case time.Time:
-		return w.writeDateTime(field)
+		return w.writeDateTime(field, DateTimeLayout)
 	case *SeqSet:
 		return w.writeString(field.String())
 	case *BodySectionName:


Test patch:
diff --git a/client/cmd_selected_test.go b/client/cmd_selected_test.go
index 464eb2c..674ef64 100644
--- a/client/cmd_selected_test.go
+++ b/client/cmd_selected_test.go
@@ -5,6 +5,7 @@ import (
 	"io"
 	"net"
 	"testing"
+	"time"
 
 	"github.com/emersion/go-imap"
 	"github.com/emersion/go-imap/client"
@@ -109,7 +110,7 @@ func TestClient_Search(t *testing.T) {
 	ct := func(c *client.Client) (err error) {
 		c.State = imap.SelectedState
 
-		date, _ := imap.ParseDate("1-Feb-1994")
+		date, _ := time.Parse(imap.DateLayout, "1-Feb-1994")
 		criteria := &imap.SearchCriteria{
 			Deleted: true,
 			From:    "Smith",
@@ -133,7 +134,7 @@ func TestClient_Search(t *testing.T) {
 		scanner := NewCmdScanner(c)
 
 		tag, cmd := scanner.Scan()
-		if cmd != "SEARCH CHARSET UTF-8 DELETED FROM Smith NOT (TO Pauline) SINCE 1-Feb-1994" {
+		if cmd != `SEARCH CHARSET UTF-8 DELETED FROM Smith NOT (TO Pauline) SINCE " 1-Feb-1994"` {
 			t.Fatal("Bad command:", cmd)
 		}
 
diff --git a/date_test.go b/date_test.go
new file mode 100644
index 0000000..678f44b
--- /dev/null
+++ b/date_test.go
@@ -0,0 +1,95 @@
+package imap
+
+import (
+	"testing"
+	"time"
+)
+
+var expectedDateTime = time.Date(2009, time.November, 2, 23, 0, 0, 0, time.FixedZone("", -6*60*60))
+var expectedDate = time.Date(2009, time.November, 2, 0, 0, 0, 0, time.FixedZone("", 0))
+
+func TestParseMessageDateTime(t *testing.T) {
+	tests := []struct {
+		in  string
+		out time.Time
+		ok  bool
+	}{
+		// some permutations
+		{"2 Nov 2009 23:00 -0600", expectedDateTime, true},
+		{"Tue, 2 Nov 2009 23:00:00 -0600", expectedDateTime, true},
+		{"Tue, 2 Nov 2009 23:00:00 -0600 (MST)", expectedDateTime, true},
+
+		// whitespace
+		{" 2 Nov 2009 23:00 -0600", expectedDateTime, true},
+		{"Tue,  2 Nov 2009 23:00:00 -0600", expectedDateTime, true},
+		{"Tue,  2 Nov 2009 23:00:00 -0600 (MST)", expectedDateTime, true},
+
+		// invalid
+		{"abc10 Nov 2009 23:00 -0600123", expectedDateTime, false},
+		{"10.Nov.2009 11:00:00 -9900", expectedDateTime, false},
+	}
+	for _, test := range tests {
+		out, err := parseMessageDateTime(test.in)
+		if !test.ok {
+			if err == nil {
+				t.Errorf("ParseMessageDateTime(%q) expected error; got %q", test.in, out)
+			}
+		} else if err != nil {
+			t.Errorf("ParseMessageDateTime(%q) expected %q; got %v", test.in, test.out, err)
+		} else if !out.Equal(test.out) {
+			t.Errorf("ParseMessageDateTime(%q) expected %q; got %q", test.in, test.out, out)
+		}
+	}
+}
+
+func TestParseDateTime(t *testing.T) {
+	tests := []struct {
+		in  string
+		out time.Time
+		ok  bool
+	}{
+		{"2-Nov-2009 23:00:00 -0600", expectedDateTime, true},
+
+		// whitespace
+		{" 2-Nov-2009 23:00:00 -0600", expectedDateTime, true},
+
+		// invalid or incorrect
+		{"10-Nov-2009", time.Time{}, false},
+		{"abc10-Nov-2009 23:00:00 -0600123", time.Time{}, false},
+	}
+	for _, test := range tests {
+		out, err := time.Parse(DateTimeLayout, test.in)
+		if !test.ok {
+			if err == nil {
+				t.Errorf("ParseDateTime(%q) expected error; got %q", test.in, out)
+			}
+		} else if err != nil {
+			t.Errorf("ParseDateTime(%q) expected %q; got %v", test.in, test.out, err)
+		} else if !out.Equal(test.out) {
+			t.Errorf("ParseDateTime(%q) expected %q; got %q", test.in, test.out, out)
+		}
+	}
+}
+
+func TestParseDate(t *testing.T) {
+	tests := []struct {
+		in  string
+		out time.Time
+		ok  bool
+	}{
+		{"2-Nov-2009", expectedDate, true},
+		{" 2-Nov-2009", expectedDate, true},
+	}
+	for _, test := range tests {
+		out, err := time.Parse(DateLayout, test.in)
+		if !test.ok {
+			if err == nil {
+				t.Errorf("ParseDate(%q) expected error; got %q", test.in, out)
+			}
+		} else if err != nil {
+			t.Errorf("ParseDate(%q) expected %q; got %v", test.in, test.out, err)
+		} else if !out.Equal(test.out) {
+			t.Errorf("ParseDate(%q) expected %q; got %q", test.in, test.out, out)
+		}
+	}
+}
diff --git a/message_test.go b/message_test.go
index d24b01b..b788a2d 100644
--- a/message_test.go
+++ b/message_test.go
@@ -29,28 +29,6 @@ func formatFields(fields []interface{}) (string, error) {
 	return b.String(), nil
 }
 
-func TestParseDate(t *testing.T) {
-	tests := []struct {
-		dateStr string
-		exp     time.Time
-	}{
-		{
-			"21-Nov-1997 09:55:06 -0600",
-			time.Date(1997, 11, 21, 9, 55, 6, 0, time.FixedZone("", -6*60*60)),
-		},
-	}
-	for _, test := range tests {
-		date, err := ParseDateTime(test.dateStr)
-		if err != nil {
-			t.Errorf("Failed parsing %q: %v", test.dateStr, err)
-			continue
-		}
-		if !date.Equal(test.exp) {
-			t.Errorf("Parse of %q: got %+v, want %+v", test.dateStr, date, test.exp)
-		}
-	}
-}
-
 var messageTests = []struct {
 	message *Message
 	fields  []interface{}
@@ -270,7 +248,7 @@ var envelopeTests = []struct {
 			MessageId: "43@example.org",
 		},
 		fields: []interface{}{
-			"10-Nov-2009 23:00:00 -0600",
+			"Tue, 10 Nov 2009 23:00:00 -0600",
 			"Hello World!",
 			[]interface{}{addrTests[0].fields},
 			[]interface{}{},
diff --git a/search_test.go b/search_test.go
index 3381ead..cb90f75 100644
--- a/search_test.go
+++ b/search_test.go
@@ -1,12 +1,13 @@
 package imap
 
 import (
+	"bytes"
 	"reflect"
 	"testing"
 	"time"
 )
 
-// Note to myself: writing these broing tests actually fixed 2 bugs :P
+// Note to myself: writing these boring tests actually fixed 2 bugs :P
 
 var searchSeqSet1, _ = NewSeqSet("1:42")
 var searchSeqSet2, _ = NewSeqSet("743:938")
@@ -14,41 +15,11 @@ var searchDate1 = time.Date(1997, 11, 21, 0, 0, 0, 0, time.UTC)
 var searchDate2 = time.Date(1984, 11, 5, 0, 0, 0, 0, time.UTC)
 
 var searchCriteriaTests = []struct {
-	fields   []interface{}
+	expected string
 	criteria *SearchCriteria
 }{
 	{
-		fields: []interface{}{
-			"1:42",
-			"ANSWERED",
-			"BCC", "root@nsa.gov",
-			"BEFORE", "21-Nov-1997",
-			"BODY", "hey there",
-			"CC", "root@gchq.gov.uk",
-			"DELETED",
-			"DRAFT",
-			"FLAGGED",
-			"FROM", "root@protonmail.com",
-			"HEADER", "Content-Type", "text/csv",
-			"KEYWORD", "cc",
-			"LARGER", "4242",
-			"NEW",
-			"NOT", []interface{}{"OLD", "ON", "5-Nov-1984"},
-			"OR", []interface{}{"RECENT", "SENTON", "21-Nov-1997"}, []interface{}{"SEEN", "SENTBEFORE", "5-Nov-1984"},
-			"SENTSINCE", "21-Nov-1997",
-			"SINCE", "5-Nov-1984",
-			"SMALLER", "643",
-			"SUBJECT", "saucisse royale",
-			"TEXT", "DILLE",
-			"TO", "cc@dille.cc",
-			"UID", "743:938",
-			"UNANSWERED",
-			"UNDELETED",
-			"UNDRAFT",
-			"UNFLAGGED",
-			"UNKEYWORD", "microsoft",
-			"UNSEEN",
-		},
+		expected: `(1:42 ANSWERED BCC root@nsa.gov BEFORE "21-Nov-1997" BODY "hey there" CC root@gchq.gov.uk DELETED DRAFT FLAGGED FROM root@protonmail.com HEADER Content-Type text/csv KEYWORD cc LARGER 4242 NEW NOT (OLD ON " 5-Nov-1984") OR (RECENT SENTON "21-Nov-1997") (SEEN SENTBEFORE " 5-Nov-1984") SENTSINCE "21-Nov-1997" SINCE " 5-Nov-1984" SMALLER 643 SUBJECT "saucisse royale" TEXT DILLE TO cc@dille.cc UID 743:938 UNANSWERED UNDELETED UNDRAFT UNFLAGGED UNKEYWORD microsoft UNSEEN)`,
 		criteria: &SearchCriteria{
 			SeqSet:   searchSeqSet1,
 			Answered: true,
@@ -91,10 +62,9 @@ func TestSearchCriteria_Format(t *testing.T) {
 		fields := test.criteria.Format()
 
 		got, _ := formatFields(fields)
-		expected, _ := formatFields(test.fields)
 
-		if got != expected {
-			t.Errorf("Invalid search criteria fields for #%v: got %v instead of %v", i, got, expected)
+		if got != test.expected {
+			t.Errorf("Invalid search criteria fields for #%v: got \n%v\n instead of \n%v", i, got, test.expected)
 		}
 	}
 }
@@ -103,7 +73,11 @@ func TestSearchCriteria_Parse(t *testing.T) {
 	for i, test := range searchCriteriaTests {
 		criteria := &SearchCriteria{}
 
-		if err := criteria.Parse(test.fields); err != nil {
+		b := bytes.NewBuffer([]byte(test.expected))
+		r := NewReader(b)
+		fields, _ := r.ReadFields()
+
+		if err := criteria.Parse(fields[0].([]interface{})); err != nil {
 			t.Errorf("Cannot parse search criteria for #%v: %v", i, err)
 		} else if !reflect.DeepEqual(criteria, test.criteria) {
 			t.Errorf("Invalid search criteria for #%v: got %v instead of %v", i, criteria, test.criteria)
diff --git a/write_test.go b/write_test.go
index 9b39f5d..66cedec 100644
--- a/write_test.go
+++ b/write_test.go
@@ -129,7 +129,7 @@ func TestWriter_WriteField_DateTime(t *testing.T) {
 	if err := w.writeField(dt); err != nil {
 		t.Error(err)
 	}
-	if b.String() != "\"10-Nov-2009 23:00:00 +0000\"" {
+	if b.String() != `"10-Nov-2009 23:00:00 +0000"` {
 		t.Error("Invalid date:", b.String())
 	}
 }