mirror of https://github.com/go-gitea/gitea.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
133 lines
3.9 KiB
133 lines
3.9 KiB
// Copyright 2024 The Gitea Authors. All rights reserved. |
|
// SPDX-License-Identifier: MIT |
|
|
|
package templates |
|
|
|
import ( |
|
"fmt" |
|
"html" |
|
"html/template" |
|
"strings" |
|
"time" |
|
|
|
"code.gitea.io/gitea/modules/setting" |
|
"code.gitea.io/gitea/modules/timeutil" |
|
) |
|
|
|
type DateUtils struct{} |
|
|
|
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 dateTimeFormat("short", time) |
|
} |
|
|
|
// AbsoluteLong renders in "January 01, 2006" format |
|
func (du *DateUtils) AbsoluteLong(time any) template.HTML { |
|
return dateTimeFormat("long", time) |
|
} |
|
|
|
// FullTime renders in "Jan 01, 2006 20:33:44" format |
|
func (du *DateUtils) FullTime(time any) template.HTML { |
|
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. |
|
// 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 anyToTime(value any) (t time.Time, isZero bool) { |
|
switch v := value.(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", value)) |
|
} |
|
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("Unsupported format " + 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()) |
|
}
|
|
|