File: UI\WebControls\Calendar.cs
Project: ndp\fx\src\xsp\system\Web\System.Web.csproj (System.Web)
//------------------------------------------------------------------------------
// <copyright file="Calendar.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
 
namespace System.Web.UI.WebControls {
    using System.Threading;
    using System.Globalization;
    using System.ComponentModel;
    using System;
    using System.Web;
    using System.Web.UI;
    using System.Web.Util;
    using System.Collections;
    using System.ComponentModel.Design;
    using System.Drawing;
    using System.Text;
 
    using System.IO;
    using System.Reflection;
 
 
 
    /// <devdoc>
    ///    <para>Displays a one-month calendar and allows the user to
    ///       view and select a specific day, week, or month.</para>
    /// </devdoc>
    [
    ControlValueProperty("SelectedDate", typeof(DateTime), "1/1/0001"),
    DataBindingHandler("System.Web.UI.Design.WebControls.CalendarDataBindingHandler, " + AssemblyRef.SystemDesign),
    DefaultEvent("SelectionChanged"),
    DefaultProperty("SelectedDate"),
    Designer("System.Web.UI.Design.WebControls.CalendarDesigner, " + AssemblyRef.SystemDesign),
    SupportsEventValidation
    ]
    public class Calendar : WebControl, IPostBackEventHandler {
 
        private static readonly object EventDayRender = new object();
        private static readonly object EventSelectionChanged = new object();
        private static readonly object EventVisibleMonthChanged = new object();
 
        private TableItemStyle titleStyle;
        private TableItemStyle nextPrevStyle;
        private TableItemStyle dayHeaderStyle;
        private TableItemStyle selectorStyle;
        private TableItemStyle dayStyle;
        private TableItemStyle otherMonthDayStyle;
        private TableItemStyle todayDayStyle;
        private TableItemStyle selectedDayStyle;
        private TableItemStyle weekendDayStyle;
        private string defaultButtonColorText;
 
        private static readonly Color DefaultForeColor = Color.Black;
        private Color defaultForeColor;
 
        private ArrayList dateList;
        private SelectedDatesCollection selectedDates;
        private System.Globalization.Calendar threadCalendar;
        private DateTime minSupportedDate;
        private DateTime maxSupportedDate;
#if DEBUG
        private bool threadCalendarInitialized;
#endif
 
        private const string SELECT_RANGE_COMMAND = "R";
        private const string NAVIGATE_MONTH_COMMAND = "V";
 
        private static DateTime baseDate = new DateTime(2000, 1, 1);
 
        private const int STYLEMASK_DAY = 16;
        private const int STYLEMASK_UNIQUE = 15;
        private const int STYLEMASK_SELECTED = 8;
        private const int STYLEMASK_TODAY = 4;
        private const int STYLEMASK_OTHERMONTH = 2;
        private const int STYLEMASK_WEEKEND = 1;
        private const string ROWBEGINTAG = "<tr>";
        private const string ROWENDTAG = "</tr>";
 
        // Cache commonly used strings. This improves performance and memory usage.
        private const int cachedNumberMax = 31;
        private static readonly string[] cachedNumbers = new string [] {
                  "0",  "1",   "2",   "3",   "4",   "5",   "6",
                  "7",  "8",   "9",  "10",  "11",  "12",  "13",
                 "14", "15",  "16",  "17",  "18",  "19",  "20",
                 "21", "22",  "23",  "24",  "25",  "26",  "27",
                 "28", "29",  "30",  "31",
        };
 
 
        /// <devdoc>
        /// <para>Initializes a new instance of the <see cref='System.Web.UI.WebControls.Calendar'/> class.</para>
        /// </devdoc>
        public Calendar() : base(HtmlTextWriterTag.Table) {
        }
 
 
        [
        Localizable(true),
        DefaultValue(""),
        WebCategory("Accessibility"),
        WebSysDescription(SR.Calendar_Caption)
        ]
        public virtual string Caption {
            get {
                string s = (string)ViewState["Caption"];
                return (s != null) ? s : String.Empty;
            }
            set {
                ViewState["Caption"] = value;
            }
        }
 
 
        [
        DefaultValue(TableCaptionAlign.NotSet),
        WebCategory("Accessibility"),
        WebSysDescription(SR.WebControl_CaptionAlign)
        ]
        public virtual TableCaptionAlign CaptionAlign {
            get {
                object o = ViewState["CaptionAlign"];
                return (o != null) ? (TableCaptionAlign)o : TableCaptionAlign.NotSet;
            }
            set {
                if ((value < TableCaptionAlign.NotSet) ||
                    (value > TableCaptionAlign.Right)) {
                    throw new ArgumentOutOfRangeException("value");
                }
                ViewState["CaptionAlign"] = value;
            }
        }
 
 
        /// <devdoc>
        ///    <para>Gets or sets the amount of space between cells.</para>
        /// </devdoc>
        [
        WebCategory("Layout"),
        DefaultValue(2),
        WebSysDescription(SR.Calendar_CellPadding)
        ]
        public int CellPadding {
            get {
                object o = ViewState["CellPadding"];
                return((o == null) ? 2 : (int)o);
            }
            set {
                if (value < - 1 ) {
                    throw new ArgumentOutOfRangeException("value");
                }
                ViewState["CellPadding"] = value;
            }
        }
 
 
        /// <devdoc>
        ///    <para>Gets or sets the amount of space between the contents of a cell
        ///       and the cell's border.</para>
        /// </devdoc>
        [
        WebCategory("Layout"),
        DefaultValue(0),
        WebSysDescription(SR.Calendar_CellSpacing)
        ]
        public int CellSpacing {
            get {
                object o = ViewState["CellSpacing"];
                return((o == null) ?  0 : (int)o);
            }
            set {
                if (value < -1 ) {
                    throw new ArgumentOutOfRangeException("value");
                }
                ViewState["CellSpacing"] = (int)value;
            }
        }
 
 
        /// <devdoc>
        ///    <para> Gets the style property of the day-of-the-week header. This property is read-only.</para>
        /// </devdoc>
        [
        WebCategory("Styles"),
        WebSysDescription(SR.Calendar_DayHeaderStyle),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
        NotifyParentProperty(true),
        PersistenceMode(PersistenceMode.InnerProperty)
        ]
        public TableItemStyle DayHeaderStyle {
            get {
                if (dayHeaderStyle == null) {
                    dayHeaderStyle = new TableItemStyle();
                    if (IsTrackingViewState)
                        ((IStateManager)dayHeaderStyle).TrackViewState();
                }
                return dayHeaderStyle;
            }
        }
 
 
        /// <devdoc>
        ///    <para>Gets or sets
        ///       the format for the names of days.</para>
        /// </devdoc>
        [
        WebCategory("Appearance"),
        DefaultValue(DayNameFormat.Short),
        WebSysDescription(SR.Calendar_DayNameFormat)
        ]
        public DayNameFormat DayNameFormat {
            get {
                object dnf = ViewState["DayNameFormat"];
                return((dnf == null) ? DayNameFormat.Short : (DayNameFormat)dnf);
            }
            set {
                if (value < DayNameFormat.Full || value > DayNameFormat.Shortest) {
                    throw new ArgumentOutOfRangeException("value");
                }
                ViewState["DayNameFormat"] = value;
            }
        }
 
 
        /// <devdoc>
        ///    <para> Gets the style properties for the days. This property is read-only.</para>
        /// </devdoc>
        [
        WebCategory("Styles"),
        DefaultValue(null),
        WebSysDescription(SR.Calendar_DayStyle),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
        NotifyParentProperty(true),
        PersistenceMode(PersistenceMode.InnerProperty)
        ]
        public TableItemStyle DayStyle {
            get {
                if (dayStyle == null) {
                    dayStyle = new TableItemStyle();
                    if (IsTrackingViewState)
                        ((IStateManager)dayStyle).TrackViewState();
                }
                return dayStyle;
            }
        }
 
 
        /// <devdoc>
        ///    <para> Gets
        ///       or sets the day of the week to display in the calendar's first
        ///       column.</para>
        /// </devdoc>
        [
        WebCategory("Appearance"),
        DefaultValue(FirstDayOfWeek.Default),
        WebSysDescription(SR.Calendar_FirstDayOfWeek)
        ]
        public FirstDayOfWeek FirstDayOfWeek {
            get {
                object o = ViewState["FirstDayOfWeek"];
                return((o == null) ? FirstDayOfWeek.Default : (FirstDayOfWeek)o);
            }
            set {
                if (value < FirstDayOfWeek.Sunday || value > FirstDayOfWeek.Default) {
                    throw new ArgumentOutOfRangeException("value");
                }
 
                ViewState["FirstDayOfWeek"] = value;
            }
        }
 
 
        /// <devdoc>
        ///    <para>Gets or sets the text shown for the next month
        ///       navigation hyperlink if the <see cref='System.Web.UI.WebControls.Calendar.ShowNextPrevMonth'/> property is set to
        ///    <see langword='true'/>.</para>
        /// </devdoc>
        [
        Localizable(true),
        WebCategory("Appearance"),
        DefaultValue("&gt;"),
        WebSysDescription(SR.Calendar_NextMonthText)
        ]
        public string NextMonthText {
            get {
                object s = ViewState["NextMonthText"];
                return((s == null) ? "&gt;" : (String) s);
            }
            set {
                ViewState["NextMonthText"] = value;
            }
        }
 
 
        /// <devdoc>
        ///    <para>Gets or sets the format of the next and previous month hyperlinks in the
        ///       title.</para>
        /// </devdoc>
        [
        WebCategory("Appearance"),
        DefaultValue(NextPrevFormat.CustomText),
        WebSysDescription(SR.Calendar_NextPrevFormat)
        ]
        public NextPrevFormat NextPrevFormat {
            get {
                object npf = ViewState["NextPrevFormat"];
                return((npf == null) ? NextPrevFormat.CustomText : (NextPrevFormat)npf);
            }
            set {
                if (value < NextPrevFormat.CustomText || value > NextPrevFormat.FullMonth) {
                    throw new ArgumentOutOfRangeException("value");
                }
                ViewState["NextPrevFormat"] = value;
            }
        }
 
 
        /// <devdoc>
        ///    <para> Gets the style properties for the next/previous month navigators. This property is
        ///       read-only.</para>
        /// </devdoc>
        [
        WebCategory("Styles"),
        WebSysDescription(SR.Calendar_NextPrevStyle),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
        NotifyParentProperty(true),
        PersistenceMode(PersistenceMode.InnerProperty)
        ]
        public TableItemStyle NextPrevStyle {
            get {
                if (nextPrevStyle == null) {
                    nextPrevStyle = new TableItemStyle();
                    if (IsTrackingViewState)
                        ((IStateManager)nextPrevStyle).TrackViewState();
                }
                return nextPrevStyle;
            }
        }
 
 
 
