diff --git a/packages/shared/useDateFormat/index.md b/packages/shared/useDateFormat/index.md index 65c2a86dfc5..a0d4e24fe48 100644 --- a/packages/shared/useDateFormat/index.md +++ b/packages/shared/useDateFormat/index.md @@ -25,8 +25,14 @@ Get the formatted date according to the string of tokens passed in, inspired by | `s` | 0-59 | The second | | `ss` | 00-59 | The second, 2-digits | | `SSS` | 000-999 | The millisecond, 3-digits | +| `A` | AM PM | The meridiem | +| `AA` | A.M. P.M. | The meridiem, periods | +| `a` | am pm | The meridiem, lowercase | +| `aa` | a.m. p.m. | The meridiem, lowercase and periods | | `d` | 0-6 | The day of the week, with Sunday as 0 | +- Meridiem is customizable by defining `customMeridiem` in `options`. + ## Usage ```html diff --git a/packages/shared/useDateFormat/index.test.ts b/packages/shared/useDateFormat/index.test.ts index fb674497165..734bc55676c 100644 --- a/packages/shared/useDateFormat/index.test.ts +++ b/packages/shared/useDateFormat/index.test.ts @@ -28,4 +28,41 @@ describe('useDateFormat', () => { it('should work with HH:mm:ss d', () => { expect(useDateFormat(new Date('2022-01-01 15:05:05'), 'HH:mm:ss d').value).toBe('15:05:05 6') }) + + describe('meridiem', () => { + it.each([ + // AM + { dateStr: '2022-01-01 03:05:05', formatStr: 'hh:mm:ss A', expected: '03:05:05 AM' }, + { dateStr: '2022-01-01 03:05:05', formatStr: 'hh:mm:ss AA', expected: '03:05:05 A.M.' }, + { dateStr: '2022-01-01 03:05:05', formatStr: 'hh:mm:ss a', expected: '03:05:05 am' }, + { dateStr: '2022-01-01 03:05:05', formatStr: 'hh:mm:ss aa', expected: '03:05:05 a.m.' }, + // PM + { dateStr: '2022-01-01 15:05:05', formatStr: 'hh:mm:ss A', expected: '03:05:05 PM' }, + { dateStr: '2022-01-01 15:05:05', formatStr: 'hh:mm:ss AA', expected: '03:05:05 P.M.' }, + { dateStr: '2022-01-01 15:05:05', formatStr: 'hh:mm:ss a', expected: '03:05:05 pm' }, + { dateStr: '2022-01-01 15:05:05', formatStr: 'hh:mm:ss aa', expected: '03:05:05 p.m.' }, + ])('should work with $formatStr', ({ dateStr, formatStr, expected }) => { + expect(useDateFormat(new Date(dateStr), formatStr).value).toBe(expected) + }) + + const customMeridiem = (hours: number, minutes: number, isLowercase?: boolean, hasPeriod?: boolean) => { + const m = hours > 11 ? (isLowercase ? 'μμ' : 'ΜΜ') : (isLowercase ? 'πμ' : 'ΠΜ') + return hasPeriod ? m.split('').reduce((acc, curr) => acc += `${curr}.`, '') : m + } + + it.each([ + // AM + { dateStr: '2022-01-01 03:05:05', formatStr: 'hh:mm:ss A', expected: '03:05:05 ΠΜ' }, + { dateStr: '2022-01-01 03:05:05', formatStr: 'hh:mm:ss AA', expected: '03:05:05 Π.Μ.' }, + { dateStr: '2022-01-01 03:05:05', formatStr: 'hh:mm:ss a', expected: '03:05:05 πμ' }, + { dateStr: '2022-01-01 03:05:05', formatStr: 'hh:mm:ss aa', expected: '03:05:05 π.μ.' }, + // PM + { dateStr: '2022-01-01 15:05:05', formatStr: 'hh:mm:ss A', expected: '03:05:05 ΜΜ' }, + { dateStr: '2022-01-01 15:05:05', formatStr: 'hh:mm:ss AA', expected: '03:05:05 Μ.Μ.' }, + { dateStr: '2022-01-01 15:05:05', formatStr: 'hh:mm:ss a', expected: '03:05:05 μμ' }, + { dateStr: '2022-01-01 15:05:05', formatStr: 'hh:mm:ss aa', expected: '03:05:05 μ.μ.' }, + ])('should work with custom meridiem with $formatStr', ({ dateStr, formatStr, expected }) => { + expect(useDateFormat(new Date(dateStr), formatStr, { customMeridiem }).value).toBe(expected) + }) + }) }) diff --git a/packages/shared/useDateFormat/index.ts b/packages/shared/useDateFormat/index.ts index 0c837e7c448..ddf77ccf599 100644 --- a/packages/shared/useDateFormat/index.ts +++ b/packages/shared/useDateFormat/index.ts @@ -4,10 +4,24 @@ import { computed } from 'vue-demi' export type DateLike = Date | number | string | undefined +export interface UseDateFormatOptions { + /** + * + */ + customMeridiem?: (hours: number, minutes: number, isLowercase?: boolean, hasPeriod?: boolean) => string +} + const REGEX_PARSE = /* #__PURE__ */ /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/ -const REGEX_FORMAT = /* #__PURE__ */ /\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g +const REGEX_FORMAT = /* #__PURE__ */ /\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a{1,2}|A{1,2}|m{1,2}|s{1,2}|Z{1,2}|SSS/g + +const defaultMeridiem = (hours: number, minutes: number, isLowercase?: boolean, hasPeriod?: boolean) => { + let m = (hours < 12 ? 'AM' : 'PM') + if (hasPeriod) + m = m.split('').reduce((acc, curr) => acc += `${curr}.`, '') + return isLowercase ? m.toLowerCase() : m +} -export const formatDate = (date: Date, formatStr: string) => { +export const formatDate = (date: Date, formatStr: string, options: UseDateFormatOptions) => { const years = date.getFullYear() const month = date.getMonth() const days = date.getDate() @@ -16,25 +30,30 @@ export const formatDate = (date: Date, formatStr: string) => { const seconds = date.getSeconds() const milliseconds = date.getMilliseconds() const day = date.getDay() - const matches: Record = { - YY: String(years).slice(-2), - YYYY: years, - M: month + 1, - MM: `${month + 1}`.padStart(2, '0'), - D: String(days), - DD: `${days}`.padStart(2, '0'), - H: String(hours), - HH: `${hours}`.padStart(2, '0'), - h: `${hours % 12 || 12}`.padStart(1, '0'), - hh: `${hours % 12 || 12}`.padStart(2, '0'), - m: String(minutes), - mm: `${minutes}`.padStart(2, '0'), - s: String(seconds), - ss: `${seconds}`.padStart(2, '0'), - SSS: `${milliseconds}`.padStart(3, '0'), - d: day, + const meridiem = options.customMeridiem ?? defaultMeridiem + const matches: Record string | number> = { + YY: () => String(years).slice(-2), + YYYY: () => years, + M: () => month + 1, + MM: () => `${month + 1}`.padStart(2, '0'), + D: () => String(days), + DD: () => `${days}`.padStart(2, '0'), + H: () => String(hours), + HH: () => `${hours}`.padStart(2, '0'), + h: () => `${hours % 12 || 12}`.padStart(1, '0'), + hh: () => `${hours % 12 || 12}`.padStart(2, '0'), + m: () => String(minutes), + mm: () => `${minutes}`.padStart(2, '0'), + s: () => String(seconds), + ss: () => `${seconds}`.padStart(2, '0'), + SSS: () => `${milliseconds}`.padStart(3, '0'), + A: () => meridiem(hours, minutes), + AA: () => meridiem(hours, minutes, false, true), + a: () => meridiem(hours, minutes, true), + aa: () => meridiem(hours, minutes, true, true), + d: () => day, } - return formatStr.replace(REGEX_FORMAT, (match, $1) => $1 || matches[match]) + return formatStr.replace(REGEX_FORMAT, (match, $1) => $1 || matches[match]()) } export const normalizeDate = (date: DateLike) => { @@ -54,7 +73,7 @@ export const normalizeDate = (date: DateLike) => { } } - return new Date(date!) + return new Date(date) } /** @@ -63,10 +82,11 @@ export const normalizeDate = (date: DateLike) => { * @see https://vueuse.org/useDateFormat * @param date * @param formatStr + * @param options */ -export function useDateFormat(date: MaybeComputedRef, formatStr: MaybeComputedRef = 'HH:mm:ss') { - return computed(() => formatDate(normalizeDate(resolveUnref(date)), resolveUnref(formatStr))) +export function useDateFormat(date: MaybeComputedRef, formatStr: MaybeComputedRef = 'HH:mm:ss', options: UseDateFormatOptions = {}) { + return computed(() => formatDate(normalizeDate(resolveUnref(date)), resolveUnref(formatStr), options)) } export type UseDateFormatReturn = ReturnType