Parametrized remaining seconds until next interval - c#

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));
}

Related

Insert object into string

I wanted to create a readonly struct that represents time in 24-hour format, so it has a method that is supposed to return a string of time (for example: "08:45" if 8 and 45 were passed respectively or "03:40" if 25 hours and 160 minutes were passed)
The problem is in the method, how do I return a string with values from the object inserted into it? I imagined something like return "0{stringtimeobj.hours}:0{stringtimeobj.minutes} but I can't really figure out how to format a string so that it has values from an object in it. Please help out!
using System;
namespace TimeStruct
{
public readonly struct Time
{
private readonly int hours2;
private readonly int minutes2;
public Time(int minutes)
: this()
{
this.minutes2 = minutes;
}
public Time(int hours, int minutes)
{
this.hours2 = hours;
this.minutes2 = minutes;
}
public int Hours
{
get
{
if (this.hours2 < 24)
{
return this.minutes2;
}
else if (this.hours2 == 24)
{
return 0;
}
else
{
double overflowtohours = ((Math.Truncate((double)this.minutes2 / 60) + 1) * 60) - 60;
return Convert.ToInt32(this.hours2 - ((Math.Truncate((double)(Convert.ToInt32(overflowtohours / 60) + this.hours2) / 24) + 1) * 24) - 24);
}
}
}
public int Minutes
{
get
{
if (this.minutes2 < 60)
{
return this.minutes2;
}
else if (this.minutes2 == 60)
{
return 0;
}
else
{
double overflowtohours = ((Math.Truncate((double)this.minutes2 / 60) + 1) * 60) - 60;
return Convert.ToInt32(this.minutes2 - overflowtohours);
}
}
}
public string ToString(int hours3, int minutes3)
{
Time stringtimeobj = new Time(hours3, minutes3);
return /* string "00:00" with hour and minute values from object stringtimeobj inserted */
}
}
}
You just need this implementation of your struct:
public readonly struct Time
{
private readonly int _minutes;
public Time(int minutes) : this(0, minutes) { }
public Time(int hours, int minutes)
{
_minutes = (hours * 60 + minutes) % (24 * 60);
}
public int Hours => _minutes / 60;
public int Minutes => _minutes % 60;
public override string ToString() => $"{this.Hours:00}:{this.Minutes:00}";
}
When I run this code:
Console.WriteLine(new Time(8, 45).ToString());
Console.WriteLine(new Time(25, 160).ToString());
I get the following output:
08:45
03:40
You can implement the standard ToString method like this:
public override string ToString()
{
return $"{this.Hours:00}:{this.Minutes:00}";
}
or equivalent:
public override string ToString()
{
return String.Format("{0:00}:{1:00}", this.Hours, this.Minutes);
}
The override keyword is required because you override the default ToString method
It doesn't need parameters, because it reads the local properties (this.Minutes and this.Hours - you can omit the "this."). Plus the standard ToString doesn't take parameters
The first one uses an interpolated string, the second example uses String.Format
In both cases the :00 means "format as two digits, adding leading 0's as needed" (docs)
Bonus: the debugger will use this method to display the value of this type
You can use built-in DateTime.ToString() with custom format. In your case it would look like:
int hour = 2;
int minute = 1;
DateTime time = new DateTime(2000, 1, 1, hour, minute, 0);
Console.WriteLine(time.ToString("HH:mm")); // 02:01
Yep , you can use it like this way below
DateTime time = new DateTime(2000, 1, 1, stringtimeobj.Hours,stringtimeobj.Minutes, 0);
return time.ToString("HH:mm");
We need to do some checking. If "stringtimeobj.hours" is greater than 9, which is 10 to 24, then "{stringtimeobj.hours}" can be used.
Also, if "stringTimeobj.minutes" is greater than 9, that is, 10 to 60, then "{stringtimeobj.minutes}" can be used.
Something like that.

Creating an Object that an only have certain int values

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

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.

Brain storm: How to arrange DateTime to particular interval frame?

