Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 41 additions & 62 deletions src/components/Base/modules/DateBox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,100 +5,79 @@

<script setup lang="ts">
import { computed } from 'vue'
import { DateTime, Duration, Interval } from 'luxon'
import { DateTime, Duration } from 'luxon'
import { getDates } from '../../../composables/optionDateTime'

interface Props {
dateTime: DateTime
startDate: DateTime
duration?: Duration
}
const { dateTime, duration = Duration.fromMillis(0) } = defineProps<Props>()

// the dates span one or more entire days
// do not display the time in this case
const allDay = computed(
() =>
dateTime.startOf('day').toSeconds() === dateTime.toSeconds()
&& duration.as('seconds') % 86400 === 0,
)

// 'to' is 'from' plus the duration
// subtract a day if allDay is true and luxonDuration is greater than 0 to match the
// end of the day after the duration instead of the beginning of the next day
const to = computed(() => {
// FIXME: this should be replaced by a more stable solution
// The last change reintroduced the DST bug (see #4276)
if (allDay.value) {
return dateTime.plus(duration)
}
return dateTime
.plus(duration)
.minus({ day: allDay.value && duration.as('seconds') > 0 ? 1 : 0 })
})

// to and from dates have the same month (and year)
// suppress the 'to' month if they are the same
const isSameMonth = computed(
() => dateTime.month === to.value.month && dateTime.year === to.value.year,
)

// to and from dates have the same day (in the same month and year)
// suppress the 'to' day if they are the same
// display the interval as timespan inside the same day
const isSameDay = computed(() => dateTime.day === to.value.day && isSameMonth.value)

// Shortcut: 'to' and 'from' are identical
// suppress the 'to' time if they are the same
const isSameTime = computed(() => duration.as('seconds') === 0)

const interval = computed(() => Interval.fromDateTimes(dateTime, to.value))

const { startDate, duration = Duration.fromMillis(0) } = defineProps<Props>()

const optionDateTimes = computed(() => getDates(startDate, duration))
</script>

