How to Work with Date in Plain Javascript — No Libraries Needed

Javascript and Java Date

Date object Pros and Cons

Date libraries proposed solutions

How to “Fix” Date?

class DateTime {
#locale = 'default'; // language
#Date = null;

constructor(...args) {
const date = new Date(...args);

// Simple validation
if(date.toString() === 'Invalid Date') {
throw new Error(`Invalid Date: "${args}"`);
}

this.#Date = date;
}
}
  get year() {
return this.#Date.getFullYear();
}

get month() {
return this.#Date.getMonth() + 1;
}

get dayOfTheWeek() {
return this.#Date.getDay() + 1;
}

get date() { // alias to dayOfTheMonth
return this.dayOfTheMonth;
}

get dayOfTheMonth() {
return this.#Date.getDate();
}

get hour() {
return this.#Date.getHours();
}

get minutes() {
return this.#Date.getMinutes();
}

get seconds() {
return this.#Date.getSeconds();
}

get milliseconds() {
return this.#Date.getMilliseconds();
}
get timestamp() {
return this.#Date.getTime();
}
get week() {
const firstDayOfTheYear = new Date(this.year, 0, 1);

// one day = 86400000ms
const pastDaysOfYear =
(this.timestamp - firstDayOfTheYear) / 86400000;
return Math.ceil(
(pastDaysOfYear + firstDayOfTheYear.getDay() + 1) / 7
);
}
static get now() {
return new DateTime(Date.now());
}
static parse(...args) {
return Date.parse(...args);
}
set() {
const date = new Date(this.timestamp);
return {
date: value => new DateTime(date.setDate(value)),
month: value => new DateTime(date.setMonth(value)),
year: value => new DateTime(date.setFullYear(value)),
hour: value => new DateTime(date.setHours(value)),
minutes: value => new DateTime(date.setMinutes(value)),
seconds: value => new DateTime(date.setSeconds(value)),
milliseconds: value => new DateTime(date.setMilliseconds(value))
};
}
static isLeapYear(year) {
year = year ?? (new Date()).getFullYear();
if(`${year}`.length !== 4 || !isNaN(year) ) {
return false;
}
return year % 100 === 0
? year % 400 === 0
: year % 4 === 0;
}

static monthSizes(year) {
return Object.freeze(
[31, (DateTime.isLeapYear(year) ? 29 : 28),
31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
);
}
static daysOfTheWeek(locale = 'default', format = 'long') {
const weekDays = Array.from({length: 7});
const date = new Date();
for(let i=1; i<=7; i++) {
weekDays[date.getDay()] =
date.toLocaleString(locale, { weekday: format});
date.setDate(date.getDate() + 1);
}
Object.freeze(weekDays); return weekDays;
}
// Usage Example:
DateTime.daysOfTheWeek
// returns ['Monday', 'Tuesday', 'Wednesday', ...];

Language/Locale support

get locale() {
return this.#locale;
}
set locale(newLocale) {
this.#locale = newLocale;
}
get monthShort() {
return this.#Date.toLocaleString(this.locale, {
month: 'short'
});
}
get monthLong() {
return this.#Date.toLocaleString(this.locale, {
month: 'long'
});
}
get dayOfTheWeekLong() {
return this.#Date.toLocaleString(this.locale, {
weekday: 'long'
});
}
get dayOfTheWeekShort() {
return this.#Date.toLocaleString(this.locale, {
weekday: 'short'
});
}
// Usage Example:
const date = new DateTime();
date.monthShort; // Feb
date.monthLong; // February
date.dayOfTheWeekLong; // Tuesday
date.dayOfTheWeekShort; // Tue

UTC Support

get utc() {
return new DateTime(
Date.UTC(
this.year, this.month, this.date, this.hours,
this.minutes, this.seconds, this.milliseconds
)
);
}
// Usage Example:
const date = new DateTime();
date.monthLong; // February
date.utc.monthLong; // March
// date sampled at the end of feb

Format Date

