Repository: mie-lab/trackintel
Pull request #101 (2021-02-09 16:44:23)

Fix datetime type
Trackintel is now timezone aware
- all required datetime columns are now forced to have a timezone and a fixed datatype using an assert statement in the accessors
- all io functions (except for postgis!) now import timezone aware using an optional timezone argument. If nothing is provided they warn and assume UTC
- Tests are updated according to these changes
- Reformated files according to PEP8 + numpy docstring standards

This solves issue #18 


Patch:
diff --git a/trackintel/io/dataset_reader.py b/trackintel/io/dataset_reader.py
index ea5cba3..44ea880 100644
--- a/trackintel/io/dataset_reader.py
+++ b/trackintel/io/dataset_reader.py
@@ -56,7 +56,6 @@ def read_geolife(geolife_path):
     'tracked_at': datetime64[ns]; 'user_id': int64; 'geom': geopandas/shapely geometry; 'accuracy': None
     """
 
-
     geolife_path = os.path.join(geolife_path, '*')
     user_folder = sorted(glob.glob(geolife_path))
 
@@ -87,7 +86,7 @@ def read_geolife(geolife_path):
                                            'date days', 'date', 'time'])
 
             data_this['tracked_at'] = pd.to_datetime(data_this['date']
-                                                     + ' ' + data_this['time'], format="%Y-%m-%d %H:%M:%S")
+                                                     + ' ' + data_this['time'], format="%Y-%m-%d %H:%M:%S", utc=True)
 
             data_this.drop(['zeros', 'date days', 'date', 'time'], axis=1,
                            inplace=True)
diff --git a/trackintel/io/file.py b/trackintel/io/file.py
index 5446510..9e526a4 100644
--- a/trackintel/io/file.py
+++ b/trackintel/io/file.py
@@ -1,10 +1,41 @@
+import warnings
+
+import dateutil
 import dateutil.parser
 import geopandas as gpd
 import pandas as pd
+import pytz
 from shapely import wkt
 from shapely.geometry import Point
 
 
+def localize_timestamp(dt_series, pytz_tzinfo, col_name):
+    """
+    Helper function that adds timezone info to timestamp
+
+    Parameters
+    ----------
+    dt_series: pandas.Series
+        a pandas datetime series
+    pytz_tzinfo: str
+        pytz compatible timezone string. If none UTC will be assumed
+    col_name: str
+        Column name for informative warning message
+
+    Returns
+    -------
+    pd.Series
+        a timezone aware pandas datetime series
+
+    """
+    if pytz_tzinfo is None:
+        warnings.warn("Assuming UTC timezone for column {}".format(col_name))
+        pytz_tzinfo = 'utc'
+
+    timezone = pytz.timezone(pytz_tzinfo)
+    return dt_series.apply(pd.Timestamp, tz=timezone)
+
+
 def read_positionfixes_csv(*args, **kwargs):
     """Wraps the pandas read_csv function, extracts longitude and latitude and 
     builds a geopandas GeoDataFrame. This also validates that the ingested data
@@ -15,6 +46,8 @@ def read_positionfixes_csv(*args, **kwargs):
     ----------
     columns : dict
         The columnnames to rename in the format {'old_name':'trackintel_standard_name'}.
+    tz : str
+        pytz compatible timezone string. If None UTC is assumed.
 
     Note that this function is primarily useful if data is available in a 
     longitude/latitude format. If your data already contains a WKT column, it
@@ -30,12 +63,25 @@ def read_positionfixes_csv(*args, **kwargs):
     >>> trackintel.read_positionfixes_csv('data.csv')
     >>> trackintel.read_positionfixes_csv('data.csv', columns={'time':'tracked_at', 'User':'user_id'})
     """
-    columns=kwargs.pop('columns',{})
+
+    columns = kwargs.pop('columns', {})
+    tz = kwargs.pop('tz', None)
+
     df = pd.read_csv(*args, **kwargs)
     df = df.rename(columns=columns)
     df['geom'] = list(zip(df.longitude, df.latitude))
     df['geom'] = df['geom'].apply(Point)
     df['tracked_at'] = df['tracked_at'].apply(dateutil.parser.parse)
+
+    # check and/or set timezone
+    for col in ['tracked_at']:
+        if not pd.api.types.is_datetime64tz_dtype(df[col]):
+            df[col] = localize_timestamp(dt_series=df[col], pytz_tzinfo=tz, col_name=col)
+        else:
+            # dateutil parser timezones are sometimes not compatible with pandas (e.g., in asserts)
+            tz = df[col].iloc[0].tzinfo.tzname(df[col].iloc[0])
+            df[col] = df[col].dt.tz_convert(tz)
+
     df = df.drop(['longitude', 'latitude'], axis=1)
     gdf = gpd.GeoDataFrame(df, geometry='geom')
     assert gdf.as_positionfixes
@@ -71,7 +117,9 @@ def read_triplegs_csv(*args, **kwargs):
     ----------
     columns : dict
         The columnnames to rename in the format {'old_name':'trackintel_standard_name'}.
-    
+    tz : str
+        pytz compatible timezone string. If None UTC is assumed.
+
     Returns
     -------
     GeoDataFrame
@@ -82,18 +130,30 @@ def read_triplegs_csv(*args, **kwargs):
     >>> trackintel.read_triplegs_csv('data.csv')
     >>> trackintel.read_triplegs_csv('data.csv', columns={'start_time':'started_at', 'User':'user_id'})
     """
-    columns=kwargs.pop('columns',{})
+
+    columns = kwargs.pop('columns', {})
+    tz = kwargs.pop('tz', None)
+
     df = pd.read_csv(*args, **kwargs)
     df = df.rename(columns=columns)
     df['geom'] = df['geom'].apply(wkt.loads)
     df['started_at'] = df['started_at'].apply(dateutil.parser.parse)
     df['finished_at'] = df['finished_at'].apply(dateutil.parser.parse)
+
+    # check and/or set timezone
+    for col in ['started_at', 'finished_at']:
+        if not pd.api.types.is_datetime64tz_dtype(df[col]):
+            df[col] = localize_timestamp(dt_series=df[col], pytz_tzinfo=tz, col_name=col)
+        else:
+            # dateutil parser timezones are sometimes not compatible with pandas (e.g., in asserts)
+            tz = df[col].iloc[0].tzinfo.tzname(df[col].iloc[0])
+            df[col] = df[col].dt.tz_convert(tz)
+
     gdf = gpd.GeoDataFrame(df, geometry='geom')
     assert gdf.as_triplegs
     return gdf
 
 
-
 def write_triplegs_csv(triplegs, filename, *args, **kwargs):
     """Wraps the pandas to_csv function, but transforms the geom into WKT 
     before writing.
@@ -106,6 +166,7 @@ def write_triplegs_csv(triplegs, filename, *args, **kwargs):
     filename : str
         The file to write to.
     """
+
     gdf = triplegs.copy()
     gdf[gdf.geometry.name] = triplegs.geometry.apply(wkt.dumps)
     gdf.to_csv(filename, index=True, *args, **kwargs)
@@ -121,6 +182,8 @@ def read_staypoints_csv(*args, **kwargs):
     ----------
     columns : dict
         The columnnames to rename in the format {'old_name':'trackintel_standard_name'}.
+    tz : str
+        pytz compatible timezone string. If None UTC is assumed.
 
     Returns
     -------
@@ -132,12 +195,25 @@ def read_staypoints_csv(*args, **kwargs):
     >>> trackintel.read_staypoints_csv('data.csv')
     >>> trackintel.read_staypoints_csv('data.csv', columns={'start_time':'started_at', 'User':'user_id'})
     """
-    columns=kwargs.pop('columns',{})
+
+    columns = kwargs.pop('columns', {})
+    tz = kwargs.pop('tz', None)
+
     df = pd.read_csv(*args, **kwargs)
     df = df.rename(columns=columns)
     df['geom'] = df['geom'].apply(wkt.loads)
     df['started_at'] = df['started_at'].apply(dateutil.parser.parse)
     df['finished_at'] = df['finished_at'].apply(dateutil.parser.parse)
+
+    # check and/or set timezone
+    for col in ['started_at', 'finished_at']:
+        if not pd.api.types.is_datetime64tz_dtype(df[col]):
+            df[col] = localize_timestamp(dt_series=df[col], pytz_tzinfo=tz, col_name=col)
+        else:
+            # dateutil parser timezones are sometimes not compatible with pandas (e.g., in asserts)
+            tz = df[col].iloc[0].tzinfo.tzname(df[col].iloc[0])
+            df[col] = df[col].dt.tz_convert(tz)
+
     gdf = gpd.GeoDataFrame(df, geometry='geom')
     assert gdf.as_staypoints
     return gdf
@@ -182,7 +258,7 @@ def read_locations_csv(*args, **kwargs):
     >>> trackintel.read_locations_csv('data.csv')
     >>> trackintel.read_locations_csv('data.csv', columns={'start_time':'started_at', 'User':'user_id'})
     """
