shineout
Version:
Shein 前端组件库
449 lines (402 loc) • 13.5 kB
text/typescript
import dayjs from 'dayjs'
import zonedTimeToUtc from 'date-fns-tz/zonedTimeToUtc'
import utcToZonedTime from 'date-fns-tz/utcToZonedTime'
import advancedFormat from 'dayjs/plugin/advancedFormat'
import quarterOfYear from 'dayjs/plugin/quarterOfYear'
import isoWeek from 'dayjs/plugin/isoWeek'
import weekday from 'dayjs/plugin/weekday'
import weekYear from 'dayjs/plugin/weekYear'
import weekOfYear from 'dayjs/plugin/weekOfYear'
import customParseFormat from 'dayjs/plugin/customParseFormat'
import relativeTime from 'dayjs/plugin/relativeTime'
import enLocale from 'dayjs/locale/en'
import { DateTimeType } from './Props'
import { DateMode } from './Props'
interface DateOptions {
timeZone?: string
weekStartsOn?: number
}
const en2Locate = {
...enLocale,
name: 'en2',
weekStart: 1,
}
dayjs.locale(en2Locate, undefined, true)
dayjs.extend(advancedFormat)
dayjs.extend(isoWeek)
dayjs.extend(relativeTime)
dayjs.extend(weekday)
dayjs.extend(weekOfYear)
dayjs.extend(weekYear)
dayjs.extend(customParseFormat)
dayjs.extend(quarterOfYear)
const TIME_FORMAT = 'HH:mm:ss'
export const compatibleFmt = (fmt?: string) => {
if (typeof fmt !== 'string') return fmt
const trans = {
yy: 'YY',
d: 'D',
a: 'A',
t: 'X',
T: 'x',
RRRR: 'GGGG',
I: 'W',
}
let result = fmt
Object.keys(trans).forEach((key: keyof typeof trans) => {
result = result.replace(new RegExp(key, 'g'), trans[key])
})
if (result !== fmt) {
console.warn(`invalid datepicker format: ${fmt} please use ${result}`)
}
return result
}
function getDayJsLocate(options?: DateOptions) {
if (options && options.weekStartsOn === 1) return 'en2'
return 'en'
}
function transDateWithZone(dd: Date, options: DateOptions = {}, back = false) {
if (options.timeZone) {
const timezoneHH = /^([+-]\d{2})$/
// 只放开两位时区
if (timezoneHH.test(options.timeZone)) {
const num = +options.timeZone
if (num <= 13 && num >= -12) {
return back ? zonedTimeToUtc(dd, options.timeZone) : utcToZonedTime(dd, options.timeZone)
}
}
console.error(new Error(`timeZone is not supported: ${options.timeZone}`))
}
return new Date(dd)
}
function addDays(date: Date, offset: number, options: DateOptions) {
const zd = transDateWithZone(date, options)
const d = dayjs(zd)
.add(offset, 'day')
.toDate()
const ud = transDateWithZone(d, options, true)
return ud
}
function addMonths(date: Date, offset: number, options: DateOptions) {
const zd = transDateWithZone(date, options)
const d = dayjs(zd)
.add(offset, 'month')
.toDate()
const ud = transDateWithZone(d, options, true)
return ud
}
function addSeconds(date: Date, offset: number, options: DateOptions) {
const zd = transDateWithZone(date, options)
const d = dayjs(zd)
.add(offset, 'seconds')
.toDate()
const ud = transDateWithZone(d, options, true)
return ud
}
function addYears(date: Date, offset: number, options: DateOptions) {
const zd = transDateWithZone(date, options)
const d = dayjs(zd)
.add(offset, 'year')
.toDate()
const ud = transDateWithZone(d, options, true)
return ud
}
function changeDate(date: Date, type: DateMode, num: number, options: DateOptions) {
const zd = transDateWithZone(date, options)
// type is year month ...
const d = (dayjs(zd) as any)[type](num).toDate()
const ud = transDateWithZone(d, options, true)
return ud
}
function getDateInfo(date: Date, type: DateMode, options: DateOptions): number {
const zd = transDateWithZone(date, options)
return (dayjs(zd) as any)[type]()
}
function compareAsc(dateA: DateTimeType, dateB: DateTimeType) {
if (!dateA || !dateB) return NaN
const a = dayjs(dateA).valueOf()
const b = dayjs(dateB).valueOf()
if (!a || !b) return NaN
if (a === b) return 0
return a > b ? 1 : -1
}
function format(date: Date, fmt?: string, options: DateOptions = {}) {
if (!date) return 'Invalid Date'
const fmt2 = compatibleFmt(fmt)
let zd = date
if (fmt2 !== 'X' && fmt2 !== 'x') {
zd = transDateWithZone(date, options)!
}
const dd = dayjs(zd).locale(getDayJsLocate(options))
const result = dd.format(fmt2)
return result
}
function isSameMonth(date1: Date, date2: Date, options: DateOptions = {}) {
return date1 && date2 && format(date1, 'YYYY-MM', options) === format(date2, 'YYYY-MM', options)
}
function isSameDay(date1: Date, date2: Date, options: DateOptions) {
return date1 && date2 && format(date1, 'YYYY-MM-DD', options) === format(date2, 'YYYY-MM-DD', options)
}
function isSameWeek(date1: Date, date2: Date, options: DateOptions) {
if (!date1 || !date2) return false
return date1 && date2 && format(date1, 'gggg-ww', options) === format(date2, 'gggg-ww', options)
}
function isSameQuarter(date1: Date, date2: Date, options: DateOptions) {
if (!date1 || !date2) return false
return date1 && date2 && format(date1, 'YYYY Q', options) === format(date2, 'YYYY Q', options)
}
function isValid(date: DateTimeType) {
if (!date) return false
if (!(date instanceof Date)) return false
return dayjs(date).isValid()
}
function parse(d: string, fmt?: string, options?: DateOptions) {
if (!d) return new Date('')
// should clear[xxx]
const reg = /[[]([^[^\]]+?)[\]]/g
const date = d && typeof d === 'string' && d.replace ? d.replace(reg, ' ') : d
const fmt2 = compatibleFmt(fmt)!.replace(reg, ' ')
// handle IOS Year Week
const index = fmt2.indexOf('GGGG')
if (index >= 0 && typeof date === 'string') {
const year = date.slice(index, index + 5)
const weekIndex = fmt2.indexOf('WW')
const week = weekIndex >= 0 ? date.slice(weekIndex, weekIndex + 3) : 1
const result = dayjs(`${year}-06-15`, 'YYYY-MM-DD')
.locale(getDayJsLocate(options))
.isoWeek(Number(week))
.toDate()
return transDateWithZone(result, options, true)
}
// handle Quarter
const quarterIndex = fmt2.indexOf('Q')
if (quarterIndex >= 0 && typeof date === 'string') {
const quarter = date.slice(quarterIndex, quarterIndex + 2)
const result = dayjs(date, fmt2)
.locale(getDayJsLocate(options))
.quarter(Number(quarter))
.toDate()
return transDateWithZone(result, options, true)
}
// dayjs parse stamp with timeZone have bug
if (fmt2 === 'x' || fmt2 === 'X') {
let stamp = +date
if (fmt2 === 'X') stamp *= 1000
return new Date(stamp)
}
const result = dayjs(date, fmt2, getDayJsLocate(options)).toDate()
return transDateWithZone(result, options, true)
}
function toDate(day: DateTimeType, options?: DateOptions): Date {
if (!day) return new Date('')
if (day instanceof Date) return dayjs(day).toDate()
if (typeof day === 'number') return new Date(day)
if (typeof day === 'string') return transDateWithZone(dayjs(day).toDate(), options, true)
return dayjs(day).toDate()
}
function getDaysOfMonth(dirtyDate: DateTimeType, options: DateOptions) {
const date = toDate(dirtyDate, options)
const temp = dayjs(transDateWithZone(date, options))
let current = dayjs(transDateWithZone(date, options))
.locale(getDayJsLocate(options))
.startOf('month')
.startOf('week')
.hour(temp.hour())
.minute(temp.minute())
.second(temp.second())
.millisecond(temp.millisecond())
const days = []
let index = 0
while (index < 42) {
days.push(transDateWithZone(current.toDate(), options, true))
current = current.add(1, 'day')
index += 1
}
return days
}
function isInvalid(date: unknown) {
// eslint-disable-next-line
return isNaN(date as number)
}
// function toDateWithFormat(dirtyDate: Date, fmt: string, def: DateTimeType, options: DateOptions): Date
// function toDateWithFormat(dirtyDate: DateTimeType, fmt: string, def: DateTimeType, options: DateOptions): DateTimeType
function toDateWithFormat(dirtyDate: DateTimeType, fmt?: string, def?: Date, options?: DateOptions) {
let date: Date
if (typeof dirtyDate === 'string') {
date = parse(dirtyDate, fmt, options)
const str = format(date, fmt, options)
if (str !== dirtyDate) {
date = toDate(dirtyDate, options)
}
} else date = toDate(dirtyDate, options)
if (isInvalid(date)) date = def!
return date
}
function compareDay(dateLeft: Date, dateRight: Date, pad = 0, options: DateOptions) {
if (!dateLeft || !dateRight) return NaN
const left = dayjs(transDateWithZone(dateLeft, options))
.startOf('date')
.toDate()
const right = dayjs(transDateWithZone(dateRight, options))
.startOf('date')
.add(pad, 'day')
.toDate()
return compareAsc(left, right)
}
function compareMonth(dateLeft: Date, dateRight: Date, pad = 0, options: DateOptions) {
if (!dateLeft || !dateRight) return 0
const left = dayjs(transDateWithZone(dateLeft, options))
.startOf('month')
.toDate()
const right = dayjs(transDateWithZone(dateRight, options))
.startOf('month')
.add(pad, 'month')
.toDate()
return compareAsc(left, right)
}
function compareWeek(dateLeft: Date, dateRight: Date, pad = 0, options: DateOptions) {
if (!dateLeft || !dateRight) return 0
const left = dayjs(transDateWithZone(dateLeft, options))
.startOf('isoWeek')
.toDate()
const right = dayjs(transDateWithZone(dateRight, options))
.startOf('isoWeek')
.add(pad, 'week')
.toDate()
return compareAsc(left, right)
}
function compareYear(dateLeft: Date, dateRight: Date, pad = 0, options: DateOptions) {
if (!dateLeft || !dateRight) return 0
const left = dayjs(transDateWithZone(dateLeft, options))
.startOf('year')
.toDate()
const right = dayjs(transDateWithZone(dateRight, options))
.startOf('year')
.add(pad, 'year')
.toDate()
return compareAsc(left, right)
}
function compareQuarter(dateLeft: Date, dateRight: Date, pad = 0, options: DateOptions) {
if (!dateLeft || !dateRight) return 0
const left = dayjs(transDateWithZone(dateLeft, options))
.startOf('quarter')
.toDate()
const right = dayjs(transDateWithZone(dateRight, options))
.startOf('quarter')
.add(pad, 'quarter')
.toDate()
return compareAsc(left, right)
}
function newDate(defaultDate?: Date | DateTimeType, options?: DateOptions) {
const date = defaultDate ? toDate(defaultDate, options) : new Date()
const zd = transDateWithZone(date, options)
const dd = dayjs(zd)
.startOf('date')
.toDate()
const ud = transDateWithZone(dd, options, true)
return ud
}
function setTime(date: Date, old: Date, options?: DateOptions) {
const zd = transDateWithZone(date, options)
const oldZd = transDateWithZone(old, options)
zd.setHours(oldZd.getHours())
zd.setMinutes(oldZd.getMinutes())
zd.setSeconds(oldZd.getSeconds())
zd.setMilliseconds(oldZd.getMilliseconds())
const ud = transDateWithZone(zd, options, true)
return ud
}
function cloneTime(date: Date, old: Date, fmt?: string, options?: DateOptions) {
if (!date) return date
const oldDate = toDateWithFormat(old, fmt, undefined, options)
if (isInvalid(oldDate)) return date
return setTime(date, oldDate, options)
}
function formatDateWithDefaultTime(
date: Date,
value: Date | undefined,
defaultTime: Date | undefined,
fmt: string,
options: DateOptions
) {
if (!date) return date
if (value) return setTime(date, value, options)
if (!defaultTime) return date
const dateHMS = toDateWithFormat(defaultTime, TIME_FORMAT, undefined, options)
if (isInvalid(dateHMS)) return date
const nDate = cloneTime(date, defaultTime, TIME_FORMAT, options)
return format(nDate, fmt, options)
}
function clearHMS(date: Date, options: DateOptions) {
if (!isValid(date)) return date
const zd = transDateWithZone(date, options)
const dd = dayjs(zd)
.startOf('date')
.toDate()
const ud = transDateWithZone(dd, options, true)
return ud
}
function compareDateArray(arr1: Date[], arr2: Date[], type = 'date', options: DateOptions) {
if (!arr1 || !arr2 || arr1.length !== arr2.length) return false
return arr1.every((v, i) => {
if (!v || !arr2[i]) return false
if (type === 'week') return format(v, 'GGGG WW', options) === format(arr2[i], 'GGGG WW', options)
return v.getTime() === arr2[i].getTime()
})
}
function getFormat(fo: string) {
let defaultFormat = 'YYYY-MM-DD HH:mm:ss.SSS'
;['H', 'm', 's', 'S', 'h'].map(v => {
if (fo.indexOf(v) <= -1) {
const reg = new RegExp(`${v}`, 'g')
defaultFormat = defaultFormat.replace(reg, '0')
}
return v
})
return defaultFormat
}
function resetTimeByFormat(value: Date | undefined, fo: string, options: DateOptions) {
if (!value) return null
const date = toDate(value, options)
return toDate(format(date, getFormat(fo), options), options)
}
function formatted(date: Date, fmt: string | Function, options: DateOptions) {
const offsetDate = transDateWithZone(date, options)
if (typeof fmt === 'function') return fmt(date, offsetDate)
return format(date, fmt, options)
}
export default {
clearHMS,
addDays,
addMonths,
addYears,
addSeconds,
cloneTime,
compareAsc,
compareMonth,
compareWeek,
compareDay,
compareQuarter,
getDaysOfMonth,
format: formatted,
isInvalid,
isSameDay,
isSameMonth,
isSameWeek,
isSameQuarter,
isValid,
newDate,
setTime,
parse,
toDate,
toDateWithFormat,
formatDateWithDefaultTime,
compareDateArray,
compareYear,
TIME_FORMAT,
resetTimeByFormat,
changeDate,
getDateInfo,
compatibleFmt,
transDateWithZone,
}