feat: format renewal duration

This commit is contained in:
Fernandez Ludovic 2026-01-21 02:20:42 +01:00
commit 2e00961c2e
2 changed files with 92 additions and 1 deletions

View file

@ -7,9 +7,11 @@ import (
"errors"
"fmt"
"log/slog"
"math"
"math/rand"
"os"
"slices"
"strings"
"time"
"github.com/go-acme/lego/v5/acme/api"
@ -435,7 +437,7 @@ func needRenewalDynamic(x509Cert *x509.Certificate, domain string, now time.Time
}
log.Infof(log.LazySprintf("Skip renewal: The certificate expires at %s, the renewal can be performed in %s.",
x509Cert.NotAfter.Format(time.RFC3339), dueDate.Sub(now)), log.DomainAttr(domain))
x509Cert.NotAfter.Format(time.RFC3339), FormattableDuration(dueDate.Sub(now))), log.DomainAttr(domain))
return false
}
@ -496,3 +498,39 @@ func merge(prevDomains, nextDomains []string) []string {
return prevDomains
}
type FormattableDuration time.Duration
func (f FormattableDuration) String() string {
d := time.Duration(f)
days := int(math.Trunc(d.Hours() / 24))
hours := int(d.Hours()) % 24
minutes := int(d.Minutes()) % 60
seconds := int(d.Seconds()) % 60
ns := int(d.Nanoseconds()) % int(time.Second)
var s strings.Builder
if days > 0 {
s.WriteString(fmt.Sprintf("%dd", days))
}
if hours > 0 {
s.WriteString(fmt.Sprintf("%dh", hours))
}
if minutes > 0 {
s.WriteString(fmt.Sprintf("%dm", minutes))
}
if seconds > 0 {
s.WriteString(fmt.Sprintf("%ds", seconds))
}
if ns > 0 {
s.WriteString(fmt.Sprintf("%dns", ns))
}
return s.String()
}

View file

@ -167,3 +167,56 @@ func Test_needRenewalDynamic(t *testing.T) {
})
}
}
func TestFormattableDuration(t *testing.T) {
testCases := []struct {
desc string
date time.Time
duration time.Duration
expected string
}{
{
desc: "all",
duration: 47*time.Hour + 3*time.Minute + 8*time.Second + 1234567890*time.Nanosecond,
expected: "1d23h3m9s234567890ns",
},
{
desc: "without nanoseconds",
duration: 47*time.Hour + 3*time.Minute + 8*time.Second,
expected: "1d23h3m8s",
},
{
desc: "without seconds",
duration: 47*time.Hour + 3*time.Minute + 2*time.Nanosecond,
expected: "1d23h3m2ns",
},
{
desc: "without minutes",
duration: 47*time.Hour + 8*time.Second + 2*time.Nanosecond,
expected: "1d23h8s2ns",
},
{
desc: "without hours",
duration: 3*time.Minute + 8*time.Second + 2*time.Nanosecond,
expected: "3m8s2ns",
},
{
desc: "only hours",
duration: 23 * time.Hour,
expected: "23h",
},
{
desc: "only days",
duration: 48 * time.Hour,
expected: "2d",
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
assert.Equal(t, test.expected, FormattableDuration(test.duration).String())
})
}
}