Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(useDateFormat): support meridiem format #2011

Merged
merged 6 commits into from Sep 23, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 27 additions & 1 deletion packages/shared/useDateFormat/index.md
Expand Up @@ -27,11 +27,17 @@ 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 |
| `dd` | S-S | The min name of the day of the week |
| `ddd` | Sun-Sat | The short name of the day of the week |
| `dddd` | Sunday-Saturday | The name of the day of the week |

- Meridiem is customizable by defining `customMeridiem` in `options`.
webfansplz marked this conversation as resolved.
Show resolved Hide resolved

## Usage

### Basic
Expand All @@ -51,7 +57,7 @@ const formatted = useDateFormat(useNow(), 'YYYY-MM-DD HH:mm:ss')
</template>
```

### Use with locale
### Use with locales

```html
<script setup lang="ts">
Expand All @@ -67,3 +73,23 @@ const formatted = useDateFormat(useNow(), 'YYYY-MM-DD (ddd)', { locales: 'en-US'
<div>{{ formatted }}</div>
</template>
```

### Use with custom meridiem

```html
<script setup lang="ts">

import { ref, computed } from 'vue-demi'
import { useNow, useDateFormat } from '@vueuse/core'

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
}

const am = useDateFormat('2022-01-01 05:05:05', 'hh:mm:ss A', { customMeridiem })
// am.value = '05:05:05 ΠΜ'
const pm = useDateFormat('2022-01-01 17:05:05', 'hh:mm:ss AA', { customMeridiem })
// pm.value = '05:05:05 Μ.Μ.'

</script>
37 changes: 37 additions & 0 deletions packages/shared/useDateFormat/index.test.ts
Expand Up @@ -43,4 +43,41 @@ describe('useDateFormat', () => {
it('should work with MMMM DD YYYY', () => {
expect(useDateFormat(new Date('2022-01-01 15:05:05'), 'MMMM DD YYYY', { locales: 'en-US' }).value).toBe('January 01 2022')
})

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)
})
})
})
42 changes: 30 additions & 12 deletions packages/shared/useDateFormat/index.ts
Expand Up @@ -11,12 +11,25 @@ export interface UseDateFormatOptions {
* [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#locales_argument).
*/
locales?: Intl.LocalesArgument

/**
* A custom function to re-modify the way to display meridiem
*
*/
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, locales?: Intl.LocalesArgument) => {
export const formatDate = (date: Date, formatStr: string, options: UseDateFormatOptions) => {
const years = date.getFullYear()
const month = date.getMonth()
const days = date.getDate()
Expand All @@ -25,13 +38,14 @@ export const formatDate = (date: Date, formatStr: string, locales?: Intl.Locales
const seconds = date.getSeconds()
const milliseconds = date.getMilliseconds()
const day = date.getDay()
const meridiem = options.customMeridiem ?? defaultMeridiem
const matches: Record<string, () => string | number> = {
YY: () => String(years).slice(-2),
YYYY: () => years,
M: () => month + 1,
MM: () => `${month + 1}`.padStart(2, '0'),
MMM: () => date.toLocaleDateString(locales, { month: 'short' }),
MMMM: () => date.toLocaleDateString(locales, { month: 'long' }),
MMM: () => date.toLocaleDateString(options.locales, { month: 'short' }),
MMMM: () => date.toLocaleDateString(options.locales, { month: 'long' }),
D: () => String(days),
DD: () => `${days}`.padStart(2, '0'),
H: () => String(hours),
Expand All @@ -44,9 +58,13 @@ export const formatDate = (date: Date, formatStr: string, locales?: Intl.Locales
ss: () => `${seconds}`.padStart(2, '0'),
SSS: () => `${milliseconds}`.padStart(3, '0'),
d: () => day,
dd: () => date.toLocaleDateString(locales, { weekday: 'narrow' }),
ddd: () => date.toLocaleDateString(locales, { weekday: 'short' }),
dddd: () => date.toLocaleDateString(locales, { weekday: 'long' }),
dd: () => date.toLocaleDateString(options.locales, { weekday: 'narrow' }),
ddd: () => date.toLocaleDateString(options.locales, { weekday: 'short' }),
dddd: () => date.toLocaleDateString(options.locales, { weekday: 'long' }),
A: () => meridiem(hours, minutes),
AA: () => meridiem(hours, minutes, false, true),
a: () => meridiem(hours, minutes, true),
aa: () => meridiem(hours, minutes, true, true),
}
return formatStr.replace(REGEX_FORMAT, (match, $1) => $1 || matches[match]())
}
Expand All @@ -68,20 +86,20 @@ export const normalizeDate = (date: DateLike) => {
}
}

return new Date(date!)
return new Date(date)
}

/**
* Get the formatted date according to the string of tokens passed in.
*
* @see https://vueuse.org/useDateFormat
* @param date
* @param formatStr
* @param options
* @param date - The date to format, can either be a `Date` object, a timestamp, or a string
* @param formatStr - The combination of tokens to format the date
* @param options - UseDateFormatOptions
*/

export function useDateFormat(date: MaybeComputedRef<DateLike>, formatStr: MaybeComputedRef<string> = 'HH:mm:ss', options: UseDateFormatOptions = {}) {
return computed(() => formatDate(normalizeDate(resolveUnref(date)), resolveUnref(formatStr), options?.locales))
return computed(() => formatDate(normalizeDate(resolveUnref(date)), resolveUnref(formatStr), options))
}

export type UseDateFormatReturn = ReturnType<typeof useDateFormat>