Skip to content

Commit

Permalink
enh: Make the date time formatting reusable for applications
Browse files Browse the repository at this point in the history
Signed-off-by: Ferdinand Thiessen <[email protected]>
  • Loading branch information
susnux committed Jan 9, 2024
1 parent fd53abe commit 32e9a1f
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 129 deletions.
106 changes: 7 additions & 99 deletions src/components/NcDateTime/NcDateTime.vue
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,8 @@ h4 {
</template>

<script>
import { getCanonicalLocale } from '@nextcloud/l10n'
import { t } from '../../l10n.js'
const FEW_SECONDS_AGO = {
long: t('a few seconds ago'),
short: t('seconds ago'), // FOR TRANSLATORS: Shorter version of 'a few seconds ago'
narrow: t('sec. ago'), // FOR TRANSLATORS: If possible in your language an even shorter version of 'a few seconds ago'
}
import { computed } from 'vue'
import { useFormatDateTime } from '../../composables/useFormatDateTime.js'
export default {
name: 'NcDateTime',
Expand Down Expand Up @@ -164,99 +158,13 @@ export default {
},
},
data() {
setup(props) {
const timestamp = computed(() => props.timestamp)
const { formattedTime, formattedFullTime } = useFormatDateTime(timestamp, props)
return {
/** Current time in ms */
currentTime: Date.now(),
/** ID of the current time interval */
intervalId: undefined,
}
},
computed: {
/** ECMA Date object of the timestamp */
dateObject() {
return new Date(this.timestamp)
},
/** Time string formatted for main text */
formattedTime() {
if (this.relativeTime !== false) {
const formatter = new Intl.RelativeTimeFormat(getCanonicalLocale(), { numeric: 'auto', style: this.relativeTime })
const diff = this.dateObject - new Date(this.currentTime)
const seconds = diff / 1000
if (Math.abs(seconds) <= 90) {
if (this.ignoreSeconds) {
return FEW_SECONDS_AGO[this.relativeTime]
} else {
return formatter.format(Math.round(seconds), 'second')
}
}
const minutes = seconds / 60
if (Math.abs(minutes) <= 90) {
return formatter.format(Math.round(minutes), 'minute')
}
const hours = minutes / 60
if (Math.abs(hours) <= 24) {
return formatter.format(Math.round(hours), 'hour')
}
const days = hours / 24
if (Math.abs(days) <= 6) {
return formatter.format(Math.round(days), 'day')
}
const weeks = days / 7
if (Math.abs(weeks) <= 4) {
return formatter.format(Math.round(weeks), 'week')
}
const months = days / 30
if (Math.abs(months) <= 12) {
return formatter.format(Math.round(months), 'month')
}
return formatter.format(Math.round(days / 365), 'year')
}
return this.formattedFullTime
},
formattedFullTime() {
const formatter = new Intl.DateTimeFormat(getCanonicalLocale(), this.format)
return formatter.format(this.dateObject)
},
},
watch: {
/**
* Set or clear interval if relative time is dis/enabled
*
* @param {boolean} newValue The new value of the relativeTime property
* @param {boolean} _oldValue The old value of the relativeTime property
*/
relativeTime(newValue, _oldValue) {
window.clearInterval(this.intervalId)
this.intervalId = undefined
if (newValue) {
this.intervalId = window.setInterval(this.setCurrentTime, 1000)
}
},
},
mounted() {
// Start the interval for setting the current time if relative time is enabled
if (this.relativeTime !== false) {
this.intervalId = window.setInterval(this.setCurrentTime, 1000)
formattedTime,
formattedFullTime,
}
},
destroyed() {
// ensure interval is cleared
window.clearInterval(this.intervalId)
},
methods: {
/**
* Set `currentTime` to the current timestamp, required as Date.now() is not reactive.
*/
setCurrentTime() {
this.currentTime = Date.now()
},
},
}
</script>
1 change: 1 addition & 0 deletions src/composables/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@

export * from './useIsFullscreen/index.js'
export * from './useIsMobile/index.js'
export * from './useFormatDateTime.js'
130 changes: 130 additions & 0 deletions src/composables/useFormatDateTime.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/**
* @copyright Copyright (c) 2024 Ferdinand Thiessen <[email protected]>
*
* @author Ferdinand Thiessen <[email protected]>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

import { getCanonicalLocale } from '@nextcloud/l10n'
import { computed, onUnmounted, ref, onMounted, watch, unref } from 'vue'
import { t } from '../l10n.js'

const FEW_SECONDS_AGO = {
long: t('a few seconds ago'),
short: t('seconds ago'), // FOR TRANSLATORS: Shorter version of 'a few seconds ago'
narrow: t('sec. ago'), // FOR TRANSLATORS: If possible in your language an even shorter version of 'a few seconds ago'
}

/**
* Composable for formatting time stamps using current users locale
*
* @param {Date | number | import('vue').Ref<Date> | import('vue').Ref<number>} timestamp Current timestamp
* @param {object} opts Optional options
* @param {Intl.DateTimeFormatOptions} opts.format The format used for displaying, or if relative time is used the format used for the title (optional)
* @param {boolean} opts.ignoreSeconds Ignore seconds when displaying the relative time and just show `a few seconds ago`
* @param {false | 'long' | 'short' | 'narrow'} opts.relativeTime Wether to display the timestamp as time from now (optional)
*/
export function useFormatDateTime(timestamp = Date.now(), opts = {}) {
// Current time as Date.now is not reactive
const currentTime = ref(Date.now())
// The interval ID for the window
let intervalId = null

const options = ref({
timeStyle: 'medium',
dateStyle: 'short',
relativeTime: 'long',
ignoreSeconds: false,
...unref(opts),
})
const wrappedOptions = computed(() => ({ ...unref(opts), ...options.value }))

/** ECMA Date object of the timestamp */
const date = computed(() => new Date(unref(timestamp)))

const formattedFullTime = computed(() => {
const formatter = new Intl.DateTimeFormat(getCanonicalLocale(), wrappedOptions.value.format)
return formatter.format(date.value)
})

/** Time string formatted for main text */
const formattedTime = computed(() => {
if (wrappedOptions.value.relativeTime !== false) {
const formatter = new Intl.RelativeTimeFormat(getCanonicalLocale(), { numeric: 'auto', style: wrappedOptions.value.relativeTime })

const diff = date.value - currentTime.value
const seconds = diff / 1000
if (Math.abs(seconds) <= 90) {
if (wrappedOptions.value.ignoreSeconds) {
return FEW_SECONDS_AGO[wrappedOptions.value.relativeTime]
} else {
return formatter.format(Math.round(seconds), 'second')
}
}
const minutes = seconds / 60
if (Math.abs(minutes) <= 90) {
return formatter.format(Math.round(minutes), 'minute')
}
const hours = minutes / 60
if (Math.abs(hours) <= 24) {
return formatter.format(Math.round(hours), 'hour')
}
const days = hours / 24
if (Math.abs(days) <= 6) {
return formatter.format(Math.round(days), 'day')
}
const weeks = days / 7
if (Math.abs(weeks) <= 4) {
return formatter.format(Math.round(weeks), 'week')
}
const months = days / 30
if (Math.abs(months) <= 12) {
return formatter.format(Math.round(months), 'month')
}
return formatter.format(Math.round(days / 365), 'year')
}
return formattedFullTime
})

// Set or clear interval if relative time is dis/enabled
watch([wrappedOptions], (opts) => {
window.clearInterval(intervalId)
intervalId = undefined
if (opts.relativeTime) {
intervalId = window.setInterval(() => { currentTime.value = new Date() }, 1000)
}
})

// Start the interval for setting the current time if relative time is enabled
onMounted(() => {
if (wrappedOptions.value.relativeTime !== false) {
intervalId = window.setInterval(() => { currentTime.value = new Date() }, 1000)
}
})

// ensure interval is cleared
onUnmounted(() => {
window.clearInterval(intervalId)
})

return {
formattedTime,
formattedFullTime,
options,
}
}
Loading

0 comments on commit 32e9a1f

Please sign in to comment.