Skip to content
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
8 changes: 4 additions & 4 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import (

var (
// ErrZoneCharacters indicates an incorrect amount of characters was passed to ParseISOZone.
ErrZoneCharacters = errors.New("iso8601: Expected between 3 and 6 characters for zone information")
ErrZoneCharacters = errors.New("iso8601: Expected 1 or between 3 and 6 characters for zone information")

// ErrInvalidZone indicates an invalid timezone per the standard that doesn't violate any specific
// character parsing rules.
ErrInvalidZone = errors.New("iso8601: Specified zone is invalid")

// ErrRemainingData indicates that there is extra data after a `Z` character.
ErrRemainingData = errors.New("iso8601: Unexepected remaining data after `Z`")
ErrRemainingData = errors.New("iso8601: Unexpected remaining data after `Z`")

// ErrNotString indicates that a non string type was passed to the UnmarshalJSON method of `Time`.
ErrNotString = errors.New("iso8601: Invalid json type (expected string)")
Expand All @@ -25,15 +25,15 @@ var (
)

func newUnexpectedCharacterError(c byte) error {
return &UnexpectedCharacterError{Character: c}
return UnexpectedCharacterError{Character: c}
}

// UnexpectedCharacterError indicates the parser scanned a character that was not expected at that time.
type UnexpectedCharacterError struct {
Character byte
}

func (e *UnexpectedCharacterError) Error() string {
func (e UnexpectedCharacterError) Error() string {
return fmt.Sprintf("iso8601: Unexpected character `%c`", e.Character)
}

Expand Down
10 changes: 9 additions & 1 deletion iso8601.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,16 @@ const (
// +01:45
// +0145
func ParseISOZone(inp []byte) (*time.Location, error) {
if len(inp) != 1 && (len(inp) < 3 || len(inp) > 6) {
if len(inp) == 0 {
return nil, ErrZoneCharacters
}

var neg bool
switch inp[0] {
case 'Z', 'z':
if len(inp) != 1 {
return nil, ErrRemainingData
}
return time.UTC, nil
case '+':
case '-':
Expand All @@ -50,6 +54,10 @@ func ParseISOZone(inp []byte) (*time.Location, error) {
return nil, newUnexpectedCharacterError(inp[0])
}

if len(inp) < 3 || len(inp) > 6 {
return nil, ErrZoneCharacters
}

var offset int

var z uint
Expand Down
97 changes: 95 additions & 2 deletions iso8601_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package iso8601

import (
"errors"
"testing"
"time"
)
Expand All @@ -27,8 +28,8 @@ type TestCase struct {
func (tc TestCase) CheckError(err error, t *testing.T) bool {
if err != nil {
if tc.ShouldInvalidRange {
re, ok := err.(*RangeError)
if !ok {
var re *RangeError
if !errors.As(err, &re) {
t.Fatalf("Found error %s of type %T but was expecting a RangeError", err, err)
}

Expand Down Expand Up @@ -333,6 +334,18 @@ var cases = []TestCase{
Using: "2017-+04-24T09:41:34.502-00:00",
ShouldFailParse: true,
},
{
Using: "2017-01-01T00:00:60.000Z+",
ShouldFailParse: true,
},
{
Using: "2017-01-01T00:00:60.000Zz",
ShouldFailParse: true,
},
{
Using: "2017-01-01T00:00:60.000Z00:00",
ShouldFailParse: true,
},

// Invalid Range Test Cases
{
Expand Down Expand Up @@ -467,3 +480,83 @@ func TestParseStringInLocation(t *testing.T) {
)
}
}

type ZoneTestCase struct {
Using string
Zone float64
Expect error
}

func TestParseISOZone(t *testing.T) {
var zoneTestCases = []ZoneTestCase{
{
Using: "",
Expect: ErrZoneCharacters,
},
{
Using: "Z",
Zone: 0,
},
{
Using: "z",
Zone: 0,
},
{
Using: "+00:00",
Zone: 0,
},
{
Using: "00:00",
Expect: UnexpectedCharacterError{Character: '0'},
},
{
Using: "-00:00",
Expect: ErrInvalidZone,
},
{
Using: "-05:30",
Zone: -5.5,
},
{
Using: "+05:30",
Zone: 5.5,
},
{
Using: "-0530",
Zone: -5.5,
},
{
Using: "Zz",
Expect: ErrRemainingData,
},
{
Using: "^",
Expect: UnexpectedCharacterError{Character: '^'},
},
{
Using: "-01",
Zone: -1,
},
}

for _, tc := range zoneTestCases {
t.Run(tc.Using, func(t *testing.T) {
z, err := ParseISOZone([]byte(tc.Using))
if !errors.Is(err, tc.Expect) {
t.Errorf("ParseISOZone expected to return error %v (%T), got %v (%T)", tc.Expect, tc.Expect, err, err)
return
}

if tc.Expect != nil {
return
}

ts := time.Date(2024, 1, 1, 1, 1, 1, 0, z)
_, offset := ts.Zone()

if offset := float64(offset) / 3600; offset != tc.Zone {
t.Errorf("ParseISOZone expected to return zone %v, got %v", tc.Zone, offset)
}
})
}
}
Loading