You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by ee...@apache.org on 2022/06/14 03:28:30 UTC

[arrow] branch master updated: ARROW-16660: [C#] Add support for Time32Array and Time64Array (#13279)

This is an automated email from the ASF dual-hosted git repository.

eerhardt pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/master by this push:
     new 6a936c4ff5 ARROW-16660: [C#] Add support for Time32Array and Time64Array (#13279)
6a936c4ff5 is described below

commit 6a936c4ff5007045e86f65f1a6b6c3c955ad5103
Author: Rishabh Rana <32...@users.noreply.github.com>
AuthorDate: Tue Jun 14 08:58:22 2022 +0530

    ARROW-16660: [C#] Add support for Time32Array and Time64Array (#13279)
    
    Implemented support without using TImespan in C# as it cannot hold values that have the resolution in microseconds & nanoseconds as specified on the Jira.
    
    Lead-authored-by: Rishabh Rana <ri...@gmail.com>
    Co-authored-by: Eric Erhardt <er...@microsoft.com>
    Co-authored-by: ranarish <ri...@squarepoint-capital.com>
    Signed-off-by: Eric Erhardt <er...@microsoft.com>
---
 .../Arrays/ArrowArrayBuilderFactory.cs             |   6 +-
 .../src/Apache.Arrow/Arrays/ArrowArrayFactory.cs   |   6 +-
 csharp/src/Apache.Arrow/Arrays/Time32Array.cs      | 117 +++++++++++++++++++++
 csharp/src/Apache.Arrow/Arrays/Time64Array.cs      | 117 +++++++++++++++++++++
 csharp/src/Apache.Arrow/Ipc/ArrowStreamWriter.cs   |   4 +
 csharp/src/Apache.Arrow/RecordBatch.Builder.cs     |   8 ++
 .../IntegrationCommand.cs                          |  20 +++-
 .../test/Apache.Arrow.Tests/ArrayBuilderTests.cs   |  70 ++++++++++++
 csharp/test/Apache.Arrow.Tests/ArrowArrayTests.cs  |   8 ++
 .../test/Apache.Arrow.Tests/ArrowReaderVerifier.cs |   4 +
 csharp/test/Apache.Arrow.Tests/TableTests.cs       |   2 +-
 csharp/test/Apache.Arrow.Tests/TestData.cs         |  30 +++++-
 dev/archery/archery/integration/datagen.py         |   3 +-
 docs/source/status.rst                             |   2 +-
 14 files changed, 386 insertions(+), 11 deletions(-)

diff --git a/csharp/src/Apache.Arrow/Arrays/ArrowArrayBuilderFactory.cs b/csharp/src/Apache.Arrow/Arrays/ArrowArrayBuilderFactory.cs
index e7360942f3..618ac0212e 100644
--- a/csharp/src/Apache.Arrow/Arrays/ArrowArrayBuilderFactory.cs
+++ b/csharp/src/Apache.Arrow/Arrays/ArrowArrayBuilderFactory.cs
@@ -56,6 +56,10 @@ namespace Apache.Arrow
                     return new Date64Array.Builder();
                 case ArrowTypeId.Date32:
                     return new Date32Array.Builder();
+                case ArrowTypeId.Time32:
+                    return new Time32Array.Builder(dataType as Time32Type);
+                case ArrowTypeId.Time64:
+                    return new Time64Array.Builder(dataType as Time64Type);
                 case ArrowTypeId.List:
                     return new ListArray.Builder(dataType as ListType);
                 case ArrowTypeId.Decimal128:
@@ -69,8 +73,6 @@ namespace Apache.Arrow
                 case ArrowTypeId.HalfFloat:
                 case ArrowTypeId.Interval:
                 case ArrowTypeId.Map:
-                case ArrowTypeId.Time32:
-                case ArrowTypeId.Time64:
                 default:
                     throw new NotSupportedException($"An ArrowArrayBuilder cannot be built for type {dataType.TypeId}.");
             }
diff --git a/csharp/src/Apache.Arrow/Arrays/ArrowArrayFactory.cs b/csharp/src/Apache.Arrow/Arrays/ArrowArrayFactory.cs
index 2b74709e73..819540a46a 100644
--- a/csharp/src/Apache.Arrow/Arrays/ArrowArrayFactory.cs
+++ b/csharp/src/Apache.Arrow/Arrays/ArrowArrayFactory.cs
@@ -65,6 +65,10 @@ namespace Apache.Arrow
                     return new Date64Array(data);
                 case ArrowTypeId.Date32:
                     return new Date32Array(data);
+                case ArrowTypeId.Time32:
+                    return new Time32Array(data);
+                case ArrowTypeId.Time64:
+                    return new Time64Array(data);
                 case ArrowTypeId.Decimal128:
                     return new Decimal128Array(data);
                 case ArrowTypeId.Decimal256:
@@ -74,8 +78,6 @@ namespace Apache.Arrow
                 case ArrowTypeId.HalfFloat:
                 case ArrowTypeId.Interval:
                 case ArrowTypeId.Map:
-                case ArrowTypeId.Time32:
-                case ArrowTypeId.Time64:
                 default:
                     throw new NotSupportedException($"An ArrowArray cannot be built for type {data.DataType.TypeId}.");
             }
diff --git a/csharp/src/Apache.Arrow/Arrays/Time32Array.cs b/csharp/src/Apache.Arrow/Arrays/Time32Array.cs
new file mode 100644
index 0000000000..bdaf64d556
--- /dev/null
+++ b/csharp/src/Apache.Arrow/Arrays/Time32Array.cs
@@ -0,0 +1,117 @@
+// Licensed to the Apache Software Foundation (ASF) under one or moreDate32Array
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using Apache.Arrow.Types;
+using System.IO;
+
+namespace Apache.Arrow
+{
+    /// <summary>
+    /// The <see cref="Time32Array"/> class holds an array of <see cref="Int32" />, where each value is
+    /// stored as the number of seconds/ milliseconds (depending on the Time32Type) since midnight.
+    /// </summary>
+    public class Time32Array : PrimitiveArray<int>
+    {
+        /// <summary>
+        /// The <see cref="Builder"/> class can be used to fluently build <see cref="Time32Array"/> objects.
+        /// </summary>
+        public class Builder : PrimitiveArrayBuilder<int, Time32Array, Builder>
+        {
+            protected override Time32Array Build(
+                ArrowBuffer valueBuffer, ArrowBuffer nullBitmapBuffer,
+                int length, int nullCount, int offset) =>
+                new Time32Array(DataType, valueBuffer, nullBitmapBuffer, length, nullCount, offset);
+            
+            protected Time32Type DataType { get; }
+
+            public Builder()
+                : this(Time32Type.Default) { }
+
+            public Builder(TimeUnit unit)
+                : this(new Time32Type(unit)) { }
+
+            /// <summary>
+            /// Construct a new instance of the <see cref="Builder"/> class.
+            /// </summary>
+            public Builder(Time32Type type)
+                : base()
+            {
+                DataType = type;
+            }
+        }
+
+        public Time32Array(
+            Time32Type type,
+            ArrowBuffer valueBuffer, ArrowBuffer nullBitmapBuffer,
+            int length, int nullCount, int offset)
+            : this(new ArrayData(type, length, nullCount, offset,
+                new[] { nullBitmapBuffer, valueBuffer }))
+        { }
+
+        public Time32Array(ArrayData data)
+            : base(data)
+        {
+            data.EnsureDataType(ArrowTypeId.Time32);
+        }
+
+        public override void Accept(IArrowArrayVisitor visitor) => Accept(this, visitor);
+
+        /// <summary>
+        /// Get the time at the specified index as seconds
+        /// </summary>
+        /// <param name="index">Index at which to get the time.</param>
+        /// <returns>Returns an <see cref="Int32" />, or <c>null</c> if there is no object at that index.
+        /// </returns>
+        public int? GetSeconds(int index)
+        {
+            int? value = GetValue(index);
+            if (value == null)
+            {
+                return null;
+            }
+
+            var unit = ((Time32Type) Data.DataType).Unit;
+            return unit switch
+            {
+                TimeUnit.Second => value,
+                TimeUnit.Millisecond => value / 1_000,
+                _ => throw new InvalidDataException($"Unsupported time unit for Time32Type: {unit}")
+            };
+        }
+
+        /// <summary>
+        /// Get the time at the specified index as milliseconds
+        /// </summary>
+        /// <param name="index">Index at which to get the time.</param>
+        /// <returns>Returns an <see cref="Int32" />, or <c>null</c> if there is no object at that index.
+        /// </returns>
+        public int? GetMilliSeconds(int index)
+        {
+            int? value = GetValue(index);
+            if (value == null)
+            {
+                return null;
+            }
+
+            var unit = ((Time32Type)Data.DataType).Unit;
+            return unit switch
+            {
+                TimeUnit.Second => value * 1_000,
+                TimeUnit.Millisecond => value,
+                _ => throw new InvalidDataException($"Unsupported time unit for Time32Type: {unit}")
+            };
+        }
+    }
+}
diff --git a/csharp/src/Apache.Arrow/Arrays/Time64Array.cs b/csharp/src/Apache.Arrow/Arrays/Time64Array.cs
new file mode 100644
index 0000000000..127db63a7e
--- /dev/null
+++ b/csharp/src/Apache.Arrow/Arrays/Time64Array.cs
@@ -0,0 +1,117 @@
+// Licensed to the Apache Software Foundation (ASF) under one or moreDate32Array
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using Apache.Arrow.Types;
+using System.IO;
+
+namespace Apache.Arrow
+{
+    /// <summary>
+    /// The <see cref="Time64Array"/> class holds an array of <see cref="Int64" />, where each value is
+    /// stored as the number of microseconds/nanoseconds (depending on the Time64Type) since midnight.
+    /// </summary>
+    public class Time64Array : PrimitiveArray<long>
+    {
+        /// <summary>
+        /// The <see cref="Builder"/> class can be used to fluently build <see cref="Time64Array"/> objects.
+        /// </summary>
+        public class Builder : PrimitiveArrayBuilder<long, Time64Array, Builder>
+        {
+            protected override Time64Array Build(
+                ArrowBuffer valueBuffer, ArrowBuffer nullBitmapBuffer,
+                int length, int nullCount, int offset) =>
+                new Time64Array(DataType, valueBuffer, nullBitmapBuffer, length, nullCount, offset);
+
+            protected Time64Type DataType { get; }
+
+            public Builder()
+                : this(Time64Type.Default) { }
+
+            public Builder(TimeUnit unit)
+                : this(new Time64Type(unit)) { }
+
+            /// <summary>
+            /// Construct a new instance of the <see cref="Builder"/> class.
+            /// </summary>
+            public Builder(Time64Type type)
+                : base()
+            {
+                DataType = type;
+            }
+        }
+
+        public Time64Array(
+            Time64Type type,
+            ArrowBuffer valueBuffer, ArrowBuffer nullBitmapBuffer,
+            int length, int nullCount, int offset)
+            : this(new ArrayData(type, length, nullCount, offset,
+                new[] { nullBitmapBuffer, valueBuffer }))
+        { }
+
+        public Time64Array(ArrayData data)
+            : base(data)
+        {
+            data.EnsureDataType(ArrowTypeId.Time64);
+        }
+
+        public override void Accept(IArrowArrayVisitor visitor) => Accept(this, visitor);
+
+        /// <summary>
+        /// Get the time at the specified index as microseconds
+        /// </summary>
+        /// <param name="index">Index at which to get the time.</param>
+        /// <returns>Returns a <see cref="Int64" />, or <c>null</c> if there is no object at that index.
+        /// </returns>
+        public long? GetMicroSeconds(int index)
+        {
+            long? value = GetValue(index);
+            if (value == null)
+            {
+                return null;
+            }
+
+            var unit = ((Time64Type)Data.DataType).Unit;
+            return unit switch
+            {
+                TimeUnit.Microsecond => value,
+                TimeUnit.Nanosecond => value / 1_000,
+                _ => throw new InvalidDataException($"Unsupported time unit for Time64Type: {unit}")
+            };
+        }
+
+        /// <summary>
+        /// Get the time at the specified index as nanoseconds
+        /// </summary>
+        /// <param name="index">Index at which to get the time.</param>
+        /// <returns>Returns a <see cref="Int64" />, or <c>null</c> if there is no object at that index.
+        /// </returns>
+        public long? GetNanoSeconds(int index)
+        {
+            long? value = GetValue(index);
+            if (value == null)
+            {
+                return null;
+            }
+
+            var unit = ((Time64Type)Data.DataType).Unit;
+            return unit switch
+            {
+                TimeUnit.Microsecond => value * 1_000,
+                TimeUnit.Nanosecond => value,
+                _ => throw new InvalidDataException($"Unsupported time unit for Time64Type: {unit}")
+            };
+        }
+    }
+}
diff --git a/csharp/src/Apache.Arrow/Ipc/ArrowStreamWriter.cs b/csharp/src/Apache.Arrow/Ipc/ArrowStreamWriter.cs
index ec1ad31f3a..0ffc856961 100644
--- a/csharp/src/Apache.Arrow/Ipc/ArrowStreamWriter.cs
+++ b/csharp/src/Apache.Arrow/Ipc/ArrowStreamWriter.cs
@@ -44,6 +44,8 @@ namespace Apache.Arrow.Ipc
             IArrowArrayVisitor<TimestampArray>,
             IArrowArrayVisitor<Date32Array>,
             IArrowArrayVisitor<Date64Array>,
+            IArrowArrayVisitor<Time32Array>,
+            IArrowArrayVisitor<Time64Array>,
             IArrowArrayVisitor<ListArray>,
             IArrowArrayVisitor<StringArray>,
             IArrowArrayVisitor<BinaryArray>,
@@ -91,6 +93,8 @@ namespace Apache.Arrow.Ipc
             public void Visit(BooleanArray array) => CreateBuffers(array);
             public void Visit(Date32Array array) => CreateBuffers(array);
             public void Visit(Date64Array array) => CreateBuffers(array);
+            public void Visit(Time32Array array) => CreateBuffers(array);
+            public void Visit(Time64Array array) => CreateBuffers(array);
 
             public void Visit(ListArray array)
             {
diff --git a/csharp/src/Apache.Arrow/RecordBatch.Builder.cs b/csharp/src/Apache.Arrow/RecordBatch.Builder.cs
index c50bf1ace4..b54c0fdfea 100644
--- a/csharp/src/Apache.Arrow/RecordBatch.Builder.cs
+++ b/csharp/src/Apache.Arrow/RecordBatch.Builder.cs
@@ -52,6 +52,14 @@ namespace Apache.Arrow
                     new Decimal256Array.Builder(type), action);
             public Date32Array Date32(Action<Date32Array.Builder> action) => Build<Date32Array, Date32Array.Builder>(new Date32Array.Builder(), action);
             public Date64Array Date64(Action<Date64Array.Builder> action) => Build<Date64Array, Date64Array.Builder>(new Date64Array.Builder(), action);
+            public Time32Array Time32(Action<Time32Array.Builder> action) => Build<Time32Array, Time32Array.Builder>(new Time32Array.Builder(), action);
+            public Time32Array Time32(Time32Type type, Action<Time32Array.Builder> action) =>
+                Build<Time32Array, Time32Array.Builder>(
+                    new Time32Array.Builder(type), action);
+            public Time64Array Time64(Action<Time64Array.Builder> action) => Build<Time64Array, Time64Array.Builder>(new Time64Array.Builder(), action);
+            public Time64Array Time64(Time64Type type, Action<Time64Array.Builder> action) =>
+                Build<Time64Array, Time64Array.Builder>(
+                    new Time64Array.Builder(type), action);
             public BinaryArray Binary(Action<BinaryArray.Builder> action) => Build<BinaryArray, BinaryArray.Builder>(new BinaryArray.Builder(), action);
             public StringArray String(Action<StringArray.Builder> action) => Build<StringArray, StringArray.Builder>(new StringArray.Builder(), action);
             public TimestampArray Timestamp(Action<TimestampArray.Builder> action) => Build<TimestampArray, TimestampArray.Builder>(new TimestampArray.Builder(), action);
diff --git a/csharp/test/Apache.Arrow.IntegrationTest/IntegrationCommand.cs b/csharp/test/Apache.Arrow.IntegrationTest/IntegrationCommand.cs
index d456624199..cb5aa74f93 100644
--- a/csharp/test/Apache.Arrow.IntegrationTest/IntegrationCommand.cs
+++ b/csharp/test/Apache.Arrow.IntegrationTest/IntegrationCommand.cs
@@ -266,6 +266,8 @@ namespace Apache.Arrow.IntegrationTest
             IArrowTypeVisitor<Decimal256Type>,
             IArrowTypeVisitor<Date32Type>,
             IArrowTypeVisitor<Date64Type>,
+            IArrowTypeVisitor<Time32Type>,
+            IArrowTypeVisitor<Time64Type>,
             IArrowTypeVisitor<TimestampType>,
             IArrowTypeVisitor<StringType>,
             IArrowTypeVisitor<BinaryType>,
@@ -310,6 +312,8 @@ namespace Apache.Arrow.IntegrationTest
             public void Visit(UInt64Type type) => GenerateLongArray<ulong, UInt64Array>((v, n, c, nc, o) => new UInt64Array(v, n, c, nc, o), s => ulong.Parse(s));
             public void Visit(FloatType type) => GenerateArray<float, FloatArray>((v, n, c, nc, o) => new FloatArray(v, n, c, nc, o));
             public void Visit(DoubleType type) => GenerateArray<double, DoubleArray>((v, n, c, nc, o) => new DoubleArray(v, n, c, nc, o));
+            public void Visit(Time32Type type) => GenerateArray<int, Time32Array>((v, n, c, nc, o) => new Time32Array(type, v, n, c, nc, o));
+            public void Visit(Time64Type type) => GenerateLongArray<long, Time64Array>((v, n, c, nc, o) => new Time64Array(type, v, n, c, nc, o), s => long.Parse(s));
 
             public void Visit(Decimal128Type type)
             {
@@ -393,7 +397,21 @@ namespace Apache.Arrow.IntegrationTest
 
             public void Visit(TimestampType type)
             {
-                throw new NotImplementedException();
+                ArrowBuffer validityBuffer = GetValidityBuffer(out int nullCount);
+
+                ArrowBuffer.Builder<long> valueBuilder = new ArrowBuffer.Builder<long>(JsonFieldData.Count);
+                var json = JsonFieldData.Data.GetRawText();
+                string[] values = JsonSerializer.Deserialize<string[]>(json, s_options);
+
+                foreach (string value in values)
+                {
+                    valueBuilder.Append(long.Parse(value));
+                }
+                ArrowBuffer valueBuffer = valueBuilder.Build();
+
+                Array = new TimestampArray(
+                    type, valueBuffer, validityBuffer,
+                    JsonFieldData.Count, nullCount, 0);
             }
 
             public void Visit(StringType type)
diff --git a/csharp/test/Apache.Arrow.Tests/ArrayBuilderTests.cs b/csharp/test/Apache.Arrow.Tests/ArrayBuilderTests.cs
index 0b0754c506..0c58617ad1 100644
--- a/csharp/test/Apache.Arrow.Tests/ArrayBuilderTests.cs
+++ b/csharp/test/Apache.Arrow.Tests/ArrayBuilderTests.cs
@@ -38,6 +38,8 @@ namespace Apache.Arrow.Tests
             TestArrayBuilder<UInt64Array, UInt64Array.Builder>(x => x.Append(10).Append(20).Append(30));
             TestArrayBuilder<FloatArray, FloatArray.Builder>(x => x.Append(10).Append(20).Append(30));
             TestArrayBuilder<DoubleArray, DoubleArray.Builder>(x => x.Append(10).Append(20).Append(30));
+            TestArrayBuilder<Time32Array, Time32Array.Builder>(x => x.Append(10).Append(20).Append(30));
+            TestArrayBuilder<Time64Array, Time64Array.Builder>(x => x.Append(10).Append(20).Append(30));
         }
 
         [Fact]
@@ -54,6 +56,8 @@ namespace Apache.Arrow.Tests
             TestArrayBuilder<UInt64Array, UInt64Array.Builder>(x => x.Append(123).AppendNull().AppendNull().Append(456), 4, 2, 0x09);
             TestArrayBuilder<FloatArray, FloatArray.Builder>(x => x.Append(123).AppendNull().AppendNull().Append(456), 4, 2, 0x09);
             TestArrayBuilder<DoubleArray, DoubleArray.Builder>(x => x.Append(123).AppendNull().AppendNull().Append(456), 4, 2, 0x09);
+            TestArrayBuilder<Time32Array, Time32Array.Builder>(x => x.Append(123).AppendNull().AppendNull().Append(456), 4, 2, 0x09);
+            TestArrayBuilder<Time64Array, Time64Array.Builder>(x => x.Append(123).AppendNull().AppendNull().Append(456), 4, 2, 0x09);
         }
 
         [Fact]
