Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add Duration plugin #858

Merged
merged 42 commits into from
Apr 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
a6c16df
init duration
iamkun Apr 7, 2020
81e693e
basic parse
iamkun Apr 7, 2020
ac1470b
basic parse2
iamkun Apr 7, 2020
d3d12de
basic parse3
iamkun Apr 7, 2020
0f0e2cc
basic parse4
iamkun Apr 7, 2020
ab3f58a
init test
iamkun Apr 7, 2020
e521c73
init test2
iamkun Apr 7, 2020
4ba1aaa
fix parse
iamkun Apr 7, 2020
6b125a8
add api
iamkun Apr 7, 2020
204fc99
add test
iamkun Apr 7, 2020
c6cec77
add test
iamkun Apr 7, 2020
2f51d42
add test
iamkun Apr 7, 2020
90568f8
add test
iamkun Apr 7, 2020
4161cb5
add test
iamkun Apr 7, 2020
8659c84
add test
iamkun Apr 7, 2020
8e632d6
add add subtract
iamkun Apr 7, 2020
e658133
add test
iamkun Apr 7, 2020
0382a88
add test
iamkun Apr 7, 2020
48315ba
add wrapper
iamkun Apr 7, 2020
9d3017d
add test
iamkun Apr 7, 2020
fccd321
add test
iamkun Apr 7, 2020
c3c3252
add test
iamkun Apr 7, 2020
5ae2d86
add test
iamkun Apr 7, 2020
f6daa5c
add test
iamkun Apr 7, 2020
2bb1d07
add test
iamkun Apr 7, 2020
5e1d587
add test
iamkun Apr 7, 2020
ca7c9a7
add test
iamkun Apr 7, 2020
3421431
add test
iamkun Apr 8, 2020
cb8a5e8
add test
iamkun Apr 8, 2020
7881fb9
add test
iamkun Apr 8, 2020
6b14741
add test
iamkun Apr 8, 2020
e1bed0c
add test
iamkun Apr 8, 2020
cdcc7af
add test
iamkun Apr 8, 2020
e09c8df
add test
iamkun Apr 8, 2020
47a847a
add prettyUnit
iamkun Apr 8, 2020
af7d1ef
add test
iamkun Apr 8, 2020
a9d9287
add test
iamkun Apr 8, 2020
b366ef8
add test
iamkun Apr 8, 2020
7e64788
add ms logic
iamkun Apr 8, 2020
d95e8ac
add
iamkun Apr 8, 2020
5770f04
add type
iamkun Apr 8, 2020
55a931a
add type
iamkun Apr 8, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

# IDE
.idea
.vscode

# npm
node_modules
Expand Down
174 changes: 174 additions & 0 deletions src/plugin/duration/index.js
Original file line number Diff line number Diff line change
@@ -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
}
181 changes: 181 additions & 0 deletions test/plugin/duration.test.js
Original file line number Diff line number Diff line change
@@ -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')
})
Loading