func (s *SkipHours) Validate() error {
	for _, hour := range s.Hours {
		if hour < 0 || hour > 23 {
			return errors.New(ErrValidation, errorMessages, "skip hour %d is out of range from 0 to 23", hour)
		}
	}
	return nil
}
Validate checks if the skip hours are valid.
package rss
import (
	"encoding/xml"
	"io"
	"net/http"
	"net/url"
	"time"
	"github.com/tideland/golib/errors"
	"github.com/tideland/golib/feed/utils"
)
const (
	Version = "2.0"
)
const (
	rssDate   = "Mon, 02 Jan 2006 15:04 MST"
	rssDateV1 = "Mon, 02 Jan 2006 15:04:05 MST"
	rssDateV2 = "02 Jan 2006 15:04 MST"
	rssDateV3 = "Mon, 02 Jan 2006 15:04 -0700"
	rssDateV4 = "02 Jan 2006 15:04 -0700"
)
type RSS struct {
	XMLName string  `xml:"rss"`
	Version string  `xml:"version,attr"`
	Channel Channel `xml:"channel"`
}
func (r *RSS) Validate() error {
	if r.Version != Version {
		return errors.New(ErrValidation, errorMessages, "invalid RSS document version: %q", r.Version)
	}
	return r.Channel.Validate()
}
type Channel struct {
	Title          string      `xml:"title"`
	Description    string      `xml:"description"`
	Link           string      `xml:"link"`
	Categories     []*Category `xml:"category,omitempty"`
	Cloud          *Cloud      `xml:"cloud,omitempty"`
	Copyright      string      `xml:"copyright,omitempty"`
	Docs           string      `xml:"docs,omitempty"`
	Generator      string      `xml:"generator,omitempty"`
	Image          *Image      `xml:"image,omitempty"`
	Language       string      `xml:"language,omitempty"`
	LastBuildDate  string      `xml:"lastBuildDate,omitempty"`
	ManagingEditor string      `xml:"managingEditor,omitempty"`
	PubDate        string      `xml:"pubDate,omitempty"`
	Rating         string      `xml:"rating,omitempty"`
	SkipDays       *SkipDays   `xml:"skipDays,omitempty"`
	SkipHours      *SkipHours  `xml:"skipHours,omitempty"`
	TextInput      string      `xml:"textInput,omitempty"`
	TTL            int         `xml:"ttl,omitempty"`
	WebMaster      string      `xml:"webMaster,omitempty"`
	Items          []*Item     `xml:"item,omitempty"`
}
func (c Channel) Validate() error {
	if c.Title == "" {
		return errors.New(ErrValidation, errorMessages, "channel title must not be empty")
	}
	if c.Description == "" {
		return errors.New(ErrValidation, errorMessages, "channel description must not be empty")
	}
	if c.Link == "" {
		return errors.New(ErrValidation, errorMessages, "channel link must not be empty")
	}
	if _, err := url.Parse(c.Link); err != nil {
		return errors.Annotate(err, ErrParsing, errorMessages, "channel link")
	}
	for _, category := range c.Categories {
		if err := category.Validate(); err != nil {
			return err
		}
	}
	if c.Cloud != nil {
		if err := c.Cloud.Validate(); err != nil {
			return err
		}
	}
	if c.Docs != "" && c.Docs != "http://blogs.law.harvard.edu/tech/rss" {
		return errors.New(ErrValidation, errorMessages, "docs %q is not valid", c.Docs)
	}
	if c.Image != nil {
		if err := c.Image.Validate(); err != nil {
			return err
		}
	}
	if c.Language != "" {
	}
	if c.LastBuildDate != "" {
		if _, err := ParseTime(c.LastBuildDate); err != nil {
			return errors.Annotate(err, ErrParsing, errorMessages, "channel last build date")
		}
	}
	if c.PubDate != "" {
		if _, err := ParseTime(c.PubDate); err != nil {
			return errors.Annotate(err, ErrParsing, errorMessages, "channel publication date")
		}
	}
	if c.SkipDays != nil {
		if err := c.SkipDays.Validate(); err != nil {
			return err
		}
	}
	if c.SkipHours != nil {
		if err := c.SkipHours.Validate(); err != nil {
			return err
		}
	}
	if c.TTL < 0 {
		return errors.New(ErrValidation, errorMessages, "channel ttl is below zero")
	}
	for _, item := range c.Items {
		if err := item.Validate(); err != nil {
			return err
		}
	}
	return nil
}
type Category struct {
	Category string `xml:",chardata"`
	Domain   string `xml:"domain,attr,omitempty"`
}
func (c *Category) Validate() error {
	if c.Category == "" {
		return errors.New(ErrValidation, errorMessages, "channel category must not be empty")
	}
	return nil
}
type Cloud struct {
	Domain            string `xml:"domain,attr"`
	Port              int    `xml:"port,attr,omitempty"`
	Path              string `xml:"path,attr"`
	RegisterProcedure string `xml:"registerProcedure,attr"`
	Protocol          string `xml:"protocol,attr"`
}
func (c *Cloud) Validate() error {
	if c.Domain == "" {
		return errors.New(ErrValidation, errorMessages, "cloud domain must not be empty")
	}
	if c.Path == "" || c.Path[0] != '/' {
		return errors.New(ErrValidation, errorMessages, "cloud path %q must not be empty and has to start with a slash", c.Path)
	}
	if c.Port < 1 || c.Port > 65535 {
		return errors.New(ErrValidation, errorMessages, "cloud port %d is out of range", c.Port)
	}
	return nil
}
type Image struct {
	Link        string `xml:"link"`
	Title       string `xml:"title"`
	URL         string `xml:"url"`
	Description string `xml:"description,omitempty"`
	Height      int    `xml:"height,omitempty"`
	Width       int    `xml:"width,omitempty"`
}
func (i *Image) Validate() error {
	if _, err := url.Parse(i.Link); err != nil {
		return errors.New(ErrValidation, errorMessages, "image link is not parsable", i.Link)
	}
	if i.Title == "" {
		return errors.New(ErrValidation, errorMessages, "image title must not be empty")
	}
	if _, err := url.Parse(i.URL); err != nil {
		return errors.Annotate(err, ErrParsing, errorMessages, "image title")
	}
	if i.Height < 1 || i.Height > 400 {
		return errors.New(ErrValidation, errorMessages, "image height %d is out of range from 1 to 400", i.Height)
	}
	if i.Width < 1 || i.Width > 144 {
		return errors.New(ErrValidation, errorMessages, "image width %d is out of range from 1 to 144", i.Width)
	}
	return nil
}
type SkipDays struct {
	Days []string `xml:"day"`
}
func (s *SkipDays) Validate() error {
	skipDays := map[string]bool{
		"Monday":    true,
		"Tuesday":   true,
		"Wednesday": true,
		"Thursday":  true,
		"Friday":    true,
		"Saturday":  true,
		"Sunday":    true,
	}
	for _, day := range s.Days {
		if !skipDays[day] {
			return errors.New(ErrValidation, errorMessages, "skip day %q is invalid", day)
		}
	}
	return nil
}
type SkipHours struct {
	Hours []int `xml:"hour"`
}