I have an enum Days that looks like this:
public enum Days
{
Sunday = 1,
Monday = 2,
Tuesday = 3,
Wednesday = 4,
Thursday = 5,
Friday = 6,
Saturday = 7,
}
and I want to have a similar enum/object for Hours that forcing the value to be an int between 0 and 23, i.e. to look something like:
public enum Hours
{
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
}
where it only has an int value and no identifier with it. Now I know enums don't work like this so I can't use an enum, but is there another way I can accomplish having an object like this?
Enums don't actually force the value to be one of the given values. For example, even if you had an Enum like this:
public enum Numbers
{
Zero = 0,
One = 1,
Two = 2
}
The following would still be considered legal syntax:
Numbers n = (Numbers)3;
Instead, what you should do is create a property for your Hours field and validate the input so that it will throw an exception if the given value isn't within the allowed range:
private int _hours;
public int Hours
{
get { return _hours; }
set
{
if (value < 0 || value > 23)
throw new ArgumentOutOfRangeException(nameof(Hours), "The value must be between 0 and 23");
_hours = value;
}
}
That being said, what you are working with is a day and an hour (and presumably minutes or seconds or months), so a DateTime object already has all the functionality you would need.
private DateTime _dt;
public int Days
{
get { return _dt.Day; }
set { _dt = new DateTime(_dt.Year, _dt.Month, value, _dt.Hour, _dt.Minute, _dt.Second, _dt.Millisecond); }
}
public int Hours
{
get { return _dt.Hour; }
set { _dt = new DateTime(_dt.Year, _dt.Month, _dt.Day, value, _dt.Minute, _dt.Second, _dt.Millisecond); }
}
I would check it in the setter:
private int hour;
public int Hour
{
get { return hour; }
set
{
if (value < 0 || value > 23)
throw new ArgumentOutOfRangeException();
hour = value;
}
}
If you really don't want to use datetime, then probably your next best thing is to use a struct with some implicit conversions
public struct Hour
{
private int val;
public Hour(int val)
{
validate(val);
this.val = val;
}
private static void validate(int hour)
{
if (hour < 0 || hour > 23)
throw new Exception("it broke");
}
public static implicit operator int(Hour h)
{
return h.val;
}
public static implicit operator Hour(int d)
{
return new Hour(d);
}
}
The implicit operators allow you to treat the object like an int most of the time and the validate function ensures that you always have a valid value.
So you should be able to do stuff like Hour h = 23; and int time = h; but not Hour h = 30;
You could make either an enum with the word for each number or an array with length 24 where the value at each index is equal to the index.
Ex:
enum
{
Zero = 0,
One,
Two
}
var days = int[24] { 0, 1, 2, ..., 22, 23 };
days[0]; // == 0
Related
I wanted to return the remaining time until the next interval (KlineInterval). Basically, I want to avoid hard-coding stuff. My code works fine for 1-hour interval but it doesn't support the rest of the intervals. I want it to support all of them and if there is a way to do that in a not hard-coded way (those ugly ifs).
Is this possible?
public enum KlineInterval
{
OneMinute = 0,
ThreeMinutes = 1,
FiveMinutes = 2,
FifteenMinutes = 3,
ThirtyMinutes = 4,
OneHour = 5,
TwoHour = 6,
FourHour = 7,
SixHour = 8,
EightHour = 9,
TwelveHour = 10,
OneDay = 11,
ThreeDay = 12,
OneWeek = 13,
OneMonth = 14
}
public static double RemainingSecondsUntilNextInterval(KlineInterval interval)
{
if (interval == KlineInterval.FiveMinutes)
{
double currentTimeUnixTimestamp = DateTimeToUnixTimestamp(DateTime.Now);
int minutesInSeconds = 5 * 60;
return minutesInSeconds - (currentTimeUnixTimestamp % minutesInSeconds);
}
else if (interval == KlineInterval.OneHour)
{
var timeOfDay = DateTime.Now.TimeOfDay;
var nextFullHour = TimeSpan.FromHours(Math.Ceiling(timeOfDay.TotalHours));
return (nextFullHour - timeOfDay).TotalSeconds;
}
else
{
throw new NotSupportedException("Interval not supported.");
}
}
Edit:
using System;
class Program
{
private static double DateTimeToUnixTimestamp(DateTime dateTime)
{
return (TimeZoneInfo.ConvertTimeToUtc(dateTime) -
new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds;
}
private static double RemainingSecondsUntilXMinutes(int minutes)
{
double currentTimeUnixTimestamp = DateTimeToUnixTimestamp(DateTime.Now);
return (minutes * 60) - (currentTimeUnixTimestamp % (minutes * 60));
}
static void Main(string[] args)
{
// Remaining seconds until the next hour
var nextHour = RemainingSecondsUntilXMinutes(60);
// Remaining seconds until the next 5 minutes
var nextFiveMinutes = RemainingSecondsUntilXMinutes(5);
Console.ReadKey();
}
}
#Max, is that what you wanted to say?
It's important to be aware of the fact that there will need to be some place where each of the enums you have declared are mapped to their expected behaviour. (There is also the option of parsing the string values of the enums. This is very hacky and I would advise against it.) This means that you will need to have some control flow that splits based on the enum type. You can do this with either an if statement or a switch statement (switch statements are more common for this purpose, but both work fine).
Still, it's not necessary to have 3 lines of code within each branch. You can bring it down to just one line per branch. When trying to reduce the amount of code duplication, it is helpful to look at which parts are the same in the different cases and which are different.
In this case, it's useful to first convert the enum into a Timespan and then implement some logic based on this Timespan that computes the number of remaining seconds. One method could deal with converting the enum to a Timespan, and the other could calculate the remaining seconds in the interval based on the Timespan, like so:
private static double RemainingSecondsUntilNextInterval(Timespan interval) {
// ...
}
private static Timespan TimespanFromKlineInterval(KlineInterval interval) {
switch(interval){
case KlineInterval.OneMinute:
// ...
}
public static double RemainingSecondsUntilNextInterval(KlineInterval interval) {
return RemainingSecondsUntilNextInterval(TimespanFromKlineInterval(interval));
}
I have two Problem with this code,
The first is that when I print it, Day 6 is not in order, it prints like this:
On Day1:Temperature was12
On Day2:Temperature was13
On Day3:Temperature was15
On Day4:Temperature was17
On Day5:Temperature was18
On Day7:Temperature was19
On Day8:Temperature was20
On Day9:Temperature was21
On Day6:Temperature was22
On Day10:Temperature was23
The Minimum Tempreature was 12,
The Maximum temperature was 23,
The average Temperature was 18,
The second problem is that when I print the days, I want to include also the days in which the Minimum and Maximum Temperatures toke place like this:
The Minimum Tempreature was 12, at Day 1
The Maximum temperature was 23,at Day 10
How Can I get the name of an enum based on its value?
this is the code for above prints,
This code takes enum (days, temperatures) as parameters and prints the days with their related temperatures and minimum, maximum and average....
// if user select February
if (DropDownList1.SelectedValue.ToString()== "February")
{
ShowsTempreatures(FebruaryMonth.Day2);
}
// The method which print and shows temperatures and minimum and maximum...
private void ShowsTempreatures(Enum February)
{
int Minimumtemperture = 40;
int Maximumtemperture = 0;
int total = 0;
int averageTemperatures = 0;
// Return the values of Enum as an Array
Array januaryData = Enum.GetValues(February.GetType());
for (int i = 0; i < januaryData.Length; i++)
{
//printing the Days and Temperatures of enum February
Label1.Text += string.Format("On {0}:Temperature was{0:D}<br/>", januaryData.GetValue(i));
//finding out minimum and maximum and average temperatures
int MonthTemp = (int)januaryData.GetValue(i);
if (MonthTemp<Minimumtemperture)
{
Minimumtemperture = MonthTemp;
}
if (MonthTemp>Maximumtemperture)
{
Maximumtemperture = MonthTemp;
}
total = total + MonthTemp;
}
int totaltempretures = januaryData.Length;
averageTemperatures = (total / totaltempretures);
// printing the minimum,maximum and average temperature
Label1.Text += string.Format("The Minimum Tempreature was {0},<br/> The Maximum temperature was {1},<br/> The average Temperature was {2},<br/> ",
Minimumtemperture, Maximumtemperture, averageTemperatures);
}
private enum FebruaryMonth
{
Day1 = 12,
Day2 = 13,
Day3 = 15,
Day4 = 17,
Day5 = 18,
Day6 = 22,
Day7 = 19,
Day8 = 20,
Day9 = 21,
Day10 = 23
}
Consider this enum:
public enum DayOfMonth
{
Day2 = 2,
Day1 = 1
}
Here is how to get all the enums:
var days = Enum.GetValues(DayOfMonth.Day1.GetType()).Cast<DayOfMonth>().ToList();
Console.WriteLine(days[0]);
Here is how to get enum by its value. It is a simple cast:
DayOfMonth day2 = (DayOfMonth)2;
Console.WriteLine(day2);
Here is how to order them:
var ordered = days.OrderBy(x => x).ToList(); // or you can use OrderByDescending
Console.WriteLine(ordered[0]);
This is because Enum is order by its Values and not by the order of the name given to each value.
You can create a IComparer which will extract each value the name and compare it.
If we will compare the enum value names stricly by lexicographical order we will intreduce a new bug. A bug which will tell us Day10 is before Day2, because in lexicographical order 10 is small then 2. Thus, it leads us to try a different way.
Another more accurate way, will be extracting the enum names and substring only the number portion, then we will parse them into integers and then apply the comparison.
class Program
{
static void Main(string[] args)
{
Array januaryData = Enum.GetValues(typeof(FebruaryMonth));
Array.Sort(januaryData, new FebruaryMonthSubstringComparer());
for (int i = 0; i < januaryData.Length; i++)
{
string res = string.Format("On {0}:Temperature was{0:D}<br/>", januaryData.GetValue(i));
Console.WriteLine(res);
}
}
}
public class FebruaryMonthSubstringComparer : IComparer
{
public int Compare(object x, object y)
{
int monthX = int.Parse(Enum.GetName(typeof(FebruaryMonth), x).Substring(3)); // Naive parsing
int monthY = int.Parse(Enum.GetName(typeof(FebruaryMonth), y).Substring(3));
return monthX - monthY;
}
}
I'm trying to write my own Date program in C#, the problem I've having is that when the user inputs a date for example 2-31, the program allows it. I want to create a condition where I can match which month is entered and then from that see if the day is available in that month. I'm using this code below but it's throwing me the exception for any day such as October 10 which should be right. If I comment this out the date works but it won't check to match the month.
public int Day
{
get
{
return day;
}
private set
{
//int[] daysPerMonth = { 0, 31, 28, 31, 30, 31, 30,
// 31, 31, 30, 31, 30, 31 };
//// check if day in range for month
//if (value > 0 && value <= daysPerMonth[Month])
// day = value;
//else // day is invalid
// throw new ArgumentOutOfRangeException(
// "Day", value, "Day out of range for current month/year");
if (value > 0 && value <= 31)
day = value;
else
throw new ArgumentOutOfRangeException("Day", value, "Day must be 1-31");
}
}
You'll have to know what month the user is selecting as well as the year (to correctly handle leap years).
It would have to be something like this:
public int Day
{
get
{
return day;
}
private set
{
var endOfMonth = new DateTime(year, month, 1).AddMonths(1).AddDays(-1);
if (value > 0 && value <= endOfMonth.Day)
day = value;
else
{
var message = string.Format("Day must be between {0} and {1}", 1 endOfMonth.Day);
throw new ArgumentOutOfRangeException("Day", value, message);
}
}
}
Where year and month are other fields in your class. If you really want to do this without any reference to the DateTime class, I recommend extracting this logic out into a static class which can do the math without having to re-code it any time you want to get the last day of the month.
public static class DateHelper
{
private int[] daysInMonth = new[] { 31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31 };
public static bool IsLeapYear(int year)
{
// TODO: taken from wikipedia, can be improved
if (year % 400 == 0)
return true;
else if (year % 100 == 0)
return false;
else if (year % 4 == 0)
return true;
return false;
}
public static bool GetDaysInMonth(int year, int month)
{
// TODO: check for valid ranges
var days = daysInMonth[month - 1];
if (month == 2 && IsLeapYear(year))
days++;
return days;
}
}
Then you can use it like this:
public int Day
{
get
{
return day;
}
private set
{
var endOfMonth = DateHelper.GetDaysInMonth(year, month);
if (value > 0 && value <= endOfMonth)
day = value;
else
{
var message = string.Format("Day must be between {0} and {1}", 1 endOfMonth);
throw new ArgumentOutOfRangeException("Day", value, message);
}
}
}
If it had to be done using DateTime it can be done using
DateTime.DaysInMonth(Year, Month);
Refrence
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.
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.