localeFormat(options = {}) {
return this.#Date.toLocaleString(this.locale, options);
}
date.localeFormat({
year: 'numeric',
month: 'numeric',
day: 'numeric'
});
// "2/23/2021"
get amPm() {
return this.#Date
.toLocaleString('en', {hour: '2-digit'}).split(' ')[1];
}
format(formatStr) {
return formatStr
.replace(/\bYYYY\b/, this.year)
.replace(/\bYYY\b/, this.yearShort)
.replace(/\bWW\b/, this.week.toString().padStart(2, '0'))
.replace(/\bW\b/, this.week)
.replace(/\bDDDD\b/, this.dayOfTheWeekLong)
.replace(/\bDDD\b/, this.dayOfTheWeekShort)
.replace(/\bDD\b/, this.date.toString().padStart(2, '0'))
.replace(/\bD\b/, this.date)
.replace(/\bMMMM\b/, this.monthLong)
.replace(/\bMMM\b/, this.monthShort)
.replace(/\bMM\b/, this.month.toString().padStart(2, '0'))
.replace(/\bM\b/, this.month)
.replace(/\bHH\b/, this.hours.toString().padStart(2, '0'))
.replace(/\bH\b/, this.hours)
.replace(/\bhh\b/, this.amPm === 'AM'
? `${this.hours}`.padStart(2, '0')
: `${this.hours % 12}`.padStart(2, '0'))
.replace(/\bh\b/, this.amPm === 'AM'
? this.hours
: this.hours % 12)
.replace(/\bmm\b/, this.minutes.toString().padStart(2, '0'))
.replace(/\bm\b/, this.minutes)
.replace(/\bss\b/, this.seconds.toString().padStart(2, '0'))
.replace(/\bs\b/, this.seconds)
.replace(/\bS\b/, this.milliseconds)
.replace(/\ba\b/, this.#Date
.toLocaleString(this.locale, {hour: '2-digit'})
.replace(
this.#Date.toLocaleString(this.locale,
{hour: '2-digit', hour12: false}),
''
).trim())
}
// Usage Example:date.format('MMMM, DD(DDD) YYYY, HH:mm[S] a');
// February, 23(Tue) 2021, 04:44[546] PM
static get weekInMilliseconds() {
return 604800000;
}
static get dayInMilliseconds() {
return 86400000;
}
static get hourInMilliseconds() {
return 3600000;
}
static get minuteInMilliseconds() {
return 60000;
}
static get quarterInMilliseconds() {
return 7884000000;
}
static get monthInMilliseconds() {
return 2629800000;
}
differenceFrom(date = null) {
const {timestamp, month, year, hours} =
date instanceof DateTime ? date : new DateTime(date);
return {
days: Math.round((this.timestamp - timestamp) / DateTime.dayInMilliseconds),
years: Math.round((this.timestamp - timestamp) / (DateTime.dayInMilliseconds * 365)),
months: Math.round((this.timestamp - timestamp) / DateTime.monthInMilliseconds),
weeks: Math.round((this.timestamp - timestamp) / DateTime.weekInMilliseconds),
quarters: Math.round((this.timestamp - timestamp) / DateTime.quarterInMilliseconds),
hours: Math.round((this.timestamp - timestamp) / DateTime.hourInMilliseconds),
minutes: Math.round((this.timestamp - timestamp) / DateTime.minuteInMilliseconds),
seconds: Math.round((this.timestamp - timestamp) / 1000)
}
}
// Usage Example:
const diff = date.differenceFrom(DateTime.now);
diff.days; // 25
diff.months; // 1
diff.years; // 0
get relativeFormat() {
const rtf = new Intl.RelativeTimeFormat(this.locale, {
localeMatcher: "best fit",
numeric: "auto",
style: "long"
});
const diff = DateTime.now.differenceFrom(this);
return {
days: rtf.format(diff.days, "day"),
months: rtf.format(diff.months, "month"),
weeks: rtf.format(diff.weeks, "week"),
quarters: rtf.format(diff.quarters, "quarter"),
years: rtf.format(diff.years, "year"),
hours: rtf.format(diff.hours, "hours"),
minutes: rtf.format(diff.minutes, "minute"),
seconds: rtf.format(diff.seconds, "second")
}
}
// Usage Example:
const date = new DateTime(2021, 1, 24);
const relativeTime = date.relativeFormat;relativeTime.days // yesterday
relativeTime.minutes // 1,063 minutes ago
relativeTime.hours // 18 hours ago

