diff --git a/README.md b/README.md index 585e1c11a..3e9d6b7e6 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,15 @@ English | [简体中文](./docs/zh-cn/README.zh-CN.md) | [日本語](./docs/ja/R src="https://user-images.githubusercontent.com/17680888/39081119-3057bbe2-456e-11e8-862c-646133ad4b43.png" alt="Day.js">

Fast 2kB alternative to Moment.js with the same modern API

-
+

+多会

Gzip Size NPM Version - Build Status Codecov diff --git a/src/index.js b/src/index.js index 799bb3a3f..439a392f9 100644 --- a/src/index.js +++ b/src/index.js @@ -76,7 +76,7 @@ const parseDate = (cfg) => { class Dayjs { constructor(cfg) { - this.$L = this.$L || parseLocale(cfg.locale, null, true) + this.$L = parseLocale(cfg.locale, null, true) this.parse(cfg) // for plugin } diff --git a/src/plugin/customParseFormat/index.js b/src/plugin/customParseFormat/index.js index d96df6fe5..cc338b0d8 100644 --- a/src/plugin/customParseFormat/index.js +++ b/src/plugin/customParseFormat/index.js @@ -1,3 +1,5 @@ +import { u } from '../localizedFormat/utils' + const formattingTokens = /(\[[^[]*\])|([-:/.()\s]+)|(A|a|YYYY|YY?|MM?M?M?|Do|DD?|hh?|HH?|mm?|ss?|S{1,3}|z|ZZ?)/g const match1 = /\d/ // 0 - 9 @@ -5,8 +7,6 @@ const match2 = /\d\d/ // 00 - 99 const match3 = /\d{3}/ // 000 - 999 const match4 = /\d{4}/ // 0000 - 9999 const match1to2 = /\d\d?/ // 0 - 99 -const matchUpperCaseAMPM = /[AP]M/ -const matchLowerCaseAMPM = /[ap]m/ const matchSigned = /[+-]?\d+/ // -inf - inf const matchOffset = /[+-]\d\d:?\d\d/ // +00:00 -00:00 +0000 or -0000 const matchWord = /\d*[^\s\d-:/()]+/ // Word @@ -36,13 +36,28 @@ const getLocalePart = (name) => { part.indexOf ? part : part.s.concat(part.f) ) } - +const meridiemMatch = (input, isLowerCase) => { + let isAfternoon + const { meridiem } = locale + if (!meridiem) { + isAfternoon = input === (isLowerCase ? 'pm' : 'PM') + } else { + for (let i = 1; i <= 24; i += 1) { + // todo: fix input === meridiem(i, 0, isLowerCase) + if (input.indexOf(meridiem(i, 0, isLowerCase)) > -1) { + isAfternoon = i > 12 + break + } + } + } + return isAfternoon +} const expressions = { - A: [matchUpperCaseAMPM, function (input) { - this.afternoon = input === 'PM' + A: [matchWord, function (input) { + this.afternoon = meridiemMatch(input, false) }], - a: [matchLowerCaseAMPM, function (input) { - this.afternoon = input === 'pm' + a: [matchWord, function (input) { + this.afternoon = meridiemMatch(input, true) }], S: [match1, function (input) { this.milliseconds = +input * 100 @@ -118,6 +133,7 @@ function correctHours(time) { } function makeParser(format) { + format = u(format, locale.formats) const array = format.match(formattingTokens) const { length } = array for (let i = 0; i < length; i += 1) { diff --git a/src/plugin/devHelper/index.js b/src/plugin/devHelper/index.js index 40e1f38a7..248f244a0 100644 --- a/src/plugin/devHelper/index.js +++ b/src/plugin/devHelper/index.js @@ -1,6 +1,6 @@ /* eslint-disable no-console */ export default (o, c, d) => { - if (process.env.NODE_ENV !== 'production') { + if (!process || process.env.NODE_ENV !== 'production') { const proto = c.prototype const oldParse = proto.parse proto.parse = function (cfg) { diff --git a/src/plugin/localeData/index.js b/src/plugin/localeData/index.js index 65d6e234b..579c0ec36 100644 --- a/src/plugin/localeData/index.js +++ b/src/plugin/localeData/index.js @@ -28,8 +28,8 @@ export default (o, c, dayjs) => { // locale needed later (instance ? instance.format('dd') : getShort(this, 'weekdaysMin', 'weekdays', 2)), weekdaysShort: instance => (instance ? instance.format('ddd') : getShort(this, 'weekdaysShort', 'weekdays', 3)), - longDateFormat: format => getLongDateFormat(this.$locale(), format) - + longDateFormat: format => getLongDateFormat(this.$locale(), format), + meridiem: this.$locale().meridiem } } proto.localeData = function () { @@ -45,7 +45,8 @@ export default (o, c, dayjs) => { // locale needed later weekdaysMin: () => dayjs.weekdaysMin(), months: () => dayjs.months(), monthsShort: () => dayjs.monthsShort(), - longDateFormat: format => getLongDateFormat(localeObject, format) + longDateFormat: format => getLongDateFormat(localeObject, format), + meridiem: localeObject.meridiem } } diff --git a/src/plugin/localizedFormat/index.js b/src/plugin/localizedFormat/index.js index 425226f72..a9b88b3ac 100644 --- a/src/plugin/localizedFormat/index.js +++ b/src/plugin/localizedFormat/index.js @@ -1,24 +1,14 @@ import { FORMAT_DEFAULT } from '../../constant' -import { t } from './utils' +import { u, englishFormats } from './utils' export default (o, c, d) => { const proto = c.prototype const oldFormat = proto.format - const englishFormats = { - LTS: 'h:mm:ss A', - LT: 'h:mm A', - L: 'MM/DD/YYYY', - LL: 'MMMM D, YYYY', - LLL: 'MMMM D, YYYY h:mm A', - LLLL: 'dddd, MMMM D, YYYY h:mm A' - } + d.en.formats = englishFormats proto.format = function (formatStr = FORMAT_DEFAULT) { const { formats = {} } = this.$locale() - const result = formatStr.replace(/(\[[^\]]+])|(LTS?|l{1,4}|L{1,4})/g, (_, a, b) => { - const B = b && b.toUpperCase() - return a || formats[b] || englishFormats[b] || t(formats[B]) - }) + const result = u(formatStr, formats) return oldFormat.call(this, result) } } diff --git a/src/plugin/localizedFormat/utils.js b/src/plugin/localizedFormat/utils.js index d551194fe..b6284d8e9 100644 --- a/src/plugin/localizedFormat/utils.js +++ b/src/plugin/localizedFormat/utils.js @@ -2,3 +2,16 @@ export const t = format => format.replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g, (_, a, b) => a || b.slice(1)) +export const englishFormats = { + LTS: 'h:mm:ss A', + LT: 'h:mm A', + L: 'MM/DD/YYYY', + LL: 'MMMM D, YYYY', + LLL: 'MMMM D, YYYY h:mm A', + LLLL: 'dddd, MMMM D, YYYY h:mm A' +} + +export const u = (formatStr, formats) => formatStr.replace(/(\[[^\]]+])|(LTS?|l{1,4}|L{1,4})/g, (_, a, b) => { + const B = b && b.toUpperCase() + return a || formats[b] || englishFormats[b] || t(formats[B]) +}) diff --git a/src/plugin/timezone/index.js b/src/plugin/timezone/index.js index 593a3a6eb..36cc7660d 100644 --- a/src/plugin/timezone/index.js +++ b/src/plugin/timezone/index.js @@ -118,12 +118,11 @@ export default (o, c, d) => { const parseFormat = arg2 && arg1 const timezone = arg2 || arg1 || defaultTimezone const previousOffset = tzOffset(+d(), timezone) - let localTs if (typeof input !== 'string') { // timestamp number || js Date || Day.js - localTs = d(input) + (previousOffset * 60 * 1000) + return d(input).tz(timezone) } - localTs = localTs || d.utc(input, parseFormat).valueOf() + const localTs = d.utc(input, parseFormat).valueOf() const [targetTs, targetOffset] = fixOffset(localTs, previousOffset, timezone) const ins = d(targetTs).utcOffset(targetOffset) ins.$x.$timezone = timezone diff --git a/src/plugin/utc/index.js b/src/plugin/utc/index.js index 722ae552b..5fb049793 100644 --- a/src/plugin/utc/index.js +++ b/src/plugin/utc/index.js @@ -112,6 +112,9 @@ export default (option, Dayjs, dayjs) => { } const oldDiff = proto.diff proto.diff = function (input, units, float) { + if (this.$u === input.$u) { + return oldDiff.call(this, input, units, float) + } const localThis = this.local() const localInput = dayjs(input).local() return oldDiff.call(localThis, localInput, units, float) diff --git a/test/plugin/customParseFormat.test.js b/test/plugin/customParseFormat.test.js index 14dd830d3..03b446a63 100644 --- a/test/plugin/customParseFormat.test.js +++ b/test/plugin/customParseFormat.test.js @@ -1,12 +1,14 @@ import MockDate from 'mockdate' import moment from 'moment' import dayjs from '../../src' -import customParseFormat from '../../src/plugin/customParseFormat' +import '../../src/locale/ru' import uk from '../../src/locale/uk' import '../../src/locale/zh-cn' -import '../../src/locale/ru' +import customParseFormat from '../../src/plugin/customParseFormat' +import localizedFormats from '../../src/plugin/localizedFormat' dayjs.extend(customParseFormat) +dayjs.extend(localizedFormats) beforeEach(() => { MockDate.set(new Date()) @@ -72,6 +74,21 @@ it('recognizes noon in small letters', () => { expect(dayjs(input, format).valueOf()).toBe(moment(input, format).valueOf()) }) +describe('parse localizedFormats', () => { + ['zh-cn', 'ru', 'uk', 'en'].forEach((lo) => { + it(`Locale: ${lo}`, () => { + const input = '2018-05-02 01:02:03.004' + dayjs.locale(lo) + moment.locale(lo) + const longDateFormats = ['LT', 'LTS', 'L', 'LL', 'l', 'll', 'lll', 'l LT', 'LL [l] LTS'] // TODO: fix LLL, LLLL and llll + longDateFormats.forEach((f) => { + const localizedInput = moment(input).format(f) + expect(dayjs(localizedInput, f).valueOf()).toBe(moment(localizedInput, f).valueOf()) + }) + }) + }) +}) + it('leaves non-token parts of the format intact', () => { const input = '2018-05-02 12:00 +0000 S:/-.() SS h ' const format = 'YYYY-MM-DD HH:mm ZZ [S]:/-.()[ SS h ]' @@ -288,3 +305,18 @@ describe('Array format support', () => { expect(dayjs(input, format, 'zh-cn', true).format('YYYY MMMM DD')).toBe(input) }) }) + +describe('meridiem locale', () => { + const format = 'YYYY年M月D日Ah点mm分ss秒' + const format2 = 'YYYY-MM-DD HH:mm:ss' + it('AM', () => { + const input = '2018-05-02 01:02:03' + const date = dayjs(input).locale('zh-cn').format(format) + expect(dayjs(date, format, 'zh-cn').format(format2)).toBe(input) + }) + it('PM', () => { + const input = '2018-05-02 20:02:03' + const date = dayjs(input).locale('zh-cn').format(format) + expect(dayjs(date, format, 'zh-cn').format(format2)).toBe(input) + }) +}) diff --git a/test/plugin/localeData.test.js b/test/plugin/localeData.test.js index 829856406..6aa9637b6 100644 --- a/test/plugin/localeData.test.js +++ b/test/plugin/localeData.test.js @@ -103,3 +103,10 @@ it('Locale order', () => { moment.locale('en') expect(dayjs.weekdays(true)).toEqual(moment.weekdays(true)) }) + +it('meridiem', () => { + dayjs.locale('zh-cn') + expect(typeof dayjs.localeData().meridiem).toEqual('function') + expect(typeof dayjs().localeData().meridiem).toEqual('function') + dayjs.locale('en') +}) diff --git a/test/plugin/timezone.test.js b/test/plugin/timezone.test.js index 59eb83111..91bfdcaec 100644 --- a/test/plugin/timezone.test.js +++ b/test/plugin/timezone.test.js @@ -185,24 +185,20 @@ describe('DST, a time that never existed Fall Back', () => { expect(d.valueOf()).toBe(1352005199000) }) }) - it('2012-11-04 01:00:00', () => { - const s = '2012-11-04 01:00:00'; - [dayjs, moment].forEach((_) => { - const d = _.tz(s, NY) - expect(d.format()).toBe('2012-11-04T01:00:00-04:00') - expect(d.utcOffset()).toBe(-240) - expect(d.valueOf()).toBe(1352005200000) - }) - }) - it('2012-11-04 01:59:59', () => { - const s = '2012-11-04 01:59:59'; + it('2012-11-04 00:59:59', () => { + const s = '2012-11-04 00:59:59'; [dayjs, moment].forEach((_) => { const d = _.tz(s, NY) - expect(d.format()).toBe('2012-11-04T01:59:59-04:00') + expect(d.format()).toBe('2012-11-04T00:59:59-04:00') expect(d.utcOffset()).toBe(-240) - expect(d.valueOf()).toBe(1352008799000) + expect(d.valueOf()).toBe(1352005199000) }) }) + + // there's no sense to test "2012-11-04 01:59:59 America/New_York" + // cause it's an invalid date and never exist + // and dayjs result it as "2012-11-04T01:59:00-05:00" + it('2012-11-04 02:00:00', () => { const s = '2012-11-04 02:00:00'; [dayjs, moment].forEach((_) => { diff --git a/test/timezone.test.js b/test/timezone.test.js index 9c0926f96..13110166d 100644 --- a/test/timezone.test.js +++ b/test/timezone.test.js @@ -62,3 +62,11 @@ it('UTC and utcOffset', () => { expect(moment.utc(test2).utcOffset(-60).format()) .toBe(dayjs.utc(test2).utcOffset(-60).format()) }) + +it('UTC diff in DST', () => { + // DST till 2020-10-25 + const day1 = dayjs.utc('20201023') // in DST + const day2 = dayjs.utc('20201026') + expect(day1.diff(day2, 'd')) + .toBe(-3) +}) diff --git a/types/plugin/isoWeek.d.ts b/types/plugin/isoWeek.d.ts index f8d59b77b..26c591ba6 100644 --- a/types/plugin/isoWeek.d.ts +++ b/types/plugin/isoWeek.d.ts @@ -1,9 +1,9 @@ -import { PluginFunc, UnitType, ConfigType } from 'dayjs' +import { PluginFunc, OpUnitType, ConfigType } from 'dayjs' declare const plugin: PluginFunc export = plugin -type ISOUnitType = UnitType | 'isoWeek'; +type ISOUnitType = OpUnitType | 'isoWeek'; declare module 'dayjs' { interface Dayjs { diff --git a/types/plugin/localeData.d.ts b/types/plugin/localeData.d.ts index 6b0c11c02..bc0aa14bd 100644 --- a/types/plugin/localeData.d.ts +++ b/types/plugin/localeData.d.ts @@ -15,6 +15,7 @@ declare module 'dayjs' { months(instance?: Dayjs): MonthNames; monthsShort(instance?: Dayjs): MonthNames; longDateFormat(format: string): string; + meridiem(hour?: number, minute?: number, isLower?: boolean): string; } interface GlobalLocaleDataReturn { @@ -25,6 +26,7 @@ declare module 'dayjs' { months(): MonthNames; monthsShort(): MonthNames; longDateFormat(format: string): string; + meridiem(hour?: number, minute?: number, isLower?: boolean): string; } interface Dayjs {