Игнорировать миллисекунды в TDateTime (те же вычтенные значения TDateTime не равны 0)

моя проблема вкратце: TDateTime A (09.03.2014 13:40) - TDateTime B (09.03.2014 13:40) = -1

У меня есть два значения TDateTime, которые я хочу сравнить, сначала я использовал оператор =, чтобы проверить, совпадают ли они, но после нескольких тестов я понял, что это не работает в моем случае. Путаница в том, что в большинстве случаев он отлично работает, но иногда нет.

Я получаю одно значение атрибута LastWriteTime из существующего файла, а другое значение — из базы данных MySQL.

Вот код:

TDateTime a := FileList[loop].Lastwritetime.AsUTCDateTime; // TDateTime from MySQL
TDateTime b := GetLastwritetimeUtc(Sourcedirectory); // TDateTime from my local file

if (CompareDateTime(a, b) = 0) then
begin
   // do some stuff.
end;

Теперь, как упоминалось ранее, этот простой код работает большую часть времени, но для некоторых значений TDateTime я получаю отрицательный результат, который должен означать, что мое значение TDateTime из базы данных MySQL раньше, чем значение TDateTime моего локального файла.

Итак, я начал отладку:

double aTicks := a; // MySQL TDateTime
double bTicks := b; // Local file TDateTime

это дает мне дни, прошедшие с 30.12.1899, и десятичные значения времени.

Примеры значений:

// a = 02.09.2014 11:42:01
// b = 02.09.2014 11:42:01
// aTicks = 41884,4875115741
// bTicks = 41884,4875153356

Десятичные числа, которые не совпадают, должны быть миллисекундами или нет (начиная с xxxx, 4875)? Теперь, если я сравню их (например, CompareDateTime(a,b) или a = b), я не получу 0/trueне сравниваю значения aTicks и bTicks).

Должен ли я вносить изменения в свой способ получения локального файла TDateTime (на данный момент я использую WinAPI, GetLastWriteTimeUTC не предоставил мне правильное время UTC)?

Я думаю, что это не очень сложная проблема, но я понятия не имею, как это решить. `` TDateTime хранит скрытые миллисекунды? В режиме отладки я не вижу миллисекунд и не знаю, как получить это значение из моего TDateTime (используя Delphi XE2).

Дополнительная информация о моем проекте

Я получаю значение TDateTime b таким образом

function GetLastwritetimeUtc(source: String): TDateTime;
var
   fad: TWin32FileAttributeData;
   SystemTime: TSystemTime;
   lastwritetimeUtc: TDateTime;
begin
   GetFileAttributesEx(PWideChar(source),GetFileExInfoStandard,@fad);
   FileTimeToSystemTime(fad.ftLastWriteTime, SystemTime);
   lastwritetimeUtc := SystemTimeToDateTime(SystemTime);
   result := lastwritetimeUtc;
end;

Если файл из базы данных MySQL «новее», я заменяю его и устанавливаю LastWriteTime из моего атрибута MySQL TDateTime a следующим образом: SetLastWriteTimeUTC(a) (а мои значения TDateTime из MySQL (a) не имеют значения в миллисекундах). Таким образом, проблема не должна возникать снова, но она возникает.

Значение TDateTime в моей базе данных MySQL взято из этого

XSDateTime c := DateTimeToXSDateTime(GetLastwritetimeUtc(sourceDirectory));
// i send this via WCF service to the MySQL database and store it in a `TDateTime` column (which does not include milliseconds)

Я надеюсь, что информации достаточно и не слишком много.

С наилучшими пожеланиями,

Никлас

ОБНОВЛЕНИЕ:

Код делает «то же самое», что и моя основная программа, как я уже сказал выше, неправильное сравнение DateTime не запускается все время только для некоторых файлов (в моем случае $ Default10.dsk).

uses
  SysUtils,
  Soap.SoapHttpTrans,
  DateUtils,
  Windows,
  System.IOUtils,
  Soap.XSBuiltIns;

var
  fad: TWin32FileAttributeData;
  SystemTime: TSystemTime;
  lastwritetimeUtcA: TDateTime;
  lastwritetimeUtcB: TDateTime;
  sourceFileA: string;
  sourceFileB: string;
  lastwritetimeXS: TXSDateTime;
