Skip to content

Commit f2e4790

Browse files
authored
fix: add preParsePostFormat plugin & update Arabic [ar] locale (#1255)
1 parent de49bb1 commit f2e4790

File tree

6 files changed

+320
-2
lines changed

6 files changed

+320
-2
lines changed

src/locale/ar.js

+38
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,31 @@
22
import dayjs from 'dayjs'
33

44
const months = 'يناير_فبراير_مارس_أبريل_مايو_يونيو_يوليو_أغسطس_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_')
5+
const symbolMap = {
6+
1: '١',
7+
2: '٢',
8+
3: '٣',
9+
4: '٤',
10+
5: '٥',
11+
6: '٦',
12+
7: '٧',
13+
8: '٨',
14+
9: '٩',
15+
0: '٠'
16+
}
17+
18+
const numberMap = {
19+
'١': '1',
20+
'٢': '2',
21+
'٣': '3',
22+
'٤': '4',
23+
'٥': '5',
24+
'٦': '6',
25+
'٧': '7',
26+
'٨': '8',
27+
'٩': '9',
28+
'٠': '0'
29+
}
530

631
const locale = {
732
name: 'ar',
@@ -26,6 +51,19 @@ const locale = {
2651
y: 'عام واحد',
2752
yy: '%d أعوام'
2853
},
54+
preparse(string) {
55+
return string
56+
.replace(
57+
/[١٢٣٤٥٦٧٨٩٠]/g,
58+
match => numberMap[match]
59+
)
60+
.replace(/،/g, ',')
61+
},
62+
postformat(string) {
63+
return string
64+
.replace(/\d/g, match => symbolMap[match])
65+
.replace(/,/g, '،')
66+
},
2967
ordinal: n => n,
3068
formats: {
3169
LT: 'HH:mm',
+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Plugin template from https://day.js.org/docs/en/plugin/plugin
2+
export default (option, dayjsClass) => {
3+
const oldParse = dayjsClass.prototype.parse
4+
dayjsClass.prototype.parse = function (cfg) {
5+
if (typeof cfg.date === 'string') {
6+
const locale = this.$locale()
7+
cfg.date =
8+
locale && locale.preparse ? locale.preparse(cfg.date) : cfg.date
9+
}
10+
// original parse result
11+
return oldParse.bind(this)(cfg)
12+
}
13+
14+
// // overriding existing API
15+
// // e.g. extend dayjs().format()
16+
const oldFormat = dayjsClass.prototype.format
17+
dayjsClass.prototype.format = function (...args) {
18+
// original format result
19+
const result = oldFormat.call(this, ...args)
20+
// return modified result
21+
const locale = this.$locale()
22+
return locale && locale.postformat ? locale.postformat(result) : result
23+
}
24+
25+
const oldFromTo = dayjsClass.prototype.fromToBase
26+
27+
if (oldFromTo) {
28+
dayjsClass.prototype.fromToBase = function (
29+
input,
30+
withoutSuffix,
31+
instance,
32+
isFrom
33+
) {
34+
const locale = this.$locale() || instance.$locale()
35+
36+
// original format result
37+
return oldFromTo.call(
38+
this,
39+
input,
40+
withoutSuffix,
41+
instance,
42+
isFrom,
43+
locale && locale.postformat
44+
)
45+
}
46+
}
47+
}

src/plugin/relativeTime/index.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export default (o, c, d) => {
1919
yy: '%d years'
2020
}
2121
d.en.relativeTime = relObj
22-
const fromTo = (input, withoutSuffix, instance, isFrom) => {
22+
proto.fromToBase = (input, withoutSuffix, instance, isFrom, postFormat) => {
2323
const loc = instance.$locale().relativeTime || relObj
2424
const T = o.thresholds || [
2525
{ l: 's', r: 44, d: C.S },
@@ -46,11 +46,14 @@ export default (o, c, d) => {
4646
? d(input).diff(instance, t.d, true)
4747
: instance.diff(input, t.d, true)
4848
}
49-
const abs = (o.rounding || Math.round)(Math.abs(result))
49+
let abs = (o.rounding || Math.round)(Math.abs(result))
5050
isFuture = result > 0
5151
if (abs <= t.r || !t.r) {
5252
if (abs <= 1 && i > 0) t = T[i - 1] // 1 minutes -> a minute, 0 seconds -> 0 second
5353
const format = loc[t.l]
54+
if (postFormat) {
55+
abs = postFormat(`${abs}`)
56+
}
5457
if (typeof format === 'string') {
5558
out = format.replace('%d', abs)
5659
} else {
@@ -66,6 +69,11 @@ export default (o, c, d) => {
6669
}
6770
return pastOrFuture.replace('%s', out)
6871
}
72+
73+
function fromTo(input, withoutSuffix, instance, isFrom) {
74+
return proto.fromToBase(input, withoutSuffix, instance, isFrom)
75+
}
76+
6977
proto.to = function (input, withoutSuffix) {
7078
return fromTo(input, withoutSuffix, this, true)
7179
}

test/locale/ar.test.js

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import moment from 'moment'
2+
import MockDate from 'mockdate'
3+
import dayjs from '../../src'
4+
import relativeTime from '../../src/plugin/relativeTime'
5+
import preParsePostFormat from '../../src/plugin/preParsePostFormat'
6+
import localeData from '../../src/plugin/localeData'
7+
import '../../src/locale/ar'
8+
9+
dayjs.extend(localeData)
10+
dayjs.extend(relativeTime)
11+
dayjs.extend(preParsePostFormat)
12+
13+
beforeEach(() => {
14+
MockDate.set(new Date())
15+
})
16+
17+
afterEach(() => {
18+
MockDate.reset()
19+
})
20+
21+
it('Format Month with locale function', () => {
22+
for (let i = 0; i <= 7; i += 1) {
23+
const dayjsAR = dayjs().locale('ar').add(i, 'day')
24+
const momentAR = moment().locale('ar').add(i, 'day')
25+
const testFormat1 = 'DD MMMM YYYY MMM'
26+
const testFormat2 = 'MMMM'
27+
const testFormat3 = 'MMM'
28+
expect(dayjsAR.format(testFormat1)).toEqual(momentAR.format(testFormat1))
29+
expect(dayjsAR.format(testFormat2)).toEqual(momentAR.format(testFormat2))
30+
expect(dayjsAR.format(testFormat3)).toEqual(momentAR.format(testFormat3))
31+
}
32+
})
33+
34+
it('Preparse with locale function', () => {
35+
for (let i = 0; i <= 7; i += 1) {
36+
dayjs.locale('ar')
37+
const momentAR = moment().locale('ar').add(i, 'day')
38+
expect(dayjs(momentAR.format()).format()).toEqual(momentAR.format())
39+
}
40+
})
41+
42+
it('RelativeTime: Time from X gets formatted', () => {
43+
const T = [
44+
[44.4, 'second', 'منذ ثانية واحدة']
45+
]
46+
47+
T.forEach((t) => {
48+
dayjs.locale('ar')
49+
expect(dayjs().from(dayjs().add(t[0], t[1])))
50+
.toBe(t[2])
51+
})
52+
})
+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import MockDate from 'mockdate'
2+
// import moment from 'moment'
3+
import dayjs from '../../src'
4+
import preParsePostFormat from '../../src/plugin/preParsePostFormat'
5+
import localeData from '../../src/plugin/localeData'
6+
import duration from '../../src/plugin/duration'
7+
import calendar from '../../src/plugin/calendar'
8+
import objectSupport from '../../src/plugin/objectSupport'
9+
import customParseFormat from '../../src/plugin/customParseFormat'
10+
import relativeTime from '../../src/plugin/relativeTime'
11+
import utc from '../../src/plugin/utc'
12+
import arraySupport from '../../src/plugin/arraySupport'
13+
import en from '../../src/locale/en'
14+
15+
dayjs.extend(utc)
16+
dayjs.extend(localeData)
17+
dayjs.extend(customParseFormat)
18+
dayjs.extend(arraySupport)
19+
dayjs.extend(objectSupport)
20+
dayjs.extend(calendar)
21+
dayjs.extend(duration)
22+
dayjs.extend(relativeTime)
23+
dayjs.extend(preParsePostFormat)
24+
25+
const symbolMap = {
26+
1: '!',
27+
2: '@',
28+
3: '#',
29+
4: '$',
30+
5: '%',
31+
6: '^',
32+
7: '&',
33+
8: '*',
34+
9: '(',
35+
0: ')'
36+
}
37+
const numberMap = {
38+
'!': '1',
39+
'@': '2',
40+
'#': '3',
41+
$: '4',
42+
'%': '5',
43+
'^': '6',
44+
'&': '7',
45+
'*': '8',
46+
'(': '9',
47+
')': '0'
48+
}
49+
50+
const localeCustomizations = {
51+
...en,
52+
preparse(string) {
53+
if (typeof string !== 'string') {
54+
// console.error('preparse - Expected string, got', {
55+
// string
56+
// })
57+
throw new Error(`preparse - Expected string, got ${typeof string}`)
58+
}
59+
try {
60+
const res = string.replace(/[!@#$%^&*()]/g, match => numberMap[match])
61+
// console.log('Called custom preparse', { string, res })
62+
return res
63+
} catch (error) {
64+
const errorMsg = `Unexpected error during preparse of '${string}' - ${error}`
65+
// console.error(errorMsg)
66+
throw new Error(errorMsg)
67+
}
68+
},
69+
postformat(string) {
70+
if (typeof string !== 'string') {
71+
// console.error('postformat - Expected string, got', {
72+
// string
73+
// })
74+
throw new Error(`postformat - Expected string, got ${typeof string}`)
75+
}
76+
try {
77+
const res = string.replace(/\d/g, match => symbolMap[match])
78+
// console.log('Called custom postformat', { string, res })
79+
return res
80+
} catch (error) {
81+
const errorMsg = `Unexpected error during postFormat of '${string}' - ${error}`
82+
// console.error(errorMsg)
83+
throw new Error(errorMsg)
84+
}
85+
}
86+
}
87+
88+
beforeEach(() => {
89+
MockDate.set(new Date())
90+
dayjs.locale('symbol', localeCustomizations)
91+
})
92+
93+
afterEach(() => {
94+
MockDate.reset()
95+
dayjs.locale('symbol', null)
96+
})
97+
98+
describe('preparse and postformat', () => {
99+
describe('transform', () => {
100+
const TEST_DATE = '@)!@-)*-@&'
101+
const TEST_NUM = 1346025600
102+
it('preparse string + format', () =>
103+
expect(dayjs.utc(TEST_DATE, 'YYYY-MM-DD').unix()).toBe(TEST_NUM))
104+
it('preparse ISO8601 string', () =>
105+
expect(dayjs.utc(TEST_DATE).unix()).toBe(TEST_NUM))
106+
it('postformat', () =>
107+
expect(dayjs
108+
.unix(TEST_NUM)
109+
.utc()
110+
.format('YYYY-MM-DD'))
111+
.toBe(TEST_DATE))
112+
})
113+
114+
describe('transform from', () => {
115+
dayjs.locale('symbol', localeCustomizations)
116+
const start = dayjs([2007, 1, 28])
117+
118+
const t1 = dayjs([2007, 1, 28]).add({ s: 90 })
119+
it('postformat should work on dayjs.fn.from', () =>
120+
expect(start.from(t1, true)).toBe('@ minutes'))
121+
122+
const t2 = dayjs().add(6, 'd')
123+
it('postformat should work on dayjs.fn.fromNow', () =>
124+
expect(t2.fromNow(true)).toBe('^ days'))
125+
126+
it('postformat should work on dayjs.duration.fn.humanize', () =>
127+
expect(dayjs.duration(10, 'h').humanize()).toBe('!) hours'))
128+
})
129+
})
130+
131+
describe('calendar day', () => {
132+
const a = dayjs()
133+
.hour(12)
134+
.minute(0)
135+
.second(0)
136+
137+
it('today at the same time', () =>
138+
expect(dayjs(a).calendar()).toBe('Today at !@:)) PM'))
139+
140+
it('Now plus 25 min', () =>
141+
expect(dayjs(a)
142+
.add({ m: 25 })
143+
.calendar())
144+
.toBe('Today at !@:@% PM'))
145+
146+
it('Now plus 1 hour', () =>
147+
expect(dayjs(a)
148+
.add({ h: 1 })
149+
.calendar())
150+
.toBe('Today at !:)) PM'))
151+
152+
it('tomorrow at the same time', () =>
153+
expect(dayjs(a)
154+
.add({ d: 1 })
155+
.calendar())
156+
.toBe('Tomorrow at !@:)) PM'))
157+
158+
it('Now minus 1 hour', () =>
159+
expect(dayjs(a)
160+
.subtract({ h: 1 })
161+
.calendar())
162+
.toBe('Today at !!:)) AM'))
163+
164+
it('yesterday at the same time', () =>
165+
expect(dayjs(a)
166+
.subtract({ d: 1 })
167+
.calendar())
168+
.toBe('Yesterday at !@:)) PM'))
169+
})

types/plugin/preParsePostFormat.d.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { PluginFunc } from 'dayjs'
2+
3+
declare const plugin: PluginFunc
4+
export = plugin

0 commit comments

Comments
 (0)