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
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
17 changes: 17 additions & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 35 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions test/test.env
Original file line number Diff line number Diff line change
Expand Up @@ -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
44 changes: 44 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading