forked from extern/egroupware
- 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'), '-');
|
||||
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();
|
||||
}
|
||||
|
||||
|
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 = [
|
||||
{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));
|
||||
});
|
||||
});
|
||||
|
||||
|
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 './Et2Box/Et2Box';
|
||||
import './Et2Button/Et2Button';
|
||||
import './Et2Date/Et2Date';
|
||||
import './Et2Date/Et2DateTime';
|
||||
import './Et2Textarea/Et2Textarea';
|
||||
import './Et2Textbox/Et2Textbox';
|
||||
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>
|
||||
<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"/>
|
||||
|
Loading…
Reference in New Issue
Block a user