Skip to content

Commit

Permalink
Add settings.createDate function (#9093)
Browse files Browse the repository at this point in the history
* Add settings.createDate function

* Remove function comments

* Move createDate to Helpers

* Add unit test

* Add T00:00:00 for iso date

* Improve unit test

* Add one unit case
  • Loading branch information
andrewtelnov authored Nov 25, 2024
1 parent d6317e0 commit ef3f8a5
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 30 deletions.
4 changes: 2 additions & 2 deletions packages/survey-core/src/expressions/expressions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HashTable, Helpers } from "../helpers";
import { HashTable, Helpers, createDate } from "../helpers";
import { FunctionFactory } from "../functionsfactory";
import { ProcessValue } from "../conditionProcessValue";
import { settings } from "../settings";
Expand Down Expand Up @@ -631,7 +631,7 @@ static unaryFunctions: HashTable<Function> = {
}
static convertValForDateCompare(val: any, second: any): any {
if(second instanceof Date && typeof val === "string") {
let res = new Date(val);
let res = createDate("expression-operand", val);
res.setHours(0, 0, 0);
return res;
}
Expand Down
32 changes: 16 additions & 16 deletions packages/survey-core/src/functionsfactory.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HashTable, Helpers } from "./helpers";
import { HashTable, Helpers, createDate } from "./helpers";
import { settings } from "./settings";
import { ConsoleWarnings } from "./console-warnings";
import { ConditionRunner } from "./conditions";
Expand Down Expand Up @@ -238,14 +238,14 @@ FunctionFactory.Instance.register("iif", iif);
function getDate(params: any[]): any {
if (!params && params.length < 1) return null;
if (!params[0]) return null;
return new Date(params[0]);
return createDate("function-getDate", params[0]);
}
FunctionFactory.Instance.register("getDate", getDate);

