Reputation: 2196
I am trying to add a feature to my program that will execute code every day after the closing of the futures markets in Chicago. This means that I want to run code at around 5:35 Eastern Time (4:35 Central Time where the markets are located). This of course will be switching back and forth between EST (UTC -5) and EST (UTC -4) as Daylight Saving Time occurs.
I know there are many similar questions, however none of them seem to offer a solution that I can use. The primary suggestions seem to be to use the Task Scheduler or Quartz, however, I am unable to implement those in my program. I think that the most promising solution would be to use a combination of TimeZoneInfo
, DateTime
, and TimeSpan
to schedule a timer every day that will trigger at the right time. The solution I currently have is this:
DateTime now = DateTime.Now;
DateTime currentDateTime = now.DateTime.Date;
DateTime expiryDateTime = currentDateTime
.AddHours(17)
.AddMinutes(35)
.AddDays(
now.DateTime.Hour >= 18 + utcOffset
|| (now.DateTime.Hour == 17 && now.DateTime.Minute >= 35) ? 1 : 0);
Timer timer = new Timer(
...,
null,
expiryDateTime - DateTime.Now,
...);
I think this this will fall apart, however, if my code is run in a time zone other than Eastern Time. I'm also concerned that this will not behave properly on the 23 or 25 hour days that occur when the time zone switches from EST to EDT and visa versa.
Is there a better way than what I am currently doing to handle the scheduling? How can I make this code more robust to handle being run in any time zone but always execute at the same time in Eastern Time?
EDIT: As mentioned above, Task Scheduler and Quartz are not options. Quartz is out because I am unable to include third party libraries. Task Scheduler is out because I need access to a lot of internal values from my program. Launching another application and exposing those values to that application adds way more complexity than I think this merits.
Upvotes: 1
Views: 6049
Reputation: 2196
I appreciate the feedback of all the other answers and would like to point out to anybody who has stumbled upon this that Quartz or Windows Task Scheduler are indeed better options if you can use them. I didn't have that option, however, so here is how I ended up implementing finding the right time every time and scheduling the timer to run:
//This code block is designed to schedule a timer for 5:35pm Eastern Time, regardless of whether is is Daylight Savings (EDT, UTC-4) or not (EST, UTC-5).
// When understanding this code it is important to remember that a DateTime in UTC time represents an ABSOLUTE point in time. Any UTC time can only occur once.
// A date time measured in local time (here we force Eastern Standard) is NOT ABSOLUTE. The same local time can occur twice (2am Nov 3nd 2013 for example)
//Gets Eastern Timezone to be used in conversions
TimeZoneInfo easternTimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
//Grabs the current time (UTC)
DateTime utcNowDateTime = DateTime.Now.ToUniversalTime();
//Gets current time in Eastern Time (could be UTC-4 or -5 depending on time of year)
DateTime easternNowDateTime = TimeZoneInfo.ConvertTime(utcNowDateTime, easternTimeZoneInfo);
//Gets todays date
DateTime easternNowDate = easternNowDateTime.Date;
//Gets 5:35pm today or tomorrow depending on whether we are past 5:35pm or not.
// Even though there are actually 18 hours from midnight to 5:35pm on Nov 2 2014 and 16 hours from midnight to 5:35pm on March 9 2014,
// this will still end up at 5:35pm on those days because DateTime DOESNOT take into account the movement of the clocks (foreward or backward) when calling
// .AddHours(), etc
DateTime easternExpiryDateTime = easternNowDate.AddHours(17).AddMinutes(35).AddDays(easternNowDateTime.Hour >= 18 || (easternNowDateTime.Hour == 17 && easternNowDateTime.Minute >= 35) ? 1 : 0);
//Convert the Easter Time date time to UTC. When subtracting this time from another UTC DateTime you will get the correct TimeSpan for use with a timer
// (even on 23 such as March 10th and 25 hour days such as November 3rd 2013)
DateTime utcExpiryDateTime = easternExpiryDateTime.ToUniversalTime();
//Set the timer to trigger at the desired point in the future.
Timer timer = new Timer(
...,
null,
utcExpiryDateTime - utcNowDateTime ,
...);
Upvotes: 1
Reputation: 241525
The correct solution is to use Quartz.net or the Windows Task Scheduler, but since you said you can't do that, let's think about what else you might do.
Your program will need to be running all the time. This means it should be a background Windows Service application. You don't want to make it a user application, because you don't want to deal with users closing the application, and it should run even if nobody is logged in.
You should not use a long timer. Instead, use a short timer that runs periodically to check if it's time to execute your event. How often should the short timer run? That is a trade off between how much resources do you want to consume while idle, and how much potential delay is acceptable for your task. If it needs to run within a few minutes of a set time, you might just want to check once per minute, or once per five minutes or so.
If possible, you should fetch the "next execution time" just once from your data. So when you check periodically, you don't have to go hit the database each time. Be careful though, if this value is volatile then you'll need a way to expire your local cached value.
Regarding DST and time zones, you need to distinguish between tasks that are scheduled to run at specific UTC times or at specific local times. For example, if you have an hourly task, you might as well use UTC so you aren't thrown off by the DST change. If you have a daily task you should probably use local time. That might be the local time of the computer, or it might be the local time in a specific time zone. That's up to you to manage. Also, you will need a strategy to deal with tasks scheduled to run during the gap created by the spring-forward DST transition, or tasks scheduled to run during the ambiguous time created by the fall-back transition. See also, the DST tag wiki.
Now look back at all of this, and realize that you have just written your own equivalent of Quartz.NET. Are you really sure you can't just build on top of one that already exists? Why reinvent the wheel?
Upvotes: 3
Reputation: 43254
You are missing the point of the Windows Task Scheduler. You do not implement it in your application. You write an application, then use the task scheduler to run it at a set time every day.
In response to your clarification regarding scheduling and the nature of your application, unfortunately, you highlight a need to rethink your application. Building a monolith application that has to run all the time and to which you keep adding features is not a good idea.
Instead, why not rethink the structure of your application. You could have the core functionality, which needs to run all the time, running as a service. Other components could then talk to it via eg a restful interface. The user interface aspect of your application can then be stopped ans started, without worrying about affecting the service, which can keep running regardless. In addition, adding extra functionality, such as scheduled apps that perform specific tsaks at certain times of the day, becomes much easier.
Upvotes: 7
Reputation: 2986
Since you can't use third party libraries, or expose your internals to another application, is it an option to expose a communication endpoint (WCF, UDP, named pipe etc.)? Or perhaps you could monitor a folder for "command files" created by another application? (using FileSystemWatcher or a simple timer)
A separate application could send messages to this endpoint/folder, and Windows Task Scheduler can schedule this application.
Upvotes: 0
Reputation: 4268
You can create Windows Service and install it on your system.
Upvotes: 0
Reputation: 44275
Setup a windows scheduled task. Long running timers tend to be problematic and certainly overly complex for such a straight forward job.
Upvotes: 9