encodetime, incmilliseconds in delphi - delphi

i have create a coding to set a 20 millisecond faster than windows system. i'm using encodetime.
here's the code
procedure TForm1.Button1Click(Sender: TObject);
Var
delphi_datetime :tDateTime;
t_date : tdatetime ;
windows_datetime : tSystemTime;
begin
t_date := dATE;
delphi_datetime := encodetime(8,44,59,980);
delphi_datetime := incmillisecond(delphi_datetime, 20);
replacedate(t_date , delphi_datetime);
datetimetosystemtime( delphi_datetime , windows_datetime );
setlocaltime( windows_datetime );
showmessage('time now = ' + timetostr(delphi_datetime));
end;
aftr i run it, show the correct time. but the date goes to 30 dec 1899.. but i want to the current today date but with the time faster 20 milliseconds. any help.. please...

You have the arguments of ReplaceDate backward. It reads the date of the second parameter and assigns the date portion of the first parameter. The date portion of delphi_datetime is 0 because that's how EncodeTime works. You take that zero value and assign it to t_date, but then you continue working with delphi_datetime.
Reverse the arguments of ReplaceDate, and you should see that your current system time gets set to 8:45:00.000 with the current date.
ReplaceDate(delphi_datetime, t_date);
You could have noticed the mistake sooner if you hadn't used a separate t_date variable. If you'd called Date directly, your code would have failed to compile:
ReplaceDate(Date, delphi_datetime); // can't pass function result as "var" parameter
This works:
ReplaceDate(delphi_datetime, Date);

Rob has identified problems with your existing code. However your existing approach is needlessly complex. If you chose a simpler approach you would find it easier to get the code right.
If you want a date time that is 20 milliseconds greater than now, do it like this:
MyDateTime := IncMillisecond(Now, 20);
If you want a date time representing 0845 today, then you write:
MyDateTime := Date + EncodeTime(8, 45, 0, 0);

Related

TXSDateTime error "local time situated within the missing period prior to DST'

We convert our local dates (no time parts) to a external system expecting UTC datetime strings, by adding TTimeZone.Local.UTCOffset (now 2 hours) to a TDateTime.
This fails for the night we switch to DST (at 02:00).
Error from System.RTLConst:
SLocalTimeInvalid = 'The given "%s" local time is invalid (situated within the missing period prior to DST).';
occurring in System.DateUtils:
function TTimeZone.GetUtcOffsetInSeconds(const ADateTime: TDateTime; const ForceDaylight: Boolean): Int64;
var
LOffset, LDSTSave: Int64;
LType: TLocalTimeType;
begin
{ Obtain the information we require }
DoGetOffsetsAndType(ADateTime, LOffset, LDSTSave, LType);
{ Select the proper offset }
if (LType = lttInvalid) then
raise ELocalTimeInvalid.CreateResFmt(#SLocalTimeInvalid, [DateTimeToStr(ADateTime)])
else if (LType = lttDaylight) or ((LType = lttAmbiguous) and ForceDaylight) then
Result := LOffset + LDSTSave
else
Result := LOffset;
end;
Code to reproduce:
function DateTime2UTCString(ADateTime: TDateTime): String;
var XSD: TXSDateTime;
begin
XSD := TXSDateTime.Create;
try
try
XSD.AsDateTime := ADateTime;
Result := XSD.NativeToXS;
except
on E:Exception do
Result := E.Message;
end;
finally
XSD.Free;
end;
end;
function Date2UTCString(ADateTime: TDateTime): String;
// Input is guaranteed to have no time fraction
begin
ADateTime := ADateTime + TTimeZone.Local.UTCOffset;
Result := DateTime2UTCString(ADateTime);
end;
procedure TFrmUTCandDST.Button1Click(Sender: TObject);
var
lDT: TDateTime;
l : integer;
begin
lDT := EncodeDate(2016,3,25);
for l := 0 to 2 do
begin
lDT := lDT +1;
Memo1.Lines.Add(DateToStr(lDT) + ' -> ' + Date2UTCString(lDT));
end;
end;
(Don't forget to use SOAP.XSBuiltIns, System.DateUtils, System.TimeSpan).
Output:
26-3-2016 -> 2016-03-26T02:00:00.000+01:00
27-3-2016 -> The given "27-3-2016 2:00:00" local time is invalid (situated within the missing period prior to DST).
28-3-2016 -> 2016-03-28T02:00:00.000+02:00
How can I graciously circumvent this? I can use TTimeZone.Local.IsInvalidTime(ADateTime) to detect invalid dates, but
26-3-2016 2:00:00 would be wrong (that's exactly the time we moved to DST), not 27-3-2016 2:00:00 - so I don't know how to adjust in case of the 'invalid' date.
There is bug in unit System.DateUtils.pas (afaik still present in 10.1).
Function AdjustDateTime first takes date and time handling it as local time, and THEN tries to put offset into it. Since during daylight saving time there is a "missing hour" (in case of central Europe it was 26.03.2017), therefore after 1:59:59 A.M you've got 3:00:00 A.M.
If you accidentally use this period (like 2:17:35), you'll get an exception.
This is also present in other functions.
Simple code to reproduce the exception (C++):
ShowMessage(ISO8601ToDate("2017-03-26T02:22:50.000Z",false));
but this one runs ok:
ShowMessage(ISO8601ToDate("2017-03-26T02:22:50.000Z",true));`
For now to avoid the exception use XSD.AsUTCDateTime, then apply the local offset.
Example in c++ :
TTimeZone * localTz = TTimeZone::Local;
TDateTime TimeSomething = localTz->ToLocalTime(XSD->AsUTCDateTime);
In your case either local time is indeed invalid (there was no "2:00"),
or somewhere you're trying to treat UTC Time as local time, which of course is invalid. Solve this and you will solve your problem.
How can I graciously circumvent this? I can use TTimeZone.Local.IsInvalidTime(ADateTime) to detect invalid dates, but 26-3-2016 2:00:00 would be wrong (that's exactly the time we moved to DST), not 27-3-2016 2:00:00 - so I don't know how to adjust in case of the 'invalid' date.
Additionally i think you missing that in year 2016 we moved to DST at 27.03 at 2:00 but THIS year at 26-03, so 27-3-2016 2:00:00 is perfectly invalid date :)
As #Vancalar said, there is a bug in ISO8601ToDate when converting from UTC to local time near a DST transition. This bug persists in Rio 10.3.3.
A simple workaround is to avoid ISO8601ToDate's timezone conversion and let Microsoft do it. That is, replace the false value with true and follow with a call to UTCToTZLocalTime, as in the following Pascal snippet:
// Get UTC datetime from ISO8601 string
datetime := ISO8601ToDate(ISO8601UTCstring, true);
// Convert to local time w/DST conversion
datetime := UTCToTZLocalTime(zoneinfo,datetime);

How to avoid TDateTime Data Rounding

I am writing column and cell classes for FMX TGrid that will contain TCalendarEdit and TTimeEdit instances in every cell. Everything works fine except the proper processing of changes done in these child controls.
type
TFMTValue<T> = record
FieldValue: T;
Modified: boolean;
Appended: boolean;
Deleted: boolean;
end;
TDateTimeCell = class(TStyledControl)
private
FDate_Time: TFMTValue<TDateTime>;
procedure SetDateTime(const Value: TFMTValue<TDateTime>);
function GetDateTime: TFMTValue<TDateTime>;
protected
procedure SetData(const Value: TValue); override;
public
property Date_Time: TFMTValue<TDateTime> read GetDateTime write SetDateTime;
...
end;
...
function TDateTimeCell.GetDateTime: TFMTValue<TDateTime>;
begin
FDate_Time.Modified := (FDate_Time.Modified) or
(FDate_Time.FieldValue <> FCalendarEdit.Date +
+ FTimeEdit.Time);
FDate_Time.FieldValue := FCalendarEdit.Date + FTimeEdit.Time;
Result := FDate_Time;
end;
procedure TDateTimeCell.SetData(const Value: TValue);
begin
Date_Time := Value.AsType<TFMTValue<TDateTime>>;
inherited SetData(TValue.From<TDateTime>(FDate_Time.FieldValue));
ApplyStyling;
end;
procedure TDateTimeCell.SetDateTime(const Value: TFMTValue<TDateTime>);
begin
FDate_Time := Value;
FCalendarEdit.Date := DateOf(FDate_Time.FieldValue);
FTimeEdit.Time := TimeOF(FDate_Time.FieldValue);
FDate_Time.FieldValue:=FCalendarEdit.Date + FTimeEdit.Time; //this line helps but not in all cases
end;
The idea is that data is assigned via TGrid OnGetValue event handler. Both date and time are displayed. The user activity is catched and Modified flag is set. The problem is that this flag is set to true sometimes even without any user activities. I suspect it is due to the rounding of time part of TDateTime. There are no other ways the code assignes values to FCalendarEdit.Date and FTimeEdit.Time.
How can I properly compare the data stored in FCalendarEdit.Date and FTimeEdit.Time with that stored in FDate_Time.FieldValue?
Appended
Setting the flag in this way does not resolve the issue.
FDate_Time.Modified := (FDate_Time.Modified) or
(DateOf(FDate_Time.FieldValue) <> FCalendarEdit.Date) or
(TimeOf(FDate_Time.FieldValue)<> FTimeEdit.Time);
Appended 2. On a valued advice of #Ken-White.
If we replace the comparison line by
FDate_Time.Modified := (FDate_Time.Modified) or
(not SameDateTime(FDate_Time.FieldValue,
FCalendarEdit.Date + FTimeEdit.Time));
It works fine. So the TDataTime comparison must be done by this function only.
TDateTime is of type Double, which means it's a floating point value, and therefore is subject to the usual issues of binary representation when doing comparisons for equality without specifying an acceptable delta (difference)..
Specifically for TDateTime values, you can use DateUtils.SameDateTime to compare equality down to less than one millisecond:
FDate_Time.Modified := (FDate_Time.Modified) or
(not SameDateTime(FDate_Time.FieldValue,
FCalendarEdit.Date + FTimeEdit.Time));
There is a bug in TCalendarEdit (a few actually) which is the underlying cause of your problem, but you can fix it with only a small change to your code.
The Problem
The TCalendarEdit makes a number of crucial errors when it applies a new Date value.
A TDate type is actually just an ordinary TDateTime in which you are supposed to ignore the time portion. Similarly a TTime is a TDateTime where you are supposed to ignore the date portion.
But you have to use these types correctly in your code - there is nothing that magically makes a TTime ignore the date or a TDate ignore the time.
For example, if you examine the constructor of the TCalendarEdit, you will see that it initialises the internal date/time to the current system date and time using Now, but truncates this to eliminate the time element:
Date := Trunc(Now);
So far so good.
But when you apply a new value via the Date property, it performs the following (simplified):
if Date <> Value then
FDateTime := Value + Time;
Both of these lines of code contain serious bugs:
It compares the Date (property returning the Date value of the control) with the Value being assigned - including any time value in that date/time. It should instead compare only the date part of the Value.
When assigning the new value to the internal date/time it adds Time to the Value you specified.
The first bug results in unnecessary changes to the internal property but is otherwise relatively innocuous. The second bug however is far more serious and is what is causing your problem.
I presume that the intention of the author of the control was to leave the time portion of the internal date/time value unchanged. However, the Value is not truncated, so it retains the time value specified in the assignment to the property. To make matters even worse, there is no Time property on this control, so this in fact adds the current system time to whatever time is specified in Value.
How This Affects Your Code and Test Case
Since your test case involved a time of midday - 12 hours - the result is that when you run this code in the afternoon, the Date of your TCalendarEdit is actually set to 25-Sep-2015 + 12 hours + the time when the control was initialised.
If you run the code in the morning, it seems to work because the time added results in a value that is still on the 25th Sep.
But when you run the code in the afternoon, the 12 hours are added to the current time and so the date rolls over to the next day!
With a more helpful diagnostic error message, or if you had inspected the properties in your code via the debugger, you would have seen this occurring.
DT := EncodeDate(2015, 9, 25) + EncodeTime(12, 0, 0, 0);
CalendarEdit1.Date := DT;
ShowMessage(DateTimeToString(CalendarEdit1.Date));
// When executed at e.g. 9am, displays: 25 Sep 2015
// When executed at e.g. 1pm, displays: 26 Sep 2015
So the reason your comparison then fails is because the date is actually completely different!
If you had tried simply using SameDateTime() for the comparison it may have appeared to have worked if you tested it in the morning but your problem would have returned in the afternoon !!
The Solution
You can work around these bugs in TCalendarEdit by ensuring that you respect the intended use of the property values yourself, assigning only those parts of the DT date/time value as appropriate in each case:
TimeEdit1.Time := TimeOf(DT);
CalendarEdit1.Date := DateOf(DT);
Although not strictly necessary in the case of the TTimeEdit, this will prevent these bugs in TCalendarEdit from causing these problems and makes it clear in your code that you are aware of what is required (consider it self documenting code if you like). :)
If you do not have TimeOf() and DateOf() functions in your version of Delphi, then the following is equivalent:
TimeEdit1.Time := DT - Trunc(DT);
CalendarEdit1.Date := Trunc(DT);
You could of course write your own versions of TimeOf() and DateOf() based on this, to make the intention clearer.
NOTE
There are precision complications arising from the floating point nature of date/time values in Delphi that could cause problems with direct comparisons with some specific values of date and time and for that reason it is highly recommended that you use the SameDateTime() function for performing such comparisons.
But this was absolutely not the cause of your problem in this case and SameDateTime() does not solve your problem.
SameDateTime() eliminates problems arising from differences in date/time values of less than 1 millisecond. The difference in this case was 24 hours!
Worth noting is that the TCalendarEdit control was deprecated in XE7 and has been removed entirely from XE8.

Calculating age in Delphi

iAge := 2013 - StrToInt(sJ) ;
if iAge< 18
then
begin
bDatum := False ;
ShowMessage('You must be older than 18!') ;
Exit ;
end; //IF
If you use this it will just take the current year and the year the user typed in and test if he is 18 or not, I'm looking for a way to to calculate the age of the user using the month and day as well but it was to no avail, so I was hoping to get some help from Stackoverflow.
Help will be much appreciated!
The simplest way to think about this is that if you know the date when the person was born, you simply need to work out whether or not their 18th birthday has passed.
Ask the user for their date of birth. Get that in the form of day, month and year.
Add 18 to the year.
Convert that into a date with EncodeDate.
Compare that to today's date, which can be found by calling Date.
The code would look like this:
if EncodeDate(dobYear + 18, dobMonth, dobDay) > Date then
ShowMessage('Too young');
Now, this almost works, but it will fail if the person was born on a leap day, that is the 29th February. You'd need to add a special case to handle that. For example, a crude approach would be like this:
if (dobMonth=2) and (dobDay=29) then
dobDay := 28;
if EncodeDate(dobYear + 18, dobMonth, dobDay) > Date then
ShowMessage('Too young');
Looks like I've just re-invented the wheel here. Always a bad idea. You can call IncYear from DateUtils to get this done, and not have to worry about leap days.
if IncYear(EncodeDate(dobYear, dobMonth, dobDay), 18) > Date then
ShowMessage('Too young');
Delphi stores the date as a real number - you must use the Extended type
function Age(TheDate: TDate): integer;
var
I: Extended; // Extended is a special type of real variable
begin
I := Now() - TheDate; // Now() is todays date in TDate format
// The type conflict is apparently ignored
Result := round(I/365.25);
If Result > 110 then Result := 0; // this copes with a missing date string
end; // Start date in Delphi is 30/12/1899
{============================================================}
I think this is simpler:
isUnder18 := YearsBetween(DOB, Now()) < 18;
YearsBetween

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.
Solution found: CRON Scheduler
Thank you LU RD and Runner.
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.
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.

Change today's date, advancing one month and setting the systemtime

I would like a code sample for a function that takes a tDateTime and an integer as input and sets the system time using setlocaltime after advancing that tDateTime by (int) months. The time should stay the same.
pseudo code example
SetNewTime(NOW,2);
The issues I'm running into are rather frustrating. I cannot use incmonth or similar with a tDateTime, only a tDate, etc.
Below is a complete command-line program that works for me. Tested in Delphi 5 and 2007. Why do you say IncMonth does not work for TDateTime?
program OneMonth;
{$APPTYPE CONSOLE}
uses
SysUtils,
Windows,
Messages;
procedure SetLocalSystemTime(settotime: TDateTime);
var
SystemTime : TSystemTime;
begin
DateTimeToSystemTime(settotime,SystemTime);
SetLocalTime(SystemTime);
//tell windows that the time changed
PostMessage(HWND_BROADCAST,WM_TIMECHANGE,0,0);
end;
begin
try
SetLocalSystemTime(IncMonth(Now,1));
except on E:Exception do
Writeln(E.Classname, ': ', E.Message);
end;
end.
IncMonth should work with a TDateTime:
function IncMonth ( const StartDate : TDateTime {; NumberOfMonths : Integer = 1} ) : TDateTime;
Keep in mind a TDate is really just a TDateTime that by convention your ignore the fraction on.
Based on your pseudocode:
procedure SetNewTime(aDateTime: TDateTime; aMonths: Integer);
var
lSystemTime: TSystemTime;
begin
DateTimeToSystemTime(aDateTime, lSystemTime);
Inc(lSystemTime.wMonth, aMonths);
setSystemTime(lSystemTime);
end;
setSystemTime uses UTC time, so you have to adjust for your time zone. The bias is the number of minutes your machine's timezone differs from UTC. This adjusts the date properly on my system:
procedure SetNewTime(aDateTime: TDateTime; aMonths: Integer);
var
lSystemTime: TSystemTime;
lTimeZone: TTimeZoneInformation;
begin
GetTimeZoneInformation(lTimeZone);
aDateTime := aDateTime + (lTimeZone.Bias / 1440);
DateTimeToSystemTime(aDateTime, lSystemTime);
Inc(lSystemTime.wMonth, aMonths);
setSystemTime(lSystemTime);
end;
There isn't enough information to provide a definitive answer to your question.
Consider what you would want to happen if the day of the current month doesn't exist in your future month. Say, January 31 + 1 month. (7 months of the year have 31 days and the rest have fewer.) You have the same problem if you increment the year and the starting date is February 29 on a leap year. So there can't be a universal IncMonth or IncYear function that will work consistantly on all dates.
For anyone interested, I heartily recommend Julian Bucknall's article on the complexities that are inherent in this type of calculation
on how to calculate the number of months and days between two dates.
The following is the only generic date increment functions possible that do not introduce anomolies into generic date math. But it only accomplishes this by shifting the responsibility back onto the programmer who presumably has the exact requirements of the specific application he/she is programming.
IncDay - Add a or subtract a number of days.
IncWeek - Add or subtract a number of weeks.
But if you must use the built in functions then at least be sure that they do what you want them to do. Have a look at the DateUtils and SysUtils units. Having the source code to these functions is one of the coolest aspects of Delphi. Having said that, here is the complete list of built in functions:
IncDay - Add a or subtract a number of days.
IncWeek - Add or subtract a number of weeks.
IncMonth - Add a or subtract a number of months.
IncYear - Add a or subtract a number of years.
As for the second part of your question, how to set the system date & time using a TDatetime, the following shamelessly stolen code from another post will do the job once you have a TDatetime that has the value you want:
procedure SetSystemDateTime(aDateTime: TDateTime);
var
lSystemTime: TSystemTime;
lTimeZone: TTimeZoneInformation;
begin
GetTimeZoneInformation(lTimeZone);
aDateTime := aDateTime + (lTimeZone.Bias / 1440);
DateTimeToSystemTime(aDateTime, lSystemTime);
setSystemTime(lSystemTime);
end;

Resources