Skip to content

Commit 1e3da90

Browse files
Merge pull request #8090 from jorisvandenbossche/sql-date-time
ENH: add support for datetime.date/time in to_sql (GH6932)
2 parents 6efa813 + a0cedf8 commit 1e3da90

File tree

3 files changed

+66
-21
lines changed

3 files changed

+66
-21
lines changed

doc/source/v0.15.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,7 @@ Enhancements
428428
~~~~~~~~~~~~
429429

430430
- Added support for a ``chunksize`` parameter to ``to_sql`` function. This allows DataFrame to be written in chunks and avoid packet-size overflow errors (:issue:`8062`)
431+
- Added support for writing ``datetime.date`` and ``datetime.time`` object columns with ``to_sql`` (:issue:`6932`).
431432

432433
- Added support for bool, uint8, uint16 and uint32 datatypes in ``to_stata`` (:issue:`7097`, :issue:`7365`)
433434

pandas/io/sql.py

+33-20
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import re
1212
import numpy as np
1313

14+
import pandas.lib as lib
1415
import pandas.core.common as com
1516
from pandas.compat import lzip, map, zip, raise_with_traceback, string_types
1617
from pandas.core.api import DataFrame, Series
@@ -689,13 +690,14 @@ def _get_column_names_and_types(self, dtype_mapper):
689690
if self.index is not None:
690691
for i, idx_label in enumerate(self.index):
691692
idx_type = dtype_mapper(
692-
self.frame.index.get_level_values(i).dtype)
693+
self.frame.index.get_level_values(i))
693694
column_names_and_types.append((idx_label, idx_type))
694695

695-
column_names_and_types += zip(
696-
list(map(str, self.frame.columns)),
697-
map(dtype_mapper, self.frame.dtypes)
698-
)
696+
column_names_and_types += [
697+
(str(self.frame.columns[i]),
698+
dtype_mapper(self.frame.iloc[:,i]))
699+
for i in range(len(self.frame.columns))
700+
]
699701
return column_names_and_types
700702

701703
def _create_table_statement(self):
@@ -761,30 +763,33 @@ def _harmonize_columns(self, parse_dates=None):
761763
except KeyError:
762764
pass # this column not in results
763765

764-
def _sqlalchemy_type(self, arr_or_dtype):
766+
def _sqlalchemy_type(self, col):
765767
from sqlalchemy.types import (BigInteger, Float, Text, Boolean,
766-
DateTime, Date, Interval)
768+
DateTime, Date, Time, Interval)
767769

768-
if arr_or_dtype is date:
769-
return Date
770-
if com.is_datetime64_dtype(arr_or_dtype):
770+
if com.is_datetime64_dtype(col):
771771
try:
772-
tz = arr_or_dtype.tzinfo
772+
tz = col.tzinfo
773773
return DateTime(timezone=True)
774774
except:
775775
return DateTime
776-
if com.is_timedelta64_dtype(arr_or_dtype):
776+
if com.is_timedelta64_dtype(col):
777777
warnings.warn("the 'timedelta' type is not supported, and will be "
778778
"written as integer values (ns frequency) to the "
779779
"database.", UserWarning)
780780
return BigInteger
781-
elif com.is_float_dtype(arr_or_dtype):
781+
elif com.is_float_dtype(col):
782782
return Float
783-
elif com.is_integer_dtype(arr_or_dtype):
783+
elif com.is_integer_dtype(col):
784784
# TODO: Refine integer size.
785785
return BigInteger
786-
elif com.is_bool_dtype(arr_or_dtype):
786+
elif com.is_bool_dtype(col):
787787
return Boolean
788+
inferred = lib.infer_dtype(com._ensure_object(col))
789+
if inferred == 'date':
790+
return Date
791+
if inferred == 'time':
792+
return Time
788793
return Text
789794

