mirror of
https://github.com/nabbar/golib.git
synced 2025-12-24 11:51:02 +08:00
Fix Package Duration & Add new Package for big duration (#203)
- Add: allowing parsing day in Duration package who's parsing value before a d letter - Add: a sub package named big with another custom type Duration but this Duration is based of number of seconds instead of number of nanosecond to golib/Duration or time.Duration package. The result big Duration package allow to manage duration up to 106,751,991,167,300 d 15 h 30 m 7 s - Add: into some BDD test with ginkgo framework - Fix: marshaller function in package Duration - Fix: big Duration - Refactor: optimize some code convertion - Refactor: truncate function to not use float64 casting
This commit is contained in:
53
duration/big/big_suite_test.go
Normal file
53
duration/big/big_suite_test.go
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2023 Nicolas JUHEL
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package big_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
/*
|
||||
Using https://onsi.github.io/ginkgo/
|
||||
Running with $> ginkgo -cover .
|
||||
*/
|
||||
|
||||
// TestGolibEncodingHexHelper tests the Golib Hex Encoding Helper function.
|
||||
func TestGolibDurationBigHelper(t *testing.T) {
|
||||
time.Sleep(500 * time.Millisecond) // Adding delay for better testing synchronization
|
||||
RegisterFailHandler(Fail) // Registering fail handler for better test failure reporting
|
||||
RunSpecs(t, "Duration Big Helper Suite") // Running the test suite for Encoding Hex Helper
|
||||
}
|
||||
|
||||
var _ = BeforeSuite(func() {
|
||||
})
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
})
|
||||
130
duration/big/big_test.go
Normal file
130
duration/big/big_test.go
Normal file
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2023 Nicolas JUHEL
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package big_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
durbig "github.com/nabbar/golib/duration/big"
|
||||
"github.com/pelletier/go-toml"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
type StructExample struct {
|
||||
Value durbig.Duration `json:"value" yaml:"value" toml:"value"`
|
||||
}
|
||||
|
||||
var valueExample = StructExample{Value: (5 * durbig.Day) + (23 * durbig.Hour) + (15 * durbig.Minute) + (13 * durbig.Second)}
|
||||
|
||||
func jsonDuration() []byte {
|
||||
return []byte(`{"value":"5d23h15m13s"}`)
|
||||
}
|
||||
|
||||
func yamlDuration() []byte {
|
||||
return []byte(`value: 5d23h15m13s
|
||||
`)
|
||||
}
|
||||
|
||||
func tomlDuration() []byte {
|
||||
return []byte(`value = "5d23h15m13s"
|
||||
`)
|
||||
}
|
||||
|
||||
var _ = Describe("duration/big", func() {
|
||||
Context("decoding value from json, yaml, toml", func() {
|
||||
var (
|
||||
err error
|
||||
obj = StructExample{}
|
||||
)
|
||||
|
||||
It("success when json decoding", func() {
|
||||
err = json.Unmarshal(jsonDuration(), &obj)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(obj.Value).To(Equal(valueExample.Value))
|
||||
})
|
||||
|
||||
It("success when yaml decoding", func() {
|
||||
err = yaml.Unmarshal(yamlDuration(), &obj)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(obj.Value).To(Equal(valueExample.Value))
|
||||
})
|
||||
|
||||
It("success when toml decoding", func() {
|
||||
err = toml.Unmarshal(tomlDuration(), &obj)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(obj.Value).To(Equal(valueExample.Value))
|
||||
})
|
||||
})
|
||||
Context("encoding value from json, yaml, toml", func() {
|
||||
var (
|
||||
err error
|
||||
res []byte
|
||||
str string
|
||||
exp string
|
||||
)
|
||||
|
||||
It("success when json encoding", func() {
|
||||
res, err = json.Marshal(&valueExample)
|
||||
str = string(res)
|
||||
exp = string(jsonDuration())
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(str).To(Equal(exp))
|
||||
})
|
||||
|
||||
It("success when yaml encoding", func() {
|
||||
res, err = yaml.Marshal(&valueExample)
|
||||
str = string(res)
|
||||
exp = string(yamlDuration())
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(str).To(Equal(exp))
|
||||
})
|
||||
|
||||
It("success when toml encoding", func() {
|
||||
res, err = toml.Marshal(&valueExample)
|
||||
str = string(res)
|
||||
exp = string(tomlDuration())
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(str).To(Equal(exp))
|
||||
})
|
||||
|
||||
It("success when json encoding of maxDuration Value", func() {
|
||||
var dur durbig.Duration = 1<<63 - 1
|
||||
res, err = json.Marshal(&dur)
|
||||
str = string(res)
|
||||
exp = `"106751991167300d15h30m7s"`
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(str).To(Equal(exp))
|
||||
})
|
||||
})
|
||||
})
|
||||
86
duration/big/encode.go
Normal file
86
duration/big/encode.go
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Nicolas JUHEL
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
package big
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/fxamacker/cbor/v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func (d Duration) MarshalJSON() ([]byte, error) {
|
||||
var s = d.String()
|
||||
return json.Marshal(s)
|
||||
}
|
||||
|
||||
func (d *Duration) UnmarshalJSON(bytes []byte) error {
|
||||
return d.unmarshall(bytes)
|
||||
}
|
||||
|
||||
func (d Duration) MarshalYAML() (interface{}, error) {
|
||||
var s = d.String()
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (d *Duration) UnmarshalYAML(value *yaml.Node) error {
|
||||
return d.unmarshall([]byte(value.Value))
|
||||
}
|
||||
|
||||
func (d Duration) MarshalTOML() ([]byte, error) {
|
||||
var s = d.String()
|
||||
return []byte("\"" + s + "\""), nil
|
||||
}
|
||||
|
||||
func (d *Duration) UnmarshalTOML(i interface{}) error {
|
||||
if b, k := i.([]byte); k {
|
||||
return d.unmarshall(b)
|
||||
}
|
||||
|
||||
if b, k := i.(string); k {
|
||||
return d.parseString(b)
|
||||
}
|
||||
|
||||
return fmt.Errorf("size: value not in valid format")
|
||||
}
|
||||
|
||||
func (d Duration) MarshalText() ([]byte, error) {
|
||||
return []byte(d.String()), nil
|
||||
}
|
||||
|
||||
func (d *Duration) UnmarshalText(bytes []byte) error {
|
||||
return d.unmarshall(bytes)
|
||||
}
|
||||
|
||||
func (d Duration) MarshalCBOR() ([]byte, error) {
|
||||
var s = d.String()
|
||||
return cbor.Marshal(s)
|
||||
}
|
||||
|
||||
func (d *Duration) UnmarshalCBOR(bytes []byte) error {
|
||||
return d.unmarshall(bytes)
|
||||
}
|
||||
104
duration/big/format.go
Normal file
104
duration/big/format.go
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Nicolas JUHEL
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
package big
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (d Duration) Time() (time.Duration, error) {
|
||||
mxt := float64(math.MaxInt64) / float64(time.Second)
|
||||
if d.Float64() > mxt {
|
||||
return 0, fmt.Errorf("overflow max time.Duration value")
|
||||
}
|
||||
|
||||
return time.Duration(d) * time.Second, nil
|
||||
}
|
||||
|
||||
// String returns a string representing the duration in the form "135d72h3m5s".
|
||||
// Leading zero units are omitted. The zero duration formats as 0s.
|
||||
func (d Duration) String() string {
|
||||
var s string
|
||||
|
||||
if d < 0 {
|
||||
s = "-"
|
||||
} else if d == 0 {
|
||||
return "0s"
|
||||
}
|
||||
|
||||
// Days
|
||||
r, p := stringUnit(int64(d.Abs()), Day.Int64(), "d")
|
||||
s += p
|
||||
|
||||
// Hours
|
||||
r, p = stringUnit(r, Hour.Int64(), "h")
|
||||
s += p
|
||||
|
||||
// Minutes
|
||||
r, p = stringUnit(r, Minute.Int64(), "m")
|
||||
s += p
|
||||
|
||||
// Seconds
|
||||
if r > 0 {
|
||||
s += fmt.Sprintf("%ds", r)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func stringUnit(val, div int64, unit string) (rest int64, str string) {
|
||||
if val == val%div {
|
||||
// same value so no unit in value, so skip
|
||||
return val, ""
|
||||
}
|
||||
|
||||
n := val % div
|
||||
v := (val - n) / div
|
||||
|
||||
if v > 0 {
|
||||
return n, fmt.Sprintf("%d%s", v, unit)
|
||||
} else {
|
||||
return val, ""
|
||||
}
|
||||
}
|
||||
|
||||
func (d Duration) Int64() int64 {
|
||||
return int64(d)
|
||||
}
|
||||
|
||||
func (d Duration) Uint64() uint64 {
|
||||
if d < 0 {
|
||||
return uint64(0)
|
||||
}
|
||||
|
||||
return uint64(d)
|
||||
}
|
||||
|
||||
func (d Duration) Float64() float64 {
|
||||
return float64(d)
|
||||
}
|
||||
89
duration/big/interface.go
Normal file
89
duration/big/interface.go
Normal file
@@ -0,0 +1,89 @@
|
||||
/***********************************************************************************************************************
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Nicolas JUHEL
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*
|
||||
**********************************************************************************************************************/
|
||||
|
||||
package big
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
const mx float64 = math.MaxInt64
|
||||
|
||||
const (
|
||||
Second Duration = 1
|
||||
Minute = 60 * Second
|
||||
Hour = 60 * Minute
|
||||
Day = 24 * Hour
|
||||
)
|
||||
|
||||
var (
|
||||
ErrOverFlow = errors.New("value overflow max int64")
|
||||
)
|
||||
|
||||
// Max Value of Big Duration : 106,751,991,167,300 d 15 h 30 m 7 s
|
||||
|
||||
type Duration time.Duration
|
||||
|
||||
func Parse(s string) (Duration, error) {
|
||||
return parseString(s)
|
||||
}
|
||||
|
||||
func ParseByte(p []byte) (Duration, error) {
|
||||
return parseString(string(p))
|
||||
}
|
||||
|
||||
func Seconds(i int64) Duration {
|
||||
return Duration(i)
|
||||
}
|
||||
|
||||
func Minutes(i int64) Duration {
|
||||
return Duration(i) * Minute
|
||||
}
|
||||
|
||||
func Hours(i int64) Duration {
|
||||
return Duration(i) * Hour
|
||||
}
|
||||
|
||||
func Days(i int64) Duration {
|
||||
return Duration(i) * Day
|
||||
}
|
||||
|
||||
func ParseDuration(d time.Duration) Duration {
|
||||
return ParseFloat64(math.Floor(d.Seconds()))
|
||||
}
|
||||
|
||||
func ParseFloat64(f float64) Duration {
|
||||
const mx float64 = math.MaxInt64
|
||||
|
||||
if f > mx {
|
||||
return Duration(math.MaxInt64)
|
||||
} else {
|
||||
return Duration(math.Round(f))
|
||||
}
|
||||
}
|
||||
62
duration/big/model.go
Normal file
62
duration/big/model.go
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Nicolas JUHEL
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
package big
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
libmap "github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
const (
|
||||
minDuration Duration = -1 << 63
|
||||
maxDuration Duration = 1<<63 - 1
|
||||
)
|
||||
|
||||
func ViperDecoderHook() libmap.DecodeHookFuncType {
|
||||
return func(from reflect.Type, to reflect.Type, data interface{}) (interface{}, error) {
|
||||
var (
|
||||
z = Duration(0)
|
||||
t string
|
||||
k bool
|
||||
)
|
||||
|
||||
// Check if the data type matches the expected one
|
||||
if from.Kind() != reflect.String {
|
||||
return data, nil
|
||||
} else if t, k = data.(string); !k {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// Check if the target type matches the expected one
|
||||
if to != reflect.TypeOf(z) {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// Format/decode/parse the data and return the new value
|
||||
return parseString(t)
|
||||
}
|
||||
}
|
||||
108
duration/big/operation.go
Normal file
108
duration/big/operation.go
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Nicolas JUHEL
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
package big
|
||||
|
||||
import (
|
||||
libpid "github.com/nabbar/golib/pidcontroller"
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultRateProportional float64 = 0.1
|
||||
DefaultRateIntegral float64 = 0.01
|
||||
DefaultRateDerivative float64 = 0.05
|
||||
)
|
||||
|
||||
// Abs returns the absolute value of d.
|
||||
// As a special case, Duration([math.MinInt64]) is converted to Duration([math.MaxInt64]),
|
||||
// reducing its magnitude by 1 nanosecond.
|
||||
func (d Duration) Abs() Duration {
|
||||
switch {
|
||||
case d >= 0:
|
||||
return d
|
||||
case d == minDuration:
|
||||
return maxDuration
|
||||
default:
|
||||
return -d
|
||||
}
|
||||
}
|
||||
|
||||
func (d Duration) RangeTo(dur Duration, rateP, rateI, rateD float64) []Duration {
|
||||
var (
|
||||
p = libpid.New(rateP, rateI, rateD)
|
||||
r = make([]Duration, 0)
|
||||
)
|
||||
|
||||
for _, v := range p.Range(d.Float64(), dur.Float64()) {
|
||||
r = append(r, ParseFloat64(v))
|
||||
}
|
||||
|
||||
if len(r) < 3 {
|
||||
r = append(make([]Duration, 0), d, dur)
|
||||
}
|
||||
|
||||
if r[0] > d {
|
||||
r = append(append(make([]Duration, 0), d), r...)
|
||||
}
|
||||
|
||||
if r[len(r)-1] < dur {
|
||||
r = append(r, dur)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (d Duration) RangeDefTo(dur Duration) []Duration {
|
||||
return d.RangeTo(dur, DefaultRateProportional, DefaultRateIntegral, DefaultRateDerivative)
|
||||
}
|
||||
|
||||
func (d Duration) RangeFrom(dur Duration, rateP, rateI, rateD float64) []Duration {
|
||||
var (
|
||||
p = libpid.New(rateP, rateI, rateD)
|
||||
r = make([]Duration, 0)
|
||||
)
|
||||
|
||||
for _, v := range p.Range(dur.Float64(), d.Float64()) {
|
||||
r = append(r, ParseFloat64(v))
|
||||
}
|
||||
|
||||
if len(r) < 3 {
|
||||
r = append(make([]Duration, 0), d, dur)
|
||||
}
|
||||
|
||||
if r[0] > dur {
|
||||
r = append(append(make([]Duration, 0), dur), r...)
|
||||
}
|
||||
|
||||
if r[len(r)-1] < d {
|
||||
r = append(r, d)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (d Duration) RangeDefFrom(dur Duration) []Duration {
|
||||
return d.RangeFrom(dur, DefaultRateProportional, DefaultRateIntegral, DefaultRateDerivative)
|
||||
}
|
||||
244
duration/big/parse.go
Normal file
244
duration/big/parse.go
Normal file
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Nicolas JUHEL
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
package big
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var errLeadingInt = errors.New("time: bad [0-9]*") // never printed
|
||||
var unitMap = map[string]uint64{
|
||||
"s": uint64(Second),
|
||||
"m": uint64(Minute),
|
||||
"h": uint64(Hour),
|
||||
"d": uint64(Day),
|
||||
}
|
||||
|
||||
func parseString(s string) (Duration, error) {
|
||||
s = strings.Replace(s, "\"", "", -1)
|
||||
s = strings.Replace(s, "'", "", -1)
|
||||
s = strings.Replace(s, " ", "", -1)
|
||||
|
||||
// err: 99d55h44m33s123ms
|
||||
return parseDuration(s)
|
||||
}
|
||||
|
||||
func (d *Duration) parseString(s string) error {
|
||||
if v, e := parseString(s); e != nil {
|
||||
return e
|
||||
} else {
|
||||
*d = v
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Duration) unmarshall(val []byte) error {
|
||||
if tmp, err := ParseByte(val); err != nil {
|
||||
return err
|
||||
} else {
|
||||
*d = tmp
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// parseDuration parses a duration string.
|
||||
// code from time.ParseDuration
|
||||
// A duration string is a possibly signed sequence of
|
||||
// decimal numbers, each with optional fraction and a unit suffix,
|
||||
// such as "300ms", "-1.5h" or "2h45m".
|
||||
// Valid time units are "s", "m", "h", "d".
|
||||
func parseDuration(s string) (Duration, error) {
|
||||
// [-+]?([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, fmt.Errorf("time: invalid duration '%s'", 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, fmt.Errorf("time: invalid duration '%s'", orig)
|
||||
}
|
||||
|
||||
// Consume [0-9]*
|
||||
pl := len(s)
|
||||
v, s, err = leadingInt(s)
|
||||
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("time: invalid duration '%s'", 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, fmt.Errorf("time: invalid duration '%s'", 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, fmt.Errorf("time: missing unit in duration '%s'" + orig)
|
||||
}
|
||||
|
||||
u := s[:i]
|
||||
s = s[i:]
|
||||
unit, ok := unitMap[u]
|
||||
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("time: unknown unit '%s' in duration '%s'", u, orig)
|
||||
}
|
||||
|
||||
if v > 1<<63/unit {
|
||||
// overflow
|
||||
return 0, fmt.Errorf("time: invalid duration '%s'", 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, fmt.Errorf("time: invalid duration '%s'", orig)
|
||||
}
|
||||
}
|
||||
d += v
|
||||
|
||||
if d > 1<<63 {
|
||||
return 0, fmt.Errorf("time: invalid duration '%s'", orig)
|
||||
}
|
||||
}
|
||||
|
||||
if neg {
|
||||
return -Duration(d), nil
|
||||
}
|
||||
|
||||
if d > 1<<63-1 {
|
||||
return 0, fmt.Errorf("time: invalid duration '%s'", orig)
|
||||
}
|
||||
return Duration(d), nil
|
||||
}
|
||||
|
||||
// leadingInt consumes the leading [0-9]* from s.
|
||||
func leadingInt[bytes []byte | string](s bytes) (x uint64, rem bytes, 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, rem, errLeadingInt
|
||||
}
|
||||
x = x*10 + uint64(c) - '0'
|
||||
if x > 1<<63 {
|
||||
// overflow
|
||||
return 0, rem, errLeadingInt
|
||||
}
|
||||
}
|
||||
return x, s[i:], nil
|
||||
}
|
||||
|
||||
// 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:]
|
||||
}
|
||||
100
duration/big/truncate.go
Normal file
100
duration/big/truncate.go
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Nicolas JUHEL
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
package big
|
||||
|
||||
// Round returns the result of rounding d to the nearest multiple of unit.
|
||||
// The rounding behavior for halfway values is to round away from zero.
|
||||
// If the result exceeds the maximum (or minimum)
|
||||
// value that can be stored in a [Duration],
|
||||
// Round returns the maximum (or minimum) duration.
|
||||
// If unit <= 0, Round returns d unchanged.
|
||||
// code from time.Duration
|
||||
func (d Duration) Round(unit Duration) Duration {
|
||||
if unit <= 0 {
|
||||
return d
|
||||
}
|
||||
|
||||
r := d % unit
|
||||
|
||||
if d < 0 {
|
||||
r = -r
|
||||
|
||||
if lessThanHalf(r, unit) {
|
||||
return d + r
|
||||
}
|
||||
|
||||
if d1 := d - unit + r; d1 < d {
|
||||
return d1
|
||||
}
|
||||
|
||||
return minDuration // overflow
|
||||
}
|
||||
|
||||
if lessThanHalf(r, unit) {
|
||||
return d - r
|
||||
}
|
||||
|
||||
if d1 := d + unit - r; d1 > d {
|
||||
return d1
|
||||
}
|
||||
|
||||
return maxDuration // overflow
|
||||
}
|
||||
|
||||
// Truncate returns the result of rounding d toward zero to a multiple of m.
|
||||
// If unit <= 0, Truncate returns d unchanged.
|
||||
// code from time.Duration
|
||||
func (d Duration) Truncate(unit Duration) Duration {
|
||||
if unit <= 0 {
|
||||
return d
|
||||
}
|
||||
return d - d%unit
|
||||
}
|
||||
|
||||
// TruncateMinutes returns the result of rounding d toward zero to a multiple of Minute.
|
||||
// If unit <= 0, TruncateMinutes returns d unchanged.
|
||||
func (d Duration) TruncateMinutes() Duration {
|
||||
return d.Truncate(Minute)
|
||||
}
|
||||
|
||||
// TruncateHours returns the result of rounding d toward zero to a multiple of Hour.
|
||||
// If unit <= 0, TruncateHours returns d unchanged.
|
||||
func (d Duration) TruncateHours() Duration {
|
||||
return d.Truncate(Hour)
|
||||
}
|
||||
|
||||
// TruncateDays returns the result of rounding d toward zero to a multiple of Day.
|
||||
// If unit <= 0, TruncateDays returns d unchanged.
|
||||
func (d Duration) TruncateDays() Duration {
|
||||
return d.Truncate(Day)
|
||||
}
|
||||
|
||||
// lessThanHalf reports whether x+x < y but avoids overflow,
|
||||
// assuming x and y are both positive (Duration is signed).
|
||||
// code from time.Duration
|
||||
func lessThanHalf(x, y Duration) bool {
|
||||
return uint64(x)+uint64(x) < uint64(y)
|
||||
}
|
||||
53
duration/big_suite_test.go
Normal file
53
duration/big_suite_test.go
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2023 Nicolas JUHEL
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package duration_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
/*
|
||||
Using https://onsi.github.io/ginkgo/
|
||||
Running with $> ginkgo -cover .
|
||||
*/
|
||||
|
||||
// TestGolibEncodingHexHelper tests the Golib Hex Encoding Helper function.
|
||||
func TestGolibDurationHelper(t *testing.T) {
|
||||
time.Sleep(500 * time.Millisecond) // Adding delay for better testing synchronization
|
||||
RegisterFailHandler(Fail) // Registering fail handler for better test failure reporting
|
||||
RunSpecs(t, "Duration Helper Suite") // Running the test suite for Encoding Hex Helper
|
||||
}
|
||||
|
||||
var _ = BeforeSuite(func() {
|
||||
})
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
})
|
||||
120
duration/big_test.go
Normal file
120
duration/big_test.go
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2023 Nicolas JUHEL
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package duration_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
libdur "github.com/nabbar/golib/duration"
|
||||
"github.com/pelletier/go-toml"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
type StructExample struct {
|
||||
Value libdur.Duration `json:"value" yaml:"value" toml:"value"`
|
||||
}
|
||||
|
||||
var valueExample = StructExample{Value: libdur.Days(5) + libdur.Hours(23) + libdur.Minutes(15) + libdur.Seconds(13)}
|
||||
|
||||
func jsonDuration() []byte {
|
||||
return []byte(`{"value":"5d23h15m13s"}`)
|
||||
}
|
||||
|
||||
func yamlDuration() []byte {
|
||||
return []byte(`value: 5d23h15m13s
|
||||
`)
|
||||
}
|
||||
|
||||
func tomlDuration() []byte {
|
||||
return []byte(`value = "5d23h15m13s"
|
||||
`)
|
||||
}
|
||||
|
||||
var _ = Describe("duration/big", func() {
|
||||
Context("decoding value from json, yaml, toml", func() {
|
||||
var (
|
||||
err error
|
||||
obj = StructExample{}
|
||||
)
|
||||
|
||||
It("success when json decoding", func() {
|
||||
err = json.Unmarshal(jsonDuration(), &obj)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(obj.Value).To(Equal(valueExample.Value))
|
||||
})
|
||||
|
||||
It("success when yaml decoding", func() {
|
||||
err = yaml.Unmarshal(yamlDuration(), &obj)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(obj.Value).To(Equal(valueExample.Value))
|
||||
})
|
||||
|
||||
It("success when toml decoding", func() {
|
||||
err = toml.Unmarshal(tomlDuration(), &obj)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(obj.Value).To(Equal(valueExample.Value))
|
||||
})
|
||||
})
|
||||
Context("encoding value from json, yaml, toml", func() {
|
||||
var (
|
||||
err error
|
||||
res []byte
|
||||
str string
|
||||
exp string
|
||||
)
|
||||
|
||||
It("success when json encoding", func() {
|
||||
res, err = json.Marshal(&valueExample)
|
||||
str = string(res)
|
||||
exp = string(jsonDuration())
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(str).To(Equal(exp))
|
||||
})
|
||||
|
||||
It("success when yaml encoding", func() {
|
||||
res, err = yaml.Marshal(&valueExample)
|
||||
str = string(res)
|
||||
exp = string(yamlDuration())
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(str).To(Equal(exp))
|
||||
})
|
||||
|
||||
It("success when toml encoding", func() {
|
||||
res, err = toml.Marshal(&valueExample)
|
||||
str = string(res)
|
||||
exp = string(tomlDuration())
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(str).To(Equal(exp))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -26,18 +26,16 @@
|
||||
package duration
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/fxamacker/cbor/v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func (d Duration) MarshalJSON() ([]byte, error) {
|
||||
t := d.String()
|
||||
b := make([]byte, 0, len(t)+2)
|
||||
b = append(b, '"')
|
||||
b = append(b, []byte(t)...)
|
||||
b = append(b, '"')
|
||||
return b, nil
|
||||
var s = d.String()
|
||||
return json.Marshal(s)
|
||||
}
|
||||
|
||||
func (d *Duration) UnmarshalJSON(bytes []byte) error {
|
||||
@@ -45,7 +43,8 @@ func (d *Duration) UnmarshalJSON(bytes []byte) error {
|
||||
}
|
||||
|
||||
func (d Duration) MarshalYAML() (interface{}, error) {
|
||||
return d.MarshalJSON()
|
||||
var s = d.String()
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (d *Duration) UnmarshalYAML(value *yaml.Node) error {
|
||||
@@ -53,7 +52,8 @@ func (d *Duration) UnmarshalYAML(value *yaml.Node) error {
|
||||
}
|
||||
|
||||
func (d Duration) MarshalTOML() ([]byte, error) {
|
||||
return d.MarshalJSON()
|
||||
var s = d.String()
|
||||
return []byte("\"" + s + "\""), nil
|
||||
}
|
||||
|
||||
func (d *Duration) UnmarshalTOML(i interface{}) error {
|
||||
@@ -77,7 +77,8 @@ func (d *Duration) UnmarshalText(bytes []byte) error {
|
||||
}
|
||||
|
||||
func (d Duration) MarshalCBOR() ([]byte, error) {
|
||||
return []byte(d.String()), nil
|
||||
var s = d.String()
|
||||
return cbor.Marshal(s)
|
||||
}
|
||||
|
||||
func (d *Duration) UnmarshalCBOR(bytes []byte) error {
|
||||
|
||||
@@ -26,21 +26,32 @@
|
||||
package duration
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var errLeadingInt = errors.New("time: bad [0-9]*") // never printed
|
||||
var unitMap = map[string]uint64{
|
||||
"ns": uint64(time.Nanosecond),
|
||||
"us": uint64(time.Microsecond),
|
||||
"µs": uint64(time.Microsecond), // U+00B5 = micro symbol
|
||||
"μs": uint64(time.Microsecond), // U+03BC = Greek letter mu
|
||||
"ms": uint64(time.Millisecond),
|
||||
"s": uint64(time.Second),
|
||||
"m": uint64(time.Minute),
|
||||
"h": uint64(time.Hour),
|
||||
"d": uint64(24 * time.Hour),
|
||||
}
|
||||
|
||||
func parseString(s string) (Duration, error) {
|
||||
s = strings.Replace(s, "\"", "", -1)
|
||||
s = strings.Replace(s, "'", "", -1)
|
||||
s = strings.Replace(s, " ", "", -1)
|
||||
|
||||
// err: 99d55h44m33s123ms
|
||||
|
||||
if v, e := time.ParseDuration(s); e != nil {
|
||||
return 0, e
|
||||
} else {
|
||||
return Duration(v), nil
|
||||
}
|
||||
return parseDuration(s)
|
||||
}
|
||||
|
||||
func (d *Duration) parseString(s string) error {
|
||||
@@ -60,3 +71,193 @@ func (d *Duration) unmarshall(val []byte) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// parseDuration parses a duration string.
|
||||
// code from time.ParseDuration
|
||||
// A duration string is a possibly signed sequence of
|
||||
// decimal numbers, each with optional fraction and a unit suffix,
|
||||
// such as "300ms", "-1.5h" or "2h45m".
|
||||
// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h", "d".
|
||||
func parseDuration(s string) (Duration, error) {
|
||||
// [-+]?([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, fmt.Errorf("time: invalid duration '%s'", 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, fmt.Errorf("time: invalid duration '%s'", orig)
|
||||
}
|
||||
|
||||
// Consume [0-9]*
|
||||
pl := len(s)
|
||||
v, s, err = leadingInt(s)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("time: invalid duration '%s'", 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, fmt.Errorf("time: invalid duration '%s'", 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, fmt.Errorf("time: missing unit in duration '%s'" + orig)
|
||||
}
|
||||
|
||||
u := s[:i]
|
||||
s = s[i:]
|
||||
unit, ok := unitMap[u]
|
||||
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("time: unknown unit '%s' in duration '%s'", u, orig)
|
||||
}
|
||||
|
||||
if v > 1<<63/unit {
|
||||
// overflow
|
||||
return 0, fmt.Errorf("time: invalid duration '%s'", 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, fmt.Errorf("time: invalid duration '%s'", orig)
|
||||
}
|
||||
}
|
||||
|
||||
d += v
|
||||
|
||||
if d > 1<<63 {
|
||||
return 0, fmt.Errorf("time: invalid duration '%s'", orig)
|
||||
}
|
||||
}
|
||||
|
||||
if neg {
|
||||
return -Duration(d), nil
|
||||
}
|
||||
|
||||
if d > 1<<63-1 {
|
||||
return 0, fmt.Errorf("time: invalid duration '%s'", orig)
|
||||
}
|
||||
|
||||
return Duration(d), nil
|
||||
}
|
||||
|
||||
// leadingInt consumes the leading [0-9]* from s.
|
||||
func leadingInt[bytes []byte | string](s bytes) (x uint64, rem bytes, 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, rem, errLeadingInt
|
||||
}
|
||||
|
||||
x = x*10 + uint64(c) - '0'
|
||||
|
||||
if x > 1<<63 {
|
||||
// overflow
|
||||
return 0, rem, errLeadingInt
|
||||
}
|
||||
}
|
||||
|
||||
return x, s[i:], nil
|
||||
}
|
||||
|
||||
// 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:]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user