4
4
package templates
5
5
6
6
import (
7
- "context"
7
+ "fmt"
8
+ "html"
8
9
"html/template"
10
+ "strings"
9
11
"time"
10
12
11
13
"code.gitea.io/gitea/modules/setting"
12
14
"code.gitea.io/gitea/modules/timeutil"
15
+ "code.gitea.io/gitea/modules/translation"
13
16
)
14
17
15
- type DateUtils struct {
16
- ctx context.Context
17
- }
18
+ type DateUtils struct {}
18
19
19
- func NewDateUtils (ctx context. Context ) * DateUtils {
20
- return & DateUtils { ctx }
20
+ func NewDateUtils () * DateUtils {
21
+ return ( * DateUtils )( nil ) // the util is stateless, and we do not need to create an instance
21
22
}
22
23
23
24
// AbsoluteShort renders in "Jan 01, 2006" format
24
25
func (du * DateUtils ) AbsoluteShort (time any ) template.HTML {
25
- return timeutil . DateTime ("short" , time )
26
+ return dateTimeFormat ("short" , time )
26
27
}
27
28
28
29
// AbsoluteLong renders in "January 01, 2006" format
29
30
func (du * DateUtils ) AbsoluteLong (time any ) template.HTML {
30
- return timeutil . DateTime ("short" , time )
31
+ return dateTimeFormat ("short" , time )
31
32
}
32
33
33
34
// FullTime renders in "Jan 01, 2006 20:33:44" format
34
35
func (du * DateUtils ) FullTime (time any ) template.HTML {
35
- return timeutil .DateTime ("full" , time )
36
+ return dateTimeFormat ("full" , time )
37
+ }
38
+
39
+ func (du * DateUtils ) TimeSince (time any ) template.HTML {
40
+ return TimeSince (time )
36
41
}
37
42
38
43
// ParseLegacy parses the datetime in legacy format, eg: "2016-01-02" in server's timezone.
@@ -56,5 +61,91 @@ func dateTimeLegacy(format string, datetime any, _ ...string) template.HTML {
56
61
if s , ok := datetime .(string ); ok {
57
62
datetime = parseLegacy (s )
58
63
}
59
- return timeutil .DateTime (format , datetime )
64
+ return dateTimeFormat (format , datetime )
65
+ }
66
+
67
+ func timeSinceLegacy (time any , _ translation.Locale ) template.HTML {
68
+ if ! setting .IsProd || setting .IsInTesting {
69
+ panic ("timeSinceLegacy is for backward compatibility only, do not use it in new code" )
70
+ }
71
+ return TimeSince (time )
72
+ }
73
+
74
+ func anyToTime (any any ) (t time.Time , isZero bool ) {
75
+ switch v := any .(type ) {
76
+ case nil :
77
+ // it is zero
78
+ case * time.Time :
79
+ if v != nil {
80
+ t = * v
81
+ }
82
+ case time.Time :
83
+ t = v
84
+ case timeutil.TimeStamp :
85
+ t = v .AsTime ()
86
+ case timeutil.TimeStampNano :
87
+ t = v .AsTime ()
88
+ case int :
89
+ t = timeutil .TimeStamp (v ).AsTime ()
90
+ case int64 :
91
+ t = timeutil .TimeStamp (v ).AsTime ()
92
+ default :
93
+ panic (fmt .Sprintf ("Unsupported time type %T" , any ))
94
+ }
95
+ return t , t .IsZero () || t .Unix () == 0
96
+ }
97
+
98
+ func dateTimeFormat (format string , datetime any ) template.HTML {
99
+ t , isZero := anyToTime (datetime )
100
+ if isZero {
101
+ return "-"
102
+ }
103
+ var textEscaped string
104
+ datetimeEscaped := html .EscapeString (t .Format (time .RFC3339 ))
105
+ if format == "full" {
106
+ textEscaped = html .EscapeString (t .Format ("2006-01-02 15:04:05 -07:00" ))
107
+ } else {
108
+ textEscaped = html .EscapeString (t .Format ("2006-01-02" ))
109
+ }
110
+
111
+ attrs := []string {`weekday=""` , `year="numeric"` }
112
+ switch format {
113
+ case "short" , "long" : // date only
114
+ attrs = append (attrs , `month="` + format + `"` , `day="numeric"` )
115
+ return template .HTML (fmt .Sprintf (`<absolute-date %s date="%s">%s</absolute-date>` , strings .Join (attrs , " " ), datetimeEscaped , textEscaped ))
116
+ case "full" : // full date including time
117
+ attrs = append (attrs , `format="datetime"` , `month="short"` , `day="numeric"` , `hour="numeric"` , `minute="numeric"` , `second="numeric"` , `data-tooltip-content` , `data-tooltip-interactive="true"` )
118
+ return template .HTML (fmt .Sprintf (`<relative-time %s datetime="%s">%s</relative-time>` , strings .Join (attrs , " " ), datetimeEscaped , textEscaped ))
119
+ default :
120
+ panic (fmt .Sprintf ("Unsupported format %s" , format ))
121
+ }
122
+ }
123
+
124
+ func timeSinceTo (then any , now time.Time ) template.HTML {
125
+ thenTime , isZero := anyToTime (then )
126
+ if isZero {
127
+ return "-"
128
+ }
129
+
130
+ friendlyText := thenTime .Format ("2006-01-02 15:04:05 -07:00" )
131
+
132
+ // document: https://github.com/github/relative-time-element
133
+ attrs := `tense="past"`
134
+ isFuture := now .Before (thenTime )
135
+ if isFuture {
136
+ attrs = `tense="future"`
137
+ }
138
+
139
+ // declare data-tooltip-content attribute to switch from "title" tooltip to "tippy" tooltip
140
+ htm := fmt .Sprintf (`<relative-time prefix="" %s datetime="%s" data-tooltip-content data-tooltip-interactive="true">%s</relative-time>` ,
141
+ attrs , thenTime .Format (time .RFC3339 ), friendlyText )
142
+ return template .HTML (htm )
143
+ }
144
+
145
+ // TimeSince renders relative time HTML given a time
146
+ func TimeSince (then any ) template.HTML {
147
+ if setting .UI .PreferredTimestampTense == "absolute" {
148
+ return dateTimeFormat ("full" , then )
149
+ }
150
+ return timeSinceTo (then , time .Now ())
60
151
}
0 commit comments