293 lines
9.1 KiB
JavaScript
293 lines
9.1 KiB
JavaScript
const YEAR = 'year';
|
|
const MONTH = 'month';
|
|
const DAY = 'day';
|
|
const HOUR = 'hour';
|
|
const MINUTE = 'minute';
|
|
const SECOND = 'second';
|
|
const MILLISECOND = 'millisecond';
|
|
|
|
export default {
|
|
parse_duration(duration) {
|
|
const regex = /([0-9]+)(y|m|d|h|min|s|ms)/gm;
|
|
const matches = regex.exec(duration);
|
|
if (matches !== null) {
|
|
if (matches[2] === 'y') {
|
|
return { duration: parseInt(matches[1]), scale: `year` };
|
|
} else if (matches[2] === 'm') {
|
|
return { duration: parseInt(matches[1]), scale: `month` };
|
|
} else if (matches[2] === 'd') {
|
|
return { duration: parseInt(matches[1]), scale: `day` };
|
|
} else if (matches[2] === 'h') {
|
|
return { duration: parseInt(matches[1]), scale: `hour` };
|
|
} else if (matches[2] === 'min') {
|
|
return { duration: parseInt(matches[1]), scale: `minute` };
|
|
} else if (matches[2] === 's') {
|
|
return { duration: parseInt(matches[1]), scale: `second` };
|
|
} else if (matches[2] === 'ms') {
|
|
return { duration: parseInt(matches[1]), scale: `millisecond` };
|
|
}
|
|
}
|
|
},
|
|
parse(date, date_separator = '-', time_separator = /[.:]/) {
|
|
if (date instanceof Date) {
|
|
return date;
|
|
}
|
|
if (typeof date === 'string') {
|
|
let date_parts, time_parts;
|
|
const parts = date.split(' ');
|
|
date_parts = parts[0]
|
|
.split(date_separator)
|
|
.map((val) => parseInt(val, 10));
|
|
time_parts = parts[1] && parts[1].split(time_separator);
|
|
|
|
// month is 0 indexed
|
|
date_parts[1] = date_parts[1] ? date_parts[1] - 1 : 0;
|
|
|
|
let vals = date_parts;
|
|
|
|
if (time_parts && time_parts.length) {
|
|
if (time_parts.length === 4) {
|
|
time_parts[3] = '0.' + time_parts[3];
|
|
time_parts[3] = parseFloat(time_parts[3]) * 1000;
|
|
}
|
|
vals = vals.concat(time_parts);
|
|
}
|
|
return new Date(...vals);
|
|
}
|
|
},
|
|
|
|
to_string(date, with_time = false) {
|
|
if (!(date instanceof Date)) {
|
|
throw new TypeError('Invalid argument type');
|
|
}
|
|
const vals = this.get_date_values(date).map((val, i) => {
|
|
if (i === 1) {
|
|
// add 1 for month
|
|
val = val + 1;
|
|
}
|
|
|
|
if (i === 6) {
|
|
return padStart(val + '', 3, '0');
|
|
}
|
|
|
|
return padStart(val + '', 2, '0');
|
|
});
|
|
const date_string = `${vals[0]}-${vals[1]}-${vals[2]}`;
|
|
const time_string = `${vals[3]}:${vals[4]}:${vals[5]}.${vals[6]}`;
|
|
|
|
return date_string + (with_time ? ' ' + time_string : '');
|
|
},
|
|
|
|
format(date, date_format = 'YYYY-MM-DD HH:mm:ss.SSS', lang = 'en') {
|
|
const dateTimeFormat = new Intl.DateTimeFormat(lang, {
|
|
month: 'long',
|
|
});
|
|
const dateTimeFormatShort = new Intl.DateTimeFormat(lang, {
|
|
month: 'short',
|
|
});
|
|
const month_name = dateTimeFormat.format(date);
|
|
const month_name_capitalized =
|
|
month_name.charAt(0).toUpperCase() + month_name.slice(1);
|
|
|
|
const values = this.get_date_values(date).map((d) => padStart(d, 2, 0));
|
|
const format_map = {
|
|
YYYY: values[0],
|
|
MM: padStart(+values[1] + 1, 2, 0),
|
|
DD: values[2],
|
|
HH: values[3],
|
|
mm: values[4],
|
|
ss: values[5],
|
|
SSS: values[6],
|
|
D: values[2],
|
|
MMMM: month_name_capitalized,
|
|
MMM: dateTimeFormatShort.format(date),
|
|
};
|
|
|
|
let str = date_format;
|
|
const formatted_values = [];
|
|
|
|
Object.keys(format_map)
|
|
.sort((a, b) => b.length - a.length) // big string first
|
|
.forEach((key) => {
|
|
if (str.includes(key)) {
|
|
str = str.replaceAll(key, `$${formatted_values.length}`);
|
|
formatted_values.push(format_map[key]);
|
|
}
|
|
});
|
|
|
|
formatted_values.forEach((value, i) => {
|
|
str = str.replaceAll(`$${i}`, value);
|
|
});
|
|
|
|
return str;
|
|
},
|
|
|
|
diff(date_a, date_b, scale = 'day') {
|
|
let milliseconds, seconds, hours, minutes, days, months, years;
|
|
|
|
milliseconds =
|
|
date_a -
|
|
date_b +
|
|
(date_b.getTimezoneOffset() - date_a.getTimezoneOffset()) * 60000;
|
|
seconds = milliseconds / 1000;
|
|
minutes = seconds / 60;
|
|
hours = minutes / 60;
|
|
days = hours / 24;
|
|
// Calculate months across years
|
|
let yearDiff = date_a.getFullYear() - date_b.getFullYear();
|
|
let monthDiff = date_a.getMonth() - date_b.getMonth();
|
|
// calculate extra
|
|
monthDiff += (days % 30) / 30;
|
|
|
|
/* If monthDiff is negative, date_b is in an earlier month than
|
|
date_a and thus subtracted from the year difference in months */
|
|
months = yearDiff * 12 + monthDiff;
|
|
/* If date_a's (e.g. march 1st) day of the month is smaller than date_b (e.g. february 28th),
|
|
adjust the month difference */
|
|
if (date_a.getDate() < date_b.getDate()) {
|
|
months--;
|
|
}
|
|
|
|
// Calculate years based on actual months
|
|
years = months / 12;
|
|
|
|
if (!scale.endsWith('s')) {
|
|
scale += 's';
|
|
}
|
|
|
|
return (
|
|
Math.round(
|
|
{
|
|
milliseconds,
|
|
seconds,
|
|
minutes,
|
|
hours,
|
|
days,
|
|
months,
|
|
years,
|
|
}[scale] * 100,
|
|
) / 100
|
|
);
|
|
},
|
|
|
|
today() {
|
|
const vals = this.get_date_values(new Date()).slice(0, 3);
|
|
return new Date(...vals);
|
|
},
|
|
|
|
now() {
|
|
return new Date();
|
|
},
|
|
|
|
add(date, qty, scale) {
|
|
qty = parseInt(qty, 10);
|
|
const vals = [
|
|
date.getFullYear() + (scale === YEAR ? qty : 0),
|
|
date.getMonth() + (scale === MONTH ? qty : 0),
|
|
date.getDate() + (scale === DAY ? qty : 0),
|
|
date.getHours() + (scale === HOUR ? qty : 0),
|
|
date.getMinutes() + (scale === MINUTE ? qty : 0),
|
|
date.getSeconds() + (scale === SECOND ? qty : 0),
|
|
date.getMilliseconds() + (scale === MILLISECOND ? qty : 0),
|
|
];
|
|
return new Date(...vals);
|
|
},
|
|
|
|
start_of(date, scale) {
|
|
const scores = {
|
|
[YEAR]: 6,
|
|
[MONTH]: 5,
|
|
[DAY]: 4,
|
|
[HOUR]: 3,
|
|
[MINUTE]: 2,
|
|
[SECOND]: 1,
|
|
[MILLISECOND]: 0,
|
|
};
|
|
|
|
function should_reset(_scale) {
|
|
const max_score = scores[scale];
|
|
return scores[_scale] <= max_score;
|
|
}
|
|
|
|
const vals = [
|
|
date.getFullYear(),
|
|
should_reset(YEAR) ? 0 : date.getMonth(),
|
|
should_reset(MONTH) ? 1 : date.getDate(),
|
|
should_reset(DAY) ? 0 : date.getHours(),
|
|
should_reset(HOUR) ? 0 : date.getMinutes(),
|
|
should_reset(MINUTE) ? 0 : date.getSeconds(),
|
|
should_reset(SECOND) ? 0 : date.getMilliseconds(),
|
|
];
|
|
|
|
return new Date(...vals);
|
|
},
|
|
|
|
clone(date) {
|
|
return new Date(...this.get_date_values(date));
|
|
},
|
|
|
|
get_date_values(date) {
|
|
return [
|
|
date.getFullYear(),
|
|
date.getMonth(),
|
|
date.getDate(),
|
|
date.getHours(),
|
|
date.getMinutes(),
|
|
date.getSeconds(),
|
|
date.getMilliseconds(),
|
|
];
|
|
},
|
|
|
|
convert_scales(period, to_scale) {
|
|
const TO_DAYS = {
|
|
millisecond: 1 / 60 / 60 / 24 / 1000,
|
|
second: 1 / 60 / 60 / 24,
|
|
minute: 1 / 60 / 24,
|
|
hour: 1 / 24,
|
|
day: 1,
|
|
month: 30,
|
|
year: 365,
|
|
};
|
|
const { duration, scale } = this.parse_duration(period);
|
|
let in_days = duration * TO_DAYS[scale];
|
|
return in_days / TO_DAYS[to_scale];
|
|
},
|
|
|
|
get_days_in_month(date) {
|
|
const no_of_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
|
|
const month = date.getMonth();
|
|
|
|
if (month !== 1) {
|
|
return no_of_days[month];
|
|
}
|
|
|
|
// Feb
|
|
const year = date.getFullYear();
|
|
if ((year % 4 === 0 && year % 100 != 0) || year % 400 === 0) {
|
|
return 29;
|
|
}
|
|
return 28;
|
|
},
|
|
|
|
get_days_in_year(date) {
|
|
return date.getFullYear() % 4 ? 365 : 366;
|
|
},
|
|
};
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
|
|
function padStart(str, targetLength, padString) {
|
|
str = str + '';
|
|
targetLength = targetLength >> 0;
|
|
padString = String(typeof padString !== 'undefined' ? padString : ' ');
|
|
if (str.length > targetLength) {
|
|
return String(str);
|
|
} else {
|
|
targetLength = targetLength - str.length;
|
|
if (targetLength > padString.length) {
|
|
padString += padString.repeat(targetLength / padString.length);
|
|
}
|
|
return padString.slice(0, targetLength) + String(str);
|
|
}
|
|
}
|