inukshuk/edtf.js

minimal node example not loading

bdarcus opened this issue · 11 comments

I just tried the example code at the top with node.js, but getting this error.

import edtf, { Date, Interval } from 'edtf'
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at internalCompileFunction (node:internal/vm:73:18)
    at wrapSafe (node:internal/modules/cjs/loader:1176:20)
    at Module._compile (node:internal/modules/cjs/loader:1218:27)
    at Module._extensions..js (node:internal/modules/cjs/loader:1308:10)
    at Module.load (node:internal/modules/cjs/loader:1117:32)
    at Module._load (node:internal/modules/cjs/loader:958:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:23:47

Node.js v18.15.0

If I add the import line to my typescript project, I get this:

npx tsc
src/reference.ts:3:38 - error TS7016: Could not find a declaration file for module 'edtf'. '/home/bruce/Code/csl-next.js/node_modules/edtf/dist/index.cjs' implicitly has an 'any' type.
  Try `npm i --save-dev @types/edtf` if it exists or add a new declaration (.d.ts) file containing `declare module 'edtf';`

3 import edtf, { Date, Interval } from 'edtf'

What am I doing wrong?

Is that example from the Node.js REPL? In the REPL you cannot use the import statement. The example should work fine if you put it into an ESM module (either any .js file if you configure the default to be ESM or a .mjs file). In the REPL you can load the ESM module with the import function dynamically:

const { default: edtf, Date, Interval } = await import('edtf')

Or you can require the CJS version:

const { default: edtf, Date, Interval } = require('edtf')

Is that example from the Node.js REPL?

Initially yes, but not in this case. I was using it in a file and getting that error:

import edtf, { Date, Interval } from 'edtf'

edtf('2016-XX');
edtf(new Date());
edtf('2016-04~/2016-05');

To go to the REPL:

Welcome to Node.js v18.15.0.
Type ".help" for more information.
> const { default: edtf, Date, Interval } = await import('edtf')
undefined
> edtf('2016-XX');
Uncaught RangeError: Maximum call stack size exceeded
    at Date.UTC (file:///home/bruce/Code/csl-next.js/node_modules/edtf/src/interface.js:17:13)
    at Date.UTC (file:///home/bruce/Code/csl-next.js/node_modules/edtf/src/interface.js:18:21)
    at Date.UTC (file:///home/bruce/Code/csl-next.js/node_modules/edtf/src/interface.js:18:21)
    at Date.UTC (file:///home/bruce/Code/csl-next.js/node_modules/edtf/src/interface.js:18:21)
    at Date.UTC (file:///home/bruce/Code/csl-next.js/node_modules/edtf/src/interface.js:18:21)
    at Date.UTC (file:///home/bruce/Code/csl-next.js/node_modules/edtf/src/interface.js:18:21)
    at Date.UTC (file:///home/bruce/Code/csl-next.js/node_modules/edtf/src/interface.js:18:21)
    at Date.UTC (file:///home/bruce/Code/csl-next.js/node_modules/edtf/src/interface.js:18:21)
    at Date.UTC (file:///home/bruce/Code/csl-next.js/node_modules/edtf/src/interface.js:18:21)
    at Date.UTC (file:///home/bruce/Code/csl-next.js/node_modules/edtf/src/interface.js:18:21)

Sorry if I'm missing something obvious; am a node/js newbie.

Oh it looks like there is some issue in the REPL if the built-in Date class is shadowed. This way it should be fine:

$ node
Welcome to Node.js v19.3.0.
Type ".help" for more information.
> const { default: edtf } = await import('edtf')
undefined
> edtf('2016-XX')
2016-01-01T00:00:00.000Z
> edtf(new Date())
2023-04-27T13:41:33.542Z
> edtf('2016-04~/2016-05')
Interval { earlier: undefined, later: undefined }

Using an ESM file should be totally fine though:

$ cat test.js 
import edtf, { Date, Interval } from 'edtf'

console.log(edtf('2016-XX'));
console.log(edtf(new Date()));
console.log(edtf('2016-04~/2016-05'));

$ node test.mjs
2016-01-01T00:00:00.000Z
2023-04-27T13:46:21.000Z
Interval { earlier: undefined, later: undefined }

Now if you change the extension to .js you may get the 'cannot use import statement' error unless you enable ESM support in your package.json. If you set type to module then it will work with .js as well.

Getting closer; REPL works with that.

EDIT: ah, I had to use the mjs extension. That works.

So these all work (I'm playing also with the JS date formatting stuff), except the last, because I guess it's an interval.

import edtf, { Date, Interval } from 'edtf'

const d1 = edtf('2016-10');
const d2 = edtf(new Date());
const d3 = edtf('2016-04~/2016-05');
const df = { weekday: "long", year: "numeric", month: "short", day: "numeric" };

console.log(d1.toLocaleDateString('en-us', df));
console.log(d2.toLocaleDateString('de', df));
console.log(d3.toLocaleDateString('ja', df));

I'm not concerned about it now, but how would one go about formatting intervals?

Manually?

Right, the date objects are all instances of the built-in Date so you can format them as usual. However, this will not take into account any of the extended functionality. For this we have a separate format function that is based on the built-in date formatting adding preliminary support for some of the extended features (precision, uncertain, approximate and unspecified dates). You can see some examples here. As formatting of extended dates isn't part of the spec (as far as I'm aware of) this is all still very exploratory.

Intervals are not supported out of the box yet, but you can get the start and end of the interval via iteration or using lower and upper and then format those individually. For example:

[...edtf('2001/2002-08~').values].map(d => format(d, 'en')).join(' until ')
//-> '2001 until ca. 8/2002'

@inukshuk - thought you might be interested that I finally got around to implementing this in this project I'm working on:

bdarcus/csl-next@c5a6151

This is only the basic date formatting ATM, however.

While I'm here, do you have suggestion on how best to confirm a string conforms to EDTF before using edtf to parse it?

I basically want to define dates in this new format as either EDTF, or else to fallback to a literal string.

Oh! That's nice, I'll have to take a looks at csl-next!

I think the best way to validate that a string conforms to EDTF is to actually parse it. You can define parser constraints if you don't want to accept everything (e.g., by level or type).

There's also the validator if you use EDTF values in JSON-Schema but under the hood this will also run the parser with the constraints set accordingly.

I think the best way to validate that a string conforms to EDTF is to actually parse it.

So basically try to parse, and pass it through if it fails?

There's also the validator if you use EDTF values in JSON-Schema but under the hood this will also run the parser with the constraints set accordingly.

Yeah, I'm planning to use that somehow; just not sure how.

I don't want to reject input data that aren't valid, for example, since I want to treat those as literals.

Yes, I'd try to parse and use the result if successful (if if the purpose is validation just ignore the result itself). Otherwise you know that it's not valid a ISO/EDTF data (within the given constraints) and can employ other parsing rules/strategies.

if the purpose is validation just ignore the result itself ...

Actually, I just want to parse and format the date, without edtf throwing an error.

E.g. with input of "2000", my function would return "2000" when given the "year" format, but if it was, say, "Han Dynasty", it would return "Han Dynasty".

That's all.