Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Zeitumstellung "wegcodieren"


D. Annies - Sa 20.11.10 10:21
Titel: Zeitumstellung "wegcodieren"
Hi, Delpher,

jedesmal, wenn auf Sommer- Winterzeit umgestellt wird, werden (bisher) viele Dateien, deren Zeit sich genau um diese eine Stunde unterscheidet, mitkopiert, obwohl das ja unnötig ist.
Ich vermute, dass ich eine AND-Bedingung an der markierten Stelle ergänzen muss, aber welche und wie?

Der bisherige, funktionierende Code ist:



Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
if FileDateToDateTime(FileAge(work_filename)) <> FileDateToDateTime(FileAge(home_filename))

// and  ?????

 then
            begin
              erg := xmessagedlg('ArchivBit wurde gesetzt' +#13 +
                       'Soll    ' + work_filename + ' '+#13+ datetimetostr(filedatetodatetime(FileAge(work_filename))) +#13+
                       'durch '   + home_filename + ' '+#13+ datetimetostr(filedatetodatetime(FileAge(home_filename))) +#13+
                       ' ersetzt werden?', mtConfirmation, [mbYes, mbno, mbcancel], ['ja''nein''Abbrechen'], self.font);
                                                             //  ???
              case erg of mryes    : CopyFile(PChar(home_filename), PChar(work_filename), FALSE);
                          mrcancel : raus := true;
              end;
            end;


Danke, Detlef


F34r0fTh3D4rk - Sa 20.11.10 12:53

Ich weiß zwar nicht, was der Code tut (bzw. in welchen Intervallen er ausgeführt wird), aber würde es nicht reichen, zu prüfen, ob die Differenz größer als eine Stunde ist? Oder würde das den allgemeinen Fall beeinflussen?


D. Annies - Sa 20.11.10 14:12

Ja, das wäre genau richtig und ok, wenn ich sagen könnte: Wenn die Differenz (genau) eine Stunde beträgt, dann
nicht speichern [weil ja nichts verändert ist], nur neuer Zeitstempel gesetzt]

Gruß, Detlef


F34r0fTh3D4rk - Sa 20.11.10 14:32

Das gilt ja aber nur bei der Umstellung auf die Sommerzeit, bei der Winterzeit ist ja eine Stunde doppelt vorhanden.
Du könntest prüfen, um welches Datum es sich handelt. Wikipedia sagt:

Zitat:
Die Umstellung von der Normal- auf die Sommerzeit findet am letzten Sonntag im März um 1 Uhr UTC, also in der mitteleuropäischen Zeitzone von 2 Uhr MEZ auf 3 Uhr MESZ, statt.
Die Umstellung von der Sommer- auf die Normalzeit findet am letzten Sonntag im Oktober um 1 Uhr UTC, also in der mitteleuropäischen Zeitzone von 3 Uhr MESZ auf 2 Uhr MEZ, statt.

Daran könntest du eine eventuelle Umstellung erkennen.


Kha - Sa 20.11.10 14:48

*hust* UTC *hust* ?
http://msdn.microsoft.com/en-us/library/ms724290%28v=VS.85%29.aspx hat folgendes geschrieben:
The NTFS file system stores time values in UTC format, so they are not affected by changes in time zone or daylight saving time.
Solche fürchterlichen Hacks solltest du also umgehen können, indem du die FileAge-Werte direkt vergleichst (ich gehe mal davon aus, dass Delphi die Umrechnung UTC->Local Time erst in FileDateToDateTime durchführt) :) .

/edit: Aber wie das Problem überhaupt entstehen kann, wenn beide Dateien gleichzeitig abgefragt werden, kann ich auch nicht erklären.


D. Annies - Sa 20.11.10 16:16

Und wie könnte es gehen?
Gruß, Detlef


Delete - Sa 20.11.10 16:39

Was willst du machen? Wenn du prüfen willst, ob sich eine Datei geändert hat, ist ein Hash besser geeignet.


D. Annies - Sa 20.11.10 19:13

Hi, Luckie,
aha, ein Hash - klingt gut, aber nie gehört. Na, da brauche ich noch weitere Informationen
Gruß, Detlef


D. Annies - So 21.11.10 10:50

Ich habe es jetzt so gemacht:


Delphi-Quelltext
1:
2:
3:
if (FileDateToDateTime(FileAge(work_filename)) <> FileDateToDateTime(FileAge(home_filename))) and
                            (fileage(work_filename) + 3600 <> FileAge(home_filename)) and
                            (fileage(work_filename)        <> FileAge(home_filename) + 3600then


Was meint ihr dazu?
Gruß, Detlef


jaenicke - So 21.11.10 11:14

Gibt es einen Grund warum du erstens teilweise ins Delphi-Datumsformat umwandelst und zweitens den Wert ständig wieder neu abfragst (also FileAge mehrfach für die selbe Datei aufrufst)?

Zudem ist FileAge veraltet und bringt zudem genau das genannte Problem mit der Sommerzeit mit sich, da du damit die Zeit explizit erst in die lokale Zeit umwandelst...

Nimm doch einfach FindFirstFile und benutze direkt ftLastWriteTime aus der entsprechenden TWin32FindData Struktur. Das ist erstens schneller und zweitens hast du dann direkt den UTC Zeitstempel, wenn es sich wie bereits geschrieben um ein NTFS Laufwerk handelt.
Eine Stunde sollte dabei 36.000.000.000 entsprechen (da der Zeitstempel die Anzahl der 100 Nanosekunden-Intervalle seit dem 01.01.1601 um 12 Uhr enthält).


D. Annies - So 21.11.10 15:12

Danke, Sebastian,

was ich gefunden habe ist der folgende Code:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
procedure BuildFileList(aSourceDir, aDestDir: string; aSource, aFiles:
  TStringList; aMaxFileAge: Integer); cdecl;
var                                             // Welche Units soll ich einbinden?
  sFileName, sSourceFileName: string;
  hSearch: THandle;
  rSourceFindData, rDestFindData: TWIN32FindData;
  ftCreationTime, ftLastWriteTime: TFileTime;
begin
  // einmal reicht ... muß ja nicht bei jeder Datei einzeln
  aSourceDir := IncludeTrailingPathDelimiter(aSourceDir);
  aDestDir := IncludeTrailingPathDelimiter(aDestDir);
  aFiles.BeginUpdate;
  try
    for sFileName in aSource do
    begin
      sSourceFileName := aSourceDir + sFileName;
      hSearch := FindFirstFile(PChar(sSourceFileName), rSourceFindData);
      Windows.FindClose(hSearch);
      if (hSearch = INVALID_HANDLE_VALUE)
          or (rSourceFindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY <> 0then
        Continue;
      hSearch := FindFirstFile(PChar(aDestDir + sFileName), rDestFindData);
      Windows.FindClose(hSearch);
      if hSearch = INVALID_HANDLE_VALUE then
      begin
        aFiles.Add(sSourceFileName);
        Continue;
      end;
      if aMaxFileAge = 0 then
      begin
        if UInt64(rSourceFindData.ftLastWriteTime) > UInt64(rDestFindData.ftLastWriteTime) then
          aFiles.Add(sSourceFileName);
      end
      else
      begin
        FileTimeToLocalFileTime(rSourceFindData.ftCreationTime, ftCreationTime);
        FileTimeToLocalFileTime(rSourceFindData.ftLastWriteTime, ftLastWriteTime);
        if (DaysBetween(FileTimeToDateTime(ftCreationTime), Date) <= aMaxFileAge)
            or (DaysBetween(FileTimeToDateTime(ftLastWriteTime), Date) <= aMaxFileAge) then
          aFiles.Add(sSourceFileName);
      end;
    end;
    aFiles.Sort;
  finally
    aFiles.EndUpdate;
  end;
end;


Da blick ich so nicht durch! Was ist dabei für mich überflüssig?
Wie gesagt, ich will ja nur speichern, wenn die Datei verändert wurde.
(Ich sehe auch nicht, dass ich fileage mehrfach für die gleiche Datei aufrufe.)
Gruß, Detlef


jaenicke - So 21.11.10 15:15

user profile iconD. Annies hat folgendes geschrieben Zum zitierten Posting springen:
(Ich sehe auch nicht, dass ich fileage mehrfach für die gleiche Datei aufrufe.)
Beispiel für die erste der beiden Dateien:
user profile iconD. Annies hat folgendes geschrieben Zum zitierten Posting springen:

Delphi-Quelltext
1:
2:
3:
if (FileDateToDateTime(FileAge(work_filename)) <> FileDateToDateTime(FileAge(home_filename))) and
                            (fileage(work_filename) + 3600 <> FileAge(home_filename)) and
                            (fileage(work_filename)        <> FileAge(home_filename) + 3600then
3 Aufrufe ;-)


D. Annies - So 21.11.10 15:52

ok, ok.


jaenicke - So 21.11.10 15:56

So, hier einmal ein schnelles Beispiel, das direkt die Systemzeit benutzt und dementsprechend die UTC Zeit:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
function GetFileTime(const FileName: string): TFileTime;
var
  SearchHandle: THandle;
  FindData: TWin32FindData;
begin
  Result.dwLowDateTime := 0;
  Result.dwHighDateTime := 0;
  SearchHandle := FindFirstFile(PChar(FileName), FindData);
  if SearchHandle <> INVALID_HANDLE_VALUE then
  begin
    Windows.FindClose(SearchHandle);
    if (FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0 then
      Result := FindData.ftLastWriteTime;
  end;
end;

function IsFileOlder(const AFileName, AReferenceFileName: string): Boolean;
var
  FileTime, ReferenceTime: TFileTime;
begin
  FileTime := GetFileTime(AFileName);
  ReferenceTime := GetFileTime(AReferenceFileName);
  Result := FileTime.dwHighDateTime < ReferenceTime.dwHighDateTime;
  if not Result then
    Result := (FileTime.dwLowDateTime < ReferenceTime.dwLowDateTime)
      and (FileTime.dwHighDateTime = ReferenceTime.dwHighDateTime);
end;

function IsFileOlderEx(const AFileName, AReferenceFileName: string;
  ADays: Int64 = 0; AHours: Int64 = 0; AMinutes: Int64 = 0; ASeconds: Int64 = 0): Boolean;
var
  AllowedDiff: Int64;
  DiffLow, DiffHigh: Cardinal;
  FileTime, ReferenceTime: TFileTime;
begin
  AllowedDiff := ((ADays * 24 + AHours) * 60 + AMinutes) * 60 + ASeconds;
  AllowedDiff := AllowedDiff * 10000000;
  DiffLow := AllowedDiff and $FFFFFFFF;
  DiffHigh := AllowedDiff shr 32;
  FileTime := GetFileTime(AFileName);
  ReferenceTime := GetFileTime(AReferenceFileName);
  Result := FileTime.dwHighDateTime + DiffHigh < ReferenceTime.dwHighDateTime;
  if not Result then
    Result := (FileTime.dwLowDateTime + DiffLow < ReferenceTime.dwLowDateTime)
      and (FileTime.dwHighDateTime + DiffHigh = ReferenceTime.dwHighDateTime);
end;
Bei IsFileOlderEx kannst du auch eine minimale Differenz angeben, damit die Funktion True zurückgibt.


Gerd Kayser - So 21.11.10 16:00

user profile iconD. Annies hat folgendes geschrieben Zum zitierten Posting springen:
Wie gesagt, ich will ja nur speichern, wenn die Datei verändert wurde.
Und warum wertest Du dann nicht einfach das Archiv-Bit aus? Wenn Archiv-Bit gesetzt, dann die Datei kopieren und anschließend das Archiv-Bit entfernen.


D. Annies - So 21.11.10 16:55

@ Sebastian: Ich bin begeistert!
Jetzt muss ich mir nur noch überlegen, wie ich die Differenz von einer Stunde ausrechne, um dann nicht zu speichern.

@Gerd: Das wirkt viel leichter! Ich weiß nicht, wie man ein Archiv-Bit setzt/ändert.

LG an euch, Detlef


jaenicke - So 21.11.10 17:07

user profile iconD. Annies hat folgendes geschrieben Zum zitierten Posting springen:
Jetzt muss ich mir nur noch überlegen, wie ich die Differenz von einer Stunde ausrechne, um dann nicht zu speichern.
Es wird doch ohnehin die Weltzeit genommen, so dass die Sommerzeit usw. ohnehin keine Rolle spielt.

Und die Berechnung der Stunde (und Sekunden usw.) habe ich doch schon drin (in IsFileOlderEx). ;-)


Delete - So 21.11.10 17:18

user profile iconGerd Kayser hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconD. Annies hat folgendes geschrieben Zum zitierten Posting springen:
Wie gesagt, ich will ja nur speichern, wenn die Datei verändert wurde.
Und warum wertest Du dann nicht einfach das Archiv-Bit aus? Wenn Archiv-Bit gesetzt, dann die Datei kopieren und anschließend das Archiv-Bit entfernen.

Gefährlich, gefährlich. Nero setzt zum Beispiel beim Brennen das Archivbit zurück. Jetzt brenne ich Daten für meinen Kumpel, das Archivbit wird zurückgesetzt und schon sind sie beim nächsten Backup nicht mehr enthalten.

Und das mit dem letzten Änderungsdatum ist auch so eine Sache. Früher als man noch auf Disketten gespeichert hat, wurde beim Kopieren von der Diskette auf die Festplatte das Änderungsdatum auf das Datum zum Zeitpunkt des Kopierens gesetzt.

Wie gesagt, um zu entscheiden, ob sich eine Datei geändert hat, geht nur einigermaßen zuverlässig mittels eines direkten Vergleiches der Dateien. Und da hilft ein Hash.


D. Annies - So 21.11.10 18:02

@sebastian: Danke für deine erneuten Hinweise. Irgendwann blicke ich durch :))

@ Luckie: Ich finde nichts (brauchbares) über den Hash :((

Gruß, Detlef


jaenicke - So 21.11.10 18:06

Es gibt diverse Hashfunktionen. Am sinnvollsten dürfte hier MD5 [http://delphi.about.com/od/objectpascalide/a/delphi-md5-hash.htm] sein, da es relativ schnell geht und dennoch eine sehr geringe Kollisionswahrscheinlichkeit hat.


Delete - So 21.11.10 18:17

user profile iconD. Annies hat folgendes geschrieben Zum zitierten Posting springen:
@ Luckie: Ich finde nichts (brauchbares) über den Hash :((

Google: delphi + hash = 687.000 Treffer. Also wenn da nichts brauchbares bei, weiß ich es auch nicht. :roll:


D. Annies - So 21.11.10 22:44

Tut mir leid, aber ich komme nicht weiter.
(Bitte nicht fragen: "und wo ist dein Problem")
Detlef


jaenicke - So 21.11.10 23:41

und wo ist dein Problem? :mrgreen:

Für das Erstellen eines MD5 Hashs reicht es im Grunde die entsprechende Unit herunterzuladen und einzubinden und dann die MD5 Hash Erstellungsfunktion aufzurufen. :gruebel:


Gerd Kayser - Mo 22.11.10 07:11

user profile iconD. Annies hat folgendes geschrieben Zum zitierten Posting springen:
@Gerd: Das wirkt viel leichter! Ich weiß nicht, wie man ein Archiv-Bit setzt/ändert.

Beispiel:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
procedure TForm1.Button1Click(Sender: TObject);
var
  Search   : TSearchRec;
  Verz     : String;
  Liste    : TStringList;
  Schleife : integer;
begin
  Verz := 'L:\';
  Liste := TStringList.Create;
  try
    if FindFirst(Verz + '*.*', faAnyFile, Search) = 0 then
      try
        repeat
          if (Search.Attr and faArchive) = faArchive then
            Liste.Add(Verz + Search.Name);
        until
          FindNext(Search) <> 0;
      finally
        SysUtils.FindClose(Search);
      end;
    for Schleife := 0 to Liste.Count - 1 do
      begin
        // Hier könnte man die Datei umkopieren.
        FileSetAttr(Liste.Strings[Schleife],
          FileGetAttr(Liste.Strings[Schleife]) and not faArchive);
      end;
  finally
    Liste.Free;
  end;
  Label1.Caption := 'fertig';
end;

Übrigens: Nero 9 setzt das Archivbit beim Brennen NICHT zurück. Ich habs vorhin getestet.


D. Annies - Mo 22.11.10 09:55

Hi, Gerd,
ich setz das jetzt mal auf deine vorgeschlagene Weise um, werde mich aber wohl bis zur
nächsten Zeitumstellung gedulden müssen ...
Gruß, Detlef


Delete - Mo 22.11.10 12:30

user profile iconGerd Kayser hat folgendes geschrieben Zum zitierten Posting springen:
Übrigens: Nero 9 setzt das Archivbit beim Brennen NICHT zurück. Ich habs vorhin getestet.

Wie es bei neuen Nero Versionen aussieht, kann ich nicht sagen. Aber bei älteren Versionen war es jedenfalls so. Nichts desto trotz sollte man sich nicht auf das Archivbit verlassen. Auch wenn es wohl ursprünglich genau für so was gedacht war.