From 84b65e163c4285aae5d4d6a2c80b7581fec8120b Mon Sep 17 00:00:00 2001 From: JounQin Date: Fri, 27 Mar 2020 00:04:09 +0800 Subject: [PATCH 1/4] feat: add strict and custom thresholds support for relativeTime plugin close #830 --- src/plugin/relativeTime/index.js | 62 ++++++++++++++++++++++++-------- src/utils.js | 1 + test/plugin/relativeTime.test.js | 37 +++++++++++++++++++ 3 files changed, 85 insertions(+), 15 deletions(-) diff --git a/src/plugin/relativeTime/index.js b/src/plugin/relativeTime/index.js index 2a68737fe..40d12358d 100644 --- a/src/plugin/relativeTime/index.js +++ b/src/plugin/relativeTime/index.js @@ -1,6 +1,36 @@ import * as C from '../../constant' +const LOOSE_THRESHOLDS = [ + { l: 's', r: 44, d: C.S }, + { l: 'm', r: 89 }, + { l: 'mm', r: 44, d: C.MIN }, + { l: 'h', r: 89 }, + { l: 'hh', r: 21, d: C.H }, + { l: 'd', r: 35 }, + { l: 'dd', r: 25, d: C.D }, + { l: 'M', r: 45 }, + { l: 'MM', r: 10, d: C.M }, + { l: 'y', r: 17 }, + { l: 'yy', d: C.Y } +] + +const STRICT_THRESHOLDS = [ + { l: 's', r: 1 }, + { l: 'ss', r: 59, d: C.S }, + { l: 'm', r: 1 }, + { l: 'mm', r: 59, d: C.MIN }, + { l: 'h', r: 1 }, + { l: 'hh', r: 23, d: C.H }, + { l: 'd', r: 1 }, + { l: 'dd', r: 29, d: C.D }, + { l: 'M', r: 1 }, + { l: 'MM', r: 11, d: C.M }, + { l: 'y' }, + { l: 'yy', d: C.Y } +] + export default (o, c, d) => { + o = o || {} const proto = c.prototype d.en.relativeTime = { future: 'in %s', @@ -17,21 +47,23 @@ export default (o, c, d) => { y: 'a year', yy: '%d years' } + if (o.strict) { + Object.assign(d.en.relativeTime, { + s: '%d second', // 0 or 1 + ss: '%d seconds', + m: '1 minute', + h: '1 hour', + d: '1 day', + M: '1 month', + y: '1 year' + }) + } const fromTo = (input, withoutSuffix, instance, isFrom) => { const loc = instance.$locale().relativeTime - const T = [ - { l: 's', r: 44, d: C.S }, - { l: 'm', r: 89 }, - { l: 'mm', r: 44, d: C.MIN }, - { l: 'h', r: 89 }, - { l: 'hh', r: 21, d: C.H }, - { l: 'd', r: 35 }, - { l: 'dd', r: 25, d: C.D }, - { l: 'M', r: 45 }, - { l: 'MM', r: 10, d: C.M }, - { l: 'y', r: 17 }, - { l: 'yy', d: C.Y } - ] + const T = o.thresholds || (o.strict ? STRICT_THRESHOLDS : LOOSE_THRESHOLDS) + if (!loc.ss) { + loc.ss = loc.s // locale like Chinese + } const Tl = T.length let result let out @@ -44,10 +76,10 @@ export default (o, c, d) => { ? d(input).diff(instance, t.d, true) : instance.diff(input, t.d, true) } - const abs = Math.round(Math.abs(result)) + const abs = Math[o.strict ? 'floor' : '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 + if (abs <= 1 && i > 0) t = T[i - 1] // 1 minutes -> a minute, 0 seconds -> 0 second const format = loc[t.l] if (typeof format === 'string') { out = format.replace('%d', abs) diff --git a/src/utils.js b/src/utils.js index 7b1712bda..53dd56e5f 100644 --- a/src/utils.js +++ b/src/utils.js @@ -45,6 +45,7 @@ const prettyUnit = (u) => { const isUndefined = s => s === undefined export default { + C, // for custom plugin s: padStart, z: padZoneStr, m: monthDiff, diff --git a/test/plugin/relativeTime.test.js b/test/plugin/relativeTime.test.js index f039ef375..60a22c8c5 100644 --- a/test/plugin/relativeTime.test.js +++ b/test/plugin/relativeTime.test.js @@ -115,3 +115,40 @@ it('Time from now with UTC', () => { expect(dutc.fromNow()).toBe(mutc.fromNow()) }) + +it('Strict support', () => { + expect(dayjs().fromNow()).toBe('a few seconds ago') + expect(dayjs().subtract(45, 's').fromNow()).toBe('a minute ago') + expect(dayjs().subtract(45, 'm').fromNow()).toBe('an hour ago') + dayjs.extend(relativeTime, { + strict: true + }) + expect(dayjs().fromNow()).toBe('0 second ago') + expect(dayjs().subtract(1, 's').fromNow()).toBe('1 second ago') + expect(dayjs().subtract(45, 's').fromNow()).toBe('45 seconds ago') + expect(dayjs().subtract(45, 'm').fromNow()).toBe('45 minutes ago') +}) + +it('Custom thresholds support', () => { + const { C } = dayjs().$utils() + dayjs.extend(relativeTime, { + thresholds: [ + { l: 's', r: 44, d: C.S }, + { l: 'm', r: 1 }, + { l: 'mm', r: 59, d: C.MIN }, + { l: 'h', r: 1 }, + { l: 'hh', r: 23, d: C.H }, + { l: 'd', r: 1 }, + { l: 'dd', r: 30, d: C.D }, + { l: 'M', r: 1 }, + { l: 'MM', r: 11, d: C.M }, + { l: 'y' }, + { l: 'yy', d: C.Y } + ] + }) + expect(dayjs().fromNow()).toBe('a few seconds ago') + expect(dayjs().subtract(44, 's').fromNow()).toBe('a few seconds ago') + expect(dayjs().subtract(45, 's').fromNow()).toBe('a minute ago') + expect(dayjs().subtract(45, 'm').fromNow()).toBe('45 minutes ago') + expect(dayjs().subtract(60, 'm').fromNow()).toBe('an hour ago') +}) From 899326f28a7cf1bc95ad73027a5823e98700c6fe Mon Sep 17 00:00:00 2001 From: JounQin Date: Fri, 27 Mar 2020 11:32:53 +0800 Subject: [PATCH 2/4] chore: better typescript support for plugin --- types/index.d.ts | 4 ++-- types/plugin/relativeTime.d.ts | 13 ++++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/types/index.d.ts b/types/index.d.ts index a0f38c89d..f971394dd 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -96,9 +96,9 @@ declare namespace dayjs { locale(preset: string | ILocale, object?: Partial): Dayjs } - export type PluginFunc = (option: any, c: typeof Dayjs, d: typeof dayjs) => void + export type PluginFunc = (option: T, c: typeof Dayjs, d: typeof dayjs) => void - export function extend(plugin: PluginFunc, option?: any): Dayjs + export function extend(plugin: PluginFunc, option?: T): Dayjs export function locale(preset: string | ILocale, object?: Partial, isLocal?: boolean): string diff --git a/types/plugin/relativeTime.d.ts b/types/plugin/relativeTime.d.ts index d08121eed..4fa2974aa 100644 --- a/types/plugin/relativeTime.d.ts +++ b/types/plugin/relativeTime.d.ts @@ -1,6 +1,17 @@ import { PluginFunc, ConfigType } from 'dayjs' -declare const plugin: PluginFunc +declare interface RelativeTimeThreshold { + l: string + r?: number + d?: string +} + +declare interface RelativeTimeOptions { + strict?: boolean + thresholds?: RelativeTimeThreshold[] +} + +declare const plugin: PluginFunc export = plugin declare module 'dayjs' { From 76e9e62859a0d74e2c869ef8af2e37cb93e9d6cc Mon Sep 17 00:00:00 2001 From: JounQin Date: Fri, 27 Mar 2020 12:34:50 +0800 Subject: [PATCH 3/4] chore: prevent using Object.assign --- src/plugin/relativeTime/index.js | 60 ++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/src/plugin/relativeTime/index.js b/src/plugin/relativeTime/index.js index 40d12358d..4b436b079 100644 --- a/src/plugin/relativeTime/index.js +++ b/src/plugin/relativeTime/index.js @@ -29,35 +29,43 @@ const STRICT_THRESHOLDS = [ { l: 'yy', d: C.Y } ] +const LOOSE_EN_LOCALE = { + future: 'in %s', + past: '%s ago', + s: 'a few seconds', + m: 'a minute', + mm: '%d minutes', + h: 'an hour', + hh: '%d hours', + d: 'a day', + dd: '%d days', + M: 'a month', + MM: '%d months', + y: 'a year', + yy: '%d years' +} + +const STRICT_EN_LOCALE = { + future: 'in %s', + past: '%s ago', + s: '%d second', // 0 or 1 + ss: '%d seconds', + m: '1 minute', + mm: '%d minutes', + h: '1 hour', + hh: '%d hours', + d: '1 day', + dd: '%d days', + M: '1 month', + MM: '%d months', + y: '1 year', + yy: '%d years' +} + export default (o, c, d) => { o = o || {} const proto = c.prototype - d.en.relativeTime = { - future: 'in %s', - past: '%s ago', - s: 'a few seconds', - m: 'a minute', - mm: '%d minutes', - h: 'an hour', - hh: '%d hours', - d: 'a day', - dd: '%d days', - M: 'a month', - MM: '%d months', - y: 'a year', - yy: '%d years' - } - if (o.strict) { - Object.assign(d.en.relativeTime, { - s: '%d second', // 0 or 1 - ss: '%d seconds', - m: '1 minute', - h: '1 hour', - d: '1 day', - M: '1 month', - y: '1 year' - }) - } + d.en.relativeTime = o.strict ? STRICT_EN_LOCALE : LOOSE_EN_LOCALE const fromTo = (input, withoutSuffix, instance, isFrom) => { const loc = instance.$locale().relativeTime const T = o.thresholds || (o.strict ? STRICT_THRESHOLDS : LOOSE_THRESHOLDS) From e2fde47f021becc252c309ad24a80ed121c08542 Mon Sep 17 00:00:00 2001 From: JounQin Date: Fri, 27 Mar 2020 17:21:01 +0800 Subject: [PATCH 4/4] feat: remove strict option, add custom rounding support --- src/plugin/relativeTime/index.js | 97 ++++++++++---------------------- src/utils.js | 1 - test/plugin/relativeTime.test.js | 25 ++------ types/plugin/relativeTime.d.ts | 2 +- 4 files changed, 35 insertions(+), 90 deletions(-) diff --git a/src/plugin/relativeTime/index.js b/src/plugin/relativeTime/index.js index 4b436b079..10a8f5de5 100644 --- a/src/plugin/relativeTime/index.js +++ b/src/plugin/relativeTime/index.js @@ -1,77 +1,38 @@ import * as C from '../../constant' -const LOOSE_THRESHOLDS = [ - { l: 's', r: 44, d: C.S }, - { l: 'm', r: 89 }, - { l: 'mm', r: 44, d: C.MIN }, - { l: 'h', r: 89 }, - { l: 'hh', r: 21, d: C.H }, - { l: 'd', r: 35 }, - { l: 'dd', r: 25, d: C.D }, - { l: 'M', r: 45 }, - { l: 'MM', r: 10, d: C.M }, - { l: 'y', r: 17 }, - { l: 'yy', d: C.Y } -] - -const STRICT_THRESHOLDS = [ - { l: 's', r: 1 }, - { l: 'ss', r: 59, d: C.S }, - { l: 'm', r: 1 }, - { l: 'mm', r: 59, d: C.MIN }, - { l: 'h', r: 1 }, - { l: 'hh', r: 23, d: C.H }, - { l: 'd', r: 1 }, - { l: 'dd', r: 29, d: C.D }, - { l: 'M', r: 1 }, - { l: 'MM', r: 11, d: C.M }, - { l: 'y' }, - { l: 'yy', d: C.Y } -] - -const LOOSE_EN_LOCALE = { - future: 'in %s', - past: '%s ago', - s: 'a few seconds', - m: 'a minute', - mm: '%d minutes', - h: 'an hour', - hh: '%d hours', - d: 'a day', - dd: '%d days', - M: 'a month', - MM: '%d months', - y: 'a year', - yy: '%d years' -} - -const STRICT_EN_LOCALE = { - future: 'in %s', - past: '%s ago', - s: '%d second', // 0 or 1 - ss: '%d seconds', - m: '1 minute', - mm: '%d minutes', - h: '1 hour', - hh: '%d hours', - d: '1 day', - dd: '%d days', - M: '1 month', - MM: '%d months', - y: '1 year', - yy: '%d years' -} - export default (o, c, d) => { o = o || {} const proto = c.prototype - d.en.relativeTime = o.strict ? STRICT_EN_LOCALE : LOOSE_EN_LOCALE + d.en.relativeTime = { + future: 'in %s', + past: '%s ago', + s: 'a few seconds', + m: 'a minute', + mm: '%d minutes', + h: 'an hour', + hh: '%d hours', + d: 'a day', + dd: '%d days', + M: 'a month', + MM: '%d months', + y: 'a year', + yy: '%d years' + } const fromTo = (input, withoutSuffix, instance, isFrom) => { const loc = instance.$locale().relativeTime - const T = o.thresholds || (o.strict ? STRICT_THRESHOLDS : LOOSE_THRESHOLDS) - if (!loc.ss) { - loc.ss = loc.s // locale like Chinese - } + const T = o.thresholds || [ + { l: 's', r: 44, d: C.S }, + { l: 'm', r: 89 }, + { l: 'mm', r: 44, d: C.MIN }, + { l: 'h', r: 89 }, + { l: 'hh', r: 21, d: C.H }, + { l: 'd', r: 35 }, + { l: 'dd', r: 25, d: C.D }, + { l: 'M', r: 45 }, + { l: 'MM', r: 10, d: C.M }, + { l: 'y', r: 17 }, + { l: 'yy', d: C.Y } + ] const Tl = T.length let result let out @@ -84,7 +45,7 @@ export default (o, c, d) => { ? d(input).diff(instance, t.d, true) : instance.diff(input, t.d, true) } - const abs = Math[o.strict ? 'floor' : 'round'](Math.abs(result)) + const 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 diff --git a/src/utils.js b/src/utils.js index 53dd56e5f..7b1712bda 100644 --- a/src/utils.js +++ b/src/utils.js @@ -45,7 +45,6 @@ const prettyUnit = (u) => { const isUndefined = s => s === undefined export default { - C, // for custom plugin s: padStart, z: padZoneStr, m: monthDiff, diff --git a/test/plugin/relativeTime.test.js b/test/plugin/relativeTime.test.js index 60a22c8c5..e8a26e0a3 100644 --- a/test/plugin/relativeTime.test.js +++ b/test/plugin/relativeTime.test.js @@ -1,6 +1,7 @@ import MockDate from 'mockdate' import moment from 'moment' import dayjs from '../../src' +import * as C from '../../src/constant' import relativeTime from '../../src/plugin/relativeTime' import utc from '../../src/plugin/utc' import '../../src/locale/ru' @@ -116,39 +117,23 @@ it('Time from now with UTC', () => { expect(dutc.fromNow()).toBe(mutc.fromNow()) }) -it('Strict support', () => { - expect(dayjs().fromNow()).toBe('a few seconds ago') - expect(dayjs().subtract(45, 's').fromNow()).toBe('a minute ago') +it('Custom thresholds and rounding support', () => { expect(dayjs().subtract(45, 'm').fromNow()).toBe('an hour ago') dayjs.extend(relativeTime, { - strict: true - }) - expect(dayjs().fromNow()).toBe('0 second ago') - expect(dayjs().subtract(1, 's').fromNow()).toBe('1 second ago') - expect(dayjs().subtract(45, 's').fromNow()).toBe('45 seconds ago') - expect(dayjs().subtract(45, 'm').fromNow()).toBe('45 minutes ago') -}) - -it('Custom thresholds support', () => { - const { C } = dayjs().$utils() - dayjs.extend(relativeTime, { + rounding: Math.floor, thresholds: [ - { l: 's', r: 44, d: C.S }, + { l: 's', r: 1 }, { l: 'm', r: 1 }, { l: 'mm', r: 59, d: C.MIN }, { l: 'h', r: 1 }, { l: 'hh', r: 23, d: C.H }, { l: 'd', r: 1 }, - { l: 'dd', r: 30, d: C.D }, + { l: 'dd', r: 29, d: C.D }, { l: 'M', r: 1 }, { l: 'MM', r: 11, d: C.M }, { l: 'y' }, { l: 'yy', d: C.Y } ] }) - expect(dayjs().fromNow()).toBe('a few seconds ago') - expect(dayjs().subtract(44, 's').fromNow()).toBe('a few seconds ago') - expect(dayjs().subtract(45, 's').fromNow()).toBe('a minute ago') expect(dayjs().subtract(45, 'm').fromNow()).toBe('45 minutes ago') - expect(dayjs().subtract(60, 'm').fromNow()).toBe('an hour ago') }) diff --git a/types/plugin/relativeTime.d.ts b/types/plugin/relativeTime.d.ts index 4fa2974aa..444b0c26e 100644 --- a/types/plugin/relativeTime.d.ts +++ b/types/plugin/relativeTime.d.ts @@ -7,7 +7,7 @@ declare interface RelativeTimeThreshold { } declare interface RelativeTimeOptions { - strict?: boolean + rounding?: (num: number) => number thresholds?: RelativeTimeThreshold[] }