The king is dead. Long live the king!
Back on Sep 14, 2020 momentjs (18kb) added a new section to their homepage called “Project Status”, which details that the project is in maintenance mode, and they recommend against using it in new projects. Seems like a lot of people turn to dayjs as an alternative. Dayjs (2kb) is lightweight and has nearly identical apis.
As I am in the process of rewriting my date time picker library I’ve been evaluating my dependencies and the impact that they have on developers being able to use my picker and the pickers maintainability.
Originally, I decided to use dayjs as a replacement, I mean why not? Smaller, modern, basically drop in to replace momentjs. Until, the picker needed plugins. I ended up needing at least three additional plugins to get the same functionality in dayjs from moment. If I was going to use these plugins in a web app or something I don’t think the additional weight would be an issue, however I didn’t think I should impose that on a developer who just wants a date picker.
I decided to create my own small date library that could provide the functions from momentjs that I needed using native javascript date functions. This library doesn’t cover everything from moment.js but meets the needs of my picker, and it’s only 3kb.
Thanks to the latest version of javascript you can extend classes like the native Date class. class DateTime extends Date
This is important because outside of using my DateTime class, the object is just a Date that means that all the things you can normally do with a date object, can be done to a DateTime object. Note, this code works in evergreen browsers. If you care about old dead browsers like Safari or IE you may want to make use of polyfills OR stop accepting requirements to use these.
One quick note: A major difference between the two libraries moment.js is mutable vs day.js which is not.
var moment = moment();
moment.format(); //2021-03-09T12:14:54-05:00
moment.add(1, day);
moment.format() //2021-03-10T12:14:54-05:00
//versus dayjs
var day = dayjs();
day.format(); //2021-03-09T12:14:54-05:00
day.add(1, day); //this is a “pure” function that returns a new instance
day.format() //2021-03-09T12:14:54-05:00
var day2 = day.add(1, day);
day2.format(); //2021-03-10T12:14:54-05:00
Because the native date object is mutable, I decided to keep my class mutable as well.
How about some code already!
I’ve done my best to document the whole thing so hopefully it doesn’t need great explanation but I’ll highlight some things
I’m using the native Intl
for localization so you can create a DateTime and set its locale to whatever Intl.DateTimeFormat
support like BCP 47 strings e.g. “en-us”. It defaults to the user’s browser preference
var dt = new DateTime();
console.log(dt.format()); //assuming default is en-us: 3/10/2021
dt.locale = ‘de’ //German
console.log(dt.format()); //10.3.2021
//you can also set the locale as
//with the benefit allowing chainable actions
console.log(dt.setLocale(‘de’).startOf(‘month’).format()); //1.3.2021
For the most part DateTime and Date are interchangeable. You can call new dateTime.setHours(2)
just like you can do with new date.setHours(2)
, however the methods like startOf
are only available with a DateTime object, so you can use:
var date = new Date(2019, 3, 5); //date from somewhere else
var dt = DateTime.convert(date)
I should point out that it’s not necessary to use convert if you’re constructing a new date like the example above, you can simply do new DateTime(2019, 3, 5)
.
I’ve provided a clone()
method similar to moment.js.
var dt = new DateTime(); //2021-03-13
dt.setDate(21); //2021-03-21
var dt2 = dt.clone(); //2021-03-21
dt.setYear(2022); //2022-03-21
//dt2 => //2021-03-21
Similar to both moment.js and day.js there are endOf
and startOf
methods that as you might have guessed bring a date to the end or beginning of a particular unit of time. I used the native date methods here so using dt.endOf(‘date’)
behind the scenes it calls this.setHours(23, 59, 59, 999)
.
Rather than provide separate add and subtract methods, I provide a single manipulate
method.This method takes an integer that can be positive or negative plus unit to affect.
var dt = new DateTime(); //2021-03-13
dt.manipulate(1, ‘date’); //2021-03-14
dt.manipulate(-1, ‘date’); //2021-02-14
I used moment.js formatting features in my date picker to provide developers a way to define what format is acceptable from their users but also how the picker formatted select dates on the way out. Javascript now provides a native formatting through Intl. To me, it doesn’t feel as clean as format(‘MM/dd/YYYY’)
you’d get from moment/day.js or C# for instance. Intl requires a template and doesn’t always produce the same expected result. The following code will produce “März” by using a German locale of “de”.
dt.format( { month: "long" }, "de" );
I’ve provided an isBefore, isAfter, isBetween and isSame similar to moments functionality except of course that I’ve used the native functions to do the heavy lifting.
var dt = new DateTime().manipulate(-1, "month"); //February 28, 2022, 11:59:59.999 PM
dt.isBefore(new Date()); //false
dt.isAfter(new Date()); //true
Since Intl.DateTimeFormat doesn’t always do what I was expecting in the way that it returns results I created a function to return a flat object of date parts. For instance, if you use new Intl.DateTimeFormat('en', { year: "numeric", month: "long", day: ...}).formatToParts(new Date())
you will get something like this:
0: Object
type: "month"
value: "March"
__proto__: Object
1: Object
type: "literal"
value: " "
__proto__: Object
10: Object
type: "second"
value: "56"
__proto__: Object
The order you get this array back is never the same and as you can see from the results above doesn’t come in a logical sense. To me, including the literals as part of the array are a bit pointless as there would be no way for you to know where in the date time string those literals are.
My approach was to remove the literals and return an object as
day: "16",
dayPeriod: "AM",
hour: "11",
minute: "09",
month: "March",
second: "43",
timeZoneName: "EDT",
weekday: "Tuesday",
year: "2021",
I find this to be a much cleaner way to get information from a date object.
I’ve also provided get/set shortcuts to the various date methods e.g. .getSeconds()
/ .setSeconds(...)
and instead you can do new DateTime().seconds
/ new DateTime().seconds = 42
.
Some other bonus functionality is the “week” method that will get the week of the year, and a couple of methods like monthFormatted that return a two-digit month. Note the monthFormatted does shift the +1 so if you had 2021-01-25 the native date methods would return 0 for the month (whose idea was that?) vs the formatted method that would return 01.
You can get the code on github and play with the code on stackblitz .
Comments
You can also comment directly on GitHub.