This repository has been archived on 2019-06-23. You can view files and clone it, but cannot push or open issues/pull-requests.
mumbledj/vendor/github.com/ChannelMeter/iso8601duration/duration.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
}