@@ -221,6 +225,72 @@ namespace Apache.Arrow.Tests
             }
         }
 
+        public class Time32ArrayBuilder
+        {
+            [Fact]
+            public void ProducesExpectedArray()
+            {
+                var time32Type = new Time32Type(TimeUnit.Second);
+                var array = new Time32Array.Builder(time32Type)
+                    .Append(1)
+                    .Build();
+
+                Assert.Equal(1, array.Length);
+                var valueSeconds = array.GetSeconds(0);
+                Assert.NotNull(valueSeconds);
+                Assert.Equal(1, valueSeconds.Value);
+                var valueMilliSeconds = array.GetMilliSeconds(0);
+                Assert.NotNull(valueMilliSeconds);
+                Assert.Equal(1_000, valueMilliSeconds.Value);
+
+                time32Type = new Time32Type(TimeUnit.Millisecond);
+                array = new Time32Array.Builder(time32Type)
+                    .Append(1_000)
+                    .Build();
+
+                Assert.Equal(1, array.Length);
+                valueSeconds = array.GetSeconds(0);
+                Assert.NotNull(valueSeconds);
+                Assert.Equal(1, valueSeconds.Value);
+                valueMilliSeconds = array.GetMilliSeconds(0);
+                Assert.NotNull(valueMilliSeconds);
+                Assert.Equal(1_000, valueMilliSeconds.Value);
+            }
+        }
+
+        public class Time64ArrayBuilder
+        {
+            [Fact]
+            public void ProducesExpectedArray()
+            {
+                var time64Type = new Time64Type(TimeUnit.Microsecond);
+                var array = new Time64Array.Builder(time64Type)
+                    .Append(1_000_000)
+                    .Build();
+
+                Assert.Equal(1, array.Length);
+                var valueMicroSeconds = array.GetMicroSeconds(0);
+                Assert.NotNull(valueMicroSeconds);
+                Assert.Equal(1_000_000, valueMicroSeconds.Value);
+                var valueNanoSeconds = array.GetNanoSeconds(0);
+                Assert.NotNull(valueNanoSeconds);
+                Assert.Equal(1_000_000_000, valueNanoSeconds.Value);
+
+                time64Type = new Time64Type(TimeUnit.Nanosecond);
+                array = new Time64Array.Builder(time64Type)
+                    .Append(1_000_000_000)
+                    .Build();
+
+                Assert.Equal(1, array.Length);
+                valueMicroSeconds = array.GetMicroSeconds(0);
+                Assert.NotNull(valueMicroSeconds);
+                Assert.Equal(1_000_000, valueMicroSeconds.Value);
+                valueNanoSeconds = array.GetNanoSeconds(0);
+                Assert.NotNull(valueNanoSeconds);
+                Assert.Equal(1_000_000_000, valueNanoSeconds.Value);
+            }
+        }
+
         private static TArray TestArrayBuilder<TArray, TArrayBuilder>(Action<TArrayBuilder> action, int expectedLength = 3, int expectedNullCount = 0, int expectedNulls = 0)
             where TArray : IArrowArray
             where TArrayBuilder : IArrowArrayBuilder<TArray>, new()
diff --git a/csharp/test/Apache.Arrow.Tests/ArrowArrayTests.cs b/csharp/test/Apache.Arrow.Tests/ArrowArrayTests.cs
index 18d405613b..1e3d4b12f5 100644
--- a/csharp/test/Apache.Arrow.Tests/ArrowArrayTests.cs
+++ b/csharp/test/Apache.Arrow.Tests/ArrowArrayTests.cs
@@ -107,6 +107,8 @@ namespace Apache.Arrow.Tests
             TestSlice<DoubleArray, DoubleArray.Builder>(x => x.Append(10).Append(20).Append(30));
             TestSlice<Date32Array, Date32Array.Builder>(x => x.Append(new DateTime(2019, 1, 1)).Append(new DateTime(2019, 1, 2)).Append(new DateTime(2019, 1, 3)));
             TestSlice<Date64Array, Date64Array.Builder>(x => x.Append(new DateTime(2019, 1, 1)).Append(new DateTime(2019, 1, 2)).Append(new DateTime(2019, 1, 3)));
+            TestSlice<Time32Array, Time32Array.Builder>(x => x.Append(10).Append(20).Append(30));
+            TestSlice<Time64Array, Time64Array.Builder>(x => x.Append(10).Append(20).Append(30));
             TestSlice<StringArray, StringArray.Builder>(x => x.Append("10").Append("20").Append("30"));
         }
 
@@ -125,6 +127,8 @@ namespace Apache.Arrow.Tests
             TestSlice<DoubleArray, DoubleArray.Builder>(x => x.Append(10).Append(20).AppendNull().Append(30));
             TestSlice<Date32Array, Date32Array.Builder>(x => x.Append(new DateTime(2019, 1, 1)).Append(new DateTime(2019, 1, 2)).AppendNull().Append(new DateTime(2019, 1, 3)));
             TestSlice<Date64Array, Date64Array.Builder>(x => x.Append(new DateTime(2019, 1, 1)).Append(new DateTime(2019, 1, 2)).AppendNull().Append(new DateTime(2019, 1, 3)));
+            TestSlice<Time32Array, Time32Array.Builder>(x => x.Append(10).AppendNull().Append(30));
+            TestSlice<Time64Array, Time64Array.Builder>(x => x.Append(10).AppendNull().Append(30));
         }
 
         [Fact]
@@ -177,6 +181,8 @@ namespace Apache.Arrow.Tests
             IArrowArrayVisitor<UInt64Array>,
             IArrowArrayVisitor<Date32Array>,
             IArrowArrayVisitor<Date64Array>,
+            IArrowArrayVisitor<Time32Array>,
+            IArrowArrayVisitor<Time64Array>,
             IArrowArrayVisitor<FloatArray>,
             IArrowArrayVisitor<DoubleArray>,
             IArrowArrayVisitor<BooleanArray>,
@@ -215,6 +221,8 @@ namespace Apache.Arrow.Tests
 
                 Assert.Equal(baseArray.GetDateTimeOffset(array.Offset), array.GetDateTimeOffset(0));
             }
+            public void Visit(Time32Array array) => ValidateArrays(array);
+            public void Visit(Time64Array array) => ValidateArrays(array);
 
             public void Visit(FloatArray array) => ValidateArrays(array);
             public void Visit(DoubleArray array) => ValidateArrays(array);
diff --git a/csharp/test/Apache.Arrow.Tests/ArrowReaderVerifier.cs b/csharp/test/Apache.Arrow.Tests/ArrowReaderVerifier.cs
index a2c9a9ef7a..4a15bc7117 100644
--- a/csharp/test/Apache.Arrow.Tests/ArrowReaderVerifier.cs
+++ b/csharp/test/Apache.Arrow.Tests/ArrowReaderVerifier.cs
@@ -80,6 +80,8 @@ namespace Apache.Arrow.Tests
             IArrowArrayVisitor<TimestampArray>,
             IArrowArrayVisitor<Date32Array>,
             IArrowArrayVisitor<Date64Array>,
+            IArrowArrayVisitor<Time32Array>,
+            IArrowArrayVisitor<Time64Array>,
             IArrowArrayVisitor<ListArray>,
             IArrowArrayVisitor<StringArray>,
             IArrowArrayVisitor<FixedSizeBinaryArray>,
@@ -114,6 +116,8 @@ namespace Apache.Arrow.Tests
             public void Visit(TimestampArray array) => CompareArrays(array);
             public void Visit(Date32Array array) => CompareArrays(array);
             public void Visit(Date64Array array) => CompareArrays(array);
+            public void Visit(Time32Array array) => CompareArrays(array);
+            public void Visit(Time64Array array) => CompareArrays(array);
             public void Visit(ListArray array) => CompareArrays(array);
             public void Visit(FixedSizeBinaryArray array) => CompareArrays(array);
             public void Visit(Decimal128Array array) => CompareArrays(array);
diff --git a/csharp/test/Apache.Arrow.Tests/TableTests.cs b/csharp/test/Apache.Arrow.Tests/TableTests.cs
index 3e1107c2a5..45a14cc256 100644
--- a/csharp/test/Apache.Arrow.Tests/TableTests.cs
+++ b/csharp/test/Apache.Arrow.Tests/TableTests.cs
@@ -60,7 +60,7 @@ namespace Apache.Arrow.Tests
 
             Table table1 = Table.TableFromRecordBatches(recordBatch1.Schema, recordBatches);
             Assert.Equal(20, table1.RowCount);
-            Assert.Equal(21, table1.ColumnCount);
+            Assert.Equal(23, table1.ColumnCount);
 
             FixedSizeBinaryType type = new FixedSizeBinaryType(17);
             Field newField1 = new Field(type.Name, type, false);
diff --git a/csharp/test/Apache.Arrow.Tests/TestData.cs b/csharp/test/Apache.Arrow.Tests/TestData.cs
index 9b6d0cf8b0..1166f95f3d 100644
--- a/csharp/test/Apache.Arrow.Tests/TestData.cs
+++ b/csharp/test/Apache.Arrow.Tests/TestData.cs
@@ -47,6 +47,8 @@ namespace Apache.Arrow.Tests
                 builder.Field(CreateField(DoubleType.Default, i));
                 builder.Field(CreateField(Date32Type.Default, i));
                 builder.Field(CreateField(Date64Type.Default, i));
+                builder.Field(CreateField(Time32Type.Default, i));
+                builder.Field(CreateField(Time64Type.Default, i));
                 builder.Field(CreateField(TimestampType.Default, i));
                 builder.Field(CreateField(StringType.Default, i));
                 builder.Field(CreateField(new StructType(new List<Field> { CreateField(StringType.Default, i), CreateField(Int32Type.Default, i) }), i));
@@ -61,8 +63,6 @@ namespace Apache.Arrow.Tests
 
                 //builder.Field(CreateField(HalfFloatType.Default));
                 //builder.Field(CreateField(StringType.Default));
-                //builder.Field(CreateField(Time32Type.Default));
-                //builder.Field(CreateField(Time64Type.Default));
             }
 
             Schema schema = builder.Build();
@@ -107,6 +107,8 @@ namespace Apache.Arrow.Tests
             IArrowTypeVisitor<BooleanType>,
             IArrowTypeVisitor<Date32Type>,
             IArrowTypeVisitor<Date64Type>,
+            IArrowTypeVisitor<Time32Type>,
+            IArrowTypeVisitor<Time64Type>,
             IArrowTypeVisitor<Int8Type>,
             IArrowTypeVisitor<Int16Type>,
             IArrowTypeVisitor<Int32Type>,
@@ -199,6 +201,30 @@ namespace Apache.Arrow.Tests
                 Array = builder.Build();
             }
 
+            public void Visit(Time32Type type)
+            {
+                var builder = new Time32Array.Builder(type).Reserve(Length);
+
+                for (var i = 0; i < Length; i++)
+                {
+                    builder.Append(i);
+                }
+
+                Array = builder.Build();
+            }
+
+            public void Visit(Time64Type type)
+            {
+                var builder = new Time64Array.Builder(type).Reserve(Length);
+
+                for (var i = 0; i < Length; i++)
+                {
+                    builder.Append(i);
+                }
+
+                Array = builder.Build();
+            }
+
             public void Visit(TimestampType type)
             {
                 var builder = new TimestampArray.Builder().Reserve(Length);
diff --git a/dev/archery/archery/integration/datagen.py b/dev/archery/archery/integration/datagen.py
index 4221307382..972f4c8265 100644
--- a/dev/archery/archery/integration/datagen.py
+++ b/dev/archery/archery/integration/datagen.py
@@ -1605,8 +1605,7 @@ def get_generated_json_files(tempdir=None):
         .skip_category('JS')
         .skip_category('Rust'),
 
-        generate_datetime_case()
-        .skip_category('C#'),
+        generate_datetime_case(),
 
         generate_duration_case()
         .skip_category('C#')
diff --git a/docs/source/status.rst b/docs/source/status.rst
index 6ea657fc5d..95f018980b 100644
--- a/docs/source/status.rst
+++ b/docs/source/status.rst
@@ -49,7 +49,7 @@ Data Types
 +-------------------+-------+-------+-------+------------+-------+-------+-------+
 | Date32/64         | ✓     | ✓     | ✓     | ✓          |  ✓    |  ✓    | ✓     |
 +-------------------+-------+-------+-------+------------+-------+-------+-------+
-| Time32/64         | ✓     | ✓     | ✓     | ✓          |       |  ✓    | ✓     |
+| Time32/64         | ✓     | ✓     | ✓     | ✓          |  ✓    |  ✓    | ✓     |
 +-------------------+-------+-------+-------+------------+-------+-------+-------+
 | Timestamp         | ✓     | ✓     | ✓     | ✓          |  ✓    |  ✓    | ✓     |
 +-------------------+-------+-------+-------+------------+-------+-------+-------+