在 Delphi 中是否有将 XML 日期和时间转换为 TDateTime 的函数
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1438870/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
In Delphi is there a function to convert XML date and time to TDateTime
提问by Charles Faiga
XML date and time are in the format
XML 日期和时间的格式为
'-'? yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)? (zzzzzz)?
'-'?yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)?(zzzzzz)?
were
是
?'-'? yyyy is a four-or-more digit optionally negative-signed numeral that represents the year; if more than four digits, leading zeros are prohibited, and '0000' is prohibited
?'-'?yyyy 是一个四位或更多位的可选负号数字,表示年份;如果超过四位,则禁止前导零,禁止'0000'
?the remaining '-'s are separators between parts of the date portion;
? 其余的“-”是日期部分之间的分隔符;
?the first mm is a two-digit numeral that represents the month;
?第一个mm是代表月份的两位数字;
?dd is a two-digit numeral that represents the day;
?dd 是代表日期的两位数字;
?'T' is a separator indicating that time-of-day follows;
?'T' 是一个分隔符,表示时间紧随其后;
?hh is a two-digit numeral that represents the hour; '24' is permitted if the minutes and seconds represented are zero, and the dateTime value so represented is the first instant of the following day (the hour property of a dateTime object in the ·value space· cannot have a value greater than 23);
?hh 是代表小时的两位数字;如果表示的分钟和秒为零,则允许使用“24”,并且如此表示的 dateTime 值是第二天的第一个时刻(·值空间·中 dateTime 对象的小时属性不能具有大于 23 的值) ;
?':' is a separator between parts of the time-of-day portion;
?':' 是时间部分之间的分隔符;
?the second mm is a two-digit numeral that represents the minute;
?秒mm是代表分钟的两位数字;
?ss is a two-integer-digit numeral that represents the whole seconds;
?ss 是代表整秒的两位整数数字;
?'.' s+ (if present) represents the fractional seconds;
?'.' s+(如果存在)表示小数秒;
?zzzzzz (if present) represents the timezone (as described below).
?zzzzzz(如果存在)表示时区(如下所述)。
here are more examples
这里有更多例子
Simple Example 2009-08-31T19:30:00
简单示例 2009-08-31T19:30:00
More complex examples
更复杂的例子
2002-10-10T12:00:00-05:00(noon on 10 October 2002, Central Daylight Savings Time as well as Eastern Standard Time in the U.S.) is 2002-10-10T17:00:00Z, five hours later than 2002-10-10T12:00:00Z.
2002-10-10T12:00:00-05:00(2002 年 10 月 10 日中午,美国中部夏令时和东部标准时间)是2002-10-10T17:00:00Z,比2002 年晚五个小时-10-10T12:00:00Z。
see www.w3.org/TR/2004/REC-xmlschema-2-20041028/datatypes.htmlfor more info
有关更多信息,请参见www.w3.org/TR/2004/REC-xmlschema-2-20041028/datatypes.html
回答by Jeroen Wiert Pluimers
Delphi has a XSBuiltInsunit (since Delphi 6) that contains data types that can help you convert some XML data types:
Delphi 有一个XSBuiltIns单元(自 Delphi 6 起),其中包含可以帮助您转换某些 XML 数据类型的数据类型:
(there are more, like TXSDecimal, you get the idea)
(还有更多,比如TXSDecimal,你懂的)
All of these contain at least these two methods:
所有这些至少包含这两种方法:
You can use it like this:
你可以这样使用它:
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;
That should get you going.
那应该能让你继续前进。
--jeroen
--杰罗恩
回答by DelphiUser
This has already been answered ok, but I'll add here somethings I ended up doing in a similar case that I had. From the XSBuiltIns unit I found the method
这已经得到了回答,但我会在这里添加一些我最终在类似案例中所做的事情。从 XSBuiltIns 单元我找到了方法
function XMLTimeToDateTime(const XMLDateTime: InvString; AsUTCTime: Boolean = False): TDateTime;
which seemed to be what I wanted. What I wanted was to be able parse all the different XML time strings defined here: http://www.w3schools.com/schema/schema_dtypes_date.asp
这似乎是我想要的。我想要的是能够解析这里定义的所有不同的 XML 时间字符串:http: //www.w3schools.com/schema/schema_dtypes_date.asp
This includes strings with just Date, just Time, or both Date and Time, and all these with the options of specified time zone or UTC time or local for the source string, and return value as local time. Also, when given a time only, I wanted it to always be within the "day zero", i.e after the operation the whole part of the returned TDateTime (cast to real number) to be zero.
这包括只有日期、只有时间或日期和时间的字符串,所有这些都带有指定时区或 UTC 时间或源字符串的本地选项,并返回本地时间值。此外,当只给出一个时间时,我希望它始终在“零日”内,即在操作之后返回的 TDateTime(转换为实数)的整个部分为零。
Finally, I wanted the function to return DateTime.MinValue on erroneous input (mainly when given an empty string).
最后,我希望函数在错误输入时返回 DateTime.MinValue(主要是在给定空字符串时)。
I'm not certain whether I was using the function differently than it is specified, but at least it unfortunately failed in several places for me. I ended up making my own function around this one, which covered all the cases I encountered and now I'm ok going forward. It can be argued that maybe I would've been better off writing the entire parsing myself since it couldn't have been much more complex than the working-around-of-problems that I ended up doing, but at least for the time being, I'm going with what I have and decided to post it here also in case someone else finds any of this useful.
我不确定我使用的功能是否与指定的不同,但至少不幸的是它在几个地方失败了。我最终围绕这个创建了自己的函数,它涵盖了我遇到的所有情况,现在我可以继续前进了。可以争辩说,也许我自己编写整个解析会更好,因为它不可能比我最终做的解决问题复杂得多,但至少在目前,我将使用我所拥有的并决定将其发布在这里,以防其他人发现这些有用。
Main problem points (I may already forget some):
主要问题点(我可能已经忘记了一些):
- Empty string results in DateTime corresponding to a date in Year 1, while Delphi's MinDateTime is in Year 100.
- Strings with only Date are always considered UTC regardless of the presence or absence of 'Z' or explicit time zone definitions.
- Strings with only Time are erroneously identified as Date strings and wrongly parsed.
- Time zone modifiers are applied ONLY if explicitly defined, otherwise all are assumed to be UTC, even when there is no 'Z'.
- Fractional seconds are not supported, but rather milliseconds are always converted to 0.
- Since only-Time strings are not supported, I had to add a dummy date to them, then ensure it is the current date (to cover DST issues when converting to/from UTC, which in turn had to be done due to the erroneous UTC considerations) and then in the end again subtract it from the result, and finally in these cases ensure the day-zero requirement for only-Time strings.
- 空字符串导致 DateTime 对应于第 1 年的日期,而 Delphi 的 MinDateTime 是在第 100 年。
- 无论是否存在“Z”或明确的时区定义,只有日期的字符串始终被视为 UTC。
- 只有时间的字符串被错误地识别为日期字符串并被错误地解析。
- 时区修饰符仅在明确定义时应用,否则所有都被假定为 UTC,即使没有“Z”。
- 不支持小数秒,而是始终将毫秒转换为 0。
- 由于不支持 only-Time 字符串,我必须向它们添加一个虚拟日期,然后确保它是当前日期(以在转换为/从 UTC 时覆盖 DST 问题,而由于错误的 UTC 而必须这样做考虑),然后最后再次从结果中减去它,最后在这些情况下确保仅时间字符串的第 0 天要求。
The end result is a function of about 100 lines (including comments etc.), which utilizes a fair amount of helper functions (which should be pretty self-explanatory and which are not the topic of this message :) ). I stripped the relevant code bits to a separate file and the unit tests I used to test this to another one, I'm including both below. Feel free to utilize and comment as necessary. Note that the form and its related using's etc. are just what Delphi put in the demo project I dropped this in, they are in no way needed.
最终结果是一个大约 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's 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's 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.
Then the unit tests are here:
然后单元测试在这里:
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.
回答by gabr
OmniXML's unit OmniXMLUtils contains bunch of funcions to do XML to date and date to XML conversions.
OmniXML的单元 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;

