diff --git a/src/locale/ar-kw.js b/src/locale/ar-kw.js index 2ed0fbc8..72a2b011 100644 --- a/src/locale/ar-kw.js +++ b/src/locale/ar-kw.js @@ -3,9 +3,9 @@ import dayjs from 'dayjs' const locale = { name: 'ar-kw', - weekdays: 'الأحد_الإتنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'), + weekdays: 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'), months: 'يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر'.split('_'), - weekdaysShort: 'احد_اتنين_ثلاثاء_اربعاء_خميس_جمعة_سبت'.split('_'), + weekdaysShort: 'احد_اثنين_ثلاثاء_اربعاء_خميس_جمعة_سبت'.split('_'), monthsShort: 'يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر'.split('_'), weekdaysMin: 'ح_ن_ث_ر_خ_ج_س'.split('_'), ordinal: n => n, diff --git a/src/locale/ar-ma.js b/src/locale/ar-ma.js index a0e803c0..39b80ef9 100644 --- a/src/locale/ar-ma.js +++ b/src/locale/ar-ma.js @@ -3,10 +3,10 @@ import dayjs from 'dayjs' const locale = { name: 'ar-ma', - weekdays: 'الأحد_الإتنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'), + weekdays: 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'), months: 'يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر'.split('_'), weekStart: 6, - weekdaysShort: 'احد_اتنين_ثلاثاء_اربعاء_خميس_جمعة_سبت'.split('_'), + weekdaysShort: 'احد_إثنين_ثلاثاء_اربعاء_خميس_جمعة_سبت'.split('_'), monthsShort: 'يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر'.split('_'), weekdaysMin: 'ح_ن_ث_ر_خ_ج_س'.split('_'), ordinal: n => n, diff --git a/src/locale/br.js b/src/locale/br.js index bbd242c7..17e4ee50 100644 --- a/src/locale/br.js +++ b/src/locale/br.js @@ -1,6 +1,47 @@ // Breton [br] import dayjs from 'dayjs' +function lastNumber(number) { + if (number > 9) { + return lastNumber(number % 10) + } + return number +} +function softMutation(text) { + const mutationTable = { + m: 'v', + b: 'v', + d: 'z' + } + return mutationTable[text.charAt(0)] + text.substring(1) +} +function mutation(text, number) { + if (number === 2) { + return softMutation(text) + } + return text +} +function relativeTimeWithMutation(number, withoutSuffix, key) { + const format = { + mm: 'munutenn', + MM: 'miz', + dd: 'devezh' + } + return `${number} ${mutation(format[key], number)}` +} +function specialMutationForYears(number) { + switch (lastNumber(number)) { + case 1: + case 3: + case 4: + case 5: + case 9: + return `${number} bloaz` + default: + return `${number} vloaz` + } +} + const locale = { name: 'br', weekdays: 'Sul_Lun_Meurzh_Mercʼher_Yaou_Gwener_Sadorn'.split('_'), @@ -18,6 +59,21 @@ const locale = { LLL: 'D [a viz] MMMM YYYY h[e]mm A', LLLL: 'dddd, D [a viz] MMMM YYYY h[e]mm A' }, + relativeTime: { + future: 'a-benn %s', + past: '%s ʼzo', + s: 'un nebeud segondennoù', + m: 'ur vunutenn', + mm: relativeTimeWithMutation, + h: 'un eur', + hh: '%d eur', + d: 'un devezh', + dd: relativeTimeWithMutation, + M: 'ur miz', + MM: relativeTimeWithMutation, + y: 'ur bloaz', + yy: specialMutationForYears + }, meridiem: hour => (hour < 12 ? 'a.m.' : 'g.m.') // a-raok merenn | goude merenn } diff --git a/src/locale/ca.js b/src/locale/ca.js index 0046871d..1af971f2 100644 --- a/src/locale/ca.js +++ b/src/locale/ca.js @@ -35,7 +35,16 @@ const locale = { y: 'un any', yy: '%d anys' }, - ordinal: n => `${n}º` + ordinal: (n) => { + let ord + + if (n === 1 || n === 3) ord = 'r' + else if (n === 2) ord = 'n' + else if (n === 4) ord = 't' + else ord = 'è' + + return `${n}${ord}` + } } dayjs.locale(locale, null, true) diff --git a/src/locale/de.js b/src/locale/de.js index 1478c370..21e9ff14 100644 --- a/src/locale/de.js +++ b/src/locale/de.js @@ -1,6 +1,28 @@ // German [de] import dayjs from 'dayjs' +const texts = { + s: 'ein paar Sekunden', + m: ['eine Minute', 'einer Minute'], + mm: '%d Minuten', + h: ['eine Stunde', 'einer Stunde'], + hh: '%d Stunden', + d: ['ein Tag', 'einem Tag'], + dd: ['%d Tage', '%d Tagen'], + M: ['ein Monat', 'einem Monat'], + MM: ['%d Monate', '%d Monaten'], + y: ['ein Jahr', 'einem Jahr'], + yy: ['%d Jahre', '%d Jahren'] +} + +function relativeTimeFormatter(number, withoutSuffix, key) { + let l = texts[key] + if (Array.isArray(l)) { + l = l[withoutSuffix ? 0 : 1] + } + return l.replace('%d', number) +} + const locale = { name: 'de', weekdays: 'Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag'.split('_'), @@ -21,17 +43,17 @@ const locale = { relativeTime: { future: 'in %s', past: 'vor %s', - s: 'wenigen Sekunden', - m: 'einer Minute', - mm: '%d Minuten', - h: 'einer Stunde', - hh: '%d Stunden', - d: 'einem Tag', - dd: '%d Tagen', - M: 'einem Monat', - MM: '%d Monaten', - y: 'einem Jahr', - yy: '%d Jahren' + s: relativeTimeFormatter, + m: relativeTimeFormatter, + mm: relativeTimeFormatter, + h: relativeTimeFormatter, + hh: relativeTimeFormatter, + d: relativeTimeFormatter, + dd: relativeTimeFormatter, + M: relativeTimeFormatter, + MM: relativeTimeFormatter, + y: relativeTimeFormatter, + yy: relativeTimeFormatter } } diff --git a/src/locale/sr-cyrl.js b/src/locale/sr-cyrl.js index 019544da..84b42add 100644 --- a/src/locale/sr-cyrl.js +++ b/src/locale/sr-cyrl.js @@ -1,6 +1,42 @@ // Serbian Cyrillic [sr-cyrl] import dayjs from 'dayjs' +const translator = { + words: { + m: ['један минут', 'једног минута'], + mm: ['%d минут', '%d минута', '%d минута'], + h: ['један сат', 'једног сата'], + hh: ['%d сат', '%d сата', '%d сати'], + d: ['један дан', 'једног дана'], + dd: ['%d дан', '%d дана', '%d дана'], + M: ['један месец', 'једног месеца'], + MM: ['%d месец', '%d месеца', '%d месеци'], + y: ['једну годину', 'једне године'], + yy: ['%d годину', '%d године', '%d година'] + }, + correctGrammarCase(number, wordKey) { + if (number % 10 >= 1 && number % 10 <= 4 && (number % 100 < 10 || number % 100 >= 20)) { + return number % 10 === 1 ? wordKey[0] : wordKey[1] + } + return wordKey[2] + }, + relativeTimeFormatter(number, withoutSuffix, key, isFuture) { + const wordKey = translator.words[key] + + if (key.length === 1) { + // Nominativ + if (key === 'y' && withoutSuffix) return 'једна година' + return isFuture || withoutSuffix ? wordKey[0] : wordKey[1] + } + + const word = translator.correctGrammarCase(number, wordKey) + // Nominativ + if (key === 'yy' && withoutSuffix && word === '%d годину') return `${number} година` + + return word.replace('%d', number) + } +} + const locale = { name: 'sr-cyrl', weekdays: 'Недеља_Понедељак_Уторак_Среда_Четвртак_Петак_Субота'.split('_'), @@ -12,26 +48,26 @@ const locale = { relativeTime: { future: 'за %s', past: 'пре %s', - s: 'секунда', - m: 'минут', - mm: '%d минута', - h: 'сат', - hh: '%d сати', - d: 'дан', - dd: '%d дана', - M: 'месец', - MM: '%d месеци', - y: 'година', - yy: '%d године' + s: 'неколико секунди', + m: translator.relativeTimeFormatter, + mm: translator.relativeTimeFormatter, + h: translator.relativeTimeFormatter, + hh: translator.relativeTimeFormatter, + d: translator.relativeTimeFormatter, + dd: translator.relativeTimeFormatter, + M: translator.relativeTimeFormatter, + MM: translator.relativeTimeFormatter, + y: translator.relativeTimeFormatter, + yy: translator.relativeTimeFormatter }, ordinal: n => `${n}.`, formats: { LT: 'H:mm', LTS: 'H:mm:ss', - L: 'DD.MM.YYYY', - LL: 'D. MMMM YYYY', - LLL: 'D. MMMM YYYY H:mm', - LLLL: 'dddd, D. MMMM YYYY H:mm' + L: 'D. M. YYYY.', + LL: 'D. MMMM YYYY.', + LLL: 'D. MMMM YYYY. H:mm', + LLLL: 'dddd, D. MMMM YYYY. H:mm' } } diff --git a/src/locale/sr.js b/src/locale/sr.js index ca0cb47f..8399d8e4 100644 --- a/src/locale/sr.js +++ b/src/locale/sr.js @@ -1,6 +1,42 @@ // Serbian [sr] import dayjs from 'dayjs' +const translator = { + words: { + m: ['jedan minut', 'jednog minuta'], + mm: ['%d minut', '%d minuta', '%d minuta'], + h: ['jedan sat', 'jednog sata'], + hh: ['%d sat', '%d sata', '%d sati'], + d: ['jedan dan', 'jednog dana'], + dd: ['%d dan', '%d dana', '%d dana'], + M: ['jedan mesec', 'jednog meseca'], + MM: ['%d mesec', '%d meseca', '%d meseci'], + y: ['jednu godinu', 'jedne godine'], + yy: ['%d godinu', '%d godine', '%d godina'] + }, + correctGrammarCase(number, wordKey) { + if (number % 10 >= 1 && number % 10 <= 4 && (number % 100 < 10 || number % 100 >= 20)) { + return number % 10 === 1 ? wordKey[0] : wordKey[1] + } + return wordKey[2] + }, + relativeTimeFormatter(number, withoutSuffix, key, isFuture) { + const wordKey = translator.words[key] + + if (key.length === 1) { + // Nominativ + if (key === 'y' && withoutSuffix) return 'jedna godina' + return isFuture || withoutSuffix ? wordKey[0] : wordKey[1] + } + + const word = translator.correctGrammarCase(number, wordKey) + // Nominativ + if (key === 'yy' && withoutSuffix && word === '%d godinu') return `${number} godina` + + return word.replace('%d', number) + } +} + const locale = { name: 'sr', weekdays: 'Nedelja_Ponedeljak_Utorak_Sreda_Četvrtak_Petak_Subota'.split('_'), @@ -12,26 +48,26 @@ const locale = { relativeTime: { future: 'za %s', past: 'pre %s', - s: 'sekunda', - m: 'minut', - mm: '%d minuta', - h: 'sat', - hh: '%d sati', - d: 'dan', - dd: '%d dana', - M: 'mesec', - MM: '%d meseci', - y: 'godina', - yy: '%d godine' + s: 'nekoliko sekundi', + m: translator.relativeTimeFormatter, + mm: translator.relativeTimeFormatter, + h: translator.relativeTimeFormatter, + hh: translator.relativeTimeFormatter, + d: translator.relativeTimeFormatter, + dd: translator.relativeTimeFormatter, + M: translator.relativeTimeFormatter, + MM: translator.relativeTimeFormatter, + y: translator.relativeTimeFormatter, + yy: translator.relativeTimeFormatter }, ordinal: n => `${n}.`, formats: { LT: 'H:mm', LTS: 'H:mm:ss', - L: 'DD.MM.YYYY', - LL: 'D. MMMM YYYY', - LLL: 'D. MMMM YYYY H:mm', - LLLL: 'dddd, D. MMMM YYYY H:mm' + L: 'D. M. YYYY.', + LL: 'D. MMMM YYYY.', + LLL: 'D. MMMM YYYY. H:mm', + LLLL: 'dddd, D. MMMM YYYY. H:mm' } } diff --git a/src/plugin/arraySupport/index.js b/src/plugin/arraySupport/index.js new file mode 100644 index 00000000..df43727f --- /dev/null +++ b/src/plugin/arraySupport/index.js @@ -0,0 +1,25 @@ +export default (o, c, dayjs) => { + const proto = c.prototype + const parseDate = (cfg) => { + const { date, utc } = cfg + if (Array.isArray(date)) { + if (utc) { + if (!date.length) { + return new Date() + } + return new Date(Date.UTC.apply(null, date)) + } + if (date.length === 1) { + return dayjs(String(date[0])).toDate() + } + return new (Function.prototype.bind.apply(Date, [null].concat(date)))() + } + return date + } + + const oldParse = proto.parse + proto.parse = function (cfg) { + cfg.date = parseDate.bind(this)(cfg) + oldParse.bind(this)(cfg) + } +} diff --git a/src/plugin/duration/index.js b/src/plugin/duration/index.js index 1c07b160..48ceadd8 100644 --- a/src/plugin/duration/index.js +++ b/src/plugin/duration/index.js @@ -177,4 +177,13 @@ export default (option, Dayjs, dayjs) => { return wrapper(input, { $l }, unit) } dayjs.isDuration = isDuration + + const oldAdd = Dayjs.prototype.add + Dayjs.prototype.add = function (addition, units) { + if (isDuration(addition)) { + addition = addition.asMilliseconds() + units = 'ms' + } + return oldAdd.bind(this)(addition, units) + } } diff --git a/src/plugin/localeData/index.js b/src/plugin/localeData/index.js index 71ded86d..503db0de 100644 --- a/src/plugin/localeData/index.js +++ b/src/plugin/localeData/index.js @@ -1,3 +1,5 @@ +import { t } from '../localizedFormat' + export default (o, c, dayjs) => { // locale needed later const proto = c.prototype const getLocalePart = part => (part && (part.indexOf ? part : part.s)) @@ -11,6 +13,9 @@ export default (o, c, dayjs) => { // locale needed later return result.map((_, index) => (result[(index + (weekStart || 0)) % 7])) } const getDayjsLocaleObject = () => dayjs.Ls[dayjs.locale()] + const getLongDateFormat = (l, format) => + l.formats[format] || t(l.formats[format.toUpperCase()]) + const localeData = function () { return { months: instance => @@ -23,7 +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 => this.$locale().formats[format] + longDateFormat: format => getLongDateFormat(this.$locale(), format) + } } proto.localeData = function () { @@ -38,7 +44,8 @@ export default (o, c, dayjs) => { // locale needed later weekdaysShort: () => dayjs.weekdaysShort(), weekdaysMin: () => dayjs.weekdaysMin(), months: () => dayjs.months(), - monthsShort: () => dayjs.monthsShort() + monthsShort: () => dayjs.monthsShort(), + longDateFormat: format => getLongDateFormat(localeObject, format) } } diff --git a/src/plugin/localizedFormat/index.js b/src/plugin/localizedFormat/index.js index b72e635b..b56d2252 100644 --- a/src/plugin/localizedFormat/index.js +++ b/src/plugin/localizedFormat/index.js @@ -1,5 +1,8 @@ import { FORMAT_DEFAULT } from '../../constant' +export const t = format => + format.replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g, (_, a, b) => a || b.slice(1)) + export default (o, c, d) => { const proto = c.prototype const oldFormat = proto.format @@ -12,7 +15,6 @@ export default (o, c, d) => { LLLL: 'dddd, MMMM D, YYYY h:mm A' } d.en.formats = englishFormats - const t = format => format.replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g, (_, a, b) => a || b.slice(1)) proto.format = function (formatStr = FORMAT_DEFAULT) { const { formats = {} } = this.$locale() const result = formatStr.replace(/(\[[^\]]+])|(LTS?|l{1,4}|L{1,4})/g, (_, a, b) => { diff --git a/src/plugin/objectSupport/index.js b/src/plugin/objectSupport/index.js index 6d161b8d..4344dc40 100644 --- a/src/plugin/objectSupport/index.js +++ b/src/plugin/objectSupport/index.js @@ -1,4 +1,4 @@ -export default (o, c) => { +export default (o, c, dayjs) => { const proto = c.prototype const isObject = obj => !(obj instanceof Date) && !(obj instanceof Array) && obj instanceof Object const prettyUnit = (u) => { @@ -9,13 +9,16 @@ export default (o, c) => { const { date, utc } = cfg const $d = {} if (isObject(date)) { - const now = new Date() + if (!Object.keys(date).length) { + return new Date() + } + const now = utc ? dayjs.utc() : dayjs() Object.keys(date).forEach((k) => { $d[prettyUnit(k)] = date[k] }) - const d = $d.day || ((!$d.year && !($d.month >= 0)) ? now.getDate() : 1) - const y = $d.year || now.getFullYear() - const M = $d.month >= 0 ? $d.month : ((!$d.year && !$d.day) ? now.getMonth() : 0)// eslint-disable-line no-nested-ternary,max-len + const d = $d.day || ((!$d.year && !($d.month >= 0)) ? now.date() : 1) + const y = $d.year || now.year() + const M = $d.month >= 0 ? $d.month : ((!$d.year && !$d.day) ? now.month() : 0)// eslint-disable-line no-nested-ternary,max-len const h = $d.hour || 0 const m = $d.minute || 0 const s = $d.second || 0 diff --git a/test/locale/br.test.js b/test/locale/br.test.js new file mode 100644 index 00000000..1aa106a4 --- /dev/null +++ b/test/locale/br.test.js @@ -0,0 +1,54 @@ +import moment from 'moment' +import MockDate from 'mockdate' +import dayjs from '../../src' +import relativeTime from '../../src/plugin/relativeTime' +import '../../src/locale/br' + +dayjs.extend(relativeTime) + +beforeEach(() => { + MockDate.set(new Date()) +}) + +afterEach(() => { + MockDate.reset() +}) + +it('Format Month with locale function', () => { + for (let i = 0; i <= 7; i += 1) { + const dayjsBR = dayjs().locale('br').add(i, 'day') + const momentBR = moment().locale('br').add(i, 'day') + const testFormat1 = 'DD MMMM YYYY MMM' + const testFormat2 = 'MMMM' + const testFormat3 = 'MMM' + expect(dayjsBR.format(testFormat1)).toEqual(momentBR.format(testFormat1)) + expect(dayjsBR.format(testFormat2)).toEqual(momentBR.format(testFormat2)) + expect(dayjsBR.format(testFormat3)).toEqual(momentBR.format(testFormat3)) + } +}) + +it('RelativeTime: Time from X', () => { + const T = [ + [44.4, 'second'], // a few seconds + [89.5, 'second'], // a minute + [130, 'second'], // two minutes + [43, 'minute'], // 44 minutes + [1, 'hour'], // 1 hour + [21, 'hour'], // 21 hours + [2, 'day'], // 2 days + [25, 'day'], // 25 days + [2, 'month'], // 2 months + [10, 'month'], // 10 months + [18, 'month'], // 2 years + [15, 'year'] // 15 years + ] + + T.forEach((t) => { + dayjs.locale('br') + moment.locale('br') + expect(dayjs().from(dayjs().add(t[0], t[1]))) + .toBe(moment().from(moment().add(t[0], t[1]))) + expect(dayjs().from(dayjs().add(t[0], t[1]), true)) + .toBe(moment().from(moment().add(t[0], t[1]), true)) + }) +}) diff --git a/test/locale/de.test.js b/test/locale/de.test.js new file mode 100644 index 00000000..699b0596 --- /dev/null +++ b/test/locale/de.test.js @@ -0,0 +1,79 @@ +import MockDate from 'mockdate' +import moment from 'moment' +import dayjs from '../../src' +import relativeTime from '../../src/plugin/relativeTime' +import '../../src/locale/de' + +dayjs.extend(relativeTime) + +beforeEach(() => { + MockDate.set(new Date()) +}) + +afterEach(() => { + MockDate.reset() +}) + +it('German locale relative time in past and future with suffix', () => { + const cases = [ + [1, 's', 'in ein paar Sekunden'], + [-1, 's', 'vor ein paar Sekunden'], + [1, 'm', 'in einer Minute'], + [-1, 'm', 'vor einer Minute'], + [1, 'h', 'in einer Stunde'], + [-1, 'h', 'vor einer Stunde'], + [1, 'd', 'in einem Tag'], + [-1, 'd', 'vor einem Tag'], + [1, 'M', 'in einem Monat'], + [-1, 'M', 'vor einem Monat'], + [2, 'd', 'in 2 Tagen'], + [-2, 'd', 'vor 2 Tagen'], + [10, 'd', 'in 10 Tagen'], + [-10, 'd', 'vor 10 Tagen'], + [6, 'm', 'in 6 Minuten'], + [-6, 'm', 'vor 6 Minuten'], + [5, 'h', 'in 5 Stunden'], + [-5, 'h', 'vor 5 Stunden'], + [3, 'M', 'in 3 Monaten'], + [-3, 'M', 'vor 3 Monaten'], + [4, 'y', 'in 4 Jahren'], + [-4, 'y', 'vor 4 Jahren'] + ] + cases.forEach((c) => { + expect(dayjs().add(c[0], c[1]).locale('de').fromNow()) + .toBe(c[2]) + expect(dayjs().add(c[0], c[1]).locale('de').fromNow()) + .toBe(moment().add(c[0], c[1]).locale('de').fromNow()) + }) +}) + +it('German locale relative time in past and future without suffix', () => { + const cases = [ + [1, 's', 'ein paar Sekunden'], + [-1, 's', 'ein paar Sekunden'], + [1, 'm', 'eine Minute'], + [-1, 'm', 'eine Minute'], + [1, 'h', 'eine Stunde'], + [-1, 'h', 'eine Stunde'], + [1, 'd', 'ein Tag'], + [-1, 'd', 'ein Tag'], + [2, 'd', '2 Tage'], + [-2, 'd', '2 Tage'], + [10, 'd', '10 Tage'], + [-10, 'd', '10 Tage'], + [6, 'm', '6 Minuten'], + [-6, 'm', '6 Minuten'], + [5, 'h', '5 Stunden'], + [-5, 'h', '5 Stunden'], + [3, 'M', '3 Monate'], + [-3, 'M', '3 Monate'], + [4, 'y', '4 Jahre'], + [-4, 'y', '4 Jahre'] + ] + cases.forEach((c) => { + expect(dayjs().add(c[0], c[1]).locale('de').fromNow(true)) + .toBe(c[2]) + expect(dayjs().add(c[0], c[1]).locale('de').fromNow(true)) + .toBe(moment().add(c[0], c[1]).locale('de').fromNow(true)) + }) +}) diff --git a/test/locale/sr-cyrl.test.js b/test/locale/sr-cyrl.test.js new file mode 100644 index 00000000..a72ed33a --- /dev/null +++ b/test/locale/sr-cyrl.test.js @@ -0,0 +1,54 @@ +import MockDate from 'mockdate' +import dayjs from '../../src' +import relativeTime from '../../src/plugin/relativeTime' +import '../../src/locale/sr-cyrl' + +dayjs.extend(relativeTime) + +beforeEach(() => { + MockDate.set(new Date()) +}) + +afterEach(() => { + MockDate.reset() +}) + +it('Serbian cyrillic locale relative time in past and future', () => { + const cases = [ + [1, 's', 'за неколико секунди', 'неколико секунди'], + [-1, 's', 'пре неколико секунди', 'неколико секунди'], + [4, 's', 'за неколико секунди', 'неколико секунди'], + [1, 'm', 'за један минут', 'један минут'], + [-1, 'm', 'пре једног минута', 'један минут'], + [4, 'm', 'за 4 минута', '4 минута'], + [5, 'm', 'за 5 минута', '5 минута'], + [21, 'm', 'за 21 минут', '21 минут'], + [1, 'h', 'за један сат', 'један сат'], + [-1, 'h', 'пре једног сата', 'један сат'], + [4, 'h', 'за 4 сата', '4 сата'], + [5, 'h', 'за 5 сати', '5 сати'], + [21, 'h', 'за 21 сат', '21 сат'], + [1, 'd', 'за један дан', 'један дан'], + [-1, 'd', 'пре једног дана', 'један дан'], + [4, 'd', 'за 4 дана', '4 дана'], + [5, 'd', 'за 5 дана', '5 дана'], + [21, 'd', 'за 21 дан', '21 дан'], + [1, 'M', 'за један месец', 'један месец'], + [-1, 'M', 'пре једног месеца', 'један месец'], + [4, 'M', 'за 4 месеца', '4 месеца'], + [5, 'M', 'за 5 месеци', '5 месеци'], + [10, 'M', 'за 10 месеци', '10 месеци'], + [1, 'y', 'за једну годину', 'једна година'], + [-1, 'y', 'пре једне године', 'једна година'], + [4, 'y', 'за 4 године', '4 године'], + [5, 'y', 'за 5 година', '5 година'], + [21, 'y', 'за 21 годину', '21 година'] + ] + + cases.forEach((c) => { + expect(dayjs().add(c[0], c[1]).locale('sr-cyrl').fromNow()).toBe(c[2]) + expect(dayjs().add(c[0], c[1]).locale('sr-cyrl').fromNow(true)).toBe(c[3]) + // TODO: compare to momentjs once logic and grammar are fixed there + }) +}) + diff --git a/test/locale/sr.test.js b/test/locale/sr.test.js new file mode 100644 index 00000000..c2ccb9f2 --- /dev/null +++ b/test/locale/sr.test.js @@ -0,0 +1,55 @@ +import MockDate from 'mockdate' +import dayjs from '../../src' +import relativeTime from '../../src/plugin/relativeTime' +import '../../src/locale/sr' + +dayjs.extend(relativeTime) + +beforeEach(() => { + MockDate.set(new Date()) +}) + +afterEach(() => { + MockDate.reset() +}) + +it('Serbian locale relative time in past and future', () => { + const cases = [ + [1, 's', 'za nekoliko sekundi', 'nekoliko sekundi'], + [-1, 's', 'pre nekoliko sekundi', 'nekoliko sekundi'], + [4, 's', 'za nekoliko sekundi', 'nekoliko sekundi'], + [1, 'm', 'za jedan minut', 'jedan minut'], + [-1, 'm', 'pre jednog minuta', 'jedan minut'], + [4, 'm', 'za 4 minuta', '4 minuta'], + [5, 'm', 'za 5 minuta', '5 minuta'], + [21, 'm', 'za 21 minut', '21 minut'], + [1, 'h', 'za jedan sat', 'jedan sat'], + [-1, 'h', 'pre jednog sata', 'jedan sat'], + [4, 'h', 'za 4 sata', '4 sata'], + [5, 'h', 'za 5 sati', '5 sati'], + [21, 'h', 'za 21 sat', '21 sat'], + [1, 'd', 'za jedan dan', 'jedan dan'], + [-1, 'd', 'pre jednog dana', 'jedan dan'], + [4, 'd', 'za 4 dana', '4 dana'], + [5, 'd', 'za 5 dana', '5 dana'], + [21, 'd', 'za 21 dan', '21 dan'], + [1, 'M', 'za jedan mesec', 'jedan mesec'], + [-1, 'M', 'pre jednog meseca', 'jedan mesec'], + [4, 'M', 'za 4 meseca', '4 meseca'], + [5, 'M', 'za 5 meseci', '5 meseci'], + [10, 'M', 'za 10 meseci', '10 meseci'], + [1, 'y', 'za jednu godinu', 'jedna godina'], + [-1, 'y', 'pre jedne godine', 'jedna godina'], + [4, 'y', 'za 4 godine', '4 godine'], + [5, 'y', 'za 5 godina', '5 godina'], + [21, 'y', 'za 21 godinu', '21 godina'] + ] + + cases.forEach((c) => { + // With suffix + expect(dayjs().add(c[0], c[1]).locale('sr').fromNow()).toBe(c[2]) + // Without suffix + expect(dayjs().add(c[0], c[1]).locale('sr').fromNow(true)).toBe(c[3]) + // TODO: compare to momentjs once logic and grammar are fixed there + }) +}) diff --git a/test/plugin/arraySupport.test.js b/test/plugin/arraySupport.test.js new file mode 100755 index 00000000..dc5dda7a --- /dev/null +++ b/test/plugin/arraySupport.test.js @@ -0,0 +1,52 @@ +import MockDate from 'mockdate' +import moment from 'moment' +import dayjs from '../../src' +import arraySupport from '../../src/plugin/arraySupport' +import utc from '../../src/plugin/utc' + +dayjs.extend(utc) +dayjs.extend(arraySupport) + +beforeEach(() => { + MockDate.set(new Date()) +}) + +afterEach(() => { + MockDate.reset() +}) + +describe('parse empty array', () => { + it('local', () => { + expect(dayjs([]).format()) + .toBe(moment([]).format()) + }) + it('utc', () => { + expect(dayjs.utc([]).format()) + .toBe(moment.utc([]).format()) + }) +}) + +const testArrs = [ + [2010, 1, 14, 15, 25, 50, 125], + [2010], + [2010, 6], + [2010, 6, 10] +] + +describe('parse array local', () => { + testArrs.forEach((testArr) => { + it(testArr, () => { + expect(dayjs(testArr).format()) + .toBe(moment(testArr).format()) + }) + }) +}) + +describe('parse array utc', () => { + testArrs.forEach((testArr) => { + it(testArr, () => { + expect(dayjs.utc(testArr).format()) + .toBe(moment.utc(testArr).format()) + }) + }) +}) diff --git a/test/plugin/duration.test.js b/test/plugin/duration.test.js index f26c78c7..a85accf3 100644 --- a/test/plugin/duration.test.js +++ b/test/plugin/duration.test.js @@ -139,6 +139,12 @@ describe('Add', () => { expect(a.add({ days: 5 }).days()).toBe(6) }) +describe('Add duration', () => { + const a = dayjs('2020-10-01') + const days = dayjs.duration(2, 'days') + expect(a.add(days).format('YYYY-MM-DD')).toBe('2020-10-03') +}) + describe('Subtract', () => { const a = dayjs.duration(3, 'days') const b = dayjs.duration(2, 'days') diff --git a/test/plugin/localeData.test.js b/test/plugin/localeData.test.js index 109d4780..82985640 100644 --- a/test/plugin/localeData.test.js +++ b/test/plugin/localeData.test.js @@ -38,7 +38,7 @@ describe('Instance localeData', () => { expect(dayjsLocaleData.weekdaysMin()).toEqual(momentLocaleData.weekdaysMin()) expect(dayjsLocaleData.weekdaysShort(d)).toBe(momentLocaleData.weekdaysShort(m)) expect(dayjsLocaleData.weekdaysShort()).toEqual(momentLocaleData.weekdaysShort()) - const longDateFormats = ['LT', 'LTS', 'L', 'LL', 'LLL', 'LLLL'] + const longDateFormats = ['LT', 'LTS', 'L', 'LL', 'LLL', 'LLLL', 'l', 'll', 'lll', 'llll'] longDateFormats.forEach((f) => { expect(dayjsLocaleData.longDateFormat(f)).toEqual(momentLocaleData.longDateFormat(f)) }) @@ -61,6 +61,10 @@ it('Global localeData', () => { expect(dayjsLocaleData.weekdays()).toEqual(momentLocaleData.weekdays()) expect(dayjsLocaleData.weekdaysShort()).toEqual(momentLocaleData.weekdaysShort()) expect(dayjsLocaleData.weekdaysMin()).toEqual(momentLocaleData.weekdaysMin()) + const longDateFormats = ['LT', 'LTS', 'L', 'LL', 'LLL', 'LLLL', 'l', 'll', 'lll', 'llll'] + longDateFormats.forEach((f) => { + expect(dayjsLocaleData.longDateFormat(f)).toEqual(momentLocaleData.longDateFormat(f)) + }) }) }) diff --git a/test/plugin/objectSupport.test.js b/test/plugin/objectSupport.test.js index 476b97f4..06b10f9d 100755 --- a/test/plugin/objectSupport.test.js +++ b/test/plugin/objectSupport.test.js @@ -21,6 +21,9 @@ const now = new Date() const currentYear = now.getFullYear() const currentMonth = utils.s(now.getMonth() + 1, 2, '0') const currentDate = utils.s(now.getDate(), 2, '0') +const currentUTCYear = now.getUTCFullYear() +const currentUTCMonth = utils.s(now.getUTCMonth() + 1, 2, '0') +const currentUTCDate = utils.s(now.getUTCDate(), 2, '0') const fmt = 'YYYY-MM-DD HH:mm:ss.SSS' const tests = [ [{ year: 2010 }, '2010-01-01 00:00:00.000'], @@ -31,7 +34,8 @@ const tests = [ { hour: 15, minute: 25, second: 50, millisecond: 125 }, - `${currentYear}-${currentMonth}-${currentDate} 15:25:50.125`], + `${currentYear}-${currentMonth}-${currentDate} 15:25:50.125`, + `${currentUTCYear}-${currentUTCMonth}-${currentUTCDate} 15:25:50.125`], [ { year: 2010, month: 1, day: 12, hours: 1 @@ -110,6 +114,18 @@ const tests = [ '2010-02-14 15:25:50.125' ] ] + +describe('parse empty object', () => { + it('local', () => { + expect(dayjs({}).format()) + .toBe(moment({}).format()) + }) + it('utc', () => { + expect(dayjs.utc({}).format()) + .toBe(moment.utc({}).format()) + }) +}) + it('Constructor from Object', () => { for (let i = 0; i < tests.length; i += 1) { expect(dayjs(tests[i][0]).format(fmt)).toBe(tests[i][1]) @@ -119,8 +135,9 @@ it('Constructor from Object', () => { it('Constructor from Object UTC', () => { for (let i = 0; i < tests.length; i += 1) { - expect(dayjs.utc(tests[i][0]).format(fmt)).toBe(tests[i][1]) - expect(moment.utc(tests[i][0]).format(fmt)).toBe(tests[i][1]) + const result = tests[i][2] || tests[i][1] + expect(dayjs.utc(tests[i][0]).format(fmt)).toBe(result) + expect(moment.utc(tests[i][0]).format(fmt)).toBe(result) } }) it('Set from Object', () => { diff --git a/types/plugin/arraySupport.d.ts b/types/plugin/arraySupport.d.ts new file mode 100755 index 00000000..30ec75e5 --- /dev/null +++ b/types/plugin/arraySupport.d.ts @@ -0,0 +1,4 @@ +import { PluginFunc } from 'dayjs' + +declare const plugin: PluginFunc +export = plugin diff --git a/types/plugin/duration.d.ts b/types/plugin/duration.d.ts index fceb1906..3f679152 100644 --- a/types/plugin/duration.d.ts +++ b/types/plugin/duration.d.ts @@ -1,58 +1,61 @@ import { PluginFunc } from 'dayjs' declare const plugin: PluginFunc -export default plugin +export as namespace plugin; +export = plugin -type DurationInputType = string | number | object -type DurationAddType = number | object | Duration +declare namespace plugin { + type DurationInputType = string | number | object + type DurationAddType = number | object | Duration -export declare class Duration { - constructor (input: DurationInputType, unit?: string, locale?: string) + interface Duration { + new (input: DurationInputType, unit?: string, locale?: string): Duration - clone(): Duration - - humanize(withSuffix?: boolean): string + clone(): Duration - milliseconds(): number - asMilliseconds(): number + humanize(withSuffix?: boolean): string - seconds(): number - asSeconds(): number + milliseconds(): number + asMilliseconds(): number - minutes(): number - asMinutes(): number + seconds(): number + asSeconds(): number - hours(): number - asHours(): number + minutes(): number + asMinutes(): number - days(): number - asDays(): number + hours(): number + asHours(): number - weeks(): number - asWeeks(): number + days(): number + asDays(): number - months(): number - asMonths(): number + weeks(): number + asWeeks(): number - years(): number - asYears(): number + months(): number + asMonths(): number - as(unit: string): number + years(): number + asYears(): number - get(unit: string): number + as(unit: string): number - add(input: DurationAddType, unit? : string): Duration - - subtract(input: DurationAddType, unit? : string): Duration + get(unit: string): number - toJSON(): string + add(input: DurationAddType, unit? : string): Duration - toISOString(): string + subtract(input: DurationAddType, unit? : string): Duration - locale(locale: string): Duration + toJSON(): string + + toISOString(): string + + locale(locale: string): Duration + } } declare module 'dayjs' { - export function duration(input?: DurationInputType , unit?: string): Duration - export function isDuration(d: any): d is Duration + export function duration(input?: plugin.DurationInputType , unit?: string): plugin.Duration + export function isDuration(d: any): d is plugin.Duration }