Browse Source

Added timestamp support for vector maps with v.timestamp.
The *.timestamp modules are now used to attach the absolute time
from the temporal database to maps in the file system. Hence, C-modules
have easier access to temporal information.


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

Soeren Gebbert 13 years ago
parent
commit
f237c91ebe

+ 2 - 0
include/vect/dig_defines.h

@@ -28,6 +28,8 @@
 #define GV_COLR_ELEMENT "colr"
 #define GV_COLR_ELEMENT "colr"
 /*! \brief Name of directory for alternative color tables */
 /*! \brief Name of directory for alternative color tables */
 #define GV_COLR2_DIRECTORY "vcolr2"
 #define GV_COLR2_DIRECTORY "vcolr2"
+/*! \brief Name of the timestamp file */
+#define GV_TIMESTAMP_ELEMENT "timestamp"
 
 
 /*! \brief Endian check
 /*! \brief Endian check
 
 

+ 59 - 9
lib/gis/timestamp.c

@@ -76,11 +76,12 @@
  */
  */
 
 
 #include <string.h>
 #include <string.h>
+#include <unistd.h>    
 #include <grass/gis.h>
 #include <grass/gis.h>
+#include <grass/vect/dig_defines.h>
 #include <grass/glocale.h>
 #include <grass/glocale.h>
 
 
 #define RAST_MISC "cell_misc"
 #define RAST_MISC "cell_misc"
-#define VECT_MISC "dig_misc"
 #define GRID3	  "grid3"
 #define GRID3	  "grid3"
 
 
 /*!
 /*!
@@ -376,26 +377,52 @@ int G_remove_raster_timestamp(const char *name)
 /*!
 /*!
   \brief Read timestamp from vector map
   \brief Read timestamp from vector map
   
   
-  Is this used anymore with the new GRASS 6 vector engine???
-  
   \param name map name
   \param name map name
   \param mapset mapset name
   \param mapset mapset name
   \param[out] ts TimeStamp struct to populate
   \param[out] ts TimeStamp struct to populate
 
 
   \return 1 on success
   \return 1 on success
-  \return 0 or negative on error
+  \return 0 no timestamp present
+  \return -1 Unable to open file 
+  \return -2 invalid time stamp
 */
 */
 int G_read_vector_timestamp(const char *name, const char *mapset,
 int G_read_vector_timestamp(const char *name, const char *mapset,
 			    struct TimeStamp *ts)
 			    struct TimeStamp *ts)
 {
 {
-    return read_timestamp("vector", VECT_MISC, name, mapset, ts);
+    FILE *fd;
+    int stat;
+    char dir[GPATH_MAX];
+    char path[GPATH_MAX + GNAME_MAX];
+
+    G_snprintf(dir, GPATH_MAX, "%s/%s", GV_DIRECTORY, name);
+    G_file_name(path, dir, GV_TIMESTAMP_ELEMENT, mapset);
+
+    G_debug(1, "Reading vector timestamp file: %s", path);
+
+    /* In case no timestamp file is present return 0 */
+    if (access(path, R_OK) != 0)
+	return 0;
+
+    fd = G_fopen_old(dir, GV_TIMESTAMP_ELEMENT, mapset);
+    
+    if (fd == NULL) {
+	G_warning(_("Unable to open timestamp file for vector map <%s@%s>"),
+		  name, G_mapset());
+	return -1;
+    }
+
+    stat = G__read_timestamp(fd, ts);
+    fclose(fd);
+    if (stat == 0)
+	return 1;
+    G_warning(_("Invalid timestamp file for vector map <%s@%s>"),
+	        name, mapset);
+    return -2;
 }
 }
 
 
 /*!
 /*!
   \brief Remove timestamp from vector map
   \brief Remove timestamp from vector map
   
   
-  Is this used anymore with the new GRASS 6 vector engine???
-  
   Only timestamp files in current mapset can be removed.
   Only timestamp files in current mapset can be removed.
 
 
   \param name map name
   \param name map name
@@ -406,7 +433,10 @@ int G_read_vector_timestamp(const char *name, const char *mapset,
 */
 */
 int G_remove_vector_timestamp(const char *name)
 int G_remove_vector_timestamp(const char *name)
 {
 {
-    return G_remove_misc(VECT_MISC, "timestamp", name);
+    char dir[GPATH_MAX];
+
+    G_snprintf(dir, GPATH_MAX, "%s/%s", GV_DIRECTORY, name);
+    return G_remove(dir, GV_TIMESTAMP_ELEMENT);
 }
 }
 
 
 /*!
 /*!
@@ -470,7 +500,27 @@ int G_write_raster_timestamp(const char *name, const struct TimeStamp *ts)
  */
  */
 int G_write_vector_timestamp(const char *name, const struct TimeStamp *ts)
 int G_write_vector_timestamp(const char *name, const struct TimeStamp *ts)
 {
 {
-    return write_timestamp("vector", VECT_MISC, name, ts);
+    FILE *fd;
+    int stat;
+    char dir[GPATH_MAX];
+
+    G_snprintf(dir, GPATH_MAX, "%s/%s", GV_DIRECTORY, name);
+
+    fd = G_fopen_new(dir, GV_TIMESTAMP_ELEMENT);
+    
+    if (fd == NULL) {
+	G_warning(_("Unable to create timestamp file for vector map <%s@%s>"),
+		  name, G_mapset());
+	return -1;
+    }
+
+    stat = G__write_timestamp(fd, ts);
+    fclose(fd);
+    if (stat == 0)
+	return 1;
+    G_warning(_("Invalid timestamp specified for vector map <%s@%s>"),
+	      name, G_mapset());
+    return -2;
 }
 }
 
 
 /*!
 /*!

+ 20 - 4
lib/python/temporal/abstract_map_dataset.py

@@ -21,6 +21,7 @@ for details.
 @author Soeren Gebbert
 @author Soeren Gebbert
 """
 """
 from abstract_dataset import *
 from abstract_dataset import *
+from datetime_math import *
 
 
 ###############################################################################
 ###############################################################################
 
 
@@ -39,7 +40,7 @@ class abstract_map_dataset(abstract_dataset):
     def get_stds_register(self):
     def get_stds_register(self):
         """Return the space time dataset register table name in which stds are listed in which this map is registered"""
         """Return the space time dataset register table name in which stds are listed in which this map is registered"""
         raise IOError("This method must be implemented in the subclasses")
         raise IOError("This method must be implemented in the subclasses")
-        
+
     def set_stds_register(self, name):
     def set_stds_register(self, name):
         """Set the space time dataset register table name.
         """Set the space time dataset register table name.
         
         
@@ -48,7 +49,11 @@ class abstract_map_dataset(abstract_dataset):
            @param ident: The name of the register table
            @param ident: The name of the register table
         """
         """
         raise IOError("This method must be implemented in the subclasses")
         raise IOError("This method must be implemented in the subclasses")
-        
+      
+    def get_timestamp_module_name(self):
+        """Return the name of the C-module to set the time stamp in the file system"""
+        raise IOError("This method must be implemented in the subclasses")
+   
     def load(self):
     def load(self):
         """Load the content of this object from map files"""
         """Load the content of this object from map files"""
         raise IOError("This method must be implemented in the subclasses")
         raise IOError("This method must be implemented in the subclasses")
@@ -162,6 +167,14 @@ class abstract_map_dataset(abstract_dataset):
         if connect == True:
         if connect == True:
             dbif.close()
             dbif.close()
 
 
+        # Start the grass C-module to set the time in the file system
+        start = datetime_to_grass_datetime_string(start_time)
+        if end_time:
+            end = datetime_to_grass_datetime_string(end_time)
+            start += " / %s"%(end)
+
+        core.run_command(self.get_timestamp_module_name(), map=self.get_id(), date=start)
+
     def set_relative_time(self, start_time, end_time=None):
     def set_relative_time(self, start_time, end_time=None):
         """Set the relative time interval 
         """Set the relative time interval 
         
         
@@ -188,8 +201,8 @@ class abstract_map_dataset(abstract_dataset):
     def update_relative_time(self, start_time, end_time=None, dbif = None):
     def update_relative_time(self, start_time, end_time=None, dbif = None):
         """Update the relative time interval
         """Update the relative time interval
 
 
-           @param start_time: A double value in days
-           @param end_time: A double value in days
+           @param start_time: A double value 
+           @param end_time: A double value 
            @param dbif: The database interface to be used
            @param dbif: The database interface to be used
         """
         """
         connect = False
         connect = False
@@ -276,6 +289,9 @@ class abstract_map_dataset(abstract_dataset):
             # Delete yourself from the database, trigger functions will take care of dependencies
             # Delete yourself from the database, trigger functions will take care of dependencies
             self.base.delete(dbif)
             self.base.delete(dbif)
 
 
+        # Remove the timestamp from the file system
+        core.run_command(self.get_timestamp_module_name(), map=self.get_id(), date="none")
+
         self.reset(None)
         self.reset(None)
         dbif.connection.commit()
         dbif.connection.commit()
 
 

+ 33 - 0
lib/python/temporal/datetime_math.py

@@ -23,6 +23,7 @@ for details.
 from datetime import datetime, date, time, timedelta
 from datetime import datetime, date, time, timedelta
 import grass.script.core as core
 import grass.script.core as core
 import copy
 import copy
+from dateutil import parser
 
 
 
 
 ###############################################################################
 ###############################################################################
@@ -314,3 +315,35 @@ def compute_datetime_delta(start, end):
 
 
     return comp
     return comp
 
 
+###############################################################################
+
+def string_to_datetime(time_string):
+    """Convert a string into a datetime object using the dateutil parser. Return None in case of failure"""
+
+    # BC is not suported
+    if time_string.find("bc") > 0:
+        core.error("Dates Before Christ are not supported in the temporal database")
+        return None
+
+    try:
+        dt = parser.parse(time_string)
+        return dt
+    except:
+        return None
+
+###############################################################################
+
+def datetime_to_grass_datetime_string(dt):
+    """Convert a python datetime object into a GRASS datetime string"""
+
+    # GRASS datetime month names
+    month_names  = ["", "jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"]
+
+    # Check for time zone infor in the datetime object
+    if dt.tzinfo != None:
+        string = "%.2i %s %.2i %.2i:%.2i:%.2i %+.4i"%(dt.day, month_names[dt.month], dt.year, \
+                 dt.hour, dt.minute, dt.second, dt.tzinfo._offset.seconds/3600*100)
+    else:
+        string = "%.2i %s %.4i %.2i:%.2i:%.2i"%(dt.day, month_names[dt.month], dt.year, dt.hour, dt.minute, dt.second)
+
+    return string

+ 12 - 0
lib/python/temporal/space_time_datasets.py

@@ -59,6 +59,10 @@ class raster_dataset(abstract_map_dataset):
     def set_stds_register(self, name):
     def set_stds_register(self, name):
         """Set the space time dataset register table name in which stds are listed in which this map is registered"""
         """Set the space time dataset register table name in which stds are listed in which this map is registered"""
         self.metadata.set_strds_register(name)
         self.metadata.set_strds_register(name)
+ 
+    def get_timestamp_module_name(self):
+        """Return the name of the C-module to set the time stamp in the file system"""
+        return "r.timestamp"
 
 
     def reset(self, ident):
     def reset(self, ident):
 	"""Reset the internal structure and set the identifier"""
 	"""Reset the internal structure and set the identifier"""
@@ -133,6 +137,10 @@ class raster3d_dataset(abstract_map_dataset):
     def set_stds_register(self, name):
     def set_stds_register(self, name):
         """Set the space time dataset register table name in which stds are listed in which this map is registered"""
         """Set the space time dataset register table name in which stds are listed in which this map is registered"""
         self.metadata.set_str3ds_register(name)
         self.metadata.set_str3ds_register(name)
+ 
+    def get_timestamp_module_name(self):
+        """Return the name of the C-module to set the time stamp in the file system"""
+        return "r3.timestamp"
 
 
     def reset(self, ident):
     def reset(self, ident):
 	"""Reset the internal structure and set the identifier"""
 	"""Reset the internal structure and set the identifier"""
@@ -211,6 +219,10 @@ class vector_dataset(abstract_map_dataset):
     def set_stds_register(self, name):
     def set_stds_register(self, name):
         """Set the space time dataset register table name in which stds are listed in which this map is registered"""
         """Set the space time dataset register table name in which stds are listed in which this map is registered"""
         self.metadata.set_stvds_register(name)
         self.metadata.set_stvds_register(name)
+ 
+    def get_timestamp_module_name(self):
+        """Return the name of the C-module to set the time stamp in the file system"""
+        return "v.timestamp"
 
 
     def reset(self, ident):
     def reset(self, ident):
 	"""Reset the internal structure and set the identifier"""
 	"""Reset the internal structure and set the identifier"""

+ 8 - 8
lib/python/temporal/space_time_datasets_tools.py

@@ -476,17 +476,17 @@ def assign_valid_time_to_map(ttype, map, start, end, increment=None, mult=1, dbi
         connect = True
         connect = True
 
 
     if ttype == "absolute":
     if ttype == "absolute":
-        # Create the start time object
-        if start.find(":") > 0:
-            time_format = "%Y-%m-%d %H:%M:%S"
-        else:
-            time_format = "%Y-%m-%d"
-
-        start_time = datetime.strptime(start, time_format)
+        start_time = string_to_datetime(start)
+        if start_time == None:
+            dbif.close()
+            core.fatal_error(_("Unable to convert string \"%s\"into a datetime object")%(start))
         end_time = None
         end_time = None
         
         
         if end:
         if end:
-            end_time = datetime.strptime(end, time_format)
+            end_time = string_to_datetime(end)
+            if end_time == None:
+                dbif.close()
+                core.fatal_error(_("Unable to convert string \"%s\"into a datetime object")%(end))
 
 
         # Add the increment
         # Add the increment
         if increment:
         if increment:

+ 12 - 0
temporal/tr.register/test.tr.register.sh

@@ -31,55 +31,67 @@ t.create --o type=strds temporaltype=absolute output=precip_abs7 title="A test"
 tr.register -i input=precip_abs1 maps=prec_1,prec_2,prec_3,prec_4,prec_5,prec_6 start="2001-01-01" increment="1 seconds"
 tr.register -i input=precip_abs1 maps=prec_1,prec_2,prec_3,prec_4,prec_5,prec_6 start="2001-01-01" increment="1 seconds"
 t.info type=strds input=precip_abs1
 t.info type=strds input=precip_abs1
 t.info -g type=strds input=precip_abs1
 t.info -g type=strds input=precip_abs1
+r.info map=prec_1
 tr.list input=precip_abs1
 tr.list input=precip_abs1
 t.topology input=precip_abs1
 t.topology input=precip_abs1
 
 
 tr.register -i input=precip_abs2 maps=prec_1,prec_2,prec_3,prec_4,prec_5,prec_6 start="2001-01-01" increment="20 seconds, 5 minutes"
 tr.register -i input=precip_abs2 maps=prec_1,prec_2,prec_3,prec_4,prec_5,prec_6 start="2001-01-01" increment="20 seconds, 5 minutes"
 t.info type=strds input=precip_abs2
 t.info type=strds input=precip_abs2
 t.info -g type=strds input=precip_abs2
 t.info -g type=strds input=precip_abs2
+r.info map=prec_1
 tr.list input=precip_abs2
 tr.list input=precip_abs2
 t.topology input=precip_abs2
 t.topology input=precip_abs2
 
 
 tr.register -i input=precip_abs3 maps=prec_1,prec_2,prec_3,prec_4,prec_5,prec_6 start="2001-01-01" increment="8 hours"
 tr.register -i input=precip_abs3 maps=prec_1,prec_2,prec_3,prec_4,prec_5,prec_6 start="2001-01-01" increment="8 hours"
 t.info -g type=strds input=precip_abs3
 t.info -g type=strds input=precip_abs3
+r.info map=prec_1
 tr.list input=precip_abs3
 tr.list input=precip_abs3
 t.topology input=precip_abs3
 t.topology input=precip_abs3
 
 
 tr.register input=precip_abs4 maps=prec_1,prec_2,prec_3,prec_4,prec_5,prec_6 start="2001-01-01" increment="3 days"
 tr.register input=precip_abs4 maps=prec_1,prec_2,prec_3,prec_4,prec_5,prec_6 start="2001-01-01" increment="3 days"
 t.info -g type=strds input=precip_abs4
 t.info -g type=strds input=precip_abs4
+r.info map=prec_1
 tr.list input=precip_abs4
 tr.list input=precip_abs4
 t.topology input=precip_abs4
 t.topology input=precip_abs4
 
 
 tr.register input=precip_abs5 maps=prec_1,prec_2,prec_3,prec_4,prec_5,prec_6 start="2001-01-01" increment="4 weeks"
 tr.register input=precip_abs5 maps=prec_1,prec_2,prec_3,prec_4,prec_5,prec_6 start="2001-01-01" increment="4 weeks"
 t.info -g type=strds input=precip_abs5
 t.info -g type=strds input=precip_abs5
+r.info map=prec_1
 tr.list input=precip_abs5
 tr.list input=precip_abs5
 t.topology input=precip_abs5
 t.topology input=precip_abs5
 
 
 tr.register input=precip_abs6 maps=prec_1,prec_2,prec_3,prec_4,prec_5,prec_6 start="2001-08-01" increment="2 months"
 tr.register input=precip_abs6 maps=prec_1,prec_2,prec_3,prec_4,prec_5,prec_6 start="2001-08-01" increment="2 months"
 t.info -g type=strds input=precip_abs6
 t.info -g type=strds input=precip_abs6
+r.info map=prec_1
 tr.list input=precip_abs6
 tr.list input=precip_abs6
 t.topology input=precip_abs6
 t.topology input=precip_abs6
 
 
 tr.register input=precip_abs7 maps=prec_1,prec_2,prec_3,prec_4,prec_5,prec_6 start="2001-01-01" increment="20 years, 3 months, 1 days, 4 hours"
 tr.register input=precip_abs7 maps=prec_1,prec_2,prec_3,prec_4,prec_5,prec_6 start="2001-01-01" increment="20 years, 3 months, 1 days, 4 hours"
 t.info -g type=strds input=precip_abs7
 t.info -g type=strds input=precip_abs7
+r.info map=prec_1
 tr.list input=precip_abs7
 tr.list input=precip_abs7
 t.topology input=precip_abs7
 t.topology input=precip_abs7
 # Register with different valid time again
 # Register with different valid time again
 tr.register input=precip_abs7 maps=prec_1,prec_2,prec_3,prec_4,prec_5,prec_6 start="2001-01-01" increment="99 years, 9 months, 9 days, 9 hours"
 tr.register input=precip_abs7 maps=prec_1,prec_2,prec_3,prec_4,prec_5,prec_6 start="2001-01-01" increment="99 years, 9 months, 9 days, 9 hours"
 t.info -g type=strds input=precip_abs7
 t.info -g type=strds input=precip_abs7
+r.info map=prec_1
 tr.list input=precip_abs7
 tr.list input=precip_abs7
 t.topology input=precip_abs7
 t.topology input=precip_abs7
 # Register with different valid time again creating an interval
 # Register with different valid time again creating an interval
 tr.register -i input=precip_abs7 maps=prec_1,prec_2,prec_3,prec_4,prec_5,prec_6 start="2001-01-01" increment="99 years, 9 months, 9 days, 9 hours"
 tr.register -i input=precip_abs7 maps=prec_1,prec_2,prec_3,prec_4,prec_5,prec_6 start="2001-01-01" increment="99 years, 9 months, 9 days, 9 hours"
 t.info -g type=strds input=precip_abs7
 t.info -g type=strds input=precip_abs7
+r.info map=prec_1
 tr.list input=precip_abs7
 tr.list input=precip_abs7
 t.topology input=precip_abs7
 t.topology input=precip_abs7
 
 
+
 tr.register input=precip_abs7 maps=prec_1,prec_2,prec_3,prec_4,prec_5,prec_6 start="2001-01-01" end="2002-01-01"
 tr.register input=precip_abs7 maps=prec_1,prec_2,prec_3,prec_4,prec_5,prec_6 start="2001-01-01" end="2002-01-01"
 t.info -g type=strds input=precip_abs7
 t.info -g type=strds input=precip_abs7
+r.info map=prec_1
 tr.list input=precip_abs7
 tr.list input=precip_abs7
 t.topology input=precip_abs7
 t.topology input=precip_abs7
 
 
 t.remove type=rast input=prec_1,prec_2,prec_3
 t.remove type=rast input=prec_1,prec_2,prec_3
 t.remove type=strds input=precip_abs1,precip_abs2,precip_abs3,precip_abs4,precip_abs5,precip_abs6,precip_abs7
 t.remove type=strds input=precip_abs1,precip_abs2,precip_abs3,precip_abs4,precip_abs5,precip_abs6,precip_abs7
 t.remove type=rast input=prec_4,prec_5,prec_6
 t.remove type=rast input=prec_4,prec_5,prec_6
+r.info map=prec_1

+ 1 - 0
vector/Makefile

@@ -79,6 +79,7 @@ SUBDIRS = \
 	v.surf.idw \
 	v.surf.idw \
 	v.surf.rst \
 	v.surf.rst \
 	v.transform \
 	v.transform \
+	v.timestamp \
 	v.to.3d \
 	v.to.3d \
 	v.to.db \
 	v.to.db \
 	v.to.points \
 	v.to.points \

+ 25 - 2
vector/v.info/print.c

@@ -229,9 +229,21 @@ void print_info(const struct Map_info *Map)
     int i;
     int i;
     char line[100];
     char line[100];
     char tmp1[100], tmp2[100];
     char tmp1[100], tmp2[100];
-
+    char timebuff[256];
+    struct TimeStamp ts;
+    int time_ok = 0, first_time_ok = 0, second_time_ok = 0;
     struct bound_box box;
     struct bound_box box;
-    
+   
+    /*Check the Timestamp */
+    time_ok = G_read_vector_timestamp(Vect_get_name(Map), "", &ts);
+    /*Check for valid entries, show none if no timestamp available */
+    if (time_ok == 1) {
+	if (ts.count > 0)
+	    first_time_ok = 1;
+	if (ts.count > 1)
+	    second_time_ok = 1;
+    }
+
     divider('+');
     divider('+');
     if (Vect_maptype(Map) & (GV_FORMAT_OGR | GV_FORMAT_OGR_DIRECT)) {
     if (Vect_maptype(Map) & (GV_FORMAT_OGR | GV_FORMAT_OGR_DIRECT)) {
 	/* for OGR format print also datasource and layer */
 	/* for OGR format print also datasource and layer */
@@ -284,6 +296,17 @@ void print_info(const struct Map_info *Map)
 	    Vect_get_map_date(Map));
 	    Vect_get_map_date(Map));
     printline(line);
     printline(line);
     
     
