com.vaadin.v7.ui.Calendar.java Source code

Java tutorial

Introduction

Here is the source code for com.vaadin.v7.ui.Calendar.java

Source

/*
 * Copyright 2000-2018 Vaadin Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.vaadin.v7.ui;

import java.lang.reflect.Method;
import java.text.DateFormat;
import java.text.DateFormatSymbols;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.EventListener;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.jsoup.nodes.Attributes;
import org.jsoup.nodes.Element;

import com.vaadin.event.Action;
import com.vaadin.event.Action.Handler;
import com.vaadin.event.dd.DropHandler;
import com.vaadin.event.dd.DropTarget;
import com.vaadin.event.dd.TargetDetails;
import com.vaadin.server.KeyMapper;
import com.vaadin.server.PaintException;
import com.vaadin.server.PaintTarget;
import com.vaadin.ui.AbstractComponent;
import com.vaadin.ui.LegacyComponent;
import com.vaadin.ui.declarative.DesignAttributeHandler;
import com.vaadin.ui.declarative.DesignContext;
import com.vaadin.v7.data.Container;
import com.vaadin.v7.data.util.BeanItemContainer;
import com.vaadin.v7.shared.ui.calendar.CalendarEventId;
import com.vaadin.v7.shared.ui.calendar.CalendarServerRpc;
import com.vaadin.v7.shared.ui.calendar.CalendarState;
import com.vaadin.v7.shared.ui.calendar.CalendarState.EventSortOrder;
import com.vaadin.v7.shared.ui.calendar.DateConstants;
import com.vaadin.v7.ui.components.calendar.CalendarComponentEvent;
import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents;
import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.BackwardEvent;
import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.BackwardHandler;
import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.DateClickEvent;
import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.DateClickHandler;
import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.EventClick;
import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.EventClickHandler;
import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.EventMoveHandler;
import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.EventResize;
import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.EventResizeHandler;
import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.ForwardEvent;
import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.ForwardHandler;
import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.MoveEvent;
import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.RangeSelectEvent;
import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.RangeSelectHandler;
import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.WeekClick;
import com.vaadin.v7.ui.components.calendar.CalendarComponentEvents.WeekClickHandler;
import com.vaadin.v7.ui.components.calendar.CalendarDateRange;
import com.vaadin.v7.ui.components.calendar.CalendarTargetDetails;
import com.vaadin.v7.ui.components.calendar.ContainerEventProvider;
import com.vaadin.v7.ui.components.calendar.event.BasicEventProvider;
import com.vaadin.v7.ui.components.calendar.event.CalendarEditableEventProvider;
import com.vaadin.v7.ui.components.calendar.event.CalendarEvent;
import com.vaadin.v7.ui.components.calendar.event.CalendarEvent.EventChangeEvent;
import com.vaadin.v7.ui.components.calendar.event.CalendarEvent.EventChangeListener;
import com.vaadin.v7.ui.components.calendar.event.CalendarEventProvider;
import com.vaadin.v7.ui.components.calendar.handler.BasicBackwardHandler;
import com.vaadin.v7.ui.components.calendar.handler.BasicDateClickHandler;
import com.vaadin.v7.ui.components.calendar.handler.BasicEventMoveHandler;
import com.vaadin.v7.ui.components.calendar.handler.BasicEventResizeHandler;
import com.vaadin.v7.ui.components.calendar.handler.BasicForwardHandler;
import com.vaadin.v7.ui.components.calendar.handler.BasicWeekClickHandler;

/**
 * <p>
 * Vaadin Calendar is for visualizing events in a calendar. Calendar events can
 * be visualized in the variable length view depending on the start and end
 * dates.
 * </p>
 *
 * <li>You can set the viewable date range with the {@link #setStartDate(Date)}
 * and {@link #setEndDate(Date)} methods. Calendar has a default date range of
 * one week</li>
 *
 * <li>Calendar has two kind of views: monthly and weekly view</li>
 *
 * <li>If date range is seven days or shorter, the weekly view is used.</li>
 *
 * <li>Calendar queries its events by using a {@link CalendarEventProvider}. By
 * default, a {@link BasicEventProvider} is used.</li>
 *
 * @since 7.1
 * @author Vaadin Ltd.
 *
 * @deprecated As of 8.0, no replacement available.
 */
