Подтвердить что ты не робот

В Delphi есть функция для преобразования даты и времени XML в TDateTime

Дата и время XML указаны в формате

'-'? yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s +)? (Zzzzzz)?

были

• '-'? yyyy - это цифра в четыре или более цифр, которая необязательно имеет цифру с отрицательным знаком, которая представляет год; если более четырех цифр, ведущие нули запрещены, а "0000" запрещено

• остальные '- являются разделителями между частями части даты;

• первый mm представляет собой двузначное число, которое представляет месяц;

• dd - двузначное число, которое представляет день;

• 'T' - разделитель, указывающий, что следует время дня;

• hh - двузначная цифра, представляющая час; "24" разрешено, если представленные минуты и секунды равны нулю, а представленное значение dateTime - это первый момент следующего дня (свойство hour объекта dateTime в пространстве значений · не может иметь значение больше 23);

• ':' является разделителем между частями части времени суток;

• второй mm - двузначное число, которое представляет минуту;

• ss - цифра с целым числом, которая представляет целые секунды;

• '' s + (если присутствует) представляет дробные секунды;

• zzzzzz (если присутствует) представляет часовой пояс (как описано ниже).

здесь приведены примеры

Простой пример 2009-08-31T19: 30: 00

Более сложные примеры

2002-10-10T12: 00: 00-05: 00 (полдень 10 октября 2002 года, центральное летнее время, а также восточное стандартное время в США) 2002- 10-10T17: 00: 00Z, на пять часов позже 2002-10-10T12: 00: 00Z.

см. www.w3.org/TR/2004/REC-xmlschema-2-20041028/datatypes.html для получения дополнительной информации

4b9b3361

Ответ 1

Delphi имеет блок XSBuiltIns (с Delphi 6), который содержит типы данных, которые могут помочь вам преобразовать некоторые типы данных XML:

(есть больше, например TXSDecimal, вы получаете идею)

Все они содержат по крайней мере эти два метода:

Вы можете использовать его следующим образом:

with TXSDateTime.Create() do
  try
    AsDateTime := ClientDataSetParam.AsDateTime; // convert from TDateTime
    Attribute.DateTimeValue := NativeToXS; // convert to WideString
  finally
    Free;
  end;

with TXSDateTime.Create() do
  try
    XSToNative(XmlAttribute.DateTimeValue); // convert from WideString
    CurrentField.AsDateTime := AsDateTime; // convert to TDateTime
  finally
    Free;
  end;

Это должно вас заставить.

- Йерун

Ответ 2

Это уже ответили нормально, но я добавлю здесь кое-что, что я сделал в аналогичном случае, который у меня был. Из модуля XSBuiltIns я нашел метод

function XMLTimeToDateTime(const XMLDateTime: InvString; AsUTCTime: Boolean = False): TDateTime;

который, казалось, был тем, что я хотел. Я хотел был бы разобрать все различные строки времени XML, определенные здесь: http://www.w3schools.com/schema/schema_dtypes_date.asp

Сюда входят строки с датой, просто временем или обоими датами и временем, а также все параметры с указанным часовым поясом или временем UTC или локальными для исходной строки и возвращаемое значение как местное время. Кроме того, когда мне дали только время, я хотел, чтобы он всегда находился в пределах "нулевого дня", т.е. После операции вся часть возвращаемого TDateTime (отбрасывается на действительное число) была равна нулю.

Наконец, я хотел, чтобы функция возвращала DateTime.MinValue на ошибочный ввод (в основном, когда задана пустая строка).

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

Основные проблемные моменты (возможно, я уже забуду некоторые):

  • Пустая строка приводит к тому, что DateTime соответствует дате в году 1, а Delphi MinDateTime - в году 100.
  • Строки с только датой всегда считаются UTC независимо от наличия или отсутствия "Z" или явных определений часовых поясов.
  • Строки с единственным временем ошибочно идентифицируются как строки даты и ошибочно разбираются.
  • Модификаторы часовых поясов применяются ТОЛЬКО, если они явно определены, иначе все предполагается UTC, даже если нет "Z".
  • Дробные секунды не поддерживаются, но миллисекунды всегда преобразуются в 0.
  • Поскольку строки только-времени не поддерживаются, мне пришлось добавить им фиктивную дату, а затем убедиться, что это текущая дата (чтобы покрыть проблемы DST при конвертировании в/из UTC, что, в свою очередь, должно было быть выполнено из-за ошибочные соображения UTC), а затем в конце снова вычесть его из результата и, наконец, в этих случаях обеспечить требование нулевого значения для строк только времени.