        /// <devdoc>
        ///    <para>Gets the style properties for the days from the months preceding and following the current month.
        ///       This property is read-only.</para>
        /// </devdoc>
        [
        WebCategory("Styles"),
        DefaultValue(null),
        WebSysDescription(SR.Calendar_OtherMonthDayStyle),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
        NotifyParentProperty(true),
        PersistenceMode(PersistenceMode.InnerProperty)
        ]
        public TableItemStyle OtherMonthDayStyle {
            get {
                if (otherMonthDayStyle == null) {
                    otherMonthDayStyle = new TableItemStyle();
                    if (IsTrackingViewState)
                        ((IStateManager)otherMonthDayStyle).TrackViewState();
 
                }
                return otherMonthDayStyle;
            }
        }
 
 
        /// <devdoc>
        ///    <para>Gets or sets the text shown for the previous month
        ///       navigation hyperlink if the <see cref='System.Web.UI.WebControls.Calendar.ShowNextPrevMonth'/> property is set to
        ///    <see langword='true'/>
        ///    .</para>
        /// </devdoc>
        [
        Localizable(true),
        WebCategory("Appearance"),
        DefaultValue("&lt;"),
        WebSysDescription(SR.Calendar_PrevMonthText)
        ]
        public string PrevMonthText {
            get {
                object s = ViewState["PrevMonthText"];
                return((s == null) ? "&lt;" : (String) s);
            }
            set {
                ViewState["PrevMonthText"] = value;
            }
        }
 
        public override bool SupportsDisabledAttribute {
            get {
                return RenderingCompatibility < VersionUtil.Framework40;
            }
        }
 
        /// <devdoc>
        ///    <para>Gets or sets the date that is currently selected
        ///       date.</para>
        /// </devdoc>
        [
        Bindable(true, BindingDirection.TwoWay),
        DefaultValue(typeof(DateTime), "1/1/0001"),
        WebSysDescription(SR.Calendar_SelectedDate)
        ]
        public DateTime SelectedDate {
            get {
                if (SelectedDates.Count == 0) {
                    return DateTime.MinValue;
                }
                return SelectedDates[0];
            }
            set {
                if (value == DateTime.MinValue) {
                    SelectedDates.Clear();
                }
                else {
                    SelectedDates.SelectRange(value, value);
                }
            }
        }
 
 
        /// <devdoc>
        /// <para>Gets a collection of <see cref='System.DateTime' qualify='true'/> objects representing days selected on the <see cref='System.Web.UI.WebControls.Calendar'/>. This
        ///    property is read-only.</para>
        /// </devdoc>
        [
        Browsable(false),
        WebSysDescription(SR.Calendar_SelectedDates),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
        ]
        public SelectedDatesCollection SelectedDates {
            get {
                if (selectedDates == null) {
                    if (dateList == null) {
                        dateList = new ArrayList();
                    }
                    selectedDates = new SelectedDatesCollection(dateList);
                }
                return selectedDates;
            }
        }
 
 
        /// <devdoc>
        ///    <para>Gets the style properties for the selected date. This property is read-only.</para>
        /// </devdoc>
        [
        WebCategory("Styles"),
        DefaultValue(null),
        WebSysDescription(SR.Calendar_SelectedDayStyle),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
        NotifyParentProperty(true),
        PersistenceMode(PersistenceMode.InnerProperty)
        ]
        public TableItemStyle SelectedDayStyle {
            get {
                if (selectedDayStyle == null) {
                    selectedDayStyle = new TableItemStyle();
                    if (IsTrackingViewState)
                        ((IStateManager)selectedDayStyle).TrackViewState();
                }
                return selectedDayStyle;
            }
        }
 
 
        /// <devdoc>
        ///    <para>Gets or sets the date selection capabilities on the
        ///    <see cref='System.Web.UI.WebControls.Calendar'/>
        ///    to allow the user to select a day, week, or month.</para>
        /// </devdoc>
        [
        WebCategory("Behavior"),
        DefaultValue(CalendarSelectionMode.Day),
        WebSysDescription(SR.Calendar_SelectionMode)
        ]
        public CalendarSelectionMode SelectionMode {
            get {
                object csm = ViewState["SelectionMode"];
                return((csm == null) ? CalendarSelectionMode.Day : (CalendarSelectionMode)csm);
            }
            set {
                if (value < CalendarSelectionMode.None || value > CalendarSelectionMode.DayWeekMonth) {
                    throw new ArgumentOutOfRangeException("value");
                }
                ViewState["SelectionMode"] = value;
            }
        }
 
 
        /// <devdoc>
        ///    <para>Gets or sets the text shown for the month selection in
        ///       the selector column if <see cref='System.Web.UI.WebControls.Calendar.SelectionMode'/> is
        ///    <see langword='CalendarSelectionMode.DayWeekMonth'/>.</para>
        /// </devdoc>
        [
        Localizable(true),
        WebCategory("Appearance"),
        DefaultValue("&gt;&gt;"),
        WebSysDescription(SR.Calendar_SelectMonthText)
        ]
        public string SelectMonthText {
            get {
                object s = ViewState["SelectMonthText"];
                return((s == null) ? "&gt;&gt;" : (String) s);
            }
            set {
                ViewState["SelectMonthText"] = value;
            }
        }
 
 
        /// <devdoc>
        ///    <para> Gets the style properties for the week and month selectors. This property is read-only.</para>
        /// </devdoc>
        [
        WebCategory("Styles"),
        WebSysDescription(SR.Calendar_SelectorStyle),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
        NotifyParentProperty(true),
        PersistenceMode(PersistenceMode.InnerProperty)
        ]
        public TableItemStyle SelectorStyle {
            get {
                if (selectorStyle == null) {
                    selectorStyle = new TableItemStyle();
                    if (IsTrackingViewState)
                        ((IStateManager)selectorStyle).TrackViewState();
                }
                return selectorStyle;
            }
        }
 
