Comparing version 0.1.0 to 1.0.0
28
index.js
'use strict' | ||
const ExtDate = require('./src/date') | ||
const Year = require('./src/year') | ||
const Decade = require('./src/decade') | ||
const Century = require('./src/century') | ||
const Season = require('./src/season') | ||
const Interval = require('./src/interval') | ||
const List = require('./src/list') | ||
const Set = require('./src/set') | ||
const Bitmask = require('./src/bitmask') | ||
const types = require('./src/types') | ||
const { sample, gen } = require('./src/sample') | ||
const { sample } = require('./src/sample') | ||
const { parse } = require('./src/parser') | ||
const { assign, keys } = Object | ||
function edtf(...args) { | ||
if (!args.length) | ||
return new ExtDate() | ||
return new edtf.Date() | ||
if (args.length === 1 && typeof args[0] === 'object') | ||
return new (edtf[args[0].type] || ExtDate)(args[0]) | ||
return new (edtf[args[0].type] || edtf.Date)(args[0]) | ||
@@ -30,16 +23,7 @@ const res = parse(...args) | ||
module.exports = Object.assign(edtf, { | ||
Date: ExtDate, | ||
Year, | ||
Decade, | ||
Century, | ||
Season, | ||
Interval, | ||
List, | ||
Set, | ||
module.exports = assign(edtf, types, { | ||
Bitmask, | ||
parse, | ||
sample, | ||
gen, | ||
types | ||
types: keys(types) | ||
}) |
{ | ||
"name": "edtf", | ||
"version": "0.1.0", | ||
"version": "1.0.0", | ||
"description": "Extended Date Time Format (EDTF) / ISO 8601-2 Parser and Library", | ||
@@ -36,3 +36,3 @@ "main": "index.js", | ||
"dependencies": { | ||
"nearley": "^2.4.1", | ||
"nearley": "^2.5.0", | ||
"randexp": "^0.4.2" | ||
@@ -42,3 +42,3 @@ }, | ||
"chai": "^3.5.0", | ||
"eslint": "^2.9.0", | ||
"eslint": "^2.10.2", | ||
"istanbul": "^0.4.3", | ||
@@ -45,0 +45,0 @@ "mocha": "^2.4.5" |
260
README.md
@@ -15,25 +15,258 @@ # EDTF.js | ||
EDTF.js fully implements [EDTF](http://www.loc.gov/standards/datetime) | ||
levels 0, 1, and 2 as specified by WD 2016-02-16 of ISO 8601-2. | ||
levels 0, 1, and 2 as specified by WD 2016-02-16 of ISO 8601-2 with | ||
the following exceptions (as raised by the EDTF community): | ||
1. Symbols for unknown and open dates in intervals have been switched: | ||
`*` makes more sense to represent an open date because it is often | ||
used as a wildcard to match "all" or "everything". Also, when an | ||
interval is blank, it suggessts "incomplete" or "unknown". | ||
2. "Before or after" is redundant and has been removed. It is covered | ||
by "One of a Set", e.g., `[1760-12..]` which means "December 1760 | ||
or some later month". | ||
### ES6 | ||
EDTF.js is written in ES6 and therefore requires Node.js 6+ or a modern | ||
browser. For Node.js 4/5 use the appropriate `--harmony` flags as necessary. | ||
## Installation | ||
### Node.js | ||
$ npm install edtf | ||
EDTF.js is written in ES6 and therefore requires Node.js 6+. You should | ||
be able to use it in Node 4 or 5 when setting the appropriate | ||
`--harmony` flags or by using your favourite transpiler. | ||
### Browser | ||
EDTF.js was written for Node.js. While we don't currently provide a | ||
browser package, it should be easily possible to create one using | ||
browserify or similar tools. | ||
## Manual | ||
## Parser | ||
EDTF.js exports a top-level function which takes either a string | ||
(with optional parser constraints), a parse result, a regular or any | ||
of the extended date objects and returns a new, extended date object | ||
as appropriate. | ||
## Generator | ||
const edtf = require('edtf') | ||
## API | ||
edtf('2016-XX') #-> returns an edtf.Date | ||
edtf(new Date()) #-> returns an edtf.Date | ||
edtf('2016-04~/2016-05') #-> returns an edtf.Interval | ||
For a list of all types supported by EDTF.js see: | ||
edtf.types | ||
#-> ['Date', 'Year', 'Decade', 'Century', 'Season', 'Interval', 'List', 'Set'] | ||
Each type provides at least the following properties: the date's | ||
corresponding EDTF string, its minimal and maximal numeric value, | ||
its type, as well as its date part values. | ||
edtf('2016?').edtf #-> '2016?' | ||
edtf('2016-02').min #-> 1454284800000, i.e. 2016-02-01T00:00:00Z | ||
edtf('2016-02').max #-> 1456790399999, i.e. 2016-02-29T23:59:59Z | ||
edtf('2016-02-2X').min #-> 1455926400000, i.e. 2016-02-20T00:00:00Z | ||
edtf('2016-02-2X').max #-> 1456790399999, i.e. 2016-02-29T23:59:59Z | ||
edtf('[..2016,2017]').min #-> -Infinity | ||
edtf('[..2016,2017]').max #-> 1514764799999, i.e. 2017-12-31T23:59:59Z | ||
edtf('2016-34').min #-> 1459468800000, i.e. 2016-04-01T00:00:00Z | ||
edtf('2016-34').max #-> 1467331199999, i.e. 2016-06-30T23:59:59Z | ||
edtf('2016?-02').values #-> [2016, 1] | ||
edtf('2016-05').values #-> [2016, 4] | ||
A date's `min` value is also used as its primitive value for numeric | ||
coercion. Because this is the case for all EDTF.js classes, comparison | ||
semantics tend to align well with common-sense expectations -- but be | ||
careful, as always when type coercion is at play. | ||
### Unspecified, uncertain, and approximate dates | ||
EDTF.js keeps track of qualified dates using bitmasks. If you are interested | ||
in binary yes or no, you can always convert a bitmask's value to boolean. For | ||
more fine-grained information, the `edtf.Bitmask` class provides a convenient | ||
interface for accessing these states: | ||
edtf('2016-05?').uncertain.value #-> 63 / yes | ||
edtf('2016-05?').approximate.value #-> 0 / no | ||
edtf('2016-05?').uncertain.is('year') #-> 15 / yes | ||
edtf('2016-05?').uncertain.is('month') #-> 48 / yes | ||
edtf('2016-05?').uncertain.is('day') #-> 0 / no | ||
edtf('2016-?05').uncertain.is('year') #-> 0 / no | ||
edtf('2016-?05').uncertain.is('month') #-> 48 / yes | ||
edtf('201X-XX').unspecified.value #-> 56 / yes | ||
edtf('201X-XX').unspecified.is('year') #-> 8 / yes | ||
edtf('201X-XX').unspecified.is('month') #-> 48 / yes | ||
edtf('201X-XX').unspecified.is('day') #-> 0 / no | ||
Instead of `year`, `month`, and `day`, you can also query a string-based | ||
representation of the bitmask: | ||
edtf('201X-XX').unspecified.test('XXYYMMDD') #-> 0 / no | ||
edtf('201X-XX').unspecified.test('YYYXMMDD') #-> 8 / yes | ||
When printing qualified dates, EDTF.js will try to find an optimal | ||
rendtition of qualification symbols. For that reason, you can use EDTF.js | ||
to normalize EDTF strings: | ||
edtf('?2016-?05-31').edtf #-> 2016-05?-31 | ||
edtf('?2016-?05~-31').edtf #-> 2016-05%-31 | ||
Because every extended date object has a `min` and `max` value, you can | ||
always test if one date covers another one: | ||
edtf('2016-06/2016-09').covers(edtf('2016-08-24')) #-> true | ||
edtf('2016-06/2016-09').covers(edtf('2016-05-31')) #-> false | ||
Iteratable dates offer an `includes()` test as well which returns true | ||
only if a given date is part of the iteration. For example: | ||
edtf('2016-06/2016-09').includes(edtf('2016-08-24')) #-> false | ||
August 24, 2016 is covered by the interval '2016-06/2016-09' but is not | ||
included in it, because the interval has month precision and can be | ||
iterated as: | ||
[...edtf('2016-06/2016-09')] | ||
#-> [2016-06, 2016-07, 2016-08, 2016-09] | ||
### Enumerating dates | ||
EDTF.js dates offer iterators to help measure the duration between | ||
two dates. These iterators are dependent on a date's precision: | ||
edtf('2016').next() #-> 2017 | ||
edtf('2016-05').next() #-> 2016-06 | ||
edtf('2016-02-28').next() #-> 2016-02-29 | ||
edtf('2017-02-28').next() #-> 2017-03-01 | ||
Careful, if your date has no precision, next will return the next | ||
second! | ||
The generator `*between()` returns all the dates, by precision, | ||
between two dates; similarly, `*until()` returns all datesi in between | ||
and includes the two dates themselves. | ||
[...edtf('2016-05').between('2016-07')] | ||
#-> [2016-06] | ||
[...edtf('2016-05').until('2016-07')] | ||
#-> [2016-05, 2016-06, 2016-07] | ||
### Iterators | ||
The EDTF.js classes `Date`, `Interval`, `List`, and `Set` (lists model | ||
EDTF's 'multiple dates', while sets model 'one of a set') are iterable. | ||
Date's are iterable. | ||
[...edtf('2015/2018')] | ||
#-> [2015, 2016, 2017, 2018] | ||
Note that this also works with varying precisions: | ||
[...edtf('2015-10/2016')] | ||
#-> [2015-10, 2015-11, 2015-12, 2016-01, 2016-02, 2016] | ||
Consecutive dates in lists and sets are expanded during an iteration: | ||
[...edtf('{2015,2018..2020}')] | ||
#-> [2015, 2018, 2019, 2020] | ||
### Parser | ||
To use EDTF.js' date parser directly, call `edtf.parse()` with an input | ||
string and optional parser constraints. The parser will always return | ||
plain JavaScript objects which you can pass to `edtf()` for conversion | ||
to extended date object, or to your own post-processing. | ||
edtf.parse('2016-02') | ||
#-> { type: 'Date', level: 0, values: [2016, 1] } | ||
As you can see, the parser output includes the compatibility level of | ||
the date parsed; the values array contains the individual date parts | ||
in a format compatible with JavaScript's Date semantics (months are | ||
a zero-based index). | ||
Unspecified, uncertain, or approximate dates are returned as a numeric | ||
value representing a bitmask; refer to the `edtf.Bitmask` class for details. | ||
edtf.parse('2016?-~02') | ||
#-> { type: 'Date', level: 2, values: [2016, 1], uncertain: 15, approximate: 48 } | ||
edtf.parse('20XX-02') | ||
#-> { type: 'Date', level: 2, values: [2000, 1], unspecified: 12 } | ||
Note that unspecified date values will always the least possible value, | ||
e.g., '2000' for '20XX'. Note, also, that EDTF.js will not parse impossible | ||
unspecified dates. For instance, none of the following examples can be | ||
valid dates: | ||
edtf.parse('2016-02-3X') #-> A day in February cannot start with a 3 | ||
edtf.parse('2016-2X-XX') #-> There are only 12 months | ||
edtf.parse('2016-XX-32') #-> No month has 32 days | ||
Intervals, Sets, and Lists will contain their parsed constiutent dates in | ||
the values array: | ||
edtf.parse('2015/2016') | ||
#-> { type: 'Interval', level: 0, values: [{..}, {..}] } | ||
By passing `level` or `types` constraints to the parser, you can ensure | ||
EDTF.js will accept only dates supported by your application. | ||
edtf.parse('2016?', { level: 0 }) #-> parse error | ||
edtf.parse('2016?', { level: 1 }) #-> ok | ||
edtf.parse('2016?-02', { level: 1 }) #-> parse error | ||
edtf.parse('2016?-02', { level: 2 }) #-> ok | ||
edtf.parse('2016-21', { types: ['Date'] }) #-> parse error | ||
edtf.parse('2016-21', { types: ['Date', 'Season'] }) #-> ok | ||
edtf.parse('2016?', { level: 0, types: ['Date'] }) #-> parse error | ||
edtf.parse('2016?', { level: 1, types: ['Date'] }) #-> ok | ||
### Generator | ||
EDTF.js can generate random EDTF strings for you. Simply call | ||
`edtf.sample()` to create a new iterator: | ||
let it = edtf.sample() | ||
it.next() #-> { value: '0097-26', done: false } | ||
it.next() #-> { value: '0000-09-30T22:50:54-07', done: false } | ||
... | ||
For a finite iterator, simply pass a count: | ||
[...edtf.sample({ count: 3 }] | ||
#-> ['-003%', '-0070-07-31%', '[-0080-10..]'] | ||
You can also generate strings at a given compatibility level: | ||
[...edtf.sample({ count: 3, level: 0 }] | ||
#-> ['0305/0070-04-30', '-07', '0000/0013'] | ||
[...edtf.sample({ count: 3, level: 1 }] | ||
#-> ['00XX', 'Y80105', '0000~'] | ||
[...edtf.sample({ count: 3, level: 2 }] | ||
#-> ['Y1E30', '-8110S2', '{%0401}'] | ||
Note that some grammar rules at levels 1 and 2 may, potentially, | ||
generate strings at a lower level (but never higher). | ||
Finally, at each level you can also limit the generated strings | ||
to a given type (you must specify a level for this to work): | ||
[...edtf.sample({ count: 3, level: 2, type: 'Decade' }] | ||
#-> ['003', '030~', '000'] | ||
## Credits | ||
@@ -47,3 +280,2 @@ The EDTF.js parser is based on the awesome | ||
## License | ||
AGPL-3.0 |
'use strict' | ||
const assert = require('assert') | ||
const { parse } = require('./parser') | ||
const ExtDate = require('./date') | ||
const ExtDateTime = require('./interface') | ||
const { abs, floor } = Math | ||
@@ -9,12 +10,6 @@ | ||
class Century { | ||
static parse(input) { | ||
return parse(input, { types: ['Century'] }) | ||
} | ||
class Century extends ExtDateTime { | ||
constructor(input) { | ||
super() | ||
static from(input) { | ||
return (input instanceof Century) ? input : new Century(input) | ||
} | ||
constructor(input) { | ||
V.set(this, []) | ||
@@ -60,6 +55,2 @@ | ||
get type() { | ||
return 'Century' | ||
} | ||
get century() { | ||
@@ -87,12 +78,8 @@ return this.values[0] | ||
get edtf() { | ||
return this.toEDTF() | ||
} | ||
get min() { | ||
return Date.UTC(this.year, 0) | ||
return ExtDate.UTC(this.year, 0) | ||
} | ||
get max() { | ||
return Date.UTC(this.year + 99, 11, 31, 24, 0, 0) | ||
return ExtDate.UTC(this.year + 100, 0) - 1 | ||
} | ||
@@ -99,0 +86,0 @@ |
@@ -6,3 +6,5 @@ 'use strict' | ||
const Bitmask = require('./bitmask') | ||
const { parse } = require('./parser') | ||
const ExtDateTime = require('./interface') | ||
const mixin = require('./mixin') | ||
const { abs } = Math | ||
@@ -16,13 +18,5 @@ const { isArray } = Array | ||
const PM = [Bitmask.YMD, Bitmask.Y, Bitmask.YM, Bitmask.YMD] | ||
class ExtDate extends Date { | ||
static parse(input) { | ||
return parse(input, { types: ['Date'] }) | ||
} | ||
static from(input) { | ||
return (input instanceof ExtDate) ? input : new ExtDate(input) | ||
} | ||
class Date extends global.Date { | ||
constructor(...args) { // eslint-disable-line complexity | ||
@@ -42,3 +36,3 @@ let precision = 0 | ||
case 'string': | ||
args = [ExtDate.parse(args[0])] | ||
args = [Date.parse(args[0])] | ||
// eslint-disable-line no-fallthrough | ||
@@ -72,8 +66,3 @@ | ||
args = [Date.UTC(...args)] | ||
// ECMA Date constructor converts 0-99 to 1900-1999! | ||
if (obj.values[0] >= 0 && obj.values[0] < 100) | ||
args[0] = adj(new Date(args[0])) | ||
args = [ExtDateTime.UTC(...args)] | ||
} | ||
@@ -114,3 +103,3 @@ | ||
set uncertain(value) { | ||
U.set(this, new Bitmask(value)) | ||
U.set(this, this.bits(value)) | ||
} | ||
@@ -123,3 +112,3 @@ | ||
set approximate(value) { | ||
A.set(this, new Bitmask(value)) | ||
A.set(this, this.bits(value)) | ||
} | ||
@@ -139,15 +128,14 @@ | ||
get type() { | ||
return 'Date' | ||
} | ||
get min() { | ||
// TODO uncertain and approximate | ||
get edtf() { | ||
return this.toEDTF() | ||
} | ||
get min() { | ||
return this.getTime() | ||
} | ||
get max() { // todo | ||
get max() { | ||
// TODO unspecified | ||
// TODO uncertain and approximate | ||
if (this.precision) return this.next() - 1 | ||
return this.getTime() | ||
@@ -195,9 +183,14 @@ } | ||
/** | ||
* Returns the next second, day, month, or year, depending on | ||
* the current date's precision. Uncertain, approximate and | ||
* unspecified masks are copied. | ||
*/ | ||
next(k = 1) { | ||
let { values, unspecified, uncertain, approximate } = this | ||
values = values.slice(0, 3) | ||
// values = values.slice(0, 3) | ||
values.push(values.pop() + k) | ||
return new ExtDate({ values, unspecified, uncertain, approximate }) | ||
return new Date({ values, unspecified, uncertain, approximate }) | ||
} | ||
@@ -219,10 +212,12 @@ | ||
*between(then) { | ||
then = Date.from(then) | ||
let cur = this | ||
let dir = this.compare(then) | ||
if (!dir) return | ||
for (;;) { | ||
cur = cur.next(-dir) | ||
dir = cur.compare(then) | ||
if (!dir) break | ||
if (cur.compare(then) !== dir) break | ||
yield cur | ||
@@ -232,12 +227,9 @@ } | ||
compare(other) { | ||
let [a, x, b, y] = [this.min, this.max, other.min, other.max] | ||
*[Symbol.iterator]() { | ||
let cur = this | ||
if (a !== b) | ||
return a < b ? -1 : 1 | ||
if (x !== y) | ||
return x < y ? -1 : 1 | ||
return 0 | ||
while (cur <= this.max) { | ||
yield cur | ||
cur = cur.next() | ||
} | ||
} | ||
@@ -248,3 +240,3 @@ | ||
let values = this.values.map(ExtDate.pad) | ||
let values = this.values.map(Date.pad) | ||
@@ -265,6 +257,2 @@ if (this.unspecified.value) | ||
[Symbol.toPrimitive](hint) { | ||
return (hint === 'number') ? this.valueOf() : this.toISOString() | ||
} | ||
static pad(number, idx = 0) { // idx 0 = year, 1 = month, ... | ||
@@ -287,11 +275,12 @@ if (!idx) { | ||
bits(value) { | ||
if (value === true) | ||
value = PM[this.precision] | ||
return new Bitmask(value) | ||
} | ||
} | ||
ExtDate.prototype.toJSON = ExtDate.prototype.toEDTF | ||
mixin(Date, ExtDateTime) | ||
function adj(date, by = 1900) { | ||
date.setUTCFullYear(date.getUTCFullYear() - by) | ||
return date.getTime() | ||
} | ||
module.exports = ExtDate | ||
module.exports = Date |
'use strict' | ||
const assert = require('assert') | ||
const { parse } = require('./parser') | ||
const ExtDate = require('./date') | ||
const ExtDateTime = require('./interface') | ||
const { abs, floor } = Math | ||
@@ -10,12 +11,6 @@ | ||
class Decade { | ||
static parse(input) { | ||
return parse(input, { types: ['Decade'] }) | ||
} | ||
class Decade extends ExtDateTime { | ||
constructor(input) { | ||
super() | ||
static from(input) { | ||
return (input instanceof Decade) ? input : new Decade(input) | ||
} | ||
constructor(input) { | ||
V.set(this, []) | ||
@@ -61,6 +56,2 @@ | ||
get type() { | ||
return 'Decade' | ||
} | ||
get decade() { | ||
@@ -88,12 +79,8 @@ return this.values[0] | ||
get edtf() { | ||
return this.toEDTF() | ||
} | ||
get min() { | ||
return Date.UTC(this.year, 0) | ||
return ExtDate.UTC(this.year, 0) | ||
} | ||
get max() { | ||
return Date.UTC(this.year + 9, 11, 31, 24, 0, 0) | ||
return ExtDate.UTC(this.year + 10, 0) - 1 | ||
} | ||
@@ -100,0 +87,0 @@ |
'use strict' | ||
const assert = require('assert') | ||
const { parse } = require('./parser') | ||
const ExtDate = require('./date') | ||
const ExtDateTime = require('./interface') | ||
@@ -9,12 +10,6 @@ const V = new WeakMap() | ||
class Interval { | ||
static parse(input) { | ||
return parse(input, { types: ['Interval'] }) | ||
} | ||
class Interval extends ExtDateTime { | ||
constructor(...args) { | ||
super() | ||
static from(input) { | ||
return (input instanceof Interval) ? input : new Interval(input) | ||
} | ||
constructor(...args) { | ||
V.set(this, [null, null]) | ||
@@ -47,2 +42,5 @@ | ||
;[this.lower, this.upper] = obj.values | ||
this.earlier = obj.earlier | ||
this.later = obj.later | ||
} | ||
@@ -64,7 +62,2 @@ break | ||
get type() { | ||
return 'Interval' | ||
} | ||
get lower() { | ||
@@ -75,3 +68,14 @@ return this.values[0] | ||
set lower(value) { | ||
this.values[0] = value // todo | ||
if (value == null) | ||
return this.values[1] = null | ||
if (value === Infinity || value === -Infinity) | ||
return this.values[1] = Infinity | ||
value = ExtDate.from(value) | ||
if (value >= this.upper && this.upper != null) | ||
throw new RangeError(`invalid lower bound: ${value}`) | ||
this.values[0] = value | ||
} | ||
@@ -84,25 +88,38 @@ | ||
set upper(value) { | ||
this.values[1] = value // todo | ||
} | ||
if (value == null) | ||
return this.values[1] = null | ||
get values() { | ||
return V.get(this) | ||
if (value === Infinity) | ||
return this.values[1] = Infinity | ||
value = ExtDate.from(value) | ||
if (value <= this.lower) | ||
throw new RangeError(`invalid upper bound: ${value}`) | ||
this.values[1] = value | ||
} | ||
get earlier() { | ||
get finite() { | ||
return (this.lower != null && this.lower !== Infinity) && | ||
(this.upper != null && this.upper !== Infinity) | ||
} | ||
get later() { | ||
*[Symbol.iterator]() { | ||
if (!this.finite) throw Error('cannot iterate infinite interval') | ||
yield* this.lower.until(this.upper) | ||
} | ||
get edtf() { | ||
return this.toEDTF() | ||
get values() { | ||
return V.get(this) | ||
} | ||
get min() { | ||
return this.lower.min | ||
let v = this.lower | ||
return !v ? null : (v === Infinity) ? -Infinity : v.min | ||
} | ||
get max() { | ||
return this.upper.max | ||
let v = this.upper | ||
return !v ? null : (v === Infinity) ? Infinity : v.max | ||
} | ||
@@ -109,0 +126,0 @@ |
'use strict' | ||
const assert = require('assert') | ||
const ExtDate = require('./date') | ||
const { parse } = require('./parser') | ||
const Date = require('./date') | ||
const ExtDateTime = require('./interface') | ||
const { isArray } = Array | ||
@@ -11,12 +11,6 @@ | ||
class List { | ||
static parse(input) { | ||
return parse(input, { types: ['List'] }) | ||
} | ||
class List extends ExtDateTime { | ||
constructor(...args) { | ||
super() | ||
static from(input) { | ||
return (input instanceof this) ? input : new this(input) | ||
} | ||
constructor(...args) { | ||
V.set(this, []) | ||
@@ -29,3 +23,3 @@ | ||
case 'string': | ||
args[0] = this.parse(args[0]) | ||
args[0] = new.target.parse(args[0]) | ||
// eslint-disable-line no-fallthrough | ||
@@ -54,6 +48,2 @@ | ||
get type() { | ||
return 'List' | ||
} | ||
get values() { | ||
@@ -93,6 +83,6 @@ return V.get(this) | ||
assert.equal(2, value.length) | ||
return this.values.push(value.map(ExtDate.from)) | ||
return this.values.push(value.map(v => Date.from(v))) | ||
} | ||
return this.values.push(ExtDate.from(value)) | ||
return this.values.push(Date.from(value)) | ||
} | ||
@@ -109,6 +99,2 @@ | ||
get edtf() { | ||
return this.toEDTF() | ||
} | ||
get min() { | ||
@@ -115,0 +101,0 @@ return this.empty ? 0 : this.first.min |
@@ -11,4 +11,6 @@ /* | ||
const randexp = require('randexp') | ||
const types = Object.keys(require('./types')) | ||
const { ParserRules: Rules } = require('./grammar') | ||
const types = require('./types') | ||
const { floor, random } = Math | ||
@@ -30,3 +32,3 @@ const NAMES = [ | ||
*sample({ count, level, type }) { | ||
*sample({ count, level, type } = {}) { | ||
let name = 'edtf' | ||
@@ -66,3 +68,3 @@ | ||
let sample = rules[ | ||
Math.floor(Math.random() * rules.length) | ||
floor(random() * rules.length) | ||
] | ||
@@ -89,4 +91,6 @@ | ||
return output.join('') | ||
return output | ||
.join('') | ||
.replace(/ +/g, '') // filter excessive whitespace | ||
} | ||
} |
'use strict' | ||
const assert = require('assert') | ||
const { parse } = require('./parser') | ||
const ExtDateTime = require('./interface') | ||
const { pad } = require('./date') | ||
@@ -9,12 +9,6 @@ | ||
class Season { | ||
static parse(input) { | ||
return parse(input, { types: ['Season'] }) | ||
} | ||
class Season extends ExtDateTime { | ||
constructor(input) { | ||
super() | ||
static from(input) { | ||
return (input instanceof Season) ? input : new Season(input) | ||
} | ||
constructor(input) { | ||
V.set(this, []) | ||
@@ -58,6 +52,2 @@ | ||
get type() { | ||
return 'Season' | ||
} | ||
get year() { | ||
@@ -83,12 +73,82 @@ return this.values[0] | ||
get edtf() { | ||
return this.toEDTF() | ||
} | ||
// TODO next/prev | ||
get min() { | ||
return Date.UTC(this.year, 0) | ||
get min() { // eslint-disable-line complexity | ||
switch (this.season) { | ||
case 21: | ||
case 25: | ||
case 32: | ||
case 33: | ||
case 40: | ||
case 37: | ||
return ExtDateTime.UTC(this.year, 0) | ||
case 22: | ||
case 26: | ||
case 31: | ||
case 34: | ||
return ExtDateTime.UTC(this.year, 3) | ||
case 23: | ||
case 27: | ||
case 30: | ||
case 35: | ||
case 41: | ||
return ExtDateTime.UTC(this.year, 6) | ||
case 24: | ||
case 28: | ||
case 29: | ||
case 36: | ||
return ExtDateTime.UTC(this.year, 9) | ||
case 38: | ||
return ExtDateTime.UTC(this.year, 4) | ||
case 39: | ||
return ExtDateTime.UTC(this.year, 8) | ||
default: | ||
return ExtDateTime.UTC(this.year, 0) | ||
} | ||
} | ||
get max() { | ||
return Date.UTC(this.year, 11, 31, 24, 0, 0) | ||
get max() { // eslint-disable-line complexity | ||
switch (this.season) { | ||
case 21: | ||
case 25: | ||
case 32: | ||
case 33: | ||
return ExtDateTime.UTC(this.year, 3) - 1 | ||
case 22: | ||
case 26: | ||
case 31: | ||
case 34: | ||
case 40: | ||
return ExtDateTime.UTC(this.year, 6) - 1 | ||
case 23: | ||
case 27: | ||
case 30: | ||
case 35: | ||
return ExtDateTime.UTC(this.year, 9) - 1 | ||
case 24: | ||
case 28: | ||
case 29: | ||
case 36: | ||
case 41: | ||
case 39: | ||
return ExtDateTime.UTC(this.year + 1, 0) - 1 | ||
case 37: | ||
return ExtDateTime.UTC(this.year, 5) - 1 | ||
case 38: | ||
return ExtDateTime.UTC(this.year, 9) - 1 | ||
default: | ||
return ExtDateTime.UTC(this.year + 1, 0) - 1 | ||
} | ||
} | ||
@@ -95,0 +155,0 @@ |
'use strict' | ||
module.exports = [ | ||
'Date', 'Year', 'Season', 'Interval', 'Set', 'List', 'Century', 'Decade' | ||
] | ||
module.exports = { | ||
Date: require('./date'), | ||
Year: require('./year'), | ||
Decade: require('./decade'), | ||
Century: require('./century'), | ||
Season: require('./season'), | ||
Interval: require('./interval'), | ||
List: require('./list'), | ||
Set: require('./set') | ||
} |
'use strict' | ||
const assert = require('assert') | ||
const { parse } = require('./parser') | ||
const { pad } = require('./date') | ||
const ExtDate = require('./date') | ||
const ExtDateTime = require('./interface') | ||
const { pad } = ExtDate | ||
const { abs } = Math | ||
@@ -11,8 +12,6 @@ | ||
class Year { | ||
static parse(input) { | ||
return parse(input, { types: ['Year'] }) | ||
} | ||
class Year extends ExtDateTime { | ||
constructor(input) { | ||
super() | ||
constructor(input) { | ||
V.set(this, []) | ||
@@ -54,6 +53,2 @@ | ||
get type() { | ||
return 'Year' | ||
} | ||
get year() { | ||
@@ -79,12 +74,8 @@ return this.values[0] | ||
get edtf() { | ||
return this.toEDTF() | ||
} | ||
get min() { | ||
return Date.UTC(this.year, 0) | ||
return ExtDateTime.UTC(this.year, 0) | ||
} | ||
get max() { | ||
return Date.UTC(this.year, 11, 31, 24, 0, 0) | ||
return ExtDateTime.UTC(this.year + 1, 0) - 1 | ||
} | ||
@@ -91,0 +82,0 @@ |
@@ -26,2 +26,14 @@ 'use strict' | ||
}) | ||
describe('bounds', () => { | ||
it('min', () => { | ||
expect(new Century(20).min) | ||
.to.eql(Date.UTC(2000, 0, 1, 0, 0, 0, 0)) | ||
}) | ||
it('max', () => { | ||
expect(new Century(20).max) | ||
.to.eql(Date.UTC(2099, 11, 31, 23, 59, 59, 999)) | ||
}) | ||
}) | ||
}) |
@@ -7,2 +7,11 @@ 'use strict' | ||
it('.type', () => { | ||
expect(Date.type).to.eql('Date') | ||
expect(new Date().type).to.eql('Date') | ||
}) | ||
it('.from()', () => { | ||
expect(Date.from('2016')).to.be.instanceof(Date) | ||
}) | ||
describe('constructor()', () => { | ||
@@ -95,2 +104,28 @@ it('Date', () => | ||
describe('max', () => { | ||
it('full precision', () => { | ||
let date = new Date() | ||
expect(date.max).to.eql(date.min) | ||
}) | ||
it('YYYY', () => { | ||
expect(new Date([2016]).max) | ||
.to.eql(global.Date.UTC(2016, 11, 31, 23, 59, 59, 999)) | ||
}) | ||
it('YYYY-MM', () => { | ||
expect(new Date([2016, 1]).max) | ||
.to.eql(global.Date.UTC(2016, 1, 29, 23, 59, 59, 999)) | ||
expect(new Date([2017, 1]).max) | ||
.to.eql(global.Date.UTC(2017, 1, 28, 23, 59, 59, 999)) | ||
expect(new Date([2016, 7]).max) | ||
.to.eql(global.Date.UTC(2016, 7, 31, 23, 59, 59, 999)) | ||
}) | ||
it('YYYY-MM-DD', () => { | ||
expect(new Date([2016, 1, 1]).max) | ||
.to.eql(global.Date.UTC(2016, 1, 1, 23, 59, 59, 999)) | ||
}) | ||
}) | ||
describe('.edtf', () => { | ||
@@ -97,0 +132,0 @@ it('default', () => |
@@ -7,2 +7,14 @@ 'use strict' | ||
describe('bounds', () => { | ||
it('min', () => { | ||
expect(new Decade(199).min) | ||
.to.eql(Date.UTC(1990, 0, 1, 0, 0, 0, 0)) | ||
}) | ||
it('max', () => { | ||
expect(new Decade(199).max) | ||
.to.eql(Date.UTC(1999, 11, 31, 23, 59, 59, 999)) | ||
}) | ||
}) | ||
describe('.edtf', () => { | ||
@@ -9,0 +21,0 @@ it('default', () => |
@@ -33,2 +33,10 @@ 'use strict' | ||
}) | ||
it('roundtrips', () => { | ||
for (let string of [ | ||
'2016', '2016-05', '2016-05-31', '2016?', '2016-05?', | ||
'2016-05-31?', '2016~-05?', '2016-~05' | ||
]) expect(edtf(string).edtf).to.eql(string) | ||
}) | ||
}) |
'use strict' | ||
const { Interval } = require('..') | ||
const { Interval, Date } = require('..') | ||
describe('Interval', () => { | ||
it('.type', () => { | ||
expect(Interval.type).to.eql('Interval') | ||
expect(new Interval().type).to.eql('Interval') | ||
}) | ||
it('.from()', () => { | ||
expect(Interval.from('2016/2017')).to.be.instanceof(Interval) | ||
}) | ||
describe('bounds', () => { | ||
it('min', () => { | ||
expect(new Interval([2001], [2003]).min) | ||
.to.eql(Date.UTC(2001, 0, 1, 0, 0, 0, 0)) | ||
}) | ||
it('max', () => { | ||
expect(new Interval([2001], [2003]).max) | ||
.to.eql(Date.UTC(2003, 11, 31, 23, 59, 59, 999)) | ||
}) | ||
}) | ||
describe('invalid', () => { | ||
it('bounds', () => { | ||
expect(() => new Interval([2001], [2000])).to.throw(RangeError) | ||
}) | ||
}) | ||
describe('iteration', () => { | ||
const Q1_94 = new Interval([1994, 0], [1994, 2]) | ||
const FEB_94 = new Date(1994, 1) | ||
const FEB1_94 = new Date(1994, 1, 1) | ||
const YEAR_94 = new Date([1994]) | ||
it('@@iterator', () => { | ||
expect([...new Interval([2001], [2003])].map(v => v.edtf)) | ||
.to.eql(['2001', '2002', '2003']) | ||
expect([...Q1_94].map(v => v.edtf)) | ||
.to.eql(['1994-01', '1994-02', '1994-03']) | ||
}) | ||
it('covers', () => { | ||
expect(Q1_94.covers(FEB_94)).to.be.true | ||
expect(Q1_94.covers(FEB1_94)).to.be.true | ||
expect(Q1_94.covers(YEAR_94)).not.to.be.true | ||
}) | ||
it('includes', () => { | ||
expect(Q1_94.includes(FEB_94)).to.be.true | ||
expect(Q1_94.includes(FEB1_94)).not.to.be.true | ||
expect(Q1_94.includes(YEAR_94)).not.to.be.true | ||
}) | ||
}) | ||
describe('.edtf', () => { | ||
@@ -8,0 +62,0 @@ it('default', () => |
@@ -546,3 +546,8 @@ 'use strict' | ||
}) | ||
describe('rejects', () => { | ||
it('empty string', () => expect(() => p('')).to.be.rejected) | ||
it('-', () => expect(() => p('-')).to.be.rejected) | ||
}) | ||
}) | ||
'use strict' | ||
const { gen, sample, parse: p } = require('..') | ||
const { sample, parse: p } = require('..') | ||
@@ -8,9 +8,5 @@ describe('sample', () => { | ||
it('iterator', () => { | ||
expect(Array.from(sample({ count: 5 }))).to.have.length(5) | ||
expect([...sample({ count: 5 })]).to.have.length(5) | ||
}) | ||
it('dates', () => { | ||
expect(p(gen('date'))).to.have.type('Date') | ||
}) | ||
describe('constraints', () => { | ||
@@ -17,0 +13,0 @@ it('level 0', () => |
@@ -7,2 +7,18 @@ 'use strict' | ||
describe('bounds', () => { | ||
it('min', () => { | ||
expect(new Season(2016, 33).min) | ||
.to.eql(Date.UTC(2016, 0, 1, 0, 0, 0, 0)) | ||
expect(new Season(2016, 34).min) | ||
.to.eql(Date.UTC(2016, 3, 1, 0, 0, 0, 0)) | ||
}) | ||
it('max', () => { | ||
expect(new Season(2016, 33).max) | ||
.to.eql(Date.UTC(2016, 2, 31, 23, 59, 59, 999)) | ||
expect(new Season(2016, 34).max) | ||
.to.eql(Date.UTC(2016, 5, 30, 23, 59, 59, 999)) | ||
}) | ||
}) | ||
describe('.edtf', () => { | ||
@@ -9,0 +25,0 @@ it('default', () => |
@@ -7,2 +7,11 @@ 'use strict' | ||
it('type', () => { | ||
expect(Set.type).to.eql('Set') | ||
expect(new Set().type).to.eql('Set') | ||
}) | ||
it('.from()', () => { | ||
expect(Set.from('[2016]')).to.be.instanceof(Set) | ||
}) | ||
describe('.edtf', () => { | ||
@@ -9,0 +18,0 @@ it('default', () => |
@@ -7,2 +7,23 @@ 'use strict' | ||
it('type', () => { | ||
expect(Year.type).to.eql('Year') | ||
expect(new Year().type).to.eql('Year') | ||
}) | ||
it('.from()', () => { | ||
expect(Year.from('Y22016')).to.be.instanceof(Year) | ||
}) | ||
describe('bounds', () => { | ||
it('min', () => { | ||
expect(new Year(-1).min) | ||
.to.eql(Date.UTC(-1, 0, 1, 0, 0, 0, 0)) | ||
}) | ||
it('max', () => { | ||
expect(new Year(-1).max) | ||
.to.eql(Date.UTC(-1, 11, 31, 23, 59, 59, 999)) | ||
}) | ||
}) | ||
describe('.edtf', () => { | ||
@@ -9,0 +30,0 @@ it('default', () => |
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
169830
43
2837
0
280
Updatednearley@^2.5.0