Skip to content

Commit c243d3f

Browse files
committed
added all features, added docs, fix README.md, added tests
1 parent c145072 commit c243d3f

8 files changed

+1125
-2
lines changed

README.md

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,9 @@
1-
# duration
2-
Library for parsing ISO 8601 duration format
1+
# ISODURATION
2+
It's multifunctional and fast a Go module for parsing [ISO 8601 duration format](https://www.digi.com/resources/documentation/digidocs/90001488-13/reference/r_iso_8601_duration_format.htm)
3+
4+
## Installation
5+
```
6+
go get github.com/MyBlackJay/isoduration
7+
```
8+
9+

benchmark_test.go

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package isoduration
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func BenchmarkParseDuration(b *testing.B) {
8+
str := "P30Y11.9M29.5D29.1WT10H30.5M5S"
9+
for i := 0; i < b.N; i++ {
10+
v, err := ParseDuration(str)
11+
if err != nil {
12+
panic(err)
13+
}
14+
v.ToTimeDuration()
15+
}
16+
}

constants.go

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package isoduration
2+
3+
const (
4+
// PERIOD is constant that defines period designators
5+
PERIOD = 'P'
6+
// TIME is constant that defines time designators
7+
TIME = 'T'
8+
9+
// YEAR is constant that defines year designators
10+
YEAR = 'Y'
11+
// MONTH is constant that defines month designators
12+
MONTH = 'M'
13+
// WEEK is constant that defines week designators
14+
WEEK = 'W'
15+
// DAY is constant that defines day designators
16+
DAY = 'D'
17+
// HOUR is constant that defines hour designators
18+
HOUR = 'H'
19+
// MINUTE is constant that defines minute designators
20+
MINUTE = 'M'
21+
// SECOND is constant that defines second designators
22+
SECOND = 'S'
23+
24+
// DayHours is constant that defines number of hours in a day
25+
DayHours = 24
26+
// WeekDays is constant that defines number of days in a week
27+
WeekDays = 7
28+
// YearDays is constant that defines number of days in a year
29+
YearDays = 365
30+
// MonthDays is constant that defines number of days in a month
31+
MonthDays = YearDays / 12
32+
)

