gitea源码

util_date.go 3.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. // Copyright 2024 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package templates
  4. import (
  5. "fmt"
  6. "html"
  7. "html/template"
  8. "strings"
  9. "time"
  10. "code.gitea.io/gitea/modules/setting"
  11. "code.gitea.io/gitea/modules/timeutil"
  12. )
  13. type DateUtils struct{}
  14. func NewDateUtils() *DateUtils {
  15. return (*DateUtils)(nil) // the util is stateless, and we do not need to create an instance
  16. }
  17. // AbsoluteShort renders in "Jan 01, 2006" format
  18. func (du *DateUtils) AbsoluteShort(time any) template.HTML {
  19. return dateTimeFormat("short", time)
  20. }
  21. // AbsoluteLong renders in "January 01, 2006" format
  22. func (du *DateUtils) AbsoluteLong(time any) template.HTML {
  23. return dateTimeFormat("long", time)
  24. }
  25. // FullTime renders in "Jan 01, 2006 20:33:44" format
  26. func (du *DateUtils) FullTime(time any) template.HTML {
  27. return dateTimeFormat("full", time)
  28. }
  29. func (du *DateUtils) TimeSince(time any) template.HTML {
  30. return TimeSince(time)
  31. }
  32. // ParseLegacy parses the datetime in legacy format, eg: "2016-01-02" in server's timezone.
  33. // It shouldn't be used in new code. New code should use Time or TimeStamp as much as possible.
  34. func (du *DateUtils) ParseLegacy(datetime string) time.Time {
  35. return parseLegacy(datetime)
  36. }
  37. func parseLegacy(datetime string) time.Time {
  38. t, err := time.Parse(time.RFC3339, datetime)
  39. if err != nil {
  40. t, _ = time.ParseInLocation(time.DateOnly, datetime, setting.DefaultUILocation)
  41. }
  42. return t
  43. }
  44. func anyToTime(value any) (t time.Time, isZero bool) {
  45. switch v := value.(type) {
  46. case nil:
  47. // it is zero
  48. case *time.Time:
  49. if v != nil {
  50. t = *v
  51. }
  52. case time.Time:
  53. t = v
  54. case timeutil.TimeStamp:
  55. t = v.AsTime()
  56. case timeutil.TimeStampNano:
  57. t = v.AsTime()
  58. case int:
  59. t = timeutil.TimeStamp(v).AsTime()
  60. case int64:
  61. t = timeutil.TimeStamp(v).AsTime()
  62. default:
  63. panic(fmt.Sprintf("Unsupported time type %T", value))
  64. }
  65. return t, t.IsZero() || t.Unix() == 0
  66. }
  67. func dateTimeFormat(format string, datetime any) template.HTML {
  68. t, isZero := anyToTime(datetime)
  69. if isZero {
  70. return "-"
  71. }
  72. var textEscaped string
  73. datetimeEscaped := html.EscapeString(t.Format(time.RFC3339))
  74. if format == "full" {
  75. textEscaped = html.EscapeString(t.Format("2006-01-02 15:04:05 -07:00"))
  76. } else {
  77. textEscaped = html.EscapeString(t.Format("2006-01-02"))
  78. }
  79. attrs := []string{`weekday=""`, `year="numeric"`}
  80. switch format {
  81. case "short", "long": // date only
  82. attrs = append(attrs, `month="`+format+`"`, `day="numeric"`)
  83. return template.HTML(fmt.Sprintf(`<absolute-date %s date="%s">%s</absolute-date>`, strings.Join(attrs, " "), datetimeEscaped, textEscaped))
  84. case "full": // full date including time
  85. attrs = append(attrs, `format="datetime"`, `month="short"`, `day="numeric"`, `hour="numeric"`, `minute="numeric"`, `second="numeric"`, `data-tooltip-content`, `data-tooltip-interactive="true"`)
  86. return template.HTML(fmt.Sprintf(`<relative-time %s datetime="%s">%s</relative-time>`, strings.Join(attrs, " "), datetimeEscaped, textEscaped))
  87. default:
  88. panic("Unsupported format " + format)
  89. }
  90. }
  91. func timeSinceTo(then any, now time.Time) template.HTML {
  92. thenTime, isZero := anyToTime(then)
  93. if isZero {
  94. return "-"
  95. }
  96. friendlyText := thenTime.Format("2006-01-02 15:04:05 -07:00")
  97. // document: https://github.com/github/relative-time-element
  98. attrs := `tense="past"`
  99. isFuture := now.Before(thenTime)
  100. if isFuture {
  101. attrs = `tense="future"`
  102. }
  103. // declare data-tooltip-content attribute to switch from "title" tooltip to "tippy" tooltip
  104. htm := fmt.Sprintf(`<relative-time prefix="" %s datetime="%s" data-tooltip-content data-tooltip-interactive="true">%s</relative-time>`,
  105. attrs, thenTime.Format(time.RFC3339), friendlyText)
  106. return template.HTML(htm)
  107. }
  108. // TimeSince renders relative time HTML given a time
  109. func TimeSince(then any) template.HTML {
  110. if setting.UI.PreferredTimestampTense == "absolute" {
  111. return dateTimeFormat("full", then)
  112. }
  113. return timeSinceTo(then, time.Now())
  114. }