在Delphi XE中将SQL日期转换为日期时间

4
我在谷歌和SO上搜索了很久,但都没有找到可用的解决方法。我正在从SQL Server中的表中检索一个date。我需要将其转换为Delphi中的DateTime对象。问题似乎出现在从SQL中获取的值上。我测试的SQL Server版本是2012(不是R2)。
如果我将查询检索到的值显示为字符串,则会得到:
myQuery.FieldByName('ActiveDate').AsString;

I get the value

2014-06-15

myQuery 是 TADOQuery 类型。

由于以下所有方法均失败,因此 DateTime 似乎不喜欢此格式:

myQuery.FieldByName('ActiveDate').AsDateTime;
StrToDateTime(myQuery.FieldByName('ActiveDate').AsString);
StrToDate(myQuery.FieldByName('ActiveDate').AsString);

捕获到的异常产生的失败信息会被记录,具体如下所示:
''2014-06-15'' is not a valid date and time

有没有不涉及复杂字符串操作,并且不受服务器区域设置影响的解决方案?


请参见Delphi SQL日期读取问题 - LU RD
“所有这些都失败”是什么意思?它们是如何失败的? - Jerry Dodge
@JayDee 我之前看到过同样的帖子,但我看到的都是从一个已建立的日期时间对象转换为字符串。我现在正在尝试做相反的事情。在 Delphi 中,当将字符串转换为日期时间时,可以指定格式吗? - mvanella
1
你的字符串里真的有两组引号吗?那肯定会导致函数出错。 - Jerry Dodge
4
我怀疑你的查询结果并不是一个日期,而是使用 CONVERT() 将日期列转换为字符串,在这种情况下你别无选择,只能对其进行解析。最简单的解决方案是从 SQL 查询中删除 CONVERT,直接返回日期本身,然后在前端应用程序(比如 Delphi 应用)中可以按需使用它。 - Ken White
显示剩余20条评论
3个回答

16

我能够重现这个问题 (SQL Server 2008 R2)。

SQL-Server数据类型 date (2008+) 映射为使用 ADO 提供程序 SQLOLEDB.1TWideStringField (ftWideString)。

使用 .AsDateTime 访问此字段 (TWideStringField) 将会引发一个异常,就像在问题描述中所述的一样:'2014-06-15' 不是有效的日期和时间

以下是相关的(或重复的?)问题: Delphi 6、ADO、MS数据库“Date”字段与ftWideString相同

可能的解决方法:

  • 使用 SQL-Server 原生客户端,例如 Provider=SQLNCLI10.1; -> date 映射到 TDateField
  • SELECT 通过 CAST(ActiveDate AS datetime) ,然后 .AsDateTime 可以正常工作。
  • 在 SQL-Server 上使用 datetime 数据类型而不是 date
  • 通过 @Chris Thornton 提供的 解决方案 访问 date 字段

6

以下是一个函数,应该可以解决问题,感谢David和Remy提供的反馈:

function AnsiDateStrToDate(AnsiDate : string) : TDate;
// Convert data in YYYY-MM-DD to TDate
var
  Fmt : TFormatSettings; // Does not need to be freed
begin
  Fmt := TFormatSettings.Create;
  Fmt.ShortDateFormat := 'YYYY-MM-DD';
  Fmt.DateSeparator := '-';
  result := StrToDate(AnsiDate,Fmt);
end;

使用方法:

if AnsiDateStrToDate(myQuery.FieldByName('ActiveDate').AsString) = Date Then
  writeln('Today!')
