Most elegant way to morph this sequence - c#

I've got the Day of the week stored in a database table (that I do not control), and I need to use it in my code.
Problem is, I want to use the System.DayOfWeek enum for representation for this, and the sequences are not the same.
In the database, it's as follows:
1 2 3 4 5 6 7
S M T W T F S
I need it as follows:
0 1 2 3 4 5 6
M T W T F S S
What's the most elegant way to do this?
for example, I could do:
i = dayOfWeek;
i = i - 2;
if (i < 0) {
i = 6;
}
but that's a bit inelegant. Any suggestions?
<EDIT>
Ahem. Apparently (.net reflector says) DayOfWeek is 0 indexed starting with Sunday.
Always read the docs before asking daft questions.
However, I'm still interested in an answer, just to satisfy my own curiosity, so go for it.
</EDIT>

The value you want is
(DayOfWeek)((dbDay + 5) % 7)
using the modulo operator %.

Wrap it in a function:
public int DbToDayOfWeek(int dbDay)
{
if (dbDay == 1)
return 6;
return dbDay -2;
}
Or:
public DayOfWeek DbToDayOfWeek(int dbDay)
{
if (dbDay == 1)
return DayOfWeek.Sunday;
return (DayOfWeek)(dbDay - 2);
}

Although I can't imagine the values changing, you should really avoid assuming that the DayOfWeek enumerated values will stay the same - so code accordingly.
static DayOfWeek[] _toDaysTable = new DayOfWeek[] {
DayOfWeek.Sunday, DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday,
DayOfWeek.Thursday, DayOfWeek.Friday, DayOfWeek.Saturday
};
static DayOfWeek ToDayOfWeek(int myDayOfWeek)
{
int index = myDayOfWeek - 1;
if (index < 0 || index >= _toDaysTable.Length)
throw new ArgumentOutOfRangeException("myDayOfWeek");
return _toDaysTable[index];
}
static int FromDayOfWeek(DayOfWeek day)
{
int index = Array.IndexOf(_toDaysTable, day);
if (index < 0)
throw new ArgumentOutOfRangeException("day");
return index + 1;
}

You could just create your own enum and map the enum directly to the value in the database
public enum DayOfWeek
{
Mon = 2,
Tue = 3,
Wed = 4,
Thu = 5,
Fri = 6,
Sat = 7,
Sun = 1
}
Then you could use an extension method of your DayOfWeek type to retrieve the value:
public static int ToInt(this DayOfWeek dow)
{
return (int)dow;
}
Unless you are relying on the DayOfWeek for actual comparisons with Dates, otherwise you will have to do the conversion between the offsets.

Related

A day and number of days ahead is given. return the day of week for number of days ahead

