Repository: iamkun/dayjs (JavaScript)

Issue:
Getter/setter plural aliases missing (e.g. hour() works but hours() does not)
https://github.com/iamkun/dayjs/issues/58#issuecomment-540910296


Hints:
It's easy to add this as a plugin, but is it necessary?
@iamkun 	I definitely think so.  You have plural support in `diff`, e.g. https://github.com/iamkun/dayjs/blob/4c862f682e763c5ad27ebc2492f7a40d0030ea4f/test/display.test.js#L180.
yes, that plural for unit
I think that if you have plural and singular in some parts of this library, it should be consistent everywhere else.  Having a MomentJS advertised compatible API should add this in - especially since it's so easy to add it in as you say.
I'll pending this issue for more discussion.  The top 1 reason is always this total bundle size. I don't want to add even 1 more byte for features that are not that important.
Any chance we could get this in as a feature?  I'd gladly tip.  I don't think it'd add much to make it so plural versions work, and the developer experience would greatly be in favor of this, it would also be in parity with moment, and make it easier for people to switch massive codebases over.
Agree to add it as a plugin, but not in the main bundle. 

Patch:
diff --git a/src/plugin/pluralGetSet/index.js b/src/plugin/pluralGetSet/index.js
new file mode 100644
--- /dev/null
+++ b/src/plugin/pluralGetSet/index.js
@@ -0,0 +1,21 @@
+export default (o, c) => {
+  const proto = c.prototype
+
+  const pluralAliases = [
+    'milliseconds',
+    'seconds',
+    'minutes',
+    'hours',
+    'days',
+    'weeks',
+    'isoWeeks',
+    'months',
+    'quarters',
+    'years',
+    'dates'
+  ]
+
+  pluralAliases.forEach((alias) => {
+    proto[alias] = proto[alias.replace(/s$/, '')]
+  })
+}
diff --git a/types/plugin/pluralGetSet.d.ts b/types/plugin/pluralGetSet.d.ts
new file mode 100644
--- /dev/null
+++ b/types/plugin/pluralGetSet.d.ts
@@ -0,0 +1,44 @@
+import { PluginFunc, UnitType, ConfigType } from 'dayjs'
+
+declare const plugin: PluginFunc
+export = plugin
+
+declare module 'dayjs' {
+  interface Dayjs {
+    years(): number
+
+    years(value: number): Dayjs
+
+    months(): number
+
+    months(value: number): Dayjs
+
+    dates(): number
+
+    dates(value: number): Dayjs
+
+    weeks(): number
+
+    weeks(value: number): Dayjs
+
+    days(): number
+
+    days(value: number): Dayjs
+
+    hours(): number
+
+    hours(value: number): Dayjs
+
+    minutes(): number
+
+    minutes(value: number): Dayjs
+
+    seconds(): number
+
+    seconds(value: number): Dayjs
+
+    milliseconds(): number
+
+    milliseconds(value: number): Dayjs
+  }
+}


