Browse Source

temporal framework: Better datetime string parsing. Parsing of datetime strings is now done in a single place.

git-svn-id: https://svn.osgeo.org/grass/grass/trunk@71573 15284696-431f-4ddb-bdfa-cd5b030d7da7
Soeren Gebbert 7 years ago
parent
commit
e2188758f6

+ 4 - 8
lib/python/temporal/abstract_space_time_dataset.py

@@ -26,7 +26,7 @@ from .temporal_granularity import check_granularity_string, compute_absolute_tim
 from .spatio_temporal_relationships import count_temporal_topology_relationships, \
 from .spatio_temporal_relationships import count_temporal_topology_relationships, \
     print_spatio_temporal_topology_relationships, SpatioTemporalTopologyBuilder, \
     print_spatio_temporal_topology_relationships, SpatioTemporalTopologyBuilder, \
     create_temporal_relation_sql_where_statement
     create_temporal_relation_sql_where_statement
-from .datetime_math import increment_datetime_by_string
+from .datetime_math import increment_datetime_by_string, string_to_datetime
 
 
 ###############################################################################
 ###############################################################################
 
 
@@ -2377,13 +2377,9 @@ class AbstractSpaceTimeDataset(AbstractDataset):
                     tstring = row[0]
                     tstring = row[0]
                     # Convert the unicode string into the datetime format
                     # Convert the unicode string into the datetime format
                     if self.is_time_absolute():
                     if self.is_time_absolute():
-                        if tstring.find(":") > 0:
-                            time_format = "%Y-%m-%d %H:%M:%S"
-                        else:
-                            time_format = "%Y-%m-%d"
-
-                        max_start_time = datetime.strptime(
-                            tstring, time_format)
+                        max_start_time = string_to_datetime(tstring)
+                        if max_start_time is None:
+                            max_start_time = end_time
                     else:
                     else:
                         max_start_time = row[0]
                         max_start_time = row[0]
                 else:
                 else:

+ 5 - 1
lib/python/temporal/core.py

@@ -55,6 +55,7 @@ except:
     pass
     pass
 
 
 import atexit
 import atexit
+from datetime import datetime
 
 
 ###############################################################################
 ###############################################################################
 
 
@@ -1207,12 +1208,15 @@ class DBConnection(object):
                     elif isinstance(args[count], float):
                     elif isinstance(args[count], float):
                         statement = "%s%f%s" % (statement[0:pos], args[count],
                         statement = "%s%f%s" % (statement[0:pos], args[count],
                                                 statement[pos + 1:])
                                                 statement[pos + 1:])
+                    elif isinstance(args[count], datetime):
+                        statement = "%s\'%s\'%s" % (statement[0:pos], str(args[count]),
+                                                statement[pos + 1:])
                     else:
                     else:
                         # Default is a string, this works for datetime
                         # Default is a string, this works for datetime
                         # objects too
                         # objects too
                         statement = "%s\'%s\'%s" % (statement[0:pos],
                         statement = "%s\'%s\'%s" % (statement[0:pos],
                                                     str(args[count]),
                                                     str(args[count]),
-                                                    statement[pos + 1:])
+                                                    str(statement[pos + 1:]))
                     count += 1
                     count += 1
 
 
                 return statement
                 return statement

+ 65 - 9
lib/python/temporal/datetime_math.py

@@ -704,23 +704,65 @@ def compute_datetime_delta(start, end):
 ###############################################################################
 ###############################################################################
 
 
 
 
-def check_datetime_string(time_string):
-    """Check if  a string can be converted into a datetime object
+def check_datetime_string(time_string, use_dateutil=True):
+    """Check if  a string can be converted into a datetime object and return the object
 
 
-        Supported ISO string formats are:
+        In case datutil is not installed the supported ISO string formats are:
 
 
         - YYYY-mm-dd
         - YYYY-mm-dd
         - YYYY-mm-dd HH:MM:SS
         - YYYY-mm-dd HH:MM:SS
+        - YYYY-mm-ddTHH:MM:SS
+        - YYYY-mm-dd HH:MM:SS.s
+        - YYYY-mm-ddTHH:MM:SS.s
+
+        Time zones are not supported
 
 
+        If dateutil is installed, all string formats of the dateutil module
+        are supported, as well as time zones
         Time zones are not supported
         Time zones are not supported
 
 
         :param time_string: The time string to be checked for conversion
         :param time_string: The time string to be checked for conversion
+        :param use_dateutil: Use dateutil if available for datetime string parsing
         :return: datetime: object or an error message string in case of an error
         :return: datetime: object or an error message string in case of an error
+
+        >>> s = "2000-01-01"
+        >>> check_datetime_string(s)
+        datetime.datetime(2000, 1, 1, 0, 0)
+        >>> s = "2000-01-01T10:00:00"
+        >>> check_datetime_string(s)
+        datetime.datetime(2000, 1, 1, 10, 0)
+        >>> s = "2000-01-01 10:00:00"
+        >>> check_datetime_string(s)
+        datetime.datetime(2000, 1, 1, 10, 0)
+        >>> s = "2000-01-01T10:00:00.000001"
+        >>> check_datetime_string(s)
+        datetime.datetime(2000, 1, 1, 10, 0, 0, 1)
+        >>> s = "2000-01-01 10:00:00.000001"
+        >>> check_datetime_string(s)
+        datetime.datetime(2000, 1, 1, 10, 0, 0, 1)
+
+        # using native implementation, ignoring dateutil
+        >>> s = "2000-01-01"
+        >>> check_datetime_string(s, False)
+        datetime.datetime(2000, 1, 1, 0, 0)
+        >>> s = "2000-01-01T10:00:00"
+        >>> check_datetime_string(s, False)
+        datetime.datetime(2000, 1, 1, 10, 0)
+        >>> s = "2000-01-01 10:00:00"
+        >>> check_datetime_string(s, False)
+        datetime.datetime(2000, 1, 1, 10, 0)
+        >>> s = "2000-01-01T10:00:00.000001"
+        >>> check_datetime_string(s, False)
+        datetime.datetime(2000, 1, 1, 10, 0, 0, 1)
+        >>> s = "2000-01-01 10:00:00.000001"
+        >>> check_datetime_string(s, False)
+        datetime.datetime(2000, 1, 1, 10, 0, 0, 1)
+
     """
     """
 
 
     global has_dateutil
     global has_dateutil
 
 
-    if has_dateutil:
+    if has_dateutil and use_dateutil is True:
         # First check if there is only a single number, which specifies
         # First check if there is only a single number, which specifies
         # relative time. dateutil will interprete a single number as a valid
         # relative time. dateutil will interprete a single number as a valid
         # time string, so we have to catch this case beforehand
         # time string, so we have to catch this case beforehand
@@ -737,15 +779,25 @@ def check_datetime_string(time_string):
         return time_object
         return time_object
 
 
     # BC is not supported
     # BC is not supported
-    if time_string.find("bc") > 0:
+    if "bc" in time_string > 0:
         return _("Dates Before Christ (BC) are not supported")
         return _("Dates Before Christ (BC) are not supported")
 
 
     # BC is not supported
     # BC is not supported
-    if time_string.find("+") > 0:
+    if "+" in time_string:
         return _("Time zones are not supported")
         return _("Time zones are not supported")
 
 
-    if time_string.find(":") > 0:
-        time_format = "%Y-%m-%d %H:%M:%S"
+    if ":" in time_string or "T" in time_string:
+        # Check for microseconds
+        if "." in time_string:
+            if "T" in time_string:
+                time_format = "%Y-%m-%dT%H:%M:%S.%f"
+            else:
+                time_format = "%Y-%m-%d %H:%M:%S.%f"
+        else:
+            if "T" in time_string:
+                time_format = "%Y-%m-%dT%H:%M:%S"
+            else:
+                time_format = "%Y-%m-%d %H:%M:%S"
     else:
     else:
         time_format = "%Y-%m-%d"
         time_format = "%Y-%m-%d"
 
 