Else
  writeln('Not Today!);

2
如果你必须这样做,使用不依赖于全局状态的转换函数。重载接受格式设置参数的函数。但是最好完全避免转换为字符串。 - David Heffernan
1
@DavidHeffernan,同意。在我看来,如果你需要它是一个日期,使用.AsDate而不是.AsString。 - Chris Thornton
1
全局格式变量不是线程安全的,并且在XE6中已经完全删除。如果您要使用FormatSettings正确的方法是使用重载版本的StrToDate(),该版本将TFormatSettings作为输入参数:Fmt := TFormatSettings.Create; Fmt.ShortDateFormat := 'YYYY-MM-DD'; Fmt.DateSeparator := '-'; ... := StrToDate(myQuery.FieldByName('ActiveDate').AsString, Fmt); - Remy Lebeau
2
我已经修复了答案。 - Remy Lebeau
1
@mvanella 哦,好吧,如果我知道你想要基于文本处理的解决方案,我就不应该删除我的答案。我不明白为什么你不想深入了解这个问题的根源。你不担心你不知道这个日期格式来自哪里吗?如果在其他机器上它以不同的格式返回会发生什么?除非你真正知道这个文本来自哪里,否则我看不出你如何继续进行。 - David Heffernan
显示剩余10条评论

0

抱歉,这个回答可能与主题有些偏离,但是它是从克里斯·桑顿的回复中延伸出来的(而这个回复本身有点跑题)。

正如已经指出的那样,在 Delphi XE6 之前的版本中,dateTimeFormat 例程不是线程安全的。此外,在 Delphi 6(或者可能是 7)之前的版本中,TFormatSetting 也不可用。

在 TFormatSettings 存在之前的 Delphi 版本中,如果您不介意线程不安全,可以使用此例程。

function AnsiDateStrToDate(AnsiDate : string) : TDateTime;
// Convert data in YYYY-MM-DD to TDate
// WARNING NOT THREAD SAFE
var
  lFmt_dateSeparator : char;
  lFmt_ShortDateFormat: string;
begin
  lFmt_dateSeparator := DateSeparator;
  lFmt_ShortDateFormat := ShortDateFormat;
  try
    ShortDateFormat := 'YYYY-MM-DD';
    DateSeparator := '-';
    result := StrToDate(AnsiDate);
  finally
     DateSeparator := lFmt_dateSeparator;
     ShortDateFormat := lFmt_ShortDateFormat;
  end
end;

我还进行了一般的重新实现。适用于宽字符串或 ANSI 字符串,支持时间并且是线程安全的(但有点冗长!)。

function SQLDateStrToDate(ASQLDate : string) : TDateTime;
// Convert String in YYYY-MM-DD HH:NN:SS.ZZZ to TDatetime
// in Delphi XE6 and previous format strings are not thread safe
// in Delphi 5 and lower, TFormatSettings are not available
// so this is threadsafe, if a little inelegant;
var
  lDate, lTime :string;
  p,c : integer;
  y,d,m,h,n,s,z : word;
begin
  result := 0;
  p := Pos('-',ASQLDate);
  if p<1 then exit;
  lDate := trim(ASQLDate);
   // Time and Date separation
  p := pos(' ',lDate);
  lTime := '';
  if (p>0) then
  begin
    lTime := copy(lDate,p+1,MaxInt);
    lDate := copy(lDate,1,p-1);
  end;
  p := pos('-',lDate);

  // Date must be of the format 'YYYY-MM-DD'
  if (p<>5) then exit;
  Val(Copy(lDate,1,4),y,c);
  if c<1 then
  begin
    Val(Copy(ldate,6,2),m,c);
    if c<1 then Val(Copy(ldate,9,2),d,c);
    Result := EncodeDate(y,m,d);
  end;

  // Time must be 24hr
  p := pos(':',lTime);
  if (p<1) then exit;
  h:=0; n:=0; s:=0; z:=0;
  Val(copy(lTime,1,p-1),h,c);
  if c<1 then
  begin
     Val(copy(lTime,4,2),n,c);
     if c>0 then exit;
     Val(copy(lTime,7,2),s,c);
     p := pos('.',lTime);
     if p>0 then Val(copy(lTime,p+1,3),z,c);
     Result := result + EncodeTime(h,n,s,z);
   end;
end;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接