Test patch:
diff --git a/test/plugin/pluralGetSet.test.js b/test/plugin/pluralGetSet.test.js
new file mode 100644
--- /dev/null
+++ b/test/plugin/pluralGetSet.test.js
@@ -0,0 +1,125 @@
+import MockDate from 'mockdate'
+import moment from 'moment'
+import dayjs from '../../src'
+import pluralGetSet from '../../src/plugin/pluralGetSet'
+
+dayjs.extend(pluralGetSet)
+
+beforeEach(() => {
+  MockDate.set(new Date())
+})
+
+afterEach(() => {
+  MockDate.reset()
+})
+
+it('Years', () => {
+  expect(dayjs().get('years')).toBe(moment().get('years'))
+  expect(dayjs().years()).toBe(moment().years())
+  expect(dayjs().years(0).valueOf()).toBe(moment().years(0).valueOf())
+  expect(dayjs().years(2000).valueOf()).toBe(moment().years(2000).valueOf())
+})
+
+it('Months', () => {
+  expect(dayjs().get('months')).toBe(moment().get('months'))
+  expect(dayjs().months()).toBe(moment().months())
+  expect(dayjs().months(0).valueOf()).toBe(moment().months(0).valueOf())
+  expect(dayjs().months(1).valueOf()).toBe(moment().months(1).valueOf())
+})
+
+it('Days of Week', () => {
+  expect(dayjs().get('days')).toBe(moment().get('days'))
+  expect(dayjs().days()).toBe(moment().days())
+  expect(dayjs().days(0).format()).toBe(moment().days(0).format())
+  expect(dayjs().days(1).format()).toBe(moment().days(1).format())
+})
+
+it('Dates', () => {
+  expect(dayjs().get('dates')).toBe(moment().get('dates'))
+  expect(dayjs().dates()).toBe(moment().dates())
+  expect(dayjs().dates(0).valueOf()).toBe(moment().dates(0).valueOf())
+  expect(dayjs().dates(1).valueOf()).toBe(moment().dates(1).valueOf())
+})
+
+it('Hours', () => {
+  expect(dayjs().get('hours')).toBe(moment().get('hours'))
+  expect(dayjs().hours()).toBe(moment().hours())
+  expect(dayjs().hours(0).valueOf()).toBe(moment().hours(0).valueOf())
+  expect(dayjs().hours(1).valueOf()).toBe(moment().hours(1).valueOf())
+})
+
+it('Minutes', () => {
+  expect(dayjs().get('minutes')).toBe(moment().get('minutes'))
+  expect(dayjs().minutes()).toBe(moment().minutes())
+  expect(dayjs().minutes(0).valueOf()).toBe(moment().minutes(0).valueOf())
+  expect(dayjs().minutes(1).valueOf()).toBe(moment().minutes(1).valueOf())
+})
+
+it('Seconds', () => {
+  expect(dayjs().get('seconds')).toBe(moment().get('seconds'))
+  expect(dayjs().seconds()).toBe(moment().seconds())
+  expect(dayjs().seconds(0).valueOf()).toBe(moment().seconds(0).valueOf())
+  expect(dayjs().seconds(1).valueOf()).toBe(moment().seconds(1).valueOf())
+})
+
+it('Milliseconds', () => {
+  expect(dayjs().get('milliseconds')).toBe(moment().get('milliseconds'))
+  expect(dayjs().milliseconds()).toBe(moment().milliseconds())
+  expect(dayjs().milliseconds(0).valueOf()).toBe(moment().milliseconds(0).valueOf())
+  expect(dayjs().milliseconds(1).valueOf()).toBe(moment().milliseconds(1).valueOf())
+})
+
+it('Set Dates', () => {
+  expect(dayjs().date(30).valueOf()).toBe(moment().dates(30).valueOf())
+  expect(dayjs().set('dates', 30).valueOf()).toBe(moment().set('dates', 30).valueOf())
+})
+
+it('Set Days of Week', () => {
+  expect(dayjs().days(0).valueOf()).toBe(moment().days(0).valueOf())
+  expect(dayjs().set('days', 0).valueOf()).toBe(moment().set('days', 0).valueOf())
+})
+
+it('Set Months', () => {
+  expect(dayjs().months(11).valueOf()).toBe(moment().months(11).valueOf())
+  expect(dayjs().set('months', 11).valueOf()).toBe(moment().set('months', 11).valueOf())
+})
+
+it('Set Years', () => {
+  expect(dayjs().years(2008).valueOf()).toBe(moment().year(2008).valueOf())
+  expect(dayjs().set('years', 2008).valueOf()).toBe(moment().set('years', 2008).valueOf())
+})
+
+it('Set Hours', () => {
+  expect(dayjs().set('hours', 6).valueOf()).toBe(moment().set('hours', 6).valueOf())
+  expect(dayjs().hours(6).valueOf()).toBe(moment().hours(6).valueOf())
+})
+
+it('Set Minutes', () => {
+  expect(dayjs().minutes(59).valueOf()).toBe(moment().minutes(59).valueOf())
+  expect(dayjs().set('minutes', 59).valueOf()).toBe(moment().set('minutes', 59).valueOf())
+})
+
+it('Set Seconds', () => {
+  expect(dayjs().seconds(59).valueOf()).toBe(moment().seconds(59).valueOf())
+  expect(dayjs().set('second', 59).valueOf()).toBe(moment().set('second', 59).valueOf())
+})
+
+it('Set Milliseconds', () => {
+  expect(dayjs().milliseconds(999).valueOf()).toBe(moment().milliseconds(999).valueOf())
+  expect(dayjs().set('millisecond', 999).valueOf()).toBe(moment().set('millisecond', 999).valueOf())
+})
+
+it('Set Month and Year in last day of month', () => {
+  // 2011-07-31 -> 2011-02-28
+  const origin = dayjs('2011-07-31T14:48:00.000Z')
+  const setMonth = origin.set('month', 1)
+  expect(setMonth.months()).toBe(1)
+  expect(origin.dates()).toBe(31)
+  expect(setMonth.dates()).toBe(28)
+  // 2000-02-29 -> 2001-02-28
+  const origin2 = dayjs('2000-02-29T14:48:00.000Z')
+  const setYear = origin2.set('years', 2001)
+  expect(setYear.months()).toBe(1)
+  expect(origin2.dates()).toBe(29)
+  expect(setYear.dates()).toBe(28)
+})