@@ -4,15 +4,41 @@ package mpd
44
55import (
66 "encoding/xml"
7+ "errors"
8+ "fmt"
9+ "regexp"
10+ "strconv"
11+ "strings"
712 "time"
813)
914
1015type Duration time.Duration
1116
17+ var (
18+ rStart = "^P" // Must start with a 'P'
19+ rDays = "(\\ d+D)?" // We only allow Days for durations, not Months or Years
20+ rTime = "(?:T" // If there's any 'time' units then they must be preceded by a 'T'
21+ rHours = "(\\ d+H)?" // Hours
22+ rMinutes = "(\\ d+M)?" // Minutes
23+ rSeconds = "([\\ d.]+S)?" // Seconds (Potentially decimal)
24+ rEnd = ")?$" // end of regex must close "T" capture group
25+ )
26+
27+ var xmlDurationRegex = regexp .MustCompile (rStart + rDays + rTime + rHours + rMinutes + rSeconds + rEnd )
28+
1229func (d Duration ) MarshalXMLAttr (name xml.Name ) (xml.Attr , error ) {
1330 return xml.Attr {name , d .String ()}, nil
1431}
1532
33+ func (d * Duration ) UnmarshalXMLAttr (attr xml.Attr ) error {
34+ dur , err := parseDuration (attr .Value )
35+ if err != nil {
36+ return err
37+ }
38+ * d = Duration (dur )
39+ return nil
40+ }
41+
1642// String renders a Duration in XML Duration Data Type format
1743func (d * Duration ) String () string {
1844 // Largest time is 2540400h10m10.000000000s
@@ -126,3 +152,55 @@ func fmtInt(buf []byte, v uint64) int {
126152 }
127153 return w
128154}
155+
156+ func parseDuration (str string ) (time.Duration , error ) {
157+ if len (str ) < 3 {
158+ return 0 , errors .New ("At least one number and designator are required" )
159+ }
160+
161+ if strings .Contains (str , "-" ) {
162+ return 0 , errors .New ("Duration cannot be negative" )
163+ }
164+
165+ // Check that only the parts we expect exist and that everything's in the correct order
166+ if ! xmlDurationRegex .Match ([]byte (str )) {
167+ return 0 , errors .New ("Duration must be in the format: P[nD][T[nH][nM][nS]]" )
168+ }
169+
170+ var parts = xmlDurationRegex .FindStringSubmatch (str )
171+ var total time.Duration
172+
173+ if parts [1 ] != "" {
174+ days , err := strconv .Atoi (strings .TrimRight (parts [1 ], "D" ))
175+ if err != nil {
176+ return 0 , fmt .Errorf ("Error parsing Days: %s" , err )
177+ }
178+ total += time .Duration (days ) * time .Hour * 24
179+ }
180+
181+ if parts [2 ] != "" {
182+ hours , err := strconv .Atoi (strings .TrimRight (parts [2 ], "H" ))
183+ if err != nil {
184+ return 0 , fmt .Errorf ("Error parsing Hours: %s" , err )
185+ }
186+ total += time .Duration (hours ) * time .Hour
187+ }
188+
189+ if parts [3 ] != "" {
190+ mins , err := strconv .Atoi (strings .TrimRight (parts [3 ], "M" ))
191+ if err != nil {
192+ return 0 , fmt .Errorf ("Error parsing Minutes: %s" , err )
193+ }
194+ total += time .Duration (mins ) * time .Minute
195+ }
196+
197+ if parts [4 ] != "" {
198+ secs , err := strconv .ParseFloat (strings .TrimRight (parts [4 ], "S" ), 64 )
199+ if err != nil {
200+ return 0 , fmt .Errorf ("Error parsing Seconds: %s" , err )
201+ }
202+ total += time .Duration (secs * float64 (time .Second ))
203+ }
204+
205+ return total , nil
206+ }
0 commit comments