mirror of
https://github.com/Luzifer/twitch-bot.git
synced 2024-12-30 00:21:16 +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
|
||||
```
|
||||
|
||||
### `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`
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
### `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`
|
||||
|
||||
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: 27%
|
||||
< Your int this hour: 70%
|
||||
```
|
||||
|
||||
### `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/service/access"
|
||||
"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/random"
|
||||
"github.com/Luzifer/twitch-bot/v3/internal/template/slice"
|
||||
|
@ -93,6 +94,7 @@ var (
|
|||
|
||||
// Template functions
|
||||
api.Register,
|
||||
date.Register,
|
||||
numeric.Register,
|
||||
random.Register,
|
||||
slice.Register,
|
||||
|
|
Loading…
Reference in a new issue