pickadate
Version:
The composable date & time picker.
196 lines (156 loc) • 5.29 kB
JavaScript
// @flow
import type { TemplateHookWords } from 'pickadate/types'
import * as jsUtil from 'pickadate/utils/jsUtil'
//////////////
// CHECKERS //
//////////////
export const isSameDate = (one: mixed, two: mixed) =>
one instanceof Date &&
two instanceof Date &&
isSameMonth(one, two) &&
one.getDate() === two.getDate()
export const isSameMonth = (one: mixed, two: mixed) =>
one instanceof Date &&
two instanceof Date &&
isSameYear(one, two) &&
one.getMonth() === two.getMonth()
export const isSameYear = (one: mixed, two: mixed) =>
one instanceof Date &&
two instanceof Date &&
one.getFullYear() === two.getFullYear()
export const isBeforeDate = (one: mixed, two: mixed) =>
one instanceof Date &&
two instanceof Date &&
(one.getFullYear() < two.getFullYear() ||
(isSameYear(one, two) && one.getMonth() < two.getMonth()) ||
(isSameMonth(one, two) && one.getDate() < two.getDate()))
export const isSameOrBeforeDate = (one: mixed, two: mixed) =>
isSameDate(one, two) || isBeforeDate(one, two)
////////////
// FORMAT //
////////////
/**
* A mapping of template hooks to formatters.
*/
const HOOK_FORMATTER = {
D: dateObject => `${dateObject.getDate()}`,
DD: dateObject => `${jsUtil.padZero(HOOK_FORMATTER.D(dateObject), 2)}`,
DDD: (dateObject, words) => words[dateObject.getDay()],
DDDD: (dateObject, words) => words[dateObject.getDay()],
M: dateObject => `${dateObject.getMonth() + 1}`,
MM: dateObject => `${jsUtil.padZero(HOOK_FORMATTER.M(dateObject), 2)}`,
MMM: (dateObject, words) => words[dateObject.getMonth()],
MMMM: (dateObject, words) => words[dateObject.getMonth()],
YYYY: dateObject => `${dateObject.getFullYear()}`,
H: dateObject => `${dateObject.getHours()}`,
HH: dateObject => `${jsUtil.padZero(HOOK_FORMATTER.H(dateObject), 2)}`,
h: dateObject => `${dateObject.getHours() % 12 || 12}`,
hh: dateObject => `${jsUtil.padZero(HOOK_FORMATTER.h(dateObject), 2)}`,
m: dateObject => `${dateObject.getMinutes()}`,
mm: dateObject => `${jsUtil.padZero(HOOK_FORMATTER.m(dateObject), 2)}`,
a: dateObject => (dateObject.getHours() < 12 ? 'a.m.' : 'p.m.'),
A: dateObject => (dateObject.getHours() < 12 ? 'AM' : 'PM'),
s: dateObject => `${dateObject.getSeconds()}`,
ss: dateObject => `${jsUtil.padZero(HOOK_FORMATTER.s(dateObject), 2)}`,
x: dateObject => `${dateObject.getTime()}`,
}
const getStartingDigits = string => string.replace(/(^\d*)(.*)/, '$1')
const getStartingWord = string => string.replace(/(^\w*)(.*)/, '$1')
const getStartingLowerCaseMeridiem = string =>
string.replace(/(^[ap]\.m\.)?(.*)/, '$1')
const getStartingUpperCaseMeridiem = string =>
string.replace(/(^[AP]M)?(.*)/, '$1')
/**
* A mapping of template hooks to parsers.
*/
const HOOK_PARSER = {
D: getStartingDigits,
DD: getStartingDigits,
DDD: getStartingWord,
DDDD: getStartingWord,
M: getStartingDigits,
MM: getStartingDigits,
MMM: getStartingWord,
MMMM: getStartingWord,
YYYY: getStartingDigits,
H: getStartingDigits,
HH: getStartingDigits,
h: getStartingDigits,
hh: getStartingDigits,
m: getStartingDigits,
mm: getStartingDigits,
a: getStartingLowerCaseMeridiem,
A: getStartingUpperCaseMeridiem,
s: getStartingDigits,
ss: getStartingDigits,
x: getStartingDigits,
}
{
/* istanbul ignore next */
if (process.env.NODE_ENV === 'development') {
const extraFormatterKeys = Object.keys(HOOK_FORMATTER).filter(
key => !HOOK_PARSER[key]
)
console.assert(
!extraFormatterKeys.length,
'Missing keys to parse with',
extraFormatterKeys
)
const extraParserKeys = Object.keys(HOOK_PARSER).filter(
key => !HOOK_FORMATTER[key]
)
console.assert(
!extraParserKeys.length,
'Missing keys to format with',
extraParserKeys
)
}
}
/**
* A collection of the template hooks.
*/
const HOOKS = Object.keys(HOOK_FORMATTER)
/**
* A regular expression that matches all segments of a template string.
*/
const HOOKS_REGEXP = new RegExp(
[
// Match any characters escaped with square brackets
'(\\[.*?\\])',
// Match any template hooks
`(?:\\b(${HOOKS.join('|')})\\b)`,
// Match all other characters
'(.)',
].join('|'),
'g'
)
type Format = (
dateObject: Date,
template: string,
templateHookWords: TemplateHookWords
) => string
export const format: Format = (dateObject, template, templateHookWords) =>
// Match hooks within the template
matchHooks(template)
// Map through the matches while formatting template hooks
// and removing the hooks of escaped characters
.map(match => {
if (match.index === 2) {
const formatter = HOOK_FORMATTER[match.chunk]
return formatter(dateObject, templateHookWords[match.chunk])
}
if (match.index === 1) {
return match.chunk.replace(/^\[(.*)]$/, '$1')
}
return match.chunk
})
// Join the chunks together into a string
.join('')
const matchHooks = template =>
template
// Split the template using the regular expression
.split(HOOKS_REGEXP)
// Map the chunks to keep a reference of their match group index
.map((chunk, index) => ({ chunk, index: index % 4 }))
// Filter out false-y chunks
.filter(match => !!match.chunk)