I am trying to write a piece of code where a day and a number of days ahead is given - for example, (Monday, 3).
This should return back "Thursday" as 3 days from Monday is Thursday.
Here is what i have done. The issue with this is, If asked for (Thursdays,5) or (Wednesday,7) or (Friday, 2) it wont be able to return anything.
As you can see I am playing around with the index of the array to get the results. I am not sure how i need to modify the code to get, for example the day 10 days from Friday.
using System;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
string Day = "Friday";
int ahead = 3;
Console.WriteLine(FindDay(Day, ahead));
}
public static string FindDay(string dayGiven, int daysAhead)
{
string[] week = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
int indexOfDayGiven = 0;
string ans = "";
foreach (string day in week)
{
if (day == dayGiven)
{
indexOfDayGiven = Array.IndexOf(week, dayGiven);
}
}
foreach(string day in week)
{
if (Array.IndexOf(week, day) ==indexOfDayGiven + daysAhead)
{
ans = day;
}
}
return ans;
}
}
}
The best way to do this is the modulo operation which is % in c#.
something like this:
var ind = (indexOfDayGiven + daysAhead) % 7;
As others have mentioned, you can use the modulus operator (which returns the remainder after division) to get a value that is within the array. So if you have Friday (5) and want to find out which day is 4 days away, we would add the two together and then take the modulus of the number of items in the array (5 + 4 = 9; 9 % 7 = 2;), or in one line: ((5 + 4) % 7) = 2, and that is the index of our result:
int result = week[(Array.IndexOf(week, "Friday") + 4) % 7];
We can also make use of the DayOfWeek enum instead of creating an array of day names. This allows us to do a case-insensitive parsing of the input day string to get the enum value. The nice thing about this is that the enum contains both the Name and the Index in one object, so we don't have to lookup indexes - we can just cast to an int, do our math, and then cast the result back to a DayOfWeek and get the string result:
public static string FindDay(string dayGiven, int daysAhead)
{
DayOfWeek startDay;
if (!Enum.TryParse(dayGiven, true, out startDay))
{
throw new ArgumentException($"'{dayGiven}' is not a valid day.");
}
return ((DayOfWeek) (((int) startDay + daysAhead) % 7)).ToString();
}
I'd make use of the DayOfWeek Enumeration like in the following example. The way to find the day stays the same as in the comments and other answers: Use the (Integer) remainder operator '%'.
using System;
using System.Globalization;
public class Program
{
public static void Main()
{
int n = 8;
for ( int startDay = 0; startDay < 7 ; startDay++)
Console.WriteLine("{0} days from {1} is {2}", n, GetDayName((DayOfWeek)startDay), GetDayName(GetDaysAhead((DayOfWeek)startDay, n)));
}
public static string GetDayName( DayOfWeek day )
{
return DateTimeFormatInfo.CurrentInfo.GetDayName(day);
}
public static DayOfWeek GetDaysAhead(DayOfWeek start, int daysAhead)
{
if ( daysAhead < 0 ) throw new ArgumentOutOfRangeException(nameof(daysAhead));
if ( daysAhead == 0 ) return start;
return (DayOfWeek) (( (int)start + daysAhead ) % 7);
}
}
DayOfWeek comes as Property of DateTime and DateTimeOffset. It isn't localized itself, but you can easily get localized strings as shown above.

Find date index in time interval

I am having a hard time to come up with a clean solution with the following problem.
I need to find an index of given date between time interval (date from/date to) based on monthly resolution.
Example:
date format = yyyy-MM-dd
timeIntervalFrom = 2016-02-01
timeIntervalTo = 2017-03-01
searchDate = 2017-01-01
Here the index would be 11.
The only thing I have come up with is brute force search, but I feel there is a cleaner way to solve this problem via some math.
var index = 0;
while (timeIntervalFrom < timeIntervalTo)
{
if (timeIntervalFrom == searchDate)
break;
index++;
timeIntervalFrom = timeIntervalFrom.AddMonths(1);
}
Any suggestion will be much appreciated!
Edit:
Here is a compilable solution which shows that using #Pikoh solution stops working correctly when time interval is wider due to months having different length, so that is not a viable solution.
using System;
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
int endYear = 2022;
var index1 = FindIndex1(new DateTime(2016, 1, 1), new DateTime(endYear, 3, 1), new DateTime(endYear, 2, 1));
var index2 = FindIndex2(new DateTime(2016, 1, 1), new DateTime(endYear, 3, 1), new DateTime(endYear, 2, 1));
Console.Out.WriteLine($"FindIndex1: {index1}, FindIndex2: {index2}, Result: {(index1 == index2 ? "OK" : "FAIL")}");
}
private static int FindIndex1(DateTime timeIntervalFrom, DateTime timeIntervalTo, DateTime searchDate)
{
var index = 0;
while (timeIntervalFrom < timeIntervalTo)
{
if (timeIntervalFrom == searchDate)
break;
index++;
timeIntervalFrom = timeIntervalFrom.AddMonths(1);
}
return index;
}
private static int FindIndex2(DateTime timeIntervalFrom, DateTime timeIntervalTo, DateTime searchDate)
{
return (searchDate - timeIntervalFrom).Days / 30;
}
}
}
Edit2:
I've managed to find the right solution reading provided link #Charles Mager gave. So in a way #Pikoh was very close. Thank you both very much!
private static int FindIndex3(DateTime timeIntervalFrom, DateTime searchDate)
{
return (int) (searchDate.Subtract(timeIntervalFrom).Days / (365.2425 / 12));
}
Try below code:
private static int FindIndex(DateTime timeIntervalFrom, DateTime timeIntervalTo, DateTime searchDate)
{
var index = -1;
if (searchDate.CompareTo(timeIntervalFrom) >= 0 && searchDate.CompareTo(timeIntervalTo) <= 0)
{
index = (searchDate.Year - timeIntervalFrom.Year) * 12
+ (searchDate.Month > timeIntervalFrom.Month ? searchDate.Month - timeIntervalFrom.Month
: searchDate.Month + 12 - timeIntervalFrom.Month);
}
return index;
}

How to properly Iterate Enum values in a cycle approach with a given counter in C#

I did a careful research about this topic but i can't find any solution.
We have a DayOfWeek enum
Sunday = 0
Monday = 1
Tuesday = 2
Wednesday = 3
Thursday = 4
Friday = 5
Saturday = 6
Now i would like to give a counter of 10 and a random starting point to Friday just an example, I would like to have this output.
Friday
Saturday
Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
As Mathias R. Jessen mentioned you can do it by % 7 operation
public static IEnumerable<DayOfWeek> GetDaySequence(DayOfWeek startDay, int count)
{
return Enumerable.Range(0, count).Select(i => (DayOfWeek)(((int)startDay + i) % 7));
}
Edit:
Usage:
var days = GetDaySequence(DayOfWeek.Friday, 10).ToList();
or
foreach (var day in GetDaySequence(DayOfWeek.Friday, 10))
{
//some logic
}
The generic method solution below allows you to write something like this:
foreach (var day in EnumRange(DayOfWeek.Sunday, 12))
{
Console.WriteLine(day);
}
It allows for non-sequential enumerations, e.g. the range is defined like:
enum NotSequential
{
Hi = 1,
Bye = 7,
AnotherValue = 12
}
Using integers will give you better performance, but will break in above case.
Note that there is no where T : enum constraint available in C#, so calling EnumRange<int>(3, 5) is accepted by the compiler, and will give a runtime exception. The solution does not count backwards, so count must be >= 0.
static IEnumerable<T> EnumRange<T>(T start, int count)
where T : struct //cannot use where Enum or similar
{
if (count < 0)
throw new ArgumentException("Count must be 0 or greater", nameof(count));
var values = Enum.GetValues(typeof (T)).Cast<T>().ToList();
var startIndex = values.IndexOf(start);
if (startIndex == -1)
throw new ArgumentException($"Value {start} not defined by enum type {typeof(T)}", nameof(start));
for (var i = startIndex; i < startIndex + count; i++)
{
yield return values[i%values.Count];
}
}

For each loop through DayOfWeek enum to start on Monday?