designators.go

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package isoduration
2+
3+
import (
4+
"strconv"
5+
"time"
6+
)
7+
8+
// supported designators
9+
var (
10+
periodDesignators = [4]rune{YEAR, MONTH, WEEK, DAY}
11+
timeDesignators = [3]rune{HOUR, MINUTE, SECOND}
12+
)
13+
14+
// designators methods
15+
var (
16+
periodDesignatorsDef = map[rune]periodDesignatorFunc{
17+
YEAR: {
18+
get: func(d *PeriodDuration) time.Duration {
19+
return time.Duration(float64(time.Hour) * DayHours * YearDays * d.years)
20+
},
21+
set: func(d *PeriodDuration, v float64) { d.years = v },
22+
string: func(d *PeriodDuration) string { return strconv.FormatFloat(d.years, 'f', -1, 64) + "Y" },
23+
checkSet: func(d *PeriodDuration) bool {
24+
if d.years == 0 {
25+
return false
26+
} else {
27+
return true
28+
}
29+
},
30+
},
31+
MONTH: {
32+
get: func(d *PeriodDuration) time.Duration {
33+
return time.Duration(float64(time.Hour) * DayHours * MonthDays * d.months)
34+
},
35+
set: func(d *PeriodDuration, v float64) { d.months = v },
36+
string: func(d *PeriodDuration) string { return strconv.FormatFloat(d.months, 'f', -1, 64) + "M" },
37+
checkSet: func(d *PeriodDuration) bool {
38+
if d.months == 0 {
39+
return false
40+
} else {
41+
return true
42+
}
43+
},
44+
},
45+
DAY: {
46+
get: func(d *PeriodDuration) time.Duration { return time.Duration(float64(time.Hour) * DayHours * d.days) },
47+
set: func(d *PeriodDuration, v float64) { d.days = v },
48+
string: func(d *PeriodDuration) string { return strconv.FormatFloat(d.days, 'f', -1, 64) + "D" },
49+
checkSet: func(d *PeriodDuration) bool {
50+
if d.days == 0 {
51+
return false
52+
} else {
53+
return true
54+
}
55+
},
56+
},
57+
WEEK: {
58+
get: func(d *PeriodDuration) time.Duration {
59+
return time.Duration(float64(time.Hour*DayHours*WeekDays) * d.weeks)
60+
},
61+
set: func(d *PeriodDuration, v float64) { d.weeks = v },
62+
string: func(d *PeriodDuration) string { return strconv.FormatFloat(d.weeks, 'f', -1, 64) + "W" },
63+
checkSet: func(d *PeriodDuration) bool {
64+
if d.weeks == 0 {
65+
return false
66+
} else {
67+
return true
68+
}
69+
},
70+
},
71+
}
72+
73+
timeDesignatorsDef = map[rune]timeDesignatorFunc{
74+
HOUR: {
75+
get: func(td *TimeDuration) time.Duration { return time.Duration(float64(time.Hour) * td.hours) },
76+
set: func(td *TimeDuration, v float64) { td.hours = v },
77+
string: func(td *TimeDuration) string { return strconv.FormatFloat(td.hours, 'f', -1, 64) + "H" },
78+
checkSet: func(td *TimeDuration) bool {
79+
if td.hours == 0 {
80+
return false
81+
} else {
82+
return true
83+
}
84+
},
85+
},
86+
MINUTE: {
87+
get: func(td *TimeDuration) time.Duration { return time.Duration(float64(time.Minute) * td.minutes) },
88+
set: func(td *TimeDuration, v float64) { td.minutes = v },
89+
string: func(td *TimeDuration) string { return strconv.FormatFloat(td.minutes, 'f', -1, 64) + "M" },
90+
checkSet: func(td *TimeDuration) bool {
91+
if td.minutes == 0 {
92+
return false
93+
} else {
94+
return true
95+
}
96+
},
97+
},
98+
SECOND: {
99+
get: func(td *TimeDuration) time.Duration { return time.Duration(float64(time.Second) * td.seconds) },
100+
set: func(td *TimeDuration, v float64) { td.seconds = v },
101+
string: func(td *TimeDuration) string { return strconv.FormatFloat(td.seconds, 'f', -1, 64) + "S" },
102+
checkSet: func(td *TimeDuration) bool {
103+
if td.seconds == 0 {
104+
return false
105+
} else {
106+
return true
107+
}
108+
},
109+
},
110+
}
111+
)
112+
113+
// designatorFunc interface for parser
114+
type designatorFunc interface {
115+
periodDesignatorFunc | timeDesignatorFunc
116+
}
117+
118+
// periodDesignatorFunc defines the available methods available for working with period designators
119+
type periodDesignatorFunc struct {
120+
get func(*PeriodDuration) time.Duration
121+
string func(*PeriodDuration) string
122+
set func(*PeriodDuration, float64)
123+
checkSet func(*PeriodDuration) bool
124+
}
125+
126+
// timeDesignatorFunc defines the available methods available for working with time designators
127+
type timeDesignatorFunc struct {
128+
get func(*TimeDuration) time.Duration
129+
string func(*TimeDuration) string
130+
set func(*TimeDuration, float64)
131+
checkSet func(duration *TimeDuration) bool
132+
}

errors.go

