Skip to content

Instantly share code, notes, and snippets.

@siddharthvp
Created December 16, 2019 17:07
Show Gist options
  • Save siddharthvp/dcdc51e05aa79a5856e63ecc116dcacf to your computer and use it in GitHub Desktop.
Save siddharthvp/dcdc51e05aa79a5856e63ecc116dcacf to your computer and use it in GitHub Desktop.
Date class for Morebits
/*************** Morebits.date ********************/
/**
* @constructor
* Create a date object. MediaWiki timestamp format is also acceptable,
* in addition to everything that JS Date() accepts.
*/
Morebits.date = function() {
var args = Array.prototype.slice.call(arguments);
this._d = new (Function.prototype.bind.apply(Date, [Date].concat(args)));
if (isNaN(this._d.getTime()) && typeof args[0] === 'string') {
// Try again after removing a comma, to get MediaWiki timestamps to parse
this._d = new (Function.prototype.bind.call(Date, Date, args[0].replace(/(\d\d:\d\d),/, '$1')));
}
};
Morebits.date.prototype = {
/** @param {(Date|Morebits.date)} date */
isBefore: function(date) {
return this.getTime() < date.getTime();
},
isAfter: function(date) {
return this.getTime() > date.getTime();
},
/**
* Add a given number of minutes, hours, days, months or years to the date.
* This is done in-place. The modified date object is also returned, allowing chaining.
* @param {number} number
* @param {string} unit
* @throws {Error} if invalid or unsupported unit is given
* @returns {Morebits.date}
*/
add: function(number, unit) {
// mapping time units with getter/setter function names
var unitMap = {
minutes: 'Minutes',
hours: 'Hours',
days: 'Date',
months: 'Month',
years: 'FullYear'
};
var unitNorm = unitMap[unit] || unitMap[unit + 's'];
if (unitNorm) {
this['set' + unitNorm](this['get' + unitNorm]() + number);
return this;
}
throw new Error('Invalid unit "' + unit + '": Only ' + Object.keys(unitMap).join(', ') + ' are allowed.');
},
/**
* Subtracts a given number of minutes, hours, days, months or years to the date.
* This is done in-place. The modified date object is also returned, allowing chaining.
* @param {number} number
* @param {string} unit
* @throws {Error} if invalid or unsupported unit is given
* @returns {Morebits.date}
*/
subtract: function(number, unit) {
return this.add(-number, unit);
},
/**
* Formats the date into a string per the given format string, and as per a given time zone,
* text between square brackets is kept as is.
* @param {string} formatstr
* @param {string|number} [zone=system] - 'system' (for browser-default time zone), 'utc' (for UTC),
* or 'user' (for time zone according to user's site preferences), or just specify a timezone as number
* of minutes past UTC.
* @returns {string}
* @requires mediawiki.user - if 'user' zone is used
*/
format: function(formatstr, zone) {
zone = zone || 'system';
var udate = this;
// create a new date object that will contain the date to display as system time
if (zone === 'utc') {
udate = new Morebits.date(this.getTime()).add(this.getTimezoneOffset(), 'minutes');
} else if (zone === 'user') {
var offset = parseInt(mw.user.options.get('timecorrection').split('|')[1]);
udate = new Morebits.date(this.getTime()).add(offset, 'minutes');
} else if (typeof zone === 'number') {
var offset = this.getTimezoneOffset() /* convert to utc */ + zone; /* add the utc offset */
udate = new Morebits.date(this.getTime()).add(offset, 'minutes');
}
var pad = function(num) {
return num < 10 ? '0' + num : num;
};
var unbinder = new Morebits.unbinder(formatstr); // escape stuff between [...]
unbinder.unbind('\\[', '\\]');
unbinder.content = unbinder.content
.replace(/HH/g, pad(udate.getHours()))
.replace(/H/g, udate.getHours())
.replace(/mm/g, pad(udate.getMinutes()))
.replace(/SS/g, pad(udate.getSeconds()))
.replace(/DD/g, pad(udate.getDate()))
.replace(/D/g, udate.getDate())
.replace(/MMMM/g, mw.config.get('wgMonthNames')[udate.getMonth() + 1])
.replace(/MMM/g, mw.config.get('wgMonthNamesShort')[udate.getMonth() + 1])
.replace(/MM/g, pad(udate.getMonth() + 1))
.replace(/M/g, udate.getMonth() + 1)
.replace(/YYYY/g, udate.getFullYear());
return unbinder.rebind().replace(/\[(.*?)\]/g, '$1');
},
// Redundant to `format`, hence probably unnecessary:
// /**
// * Returns a "HH:mm, D MMMM YYYY" UTC timestamp. The default MediaWiki timestamp format.
// * @returns {string}
// */
// toTimestamp: function() {
// var h = this.getUTCHours(), m = this.getUTCMinutes();
// return (h < 10 ? '0' + h : h) + ':' + (m < 10 ? '0' + m : m) + ', ' +
// this.getUTCDate() + ' ' + this.getUTCMonthName() + ' ' + this.getUTCFullYear();
// },
// /**
// * @param {string} [format=dmy] - 'dmy', 'mdy' or 'ymd'
// * @returns {string}
// */
// dateString: function(format) {
// var D = this.getUTCDate(), M = this.getUTCMonthName(), Y = this.getUTCFullYear();
// switch (format) {
// case 'ymd':
// return Y + ' ' + M + ' ' + D;
// case 'mdy':
// return M + ' ' + D + ', ' + Y;
// default: // dmy
// return D + ' ' + M + ' ' + Y;
// }
// },
/**
* Gives a readable relative time string such as "Yesterday at 6:43 PM" or "Last Thursday at 11:45 AM".
* @returns {string}
*/
calendar: function() {
var days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
var now = new Date(), thisdate = new Morebits.date(this.getTime()); // create a copy
now.setHours(0, 0, 0, 0); // zero out the hours, minutes, seconds and milliseconds - keeping only the date
thisdate.setHours(0, 0, 0, 0);
var datediff = Math.abs((now.getTime() - thisdate.getTime())) / 86400000;
if (datediff >= 7) {
return thisdate.format('DD/MM/YYYY');
}
var result;
if (datediff === 0) {
result = 'Today';
} else if (datediff === 1) {
result = thisdate.isBefore(now) ? 'Yesterday' : 'Tomorrow';
} else if (datediff < 7) {
result = (thisdate.isBefore(now) ? 'Last ' : '') + days[thisdate.getDay()];
}
var h = this.getHours(), m = this.getMinutes();
result += ' at ' + (h > 12 ? h - 12 : h) + ':' + (m < 10 ? '0' + m : m) + ' ' + (h >= 12 ? 'PM' : 'AM');
return result;
},
/**
* Returns a regex that matches wikitext section titles such as ==December 2019== or
* === Jan 2018 ===
* @returns {RegExp}
*/
monthHeaderRegex: function() {
return new RegExp('^==+\\s*(?:' + this.getUTCMonthName() + '|' + this.getUTCMonthNameAbbrev() +
')\\s+' + this.getUTCFullYear() + '\\s*==+', 'mg');
},
/**
* Creates a wikitext section header with the month and year.
* @param {number} [level=2] - Header level (default 2)
* @returns {string}
*/
monthHeader: function(level) {
level = level || 2;
var header = ''; // String.prototype.repeat is not supported in IE 11, so...
for (var i = 0; i < level; i++) {
header += '=';
}
return header + ' ' + this.getUTCMonthName() + ' ' + this.getUTCFullYear() + ' ' + header;
}
};
// Allow native Date.prototype methods to be used on Morebits.date objects
// Note that this includes getUTCMonthName() and getUTCMonthNameAbbrev(), which should probably be removed
// from Date.prototype and moved above.
Object.getOwnPropertyNames(Date.prototype).forEach(function(func) {
Morebits.date.prototype[func] = function() {
return this._d[func].apply(this._d, Array.prototype.slice.call(arguments));
};
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment