Search code examples
c#datetimetimeuwpdata-management

Getting the first alarm time from set of alarms


I'm currently working on an alarm clock with multiple (re-occuring) alarms.

I'm using a raspberry pi with Microsoft IoT installed and UWP (C#) for the layout and underlying algorithms.

The problem I'm running into is retrieving the next alarm time.

Pseudo code:

Select nextAlarm()
   For all alarms a
       if (((a.time >= now.time AND a.repeatDay == now.DayOfWeek) 
          OR a.repeatDay > now.DayOfWeek) AND a.dateTime < currentAlarm.dateTime)
              currentAlarm = a;

However this will take O(n) time for every alarm and the function a.repeatDay > now.DayOfWeek isn't a trivial function (if the current day is Wednesday and the next alarm is on Monday, the function doesn't work).

What I'm asking is how can I store the alarms in such a way that above function works (and preferably faster than O(n)) or how I can store the repeat days that said problem is solved.

Currently using SQLite.net-pcl package

Alarm and RepeatDay class:

public class Alarm
{
    [PrimaryKey, AutoIncrement]
    public long Id { get; set; }

    [NotNull]
    public string Name { get; set; }

    [NotNull]
    public DateTime Time { get; set; }

    [NotNull]
    public int Repeat { get; set; }

    public Alarm(string name, DateTime time, RepeatWeek repeat)
    {
        this.Name = name;
        this.Time = time;
        this.Repeat = repeat;
    }

}

public class RepeatWeek
{
    int repeat = 0;
    public static implicit operator int(RepeatWeek w)
    {
        return w.repeat;
    }

    public void setDay(DayOfWeek w)
    {
        repeat |= 1 << (int)w;
    }

    public void removeDay(DayOfWeek w)
    {
        repeat &= ~(1 << (int)w);
    }

    public DayOfWeek getNext(DayOfWeek d, bool inclToday = false)
    {
        throw new NotImplementedException();
        return DayOfWeek.Monday; //Needs work
    }
}    

Solution

  • I've tried my hand to implement GetNextDay. It then becomes a simple matter to implement Alarm.GetNext, and an even simpler LINQ query takes care of your requirement. I left some of it for you to implement so you can say you did it.

    public class Alarm
    {
        public long Id { get; set; }
    
        public string Name { get; set; }
    
        public DateTime Time { get; set; }
    
        public int Repeat { get; set; }
    
        public Alarm(string name, DateTime time, RepeatWeek repeat)
        {
            this.Name = name;
            this.Time = time;
            this.Repeat = repeat;
        }
    
        public DateTime GetNext()
        {
            var includeToday = true;
            if (DateTime.Now.TimeOfDay > Time.TimeOfDay)
            {
                includeToday = false;
            }
    
            var repeat = new RepeatWeek(Repeat);
            var nextDayOfWeek = repeat.GetNextDay(includeToday);
            return MergeDayOfWeekAndTime(nextDayOfWeek, Time);
        }
    
        private DateTime MergeDayOfWeekAndTime(DayOfWeek? nextDayOfWeek, DateTime Time)
        {
            //Left as exercise to the reader.
            throw new NotImplementedException();
        }
    }
    
    public class RepeatWeek
    {
        int Repeat;
    
        public RepeatWeek(int repeat = 0)
        {
            Repeat = repeat;
        }
    
        public static implicit operator int(RepeatWeek w)
        {
            return w.Repeat;
        }
    
        public void setDay(DayOfWeek w)
        {
            Repeat |= 1 << (int)w;
        }
    
        public void removeDay(DayOfWeek w)
        {
            Repeat &= ~(1 << (int)w);
        }
    
        public static DayOfWeek FollowingDayOfWeek(DayOfWeek day)
        {
            if (day == DayOfWeek.Saturday)
            {
                return DayOfWeek.Sunday;
            }
            else
            {
                return day + 1;
            }
        }
    
        public DayOfWeek? GetNextDay(bool inclToday = false)
        {
            var inspect = DateTime.Now.DayOfWeek;
            if (!inclToday)
            {
                inspect = FollowingDayOfWeek(inspect);
            }
    
            for (int i = 0; i < 7; i++)
            {
                if ((Repeat & (1 << (int)inspect)) > 0) return inspect;
                inspect = FollowingDayOfWeek(inspect);
            }
            return null;
        }
    }
    
    [TestClass]
    public class MyTestClass
    {
        [TestMethod]
        public void GetNextDayOfWeek()
        {
            var repeat = new RepeatWeek();
            repeat.setDay(DayOfWeek.Monday);
            repeat.setDay(DayOfWeek.Tuesday);
            var expected = DayOfWeek.Monday;
            if (DateTime.Now.DayOfWeek == DayOfWeek.Monday)
            {
                expected = DayOfWeek.Tuesday;
            }
    
            var actual = repeat.GetNextDay();
            Assert.AreEqual(expected, actual);
        }
    
        [TestMethod]
        public void GetNextAlarm()
        {
            //Populate this yourself.
            var alarms = new List<Alarm>();
            var nextAlarm = alarms.Select(a => a.GetNext()).OrderBy(a => a.Ticks).FirstOrDefault();
        }
    }