Skip to content

Commit e083aa8

Browse files
authored
Merge pull request #1074 from finos/datetime-fixes
Use local time for column/row headers and computed functions
2 parents 031c390 + 8a47108 commit e083aa8

File tree

19 files changed

+503
-72
lines changed

19 files changed

+503
-72
lines changed

cpp/perspective/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,21 +45,26 @@ function (psp_build_dep name cmake_file)
4545
message(WARNING "${Cyan}Dependency found - not rebuilding - ${CMAKE_BINARY_DIR}/${name}-build${ColorReset}")
4646
else()
4747
configure_file(${cmake_file} ${name}-download/CMakeLists.txt)
48+
4849
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
4950
RESULT_VARIABLE result
5051
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/${name}-download )
52+
5153
if(result)
5254
message(FATAL_ERROR "CMake step for ${name} failed: ${result}")
5355
endif()
56+
5457
execute_process(COMMAND ${CMAKE_COMMAND} --build .
5558
RESULT_VARIABLE result
5659
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/${name}-download )
60+
5761
if(result)
5862
message(FATAL_ERROR "Build step for ${name} failed: ${result}")
5963
endif()
6064
endif()
6165

6266
if(${name} STREQUAL arrow)
67+
# Overwrite arrow's CMakeLists with our custom, minimal one
6368
configure_file(${PSP_CMAKE_MODULE_PATH}/arrow/CMakeLists.txt ${CMAKE_BINARY_DIR}/arrow-src/cpp/ COPYONLY)
6469
configure_file(${PSP_CMAKE_MODULE_PATH}/arrow/config.h ${CMAKE_BINARY_DIR}/arrow-src/cpp/src/arrow/util/ COPYONLY)
6570
add_subdirectory(${CMAKE_BINARY_DIR}/arrow-src/cpp/
@@ -635,6 +640,7 @@ elseif(PSP_CPP_BUILD OR PSP_PYTHON_BUILD)
635640
endif()
636641

637642
target_link_libraries(psp tbb)
643+
638644
target_link_libraries(binding tbb)
639645

640646
target_link_libraries(binding psp)

cpp/perspective/src/cpp/computed.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,7 +1043,7 @@ t_computed_column::computed_functions = {
10431043
{"category", "FunctionTokenType"},
10441044
{"num_params", "1"},
10451045
{"format_function", "x => `hour_of_day(${x})`"},
1046-
{"help", "Returns the hour of day (0-23) in UTC for the datetime column."},
1046+
{"help", "Returns the hour of day (0-23) for the datetime column."},
10471047
{"signature", "hour_of_day(x: Datetime): Number"}
10481048
}},
10491049
{"day_of_week", {
@@ -1056,7 +1056,7 @@ t_computed_column::computed_functions = {
10561056
{"category", "FunctionTokenType"},
10571057
{"num_params", "1"},
10581058
{"format_function", "x => `day_of_week(${x})`"},
1059-
{"help", "Returns the day of week in UTC for the datetime column."},
1059+
{"help", "Returns the day of week for the datetime column."},
10601060
{"signature", "day_of_week(x: Datetime): String"}
10611061
}},
10621062
{"month_of_year", {
@@ -1069,7 +1069,7 @@ t_computed_column::computed_functions = {
10691069
{"category", "FunctionTokenType"},
10701070
{"num_params", "1"},
10711071
{"format_function", "x => `month_of_year(${x})`"},
1072-
{"help", "Returns the month of year in UTC for the datetime column."},
1072+
{"help", "Returns the month of year for the datetime column."},
10731073
{"signature", "month_of_year(x: Datetime): String"}
10741074
}},
10751075
{"second_bucket", {

cpp/perspective/src/cpp/computed_function.cpp

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -527,14 +527,13 @@ t_tscalar hour_of_day<DTYPE_TIME>(t_tscalar x) {
527527
// Convert the timestamp to a `sys_time` (alias for `time_point`)
528528
date::sys_time<std::chrono::milliseconds> ts(timestamp);
529529

530-
// Create a copy of the timestamp with day precision
531-
date::sys_days days = date::floor<date::days>(ts);
532-
533-
// Subtract the day-precision `time_point` from the datetime-precision one
534-
auto time_of_day = date::make_time(ts - days);
530+
// Use localtime so that the hour of day is consistent with all output
531+
// datetimes, which are in local time
532+
std::time_t temp = std::chrono::system_clock::to_time_t(ts);
533+
std::tm* t = std::localtime(&temp);
535534

536-
// Get the hour from the resulting `time_point`
537-
rval.set(static_cast<std::int64_t>(time_of_day.hours().count()));
535+
// Get the hour from the resulting `std::tm`
536+
rval.set(static_cast<std::int64_t>(t->tm_hour));
538537
return rval;
539538
}
540539

@@ -831,14 +830,14 @@ void day_of_week<DTYPE_TIME>(
831830
// Convert the timestamp to a `sys_time` (alias for `time_point`)
832831
date::sys_time<std::chrono::milliseconds> ts(timestamp);
833832

834-
// Create a copy of the timestamp with day precision
835-
auto days = date::floor<date::days>(ts);
836-
837-
// Find the weekday and write it to the output column
838-
auto weekday = date::year_month_weekday(days).weekday_indexed().weekday();
833+
// Use localtime so that the hour of day is consistent with all output
834+
// datetimes, which are in local time
835+
std::time_t temp = std::chrono::system_clock::to_time_t(ts);
836+
std::tm* t = std::localtime(&temp);
839837

838+
// Get the weekday from the resulting `std::tm`
840839
output_column->set_nth(
841-
idx, days_of_week[(weekday - date::Sunday).count()]);
840+
idx, days_of_week[t->tm_wday]);
842841
}
843842

844843
template <>
@@ -871,18 +870,16 @@ void month_of_year<DTYPE_TIME>(
871870
// Convert the timestamp to a `sys_time` (alias for `time_point`)
872871
date::sys_time<std::chrono::milliseconds> ts(timestamp);
873872

874-
// Create a copy of the timestamp with day precision
875-
auto days = date::floor<date::days>(ts);
876-
877-
// Cast the `time_point` to contain year/month/day
878-
auto ymd = date::year_month_day(days);
873+
// Use localtime so that the hour of day is consistent with all output
874+
// datetimes, which are in local time
875+
std::time_t temp = std::chrono::system_clock::to_time_t(ts);
876+
std::tm* t = std::localtime(&temp);
879877

880-
// Get the month as an integer from 0 to 11
881-
auto month = (ymd.month() - date::January).count();
878+
// Get the month from the resulting `std::tm`
879+
auto month = t->tm_mon;
882880

883881
// Get the month string and write into the output column
884-
std::string month_of_year = months_of_year[month];
885-
output_column->set_nth(idx, month_of_year);
882+
output_column->set_nth(idx, months_of_year[month]);
886883
}
887884

888885
} // end namespace computed_function

cpp/perspective/src/cpp/scalar.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -679,9 +679,23 @@ t_tscalar::to_string(bool for_expr) const {
679679
return ss.str();
680680
} break;
681681
case DTYPE_TIME: {
682+
// Convert a millisecond UTC timestamp to a formatted datestring in
683+
// local time, as all datetimes exported to the user happens in
684+
// local time and not UTC.
682685
std::chrono::milliseconds timestamp(to_int64());
683686
date::sys_time<std::chrono::milliseconds> ts(timestamp);
684-
ss << date::format("%Y-%m-%d %H:%M:%S", ts);
687+
std::time_t temp = std::chrono::system_clock::to_time_t(ts);
688+
std::tm* t = std::localtime(&temp);
689+
690+
// use a mix of std::put_time and date::format to properly
691+
// represent datetimes to millisecond precision
692+
ss << std::put_time(t, "%Y-%m-%d %H:%M:"); // ymd h:m
693+
694+
// TODO: we currently can't print out millisecond precision, but
695+
// we need to.
696+
ss << date::format("%S", ts); // represent second and millisecond
697+
ss << std::put_time(t, " %Z"); // timezone
698+
685699
return ss.str();
686700
} break;
687701
case DTYPE_STR: {

cpp/perspective/src/include/perspective/base.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <fstream>
3131
#include <boost/unordered_map.hpp>
3232
#include <perspective/portable.h>
33+
#include <stdlib.h>
3334

3435
namespace perspective {
3536

docs/i18n/en.json

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,6 @@
3535
"node_modules/react/README": {
3636
"title": "node_modules/react/README"
3737
},
38-
"obj/perspective-python": {
39-
"title": "perspective-python API"
40-
},
41-
"obj/perspective-viewer": {
42-
"title": "perspective-viewer API"
43-
},
44-
"obj/perspective": {
45-
"title": "perspective API"
46-
},
4738
"README": {
4839
"title": "README"
4940
}

docs/md/python.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -159,18 +159,18 @@ two methods into your object:
159159

160160
#### Time Zone Handling
161161

162-
When passing in `datetime` objects, Perspective checks the `tzinfo` attribute
163-
to see if a time zone is set. For more details, see this in-depth [explanation](https://github.com/finos/perspective/pull/867)
164-
of `perspective-python` semantics around time zone handling.
162+
Columns with the `datetime` type are stored internally as UTC timestamps in milliseconds since epoch (Unix Time),
163+
and are serialized to the user as `datetime.datetime` objects in _local time_ according to the Python runtime.
165164

166-
##### Naive Datetimes
165+
Both ["naive" and "aware" datetimes](https://docs.python.org/3/library/datetime.html#aware-and-naive-objects) will be
166+
serialized to local time by Perspective, with the conversion determined by the `tzinfo` attribute:
167167

168-
Objects with an unset `tzinfo` attribute (naive datetimes) are treated as _local time_, and do not undergo any time zone conversion.
168+
- "Naive" datetimes are assumed to be already in local time and are serialized as-is.
169+
- "Aware" datetimes will be converted to UTC from their original timezone, and then converted to local time
170+
from UTC.
169171

170-
##### Aware Datetimes
171-
172-
Objects with the `tzinfo` attribute set (aware datetimes) will be _converted into UTC_ before being stored in
173-
Perspective, and they will be _serialized as local time_.
172+
This behavior is consistent with Perspective's behavior in Javascript. For more details, see this
173+
in-depth [explanation](https://github.com/finos/perspective/pull/867) of `perspective-python` semantics around time zone handling.
174174

175175
##### Pandas Timestamps
176176

packages/perspective-viewer/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,19 +166,19 @@ Sets new computed columns for the viewer.
166166
**Params**
167167

168168
- computed-columns <code>[ &#x27;Array&#x27; ].&lt;Object&gt;</code> - An Array of computed column objects,
169-
which have three properties: `name`, a column name for the new column,
169+
which have three properties: `column`, a column name for the new column,
170170
`computed_function_name`, a String representing the computed function to
171171
apply, and `inputs`, an Array of String column names to be used as
172172
inputs to the computation.
173173

174174
**Example** *(via Javascript DOM)*
175175
```js
176176
let elem = document.getElementById('my_viewer');
177-
elem.setAttribute('computed-columns', JSON.stringify([{name: "x+y", computed_function_name: "+", inputs: ["x", "y"]}]));
177+
elem.setAttribute('computed-columns', JSON.stringify([{column: "x+y", computed_function_name: "+", inputs: ["x", "y"]}]));
178178
```
179179
**Example** *(via HTML)*
180180
```js
181-
<perspective-viewer computed-columns="[{name:'x+y',computed_function_name:'+',inputs:['x','y']}]""></perspective-viewer>
181+
<perspective-viewer computed-columns="[{column:'x+y',computed_function_name:'+',inputs:['x','y']}]""></perspective-viewer>
182182
```
183183
184184
* * *

packages/perspective-viewer/src/js/autocomplete_widget.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,7 @@ class PerspectiveAutocompleteWidget extends HTMLElement {
218218

219219
if (idx > -1 && children.length > 0) {
220220
children[idx].setAttribute("aria-selected", "true");
221-
children[idx].scrollIntoView(true, {
222-
behavior: "smooth",
221+
children[idx].scrollIntoView({
223222
block: "nearest"
224223
});
225224

packages/perspective-viewer/src/js/viewer.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,16 +182,16 @@ class PerspectiveViewer extends ActionElement {
182182
* @kind member
183183
* @type {Array<Object>}
184184
* @param {Array<Object>} computed-columns An Array of computed column objects,
185-
* which have three properties: `name`, a column name for the new column,
185+
* which have three properties: `column`, a column name for the new column,
186186
* `computed_function_name`, a String representing the computed function to
187187
* apply, and `inputs`, an Array of String column names to be used as
188188
* inputs to the computation.
189189
* @fires PerspectiveViewer#perspective-config-update
190190
* @example <caption>via Javascript DOM</caption>
191191
* let elem = document.getElementById('my_viewer');
192-
* elem.setAttribute('computed-columns', JSON.stringify([{name: "x+y", computed_function_name: "+", inputs: ["x", "y"]}]));
192+
* elem.setAttribute('computed-columns', JSON.stringify([{column: "x+y", computed_function_name: "+", inputs: ["x", "y"]}]));
193193
* @example <caption>via HTML</caption>
194-
* <perspective-viewer computed-columns="[{name:'x+y',computed_function_name:'+',inputs:['x','y']}]""></perspective-viewer>
194+
* <perspective-viewer computed-columns="[{column:'x+y',computed_function_name:'+',inputs:['x','y']}]""></perspective-viewer>
195195
*/
196196
@array_attribute
197197
"computed-columns"(computed_columns) {

0 commit comments

Comments
 (0)