I am iterating through the DayOfWeek Enum like this :
foreach (DayOfWeek day in Enum.GetValues(typeof(DayOfWeek)))
{
// Add stuff to a list
}
And my problem is that I would like my enum to start by Monday instead of Sunday.
I tried to do :
CultureInfo ci = Thread.CurrentThread.CurrentCulture;
ci.DateTimeFormat.FirstDayOfWeek = DayOfWeek.Monday;
But the foreach loop still started with Sunday.
How can I do this ?
My last idea would be to reorder my resulting list to the order of day I want
but that would mean more iterations.
Thanks !
That isn't possible, purely because setting the culture doesn't change the fact that the DayOfWeek enum is defined as such:
public enum DayOfWeek {
Sunday = 0,
Monday = 1,
Tuesday = 2,
Wednesday = 3,
Thursday = 4,
Friday = 5,
Saturday = 6,
}
You can, however, skip the first entry and add it later.. perhaps like this:
foreach (DayOfWeek day in Enum.GetValues(typeof(DayOfWeek))
.OfType<DayOfWeek>()
.ToList()
.Skip(1)) {
list.Add(day.ToString());
}
list.Add(DayOfWeek.Sunday.ToString());
A single call to OrderBy can order them as desired. It's not possible to change the order of the call to Enum.GetValues.
var daysOfWeek = Enum.GetValues(typeof(DayOfWeek))
.OfType<DayOfWeek>()
.OrderBy(day => day < DayOfWeek.Monday);
Use a for loop and the modulo (%) operator :
DayOfWeek day;
for( int i = 0 ; i < 7 ; i++ )
{
day = (DayOfWeek) ((i + 1) % 7);
// Add stuff to a list
}
If you want to use this loop to shift another enum, you can always replace the number 7 with a variable initialized like this :
int length = Enum.GetValues(typeof(DayOfWeek)).Length;
You could even write a method to shift any enum, assuming that this enum has all the values between 0, 1, ..., n :
static IEnumerable<TEnum> Shift<TEnum>(int offset) where TEnum : struct
{
int length = Enum.GetValues(typeof(TEnum)).Length;
for( int i = 0 ; i < length ; i++ )
{
yield return (TEnum) (object) ((i + offset) % length);
}
}
Start with a custom IComparer<DayOfWeek>:
public class DayOfWeekComparer : IComparer<DayOfWeek> {
public int Compare(DayOfWeek x, DayOfWeek y) {
return ModifyDayOfWeek(x).CompareTo(ModifyDayOfWeek(y));
}
private static int ModifyDayOfWeek(DayOfWeek x) {
// redefine Sunday so it appears at the end of the ordering
return x == DayOfWeek.Sunday ? 7 : (int)x;
}
}
Then:
foreach(DayOfWeek day in Enum.GetValues(typeof(DayOfWeek))
.OfType<DayOfWeek>()
.OrderBy(x => x, new DayOfWeekComparer())) {
// will see Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
}
Edit to add:
Actually, even that is too complicated, as pretty as it is. Why don't you just put
static IEnumerable<DayOfWeek> DaysOfWeek {
get {
yield return DayOfWeek.Monday;
yield return DayOfWeek.Tuesday;
yield return DayOfWeek.Wednesday;
yield return DayOfWeek.Thursday;
yield return DayOfWeek.Friday;
yield return DayOfWeek.Saturday;
yield return DayOfWeek.Sunday;
}
}
somewhere, anywhere, and just be done with it! KISS!
Enum.GetValues returns elements which are sorted by the binary values of the enumeration constants (see Remarks on MSDN). DayOfWeek enumeration defined this way:
public enum DayOfWeek
{
Sunday = 0,
Monday = 1,
Tuesday = 2,
Wednesday = 3
Thursday = 4,
Friday = 5,
Saturday = 6,
}
As you can see, Monday defined as 1 and Sunday defined as 0. So, you can't force Enum.GetValues method to return Monday before Sunday.
This work for me:
foreach (DayOfWeek day in Enum.GetValues(typeof(DayOfWeek))
.OfType<DayOfWeek>()
.ToList()
.Skip(1).Union(new List<DayOfWeek> { DayOfWeek.Sunday}))
{
// Do stuff
}

Calculating the next time to run a task, based on a Day and a Time

