
Handling Dates in JavaScript the Clean Way with Luxon
Handling dates in JavaScript can quickly become a nightmare, especially when dealing with timezones. While building Max Explorer, I discovered how unreliable native Date objects could be β leading to subtle bugs. In this article, I share how switching to Luxon, a modern date-time library, helped me build clean and reliable date logic, and how I centralized it into a production-ready utility module.
Tech Stack
Why I Needed Better Date Management
While building Max Explorer, a train itinerary search engine, I ran into a familiar developer pain: handling dates in JavaScript.
To calculate connections and available train options, I had to deal with departure and return dates precisely. But using native Date
objects quickly became a problem. JavaScript tends to interpret dates relative to the serverβs local timezone, which led to some unexpected behavior:
new Date('2025-04-15')
// β Returns April 14th at 20:00 depending on the server's timezone
This was clearly not sustainable. So I looked for a reliable, readable, and modern library. That's when I found Luxon.
What is Luxon?
Luxon is a modern JavaScript library for working with dates and times. Created by one of the Moment.js authors, it offers:
- A clean, chainable, and object-oriented API
- First-class timezone support
- Native parsing and formatting of ISO strings
- Great support for formatting, comparisons, and math operations
Perfect for backend use and clean architecture projects.
Installing Luxon
npm install luxon
Production-Friendly Utility: dateUtils.ts
To keep things clean and reusable across my codebase, I created a small utility module using Luxon:
// libs/dateUtils.ts
import { DateTime, DurationLikeObject } from 'luxon'
const DEFAULT_ZONE = 'Europe/Paris'
export const parseISODate = (iso: string): DateTime => {
return DateTime.fromISO(iso, { zone: DEFAULT_ZONE })
}
export const fromJSDate = (date: Date): DateTime => {
return DateTime.fromJSDate(date, { zone: DEFAULT_ZONE })
}
export const toUTCJSDate = (dt: DateTime): Date => {
return dt.toUTC().toJSDate()
}
export const addDuration = (dt: DateTime, duration: DurationLikeObject): DateTime => {
return dt.plus(duration)
}
export const isBefore = (dt1: DateTime, dt2: DateTime): boolean => {
return dt1.toUTC() < dt2.toUTC()
}
Example usage:
import { parseISODate, addDuration, toUTCJSDate } from '~/libs/dateUtils'
const departure = parseISODate('2025-04-15')
const returnDate = addDuration(departure, { days: 8 })
console.log('Departure:', toUTCJSDate(departure))
console.log('Return:', toUTCJSDate(returnDate))
Key Takeaways
- Native
Date
is unreliable when it comes to timezones β avoid it when possible. - Luxon gives you full control over timezones, parsing, and date manipulation.
- Centralize logic in a utility (
dateUtils.ts
) to keep your code maintainable and testable. - Ideal for clean architecture projects where framework-agnostic logic is a priority.