TLDR; Checkout the Tempo docs.
Working with dates and time is one of JavaScript’s weakest points. The native Date object is, under the hood, just a unix timestamp with some utility methods.
Fortunately, there is no shortage of libraries like moment.js, luxon, date-fns, and day.js to fill the gap. Which brings up the question you’ve already asked yourself, — why another date library?
Of course I (👋 its me, Justin Schroeder) didn’t set out to create a whole new library, but the following needs have pushed me over the edge.
Intl.DateTimeFormat
styles
The Intl.DateTimeFormat
API is an incredible underutilized resource. For example, while it is excellent at formatting dates with locale aware format strings like { dateStyle: "full" }
there is no easy way to reverse this process. Friday, March 14, 1997
is the "full" dateStyle in the locale en-US
but how do you parse it back into a date? With tempo you can:
import { parse } from '@formkit/tempo'
parse('Friday, March 14, 1997', 'full', 'en')
Parse what you format
Anything you can format with Tempo you can parse with Tempo. For many libraries this is not the case. Bi-directional support allows you to build great locale aware user experiences like the datepicker in FormKit.
Timezones out of the box
Working with dates in real-world applications requires timezone support. Tempo does not require a plugin or additional package but instead mines the Intl.DateTimeFormat
for you. Need to know what the time difference between Amsterdam and Kolkata is in 2 months?
import { offset, addMonth } from '@formkit/tempo'
const inTwoMonths = addMonth(new Date(), 2)
offset(inTwoMonths, 'Europe/Amsterdam', 'Asia/Kolkata')
Death to the builder pattern
In almost all cases we should().not().use().theBuilder().pattern()
. The builder pattern cannot be tree shaken and offers no substantial benefits over a more functional style (this is not a rebuke of objects, just this pattern). Unfortunately tools like Luxon and DayJS make heavy use of the builder pattern.
Stop the plugins
Many of my favorite date libraries promise an itty bitty core with optional plugins to expand the scope of functionality — unfortunately that little core often doesn’t do much. The same benefit is available to anyone using function notation while tree shaking ensures your project is never bigger than it needs to be. In real-world use cases Tempo is generally ~3-5kb min/gzip including robust support, but depending on what you import it can be as small as 80 bytes.
Better docs
Most of our time interacting with date libraries is reading docs. I want to enjoy that experience. I also want to tinker with things like the format function directly on the page. Tempo’s docs are (I think) great and every example is interactive so you can test your use case before you write a line of code locally.
Why not?
Seriously though, this is an open source project and a free gift to the world. If you are perfectly happy with your current solution, keep using it. If you’d like to try something new — give Tempo a shot.
Highlights
Tempo is full featured in ways you would expect, with full support for day.js style formatting tokens, date parsing, and date manipulation. However it does have some nifty tricks up its sleeves that are worth pointing out:
Convert a dateStyle
to tokens
Tempo allows you to extract the formatting tokens of any dateStyle or timeStyle:
import { formatStr } from '@formkit/tempo'
formatStr({ date: 'full', time: 'full' }, 'en')
// dddd, MMMM D, YYYY at h:mm:ss A Z
Using this technique you can create incredible user experiences where users are presented editable dates in their own locale. For examples, checkout FormKit’s datepicker where we use this exact feature: https://formkit.com/inputs/datepicker
Tip: For even more detail you can use the parts()
function.
Using timezones
Tempo ships with first class support for timezones — it’s even baked into the format()
function.
import { format } from "@formkit/tempo"
// What time is it in LA?
format({
date: new Date(),
format: 'hh:mm a',
tz: 'America/Los_Angeles'
})
You can also easily determine the timezone offset from the user’s location to a fixed place. This is useful when building apps where time at a target location matters. For example booking a rental or a meeting in at a given location from another timezone.
import { tzDate } from '@formkit/tempo'
// Create the date for 1:44pm in Dubai.
tzDate('2025-02-28 13:44', 'Asia/Dubai')
Token ranges
Tempo can "mine" information from Intl.DateTimeFormat
. For example, if you want to create a select list of months and display them to a user in their own locale, how can you get all those strings?
import { range } from '@formkit/tempo'
const monthNames = range('MMMM')
// January, February, March...
// or in french:
// janvier,février,mars..
Get building
There are many more features to explore in Tempo — give the docs a quick scan so you know what’s available the next time you’re working on a project that requires working with dates.
While I have you here — can I ask you a personal favor? Consider giving Tempo a star on GitHub...it makes me happy.
If you really like Tempo, consider sponsoring FormKit! We don’t make a dime off this open source work, so any sponsorships we get are hugely encouraging.
❤️
See announcement tweet 👇
Top comments (11)
off-topic: how did you build the interactive code demo in the docs?
Is this custom made using monaco and
eval
, or did you use some product like jsbin?It has a very clean look, and I couldn't find anything similar.
If it's custom and you want to share a gist with the code it will be highly appreciated :)
It’s custom — all open-source in the repo though in the docs directory: github.com/formkit/tempo/tree/main...
Cool, thanks! I'll take a look
Thats the difference to dayjs?
github.com/iamkun/dayjs
dayjs is great, but has no locale aware formatting options or parsing options and anything like timezone support is not 2kb as it requires additional plugins.
dayjs with a locale imported is 6kb for me in my current configuration
https://bundlejs.com/?q=dayjs%2Cdayjs%2Flocale%2Fde%2Cdayjs%2Fplugin%2Fduration%2Cdayjs%2Fplugin%2FquarterOfYear%2Cdayjs%2Fplugin%2FrelativeTime%2Cdayjs%2Fplugin%2FweekOfYear&treeshake=%5B*%5D%2C%5B*%5D%2C%5B*%5D%2C%5B*%5D%2C%5B*%5D%2C%5B*%5D&share=CYQwngVgzgdApgDwC5wHbABQCc4BsRICWAbnACqEC2cAlAAQD0DdAFkkgA5QBcToYMaDAD2WAOYNgwgMZQGaBh1wBXMYVQMc%2BIqQC0RagCh%2BQ3DJC44GAETA41%2Bk1bsuvSeEGxREqbPkbCAEYADg1pFhBUNSjdM2kLOGNwIUQUdAwAEWUsAkJhVEdmNk4ePg8hb0kZOQUlVXVJbNz8pMhYVLRMAHc4OABrAHkAMwBNOBAsQucSt35PEXEqv1qVaIYe-t1hId0wcaxWlOROjABHZQmULGGxiani1zKBCsXfGo06tfPLuCwtnb2E0MhkQHFESDodiGIGUuAhJigQA
What kind of "locale aware formatting options" do you mean?
If I use
dayjs().format("MMMM YYYY")
it works just fine for me with the configured localeBut
MMMM YYYYY
may not be the correct formatting for a user’s locale. Sure you can force a format, but you dont magically get the correct format like you would with Tempo.Have you read the docs yet? They’re pretty short and to the point:
tempo.formkit.com/#format-styles
What is the size of Tempo? (gzipped)
(EDIT: I mean the full version size, I understand that care has been taken to enable tree shaking and minimizet the actual real world size)
All of tempo with no tree shaking at all is 5.2Kb gzipped.
This is good!
The introduction of Tempo, a new date library for JavaScript and TypeScript, brings attention to its unique features and aims to address shortcomings in existing libraries. One standout feature is its utilization of the Intl.DateTimeFormat API for styles, allowing both formatting and parsing in a locale-aware manner. Could you elaborate on how Tempo's approach to handling timezones without the need for additional plugins sets it apart? Additionally, the avoidance of the builder pattern and minimal reliance on plugins are highlighted as strengths. How does Tempo achieve a small core size while maintaining robust functionality, and what benefits does this bring to developers in terms of project size and simplicity? Lastly, the documentation and interactive examples are emphasized as user-friendly. How does Tempo's documentation enhance the developer experience, and what interactive features does it offer to aid developers in testing use cases before implementation?
"𝗙𝗢𝗟𝗟𝗢𝗪 𝗕𝗔𝗖𝗞 𝗙𝗢𝗥 𝗠𝗢𝗥𝗘 𝗜𝗡𝗦𝗜𝗚𝗛𝗧𝗙𝗨𝗟 𝗗𝗜𝗦𝗖𝗨𝗦𝗦𝗜𝗢𝗡"