diff --git a/src/plugin/duration/index.js b/src/plugin/duration/index.js
new file mode 100644
--- /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/types/plugin/duration.d.ts b/types/plugin/duration.d.ts
new file mode 100644
--- /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/test/plugin/duration.test.js b/test/plugin/duration.test.js
new file mode 100644
--- /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')
+})

Feature request: Add Duration Plugin
Update: DONE  🎉🎉🎉🎉🎉🎉

Duration 
https://day.js.org/docs/en/durations/durations

v1.8.24

--------------

Official duration plugin  https://github.com/iamkun/dayjs/pull/858

---------


Goal: to keep the same with moment.duration
1. Parse ISO duration format  #122
2. Duration object to ISO string `toISOString()` #519
3. Using Duration with Diff `.duration(x.diff(y))`  #414


ref: https://en.wikipedia.org/wiki/ISO_8601#Durations


<!-- 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: $116.00)

- [<img src='https://avatars3.githubusercontent.com/u/44827199?v=4' alt='issuehunt' width=24 height=24> issuehunt](https://issuehunt.io/u/issuehunt) ($100.00)
- [<img src='https://avatars0.githubusercontent.com/u/42378428?v=4' alt='xxyuk' width=24 height=24> xxyuk](https://issuehunt.io/u/xxyuk) ($2.00)
- [<img src='https://avatars1.githubusercontent.com/u/10731096?v=4' alt='zazzaz' width=24 height=24> zazzaz](https://issuehunt.io/u/zazzaz) ($2.00)
- [<img src='https://avatars3.githubusercontent.com/u/42496021?v=4' alt='waseemahmad31' width=24 height=24> waseemahmad31](https://issuehunt.io/u/waseemahmad31) ($2.00)
- [<img src='https://avatars3.githubusercontent.com/u/11534760?v=4' alt='muuvmuuv' width=24 height=24> muuvmuuv](https://issuehunt.io/u/muuvmuuv) ($10.00)

### Submitted pull Requests
- [#621 feat: Duration plugin, see: https://github.com/iamkun/dayjs/issues/564](https://issuehunt.io/r/iamkun/dayjs/pull/621)
- [#858 fix: add Duration plugin](https://issuehunt.io/r/iamkun/dayjs/pull/858)
---

### 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-->

@issuehunt has funded $100.00 to this issue.

---
- Submit pull request via [IssueHunt](https://issuehunt.io/repos/128907699/issues/564) to receive this reward.
- Want to contribute? Chip in to this issue via [IssueHunt](https://issuehunt.io/repos/128907699/issues/564).
- 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.
WIP
@xxyuk has funded $2.00 to this issue.

---
- Submit pull request via [IssueHunt](https://issuehunt.io/repos/128907699/issues/564) to receive this reward.
- Want to contribute? Chip in to this issue via [IssueHunt](https://issuehunt.io/repos/128907699/issues/564).
- 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.
@zazzaz has funded $2.00 to this issue.

---
- Submit pull request via [IssueHunt](https://issuehunt.io/repos/128907699/issues/564) to receive this reward.
- Want to contribute? Chip in to this issue via [IssueHunt](https://issuehunt.io/repos/128907699/issues/564).
- 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.
@waseemahmad31 has funded $2.00 to this issue.

---
- Submit pull request via [IssueHunt](https://issuehunt.io/repos/128907699/issues/564) to receive this reward.
- Want to contribute? Chip in to this issue via [IssueHunt](https://issuehunt.io/repos/128907699/issues/564).
- 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.
Could this include functionality similar to [moment-duration-format](https://github.com/jsmreese/moment-duration-format)?
will include moment.duration in the first step
hi, may I work on this one? I did the plugin on my fork and it works
see:
![durationISO8601](https://user-images.githubusercontent.com/17609793/60066378-e5779a80-9741-11e9-97f8-244921a82732.gif)

it return an object with this format:
`
const duration = {
      ISO: '',
      s: 0,
      m: 0,
      H: 0,
      D: 0,
      M: 0,
      y: 0
    }
`
Any news on this?
alternative: [humanize-duration package](https://www.npmjs.com/package/humanize-duration) 
it may be convenient to use that package as dependency in the Duration plugin

how I can use it?
Any news on this? (
Any news on this?
[@muuvmuuv](https://issuehunt.io/u/muuvmuuv) has funded $10.00 to this issue.

---
- Submit pull request via [IssueHunt](https://issuehunt.io/repos/128907699/issues/564) to receive this reward.
- Want to contribute? Chip in to this issue via [IssueHunt](https://issuehunt.io/repos/128907699/issues/564).
- 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.
Hello, any news regarding the work on the duration plugin?

@lockee14 provided a PR (https://github.com/iamkun/dayjs/pull/621), is something preventing from merging it?

Thanks.
have the same 请尽快合并pr吧 @iamkun 
Any updates here? Would really like to have it :) 
Just to be clear.. I'm looking for something similar to https://momentjs.com/docs/#/durations/ :)
@iamkun any feed back would be greatly appreciated.. just to let us know an eta or even if this will ever happen.. :/ 
test/timezone.test.js(1) test/timezone.test.js(2) test/timezone.test.js(3) test/timezone.test.js(4)
test/plugin/duration.test.js