diff --git a/packages/perspective-viewer-highcharts/src/js/config.js b/packages/perspective-viewer-highcharts/src/js/config.js index b7aa5e5e8c..34e66a95a1 100644 --- a/packages/perspective-viewer-highcharts/src/js/config.js +++ b/packages/perspective-viewer-highcharts/src/js/config.js @@ -14,8 +14,8 @@ export function set_boost(config, series, ...types) { if (count > 5000) { Object.assign(config, { boost: { - useGPUTranslations: types.indexOf("date") === -1, - usePreAllocated: types.indexOf("date") === -1 + useGPUTranslations: types.indexOf("datetime") === -1 && types.indexOf("date") === -1, + usePreAllocated: types.indexOf("datetime") === -1 && types.indexOf("date") === -1 } }); config.plotOptions.series.boostThreshold = 1; @@ -40,7 +40,7 @@ export function set_both_axis(config, axis, name, type, tree_type, top) { export function set_axis(config, axis, name, type) { let opts = { - type: type === "date" ? "datetime" : undefined, + type: ["datetime", "date"].indexOf(type) > -1 ? "datetime" : undefined, startOnTick: false, endOnTick: false, title: { @@ -55,7 +55,7 @@ export function set_axis(config, axis, name, type) { } export function set_category_axis(config, axis, type, top) { - if (type === "date") { + if (type === "datetime") { Object.assign(config, { [axis]: { categories: top.categories.map(x => new Date(x).toLocaleString("en-us", {year: "numeric", month: "numeric", day: "numeric", hour: "numeric", minute: "numeric"})), @@ -65,6 +65,16 @@ export function set_category_axis(config, axis, type, top) { } } }); + } else if (type === "date") { + Object.assign(config, { + [axis]: { + categories: top.categories.map(x => new Date(x).toLocaleString("en-us", {year: "numeric", month: "numeric", day: "numeric"})), + labels: { + enabled: top.categories.length > 0, + autoRotation: [-5] + } + } + }); } else { let opts = { categories: top.categories, diff --git a/packages/perspective-viewer-highcharts/src/js/tooltip.js b/packages/perspective-viewer-highcharts/src/js/tooltip.js index cd48b65549..6e23816bb0 100644 --- a/packages/perspective-viewer-highcharts/src/js/tooltip.js +++ b/packages/perspective-viewer-highcharts/src/js/tooltip.js @@ -144,8 +144,10 @@ function value_exists(value) { } function format_value(value, type) { - if (type === "date") { + if (type === "datetime") { return new Date(value).toLocaleString(); + } else if (type === "date") { + return new Date(value).toLocaleString("en-us", {year: "numeric", month: "numeric", day: "numeric"}); } else if (type === "float" || type === "integer") { return format_number(value, type); } else { diff --git a/packages/perspective-viewer-hypergrid/src/js/hypergrid.js b/packages/perspective-viewer-hypergrid/src/js/hypergrid.js index 5e100793c5..71d3b40c3d 100644 --- a/packages/perspective-viewer-hypergrid/src/js/hypergrid.js +++ b/packages/perspective-viewer-hypergrid/src/js/hypergrid.js @@ -235,7 +235,7 @@ bindTemplate(TEMPLATE)( const integer_formatter = null_formatter(new this.grid.localization.NumberFormatter("en-US", {})); this.grid.localization.add("FinanceInteger", integer_formatter); - const date_formatter = null_formatter( + const datetime_formatter = null_formatter( new this.grid.localization.DateFormatter("en-us", { week: "numeric", year: "numeric", @@ -247,12 +247,24 @@ bindTemplate(TEMPLATE)( }), -1 ); + + const date_formatter = null_formatter( + new this.grid.localization.DateFormatter("en-us", { + week: "numeric", + year: "numeric", + month: "numeric", + day: "numeric" + }), + -1 + ); + this.grid.localization.add("FinanceDatetime", datetime_formatter); this.grid.localization.add("FinanceDate", date_formatter); this.grid.localization.add("FinanceTree", { format: function(val, type) { const f = { date: date_formatter, + datetime: datetime_formatter, integer: integer_formatter, float: float_formatter }[type]; diff --git a/packages/perspective-viewer-hypergrid/src/js/perspective-plugin.js b/packages/perspective-viewer-hypergrid/src/js/perspective-plugin.js index fdd1e57d2e..791903dfd1 100644 --- a/packages/perspective-viewer-hypergrid/src/js/perspective-plugin.js +++ b/packages/perspective-viewer-hypergrid/src/js/perspective-plugin.js @@ -92,6 +92,9 @@ function setColumnPropsByType(column) { case "date": props.format = "FinanceDate"; break; + case "datetime": + props.format = "FinanceDatetime"; + break; default: if (column.index === this.treeColumnIndex) { props.format = "FinanceTree"; diff --git a/packages/perspective-viewer/src/js/computed_column.js b/packages/perspective-viewer/src/js/computed_column.js index dcbb969cdb..1ba8e54653 100644 --- a/packages/perspective-viewer/src/js/computed_column.js +++ b/packages/perspective-viewer/src/js/computed_column.js @@ -42,7 +42,7 @@ const hour_bucket = function(val) { let date = new Date(val); date.setMinutes(0); date.setSeconds(0); - return +date; + return date; }; const day_bucket = function(val) { @@ -50,7 +50,7 @@ const day_bucket = function(val) { date.setHours(0); date.setMinutes(0); date.setSeconds(0); - return +date; + return date; }; const week_bucket = function(val) { @@ -61,7 +61,7 @@ const week_bucket = function(val) { date.setMinutes(0); date.setSeconds(0); date.setDate(diff); - return +date; + return date; }; const month_bucket = function(val) { @@ -70,17 +70,17 @@ const month_bucket = function(val) { date.setMinutes(0); date.setSeconds(0); date.setDate(1); - return +date; + return date; }; export const COMPUTATIONS = { - hour_of_day: new Computation("hour_of_day", "date", "integer", hour_of_day), - day_of_week: new Computation("day_of_week", "date", "string", day_of_week), - month_of_year: new Computation("month_of_year", "date", "string", month_of_year), - hour_bucket: new Computation("hour_bucket", "date", "date", hour_bucket), - day_bucket: new Computation("day_bucket", "date", "date", day_bucket), - week_bucket: new Computation("week_bucket", "date", "date", week_bucket), - month_bucket: new Computation("month_bucket", "date", "date", month_bucket), + hour_of_day: new Computation("hour_of_day", "datetime", "integer", hour_of_day), + day_of_week: new Computation("day_of_week", "datetime", "string", day_of_week), + month_of_year: new Computation("month_of_year", "datetime", "string", month_of_year), + hour_bucket: new Computation("hour_bucket", "datetime", "datetime", hour_bucket), + day_bucket: new Computation("day_bucket", "datetime", "date", day_bucket), + week_bucket: new Computation("week_bucket", "datetime", "date", week_bucket), + month_bucket: new Computation("month_bucket", "datetime", "date", month_bucket), uppercase: new Computation("uppercase", "string", "string", x => x.toUpperCase()), lowercase: new Computation("lowercase", "string", "string", x => x.toLowerCase()), length: new Computation("length", "string", "integer", x => x.length), @@ -107,7 +107,7 @@ class ComputedColumn extends HTMLElement { integer: "123", string: "abc", boolean: "t/f", - date: "mdy" + datetime: "mdy" }; } @@ -329,7 +329,11 @@ class ComputedColumn extends HTMLElement { const index = Number.parseInt(target.getAttribute("data-index")); - if ((computation_type !== "float" && type !== computation_type) || (computation_type === "float" && type !== "float" && type !== "integer")) { + if ( + (computation_type !== "float" && computation_type !== "datetime" && type !== computation_type) || + (computation_type === "float" && type !== "float" && type !== "integer") || + (computation_type === "datetime" && type !== "datetime" && type !== "date") + ) { this._register_inputs(); this.state.errors.input_column = `Input column type (${type}) must match computation input type (${computation_type}).`; this._set_error_message("input_column", this._input_column_error_message); diff --git a/packages/perspective-viewer/src/js/row.js b/packages/perspective-viewer/src/js/row.js index 0eba67ad1e..cc10f0f067 100644 --- a/packages/perspective-viewer/src/js/row.js +++ b/packages/perspective-viewer/src/js/row.js @@ -94,8 +94,12 @@ class Row extends HTMLElement { filter_dropdown.innerHTML = perspective.TYPE_FILTERS.boolean.map(agg => ``).join(""); break; case "date": - agg_dropdown.innerHTML = perspective.TYPE_AGGREGATES.date.map(agg => ``).join(""); - filter_dropdown.innerHTML = perspective.TYPE_FILTERS.date.map(agg => ``).join(""); + agg_dropdown.innerHTML = perspective.TYPE_AGGREGATES.datetime.map(agg => ``).join(""); + filter_dropdown.innerHTML = perspective.TYPE_FILTERS.datetime.map(agg => ``).join(""); + break; + case "datetime": + agg_dropdown.innerHTML = perspective.TYPE_AGGREGATES.datetime.map(agg => ``).join(""); + filter_dropdown.innerHTML = perspective.TYPE_FILTERS.datetime.map(agg => ``).join(""); break; case "string": agg_dropdown.innerHTML = perspective.TYPE_AGGREGATES.string.map(agg => ``).join(""); diff --git a/packages/perspective-viewer/src/js/view.js b/packages/perspective-viewer/src/js/view.js index c87a4344e1..fe69b5f0de 100755 --- a/packages/perspective-viewer/src/js/view.js +++ b/packages/perspective-viewer/src/js/view.js @@ -361,7 +361,7 @@ async function loadTable(table, redraw = true) { this.setAttribute("columns", JSON.stringify(this._initial_col_order)); } - let type_order = {integer: 2, string: 0, float: 3, boolean: 4, date: 1}; + let type_order = {integer: 2, string: 0, float: 3, boolean: 4, datetime: 1}; // Sort columns by type and then name cols.sort((a, b) => { diff --git a/packages/perspective-viewer/src/less/computed_column.less b/packages/perspective-viewer/src/less/computed_column.less index 0260d467cd..f1514c1ccf 100644 --- a/packages/perspective-viewer/src/less/computed_column.less +++ b/packages/perspective-viewer/src/less/computed_column.less @@ -185,6 +185,12 @@ perspective-computed-column { color: #999999; } + &.datetime::before { + .column_row(); + content: "mdy"; + color: #999999; + } + &.date::before { .column_row(); content: "mdy"; @@ -252,7 +258,7 @@ perspective-computed-column { text-align: center; padding-top: 4px; - .date, + .datetime, .boolean { color: #999; } diff --git a/packages/perspective-viewer/src/less/default.less b/packages/perspective-viewer/src/less/default.less index 4424fcd8bf..2a5d3149cd 100644 --- a/packages/perspective-viewer/src/less/default.less +++ b/packages/perspective-viewer/src/less/default.less @@ -268,6 +268,12 @@ perspective-viewer { color: #999999; } + .datetime::before { + .column_row(); + content: "mdy"; + color: #999999; + } + .date::before { .column_row(); content: "mdy"; diff --git a/packages/perspective-viewer/src/less/material.less b/packages/perspective-viewer/src/less/material.less index ad0e2e0161..057a756940 100644 --- a/packages/perspective-viewer/src/less/material.less +++ b/packages/perspective-viewer/src/less/material.less @@ -154,7 +154,8 @@ perspective-viewer #app { &.string:before, &.float:before, &.integer:before, - &.date:before { + &.date:before, + &.datetime:before { font-family: @material-monospace-fonts; padding-left: 0; } @@ -190,6 +191,11 @@ perspective-viewer #app { content: "t/f"; } + #side_panel perspective-row .datetime::before { + .column_row(); + content: "mdy"; + } + #side_panel perspective-row .date::before { .column_row(); content: "mdy"; diff --git a/packages/perspective-viewer/src/less/row.less b/packages/perspective-viewer/src/less/row.less index a992294fff..e17402c35b 100644 --- a/packages/perspective-viewer/src/less/row.less +++ b/packages/perspective-viewer/src/less/row.less @@ -228,6 +228,11 @@ perspective-row { content: "mdy"; } +#side_panel perspective-row .datetime::before { + .column_row(); + content: "mdy"; +} + perspective-row #column_aggregate { display: none; font-size: 10px; diff --git a/packages/perspective-viewer/test/js/computed_column_tests.js b/packages/perspective-viewer/test/js/computed_column_tests.js index 22e1f7a47b..4b2897d0ab 100644 --- a/packages/perspective-viewer/test/js/computed_column_tests.js +++ b/packages/perspective-viewer/test/js/computed_column_tests.js @@ -13,7 +13,7 @@ const add_computed_column = async page => { await page.evaluate(element => element.setAttribute("columns", '["Row ID","Quantity"]'), viewer); await page.click("#add-computed-column"); await page.$eval("perspective-computed-column", element => { - const columns = [{name: "Order Date", type: "date"}]; + const columns = [{name: "Order Date", type: "datetime"}]; element._apply_state(columns, element.computations["day_of_week"], "new_cc"); }); await page.click("#psp-cc-button-save"); diff --git a/packages/perspective/src/cpp/main.cpp b/packages/perspective/src/cpp/main.cpp index 1cb486eede..dc331fe34f 100644 --- a/packages/perspective/src/cpp/main.cpp +++ b/packages/perspective/src/cpp/main.cpp @@ -106,9 +106,12 @@ _get_fterms(t_schema schema, val j_filters) { case DTYPE_BOOL: term = mktscalar(filter[2].as()); break; + case DTYPE_DATE: + term = mktscalar(t_date(filter[2].as())); + break; case DTYPE_TIME: - term = mktscalar( - t_time(static_cast(filter[2].as()))); + term = mktscalar(t_time(static_cast( + filter[2].call("getTime").as()))); break; default: { term @@ -315,12 +318,69 @@ _fill_col(val dcol, t_col_sptr col, t_bool is_arrow) { continue; } - auto elem = static_cast(dcol[i].as()); + auto elem = static_cast(dcol[i].call("getTime").as()); col->set_nth(i, elem); } } } +t_date +jsdate_to_t_date(val date) { + return t_date(date.call("getFullYear").as(), + date.call("getMonth").as(), date.call("getDate").as()); +} + +val +t_date_to_jsdate(t_date date) { + val jsdate = val::global("Date").new_(); + jsdate.call("setYear", date.year()); + jsdate.call("setMonth", date.month()); + jsdate.call("setDate", date.day()); + jsdate.call("setHours", 0); + jsdate.call("setMinutes", 0); + jsdate.call("setSeconds", 0); + jsdate.call("setMilliseconds", 0); + return jsdate; +} + +template <> +void +_fill_col(val dcol, t_col_sptr col, t_bool is_arrow) { + t_uindex nrows = col->size(); + + if (is_arrow) { + // val data = dcol["values"]; + // // arrow packs 64 bit into two 32 bit ints + // arrow::vecFromTypedArray(data, col->get_nth(0), nrows * 2); + + // t_int8 unit = dcol["type"]["unit"].as(); + // if (unit != /* Arrow.enum_.TimeUnit.MILLISECOND */ 1) { + // // Slow path - need to convert each value + // t_int64 factor = 1; + // if (unit == /* Arrow.enum_.TimeUnit.NANOSECOND */ 3) { + // factor = 1e6; + // } else if (unit == /* Arrow.enum_.TimeUnit.MICROSECOND */ 2) { + // factor = 1e3; + // } + // for (auto i = 0; i < nrows; ++i) { + // col->set_nth(i, *(col->get_nth(i)) / factor); + // } + // } + } else { + for (auto i = 0; i < nrows; ++i) { + if (dcol[i].isUndefined()) + continue; + + if (dcol[i].isNull()) { + col->unset(i); + continue; + } + + col->set_nth(i, jsdate_to_t_date(dcol[i])); + } + } +} + template <> void _fill_col(val dcol, t_col_sptr col, t_bool is_arrow) { @@ -456,6 +516,9 @@ _fill_data(t_table_sptr tbl, t_svec ocolnames, val j_data, std::vector case DTYPE_FLOAT64: { _fill_col(dcol, col, is_arrow); } break; + case DTYPE_DATE: { + _fill_col(dcol, col, is_arrow); + } break; case DTYPE_TIME: { _fill_col(dcol, col, is_arrow); } break; @@ -725,12 +788,13 @@ scalar_to_val(const t_tscalar scalar) { return val(false); } } + case DTYPE_TIME: case DTYPE_FLOAT64: case DTYPE_FLOAT32: { return val(scalar.to_double()); } - case DTYPE_TIME: { - return val(scalar.to_double()); + case DTYPE_DATE: { + return t_date_to_jsdate(scalar.get()).call("getTime"); } case DTYPE_UINT8: case DTYPE_UINT16: @@ -817,6 +881,10 @@ set_column_nth(t_column* col, t_uindex idx, val value) { col->set_nth(idx, elem, STATUS_VALID); break; } + case DTYPE_DATE: { + col->set_nth(idx, jsdate_to_t_date(value), STATUS_VALID); + break; + } case DTYPE_TIME: { col->set_nth( idx, static_cast(value.as()), STATUS_VALID); diff --git a/packages/perspective/src/js/date_parser.js b/packages/perspective/src/js/date_parser.js index a2ecf95de0..9531ca204f 100644 --- a/packages/perspective/src/js/date_parser.js +++ b/packages/perspective/src/js/date_parser.js @@ -37,7 +37,7 @@ export class DateParser { parse(input) { if (this.date_exclusions.indexOf(input) > -1) { - return -1; + return null; } else { let val = input; if (typeof val === "string") { @@ -48,14 +48,15 @@ export class DateParser { if (val.isValid()) { this.date_types.push(candidate); this.date_candidates.splice(this.date_candidates.indexOf(candidate), 1); - return +val; + return val.toDate(); } } this.date_exclusions.push(input); - return -1; + return null; } + return val.toDate(); } - return +val; + return val; } } } diff --git a/packages/perspective/src/js/defaults.js b/packages/perspective/src/js/defaults.js index 0cb2dae18b..0528276579 100644 --- a/packages/perspective/src/js/defaults.js +++ b/packages/perspective/src/js/defaults.js @@ -40,6 +40,7 @@ export const TYPE_AGGREGATES = { float: NUMBER_AGGREGATES, integer: NUMBER_AGGREGATES, boolean: BOOLEAN_AGGREGATES, + datetime: STRING_AGGREGATES, date: STRING_AGGREGATES }; @@ -48,6 +49,7 @@ export const AGGREGATE_DEFAULTS = { float: "sum", integer: "sum", boolean: "distinct count", + datetime: "distinct count", date: "distinct count" }; @@ -57,7 +59,7 @@ const NUMBER_FILTERS = ["<", ">", "==", "<=", ">=", "!=", "is nan", "is not nan" const STRING_FILTERS = ["==", "contains", "!=", "in", "begins with", "ends with"]; -const DATE_FILTERS = ["<", ">", "==", "<=", ">=", "!="]; +const DATETIME_FILTERS = ["<", ">", "==", "<=", ">=", "!="]; export const COLUMN_SEPARATOR_STRING = "|"; @@ -66,7 +68,8 @@ export const TYPE_FILTERS = { float: NUMBER_FILTERS, integer: NUMBER_FILTERS, boolean: BOOLEAN_FILTERS, - date: DATE_FILTERS + datetime: DATETIME_FILTERS, + date: DATETIME_FILTERS }; export const FILTER_DEFAULTS = { @@ -74,5 +77,6 @@ export const FILTER_DEFAULTS = { float: "==", integer: "==", boolean: "==", + datetime: "==", date: "==" }; diff --git a/packages/perspective/src/js/perspective.js b/packages/perspective/src/js/perspective.js index 2c81d6b66a..218489a887 100644 --- a/packages/perspective/src/js/perspective.js +++ b/packages/perspective/src/js/perspective.js @@ -53,7 +53,11 @@ module.exports = function(Module) { } else if (typeof x === "boolean") { t = __MODULE__.t_dtype.DTYPE_BOOL; } else if (x instanceof Date) { - t = __MODULE__.t_dtype.DTYPE_TIME; + if (x.getHours() === 0 && x.getMinutes() === 0 && x.getSeconds() === 0 && x.getMilliseconds() === 0) { + t = __MODULE__.t_dtype.DTYPE_DATE; + } else { + t = __MODULE__.t_dtype.DTYPE_TIME; + } } else if (!isNaN(Number(x)) && x !== "") { t = __MODULE__.t_dtype.DTYPE_FLOAT64; } else if (typeof x === "string" && is_valid_date(x)) { @@ -84,36 +88,12 @@ module.exports = function(Module) { } else if (val === 11) { return "boolean"; } else if (val === 12) { + return "datetime"; + } else if (val === 13) { return "date"; } } - /** - * Do any necessary data transforms on columns. Currently it does the following - * transforms - * 1. Date objects are converted into float millis since epoch - * 2. Null strings are converted into null values - * - * @private - * @param {string} type type of column - * @param {array} data array of columnar data - * - * @returns transformed array of columnar data - */ - function transform_data(type, data) { - let rv = []; - for (let x = 0; x < data.length; x++) { - let tmp = clean_data(data[x]); - - if (type == __MODULE__.t_dtype.DTYPE_TIME && tmp !== null) { - tmp = +data[x]; - } - - rv.push(tmp); - } - return rv; - } - /** * Coerce string null into value null * @private @@ -230,7 +210,7 @@ module.exports = function(Module) { } else { col.push(!!cell); } - } else if (inferredType.value === __MODULE__.t_dtype.DTYPE_TIME.value) { + } else if (inferredType.value === __MODULE__.t_dtype.DTYPE_TIME.value || inferredType.value === __MODULE__.t_dtype.DTYPE_DATE.value) { let val = clean_data(data[x][name]); if (val !== null) { col.push(parser.parse(val)); @@ -271,7 +251,7 @@ module.exports = function(Module) { // Extract the data or fill with undefined if column doesn't exist (nothing in column changed) let transformed; if (data.hasOwnProperty(name)) { - transformed = transform_data(types[col_num], data[name]); + transformed = data[name].map(clean_data); } else { transformed = new Array(row_count); } @@ -294,8 +274,10 @@ module.exports = function(Module) { types.push(__MODULE__.t_dtype.DTYPE_STR); } else if (data[name] === "boolean") { types.push(__MODULE__.t_dtype.DTYPE_BOOL); - } else if (data[name] === "date") { + } else if (data[name] === "datetime") { types.push(__MODULE__.t_dtype.DTYPE_TIME); + } else if (data[name] === "date") { + types.push(__MODULE__.t_dtype.DTYPE_DATE); } else { throw `Unknown type ${data[name]}`; } @@ -567,6 +549,8 @@ module.exports = function(Module) { } else if (types[col_name] === 11) { new_schema[col_name] = "boolean"; } else if (types[col_name] === 12) { + new_schema[col_name] = "datetime"; + } else if (types[col_name] === 13) { new_schema[col_name] = "date"; } if (this.sides() > 0 && this.config.row_pivot.length > 0) { @@ -956,6 +940,9 @@ module.exports = function(Module) { dtype = __MODULE__.t_dtype.DTYPE_BOOL; break; case "date": + dtype = __MODULE__.t_dtype.DTYPE_DATE; + break; + case "datetime": dtype = __MODULE__.t_dtype.DTYPE_TIME; break; case "string": @@ -1186,8 +1173,8 @@ module.exports = function(Module) { if (config.filter) { let schema = this._schema(); filters = config.filter.map(function(filter) { - if (schema[filter[0]] === "date") { - return [filter[0], _string_to_filter_op[filter[1]], +new DateParser().parse(filter[2])]; + if (schema[filter[0]] === "datetime" || schema[filter[0]] === "date") { + return [filter[0], _string_to_filter_op[filter[1]], new DateParser().parse(filter[2])]; } else { return [filter[0], _string_to_filter_op[filter[1]], filter[2]]; } @@ -1330,7 +1317,15 @@ module.exports = function(Module) { let types = schema.types(); if (data instanceof ArrayBuffer) { + if (this.size() === 0) { + throw new Error("Overriding Arrow Schema is not supported."); + } pdata = load_arrow_buffer(data, cols, types); + } else if (typeof data === "string") { + if (data[0] === ",") { + data = "_" + data; + } + pdata = [parse_data(papaparse.parse(data.trim(), {dynamicTyping: true, header: true}).data, cols, types)]; } else { pdata = [parse_data(data, cols, types)]; } diff --git a/packages/perspective/test/js/constructors.js b/packages/perspective/test/js/constructors.js index 37a9b30c4d..a970f747b4 100644 --- a/packages/perspective/test/js/constructors.js +++ b/packages/perspective/test/js/constructors.js @@ -119,11 +119,13 @@ var arrow_result = [ ]; var dt = new Date(); +dt.setHours(4); +dt.setMinutes(12); var data_4 = [{v: dt}]; var data_5 = [{v: "11-09-2017"}]; -var meta_4 = {v: "date"}; +var meta_4 = {v: "datetime"}; var csv = "x,y,z\n1,a,true\n2,b,false\n3,c,true\n4,d,false"; @@ -298,18 +300,30 @@ module.exports = perspective => { expect([{v: +data_4[0]["v"]}]).toEqual(result); }); - it("Handles date values", async function() { + it("Handles datetime values", async function() { var table = perspective.table(data_4); let result2 = await table.view({}).to_json(); expect([{v: +data_4[0]["v"]}]).toEqual(result2); }); - it("Handles date strings", async function() { + it("Handles datetime strings", async function() { var table = perspective.table(data_5); let result2 = await table.view({}).to_json(); expect([{v: +moment(data_5[0]["v"], "MM-DD-YYYY")}]).toEqual(result2); }); + it("Handles date values", async function() { + var table = perspective.table({v: "date"}); + table.update(data_4); + let result2 = await table.view({}).to_json(); + let d = new Date(data_4[0]["v"]); + d.setHours(0); + d.setMinutes(0); + d.setSeconds(0); + d.setMilliseconds(0); + expect([{v: +d}]).toEqual(result2); + }); + it("Handles utf16", async function() { var table = perspective.table(data_6); let result = await table.view({}).to_json();