Конечный результат - это функция около 100 строк (включая комментарии и т.д.), в которой используется справедливое количество вспомогательных функций (которые должны быть довольно понятными и которые не являются темой этого сообщения:)). Я разделил соответствующие биты кода на отдельный файл, и тесты модулей, которые я использовал, чтобы проверить это на другой, я включаю оба ниже. Не стесняйтесь использовать и комментировать при необходимости. Обратите внимание, что форма и связанное с ней использование и т.д. - это то, что Delphi поставил в демонстрационный проект, в который я его бросил, они никоим образом не нужны.

unit Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, XSBuiltIns, Math, DateUtils;

const
  EPSILON = 10e-9;

type
  TForm1 = class(TForm)
  private
    { Private declarations }
  public
    { Public declarations }
  end;

{Returns whether the given variable represents negative infinity.}
function IsNegInf(AValue : extended) : boolean;

{Returns whether the given variable represents positive infinity.}
function IsPosInf(AValue : extended) : boolean;

{Checks the less than relation of the given real numbers (R1 < R2), up to
precision EPSILON.}
function RealLessThan(R1, R2 : double) : boolean;

{Checks the greater than or equal to relation of the given real numbers (R1 >= R2),
 up to precision EPSILON.}
function RealGreaterThanOrEqualTo(R1, R2 : double) : boolean;

{Checks the less than or equal to relation of the given real numbers (R1 <= R2),
up to precision EPSILON.}
function RealLessThanOrEqualTo(R1, R2 : double) : boolean;

{Return the floor of R, up to precision EPSILON. If Frac(R) < EPSILON, return R.}
function RealFloor(R : extended) : extended;

{Return the floor of R as integer, up to precision EPSILON. If Frac(R) < EPSILON, return R.}
function RealFloorInt(R : extended) : integer;

{Round the value X (properly) to an integer.}
function RoundProper(X : extended) : integer; overload;

function UtcTimeToLocalTime(AUtcTime: TDateTime): TDateTime;
function LocalTimeToUtcTime(ALocalTime: TDateTime): TDateTime;

function CountOccurrences(const SubText: string; const Text: string): Integer;
  // Returns a count of the number of occurences of SubText in Text

function XMLTimeStamp2DateTime(TimeStamp : String): TDateTime;
  // Parses an XML time stamp string to a TDateTime. All returned times are in
  // local time. If time stamp string contains no time stamp definition (either
  // explicit time zone info or UTC flag), the time is assumed to be in local time.
  // Otherwise the time is parsed as the time zone indicated, and converted to local.
  // If no time section is contained in the stamp, the time is assumed to be
  // 0:00:00 in the time zone specified (or local time if no specification set).
  // If time string is not valid MinDateTime is returned.

var
  Form1: TForm1;

implementation

{$R *.dfm}

function XMLTimeStamp2DateTime(TimeStamp : String): TDateTime;
var
  HasDateAndTimePart, HasUTCForce, HasExplicitTimeZone, HasDatePart, HasFractionalSeconds: Boolean;
  PlusCount, MinusCount, HourOffset, MinuteOffset, FractionIndex, I: Integer;
  TimeOffset: TDateTime;
  TimeZoneString, TimeZoneDelimiter: string;
  Year, Month, Day, MilliSeconds: Word;
  YearS, MonthS, DayS, FracSecS: string;
  CurrentDate, MSecsFromFractions: TDateTime;
  DotSeparatedDecimals: TFormatSettings;
