Jackson
Jackson

Reputation: 5657

XMLProvider and issue with DateTime

I'm trying to use the XMLProvider to work with a file that uses the AEOI XML format for exchange of tax information.

I'm generating the type from the .xsd file and the relevant bit is:

    <xs:element name="XMLTimeStamp" type="xs:dateTime" id="S1.2">
        <xs:annotation>
            <xs:documentation>Date and time that the XML was originally created.</xs:documentation>
        </xs:annotation>
    </xs:element>

And then in the XML file I have:

<XMLTimeStamp>2022-04-28T10:09:17</XMLTimeStamp>

The provider gets created and used like this:

    type CrsReport = XmlProvider<Schema="X:\\x\\uk_aeoi_submission_v2.0.xsd", 
                                    ResolutionFolder="X:\\x">
    let sample = CrsReport.Load("X:\\x\\9999999999SAMPLE_FILE.xml")
    let ts = sample.MessageData.XmlTimeStamp

However when I try and access the element in question I get this error:

Installed Packages
Fsharp.Data, 4.2.8
Error: System.Exception: Expecting DateTimeOffset in Value, got 2022-04-28T10:09:17
   at Microsoft.FSharp.Core.PrintfModule.PrintFormatToStringThenFail@1439.Invoke(String message) in D:\workspace\_work\1\s\src\fsharp\FSharp.Core\printf.fs:line 1439
   at System.Runtime.CompilerServices.RuntimeHelpers.DispatchTailCalls(IntPtr callersRetAddrSlot, IntPtr callTarget, IntPtr retVal)
   at FSharp.Data.Runtime.TextRuntime.GetNonOptionalValue[T](String name, FSharpOption`1 opt, FSharpOption`1 originalValue)
   at <StartupCode$FSI_0029>.$FSI_0029.main@()

So it looks as if type provider is expecting a DateTimeOffset instead of a DateTime and if I use a sample of XML to generate the provider it correctly identifies the element of the file as a DateTime.

Is this a bug with the provider or am I missing something about how to access or specify these elements?

Having spent some time reading the FSharp.Data code it looks as if it is a deliberate decision to use DateTimeOffset for xs:dateTime values. The value given should parse to either a DateTime or a DateTimeOffset so something must be going on in the conversion process.

Upvotes: 0

Views: 144

Answers (1)

Jackson
Jackson

Reputation: 5657

This looks like a bug in the AsDateTimeOffset function in the TextConversions.fs.

The code is currently as shown below:

  let ParseISO8601FormattedDateTime text cultureInfo =
    match DateTime.TryParse(text, cultureInfo, dateTimeStyles) with
    | true, d -> d |> Some
    | false, _ -> None

  match ParseISO8601FormattedDateTime text cultureInfo with
  | Some d when d.Kind <> DateTimeKind.Unspecified -> 
    match DateTimeOffset.TryParse(text, cultureInfo, dateTimeStyles) with
    | true, dto -> dto |> Some
    | false, _ -> None
  | _ -> None

So what is happening is that when the date is 2022-04-28T10:09:17 them d.Kind is set to DateTimeKind.Unspecified and the code returns None. Changing the date 2022-04-28T10:09:17Z causes the d.Kind to be set and the date gets returned correctly. (So I have a work around for now)

I think that the current code might be over complicating things, it parses the date string twice and I don't see why it needs to worry about d.Kind being set or not. My suggestion for this part of the code would be:

  match ParseISO8601FormattedDateTime text cultureInfo with
  | Some d -> new DateTimeOffset(d) |> Some
  | _ -> None

I did consider doing something like the xs:date conversion does which is to set the Kind to local.

  // Parse ISO 8601 format, fixing time zone if needed
  match ParseISO8601FormattedDateTime text cultureInfo with
  | Some d when d.Kind = DateTimeKind.Unspecified -> new DateTime(d.Ticks, DateTimeKind.Local) |> Some
  | x -> x

So for asDateTimeOffset this would then be

  match ParseISO8601FormattedDateTime text cultureInfo with
  | Some d when d.Kind = DateTimeKind.Unspecified ->
            let d1 = new DateTime(d.Ticks,DateTimeKind.Local)
            new DateTimeOffset(d1) |> Some
  | Some d -> new DateTimeOffset(d) |> Some
  | _ -> None

This doesn't set the Kind in the DateTime internals of the DateTimeOffset, but then DateTimeOffset is designed to:

defines the difference between the current DateTimeOffset instance's date and time and Coordinated Universal Time (UTC)

So the offset encodes this information.

Upvotes: 1

Related Questions