Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Refactor DateUtils and merge TimeSince #32409

Merged
merged 5 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions modules/templates/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/svg"
"code.gitea.io/gitea/modules/templates/eval"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/gitdiff"
"code.gitea.io/gitea/services/webtheme"
Expand Down Expand Up @@ -67,16 +66,18 @@ func NewFuncMap() template.FuncMap {

// -----------------------------------------------------------------
// time / number / format
"FileSize": base.FileSize,
"CountFmt": base.FormatNumberSI,
"TimeSince": timeutil.TimeSince,
"TimeSinceUnix": timeutil.TimeSinceUnix,
"DateTime": dateTimeLegacy, // for backward compatibility only, do not use it anymore
"Sec2Time": util.SecToTime,
"FileSize": base.FileSize,
"CountFmt": base.FormatNumberSI,
"Sec2Time": util.SecToTime,
"LoadTimes": func(startTime time.Time) string {
return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms"
},

// for backward compatibility only, do not use them anymore
"TimeSince": timeSinceLegacy,
"TimeSinceUnix": timeSinceLegacy,
"DateTime": dateTimeLegacy,

// -----------------------------------------------------------------
// setting
"AppName": func() string {
Expand Down
111 changes: 101 additions & 10 deletions modules/templates/util_date.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,40 @@
package templates

import (
"context"
"fmt"
"html"
"html/template"
"strings"
"time"

"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/translation"
)

type DateUtils struct {
ctx context.Context
}
type DateUtils struct{}

func NewDateUtils(ctx context.Context) *DateUtils {
return &DateUtils{ctx}
func NewDateUtils() *DateUtils {
return (*DateUtils)(nil) // the util is stateless, and we do not need to create an instance
}

// AbsoluteShort renders in "Jan 01, 2006" format
func (du *DateUtils) AbsoluteShort(time any) template.HTML {
return timeutil.DateTime("short", time)
return dateTimeFormat("short", time)
}

// AbsoluteLong renders in "January 01, 2006" format
func (du *DateUtils) AbsoluteLong(time any) template.HTML {
return timeutil.DateTime("short", time)
return dateTimeFormat("short", time)
}

// FullTime renders in "Jan 01, 2006 20:33:44" format
func (du *DateUtils) FullTime(time any) template.HTML {
return timeutil.DateTime("full", time)
return dateTimeFormat("full", time)
}

func (du *DateUtils) TimeSince(time any) template.HTML {
return TimeSince(time)
}

// ParseLegacy parses the datetime in legacy format, eg: "2016-01-02" in server's timezone.
Expand All @@ -56,5 +61,91 @@ func dateTimeLegacy(format string, datetime any, _ ...string) template.HTML {
if s, ok := datetime.(string); ok {
datetime = parseLegacy(s)
}
return timeutil.DateTime(format, datetime)
return dateTimeFormat(format, datetime)
}

func timeSinceLegacy(time any, _ translation.Locale) template.HTML {
if !setting.IsProd || setting.IsInTesting {
panic("timeSinceLegacy is for backward compatibility only, do not use it in new code")
}
return TimeSince(time)
}

func anyToTime(any any) (t time.Time, isZero bool) {
switch v := any.(type) {
case nil:
// it is zero
case *time.Time:
if v != nil {
t = *v
}
case time.Time:
t = v
case timeutil.TimeStamp:
t = v.AsTime()
case timeutil.TimeStampNano:
t = v.AsTime()
case int:
t = timeutil.TimeStamp(v).AsTime()
case int64:
t = timeutil.TimeStamp(v).AsTime()
default:
panic(fmt.Sprintf("Unsupported time type %T", any))
}
return t, t.IsZero() || t.Unix() == 0
}

func dateTimeFormat(format string, datetime any) template.HTML {
t, isZero := anyToTime(datetime)
if isZero {
return "-"
}
var textEscaped string
datetimeEscaped := html.EscapeString(t.Format(time.RFC3339))
if format == "full" {
textEscaped = html.EscapeString(t.Format("2006-01-02 15:04:05 -07:00"))
} else {
textEscaped = html.EscapeString(t.Format("2006-01-02"))
}

attrs := []string{`weekday=""`, `year="numeric"`}
switch format {
case "short", "long": // date only
attrs = append(attrs, `month="`+format+`"`, `day="numeric"`)
return template.HTML(fmt.Sprintf(`<absolute-date %s date="%s">%s</absolute-date>`, strings.Join(attrs, " "), datetimeEscaped, textEscaped))
case "full": // full date including time
attrs = append(attrs, `format="datetime"`, `month="short"`, `day="numeric"`, `hour="numeric"`, `minute="numeric"`, `second="numeric"`, `data-tooltip-content`, `data-tooltip-interactive="true"`)
return template.HTML(fmt.Sprintf(`<relative-time %s datetime="%s">%s</relative-time>`, strings.Join(attrs, " "), datetimeEscaped, textEscaped))
default:
panic(fmt.Sprintf("Unsupported format %s", format))
}
}

func timeSinceTo(then any, now time.Time) template.HTML {
thenTime, isZero := anyToTime(then)
if isZero {
return "-"
}

friendlyText := thenTime.Format("2006-01-02 15:04:05 -07:00")

// document: https://github.com/github/relative-time-element
attrs := `tense="past"`
isFuture := now.Before(thenTime)
if isFuture {
attrs = `tense="future"`
}

// declare data-tooltip-content attribute to switch from "title" tooltip to "tippy" tooltip
htm := fmt.Sprintf(`<relative-time prefix="" %s datetime="%s" data-tooltip-content data-tooltip-interactive="true">%s</relative-time>`,
attrs, thenTime.Format(time.RFC3339), friendlyText)
return template.HTML(htm)
}

// TimeSince renders relative time HTML given a time
func TimeSince(then any) template.HTML {
if setting.UI.PreferredTimestampTense == "absolute" {
return dateTimeFormat("full", then)
}
return timeSinceTo(then, time.Now())
}
23 changes: 22 additions & 1 deletion modules/templates/util_date_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func TestDateTime(t *testing.T) {
defer test.MockVariableValue(&setting.DefaultUILocation, testTz)()
defer test.MockVariableValue(&setting.IsInTesting, false)()

du := NewDateUtils(nil)
du := NewDateUtils()

refTimeStr := "2018-01-01T00:00:00Z"
refDateStr := "2018-01-01"
Expand Down Expand Up @@ -49,3 +49,24 @@ func TestDateTime(t *testing.T) {
actual = du.FullTime(refTimeStamp)
assert.EqualValues(t, `<relative-time weekday="" year="numeric" format="datetime" month="short" day="numeric" hour="numeric" minute="numeric" second="numeric" data-tooltip-content data-tooltip-interactive="true" datetime="2017-12-31T19:00:00-05:00">2017-12-31 19:00:00 -05:00</relative-time>`, actual)
}

func TestTimeSince(t *testing.T) {
testTz, _ := time.LoadLocation("America/New_York")
defer test.MockVariableValue(&setting.DefaultUILocation, testTz)()
defer test.MockVariableValue(&setting.IsInTesting, false)()

du := NewDateUtils()
assert.EqualValues(t, "-", du.TimeSince(nil))

refTimeStr := "2018-01-01T00:00:00Z"
refTime, _ := time.Parse(time.RFC3339, refTimeStr)

actual := du.TimeSince(refTime)
assert.EqualValues(t, `<relative-time prefix="" tense="past" datetime="2018-01-01T00:00:00Z" data-tooltip-content data-tooltip-interactive="true">2018-01-01 00:00:00 +00:00</relative-time>`, actual)

actual = timeSinceTo(&refTime, time.Time{})
assert.EqualValues(t, `<relative-time prefix="" tense="future" datetime="2018-01-01T00:00:00Z" data-tooltip-content data-tooltip-interactive="true">2018-01-01 00:00:00 +00:00</relative-time>`, actual)

actual = timeSinceLegacy(timeutil.TimeStampNano(refTime.UnixNano()), nil)
assert.EqualValues(t, `<relative-time prefix="" tense="past" datetime="2017-12-31T19:00:00-05:00" data-tooltip-content data-tooltip-interactive="true">2017-12-31 19:00:00 -05:00</relative-time>`, actual)
}
60 changes: 0 additions & 60 deletions modules/timeutil/datetime.go

This file was deleted.

41 changes: 2 additions & 39 deletions modules/timeutil/since.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,9 @@
package timeutil

import (
"fmt"
"html/template"
"strings"
"time"

"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/translation"
)

Expand Down Expand Up @@ -81,16 +78,11 @@ func computeTimeDiffFloor(diff int64, lang translation.Locale) (int64, string) {
return diff, diffStr
}

// MinutesToFriendly returns a user friendly string with number of minutes
// MinutesToFriendly returns a user-friendly string with number of minutes
// converted to hours and minutes.
func MinutesToFriendly(minutes int, lang translation.Locale) string {
duration := time.Duration(minutes) * time.Minute
return TimeSincePro(time.Now().Add(-duration), lang)
}

// TimeSincePro calculates the time interval and generate full user-friendly string.
func TimeSincePro(then time.Time, lang translation.Locale) string {
return timeSincePro(then, time.Now(), lang)
return timeSincePro(time.Now().Add(-duration), time.Now(), lang)
}

func timeSincePro(then, now time.Time, lang translation.Locale) string {
Expand All @@ -114,32 +106,3 @@ func timeSincePro(then, now time.Time, lang translation.Locale) string {
}
return strings.TrimPrefix(timeStr, ", ")
}

func timeSinceUnix(then, now time.Time, _ translation.Locale) template.HTML {
friendlyText := then.Format("2006-01-02 15:04:05 -07:00")

// document: https://github.com/github/relative-time-element
attrs := `tense="past"`
isFuture := now.Before(then)
if isFuture {
attrs = `tense="future"`
}

// declare data-tooltip-content attribute to switch from "title" tooltip to "tippy" tooltip
htm := fmt.Sprintf(`<relative-time prefix="" %s datetime="%s" data-tooltip-content data-tooltip-interactive="true">%s</relative-time>`,
attrs, then.Format(time.RFC3339), friendlyText)
return template.HTML(htm)
}

// TimeSince renders relative time HTML given a time.Time
func TimeSince(then time.Time, lang translation.Locale) template.HTML {
if setting.UI.PreferredTimestampTense == "absolute" {
return DateTime("full", then)
}
return timeSinceUnix(then, time.Now(), lang)
}

// TimeSinceUnix renders relative time HTML given a TimeStamp
func TimeSinceUnix(then TimeStamp, lang translation.Locale) template.HTML {
return TimeSince(then.AsLocalTime(), lang)
}
3 changes: 1 addition & 2 deletions routers/web/repo/blame.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
files_service "code.gitea.io/gitea/services/repository/files"
Expand Down Expand Up @@ -280,7 +279,7 @@ func renderBlame(ctx *context.Context, blameParts []*git.BlamePart, commitNames
commitCnt++

// User avatar image
commitSince := timeutil.TimeSinceUnix(timeutil.TimeStamp(commit.Author.When.Unix()), ctx.Locale)
commitSince := templates.TimeSince(commit.Author.When)

var avatar string
if commit.User != nil {
Expand Down
5 changes: 2 additions & 3 deletions routers/web/repo/issue_content_history.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/services/context"

"github.com/sergi/go-diff/diffmatchpatch"
Expand Down Expand Up @@ -73,10 +72,10 @@ func GetContentHistoryList(ctx *context.Context) {
class := avatars.DefaultAvatarClass + " tw-mr-2"
name := html.EscapeString(username)
avatarHTML := string(templates.AvatarHTML(src, 28, class, username))
timeSinceText := string(timeutil.TimeSinceUnix(item.EditedUnix, ctx.Locale))
timeSinceHTML := string(templates.TimeSince(item.EditedUnix))

results = append(results, map[string]any{
"name": avatarHTML + "<strong>" + name + "</strong> " + actionText + " " + timeSinceText,
"name": avatarHTML + "<strong>" + name + "</strong> " + actionText + " " + timeSinceHTML,
"value": item.HistoryID,
})
}
Expand Down
1 change: 0 additions & 1 deletion services/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ func NewTemplateContextForWeb(ctx *Context) TemplateContext {
tmplCtx := NewTemplateContext(ctx)
tmplCtx["Locale"] = ctx.Base.Locale
tmplCtx["AvatarUtils"] = templates.NewAvatarUtils(ctx)
tmplCtx["DateUtils"] = templates.NewDateUtils(ctx)
tmplCtx["RootData"] = ctx.Data
tmplCtx["Consts"] = map[string]any{
"RepoUnitTypeCode": unit.TypeCode,
Expand Down
4 changes: 2 additions & 2 deletions templates/admin/auth/list.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
<td><a href="{{AppSubUrl}}/-/admin/auths/{{.ID}}">{{.Name}}</a></td>
<td>{{.TypeName}}</td>
<td>{{svg (Iif .IsActive "octicon-check" "octicon-x")}}</td>
<td>{{ctx.DateUtils.AbsoluteShort .UpdatedUnix}}</td>
<td>{{ctx.DateUtils.AbsoluteShort .CreatedUnix}}</td>
<td>{{DateUtils.AbsoluteShort .UpdatedUnix}}</td>
<td>{{DateUtils.AbsoluteShort .CreatedUnix}}</td>
<td><a href="{{AppSubUrl}}/-/admin/auths/{{.ID}}">{{svg "octicon-pencil"}}</a></td>
</tr>
{{end}}
Expand Down
Loading
Loading