Skip to content

Commit

Permalink
implement Intl.DateTime.format options
Browse files Browse the repository at this point in the history
  • Loading branch information
JohnRDOrazio committed Mar 17, 2024
1 parent b1c87c4 commit 0ad6c61
Showing 1 changed file with 82 additions and 29 deletions.
111 changes: 82 additions & 29 deletions jqClock.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
* @timezone defaults to detection of client timezone, but can be passed in as a string such as "UTC-6" when using server generated timestamps
* @langSet defaults to navigator language else "en", possible values are: "af", "am", "ar", "bg", "bn", "ca", "cs", "da", "de", "el", "en", "es", "et", "fa", "fi", "fr", "gu", "he", "hi", "hr", "hu", "id", "in", "it", "iw", "ja", "kn", "ko", "lt", "lv", "ml", "mo", "mr", "ms", "nb", "nl", "no", "pl", "pt", "ro", "ru", "sh", "sk", "sl", "sr", "sv", "sw", "ta", "te", "th", "tl", "tr", "uk", "ur", "vi", "zh", "arb", "cmn", "cnr", "drw", "ekk", "fil", "lvs", "pes", "prs", "swc", "swh", "tnf", "zsm" (can optionally add region)
* @calendar defaults to "true", possible value are: boolean "true" or "false"
* @dateFormat defaults to "l, F j, Y" when langSet==="en", else to "l, j F Y"
* @timeFormat defaults to "h:i:s A" when langSet==="en", else to "H:i:s"
* @dateFormat defaults to "l, F j, Y" when langSet==="en", else to "l, j F Y"; can also take Intl.DateTime.format options object; if set to false @calendar will be false
* @timeFormat defaults to "h:i:s A" when langSet==="en", else to "H:i:s"; can also take Intl.DateTime.format options object
* @isDST possible values are boolean `true` or `false`, if not passed in will be calculated based on client time (default)
*
* $("#mydiv").clock(); >> will display in English and in 12 hour format
Expand Down Expand Up @@ -259,6 +259,7 @@ if (!Number.prototype.map) {
"I",
"O",
"P",
"T",
"Z",
"c",
"r",
Expand Down Expand Up @@ -304,6 +305,7 @@ if (!Number.prototype.map) {
this.destroy = () => _this.each((idx,selfRef) => {
pluginMethods.destroy(selfRef);
});

this.stop = () => _this.each((idx,selfRef) => {
pluginMethods.stop(selfRef);
});
Expand All @@ -312,6 +314,8 @@ if (!Number.prototype.map) {
pluginMethods.start(selfRef);
});

const isObject = ( myVar ) => Object.prototype.toString.call( myVar ) === '[object Object]';

const dateFormatCharacters = {
//DAY
//Day of the Month, 2 digits with leading zeros
Expand Down Expand Up @@ -416,7 +420,10 @@ if (!Number.prototype.map) {
: "+00"
) + ":00",
//Timezone abbreviation
/*"T": ( clk ) => timezone_abbrev...*/
"T": ( clk ) => new Intl.DateTimeFormat( clk.myoptions.langSet, {
timeZone: clk.myoptions.timezone,
timeZoneName: "short"
} ).format( clk.mytimestamp_sysdiff ),
//Timezone offset in seconds. The offset for timezones west of UTC is always negative, and for those east of UTC is always positive.
"Z": ( clk ) => clk.tzS < 0
? "" + Math.abs(clk.tzS)
Expand Down Expand Up @@ -475,6 +482,12 @@ if (!Number.prototype.map) {
//Seconds since the Unix Epoch
"U": ( clk ) => Math.floor(clk.mytimestamp / 1000)
},
dateFormatOptions = [
"calendar", "numberingSystem", "weekday", "era", "year", "month", "day", "dateStyle"
],
timeFormatOptions = [
"numberingSystem", "hour12", "hourCycle", "timeZone", "timeZoneName", "hour", "minute", "second", "fractionalSecondDigits", "dayPeriod", "timeStyle"
],
pluginMethods = {
"destroy": ( selfRef ) => {
let el_id = $(selfRef).attr("id");
Expand Down Expand Up @@ -503,7 +516,7 @@ if (!Number.prototype.map) {
_jqClock.hasOwnProperty(el_id) === false
) {
_jqClock[el_id] = setTimeout(
() => { _updateClock($(selfRef)); },
() => { _updateClock( selfRef ); },
current_options.rate
);
}
Expand Down Expand Up @@ -592,15 +605,18 @@ if (!Number.prototype.map) {
$(el).html(clk.calendElem + clk.clockElem);
let el_id = $(el).attr("id");
_jqClock[el_id] = setTimeout(
() => { _updateClock( $(el) ); },
() => { _updateClock( el ); },
clk.myoptions.rate
);
},
formatDateString = ( clk ) => {
let dateStr = "";
let chr;
const { myoptions } = clk;
if( isObject( myoptions.dateFormat ) ) {
dateStr = new Intl.DateTimeFormat(myoptions.langSet, myoptions.dateFormat).format(clk.mytimestamp_sysdiff);
} else {
/* Format Date String according to PHP style Format Characters http://php.net/manual/en/function.date.php */
let dateStr = "";
let chr;
const { myoptions } = clk;
for (let n = 0; n <= myoptions.dateFormat.length; n++) {
chr = myoptions.dateFormat.charAt(n);
if( chr in dateFormatCharacters ) {
Expand All @@ -618,27 +634,32 @@ if (!Number.prototype.map) {
}
}
}
return '<span class="clockdate">' + dateStr + "</span>";
}
return '<span class="clockdate">' + dateStr + "</span>";
},
formatTimeString = ( clk ) => {
/* Prepare Time String using PHP style Format Characters http://php.net/manual/en/function.date.php */
let timeStr = "";
let chr;
const { myoptions } = clk;
for (let n = 0; n <= myoptions.timeFormat.length; n++) {
chr = myoptions.timeFormat.charAt(n);
if( chr in timeFormatCharacters ) {
timeStr += timeFormatCharacters[chr]( clk );
} else {
switch (chr) {
case String.fromCharCode(92): //backslash character, which would have to be a double backslash in the original string!!!
timeStr += myoptions.timeFormat.charAt(++n);
break;
case "%":
[ timeStr, n ] = processLiterals( myoptions, n, false, timeStr, chr );
break;
default:
timeStr += chr;
if( isObject( myoptions.timeFormat ) ) {
timeStr = new Intl.DateTimeFormat(myoptions.langSet, myoptions.timeFormat).format(clk.mytimestamp_sysdiff);
} else {
/* Prepare Time String using PHP style Format Characters http://php.net/manual/en/function.date.php */
for (let n = 0; n <= myoptions.timeFormat.length; n++) {
chr = myoptions.timeFormat.charAt(n);
if( chr in timeFormatCharacters ) {
timeStr += timeFormatCharacters[chr]( clk );
} else {
switch (chr) {
case String.fromCharCode(92): //backslash character, which would have to be a double backslash in the original string!!!
timeStr += myoptions.timeFormat.charAt(++n);
break;
case "%":
[ timeStr, n ] = processLiterals( myoptions, n, false, timeStr, chr );
break;
default:
timeStr += chr;
}
}
}
}
Expand All @@ -649,11 +670,14 @@ if (!Number.prototype.map) {
/* I prefer this method to jQuery.extend because we can dynamically set each option based on a preceding option's value */
options.timestamp = options.timestamp || "localsystime";
options.langSet = options.langSet || navigator.language || "en";
if( options.hasOwnProperty("calendar") ) {
console.warn( 'the `calendar` option is deprecated since jqclock v2.3.7 and will be removed in a future release. Please use `dateFormat: false` instead of `calendar: false` to remove the calendar from the jQuery Clock.' );
}
options.calendar = options.hasOwnProperty("calendar")
? options.calendar
: true;
options.dateFormat =
options.dateFormat ||
options.dateFormat ??
(options.langSet === "en" ? "l, F j, Y" : "l, j F Y");
options.timeFormat =
options.timeFormat ||
Expand All @@ -678,10 +702,39 @@ if (!Number.prototype.map) {
options.calendar = Boolean(options.calendar); //do our best to get a boolean value
}
if (typeof options.dateFormat !== "string") {
options.dateFormat = "" + options.dateFormat;
if( isObject( options.dateFormat ) ) {
if ( Object.keys( options.dateFormat ).every( key => dateFormatOptions.includes( key ) ) === false ) {
const extra = Object.keys( options.dateFormat ).reduce((acc,curr) => { if(dateFormatOptions.includes(curr) === false){ acc.push(curr) }; return acc; },[]);
console.error( `unrecognized options passed to dateFormat option: ${ extra.join(', ') }. Valid options are: ${ dateFormatOptions.join(', ') }` );
} else {
options.calendar = true;
}
} else if ( options.dateFormat === false ) {
options.calendar = false;
} else {
console.error( `dateFormat option has unsupported type, must be either string or object instead is ${ Object.prototype.toString.call( options.dateFormat ) }` );
options.dateFormat = false;
options.calendar = false;
}
} else {
if( options.dateFormat === 'false' ) {
options.dateFormat = false;
options.calendar = false;
} else {
options.calendar = true;
}
}
if (typeof options.timeFormat !== "string") {
options.timeFormat = "" + options.dateFormat;
if( isObject( options.timeFormat ) ) {
if ( Object.keys( options.timeFormat ).every( key => timeFormatOptions.includes( key ) ) === false ) {
const extra = Object.keys( options.timeFormat ).reduce((acc,curr) => { if(timeFormatOptions.includes(curr) === false){ acc.push(curr) }; return acc; },[]);
console.error( `unrecognized options passed to timeFormat option: ${ extra.join(', ') }. Valid options are: ${ timeFormatOptions.join(', ') }` );
}
} else {
console.error( `timeFormat option has unsupported type, must be either string or object instead is ${ Object.prototype.toString.call( options.dateFormat ) }` );
//let's try to salvage the situation if at all possible...
options.timeFormat = "" + options.timeFormat;
}
}
if (typeof options.timezone !== "string") {
options.timezone = "" + options.timezone;
Expand Down Expand Up @@ -760,7 +813,7 @@ if (!Number.prototype.map) {
$(selfRef).data("clockoptions", options);
//only allow one associated settimeout at a time! basically, only one plugin instance per dom element
if (_jqClock.hasOwnProperty($(selfRef).attr("id")) === false) {
_updateClock($(selfRef));
_updateClock(selfRef);
}
};

Expand All @@ -775,7 +828,7 @@ if (!Number.prototype.map) {
// It's no use using a client timestamp's check for DST when a server timestamp is passed!
// To fix this, we will give a console warning when a server timestamp is passed but the isDST option is not...
// In order to do that, we need to save a reference to the original isDST option before ensuring default options
const origDST = options.isDST || null;
const origDST = isObject(options) && options.isDST ? options.isDST : null;
options = ensureDefaultOptions( options, sysDateObj );
options = normalizeOptions( options );

Expand Down

0 comments on commit 0ad6c61

Please sign in to comment.