From 17265c2d6e7a392b08edb8343d364c5e31994928 Mon Sep 17 00:00:00 2001 From: recursivefunk Date: Tue, 21 Apr 2026 21:14:21 -0400 Subject: [PATCH] Add getDate for parsing date strings to Date objects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces getDate(key, defaultVal) and the date() alias, which parse values via JavaScript's Date constructor — ISO 8601 strings are the recommended input. Defaults may be ISO strings or Date instances (Date instances are returned as-is). Numbers and other types are rejected; use a Date instance if you want to pass a timestamp. Returns null when neither the env value nor the default resolves to a valid date. Also: - TypeScript declarations for getDate and date. - Test fixtures and tape tests covering ISO datetime, ISO date-only, invalid values, default fallback, string/Date defaults, unparseable defaults (garbage string, invalid Date, number, object), and the alias. - README: new example in the Type Conversion section. - Bumps minor to 7.8.0 (additive API). Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 12 ++++++++++++ package.json | 2 +- src/index.d.ts | 17 +++++++++++++++++ src/index.js | 35 +++++++++++++++++++++++++++++++++++ test/test.env | 3 +++ test/test.js | 44 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 112 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5b43196..c061144 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,18 @@ env.getDuration('CACHE_TTL', 1000); // 1000 if CACHE_TTL is not set // Supported units: ms, s, m, h, d, w (case-insensitive, decimals allowed) // Returns null if the value and default are both unparseable. + +// Get a Date +// RELEASE_AT=2024-01-15T10:30:00Z +env.getDate('RELEASE_AT'); // Date object +env.date('RELEASE_AT'); // Shorthand for getDate() + +// Defaults may be ISO strings or Date instances +env.getDate('RELEASE_AT', '2024-01-01'); // Parsed if RELEASE_AT is not set +env.getDate('RELEASE_AT', new Date()); // Returned as-is if RELEASE_AT is not set + +// Follows JavaScript Date parsing rules — prefer ISO 8601 with a timezone. +// Returns null if neither the value nor the default is a valid date. ``` #### URLs and IPs diff --git a/package.json b/package.json index 04e2b48..b7d142a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "good-env", - "version": "7.7.0", + "version": "7.8.0", "description": "Better environment variable handling for Twelve-Factor node apps", "main": "src/index.js", "scripts": { diff --git a/src/index.d.ts b/src/index.d.ts index 7652a84..0b4db0b 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -143,6 +143,23 @@ declare module "good-env" { * */ export const duration: (key: string, defaultVal?: string | number) => number | null; + /** + * @description Fetches the value at the given key and parses it into a + * Date. Follows JavaScript's Date constructor rules (ISO 8601 is + * recommended; strings without a timezone fall back to local time). + * Accepts string values or Date instances as the default. Returns null + * if the env value is invalid and the default doesn't resolve. + * + * @param {string} key - A unique key + * @param {(string|Date)} defaultVal - A date string or Date instance + * + */ + export const getDate: (key: string, defaultVal?: string | Date) => Date | null; + /** + * @description An alias function for getDate() + * + */ + export const date: (key: string, defaultVal?: string | Date) => Date | null; /** * @description Fetches the value at the given key and attempts to * coherse it into a list of literal values diff --git a/src/index.js b/src/index.js index c2300c6..e2261dc 100644 --- a/src/index.js +++ b/src/index.js @@ -28,6 +28,14 @@ const parseDuration = input => { if (!match) return null; return parseFloat(match[1]) * DURATION_UNITS[match[2].toLowerCase()]; }; +const parseDate = input => { + if (input instanceof Date) { + return Number.isFinite(input.getTime()) ? input : null; + } + if (!isString(input)) return null; + const d = new Date(input.trim()); + return Number.isFinite(d.getTime()) ? d : null; +}; const store = { ...process.env }; module.exports = Object @@ -383,6 +391,33 @@ module.exports = Object return this.getDuration(key, defaultVal); }, + /** + * @description Fetches the value at the given key and parses it into + * a Date. Follows JavaScript's Date constructor rules (ISO 8601 is + * recommended; strings without a timezone fall back to local time). + * Accepts string values or Date instances as the default. Returns + * null if the env value is invalid and the default doesn't resolve. + * + * @param {string} key - A unique key + * @param {(string|Date)} defaultVal - A date string or Date instance + * + */ + getDate (key, defaultVal) { + const parsed = parseDate(this.get(key)); + if (parsed !== null) return parsed; + const parsedDefault = parseDate(defaultVal); + if (parsedDefault !== null) return parsedDefault; + return null; + }, + + /** + * @description An alias function for getDate() + * + */ + date (key, defaultVal) { + return this.getDate(key, defaultVal); + }, + /** * @description Fetches the value at the given key and attempts to * coerce it into a list of literal values diff --git a/test/test.env b/test/test.env index 46be7d5..c4668d7 100644 --- a/test/test.env +++ b/test/test.env @@ -30,3 +30,6 @@ DURATION_FLOAT=1.5h DURATION_UPPER=10S DURATION_SPACED=5 m DURATION_INVALID=nope +VALID_ISO_DATETIME=2024-01-15T10:30:00Z +VALID_ISO_DATE=2024-01-15 +INVALID_DATE=nope diff --git a/test/test.js b/test/test.js index e66f864..610ec0d 100644 --- a/test/test.js +++ b/test/test.js @@ -325,6 +325,50 @@ test('duration() is an alias for getDuration()', (t) => { t.end(); }); +test('getDate parses ISO datetime and date-only strings', (t) => { + const dt = env.getDate('VALID_ISO_DATETIME'); + t.ok(dt instanceof Date); + t.equals(dt.toISOString(), '2024-01-15T10:30:00.000Z'); + const d = env.getDate('VALID_ISO_DATE'); + t.ok(d instanceof Date); + t.equals(d.toISOString(), '2024-01-15T00:00:00.000Z'); + t.end(); +}); + +test('getDate returns null for invalid env values', (t) => { + t.equals(env.getDate('INVALID_DATE'), null); + t.end(); +}); + +test('getDate falls back to default on invalid env value', (t) => { + const d = env.getDate('INVALID_DATE', '2024-06-01'); + t.equals(d.toISOString(), '2024-06-01T00:00:00.000Z'); + t.end(); +}); + +test('getDate resolves defaults for missing keys', (t) => { + t.equals(env.getDate('DATE_NOPE'), null); + const d1 = env.getDate('DATE_NOPE', '2024-06-01'); + t.equals(d1.toISOString(), '2024-06-01T00:00:00.000Z'); + const now = new Date(); + t.equals(env.getDate('DATE_NOPE', now), now); + t.end(); +}); + +test('getDate returns null for unparseable defaults', (t) => { + t.equals(env.getDate('DATE_NOPE', 'garbage'), null); + t.equals(env.getDate('DATE_NOPE', new Date('nope')), null); + t.equals(env.getDate('DATE_NOPE', 12345), null); + t.equals(env.getDate('DATE_NOPE', {}), null); + t.end(); +}); + +test('date() is an alias for getDate()', (t) => { + const d = env.date('VALID_ISO_DATE'); + t.equals(d.toISOString(), '2024-01-15T00:00:00.000Z'); + t.end(); +}); + test('returns a list of values', (t) => { let result = env.getList('MY_LIST'); t.equals(result.length, 3);