Establecer la hora programáticamente usando C #
Pregunta
¿Cuál es la mejor manera de establecer la hora en una máquina remota de forma remota? La máquina está ejecutando Windows XP y está recibiendo la nueva hora a través de una llamada de servicio web. El objetivo es mantener las máquinas remotas sincronizadas con el servidor. El sistema está bloqueado para que nuestro servicio web sea el único acceso, por lo que no puedo usar un servidor horario en cada máquina remota.
Solución
Yo usaría las capacidades de tiempo de Internet incorporadas de Windows. Puede configurar un servidor de tiempo en su servidor, haga que obtenga tiempo de un segundo nivel timeserver, y haga que todas las máquinas de sus clientes obtengan tiempo de ello.
Antes he estado en el camino de la hora de configuración de la aplicación.
Otros consejos
Esta es la llamada a la API de Win32 para configurar la hora del sistema:
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEMTIME {
public short wYear;
public short wMonth;
public short wDayOfWeek;
public short wDay;
public short wHour;
public short wMinute;
public short wSecond;
public short wMilliseconds;
}
[DllImport("kernel32.dll", SetLastError=true)]
public static extern bool SetSystemTime(ref SYSTEMTIME theDateTime );
Sin embargo, no estoy exactamente seguro de cómo se resolvería la seguridad, de modo que podría ejecutar esa función en el cliente.
Puede obtener muchos más detalles sobre la configuración de la hora del sistema en PInvoke .
La forma de consultar a una máquina de la red para conocer la hora del sistema es NetRemoteTOD .
Aquí hay un código para hacerlo en Delphi (un ejemplo de uso se publica a continuación).
Dado que se basa en las llamadas a la API de Windows, no debería ser muy diferente en C #.
unit TimeHandler;
interface
type
TTimeHandler = class
private
FServerName : widestring;
public
constructor Create(servername : widestring);
function RemoteSystemTime : TDateTime;
procedure SetLocalSystemTime(settotime : TDateTime);
end;
implementation
uses
Windows, SysUtils, Messages;
function NetRemoteTOD(ServerName :PWideChar; var buffer :pointer) : integer; stdcall; external 'netapi32.dll';
function NetApiBufferFree(buffer : Pointer) : integer; stdcall; external 'netapi32.dll';
type
//See MSDN documentation on the TIME_OF_DAY_INFO structure.
PTime_Of_Day_Info = ^TTime_Of_Day_Info;
TTime_Of_Day_Info = record
ElapsedDate : integer;
Milliseconds : integer;
Hours : integer;
Minutes : integer;
Seconds : integer;
HundredthsOfSeconds : integer;
TimeZone : LongInt;
TimeInterval : integer;
Day : integer;
Month : integer;
Year : integer;
DayOfWeek : integer;
end;
constructor TTimeHandler.Create(servername: widestring);
begin
inherited Create;
FServerName := servername;
end;
function TTimeHandler.RemoteSystemTime: TDateTime;
var
Buffer : pointer;
Rek : PTime_Of_Day_Info;
DateOnly, TimeOnly : TDateTime;
timezone : integer;
begin
//if the call is successful...
if 0 = NetRemoteTOD(PWideChar(FServerName),Buffer) then begin
//store the time of day info in our special buffer structure
Rek := PTime_Of_Day_Info(Buffer);
//windows time is in GMT, so we adjust for our current time zone
if Rek.TimeZone <> -1 then
timezone := Rek.TimeZone div 60
else
timezone := 0;
//decode the date from integers into TDateTimes
//assume zero milliseconds
try
DateOnly := EncodeDate(Rek.Year,Rek.Month,Rek.Day);
TimeOnly := EncodeTime(Rek.Hours,Rek.Minutes,Rek.Seconds,0);
except on e : exception do
raise Exception.Create(
'Date retrieved from server, but it was invalid!' +
#13#10 +
e.Message
);
end;
//translate the time into a TDateTime
//apply any time zone adjustment and return the result
Result := DateOnly + TimeOnly - (timezone / 24);
end //if call was successful
else begin
raise Exception.Create('Time retrieval failed from "'+FServerName+'"');
end;
//free the data structure we created
NetApiBufferFree(Buffer);
end;
procedure TTimeHandler.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;
Y aquí está el ejemplo de uso:
procedure TfrmMain.SynchLocalTimeWithServer;
var
tod : TTimeHandler;
begin
tod := TTimeHandler.Create(cboServerName.Text);
try
tod.SetLocalSystemTime(tod.RemoteSystemTime);
finally
FreeAndNil(tod);
end; //try-finally
end;
Aquí está la rutina que he estado usando durante años para leer el valor de DateTime
de nuestro nuestro servidor SQL Server (usando el tiempo de archivo), convertirlo a un SYSTEMTIME
que está configurado en la PC.
Esto funciona para PC y dispositivos con Windows Mobile.
Puede llamarse en cualquier momento que esté llamando a su servidor SQL.
public class TimeTool {
private static readonly DateTime NODATE = new DateTime(1900, 1, 1);
#if PocketPC
[DllImport("coredll.dll")]
#else
[DllImport("kernel32.dll")]
#endif
static extern bool SetLocalTime([In] ref SYSTEMTIME lpLocalTime);
public struct SYSTEMTIME {
public short Year, Month, DayOfWeek, Day, Hour, Minute, Second, Millisecond;
/// <summary>
/// Convert form System.DateTime
/// </summary>
/// <param name="time">Creates System Time from this variable</param>
public void FromDateTime(DateTime time) {
Year = (short)time.Year;
Month = (short)time.Month;
DayOfWeek = (short)time.DayOfWeek;
Day = (short)time.Day;
Hour = (short)time.Hour;
Minute = (short)time.Minute;
Second = (short)time.Second;
Millisecond = (short)time.Millisecond;
}
public DateTime ToDateTime() {
return new DateTime(Year, Month, Day, Hour, Minute, Second, Millisecond);
}
public static DateTime ToDateTime(SYSTEMTIME time) {
return time.ToDateTime();
}
}
// read SQL Time and set time on device
public static int SyncWithSqlTime(System.Data.SqlClient.SqlConnection con) {
SYSTEMTIME systemTime = new SYSTEMTIME();
DateTime sqlTime = NODATE;
string sql = "SELECT GETDATE() AS [CurrentDateTime]";
using (System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand(sql, con)) {
try {
cmd.Connection.Open();
System.Data.SqlClient.SqlDataReader r = cmd.ExecuteReader();
while (r.Read()) {
if (!r.IsDBNull(0)) {
sqlTime = (DateTime)r[0];
}
}
} catch (Exception) {
return -1;
}
}
if (sqlTime != NODATE) {
systemTime.FromDateTime(sqlTime); // Convert to SYSTEMTIME
if (SetLocalTime(ref systemTime)) { //Call Win32 API to set time
return 1;
}
}
return 0;
}
}
Probablemente también podrías hacer esto en un archivo por lotes usando alguna combinación de
TIME
para ajustar la hora, y
net time \\server_name
para recuperar la hora de un servidor.