diff --git a/.eslintrc.json b/.eslintrc.json
index 97a4645f..2fa59438 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -38,7 +38,7 @@
],
"import/extensions": [
2,
- "ignorePackages"
+ "never"
]
}
-}
\ No newline at end of file
+}
diff --git a/.gitignore b/.gitignore
index 7894e392..951f5593 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@
# IDE
.idea
+.vscode
# npm
node_modules
diff --git a/.travis.yml b/.travis.yml
index 5f28442a..91412f24 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,6 +4,7 @@ cache:
- ~/.npm
- node_modules
node_js:
+ - '12'
- '10'
- '8'
- '6'
diff --git a/README.md b/README.md
index c1394b9c..3443199c 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-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)
+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)
+Быстрая 2kB альтернатива Moment.js с тем же современным API
+
+
+
+
+
+
+
+
+
+
+
+
+
+> Day.js - это миниатюрная JavaScript библиотека, которая анализирует, валидирует, манипулирует, и отображает даты и время для современных браузеров, также обладает внушительной совместимостью с Moment.js API. Если вы используете Moment.js, тогда вы уже знаете как пользоваться Day.js.
+
+```js
+dayjs().startOf('month').add(1, 'day').set('year', 2018).format('YYYY-MM-DD HH:mm:ss');
+```
+
+* 🕒 Хорошо знакомые API и паттерны (шаблоны) Moment.js
+* 💪 Неизменная
+* 🔥 Цепная
+* 🌐 Поддержка интернационализации (I18n)
+* 📦 2kb мини-библиотека
+* 👫 Поддержка всеми браузерами
+
+---
+
+## Начало работы
+
+### Документация
+
+Вы можете найти больше детальной информации, API, и других документов на веб-сайте [day.js.org](https://day.js.org/).
+
+### Установка
+
+```console
+npm install dayjs --save
+```
+
+📚[Инструкция по установке](https://day.js.org/docs/en/installation/installation)
+
+### API
+
+API Day.js легко использовать для анализа, проверки, воздействия, и отображения дат и времени.
+
+```javascript
+dayjs('2018-08-08') // parse
+
+dayjs().format('{YYYY} MM-DDTHH:mm:ss SSS [Z] A') // отображение
+
+dayjs().set('month', 3).month() // получение значения и его установка
+
+dayjs().add(1, 'year') // влияние
+
+dayjs().isBefore(dayjs()) // осведомление
+```
+
+📚[Ссылка на API](https://day.js.org/docs/en/parse/parse)
+
+### I18n
+
+Day.js обладает великолепной поддержкой интернационализации.
+
+Но ни одна из локализаций не будет включена в вашу сборку до тех пор, пока вы не начнете ее использовать.
+```javascript
+import 'dayjs/locale/es' // загрузка по требованию
+
+dayjs.locale('es') // глобальное использование Испанской локали
+
+dayjs('2018-05-05').locale('zh-cn').format() // использование упрощенной Китайской локали в конкретном случае
+```
+📚[Интернационализация](https://day.js.org/docs/en/i18n/i18n)
+
+### Плагин
+
+Плагин - это независимый модуль, который может быть добавлен в Day.js с целью расширения функциональных возможностей или добавления новых особенностей.
+
+```javascript
+import advancedFormat from 'dayjs/plugin/advancedFormat' // загрузка по требованию
+
+dayjs.extend(advancedFormat) // использование плагина
+
+dayjs().format('Q Do k kk X x') // больше доступных форматов
+```
+
+📚[Список плагинов](https://day.js.org/docs/en/plugin/plugin)
+
+## Спонсоры
+
+Поддержите этот проект, став спонсором. Ваш логотип будет показан здесь с ссылкой на ваш веб-сайт. [[Стать спонсором](https://opencollective.com/dayjs#sponsor)]
+
+
+
+
+
+
+## Контрибьюторы
+
+Этот проект существует благодаря всем людям, кто вносит свой вклад в его развитие.
+
+Пожалуйста поставьте 💖 звездочку 💖, чтобы поддержать нас. Спасибо.
+
+Также выражаю благодарность всем нашим спонсорам! 🙏
+
+
+
+
+
+
+
+## Лицензия
+
+Day.js распростроняется под [лицензией MIT](./LICENSE).
diff --git a/src/locale/br.js b/src/locale/br.js
index 57d7d4f7..bbd242c7 100644
--- a/src/locale/br.js
+++ b/src/locale/br.js
@@ -3,11 +3,11 @@ import dayjs from 'dayjs'
const locale = {
name: 'br',
- weekdays: "Sul_Lun_Meurzh_Merc'her_Yaou_Gwener_Sadorn".split('_'),
- months: "Genver_C'hwevrer_Meurzh_Ebrel_Mae_Mezheven_Gouere_Eost_Gwengolo_Here_Du_Kerzu".split('_'),
+ weekdays: 'Sul_Lun_Meurzh_Mercʼher_Yaou_Gwener_Sadorn'.split('_'),
+ months: 'Genver_Cʼhwevrer_Meurzh_Ebrel_Mae_Mezheven_Gouere_Eost_Gwengolo_Here_Du_Kerzu'.split('_'),
weekStart: 1,
weekdaysShort: 'Sul_Lun_Meu_Mer_Yao_Gwe_Sad'.split('_'),
- monthsShort: "Gen_C'hwe_Meu_Ebr_Mae_Eve_Gou_Eos_Gwe_Her_Du_Ker".split('_'),
+ monthsShort: 'Gen_Cʼhwe_Meu_Ebr_Mae_Eve_Gou_Eos_Gwe_Her_Du_Ker'.split('_'),
weekdaysMin: 'Su_Lu_Me_Mer_Ya_Gw_Sa'.split('_'),
ordinal: n => n,
formats: {
@@ -17,10 +17,10 @@ const locale = {
LL: 'D [a viz] MMMM YYYY',
LLL: 'D [a viz] MMMM YYYY h[e]mm A',
LLLL: 'dddd, D [a viz] MMMM YYYY h[e]mm A'
- }
+ },
+ meridiem: hour => (hour < 12 ? 'a.m.' : 'g.m.') // a-raok merenn | goude merenn
}
dayjs.locale(locale, null, true)
export default locale
-
diff --git a/src/locale/en-in.js b/src/locale/en-in.js
new file mode 100644
index 00000000..9913857a
--- /dev/null
+++ b/src/locale/en-in.js
@@ -0,0 +1,46 @@
+// English (India) [en-in]
+import dayjs from 'dayjs'
+
+const locale = {
+ name: 'en-in',
+ weekdays: 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'),
+ weekdaysShort: 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'),
+ weekdaysMin: 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'),
+ months: 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'),
+ monthsShort: 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'),
+ weekStart: 1,
+ yearStart: 4,
+ 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'
+ },
+ formats: {
+ LT: 'HH:mm',
+ LTS: 'HH:mm:ss',
+ L: 'DD/MM/YYYY',
+ LL: 'D MMMM YYYY',
+ LLL: 'D MMMM YYYY HH:mm',
+ LLLL: 'dddd, D MMMM YYYY HH:mm'
+ },
+ ordinal: (n) => {
+ const s = ['th', 'st', 'nd', 'rd']
+ const v = n % 100
+ return `[${n}${(s[(v - 20) % 10] || s[v] || s[0])}]`
+ }
+}
+
+dayjs.locale(locale, null, true)
+
+export default locale
+
diff --git a/src/locale/en-tt.js b/src/locale/en-tt.js
new file mode 100644
index 00000000..99212232
--- /dev/null
+++ b/src/locale/en-tt.js
@@ -0,0 +1,46 @@
+// English (Trinidad & Tobago) [en-tt]
+import dayjs from 'dayjs'
+
+const locale = {
+ name: 'en-tt',
+ weekdays: 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'),
+ weekdaysShort: 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'),
+ weekdaysMin: 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'),
+ months: 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'),
+ monthsShort: 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'),
+ weekStart: 1,
+ yearStart: 4,
+ 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'
+ },
+ formats: {
+ LT: 'HH:mm',
+ LTS: 'HH:mm:ss',
+ L: 'DD/MM/YYYY',
+ LL: 'D MMMM YYYY',
+ LLL: 'D MMMM YYYY HH:mm',
+ LLLL: 'dddd, D MMMM YYYY HH:mm'
+ },
+ ordinal: (n) => {
+ const s = ['th', 'st', 'nd', 'rd']
+ const v = n % 100
+ return `[${n}${(s[(v - 20) % 10] || s[v] || s[0])}]`
+ }
+}
+
+dayjs.locale(locale, null, true)
+
+export default locale
+
diff --git a/src/locale/fr.js b/src/locale/fr.js
index 72290622..cefee134 100644
--- a/src/locale/fr.js
+++ b/src/locale/fr.js
@@ -7,7 +7,7 @@ const locale = {
weekdaysShort: 'dim._lun._mar._mer._jeu._ven._sam.'.split('_'),
weekdaysMin: 'di_lu_ma_me_je_ve_sa'.split('_'),
months: 'janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre'.split('_'),
- monthsShort: 'janv_févr_mars_avril_mai_juin_juil_août_sept_oct_nov_déc'.split('_'),
+ monthsShort: 'janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.'.split('_'),
weekStart: 1,
formats: {
LT: 'HH:mm',
diff --git a/src/locale/uk.js b/src/locale/uk.js
index 4cfa84d8..27bfacf1 100644
--- a/src/locale/uk.js
+++ b/src/locale/uk.js
@@ -16,6 +16,8 @@ function relativeTimeWithPlural(number, withoutSuffix, key) {
}
if (key === 'm') {
return withoutSuffix ? 'хвилина' : 'хвилину'
+ } else if (key === 'h') {
+ return withoutSuffix ? 'година' : 'годину'
}
return `${number} ${plural(format[key], +number)}`
@@ -35,7 +37,7 @@ const locale = {
s: 'декілька секунд',
m: relativeTimeWithPlural,
mm: relativeTimeWithPlural,
- h: 'годину',
+ h: relativeTimeWithPlural,
hh: relativeTimeWithPlural,
d: 'день',
dd: relativeTimeWithPlural,
diff --git a/src/plugin/calendar/index.js b/src/plugin/calendar/index.js
index e62e1fa3..d8a8b516 100644
--- a/src/plugin/calendar/index.js
+++ b/src/plugin/calendar/index.js
@@ -23,7 +23,11 @@ export default (o, c, d) => {
diff < 2 ? 'nextDay' :
diff < 7 ? 'nextWeek' : sameElse
/* eslint-enable no-nested-ternary */
- return this.format(format[retVal] || calendarFormat[retVal])
+ const currentFormat = format[retVal] || calendarFormat[retVal]
+ if (typeof currentFormat === 'function') {
+ return currentFormat.call(this, d())
+ }
+ return this.format(currentFormat)
}
}
diff --git a/src/plugin/duration/index.js b/src/plugin/duration/index.js
new file mode 100644
index 00000000..835456b2
--- /dev/null
+++ b/src/plugin/duration/index.js
@@ -0,0 +1,174 @@
+import { MILLISECONDS_A_WEEK, MILLISECONDS_A_DAY, MILLISECONDS_A_HOUR, MILLISECONDS_A_MINUTE, MILLISECONDS_A_SECOND } from '../../constant'
+
+const MILLISECONDS_A_YEAR = MILLISECONDS_A_DAY * 365
+const MILLISECONDS_A_MONTH = MILLISECONDS_A_DAY * 30
+
+const durationRegex = /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/
+
+const unitToMS = {
+ years: MILLISECONDS_A_YEAR,
+ months: MILLISECONDS_A_MONTH,
+ days: MILLISECONDS_A_DAY,
+ hours: MILLISECONDS_A_HOUR,
+ minutes: MILLISECONDS_A_MINUTE,
+ seconds: MILLISECONDS_A_SECOND,
+ weeks: MILLISECONDS_A_WEEK
+}
+
+const isDuration = d => (d instanceof Duration) // eslint-disable-line no-use-before-define
+
+let $d
+let $u
+
+const wrapper = (input, instance, unit) =>
+ new Duration(input, unit, instance.$l) // eslint-disable-line no-use-before-define
+
+const prettyUnit = unit => `${$u.p(unit)}s`
+
+class Duration {
+ constructor(input, unit, locale) {
+ this.$d = {}
+ this.$l = locale || 'en'
+ if (unit) {
+ return wrapper(input * unitToMS[prettyUnit(unit)], this)
+ }
+ if (typeof input === 'number') {
+ this.$ms = input
+ this.parseFromMilliseconds()
+ return this
+ }
+ if (typeof input === 'object') {
+ Object.keys(input).forEach((k) => {
+ this.$d[prettyUnit(k)] = input[k]
+ })
+ this.calMilliseconds()
+ return this
+ }
+ if (typeof input === 'string') {
+ const d = input.match(durationRegex)
+ if (d) {
+ [,,
+ this.$d.years, this.$d.months,,
+ this.$d.days, this.$d.hours, this.$d.minutes, this.$d.seconds] = d
+ this.calMilliseconds()
+ return this
+ }
+ }
+ return this
+ }
+
+ calMilliseconds() {
+ this.$ms = Object.keys(this.$d).reduce((total, unit) => (
+ total + ((this.$d[unit] || 0) * (unitToMS[unit] || 1))
+ ), 0)
+ }
+
+ parseFromMilliseconds() {
+ let { $ms } = this
+ this.$d.years = Math.floor($ms / MILLISECONDS_A_YEAR)
+ $ms %= MILLISECONDS_A_YEAR
+ this.$d.months = Math.floor($ms / MILLISECONDS_A_MONTH)
+ $ms %= MILLISECONDS_A_MONTH
+ this.$d.days = Math.floor($ms / MILLISECONDS_A_DAY)
+ $ms %= MILLISECONDS_A_DAY
+ this.$d.hours = Math.floor($ms / MILLISECONDS_A_HOUR)
+ $ms %= MILLISECONDS_A_HOUR
+ this.$d.minutes = Math.floor($ms / MILLISECONDS_A_MINUTE)
+ $ms %= MILLISECONDS_A_MINUTE
+ this.$d.seconds = $ms / MILLISECONDS_A_SECOND
+ }
+
+ toISOString() {
+ const Y = this.$d.years ? `${this.$d.years}Y` : ''
+ const M = this.$d.months ? `${this.$d.months}M` : ''
+ let days = this.$d.days || 0
+ if (this.$d.weeks) {
+ days += this.$d.weeks * 7
+ }
+ const D = days ? `${days}D` : ''
+ const H = this.$d.hours ? `${this.$d.hours}H` : ''
+ const m = this.$d.minutes ? `${this.$d.minutes}M` : ''
+ let seconds = this.$d.seconds || 0
+ if (this.$d.milliseconds) {
+ seconds += this.$d.milliseconds / 1000
+ }
+ const S = seconds ? `${seconds}S` : ''
+ const T = (H || M || S) ? 'T' : ''
+ const result = `P${Y}${M}${D}${T}${H}${m}${S}`
+ return result === 'P' ? 'P0D' : result
+ }
+
+ toJSON() {
+ return this.toISOString()
+ }
+
+ as(unit) {
+ return this.$ms / (unitToMS[prettyUnit(unit)] || 1)
+ }
+
+ get(unit) {
+ let base = this.$ms
+ const pUnit = prettyUnit(unit)
+ if (pUnit === 'milliseconds') {
+ base %= 1000
+ } else {
+ base = Math.floor(base / unitToMS[pUnit])
+ }
+ return base
+ }
+
+ add(input, unit, isSubtract) {
+ let another
+ if (unit) {
+ another = input * unitToMS[prettyUnit(unit)]
+ } else if (isDuration(input)) {
+ another = input.$ms
+ } else {
+ another = wrapper(input, this).$ms
+ }
+ return wrapper(this.$ms + (another * (isSubtract ? -1 : 1)), this)
+ }
+
+ subtract(input, unit) {
+ return this.add(input, unit, true)
+ }
+
+ locale(l) {
+ const that = this.clone()
+ that.$l = l
+ return that
+ }
+
+ clone() {
+ return wrapper(this.$ms, this)
+ }
+
+ humanize(withSuffix) {
+ return $d().add(this.$ms, 'ms').locale(this.$l).fromNow(!withSuffix)
+ }
+
+ milliseconds() { return this.get('milliseconds') }
+ asMilliseconds() { return this.as('milliseconds') }
+ seconds() { return this.get('seconds') }
+ asSeconds() { return this.as('seconds') }
+ minutes() { return this.get('minutes') }
+ asMinutes() { return this.as('minutes') }
+ hours() { return this.get('hours') }
+ asHours() { return this.as('hours') }
+ days() { return this.get('days') }
+ asDays() { return this.as('days') }
+ weeks() { return this.get('weeks') }
+ asWeeks() { return this.as('weeks') }
+ months() { return this.get('months') }
+ asMonths() { return this.as('months') }
+ years() { return this.get('years') }
+ asYears() { return this.as('years') }
+}
+export default (option, Dayjs, dayjs) => {
+ $d = dayjs
+ $u = dayjs().$utils()
+ dayjs.duration = function (input, unit) {
+ return wrapper(input, {}, unit)
+ }
+ dayjs.isDuration = isDuration
+}
diff --git a/src/plugin/isToday/index.js b/src/plugin/isToday/index.js
new file mode 100644
index 00000000..fe97a8b6
--- /dev/null
+++ b/src/plugin/isToday/index.js
@@ -0,0 +1,9 @@
+export default (o, c, d) => {
+ const proto = c.prototype
+ proto.isToday = function () {
+ const comparisonTemplate = 'YYYY-MM-DD'
+ const now = d()
+
+ return this.format(comparisonTemplate) === now.format(comparisonTemplate)
+ }
+}
diff --git a/src/plugin/isTomorrow/index.js b/src/plugin/isTomorrow/index.js
new file mode 100644
index 00000000..f117766d
--- /dev/null
+++ b/src/plugin/isTomorrow/index.js
@@ -0,0 +1,11 @@
+export default (o, c, d) => {
+ const proto = c.prototype
+ proto.isTomorrow = function () {
+ const comparisonTemplate = 'YYYY-MM-DD'
+ const tomorrow = d().add(1, 'day')
+
+ return (
+ this.format(comparisonTemplate) === tomorrow.format(comparisonTemplate)
+ )
+ }
+}
diff --git a/src/plugin/isYesterday/index.js b/src/plugin/isYesterday/index.js
new file mode 100644
index 00000000..aac904b2
--- /dev/null
+++ b/src/plugin/isYesterday/index.js
@@ -0,0 +1,11 @@
+export default (o, c, d) => {
+ const proto = c.prototype
+ proto.isYesterday = function () {
+ const comparisonTemplate = 'YYYY-MM-DD'
+ const yesterday = d().subtract(1, 'day')
+
+ return (
+ this.format(comparisonTemplate) === yesterday.format(comparisonTemplate)
+ )
+ }
+}
diff --git a/src/plugin/relativeTime/index.js b/src/plugin/relativeTime/index.js
index 2a68737f..10a8f5de 100644
--- a/src/plugin/relativeTime/index.js
+++ b/src/plugin/relativeTime/index.js
@@ -1,6 +1,7 @@
import * as C from '../../constant'
export default (o, c, d) => {
+ o = o || {}
const proto = c.prototype
d.en.relativeTime = {
future: 'in %s',
@@ -19,7 +20,7 @@ export default (o, c, d) => {
}
const fromTo = (input, withoutSuffix, instance, isFrom) => {
const loc = instance.$locale().relativeTime
- const T = [
+ const T = o.thresholds || [
{ l: 's', r: 44, d: C.S },
{ l: 'm', r: 89 },
{ l: 'mm', r: 44, d: C.MIN },
@@ -44,10 +45,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 = (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
+ 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/test/locale/en.test.js b/test/locale/en.test.js
new file mode 100644
index 00000000..c7b3de1b
--- /dev/null
+++ b/test/locale/en.test.js
@@ -0,0 +1,24 @@
+import dayjs from '../../src'
+import '../../src/locale/en'
+import '../../src/locale/en-gb'
+import '../../src/locale/en-in'
+import '../../src/locale/en-tt'
+import localizedFormat from '../../src/plugin/localizedFormat'
+
+dayjs.extend(localizedFormat)
+
+const locales = [
+ { locale: 'en', expectedDate: '12/25/2019' },
+ { locale: 'en-gb', expectedDate: '25/12/2019' },
+ { locale: 'en-in', expectedDate: '25/12/2019' },
+ { locale: 'en-tt', expectedDate: '25/12/2019' }
+]
+
+describe('English date formats', () => {
+ locales.forEach((locale) => {
+ it(`should correctly format date with locale - ${locale.locale}`, () => {
+ const dayjsWithLocale = dayjs('2019-12-25').locale(locale.locale)
+ expect(dayjsWithLocale.format('L')).toEqual(locale.expectedDate)
+ })
+ })
+})
diff --git a/test/locale/uk.test.js b/test/locale/uk.test.js
index bb734cde..e1a88be2 100644
--- a/test/locale/uk.test.js
+++ b/test/locale/uk.test.js
@@ -34,3 +34,13 @@ it('RelativeTime: Time from X', () => {
.toBe(moment().from(moment().add(t[0], t[1]), true))
})
})
+
+it('hour', () => {
+ const str0 = '2020-03-18 19:15:00'
+ const str = '2020-03-18 20:15:00'
+ const result = dayjs(str0).locale('uk').to(str)
+
+ expect(result).toEqual(moment(str0).locale('uk').to(str))
+ const result2 = dayjs(str).locale('uk').to(str0, true)
+ expect(result2).toEqual('година') // different from moment.js
+})
diff --git a/test/locale/zh.test.js b/test/locale/zh.test.js
index f018eda1..017582a2 100644
--- a/test/locale/zh.test.js
+++ b/test/locale/zh.test.js
@@ -1,8 +1,8 @@
import dayjs from '../../src'
import advancedFormat from '../../src/plugin/advancedFormat'
import weekOfYear from '../../src/plugin/weekOfYear'
-import '../../src/locale/zh.js'
-import '../../src/locale/zh-cn.js'
+import '../../src/locale/zh'
+import '../../src/locale/zh-cn'
dayjs.extend(advancedFormat).extend(weekOfYear)
diff --git a/test/plugin/calendar.test.js b/test/plugin/calendar.test.js
index 31391ad0..1bdff8c7 100644
--- a/test/plugin/calendar.test.js
+++ b/test/plugin/calendar.test.js
@@ -81,6 +81,51 @@ it('Custom format', () => {
.toEqual(moment(now).calendar(nextDayWithoutFormat, format))
})
+it('Custom callback', () => {
+ const callbacks = {
+ sameDay: jest.fn(),
+ sameElse: jest.fn()
+ }
+ const now = '2015-01-15T14:21:22.000Z'
+ const nextDayWithoutFormat = '2015-01-14T11:23:55.000Z'
+ expect(dayjs(now).calendar(nextDayWithoutFormat, callbacks))
+ .toEqual(moment(now).calendar(nextDayWithoutFormat, callbacks))
+})
+
+it('Calls callback', () => {
+ const callbacks = {
+ sameDay: jest.fn(),
+ sameElse: jest.fn()
+ }
+ dayjs().calendar(null, callbacks)
+ expect(callbacks.sameElse).not.toBeCalled()
+ expect(callbacks.sameDay).toBeCalled()
+})
+
+it('callback is a function with the scope of the current moment', () => {
+ const callbacks = {
+ sameDay: jest.fn()
+ }
+ expect(dayjs().calendar(null, callbacks)).toEqual(callbacks.sameDay())
+ const callbacks2 = {
+ sameDay: function cb() {
+ return this
+ }
+ }
+ const result = dayjs().calendar(null, callbacks2)
+ expect(result.format).not.toBeUndefined()
+ expect(dayjs.isDayjs(result)).toBeTruthy()
+})
+
+it('callback is a function and first argument a moment that depicts now', () => {
+ const callbacks = {
+ sameDay: jest.fn()
+ }
+ const now = dayjs()
+ dayjs(now).calendar(now, callbacks)
+ expect(callbacks.sameDay).toBeCalledWith(now)
+})
+
it('set global calendar in locale file', () => {
const now = '2019-04-03T14:21:22.000Z'
zhCn.calendar = {
diff --git a/test/plugin/duration.test.js b/test/plugin/duration.test.js
new file mode 100644
index 00000000..41be206d
--- /dev/null
+++ b/test/plugin/duration.test.js
@@ -0,0 +1,181 @@
+import MockDate from 'mockdate'
+import dayjs from '../../src'
+import duration from '../../src/plugin/duration'
+import relativeTime from '../../src/plugin/relativeTime'
+import '../../src/locale/fr'
+import '../../src/locale/es'
+
+dayjs.extend(relativeTime)
+dayjs.extend(duration)
+
+beforeEach(() => {
+ MockDate.set(new Date())
+})
+
+afterEach(() => {
+ MockDate.reset()
+})
+
+describe('Creating', () => {
+ it('milliseconds', () => {
+ expect(dayjs.duration(100).toISOString()).toBe('PT0.1S')
+ expect(dayjs.duration(1000).toISOString()).toBe('PT1S')
+ })
+ it('two argument will bubble up to the next', () => {
+ expect(dayjs.duration(59, 'seconds').toISOString()).toBe('PT59S')
+ expect(dayjs.duration(60, 'seconds').toISOString()).toBe('P1M')
+ expect(dayjs.duration(13213, 'seconds').toISOString()).toBe('PT3H40M13S')
+ })
+ it('object with float', () => {
+ expect(dayjs.duration({
+ seconds: 1,
+ minutes: 2,
+ hours: 3,
+ days: 4,
+ months: 6,
+ years: 7
+ }).toISOString()).toBe('P7Y6M4DT3H2M1S')
+ })
+ it('object with weeks and float', () => {
+ expect(dayjs.duration({
+ seconds: 1.1,
+ minutes: 2,
+ hours: 3,
+ days: 4,
+ weeks: 5,
+ months: 6,
+ years: 7
+ }).toISOString()).toBe('P7Y6M39DT3H2M1.1S')
+ })
+ it('object with millisecond', () => {
+ expect(dayjs.duration({
+ ms: 1
+ }).toISOString()).toBe('PT0.001S')
+ })
+})
+
+
+describe('Parse ISO string', () => {
+ it('Full ISO string', () => {
+ expect(dayjs.duration('P7Y6M4DT3H2M1S').toISOString()).toBe('P7Y6M4DT3H2M1S')
+ })
+ it('Part ISO string', () => {
+ expect(dayjs.duration('PT2777H46M40S').toISOString()).toBe('PT2777H46M40S')
+ })
+ it('Invalid ISO string', () => {
+ expect(dayjs.duration('Invalid').toISOString()).toBe('P0D')
+ })
+})
+
+it('Is duration', () => {
+ expect(dayjs.isDuration(dayjs.duration())).toBe(true)
+ expect(dayjs.isDuration(dayjs.duration(1))).toBe(true)
+ expect(dayjs.isDuration(dayjs())).toBe(false)
+ expect(dayjs.isDuration({})).toBe(false)
+ expect(dayjs.isDuration()).toBe(false)
+})
+
+it('toJSON', () => {
+ expect(JSON.stringify({
+ postDuration: dayjs.duration(5, 'minutes')
+ })).toBe('{"postDuration":"P5M"}')
+})
+
+describe('Humanize', () => {
+ it('Humaniz', () => {
+ expect(dayjs.duration(1, 'minutes').humanize()).toBe('a minute')
+ expect(dayjs.duration(2, 'minutes').humanize()).toBe('2 minutes')
+ expect(dayjs.duration(24, 'hours').humanize()).toBe('a day')
+ expect(dayjs.duration(1, 'minutes').humanize(true)).toBe('in a minute')
+ expect(dayjs.duration(-1, 'minutes').humanize(true)).toBe('a minute ago')
+ })
+
+ it('Locale', () => {
+ expect(dayjs.duration(1, 'minutes').humanize(true)).toBe('in a minute')
+ expect(dayjs.duration(1, 'minutes').locale('fr').humanize(true)).toBe('dans une minute')
+ expect(dayjs.duration(1, 'minutes').locale('es').humanize(true)).toBe('en un minuto')
+ })
+})
+
+describe('Clone', () => {
+ it('Locale clone', () => {
+ const d = dayjs.duration(1, 'minutes').locale('fr')
+ const r = 'dans une minute'
+ expect(d.humanize(true)).toBe(r)
+ expect(d.clone().humanize(true)).toBe(r)
+ })
+})
+
+describe('Milliseconds', () => {
+ expect(dayjs.duration(500).milliseconds()).toBe(500)
+ expect(dayjs.duration(1500).milliseconds()).toBe(500)
+ expect(dayjs.duration(15000).milliseconds()).toBe(0)
+ expect(dayjs.duration(500).asMilliseconds()).toBe(500)
+ expect(dayjs.duration(1500).asMilliseconds()).toBe(1500)
+ expect(dayjs.duration(15000).asMilliseconds()).toBe(15000)
+})
+
+describe('Add', () => {
+ const a = dayjs.duration(1, 'days')
+ const b = dayjs.duration(2, 'days')
+ expect(a.add(b).days()).toBe(3)
+ expect(a.add(1, 'days').days()).toBe(2)
+ expect(a.add({ days: 5 }).days()).toBe(6)
+})
+
+describe('Subtract', () => {
+ const a = dayjs.duration(3, 'days')
+ const b = dayjs.duration(2, 'days')
+ expect(a.subtract(b).days()).toBe(1)
+})
+
+
+describe('Seconds', () => {
+ expect(dayjs.duration(500).seconds()).toBe(0)
+ expect(dayjs.duration(1500).seconds()).toBe(1)
+ expect(dayjs.duration(15000).seconds()).toBe(15)
+ expect(dayjs.duration(500).asSeconds()).toBe(0.5)
+ expect(dayjs.duration(1500).asSeconds()).toBe(1.5)
+ expect(dayjs.duration(15000).asSeconds()).toBe(15)
+})
+
+describe('Minutes', () => {
+ expect(dayjs.duration(100000).minutes()).toBe(1)
+ expect(dayjs.duration(100000).asMinutes().toFixed(2)).toBe('1.67')
+})
+
+describe('Hours', () => {
+ expect(dayjs.duration(10000000).hours()).toBe(2)
+ expect(dayjs.duration(10000000).asHours().toFixed(2)).toBe('2.78')
+})
+
+describe('Days', () => {
+ expect(dayjs.duration(100000000).days()).toBe(1)
+ expect(dayjs.duration(100000000).asDays().toFixed(2)).toBe('1.16')
+})
+
+describe('Weeks', () => {
+ expect(dayjs.duration(1000000000).weeks()).toBe(1)
+ expect(dayjs.duration(1000000000).asWeeks().toFixed(2)).toBe('1.65')
+})
+
+describe('Month', () => {
+ expect(dayjs.duration(10000000000).months()).toBe(3)
+ expect(dayjs.duration({ months: 3 }).asMonths()).toBe(3)
+})
+
+describe('Years', () => {
+ expect(dayjs.duration(100000000000).years()).toBe(3)
+ expect(dayjs.duration(100000000000).asYears().toFixed(2)).toBe('3.17')
+})
+
+describe('prettyUnit', () => {
+ const d = dayjs.duration(2, 's')
+ expect(d.toISOString()).toBe('PT2S')
+ expect(d.as('Second')).toBe(2)
+ expect(d.get('s')).toBe(2)
+ expect(dayjs.duration({
+ M: 12,
+ m: 12
+ }).toISOString()).toBe('P12MT12M')
+})
diff --git a/test/plugin/isToday.test.js b/test/plugin/isToday.test.js
new file mode 100644
index 00000000..0e57a707
--- /dev/null
+++ b/test/plugin/isToday.test.js
@@ -0,0 +1,18 @@
+import MockDate from 'mockdate'
+import dayjs from '../../src'
+import isToday from '../../src/plugin/isToday'
+
+dayjs.extend(isToday)
+
+beforeEach(() => {
+ MockDate.set(new Date())
+})
+
+afterEach(() => {
+ MockDate.reset()
+})
+
+it('is today', () => {
+ expect(dayjs(new Date()).isToday()).toBeTruthy()
+ expect(dayjs('2017-01-01').isToday()).toBeFalsy()
+})
diff --git a/test/plugin/isTomorrow.test.js b/test/plugin/isTomorrow.test.js
new file mode 100644
index 00000000..bd73d77e
--- /dev/null
+++ b/test/plugin/isTomorrow.test.js
@@ -0,0 +1,18 @@
+import MockDate from 'mockdate'
+import dayjs from '../../src'
+import isTomorrow from '../../src/plugin/isTomorrow'
+
+dayjs.extend(isTomorrow)
+
+beforeEach(() => {
+ MockDate.set(new Date())
+})
+
+afterEach(() => {
+ MockDate.reset()
+})
+
+it('is tomorrow', () => {
+ expect(dayjs().add(1, 'day').isTomorrow()).toBeTruthy()
+ expect(dayjs('2017-01-01').isTomorrow('2019-01-01', '2017-01-01')).toBeFalsy()
+})
diff --git a/test/plugin/isYesterday.test.js b/test/plugin/isYesterday.test.js
new file mode 100644
index 00000000..ffb9a57e
--- /dev/null
+++ b/test/plugin/isYesterday.test.js
@@ -0,0 +1,18 @@
+import MockDate from 'mockdate'
+import dayjs from '../../src'
+import isYesterday from '../../src/plugin/isYesterday'
+
+dayjs.extend(isYesterday)
+
+beforeEach(() => {
+ MockDate.set(new Date())
+})
+
+afterEach(() => {
+ MockDate.reset()
+})
+
+it('is yesterday', () => {
+ expect(dayjs().subtract(1, 'day').isYesterday()).toBeTruthy()
+ expect(dayjs('2017-01-01').isYesterday()).toBeFalsy()
+})
diff --git a/test/plugin/localeData.test.js b/test/plugin/localeData.test.js
index 28d667a0..288c133f 100644
--- a/test/plugin/localeData.test.js
+++ b/test/plugin/localeData.test.js
@@ -3,6 +3,7 @@ import moment from 'moment'
import dayjs from '../../src'
import localeData from '../../src/plugin/localeData'
import localizedFormat from '../../src/plugin/localizedFormat'
+import '../../src/locale/fr'
import '../../src/locale/zh-cn'
dayjs.extend(localizedFormat)
@@ -38,7 +39,7 @@ it('Instance localeData', () => {
it('Global localeData', () => {
- ['zh-cn', 'en'].forEach((lo) => {
+ ['zh-cn', 'en', 'fr'].forEach((lo) => {
dayjs.locale(lo)
moment.locale(lo)
const dayjsLocaleData = dayjs.localeData()
@@ -54,7 +55,7 @@ it('Global localeData', () => {
it('Listing the months and weekdays', () => {
- ['zh-cn', 'en'].forEach((lo) => {
+ ['zh-cn', 'en', 'fr'].forEach((lo) => {
dayjs.locale(lo)
moment.locale(lo)
expect(dayjs.months()).toEqual(moment.months())
diff --git a/test/plugin/relativeTime.test.js b/test/plugin/relativeTime.test.js
index 11b1d5d5..e8a26e0a 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'
@@ -84,7 +85,7 @@ it('Time to X', () => {
expect(dayjs().to(dayjs().subtract(3, 'year'))).toBe(moment().to(moment().subtract(3, 'year')))
})
-it('Locale Fonction', () => {
+it('Locale Function', () => {
// e.g. in ru locale, m: x minute require additional processing
// and provides as a function instead of a string
const str0 = '2020-01-06 15:53:00'
@@ -115,3 +116,24 @@ it('Time from now with UTC', () => {
expect(dutc.fromNow()).toBe(mutc.fromNow())
})
+
+it('Custom thresholds and rounding support', () => {
+ expect(dayjs().subtract(45, 'm').fromNow()).toBe('an hour ago')
+ dayjs.extend(relativeTime, {
+ rounding: Math.floor,
+ thresholds: [
+ { 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: 29, d: C.D },
+ { l: 'M', r: 1 },
+ { l: 'MM', r: 11, d: C.M },
+ { l: 'y' },
+ { l: 'yy', d: C.Y }
+ ]
+ })
+ expect(dayjs().subtract(45, 'm').fromNow()).toBe('45 minutes ago')
+})
diff --git a/types/index.d.ts b/types/index.d.ts
index a0f38c89..f971394d 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/duration.d.ts b/types/plugin/duration.d.ts
new file mode 100644
index 00000000..38225c92
--- /dev/null
+++ b/types/plugin/duration.d.ts
@@ -0,0 +1,58 @@
+import { PluginFunc } from 'dayjs'
+
+declare const plugin: PluginFunc
+export = plugin
+
+type DurationInputType = string | number | object
+type DurationAddType = number | object | Duration
+
+declare class Duration {
+ constructor (input: DurationInputType, unit?: string, locale?: string)
+
+ clone(): Duration
+
+ humanize(withSuffix: boolean): string
+
+ milliseconds(): number
+ asMilliseconds(): number
+
+ seconds(): number
+ asSeconds(): number
+
+ minutes(): number
+ asMinutes(): number
+
+ hours(): number
+ asHours(): number
+
+ days(): number
+ asDays(): number
+
+ weeks(): number
+ asWeeks(): number
+
+ months(): number
+ asMonths(): number
+
+ years(): number
+ asYears(): number
+
+ as(unit: string): number
+
+ get(unit: string): number
+
+ add(input: DurationAddType, unit? : string): Duration
+
+ subtract(input: DurationAddType, unit? : 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
+}
diff --git a/types/plugin/isToday.d.ts b/types/plugin/isToday.d.ts
new file mode 100644
index 00000000..04ac5818
--- /dev/null
+++ b/types/plugin/isToday.d.ts
@@ -0,0 +1,10 @@
+import { PluginFunc } from 'dayjs'
+
+declare const plugin: PluginFunc
+export = plugin
+
+declare module 'dayjs' {
+ interface Dayjs {
+ isToday(): boolean
+ }
+}
diff --git a/types/plugin/isTomorrow.d.ts b/types/plugin/isTomorrow.d.ts
new file mode 100644
index 00000000..08110b6e
--- /dev/null
+++ b/types/plugin/isTomorrow.d.ts
@@ -0,0 +1,10 @@
+import { PluginFunc } from 'dayjs'
+
+declare const plugin: PluginFunc
+export = plugin
+
+declare module 'dayjs' {
+ interface Dayjs {
+ isTomorrow(): boolean
+ }
+}
diff --git a/types/plugin/isYesterday.d.ts b/types/plugin/isYesterday.d.ts
new file mode 100644
index 00000000..2d8ae9e1
--- /dev/null
+++ b/types/plugin/isYesterday.d.ts
@@ -0,0 +1,10 @@
+import { PluginFunc } from 'dayjs'
+
+declare const plugin: PluginFunc
+export = plugin
+
+declare module 'dayjs' {
+ interface Dayjs {
+ isYesterday(): boolean
+ }
+}
diff --git a/types/plugin/relativeTime.d.ts b/types/plugin/relativeTime.d.ts
index d08121ee..444b0c26 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 {
+ rounding?: (num: number) => number
+ thresholds?: RelativeTimeThreshold[]
+}
+
+declare const plugin: PluginFunc
export = plugin
declare module 'dayjs' {