Browse Source

New functions to compute the temporal granularity from registered maps
for absolute and relative time.


git-svn-id: https://svn.osgeo.org/grass/grass/trunk@48845 15284696-431f-4ddb-bdfa-cd5b030d7da7

Soeren Gebbert 13 years ago
parent
commit
0007aa52e2

+ 1 - 1
lib/python/temporal/Makefile

@@ -8,7 +8,7 @@ PYDIR = $(ETC)/python
 GDIR = $(PYDIR)/grass
 GDIR = $(PYDIR)/grass
 DSTDIR = $(GDIR)/temporal
 DSTDIR = $(GDIR)/temporal
 
 
-MODULES = base core abstract_dataset abstract_map_dataset abstract_space_time_dataset space_time_datasets space_time_datasets_tools metadata spatial_extent temporal_extent datetime_math
+MODULES = base core abstract_dataset abstract_map_dataset abstract_space_time_dataset space_time_datasets space_time_datasets_tools metadata spatial_extent temporal_extent datetime_math temporal_granularity unit_tests
 
 
 PYFILES := $(patsubst %,$(DSTDIR)/%.py,$(MODULES) __init__)
 PYFILES := $(patsubst %,$(DSTDIR)/%.py,$(MODULES) __init__)
 PYCFILES := $(patsubst %,$(DSTDIR)/%.pyc,$(MODULES) __init__)
 PYCFILES := $(patsubst %,$(DSTDIR)/%.pyc,$(MODULES) __init__)

+ 2 - 0
lib/python/temporal/__init__.py

@@ -9,3 +9,5 @@ from abstract_space_time_dataset import *
 from space_time_datasets import *
 from space_time_datasets import *
 from space_time_datasets_tools import *
 from space_time_datasets_tools import *
 from datetime_math import *
 from datetime_math import *
+from temporal_granularity import *
+from unit_tests import *

+ 17 - 0
lib/python/temporal/abstract_dataset.py

@@ -49,6 +49,23 @@ class abstract_dataset(object):
     def get_id(self):
     def get_id(self):
         return self.base.get_id()
         return self.base.get_id()
 
 
+    def get_valid_time(self):
+        """Returns a tuple of the start, the end valid time, this can be either datetime or double values
+           @return A tuple of (start_time, end_time)
+        """
+
+        start = None
+        end = None
+               
+	if self.is_time_absolute():
+            start = self.absolute_time.get_start_time()
+            end = self.absolute_time.get_end_time()
+        if self.is_time_relative():
+            start = self.relative_time.get_start_time()
+            end = self.relative_time.get_end_time()
+        
+        return (start, end)
+ 
     def get_absolute_time(self):
     def get_absolute_time(self):
         """Returns a tuple of the start, the end valid time and the timezone of the map
         """Returns a tuple of the start, the end valid time and the timezone of the map
            @return A tuple of (start_time, end_time, timezone)
            @return A tuple of (start_time, end_time, timezone)

+ 7 - 0
lib/python/temporal/abstract_map_dataset.py

@@ -221,6 +221,8 @@ class abstract_map_dataset(abstract_dataset):
 	""" Remove the map entry in each space time dataset in which this map is registered
 	""" Remove the map entry in each space time dataset in which this map is registered
 
 
            @param dbif: The database interface to be used
            @param dbif: The database interface to be used
+           @param update: Call for each unregister statement the update_from_registered_maps 
+                          of the space time dataset. This can slow down the unregistration process significantly.
         """
         """
 
 
         core.verbose(_("Unregister %s dataset <%s> from space time datasets") % (self.get_type(), self.get_id()))
         core.verbose(_("Unregister %s dataset <%s> from space time datasets") % (self.get_type(), self.get_id()))
@@ -237,7 +239,11 @@ class abstract_map_dataset(abstract_dataset):
 
 
         # For each stds in which the map is registered
         # For each stds in which the map is registered
         if rows:
         if rows:
+            count = 0
+            num_sps = len(rows)
             for row in rows:
             for row in rows:
+                core.percent(count, num_sps, 1)
+                count += 1
                 # Create a space time dataset object to remove the map
                 # Create a space time dataset object to remove the map
                 # from its register
                 # from its register
                 stds = self.get_new_stds_instance(row["id"])
                 stds = self.get_new_stds_instance(row["id"])
@@ -248,6 +254,7 @@ class abstract_map_dataset(abstract_dataset):
                 if update == True:
                 if update == True:
                     stds.update_from_registered_maps(dbif)
                     stds.update_from_registered_maps(dbif)
 
 
+            core.percent(1, 1, 1)
         dbif.connection.commit()
         dbif.connection.commit()
 
 
         if connect == True:
         if connect == True:

+ 49 - 15
lib/python/temporal/abstract_space_time_dataset.py

@@ -20,6 +20,7 @@ for details.
 @author Soeren Gebbert
 @author Soeren Gebbert
 """
 """
 from abstract_dataset import *
 from abstract_dataset import *
+from temporal_granularity import *
 
 
 ###############################################################################
 ###############################################################################
 
 
@@ -65,13 +66,10 @@ class abstract_space_time_dataset(abstract_dataset):
         """
         """
         raise IOError("This method must be implemented in the subclasses")
         raise IOError("This method must be implemented in the subclasses")
 
 
-    def set_initial_values(self, granularity, temporal_type, semantic_type, \
+    def set_initial_values(self, temporal_type, semantic_type, \
                            title=None, description=None):
                            title=None, description=None):
         """Set the initial values of the space time dataset
         """Set the initial values of the space time dataset
 
 
-           @param granularity: The temporal granularity of this dataset. This value
-                               should be computed by the space time dataset itself,
-                               based on the granularity of the registered maps
            @param temporal_type: The temporal type of this space time dataset (absolute or relative)
            @param temporal_type: The temporal type of this space time dataset (absolute or relative)
            @param semantic_type: The semantic type of this dataset
            @param semantic_type: The semantic type of this dataset
            @param title: The title
            @param title: The title
@@ -80,10 +78,8 @@ class abstract_space_time_dataset(abstract_dataset):
 
 
         if temporal_type == "absolute":
         if temporal_type == "absolute":
             self.set_time_to_absolute()
             self.set_time_to_absolute()
-            self.absolute_time.set_granularity(granularity)
         elif temporal_type == "relative":
         elif temporal_type == "relative":
             self.set_time_to_relative()
             self.set_time_to_relative()
-            self.relative_time.set_granularity(granularity)
         else:
         else:
             core.fatal(_("Unknown temporal type \"%s\"") % (temporal_type))
             core.fatal(_("Unknown temporal type \"%s\"") % (temporal_type))
 
 
@@ -92,7 +88,17 @@ class abstract_space_time_dataset(abstract_dataset):
         self.metadata.set_description(description)
         self.metadata.set_description(description)
 
 
     def get_initial_values(self):
     def get_initial_values(self):
-        """Return the initial values: granularity, temporal_type, semantic_type, title, description"""
+        """Return the initial values: temporal_type, semantic_type, title, description"""
+        
+        temporal_type = self.get_temporal_type()
+        semantic_type = self.base.get_semantic_type()
+        title = self.metadata.get_title()
+        description = self.metadata.get_description()
+
+        return temporal_type, semantic_type, title, description
+
+    def get_granularity(self):
+        """Return the granularity"""
         
         
         temporal_type = self.get_temporal_type()
         temporal_type = self.get_temporal_type()
 
 
@@ -101,11 +107,20 @@ class abstract_space_time_dataset(abstract_dataset):
         elif temporal_type == "relative":
         elif temporal_type == "relative":
             granularity = self.relative_time.get_granularity()
             granularity = self.relative_time.get_granularity()
 
 
