0
0
mirror of https://github.com/kevinpapst/kimai2.git synced 2024-12-22 12:18:29 +00:00
kevinpapst_kimai2/assets/js/plugins/KimaiDateUtils.js
Kevin Papst 8449eafcb6
Release 2.0.8 (#3914)
* support parsing negative durations in JS
* bump luxon dependency
* make sure that 2FA is not required for session based API calls
* show name of items to delete
* fix permission issue for recent activity items
2023-03-13 01:48:53 +01:00

307 lines
8.2 KiB
JavaScript

/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/*!
* [KIMAI] KimaiDateUtils: responsible for handling date specific tasks
*/
import KimaiPlugin from '../KimaiPlugin';
import { DateTime, Duration } from 'luxon';
export default class KimaiDateUtils extends KimaiPlugin {
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');
}
/**
* @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}
*/
format(format, dateTime)
{
let newDate = null;
if (dateTime === null || dateTime === undefined) {
newDate = DateTime.now();
} else if (dateTime instanceof Date) {
newDate = DateTime.fromJSDate(dateTime);
} else {
newDate = DateTime.fromISO(dateTime);
}
// 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 {string|Date} dateTime
* @returns {string}
*/
getFormattedDate(dateTime)
{
return this.format(this._parseFormat(this.dateFormat), dateTime);
}
/**
* 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);
}
/**
* @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);
format = '-' + format;
}
return format.replace('%h', hours.toString()).replace('%m', ('0' + minutes).slice(-2));
}
/**
* @param {string} duration
* @returns {int}
*/
getSecondsFromDurationString(duration)
{
const luxonDuration = this.parseDuration(duration);
if (luxonDuration === null || !luxonDuration.isValid) {
return 0;
}
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});
}
// actually, the parsing above should be improved, but that works as well
if (duration[0] === '-' && luxonDuration.valueOf() > 0) {
return luxonDuration.negate();
}
return luxonDuration;
}
}