        /// <devdoc>
        ///    <para>Gets or sets the text shown for the week selection in
        ///       the selector column if <see cref='System.Web.UI.WebControls.Calendar.SelectionMode'/> is
        ///    <see langword='CalendarSelectionMode.DayWeek '/>or
        ///    <see langword='CalendarSelectionMode.DayWeekMonth'/>.</para>
        /// </devdoc>
        [
        Localizable(true),
        WebCategory("Appearance"),
        DefaultValue("&gt;"),
        WebSysDescription(SR.Calendar_SelectWeekText)
        ]
        public string SelectWeekText {
            get {
                object s = ViewState["SelectWeekText"];
                return((s == null) ? "&gt;" : (String) s);
            }
            set {
                ViewState["SelectWeekText"] = value;
            }
        }
 
 
        /// <devdoc>
        ///    <para>Gets or sets
        ///       a value indicating whether the days of the week are displayed.</para>
        /// </devdoc>
        [
        WebCategory("Appearance"),
        DefaultValue(true),
        WebSysDescription(SR.Calendar_ShowDayHeader)
        ]
        public bool ShowDayHeader {
            get {
                object b = ViewState["ShowDayHeader"];
                return((b == null) ? true : (bool)b);
            }
            set {
                ViewState["ShowDayHeader"] = value;
            }
        }
 
 
        /// <devdoc>
        ///    <para>Gets or set
        ///       a value indicating whether days on the calendar are displayed with a border.</para>
        /// </devdoc>
        [
        WebCategory("Appearance"),
        DefaultValue(false),
        WebSysDescription(SR.Calendar_ShowGridLines)
        ]
        public bool ShowGridLines {
            get {
                object b= ViewState["ShowGridLines"];
                return((b == null) ? false : (bool)b);
            }
            set {
                ViewState["ShowGridLines"] = value;
            }
        }
 
 
        /// <devdoc>
        /// <para>Gets or sets a value indicating whether the <see cref='System.Web.UI.WebControls.Calendar'/>
        /// displays the next and pervious month
        /// hyperlinks in the title.</para>
        /// </devdoc>
        [
        WebCategory("Appearance"),
        DefaultValue(true),
        WebSysDescription(SR.Calendar_ShowNextPrevMonth)
        ]
        public bool ShowNextPrevMonth {
            get {
                object b = ViewState["ShowNextPrevMonth"];
                return((b == null) ? true : (bool)b);
            }
            set {
                ViewState["ShowNextPrevMonth"] = value;
            }
        }
 
 
        /// <devdoc>
        ///    <para> Gets or
        ///       sets a value indicating whether the title is displayed.</para>
        /// </devdoc>
        [
        WebCategory("Appearance"),
        DefaultValue(true),
        WebSysDescription(SR.Calendar_ShowTitle)
        ]
        public bool ShowTitle {
            get {
                object b = ViewState["ShowTitle"];
                return((b == null) ? true : (bool)b);
            }
            set {
                ViewState["ShowTitle"] = value;
            }
        }
 
 
        /// <devdoc>
        ///    <para>Gets or sets how the month name is formatted in the title
        ///       bar.</para>
        /// </devdoc>
        [
        WebCategory("Appearance"),
        DefaultValue(TitleFormat.MonthYear),
        WebSysDescription(SR.Calendar_TitleFormat)
        ]
        public TitleFormat TitleFormat {
            get {
                object tf = ViewState["TitleFormat"];
                return((tf == null) ? TitleFormat.MonthYear : (TitleFormat)tf);
            }
            set {
                if (value < TitleFormat.Month || value > TitleFormat.MonthYear) {
                    throw new ArgumentOutOfRangeException("value");
                }
                ViewState["TitleFormat"] = value;
            }
        }
 
 
        /// <devdoc>
        /// <para>Gets the style properties of the <see cref='System.Web.UI.WebControls.Calendar'/> title. This property is
        ///    read-only.</para>
        /// </devdoc>
        [
        WebCategory("Styles"),
        WebSysDescription(SR.Calendar_TitleStyle),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
        NotifyParentProperty(true),
        PersistenceMode(PersistenceMode.InnerProperty),
        ]
        public TableItemStyle TitleStyle {
            get {
                if (titleStyle == null) {
                    titleStyle = new TableItemStyle();
                    if (IsTrackingViewState)
                        ((IStateManager)titleStyle).TrackViewState();
                }
                return titleStyle;
            }
        }
 
 
        /// <devdoc>
        ///    <para>Gets the style properties for today's date on the
        ///    <see cref='System.Web.UI.WebControls.Calendar'/>. This
        ///       property is read-only.</para>
        /// </devdoc>
        [
        WebCategory("Styles"),
        DefaultValue(null),
        WebSysDescription(SR.Calendar_TodayDayStyle),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
        NotifyParentProperty(true),
        PersistenceMode(PersistenceMode.InnerProperty)
        ]
        public TableItemStyle TodayDayStyle {
            get {
                if (todayDayStyle == null) {
                    todayDayStyle = new TableItemStyle();
                    if (IsTrackingViewState)
                        ((IStateManager)todayDayStyle).TrackViewState();
                }
                return todayDayStyle;
            }
        }
 
 
        /// <devdoc>
        ///    <para>Gets or sets the value to use as today's date.</para>
        /// </devdoc>
        [
        Browsable(false),
        WebSysDescription(SR.Calendar_TodaysDate),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
        ]
        public DateTime TodaysDate {
            get {
                object o = ViewState["TodaysDate"];
                return((o == null) ? DateTime.Today : (DateTime)o);
            }
            set {
                ViewState["TodaysDate"] = value.Date;
            }
        }
 
 
        [
        DefaultValue(true),
        WebCategory("Accessibility"),
        WebSysDescription(SR.Table_UseAccessibleHeader)
        ]
        public virtual bool UseAccessibleHeader {
            get {
                object o = ViewState["UseAccessibleHeader"];
                return (o != null) ? (bool)o : true;
            }
            set {
                ViewState["UseAccessibleHeader"] = value;
            }
        }
 
 
        /// <devdoc>
        ///    <para>Gets or sets the date that specifies what month to display. The date can be
        ///       be any date within the month.</para>
        /// </devdoc>
        [
        Bindable(true),
        DefaultValue(typeof(DateTime), "1/1/0001"),
        WebSysDescription(SR.Calendar_VisibleDate)
        ]
        public DateTime VisibleDate {
            get {
                object o = ViewState["VisibleDate"];
                return((o == null) ? DateTime.MinValue : (DateTime)o);
            }
            set {
                ViewState["VisibleDate"] = value.Date;
            }
        }
 
 
        /// <devdoc>
        ///    <para>Gets the style properties for the displaying weekend dates. This property is
        ///       read-only.</para>
        /// </devdoc>
        [
        WebCategory("Styles"),
        WebSysDescription(SR.Calendar_WeekendDayStyle),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
        NotifyParentProperty(true),
        PersistenceMode(PersistenceMode.InnerProperty)
        ]
        public TableItemStyle WeekendDayStyle {
            get {
                if (weekendDayStyle == null) {
                    weekendDayStyle = new TableItemStyle();
                    if (IsTrackingViewState)
                        ((IStateManager)weekendDayStyle).TrackViewState();
                }
                return weekendDayStyle;
            }
        }
 
 
 
        /// <devdoc>
        /// <para>Occurs when each day is created in teh control hierarchy for the <see cref='System.Web.UI.WebControls.Calendar'/>.</para>
        /// </devdoc>
        [
        WebCategory("Action"),
        WebSysDescription(SR.Calendar_OnDayRender)
        ]
        public event DayRenderEventHandler DayRender {
            add {
                Events.AddHandler(EventDayRender, value);
            }
            remove {
                Events.RemoveHandler(EventDayRender, value);
            }
        }
 
 
 
 
        /// <devdoc>
        ///    <para>Occurs when the user clicks on a day, week, or month
        ///       selector and changes the <see cref='System.Web.UI.WebControls.Calendar.SelectedDate'/>.</para>
        /// </devdoc>
        [
        WebCategory("Action"),
        WebSysDescription(SR.Calendar_OnSelectionChanged)
        ]
        public event EventHandler SelectionChanged {
            add {
                Events.AddHandler(EventSelectionChanged, value);
            }
            remove {
                Events.RemoveHandler(EventSelectionChanged, value);
            }
        }
 
 
 
        /// <devdoc>
        ///    <para>Occurs when the
        ///       user clicks on the next or previous month <see cref='System.Web.UI.WebControls.Button'/> controls on the title.</para>
        /// </devdoc>
        [
        WebCategory("Action"),
        WebSysDescription(SR.Calendar_OnVisibleMonthChanged)
        ]
        public event MonthChangedEventHandler VisibleMonthChanged {
            add {
                Events.AddHandler(EventVisibleMonthChanged, value);
            }
            remove {
                Events.RemoveHandler(EventVisibleMonthChanged, value);
            }
        }
 
        // Methods
 
 
        /// <devdoc>
        /// </devdoc>
        private void ApplyTitleStyle(TableCell titleCell, Table titleTable, TableItemStyle titleStyle) {
            // apply affects that affect the whole background to the cell
            if (titleStyle.BackColor != Color.Empty) {
                titleCell.BackColor = titleStyle.BackColor;
            }
            if (titleStyle.BorderColor != Color.Empty) {
                titleCell.BorderColor = titleStyle.BorderColor;
            }
            if (titleStyle.BorderWidth != Unit.Empty) {
                titleCell.BorderWidth= titleStyle.BorderWidth;
            }
            if (titleStyle.BorderStyle != BorderStyle.NotSet) {
                titleCell.BorderStyle = titleStyle.BorderStyle;
            }
            if (titleStyle.Height != Unit.Empty) {
                titleCell.Height = titleStyle.Height;
            }
            if (titleStyle.VerticalAlign != VerticalAlign.NotSet) {
                titleCell.VerticalAlign = titleStyle.VerticalAlign;
            }
 
            // apply affects that affect everything else to the table
            if (titleStyle.CssClass.Length > 0) {
                titleTable.CssClass = titleStyle.CssClass;
            }
            else if (CssClass.Length > 0) {
                titleTable.CssClass = CssClass;
            }
 
            if (titleStyle.ForeColor != Color.Empty) {
                titleTable.ForeColor = titleStyle.ForeColor;
            }
            else if (ForeColor != Color.Empty) {
                titleTable.ForeColor = ForeColor;
            }
            titleTable.Font.CopyFrom(titleStyle.Font);
            titleTable.Font.MergeWith(this.Font);
 
        }
 
 
        /// <internalonly/>
        /// <devdoc>
        /// </devdoc>
        protected override ControlCollection CreateControlCollection() {
            return new InternalControlCollection(this);
        }
 
 
 
        /// <devdoc>
        /// </devdoc>
        private DateTime EffectiveVisibleDate() {
            DateTime visDate = VisibleDate;
            if (visDate.Equals(DateTime.MinValue)) {
                visDate = TodaysDate;
            }
 
            // VSWhidbey 366243
            if (IsMinSupportedYearMonth(visDate)) {
                return minSupportedDate;
            }
            else {
                return threadCalendar.AddDays(visDate, -(threadCalendar.GetDayOfMonth(visDate) - 1));
            }
        }
 
 
        /// <devdoc>
        /// </devdoc>
        private DateTime FirstCalendarDay(DateTime visibleDate) {
            DateTime firstDayOfMonth = visibleDate;
 
            // VSWhidbey 366243
            if (IsMinSupportedYearMonth(firstDayOfMonth)) {
                return firstDayOfMonth;
            }
 
            int daysFromLastMonth = ((int)threadCalendar.GetDayOfWeek(firstDayOfMonth)) - NumericFirstDayOfWeek();
            // Always display at least one day from the previous month
            if (daysFromLastMonth <= 0) {
                daysFromLastMonth += 7;
            }
            return threadCalendar.AddDays(firstDayOfMonth, -daysFromLastMonth);
        }
 
 
        /// <devdoc>
        /// </devdoc>
        private string GetCalendarButtonText(string eventArgument, string buttonText, string title, bool showLink, Color foreColor) {
            if (showLink) {
                StringBuilder sb = new StringBuilder();
                sb.Append("<a href=\"");
                sb.Append(Page.ClientScript.GetPostBackClientHyperlink(this, eventArgument, true));
 
                // ForeColor needs to go on the actual link. This breaks the uplevel/downlevel rules a little bit,
                // but it is worth doing so the day links do not change color when they go in the history on
                // downlevel browsers. Otherwise, people get it confused with the selection mechanism.
                sb.Append("\" style=\"color:");
                sb.Append(foreColor.IsEmpty ? defaultButtonColorText : ColorTranslator.ToHtml(foreColor));
 
                if (!String.IsNullOrEmpty(title)) {
                    sb.Append("\" title=\"");
                    sb.Append(title);
                }
 
                sb.Append("\">");
                sb.Append(buttonText);
                sb.Append("</a>");
                return sb.ToString();
            }
            else {
                return buttonText;
            }
        }
 
 
        /// <devdoc>
        /// </devdoc>
        private int GetDefinedStyleMask() {
 
            // Selected is always defined because it has default effects
            int styleMask = STYLEMASK_SELECTED;
 
            if (dayStyle != null && !dayStyle.IsEmpty)
                styleMask |= STYLEMASK_DAY;
            if (todayDayStyle != null && !todayDayStyle.IsEmpty)
                styleMask |= STYLEMASK_TODAY;
            if (otherMonthDayStyle != null && !otherMonthDayStyle.IsEmpty)
                styleMask |= STYLEMASK_OTHERMONTH;
            if (weekendDayStyle != null && !weekendDayStyle.IsEmpty)
                styleMask |= STYLEMASK_WEEKEND;
            return styleMask;
        }
 
 
        /// <devdoc>
        /// </devdoc>
        private string GetMonthName(int m, bool bFull) {
            if (bFull) {
                return DateTimeFormatInfo.CurrentInfo.GetMonthName(m);
            }
            else {
                return DateTimeFormatInfo.CurrentInfo.GetAbbreviatedMonthName(m);
            }
        }
 
 
        /// <devdoc>
        /// <para>Determines if a <see cref='System.Web.UI.WebControls.CalendarSelectionMode'/>
        /// contains week selectors.</para>
        /// </devdoc>
        protected bool HasWeekSelectors(CalendarSelectionMode selectionMode) {
            return(selectionMode == CalendarSelectionMode.DayWeek
                   || selectionMode == CalendarSelectionMode.DayWeekMonth);
        }
 
        private bool IsTheSameYearMonth(DateTime date1, DateTime date2) {
#if DEBUG
            Debug.Assert(threadCalendarInitialized);
#endif
            return (threadCalendar.GetEra(date1) == threadCalendar.GetEra(date2) &&
                    threadCalendar.GetYear(date1) == threadCalendar.GetYear(date2) &&
                    threadCalendar.GetMonth(date1) == threadCalendar.GetMonth(date2));
        }
 
        private bool IsMinSupportedYearMonth(DateTime date) {
#if DEBUG
            Debug.Assert(threadCalendarInitialized);
#endif
            return IsTheSameYearMonth(minSupportedDate, date);
        }
 
        private bool IsMaxSupportedYearMonth(DateTime date) {
#if DEBUG
            Debug.Assert(threadCalendarInitialized);
#endif
            return IsTheSameYearMonth(maxSupportedDate, date);
        }
 
        /// <internalonly/>
        /// <devdoc>
        /// <para>Loads a saved state of the <see cref='System.Web.UI.WebControls.Calendar'/>. </para>
        /// </devdoc>
        protected override void LoadViewState(object savedState) {
            if (savedState != null) {
                object[] myState = (object[])savedState;
 
                if (myState[0] != null)
                    base.LoadViewState(myState[0]);
                if (myState[1] != null)
                    ((IStateManager)TitleStyle).LoadViewState(myState[1]);
                if (myState[2] != null)
                    ((IStateManager)NextPrevStyle).LoadViewState(myState[2]);
                if (myState[3] != null)
                    ((IStateManager)DayStyle).LoadViewState(myState[3]);
                if (myState[4] != null)
                    ((IStateManager)DayHeaderStyle).LoadViewState(myState[4]);
                if (myState[5] != null)
                    ((IStateManager)TodayDayStyle).LoadViewState(myState[5]);
                if (myState[6] != null)
                    ((IStateManager)WeekendDayStyle).LoadViewState(myState[6]);
                if (myState[7] != null)
                    ((IStateManager)OtherMonthDayStyle).LoadViewState(myState[7]);
                if (myState[8] != null)
                    ((IStateManager)SelectedDayStyle).LoadViewState(myState[8]);
                if (myState[9] != null)
                    ((IStateManager)SelectorStyle).LoadViewState(myState[9]);
 
                ArrayList selDates = (ArrayList)ViewState["SD"];
                if (selDates != null) {
                    dateList = selDates;
                    selectedDates = null;   // reset wrapper collection
                }
 
            }
        }
 
 
        /// <internalonly/>
        /// <devdoc>
        ///    <para>Marks the starting point to begin tracking and saving changes to the
        ///       control as part of the control viewstate.</para>
        /// </devdoc>
        protected override void TrackViewState() {
            base.TrackViewState();
 
            if (titleStyle != null)
                ((IStateManager)titleStyle).TrackViewState();
            if (nextPrevStyle != null)
                ((IStateManager)nextPrevStyle).TrackViewState();
            if (dayStyle != null)
                ((IStateManager)dayStyle).TrackViewState();
            if (dayHeaderStyle != null)
                ((IStateManager)dayHeaderStyle).TrackViewState();
            if (todayDayStyle != null)
                ((IStateManager)todayDayStyle).TrackViewState();
            if (weekendDayStyle != null)
                ((IStateManager)weekendDayStyle).TrackViewState();
            if (otherMonthDayStyle != null)
                ((IStateManager)otherMonthDayStyle).TrackViewState();
            if (selectedDayStyle != null)
                ((IStateManager)selectedDayStyle).TrackViewState();
            if (selectorStyle != null)
                ((IStateManager)selectorStyle).TrackViewState();
        }
 
 
        /// <devdoc>
        /// </devdoc>
        private int NumericFirstDayOfWeek() {
            // Used globalized value by default
            return(FirstDayOfWeek == FirstDayOfWeek.Default)
            ? (int) DateTimeFormatInfo.CurrentInfo.FirstDayOfWeek
            : (int) FirstDayOfWeek;
        }
 
 
        /// <devdoc>
        /// <para>Raises the <see langword='DayRender '/>event for a <see cref='System.Web.UI.WebControls.Calendar'/>.</para>
        /// </devdoc>
        protected virtual void OnDayRender(TableCell cell, CalendarDay day) {
            DayRenderEventHandler handler = (DayRenderEventHandler)Events[EventDayRender];
            if (handler != null) {
                int absoluteDay = day.Date.Subtract(baseDate).Days;
 
                // VSWhidbey 215383: We return null for selectUrl if a control is not in
                // the page control tree.
                string selectUrl = null;
                Page page = Page;
                if (page != null) {
                    string eventArgument = absoluteDay.ToString(CultureInfo.InvariantCulture);
                    selectUrl = Page.ClientScript.GetPostBackClientHyperlink(this, eventArgument, true);
                }
                handler(this, new DayRenderEventArgs(cell, day, selectUrl));
            }
        }
 
        /// <devdoc>
        /// <para>Raises the <see langword='SelectionChanged '/>event for a <see cref='System.Web.UI.WebControls.Calendar'/>.</para>
        /// </devdoc>
        protected virtual void OnSelectionChanged() {
            EventHandler handler = (EventHandler)Events[EventSelectionChanged];
            if (handler != null) {
                handler(this, EventArgs.Empty);
            }
        }
 
 
        /// <devdoc>
        /// <para>Raises the <see langword='VisibleMonthChanged '/>event for a <see cref='System.Web.UI.WebControls.Calendar'/>.</para>
        /// </devdoc>
        protected virtual void OnVisibleMonthChanged(DateTime newDate, DateTime previousDate) {
            MonthChangedEventHandler handler = (MonthChangedEventHandler)Events[EventVisibleMonthChanged];
            if (handler != null) {
                handler(this, new MonthChangedEventArgs(newDate, previousDate));
            }
        }
 
 
        /// <internalonly/>
        /// <devdoc>
        /// <para>Raises events on post back for the <see cref='System.Web.UI.WebControls.Calendar'/> control.</para>
        /// </devdoc>
        protected virtual void RaisePostBackEvent(string eventArgument) {
 
            ValidateEvent(UniqueID, eventArgument);
 
            if (AdapterInternal != null) {
                IPostBackEventHandler pbeh = AdapterInternal as IPostBackEventHandler;
                if (pbeh != null) {
                    pbeh.RaisePostBackEvent(eventArgument);
                }
            } else {
 
                if (String.Compare(eventArgument, 0, NAVIGATE_MONTH_COMMAND, 0, NAVIGATE_MONTH_COMMAND.Length, StringComparison.Ordinal) == 0) {
                    // Month navigation. The command starts with a "V" and the remainder is day difference from the
                    // base date.
                    DateTime oldDate = VisibleDate;
                    if (oldDate.Equals(DateTime.MinValue)) {
                        oldDate = TodaysDate;
                    }
                    int newDateDiff = Int32.Parse(eventArgument.Substring(NAVIGATE_MONTH_COMMAND.Length), CultureInfo.InvariantCulture);
                    VisibleDate = baseDate.AddDays(newDateDiff);
                    if (VisibleDate == DateTime.MinValue) {
                        // MinValue would make the calendar shows today's month instead because it
                        // is the default value of VisibleDate property, so we add a day to keep
                        // showing the first supported month.
                        // We assume the first supported month has more than one day.
                        VisibleDate = DateTimeFormatInfo.CurrentInfo.Calendar.AddDays(VisibleDate, 1);
                    }
                    OnVisibleMonthChanged(VisibleDate, oldDate);
                }
                else if (String.Compare(eventArgument, 0, SELECT_RANGE_COMMAND, 0, SELECT_RANGE_COMMAND.Length, StringComparison.Ordinal) == 0) {
                    // Range selection. The command starts with an "R". The remainder is an integer. When divided by 100
                    // the result is the day difference from the base date of the first day, and the remainder is the
                    // number of days to select.
                    int rangeValue = Int32.Parse(eventArgument.Substring(SELECT_RANGE_COMMAND.Length), CultureInfo.InvariantCulture);
                    int dayDiff = rangeValue / 100;
                    int dayRange = rangeValue % 100;
                    if (dayRange < 1) {
                        dayRange = 100 + dayRange;
                        dayDiff -= 1;
                    }
                    DateTime dt = baseDate.AddDays(dayDiff);
                    SelectRange(dt, dt.AddDays(dayRange - 1));
                }
                else {
                    // Single day selection. This is just a number which is the day difference from the base date.
                    int dayDiff = Int32.Parse(eventArgument, CultureInfo.InvariantCulture);
                    DateTime dt = baseDate.AddDays(dayDiff);
                    SelectRange(dt, dt);
                }
            }
        }
 
 
        void IPostBackEventHandler.RaisePostBackEvent(string eventArgument) {
            RaisePostBackEvent(eventArgument);
        }
 
 
        /// <internalonly/>
        protected internal override void OnPreRender(EventArgs e) {
            base.OnPreRender(e);
            if (Page != null) {
                Page.RegisterPostBackScript();
            }
        }
 
 
        /// <internalonly/>
        /// <devdoc>
        /// <para>Displays the <see cref='System.Web.UI.WebControls.Calendar'/> control on the client.</para>
        /// </devdoc>
        protected internal override void Render(HtmlTextWriter writer) {
            threadCalendar = DateTimeFormatInfo.CurrentInfo.Calendar;
            minSupportedDate = threadCalendar.MinSupportedDateTime;
            maxSupportedDate = threadCalendar.MaxSupportedDateTime;
#if DEBUG
            threadCalendarInitialized = true;
#endif
            DateTime visibleDate = EffectiveVisibleDate();
            DateTime firstDay = FirstCalendarDay(visibleDate);
            CalendarSelectionMode selectionMode = SelectionMode;
 
            // Make sure we are in a form tag with runat=server.
            if (Page != null) {
                Page.VerifyRenderingInServerForm(this);
            }
 
            // We only want to display the link if we have a page, or if we are on the design surface
            // If we can stops links being active on the Autoformat dialog, then we can remove this these checks.
            Page page = Page;
            bool buttonsActive;
            if (page == null || DesignMode) {
                buttonsActive = false;
            }
            else {
                buttonsActive = IsEnabled;
            }
 
            defaultForeColor = ForeColor;
            if (defaultForeColor == Color.Empty) {
                defaultForeColor = DefaultForeColor;
            }
            defaultButtonColorText = ColorTranslator.ToHtml(defaultForeColor);
 
            Table table = new Table();
 
            if (ID != null) {
                table.ID = ClientID;
            }
            table.CopyBaseAttributes(this);
            if (ControlStyleCreated) {
                table.ApplyStyle(ControlStyle);
            }
            table.Width = Width;
            table.Height = Height;
            table.CellPadding = CellPadding;
            table.CellSpacing = CellSpacing;
 
            // default look
            if ((ControlStyleCreated == false) ||
                (ControlStyle.IsSet(System.Web.UI.WebControls.Style.PROP_BORDERWIDTH) == false) ||
                BorderWidth.Equals(Unit.Empty)) {
                table.BorderWidth = Unit.Pixel(1);
            }
 
            if (ShowGridLines) {
                table.GridLines = GridLines.Both;
            }
            else {
                table.GridLines = GridLines.None;
            }
 
            bool useAccessibleHeader = UseAccessibleHeader;
            if (useAccessibleHeader) {
                if (table.Attributes["title"] == null) {
                    table.Attributes["title"] = SR.GetString(SR.Calendar_TitleText);
                }
            }
 
            string caption = Caption;
            if (caption.Length > 0) {
                table.Caption = caption;
                table.CaptionAlign = CaptionAlign;
            }
 
            table.RenderBeginTag(writer);
 
            if (ShowTitle) {
                RenderTitle(writer, visibleDate, selectionMode, buttonsActive, useAccessibleHeader);
            }
 
            if (ShowDayHeader) {
                RenderDayHeader(writer, visibleDate, selectionMode, buttonsActive, useAccessibleHeader);
            }
 
            RenderDays(writer, firstDay, visibleDate, selectionMode, buttonsActive, useAccessibleHeader);
 
            table.RenderEndTag(writer);
        }
 
