npm.io
0.2.1 • Published 21h ago

@dyadav0607/calendardate

Licence
MIT
Version
0.2.1
Deps
0
Size
14 kB
Vulns
0
Weekly
0

@dyadav0607/calendardate

Calendar dates without a timezone — a year, a month, a day, and the arithmetic that goes with them.

import { parseCalendarDate, addMonths, formatIso } from '@dyadav0607/calendardate'

const released = parseCalendarDate('2026-08-19')
formatIso(addMonths(released, -6)) // '2026-02-19'

No dependencies. Under 200 lines.

npm install @dyadav0607/calendardate

Why

A birthday is not an instant. Neither is a release date, a due date, or a public holiday. "The 19th of August" is the 19th of August in Mumbai and in Texas, and the moment you store it as a timestamp it stops being that.

new Date('2026-08-19').getDate()
// 19 in London
// 18 in Los Angeles

That is a real bug, not a curiosity. It is why a film released on a Friday shows up as Thursday for some readers, why a "today" filter drops a row after 5pm, and why a report run in one office disagrees with the same report run in another.

calendardate has no instant to get wrong. A date is { year, month, day } and stays that way.

What it does

// parse and format, ISO 8601
parseCalendarDate('2026-08-19') // { year: 2026, month: 8, day: 19 } | null
formatIso(date) // '2026-08-19'
isValidCalendarDate(value)

// arithmetic
addDays(date, 14)
addMonths(date, -6) // clamps: 31 Aug − 6 months → 28 Feb
daysInMonth(2026, 2)

// comparison
compareDates(a, b) // negative, zero, positive
datesEqual(a, b)
isWithin(date, start, end) // inclusive
isSameMonth(a, b)

// boundaries — weeks start Monday
startOfWeek(date)
endOfWeek(date)
startOfMonth(date)
endOfMonth(date)
isoWeekday(date) // 1 Monday … 7 Sunday

// the reader's own date
todayInLocalTime()

The one impure function

todayInLocalTime() reads the system clock. Everything else is a pure function of its arguments.

It is here because the alternative is worse: without it, every caller writes the same conversion from Date to a calendar date, and gets it subtly wrong. It takes an optional Date so a test can pass its own:

todayInLocalTime(new Date('2026-08-19T23:30:00-05:00')) // { year: 2026, month: 8, day: 19 }

What it is not

  • Not a time library. No hours, no minutes, no timezones, no durations. Reach for Temporal or date-fns when you need an instant.
  • Not localised. Nothing here produces a word. Month names and formatting belong to whatever is doing the displaying, which knows the language.
  • Not a Date replacement. It is for the dates that were never instants in the first place.

Should you use this?

Temporal.PlainDate is the same idea, in the language itself, and it is landing in browsers. When you can rely on it, use it.

Until then, or if you want something small with no dependencies and no polyfill, this does the job. It was extracted from a production site where the timezone bug above was a real one.

Weeks start on Monday

startOfWeek and isoWeekday follow ISO 8601. There is no option to change it — an option would mean every caller deciding, and every caller deciding is how two parts of one system come to disagree about what week it is.

License

MIT

Keywords