カレンダー,どうかく?

カレンダーを作ってみよう - かずきのBlog』より

前回:http://blogs.wankuma.com/kazuki/archive/2008/01/20/118336.aspx

前回、なんとなく表示されるまで作ったカレンダーだけど、デザイナ上で例外が出たとかいって表示されない。
これは、コンバータの手抜き実装が原因でif文を1ついれてあげるだけでとりあえずOK。

namespace WpfCalendar
{
    public class DateTimeConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            // 今までnullを気にしてなかった   
            if (value == null)
            {
                return new List<DateTime>();
            }
            DateTime date = (DateTime)value;
            var days = (from day in Enumerable.Range(1, DateTime.DaysInMonth(date.Year, date.Month))
                        select new DateTime(date.Year, date.Month, day)).ToList();
            var first = days.First();
            for (int i = 0; i < (int)first.DayOfWeek; i++)
            {
                days.Insert(0, days.First().AddDays(-1));
            }

            var last = days.Last();
            for (int i = 0; i < 6 - (int)last.DayOfWeek; i++)
            {
                days.Add(days.Last().AddDays(1));
            }

            return days;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

お題が面白かったので for loop を消してみる実験.
(追記) コメント追加してみた.

using System;
using System.Linq;
using System.Windows.Data;
using System.Globalization;

namespace WpfCalendar
{
    public class DateTimeConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value == null) return Enumerable.Empty<DateTime>().ToList();

            var days =
                from date in Enumerable.Repeat((DateTime)value, 1)
                // 指定された月に含まれる日付全部
                let thisMonth = from day in Enumerable.Range(1, DateTime.DaysInMonth(date.Year, date.Month))
                                select new DateTime(date.Year, date.Month, day)
                let first = thisMonth.First()
                let last = thisMonth.Last()
                // 最初の週を前の日曜日まで(後ろ向きに)補完(してReverse)
                let prev = Enumerable.Range(1, 6).Select(offset => first.AddDays(-offset).Date)
                                     .TakeWhile(day => day.DayOfWeek != DayOfWeek.Saturday)
                                     .Reverse()
                // 最後の週を次の土曜日まで補完
                let next = Enumerable.Range(1, 6).Select(offset => last.AddDays(offset).Date)
                                     .TakeWhile(day => day.DayOfWeek != DayOfWeek.Sunday)
                // 全部連結
                from day in prev.Concat(thisMonth).Concat(next)
                select day;

            return days.ToList();
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

(追記)以下も追記.
やっていることは同じだけど複数の文で書いたバージョン.

using System;
using System.Linq;
using System.Windows.Data;
using System.Globalization;

namespace WpfCalendar
{
    public class DateTimeConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value == null) return Enumerable.Empty<DateTime>().ToList();

            var date = (DateTime)value;

            // 指定された月に含まれる日付全部
            var daysInThisMonth =
                from day in Enumerable.Range(1, DateTime.DaysInMonth(date.Year, date.Month))
                select new DateTime(date.Year, date.Month, day);

            // 指定された月の最初の日
            var firstDay = daysInThisMonth.First();

            // 指定された月の最後の日
            var lastDay = daysInThisMonth.Last();

            // 最初の週を前の日曜日まで(後ろ向きに)補完(してReverse)
            var daysInPrevMonth =
                Enumerable.Range(1, 6)
                          .Select(offset => firstDay.AddDays(-offset).Date)
                          .TakeWhile(day => day.DayOfWeek != DayOfWeek.Saturday)
                          .Reverse();

            // 最後の週を次の土曜日まで補完
            var daysInNextMonth =
                Enumerable.Range(1, 6)
                          .Select(offset => lastDay.AddDays(offset).Date)
                          .TakeWhile(day => day.DayOfWeek != DayOfWeek.Sunday);

            // 全部連結
            return daysInPrevMonth.Concat(daysInThisMonth).Concat(daysInNextMonth).ToList();
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

(追記2)以下も追記.
こっちは最初からデザインし直した別解.

using System;
using System.Linq;
using System.Windows.Data;
using System.Globalization;

namespace WpfCalendar
{
    public class DateTimeConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value == null) return Enumerable.Empty<DateTime>().ToList();

            var date = (DateTime)value;

            // 指定された日を含む週の日曜日を返す関数
            Func<DateTime, DateTime> getSundayOfTheWeek = 
                theDay => theDay.AddDays(-(int)theDay.DayOfWeek).Date;

            // 指定された月に含まれる全て週について,その週の日曜日を列挙
            var sundays = ( from day in Enumerable.Range(1, DateTime.DaysInMonth(date.Year, date.Month))
                            select getSundayOfTheWeek(new DateTime(date.Year, date.Month, day))
                          ).Distinct();

            // 各日曜日から始めて一週間を作り,それを連結
            return from sunday in sundays
                   from offset in Enumerable.Range(0, 7)
                   select sunday.AddDays(offset).Date;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}