@@ -5,13 +5,27 @@ package mpd
55import (
66 "encoding/xml"
77 "errors"
8+ "fmt"
9+ "regexp"
810 "strconv"
911 "strings"
1012 "time"
1113)
1214
1315type Duration time.Duration
1416
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+
1529func (d Duration ) MarshalXMLAttr (name xml.Name ) (xml.Attr , error ) {
1630 return xml.Attr {name , d .String ()}, nil
1731}
@@ -141,77 +155,52 @@ func fmtInt(buf []byte, v uint64) int {
141155
142156func parseDuration (str string ) (time.Duration , error ) {
143157 if len (str ) < 3 {
144- return 0 , errors .New ("input duration too short " )
158+ return 0 , errors .New ("At least one number and designator are required " )
145159 }
146160
147- var minus bool
148- offset := 0
149- if str [offset ] == '-' {
150- minus = true
151- offset ++
161+ if strings .Contains (str , "-" ) {
162+ return 0 , errors .New ("Duration cannot be negative" )
152163 }
153164
154- if str [offset ] != 'P' {
155- return 0 , errors .New ("input duration does not have a valid prefix" )
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]]" )
156168 }
157- offset ++
158169
159- var dateStr , timeStr string
160- if i := strings .IndexByte (str [offset :], 'T' ); i != - 1 {
161- dateStr = str [offset : offset + i ]
162- timeStr = str [offset + i + 1 :]
163- } else {
164- dateStr = str [offset :]
165- }
170+ var parts = xmlDurationRegex .FindStringSubmatch (str )
171+ var total time.Duration
166172
167- var sum float64
168- if len ( dateStr ) > 0 {
169- if i := strings . IndexByte ( dateStr , 'Y' ); i != - 1 {
170- return 0 , errors . New ( "input duration contains Years notation" )
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 )
171177 }
178+ total += time .Duration (days ) * time .Hour * 24
179+ }
172180
173- if i := strings .IndexByte (dateStr , 'M' ); i != - 1 {
174- return 0 , errors .New ("input duration contains Months notation" )
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 )
175185 }
186+ total += time .Duration (hours ) * time .Hour
187+ }
176188
177- if i := strings .IndexByte (dateStr , 'D' ); i != - 1 {
178- days , err := strconv .Atoi (dateStr [0 :i ])
179- if err != nil {
180- return 0 , err
181- }
182- sum += float64 (days ) * 86400
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 )
183193 }
194+ total += time .Duration (mins ) * time .Minute
184195 }
185196
186- if len (timeStr ) > 0 {
187- var pos int
188- if i := strings .IndexByte (timeStr [pos :], 'H' ); i != - 1 {
189- hours , err := strconv .ParseInt (timeStr [pos :pos + i ], 10 , 64 )
190- if err != nil {
191- return 0 , err
192- }
193- sum += float64 (hours ) * 3600
194- pos += i + 1
195- }
196- if i := strings .IndexByte (timeStr [pos :], 'M' ); i != - 1 {
197- minutes , err := strconv .ParseInt (timeStr [pos :pos + i ], 10 , 64 )
198- if err != nil {
199- return 0 , err
200- }
201- sum += float64 (minutes ) * 60
202- pos += i + 1
203- }
204- if i := strings .IndexByte (timeStr [pos :], 'S' ); i != - 1 {
205- seconds , err := strconv .ParseFloat (timeStr [pos :pos + i ], 64 )
206- if err != nil {
207- return 0 , err
208- }
209- sum += seconds
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 )
210201 }
202+ total += time .Duration (secs * float64 (time .Second ))
211203 }
212204
213- if minus {
214- sum = - sum
215- }
216- return time .Duration (sum * float64 (time .Second )), nil
205+ return total , nil
217206}
0 commit comments