Repository: amir20/dozzle (language: go)

Issue:
Configurable date format of timestamp
**Is your feature request related to a problem? Please describe.**
The date format of the timestamp of a log message seems to take the language from the browser. 
In my case is this en-US, but I am from Europe and a dateformat MM/DD/YYYY is hard to read for me.

**Describe the solution you'd like**
I would like to have a UI setting with a text input to define the date format like for example: "DD.MM.YYYY"
Like there is a setting for 12 or 24 hours format.

**Describe alternatives you've considered**
An alternative would be to define it via an environment variable.

**Additional context**
![image](https://github.com/amir20/dozzle/assets/12148533/60c6aabf-021e-45e7-9bd4-12d80e5ceda5)



Discussion / hints:
> In my case is this en-US, but I am from Europe and a dateformat MM/DD/YYYY is hard to read for me.

:) I never thought of that. 

I am not sure if you are familiar with javascript, but I use Intl.[DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat) which doesn't support a custom format. Only a specific locale. So I don't think it would be easy to do that. Maybe a drop down to override the locale but I think that would be confusing personally?


Yeah a dropdown with locale wouldn´t be good.
But we could add a dropdown with dateformats and then use the corresponding locales:

MM/DD/YYYY -> en-US
DD/MM/YYYY -> en-GB
DD.MM.YYYY -> de-DE
YYYY-MM-DD -> en-CA

I tested the 4 with Intl.DateTimeFormat
consider using https://momentjs.com/
> consider using https://momentjs.com/

Thanks for the tip but:

This is on the moment main page:
_Considering using Moment in your project?
There may be better modern alternatives.
For more details and recommendations,
please see [Project Status](https://momentjs.com/docs/#/-project-status/) in the docs._

So yeah, it would be ok to consider to use dayjs, but moment is not recommended.
@masterwendu That's pretty awesome. I didn't realize different locales change slashes and dots. I actually do the same thing things with time. I use `en-gb` for 24-hour style and `en-us` for 12 hour. 

When I get some time, I'll try it out today for date and use the ones you suggested. 

@Nicokalak momentjs is very outdated. Most functionalities can be done with browser. 

Dozzle's tries to have good out of the box defaults so I want to make sure I use built-in date formatters. 



Fix patch:
diff --git a/assets/auto-imports.d.ts b/assets/auto-imports.d.ts
index 3172f6e84b8f..e1d083e94de5 100644
--- a/assets/auto-imports.d.ts
+++ b/assets/auto-imports.d.ts
@@ -40,6 +40,7 @@ declare global {
   const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise']
   const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn']
   const customRef: typeof import('vue')['customRef']
+  const dateLocale: typeof import('./stores/settings')['dateLocale']
   const debouncedRef: typeof import('@vueuse/core')['debouncedRef']
   const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch']
   const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
@@ -393,6 +394,7 @@ declare module 'vue' {
     readonly createTemplatePromise: UnwrapRef<typeof import('@vueuse/core')['createTemplatePromise']>
     readonly createUnrefFn: UnwrapRef<typeof import('@vueuse/core')['createUnrefFn']>
     readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
+    readonly dateLocale: UnwrapRef<typeof import('./stores/settings')['dateLocale']>
     readonly debouncedRef: UnwrapRef<typeof import('@vueuse/core')['debouncedRef']>
     readonly debouncedWatch: UnwrapRef<typeof import('@vueuse/core')['debouncedWatch']>
     readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
@@ -738,6 +740,7 @@ declare module '@vue/runtime-core' {
     readonly createTemplatePromise: UnwrapRef<typeof import('@vueuse/core')['createTemplatePromise']>
     readonly createUnrefFn: UnwrapRef<typeof import('@vueuse/core')['createUnrefFn']>
     readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
+    readonly dateLocale: UnwrapRef<typeof import('./stores/settings')['dateLocale']>
     readonly debouncedRef: UnwrapRef<typeof import('@vueuse/core')['debouncedRef']>
     readonly debouncedWatch: UnwrapRef<typeof import('@vueuse/core')['debouncedWatch']>
     readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
diff --git a/assets/components/common/DateTime.vue b/assets/components/common/DateTime.vue
index d82013b2c2c6..8f80ef28fe44 100644
--- a/assets/components/common/DateTime.vue
+++ b/assets/components/common/DateTime.vue
@@ -10,13 +10,21 @@ const props = defineProps<{
   date: Date;
 }>();
 
-const dateFormatter = new Intl.DateTimeFormat(undefined, { day: "2-digit", month: "2-digit", year: "numeric" });
-const use12Hour = $computed(() => ({ auto: undefined, "12": true, "24": false })[hourStyle.value]);
-const timeFormatter = $computed(
+const dateOverride = computed(() => (dateLocale.value === "auto" ? undefined : dateLocale.value));
+const dateFormatter = computed(
+  () => new Intl.DateTimeFormat(dateOverride.value, { day: "2-digit", month: "2-digit", year: "numeric" }),
+);
+const use12Hour = computed(() => ({ auto: undefined, "12": true, "24": false })[hourStyle.value]);
+const timeFormatter = computed(
   () =>
-    new Intl.DateTimeFormat(undefined, { hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: use12Hour }),
+    new Intl.DateTimeFormat(undefined, {
+      hour: "2-digit",
+      minute: "2-digit",
+      second: "2-digit",
+      hour12: use12Hour.value,
+    }),
 );
 
-const dateStr = $computed(() => dateFormatter.format(props.date));
-const timeStr = $computed(() => timeFormatter.format(props.date));
+const dateStr = computed(() => dateFormatter.value.format(props.date));
+const timeStr = computed(() => timeFormatter.value.format(props.date));
 </script>
diff --git a/assets/pages/settings.vue b/assets/pages/settings.vue
index 164b6cac17dd..ca9285f876ca 100644
--- a/assets/pages/settings.vue
+++ b/assets/pages/settings.vue
@@ -31,17 +31,29 @@
 
           <labeled-input>
             <template #label>
-              {{ $t("settings.12-24-format") }}
+              {{ $t("settings.datetime-format") }}
             </template>
             <template #input>
-              <dropdown-menu
-                v-model="hourStyle"
-                :options="[
-                  { label: 'Auto', value: 'auto' },
-                  { label: '12', value: '12' },
-                  { label: '24', value: '24' },
-                ]"
-              />
+              <div class="flex gap-2">
+                <dropdown-menu
+                  v-model="dateLocale"
+                  :options="[
+                    { label: 'Auto', value: 'auto' },
+                    { label: 'MM/DD/YYYY', value: 'en-US' },
+                    { label: 'DD/MM/YYYY', value: 'en-GB' },
+                    { label: 'DD.MM.YYYY', value: 'de-DE' },
+                    { label: 'YYYY-MM-DD', value: 'en-CA' },
+                  ]"
+                />
+                <dropdown-menu
+                  v-model="hourStyle"
+                  :options="[
+                    { label: 'Auto', value: 'auto' },
+                    { label: '12', value: '12' },
+                    { label: '24', value: '24' },
+                  ]"
+                />
+              </div>
             </template>
           </labeled-input>
 
@@ -113,6 +125,7 @@ import { ComplexLogEntry, SimpleLogEntry } from "@/models/LogEntry";
 import {
   automaticRedirect,
   hourStyle,
+  dateLocale,
   lightTheme,
   search,
   showAllContainers,
diff --git a/assets/stores/settings.ts b/assets/stores/settings.ts
index 94f9c1bc72e1..3c5831d626d5 100644
--- a/assets/stores/settings.ts
+++ b/assets/stores/settings.ts
@@ -10,6 +10,7 @@ export type Settings = {
   showAllContainers: boolean;
   lightTheme: "auto" | "dark" | "light";
   hourStyle: "auto" | "24" | "12";
+  dateLocale: "auto" | "en-US" | "en-GB" | "de-DE" | "en-CA";
   softWrap: boolean;
   collapseNav: boolean;
   automaticRedirect: boolean;
@@ -24,6 +25,7 @@ export const DEFAULT_SETTINGS: Settings = {
   showAllContainers: false,
   lightTheme: "auto",
   hourStyle: "auto",
+  dateLocale: "auto",
   softWrap: true,
   collapseNav: false,
   automaticRedirect: true,
@@ -35,6 +37,7 @@ export const {
   collapseNav,
   softWrap,
   hourStyle,
+  dateLocale,
   lightTheme,
   showAllContainers,
   showTimestamp,
diff --git a/internal/profile/disk.go b/internal/profile/disk.go
index cd0b74b0846e..851fae7b0595 100644
--- a/internal/profile/disk.go
+++ b/internal/profile/disk.go
@@ -32,6 +32,7 @@ type Settings struct {
 	Size              string  `json:"size,omitempty"`
 	LightTheme        string  `json:"lightTheme,omitempty"`
 	HourStyle         string  `json:"hourStyle,omitempty"`
+	DateLocale        string  `json:"dateLocale,omitempty"`
 }
 
 type Profile struct {
diff --git a/locales/en.yml b/locales/en.yml
index e7bc0e97fc2a..9de87894eb92 100644
--- a/locales/en.yml
+++ b/locales/en.yml
@@ -68,7 +68,7 @@ settings:
   small-scrollbars: Use smaller scrollbars
   show-timesamps: Show timestamps
   soft-wrap: Soft wrap lines
-  12-24-format: Time format
+  datetime-format: Override date and time format
   font-size: Font size to use for logs
   color-scheme: Color scheme
   options: Options


Test patch: