bdarcus / csl-next

An experimental reimagining of CSL
Mozilla Public License 2.0
8 stars 0 forks source link

Fix bug in year rendering #134

Closed bdarcus closed 1 year ago

bdarcus commented 1 year ago

Not sure how this happened, but the rendered year is one less than the input year!

Ah, the problem for some reason is here:

> const d1 = edtf.default("2020");
undefined
> d1
2020-01-01T00:00:00.000Z
> new Intl.DateTimeFormat('en-US', { year: "numeric" }).format(d1);
"2019"

@inukshuk - am I doing something wrong here?

inukshuk commented 1 year ago

That's most likely a timezone issue. All unqualified EDTF strings are interpreted as UTC so it's best to use UTC when formatting. That said, your example works fine for me even though I'm not in UTC either:

Screenshot 2023-05-22 at 10 46 24

You can use edtf's format method to format times. This uses DateTimeFormat as well but sets some options (e.g., to make sure the date is printed according to the precision given by the input string). It also sets the time zone so I'd hope that it works for you if you use that function.

You can also set all the options yourself of course, I've included them in the screenshot above.

bdarcus commented 1 year ago

Thank you!

I'll try edtf.format then.

...your example works fine for me even though I'm not in UTC either

I'm using Deno, so maybe there's some difference with node there.

One follow-up:

... it's best to use UTC when formatting.

How do I do that?

EDIT:

I think maybe I'm misunderstanding this:

You can use edtf's format method to format times. This uses DateTimeFormat as well ...

So these two should be equivalent, so I can just remove the second?

    const formattedDate = edtf.format(parsedDate, "en-US", options);
    const dateString = new Intl.DateTimeFormat("en-US", options).format(
      parsedDate,
    );

But then shouldn't this just return the year then?

> edtf.format(d2, 'en-US', {year: 'numeric'});
"10/2020"

I'm hoping to have the formatDate method be pretty simple, and just use the options to configure different output, with year being by far the most important.

 formatDate(date: string, options: Intl.DateTimeFormatOptions): string {
    const parsedDate = edtf.default(date);
    const formattedDate = edtf.format(parsedDate, "en-US", options);
    return formattedDate;
  }

But that doesn't work as I expect.

OK, this works, at least for getting the necessary year.

  formatDate(date: string, options: Intl.DateTimeFormatOptions): string {
    const parsedDate = edtf.default(date);
    const year = parsedDate.year.toString();
    const optKeys = Object.keys(options);
    const useYear = optKeys.includes("year") && optKeys.length === 1;
    const formattedDate = useYear
      ? year
      : edtf.format(parsedDate, "en-US", options);
    return formattedDate;
  }

I've opened the linked PR, if you have any feedback. I suspect it still won't work as I expected with other kinds of dates, without some adjustment.

inukshuk commented 1 year ago

All EDTF dates inherit directly from the built-in Date class, so you can typically use Intl.DateTimeFormat to format them just like any other date. But you need to keep in mind that the dates use UTC so it's good practice to set timezone: 'UTC' in your options to guarantee you that the input matches the output always.

However, there are some shortcomings because the Intl.DateTimeFormat doesn't know about all the extended attributes. So I'd generally suggest to use EDTF's format instead; it's only an exploratory implementation at this point but it should definitely give you better results already. For example, using Intl.DateTimeFormat will not work with the masked parts of unspecified dates (e.g., "202X"). The format function also handles date precision for you. That means that if you pass in "2020" it will not format the month and day. In your example I assume that d2 had month precision, that's why the formatter returned a string including the month.

bdarcus commented 1 year ago

In your example I assume that d2 had month precision, that's why the formatter returned a string including the month.

This was the example code for that:

const d1 = edtf.default('2020-10');

If I only pass it the year option, shouldn't it only return the year?

inukshuk commented 1 year ago

By default format will look at the date object and adjust the formatting options based on the date's precision. "2020-10" has month precision, so that was used. You can override this when you invoke the function, i.e., you want to format only the year you can do this:

format(edtf(string), 'en-US', { month: undefined, day: undefined })
bdarcus commented 1 year ago

OK, I made that adjustment.

Thanks!