mirror of
https://github.com/Luzifer/twitch-bot.git
synced 2024-12-20 20:01:17 +00:00
[templating] add humanDateDiff
and formatHumanDateDiff
functions
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
bc9c3eeb15
commit
e8eb6cd0f4
6 changed files with 288 additions and 1 deletions
|
@ -256,6 +256,19 @@ Example:
|
||||||
< 5 hours, 33 minutes, 12 seconds - 5 hours, 33 minutes
|
< 5 hours, 33 minutes, 12 seconds - 5 hours, 33 minutes
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `formatHumanDateDiff`
|
||||||
|
|
||||||
|
Formats a DateInterval object according to the format (%Y, %M, %D, %H, %I, %S for years, months, days, hours, minutes, seconds - Lowercase letters without leading zeros)
|
||||||
|
|
||||||
|
Syntax: `formatHumanDateDiff <format> <obj>`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
# {{ humanDateDiff (mustToDate "2006-01-02 -0700" "2024-05-05 +0200") (mustToDate "2006-01-02 -0700" "2023-01-09 +0100") | formatHumanDateDiff "%Y years, %M months, %D days" }}
|
||||||
|
< 01 years, 03 months, 25 days
|
||||||
|
```
|
||||||
|
|
||||||
### `group`
|
### `group`
|
||||||
|
|
||||||
Gets matching group specified by index from `match_message` regular expression, when `fallback` is defined, it is used when group has an empty match
|
Gets matching group specified by index from `match_message` regular expression, when `fallback` is defined, it is used when group has an empty match
|
||||||
|
@ -271,6 +284,19 @@ Example:
|
||||||
< test - oops
|
< test - oops
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `humanDateDiff`
|
||||||
|
|
||||||
|
Returns a DateInterval object describing the time difference between a and b in a "human" way of counting the time (2023-02-05 -> 2024-03-05 = 1 Year, 1 Month)
|
||||||
|
|
||||||
|
Syntax: `humanDateDiff <a> <b>`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
# {{ humanDateDiff (mustToDate "2006-01-02 -0700" "2024-05-05 +0200") (mustToDate "2006-01-02 -0700" "2023-01-09 +0100") }}
|
||||||
|
< {1 3 25 23 0 0}
|
||||||
|
```
|
||||||
|
|
||||||
### `idForUsername`
|
### `idForUsername`
|
||||||
|
|
||||||
Returns the user-id for the given username
|
Returns the user-id for the given username
|
||||||
|
@ -441,7 +467,7 @@ Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
# Your int this hour: {{ printf "%.0f" (mulf (seededRandom (list "int" .username (now | date "2006-01-02 15") | join ":")) 100) }}%
|
# Your int this hour: {{ printf "%.0f" (mulf (seededRandom (list "int" .username (now | date "2006-01-02 15") | join ":")) 100) }}%
|
||||||
< Your int this hour: 27%
|
< Your int this hour: 70%
|
||||||
```
|
```
|
||||||
|
|
||||||
### `spotifyCurrentPlaying`
|
### `spotifyCurrentPlaying`
|
||||||
|
|
31
internal/template/date/date.go
Normal file
31
internal/template/date/date.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// Package date adds date-based helper functions for templating
|
||||||
|
package date
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Register provides the plugins.RegisterFunc
|
||||||
|
func Register(args plugins.RegistrationArguments) error {
|
||||||
|
args.RegisterTemplateFunction("humanDateDiff", plugins.GenericTemplateFunctionGetter(NewInterval), plugins.TemplateFuncDocumentation{
|
||||||
|
Description: `Returns a DateInterval object describing the time difference between a and b in a "human" way of counting the time (2023-02-05 -> 2024-03-05 = 1 Year, 1 Month)`,
|
||||||
|
Syntax: "humanDateDiff <a> <b>",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `{{ humanDateDiff (mustToDate "2006-01-02 -0700" "2024-05-05 +0200") (mustToDate "2006-01-02 -0700" "2023-01-09 +0100") }}`,
|
||||||
|
ExpectedOutput: "{1 3 25 23 0 0}",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
args.RegisterTemplateFunction("formatHumanDateDiff", plugins.GenericTemplateFunctionGetter(func(format string, d Interval) string {
|
||||||
|
return d.Format(format)
|
||||||
|
}), plugins.TemplateFuncDocumentation{
|
||||||
|
Description: "Formats a DateInterval object according to the format (%Y, %M, %D, %H, %I, %S for years, months, days, hours, minutes, seconds - Lowercase letters without leading zeros)",
|
||||||
|
Syntax: "formatHumanDateDiff <format> <obj>",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
Template: `{{ humanDateDiff (mustToDate "2006-01-02 -0700" "2024-05-05 +0200") (mustToDate "2006-01-02 -0700" "2023-01-09 +0100") | formatHumanDateDiff "%Y years, %M months, %D days" }}`,
|
||||||
|
ExpectedOutput: "01 years, 03 months, 25 days",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
94
internal/template/date/interval.go
Normal file
94
internal/template/date/interval.go
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
package date
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Interval represents a human-minded diff of two dates which is in
|
||||||
|
// no way interchangeable with a time.Duration: The DateInterval
|
||||||
|
// takes each date component and subtracts them. This causes the
|
||||||
|
// 03/25 to be exactly one month distant from 02/25 even though the
|
||||||
|
// distance would be different than with 03/25 and 04/25 which is
|
||||||
|
// also exactly one month.
|
||||||
|
Interval struct {
|
||||||
|
Years int
|
||||||
|
Months int
|
||||||
|
Days int
|
||||||
|
Hours int
|
||||||
|
Minutes int
|
||||||
|
Seconds int
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewInterval creates an Interval from two given dates
|
||||||
|
func NewInterval(a, b time.Time) (i Interval) {
|
||||||
|
var l, u time.Time
|
||||||
|
if a.Before(b) {
|
||||||
|
l, u = a.UTC(), b.UTC()
|
||||||
|
} else {
|
||||||
|
l, u = b.UTC(), a.UTC()
|
||||||
|
}
|
||||||
|
|
||||||
|
i.Years = u.Year() - l.Year()
|
||||||
|
i.Months = int(u.Month() - l.Month())
|
||||||
|
i.Days = u.Day() - l.Day()
|
||||||
|
i.Hours = u.Hour() - l.Hour()
|
||||||
|
i.Minutes = u.Minute() - l.Minute()
|
||||||
|
i.Seconds = u.Second() - l.Second()
|
||||||
|
|
||||||
|
if i.Seconds < 0 {
|
||||||
|
i.Minutes, i.Seconds = i.Minutes-1, i.Seconds+60 //nolint:gomnd
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Minutes < 0 {
|
||||||
|
i.Hours, i.Minutes = i.Hours-1, i.Minutes+60 //nolint:gomnd
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Hours < 0 {
|
||||||
|
i.Days, i.Hours = i.Days-1, i.Hours+24 //nolint:gomnd
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Days < 0 {
|
||||||
|
// oh boi.
|
||||||
|
i.Months, i.Days = i.Months-1, daysInMonth(u.Year(), int(u.Month())-1)+i.Days
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Months < 0 {
|
||||||
|
i.Years, i.Months = i.Years-1, i.Months+12 //nolint:gomnd
|
||||||
|
}
|
||||||
|
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func daysInMonth(year, month int) int {
|
||||||
|
return time.Date(year, time.Month(month+1), 1, 0, 0, 0, 0, time.Local).Add(-time.Second).Day()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format takes a template string analog to a strftime string and formats
|
||||||
|
// the Interval accordingly:
|
||||||
|
//
|
||||||
|
// %Y / %y = Years with / without leading digit to 2 places
|
||||||
|
// %M / %m = Months with / without leading digit to 2 places
|
||||||
|
// %D / %d = Days with / without leading digit to 2 places
|
||||||
|
// %H / %h = Hours with / without leading digit to 2 places
|
||||||
|
// %I / %i = Minutes with / without leading digit to 2 places
|
||||||
|
// %S / %s = Seconds with / without leading digit to 2 places
|
||||||
|
func (i Interval) Format(tplString string) string {
|
||||||
|
return strings.NewReplacer(
|
||||||
|
"%Y", fmt.Sprintf("%02d", i.Years),
|
||||||
|
"%y", fmt.Sprintf("%d", i.Years),
|
||||||
|
"%M", fmt.Sprintf("%02d", i.Months),
|
||||||
|
"%m", fmt.Sprintf("%d", i.Months),
|
||||||
|
"%D", fmt.Sprintf("%02d", i.Days),
|
||||||
|
"%d", fmt.Sprintf("%d", i.Days),
|
||||||
|
"%H", fmt.Sprintf("%02d", i.Hours),
|
||||||
|
"%h", fmt.Sprintf("%d", i.Hours),
|
||||||
|
"%I", fmt.Sprintf("%02d", i.Minutes),
|
||||||
|
"%i", fmt.Sprintf("%d", i.Minutes),
|
||||||
|
"%S", fmt.Sprintf("%02d", i.Seconds),
|
||||||
|
"%s", fmt.Sprintf("%d", i.Seconds),
|
||||||
|
).Replace(tplString)
|
||||||
|
}
|
134
internal/template/date/interval_test.go
Normal file
134
internal/template/date/interval_test.go
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
package date
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed tzdata
|
||||||
|
var tzDataEuropeBerlin []byte
|
||||||
|
|
||||||
|
//nolint:funlen // This is just a collection of test cases
|
||||||
|
func TestNewInterval(t *testing.T) {
|
||||||
|
tz, err := time.LoadLocationFromTZData("Europe/Berlin", tzDataEuropeBerlin)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for i, tc := range []struct {
|
||||||
|
A, B time.Time
|
||||||
|
Exp Interval
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// Plain and simple: 1 Month
|
||||||
|
A: time.Date(2024, 3, 3, 0, 0, 0, 0, tz),
|
||||||
|
B: time.Date(2024, 2, 3, 0, 0, 0, 0, tz),
|
||||||
|
Exp: Interval{0, 1, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Plain and simple: 1 Month, reversed
|
||||||
|
A: time.Date(2024, 2, 3, 0, 0, 0, 0, tz),
|
||||||
|
B: time.Date(2024, 3, 3, 0, 0, 0, 0, tz),
|
||||||
|
Exp: Interval{0, 1, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Plain and simple: 1 Year, 1 Month
|
||||||
|
A: time.Date(2023, 2, 3, 0, 0, 0, 0, tz),
|
||||||
|
B: time.Date(2024, 3, 3, 0, 0, 0, 0, tz),
|
||||||
|
Exp: Interval{1, 1, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 11 Months, so Year and Month needs to be adjusted
|
||||||
|
A: time.Date(2023, 3, 3, 0, 0, 0, 0, tz),
|
||||||
|
B: time.Date(2024, 2, 3, 0, 0, 0, 0, tz),
|
||||||
|
Exp: Interval{0, 11, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Plain and simple: 2 Days
|
||||||
|
A: time.Date(2024, 3, 3, 0, 0, 0, 0, tz),
|
||||||
|
B: time.Date(2024, 3, 5, 0, 0, 0, 0, tz),
|
||||||
|
Exp: Interval{0, 0, 2, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 1 Month and a few days, so Month and Day needs to be adjusted
|
||||||
|
A: time.Date(2024, 3, 25, 0, 0, 0, 0, tz),
|
||||||
|
B: time.Date(2024, 5, 5, 0, 0, 0, 0, tz),
|
||||||
|
Exp: Interval{0, 1, 9, 23, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 1 Month and a few days, so Month and Day needs to be adjusted
|
||||||
|
A: time.Date(2024, 2, 25, 0, 0, 0, 0, tz),
|
||||||
|
B: time.Date(2024, 4, 5, 0, 0, 0, 0, tz),
|
||||||
|
Exp: Interval{0, 1, 10, 23, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 1 Month and a few days, so Month and Day needs to be adjusted
|
||||||
|
A: time.Date(2024, 1, 25, 0, 0, 0, 0, tz),
|
||||||
|
B: time.Date(2024, 3, 5, 0, 0, 0, 0, tz),
|
||||||
|
Exp: Interval{0, 1, 9, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 1 Month and a few days, so Month and Day needs to be adjusted
|
||||||
|
A: time.Date(2023, 1, 25, 0, 0, 0, 0, tz),
|
||||||
|
B: time.Date(2023, 3, 5, 0, 0, 0, 0, tz),
|
||||||
|
Exp: Interval{0, 1, 8, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 1 Day and a few hours, so Day and Hours needs to be adjusted
|
||||||
|
A: time.Date(2024, 3, 5, 14, 0, 0, 0, tz),
|
||||||
|
B: time.Date(2024, 3, 7, 0, 0, 0, 0, tz),
|
||||||
|
Exp: Interval{0, 0, 1, 10, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 1 Hour and a few minutes, so Hours and Minutes needs to be adjusted
|
||||||
|
A: time.Date(2024, 3, 5, 14, 25, 0, 0, tz),
|
||||||
|
B: time.Date(2024, 3, 5, 16, 12, 0, 0, tz),
|
||||||
|
Exp: Interval{0, 0, 0, 1, 47, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 1 Minute and a few seconds, so Minutes and Seconds needs to be adjusted
|
||||||
|
A: time.Date(2024, 3, 5, 14, 25, 13, 0, tz),
|
||||||
|
B: time.Date(2024, 3, 5, 14, 27, 0, 0, tz),
|
||||||
|
Exp: Interval{0, 0, 0, 0, 1, 47},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Nearly one year but a few seconds, everything needs to be adjusted
|
||||||
|
A: time.Date(2024, 3, 5, 14, 25, 0, 0, tz),
|
||||||
|
B: time.Date(2023, 3, 5, 14, 25, 13, 0, tz),
|
||||||
|
Exp: Interval{0, 11, 28, 23, 59, 47},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Nearly one year but a few seconds, everything needs to be adjusted
|
||||||
|
A: time.Date(2024, 8, 5, 14, 25, 0, 0, tz),
|
||||||
|
B: time.Date(2023, 8, 5, 14, 25, 13, 0, tz),
|
||||||
|
Exp: Interval{0, 11, 30, 23, 59, 47},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Nearly one year but a few seconds, everything needs to be adjusted
|
||||||
|
A: time.Date(2024, 7, 5, 14, 25, 0, 0, tz),
|
||||||
|
B: time.Date(2023, 7, 5, 14, 25, 13, 0, tz),
|
||||||
|
Exp: Interval{0, 11, 29, 23, 59, 47},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Nearly one year but a few seconds, everything needs to be adjusted
|
||||||
|
A: time.Date(2024, 2, 5, 14, 25, 0, 0, tz),
|
||||||
|
B: time.Date(2023, 2, 5, 14, 25, 13, 0, tz),
|
||||||
|
Exp: Interval{0, 11, 30, 23, 59, 47},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
assert.Equal(t,
|
||||||
|
tc.Exp,
|
||||||
|
NewInterval(tc.A, tc.B),
|
||||||
|
fmt.Sprintf("%d: %s -> %s", i, tc.A, tc.B))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatInterval(t *testing.T) {
|
||||||
|
ti := Interval{1, 2, 3, 4, 5, 6}
|
||||||
|
assert.Equal(t,
|
||||||
|
"01 1 years 02 2 months 03 3 days 04 4 hours 05 5 minutes 06 6 seconds",
|
||||||
|
ti.Format("%Y %y years %M %m months %D %d days %H %h hours %I %i minutes %S %s seconds"))
|
||||||
|
}
|
BIN
internal/template/date/tzdata
Normal file
BIN
internal/template/date/tzdata
Normal file
Binary file not shown.
|
@ -46,6 +46,7 @@ import (
|
||||||
"github.com/Luzifer/twitch-bot/v3/internal/apimodules/raffle"
|
"github.com/Luzifer/twitch-bot/v3/internal/apimodules/raffle"
|
||||||
"github.com/Luzifer/twitch-bot/v3/internal/service/access"
|
"github.com/Luzifer/twitch-bot/v3/internal/service/access"
|
||||||
"github.com/Luzifer/twitch-bot/v3/internal/template/api"
|
"github.com/Luzifer/twitch-bot/v3/internal/template/api"
|
||||||
|
"github.com/Luzifer/twitch-bot/v3/internal/template/date"
|
||||||
"github.com/Luzifer/twitch-bot/v3/internal/template/numeric"
|
"github.com/Luzifer/twitch-bot/v3/internal/template/numeric"
|
||||||
"github.com/Luzifer/twitch-bot/v3/internal/template/random"
|
"github.com/Luzifer/twitch-bot/v3/internal/template/random"
|
||||||
"github.com/Luzifer/twitch-bot/v3/internal/template/slice"
|
"github.com/Luzifer/twitch-bot/v3/internal/template/slice"
|
||||||
|
@ -93,6 +94,7 @@ var (
|
||||||
|
|
||||||
// Template functions
|
// Template functions
|
||||||
api.Register,
|
api.Register,
|
||||||
|
date.Register,
|
||||||
numeric.Register,
|
numeric.Register,
|
||||||
random.Register,
|
random.Register,
|
||||||
slice.Register,
|
slice.Register,
|
||||||
|
|
Loading…
Reference in a new issue