I have the following code:
Dictionary<DayOfWeek, List<TimeSpan>> Daily = new Dictionary<DayOfWeek, List<TimeSpan>>();
The idea is that i can add a day with a time to the Daily dictionary. But a job can execute on the same day more than once.
so Daily can look like this:
{ "Monday" : [{"Hour":10, "Minute": 15}, {"Hour": 8, "Minute":5}] }
Now i would like to get the next execution datetime.
private void UpdateNextExecutionTime()
{
TimeSpan t = new TimeSpan(15, 15, 0);
DayOfWeek current = DateTime.Now.DayOfWeek;
DayOfWeek tmp = current;
TimeSpan time = new TimeSpan(DateTime.Now.Hour, DateTime.Now.Minute, DateTime.Now.Second);
int cur = (int)current;
for(int i = 0; i < 7; i++) {
if(Daily.ContainsKey((DayOfWeek)cur)) {
tmp = (DayOfWeek)cur;
/* stuck */
continue;
}
cur++;
if (cur >= 7)
cur = 0;
}
}
I managed to get the first upcoming day (including today) in the Daily dictionary but i'm stuck on the getting the time.
How would i be able to do this?
Assuming t is the time you're using as the point at which you'd like to find the next execution time, put this where you have 'stuck' (edit; just realized you changed to using TimeSpan in your list):
var execTimes = Daily[tmp];
if (execTimes != null) {
var nextTime = execTimes.OrderBy(x => x).FirstOrDefault(x => x > t);
if (nextTime != default(TimeSpan)) {
// do something...
}
}
You can also have the day in the TimeSpan,
Here is a simple example the finds the next date:
var schedule = new List<TimeSpan>{
new TimeSpan(0,16,30,0),
new TimeSpan(1,16,30,0),
new TimeSpan(5,16,30,0)
};
var monday = DateTime.Today.AddDays(-(int)DateTime.Today.DayOfWeek + 1);
var fromMonday = DateTime.Now - monday;
var next = schedule.OrderBy(t => t).FirstOrDefault(t => t > fromMonday);
Console.Write(monday + next - DateTime.Now);
Related
What I want to do is basically in the question title.
This is what I've tried so far, unsuccessfully.
Note that I haven't implemented exact hour and minute yet (9:30 pm).
It actually seems to always return a value between 00:00:59 and 00:00:01 for some reason
DateTime nextSunday = DateTime.Today.AddDays(((int)DayOfWeek.Sunday - (int)DateTime.Today.DayOfWeek + 7) % 7) + new TimeSpan(21, 30, 0);
TimeSpan untilNextSunday = nextSunday - DateTime.Now;
await ReplyAsync($"It is in **{TimeSpan.FromSeconds(untilNextSunday.Seconds)}**");
Which equals to
var today = DateTime.Today;
var daysUntilSunday = ((int)DayOfWeek.Sunday - (int)today.DayOfWeek + 7) % 7;
var nextSunday = today.AddDays(daysUntilSunday);
var ts = new TimeSpan(21, 30, 0);
nextSunday = nextSunday.Date + ts;
TimeSpan untilNextSunday = nextSunday - DateTime.Now;
If possible, I'd also like to use Paris TimeZone.
I tend to find all of the DateTime.Today.AddDays(((int)DayOfWeek.Sunday - (int)DateTime.Today.DayOfWeek + 7) % 7) + new TimeSpan(21, 30, 0) arithmetic quite confusing. Instead I try to go with a more iterative approach that can be clearly reasoned about.
Try this:
public static DateTime GetNextDateTime(DateTime now, DayOfWeek targetDay, TimeSpan targetTime)
{
DateTime target = now.Date.Add(targetTime);
while (target < now || target.DayOfWeek != targetDay)
{
target = target.AddDays(1.0);
}
return target;
}
Now you can use it like this:
DateTime now = DateTime.Now;
DateTime target = GetNextDateTime(DateTime.Now, DayOfWeek.Sunday, new TimeSpan(21, 30, 0));
TimeSpan untilNextSunday = target.Subtract(now);
Here's an example using Noda Time, including time zone handling. It doesn't attempt to handle "interesting" situations where (say) you ask for the next 1:30am, and it's already 1:45am but the clock goes back at 2am - in which case the right answer is really "45 minutes" but this code will give you a week instead.
using System;
using NodaTime;
class Test
{
static void Main()
{
var duration = GetDurationToNext(
IsoDayOfWeek.Sunday, new LocalTime(21, 30),
DateTimeZoneProviders.Tzdb["Europe/Paris"],
SystemClock.Instance);
Console.WriteLine($"Duration: {duration}");
}
static Duration GetDurationToNext(
IsoDayOfWeek dayOfWeek,
LocalTime timeOfDay,
DateTimeZone zone,
IClock clock) // Or just take an instant
{
var now = clock.GetCurrentInstant();
var localNow = now.InZone(zone).LocalDateTime;
var localNext = localNow
.Date.With(DateAdjusters.NextOrSame(dayOfWeek))
.At(timeOfDay);
// Handle "we're already on the right day-of-week, but
// later in the day"
if (localNext <= localNow)
{
localNext = localNext.PlusWeeks(1);
}
var zonedNext = localNext.InZoneLeniently(zone);
var instantNext = zonedNext.ToInstant();
return instantNext - now;
}
}
Having this data (SQL):
I call (every minute with timer )data then group into 30 minutes interval with this method:
public void GetData()
{
try
{
using (crypto_dbEntities1 context = new crypto_dbEntities1(con))
{
var result = context.kraken_btc.Where(x => x.date >= LastRecordedPoint).ToList();
result = AggregateCandlesIntoRequestedTimePeriod(Period.Minute, Period.HalfAnHour, result);
foreach (var data in result.OrderBy(x => x.date))
{
data.date = DateTime.SpecifyKind(data.date.DateTime, DateTimeKind.Utc).ToUniversalTime();
Point pb = new Point
{
Date = data.date.DateTime.ToLocalTime(),
JDate = (long)data.javaDate,
Open = (double)data.open,
High = (double)data.hight,
Close = (double)data.close,
Low = (double)data.low,
Volume = (long)data.volume,
};
if (pb.Date <= LastRecordedPoint)
{
MainCollection.Last().Date = data.date.DateTime.ToLocalTime();
MainCollection.Last().Close = (double)data.close;
MainCollection.Last().High = (double)data.hight;
MainCollection.Last().Open = (double)data.open;
MainCollection.Last().Low = (double)data.low;
MainCollection.Last().Volume = (long)data.volume;
Debug.WriteLine(pb.Date + " Updated data ..");
}
else
{
MainCollection.Add(pb);
}
LastRecordedPoint = pb.Date;
}
}
}
catch (Exception err)
{
MessageBox.Show(err.ToString());
}
}
public enum Period
{
Minute = 1,
FiveMinutes = 5,
TenMinutes = 10,
QuarterOfAnHour = 15,
HalfAnHour = 30,
Hour = 60
}
private List<kraken_btc> AggregateCandlesIntoRequestedTimePeriod(Period rawPeriod, Period requestedPeriod, List<kraken_btc> candles)
{
int rawPeriodDivisor = (int)requestedPeriod;
candles = candles.GroupBy(g => new { TimeBoundary = new DateTime(g.date.Year, g.date.Month, g.date.Day, g.date.Hour, (g.date.Minute / rawPeriodDivisor) * rawPeriodDivisor, 0) })
.Select(s => new kraken_btc
{
date = s.Key.TimeBoundary,
hight = s.Max(z => z.hight),
low = s.Min(z => z.low),
open = s.First().open,
close = s.Last().close,
volume = s.Sum(z => z.volume),
})
.OrderBy(o => o.date)
.ToList();
return candles;
}
And it does the job of aggregating the data, but the problem is I have to wait 30 minutes for my serie finally update if you look at the last candle its time is 17.30 when database has data for 17.47 (im UTC +2.00).
So the question is how can I group data and start draw an incomplete candle at 18.00 like all exchange platform does...
The GroupBy statement rounds down the dates, so all data from 17:30 up to 17:47 is rounded down to 17:30.
I would move the code to calculate the TimeBoundary to its own method so you can unit test it fully, using the RoundUp/RoundDown methods in this question How can I round up the time to the nearest X minutes? by redent84
public static DateTime RoundUp(this DateTime dt, TimeSpan d)
{
var modTicks = dt.Ticks % d.Ticks;
var delta = modTicks != 0 ? d.Ticks - modTicks : 0;
return new DateTime(dt.Ticks + delta, dt.Kind);
}
public static DateTime RoundDown(this DateTime dt, TimeSpan d)
{
var delta = dt.Ticks % d.Ticks;
return new DateTime(dt.Ticks - delta, dt.Kind);
}
I am receiving large amounts of time series data in a list timeSeries.Records. The object has a date time property of (10 minute intervals) TimeStamp and a data value property Data
The time series may have gaps which I need to pad with the appropriate date time stamp and a value of double.NaN. This will allow me to display the data correctly within a chart in the UI.
I have written the following code which works but is incredibly slow! I suspect this is because I am newing up new objects & returning them in the while loop. I assume this can be optimised significantly and maybe is the wrong approach completely but not sure where to start...
Here's the code which appends the data to the chart:
foreach (TimeSeriesRecord record in this.FillTimeSeriesGaps(timeSeries))
{
dataSeries.Append(record.TimeStamp, record.Data);
}
Filling the gaps:
private IEnumerable<TimeSeriesRecord> FillTimeSeriesGaps(ITimeSeriesProvider timeSeries)
{
// Get the min & max records by date time
TimeSeriesRecord minRecord = timeSeries.Records.OrderBy(r => r.TimeStamp).FirstOrDefault();
TimeSeriesRecord maxRecord = timeSeries.Records.OrderByDescending(r => r.TimeStamp).FirstOrDefault();
// 10 sec time interval
TimeSpan seriesIntervalTime = new TimeSpan(0, 10, 0);
DateTime workingDateTime = minRecord.TimeStamp;
while (minRecord.TimeStamp <= maxRecord.TimeStamp)
{
if (timeSeries.Records.All(r => r.TimeStamp != workingDateTime))
{
yield return new TimeSeriesRecord() {TimeStamp = workingDateTime, Data = double.NaN};
}
else
{
yield return new TimeSeriesRecord()
{
TimeStamp = workingDateTime,
Data = (from r in timeSeries.Records
where r.TimeStamp == workingDateTime
select r.Data).First()
};
workingDateTime = workingDateTime.Add(seriesIntervalTime);
}
}
}
For anyone who is interested this is how I sped things up:
private static IEnumerable<TimeSeriesRecord> FillTimeSeriesGaps(ITimeSeriesProvider timeSeries)
{
TimeSpan seriesIntervalTime = new TimeSpan(0, 10, 0);
DateTime previousDateTime = DateTime.MinValue;
foreach (GenericTimeSeriesRecord record in timeSeries.Records)
{
if (previousDateTime == DateTime.MinValue)
{
yield return record;
previousDateTime = record.TimeStamp;
continue;
}
if (previousDateTime + seriesIntervalTime == record.TimeStamp)
{
yield return record;
previousDateTime = record.TimeStamp;
continue;
}
else
{
yield return new TimeSeriesRecord() { TimeStamp = previousDateTime + seriesIntervalTime, Data = double.NaN };
previousDateTime = previousDateTime + seriesIntervalTime;
}
}
}
How to make newTime as a global variable?
It should be added 30 minutes in time and use it in a condition. It is necessary to make var newTime as global var.
if (timerCheck == 0)
{
var today = DateTime.Now;
var interval = new TimeSpan(00, 30, 00);
var newTime = today + interval;
timerCheck = 1;
}
if (timerCheck == 1)
{
var today = DateTime.Now;
if (today >= newTime)
{
You can do it like this
public static class GlobalVariables
{
public static DateTime NewTime { get; set; }
}
Then call it like this:
if (today >= GlobalVariables.NewTime)
Just move it outside if.
I don't test it, but It might work.
DateTime newTime;
if (timerCheck == 0)
{
var today = DateTime.Now;
var interval = new TimeSpan(00, 30, 00);
newTime = today + interval;
timerCheck = 1;
}
if (timerCheck == 1)
{
if (newTime.Equals(default(DateTime)) return; // don't sure is it required or not
var today = DateTime.Now;
if (today >= newTime)
{
Well i think you should put it outside the body of If first as a variable then use it in the condition.
Is there a way to fill a List by all of minutes of a day? So, it must has 1440 (60 * 24) minutes as DateTime
(14:37, 14:38, 14:39 etc..)
object.
Thanks in advance.
var startTime = DateTime.Now.Date;
var minsOfDay =
Enumerable.Range(0, 1440).Select(i => startTime.AddMinutes(i)).ToList();
A longer, though easier to follow way:
var timeStart = DateTime.Now.Date;
var timeStop = DateTime.Now.Date.AddDays(1);
var mins = new List<DateTime>();
while(timeStart < timeStop)
{
mins.Add(timeStart);
timeStart = timeStart.AddMinutes(1);
}
As an alternative, you can use List<string> with AddMinutes methods like;
List<string> list = new List<string>();
DateTime midnight = DateTime.Today;
while (midnight < DateTime.Today.AddDays(1))
{
list.Add(midnight.ToString("HH:mm"));
midnight = midnight.AddMinutes(1);
}
foreach (var item in list)
{
Console.WriteLine(item);
}
Here a demonstration.