[IMP] spreadsheet: interpret more months reprensentation

This commit improves the way months are recognized in pivot functions.
It also removes the use of `moment.js` which will be removed in the future
(see task 3391739)

Currently, months in a pivot function must follow a very specific
format.
It must be mm/yyyy

`ODOO.PIVOT(1, "amount", "date:month", "01/2020")`

Among other things: "1/2020" does not work, it must be prefixed by a
leading zeros.

Now, any date-like value would work:

month: "1/2020"
first of the month: "1/1/2020"
any day in the month: "1/2/2020"
using the DATE function: DATE(2020, 12, 1)
using NOW()

Future work: `PIVOT.HEADER` should return a the date as a number
when the date is a month. But it's currenctly quite a mess.
because we use the same method for a million things (`getGroupByDisplayLabel`).

Task 3413243

closes odoo/odoo#129782

Related: odoo/enterprise#44684
Signed-off-by: Rémi Rahir (rar) <rar@odoo.com>
This commit is contained in:
Lucas Lefèvre (lul)
2023-08-07 10:58:29 +02:00
committed by Rmi Rahir (rar)
parent b6f2087bf8
commit 5318e947bd
8 changed files with 410 additions and 121 deletions
@@ -4,14 +4,6 @@ import { _t } from "@web/core/l10n/translation";
export const DEFAULT_LINES_NUMBER = 20;
export const FORMATS = {
day: { out: "MM/DD/YYYY", display: "DD MMM YYYY", interval: "d" },
week: { out: "ww/YYYY", display: "[W]w YYYY", interval: "w" },
month: { out: "MM/YYYY", display: "MMMM YYYY", interval: "M" },
quarter: { out: "Q/YYYY", display: "[Q]Q YYYY", interval: "Q" },
year: { out: "YYYY", display: "YYYY", interval: "y" },
};
export const HEADER_STYLE = { fillColor: "#E6F2F3" };
export const TOP_LEVEL_STYLE = { bold: true, fillColor: "#E6F2F3" };
export const MEASURE_STYLE = { fillColor: "#E6F2F3", textColor: "#756f6f" };
@@ -141,11 +141,12 @@ export default class PivotDataSource extends OdooViewsDataSource {
/**
* @param {string} fieldName
* @param {string} value raw string value
* @param {object} locale
* @returns {string}
*/
getGroupByDisplayLabel(fieldName, value) {
getGroupByDisplayLabel(fieldName, value, locale) {
this._assertDataIsLoaded();
return this._model.getGroupByDisplayLabel(fieldName, value);
return this._model.getGroupByDisplayLabel(fieldName, value, locale);
}
/**
@@ -98,7 +98,7 @@ functionRegistry
const args = domain.map(toString);
assertPivotsExists(pivotId, this.getters);
assertDomainLength(args);
return this.getters.getDisplayedPivotHeaderValue(pivotId, args);
return this.getters.getDisplayedPivotHeaderValue(pivotId, args, this.locale);
},
computeFormat: function (pivotId, ...domain) {
pivotId = toString(pivotId.value);
@@ -121,10 +121,18 @@ functionRegistry
return "#,##0.00";
case "date":
case "datetime":
if (aggregateOperator === "day") {
return this.locale.dateFormat;
switch (aggregateOperator) {
case "day":
return this.locale.dateFormat;
case "month":
return "mmmm yyyy";
case "year":
return "0";
case "week":
case "quarter":
return undefined;
}
return undefined;
break;
default:
return undefined;
}
@@ -1,7 +1,6 @@
/** @odoo-module **/
import { _t } from "@web/legacy/js/services/core";
import { FORMATS } from "../helpers/constants";
import { getOdooFunctions } from "../helpers/odoo_functions_helpers";
export const pivotFormulaRegex = /^=.*PIVOT/;
@@ -10,19 +9,6 @@ export const pivotFormulaRegex = /^=.*PIVOT/;
// Public
//--------------------------------------------------------------------------
/**
* Format a data
*
* @param {string} interval aggregate interval i.e. month, week, quarter, ...
* @param {string} value
*/
export function formatDate(interval, value) {
const output = FORMATS[interval].display;
const input = FORMATS[interval].out;
const date = moment(value, input);
return date.isValid() ? date.format(output) : _t("None");
}
/**
* Parse a spreadsheet formula and detect the number of PIVOT functions that are
* present in the given formula.
@@ -5,14 +5,11 @@ import { Domain } from "@web/core/domain";
import { sprintf } from "@web/core/utils/strings";
import { PivotModel } from "@web/views/pivot/pivot_model";
import { computeReportMeasures } from "@web/views/utils";
import { session } from "@web/session";
import { FORMATS } from "../helpers/constants";
import * as spreadsheet from "@odoo/o-spreadsheet";
import { formatDate } from "./pivot_helpers";
import { PERIODS } from "@spreadsheet/pivot/pivot_helpers";
import { SpreadsheetPivotTable } from "@spreadsheet/pivot/pivot_table";
import { pivotTimeAdapter } from "./pivot_time_adapters";
const { toString, toNumber, toBoolean } = spreadsheet.helpers;
const { DEFAULT_LOCALE } = spreadsheet.constants;
@@ -81,9 +78,10 @@ function throwUnsupportedFieldError(field) {
* the two group values are "42" and "won".
* @param {object} field
* @param {number | boolean | string} groupValue
* @param {"day" | "week" | "month" | "quarter" | "year" | undefined} aggregateOperator
* @returns {number | boolean | string}
*/
export function parsePivotFormulaFieldValue(field, groupValue) {
export function toNormalizedPivotValue(field, groupValue, aggregateOperator) {
const groupValueString =
typeof groupValue === "boolean"
? toString(groupValue).toLocaleLowerCase()
@@ -98,7 +96,10 @@ export function parsePivotFormulaFieldValue(field, groupValue) {
switch (field.type) {
case "datetime":
case "date":
return toString(groupValueString);
return pivotTimeAdapter(aggregateOperator).normalizeFunctionValue(
groupValueString,
field
);
case "selection":
case "char":
case "text":
@@ -299,7 +300,7 @@ export class SpreadsheetPivotModel extends PivotModel {
* @param {string} groupValueString Value of the group by
* @returns {string}
*/
getGroupByDisplayLabel(groupFieldString, groupValueString) {
getGroupByDisplayLabel(groupFieldString, groupValueString, locale = DEFAULT_LOCALE) {
if (groupValueString === NO_RECORD_AT_THIS_POSITION) {
return "";
}
@@ -311,13 +312,15 @@ export class SpreadsheetPivotModel extends PivotModel {
return this.parseGroupField(groupValueString).field.string;
}
const { field, aggregateOperator } = this.parseGroupField(groupFieldString);
const value = parsePivotFormulaFieldValue(field, groupValueString);
const value = toNormalizedPivotValue(field, groupValueString, aggregateOperator);
const undef = _t("None");
if (this._isDateField(field)) {
// TODO include this parsing to the pivot time adapters and extend it to other time periods
if (value && aggregateOperator === "day") {
return toNumber(value, DEFAULT_LOCALE);
}
return formatDate(aggregateOperator, value);
const adapter = pivotTimeAdapter(aggregateOperator);
return adapter.format(value, locale);
}
if (field.relation) {
const label = this.metadataRepository.getRecordDisplayName(field.relation, value);
@@ -455,39 +458,16 @@ export class SpreadsheetPivotModel extends PivotModel {
return groupBys.map((groupBy) => {
const { field, aggregateOperator } = this.parseGroupField(groupBy);
if (this._isDateField(field)) {
const value = this._getGroupStartingDay(groupBy, group);
if (!value) {
return false;
}
const fOut = FORMATS[aggregateOperator]["out"];
// eslint-disable-next-line no-undef
const date = moment(value);
return date.isValid() ? date.format(fOut) : false;
return pivotTimeAdapter(aggregateOperator).normalizeServerValue(
groupBy,
field,
group
);
}
return this._sanitizeValue(group[groupBy]);
});
}
/**
* When grouping by a time field, return
* the group starting day (local to the timezone)
* @param {string} groupBy
* @param {object} readGroup
* @returns {string | undefined}
*/
_getGroupStartingDay(groupBy, readGroup) {
if (!readGroup["__range"] || !readGroup["__range"][groupBy]) {
return undefined;
}
const { field } = this.parseGroupField(groupBy);
const sqlValue = readGroup["__range"][groupBy].from;
if (this.metaData.fields[field.name].type === "date") {
return sqlValue;
}
const userTz = session.user_context.tz || luxon.Settings.defaultZoneName;
return luxon.DateTime.fromSQL(sqlValue, { zone: "utc" }).setZone(userTz).toISODate();
}
/**
* Check if the given field is used as col group by
*/
@@ -555,12 +535,13 @@ export class SpreadsheetPivotModel extends PivotModel {
while (i < domain.length) {
const groupFieldString = domain[i];
const groupValue = domain[i + 1];
const { field, isPositional } = this.parseGroupField(groupFieldString);
const { field, isPositional, aggregateOperator } =
this.parseGroupField(groupFieldString);
let value;
if (isPositional) {
value = this._parsePivotFormulaWithPosition(field, groupValue, cols, rows);
} else {
value = parsePivotFormulaFieldValue(field, groupValue);
value = toNormalizedPivotValue(field, groupValue, aggregateOperator);
}
if (this._isCol(field)) {
cols.push(value);
@@ -0,0 +1,274 @@
/** @odoo-module */
import { helpers, constants } from "@odoo/o-spreadsheet";
import { deserializeDate } from "@web/core/l10n/dates";
import { _t } from "@web/core/l10n/translation";
import { sprintf } from "@web/core/utils/strings";
import { session } from "@web/session";
const { toNumber, formatValue } = helpers;
const { DEFAULT_LOCALE } = constants;
const { DateTime } = luxon;
/**
* @param {"day" | "week" | "month" | "quarter" | "month"} groupAggregate
* @returns {PivotTimeAdapter}
*/
export function pivotTimeAdapter(groupAggregate) {
return TIME_ADAPTERS[groupAggregate];
}
/**
* The Time Adapter: Managing Time Periods for Pivot Functions
*
* Overview:
* A time adapter is responsible for managing time periods associated with pivot functions.
* Each type of period (day, week, month, quarter, etc.) has its own dedicated adapter.
* The adapter's primary role is to normalize period values between spreadsheet functions,
* the server, and the datasource.
* By normalizing the period value, it can be stored consistently in the datasource.
*
* Normalization Process:
* When dealing with the server, the time adapter ensures that the received periods are
* normalized before being stored in the datasource.
* For example, if the server returns a day period as "2023-12-25 22:00:00," the time adapter
* transforms it into the normalized form "12/25/2023" for storage in the datasource.
*
* Similarly, when working with functions in the spreadsheet, the time adapter normalizes
* the provided period to facilitate accurate lookup of values in the datasource.
* For instance, if the spreadsheet function represents a day period as a number generated
* by the DATE function (DATE(2023, 12, 25)), the time adapter will normalize it accordingly.
*
* Example:
* To illustrate the normalization process, let's consider the day period:
*
* 1. The server returns a day period as "2023-12-25 22:00:00"
* 2. The time adapter normalizes this period to "12/25/2023" for storage in the datasource.
* 3. Meanwhile, the spreadsheet function represents the day period as a number obtained from
* the DATE function (DATE(2023, 12, 25)).
*
* By applying the appropriate normalization, the time adapter ensures that the periods from
* different sources are consistently represented and can be effectively utilized for lookup
* operations in the datasource.
*
* Implementation notes/tips:
* - Do not mix luxon and spreadsheet dates in the same function. Timezones are not handled the same way.
* Spreadsheet dates are naive dates (no timezone) while luxon dates are timezone aware dates.
* **Don't do this**: DateTime.fromJSDate(toJsDate(value)) (it will be interpreted as UTC)
*
* - spreadsheet formats and luxon formats are not the same but can be equivalent.
* For example: "MM/dd/yyyy" (luxon format) is equivalent to "mm/dd/yyyy" (spreadsheet format)
*
* Limitations:
* If a period value is provided as a **string** to a function, it will interpreted as being in the default locale.
* e.g. in `ODOO.PIVOT(1, "amount", "create_date", "1/5/2023")`, the day is interpreted as being the 5th of January 2023,
* even if the spreadsheet locale is set to French and such a date is usually interpreted as the 1st of May 2023.
* The reason is ODOO.PIVOT functions are currently generated without being aware of the spreadsheet locale.
*
* @typedef {Object} PivotTimeAdapter
* @property {(groupBy: string, field: string, readGroupResult: object) => string} normalizeServerValue
* @property {(value: string) => string} normalizeFunctionValue
* @property {(normalizedValue: string, step: number) => string} increment
* @property {(normalizedValue: string, locale: Object) => string} format
*/
/**
* @type {PivotTimeAdapter}
* Normalized value: "12/25/2023"
*
* Note: Those two format are equivalent:
* - "MM/dd/yyyy" (luxon format)
* - "mm/dd/yyyy" (spreadsheet format)
**/
const dayAdapter = {
normalizeServerValue(groupBy, field, readGroupResult) {
const serverDayValue = getGroupStartingDay(field, groupBy, readGroupResult);
const date = deserializeDate(serverDayValue);
return date.toFormat("MM/dd/yyyy");
},
normalizeFunctionValue(value) {
const date = toNumber(value, DEFAULT_LOCALE);
return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/dd/yyyy" });
},
increment(normalizedValue, step) {
const date = DateTime.fromFormat(normalizedValue, "MM/dd/yyyy");
return date.plus({ days: step }).toFormat("MM/dd/yyyy");
},
format(normalizedValue, locale) {
const value = toNumber(normalizedValue, DEFAULT_LOCALE);
return formatValue(value, { locale, format: locale.dateFormat });
},
};
/**
* @type {PivotTimeAdapter}
* Normalized value: "2/2023" for week 2 of 2023
*/
const weekAdapter = {
normalizeServerValue(groupBy, field, readGroupResult) {
const weekValue = readGroupResult[groupBy];
const { week, year } = parseServerWeekHeader(weekValue);
return `${week}/${year}`;
},
normalizeFunctionValue(value) {
const [week, year] = value.split("/");
return `${Number(week)}/${Number(year)}`;
},
increment(normalizedValue, step) {
const [week, year] = normalizedValue.split("/");
const weekNumber = Number(week);
const yearNumber = Number(year);
const date = DateTime.fromObject({ weekNumber, weekYear: yearNumber });
const nextWeek = date.plus({ weeks: step });
return `${nextWeek.weekNumber}/${nextWeek.weekYear}`;
},
format(normalizedValue, locale) {
const [year, week] = normalizedValue.split("/");
return sprintf(_t("W%(week)s %(year)s"), { week, year });
},
};
/**
* @type {PivotTimeAdapter}
* normalized month value is a string formatted as "MM/yyyy" (luxon format)
* e.g. "01/2020" for January 2020
*/
const monthAdapter = {
normalizeServerValue(groupBy, field, readGroupResult) {
const firstOfTheMonth = getGroupStartingDay(field, groupBy, readGroupResult);
const date = deserializeDate(firstOfTheMonth);
return date.toFormat("MM/yyyy");
},
normalizeFunctionValue(value) {
const date = toNumber(value, DEFAULT_LOCALE);
return formatValue(date, { DEFAULT_LOCALE, format: "mm/yyyy" });
},
increment(normalizedValue, step) {
return DateTime.fromFormat(normalizedValue, "MM/yyyy")
.plus({ months: step })
.toFormat("MM/yyyy");
},
format(normalizedValue, locale) {
const value = toNumber(normalizedValue, DEFAULT_LOCALE);
return formatValue(value, { locale, format: "mmmm yyyy" });
},
};
/**
* @type {PivotTimeAdapter}
* normalized quarter value is "quarter/year"
* e.g. "1/2020" for Q1 2020
*/
const quarterAdapter = {
normalizeServerValue(groupBy, field, readGroupResult) {
const firstOfTheQuarter = getGroupStartingDay(field, groupBy, readGroupResult);
const date = deserializeDate(firstOfTheQuarter);
return `${date.quarter}/${date.year}`;
},
normalizeFunctionValue(value) {
const [quarter, year] = value.split("/");
return `${quarter}/${year}`;
},
increment(normalizedValue, step) {
const [quarter, year] = normalizedValue.split("/");
const date = DateTime.fromObject({ year: Number(year), month: Number(quarter) * 3 });
const nextQuarter = date.plus({ quarters: step });
return `${nextQuarter.quarter}/${nextQuarter.year}`;
},
format(normalizedValue, locale) {
const [quarter, year] = normalizedValue.split("/");
return sprintf(_t("Q%(quarter)s %(year)s"), { quarter, year });
},
};
/**
* @type {PivotTimeAdapter}
*/
const yearAdapter = {
normalizeServerValue(groupBy, field, readGroupResult) {
return Number(readGroupResult[groupBy]);
},
normalizeFunctionValue(value) {
return toNumber(value, DEFAULT_LOCALE);
},
increment(normalizedValue, step) {
return normalizedValue + step;
},
format(normalizedValue, locale) {
return formatValue(normalizedValue, { locale, format: "0" });
},
};
/**
* Decorate adapter functions to handle the empty value "false"
* @param {PivotTimeAdapter} adapter
*/
function falseHandlerDecorator(adapter) {
return {
normalizeServerValue(groupBy, field, readGroupResult) {
if (readGroupResult[groupBy] === false) {
return false;
}
return adapter.normalizeServerValue(groupBy, field, readGroupResult);
},
normalizeFunctionValue(value) {
if (value === false || value === "false") {
return false;
}
return adapter.normalizeFunctionValue(value);
},
increment(normalizedValue, step) {
if (normalizedValue === false) {
return false;
}
return adapter.increment(normalizedValue, step);
},
format(normalizedValue, locale) {
if (normalizedValue === false) {
return _t("None");
}
return adapter.format(normalizedValue, locale);
},
};
}
const TIME_ADAPTERS = {
day: falseHandlerDecorator(dayAdapter),
week: falseHandlerDecorator(weekAdapter),
month: falseHandlerDecorator(monthAdapter),
quarter: falseHandlerDecorator(quarterAdapter),
year: falseHandlerDecorator(yearAdapter),
};
/**
* When grouping by a time field, return
* the group starting day (local to the timezone)
* @param {object} field
* @param {string} groupBy
* @param {object} readGroup
* @returns {string | undefined}
*/
function getGroupStartingDay(field, groupBy, readGroup) {
if (!readGroup["__range"] || !readGroup["__range"][groupBy]) {
return undefined;
}
const sqlValue = readGroup["__range"][groupBy].from;
if (field.type === "date") {
return sqlValue;
}
const userTz = session.user_context.tz || luxon.Settings.defaultZoneName;
return DateTime.fromSQL(sqlValue, { zone: "utc" }).setZone(userTz).toISODate();
}
/**
* Parses a pivot week header value.
* @param {string} value
* @example
* parseServerWeekHeader("W1 2020") // { week: 1, year: 2020 }
*/
function parseServerWeekHeader(value) {
// Value is always formatted as "W1 2020", no matter the language.
// Parsing this formatted value is the only way to ensure we get the same
// locale aware week number as the one used in the server.
const [week, year] = value.split(" ");
return { week: Number(week.slice(1)), year: Number(year) };
}
@@ -586,6 +586,20 @@ QUnit.module("spreadsheet > pivot plugin", {}, () => {
assert.equal(getCellValue(model, "C5"), "");
});
QUnit.test("PIVOT.HEADER grouped by date field without value", async function (assert) {
for (const interval of ["day", "week", "month", "quarter", "year"]) {
const { model } = await createSpreadsheetWithPivot({
arch: /* xml */ `
<pivot>
<field name="date" interval="${interval}" type="col"/>
<field name="foo" type="measure"/>
</pivot>`,
});
setCellContent(model, "A1", `=ODOO.PIVOT.HEADER(1, "date:${interval}", "false")`);
assert.equal(getCellValue(model, "A1"), "None");
}
});
QUnit.test("PIVOT formulas are correctly formatted at evaluation", async function (assert) {
const { model } = await createSpreadsheetWithPivot({
arch: /* xml */ `
@@ -615,6 +629,19 @@ QUnit.module("spreadsheet > pivot plugin", {}, () => {
}
);
QUnit.test("PIVOT.HEADER day are correctly formatted at evaluation", async function (assert) {
const { model } = await createSpreadsheetWithPivot({
arch: /* xml */ `
<pivot>
<field name="date" interval="day" type="col"/>
<field name="foo" type="measure"/>
</pivot>`,
});
assert.strictEqual(getEvaluatedCell(model, "B1").format, "m/d/yyyy");
assert.strictEqual(getEvaluatedCell(model, "B1").value, 42474);
assert.strictEqual(getEvaluatedCell(model, "B1").formattedValue, "4/14/2016");
});
QUnit.test(
"PIVOT.HEADER formulas are correctly formatted at evaluation",
async function (assert) {
@@ -1,7 +1,7 @@
/** @odoo-module */
import { getFirstPivotFunction, getNumberOfPivotFormulas } from "@spreadsheet/pivot/pivot_helpers";
import { getFirstListFunction, getNumberOfListFormulas } from "@spreadsheet/list/list_helpers";
import { parsePivotFormulaFieldValue } from "@spreadsheet/pivot/pivot_model";
import { toNormalizedPivotValue } from "@spreadsheet/pivot/pivot_model";
function stringArg(value) {
return { type: "STRING", value: `${value}` };
@@ -27,9 +27,7 @@ QUnit.module("spreadsheet > pivot_helpers", {}, () => {
QUnit.test("Extraction with two PIVOT formulas", async function (assert) {
const formula = `=ODOO.PIVOT("1", "test") + ODOO.PIVOT("2", "hello", "bla")`;
let functionName;
let args;
({ functionName, args } = getFirstPivotFunction(formula));
const { functionName, args } = getFirstPivotFunction(formula);
assert.strictEqual(functionName, "ODOO.PIVOT");
assert.strictEqual(args.length, 2);
assert.deepEqual(args[0], stringArg("1"));
@@ -59,22 +57,22 @@ QUnit.module("spreadsheet > pivot_helpers", {}, () => {
});
});
QUnit.module("spreadsheet > parsePivotFormulaFieldValue", {}, () => {
QUnit.module("spreadsheet > toNormalizedPivotValue", {}, () => {
QUnit.test("parse values of a selection, char or text field", (assert) => {
for (const fieldType of ["selection", "text", "char"]) {
const field = {
type: fieldType,
string: "A field",
};
assert.strictEqual(parsePivotFormulaFieldValue(field, "won"), "won");
assert.strictEqual(parsePivotFormulaFieldValue(field, "1"), "1");
assert.strictEqual(parsePivotFormulaFieldValue(field, 1), "1");
assert.strictEqual(parsePivotFormulaFieldValue(field, "11/2020"), "11/2020");
assert.strictEqual(parsePivotFormulaFieldValue(field, "2020"), "2020");
assert.strictEqual(parsePivotFormulaFieldValue(field, "01/11/2020"), "01/11/2020");
assert.strictEqual(parsePivotFormulaFieldValue(field, "false"), false);
assert.strictEqual(parsePivotFormulaFieldValue(field, false), false);
assert.strictEqual(parsePivotFormulaFieldValue(field, "true"), "true");
assert.strictEqual(toNormalizedPivotValue(field, "won"), "won");
assert.strictEqual(toNormalizedPivotValue(field, "1"), "1");
assert.strictEqual(toNormalizedPivotValue(field, 1), "1");
assert.strictEqual(toNormalizedPivotValue(field, "11/2020"), "11/2020");
assert.strictEqual(toNormalizedPivotValue(field, "2020"), "2020");
assert.strictEqual(toNormalizedPivotValue(field, "01/11/2020"), "01/11/2020");
assert.strictEqual(toNormalizedPivotValue(field, "false"), false);
assert.strictEqual(toNormalizedPivotValue(field, false), false);
assert.strictEqual(toNormalizedPivotValue(field, "true"), "true");
}
});
@@ -84,16 +82,38 @@ QUnit.module("spreadsheet > parsePivotFormulaFieldValue", {}, () => {
type: fieldType,
string: "A field",
};
assert.strictEqual(parsePivotFormulaFieldValue(field, "11/2020"), "11/2020");
assert.strictEqual(parsePivotFormulaFieldValue(field, "2020"), "2020");
assert.strictEqual(parsePivotFormulaFieldValue(field, "01/11/2020"), "01/11/2020");
assert.strictEqual(parsePivotFormulaFieldValue(field, "1"), "1");
assert.strictEqual(parsePivotFormulaFieldValue(field, 1), "1");
assert.strictEqual(parsePivotFormulaFieldValue(field, "false"), false);
assert.strictEqual(parsePivotFormulaFieldValue(field, false), false);
assert.strictEqual(parsePivotFormulaFieldValue(field, "true"), "true"); // this should throw because it's not a valid date
assert.strictEqual(parsePivotFormulaFieldValue(field, true), "true"); // this should throw because it's not a valid date
assert.strictEqual(parsePivotFormulaFieldValue(field, "won"), "won"); // this should throw because it's not a valid date
// day
assert.strictEqual(toNormalizedPivotValue(field, "1/11/2020", "day"), "01/11/2020");
assert.strictEqual(toNormalizedPivotValue(field, "01/11/2020", "day"), "01/11/2020");
assert.strictEqual(toNormalizedPivotValue(field, "11/2020", "day"), "11/01/2020");
assert.strictEqual(toNormalizedPivotValue(field, "1", "day"), "12/31/1899");
assert.strictEqual(toNormalizedPivotValue(field, 1, "day"), "12/31/1899");
assert.strictEqual(toNormalizedPivotValue(field, "false", "day"), false);
assert.strictEqual(toNormalizedPivotValue(field, false, "day"), false);
// week
assert.strictEqual(toNormalizedPivotValue(field, "11/2020", "week"), "11/2020");
assert.strictEqual(toNormalizedPivotValue(field, "1/2020", "week"), "1/2020");
assert.strictEqual(toNormalizedPivotValue(field, "01/2020", "week"), "1/2020");
assert.strictEqual(toNormalizedPivotValue(field, "false", "week"), false);
assert.strictEqual(toNormalizedPivotValue(field, false, "week"), false);
// month
assert.strictEqual(toNormalizedPivotValue(field, "11/2020", "month"), "11/2020");
assert.strictEqual(toNormalizedPivotValue(field, "1/2020", "month"), "01/2020");
assert.strictEqual(toNormalizedPivotValue(field, "01/2020", "month"), "01/2020");
assert.strictEqual(toNormalizedPivotValue(field, "2/11/2020", "month"), "02/2020");
assert.strictEqual(toNormalizedPivotValue(field, "2/1/2020", "month"), "02/2020");
assert.strictEqual(toNormalizedPivotValue(field, 1, "month"), "12/1899");
assert.strictEqual(toNormalizedPivotValue(field, "false", "month"), false);
assert.strictEqual(toNormalizedPivotValue(field, false, "month"), false);
// year
assert.strictEqual(toNormalizedPivotValue(field, "2020", "year"), 2020);
assert.strictEqual(toNormalizedPivotValue(field, 2020, "year"), 2020);
assert.strictEqual(toNormalizedPivotValue(field, "false", "year"), false);
assert.strictEqual(toNormalizedPivotValue(field, false, "year"), false);
assert.throws(() => toNormalizedPivotValue(field, "true", "month"));
assert.throws(() => toNormalizedPivotValue(field, true, "month"));
assert.throws(() => toNormalizedPivotValue(field, "won", "month"));
}
});
@@ -102,16 +122,16 @@ QUnit.module("spreadsheet > parsePivotFormulaFieldValue", {}, () => {
type: "boolean",
string: "A field",
};
assert.strictEqual(parsePivotFormulaFieldValue(field, "false"), false);
assert.strictEqual(parsePivotFormulaFieldValue(field, false), false);
assert.strictEqual(parsePivotFormulaFieldValue(field, "true"), true);
assert.strictEqual(parsePivotFormulaFieldValue(field, true), true);
assert.throws(() => parsePivotFormulaFieldValue(field, "11/2020"));
assert.throws(() => parsePivotFormulaFieldValue(field, "2020"));
assert.throws(() => parsePivotFormulaFieldValue(field, "01/11/2020"));
assert.throws(() => parsePivotFormulaFieldValue(field, "1"));
assert.throws(() => parsePivotFormulaFieldValue(field, 1));
assert.throws(() => parsePivotFormulaFieldValue(field, "won"));
assert.strictEqual(toNormalizedPivotValue(field, "false"), false);
assert.strictEqual(toNormalizedPivotValue(field, false), false);
assert.strictEqual(toNormalizedPivotValue(field, "true"), true);
assert.strictEqual(toNormalizedPivotValue(field, true), true);
assert.throws(() => toNormalizedPivotValue(field, "11/2020"));
assert.throws(() => toNormalizedPivotValue(field, "2020"));
assert.throws(() => toNormalizedPivotValue(field, "01/11/2020"));
assert.throws(() => toNormalizedPivotValue(field, "1"));
assert.throws(() => toNormalizedPivotValue(field, 1));
assert.throws(() => toNormalizedPivotValue(field, "won"));
});
QUnit.test("parse values of numeric fields", (assert) => {
@@ -120,16 +140,16 @@ QUnit.module("spreadsheet > parsePivotFormulaFieldValue", {}, () => {
type: fieldType,
string: "A field",
};
assert.strictEqual(parsePivotFormulaFieldValue(field, "2020"), 2020);
assert.strictEqual(parsePivotFormulaFieldValue(field, "01/11/2020"), 43841); // a date is actually a number in a spreadsheet
assert.strictEqual(parsePivotFormulaFieldValue(field, "1"), 1);
assert.strictEqual(parsePivotFormulaFieldValue(field, 1), 1);
assert.strictEqual(parsePivotFormulaFieldValue(field, "false"), false);
assert.strictEqual(parsePivotFormulaFieldValue(field, false), false);
assert.throws(() => parsePivotFormulaFieldValue(field, "true"));
assert.throws(() => parsePivotFormulaFieldValue(field, true));
assert.throws(() => parsePivotFormulaFieldValue(field, "won"));
assert.throws(() => parsePivotFormulaFieldValue(field, "11/2020"));
assert.strictEqual(toNormalizedPivotValue(field, "2020"), 2020);
assert.strictEqual(toNormalizedPivotValue(field, "01/11/2020"), 43841); // a date is actually a number in a spreadsheet
assert.strictEqual(toNormalizedPivotValue(field, "11/2020"), 44136); // 1st of november 2020
assert.strictEqual(toNormalizedPivotValue(field, "1"), 1);
assert.strictEqual(toNormalizedPivotValue(field, 1), 1);
assert.strictEqual(toNormalizedPivotValue(field, "false"), false);
assert.strictEqual(toNormalizedPivotValue(field, false), false);
assert.throws(() => toNormalizedPivotValue(field, "true"));
assert.throws(() => toNormalizedPivotValue(field, true));
assert.throws(() => toNormalizedPivotValue(field, "won"));
}
});
@@ -139,16 +159,16 @@ QUnit.module("spreadsheet > parsePivotFormulaFieldValue", {}, () => {
type: fieldType,
string: "A field",
};
assert.throws(() => parsePivotFormulaFieldValue(field, "false"));
assert.throws(() => parsePivotFormulaFieldValue(field, false));
assert.throws(() => parsePivotFormulaFieldValue(field, "true"));
assert.throws(() => parsePivotFormulaFieldValue(field, true));
assert.throws(() => parsePivotFormulaFieldValue(field, "11/2020"));
assert.throws(() => parsePivotFormulaFieldValue(field, "2020"));
assert.throws(() => parsePivotFormulaFieldValue(field, "01/11/2020"));
assert.throws(() => parsePivotFormulaFieldValue(field, "1"));
assert.throws(() => parsePivotFormulaFieldValue(field, 1));
assert.throws(() => parsePivotFormulaFieldValue(field, "won"));
assert.throws(() => toNormalizedPivotValue(field, "false"));
assert.throws(() => toNormalizedPivotValue(field, false));
assert.throws(() => toNormalizedPivotValue(field, "true"));
assert.throws(() => toNormalizedPivotValue(field, true));
assert.throws(() => toNormalizedPivotValue(field, "11/2020"));
assert.throws(() => toNormalizedPivotValue(field, "2020"));
assert.throws(() => toNormalizedPivotValue(field, "01/11/2020"));
assert.throws(() => toNormalizedPivotValue(field, "1"));
assert.throws(() => toNormalizedPivotValue(field, 1));
assert.throws(() => toNormalizedPivotValue(field, "won"));
}
});
});