@barchart/common-js
Version:
Library of common JavaScript utilities
547 lines (541 loc) • 16.2 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var Day_exports = {};
__export(Day_exports, {
default: () => Day
});
module.exports = __toCommonJS(Day_exports);
var assert = __toESM(require("./assert.js"));
var comparators = __toESM(require("./../collections/sorting/comparators.js"));
var is = __toESM(require("./is.js"));
var import_ComparatorBuilder = __toESM(require("./../collections/sorting/ComparatorBuilder.js"));
var import_DayFormatType = __toESM(require("./DayFormatType.js"));
class Day {
#year;
#month;
#day;
/**
* @param {number} year
* @param {number} month
* @param {number} day
*/
constructor(year, month, day) {
if (!Day.validate(year, month, day)) {
throw new Error(`Unable to instantiate [ Day ], input is invalid [ ${year} ], [ ${month} ], [ ${day} ]`);
}
this.#year = year;
this.#month = month;
this.#day = day;
}
/**
* Calculates a new {@link Day} in the future (or past).
*
* @public
* @param {number} days - The number of days to add (negative numbers can be used for subtraction).
* @param {boolean=} inverse - If true, the sign of the "days" value will be flipped.
* @returns {Day}
*/
addDays(days, inverse) {
assert.argumentIsRequired(days, "days", Number);
assert.argumentIsOptional(inverse, "inverse", Boolean);
assert.argumentIsValid(days, "days", is.large, "is an integer");
let totalDaysToShift;
if (is.boolean(inverse) && inverse) {
totalDaysToShift = days * -1;
} else {
totalDaysToShift = days;
}
const positive = is.positive(totalDaysToShift);
let shiftedDay = this.#day;
let shiftedMonth = this.#month;
let shiftedYear = this.#year;
while (totalDaysToShift !== 0) {
let monthDaysAvailable;
let monthDaysToShift;
if (positive) {
monthDaysAvailable = Day.getDaysInMonth(shiftedYear, shiftedMonth) - shiftedDay;
monthDaysToShift = Math.min(totalDaysToShift, monthDaysAvailable);
} else {
monthDaysAvailable = 1 - shiftedDay;
monthDaysToShift = Math.max(totalDaysToShift, monthDaysAvailable);
}
totalDaysToShift = totalDaysToShift - monthDaysToShift;
if (totalDaysToShift === 0) {
shiftedDay = shiftedDay + monthDaysToShift;
} else if (positive) {
shiftedMonth++;
if (shiftedMonth > 12) {
shiftedYear++;
shiftedMonth = 1;
}
shiftedDay = 0;
} else {
shiftedMonth--;
if (shiftedMonth < 1) {
shiftedYear--;
shiftedMonth = 12;
}
shiftedDay = Day.getDaysInMonth(shiftedYear, shiftedMonth) + 1;
}
}
return new Day(shiftedYear, shiftedMonth, shiftedDay);
}
/**
* Calculates a new {@link Day} in the past (or future).
*
* @public
* @param {number} days - The number of days to subtract (negative numbers can be used for addition).
* @returns {Day}
*/
subtractDays(days) {
return this.addDays(days, true);
}
/**
* Calculates a new {@link Day} in the future (or past). If the new date is at the end of
* the month and the new month has fewer days than the current month, days will be subtracted
* as necessary (e.g. adding one month to March 31 will return April 30).
*
* @public
* @param {number} months - The number of months to add (negative numbers can be used for subtraction).
* @param {boolean=} inverse - If true, the sign of the "days" value will be flipped.
* @returns {Day}
*/
addMonths(months, inverse) {
assert.argumentIsRequired(months, "months", Number);
assert.argumentIsOptional(inverse, "inverse", Boolean);
assert.argumentIsValid(months, "months", is.large, "is an integer");
let totalMonthsToShift;
if (is.boolean(inverse) && inverse) {
totalMonthsToShift = months * -1;
} else {
totalMonthsToShift = months;
}
const monthsToShift = totalMonthsToShift % 12;
const yearsToShift = (totalMonthsToShift - monthsToShift) / 12;
let shiftedYear = this.year + yearsToShift;
let shiftedMonth = this.month + monthsToShift;
let shiftedDay = this.day;
if (shiftedMonth > 12) {
shiftedYear = shiftedYear + 1;
shiftedMonth = shiftedMonth - 12;
}
if (shiftedMonth < 1) {
shiftedYear = shiftedYear - 1;
shiftedMonth = shiftedMonth + 12;
}
while (!Day.validate(shiftedYear, shiftedMonth, shiftedDay)) {
shiftedDay = shiftedDay - 1;
}
return new Day(shiftedYear, shiftedMonth, shiftedDay);
}
/**
* Calculates a new {@link Day} in the past (or future).
*
* @public
* @param {number} months - The number of months to subtract (negative numbers can be used for addition).
* @returns {Day}
*/
subtractMonths(months) {
return this.addMonths(months, true);
}
/**
* Calculates a new {@link Day} in the future (or past). If the new date is at the end of
* the month and the new month has fewer days than the current month, days will be subtracted
* as necessary (e.g. adding one year to February 29 will return February 28).
*
* @public
* @param {number} years - The number of years to add (negative numbers can be used for subtraction).
* @param {boolean=} inverse - If true, the sign of the "days" value will be flipped.
* @returns {Day}
*/
addYears(years, inverse) {
assert.argumentIsRequired(years, "years", Number);
assert.argumentIsOptional(inverse, "inverse", Boolean);
assert.argumentIsValid(years, "years", is.large, "is an integer");
let yearsToShift;
if (is.boolean(inverse) && inverse) {
yearsToShift = years * -1;
} else {
yearsToShift = years;
}
let shiftedYear = this.year + yearsToShift;
let shiftedMonth = this.month;
let shiftedDay = this.day;
while (!Day.validate(shiftedYear, shiftedMonth, shiftedDay)) {
shiftedDay = shiftedDay - 1;
}
return new Day(shiftedYear, shiftedMonth, shiftedDay);
}
/**
* Calculates a new {@link Day} in the past (or future).
*
* @public
* @param {number} years - The number of years to subtract (negative numbers can be used for addition).
* @returns {Day}
*/
subtractYears(years) {
return this.addYears(years, true);
}
/**
* Returns a new {@link Day} instance for the start of the month referenced by the current instance.
*
* @public
* @returns {Day}
*/
getStartOfMonth() {
return new Day(this.year, this.month, 1);
}
/**
* Returns a new instance for the {@link Day} end of the month referenced by the current instance.
*
* @public
* @returns {Day}
*/
getEndOfMonth() {
return new Day(this.year, this.month, Day.getDaysInMonth(this.year, this.month));
}
/**
* Indicates if the current {@link Day} instance occurs before another day.
*
* @public
* @param {Day} other
* @returns {boolean}
*/
getIsBefore(other) {
return Day.compareDays(this, other) < 0;
}
/**
* Indicates if the current {@link Day} instance occurs after another day.
*
* @public
* @param {Day} other
* @returns {boolean}
*/
getIsAfter(other) {
return Day.compareDays(this, other) > 0;
}
/**
* Indicates the current day falls between two other days, inclusive
* of the range boundaries.
*
* @public
* @param {Day=} first
* @param {Day=} last
* @returns {boolean}
*/
getIsContained(first, last) {
assert.argumentIsOptional(first, "first", Day, "Day");
assert.argumentIsOptional(last, "last", Day, "Day");
let notAfter;
let notBefore;
if (first && last && first.getIsAfter(last)) {
notBefore = false;
notAfter = false;
} else {
notAfter = !(last instanceof Day) || !this.getIsAfter(last);
notBefore = !(first instanceof Day) || !this.getIsBefore(first);
}
return notAfter && notBefore;
}
/**
* Indicates if another {@link Day} refers to the same moment.
*
* @public
* @param {Day} other
* @returns {boolean}
*/
getIsEqual(other) {
return Day.compareDays(this, other) === 0;
}
/**
* Calculates and returns name of the day of the week (e.g. Monday, Tuesday, Wednesday, etc.).
*
* @public
* @returns {string}
*/
getName() {
const count = Day.countDaysBetween(REFERENCE_MONDAY, this);
let index = count % NAMES_OF_DAYS.length;
if (index < 0) {
index = index + NAMES_OF_DAYS.length;
}
return NAMES_OF_DAYS[index];
}
/**
* The year.
*
* @public
* @returns {number}
*/
get year() {
return this.#year;
}
/**
* The month of the year (January is one, December is twelve).
*
* @public
* @returns {number}
*/
get month() {
return this.#month;
}
/**
* The day of the month.
*
* @public
* @returns {number}
*/
get day() {
return this.#day;
}
/**
* Outputs the date as the formatted string: {year}-{month}-{day}.
*
* @public
* @returns {string}
*/
format() {
return `${leftPad(this.#year, 4, "0")}-${leftPad(this.#month, 2, "0")}-${leftPad(this.#day, 2, "0")}`;
}
/**
* Returns the JSON representation.
*
* @public
* @returns {string}
*/
toJSON() {
return this.format();
}
/**
* Clones a {@link Day} instance.
*
* @public
* @static
* @param {Day} value
* @returns {Day}
*/
static clone(value) {
assert.argumentIsRequired(value, "value", Day, "Day");
return new Day(value.year, value.month, value.day);
}
/**
* Converts a string (which matches the output of {@link Day#format}) into
* a {@link Day} instance.
*
* @public
* @static
* @param {string} value
* @param {DayFormatType=} type
* @returns {Day}
*/
static parse(value, type) {
assert.argumentIsRequired(value, "value", String);
let t;
if (type instanceof import_DayFormatType.default) {
t = type;
} else {
t = import_DayFormatType.default.YYYY_MM_DD;
}
const match = value.match(t.regex);
if (match === null) {
throw new Error(`Unable to parse value as Day [ ${value} ]`);
}
return new Day(parseInt(match[t.yearIndex]) + t.yearShift, parseInt(match[t.monthIndex]), parseInt(match[t.dayIndex]));
}
/**
* Creates a {@link Day} from the year, month, and day properties (in local time)
* of the {@link Date} argument.
*
* @public
* @static
* @param {Date} date
* @returns {Day}
*/
static fromDate(date) {
assert.argumentIsRequired(date, "date", Date);
return new Day(date.getFullYear(), date.getMonth() + 1, date.getDate());
}
/**
* Creates a {@link Day} from the year, month, and day properties (in UTC)
* of the {@link Date} argument.
*
* @public
* @static
* @param {Date} date
* @returns {Day}
*/
static fromDateUtc(date) {
assert.argumentIsRequired(date, "date", Date);
return new Day(date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate());
}
/**
* Returns a {@link Day} instance using today's local date.
*
* @public
* @static
* @returns {Day}
*/
static getToday() {
return Day.fromDate(/* @__PURE__ */ new Date());
}
/**
* Returns true if the year, month, and day combination is valid; otherwise false.
*
* @public
* @static
* @param {number} year
* @param {number} month
* @param {number} day
* @returns {boolean}
*/
static validate(year, month, day) {
return is.integer(year) && is.integer(month) && is.integer(day) && !(month < 1) && !(month > 12) && !(day < 1) && !(day > Day.getDaysInMonth(year, month));
}
/**
* Returns the number of days in a given month.
*
* @public
* @static
* @param {number} year - The year number (e.g. 2017)
* @param {number} month - The month number (e.g. 2 is February)
* @returns {number}
*/
static getDaysInMonth(year, month) {
switch (month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12: {
return 31;
}
case 4:
case 6:
case 9:
case 11: {
return 30;
}
case 2: {
if (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0) {
return 29;
} else {
return 28;
}
}
}
}
/**
* A comparator function for {@link Day} instances.
*
* @public
* @static
* @param {Day} a
* @param {Day} b
* @returns {number}
*/
static compareDays(a, b) {
assert.argumentIsRequired(a, "a", Day, "Day");
assert.argumentIsRequired(b, "b", Day, "Day");
return comparator(a, b);
}
/**
* Calculates the number of days between two {@link Day} instances (may return
* a negative value).
*
* @public
* @static
* @param {Day} a
* @param {Day} b
* @returns {number}
*/
static countDaysBetween(a, b) {
assert.argumentIsRequired(a, "a", Day, "Day");
assert.argumentIsRequired(b, "b", Day, "Day");
if (a.getIsEqual(b)) {
return 0;
}
let start;
let end;
let reversed = b.getIsBefore(a);
if (reversed) {
start = b;
end = a;
} else {
start = a;
end = b;
}
let currentMonth = start.month;
let currentYear = start.year;
let counter = 0 - start.day;
while (!(currentMonth === end.month && currentYear === end.year)) {
counter = counter + Day.getDaysInMonth(currentYear, currentMonth);
if (currentMonth === 12) {
currentMonth = 1;
currentYear = currentYear + 1;
} else {
currentMonth = currentMonth + 1;
}
}
counter = counter + end.day;
if (reversed) {
counter = counter * -1;
}
return counter;
}
/**
* Returns a string representation.
*
* @public
* @returns {string}
*/
toString() {
return "[Day]";
}
}
function leftPad(value, digits, character) {
let string = value.toString();
let padding = digits - string.length;
return `${character.repeat(padding)}${string}`;
}
const comparator = import_ComparatorBuilder.default.startWith((a, b) => comparators.compareNumbers(a.year, b.year)).thenBy((a, b) => comparators.compareNumbers(a.month, b.month)).thenBy((a, b) => comparators.compareNumbers(a.day, b.day)).toComparator();
const NAMES_OF_DAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
const REFERENCE_MONDAY = new Day(2024, 1, 1);
{
const cjsExports = module.exports;
const cjsDefaultExport = cjsExports && cjsExports.__esModule ? cjsExports.default : cjsExports;
if (cjsDefaultExport && (typeof cjsDefaultExport === 'function' || typeof cjsDefaultExport === 'object')) {
Object.keys(cjsExports).forEach((key) => {
if (key !== 'default' && key !== '__esModule') {
cjsDefaultExport[key] = cjsExports[key];
}
});
}
module.exports = cjsDefaultExport;
}