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

Add parseISODuration #1947

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions src/fp/parseISODuration/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// This file is generated automatically by `scripts/build/typings.js`. Please, don't change it.

import { parseISODuration } from 'date-fns/fp'
export default parseISODuration
8 changes: 8 additions & 0 deletions src/fp/parseISODuration/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// This file is generated automatically by `scripts/build/fp.js`. Please, don't change it.

import fn from '../../parseISODuration/index.js'
import convertToFP from '../_lib/convertToFP/index.js'

var parseISODuration = convertToFP(fn, 1)

export default parseISODuration
52 changes: 52 additions & 0 deletions src/fp/parseISODuration/index.js.flow
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// @flow
// This file is generated automatically by `scripts/build/typings.js`. Please, don't change it.

export type Interval = {
start: Date | number,
end: Date | number
}

export type Locale = {
code?: string,
formatDistance?: (...args: Array<any>) => any,
formatRelative?: (...args: Array<any>) => any,
localize?: {
ordinalNumber: (...args: Array<any>) => any,
era: (...args: Array<any>) => any,
quarter: (...args: Array<any>) => any,
month: (...args: Array<any>) => any,
day: (...args: Array<any>) => any,
dayPeriod: (...args: Array<any>) => any
},
formatLong?: {
date: (...args: Array<any>) => any,
time: (...args: Array<any>) => any,
dateTime: (...args: Array<any>) => any
},
match?: {
ordinalNumber: (...args: Array<any>) => any,
era: (...args: Array<any>) => any,
quarter: (...args: Array<any>) => any,
month: (...args: Array<any>) => any,
day: (...args: Array<any>) => any,
dayPeriod: (...args: Array<any>) => any
},
options?: {
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6,
firstWeekContainsDate?: 1 | 2 | 3 | 4 | 5 | 6 | 7
}
}

export type Duration = {
years?: number,
months?: number,
weeks?: number,
days?: number,
hours?: number,
minutes?: number,
seconds?: number
}

type CurriedFn1<A, R> = <A>(a: A) => R

declare module.exports: CurriedFn1<string, Duration | null>
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ export { default as max } from './max/index'
export { default as min } from './min/index'
export { default as parse } from './parse/index'
export { default as parseISO } from './parseISO/index'
export { default as parseISODuration } from './parseISODuration/index'
export { default as parseJSON } from './parseJSON/index'
export { default as roundToNearestMinutes } from './roundToNearestMinutes/index'
export { default as set } from './set/index'
Expand Down
2 changes: 2 additions & 0 deletions src/index.js.flow
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,8 @@ declare module.exports: {
}
) => Date,

parseISODuration: (argument: string) => Duration | null,

parseJSON: (argument: string | number | Date) => Date,

