-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2cd0053
commit 43698f9
Showing
4 changed files
with
349 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package value | ||
|
||
import ( | ||
"fmt" | ||
"time" | ||
) | ||
|
||
// ValueBaseTime is a [debefix.ValueCalculator] calculate times based on a base time and a duration. | ||
// In addition to [time.Duration], 3 more suffixes are supported: d (days), w (weeks), y (years). | ||
type ValueBaseTime struct { | ||
baseTime time.Time | ||
typeName string | ||
} | ||
|
||
func NewValueBaseTime(baseTime time.Time, options ...ValueTimeOption) *ValueBaseTime { | ||
ret := &ValueBaseTime{ | ||
baseTime: baseTime, | ||
typeName: "basetime", | ||
} | ||
for _, opt := range options { | ||
opt(ret) | ||
} | ||
return ret | ||
} | ||
|
||
func (v ValueBaseTime) CalculateValue(typ string, parameter string) (bool, any, error) { | ||
if typ != v.typeName { | ||
return false, nil, nil | ||
} | ||
|
||
if parameter == "" { | ||
return true, v.baseTime, nil | ||
} | ||
|
||
duration, err := parseDuration(parameter) | ||
if err != nil { | ||
return false, nil, fmt.Errorf("error parsing duration '%s': %w", parameter, err) | ||
} | ||
|
||
return true, v.baseTime.Add(time.Duration(duration)), nil | ||
} | ||
|
||
type ValueTimeOption func(*ValueBaseTime) | ||
|
||
func WithValueTimeTypeName(typeName string) ValueTimeOption { | ||
return func(v *ValueBaseTime) { | ||
v.typeName = typeName | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package value | ||
|
||
import ( | ||
"testing" | ||
"testing/fstest" | ||
"time" | ||
|
||
"github.com/rrgmc/debefix" | ||
"gotest.tools/v3/assert" | ||
) | ||
|
||
func TestBaseTimeValueCalculated(t *testing.T) { | ||
provider := debefix.NewFSFileProvider(fstest.MapFS{ | ||
"users.dbf.yaml": &fstest.MapFile{ | ||
Data: []byte(`tables: | ||
tags: | ||
rows: | ||
- tag_id: 12 | ||
tag_name: "All" | ||
created_at: !expr "calculated:basetime:1d5h" | ||
updated_at: !expr "calculated:basetime:1d5h10m" | ||
`), | ||
}, | ||
}) | ||
|
||
data, err := debefix.Load(provider) | ||
assert.NilError(t, err) | ||
|
||
rowCount := map[string]int{} | ||
|
||
baseTime := time.Date(2014, 3, 4, 10, 10, 12, 0, time.UTC) | ||
|
||
resolved, err := debefix.Resolve(data, func(ctx debefix.ResolveContext, fields map[string]any) error { | ||
rowCount[ctx.TableID()]++ | ||
return nil | ||
}, debefix.WithResolvedValueCalculator(NewValueBaseTime(baseTime))) | ||
assert.NilError(t, err) | ||
|
||
assert.DeepEqual(t, map[string]int{ | ||
"tags": 1, | ||
}, rowCount) | ||
|
||
ctime, isTime := resolved.Tables["tags"].Rows[0].Fields["created_at"].(time.Time) | ||
assert.Assert(t, isTime, "field value should be of time.Time value") | ||
|
||
assert.Equal(t, 5, ctime.Day()) | ||
assert.Equal(t, 15, ctime.Hour()) | ||
} | ||
|
||
func TestBaseTimeValueCalculatedEmptyParameter(t *testing.T) { | ||
provider := debefix.NewFSFileProvider(fstest.MapFS{ | ||
"users.dbf.yaml": &fstest.MapFile{ | ||
Data: []byte(`tables: | ||
tags: | ||
rows: | ||
- tag_id: 12 | ||
tag_name: "All" | ||
created_at: !expr "calculated:basetime" | ||
`), | ||
}, | ||
}) | ||
|
||
data, err := debefix.Load(provider) | ||
assert.NilError(t, err) | ||
|
||
rowCount := map[string]int{} | ||
|
||
baseTime := time.Date(2014, 3, 4, 10, 10, 12, 0, time.UTC) | ||
|
||
resolved, err := debefix.Resolve(data, func(ctx debefix.ResolveContext, fields map[string]any) error { | ||
rowCount[ctx.TableID()]++ | ||
return nil | ||
}, debefix.WithResolvedValueCalculator(NewValueBaseTime(baseTime))) | ||
assert.NilError(t, err) | ||
|
||
assert.DeepEqual(t, map[string]int{ | ||
"tags": 1, | ||
}, rowCount) | ||
|
||
ctime, isTime := resolved.Tables["tags"].Rows[0].Fields["created_at"].(time.Time) | ||
assert.Assert(t, isTime, "field value should be of time.Time value") | ||
|
||
assert.Equal(t, 4, ctime.Day()) | ||
assert.Equal(t, 10, ctime.Hour()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
package value | ||
|
||
// source: https://github.com/wangyumu/duration/blob/main/duration.go | ||
|
||
import ( | ||
"errors" | ||
"time" | ||
) | ||
|
||
// duration is a standard unit of time. | ||
type duration time.Duration | ||
|
||
// Nanoseconds returns the duration as an integer nanosecond count. | ||
func (d duration) Nanoseconds() int64 { return int64(d) } | ||
|
||
// Seconds returns the duration as a floating point number of seconds. | ||
func (d duration) Seconds() float64 { | ||
sec := d / Second | ||
nsec := d % Second | ||
return float64(sec) + float64(nsec)*1e-9 | ||
} | ||
|
||
// Hours returns the duration as a floating point number of hours. | ||
func (d duration) Hours() float64 { | ||
hour := d / Hour | ||
nsec := d % Hour | ||
return float64(hour) + float64(nsec)*(1e-9/60/60) | ||
} | ||
|
||
// Days returns the duration as a floating point number of days. | ||
func (d duration) Days() float64 { | ||
hour := d / Hour | ||
nsec := d % Hour | ||
return float64(hour) + float64(nsec)*(1e-9/60/60/24) | ||
} | ||
|
||
// Weeks returns the duration as a floating point number of days. | ||
func (d duration) Weeks() float64 { | ||
hour := d / Hour | ||
nsec := d % Hour | ||
return float64(hour) + float64(nsec)*(1e-9/60/60/24/7) | ||
} | ||
|
||
// Minutes returns the duration as a floating point number of minutes. | ||
func (d duration) Minutes() float64 { | ||
min := d / Minute | ||
nsec := d % Minute | ||
return float64(min) + float64(nsec)*(1e-9/60) | ||
} | ||
|
||
// Standard unit of time. | ||
var ( | ||
Nanosecond = duration(time.Nanosecond) | ||
Microsecond = duration(time.Microsecond) | ||
Millisecond = duration(time.Millisecond) | ||
Second = duration(time.Second) | ||
Minute = duration(time.Minute) | ||
Hour = duration(time.Hour) | ||
Day = Hour * 24 | ||
Week = Day * 7 | ||
Fortnight = Week * 2 | ||
Month = Day * 30 // Approximation | ||
Year = Day * 365 // Approximation | ||
Decade = Year * 10 // Approximation | ||
Century = Year * 100 // Approximation | ||
Millennium = Year * 1000 // Approximation | ||
) | ||
|
||
var errLeadingInt = errors.New("duration: bad [0-9]*") // never printed | ||
|
||
// leadingInt consumes the leading [0-9]* from s. | ||
func leadingInt(s string) (x int64, rem string, err error) { | ||
i := 0 | ||
for ; i < len(s); i++ { | ||
c := s[i] | ||
if c < '0' || c > '9' { | ||
break | ||
} | ||
if x > (1<<63-1)/10 { | ||
// overflow | ||
return 0, "", errLeadingInt | ||
} | ||
x = x*10 + int64(c) - '0' | ||
if x < 0 { | ||
// overflow | ||
return 0, "", errLeadingInt | ||
} | ||
} | ||
return x, s[i:], nil | ||
} | ||
|
||
var unitMap = map[string]int64{ | ||
"ns": int64(Nanosecond), | ||
"us": int64(Microsecond), | ||
"µs": int64(Microsecond), // U+00B5 = micro symbol | ||
"μs": int64(Microsecond), // U+03BC = Greek letter mu | ||
"ms": int64(Millisecond), | ||
"s": int64(Second), | ||
"m": int64(Minute), | ||
"h": int64(Hour), | ||
"d": int64(Day), | ||
"w": int64(Week), | ||
"y": int64(Year), // Approximation | ||
} | ||
|
||
// parseDuration 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 "300ms", "-1.5h" or "2h45m". | ||
// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h", "d", "w", "y". | ||
func parseDuration(s string) (duration, error) { | ||
// [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+ | ||
orig := s | ||
var d int64 | ||
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 " + orig) | ||
} | ||
for s != "" { | ||
var ( | ||
v, f int64 // 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 " + orig) | ||
} | ||
// Consume [0-9]* | ||
pl := len(s) | ||
v, s, err = leadingInt(s) | ||
if err != nil { | ||
return 0, errors.New("time: invalid duration " + 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, s, err = leadingInt(s) | ||
if err != nil { | ||
return 0, errors.New("time: invalid duration " + orig) | ||
} | ||
for n := pl - len(s); n > 0; n-- { | ||
scale *= 10 | ||
} | ||
post = pl != len(s) | ||
} | ||
if !pre && !post { | ||
// no digits (e.g. ".s" or "-.s") | ||
return 0, errors.New("time: invalid duration " + 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 " + orig) | ||
} | ||
u := s[:i] | ||
s = s[i:] | ||
unit, ok := unitMap[u] | ||
if !ok { | ||
return 0, errors.New("time: unknown unit " + u + " in duration " + orig) | ||
} | ||
if v > (1<<63-1)/unit { | ||
// overflow | ||
return 0, errors.New("time: invalid duration " + 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 += int64(float64(f) * (float64(unit) / scale)) | ||
if v < 0 { | ||
// overflow | ||
return 0, errors.New("time: invalid duration " + orig) | ||
} | ||
} | ||
d += v | ||
if d < 0 { | ||
// overflow | ||
return 0, errors.New("time: invalid duration " + orig) | ||
} | ||
} | ||
|
||
if neg { | ||
d = -d | ||
} | ||
return duration(d), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters