Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi 'Alarm Clock'-like application

I need to make a simple alarm clock application that, instead of playing a sound, will upload a file to the ftp (got the latter figured out). Timers have proved to be ineffective when it comes to executing a thread.

Here's what I got so far:

var
ttime     : tDateTime;
timerstr  : string;
timealarm : string;
aThread : TMyThread;
begin
aThread := tMyThread.Create(false);
ttime := Now;
timestr := FormatDateTime('hh:nn:ss', ttime);
timealarm := '09:30:30';
if timestr = timealarm then
aThread.Resume; //The thread will execute once and terminate;
end;

Can you guys think of another way to make that command happen once a day in a more effective way?

Thank you.

like image 964
John Rosenberg Avatar asked Nov 29 '11 23:11

John Rosenberg


3 Answers

Solution found: CRON Scheduler

Thank you LU RD and Runner.

like image 174
John Rosenberg Avatar answered Nov 04 '22 23:11

John Rosenberg


Here's my sample code. It's important to note that the comparison is between TDateTime values instead of strings, the comparison is >= rather than =, I'm careful to exclude the day portion when I don't want it, and I'm keeping track of the last day it ran.

procedure TForm1.Timer1Timer(Sender: TObject);
var
  currentTime: TDateTime;
  alarmTime: TDateTime;
begin
  // Time is a floating point value with everything to the left of the zero
  // representing the days, and everything to the right of the decimal
  // point representing time of day

  // Date() gets just the day portion, and lastDateExecuted is a global TDateTime
  // variable where we store the last day the program ran
  // We only go further if it didn't execute today
  if (Date > lastDateExecuted) then
  begin
    // Use Time() instead of Now() to get just the time portion and leave the day
    // portion at 0
    currentTime := Time;
    // Covert our alarm string to TDateTime instead of TDateTime to string
    alarmTime := EncodeTime(9, 30, 30, 0);
    // Is it time to run?
    // Greater than or equal comparison makes the application still run in case
    // our timer happens to miss the exact millisecond of the target time
    if currentTime >= alarmTime then
    begin
      // Immediately set the last run date to keep the next timer event
      // from repeating this
      lastDateExecuted := Date;
      // Do your work here
      DoMyThing;
    end;
  end;
end;

Be sure to initialize the lastDateExecuted value to something like 0.

Use the timer interval for your granularity. If you want it to run within a minute of the target time, set the Interval to a minute. If you want it to try to run within a second, set the timer interval to a second.

like image 23
Marcus Adams Avatar answered Nov 04 '22 22:11

Marcus Adams


If I understand you right, all you need to know is how to recognize when a particular time has been reached in order to execute this thread. Indeed, a timer isn't necessarily an ideal tool to use for this, as timers don't actually trigger in real-time (you can have the interval on 500 msec, but over after 1 minute, or 60,000 msec, it will not be perfectly lined up to an exact 120 executions, as you would wish). However, that doesn't mean we can't use timers.

Inside the timer (or you can make another repeating thread for this too), you simply get the current date/time. IMPORTANT: Make sure the interval of this timer or thread is less than half a second - this will ensure that your time won't be missed. So you would read it like this...

uses
  DateUtils;

.....

var
  HasExecuted: Bool;

.....

constructor TForm1.Create(Sender: TObject);
begin
  HasExecuted:= False;
end;

procedure TimerOnTimer(Sender: TObject);
var
  N, A: TDateTime;
  Thread: TMyThread;
begin
  N := Now;
  A := StrToDateTime('11/20/2011 15:30:30'); //24 hour time
  //If now is within 1 second over/under alarm time...
  if (N >= IncSecond(A, -1)) and (N <= IncSecond(A, 1)) then
  begin
    if not HasExecuted then begin
      HasExecuted:= True;
      aThread := tMyThread.Create(true);
      aThread.Resume; //The thread will execute once and terminate;  
    end;
  end;
end;

It probably doesn't work right for you because you are using a = operator. What if this timer skips that 1 second? One execution could be the second before, and the next execution could be the second answer, in which case it won't evaluate this expression to equal true.

On another note, your TMyThread constructor - are you overriding this? If so, is the False still the original CreateSuspended parameter? If so, then you are telling this thread to execute immediately upon creation. Pass True in this parameter to make it suspended upon creation, because I see you are also calling Thread.Resume below that (which if the parameter remains false, it's already resumed). On the other hand, you also do not need to create that thread (at least I'm assuming) unless the time has been reached. Why are you creating it before you even check? It's creating one of these for each and every time this timer is executed (I'm presuming this is the thread that will take care of the uploading, as needed). Also, make sure that the thread is properly free'ing its self when it's done, and doesn't just get stuck...

constructor TMyThread.Create(CreateSuspended: Bool);
begin
  inherited Create(CreateSuspended);
  FreeOnTerminate:= True; //Make sure it free's its self when it's terminated
end;

EDIT:

I missed something - if, let's say, the interval of this timer was 1 (it should be more like 200-400), then it could very well execute many times in a row, during this time period. I modified the code above to also make sure it's only executed once. NOTE: This code was typed by memory, not in a delphi environment.

like image 41
Jerry Dodge Avatar answered Nov 04 '22 21:11

Jerry Dodge