mirror of
https://bitbucket.org/studiotem/jandl-taskmanager-app.git
synced 2026-10-21 14:29:34 +00:00
vue-cal
This commit is contained in:
1 parent
f6d0764969
commit
944ea92cef
56 files changed
+5535
No files matched your search
@@ -0,0 +1,376 @@
|
||||
/**
|
||||
* Date Utils & prototypes.
|
||||
*
|
||||
* Waiting for VS Code to support JavaScript private fields.
|
||||
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Class_fields
|
||||
* Meantime keep `_` for private.
|
||||
*/
|
||||
|
||||
let now, todayDate, todayF, self
|
||||
let _dateObject = {}
|
||||
let _timeObject = {}
|
||||
|
||||
export default class DateUtils {
|
||||
texts = {}
|
||||
|
||||
constructor (texts, noPrototypes = false) {
|
||||
self = this // For use in Date prototypes.
|
||||
this._texts = texts
|
||||
|
||||
// Add prototypes ASAP - only once.
|
||||
if (!noPrototypes && Date && !Date.prototype.addDays) this._initDatePrototypes()
|
||||
|
||||
// @todo: This would be nicer, but how to set Date.noPrototypes ASAP only if user wants?
|
||||
// if (Date.noPrototypes) delete Date.noPrototypes
|
||||
// else this._initDatePrototypes()
|
||||
}
|
||||
|
||||
_initDatePrototypes () {
|
||||
/* eslint-disable no-extend-native */
|
||||
Date.prototype.addDays = function (days) { return self.addDays(this, days) }
|
||||
Date.prototype.subtractDays = function (days) { return self.subtractDays(this, days) }
|
||||
Date.prototype.addHours = function (hours) { return self.addHours(this, hours) }
|
||||
Date.prototype.subtractHours = function (hours) { return self.subtractHours(this, hours) }
|
||||
Date.prototype.addMinutes = function (minutes) { return self.addMinutes(this, minutes) }
|
||||
Date.prototype.subtractMinutes = function (minutes) { return self.subtractMinutes(this, minutes) }
|
||||
Date.prototype.getWeek = function () { return self.getWeek(this) }
|
||||
Date.prototype.isToday = function () { return self.isToday(this) }
|
||||
Date.prototype.isLeapYear = function () { return self.isLeapYear(this) }
|
||||
Date.prototype.format = function (format = 'YYYY-MM-DD') { return self.formatDate(this, format) }
|
||||
Date.prototype.formatTime = function (format = 'HH:mm') { return self.formatTime(this, format) }
|
||||
/* eslint-enable no-extend-native */
|
||||
}
|
||||
|
||||
removePrototypes () {
|
||||
delete Date.prototype.addDays
|
||||
delete Date.prototype.subtractDays
|
||||
delete Date.prototype.addHours
|
||||
delete Date.prototype.subtractHours
|
||||
delete Date.prototype.addMinutes
|
||||
delete Date.prototype.subtractMinutes
|
||||
delete Date.prototype.getWeek
|
||||
delete Date.prototype.isToday
|
||||
delete Date.prototype.isLeapYear
|
||||
delete Date.prototype.format
|
||||
delete Date.prototype.formatTime
|
||||
}
|
||||
|
||||
updateTexts (texts) {
|
||||
this._texts = texts
|
||||
}
|
||||
|
||||
// Cache Today's date (to a maximum) for better isToday() performances. Formatted without leading 0.
|
||||
// We still need to update Today's date when Today changes without page refresh.
|
||||
_todayFormatted () {
|
||||
if (todayDate !== (new Date()).getDate()) {
|
||||
now = new Date()
|
||||
todayDate = now.getDate()
|
||||
todayF = `${now.getFullYear()}-${now.getMonth()}-${now.getDate()}`
|
||||
}
|
||||
|
||||
return todayF
|
||||
}
|
||||
|
||||
// UTILITIES.
|
||||
// ====================================================================
|
||||
addDays (date, days) {
|
||||
const d = new Date(date.valueOf())
|
||||
d.setDate(d.getDate() + days)
|
||||
return d
|
||||
}
|
||||
|
||||
subtractDays (date, days) {
|
||||
const d = new Date(date.valueOf())
|
||||
d.setDate(d.getDate() - days)
|
||||
return d
|
||||
}
|
||||
|
||||
addHours (date, hours) {
|
||||
const d = new Date(date.valueOf())
|
||||
d.setHours(d.getHours() + hours)
|
||||
return d
|
||||
}
|
||||
|
||||
subtractHours (date, hours) {
|
||||
const d = new Date(date.valueOf())
|
||||
d.setHours(d.getHours() - hours)
|
||||
return d
|
||||
}
|
||||
|
||||
addMinutes (date, minutes) {
|
||||
const d = new Date(date.valueOf())
|
||||
d.setMinutes(d.getMinutes() + minutes)
|
||||
return d
|
||||
}
|
||||
|
||||
subtractMinutes (date, minutes) {
|
||||
const d = new Date(date.valueOf())
|
||||
d.setMinutes(d.getMinutes() - minutes)
|
||||
return d
|
||||
}
|
||||
|
||||
getWeek (date) {
|
||||
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()))
|
||||
const dayNum = d.getUTCDay() || 7
|
||||
d.setUTCDate(d.getUTCDate() + 4 - dayNum)
|
||||
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1))
|
||||
return Math.ceil((((d - yearStart) / 86400000) + 1) / 7)
|
||||
}
|
||||
|
||||
isToday (date) {
|
||||
return `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}` === this._todayFormatted()
|
||||
}
|
||||
|
||||
isLeapYear (date) {
|
||||
const year = date.getFullYear()
|
||||
return !(year % 400) || (year % 100 && !(year % 4))
|
||||
}
|
||||
|
||||
// Returns today if it's FirstDayOfWeek (Monday or Sunday) or previous FirstDayOfWeek otherwise.
|
||||
getPreviousFirstDayOfWeek (date = null, weekStartsOnSunday) {
|
||||
const prevFirstDayOfWeek = (date && new Date(date.valueOf())) || new Date()
|
||||
const dayModifier = weekStartsOnSunday ? 7 : 6
|
||||
prevFirstDayOfWeek.setDate(prevFirstDayOfWeek.getDate() - (prevFirstDayOfWeek.getDay() + dayModifier) % 7)
|
||||
return prevFirstDayOfWeek
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string to a Javascript Date object. If a Date object is passed, return it as is.
|
||||
*
|
||||
* @param {String | Date} date the string to convert to Date.
|
||||
* @return {Date} the equivalent Javascript Date object.
|
||||
*/
|
||||
stringToDate (date) {
|
||||
if (date instanceof Date) return date
|
||||
// Regexp way is less performant: https://jsperf.com/string-to-date-regexp-vs-new-date
|
||||
// const [, y, m, d, h = 0, min = 0] = date.match(/(\d{4})-(\d{2})-(\d{2})(?: (\d{2}):(\d{2}))?/)
|
||||
// return new Date(y, parseInt(m) - 1, d, h, min)
|
||||
|
||||
if (date.length === 10) date += ' 00:00'
|
||||
return new Date(date.replace(/-/g, '/')) // replace '-' with '/' for Safari.
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply takes a Date and returns the associated time in minutes (sum of hours + minutes).
|
||||
*
|
||||
* @param {Date} date the JavaScript Date to extract minutes from.
|
||||
* @return {Number} the number of minutes (total of hours plus minutes).
|
||||
*/
|
||||
dateToMinutes = date => date.getHours() * 60 + date.getMinutes()
|
||||
|
||||
/**
|
||||
* Count the number of days this date range spans onto.
|
||||
* E.g. countDays(2019-11-02 18:00, 2019-11-03 02:00) = 2
|
||||
*
|
||||
* @param {String | Date} start the start date
|
||||
* @param {String | Date} end the end date
|
||||
* @return {Integer} The number of days this date range involves
|
||||
*/
|
||||
countDays (start, end) {
|
||||
// replace '-' with '/' for Safari.
|
||||
if (typeof start === 'string') start = start.replace(/-/g, '/')
|
||||
if (typeof end === 'string') end = end.replace(/-/g, '/')
|
||||
|
||||
// Set start & end at midnight then compare the delta. Don't modify the original dates.
|
||||
start = (new Date(start)).setHours(0, 0, 0, 0)
|
||||
// Set end at midnight plus 1 sec, so Math.ceil will round it up to a full day.
|
||||
end = (new Date(end)).setHours(0, 0, 1, 0)
|
||||
|
||||
// Remove the potential daylight saving delta.
|
||||
const timezoneDiffMs = (new Date(end).getTimezoneOffset() - new Date(start).getTimezoneOffset()) * 60 * 1000
|
||||
return Math.ceil((end - start - timezoneDiffMs) / (24 * 3600 * 1000))
|
||||
}
|
||||
|
||||
/**
|
||||
* Take 2 dates and check if within the same time step (useful in overlapping events).
|
||||
*
|
||||
* @return {Boolean} `true` if their time is included in the same time step,
|
||||
* this means these 2 dates are very close.
|
||||
*/
|
||||
datesInSameTimeStep (date1, date2, timeStep) {
|
||||
return Math.abs(date1.getTime() - date2.getTime()) <= timeStep * 60 * 1000
|
||||
}
|
||||
// ====================================================================
|
||||
|
||||
// FORMATTERS.
|
||||
// ====================================================================
|
||||
/**
|
||||
* Formats a date/time to the given format and returns the formatted string.
|
||||
*
|
||||
* @param {Date} date a JavaScript Date object to format.
|
||||
* @param {String} format the wanted format.
|
||||
* @param {Object} texts Optional: the localized texts object to override the vue-cal one in this._texts.
|
||||
* This becomes useful when showing multiple instances with different languages,
|
||||
* like in the documentation page.
|
||||
* @return {String} the formatted date.
|
||||
*/
|
||||
formatDate (date, format = 'YYYY-MM-DD', texts = null) {
|
||||
if (!texts) texts = this._texts
|
||||
if (!format) format = 'YYYY-MM-DD' // Allows passing null for default format.
|
||||
if (format === 'YYYY-MM-DD') return this.formatDateLite(date)
|
||||
|
||||
// Reinit the date and time object on each function call.
|
||||
_dateObject = {}
|
||||
_timeObject = {}
|
||||
|
||||
// Each keyword is a function to load the dateObject or timeObject on demand: no wasted resource.
|
||||
const dateObj = {
|
||||
YYYY: () => this._hydrateDateObject(date, texts).YYYY,
|
||||
YY: () => this._hydrateDateObject(date, texts).YY(),
|
||||
M: () => this._hydrateDateObject(date, texts).M,
|
||||
MM: () => this._hydrateDateObject(date, texts).MM(),
|
||||
MMM: () => this._hydrateDateObject(date, texts).MMM(),
|
||||
MMMM: () => this._hydrateDateObject(date, texts).MMMM(),
|
||||
MMMMG: () => this._hydrateDateObject(date, texts).MMMMG(),
|
||||
D: () => this._hydrateDateObject(date, texts).D,
|
||||
DD: () => this._hydrateDateObject(date, texts).DD(),
|
||||
S: () => this._hydrateDateObject(date, texts).S(),
|
||||
d: () => this._hydrateDateObject(date, texts).d,
|
||||
dd: () => this._hydrateDateObject(date, texts).dd(),
|
||||
ddd: () => this._hydrateDateObject(date, texts).ddd(),
|
||||
dddd: () => this._hydrateDateObject(date, texts).dddd(),
|
||||
HH: () => this._hydrateTimeObject(date, texts).HH,
|
||||
H: () => this._hydrateTimeObject(date, texts).H,
|
||||
hh: () => this._hydrateTimeObject(date, texts).hh,
|
||||
h: () => this._hydrateTimeObject(date, texts).h,
|
||||
am: () => this._hydrateTimeObject(date, texts).am,
|
||||
AM: () => this._hydrateTimeObject(date, texts).AM,
|
||||
mm: () => this._hydrateTimeObject(date, texts).mm,
|
||||
m: () => this._hydrateTimeObject(date, texts).m
|
||||
}
|
||||
|
||||
return format.replace(/(\{[a-zA-Z]+\}|[a-zA-Z]+)/g, (m, contents) => {
|
||||
const result = dateObj[contents.replace(/\{|\}/g, '')]
|
||||
return result !== undefined ? result() : contents
|
||||
})
|
||||
}
|
||||
|
||||
// More performant function to convert a Date to `YYYY-MM-DD` formatted string only.
|
||||
formatDateLite (date) {
|
||||
const m = date.getMonth() + 1
|
||||
const d = date.getDate()
|
||||
return `${date.getFullYear()}-${m < 10 ? '0' : ''}${m}-${d < 10 ? '0' : ''}${d}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a time (from Date or number of mins) to the given format and returns the formatted string.
|
||||
*
|
||||
* @param {Date | Number} date a JavaScript Date object or a time in minutes.
|
||||
* @param {String} format the wanted format.
|
||||
* @param {Object} texts Optional: the localized texts object to override the vue-cal one in this._texts.
|
||||
* This becomes useful when showing multiple instances with different languages,
|
||||
* like in the documentation page.
|
||||
* @param {Boolean} round if time is 23:59:59, rounds up to 24:00 for formatting only.
|
||||
* @return {String} the formatted time.
|
||||
*/
|
||||
formatTime (date, format = 'HH:mm', texts = null, round = false) {
|
||||
let shouldRound = false
|
||||
if (round) {
|
||||
const [h, m, s] = [date.getHours(), date.getMinutes(), date.getSeconds()]
|
||||
if ((h + m + s) === (23 + 59 + 59)) shouldRound = true
|
||||
}
|
||||
|
||||
if (date instanceof Date && format === 'HH:mm') return shouldRound ? '24:00' : this.formatTimeLite(date)
|
||||
|
||||
_timeObject = {} // Reinit the time object on each function call.
|
||||
if (!texts) texts = this._texts
|
||||
const timeObj = this._hydrateTimeObject(date, texts)
|
||||
|
||||
const formatted = format.replace(/(\{[a-zA-Z]+\}|[a-zA-Z]+)/g, (m, contents) => {
|
||||
const result = timeObj[contents.replace(/\{|\}/g, '')]
|
||||
return result !== undefined ? result : contents
|
||||
})
|
||||
|
||||
// Round 23:59:59 to 24:00. For 12-hour format there is nothing to replace: as both are 12am.
|
||||
// Also don't return `24:00` straight away as the user format may be different.
|
||||
return shouldRound ? formatted.replace('23:59', '24:00') : formatted
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a time to 'HH:mm' from a Date and returns the formatted string.
|
||||
*
|
||||
* @param {Date} date a JavaScript Date object to format.
|
||||
* @return {String} the formatted time.
|
||||
*/
|
||||
formatTimeLite (date) {
|
||||
const h = date.getHours()
|
||||
const m = date.getMinutes()
|
||||
return `${(h < 10 ? '0' : '') + h}:${(m < 10 ? '0' : '') + m}`
|
||||
}
|
||||
|
||||
_nth (d) {
|
||||
if (d > 3 && d < 21) return 'th'
|
||||
switch (d % 10) {
|
||||
case 1: return 'st'
|
||||
case 2: return 'nd'
|
||||
case 3: return 'rd'
|
||||
default: return 'th'
|
||||
}
|
||||
}
|
||||
|
||||
_hydrateDateObject (date, texts) {
|
||||
if (_dateObject.D) return _dateObject
|
||||
|
||||
const YYYY = date.getFullYear()
|
||||
const M = date.getMonth() + 1
|
||||
const D = date.getDate()
|
||||
const day = date.getDay() // Day of the week.
|
||||
const dayNumber = (day - 1 + 7) % 7 // Day of the week. 0 to 6 with 6 = Sunday.
|
||||
// Some of this props are functions, to only calculate on demand.
|
||||
_dateObject = {
|
||||
// Year.
|
||||
YYYY, // 2019.
|
||||
YY: () => YYYY.toString().substring(2), // 19.
|
||||
|
||||
// Month.
|
||||
M, // 1 to 12.
|
||||
MM: () => (M < 10 ? '0' : '') + M, // 01 to 12.
|
||||
MMM: () => texts.months[M - 1].substring(0, 3), // Jan to Dec.
|
||||
MMMM: () => texts.months[M - 1], // January to December.
|
||||
MMMMG: () => (texts.monthsGenitive || texts.months)[M - 1], // January to December in genitive form (Greek...)
|
||||
|
||||
// Day.
|
||||
D, // 1 to 31.
|
||||
DD: () => (D < 10 ? '0' : '') + D, // 01 to 31.
|
||||
S: () => this._nth(D), // st, nd, rd, th.
|
||||
|
||||
// Day of the week.
|
||||
d: dayNumber + 1, // 1 to 7 with 7 = Sunday.
|
||||
dd: () => texts.weekDays[dayNumber][0], // M to S.
|
||||
ddd: () => texts.weekDays[dayNumber].substr(0, 3), // Mon to Sun.
|
||||
dddd: () => texts.weekDays[dayNumber] // Monday to Sunday.
|
||||
}
|
||||
|
||||
return _dateObject
|
||||
}
|
||||
|
||||
_hydrateTimeObject (date, texts) {
|
||||
if (_timeObject.am) return _timeObject
|
||||
|
||||
let H, m
|
||||
if (date instanceof Date) {
|
||||
H = date.getHours()
|
||||
m = date.getMinutes()
|
||||
}
|
||||
else {
|
||||
H = Math.floor(date / 60)
|
||||
m = Math.floor(date % 60)
|
||||
}
|
||||
|
||||
const h = H % 12 ? H % 12 : 12
|
||||
const am = (texts || { am: 'am', pm: 'pm' })[H === 24 || H < 12 ? 'am' : 'pm']
|
||||
_timeObject = {
|
||||
H,
|
||||
h,
|
||||
HH: (H < 10 ? '0' : '') + H,
|
||||
hh: (h < 10 ? '0' : '') + h,
|
||||
am,
|
||||
AM: am.toUpperCase(),
|
||||
m,
|
||||
mm: (m < 10 ? '0' : '') + m
|
||||
}
|
||||
|
||||
return _timeObject
|
||||
}
|
||||
// ====================================================================
|
||||
}
|
||||
Reference in new issue
Block a user