        private void RenderCalendarCell(HtmlTextWriter writer, TableItemStyle style, string text, string title, bool hasButton, string eventArgument) {
            style.AddAttributesToRender(writer, this);
            writer.RenderBeginTag(HtmlTextWriterTag.Td);
 
            if (hasButton) {
 
                // render the button
                Color foreColor = style.ForeColor;
                writer.Write("<a href=\"");
                writer.Write(Page.ClientScript.GetPostBackClientHyperlink(this, eventArgument, true));
 
                // ForeColor needs to go on the actual link. This breaks the uplevel/downlevel rules a little bit,
                // but it is worth doing so the day links do not change color when they go in the history on
                // downlevel browsers. Otherwise, people get it confused with the selection mechanism.
                writer.Write("\" style=\"color:");
                writer.Write(foreColor.IsEmpty ? defaultButtonColorText : ColorTranslator.ToHtml(foreColor));
 
                if (!String.IsNullOrEmpty(title)) {
                    writer.Write("\" title=\"");
                    writer.Write(title);
                }
 
                writer.Write("\">");
                writer.Write(text);
                writer.Write("</a>");
            }
            else {
                writer.Write(text);
            }
 
            writer.RenderEndTag();
        }
 
        private void RenderCalendarHeaderCell(HtmlTextWriter writer, TableItemStyle style, string text, string abbrText) {
            style.AddAttributesToRender(writer, this);
            writer.AddAttribute("abbr", abbrText);
            writer.AddAttribute("scope", "col");
            writer.RenderBeginTag(HtmlTextWriterTag.Th);
            writer.Write(text);
            writer.RenderEndTag();
        }
 
 
        /// <devdoc>
        /// </devdoc>
        private void RenderDayHeader(HtmlTextWriter writer, DateTime visibleDate, CalendarSelectionMode selectionMode, bool buttonsActive, bool useAccessibleHeader) {
 
            writer.Write(ROWBEGINTAG);
 
            DateTimeFormatInfo dtf = DateTimeFormatInfo.CurrentInfo;
 
            if (HasWeekSelectors(selectionMode)) {
                TableItemStyle monthSelectorStyle = new TableItemStyle();
                monthSelectorStyle.HorizontalAlign = HorizontalAlign.Center;
                // add the month selector button if required;
                if (selectionMode == CalendarSelectionMode.DayWeekMonth) {
 
                    // Range selection. The command starts with an "R". The remainder is an integer. When divided by 100
                    // the result is the day difference from the base date of the first day, and the remainder is the
                    // number of days to select.
                    int startOffset = visibleDate.Subtract(baseDate).Days;
                    int monthLength = threadCalendar.GetDaysInMonth(threadCalendar.GetYear(visibleDate), threadCalendar.GetMonth(visibleDate), threadCalendar.GetEra(visibleDate));
                    if (IsMinSupportedYearMonth(visibleDate)) {
                        // The first supported month might not start with day 1
                        // (e.g. Sept 8 is the first supported date of JapaneseCalendar)
                        monthLength = monthLength - threadCalendar.GetDayOfMonth(visibleDate) + 1;
                    }
                    else if (IsMaxSupportedYearMonth(visibleDate)) {
                        // The last supported month might not have all days supported in that calendar month
                        // (e.g. April 3 is the last supported date of HijriCalendar)
                        monthLength = threadCalendar.GetDayOfMonth(maxSupportedDate);
                    }
 
                    string monthSelectKey = SELECT_RANGE_COMMAND + ((startOffset * 100) + monthLength).ToString(CultureInfo.InvariantCulture);
                    monthSelectorStyle.CopyFrom(SelectorStyle);
 
                    string selectMonthTitle = null;
                    if (useAccessibleHeader) {
                        selectMonthTitle = SR.GetString(SR.Calendar_SelectMonthTitle);
                    }
                    RenderCalendarCell(writer, monthSelectorStyle, SelectMonthText, selectMonthTitle, buttonsActive, monthSelectKey);
                }
                else {
                    // otherwise make it look like the header row
                    monthSelectorStyle.CopyFrom(DayHeaderStyle);
                    RenderCalendarCell(writer, monthSelectorStyle, string.Empty, null, false, null);
                }
            }
 
            TableItemStyle dayNameStyle = new TableItemStyle();
            dayNameStyle.HorizontalAlign = HorizontalAlign.Center;
            dayNameStyle.CopyFrom(DayHeaderStyle);
            DayNameFormat dayNameFormat = DayNameFormat;
 
            int numericFirstDay = NumericFirstDayOfWeek();
            for (int i = numericFirstDay; i < numericFirstDay + 7; i++) {
                string dayName;
                int dayOfWeek = i % 7;
                switch (dayNameFormat) {
                    case DayNameFormat.FirstLetter:
                        dayName = dtf.GetDayName((DayOfWeek)dayOfWeek).Substring(0, 1);
                        break;
                    case DayNameFormat.FirstTwoLetters:
                        dayName = dtf.GetDayName((DayOfWeek)dayOfWeek).Substring(0, 2);
                        break;
                    case DayNameFormat.Full:
                        dayName = dtf.GetDayName((DayOfWeek)dayOfWeek);
                        break;
                    case DayNameFormat.Short:
                        dayName = dtf.GetAbbreviatedDayName((DayOfWeek)dayOfWeek);
                        break;
                    case DayNameFormat.Shortest:
                        dayName = dtf.GetShortestDayName((DayOfWeek)dayOfWeek);
                        break;
                    default:
                        Debug.Assert(false, "Unknown DayNameFormat value!");
                        goto case DayNameFormat.Short;
                }
 
                if (useAccessibleHeader) {
                    string fullDayName = dtf.GetDayName((DayOfWeek)dayOfWeek);
                    RenderCalendarHeaderCell(writer, dayNameStyle, dayName, fullDayName);
                }
                else {
                    RenderCalendarCell(writer, dayNameStyle, dayName, null, false, null);
                }
            }
            writer.Write(ROWENDTAG);
        }
 
 
        /// <devdoc>
        /// </devdoc>
        private void RenderDays(HtmlTextWriter writer, DateTime firstDay, DateTime visibleDate, CalendarSelectionMode selectionMode, bool buttonsActive, bool useAccessibleHeader) {
            // Now add the rows for the actual days
 
            DateTime d = firstDay;
            TableItemStyle weekSelectorStyle = null;
            Unit defaultWidth;
            bool hasWeekSelectors = HasWeekSelectors(selectionMode);
            if (hasWeekSelectors) {
                weekSelectorStyle = new TableItemStyle();
                weekSelectorStyle.Width = Unit.Percentage(12);
                weekSelectorStyle.HorizontalAlign = HorizontalAlign.Center;
                weekSelectorStyle.CopyFrom(SelectorStyle);
                defaultWidth = Unit.Percentage(12);
            }
            else {
                defaultWidth = Unit.Percentage(14);
            }
 
            // This determines whether we need to call DateTime.ToString for each day. The only culture/calendar
            // that requires this for now is the HebrewCalendar.
            bool usesStandardDayDigits = !(threadCalendar is HebrewCalendar);
 
            // This determines whether we can write out cells directly, or whether we have to create whole
            // TableCell objects for each day.
            bool hasRenderEvent = (this.GetType() != typeof(Calendar)
                                   || Events[EventDayRender] != null);
 
            TableItemStyle [] cellStyles = new TableItemStyle[16];
            int definedStyleMask = GetDefinedStyleMask();
            DateTime todaysDate = TodaysDate;
            string selectWeekText = SelectWeekText;
            bool daysSelectable = buttonsActive && (selectionMode != CalendarSelectionMode.None);
            int visibleDateMonth = threadCalendar.GetMonth(visibleDate);
            int absoluteDay = firstDay.Subtract(baseDate).Days;
 
            // VSWhidbey 480155: flag to indicate if forecolor needs to be set
            // explicitly in design mode to mimic runtime rendering with the
            // limitation of not supporting CSS class color setting.
            bool inDesignSelectionMode = (DesignMode && SelectionMode != CalendarSelectionMode.None);
 
            //------------------------------------------------------------------
            // VSWhidbey 366243: The following variables are for boundary cases
            // such as the current visible month is the first or the last
            // supported month.  They are used in the 'for' loops below.
 
            // For the first supported month, calculate how many days to
            // skip at the beginning of the first month.  E.g. JapaneseCalendar
            // starts at Sept 8.
            int numOfFirstDaysToSkip = 0;
            if (IsMinSupportedYearMonth(visibleDate)) {
                numOfFirstDaysToSkip = (int)threadCalendar.GetDayOfWeek(firstDay) - NumericFirstDayOfWeek();
                // If negative, it simply means the the index of the starting
                // day name is greater than the day name of the first supported
                // date.  We add back 7 to get the number of days to skip.
                if (numOfFirstDaysToSkip < 0) {
                    numOfFirstDaysToSkip += 7;
                }
            }
            Debug.Assert(numOfFirstDaysToSkip < 7);
 
            // For the last or second last supported month, initialize variables
            // to identify the last supported date of the current calendar.
            // e.g. The last supported date of HijriCalendar is April 3.  When
            // the second last monthh is shown, it can be the case that not all
            // cells will be filled up.
            bool passedLastSupportedDate = false;
            DateTime secondLastMonth = threadCalendar.AddMonths(maxSupportedDate, -1);
            bool lastOrSecondLastMonth = (IsMaxSupportedYearMonth(visibleDate) ||
                                IsTheSameYearMonth(secondLastMonth, visibleDate));
            //------------------------------------------------------------------
 
            for (int iRow = 0; iRow < 6; iRow++) {
                if (passedLastSupportedDate) {
                    break;
                }
 
                writer.Write(ROWBEGINTAG);
 
                // add week selector column and button if required
                if (hasWeekSelectors) {
                    // Range selection. The command starts with an "R". The remainder is an integer. When divided by 100
                    // the result is the day difference from the base date of the first day, and the remainder is the
                    // number of days to select.
                    int dayDiffParameter = (absoluteDay * 100) + 7;
 
                    // Adjust the dayDiff for the first or the last supported month
                    if (numOfFirstDaysToSkip > 0) {
                        dayDiffParameter -= numOfFirstDaysToSkip;
                    }
                    else if (lastOrSecondLastMonth) {
                        int daysFromLastDate = maxSupportedDate.Subtract(d).Days;
                        if (daysFromLastDate < 6) {
                            dayDiffParameter -= (6 - daysFromLastDate);
                        }
                    }
                    string weekSelectKey = SELECT_RANGE_COMMAND + dayDiffParameter.ToString(CultureInfo.InvariantCulture);
 
                    string selectWeekTitle = null;
                    if (useAccessibleHeader) {
                        int weekOfMonth = iRow + 1;
                        selectWeekTitle = SR.GetString(SR.Calendar_SelectWeekTitle, weekOfMonth.ToString(CultureInfo.InvariantCulture));
                    }
                    RenderCalendarCell(writer, weekSelectorStyle, selectWeekText, selectWeekTitle, buttonsActive, weekSelectKey);
                }
 
                for (int iDay = 0; iDay < 7; iDay++) {
 
                    // Render empty cells for special cases to handle the first
                    // or last supported month.
                    if (numOfFirstDaysToSkip > 0) {
                        iDay += numOfFirstDaysToSkip;
                        for ( ; numOfFirstDaysToSkip > 0; numOfFirstDaysToSkip--) {
                            writer.RenderBeginTag(HtmlTextWriterTag.Td);
                            writer.RenderEndTag();
                        }
                    }
                    else if (passedLastSupportedDate) {
                        for ( ; iDay < 7; iDay++) {
                            writer.RenderBeginTag(HtmlTextWriterTag.Td);
                            writer.RenderEndTag();
                        }
                        break;
                    }
 
                    int dayOfWeek = (int)threadCalendar.GetDayOfWeek(d);
                    int dayOfMonth = threadCalendar.GetDayOfMonth(d);
                    string dayNumberText;
                    if ((dayOfMonth <= cachedNumberMax) && usesStandardDayDigits) {
                        dayNumberText = cachedNumbers[dayOfMonth];
                    }
                    else {
                        dayNumberText = d.ToString("dd", CultureInfo.CurrentCulture);
                    }
 
                    CalendarDay day = new CalendarDay(d,
                                                      (dayOfWeek == 0 || dayOfWeek == 6), // IsWeekend
                                                      d.Equals(todaysDate), // IsToday
                                                      (selectedDates != null) && selectedDates.Contains(d), // IsSelected
                                                      threadCalendar.GetMonth(d) != visibleDateMonth, // IsOtherMonth
                                                      dayNumberText // Number Text
                                                      );
 
                    int styleMask = STYLEMASK_DAY;
                    if (day.IsSelected)
                        styleMask |= STYLEMASK_SELECTED;
                    if (day.IsOtherMonth)
                        styleMask |= STYLEMASK_OTHERMONTH;
                    if (day.IsToday)
                        styleMask |= STYLEMASK_TODAY;
                    if (day.IsWeekend)
                        styleMask |= STYLEMASK_WEEKEND;
                    int dayStyleMask = definedStyleMask  & styleMask;
                    // determine the unique portion of the mask for the current calendar,
                    // which will strip out the day style bit
                    int dayStyleID = dayStyleMask & STYLEMASK_UNIQUE;
 
                    TableItemStyle cellStyle = cellStyles[dayStyleID];
                    if (cellStyle == null) {
                        cellStyle = new TableItemStyle();
                        SetDayStyles(cellStyle, dayStyleMask, defaultWidth);
                        cellStyles[dayStyleID] = cellStyle;
                    }
 
 
                    string dayTitle = null;
                    if (useAccessibleHeader) {
                        dayTitle = d.ToString("m", CultureInfo.CurrentCulture);
                    }
 
                    if (hasRenderEvent) {
 
                        TableCell cdc = new TableCell();
                        cdc.ApplyStyle(cellStyle);
 
                        LiteralControl dayContent = new LiteralControl(dayNumberText);
                        cdc.Controls.Add(dayContent);
 
                        day.IsSelectable = daysSelectable;
 
                        OnDayRender(cdc, day);
 
                        // refresh the day content
                        dayContent.Text = GetCalendarButtonText(absoluteDay.ToString(CultureInfo.InvariantCulture),
                                                                dayNumberText,
                                                                dayTitle,
                                                                buttonsActive && day.IsSelectable,
                                                                cdc.ForeColor);
                        cdc.RenderControl(writer);
 
                    }
                    else {
                        // VSWhidbey 480155: In design mode we render days as
                        // texts instead of links so CSS class color setting is
                        // supported.  But this differs in runtime rendering
                        // where CSS class color setting is not supported.  To
                        // correctly mimic the forecolor of runtime rendering in
                        // design time, the default color, which is used in
                        // runtime rendering, is explicitly set in this case.
                        if (inDesignSelectionMode && cellStyle.ForeColor.IsEmpty) {
                            cellStyle.ForeColor = defaultForeColor;
                        }
 
                        RenderCalendarCell(writer, cellStyle, dayNumberText, dayTitle, daysSelectable, absoluteDay.ToString(CultureInfo.InvariantCulture));
                    }
 
                    Debug.Assert(!passedLastSupportedDate);
                    if (lastOrSecondLastMonth && d.Month == maxSupportedDate.Month && d.Day == maxSupportedDate.Day) {
                        passedLastSupportedDate = true;
                    }
                    else {
                        d = threadCalendar.AddDays(d, 1);
                        absoluteDay++;
                    }
                }
                writer.Write(ROWENDTAG);
            }
        }
 
 
        /// <devdoc>
        /// </devdoc>
        private void RenderTitle(HtmlTextWriter writer, DateTime visibleDate, CalendarSelectionMode selectionMode, bool buttonsActive, bool useAccessibleHeader) {
            writer.Write(ROWBEGINTAG);
 
            TableCell titleCell = new TableCell();
            Table titleTable = new Table();
 
            // default title table/cell styles
            titleCell.ColumnSpan = HasWeekSelectors(selectionMode) ? 8 : 7;
            titleCell.BackColor = Color.Silver;
            titleTable.GridLines = GridLines.None;
            titleTable.Width = Unit.Percentage(100);
            titleTable.CellSpacing = 0;
 
            TableItemStyle titleStyle = TitleStyle;
            ApplyTitleStyle(titleCell, titleTable, titleStyle);
 
            titleCell.RenderBeginTag(writer);
            titleTable.RenderBeginTag(writer);
            writer.Write(ROWBEGINTAG);
 
            NextPrevFormat nextPrevFormat = NextPrevFormat;
 
            TableItemStyle nextPrevStyle = new TableItemStyle();
            nextPrevStyle.Width = Unit.Percentage(15);
            nextPrevStyle.CopyFrom(NextPrevStyle);
            if (ShowNextPrevMonth) {
                if (IsMinSupportedYearMonth(visibleDate)) {
                    writer.RenderBeginTag(HtmlTextWriterTag.Td);
                    writer.RenderEndTag();
                }
                else {
                    string prevMonthText;
                    if (nextPrevFormat == NextPrevFormat.ShortMonth || nextPrevFormat == NextPrevFormat.FullMonth) {
                        int monthNo = threadCalendar.GetMonth(threadCalendar.AddMonths(visibleDate, - 1));
                        prevMonthText = GetMonthName(monthNo, (nextPrevFormat == NextPrevFormat.FullMonth));
                    }
                    else {
                        prevMonthText = PrevMonthText;
                    }
                    // Month navigation. The command starts with a "V" and the remainder is day difference from the
                    // base date.
                    DateTime prevMonthDate;
 
                    // VSWhidbey 366243: Some calendar's min supported date is
                    // not the first day of the month (e.g. JapaneseCalendar.
                    // So if we are setting the second supported month, the prev
                    // month link should always point to the first supported
                    // date instead of the first day of the previous month.
                    DateTime secondSupportedMonth = threadCalendar.AddMonths(minSupportedDate, 1);
                    if (IsTheSameYearMonth(secondSupportedMonth, visibleDate)) {
                        prevMonthDate = minSupportedDate;
                    }
                    else {
                        prevMonthDate = threadCalendar.AddMonths(visibleDate, -1);
                    }
 
                    string prevMonthKey = NAVIGATE_MONTH_COMMAND + (prevMonthDate.Subtract(baseDate)).Days.ToString(CultureInfo.InvariantCulture);
 
                    string previousMonthTitle = null;
                    if (useAccessibleHeader) {
                        previousMonthTitle = SR.GetString(SR.Calendar_PreviousMonthTitle);
                    }
                    RenderCalendarCell(writer, nextPrevStyle, prevMonthText, previousMonthTitle, buttonsActive, prevMonthKey);
                }
            }
 
 
            TableItemStyle cellMainStyle = new TableItemStyle();
 
            if (titleStyle.HorizontalAlign != HorizontalAlign.NotSet) {
                cellMainStyle.HorizontalAlign = titleStyle.HorizontalAlign;
            }
            else {
                cellMainStyle.HorizontalAlign = HorizontalAlign.Center;
            }
            cellMainStyle.Wrap = titleStyle.Wrap;
            cellMainStyle.Width = Unit.Percentage(70);
 
            string titleText;
 
            switch (TitleFormat) {
                case TitleFormat.Month:
                    titleText = visibleDate.ToString("MMMM", CultureInfo.CurrentCulture);
                    break;
                case TitleFormat.MonthYear:
                    string titlePattern = DateTimeFormatInfo.CurrentInfo.YearMonthPattern;
                    // Some cultures have a comma in their YearMonthPattern, which does not look
                    // right in a calendar. Use a fixed pattern for those.
                    if (titlePattern.IndexOf(',') >= 0) {
                        titlePattern = "MMMM yyyy";
                    }
                    titleText = visibleDate.ToString(titlePattern, CultureInfo.CurrentCulture);
                    break;
                default:
                    Debug.Assert(false, "Unknown TitleFormat value!");
                    goto case TitleFormat.MonthYear;
            }
            RenderCalendarCell(writer, cellMainStyle, titleText, null, false, null);
 
            if (ShowNextPrevMonth) {
                if (IsMaxSupportedYearMonth(visibleDate)) {
                    writer.RenderBeginTag(HtmlTextWriterTag.Td);
                    writer.RenderEndTag();
                }
                else {
                    // Style for this one is identical bar
                    nextPrevStyle.HorizontalAlign = HorizontalAlign.Right;
                    string nextMonthText;
                    if (nextPrevFormat == NextPrevFormat.ShortMonth || nextPrevFormat == NextPrevFormat.FullMonth) {
                        int monthNo = threadCalendar.GetMonth(threadCalendar.AddMonths(visibleDate, 1));
                        nextMonthText = GetMonthName(monthNo, (nextPrevFormat == NextPrevFormat.FullMonth));
                    }
                    else {
                        nextMonthText = NextMonthText;
                    }
                    // Month navigation. The command starts with a "V" and the remainder is day difference from the
                    // base date.
                    DateTime nextMonthDate = threadCalendar.AddMonths(visibleDate, 1);
                    string nextMonthKey = NAVIGATE_MONTH_COMMAND + (nextMonthDate.Subtract(baseDate)).Days.ToString(CultureInfo.InvariantCulture);
 
                    string nextMonthTitle = null;
                    if (useAccessibleHeader) {
                        nextMonthTitle = SR.GetString(SR.Calendar_NextMonthTitle);
                    }
                    RenderCalendarCell(writer, nextPrevStyle, nextMonthText, nextMonthTitle, buttonsActive, nextMonthKey);
                }
            }
            writer.Write(ROWENDTAG);
            titleTable.RenderEndTag(writer);
            titleCell.RenderEndTag(writer);
            writer.Write(ROWENDTAG);
 
        }
 
 
        /// <internalonly/>
        /// <devdoc>
        /// <para>Stores the state of the System.Web.UI.WebControls.Calender.</para>
        /// </devdoc>
        protected override object SaveViewState() {
            if (SelectedDates.Count > 0)
                ViewState["SD"] = dateList;
 
            object[] myState = new object[10];
 
            myState[0] = base.SaveViewState();
            myState[1] = (titleStyle != null) ? ((IStateManager)titleStyle).SaveViewState() : null;
            myState[2] = (nextPrevStyle != null) ? ((IStateManager)nextPrevStyle).SaveViewState() : null;
            myState[3] = (dayStyle != null) ? ((IStateManager)dayStyle).SaveViewState() : null;
            myState[4] = (dayHeaderStyle != null) ? ((IStateManager)dayHeaderStyle).SaveViewState() : null;
            myState[5] = (todayDayStyle != null) ? ((IStateManager)todayDayStyle).SaveViewState() : null;
            myState[6] = (weekendDayStyle != null) ? ((IStateManager)weekendDayStyle).SaveViewState() : null;
            myState[7] = (otherMonthDayStyle != null) ? ((IStateManager)otherMonthDayStyle).SaveViewState() : null;
            myState[8] = (selectedDayStyle != null) ? ((IStateManager)selectedDayStyle).SaveViewState() : null;
            myState[9] = (selectorStyle != null) ? ((IStateManager)selectorStyle).SaveViewState() : null;
 
            for (int i = 0; i<myState.Length; i++) {
                if (myState[i] != null)
                    return myState;
            }
 
            return null;
        }
 
