Skip to content

Commit a0cedf8

Browse files
ENH: add support for datetime.date/time in to_sql (GH6932)
1 parent b8c36be commit a0cedf8

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
@@ -427,6 +427,7 @@ Enhancements
427427
~~~~~~~~~~~~
428428

429429
- 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`)
430+
- Added support for writing ``datetime.date`` and ``datetime.time`` object columns with ``to_sql`` (:issue:`6932`).
430431

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

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
@@ -684,13 +685,14 @@ def _get_column_names_and_types(self, dtype_mapper):
684685
if self.index is not None:
685686
for i, idx_label in enumerate(self.index):
686687
idx_type = dtype_mapper(
687-
self.frame.index.get_level_values(i).dtype)
688+
self.frame.index.get_level_values(i))
688689
column_names_and_types.append((idx_label, idx_type))
689690

690-
column_names_and_types += zip(
691-
list(map(str, self.frame.columns)),
692-
map(dtype_mapper, self.frame.dtypes)
693-
)
691+
column_names_and_types += [
692+
(str(self.frame.columns[i]),
693+
dtype_mapper(self.frame.iloc[:,i]))
694+
for i in range(len(self.frame.columns))
695+
]
694696
return column_names_and_types
695697

696698
def _create_table_statement(self):
@@ -756,30 +758,33 @@ def _harmonize_columns(self, parse_dates=None):
756758
except KeyError:
757759
pass # this column not in results
758760

759-
def _sqlalchemy_type(self, arr_or_dtype):
761+
def _sqlalchemy_type(self, col):
760762
from sqlalchemy.types import (BigInteger, Float, Text, Boolean,
761-
DateTime, Date, Interval)
763+
DateTime, Date, Time, Interval)
762764

763-
if arr_or_dtype is date:
764-
return Date
765-
if com.is_datetime64_dtype(arr_or_dtype):
765+
if com.is_datetime64_dtype(col):
766766
try:
767-
tz = arr_or_dtype.tzinfo
767+
tz = col.tzinfo
768768
return DateTime(timezone=True)
769769
except:
770770
return DateTime
771-
if com.is_timedelta64_dtype(arr_or_dtype):
771+
if com.is_timedelta64_dtype(col):
772772
warnings.warn("the 'timedelta' type is not supported, and will be "
773773
"written as integer values (ns frequency) to the "
774774
"database.", UserWarning)
775775
return BigInteger
776-
elif com.is_float_dtype(arr_or_dtype):
776+
elif com.is_float_dtype(col):
777777
return Float
778-
elif com.is_integer_dtype(arr_or_dtype):
778+
elif com.is_integer_dtype(col):
779779
# TODO: Refine integer size.
780780
return BigInteger
781-
elif com.is_bool_dtype(arr_or_dtype):
781+
elif com.is_bool_dtype(col):
782782
return Boolean
783+
inferred = lib.infer_dtype(com._ensure_object(col))
784+
if inferred == 'date':
785+
return Date
786+
if inferred == 'time':
787+
return Time
783788
return Text
784789

785790
def _numpy_type(self, sqltype):
@@ -908,7 +913,11 @@ def _create_sql_schema(self, frame, table_name):
908913
},
909914
'date': {
910915
'mysql': 'DATE',
911-
'sqlite': 'TIMESTAMP',
916+
'sqlite': 'DATE',
917+
},
918+
'time': {
919+
'mysql': 'TIME',
920+
'sqlite': 'TIME',
912921
},
913922
'bool': {
914923
'mysql': 'BOOLEAN',
@@ -1014,8 +1023,8 @@ def _create_table_statement(self):
10141023
create_statement = template % {'name': self.name, 'columns': columns}
10151024
return create_statement
10161025

1017-
def _sql_type_name(self, dtype):
1018-
pytype = dtype.type
1026+
def _sql_type_name(self, col):
1027+
pytype = col.dtype.type
10191028
pytype_name = "text"
10201029
if issubclass(pytype, np.floating):
10211030
pytype_name = "float"
@@ -1029,10 +1038,14 @@ def _sql_type_name(self, dtype):
10291038
elif issubclass(pytype, np.datetime64) or pytype is datetime:
10301039
# Caution: np.datetime64 is also a subclass of np.number.
10311040
pytype_name = "datetime"
1032-
elif pytype is datetime.date:
1033-
pytype_name = "date"
10341041
elif issubclass(pytype, np.bool_):
10351042
pytype_name = "bool"
1043+
elif issubclass(pytype, np.object):
1044+
pytype = lib.infer_dtype(com._ensure_object(col))
1045+
if pytype == "date":
1046+
pytype_name = "date"
1047+
elif pytype == "time":
1048+
pytype_name = "time"
10361049

10371050
return _SQL_TYPES[pytype_name][self.pd_sql.flavor]
10381051

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)