@@ -4,15 +4,41 @@ package mpd
4
4
5
5
import (
6
6
"encoding/xml"
7
+ "errors"
8
+ "fmt"
9
+ "regexp"
10
+ "strconv"
11
+ "strings"
7
12
"time"
8
13
)
9
14
10
15
type Duration time.Duration
11
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
+
12
29
func (d Duration ) MarshalXMLAttr (name xml.Name ) (xml.Attr , error ) {
13
30
return xml.Attr {name , d .String ()}, nil
14
31
}
15
32
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
+
16
42
// String renders a Duration in XML Duration Data Type format
17
43
func (d * Duration ) String () string {
18
44
// Largest time is 2540400h10m10.000000000s
@@ -126,3 +152,55 @@ func fmtInt(buf []byte, v uint64) int {
126
152
}
127
153
return w
128
154
}
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