        private void SelectRange(DateTime dateFrom, DateTime dateTo) {
 
            Debug.Assert(dateFrom <= dateTo, "Bad Date Range");
 
            // see if this range differs in any way from the current range
            // these checks will determine this because the colleciton is sorted
            TimeSpan ts = dateTo - dateFrom;
            if (SelectedDates.Count != ts.Days + 1
                || SelectedDates[0] != dateFrom
                || SelectedDates[SelectedDates.Count - 1] != dateTo) {
                SelectedDates.SelectRange(dateFrom, dateTo);
                OnSelectionChanged();
            }
        }
 
 
        /// <devdoc>
        /// </devdoc>
        private void SetDayStyles(TableItemStyle style, int styleMask, Unit defaultWidth) {
 
            // default day styles
            style.Width = defaultWidth;
            style.HorizontalAlign = HorizontalAlign.Center;
 
            if ((styleMask & STYLEMASK_DAY) != 0) {
                style.CopyFrom(DayStyle);
            }
            if ((styleMask & STYLEMASK_WEEKEND) != 0) {
                style.CopyFrom(WeekendDayStyle);
            }
            if ((styleMask & STYLEMASK_OTHERMONTH) != 0) {
                style.CopyFrom(OtherMonthDayStyle);
            }
            if ((styleMask & STYLEMASK_TODAY) != 0) {
                style.CopyFrom(TodayDayStyle);
            }
 
            if ((styleMask & STYLEMASK_SELECTED) != 0) {
                // default selected day style
                style.ForeColor = Color.White;
                style.BackColor = Color.Silver;
 
                style.CopyFrom(SelectedDayStyle);
            }
        }
    }
}