-
Notifications
You must be signed in to change notification settings - Fork 935
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(parse-iso-date): Convert to TypeScript (#2080)
* test(parse-iso-date): Port over tests Adapted from: https://github.com/csnover/js-iso8601/blob/master/tests/test.js NOTE: Many of these tests do not currently pass because of modifications that have been made to parseIsoDate. Those changes include (but probably aren't limited to): - The regex has been modified to add more "?" conditionals to make the parsing more forgiving - Times that lack a time zone specifier are treated as local time * test(parse-iso-date): Adapt the tests for current impl All tests now pass with the current implementation of parseIsoDate. * refactor(parse-iso-date): Convert to TypeScript It was the only JS file remaining in the repo, it was also written in an old, difficult to read style of JavaScript The functionality remains identical.
- Loading branch information
Showing
4 changed files
with
315 additions
and
67 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
/** | ||
* This file is a modified version of the file from the following repository: | ||
* Date.parse with progressive enhancement for ISO 8601 <https://github.com/csnover/js-iso8601> | ||
* NON-CONFORMANT EDITION. | ||
* © 2011 Colin Snover <http://zetafleet.com> | ||
* Released under MIT license. | ||
*/ | ||
|
||
// prettier-ignore | ||
// 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ± 10 tzHH 11 tzmm | ||
const isoReg = /^(\d{4}|[+-]\d{6})(?:-?(\d{2})(?:-?(\d{2}))?)?(?:[ T]?(\d{2}):?(\d{2})(?::?(\d{2})(?:[,.](\d{1,}))?)?(?:(Z)|([+-])(\d{2})(?::?(\d{2}))?)?)?$/; | ||
|
||
function toNumber(str: string, defaultValue = 0) { | ||
return Number(str) || defaultValue; | ||
} | ||
|
||
export function parseIsoDate(date: string): number { | ||
const regexResult = isoReg.exec(date); | ||
if (!regexResult) return Date.parse ? Date.parse(date) : Number.NaN; | ||
|
||
// use of toNumber() avoids NaN timestamps caused by “undefined” | ||
// values being passed to Date constructor | ||
const struct = { | ||
year: toNumber(regexResult[1]), | ||
month: toNumber(regexResult[2], 1) - 1, | ||
day: toNumber(regexResult[3], 1), | ||
hour: toNumber(regexResult[4]), | ||
minute: toNumber(regexResult[5]), | ||
second: toNumber(regexResult[6]), | ||
millisecond: regexResult[7] | ||
? // allow arbitrary sub-second precision beyond milliseconds | ||
toNumber(regexResult[7].substring(0, 3)) | ||
: 0, | ||
z: regexResult[8] || undefined, | ||
plusMinus: regexResult[9] || undefined, | ||
hourOffset: toNumber(regexResult[10]), | ||
minuteOffset: toNumber(regexResult[11]), | ||
}; | ||
|
||
// timestamps without timezone identifiers should be considered local time | ||
if (struct.z === undefined && struct.plusMinus === undefined) { | ||
return new Date( | ||
struct.year, | ||
struct.month, | ||
struct.day, | ||
struct.hour, | ||
struct.minute, | ||
struct.second, | ||
struct.millisecond, | ||
).valueOf(); | ||
} | ||
|
||
let totalMinutesOffset = 0; | ||
if (struct.z !== 'Z' && struct.plusMinus !== undefined) { | ||
totalMinutesOffset = struct.hourOffset * 60 + struct.minuteOffset; | ||
if (struct.plusMinus === '+') totalMinutesOffset = 0 - totalMinutesOffset; | ||
} | ||
|
||
return Date.UTC( | ||
struct.year, | ||
struct.month, | ||
struct.day, | ||
struct.hour, | ||
struct.minute + totalMinutesOffset, | ||
struct.second, | ||
struct.millisecond, | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,245 @@ | ||
/** | ||
* This file is a modified version of the test file from the following repository: | ||
* Date.parse with progressive enhancement for ISO 8601 <https://github.com/csnover/js-iso8601> | ||
* NON-CONFORMANT EDITION. | ||
* © 2011 Colin Snover <http://zetafleet.com> | ||
* Released under MIT license. | ||
*/ | ||
|
||
import { parseIsoDate } from '../../src/util/parseIsoDate'; | ||
|
||
const sixHours = 6 * 60 * 60 * 1000; | ||
const sixHoursThirty = sixHours + 30 * 60 * 1000; | ||
const epochLocalTime = new Date(1970, 0, 1, 0, 0, 0, 0).valueOf(); | ||
|
||
describe('plain date (no time)', () => { | ||
describe('valid dates', () => { | ||
test('Unix epoch', () => { | ||
const result = parseIsoDate('1970-01-01'); | ||
expect(result).toBe(epochLocalTime); | ||
}); | ||
test('2001', () => { | ||
const result = parseIsoDate('2001'); | ||
const expected = new Date(2001, 0, 1, 0, 0, 0, 0).valueOf(); | ||
expect(result).toBe(expected); | ||
}); | ||
test('2001-02', () => { | ||
const result = parseIsoDate('2001-02'); | ||
const expected = new Date(2001, 1, 1, 0, 0, 0, 0).valueOf(); | ||
expect(result).toBe(expected); | ||
}); | ||
test('2001-02-03', () => { | ||
const result = parseIsoDate('2001-02-03'); | ||
const expected = new Date(2001, 1, 3, 0, 0, 0, 0).valueOf(); | ||
expect(result).toBe(expected); | ||
}); | ||
test('-002001', () => { | ||
const result = parseIsoDate('-002001'); | ||
const expected = new Date(-2001, 0, 1, 0, 0, 0, 0).valueOf(); | ||
expect(result).toBe(expected); | ||
}); | ||
test('-002001-02', () => { | ||
const result = parseIsoDate('-002001-02'); | ||
const expected = new Date(-2001, 1, 1, 0, 0, 0, 0).valueOf(); | ||
expect(result).toBe(expected); | ||
}); | ||
test('-002001-02-03', () => { | ||
const result = parseIsoDate('-002001-02-03'); | ||
const expected = new Date(-2001, 1, 3, 0, 0, 0, 0).valueOf(); | ||
expect(result).toBe(expected); | ||
}); | ||
test('+010000-02', () => { | ||
const result = parseIsoDate('+010000-02'); | ||
const expected = new Date(10000, 1, 1, 0, 0, 0, 0).valueOf(); | ||
expect(result).toBe(expected); | ||
}); | ||
test('+010000-02-03', () => { | ||
const result = parseIsoDate('+010000-02-03'); | ||
const expected = new Date(10000, 1, 3, 0, 0, 0, 0).valueOf(); | ||
expect(result).toBe(expected); | ||
}); | ||
test('-010000-02', () => { | ||
const result = parseIsoDate('-010000-02'); | ||
const expected = new Date(-10000, 1, 1, 0, 0, 0, 0).valueOf(); | ||
expect(result).toBe(expected); | ||
}); | ||
test('-010000-02-03', () => { | ||
const result = parseIsoDate('-010000-02-03'); | ||
const expected = new Date(-10000, 1, 3, 0, 0, 0, 0).valueOf(); | ||
expect(result).toBe(expected); | ||
}); | ||
}); | ||
|
||
describe('invalid dates', () => { | ||
test('invalid YYYY (non-digits)', () => { | ||
expect(parseIsoDate('asdf')).toBeNaN(); | ||
}); | ||
test('invalid YYYY-MM-DD (non-digits)', () => { | ||
expect(parseIsoDate('1970-as-df')).toBeNaN(); | ||
}); | ||
test('invalid YYYY-MM- (extra hyphen)', () => { | ||
expect(parseIsoDate('1970-01-')).toBe(epochLocalTime); | ||
}); | ||
test('invalid YYYY-MM-DD (missing hyphens)', () => { | ||
expect(parseIsoDate('19700101')).toBe(epochLocalTime); | ||
}); | ||
test('ambiguous YYYY-MM/YYYYYY (missing plus/minus or hyphen)', () => { | ||
expect(parseIsoDate('197001')).toBe(epochLocalTime); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('date-time', () => { | ||
describe('no time zone', () => { | ||
test('2001-02-03T04:05', () => { | ||
const result = parseIsoDate('2001-02-03T04:05'); | ||
const expected = new Date(2001, 1, 3, 4, 5, 0, 0).valueOf(); | ||
expect(result).toBe(expected); | ||
}); | ||
test('2001-02-03T04:05:06', () => { | ||
const result = parseIsoDate('2001-02-03T04:05:06'); | ||
const expected = new Date(2001, 1, 3, 4, 5, 6, 0).valueOf(); | ||
expect(result).toBe(expected); | ||
}); | ||
test('2001-02-03T04:05:06.007', () => { | ||
const result = parseIsoDate('2001-02-03T04:05:06.007'); | ||
const expected = new Date(2001, 1, 3, 4, 5, 6, 7).valueOf(); | ||
expect(result).toBe(expected); | ||
}); | ||
}); | ||
|
||
describe('Z time zone', () => { | ||
test('2001-02-03T04:05Z', () => { | ||
const result = parseIsoDate('2001-02-03T04:05Z'); | ||
const expected = Date.UTC(2001, 1, 3, 4, 5, 0, 0); | ||
expect(result).toBe(expected); | ||
}); | ||
test('2001-02-03T04:05:06Z', () => { | ||
const result = parseIsoDate('2001-02-03T04:05:06Z'); | ||
const expected = Date.UTC(2001, 1, 3, 4, 5, 6, 0); | ||
expect(result).toBe(expected); | ||
}); | ||
test('2001-02-03T04:05:06.007Z', () => { | ||
const result = parseIsoDate('2001-02-03T04:05:06.007Z'); | ||
const expected = Date.UTC(2001, 1, 3, 4, 5, 6, 7); | ||
expect(result).toBe(expected); | ||
}); | ||
}); | ||
|
||
describe('offset time zone', () => { | ||
test('2001-02-03T04:05-00:00', () => { | ||
const result = parseIsoDate('2001-02-03T04:05-00:00'); | ||
const expected = Date.UTC(2001, 1, 3, 4, 5, 0, 0); | ||
expect(result).toBe(expected); | ||
}); | ||
test('2001-02-03T04:05:06-00:00', () => { | ||
const result = parseIsoDate('2001-02-03T04:05:06-00:00'); | ||
const expected = Date.UTC(2001, 1, 3, 4, 5, 6, 0); | ||
expect(result).toBe(expected); | ||
}); | ||
test('2001-02-03T04:05:06.007-00:00', () => { | ||
const result = parseIsoDate('2001-02-03T04:05:06.007-00:00'); | ||
const expected = Date.UTC(2001, 1, 3, 4, 5, 6, 7); | ||
expect(result).toBe(expected); | ||
}); | ||
|
||
test('2001-02-03T04:05+00:00', () => { | ||
const result = parseIsoDate('2001-02-03T04:05+00:00'); | ||
const expected = Date.UTC(2001, 1, 3, 4, 5, 0, 0); | ||
expect(result).toBe(expected); | ||
}); | ||
test('2001-02-03T04:05:06+00:00', () => { | ||
const result = parseIsoDate('2001-02-03T04:05:06+00:00'); | ||
const expected = Date.UTC(2001, 1, 3, 4, 5, 6, 0); | ||
expect(result).toBe(expected); | ||
}); | ||
test('2001-02-03T04:05:06.007+00:00', () => { | ||
const result = parseIsoDate('2001-02-03T04:05:06.007+00:00'); | ||
const expected = Date.UTC(2001, 1, 3, 4, 5, 6, 7); | ||
expect(result).toBe(expected); | ||
}); | ||
|
||
test('2001-02-03T04:05-06:30', () => { | ||
const result = parseIsoDate('2001-02-03T04:05-06:30'); | ||
const expected = Date.UTC(2001, 1, 3, 4, 5, 0, 0) + sixHoursThirty; | ||
expect(result).toBe(expected); | ||
}); | ||
test('2001-02-03T04:05:06-06:30', () => { | ||
const result = parseIsoDate('2001-02-03T04:05:06-06:30'); | ||
const expected = Date.UTC(2001, 1, 3, 4, 5, 6, 0) + sixHoursThirty; | ||
expect(result).toBe(expected); | ||
}); | ||
test('2001-02-03T04:05:06.007-06:30', () => { | ||
const result = parseIsoDate('2001-02-03T04:05:06.007-06:30'); | ||
const expected = Date.UTC(2001, 1, 3, 4, 5, 6, 7) + sixHoursThirty; | ||
expect(result).toBe(expected); | ||
}); | ||
|
||
test('2001-02-03T04:05+06:30', () => { | ||
const result = parseIsoDate('2001-02-03T04:05+06:30'); | ||
const expected = Date.UTC(2001, 1, 3, 4, 5, 0, 0) - sixHoursThirty; | ||
expect(result).toBe(expected); | ||
}); | ||
test('2001-02-03T04:05:06+06:30', () => { | ||
const result = parseIsoDate('2001-02-03T04:05:06+06:30'); | ||
const expected = Date.UTC(2001, 1, 3, 4, 5, 6, 0) - sixHoursThirty; | ||
expect(result).toBe(expected); | ||
}); | ||
test('2001-02-03T04:05:06.007+06:30', () => { | ||
const result = parseIsoDate('2001-02-03T04:05:06.007+06:30'); | ||
const expected = Date.UTC(2001, 1, 3, 4, 5, 6, 7) - sixHoursThirty; | ||
expect(result).toBe(expected); | ||
}); | ||
}); | ||
|
||
describe('incomplete dates', () => { | ||
test('2001T04:05:06.007', () => { | ||
const result = parseIsoDate('2001T04:05:06.007'); | ||
const expected = new Date(2001, 0, 1, 4, 5, 6, 7).valueOf(); | ||
expect(result).toBe(expected); | ||
}); | ||
test('2001-02T04:05:06.007', () => { | ||
const result = parseIsoDate('2001-02T04:05:06.007'); | ||
const expected = new Date(2001, 1, 1, 4, 5, 6, 7).valueOf(); | ||
expect(result).toBe(expected); | ||
}); | ||
|
||
test('-010000T04:05', () => { | ||
const result = parseIsoDate('-010000T04:05'); | ||
const expected = new Date(-10000, 0, 1, 4, 5, 0, 0).valueOf(); | ||
expect(result).toBe(expected); | ||
}); | ||
test('-010000-02T04:05', () => { | ||
const result = parseIsoDate('-010000-02T04:05'); | ||
const expected = new Date(-10000, 1, 1, 4, 5, 0, 0).valueOf(); | ||
expect(result).toBe(expected); | ||
}); | ||
test('-010000-02-03T04:05', () => { | ||
const result = parseIsoDate('-010000-02-03T04:05'); | ||
const expected = new Date(-10000, 1, 3, 4, 5, 0, 0).valueOf(); | ||
expect(result).toBe(expected); | ||
}); | ||
}); | ||
|
||
describe('invalid date-times', () => { | ||
test('missing T', () => { | ||
expect(parseIsoDate('1970-01-01 00:00:00')).toBe(epochLocalTime); | ||
}); | ||
test('too many characters in millisecond part', () => { | ||
expect(parseIsoDate('1970-01-01T00:00:00.000000')).toBe(epochLocalTime); | ||
}); | ||
test('comma instead of dot', () => { | ||
expect(parseIsoDate('1970-01-01T00:00:00,000')).toBe(epochLocalTime); | ||
}); | ||
test('missing colon in timezone part', () => { | ||
const subject = '1970-01-01T00:00:00+0630'; | ||
expect(parseIsoDate(subject)).toBe(Date.parse(subject)); | ||
}); | ||
test('missing colon in time part', () => { | ||
expect(parseIsoDate('1970-01-01T0000')).toBe(epochLocalTime); | ||
}); | ||
test('msec with missing seconds', () => { | ||
expect(parseIsoDate('1970-01-01T00:00.000')).toBeNaN(); | ||
}); | ||
}); | ||
}); |