mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-22 07:53:39 +01:00
- DateTime widget to handle dates with times
- Time & Date+time parser & formatter functions - Date widget does not return a time
This commit is contained in:
parent
312bf62adc
commit
672ed0aa0e
@ -40,34 +40,26 @@ export function parseDate(dateString)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let formatString = <string>(egw.preference("dateformat") || 'Y-m-d');
|
let formatString = <string>(window.egw.preference("dateformat") || 'Y-m-d');
|
||||||
formatString = formatString.replaceAll(new RegExp('[-/\.]', 'ig'), '-');
|
formatString = formatString.replaceAll(new RegExp('[-/\.]', 'ig'), '-');
|
||||||
let parsedString = "";
|
let parsedString = "";
|
||||||
switch(formatString)
|
switch(formatString)
|
||||||
{
|
{
|
||||||
case 'd-m-Y':
|
case 'd-m-Y':
|
||||||
parsedString = `${dateString.slice(6, 10)}/${dateString.slice(
|
parsedString = `${dateString.slice(6, 10)}/${dateString.slice(3, 5,)}/${dateString.slice(0, 2)}`;
|
||||||
3,
|
|
||||||
5,
|
|
||||||
)}/${dateString.slice(0, 2)}`;
|
|
||||||
break;
|
break;
|
||||||
case 'm-d-Y':
|
case 'm-d-Y':
|
||||||
parsedString = `${dateString.slice(6, 10)}/${dateString.slice(
|
parsedString = `${dateString.slice(6, 10)}/${dateString.slice(0, 2,)}/${dateString.slice(3, 5)}`;
|
||||||
0,
|
|
||||||
2,
|
|
||||||
)}/${dateString.slice(3, 5)}`;
|
|
||||||
break;
|
break;
|
||||||
case 'Y-m-d':
|
case 'Y-m-d':
|
||||||
parsedString = `${dateString.slice(0, 4)}/${dateString.slice(
|
parsedString = `${dateString.slice(0, 4)}/${dateString.slice(5, 7,)}/${dateString.slice(8, 10)}`;
|
||||||
5,
|
break;
|
||||||
7,
|
case 'Y-d-m':
|
||||||
)}/${dateString.slice(8, 10)}`;
|
parsedString = `${dateString.slice(0, 4)}/${dateString.slice(8, 10)}/${dateString.slice(5, 7)}`;
|
||||||
break;
|
break;
|
||||||
case 'd-M-Y':
|
case 'd-M-Y':
|
||||||
parsedString = `${dateString.slice(6, 10)}/${dateString.slice(
|
parsedString = `${dateString.slice(6, 10)}/${dateString.slice(3, 5,)}/${dateString.slice(0, 2)}`;
|
||||||
3,
|
break;
|
||||||
5,
|
|
||||||
)}/${dateString.slice(0, 2)}`;
|
|
||||||
default:
|
default:
|
||||||
parsedString = '0000/00/00';
|
parsedString = '0000/00/00';
|
||||||
}
|
}
|
||||||
@ -89,6 +81,97 @@ export function parseDate(dateString)
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To parse a time into the right format
|
||||||
|
* Date will be 1970-01-01
|
||||||
|
*
|
||||||
|
* @param {string} timeString
|
||||||
|
* @returns {Date | undefined}
|
||||||
|
*/
|
||||||
|
export function parseTime(timeString)
|
||||||
|
{
|
||||||
|
// First try the server format
|
||||||
|
if(timeString.substr(-1) === "Z")
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
let date = new Date(timeString);
|
||||||
|
if(date instanceof Date)
|
||||||
|
{
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(e)
|
||||||
|
{
|
||||||
|
// Nope, that didn't parse directly
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let am_pm = timeString.endsWith("pm") || timeString.endsWith("PM") ? 12 : 0;
|
||||||
|
timeString = timeString.replaceAll(/[^0-9:]/gi, '');
|
||||||
|
const [hour, minute] = timeString.split(':').map(Number);
|
||||||
|
|
||||||
|
const parsedDate = new Date("1970-01-01T00:00:00Z");
|
||||||
|
parsedDate.setUTCHours(hour + am_pm);
|
||||||
|
parsedDate.setUTCMinutes(minute);
|
||||||
|
|
||||||
|
// Check if parsedDate is not `Invalid Date` or that the time has changed
|
||||||
|
if(
|
||||||
|
parsedDate.getUTCHours() === hour + am_pm &&
|
||||||
|
parsedDate.getUTCMinutes() === minute
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return parsedDate;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To parse a date+time into an object
|
||||||
|
*
|
||||||
|
* @param {string} dateTimeString
|
||||||
|
* @returns {Date | undefined}
|
||||||
|
*/
|
||||||
|
export function parseDateTime(dateTimeString)
|
||||||
|
{
|
||||||
|
// First try the server format
|
||||||
|
if(typeof dateTimeString === "string" && dateTimeString.substr(-1) === "Z" || !isNaN(dateTimeString))
|
||||||
|
{
|
||||||
|
if(!isNaN(dateTimeString) && parseInt(dateTimeString) == dateTimeString)
|
||||||
|
{
|
||||||
|
this.egw().debug("warn", "Invalid date/time string: " + dateTimeString);
|
||||||
|
dateTimeString *= 1000;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
let date = new Date(dateTimeString);
|
||||||
|
if(date instanceof Date)
|
||||||
|
{
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(e)
|
||||||
|
{
|
||||||
|
// Nope, that didn't parse directly
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = parseDate(dateTimeString);
|
||||||
|
|
||||||
|
let explody = dateTimeString.split(" ");
|
||||||
|
explody.shift();
|
||||||
|
const time = parseTime(explody.join(" "));
|
||||||
|
|
||||||
|
if(typeof date === "undefined" || typeof time === "undefined")
|
||||||
|
{
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
date.setUTCHours(time.getUTCHours());
|
||||||
|
date.setUTCMinutes(time.getUTCMinutes());
|
||||||
|
date.setUTCSeconds(time.getUTCSeconds());
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format dates according to user preference
|
* Format dates according to user preference
|
||||||
*
|
*
|
||||||
@ -97,9 +180,9 @@ export function parseDate(dateString)
|
|||||||
* set 'dateFormat': "Y-m-d" to specify a particular format
|
* set 'dateFormat': "Y-m-d" to specify a particular format
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function formatDate(date: Date, options): string
|
export function formatDate(date : Date, options) : string
|
||||||
{
|
{
|
||||||
if (!date || !(date instanceof Date))
|
if(!date || !(date instanceof Date))
|
||||||
{
|
{
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@ -107,7 +190,7 @@ export function formatDate(date: Date, options): string
|
|||||||
// Add timezone offset back in, or formatDate will lose those hours
|
// Add timezone offset back in, or formatDate will lose those hours
|
||||||
let formatDate = new Date(date.valueOf() - date.getTimezoneOffset() * 60 * 1000);
|
let formatDate = new Date(date.valueOf() - date.getTimezoneOffset() * 60 * 1000);
|
||||||
|
|
||||||
let dateformat = options.dateFormat || <string>egw.preference("dateformat") || 'Y-m-d';
|
let dateformat = options.dateFormat || <string>window.egw.preference("dateformat") || 'Y-m-d';
|
||||||
|
|
||||||
var replace_map = {
|
var replace_map = {
|
||||||
d: (date.getUTCDate() < 10 ? "0" : "") + date.getUTCDate(),
|
d: (date.getUTCDate() < 10 ? "0" : "") + date.getUTCDate(),
|
||||||
@ -115,13 +198,61 @@ export function formatDate(date: Date, options): string
|
|||||||
Y: "" + date.getUTCFullYear()
|
Y: "" + date.getUTCFullYear()
|
||||||
}
|
}
|
||||||
var re = new RegExp(Object.keys(replace_map).join("|"), "gi");
|
var re = new RegExp(Object.keys(replace_map).join("|"), "gi");
|
||||||
_value = dateformat.replace(re, function (matched)
|
_value = dateformat.replace(re, function(matched)
|
||||||
{
|
{
|
||||||
return replace_map[matched];
|
return replace_map[matched];
|
||||||
});
|
});
|
||||||
return _value;
|
return _value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format dates according to user preference
|
||||||
|
*
|
||||||
|
* @param {Date} date
|
||||||
|
* @param {import('@lion/localize/types/LocalizeMixinTypes').FormatDateOptions} [options] Intl options are available
|
||||||
|
* set 'timeFormat': "12" to specify a particular format
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function formatTime(date : Date, options) : string
|
||||||
|
{
|
||||||
|
if(!date || !(date instanceof Date))
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
let _value = '';
|
||||||
|
|
||||||
|
let timeformat = options.timeFormat || <string>window.egw.preference("timeformat") || '24';
|
||||||
|
let hours = (timeformat == "12" && date.getUTCHours() > 12) ? (date.getUTCHours() - 12) : date.getUTCHours();
|
||||||
|
if(timeformat == "12" && hours == 0)
|
||||||
|
{
|
||||||
|
// 00:00 is 12:00 am
|
||||||
|
hours = 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
_value = (timeformat == "24" && hours < 10 ? "0" : "") + hours + ":" +
|
||||||
|
(date.getUTCMinutes() < 10 ? "0" : "") + (date.getUTCMinutes()) +
|
||||||
|
(timeformat == "24" ? "" : (date.getUTCHours() < 12 ? " am" : " pm"));
|
||||||
|
|
||||||
|
return _value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format date+time according to user preference
|
||||||
|
*
|
||||||
|
* @param {Date} date
|
||||||
|
* @param {import('@lion/localize/types/LocalizeMixinTypes').FormatDateOptions} [options] Intl options are available
|
||||||
|
* set 'dateFormat': "Y-m-d", 'timeFormat': "12" to specify a particular format
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function formatDateTime(date : Date, options) : string
|
||||||
|
{
|
||||||
|
if(!date || !(date instanceof Date))
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return formatDate(date, options) + " " + formatTime(date, options);
|
||||||
|
}
|
||||||
|
|
||||||
export class Et2Date extends Et2InputWidget(LionInputDatepicker)
|
export class Et2Date extends Et2InputWidget(LionInputDatepicker)
|
||||||
{
|
{
|
||||||
static get styles()
|
static get styles()
|
||||||
@ -196,6 +327,10 @@ export class Et2Date extends Et2InputWidget(LionInputDatepicker)
|
|||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
this.modelValue.setUTCHours(0);
|
||||||
|
this.modelValue.setUTCMinutes(0);
|
||||||
|
this.modelValue.setSeconds(0, 0);
|
||||||
|
|
||||||
return this.modelValue.toJSON();
|
return this.modelValue.toJSON();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
69
api/js/etemplate/Et2Date/Et2DateTime.ts
Normal file
69
api/js/etemplate/Et2Date/Et2DateTime.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* EGroupware eTemplate2 - Date+Time widget (WebComponent)
|
||||||
|
*
|
||||||
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||||
|
* @package etemplate
|
||||||
|
* @subpackage api
|
||||||
|
* @link https://www.egroupware.org
|
||||||
|
* @author Nathan Gray
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
import {css, html} from "@lion/core";
|
||||||
|
import {Et2Date, formatDateTime, parseDateTime} from "./Et2Date";
|
||||||
|
import {Unparseable} from "@lion/form-core";
|
||||||
|
|
||||||
|
|
||||||
|
export class Et2DateTime extends Et2Date
|
||||||
|
{
|
||||||
|
static get styles()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
...super.styles,
|
||||||
|
css`
|
||||||
|
:host([focused]) ::slotted(button), :host(:hover) ::slotted(button) {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
::slotted(.calendar_button) {
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
margin-left: -20px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
static get properties()
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
...super.properties
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor()
|
||||||
|
{
|
||||||
|
super();
|
||||||
|
this.parser = parseDateTime;
|
||||||
|
this.formatter = formatDateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue()
|
||||||
|
{
|
||||||
|
if(this.readOnly)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The supplied value was not understandable, return null
|
||||||
|
if(this.modelValue instanceof Unparseable || !this.modelValue)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.modelValue.toJSON();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore TypeScript is not recognizing that Et2DateTime is a LitElement
|
||||||
|
customElements.define("et2-datetime", Et2DateTime);
|
@ -63,7 +63,7 @@ describe("Date widget", () =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
const tz_list = [
|
const tz_list = [
|
||||||
{name: "America/Edmonton", offset: 600},
|
{name: "America/Edmonton", offset: -600},
|
||||||
{name: "UTC", offset: 0},
|
{name: "UTC", offset: 0},
|
||||||
{name: "Australia/Adelaide", offset: 630}
|
{name: "Australia/Adelaide", offset: 630}
|
||||||
];
|
];
|
||||||
@ -85,8 +85,8 @@ describe("Date widget", () =>
|
|||||||
// Use a Promise to wait for asychronous changes to the DOM
|
// Use a Promise to wait for asychronous changes to the DOM
|
||||||
return Promise.resolve().then(() =>
|
return Promise.resolve().then(() =>
|
||||||
{
|
{
|
||||||
// Widget gives time as a string so we can send to server
|
// Widget gives time as a string so we can send to server, but zeros the time
|
||||||
assert.equal(element.getValue(), test_time_string);
|
assert.equal(element.getValue().substr(0, 11), test_time_string.substr(0, 11));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
59
api/js/etemplate/Et2Date/test/Parser.test.ts
Normal file
59
api/js/etemplate/Et2Date/test/Parser.test.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* Test file for Etemplate date parsing
|
||||||
|
*/
|
||||||
|
import {assert, fixture} from '@open-wc/testing';
|
||||||
|
import {Et2Date, parseDate} from "../Et2Date";
|
||||||
|
import {html} from "lit-element";
|
||||||
|
import * as sinon from 'sinon';
|
||||||
|
|
||||||
|
describe("Date parsing", () =>
|
||||||
|
{
|
||||||
|
// Function under test
|
||||||
|
let parser = parseDate;
|
||||||
|
|
||||||
|
// Setup run before each test
|
||||||
|
beforeEach(async() =>
|
||||||
|
{
|
||||||
|
// Stub global egw for preference
|
||||||
|
// @ts-ignore
|
||||||
|
window.egw = {
|
||||||
|
preference: () => 'Y-m-d'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Handles server format", () =>
|
||||||
|
{
|
||||||
|
let test_string = '2021-09-22T19:22:00Z';
|
||||||
|
let test_date = new Date(test_string);
|
||||||
|
|
||||||
|
let parsed = parser(test_string);
|
||||||
|
|
||||||
|
// Can't compare results - different objects
|
||||||
|
//assert.equal(parsed, test_date);
|
||||||
|
assert.equal(parsed.toJSON(), test_date.toJSON());
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Handles Y-m-d", () =>
|
||||||
|
{
|
||||||
|
let test_string = '2021-09-22';
|
||||||
|
let test_date = new Date(2021, 8, 22, 0, 0, 0);
|
||||||
|
|
||||||
|
let parsed = parser(test_string);
|
||||||
|
|
||||||
|
assert.equal(parsed.toJSON(), test_date.toJSON());
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Handles Y.d.m", () =>
|
||||||
|
{
|
||||||
|
let test_string = '2021.22.09';
|
||||||
|
let test_date = new Date(2021, 8, 22, 0, 0, 0);
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
window.egw = {
|
||||||
|
preference: () => 'Y.d.m'
|
||||||
|
};
|
||||||
|
let parsed = parser(test_string);
|
||||||
|
|
||||||
|
assert.equal(parsed.toJSON(), test_date.toJSON());
|
||||||
|
});
|
||||||
|
});
|
@ -25,7 +25,7 @@ import '../jsapi/egw_json.js';
|
|||||||
import {egwIsMobile} from "../egw_action/egw_action_common.js";
|
import {egwIsMobile} from "../egw_action/egw_action_common.js";
|
||||||
import './Et2Box/Et2Box';
|
import './Et2Box/Et2Box';
|
||||||
import './Et2Button/Et2Button';
|
import './Et2Button/Et2Button';
|
||||||
import './Et2Date/Et2Date';
|
import './Et2Date/Et2DateTime';
|
||||||
import './Et2Textarea/Et2Textarea';
|
import './Et2Textarea/Et2Textarea';
|
||||||
import './Et2Textbox/Et2Textbox';
|
import './Et2Textbox/Et2Textbox';
|
||||||
import './Et2Colorpicker/Et2Colorpicker';
|
import './Et2Colorpicker/Et2Colorpicker';
|
||||||
|
@ -282,4 +282,6 @@ class Date extends Transformer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
\EGroupware\Api\Etemplate\Widget::registerWidget(__NAMESPACE__ . '\\Date', array('et2-date', 'time_or_date'));
|
\EGroupware\Api\Etemplate\Widget::registerWidget(__NAMESPACE__ . '\\Date',
|
||||||
|
array('et2-date', 'et2-datetime', 'time_or_date')
|
||||||
|
);
|
@ -165,7 +165,9 @@
|
|||||||
</menulist>
|
</menulist>
|
||||||
<description/>
|
<description/>
|
||||||
<description value="Startdate" for="info_startdate"/>
|
<description value="Startdate" for="info_startdate"/>
|
||||||
<et2-date statustext="when should the ToDo or Phonecall be started, it shows up from that date in the filter open or own open (startpage)" id="info_startdate" class="et2_fullWidth"></et2-date>
|
<et2-datetime
|
||||||
|
statustext="when should the ToDo or Phonecall be started, it shows up from that date in the filter open or own open (startpage)"
|
||||||
|
id="info_startdate" class="et2_fullWidth"></et2-datetime>
|
||||||
</row>
|
</row>
|
||||||
<row class="dialogHeader3">
|
<row class="dialogHeader3">
|
||||||
<description value="Contact"/>
|
<description value="Contact"/>
|
||||||
|
Loading…
Reference in New Issue
Block a user