Skip to content

Commit

Permalink
Properly format itunes duration.
Browse files Browse the repository at this point in the history
  • Loading branch information
Juraj Bubniak committed Jul 6, 2020
1 parent 8cabe54 commit e0c5aed
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 4 deletions.
48 changes: 47 additions & 1 deletion feed.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"bytes"
"encoding/xml"
"io"
"strconv"
"strings"
"time"
)

Expand Down Expand Up @@ -34,6 +36,50 @@ func (p PubDate) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
return e.EncodeToken(xml.EndElement{Name: start.Name})
}

// NewDuration returns a new Duration.
func NewDuration(d time.Duration) *Duration {
return &Duration{d}
}

// Duration represents itunes:duration attribute of given podcast item.
type Duration struct {
time.Duration
}

// MarshalXML marshalls duration using HH:MM:SS, H:MM:SS, MM:SS, M:SS formats.
func (d Duration) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
if err := e.EncodeToken(start); err != nil {
return err
}
if err := e.EncodeToken(xml.CharData(formatDuration(d.Duration))); err != nil {
return err
}
return e.EncodeToken(xml.EndElement{Name: start.Name})
}

// formatDuration formats duration in these formats: HH:MM:SS, H:MM:SS, MM:SS, M:SS.
func formatDuration(d time.Duration) string {
total := int(d.Seconds())
hours := total / 3600
total %= 3600
minutes := total / 60
total %= 60

var b strings.Builder
if hours > 0 {
b.WriteString(strconv.Itoa(hours) + ":")
}
if hours > 0 && minutes < 10 {
b.WriteString("0")
}
b.WriteString(strconv.Itoa(minutes) + ":")
if total < 10 {
b.WriteString("0")
}
b.WriteString(strconv.Itoa(total))
return b.String()
}

// ItunesOwner represents the itunes:owner of given channel.
type ItunesOwner struct {
XMLName xml.Name `xml:"itunes:owner"`
Expand Down Expand Up @@ -76,7 +122,7 @@ type Item struct {
PubDate *PubDate `xml:"pubDate"`
Author string `xml:"itunes:author,omitempty"`
Block string `xml:"itunes:block,omitempty"`
Duration time.Duration `xml:"itunes:duration,omitempty"`
Duration *Duration `xml:"itunes:duration,omitempty"`
Explicit string `xml:"itunes:explicit,omitempty"`
ClosedCaptioned string `xml:"itunes:isClosedCaptioned,omitempty"`
Order int `xml:"itunes:order,omitempty"`
Expand Down
45 changes: 45 additions & 0 deletions feed_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,48 @@ func TestPubDateMarshalling(t *testing.T) {
t.Errorf("expected %v got %v", want, got)
}
}

func TestDurationMarshalling(t *testing.T) {
cases := []struct {
dur time.Duration
want string
}{
{
dur: 0,
want: "<Duration>0:00</Duration>",
},
{
dur: time.Second * 6,
want: "<Duration>0:06</Duration>",
},
{
dur: time.Second * 64,
want: "<Duration>1:04</Duration>",
},
{
dur: time.Second * 125,
want: "<Duration>2:05</Duration>",
},
{
dur: time.Second * 3600,
want: "<Duration>1:00:00</Duration>",
},
{
dur: time.Second * 37000,
want: "<Duration>10:16:40</Duration>",
},
}

for _, cs := range cases {
t.Run(cs.want, func(t *testing.T) {
dur := NewDuration(cs.dur)
out, err := xml.Marshal(dur)
if err != nil {
t.Errorf("unexpected error %v", err)
}
if got := string(out); cs.want != got {
t.Errorf("expected %v got %v", cs.want, got)
}
})
}
}
16 changes: 13 additions & 3 deletions podcast_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ var (
enclosureURL string
enclosureLength string
enclosureType string
duration time.Duration
durationStr string
}{
{
title: "Item 1",
Expand All @@ -35,6 +37,8 @@ var (
enclosureURL: "http://www.example-podcast.com/my-podcast/2/episode-two",
enclosureLength: "56445",
enclosureType: "WAV",
duration: time.Second * 94,
durationStr: "1:34",
},
}
)
Expand Down Expand Up @@ -294,6 +298,11 @@ func TestContainsItemElements(t *testing.T) {
if want := fmt.Sprintf(`<enclosure url="%v" length="%v" type="%v"></enclosure>`, item.enclosureURL, item.enclosureLength, item.enclosureType); !strings.Contains(data, want) {
t.Errorf("expected %v to contain %v", data, want)
}
if item.durationStr != "" {
if want := fmt.Sprintf("<itunes:duration>%v</itunes:duration>", item.durationStr); !strings.Contains(data, want) {
t.Errorf("expected %v to contain %v", data, want)
}
}
if want := "</item>"; !strings.Contains(data, want) {
t.Errorf("expected %v to contain %v", data, want)
}
Expand Down Expand Up @@ -326,9 +335,10 @@ func setupPodcast() *Podcast {
podcast := &Podcast{}
for _, item := range validItems {
podcast.AddItem(&Item{
Title: item.title,
GUID: item.guid,
PubDate: NewPubDate(item.pubDate),
Title: item.title,
GUID: item.guid,
PubDate: NewPubDate(item.pubDate),
Duration: NewDuration(item.duration),
Enclosure: &Enclosure{
URL: item.enclosureURL,
Length: item.enclosureLength,
Expand Down

0 comments on commit e0c5aed

Please sign in to comment.