@@ -764,7 +816,11 @@ def string_to_datetime(time_string):
 
 
         - YYYY-mm-dd
         - YYYY-mm-dd
         - YYYY-mm-dd HH:MM:SS
         - YYYY-mm-dd HH:MM:SS
-        - Time zones are not supported
+        - YYYY-mm-ddTHH:MM:SS
+        - YYYY-mm-dd HH:MM:SS.s
+        - YYYY-mm-ddTHH:MM:SS.s
+
+        Time zones are not supported
 
 
         If dateutil is installed, all string formats of the dateutil module
         If dateutil is installed, all string formats of the dateutil module
         are supported, as well as time zones
         are supported, as well as time zones

+ 3 - 3
lib/python/temporal/space_time_datasets.py

@@ -119,7 +119,7 @@ class RasterDataset(AbstractMapDataset):
             >>> rmap.get_temporal_extent_as_tuple()
             >>> rmap.get_temporal_extent_as_tuple()
             (datetime.datetime(2001, 1, 1, 0, 0), datetime.datetime(2012, 1, 1, 0, 0))
             (datetime.datetime(2001, 1, 1, 0, 0), datetime.datetime(2012, 1, 1, 0, 0))
             >>> rmap.get_name()
             >>> rmap.get_name()
-            u'strds_map_test_case'
+            'strds_map_test_case'
             >>> rmap.get_mapset() == mapset
             >>> rmap.get_mapset() == mapset
             True
             True
             >>> rmap.get_temporal_type()
             >>> rmap.get_temporal_type()
@@ -452,7 +452,7 @@ class Raster3DDataset(AbstractMapDataset):
             >>> r3map.get_temporal_extent_as_tuple()
             >>> r3map.get_temporal_extent_as_tuple()
             (datetime.datetime(2001, 1, 1, 0, 0), datetime.datetime(2012, 1, 1, 0, 0))
             (datetime.datetime(2001, 1, 1, 0, 0), datetime.datetime(2012, 1, 1, 0, 0))
             >>> r3map.get_name()
             >>> r3map.get_name()
-            u'str3ds_map_test_case'
+            'str3ds_map_test_case'
             >>> r3map.get_mapset() == mapset
             >>> r3map.get_mapset() == mapset
             True
             True
             >>> r3map.get_temporal_type()
             >>> r3map.get_temporal_type()
@@ -797,7 +797,7 @@ class VectorDataset(AbstractMapDataset):
             >>> vmap.get_temporal_extent_as_tuple()
             >>> vmap.get_temporal_extent_as_tuple()
             (datetime.datetime(2001, 1, 1, 0, 0), datetime.datetime(2012, 1, 1, 0, 0))
             (datetime.datetime(2001, 1, 1, 0, 0), datetime.datetime(2012, 1, 1, 0, 0))
             >>> vmap.get_name()
             >>> vmap.get_name()
-            u'stvds_map_test_case'
+            'stvds_map_test_case'
             >>> vmap.get_mapset() == mapset
             >>> vmap.get_mapset() == mapset
             True
             True
             >>> vmap.get_temporal_type()
             >>> vmap.get_temporal_type()

+ 4 - 11
lib/python/temporal/temporal_algebra.py

@@ -463,7 +463,7 @@ from .factory import dataset_factory
 from .open_stds import open_new_stds, open_old_stds
 from .open_stds import open_new_stds, open_old_stds
 from .temporal_operator import TemporalOperatorParser
 from .temporal_operator import TemporalOperatorParser
 from spatio_temporal_relationships import SpatioTemporalTopologyBuilder
 from spatio_temporal_relationships import SpatioTemporalTopologyBuilder
