Skip to content
Merged
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
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,9 @@
- Map, Set and typed array support
- Simple math expression evaluation
- Optional env variable expansion and function-string parsing
- Advanced features must be enabled individually via options
- Advanced features must be enabled individually via options

## 2.2.0 (2025-06-08)

- Built-in date/time recognition for ISO 8601 and common local formats
- New `parseDates` option to enable the feature
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ A small utility that automatically converts strings and other values into the mo
- Converts `Map:` and `Set:` strings into real objects
- Supports typed arrays
- Evaluates simple math expressions
- Recognizes common date/time formats
- Optional environment variable expansion
- Optional function-string parsing
- Advanced features are disabled by default and can be enabled individually
Expand Down Expand Up @@ -60,6 +61,7 @@ autoParse('yes', { booleanSynonyms: true }) // => true
autoParse('Map:[["a",1]]', { parseMapSets: true }).get('a') // => 1
autoParse('Uint8Array[1,2]', { parseTypedArrays: true })[0] // => 1
autoParse('2 + 3 * 4', { parseExpressions: true }) // => 14
autoParse('2023-06-01', { parseDates: true }) // => Date object
process.env.TEST_ENV = '123'
autoParse('$TEST_ENV', { expandEnv: true }) // => 123
const double = autoParse('x => x * 2', { parseFunctionStrings: true })
Expand Down Expand Up @@ -123,9 +125,10 @@ More examples can be found in the [`examples/`](examples) directory.
- `parseMapSets` – convert `Map:` and `Set:` strings.
- `parseTypedArrays` – support typed array notation.
- `parseExpressions` – evaluate simple math expressions.
- `parseDates` – recognize ISO 8601 and common local date/time strings.
- `currencySymbols` – object mapping extra currency symbols to codes, e.g. `{ 'r$': 'BRL', "\u20BA": 'TRY' }`.

## Benchmarks (v2.1.0)
## Benchmarks (v2.2.0)

The following timings are measured on Node.js using `npm test` and represent roughly how long it takes to parse 10 000 values after warm‑up:

Expand All @@ -139,6 +142,7 @@ The following timings are measured on Node.js using `npm test` and represent rou
| plain objects | ~3 |
| options combined | ~6 |
| plugin hook | ~4 |
| date/time parse | ~5 |

Even a single parse is extremely fast:

Expand All @@ -152,6 +156,7 @@ Even a single parse is extremely fast:
| plain objects | ~0.0003 |
| options combined | ~0.0006 |
| plugin hook | ~0.0004 |
| date/time parse | ~0.0005 |

These numbers demonstrate the parser runs in well under a millisecond for typical values, so performance should never be a concern.

Expand Down Expand Up @@ -186,6 +191,9 @@ strings, Map and Set objects, typed arrays, simple expression evaluation and
optional environment variable and function-string handling. See
[docs/RELEASE_NOTES_2.1.md](docs/RELEASE_NOTES_2.1.md) for details.

Version 2.2 introduces optional date/time recognition. See
[docs/RELEASE_NOTES_2.2.md](docs/RELEASE_NOTES_2.2.md) for details.

## Contributing

1. Fork the repository and create a branch for your feature or fix.
Expand Down
55 changes: 55 additions & 0 deletions dist/auto-parse.esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,56 @@ var require_auto_parse = __commonJS({
}
return null;
}
function parseDateTimeString(str) {
const iso = /^\d{4}-\d{2}-\d{2}(?:[ T]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
if (iso.test(str)) {
const d = new Date(str);
if (!Number.isNaN(d.getTime()))
return d;
}
let m = /^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:\s+(\d{1,2}):(\d{2})(?::(\d{2}))?(?:\s*([AP]M))?)?$/i.exec(str);
if (m) {
let [, month, day, year, h, min, sec, ap] = m;
const date = new Date(Number(year), Number(month) - 1, Number(day));
if (h !== void 0) {
h = Number(h);
if (ap) {
ap = ap.toLowerCase();
if (ap === "pm" && h < 12)
h += 12;
if (ap === "am" && h === 12)
h = 0;
}
date.setHours(h, Number(min), Number(sec || 0), 0);
}
return date;
}
m = /^(\d{1,2})-(\d{1,2})-(\d{4})(?:\s+(\d{1,2}):(\d{2})(?::(\d{2}))?)?$/.exec(str);
if (m) {
const [, day, month, year, h, min, sec] = m;
const date = new Date(Number(year), Number(month) - 1, Number(day));
if (h !== void 0) {
date.setHours(Number(h), Number(min), Number(sec || 0), 0);
}
return date;
}
m = /^(\d{1,2}):(\d{2})(?::(\d{2}))?(?:\s*([AP]M))?$/i.exec(str);
if (m) {
let [, h, min, sec, ap] = m;
h = Number(h);
if (ap) {
ap = ap.toLowerCase();
if (ap === "pm" && h < 12)
h += 12;
if (ap === "am" && h === 12)
h = 0;
}
const date = /* @__PURE__ */ new Date();
date.setHours(h, Number(min), Number(sec || 0), 0);
return date;
}
return null;
}
function parseExpressionString(str) {
if (/^[0-9+\-*/() %.]+$/.test(str) && /[+\-*/()%]/.test(str)) {
try {
Expand Down Expand Up @@ -411,6 +461,11 @@ var require_auto_parse = __commonJS({
if (fn)
return returnIfAllowed(fn, options, originalValue);
}
if (options.parseDates) {
const dt = parseDateTimeString(trimmed);
if (dt)
return returnIfAllowed(dt, options, originalValue);
}
value = stripTrimLower(trimmed, Object.assign({}, options, { stripStartChars: false }));
if (value === "undefined" || value === "") {
return returnIfAllowed(void 0, options, originalValue);
Expand Down
55 changes: 55 additions & 0 deletions dist/auto-parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,56 @@ function parseMapSetString(str, options) {
}
return null;
}
function parseDateTimeString(str) {
const iso = /^\d{4}-\d{2}-\d{2}(?:[ T]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
if (iso.test(str)) {
const d = new Date(str);
if (!Number.isNaN(d.getTime()))
return d;
}
let m = /^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:\s+(\d{1,2}):(\d{2})(?::(\d{2}))?(?:\s*([AP]M))?)?$/i.exec(str);
if (m) {
let [, month, day, year, h, min, sec, ap] = m;
const date = new Date(Number(year), Number(month) - 1, Number(day));
if (h !== void 0) {
h = Number(h);
if (ap) {
ap = ap.toLowerCase();
if (ap === "pm" && h < 12)
h += 12;
if (ap === "am" && h === 12)
h = 0;
}
date.setHours(h, Number(min), Number(sec || 0), 0);
}
return date;
}
m = /^(\d{1,2})-(\d{1,2})-(\d{4})(?:\s+(\d{1,2}):(\d{2})(?::(\d{2}))?)?$/.exec(str);
if (m) {
const [, day, month, year, h, min, sec] = m;
const date = new Date(Number(year), Number(month) - 1, Number(day));
if (h !== void 0) {
date.setHours(Number(h), Number(min), Number(sec || 0), 0);
}
return date;
}
m = /^(\d{1,2}):(\d{2})(?::(\d{2}))?(?:\s*([AP]M))?$/i.exec(str);
if (m) {
let [, h, min, sec, ap] = m;
h = Number(h);
if (ap) {
ap = ap.toLowerCase();
if (ap === "pm" && h < 12)
h += 12;
if (ap === "am" && h === 12)
h = 0;
}
const date = /* @__PURE__ */ new Date();
date.setHours(h, Number(min), Number(sec || 0), 0);
return date;
}
return null;
}
function parseExpressionString(str) {
if (/^[0-9+\-*/() %.]+$/.test(str) && /[+\-*/()%]/.test(str)) {
try {
Expand Down Expand Up @@ -404,6 +454,11 @@ function autoParse(value, typeOrOptions) {
if (fn)
return returnIfAllowed(fn, options, originalValue);
}
if (options.parseDates) {
const dt = parseDateTimeString(trimmed);
if (dt)
return returnIfAllowed(dt, options, originalValue);
}
value = stripTrimLower(trimmed, Object.assign({}, options, { stripStartChars: false }));
if (value === "undefined" || value === "") {
return returnIfAllowed(void 0, options, originalValue);
Expand Down
10 changes: 10 additions & 0 deletions docs/RELEASE_NOTES_2.2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Release Notes: Version 2.2

Version 2.2 adds optional date and time parsing to **auto-parse**.

- ISO 8601 strings like `2023-04-05T12:00:00Z` are recognized automatically.
- Localized formats such as `03/10/2020` or `10-03-2020 14:30` are supported.
- Standalone times like `8:45 PM` return `Date` objects for the current day.
- Enable via the `parseDates` option. It is disabled by default.

See the [CHANGELOG](../CHANGELOG.md) for a detailed history.
2 changes: 2 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ them with `node <file>` from this directory.
- `basic.js` showcases parsing of primitive values and objects.
- `plugin.js` illustrates registering a simple plugin.
- `types.js` covers advanced types like `BigInt` and `Symbol`.
- `dates.js` demonstrates the optional date/time parsing capability.
- `all-options.js` exercises every available option in one script.
24 changes: 24 additions & 0 deletions examples/all-options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const autoParse = require('..')

process.env.TEST_ENV = '123'

console.log('preserveLeadingZeros:', autoParse('0005', { preserveLeadingZeros: true }))
console.log('allowedTypes:', autoParse('42', { allowedTypes: ['string'] }))
console.log('stripStartChars:', autoParse('#5', { stripStartChars: '#' }))
console.log('parseCommaNumbers:', autoParse('1,234', { parseCommaNumbers: true }))
console.log('parseCurrency:', autoParse('$9.99', { parseCurrency: true }))
console.log('currencyAsObject:', autoParse('€9.99', { parseCurrency: true, currencyAsObject: true }))
console.log('currencySymbols:', autoParse('R$5', { parseCurrency: true, currencySymbols: { 'R$': 'BRL' } }))
console.log('parsePercent:', autoParse('85%', { parsePercent: true }))
console.log('percentAsObject:', autoParse('85%', { parsePercent: true, percentAsObject: true }))
console.log('parseUnits:', autoParse('10px', { parseUnits: true }))
console.log('parseRanges:', autoParse('1..3', { parseRanges: true }))
console.log('rangeAsObject:', autoParse('1..3', { parseRanges: true, rangeAsObject: true }))
console.log('booleanSynonyms:', autoParse('yes', { booleanSynonyms: true }))
console.log('parseMapSets:', autoParse('Map:[["a",1]]', { parseMapSets: true }))
console.log('parseTypedArrays:', autoParse('Uint8Array[1,2]', { parseTypedArrays: true }))
console.log('parseExpressions:', autoParse('2 + 3 * 4', { parseExpressions: true }))
console.log('parseDates:', autoParse('2023-06-01', { parseDates: true }))
console.log('expandEnv:', autoParse('$TEST_ENV', { expandEnv: true }))
console.log('parseFunctionStrings:', autoParse('x => x * 2', { parseFunctionStrings: true })(3))
console.log('type option:', autoParse('9007199254740991', { type: BigInt }))
5 changes: 5 additions & 0 deletions examples/dates.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const autoParse = require('..')

console.log('ISO:', autoParse('2023-06-01T12:00:00Z', { parseDates: true }))
console.log('US:', autoParse('03/10/2020 2:30 PM', { parseDates: true }))
console.log('Time:', autoParse('13:45', { parseDates: true }))
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface AutoParseOptions {
parseMapSets?: boolean;
parseTypedArrays?: boolean;
parseExpressions?: boolean;
parseDates?: boolean;
type?: any;
}
export type Parser = (value: any, type?: any, options?: AutoParseOptions) => any | undefined;
Expand Down
50 changes: 50 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,52 @@ function parseMapSetString (str, options) {
return null
}

function parseDateTimeString (str) {
const iso = /^\d{4}-\d{2}-\d{2}(?:[ T]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/
if (iso.test(str)) {
const d = new Date(str)
if (!Number.isNaN(d.getTime())) return d
}
let m = /^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:\s+(\d{1,2}):(\d{2})(?::(\d{2}))?(?:\s*([AP]M))?)?$/i.exec(str)
if (m) {
let [, month, day, year, h, min, sec, ap] = m
const date = new Date(Number(year), Number(month) - 1, Number(day))
if (h !== undefined) {
h = Number(h)
if (ap) {
ap = ap.toLowerCase()
if (ap === 'pm' && h < 12) h += 12
if (ap === 'am' && h === 12) h = 0
}
date.setHours(h, Number(min), Number(sec || 0), 0)
}
return date
}
m = /^(\d{1,2})-(\d{1,2})-(\d{4})(?:\s+(\d{1,2}):(\d{2})(?::(\d{2}))?)?$/.exec(str)
if (m) {
const [, day, month, year, h, min, sec] = m
const date = new Date(Number(year), Number(month) - 1, Number(day))
if (h !== undefined) {
date.setHours(Number(h), Number(min), Number(sec || 0), 0)
}
return date
}
m = /^(\d{1,2}):(\d{2})(?::(\d{2}))?(?:\s*([AP]M))?$/i.exec(str)
if (m) {
let [, h, min, sec, ap] = m
h = Number(h)
if (ap) {
ap = ap.toLowerCase()
if (ap === 'pm' && h < 12) h += 12
if (ap === 'am' && h === 12) h = 0
}
const date = new Date()
date.setHours(h, Number(min), Number(sec || 0), 0)
return date
}
return null
}

function parseExpressionString (str) {
if (/^[0-9+\-*/() %.]+$/.test(str) && /[+\-*/()%]/.test(str)) {
try {
Expand Down Expand Up @@ -480,6 +526,10 @@ function autoParse (value, typeOrOptions) {
const fn = parseFunctionString(trimmed)
if (fn) return returnIfAllowed(fn, options, originalValue)
}
if (options.parseDates) {
const dt = parseDateTimeString(trimmed)
if (dt) return returnIfAllowed(dt, options, originalValue)
}
value = stripTrimLower(trimmed, Object.assign({}, options, { stripStartChars: false }))
if (value === 'undefined' || value === '') {
return returnIfAllowed(undefined, options, originalValue)
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "auto-parse",
"version": "2.1.0",
"version": "2.2.0",
"description": "Automatically convert any value to its best matching JavaScript type. Supports numbers, booleans, objects, arrays, BigInt, Symbol, comma-separated numbers, prefix stripping, allowed type enforcement and a plugin API.",
"main": "index.js",
"types": "index.d.ts",
Expand Down
29 changes: 29 additions & 0 deletions test/date-time.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const autoParse = require('../index.js')
const { assert } = require('chai')

describe('Date/time parsing', function () {
it('parses ISO dates', function () {
const d = autoParse('2023-06-01T12:00:00Z', { parseDates: true })
assert.instanceOf(d, Date)
assert.strictEqual(d.toISOString(), '2023-06-01T12:00:00.000Z')
})

it('parses US dates', function () {
const d = autoParse('03/10/2020', { parseDates: true })
assert.instanceOf(d, Date)
assert.strictEqual(d.getFullYear(), 2020)
assert.strictEqual(d.getMonth(), 2)
assert.strictEqual(d.getDate(), 10)
})

it('parses time strings', function () {
const d = autoParse('13:45', { parseDates: true })
assert.instanceOf(d, Date)
assert.strictEqual(d.getHours(), 13)
assert.strictEqual(d.getMinutes(), 45)
})

it('defaults to string when disabled', function () {
assert.strictEqual(autoParse('2023-06-01'), '2023-06-01')
})
})
13 changes: 13 additions & 0 deletions test/performance.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,17 @@ describe('Performance', () => {
console.log('expression parse time', time)
expect(time).toBeLessThan(300)
})

test('date parse performance', () => {
for (let i = 0; i < 1000; i++) {
autoParse('2023-06-01T12:00:00Z', { parseDates: true })
}
const time = benchmark(() => {
for (let i = 0; i < 10000; i++) {
autoParse('2023-06-01T12:00:00Z', { parseDates: true })
}
})
console.log('date parse time', time)
expect(time).toBeLessThan(300)
})
})