Next major version 2 with PHP 8.1, Symfony 6, Tabler UI, 2FA ... (#2902)
This commit is contained in:
@@ -10,71 +10,241 @@
|
||||
*/
|
||||
|
||||
import KimaiPlugin from '../KimaiPlugin';
|
||||
import moment from 'moment';
|
||||
import { DateTime, Duration } from 'luxon';
|
||||
|
||||
export default class KimaiDateUtils extends KimaiPlugin {
|
||||
|
||||
getId() {
|
||||
getId()
|
||||
{
|
||||
return 'date';
|
||||
}
|
||||
|
||||
init()
|
||||
{
|
||||
if (this.getConfigurations().is24Hours()) {
|
||||
this.timeFormat = 'HH:mm';
|
||||
} else {
|
||||
this.timeFormat = 'hh:mm a';
|
||||
}
|
||||
this.durationFormat = this.getConfiguration('formatDuration');
|
||||
this.dateFormat = this.getConfiguration('formatDate');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} dateTime
|
||||
* @see https://moment.github.io/luxon/#/formatting?id=table-of-tokens
|
||||
* @param {string} format
|
||||
* @returns {string}
|
||||
* @private
|
||||
*/
|
||||
_parseFormat(format)
|
||||
{
|
||||
format = format.replace('DD', 'dd');
|
||||
format = format.replace('D', 'd');
|
||||
format = format.replace('MM', 'LL');
|
||||
format = format.replace('M', 'L');
|
||||
format = format.replace('YYYY', 'yyyy');
|
||||
format = format.replace('YY', 'yy');
|
||||
format = format.replace('A', 'a');
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} format
|
||||
* @param {string|Date|null|undefined} dateTime
|
||||
* @returns {string}
|
||||
*/
|
||||
getFormattedDate(dateTime) {
|
||||
return moment(dateTime).format(this.getConfiguration('formatDate'));
|
||||
}
|
||||
format(format, dateTime)
|
||||
{
|
||||
let newDate = null;
|
||||
|
||||
getWeekDaysShort() {
|
||||
return moment.localeData().weekdaysShort();
|
||||
}
|
||||
if (dateTime === null || dateTime === undefined) {
|
||||
newDate = DateTime.now();
|
||||
} else if (dateTime instanceof Date) {
|
||||
newDate = DateTime.fromJSDate(dateTime);
|
||||
} else {
|
||||
newDate = DateTime.fromISO(dateTime);
|
||||
}
|
||||
|
||||
getMonthNames() {
|
||||
return moment.localeData().months();
|
||||
}
|
||||
|
||||
formatDuration(since) {
|
||||
const duration = moment.duration(moment(new Date()).diff(moment(since)));
|
||||
|
||||
return this.formatMomentDuration(duration);
|
||||
}
|
||||
|
||||
formatSeconds(seconds) {
|
||||
const duration = moment.duration('PT' + seconds + 'S');
|
||||
|
||||
return this.formatMomentDuration(duration);
|
||||
// using locale english here prevents that that AM/PM is translated to the
|
||||
// locale variant: e.g. "ko" translates it to 오후 / 오전
|
||||
return newDate.toFormat(this._parseFormat(format), { locale: 'en-us' });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {moment.Duration} duration
|
||||
* @returns {string|*}
|
||||
* @param {string|Date} dateTime
|
||||
* @returns {string}
|
||||
*/
|
||||
formatMomentDuration(duration) {
|
||||
const hours = parseInt(duration.asHours());
|
||||
const minutes = duration.minutes();
|
||||
|
||||
return this.formatTime(hours, minutes);
|
||||
getFormattedDate(dateTime)
|
||||
{
|
||||
return this.format(this._parseFormat(this.dateFormat), dateTime);
|
||||
}
|
||||
|
||||
formatTime(hours, minutes) {
|
||||
let format = this.getConfiguration('formatDuration');
|
||||
/**
|
||||
* Returns a "YYYY-MM-DDTHH:mm:ss" formatted string in local time.
|
||||
* This can take Date objects (e.g. from FullCalendar) and turn them into the correct format.
|
||||
*
|
||||
* @param {Date|DateTime} date
|
||||
* @param {boolean|undefined} isUtc
|
||||
* @return {string}
|
||||
*/
|
||||
formatForAPI(date, isUtc = false)
|
||||
{
|
||||
if (date instanceof Date) {
|
||||
date = DateTime.fromJSDate(date);
|
||||
}
|
||||
|
||||
if (isUtc === undefined || !isUtc) {
|
||||
date = date.toUTC();
|
||||
}
|
||||
|
||||
return date.toISO({ includeOffset: false, suppressMilliseconds: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} date
|
||||
* @param {string} format
|
||||
* @return {DateTime}
|
||||
*/
|
||||
fromFormat(date, format)
|
||||
{
|
||||
// using locale en-us here prevents that Luxon expects the localized
|
||||
// version of AM/PM (e.g. 오후 / 오전 for locale "ko")
|
||||
return DateTime.fromFormat(date, this._parseFormat(format), { locale: 'en-us' });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string|null} date
|
||||
* @param {string|null} time
|
||||
* @return {DateTime}
|
||||
*/
|
||||
fromHtml5Input(date, time)
|
||||
{
|
||||
date = date ?? '';
|
||||
time = time ?? '';
|
||||
|
||||
if (date === '' && time === '') {
|
||||
return DateTime.invalid('Empty date and time given');
|
||||
}
|
||||
|
||||
if (date !== '' && time !== '') {
|
||||
date = date + 'T' + time;
|
||||
}
|
||||
|
||||
return DateTime.fromISO(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} date
|
||||
* @param {string} format
|
||||
* @return {boolean}
|
||||
*/
|
||||
isValidDateTime(date, format)
|
||||
{
|
||||
return this.fromFormat(date, format).isValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a string like "00:30:00" or "01:15" to a given date.
|
||||
*
|
||||
* @param {Date} date
|
||||
* @param {string} duration
|
||||
* @return {Date}
|
||||
*/
|
||||
addHumanDuration(date, duration)
|
||||
{
|
||||
/** @type {DateTime} newDate */
|
||||
let newDate = null;
|
||||
|
||||
if (date instanceof Date) {
|
||||
newDate = DateTime.fromJSDate(date);
|
||||
} else if (date instanceof DateTime) {
|
||||
newDate = date;
|
||||
} else {
|
||||
throw 'addHumanDuration() needs a JS Date';
|
||||
}
|
||||
|
||||
const parsed = DateTime.fromISO(duration);
|
||||
const today = DateTime.now().startOf('day');
|
||||
const timeOfDay = parsed.diff(today);
|
||||
|
||||
return newDate.plus(timeOfDay).toJSDate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string|integer|null} since
|
||||
* @return {string}
|
||||
*/
|
||||
formatDuration(since)
|
||||
{
|
||||
let duration = null;
|
||||
|
||||
if (typeof since === 'string') {
|
||||
duration = DateTime.now().diff(DateTime.fromISO(since));
|
||||
} else {
|
||||
duration = Duration.fromISO('PT' + (since === null ? 0 : since) + 'S');
|
||||
}
|
||||
|
||||
return this.formatLuxonDuration(duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {integer} seconds
|
||||
* @return {string}
|
||||
*/
|
||||
formatSeconds(seconds)
|
||||
{
|
||||
return this.formatLuxonDuration(Duration.fromObject({seconds: seconds}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Duration} duration
|
||||
* @returns {string}
|
||||
* @private
|
||||
*/
|
||||
formatLuxonDuration(duration)
|
||||
{
|
||||
duration = duration.shiftTo('hours', 'minutes', 'seconds');
|
||||
|
||||
return this.formatAsDuration(duration.hours, duration.minutes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Date} date
|
||||
* @param {boolean|undefined} isUtc
|
||||
* @return {string}
|
||||
*/
|
||||
formatTime(date, isUtc = false)
|
||||
{
|
||||
let newDate = DateTime.fromJSDate(date);
|
||||
|
||||
if (isUtc === undefined || !isUtc) {
|
||||
newDate = newDate.toUTC();
|
||||
}
|
||||
|
||||
// .utc() is required for calendar
|
||||
return newDate.toFormat(this.timeFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO remove seconds
|
||||
*
|
||||
* @param {int} hours
|
||||
* @param {int} minutes
|
||||
* @return {string}
|
||||
*/
|
||||
formatAsDuration(hours, minutes)
|
||||
{
|
||||
let format = this.durationFormat;
|
||||
|
||||
if (hours < 0 || minutes < 0) {
|
||||
hours = Math.abs(hours);
|
||||
minutes = Math.abs(minutes);
|
||||
if (minutes > 0 || hours > 0) {
|
||||
format = '-' + format;
|
||||
}
|
||||
format = '-' + format;
|
||||
}
|
||||
|
||||
// special case for hours, as they can overflow the 24h barrier - Kimai does not support days as duration unit
|
||||
if (hours < 10) {
|
||||
hours = '0' + hours;
|
||||
}
|
||||
|
||||
|
||||
return format.replace('%h', hours).replace('%m', ('0' + minutes).substr(-2));
|
||||
return format.replace('%h', (hours < 10 ? '0' + hours : hours)).replace('%m', ('0' + minutes).slice(-2));
|
||||
//return format.replace('%h', (hours < 10 ? '0' + hours : hours)).replace('%m', ('0' + minutes).slice(-2)).replace('%s', ('0' + seconds).slice(-2));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,32 +253,52 @@ export default class KimaiDateUtils extends KimaiPlugin {
|
||||
*/
|
||||
getSecondsFromDurationString(duration)
|
||||
{
|
||||
duration = duration.trim().toUpperCase();
|
||||
let momentDuration = moment.duration(NaN);
|
||||
const luxonDuration = this.parseDuration(duration);
|
||||
|
||||
if (duration.indexOf(':') !== -1) {
|
||||
momentDuration = moment.duration(duration);
|
||||
} else if (duration.indexOf('.') !== -1 || duration.indexOf(',') !== -1) {
|
||||
duration = duration.replace(/,/, '.');
|
||||
duration = (parseFloat(duration) * 3600).toString();
|
||||
momentDuration = moment.duration('PT' + duration + 'S');
|
||||
} else if (duration.indexOf('H') !== -1 || duration.indexOf('M') !== -1 || duration.indexOf('S') !== -1) {
|
||||
/* D for days does not work, because 'PT1H' but with days 'P1D' is used */
|
||||
momentDuration = moment.duration('PT' + duration);
|
||||
} else {
|
||||
let c = parseInt(duration);
|
||||
let d = parseInt(duration).toFixed();
|
||||
if (!isNaN(c) && duration === d) {
|
||||
duration = (c * 3600).toString();
|
||||
momentDuration = moment.duration('PT' + duration + 'S');
|
||||
}
|
||||
}
|
||||
|
||||
if (!momentDuration.isValid()) {
|
||||
if (luxonDuration === null || !luxonDuration.isValid) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return momentDuration.asSeconds();
|
||||
return luxonDuration.as('seconds');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} duration
|
||||
* @returns {Duration}
|
||||
*/
|
||||
parseDuration(duration)
|
||||
{
|
||||
if (duration === undefined || duration === null || duration === '') {
|
||||
return new Duration({seconds: 0});
|
||||
}
|
||||
|
||||
duration = duration.trim().toUpperCase();
|
||||
let luxonDuration = null;
|
||||
|
||||
if (duration.indexOf(':') !== -1) {
|
||||
const [, hours, minutes, seconds] = duration.match(/(\d+):(\d+)(?::(\d+))*/);
|
||||
luxonDuration = Duration.fromObject({hours: hours, minutes: minutes, seconds: seconds});
|
||||
} else if (duration.indexOf('.') !== -1 || duration.indexOf(',') !== -1) {
|
||||
duration = duration.replace(/,/, '.');
|
||||
duration = (parseFloat(duration) * 3600).toString();
|
||||
luxonDuration = Duration.fromISO('PT' + duration + 'S');
|
||||
} else if (duration.indexOf('H') !== -1 || duration.indexOf('M') !== -1 || duration.indexOf('S') !== -1) {
|
||||
/* D for days does not work, because 'PT1H' but with days 'P1D' is used */
|
||||
luxonDuration = Duration.fromISO('PT' + duration);
|
||||
} else {
|
||||
let c = parseInt(duration);
|
||||
const d = parseInt(duration).toFixed();
|
||||
if (!isNaN(c) && duration === d) {
|
||||
duration = (c * 3600).toString();
|
||||
luxonDuration = Duration.fromISO('PT' + duration + 'S');
|
||||
}
|
||||
}
|
||||
|
||||
if (luxonDuration === null || !luxonDuration.isValid) {
|
||||
return new Duration({seconds: 0});
|
||||
}
|
||||
|
||||
return luxonDuration;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user