Index: /main/waeup.kofa/trunk/src/waeup/kofa/schema/field.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/schema/field.py	(revision 8157)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/schema/field.py	(revision 8158)
@@ -19,10 +19,10 @@
 """
 from zope.interface import implements
-from zope.schema import TextLine, Date
+from zope.schema import TextLine, Date, Datetime
 from zope.schema.interfaces import (
     ITextLine, IBaseVocabulary, ISource, IContextSourceBinder, InvalidValue,)
 from zope.schema.vocabulary import (
     SimpleVocabulary, getVocabularyRegistry, VocabularyRegistryError)
-from waeup.kofa.schema.interfaces import IFormattedDate
+from waeup.kofa.schema.interfaces import IFormattedDate, IFormattedDatetime
 
 class CustomizableErrorMsg(object):
@@ -153,2 +153,30 @@
         self.show_year = show_year
         return super(FormattedDate, self).__init__(*args, **kw)
+
+class FormattedDatetime(Datetime):
+    """A datetime field that supports additional formatting attributes.
+
+    Stores extra attributes (see below). To make use of these
+    attributes in forms, you have to provide widgets that read them
+    and use them in their operations, for instance the
+    `waeup.kofa.widgets.datewidget.FormattedDatetimeWidget`.
+
+    Extra attributes are as follows:
+
+    `date_format`
+      additional attribute to describe desired date format. Must be a
+      string that can be fed to strftime/strptime functions. By
+      default `None`.
+
+    `show_time`
+      boolean indicating whether also time should be displayed when
+      rendering the value of this field. This should usually be
+      `True`, so it's also the default.
+    """
+    implements(IFormattedDatetime)
+    date_format = None
+    show_time = True
+    def __init__(self, date_format=None, show_time=True, *args, **kw):
+        self.date_format = date_format
+        self.show_time = show_time
+        return super(FormattedDatetime, self).__init__(*args, **kw)
Index: /main/waeup.kofa/trunk/src/waeup/kofa/schema/interfaces.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/schema/interfaces.py	(revision 8157)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/schema/interfaces.py	(revision 8158)
@@ -17,5 +17,5 @@
 ##
 from zope import schema
-from zope.schema.interfaces import IDate
+from zope.schema.interfaces import IDate, IDatetime
 
 class IFormattedDate(IDate):
@@ -37,2 +37,21 @@
         default = '%Y-%m-%d',
         )
+
+class IFormattedDatetime(IDatetime):
+    """A formatted datetime.
+
+    Basically a zope.schema.IDatetime, but with optional additional
+    attributes. These attributes _can_ be used by widgets to change
+    the way of editing or displaying a date.
+
+    The waeup.kofa.widgets.datetimewidget.FormattedDatetimeWidget is a
+    widget that supports these additional attributes.
+    """
+    show_time = schema.Bool(
+        title = u'Show time when displaying this date?',
+        default = True,
+        )
+    date_format = schema.ASCII(
+        title = u'A date format string suitable for use with strftime.',
+        default = '%Y-%m-%d %H:%M:%S',
+        )
Index: /main/waeup.kofa/trunk/src/waeup/kofa/schema/tests/test_fields.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/schema/tests/test_fields.py	(revision 8157)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/schema/tests/test_fields.py	(revision 8158)
@@ -3,7 +3,7 @@
 import unittest
 from zope.interface.verify import verifyClass, verifyObject
-from zope.schema.interfaces import IDate
-from waeup.kofa.schema import FormattedDate
-from waeup.kofa.schema.interfaces import IFormattedDate
+from zope.schema.interfaces import IDate, IDatetime
+from waeup.kofa.schema import FormattedDate, FormattedDatetime
+from waeup.kofa.schema.interfaces import IFormattedDate, IFormattedDatetime
 
 class FormattedDateTests(unittest.TestCase):
@@ -32,2 +32,29 @@
         self.assertEqual(obj.date_format, '%d.%m.%Y')
         return
+
+class FormattedDatetimeTests(unittest.TestCase):
+    # Tests for FormattedDatetime field.
+
+    def test_iface(self):
+        # make sure we fullfill interface contracts
+        obj = FormattedDatetime()
+        verifyClass(IDatetime, FormattedDatetime)
+        verifyClass(IFormattedDatetime, FormattedDatetime)
+        verifyObject(IDatetime, obj)
+        verifyObject(IFormattedDatetime, obj)
+        return
+
+    def test_defaults(self):
+        # we get expected default values for datetimes.
+        obj = FormattedDatetime()
+        self.assertEqual(obj.show_time, True)
+        self.assertEqual(obj.date_format, None)
+        return
+
+    def test_attribs(self):
+        # we can set the promised attributes.
+        obj = FormattedDatetime(show_time=False,
+                                date_format='%d.%m.%Y %H:%M:%S Uhr')
+        self.assertEqual(obj.show_time, False)
+        self.assertEqual(obj.date_format, '%d.%m.%Y %H:%M:%S Uhr')
+        return
Index: /main/waeup.kofa/trunk/src/waeup/kofa/widgets/datetimewidget.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/widgets/datetimewidget.py	(revision 8158)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/widgets/datetimewidget.py	(revision 8158)
@@ -0,0 +1,170 @@
+## $Id$
+##
+## Copyright (C) 2012 Uli Fouquet & Henrik Bettermann
+## This program 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.
+##
+## This program 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 this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""
+A datewidget with customizable date format.
+"""
+from datetime import datetime
+from zope.formlib.interfaces import ConversionError, IDisplayWidget
+from zope.formlib.textwidgets import (
+    DatetimeWidget, DatetimeDisplayWidget, escape)
+from zope.formlib.widget import renderElement, CustomWidgetFactory
+from zope.interface import implements
+
+#: A dictionary of supported datetime formats.
+#:
+#: The following formats are supported:
+#:
+#: ``iso``
+#:    ISO format ``YYYY-MM-DD HH:MM:SS``
+#: ``le``
+#:    little endian with slashes: ``DD/MM/YYYY HH:MM:SS``
+#: ``de``
+#:    german date format: ``DD.MM.YYYY HH:MM:SS``
+#: ``us``
+#:    middle endian format common in the U.S.: ``MM/DD/YYYY HH:MM:SS``
+#:
+DATETIME_FORMATS = {
+    'iso': ('datetimepicker', '%Y-%m-%d %H:%M:%S'),
+    'le':  ('datetimepicker-le', '%d/%m/%Y %H:%M:%S'),
+    'de':  ('datetimepicker-de', '%d.%m.%Y %H:%M:%S'),
+    'us':  ('datetimepicker-us', '%m/%d/%Y %H:%M:%S'),
+    }
+
+#: a dict containing <FORMAT> as key and suitable CSS tags as values.
+FORMATS_BY_VALUE = dict(
+    [(val[1], val[0]) for key, val in DATETIME_FORMATS.items()])
+
+class FormattedDatetimeWidget(DatetimeWidget):
+    """A datetime widget that supports different (and _explicit_) date formats.
+
+    If the widget is bound to a schema field with respective
+    attributes, it reads its `date_format` attribute (see
+    waeup.kofa.schema.FormattedDatetime for an example) and sets a CSS
+    tag according to the values.
+
+    The widget also accepts ISO format as a fallback, even if a
+    different format was set. This should help with imports.
+
+    This is an input widget.
+    """
+    date_format = '%Y-%m-%d %H:%M:%S'
+    show_time = True
+
+    def __init__(self, context, request, *args, **kw):
+        # try to grab date_format and show_time from bound schema field.
+        date_format = getattr(context, 'date_format', self.date_format)
+        if date_format is not None:
+            self.date_format = date_format
+        self.show_time = getattr(context, 'show_time', self.show_time)
+        # add css class determined by date_format
+        css_cls = FORMATS_BY_VALUE.get(self.date_format, '')
+        self.cssClass = ' '.join([self.cssClass, css_cls]).strip()
+        return super(FormattedDatetimeWidget, self).__init__(
+            context, request, *args, **kw)
+
+    def _toFieldValue(self, input):
+        # In import files we can use the hash symbol at the end of a
+        # datetime string to avoid annoying automatic datetime transformation
+        # by Excel or Calc
+        input = input.strip('#')
+        if input == self._missing:
+            return self.context.missing_value
+        else:
+            try:
+                value = datetime.strptime(input, self.date_format)
+            except (ValueError, IndexError), v:
+                try:
+                    # Try ISO format as fallback.
+                    # This is needed for instance during imports.
+                    value = datetime.strptime(
+                        input, FormattedDatetimeWidget.date_format)
+                except (ValueError, IndexError), v:
+                    raise ConversionError("Invalid datetime data", v)
+        return value
+
+    def _toFormValue(self, value):
+        if value:
+            value = value.strftime(self.date_format)
+        return value
+
+
+class FormattedDatetimeDisplayWidget(DatetimeDisplayWidget):
+    """A datetime widget that supports different (and _explicit_) date formats.
+
+    This is a display widget.
+    """
+    date_format = '%Y-%m-%d %H:%M:%S'
+    show_time = False
+
+    implements(IDisplayWidget)
+
+    def __init__(self, context, request, *args, **kw):
+        # try to grab date_format and show_time from bound schema field.
+        date_format = getattr(context, 'date_format', self.date_format)
+        if date_format is not None:
+            self.date_format = date_format
+        self.show_time = getattr(context, 'show_time', self.show_time)
+        return super(FormattedDatetimeDisplayWidget, self).__init__(
+            context, request, *args, **kw)
+
+    def __call__(self):
+        if self._renderedValueSet():
+            content = self._data
+        else:
+            content = self.context.default
+        if content == self.context.missing_value:
+            return ""
+        content = content.strftime(self.date_format)
+        return renderElement("span", contents=escape(content),
+                             cssClass=self.cssClass)
+
+class DatetimeLEWidget(FormattedDatetimeWidget):
+    date_format = '%d/%m/%Y %H:%M:%S'
+
+class DatetimeDEWidget(FormattedDatetimeWidget):
+    date_format = '%d.%m.%Y %H:%M:%S'
+
+class DatetimeUSWidget(FormattedDatetimeWidget):
+    date_format = '%m/%d/%Y %H:%M:%S'
+
+class DatetimeLEDisplayWidget(FormattedDatetimeDisplayWidget):
+    date_format = '%d/%m/%Y %H:%M:%S'
+
+class DatetimeDEDisplayWidget(FormattedDatetimeDisplayWidget):
+    date_format = '%d.%m.%Y %H:%M:%S'
+
+class DatetimeUSDisplayWidget(FormattedDatetimeDisplayWidget):
+    date_format = '%m/%d/%Y %H:%M:%S'
+
+def FriendlyDatetimeDisplayWidget(format):
+    """Get a friendly datetime display widget for `format`.
+
+    This widget is suitable for display forms.
+
+    Valid `format` values are the keys of `DATE_FORMATS`
+    dict. Default is ``le`` (little endian; DD/MM/YYYY %H:%M:%S).
+
+    This widget is not rendered with a specialized CSS tag for
+    enabling JavaScript datepickers. `css_class` is ignored which means
+    there is no difference between e.g. ``le`` and ``le-year``.`
+    """
+    css_class, date_format = DATE_FORMATS.get(format, DATE_FORMATS['le'])
+    datetime_format = date_format + ' %H:%M:%S'
+    return CustomWidgetFactory(
+        FormattedDateDisplayWidget,
+        date_format=datetime_format)
Index: /main/waeup.kofa/trunk/src/waeup/kofa/widgets/overrides.zcml
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/widgets/overrides.zcml	(revision 8157)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/widgets/overrides.zcml	(revision 8158)
@@ -25,3 +25,19 @@
       />
 
+  <adapter
+      for="waeup.kofa.schema.interfaces.IFormattedDatetime
+           zope.publisher.interfaces.browser.IBrowserRequest"
+      provides="zope.formlib.interfaces.ISimpleInputWidget"
+      factory="waeup.kofa.widgets.datetimewidget.DatetimeLEWidget"
+      permission="zope.Public"
+      />
+
+  <adapter
+      for="waeup.kofa.schema.interfaces.IFormattedDatetime
+           zope.publisher.interfaces.browser.IBrowserRequest"
+      provides="zope.formlib.interfaces.IDisplayWidget"
+      factory="waeup.kofa.widgets.datetimewidget.DatetimeLEDisplayWidget"
+      permission="zope.Public"
+      />
+
 </configure>
Index: /main/waeup.kofa/trunk/src/waeup/kofa/widgets/tests/test_datetimewidget.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/widgets/tests/test_datetimewidget.py	(revision 8158)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/widgets/tests/test_datetimewidget.py	(revision 8158)
@@ -0,0 +1,127 @@
+"""Formatted Datetime widget tests.
+"""
+import datetime
+import unittest
+import doctest
+from zope.schema import Datetime
+from zope.interface.verify import verifyClass
+
+from zope.formlib.tests.test_browserwidget import (
+    SimpleInputWidgetTest, BrowserWidgetTest, )
+from zope.formlib.interfaces import IInputWidget, IDisplayWidget
+from waeup.kofa.widgets.datetimewidget import (
+    FormattedDatetimeWidget, FormattedDatetimeDisplayWidget, )
+from zope.formlib.widgets import DateI18nWidget
+from zope.formlib.interfaces import ConversionError, WidgetInputError
+
+
+class FormattedDatetimeWidgetTest(SimpleInputWidgetTest):
+    """Documents and tests the formatted datetime widget.
+
+        >>> verifyClass(IInputWidget, FormattedDatetimeWidget)
+        True
+    """
+
+    _FieldFactory = Datetime
+    _WidgetFactory = FormattedDatetimeWidget
+
+    def testRender(self):
+        super(FormattedDatetimeWidgetTest, self).testRender(
+            datetime.datetime(2003, 3, 26, 12, 10, 20),
+            ('type="text"', 'id="field.foo"', 'name="field.foo"',
+             'value="2003-03-26 12:10:20"'))
+
+    def testRenderCustomFormat(self):
+        self._widget.date_format = "%d/%m/%y %H:%M:%S"
+        super(FormattedDatetimeWidgetTest, self).testRender(
+            datetime.datetime(2004, 3, 26, 12, 10, 20),
+            ('type="text"', 'id="field.foo"', 'name="field.foo"',
+             'value="26/03/04 12:10:20"'))
+
+    def testProperties(self):
+        self.assertEqual(self._widget.tag, 'input')
+        self.assertEqual(self._widget.type, 'text')
+        # By default the datetime format is ISO and the rendered CSS
+        # 'datetimepicker'
+        self.assertEqual(self._widget.cssClass, 'datetimepicker')
+        self.assertEqual(self._widget.extra, '')
+
+    def test_hasInput(self):
+        del self._widget.request.form['field.foo']
+        self.assertFalse(self._widget.hasInput())
+        self._widget.request.form['field.foo'] = u''
+        self.assertTrue(self._widget.hasInput())
+        self._widget.request.form['field.foo'] = u'2003-03-26 12:10:23'
+        self.assertTrue(self._widget.hasInput())
+
+    def test_getDefaultInputValue(self,
+            value=u'2004-03-26 12:10:20',
+            check_value=datetime.datetime(2004, 3, 26, 12, 10, 20)):
+        self._widget.request.form['field.foo'] = u''
+        self.assertRaises(WidgetInputError, self._widget.getInputValue)
+        self._widget.request.form['field.foo'] = value
+        self.assertEquals(self._widget.getInputValue(), check_value)
+        self._widget.request.form['field.foo'] = u'abc'
+        self.assertRaises(ConversionError, self._widget.getInputValue)
+
+    def test_getCustomInputValue_de(self):
+        self._widget.date_format = "%d.%m.%y %H:%M:%S"
+        self.test_getDefaultInputValue(u'26.03.04 12:10:20')
+
+    def test_getCustomInputValue_de2(self):
+        self._widget.date_format = "%d.%m.%Y %H:%M:%S"
+        self.test_getDefaultInputValue(u'26.03.2004 12:10:20')
+
+    def test_getCustomInputValue_us(self):
+        self._widget.date_format = "%m/%d/%Y %H:%M:%S"
+        self.test_getDefaultInputValue(u'03/26/2004 12:10:20')
+
+class FormattedDatetimeDisplayWidgetTest(BrowserWidgetTest):
+    """The FormatterdDisplayDatetimeWidget complies with IDisplayWidget.
+
+        >>> verifyClass(IDisplayWidget, FormattedDatetimeDisplayWidget)
+        True
+    """
+
+    _WidgetFactory = FormattedDatetimeDisplayWidget
+    expected_class = "dateTime"
+
+    def setUp(self):
+        super(FormattedDatetimeDisplayWidgetTest, self).setUp()
+        self._value = datetime.date(2004, 12, 01)
+
+    def testDefaultDisplayStyle(self):
+        self.assertFalse(self._widget.displayStyle)
+
+    def testRenderDefault(self):
+        self._widget.setRenderedValue(self._value)
+        self.verifyResult(self._widget(),
+                          ["<span",
+                           'class="%s"' % self.expected_class,
+                           "2004-12-01",
+                           "</span"])
+
+    def testRenderCustom(self):
+        self._widget.setRenderedValue(self._value)
+        self._widget.date_format = '%m/%d/%Y'
+        self.verifyResult(self._widget(),
+                          ["<span",
+                           'class="%s"' % self.expected_class,
+                           "12/01/2004",
+                           "</span"])
+
+    def testRenderNone(self):
+        self._widget.setRenderedValue(None)
+        self._widget.date_format = '%m/%d/%Y'
+        self.assertEqual(self._widget(), '')
+
+    def testNoValueSet(self):
+        self._widget.date_format = '%m/%d/%Y'
+        self.assertEqual(self._widget(), '')
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(FormattedDatetimeWidgetTest),
+        unittest.makeSuite(FormattedDatetimeDisplayWidgetTest),
+        doctest.DocTestSuite(),
+        ))
