views:

755

answers:

5

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.

+5  A: 

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.
JosephStyons
Bruce, your code was really clean but Kogus's code correctly maintained the time. Thanks to both of you!
Bruce the Hoon
That's fair. I left something important out.
Bruce McGee
+1  A: 

Based on your pseudocode:

procedure SetNewTime(aDateTime: TDateTime; aMonths: Integer);
var
  lSystemTime: TSystemTime;
begin
  DateTimeToSystemTime(aDateTime, lSystemTime);
  Inc(lSystemTime.wMonth, aMonths);
  setSystemTime(lSystemTime);
end;
Bruce McGee
Bruce,I'm really liking the simplicity here but Im not having luck with the time. The days increment and decrement as expected, but the time of day gets messed up when I do so... Ideas?
Bruce the Hoon
Oops... Added a new response for code formatting.
Bruce McGee
+1  A: 

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.

Jim McKeeth
+1  A: 

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;
Bruce McGee
A: 

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;