jamesstill / ScheduleWidget

ScheduleWidget handles recurring events for calendars
MIT License
42 stars 39 forks source link

Biweekly from monday to sunday (same weeknumber) #31

Closed mm98 closed 9 years ago

mm98 commented 9 years ago

Hi,

Currently ScheduleWidget is based on weekstart sunday. There is no way to defined weekstart ex. monday. Would be great to have this option for EU users =)

Its a problem when selecting every day (mon to sun), with Weekly option and repeat interval = 2. Possibly a way to define it by param or CultureInfo?

mm98 commented 9 years ago

I've never used Git before, so i'll add an snippet her. I've added a DayOfWeekComparer to the foreach loop, where the constructor takes a DayofWeekNum as FirstDayOfWeek. This will order the daysOfWeek foreach correctly based on FirstDayOfWeek.

Event.cs

public DayOfWeekEnum FirstDayOfWeek { get; set; }

WeeklyEventBuilder.cs

public class WeeklyEventBuilder : IEventFrequencyBuilder
{
    private readonly Event _event;
    public WeeklyEventBuilder(Event aEvent)
    {
        _event = aEvent;
    }

    public UnionTE Create()
    {
        var union = new UnionTE();
        var daysOfWeek = EnumExtensions.GetFlags(_event.DaysOfWeekOptions);
        var weeklyIntervals = _event.RepeatInterval;
        if (weeklyIntervals > 0 && _event.StartDateTime != null)
        {
            foreach (DayOfWeekEnum day in daysOfWeek.Cast<DayOfWeekEnum>().OrderBy(e => e, new DayOfWeekComparer(_event.FirstDayOfWeek)))
            {
                var dayOfWeek = new DayInWeekTE(day, (DateTime)_event.StartDateTime, weeklyIntervals);
                union.Add(dayOfWeek);
            }
        }
        else
        {
            foreach (DayOfWeekEnum day in daysOfWeek.Cast<DayOfWeekEnum>().OrderBy(e => e, new DayOfWeekComparer(_event.FirstDayOfWeek)))
            {
                var dayOfWeek = new DayOfWeekTE(day);
                union.Add(dayOfWeek);
            }
        }

        return union;
    }
}

/// Stackoverflow: "Sort by day of week" (by Colin Breame)
/// http://stackoverflow.com/a/22818999/319111
public class DayOfWeekComparer : IComparer<DayOfWeekEnum>
{
    public static int Rank(DayOfWeekEnum firstDayOfWeek, DayOfWeekEnum x)
    {
        return (int)x + (x < firstDayOfWeek ? 128 : 0);
    }

    public static int Compare(DayOfWeekEnum firstDayOfWeek, DayOfWeekEnum x, DayOfWeekEnum y)
    {
        return Rank(firstDayOfWeek, x).CompareTo(Rank(firstDayOfWeek, y));
    }

    DayOfWeekEnum firstDayOfWeek;
    public DayOfWeekComparer(DayOfWeekEnum firstDayOfWeek)
    {
        this.firstDayOfWeek = firstDayOfWeek;
    }

    public int Compare(DayOfWeekEnum x, DayOfWeekEnum y)
    {
        return DayOfWeekComparer.Compare(this.firstDayOfWeek, x, y);
    }
}
mm98 commented 9 years ago

DayInWeekTE also needed change, since firstDayOfWeek (Sunday) was hardcoded.

DayInWeekTE.cs (firstDayOfWeek added in constructor)

/// <summary>
/// Compares two specific days of week exactly
/// </summary>
public class DayInWeekTE : TemporalExpression
{
    private readonly DayOfWeekEnum _dayOfWeek;
    private readonly DayOfWeekEnum _firstDayOfWeek;
    private readonly DateTime _firstDateOfWeek;
    private readonly int _weeklyIntervals;
    private readonly DateTime _firstDateTime;

    /// <summary>
    /// The day of week value
    /// </summary>
    /// <param name="aDayOfWeek"></param>
    /// <param name="aFirstDayOfWeek"></param>
    /// <param name="aFirstDateTime"></param>
    /// <param name="aWeeklyInterval"></param>
    public DayInWeekTE(DayOfWeekEnum aDayOfWeek, DayOfWeekEnum aFirstDayOfWeek, DateTime aFirstDateTime, int aWeeklyInterval)
    {
        _dayOfWeek = aDayOfWeek;
        _firstDayOfWeek = aFirstDayOfWeek;
        _firstDateOfWeek = StartOfWeek(aFirstDateTime, _firstDayOfWeek.GetDayOfWeek());
        _weeklyIntervals = aWeeklyInterval;
        _firstDateTime = aFirstDateTime;
    }

    /// <summary>
    /// Returns true if the weekly interval and the day matches.
    /// </summary>
    /// <param name="aDate"></param>
    /// <returns></returns>
    public override bool Includes(DateTime aDate)
    {
        if (aDate < _firstDateTime)
        {
            return false;
        }
        return WeekMatches(aDate) && DayMatches(aDate);
    }

