Skip to content

fix(#169): keep the date that was picked in the date picker - #187

Closed
muditagrawal2007 wants to merge 1 commit into
joplin:masterfrom
muditagrawal2007:fix/169-date-picker-off-by-one
Closed

muditagrawal2007 wants to merge 1 commit into
joplin:masterfrom
muditagrawal2007:fix/169-date-picker-off-by-one

Conversation

@muditagrawal2007

Copy link
Copy Markdown

Fixes #169

The bug

A date template variable is filled in with an HTML date picker. The HTML spec requires such an input to submit an ISO yyyy-mm-dd value, whatever the locale of the browser or the user is.

src/variables/types/date.ts turned that value into a date with:

const inputDate = new Date(input);

new Date("2026-06-24") is parsed as UTC midnight, because a date-only ISO string is defined to be UTC. The value was then formatted in local time:

return dateAndTimeUtils.formatMsToLocal(inputDate.getTime(), dateAndTimeUtils.getDateFormat());

So for every timezone west of UTC, the day the user picked was moved back by one. Picking the 24th of June produced a note dated the 23rd of June, and picking today produced yesterday. Timezones east of UTC were unaffected, which is why it went unnoticed for so long.

This was the only place in the plugin where a user supplied string was turned into a Date that way, so no other variable type was affected.

Before / after

The user picks the 24th of June 2026, the note says the 23rd:

Before the fix

After the fix the note keeps the day that was picked:

After the fix

Both images are rendered from the real production code and the real plugin CSS, in a America/New_York timezone with the DD/MM/YYYY date format.

The fix

src/utils/dateAndTime.ts

Added formatDateInput(), which parses an ISO date as a local date and formats it with the user's date format:

const isoDate = moment(trimmedInput, "YYYY-MM-DD", true);
if (isoDate.isValid()) {
    return this.formatMsToLocal(isoDate.valueOf(), format);
}

A local date formatted in local time is the same calendar day in every timezone, so the result no longer depends on where the user is. Values that aren't an ISO date still go through the generic parser, so nothing that worked before changes.

src/variables/types/date.ts

processInput() now uses formatDateInput(). An empty date input returns an empty string instead of putting Invalid date into the note.

Tests

The bug only shows up in a timezone behind of UTC, and Jest sandboxes process.env, so the timezone can't be changed from inside a test. The regression tests therefore run in a second suite that has the timezone set for the whole process:

  • tests/variables/date.spec.ts — unit tests for the date variable.
  • tests/variables/date.timezone.spec.ts — the regression tests, including one that creates a note from a template.
  • jest.timezone.config.js / tests/jest-timezone-setup.js — the second suite.
  • package.json — npm test and npm run coverage now run both suites.

Against the old implementation the new regression tests fail:

✕ should keep the day that was picked in the date picker (#169)
✕ should keep the day that was picked in the date picker at a month boundary (#169)
✕ should keep the day that was picked in the date picker at a year boundary (#169)
✕ should not shift the date in a note created from a template (#169)

and they pass with the fix.

Testing

  1. npm test — both suites pass (106 + 5 tests).
  2. npm run lint — no new warnings.
  3. npm run dist — the plugin builds.
  4. In the app: create a template with a date variable, create a note from it and pick a date. The note now carries the date that was picked, in a timezone west of UTC as well as east of it.

A date picker always submits an ISO "yyyy-mm-dd" value, no matter which
locale the browser or the user is using. `new Date("2026-06-24")` parses
such a value as UTC midnight, so formatting it in local time moved the date
back by one day for every timezone west of UTC. Picking the 24th of June
produced a note dated the 23rd of June, and picking today produced
yesterday.

src/utils/dateAndTime.ts
- Added formatDateInput(), which parses an ISO date as a local date and
  formats it with the user's date format. Values that aren't an ISO date
  still go through the generic parser.

src/variables/types/date.ts
- Use formatDateInput() instead of new Date(input).
- An empty date input now yields an empty string instead of "Invalid date".

tests/variables/date.spec.ts
- Added unit tests for the date variable.

tests/variables/date.timezone.spec.ts
- Added regression tests for the bug. They only fail in a timezone behind
  UTC, so they run in a separate suite (jest.timezone.config.js) that has
  the timezone set for the whole process. Jest sandboxes process.env, so
  the timezone can't be changed from inside a test.
@muditagrawal2007

Copy link
Copy Markdown
Author

Closing this in favour of a fix that doesn't overlap with the other open PRs.

PR #146 replaces the native date input with a Flatpickr text input, which already removes this symptom (the value then arrives in the user's own date format and is parsed as a local date). Keeping this PR open would mean two competing fixes for the same behaviour.

The underlying parsing bug in the datetime helper is unrelated and is not covered by any open PR, so I'm taking that one to #114 instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

When using the date picker, the date I select gets reported one day earlier

1 participant