//################################################################################################
/** @file Additional Date Functionality mixin for ish.js
* @mixin ish.type.date
* @author Nick Campbell
* @license MIT
* @copyright 2003-2023, Nick Campbell
* @ignore
*/ //############################################################################################
/*global module, define */ //# Enable Node globals for JSHint
/*jshint maxcomplexity:9 */ //# Enable max complexity warnings for JSHint
(function () {
'use strict'; //<MIXIN>
function init(core) {
//################################################################################################
/** Collection of additional Date-based functionality.
* @namespace ish.type.date
* @ignore
*/ //############################################################################################
core.oop.partial(core.type, function (/*oProtected*/) {
var oDate,
enumDays = {
sun: 0,
mon: 1,
tue: 3,
wed: 4,
thu: 5,
sat: 6
},
oConfig = {
weekOfYear: enumDays.sun, //# === core.type.date.enums.weekOfYear.simple.sun
format: {
s: {
"1": "st",
"2": "nd",
"3": "rd",
"11": "th",
"12": "th",
"13": "th",
x: "th"
},
WWW: ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],
WWWW: ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],
MMM: ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],
MMMM: ["January","February","March","April","May","June","July","August","September","October","November","December"],
T: ["a","p"],
TT: ["am","pm"]
}
}
;
/*
//############################################################
//# Determines the ISO 8601 week number (also known as the 4 day rule) for the given date.
//############################################################
//# Last Updated: April 19, 2006
function weekOfYear_ISO8601(dDateTime) {
var iDaysInWeek1, iJan1, iDec31,
cTHURSDAY = 4,
iYYYY = dDateTime.getFullYear(),
oReturnVal = {
w: 0,
yyyy: iYYYY
}
;
//#### Determine the iJan1 and iDec31 days of the week for the year (index 1)
//#### NOTE: Sunday = 0 ... Saturday = 6
//#### NOTE: January = 0 ... December = 11
iJan1 = new Date(iYYYY, 0, 1).getDay();
iDec31 = new Date(iYYYY, 11, 31).getDay();
//#### Convert iJan1 to 1-based and starting on Monday (as per ISO 8601)
//#### NOTE: Due to the nature of the calculations below, we do not need to do this conversion on iDec31
//#### NOTE: Monday = 1 ... Sunday = 7
if (iJan1 == 0) {
iJan1 = 7;
}
//#### Determine the number of iDaysInWeek1
//#### NOTE: Since Monday is the start of the week (as per ISO 8601) and iJan1 was converted to conform to "Monday = 1 ... Sunday = 7" above, -8 is the correct means to calculate the iDaysInWeek1 (ex: 8 - 7 [Sun] = 1 day in that week)
iDaysInWeek1 = (8 - iJan1);
//#### Calculate the number of .w(eeks) (NOT including the first week) between the start of the year (index 1) and the passed dDateTime
//#### NOTE: Partial weeks are also counted thanks to the rounding up of .ceil
oReturnVal.w = Math.ceil((oDate.dayOfYear(dDateTime) - iDaysInWeek1) / 7);
//#### If the iDaysInWeek1 includes cTHURSDAY, include the first week in the a_iReturn value's week (index 0) (so inc iReturn by 1)
//#### NOTE: Since "Monday = 1 ... Sunday = 7", the simple "greater than" calculation below works as required
if (iDaysInWeek1 >= cTHURSDAY) {
oReturnVal.w++;
}
//#### If the week (index 0) has been calculated to 53 on a non-53 week year
//#### NOTE: .Years with 53 weeks must either start or end on a cTHURSDAY, otherwise they would not have more then 364 days to push them into the 53rd week (as 364 / 7 == 52, so any year with 364 or fewer days has 52 weeks)
if (oReturnVal.w > 52 && iJan1 != cTHURSDAY && iDec31 != cTHURSDAY) {
//#### Increment the .yyyy and reset the .w(eek) to 1 (as the date is part of next year's week count)
oReturnVal.yyyy++;
oReturnVal.w = 1;
}
//#### If the week (index 0) is still 0, then we are looking at the last week of the previous year
if (oReturnVal.w == 0) {
//#### Recurse to calculate the week containing Dec31 for the previous year
oReturnVal = weekOfYear_ISO8601(new Date(iYYYY - 1, 11, 31));
}
return oReturnVal;
} //# weekOfYear_ISO8601
*/
//############################################################
//# Determines the simple week number (functionally equivalent to Excel's WeekNum) for the given date.
//############################################################
//# Last Updated: April 19, 2006
function weekOfYear_Simple(vDateTime, eStartOfWeek) {
var iDaysInFirstWeek = 7,
dDate = core.type.date.mk(vDateTime, 0),
eJan1 = (new Date(dDate.getFullYear(), 0, 1)).getDay()
;
//#### If eJan1 differs from the eStartOf(the)Week
//#### NOTE: This logic is correct thanks to the +1 in the line below. This accounts for the first week (even if, as in this case, the first week is a full week)
//#### TODO: Verify "+ 1" change below is working
if (eJan1 !== eStartOfWeek) {
//#### Calculate the iDaysIn(the)FirstWeek based on the eStart(day)Of(the)Week less Jan 1st's .DayOfWeek (+7/%7 so that the result is positive and properly looped back around)
iDaysInFirstWeek = ((eStartOfWeek - eJan1 + 1 + 7) % 7);
}
//#### Determine and return the .WeekOfYear_Simple based on the passed vDateTime's .DayOfYear, less the iDaysIn(the)FirstWeek +1 (to allow for the first week)
return Math.ceil((oDate.dayOfYear(dDate) - iDaysInFirstWeek) / 7) + 1;
} //# weekOfYear_Simple
//#
oDate = {
//#########
/** Enumerations for Days (<code>ish.type.date.enum.days</code>) and Week of Year (<code>ish.type.date.enum.weekOfYear</code>).
* @function ish.type.date.enums
* @$asProperty
*/ //#####
enums: { //# TODO: @returns =days... see: pagination:
days: enumDays,
weekOfYear: {
absolute: -1,
//iso8601: -2,
simple: enumDays
}
}, //# type.date.enums
//#########
/** Formats the passed value based on the passed format.
* @function ish.type.date.format
* @$note The date format is specified using the following values:
* <ul>
* <li><code>D</code>: Day of Month as number</li><li><code>DD</code>: Day of Month as 2-digit number (zero-padded)</li><li><code>S</code>: Day of Month's One's Place Suffix (e.g. <code>st</code>, <code>nd</code>, <code>rd</code>, <code>th</code>).</li>
* <li style='margin-top: 15px;'><code>W</code>: Day of Week as 1-character (e.g. <code>M</code>)</li><li><code>WWW</code>: Day of Week as 3-characters (e.g. <code>Mon</code>)</li><li><code>WWWW</code>: Day of Week as word (e.g. <code>Monday</code>)</li>
* <li style='margin-top: 15px;'><code>M</code>: Month as number</li><li><code>MM</code>: Month as 2-digit number (zero-padded)</li><li><code>MMM</code>: Month as 3-characters (e.g. <code>Jan</code>)</li><li><code>MMMM</code>: Month as word (e.g. <code>January</code>)</li>
* <li style='margin-top: 15px;'><code>w</code>: Week of Year's Week as number</li><li><code>ww</code>: Week of Year's Week as 2-digit number (zero-padded)</li><li><code>yy</code>: Week of Year's Year as 2-digit number</li><li><code>yyyy</code>: Week of Year's Year as 4-digit number</li>
* <li style='margin-top: 15px;'><code>YY</code>: Year as 2-digit number (zero-padded)</li><li><code>YYYY</code>: Year as 4-digit number</li>
* <li style='margin-top: 15px;'><code>J</code>: Day of Year as number</li><li><code>JJJ</code>: Day of Year as 3-digit number (zero-padded)</li>
* <li style='margin-top: 15px;'><code>E</code>: Timestamp as number</li><li><code>e</code>: <code>window.performance</code>-based Timestamp as number</li>
* <li style='margin-top: 15px;'><code>H</code>: 24 Hour as number</li><li><code>HH</code>: 24 Hour as 2-digit number (zero-padded)</li>
* <li style='margin-top: 15px;'><code>h</code>: 12 Hour as number</li><li><code>hh</code>: 12 Hour as 2-digit number (zero-padded)</li>
* <li style='margin-top: 15px;'><code>m</code>: Minutes as number</li><li><code>mm</code>: Minutes as 2-digit number (zero-padded)</li>
* <li style='margin-top: 15px;'><code>s</code>: Seconds as number</li><li><code>ss</code>: Seconds as 2-digit number (zero-padded)</li>
* <li style='margin-top: 15px;'><code>t</code>: Meridian as 1-character (e.g. <code>a</code>, <code>p</code>)</li><li><code>tt</code>: Meridian as 2-characters (e.g. <code>am</code>, <code>pm</code>)</li>
* <li style='margin-top: 15px;'><code>zz</code>: Timezone Offset in Minutes as number (per {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset|MDN})</li><li><code>zzzz</code>: Timezone Offset as <code>±hh:mm</code> (e.g. <code>-08:00</code>, per {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset|MDN})</li>
* </ul>
* @param {variant} vDateTime Value to interrogate.
* @param {string} sFormat Value representing the date format.
* @returns {string} Value representing the formatted date.
*/ //#####
//# Last Updated: April 19, 2006
format: function (vDateTime, sFormat) {
var vTemp, i,
oDecoders = {
$order: []
},
fnLpad = core.type.fn.mk(core.type.str.lpad, function (x) {
return (core.resolve(x, "length") === 0 ? "0" + x : x);
})
;
//# .registers the decoder value
function register(sDecode, sValue) {
oDecoders[sDecode] = sValue;
oDecoders.$order.push(sDecode);
} //# register
vDateTime = core.type.date.mk(vDateTime, null);
//# If we were passed a valid vDateTime and sFormat
if (core.type.date.is(vDateTime) && core.type.str.is(sFormat, true)) {
//#### Borrow the use of i to store the month day, setting D, DD and S accordingly
i = vDateTime.getDate();
register("D", i);
register("DD", fnLpad(i, '0', 2));
register("S", oConfig.format.s[i] || oConfig.format.s[i % 10] || oConfig.format.s.x);
//#### Borrow the use of i to store the day of the week, setting W, WWW and WWWW according to the above determined .DayOfWeek in the borrowed i
i = vDateTime.getDay();
register("W", i);
register("WWW", oConfig.format.WWW[i]);
register("WWWW", oConfig.format.WWWW[i]);
//#### Borrow the use of i to store the month, setting M, MM, MMM and MMMM accordingly
//#### NOTE: .getMonth is 0-based
i = vDateTime.getMonth();
register("M", i + 1);
register("MM", fnLpad(i + 1, '0', 2));
register("MMM", oConfig.format.MMM[i]);
register("MMMM", oConfig.format.MMMM[i]);
//#### Borrow the use of vTemp to store the .WeekOfYear, setting w, ww, yy and yyyy accordingly
vTemp = core.type.date.weekOfYear(vDateTime, oConfig.weekOfYear);
register("w", vTemp.w);
register("ww", fnLpad(vTemp.w, '0', 2));
//#### Reset the borrowed vTemp to store the MakeString'd .Year and then borrow i to determine its .Length, finally setting yy and yyyy accordingly
vTemp = core.type.str.mk(vTemp.yyyy);
i = (vTemp.length - 2);
register("yy", fnLpad(vTemp.substr(i < 0 ? 0 : i), '0', 2));
register("yyyy", vTemp);
//#### Borrow the use of vTemp to store the MakeString'd .getFullYear and then borrow i to determine its .Length, finally setting YY and YYYY accordingly
vTemp = core.type.str.mk(vDateTime.getFullYear());
i = (vTemp.length - 2);
register("YY", fnLpad(vTemp.substr(i < 0 ? 0 : i), '0', 2));
register("YYYY", vTemp);
//#### Borrow the use of i to store the .dayOfYear, setting J and JJJ accordingly
i = oDate.dayOfYear(vDateTime);
register("J", i);
register("JJJ", fnLpad(i, '0', 3));
//#### Set E based on the current Timestamp (down converting the .getTime'd returned milliseconds into seconds)
//#### NOTE: Can debug output at http://www.argmax.com/mt_blog/archive/000328.php?ts=1058415153
register("E", parseInt(vDateTime.getTime() / 1000));
register("e", core.type.date.timestamp());
//#### Borrow the use of i to store the 24 hour, setting HH and H accordingly
i = vDateTime.getHours();
register("H", i);
register("HH", fnLpad(i, '0', 2));
//# If the .Hour within i is before noon, set tt to "am", else set tt to "pm"
register("t", oConfig.format.T[(i % 12) === i ? 0 : 1]);
register("tt", oConfig.format.TT[(i % 12) === i ? 0 : 1]);
//#### Determine the 12-hour time from the above collected .Hour (fixing 0 hours as necessary), then set hh and hh accordingly
i = (i % 12 || 12);
register("h", i);
register("hh", fnLpad(i, '0', 2));
//#### Borrow the use of i to store the .Minute, setting m and mm accordingly
i = vDateTime.getMinutes();
register("m", i);
register("mm", fnLpad(i, '0', 2));
//#### Borrow the use of i to store the .Second, setting s and ss accordingly
i = vDateTime.getSeconds();
register("s", i);
register("ss", fnLpad(i, '0', 2));
//# Borrow the use of i to store the .getTimezoneOffset, setting zzzz accordingly
i = -vDateTime.getTimezoneOffset();
register("zz", i * -1);
register("zzzz",
(i > -1 ? '+' : '-') + fnLpad(Math.floor(Math.abs(i / 60)), '0', 2) + ':' + fnLpad(Math.floor(Math.abs(i % 60)), '0', 2)
);
//# Traverse the above defined oDecoders, preprocessing the sFormat by replacing the user-set values with {{i}}
//# NOTE: Preprocessing is required as (for example) "m" appears in "December"
for (i = oDecoders.$order.length - 1; i > -1; i--) {
vTemp = oDecoders.$order[i];
sFormat = sFormat.replace(new RegExp("[$]?" + vTemp, 'g'), "{{" + i + "}}"); //# Support $legacy format
}
//#### Traverse the above defined oDecoders, replacing each ordered .$order[key] with its above determined value within the passed sFormat
//#### NOTE: The ordered oDecoders.$order is traversed in reverse to because definitions are set from shortest to longest (i.e. - M, MM, MMM, and MMMM)
for (i = oDecoders.$order.length - 1; i > -1; i--) {
vTemp = oDecoders.$order[i];
sFormat = sFormat.replace(new RegExp("\\{\\{" + i + "\\}\\}", 'g'), oDecoders[vTemp]);
//sFormat = sFormat.replace(new RegExp("[$]" + vTemp, 'g'), oDecoders[vTemp]);
}
}
//# Else something was amiss, so reset the sFormat to a null-string
else {
sFormat = "";
}
//# Return the above modified sFormat to the caller
return sFormat;
}, //# type.date.format
//#########
/** Formats the passed value in ISO 8601 format with the local timezone offset.
* @function ish.type.date.isoLocalString
* @param {variant} vDateTime Value to interrogate.
* @returns {string} Value representing the passed value in ISO 8601 format with the local timezone offset.
* @see {@link https://stackoverflow.com/a/17415677/235704|StackOverflow.com}
* @see {@link https://en.wikipedia.org/wiki/ISO_8601#Time_offsets_from_UTC|ISO 8601}
*/ //#####
isoLocalString: function () {
function l0Pad(i) {
return core.type.str.lpad(Math.floor(Math.abs(i)), '0', 2);
} //# l0Pad
return function (vDateTime) {
var dDate = core.type.date.mk(vDateTime),
iTimezoneOffset = -dDate.getTimezoneOffset()
;
return dDate.getFullYear() + '-' + l0Pad(dDate.getMonth() + 1) + '-' + l0Pad(dDate.getDate()) +
'T' + l0Pad(dDate.getHours()) + ':' + l0Pad(dDate.getMinutes()) + ':' + l0Pad(dDate.getSeconds()) +
(iTimezoneOffset > -1 ? '+' : '-') + l0Pad(iTimezoneOffset / 60) + ':' + l0Pad(iTimezoneOffset % 60)
;
};
}(), //# type.date.isoLocalString
//#########
/** Determines the day of the year of the passed value.
* @function ish.type.date.dayOfYear
* @param {variant} vDateTime Value to interrogate.
* @returns {string} Value representing the day of the year of the passed value.
*/ //#####
//# Last Updated: April 12, 2006
dayOfYear: function (vDateTime) {
vDateTime = core.type.date.mk(vDateTime, null);
//# If the caller passed in a valid vDateTime
if (core.type.date.is(vDateTime)) {
return Math.ceil(
//#### Retrieve the number of milliseconds different between the passed vDateTime and Jan 1st of the year it represents
(vDateTime.getTime() - new Date(vDateTime.getFullYear(), 0, 1, 0, 0, 0, 0).getTime()) /
//#### Convert the above retrieved value from milliseconds into seconds (hence / 1000), then into whole (Math.ceil) days (60 seconds in a minute, 60 minutes in an hour and 24 hours in a day, hence 60 / 60 / 24) + 1 (as it is a 1-based calculation), returning the result to the caller
1000 / 60 / 60 / 24
) + 1;
}
}, //# type.date.dayOfYear
//#########
/** Determines the week of the year of the passed value.
* @function ish.type.date.weekOfYear
* @param {variant} vDateTime Value to interrogate.
* @returns {string} Value representing the week of the year of the passed value.
*/ //#####
//# Last Updated: April 13, 2006
weekOfYear: function (vDateTime, eWeekOfYear) {
var enumWeekOfYear = oDate.enums.weekOfYear,
oReturnVal = {
w: 0,
yyyy: 0
}
;
//# Ensure the caller passed a valid vDateTime
vDateTime = core.type.date.mk(vDateTime, null);
//# If the caller passed a valid vDateTime
if (vDateTime) {
//#### Re-default the oReturnVal's .yyyy to the passed vDateTime's .getFullYear and ensure eWeekOfYear is set
oReturnVal.yyyy = vDateTime.getFullYear();
eWeekOfYear = core.type.int.mk(eWeekOfYear, oConfig.weekOfYear);
//#### Determine the eWeekOfYear and process accordingly
switch (eWeekOfYear) {
/*
//#### If this is an .iso8601 week number request, pass the call off to .iso8601
case enumWeekOfYear.iso8601: {
oReturnVal = weekOfYear_ISO8601(vDateTime);
break;
}
*/
//#### If this is an .absolute week number request
case enumWeekOfYear.absolute: {
//#### Calculate the .absolute week number based on the rounded up Julian .dayOfYear (as .absolute week numbers are based on days since Jan 1st, irrespective of its week day)
oReturnVal.w = Math.ceil(oDate.dayOfYear(vDateTime) / 7);
break;
}
//#### If this is an .cnSimple_* week number request, pass the call off to .weekOfYear.simple
case enumWeekOfYear.simple.sun:
case enumWeekOfYear.simple.mon:
case enumWeekOfYear.simple.tue:
case enumWeekOfYear.simple.wed:
case enumWeekOfYear.simple.thu:
case enumWeekOfYear.simple.fri:
case enumWeekOfYear.simple.sat: {
//#### Determine oReturnVal's .w(eek)
oReturnVal.w = weekOfYear_Simple(vDateTime, eWeekOfYear);
break;
}
}
}
return oReturnVal;
}, //# type.date.weekOfYear
//#########
/** Determines the last day of the month for the passed values.
* @function ish.type.date.lastDayOfMonth
* @param {date|integer} [x=new Date().getMonth() + 1] Value representing the date to interrogate or the month as a 1-based number (e.g. January = <code>1</code>).
* @param {integer} [iYear=new Date().getFullYear()] Value representing the year as a 4-digit number (e.g. <code>1970</code>).
* @returns {integer} Value representing the last day of the month for the passed values.
*/ //#####
lastDayOfMonth: function (x, iYear) {
var dDate = core.type.date.mk(x),
iMonth = core.type.int.mk(x, dDate.getMonth() + 1)
;
//# Ensure the passed iYear .is .int, defaulting to dDate's if necessary
iYear = core.type.int.mk(iYear, dDate.getFullYear());
return new Date((new Date(iYear, iMonth, 1)) - 1);
}, //# date.lastDayOfMonth
//#########
/** Determines if the year is a leap year.
* @function ish.type.date.leapYear
* @param {date|integer} [x=new Date()] Value representing the date to interrogate or the year as a 4-digit number (e.g. <code>1970</code>).
* @returns {integer} Value representing if the year is a leap year.
*/ //#####
leapYear: function (x) {
var dDate = core.type.date.mk(x),
iYear = core.type.int.mk(x, dDate.getFullYear())
;
return ((iYear % 4 == 0) && (iYear % 100 != 0)) || (iYear % 400 == 0);
} //# date.leapYear
};
//#########
/** <code>ish.type.date</code> configuration values.
* @function ish.config.type:date
* @param {object} [oOptions] Value representing the updated configuration values.
* @returns {object} Value representing <code>ish.type.date</code>'s configuration values.
*/ //#####
if (!core.type.fn.run(core.resolve(core.config, "type.date"), { args: oConfig })) {
core.resolve(true, core.config, "type").date = core.config(oConfig);
}
return {
date: oDate
};
}); //# core.type.date
//# .fire the plugin's loaded event
core.io.event.fire("ish.type.date-format");
//# Return core to allow for chaining
return core;
} //# init
//# If we are running server-side
//# NOTE: Compliant with UMD, see: https://github.com/umdjs/umd/blob/master/templates/returnExports.js
//# NOTE: Does not work with strict CommonJS, but only CommonJS-like environments that support module.exports, like Node.
if (typeof module === 'object' && module.exports) { //if (typeof module !== 'undefined' && this.module !== module && module.exports) {
module.exports = init;
}
//# Else if we are running in an .amd environment, register as an anonymous module
else if (typeof define === 'function' && define.amd) {
define([], init);
}
//# Else we are running in the browser, so we need to setup the _document-based features
else {
return init(window.head.ish || document.querySelector("SCRIPT[ish]").ish);
}
//</MIXIN>
}());