-    columns=kwargs.pop('columns',{})
+    columns = kwargs.pop('columns', {})
     df = pd.read_csv(*args, **kwargs)
     df = df.rename(columns=columns)
     df['center'] = df['center'].apply(wkt.loads)
@@ -221,6 +297,8 @@ def read_trips_csv(*args, **kwargs):
     ----------
     columns : dict
         The columnnames to rename in the format {'old_name':'trackintel_standard_name'}.
+    tz : str
+        pytz compatible timezone string. If None UTC is assumed.
 
     Returns
     -------
@@ -233,16 +311,27 @@ def read_trips_csv(*args, **kwargs):
     >>> trackintel.read_trips_csv('data.csv')
     >>> trackintel.read_trips_csv('data.csv', columns={'start_time':'started_at', 'User':'user_id'})
     """
-    columns=kwargs.pop('columns',{})
+
+    columns = kwargs.pop('columns', {})
+    tz = kwargs.pop('tz', None)
     df = pd.read_csv(*args, **kwargs)
     df = df.rename(columns=columns)
     df['started_at'] = df['started_at'].apply(dateutil.parser.parse)
     df['finished_at'] = df['finished_at'].apply(dateutil.parser.parse)
+
+    # check and/or set timezone
+    for col in ['started_at', 'finished_at']:
+        if not pd.api.types.is_datetime64tz_dtype(df[col]):
+            df[col] = localize_timestamp(dt_series=df[col], pytz_tzinfo=tz, col_name=col)
+        else:
+            # dateutil parser timezones are sometimes not compatible with pandas (e.g., in asserts)
+            tz = df[col].iloc[0].tzinfo.tzname(df[col].iloc[0])
+            df[col] = df[col].dt.tz_convert(tz)
+
     assert df.as_trips
     return df
 
 
-
 def write_trips_csv(trips, filename, *args, **kwargs):
     """Wraps the pandas to_csv function.
 
@@ -263,12 +352,26 @@ def read_tours_csv(*args, **kwargs):
     validates that the ingested data conforms to the trackintel understanding 
     of tours (see :doc:`/modules/model`).
 
+    columns : dict
+        The columnnames to rename in the format {'old_name':'trackintel_standard_name'}.
+    tz : str
+        pytz compatible timezone string. If None UTC is assumed.
+
     Returns
     -------
     DataFrame
         A DataFrame containing the tours.
     """
     # TODO: implement the reading function for tours
+
+    # # check and/or set timezone
+    # for col in ['started_at', 'finished_at']:
+    #     if not pd.api.types.is_datetime64tz_dtype(df[col]):
+    #         df[col] = localize_timestamp(dt_series=df[col], pytz_tzinfo=kwargs.pop('tz', None), col_name=col)
+    #         else:
+    #             # dateutil parser timezones are sometimes not compatible with pandas (e.g., in asserts)
+    #             tz = df[col].iloc[0].tzinfo.tzname(df[col].iloc[0])
+    #             df[col] = df[col].dt.tz_convert(tz)
     pass
 
 
diff --git a/trackintel/io/from_geopandas.py b/trackintel/io/from_geopandas.py
index e59fa56..8fb817d 100644
--- a/trackintel/io/from_geopandas.py
+++ b/trackintel/io/from_geopandas.py
@@ -1,203 +1,274 @@
-import geopandas as gpd
 import pandas as pd
 
-def positionfixes_from_gpd(gdf, tracked_at='tracked_at', user_id='user_id', geom='geom', mapper={}):
+from trackintel.io.file import localize_timestamp
+
+
+def positionfixes_from_gpd(gdf, tracked_at='tracked_at', user_id='user_id', geom='geom', tz=None, mapper={}):
     """
     warps the pd.rename function to simplify the import of GeoDataFrames
 
     Parameters
     ----------
-    gdf : GeoDataFrame with valid point geometry, containing the positionfixes to import
-    tracked_at : str, name of the column storing the timestamps. The default is 'tracked_at'.
-    user_id : str, name of the column storing the user_id. The default is 'user_id'.
-    geom : str, name of the column storing the geometry. The default is 'geom'.
-    mapper : dict, further columns that should be renamed. 
+    gdf :
+        GeoDataFrame with valid point geometry, containing the positionfixes to import
+    tracked_at : str
+        name of the column storing the timestamps. The default is 'tracked_at'.
+    user_id : str
+        name of the column storing the user_id. The default is 'user_id'.
+    geom : str
+        name of the column storing the geometry. The default is 'geom'.
+    tz: str
+        pytz compatible timezone string. If None UTC will be assumed
+    mapper : dict
+        further columns that should be renamed.
 
     Returns
     -------
     gdf : a validated positionfixes GeoDataFrame
 
     """
-  
-    
-    columns = {tracked_at:'tracked_at', 
-               user_id:'user_id',
-               geom:'geom'}
+
+    columns = {tracked_at: 'tracked_at',
+               user_id: 'user_id',
+               geom: 'geom'}
     columns.update(mapper)
-    
+
     pfs = gdf.rename(columns=columns)
     pfs = pfs.set_geometry('geom')
-    
+
+    # check and/or set timezone
+    for col in ['tracked_at']:
+        if not pd.api.types.is_datetime64tz_dtype(pfs[col]):
+            pfs[col] = localize_timestamp(dt_series=pfs[col], pytz_tzinfo=tz, col_name=col)
+
     assert pfs.as_positionfixes
-    
 
     return pfs
 
-def staypoints_from_gpd(gdf, started_at='started_at', finished_at='finished_at', user_id='user_id', geom='geom', mapper={}):
+
+def staypoints_from_gpd(gdf, started_at='started_at', finished_at='finished_at', user_id='user_id', geom='geom',
+                        tz=None,
+                        mapper={}):
     """
     warps the pd.rename function to simplify the import of GeoDataFrames
 
     Parameters
     ----------
-    gdf : GeoDataFrame with valid point geometry, containing the staypoints to import
-    started_at : str, name of the column storing the starttime of the staypoints. The default is 'started_at'.
-    finished_at : str, name of the column storing the endtime of the staypoints. The default is 'finished_at'.
-    user_id : str, name of the column storing the user_id. The default is 'user_id'.
-    geom : str, name of the column storing the geometry. The default is 'geom'.
-    mapper : dict, further columns that should be renamed. 
+    gdf : GeoDataFrame
+        GeoDataFrame with valid point geometry, containing the staypoints to import
+    started_at : str
+        name of the column storing the starttime of the staypoints. The default is 'started_at'.
+    finished_at : str
+        name of the column storing the endtime of the staypoints. The default is 'finished_at'.
+    user_id : str
+        name of the column storing the user_id. The default is 'user_id'.
+    geom : str
+        name of the column storing the geometry. The default is 'geom'.
+    tz: str
+        pytz compatible timezone string. If None UTC is assumed.
+    mapper : dict
+        further columns that should be renamed.
 
     Returns
     -------
     gdf : a validated staypoints GeoDataFrame
 
     """
-  
-    
-    columns = {started_at:'started_at',
-               finished_at:'finished_at',
-               user_id:'user_id',
-               geom:'geom'}
+
+    columns = {started_at: 'started_at',
+               finished_at: 'finished_at',
+               user_id: 'user_id',
+               geom: 'geom'}
     columns.update(mapper)
-    
+
     stp = gdf.rename(columns=columns)
     stp = stp.set_geometry('geom')
-    
+
+    # check and/or set timezone
+    for col in ['started_at', 'finished_at']:
+        if not pd.api.types.is_datetime64tz_dtype(stp[col]):
+            stp[col] = localize_timestamp(dt_series=stp[col], pytz_tzinfo=tz, col_name=col)
+
     assert stp.as_staypoints
-    
 
     return stp
 
-def triplegs_from_gpd(gdf, started_at='started_at', finished_at='finished_at', user_id='user_id', geom='geometry', mapper={}):
+
+def triplegs_from_gpd(gdf, started_at='started_at', finished_at='finished_at', user_id='user_id', geom='geometry',
+                      tz=None, mapper={}):
     """
     warps the pd.rename function to simplify the import of GeoDataFrames
 
     Parameters
     ----------
