-
Notifications
You must be signed in to change notification settings - Fork 19
Support timezone-aware datetime objects #178
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
base: master
Are you sure you want to change the base?
Conversation
connection: - Added support for explicit TimeZone connection property datatype: - Use Julian calendar for date/datetime before 1582 Oct 4. When the Gregorian calendar was introduced: . The day after October 4, 1582 (Julian) became October 15, 1582 (Gregorian). . That 10-day jump is not accounted for in C's calendar logic. . Julian calendar handles leap years differently. The utc calendar the engine is using understands this the python functions based on C calendar logic do not. - time.mktime , time.localtime don't support older dates while engine does changing to use calendar above and calculate time, avoid these issues. - use of datetime + timezone should handle daylight savings times and not be off by an hour. tests: - Use connection property TimeZone to change timezone - Support timezone-aware datetime - Use pytz correctly with localize if pytz is used - Use zoneinfo.ZoneInfo if available encodedsession: - Manage and verify TimeZone settings on per connection basis - Allows connections with different TimeZone setting from same application requirements.txt: - Add requirement for tzlocal, should be available on all supported python versions. There is a change in api between releases , this is handled in the code.
I expect the cursor test failure is related to the callback to engine to get the real time zone used. |
Hi @butson I will try to get to the review proper this week. Just to start out, there are a number of PEP8 violations in the new code. What I recommend for finding them, if you don't already have an IDE which is capable of enabling flake8 or similar add-ons to do this sort of syntax checking, is to get Visual Studio Code (this is not the full Visual Studio suite, it's a stand-alone, open source editor which runs on all major plaforms: Windows, GNU/Linux, and MacOS): https://code.visualstudio.com/ . Then you should configure it for Python development by installing these extensions: Python (Microsoft), Pylance (Microsoft), and Flake8. Once you have done that (I recommend restarting after you install the extensions) you can visit the files in your Git repository in the editor, and use CTRL-SHIFT-M to open the "Problems" window and see all the issues reported there. If there are errors not in code you didn't write/update you don't need to worry about it. In particular, the "imported but not used" messages should mostly be ignored, because these tools don't grok the "type:" comments we're using with mypy. But, there are lots of whitespace issues such as, missing spaces after commas etc. in the changed code: these need to be addressed. |
Secondly, the error found by circleci in the test_result_set_gets_closed test is due to an incorrect implementation of the new method _set_timezone(). If you write it like this, instead, it will work without errors:
Note, you can run these tests locally on your system; you need to set it up but once you do, you can verify changes before you push to GitHub and wait for the CircleCi results. If you're not sure how to do it let me know. |
Thanks @butson . Note, there are a lot of mypy and pylint errors as well. You can run On master, mypy is 100% clean and pylint only complains about a few TODO comments. |
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.
The thing I'm curious about is that there don't seem to be any NEW tests that test the behavior changes that were made here. It seems like only the existing tests were updated. Put another way, if we kept only the changes to the tests but did NOT add any of the other changes (except the timezone parameter of course), would all the tests pass?
The changes for the timezone parameter are good, but I don't really understand what some of the other changes are accomplishing (like the retrieval of the server zone info etc.) and I was hoping to see some tests that were making that more clear: tests that represented the missing or incorrect facilities and would fail without the changes, and then pass after the changes. Adding/retrieving dates that used to not work, but now work properly, etc.
microsecond=microsecond, tzinfo=TimeZone.utc) | ||
|
||
def timezone_aware(tstamp, tzinfo): | ||
return tstamp.replace(tzinfo=tzinfo) |
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.
This method is missing a # type: comment
def timezone_aware(tstamp, tzinfo): | ||
return tzinfo.localize(tstamp, is_dst=None) |
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.
Missing a # type: comment
from .exception import DataError | ||
from .calendar import ymd2day, day2ymd | ||
|
||
try: |
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.
It would be good to add a short comment to the two legs of this try/except explaining under what circumstances each one will be used.
@@ -0,0 +1,146 @@ | |||
|
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.
Please add copyright info here, whatever you think is correct (for example, the copyright of wherever you copied the code from, if you did, or else for DS if you wrote it yourself from scratch).
It's hard for me to believe that there's no facility that does this same thing already available but I didn't look so maybe not.
hours = (seconds // 3600) % 24 | ||
minutes = (seconds // 60) % 60 | ||
seconds = seconds % 60 | ||
microseconds = micro % 1000000 |
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.
It's a little strange to mod a variable named micro
by 1000000
; the only way this could have any effect is if the value passed in was >1 second. If that's the case, do we really just want to ignore that silently?
maybe this should be an assert...?
sql = "select value from system.connectionproperties where property='TimeZone'" | ||
self.execute_statement(stmt, sql) | ||
rset = self.fetch_result_set(stmt).fetchone() | ||
self.timezone_name = rset[0] |
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.
I wonder if we shouldn't add this into a try block and put the close_statement() in a finally clause at the end to be sure we close the connection.
I really don't like this idea of having a secret statement run behind the user's back every time they open a connection. Is it absolutely necessary? Do the other drivers like JDBC and C/C++ do this? If not why is it needed here?
@@ -931,6 +986,21 @@ def getClob(self): | |||
|
|||
raise DataError('Not a clob') | |||
|
|||
__shifters = [1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000] |
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.
I still don't like this. We should just compute the value inline using 10 ** scale, it's much simpler to understand. There can't possibly be any performance benefit to this that is noticeable.
time.tzset() | ||
|
||
con = self._connect() | ||
con = self._connect(options={'TimeZone':'EST5EDT'}) |
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.
Yay!
oldtz = os.environ.get('TZ') | ||
try: |
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.
You should remove oldtz and also the try/finally clause. You don't need to reset the timezone anymore, if you're setting the TZ in the connection.
#!/usr/bin/env python | ||
# -*- coding: utf-8 -*- |
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.
These are not needed, but there should be a copyright statement on this file.
connection:
datatype:
tests:
encodedsession:
requirements.txt: