jalaliday
Version:
Persian (Jalali, Khorshidi) Plugin for Day.js
325 lines (296 loc) • 8.81 kB
text/typescript
import type { PluginFunc } from 'dayjs'
import fa from 'dayjs/locale/fa'
import jdate from '../calendar'
import * as C from '../constant'
const plugin: PluginFunc = (o, Dayjs, dayjs) => {
const proto = Dayjs.prototype
const U = proto.$utils()
const $isJalali = v => v.$C === 'jalali'
const $prettyUnit = U.prettyUnit || U.p
const $isUndefined = U.isUndefined || U.u
const $padStart = U.padStart || U.s
const $monthDiff = U.monthDiff || U.m
const $absFloor = U.absFloor || U.a
const wrapperOfTruth = action => function (...args) {
const unsure = action.bind(this)(...args)
unsure.$C = this.$C
if (unsure.isJalali()) {
unsure.InitJalali()
}
return unsure
}
// keep calendar on date manipulation
proto.startOf = wrapperOfTruth(proto.startOf)
proto.endOf = wrapperOfTruth(proto.endOf)
proto.add = wrapperOfTruth(proto.add)
proto.subtract = wrapperOfTruth(proto.subtract)
proto.set = wrapperOfTruth(proto.set)
const oldParse = proto.parse
const oldInit = proto.init
const oldStartOf = proto.startOf
const old$Set = proto.$set
const oldAdd = proto.add
const oldFormat = proto.format
const oldDiff = proto.diff
const oldYear = proto.year
const oldMonth = proto.month
const oldDate = proto.date
const oldDaysInMonth = proto.daysInMonth
const oldToArray = proto.toArray
dayjs.$C = 'gregory'
// First Day Of Week
dayjs.$fdow = 6 // 0: sunday, ...
dayjs.calendar = function (calendar) {
dayjs.$C = calendar
return dayjs
}
proto.calendar = function (calendar) {
const that = this.clone()
that.$C = calendar
if (that.isJalali()) {
that.InitJalali()
}
return that
}
proto.isJalali = function () {
return $isJalali(this)
}
dayjs.en.jmonths = 'Farvardin_Ordibehesht_Khordaad_Tir_Mordaad_Shahrivar_Mehr_Aabaan_Aazar_Dey_Bahman_Esfand'.split(
'_',
)
dayjs.locale('fa', { ...fa, ...C.fa }, true)
const wrapper = function (date, instance) {
return dayjs(date, {
locale: instance.$L,
utc: instance.$u,
calendar: instance.$C,
})
}
proto.init = function (cfg = {}) {
oldInit.bind(this)(cfg)
if (this.isJalali()) {
this.InitJalali()
}
}
proto.parse = function (cfg) {
this.$C = cfg.calendar || this.$C || dayjs.$C
if (
cfg.jalali
&& typeof cfg.date === 'string'
&& /.*[^Z]$/i.test(cfg.date) // looking for a better way
) {
const reg = cfg.date.match(C.REGEX_PARSE)
if (reg) {
// 1397-08-08 or 13970808
const [y, m, d] = jdate.G(
Number.parseInt(reg[1], 10),
Number.parseInt(reg[2], 10),
Number.parseInt(reg[3] || 1, 10),
)
cfg.date = `${y}-${m}-${d}${reg[4] || ''}`
}
}
return oldParse.bind(this)(cfg)
}
proto.InitJalali = function () {
const [jy, jm, jd] = jdate.J(this.$y, this.$M + 1, this.$D)
this.$jy = jy
this.$jM = jm - 1
this.$jD = jd
}
proto.startOf = function (units, startOf) { // startOf -> endOf
if (!$isJalali(this)) {
return oldStartOf.bind(this)(units, startOf)
}
const isStartOf = !$isUndefined(startOf) ? startOf : true
const unit = $prettyUnit(units)
const instanceFactory = (d, m, y = this.$jy) => {
const [gy, gm, gd] = jdate.G(y, m + 1, d)
const ins = wrapper(new Date(gy, gm - 1, gd), this)
return (isStartOf ? ins : ins.endOf(C.D))
.$set('hour', 1) // prevent daylight saving issue in safari
}
const WModifier = (this.$W + (7 - dayjs.$fdow)) % 7
switch (unit) {
case C.Y:
return isStartOf
? instanceFactory(1, 0)
: instanceFactory(0, 0, this.$jy + 1)
case C.M:
return isStartOf
? instanceFactory(1, this.$jM)
: instanceFactory(
0,
(this.$jM + 1) % 12,
this.$jy + Math.floor((this.$jM + 1) / 12),
)
case C.W:
return isStartOf
? instanceFactory(this.$jD - WModifier, this.$jM)
: instanceFactory(this.$jD + (6 - WModifier), this.$jM)
default:
return oldStartOf.bind(this)(units, startOf)
}
}
proto.$set = function (units, int) {
if (!$isJalali(this)) {
return old$Set.bind(this)(units, int)
}
const unit = $prettyUnit(units)
const instanceFactory = (d, m, y = this.$jy) => {
const [gy, gm, gd] = jdate.G(y, m + 1, d)
this.$d.setFullYear(gy)
this.$d.setMonth(gm - 1)
this.$d.setDate(gd)
return this
}
switch (unit) {
case C.DATE:
case C.D:
instanceFactory(int, this.$jM)
break
case C.M:
instanceFactory(this.$jD, int)
break
case C.Y:
instanceFactory(this.$jD, this.$jM, int)
break
default:
return old$Set.bind(this)(units, int)
}
this.init()
return this
}
proto.add = function (number, units) {
if (!$isJalali(this)) {
return oldAdd.bind(this)(number, units)
}
number = Number(number)
// units === 'ms' hard code here, will update in next release
const unit = units && (units.length === 1 || units === 'ms')
? units
: $prettyUnit(units)
const instanceFactory = (u, n) => {
const date = this.set(C.DATE, 1).set(u, n + number)
return date.set(C.DATE, Math.min(this.$jD, date.daysInMonth()))
}
if (['M', C.M].includes(unit)) {
const n = this.$jM + number
const y = n < 0 ? -Math.ceil(-n / 12) : Math.floor(n / 12)
const d = this.$jD
const x = this.set(C.D, 1)
.add(y, C.Y)
.set(C.M, n - y * 12)
return x.set(C.D, Math.min(x.daysInMonth(), d))
}
if (['y', C.Y].includes(unit)) {
return instanceFactory(C.Y, this.$jy)
}
if (['d', C.D].includes(unit)) {
const date = new Date(this.$d)
date.setDate(date.getDate() + number)
return wrapper(date, this)
}
if (['w', C.W].includes(unit)) {
const date = new Date(this.$d)
date.setDate(date.getDate() + number * 7)
return wrapper(date, this)
}
return oldAdd.bind(this)(number, units)
}
proto.format = function (formatStr, localeObject) {
if (!$isJalali(this)) {
return oldFormat.bind(this)(formatStr, localeObject)
}
const str = formatStr || C.FORMAT_DEFAULT
const locale = localeObject || this.$locale()
const { jmonths } = locale
return str.replace(C.REGEX_FORMAT, (match) => {
if (match.includes('['))
return match.replace(/\[|\]/g, '')
switch (match) {
case 'YY':
return String(this.$jy).slice(-2)
case 'YYYY':
return String(this.$jy)
case 'M':
return String(this.$jM + 1)
case 'MM':
return $padStart(this.$jM + 1, 2, '0')
case 'MMM':
return jmonths[this.$jM].slice(0, 3)
case 'MMMM':
return jmonths[this.$jM]
case 'D':
return String(this.$jD)
case 'DD':
return $padStart(this.$jD, 2, '0')
default:
return oldFormat.bind(this)(match, localeObject)
}
})
}
proto.diff = function (input, units, float) {
if (!$isJalali(this)) {
return oldDiff.bind(this)(input, units, float)
}
const unit = $prettyUnit(units)
const that = dayjs(input)
let result = $monthDiff(this, that)
switch (unit) {
case C.Y:
result /= 12
break
case C.M:
break
default: // milliseconds
return oldDiff.bind(this)(input, units, float)
}
return float ? result : $absFloor(result)
}
proto.$g = function (input, get, set) {
if ($isUndefined(input))
return this[get]
return this.set(set, input)
}
proto.year = function (input) {
if (!$isJalali(this)) {
return oldYear.bind(this)(input)
}
return this.$g(input, '$jy', C.Y)
}
proto.month = function (input) {
if (!$isJalali(this)) {
return oldMonth.bind(this)(input)
}
return this.$g(input, '$jM', C.M)
}
proto.date = function (input) {
if (!$isJalali(this)) {
return oldDate.bind(this)(input)
}
return this.$g(input, '$jD', C.D)
}
proto.daysInMonth = function () {
if (!$isJalali(this)) {
return oldDaysInMonth.bind(this)()
}
return this.endOf(C.M).$jD
}
/**
* toArray function moved to official plugin
* Check function existence before override
*/
if (oldToArray) {
proto.toArray = function () {
if (!$isJalali(this)) {
return oldToArray.bind(this)()
}
return [this.$jy, this.$jM, this.$jD, this.$H, this.$m, this.$s, this.$ms]
}
}
proto.clone = function () {
return wrapper(this.toDate(), this)
}
}
export default plugin