diff -r 2b7b203e3909 Doc/includes/sqlite3/pysqlite_datetime.py --- a/Doc/includes/sqlite3/pysqlite_datetime.py Wed Jan 11 20:18:03 2017 +0200 +++ b/Doc/includes/sqlite3/pysqlite_datetime.py Thu Jan 12 13:42:43 2017 +0800 @@ -3,18 +3,25 @@ con = sqlite3.connect(":memory:", detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES) cur = con.cursor() -cur.execute("create table test(d date, ts timestamp)") +cur.execute("create table test(d date, ts timestamp, tsz timestamptz)") today = datetime.date.today() now = datetime.datetime.now() +utcnow = datetime.datetime.now(datetime.timezone.utc) -cur.execute("insert into test(d, ts) values (?, ?)", (today, now)) -cur.execute("select d, ts from test") +cur.execute("insert into test(d, ts, tsz) values (?, ?, ?)", (today, now, utcnow)) +cur.execute("select d, ts, tsz from test") row = cur.fetchone() print(today, "=>", row[0], type(row[0])) print(now, "=>", row[1], type(row[1])) +print(utcnow, "=>", row[2], type(row[2])) -cur.execute('select current_date as "d [date]", current_timestamp as "ts [timestamp]"') +cur.execute('''select + current_date as "d [date]", + current_timestamp as "ts [timestamp]", + current_timestamp as "tsz [timestamptz]" +''') row = cur.fetchone() print("current_date", row[0], type(row[0])) print("current_timestamp", row[1], type(row[1])) +print("current_timestamp", row[2], type(row[2])) diff -r 2b7b203e3909 Doc/library/sqlite3.rst --- a/Doc/library/sqlite3.rst Wed Jan 11 20:18:03 2017 +0200 +++ b/Doc/library/sqlite3.rst Thu Jan 12 13:42:43 2017 +0800 @@ -902,8 +902,10 @@ module. They will be sent as ISO dates/ISO timestamps to SQLite. The default converters are registered under the name "date" for -:class:`datetime.date` and under the name "timestamp" for -:class:`datetime.datetime`. +:class:`datetime.date`, under the name "timestamp" for naive +:class:`datetime.datetime` objects and under the name "timestamptz" +for aware :class:`datetime.datetime` objects. Using "timestamp" for +aware :class:`datetime.datetime` objects may cause unexpected results. This way, you can use date/timestamps from Python without any additional fiddling in most cases. The format of the adapters is also compatible with the @@ -917,6 +919,8 @@ numbers, its value will be truncated to microsecond precision by the timestamp converter. +.. versionadded:: 3.7 + Add "timestamptz" converter for aware :class:`datetime.datetime` objects. .. _sqlite3-controlling-transactions: diff -r 2b7b203e3909 Lib/sqlite3/dbapi2.py --- a/Lib/sqlite3/dbapi2.py Wed Jan 11 20:18:03 2017 +0200 +++ b/Lib/sqlite3/dbapi2.py Thu Jan 12 13:42:43 2017 +0800 @@ -63,24 +63,46 @@ def convert_date(val): return datetime.date(*map(int, val.split(b"-"))) - def convert_timestamp(val): - datepart, timepart = val.split(b" ") - year, month, day = map(int, datepart.split(b"-")) - timepart_full = timepart.split(b".") - hours, minutes, seconds = map(int, timepart_full[0].split(b":")) - if len(timepart_full) == 2: - microseconds = int('{:0<6.6}'.format(timepart_full[1].decode())) + def _parse_datetime(date, time): + year, month, day = map(int, date.split(b"-")) + time_full = time.split(b".") + hours, minutes, seconds = map(int, time_full[0].split(b":")) + if len(time_full) == 2: + microseconds = int('{:0<6.6}'.format(time_full[1].decode())) else: microseconds = 0 - val = datetime.datetime(year, month, day, hours, minutes, seconds, microseconds) - return val + return year, month, day, hours, minutes, seconds, microseconds + def convert_timestamp(val): + date, time = val.split(b" ") + return datetime.datetime(*_parse_datetime(date, time)) + + def convert_timestamptz(val): + date, timetz = val.split(b" ") + if b"+" in timetz: + tzsign = 1 + time, tz = timetz.split(b"+") + elif b"-" in timetz: + tzsign = -1 + time, tz = timetz.split(b"-") + else: + time, tz = timetz, None + + if tz is not None: + tzhours, tzminutes = map(int, tz.split(b":")) + tz = datetime.timezone( + tzsign * datetime.timedelta(hours=tzhours, minutes=tzminutes)) + else: + tz = None + + return datetime.datetime(*_parse_datetime(date, time), tz) register_adapter(datetime.date, adapt_date) register_adapter(datetime.datetime, adapt_datetime) register_converter("date", convert_date) register_converter("timestamp", convert_timestamp) + register_converter("timestamptz", convert_timestamptz) register_adapters_and_converters() diff -r 2b7b203e3909 Lib/sqlite3/test/types.py --- a/Lib/sqlite3/test/types.py Wed Jan 11 20:18:03 2017 +0200 +++ b/Lib/sqlite3/test/types.py Thu Jan 12 13:42:43 2017 +0800 @@ -359,7 +359,7 @@ def setUp(self): self.con = sqlite.connect(":memory:", detect_types=sqlite.PARSE_DECLTYPES) self.cur = self.con.cursor() - self.cur.execute("create table test(d date, ts timestamp)") + self.cur.execute("create table test(d date, ts timestamp, tsz timestamptz)") def tearDown(self): self.cur.close() @@ -383,11 +383,14 @@ 'the date functions are available on 3.1 or later') def CheckSqlTimestamp(self): now = datetime.datetime.utcnow() - self.cur.execute("insert into test(ts) values (current_timestamp)") - self.cur.execute("select ts from test") - ts = self.cur.fetchone()[0] + self.cur.execute("insert into test(ts, tsz) values (current_timestamp, current_timestamp)") + self.cur.execute("select ts, tsz from test") + res = self.cur.fetchone() + ts, tsz = res[0], res[1] self.assertEqual(type(ts), datetime.datetime) + self.assertEqual(type(tsz), datetime.datetime) self.assertEqual(ts.year, now.year) + self.assertEqual(tsz.year, now.year) def CheckDateTimeSubSeconds(self): ts = sqlite.Timestamp(2004, 2, 14, 7, 15, 0, 500000) @@ -403,6 +406,22 @@ ts2 = self.cur.fetchone()[0] self.assertEqual(ts, ts2) + def CheckDateTimeTimezone(self): + tzs = [ + datetime.timezone(datetime.timedelta(hours=6)), + datetime.timezone(datetime.timedelta(hours=4, minutes=30)), + datetime.timezone.utc, + datetime.timezone(-datetime.timedelta(hours=4, minutes=30)), + datetime.timezone(-datetime.timedelta(hours=6)) + ] + tss = [sqlite.Timestamp(2004, 2, 14, 7, 15, 0, ms, tz) + for tz in tzs for ms in (0, 500000)] + self.cur.executemany("insert into test(tsz) values (?)", + [(ts,) for ts in tss]) + self.cur.execute("select tsz from test") + res = [row[0] for row in self.cur.fetchall()] + self.assertEqual(sorted(res), sorted(tss)) + def suite(): sqlite_type_suite = unittest.makeSuite(SqliteTypeTests, "Check") decltypes_type_suite = unittest.makeSuite(DeclTypesTests, "Check")