【问题标题】:Java can't parse the datetime value which from Datasnap serverJava 无法解析来自 Datasnap 服务器的日期时间值
【发布时间】:2014-08-19 10:17:36
【问题描述】:

这个问题来自"DataSnap return empty dataset" 我认为这是另一个问题,所以我在这里创建一个新线程。

我编写了一个 DataSnap 休息服务器,它返回一个带有 TDateTime 字段的数据集。

function TServerMethods1.GetDataSet: TDataset;
begin
  FDQuery1.SQL.Clear;
  FDQuery1.SQL.Text := 'select EmployeeID, LastName, FirstName, BirthDate from Employees';
  FDQuery1.Open();
  Result := FDQuery1;
end;

但是我的 android 客户端 (Java) 无法解析从服务器获取的日期时间值。

我从服务器跟踪Json数据,发现TDatetime类型值:

"BirthDate":["1948-12-08.0","1952-02-19.0","1963-08-30.0"]

每个日期值都附加一个“.0”尾部。所以,Java 无法转换回 Date 类型:

然后,我添加一个新的服务器方法来测试返回 TDateTime 类型:

function TServerMethods1.GetDateTime: TDateTime;
begin
  Result := int(Now);
end;

DataSnap 也把它放到了下面的样式中:

{"result":["2014-06-27.0"]}

那么,如何修复 Java 客户端中的这个 bug?

[更新,临时解决方案]----------

@BasilBourque,谢谢。你给了我一些灵感。我认为如果替换 Java 类或类库是一个很大的工作量。所以我选择了一个简单的解决方案。

现在,我修复了 DataSnap 服务器自动生成的 Java 代码文件“DBXDefaultFormatter.java”。

DBXDefaultFormatter.java,第 257 行, 原代码:

public Date StringToDateTime(String value) throws ParseException {
    return datetimeFormatter.parse(value);
}

固定代码:

public Date StringToDateTime(String value) throws ParseException {
    if ((value.length() == 12) & (value.endsWith(".0"))) {
        return dateFormatter.parse(value);
    } else {
        return datetimeFormatter.parse(value);
    }
}   

我知道,这不是一个完美的解决方案,但基本上就足够了。

【问题讨论】:

  • 这看起来像 datasnap 中的一个错误。 .0 是否意味着什么?
  • @PeterLawrey,这不是错误。结果类型为 TDateTime,因此 .0 表示该值的时间分数。要获取日期,只需将结果类型声明为 TDate。关于数据集,必须选择适当的字段类型来仅表示日期值(取决于数据库)。
  • @UweRaabe 为什么 DataSnap 在将 TDateTime 序列化为 JSON 时不使用 ISO-8601 文本编码,或者使用相应双精度变量的纯浮点值进行传输?如果它“不是错误,而是功能”,对我来说听起来像是一个非常误解的功能。
  • @Uwe Raabe,1. DataSnap 无法导出(ProxyGenerator)TDate 或 TTime 类型。我们在使用 TDatetime 时会遇到类似的“2014-06-28 00:00:00”数据。 Delphi客户端可以解析它,但java不能。 2.我们不能改变Table Field类型来适应这个问题,这不是一个明智的解决方案。 3.我认为最好的解决方案是改变DataSnap输出的json内容,或者让Java可以接受json内容。但是,我仍然无法找出修复条目。
  • 您是否考虑过要求供应商 (RemObjects) 调查您的错误?

标签: java delphi datasnap


【解决方案1】:

是的,在 Java 中工作

可以使用Joda-Time 2.3 库在Java 中解析这些字符串。

删除违规字符

一种方法是去掉最后两个字符。

String input = "1948-12-08.0";
String substring = input.substring( 0, 10 );
LocalDate localDateFromSubstring = new LocalDate( substring );

转储到控制台。

System.out.println( "substring: " + substring );
System.out.println( "localDateFromSubstring: " + localDateFromSubstring );

运行时。

substring: 1948-12-08
localDateFromSubstring: 1948-12-08

容忍违规字符

另一种方法是定义一个需要最后两个字符的自定义格式化程序。如果不存在,格式化程序将抛出异​​常。单引号 (APOSTROPHE-QUOTE) 标记文字字符序列,在我们的例子中为 .0

String input = "1948-12-08.0";
DateTimeFormatter formatter = DateTimeFormat.forPattern( "yyyy-MM-dd'.0'" );
LocalDate localDate = formatter.parseLocalDate( input );

转储到控制台。

System.out.println( "input: " + input );
System.out.println( "localDate: " + localDate );

运行时。

input: 1948-12-08.0
localDate: 1948-12-08

您可以使用与 Java 捆绑在一起的 java.util.Date 和 .Calendar 类来做同样的事情。但是这些课程是出了名的麻烦。避开他们。使用 Joda-Time 或 Java 8 中的 java.time package new。

Joda-Time 和 java.time 都提供了一个 LocalDate 类来表示仅日期,没有任何时间或时区部分。旧的 Java 类没有。

