From 17d561f8296b18855e8f8846ca0093e2957e9343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?CORNIERE=20R=C3=A9mi?= Date: Wed, 29 Apr 2020 10:13:31 +0200 Subject: [PATCH] Support for XEP-0082. Parsing of times with an offset does not work for now (should it ?) --- stanza/datetime_profiles.go | 70 +++++++++++ stanza/datetime_profiles_test.go | 191 +++++++++++++++++++++++++++++++ 2 files changed, 261 insertions(+) create mode 100644 stanza/datetime_profiles.go create mode 100644 stanza/datetime_profiles_test.go diff --git a/stanza/datetime_profiles.go b/stanza/datetime_profiles.go new file mode 100644 index 0000000..7a2ef74 --- /dev/null +++ b/stanza/datetime_profiles.go @@ -0,0 +1,70 @@ +package stanza + +import ( + "errors" + "strings" + "time" +) + +// Helper structures and functions to manage dates and timestamps as defined in +// XEP-0082: XMPP Date and Time Profiles (https://xmpp.org/extensions/xep-0082.html) + +const dateLayoutXEP0082 = "2006-01-02" +const timeLayoutXEP0082 = "15:04:05+00:00" + +var InvalidDateInput = errors.New("could not parse date. Input might not be in a supported format") +var InvalidDateOutput = errors.New("could not format date as desired") + +type JabberDate struct { + value time.Time +} + +func (d JabberDate) DateToString() string { + return d.value.Format(dateLayoutXEP0082) +} + +func (d JabberDate) DateTimeToString(nanos bool) string { + if nanos { + return d.value.Format(time.RFC3339Nano) + } + return d.value.Format(time.RFC3339) +} + +func (d JabberDate) TimeToString(nanos bool) (string, error) { + if nanos { + spl := strings.Split(d.value.Format(time.RFC3339Nano), "T") + if len(spl) != 2 { + return "", InvalidDateOutput + } + return spl[1], nil + } + spl := strings.Split(d.value.Format(time.RFC3339), "T") + if len(spl) != 2 { + return "", InvalidDateOutput + } + return spl[1], nil +} + +func NewJabberDateFromString(strDate string) (JabberDate, error) { + t, err := time.Parse(time.RFC3339, strDate) + if err == nil { + return JabberDate{value: t}, nil + } + + t, err = time.Parse(time.RFC3339Nano, strDate) + if err == nil { + return JabberDate{value: t}, nil + } + + t, err = time.Parse(dateLayoutXEP0082, strDate) + if err == nil { + return JabberDate{value: t}, nil + } + + t, err = time.Parse(timeLayoutXEP0082, strDate) + if err == nil { + return JabberDate{value: t}, nil + } + + return JabberDate{}, InvalidDateInput +} diff --git a/stanza/datetime_profiles_test.go b/stanza/datetime_profiles_test.go new file mode 100644 index 0000000..98aa4cd --- /dev/null +++ b/stanza/datetime_profiles_test.go @@ -0,0 +1,191 @@ +package stanza + +import ( + "testing" + "time" +) + +func TestDateToString(t *testing.T) { + t1 := JabberDate{value: time.Now()} + t2 := JabberDate{value: time.Now().Add(24 * time.Hour)} + + t1Str := t1.DateToString() + t2Str := t2.DateToString() + + if t1Str == t2Str { + t.Fatalf("time representations should not be identical") + } +} + +func TestDateToStringOracle(t *testing.T) { + expected := "2009-11-10" + loc, err := time.LoadLocation("Asia/Shanghai") + if err != nil { + t.Fatalf(err.Error()) + } + t1 := JabberDate{value: time.Date(2009, time.November, 10, 23, 3, 22, 89, loc)} + + t1Str := t1.DateToString() + if t1Str != expected { + t.Fatalf("time is different than expected. Expected: %s, Actual: %s", expected, t1Str) + } +} + +func TestDateTimeToString(t *testing.T) { + t1 := JabberDate{value: time.Now()} + t2 := JabberDate{value: time.Now().Add(10 * time.Second)} + + t1Str := t1.DateTimeToString(false) + t2Str := t2.DateTimeToString(false) + + if t1Str == t2Str { + t.Fatalf("time representations should not be identical") + } +} + +func TestDateTimeToStringOracle(t *testing.T) { + expected := "2009-11-10T23:03:22+08:00" + loc, err := time.LoadLocation("Asia/Shanghai") + if err != nil { + t.Fatalf(err.Error()) + } + t1 := JabberDate{value: time.Date(2009, time.November, 10, 23, 3, 22, 89, loc)} + + t1Str := t1.DateTimeToString(false) + if t1Str != expected { + t.Fatalf("time is different than expected. Expected: %s, Actual: %s", expected, t1Str) + } +} + +func TestDateTimeToStringNanos(t *testing.T) { + t1 := JabberDate{value: time.Now()} + time.After(10 * time.Millisecond) + t2 := JabberDate{value: time.Now()} + + t1Str := t1.DateTimeToString(true) + t2Str := t2.DateTimeToString(true) + + if t1Str == t2Str { + t.Fatalf("time representations should not be identical") + } +} + +func TestDateTimeToStringNanosOracle(t *testing.T) { + expected := "2009-11-10T23:03:22.000000089+08:00" + loc, err := time.LoadLocation("Asia/Shanghai") + if err != nil { + t.Fatalf(err.Error()) + } + t1 := JabberDate{value: time.Date(2009, time.November, 10, 23, 3, 22, 89, loc)} + + t1Str := t1.DateTimeToString(true) + if t1Str != expected { + t.Fatalf("time is different than expected. Expected: %s, Actual: %s", expected, t1Str) + } +} + +func TestTimeToString(t *testing.T) { + t1 := JabberDate{value: time.Now()} + t2 := JabberDate{value: time.Now().Add(10 * time.Second)} + + t1Str, err := t1.TimeToString(false) + if err != nil { + t.Fatalf(err.Error()) + } + t2Str, err := t2.TimeToString(false) + if err != nil { + t.Fatalf(err.Error()) + } + + if t1Str == t2Str { + t.Fatalf("time representations should not be identical") + } +} + +func TestTimeToStringOracle(t *testing.T) { + expected := "23:03:22+08:00" + loc, err := time.LoadLocation("Asia/Shanghai") + if err != nil { + t.Fatalf(err.Error()) + } + t1 := JabberDate{value: time.Date(2009, time.November, 10, 23, 3, 22, 89, loc)} + + t1Str, err := t1.TimeToString(false) + if err != nil { + t.Fatalf(err.Error()) + } + + if t1Str != expected { + t.Fatalf("time is different than expected. Expected: %s, Actual: %s", expected, t1Str) + } +} + +func TestTimeToStringNanos(t *testing.T) { + t1 := JabberDate{value: time.Now()} + time.After(10 * time.Millisecond) + t2 := JabberDate{value: time.Now()} + + t1Str, err := t1.TimeToString(true) + if err != nil { + t.Fatalf(err.Error()) + } + t2Str, err := t2.TimeToString(true) + if err != nil { + t.Fatalf(err.Error()) + } + + if t1Str == t2Str { + t.Fatalf("time representations should not be identical") + } +} +func TestTimeToStringNanosOracle(t *testing.T) { + expected := "23:03:22.000000089+08:00" + loc, err := time.LoadLocation("Asia/Shanghai") + if err != nil { + t.Fatalf(err.Error()) + } + t1 := JabberDate{value: time.Date(2009, time.November, 10, 23, 3, 22, 89, loc)} + + t1Str, err := t1.TimeToString(true) + if err != nil { + t.Fatalf(err.Error()) + } + + if t1Str != expected { + t.Fatalf("time is different than expected. Expected: %s, Actual: %s", expected, t1Str) + } +} + +func TestJabberDateParsing(t *testing.T) { + date := "2009-11-10" + _, err := NewJabberDateFromString(date) + if err != nil { + t.Fatalf(err.Error()) + } + + dateTime := "2009-11-10T23:03:22+08:00" + _, err = NewJabberDateFromString(dateTime) + if err != nil { + t.Fatalf(err.Error()) + } + + dateTimeNanos := "2009-11-10T23:03:22.000000089+08:00" + _, err = NewJabberDateFromString(dateTimeNanos) + if err != nil { + t.Fatalf(err.Error()) + } + + // TODO : fix these. Parsing a time with an offset doesn't work + //time := "23:03:22+08:00" + //_, err = NewJabberDateFromString(time) + //if err != nil { + // t.Fatalf(err.Error()) + //} + + //timeNanos := "23:03:22.000000089+08:00" + //_, err = NewJabberDateFromString(timeNanos) + //if err != nil { + // t.Fatalf(err.Error()) + //} + +}