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(), which

View on GitHub (pinned to 2654b33620)

Solutions

  1. Pass a defaultValue to dateToTimestamp() so unparseable dates fall back to a sane timestamp instead of throwing.
  2. Re-export from Evernote using the official client to get standard date formats.
  3. Pre-process the ENEX XML to normalize date elements into YYYYMMDDTHHmmssZ before import.
  4. 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

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


AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12). Data as JSON: /api/errors/ad6377761cf8f0c6. Report an issue: GitHub.