正如问题中的 cmets 所讨论的,DataSnap 选择的这种格式是完全愚蠢的。这里的问题在于 DataSnap,而不是 Java。在序列化日期或日期时间时,字符串表示的明显格式是合理的ISO 8601 标准格式。 Joda-Time 和 java.time 都使用 ISO 8601 作为解析和生成字符串表示的默认格式。

【讨论】:

  • +1 从技术上讲,它不会忽略多余的字符,它需要它们并且如果它们不存在则会出错。
  • @PeterLawrey 感谢您的想法。并入答案。
  • @BasilBourque,谢谢。你给了我一些灵感。
【解决方案2】:

我不知道这是否有帮助,但这里是一个简单项目的代码和 DFM,它从数据集中生成 JSON,而不包括您获得的“.0”(或 TDateTimeField 值的正常格式化时间部分) .

我认为从源头而不是在客户端解决问题更好,特别是因为可能有不止一个客户端实现,而未来的实现可能不在 Java 中。

我不知道你当前是如何生成 JSON 的,所以我的可能不是你想要的格式,但无论如何。

正如我在评论中提到的,您可以在字段的 GetText 事件中处理 TDateTimeFields 的字符串表示形式,但这不会在服务器的其他地方产生您想要的内容(注意我的网格包括字符串值的时间部分) ;

function DataSetToJSONString(ADataSet : TDataSet): String;
var
  JSO,
  JSOOuter : TJsonObject;
  JSA : TJsonArray;
  JSP : TJsonPair;
  I,
  P : Integer;
  S : String;
  AField: TField;
Begin
  JSOOuter := TJsonObject.Create;
  JSA := TJsonArray.Create;
  JSP := TJSONPair.Create('CDS', JSA);
  JSOOuter.AddPair(jsp);

  ADataSet.DisableControls;
  ADataSet.First;
  try
    while not ADataset.Eof do
    begin
      JSO := TJsonObject.Create;
      for I := 0 to ADataSet.FieldCount - 1 do begin
        AField := ADataSet.Fields[I];
        case AField.DataType of
          ftBlob :
          begin
            raise Exception.Create('Unsupported field type');
          end;
          ftDateTime :
          begin
            S := AField.AsString;

            // check whether resulting string contains a space and truncate it 1 char before if so
            P := Pos(' ', S);
            if P > 0 then
              S := Copy(S, 1, P - 1);
          end
          else
            begin
              S := AField.AsString;
            end;
        end;
        JSO.AddPair(TJSonPair.Create(AField.FieldName, S));
      end;

      JSA.AddElement(JSO);
      ADataSet.Next;
    end;
  finally
    ADataSet.EnableControls;
    Result := JSOOuter.ToString;
    JSOouter.Free;
  end;
end;

procedure TForm2.OpenDataSet;
begin
  if CDS.Active then
    CDS.Close;
  CDS.CreateDataSet;

  CDS.Insert;
  CDSName.AsString := 'Row ' + '"' + '1';    // to see what JSON does with double quotes embedded in data
  CDSDate.AsDateTime := Now;
  CDS.Post;

  CDS.Insert;
  CDSName.AsString := 'Row ' + '"' + '2';
  CDSDate.AsDateTime := Now;
  CDS.Post;
end;

procedure TForm2.Button1Click(Sender: TObject);
begin
  OpenDataSet;
  Memo1.Lines.Add(DataSetToJSONString(CDS));
end;

DFM:

object Form2: TForm2
  Left = 304
  Top = 94
  Caption = 'Form2'
  ClientHeight = 318
  ClientWidth = 758
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
  object DBGrid1: TDBGrid
    Left = 26
    Top = 13
    Width = 705
    Height = 120
    DataSource = dsCDS
    TabOrder = 0
    TitleFont.Charset = DEFAULT_CHARSET
    TitleFont.Color = clWindowText
    TitleFont.Height = -11
    TitleFont.Name = 'Tahoma'
    TitleFont.Style = []
  end
  object Memo1: TMemo
    Left = 26
    Top = 168
    Width = 705
    Height = 129
    Font.Charset = ANSI_CHARSET
    Font.Color = clWindowText
    Font.Height = -13
    Font.Name = 'Courier New'
    Font.Style = []
    ParentFont = False
    TabOrder = 1
  end
  object Button1: TButton
    Left = 271
    Top = 139
    Width = 75
    Height = 25
    Caption = 'Button1'
    TabOrder = 2
    OnClick = Button1Click
  end
  object CDS: TClientDataSet
    Aggregates = <>
    IndexFieldNames = 'ID'
    Params = <>
    Left = 48
    Top = 24
    object CDSID: TAutoIncField
      FieldName = 'ID'
    end
    object CDSName: TStringField
      FieldName = 'Name'
    end
    object CDSDate: TDateTimeField
      FieldName = 'Date'
    end
  end
  object dsCDS: TDataSource
    DataSet = CDS
    Left = 112
    Top = 24
  end
end

【讨论】:

    猜你喜欢
    • 2020-08-06
    • 1970-01-01
    • 1970-01-01
    • 2021-02-27
    • 1970-01-01
    • 2012-03-18
    • 1970-01-01
    • 2022-01-03
    • 1970-01-01
    相关资源
    最近更新 更多