+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package isoduration
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"reflect"
7+
)
8+
9+
// is checks type matching
10+
var is = func(err, target error) bool {
11+
if err == nil {
12+
return false
13+
}
14+
15+
if reflect.TypeOf(err) != reflect.TypeOf(target) {
16+
return false
17+
}
18+
19+
return reflect.DeepEqual(err, target)
20+
}
21+
22+
// IsNotIsoFormatError occurs when parsing a string in ISO 8601 duration format, the error cannot be clearly identified
23+
var IsNotIsoFormatError = errors.New("incorrect ISO 8601 duration format")
24+
25+
// TimeIsEmptyError occurs when a time designator is found in a line, but its value is not found
26+
// For example: P10YT
27+
var TimeIsEmptyError = errors.New("incorrect ISO 8601 T duration format, designator T found, but value is empty")
28+
29+
// PeriodIsEmptyError occurs when a period designator is found in a line, but its value is not found
30+
// For example: P
31+
var PeriodIsEmptyError = errors.New("incorrect ISO 8601 P duration format, designator P found, but value is empty")
32+
33+
// IncorrectIsoFormatError occurs when a token is found in a string that cannot be converted to the float64 type
34+
// For example: P10,5Y
35+
type IncorrectIsoFormatError struct {
36+
text string
37+
in string
38+
}
39+
40+
// Error defines error output
41+
func (i *IncorrectIsoFormatError) Error() string {
42+
return fmt.Sprintf(i.text, i.in)
43+
}
44+
45+
// Is checks for object matching
46+
func (i *IncorrectIsoFormatError) Is(err error) bool {
47+
return is(i, err)
48+
}
49+
50+
// NewIncorrectIsoFormatError creates new IncorrectIsoFormatError
51+
func NewIncorrectIsoFormatError(in string) *IncorrectIsoFormatError {
52+
return &IncorrectIsoFormatError{"incorrect ISO 8601 duration format, invalid tokens %s", in}
53+
}
54+
55+
// IncorrectDesignatorError occurs when an unknown designator is encountered in the line.
56+
// For example: PT10Y or P1V
57+
type IncorrectDesignatorError struct {
58+
text string
59+
designator rune
60+
state rune
61+
}
62+
63+
// Error defines error output
64+
func (i *IncorrectDesignatorError) Error() string {
65+
return fmt.Sprintf(i.text, i.state, i.designator)
66+
}
67+
68+
// Is checks for object matching
69+
func (i *IncorrectDesignatorError) Is(err error) bool {
70+
return is(i, err)
71+
}
72+
73+
// NewIncorrectDesignatorError creates new IncorrectDesignatorError
74+
func NewIncorrectDesignatorError(state, designator rune) *IncorrectDesignatorError {
75+
return &IncorrectDesignatorError{"incorrect ISO 8601 duration %c format, invalid designator %c", designator, state}
76+
}
77+
78+
// DesignatorNotFoundError occurs when there is no designator in the line after the token
79+
// For example P10T10H.
80+
type DesignatorNotFoundError struct {
81+
text string
82+
state rune
83+
after string
84+
}
85+
86+
// Error defines error output
87+
func (i *DesignatorNotFoundError) Error() string {
88+
return fmt.Sprintf(i.text, i.state, i.after)
89+
}
90+
91+
// Is checks for object matching
92+
func (i *DesignatorNotFoundError) Is(err error) bool {
93+
return is(i, err)
94+
}
95+
96+
// NewDesignatorNotFoundError creates new DesignatorNotFoundError
97+
func NewDesignatorNotFoundError(state rune, after string) *DesignatorNotFoundError {
98+
return &DesignatorNotFoundError{"incorrect ISO 8601 duration %c format, designator not found after token %s", state, after}
99+
}
100+
101+
// DesignatorValueNotFoundError occurs when no value is found for the designator
102+
// For example: PT1HM
103+
type DesignatorValueNotFoundError struct {
104+
text string
105+
state rune
106+
designator rune
107+
}
108+
109+
// Error defines error output
110+
func (i *DesignatorValueNotFoundError) Error() string {
111+
return fmt.Sprintf(i.text, i.state, i.designator)
112+
}
113+
114+
// Is checks for object matching
115+
func (i *DesignatorValueNotFoundError) Is(err error) bool {
116+
return is(i, err)
117+
}
118+
119+
// NewDesignatorValueNotFoundError creates new DesignatorValueNotFoundError
120+
func NewDesignatorValueNotFoundError(state, designator rune) *DesignatorValueNotFoundError {
121+
return &DesignatorValueNotFoundError{"incorrect ISO 8601 duration %c format, %c designator's value not found", state, designator}
122+
}
123+
124+
// DesignatorMetError occurs when this designator has already been processed previously.
125+
// Affect: if you write string like PT10H0M10M it defines value as 10
126+
// For example: P1Y5Y
127+
type DesignatorMetError struct {
128+
text string
129+
designator rune
130+
}
131+
132+
// Error defines error output
133+
func (i *DesignatorMetError) Error() string {
134+
return fmt.Sprintf(i.text, i.designator)
135+
}
136+
137+
// Is checks for object matching
138+
func (i *DesignatorMetError) Is(err error) bool {
139+
return is(i, err)
140+
}
141+
142+
// NewDesignatorMetError creates new DesignatorMetError
143+
func NewDesignatorMetError(designator rune) *DesignatorMetError {
144+
return &DesignatorMetError{"incorrect ISO 8601 duration format, the designator %c has already been processed", designator}
145+
}

0 commit comments

Comments
 (0)