+    /*This shows the TimeStamp */
+    if (time_ok  == 1 && (first_time_ok || second_time_ok)) {
+        G_format_timestamp(&ts, timebuff);
+        sprintf(line, "%-17s%s", _("Timestamp: "), timebuff);
+    }
+    else {
+        sprintf(line, "%-17s%s", _("Timestamp: none"), timebuff);
+    }
+    printline(line);
+
+
     divider('|');
     divider('|');
     
     
     sprintf(line, "  %s: %s (%s: %i)",
     sprintf(line, "  %s: %s (%s: %i)",

+ 12 - 0
vector/v.timestamp/Makefile

@@ -0,0 +1,12 @@
+MODULE_TOPDIR = ../..
+
+PGM = v.timestamp
+
+LIBES = $(VECTORLIB) $(DBMILIB) $(GISLIB)
+DEPENDENCIES = $(VECTORDEP) $(DBMIDEP) $(GISDEP)
+EXTRA_INC = $(VECT_INC)
+EXTRA_CFLAGS = $(VECT_CFLAGS)
+
+include $(MODULE_TOPDIR)/include/Make/Module.make
+
+default: cmd

+ 79 - 0
vector/v.timestamp/main.c

@@ -0,0 +1,79 @@
+
+/****************************************************************************
+ *
+ * MODULE:       v.timestamp
+ *  
+ * AUTHOR(S):    Soeren Gebbert <soerengebbert googlemail.com>
+ *               based on r.timestamp from Michael Shapiro, CERL (original contributor)
+ *
+ * PURPOSE:      Print/add/remove a timestamp for a vector map
+ * COPYRIGHT:    (C) 2012 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.
+ *
+ *****************************************************************************/
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <grass/gis.h>
+#include <grass/vector.h>
+#include <grass/glocale.h>
+
+int main(int argc, char *argv[])
+{
+    struct GModule *module;
+    struct Option *map, *date;
+    struct TimeStamp ts;
+    char *name;
+    int modify;
+
+    G_gisinit(argv[0]);
+
+    module = G_define_module();
+    G_add_keyword(_("vector"));
+    G_add_keyword(_("metadata"));
+    G_add_keyword(_("timestamp"));
+    module->label = _("Modifies a timestamp for a vector map.");
+    module->description = _("Print/add/remove a timestamp for a vector map.");
+
+    map = G_define_standard_option(G_OPT_V_MAP);
+
+    date = G_define_option();
+    date->key = "date";
+    date->key_desc = "timestamp";
+    date->required = NO;
+    date->type = TYPE_STRING;
+    date->label = _("Datetime, datetime1/datetime2, or 'none' to remove");
+    date->description = _("Format: '15 jan 1994' (absolute) or '2 years' (relative)");
+    
+    if (G_parser(argc, argv))
+	exit(EXIT_FAILURE);
+
+    name = map->answer;
+
+    modify = date->answer != NULL;
+
+    if (!modify) {
+	if (G_read_vector_timestamp(name, "", &ts) == 1) {
+	    G__write_timestamp(stdout, &ts);
+	    exit(EXIT_SUCCESS);
+	}
+	else
+	    exit(EXIT_FAILURE);
+    }
+    if (strcmp(date->answer, "none") == 0) {
+	G_remove_vector_timestamp(name);
+	exit(EXIT_SUCCESS);
+    }
+
+    if (1 == G_scan_timestamp(&ts, date->answer)) {
+	G_write_vector_timestamp(name, &ts);
+	exit(EXIT_SUCCESS);
+    }
+    else
+	G_fatal_error(_("Invalid timestamp"));
+
+    exit(EXIT_SUCCESS);
+}

+ 134 - 0
vector/v.timestamp/v.timestamp.html

@@ -0,0 +1,134 @@
+<h2>DESCRIPTION</h2>
+
+This command has 2 modes of operation. If no <b>date</b> argument is
+supplied, then the current timestamp for the vector map is printed. If
+a date argument is specified, then the timestamp for the vector map is
+set to the specified date(s). See examples below.
+
+<h2>NOTES</h2>
+
+Strings containing spaces should be quoted. For specifying a range of
+time, the two timestamps should be separated by a forward slash. To
+remove the timestamp from a vector map, use <b>date=none</b>.
+
+<h2>TIMESTAMP FORMAT</h2>
+
+The timestamp values must use the format as described in the <em>GRASS
+Datetime Library</em>. The source tree for this library should have a
+description of the format. For convience, the formats are reproduced
+here:
+
+<p>There are two types of datetime values:
+
+<ul>
+  <li><em>absolute</em> and
+  <li><em>relative</em>.
+</ul>
+
+Absolute values specify exact dates and/or times. Relative values
+specify a span of time. 
+
+<h3>Absolute</h3>
+
+The general format for absolute values is:
+
+<div class="code"><pre>
+  day month year [bc] hour:minute:seconds timezone
+
+	     day is 1-31
+	     month is jan,feb,...,dec
+	     year is 4 digit year
+	     [bc] if present, indicates dates is BC
+	     hour is 0-23 (24 hour clock)
+	     mintue is 0-59
+	     second is 0-59.9999 (fractions of second allowed)
+	     timezone is +hhmm or -hhmm (eg, -0600)
+</pre></div>
+
+Some parts can be missing, for example
+
+<div class="code"><pre>
+	     1994 [bc]
+	     Jan 1994 [bc]
+	     15 jan 1000 [bc]
+	     15 jan 1994 [bc] 10 [+0000]
+	     15 jan 1994 [bc] 10:00 [+0100]
+	     15 jan 1994 [bc] 10:00:23.34 [-0500]
+</pre></div>
+
+<h3>Relative</h3>
+
+There are two types of relative datetime values, year-month and
+day-second. The formats are:
+
+<div class="code"><pre>
+	     [-] # years # months
+	     [-] # days # hours # minutes # seconds
+</pre></div>
+
+The words years, months, days, hours, minutes, seconds are literal
+words, and the # are the numeric values. Examples:
+
+<div class="code"><pre>
+	     2 years
+	     5 months
+	     2 years 5 months
+	     100 days
+	     15 hours 25 minutes 35.34 seconds
+	     100 days 25 minutes
+	     1000 hours 35.34 seconds
+</pre></div>
+
+The following are <i>illegal</i> because it mixes year-month and
+day-second (because the number of days in a month or in a year vary):
+
+<div class="code"><pre>
+	     3 months 15 days
+	     3 years 10 days
+</pre></div>
+
+<h2>EXAMPLES</h2>
+
+Prints the timestamp for the "lidar" vector map. If there is no
+timestamp for "lidar", nothing is printed. If there is a timestamp,
+one or two time strings are printed, depending on if the timestamp for
+the map consists of a single date or two dates (ie start and end
+dates).
+
+<div class="code"><pre>
+    v.timestamp map=lidar
+</pre></div>
+
+Sets the timestamp for "lidar" to the single date "15 sep 1987".
+
+<div class="code"><pre>
+    v.timestamp map=lidar date='15 sep 1987'
+</pre></div>
+
+Sets the timestamp for "lidar" to have the start date "15 sep 1987"
+and the end date "20 feb 1988".
+
+<div class="code"><pre>
+    v.timestamp map=lidar date='15 sep 1987/20 feb 1988'
+</pre></div>
+
+Removes the timestamp for the "lidar" vector map.
+
+<div class="code"><pre>
+    v.timestamp map=lidar date=none
+</pre></div>
+
+<h2>BUGS</h2>
+Spaces in the timestamp value are required.
+
+<h2>SEE ALSO</h2>
+
+<em>
+  <a href="v.info.html">v.info</a>
+</em>
+
+<h2>AUTHOR</h2>
+
+Michael Shapiro, U.S.Army Construction Engineering Research Laboratory
+
+<p><i>Last changed: $Date: 2011-11-08 22:24:20 +0100 (Di, 08 Nov 2011) $</i>