diff -r d571d8cd4258 Doc/includes/sqlite3/pysqlite_datetime.py --- a/Doc/includes/sqlite3/pysqlite_datetime.py Fri Jan 13 09:10:51 2017 +0200 +++ b/Doc/includes/sqlite3/pysqlite_datetime.py Fri Jan 13 19:21:04 2017 +0800 @@ -3,16 +3,18 @@ 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]"') row = cur.fetchone() diff -r d571d8cd4258 Doc/library/sqlite3.rst --- a/Doc/library/sqlite3.rst Fri Jan 13 09:10:51 2017 +0200 +++ b/Doc/library/sqlite3.rst Fri Jan 13 19:21:04 2017 +0800 @@ -902,8 +902,9 @@ 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. 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 +918,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 d571d8cd4258 Lib/sqlite3/dbapi2.py --- a/Lib/sqlite3/dbapi2.py Fri Jan 13 09:10:51 2017 +0200 +++ b/Lib/sqlite3/dbapi2.py Fri Jan 13 19:21:04 2017 +0800 @@ -63,24 +63,55 @@ 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(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"-") + elif timetz[-1] == b'Z'[0]: + tzsign = 1 + time, tz = timetz[:-1], b'00:00' + else: + time, tz = timetz, None + 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 + 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 year, month, day, hours, minutes, seconds, microseconds, tz - val = datetime.datetime(year, month, day, hours, minutes, seconds, microseconds) - return val + def convert_timestamp(val): + args = _parse_datetime(val) + if args[-1] is not None: + raise ValueError( + '"timestamp" cannot be used for aware datetime objects,' + ' use "timestamptz" instead') + return datetime.datetime(*args[:-1]) + def convert_timestamptz(val): + args = _parse_datetime(val) + if args[-1] is None: + raise ValueError( + '"timestamptz" cannot be used for naive datetime objects,' + ' use "timestamp" instead') + return datetime.datetime(*args) 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 d571d8cd4258 Lib/sqlite3/test/types.py --- a/Lib/sqlite3/test/types.py Fri Jan 13 09:10:51 2017 +0200 +++ b/Lib/sqlite3/test/types.py Fri Jan 13 19:21:04 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() @@ -403,6 +403,42 @@ ts2 = self.cur.fetchone()[0] self.assertEqual(ts, ts2) + def CheckDateTimeErrors(self): + utcnow = datetime.datetime.now(datetime.timezone.utc).isoformat(" ") + self.cur.execute("insert into test(ts) values ({!r})".format(utcnow)) + with self.assertRaises(ValueError): + self.cur.execute("select ts from test") + + 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 CheckDateTimeTimeZoneErrors(self): + now = datetime.datetime.now().isoformat(" ") + self.cur.execute("insert into test(tsz) values ({!r})".format(now)) + with self.assertRaises(ValueError): + self.cur.execute("select tsz from test") + + def CheckDateTimeTimeZoneSuffixZ(self): + utcnow = datetime.datetime.now(datetime.timezone.utc) + str = utcnow.replace(tzinfo=None).isoformat(" ") + "Z" + self.cur.execute("insert into test(tsz) values ({!r})".format(str)) + self.cur.execute("select tsz from test") + res = self.cur.fetchone()[0] + self.assertEqual(res, utcnow) + def suite(): sqlite_type_suite = unittest.makeSuite(SqliteTypeTests, "Check") decltypes_type_suite = unittest.makeSuite(DeclTypesTests, "Check")