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 2020/08/14 23:52:46 UTC
[arrow] branch master updated: ARROW-8581: [C#] Accept and return
DateTime from DateXXArray
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 5677f9e ARROW-8581: [C#] Accept and return DateTime from DateXXArray
5677f9e is described below
commit 5677f9e7aeb5458d22ea799dc3d0bf23a3a388d6
Author: Adam Szmigin <ad...@jetstoneam.com>
AuthorDate: Fri Aug 14 18:51:56 2020 -0500
ARROW-8581: [C#] Accept and return DateTime from DateXXArray
This PR introduces additions to the public API for `Date32Array` and `Date64Array` by accepting and returning `System.DateTime` in additio to `System.DateTimeOffset`.
The rationale for making this change is explained in detail on the [JIRA ticket](https://issues.apache.org/jira/browse/ARROW-8581), but briefly: making this change avoids a class of bugs that manifest based on the user's timezone, and where it is very easy to unknowingly fall into a trap.
Note that the array builders no longer derive from `PrimitiveArrayBuilder<TFrom, TTo, ...>`: that base class is designed for conversion from exactly one outward-facing type to the underlying type, but these builders now have two outward-facing types. As a replacement, I have introduced abstract types `DateArrayBuilder` (specific to dates) and `DelegatingArrayBuilder` (more general-purpose). See inline comments for further details.
Closes #7654 from mr-smidge/arrow-8581/date-array-builder-type-change
Authored-by: Adam Szmigin <ad...@jetstoneam.com>
Signed-off-by: Eric Erhardt <er...@microsoft.com>
---
csharp/src/Apache.Arrow/Arrays/Date32Array.cs | 66 +++++--
csharp/src/Apache.Arrow/Arrays/Date64Array.cs | 77 ++++++--
csharp/src/Apache.Arrow/Arrays/DateArrayBuilder.cs | 209 +++++++++++++++++++++
.../Apache.Arrow/Arrays/DelegatingArrayBuilder.cs | 102 ++++++++++
csharp/test/Apache.Arrow.Tests/ArrowArrayTests.cs | 4 +-
csharp/test/Apache.Arrow.Tests/Date32ArrayTests.cs | 115 ++++++++++--
csharp/test/Apache.Arrow.Tests/Date64ArrayTests.cs | 133 +++++++++++++
.../test/Apache.Arrow.Tests/TestDateAndTimeData.cs | 83 ++++++++
8 files changed, 741 insertions(+), 48 deletions(-)
diff --git a/csharp/src/Apache.Arrow/Arrays/Date32Array.cs b/csharp/src/Apache.Arrow/Arrays/Date32Array.cs
index bb07c9d..35c0065 100644
--- a/csharp/src/Apache.Arrow/Arrays/Date32Array.cs
+++ b/csharp/src/Apache.Arrow/Arrays/Date32Array.cs
@@ -18,13 +18,20 @@ using System;
namespace Apache.Arrow
{
+ /// <summary>
+ /// The <see cref="Date32Array"/> class holds an array of dates in the <c>Date32</c> format, where each date is
+ /// stored as the number of days since the dawn of (UNIX) time.
+ /// </summary>
public class Date32Array : PrimitiveArray<int>
{
- private const long MillisecondsPerDay = 86400000;
+ private static readonly DateTime _epochDate = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Unspecified);
- public class Builder : PrimitiveArrayBuilder<DateTimeOffset, int, Date32Array, Builder>
+ /// <summary>
+ /// The <see cref="Builder"/> class can be used to fluently build <see cref="Date32Array"/> objects.
+ /// </summary>
+ public class Builder : DateArrayBuilder<int, Date32Array, Builder>
{
- internal class DateBuilder : PrimitiveArrayBuilder<int, Date32Array, DateBuilder>
+ private class DateBuilder : PrimitiveArrayBuilder<int, Date32Array, DateBuilder>
{
protected override Date32Array Build(
ArrowBuffer valueBuffer, ArrowBuffer nullBitmapBuffer,
@@ -32,11 +39,23 @@ namespace Apache.Arrow
new Date32Array(valueBuffer, nullBitmapBuffer, length, nullCount, offset);
}
+ /// <summary>
+ /// Construct a new instance of the <see cref="Builder"/> class.
+ /// </summary>
public Builder() : base(new DateBuilder()) { }
- protected override int ConvertTo(DateTimeOffset value)
+ protected override int Convert(DateTime dateTime)
{
- return (int)(value.ToUnixTimeMilliseconds() / MillisecondsPerDay);
+ return (int)(dateTime.Date - _epochDate).TotalDays;
+ }
+
+ protected override int Convert(DateTimeOffset dateTimeOffset)
+ {
+ // The internal value stored for a DateTimeOffset can be thought of as the number of 24-hour "blocks"
+ // of time that have elapsed since the UNIX epoch. This is the same as converting it to UTC first and
+ // then taking the date element from that. It is not the same as what would result from looking at the
+ // DateTimeOffset.Date property.
+ return (int)(dateTimeOffset.UtcDateTime.Date - _epochDate).TotalDays;
}
}
@@ -55,16 +74,39 @@ namespace Apache.Arrow
public override void Accept(IArrowArrayVisitor visitor) => Accept(this, visitor);
- public DateTimeOffset? GetDate(int index)
+ [Obsolete("Use `GetDateTimeOffset()` instead")]
+ public DateTimeOffset? GetDate(int index) => GetDateTimeOffset(index);
+
+ /// <summary>
+ /// Get the date at the specified index in the form of a <see cref="DateTime"/> object.
+ /// </summary>
+ /// <remarks>
+ /// The <see cref="DateTime.Kind"/> property of the returned object is set to
+ /// <see cref="DateTimeKind.Unspecified"/>.
+ /// </remarks>
+ /// <param name="index">Index at which to get the date.</param>
+ /// <returns>Returns a <see cref="DateTime"/> object, or <c>null</c> if there is no object at that index.
+ /// </returns>
+ public DateTime? GetDateTime(int index)
{
int? value = GetValue(index);
+ return value.HasValue
+ ? _epochDate.AddDays(value.Value)
+ : default(DateTime?);
+ }
- if (!value.HasValue)
- {
- return default;
- }
-
- return DateTimeOffset.FromUnixTimeMilliseconds(value.Value * MillisecondsPerDay);
+ /// <summary>
+ /// Get the date at the specified index in the form of a <see cref="DateTimeOffset"/> object.
+ /// </summary>
+ /// <param name="index">Index at which to get the date.</param>
+ /// <returns>Returns a <see cref="DateTimeOffset"/> object, or <c>null</c> if there is no object at that index.
+ /// </returns>
+ public DateTimeOffset? GetDateTimeOffset(int index)
+ {
+ int? value = GetValue(index);
+ return value.HasValue
+ ? new DateTimeOffset(_epochDate.AddDays(value.Value), TimeSpan.Zero)
+ : default(DateTimeOffset?);
}
}
}
diff --git a/csharp/src/Apache.Arrow/Arrays/Date64Array.cs b/csharp/src/Apache.Arrow/Arrays/Date64Array.cs
index 763986d..cf977b2 100644
--- a/csharp/src/Apache.Arrow/Arrays/Date64Array.cs
+++ b/csharp/src/Apache.Arrow/Arrays/Date64Array.cs
@@ -15,13 +15,18 @@
using Apache.Arrow.Types;
using System;
-using System.Collections.Generic;
-using Apache.Arrow.Memory;
namespace Apache.Arrow
{
+ /// <summary>
+ /// The <see cref="Date64Array"/> class holds an array of dates in the <c>Date64</c> format, where each date is
+ /// stored as the number of milliseconds since the dawn of (UNIX) time, excluding leap seconds, in multiples of
+ /// 86400000.
+ /// </summary>
public class Date64Array: PrimitiveArray<long>
{
+ private const long MillisecondsPerDay = 86400000;
+
public Date64Array(
ArrowBuffer valueBuffer, ArrowBuffer nullBitmapBuffer,
int length, int nullCount, int offset)
@@ -29,25 +34,44 @@ namespace Apache.Arrow
new[] { nullBitmapBuffer, valueBuffer }))
{ }
- public class Builder : PrimitiveArrayBuilder<DateTimeOffset, long, Date64Array, Builder>
+ /// <summary>
+ /// The <see cref="Builder"/> class can be used to fluently build <see cref="Date64Array"/> objects.
+ /// </summary>
+ public class Builder : DateArrayBuilder<long, Date64Array, Builder>
{
- internal class DateBuilder: PrimitiveArrayBuilder<long, Date64Array, DateBuilder>
+ private class DateBuilder: PrimitiveArrayBuilder<long, Date64Array, DateBuilder>
{
protected override Date64Array Build(
ArrowBuffer valueBuffer, ArrowBuffer nullBitmapBuffer,
int length, int nullCount, int offset) =>
new Date64Array(valueBuffer, nullBitmapBuffer, length, nullCount, offset);
- }
+ }
+ /// <summary>
+ /// Construct a new instance of the <see cref="Builder"/> class.
+ /// </summary>
public Builder() : base(new DateBuilder()) { }
- protected override long ConvertTo(DateTimeOffset value)
+ protected override long Convert(DateTime dateTime)
+ {
+ var dateTimeOffset = new DateTimeOffset(
+ DateTime.SpecifyKind(dateTime.Date, DateTimeKind.Unspecified),
+ TimeSpan.Zero);
+ return dateTimeOffset.ToUnixTimeMilliseconds();
+ }
+
+ protected override long Convert(DateTimeOffset dateTimeOffset)
{
- return value.ToUnixTimeMilliseconds();
+ // The internal value stored for a DateTimeOffset can be thought of as the number of milliseconds,
+ // in multiples of 86400000, that have passed since the UNIX epoch. It is not the same as what would
+ // result from encoding the date from the DateTimeOffset.Date property.
+ long millis = dateTimeOffset.ToUnixTimeMilliseconds();
+ long days = millis / MillisecondsPerDay;
+ return (millis < 0 ? days - 1 : days) * MillisecondsPerDay;
}
}
- public Date64Array(ArrayData data)
+ public Date64Array(ArrayData data)
: base(data)
{
data.EnsureDataType(ArrowTypeId.Date64);
@@ -55,16 +79,39 @@ namespace Apache.Arrow
public override void Accept(IArrowArrayVisitor visitor) => Accept(this, visitor);
- public DateTimeOffset? GetDate(int index)
+ [Obsolete("Use `GetDateTimeOffset()` instead")]
+ public DateTimeOffset? GetDate(int index) => GetDateTimeOffset(index);
+
+ /// <summary>
+ /// Get the date at the specified index in the form of a <see cref="DateTime"/> object.
+ /// </summary>
+ /// <remarks>
+ /// The <see cref="DateTime.Kind"/> property of the returned object is set to
+ /// <see cref="DateTimeKind.Unspecified"/>.
+ /// </remarks>
+ /// <param name="index">Index at which to get the date.</param>
+ /// <returns>Returns a <see cref="DateTime"/> object, or <c>null</c> if there is no object at that index.
+ /// </returns>
+ public DateTime? GetDateTime(int index)
{
long? value = GetValue(index);
+ return value.HasValue
+ ? DateTimeOffset.FromUnixTimeMilliseconds(value.Value).Date
+ : default(DateTime?);
+ }
- if (!value.HasValue)
- {
- return default;
- }
-
- return DateTimeOffset.FromUnixTimeMilliseconds(value.Value);
+ /// <summary>
+ /// Get the date at the specified index in the form of a <see cref="DateTimeOffset"/> object.
+ /// </summary>
+ /// <param name="index">Index at which to get the date.</param>
+ /// <returns>Returns a <see cref="DateTimeOffset"/> object, or <c>null</c> if there is no object at that index.
+ /// </returns>
+ public DateTimeOffset? GetDateTimeOffset(int index)
+ {
+ long? value = GetValue(index);
+ return value.HasValue
+ ? DateTimeOffset.FromUnixTimeMilliseconds(value.Value)
+ : default(DateTimeOffset?);
}
}
}
diff --git a/csharp/src/Apache.Arrow/Arrays/DateArrayBuilder.cs b/csharp/src/Apache.Arrow/Arrays/DateArrayBuilder.cs
new file mode 100644
index 0000000..4e69f6f
--- /dev/null
+++ b/csharp/src/Apache.Arrow/Arrays/DateArrayBuilder.cs
@@ -0,0 +1,209 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// 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 System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Apache.Arrow
+{
+ /// <summary>
+ /// The <see cref="DateArrayBuilder{TUnderlying,TArray,TBuilder}"/> class is an abstract array builder that can
+ /// accept dates in the form of <see cref="DateTime"/> or <see cref="DateTimeOffset"/> and convert to some
+ /// underlying date representation.
+ /// </summary>
+ public abstract class DateArrayBuilder<TUnderlying, TArray, TBuilder> :
+ DelegatingArrayBuilder<TUnderlying, TArray, TBuilder>,
+ IArrowArrayBuilder<DateTime, TArray, TBuilder>,
+ IArrowArrayBuilder<DateTimeOffset, TArray, TBuilder>
+ where TArray : IArrowArray
+ where TBuilder : class, IArrowArrayBuilder<TArray>
+ {
+ /// <summary>
+ /// Construct a new instance of the <see cref="DateArrayBuilder{TUnderlying,TArray,TBuilder}"/> class.
+ /// </summary>
+ /// <param name="innerBuilder">Inner builder that will produce arrays of type <typeparamref name="TArray"/>.
+ /// </param>
+ protected DateArrayBuilder(IArrowArrayBuilder<TUnderlying, TArray, IArrowArrayBuilder<TArray>> innerBuilder)
+ : base(innerBuilder)
+ { }
+
+ /// <summary>
+ /// Append a date in the form of a <see cref="DateTime"/> object to the array.
+ /// </summary>
+ /// <remarks>
+ /// The value of <see cref="DateTime.Kind"/> on the input does not have any effect on the behaviour of this
+ /// method.
+ /// </remarks>
+ /// <param name="value">Date to add.</param>
+ /// <returns>Returns the builder (for fluent-style composition).</returns>
+ public TBuilder Append(DateTime value)
+ {
+ InnerBuilder.Append(Convert(value));
+ return this as TBuilder;
+ }
+
+ /// <summary>
+ /// Append a date from a <see cref="DateTimeOffset"/> object to the array.
+ /// </summary>
+ /// <remarks>
+ /// Note that to convert the supplied <paramref name="value"/> parameter to a date, it is first converted to
+ /// UTC and the date then taken from the UTC date/time. Depending on the value of its
+ /// <see cref="DateTimeOffset.Offset"/> property, this may not necessarily be the same as the date obtained by
+ /// calling its <see cref="DateTimeOffset.Date"/> property.
+ /// </remarks>
+ /// <param name="value">Date to add.</param>
+ /// <returns>Returns the builder (for fluent-style composition).</returns>
+ public TBuilder Append(DateTimeOffset value)
+ {
+ InnerBuilder.Append(Convert(value));
+ return this as TBuilder;
+ }
+
+ /// <summary>
+ /// Append a span of dates in the form of <see cref="DateTime"/> objects to the array.
+ /// </summary>
+ /// <remarks>
+ /// The value of <see cref="DateTime.Kind"/> on any of the inputs does not have any effect on the behaviour of
+ /// this method.
+ /// </remarks>
+ /// <param name="span">Span of dates to add.</param>
+ /// <returns>Returns the builder (for fluent-style composition).</returns>
+ public TBuilder Append(ReadOnlySpan<DateTime> span)
+ {
+ InnerBuilder.Reserve(span.Length);
+ foreach (var item in span)
+ {
+ InnerBuilder.Append(Convert(item));
+ }
+
+ return this as TBuilder;
+ }
+
+ /// <summary>
+ /// Append a span of dates in the form of <see cref="DateTimeOffset"/> objects to the array.
+ /// </summary>
+ /// <remarks>
+ /// Note that to convert the <see cref="DateTimeOffset"/> objects in the <paramref name="span"/> parameter to
+ /// dates, they are first converted to UTC and the date then taken from the UTC date/times. Depending on the
+ /// value of each <see cref="DateTimeOffset.Offset"/> property, this may not necessarily be the same as the
+ /// date obtained by calling the <see cref="DateTimeOffset.Date"/> property.
+ /// </remarks>
+ /// <param name="span">Span of dates to add.</param>
+ /// <returns>Returns the builder (for fluent-style composition).</returns>
+ public TBuilder Append(ReadOnlySpan<DateTimeOffset> span)
+ {
+ InnerBuilder.Reserve(span.Length);
+ foreach (var item in span)
+ {
+ InnerBuilder.Append(Convert(item));
+ }
+
+ return this as TBuilder;
+ }
+
+ /// <summary>
+ /// Append a null date to the array.
+ /// </summary>
+ /// <returns>Returns the builder (for fluent-style composition).</returns>
+ public TBuilder AppendNull()
+ {
+ InnerBuilder.AppendNull();
+ return this as TBuilder;
+ }
+
+ /// <summary>
+ /// Append a collection of dates in the form of <see cref="DateTime"/> objects to the array.
+ /// </summary>
+ /// <remarks>
+ /// The value of <see cref="DateTime.Kind"/> on any of the inputs does not have any effect on the behaviour of
+ /// this method.
+ /// </remarks>
+ /// <param name="values">Collection of dates to add.</param>
+ /// <returns>Returns the builder (for fluent-style composition).</returns>
+ public TBuilder AppendRange(IEnumerable<DateTime> values)
+ {
+ InnerBuilder.AppendRange(values.Select(Convert));
+ return this as TBuilder;
+ }
+
+ /// <summary>
+ /// Append a collection of dates in the form of <see cref="DateTimeOffset"/> objects to the array.
+ /// </summary>
+ /// <remarks>
+ /// Note that to convert the <see cref="DateTimeOffset"/> objects in the <paramref name="values"/> parameter to
+ /// dates, they are first converted to UTC and the date then taken from the UTC date/times. Depending on the
+ /// value of each <see cref="DateTimeOffset.Offset"/> property, this may not necessarily be the same as the
+ /// date obtained by calling the <see cref="DateTimeOffset.Date"/> property.
+ /// </remarks>
+ /// <param name="values">Collection of dates to add.</param>
+ /// <returns>Returns the builder (for fluent-style composition).</returns>
+ public TBuilder AppendRange(IEnumerable<DateTimeOffset> values)
+ {
+ InnerBuilder.AppendRange(values.Select(Convert));
+ return this as TBuilder;
+ }
+
+ /// <summary>
+ /// Set the value of a date in the form of a <see cref="DateTime"/> object at the specified index.
+ /// </summary>
+ /// <remarks>
+ /// The value of <see cref="DateTime.Kind"/> on the input does not have any effect on the behaviour of this
+ /// method.
+ /// </remarks>
+ /// <param name="index">Index at which to set value.</param>
+ /// <param name="value">Date to set.</param>
+ /// <returns>Returns the builder (for fluent-style composition).</returns>
+ public TBuilder Set(int index, DateTime value)
+ {
+ InnerBuilder.Set(index, Convert(value));
+ return this as TBuilder;
+ }
+
+ /// <summary>
+ /// Set the value of a date in the form of a <see cref="DateTimeOffset"/> object at the specified index.
+ /// </summary>
+ /// <remarks>
+ /// Note that to convert the supplied <paramref name="value"/> parameter to a date, it is first converted to
+ /// UTC and the date then taken from the UTC date/time. Depending on the value of its
+ /// <see cref="DateTimeOffset.Offset"/> property, this may not necessarily be the same as the date obtained by
+ /// calling its <see cref="DateTimeOffset.Date"/> property.
+ /// </remarks>
+ /// <param name="index">Index at which to set value.</param>
+ /// <param name="value">Date to set.</param>
+ /// <returns>Returns the builder (for fluent-style composition).</returns>
+ public TBuilder Set(int index, DateTimeOffset value)
+ {
+ InnerBuilder.Set(index, Convert(value));
+ return this as TBuilder;
+ }
+
+ /// <summary>
+ /// Swap the values of the dates at the specified indices.
+ /// </summary>
+ /// <param name="i">First index.</param>
+ /// <param name="j">Second index.</param>
+ /// <returns>Returns the builder (for fluent-style composition).</returns>
+ public TBuilder Swap(int i, int j)
+ {
+ InnerBuilder.Swap(i, j);
+ return this as TBuilder;
+ }
+
+ protected abstract TUnderlying Convert(DateTime dateTime);
+
+ protected abstract TUnderlying Convert(DateTimeOffset dateTimeOffset);
+ }
+}
diff --git a/csharp/src/Apache.Arrow/Arrays/DelegatingArrayBuilder.cs b/csharp/src/Apache.Arrow/Arrays/DelegatingArrayBuilder.cs
new file mode 100644
index 0000000..f2ab3ee
--- /dev/null
+++ b/csharp/src/Apache.Arrow/Arrays/DelegatingArrayBuilder.cs
@@ -0,0 +1,102 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// 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 System;
+using Apache.Arrow.Memory;
+
+namespace Apache.Arrow
+{
+ /// <summary>
+ /// The <see cref="DelegatingArrayBuilder{T,TArray,TBuilder}"/> class can be used as the base for any array builder
+ /// that needs to delegate most of its functionality to an inner array builder.
+ /// </summary>
+ /// <remarks>
+ /// The typical use case is when an array builder may accept a number of different types as input, but which are
+ /// all internally converted to a single type for assembly into an array.
+ /// </remarks>
+ /// <typeparam name="T">Type of item accepted by inner array builder.</typeparam>
+ /// <typeparam name="TArray">Type of array produced by this (and the inner) builder.</typeparam>
+ /// <typeparam name="TBuilder">Type of builder (see Curiously-Recurring Template Pattern).</typeparam>
+ public abstract class DelegatingArrayBuilder<T, TArray, TBuilder> : IArrowArrayBuilder<TArray, TBuilder>
+ where TArray : IArrowArray
+ where TBuilder : class, IArrowArrayBuilder<TArray>
+ {
+ /// <summary>
+ /// Gets the inner array builder.
+ /// </summary>
+ protected IArrowArrayBuilder<T, TArray, IArrowArrayBuilder<TArray>> InnerBuilder { get; }
+
+ /// <summary>
+ /// Gets the number of items added to the array so far.
+ /// </summary>
+ public int Length => InnerBuilder.Length;
+
+ /// <summary>
+ /// Construct a new instance of the <see cref="DelegatingArrayBuilder{T,TArray,TBuilder}"/> class.
+ /// </summary>
+ /// <param name="innerBuilder">Inner array builder.</param>
+ protected DelegatingArrayBuilder(IArrowArrayBuilder<T, TArray, IArrowArrayBuilder<TArray>> innerBuilder)
+ {
+ InnerBuilder = innerBuilder ?? throw new ArgumentNullException(nameof(innerBuilder));
+ }
+
+ /// <summary>
+ /// Build an Arrow Array from the appended contents so far.
+ /// </summary>
+ /// <param name="allocator">Optional memory allocator.</param>
+ /// <returns>Returns the built array.</returns>
+ public TArray Build(MemoryAllocator allocator = default) => InnerBuilder.Build(allocator);
+
+ /// <summary>
+ /// Reserve a given number of items' additional capacity.
+ /// </summary>
+ /// <param name="additionalCapacity">Number of items of required additional capacity.</param>
+ /// <returns>Returns the builder (for fluent-style composition).</returns>
+ public TBuilder Reserve(int additionalCapacity)
+ {
+ InnerBuilder.Reserve(additionalCapacity);
+ return this as TBuilder;
+ }
+
+ /// <summary>
+ /// Resize the array to a given size.
+ /// </summary>
+ /// <remarks>
+ /// Note that if the required capacity is larger than the current length of the populated array so far,
+ /// the array's contents in the new, expanded region are undefined.
+ /// </remarks>
+ /// <remarks>
+ /// Note that if the required capacity is smaller than the current length of the populated array so far,
+ /// the array will be truncated and items at the end of the array will be lost.
+ /// </remarks>
+ /// <param name="capacity">Number of items of required capacity.</param>
+ /// <returns>Returns the builder (for fluent-style composition).</returns>
+ public TBuilder Resize(int capacity)
+ {
+ InnerBuilder.Resize(capacity);
+ return this as TBuilder;
+ }
+
+ /// <summary>
+ /// Clear all contents appended so far.
+ /// </summary>
+ /// <returns>Returns the builder (for fluent-style composition).</returns>
+ public TBuilder Clear()
+ {
+ InnerBuilder.Clear();
+ return this as TBuilder;
+ }
+ }
+}
diff --git a/csharp/test/Apache.Arrow.Tests/ArrowArrayTests.cs b/csharp/test/Apache.Arrow.Tests/ArrowArrayTests.cs
index 32290fe..18d4056 100644
--- a/csharp/test/Apache.Arrow.Tests/ArrowArrayTests.cs
+++ b/csharp/test/Apache.Arrow.Tests/ArrowArrayTests.cs
@@ -204,7 +204,7 @@ namespace Apache.Arrow.Tests
Assert.IsAssignableFrom<Date32Array>(_baseArray);
var baseArray = (Date32Array)_baseArray;
- Assert.Equal(baseArray.GetDate(array.Offset), array.GetDate(0));
+ Assert.Equal(baseArray.GetDateTimeOffset(array.Offset), array.GetDateTimeOffset(0));
}
public void Visit(Date64Array array)
@@ -213,7 +213,7 @@ namespace Apache.Arrow.Tests
Assert.IsAssignableFrom<Date64Array>(_baseArray);
var baseArray = (Date64Array)_baseArray;
- Assert.Equal(baseArray.GetDate(array.Offset), array.GetDate(0));
+ Assert.Equal(baseArray.GetDateTimeOffset(array.Offset), array.GetDateTimeOffset(0));
}
public void Visit(FloatArray array) => ValidateArrays(array);
diff --git a/csharp/test/Apache.Arrow.Tests/Date32ArrayTests.cs b/csharp/test/Apache.Arrow.Tests/Date32ArrayTests.cs
index f056b11..0d6aad9 100644
--- a/csharp/test/Apache.Arrow.Tests/Date32ArrayTests.cs
+++ b/csharp/test/Apache.Arrow.Tests/Date32ArrayTests.cs
@@ -14,34 +14,111 @@
// limitations under the License.
using System;
+using System.Collections.Generic;
+using System.Linq;
using Xunit;
namespace Apache.Arrow.Tests
{
public class Date32ArrayTests
{
- public class Set
+ public static IEnumerable<object[]> GetDatesData() =>
+ TestDateAndTimeData.ExampleDates.Select(d => new object[] { d });
+
+ public static IEnumerable<object[]> GetDateTimesData() =>
+ TestDateAndTimeData.ExampleDateTimes.Select(dt => new object[] { dt });
+
+ public static IEnumerable<object[]> GetDateTimeOffsetsData() =>
+ TestDateAndTimeData.ExampleDateTimeOffsets.Select(dto => new object[] { dto });
+
+ public class AppendNull
{
[Fact]
- public void SetAndGet()
+ public void AppendThenGetGivesNull()
+ {
+ // Arrange
+ var builder = new Date32Array.Builder();
+
+ // Act
+ builder = builder.AppendNull();
+
+ // Assert
+ var array = builder.Build();
+ Assert.Equal(1, array.Length);
+ Assert.Null(array.GetDateTime(0));
+ Assert.Null(array.GetDateTimeOffset(0));
+ Assert.Null(array.GetValue(0));
+ }
+ }
+
+ public class AppendDateTime
+ {
+ [Theory]
+ [MemberData(nameof(GetDatesData), MemberType = typeof(Date32ArrayTests))]
+ public void AppendDateGivesSameDate(DateTime date)
+ {
+ // Arrange
+ var builder = new Date32Array.Builder();
+ var expectedDateTime = date;
+ var expectedDateTimeOffset =
+ new DateTimeOffset(DateTime.SpecifyKind(date, DateTimeKind.Unspecified), TimeSpan.Zero);
+ int expectedValue = (int)date.Subtract(new DateTime(1970, 1, 1)).TotalDays;
+
+ // Act
+ builder = builder.Append(date);
+
+ // Assert
+ var array = builder.Build();
+ Assert.Equal(1, array.Length);
+ Assert.Equal(expectedDateTime, array.GetDateTime(0));
+ Assert.Equal(expectedDateTimeOffset, array.GetDateTimeOffset(0));
+ Assert.Equal(expectedValue, array.GetValue(0));
+ }
+
+ [Theory]
+ [MemberData(nameof(GetDateTimesData), MemberType = typeof(Date32ArrayTests))]
+ public void AppendWithTimeGivesSameWithTimeIgnored(DateTime dateTime)
+ {
+ // Arrange
+ var builder = new Date32Array.Builder();
+ var expectedDateTime = dateTime.Date;
+ var expectedDateTimeOffset =
+ new DateTimeOffset(DateTime.SpecifyKind(dateTime.Date, DateTimeKind.Unspecified), TimeSpan.Zero);
+ int expectedValue = (int)dateTime.Date.Subtract(new DateTime(1970, 1, 1)).TotalDays;
+
+ // Act
+ builder = builder.Append(dateTime);
+
+ // Assert
+ var array = builder.Build();
+ Assert.Equal(1, array.Length);
+ Assert.Equal(expectedDateTime, array.GetDateTime(0));
+ Assert.Equal(expectedDateTimeOffset, array.GetDateTimeOffset(0));
+ Assert.Equal(expectedValue, array.GetValue(0));
+ }
+ }
+
+ public class AppendDateTimeOffset
+ {
+ [Theory]
+ [MemberData(nameof(GetDateTimeOffsetsData), MemberType = typeof(Date32ArrayTests))]
+ public void AppendGivesUtcDate(DateTimeOffset dateTimeOffset)
{
- var now = DateTimeOffset.UtcNow;
-
- // throw away the time portion of the date time
- var expected = new DateTime(now.Year,
- now.Month,
- now.Day,
- 0,
- 0,
- 0,
- DateTimeKind.Utc);
-
- var array = new Date32Array.Builder()
- .Resize(1)
- .Set(0, expected)
- .Build();
-
- Assert.Equal(expected, array.GetDate(0).Value.Date);
+ // Arrange
+ var builder = new Date32Array.Builder();
+ var expectedDateTime = dateTimeOffset.UtcDateTime.Date;
+ var expectedDateTimeOffset = new DateTimeOffset(dateTimeOffset.UtcDateTime.Date, TimeSpan.Zero);
+ int expectedValue = (int)dateTimeOffset.UtcDateTime.Date.Subtract(new DateTime(1970, 1, 1)).TotalDays;
+
+ // Act
+ builder = builder.Append(dateTimeOffset);
+
+ // Assert
+ var array = builder.Build();
+ Assert.Equal(1, array.Length);
+ Assert.Equal(expectedDateTime, array.GetDateTime(0));
+ Assert.Equal(expectedDateTimeOffset, array.GetDateTimeOffset(0));
+ Assert.Equal(expectedValue, array.GetValue(0));
}
}
}
diff --git a/csharp/test/Apache.Arrow.Tests/Date64ArrayTests.cs b/csharp/test/Apache.Arrow.Tests/Date64ArrayTests.cs
new file mode 100644
index 0000000..65cffc8
--- /dev/null
+++ b/csharp/test/Apache.Arrow.Tests/Date64ArrayTests.cs
@@ -0,0 +1,133 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// 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 System;
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+
+namespace Apache.Arrow.Tests
+{
+ public class Date64ArrayTests
+ {
+ private const long MillisecondsPerDay = 86400000;
+
+ public static IEnumerable<object[]> GetDatesData() =>
+ TestDateAndTimeData.ExampleDates.Select(d => new object[] { d });
+
+ public static IEnumerable<object[]> GetDateTimesData() =>
+ TestDateAndTimeData.ExampleDateTimes.Select(dt => new object[] { dt });
+
+ public static IEnumerable<object[]> GetDateTimeOffsetsData() =>
+ TestDateAndTimeData.ExampleDateTimeOffsets.Select(dto => new object[] { dto });
+
+ public class AppendNull
+ {
+ [Fact]
+ public void AppendThenGetGivesNull()
+ {
+ // Arrange
+ var builder = new Date64Array.Builder();
+
+ // Act
+ builder = builder.AppendNull();
+
+ // Assert
+ var array = builder.Build();
+ Assert.Equal(1, array.Length);
+ Assert.Null(array.GetDateTime(0));
+ Assert.Null(array.GetDateTimeOffset(0));
+ Assert.Null(array.GetValue(0));
+ }
+ }
+
+ public class AppendDateTime
+ {
+ [Theory]
+ [MemberData(nameof(GetDatesData), MemberType = typeof(Date64ArrayTests))]
+ public void AppendDateGivesSameDate(DateTime date)
+ {
+ // Arrange
+ var builder = new Date64Array.Builder();
+ var expectedDateTime = date;
+ var expectedDateTimeOffset =
+ new DateTimeOffset(DateTime.SpecifyKind(date, DateTimeKind.Unspecified), TimeSpan.Zero);
+ long expectedValue = (long)date.Subtract(new DateTime(1970, 1, 1)).TotalDays * MillisecondsPerDay;
+
+ // Act
+ builder = builder.Append(date);
+
+ // Assert
+ var array = builder.Build();
+ Assert.Equal(1, array.Length);
+ Assert.Equal(expectedDateTime, array.GetDateTime(0));
+ Assert.Equal(expectedDateTimeOffset, array.GetDateTimeOffset(0));
+ Assert.Equal(expectedValue, array.GetValue(0));
+ Assert.Equal(0, array.GetValue(0).Value % MillisecondsPerDay);
+ }
+
+ [Theory]
+ [MemberData(nameof(GetDateTimesData), MemberType = typeof(Date64ArrayTests))]
+ public void AppendWithTimeGivesSameWithTimeIgnored(DateTime dateTime)
+ {
+ // Arrange
+ var builder = new Date64Array.Builder();
+ var expectedDateTime = dateTime.Date;
+ var expectedDateTimeOffset =
+ new DateTimeOffset(DateTime.SpecifyKind(dateTime.Date, DateTimeKind.Unspecified), TimeSpan.Zero);
+ long expectedValue =
+ (long)dateTime.Date.Subtract(new DateTime(1970, 1, 1)).TotalDays * MillisecondsPerDay;
+
+ // Act
+ builder = builder.Append(dateTime);
+
+ // Assert
+ var array = builder.Build();
+ Assert.Equal(1, array.Length);
+ Assert.Equal(expectedDateTime, array.GetDateTime(0));
+ Assert.Equal(expectedDateTimeOffset, array.GetDateTimeOffset(0));
+ Assert.Equal(expectedValue, array.GetValue(0));
+ Assert.Equal(0, array.GetValue(0).Value % MillisecondsPerDay);
+ }
+ }
+
+ public class AppendDateTimeOffset
+ {
+ [Theory]
+ [MemberData(nameof(GetDateTimeOffsetsData), MemberType = typeof(Date64ArrayTests))]
+ public void AppendGivesUtcDate(DateTimeOffset dateTimeOffset)
+ {
+ // Arrange
+ var builder = new Date64Array.Builder();
+ var expectedDateTime = dateTimeOffset.UtcDateTime.Date;
+ var expectedDateTimeOffset = new DateTimeOffset(dateTimeOffset.UtcDateTime.Date, TimeSpan.Zero);
+ long expectedValue =
+ (long)dateTimeOffset.UtcDateTime.Date.Subtract(new DateTime(1970, 1, 1)).TotalDays *
+ MillisecondsPerDay;
+
+ // Act
+ builder = builder.Append(dateTimeOffset);
+
+ // Assert
+ var array = builder.Build();
+ Assert.Equal(1, array.Length);
+ Assert.Equal(expectedDateTime, array.GetDateTime(0));
+ Assert.Equal(expectedDateTimeOffset, array.GetDateTimeOffset(0));
+ Assert.Equal(expectedValue, array.GetValue(0));
+ Assert.Equal(0, array.GetValue(0).Value % MillisecondsPerDay);
+ }
+ }
+ }
+}
diff --git a/csharp/test/Apache.Arrow.Tests/TestDateAndTimeData.cs b/csharp/test/Apache.Arrow.Tests/TestDateAndTimeData.cs
new file mode 100644
index 0000000..1f2eae4
--- /dev/null
+++ b/csharp/test/Apache.Arrow.Tests/TestDateAndTimeData.cs
@@ -0,0 +1,83 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// 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 System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Apache.Arrow.Tests
+{
+ /// <summary>
+ /// The <see cref="TestDateAndTimeData"/> class holds example dates and times useful for testing.
+ /// </summary>
+ internal static class TestDateAndTimeData
+ {
+ private static readonly DateTime _earliestDate = new DateTime(1, 1, 1);
+ private static readonly DateTime _latestDate = new DateTime(9999, 12, 31);
+
+ private static readonly DateTime[] _exampleDates =
+ {
+ _earliestDate, new DateTime(1969, 12, 31), new DateTime(1970, 1, 1), new DateTime(1970, 1, 2),
+ new DateTime(1972, 6, 30), new DateTime(2015, 6, 30), new DateTime(2016, 12, 31), new DateTime(2020, 2, 29),
+ new DateTime(2020, 7, 1), _latestDate,
+ };
+
+ private static readonly TimeSpan[] _exampleTimes =
+ {
+ new TimeSpan(0, 0, 1), new TimeSpan(12, 0, 0), new TimeSpan(23, 59, 59),
+ };
+
+ private static readonly DateTimeKind[] _exampleKinds =
+ {
+ DateTimeKind.Local, DateTimeKind.Unspecified, DateTimeKind.Utc,
+ };
+
+ private static readonly TimeSpan[] _exampleOffsets =
+ {
+ TimeSpan.FromHours(-2),
+ TimeSpan.Zero,
+ TimeSpan.FromHours(2),
+ };
+
+ /// <summary>
+ /// Gets a collection of example dates (i.e. with a zero time component), of all different kinds.
+ /// </summary>
+ public static IEnumerable<DateTime> ExampleDates =>
+ from date in _exampleDates
+ from kind in _exampleKinds
+ select DateTime.SpecifyKind(date, kind);
+
+ /// <summary>
+ /// Gets a collection of example date/times, of all different kinds.
+ /// </summary>
+ public static IEnumerable<DateTime> ExampleDateTimes =>
+ from date in _exampleDates
+ from time in _exampleTimes
+ from kind in _exampleKinds
+ select DateTime.SpecifyKind(date.Add(time), kind);
+
+ /// <summary>
+ /// Gets a collection of example date time offsets.
+ /// </summary>
+ /// <returns></returns>
+ public static IEnumerable<DateTimeOffset> ExampleDateTimeOffsets =>
+ from date in _exampleDates
+ from time in _exampleTimes
+ from offset in _exampleOffsets
+ where !(date == _earliestDate && offset.Ticks > 0)
+ where !(date == _latestDate && offset.Ticks < 0)
+ select new DateTimeOffset(date.Add(time), offset);
+ }
+}