Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add iso8601 support #241

Merged
merged 2 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/gorilla/websocket v1.4.1
github.com/kr/pretty v0.1.0 // indirect
github.com/leodido/go-urn v1.1.0 // indirect
github.com/relvacode/iso8601 v1.3.0 // indirect
github.com/sirupsen/logrus v1.4.2
github.com/stretchr/testify v1.8.0
golang.org/x/sys v0.0.0-20220804214406-8e32c043e418 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/relvacode/iso8601 v1.3.0 h1:HguUjsGpIMh/zsTczGN3DVJFxTU/GX+MMmzcKoMO7ko=
github.com/relvacode/iso8601 v1.3.0/go.mod h1:FlNp+jz+TXpyRqgmM7tnzHHzBnz776kmAH2h3sZCn0I=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down
46 changes: 26 additions & 20 deletions ocpp1.6/types/datetime.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ package types

import (
"encoding/json"
"strings"
"errors"
"time"
)

// ISO8601 time format, assuming Zulu timestamp.
const ISO8601 = "2006-01-02T15:04:05Z"
"github.com/relvacode/iso8601"
)

// DateTimeFormat to be used for all OCPP messages.
// DateTimeFormat to be used when serializing all OCPP messages.
//
// The default dateTime format is RFC3339.
// Change this if another format is desired.
Expand All @@ -25,24 +24,31 @@ func NewDateTime(time time.Time) *DateTime {
return &DateTime{Time: time}
}

func null(b []byte) bool {
if len(b) != 4 {
return false
}
if b[0] != 'n' && b[1] != 'u' && b[2] != 'l' && b[3] != 'l' {
return false
}
return true
}

func (dt *DateTime) UnmarshalJSON(input []byte) error {
strInput := string(input)
strInput = strings.Trim(strInput, `"`)
if DateTimeFormat == "" {
var defaultTime time.Time
err := json.Unmarshal(input, &defaultTime)
if err != nil {
return err
}
dt.Time = defaultTime.Local()
// Do not parse null timestamps
if null(input) {
return nil
}
// Assert that timestamp is a string
if len(input) > 0 && input[0] == '"' && input[len(input)-1] == '"' {
input = input[1 : len(input)-1]
} else {
newTime, err := time.Parse(DateTimeFormat, strInput)
if err != nil {
return err
}
dt.Time = newTime.Local()
return errors.New("timestamp not enclosed in double quotes")
}
return nil
// Parse ISO8601
var err error
dt.Time, err = iso8601.Parse(input)
return err
}

func (dt *DateTime) MarshalJSON() ([]byte, error) {
Expand Down
64 changes: 63 additions & 1 deletion ocpp1.6_test/common_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package ocpp16_test

import (
"github.com/lorenzodonini/ocpp-go/ocpp1.6/types"
"encoding/json"
"strings"
"time"

"github.com/relvacode/iso8601"

"github.com/lorenzodonini/ocpp-go/ocpp1.6/types"
)

// Utility functions
Expand Down Expand Up @@ -114,3 +119,60 @@ func (suite *OcppV16TestSuite) TestMeterValueValidation() {
}
ExecuteGenericTestTable(suite.T(), testTable)
}

func (suite *OcppV16TestSuite) TestUnmarshalDateTime() {
testTable := []struct {
RawDateTime string
ExpectedValid bool
ExpectedTime time.Time
ExpectedError error
}{
{"\"2019-03-01T10:00:00Z\"", true, time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), nil},
{"\"2019-03-01T10:00:00+01:00\"", true, time.Date(2019, 3, 1, 9, 0, 0, 0, time.UTC), nil},
{"\"2019-03-01T10:00:00.000Z\"", true, time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), nil},
{"\"2019-03-01T10:00:00.000+01:00\"", true, time.Date(2019, 3, 1, 9, 0, 0, 0, time.UTC), nil},
{"\"2019-03-01T10:00:00\"", true, time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), nil},
{"\"2019-03-01T10:00:00+01\"", true, time.Date(2019, 3, 1, 9, 0, 0, 0, time.UTC), nil},
{"\"2019-03-01T10:00:00.000\"", true, time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), nil},
{"\"2019-03-01T10:00:00.000+01\"", true, time.Date(2019, 3, 1, 9, 0, 0, 0, time.UTC), nil},
{"\"2019-03-01 10:00:00+00:00\"", false, time.Time{}, &iso8601.UnexpectedCharacterError{Character: ' '}},
{"\"null\"", false, time.Time{}, &iso8601.UnexpectedCharacterError{Character: 110}},
{"\"\"", false, time.Time{}, &iso8601.RangeError{Element: "month", Min: 1, Max: 12}},
{"null", true, time.Time{}, nil},
}
for _, dt := range testTable {
jsonStr := []byte(dt.RawDateTime)
var dateTime types.DateTime
err := json.Unmarshal(jsonStr, &dateTime)
if dt.ExpectedValid {
suite.NoError(err)
suite.NotNil(dateTime)
suite.True(dt.ExpectedTime.Equal(dateTime.Time))
} else {
suite.Error(err)
suite.ErrorAs(err, &dt.ExpectedError)
}
}
}

func (suite *OcppV16TestSuite) TestMarshalDateTime() {
testTable := []struct {
Time time.Time
Format string
ExpectedFormattedString string
}{
{time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), "", "2019-03-01T10:00:00Z"},
{time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), time.RFC3339, "2019-03-01T10:00:00Z"},
{time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), time.RFC822, "01 Mar 19 10:00 UTC"},
{time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), time.RFC1123, "Fri, 01 Mar 2019 10:00:00 UTC"},
{time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), "invalidFormat", "invalidFormat"},
}
for _, dt := range testTable {
dateTime := types.NewDateTime(dt.Time)
types.DateTimeFormat = dt.Format
rawJson, err := dateTime.MarshalJSON()
suite.NoError(err)
formatted := strings.Trim(string(rawJson), "\"")
suite.Equal(dt.ExpectedFormattedString, formatted)
}
}
2 changes: 2 additions & 0 deletions ocpp1.6_test/ocpp16_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net/http"
"reflect"
"testing"
"time"

"github.com/stretchr/testify/require"

Expand Down Expand Up @@ -708,6 +709,7 @@ func (suite *OcppV16TestSuite) SetupTest() {
return defaultMessageId
}}
ocppj.SetMessageIdGenerator(suite.messageIdGenerator.generateId)
types.DateTimeFormat = time.RFC3339
}

