fix timezone issues and add support for timezone user setting

This commit is contained in:
Gregor Vostrak
2024-04-23 02:54:28 +02:00
parent b28f2b0a1c
commit 52381be399
5 changed files with 48 additions and 17 deletions

View File

@@ -1,26 +1,26 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed } from 'vue';
import dayjs from 'dayjs'; import { getLocalizedDayJs } from '@/utils/time';
const model = defineModel<string | null>({ const model = defineModel<string | null>({
default: null, default: null,
}); });
const hours = computed(() => { const hours = computed(() => {
return model.value ? dayjs(model.value).utc().hour() : null; return model.value ? getLocalizedDayJs(model.value).hour() : null;
}); });
const minutes = computed(() => { const minutes = computed(() => {
return model.value ? dayjs(model.value).utc().minute() : null; return model.value ? getLocalizedDayJs(model.value).minute() : null;
}); });
function updateMinutes(event: Event) { function updateMinutes(event: Event) {
const target = event.target as HTMLInputElement; const target = event.target as HTMLInputElement;
const newValue = target.value; const newValue = target.value;
if (!isNaN(parseInt(newValue))) { if (!isNaN(parseInt(newValue))) {
model.value = dayjs(model.value) model.value = getLocalizedDayJs(model.value)
.utc()
.set('minutes', parseInt(newValue)) .set('minutes', parseInt(newValue))
.utc()
.format(); .format();
} }
} }
@@ -29,9 +29,9 @@ function updateHours(event: Event) {
const target = event.target as HTMLInputElement; const target = event.target as HTMLInputElement;
const newValue = target.value; const newValue = target.value;
if (!isNaN(parseInt(newValue))) { if (!isNaN(parseInt(newValue))) {
model.value = dayjs(model.value) model.value = getLocalizedDayJs(model.value)
.utc()
.set('hours', parseInt(newValue)) .set('hours', parseInt(newValue))
.utc()
.format(); .format();
} }
} }

View File

@@ -13,7 +13,7 @@ import {
} from 'echarts/components'; } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers'; import { CanvasRenderer } from 'echarts/renderers';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { formatDate, formatTime } from '@/utils/time'; import { formatDate, formatHumanReadableDuration } from '@/utils/time';
const props = defineProps<{ const props = defineProps<{
dailyHoursTracked: { duration: number; date: string }[]; dailyHoursTracked: { duration: number; date: string }[];
@@ -76,11 +76,11 @@ const option = ref({
borderRadius: 5, borderRadius: 5,
}, },
tooltip: { tooltip: {
valueFormatter: (value: string, dataIndex: number) => { valueFormatter: (value: number, dataIndex: number) => {
return ( return (
formatDate(props.dailyHoursTracked[dataIndex].date) + formatDate(props.dailyHoursTracked[dataIndex].date) +
': ' + ': ' +
formatTime(value) formatHumanReadableDuration(value)
); );
}, },
}, },

View File

@@ -5,12 +5,12 @@ import { computed, onMounted, ref, watch } from 'vue';
import MainContainer from '@/Pages/MainContainer.vue'; import MainContainer from '@/Pages/MainContainer.vue';
import { useTimeEntriesStore } from '@/utils/useTimeEntries'; import { useTimeEntriesStore } from '@/utils/useTimeEntries';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import dayjs from 'dayjs';
import type { TimeEntry } from '@/utils/api'; import type { TimeEntry } from '@/utils/api';
import TimeEntryRowHeading from '@/Components/Common/TimeEntry/TimeEntryRowHeading.vue'; import TimeEntryRowHeading from '@/Components/Common/TimeEntry/TimeEntryRowHeading.vue';
import TimeEntryRow from '@/Components/Common/TimeEntry/TimeEntryRow.vue'; import TimeEntryRow from '@/Components/Common/TimeEntry/TimeEntryRow.vue';
import { useElementVisibility } from '@vueuse/core'; import { useElementVisibility } from '@vueuse/core';
import { ClockIcon } from '@heroicons/vue/20/solid'; import { ClockIcon } from '@heroicons/vue/20/solid';
import { getLocalizedDateFromTimestamp } from '@/utils/time';
const timeEntriesStore = useTimeEntriesStore(); const timeEntriesStore = useTimeEntriesStore();
const { timeEntries, allTimeEntriesLoaded } = storeToRefs(timeEntriesStore); const { timeEntries, allTimeEntriesLoaded } = storeToRefs(timeEntriesStore);
@@ -38,10 +38,9 @@ const groupedTimeEntries = computed(() => {
const groupedEntries: Record<string, TimeEntry[]> = {}; const groupedEntries: Record<string, TimeEntry[]> = {};
for (const entry of timeEntries.value) { for (const entry of timeEntries.value) {
const oldEntries = const oldEntries =
groupedEntries[dayjs(entry.start).utc().format('YYYY-MM-DD')]; groupedEntries[getLocalizedDateFromTimestamp(entry.start)];
const newEntries = [...(oldEntries ?? []), entry]; const newEntries = [...(oldEntries ?? []), entry];
groupedEntries[dayjs(entry.start).utc().format('YYYY-MM-DD')] = groupedEntries[getLocalizedDateFromTimestamp(entry.start)] = newEntries;
newEntries;
} }
return groupedEntries; return groupedEntries;
}); });