I have a rather specific problem working out the best way to calculate the next time a "task" in my program should run, based on the configuration of that task.
Starting with the definition of some things that come through to configure this "Task". First off, an enumeration which looks much like the framework's DayOfWeek enum, which I have called DaysOfWeek and marked it up with the FlagsAttribute to indicate it can be a multiple thereof:
[Flags]
public enum DaysOfWeek
{
Sunday = 1,
Monday = 2,
Tuesday = 4,
Wednesday = 8,
Thursday = 16,
Friday = 32,
Saturday = 64
}
Secondly the class in question with the appropriate properties, and the method im trying to implement:
public class WeeklySchedule
{
public DaysOfWeek DaysToRun { get; set; }
public TimeSpan TimeToRun{ get; set; }
public override DateTime CalculateNextRunTime(DateTime lastRun)
{
// Here's what im trying to implement
}
}
The requirements should be pretty obvious
If DaysToRun is today, but TimeToRun has already gone today, return the next time/day
If today is not included in DaysToRun, then find the next day/time to run
Im obviously just having a brain-fart-monday because I cant work out an efficient way to calculate this, short of ShouldExecuteToday() method, followed by FindNextExecutionDay() and so on (maybe this is the right way.....)
Edit: Ok the weekend brain-fog is lifting, here's where Im at so far. If anyone can improve on this it would be appreciated:
First off, ive put a mapping of the two enums into a static member of my class, I know I could Parse from one to the other as per #DorCohen's example, but this makes me feel icky.
private static Dictionary<DayOfWeek, DaysOfWeek> DayToDaysMap
= new Dictionary<DayOfWeek, DaysOfWeek>()
{
{DayOfWeek.Monday, DaysOfWeek.Monday},
{DayOfWeek.Tuesday, DaysOfWeek.Tuesday},
{DayOfWeek.Wednesday, DaysOfWeek.Wednesday},
{DayOfWeek.Thursday, DaysOfWeek.Thursday},
{DayOfWeek.Friday, DaysOfWeek.Friday},
{DayOfWeek.Saturday, DaysOfWeek.Saturday},
{DayOfWeek.Sunday, DaysOfWeek.Sunday},
};
Then this method to determine if it should be run on a day:
private bool ShouldRunOn(DateTime now)
{
var days = DayToDaysMap[now.DayOfWeek];
// If the schedule is not set for the specified day, return false
if (!this.DaysToRun.HasFlag(days))
return false;
// Schedule should run on specified day, just determine if it is in the past
return this.TimeOfDay > now.TimeOfDay;
}
Then the implementation becomes; "can I run today" and if not "advance up to 6 days and see if I can run that day". Note that the parameter lastRun is not used in this implementation, it's used for others (such as a repeating schedule).
public override DateTime CalculateNextRunTime(DateTime lastRun)
{
var now = DateTime.Now;
if (ShouldRunOn(now))
return new DateTime(now.Year,now.Month,now.Day,this.TimeOfDay.Hours,
this.TimeOfDay.Minutes,this.TimeOfDay.Seconds);
for (var i = 1; i < 7; i++)
{
now = now.AddDays(1).Date;
if(ShouldRunOn(now))
return new DateTime(now.Year, now.Month, now.Day,
this.TimeOfDay.Hours, this.TimeOfDay.Minutes, this.TimeOfDay.Seconds);
}
return DateTime.MinValue;
}
Improvements welcomed!
Here's a rewrite from me:
public DateTime CalculateNextRunTime()
{
var now = DateTime.Now;
for (var i = 0; i<=7; i++)
{
var potentialRunTime = now.AddDays(i);
if (!DateInDayOfWeek(potentialRunTime))
continue;
potentialRunTime = potentialRunTime.Date + TimeToRun;
if (potentialRunTime < DateTime.Now)
continue;
return potentialRunTime;
}
return DateTime.MinValue;
}
The rough logic is:
For each day starting from today:
-Check if day is valid, if not skip to next day
-Create the runtime for the day
-Check if the runtime is in the past, if it is skip to next day else return this runtime.
The checking if it is in the past is obviously superfluous for all loops after the first but it is neater to do it for all loops and I doubt the extra comparison is likely to be a bottleneck. :)
DateInDayOfWeek in the above is just a method that returns true if the passed day matches one of the days of week held in the DaysToRun property. I couldn't use hasFlags since I wasn't using .NET 4 in writing my test code. You might want to keep it as a separate method though to avoid it getting cluttered. ;-)
in both of else statements you need to return the next day to run the task,
you can do it by simple loop.
DaysOfWeek DaysToRun = DaysOfWeek.Friday | DaysOfWeek.Monday;
TimeSpan timeToRun = new TimeSpan(12,0,0);
DateTime now = DateTime.Today;
DaysOfWeek Day = (DaysOfWeek)Enum.Parse(typeof(DaysOfWeek), now.DayOfWeek.ToString());
if (DaysToRun.HasFlag(Day))
{
if (now.TimeOfDay < timeToRun )
{
MessageBox.Show(nowTime.ToString());
}
else
{
//return next day
}
}
else
{
//return next day
}
I'm using bitwise to collect weekdays or month days.
Here are the bitwise values for weekdays:
2 Sunday
4 Monday
8 Tuesday
16 Wednesday
32 Thursday
64 Friday
128 Saturday
If you need to calculate the next run date for Monday, Thursday and Saturday then you'll need to sum 4, 32 and 128 and pass the result value as a collection day.
Example: (4 + 32 + 128) = 164
You will use the same method as a collection day for month days.
Here are the bitwise values for month days, from day 1 to 31:
2
4
8
16
32
64
128
256
512
1024
2048
4096
8192
16384
32768
65536
131072
262144
524288
1048576
2097152
4194304
8388608
16777216
33554432
67108864
134217728
268435456
536870912
1073741824
2147483648
Here is the function:
static DateTime GetNextRun(int hour, int min, bool isDaily, bool isWeekly, bool isMonthly, bool isLastDayOfMonth, int collectionDay)
{
var today = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, hour, min, 0, 0);
var tomorrow = today.AddDays(1);
if (isDaily)
{
return tomorrow;
}
else if (isWeekly)
{
if (collectionDay < 2)
{
throw new Exception("The collection Day is invalid.");
}
if (collectionDay > 255)
{
throw new Exception("The collection Day is invalid.");
}
for (int i = 1; i < 8; i++)
{
var dayOfWeek = (int)today.AddDays(i).DayOfWeek;
var power = (int)(Math.Pow(2, dayOfWeek + 1));
if ((power & collectionDay) > 0)
{
return today.AddDays(i);
}
}
}
else if (isMonthly)
{
var nextDate = tomorrow;
if (collectionDay < 2 && isLastDayOfMonth)
{
return new DateTime(tomorrow.Year, tomorrow.Month, GetDaysInMonth(tomorrow), hour, min, 0, 0);
}
if (collectionDay < 2)
{
throw new Exception("The collection Day is invalid.");
}
while (true)
{
var power = (int)(Math.Pow(2, nextDate.Day));
if ((power & collectionDay) > 0)
{
if (isLastDayOfMonth && nextDate.Month != tomorrow.Month)
{
return new DateTime(tomorrow.Year, tomorrow.Month, GetDaysInMonth(tomorrow), hour, min, 0, 0);
}
return nextDate;
}
nextDate = nextDate.AddDays(1);
}
}
return DateTime.MaxValue;
}
static int GetDaysInMonth(DateTime d)
{
for (int i = 28; i < 33; i++)
{
try
{
new DateTime(d.Year, d.Month, i, 1, 1, 0, 0);
}
catch (Exception)
{
return (i - 1);
}
}
return 31;
}
How to use:
To get the next Monday:
var d = GetNextRun(16, 13, false, true, false, false, 2);
To get the next Monday or Thursday or Saturday "(4 + 32 + 128) = 164":
var d = GetNextRun(16, 13, false, true, false, false, 164);
To get the next second days of a month:
var d = GetNextRun(16, 13, false, false, true, false, 4);
To get the next 2 or 5 or 7 days of a month "(4 + 32 + 128) = 164":
var d = GetNextRun(16, 13, false, false, true, false, 164);
Also you can set the isLastDayOfMonth flag to calculate the last day of month for the next run.

Categories