790795
def _numpy_type(self, sqltype):
@@ -913,7 +918,11 @@ def _create_sql_schema(self, frame, table_name):
913918
},
914919
'date': {
915920
'mysql': 'DATE',
916-
'sqlite': 'TIMESTAMP',
921+
'sqlite': 'DATE',
922+
},
923+
'time': {
924+
'mysql': 'TIME',
925+
'sqlite': 'TIME',
917926
},
918927
'bool': {
919928
'mysql': 'BOOLEAN',
@@ -1019,8 +1028,8 @@ def _create_table_statement(self):
10191028
create_statement = template % {'name': self.name, 'columns': columns}
10201029
return create_statement
10211030

1022-
def _sql_type_name(self, dtype):
1023-
pytype = dtype.type
1031+
def _sql_type_name(self, col):
1032+
pytype = col.dtype.type
10241033
pytype_name = "text"
10251034
if issubclass(pytype, np.floating):
10261035
pytype_name = "float"
@@ -1034,10 +1043,14 @@ def _sql_type_name(self, dtype):
10341043
elif issubclass(pytype, np.datetime64) or pytype is datetime:
10351044
# Caution: np.datetime64 is also a subclass of np.number.
10361045
pytype_name = "datetime"
1037-
elif pytype is datetime.date:
1038-
pytype_name = "date"
10391046
elif issubclass(pytype, np.bool_):
10401047
pytype_name = "bool"
1048+
elif issubclass(pytype, np.object):
1049+
pytype = lib.infer_dtype(com._ensure_object(col))
1050+
if pytype == "date":
1051+
pytype_name = "date"
1052+
elif pytype == "time":
1053+
pytype_name = "time"
10411054

10421055
return _SQL_TYPES[pytype_name][self.pd_sql.flavor]
10431056

pandas/io/tests/test_sql.py

+32-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import warnings
2727
import numpy as np
2828

29-
from datetime import datetime
29+
from datetime import datetime, date, time
3030

3131
from pandas import DataFrame, Series, Index, MultiIndex, isnull
3232
from pandas import date_range, to_datetime, to_timedelta
@@ -35,6 +35,7 @@
3535
from pandas.core.datetools import format as date_format
3636

3737
import pandas.io.sql as sql
38+
from pandas.io.sql import read_sql_table, read_sql_query
3839
import pandas.util.testing as tm
3940

4041

@@ -976,6 +977,21 @@ def test_datetime_NaT(self):
976977
else:
977978
tm.assert_frame_equal(result, df)
978979

980+
def test_datetime_date(self):
981+
# test support for datetime.date
982+
df = DataFrame([date(2014, 1, 1), date(2014, 1, 2)], columns=["a"])
983+
df.to_sql('test_date', self.conn, index=False)
984+
res = read_sql_table('test_date', self.conn)
985+
# comes back as datetime64
986+
tm.assert_series_equal(res['a'], to_datetime(df['a']))
987+
988+
def test_datetime_time(self):
989+
# test support for datetime.time
990+
df = DataFrame([time(9, 0, 0), time(9, 1, 30)], columns=["a"])
991+
df.to_sql('test_time', self.conn, index=False)
992+
res = read_sql_table('test_time', self.conn)
993+
tm.assert_frame_equal(res, df)
994+
979995
def test_mixed_dtype_insert(self):
980996
# see GH6509
981997
s1 = Series(2**25 + 1,dtype=np.int32)
@@ -1269,6 +1285,21 @@ def test_roundtrip(self):
12691285
def test_execute_sql(self):
12701286
self._execute_sql()
12711287

1288+
def test_datetime_date(self):
1289+
# test support for datetime.date
1290+
df = DataFrame([date(2014, 1, 1), date(2014, 1, 2)], columns=["a"])
1291+
df.to_sql('test_date', self.conn, index=False, flavor=self.flavor)
1292+
res = read_sql_query('SELECT * FROM test_date', self.conn)
1293+
if self.flavor == 'sqlite':
1294+
# comes back as strings
1295+
tm.assert_frame_equal(res, df.astype(str))
1296+
elif self.flavor == 'mysql':
1297+
tm.assert_frame_equal(res, df)
1298+
1299+
def test_datetime_time(self):
1300+
# test support for datetime.time
1301+
raise nose.SkipTest("datetime.time not supported for sqlite fallback")
1302+
12721303

12731304
class TestMySQLLegacy(TestSQLiteLegacy):
12741305
"""

0 commit comments

Comments
 (0)