-
-
Notifications
You must be signed in to change notification settings - Fork 17.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ENH: Support writing timestamps with timezones with to_sql #22654
Changes from 18 commits
776240b
befd200
e9f122f
969d2da
cc79b90
24dbaa5
6e86d58
c7c4a7a
6aa4878
513bbc8
58772e1
1a29148
96e9188
ded5584
d575089
a7d1b3e
305759c
7a79531
24823f8
7db4eaa
76e46dc
978a0d3
8025248
0e89370
bab5cfb
de62788
e940279
8c754b5
e85842f
5af83f7
6b3a3f1
c4304ec
1054fdb
f21c755
f872ff7
ef3b20f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2306,6 +2306,13 @@ def to_sql(self, name, con, schema=None, if_exists='fail', index=True, | |
-------- | ||
pandas.read_sql : read a DataFrame from a table | ||
|
||
Notes | ||
----- | ||
Timezone aware datetime columns will be written as | ||
``Timestamp with timezone`` type with SQLAlchemy if supported by the | ||
database. Otherwise, the datetimes will be stored as local, naive | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think "local, naive" timestamps can be a bit confusing. Is it local to the system running python, or local to the timezone of the datetime (so basically what you get with |
||
timestamps. | ||
|
||
References | ||
---------- | ||
.. [1] http://docs.sqlalchemy.org | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -962,7 +962,8 @@ def test_sqlalchemy_type_mapping(self): | |
utc=True)}) | ||
db = sql.SQLDatabase(self.conn) | ||
table = sql.SQLTable("test_type", db, frame=df) | ||
assert isinstance(table.table.c['time'].type, sqltypes.DateTime) | ||
# GH 9086: TIMESTAMP is the suggested type for datetimes with timezones | ||
assert isinstance(table.table.c['time'].type, sqltypes.TIMESTAMP) | ||
|
||
def test_database_uri_string(self): | ||
|
||
|
@@ -1362,9 +1363,42 @@ def check(col): | |
df = sql.read_sql_table("types_test_data", self.conn) | ||
check(df.DateColWithTz) | ||
|
||
def test_datetime_with_timezone_roundtrip(self): | ||
# GH 9086 | ||
# Write datetimetz data to a db and read it back | ||
# For dbs that support timestamps with timezones, should get back UTC | ||
# otherwise naive data should be returned | ||
expected = DataFrame({'A': date_range( | ||
'2013-01-01 09:00:00', periods=3, tz='US/Pacific' | ||
)}) | ||
expected.to_sql('test_datetime_tz', self.conn) | ||
|
||
if self.flavor == 'postgresql': | ||
# SQLalchemy "timezones" (i.e. offsets) are coerced to UTC | ||
expected['A'] = expected['A'].dt.tz_convert('UTC') | ||
else: | ||
# Otherwise, timestamps are returned as local, naive | ||
expected['A'] = expected['A'].dt.tz_localize(None) | ||
|
||
result = sql.read_sql_table('test_datetime_tz', self.conn) | ||
result = result.drop('index', axis=1) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can put |
||
tm.assert_frame_equal(result, expected) | ||
|
||
result = sql.read_sql_query( | ||
'SELECT * FROM test_datetime_tz', self.conn | ||
) | ||
result = result.drop('index', axis=1) | ||
if self.flavor == 'sqlite': | ||
# read_sql_query does not return datetime type like read_sql_table | ||
assert isinstance(result.loc[0, 'A'], string_types) | ||
result['A'] = to_datetime(result['A']) | ||
tm.assert_frame_equal(result, expected) | ||
|
||
def test_date_parsing(self): | ||
# No Parsing | ||
df = sql.read_sql_table("types_test_data", self.conn) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't we rather test that it is not parsed instead of removing it? (but I agree this currently looks like this is not doing much) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh I see. My cursory glance thought it was duplicate of the line below; you're right, I will try to add an assert to this result as well |
||
expected_type = object if self.flavor == 'sqlite' else np.datetime64 | ||
assert issubclass(df.DateCol.dtype.type, expected_type) | ||
|
||
df = sql.read_sql_table("types_test_data", self.conn, | ||
parse_dates=['DateCol']) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be