Replace DateTime with proper functions (#32402)

Follow #32383

This PR cleans up the "Deadline" usages in templates, make them call
`ParseLegacy` first to get a `Time` struct then display by `DateUtils`.

Now it should be pretty clear how "deadline string" works, it makes it
possible to do further refactoring and correcting.
This commit is contained in:
wxiaoguang 2024-11-03 05:04:53 +08:00 committed by GitHub
parent e524f63d58
commit 259811617b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 59 additions and 35 deletions

View File

@ -54,7 +54,7 @@ func NewFuncMap() template.FuncMap {
"StringUtils": NewStringUtils, "StringUtils": NewStringUtils,
"SliceUtils": NewSliceUtils, "SliceUtils": NewSliceUtils,
"JsonUtils": NewJsonUtils, "JsonUtils": NewJsonUtils,
"DateUtils": NewDateUtils, // TODO: to be replaced by DateUtils "DateUtils": NewDateUtils,
// ----------------------------------------------------------------- // -----------------------------------------------------------------
// svg / avatar / icon / color // svg / avatar / icon / color
@ -71,7 +71,7 @@ func NewFuncMap() template.FuncMap {
"CountFmt": base.FormatNumberSI, "CountFmt": base.FormatNumberSI,
"TimeSince": timeutil.TimeSince, "TimeSince": timeutil.TimeSince,
"TimeSinceUnix": timeutil.TimeSinceUnix, "TimeSinceUnix": timeutil.TimeSinceUnix,
"DateTime": timeutil.DateTime, "DateTime": dateTimeLegacy, // for backward compatibility only, do not use it anymore
"Sec2Time": util.SecToTime, "Sec2Time": util.SecToTime,
"LoadTimes": func(startTime time.Time) string { "LoadTimes": func(startTime time.Time) string {
return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms" return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms"

View File

@ -6,7 +6,9 @@ package templates
import ( import (
"context" "context"
"html/template" "html/template"
"time"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
) )
@ -32,3 +34,27 @@ func (du *DateUtils) AbsoluteLong(time any) template.HTML {
func (du *DateUtils) FullTime(time any) template.HTML { func (du *DateUtils) FullTime(time any) template.HTML {
return timeutil.DateTime("full", time) return timeutil.DateTime("full", time)
} }
// ParseLegacy parses the datetime in legacy format, eg: "2016-01-02" in server's timezone.
// It shouldn't be used in new code. New code should use Time or TimeStamp as much as possible.
func (du *DateUtils) ParseLegacy(datetime string) time.Time {
return parseLegacy(datetime)
}
func parseLegacy(datetime string) time.Time {
t, err := time.Parse(time.RFC3339, datetime)
if err != nil {
t, _ = time.ParseInLocation(time.DateOnly, datetime, setting.DefaultUILocation)
}
return t
}
func dateTimeLegacy(format string, datetime any, _ ...string) template.HTML {
if !setting.IsProd || setting.IsInTesting {
panic("dateTimeLegacy is for backward compatibility only, do not use it in new code")
}
if s, ok := datetime.(string); ok {
datetime = parseLegacy(s)
}
return timeutil.DateTime(format, datetime)
}

View File

@ -1,7 +1,7 @@
// Copyright 2023 The Gitea Authors. All rights reserved. // Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
package timeutil package templates
import ( import (
"testing" "testing"
@ -9,6 +9,7 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/timeutil"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -16,32 +17,35 @@ import (
func TestDateTime(t *testing.T) { func TestDateTime(t *testing.T) {
testTz, _ := time.LoadLocation("America/New_York") testTz, _ := time.LoadLocation("America/New_York")
defer test.MockVariableValue(&setting.DefaultUILocation, testTz)() defer test.MockVariableValue(&setting.DefaultUILocation, testTz)()
defer test.MockVariableValue(&setting.IsInTesting, false)()
du := NewDateUtils(nil)
refTimeStr := "2018-01-01T00:00:00Z" refTimeStr := "2018-01-01T00:00:00Z"
refDateStr := "2018-01-01" refDateStr := "2018-01-01"
refTime, _ := time.Parse(time.RFC3339, refTimeStr) refTime, _ := time.Parse(time.RFC3339, refTimeStr)
refTimeStamp := TimeStamp(refTime.Unix()) refTimeStamp := timeutil.TimeStamp(refTime.Unix())
assert.EqualValues(t, "-", DateTime("short", nil)) assert.EqualValues(t, "-", du.AbsoluteShort(nil))
assert.EqualValues(t, "-", DateTime("short", 0)) assert.EqualValues(t, "-", du.AbsoluteShort(0))
assert.EqualValues(t, "-", DateTime("short", time.Time{})) assert.EqualValues(t, "-", du.AbsoluteShort(time.Time{}))
assert.EqualValues(t, "-", DateTime("short", TimeStamp(0))) assert.EqualValues(t, "-", du.AbsoluteShort(timeutil.TimeStamp(0)))
actual := DateTime("short", "invalid") actual := dateTimeLegacy("short", "invalid")
assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="invalid">invalid</absolute-date>`, actual) assert.EqualValues(t, `-`, actual)
actual = DateTime("short", refTimeStr) actual = dateTimeLegacy("short", refTimeStr)
assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01T00:00:00Z">2018-01-01T00:00:00Z</absolute-date>`, actual)
actual = DateTime("short", refTime)
assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01T00:00:00Z">2018-01-01</absolute-date>`, actual) assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01T00:00:00Z">2018-01-01</absolute-date>`, actual)
actual = DateTime("short", refDateStr) actual = du.AbsoluteShort(refTime)
assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01">2018-01-01</absolute-date>`, actual) assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01T00:00:00Z">2018-01-01</absolute-date>`, actual)
actual = DateTime("short", refTimeStamp) actual = dateTimeLegacy("short", refDateStr)
assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01T00:00:00-05:00">2018-01-01</absolute-date>`, actual)
actual = du.AbsoluteShort(refTimeStamp)
assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2017-12-31T19:00:00-05:00">2017-12-31</absolute-date>`, actual) assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2017-12-31T19:00:00-05:00">2017-12-31</absolute-date>`, actual)
actual = DateTime("full", refTimeStamp) 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) 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)
} }

View File

@ -12,9 +12,7 @@ import (
) )
// DateTime renders an absolute time HTML element by datetime. // DateTime renders an absolute time HTML element by datetime.
func DateTime(format string, datetime any, extraAttrs ...string) template.HTML { func DateTime(format string, datetime any) template.HTML {
// TODO: remove the extraAttrs argument, it's not used in any call to DateTime
if p, ok := datetime.(*time.Time); ok { if p, ok := datetime.(*time.Time); ok {
datetime = *p datetime = *p
} }
@ -34,9 +32,6 @@ func DateTime(format string, datetime any, extraAttrs ...string) template.HTML {
switch v := datetime.(type) { switch v := datetime.(type) {
case nil: case nil:
return "-" return "-"
case string:
datetimeEscaped = html.EscapeString(v)
textEscaped = datetimeEscaped
case time.Time: case time.Time:
if v.IsZero() || v.Unix() == 0 { if v.IsZero() || v.Unix() == 0 {
return "-" return "-"
@ -51,10 +46,7 @@ func DateTime(format string, datetime any, extraAttrs ...string) template.HTML {
panic(fmt.Sprintf("Unsupported time type %T", datetime)) panic(fmt.Sprintf("Unsupported time type %T", datetime))
} }
attrs := make([]string, 0, 10+len(extraAttrs)) attrs := []string{`weekday=""`, `year="numeric"`}
attrs = append(attrs, extraAttrs...)
attrs = append(attrs, `weekday=""`, `year="numeric"`)
switch format { switch format {
case "short", "long": // date only case "short", "long": // date only
attrs = append(attrs, `month="`+format+`"`, `day="numeric"`) attrs = append(attrs, `month="`+format+`"`, `day="numeric"`)

View File

@ -38,7 +38,7 @@
{{if .Milestone.DeadlineString}} {{if .Milestone.DeadlineString}}
<span{{if .IsOverdue}} class="text red"{{end}}> <span{{if .IsOverdue}} class="text red"{{end}}>
{{svg "octicon-calendar"}} {{svg "octicon-calendar"}}
{{DateTime "short" .Milestone.DeadlineString}} {{ctx.DateUtils.AbsoluteShort (.Milestone.DeadlineString|ctx.DateUtils.ParseLegacy)}}
</span> </span>
{{else}} {{else}}
{{svg "octicon-calendar"}} {{svg "octicon-calendar"}}

View File

@ -59,7 +59,7 @@
{{if .DeadlineString}} {{if .DeadlineString}}
<span class="flex-text-inline {{if .IsOverdue}}text red{{end}}"> <span class="flex-text-inline {{if .IsOverdue}}text red{{end}}">
{{svg "octicon-calendar" 14}} {{svg "octicon-calendar" 14}}
{{DateTime "short" .DeadlineString}} {{ctx.DateUtils.AbsoluteShort (.DeadlineString|ctx.DateUtils.ParseLegacy)}}
</span> </span>
{{else}} {{else}}
{{svg "octicon-calendar" 14}} {{svg "octicon-calendar" 14}}

View File

@ -296,7 +296,8 @@
{{template "shared/user/avatarlink" dict "user" .Poster}} {{template "shared/user/avatarlink" dict "user" .Poster}}
<span class="text grey muted-links"> <span class="text grey muted-links">
{{template "shared/user/authorlink" .Poster}} {{template "shared/user/authorlink" .Poster}}
{{ctx.Locale.Tr "repo.issues.due_date_added" (DateTime "long" .Content) $createdStr}} {{$dueDate := ctx.DateUtils.AbsoluteLong (.Content|ctx.DateUtils.ParseLegacy)}}
{{ctx.Locale.Tr "repo.issues.due_date_added" $dueDate $createdStr}}
</span> </span>
</div> </div>
{{else if eq .Type 17}} {{else if eq .Type 17}}
@ -307,8 +308,8 @@
{{template "shared/user/authorlink" .Poster}} {{template "shared/user/authorlink" .Poster}}
{{$parsedDeadline := StringUtils.Split .Content "|"}} {{$parsedDeadline := StringUtils.Split .Content "|"}}
{{if eq (len $parsedDeadline) 2}} {{if eq (len $parsedDeadline) 2}}
{{$from := DateTime "long" (index $parsedDeadline 1)}} {{$to := ctx.DateUtils.AbsoluteLong ((index $parsedDeadline 0)|ctx.DateUtils.ParseLegacy)}}
{{$to := DateTime "long" (index $parsedDeadline 0)}} {{$from := ctx.DateUtils.AbsoluteLong ((index $parsedDeadline 1)|ctx.DateUtils.ParseLegacy)}}
{{ctx.Locale.Tr "repo.issues.due_date_modified" $to $from $createdStr}} {{ctx.Locale.Tr "repo.issues.due_date_modified" $to $from $createdStr}}
{{end}} {{end}}
</span> </span>
@ -319,7 +320,8 @@
{{template "shared/user/avatarlink" dict "user" .Poster}} {{template "shared/user/avatarlink" dict "user" .Poster}}
<span class="text grey muted-links"> <span class="text grey muted-links">
{{template "shared/user/authorlink" .Poster}} {{template "shared/user/authorlink" .Poster}}
{{ctx.Locale.Tr "repo.issues.due_date_remove" (DateTime "long" .Content) $createdStr}} {{$dueDate := ctx.DateUtils.AbsoluteLong (.Content|ctx.DateUtils.ParseLegacy)}}
{{ctx.Locale.Tr "repo.issues.due_date_remove" $dueDate $createdStr}}
</span> </span>
</div> </div>
{{else if eq .Type 19}} {{else if eq .Type 19}}

View File

@ -116,7 +116,7 @@
{{if .DeadlineString}} {{if .DeadlineString}}
<span{{if .IsOverdue}} class="text red"{{end}}> <span{{if .IsOverdue}} class="text red"{{end}}>
{{svg "octicon-calendar" 14}} {{svg "octicon-calendar" 14}}
{{DateTime "short" .DeadlineString}} {{ctx.DateUtils.AbsoluteShort (.DeadlineString|ctx.DateUtils.ParseLegacy)}}
</span> </span>
{{else}} {{else}}
{{svg "octicon-calendar" 14}} {{svg "octicon-calendar" 14}}