Display time in timezone configured on the account
Use the timezone configured on the account, or the default timezone if no timezone is confirude to display the timetable and events in local time.
This commit is contained in:
parent
1ac607a712
commit
41528e8193
2 changed files with 121 additions and 67 deletions
|
@ -18,7 +18,7 @@
|
|||
<h4>Timeslots</h4>
|
||||
<ul>
|
||||
<li v-for="slot in event.slots" :key="slot.id">
|
||||
{{ slot.start }} - {{ slot.end }}
|
||||
{{ formatTime(slot.start) }} - {{ formatTime(slot.end) }}
|
||||
<button
|
||||
v-if="session && event.slots.length > 1"
|
||||
class="interested"
|
||||
|
@ -37,14 +37,21 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DateTime } from 'luxon';
|
||||
import type { ScheduleEvent } from '~/shared/types/schedule';
|
||||
|
||||
defineProps<{
|
||||
event: ScheduleEvent
|
||||
}>()
|
||||
|
||||
const { data: session, refresh: refreshSession } = useAccountSession();
|
||||
const runtimeConfig = useRuntimeConfig();
|
||||
const { data: session, refresh: refreshSession } = await useAccountSession();
|
||||
const interestedIds = computed(() => new Set(session.value?.account.interestedIds ?? []));
|
||||
const timezone = computed(() => session.value?.account.timezone ?? runtimeConfig.public.defaultTimezone);
|
||||
|
||||
function formatTime(time: string) {
|
||||
return DateTime.fromISO(time, { zone: timezone.value }).toFormat("yyyy-LL-dd HH:mm");
|
||||
}
|
||||
|
||||
async function toggle(id: string, slotIds?: string[]) {
|
||||
let newIds = [...session.value!.account.interestedIds ?? []];
|
||||
|
|
|
@ -2,27 +2,37 @@
|
|||
<figure class="timetable">
|
||||
<details>
|
||||
<summary>Debug</summary>
|
||||
<p><b>Junctions</b></p>
|
||||
<div v-for="j in junctions" :key="j.ts">
|
||||
{{ j.ts }}: {{ j.edges.map(e => `${e.type} ${e.slot.id}`).join(", ") }}
|
||||
</div>
|
||||
<p><b>Stretches</b></p>
|
||||
<ol>
|
||||
<li v-for="st in stretches" :key="st.start">
|
||||
<p>Stretch from {{ st.start }} to {{ st.end }}.</p>
|
||||
<p>Spans:</p>
|
||||
<ul>
|
||||
<li v-for="s in st.spans" :key="s.start.ts">
|
||||
{{ s.start.ts }} - {{ s.end.ts }}:
|
||||
<ul>
|
||||
<li v-for="[id, slots] in s.locations" :key="id">
|
||||
{{ id }}: {{ [...slots].map(s => s.id).join(", ") }}
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
<details>
|
||||
<summary><b>Junctions</b></summary>
|
||||
<div v-for="j in junctions" :key="j.ts">
|
||||
{{ j.ts }}: {{ j.edges.map(e => `${e.type} ${e.slot.id}`).join(", ") }}
|
||||
</div>
|
||||
</details>
|
||||
<details>
|
||||
<summary><b>Stretches</b></summary>
|
||||
<ol>
|
||||
<li v-for="st in stretches" :key="st.start">
|
||||
<p>Stretch from {{ st.start }} to {{ st.end }}.</p>
|
||||
<p>Spans:</p>
|
||||
<ul>
|
||||
<li v-for="s in st.spans" :key="s.start.ts">
|
||||
{{ s.start.ts }} - {{ s.end.ts }}:
|
||||
<ul>
|
||||
<li v-for="[id, slots] in s.locations" :key="id">
|
||||
{{ id }}: {{ [...slots].map(s => s.id).join(", ") }}
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
</details>
|
||||
<p>
|
||||
<label>
|
||||
Timezone:
|
||||
<input type="text" v-model="timezone">
|
||||
</label>
|
||||
</p>
|
||||
</details>
|
||||
<table>
|
||||
<colgroup>
|
||||
|
@ -63,6 +73,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DateTime } from "luxon";
|
||||
import type { ScheduleEvent, ScheduleLocation, TimeSlot } from "~/shared/types/schedule";
|
||||
|
||||
const oneDayMs = 24 * 60 * 60 * 1000;
|
||||
|
@ -204,27 +215,10 @@ function* spansFromJunctions(
|
|||
}
|
||||
|
||||
function createStretch(spans: Span[]): Stretch {
|
||||
let start = spans[0].start.ts - oneHourMs;
|
||||
let end = spans[spans.length - 1].end.ts + oneHourMs;
|
||||
// Extend stretch to nearest whole hours
|
||||
start = Math.floor(start / oneHourMs) * oneHourMs;
|
||||
end = Math.ceil(end / oneHourMs) * oneHourMs;
|
||||
return {
|
||||
spans: [
|
||||
{
|
||||
start: { ts: start, edges: [] },
|
||||
end: spans[0].start,
|
||||
locations: new Map(),
|
||||
},
|
||||
...spans,
|
||||
{
|
||||
start: spans[spans.length - 1].end,
|
||||
end: { ts: end, edges: [] },
|
||||
locations: new Map(),
|
||||
},
|
||||
],
|
||||
start,
|
||||
end,
|
||||
spans,
|
||||
start: spans[0].start.ts,
|
||||
end: spans[spans.length - 1].end.ts,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -247,41 +241,76 @@ function* stretchesFromSpans(spans: Iterable<Span>, minSeparation: number): Gene
|
|||
}
|
||||
|
||||
/** Cuts up a span by whole hours that crosses it */
|
||||
function* cutSpansByHours(span: Span): Generator<Span> {
|
||||
const startHour = span.start.ts / oneHourMs;
|
||||
const endHour = span.end.ts / oneHourMs;
|
||||
function* cutSpansByHours(span: Span, timezone: string): Generator<Span> {
|
||||
const startHour = DateTime.fromMillis(span.start.ts, { zone: timezone })
|
||||
.startOf("hour")
|
||||
;
|
||||
const end = span.end.ts;
|
||||
|
||||
let currentStart = startHour;
|
||||
let currentEnd = Math.min(Math.floor(startHour + 1), endHour);
|
||||
if (currentEnd === endHour) {
|
||||
let currentEnd = startHour.plus({ hours: 1 });
|
||||
if (!startHour.isValid || currentEnd.toMillis() >= end) {
|
||||
yield span;
|
||||
return;
|
||||
}
|
||||
|
||||
yield {
|
||||
start: span.start,
|
||||
end: { ts: currentEnd * oneHourMs, edges: [] },
|
||||
end: { ts: currentEnd.toMillis(), edges: [] },
|
||||
locations: span.locations,
|
||||
}
|
||||
|
||||
currentStart = currentEnd;
|
||||
while (++currentEnd < endHour) {
|
||||
while (true) {
|
||||
currentStart = currentEnd;
|
||||
currentEnd = currentEnd.plus({ hours: 1 });
|
||||
if (currentEnd.toMillis() >= end) {
|
||||
break;
|
||||
}
|
||||
yield {
|
||||
start: { ts: currentStart * oneHourMs, edges: [] },
|
||||
end: { ts: currentEnd * oneHourMs, edges: [] },
|
||||
start: { ts: currentStart.toMillis(), edges: [] },
|
||||
end: { ts: currentEnd.toMillis(), edges: [] },
|
||||
locations: span.locations,
|
||||
}
|
||||
currentStart += 1;
|
||||
}
|
||||
|
||||
yield {
|
||||
start: { ts: currentStart * oneHourMs, edges: [] },
|
||||
start: { ts: currentStart.toMillis(), edges: [] },
|
||||
end: span.end,
|
||||
locations: span.locations,
|
||||
}
|
||||
}
|
||||
|
||||
function padStretch(stretch: Stretch, timezone: string): Stretch {
|
||||
// Pad by one hour and extend it to the nearest whole hour.
|
||||
let start = DateTime.fromMillis(stretch.start, { zone: timezone })
|
||||
.minus(oneHourMs)
|
||||
.startOf("hour")
|
||||
;
|
||||
let end = DateTime.fromMillis(stretch.end, { zone: timezone })
|
||||
.plus(2 * oneHourMs - 1)
|
||||
.startOf("hour")
|
||||
;
|
||||
return {
|
||||
spans: [
|
||||
{
|
||||
start: { ts: start.toMillis(), edges: [] },
|
||||
end: stretch.spans[0].start,
|
||||
locations: new Map(),
|
||||
},
|
||||
...stretch.spans,
|
||||
{
|
||||
start: stretch.spans[stretch.spans.length - 1].end,
|
||||
end: { ts: end.toMillis(), edges: [] },
|
||||
locations: new Map(),
|
||||
},
|
||||
],
|
||||
start: start.toMillis(),
|
||||
end: end.toMillis(),
|
||||
}
|
||||
}
|
||||
|
||||
function tableElementsFromStretches(
|
||||
stretches: Iterable<Stretch>, locations: ScheduleLocation[]
|
||||
stretches: Iterable<Stretch>, locations: ScheduleLocation[], timezone: string,
|
||||
) {
|
||||
type Col = { minutes?: number };
|
||||
type DayHead = { span: number, content?: string }
|
||||
|
@ -315,18 +344,20 @@ function tableElementsFromStretches(
|
|||
}
|
||||
|
||||
let first = true;
|
||||
for (const stretch of stretches) {
|
||||
for (let stretch of stretches) {
|
||||
stretch = padStretch(stretch, timezone);
|
||||
const startDate = DateTime.fromMillis(stretch.start, { zone: timezone });
|
||||
if (first) {
|
||||
first = false;
|
||||
startColumnGroup();
|
||||
startDay(isoStringFromTs(stretch.start).slice(0, 10));
|
||||
startHour(isoStringFromTs(stretch.start).slice(11, 16));
|
||||
startDay(startDate.toFormat("yyyy-LL-dd"));
|
||||
startHour(startDate.toFormat("HH:mm"));
|
||||
for(const location of locations) {
|
||||
startLocation(location.id);
|
||||
}
|
||||
} else {
|
||||
startColumnGroup("break");
|
||||
const dayName = isoStringFromTs(stretch.start).slice(0, 10)
|
||||
const dayName = startDate.toFormat("yyyy-LL-dd");
|
||||
const lastDayHeader = dayHeaders[dayHeaders.length - 1]
|
||||
const sameDay = dayName === lastDayHeader.content && lastDayHeader.span;
|
||||
if (!sameDay)
|
||||
|
@ -340,14 +371,14 @@ function tableElementsFromStretches(
|
|||
startColumnGroup();
|
||||
if (!sameDay)
|
||||
startDay(dayName);
|
||||
startHour(isoStringFromTs(stretch.start).slice(11, 16));
|
||||
startHour(startDate.toFormat("HH:mm"));
|
||||
for(const location of locations) {
|
||||
startLocation(location.id);
|
||||
}
|
||||
}
|
||||
|
||||
for (const span of stretch.spans) {
|
||||
for (const cutSpan of cutSpansByHours(span)) {
|
||||
for (const cutSpan of cutSpansByHours(span, timezone)) {
|
||||
const end = cutSpan.end.ts;
|
||||
const durationMs = end - cutSpan.start.ts;
|
||||
|
||||
|
@ -361,11 +392,18 @@ function tableElementsFromStretches(
|
|||
}
|
||||
|
||||
pushColumn(durationMs / oneMinMs);
|
||||
if (end % oneDayMs === 0) {
|
||||
startDay(isoStringFromTs(cutSpan.end.ts).slice(0, 10));
|
||||
const endDate = DateTime.fromMillis(end, { zone: timezone });
|
||||
if (end === endDate.startOf("day").toMillis()) {
|
||||
startDay(
|
||||
DateTime.fromMillis(cutSpan.end.ts, { zone: timezone })
|
||||
.toFormat("yyyy-LL-dd")
|
||||
);
|
||||
}
|
||||
if (end % oneHourMs === 0) {
|
||||
startHour(isoStringFromTs(cutSpan.end.ts).slice(11, 16));
|
||||
if (end === endDate.startOf("hour").toMillis()) {
|
||||
startHour(
|
||||
DateTime.fromMillis(cutSpan.end.ts, { zone: timezone })
|
||||
.toFormat("HH:mm")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -384,7 +422,16 @@ const junctions = computed(() => junctionsFromEdges(edgesFromEvents(schedule.val
|
|||
const stretches = computed(() => [
|
||||
...stretchesFromSpans(spansFromJunctions(junctions.value, schedule.value.locations), oneHourMs * 5)
|
||||
])
|
||||
const elements = computed(() => tableElementsFromStretches(stretches.value, schedule.value.locations));
|
||||
|
||||
const runtimeConfig = useRuntimeConfig();
|
||||
const { data: session } = await useAccountSession();
|
||||
const debugTimezone = ref<undefined | string>();
|
||||
const timezone = computed({
|
||||
get: () => debugTimezone.value ?? session.value?.account.timezone ?? runtimeConfig.public.defaultTimezone,
|
||||
set: (value: string) => { debugTimezone.value = value },
|
||||
});
|
||||
|
||||
const elements = computed(() => tableElementsFromStretches(stretches.value, schedule.value.locations, timezone.value));
|
||||
const columnGroups = computed(() => elements.value.columnGroups);
|
||||
const dayHeaders = computed(() => elements.value.dayHeaders);
|
||||
const hourHeaders = computed(() => elements.value.hourHeaders);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue