Yahia
Yahia

Reputation: 21

How to add only some days to date-time variable?

When I need to increase date 1/1/2016 by 7 days, I can simply do:

IncDay(myDate, 7); // Result 8/1/2016

What do I do, if I need to ignore some days (e.g. Saturdays), so the Result is 10/1/2016 ?

Upvotes: 0

Views: 1867

Answers (4)

Disillusioned
Disillusioned

Reputation: 14832

You've been given a couple of answers for the special case of excluding a single specific weekday. This is not particularly flexible.

You could try for implementing a function that takes a set of weekdays that are 'valid' as one of its parameters. The number of days to increment the date by is approximately AIncByDays * 7 / NoOfDaysInSet. But it gets rather tricky adjusting the result correctly for valid/invalid weekdays 'near' the start date. Even after all this complexity, you still wouldn't have a way to deal with special dates, like public holidays.

Fortunately there's different approach that's much simpler to implement and far more flexible. It's only drawback is that it's inefficient for 'large' increments.

  • The general approach is to increment 1 day at a time.
  • And on each increment check the validity of the new date.
  • Only if the new date is valid, reduce the increment counter by 1.
  • Repeat the above in a loop until the increment counter is reduced to 0.

The following uses a callback function to check the validity of each date.

type
  TValidDateFunc = function (ADate: TDateTime): Boolean;

function IncValidDays(AStartDate: TDateTime; AIncBy: Integer; AIsValid: TValidDateFunc): TDateTime;
var
  LIncDirection: Integer;
begin
  // Support dec using negative AIncBy
  if AIncBy >= 0 then
    LIncDirection := 1
  else
    LIncDirection := -1;

  Result := AStartDate;
  while (AIncBy <> 0) do
  begin
    IncDay(Result, LIncDirection);
    if (AIsValid(Result)) then
      Dec(AIncBy, LIncDirection);
  end;
end;

Now you can simply write whatever function you desire to determine a valid date and use it in the above function. E.g.

function DateNotSaturday(ADate: TDateTime): Boolean;
begin
  Result := (DayOfTheWeek(ADate) <> DaySaturday);
end;


NewDate := IncValidDays(SomeDate, 10, DateNotSaturday);

Note that it now becomes quite easy to write a function that uses only work days that aren't public holidays. E.g.

function IsWorkDay(ADate: TDateTime): Boolean;
var
  LDay, LMonth, LYear: Word;
begin
  DecodeDate(ADate, LYear, LMonth, LDay);

  Result := True;
  Result := Result and (DayOfTheWeek(ADate) <> DaySaturday);
  Result := Result and (DayOfTheWeek(ADate) <> DaySunday);
  Result := Result and ((LDay <> 1) or (LMonth <> 1)); //Excludes New Years day.
  ...
end;

The biggest advantage of this approach is that you don't have to deal with the risk of 'double-ignoring' a date because it's both a weekend day and a public holiday.

NOTE: In recent versions of Delphi you could replace the callback function with an anonymous method.

Upvotes: 0

fantaghirocco
fantaghirocco

Reputation: 4868

This adds a day to the calculation for each Saturday found in the ANumberOfDays range:

{.$DEFINE UNCLEAR_WHAT_YOU_R_ASKING}

function IncDayIgnoringSaturdays(const AValue: TDateTime; const ANumberOfDays: Integer = 1): TDateTime;
var
  i, j: Integer;
begin
  i := ANumberOfDays;
  j := 0;
  Result := AValue;
  while i > 0 do begin
    Result := IncDay(Result);
    if DayOfTheWeek(Result) = DaySaturday then
      Inc(j);
    Dec(i);
  end;
  Result := IncDay(Result, j);

  {$IFDEF UNCLEAR_WHAT_YOU_R_ASKING}
  if DayOfTheWeek(Result) = DaySaturday then
    Result := IncDay(Result);
  {$ENDIF}
end;

begin
  WriteLn(DateTimeToStr(IncDayIgnoringSaturdays(StrToDateTime('1/1/2016'), 7)));
  WriteLn(DateTimeToStr(IncDayIgnoringSaturdays(StrToDateTime('1/1/2016'), 14)));
  ReadLn;
end.

EDIT
The above may return a date on Saturday or not, depending on the UNCLEAR_WHAT_YOU_R_ASKING conditional define.

Upvotes: 2

David Heffernan
David Heffernan

Reputation: 612784

Apparently, based on the somewhat cryptic comments, you wish to increment a date by a number of days, excluding Saturday. You can do that by making use of the the DayOfTheWeek function in DateUtils. This will tell you which day of the week a specified date falls on.

So your function is something like this:

function IncExcludingSaturday(FromDate: TDateTime; IncDays: Integer): TDateTime;
begin
  Assert(IncDays >= 0);

  Result := FromDate;
  if DayOfTheWeek(Result) = DaySaturday then
    Result := IncDay(Result);

  while IncDays > 0 do
  begin
    Result := IncDay(Result);
    if DayOfTheWeek(Result) = DaySaturday then
      Result := IncDay(Result);

    dec(IncDays);
  end;
end;

This is a rather crude way to achieve your goal. You can find more interesting ideas here: AddBusinessDays and GetBusinessDays

Now, in the question you suggest that 7 days from 01/01/2016, excluding Saturdays, takes you to 09/01/2016. But that is surely wrong since that date is a Saturday. The correct answer is surely 10/01/2016 which is a Sunday. In other words we need to skip over two Saturdays, on the 2nd and the 9th.

Upvotes: 3

Torbins
Torbins

Reputation: 2216

Determine the day of the week using DayOfTheWeek function. Now you know when will be next Saturday and whether it will get inside your period. If your period is larger than one week, then you can multiple number of Saturdays in your period by a number of full weeks. If your period is larger than 7 weeks, then you will have to add one more day for each 7 weeks.

Upvotes: -1

Related Questions