begin
  while True do
  begin
    sourceFileA := 'Path to a file on your computer no matter which';
    sourceFileB := 'Path to another file on your computer no matter which';

    //GetLastWriteTime from local file
    GetFileAttributesEx(PWideChar(sourceFileA),GetFileExInfoStandard,@fad);
    FileTimeToSystemTime(fad.ftLastWriteTime, SystemTime);
    lastwritetimeUtcA := SystemTimeToDateTime(SystemTime);

    //Set the localfile lastwritetime to the theoretical mySQL file
    // in my main program there does not exist a mySQL file (only a value of TDateTime in the TDateTime column of my database where i store the lastwritetime from the local files
    TFile.SetLastWriteTimeUtc(sourceFileB, lastwritetimeUtcA);

    //Get the LastWriteTime from theoretical mySQL file
    // in my main program i get the lastwritetime value from the MySQL database via WCF that is the reason for the convertion of XSDateTime.AsUTCDateTime and DateTimeToXSDateTime
    GetFileAttributesEx(PWideChar(sourceFileB),GetFileExInfoStandard,@fad);
    FileTimeToSystemTime(fad.ftLastWriteTime, SystemTime);
    lastwritetimeUtcB := SystemTimeToDateTime(SystemTime);

    //Convert lastwritetime to XSDatetime - how i do it in my program
    lastwritetimeXS := DateTimeToXSDateTime(lastwritetimeUtcB);
    {Convert it back to DateTime}
    lastwritetimeUtcB := lastwritetimeXS.AsUTCDateTime;

    //Compare them
    if lastwritetimeUtcA = lastwritetimeUtcB then
      Writeln('Same time')
    else
      writeln('Not same time');
    Sleep(500);
  end;
end;

person TryToSolveItSimple    schedule 03.09.2014    source источник
comment
Почему бы вам не игнорировать миллисекунды?   -  person David Heffernan    schedule 03.09.2014
comment
Я не знаю, как и не нашел способов сделать это - это может решить мою проблему, и я был бы очень рад, если бы вы могли предоставить мне код для этого, чтобы я мог его проверить. Но также было бы очень неплохо узнать, почему это происходит?   -  person TryToSolveItSimple    schedule 03.09.2014
comment
Пожалуйста, не могли бы вы дать короткую полную программу для демонстрации проблемы.   -  person David Heffernan    schedule 03.09.2014
comment
я создам один и загружу его на файлообменник или в облачное пространство, но я думаю, что это не будет представлять мою собственную существующую особую проблему. Как упоминалось выше, в большинстве случаев это работает хорошо. Я сравниваю значения TDateTime из моего MySQL и моих локальных файлов. Но несколько файлов (всегда одно и то же в одном случае) несопоставимы, тогда ситуация описана выше. Я удалил эти файлы и воссоздал их с данными из моей базы данных MySQL, но это все еще происходит. РЕДАКТИРОВАТЬ: я думаю, что знаю, как воссоздать его, и предоставлю вам те же файлы и т. д., пожалуйста, подождите несколько минут или больше.   -  person TryToSolveItSimple    schedule 03.09.2014
comment
Вы можете использовать MilliSecondsBetween() для сравнения, и если это › x, вы можете предположить, что они разные.   -  person LU RD    schedule 03.09.2014
comment
Пожалуйста, ничего не загружайте. Просто добавьте к вопросу короткое полное консольное приложение.   -  person David Heffernan    schedule 03.09.2014
comment
@DavidHeffernan, я обновил свой пост. Но есть странное исключение^^. Ядро кода такое же, как и в моей основной программе. Разница: мой lastwritetimeA из локального файла, а lastwritetimeB из базы данных MySQL, и я думаю, что вот реальная разница   -  person TryToSolveItSimple    schedule 03.09.2014
comment
Эта программа бесполезна для нас, потому что у нас нет ваших файлов. Не могли бы вы дать нам что-нибудь, что мы могли бы запустить?   -  person David Heffernan    schedule 03.09.2014
comment
как я мог это сделать? Без загрузки ... вы можете заменить путь путями ваших файлов. Это происходит с одним файлом все время и с некоторыми файлами случайным образом. Я могу дать вам один или несколько из этих файлов. Но я думаю, что важно то, что MySQL хранит и получает время последней записи, а не сами файлы.   -  person TryToSolveItSimple    schedule 03.09.2014
comment
Программа в порядке, просто замените TDirectory.SetLastWriteTimeUtc на TFile.SetLastWriteTimeUtc и удалите ; перед else, компилятор скажет, где. Используйте любые два файла. Проблема заключается в сравнении с плавающей запятой.   -  person Sertac Akyuz    schedule 03.09.2014
comment
Точное сравнение с плавающей запятой @SertacAkyuz - я понял это в своем посте выше (см. Примеры значений с aTicks и bTicks в комментариях). Извините за неправильную точку с запятой, я редактировал свой код в сообщении и не видел этого. Я попробую сравнить с отдельными частями, как вы сказали, но мне все еще интересно, почему сравнение работает большую часть времени.   -  person TryToSolveItSimple    schedule 03.09.2014
comment
@Ask - Вы удалили неправильную точку с запятой, она должна быть перед другой, ;), не очень важно ... Если вы обязаны использовать плавающую точку, «математическая» единица должна иметь некоторый CompareValue, который вы можете использовать для эпсилон, который вы определяете, здесь также должны быть некоторые вопросы по SO.   -  person Sertac Akyuz    schedule 03.09.2014
comment
Я не обязан использовать плавающую точку, это был всего лишь способ выяснить проблему. Самый простой способ — игнорировать миллисекунды или установить их равными нулю для обоих значений TDateTime. Есть ли более умный способ, чем DecodeDateTime и EncodeDateTime DateTime? Или я должен использовать плавающую точку и сравнить ее с математическим блоком?   -  person TryToSolveItSimple    schedule 03.09.2014
comment
@Ask. Не используя плавающую точку, я имел в виду сохранение времени в базе данных в виде метки времени или что-то в этом роде, а не использование даты и времени на клиенте вообще.   -  person Sertac Akyuz    schedule 03.09.2014


Ответы (2)


Если вы хотите сравнить два значения TDateTime и сопоставить их со вторым, игнорируя разницу в миллисекундах, используйте SecondsBetween из единицы DateUtils:

program Project1;

uses
  SysUtils, DateUtils;

var
  dtOne, dtTwo: TDateTime;

begin
  dtOne := 41884.4875115741;
  dtTwo := 41884.4875153356;

  if SecondsBetween(dtOne, dtTwo) = 0 then
    WriteLn('Dates the same without MS')
  else
    WriteLn('Not the same dates.');

  ReadLn;
end.

Существуют аналогичные функции для других различий, таких как DaysBetween, MinutesBetween и MilliSecondsBetween, если вам нужно соответствовать другим разрешениям. Вот вспомогательная функция, которая работает для различных разрешений (точное совпадение, день, час, минута или секунда):

type
  TDiffResolution = (tdrExact, tdrDay, tdrHour, tdrMin, tdrSec);

function IsSameDateTime(dValOne, dValTwo: TDateTime;
  const Resolution: TDiffResolution = tdrSec): Boolean;
begin
  case Resolution of
    tdrExact: Result := MillisecondsBetween(dValOne, dValTwo) = 0;
    tdrDay: Result := IsSameDay(dValOne, dValTwo);
    tdrHour: Result := HoursBetween(dValOne, dValTwo) = 0;
    tdrMin: Result := MinutesBetween(dValOne, dValTwo) = 0;
    tdrSec: Result := SecondsBetween(dValOne, dValTwo) = 0;
  else
    raise Exception.CreateFmt('Invalid resolution value (%d) provided.',
                              [Ord(Resolution)]);
  end;
end;

Пример использования:

  dtOne := 41884.4875115741;
  dtTwo := 41884.4875153356;

  if IsSameDateTime(dtOne, dtTwo, tdrSec) then
    WriteLn('Dates are the same.')
  else
    WriteLn('Dates are different.');
  ReadLn;    
person Ken White    schedule 03.09.2014
comment
Или вы можете просто перекодировать значения TDateTime, чтобы удалить миллисекунды. Посмотрите на System.DateUtils.RecodeMilliSecond() для этого. Или используйте EncodeDate() и EncodeTime() напрямую вместо SystemTimeToDateTime(), чтобы вы могли установить миллисекунды на 0 с самого начала. - person Remy Lebeau; 03.09.2014
comment
@Remy: Да, это тоже жизнеспособные альтернативы. Я выбрал этот способ, потому что его легко расширить для работы с игнорированием разницы в минутах или часах (путем добавления типа разрешения в качестве параметра), а также потому, что его было легко написать. :-) - person Ken White; 03.09.2014
comment
это умный способ. Спасибо за это, это работает хорошо для меня! - person TryToSolveItSimple; 04.09.2014
comment
Извините, у меня вопрос для сравнения с SecondsBetween, я прав, что этот метод не сравнивает часы, минуты, дни и т. д.? только секунды? В моем тестовом случае это дает мне результат, что мой вчерашний файл новее, чем мой текущий файл, потому что секунды больше. Когда я прокомментировал ваш пост несколько часов назад, я проверил его только с сегодняшними файлами. - person TryToSolveItSimple; 04.09.2014
comment
@Askinator: сравнивает секунды между ними, которые, если секунды не совпадают, будут больше нуля; если минуты не совпадают, будет больше (полная минута вернет 60 секунд между ними), час будет еще больше (60 * 60 = 3600 секунд между) и т. д. Полная разница в 24 часа приведет к 86400 секундам между . Таким образом, он сравнивает каждую часть значений с точностью до секунд, но игнорирует миллисекунды. Если вы хотите включить миллисекунды, используйте вместо них MillisecondsBetween. - person Ken White; 04.09.2014
comment
@KenWhite Ах! Я правильно понимаю, что он показывает мне только то, что он не совпадает, а не то, что старше или новее? Я переписал свой GetLastWriteTimeUtc, чтобы он не включал ms, который решил мою проблему сейчас, но я подумал, что мог бы решить ее немного проще (это было не сложнее, после того, как все ответили здесь, но нет oneliner: P) - person TryToSolveItSimple; 04.09.2014
comment
Он показывает только разницу, а не то, какой из них старше/новее. Однако вы можете понять это сами; если файлы имеют разную дату, вы сравниваете дату FileA с датой FileB, и если дата FileA больше, то она новее, в противном случае FileB. Первый тест (что они не совпадают с датой начала) говорит вам, нужно ли вам делать второй тест или нет. Это всего лишь пара строк кода: if SecondsBetween(DateA, DateB) <> 0 then if DateA > DateB then FileA is newest else FileB is newest. - person Ken White; 04.09.2014
comment
@KenWhite, ты сейчас упускаешь суть. Моя проблема заключалась именно в том, что я не могу так просто сравнить, проверив DateA › DateB или DateA = DateB из-за ms. Это была моя тема - игнорирование мс, а не само сравнение, но вы опубликовали метод сравнения без проверки мс, который был кодом, который я искал, но в конце концов это не решило проблему, потому что только сказал: это не же было не все. Но мы не должны обсуждать здесь решенную проблему. Если вы хотите иметь расширенную дискуссию, мы могли бы поговорить. - person TryToSolveItSimple; 04.09.2014

Вы также можете поиграть, преобразовав TDateTime в строку и сравнив их. Таким образом, вы можете создавать очень сложные условия, устанавливая формат времени. Например, вы можете проверить, находятся ли даты в пределах одного часа, игнорируя минуты и секунды:

If FormatDateTime('yyyymmddhh', Date1) = FormatDateTime('yyyymmddhh', Date2) ...

Другой способ - расшифровать их и сравнить интересующие вас части, например:

DecodeDateTime (Date1, Y1, M1, D1, H1, N1, S1, mS1);
DecodeDateTime (Date2, Y2, M2, D2, H2, N2, S2, mS2);
If (Y1 = Y2) and (M1 = M2) and (D1 = D2) and (H1 = H2) then ...
person adlabac    schedule 04.09.2014