<template>
<div :title="interval.toISO()" class="datebox">
<div class="month from" :class="{ span: isSameMonth }">
<div :title="optionDateTimes.optionInterval.toISO()" class="datebox">
<div class="month from" :class="{ span: optionDateTimes.isSameMonth }">
{{
dateTime.toLocaleString(
DateTime.now().year === dateTime.year
startDate.toLocaleString(
DateTime.now().year === startDate.year
? { month: 'short' }
: { month: 'short', year: '2-digit' },
)
}}
</div>

<div v-if="!isSameMonth" class="month to">
<div v-if="!optionDateTimes.isSameMonth" class="month to">
{{
to.toLocaleString(
DateTime.now().year === dateTime.year
optionDateTimes.optionEnd.toLocaleString(
DateTime.now().year === startDate.year
? { month: 'short' }
: { month: 'short', year: '2-digit' },
)
}}
</div>

<div class="day from" :class="{ span: isSameDay }">
{{ dateTime.toLocaleString({ weekday: 'short', day: 'numeric' }) }}
<div class="day from" :class="{ span: optionDateTimes.isSameDay }">
{{ startDate.toLocaleString({ weekday: 'short', day: 'numeric' }) }}
</div>

<span v-if="!isSameDay" class="day divider">–</span>
<span v-if="!optionDateTimes.isSameDay" class="day divider">–</span>

<div v-if="!isSameDay" class="day to">
{{ to.toLocaleString({ weekday: 'short', day: 'numeric' }) }}
<div v-if="!optionDateTimes.isSameDay" class="day to">
{{
optionDateTimes.optionEnd.toLocaleString({
weekday: 'short',
day: 'numeric',
})
}}
</div>

<div v-if="!allDay" class="time from" :class="{ span: isSameDay }">
<div
v-if="!optionDateTimes.isFullDays"
class="time from"
:class="{ span: optionDateTimes.isSameDay }">
{{
isSameDay && !isSameTime
? interval.toLocaleString(DateTime.TIME_SIMPLE)
: dateTime.toLocaleString(DateTime.TIME_SIMPLE)
optionDateTimes.isSameDay && !optionDateTimes.isSameTime
? optionDateTimes.optionInterval.toLocaleString(
DateTime.TIME_SIMPLE,
)
: startDate.toLocaleString(DateTime.TIME_SIMPLE)
}}
</div>

<span v-if="!allDay && !isSameDay" class="time divider">
{{ isSameDay ? '-' : '&nbsp;' }}
<span
v-if="!optionDateTimes.isFullDays && !optionDateTimes.isSameDay"
class="time divider">
{{ optionDateTimes.isSameDay ? '-' : '&nbsp;' }}
</span>

<div v-if="!allDay && !isSameDay" class="time to">
{{ to.toLocaleString(DateTime.TIME_SIMPLE) }}
<div
v-if="!optionDateTimes.isFullDays && !optionDateTimes.isSameDay"
class="time to">
{{ optionDateTimes.optionEnd.toLocaleString(DateTime.TIME_SIMPLE) }}
</div>
</div>
</template>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Options/OptionItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const pollStore = usePollStore()
<DateBox
v-else
class="option-item__option--date"
:date-time="DateTime.fromSeconds(option.timestamp)"
:start-date="DateTime.fromSeconds(option.timestamp)"
:duration="Duration.fromMillis(option.duration * 1000)" />

<slot name="actions" />
Expand Down
2 changes: 1 addition & 1 deletion src/components/Options/OptionItemDateBox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const duration = Duration.fromMillis(durationSeconds * 1000)

<template>
<div class="option-item__option--datebox">
<DateBox :date-time="from" :duration="duration" />
<DateBox :start-date="from" :duration="duration" />
</div>
</template>

Expand Down
4 changes: 2 additions & 2 deletions src/components/Options/OptionsDateAddDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ async function addOption(): Promise<void> {
<div class="preview___entry">
<DateBox
class="from"
:date-time="from"
:start-date="from"
:duration="duration" />
</div>
<div
Expand All @@ -296,7 +296,7 @@ async function addOption(): Promise<void> {
<div class="preview___entry">
<DateBox
class="from"
:date-time="lastFrom"
:start-date="lastFrom"
:duration="duration" />
</div>
</div>
Expand Down
73 changes: 73 additions & 0 deletions src/composables/optionDateTime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* SPDX-FileCopyrightText: 2024 Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { DateTime, Duration, Interval } from 'luxon'
import { ref, toValue, watchEffect } from 'vue'

/**
* returns the width of the element with the given id
*
* @param elementId the id of the element whose width should be checked
* @param elWidthOffset the width offset to check against
*/

export const dateFrom = ref()

export function getDates(optionStart: DateTime, optionDuration: Duration | null) {
const endDate = ref(optionStart)
const localDuration = ref(null as Duration | null)
const interval = ref(Interval.fromDateTimes(optionStart, optionStart))

const fullDays = ref(false)
const isSameMonth = ref(false)
const isSameDay = ref(false)
const isSameTime = ref(false)

const calculateValues = () => {
endDate.value = toValue(optionStart)
localDuration.value = toValue(optionDuration)
fullDays.value = false

if (!localDuration.value) {
// without duration, no further calculation is possible
// end date remains the same as from date
return
}

// Check if the duration represents full days
fullDays.value =
optionStart.valueOf() === optionStart.startOf('day').valueOf()
&& localDuration.value.hours + localDuration.value.minutes === 0

// If full days are selected and duration is 0 days, set duration to 1 day
if (fullDays.value && localDuration.value.as('days') === 0) {
optionDuration = Duration.fromObject({ days: 1 })
}

endDate.value = optionStart.plus(localDuration.value)
// If full days are selected, subtract 1 millisecond for display purposes
if (fullDays.value) {
endDate.value = endDate.value.minus({ milliseconds: 1 })
}
isSameMonth.value = optionStart.hasSame(endDate.value, 'month')
isSameDay.value = optionStart.hasSame(endDate.value, 'day')
isSameTime.value = localDuration.value.as('minutes') === 0
interval.value = Interval.fromDateTimes(optionStart, endDate.value)
}

watchEffect(() => {
calculateValues()
})

return {
optionStart,
optionEnd: endDate.value,
isFullDays: fullDays.value,
isSameMonth: isSameMonth.value,
isSameDay: isSameDay.value,
isSameTime: isSameTime.value,
optionInterval: interval.value,
}
}