1 Star 0 Fork 0

wqt / obix

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
reltime.go 8.89 KB
一键复制 编辑 原始数据 按行查看 历史
wuqingtao 提交于 2022-11-16 18:30 . update
package types
import (
"errors"
"strings"
"time"
)
const (
Nanosecond Reltime = 1
Microsecond = 1000 * Nanosecond
Millisecond = 1000 * Microsecond
Second = 1000 * Millisecond
Minute = 60 * Second
Hour = 60 * Minute
Day = 24 * Hour
)
// RelTime obix data type
type RelTime struct {
// XMLName xml.Name `xml:"reltime"`
Val string `xml:"val,attr,omitempty"`
// XMLGregorianCalendar
Min string `xml:"min,attr,omitempty" json:",omitempty"`
Max string `xml:"max,attr,omitempty" json:",omitempty"`
*Object `xml:",omitempty"`
}
type Reltime time.Duration
var replacer = strings.NewReplacer("P", "", "T", "")
// ParseReltime parses a duration string.
// A duration string is a possibly signed sequence of
// decimal numbers, each with optional fraction and a unit suffix,
// such as "P0D", "P2D" or "-P2D".
// Valid time units are "S", "M", "H", "D".
// copy from stdlib
func ParseReltime(s string) (Reltime, error) {
s = replacer.Replace(s)
// [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+
orig := s
var d uint64
neg := false
// Consume [-+]?
if s != "" {
c := s[0]
if c == '-' || c == '+' {
neg = c == '-'
s = s[1:]
}
}
// Special case: if all that is left is "0", this is zero.
if s == "0" {
return 0, nil
}
if s == "" {
return 0, errors.New("time: invalid duration " + quote(orig))
}
for s != "" {
var (
v, f uint64 // integers before, after decimal point
scale float64 = 1 // value = v + f/scale
)
var err error
// The next character must be [0-9.]
if !(s[0] == '.' || '0' <= s[0] && s[0] <= '9') {
return 0, errors.New("time: invalid duration " + quote(orig))
}
// Consume [0-9]*
pl := len(s)
v, s, err = leadingInt(s)
if err != nil {
return 0, errors.New("time: invalid duration " + quote(orig))
}
pre := pl != len(s) // whether we consumed anything before a period
// Consume (\.[0-9]*)?
post := false
if s != "" && s[0] == '.' {
s = s[1:]
pl := len(s)
f, scale, s = leadingFraction(s)
post = pl != len(s)
}
if !pre && !post {
// no digits (e.g. ".s" or "-.s")
return 0, errors.New("time: invalid duration " + quote(orig))
}
// Consume unit.
i := 0
for ; i < len(s); i++ {
c := s[i]
if c == '.' || '0' <= c && c <= '9' {
break
}
}
if i == 0 {
return 0, errors.New("time: missing unit in duration " + quote(orig))
}
u := s[:i]
s = s[i:]
unit, ok := unitMap[u]
if !ok {
return 0, errors.New("time: unknown unit " + quote(u) + " in duration " + quote(orig))
}
if v > 1<<63/unit {
// overflow
return 0, errors.New("time: invalid duration " + quote(orig))
}
v *= unit
if f > 0 {
// float64 is needed to be nanosecond accurate for fractions of hours.
// v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit)
v += uint64(float64(f) * (float64(unit) / scale))
if v > 1<<63 {
// overflow
return 0, errors.New("time: invalid duration " + quote(orig))
}
}
d += v
if d > 1<<63 {
return 0, errors.New("time: invalid duration " + quote(orig))
}
}
if neg {
return -Reltime(d), nil
}
if d > 1<<63-1 {
return 0, errors.New("time: invalid duration " + quote(orig))
}
return Reltime(d), nil
}
// These are borrowed from unicode/utf8 and strconv and replicate behavior in
// that package, since we can't take a dependency on either.
const (
lowerhex = "0123456789abcdef"
runeSelf = 0x80
runeError = '\uFFFD'
)
func quote(s string) string {
buf := make([]byte, 1, len(s)+2) // slice will be at least len(s) + quotes
buf[0] = '"'
for i, c := range s {
if c >= runeSelf || c < ' ' {
// This means you are asking us to parse a time.Duration or
// time.Location with unprintable or non-ASCII characters in it.
// We don't expect to hit this case very often. We could try to
// reproduce strconv.Quote's behavior with full fidelity but
// given how rarely we expect to hit these edge cases, speed and
// conciseness are better.
var width int
if c == runeError {
width = 1
if i+2 < len(s) && s[i:i+3] == string(runeError) {
width = 3
}
} else {
width = len(string(c))
}
for j := 0; j < width; j++ {
buf = append(buf, `\x`...)
buf = append(buf, lowerhex[s[i+j]>>4])
buf = append(buf, lowerhex[s[i+j]&0xF])
}
} else {
if c == '"' || c == '\\' {
buf = append(buf, '\\')
}
buf = append(buf, string(c)...)
}
}
buf = append(buf, '"')
return string(buf)
}
// leadingInt consumes the leading [0-9]* from s.
func leadingInt(s string) (x uint64, rem string, err error) {
i := 0
for ; i < len(s); i++ {
c := s[i]
if c < '0' || c > '9' {
break
}
if x > 1<<63/10 {
// overflow
return 0, "", errLeadingInt
}
x = x*10 + uint64(c) - '0'
if x > 1<<63 {
// overflow
return 0, "", errLeadingInt
}
}
return x, s[i:], nil
}
var errLeadingInt = errors.New("time: bad [0-9]*") // never printed
// leadingFraction consumes the leading [0-9]* from s.
// It is used only for fractions, so does not return an error on overflow,
// it just stops accumulating precision.
func leadingFraction(s string) (x uint64, scale float64, rem string) {
i := 0
scale = 1
overflow := false
for ; i < len(s); i++ {
c := s[i]
if c < '0' || c > '9' {
break
}
if overflow {
continue
}
if x > (1<<63-1)/10 {
// It's possible for overflow to give a positive number, so take care.
overflow = true
continue
}
y := x*10 + uint64(c) - '0'
if y > 1<<63 {
overflow = true
continue
}
x = y
scale *= 10
}
return x, scale, s[i:]
}
var unitMap = map[string]uint64{
"S": uint64(Second),
"M": uint64(Minute),
"H": uint64(Hour),
"D": uint64(Day),
}
func (d Reltime) format() string {
// Largest time is 2540400h10m10.000000000s
var buf [32]byte
w := len(buf)
u := uint64(d)
neg := d < 0
if neg {
u = -u
}
if u < uint64(Second) {
// Special case: if duration is smaller than a second,
// use smaller units, like 1.2ms
// var prec int
w--
// buf[w] = 'S'
// w--
// switch {
// case u == 0:
// return "0s"
// case u < uint64(Microsecond):
// // print nanoseconds
// prec = 0
// buf[w] = 'n'
// case u < uint64(Millisecond):
// // print microseconds
// prec = 3
// // U+00B5 'µ' micro sign == 0xC2 0xB5
// w-- // Need room for two bytes.
// copy(buf[w:], "µ")
// default:
// // print milliseconds
// prec = 6
// buf[w] = 'm'
// }
buf[w] = 'S'
w, u = fmtFrac(buf[:w], u, 9)
w = fmtInt(buf[:w], u)
} else {
w--
buf[w] = 'S'
w, u = fmtFrac(buf[:w], u, 9)
// u is now integer seconds
w = fmtInt(buf[:w], u%60)
u /= 60
// u is now integer minutes
if u > 0 {
w--
buf[w] = 'M'
w = fmtInt(buf[:w], u%60)
u /= 60
// u is now integer hours
// Stop at hours because days can be different lengths.
if u > 0 {
w--
buf[w] = 'H'
w = fmtInt(buf[:w], u%24)
u /= 24
if u > 0 {
w--
buf[w] = 'D'
w = fmtInt(buf[:w], u)
}
}
}
}
if neg {
w--
buf[w] = '-'
}
return string(buf[w:])
}
// String returns a string representing the duration in the form "72h3m0.5s".
// Leading zero units are omitted. As a special case, durations less than one
// second format use a smaller unit (milli-, micro-, or nanoseconds) to ensure
// that the leading digit is non-zero. The zero duration formats as 0s.
func (d Reltime) String() string {
if d == 0 {
return "PT0S"
}
s := d.format()
buf := new(strings.Builder)
var (
pre int
appendT bool
)
if strings.HasPrefix(s, "-") {
buf.WriteByte('-')
s = strings.TrimPrefix(s, "-")
}
buf.WriteByte('P')
for i, v := range s {
switch v {
case 'D', 'H', 'M', 'S':
if s[pre:i] == "0" || ((s[pre] == 'D' || s[pre] == 'H' || s[pre] == 'M' || s[pre] == 'S') && s[pre+1:i] == "0") {
pre = i
continue
}
if pre > 0 {
pre += 1
}
if v == 'D' {
buf.WriteString(s[pre:i])
buf.WriteRune(v)
}
if !appendT {
appendT = true
buf.WriteByte('T')
}
if v != 'D' {
buf.WriteString(s[pre:i])
buf.WriteRune(v)
}
pre = i
}
}
return strings.TrimSuffix(buf.String(), "T")
}
// fmtFrac formats the fraction of v/10**prec (e.g., ".12345") into the
// tail of buf, omitting trailing zeros. It omits the decimal
// point too when the fraction is 0. It returns the index where the
// output bytes begin and the value v/10**prec.
func fmtFrac(buf []byte, v uint64, prec int) (nw int, nv uint64) {
// Omit trailing zeros up to and including decimal point.
w := len(buf)
print := false
for i := 0; i < prec; i++ {
digit := v % 10
print = print || digit != 0
if print {
w--
buf[w] = byte(digit) + '0'
}
v /= 10
}
if print {
w--
buf[w] = '.'
}
return w, v
}
// fmtInt formats v into the tail of buf.
// It returns the index where the output begins.
func fmtInt(buf []byte, v uint64) int {
w := len(buf)
if v == 0 {
w--
buf[w] = '0'
} else {
for v > 0 {
w--
buf[w] = byte(v%10) + '0'
v /= 10
}
}
return w
}
Go
1
https://gitee.com/wqt/obix.git
git@gitee.com:wqt/obix.git
wqt
obix
obix
v1.0.3

搜索帮助