@@ -5,13 +5,27 @@ package mpd
5
5
import (
6
6
"encoding/xml"
7
7
"errors"
8
+ "fmt"
9
+ "regexp"
8
10
"strconv"
9
11
"strings"
10
12
"time"
11
13
)
12
14
13
15
type Duration time.Duration
14
16
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
+
15
29
func (d Duration ) MarshalXMLAttr (name xml.Name ) (xml.Attr , error ) {
16
30
return xml.Attr {name , d .String ()}, nil
17
31
}
@@ -141,77 +155,52 @@ func fmtInt(buf []byte, v uint64) int {
141
155
142
156
func parseDuration (str string ) (time.Duration , error ) {
143
157
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 " )
145
159
}
146
160
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" )
152
163
}
153
164
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]]" )
156
168
}
157
- offset ++
158
169
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
166
172
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 )
171
177
}
178
+ total += time .Duration (days ) * time .Hour * 24
179
+ }
172
180
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 )
175
185
}
186
+ total += time .Duration (hours ) * time .Hour
187
+ }
176
188
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 )
183
193
}
194
+ total += time .Duration (mins ) * time .Minute
184
195
}
185
196
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 )
210
201
}
202
+ total += time .Duration (secs * float64 (time .Second ))
211
203
}
212
204
213
- if minus {
214
- sum = - sum
215
- }
216
- return time .Duration (sum * float64 (time .Second )), nil
205
+ return total , nil
217
206
}
0 commit comments