Skip to content

Commit

Permalink
feat(useDateFormat): add date ordinal formatting (#3474)
Browse files Browse the repository at this point in the history
Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
  • Loading branch information
loongzhu and antfu committed Nov 9, 2023
1 parent 771e7ff commit 61ceb19
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 28 deletions.
61 changes: 34 additions & 27 deletions packages/shared/useDateFormat/index.md
Expand Up @@ -8,33 +8,40 @@ Get the formatted date according to the string of tokens passed in, inspired by

**List of all available formats (HH:mm:ss by default):**

| Format | Output | Description |
|--------| ---------------- |---------------------------------------|
| `YY` | 18 | Two-digit year |
| `YYYY` | 2018 | Four-digit year |
| `M` | 1-12 | The month, beginning at 1 |
| `MM` | 01-12 | The month, 2-digits |
| `MMM` | Jan-Dec | The abbreviated month name |
| `MMMM` | January-December | The full month name |
| `D` | 1-31 | The day of the month |
| `DD` | 01-31 | The day of the month, 2-digits |
| `H` | 0-23 | The hour |
| `HH` | 00-23 | The hour, 2-digits |
| `h` | 1-12 | The hour, 12-hour clock |
| `hh` | 01-12 | The hour, 12-hour clock, 2-digits |
| `m` | 0-59 | The minute |
| `mm` | 00-59 | The minute, 2-digits |
| `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 |
| Format | Output | Description |
|--------| ------------------------ |-----------------------------------------|
| `Yo` | 2018th | Ordinal formatted year |
| `YY` | 18 | Two-digit year |
| `YYYY` | 2018 | Four-digit year |
| `M` | 1-12 | The month, beginning at 1 |
| `Mo` | 1st, 2nd, ..., 12th | The month, ordinal formatted |
| `MM` | 01-12 | The month, 2-digits |
| `MMM` | Jan-Dec | The abbreviated month name |
| `MMMM` | January-December | The full month name |
| `D` | 1-31 | The day of the month |
| `Do` | 1st, 2nd, ..., 31st | The day of the month, ordinal formatted |
| `DD` | 01-31 | The day of the month, 2-digits |
| `H` | 0-23 | The hour |
| `Ho` | 0th, 1st, 2nd, ..., 23rd | The hour, ordinal formatted |
| `HH` | 00-23 | The hour, 2-digits |
| `h` | 1-12 | The hour, 12-hour clock |
| `ho` | 1st, 2nd, ..., 12th | The hour, 12-hour clock, sorted |
| `hh` | 01-12 | The hour, 12-hour clock, 2-digits |
| `m` | 0-59 | The minute |
| `mo` | 0th, 1st, ..., 59th | The minute, ordinal formatted |
| `mm` | 00-59 | The minute, 2-digits |
| `s` | 0-59 | The second |
| `so` | 0th, 1st, ..., 59th | The second, ordinal formatted |
| `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`.

Expand Down
6 changes: 6 additions & 0 deletions packages/shared/useDateFormat/index.test.ts
Expand Up @@ -67,6 +67,12 @@ 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')
})
it('should work with Mo Do Yo', () => {
expect(useDateFormat(new Date('2022-01-01 15:05:05'), 'MMMM Do Yo', { locales: 'en-US' }).value).toBe('January 1st 2022nd')
expect(useDateFormat(new Date('2022-12-11 15:05:05'), 'MMMM Do Yo', { locales: 'en-US' }).value).toBe('December 11th 2022nd')
expect(useDateFormat(new Date('2023-12-12 15:05:05'), 'MMMM Do Yo', { locales: 'en-US' }).value).toBe('December 12th 2023rd')
expect(useDateFormat(new Date('2024-12-23 15:05:05'), 'MMMM Do Yo', { locales: 'en-US' }).value).toBe('December 23rd 2024th')
})

describe('meridiem', () => {
it.each([
Expand Down
15 changes: 14 additions & 1 deletion packages/shared/useDateFormat/index.ts
Expand Up @@ -20,7 +20,7 @@ export interface UseDateFormatOptions {
}

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{1,2}|A{1,2}|m{1,2}|s{1,2}|Z{1,2}|SSS/g
const REGEX_FORMAT = /* #__PURE__ */ /[YMDHhms]o|\[([^\]]+)]|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

function defaultMeridiem(hours: number, minutes: number, isLowercase?: boolean, hasPeriod?: boolean) {
let m = (hours < 12 ? 'AM' : 'PM')
Expand All @@ -29,6 +29,12 @@ function defaultMeridiem(hours: number, minutes: number, isLowercase?: boolean,
return isLowercase ? m.toLowerCase() : m
}

function formatOrdinal(num: number) {
const suffixes = ['th', 'st', 'nd', 'rd']
const v = num % 100
return num + (suffixes[(v - 20) % 10] || suffixes[v] || suffixes[0])
}

export function formatDate(date: Date, formatStr: string, options: UseDateFormatOptions = {}) {
const years = date.getFullYear()
const month = date.getMonth()
Expand All @@ -40,21 +46,28 @@ export function formatDate(date: Date, formatStr: string, options: UseDateFormat
const day = date.getDay()
const meridiem = options.customMeridiem ?? defaultMeridiem
const matches: Record<string, () => string | number> = {
Yo: () => formatOrdinal(years),
YY: () => String(years).slice(-2),
YYYY: () => years,
M: () => month + 1,
Mo: () => formatOrdinal(month + 1),
MM: () => `${month + 1}`.padStart(2, '0'),
MMM: () => date.toLocaleDateString(options.locales, { month: 'short' }),
MMMM: () => date.toLocaleDateString(options.locales, { month: 'long' }),
D: () => String(days),
Do: () => formatOrdinal(days),
DD: () => `${days}`.padStart(2, '0'),
H: () => String(hours),
Ho: () => formatOrdinal(hours),
HH: () => `${hours}`.padStart(2, '0'),
h: () => `${hours % 12 || 12}`.padStart(1, '0'),
ho: () => formatOrdinal(hours % 12 || 12),
hh: () => `${hours % 12 || 12}`.padStart(2, '0'),
m: () => String(minutes),
mo: () => formatOrdinal(minutes),
mm: () => `${minutes}`.padStart(2, '0'),
s: () => String(seconds),
so: () => formatOrdinal(seconds),
ss: () => `${seconds}`.padStart(2, '0'),
SSS: () => `${milliseconds}`.padStart(3, '0'),
d: () => day,
Expand Down

0 comments on commit 61ceb19

Please sign in to comment.