diff --git a/modules/timeutil/datetime.go b/modules/timeutil/datetime.go
index 62b94f7cf481..50c8d44f132b 100644
--- a/modules/timeutil/datetime.go
+++ b/modules/timeutil/datetime.go
@@ -51,18 +51,16 @@ func DateTime(format string, datetime any, extraAttrs ...string) template.HTML {
attrs := make([]string, 0, 10+len(extraAttrs))
attrs = append(attrs, extraAttrs...)
- attrs = append(attrs, `data-tooltip-content`, `data-tooltip-interactive="true"`)
- attrs = append(attrs, `format="datetime"`, `weekday=""`, `year="numeric"`)
+ attrs = append(attrs, `weekday=""`, `year="numeric"`)
switch format {
- case "short":
- attrs = append(attrs, `month="short"`, `day="numeric"`)
- case "long":
- attrs = append(attrs, `month="long"`, `day="numeric"`)
- case "full":
- attrs = append(attrs, `month="short"`, `day="numeric"`, `hour="numeric"`, `minute="numeric"`, `second="numeric"`)
+ case "short", "long": // date only
+ attrs = append(attrs, `month="`+format+`"`, `day="numeric"`)
+ return template.HTML(fmt.Sprintf(`%s`, 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(`%s`, strings.Join(attrs, " "), datetimeEscaped, textEscaped))
default:
panic(fmt.Sprintf("Unsupported format %s", format))
}
- return template.HTML(fmt.Sprintf(`%s`, strings.Join(attrs, " "), datetimeEscaped, textEscaped))
}
diff --git a/modules/timeutil/datetime_test.go b/modules/timeutil/datetime_test.go
index 26494b84754f..39aecbc43bdf 100644
--- a/modules/timeutil/datetime_test.go
+++ b/modules/timeutil/datetime_test.go
@@ -18,6 +18,7 @@ func TestDateTime(t *testing.T) {
defer test.MockVariableValue(&setting.DefaultUILocation, testTz)()
refTimeStr := "2018-01-01T00:00:00Z"
+ refDateStr := "2018-01-01"
refTime, _ := time.Parse(time.RFC3339, refTimeStr)
refTimeStamp := TimeStamp(refTime.Unix())
@@ -27,17 +28,20 @@ func TestDateTime(t *testing.T) {
assert.EqualValues(t, "-", DateTime("short", TimeStamp(0)))
actual := DateTime("short", "invalid")
- assert.EqualValues(t, `invalid`, actual)
+ assert.EqualValues(t, `invalid`, actual)
actual = DateTime("short", refTimeStr)
- assert.EqualValues(t, `2018-01-01T00:00:00Z`, actual)
+ assert.EqualValues(t, `2018-01-01T00:00:00Z`, actual)
actual = DateTime("short", refTime)
- assert.EqualValues(t, `2018-01-01`, actual)
+ assert.EqualValues(t, `2018-01-01`, actual)
+
+ actual = DateTime("short", refDateStr)
+ assert.EqualValues(t, `2018-01-01`, actual)
actual = DateTime("short", refTimeStamp)
- assert.EqualValues(t, `2017-12-31`, actual)
+ assert.EqualValues(t, `2017-12-31`, actual)
actual = DateTime("full", refTimeStamp)
- assert.EqualValues(t, `2017-12-31 19:00:00 -05:00`, actual)
+ assert.EqualValues(t, `2017-12-31 19:00:00 -05:00`, actual)
}
diff --git a/templates/devtest/gitea-ui.tmpl b/templates/devtest/gitea-ui.tmpl
index ccf188609c91..e551572b96c1 100644
--- a/templates/devtest/gitea-ui.tmpl
+++ b/templates/devtest/gitea-ui.tmpl
@@ -110,6 +110,16 @@
+
+
GiteaAbsoluteDate
+
+
+
+
+
+
relative-time:
+
+
LocaleNumber
{{ctx.Locale.PrettyNumber 1}}
diff --git a/web_src/js/webcomponents/GiteaAbsoluteDate.js b/web_src/js/webcomponents/GiteaAbsoluteDate.js
new file mode 100644
index 000000000000..660aa99d0703
--- /dev/null
+++ b/web_src/js/webcomponents/GiteaAbsoluteDate.js
@@ -0,0 +1,40 @@
+window.customElements.define('gitea-absolute-date', class extends HTMLElement {
+ static observedAttributes = ['date', 'year', 'month', 'weekday', 'day'];
+
+ update = () => {
+ const year = this.getAttribute('year') ?? '';
+ const month = this.getAttribute('month') ?? '';
+ const weekday = this.getAttribute('weekday') ?? '';
+ const day = this.getAttribute('day') ?? '';
+ const lang = this.closest('[lang]')?.getAttribute('lang') ||
+ this.ownerDocument.documentElement.getAttribute('lang') ||
+ '';
+
+ // only extract the `yyyy-mm-dd` part. When converting to Date, it will become midnight UTC and when rendered
+ // as localized date, will have a offset towards UTC, which we remove to shift the timestamp to midnight in the
+ // localized date. We should eventually use `Temporal.PlainDate` which will make the correction unnecessary.
+ // - https://stackoverflow.com/a/14569783/808699
+ // - https://tc39.es/proposal-temporal/docs/plaindate.html
+ const date = new Date(this.getAttribute('date').substring(0, 10));
+ const correctedDate = new Date(date.getTime() - date.getTimezoneOffset() * -60000);
+
+ if (!this.shadowRoot) this.attachShadow({mode: 'open'});
+ this.shadowRoot.textContent = correctedDate.toLocaleString(lang ?? [], {
+ ...(year && {year}),
+ ...(month && {month}),
+ ...(weekday && {weekday}),
+ ...(day && {day}),
+ });
+ };
+
+ attributeChangedCallback(_name, oldValue, newValue) {
+ if (!this.initialized || oldValue === newValue) return;
+ this.update();
+ }
+
+ connectedCallback() {
+ this.initialized = false;
+ this.update();
+ this.initialized = true;
+ }
+});
diff --git a/web_src/js/webcomponents/webcomponents.js b/web_src/js/webcomponents/webcomponents.js
index 916a588db64b..03348d895fe4 100644
--- a/web_src/js/webcomponents/webcomponents.js
+++ b/web_src/js/webcomponents/webcomponents.js
@@ -3,3 +3,4 @@ import './polyfill.js';
import '@github/relative-time-element';
import './GiteaOriginUrl.js';
+import './GiteaAbsoluteDate.js';