github.com/puellanivis/breton@v0.2.16/lib/net/dash/mpd/duration.go (about)

     1  package mpd
     2  
     3  import (
     4  	"encoding/xml"
     5  	"strconv"
     6  	"time"
     7  
     8  	"github.com/pkg/errors"
     9  )
    10  
    11  // Duration naively implements the xsd:duration format defined by https://www.w3.org/TR/2004/REC-xmlschema-2-20041028/datatypes.html#duration
    12  // N.B.: It is not intended to serve as a general xsd:duration as it does not implement the month side of xsd:duration.
    13  type Duration struct {
    14  	ns time.Duration
    15  	m  int
    16  }
    17  
    18  // A copy of various time.Duration constants.
    19  const (
    20  	Day         = 24 * time.Hour
    21  	Hour        = time.Hour
    22  	Minute      = time.Minute
    23  	Second      = time.Second
    24  	Millisecond = time.Millisecond
    25  	Microsecond = time.Microsecond
    26  	Nanosecond  = time.Nanosecond
    27  )
    28  
    29  // Duration returns the mpd.Duration value as a time.Duration.
    30  //
    31  // If Duration contains a non-zero value for the month side of the xsd:duration,
    32  // then this function will return an error noting that the value is out of range..
    33  func (d Duration) Duration() (time.Duration, error) {
    34  	if d.m != 0 {
    35  		return 0, errors.New("value out of range")
    36  	}
    37  
    38  	return d.ns, nil
    39  }
    40  
    41  // Add returns the mpd.Duration plus the given time.Duration.
    42  func (d Duration) Add(dur time.Duration) Duration {
    43  	d.ns += dur
    44  
    45  	return d
    46  }
    47  
    48  // Scale returns the mpd.Duration scaled by the given time.Duration.
    49  func (d Duration) Scale(dur time.Duration) Duration {
    50  	d.ns *= dur
    51  
    52  	return d
    53  }
    54  
    55  // AddToTime returns the given time.Time plus the given mpd.Duration value..
    56  func (d Duration) AddToTime(t time.Time) time.Time {
    57  	if d.m != 0 {
    58  		t = t.AddDate(0, d.m, 0)
    59  	}
    60  
    61  	return t.Add(d.ns)
    62  }
    63  
    64  // MarshalXMLAttr implements xml.MarshalerAttr.
    65  func (d Duration) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
    66  	if s := d.XMLString(); s != "" {
    67  		return xml.Attr{
    68  			Name:  name,
    69  			Value: s,
    70  		}, nil
    71  	}
    72  
    73  	return xml.Attr{}, nil
    74  }
    75  
    76  // NewDuration returns an mpd.Duration corresponding to the length in months and time.Duration.
    77  //
    78  // It returns an error if the sign of two arguments are different.
    79  func NewDuration(months int, d time.Duration) (Duration, error) {
    80  	if (d < 0 && months > 0) || (d > 0 && months < 0) {
    81  		return Duration{}, errors.New("invalid duration")
    82  	}
    83  
    84  	return Duration{
    85  		ns: d,
    86  		m:  months,
    87  	}, nil
    88  }
    89  
    90  // XMLString returns the XML representation of the Duration as a string.
    91  func (d Duration) XMLString() string {
    92  	var b []byte
    93  	dur := d.ns
    94  	m := int64(d.m)
    95  
    96  	if dur < 0 {
    97  		dur = -dur
    98  		m = -m
    99  		b = append(b, '-')
   100  	}
   101  
   102  	b = append(b, 'P')
   103  
   104  	seconds, frac := int64(dur/Second), int64(dur%Second)
   105  
   106  	minutes, seconds := seconds/60, seconds%60
   107  	hours, minutes := minutes/60, minutes%60
   108  	days, hours := hours/24, hours%24
   109  
   110  	years, months := m/12, m%12
   111  
   112  	if years > 0 {
   113  		b = strconv.AppendInt(b, years, 10)
   114  		b = append(b, 'Y')
   115  	}
   116  
   117  	if months > 0 {
   118  		b = strconv.AppendInt(b, months, 10)
   119  		b = append(b, 'M')
   120  	}
   121  
   122  	if days > 0 {
   123  		b = strconv.AppendInt(b, days, 10)
   124  		b = append(b, 'D')
   125  	}
   126  
   127  	if hours > 0 || minutes > 0 || seconds > 0 || frac > 0 {
   128  		b = append(b, 'T')
   129  
   130  		if hours > 0 {
   131  			b = strconv.AppendInt(b, hours, 10)
   132  			b = append(b, 'H')
   133  		}
   134  		if minutes > 0 {
   135  			b = strconv.AppendInt(b, minutes, 10)
   136  			b = append(b, 'M')
   137  		}
   138  
   139  		if seconds+frac != 0 || hours+minutes == 0 {
   140  			b = strconv.AppendInt(b, seconds, 10)
   141  			if frac > 0 {
   142  				b = append(b, '.')
   143  
   144  				f := strconv.AppendInt(nil, frac, 10)
   145  
   146  				if len(f) < 9 {
   147  					nb := make([]byte, 9-len(f))
   148  					nb[0] = '0'
   149  
   150  					bp := 1
   151  					for bp < len(nb) {
   152  						copy(nb[bp:], nb[:bp])
   153  						bp *= 2
   154  					}
   155  
   156  					b = append(b, nb...)
   157  				}
   158  
   159  				l := len(f)
   160  				for f[l-1] == '0' {
   161  					l--
   162  				}
   163  
   164  				b = append(b, f[:l]...)
   165  			}
   166  			b = append(b, 'S')
   167  		}
   168  	}
   169  
   170  	return string(b)
   171  }
   172  
   173  var durationScales = []time.Duration{
   174  	Second,
   175  
   176  	100 * Millisecond,
   177  	10 * Millisecond,
   178  	Millisecond,
   179  
   180  	100 * Microsecond,
   181  	10 * Microsecond,
   182  	Microsecond,
   183  
   184  	100 * Nanosecond,
   185  	10 * Nanosecond,
   186  	Nanosecond,
   187  }
   188  
   189  // UnmarshalXMLAttr implements xml.UnmarshalerAttr.
   190  func (d *Duration) UnmarshalXMLAttr(attr xml.Attr) error {
   191  	var dur time.Duration
   192  	var months int
   193  
   194  	v := attr.Value
   195  
   196  	if len(v) < 1 {
   197  		return errors.New("invalid duration")
   198  	}
   199  
   200  	var neg bool
   201  	if v[0] == '-' {
   202  		neg = true
   203  		v = v[1:]
   204  	}
   205  
   206  	if len(v) < 1 || v[0] != 'P' {
   207  		return errors.New("invalid duration")
   208  	}
   209  
   210  	var n time.Duration
   211  
   212  	var radix bool
   213  	var scale uint
   214  
   215  	var t, end bool
   216  	for _, r := range v[1:] {
   217  		if end {
   218  			// we had extra chars after a fractional value, this is an error
   219  			return errors.New("invalid duration")
   220  		}
   221  
   222  		switch r {
   223  		case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
   224  			n = (n * 10) + time.Duration(r-'0')
   225  			if radix {
   226  				scale++
   227  			}
   228  
   229  		case 'T':
   230  			if t {
   231  				return errors.New("invalid duration")
   232  			}
   233  			t = true
   234  
   235  		case '.':
   236  			if radix {
   237  				return errors.New("invalid duration")
   238  			}
   239  			radix = true
   240  
   241  		case 'Y':
   242  			if t || radix {
   243  				return errors.New("invalid duration")
   244  			}
   245  
   246  			months += int(n) * 12
   247  			n = 0
   248  
   249  		case 'D':
   250  			if t || radix {
   251  				return errors.New("invalid duration")
   252  			}
   253  
   254  			dur += n * Day
   255  			n = 0
   256  
   257  		case 'H':
   258  			if !t || radix {
   259  				return errors.New("invalid duration")
   260  			}
   261  
   262  			dur += n * Hour
   263  			n = 0
   264  
   265  		case 'M':
   266  			switch {
   267  			case radix:
   268  				return errors.New("invalid duration")
   269  
   270  			case t:
   271  				dur += n * Minute
   272  
   273  			default:
   274  				months += int(n)
   275  			}
   276  			n = 0
   277  
   278  		case 'S':
   279  			if !t || scale > 9 {
   280  				return errors.New("invalid duration")
   281  			}
   282  
   283  			dur += n * durationScales[scale]
   284  			end = true
   285  
   286  		default:
   287  			return errors.New("invalid duration")
   288  		}
   289  	}
   290  
   291  	if neg {
   292  		dur = -dur
   293  	}
   294  
   295  	d.ns = dur
   296  	d.m = months
   297  
   298  	return nil
   299  }