function dateDiffMonths(date1Param: any, date2Param: any, type: string): number {
if(type === "days") return diffDays([date1Param, date2Param]);
const date1 = !!date1Param ? new Date(date1Param) : new Date();
const date2 = !!date2Param ? new Date(date2Param) : new Date();
const date1 = createDate("function-dateDiffMonths", date1Param);
const date2 = createDate("function-dateDiffMonths", date2Param);
const age = date2.getFullYear() - date1.getFullYear();
type = type || "years";
let ageInMonths = age * 12 + date2.getMonth() - date1.getMonth();
Expand Down Expand Up @@ -304,12 +304,12 @@ function isDisplayMode() {
FunctionFactory.Instance.register("isDisplayMode", isDisplayMode);

function currentDate() {
return new Date();
return createDate("function-currentDate");
}
FunctionFactory.Instance.register("currentDate", currentDate);

function today(params: any[]) {
var res = new Date();
var res = createDate("function-today");
if(settings.localization.useLocalTimeZone) {
res.setHours(0, 0, 0, 0);
} else {
Expand All @@ -324,53 +324,53 @@ FunctionFactory.Instance.register("today", today);

function getYear(params: any[]) {
if(params.length !== 1 || !params[0]) return undefined;
return new Date(params[0]).getFullYear();
return createDate("function-getYear", params[0]).getFullYear();
}
FunctionFactory.Instance.register("getYear", getYear);

function currentYear() {
return new Date().getFullYear();
return createDate("function-currentYear").getFullYear();
}
FunctionFactory.Instance.register("currentYear", currentYear);

function diffDays(params: any[]) {
if (!Array.isArray(params) || params.length !== 2) return 0;
if (!params[0] || !params[1]) return 0;
const date1: any = new Date(params[0]);
const date2: any = new Date(params[1]);
const date1: any = createDate("function-diffDays", params[0]);
const date2: any = createDate("function-diffDays", params[1]);
const diffTime = Math.abs(date2 - date1);
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
}
FunctionFactory.Instance.register("diffDays", diffDays);

function dateFromFirstParameterOrToday(params: any[]) {
function dateFromFirstParameterOrToday(name: string, params: any[]) {
let date = today(undefined);
if (params && params[0]) {
date = new Date(params[0]);
date = createDate("function-" + name, params[0]);
}
return date;
}

function year(params: any[]): any {
let date = dateFromFirstParameterOrToday(params);
let date = dateFromFirstParameterOrToday("year", params);
return date.getFullYear();
}
FunctionFactory.Instance.register("year", year);

function month(params: any[]): any {
let date = dateFromFirstParameterOrToday(params);
let date = dateFromFirstParameterOrToday("month", params);
return date.getMonth() + 1;
}
FunctionFactory.Instance.register("month", month);

function day(params: any[]): any {
let date = dateFromFirstParameterOrToday(params);
let date = dateFromFirstParameterOrToday("day", params);
return date.getDate();
}
FunctionFactory.Instance.register("day", day);

function weekday(params: any[]): any {
let date = dateFromFirstParameterOrToday(params);
let date = dateFromFirstParameterOrToday("weekday", params);
return date.getDay();
}
FunctionFactory.Instance.register("weekday", weekday);
Expand Down
17 changes: 16 additions & 1 deletion packages/survey-core/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,27 @@ export interface HashTable<T = any> {
[key: string]: T;
}

export function createDate(reason: string, val?: number | string | Date): Date {
if(!val) return new Date();
if(!settings.storeUtcDates && typeof val === "string" && isISODateOnly(val)) {
val += "T00:00:00";
}
const d = new Date(val);
return settings.onDateCreated(d, reason, val);
}

function isISODateOnly(str: string): boolean {
if(str.indexOf("T") > 0) return false;
if (!/\d{4}-\d{2}-\d{2}/.test(str)) return false;
return !isNaN(new Date(str).getTime());
}

export class Helpers {
/**
* A static methods that returns true if a value undefined, null, empty string or empty array.
* @param value
*/
public static isValueEmpty(value: any) {
public static isValueEmpty(value: any): boolean {
if (Array.isArray(value) && value.length === 0) return true;
if (!!value && Helpers.isValueObject(value) && value.constructor === Object) {
for (var key in value) {
Expand Down
5 changes: 3 additions & 2 deletions packages/survey-core/src/question_expression.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { HashTable, Helpers } from "./helpers";
import { HashTable, Helpers, createDate } from "./helpers";
import { Question } from "./question";
import { Serializer } from "./jsonobject";
import { QuestionFactory } from "./questionfactory";
import { LocalizableString } from "./localizablestring";
import { ExpressionRunner } from "./conditions";
import { settings } from "./settings";

/**
* A class that describes the Expression question type. It is a read-only question type that calculates a value based on a specified expression.
Expand Down Expand Up @@ -220,7 +221,7 @@ export class QuestionExpressionModel extends Question {
}
protected getValueAsStr(val: any): string {
if (this.displayStyle == "date") {
var d = new Date(val);
const d = createDate("question-expression", val);
if (!!d && !!d.toLocaleDateString) return d.toLocaleDateString();
}
if (this.displayStyle != "none" && Helpers.isNumber(val)) {
Expand Down
20 changes: 12 additions & 8 deletions packages/survey-core/src/question_text.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { QuestionFactory } from "./questionfactory";
import { Serializer, property } from "./jsonobject";
import { LocalizableString, LocalizableStrings } from "./localizablestring";
import { Helpers, HashTable } from "./helpers";
import { Helpers, HashTable, createDate } from "./helpers";
import { EmailValidator } from "./validator";
import { SurveyError } from "./survey-error";
import { CustomError } from "./error";
Expand Down Expand Up @@ -359,16 +359,19 @@ export class QuestionTextModel extends QuestionTextBase {
private hasToConvertToUTC(val: any): boolean {
return settings.storeUtcDates && this.isDateTimeLocaleType() && !!val;
}
private createDate(val?: number | string | Date): Date {
return createDate("question-text", val);
}
protected valueForSurveyCore(val: any): any {
if(this.hasToConvertToUTC(val)) {
val = new Date(val).toISOString();
val = this.createDate(val).toISOString();
}
return super.valueForSurveyCore(val);
}
protected valueFromDataCore(val: any): any {
if(this.hasToConvertToUTC(val)) {
const d = new Date(val);
const locale_d = new Date(d.getTime() - d.getTimezoneOffset() * 60 * 1000);
const d = this.createDate(val);
const locale_d = this.createDate(d.getTime() - d.getTimezoneOffset() * 60 * 1000);
let res = locale_d.toISOString();
val = res.substring(0, res.length - 2);
}
Expand Down Expand Up @@ -468,7 +471,7 @@ export class QuestionTextModel extends QuestionTextBase {
}
private getCalculatedMinMax(minMax: any): any {
if (this.isValueEmpty(minMax)) return minMax;
return this.isDateInputType ? new Date(minMax) : minMax;
return this.isDateInputType ? this.createDate(minMax) : minMax;
}
private setRenderedMinMax(
values: HashTable<any> = null,
Expand Down Expand Up @@ -554,7 +557,7 @@ export class QuestionTextModel extends QuestionTextBase {
return Helpers.isNumber(newValue) ? Helpers.getNumber(newValue) : "";
}
if(this.inputType === "month") {
const d = new Date(newValue);
const d = this.createDate(newValue);
const isUtc = d.toISOString().indexOf(newValue) == 0 && newValue.indexOf("T") == -1;
const month = isUtc ? d.getUTCMonth() : d.getMonth();
const year = isUtc ? d.getUTCFullYear() : d.getFullYear();
Expand Down Expand Up @@ -697,8 +700,9 @@ function getCorrectMinMax(obj: QuestionTextBase, min: any, max: any, isMax: bool
if(Helpers.isValueEmpty(min) || Helpers.isValueEmpty(max)) return val;
if(obj.inputType.indexOf("date") === 0 || obj.inputType === "month") {
const isMonth = obj.inputType === "month";
const dMin = new Date(isMonth ? min + "-01" : min);
const dMax = new Date(isMonth ? max + "-01" : max);
const reason = "question-text-minmax";
const dMin = createDate(reason, isMonth ? min + "-01" : min);
const dMax = createDate(reason, isMonth ? max + "-01" : max);
if(!dMin || !dMax) return val;
if(dMin > dMax) return isMax ? min : max;
}
Expand Down
4 changes: 4 additions & 0 deletions packages/survey-core/src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,10 @@ export var settings = {
* ```
*/
storeUtcDates: false,
// @param reason "function-[functionname]", "question-[questionname]", "expression-operand"
onDateCreated: (newDate: Date, reason: string, val?: number | string | Date): Date => {
return newDate;
},
/**
* A function that allows you to define custom parsing rules for numbers represented as string values.
*
Expand Down
42 changes: 41 additions & 1 deletion packages/survey-core/tests/helperstests.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Helpers } from "../src/helpers";
import { Helpers, createDate } from "../src/helpers";
import { EmailValidator } from "../src/validator";
import { SurveyModel } from "../src/survey";
import { ProcessValue } from "../src/conditionProcessValue";
Expand Down Expand Up @@ -585,3 +585,43 @@ QUnit.test("compareVersions", function(assert) {
assert.equal(Helpers.compareVerions("1.2", "1.2.3"), -1, "#10");
assert.equal(Helpers.compareVerions("1.2.3", "1.2"), 1, "#11");
});
QUnit.test("createDate & settings.onDateCreated", function(assert) {
assert.equal(createDate("#1", "2024-10-10").getDate(), 10, "#1");
let func_val: any = "";
let func_reason = "";
settings.onDateCreated = (newDate, reason, val) => {
func_val = val;
func_reason = reason;
newDate.setDate(newDate.getDate() + 1);
return newDate;
};
assert.equal(createDate("#2", "2024-10-10").getDate(), 11, "#2");
assert.equal(func_val, "2024-10-10T00:00:00", "val");
assert.equal(func_reason, "#2", "reason");
settings.onDateCreated = (date, reason, val) => {
return date;
};
assert.equal(createDate("#3", "2024-10-10").getDate(), 10, "#3");
});
QUnit.test("createDate & T00:00:00 & settings.storeUtcDates", function(assert) {
let func_val: any = "";
settings.onDateCreated = (newDate, reason, val) => {
func_val = val;
return newDate;
};
createDate("#1", "2024-10-10");
assert.equal(func_val, "2024-10-10T00:00:00", "#1");
createDate("#2", "2024-10-10T02:00:00");
assert.equal(func_val, "2024-10-10T02:00:00", "#2");
createDate("#3", "10/10/2024");
assert.equal(func_val, "10/10/2024", "#3");

settings.storeUtcDates = true;
createDate("#4", "2024-10-10");
assert.equal(func_val, "2024-10-10", "#4");
settings.storeUtcDates = false;

settings.onDateCreated = (date, reason, val) => {
return date;
};
});

0 comments on commit ef3f8a5

Please sign in to comment.