begin
  TimeOffset := 0; TimeZoneString := ''; TimeZoneDelimiter := '+';
  FractionIndex := Pos('.', TimeStamp);
  {$REGION 'Get the fractional seconds as milliseconds'}
  HasFractionalSeconds := FractionIndex > 0;
  FracSecS := '0.';
  if HasFractionalSeconds then
  begin
    for I := FractionIndex + 1 to Length(TimeStamp) do
    begin
      if CharInSet(TimeStamp[I], ['0'..'9']) then FracSecS := FracSecS + TimeStamp[I]
      else Break;
    end;
  end else FracSecS := FracSecS + '0';
  DotSeparatedDecimals.Create;
  DotSeparatedDecimals.DecimalSeparator := '.';
  DotSeparatedDecimals.ThousandSeparator := #0;
  MilliSeconds := RoundProper(StrToFloatDef(FracSecS, 0, DotSeparatedDecimals) * 1000);
  MSecsFromFractions := EncodeTime(0, 0, 0, MilliSeconds);
  {$ENDREGION}
  MinusCount := CountOccurrences('-', TimeStamp);
  HasDatePart := (MinusCount > 1) or (TimeStamp = '');
  PlusCount := CountOccurrences('+', TimeStamp);
  HasExplicitTimeZone := PlusCount > 0;
  if not HasExplicitTimeZone then
  begin
    HasExplicitTimeZone := Odd(MinusCount); // 1 or 3 minuses => explicit time zone
    TimeZoneDelimiter := '-';
  end;
  if HasExplicitTimeZone then
  begin
    TimeZoneString := Copy(TimeStamp, LastDelimiter(TimeZoneDelimiter, TimeStamp) + 1, Length(TimeStamp));
    // Now TimeZoneString should be of format xx:xx where x are numbers!
    if (Length(TimeZoneString) = 5) and (TimeZoneString[3] = ':') then
    begin
      HourOffset := StrToIntDef(Copy(TimeZoneString, 1, 2), 0);
      MinuteOffset := StrToIntDef(Copy(TimeZoneString, 3, 2), 0);
      TimeOffset := EncodeTime(HourOffset, MinuteOffset, 0, 0);
      if TimeZoneDelimiter = '-' then TimeOffset := -TimeOffset;
    end;
  end;
  CurrentDate := Now;
  Year := 0; Month := 0; Day := 0;
  DecodeDate(CurrentDate, Year, Month, Day);
  if not HasDatePart then
  begin
    // Since XMLTimeToDateTime doesn't cope with strings without date part, add
    // a dummy one on current date if it doesn't exist - we can't use day zero
    // since then the daylight saving time calculation in the LocalTimeToUtcTime
    // fixup being possibly done later will go wrong, if local time is in DST
    // and day zero is not. So we have to use current day here, then remove it
    // from the final result once we're done otherwise.
    YearS := IntToStr(Year);
    MonthS := IntToStr(Month);
    DayS := IntToStr(Day);
    while Length(YearS) < 4 do YearS := '0' + YearS;
    while Length(MonthS) < 2 do MonthS := '0' + MonthS;
    while Length(DayS) < 2 do DayS := '0' + DayS;
    TimeStamp := YearS + '-' + MonthS + '-' + DayS + SoapTimePrefix + TimeStamp;
  end;
  HasDateAndTimePart := Pos(SoapTimePrefix, TimeStamp) > 0;
  HasUTCForce := Pos(SLocalTimeMarker, TimeStamp) > 0;
  Result := XMLTimeToDateTime(TimeStamp); // This doesn't support fractions of a second!
  // Now the conversion is done with zero milliseconds, we need to add the fractions
  Result := Result + MSecsFromFractions;
  // XMLTimeToDateTime assumes source as UTC when:
  // - No time part is defined and one of the following holds:
  //   - Explicit time zone is defined (to other than UTC) - here it works WRONG!
  //   - Explicit time zone is NOT defined and UTC flag is NOT defined - here it works WRONG!
  //   - Explicit UTC flag is defined - here it works CORRECT!
  // - Time part is defined and one of the following holds:
  //   - Explicit time zone is NOT defined and UTC flag is NOT defined - here it works WRONG!
  //   - Explicit UTC flag is defined - here it works CORRECT!
  // In the cases where it works wrong, we need to manually offset its result
  // by the local-to-UTC difference.
  if (not HasExplicitTimeZone) and (not HasUTCForce) then
    Result := LocalTimeToUtcTime(Result)
  else if HasExplicitTimeZone and (not HasDateAndTimePart) then
    Result := Result - TimeOffset;  // Minus to remove the effect of the offset
  if not HasDatePart then
  begin
    // We added the current date to make XMLTimeToDateTime work, now we need to
    // remove (the date part of) it back from the end result.
    Result := Result - EncodeDate(Year, Month, Day);
    // Since there originally was no date part, then there should not be one in
    // the end result also, meaning that the result date should correspond to
    // the zero-day.
    while RealGreaterThanOrEqualTo(Result, 1) do Result := Result - 1;
    while RealLessThan(Result, 0) do Result := Result + 1;
  end;
  Result := Max(Result, MinDateTime); // In erroneous situations XMLTimeToDateTime returns something less than MinDateTime, which we want as default
