Skip to content

Maxed-OSS/datelite

Repository files navigation

datelite

Timezone-safe date-only (YYYY-MM-DD) parsing and formatting for JavaScript and TypeScript. Zero dependencies.

A "date-only" value, like an invoice date, a due date, or a date of birth, has no time and no timezone. It is just a label on the calendar. The obvious JavaScript code gets this wrong all the time:

new Date("2024-01-15").getDate()
// 14  in any negative-UTC-offset zone (most of the Americas)
// 15  in UTC or positive offsets

new Date("2024-01-15") is parsed as midnight UTC, so once you read it back with local getters in a zone like America/New_York or America/Los_Angeles it lands on the day before. The same trap hits date.toISOString().slice(0, 10) when the Date was built from local fields.

datelite sidesteps the whole problem. It represents a date as three plain integers (year, month, day) and never runs a calendar date through a timezone conversion. Parsing and formatting a YYYY-MM-DD string is exact in every timezone.

Install

npm install datelite

Usage

import {
  parse,
  format,
  addDays,
  diffDays,
  fromLocalDate,
  toUTCDate,
} from "datelite";

// Parse is exact everywhere. No off-by-one, no matter the TZ.
const due = parse("2024-01-15");
due.day; // 15, always

// Format round-trips parse exactly.
format(due); // "2024-01-15"

// Date math is timezone-safe and DST-proof.
format(addDays(due, 30)); // "2024-02-14"
diffDays(parse("2024-02-14"), due); // 30

// Read the calendar day a user actually picked in their own timezone,
// instead of the UTC date.
const picked = new Date(2024, 0, 15, 9, 30); // local Jan 15
fromLocalDate(picked); // { year: 2024, month: 1, day: 15 }

// When you do need a Date (for an API), anchor it at UTC midnight and
// read it back with the UTC getters, never the local ones.
toUTCDate(due).toISOString(); // "2024-01-15T00:00:00.000Z"

API

A PlainDate is { year: number; month: number; day: number } with a 1-based month and day.

Function Description
parse(input) Parse a strict "YYYY-MM-DD" string into a PlainDate. Throws on bad format or impossible dates (for example 2024-02-30).
tryParse(input) Like parse but returns null instead of throwing.
format(date) Format a PlainDate back to "YYYY-MM-DD". Round-trips parse exactly.
fromLocalDate(date) Build a PlainDate from a Date using its local calendar fields (the day the user saw).
fromUTCDate(date) Build a PlainDate from a Date using its UTC calendar fields.
toUTCDate(date) Turn a PlainDate into a Date anchored at UTC midnight.
addDays(date, days) Add a whole number of days (may be negative). Timezone-safe and DST-proof.
diffDays(a, b) Whole-day difference a - b.
compare(a, b) Negative, zero, or positive. Sortable.
equals(a, b) True when two dates are the same calendar day.
today() Today in the local timezone, as a PlainDate.

Why not just use Date?

Date is an instant in time, not a calendar date. Storing a date-only value in a Date forces you to pick a timezone, and the default UTC parse plus local read is the most common off-by-one-day bug in web apps. datelite keeps the value as a calendar label until the moment you genuinely need an instant, and gives you explicit fromLocalDate / fromUTCDate / toUTCDate conversions so the direction is never ambiguous.

Test

npm test

The tests are run under a negative-UTC timezone in CI so the round-trip guarantees are exercised in exactly the conditions where naive code breaks.

License

MIT. See LICENSE.

About

Timezone-safe date-only (YYYY-MM-DD) parsing, formatting, and math. Zero dependencies. A date is three plain integers, never an instant, so the off-by-one-day bug cannot happen.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors