Repository: pandas-dev/pandas

Issue:
BUG: Timestamps in object column not written as DateTime data in to_sql
When you have datetime data with timezone info attached, the data is stored as `Timestamp` objects in a `object` dtyped column, instead of in a ´datetime64´ column (as this cannot hold the timezone info). 
But, this causes `to_sql` to gives this the `TEXT` type:

With a postgresql engine:

```
In [48]: times = ['201412120154', '201412110254']
   ....: df = pd.DataFrame()
   ....: df['time'] = pd.to_datetime(times, utc=True)

In [49]: df
Out[49]:
    time
0   2014-12-12 01:54:00+00:00
1   2014-12-11 02:54:00+00:00

In [50]: df.dtypes
Out[50]:
time    object
dtype: object

In [51]: df.time.values
Out[51]:
array([Timestamp('2014-12-12 01:54:00+0000', tz='UTC'),
       Timestamp('2014-12-11 02:54:00+0000', tz='UTC')], dtype=object)

In [53]: print pd.io.sql.get_schema(df, 'test', con=engine_postgres)

CREATE TABLE test (
    time TEXT
)
```

This is because:

```
In [54]: pd.lib.infer_dtype(df['time'])
Out[54]: 
'datetime'
```

instead of `datetime64`, and this case is not handled at the moment.



Discussion:

Resolving patch:
diff --git a/pandas/io/sql.py b/pandas/io/sql.py
--- a/pandas/io/sql.py
+++ b/pandas/io/sql.py
@@ -911,7 +911,7 @@ def _sqlalchemy_type(self, col):
         from sqlalchemy.types import (BigInteger, Float, Text, Boolean,
             DateTime, Date, Time)
 
-        if col_type == 'datetime64': 
+        if col_type == 'datetime64' or col_type == 'datetime':
             try:
                 tz = col.tzinfo
                 return DateTime(timezone=True)


Test patch:
diff --git a/pandas/io/tests/test_sql.py b/pandas/io/tests/test_sql.py
--- a/pandas/io/tests/test_sql.py
+++ b/pandas/io/tests/test_sql.py
@@ -792,6 +792,15 @@ def _get_index_columns(self, tbl_name):
         ixs = [i['column_names'] for i in ixs]
         return ixs
 
+    def test_sqlalchemy_type_mapping(self):
+
+        # Test Timestamp objects (no datetime64 because of timezone) (GH9085)
+        df = DataFrame({'time': to_datetime(['201412120154', '201412110254'],
+                                            utc=True)})
+        db = sql.SQLDatabase(self.conn)
+        table = sql.SQLTable("test_type", db, frame=df)
+        self.assertTrue(isinstance(table.table.c['time'].type, sqltypes.DateTime))
+
 
 class TestSQLiteFallbackApi(_TestSQLApi):
     """
@@ -853,6 +862,24 @@ def test_uquery(self):
             rows = sql.uquery("SELECT * FROM iris LIMIT 1", con=self.conn)
         self.assertEqual(rows, -1)
 
+    def _get_sqlite_column_type(self, schema, column):
+
+        for col in schema.split('\n'):
+            if col.split()[0].strip('[]') == column:
+                return col.split()[1]
+        raise ValueError('Column %s not found' % (column))
+
+    def test_sqlite_type_mapping(self):
+
+        # Test Timestamp objects (no datetime64 because of timezone) (GH9085)
+        df = DataFrame({'time': to_datetime(['201412120154', '201412110254'],
+                                            utc=True)})
+        db = sql.SQLiteDatabase(self.conn, self.flavor)
+        table = sql.SQLiteTable("test_type", db, frame=df)
+        schema = table.sql_schema()
+        self.assertEqual(self._get_sqlite_column_type(schema, 'time'),
+                         "TIMESTAMP")
+
 
 #------------------------------------------------------------------------------
 #--- Database flavor specific tests
@@ -1550,7 +1577,7 @@ def test_dtype(self):
 
         # sqlite stores Boolean values as INTEGER
         self.assertEqual(self._get_sqlite_column_type('dtype_test', 'B'), 'INTEGER')
-        
+
         self.assertEqual(self._get_sqlite_column_type('dtype_test2', 'B'), 'STRING')
         self.assertRaises(ValueError, df.to_sql,
                           'error', self.conn, dtype={'B': bool})
@@ -1558,7 +1585,7 @@ def test_dtype(self):
     def test_notnull_dtype(self):
         if self.flavor == 'mysql':
             raise nose.SkipTest('Not applicable to MySQL legacy')
-            
+
         cols = {'Bool': Series([True,None]),
                 'Date': Series([datetime(2012, 5, 1), None]),
                 'Int' : Series([1, None], dtype='object'),