/*
* Your installation or use of this SugarCRM file is subject to the applicable
* terms available at
* http://support.sugarcrm.com/Resources/Master_Subscription_Agreements/.
* If you do not agree to all of the applicable terms or do not have the
* authority to bind the entity as an authorized representative, then do not
* install or use this SugarCRM file.
*
* Copyright (C) SugarCRM Inc. All rights reserved.
*/
const User = require('../core/user');
const Language = require('../core/language');
var date = moment;
/**
* Extends {@link https://momentjs.com/|moment.js} with additional features.
*
* Also provides a backwards compatibility layer for existing code; however,
* this is deprecated and you are strongly advised not to use it.
*
* @module Utils/Date
* @class
*/
_.extend(date, {
/**
* Exception to be used when an invalid date/time is passed.
*
* @param {string} message The error message.
* @param {string} date The date that is invalid.
* @class
* @memberOf Utils/Date
* @private
*/
InvalidException: function(message, date) {
this.name = 'InvalidException';
this.message = message;
this.date = date;
},
/**
* Cached formats of converted date/time formats from server to client.
*
* This cache is populated by {@link Utils/Date#convertFormat}.
*
* @type {Object}
* @private
*/
_cachedFormats: {},
/**
* Server date format according to moment.js.
* This is exactly the same as the server is expecting `Y-m-d`.
*
* @type {string}
* @private
*/
_serverDateFormat: 'YYYY-MM-DD',
/**
* Server's locale to get correct encoding
*
* @type {string}
* @private
*/
_serverLocale: 'en',
/**
* Converts PHP date/time format preference to JS.
*
* Momentjs uses a different formatting tokens:
* {@link http://momentjs.com/docs/#/parsing/string-format/}
*
* Known possible values to convert (out of the box):
*
* ```
* // dates
* 'Y-m-d'
* 'd-m-Y'
* 'm-d-Y'
* 'Y/m/d'
* 'd/m/Y'
* 'm/d/Y'
* 'Y.m.d'
* 'd.m.Y'
* 'm.d.Y'
*
* // time
* 'H:i'
* 'h:ia'
* 'h:iA'
* 'H.i'
* 'h.ia'
* 'h.iA'
* 'G:i'
* 'g:ia'
* 'G.i'
* 'g.ia'
* 'g.iA'
* ```
*
* @param {string} server The server format preference to convert.
* @return {string} The converted format that momentjs understands.
* @private
* @memberOf Utils/Date
*/
convertFormat: function(server) {
if (date._cachedFormats[server]) {
return date._cachedFormats[server];
}
let convertTable = [
// date
['d', 'DD'], // day of the month w\ leading zeros
['m', 'MM'], // numeric month w\ leading zeros
['Y', 'YYYY'], // full numeric year
// time
['H', 'HH'], // 24-hour format w\ leading zeros
['h', 'hh'], // 12-hour format w\ leading zeros
['G', 'H'], // 24-hour format without leading zeros
['g', 'h'], // 12-hour format without leading zeros
['i', 'mm'], // minutes w\ leading zeros
];
var client = server;
_.each(convertTable, conversion => {
let [from, to] = conversion;
client = client.replace(from, to);
});
date._cachedFormats[server] = client;
return client;
},
/**
* Performs a three-way comparison between two dates.
* Compatible with `Array.sort` and similar functions.
*
* @param {string} date1 The first date to compare.
* @param {string} date2 The second date to compare.
* @return {number} The result of comparing `date1` to `date2`:
* * `-1` if date1 < date2
* * `0` if date1 = date2
* * `1` if date1 > date2
*
* @throws if any of the given dates are invalid.
* @memberOf Utils/Date
*/
compare: function(date1, date2) {
var mDate1 = moment(date1),
mDate2 = moment(date2);
if (!mDate1.isValid()) {
throw new date.InvalidException('Invalid date passed for comparison.', date1);
}
if (!mDate2.isValid()) {
throw new date.InvalidException('Invalid date passed for comparison.', date2);
}
if (mDate1.isBefore(mDate2)) {
return -1;
}
if (mDate1.isAfter(mDate2)) {
return 1;
}
return 0;
},
/**
* Gets the date format preference for the given user.
*
* @param {Data/Bean|Object} [user=Core.User] The user whose date format
* you want.
* @return {string} A format string for the user's preferred date format.
* @memberOf Utils/Date
*/
getUserDateFormat: function(user) {
user = user || User;
return this.convertFormat(user.getPreference('datepref'));
},
/**
* Gets the time format preference for the given user.
*
* @param {Data/Bean|Object} [user=Core.User] The user whose time format
* you want.
* @return {string} A format string for the user's preferred time format.
* @memberOf Utils/Date
*/
getUserTimeFormat: function(user) {
user = user || User;
return this.convertFormat(user.getPreference('timepref'));
},
});
// hash table of the datetime formats that have already been parsed
// FIXME: this is only used by deprecated code
var _formatStringCache = {};
// Deprecated functionality will be kept in the old singleton prototype.
_.extend(date, {
/**
* Parses date strings into JavaScript Dates.
*
* @param {string} date The date string to parse.
* @param {string} [oldFormat] Date format string. If not specified,
* this function will guess the date format.
* @return {Date} A Date object corresponding to the given `date`.
* @deprecated since 7.2.0.
* @memberOf Utils/Date
*/
parse: function(date, oldFormat) {
SUGAR.App.logger.warn('The function `Utils.Date.parse()` is deprecated and will be removed in a future release. ' +
'Please do not use it');
var jsDate = new Date("Jan 1, 1970 00:00:00"),
part = "",
dateRemain, j, c, i, v, timeformat;
//if already a Date return it
if (date instanceof Date) return date;
// TODO add user prefs support
if (!oldFormat) {
oldFormat = this.guessFormat(date);
}
if (oldFormat === false) {
//Check if date is a timestamp
if (/^\d+$/.test(date)) {
return new Date(date);
}
//we cant figure out the format so return false
return false;
}
dateRemain = date.trim();
oldFormat = oldFormat.trim() + " "; // Trailing space to read as last separator.
for (j = 0; j < oldFormat.length; j++) {
c = oldFormat.charAt(j);
if (c === ':' || c === '/' || c === '-' || c === '.' || c === " " || c === 'a' || c === "A") {
i = dateRemain.indexOf(c);
if (i === -1) i = dateRemain.length;
v = dateRemain.substring(0, i);
dateRemain = dateRemain.substring(i + 1);
switch (part) {
case 'm':
if (!(v > 0 && v < 13)) return false;
jsDate.setMonth(v - 1);
break;
case 'd':
if (!(v > 0 && v < 32)) return false;
jsDate.setDate(v);
break;
case 'Y':
if (v > 0 === false) return false;
jsDate.setYear(v);
break;
case 'h':
//Read time, assume minutes are at end of date string (we do not accept seconds)
timeformat = oldFormat.substring(oldFormat.length - 4);
v = parseInt(v, 10);
var timeFormatString = (timeformat.toLowerCase()).trim();
if (timeFormatString === "i a" || timeFormatString === c + "ia") {
var postfix = dateRemain.substring(dateRemain.length - 2).toLowerCase();
if (postfix === 'pm') {
if (v < 12) {
v += 12;
}
}
// 12:00am => 00:00:00
else if (postfix === 'am' && v === 12) {
v = 0;
}
}
jsDate.setHours(v);
break;
case 'H':
jsDate.setHours(v);
break;
case 'i':
v = v.substring(0, 2);
jsDate.setMinutes(v);
break;
case 's':
jsDate.setSeconds(v);
break;
}
part = "";
} else {
part = c;
}
}
return jsDate;
},
/**
* Guesses the format of a date string.
*
* @param {string} date A date string.
* @return {string|boolean} A string encoding the date/time format used by
* `date`, or `false` if `date` is invalid.
* @deprecated since 7.2.0.
* @memberOf Utils/Date
*/
guessFormat: function(date) {
SUGAR.App.logger.warn('The function `Utils.Date.guessFormat()` is deprecated and will be removed in a future release. ' +
'Please do not use it');
if (typeof date !== "string")
return false;
//Is there a time
var time = "", dateSep, dateParts, dateFormat, timeFormat, timeParts,
timeSep, ampmCase, timeEnd;
if (date.indexOf(" ") !== -1) {
time = date.substring(date.indexOf(" ") + 1, date.length);
date = date.substring(0, date.indexOf(" "));
}
//First detect if the date contains "-" or "/"
dateSep = "/";
if (date.indexOf("/") !== -1) {
}
else if (date.indexOf("-") !== -1) {
dateSep = "-";
}
else if (date.indexOf(".") !== -1) {
dateSep = ".";
}
else {
return false;
}
dateParts = date.split(dateSep);
dateFormat = "";
if (dateParts[0].length === 4) {
dateFormat = "Y" + dateSep + "m" + dateSep + "d";
}
else if (dateParts[2].length === 4) {
dateFormat = "m" + dateSep + "d" + dateSep + "Y";
}
else {
return false;
}
timeFormat = "";
timeParts = [];
// Check for time
if (time !== "") {
// start at the i, we always have minutes
timeParts.push("i");
timeSep = ":";
if (time.indexOf(".") === 2) {
timeSep = ".";
}
// if its long we have seconds
if (time.split(timeSep).length === 3) {
timeParts.push("s");
}
ampmCase = '';
// check for am/pm
timeEnd = time.substring(time.length - 2, time.length);
if (timeEnd.toLowerCase() === "am" || timeEnd.toLowerCase() === "pm") {
timeParts.unshift("h");
if (timeEnd.toLowerCase() === timeEnd) {
ampmCase = 'lower';
} else {
ampmCase = 'upper';
}
} else {
timeParts.unshift("H");
}
timeFormat = timeParts.join(timeSep);
// check for space between am/pm and time
if (time.indexOf(" ") !== -1) {
timeFormat += " ";
}
// deal with upper and lowercase am pm
if (ampmCase && ampmCase === 'upper') {
timeFormat += "A";
} else if (ampmCase && ampmCase === 'lower') {
timeFormat += "a";
}
dateFormat = dateFormat + " " + timeFormat;
}
return dateFormat;
},
/**
* Parses a date format string into each of its individual representations.
* Supports only the options that are supported by the `date.format`
* function.
*
* @param {string} format A date format string such as 'Y-m-d H:i:s'.
* @return {Object} Object with properties representing each piece of a
* format.
* @deprecated since 7.2.0.
* @memberOf Utils/Date
*/
parseFormat: function(format) {
SUGAR.App.logger.warn('The function `Utils.Date.parseFormat()` is deprecated and will be removed in a future release. ' +
'Please do not use it');
// if this format has been seen before then don't bother parsing it again
if (!(format in _formatStringCache)) {
var parts = {},
i,
c;
for (i = 0; i < format.length; i++) {
c = format.charAt(i);
switch (c) {
case 'm':
case 'n':
parts.month = c;
break;
case 'd':
case 'j':
parts.day = c;
break;
case 'Y':
parts.year = c;
break;
case 'H':
case 'h':
case 'g':
parts.hours = c;
break;
case 'i':
parts.minutes = c;
break;
case 's':
parts.seconds = c;
break;
case 'A':
case 'a':
parts.amPm = c;
break;
default:
break;
}
}
// cache the result so it doesn't have to be parsed again
_formatStringCache[format] = parts;
}
return _formatStringCache[format];
},
/**
* Formats JavaScript Date objects into date strings.
*
* @param {Date} date The date to format.
* @param {string} format Date format string such as 'Y-m-d H:i:s'.
* @return {string} The formatted date string.
* @deprecated since 7.2.0.
* @memberOf Utils/Date
*/
format: function(date, format) {
SUGAR.App.logger.warn('The function `Utils.Date.format()` is deprecated and will be removed in a future release. ' +
'Please do not use it');
if (!_.isDate(date)) return "";
// TODO: add support for userPrefs
var out = "", i, c, d, h, m, s;
for (i = 0; i < format.length; i++) {
c = format.charAt(i);
switch (c) {
case '\\':
out += format.charAt(i+1);
// skip next character
i++;
break;
case 'm':
case 'n':
m = date.getMonth() + 1;
out += (m < 10 && c === "m") ? "0" + m : m;
break;
case 'd':
case 'j':
d = date.getDate();
out += (d < 10 && c === "d") ? "0" + d : d;
break;
case 'Y':
out += date.getFullYear();
break;
case 'H':
case 'h':
case 'g':
h = date.getHours();
if (c === "h" || c === "g") {
h = h > 12 ? h - 12 : h;
//Convert 0 to 12 for 12 hour formats
h = h === 0 ? 12 : h;
}
out += (h < 10 && c !== "g") ? "0" + h : h;
break;
case 'i':
m = date.getMinutes();
out += m < 10 ? "0" + m : m;
break;
case 's':
s = date.getSeconds();
out += s < 10 ? "0" + s : s;
break;
case 'a':
case 'A':
if (date.getHours() < 12)
out += (c === "a") ? "am" : "AM";
else
out += (c === "a") ? "pm" : "PM";
break;
default :
out += c;
}
}
return out;
},
/**
* Rounds a date to the nearest 15 minutes.
*
* @param {Date} date A date to round.
* @return {Date} Rounded date.
* @deprecated since 7.2.0.
* @memberOf Utils/Date
*/
roundTime: function(date) {
SUGAR.App.logger.warn('The function `Utils.Date.roundTime()` is deprecated and will be removed in a future release. ' +
'Please do not use it');
if (!date.getMinutes) return 0;
var min = date.getMinutes();
if (min < 1) {
min = 0;
}
else if (min < 16) {
min = 15;
}
else if (min < 31) {
min = 30;
}
else if (min < 46) {
min = 45;
}
else {
min = 0;
date.setHours(date.getHours() + 1);
}
date.setMinutes(min);
return date;
},
/**
* Converts a UTC date to a local time date.
*
* @param {Date} date A UTC date.
* @return {Date} Date converted to local time.
* @deprecated since 7.2.0.
* @memberOf Utils/Date
*/
UTCtoLocalTime: function(date) {
SUGAR.App.logger.warn('The function `Utils.Date.UTCtoLocalTime()` is deprecated and will be removed in a future release. ' +
'Please do not use it');
//if not a Date return it
if (!(date instanceof Date)) return date;
// Push the UTC tag to convert the date into local date
return new Date(this.toUTC(date));
},
/**
* Converts a UTC date to a date in the timezone represented by the offset.
*
* @param {Date} date A UTC date.
* @param {number} offset The timezone's UTC offset in hours.
* @return {Date} Converted date.
* @deprecated since 7.2.0.
* @memberOf Utils/Date
*/
UTCtoTimezone: function(date, offset) {
SUGAR.App.logger.warn('The function `Utils.Date.UTCtoTimezone()` is deprecated and will be removed in a future release. ' +
'Please do not use it');
// if date is not a Date or there is no offset then return date
if (!(date instanceof Date) || undefined === offset || null === offset) {
return date;
}
// convert the offset to milliseconds
// parseFloat is used to guarantee that offset is numerical before converting it
offset = parseFloat(offset) * 60 * 60 * 1000;
// the offset really needs to be the difference between the local time offset and the user's
// preferred offset since javascript always represents dates in local time
// javascript uses +7 hours while the api uses -7 hours to represent the same timezone offset,
// so the javascript offset must be negated
offset -= date.getTimezoneOffset() * 60 * 1000 * -1;
return new Date(this.toUTC(date) + offset);
},
/**
* Converts the date from local time to UTC.
*
* @param {Date} date A UTC date.
* @return {number} The given `date` as milliseconds since the Unix epoch
* in UTC.
* @deprecated since 7.2.0.
* @memberOf Utils/Date
*/
toUTC: function(date) {
SUGAR.App.logger.warn('The function `Utils.Date.toUTC()` is deprecated and will be removed in a future release. ' +
'Please do not use it');
//if not a Date return it
if (!(date instanceof Date)) {
return date;
}
var year = date.getFullYear(),
month = date.getMonth(),
day = date.getDate(),
hours = date.getHours(),
minutes = date.getMinutes(),
seconds = date.getSeconds(),
milliseconds = date.getMilliseconds();
return Date.UTC(year, month, day, hours, minutes, seconds, milliseconds);
},
/**
* Converts a Date object into a relative time.
*
* @param {Date} date Date object to convert.
* @return {Object} Object containing relative time key string and value
* suitable for passing to a Handlebars template.
* @deprecated since 7.2.0.
* @memberOf Utils/Date
*/
getRelativeTimeLabel: function(date) {
SUGAR.App.logger.warn('The function `Utils.Date.getRelativeTimeLabel()` is deprecated and will be removed in a future release. ' +
'Please do not use it');
var rightNow = new Date();
var isFuture = rightNow.getTime() < date.getTime();
//If incoming date is future we want to flip the calculation
var diff = isFuture ? date - rightNow : rightNow - date;
var second = 1000,
minute = second * 60,
hour = minute * 60,
day = hour * 24,
ctx = { str : "", value: undefined};
if (isNaN(diff) || diff < 0) {
return ctx; // return blank string if unknown
}
if (diff < second * 2) {
// within 2 seconds
ctx.str = 'LBL_TIME_AGO_NOW';
return ctx;
}
if (diff < minute) {
ctx.str = isFuture ? 'LBL_TIME_UNTIL_SECONDS' : 'LBL_TIME_AGO_SECONDS';
ctx.value = Math.floor(diff / second);
return ctx;
}
if (diff < minute * 2) {
ctx.str = isFuture ? 'LBL_TIME_UNTIL_MINUTE' : 'LBL_TIME_AGO_MINUTE';
return ctx;
}
if (diff < hour) {
ctx.str = isFuture ? 'LBL_TIME_UNTIL_MINUTES' : 'LBL_TIME_AGO_MINUTES';
ctx.value = Math.floor(diff / minute);
return ctx;
}
if (diff < hour * 2) {
ctx.str = isFuture ? 'LBL_TIME_UNTIL_HOUR' : 'LBL_TIME_AGO_HOUR';
return ctx;
}
if (diff < day) {
ctx.str = isFuture ? 'LBL_TIME_UNTIL_HOURS' : 'LBL_TIME_AGO_HOURS';
ctx.value = Math.floor(diff / hour);
return ctx;
}
if (diff > day && diff < day * 2) {
ctx.str = isFuture ? 'LBL_TIME_UNTIL_DAY' : 'LBL_TIME_AGO_DAY';
return ctx;
}
if (diff < day * 365) {
ctx.str = isFuture ? 'LBL_TIME_UNTIL_DAYS' : 'LBL_TIME_AGO_DAYS';
ctx.value = Math.floor(diff / day);
return ctx;
}
else {
ctx.str = isFuture ? 'LBL_TIME_UNTIL_YEAR' : 'LBL_TIME_AGO_YEAR';
return ctx;
}
},
/**
* Parses the `display_default` property which the server returns for
* datetimecombo metadata, etc. (for example, if the user sets the default
* in Studio).
*
* Examples:
* ```
* const DateManager = require('./date');
* DateManager.parseDisplayDefault('+1 day&06:00pm');
* DateManager.parseDisplayDefault('-1 day&06:00pm');
* DateManager.parseDisplayDefault('+1 week&06:00pm');
* DateManager.parseDisplayDefault('+1 month&06:00pm');
* DateManager.parseDisplayDefault('+1 year&06:00pm');
* DateManager.parseDisplayDefault('now&06:00pm');
* DateManager.parseDisplayDefault('next monday&06:00pm');
* DateManager.parseDisplayDefault('next friday&06:00pm');
* DateManager.parseDisplayDefault('first of next month@06:00pm');
* ```
*
* @param {string} displayDefault The value of the `display_default`
* property.
* @param {Date} [now=new Date()] An optional date to use as a point of
* reference (since we essentially convert "+1 day", etc., to an
* adjusted date). This is mainly for testing odd dates like adding a
* month specifically to January 31, etc.
* @return {Date|undefined} The date created by evaluating `displayDefault`
* relative to `now`.
* @deprecated since 7.2.0.
* @memberOf Utils/Date
*/
parseDisplayDefault: function(displayDefault, now) {
SUGAR.App.logger.warn('The function `Utils.Date.parseDisplayDefault()` is deprecated and will be removed in a future release. ' +
'Please do not use it');
var dateMap, d, dt, humanDate, defaultTime, addMonths, next, setTimePart;
var dateObj = now ? now : new Date(), op, timeAway, parts;
if(!displayDefault) return displayDefault;
// This adds months from 'd' date passed in. 'n' is the number of months to add.
// Of course, you can use a negative sign to subtract months.
addMonths = function(d, n) {
var day = d.getDate();
d.setMonth(d.getMonth()+n);
if (d.getDate() < day) {
d.setDate(1);
d.setDate(d.getDate()-1);
}
return d;
};
// Helper to return the number of days from now to 'day' (see dateObj initialization above)
next = function(day) {
var days, todayDay, daysUntilNext;
days = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
todayDay = dateObj.getDay();
day = day.toLowerCase();
for (var i = 7; i--;) {
if (day === days[i]) {
day = (i <= todayDay) ? (i + 7) : i;
break;
}
}
daysUntilNext = day - todayDay;
return daysUntilNext;
};
// Maps keys with functions that converts human date as appropriate per key (e.g. +1 day, etc.)
dateMap = {
day: function(operator) {
return new Date(dateObj.setDate(dateObj.getDate() + operator));
},
week: function(operator) {
return new Date(dateObj.setDate(dateObj.getDate() + (operator * 7)));
},
month: function(operator) {
return addMonths(dateObj, operator);
},
year: function(operator) {
return addMonths(dateObj, (operator*12));
},
'now': function() { return dateObj; },
'next monday': function() {
return new Date(dateObj.setDate(dateObj.getDate() + next("monday")));
},
'next friday': function() {
return new Date(dateObj.setDate(dateObj.getDate() + next("friday")));
},
'first of next month': function() {
// First set date to first day of "this month" so we don't have a potential
// date overrun (e.g. today's Oct 31 and we +1 month but no Nov 31 so Dec 1!)
dateObj.setDate(1);
// Now it's safe to add +1 months
var sinceEpoch = dateObj.setMonth(dateObj.getMonth() + 1);
return new Date(sinceEpoch);
}
};
// Main entry point - parses displayDefault and calls correspondingly
// mapped function to resolve to suitable date string for date.parse
if(displayDefault && displayDefault.indexOf('&') >= 0) {
dt = displayDefault.split("&"); //e.g. +1 day&06:00pm
humanDate = dt[0];
defaultTime = dt[1];
} else {
humanDate = displayDefault; // assume no time part e.g. for type date
}
if(humanDate.match(/\b(now|day|week|month|year)/g)) {
if(humanDate.indexOf('now') === 0) {
timeAway = 'now';
} else if(humanDate.indexOf("first day of next month") !== -1) {
timeAway = 'firstnextmonth';
} else {
parts = humanDate.split(' ');
op = parts[0];
timeAway = parts[1];
}
if(timeAway.match('now')) {
d = dateMap.now();
}
else if(timeAway.match('firstnextmonth')) {
d = dateMap['first of next month']();
}
else if(timeAway.match('days?')) {
d = dateMap.day(parseInt(op, 10));
}
else if(timeAway.match('weeks?')) {
d = dateMap.week(parseInt(op, 10));
}
else if(timeAway.match('months?')) {
d = dateMap.month(parseInt(op, 10));
}
else if(timeAway.match('years?')) {
d = dateMap.year(parseInt(op, 10));
}
else {
if (!dateMap[humanDate]) {
return;
}
// TODO Better to log error???
d = dateMap[humanDate]();
}
} else {
if (!dateMap[humanDate]) {
return;
}
d = dateMap[humanDate]();
}
setTimePart = (function(d) {
var timeParts, hours, minutes;
//Fixes bug SP-1280: Turns out hours in displayDefault can be either of single/double digits
timeParts = /^(\d\d?).(\d{2}).?([ap]m)?$/.exec(defaultTime);
if (timeParts && timeParts.length > 2) {
hours = timeParts[1];
if (hours) {
if (timeParts[3] === 'pm') {
if (hours < 12) {
hours = (parseInt(hours, 10) + 12);
}
} else {
// Set 12am to 00
if (hours === '12') {
hours = '0';
}
}
d.setHours(parseInt(hours, 10));
}
minutes = timeParts[2];
if (minutes) {
d.setMinutes(parseInt(minutes, 10));
}
}
}(d));
return d;
},
/**
* Converts a PHP date format string to its Bootstrap Datepicker equivalent.
*
* @param {string} formatSpec The original SugarCRM (PHP) date format.
* @return {string} Format spec passed in normalized for the Bootstrap
* Datepicker widget. If falsy, returns empty string.
* @memberOf Utils/Date
*/
toDatepickerFormat: function(formatSpec) {
if(_.isUndefined(formatSpec) || !formatSpec) return '';
var normalizedFormatSpec,
sugarToDatepickerMap = {
'y': 'yy',
'Y': 'yyyy',
'm': 'mm',
'd': 'dd'
};
normalizedFormatSpec = formatSpec.replace(/[yYmd]/g, function(s) {
return sugarToDatepickerMap[s];
});
return normalizedFormatSpec;
},
/**
* Strips out the 'T' and either the 'Z' or +00:00 from a date string.
*
* @param {string} value The date string to strip in ISO 8601 format.
* @return {string} The result of removing the time delimiter and time zone
* indicator from `value`.
* @deprecated since 7.2.0.
* @memberOf Utils/Date
*/
stripIsoTimeDelimterAndTZ: function(value) {
SUGAR.App.logger.warn('The function `Utils.Date.stripIsoTimeDelimterAndTZ()` is deprecated and will be removed in a future release. ' +
'Please do not use it');
if(!_.isUndefined(value) && value) {
// Since s.replace('T', ' ').replace('Z','') assumes we have Z it's better to do:
// str.replace('T', ' ').substr(0, 19) ... which works for both of following formats:
// '2012-11-07T04:28:52+00:00'.replace('T', ' ').substr(0, 19)
// "2012-11-06 20:00:06.651Z".replace('T', ' ').substring(0, 19)
return value.replace("T", " ").substr(0, 19);
}
},
/**
* Determines if a date string is in ISO 8601 format.
*
* @param {string} val The date string to check.
* @return {boolean} `true` if `val` is compliant with ISO 8601.
* @deprecated since 7.2.0.
* @memberOf Utils/Date
*/
isIso: function(val) {
SUGAR.App.logger.warn('The function `Utils.Date.isIso()` is deprecated and will be removed in a future release. ' +
'Please do not use it');
var yyyymmddExact,
yyyymmddLooseMatch;
// First match most nominal case yyyy-mm-dd
yyyymmddExact = val.match(/^([0-9]{4})-?(1[0-2]|0[1-9])-?(3[0-1]|0[1-9]|[1-2][0-9])$/);
if (yyyymmddExact) {
return true;
}
// If we have any of the iso 8601 chars and still starts with yyyy-mm-dd
if (val.match(/[T\+Z ]/g)) {
yyyymmddLooseMatch = val.match(/^([0-9]{4})-?(1[0-2]|0[1-9])-?(3[0-1]|0[1-9]|[1-2][0-9])/);
if (yyyymmddLooseMatch) return true;
}
return false;
}
});
_.extend(date.fn, {
/**
* Formats this date to a string based on user preferences.
*
* @param {boolean} [dateOnly=false] Pass `true` to only get the date.
* @param {Data/Bean|Object} [user=Core.User] The user bean or the
* current logged in user object.
* @return {string} The formatted date based on `user`'s preference.
* @memberOf Utils/Date
* @instance
*/
formatUser: function(dateOnly, user) {
var format = date.getUserDateFormat(user);
if (!dateOnly) {
format += ' ' + date.getUserTimeFormat(user);
}
return this.format(format);
},
/**
* Formats this date to a string according to the server date format and locale.
*
* @param {boolean} [dateOnly=false] Pass `true` to only get the date.
* @return {string} This date formatted according to the server date format.
* @memberOf Utils/Date
* @instance
*/
formatServer: function(dateOnly) {
var format = dateOnly && date._serverDateFormat;
let originalLocale = this.locale();
this.locale(date._serverLocale);
let formatted = this.format(format);
this.locale(originalLocale);
return formatted;
},
/**
* Returns `true` if this date is between the given `startDate` and
* `endDate`.
*
* @param {string} startDate The start date of the period to test.
* @param {string} endDate The end date of the period to test.
* @param {boolean} [inclusive=true] If `true`, include `startDate` and
* `endDate` in the range of acceptable dates.
* @return {boolean} `true` if this date is between `startDate` and `endDate`;
* `false` otherwise.
* @memberOf Utils/Date
* @instance
*/
isBetween: function(startDate, endDate, inclusive) {
// inclusive should default to true unless explicitly sent false as the param
inclusive = _.isUndefined(inclusive) ? true : inclusive;
var isInRange = (this.isAfter(startDate) && this.isBefore(endDate));
// if we're including start and end dates and date1 hasn't been found to be inside the range yet
if (inclusive && !isInRange) {
// check if date1 is on the start or end dates
isInRange = (this.isSame(startDate) || this.isSame(endDate));
}
return isInRange;
}
});
/**
* Extends
* {@link https://momentjs.com/docs/#/durations/|moment.js' Duration class}.
*
* @class Utils/Date.duration
*/
_.extend(date.duration.fn, {
/**
* Gets the display string for this duration.
* Note: It only supports days, hours, and minutes.
*
* @return {string} This `duration` formatted as a human-readable string.
* (eg. '8 minutes').
* @memberOf Utils/Date.duration
* @instance
*/
format: function() {
var duration = [],
minutes = this.minutes(),
hours = this.hours(),
days = Math.floor(this.asDays());
if (days > 0) {
duration.push(days);
duration.push(Language.get(days === 1 ? 'LBL_DURATION_DAY' : 'LBL_DURATION_DAYS'));
}
if (hours > 0) {
duration.push(hours);
duration.push(Language.get(hours === 1 ? 'LBL_DURATION_HOUR' : 'LBL_DURATION_HOURS'));
}
if (minutes > 0) {
duration.push(minutes);
duration.push(Language.get(minutes === 1 ? 'LBL_DURATION_MINUTE' : 'LBL_DURATION_MINUTES'));
}
return duration.join(' ');
}
});
module.exports = date;