- 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:
nathan 2021-11-03 09:05:16 -06:00
parent 312bf62adc
commit 672ed0aa0e
7 changed files with 294 additions and 27 deletions

View File

@ -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'), '-');
let parsedString = "";
switch(formatString)
{
case 'd-m-Y':
parsedString = `${dateString.slice(6, 10)}/${dateString.slice(
3,
5,
)}/${dateString.slice(0, 2)}`;
parsedString = `${dateString.slice(6, 10)}/${dateString.slice(3, 5,)}/${dateString.slice(0, 2)}`;
break;
case 'm-d-Y':
parsedString = `${dateString.slice(6, 10)}/${dateString.slice(
0,
2,
)}/${dateString.slice(3, 5)}`;
parsedString = `${dateString.slice(6, 10)}/${dateString.slice(0, 2,)}/${dateString.slice(3, 5)}`;
break;
case 'Y-m-d':
parsedString = `${dateString.slice(0, 4)}/${dateString.slice(
5,
7,
)}/${dateString.slice(8, 10)}`;
parsedString = `${dateString.slice(0, 4)}/${dateString.slice(5, 7,)}/${dateString.slice(8, 10)}`;
break;
case 'Y-d-m':
parsedString = `${dateString.slice(0, 4)}/${dateString.slice(8, 10)}/${dateString.slice(5, 7)}`;
break;
case 'd-M-Y':
parsedString = `${dateString.slice(6, 10)}/${dateString.slice(
3,
5,
)}/${dateString.slice(0, 2)}`;
parsedString = `${dateString.slice(6, 10)}/${dateString.slice(3, 5,)}/${dateString.slice(0, 2)}`;
break;
default:
parsedString = '0000/00/00';
}
@ -89,6 +81,97 @@ export function parseDate(dateString)
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
*
@ -97,9 +180,9 @@ export function parseDate(dateString)
* set 'dateFormat': "Y-m-d" to specify a particular format
* @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 "";
}
@ -107,7 +190,7 @@ export function formatDate(date: Date, options): string
// Add timezone offset back in, or formatDate will lose those hours
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 = {
d: (date.getUTCDate() < 10 ? "0" : "") + date.getUTCDate(),
@ -115,13 +198,61 @@ export function formatDate(date: Date, options): string
Y: "" + date.getUTCFullYear()
}
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 _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)
{
static get styles()
@ -196,6 +327,10 @@ export class Et2Date extends Et2InputWidget(LionInputDatepicker)
{
return null;
}
this.modelValue.setUTCHours(0);
this.modelValue.setUTCMinutes(0);
this.modelValue.setSeconds(0, 0);
return this.modelValue.toJSON();
}

View 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);

View File

@ -63,7 +63,7 @@ describe("Date widget", () =>
});
const tz_list = [
{name: "America/Edmonton", offset: 600},
{name: "America/Edmonton", offset: -600},
{name: "UTC", offset: 0},
{name: "Australia/Adelaide", offset: 630}
];
@ -85,8 +85,8 @@ describe("Date widget", () =>
// Use a Promise to wait for asychronous changes to the DOM
return Promise.resolve().then(() =>
{
// Widget gives time as a string so we can send to server
assert.equal(element.getValue(), test_time_string);
// Widget gives time as a string so we can send to server, but zeros the time
assert.equal(element.getValue().substr(0, 11), test_time_string.substr(0, 11));
});
});

View 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());
});
});

View File

@ -25,7 +25,7 @@ import '../jsapi/egw_json.js';
import {egwIsMobile} from "../egw_action/egw_action_common.js";
import './Et2Box/Et2Box';
import './Et2Button/Et2Button';
import './Et2Date/Et2Date';
import './Et2Date/Et2DateTime';
import './Et2Textarea/Et2Textarea';
import './Et2Textbox/Et2Textbox';
import './Et2Colorpicker/Et2Colorpicker';

View File

@ -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')
);

View File

@ -165,7 +165,9 @@
</menulist>
<description/>
<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 class="dialogHeader3">
<description value="Contact"/>