diff --git a/docs/en/I18n.md b/docs/en/I18n.md index 2f9a8bddb..6226e32f6 100644 --- a/docs/en/I18n.md +++ b/docs/en/I18n.md @@ -100,7 +100,12 @@ const localeObject = { L: 'MM/DD/YYYY', LL: 'MMMM D, YYYY', LLL: 'MMMM D, YYYY h:mm A', - LLLL: 'dddd, MMMM D, YYYY h:mm A' + LLLL: 'dddd, MMMM D, YYYY h:mm A', + // lowercase/short, optional formats for localization + l: 'D/M/YYYY', + ll: 'D MMM, YYYY', + lll: 'D MMM, YYYY h:mm A', + llll: 'ddd, MMM D, YYYY h:mm A' }, relativeTime: { // relative time format strings, keep %s %d as the same diff --git a/docs/en/Plugin.md b/docs/en/Plugin.md index 4a8300383..586d01f3b 100644 --- a/docs/en/Plugin.md +++ b/docs/en/Plugin.md @@ -89,6 +89,10 @@ List of added formats: | `LL` | MMMM D, YYYY | August 16, 2018 | | `LLL` | MMMM D, YYYY h:mm A | August 16, 2018 8:02 PM | | `LLLL` | dddd, MMMM D, YYYY h:mm A | Thursday, August 16, 2018 8:02 PM | +| `l` | M/D/YYYY | 8/16/2018 | +| `ll` | MMM D, YYYY | Aug 16, 2018 | +| `lll` | MMM D, YYYY h:mm A | Aug 16, 2018 8:02 PM | +| `llll` | ddd, MMM D, YYYY h:mm A | Thu, Aug 16, 2018 8:02 PM | ### RelativeTime diff --git a/docs/pt-br/I18n.md b/docs/pt-br/I18n.md index 108263524..901b44c4a 100644 --- a/docs/pt-br/I18n.md +++ b/docs/pt-br/I18n.md @@ -93,6 +93,20 @@ const objetoLocale = { months: 'Enero_Febrero ... '.split('_'), // meses: Array monthsShort: 'Jan_F'.split('_'), // OPCIONAL, meses com nome curto: Array, utiliza as três primeiras letras se nenhuma for especificada ordinal: n => `${n}º`, // ordinal: Function (number) => retorna number + saída + formats: { + // opções para formatos localizados + 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', + // formatos localizados/curtos opcionais + l: 'D/M/YYYY', + ll: 'D MMM, YYYY', + lll: 'D MMM, YYYY h:mm A', + llll: 'ddd, MMM D, YYYY h:mm A' + }, relativeTime: { // formato relativo de horas, mantém %s %d como o mesmo future: 'em %s', // exemplo: em 2 horas, %s será substituído por 2 horas diff --git a/src/constant.js b/src/constant.js index 2c9dd9aa0..5dbff8054 100644 --- a/src/constant.js +++ b/src/constant.js @@ -27,4 +27,4 @@ export const INVALID_DATE_STRING = 'Invalid Date' // regex export const REGEX_PARSE = /^(\d{4})-?(\d{1,2})-?(\d{0,2})[^0-9]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?.?(\d{1,3})?$/ -export const REGEX_FORMAT = /\[.*?\]|Y{2,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g +export const REGEX_FORMAT = /\[([^\]]+)]|Y{2,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g diff --git a/src/index.js b/src/index.js index 56ce8d6cb..6f2ebb326 100644 --- a/src/index.js +++ b/src/index.js @@ -296,10 +296,7 @@ class Dayjs { Z: zoneStr } - return str.replace(C.REGEX_FORMAT, (match) => { - if (match.indexOf('[') > -1) return match.replace(/\[|\]/g, '') - return matches[match] || zoneStr.replace(':', '') // 'ZZ' - }) + return str.replace(C.REGEX_FORMAT, (match, $1) => $1 || matches[match] || zoneStr.replace(':', '')) // 'ZZ' } utcOffset() { diff --git a/src/locale/ca.js b/src/locale/ca.js index 5702a4d56..8586d6d2f 100644 --- a/src/locale/ca.js +++ b/src/locale/ca.js @@ -11,7 +11,10 @@ const locale = { L: 'DD/MM/YYYY', LL: 'D MMMM [de] YYYY', LLL: 'D MMMM [de] YYYY [a les] H:mm', - LLLL: 'dddd D MMMM [de] YYYY [a les] H:mm' + LLLL: 'dddd D MMMM [de] YYYY [a les] H:mm', + ll: 'D MMM YYYY', + lll: 'D MMM YYYY, H:mm', + llll: 'ddd D MMM YYYY, H:mm' }, relativeTime: { future: 'en %s', diff --git a/src/locale/cs.js b/src/locale/cs.js index 10e456e5f..0260aa00e 100644 --- a/src/locale/cs.js +++ b/src/locale/cs.js @@ -12,7 +12,8 @@ const locale = { L: 'DD.MM.YYYY', LL: 'D. MMMM YYYY', LLL: 'D. MMMM YYYY H:mm', - LLLL: 'dddd D. MMMM YYYY H:mm' + LLLL: 'dddd D. MMMM YYYY H:mm', + l: 'D. M. YYYY' }, relativeTime: { future: 'za %s', diff --git a/src/locale/fi.js b/src/locale/fi.js index e3661a3ec..f1f410898 100644 --- a/src/locale/fi.js +++ b/src/locale/fi.js @@ -32,6 +32,18 @@ const locale = { MM: '%d kuukautta', // for past tense y: 'vuosi', // for past tense yy: '%d vuotta' // for past tense + }, + formats: { + LT: 'HH.mm', + LTS: 'HH.mm.ss', + L: 'DD.MM.YYYY', + LL: 'Do MMMM[ta] YYYY', + LLL: 'Do MMMM[ta] YYYY, [klo] HH.mm', + LLLL: 'dddd, Do MMMM[ta] YYYY, [klo] HH.mm', + l: 'D.M.YYYY', + ll: 'Do MMM YYYY', + lll: 'Do MMM YYYY, [klo] HH.mm', + llll: 'ddd, Do MMM YYYY, [klo] HH.mm' } } diff --git a/src/locale/he.js b/src/locale/he.js index 823f8327a..b9353760c 100644 --- a/src/locale/he.js +++ b/src/locale/he.js @@ -21,7 +21,19 @@ const locale = { y: 'שנה', yy: '%d שנים' }, - ordinal: n => n + ordinal: n => n, + format: { + 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', + l: 'D/M/YYYY', + ll: 'D MMM YYYY', + lll: 'D MMM YYYY HH:mm', + llll: 'ddd, D MMM YYYY HH:mm' + } } dayjs.locale(locale, null, true) diff --git a/src/locale/ja.js b/src/locale/ja.js index a088ff666..72520a7cb 100644 --- a/src/locale/ja.js +++ b/src/locale/ja.js @@ -13,7 +13,11 @@ const locale = { L: 'YYYY/MM/DD', LL: 'YYYY年M月D日', LLL: 'YYYY年M月D日 HH:mm', - LLLL: 'YYYY年M月D日 dddd HH:mm' + LLLL: 'YYYY年M月D日 dddd HH:mm', + l: 'YYYY/MM/DD', + ll: 'YYYY年M月D日', + lll: 'YYYY年M月D日 HH:mm', + llll: 'YYYY年M月D日(ddd) HH:mm' }, relativeTime: { future: '%s後', diff --git a/src/locale/ko.js b/src/locale/ko.js index c5d3860a9..c0eef1028 100644 --- a/src/locale/ko.js +++ b/src/locale/ko.js @@ -11,7 +11,11 @@ const locale = { L: 'YYYY.MM.DD.', LL: 'YYYY년 MMMM D일', LLL: 'YYYY년 MMMM D일 A h:mm', - LLLL: 'YYYY년 MMMM D일 dddd A h:mm' + LLLL: 'YYYY년 MMMM D일 dddd A h:mm', + l: 'YYYY.MM.DD.', + ll: 'YYYY년 MMMM D일', + lll: 'YYYY년 MMMM D일 A h:mm', + llll: 'YYYY년 MMMM D일 dddd A h:mm' }, relativeTime: { future: '%s 후', diff --git a/src/locale/lt.js b/src/locale/lt.js index 0aa5abf44..9c2091852 100644 --- a/src/locale/lt.js +++ b/src/locale/lt.js @@ -20,6 +20,18 @@ const locale = { MM: '%d mėnesius', y: 'metus', yy: '%d metus' + }, + format: { + LT: 'HH:mm', + LTS: 'HH:mm:ss', + L: 'YYYY-MM-DD', + LL: 'YYYY [m.] MMMM D [d.]', + LLL: 'YYYY [m.] MMMM D [d.], HH:mm [val.]', + LLLL: 'YYYY [m.] MMMM D [d.], dddd, HH:mm [val.]', + l: 'YYYY-MM-DD', + ll: 'YYYY [m.] MMMM D [d.]', + lll: 'YYYY [m.] MMMM D [d.], HH:mm [val.]', + llll: 'YYYY [m.] MMMM D [d.], ddd, HH:mm [val.]' } } diff --git a/src/locale/sv.js b/src/locale/sv.js index f4a713551..aa61d8f13 100644 --- a/src/locale/sv.js +++ b/src/locale/sv.js @@ -19,7 +19,9 @@ const locale = { L: 'YYYY-MM-DD', LL: 'D MMMM YYYY', LLL: 'D MMMM YYYY [kl.] HH:mm', - LLLL: 'dddd D MMMM YYYY [kl.] HH:mm' + LLLL: 'dddd D MMMM YYYY [kl.] HH:mm', + lll: 'D MMM YYYY HH:mm', + llll: 'ddd D MMM YYYY HH:mm' }, relativeTime: { future: 'om %s', diff --git a/src/locale/zh-cn.js b/src/locale/zh-cn.js index bc28c7edf..8c51a01ef 100644 --- a/src/locale/zh-cn.js +++ b/src/locale/zh-cn.js @@ -15,7 +15,11 @@ const locale = { L: 'YYYY/MM/DD', LL: 'YYYY年M月D日', LLL: 'YYYY年M月D日Ah点mm分', - LLLL: 'YYYY年M月D日ddddAh点mm分' + LLLL: 'YYYY年M月D日ddddAh点mm分', + l: 'YYYY/M/D', + ll: 'YYYY年M月D日', + lll: 'YYYY年M月D日 HH:mm', + llll: 'YYYY年M月D日dddd HH:mm' }, relativeTime: { future: '%s内', diff --git a/src/locale/zh-tw.js b/src/locale/zh-tw.js index 91a6e02e7..572b86c24 100644 --- a/src/locale/zh-tw.js +++ b/src/locale/zh-tw.js @@ -14,7 +14,11 @@ const locale = { L: 'YYYY/MM/DD', LL: 'YYYY年M月D日', LLL: 'YYYY年M月D日 HH:mm', - LLLL: 'YYYY年M月D日dddd HH:mm' + LLLL: 'YYYY年M月D日dddd HH:mm', + l: 'YYYY/M/D', + ll: 'YYYY年M月D日', + lll: 'YYYY年M月D日 HH:mm', + llll: 'YYYY年M月D日dddd HH:mm' }, relativeTime: { future: '%s內', diff --git a/src/plugin/localizedFormat/index.js b/src/plugin/localizedFormat/index.js index 37d4db0ab..e3412dcfa 100644 --- a/src/plugin/localizedFormat/index.js +++ b/src/plugin/localizedFormat/index.js @@ -9,15 +9,19 @@ export default (o, c, d) => { L: 'MM/DD/YYYY', LL: 'MMMM D, YYYY', LLL: 'MMMM D, YYYY h:mm A', - LLLL: 'dddd, MMMM D, YYYY h:mm A' + LLLL: 'dddd, MMMM D, YYYY h:mm A', + l: 'M/D/YYYY', + ll: 'MMM D, YYYY', + lll: 'MMM D, YYYY h:mm A', + llll: 'ddd, MMM D, YYYY h:mm A' } d.en.formats = englishFormats proto.format = function (formatStr) { const locale = this.$locale() const formats = locale.formats || {} const str = formatStr || FORMAT_DEFAULT - const result = str.replace(/LTS|LT|L{1,4}/g, match => - formats[match] || englishFormats[match]) + const result = str.replace(/(\[[^\]]+])|(LTS?|l{1,4}|L{1,4})/g, (_, a, b) => + a || formats[b] || englishFormats[b]) return oldFormat.call(this, result) } } diff --git a/test/display.test.js b/test/display.test.js index a8a328688..524aa8ecc 100644 --- a/test/display.test.js +++ b/test/display.test.js @@ -138,7 +138,9 @@ it('Format Complex with other string - : / ', () => { }) it('Format Escaping characters', () => { - const string = '[Z] Z' + let string = '[Z] Z' + expect(dayjs().format(string)).toBe(moment().format(string)) + string = '[Z] Z [Z]' expect(dayjs().format(string)).toBe(moment().format(string)) }) diff --git a/test/locale/keys.test.js b/test/locale/keys.test.js index c22a86807..a01562e26 100644 --- a/test/locale/keys.test.js +++ b/test/locale/keys.test.js @@ -3,17 +3,17 @@ import path from 'path' import dayjs from '../../src' const localeDir = '../../src/locale' -const L = [] +const Locale = [] // load all locales from locale dir fs.readdirSync(path.join(__dirname, localeDir)) .forEach((file) => { // eslint-disable-next-line - L.push(require(path.join(__dirname, localeDir, file)).default) + Locale.push(require(path.join(__dirname, localeDir, file)).default) }) it('Locale keys', () => { - L.forEach((l) => { + Locale.forEach((locale) => { const { name, ordinal, @@ -25,7 +25,7 @@ it('Locale keys', () => { monthsShort, weekdaysMin, weekStart - } = l + } = locale expect(name).toEqual(expect.any(String)) expect(weekdays).toEqual(expect.any(Array)) @@ -44,7 +44,32 @@ it('Locale keys', () => { expect(dayjs().locale(name).$locale().name).toBe(name) if (formats) { - expect(Object.keys(formats).sort()).toEqual(['L', 'LL', 'LLL', 'LLLL', 'LT', 'LTS'].sort()) + const { + LT, + LTS, + L, + LL, + LLL, + LLLL, + l, + ll, + lll, + llll, + ...remainingFormats + } = formats + expect(formats).toEqual(expect.objectContaining({ + L: expect.any(String), + LL: expect.any(String), + LLL: expect.any(String), + LLLL: expect.any(String), + LT: expect.any(String), + LTS: expect.any(String) + })) + expect(Object.keys(remainingFormats).length).toEqual(0) + if (l) expect(l).toEqual(expect.any(String)) + if (ll) expect(ll).toEqual(expect.any(String)) + if (lll) expect(lll).toEqual(expect.any(String)) + if (llll) expect(llll).toEqual(expect.any(String)) } if (relativeTime) { expect(Object.keys(relativeTime).sort()).toEqual(['d', 'dd', 'future', 'h', 'hh', 'm', 'mm', 'M', 'MM', diff --git a/test/plugin/localizableFormat.test.js b/test/plugin/localizableFormat.test.js index 5e6b3055d..dc6348125 100644 --- a/test/plugin/localizableFormat.test.js +++ b/test/plugin/localizableFormat.test.js @@ -18,14 +18,25 @@ afterEach(() => { it('Declares English localized formats', () => { expect(dayjs.en).toBeDefined() expect(dayjs.en.formats).toBeDefined(); - ['LT', 'LTS', 'L', 'LL', 'LLL', 'LLLL'].forEach(option => + ['LT', 'LTS', 'L', 'LL', 'LLL', 'LLLL', 'l', 'll', 'lll', 'llll'].forEach(option => expect(dayjs.en.formats[option]).toBeDefined()) }) +it('Should not interpolate characters inside square brackets', () => { + const date = new Date(0) + const actualDate = dayjs(date) + const expectedDate = moment(date) + + expect(actualDate.format('[l]')).toBe('l') + expect(actualDate.format('YYYY [l] YYYY')).toBe('1970 l 1970') + expect(actualDate.format('l [l] l')).toBe('1/1/1970 l 1/1/1970') + expect(actualDate.format('[L LL LLL LLLL]')).toBe(expectedDate.format('[L LL LLL LLLL]')) +}) + it('Recognizes localized format options', () => { const { formats } = dayjs.en const date = dayjs(); - ['LT', 'LTS', 'L', 'LL', 'LLL', 'LLLL'].forEach(option => + ['LT', 'LTS', 'L', 'LL', 'LLL', 'LLLL', 'l', 'll', 'lll', 'llll'].forEach(option => expect(date.format(option)).toBe(date.format(formats[option]))) }) @@ -33,7 +44,7 @@ it('Uses correct English formats', () => { const date = new Date() const actualDate = dayjs(date) const expectedDate = moment(date); - ['LT', 'LTS', 'L', 'LL', 'LLL', 'LLLL'].forEach(option => + ['LT', 'LTS', 'L', 'LL', 'LLL', 'LLLL', 'l', 'll', 'lll', 'llll'].forEach(option => expect(actualDate.format(option)).toBe(expectedDate.format(option))) })