Skip to content

Commit

Permalink
fix: add preParsePostFormat plugin & update Arabic [ar] locale (#1255)
Browse files Browse the repository at this point in the history
  • Loading branch information
1 parent 05d8ea8 commit 1d5566d
Show file tree
Hide file tree
Showing 6 changed files with 320 additions and 2 deletions.
38 changes: 38 additions & 0 deletions src/locale/ar.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,31 @@
import dayjs from 'dayjs'

const months = 'يناير_فبراير_مارس_أبريل_مايو_يونيو_يوليو_أغسطس_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_')
const symbolMap = {
1: '١',
2: '٢',
3: '٣',
4: '٤',
5: '٥',
6: '٦',
7: '٧',
8: '٨',
9: '٩',
0: '٠'
}

const numberMap = {
'١': '1',
'٢': '2',
'٣': '3',
'٤': '4',
'٥': '5',
'٦': '6',
'٧': '7',
'٨': '8',
'٩': '9',
'٠': '0'
}

const locale = {
name: 'ar',
Expand All @@ -26,6 +51,19 @@ const locale = {
y: 'عام واحد',
yy: '%d أعوام'
},
preparse(string) {
return string
.replace(
/[١٢٣٤٥٦٧٨٩٠]/g,
match => numberMap[match]
)
.replace(/،/g, ',')
},
postformat(string) {
return string
.replace(/\d/g, match => symbolMap[match])
.replace(/,/g, '،')
},
ordinal: n => n,
formats: {
LT: 'HH:mm',
Expand Down
47 changes: 47 additions & 0 deletions src/plugin/preParsePostFormat/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Plugin template from https://day.js.org/docs/en/plugin/plugin
export default (option, dayjsClass) => {
const oldParse = dayjsClass.prototype.parse
dayjsClass.prototype.parse = function (cfg) {
if (typeof cfg.date === 'string') {
const locale = this.$locale()
cfg.date =
locale && locale.preparse ? locale.preparse(cfg.date) : cfg.date
}
// original parse result
return oldParse.bind(this)(cfg)
}

// // overriding existing API
// // e.g. extend dayjs().format()
const oldFormat = dayjsClass.prototype.format
dayjsClass.prototype.format = function (...args) {
// original format result
const result = oldFormat.call(this, ...args)
// return modified result
const locale = this.$locale()
return locale && locale.postformat ? locale.postformat(result) : result
}

const oldFromTo = dayjsClass.prototype.fromToBase

if (oldFromTo) {
dayjsClass.prototype.fromToBase = function (
input,
withoutSuffix,
instance,
isFrom
) {
const locale = this.$locale() || instance.$locale()

// original format result
return oldFromTo.call(
this,
input,
withoutSuffix,
instance,
isFrom,
locale && locale.postformat
)
}
}
}
12 changes: 10 additions & 2 deletions src/plugin/relativeTime/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default (o, c, d) => {
yy: '%d years'
}
d.en.relativeTime = relObj
const fromTo = (input, withoutSuffix, instance, isFrom) => {
proto.fromToBase = (input, withoutSuffix, instance, isFrom, postFormat) => {
const loc = instance.$locale().relativeTime || relObj
const T = o.thresholds || [
{ l: 's', r: 44, d: C.S },
Expand All @@ -46,11 +46,14 @@ export default (o, c, d) => {
? d(input).diff(instance, t.d, true)
: instance.diff(input, t.d, true)
}
const abs = (o.rounding || Math.round)(Math.abs(result))
let abs = (o.rounding || Math.round)(Math.abs(result))
isFuture = result > 0
if (abs <= t.r || !t.r) {
if (abs <= 1 && i > 0) t = T[i - 1] // 1 minutes -> a minute, 0 seconds -> 0 second
const format = loc[t.l]
if (postFormat) {
abs = postFormat(`${abs}`)
}
if (typeof format === 'string') {
out = format.replace('%d', abs)
} else {
Expand All @@ -66,6 +69,11 @@ export default (o, c, d) => {
}
return pastOrFuture.replace('%s', out)
}

function fromTo(input, withoutSuffix, instance, isFrom) {
return proto.fromToBase(input, withoutSuffix, instance, isFrom)
}

proto.to = function (input, withoutSuffix) {
return fromTo(input, withoutSuffix, this, true)
}
Expand Down
52 changes: 52 additions & 0 deletions test/locale/ar.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import moment from 'moment'
import MockDate from 'mockdate'
import dayjs from '../../src'
import relativeTime from '../../src/plugin/relativeTime'
import preParsePostFormat from '../../src/plugin/preParsePostFormat'
import localeData from '../../src/plugin/localeData'
import '../../src/locale/ar'

dayjs.extend(localeData)
dayjs.extend(relativeTime)
dayjs.extend(preParsePostFormat)

beforeEach(() => {
MockDate.set(new Date())
})

afterEach(() => {
MockDate.reset()
})

it('Format Month with locale function', () => {
for (let i = 0; i <= 7; i += 1) {
const dayjsAR = dayjs().locale('ar').add(i, 'day')
const momentAR = moment().locale('ar').add(i, 'day')
const testFormat1 = 'DD MMMM YYYY MMM'
const testFormat2 = 'MMMM'
const testFormat3 = 'MMM'
expect(dayjsAR.format(testFormat1)).toEqual(momentAR.format(testFormat1))
expect(dayjsAR.format(testFormat2)).toEqual(momentAR.format(testFormat2))
expect(dayjsAR.format(testFormat3)).toEqual(momentAR.format(testFormat3))
}
})

it('Preparse with locale function', () => {
for (let i = 0; i <= 7; i += 1) {
dayjs.locale('ar')
const momentAR = moment().locale('ar').add(i, 'day')
expect(dayjs(momentAR.format()).format()).toEqual(momentAR.format())
}
})

it('RelativeTime: Time from X gets formatted', () => {
const T = [
[44.4, 'second', 'منذ ثانية واحدة']
]

T.forEach((t) => {
dayjs.locale('ar')
expect(dayjs().from(dayjs().add(t[0], t[1])))
.toBe(t[2])
})
})
169 changes: 169 additions & 0 deletions test/plugin/preParsePostFormat.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import MockDate from 'mockdate'
// import moment from 'moment'
import dayjs from '../../src'
import preParsePostFormat from '../../src/plugin/preParsePostFormat'
import localeData from '../../src/plugin/localeData'
import duration from '../../src/plugin/duration'
import calendar from '../../src/plugin/calendar'
import objectSupport from '../../src/plugin/objectSupport'
import customParseFormat from '../../src/plugin/customParseFormat'
import relativeTime from '../../src/plugin/relativeTime'
import utc from '../../src/plugin/utc'
import arraySupport from '../../src/plugin/arraySupport'
import en from '../../src/locale/en'

dayjs.extend(utc)
dayjs.extend(localeData)
dayjs.extend(customParseFormat)
dayjs.extend(arraySupport)
dayjs.extend(objectSupport)
dayjs.extend(calendar)
dayjs.extend(duration)
dayjs.extend(relativeTime)
dayjs.extend(preParsePostFormat)

const symbolMap = {
1: '!',
2: '@',
3: '#',
4: '$',
5: '%',
6: '^',
7: '&',
8: '*',
9: '(',
0: ')'
}
const numberMap = {
'!': '1',
'@': '2',
'#': '3',
$: '4',
'%': '5',
'^': '6',
'&': '7',
'*': '8',
'(': '9',
')': '0'
}

const localeCustomizations = {
...en,
preparse(string) {
if (typeof string !== 'string') {
// console.error('preparse - Expected string, got', {
// string
// })
throw new Error(`preparse - Expected string, got ${typeof string}`)
}
try {
const res = string.replace(/[!@#$%^&*()]/g, match => numberMap[match])
// console.log('Called custom preparse', { string, res })
return res
} catch (error) {
const errorMsg = `Unexpected error during preparse of '${string}' - ${error}`
// console.error(errorMsg)
throw new Error(errorMsg)
}
},
postformat(string) {
if (typeof string !== 'string') {
// console.error('postformat - Expected string, got', {
// string
// })
throw new Error(`postformat - Expected string, got ${typeof string}`)
}
try {
const res = string.replace(/\d/g, match => symbolMap[match])
// console.log('Called custom postformat', { string, res })
return res
} catch (error) {
const errorMsg = `Unexpected error during postFormat of '${string}' - ${error}`
// console.error(errorMsg)
throw new Error(errorMsg)
}
}
}

beforeEach(() => {
MockDate.set(new Date())
dayjs.locale('symbol', localeCustomizations)
})

afterEach(() => {
MockDate.reset()
dayjs.locale('symbol', null)
})

describe('preparse and postformat', () => {
describe('transform', () => {
const TEST_DATE = '@)!@-)*-@&'
const TEST_NUM = 1346025600
it('preparse string + format', () =>
expect(dayjs.utc(TEST_DATE, 'YYYY-MM-DD').unix()).toBe(TEST_NUM))
it('preparse ISO8601 string', () =>
expect(dayjs.utc(TEST_DATE).unix()).toBe(TEST_NUM))
it('postformat', () =>
expect(dayjs
.unix(TEST_NUM)
.utc()
.format('YYYY-MM-DD'))
.toBe(TEST_DATE))
})

describe('transform from', () => {
dayjs.locale('symbol', localeCustomizations)
const start = dayjs([2007, 1, 28])

const t1 = dayjs([2007, 1, 28]).add({ s: 90 })
it('postformat should work on dayjs.fn.from', () =>
expect(start.from(t1, true)).toBe('@ minutes'))

const t2 = dayjs().add(6, 'd')
it('postformat should work on dayjs.fn.fromNow', () =>
expect(t2.fromNow(true)).toBe('^ days'))

it('postformat should work on dayjs.duration.fn.humanize', () =>
expect(dayjs.duration(10, 'h').humanize()).toBe('!) hours'))
})
})

describe('calendar day', () => {
const a = dayjs()
.hour(12)
.minute(0)
.second(0)

it('today at the same time', () =>
expect(dayjs(a).calendar()).toBe('Today at !@:)) PM'))

it('Now plus 25 min', () =>
expect(dayjs(a)
.add({ m: 25 })
.calendar())
.toBe('Today at !@:@% PM'))

it('Now plus 1 hour', () =>
expect(dayjs(a)
.add({ h: 1 })
.calendar())
.toBe('Today at !:)) PM'))

it('tomorrow at the same time', () =>
expect(dayjs(a)
.add({ d: 1 })
.calendar())
.toBe('Tomorrow at !@:)) PM'))

it('Now minus 1 hour', () =>
expect(dayjs(a)
.subtract({ h: 1 })
.calendar())
.toBe('Today at !!:)) AM'))

it('yesterday at the same time', () =>
expect(dayjs(a)
.subtract({ d: 1 })
.calendar())
.toBe('Yesterday at !@:)) PM'))
})
4 changes: 4 additions & 0 deletions types/plugin/preParsePostFormat.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { PluginFunc } from 'dayjs'

declare const plugin: PluginFunc
export = plugin

0 comments on commit 1d5566d

Please sign in to comment.