github.com/puellanivis/breton@v0.2.16/lib/net/dash/mpd/duration.go (about) 1 package mpd 2 3 import ( 4 "encoding/xml" 5 "strconv" 6 "time" 7 8 "github.com/pkg/errors" 9 ) 10 11 // Duration naively implements the xsd:duration format defined by https://www.w3.org/TR/2004/REC-xmlschema-2-20041028/datatypes.html#duration 12 // N.B.: It is not intended to serve as a general xsd:duration as it does not implement the month side of xsd:duration. 13 type Duration struct { 14 ns time.Duration 15 m int 16 } 17 18 // A copy of various time.Duration constants. 19 const ( 20 Day = 24 * time.Hour 21 Hour = time.Hour 22 Minute = time.Minute 23 Second = time.Second 24 Millisecond = time.Millisecond 25 Microsecond = time.Microsecond 26 Nanosecond = time.Nanosecond 27 ) 28 29 // Duration returns the mpd.Duration value as a time.Duration. 30 // 31 // If Duration contains a non-zero value for the month side of the xsd:duration, 32 // then this function will return an error noting that the value is out of range.. 33 func (d Duration) Duration() (time.Duration, error) { 34 if d.m != 0 { 35 return 0, errors.New("value out of range") 36 } 37 38 return d.ns, nil 39 } 40 41 // Add returns the mpd.Duration plus the given time.Duration. 42 func (d Duration) Add(dur time.Duration) Duration { 43 d.ns += dur 44 45 return d 46 } 47 48 // Scale returns the mpd.Duration scaled by the given time.Duration. 49 func (d Duration) Scale(dur time.Duration) Duration { 50 d.ns *= dur 51 52 return d 53 } 54 55 // AddToTime returns the given time.Time plus the given mpd.Duration value.. 56 func (d Duration) AddToTime(t time.Time) time.Time { 57 if d.m != 0 { 58 t = t.AddDate(0, d.m, 0) 59 } 60 61 return t.Add(d.ns) 62 } 63 64 // MarshalXMLAttr implements xml.MarshalerAttr. 65 func (d Duration) MarshalXMLAttr(name xml.Name) (xml.Attr, error) { 66 if s := d.XMLString(); s != "" { 67 return xml.Attr{ 68 Name: name, 69 Value: s, 70 }, nil 71 } 72 73 return xml.Attr{}, nil 74 } 75 76 // NewDuration returns an mpd.Duration corresponding to the length in months and time.Duration. 77 // 78 // It returns an error if the sign of two arguments are different. 79 func NewDuration(months int, d time.Duration) (Duration, error) { 80 if (d < 0 && months > 0) || (d > 0 && months < 0) { 81 return Duration{}, errors.New("invalid duration") 82 } 83 84 return Duration{ 85 ns: d, 86 m: months, 87 }, nil 88 } 89 90 // XMLString returns the XML representation of the Duration as a string. 91 func (d Duration) XMLString() string { 92 var b []byte 93 dur := d.ns 94 m := int64(d.m) 95 96 if dur < 0 { 97 dur = -dur 98 m = -m 99 b = append(b, '-') 100 } 101 102 b = append(b, 'P') 103 104 seconds, frac := int64(dur/Second), int64(dur%Second) 105 106 minutes, seconds := seconds/60, seconds%60 107 hours, minutes := minutes/60, minutes%60 108 days, hours := hours/24, hours%24 109 110 years, months := m/12, m%12 111 112 if years > 0 { 113 b = strconv.AppendInt(b, years, 10) 114 b = append(b, 'Y') 115 } 116 117 if months > 0 { 118 b = strconv.AppendInt(b, months, 10) 119 b = append(b, 'M') 120 } 121 122 if days > 0 { 123 b = strconv.AppendInt(b, days, 10) 124 b = append(b, 'D') 125 } 126 127 if hours > 0 || minutes > 0 || seconds > 0 || frac > 0 { 128 b = append(b, 'T') 129 130 if hours > 0 { 131 b = strconv.AppendInt(b, hours, 10) 132 b = append(b, 'H') 133 } 134 if minutes > 0 { 135 b = strconv.AppendInt(b, minutes, 10) 136 b = append(b, 'M') 137 } 138 139 if seconds+frac != 0 || hours+minutes == 0 { 140 b = strconv.AppendInt(b, seconds, 10) 141 if frac > 0 { 142 b = append(b, '.') 143 144 f := strconv.AppendInt(nil, frac, 10) 145 146 if len(f) < 9 { 147 nb := make([]byte, 9-len(f)) 148 nb[0] = '0' 149 150 bp := 1 151 for bp < len(nb) { 152 copy(nb[bp:], nb[:bp]) 153 bp *= 2 154 } 155 156 b = append(b, nb...) 157 } 158 159 l := len(f) 160 for f[l-1] == '0' { 161 l-- 162 } 163 164 b = append(b, f[:l]...) 165 } 166 b = append(b, 'S') 167 } 168 } 169 170 return string(b) 171 } 172 173 var durationScales = []time.Duration{ 174 Second, 175 176 100 * Millisecond, 177 10 * Millisecond, 178 Millisecond, 179 180 100 * Microsecond, 181 10 * Microsecond, 182 Microsecond, 183 184 100 * Nanosecond, 185 10 * Nanosecond, 186 Nanosecond, 187 } 188 189 // UnmarshalXMLAttr implements xml.UnmarshalerAttr. 190 func (d *Duration) UnmarshalXMLAttr(attr xml.Attr) error { 191 var dur time.Duration 192 var months int 193 194 v := attr.Value 195 196 if len(v) < 1 { 197 return errors.New("invalid duration") 198 } 199 200 var neg bool 201 if v[0] == '-' { 202 neg = true 203 v = v[1:] 204 } 205 206 if len(v) < 1 || v[0] != 'P' { 207 return errors.New("invalid duration") 208 } 209 210 var n time.Duration 211 212 var radix bool 213 var scale uint 214 215 var t, end bool 216 for _, r := range v[1:] { 217 if end { 218 // we had extra chars after a fractional value, this is an error 219 return errors.New("invalid duration") 220 } 221 222 switch r { 223 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': 224 n = (n * 10) + time.Duration(r-'0') 225 if radix { 226 scale++ 227 } 228 229 case 'T': 230 if t { 231 return errors.New("invalid duration") 232 } 233 t = true 234 235 case '.': 236 if radix { 237 return errors.New("invalid duration") 238 } 239 radix = true 240 241 case 'Y': 242 if t || radix { 243 return errors.New("invalid duration") 244 } 245 246 months += int(n) * 12 247 n = 0 248 249 case 'D': 250 if t || radix { 251 return errors.New("invalid duration") 252 } 253 254 dur += n * Day 255 n = 0 256 257 case 'H': 258 if !t || radix { 259 return errors.New("invalid duration") 260 } 261 262 dur += n * Hour 263 n = 0 264 265 case 'M': 266 switch { 267 case radix: 268 return errors.New("invalid duration") 269 270 case t: 271 dur += n * Minute 272 273 default: 274 months += int(n) 275 } 276 n = 0 277 278 case 'S': 279 if !t || scale > 9 { 280 return errors.New("invalid duration") 281 } 282 283 dur += n * durationScales[scale] 284 end = true 285 286 default: 287 return errors.New("invalid duration") 288 } 289 } 290 291 if neg { 292 dur = -dur 293 } 294 295 d.ns = dur 296 d.m = months 297 298 return nil 299 }