Repository: iamkun/dayjs (JavaScript)

Issue:
Please fix formatting months in russian
<!-- Issuehunt Badges -->
[<img alt="Issuehunt badges" src="https://img.shields.io/badge/IssueHunt-%2450%20Rewarded-%237E24E3.svg" />](https://issuehunt.io/r/iamkun/dayjs/issues/577)
<!-- /Issuehunt Badges -->


**Describe the bug**
Long description of months (MMMM) are showing not correctly.

**Expected behavior**
I created comparison, so you can see what it should look like. Also I've included possible fix (different months field in object fixedRuLocale) - https://runkit.com/bagmutva/5cbc390d7da00600130f419c
For now in my work project we're using dayjs and moment. Unfortunately because of this bug we using moment for formatting long dates. But we want to use only one library - dayjs. So please fix this.

**Information**
 - Day.js Version 1.8.12
 - OS: MacOS Mojave 10.14.4
 - Browser: Google Chrome 73.0.3683.86


<!-- Issuehunt content -->

---

<details>
<summary>
<b>IssueHunt Summary</b>
</summary>

#### [<img src='https://avatars0.githubusercontent.com/u/17680888?v=4' alt='iamkun' width=24 height=24> iamkun](https://issuehunt.io/u/iamkun) has been rewarded.

### Backers (Total: $50.00)

- [<img src='https://avatars3.githubusercontent.com/u/44827199?v=4' alt='issuehunt' width=24 height=24> issuehunt](https://issuehunt.io/u/issuehunt) ($50.00)
### Submitted pull Requests
- [#581 fix: Update locale month to support both array and function](https://issuehunt.io/r/iamkun/dayjs/pull/581)
---

### Tips

- Checkout the [Issuehunt explorer](https://issuehunt.io/r/iamkun/dayjs/) to discover more funded issues.
- Need some help from other developers? [Add your repositories](https://issuehunt.io/r/new) on IssueHunt to raise funds.
---
IssueHunt has been backed by the following sponsors. [Become a sponsor](https://issuehunt.io/membership/members)
</details>
<!-- /Issuehunt content-->


Hints:
This might be a breaking change of Day.js. 

After searching: 

> In this example, in Russian language a stand-alone month is in the nominative case ("январь"), and formatting month is in the genitive case ("января" as in "1-е января"). Notice the different endings:

That is to say we should add two different format string for format and standalone type of different formatting cases.

Any good idea, guys?


@issuehunt has funded $50.00 to this issue.

---
- Submit pull request via [IssueHunt](https://issuehunt.io/repos/128907699/issues/577) to receive this reward.
- Want to contribute? Chip in to this issue via [IssueHunt](https://issuehunt.io/repos/128907699/issues/577).
- Checkout the [IssueHunt Issue Explorer](https://issuehunt.io/issues) to see more funded issues.
- Need help from developers? [Add your repository](https://issuehunt.io/r/new) on IssueHunt to raise funds.

Patch:
diff --git a/src/index.js b/src/index.js
--- a/src/index.js
+++ b/src/index.js
@@ -278,14 +278,15 @@ class Dayjs {
     const str = formatStr || C.FORMAT_DEFAULT
     const zoneStr = Utils.z(this)
     const locale = this.$locale()
+    const { $H, $m, $M } = this
     const {
       weekdays, months, meridiem
     } = locale
     const getShort = (arr, index, full, length) => (
-      (arr && arr[index]) || full[index].substr(0, length)
+      (arr && (arr[index] || arr(this, str))) || full[index].substr(0, length)
     )
     const get$H = num => (
-      Utils.s(this.$H % 12 || 12, num, '0')
+      Utils.s($H % 12 || 12, num, '0')
     )
 
     const meridiemFunc = meridiem || ((hour, minute, isLowercase) => {
@@ -295,32 +296,32 @@ class Dayjs {
 
     const matches = {
       YY: String(this.$y).slice(-2),
-      YYYY: String(this.$y),
-      M: String(this.$M + 1),
-      MM: Utils.s(this.$M + 1, 2, '0'),
-      MMM: getShort(locale.monthsShort, this.$M, months, 3),
-      MMMM: months[this.$M],
-      D: String(this.$D),
+      YYYY: this.$y,
+      M: $M + 1,
+      MM: Utils.s($M + 1, 2, '0'),
+      MMM: getShort(locale.monthsShort, $M, months, 3),
+      MMMM: months[$M] || months(this, str),
+      D: this.$D,
       DD: Utils.s(this.$D, 2, '0'),
-      d: String(this.$W),
+      d: this.$W,
       dd: getShort(locale.weekdaysMin, this.$W, weekdays, 2),
       ddd: getShort(locale.weekdaysShort, this.$W, weekdays, 3),
       dddd: weekdays[this.$W],
-      H: String(this.$H),
-      HH: Utils.s(this.$H, 2, '0'),
+      H: $H,
+      HH: Utils.s($H, 2, '0'),
       h: get$H(1),
       hh: get$H(2),
-      a: meridiemFunc(this.$H, this.$m, true),
-      A: meridiemFunc(this.$H, this.$m, false),
-      m: String(this.$m),
-      mm: Utils.s(this.$m, 2, '0'),
-      s: String(this.$s),
+      a: meridiemFunc($H, $m, true),
+      A: meridiemFunc($H, $m, false),
+      m: $m,
+      mm: Utils.s($m, 2, '0'),
+      s: this.$s,
       ss: Utils.s(this.$s, 2, '0'),
       SSS: Utils.s(this.$ms, 3, '0'),
       Z: zoneStr // 'ZZ' logic below
     }
 
-    return str.replace(C.REGEX_FORMAT, (match, $1) => $1 || matches[match] || zoneStr.replace(':', '')) // 'ZZ'
+    return String(str.replace(C.REGEX_FORMAT, (match, $1) => $1 || matches[match] || zoneStr.replace(':', ''))) // 'ZZ'
   }
 
   utcOffset() {
diff --git a/src/locale/ru.js b/src/locale/ru.js
--- a/src/locale/ru.js
+++ b/src/locale/ru.js
@@ -1,12 +1,29 @@
 import dayjs from 'dayjs'
 
+const monthFormat = 'января_февраля_марта_апреля_мая_июня_июля_августа_сентября_октября_ноября_декабря'.split('_')
+const monthStandalone = 'январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь'.split('_')
+
+const monthShortFormat = 'янв._февр._мар._апр._мая_июня_июля_авг._сент._окт._нояб._дек.'.split('_')
+const monthShortStandalone = 'янв._февр._март_апр._май_июнь_июль_авг._сент._окт._нояб._дек.'.split('_')
+
+const MONTHS_IN_FORMAT = /D[oD]?(\[[^[\]]*\]|\s)+MMMM?/
 const locale = {
   name: 'ru',
   weekdays: 'воскресенье_понедельник_вторник_среда_четверг_пятница_суббота'.split('_'),
   weekdaysShort: 'вск_пнд_втр_срд_чтв_птн_сбт'.split('_'),
   weekdaysMin: 'вс_пн_вт_ср_чт_пт_сб'.split('_'),
-  months: 'январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь'.split('_'),
-  monthsShort: 'янв_фев_мар_апр_май_июн_июл_авг_сен_окт_ноя_дек'.split('_'),
+  months: (dayjsInstance, format) => {
+    if (MONTHS_IN_FORMAT.test(format)) {
+      return monthFormat[dayjsInstance.month()]
+    }
+    return monthStandalone[dayjsInstance.month()]
+  },
+  monthsShort: (dayjsInstance, format) => {
+    if (MONTHS_IN_FORMAT.test(format)) {
+      return monthShortFormat[dayjsInstance.month()]
+    }
+    return monthShortStandalone[dayjsInstance.month()]
+  },
   weekStart: 1,
   formats: {
     LT: 'H:mm',


Test patch:
diff --git a/test/locale/keys.test.js b/test/locale/keys.test.js
--- a/test/locale/keys.test.js
+++ b/test/locale/keys.test.js
@@ -35,11 +35,23 @@ Locale.forEach((locale) => {
     expect(weekdays).toEqual(expect.any(Array))
 
     if (weekdaysShort) expect(weekdaysShort).toEqual(expect.any(Array))
-    if (monthsShort) expect(monthsShort).toEqual(expect.any(Array))
     if (weekdaysMin) expect(weekdaysMin).toEqual(expect.any(Array))
     if (weekStart) expect(weekStart).toEqual(expect.any(Number))
 
-    expect(months).toEqual(expect.any(Array))
+    // months could be a function or array
+    if (Array.isArray(months)) {
+      expect(months).toEqual(expect.any(Array))
+    } else {
+      expect(months(dayjs(), 'str')).toEqual(expect.any(String))
+    }
+    // monthsShort could be a function or array
+    if (monthsShort) {
+      if (Array.isArray(monthsShort)) {
+        expect(monthsShort).toEqual(expect.any(Array))
+      } else {
+        expect(monthsShort(dayjs(), 'str')).toEqual(expect.any(String))
+      }
+    }
     // function pass date return string or number or null
     if (name !== 'en') { // en ordinal set in advancedFormat
       for (let i = 1; i <= 31; i += 1) {
diff --git a/test/locale/ru.test.js b/test/locale/ru.test.js
new file mode 100644
--- /dev/null
+++ b/test/locale/ru.test.js
@@ -0,0 +1,25 @@
+import moment from 'moment'
+import MockDate from 'mockdate'
+import dayjs from '../../src'
+import '../../src/locale/ru'
+
+beforeEach(() => {
+  MockDate.set(new Date())
+})
+
+afterEach(() => {
+  MockDate.reset()
+})
+
+it('Format Month with locale function', () => {
+  for (let i = 0; i <= 7; i += 1) {
+    const dayjsRU = dayjs().locale('ru').add(i, 'day')
+    const momentRU = moment().locale('ru').add(i, 'day')
+    const testFormat1 = 'DD MMMM YYYY MMM'
+    const testFormat2 = 'MMMM'
+    const testFormat3 = 'MMM'
+    expect(dayjsRU.format(testFormat1)).toEqual(momentRU.format(testFormat1))
+    expect(dayjsRU.format(testFormat2)).toEqual(momentRU.format(testFormat2))
+    expect(dayjsRU.format(testFormat3)).toEqual(momentRU.format(testFormat3))
+  }
+})