-from .datetime_math import time_delta_to_relative_time
+from .datetime_math import time_delta_to_relative_time, string_to_datetime
 from abstract_space_time_dataset import AbstractSpaceTimeDataset
 from abstract_space_time_dataset import AbstractSpaceTimeDataset
 
 
 ##############################################################################
 ##############################################################################
@@ -1965,18 +1965,11 @@ class TemporalAlgebraParser(object):
             # Get value for function name from dictionary.
             # Get value for function name from dictionary.
             tfuncval = tfuncdict[tfunc]
             tfuncval = tfuncdict[tfunc]
             # Check if value has to be transferred to datetime object for comparison.
             # Check if value has to be transferred to datetime object for comparison.
-            if tfunc in ["START_DATE", "END_DATE"]:
-                timeobj = datetime.strptime(value.replace("\"",""), '%Y-%m-%d')
+            if tfunc in ["START_DATE", "END_DATE", "START_TIME", "END_TIME",
+                         "START_DATETIME", "END_DATETIME"]:
+                timeobj = string_to_datetime(value.replace("\"",""))
                 value = timeobj.date()
                 value = timeobj.date()
                 boolname = self.eval_datetime_str(tfuncval, comp_op, value)
                 boolname = self.eval_datetime_str(tfuncval, comp_op, value)
-            elif tfunc in ["START_TIME", "END_TIME"]:
-                timeobj = datetime.strptime(value.replace("\"",""), '%H:%M:%S')
-                value = timeobj.time()
-                boolname = self.eval_datetime_str(tfuncval, comp_op, value)
-            elif tfunc in ["START_DATETIME", "END_DATETIME"]:
-                timeobj = datetime.strptime(value.replace("\"",""), '%Y-%m-%d %H:%M:%S')
-                value = timeobj
-                boolname = self.eval_datetime_str(tfuncval, comp_op, value)
             else:
             else:
                 boolname = eval(str(tfuncval) + comp_op + str(value))
                 boolname = eval(str(tfuncval) + comp_op + str(value))
             # Add conditional boolean value to the map.
             # Add conditional boolean value to the map.

+ 1 - 2
lib/python/temporal/testsuite/test_doctests.py

@@ -24,11 +24,10 @@ def load_tests(loader, tests, ignore):
     tests.addTests(doctest.DocTestSuite(grass.temporal.abstract_map_dataset))
     tests.addTests(doctest.DocTestSuite(grass.temporal.abstract_map_dataset))
     tests.addTests(doctest.DocTestSuite(grass.temporal.abstract_space_time_dataset))
     tests.addTests(doctest.DocTestSuite(grass.temporal.abstract_space_time_dataset))
     tests.addTests(doctest.DocTestSuite(grass.temporal.base))
     tests.addTests(doctest.DocTestSuite(grass.temporal.base))
-    # Unexpected error here
     tests.addTests(doctest.DocTestSuite(grass.temporal.core))
     tests.addTests(doctest.DocTestSuite(grass.temporal.core))
     tests.addTests(doctest.DocTestSuite(grass.temporal.datetime_math))
     tests.addTests(doctest.DocTestSuite(grass.temporal.datetime_math))
     # Unexpected error here
     # Unexpected error here
-    ##tests.addTests(doctest.DocTestSuite(grass.temporal.list_stds))
+    #tests.addTests(doctest.DocTestSuite(grass.temporal.list_stds))
     tests.addTests(doctest.DocTestSuite(grass.temporal.metadata))
     tests.addTests(doctest.DocTestSuite(grass.temporal.metadata))
     tests.addTests(doctest.DocTestSuite(grass.temporal.register))
     tests.addTests(doctest.DocTestSuite(grass.temporal.register))
     tests.addTests(doctest.DocTestSuite(grass.temporal.space_time_datasets))
     tests.addTests(doctest.DocTestSuite(grass.temporal.space_time_datasets))