matterbridge/vendor/github.com/rickb777/date/period/parse.go

153 lines
3.7 KiB
Go

// Copyright 2015 Rick Beton. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package period
import (
"fmt"
"strconv"
"strings"
)
// MustParse is as per Parse except that it panics if the string cannot be parsed.
// This is intended for setup code; don't use it for user inputs.
func MustParse(value string) Period {
d, err := Parse(value)
if err != nil {
panic(err)
}
return d
}
// Parse parses strings that specify periods using ISO-8601 rules.
//
// In addition, a plus or minus sign can precede the period, e.g. "-P10D"
//
// The value is normalised, e.g. multiple of 12 months become years so "P24M"
// is the same as "P2Y". However, this is done without loss of precision, so
// for example whole numbers of days do not contribute to the months tally
// because the number of days per month is variable.
//
// The zero value can be represented in several ways: all of the following
// are equivalent: "P0Y", "P0M", "P0W", "P0D", "PT0H", PT0M", PT0S", and "P0".
// The canonical zero is "P0D".
func Parse(period string) (Period, error) {
if period == "" {
return Period{}, fmt.Errorf("cannot parse a blank string as a period")
}
if period == "P0" {
return Period{}, nil
}
result := period64{}
pcopy := period
if pcopy[0] == '-' {
result.neg = true
pcopy = pcopy[1:]
} else if pcopy[0] == '+' {
pcopy = pcopy[1:]
}
if pcopy[0] != 'P' {
return Period{}, fmt.Errorf("expected 'P' period mark at the start: %s", period)
}
pcopy = pcopy[1:]
st := parseState{period, pcopy, false, nil}
t := strings.IndexByte(pcopy, 'T')
if t >= 0 {
st.pcopy = pcopy[t+1:]
result.hours, st = parseField(st, 'H')
if st.err != nil {
return Period{}, fmt.Errorf("expected a number before the 'H' marker: %s", period)
}
result.minutes, st = parseField(st, 'M')
if st.err != nil {
return Period{}, fmt.Errorf("expected a number before the 'M' marker: %s", period)
}
result.seconds, st = parseField(st, 'S')
if st.err != nil {
return Period{}, fmt.Errorf("expected a number before the 'S' marker: %s", period)
}
st.pcopy = pcopy[:t]
}
result.years, st = parseField(st, 'Y')
if st.err != nil {
return Period{}, fmt.Errorf("expected a number before the 'Y' marker: %s", period)
}
result.months, st = parseField(st, 'M')
if st.err != nil {
return Period{}, fmt.Errorf("expected a number before the 'M' marker: %s", period)
}
weeks, st := parseField(st, 'W')
if st.err != nil {
return Period{}, fmt.Errorf("expected a number before the 'W' marker: %s", period)
}
days, st := parseField(st, 'D')
if st.err != nil {
return Period{}, fmt.Errorf("expected a number before the 'D' marker: %s", period)
}
result.days = weeks*7 + days
//fmt.Printf("%#v\n", st)
if !st.ok {
return Period{}, fmt.Errorf("expected 'Y', 'M', 'W', 'D', 'H', 'M', or 'S' marker: %s", period)
}
return result.normalise64(true).toPeriod(), nil
}
type parseState struct {
period, pcopy string
ok bool
err error
}
func parseField(st parseState, mark byte) (int64, parseState) {
//fmt.Printf("%c %#v\n", mark, st)
r := int64(0)
m := strings.IndexByte(st.pcopy, mark)
if m > 0 {
r, st.err = parseDecimalFixedPoint(st.pcopy[:m], st.period)
if st.err != nil {
return 0, st
}
st.pcopy = st.pcopy[m+1:]
st.ok = true
}
return r, st
}
// Fixed-point three decimal places
func parseDecimalFixedPoint(s, original string) (int64, error) {
//was := s
dec := strings.IndexByte(s, '.')
if dec < 0 {
dec = strings.IndexByte(s, ',')
}
if dec >= 0 {
dp := len(s) - dec
if dp > 1 {
s = s[:dec] + s[dec+1:dec+2]
} else {
s = s[:dec] + s[dec+1:] + "0"
}
} else {
s = s + "0"
}
return strconv.ParseInt(s, 10, 64)
}