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

format range function #3577

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions package.json
Expand Up @@ -309,6 +309,10 @@
"require": "./formatISO.js",
"import": "./formatISO.mjs"
},
"./formatRange": {
"require": "./formatRange.js",
"import": "./formatRange.mjs"
},
"./formatISO9075": {
"require": "./formatISO9075.js",
"import": "./formatISO9075.mjs"
Expand Down
88 changes: 88 additions & 0 deletions src/formatRange/index.ts
@@ -0,0 +1,88 @@
export interface DateFormat {
day?: 'numeric' | '2-digit'
month?: 'numeric' | '2-digit' | 'long' | 'short' | 'narrow'
year?: 'numeric' | '2-digit'
}

export default function formatRange<DateType extends Date>(
from: DateType,
to: DateType,
format: DateFormat
): string | undefined {
const defaultFormat: DateFormat = {
day: 'numeric',
month: 'numeric',
year: 'numeric',
}
const mergedFormats: DateFormat = { ...defaultFormat, ...format }

const fromFormattedParts: Intl.DateTimeFormatPart[] = new Intl.DateTimeFormat(
undefined,
mergedFormats
)
.formatToParts(from)
.filter((part) => part.type !== 'literal')
const toFormattedParts: Intl.DateTimeFormatPart[] = new Intl.DateTimeFormat(
undefined,
mergedFormats
)
.formatToParts(to)
.filter((part) => part.type !== 'literal')

if (fromFormattedParts.length > 3 || toFormattedParts.length > 3) {
console.error('Unexpected length of formatted parts array.')
return undefined
}

const datePartsType: string[] = fromFormattedParts.map((part) => part.type)

if (fromFormattedParts === toFormattedParts) {
return fromFormattedParts.join(' ')
} else if (
from.getMonth() === to.getMonth() &&
from.getFullYear() === to.getFullYear()
) {
let format: string = `${getDatePartValueByType(
'day',
fromFormattedParts
)}-${getDatePartValueByType('day', toFormattedParts)}`
datePartsType
.filter((type) => type !== 'day')
.forEach((type) => {
format += ` ${getDatePartValueByType(type, fromFormattedParts)}`
})

return format
} else if (from.getFullYear() === to.getFullYear()) {
const fromFormat: string[] = []
const toFormat: string[] = []

datePartsType
.filter((type) => type !== 'year')
.forEach((type) => {
fromFormat.push(getDatePartValueByType(type, fromFormattedParts))
toFormat.push(getDatePartValueByType(type, toFormattedParts))
})

return `${fromFormat.join(' ')}-${toFormat.join(
' '
)} ${getDatePartValueByType('year', fromFormattedParts)}`
} else {
const fromFormat: string[] = []
const toFormat: string[] = []

datePartsType.forEach((type) => {
fromFormat.push(getDatePartValueByType(type, fromFormattedParts))
toFormat.push(getDatePartValueByType(type, toFormattedParts))
})

return `${fromFormat.join(' ')}-${toFormat.join(' ')}`
}
}

function getDatePartValueByType(
type: string,
dateParts: Intl.DateTimeFormatPart[]
): string {
return dateParts.filter((part) => part.type === type)[0].value
}
163 changes: 163 additions & 0 deletions src/formatRange/test.ts
@@ -0,0 +1,163 @@
import formatRange from './index'
import { describe, it } from 'vitest'
import assert from 'assert'

const from = new Date(2023, 1, 1)
const to = new Date(2023, 1, 2)

const from2 = new Date(2023, 1, 1)
const to2 = new Date(2023, 2, 12)

const from3 = new Date(2023, 1, 1)
const to3 = new Date(2024, 2, 12)

describe('formatRange', () => {
it('only day range', () => {
assert(
formatRange(from, to, { day: 'numeric', month: 'long', year: 'numeric' }),
'1-2 februára 2023'
)
assert(
formatRange(from, to, {
day: 'numeric',
month: undefined,
year: 'numeric',
}),
'1-2 2023'
)
assert(
formatRange(from, to, {
day: 'numeric',
month: 'short',
year: 'numeric',
}),
'1-2 2 2023'
)
assert(
formatRange(from, to, {
day: 'numeric',
month: '2-digit',
year: 'numeric',
}),
'1-2 2 2023'
)
assert(
formatRange(from, to, {
day: 'numeric',
month: 'numeric',
year: 'numeric',
}),
'1-2 2 2023'
)
assert(
formatRange(from, to, {
day: 'numeric',
month: 'narrow',
year: 'numeric',
}),
'1-2 2 2023'
)
})

it('only month range', () => {
assert(
formatRange(from2, to2, {
day: 'numeric',
month: 'long',
year: 'numeric',
}),
'1 februára-12 marca 2023'
)
assert(
formatRange(from2, to2, {
day: 'numeric',
month: 'short',
year: 'numeric',
}),
'1 2-12 3 2023'
)
assert(
formatRange(from2, to2, {
day: 'numeric',
month: '2-digit',
year: 'numeric',
}),
'1 02-12 03 2023'
)
assert(
formatRange(from2, to2, {
day: 'numeric',
month: 'numeric',
year: 'numeric',
}),
'1 2-12 3 2023'
)
assert(
formatRange(from2, to2, {
day: 'numeric',
month: 'narrow',
year: 'numeric',
}),
'1 2-12 3 2023'
)
assert(
formatRange(from2, to2, {
day: 'numeric',
month: undefined,
year: 'numeric',
}),
'1-12 2023'
)
})

it('only month range', () => {
assert(
formatRange(from3, to3, {
day: 'numeric',
month: 'long',
year: 'numeric',
}),
'1 februára 2023-12 marca 2024'
)
assert(
formatRange(from3, to3, {
day: 'numeric',
month: 'short',
year: 'numeric',
}),
'1 2 2023-12 3 2024'
)
assert(
formatRange(from3, to3, {
day: 'numeric',
month: '2-digit',
year: 'numeric',
}),
'1 02 2023-12 03 2024'
)
assert(
formatRange(from3, to3, {
day: 'numeric',
month: 'numeric',
year: 'numeric',
}),
'1 2 2023-12 3 2024'
)
assert(
formatRange(from3, to3, {
day: 'numeric',
month: 'narrow',
year: 'numeric',
}),
'1 2 2023-12 3 2024'
)
assert(
formatRange(from3, to3, {
day: 'numeric',
month: undefined,
year: 'numeric',
}),
'2023 1-2024 12'
)
})
})
1 change: 1 addition & 0 deletions src/index.ts
Expand Up @@ -69,6 +69,7 @@ export { default as formatDistanceToNow } from './formatDistanceToNow/index'
export { default as formatDistanceToNowStrict } from './formatDistanceToNowStrict/index'
export { default as formatDuration } from './formatDuration/index'
export { default as formatISO } from './formatISO/index'
export { default as formatRange } from './formatRange/index'
export { default as formatISO9075 } from './formatISO9075/index'
export { default as formatISODuration } from './formatISODuration/index'
export { default as formatRFC3339 } from './formatRFC3339/index'
Expand Down