stash/vendor/github.com/zencoder/go-dash/v3/mpd/duration.go
2023-03-07 12:57:27 +11:00

206 lines
4.4 KiB
Go

// based on code from golang src/time/time.go
package mpd
import (
"encoding/xml"
"errors"
"fmt"
"regexp"
"strconv"
"strings"
"time"
)
type Duration time.Duration
var (
rStart = "^P" // Must start with a 'P'
rDays = "(\\d+D)?" // We only allow Days for durations, not Months or Years
rTime = "(?:T" // If there's any 'time' units then they must be preceded by a 'T'
rHours = "(\\d+H)?" // Hours
rMinutes = "(\\d+M)?" // Minutes
rSeconds = "([\\d.]+S)?" // Seconds (Potentially decimal)
rEnd = ")?$" // end of regex must close "T" capture group
)
var xmlDurationRegex = regexp.MustCompile(rStart + rDays + rTime + rHours + rMinutes + rSeconds + rEnd)
func (d Duration) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
return xml.Attr{Name: name, Value: d.String()}, nil
}
func (d *Duration) UnmarshalXMLAttr(attr xml.Attr) error {
dur, err := ParseDuration(attr.Value)
if err != nil {
return err
}
*d = Duration(dur)
return nil
}
// String renders a Duration in XML Duration Data Type format
func (d *Duration) String() string {
// Largest time is 2540400h10m10.000000000s
var buf [32]byte
w := len(buf)
u := uint64(*d)
neg := *d < 0
if neg {
u = -u
}
if u < uint64(time.Second) {
// Special case: if duration is smaller than a second,
// use smaller units, like 1.2ms
var prec int
w--
buf[w] = 'S'
w--
if u == 0 {
return "PT0S"
}
/*
switch {
case u < uint64(Millisecond):
// print microseconds
prec = 3
// U+00B5 'µ' micro sign == 0xC2 0xB5
w-- // Need room for two bytes.
copy(buf[w:], "µ")
default:
// print milliseconds
prec = 6
buf[w] = 'm'
}
*/
w, u = fmtFrac(buf[:w], u, prec)
w = fmtInt(buf[:w], u)
} else {
w--
buf[w] = 'S'
w, u = fmtFrac(buf[:w], u, 9)
// u is now integer seconds
w = fmtInt(buf[:w], u%60)
u /= 60
// u is now integer minutes
if u > 0 {
w--
buf[w] = 'M'
w = fmtInt(buf[:w], u%60)
u /= 60
// u is now integer hours
// Stop at hours because days can be different lengths.
if u > 0 {
w--
buf[w] = 'H'
w = fmtInt(buf[:w], u)
}
}
}
if neg {
w--
buf[w] = '-'
}
return "PT" + string(buf[w:])
}
// fmtFrac formats the fraction of v/10**prec (e.g., ".12345") into the
// tail of buf, omitting trailing zeros. it omits the decimal
// point too when the fraction is 0. It returns the index where the
// output bytes begin and the value v/10**prec.
func fmtFrac(buf []byte, v uint64, prec int) (nw int, nv uint64) {
// Omit trailing zeros up to and including decimal point.
w := len(buf)
print := false
for i := 0; i < prec; i++ {
digit := v % 10
print = print || digit != 0
if print {
w--
buf[w] = byte(digit) + '0'
}
v /= 10
}
if print {
w--
buf[w] = '.'
}
return w, v
}
// fmtInt formats v into the tail of buf.
// It returns the index where the output begins.
func fmtInt(buf []byte, v uint64) int {
w := len(buf)
if v == 0 {
w--
buf[w] = '0'
} else {
for v > 0 {
w--
buf[w] = byte(v%10) + '0'
v /= 10
}
}
return w
}
func ParseDuration(str string) (time.Duration, error) {
if len(str) < 3 {
return 0, errors.New("At least one number and designator are required")
}
if strings.Contains(str, "-") {
return 0, errors.New("Duration cannot be negative")
}
// Check that only the parts we expect exist and that everything's in the correct order
if !xmlDurationRegex.Match([]byte(str)) {
return 0, errors.New("Duration must be in the format: P[nD][T[nH][nM][nS]]")
}
var parts = xmlDurationRegex.FindStringSubmatch(str)
var total time.Duration
if parts[1] != "" {
days, err := strconv.Atoi(strings.TrimRight(parts[1], "D"))
if err != nil {
return 0, fmt.Errorf("Error parsing Days: %s", err)
}
total += time.Duration(days) * time.Hour * 24
}
if parts[2] != "" {
hours, err := strconv.Atoi(strings.TrimRight(parts[2], "H"))
if err != nil {
return 0, fmt.Errorf("Error parsing Hours: %s", err)
}
total += time.Duration(hours) * time.Hour
}
if parts[3] != "" {
mins, err := strconv.Atoi(strings.TrimRight(parts[3], "M"))
if err != nil {
return 0, fmt.Errorf("Error parsing Minutes: %s", err)
}
total += time.Duration(mins) * time.Minute
}
if parts[4] != "" {
secs, err := strconv.ParseFloat(strings.TrimRight(parts[4], "S"), 64)
if err != nil {
return 0, fmt.Errorf("Error parsing Seconds: %s", err)
}
total += time.Duration(secs * float64(time.Second))
}
return total, nil
}