/**
*
* @class DateTime contains various date and time utility functions.
* @hideconstructor
*
* @copyright (c) 2021 TLF Research Ltd.
*/
function DateTime() { }
DateTime.JANUARY = 0;
DateTime.FEBRUARY = 1;
DateTime.MARCH = 2;
DateTime.APRIL = 3;
DateTime.MAY = 4;
DateTime.JUNE = 5;
DateTime.JULY = 6;
DateTime.AUGUST = 7;
DateTime.SEPTEMBER = 8;
DateTime.OCTOBER = 9;
DateTime.NOVEMBER = 10;
DateTime.DECEMBER = 11;
DateTime.MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
DateTime.MONTHS_SHORT = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
DateTime.SUNDAY = 0;
DateTime.MONDAY = 1;
DateTime.TUESDAY = 2;
DateTime.WEDNESDAY = 3;
DateTime.THURSDAY = 4;
DateTime.FRIDAY = 5;
DateTime.SATURDAY = 6;
DateTime.DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
DateTime.DAYS_SHORT = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
DateTime.rxDateISO8601YearMonth = /^(\d{4})-(\d{2})$/;
DateTime.rxDateISO8601Short = /^(\d{4})-(\d{2})-(\d{2})$/;
DateTime.rxDateISO8601Long = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?(?:[+\-]\d\d:?\d\d|[+\-]\d\d|Z)?$/;
/**
* Returns an object contaiing a set of preset date ranges, optionally based on the given date.
*
* @example
*
* const base = new Date('March 15, 2020 12:34:56');
* const presets = DateTime.getPresets(base, 3);
* // presets is:
* // {
* // today: {starts: Sun Mar 15 2020 00:00:00 GMT+0000 (Greenwich Mean Time), ends: Sun Mar 15 2020 23:59:59 GMT+0000 (Greenwich Mean Time)},
* // yesterday: {starts: Sat Mar 14 2020 00:00:00 GMT+0000 (Greenwich Mean Time), ends: Sat Mar 14 2020 23:59:59 GMT+0000 (Greenwich Mean Time)},
* // thisWeek: {starts: Mon Mar 09 2020 00:00:00 GMT+0000 (Greenwich Mean Time), ends: Sun Mar 15 2020 23:59:59 GMT+0000 (Greenwich Mean Time)},
* // lastWeek: {starts: Mon Mar 02 2020 00:00:00 GMT+0000 (Greenwich Mean Time), ends: Sun Mar 08 2020 23:59:59 GMT+0000 (Greenwich Mean Time)},
* // thisMonth: {starts: Sun Mar 01 2020 00:00:00 GMT+0000 (Greenwich Mean Time), ends: Tue Mar 31 2020 23:59:59 GMT+0100 (British Summer Time)},
* // thisMonthLastYear: {starts: Fri Mar 01 2019 00:00:00 GMT+0000 (Greenwich Mean Time), ends: Sun Mar 31 2019 23:59:59 GMT+0100 (British Summer Time)},
* // lastMonth: {starts: Sat Feb 01 2020 00:00:00 GMT+0000 (Greenwich Mean Time), ends: Sat Feb 29 2020 23:59:59 GMT+0000 (Greenwich Mean Time)},
* // thisQuarter: {starts: Wed Jan 01 2020 00:00:00 GMT+0000 (Greenwich Mean Time), ends: Tue Mar 31 2020 23:59:59 GMT+0100 (British Summer Time)},
* // lastQuarter: {starts: Tue Oct 01 2019 00:00:00 GMT+0100 (British Summer Time), ends: Tue Dec 31 2019 23:59:59 GMT+0000 (Greenwich Mean Time)},
* // thisYear: {starts: Wed Jan 01 2020 00:00:00 GMT+0000 (Greenwich Mean Time), ends: Thu Dec 31 2020 23:59:59 GMT+0000 (Greenwich Mean Time)},
* // lastYear: {starts: Tue Jan 01 2019 00:00:00 GMT+0000 (Greenwich Mean Time), ends: Tue Dec 31 2019 23:59:59 GMT+0000 (Greenwich Mean Time)}
* // thisFinancialYear: {starts: Wed Apr 01 2019 00:00:00 GMT+0000 (Greenwich Mean Time), ends: Thu Mar 31 2020 23:59:59 GMT+0000 (Greenwich Mean Time)},
* // lastFinancialYear: {starts: Wed Apr 01 2018 00:00:00 GMT+0000 (Greenwich Mean Time), ends: Thu Mar 31 2019 23:59:59 GMT+0000 (Greenwich Mean Time)},
* // twoYearsAgoFinancialYear: {starts: Wed Apr 01 2017 00:00:00 GMT+0000 (Greenwich Mean Time), ends: Thu Mar 31 2018 23:59:59 GMT+0000 (Greenwich Mean Time)},
* // }
*
* @param {date} [date=today] Base date for presets
* @param {date} [quarterStartMonth=0] Zer-based month when a new quarter starts (default is 0 = Jan)
*
* @return {object}
*/
DateTime.getPresets = function (date, quarterStartMonth = 0) {
date = date || new Date();
const todayStarts = DateTime.setStartOfDay(date);
const todayEnds = DateTime.setEndOfDay(date);
const lastMondayStarts = date.getDay() === 1 ? DateTime.setStartOfDay(date) : DateTime.getPreviousDay(todayStarts, DateTime.MONDAY);
const lastSundayEnds = new Date(lastMondayStarts.getTime() - 1000);
const firstOfThisMonth = DateTime.getFirstOfMonth(todayStarts);
const firstOfThisMonthLastYear = DateTime.addYears(firstOfThisMonth, -1);
const thisYearStarts = DateTime.getFirstOfYear(todayStarts);
return {
today: {
starts: todayStarts,
ends: todayEnds
},
yesterday: {
starts: DateTime.addDays(todayStarts, -1),
ends: DateTime.addDays(todayEnds, -1)
},
thisWeek: {
starts: lastMondayStarts,
ends: DateTime.setEndOfDay(DateTime.addDays(lastMondayStarts, 6))
},
lastWeek: {
starts: DateTime.setStartOfDay(DateTime.getPreviousDay(lastSundayEnds, DateTime.MONDAY)),
ends: lastSundayEnds
},
thisMonth: {
starts: firstOfThisMonth,
ends: DateTime.getLastOfMonth(date)
},
thisMonthLastYear: {
starts: firstOfThisMonthLastYear,
ends: DateTime.getLastOfMonth(firstOfThisMonthLastYear)
},
lastMonth: {
starts: DateTime.addMonths(firstOfThisMonth, -1),
ends: DateTime.setEndOfDay(DateTime.addDays(firstOfThisMonth, -1))
},
thisQuarter: DateTime.getQuarter(0, DateTime.JANUARY, date),
lastQuarter: DateTime.getQuarter(-1, DateTime.JANUARY, date),
thisYear: {
starts: thisYearStarts,
ends: DateTime.getLastOfYear(date)
},
lastYear: {
starts: DateTime.addYears(thisYearStarts, -1),
ends: DateTime.setEndOfDay(DateTime.addDays(thisYearStarts, -1))
},
thisFinancialYear: this.getCurrentFinancialYear(todayStarts, quarterStartMonth),
lastFinancialYear: this.getCurrentFinancialYear(DateTime.addYears(todayStarts, -1), quarterStartMonth),
twoYearsAgoFinancialYear: this.getCurrentFinancialYear(DateTime.addYears(todayStarts, -2), quarterStartMonth),
}
};
/**
* Adds (subtracts if <code>months</code> is negative) the given number of months to the given date. The supplied <code>date</code> object remains unchanged.
* It's smart about handling months of different lengths so e.g. 31 Oct + 1 month = 30 Nov and 31 March - 1 month = 28 (or 29) Feb.
*
* @example
*
* const base = new Date('March 31, 2020 12:34:56');
* const d1 = DateTime.addMonths(base, 1);
* const d2 = DateTime.addMonths(base, -1);
* // d1 is Thu Apr 30 2020 12:34:56 GMT+0100 (British Summer Time)
* // d2 is Sat Feb 29 2020 12:34:56 GMT+0000 (Greenwich Mean Time)
*
* @param {date} date Base date for calculation
* @param {number} months Number of months to add or subtract if negative)
*
* @returns {date} Modified date
*/
DateTime.addMonths = function (date, months) {
if (!(date instanceof Date)) {
console.error(`${date} is not a valid Date object`);
return date;
};
let clone = new Date(date.getTime());
const currentDate = clone.getDate();
const currentLastDate = new Date(clone.getFullYear(), clone.getMonth() + 1, 0).getDate();
const targetLastDate = new Date(clone.getFullYear(), clone.getMonth() + 1 + months, 0).getDate();
if (currentDate > targetLastDate) {
clone.setDate(targetLastDate);
}
clone.setMonth(clone.getMonth() + months);
if (currentDate === currentLastDate) {
clone.setDate(targetLastDate);
}
return clone;
};
/**
* Returns a 'YYYY-MM-DD' formatted date string suitable for inclusion in an SQL query.
*
* @example
*
* const base = new Date('March 31, 2020 12:34:56');
* const sql = DateTime.getSQLDate(base);
* // sql is "2020-03-31"
*
* @param {date} date Date to be formatted
* @return {string} Formatted date
*/
DateTime.getSQLDate = function (date) {
return date ? date.getFullYear() + '-' + String(date.getMonth() + 1).padStart(2, '0') + '-' + String(date.getDate()).padStart(2, '0') : '';
};
/**
* Returns a 'YYYY-MM' formatted date string suitable for inclusion in an SQL query.
*
* @example
*
* const base = new Date('March 31, 2020 12:34:56');
* const sql = DateTime.getSQLDate(base);
* // sql is "2020-03"
*
* @param {date} date Date to be formatted
* @return {string} Formatted date
*/
DateTime.getSQLMonth = function (date) {
return date ? date.getFullYear() + '-' + String(date.getMonth() + 1).padStart(2, '0') : '';
};
/**
* Returns a 'Qn yyyy' formatted date string
*
* @example
*
* const base = new Date('March 31, 2020 12:34:56');
* const s = DateTime.toQuarter(base);
* // s is "Q1 2020"
*
* @param {date|string} Date to be formatted
* @param {number} [starts] Month that Q1 starts (0-based) - default 0 (January)
* @return {string} Formatted date
*/
DateTime.toQuarter = function (date, starts = 0) {
const dateString = typeof date === 'string' ? date : DateTime.getSQLDate(date);
const dateParts = dateString.split('-');
if (dateParts.length < 2) {
console.error(`${date} is not a valid date - must be of the form YYYY-MM or YYYY-MM-DD`);
return 'INVALID';
}
const year = parseInt(dateParts[0], 10);
if (Number.isNaN(year) || year < 2015 || year > 2035) {
console.error(`${date} is not a valid date - must be of the form YYYY-MM or YYYY-MM-DD`);
return 'INVALID';
}
const month = parseInt(dateParts[1], 10);
if (Number.isNaN(month) || month < 1 || month > 12) {
console.error(`${date} is not a valid date - must be of the form YYYY-MM or YYYY-MM-DD`);
return 'INVALID';
}
const q = Math.ceil((month - starts) / 3);
const quarter = q < 1 ? q + 4 : q;
return `Q${quarter} ${year}`;
};
/**
* Returns an SQL <code>BETWEEN</code> clause to select all dates in the given range.
*
* @example
*
* const start = new Date('March 15, 2020 12:34:56');
* const end = new Date('July 11, 2020 12:34:56');
* const sql = DateTime.getSQLRange(start, end);
* // sql is 'BETWEEN "2020-03-15 00:00:00" AND "2020-07-11 23:59:59"'
*
* @param {date} from Start of date range
* @param {date} to End of date range
*
* @return {string} SQL <code>BETWEEN</code> clause required to <code>SELECT</code> the given range
*/
DateTime.getSQLRange = function (from, to) {
return 'BETWEEN "' + DateTime.getSQLDate(from) + ' 00:00:00" AND "' + DateTime.getSQLDate(to) + ' 23:59:59"';
};
/**
* Adds (subtracts if <code>years</code> is negative) the given number of years to the given date (subtracts if year is negative). The supplied <code>date</code> object remains unchanged.
* It handles leap years properly so 29 Feb 2020 - 1 year = 28 Feb 2019, but 29 Feb 2020 - 4 years = 29 Feb 2016
*
* @example
*
* const base = new Date('March 15, 2020 12:34:56');
* const d1 = DateTime.addYears(base, 1);
* const d2 = DateTime.addYears(base, -1);
* // d1 is Mon Mar 15 2021 12:34:56 GMT+0000 (Greenwich Mean Time)
* // d2 is Fri Mar 15 2019 12:34:56 GMT+0000 (Greenwich Mean Time)
*
* @param {date} date Base date for calculation
* @param {number} years Number of years to add (or subtract if negative)
*
* @returns {date} Modified date
*/
DateTime.addYears = function (date, years) {
if (!(date instanceof Date)) {
console.error(`${date} is not a valid Date object`);
return date;
};
let result = new Date(date.getTime());
const wasFeb28 = result.getMonth() === 1 && result.getDate() === 28;
const wasFeb29 = result.getMonth() === 1 && result.getDate() === 29;
const oldYearWasLeap = result.getFullYear() % 4 === 0;
const newYearIsLeap = (result.getFullYear() + years) % 4 === 0;
if (wasFeb29 && !newYearIsLeap) {
result.setDate(28);
}
result.setYear(result.getFullYear() + years);
if (wasFeb28 && !oldYearWasLeap && newYearIsLeap) {
result.setDate(29);
}
return result;
};
/**
* Returns a date with one or more elements set in the given base date.
* The supplied <code>date</code> object remains unchanged.
* NOTE: The m parameter is zero-based.
*
* @example
*
* const base = new Date('March 15, 2020 12:34:56');
* const d1 = DateTime.getAbsoluteDate(1999, null, null, base);
* const d2 = DateTime.getAbsoluteDate(null, 1, null, base);
* const d3 = DateTime.getAbsoluteDate(null, null, 3, base);
* // d1 is Mon Mar 15 1999 12:34:56 GMT+0000 (Greenwich Mean Time)
* // d2 is Sat Feb 15 2020 12:34:56 GMT+0000 (Greenwich Mean Time)
* // d3 is Tue Mar 03 2020 12:34:56 GMT+0000 (Greenwich Mean Time)
*
* @param {number|null} y Set absolute year.
* @param {number|null} m Set absolute month index (0-based).
* @param {number|null} d Set absolute date.
* @param {date} [base=today] Base date to apply the absolute values to.
*
* @returns {date} Modified date
*/
DateTime.getAbsoluteDate = function (y, m, d, base) {
let clone = base ? new Date(base.getTime()) : new Date();
if (y !== null) {
clone.setFullYear(y)
}
if (m !== null) {
clone.setMonth(m)
}
if (d !== null) {
clone.setDate(d)
}
return clone;
};
/**
* Adds (subtracts if <code>days</code> is negative) the given number of days to the given date. The supplied <code>date</code> object remains unchanged.
*
* @example
*
* const base = new Date('March 1, 2020 12:34:56');
* const d1 = DateTime.addDays(base, 1);
* const d2 = DateTime.addDays(base, -1);
* // d1 is Mon Mar 02 2020 12:34:56 GMT+0000 (Greenwich Mean Time)
* // d2 is Sat Feb 29 2020 12:34:56 GMT+0000 (Greenwich Mean Time)
*
* @param {date} date Base date for calculation
* @param {number} days Number of days to add (or subtract if negative)
*
* @returns {date} Modified date
*/
DateTime.addDays = function (date, days) {
if (!(date instanceof Date)) {
console.error(`${date} is not a valid Date object`);
return date;
};
let clone = new Date(date.getTime());
clone.setDate(clone.getDate() + days);
return clone;
};
/**
* Returns the date representing the first day of the year in the given date (or the first day of the current year if no date is provided).
* The supplied <code>date</code> object remains unchanged.
*
* @example
*
* const base = new Date('March 15, 2020 12:34:56');
* const d = DateTime.getFirstOfYear(base);
* // d is Wed Jan 01 2020 00:00:00 GMT+0000 (Greenwich Mean Time)
*
* @param {date} [date=today] Base date for calculation
*
* @return {date} First day of the year for the given date
*/
DateTime.getFirstOfYear = function (date) {
date = date || new Date();
return new Date(date.getFullYear(), 0, 1);
};
/**
* Returns the date representing the last day of the year in the given date (or the last day of the current year if no date is provided).
* The supplied <code>date</code> object remains unchanged.
*
* @example
*
* const base = new Date('March 15, 2020 12:34:56');
* const d = DateTime.getLastOfYear(base);
* // d is Thu Dec 31 2020 23:59:59 GMT+0000 (Greenwich Mean Time)
*
* @param {date} [date=today] Base date for calculation
*
* @return {date} Last day of the year for the given date
*/
DateTime.getLastOfYear = function (date) {
date = date || new Date();
return DateTime.setEndOfDay(new Date(date.getFullYear(), 11, 31));
};
/**
* Returns the date representing the first day of the given date's month (or the first day of the current month if no date is provided).
* The supplied <code>date</code> object remains unchanged.
*
* @example
*
* const base = new Date('March 15, 2020 12:34:56');
* const d = DateTime.getFirstOfMonth(base);
* // d is Sun Mar 01 2020 00:00:00 GMT+0000 (Greenwich Mean Time)
*
* @param {date} [date=today] Base date for calculation
*
* @return {date} First day of the given date's month
*/
DateTime.getFirstOfMonth = function (date) {
date = date || new Date();
return new Date(date.getFullYear(), date.getMonth(), 1);
};
/**
* Returns the last day of the given date's month (or the current month if no date is provided).
* The supplied <code>date</code> object remains unchanged.
*
* @example
*
* const base = new Date('March 15, 2020 12:34:56');
* const d = DateTime.getLastOfMonth(base);
* // d is Tue Mar 31 2020 23:59:59 GMT+0100 (British Summer Time)
*
* @param {date} [date=today] Base date for calculation
*
* @return {date} Last day of the given date's month
*/
DateTime.getLastOfMonth = function (date) {
date = date || new Date();
return DateTime.setEndOfDay(new Date(date.getFullYear(), date.getMonth() + 1, 0));
};
/**
* Returns the date of the last day of the given month in the given year.
*
* @example
*
* const d1 = DateTime.getLastOfMonthDate(1, 2020);
* const d2 = DateTime.getLastOfMonthDate(1, 2021);
* // d1 is 29
* // d2 is 28
*
* @param {number} month Zero-based month
* @param {number} year Year
*
* @return {number} Last day of the given month in the given year
*/
DateTime.getLastOfMonthDate = function (month, year) {
return new Date(year, month + 1, 0).getDate();
};
/**
* Returns the date of the previous weekday relative to the given date.
* If the base <code>date</code>'s weekday matches the required weekday parameter then the returned result is the same as the base date.
* The supplied <code>date</code> object remains unchanged.
*
* @example
*
* const base = new Date('March 15, 2020 12:34:56'); // Sunday
* const d1 = DateTime.getPreviousDay(base, DateTime.SUNDAY);
* const d2 = DateTime.getPreviousDay(base, DateTime.MONDAY);
* const d3 = DateTime.getPreviousDay(base, DateTime.SATURDAY);
* // d1 is Sun Mar 15 2020 12:34:56 GMT+0000 (Greenwich Mean Time)
* // d2 is Mon Mar 09 2020 12:34:56 GMT+0000 (Greenwich Mean Time)
* // d3 is Sat Mar 14 2020 12:34:56 GMT+0000 (Greenwich Mean Time)
*
* @param {date} date Base date for calculation
* @param {number} weekday (0 = Sunday, 1 = Monday...)
*
* @return {date} Date of the previous weekday relative to the given date.
*/
DateTime.getPreviousDay = function (date, weekday) {
if (!(date instanceof Date)) {
console.error(`${date} is not a valid Date object`);
return date;
};
let clone = new Date(date.getTime());
const currDay = clone.getDay();
clone.setDate(clone.getDate() - currDay); // last Sunday
const delta = weekday < currDay ? weekday : weekday - 7;
return DateTime.addDays(clone, delta)
};
/**
* Returns the date of 'this' weekday relative to the given date. The returned date may be in the future.
* If the given date is already on the requested weekday then it's returned, otherwise the date of the previous weekday is returned.
* The supplied <code>date</code> object remains unchanged.
*
* @example
*
* const base = new Date('March 15, 2020 12:34:56'); // Sunday
* const d1 = DateTime.getThisDay(base, DateTime.SUNDAY);
* const d2 = DateTime.getThisDay(base, DateTime.MONDAY);
* const d3 = DateTime.getThisDay(base, DateTime.SATURDAY);
* // d1 is Sun Mar 15 2020 12:34:56 GMT+0000 (Greenwich Mean Time)
* // d2 is Mon Mar 09 2020 12:34:56 GMT+0000 (Greenwich Mean Time)
* // d3 is Sat Mar 14 2020 12:34:56 GMT+0000 (Greenwich Mean Time)
*
* @param {date} date Base date for calculation
* @param {number} weekday (0 = Sunday, 1 = Monday...)
*
* @return {date} Date of 'this' weekday relative to the given date
*/
DateTime.getThisDay = function (date, weekday) {
if (!(date instanceof Date)) {
console.error(`${date} is not a valid Date object`);
return date;
};
return date.getDay() === weekday ? date : DateTime.getPreviousDay(DateTime.addDays(date, 7), weekday);
};
/**
* Returns a range object that defines the current financial year.
*
* @example
*
* const base = new Date('March 15, 2020 12:34:56'); // Sunday
* const d1 = DateTime.getCurrentFinancialYear(base, DateTime.JANUARY);
* const d2 = DateTime.getCurrentFinancialYear(base, DateTime.FEBRUARY);
* const d3 = DateTime.getCurrentFinancialYear(base, DateTime.MARCH);
* const d4 = DateTime.getCurrentFinancialYear(base, DateTime.APRIL);
* const d5 = DateTime.getCurrentFinancialYear(base, DateTime.OCTOBER);
* const d6 = DateTime.getCurrentFinancialYear(base, DateTime.DECEMBER);
* // d1 is {starts: Wed Jan 01 2020 00:00:00 GMT+0000 (Greenwich Mean Time), ends: Thu Dec 31 2020 00:00:00 GMT+0000 (Greenwich Mean Time)}
* // d2 is {starts: Sat Feb 01 2020 00:00:00 GMT+0000 (Greenwich Mean Time), ends: Sun Jan 31 2021 00:00:00 GMT+0000 (Greenwich Mean Time)
* // d3 is {starts: Sun Mar 01 2020 00:00:00 GMT+0000 (Greenwich Mean Time), ends: Sun Feb 28 2021 00:00:00 GMT+0000 (Greenwich Mean Time)}
* // d4 is {starts: Mon Apr 01 2019 00:00:00 GMT+0100 (British Summer Time), ends: Tue Mar 31 2020 00:00:00 GMT+0100 (British Summer Time)}
* // d5 is {starts: Tue Oct 01 2019 00:00:00 GMT+0100 (British Summer Time), ends: Wed Sep 30 2020 00:00:00 GMT+0100 (British Summer Time)}
* // d6 is {starts: Sun Dec 01 2019 00:00:00 GMT+0000 (Greenwich Mean Time), ends: Mon Nov 30 2020 00:00:00 GMT+0000 (Greenwich Mean Time)}
*
* @param {date} [date=today] Date to base the calculation on.
* @param {number} [starts] Month that the FY starts (0-based) - default 0 (January)
*
* @return {object} Range object
*/
DateTime.getCurrentFinancialYear = function (base = new Date(), starts = 0) {
const fyStarts = new Date(base.getFullYear(), starts, 1);
const fyEnds = DateTime.addDays(DateTime.addYears(fyStarts, 1), -1);
const yearHasAlreadyStarted = base >= fyStarts;
return {
starts: yearHasAlreadyStarted ? fyStarts : DateTime.addYears(fyStarts, -1),
ends: yearHasAlreadyStarted ? fyEnds : DateTime.addYears(fyEnds, -1)
}
};
/**
* Returns a range object that defines the requested quarter.
*
* @example
*
* const base = new Date('March 15, 2020 12:34:56'); // Sunday
* const curr = DateTime.getQuarter(0, DateTime.JANUARY, base);
* const q1 = DateTime.getQuarter(1, DateTime.JANUARY, base);
* const q2 = DateTime.getQuarter(2, DateTime.FEBRUARY, base);
* const lq = DateTime.getQuarter(-1, DateTime.JANUARY, base);
* // curr is {starts: Wed Jan 01 2020 00:00:00 GMT+0000 (Greenwich Mean Time), ends: Tue Mar 31 2020 23:59:59 GMT+0100 (British Summer Time)}
* // q1 is {starts: Wed Jan 01 2020 00:00:00 GMT+0000 (Greenwich Mean Time), ends: Tue Mar 31 2020 23:59:59 GMT+0100 (British Summer Time)}
* // q2 is {starts: Fri May 01 2020 00:00:00 GMT+0100 (British Summer Time), ends: Fri Jul 31 2020 23:59:59 GMT+0100 (British Summer Time)}
* // lq is {starts: Tue Oct 01 2019 00:00:00 GMT+0100 (British Summer Time), ends: Tue Dec 31 2019 23:59:59 GMT+0000 (Greenwich Mean Time)}
*
* @param {number} [quarter] If positive, returns the nth quarter (1 - 4); if 0 (default) returns the current quarter; if negative returns n quarters ago.
* @param {number} [startMonth] Calendar month corresponding to the start of the first quarter (0-based) - default January
* @param {date} [date=today] Date to base the calculation on.
*
* @return {object} Range object
*/
DateTime.getQuarter = function (quarter, startMonth, date) {
const baseDate = date || new Date();
quarter = quarter || 0;
startMonth = startMonth || DateTime.JANUARY;
const thisMonth = baseDate.getMonth();
const thisYear = baseDate.getFullYear();
// The base starts off as the beginning of the 1st quarter
let base = thisMonth < startMonth ? new Date(thisYear - 1, startMonth, 1) : new Date(thisYear, startMonth, 1);
// For zero or negative quarters move the base to the current quarter
if (quarter <= 0) {
const currentQuarter = thisMonth < startMonth ? Math.floor((12 + thisMonth - startMonth) / 3) + 1 : Math.floor((thisMonth - startMonth) / 3) + 1;
base = DateTime.addMonths(base, 3 * (currentQuarter - 1))
quarter = quarter + 1;
}
return {
starts: DateTime.addMonths(base, 3 * (quarter - 1)),
ends: DateTime.setEndOfDay(DateTime.addDays(DateTime.addMonths(base, 3 * quarter), -1))
}
};
/**
* Returns the number of the quarter for the given date and start month.
*
* @example
* const base = new Date('March 15, 2020 12:34:56'); // Sunday
* console.log(DateTime.getQuarterNumber(base, DateTime.JANUARY)); // Outputs 1
*
* @param {date} date The input date for which we want to find the quarter number.
* @param {number} startMonth Calendar month corresponding to the start of the first quarter (0-based) - default January
*
* @return {number} Quarter number in range 1 to 4
*/
DateTime.getQuarterNumber = function (date, startMonth) {
const baseDate = date || new Date();
startMonth = startMonth || DateTime.JANUARY;
const thisMonth = baseDate.getMonth();
// Calculate the current quarter based on the input date and start month
const currentQuarter = Math.floor((thisMonth - startMonth + 3) / 3);
return currentQuarter;
}
/**
* Parses a string in an ISO8601-compliant format i.e. <code>YYYY-MM</code>, <code>YYYY-MM-DD</code>, <code>YYYY-MM-DDThh:mm:ss.ssssZ</code> or <code>YYYY-MM-DDThh:mm:ss.ssss+HH:MM</code>
* <br>If the date is not in a supported format then null is returned.
* NOTE: If provided the timezone is parsed but its value is ignored.
*
* @example
*
* const d1 = DateTime.parseISO8601("2020-03");
* const d2 = DateTime.parseISO8601("2020-03-15");
* const d3 = DateTime.parseISO8601("2020-03-15T12:34:56Z");
* const d4 = DateTime.parseISO8601("2020-03-15T12:34:56.789+01:00");
* const d5 = DateTime.parseISO8601("2020-03-15T12:34:56-02:00");
* // d1 is Sun Mar 01 2020 00:00:00 GMT+0000 (Greenwich Mean Time)
* // d2 is Sun Mar 15 2020 00:00:00 GMT+0000 (Greenwich Mean Time)
* // d2 is Sun Mar 15 2020 12:34:56 GMT+0000 (Greenwich Mean Time)
* // d3 is Sun Mar 15 2020 12:34:56 GMT+0000 (Greenwich Mean Time)
* // d4 is Sun Mar 15 2020 12:34:56 GMT+0000 (Greenwich Mean Time)
*
* @param {string} s Date string to be parsed
*
* @return {date|null} Parsed date
*/
DateTime.parseISO8601 = function (s) {
if (!s) {
console.error('Missing date');
return null;
}
// Handle YYYY-MM format
let m = s.match(DateTime.rxDateISO8601YearMonth);
if (m !== null) {
return new Date(m[1], m[2] - 1, 1);
}
// Handle YYYY-MM-DD format
m = s.match(DateTime.rxDateISO8601Short);
if (m !== null) {
return new Date(m[1], m[2] - 1, m[3]);
}
// Handle YYYY-MM-DDThh:mm:ss.mmmnnnTZD format
m = s.match(DateTime.rxDateISO8601Long);
if (m === null) {
console.error('Unsupported date format', s);
return null;
}
return new Date(m[1], m[2] - 1, m[3], m[4], m[5], m[6]);
};
/**
* Returns a new Date based on the given date with the time part set to 00:00:00.
* The supplied <code>date</code> object remains unchanged.
*
* @example
*
* const base = new Date('March 15, 2020 12:34:56');
* const d = DateTime.setStartOfDay(base);
* // d is Sun Mar 15 2020 00:00:00 GMT+0000 (Greenwich Mean Time)
*
* @param {date} date Date to base the calculation on.
*
* @return {date} New date
*/
DateTime.setStartOfDay = function (date) {
if (!(date instanceof Date)) {
console.error(`${date} is not a valid Date object`);
return date;
};
let clone = new Date(date.getTime());
clone.setHours(0);
clone.setMinutes(0);
clone.setSeconds(0);
clone.setMilliseconds(0);
return clone;
};
/**
* Returns a new Date based on the given date with the time part set to 23:59:59.
* The supplied <code>date</code> object remains unchanged.
*
* @example
*
* const base = new Date('March 15, 2020 12:34:56');
* const d = DateTime.setEndOfDay(base);
* // d is Sun Mar 15 2020 23:59:59 GMT+0000 (Greenwich Mean Time)
*
* @param {date} date Date to base the calculation on.
*
* @return {date} New date
*/
DateTime.setEndOfDay = function (date) {
if (!(date instanceof Date)) {
console.error(`${date} is not a valid Date object`);
return date;
};
let clone = new Date(date.getTime());
clone.setHours(23);
clone.setMinutes(59);
clone.setSeconds(59);
clone.setMilliseconds(999);
return clone;
};
/**
* Returns the long name of the month in the given date.
*
* @example
*
* const base = new Date('March 15, 2020 12:34:56');
* const d = DateTime.getLongMonthName(base);
* // d is 'March'
*
* @param {date} date Date to base the calculation on.
*
* @return {string}
*/
DateTime.getLongMonthName = function (date) {
if (!(date instanceof Date)) {
console.error(`${date} is not a valid Date object`);
return date;
};
return date.toLocaleString('en-gb', {
month: 'long'
});
};
/**
* Returns the short name of the month in the given date.
*
* @example
*
* const base = new Date('April 15, 2020 12:34:56');
* const d = DateTime.getShortMonthName(base);
* // d is 'Apr'
*
* @param {date} date Date to base the calculation on.
*
* @return {string} Month name
*/
DateTime.getShortMonthName = function (date) {
if (!(date instanceof Date)) {
console.error(`${date} is not a valid Date object`);
return date;
};
return date.toLocaleString('en-gb', {
month: 'short'
});
};
/**
* Returns an array of formatted strings of the form 'YYYY-MM' across the required closed range [start, end].
*
* @example
*
* const months = getAllMonths(new Date('2022-02), new Date('2023-01));
*
* // months is [
* '2022-02',
* '2022-03',
* '2022-04',
* '2022-05',
* '2022-06',
* '2022-07',
* '2022-08',
* '2022-09',
* '2022-10',
* '2022-11',
* '2022-12',
* '2023-01'
* ]
*
* @param {start} date Start date of the range to be generated.
* @param {end} date End date of the range to be generated.
*
* @return {array} Array of strings of the form 'YYYY-MM' across the required range.
*/
DateTime.getAllMonths = function (start, end) {
if (!(start instanceof Date)) {
console.error(`Start ${date} is not a valid Date object`);
return date;
};
if (!(end instanceof Date)) {
console.error(`End ${date} is not a valid Date object`);
return date;
};
if (end < start) {
console.error('start must be before end');
return;
}
start.setDate(1);
end.setDate(1);
const results = [];
try {
results.push(fmt(start));
while (fmt(start) < fmt(end)) {
start = this.addMonths(start, 1);
results.push(fmt(start));
}
} catch (e) {
console.error(e);
}
return results;
function fmt(d) {
const year = d.toLocaleString('default', { year: 'numeric' });
const month = d.toLocaleString('default', { month: '2-digit' });
return `${year}-${month}`;
}
}