Comparison methods

is() {
const thisDate = this;

const getTimestamp = date =>
date instanceof DateTime ? date.timestamp : date.getTime();
return {
before: (date) => thisDate.timestamp < getTimestamp(date),
after: (date) => thisDate.timestamp > getTimestamp(date),
equalTo: (date) => thisDate.timestamp === getTimestamp(date),
inRange: (date1, date2) =>
thisDate.timestamp > getTimestamp(date1) &&
thisDate.timestamp <= getTimestamp(date2),
weekend: () => [7, 1].includes(this.dayOfTheWeek),
weekdays: () => [2, 3, 4, 5, 6].includes(this.dayOfTheWeek),
am: () => this.amPm === 'AM',
pm: () => this.amPm === 'PM',
future: () => thisDate.timestamp > Date.now(),
past: () => thisDate.timestamp < Date.now(),
leapYear: () => DateTime.isLeapYear(thisDate.year),
monday: () => this.dayOfTheWeek === 2,
tuesday: () => this.dayOfTheWeek === 3,
wednesday: () => this.dayOfTheWeek === 4,
thursday: () => this.dayOfTheWeek === 5,
friday: () => this.dayOfTheWeek === 6,
saturday: () => this.dayOfTheWeek === 7,
sunday: () => this.dayOfTheWeek === 1,
firstQuarter: () => [1, 2, 3].includes(this.month),
secondQuarter: () => [4, 5, 6].includes(this.month),
thirdQuarter: () => [7, 8, 9].includes(this.month),
forthQuarter: () => [10, 11, 12].includes(this.month),
}
}
// Usage Example:
const date = new DateTime(2021, 1, 25);
date.is().before(Date.now()) // true
date.is().monday() // false

Simple math methods

add(value) {
const date = new Date(this.timestamp);
return {
get days() {
return new DateTime(date.setDate(date.getDate() + value));
},
get months() {
return new DateTime(date.setMonth(date.getMonth() + value));
},
get years() {
return new DateTime(date.setFullYear(date.getFullYear() + value));
},
get hours() {
return new DateTime(date.setHours(date.getHours() + value));
},
get minutes() {
return new DateTime(date.setMinutes(date.getMinutes() + value));
},
get seconds() {
return new DateTime(date.setSeconds(date.getSeconds() + value));
},
get milliseconds() {
return new DateTime(date.setMilliseconds(date.getMilliseconds() + value));
},
}
}
// Usage Example:
date.add(12).days
date.add(1).years.add(30).days
subtract(value) {
const date = new Date(this.timestamp);
return {
get days() {
return new DateTime(date.setDate(date.getDate() - value));
},
get months() {
return new DateTime(date.setMonth(date.getMonth() - value));
},
get years() {
return new DateTime(date.setFullYear(date.getFullYear() - value));
},
get hours() {
return new DateTime(date.setHours(date.getHours() - value));
},
get minutes() {
return new DateTime(date.setMinutes(date.getMinutes() - value));
},
get seconds() {
return new DateTime(date.setSeconds(date.getSeconds() - value));
},
get milliseconds() {
return new DateTime(date.setMilliseconds(date.getMilliseconds() - value));
},
}
}
// Usage Example:date.subtract(1).years
date.subtract(6).months.subtract(8).hours

Final Details

clone() {
return new DateTime(this.timestamp);
}

Conclusion

Blog & YouTube Channel for Web, UI & Software Development - beforesemicolon.comyoutube.com/c/BeforeSemicolon

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store