I'm looking for an algorithm that will help calculate a workday working time length. It would have an input date range and then allow subtracting partially or completely intersecting time range slices from that date range and the result would be the number of minutes (or the fraction/multiple of a day) left in the original date range, after subtracting out the various non-working time slices.
For Example:
Input date range: 1/4/2010 11:21 am - 1/5/2010 3:00 pm
Subtract out any partially or completely intersecting slices like this:
Remove all day Sunday
Non-Sundays remove 11:00 - 12:00
Non-Sundays remove time after 5:00 pm
Non-Sundays remove time before 8:00 am
Non-Sundays remove time 9:15 - 9:30 am
Output: # of minutes left in the input date range
I don't need anything overly-general. I could hardcode the rules to simplify the code. If anyone knows of sample code or a library/function somewhere, or has some pseudo-code ideas, I'd love something to start with. I didn't see anything in DateUtils, for example. Even a basic function that calculates the number of minutes of overlap in two date ranges to subtract out would be a good start.
Interesting requirements... But not so hard to achieve in a "hardcoded" way.
Enjoy
uses
Math, DateUtils;
function TimeRangeOverlap(Range1Start, Range1Finish, Range2Start, Range2Finish : TDateTime) : TDateTime;
begin
Result := Max(Min(Range1Finish, Range2Finish) - Max(Range1Start, Range2Start), 0);
end;
function TotalTime(Start, Finish : TDateTime) : TDateTime;
var DayStart, DayFinish : TDateTime;
I : Integer;
begin
Result := 0;
for I := Floor(Start) to Floor(Finish) do //For each day in range;
begin
if DayOfWeek(I) = 1 then CONTINUE; //Remove all sundays.
DayStart := Max(Start, I); //Midnight on the start of the day, except on the first day;
DayFinish := Min(Finish, I + 1 - OneMillisecond); //Midnight minus 1 msec of the following day.
Result := Result + DayFinish - DayStart;
//Adjustment part
Result := Result - TimeRangeOverlap(DayStart, DayFinish, I + EncodeTime(11,00,00,00), I + EncodeTime(12,00,00,00)); //Remove time between 11:00 and 12:00
Result := Result - TimeRangeOverlap(DayStart, DayFinish, I + EncodeTime(17,00,00,00), I + 1); //Remove time after 5:00 PM
Result := Result - TimeRangeOverlap(DayStart, DayFinish, I , I + EncodeTime(8,00,00,00)); //Remove time after 8:00 AM
Result := Result - TimeRangeOverlap(DayStart, DayFinish, I + EncodeTime(9,15,00,00), I + EncodeTime(9,30,00,00)); //Remove time between 9:15 and 9:30
end;
end;
Simply use the routines in DateUtils and elsewhere to implement the rules you yourself describe.
If you want an idea to get you started, off the cuff I'd suggest calculating the minutes in your input range (remembering that a TDateTime value is a floating point where the integral value is the number of days and the fractional part is a portion of a day) then incrementing thru the range and for each integer step (day) subtract the appropriate number of minutes for that day from the total based on your rules and the start/end-time of the first/last days in the range when those days are encountered in the range (intervening days being of course complete 24 hour periods).
Really to provide any more detailed "outline" one might as well implement the complete routine for you, which I might do if I had more time, which sadly I don't right now.
Related
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
I would like to add seconds to a TDateTime variable, so that the result is the top of the minute. For example, if it's 08:30:25, I want change the TDateTime variable to store 08:31:00.
I see that TDateTime has a Decode function, which I could use. There isn't, however, an encode function to put the altered time back into a TDateTime variable.
Using DateUtils it's possible to do it like this:
Uses
DateUtils;
var
Seconds : Word;
Seconds := SecondOfTheMinute(MyTime); // Seconds from last whole minute
// Seconds := SecondOf(MyTime); is equivalent to SecondOfTheMinute()
if (Seconds > 0) then
MyTime := IncSecond(MyTime,60 - Seconds);
There sure is, at least in the recent versions - see the DateUtils unit, especially all the Recode* routines and EncodeDateTime. The DateUtils unit is already available in Delphi 2010, perhaps even in earlier version.
Theory
The TDateTime data type represents number of days since 30 Dec 1899 as a real number. That is, the integral part of TDateTime is an amount of whole days, and the fractional part represents a time of day.
Practical
Therefore, your problem could be solved using simple arithmetics:
var
Days: TDateTime;
Mins: Extended; { widen TDateTime's mantissa by 11 bits to accommodate division error }
begin
Days := Date + StrToTime('08:30:25');
Writeln(DateTimeToStr(Days));
Mins := Days * 24 * 60 ; // compute minutes
Mins := Math.Ceil(Mins); // round them up
Days := Mins / (24 * 60); // and back to days
{ or as simple and concise expression as: }
// Days := Ceil(Days * MinsPerDay) / MinsPerDay;
Writeln(DateTimeToStr(Days));
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);
This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 10 years ago.
I have this code that only runs twice with the variables
DateStart := 27-01-2013
DateStop := 31-03-2013
I think it should run with the results of 31-01-2013, 28-02-2013 and 31-3-2013
But I only get 2 results
I am quite sure I have been gazing to much and can't see the problem
begin
DateStart := EndOfTheMonth(DateStart);
while DateStart <= DateStop do
begin
FsFutureCreate(DateStart, cxDebit.Value, cxKredit.Value, aAccount, aType, aStore, aCity, txtText.Text, lRecord);
DateStart := EndOfTheMonth(IncMonth(DateStart));
end;
end;
Either FsFutureCreate (which you never told us what it is) has some side-effects, or you have some issue with floating-point fuzz. As you know, date and time values are doubles, and so comparisons like <= are dangerous. (Especially if you disregard the time part, as my analysis below shows.)
The second one is the more likely one. I just tried
procedure TForm1.FormCreate(Sender: TObject);
var d: TDate; d2: TDate;
begin
d := EncodeDate(2013, 01, 31);
d := IncMonth(d); // 2013-02-28
d := EndOfTheMonth(d); // 2013-02-28
d := IncMonth(d); // 2013-03-28
d := EndOfTheMonth(d); // 2013-03-31
d2 := EncodeDate(2013, 03, 31);
// d is now 2013-03-31 23:59:59
// d2 is now 2013-03-31 00:00:00
ShowMessage(BoolToStr(d <= d2, true));
end;
and got false, as one would expect. Hence, the problem, in this case, is that the EndOfTheMonth function also sets the time to the last second (or millisec) of the day. But even if this wasn't the case, doing comparisons using = is dangerous when it comes to floating-point values.
To fix your comparison, do
CompareDate(d, d2) <= 0
instead of
d <= d2.
I leave it as an exercise to find out, using the documentation, why this works and is robust.
The reason is that EndOfTheMonth gives you the last day of the month, but also a time corresponding to the end of the day. But your DateStop is probably the very beginning of the day.
If you print out the raw values of DateStart and DateStop in the loop you see this:
41333.9999999884 41364
41364.9999999884 41364
So, looking at the final line you can see we have a time of 23:59 on day 41364.
I'd fix this by working with pure dates and not letting time get in the way of things. Change the incrementing code to remove the time part of the date. And I'd also be explicit about doing the same in the loop test.
while DateOf(DateStart) <= DateOf(DateStop) do
begin
....
DateStart := DateOf(EndOfTheMonth(IncMonth(DateStart)));
end;
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;