-        semantic_type = self.base.get_semantic_type()
-        title = self.metadata.get_title()
-        description = self.metadata.get_description()
+        return granularity
+
+    def set_granularity(self, granularity):
 
 
-        return granularity, temporal_type, semantic_type, title, description
+        temporal_type = self.get_temporal_type()
+ 
+        if temporal_type == "absolute":
+            self.set_time_to_absolute()
+            self.absolute_time.set_granularity(granularity)
+        elif temporal_type == "relative":
+            self.set_time_to_relative()
+            self.relative_time.set_granularity(granularity)
+        else:
+            core.fatal(_("Unknown temporal type \"%s\"") % (temporal_type))
 
 
     def get_map_time(self):
     def get_map_time(self):
         """Return the type of the map time, interval, point, maixed or invalid"""
         """Return the type of the map time, interval, point, maixed or invalid"""
@@ -443,10 +458,15 @@ class abstract_space_time_dataset(abstract_dataset):
             rows = self.get_registered_maps("id", None, None, dbif)
             rows = self.get_registered_maps("id", None, None, dbif)
             # Unregister each registered map in the table
             # Unregister each registered map in the table
             if rows:
             if rows:
+                num_maps = len(rows)
+                count = 0
                 for row in rows:
                 for row in rows:
+	            core.percent(count, num_maps, 1)
                     # Unregister map
                     # Unregister map
                     map = self.get_new_map_instance(row["id"])
                     map = self.get_new_map_instance(row["id"])
                     self.unregister_map(map, dbif)
                     self.unregister_map(map, dbif)
+                    count += 1
+	        core.percent(1, 1, 1)
             try:
             try:
                 # Drop the map register table
                 # Drop the map register table
                 sql = "DROP TABLE " + self.get_map_register()
                 sql = "DROP TABLE " + self.get_map_register()
@@ -836,7 +856,8 @@ class abstract_space_time_dataset(abstract_dataset):
 		dbif.cursor.execute(sql)
 		dbif.cursor.execute(sql)
 
 
         # Count the temporal map types
         # Count the temporal map types
-        tlist = self.count_temporal_types(self.get_registered_maps_as_objects(dbif=dbif))
+        maps = self.get_registered_maps_as_objects(dbif=dbif)
+        tlist = self.count_temporal_types(maps)
 
 
         if tlist["interval"] > 0 and tlist["point"] == 0 and tlist["invalid"] == 0:
         if tlist["interval"] > 0 and tlist["point"] == 0 and tlist["invalid"] == 0:
             map_time = "interval"
             map_time = "interval"
@@ -847,25 +868,38 @@ class abstract_space_time_dataset(abstract_dataset):
         else:
         else:
             map_time = "invalid"
             map_time = "invalid"
 
 
-        # Set the map time type
+        # Compute the granularity
+
+        if map_time != "invalid":
+            # Smallest supported temporal resolution
+            if self.is_time_absolute():
+                gran = compute_absolute_time_granularity(maps)
+            elif self.is_time_relative():
+                gran = compute_absolute_time_granularity(maps)
+        else:
+            gran = None
+
+        # Set the map time type and update the time objects
         if self.is_time_absolute():
         if self.is_time_absolute():
             self.absolute_time.select(dbif)
             self.absolute_time.select(dbif)
             self.metadata.select(dbif)
             self.metadata.select(dbif)
             if self.metadata.get_number_of_maps() > 0:
             if self.metadata.get_number_of_maps() > 0:
                 self.absolute_time.set_map_time(map_time)
                 self.absolute_time.set_map_time(map_time)
+                self.absolute_time.set_granularity(gran)
             else:
             else:
                 self.absolute_time.set_map_time(None)
                 self.absolute_time.set_map_time(None)
+                self.absolute_time.set_granularity(None)
             self.absolute_time.update_all(dbif)
             self.absolute_time.update_all(dbif)
         else:
         else:
             self.relative_time.select(dbif)
             self.relative_time.select(dbif)
             self.metadata.select(dbif)
             self.metadata.select(dbif)
             if self.metadata.get_number_of_maps() > 0:
             if self.metadata.get_number_of_maps() > 0:
                 self.relative_time.set_map_time(map_time)
                 self.relative_time.set_map_time(map_time)
+                self.relative_time.set_granularity(gran)
             else:
             else:
                 self.relative_time.set_map_time(None)
                 self.relative_time.set_map_time(None)
+                self.relative_time.set_granularity(None)
             self.relative_time.update_all(dbif)
             self.relative_time.update_all(dbif)
 
 
-        # TODO: Compute the granularity of the dataset and update the database entry
-
         if connect == True:
         if connect == True:
             dbif.close()
             dbif.close()

+ 2 - 0
lib/python/temporal/base.py

@@ -219,6 +219,8 @@ class sql_database_interface(dict_sql_serializer):
 	    self.connection.row_factory = dbmi.Row
 	    self.connection.row_factory = dbmi.Row
             self.connection.isolation_level = None
             self.connection.isolation_level = None
 	    self.cursor = self.connection.cursor()
 	    self.cursor = self.connection.cursor()
