122 lines
2.7 KiB
Go
122 lines
2.7 KiB
Go
// Package duration provides a partial implementation of ISO8601 durations. (no months)
|
|
package duration
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
"text/template"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
// ErrBadFormat is returned when parsing fails
|
|
ErrBadFormat = errors.New("bad format string")
|
|
|
|
// ErrNoMonth is raised when a month is in the format string
|
|
ErrNoMonth = errors.New("no months allowed")
|
|
|
|
tmpl = template.Must(template.New("duration").Parse(`P{{if .Years}}{{.Years}}Y{{end}}{{if .Weeks}}{{.Weeks}}W{{end}}{{if .Days}}{{.Days}}D{{end}}{{if .HasTimePart}}T{{end }}{{if .Hours}}{{.Hours}}H{{end}}{{if .Minutes}}{{.Minutes}}M{{end}}{{if .Seconds}}{{.Seconds}}S{{end}}`))
|
|
|
|
full = regexp.MustCompile(`P((?P<year>\d+)Y)?((?P<month>\d+)M)?((?P<day>\d+)D)?(T((?P<hour>\d+)H)?((?P<minute>\d+)M)?((?P<second>\d+)S)?)?`)
|
|
week = regexp.MustCompile(`P((?P<week>\d+)W)`)
|
|
)
|
|
|
|
type Duration struct {
|
|
Years int
|
|
Weeks int
|
|
Days int
|
|
Hours int
|
|
Minutes int
|
|
Seconds int
|
|
}
|
|
|
|
func FromString(dur string) (*Duration, error) {
|
|
var (
|
|
match []string
|
|
re *regexp.Regexp
|
|
)
|
|
|
|
if week.MatchString(dur) {
|
|
match = week.FindStringSubmatch(dur)
|
|
re = week
|
|
} else if full.MatchString(dur) {
|
|
match = full.FindStringSubmatch(dur)
|
|
re = full
|
|
} else {
|
|
return nil, ErrBadFormat
|
|
}
|
|
|
|
d := &Duration{}
|
|
|
|
for i, name := range re.SubexpNames() {
|
|
part := match[i]
|
|
if i == 0 || name == "" || part == "" {
|
|
continue
|
|
}
|
|
|
|
val, err := strconv.Atoi(part)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
switch name {
|
|
case "year":
|
|
d.Years = val
|
|
case "month":
|
|
return nil, ErrNoMonth
|
|
case "week":
|
|
d.Weeks = val
|
|
case "day":
|
|
d.Days = val
|
|
case "hour":
|
|
d.Hours = val
|
|
case "minute":
|
|
d.Minutes = val
|
|
case "second":
|
|
d.Seconds = val
|
|
default:
|
|
return nil, errors.New(fmt.Sprintf("unknown field %s", name))
|
|
}
|
|
}
|
|
|
|
return d, nil
|
|
}
|
|
|
|
// String prints out the value passed in. It's not strictly according to the
|
|
// ISO spec, but it's pretty close. In particular, to completely conform it
|
|
// would need to round up to the next largest unit. 61 seconds to 1 minute 1
|
|
// second, for example. It would also need to disallow weeks mingling with
|
|
// other units.
|
|
func (d *Duration) String() string {
|
|
var s bytes.Buffer
|
|
|
|
err := tmpl.Execute(&s, d)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return s.String()
|
|
}
|
|
|
|
func (d *Duration) HasTimePart() bool {
|
|
return d.Hours != 0 || d.Minutes != 0 || d.Seconds != 0
|
|
}
|
|
|
|
func (d *Duration) ToDuration() time.Duration {
|
|
day := time.Hour * 24
|
|
year := day * 365
|
|
|
|
tot := time.Duration(0)
|
|
|
|
tot += year * time.Duration(d.Years)
|
|
tot += day * 7 * time.Duration(d.Weeks)
|
|
tot += day * time.Duration(d.Days)
|
|
tot += time.Hour * time.Duration(d.Hours)
|
|
tot += time.Minute * time.Duration(d.Minutes)
|
|
tot += time.Second * time.Duration(d.Seconds)
|
|
|
|
return tot
|
|
}
|