From b56936ab77b8f6289a1b77d49307b495c4bf9f91 Mon Sep 17 00:00:00 2001 From: iamkun Date: Thu, 12 Nov 2020 14:32:59 +0800 Subject: [PATCH 1/8] fix: update customParseFormat plugin to parse 2-digit offset (#1209) fix #1205 --- src/plugin/customParseFormat/index.js | 5 +++-- test/plugin/customParseFormat.test.js | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/plugin/customParseFormat/index.js b/src/plugin/customParseFormat/index.js index 940bb43a5..f2eabb2b7 100644 --- a/src/plugin/customParseFormat/index.js +++ b/src/plugin/customParseFormat/index.js @@ -8,14 +8,15 @@ const match3 = /\d{3}/ // 000 - 999 const match4 = /\d{4}/ // 0000 - 9999 const match1to2 = /\d\d?/ // 0 - 99 const matchSigned = /[+-]?\d+/ // -inf - inf -const matchOffset = /[+-]\d\d:?\d\d/ // +00:00 -00:00 +0000 or -0000 +const matchOffset = /[+-]\d\d:?(\d\d)?/ // +00:00 -00:00 +0000 or -0000 +00 const matchWord = /\d*[^\s\d-:/()]+/ // Word let locale function offsetFromString(string) { + if (!string) return 0 const parts = string.match(/([+-]|\d\d)/g) - const minutes = +(parts[1] * 60) + +parts[2] + const minutes = +(parts[1] * 60) + (+parts[2] || 0) return minutes === 0 ? 0 : parts[0] === '+' ? -minutes : minutes // eslint-disable-line no-nested-ternary } diff --git a/test/plugin/customParseFormat.test.js b/test/plugin/customParseFormat.test.js index 03b446a63..ef8bfd625 100644 --- a/test/plugin/customParseFormat.test.js +++ b/test/plugin/customParseFormat.test.js @@ -101,6 +101,22 @@ it('timezone with no hour', () => { expect(dayjs(input, format).valueOf()).toBe(moment(input, format).valueOf()) }) +describe('Timezone Offset', () => { + it('timezone with 2-digit offset', () => { + const input = '2020-12-01T20:00:00+09' + const format = 'YYYY-MM-DD[T]HH:mm:ssZZ' + const result = dayjs(input, format) + expect(result.valueOf()).toBe(moment(input, format).valueOf()) + expect(result.valueOf()).toBe(1606820400000) + }) + it('no timezone format token should parse in local time', () => { + const input = '2020-12-01T20:00:00+01:00' + const format = 'YYYY-MM-DD[T]HH:mm:ss' + const result = dayjs(input, format) + expect(result.valueOf()).toBe(moment(input, format).valueOf()) + }) +}) + it('parse hh:mm', () => { const input = '12:00' const format = 'hh:mm' From ef1979ce85c61fe2d759ef3c37cb6aaf2358094f Mon Sep 17 00:00:00 2001 From: Ryan Nixon Salim Date: Fri, 13 Nov 2020 02:18:55 +0700 Subject: [PATCH 2/8] fix: Add function handling for relativeTime.future and relativeTime.past (#1197) --- src/plugin/relativeTime/index.js | 6 +++++- test/plugin/relativeTime.test.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/plugin/relativeTime/index.js b/src/plugin/relativeTime/index.js index 022fbfbd8..e11b67a8e 100644 --- a/src/plugin/relativeTime/index.js +++ b/src/plugin/relativeTime/index.js @@ -60,7 +60,11 @@ export default (o, c, d) => { } } if (withoutSuffix) return out - return (isFuture ? loc.future : loc.past).replace('%s', out) + const pastOrFuture = isFuture ? loc.future : loc.past + if (typeof pastOrFuture === 'function') { + return pastOrFuture(out) + } + return pastOrFuture.replace('%s', out) } proto.to = function (input, withoutSuffix) { return fromTo(input, withoutSuffix, this, true) diff --git a/test/plugin/relativeTime.test.js b/test/plugin/relativeTime.test.js index 19994381f..0ef8e796a 100644 --- a/test/plugin/relativeTime.test.js +++ b/test/plugin/relativeTime.test.js @@ -3,6 +3,7 @@ import moment from 'moment' import dayjs from '../../src' import * as C from '../../src/constant' import relativeTime from '../../src/plugin/relativeTime' +import updateLocale from '../../src/plugin/updateLocale' import utc from '../../src/plugin/utc' import '../../src/locale/ru' @@ -143,3 +144,30 @@ it('Locale without relativeTime config fallback', () => { name: 'test-locale' }).fromNow()).toEqual(expect.any(String)) }) + +it('Past and Future keys should support function for additional processing', () => { + dayjs.extend(updateLocale) + dayjs.updateLocale('en', { + relativeTime: { + future: input => `${input} modified`, + past: input => `${input} modified`, + s: 'just now', + m: ' 1 min', + mm: '%d min', + h: '1 hr', + hh: '%d hrs', + d: 'a day', + dd: '%d days', + M: 'a month', + MM: '%d months', + y: 'a year', + yy: '%d years' + } + }) + + + const past = Date.now() - 1000 + expect(dayjs(past).fromNow()).toEqual(' 1 min modified') + const future = Date.now() + 1000 + expect(dayjs(future).fromNow()).toEqual(' 1 min modified') +}) From 971b3d40b4c9403165138f1034e2223cd97c3abf Mon Sep 17 00:00:00 2001 From: iamkun Date: Sun, 15 Nov 2020 00:28:32 +0800 Subject: [PATCH 3/8] fix: fix utc plugin diff edge case (#1187) --- test/display.test.js | 4 ++++ test/plugin/utc.test.js | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/test/display.test.js b/test/display.test.js index 42975c3cb..7eb48fe82 100644 --- a/test/display.test.js +++ b/test/display.test.js @@ -215,6 +215,10 @@ describe('Difference', () => { expect(dayjs('2018-08-08').diff(dayjs('2018-09-08'), 'month')).toEqual(-1) expect(dayjs('2018-01-01').diff(dayjs('2018-01-01'), 'month')).toEqual(0) }) + + it('undefined edge case', () => { + expect(dayjs().diff(undefined, 'seconds')).toBeDefined() + }) }) it('Unix Timestamp (milliseconds)', () => { diff --git a/test/plugin/utc.test.js b/test/plugin/utc.test.js index d9761f9cb..888412a8c 100644 --- a/test/plugin/utc.test.js +++ b/test/plugin/utc.test.js @@ -269,3 +269,7 @@ it('utc keepLocalTime', () => { expect(dd).toEqual(dm) expect(vd).toEqual(vm) }) + +it('utc diff undefined edge case', () => { + expect(dayjs().diff(undefined, 'seconds')).toBeDefined() +}) From 34cfb920b9653ad44d4b31fe49e533692a3ce01b Mon Sep 17 00:00:00 2001 From: Nat Budin Date: Sun, 15 Nov 2020 18:32:26 -0800 Subject: [PATCH 4/8] fix: Update timezone plugin type definition (#1221) --- types/plugin/timezone.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/types/plugin/timezone.d.ts b/types/plugin/timezone.d.ts index f356a8788..d504f6927 100644 --- a/types/plugin/timezone.d.ts +++ b/types/plugin/timezone.d.ts @@ -6,6 +6,7 @@ export = plugin declare module 'dayjs' { interface Dayjs { tz(timezone?: string, keepLocalTime?: boolean): Dayjs + offsetName(type?: 'short' | 'long'): string | undefined } interface DayjsTimezone { From a92eb6c4dc1437ec920e69484d52984f5921a8ea Mon Sep 17 00:00:00 2001 From: morning Date: Mon, 23 Nov 2020 17:00:32 +0800 Subject: [PATCH 5/8] fix: avoid install installed plugin (#1214) --- src/index.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/index.js b/src/index.js index 439a392f9..6bf5ee656 100644 --- a/src/index.js +++ b/src/index.js @@ -387,7 +387,12 @@ dayjs.prototype = proto; }) dayjs.extend = (plugin, option) => { + if (plugin.installed) { + return dayjs + } + plugin(option, Dayjs, dayjs) + plugin.installed = true return dayjs } From b8d2e32a9eb59661a7ed6200daa070687becaebd Mon Sep 17 00:00:00 2001 From: iamkun Date: Mon, 23 Nov 2020 17:11:57 +0800 Subject: [PATCH 6/8] fix: avoid memory leak after installing a plugin too many times --- src/index.js | 8 +++----- test/plugin/relativeTime.test.js | 1 + 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/index.js b/src/index.js index 6bf5ee656..f50b59e78 100644 --- a/src/index.js +++ b/src/index.js @@ -387,12 +387,10 @@ dayjs.prototype = proto; }) dayjs.extend = (plugin, option) => { - if (plugin.installed) { - return dayjs + if (!plugin.$i) { // install plugin only once + plugin(option, Dayjs, dayjs) + plugin.$i = true } - - plugin(option, Dayjs, dayjs) - plugin.installed = true return dayjs } diff --git a/test/plugin/relativeTime.test.js b/test/plugin/relativeTime.test.js index 0ef8e796a..6bcb5ac95 100644 --- a/test/plugin/relativeTime.test.js +++ b/test/plugin/relativeTime.test.js @@ -120,6 +120,7 @@ it('Time from now with UTC', () => { it('Custom thresholds and rounding support', () => { expect(dayjs().subtract(45, 'm').fromNow()).toBe('an hour ago') + delete relativeTime.$i // this allow plugin to be installed again dayjs.extend(relativeTime, { rounding: Math.floor, thresholds: [ From 9a859a147ba223a1eeff0f2bb6f33d97e0ccc6c7 Mon Sep 17 00:00:00 2001 From: Benjamin Tanone Date: Tue, 24 Nov 2020 21:55:46 +1100 Subject: [PATCH 7/8] fix: add duration.format to format a Duration (#1202) --- src/constant.js | 2 +- src/plugin/duration/index.js | 23 ++++++++++++++++++++++- test/plugin/duration.test.js | 24 ++++++++++++++++++++++++ types/plugin/duration.d.ts | 2 ++ 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/constant.js b/src/constant.js index a4f034fa2..22c1319e0 100644 --- a/src/constant.js +++ b/src/constant.js @@ -27,4 +27,4 @@ export const INVALID_DATE_STRING = 'Invalid Date' // regex export const REGEX_PARSE = /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[^0-9]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?.?(\d+)?$/ -export const REGEX_FORMAT = /\[([^\]]+)]|Y{2,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g +export const REGEX_FORMAT = /\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g diff --git a/src/plugin/duration/index.js b/src/plugin/duration/index.js index 50f660484..02f12bcb4 100644 --- a/src/plugin/duration/index.js +++ b/src/plugin/duration/index.js @@ -1,4 +1,4 @@ -import { MILLISECONDS_A_DAY, MILLISECONDS_A_HOUR, MILLISECONDS_A_MINUTE, MILLISECONDS_A_SECOND, MILLISECONDS_A_WEEK } from '../../constant' +import { MILLISECONDS_A_DAY, MILLISECONDS_A_HOUR, MILLISECONDS_A_MINUTE, MILLISECONDS_A_SECOND, MILLISECONDS_A_WEEK, REGEX_FORMAT } from '../../constant' const MILLISECONDS_A_YEAR = MILLISECONDS_A_DAY * 365 const MILLISECONDS_A_MONTH = MILLISECONDS_A_DAY * 30 @@ -105,6 +105,27 @@ class Duration { return this.toISOString() } + format(formatStr) { + const str = formatStr || 'YYYY-MM-DDTHH:mm:ss' + const matches = { + Y: this.$d.years, + YY: $u.s(this.$d.years, 2, '0'), + YYYY: $u.s(this.$d.years, 4, '0'), + M: this.$d.months, + MM: $u.s(this.$d.months, 2, '0'), + D: this.$d.days, + DD: $u.s(this.$d.days, 2, '0'), + H: this.$d.hours, + HH: $u.s(this.$d.hours, 2, '0'), + m: this.$d.minutes, + mm: $u.s(this.$d.minutes, 2, '0'), + s: this.$d.seconds, + ss: $u.s(this.$d.seconds, 2, '0'), + SSS: $u.s(this.$d.milliseconds, 3, '0') + } + return str.replace(REGEX_FORMAT, (match, $1) => $1 || String(matches[match])) + } + as(unit) { return this.$ms / (unitToMS[prettyUnit(unit)]) } diff --git a/test/plugin/duration.test.js b/test/plugin/duration.test.js index 5e5447658..15e60b925 100644 --- a/test/plugin/duration.test.js +++ b/test/plugin/duration.test.js @@ -208,3 +208,27 @@ describe('prettyUnit', () => { m: 12 }).toISOString()).toBe('P12MT12M') }) + +describe('Format', () => { + test('no formatStr', () => { + const d = dayjs.duration(15, 'seconds') + .add(13, 'hours') + .add(35, 'minutes') + .add(16, 'days') + .add(10, 'months') + .add(22, 'years') + expect(d.format()).toBe('0022-10-16T13:35:15') + }) + + test('with formatStr for all tokens', () => { + const d = dayjs.duration(1, 'seconds') + .add(8, 'hours') + .add(5, 'minutes') + .add(6, 'days') + .add(9, 'months') + .add(2, 'years') + .add(10, 'milliseconds') + expect(d.format('Y/YY.YYYYTESTM:MM:D:DD:H:HH:m:mm:s:ss:SSS')) + .toBe('2/02.0002TEST9:09:6:06:8:08:5:05:1:01:010') + }) +}) diff --git a/types/plugin/duration.d.ts b/types/plugin/duration.d.ts index c09dc9f09..3695eeaaf 100644 --- a/types/plugin/duration.d.ts +++ b/types/plugin/duration.d.ts @@ -51,6 +51,8 @@ declare namespace plugin { toISOString(): string + format(formatStr?: string): string + locale(locale: string): Duration } } From eb5fbc4c7d1b62a8615d2f263b404a9515d8e15c Mon Sep 17 00:00:00 2001 From: Nat Budin Date: Tue, 24 Nov 2020 02:57:13 -0800 Subject: [PATCH 8/8] fix: fix startOf/endOf bug in timezone plugin (#1229) --- src/plugin/timezone/index.js | 11 +++++++++++ test/plugin/timezone.test.js | 14 ++++++++++++++ test/timezone.test.js | 2 ++ 3 files changed, 27 insertions(+) diff --git a/src/plugin/timezone/index.js b/src/plugin/timezone/index.js index 36cc7660d..97f24060b 100644 --- a/src/plugin/timezone/index.js +++ b/src/plugin/timezone/index.js @@ -114,6 +114,17 @@ export default (o, c, d) => { return result && result.value } + const oldStartOf = proto.startOf + proto.startOf = function (units, startOf) { + if (!this.$x || !this.$x.$timezone) { + return oldStartOf.call(this, units, startOf) + } + + const withoutTz = d(this.format('YYYY-MM-DD HH:mm:ss:SSS')) + const startOfWithoutTz = oldStartOf.call(withoutTz, units, startOf) + return startOfWithoutTz.tz(this.$x.$timezone, true) + } + d.tz = function (input, arg1, arg2) { const parseFormat = arg2 && arg1 const timezone = arg2 || arg1 || defaultTimezone diff --git a/test/plugin/timezone.test.js b/test/plugin/timezone.test.js index 91bfdcaec..4feb7bf3d 100644 --- a/test/plugin/timezone.test.js +++ b/test/plugin/timezone.test.js @@ -290,3 +290,17 @@ describe('CustomPraseFormat', () => { expect(dayjs.tz('10/15/2020 12:30', 'MM/DD/YYYY HH:mm', DEN).unix()).toBe(result) }) }) + +describe('startOf and endOf', () => { + it('corrects for timezone offset in startOf', () => { + const originalDay = dayjs.tz('2010-01-01 00:00:00', NY) + const startOfDay = originalDay.startOf('day') + expect(startOfDay.valueOf()).toEqual(originalDay.valueOf()) + }) + + it('corrects for timezone offset in endOf', () => { + const originalDay = dayjs.tz('2009-12-31 23:59:59.999', NY) + const endOfDay = originalDay.endOf('day') + expect(endOfDay.valueOf()).toEqual(originalDay.valueOf()) + }) +}) diff --git a/test/timezone.test.js b/test/timezone.test.js index 13110166d..58e83d225 100644 --- a/test/timezone.test.js +++ b/test/timezone.test.js @@ -1,9 +1,11 @@ import MockDate from 'mockdate' import moment from 'moment' import dayjs from '../src' +import timezone from '../src/plugin/timezone' import utc from '../src/plugin/utc' dayjs.extend(utc) +dayjs.extend(timezone) beforeEach(() => { MockDate.set(new Date())