    /// <summary>
    /// Returns if the day matches the specified day of week.
    /// </summary>
    /// <param name="dt"></param>
    /// <returns></returns>
    protected bool DayMatches(DateTime aDate)
    {
        switch (aDate.DayOfWeek)
        {
            case DayOfWeek.Sunday:
                return ((int)_dayOfWeek == 1);

            case DayOfWeek.Monday:
                return ((int)_dayOfWeek == 2);

            case DayOfWeek.Tuesday:
                return ((int)_dayOfWeek == 4);

            case DayOfWeek.Wednesday:
                return ((int)_dayOfWeek == 8);

            case DayOfWeek.Thursday:
                return ((int)_dayOfWeek == 16);

            case DayOfWeek.Friday:
                return ((int)_dayOfWeek == 32);

            case DayOfWeek.Saturday:
                return ((int)_dayOfWeek == 64);

            default:
                return false;
        }
    }

    /// <summary>
    /// Returns true if the date falls in a week that matches the weekly interval.
    /// </summary>
    /// <param name="dt"></param>
    /// <returns></returns>
    protected bool WeekMatches(DateTime aDate)
    {
        var startOfWeek = StartOfWeek(aDate, _firstDayOfWeek.GetDayOfWeek());
        double weeks = Math.Round((startOfWeek - _firstDateOfWeek).TotalDays / 7);

        if (weeks % _weeklyIntervals == 0)
        {
            var endOfWeek = startOfWeek.AddDays(7);

            if (aDate >= startOfWeek && aDate <= endOfWeek)
            {
                return true;
            }
        }
        return false;
    }

    /// <summary>
    /// Returns the first day of the week based on the provided starting day.
    /// </summary>
    /// <param name="dt"></param>
    /// <returns></returns>
    protected DateTime StartOfWeek(DateTime dt, DayOfWeek startOfWeek)
    {
        int diff = dt.DayOfWeek - startOfWeek;
        if (diff < 0)
        {
            diff += 7;
        }

        return dt.AddDays(-1 * diff).Date;
    }
}

WeeklyEventBuilder.cs (firstDayOfWeek added to DayInWeekTE constructor)

public class WeeklyEventBuilder : IEventFrequencyBuilder
{
    private readonly Event _event;
    public WeeklyEventBuilder(Event aEvent)
    {
        _event = aEvent;
    }

    public UnionTE Create()
    {
        var union = new UnionTE();
        var daysOfWeek = EnumExtensions.GetFlags(_event.DaysOfWeekOptions);
        var weeklyIntervals = _event.RepeatInterval;
        if (weeklyIntervals > 0 && _event.StartDateTime != null)
        {
            foreach (DayOfWeekEnum day in daysOfWeek.Cast<DayOfWeekEnum>().OrderBy(e => e, new DayOfWeekEnumComparer(_event.FirstDayOfWeek)))
            {
                var dayOfWeek = new DayInWeekTE(day, _event.FirstDayOfWeek, (DateTime)_event.StartDateTime, weeklyIntervals);
                union.Add(dayOfWeek);
            }
        }
        else
        {
            foreach (DayOfWeekEnum day in daysOfWeek.Cast<DayOfWeekEnum>().OrderBy(e => e, new DayOfWeekEnumComparer(_event.FirstDayOfWeek)))
            {
                var dayOfWeek = new DayOfWeekTE(day);
                union.Add(dayOfWeek);
            }
        }

        return union;
    }
}

public class DayOfWeekEnumComparer : IComparer<DayOfWeekEnum>
{
    public static int Rank(DayOfWeekEnum firstDayOfWeek, DayOfWeekEnum x)
    {
        return (int)x + (x < firstDayOfWeek ? 128 : 0);
    }

    public static int Compare(DayOfWeekEnum firstDayOfWeek, DayOfWeekEnum x, DayOfWeekEnum y)
    {
        return Rank(firstDayOfWeek, x).CompareTo(Rank(firstDayOfWeek, y));
    }

    DayOfWeekEnum firstDayOfWeek;
    public DayOfWeekEnumComparer(DayOfWeekEnum firstDayOfWeek)
    {
        this.firstDayOfWeek = firstDayOfWeek;
    }

    public int Compare(DayOfWeekEnum x, DayOfWeekEnum y)
    {
        return DayOfWeekEnumComparer.Compare(this.firstDayOfWeek, x, y);
    }
}

EnumExtensions.cs (added DayOfWeekEnum > System.DayOfWeek extension)

internal static class EnumExtensions
{
    public const int MonthlyIntervalMaxBitField = 31;
    public const int FrequencyTypeMaxBitField = 7;
    public const int DayOfWeekMaxBitField = 127;

    public static IEnumerable<Enum> GetFlags(Enum input)
    {
        foreach (Enum value in Enum.GetValues(input.GetType()))
            if (input.HasFlag(value))
                yield return value;
    }

    public static DayOfWeek GetDayOfWeek(this DayOfWeekEnum dayOfWeek)
    {
        switch (dayOfWeek)
        {
            case DayOfWeekEnum.Sun:
                return DayOfWeek.Sunday;
            case DayOfWeekEnum.Mon:
                return DayOfWeek.Monday;
            case DayOfWeekEnum.Tue:
                return DayOfWeek.Tuesday;
            case DayOfWeekEnum.Wed:
                return DayOfWeek.Wednesday;
            case DayOfWeekEnum.Thu:
                return DayOfWeek.Thursday;
            case DayOfWeekEnum.Fri:
                return DayOfWeek.Friday;
            case DayOfWeekEnum.Sat:
                return DayOfWeek.Saturday;
        }
        return DayOfWeek.Sunday;
    }
}