+            self.cursor.execute("PRAGMA synchronous = OFF")
+            self.cursor.execute("PRAGMA journal_mode = MEMORY")
         elif dbmi.__name__ == "psycopg2":
         elif dbmi.__name__ == "psycopg2":
 	    self.connection = dbmi.connect(self.database)
 	    self.connection = dbmi.connect(self.database)
 	    self.connection.set_isolation_level(dbmi.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
 	    self.connection.set_isolation_level(dbmi.extensions.ISOLATION_LEVEL_AUTOCOMMIT)

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

@@ -27,11 +27,13 @@ import grass.script.core as core
 
 
 ###############################################################################
 ###############################################################################
 
 
-def get_temporal_dbmi_default_path(grassenv):
+def get_temporal_sqlite3_default_path(grassenv):
     dbpath = os.path.join(grassenv["GISDBASE"], grassenv["LOCATION_NAME"])
     dbpath = os.path.join(grassenv["GISDBASE"], grassenv["LOCATION_NAME"])
     dbpath = os.path.join(dbpath, "PERMANENT")
     dbpath = os.path.join(dbpath, "PERMANENT")
-    return os.path.join(dbpath, "grass.db")
+    return dbpath
 
 
+def get_temporal_sqlite3_default_dbname(grassenv):
+    return "tgis.db"
 
 
 # The chosen DBMI backend can be defined on runtime
 # The chosen DBMI backend can be defined on runtime
 # Check the grass environment before import
 # Check the grass environment before import
@@ -48,8 +50,9 @@ if grassenv.has_key("TDBMI"):
 else:
 else:
     # Use the default sqlite variable
     # Use the default sqlite variable
     import sqlite3 as dbmi
     import sqlite3 as dbmi
-    core.run_command("g.gisenv", set="TDBMI=sqlite3")
-    core.run_command("g.gisenv", set="TDBMI_INIT=%s" % get_temporal_dbmi_default_path(grassenv))
+    # We do not set the path due to issues with the grass build system
+    #core.run_command("g.gisenv", set="TDBMI=sqlite3")
+    #core.run_command("g.gisenv", set="TDBMI_INIT=%s" % get_temporal_sqlite3_default_dbname(grassenv))
 
 
 ###############################################################################
 ###############################################################################
 
 
@@ -57,9 +60,16 @@ def get_temporal_dbmi_init_string():
     grassenv = core.gisenv()
     grassenv = core.gisenv()
     if dbmi.__name__ == "sqlite3":
     if dbmi.__name__ == "sqlite3":
         if grassenv.has_key("TDBMI_INIT"):
         if grassenv.has_key("TDBMI_INIT"):
+
+            string =  grassenv["TDBMI_INIT"]
+
+            # In case no path is present in the sqlite3 database string, we attach the default path
+            if string.find("/") < 0 or string.find("\\") < 0:
+                return os.path.join(get_temporal_sqlite3_default_path(grassenv), string)
+            
             return grassenv["TDBMI_INIT"]
             return grassenv["TDBMI_INIT"]
         else:
         else:
-            return get_temporal_dbmi_default_path(grassenv)
+            return os.path.join(get_temporal_sqlite3_default_path(grassenv), get_temporal_sqlite3_default_dbname(grassenv))
     elif dbmi.__name__ == "psycopg2":
     elif dbmi.__name__ == "psycopg2":
         if grassenv.has_key("TDBMI_INIT"):
         if grassenv.has_key("TDBMI_INIT"):
             return grassenv["TDBMI_INIT"]
             return grassenv["TDBMI_INIT"]

+ 102 - 193
lib/python/temporal/datetime_math.py

@@ -24,14 +24,27 @@ from datetime import datetime, date, time, timedelta
 import grass.script.core as core
 import grass.script.core as core
 import copy
 import copy
 
 
+
 ###############################################################################
 ###############################################################################
 
 
-def datetime_delta_to_double(dt1, dt2):
-    """Compute the the dfference dt2 - dt1 and convert the time delta into a 
-       double value, representing days.
+def relative_time_to_time_delta(value):
+    """Convert the double value representing days
+       into a timedelta object.
     """
     """
 
 
-    delta = dt2 - dt1
+
+    days = int(value)
+    seconds = value % 1
+    seconds = round(seconds * 86400)
+
+    return timedelta(days, seconds)
+
+###############################################################################
+
+def time_delta_to_relative_time(delta):
+    """Convert the time delta into a 
+       double value, representing days.
+    """
 
 
     return float(delta.days) + float(delta.seconds/86400.0)
     return float(delta.days) + float(delta.seconds/86400.0)
 
 
@@ -90,74 +103,6 @@ def increment_datetime_by_string(mydate, increment, mult = 1):
 
 
 ###############################################################################
 ###############################################################################
 
 
-def test_increment_datetime_by_string():
-
-    # First test
-    print "# Test 1"
-    dt = datetime(2001, 9, 1, 0, 0, 0)
-    string = "60 seconds, 4 minutes, 12 hours, 10 days, 1 weeks, 5 months, 1 years"
-
-    dt1 = datetime(2003,2,18,12,5,0)
-    dt2 = increment_datetime_by_string(dt, string)
-
-    print dt
-    print dt2
-
-    delta = dt1 -dt2
-
-    if delta.days != 0 or delta.seconds != 0:
-        core.error("increment computation is wrong %s" % (delta))
-
-    # Second test
-    print "# Test 2"
-    dt = datetime(2001, 11, 1, 0, 0, 0)
-    string = "1 months"
-
-    dt1 = datetime(2001,12,1)
-    dt2 = increment_datetime_by_string(dt, string)
-
-    print dt
-    print dt2
-
-    delta = dt1 -dt2
-
-    if delta.days != 0 or delta.seconds != 0:
-        core.error("increment computation is wrong %s" % (delta))
-
-    # Third test
-    print "# Test 3"
-    dt = datetime(2001, 11, 1, 0, 0, 0)
-    string = "13 months"
-
-    dt1 = datetime(2002,12,1)
-    dt2 = increment_datetime_by_string(dt, string)
-
-    print dt
-    print dt2
-
-    delta = dt1 -dt2
-
-    if delta.days != 0 or delta.seconds != 0:
-        core.error("increment computation is wrong %s" % (delta))
-
-    # 4. test
-    print "# Test 4"
-    dt = datetime(2001, 1, 1, 0, 0, 0)
-    string = "72 months"
-
-    dt1 = datetime(2007,1,1)
-    dt2 = increment_datetime_by_string(dt, string)
-
-    print dt
-    print dt2
-
-    delta = dt1 -dt2
-
-    if delta.days != 0 or delta.seconds != 0:
-        core.error("increment computation is wrong %s" % (delta))
-
-###############################################################################
-
 def increment_datetime(mydate, years=0, months=0, weeks=0, days=0, hours=0, minutes=0, seconds=0):
 def increment_datetime(mydate, years=0, months=0, weeks=0, days=0, hours=0, minutes=0, seconds=0):
     """Return a new datetime object incremented with the provided relative dates and times"""
     """Return a new datetime object incremented with the provided relative dates and times"""
 
 
@@ -199,7 +144,6 @@ def increment_datetime(mydate, years=0, months=0, weeks=0, days=0, hours=0, minu
     return mydate + tdelta_seconds + tdelta_minutes + tdelta_hours + \
     return mydate + tdelta_seconds + tdelta_minutes + tdelta_hours + \
                     tdelta_days + tdelta_weeks + tdelta_months + tdelta_years
                     tdelta_days + tdelta_weeks + tdelta_months + tdelta_years
 
 
-
 ###############################################################################
 ###############################################################################
 
 
 def adjust_datetime_to_granularity(mydate, granularity):
 def adjust_datetime_to_granularity(mydate, granularity):
@@ -282,126 +226,91 @@ def adjust_datetime_to_granularity(mydate, granularity):
 
 
         return result
         return result
 
 
+###############################################################################
 
 
-def test_adjust_datetime_to_granularity():
-
-    # First test
-    print "Test 1"
-    dt = datetime(2001, 8, 8, 12,30,30)
-    result = adjust_datetime_to_granularity(dt, "5 seconds")
-    correct =  datetime(2001, 8, 8, 12,30,30)
-
-    delta = correct - result 
-
-    if delta.days != 0 or delta.seconds != 0:
-        core.error("Granularity adjustment computation is wrong %s" % (delta))
-
-    # Second test
-    print "Test 2"
-    result = adjust_datetime_to_granularity(dt, "20 minutes")
-    correct =  datetime(2001, 8, 8, 12,30,00)
-
-    delta = correct - result 
-
-    if delta.days != 0 or delta.seconds != 0:
-        core.error("Granularity adjustment computation is wrong %s" % (delta))
-
-    # Third test
-    print "Test 2"
-    result = adjust_datetime_to_granularity(dt, "20 minutes")
-    correct =  datetime(2001, 8, 8, 12,30,00)
-
-    delta = correct - result 
-
-    if delta.days != 0 or delta.seconds != 0:
-        core.error("Granularity adjustment computation is wrong %s" % (delta))
-
-    # 4. test
-    print "Test 4"
-    result = adjust_datetime_to_granularity(dt, "3 hours")
-    correct =  datetime(2001, 8, 8, 12,00,00)
-
-    delta = correct - result 
-
-    if delta.days != 0 or delta.seconds != 0:
-        core.error("Granularity adjustment computation is wrong %s" % (delta))
-
-    # 5. test
-    print "Test 5"
-    result = adjust_datetime_to_granularity(dt, "5 days")
-    correct =  datetime(2001, 8, 8, 00,00,00)
-
-    delta = correct - result 
-
-    if delta.days != 0 or delta.seconds != 0:
-        core.error("Granularity adjustment computation is wrong %s" % (delta))
-
-    # 6. test
-    print "Test 6"
-    result = adjust_datetime_to_granularity(dt, "2 weeks")
-    correct =  datetime(2001, 8, 6, 00,00,00)
-
-    delta = correct - result 
-
-    if delta.days != 0 or delta.seconds != 0:
-        core.error("Granularity adjustment computation is wrong %s" % (delta))
-
-    # 7. test
-    print "Test 7"
-    result = adjust_datetime_to_granularity(dt, "6 months")
-    correct =  datetime(2001, 8, 1, 00,00,00)
-
-    delta = correct - result 
-
-    if delta.days != 0 or delta.seconds != 0:
-        core.error("Granularity adjustment computation is wrong %s" % (delta))
-
-    # 8. test
-    print "Test 8"
-    result = adjust_datetime_to_granularity(dt, "2 years")
-    correct =  datetime(2001, 1, 1, 00,00,00)
-
-    delta = correct - result 
-
-    if delta.days != 0 or delta.seconds != 0:
-        core.error("Granularity adjustment computation is wrong %s" % (delta))
-
-    # 9. test
-    print "Test 9"
-    result = adjust_datetime_to_granularity(dt, "2 years, 3 months, 5 days, 3 hours, 3 minutes, 2 seconds")
-    correct =  datetime(2001, 8, 8, 12,30,30)
-
-    delta = correct - result 
-
-    if delta.days != 0 or delta.seconds != 0:
-        core.error("Granularity adjustment computation is wrong %s" % (delta))
-
-    # 10. test
-    print "Test 10"
-    result = adjust_datetime_to_granularity(dt, "3 months, 5 days, 3 minutes")
-    correct =  datetime(2001, 8, 8, 12,30,00)
-
-    delta = correct - result 
-
-    if delta.days != 0 or delta.seconds != 0:
-        core.error("Granularity adjustment computation is wrong %s" % (delta))
-
-    # 11. test
-    print "Test 11"
-    result = adjust_datetime_to_granularity(dt, "3 weeks, 5 days")
-    correct =  datetime(2001, 8, 8, 00,00,00)
-
-    delta = correct - result 
-
-    if delta.days != 0 or delta.seconds != 0:
-        core.error("Granularity adjustment computation is wrong %s" % (delta))
-
-
-
-
-
-
-
-
+def compute_datetime_delta(start, end):
+    """Return a dictionary with the accumulated delta in year, month, day, hour, minute and second
+    
+       @return A dictionary with year, month, day, hour, minute and second as keys()
+    """
+    comp = {}
+
+    day_diff = (end - start).days
+
+    comp["max_days"] = day_diff
+
+    # Date
+    # Count full years
+    d = end.year - start.year
+    comp["year"] = d
+
+    # Count full months
+    if start.month == 1 and end.month == 1:
+        comp["month"] = 0
+    elif   start.day == 1 and end.day == 1:
+        d = end.month - start.month
+        if d < 0:
+            d = d + 12 * comp["year"]
+        elif d == 0:
+            d = 12 * comp["year"]
+        comp["month"] = d
+
+    # Count full days
+    if  start.day == 1 and end.day == 1:
+        comp["day"] = 0
+    else:
+        comp["day"] = day_diff
+
+    # Time
+    # Hours
+    if start.hour == 0 and end.hour == 0:
+        comp["hour"] = 0
+    else:
+        d = end.hour - start.hour
+        if d < 0:
+            d = d + 24  + 24 * day_diff
+        else:
+            d = d + 24 * day_diff
+        comp["hour"] = d
+    
+    # Minutes
+    if start.minute == 0 and end.minute == 0:
+        comp["minute"] = 0
+    else:
+        d = end.minute - start.minute
+        if d != 0:
+            if comp["hour"]:
+                d = d + 60 * comp["hour"]
+            else:
+                d = d + 24 * 60 * day_diff
+        elif d == 0:
+            if comp["hour"]:
+                d = 60* comp["hour"]
+            else:
+                d = 24 * 60 * day_diff
+
+        comp["minute"] = d
+
+    # Seconds
+    if start.second == 0 and end.second == 0:
+        comp["second"] = 0
+    else:
+        d = end.second - start.second
+        if d != 0:
+            if comp["minute"]:
+                d = d + 60* comp["minute"]
+            elif comp["hour"]:
+                d = d + 3600* comp["hour"]
+            else:
+                d = d + 24 * 60 * 60 * day_diff
+        elif d == 0:
+            if comp["minute"]:
+                d = 60* comp["minute"]
+            elif comp["hour"]:
+                d = 3600 * comp["hour"]
+            else:
+                d = 24 * 60 * 60 * day_diff
+        comp["second"] = d
 
 
+    return comp
 
 

+ 229 - 0
lib/python/temporal/temporal_granularity.py

@@ -0,0 +1,229 @@
+"""!@package grass.temporal
+
+@brief GRASS Python scripting module (temporal GIS functions)
+
+Temporal GIS related functions to be used in temporal GIS Python library package.
+
+Usage:
+
+@code
+import grass.temporal as tgis
+
+tgis.compute_relative_time_granularity(maps)
+...
+@endcode
+
+(C) 2008-2011 by the GRASS Development Team
+This program is free software under the GNU General Public
+License (>=v2). Read the file COPYING that comes with GRASS
+for details.
+
+@author Soeren Gebbert
+"""
+from abstract_dataset import *
+from datetime_math import *
+
+###############################################################################
+
+def compute_relative_time_granularity(maps):            
+    granularity = None
+
+    delta = []
+    # First we compute the timedelta of the intervals
+    for map in maps:
+        start, end = map.get_valid_time()
+        if start and end:
+            t =  relative_time_to_time_delta(abs(end - start))
+            full_seconds = t.days * 86400 + t.seconds
+            delta.append(full_seconds)
+
+    # Compute the timedelta of the gaps
+    for i in range(len(maps)):
+        if i < len(maps) - 1:
+            relation = maps[i + 1].temporal_relation(maps[i])
+            if relation == "after":
+                start1, end1 = maps[i].get_valid_time()
+                start2, end2 = maps[i + 1].get_valid_time()
+                # Gaps are between intervals, intervals and points, points and points
+                if end1 and start2:
+                    t =  relative_time_to_time_delta(abs(end1 - start2))
+                    full_seconds = t.days * 86400 + t.seconds
+                    delta.append(full_seconds)
+                if  not end1 and start2:
+                    t =  relative_time_to_time_delta(abs(start1 - start2))
+                    full_seconds = t.days * 86400 + t.seconds
+                    delta.append(full_seconds)
+
+    delta.sort()
+    ulist = list(set(delta))
+    if len(ulist) > 1:
+        # Find greatest common divisor
+        granularity = gcd_list(ulist)
+    else:
+        granularity = ulist[0]
+
+    return float(granularity / 86400.0)
+
+###############################################################################
+
+def compute_absolute_time_granularity(maps):            
+
+    has_seconds = False
+    has_minutes = False
+    has_hours = False
+    has_days = False
+    has_months = False
+    has_years = False
+
+    use_seconds = False
+    use_minutes = False
+    use_hours = False
+    use_days = False
+    use_months = False
+    use_years = False
+
+    delta = []
+    datetime_delta = []
+    # First we compute the timedelta of the intervals
+    for map in maps:
+        start, end = map.get_valid_time()
+        if start and end:
+            delta.append(end - start)
+            datetime_delta.append(compute_datetime_delta(start, end))
+
+    # Compute the timedelta of the gaps
+    for i in range(len(maps)):
+        if i < len(maps) - 1:
+            relation = maps[i + 1].temporal_relation(maps[i])
+            if relation == "after":
+                start1, end1 = maps[i].get_valid_time()
+                start2, end2 = maps[i + 1].get_valid_time()
+                # Gaps are between intervals, intervals and points, points and points
+                if end1 and start2:
+                    delta.append(end1 - start2)
+                    datetime_delta.append(compute_datetime_delta(end1, start2))
+                if  not end1 and start2:
+                    delta.append(start2 - start1)
+                    datetime_delta.append(compute_datetime_delta(start1, start2))
+
+    # Check what changed
+    dlist = []
+    for d in datetime_delta:
+        if d.has_key("second") and d["second"] > 0:
+            has_seconds = True
+        if d.has_key("minute") and d["minute"] > 0:
+            has_minutes = True
+        if d.has_key("hour") and d["hour"] > 0:
+            has_hours = True
+        if d.has_key("day") and d["day"] > 0:
+            has_days = True
+        if d.has_key("month") and d["month"] > 0:
+            has_months = True
+        if d.has_key("year") and d["year"] > 0:
+            has_years = True
+
+    # Create a list with a single time unit only
+    if has_seconds:
+        for d in datetime_delta:
+            if d.has_key("second"):
+                dlist.append(d["second"])   
+            elif d.has_key("minute"):
+                dlist.append(d["minute"] * 60)   
+            elif d.has_key("hour"):
+                dlist.append(d["hour"] * 3600)   
+            elif d.has_key("day"):
+                dlist.append(d["day"] * 24 * 3600)   
+            else:
+                dlist.append(d["max_days"] * 24 * 3600)   
+        use_seconds = True        
+    elif has_minutes:
+        for d in datetime_delta:
+            if d.has_key("minute"):
+                dlist.append(d["minute"])   
+            elif d.has_key("hour"):
+                dlist.append(d["hour"] * 60)   
+            elif d.has_key("day"):
+                dlist.append(d["day"] * 24 * 60)   
+            else:
+                dlist.append(d["max_days"] * 24 * 60)   
+        use_minutes = True        
+    elif has_hours:
+        for d in datetime_delta:
+            if d.has_key("hour"):
+                dlist.append(d["hour"])   
+            elif d.has_key("day"):
+                dlist.append(d["day"] * 24)   
+            else:
+                dlist.append(d["max_days"] * 24)   
+        use_hours = True        
+    elif has_days:
+        for d in datetime_delta:
+            if d.has_key("day"):
+                dlist.append(d["day"])   
+            else:
+                dlist.append(d["max_days"])   
+        use_days = True        
+    elif has_months:
+        for d in datetime_delta:
+            if d.has_key("month"):
+                dlist.append(d["month"])   
+            elif d.has_key("year"):
+                dlist.append(d["year"] * 12)   
+        use_months = True        
+    elif has_years:
+        for d in datetime_delta:
+            if d.has_key("year"):
+                dlist.append(d["year"])   
+        use_years = True        
+
+    dlist.sort()
+    ulist = list(set(dlist))
+
+    if len(ulist) == 0:
+        return None
+
+    if len(ulist) > 1:
+        # Find greatest common divisor
+        granularity = gcd_list(ulist)
+    else:
+        granularity = ulist[0]
+
+    if use_seconds:
+        return "%i seconds" % granularity
+    elif use_minutes:
+        return "%i minutes" % granularity
+    elif use_hours:
+        return "%i hours" % granularity
+    elif use_days:
+        return "%i days" % granularity
+    elif use_months:
+        return "%i months" % granularity
+    elif use_years:
+        return "%i years" % granularity
+
+    return None
+
+###############################################################################
+# http://akiscode.com/articles/gcd_of_a_list.shtml
+# Copyright (c) 2010 Stephen Akiki
+# MIT License (Means you can do whatever you want with this)
+#  See http://www.opensource.org/licenses/mit-license.php
+# Error Codes:
+#   None
+def gcd(a,b):
+	""" The Euclidean Algorithm """
+	a = abs(a)
+	b = abs(b)
+        while a:
+                a, b = b%a, a
+        return b
+        
+###############################################################################
+
+def gcd_list(list):
+	""" Finds the GCD of numbers in a list.
+	Input: List of numbers you want to find the GCD of
+		E.g. [8, 24, 12]
+	Returns: GCD of all numbers
+	"""
+	return reduce(gcd, list)

+ 974 - 0
lib/python/temporal/unit_tests.py

@@ -0,0 +1,974 @@
+"""!@package grass.temporal
+
+@brief GRASS Python scripting module (temporal GIS functions)
+
+Temporal GIS unit tests
+
+Usage:
+
+@code
+import grass.temporal as tgis
+
+tgis.test_increment_datetime_by_string()
+...
+@endcode
+
+(C) 2008-2011 by the GRASS Development Team
+This program is free software under the GNU General Public
+License (>=v2). Read the file COPYING that comes with GRASS
+for details.
+
+@author Soeren Gebbert
+"""
+from datetime import datetime, date, time, timedelta
+import grass.script.core as core
+from temporal_granularity import *
+from datetime_math import *
+from space_time_datasets import *
+
+###############################################################################
+
+def test_increment_datetime_by_string():
+
+    # First test
+    print "# Test 1"
+    dt = datetime(2001, 9, 1, 0, 0, 0)
+    string = "60 seconds, 4 minutes, 12 hours, 10 days, 1 weeks, 5 months, 1 years"
+
+    dt1 = datetime(2003,2,18,12,5,0)
+    dt2 = increment_datetime_by_string(dt, string)
+
+    print dt
+    print dt2
+
+    delta = dt1 -dt2
+
+    if delta.days != 0 or delta.seconds != 0:
+        core.error("increment computation is wrong %s" % (delta))
+
+    # Second test
+    print "# Test 2"
+    dt = datetime(2001, 11, 1, 0, 0, 0)
+    string = "1 months"
+
+    dt1 = datetime(2001,12,1)
+    dt2 = increment_datetime_by_string(dt, string)
+
+    print dt
+    print dt2
+
+    delta = dt1 -dt2
+
+    if delta.days != 0 or delta.seconds != 0:
+        core.error("increment computation is wrong %s" % (delta))
+
+    # Third test
+    print "# Test 3"
+    dt = datetime(2001, 11, 1, 0, 0, 0)
+    string = "13 months"
+
+    dt1 = datetime(2002,12,1)
+    dt2 = increment_datetime_by_string(dt, string)
+
+    print dt
+    print dt2
+
+    delta = dt1 -dt2
+
+    if delta.days != 0 or delta.seconds != 0:
+        core.error("increment computation is wrong %s" % (delta))
+
+    # 4. test
+    print "# Test 4"
+    dt = datetime(2001, 1, 1, 0, 0, 0)
+    string = "72 months"
+
+    dt1 = datetime(2007,1,1)
+    dt2 = increment_datetime_by_string(dt, string)
+
+    print dt
+    print dt2
+
+    delta = dt1 -dt2
+
+    if delta.days != 0 or delta.seconds != 0:
+        core.error("increment computation is wrong %s" % (delta))
+
+###############################################################################
+
+def test_adjust_datetime_to_granularity():
+
+    # First test
+    print "Test 1"
+    dt = datetime(2001, 8, 8, 12,30,30)
+    result = adjust_datetime_to_granularity(dt, "5 seconds")
+    correct =  datetime(2001, 8, 8, 12,30,30)
+
+    delta = correct - result 
+
+    if delta.days != 0 or delta.seconds != 0:
+        core.error("Granularity adjustment computation is wrong %s" % (delta))
+
+    # Second test
+    print "Test 2"
+    result = adjust_datetime_to_granularity(dt, "20 minutes")
+    correct =  datetime(2001, 8, 8, 12,30,00)
+
+    delta = correct - result 
+
+    if delta.days != 0 or delta.seconds != 0:
+        core.error("Granularity adjustment computation is wrong %s" % (delta))
+
+    # Third test
+    print "Test 2"
+    result = adjust_datetime_to_granularity(dt, "20 minutes")
+    correct =  datetime(2001, 8, 8, 12,30,00)
+
+    delta = correct - result 
+
+    if delta.days != 0 or delta.seconds != 0:
+        core.error("Granularity adjustment computation is wrong %s" % (delta))
+
+    # 4. test
+    print "Test 4"
+    result = adjust_datetime_to_granularity(dt, "3 hours")
+    correct =  datetime(2001, 8, 8, 12,00,00)
+
+    delta = correct - result 
+
+    if delta.days != 0 or delta.seconds != 0:
+        core.error("Granularity adjustment computation is wrong %s" % (delta))
+
+    # 5. test
+    print "Test 5"
+    result = adjust_datetime_to_granularity(dt, "5 days")
+    correct =  datetime(2001, 8, 8, 00,00,00)
+
+    delta = correct - result 
+
+    if delta.days != 0 or delta.seconds != 0:
+        core.error("Granularity adjustment computation is wrong %s" % (delta))
+
+    # 6. test
+    print "Test 6"
+    result = adjust_datetime_to_granularity(dt, "2 weeks")
+    correct =  datetime(2001, 8, 6, 00,00,00)
+
+    delta = correct - result 
+
+    if delta.days != 0 or delta.seconds != 0:
+        core.error("Granularity adjustment computation is wrong %s" % (delta))
+
+    # 7. test
+    print "Test 7"
+    result = adjust_datetime_to_granularity(dt, "6 months")
+    correct =  datetime(2001, 8, 1, 00,00,00)
+
+    delta = correct - result 
+
+    if delta.days != 0 or delta.seconds != 0:
+        core.error("Granularity adjustment computation is wrong %s" % (delta))
+
+    # 8. test
+    print "Test 8"
+    result = adjust_datetime_to_granularity(dt, "2 years")
+    correct =  datetime(2001, 1, 1, 00,00,00)
+
+    delta = correct - result 
+
+    if delta.days != 0 or delta.seconds != 0:
+        core.error("Granularity adjustment computation is wrong %s" % (delta))
+
+    # 9. test
+    print "Test 9"
+    result = adjust_datetime_to_granularity(dt, "2 years, 3 months, 5 days, 3 hours, 3 minutes, 2 seconds")
+    correct =  datetime(2001, 8, 8, 12,30,30)
+
+    delta = correct - result 
+
+    if delta.days != 0 or delta.seconds != 0:
+        core.error("Granularity adjustment computation is wrong %s" % (delta))
+
+    # 10. test
+    print "Test 10"
+    result = adjust_datetime_to_granularity(dt, "3 months, 5 days, 3 minutes")
+    correct =  datetime(2001, 8, 8, 12,30,00)
+
+    delta = correct - result 
+
+    if delta.days != 0 or delta.seconds != 0:
+        core.error("Granularity adjustment computation is wrong %s" % (delta))
+
+    # 11. test
+    print "Test 11"
+    result = adjust_datetime_to_granularity(dt, "3 weeks, 5 days")
+    correct =  datetime(2001, 8, 8, 00,00,00)
+
+    delta = correct - result 
+
+    if delta.days != 0 or delta.seconds != 0:
+        core.error("Granularity adjustment computation is wrong %s" % (delta))
+
+###############################################################################
+
+def test_compute_datetime_delta():
+
+    print "Test 1"
+    start = datetime(2001, 1, 1, 00,00,00)
+    end = datetime(2001, 1, 1, 00,00,00)
+
+    comp = compute_datetime_delta(start, end)
+
+    result = comp["second"]
+    correct = 0
+
+    delta = correct - result 
+
+    if delta != 0:
+        core.error("Compute datetime delta is wrong %s" % (delta))
+
+    print "Test 2"
+    start = datetime(2001, 1, 1, 00,00,14)
+    end = datetime(2001, 1, 1, 00,00,44)
+
+    comp = compute_datetime_delta(start, end)
+
+    result = comp["second"]
+    correct = 30
+
+    delta = correct - result 
+
+    if delta != 0:
+        core.error("Compute datetime delta is wrong %s" % (delta))
+
+    print "Test 3"
+    start = datetime(2001, 1, 1, 00,00,44)
+    end = datetime(2001, 1, 1, 00,01,14)
+
+    comp = compute_datetime_delta(start, end)
+
+    result = comp["second"]
+    correct = 30
+
+    delta = correct - result 
+
+    if delta != 0:
+        core.error("Compute datetime delta is wrong %s" % (delta))
+
+    print "Test 4"
+    start = datetime(2001, 1, 1, 00,00,30)
+    end = datetime(2001, 1, 1, 00,05,30)
+
+    comp = compute_datetime_delta(start, end)
+
+    result = comp["second"]
+    correct = 300
+
+    delta = correct - result 
+
+    if delta != 0:
+        core.error("Compute datetime delta is wrong %s" % (delta))
+
+    print "Test 5"
+    start = datetime(2001, 1, 1, 00,00,00)
+    end = datetime(2001, 1, 1, 00,01,00)
+
+    comp = compute_datetime_delta(start, end)
+
+    result = comp["minute"]
+    correct = 1
+
+    delta = correct - result 
+
+    if delta != 0:
+        core.error("Compute datetime delta is wrong %s" % (delta))
+
+    print "Test 6"
+    start = datetime(2011,10,31, 00,45,00)
+    end = datetime(2011,10,31, 01,45,00)
+
+    comp = compute_datetime_delta(start, end)
+
+    result = comp["minute"]
+    correct = 60
+
+    delta = correct - result 
+
+    if delta != 0:
+        core.error("Compute datetime delta is wrong %s" % (delta))
+
+    print "Test 7"
+    start = datetime(2011,10,31, 00,45,00)
+    end = datetime(2011,10,31, 01,15,00)
+
+    comp = compute_datetime_delta(start, end)
+
+    result = comp["minute"]
+    correct = 30
+
+    delta = correct - result 
+
+    if delta != 0:
+        core.error("Compute datetime delta is wrong %s" % (delta))
+
+    print "Test 8"
+    start = datetime(2011,10,31, 00,45,00)
+    end = datetime(2011,10,31, 12,15,00)
+
+    comp = compute_datetime_delta(start, end)
+
+    result = comp["minute"]
+    correct = 690
+
+    delta = correct - result 
+
+    if delta != 0:
+        core.error("Compute datetime delta is wrong %s" % (delta))
+
+    print "Test 9"
+    start = datetime(2011,10,31, 00,00,00)
+    end = datetime(2011,10,31, 01,00,00)
+
+    comp = compute_datetime_delta(start, end)
+
+    result = comp["hour"]
+    correct = 1
+
+    delta = correct - result 
+
+    if delta != 0:
+        core.error("Compute datetime delta is wrong %s" % (delta))
+
+    print "Test 10"
+    start = datetime(2011,10,31, 00,00,00)
+    end = datetime(2011,11,01, 01,00,00)
+
+    comp = compute_datetime_delta(start, end)
+
+    result = comp["hour"]
+    correct = 25
+
+    delta = correct - result 
+
+    if delta != 0:
+        core.error("Compute datetime delta is wrong %s" % (delta))
+
+    print "Test 11"
+    start = datetime(2011,10,31, 12,00,00)
+    end = datetime(2011,11,01, 06,00,00)
+
+    comp = compute_datetime_delta(start, end)
+
+    result = comp["hour"]
+    correct = 18
+
+    delta = correct - result 
+
+    if delta != 0:
+        core.error("Compute datetime delta is wrong %s" % (delta))
+
+    print "Test 12"
+    start = datetime(2011,11,01, 00,00,00)
+    end = datetime(2011,12,01, 01,00,00)
+
+    comp = compute_datetime_delta(start, end)
+
+    result = comp["hour"]
+    correct = 30 * 24 + 1
+
+    delta = correct - result 
+
+    if delta != 0:
+        core.error("Compute datetime delta is wrong %s" % (delta))
+
+
+    print "Test 13"
+    start = datetime(2011,11,01, 00,00,00)
+    end = datetime(2011,11,05, 00,00,00)
+
+    comp = compute_datetime_delta(start, end)
+
+    result = comp["day"]
+    correct = 4
+
+    delta = correct - result 
+
+    if delta != 0:
+        core.error("Compute datetime delta is wrong %s" % (delta))
+
+    print "Test 14"
+    start = datetime(2011,10,06, 00,00,00)
+    end = datetime(2011,11,05, 00,00,00)
+
+    comp = compute_datetime_delta(start, end)
+
+    result = comp["day"]
+    correct = 30
+
+    delta = correct - result 
+
+    if delta != 0:
+        core.error("Compute datetime delta is wrong %s" % (delta))
+
+    print "Test 15"
+    start = datetime(2011,12,02, 00,00,00)
+    end = datetime(2012,01,01, 00,00,00)
+
+    comp = compute_datetime_delta(start, end)
+
+    result = comp["day"]
+    correct = 30
+
+    delta = correct - result 
+
+    if delta != 0:
+        core.error("Compute datetime delta is wrong %s" % (delta))
+
+    print "Test 16"
+    start = datetime(2011,01,01, 00,00,00)
+    end = datetime(2011,02,01, 00,00,00)
+
+    comp = compute_datetime_delta(start, end)
+
+    result = comp["month"]
+    correct = 1
+
+    delta = correct - result 
+
+    if delta != 0:
+        core.error("Compute datetime delta is wrong %s" % (delta))
+
+    print "Test 17"
+    start = datetime(2011,12,01, 00,00,00)
+    end = datetime(2012,01,01, 00,00,00)
+
+    comp = compute_datetime_delta(start, end)
+
+    result = comp["month"]
+    correct = 1
+
+    delta = correct - result 
+
+    if delta != 0:
+        core.error("Compute datetime delta is wrong %s" % (delta))
+
+    print "Test 18"
+    start = datetime(2011,12,01, 00,00,00)
+    end = datetime(2012,06,01, 00,00,00)
+
+    comp = compute_datetime_delta(start, end)
+
+    result = comp["month"]
+    correct = 6
+
+    delta = correct - result 
+
+    if delta != 0:
+        core.error("Compute datetime delta is wrong %s" % (delta))
+
+    print "Test 19"
+    start = datetime(2011,06,01, 00,00,00)
+    end = datetime(2021,06,01, 00,00,00)
+
+    comp = compute_datetime_delta(start, end)
+
+    result = comp["year"]
+    correct = 10
+
+    delta = correct - result 
+
+    if delta != 0:
+        core.error("Compute datetime delta is wrong %s" % (delta))
+
+    print "Test 20"
+    start = datetime(2011,06,01, 00,00,00)
+    end = datetime(2012,06,01, 12,00,00)
+
+    comp = compute_datetime_delta(start, end)
+
+    result = comp["hour"]
+    d = end - start
+    correct = 12 + d.days * 24
+
+    delta = correct - result 
+
+    if delta != 0:
+        core.error("Compute datetime delta is wrong %s" % (delta))
+
+    print "Test 21"
+    start = datetime(2011,06,01, 00,00,00)
+    end = datetime(2012,06,01, 12,30,00)
+
+    comp = compute_datetime_delta(start, end)
+
+    result = comp["minute"]
+    d = end - start
+    correct = d.days * 24 * 60 + 12 * 60 + 30
+
+    delta = correct - result 
+
+    if delta != 0:
+        core.error("Compute datetime delta is wrong %s" % (delta))
+
+    print "Test 22"
+    start = datetime(2011,06,01, 00,00,00)
+    end = datetime(2012,06,01, 12,00,05)
+
+    comp = compute_datetime_delta(start, end)
+
+    result = comp["second"]
+    d = end - start
+    correct = 5 + 60 * 60 * 12 + d.days * 24 * 60 * 60
+
+    delta = correct - result 
+
+    if delta != 0:
+        core.error("Compute datetime delta is wrong %s" % (delta))
+
+    print "Test 23"
+    start = datetime(2011,06,01, 00,00,00)
+    end = datetime(2012,06,01, 00,30,00)
+
+    comp = compute_datetime_delta(start, end)
+
+    result = comp["minute"]
+    d = end - start
+    correct = 30 + d.days * 24 * 60
+
+    delta = correct - result 
+
+    if delta != 0:
+        core.error("Compute datetime delta is wrong %s" % (delta))
+
+    print "Test 24"
+    start = datetime(2011,06,01, 00,00,00)
+    end = datetime(2012,06,01, 00,00,05)
+
+    comp = compute_datetime_delta(start, end)
+
+    result = comp["second"]
+    d = end - start
+    correct = 5 + d.days * 24 * 60 * 60
+
+    delta = correct - result 
+
+    if delta != 0:
+        core.error("Compute datetime delta is wrong %s" % (delta))
+
+###############################################################################
+
+def test_compute_relative_time_granularity():            
+    
+
+    # First we test intervals
+
+    print "Test 1"
+    maps = []
+    fact = 5
+    start = 1
+    end = start * fact
+    for i in range(6):
+        end = start * fact
+        map = raster_dataset(None)
+        map.set_relative_time(start, end)
+        maps.append(map)
+        start = end
+
+    fact = fact - 1
+    gran = round(compute_relative_time_granularity(maps))
+    if fact - gran != 0:
+        core.error("Wrong granularity reference %i != gran %i" % (fact, gran))
+ 
+    print "Test 2"
+    maps = []
+    fact = 3
+    start = 1.0/86400
+    end = start * fact
+    for i in range(10):
+        end = start * fact
+        map = raster_dataset(None)
+        map.set_relative_time(start, end)
+        maps.append(map)
+        start = end
+
+    fact = fact - 1
+    gran = round(compute_relative_time_granularity(maps) * 86400)
+    if fact - gran != 0:
+        core.error("Wrong granularity reference %i != gran %i" % (fact, gran))
+
+    print "Test 3 with gaps"
+    maps = []
+    fact = 3
+    start = 1
+    end = start + fact
+    for i in range(10):
+        shift = i*2*fact
+        start = shift
+        end = start + fact
+
+        map = raster_dataset(None)
+        map.set_relative_time(start, end)
+        maps.append(map)
+
+    gran = round(compute_relative_time_granularity(maps))
+    if fact - gran != 0:
+        core.error("Wrong granularity reference %i != gran %i" % (fact, gran))
+
+    # Second we test intervals and points mixed
+
+    print "Test 4 intervals and points"
+    maps = []
+    fact = 5
+    start = 1
+    end = start * fact
+    count = 0
+    for i in range(6):
+        end = start * fact
+        map = raster_dataset(None)
+        if count % 2 == 0:
+            map.set_relative_time(start, end)
+        else:
+            map.set_relative_time(start, None)
+        maps.append(map)
+        start = end
+        count += 1
+
+    fact = fact - 1
+    gran = round(compute_relative_time_granularity(maps))
+    if fact - gran != 0:
+        core.error("Wrong granularity reference %i != gran %i" % (fact, gran))
+ 
+    # Second we test points only
+ 
+    print "Test 5 points only"
+    maps = []
+    fact = 3
+    start = 1.0/86400
+    for i in range(10):
+        point = (i + 1)*fact*start
+        map = raster_dataset(None)
+        map.set_relative_time(point, None)
+        maps.append(map)
+
+    gran = round(compute_relative_time_granularity(maps) * 86400)
+    if fact - gran != 0:
+        core.error("Wrong granularity reference %i != gran %i" % (fact, gran))
+
+###############################################################################
+
+def test_compute_absolute_time_granularity():
+ 
+
+    # First we test intervals
+
+    print "Test 1"
+    maps = []
+    a = datetime(2001, 1, 1)
+    increment = "1 years"
+    for i in range(10):
+        start = increment_datetime_by_string(a, increment, i)
+        end = increment_datetime_by_string(a, increment, i + 1)
+        map = raster_dataset(None)
+        map.set_absolute_time(start, end)
+        maps.append(map)
+
+    gran = compute_absolute_time_granularity(maps)
+    if increment != gran:
+        core.error("Wrong granularity reference %s != gran %s" % (increment, gran))
+
+    print "Test 2"
+    maps = []
+    a = datetime(2001, 1, 1)
+    increment = "3 years"
+    for i in range(10):
+        start = increment_datetime_by_string(a, increment, i)
+        end = increment_datetime_by_string(a, increment, i + 1)
+        map = raster_dataset(None)
+        map.set_absolute_time(start, end)
+        maps.append(map)
+
+    gran = compute_absolute_time_granularity(maps)
+    if increment != gran:
+        core.error("Wrong granularity reference %s != gran %s" % (increment, gran))
+
+    print "Test 3"
+    maps = []
+    a = datetime(2001, 5, 1)
+    increment = "1 months"
+    for i in range(20):
+        start = increment_datetime_by_string(a, increment, i)
+        end = increment_datetime_by_string(a, increment, i + 1)
+        map = raster_dataset(None)
+        map.set_absolute_time(start, end)
+        maps.append(map)
+
+    gran = compute_absolute_time_granularity(maps)
+    if increment != gran:
+        core.error("Wrong granularity reference %s != gran %s" % (increment, gran))
+
+    print "Test 4"
+    maps = []
+    a = datetime(2001, 1, 1)
+    increment = "3 months"
+    for i in range(20):
+        start = increment_datetime_by_string(a, increment, i)
+        end = increment_datetime_by_string(a, increment, i + 1)
+        map = raster_dataset(None)
+        map.set_absolute_time(start, end)
+        maps.append(map)
+
+    gran = compute_absolute_time_granularity(maps)
+    if increment != gran:
+        core.error("Wrong granularity reference %s != gran %s" % (increment, gran))
+
+    print "Test 3"
+    maps = []
+    a = datetime(2001, 1, 1)
+    increment = "1 days"
+    for i in range(6):
+        start = increment_datetime_by_string(a, increment, i)
+        end = increment_datetime_by_string(a, increment, i + 1)
+        map = raster_dataset(None)
+        map.set_absolute_time(start, end)
+        maps.append(map)
+
+    gran = compute_absolute_time_granularity(maps)
+    if increment != gran:
+        core.error("Wrong granularity reference %s != gran %s" % (increment, gran))
+
+    print "Test 4"
+    maps = []
+    a = datetime(2001, 1, 14)
+    increment = "14 days"
+    for i in range(6):
+        start = increment_datetime_by_string(a, increment, i)
+        end = increment_datetime_by_string(a, increment, i + 1)
+        map = raster_dataset(None)
+        map.set_absolute_time(start, end)
+        maps.append(map)
+
+    gran = compute_absolute_time_granularity(maps)
+    if increment != gran:
+        core.error("Wrong granularity reference %s != gran %s" % (increment, gran))
+
+    print "Test 5"
+    maps = []
+    a = datetime(2001, 3, 1)
+    increment = "1 months, 4 days"
+    for i in range(20):
+        start = increment_datetime_by_string(a, increment, i)
+        end = increment_datetime_by_string(a, increment, i + 1)
+        map = raster_dataset(None)
+        map.set_absolute_time(start, end)
+        maps.append(map)
+
+    increment = "1 days"
+    gran = compute_absolute_time_granularity(maps)
+    if increment != gran:
+        core.error("Wrong granularity reference %s != gran %s" % (increment, gran))
+
+    print "Test 6"
+    maps = []
+    a = datetime(2001, 2, 11)
+    increment = "1 days, 1 hours"
+    for i in range(20):
+        start = increment_datetime_by_string(a, increment, i)
+        end = increment_datetime_by_string(a, increment, i + 1)
+        map = raster_dataset(None)
+        map.set_absolute_time(start, end)
+        maps.append(map)
+
+    increment = "25 hours"
+    gran = compute_absolute_time_granularity(maps)
+    if increment != gran:
+        core.error("Wrong granularity reference %s != gran %s" % (increment, gran))
+
+    print "Test 7"
+    maps = []
+    a = datetime(2001, 6, 12)
+    increment = "6 hours"
+    for i in range(20):
+        start = increment_datetime_by_string(a, increment, i)
+        end = increment_datetime_by_string(a, increment, i + 1)
+        map = raster_dataset(None)
+        map.set_absolute_time(start, end)
+        maps.append(map)
+
+    gran = compute_absolute_time_granularity(maps)
+    if increment != gran:
+        core.error("Wrong granularity reference %s != gran %s" % (increment, gran))
+
+    print "Test 8"
+    maps = []
+    a = datetime(2001, 1, 1)
+    increment = "20 minutes"
+    for i in range(20):
+        start = increment_datetime_by_string(a, increment, i)
+        end = increment_datetime_by_string(a, increment, i + 1)
+        map = raster_dataset(None)
+        map.set_absolute_time(start, end)
+        maps.append(map)
+
+    gran = compute_absolute_time_granularity(maps)
+    if increment != gran:
+        core.error("Wrong granularity reference %s != gran %s" % (increment, gran))
+
+    print "Test 9"
+    maps = []
+    a = datetime(2001, 1, 1)
+    increment = "5 hours, 25 minutes"
+    for i in range(20):
+        start = increment_datetime_by_string(a, increment, i)
+        end = increment_datetime_by_string(a, increment, i + 1)
+        map = raster_dataset(None)
+        map.set_absolute_time(start, end)
+        maps.append(map)
+
+    increment = "325 minutes"
+    gran = compute_absolute_time_granularity(maps)
+    if increment != gran:
+        core.error("Wrong granularity reference %s != gran %s" % (increment, gran))
+
+    print "Test 10"
+    maps = []
+    a = datetime(2001, 1, 1)
+    increment = "5 minutes, 30 seconds"
+    for i in range(20):
+        start = increment_datetime_by_string(a, increment, i)
+        end = increment_datetime_by_string(a, increment, i + 1)
+        map = raster_dataset(None)
+        map.set_absolute_time(start, end)
+        maps.append(map)
+
+    increment = "330 seconds"
+    gran = compute_absolute_time_granularity(maps)
+    if increment != gran:
+        core.error("Wrong granularity reference %s != gran %s" % (increment, gran))
+
+    print "Test 11"
+    maps = []
+    a = datetime(2001,12,31)
+    increment = "60 minutes, 30 seconds"
+    for i in range(24):
+        start = increment_datetime_by_string(a, increment, i)
+        end = increment_datetime_by_string(a, increment, i + 1)
+        map = raster_dataset(None)
+        map.set_absolute_time(start, end)
+        maps.append(map)
+
+    increment = "3630 seconds"
+    gran = compute_absolute_time_granularity(maps)
+    if increment != gran:
+        core.error("Wrong granularity reference %s != gran %s" % (increment, gran))
+
+    print "Test 12"
+    maps = []
+    a = datetime(2001,12,31, 12, 30, 30)
+    increment = "3600 seconds"
+    for i in range(24):
+        start = increment_datetime_by_string(a, increment, i)
+        end = increment_datetime_by_string(a, increment, i + 1)
+        map = raster_dataset(None)
+        map.set_absolute_time(start, end)
+        maps.append(map)
+
+    gran = compute_absolute_time_granularity(maps)
+    if increment != gran:
+        core.error("Wrong granularity reference %s != gran %s" % (increment, gran))
+
+    # Test absolute time points    
+
+    print "Test 13"
+    maps = []
+    a = datetime(2001,12,31, 12, 30, 30)
+    increment = "3600 seconds"
+    for i in range(24):
+        start = increment_datetime_by_string(a, increment, i)
+        end = None
+        map = raster_dataset(None)
+        map.set_absolute_time(start, end)
+        maps.append(map)
+
+    gran = compute_absolute_time_granularity(maps)
+    if increment != gran:
+        core.error("Wrong granularity reference %s != gran %s" % (increment, gran))
+
+    print "Test 14"
+    maps = []
+    a = datetime(2001,12,31, 00, 00, 00)
+    increment = "20 days"
+    for i in range(24):
+        start = increment_datetime_by_string(a, increment, i)
+        end = None
+        map = raster_dataset(None)
+        map.set_absolute_time(start, end)
+        maps.append(map)
+
+    gran = compute_absolute_time_granularity(maps)
+    if increment != gran:
+        core.error("Wrong granularity reference %s != gran %s" % (increment, gran))
+
+    print "Test 15"
+    maps = []
+    a = datetime(2001,12,01, 00, 00, 00)
+    increment = "5 months"
+    for i in range(24):
+        start = increment_datetime_by_string(a, increment, i)
+        end = None
+        map = raster_dataset(None)
+        map.set_absolute_time(start, end)
+        maps.append(map)
+
+    gran = compute_absolute_time_granularity(maps)
+    if increment != gran:
+        core.error("Wrong granularity reference %s != gran %s" % (increment, gran))
+
+    # Test absolute time interval and points    
+
+    print "Test 16"
+    maps = []
+    a = datetime(2001,12,31, 12, 30, 30)
+    increment = "3600 seconds"
+
+    for i in range(24):
+        start = increment_datetime_by_string(a, increment, i)
+        end = increment_datetime_by_string(a, increment, i + 1)
+        map = raster_dataset(None)
+        map.set_absolute_time(start, end)
+        maps.append(map)
+
+    a = datetime(2002,02,01, 12, 30, 30)
+    for i in range(24):
+        start = increment_datetime_by_string(a, increment, i)
+        end = None
+        map = raster_dataset(None)
+        map.set_absolute_time(start, end)
+        maps.append(map)
+
+    gran = compute_absolute_time_granularity(maps)
+    if increment != gran:
+        core.error("Wrong granularity reference %s != gran %s" % (increment, gran))
+
+    print "Test 17"
+    maps = []
+    a = datetime(2001,1,1)
+    increment = "2 days"
+
+    for i in range(8):
+        start = increment_datetime_by_string(a, increment, i)
+        end = increment_datetime_by_string(a, increment, i + 1)
+        map = raster_dataset(None)
+        map.set_absolute_time(start, end)
+        maps.append(map)
+
+    a = datetime(2001,02,02)
+    for i in range(8):
+        start = increment_datetime_by_string(a, increment, i)
+        end = None
+        map = raster_dataset(None)
+        map.set_absolute_time(start, end)
+        maps.append(map)
+
+    gran = compute_absolute_time_granularity(maps)
+    if increment != gran:
+        core.error("Wrong granularity reference %s != gran %s" % (increment, gran))