laurent22/joplin · warning · Error
Invalid date: ${s}
Error message
Invalid date: ${s} What it means
Thrown by dateToTimestamp() in the Evernote ENEX importer. It tries two moment formats (YYYYMMDDTHHmmssZ and YYYYMMDDThmmss AZ) to cover common Evernote date variants; if neither parses and no defaultValue was supplied, it throws rather than emit a garbage timestamp.
Source
Thrown at packages/lib/import-enex.ts:37
import type * as FsExtra from 'fs-extra';
let fs_: typeof FsExtra = null;
const fs = () => {
fs_ ??= shim.requireDynamic('fs-extra');
return fs_;
};
function dateToTimestamp(s: string, defaultValue: number = null): number {
// Most dates seem to be in this format
let m = moment(s, 'YYYYMMDDTHHmmssZ');
// But sometimes they might be in this format eg. 20180306T91108 AMZ
// https://github.com/laurent22/joplin/issues/557
if (!m.isValid()) m = moment(s, 'YYYYMMDDThmmss AZ');
if (!m.isValid()) {
if (defaultValue !== null) return defaultValue;
throw new Error(`Invalid date: ${s}`);
}
return m.toDate().getTime();
}
function extractRecognitionObjId(recognitionXml: string) {
const r = recognitionXml.match(/objID="(.*?)"/);
return r && r.length >= 2 ? r[1] : null;
}
async function decodeBase64File(sourceFilePath: string, destFilePath: string) {
// When something goes wrong with streams you can get an error "EBADF, Bad file descriptor"
// with no strack trace to tell where the error happened.
// Also note that this code is not great because there's a source and a destination stream
// and while one stream might end, the other might throw an error or vice-versa. However
// we can only throw one error from a promise. So before one stream
// could end with resolve(), then another stream would get an error and call reject(), whichView on GitHub (pinned to 2654b33620)
Solutions
- Pass a defaultValue to dateToTimestamp() so unparseable dates fall back to a sane timestamp instead of throwing.
- Re-export from Evernote using the official client to get standard date formats.
- Pre-process the ENEX XML to normalize date elements into YYYYMMDDTHHmmssZ before import.
- If editing source, add another moment format string covering the observed variant.
Example fix
// before const ts = dateToTimestamp(s); // after - supply a default so import continues dateToTimestamp.timestamp = dateToTimestamp(s, Date.now()); // or in the importer call site: const ts = dateToTimestamp(s, time.unixMs());
Defensive patterns
Strategy: fallback
Validate before calling
// Pass a default so unparseable dates degrade instead of throwing. const ts = dateToTimestamp(enexDate, time.unixMs());
Type guard
function isParsableEnexDate(s: string): boolean {
if (!s) return false;
return require('moment')(s, ['YYYYMMDDTHHmmssZ', 'YYYYMMDDThmmss AZ'], true).isValid();
} Try / catch
let ts;
try {
ts = dateToTimestamp(s);
} catch (e) {
if (/Invalid date:/.test(e.message)) {
logger.warn('Unparseable ENEX date, using now:', s);
ts = time.unixMs();
} else throw e;
} Prevention
- Pass a defaultValue to dateToTimestamp when importing untrusted ENEX files.
- Re-export from the official Evernote client for standard date formats.
- Normalize ENEX date elements to YYYYMMDDTHHmmssZ before import.
- Extend the parser with additional moment formats if you control the importer.
When it happens
Trigger: Importing an .enex whose <created>/<updated> element contains a date string neither moment format recognizes — e.g. an unusual locale format, a malformed export, or a third-party tool that wrote non-standard dates.
Common situations: Evernote export produced by a non-official tool; regional date format in the ENEX; truncated/garbled export; very old Evernote format predating the standard; an ENEX hand-edited by the user.
Related errors
- Cannot decode resource with encoding: ${dataEncoding}
- Cannot find "%s".
- Invalid date: ${lastModifiedString}
- Cannot find unique filename
AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12).
Data as JSON: /api/errors/ad6377761cf8f0c6.
Report an issue: GitHub.