Implemented datetime.astimezone() and datetimetz.astimezone().
diff --git a/Doc/lib/libdatetime.tex b/Doc/lib/libdatetime.tex
index f29db5b..1adbc8e 100644
--- a/Doc/lib/libdatetime.tex
+++ b/Doc/lib/libdatetime.tex
@@ -601,6 +601,11 @@
     Return a datetime with the same value, except for those fields given
     new values by whichever keyword arguments are specified.
 
+  - astimezone(tz)
+    Return a \class{datetimetz} with the same date and time fields, and
+    with \member{tzinfo} member \var{tz}.  \var{tz} must be an instance
+    of a \class{tzinfo} subclass.
+
   - timetuple()
     Return a 9-element tuple of the form returned by
     \function{time.localtime()}.
@@ -1083,6 +1088,23 @@
     \code{tzinfo=None} can be specified to create a naive datetimetz from
     an aware datetimetz.
 
+  - astimezone(tz)
+    Return a \class{datetimetz} with new tzinfo member \var{tz}.  \var{tz}
+    must be an instance of a \class{tzinfo} subclass.  If self is naive, or
+    if \code(tz.utcoffset(self)} returns \code{None},
+    \code{self.astimezone(tz)} is equivalent to
+    \code{self.replace(tzinfo=tz)}:  a new timezone object is attached
+    without any conversion of date or time fields.  If self is aware and
+    \code{tz.utcoffset(self)} does not return \code{None}, the date and
+    time fields are adjusted so that the result is local time in timezone
+    tz, representing the same UTC time as self.  \code{self.astimezone(tz)}
+    is then equivalent to
+    \begin{verbatim}
+        (self - (self.utcoffset() - tz.utcoffset(self)).replace(tzinfo=tz)
+    \end{verbatim}
+    where the result of \code{tz.uctcoffset(self)} is converted to a
+    \class{timedelta} if it's an integer.
+
   - utcoffset()
     If \member{tzinfo} is \code{None}, returns \code{None}, else
     \code{tzinfo.utcoffset(self)} converted to a \class{timedelta}
diff --git a/Lib/test/test_datetime.py b/Lib/test/test_datetime.py
index 25c7716..de0d17a 100644
--- a/Lib/test/test_datetime.py
+++ b/Lib/test/test_datetime.py
@@ -1295,6 +1295,21 @@
         base = cls(2000, 2, 29)
         self.assertRaises(ValueError, base.replace, year=2001)
 
+    def test_astimezone(self):
+        # Pretty boring for a datetime!  datetimetz is more interesting here.
+        dt = self.theclass.now()
+        f = FixedOffset(44, "")
+        for dtz in dt.astimezone(f), dt.astimezone(tz=f):
+            self.failUnless(isinstance(dtz, datetimetz))
+            self.assertEqual(dt.date(), dtz.date())
+            self.assertEqual(dt.time(), dtz.time())
+            self.failUnless(dtz.tzinfo is f)
+            self.assertEqual(dtz.utcoffset(), timedelta(minutes=44))
+
+        self.assertRaises(TypeError, dt.astimezone) # not enough args
+        self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
+        self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
+
 
 class TestTime(unittest.TestCase):
 
@@ -2308,6 +2323,44 @@
         base = cls(2000, 2, 29)
         self.assertRaises(ValueError, base.replace, year=2001)
 
+    def test_more_astimezone(self):
+        # The inherited test_astimezone covered some trivial and error cases.
+        fnone = FixedOffset(None, "None")
+        f44m = FixedOffset(44, "44")
+        fm5h = FixedOffset(-timedelta(hours=5), "m300")
+
+        dt = self.theclass.now(tzinfo=f44m)
+        self.failUnless(dt.tzinfo is f44m)
+        # Replacing with degenerate tzinfo doesn't do any adjustment.
+        for x in dt.astimezone(fnone), dt.astimezone(tz=fnone):
+            self.failUnless(x.tzinfo is fnone)
+            self.assertEqual(x.date(), dt.date())
+            self.assertEqual(x.time(), dt.time())
+        # Ditt with None tz.
+        x = dt.astimezone(tz=None)
+        self.failUnless(x.tzinfo is None)
+        self.assertEqual(x.date(), dt.date())
+        self.assertEqual(x.time(), dt.time())
+        # Ditto replacing with same tzinfo.
+        x = dt.astimezone(dt.tzinfo)
+        self.failUnless(x.tzinfo is f44m)
+        self.assertEqual(x.date(), dt.date())
+        self.assertEqual(x.time(), dt.time())
+
+        # Replacing with different tzinfo does adjust.
+        got = dt.astimezone(fm5h)
+        self.failUnless(got.tzinfo is fm5h)
+        self.assertEqual(got.utcoffset(), timedelta(hours=-5))
+        expected = dt - dt.utcoffset()  # in effect, convert to UTC
+        expected += fm5h.utcoffset(dt)  # and from there to local time
+        expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
+        self.assertEqual(got.date(), expected.date())
+        self.assertEqual(got.time(), expected.time())
+        self.assertEqual(got.timetz(), expected.timetz())
+        self.failUnless(got.tzinfo is expected.tzinfo)
+        self.assertEqual(got, expected)
+
+
 def test_suite():
     allsuites = [unittest.makeSuite(klass, 'test')
                  for klass in (TestModule,
diff --git a/Modules/datetimemodule.c b/Modules/datetimemodule.c
index 6fd10ed..d7c6005 100644
--- a/Modules/datetimemodule.c
+++ b/Modules/datetimemodule.c
@@ -600,6 +600,18 @@
 	return tzinfo;
 }
 
+/* self is a datetimetz.  Replace its tzinfo member. */
+void
+replace_tzinfo(PyObject *self, PyObject *newtzinfo)
+{
+	assert(self != NULL);
+	assert(PyDateTimeTZ_Check(self));
+	assert(check_tzinfo_subclass(newtzinfo) >= 0);
+	Py_INCREF(newtzinfo);
+	Py_DECREF(((PyDateTime_DateTimeTZ *)self)->tzinfo);
+	((PyDateTime_DateTimeTZ *)self)->tzinfo = newtzinfo;
+}
+
 /* Internal helper.
  * Call getattr(tzinfo, name)(tzinfoarg), and extract an int from the
  * result.  tzinfo must be an instance of the tzinfo class.  If the method
@@ -2915,10 +2927,7 @@
 				    		TIME_GET_MICROSECOND(time));
 	if (result && PyTimeTZ_Check(time) && PyDateTimeTZ_Check(result)) {
 		/* Copy the tzinfo field. */
-		PyObject *tzinfo = ((PyDateTime_TimeTZ *)time)->tzinfo;
-		Py_INCREF(tzinfo);
-		Py_DECREF(((PyDateTime_DateTimeTZ *)result)->tzinfo);
-		((PyDateTime_DateTimeTZ *)result)->tzinfo = tzinfo;
+		replace_tzinfo(result, ((PyDateTime_TimeTZ *)time)->tzinfo);
 	}
 	return result;
 }
@@ -3247,6 +3256,24 @@
 }
 
 static PyObject *
+datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
+{
+	PyObject *tzinfo;
+	static char *keywords[] = {"tz", NULL};
+
+	if (! PyArg_ParseTupleAndKeywords(args, kw, "O:astimezone", keywords,
+					  &tzinfo))
+		return NULL;
+	if (check_tzinfo_subclass(tzinfo) < 0)
+		return NULL;
+	return new_datetimetz(GET_YEAR(self), GET_MONTH(self), GET_DAY(self),
+			      DATE_GET_HOUR(self), DATE_GET_MINUTE(self),
+			      DATE_GET_SECOND(self),
+			      DATE_GET_MICROSECOND(self),
+			      tzinfo);
+}
+
+static PyObject *
 datetime_timetuple(PyDateTime_DateTime *self)
 {
 	return build_struct_time(GET_YEAR(self),
@@ -3397,6 +3424,9 @@
 	{"replace",     (PyCFunction)datetime_replace,	METH_KEYWORDS,
 	 PyDoc_STR("Return datetime with new specified fields.")},
 
+	{"astimezone",  (PyCFunction)datetime_astimezone, METH_KEYWORDS,
+	 PyDoc_STR("tz -> datetimetz with same date & time, and tzinfo=tz\n")},
+
 	{"__setstate__", (PyCFunction)datetime_setstate, METH_O,
 	 PyDoc_STR("__setstate__(state)")},
 
@@ -4398,20 +4428,6 @@
  * optional tzinfo argument.
  */
 
-/* Internal helper.
- * self is a datetimetz.  Replace its tzinfo member.
- */
-void
-replace_tzinfo(PyObject *self, PyObject *newtzinfo)
-{
-	assert(self != NULL);
-	assert(newtzinfo != NULL);
-	assert(PyDateTimeTZ_Check(self));
-	Py_INCREF(newtzinfo);
-	Py_DECREF(((PyDateTime_DateTimeTZ *)self)->tzinfo);
-	((PyDateTime_DateTimeTZ *)self)->tzinfo = newtzinfo;
-}
-
 static char *datetimetz_kws[] = {
 	"year", "month", "day", "hour", "minute", "second",
 	"microsecond", "tzinfo", NULL
@@ -4697,6 +4713,53 @@
 }
 
 static PyObject *
+datetimetz_astimezone(PyDateTime_DateTimeTZ *self, PyObject *args,
+		      PyObject *kw)
+{
+	int y = GET_YEAR(self);
+	int m = GET_MONTH(self);
+	int d = GET_DAY(self);
+	int hh = DATE_GET_HOUR(self);
+	int mm = DATE_GET_MINUTE(self);
+	int ss = DATE_GET_SECOND(self);
+	int us = DATE_GET_MICROSECOND(self);
+
+	PyObject *tzinfo;
+	static char *keywords[] = {"tz", NULL};
+
+	if (! PyArg_ParseTupleAndKeywords(args, kw, "O:astimezone", keywords,
+					  &tzinfo))
+		return NULL;
+	if (check_tzinfo_subclass(tzinfo) < 0)
+		return NULL;
+
+	if (tzinfo != Py_None && self->tzinfo != Py_None) {
+		int none;
+		int selfoffset;
+		selfoffset = call_utcoffset(self->tzinfo,
+					    (PyObject *)self,
+					    &none);
+	        if (selfoffset == -1 && PyErr_Occurred())
+	        	return NULL;
+	        if (! none) {
+			int tzoffset;
+	        	tzoffset = call_utcoffset(tzinfo,
+	        				  (PyObject *)self,
+	        				  &none);
+	        	if (tzoffset == -1 && PyErr_Occurred())
+	        		return NULL;
+	        	if (! none) {
+	        		mm -= selfoffset - tzoffset;
+	        		if (normalize_datetime(&y, &m, &d,
+	        				       &hh, &mm, &ss, &us) < 0)
+	        			return NULL;
+	        	}
+	        }
+	}
+	return new_datetimetz(y, m, d, hh, mm, ss, us, tzinfo);
+}
+
+static PyObject *
 datetimetz_timetuple(PyDateTime_DateTimeTZ *self)
 {
 	int dstflag = -1;
@@ -4908,6 +4971,9 @@
 	{"replace",     (PyCFunction)datetimetz_replace,	METH_KEYWORDS,
 	 PyDoc_STR("Return datetimetz with new specified fields.")},
 
+	{"astimezone",  (PyCFunction)datetimetz_astimezone, METH_KEYWORDS,
+	 PyDoc_STR("tz -> convert to local time in new timezone tz\n")},
+
 	{"__setstate__", (PyCFunction)datetimetz_setstate, METH_O,
 	 PyDoc_STR("__setstate__(state)")},