View File

@@ -4,12 +4,19 @@ import relativeTime from 'dayjs/plugin/relativeTime';
import isToday from 'dayjs/plugin/isToday'; import isToday from 'dayjs/plugin/isToday';
import isYesterday from 'dayjs/plugin/isYesterday'; import isYesterday from 'dayjs/plugin/isYesterday';
import utc from 'dayjs/plugin/utc'; import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import { getUserTimezone } from '@/utils/useUser';
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
dayjs.extend(isToday); dayjs.extend(isToday);
dayjs.extend(isYesterday); dayjs.extend(isYesterday);
dayjs.extend(duration); dayjs.extend(duration);
dayjs.extend(utc); dayjs.extend(utc);
dayjs.extend(timezone);
export function getDayJsInstance() {
return dayjs;
}
export function formatHumanReadableDuration(duration: number): string { export function formatHumanReadableDuration(duration: number): string {
return dayjs.duration(duration, 's').format('HH[h] mm[min]'); return dayjs.duration(duration, 's').format('HH[h] mm[min]');
@@ -21,14 +28,35 @@ export function calculateDifference(start: string, end: string | null) {
} }
return dayjs(end).diff(dayjs(start), 'second'); return dayjs(end).diff(dayjs(start), 'second');
} }
/**
* Returns a formatted time.
* @param date - A UTC date time string.
*/
export function formatTime(date: string) { export function formatTime(date: string) {
return dayjs(date).utc().format('HH:mm'); return dayjs.utc(date).tz(getUserTimezone()).format('HH:mm');
} }
export function getLocalizedDayJs(timestamp: string | null) {
return dayjs.utc(timestamp).tz(getUserTimezone());
}
export function getLocalizedDateFromTimestamp(timestamp: string) {
return getLocalizedDayJs(timestamp).format('YYYY-MM-DD');
}
/*
* Returns a formatted date.
* @param date - date in the format of 'YYYY-MM-DD'
*/
export function formatDate(date: string): string { export function formatDate(date: string): string {
return dayjs(date).utc().format('DD.MM.YYYY'); return dayjs(date).format('DD.MM.YYYY');
} }
/*
* Returns a human readable date format.
* @param date - date in the format of 'YYYY-MM-DD'
*/
export function formatHumanReadableDate(date: string) { export function formatHumanReadableDate(date: string) {
if (dayjs(date).isToday()) { if (dayjs(date).isToday()) {
return 'Today'; return 'Today';

View File

@@ -14,4 +14,8 @@ function getCurrentOrganizationId() {
return page.props.auth.user.current_team_id; return page.props.auth.user.current_team_id;
} }
export { getCurrentOrganizationId, getCurrentUserId }; function getUserTimezone() {
return page.props.auth.user.timezone;
}
export { getCurrentOrganizationId, getCurrentUserId, getUserTimezone };