Skip to content

Commit

Permalink
Merge pull request #1304 from nhirschey/allow-datetimeoffset-schema
Browse files Browse the repository at this point in the history
Add DateTimeOffset for Csv Schema Inference
  • Loading branch information
dsyme authored Oct 4, 2020
2 parents dad0ecf + 53b7579 commit 478803d
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 32 deletions.
3 changes: 3 additions & 0 deletions docs/content/library/CsvProvider.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,9 @@ specify the units of measure. This will override both `AssumeMissingValues` and
* `date`
* `date?`
* `date option`
* `datetimeoffset`
* `datetimeoffset?`
* `datetimeoffset option`
* `guid`
* `guid?`
* `guid option`
Expand Down
5 changes: 4 additions & 1 deletion src/CommonRuntime/StructuralTypes.fs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace FSharp.Data.Runtime.StructuralTypes
namespace FSharp.Data.Runtime.StructuralTypes

open System
open FSharp.Data.Runtime
Expand Down Expand Up @@ -33,6 +33,7 @@ and [<RequireQualifiedAccess>] InferedTypeTag =
| Json
| DateTime
| TimeSpan
| DateTimeOffset
| Guid
// Collections and sum types
| Collection
Expand Down Expand Up @@ -123,6 +124,7 @@ type InferedTypeTag with
| String -> "String"
| DateTime -> "DateTime"
| TimeSpan -> "TimeSpan"
| DateTimeOffset -> "DateTimeOffset"
| Guid -> "Guid"
| Collection -> "Array"
| Heterogeneous -> "Choice"
Expand All @@ -147,6 +149,7 @@ type InferedTypeTag with
| "String" -> String
| "DateTime" -> DateTime
| "TimeSpan" -> TimeSpan
| "DateTimeOffset" -> DateTimeOffset
| "Guid" -> Guid
| "Array" -> Collection
| "Choice" -> Heterogeneous
Expand Down
55 changes: 29 additions & 26 deletions src/Csv/CsvInference.fs
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,35 @@ open FSharp.Data.Runtime.StructuralInference
/// The schema may be set explicitly. This table specifies the mapping
/// from the names that users can use to the types used.
let private nameToType =
["int" , (typeof<int> , TypeWrapper.None )
"int64", (typeof<int64> , TypeWrapper.None )
"bool", (typeof<bool> , TypeWrapper.None )
"float", (typeof<float> , TypeWrapper.None )
"decimal", (typeof<decimal> , TypeWrapper.None )
"date", (typeof<DateTime>, TypeWrapper.None )
"timespan", (typeof<TimeSpan>, TypeWrapper.None )
"guid", (typeof<Guid> , TypeWrapper.None )
"string", (typeof<String> , TypeWrapper.None )
"int?", (typeof<int> , TypeWrapper.Nullable)
"int64?", (typeof<int64> , TypeWrapper.Nullable)
"bool?", (typeof<bool> , TypeWrapper.Nullable)
"float?", (typeof<float> , TypeWrapper.Nullable)
"decimal?", (typeof<decimal> , TypeWrapper.Nullable)
"date?", (typeof<DateTime>, TypeWrapper.Nullable)
"timespan?", (typeof<TimeSpan>, TypeWrapper.Nullable)
"guid?", (typeof<Guid> , TypeWrapper.Nullable)
"int option", (typeof<int> , TypeWrapper.Option )
"int64 option", (typeof<int64> , TypeWrapper.Option )
"bool option", (typeof<bool> , TypeWrapper.Option )
"float option", (typeof<float> , TypeWrapper.Option )
"decimal option", (typeof<decimal> , TypeWrapper.Option )
"date option", (typeof<DateTime>, TypeWrapper.Option )
"timespan option",(typeof<TimeSpan>, TypeWrapper.Option )
"guid option", (typeof<Guid> , TypeWrapper.Option )
"string option", (typeof<string> , TypeWrapper.Option )]
["int" , (typeof<int> , TypeWrapper.None )
"int64", (typeof<int64> , TypeWrapper.None )
"bool", (typeof<bool> , TypeWrapper.None )
"float", (typeof<float> , TypeWrapper.None )
"decimal", (typeof<decimal> , TypeWrapper.None )
"date", (typeof<DateTime> , TypeWrapper.None )
"datetimeoffset", (typeof<DateTimeOffset>, TypeWrapper.None )
"timespan", (typeof<TimeSpan> , TypeWrapper.None )
"guid", (typeof<Guid> , TypeWrapper.None )
"string", (typeof<String> , TypeWrapper.None )
"int?", (typeof<int> , TypeWrapper.Nullable)
"int64?", (typeof<int64> , TypeWrapper.Nullable)
"bool?", (typeof<bool> , TypeWrapper.Nullable)
"float?", (typeof<float> , TypeWrapper.Nullable)
"decimal?", (typeof<decimal> , TypeWrapper.Nullable)
"date?", (typeof<DateTime> , TypeWrapper.Nullable)
"datetimeoffset?", (typeof<DateTimeOffset>, TypeWrapper.Nullable)
"timespan?", (typeof<TimeSpan> , TypeWrapper.Nullable)
"guid?", (typeof<Guid> , TypeWrapper.Nullable)
"int option", (typeof<int> , TypeWrapper.Option )
"int64 option", (typeof<int64> , TypeWrapper.Option )
"bool option", (typeof<bool> , TypeWrapper.Option )
"float option", (typeof<float> , TypeWrapper.Option )
"decimal option", (typeof<decimal> , TypeWrapper.Option )
"date option", (typeof<DateTime> , TypeWrapper.Option )
"datetimeoffset option",(typeof<DateTimeOffset>, TypeWrapper.Option )
"timespan option", (typeof<TimeSpan> , TypeWrapper.Option )
"guid option", (typeof<Guid> , TypeWrapper.Option )
"string option", (typeof<string> , TypeWrapper.Option )]
|> dict

let private nameAndTypeRegex = lazy Regex(@"^(?<name>.+)\((?<type>.+)\)$", RegexOptions.Compiled ||| RegexOptions.RightToLeft)
Expand Down
3 changes: 3 additions & 0 deletions src/Json/JsonRuntime.fs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,9 @@ type JsonRuntime =
let cultureInfo = TextRuntime.GetCulture cultureStr
fun json -> (JsonConversions.AsDateTimeOffset cultureInfo json).IsSome ||
(JsonConversions.AsDateTime cultureInfo json).IsSome
| InferedTypeTag.DateTimeOffset ->
let cultureInfo = TextRuntime.GetCulture cultureStr
fun json -> (JsonConversions.AsDateTimeOffset cultureInfo json).IsSome
| InferedTypeTag.TimeSpan ->
JsonConversions.AsTimeSpan (TextRuntime.GetCulture cultureStr)
>> Option.isSome
Expand Down
15 changes: 10 additions & 5 deletions tests/FSharp.Data.Tests/CsvProvider.fs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#if INTERACTIVE
#if INTERACTIVE
#r "../../bin/lib/net45/FSharp.Data.dll"
#r "../../packages/test/NUnit/lib/net45/nunit.framework.dll"
#r "../../packages/test/FsUnit/lib/net46/FsUnit.NUnit.dll"
Expand Down Expand Up @@ -98,18 +98,23 @@ let ``Inference of numbers with empty values`` () =
let actual = row.Float1, row.Float2, row.Float3, row.Float4, row.Int, row.Float5, row.Float6, row.Date
actual |> should equal expected

let [<Literal>] csvData = """DateOnly,DateWithOffset,MixedDate
2018-04-21,2018-04-20+10:00,2018-04-19+11:00
2018-04-18,2018-04-17-06:00,2018-04-16"""
let [<Literal>] csvData = """DateOnly,DateWithOffset,MixedDate,OffsetOption (datetimeoffset option),OffsetNullable (datetimeoffset?)
2018-04-21,2018-04-20+10:00,2018-04-19+11:00,2018-04-20+10:00,2018-04-20+10:00
2018-04-18,2018-04-17-06:00,2018-04-16,,"""

[<Test>]
let ``Can infer DateTime and DateTimeOffset types correctly`` () =
let csv = CsvProvider<csvData, ",", InferRows = 0>.GetSample()
let firstRow = csv.Rows |> Seq.head
let secondRow = csv.Rows |> Seq.item 1

firstRow.DateOnly.GetType() |> should equal typeof<DateTime>
firstRow.DateWithOffset.GetType() |> should equal typeof<DateTimeOffset>
firstRow.MixedDate.GetType() |> should equal typeof<DateTime>
firstRow.OffsetOption.GetType() |> should equal typeof<DateTimeOffset Option>
firstRow.OffsetNullable.GetType() |> should equal typeof<DateTimeOffset>
secondRow.OffsetOption |> should equal None
secondRow.OffsetNullable |> should equal null


[<Test>]
Expand Down Expand Up @@ -618,4 +623,4 @@ let ``Parses timespan greater than max as string`` () =
[<Test>]
let ``Parses timespan less than min as string`` () =
let span = row.TimespanOneTickLessThanMinValue
span.GetType() |> should equal (typeof<string>)
span.GetType() |> should equal (typeof<string>)

0 comments on commit 478803d

Please sign in to comment.