roundToNearestMinutes: (
Expand Down
16 changes: 16 additions & 0 deletions src/parseISODuration/benchmark.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// @flow
/* eslint-env mocha */
/* global suite, benchmark */

import parseISODuration from '.'
import moment from 'moment'

suite('parseISODuration', function() {
benchmark('date-fns', function() {
return parseISODuration('P2.2Y3.3M4.4DT5.5H6.6M7.7S')
})

benchmark('Moment.js', function() {
return moment.duration('P2.2Y3.3M4.4DT5.5H6.6M7.7S')
})
})
4 changes: 4 additions & 0 deletions src/parseISODuration/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// This file is generated automatically by `scripts/build/typings.js`. Please, don't change it.

import { parseISODuration } from 'date-fns'
export default parseISODuration
68 changes: 68 additions & 0 deletions src/parseISODuration/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import requiredArgs from '../_lib/requiredArgs/index'

var nr = '\\d+(?:[\\.,]\\d+)?'
var dateRegex = '(' + nr + 'Y)?(' + nr + 'M)?(' + nr + 'D)?'
var timeRegex = 'T(' + nr + 'H)?(' + nr + 'M)?(' + nr + 'S)?'
var durationRegex = new RegExp('P' + dateRegex + '(?:' + timeRegex + ')?')

/**
* @name parseISODuration
* @category Common Helpers
* @summary Parse ISO duration string
*
* @description
* Parse the given string in ISO 8601 duration format and return an instance of Duration.
*
* Function accepts complete ISO 8601 formats.
* ISO 8601: http://en.wikipedia.org/wiki/ISO_8601
*
* If the argument isn't a string, the function cannot parse the string or
* the values are invalid, it returns null.
*
* @param {String} argument - the value to convert
* @returns {Duration | null} the parsed duration or null
* @throws {TypeError} 1 argument required
*
* @example
* // Convert string 'P1DT5M30S' to duration:
* var result = parseISO('P1HT5M30S')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* var result = parseISO('P1HT5M30S')
* var result = parseISODuration('P1HT5M30S')

* //=> { days: 1, minutes: 5, seconds: 30 }
*/
export default function parseISODuration(argument) {
requiredArgs(1, arguments)

if (
!(
typeof argument === 'string' ||
Object.prototype.toString.call(argument) === '[object String]'
)
) {
return null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and everywhere below, I suggest returning an empty object instead of null. It will be more or less consistent with parse, which in similar cases returns Invalid Date or parseInt that return NaN. In all these cases, the returned value is of the expected type. I know that null is "an object," but that's just a bug.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return null
return {}

}

var match = argument.match(durationRegex)
if (!match) {
return null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return null
return {}

}

// at least one part must be specified
if (
!match[1] &&
!match[2] &&
!match[3] &&
!match[4] &&
!match[5] &&
!match[6]
) {
return null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return null
return {}

}

var duration = {}
if (match[1]) duration.years = parseFloat(match[1])
if (match[2]) duration.months = parseFloat(match[2])
if (match[3]) duration.days = parseFloat(match[3])
if (match[4]) duration.hours = parseFloat(match[4])
if (match[5]) duration.minutes = parseFloat(match[5])
if (match[6]) duration.seconds = parseFloat(match[6])
return duration
}
50 changes: 50 additions & 0 deletions src/parseISODuration/index.js.flow
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// @flow
// This file is generated automatically by `scripts/build/typings.js`. Please, don't change it.

export type Interval = {
start: Date | number,
end: Date | number
}

export type Locale = {
code?: string,
formatDistance?: (...args: Array<any>) => any,
formatRelative?: (...args: Array<any>) => any,
localize?: {
ordinalNumber: (...args: Array<any>) => any,
era: (...args: Array<any>) => any,
quarter: (...args: Array<any>) => any,
month: (...args: Array<any>) => any,
day: (...args: Array<any>) => any,
dayPeriod: (...args: Array<any>) => any
},
formatLong?: {
date: (...args: Array<any>) => any,
time: (...args: Array<any>) => any,
dateTime: (...args: Array<any>) => any
},
match?: {
ordinalNumber: (...args: Array<any>) => any,
era: (...args: Array<any>) => any,
quarter: (...args: Array<any>) => any,
month: (...args: Array<any>) => any,
day: (...args: Array<any>) => any,
dayPeriod: (...args: Array<any>) => any
},
options?: {
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6,
firstWeekContainsDate?: 1 | 2 | 3 | 4 | 5 | 6 | 7
}
}

export type Duration = {
years?: number,
months?: number,
weeks?: number,
days?: number,
hours?: number,
minutes?: number,
seconds?: number
}

declare module.exports: (argument: string) => Duration | null
80 changes: 80 additions & 0 deletions src/parseISODuration/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// @flow
/* eslint-env mocha */

import assert from 'power-assert'
import parseISODuration from '.'

describe('parseISODuration', () => {
describe('parsing', () => {
it('parses with all members', () => {
const result = parseISODuration('P2.2Y3.3M4.4DT5.5H6.6M7.7S')
assert.deepEqual(result, {
years: 2.2,
months: 3.3,
days: 4.4,
hours: 5.5,
minutes: 6.6,
seconds: 7.7
})
})

it('parses without time', () => {
const result = parseISODuration('P2Y3M5D')
assert.deepEqual(result, { years: 2, months: 3, days: 5 })
})

it('parses missing years without time', () => {
const result = parseISODuration('P3M5D')
assert.deepEqual(result, { months: 3, days: 5 })
})

it('parses missing months without time', () => {
const result = parseISODuration('P2Y5D')
assert.deepEqual(result, { years: 2, days: 5 })
})

it('parses missing days without time', () => {
const result = parseISODuration('P2Y3M')
assert.deepEqual(result, { years: 2, months: 3 })
})

it('parses without days', () => {
const result = parseISODuration('PT65H40M22S')
assert.deepEqual(result, { hours: 65, minutes: 40, seconds: 22 })
})

it('parses missing hours without date', () => {
const result = parseISODuration('PT3M4S')
assert.deepEqual(result, { minutes: 3, seconds: 4 })
})

it('parses missing minutes without date', () => {
const result = parseISODuration('PT2H4S')
assert.deepEqual(result, { hours: 2, seconds: 4 })
})

it('parses missing seconds without date', () => {
const result = parseISODuration('PT2H3M')
assert.deepEqual(result, { hours: 2, minutes: 3 })
})
})

describe('validation', () => {
it('returns `null` for invalid string', () => {
const result = parseISODuration('abcdef')
assert(result === null)
})
it('returns null when all parts are missing', () => {
const result = parseISODuration('P')
assert.deepEqual(result, null)
})
it('returns null for non-string', () => {
// $ExpectedMistake
const result = parseISODuration(150)
assert(result === null)
})
it('throws TypeError exception if passed less than 1 argument', () => {
assert.throws(parseISODuration.bind(null), TypeError)
})
})
})
Loading