aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOlivier Tilloy <olivier@tilloy.net>2011-08-12 08:29:47 +0200
committerOlivier Tilloy <olivier@tilloy.net>2011-08-12 08:29:47 +0200
commitaecc90f8bc3c9739d818749fd1bc458884300148 (patch)
treeec913e0c133aac45ac9818bc3afc4942074d6817
parent75693ed13e3ed48e2f0636b1681948b27ae8f312 (diff)
downloadpyexiv2-aecc90f8bc3c9739d818749fd1bc458884300148.tar.gz
Custom DateTimeFormatter helper to convert date/time objects to strings conforming to the EXIF/IPTC/XMP formats.
-rw-r--r--src/pyexiv2/exif.py11
-rw-r--r--src/pyexiv2/iptc.py24
-rw-r--r--src/pyexiv2/utils.py156
-rw-r--r--src/pyexiv2/xmp.py28
-rwxr-xr-xtest/TestsRunner.py4
-rw-r--r--test/datetimeformatter.py190
6 files changed, 364 insertions, 49 deletions
diff --git a/src/pyexiv2/exif.py b/src/pyexiv2/exif.py
index b0c6374..1d56be8 100644
--- a/src/pyexiv2/exif.py
+++ b/src/pyexiv2/exif.py
@@ -2,7 +2,7 @@
# ******************************************************************************
#
-# Copyright (C) 2006-2010 Olivier Tilloy <olivier@tilloy.net>
+# Copyright (C) 2006-2011 Olivier Tilloy <olivier@tilloy.net>
#
# This file is part of the pyexiv2 distribution.
#
@@ -32,7 +32,8 @@ import libexiv2python
from pyexiv2.utils import is_fraction, make_fraction, fraction_to_string, \
NotifyingList, ListenerInterface, \
- undefined_to_string, string_to_undefined
+ undefined_to_string, string_to_undefined, \
+ DateTimeFormatter
import time
import datetime
@@ -347,13 +348,13 @@ class ExifTag(ListenerInterface):
"""
if self.type == 'Ascii':
if isinstance(value, datetime.datetime):
- return value.strftime(self._datetime_formats[0])
+ return DateTimeFormatter.exif(value)
elif isinstance(value, datetime.date):
if self.key == 'Exif.GPSInfo.GPSDateStamp':
# Special case
- return value.strftime(self._date_formats[0])
+ return DateTimeFormatter.exif(value)
else:
- return value.strftime('%s 00:00:00' % self._date_formats[0])
+ return '%s 00:00:00' % DateTimeFormatter.exif(value)
elif isinstance(value, unicode):
try:
return value.encode('utf-8')
diff --git a/src/pyexiv2/iptc.py b/src/pyexiv2/iptc.py
index f2a360d..c6ed36f 100644
--- a/src/pyexiv2/iptc.py
+++ b/src/pyexiv2/iptc.py
@@ -2,7 +2,7 @@
# ******************************************************************************
#
-# Copyright (C) 2006-2010 Olivier Tilloy <olivier@tilloy.net>
+# Copyright (C) 2006-2011 Olivier Tilloy <olivier@tilloy.net>
#
# This file is part of the pyexiv2 distribution.
#
@@ -30,7 +30,8 @@ IPTC specific code.
import libexiv2python
-from pyexiv2.utils import ListenerInterface, NotifyingList, FixedOffset
+from pyexiv2.utils import ListenerInterface, NotifyingList, \
+ FixedOffset, DateTimeFormatter
import time
import datetime
@@ -335,28 +336,13 @@ class IptcTag(ListenerInterface):
elif self.type == 'Date':
if isinstance(value, (datetime.date, datetime.datetime)):
- # ISO 8601 date format.
- # According to the IPTC specification, the format for a string
- # field representing a date is '%Y%m%d'. However, the string
- # expected by exiv2's DateValue::read(string) should be
- # formatted using pattern '%Y-%m-%d'.
- return value.strftime('%Y-%m-%d')
+ return DateTimeFormatter.iptc_date(value)
else:
raise IptcValueError(value, self.type)
elif self.type == 'Time':
if isinstance(value, (datetime.time, datetime.datetime)):
- # According to the IPTC specification, the format for a string
- # field representing a time is '%H%M%S±%H%M'. However, the
- # string expected by exiv2's TimeValue::read(string) should be
- # formatted using pattern '%H:%M:%S±%H:%M'.
- r = value.strftime('%H:%M:%S')
- if value.tzinfo is not None:
- s = value.strftime('%z') # of the form ±%H%M
- r += s[:3] + ':' + s[3:]
- else:
- r += '+00:00'
- return r
+ return DateTimeFormatter.iptc_time(value)
else:
raise IptcValueError(value, self.type)
diff --git a/src/pyexiv2/utils.py b/src/pyexiv2/utils.py
index de56c21..9473aff 100644
--- a/src/pyexiv2/utils.py
+++ b/src/pyexiv2/utils.py
@@ -2,7 +2,7 @@
# ******************************************************************************
#
-# Copyright (C) 2006-2010 Olivier Tilloy <olivier@tilloy.net>
+# Copyright (C) 2006-2011 Olivier Tilloy <olivier@tilloy.net>
#
# This file is part of the pyexiv2 distribution.
#
@@ -573,3 +573,157 @@ class GPSCoordinate(object):
return '%d,%d,%d%s' % (self._degrees, self._minutes, self._seconds,
self._direction)
+
+class DateTimeFormatter(object):
+
+ """
+ Convenience object that exposes static methods to convert a date, time or
+ datetime object to a string representation suitable for various metadata
+ standards.
+
+ This is needed because python’s
+ `strftime() <http://docs.python.org/library/datetime.html#strftime-strptime-behavior>`_
+ doesn’t work for years before 1900.
+
+ This class mostly exists for internal usage only. Clients should never need
+ to use it.
+ """
+
+ @staticmethod
+ def timedelta_to_offset(t):
+ """
+ Convert a time delta to a string representation in the form ``±%H:%M``.
+
+ :param t: a time delta
+ :type t: :class:`datetime.timedelta`
+
+ :return: a string representation of the time delta in the form
+ ``±%H:%M``
+ :rtype: string
+ """
+ seconds = t.total_seconds()
+ hours = int(seconds / 3600)
+ minutes = abs(int((seconds - hours * 3600) / 60))
+ return '%+03d:%02d' % (hours, minutes)
+
+ @staticmethod
+ def exif(d):
+ """
+ Convert a date/time object to a string representation conforming to
+ libexiv2’s internal representation for the EXIF standard.
+
+ :param d: a datetime or date object
+ :type d: :class:`datetime.datetime` or :class:`datetime.date`
+
+ :return: a string representation conforming to the EXIF standard
+ :rtype: string
+
+ :raise TypeError: if the parameter is not a datetime or a date object
+ """
+ if isinstance(d, datetime.datetime):
+ return '%04d:%02d:%02d %02d:%02d:%02d' % \
+ (d.year, d.month, d.day, d.hour, d.minute, d.second)
+ elif isinstance(d, datetime.date):
+ return '%04d:%02d:%02d' % (d.year, d.month, d.day)
+ else:
+ raise TypeError('expecting an object of type '
+ 'datetime.datetime or datetime.date')
+
+ @staticmethod
+ def iptc_date(d):
+ """
+ Convert a date object to a string representation conforming to
+ libexiv2’s internal representation for the IPTC standard.
+
+ :param d: a datetime or date object
+ :type d: :class:`datetime.datetime` or :class:`datetime.date`
+
+ :return: a string representation conforming to the IPTC standard
+ :rtype: string
+
+ :raise TypeError: if the parameter is not a datetime or a date object
+ """
+ if isinstance(d, (datetime.date, datetime.datetime)):
+ # ISO 8601 date format.
+ # According to the IPTC specification, the format for a string
+ # field representing a date is '%Y%m%d'. However, the string
+ # expected by exiv2's DateValue::read(string) should be
+ # formatted using pattern '%Y-%m-%d'.
+ return '%04d-%02d-%02d' % (d.year, d.month, d.day)
+ else:
+ raise TypeError('expecting an object of type '
+ 'datetime.datetime or datetime.date')
+
+ @staticmethod
+ def iptc_time(d):
+ """
+ Convert a time object to a string representation conforming to
+ libexiv2’s internal representation for the IPTC standard.
+
+ :param d: a datetime or time object
+ :type d: :class:`datetime.datetime` or :class:`datetime.time`
+
+ :return: a string representation conforming to the IPTC standard
+ :rtype: string
+
+ :raise TypeError: if the parameter is not a datetime or a time object
+ """
+ if isinstance(d, (datetime.time, datetime.datetime)):
+ # According to the IPTC specification, the format for a string
+ # field representing a time is '%H%M%S±%H%M'. However, the
+ # string expected by exiv2's TimeValue::read(string) should be
+ # formatted using pattern '%H:%M:%S±%H:%M'.
+ r = '%02d:%02d:%02d' % (d.hour, d.minute, d.second)
+ if d.tzinfo is not None:
+ t = d.utcoffset()
+ if t is not None:
+ r += DateTimeFormatter.timedelta_to_offset(t)
+ else:
+ r += '+00:00'
+ return r
+ else:
+ raise TypeError('expecting an object of type '
+ 'datetime.datetime or datetime.time')
+
+ @staticmethod
+ def xmp(d):
+ """
+ Convert a date/time object to a string representation conforming to
+ libexiv2’s internal representation for the XMP standard.
+
+ :param d: a datetime or date object
+ :type d: :class:`datetime.datetime` or :class:`datetime.date`
+
+ :return: a string representation conforming to the XMP standard
+ :rtype: string
+
+ :raise TypeError: if the parameter is not a datetime or a date object
+ """
+ if isinstance(d, datetime.datetime):
+ t = d.utcoffset()
+ if d.tzinfo is None or t is None or t == datetime.timedelta(0):
+ tz = 'Z'
+ else:
+ tz = DateTimeFormatter.timedelta_to_offset(t)
+ if d.hour == 0 and d.minute == 0 and \
+ d.second == 0 and d.microsecond == 0 and \
+ (d.tzinfo is None or d.utcoffset() == datetime.timedelta(0)):
+ return '%04d-%02d-%02d' % (d.year, d.month, d.day)
+ elif d.second == 0 and d.microsecond == 0:
+ return '%04d-%02d-%02dT%02d:%02d%s' % \
+ (d.year, d.month, d.day, d.hour, d.minute, tz)
+ elif d.microsecond == 0:
+ return '%04d-%02d-%02dT%02d:%02d:%02d%s' % \
+ (d.year, d.month, d.day, d.hour, d.minute, d.second, tz)
+ else:
+ r = '%04d-%02d-%02dT%02d:%02d:%02d.' % \
+ (d.year, d.month, d.day, d.hour, d.minute, d.second)
+ r += str(int(d.microsecond) / 1E6)[2:]
+ r += tz
+ return r
+ elif isinstance(d, datetime.date):
+ return '%04d-%02d-%02d' % (d.year, d.month, d.day)
+ else:
+ raise TypeError('expecting an object of type '
+ 'datetime.datetime or datetime.date')
+
diff --git a/src/pyexiv2/xmp.py b/src/pyexiv2/xmp.py
index 7240527..bed1b66 100644
--- a/src/pyexiv2/xmp.py
+++ b/src/pyexiv2/xmp.py
@@ -2,7 +2,7 @@
# ******************************************************************************
#
-# Copyright (C) 2006-2010 Olivier Tilloy <olivier@tilloy.net>
+# Copyright (C) 2006-2011 Olivier Tilloy <olivier@tilloy.net>
#
# This file is part of the pyexiv2 distribution.
#
@@ -30,7 +30,8 @@ XMP specific code.
import libexiv2python
-from pyexiv2.utils import FixedOffset, is_fraction, make_fraction, GPSCoordinate
+from pyexiv2.utils import FixedOffset, is_fraction, make_fraction, \
+ GPSCoordinate, DateTimeFormatter
import datetime
import re
@@ -387,27 +388,8 @@ class XmpTag(object):
raise XmpValueError(value, type)
elif type == 'Date':
- if isinstance(value, datetime.datetime):
- if value.tzinfo is None or value.utcoffset() == datetime.timedelta(0):
- tz = 'Z'
- else:
- tz = value.strftime('%z') # of the form ±%H%M
- tz = tz[:3] + ':' + tz[3:]
- if value.hour == 0 and value.minute == 0 and \
- value.second == 0 and value.microsecond == 0 and \
- (value.tzinfo is None or value.utcoffset() == datetime.timedelta(0)):
- return value.strftime('%Y-%m-%d')
- elif value.second == 0 and value.microsecond == 0:
- return value.strftime('%Y-%m-%dT%H:%M') + tz
- elif value.microsecond == 0:
- return value.strftime('%Y-%m-%dT%H:%M:%S') + tz
- else:
- r = value.strftime('%Y-%m-%dT%H:%M:%S.')
- r += str(int(value.microsecond) / 1E6)[2:]
- r += tz
- return r
- elif isinstance(value, datetime.date):
- return value.isoformat()
+ if isinstance(value, (datetime.date, datetime.datetime)):
+ return DateTimeFormatter.xmp(value)
else:
raise XmpValueError(value, type)
diff --git a/test/TestsRunner.py b/test/TestsRunner.py
index ba5162c..9b7d9e5 100755
--- a/test/TestsRunner.py
+++ b/test/TestsRunner.py
@@ -3,7 +3,7 @@
# ******************************************************************************
#
-# Copyright (C) 2008-2010 Olivier Tilloy <olivier@tilloy.net>
+# Copyright (C) 2008-2011 Olivier Tilloy <olivier@tilloy.net>
#
# This file is part of the pyexiv2 distribution.
#
@@ -41,6 +41,7 @@ from encoding import TestEncodings
from utils import TestConversions, TestFractions
from usercomment import TestUserCommentReadWrite, TestUserCommentAdd
from pickling import TestPicklingTags
+from datetimeformatter import TestDateTimeFormatter
def run_unit_tests():
@@ -62,6 +63,7 @@ def run_unit_tests():
suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestUserCommentReadWrite))
suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestUserCommentAdd))
suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestPicklingTags))
+ suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestDateTimeFormatter))
# Run the test suite
return unittest.TextTestRunner(verbosity=2).run(suite)
diff --git a/test/datetimeformatter.py b/test/datetimeformatter.py
new file mode 100644
index 0000000..2626713
--- /dev/null
+++ b/test/datetimeformatter.py
@@ -0,0 +1,190 @@
+# -*- coding: utf-8 -*-
+
+# ******************************************************************************
+#
+# Copyright (C) 2011 Olivier Tilloy <olivier@tilloy.net>
+#
+# This file is part of the pyexiv2 distribution.
+#
+# pyexiv2 is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# pyexiv2 is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with pyexiv2; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA.
+#
+# Author: Olivier Tilloy <olivier@tilloy.net>
+#
+# ******************************************************************************
+
+import unittest
+
+from pyexiv2.utils import DateTimeFormatter, FixedOffset
+
+import datetime
+
+
+class TestDateTimeFormatter(unittest.TestCase):
+
+ def test_timedelta_to_offset(self):
+ # positive deltas
+ t = datetime.timedelta(hours=5)
+ self.assertEqual(DateTimeFormatter.timedelta_to_offset(t), '+05:00')
+ t = datetime.timedelta(minutes=300)
+ self.assertEqual(DateTimeFormatter.timedelta_to_offset(t), '+05:00')
+ t = datetime.timedelta(hours=5, minutes=12)
+ self.assertEqual(DateTimeFormatter.timedelta_to_offset(t), '+05:12')
+ t = datetime.timedelta(seconds=10800)
+ self.assertEqual(DateTimeFormatter.timedelta_to_offset(t), '+03:00')
+
+ # negative deltas
+ t = datetime.timedelta(hours=-4)
+ self.assertEqual(DateTimeFormatter.timedelta_to_offset(t), '-04:00')
+ t = datetime.timedelta(minutes=-258)
+ self.assertEqual(DateTimeFormatter.timedelta_to_offset(t), '-04:18')
+ t = datetime.timedelta(hours=-2, minutes=-12)
+ self.assertEqual(DateTimeFormatter.timedelta_to_offset(t), '-02:12')
+ t = datetime.timedelta(seconds=-10000)
+ self.assertEqual(DateTimeFormatter.timedelta_to_offset(t), '-02:46')
+
+ def test_exif(self):
+ # datetime
+ d = datetime.datetime(1899, 12, 31)
+ self.assertEqual(DateTimeFormatter.exif(d), '1899:12:31 00:00:00')
+ d = datetime.datetime(1899, 12, 31, 23)
+ self.assertEqual(DateTimeFormatter.exif(d), '1899:12:31 23:00:00')
+ d = datetime.datetime(1899, 12, 31, 23, 59)
+ self.assertEqual(DateTimeFormatter.exif(d), '1899:12:31 23:59:00')
+ d = datetime.datetime(1899, 12, 31, 23, 59, 59)
+ self.assertEqual(DateTimeFormatter.exif(d), '1899:12:31 23:59:59')
+ d = datetime.datetime(1899, 12, 31, 23, 59, 59, 999999)
+ self.assertEqual(DateTimeFormatter.exif(d), '1899:12:31 23:59:59')
+ d = datetime.datetime(1899, 12, 31, 23, 59, 59, tzinfo=FixedOffset())
+ self.assertEqual(DateTimeFormatter.exif(d), '1899:12:31 23:59:59')
+ d = datetime.datetime(1899, 12, 31, 23, 59, 59, tzinfo=FixedOffset(hours=5))
+ self.assertEqual(DateTimeFormatter.exif(d), '1899:12:31 23:59:59')
+ d = datetime.datetime(2011, 8, 8, 19, 3, 37)
+ self.assertEqual(DateTimeFormatter.exif(d), '2011:08:08 19:03:37')
+
+ # date
+ d = datetime.date(1899, 12, 31)
+ self.assertEqual(DateTimeFormatter.exif(d), '1899:12:31')
+ d = datetime.date(2011, 8, 8)
+ self.assertEqual(DateTimeFormatter.exif(d), '2011:08:08')
+
+ # invalid type
+ self.assertRaises(TypeError, DateTimeFormatter.exif, None)
+ self.assertRaises(TypeError, DateTimeFormatter.exif, 3.14)
+
+ def test_iptc_date(self):
+ # datetime
+ d = datetime.datetime(1899, 12, 31)
+ self.assertEqual(DateTimeFormatter.iptc_date(d), '1899-12-31')
+ d = datetime.datetime(1899, 12, 31, 23)
+ self.assertEqual(DateTimeFormatter.iptc_date(d), '1899-12-31')
+ d = datetime.datetime(1899, 12, 31, 23, 59)
+ self.assertEqual(DateTimeFormatter.iptc_date(d), '1899-12-31')
+ d = datetime.datetime(1899, 12, 31, 23, 59, 59)
+ self.assertEqual(DateTimeFormatter.iptc_date(d), '1899-12-31')
+ d = datetime.datetime(1899, 12, 31, 23, 59, 59, 999999)
+ self.assertEqual(DateTimeFormatter.iptc_date(d), '1899-12-31')
+ d = datetime.datetime(1899, 12, 31, 23, 59, 59, tzinfo=FixedOffset())
+ self.assertEqual(DateTimeFormatter.iptc_date(d), '1899-12-31')
+ d = datetime.datetime(1899, 12, 31, 23, 59, 59, tzinfo=FixedOffset(hours=5))
+ self.assertEqual(DateTimeFormatter.iptc_date(d), '1899-12-31')
+ d = datetime.datetime(2011, 8, 8, 19, 3, 37)
+ self.assertEqual(DateTimeFormatter.iptc_date(d), '2011-08-08')
+
+ # date
+ d = datetime.date(1899, 12, 31)
+ self.assertEqual(DateTimeFormatter.iptc_date(d), '1899-12-31')
+ d = datetime.date(2011, 8, 8)
+ self.assertEqual(DateTimeFormatter.iptc_date(d), '2011-08-08')
+
+ # invalid type
+ self.assertRaises(TypeError, DateTimeFormatter.iptc_date, None)
+ self.assertRaises(TypeError, DateTimeFormatter.iptc_date, 3.14)
+
+ def test_iptc_time(self):
+ # datetime
+ d = datetime.datetime(1899, 12, 31)
+ self.assertEqual(DateTimeFormatter.iptc_time(d), '00:00:00+00:00')
+ d = datetime.datetime(1899, 12, 31, 23)
+ self.assertEqual(DateTimeFormatter.iptc_time(d), '23:00:00+00:00')
+ d = datetime.datetime(1899, 12, 31, 23, 59)
+ self.assertEqual(DateTimeFormatter.iptc_time(d), '23:59:00+00:00')
+ d = datetime.datetime(1899, 12, 31, 23, 59, 59)
+ self.assertEqual(DateTimeFormatter.iptc_time(d), '23:59:59+00:00')
+ d = datetime.datetime(1899, 12, 31, 23, 59, 59, 999999)
+ self.assertEqual(DateTimeFormatter.iptc_time(d), '23:59:59+00:00')
+ d = datetime.datetime(1899, 12, 31, 23, 59, 59, tzinfo=FixedOffset())
+ self.assertEqual(DateTimeFormatter.iptc_time(d), '23:59:59+00:00')
+ d = datetime.datetime(1899, 12, 31, 23, 59, 59, tzinfo=FixedOffset(hours=5))
+ self.assertEqual(DateTimeFormatter.iptc_time(d), '23:59:59+05:00')
+ d = datetime.datetime(2011, 8, 8, 19, 3, 37)
+ self.assertEqual(DateTimeFormatter.iptc_time(d), '19:03:37+00:00')
+
+ # time
+ d = datetime.time(23)
+ self.assertEqual(DateTimeFormatter.iptc_time(d), '23:00:00+00:00')
+ d = datetime.time(23, 59)
+ self.assertEqual(DateTimeFormatter.iptc_time(d), '23:59:00+00:00')
+ d = datetime.time(23, 59, 59)
+ self.assertEqual(DateTimeFormatter.iptc_time(d), '23:59:59+00:00')
+ d = datetime.time(23, 59, 59, 999999)
+ self.assertEqual(DateTimeFormatter.iptc_time(d), '23:59:59+00:00')
+ d = datetime.time(23, 59, 59, tzinfo=FixedOffset())
+ self.assertEqual(DateTimeFormatter.iptc_time(d), '23:59:59+00:00')
+ d = datetime.time(23, 59, 59, tzinfo=FixedOffset(hours=5))
+ self.assertEqual(DateTimeFormatter.iptc_time(d), '23:59:59+05:00')
+ d = datetime.time(19, 3, 37)
+ self.assertEqual(DateTimeFormatter.iptc_time(d), '19:03:37+00:00')
+
+ # invalid type
+ self.assertRaises(TypeError, DateTimeFormatter.iptc_time, None)
+ self.assertRaises(TypeError, DateTimeFormatter.iptc_time, 3.14)
+
+ def test_xmp(self):
+ # datetime
+ d = datetime.datetime(1899, 12, 31)
+ self.assertEqual(DateTimeFormatter.xmp(d), '1899-12-31')
+ d = datetime.datetime(1899, 12, 31, tzinfo=FixedOffset())
+ self.assertEqual(DateTimeFormatter.xmp(d), '1899-12-31')
+ d = datetime.datetime(1899, 12, 31, 23, 59)
+ self.assertEqual(DateTimeFormatter.xmp(d), '1899-12-31T23:59Z')
+ d = datetime.datetime(1899, 12, 31, 23, 59, tzinfo=FixedOffset())
+ self.assertEqual(DateTimeFormatter.xmp(d), '1899-12-31T23:59Z')
+ d = datetime.datetime(1899, 12, 31, 23, 59, tzinfo=FixedOffset(hours=3))
+ self.assertEqual(DateTimeFormatter.xmp(d), '1899-12-31T23:59+03:00')
+ d = datetime.datetime(1899, 12, 31, 23, 59, 59)
+ self.assertEqual(DateTimeFormatter.xmp(d), '1899-12-31T23:59:59Z')
+ d = datetime.datetime(1899, 12, 31, 23, 59, 59, tzinfo=FixedOffset())
+ self.assertEqual(DateTimeFormatter.xmp(d), '1899-12-31T23:59:59Z')
+ d = datetime.datetime(1899, 12, 31, 23, 59, 59, tzinfo=FixedOffset(hours=3))
+ self.assertEqual(DateTimeFormatter.xmp(d), '1899-12-31T23:59:59+03:00')
+ d = datetime.datetime(1899, 12, 31, 23, 59, 59, 999999)
+ self.assertEqual(DateTimeFormatter.xmp(d), '1899-12-31T23:59:59.999999Z')
+ d = datetime.datetime(1899, 12, 31, 23, 59, 59, 999999, tzinfo=FixedOffset())
+ self.assertEqual(DateTimeFormatter.xmp(d), '1899-12-31T23:59:59.999999Z')
+ d = datetime.datetime(1899, 12, 31, 23, 59, 59, 999999, tzinfo=FixedOffset(hours=3))
+ self.assertEqual(DateTimeFormatter.xmp(d), '1899-12-31T23:59:59.999999+03:00')
+ d = datetime.datetime(2011, 8, 11, 9, 23, 44)
+ self.assertEqual(DateTimeFormatter.xmp(d), '2011-08-11T09:23:44Z')
+
+ # date
+ d = datetime.date(1899, 12, 31)
+ self.assertEqual(DateTimeFormatter.xmp(d), '1899-12-31')
+ d = datetime.date(2011, 8, 8)
+ self.assertEqual(DateTimeFormatter.xmp(d), '2011-08-08')
+
+ # invalid type
+ self.assertRaises(TypeError, DateTimeFormatter.xmp, None)
+ self.assertRaises(TypeError, DateTimeFormatter.xmp, 3.14)
+