@SuppressWarnings("serial")
@Deprecated
public class Calendar extends AbstractLegacyComponent implements CalendarComponentEvents.NavigationNotifier,
        CalendarComponentEvents.EventMoveNotifier, CalendarComponentEvents.RangeSelectNotifier,
        CalendarComponentEvents.EventResizeNotifier, CalendarEventProvider.EventSetChangeListener, DropTarget,
        CalendarEditableEventProvider, Action.Container, LegacyComponent {

    /**
     * Calendar can use either 12 hours clock or 24 hours clock.
     */
    @Deprecated
    public enum TimeFormat {

        Format12H(), Format24H();
    }

    /** Defines currently active format for time. 12H/24H. */
    protected TimeFormat currentTimeFormat;

    /** Internal calendar data source. */
    protected java.util.Calendar currentCalendar = java.util.Calendar.getInstance();

    /** Defines the component's active time zone. */
    protected TimeZone timezone;

    /** Defines the calendar's date range starting point. */
    protected Date startDate = null;

    /** Defines the calendar's date range ending point. */
    protected Date endDate = null;

    /** Event provider. */
    private CalendarEventProvider calendarEventProvider;

    /**
     * Internal buffer for the events that are retrieved from the event
     * provider.
     */
    protected List<CalendarEvent> events;

    /** Date format that will be used in the UIDL for dates. */
    protected DateFormat df_date = new SimpleDateFormat("yyyy-MM-dd");

    /** Time format that will be used in the UIDL for time. */
    protected DateFormat df_time = new SimpleDateFormat("HH:mm:ss");

    /** Date format that will be used in the UIDL for both date and time. */
    protected DateFormat df_date_time = new SimpleDateFormat(
            DateConstants.CLIENT_DATE_FORMAT + "-" + DateConstants.CLIENT_TIME_FORMAT);

    /**
     * Week view's scroll position. Client sends updates to this value so that
     * scroll position wont reset all the time.
     */
    private int scrollTop = 0;

    /** Caption format for the weekly view */
    private String weeklyCaptionFormat = null;

    /** Map from event ids to event handlers */
    private final Map<String, EventListener> handlers;

    /**
     * Drop Handler for Vaadin DD. By default null.
     */
    private DropHandler dropHandler;

    /**
     * First day to show for a week
     */
    private int firstDay = 1;

    /**
     * Last day to show for a week
     */
    private int lastDay = 7;

    /**
     * First hour to show for a day
     */
    private int firstHour = 0;

    /**
     * Last hour to show for a day
     */
    private int lastHour = 23;

    /**
     * List of action handlers.
     */
    private LinkedList<Action.Handler> actionHandlers = null;

    /**
     * Action mapper.
     */
    private KeyMapper<Action> actionMapper = null;

    /**
     *
     */
    private CalendarServerRpcImpl rpc = new CalendarServerRpcImpl();

    /**
     * The cached minimum minute shown when using
     * {@link #autoScaleVisibleHoursOfDay()}.
     */
    private Integer minTimeInMinutes;

    /**
     * The cached maximum minute shown when using
     * {@link #autoScaleVisibleHoursOfDay()}.
     */
    private Integer maxTimeInMinutes;

    private Integer customFirstDayOfWeek;

    /**
     * Returns the logger for the calendar.
     */
    protected Logger getLogger() {
        return Logger.getLogger(Calendar.class.getName());
    }

    /**
     * Construct a Vaadin Calendar with a BasicEventProvider and no caption.
     * Default date range is one week.
     */
    public Calendar() {
        this(null, new BasicEventProvider());
    }

    /**
     * Construct a Vaadin Calendar with a BasicEventProvider and the provided
     * caption. Default date range is one week.
     *
     * @param caption
     */
    public Calendar(String caption) {
        this(caption, new BasicEventProvider());
    }

    /**
     * <p>
     * Construct a Vaadin Calendar with event provider. Event provider is
     * obligatory, because calendar component will query active events through
     * it.
     * </p>
     *
     * <p>
     * By default, Vaadin Calendar will show dates from the start of the current
     * week to the end of the current week. Use {@link #setStartDate(Date)} and
     * {@link #setEndDate(Date)} to change this.
     * </p>
     *
     * @param eventProvider
     *            Event provider, cannot be null.
     */
    public Calendar(CalendarEventProvider eventProvider) {
        this(null, eventProvider);
    }

    /**
     * <p>
     * Construct a Vaadin Calendar with event provider and a caption. Event
     * provider is obligatory, because calendar component will query active
     * events through it.
     * </p>
     *
     * <p>
     * By default, Vaadin Calendar will show dates from the start of the current
     * week to the end of the current week. Use {@link #setStartDate(Date)} and
     * {@link #setEndDate(Date)} to change this.
     * </p>
     *
     * @param eventProvider
     *            Event provider, cannot be null.
     */
    // this is the constructor every other constructor calls
    public Calendar(String caption, CalendarEventProvider eventProvider) {
        registerRpc(rpc);
        setCaption(caption);
        handlers = new HashMap<String, EventListener>();
        setDefaultHandlers();
        currentCalendar.setTime(new Date());
        setEventProvider(eventProvider);
        getState().firstDayOfWeek = firstDay;
        getState().lastVisibleDayOfWeek = lastDay;
        getState().firstHourOfDay = firstHour;
        getState().lastHourOfDay = lastHour;
        setTimeFormat(null);

    }

    @Override
    public CalendarState getState() {
        return (CalendarState) super.getState();
    }

    @Override
    protected CalendarState getState(boolean markAsDirty) {
        return (CalendarState) super.getState(markAsDirty);
    }

    @Override
    public void beforeClientResponse(boolean initial) {
        super.beforeClientResponse(initial);

        initCalendarWithLocale();

        getState().format24H = TimeFormat.Format24H == getTimeFormat();
        setupDaysAndActions();
        setupCalendarEvents();
        rpc.scroll(scrollTop);
    }

    /**
     * Set all the wanted default handlers here. This is always called after
     * constructing this object. All other events have default handlers except
     * range and event click.
     */
    protected void setDefaultHandlers() {
        setHandler(new BasicBackwardHandler());
        setHandler(new BasicForwardHandler());
        setHandler(new BasicWeekClickHandler());
        setHandler(new BasicDateClickHandler());
        setHandler(new BasicEventMoveHandler());
        setHandler(new BasicEventResizeHandler());
    }

    /**
     * Gets the calendar's start date.
     *
     * @return First visible date.
     */
    public Date getStartDate() {
        if (startDate == null) {
            currentCalendar.set(java.util.Calendar.MILLISECOND, 0);
            currentCalendar.set(java.util.Calendar.SECOND, 0);
            currentCalendar.set(java.util.Calendar.MINUTE, 0);
            currentCalendar.set(java.util.Calendar.HOUR_OF_DAY, 0);
            currentCalendar.set(java.util.Calendar.DAY_OF_WEEK, currentCalendar.getFirstDayOfWeek());
            return currentCalendar.getTime();
        }
        return startDate;
    }

    /**
     * Sets start date for the calendar. This and {@link #setEndDate(Date)}
     * control the range of dates visible on the component. The default range is
     * one week.
     *
     * @param date
     *            First visible date to show.
     */
    public void setStartDate(Date date) {
        if (!date.equals(startDate)) {
            startDate = date;
            markAsDirty();
        }
    }

    /**
     * Gets the calendar's end date.
     *
     * @return Last visible date.
     */
    public Date getEndDate() {
        if (endDate == null) {
            currentCalendar.set(java.util.Calendar.MILLISECOND, 0);
            currentCalendar.set(java.util.Calendar.SECOND, 59);
            currentCalendar.set(java.util.Calendar.MINUTE, 59);
            currentCalendar.set(java.util.Calendar.HOUR_OF_DAY, 23);
            currentCalendar.set(java.util.Calendar.DAY_OF_WEEK, currentCalendar.getFirstDayOfWeek() + 6);
            return currentCalendar.getTime();
        }
        return endDate;
    }

    /**
     * Sets end date for the calendar. Starting from startDate, only six weeks
     * will be shown if duration to endDate is longer than six weeks.
     *
     * This and {@link #setStartDate(Date)} control the range of dates visible
     * on the component. The default range is one week.
     *
     * @param date
     *            Last visible date to show.
     */
    public void setEndDate(Date date) {
        if (startDate != null && startDate.after(date)) {
            startDate = (Date) date.clone();
            markAsDirty();
        } else if (!date.equals(endDate)) {
            endDate = date;
            markAsDirty();
        }
    }

    /**
     * Sets the locale to be used in the Calendar component.
     *
     * @see AbstractComponent#setLocale(java.util.Locale)
     */
    @Override
    public void setLocale(Locale newLocale) {
        super.setLocale(newLocale);
        initCalendarWithLocale();
    }

    /**
     * Initialize the java calendar instance with the current locale and
     * timezone.
     */
    private void initCalendarWithLocale() {
        if (timezone != null) {
            currentCalendar = java.util.Calendar.getInstance(timezone, getLocale());

        } else {
            currentCalendar = java.util.Calendar.getInstance(getLocale());
        }

        if (customFirstDayOfWeek != null) {
            currentCalendar.setFirstDayOfWeek(customFirstDayOfWeek);
        }
    }

    private void setupCalendarEvents() {
        int durationInDays = (int) ((endDate.getTime() - startDate.getTime()) / DateConstants.DAYINMILLIS);
        durationInDays++;
        if (durationInDays > 60) {
            throw new RuntimeException("Daterange is too big (max 60) = " + durationInDays);
        }

        Date firstDateToShow = expandStartDate(startDate, durationInDays > 7);
        Date lastDateToShow = expandEndDate(endDate, durationInDays > 7);

        currentCalendar.setTime(firstDateToShow);
        events = getEventProvider().getEvents(firstDateToShow, lastDateToShow);
        cacheMinMaxTimeOfDay(events);

        List<CalendarState.Event> calendarStateEvents = new ArrayList<CalendarState.Event>();
        if (events != null) {
            for (int i = 0; i < events.size(); i++) {
                CalendarEvent e = events.get(i);
                CalendarState.Event event = new CalendarState.Event();
                event.index = i;
                event.caption = e.getCaption() == null ? "" : e.getCaption();
                event.dateFrom = df_date.format(e.getStart());
                event.dateTo = df_date.format(e.getEnd());
                event.timeFrom = df_time.format(e.getStart());
                event.timeTo = df_time.format(e.getEnd());
                event.description = e.getDescription() == null ? "" : e.getDescription();
                event.styleName = e.getStyleName() == null ? "" : e.getStyleName();
                event.allDay = e.isAllDay();
                calendarStateEvents.add(event);
            }
        }
        getState().events = calendarStateEvents;
    }

    /**
     * Stores the minimum and maximum time-of-day in minutes for the events.
     *
     * @param events
     *            A list of calendar events. Can be <code>null</code>.
     */
    private void cacheMinMaxTimeOfDay(List<CalendarEvent> events) {
        minTimeInMinutes = null;
        maxTimeInMinutes = null;
        if (events != null) {
            for (CalendarEvent event : events) {
                int minuteOfDayStart = getMinuteOfDay(event.getStart());
                int minuteOfDayEnd = getMinuteOfDay(event.getEnd());
                if (minTimeInMinutes == null) {
                    minTimeInMinutes = minuteOfDayStart;
                    maxTimeInMinutes = minuteOfDayEnd;
                } else {
                    if (minuteOfDayStart < minTimeInMinutes) {
                        minTimeInMinutes = minuteOfDayStart;
                    }
                    if (minuteOfDayEnd > maxTimeInMinutes) {
                        maxTimeInMinutes = minuteOfDayEnd;
                    }
                }
            }
        }
    }

    private static int getMinuteOfDay(Date date) {
        java.util.Calendar calendar = java.util.Calendar.getInstance();
        calendar.setTime(date);
        return calendar.get(java.util.Calendar.HOUR_OF_DAY) * 60 + calendar.get(java.util.Calendar.MINUTE);
    }

    /**
     * Sets the displayed start and end time to fit all current events that were
     * retrieved from the last call to getEvents().
     * <p>
     * If no events exist, nothing happens.
     * <p>
     * <b>NOTE: triggering this method only does this once for the current
     * events - events that are not in the current visible range, are
     * ignored!</b>
     *
     * @see #setFirstVisibleHourOfDay(int)
     * @see #setLastVisibleHourOfDay(int)
     */
    public void autoScaleVisibleHoursOfDay() {
        if (minTimeInMinutes != null) {
            setFirstVisibleHourOfDay(minTimeInMinutes / 60);
            // Do not show the final hour if last minute ends on it
            setLastVisibleHourOfDay((maxTimeInMinutes - 1) / 60);
        }
    }

    /**
     * Resets the {@link #setFirstVisibleHourOfDay(int)} and
     * {@link #setLastVisibleHourOfDay(int)} to the default values, 0 and 23
     * respectively.
     *
     * @see #autoScaleVisibleHoursOfDay()
     * @see #setFirstVisibleHourOfDay(int)
     * @see #setLastVisibleHourOfDay(int)
     */
    public void resetVisibleHoursOfDay() {
        setFirstVisibleHourOfDay(0);
        setLastVisibleHourOfDay(23);
    }

    private void setupDaysAndActions() {
        // Make sure we have a up-to-date locale
        initCalendarWithLocale();

        CalendarState state = getState();

        state.firstDayOfWeek = currentCalendar.getFirstDayOfWeek();

        // If only one is null, throw exception
        // If both are null, set defaults
        if (startDate == null ^ endDate == null) {
            String message = "Schedule cannot be painted without a proper date range.\n";
            if (startDate == null) {
                throw new IllegalStateException(message + "You must set a start date using setStartDate(Date).");

            } else {
                throw new IllegalStateException(message + "You must set an end date using setEndDate(Date).");
            }

        } else if (startDate == null && endDate == null) {
            // set defaults
            startDate = getStartDate();
            endDate = getEndDate();
        }

        int durationInDays = (int) ((endDate.getTime() - startDate.getTime()) / DateConstants.DAYINMILLIS);
        durationInDays++;
        if (durationInDays > 60) {
            throw new RuntimeException("Daterange is too big (max 60) = " + durationInDays);
        }

        state.dayNames = getDayNamesShort();
        state.monthNames = getMonthNamesShort();

        // Use same timezone in all dates this component handles.
        // Show "now"-marker in browser within given timezone.
        Date now = new Date();
        currentCalendar.setTime(now);
        now = currentCalendar.getTime();

        // Reset time zones for custom date formats
        df_date.setTimeZone(currentCalendar.getTimeZone());
        df_time.setTimeZone(currentCalendar.getTimeZone());

        state.now = df_date.format(now) + " " + df_time.format(now);

        Date firstDateToShow = expandStartDate(startDate, durationInDays > 7);
        Date lastDateToShow = expandEndDate(endDate, durationInDays > 7);

        currentCalendar.setTime(firstDateToShow);

        DateFormat weeklyCaptionFormatter = getWeeklyCaptionFormatter();
        weeklyCaptionFormatter.setTimeZone(currentCalendar.getTimeZone());

        Map<CalendarDateRange, Set<Action>> actionMap = new HashMap<CalendarDateRange, Set<Action>>();

        List<CalendarState.Day> days = new ArrayList<CalendarState.Day>();

        // Send all dates to client from server. This
        // approach was taken because gwt doesn't
        // support date localization properly.
        while (currentCalendar.getTime().compareTo(lastDateToShow) < 1) {
            final Date date = currentCalendar.getTime();
            final CalendarState.Day day = new CalendarState.Day();
            day.date = df_date.format(date);
            day.localizedDateFormat = weeklyCaptionFormatter.format(date);
            day.dayOfWeek = getDowByLocale(currentCalendar);
            day.week = getWeek(currentCalendar);
            day.yearOfWeek = getYearOfWeek(currentCalendar);

            days.add(day);

            // Get actions for a specific date
            if (actionHandlers != null) {
                for (Action.Handler actionHandler : actionHandlers) {

                    // Create calendar which omits time
                    GregorianCalendar cal = new GregorianCalendar(getTimeZone(), getLocale());
                    cal.clear();
                    cal.set(currentCalendar.get(java.util.Calendar.YEAR),
                            currentCalendar.get(java.util.Calendar.MONTH),
                            currentCalendar.get(java.util.Calendar.DATE));

                    // Get day start and end times
                    Date start = cal.getTime();
                    cal.add(java.util.Calendar.DATE, 1);
                    cal.add(java.util.Calendar.SECOND, -1);
                    Date end = cal.getTime();

                    boolean monthView = durationInDays > 7;

                    /**
                     * If in day or week view add actions for each half-an-hour.
                     * If in month view add actions for each day
                     */
                    if (monthView) {
                        setActionsForDay(actionMap, start, end, actionHandler);
                    } else {
                        setActionsForEachHalfHour(actionMap, start, end, actionHandler);
                    }

                }
            }

            currentCalendar.add(java.util.Calendar.DATE, 1);
        }
        state.days = days;
        state.actions = createActionsList(actionMap);
    }

    private int getWeek(java.util.Calendar calendar) {
        return calendar.get(java.util.Calendar.WEEK_OF_YEAR);
    }

    private int getYearOfWeek(java.util.Calendar calendar) {
        // Would use calendar.getWeekYear() but it's only available since 1.7.
        int week = getWeek(calendar);
        int month = calendar.get(java.util.Calendar.MONTH);
        int year = calendar.get(java.util.Calendar.YEAR);

        if (week == 1 && month == java.util.Calendar.DECEMBER) {
            return year + 1;
        }

        return year;
    }

    private void setActionsForEachHalfHour(Map<CalendarDateRange, Set<Action>> actionMap, Date start, Date end,
            Action.Handler actionHandler) {
        GregorianCalendar cal = new GregorianCalendar(getTimeZone(), getLocale());
        cal.setTime(start);
        while (cal.getTime().before(end)) {
            Date s = cal.getTime();
            cal.add(java.util.Calendar.MINUTE, 30);
            Date e = cal.getTime();
            CalendarDateRange range = new CalendarDateRange(s, e, getTimeZone());
            Action[] actions = actionHandler.getActions(range, this);
            if (actions != null) {
                Set<Action> actionSet = new LinkedHashSet<Action>(Arrays.asList(actions));
                actionMap.put(range, actionSet);
            }
        }
    }

    private void setActionsForDay(Map<CalendarDateRange, Set<Action>> actionMap, Date start, Date end,
            Action.Handler actionHandler) {
        CalendarDateRange range = new CalendarDateRange(start, end, getTimeZone());
        Action[] actions = actionHandler.getActions(range, this);
        if (actions != null) {
            Set<Action> actionSet = new LinkedHashSet<Action>(Arrays.asList(actions));
            actionMap.put(range, actionSet);
        }
    }

    private List<CalendarState.Action> createActionsList(Map<CalendarDateRange, Set<Action>> actionMap) {
        if (actionMap.isEmpty()) {
            return null;
        }

        List<CalendarState.Action> calendarActions = new ArrayList<CalendarState.Action>();

        SimpleDateFormat formatter = new SimpleDateFormat(DateConstants.ACTION_DATE_FORMAT_PATTERN);
        formatter.setTimeZone(getTimeZone());

        for (Entry<CalendarDateRange, Set<Action>> entry : actionMap.entrySet()) {
            CalendarDateRange range = entry.getKey();
            Set<Action> actions = entry.getValue();
            for (Action action : actions) {
                String key = actionMapper.key(action);
                CalendarState.Action calendarAction = new CalendarState.Action();
                calendarAction.actionKey = key;
                calendarAction.caption = action.getCaption();
                setResource(key, action.getIcon());
                calendarAction.iconKey = key;
                calendarAction.startDate = formatter.format(range.getStart());
                calendarAction.endDate = formatter.format(range.getEnd());
                calendarActions.add(calendarAction);
            }
        }

        return calendarActions;
    }

    /**
     * Gets currently active time format. Value is either TimeFormat.Format12H
     * or TimeFormat.Format24H.
     *
     * @return TimeFormat Format for the time.
     */
    public TimeFormat getTimeFormat() {
        if (currentTimeFormat == null) {
            SimpleDateFormat f;
            if (getLocale() == null) {
                f = (SimpleDateFormat) SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT);
            } else {
                f = (SimpleDateFormat) SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT, getLocale());
            }
            String p = f.toPattern();
            if (p.indexOf("HH") != -1 || p.indexOf("H") != -1) {
                return TimeFormat.Format24H;
            }
            return TimeFormat.Format12H;
        }
        return currentTimeFormat;
    }

    /**
     * Example: <code>setTimeFormat(TimeFormat.Format12H);</code><br>
     * Set to null, if you want the format being defined by the locale.
     *
     * @param format
     *            Set 12h or 24h format. Default is defined by the locale.
     */
    public void setTimeFormat(TimeFormat format) {
        currentTimeFormat = format;
        markAsDirty();
    }

    /**
     * Returns a time zone that is currently used by this component.
     *
     * @return Component's Time zone
     */
    public TimeZone getTimeZone() {
        if (timezone == null) {
            return currentCalendar.getTimeZone();
        }
        return timezone;
    }

    /**
     * Set time zone that this component will use. Null value sets the default
     * time zone.
     *
     * @param zone
     *            Time zone to use
     */
    public void setTimeZone(TimeZone zone) {
        timezone = zone;
        if (!currentCalendar.getTimeZone().equals(zone)) {
            if (zone == null) {
                zone = TimeZone.getDefault();
            }
            currentCalendar.setTimeZone(zone);
            df_date_time.setTimeZone(zone);
            markAsDirty();
        }
    }

    /**
     * Get the internally used Calendar instance. This is the currently used
     * instance of {@link java.util.Calendar} but is bound to change during the
     * lifetime of the component.
     *
     * @return the currently used java calendar
     */
    public java.util.Calendar getInternalCalendar() {
        return currentCalendar;
    }

    /**
     * <p>
     * This method restricts the weekdays that are shown. This affects both the
     * monthly and the weekly view. The general contract is that <b>firstDay <
     * lastDay</b>.
     * </p>
     *
     * <p>
     * Note that this only affects the rendering process. Events are still
     * requested by the dates set by {@link #setStartDate(Date)} and
     * {@link #setEndDate(Date)}.
     * </p>
     *
     * @param firstDay
     *            the first day of the week to show, between 1 and 7
     */
    public void setFirstVisibleDayOfWeek(int firstDay) {
        if (this.firstDay != firstDay && firstDay >= 1 && firstDay <= 7 && getLastVisibleDayOfWeek() >= firstDay) {
            this.firstDay = firstDay;
            getState().firstVisibleDayOfWeek = firstDay;
        }
    }

    /**
     * Get the first visible day of the week. Returns the weekdays as integers
     * represented by {@link java.util.Calendar#DAY_OF_WEEK}
     *
     * @return An integer representing the week day according to
     *         {@link java.util.Calendar#DAY_OF_WEEK}
     */
    public int getFirstVisibleDayOfWeek() {
        return firstDay;
    }

    /**
     * <p>
     * This method restricts the weekdays that are shown. This affects both the
     * monthly and the weekly view. The general contract is that <b>firstDay <
     * lastDay</b>.
     * </p>
     *
     * <p>
     * Note that this only affects the rendering process. Events are still
     * requested by the dates set by {@link #setStartDate(Date)} and
     * {@link #setEndDate(Date)}.
     * </p>
     *
     * @param lastDay
     *            the first day of the week to show, between 1 and 7
     */
    public void setLastVisibleDayOfWeek(int lastDay) {
        if (this.lastDay != lastDay && lastDay >= 1 && lastDay <= 7 && getFirstVisibleDayOfWeek() <= lastDay) {
            this.lastDay = lastDay;
            getState().lastVisibleDayOfWeek = lastDay;
        }
    }

    /**
     * Get the last visible day of the week. Returns the weekdays as integers
     * represented by {@link java.util.Calendar#DAY_OF_WEEK}
     *
     * @return An integer representing the week day according to
     *         {@link java.util.Calendar#DAY_OF_WEEK}
     */
    public int getLastVisibleDayOfWeek() {
        return lastDay;
    }

    /**
     * <p>
     * This method restricts the hours that are shown per day. This affects the
     * weekly view. The general contract is that <b>firstHour < lastHour</b>.
     * </p>
     *
     * <p>
     * Note that this only affects the rendering process. Events are still
     * requested by the dates set by {@link #setStartDate(Date)} and
     * {@link #setEndDate(Date)}.
     * </p>
     * You can use {@link #autoScaleVisibleHoursOfDay()} for automatic scaling
     * of the visible hours based on current events.
     *
     * @param firstHour
     *            the first hour of the day to show, between 0 and 23
     * @see #autoScaleVisibleHoursOfDay()
     */
    public void setFirstVisibleHourOfDay(int firstHour) {
        if (this.firstHour != firstHour && firstHour >= 0 && firstHour <= 23
                && firstHour <= getLastVisibleHourOfDay()) {
            this.firstHour = firstHour;
            getState().firstHourOfDay = firstHour;
        }
    }

    /**
     * Returns the first visible hour in the week view. Returns the hour using a
     * 24h time format
     *
     */
    public int getFirstVisibleHourOfDay() {
        return firstHour;
    }

    /**
     * This method restricts the hours that are shown per day. This affects the
     * weekly view. The general contract is that <b>firstHour < lastHour</b>.
     * <p>
     * Note that this only affects the rendering process. Events are still
     * requested by the dates set by {@link #setStartDate(Date)} and
     * {@link #setEndDate(Date)}.
     * <p>
     * You can use {@link #autoScaleVisibleHoursOfDay()} for automatic scaling
     * of the visible hours based on current events.
     *
     * @param lastHour
     *            the first hour of the day to show, between 0 and 23
     * @see #autoScaleVisibleHoursOfDay()
     */
    public void setLastVisibleHourOfDay(int lastHour) {
        if (this.lastHour != lastHour && lastHour >= 0 && lastHour <= 23
                && lastHour >= getFirstVisibleHourOfDay()) {
            this.lastHour = lastHour;
            getState().lastHourOfDay = lastHour;
        }
    }

    /**
     * Returns the last visible hour in the week view. Returns the hour using a
     * 24h time format
     *
     */
    public int getLastVisibleHourOfDay() {
        return lastHour;
    }

    /**
     * Gets the date caption format for the weekly view.
     *
     * @return The pattern used in caption of dates in weekly view.
     */
    public String getWeeklyCaptionFormat() {
        return weeklyCaptionFormat;
    }

    /**
     * Sets custom date format for the weekly view. This is the caption of the
     * date. Format could be like "mmm MM/dd".
     *
     * @param dateFormatPattern
     *            The date caption pattern.
     */
    public void setWeeklyCaptionFormat(String dateFormatPattern) {
        if (weeklyCaptionFormat == null && dateFormatPattern != null
                || weeklyCaptionFormat != null && !weeklyCaptionFormat.equals(dateFormatPattern)) {
            weeklyCaptionFormat = dateFormatPattern;
            markAsDirty();
        }
    }

    /**
     * Sets sort order for events. By default sort order is
     * {@link EventSortOrder#DURATION_DESC}.
     *
     * @param order
     *            sort strategy for events
     */
    public void setEventSortOrder(EventSortOrder order) {
        if (order == null) {
            getState().eventSortOrder = EventSortOrder.DURATION_DESC;
        } else {
            getState().eventSortOrder = EventSortOrder.values()[order.ordinal()];
        }
    }

    /**
     * Returns sort order for events.
     *
     * @return currently active sort strategy
     */
    public EventSortOrder getEventSortOrder() {
        EventSortOrder order = getState(false).eventSortOrder;
        if (order == null) {
            return EventSortOrder.DURATION_DESC;
        } else {
            return order;
        }
    }

    private DateFormat getWeeklyCaptionFormatter() {
        if (weeklyCaptionFormat != null) {
            return new SimpleDateFormat(weeklyCaptionFormat, getLocale());
        } else {
            return SimpleDateFormat.getDateInstance(SimpleDateFormat.SHORT, getLocale());
        }
    }

    /**
     * Get the day of week by the given calendar and its locale
     *
     * @param calendar
     *            The calendar to use
     * @return
     */
    private static int getDowByLocale(java.util.Calendar calendar) {
        int fow = calendar.get(java.util.Calendar.DAY_OF_WEEK);

        // monday first
        if (calendar.getFirstDayOfWeek() == java.util.Calendar.MONDAY) {
            fow = fow == java.util.Calendar.SUNDAY ? 7 : fow - 1;
        }

        return fow;
    }

    /**
     * Is the user allowed to trigger events which alters the events.
     *
     * @return true if the client is allowed to send changes to server
     * @see #isEventClickAllowed()
     */
    protected boolean isClientChangeAllowed() {
        return !isReadOnly();
    }

    /**
     * Is the user allowed to trigger click events. Returns {@code true} by
     * default. Subclass can override this method to disallow firing event
     * clicks got from the client side.
     *
     * @return true if the client is allowed to click events
     * @see #isClientChangeAllowed()
     * @deprecated As of 7.4, override {@link #fireEventClick(Integer)} instead.
     */
    @Deprecated
    protected boolean isEventClickAllowed() {
        return true;
    }

    /**
     * Fires an event when the user selecing moving forward/backward in the
     * calendar.
     *
     * @param forward
     *            True if the calendar moved forward else backward is assumed.
     */
    protected void fireNavigationEvent(boolean forward) {
        if (forward) {
            fireEvent(new ForwardEvent(this));
        } else {
            fireEvent(new BackwardEvent(this));
        }
    }

    /**
     * Fires an event move event to all server side move listeners.
     *
     * @param index
     *            The index of the event in the events list
     * @param newFromDatetime
     *            The changed from date time
     */
    protected void fireEventMove(int index, Date newFromDatetime) {
        MoveEvent event = new MoveEvent(this, events.get(index), newFromDatetime);

        if (calendarEventProvider instanceof EventMoveHandler) {
            // Notify event provider if it is an event move handler
            ((EventMoveHandler) calendarEventProvider).eventMove(event);
        }

        // Notify event move handler attached by using the
        // setHandler(EventMoveHandler) method
        fireEvent(event);
    }

    /**
     * Fires event when a week was clicked in the calendar.
     *
     * @param week
     *            The week that was clicked
     * @param year
     *            The year of the week
     */
    protected void fireWeekClick(int week, int year) {
        fireEvent(new WeekClick(this, week, year));
    }

    /**
     * Fires event when a date was clicked in the calendar. Uses an existing
     * event from the event cache.
     *
     * @param index
     *            The index of the event in the event cache.
     */
    protected void fireEventClick(Integer index) {
        fireEvent(new EventClick(this, events.get(index)));
    }

    /**
     * Fires event when a date was clicked in the calendar. Creates a new event
     * for the date and passes it to the listener.
     *
     * @param date
     *            The date and time that was clicked
     */
    protected void fireDateClick(Date date) {
        fireEvent(new DateClickEvent(this, date));
    }

    /**
     * Fires an event range selected event. The event is fired when a user
     * highlights an area in the calendar. The highlighted areas start and end
     * dates are returned as arguments.
     *
     * @param from
     *            The start date and time of the highlighted area
     * @param to
     *            The end date and time of the highlighted area
     * @param monthlyMode
     *            Is the calendar in monthly mode
     */
    protected void fireRangeSelect(Date from, Date to, boolean monthlyMode) {
        fireEvent(new RangeSelectEvent(this, from, to, monthlyMode));
    }

    /**
     * Fires an event resize event. The event is fired when a user resizes the
     * event in the calendar causing the time range of the event to increase or
     * decrease. The new start and end times are returned as arguments to this
     * method.
     *
     * @param index
     *            The index of the event in the event cache
     * @param startTime
     *            The new start date and time of the event
     * @param endTime
     *            The new end date and time of the event
     */
    protected void fireEventResize(int index, Date startTime, Date endTime) {
        EventResize event = new EventResize(this, events.get(index), startTime, endTime);

        if (calendarEventProvider instanceof EventResizeHandler) {
            // Notify event provider if it is an event resize handler
            ((EventResizeHandler) calendarEventProvider).eventResize(event);
        }

        // Notify event resize handler attached by using the
        // setHandler(EventMoveHandler) method
        fireEvent(event);
    }

    /**
     * Localized display names for week days starting from sunday. Returned
     * array's length is always 7.
     *
     * @return Array of localized weekday names.
     */
    protected String[] getDayNamesShort() {
        DateFormatSymbols s = new DateFormatSymbols(getLocale());
        return Arrays.copyOfRange(s.getWeekdays(), 1, 8);
    }

    /**
     * Localized display names for months starting from January. Returned
     * array's length is always 12.
     *
     * @return Array of localized month names.
     */
    protected String[] getMonthNamesShort() {
        DateFormatSymbols s = new DateFormatSymbols(getLocale());
        return Arrays.copyOf(s.getShortMonths(), 12);
    }

    /**
     * Gets a date that is first day in the week that target given date belongs
     * to.
     *
     * @param date
     *            Target date
     * @return Date that is first date in same week that given date is.
     */
    protected Date getFirstDateForWeek(Date date) {
        int firstDayOfWeek = currentCalendar.getFirstDayOfWeek();
        currentCalendar.setTime(date);
        while (firstDayOfWeek != currentCalendar.get(java.util.Calendar.DAY_OF_WEEK)) {
            currentCalendar.add(java.util.Calendar.DATE, -1);
        }
        return currentCalendar.getTime();
    }

    /**
     * Gets a date that is last day in the week that target given date belongs
     * to.
     *
     * @param date
     *            Target date
     * @return Date that is last date in same week that given date is.
     */
    protected Date getLastDateForWeek(Date date) {
        currentCalendar.setTime(date);
        currentCalendar.add(java.util.Calendar.DATE, 1);
        int firstDayOfWeek = currentCalendar.getFirstDayOfWeek();
        // Roll to weeks last day using firstdayofweek. Roll until FDofW is
        // found and then roll back one day.
        while (firstDayOfWeek != currentCalendar.get(java.util.Calendar.DAY_OF_WEEK)) {
            currentCalendar.add(java.util.Calendar.DATE, 1);
        }
        currentCalendar.add(java.util.Calendar.DATE, -1);
        return currentCalendar.getTime();
    }

    /**
     * Calculates the end time of the day using the given calendar and date
     *
     * @param date
     * @param calendar
     *            the calendar instance to be used in the calculation. The given
     *            instance is unchanged in this operation.
     * @return the given date, with time set to the end of the day
     */
    private static Date getEndOfDay(java.util.Calendar calendar, Date date) {
        java.util.Calendar calendarClone = (java.util.Calendar) calendar.clone();

        calendarClone.setTime(date);
        calendarClone.set(java.util.Calendar.MILLISECOND,
                calendarClone.getActualMaximum(java.util.Calendar.MILLISECOND));
        calendarClone.set(java.util.Calendar.SECOND, calendarClone.getActualMaximum(java.util.Calendar.SECOND));
        calendarClone.set(java.util.Calendar.MINUTE, calendarClone.getActualMaximum(java.util.Calendar.MINUTE));
        calendarClone.set(java.util.Calendar.HOUR, calendarClone.getActualMaximum(java.util.Calendar.HOUR));
        calendarClone.set(java.util.Calendar.HOUR_OF_DAY,
                calendarClone.getActualMaximum(java.util.Calendar.HOUR_OF_DAY));

        return calendarClone.getTime();
    }

    /**
     * Calculates the end time of the day using the given calendar and date
     *
     * @param date
     * @param calendar
     *            the calendar instance to be used in the calculation. The given
     *            instance is unchanged in this operation.
     * @return the given date, with time set to the end of the day
     */
    private static Date getStartOfDay(java.util.Calendar calendar, Date date) {
        java.util.Calendar calendarClone = (java.util.Calendar) calendar.clone();

        calendarClone.setTime(date);
        calendarClone.set(java.util.Calendar.MILLISECOND, 0);
        calendarClone.set(java.util.Calendar.SECOND, 0);
        calendarClone.set(java.util.Calendar.MINUTE, 0);
        calendarClone.set(java.util.Calendar.HOUR, 0);
        calendarClone.set(java.util.Calendar.HOUR_OF_DAY, 0);

        return calendarClone.getTime();
    }

    /**
     * Finds the first day of the week and returns a day representing the start
     * of that day.
     *
     * @param start
     *            The actual date
     * @param expandToFullWeek
     *            Should the returned date be moved to the start of the week
     * @return If expandToFullWeek is set then it returns the first day of the
     *         week, else it returns a clone of the actual date with the time
     *         set to the start of the day
     */
    protected Date expandStartDate(Date start, boolean expandToFullWeek) {
        // If the duration is more than week, use monthly view and get startweek
        // and endweek. Example if views daterange is from tuesday to next weeks
        // wednesday->expand to monday to nextweeks sunday. If firstdayofweek =
        // monday
        if (expandToFullWeek) {
            start = getFirstDateForWeek(start);

        } else {
            start = (Date) start.clone();
        }

        // Always expand to the start of the first day to the end of the last
        // day
        start = getStartOfDay(currentCalendar, start);

        return start;
    }

    /**
     * Finds the last day of the week and returns a day representing the end of
     * that day.
     *
     * @param end
     *            The actual date
     * @param expandToFullWeek
     *            Should the returned date be moved to the end of the week
     * @return If expandToFullWeek is set then it returns the last day of the
     *         week, else it returns a clone of the actual date with the time
     *         set to the end of the day
     */
    protected Date expandEndDate(Date end, boolean expandToFullWeek) {
        // If the duration is more than week, use monthly view and get startweek
        // and endweek. Example if views daterange is from tuesday to next weeks
        // wednesday->expand to monday to nextweeks sunday. If firstdayofweek =
        // monday
        if (expandToFullWeek) {
            end = getLastDateForWeek(end);

        } else {
            end = (Date) end.clone();
        }

        // Always expand to the start of the first day to the end of the last
        // day
        end = getEndOfDay(currentCalendar, end);

        return end;
    }

    /**
     * Set the {@link CalendarEventProvider} to be used with this calendar. The
     * EventProvider is used to query for events to show, and must be non-null.
     * By default a {@link BasicEventProvider} is used.
     *
     * @param calendarEventProvider
     *            the calendarEventProvider to set. Cannot be null.
     */
    public void setEventProvider(CalendarEventProvider calendarEventProvider) {
        if (calendarEventProvider == null) {
            throw new IllegalArgumentException("Calendar event provider cannot be null");
        }

        // remove old listener
        if (getEventProvider() instanceof EventSetChangeNotifier) {
            ((EventSetChangeNotifier) getEventProvider()).removeEventSetChangeListener(this);
        }

        this.calendarEventProvider = calendarEventProvider;

        // add new listener
        if (calendarEventProvider instanceof EventSetChangeNotifier) {
            ((EventSetChangeNotifier) calendarEventProvider).addEventSetChangeListener(this);
        }
    }

    /**
     * @return the {@link CalendarEventProvider} currently used
     */
    public CalendarEventProvider getEventProvider() {
        return calendarEventProvider;
    }

    @Override
    public void eventSetChange(EventSetChangeEvent changeEvent) {
        // sanity check
        if (calendarEventProvider == changeEvent.getProvider()) {
            markAsDirty();
        }
    }

    /**
     * Set the handler for the given type information. Mirrors
     * {@link #addListener(String, Class, Object, Method) addListener} from
     * AbstractComponent
     *
     * @param eventId
     *            A unique id for the event. Usually one of
     *            {@link CalendarEventId}
     * @param eventType
     *            The class of the event, most likely a subclass of
     *            {@link CalendarComponentEvent}
     * @param listener
     *            A listener that listens to the given event
     * @param listenerMethod
     *            The method on the lister to call when the event is triggered
     */
    protected void setHandler(String eventId, Class<?> eventType, EventListener listener, Method listenerMethod) {
        if (handlers.get(eventId) != null) {
            removeListener(eventId, eventType, handlers.get(eventId));
            handlers.remove(eventId);
        }

        if (listener != null) {
            addListener(eventId, eventType, listener, listenerMethod);
            handlers.put(eventId, listener);
        }
    }

    @Override
    public void setHandler(ForwardHandler listener) {
        setHandler(ForwardEvent.EVENT_ID, ForwardEvent.class, listener, ForwardHandler.forwardMethod);
    }

    @Override
    public void setHandler(BackwardHandler listener) {
        setHandler(BackwardEvent.EVENT_ID, BackwardEvent.class, listener, BackwardHandler.backwardMethod);
    }

    @Override
    public void setHandler(DateClickHandler listener) {
        setHandler(DateClickEvent.EVENT_ID, DateClickEvent.class, listener, DateClickHandler.dateClickMethod);
    }

    @Override
    public void setHandler(EventClickHandler listener) {
        setHandler(EventClick.EVENT_ID, EventClick.class, listener, EventClickHandler.eventClickMethod);
    }

    @Override
    public void setHandler(WeekClickHandler listener) {
        setHandler(WeekClick.EVENT_ID, WeekClick.class, listener, WeekClickHandler.weekClickMethod);
    }

    @Override
    public void setHandler(EventResizeHandler listener) {
        setHandler(EventResize.EVENT_ID, EventResize.class, listener, EventResizeHandler.eventResizeMethod);
    }

    @Override
    public void setHandler(RangeSelectHandler listener) {
        setHandler(RangeSelectEvent.EVENT_ID, RangeSelectEvent.class, listener,
                RangeSelectHandler.rangeSelectMethod);

    }

    @Override
    public void setHandler(EventMoveHandler listener) {
        setHandler(MoveEvent.EVENT_ID, MoveEvent.class, listener, EventMoveHandler.eventMoveMethod);
    }

    @Override
    public EventListener getHandler(String eventId) {
        return handlers.get(eventId);
    }

    /**
     * Get the currently active drop handler.
     */
    @Override
    public DropHandler getDropHandler() {
        return dropHandler;
    }

    /**
     * Set the drop handler for the calendar See {@link DropHandler} for
     * implementation details.
     *
     * @param dropHandler
     *            The drop handler to set
     */
    public void setDropHandler(DropHandler dropHandler) {
        this.dropHandler = dropHandler;
    }

    @Override
    public TargetDetails translateDropTargetDetails(Map<String, Object> clientVariables) {
        Map<String, Object> serverVariables = new HashMap<String, Object>();

        if (clientVariables.containsKey("dropSlotIndex")) {
            int slotIndex = (Integer) clientVariables.get("dropSlotIndex");
            int dayIndex = (Integer) clientVariables.get("dropDayIndex");

            currentCalendar.setTime(getStartOfDay(currentCalendar, startDate));
            currentCalendar.add(java.util.Calendar.DATE, dayIndex);

            // change this if slot length is modified
            currentCalendar.add(java.util.Calendar.MINUTE, slotIndex * 30);

            serverVariables.put("dropTime", currentCalendar.getTime());

        } else {
            int dayIndex = (Integer) clientVariables.get("dropDayIndex");
            currentCalendar.setTime(expandStartDate(startDate, true));
            currentCalendar.add(java.util.Calendar.DATE, dayIndex);
            serverVariables.put("dropDay", currentCalendar.getTime());
        }
        serverVariables.put("mouseEvent", clientVariables.get("mouseEvent"));

        CalendarTargetDetails td = new CalendarTargetDetails(serverVariables, this);
        td.setHasDropTime(clientVariables.containsKey("dropSlotIndex"));

        return td;
    }

    /**
     * Sets a container as a data source for the events in the calendar.
     * Equivalent for doing
     * <code>Calendar.setEventProvider(new ContainerEventProvider(container))</code>
     *
     * Use this method if you are adding a container which uses the default
     * property ids like {@link BeanItemContainer} for instance. If you are
     * using custom properties instead use
     * {@link Calendar#setContainerDataSource(Container.Indexed, Object, Object, Object, Object, Object)}
     *
     * Please note that the container must be sorted by date!
     *
     * @param container
     *            The container to use as a datasource
     */
    public void setContainerDataSource(Container.Indexed container) {
        ContainerEventProvider provider = new ContainerEventProvider(container);
        provider.addEventSetChangeListener(new CalendarEventProvider.EventSetChangeListener() {
            @Override
            public void eventSetChange(EventSetChangeEvent changeEvent) {
                // Repaint if events change
                markAsDirty();
            }
        });
        provider.addEventChangeListener(new EventChangeListener() {
            @Override
            public void eventChange(EventChangeEvent changeEvent) {
                // Repaint if event changes
                markAsDirty();
            }
        });
        setEventProvider(provider);
    }

    /**
     * Sets a container as a data source for the events in the calendar.
     * Equivalent for doing
     * <code>Calendar.setEventProvider(new ContainerEventProvider(container))</code>
     *
     * Please note that the container must be sorted by date!
     *
     * @param container
     *            The container to use as a data source
     * @param captionProperty
     *            The property that has the caption, null if no caption property
     *            is present
     * @param descriptionProperty
     *            The property that has the description, null if no description
     *            property is present
     * @param startDateProperty
     *            The property that has the starting date
     * @param endDateProperty
     *            The property that has the ending date
     * @param styleNameProperty
     *            The property that has the stylename, null if no stylname
     *            property is present
     */
    public void setContainerDataSource(Container.Indexed container, Object captionProperty,
            Object descriptionProperty, Object startDateProperty, Object endDateProperty,
            Object styleNameProperty) {
        ContainerEventProvider provider = new ContainerEventProvider(container);
        provider.setCaptionProperty(captionProperty);
        provider.setDescriptionProperty(descriptionProperty);
        provider.setStartDateProperty(startDateProperty);
        provider.setEndDateProperty(endDateProperty);
        provider.setStyleNameProperty(styleNameProperty);
        provider.addEventSetChangeListener(new CalendarEventProvider.EventSetChangeListener() {
            @Override
            public void eventSetChange(EventSetChangeEvent changeEvent) {
                // Repaint if events change
                markAsDirty();
            }
        });
        provider.addEventChangeListener(new EventChangeListener() {
            @Override
            public void eventChange(EventChangeEvent changeEvent) {
                // Repaint if event changes
                markAsDirty();
            }
        });
        setEventProvider(provider);
    }

    @Override
    public List<CalendarEvent> getEvents(Date startDate, Date endDate) {
        List<CalendarEvent> events = getEventProvider().getEvents(startDate, endDate);
        cacheMinMaxTimeOfDay(events);
        return events;
    }

    @Override
    public void addEvent(CalendarEvent event) {
        if (getEventProvider() instanceof CalendarEditableEventProvider) {
            CalendarEditableEventProvider provider = (CalendarEditableEventProvider) getEventProvider();
            provider.addEvent(event);
            markAsDirty();
        } else {
            throw new UnsupportedOperationException("Event provider does not support adding events");
        }
    }

    @Override
    public void removeEvent(CalendarEvent event) {
        if (getEventProvider() instanceof CalendarEditableEventProvider) {
            CalendarEditableEventProvider provider = (CalendarEditableEventProvider) getEventProvider();
            provider.removeEvent(event);
            markAsDirty();
        } else {
            throw new UnsupportedOperationException("Event provider does not support removing events");
        }
    }

    /**
     * Adds an action handler to the calendar that handles event produced by the
     * context menu.
     *
     * <p>
     * The {@link Handler#getActions(Object, Object)} parameters depend on what
     * view the Calendar is in:
     * <ul>
     * <li>If the Calendar is in <i>Day or Week View</i> then the target
     * parameter will be a {@link CalendarDateRange} with a range of
     * half-an-hour. The {@link Handler#getActions(Object, Object)} method will
     * be called once per half-hour slot.</li>
     * <li>If the Calendar is in <i>Month View</i> then the target parameter
     * will be a {@link CalendarDateRange} with a range of one day. The
     * {@link Handler#getActions(Object, Object)} will be called once for each
     * day.
     * </ul>
     * The Dates passed into the {@link CalendarDateRange} are in the same
     * timezone as the calendar is.
     * </p>
     *
     * <p>
     * The {@link Handler#handleAction(Action, Object, Object)} parameters
     * depend on what the context menu is called upon:
     * <ul>
     * <li>If the context menu is called upon an event then the target parameter
     * is the event, i.e. instanceof {@link CalendarEvent}</li>
     * <li>If the context menu is called upon an empty slot then the target is a
     * {@link Date} representing that slot
     * </ul>
     * </p>
     */
    @Override
    public void addActionHandler(Handler actionHandler) {
        if (actionHandler != null) {
            if (actionHandlers == null) {
                actionHandlers = new LinkedList<Handler>();
                actionMapper = new KeyMapper<Action>();
            }
            if (!actionHandlers.contains(actionHandler)) {
                actionHandlers.add(actionHandler);
                markAsDirty();
            }
        }
    }

    /**
     * Is the calendar in a mode where all days of the month is shown.
     *
     * @return Returns true if calendar is in monthly mode and false if it is in
     *         weekly mode
     */
    public boolean isMonthlyMode() {
        CalendarState state = getState(false);
        if (state.days != null) {
            return state.days.size() > 7;
        } else {
            // Default mode
            return true;
        }
    }

    @Override
    public void removeActionHandler(Handler actionHandler) {
        if (actionHandlers != null && actionHandlers.contains(actionHandler)) {
            actionHandlers.remove(actionHandler);
            if (actionHandlers.isEmpty()) {
                actionHandlers = null;
                actionMapper = null;
            }
            markAsDirty();
        }
    }

    private class CalendarServerRpcImpl implements CalendarServerRpc {

        @Override
        public void eventMove(int eventIndex, String newDate) {
            if (!isClientChangeAllowed()) {
                return;
            }
            if (newDate != null) {
                try {
                    Date d = df_date_time.parse(newDate);
                    if (eventIndex >= 0 && eventIndex < events.size() && events.get(eventIndex) != null) {
                        fireEventMove(eventIndex, d);
                    }
                } catch (ParseException e) {
                    getLogger().log(Level.WARNING, e.getMessage());
                }
            }
        }

        @Override
        public void rangeSelect(String range) {
            if (!isClientChangeAllowed()) {
                return;
            }

            if (range != null && range.length() > 14 && range.contains("TO")) {
                String[] dates = range.split("TO");
                try {
                    Date d1 = df_date.parse(dates[0]);
                    Date d2 = df_date.parse(dates[1]);

                    fireRangeSelect(d1, d2, true);

                } catch (ParseException e) {
                    // NOP
                }
            } else if (range != null && range.length() > 12 && range.contains(":")) {
                String[] dates = range.split(":");
                if (dates.length == 3) {
                    try {
                        Date d = df_date.parse(dates[0]);
                        currentCalendar.setTime(d);
                        int startMinutes = Integer.parseInt(dates[1]);
                        int endMinutes = Integer.parseInt(dates[2]);
                        currentCalendar.add(java.util.Calendar.MINUTE, startMinutes);
                        Date start = currentCalendar.getTime();
                        currentCalendar.add(java.util.Calendar.MINUTE, endMinutes - startMinutes);
                        Date end = currentCalendar.getTime();
                        fireRangeSelect(start, end, false);
                    } catch (ParseException e) {
                        // NOP
                    } catch (NumberFormatException e) {
                        // NOP
                    }
                }
            }
        }

        @Override
        public void forward() {
            fireEvent(new ForwardEvent(Calendar.this));
        }

        @Override
        public void backward() {
            fireEvent(new BackwardEvent(Calendar.this));
        }

        @Override
        public void dateClick(String date) {
            if (date != null && date.length() > 6) {
                try {
                    Date d = df_date.parse(date);
                    fireDateClick(d);
                } catch (ParseException e) {
                }
            }
        }

        @Override
        public void weekClick(String event) {
            if (!event.isEmpty() && event.contains("w")) {
                String[] splitted = event.split("w");
                if (splitted.length == 2) {
                    try {
                        int yr = Integer.parseInt(splitted[0]);
                        int week = Integer.parseInt(splitted[1]);
                        fireWeekClick(week, yr);
                    } catch (NumberFormatException e) {
                        // NOP
                    }
                }
            }
        }

        @Override
        public void eventClick(int eventIndex) {
            if (!isEventClickAllowed()) {
                return;
            }
            if (eventIndex >= 0 && eventIndex < events.size() && events.get(eventIndex) != null) {
                fireEventClick(eventIndex);
            }
        }

        @Override
        public void eventResize(int eventIndex, String newStartDate, String newEndDate) {
            if (!isClientChangeAllowed()) {
                return;
            }
            if (newStartDate != null && !"".equals(newStartDate) && newEndDate != null && !"".equals(newEndDate)) {
                try {
                    Date newStartTime = df_date_time.parse(newStartDate);
                    Date newEndTime = df_date_time.parse(newEndDate);

                    fireEventResize(eventIndex, newStartTime, newEndTime);
                } catch (ParseException e) {
                    // NOOP
                }
            }
        }

        @Override
        public void scroll(int scrollPosition) {
            scrollTop = scrollPosition;
            markAsDirty();
        }

        @Override
        public void actionOnEmptyCell(String actionKey, String startDate, String endDate) {
            Action action = actionMapper.get(actionKey);
            SimpleDateFormat formatter = new SimpleDateFormat(DateConstants.ACTION_DATE_FORMAT_PATTERN);
            formatter.setTimeZone(getTimeZone());
            try {
                Date start = formatter.parse(startDate);
                for (Action.Handler ah : actionHandlers) {
                    ah.handleAction(action, Calendar.this, start);
                }

            } catch (ParseException e) {
                getLogger().log(Level.WARNING, "Could not parse action date string");
            }

        }

        @Override
        public void actionOnEvent(String actionKey, String startDate, String endDate, int eventIndex) {
            Action action = actionMapper.get(actionKey);
            SimpleDateFormat formatter = new SimpleDateFormat(DateConstants.ACTION_DATE_FORMAT_PATTERN);
            formatter.setTimeZone(getTimeZone());
            for (Action.Handler ah : actionHandlers) {
                ah.handleAction(action, Calendar.this, events.get(eventIndex));
            }
        }
    }

    @Override
    public void changeVariables(Object source, Map<String, Object> variables) {
        /*
         * Only defined to fulfill the LegacyComponent interface used for
         * calendar drag & drop. No implementation required.
         */
    }

    @Override
    public void paintContent(PaintTarget target) throws PaintException {
        if (dropHandler != null) {
            dropHandler.getAcceptCriterion().paint(target);
        }
    }

    /**
     * Sets whether the event captions are rendered as HTML.
     * <p>
     * If set to true, the captions are rendered in the browser as HTML and the
     * developer is responsible for ensuring no harmful HTML is used. If set to
     * false, the caption is rendered in the browser as plain text.
     * <p>
     * The default is false, i.e. to render that caption as plain text.
     *
     * @param eventCaptionAsHtml
     *            {@code true} if the captions are rendered as HTML,
     *            {@code false} if rendered as plain text
     */
    public void setEventCaptionAsHtml(boolean eventCaptionAsHtml) {
        getState().eventCaptionAsHtml = eventCaptionAsHtml;
    }

    /**
     * Checks whether event captions are rendered as HTML
     * <p>
     * The default is false, i.e. to render that caption as plain text.
     *
     * @return true if the captions are rendered as HTML, false if rendered as
     *         plain text
     */
    public boolean isEventCaptionAsHtml() {
        return getState(false).eventCaptionAsHtml;
    }

    @Override
    public void readDesign(Element design, DesignContext designContext) {
        super.readDesign(design, designContext);

        Attributes attr = design.attributes();
        if (design.hasAttr("time-format")) {
            setTimeFormat(TimeFormat.valueOf("Format" + design.attr("time-format").toUpperCase(Locale.ROOT)));
        }

        if (design.hasAttr("start-date")) {
            setStartDate(DesignAttributeHandler.readAttribute("start-date", attr, Date.class));
        }
        if (design.hasAttr("end-date")) {
            setEndDate(DesignAttributeHandler.readAttribute("end-date", attr, Date.class));
        }
    };

    @Override
    public void writeDesign(Element design, DesignContext designContext) {
        super.writeDesign(design, designContext);

        if (currentTimeFormat != null) {
            design.attr("time-format", currentTimeFormat == TimeFormat.Format12H ? "12h" : "24h");
        }
        if (startDate != null) {
            design.attr("start-date", df_date.format(getStartDate()));
        }
        if (endDate != null) {
            design.attr("end-date", df_date.format(getEndDate()));
        }
        if (!getTimeZone().equals(TimeZone.getDefault())) {
            design.attr("time-zone", getTimeZone().getID());
        }
    }

    @Override
    protected Collection<String> getCustomAttributes() {
        Collection<String> customAttributes = super.getCustomAttributes();
        customAttributes.add("time-format");
        customAttributes.add("start-date");
        customAttributes.add("end-date");
        return customAttributes;
    }

    /**
     * Allow setting first day of week independent of Locale. Set to null if you
     * want first day of week being defined by the locale
     *
     * @since 7.6
     * @param dayOfWeek
     *            any of java.util.Calendar.SUNDAY..java.util.Calendar.SATURDAY
     *            or null to revert to default first day of week by locale
     */
    public void setFirstDayOfWeek(Integer dayOfWeek) {
        int minimalSupported = java.util.Calendar.SUNDAY;
        int maximalSupported = java.util.Calendar.SATURDAY;
        if (dayOfWeek != null && (dayOfWeek < minimalSupported || dayOfWeek > maximalSupported)) {
            throw new IllegalArgumentException(
                    String.format("Day of week must be between %s and %s. Actually received: %s", minimalSupported,
                            maximalSupported, dayOfWeek));
        }
        customFirstDayOfWeek = dayOfWeek;
        markAsDirty();
    }
}