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
40 changes: 36 additions & 4 deletions src/formatters/formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ export abstract class Formatter {
}

if (variableName && dateFormat) {
const existingValue = this.variables.get(variableName) as string;
const existingValue = this.variables.get(variableName);

// Check if we already have this date variable stored
if (!existingValue) {
Expand Down Expand Up @@ -547,9 +547,38 @@ export abstract class Formatter {

// Format the date based on what's stored
let formattedDate = "";
const storedValue = this.variables.get(variableName) as string;
let storedValue = this.variables.get(variableName);

// If a VDATE variable was pre-seeded (e.g., via API/URL) as a plain string,
// attempt to coerce it into the internal @date:ISO form so formatting works.
if (
typeof storedValue === "string" &&
storedValue &&
!storedValue.startsWith("@date:")
) {
if (this.dateParser) {
const aliasMap = settingsStore.getState().dateAliases;
const normalizedInput = normalizeDateInput(storedValue, aliasMap);
const parseAttempt = this.dateParser.parseDate(normalizedInput);

// Keep backwards compatibility: only coerce if we can parse it.
if (parseAttempt) {
const iso = parseAttempt.moment.toISOString();
const coerced = `@date:${iso}`;
this.variables.set(variableName, coerced);
storedValue = coerced;
}
}
} else if (storedValue instanceof Date) {
// Some callers may pass actual Date objects through the JS API.
if (!Number.isNaN(storedValue.getTime())) {
const coerced = `@date:${storedValue.toISOString()}`;
this.variables.set(variableName, coerced);
storedValue = coerced;
}
}

if (storedValue && storedValue.startsWith("@date:")) {
if (typeof storedValue === "string" && storedValue.startsWith("@date:")) {
// It's a date variable, extract and format it
const isoString = storedValue.substring(6);

Expand All @@ -559,9 +588,12 @@ export abstract class Formatter {
formattedDate = moment.format(dateFormat);
}
}
} else if (storedValue) {
} else if (typeof storedValue === "string" && storedValue) {
// Backward compatibility: use the stored value as-is
formattedDate = storedValue;
} else if (storedValue != null) {
// Fallback: avoid throwing if a non-string value is stored.
formattedDate = `${storedValue}`;
}

// Replace the specific match rather than using regex again
Expand Down
13 changes: 13 additions & 0 deletions src/formatters/vdate-default.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,19 @@ describe('VDATE Default Value Support', () => {
expect(formatter.testDateParser.parseDate).toHaveBeenCalledWith("today");
expect(result).toBe("Date1: YYYY-MM-DD-formatted Date2: MM/DD/YYYY-formatted");
});

it('should parse and format pre-seeded string variables used in VDATE', async () => {
// Mirrors issue #1074: variables passed via API/URL are plain strings.
formatter.variables.set('date', '2026-12-31');

const result = await formatter.testReplaceDateVariableInString(
"Test {{VDATE:date,YYYY-MM-DD}}",
);

// The formatter should attempt to parse the existing string into a date.
expect(formatter.testDateParser.parseDate).toHaveBeenCalledWith("2026-12-31");
expect(result).toBe("Test YYYY-MM-DD-formatted");
});
});

describe('Edge Cases', () => {
Expand Down