-    gdf : GeoDataFrame with valid line geometry, containing the triplegs to import
-    started_at : str, name of the column storing the starttime of the triplegs. The default is 'started_at'.
-    finished_at : str, name of the column storing the endtime of the triplegs. The default is 'finished_at'.
-    user_id : str, name of the column storing the user_id. The default is 'user_id'.
-    geom : str, name of the column storing the geometry. The default is 'geom'.
-    mapper : dict, further columns that should be renamed. 
+    gdf : GeoDataFrame
+        GeoDataFrame with valid line geometry, containing the triplegs to import
+    started_at : str
+        name of the column storing the starttime of the triplegs. The default is 'started_at'.
+    finished_at : str
+        name of the column storing the endtime of the triplegs. The default is 'finished_at'.
+    user_id : str
+        name of the column storing the user_id. The default is 'user_id'.
+    geom : str
+        name of the column storing the geometry. The default is 'geom'.
+    tz : str
+        pytz compatible timezone string. If None UTC is assumed.
+    mapper : dict
+        further columns that should be renamed.
 
     Returns
     -------
     gdf : a validated triplegs GeoDataFrame
 
     """
-  
-    
-    columns = {started_at:'started_at',
-               finished_at:'finished_at',
-               user_id:'user_id',
-               geom:'geom'}
+
+    columns = {started_at: 'started_at',
+               finished_at: 'finished_at',
+               user_id: 'user_id',
+               geom: 'geom'}
     columns.update(mapper)
-    
+
     tpl = gdf.rename(columns=columns)
     tpl = tpl.set_geometry('geom')
-    
+
+    # check and/or set timezone
+    for col in ['started_at', 'finished_at']:
+        if not pd.api.types.is_datetime64tz_dtype(tpl[col]):
+            tpl[col] = localize_timestamp(dt_series=tpl[col], pytz_tzinfo=tz, col_name=col)
+
     assert tpl.as_triplegs
-    
 
     return tpl
 
-def trips_from_gpd(gdf, started_at='started_at', finished_at='finished_at', user_id='user_id', origin_staypoint_id='origin_staypoint_id', destination_staypoint_id='destination_staypoint_id', mapper={}):
+
+def trips_from_gpd(gdf, started_at='started_at', finished_at='finished_at', user_id='user_id',
+                   origin_staypoint_id='origin_staypoint_id', destination_staypoint_id='destination_staypoint_id',
+                   tz=None, mapper={}):
     """
     warps the pd.rename function to simplify the import of GeoDataFrames
 
     Parameters
     ----------
-    gdf : GeoDataFrame with valid geometry, containing the trips to import
-    started_at : str, name of the column storing the starttime of the staypoints. The default is 'started_at'.
-    finished_at : str, name of the column storing the endtime of the staypoints. The default is 'finished_at'.
-    user_id : str, name of the column storing the user_id. The default is 'user_id'.
-    origin_staypoint_id : str, name of the column storing the staypoint_id of the start of the tripleg
-    destination_staypoint_id : str, name of the column storing the staypoint_id of the end of the tripleg
-    mapper : dict, further columns that should be renamed. 
+    gdf : GeoDataFrame
+        GeoDataFrame with valid geometry, containing the trips to import
+    started_at : str
+        name of the column storing the starttime of the staypoints. The default is 'started_at'.
+    finished_at : str
+        name of the column storing the endtime of the staypoints. The default is 'finished_at'.
+    user_id : str
+        name of the column storing the user_id. The default is 'user_id'.
+    origin_staypoint_id : str
+        name of the column storing the staypoint_id of the start of the tripleg
+    destination_staypoint_id : str
+        name of the column storing the staypoint_id of the end of the tripleg
+    tz : str
+        pytz compatible timezone string. If None UTC is assumed.
+    mapper : dict
+        further columns that should be renamed.
 
     Returns
     -------
     gdf : a validated trips GeoDataFrame
 
     """
-  
-    
-    columns = {started_at:'started_at',
-               finished_at:'finished_at',
-               user_id:'user_id',
-               origin_staypoint_id:'origin_staypoint_id',
-               destination_staypoint_id:'destination_staypoint_id'
+
+    columns = {started_at: 'started_at',
+               finished_at: 'finished_at',
+               user_id: 'user_id',
+               origin_staypoint_id: 'origin_staypoint_id',
+               destination_staypoint_id: 'destination_staypoint_id'
                }
     columns.update(mapper)
-    
+
     tps = gdf.rename(columns=columns)
-    
-    assert tps.as_trips
-    
 
+    # check and/or set timezone
+    for col in ['started_at', 'finished_at']:
+        if not pd.api.types.is_datetime64tz_dtype(tps[col]):
+            tps[col] = localize_timestamp(dt_series=tps[col], pytz_tzinfo=tz, col_name=col)
+
+    assert tps.as_trips
     return tps
 
+
 def locations_from_gpd(gdf, user_id='user_id', center='center', mapper={}):
     """
     warps the pd.rename function to simplify the import of GeoDataFrames
 
     Parameters
     ----------
-    gdf : GeoDataFrame with valid point geometry, containing the locations to import
-    user_id : str, name of the column storing the user_id. The default is 'user_id'.
-    center : str, name of the column storing the geometry (Center of the location). The default is 'center'.
-    mapper : dict, further columns that should be renamed. 
+    gdf : GeoDataFrame
+        GeoDataFrame with valid point geometry, containing the locations to import
+    user_id : str
+        name of the column storing the user_id. The default is 'user_id'.
+    center : str
+        name of the column storing the geometry (Center of the location). The default is 'center'.
+    tz : str
+        pytz compatible timezone string. If None UTC is assumed.
+    mapper : dict
+        further columns that should be renamed.
 
     Returns
     -------
     gdf : a validated locations GeoDataFrame
 
     """
-  
-    
-    columns = {user_id:'user_id',
-               center:'center'}
+
+    columns = {user_id: 'user_id',
+               center: 'center'}
     columns.update(mapper)
-    
+
     lcs = gdf.rename(columns=columns)
     lcs = lcs.set_geometry('center')
-    
+
     assert lcs.as_locations
-    
 
     return lcs
 
-def tours_from_gpd(gdf, user_id='user_id',started_at='started_at', finished_at='finished_at', origin_destination_location_id='origin_destination_location_id', journey='journey', mapper={}):
+
+def tours_from_gpd(gdf, user_id='user_id', started_at='started_at', finished_at='finished_at',
+                   origin_destination_location_id='origin_destination_location_id', journey='journey',
+                   tz=None, mapper={}):
     """
-    warps the pd.rename function to simplify the import of GeoDataFrames
+    wraps the pd.rename function to simplify the import of GeoDataFrames
 
     Parameters
     ----------
-    gdf : GeoDataFrame with valid point geometry, containing the locations to import
-    user_id : str, name of the column storing the user_id. The default is 'user_id'.
-    started_at : str, name of the column storing the starttime of the staypoints. The default is 'started_at'.
-    finished_at : str, name of the column storing the endtime of the staypoints. The default is 'finished_at'.
-    origin_destination_location_id : the name of the column storing the id of the location where the tour starts and ends. The default is 'origin_destination_location_id'.
-    journey : str, name of the column storing the information (bool) if the tour is a journey. The default is 'journey'.
-    mapper : dict, further columns that should be renamed. 
+    gdf : GeoDataFrame
+        GeoDataFrame with valid point geometry, containing the locations to import
+    user_id : str
+        name of the column storing the user_id. The default is 'user_id'.
+    started_at : str
+        name of the column storing the starttime of the staypoints. The default is 'started_at'.
+    finished_at : str
+        name of the column storing the endtime of the staypoints. The default is 'finished_at'.
+    origin_destination_location_id :
+        the name of the column storing the id of the location where the tour starts and ends. The default is 'origin_destination_location_id'.
+    journey : str
+        name of the column storing the information (bool) if the tour is a journey. The default is 'journey'.
+    mapper : dict
+        further columns that should be renamed.
 
     Returns
     -------
