Dates and Times That Speak Human
Every application eventually deals with dates and times. A birthday, a deadline, a log entry, a schedule. It’s one of the most universal concepts in programming - and one of the most consistently painful.
Some languages give you a single timestamp type and leave you to extract meaning from it. Others provide rich libraries that require you to learn a small language of format strings, locale objects, and timezone rules before you can print today’s date. In many cases, the default representation of a date is something like 2024-12-25T14:30:00Z - a format designed for machines, not for the person reading the code.
Luma now has full support for dates and times. And its starting point is simple: the default format should be something a human would write.
Three types, one idea
Luma provides three distinct types that match how people actually think about time:
dt: datetime = datetime.now() // 2026-02-09 16:45:30
d: date = date.now() // 2026-02-09
t: time = time.now() // 16:45:30A datetime is a full moment in time. A date is just a day on the calendar. A time is just a time of day. You pick the one that matches what you’re actually working with.
There’s no single “Date” object that sometimes represents a date, sometimes a timestamp, and sometimes midnight on a particular day. Each type says exactly what it holds.
Human-readable by default
In most languages, parsing a date from a string means writing something like "2024-12-25T14:30:00Z". That format exists because it’s unambiguous to machines. But it’s not what anyone would type in a note, a spreadsheet, or a conversation.
In Luma, the default is simply:
dt: datetime = datetime.parse("2024-12-25 14:30:00")
d: date = date.parse("2024-12-25")
t: time = time.parse("14:30:00")No T. No Z. No timezone suffixes to remember. If you’re working with an API that sends ISO 8601, Luma accepts that too - but it’s the fallback, not the default.
Output works the same way:
print(dt.to_string()) // 2024-12-25 14:30:00
print(d.to_string()) // 2024-12-25
print(t.to_string()) // 14:30:00What goes in is what comes out. No surprises.
Creating values feels natural
You can create dates and times from components, from strings, or from the current moment:
// From the current moment
today: date = date.now()
right_now: time = time.now()
// From components
xmas: date = date({"year": 2024, "month": 12, "day": 25})
lunch: time = time({"hour": 12, "minute": 30, "second": 0})
// From strings
deadline: datetime = datetime.parse("2025-03-15 09:00:00")For datetimes specifically, you can also parse from Unix timestamps when working with external systems:
dt: datetime = datetime.from_unix(1735122600)Every creation method returns a typed value. There’s no ambiguity about what you’re holding.
Arithmetic that reads like English
Date math in many languages involves importing duration classes, converting between units, or manually adding seconds. In Luma, you describe what you want:
tomorrow: date = today.add({"days": 1})
next_month: date = today.add({"months": 1})
in_two_hours: time = right_now.add({"hours": 2})Time arithmetic wraps around midnight naturally:
late: time = time.parse("23:30:00")
wrapped: time = late.add({"hours": 2})
print(wrapped.to_string()) // 01:30:00No special cases. No overflow errors. It just works the way you’d expect.
Comparisons work everywhere
All three types support the comparison operators you’d naturally reach for:
d1: date = date.parse("2024-12-25")
d2: date = date.parse("2024-12-26")
print((d1 < d2).to_str()) // true
t1: time = time.parse("09:00")
t2: time = time.parse("17:00")
print((t1 < t2).to_str()) // trueAnd when you want readability over symbols, methods are there too:
print(d1.is_before(d2).to_str()) // trueConverting between types
Sometimes you have a datetime but only need the date. Sometimes you have a date and a time and need to combine them. Luma makes this explicit and clean:
dt: datetime = datetime.parse("2024-12-25 14:30:00")
// Extract parts
d: date = dt.to_date() // 2024-12-25
t: time = dt.to_time() // 14:30:00
// Combine back
combined: datetime = d.combine(t)
print(combined.to_string()) // 2024-12-25 14:30:00The round-trip is lossless. What you take apart, you can put back together.
Practical utilities
Each type comes with methods that answer the questions you actually ask in real code:
d: date = date.now()
d.is_today() // true
d.is_weekend() // true/false
d.day_of_week() // 1 (Monday) to 7 (Sunday)
d.days_in_month() // 28-31
t: time = time.now()
t.is_am() // true/false
t.is_pm() // true/false
dt: datetime = datetime.now()
dt.is_past() // true/false
dt.is_future() // true/falseThese aren’t clever abstractions. They’re the exact checks that show up again and again in real programs - scheduling, validation, display logic, logging.
A real example
Here’s a small but realistic scenario that uses all three types together:
// Schedule a meeting
meeting_date: date = date.parse("2025-03-15")
meeting_time: time = time.parse("10:00")
meeting: datetime = meeting_date.combine(meeting_time)
print("Meeting scheduled: " + meeting.to_string())
print("That's a " + meeting_date.day_of_week().to_str())
// Check if it's in the future
if meeting.is_future() {
print("Coming up!")
}
// Calculate age
birthday: date = date({"year": 1990, "month": 5, "day": 15})
today: date = date.now()
age: int = today.year() - birthday.year()
print("Age: ~" + age.to_str())No imports. No configuration. No format strings to memorize. Just dates, times, and the questions you want to ask about them.
Why this matters
Date and time handling is one of those features that reveals a language’s priorities. When the default format is a machine-readable ISO string, the language is optimizing for interoperability. When it requires importing a library, the language is optimizing for a small standard library. When it conflates dates and timestamps into a single type, the language is optimizing for simplicity at the cost of clarity.
Luma optimizes for the person writing the code. The defaults are human-readable. The types match how people think. The methods answer the questions people actually ask. And when you need machine formats for APIs or databases, they’re one method call away - but they’re not forced on you as the default.
That’s the philosophy behind everything in Luma: the common case should feel effortless, and the uncommon case should remain possible.
Dates and times are now a first-class part of that story.