## $Id: datewidget.py 8152 2012-04-14 14:13:39Z uli $
##
## Copyright (C) 2011 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 DateWidget, DateDisplayWidget, escape
from zope.formlib.widget import renderElement, CustomWidgetFactory
from zope.interface import implements

#: A dictionary of supported date formats.
#:
#: The following formats are supported:
#:
#: ``iso``
#:    ISO format ``YYYY-MM-DD``
#: ``le``
#:    little endian with slashes: ``DD/MM/YYYY``
#: ``de``
#:    german date format: ``DD.MM.YYYY``
#: ``us``
#:    middle endian format common in the U.S.: ``MM/DD/YYYY``
#:
#: Furthermore we support for input widgets an additional year
#: marker. Input date widgets with this marker provide also a year
#: selector, handy for dates of birth etc.
#:
#: The year-supporting formats are similar to the basic versions above:
#:
#: ``iso-year``
#:    ISO format ``YYYY-MM-DD``
#: ``le-year``
#:    little endian with slashes: ``DD/MM/YYYY``
#: ``de-year``
#:    german date format: ``DD.MM.YYYY``
#: ``us-year``
#:    middle endian format common in the U.S.: ``MM/DD/YYYY``
#:
#: For date display widgets there is naturally no difference between a
#: year and non-year setting (you can for instance use 'le' or 'le-year'
#: with the same output).
DATE_FORMATS = {
    'iso': ('datepicker', '%Y-%m-%d'),
    'le':  ('datepicker-le', '%d/%m/%Y'),
    'de':  ('datepicker-de', '%d.%m.%Y'),
    'us':  ('datepicker-us', '%m/%d/%Y'),
    'iso-year': ('datepicker-year', '%Y-%m-%d'),
    'le-year':  ('datepicker-le-year', '%d/%m/%Y'),
    'de-year':  ('datepicker-de-year', '%d.%m.%Y'),
    'us-year':  ('datepicker-us-year', '%m/%d/%Y'),
    }

#: a dict containing tuples (<FORMAT>, <SHOW_YEAR>) as keys and
#: suitable CSS tags as values.
FORMATS_BY_VALUE = dict(
    [((val[1], 'year' in key), val[0]) for key, val in DATE_FORMATS.items()])

class FormattedDateWidget(DateWidget):
    """A date widget that supports different (and _explicit_) date formats.

    If the widget is bound to a schema field with respective
    attributes, it reads its `show_year` and `date_format` attributes
    (see waeup.kofa.schema.FormattedDate for an example) and sets a
    CSS tag according to these 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'
    show_year = False

    def __init__(self, context, request, *args, **kw):
        # try to grab date_format and show_year 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_year = getattr(context, 'show_year', self.show_year)
        # add css class determined by date_format and show_year
        css_cls = FORMATS_BY_VALUE.get((self.date_format, self.show_year), '')
        self.cssClass = ' '.join([self.cssClass, css_cls]).strip()
        return super(FormattedDateWidget, self).__init__(
            context, request, *args, **kw)

    def _toFieldValue(self, input):
        # In import files we can use the hash symbol at the end of a
        # date string to avoid annoying automatic date 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, FormattedDateWidget.date_format)
                except (ValueError, IndexError), v:
                    raise ConversionError("Invalid datetime data", v)
        return value.date()

    def _toFormValue(self, value):
        if value:
            value = value.strftime(self.date_format)
        return value


class FormattedDateDisplayWidget(DateDisplayWidget):
    """A date widget that supports different (and _explicit_) date formats.

    This is a display widget.
    """
    date_format = '%Y-%m-%d'
    show_year = False

    implements(IDisplayWidget)

    def __init__(self, context, request, *args, **kw):
        # try to grab date_format and show_year 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_year = getattr(context, 'show_year', self.show_year)
        return super(FormattedDateDisplayWidget, 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 DateLEWidget(FormattedDateWidget):
    date_format = '%d/%m/%Y'

class DateDEWidget(FormattedDateWidget):
    date_format = '%d.%m.%Y'

class DateUSWidget(FormattedDateWidget):
    date_format = '%m/%d/%Y'

class DateLEDisplayWidget(FormattedDateDisplayWidget):
    date_format = '%d/%m/%Y'

class DateDEDisplayWidget(FormattedDateDisplayWidget):
    date_format = '%d.%m.%Y'

class DateUSDisplayWidget(FormattedDateDisplayWidget):
    date_format = '%m/%d/%Y'

def FriendlyDateWidget(format):
    """Get a friendly date input widget for `format`.

    This widget is suitable for edit and add forms.

    Valid `format` values are the keys of `DATE_FORMATS`
    dict. Default is ``le`` (little endian; DD/MM/YYYY).

    Friendly date widgets are rendered with a specialized CSS tag for
    enabling JavaScript datepickers.
    """
    css_class, date_format = DATE_FORMATS.get(format, DATE_FORMATS['le'])
    return CustomWidgetFactory(
        FormattedDateWidget,
        cssClass=css_class,
        date_format=date_format)

def FriendlyDateDisplayWidget(format):
    """Get a friendly date 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).

    This widget is not rendered with a specialized CSS tag for
    enabling JavaScript datepickers. `css_class` is ignored which means
    there is nor difference between e.g. ``le`` and ``le-year``.`
    """
    css_class, date_format = DATE_FORMATS.get(format, DATE_FORMATS['le'])
    return CustomWidgetFactory(
        FormattedDateDisplayWidget,
        date_format=date_format)

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)
