diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bf7aa0..5624cce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,3 +19,8 @@ - Fix inserting fails silently when no value is provided for 'Seq' - Handle Nullable value more user friendly - Added insert data with geometry and update geometry function + +## [2024.1.0] - 2024-11-28 + +### Added +- Editing date and timestamp values \ No newline at end of file diff --git a/mikeplus/datatableaccess.py b/mikeplus/datatableaccess.py index c331b30..4c9fe88 100644 --- a/mikeplus/datatableaccess.py +++ b/mikeplus/datatableaccess.py @@ -14,6 +14,9 @@ from DHI.Amelia.DataModule.Services.DataTables import AmlUndoRedoManager from .dotnet import as_dotnet_list +from .dotnet import from_dotnet_datetime +from .dotnet import to_dotnet_datetime +from datetime import datetime class DataTableAccess: @@ -143,6 +146,8 @@ def get_field_values(self, table_name, muid, fields): if values[i] is not None: wkt = GeoAPIHelper.GetWKTIGeometry(values[i]) pyValues.append(wkt) + elif isinstance(values[i], System.DateTime): + pyValues.append(from_dotnet_datetime(values[i])) else: pyValues.append(values[i]) i += 1 @@ -182,6 +187,8 @@ def get_muid_field_values(self, table_name, fields, where=None): if val is not None: wkt = GeoAPIHelper.GetWKTIGeometry(val) mylist.append(wkt) + elif isinstance(val, System.DateTime): + mylist.append(from_dotnet_datetime(val)) else: mylist.append(val) mydict[feildVal.Key] = mylist @@ -222,6 +229,8 @@ def set_value(self, table_name, muid, column, value): geomTable = IMuGeomTable(self._datatables[table_name]) geomTable.UpdateGeomByCommand(muid, geom) else: + if isinstance(value, datetime): + value = to_dotnet_datetime(value) self._datatables[table_name].SetValueByCommand(muid, column, value) def set_values(self, table_name, muid, values): @@ -259,6 +268,8 @@ def set_values(self, table_name, muid, values): geom = GeoAPIHelper.GetIGeometryFromWKT(wkt) geomTable = IMuGeomTable(self._datatables[table_name]) geomTable.UpdateGeomByCommand(muid, geom) + elif isinstance(values[col], datetime): + value_dict[col] = to_dotnet_datetime(values[col]) else: value_dict[col] = values[col] self._datatables[table_name].SetValuesByCommand(muid, value_dict) @@ -300,6 +311,8 @@ def insert(self, table_name, muid, values=None): geom = GeoAPIHelper.GetIGeometryFromWKT(wkt) if isinstance(values[col], int): value_dict[col] = System.Nullable[int](values[col]) + elif isinstance(values[col], datetime): + value_dict[col] = to_dotnet_datetime(values[col]) else: value_dict[col] = values[col] result, new_muid = self._datatables[table_name].InsertByCommand( diff --git a/mikeplus/dotnet.py b/mikeplus/dotnet.py index f0caa1c..b40bccb 100644 --- a/mikeplus/dotnet.py +++ b/mikeplus/dotnet.py @@ -1,7 +1,9 @@ import clr # noqa: F401 +import System from System import String from System.Collections.Generic import List +import datetime def as_dotnet_list(py_list: list, dotnet_type=None): @@ -32,3 +34,33 @@ def as_dotnet_list(py_list: list, dotnet_type=None): for item in py_list: dotnet_list.Add(item) return dotnet_list + + +def to_dotnet_datetime(x): + """Convert from python datetime to .NET System.DateTime.""" + dotnet_datetime = System.DateTime( + x.year, x.month, x.day, x.hour, x.minute, x.second + ) + # Get .NET ticks microseconds + ticks = x.microsecond * 10 + dotnet_datetime = dotnet_datetime.AddTicks(ticks) + return dotnet_datetime + + +def from_dotnet_datetime(x, round_to_milliseconds=True): + """Convert from .NET System.DateTime to python datetime.""" + # Get microseconds from .NET ticks + microseconds = x.Ticks % 10**7 // 10 + time = datetime.datetime( + x.Year, x.Month, x.Day, x.Hour, x.Minute, x.Second, microseconds + ) + + # Round to milliseconds if requested + if round_to_milliseconds: + microseconds_rounded = round(time.microsecond, -3) + if microseconds_rounded == 10**6: + time += datetime.timedelta(seconds=1) + microseconds_rounded = 0 + time = time.replace(microsecond=microseconds_rounded) + + return time diff --git a/notebooks/DataTableAccessor.ipynb b/notebooks/DataTableAccessor.ipynb index e5c4769..66e1bb2 100644 --- a/notebooks/DataTableAccessor.ipynb +++ b/notebooks/DataTableAccessor.ipynb @@ -85,28 +85,17 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "a0dfb0ce", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "data_access.is_database_open()" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "cfabd6c4", "metadata": {}, "outputs": [], @@ -121,18 +110,10 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "28644d8f", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "['link_test']\n" - ] - } - ], + "outputs": [], "source": [ "query = data_access.get_muid_where(\"msm_Link\", \"MUID='link_test'\")\n", "print(query)" @@ -140,20 +121,10 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "6dff5333", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Diameter:2.0\n", - "Description:insertValues\n", - "geometry:LINESTRING (3 4, 10 50, 20 25)\n" - ] - } - ], + "outputs": [], "source": [ "fields = [\"Diameter\", \"Description\", \"geometry\"]\n", "values = data_access.get_field_values(\"msm_Link\", \"link_test\", fields)\n", @@ -162,20 +133,10 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "b12d1430", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Diameter:1.0\n", - "Description:setValues\n", - "geometry:LINESTRING (4 5, 20 60, 30 35)\n" - ] - } - ], + "outputs": [], "source": [ "values = {\n", " \"Diameter\": 1.0,\n", @@ -189,18 +150,10 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "fa039e4e", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "geometry:LINESTRING (5 6, 30 70, 40 45)\n" - ] - } - ], + "outputs": [], "source": [ "\"\"\"Update link geometry\"\"\"\n", "data_access.set_value(\n", @@ -213,7 +166,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "10797bb4", "metadata": {}, "outputs": [], @@ -223,29 +176,10 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "id": "f45ece86", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "node has been inserted, current data is:\n", - "GeomX:6.0\n", - "GeomY:10.0\n", - "geometry:POINT (6 10)\n", - "GeomX has been updated, current data is:\n", - "GeomX:8.0\n", - "GeomY:10.0\n", - "geometry:POINT (8 10)\n", - "geometry has been updated, current data is:\n", - "GeomX:8.0\n", - "GeomY:10.0\n", - "geometry:POINT (8 10)\n" - ] - } - ], + "outputs": [], "source": [ "\"\"\"Insert node with geometry of wkt format, and update node coordinate x and y\"\"\"\n", "values = {\"geometry\": \"POINT(6 10)\"}\n", @@ -266,21 +200,10 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "id": "d9fcd4f5", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "link has been inserted, current data is:\n", - "geometry:LINESTRING (0 0, 10 10)\n", - "link geometry has been updated, current data is:\n", - "geometry:LINESTRING (10 10, 20 20)\n" - ] - } - ], + "outputs": [], "source": [ "\"\"\"Insert line with geometry of shapely object, and update geometry with shapely object\"\"\"\n", "from shapely.geometry import LineString\n", @@ -297,12 +220,60 @@ "values = data_access.get_field_values(\"msm_Link\", \"link_shp_test\", fields)\n", "print(\"link geometry has been updated, current data is:\")\n", "print_field_values(fields, values)\n", - "data_access.delete(\"msm_Link\", \"link_shp_test\")\n" + "data_access.delete(\"msm_Link\", \"link_shp_test\")" ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, + "id": "aeb30396", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ComputationBegin:01-11-2023 01:00:00\n", + "ComputationEnd:01-11-2023 02:00:00\n" + ] + } + ], + "source": [ + "\"\"\"Change start time and end time for simulation\"\"\"\n", + "from datetime import datetime\n", + "\n", + "values = {\n", + " \"ComputationBegin\": datetime(2023, 11, 1, 0, 0, 0, 0),\n", + " \"ComputationEnd\": datetime(2023, 11, 1, 1, 0, 0, 0),\n", + "}\n", + "data_access.insert(\"msm_Project\", \"sim_test\", values)\n", + "fields = [\"ComputationBegin\", \"ComputationEnd\"]\n", + "values = data_access.get_field_values(\"msm_Project\", \"sim_test\", fields)\n", + "print_field_values(fields, values)\n", + "\n", + "values = {\n", + " \"ComputationBegin\": datetime(2023, 11, 1, 1, 0, 0, 0),\n", + " \"ComputationEnd\": datetime(2023, 11, 1, 2, 0, 0, 0),\n", + "}\n", + "data_access.set_values(\"msm_Project\", \"sim_test\", values)\n", + "values = data_access.get_field_values(\"msm_Project\", \"sim_test\", fields)\n", + "print_field_values(fields, values)\n", + "data_access.delete(\"msm_Project\", \"sim_test\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "c99513d8", + "metadata": {}, + "outputs": [], + "source": [ + "data_access.delete(\"msm_Project\", \"sim_test\")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, "id": "f31060ad", "metadata": {}, "outputs": [], diff --git a/tests/test_datatable.py b/tests/test_datatable.py index 0b99c07..15a8559 100644 --- a/tests/test_datatable.py +++ b/tests/test_datatable.py @@ -2,6 +2,7 @@ import os from mikeplus import DataTableDemoAccess +from datetime import datetime def test_opendatabase(): @@ -68,6 +69,24 @@ def test_manipulate_data(): data_access.delete("msm_Link", "link_test") muids = data_access.get_muid_where("msm_Link", "MUID='link_test'") assert len(muids) == 0 + field_values = { + "ComputationBegin": datetime(2023, 11, 1, 0, 0, 0, 0), + "ComputationEnd": datetime(2023, 11, 1, 1, 0, 0, 0), + } + data_access.insert("msm_Project", "sim_test", field_values) + fields = ["ComputationBegin", "ComputationEnd"] + values_get = data_access.get_field_values("msm_Project", "sim_test", fields) + assert values_get[0] == datetime(2023, 11, 1, 0, 0, 0, 0) + assert values_get[1] == datetime(2023, 11, 1, 1, 0, 0, 0) + field_values = { + "ComputationBegin": datetime(2023, 11, 1, 1, 0, 0, 0), + "ComputationEnd": datetime(2023, 11, 1, 2, 0, 0, 0), + } + data_access.set_values("msm_Project", "sim_test", field_values) + values_get = data_access.get_field_values("msm_Project", "sim_test", fields) + assert values_get[0] == datetime(2023, 11, 1, 1, 0, 0, 0) + assert values_get[1] == datetime(2023, 11, 1, 2, 0, 0, 0) + data_access.delete("msm_Project", "sim_test") data_access.close_database()