How to work with Date | JavaScript Weird Parts

How to work with Date | JavaScript Weird Parts

Working with JavaScript has never been easy. You always required continuous learning and exploration of new APIs/specs. One of the toughest things

Working with JavaScript has never been easy. You always required continuous learning and exploration of new APIs/specs. One of the toughest things is working with Date. You always struggle to find a suitable method or end up importing external libraries like momentjs. Here in this article, I will explain how you can create a simple utility library for all your needs by using plain JavaScript.

Table of Contents:

1. Create a simple exportable module

Before going further, Let's create an exportable module. Exporting methods can vary according to the framework used. Below, I have mention a few examples of how to export a method as a module.

Nodejs:

//date.js
exports.isValid = (date) => {
  // TODO:
};

React with Webpack:

//date.js
export const isValid = (date) => {
  // TODO:
};

TypeScript:

//date.ts
export const isValid = (date: Date) => {
  // TODO:
};

2. Creating a Date

Date class provide multiple ways to create an object. To get the current date object you can use new Date(), Date constructor without any parameter. The other way to create a Date object, use the number value in the constructor(new Date(num)). Here, num representing the number of milliseconds since January 1, 1970, 00:00:00.

To get current Date:

/**
 * today: Get the current date
 */
exports.today = () => new Date();

// main.js
const { today } = require("./date");
console.log(today()); // 2021-08-22T15:59:47.482Z

To get current timestamp in milliseconds:

// date.js

/**
 * now: Get the current timestamp
 */
exports.now = () => Date.now();

// main.js

const { today, now } = require("./date");
console.log(now()); // 1629647987489

Simple hacks to Date constructor

// date.js

const [HR, DAY, WEEK] = [
  60 * 60 * 1000,
  60 * 60 * 1000 * 24,
  60 * 60 * 1000 * 24 * 7,
];
exports.hourBefore = () => new Date(this.now() - HR);
exports.dayBefore = () => new Date(this.now() - DAY);
exports.weekBefore = () => new Date(this.now() - WEEK);

//main.js

console.log(hourBefore()); //2021-08-22T14:59:47.489Z
console.log(dayBefore()); // 2021-08-21T15:59:47.489Z
console.log(weekBefore()); // 2021-08-15T15:59:47.489Z

Note: Since a month can be 28,29,30 or 31 days base on the type of year. And same time timezone varies with a local date. So working with a date in the month is a little tricky. I have explained few hacks to work with months in the below setters examples.

3. Get day, month, year

Getting a day, month or year from a Date object is quite simple. There are a lot of getter methods within a date object.

Sample:

const currentDate = today();
console.log(currentDate.toISOString()); // 2021-08-22T16:38:27.164Z
console.log(currentDate.getTime()); // 1629650307164
console.log(currentDate.getDate()); // 23
console.log(currentDate.getDay()); // 1
console.log(currentDate.getMonth()); // 7
console.log(currentDate.getFullYear()); // 2021
console.log(currentDate.getHours()); // 0
console.log(currentDate.getMinutes()); // 38

Note: Few of the biggest confusions are getDay and getMonth. These both two starts with 0. Meaning, month Jan and day Sunday represent as 0 in month and day respectively. This is very confusing for the first time. To work with month and day, You can create an enum/constant map.

//date.js

const MONTHS = { 0: "January", 1: "February", 2: "March", 7: "August" }; // TODO: complete
exports.monthInString = (date) => {
  return MONTHS[date.getMonth()];
};

const DAYS = { 0: "Sunday", 1: "Monday", 2: "Tuesday", 3: "Wednesday" }; // TODO: complete
exports.dayOfWeek = (date) => {
  return DAYS[date.getDay()];
};

// main.js

console.log(monthInString(today())); // August

console.log(dayOfWeek(today())); // Monday

Simple hacks to Date getters

With the new Date.toLocaleString method, Now it is very easy to get day/month as string. You can pass locale as "en-US" and other options to format date string.

// Get Day of the Week
console.log(today(), today().toLocaleString("en-US", { weekday: "long" })); // 021-08-22T16:58:30.238Z Monday
// Get month of the Year
console.log(today(), today().toLocaleString("en-US", { month: "long" })); // 021-08-22T16:58:30.238Z August

You can also collect all information at once using destructure array.

const [month, day, year, hr, min, sec, meridiem] = new Date(
  2021,
  08,
  22,
  18,
  08,
  08
)
  .toLocaleString("en-US")
  .split(/\W+/); //// 9/22/2021, 8:08:08 PM
console.log(month, day, year, hr, min, sec, meridiem); // 9 22 2021 6 08 08 PM

If you want to format or prefix a day/month/hr/min with zero, You can do so by passing the option "2-digit". You can also show the full name of a month.

const [month, day, year, hr, min, sec] = new Date(2021, 08, 22, 18, 08, 08)
  .toLocaleString("en-US", {
    day: "2-digit",
    month: "long",
    year: "2-digit",
    hour: "2-digit",
    minute: "2-digit",
    second: "2-digit",
    hour12: false,
  })
  .split(/\W+/); //// 9/22/2021, 8:08:08 PM
console.log(month, day, year, hr, min, sec); // September 22 21 18 08 08

_For more options, You can read this nice article._

4. Set day, month, year

Setting a day, month or year from a Date object is quite simple. Same as a getter, are a lot of setter methods available in a date object.

List of setters: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date

Simple hacks working with month(add/subtract)

// date.js
/**
 * addMonths: add a month to date
 * You can pass -(month) to subtract
 *
 * @param {Date} date
 * @param {number} months
 */
exports.addMonths = (date, months) => {
  var d = date.getDate();
  date.setMonth(date.getMonth() + +months);
  if (date.getDate() != d) {
    date.setDate(0);
  }
  return date;
};