Suppose i have a DateTime, e. g. 2010.12.27 12:33:58 and i have an interval frames of, suppose, 2 seconds, excluding the last border.
So, i have the following frames:
12:33:58(incl.)-12:34:00(excl.) - let it be interval 1
12:34:00(incl.)-12:34:02(excl.) - let it be interval 2
12:34:02(incl.)-12:34:04(excl.) - let it be interval 3
and so on.
I'm given a random DateTime value and i have to correlate that value according the above rules.
E. g. the value "12:33:58" falls into interval 1, "12:33:59" falls into interval 1, "12:34:00" falls into interval 2 and so on.
In code it should look like the following:
var dt = DateTime.Now;
DateTime intervalStart = apply_the_algorythm(dt);
It seems to be some simple arithmetic action(s) with float or something, any decisions are welcome!
If the interval is only second resolution and always divided 86400, then take the number of seconds that have passed today, divide it by the interval, round it to an integer value, multiply it, and add it back to today. Something like dateinquestion.Subtract(dateinquestion.Date).TotalSeconds, ((int)seconds/interval)*interval, dateinquestion.Date.AddSeconds(...)
If you want the range of all your intervals to span several days, possibly a long time, you might want to express your DateTime values in UNIX-seconds (the number of seconds since 1970-01-01). Then you just find out when your very first interval started, calculate how many seconds passed since then, and divide by two:
int secondsSinceFirstInterval = <currDate in UNIX time>
- <start of first interval in UNIX time>;
int intervalIndex = secondsSinceFirstInterval / 2;
Otherwise you're better off just counting from midnight.
Use TimeSpan.TotalSeconds and divide the result by the size of the interval.
const long intervalSize = 2;
DateTime start = new DateTime(2010, 12, 27, 12, 33, 58);
TimeSpan timeSpan = DateTime.Now - start;
long intervalInSeconds = (long)timeSpan.TotalSeconds;
long intervalNumber = 1 + intervalInSeconds / intervalSize;
DateTime start = new DateTime(2010, 12, 31, 12, 0, 0);
TimeSpan frameLength = new TimeSpan(0, 0, 3);
DateTime testTime = new DateTime(2010, 12, 31, 12, 0, 4);
int frameIndex = 0;
while (testTime >= start)
{
frameIndex++;
start = start.Add(frameLength);
}
Console.WriteLine(frameIndex);
dates = new List<DateTime>
{
DateTime.Now.AddHours(-1),
DateTime.Now.AddHours(-2),
DateTime.Now.AddHours(-3)
};
dates.Sort((x, y) => DateTime.Compare(x.Date, y.Date));
DateTime dateToCheck = DateTime.Now.AddMinutes(-120);
int place = apply_the_algorythm(dateToCheck);
Console.WriteLine(dateToCheck.ToString() + " is in interval :" +(place+1).ToString());
private int apply_the_algorythm(DateTime date)
{
if (dates.Count == 0)
return -1;
for (int i = 0; i < dates.Count; i++)
{
// check if the given date does not fall into any range.
if (date < dates[0] || date > dates[dates.Count - 1])
{
return -1;
}
else
{
if (date >= dates[i]
&& date < dates[i + 1])
return i;
}
}
return dates.Count-1;
}

How to deal with Rounding-off TimeSpan?

I take the difference between two DateTime fields, and store it in a TimeSpan variable, Now I have to round-off the TimeSpan by the following rules:
if the minutes in TimeSpan is less than 30 then Minutes and Seconds must be set to zero,
if the minutes in TimeSpan is equal to or greater than 30 then hours must be incremented by 1 and Minutes and Seconds must be set to zero.
TimeSpan can also be a negative value, so in that case I need to preserve the sign..
I could be able to achieve the requirement if the TimeSpan wasn't a negative value, though I have written a code I am not happy with its inefficiency as it is more bulky ..
Please suggest me a simpler and efficient method.
Thanks regards,
This is my code which works fine, when TimeSpan is not negative value ..
TimeSpan time_span = endTime.Subtract(startTime);
TimeSpan time_span1;
if (time_span.Minutes >= 30)
{
time_span1 = new TimeSpan(time_span.Hours + 1, 0, 0);
}
else
{
time_span1 = new TimeSpan(time_span.Hours, 0, 0);
}
time_span1 will contain the result ..
How about:
public static TimeSpan Round(TimeSpan input)
{
if (input < TimeSpan.Zero)
{
return -Round(-input);
}
int hours = (int) input.TotalHours;
if (input.Minutes >= 30)
{
hours++;
}
return TimeSpan.FromHours(hours);
}
You can use
double v = span.TotalHours;
v = Math.Round(v, MidpointRounding.AwayFromZero);
span = TimeSpan.FromHours(v);
It depends on whether I understood your rules for negative values correctly.
TimeSpan is immutable, so you have to create a new one. This is also a perfect case for using extension methods in C#:
public static class TimeSpanUtility
{
public static TimeSpan Round( this TimeSpan ts )
{
var sign = ts < TimeSpan.Zero ? -1 : 1;
var roundBy = Math.Abs(ts.Minutes) >= 30 ? 1 : 0;
return TimeSpan.FromHours( ts.TotalHours + (sign * roundBy) );
}
}
// usage would be:
var someTimeSpan = new TimeSpan( 2, 45, 15 );
var roundedTime = someTimeSpan.Round();

Categories