diff --git a/README.md b/README.md index 8516adcfe..b713eaa33 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,27 @@ +
+ + Warp sponsorship +
+ Warp is built for coding with multiple AI agents +
+
+ +--- + +
+ +
+ Requestly sponsorship +
+ Requestly - Free & Open-Source alternative to Postman +
+ All-in-one platform to Test, Mock and Intercept APIs. +
+
+
+ +--- + English | [简体中文](./docs/zh-cn/README.zh-CN.md) | [日本語](./docs/ja/README-ja.md) | [Português Brasileiro](./docs/pt-br/README-pt-br.md) | [한국어](./docs/ko/README-ko.md) | [Español (España)](./docs/es-es/README-es-es.md) | [Русский](./docs/ru/README-ru.md) | [Türkçe](./docs/tr/README-tr.md) | [සිංහල](./docs/si/README-si.md) | [עברית](./docs/he/README-he.md)

- - - ## Sponsors Support this project by becoming a sponsor. Your logo will show up here with a link to your website. @@ -123,12 +141,8 @@ Support this project by becoming a sponsor. Your logo will show up here with a l          - - - -         - - + +          @@ -139,6 +153,14 @@ Support this project by becoming a sponsor. Your logo will show up here with a l          + + + +         + + Instagram Story Viewer + +         BestKru diff --git a/docs/es-es/README-es-es.md b/docs/es-es/README-es-es.md index d9b54c794..e3519168f 100644 --- a/docs/es-es/README-es-es.md +++ b/docs/es-es/README-es-es.md @@ -108,12 +108,6 @@ dayjs().format('Q Do k kk X x') // ahora tenemos más formatos disponibles 📚[Lista de complementos](https://day.js.org/docs/en/plugin/plugin) -### Tendencia de Uso - - - - - ## Patrocinadores Apoya a este proyecto convirtiéndote en un patrocinador. Tu logo aparecerá aquí, enlazado a tu sitio web. [[Conviértete en un patrocinador](https://opencollective.com/dayjs#sponsor)] diff --git a/docs/he/README-he.md b/docs/he/README-he.md index f410269ef..00cc6a2cb 100644 --- a/docs/he/README-he.md +++ b/docs/he/README-he.md @@ -120,12 +120,6 @@ dayjs().format('Q Do k kk X x') // כעת יותר אפשרויות זמינות 📚[רשימת תוספים](https://day.js.org/docs/en/plugin/plugin) -### מגמת השימוש - - - - - ### ספונסרים תמכו בפרויקט זה כדי להיות ספונסר. קבלו לוגו עם קישור לאתר שלכם שיופיע כאן. diff --git a/docs/ja/README-ja.md b/docs/ja/README-ja.md index 783e86801..17f7fbe1d 100644 --- a/docs/ja/README-ja.md +++ b/docs/ja/README-ja.md @@ -108,12 +108,6 @@ dayjs().format('Q Do k kk X x') // 多様なフォーマットが利用可能に 📚[プラグインリスト](https://day.js.org/docs/en/plugin/plugin) -### 使用トレンド - - - - - ## ライセンス Day.js は [MIT License](../../LICENSE) のもとで利用を許諾します。 diff --git a/docs/ko/README-ko.md b/docs/ko/README-ko.md index 3b1b985b5..9a66dfcb5 100644 --- a/docs/ko/README-ko.md +++ b/docs/ko/README-ko.md @@ -108,12 +108,6 @@ dayjs().format('Q Do k kk X x') // more available formats 📚[플러그인 목록](https://day.js.org/docs/en/plugin/plugin) -### 사용 트렌드 - - - - - ## License Day.js는 [MIT License](./LICENSE)를 사용합니다. diff --git a/docs/pt-br/README-pt-br.md b/docs/pt-br/README-pt-br.md index 31d0bdbc4..0721771d2 100644 --- a/docs/pt-br/README-pt-br.md +++ b/docs/pt-br/README-pt-br.md @@ -107,12 +107,6 @@ dayjs().format('Q Do k kk X x') // mais formatos disponíveis pelo plugin 📚[Lista de Plugins](https://day.js.org/docs/en/plugin/plugin) -### Tendência de Uso - - - - - ## Patrocinadores Ajude este projeto se tornando um patrocinador. O seu logo será exibido aqui, com um link para o seu site. [[Tornar-se um Patrocinador](https://opencollective.com/dayjs#sponsor)]. diff --git a/docs/ru/README-ru.md b/docs/ru/README-ru.md index 065af14ba..2d1079c77 100644 --- a/docs/ru/README-ru.md +++ b/docs/ru/README-ru.md @@ -98,12 +98,6 @@ dayjs().format('Q Do k kk X x') // больше доступных формат 📚[Список плагинов](https://day.js.org/docs/ru/plugin/plugin) -### Тенденция использования - - - - - ## Спонсоры Поддержите этот проект, став спонсором. Ваш логотип будет показан здесь с ссылкой на ваш веб-сайт. [[Стать спонсором](https://opencollective.com/dayjs#sponsor)] diff --git a/docs/si/README-si.md b/docs/si/README-si.md index e5f7fb92b..b170a516b 100644 --- a/docs/si/README-si.md +++ b/docs/si/README-si.md @@ -98,12 +98,6 @@ dayjs().format('Q Do k kk X x') // more available formats 📚[ දිගු ලේඛනය](https://day.js.org/docs/en/plugin/plugin) -### භාවිත ප්‍රමාණයේ ප්‍රසූතිය - - - - - ## අනුග්‍රාහකයින් අනුග්‍රහය දැක්වීමෙන් මෙම ව්‍යාපෘතියට සහාය වන්න. ඔබගේ අඩවියේ සබැඳියක් සමඟ ඔබගේ ලාංඡනය මෙහි පෙන්වනු ඇත. diff --git a/docs/tr/README-tr.md b/docs/tr/README-tr.md index 2b1a63549..e408dd77c 100644 --- a/docs/tr/README-tr.md +++ b/docs/tr/README-tr.md @@ -108,12 +108,6 @@ dayjs().format('Q Do k kk X x') // diğer mevcut formatlar 📚[Eklenti Listesi](https://day.js.org/docs/en/plugin/plugin) -### Kullanım Trendi - - - - - ## Sponsorlar Sponsor olarak bu projeye destek olun. Logonuz, web sayfanızın linki ile birlikte burada görünür. [[Sponsor Ol](https://opencollective.com/dayjs#sponsor)] diff --git a/docs/zh-cn/README.zh-CN.md b/docs/zh-cn/README.zh-CN.md index d7bb1ecbc..5abdea0a3 100644 --- a/docs/zh-cn/README.zh-CN.md +++ b/docs/zh-cn/README.zh-CN.md @@ -108,12 +108,6 @@ dayjs().format('Q Do k kk X x') // 使用扩展后的API 📚[插件列表](https://day.js.org/docs/zh-CN/plugin/plugin) -### 使用量趋势 - - - - - ## 开源协议 Day.js 遵循 [MIT 开源协议](../../LICENSE). diff --git a/package.json b/package.json index 2c74cddc7..e2baf268a 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "dayjs.min.js", "types": "index.d.ts", "scripts": { - "test": "TZ=Pacific/Auckland npm run test-tz && TZ=Europe/London npm run test-tz && TZ=America/Whitehorse npm run test-tz && npm run test-tz && jest", + "test": "TZ=Pacific/Auckland npm run test-tz && TZ=Europe/London npm run test-tz && TZ=America/Whitehorse npm run test-tz && npm run test-tz && jest --coverage --coverageThreshold='{ \"global\": { \"lines\": 100} }'", "test-tz": "date && jest test/timezone.test --coverage=false", "lint": "./node_modules/.bin/eslint src/* test/* build/*", "prettier": "prettier --write \"docs/**/*.md\"", diff --git a/src/locale/ar.js b/src/locale/ar.js index f418379c8..5d28a1d31 100644 --- a/src/locale/ar.js +++ b/src/locale/ar.js @@ -29,6 +29,12 @@ const numberMap = { '٠': '0' } +const fromArabNumeralsRegex = /[١٢٣٤٥٦٧٨٩٠]/g +const fromArabComaRegex = /،/g + +const toArabNumeralsRegex = /\d/g +const toArabComaRegex = /,/g + const locale = { name: 'ar', weekdays: 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'), @@ -56,15 +62,15 @@ const locale = { preparse(string) { return string .replace( - /[١٢٣٤٥٦٧٨٩٠]/g, + fromArabNumeralsRegex, match => numberMap[match] ) - .replace(/،/g, ',') + .replace(fromArabComaRegex, ',') }, postformat(string) { return string - .replace(/\d/g, match => symbolMap[match]) - .replace(/,/g, '،') + .replace(toArabNumeralsRegex, match => symbolMap[match]) + .replace(toArabComaRegex, '،') }, ordinal: n => n, formats: { diff --git a/src/locale/be.js b/src/locale/be.js index 3b4e2d731..c35d56574 100644 --- a/src/locale/be.js +++ b/src/locale/be.js @@ -1,15 +1,63 @@ // Belarusian [be] import dayjs from 'dayjs' +const monthFormat = 'студзеня_лютага_сакавіка_красавіка_траўня_чэрвеня_ліпеня_жніўня_верасня_кастрычніка_лістапада_снежня'.split('_') +const monthStandalone = 'студзень_лютый_сакавік_красавік_травень_чэрвень_ліпень_жнівень_верасень_кастрычнік_лістапад_снежань'.split('_') + +const monthShortFormat = 'студ_лют_сак_крас_трав_чэрв_ліп_жнів_вер_каст_ліст_снеж.'.split('_') +const monthShortStandalone = 'студ_лют_сак_крас_трав_чэрв_ліп_жнів_вер_каст_ліст_снеж'.split('_') + +const MONTHS_IN_FORMAT = /D[oD]?(\[[^[\]]*\]|\s)+MMMM?/ + +function plural(word, num) { + const forms = word.split('_') + return num % 10 === 1 && num % 100 !== 11 ? forms[0] : (num % 10 >= 2 && num % 10 <= 4 && (num % 100 < 10 || num % 100 >= 20) ? forms[1] : forms[2]) // eslint-disable-line +} +function relativeTimeWithPlural(number, withoutSuffix, key) { + const format = { + ss: withoutSuffix ? 'секунда_секунды_секунд' : 'секунду_секунды_секунд', + mm: withoutSuffix ? 'хвіліна_хвіліны_хвілін' : 'хвіліну_хвіліны_хвілін', + hh: withoutSuffix ? 'гадзіна_гадзіны_гадзін' : 'гадзіну_гадзіны_гадзін', + dd: 'дзень_дні_дзён', + MM: 'месяц_месяцы_месяцаў', + yy: 'год_гады_гадоў' + } + if (key === 'm') { + return withoutSuffix ? 'хвіліна' : 'хвіліну' + } else if (key === 'h') { + return withoutSuffix ? 'гадзіна' : 'гадзіну' + } + + return `${number} ${plural(format[key], +number)}` +} + +const months = (dayjsInstance, format) => { + if (MONTHS_IN_FORMAT.test(format)) { + return monthFormat[dayjsInstance.month()] + } + return monthStandalone[dayjsInstance.month()] +} +months.s = monthStandalone +months.f = monthFormat + +const monthsShort = (dayjsInstance, format) => { + if (MONTHS_IN_FORMAT.test(format)) { + return monthShortFormat[dayjsInstance.month()] + } + return monthShortStandalone[dayjsInstance.month()] +} +monthsShort.s = monthShortStandalone +monthsShort.f = monthShortFormat + const locale = { name: 'be', - weekdays: 'нядзелю_панядзелак_аўторак_сераду_чацвер_пятніцу_суботу'.split('_'), - months: 'студзеня_лютага_сакавіка_красавіка_траўня_чэрвеня_ліпеня_жніўня_верасня_кастрычніка_лістапада_снежня'.split('_'), + weekdays: 'нядзеля_панядзелак_аўторак_серада_чацвер_пятніца_субота'.split('_'), + weekdaysShort: 'няд_пнд_аўт_сер_чцв_пят_суб'.split('_'), + weekdaysMin: 'нд_пн_аў_ср_чц_пт_сб'.split('_'), + months, + monthsShort, weekStart: 1, - weekdaysShort: 'нд_пн_ат_ср_чц_пт_сб'.split('_'), - monthsShort: 'студ_лют_сак_крас_трав_чэрв_ліп_жнів_вер_каст_ліст_снеж'.split('_'), - weekdaysMin: 'нд_пн_ат_ср_чц_пт_сб'.split('_'), - ordinal: n => n, + yearStart: 4, formats: { LT: 'HH:mm', LTS: 'HH:mm:ss', @@ -17,10 +65,35 @@ const locale = { LL: 'D MMMM YYYY г.', LLL: 'D MMMM YYYY г., HH:mm', LLLL: 'dddd, D MMMM YYYY г., HH:mm' + }, + relativeTime: { + future: 'праз %s', + past: '%s таму', + s: 'некалькі секунд', + m: relativeTimeWithPlural, + mm: relativeTimeWithPlural, + h: relativeTimeWithPlural, + hh: relativeTimeWithPlural, + d: 'дзень', + dd: relativeTimeWithPlural, + M: 'месяц', + MM: relativeTimeWithPlural, + y: 'год', + yy: relativeTimeWithPlural + }, + ordinal: n => n, + meridiem: (hour) => { + if (hour < 4) { + return 'ночы' + } else if (hour < 12) { + return 'раніцы' + } else if (hour < 17) { + return 'дня' + } + return 'вечара' } } dayjs.locale(locale, null, true) export default locale - diff --git a/src/locale/it.js b/src/locale/it.js index 08f660bef..b4bceb00d 100644 --- a/src/locale/it.js +++ b/src/locale/it.js @@ -23,7 +23,7 @@ const locale = { s: 'qualche secondo', m: 'un minuto', mm: '%d minuti', - h: 'un\' ora', + h: 'un\'ora', hh: '%d ore', d: 'un giorno', dd: '%d giorni', diff --git a/src/plugin/devHelper/index.js b/src/plugin/devHelper/index.js index bdb83caf7..2f163a25f 100644 --- a/src/plugin/devHelper/index.js +++ b/src/plugin/devHelper/index.js @@ -26,6 +26,15 @@ export default (o, c, d) => { } return oldLocale(preset, object, isLocal) } + + const oldDiff = proto.diff + proto.diff = function (date, unit, float) { + const isInvalidDate = !date || !d(date).isValid() + if (isInvalidDate) { + console.warn('Invalid usage: diff() requires a valid comparison date as the first argument. https://day.js.org/docs/en/display/difference') + } + + return oldDiff.call(this, date, unit, float) + } } } - diff --git a/test/locale/be.test.js b/test/locale/be.test.js new file mode 100644 index 000000000..0c39f224b --- /dev/null +++ b/test/locale/be.test.js @@ -0,0 +1,142 @@ +import MockDate from 'mockdate' +import moment from 'moment' +import dayjs from '../../src' +import '../../src/locale/be' +import relativeTime from '../../src/plugin/relativeTime' + +dayjs.extend(relativeTime) + +beforeEach(() => { + MockDate.set(new Date()) +}) + +afterEach(() => { + MockDate.reset() +}) + +it('Belarusian locale relative time in past and future with suffix', () => { + const cases = [ + [1, 's', 'праз некалькі секунд'], + [-1, 's', 'некалькі секунд таму'], + [1, 'm', 'праз хвіліну'], + [-1, 'm', 'хвіліну таму'], + [1, 'h', 'праз гадзіну'], + [-1, 'h', 'гадзіну таму'], + [1, 'd', 'праз дзень'], + [-1, 'd', 'дзень таму'], + [1, 'M', 'праз месяц'], + [-1, 'M', 'месяц таму'], + [2, 'd', 'праз 2 дні'], + [-2, 'd', '2 дні таму'], + [10, 'd', 'праз 10 дзён'], + [-10, 'd', '10 дзён таму'], + [6, 'm', 'праз 6 хвілін'], + [-6, 'm', '6 хвілін таму'], + [5, 'h', 'праз 5 гадзін'], + [-5, 'h', '5 гадзін таму'], + [3, 'M', 'праз 3 месяцы'], + [-3, 'M', '3 месяцы таму'], + [4, 'y', 'праз 4 гады'], + [-4, 'y', '4 гады таму'] + ] + + const locales = ['be'] + locales.forEach((locale) => { + cases.forEach((c) => { + expect(dayjs() + .add(c[0], c[1]) + .locale(locale) + .fromNow()).toBe(c[2]) + expect(dayjs() + .add(c[0], c[1]) + .locale(locale) + .fromNow()).toBe(moment() + .add(c[0], c[1]) + .locale(locale) + .fromNow()) + }) + }) +}) + +it('Belarusian locale relative time in past and future without suffix', () => { + const cases = [ + [1, 's', 'некалькі секунд'], + [-1, 's', 'некалькі секунд'], + + [1, 'm', 'хвіліна'], + [-1, 'm', 'хвіліна'], + [1, 'h', 'гадзіна'], + [-1, 'h', 'гадзіна'], + + // Test all plural forms for days + [1, 'd', 'дзень'], + [21, 'd', '21 дзень'], + [31, 'd', 'месяц'], + // 2-4 form + [2, 'd', '2 дні'], + [3, 'd', '3 дні'], + [4, 'd', '4 дні'], + // 5-20 and other cases + [5, 'd', '5 дзён'], + [6, 'd', '6 дзён'], + // 11-14 special case + [11, 'd', '11 дзён'], + [12, 'd', '12 дзён'], + [13, 'd', '13 дзён'], + [14, 'd', '14 дзён'], + // 22-24 + [22, 'd', '22 дні'], + [23, 'd', '23 дні'], + [24, 'd', '24 дні'], + + // Test all plural forms for months + [1, 'M', 'месяц'], + [2, 'M', '2 месяцы'], + [5, 'M', '5 месяцаў'], + + // Test all plural forms for years + [1, 'y', 'год'], + [2, 'y', '2 гады'], + [5, 'y', '5 гадоў'], + [11, 'y', '11 гадоў'], + [21, 'y', '21 год'] + ] + + const locales = ['be'] + locales.forEach((locale) => { + cases.forEach((c) => { + expect(dayjs() + .add(c[0], c[1]) + .locale(locale) + .fromNow(true)).toBe(c[2]) + expect(dayjs() + .add(c[0], c[1]) + .locale(locale) + .fromNow(true)).toBe(moment() + .add(c[0], c[1]) + .locale(locale) + .fromNow(true)) + }) + }) +}) + +it('Belarusian locale formats dates with correct month forms', () => { + const tests = [ + // Full month names + { date: '2022-01-19', format: 'dd, D MMMM YYYY г.', expected: 'ср, 19 студзеня 2022 г.' }, + { date: '2022-01-01', format: 'MMMM', expected: 'студзень' }, + + // Short month names in format form (with day) + { date: '2022-01-15', format: 'D MMM', expected: '15 студ' }, + { date: '2022-02-15', format: 'D MMM', expected: '15 лют' }, + + // Short month names in standalone form + { date: '2022-01-01', format: 'MMM', expected: 'студ' }, + { date: '2022-02-01', format: 'MMM', expected: 'лют' } + ] + + tests.forEach(({ date, format, expected }) => { + const dayjsWithLocale = dayjs(date).locale('be') + expect(dayjsWithLocale.format(format)).toEqual(expected) + }) +}) diff --git a/test/locale/it.test.js b/test/locale/it.test.js new file mode 100644 index 000000000..56043b1de --- /dev/null +++ b/test/locale/it.test.js @@ -0,0 +1,84 @@ +import moment from 'moment' +import MockDate from 'mockdate' +import dayjs from '../../src' +import '../../src/locale/it' +import relativeTime from '../../src/plugin/relativeTime' +import localizedFormat from '../../src/plugin/localizedFormat' + +dayjs.extend(relativeTime) +dayjs.extend(localizedFormat) + +describe('Italian formats', () => { + beforeEach(() => { + dayjs.locale('it') + moment.locale('it') + + MockDate.set(new Date()) + }) + + afterEach(() => { + MockDate.reset() + }) + + + it('Format month with locale function', () => { + for (let i = 0; i <= 7; i += 1) { + const dayjsWithLocale = dayjs().add(i, 'day') + const momentWithLocale = moment().add(i, 'day') + const testFormat1 = 'DD MMMM YYYY MMM' + const testFormat2 = 'dddd, MMMM D YYYY' + const testFormat3 = 'MMMM' + const testFormat4 = 'MMM' + const testFormat5 = 'L' + expect(dayjsWithLocale.format(testFormat1)).toEqual(momentWithLocale.format(testFormat1)) + expect(dayjsWithLocale.format(testFormat2)).toEqual(momentWithLocale.format(testFormat2)) + expect(dayjsWithLocale.format(testFormat3)).toEqual(momentWithLocale.format(testFormat3)) + expect(dayjsWithLocale.format(testFormat4)).toEqual(momentWithLocale.format(testFormat4)) + expect(dayjsWithLocale.format(testFormat5)).toEqual(momentWithLocale.format(testFormat5)) + } + }) + + it('RelativeTime: Time from X', () => { + const T = [ + [89.5, 'second'], // a minute + [2, 'minute'], // 2 minutes + [5, 'minute'], // 5 minutes + [43, 'minute'], // 44 minutes + [45, 'minute'], // an hour + [3, 'hour'], // 3 hours + [21, 'hour'], // 21 hours + [1, 'day'], // a day + [3, 'day'], // 3 day + [25, 'day'], // 25 days + [1, 'month'], // a month + [2, 'month'], // 2 month + [10, 'month'], // 10 month + [1, 'year'], // a year + [2, 'year'], // 2 year + [5, 'year'], // 5 year + [18, 'month'] // 2 years + ] + + + T.forEach((t) => { + 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)) + }) + }) + + // moment.js uses `alcuni secondi` while dayjs uses `qualche secondo`. + it('RelativeTime: A few seconds', () => { + const T = [ + [44.4, 'second'] // a few seconds + ] + + T.forEach((t) => { + expect(dayjs().from(dayjs().add(t[0], t[1]))) + .toBe('qualche secondo fa') + expect(dayjs().from(dayjs().add(t[0], t[1]), true)) + .toBe('qualche secondo') + }) + }) +}) diff --git a/test/plugin/devHelper.test.js b/test/plugin/devHelper.test.js index c69190a14..ff63d434d 100644 --- a/test/plugin/devHelper.test.js +++ b/test/plugin/devHelper.test.js @@ -1,6 +1,8 @@ import MockDate from 'mockdate' import dayjs from '../../src' +import customParseFormat from '../../src/plugin/customParseFormat' import devHelper from '../../src/plugin/devHelper' +import localeData from '../../src/plugin/localeData' dayjs.extend(devHelper) @@ -38,3 +40,46 @@ it('Warning: Setting locale before loading locale', () => { dayjs.locale('zh-cn') expect(consoleSpy).toHaveBeenCalledWith('Guessing you may want to use locale zh-cn, you have to load it before using it. https://day.js.org/docs/en/i18n/loading-into-nodejs') }) + +describe('dev-helper: diff() usage warnings', () => { + const diffWarningMsg = 'Invalid usage: diff() requires a valid comparison date as the first argument. https://day.js.org/docs/en/display/difference' + + beforeAll(() => { + dayjs.extend(customParseFormat) + dayjs.extend(localeData) + }) + + beforeEach(() => { + jest.clearAllMocks() + }) + + it('warns when diff() is called with no comparison date', () => { + const consoleSpy = jest.spyOn(console, 'warn') + dayjs('2025-01-10').diff() + expect(consoleSpy).toHaveBeenCalledWith(diffWarningMsg) + }) + + it('warns when diff() is called with just the unit', () => { + const consoleSpy = jest.spyOn(console, 'warn') + dayjs('2025-01-10').diff('days') + expect(consoleSpy).toHaveBeenCalledWith(diffWarningMsg) + }) + + it('warns when diff() is called with an invalid comparison date (unparsable string)', () => { + const consoleSpy = jest.spyOn(console, 'warn') + dayjs('2025-01-10').diff('invalid-date', 'days') + expect(consoleSpy).toHaveBeenCalledWith(diffWarningMsg) + }) + + it('does NOT warn when diff() is called with a valid string date', () => { + const consoleSpy = jest.spyOn(console, 'warn') + dayjs('2025-01-10').diff('2025-01-09', 'days') + expect(consoleSpy).not.toHaveBeenCalledWith(diffWarningMsg) + }) + + it('does NOT warn when diff() is called with a valid Day.js instance', () => { + const consoleSpy = jest.spyOn(console, 'warn') + dayjs('2025-01-10').diff(dayjs(), 'days') + expect(consoleSpy).not.toHaveBeenCalledWith(diffWarningMsg) + }) +})