exports.subMonths = (date, months) => this.addMonths(date, -1 * months);

//main.js

console.log(today(), addMonths(today(), 2)); //2021-08-22T16:30:11.826Z 2021-10-22T16:30:11.826Z
console.log(today(), subMonths(today(), 2)); //2021-08-22T16:34:10.895Z 2021-06-22T16:34:10.895Z

To learn more on adding/subtracting dates, Read below mention StackOverflow links.

1_1X-_xDbm03LntXVWYhKb7g.png

5. Format a date

There are multiple default standard format methods supported by the Date objects. You can use method likes toUTCString, toISOString, toLocaleString to get formatted string of date. However, there is no standard custom format method supported by the Date object. Intl supports a lot of helper methods to work with date formatting.

Note: Intl is not fully supported by all the browsers. You may have to polyfill methods based on need.

var date = new Date(Date.UTC(2012, 11, 20, 3, 0, 0));

// US English uses month-day-year order
console.log(new Intl.DateTimeFormat("en-US").format(date));
// → "12/19/2012"
// British English uses day-month-year order
console.log(new Intl.DateTimeFormat("en-GB").format(date));
// → "19/12/2012"

// Chinese
console.log(new Intl.DateTimeFormat("zh-TW").format(date));
// → "2012/12/20"

Same as toLocaleString, You can also pass options along with locale in DateTimeFormat constructor.

var options = {
  weekday: "long",
  year: "numeric",
  month: "long",
  day: "numeric",
};
console.log(new Intl.DateTimeFormat("de-DE", options).format(date));
// → "Donnerstag, 20. Dezember 2012"

Learn more on DateTimeFormat: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat

Simple custom format method

Writing a format function is very complex. It evolves by parsing a date object, extract values and compact in a date string. As part of this article, we will not go in-depth. However based on the above useful utility methods, We can create a simple format function using replace command.

//date.js
exports.formatString = (format, date) => {
  const [MM, DD, YYYY, hh, mm, ss, A] = date
    .toLocaleString("en-US", {
      day: "2-digit",
      month: "2-digit",
      year: "numeric",
      hour: "2-digit",
      minute: "2-digit",
      second: "2-digit",
    })
    .split(/\W+/);
  const timeMap = { MM, DD, YYYY, hh, mm, ss, A };
  return format.replace(/(\w+)/g, (key) => timeMap[key]);
};

// main.js
var date = new Date(2012, 11, 20, 3, 0, 0);
console.log(formatString("DD-MM-YYYY hh:mm:ss A", date)); // 20-12-2012 03:00:00 AM

Note: As mentioned above, This format method is not tested for production support. It is just to demonstrate how a simple format function can be written. You can think of more complex definitions to create a format function.

6. Invalid Date

Invalid Date is a spatial date object. Whenever you create a Date object with an invalid date value. Instead of throwing an error, It creates an object with a special value Invalid Date. This special object is very confusing. This will not throw any error in most of the operations performed on it. which can cause a lot of production issues. To avoid such issues, You can create an isValid function.

//date.js
exports.isValid = (date) => date.toString() !== "Invalid Date";

//main.js
console.log(isValid(new Date("2021-08-22T16:30:11.826Z"))); // true
console.log(isValid(new Date("ff-08-22T16:30:11.826Z"))); // false

Conclusion

In javascript, there is a lot of flexibility. However, This flexibility creates a lot of confusion and chaos. If you know these hacks, It can significantly improve your productivity. And a good way to learn, try and test in your project. Write more code and review more.

If you are looking for more useful methods like these, Please checkout 30-seconds-of-typescript/

helpful-tips-picture-id933100878.jpeg

Some useful hacks

  • Get timestamp without now/getTime method
console.log(+new Date());
//same as
console.log(new Date().getTime());
  • Getter than and equals
//date.js
const isAfterDate = (dateA, dateB) => dateA > dateB;
const isBeforeDate = (dateA, dateB) => dateA < dateB;

//main.js
console.log(new Date(2021, 08, 11) > new Date(2021, 08, 10));
console.log(new Date(2021, 08, 09) > new Date(2021, 08, 10));
console.log(
  new Date(2021, 08, 10).toISOString() === new Date(2021, 08, 10).toISOString()
);

Read more: https://decipher.dev/30-seconds-of-typescript/docs/isSameDate

  • Sort array by date
const users = [
  { name: "1", dob: "1989/8/26" },
  { name: "2", dob: "1989/8/23" },
  { name: "3", dob: "1989/8/25" },
];

const sortedUsers = users.sort(
  ({ dob: dob1 }, { dob: dob2 }) =>
    new Date(dob1).getTime() - new Date(dob2).getTime()
);
console.log(sortedUsers);

/**
[
  { name: '2', dob: '1989/8/23' },
  { name: '3', dob: '1989/8/25' },
  { name: '1', dob: '1989/8/26' }
]
**/
  • Humanize duration
//date.js

exports.formatDuration = (ms) => {
  ms = Math.abs(ms);
  const time = {
    day: Math.floor(ms / 86400000),
    hour: Math.floor(ms / 3600000) % 24,
    minute: Math.floor(ms / 60000) % 60,
    second: Math.floor(ms / 1000) % 60,
    millisecond: Math.floor(ms) % 1000,
  };
  return Object.entries(time)
    .filter((val) => val[1] !== 0)
    .map(([key, val]) => `${val} ${key}${val !== 1 ? "s" : ""}`)
    .join(", ");
};
// main.js

console.log(formatDuration(34325055574));

Read more: https://decipher.dev/30-seconds-of-typescript/docs/formatDuration

References

Did you find this article valuable?

Support Deepak Vishwakarma by becoming a sponsor. Any amount is appreciated!