-    gdf : a validated locations GeoDataFrame
+    gdf : GeoDataFrame
+        a validated locations GeoDataFrame
 
     """
-  
-    
-    columns = {user_id:'user_id',
-               started_at:'tracked_at',
-               finished_at:'finished_at',
-               origin_destination_location_id:'origin_destination_location_id',
-               journey:'journey'}
+
+    columns = {user_id: 'user_id',
+               started_at: 'tracked_at',
+               finished_at: 'finished_at',
+               origin_destination_location_id: 'origin_destination_location_id',
+               journey: 'journey'}
     columns.update(mapper)
-    
+
     trs = gdf.rename(columns=columns)
-    
-    assert trs.as_tours
-    
 
+    # check and/or set timezone
+    for col in ['started_at', 'finished_at']:
+        if not pd.api.types.is_datetime64tz_dtype(trs[col]):
+            trs[col] = localize_timestamp(dt_series=trs[col], pytz_tzinfo=tz, col_name=col)
+
+    assert trs.as_tours
     return trs
diff --git a/trackintel/model/positionfixes.py b/trackintel/model/positionfixes.py
index a2ef265..6cb3639 100644
--- a/trackintel/model/positionfixes.py
+++ b/trackintel/model/positionfixes.py
@@ -23,6 +23,8 @@ class PositionfixesAccessor(object):
     In GPS based movement data analysis `Positionfixes` are the smallest unit of tracking and
     represent timestamped locations.
 
+    ``tracked_at`` is a timezone aware pandas datetime object.
+
     Examples
     --------
     >>> df.as_positionfixes.generate_staypoints()
@@ -50,6 +52,10 @@ class PositionfixesAccessor(object):
         if obj.geometry.iloc[0].geom_type != 'Point':
             raise AttributeError("The geometry must be a Point (only first checked).")
 
+        # check timestamp dtypes
+        assert pd.api.types.is_datetime64tz_dtype(obj['tracked_at']), \
+            "dtype of tracked_at is {} but has to be datetime64 and timezone aware".format(obj['tracked_at'].dtype)
+
     @property
     def center(self):
         """Returns the center coordinate of this collection of positionfixes."""
@@ -94,12 +100,12 @@ class PositionfixesAccessor(object):
         ti.io.file.write_positionfixes_csv(self._obj, filename, *args, **kwargs)
 
     def to_postgis(self, conn_string, table_name, schema=None,
-            sql_chunksize=None, if_exists='replace'):
+                   sql_chunksize=None, if_exists='replace'):
         """Stores this collection of positionfixes to PostGIS.
         See :func:`trackintel.io.postgis.write_positionfixes_postgis`."""
-        ti.io.postgis.write_positionfixes_postgis(self._obj, conn_string, table_name, 
-            schema, sql_chunksize, if_exists)
-        
+        ti.io.postgis.write_positionfixes_postgis(self._obj, conn_string, table_name,
+                                                  schema, sql_chunksize, if_exists)
+
     def similarity_matrix(self, method, field='tripleg_id', trsh=None, eps=None, dist=False, **kwargs):
         """Calculates Similarity (/distance) matrix. See: func: 'trackintel.similarity.detection.similarity_matrix' """
         return ti.similarity.similarity_matrix(self._obj, method, field, trsh, eps, dist, **kwargs)
diff --git a/trackintel/model/staypoints.py b/trackintel/model/staypoints.py
index 60b5f3a..733c1d3 100644
--- a/trackintel/model/staypoints.py
+++ b/trackintel/model/staypoints.py
@@ -21,8 +21,10 @@ class StaypointsAccessor(object):
     Notes
     -------
     Staypoints are defined as location were a person did not move for a while. Under consideration of location
-     uncertainty this means that a person stays within a certain radius for a certain amount of time.
-     The exact definition is use-case dependent.
+    uncertainty this means that a person stays within a certain radius for a certain amount of time.
+    The exact definition is use-case dependent.
+
+    ``started_at`` and ``finished_at`` are timezone aware pandas datetime objects.
 
     Examples
     --------
@@ -48,6 +50,12 @@ class StaypointsAccessor(object):
         if obj.geometry.iloc[0].geom_type != 'Point':
             raise AttributeError("The geometry must be a Point (only first checked).")
 
+        # check timestamp dtypes
+        assert pd.api.types.is_datetime64tz_dtype(obj['started_at']), \
+            "dtype of started_at is {} but has to be tz aware datetime64".format(obj['started_at'].dtype)
+        assert pd.api.types.is_datetime64tz_dtype(obj['finished_at']), \
+            "dtype of finished_at is {} but has to be tz aware datetime64".format(obj['finished_at'].dtype)
+
     @property
     def center(self):
         """Returns the center coordinate of this collection of staypoints."""
diff --git a/trackintel/model/tours.py b/trackintel/model/tours.py
index 3d2f53c..3f5c285 100644
--- a/trackintel/model/tours.py
+++ b/trackintel/model/tours.py
@@ -18,6 +18,8 @@ class ToursAccessor(object):
     Tours are an aggregation level in transport planning that summarize all trips until a person returns to the
     same location. Tours starting and ending at home (=journey) are especially important.
 
+    ``started_at`` and ``finished_at`` are timezone aware pandas datetime objects.
+
     Examples
     --------
     >>> df.as_tours.plot()
@@ -33,14 +35,20 @@ class ToursAccessor(object):
     def _validate(obj):
         if any([c not in obj.columns for c in ToursAccessor.required_columns]):
             raise AttributeError("To process a DataFrame as a collection of staypoints, " \
-                + "it must have the properties [%s], but it has [%s]." \
-                % (', '.join(ToursAccessor.required_columns), ', '.join(obj.columns)))
-            
+                                 + "it must have the properties [%s], but it has [%s]." \
+                                 % (', '.join(ToursAccessor.required_columns), ', '.join(obj.columns)))
+
+        # check timestamp dtypes
+        assert pd.api.types.is_datetime64tz_dtype(obj['started_at']), \
+            "dtype of started_at is {} but has to be datetime64 and timezone aware".format(obj['started_at'].dtype)
+        assert pd.api.types.is_datetime64tz_dtype(obj['finished_at']), \
+            "dtype of finished_at is {} but has to be datetime64 and timezone aware".format(obj['finished_at'].dtype)
+
     def to_csv(self, filename, *args, **kwargs):
         """Stores this collection of tours as a CSV file.
         See :func:`trackintel.io.file.write_tours_csv`."""
         raise NotImplementedError
-        
+
     def plot(self, *args, **kwargs):
         """Plots this collection of tours. 
         See :func:`trackintel.visualization.tours.plot_tours`."""
diff --git a/trackintel/model/triplegs.py b/trackintel/model/triplegs.py
index 58eec08..69f1206 100644
--- a/trackintel/model/triplegs.py
+++ b/trackintel/model/triplegs.py
@@ -24,6 +24,8 @@ class TriplegsAccessor(object):
     -------
     A `tripleg` (also called `stage`) is defined as continuous movement without changing the mode of transport.
 
+    ``started_at`` and ``finished_at`` are timezone aware pandas datetime objects.
+
     Examples
     --------
     >>> df.as_triplegs.plot()
@@ -49,6 +51,12 @@ class TriplegsAccessor(object):
         if obj.geometry.iloc[0].geom_type != 'LineString':
             raise AttributeError("The geometry must be a LineString (only first checked).")
 
+        # check timestamp dtypes
+        assert pd.api.types.is_datetime64tz_dtype(obj['started_at']), \
+            "dtype of started_at is {} but has to be datetime64 and timezone aware".format(obj['started_at'].dtype)
+        assert pd.api.types.is_datetime64tz_dtype(obj['finished_at']), \
+            "dtype of finished_at is {} but has to be datetime64 and timezone aware".format(obj['finished_at'].dtype)
+
     def plot(self, *args, **kwargs):
         """Plots this collection of triplegs. 
         See :func:`trackintel.visualization.triplegs.plot_triplegs`."""
@@ -69,8 +77,8 @@ class TriplegsAccessor(object):
         See :func:`trackintel.geogr.distances.calculate_distance_matrix`.
         """
         return ti.geogr.distances.calculate_distance_matrix(self._obj, *args, **kwargs)
-    
+
     def spatial_filter(self, *args, **kwargs):
         """Filter triplegs with a geo extent.
         See :func:`trackintel.preprocessing.filter.spatial_filter`."""
-        return ti.preprocessing.filter.spatial_filter(self._obj, *args, **kwargs)
\ No newline at end of file
+        return ti.preprocessing.filter.spatial_filter(self._obj, *args, **kwargs)
diff --git a/trackintel/model/trips.py b/trackintel/model/trips.py
index d2e9bd1..d0b6b79 100644
--- a/trackintel/model/trips.py
+++ b/trackintel/model/trips.py
@@ -30,6 +30,8 @@ class TripsAccessor(object):
         - Trips that start/end in a recording gap can have an unknown origin/destination
         - There are no trips without a (recored) tripleg
 
+    ``started_at`` and ``finished_at`` are timezone aware pandas datetime objects.
+
     Examples
     --------
     >>> df.as_trips.plot()
@@ -46,9 +48,15 @@ class TripsAccessor(object):
     @staticmethod
     def _validate(obj):
         if any([c not in obj.columns for c in TripsAccessor.required_columns]):
-            raise AttributeError("To process a DataFrame as a collection of staypoints, " \
-                + "it must have the properties [%s], but it has [%s]." \
-                % (', '.join(TripsAccessor.required_columns), ', '.join(obj.columns)))
+            raise AttributeError("To process a DataFrame as a collection of staypoints, "
+                                 + "it must have the properties [%s], but it has [%s]."
+                                 % (', '.join(TripsAccessor.required_columns), ', '.join(obj.columns)))
+
+        # check timestamp dtypes
+        assert pd.api.types.is_datetime64tz_dtype(obj['started_at']), \
+            "dtype of started_at is {} but has to be datetime64 and timezone aware".format(obj['started_at'].dtype)
+        assert pd.api.types.is_datetime64tz_dtype(obj['finished_at']), \
+            "dtype of finished_at is {} but has to be datetime64 and timezone aware".format(obj['finished_at'].dtype)
 
     def plot(self, *args, **kwargs):
         """Plots this collection of trips. 
@@ -61,8 +69,8 @@ class TripsAccessor(object):
         ti.io.file.write_trips_csv(self._obj, filename, *args, **kwargs)
 
     def to_postgis(self, conn_string, table_name, schema=None,
-            sql_chunksize=None, if_exists='replace'):
+                   sql_chunksize=None, if_exists='replace'):
         """Stores this collection of trips to PostGIS.
         See :func:`trackintel.io.postgis.write_trips_postgis`."""
-        ti.io.postgis.write_trips_postgis(self._obj, conn_string, table_name, 
-            schema, sql_chunksize, if_exists)
+        ti.io.postgis.write_trips_postgis(self._obj, conn_string, table_name,
+                                          schema, sql_chunksize, if_exists)
diff --git a/trackintel/preprocessing/staypoints.py b/trackintel/preprocessing/staypoints.py
index 7b54e9e..70d8e10 100644
--- a/trackintel/preprocessing/staypoints.py
+++ b/trackintel/preprocessing/staypoints.py
@@ -1,17 +1,18 @@
 import datetime
+from math import radians
 
-import numpy as np
 import geopandas as gpd
+import numpy as np
 from shapely.geometry import Point
 from sklearn.cluster import DBSCAN
-from math import radians
 
 from trackintel.geogr.distances import meters_to_decimal_degrees
 
-def generate_locations(staypoints, 
+
+def generate_locations(staypoints,
                        method='dbscan',
-                       epsilon=100, 
-                       num_samples=1, 
+                       epsilon=100,
+                       num_samples=1,
                        distance_matrix_metric='euclidean',
                        agg_level='user'):
     """generate locations from the staypoints.
@@ -58,7 +59,7 @@ def generate_locations(staypoints,
     
     # initialize the return GeoDataFrames
     ret_stps = staypoints.copy()
-    ret_loc = gpd.GeoDataFrame([], columns=['user_id', 'id', 'center', 'extent'])
+    ret_loc = gpd.GeoDataFrame([], columns=['user_id', 'id', 'center', 'extent'])  # todo: define default dtypes
     
     if method=='dbscan':
 


Test patch:
diff --git a/tests/geogr/test_distances.py b/tests/geogr/test_distances.py
index 01d5f91..664387a 100644
--- a/tests/geogr/test_distances.py
+++ b/tests/geogr/test_distances.py
@@ -11,7 +11,7 @@ from trackintel.geogr.distances import meters_to_decimal_degrees, calculate_dist
 class TestCalculate_distance_matrix:
 
     def test_shape_for_different_array_length(self):
-        spts = ti.read_staypoints_csv(os.path.join('tests', 'data', 'geolife', 'geolife_staypoints.csv'))
+        spts = ti.read_staypoints_csv(os.path.join('tests', 'data', 'geolife', 'geolife_staypoints.csv'), tz='utc')
 
         x = spts.iloc[0:5]
         y = spts.iloc[5:15]
@@ -27,7 +27,7 @@ class TestCalculate_distance_matrix:
         assert np.isclose(0, np.sum(np.abs(d_hav1 - d_hav2.T)))
 
     def test_keyword_combinations(self):
-        spts = ti.read_staypoints_csv(os.path.join('tests', 'data', 'geolife', 'geolife_staypoints.csv'))
+        spts = ti.read_staypoints_csv(os.path.join('tests', 'data', 'geolife', 'geolife_staypoints.csv'), tz='utc')
 
         x = spts.iloc[0:5]
         y = spts.iloc[5:15]
@@ -43,7 +43,7 @@ class TestCalculate_distance_matrix:
 
 
     def test_compare_haversine_to_scikit_xy(self):
-        spts = ti.read_staypoints_csv(os.path.join('tests', 'data', 'geolife', 'geolife_staypoints.csv'))
+        spts = ti.read_staypoints_csv(os.path.join('tests', 'data', 'geolife', 'geolife_staypoints.csv'), tz='utc')
         our_d_matrix = calculate_distance_matrix(X=spts, Y=spts, dist_metric='haversine')
 
         x = spts.geometry.x.values
@@ -57,14 +57,14 @@ class TestCalculate_distance_matrix:
         assert np.allclose(np.abs(our_d_matrix - their_d_matrix), 0, atol=0.001) # atol = 1mm
 
     def test_trajectory_distance(self):
-        tpls = ti.read_triplegs_csv(os.path.join('tests', 'data', 'geolife', 'geolife_triplegs.csv'))
+        tpls = ti.read_triplegs_csv(os.path.join('tests', 'data', 'geolife', 'geolife_triplegs.csv'), tz='utc')
         D_single = calculate_distance_matrix(X=tpls.iloc[0:4], dist_metric='dtw', n_jobs=1)
         D_multi = calculate_distance_matrix(X=tpls.iloc[0:4], dist_metric='dtw', n_jobs=4)
 
         assert np.isclose(np.sum(np.abs(D_single - D_multi)), 0)
 
     def test_trajectory_distance_via_accessor_x(self):
-        tpls = ti.read_triplegs_csv(os.path.join('tests', 'data', 'geolife', 'geolife_triplegs.csv'))
+        tpls = ti.read_triplegs_csv(os.path.join('tests', 'data', 'geolife', 'geolife_triplegs.csv'), tz='utc')
 
 
         D_single = tpls.iloc[0:4].as_triplegs.similarity(dist_metric='dtw', n_jobs=1)
@@ -73,7 +73,7 @@ class TestCalculate_distance_matrix:
         assert np.isclose(np.sum(np.abs(D_single - D_multi)), 0)
 
     def test_trajectory_distance_via_accessor_xy(self):
-        tpls = ti.read_triplegs_csv(os.path.join('tests', 'data', 'geolife', 'geolife_triplegs.csv'))
+        tpls = ti.read_triplegs_csv(os.path.join('tests', 'data', 'geolife', 'geolife_triplegs.csv'), tz='utc')
 
         x = tpls.iloc[0:2]
         y = tpls.iloc[4:8]
diff --git a/tests/geogr/test_point_distances.py b/tests/geogr/test_point_distances.py
index 5d63668..336a3fc 100644
--- a/tests/geogr/test_point_distances.py
+++ b/tests/geogr/test_point_distances.py
@@ -34,7 +34,7 @@ class TestHaversineDist:
             assert np.isclose(haversine_output, haversine, atol=0.1)
 
     def test_haversine_vectorized(self):
-        spts = ti.read_staypoints_csv(os.path.join('tests', 'data', 'geolife', 'geolife_staypoints.csv'))
+        spts = ti.read_staypoints_csv(os.path.join('tests', 'data', 'geolife', 'geolife_staypoints.csv'), tz='utc')
         x = spts.geometry.x.values
         y = spts.geometry.y.values
 
diff --git a/tests/io/test_file.py b/tests/io/test_file.py
index ae69801..fee63e3 100644
--- a/tests/io/test_file.py
+++ b/tests/io/test_file.py
@@ -2,9 +2,6 @@ import filecmp
 import os
 
 import trackintel as ti
-import geopandas as gpd
-import pandas as pd
-
 
 
 class TestFile:
@@ -31,8 +28,9 @@ class TestFile:
         orig_file = os.path.join('tests', 'data', 'triplegs.csv')
         mod_file = os.path.join('tests','data','triplegs_mod_columns.csv')
         tmp_file = os.path.join('tests', 'data', 'triplegs_test.csv')
-        tpls = ti.read_triplegs_csv(orig_file, sep=';')
-        mod_tpls = ti.read_triplegs_csv(mod_file, columns={'start_time':'started_at','end_time':'finished_at', 'tripleg':'geom'},sep=';')
+        tpls = ti.read_triplegs_csv(orig_file, sep=';', tz='utc')
+        mod_tpls = ti.read_triplegs_csv(mod_file, columns={'start_time': 'started_at', 'end_time': 'finished_at',
+                                                           'tripleg': 'geom'}, sep=';')
         assert mod_tpls.equals(tpls)
         tpls['started_at'] = tpls['started_at'].apply(lambda d: d.isoformat().replace('+00:00', 'Z'))
         tpls['finished_at'] = tpls['finished_at'].apply(lambda d: d.isoformat().replace('+00:00', 'Z'))
@@ -52,8 +50,8 @@ class TestFile:
         orig_file = os.path.join('tests', 'data', 'staypoints.csv')
         mod_file = os.path.join('tests', 'data', 'staypoints_mod_columns.csv')
         tmp_file = os.path.join('tests', 'data', 'staypoints_test.csv')
-        stps = ti.read_staypoints_csv(orig_file, sep=';')
-        mod_stps = ti.read_staypoints_csv(mod_file, columns={'User':'user_id'},sep=';')
+        stps = ti.read_staypoints_csv(orig_file, sep=';', tz='utc')
+        mod_stps = ti.read_staypoints_csv(mod_file, columns={'User': 'user_id'}, sep=';')
         assert mod_stps.equals(stps)
         stps['started_at'] = stps['started_at'].apply(lambda d: d.isoformat().replace('+00:00', 'Z'))
         stps['finished_at'] = stps['finished_at'].apply(lambda d: d.isoformat().replace('+00:00', 'Z'))
@@ -66,7 +64,6 @@ class TestFile:
         # TODO Implement some tests for PostGIS.
         pass
     
-    
 
     def test_locations_from_to_csv(self):
         orig_file = os.path.join('tests', 'data', 'locations.csv')
diff --git a/tests/io/test_from_geopandas.py b/tests/io/test_from_geopandas.py
index d4dd835..6421d85 100644
--- a/tests/io/test_from_geopandas.py
+++ b/tests/io/test_from_geopandas.py
@@ -1,71 +1,51 @@
 import os
 
-import trackintel as ti
 import geopandas as gpd
 import pandas as pd
 
+import trackintel as ti
+
+
 class TestFromGeopandas:
-        def test_positionfixes_from_gpd(self):
-        
-            gdf = gpd.read_file(os.path.join('tests','data','positionfixes.geojson'))
-            pfs_from_gpd = ti.io.from_geopandas.positionfixes_from_gpd(gdf, user_id='User', geom='geometry')
-            pfs_from_csv = ti.read_positionfixes_csv(os.path.join('tests', 'data', 'positionfixes.csv'), sep=';')
-            
-            # TODO: datetime format not yet implemented and checked in model
-            pfs_from_csv['tracked_at'] = pfs_from_csv['tracked_at'].apply(lambda d: d.isoformat().replace(' ', 'T'))
-            # pfs_from_csv['tracked_at'] = pfs_from_csv['tracked_at'].apply(lambda d: d.replace('+00:00', ''))
-            
-            assert pfs_from_gpd.equals(pfs_from_csv)
-            
-        def test_triplegs_from_gpd(self):
-            gdf = gpd.read_file(os.path.join('tests','data','triplegs.geojson'))
-            tpls_from_gpd = ti.io.from_geopandas.triplegs_from_gpd(gdf,user_id='User',geom='geometry')
-            tpls_from_csv = ti.read_triplegs_csv(os.path.join('tests','data','triplegs.csv'),sep=';')
-            
-            # TODO: datetime format not yet implemented and checked in model
-            tpls_from_csv['started_at'] = tpls_from_csv['started_at'].apply(lambda d: d.isoformat().replace(' ', 'T')) 
-            tpls_from_csv['finished_at'] = tpls_from_csv['finished_at'].apply(lambda d: d.isoformat().replace(' ', 'T'))
-            # tpls_from_csv['started_at'] = tpls_from_csv['started_at'].apply(lambda d: d.replace('+00:00', ''))
-            # tpls_from_csv['finished_at'] = tpls_from_csv['finished_at'].apply(lambda d: d.replace('+00:00', ''))
-            
-            assert tpls_from_gpd.equals(tpls_from_csv)
-            
-        def test_staypoints_from_gpd(self):
-            gdf = gpd.read_file(os.path.join('tests','data','staypoints.geojson'))
-            stps_from_gpd = ti.io.from_geopandas.staypoints_from_gpd(gdf,'start_time','end_time', geom='geometry')
-            stps_from_csv = ti.read_staypoints_csv(os.path.join('tests','data','staypoints.csv'),sep=';')
-            
-            # TODO: datetime format not yet implemented and checked in model
-            stps_from_csv['started_at'] = stps_from_csv['started_at'].apply(lambda d: d.isoformat().replace(' ', 'T'))  
-            stps_from_csv['finished_at'] = stps_from_csv['finished_at'].apply(lambda d: d.isoformat().replace(' ', 'T'))
-            # stps_from_csv['started_at'] = stps_from_csv['started_at'].apply(lambda d: d.replace('+00:00', ''))
-            # stps_from_csv['finished_at'] = stps_from_csv['finished_at'].apply(lambda d: d.replace('+00:00', ''))
-            
-            assert stps_from_gpd.equals(stps_from_csv)
-            
-        def test_locations_from_gpd(self): 
-            #TODO: Problem with multiple geometry columns and geojson format
-            gdf = gpd.read_file(os.path.join('tests','data','locations.geojson'))
-            plcs_from_gpd = ti.io.from_geopandas.locations_from_gpd(gdf,user_id='User',center='geometry')
-            plcs_from_csv = ti.read_locations_csv(os.path.join('tests','data','locations.csv'),sep=';')
-            
-            # drop the second geometry column manually because not storable in GeoJSON (from Geopandas)
-            plcs_from_csv = plcs_from_csv.drop(columns='extent') 
-            assert plcs_from_gpd.equals(plcs_from_csv)
-            
-        def test_trips_from_gpd(self): 
-            df = pd.read_csv(os.path.join('tests','data','trips.csv'), sep=';')
-            trips_from_gpd = ti.io.from_geopandas.trips_from_gpd(df)
-            trips_from_csv = ti.read_trips_csv(os.path.join('tests','data','trips.csv'),sep=';')
-            
-            # TODO: datetime format not yet implemented and checked in model
-            trips_from_csv['started_at'] = trips_from_csv['started_at'].apply(lambda d: d.isoformat().replace(' ', 'T'))  
-            trips_from_csv['finished_at'] = trips_from_csv['finished_at'].apply(lambda d: d.isoformat().replace(' ', 'T'))
-            trips_from_csv['started_at'] = trips_from_csv['started_at'].apply(lambda d: d.replace('+00:00', 'Z'))
-            trips_from_csv['finished_at'] = trips_from_csv['finished_at'].apply(lambda d: d.replace('+00:00', 'Z'))
-            
-            assert trips_from_gpd.equals(trips_from_csv)
-            
-        def test_tours_from_gpd(self):
-            # TODO: implement tests for reading tours from Geopandas
-            pass
\ No newline at end of file
+    def test_positionfixes_from_gpd(self):
+        gdf = gpd.read_file(os.path.join('tests', 'data', 'positionfixes.geojson'))
+        pfs_from_gpd = ti.io.from_geopandas.positionfixes_from_gpd(gdf, user_id='User', geom='geometry', tz='utc')
+        pfs_from_csv = ti.read_positionfixes_csv(os.path.join('tests', 'data', 'positionfixes.csv'), sep=';')
+
+        pd.testing.assert_frame_equal(pfs_from_gpd, pfs_from_csv, check_exact=False)
+
+    def test_triplegs_from_gpd(self):
+        gdf = gpd.read_file(os.path.join('tests', 'data', 'triplegs.geojson'))
+        tpls_from_gpd = ti.io.from_geopandas.triplegs_from_gpd(gdf, user_id='User', geom='geometry', tz='utc')
+        tpls_from_csv = ti.read_triplegs_csv(os.path.join('tests', 'data', 'triplegs.csv'), sep=';')
+
+        pd.testing.assert_frame_equal(tpls_from_gpd, tpls_from_csv, check_exact=False)
+
+    def test_staypoints_from_gpd(self):
+        gdf = gpd.read_file(os.path.join('tests', 'data', 'staypoints.geojson'))
+        stps_from_gpd = ti.io.from_geopandas.staypoints_from_gpd(gdf, 'start_time', 'end_time', geom='geometry',
+                                                                 tz='utc')
+        stps_from_csv = ti.read_staypoints_csv(os.path.join('tests', 'data', 'staypoints.csv'), sep=';')
+
+        pd.testing.assert_frame_equal(stps_from_gpd, stps_from_csv, check_exact=False)
+
+    def test_locations_from_gpd(self):
+        # TODO: Problem with multiple geometry columns and geojson format
+        gdf = gpd.read_file(os.path.join('tests', 'data', 'locations.geojson'))
+        plcs_from_gpd = ti.io.from_geopandas.locations_from_gpd(gdf, user_id='User', center='geometry')
+        plcs_from_csv = ti.read_locations_csv(os.path.join('tests', 'data', 'locations.csv'), sep=';')
+
+        # drop the second geometry column manually because not storable in GeoJSON (from Geopandas)
+        plcs_from_csv = plcs_from_csv.drop(columns='extent')
+        pd.testing.assert_frame_equal(plcs_from_csv, plcs_from_gpd, check_exact=False)
+
+    def test_trips_from_gpd(self):
+        df = pd.read_csv(os.path.join('tests', 'data', 'trips.csv'), sep=';')
+        trips_from_gpd = ti.io.from_geopandas.trips_from_gpd(df, tz='utc')
+        trips_from_csv = ti.read_trips_csv(os.path.join('tests', 'data', 'trips.csv'), sep=';')
+
+        pd.testing.assert_frame_equal(trips_from_gpd, trips_from_csv, check_exact=False)
+
+    def test_tours_from_gpd(self):
+        # TODO: implement tests for reading tours from Geopandas
+        pass
diff --git a/tests/preprocessing/test_filter.py b/tests/preprocessing/test_filter.py
index 86dec56..609d9a4 100644
--- a/tests/preprocessing/test_filter.py
+++ b/tests/preprocessing/test_filter.py
@@ -1,5 +1,5 @@
-import pytest
 import os
+
 import geopandas as gpd
 
 import trackintel as ti
@@ -9,7 +9,7 @@ class TestSpatial_filter():
     
     def test_filter_staypoints(self):
         # read staypoints and area file
-        spts = ti.read_staypoints_csv(os.path.join('tests', 'data', 'geolife', 'geolife_staypoints.csv'))
+        spts = ti.read_staypoints_csv(os.path.join('tests', 'data', 'geolife', 'geolife_staypoints.csv'), tz='utc')
         extent = gpd.read_file(os.path.join('tests', 'data', 'area', 'tsinghua.geojson'))
         
         # the projection needs to be defined: WGS84
@@ -29,7 +29,7 @@ class TestSpatial_filter():
         
     def test_filter_triplegs(self):
         # read triplegs and area file
-        tl = ti.read_triplegs_csv(os.path.join('tests', 'data', 'geolife', 'geolife_triplegs.csv'))
+        tl = ti.read_triplegs_csv(os.path.join('tests', 'data', 'geolife', 'geolife_triplegs.csv'), tz='utc')
         extent = gpd.read_file(os.path.join('tests', 'data', 'area', 'tsinghua.geojson'))
         
         # the projection needs to be defined: WGS84
@@ -51,7 +51,7 @@ class TestSpatial_filter():
     
     def test_filter_locations(self):
         # read staypoints and area file
-        spts = ti.read_staypoints_csv(os.path.join('tests', 'data', 'geolife', 'geolife_staypoints.csv'))
+        spts = ti.read_staypoints_csv(os.path.join('tests', 'data', 'geolife', 'geolife_staypoints.csv'), tz='utc')
         extent = gpd.read_file(os.path.join('tests', 'data', 'area', 'tsinghua.geojson'))
         
         # cluster staypoints to locations
diff --git a/tests/preprocessing/test_positionfixes.py b/tests/preprocessing/test_positionfixes.py
index 9f3a78c..1939240 100644
--- a/tests/preprocessing/test_positionfixes.py
+++ b/tests/preprocessing/test_positionfixes.py
@@ -1,14 +1,15 @@
-import sys
-import os
 import datetime
+import os
+import sys
 
 import numpy as np
 
 import trackintel as ti
 
+
 class TestGenerate_staypoints():
     def test_generate_staypoints_sliding_min(self):
-        pfs = ti.read_positionfixes_csv(os.path.join('tests','data','positionfixes.csv'), sep=';')
+        pfs = ti.read_positionfixes_csv(os.path.join('tests', 'data', 'positionfixes.csv'), sep=';')
         pfs, spts = pfs.as_positionfixes.generate_staypoints(method='sliding', dist_threshold=0, time_threshold=0)
         assert len(spts) == len(pfs), "With small thresholds, staypoint extraction should yield each positionfix"
         
@@ -35,7 +36,8 @@ class TestGenerate_triplegs():
         pfs, tpls = pfs.as_positionfixes.generate_triplegs(spts)
 
         # load pregenerated test-triplegs
-        tpls_test = ti.read_triplegs_csv(os.path.join('tests', 'data', 'geolife', 'geolife_triplegs_short.csv'))
+        tpls_test = ti.read_triplegs_csv(os.path.join('tests', 'data', 'geolife', 'geolife_triplegs_short.csv'),
+                                         tz='utc')
 
         assert len(tpls) > 0
         assert len(tpls) == len(tpls)
diff --git a/tests/preprocessing/test_staypoints.py b/tests/preprocessing/test_staypoints.py
index 9729f36..844c068 100644
--- a/tests/preprocessing/test_staypoints.py
+++ b/tests/preprocessing/test_staypoints.py
@@ -1,15 +1,17 @@
 import os
-import pandas as pd
+
 import geopandas as gpd
+import pandas as pd
 from shapely.geometry import Point
 from sklearn.cluster import DBSCAN
 
 import trackintel as ti
 from trackintel.geogr.distances import calculate_distance_matrix
 
+
 class TestGenerate_locations():
     def test_generate_locations_dbscan_hav_euc(self):
-        stps = ti.read_staypoints_csv(os.path.join('tests', 'data', 'geolife', 'geolife_staypoints.csv'))
+        stps = ti.read_staypoints_csv(os.path.join('tests', 'data', 'geolife', 'geolife_staypoints.csv'), tz='utc')
         
         # haversine calculation 
         _, loc_har = stps.as_staypoints.generate_locations(method='dbscan', epsilon=100, 
@@ -29,7 +31,7 @@ class TestGenerate_locations():
             "and euclidean distances"
             
     def test_generate_locations_dbscan_haversine(self):
-        stps = ti.read_staypoints_csv(os.path.join('tests', 'data', 'geolife', 'geolife_staypoints.csv'))
+        stps = ti.read_staypoints_csv(os.path.join('tests', 'data', 'geolife', 'geolife_staypoints.csv'), tz='utc')
         
         # haversine calculation using sklearn.metrics.pairwise_distances, epsilon converted to radius
         stps, locs = stps.as_staypoints.generate_locations(method='dbscan', epsilon=10, 
@@ -44,10 +46,10 @@ class TestGenerate_locations():
         assert len(set(locs['id'])) == len(set(labels)) , "The #location should be the same"
     
     def test_generate_locations_dbscan_loc(self):
-        stps = ti.read_staypoints_csv(os.path.join('tests', 'data', 'geolife', 'geolife_staypoints.csv'))
-        stps, locs = stps.as_staypoints.generate_locations(method='dbscan', epsilon=10, 
-                                                          num_samples=0, distance_matrix_metric='haversine',
-                                                          agg_level='dataset')
+        stps = ti.read_staypoints_csv(os.path.join('tests', 'data', 'geolife', 'geolife_staypoints.csv'), tz='utc')
+        stps, locs = stps.as_staypoints.generate_locations(method='dbscan', epsilon=10,
+                                                           num_samples=0, distance_matrix_metric='haversine',
+                                                           agg_level='dataset')
 
         # create locations as grouped staypoints, another way to create locations
         other_locs = pd.DataFrame(columns=['user_id', 'id','center'])
@@ -71,7 +73,7 @@ class TestGenerate_locations():
         assert all(other_locs['id'] == locs['id']), "The location id should be the same"
     
     def test_generate_locations_dbscan_user_dataset(self):
-        stps = ti.read_staypoints_csv(os.path.join('tests', 'data', 'geolife', 'geolife_staypoints.csv'))
+        stps = ti.read_staypoints_csv(os.path.join('tests', 'data', 'geolife', 'geolife_staypoints.csv'), tz='utc')
         # take the first row and duplicate once
         stps = stps.head(1)
         stps = stps.append(stps, ignore_index=True)
@@ -91,7 +93,7 @@ class TestGenerate_locations():
         assert loc_us_num == 2, "Considering user staypoints separately, there should be two locations"
     
     def test_generate_locations_dbscan_min(self):
-        pfs = ti.read_positionfixes_csv(os.path.join('tests','data','positionfixes.csv'), sep=';')
+        pfs = ti.read_positionfixes_csv(os.path.join('tests', 'data', 'positionfixes.csv'), sep=';', tz='utc')
         _, stps = pfs.as_positionfixes.generate_staypoints(method='sliding', dist_threshold=0, time_threshold=0)
         _, locs_user = stps.as_staypoints.generate_locations(method='dbscan', epsilon=1e-18, 
                                                             num_samples=0, agg_level='user')
@@ -101,7 +103,7 @@ class TestGenerate_locations():
         assert len(locs_data) == len(stps), "With small hyperparameters, clustering should not reduce the number"
 
     def test_generate_locations_dbscan_max(self):
-        pfs = ti.read_positionfixes_csv(os.path.join('tests','data','positionfixes.csv'), sep=';')
+        pfs = ti.read_positionfixes_csv(os.path.join('tests', 'data', 'positionfixes.csv'), sep=';', tz='utc')
         _, stps = pfs.as_positionfixes.generate_staypoints(method='sliding', dist_threshold=0, time_threshold=0)
         _, locs_user = stps.as_staypoints.generate_locations(method='dbscan', epsilon=1e18, 
                                                             num_samples=1000, agg_level='user')
@@ -111,7 +113,7 @@ class TestGenerate_locations():
         assert len(locs_data) == 0, "With large hyperparameters, every dataset location is an outlier"
     
     def test_generate_locations_dtype_consistent(self):
-        stps = ti.read_staypoints_csv(os.path.join('tests', 'data', 'geolife', 'geolife_staypoints.csv'))
+        stps = ti.read_staypoints_csv(os.path.join('tests', 'data', 'geolife', 'geolife_staypoints.csv'), tz='utc')
         # 
         stps, locs = stps.as_staypoints.generate_locations(method='dbscan', 
                                                            epsilon=10, 
diff --git a/tests/preprocessing/test_triplegs.py b/tests/preprocessing/test_triplegs.py
index 565dfb6..004e9c7 100644
--- a/tests/preprocessing/test_triplegs.py
+++ b/tests/preprocessing/test_triplegs.py
@@ -1,10 +1,12 @@
 import os
-import pandas as pd
+
 import geopandas as gpd
+import pandas as pd
 from shapely.geometry import Point, LineString
 
 import trackintel as ti
 
+
 class TestSmoothen_triplegs():
     def test_smoothen_triplegs(self):
         tpls = ti.read_triplegs_csv(os.path.join('tests','data','triplegs_with_too_many_points_test.csv'), sep=';')
@@ -29,14 +31,14 @@ class TestGenerate_trips():
         gap_threshold = 15
         # load pregenerated trips
         trips_loaded = pd.read_csv(os.path.join('tests', 'data', 'geolife_long', 'trips.csv'), index_col='id')
-        trips_loaded['started_at'] = pd.to_datetime(trips_loaded['started_at'])
-        trips_loaded['finished_at'] = pd.to_datetime(trips_loaded['finished_at'])
+        trips_loaded['started_at'] = pd.to_datetime(trips_loaded['started_at'], utc=True)
+        trips_loaded['finished_at'] = pd.to_datetime(trips_loaded['finished_at'], utc=True)
         trips_loaded['user_id'] = trips_loaded['user_id'].astype("int64")
 
         # create trips from geolife (based on positionfixes)
         pfs = ti.io.dataset_reader.read_geolife(os.path.join('tests', 'data', 'geolife_long'))
         pfs, stps = pfs.as_positionfixes.generate_staypoints(method='sliding', dist_threshold=25,
-                                                        time_threshold=5 * 60)
+                                                             time_threshold=5 * 60)
         stps = stps.as_staypoints.create_activity_flag()
         pfs, tpls = pfs.as_positionfixes.generate_triplegs(stps)
 
@@ -93,6 +95,8 @@ class TestGenerate_trips():
                               infer_datetime_format=True, dayfirst=True)
         stps_in['geom'] = Point(1, 1)
         stps_in = gpd.GeoDataFrame(stps_in, geometry='geom')
+        stps_in = ti.io.staypoints_from_gpd(stps_in, tz='utc')
+
         assert stps_in.as_staypoints
 
         tpls_in = pd.read_csv(os.path.join('.', 'tests', 'data', 'trips', 'triplegs_gaps.csv'),
@@ -100,25 +104,27 @@ class TestGenerate_trips():
                               infer_datetime_format=True, dayfirst=True)
         tpls_in['geom'] = LineString([[1, 1], [2, 2]])
         tpls_in = gpd.GeoDataFrame(tpls_in, geometry='geom')
+        tpls_in = ti.io.triplegs_from_gpd(tpls_in, tz='utc')
+
         assert tpls_in.as_triplegs
 
         # load ground truth data
         trips_loaded = pd.read_csv(os.path.join('.', 'tests', 'data', 'trips', 'trips_gaps.csv'), index_col='id')
-        trips_loaded['started_at'] = pd.to_datetime(trips_loaded['started_at'])
-        trips_loaded['finished_at'] = pd.to_datetime(trips_loaded['finished_at'])
+        trips_loaded['started_at'] = pd.to_datetime(trips_loaded['started_at'], utc=True)
+        trips_loaded['finished_at'] = pd.to_datetime(trips_loaded['finished_at'], utc=True)
 
         stps_tpls_loaded = pd.read_csv(os.path.join('.', 'tests', 'data', 'trips', 'stps_tpls_gaps.csv')
                                        , index_col='id')
-        stps_tpls_loaded['started_at'] = pd.to_datetime(stps_tpls_loaded['started_at'])
-        stps_tpls_loaded['started_at_next'] = pd.to_datetime(stps_tpls_loaded['started_at_next'])
-        stps_tpls_loaded['finished_at'] = pd.to_datetime(stps_tpls_loaded['finished_at'])
+        stps_tpls_loaded['started_at'] = pd.to_datetime(stps_tpls_loaded['started_at'], utc=True)
+        stps_tpls_loaded['started_at_next'] = pd.to_datetime(stps_tpls_loaded['started_at_next'], utc=True)
+        stps_tpls_loaded['finished_at'] = pd.to_datetime(stps_tpls_loaded['finished_at'], utc=True)
 
         # generate trips and a joint staypoint/triplegs dataframe
         stps_proc, tpls_proc, trips = ti.preprocessing.triplegs.generate_trips(stps_in, tpls_in,
-                                                                               gap_threshold=15, 
+                                                                               gap_threshold=15,
                                                                                id_offset=0)
         spts_tpls = _create_debug_spts_tpls_data(stps_proc, tpls_proc, gap_threshold=gap_threshold)
-        
+
         trips.set_index('id', inplace=True)
         # test if generated trips are equal
         pd.testing.assert_frame_equal(trips_loaded, trips)
diff --git a/tests/test_staypoints.py b/tests/test_staypoints.py
index 115dcd9..73fb82f 100644
--- a/tests/test_staypoints.py
+++ b/tests/test_staypoints.py
@@ -1,11 +1,16 @@
-import pytest
 import trackintel as ti
 import os
 import pandas as pd
+import os
+
+import pandas as pd
+
+import trackintel as ti
+
 
 class TestCreate_activity_flag():
     def test_create_activity_flag(self):
-        spts_test = ti.read_staypoints_csv(os.path.join('tests', 'data', 'geolife', 'geolife_staypoints.csv'))
+        spts_test = ti.read_staypoints_csv(os.path.join('tests', 'data', 'geolife', 'geolife_staypoints.csv'), tz='utc')
 
         activity_true = spts_test['activity'].copy()
         spts_test['activity'] = False