//Initialise functions
{
if (!global.Date)
/**
* The namespace for all UF/Object utility functions, typically for static methods.
*
* @namespace Date
*/
global.Date = {};
/**
* @type {number[]}
*/
Date.bc_leap_years = [
-45, -42, -39, -36, -33, -30, -27, -24, -21, -18, -15, -12, -9 //(Ideler 1825); Triennial leap years
];
/**
* @type {{"<month_key>": { name: string, month: number, days: number, leap_year_days: number }}}
*/
Date.months = {
january: {
name: "January",
days: 31
},
february: {
name: "February",
days: 28,
leap_year_days: 29
},
march: {
name: "March",
days: 31
},
april: {
name: "April",
days: 30
},
may: {
name: "May",
days: 31
},
june: {
name: "June",
days: 30
},
july: {
name: "July",
days: 31
},
august: {
name: "August",
days: 31
},
september: {
name: "September",
days: 30
},
october: {
name: "October",
days: 31
},
november: {
name: "November",
days: 30
},
december: {
name: "December",
days: 31
}
};
/**
* @type {string[]}
*/
Date.all_months = Object.keys(Date.months);
for (let i = 0; i < Date.all_months.length; i++)
Date.months[Date.all_months[i]].month = i;
/**
* Returns a UF Date object from a proper {@link Date} object.
* @alias Date.fromDate
*
* @param arg0_date
* @returns {{year: number, month, day: *|number, hour: number, minute: number}}
*/
Date.fromDate = function (arg0_date) {
//Convert from parameters
let date = Date.getDate(arg0_date);
//Return statement
return {
year: date.getFullYear(),
month: date.getMonth() + 1,
day: date.getDate(),
hour: date.getHours(),
minute: date.getMinutes()
}
};
/**
* Returns a blank date template starting at '0AD'.
* @alias Date.getBlankDate
*
* @returns {{year: number, month: number, day: number, hour: number, minute: number}}
*/
Date.getBlankDate = function () {
//Return statement
return { year: 0, month: 1, day: 1, hour: 0, minute: 0 }; //Must be Year 0, even if it doesn't exist
};
/**
* Returns the present Date.
* @alias Date.getCurrentDate
*
* @returns {{year: number, month, day: number, hour: number, minute: number}}
*/
Date.getCurrentDate = function () {
//Return statement
return Date.fromDate(new Date());
};
/**
* Returns a Date object from a variable type.
* @alias Date.getDate
*
* @param {Date|Object|number|string} arg0_date
*
* @returns {Date}
*/
Date.getDate = function (arg0_date) {
//Convert from parameters
let date = arg0_date;
//Declare local instance variables
if (typeof date === "string" && !isNaN(parseInt(date))) date = parseInt(date);
if (typeof date === "number") date = new Date(date);
if (typeof date === "string") date = Date.parse(date);
if (typeof date === "object" && !(date instanceof Date))
if (Object.hasKeys(date, ["year", "month", "day", "hour", "minute"], { mode: "any" }))
date = new Date(date.year, date.month, date.day, date.hour, date.minute);
//Return statement
return date;
};
/**
* Returns the number of days in the `.months` value within a Date object.
* @alias Date.getDaysInMonths
*
* @param {Object} arg0_date_object
*
* @returns {number}
*/
Date.getDaysInMonths = function (arg0_date_object) {
//Convert from parameters
let date_obj = arg0_date_object;
//Declare local instance variables
let all_months = Object.keys(Date.months);
let days = 0;
//Iterate over all elapsed months
for (let i = 0; i < date_obj.month - 1; i++)
days += Date.months[all_months[i]].days;
if (Date.isLeapYear(date_obj.year) && date_obj.month >= 2) days++;
//Return statement
return days;
};
/**
* Fetches the number of leap years before a certain year.
* @alias Date.getLeapYearsBefore
*
* @param {number} arg0_year
*
* @returns {number}
*/
Date.getLeapYearsBefore = function (arg0_year) {
//Convert from parameters
let year = parseInt(arg0_year);
//Return statement
return year/4 - year/100 + year/400 - 1; //4AD was not a leap year
};
/**
* Fetches the number of leap years between two years.
* @alias Date.getLeapYearsBetween
*
* @param {number|string} arg0_start_year
* @param {number|string} arg1_end_year
*
* @returns {number}
*/
Date.getLeapYearsBetween = function (arg0_start_year, arg1_end_year) {
//Convert from parameters
let start_year = parseInt(arg0_start_year);
let end_year = parseInt(arg1_end_year);
//Return statement
return Date.getLeapYearsBefore(end_year) - Date.getLeapYearsBefore(start_year + 1);
};
/**
* Fetches the month index from its name.
* @alias Date.getMonth
*
* @param {string} arg0_month_name
*
* @returns {number}
*/
Date.getMonth = function (arg0_month_name) {
//Convert from parameters
let month_name = arg0_month_name.toString().toLowerCase();
//Declare local instance variables
let all_months = Object.keys(Date.months);
let month_found = all_months.indexOf(month_name) + 1;
if (!month_found)
for (let i = 0; i < all_months.length; i++)
if (all_months[i].includes(month_name)) {
month_found = i + 1;
break;
}
if (!month_found) month_found = 1;
//Return statement
return month_found;
};
/**
* Returns the number of months from the number of `.day` within the date object.
* @alias Date.getMonthsFromDays
*
* @param {Object} arg0_date_object
*
* @returns {number}
*/
Date.getMonthsFromDays = function (arg0_date_object) {
// Convert from parameters
let date_obj = Date.convertTimestampToDate(arg0_date_object);
//Declare local instance variables
let is_leap_year = Date.isLeapYear(date_obj.year);
let remaining_days = date_obj.day;
let months = 1;
// Iterate over all months and count full ones
let all_months = Object.keys(Date.months);
for (let i = 0; i < all_months.length; i++) {
let local_month = Date.months[all_months[i]];
let days_in_month = local_month.days;
if (is_leap_year && local_month.leap_year_days)
days_in_month = local_month.leap_year_days;
if (remaining_days > days_in_month) {
remaining_days -= days_in_month;
months++;
} else {
break; // stop once we reach the current month
}
}
// Clamp just in case (should never exceed 12)
if (months > 12) months = 12;
// Return statement
return months;
};
/**
* Returns the modern timestamp (UNIX-based) for a given date.
* @alias Date.getModernTimestamp
*
* @param {Date|any} arg0_date
*
* @returns {number}
*/
Date.getModernTimestamp = function (arg0_date) {
//Convert from parameters
let date = Date.getDate(arg0_date);
//Return statement
return date.getTime();
};
/**
* Returns a numeric timestamp from a specific Date object. Number of minutes from 1 January, 00:00 on 1AD.
* @alias Date.getTimestamp
*
* @param {Object} arg0_date_object
*
* @returns {number}
*/
Date.getTimestamp = function (arg0_date_object) {
let date = arg0_date_object ? arg0_date_object : Date.getBlankDate();
if (typeof date === "string")
if (!isNaN(parseFloat(date))) return parseFloat(date);
let date_obj = {
...Date.getBlankDate(),
...date,
};
let minutes = 0;
// Normalise minor overflows
if (date_obj.minute >= 60) {
date_obj.hour += Math.floor(date_obj.minute / 60);
date_obj.minute %= 60;
}
if (date_obj.hour >= 24) {
date_obj.day += Math.floor(date_obj.hour / 24);
date_obj.hour %= 24;
}
while (date_obj.month > 12) {
date_obj.month -= 12;
date_obj.year++;
}
while (date_obj.month < 1) {
date_obj.month += 12;
date_obj.year--;
}
// 1. Add full years before the current one
// Epoch = start of year 0, so we sum years [0 .. year-1] or
// subtract years [-1 .. year] depending on direction.
if (date_obj.year > 0) {
for (let i = 0; i < date_obj.year; i++)
minutes += (Date.isLeapYear(i) ? 366 : 365) * 24 * 60;
} else if (date_obj.year < 0) {
for (let i = -1; i >= date_obj.year; i--)
minutes -= (Date.isLeapYear(i) ? 366 : 365) * 24 * 60;
}
// year === 0 contributes 0 full-year minutes (we're inside year 0)
// 2. Add completed months of current year
let all_months = Object.keys(Date.months);
for (let i = 1; i < date_obj.month; i++) {
let local_month = Date.months[all_months[i - 1]];
let dim = Date.isLeapYear(date_obj.year)
? local_month.leap_year_days || local_month.days
: local_month.days;
minutes += dim * 24 * 60;
}
// 3. Add completed days
minutes += (date_obj.day - 1) * 24 * 60;
// 4. Add partial day
minutes += date_obj.hour * 60 + date_obj.minute;
return minutes;
};
/**
* Whether the given year is a leap year.
* @alias Date.isLeapYear
*
* @param {number} arg0_year
*
* @returns {boolean}
*/
Date.isLeapYear = function (arg0_year) {
//Convert from parameters
let year = parseInt(arg0_year);
//Return statement
if (Date.bc_leap_years.indexOf(year) !== -1) return true;
return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0) && year !== 4);
};
/**
* Parses a {@link float} number of years into a specific Date object.
* @alias Date.parseYears
*
* @param {number} arg0_years
*
* @returns {{year: number, month: number, day: number, hour: number, minute: number}}
*/
Date.parseYears = function (arg0_years) {
// Convert from parameters
let years = parseFloat(arg0_years);
// Declare local instance variables
let date_obj = Date.getBlankDate();
// 1. Parse whole years
date_obj.year = Math.floor(years);
let remainder = years - date_obj.year;
// 2. Convert remaining fractional years into days
let days = remainder * 365; // Approximation
date_obj.day = Math.floor(days);
// 3. Parse months (1-based)
date_obj.month = 1;
let all_months = Object.keys(Date.months);
for (let i = 0; i < all_months.length; i++) {
let local_month = Date.months[all_months[i]];
let days_in_month = local_month.days;
if (
Date.isLeapYear(date_obj.year) &&
local_month.leap_year_days
) {
days_in_month = local_month.leap_year_days;
}
if (date_obj.day >= days_in_month) {
date_obj.day -= days_in_month;
date_obj.month++;
if (date_obj.month > 12) {
date_obj.month = 1;
date_obj.year++;
}
} else {
break;
}
}
// 4. Convert remaining day fraction into hours
let remaining_day_fraction = days - Math.floor(days);
date_obj.hour = Math.floor(remaining_day_fraction * 24);
// Return statement
return date_obj;
};
}