end;

{ Returns a count of the number of occurences of SubText in Text }
function CountOccurrences(const SubText: string; const Text: string): Integer;
var
  i, j, SubLength: Integer;
  First: Char;
begin
  Result := 0;
  if Length(SubText) <= 0 then Exit;
  First := SubText[1];
  SubLength := Length(SubText);
  for i := 1 to Length(Text) do
  begin
    if Text[i] = First then
    begin
      j := 2;
      while (j <= SubLength) and (Text[i + j - 1] = SubText[j]) do Inc(j);
      if j > SubLength then Inc(result); // Matched all the way
    end;
  end;
end;

function UtcTimeToLocalTime(AUtcTime: TDateTime): TDateTime;
begin
  Result := TTimeZone.Local.ToLocalTime(AUtcTime);
end;

function LocalTimeToUtcTime(ALocalTime: TDateTime): TDateTime;
begin
  Result := TTimeZone.Local.ToUniversalTime(ALocalTime);
end;

function RoundProper(X : extended) : integer;
begin
  Result := RealFloorInt(0.5 + x);
end;

function RealFloorInt(R : extended) : integer;
begin
  Result := Trunc(RealFloor(R));
end;

function RealFloor(R : extended) : extended;
var
  FracR : Extended;
begin
  Result := R;
  FracR := Abs(Frac(R));
  if (FracR >= EPSILON) and RealLessThan(FracR, 1) then begin
    if Frac(R) > 0 then Result := R - Frac(R)
    else Result := R - (1 - Abs(Frac(R)));
  end;
end;

function RealLessThan(R1, R2 : double) : boolean;
begin
  if IsPosInf(R2) then Result := not IsPosInf(R1)
  else if IsNegInf(R2) or IsPosInf(R1) then Result := False
  else if IsNegInf(R1) then Result := not IsNegInf(R2)
  else                            // (-Inf, -EPSILON) => Less,
    Result := R1 - R2 < -EPSILON; // [-EPSILON, EPSILON] => Equal
end;                              // (EPSILON, Inf) => Greater

function RealGreaterThanOrEqualTo(R1, R2 : double) : boolean;
begin
  if IsPosInf(R1) or IsNegInf(R2) then Result := True
  else if IsPosInf(R2) or IsNegInf(R1) then Result := False
  else                            // (-Inf, -EPSILON) => Less,
    Result := R1 - R2 > -EPSILON; // [-EPSILON, EPSILON] => Equal
end;                              // (EPSILON, Inf) => Greater

function RealLessThanOrEqualTo(R1, R2 : double) : boolean;
begin
  if IsPosInf(R2) or IsNegInf(R1) then Result := True
  else if IsPosInf(R1) or IsNegInf(R2) then Result := False
  else                            // (-Inf, -EPSILON) => Less,
    Result := R1 - R2 < EPSILON;  // [-EPSILON, EPSILON] => Equal
end;                              // (EPSILON, Inf) => Greater

function IsPosInf(AValue : extended) : boolean;
begin
  Result := IsInfinite(AValue) and (Sign(AValue) = 1);
end;

function IsNegInf(AValue : extended) : boolean;
begin
  Result := IsInfinite(AValue) and (Sign(AValue) = -1);
end;

end.

Тогда юнит-тесты здесь:

unit TestMain;
{

  Delphi DUnit Test Case
  ----------------------
  This unit contains a skeleton test case class generated by the Test Case Wizard.
  Modify the generated code to correctly setup and call the methods from the unit 
  being tested.

}

interface

uses
  TestFramework, System.SysUtils, Vcl.Graphics, XSBuiltIns, Winapi.Windows,
  System.Variants, DateUtils, Vcl.Dialogs, Vcl.Controls, Vcl.Forms, Winapi.Messages, Math,
  System.Classes, Main;

type
  // Test methods for class TForm1

  TestTForm1 = class(TTestCase)
  strict private
  public
    procedure SetUp; override;
    procedure TearDown; override;
  published
    procedure TestXMLTimeStamp2DateTime;
  end;

implementation

procedure TestTForm1.SetUp;
begin
  // Nothing to do here
end;

procedure TestTForm1.TearDown;
begin
  // Nothing to do here
end;

procedure TestTForm1.TestXMLTimeStamp2DateTime;
const
  TIME_TOLERANCE = 0.0000000115741; // Approximately 1 millisecond, in days
var
  Source: string;
  ReturnValue, ExpectedValue, Today: TDateTime;

  function DateTimeOfToday: TDateTime;
  var
    Year, Month, Day: Word;
  begin
    Year := 0; Month := 0; Day := 0;
    DecodeDate(Now, Year, Month, Day);
    Result := EncodeDate(Year, Month, Day);
  end;

begin
  Today := DateTimeOfToday; // Counted only once, we ignore the theoretic chance of day changing during the test execution from DST to non-DST or vice versa
  {$REGION 'Empty string'}
  // Setup method call parameters
  Source := '';
  ExpectedValue := MinDateTime;
  // Call the method
  ReturnValue := XMLTimeStamp2DateTime(Source);
  // Validate method results
  CheckEquals(ExpectedValue, ReturnValue, TIME_TOLERANCE, 'XMLTimeStamp2DateTime for empty string should return MinDateTime, but did not!');
  {$ENDREGION}
  {$REGION 'Date only strings'}
  {$REGION 'Date string - local'}
  // Setup method call parameters
  Source := '2002-09-24';
  ExpectedValue := EncodeDate(2002, 9, 24);
  // Call the method
  ReturnValue := XMLTimeStamp2DateTime(Source);
  // Validate method results
  CheckEquals(ExpectedValue, ReturnValue, 'XMLTimeStamp2DateTime for date string - local should return 24.9.2002, but did not!');
  {$ENDREGION}
  {$REGION 'Date string - UTC'}
  // Setup method call parameters
  Source := '2002-09-24Z';
  ExpectedValue := EncodeDate(2002, 9, 24);
  ExpectedValue := UtcTimeToLocalTime(ExpectedValue);
  // Call the method
  ReturnValue := XMLTimeStamp2DateTime(Source);
  // Validate method results
  CheckEquals(ExpectedValue, ReturnValue, TIME_TOLERANCE, 'XMLTimeStamp2DateTime for date string - UTC should return 24.9.2002 + local time offset, but did not!');
  {$ENDREGION}
  {$REGION 'Date string - negative offset'}
  // Setup method call parameters
  Source := '2002-09-24-03:00';
  ExpectedValue := EncodeDate(2002, 9, 24);
  ExpectedValue := ExpectedValue + EncodeTime(3, 0, 0, 0);  // First convert to UTC by removing the offset
  ExpectedValue := UtcTimeToLocalTime(ExpectedValue); // Then convert to local from UTC
  // Call the method
  ReturnValue := XMLTimeStamp2DateTime(Source);
  // Validate method results
  CheckEquals(ExpectedValue, ReturnValue, TIME_TOLERANCE, 'XMLTimeStamp2DateTime for date string - negative offset should return 24.9.2002 + three hours + local time offset, but did not!');
  {$ENDREGION}
  {$REGION 'Date string - positive offset'}
  // Setup method call parameters
  Source := '2002-09-24+11:00';
  ExpectedValue := EncodeDate(2002, 9, 24);
  ExpectedValue := ExpectedValue - EncodeTime(11, 0, 0, 0); // First convert to UTC by removing the offset
  ExpectedValue := UtcTimeToLocalTime(ExpectedValue); // Then convert to local from UTC
  // Call the method
  ReturnValue := XMLTimeStamp2DateTime(Source);
  // Validate method results
  CheckEquals(ExpectedValue, ReturnValue, TIME_TOLERANCE, 'XMLTimeStamp2DateTime for date string - positive offset should return 24.9.2002 - eleven hours + local time offset, but did not!');
  {$ENDREGION}
  {$ENDREGION}
  {$REGION 'Time only strings'}
  {$REGION 'Time string - local'}
  // Setup method call parameters
  Source := '09:30:10';
  ExpectedValue := EncodeTime(9, 30, 10, 0);
  // Call the method
  ReturnValue := XMLTimeStamp2DateTime(Source);
  // Validate method results
  CheckEquals(ExpectedValue, ReturnValue, TIME_TOLERANCE, 'XMLTimeStamp2DateTime for time string - local should return 09:30:10, but did not!');
  {$ENDREGION}
  {$REGION 'Time string - UTC'}
  // Setup method call parameters
  Source := '09:30:10Z';
  // Have to add Today for the UtcTimeToLocalTime call to have correct DST
  // - then have to remove Today again away to have correct zero-day date
  ExpectedValue := Today + EncodeTime(9, 30, 10, 0);
  ExpectedValue := UtcTimeToLocalTime(ExpectedValue);
  ExpectedValue := ExpectedValue - Today;
  // Call the method
  ReturnValue := XMLTimeStamp2DateTime(Source);
  // Validate method results
  CheckEquals(ExpectedValue, ReturnValue, TIME_TOLERANCE, 'XMLTimeStamp2DateTime for time string - UTC should return 09:30:10 + local time offset, but did not!');
  {$ENDREGION}
  {$REGION 'Time string - negative offset'}
  // Setup method call parameters
  Source := '09:30:10-03:00';
  // Have to add Today for the UtcTimeToLocalTime call to have correct DST
  // - then have to remove Today again away to have correct zero-day date
  ExpectedValue := Today + EncodeTime(9, 30, 10, 0);
  ExpectedValue := ExpectedValue + EncodeTime(3, 0, 0, 0);  // First convert to UTC by removing the offset
  ExpectedValue := UtcTimeToLocalTime(ExpectedValue); // Then convert to local from UTC
  ExpectedValue := ExpectedValue - Today;
  // Call the method
  ReturnValue := XMLTimeStamp2DateTime(Source);
  // Validate method results
  CheckEquals(ExpectedValue, ReturnValue, TIME_TOLERANCE, 'XMLTimeStamp2DateTime for time string - negative offset should return 09:30:10 + three hours + local time offset, but did not!');
  {$ENDREGION}
  {$REGION 'Time string - positive offset over date line'}
  // Setup method call parameters
  Source := '06:30:10+11:00';
  // Have to add Today for the UtcTimeToLocalTime call to have correct DST
  // - then have to remove Today again away to have correct zero-day date
  ExpectedValue := Today + EncodeTime(6, 30, 10, 0);
  ExpectedValue := ExpectedValue - EncodeTime(11, 0, 0, 0);  // First convert to UTC by removing the offset
  ExpectedValue := UtcTimeToLocalTime(ExpectedValue); // Then convert to local from UTC
  ExpectedValue := ExpectedValue - Today;
  if RealGreaterThanOrEqualTo(ExpectedValue, 1) then ExpectedValue := ExpectedValue - 1;  // When having time only, date should always be zero!
  if RealLessThan(ExpectedValue, 0) then ExpectedValue := ExpectedValue + 1;  // When having time only, date should always be zero!
  // Call the method
  ReturnValue := XMLTimeStamp2DateTime(Source);
  // Validate method results
  CheckEquals(ExpectedValue, ReturnValue, TIME_TOLERANCE, 'XMLTimeStamp2DateTime for time string - positive offset (over day change) should return 06:30:10 - eleven hours + local time offset (modulo 24 hours), but did not!');
  {$ENDREGION}
  {$REGION 'Fractional time string with negative offset over date line'}
  // Setup method call parameters
  Source := '14:30:10.25-11:00';
  // Have to add Today for the UtcTimeToLocalTime call to have correct DST
  // - then have to remove Today again away to have correct zero-day date
  ExpectedValue := Today + EncodeTime(14, 30, 10, 250);
  ExpectedValue := ExpectedValue + EncodeTime(11, 0, 0, 0);  // First convert to UTC by removing the offset
  ExpectedValue := UtcTimeToLocalTime(ExpectedValue); // Then convert to local from UTC
  ExpectedValue := ExpectedValue - Today;
  if RealGreaterThanOrEqualTo(ExpectedValue, 1) then ExpectedValue := ExpectedValue - 1;  // When having time only, date should always be zero!
  if RealLessThanOrEqualTo(ExpectedValue, 0) then ExpectedValue := ExpectedValue + 1;  // When having time only, date should always be zero!
  // Call the method
  ReturnValue := XMLTimeStamp2DateTime(Source);
  // Validate method results
  CheckEquals(ExpectedValue, ReturnValue, TIME_TOLERANCE, 'XMLTimeStamp2DateTime for fractional time string - negative offset (over day change) should return 14:30:10.25 + eleven hours + local time offset (modulo 24 hours), but did not!');
  {$ENDREGION}
  {$ENDREGION}
  {$REGION 'Date and time strings}
  {$REGION 'Date and time string - local'}
  // Setup method call parameters
  Source := '2002-09-24T09:30:10.25';
  ExpectedValue := EncodeDate(2002, 9, 24) + EncodeTime(9, 30, 10, 250);
  // Call the method
  ReturnValue := XMLTimeStamp2DateTime(Source);
  // Validate method results
  CheckEquals(ExpectedValue, ReturnValue, TIME_TOLERANCE, 'XMLTimeStamp2DateTime for date and time string - local should return 24.9.2002 09:30:10.25, but did not!');
  {$ENDREGION}
  {$REGION 'Date and time string - UTC'}
  // Setup method call parameters
  Source := '2002-09-24T09:30:10.25Z';
  ExpectedValue := EncodeDate(2002, 9, 24) + EncodeTime(9, 30, 10, 250);
  ExpectedValue := UtcTimeToLocalTime(ExpectedValue);
  // Call the method
  ReturnValue := XMLTimeStamp2DateTime(Source);
  // Validate method results
  CheckEquals(ExpectedValue, ReturnValue, TIME_TOLERANCE, 'XMLTimeStamp2DateTime for date and time string - UTC should return 24.9.2002 09:30:10.25 + local time offset, but did not!');
  {$ENDREGION}
  {$REGION 'Date and time string - positive offset over date line'}
  // Setup method call parameters
  Source := '2002-09-24T06:30:10.25+11:00';
  ExpectedValue := EncodeDate(2002, 9, 24) + EncodeTime(6, 30, 10, 250);
  ExpectedValue := ExpectedValue - EncodeTime(11, 0, 0, 0);  // First convert to UTC by removing the offset
  ExpectedValue := UtcTimeToLocalTime(ExpectedValue); // Then convert to local from UTC
  // Call the method
  ReturnValue := XMLTimeStamp2DateTime(Source);
  // Validate method results
  CheckEquals(ExpectedValue, ReturnValue, TIME_TOLERANCE, 'XMLTimeStamp2DateTime for date and time string - positive offset (over day change) should return 24.9.2002 06:30:10.25 - eleven hours + local time offset, but did not!');
  {$ENDREGION}
  {$REGION 'Date and time string - negative offset over date line'}
  // Setup method call parameters
  Source := '2002-09-24T14:30:10.25-11:00';
  ExpectedValue := EncodeDate(2002, 9, 24) + EncodeTime(14, 30, 10, 250);
  ExpectedValue := ExpectedValue + EncodeTime(11, 0, 0, 0);  // First convert to UTC by removing the offset
  ExpectedValue := UtcTimeToLocalTime(ExpectedValue); // Then convert to local from UTC
  // Call the method
  ReturnValue := XMLTimeStamp2DateTime(Source);
  // Validate method results
  CheckEquals(ExpectedValue, ReturnValue, TIME_TOLERANCE, 'XMLTimeStamp2DateTime for date and time string - negative offset (over day change) should return 14:30:10.25 + eleven hours + local time offset, but did not!');
  {$ENDREGION}
  {$ENDREGION}
end;

initialization
  // Register any test cases with the test runner
  RegisterTest(TestTForm1.Suite);
end.

Ответ 3

OmniXML unit OmniXMLUtils содержит множество функций для создания XML до даты и даты для конверсий XML.

function XMLStrToDateTime(nodeValue: XmlString; var value: TDateTime): boolean; overload;
function XMLStrToDateTime(nodeValue: XmlString): TDateTime; overload;
function XMLStrToDateTimeDef(nodeValue: XmlString; defaultValue: TDateTime): TDateTime;
function XMLStrToDate(nodeValue: XmlString; var value: TDateTime): boolean; overload;
function XMLStrToDate(nodeValue: XmlString): TDateTime; overload;
function XMLStrToDateDef(nodeValue: XmlString; defaultValue: TDateTime): TDateTime;
function XMLStrToTime(nodeValue: XmlString; var value: TDateTime): boolean; overload;
function XMLStrToTime(nodeValue: XmlString): TDateTime; overload;
function XMLStrToTimeDef(nodeValue: XmlString; defaultValue: TDateTime): TDateTime;

function XMLDateTimeToStr(value: TDateTime): XmlString;
function XMLDateTimeToStrEx(value: TDateTime): XmlString;
function XMLDateToStr(value: TDateTime): XmlString;
function XMLTimeToStr(value: TDateTime): XmlString;