func (suite *OcppV16TestSuite) TestIsConnected() {
Expand Down
46 changes: 25 additions & 21 deletions ocpp2.0.1/types/datetime.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package types

import (
"encoding/json"
"strings"
"errors"
"time"

"github.com/relvacode/iso8601"
)

// DateTimeFormat to be used for all OCPP messages.
// DateTimeFormat to be used when serializing all OCPP messages.
//
// The default dateTime format is RFC3339.
// Change this if another format is desired.
Expand All @@ -22,36 +24,38 @@ func NewDateTime(time time.Time) *DateTime {
return &DateTime{Time: time}
}

// Creates a new DateTime struct, embedding a struct generated using time.Now().
func Now() *DateTime {
return &DateTime{Time: time.Now()}
func null(b []byte) bool {
if len(b) != 4 {
return false
}
if b[0] != 'n' && b[1] != 'u' && b[2] != 'l' && b[3] != 'l' {
return false
}
return true
}

func (dt *DateTime) UnmarshalJSON(input []byte) error {
strInput := string(input)
strInput = strings.Trim(strInput, `"`)
if DateTimeFormat == "" {
var defaultTime time.Time
err := json.Unmarshal(input, &defaultTime)
if err != nil {
return err
}
dt.Time = defaultTime.Local()
// Do not parse null timestamps
if null(input) {
return nil
}
// Assert that timestamp is a string
if len(input) > 0 && input[0] == '"' && input[len(input)-1] == '"' {
input = input[1 : len(input)-1]
} else {
newTime, err := time.Parse(DateTimeFormat, strInput)
if err != nil {
return err
}
dt.Time = newTime.Local()
return errors.New("timestamp not enclosed in double quotes")
}
return nil
// Parse ISO8601
var err error
dt.Time, err = iso8601.Parse(input)
return err
}

func (dt *DateTime) MarshalJSON() ([]byte, error) {
if DateTimeFormat == "" {
return json.Marshal(dt.Time)
}
timeStr := FormatTimestamp(dt.Time)
timeStr := dt.FormatTimestamp()
return json.Marshal(timeStr)
}

Expand Down
60 changes: 60 additions & 0 deletions ocpp2.0.1_test/common_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package ocpp2_test

import (
"encoding/json"
"strings"
"time"

"github.com/relvacode/iso8601"

"github.com/lorenzodonini/ocpp-go/ocpp2.0.1/display"
"github.com/lorenzodonini/ocpp-go/ocpp2.0.1/types"
)
Expand Down Expand Up @@ -278,3 +281,60 @@ func (suite *OcppV2TestSuite) TestMessageInfoValidation() {
}
ExecuteGenericTestTable(suite.T(), testTable)
}

func (suite *OcppV2TestSuite) TestUnmarshalDateTime() {
testTable := []struct {
RawDateTime string
ExpectedValid bool
ExpectedTime time.Time
ExpectedError error
}{
{"\"2019-03-01T10:00:00Z\"", true, time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), nil},
{"\"2019-03-01T10:00:00+01:00\"", true, time.Date(2019, 3, 1, 9, 0, 0, 0, time.UTC), nil},
{"\"2019-03-01T10:00:00.000Z\"", true, time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), nil},
{"\"2019-03-01T10:00:00.000+01:00\"", true, time.Date(2019, 3, 1, 9, 0, 0, 0, time.UTC), nil},
{"\"2019-03-01T10:00:00\"", true, time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), nil},
{"\"2019-03-01T10:00:00+01\"", true, time.Date(2019, 3, 1, 9, 0, 0, 0, time.UTC), nil},
{"\"2019-03-01T10:00:00.000\"", true, time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), nil},
{"\"2019-03-01T10:00:00.000+01\"", true, time.Date(2019, 3, 1, 9, 0, 0, 0, time.UTC), nil},
{"\"2019-03-01 10:00:00+00:00\"", false, time.Time{}, &iso8601.UnexpectedCharacterError{Character: ' '}},
{"\"null\"", false, time.Time{}, &iso8601.UnexpectedCharacterError{Character: 110}},
{"\"\"", false, time.Time{}, &iso8601.RangeError{Element: "month", Min: 1, Max: 12}},
{"null", true, time.Time{}, nil},
}
for _, dt := range testTable {
jsonStr := []byte(dt.RawDateTime)
var dateTime types.DateTime
err := json.Unmarshal(jsonStr, &dateTime)
if dt.ExpectedValid {
suite.NoError(err)
suite.NotNil(dateTime)
suite.True(dt.ExpectedTime.Equal(dateTime.Time))
} else {
suite.Error(err)
suite.ErrorAs(err, &dt.ExpectedError)
}
}
}

func (suite *OcppV2TestSuite) TestMarshalDateTime() {
testTable := []struct {
Time time.Time
Format string
ExpectedFormattedString string
}{
{time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), "", "2019-03-01T10:00:00Z"},
{time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), time.RFC3339, "2019-03-01T10:00:00Z"},
{time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), time.RFC822, "01 Mar 19 10:00 UTC"},
{time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), time.RFC1123, "Fri, 01 Mar 2019 10:00:00 UTC"},
{time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), "invalidFormat", "invalidFormat"},
}
for _, dt := range testTable {
dateTime := types.NewDateTime(dt.Time)
types.DateTimeFormat = dt.Format
rawJson, err := dateTime.MarshalJSON()
suite.NoError(err)
formatted := strings.Trim(string(rawJson), "\"")
suite.Equal(dt.ExpectedFormattedString, formatted)
}
}
2 changes: 2 additions & 0 deletions ocpp2.0.1_test/ocpp2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net/http"
"reflect"
"testing"
"time"

"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -1141,6 +1142,7 @@ func (suite *OcppV2TestSuite) SetupTest() {
return defaultMessageId
}}
ocppj.SetMessageIdGenerator(suite.messageIdGenerator.generateId)
types.DateTimeFormat = time.RFC3339
}

func (suite *OcppV2TestSuite) TestIsConnected() {
Expand Down