diff --git a/DuckDB.NET.Bindings/DuckDBDateOnly.cs b/DuckDB.NET.Bindings/DuckDBDateOnly.cs
index db32b46..9133432 100644
--- a/DuckDB.NET.Bindings/DuckDBDateOnly.cs
+++ b/DuckDB.NET.Bindings/DuckDBDateOnly.cs
@@ -6,6 +6,26 @@ namespace DuckDB.NET.Native;
[StructLayout(LayoutKind.Sequential)]
public readonly struct DuckDBDateOnly(int year, byte month, byte day)
{
+ ///
+ /// Represents positive infinity for DuckDB dates.
+ ///
+ public static readonly DuckDBDateOnly PositiveInfinity =
+ // This is the value returned by DuckDB for positive infinity dates when
+ // passed to duckdb_from_date, and it is used for backwards compatibility.
+ // It is theoretically equal to the max date value plus one day:
+ // '5881580-07-10'::date + 1
+ new(5881580, 7, 11);
+
+ ///
+ /// Represents negative infinity for DuckDB dates.
+ ///
+ public static readonly DuckDBDateOnly NegativeInfinity =
+ // This is the value returned by DuckDB for negative infinity dates when
+ // passed to duckdb_from_date, and it is used for backwards compatibility.
+ // It is theoretically equal to the min date value minus one day:
+ // '5877642-06-25 (BC)'::date - 1
+ new(-5877641, 6, 24);
+
public int Year { get; } = year;
public byte Month { get; } = month;
@@ -14,19 +34,68 @@ public readonly struct DuckDBDateOnly(int year, byte month, byte day)
internal static readonly DuckDBDateOnly MinValue = FromDateTime(DateTime.MinValue);
+ ///
+ /// Returns true if this date represents positive or negative infinity.
+ ///
+ public bool IsInfinity => IsPositiveInfinity || IsNegativeInfinity;
+
+ ///
+ /// Returns true if this date represents positive infinity.
+ ///
+ public bool IsPositiveInfinity => Equals(PositiveInfinity);
+
+ ///
+ /// Returns true if this date represents negative infinity.
+ ///
+ public bool IsNegativeInfinity => Equals(NegativeInfinity);
+
public static DuckDBDateOnly FromDateTime(DateTime dateTime) => new DuckDBDateOnly(dateTime.Year, (byte)dateTime.Month, (byte)dateTime.Day);
public DateTime ToDateTime() => new DateTime(Year, Month, Day);
+#if NET6_0_OR_GREATER
+
+ public static DuckDBDateOnly FromDateOnly(DateOnly dateOnly) => new DuckDBDateOnly(dateOnly.Year, (byte)dateOnly.Month, (byte)dateOnly.Day);
+
+ public DateOnly ToDateOnly() => new DateOnly(Year, Month, Day);
+
+#endif
+
+ ///
+ /// Converts a DuckDBDate to DuckDBDateOnly, handling infinity values.
+ ///
+ public static DuckDBDateOnly FromDuckDBDate(DuckDBDate date)
+ {
+ if (date.IsPositiveInfinity)
+ return PositiveInfinity;
+ if (date.IsNegativeInfinity)
+ return NegativeInfinity;
+
+ return NativeMethods.DateTimeHelpers.DuckDBFromDate(date);
+ }
+
+ ///
+ /// Converts this DuckDBDateOnly to a DuckDBDate, handling infinity values.
+ ///
+ public DuckDBDate ToDuckDBDate()
+ {
+ if (IsPositiveInfinity)
+ return DuckDBDate.PositiveInfinity;
+ if (IsNegativeInfinity)
+ return DuckDBDate.NegativeInfinity;
+
+ return NativeMethods.DateTimeHelpers.DuckDBToDate(this);
+ }
+
public static explicit operator DateTime(DuckDBDateOnly dateOnly) => dateOnly.ToDateTime();
-
+
public static explicit operator DuckDBDateOnly(DateTime dateTime) => FromDateTime(dateTime);
-
+
#if NET6_0_OR_GREATER
-
- public static implicit operator DateOnly(DuckDBDateOnly dateOnly) => new DateOnly(dateOnly.Year, dateOnly.Month, dateOnly.Day);
-
- public static implicit operator DuckDBDateOnly(DateOnly date) => new DuckDBDateOnly(date.Year, (byte)date.Month, (byte) date.Day);
-
+
+ public static implicit operator DateOnly(DuckDBDateOnly dateOnly) => dateOnly.ToDateOnly();
+
+ public static implicit operator DuckDBDateOnly(DateOnly date) => DuckDBDateOnly.FromDateOnly(date);
+
#endif
-}
\ No newline at end of file
+}
diff --git a/DuckDB.NET.Bindings/DuckDBNativeObjects.cs b/DuckDB.NET.Bindings/DuckDBNativeObjects.cs
index d13ceaa..42676f5 100644
--- a/DuckDB.NET.Bindings/DuckDBNativeObjects.cs
+++ b/DuckDB.NET.Bindings/DuckDBNativeObjects.cs
@@ -115,7 +115,22 @@ public void Close()
[StructLayout(LayoutKind.Sequential)]
public struct DuckDBDate
{
+ ///
+ /// Represents DuckDB's positive infinity date value.
+ /// This is the value used in the DuckDB source code for +infinity dates.
+ ///
+ public static readonly DuckDBDate PositiveInfinity = new() { Days = int.MaxValue };
+ ///
+ /// Represents DuckDB's negative infinity date value.
+ /// This is the value used in the DuckDB source code for -infinity dates.
+ ///
+ public static readonly DuckDBDate NegativeInfinity = new() { Days = -int.MaxValue };
+
public int Days { get; set; }
+
+ public bool IsInfinity => IsPositiveInfinity || IsNegativeInfinity;
+ public bool IsPositiveInfinity => Days == int.MaxValue;
+ public bool IsNegativeInfinity => Days == -int.MaxValue;
}
[StructLayout(LayoutKind.Sequential)]
@@ -140,7 +155,22 @@ public struct DuckDBTimeTz
[StructLayout(LayoutKind.Sequential)]
public struct DuckDBTimestampStruct
{
+ ///
+ /// Represents DuckDB's positive infinity timestamp value.
+ /// This is the value used in the DuckDB source code for +infinity timestamps.
+ ///
+ public static readonly DuckDBTimestampStruct PositiveInfinity = new() { Micros = long.MaxValue };
+ ///
+ /// Represents DuckDB's negative infinity timestamp value.
+ /// This is the value used in the DuckDB source code for -infinity timestamps.
+ ///
+ public static readonly DuckDBTimestampStruct NegativeInfinity = new() { Micros = -long.MaxValue };
+
public long Micros { get; set; }
+
+ public bool IsInfinity => IsPositiveInfinity || IsNegativeInfinity;
+ public bool IsPositiveInfinity => Micros == long.MaxValue;
+ public bool IsNegativeInfinity => Micros == -long.MaxValue;
}
[StructLayout(LayoutKind.Sequential)]
diff --git a/DuckDB.NET.Bindings/DuckDBTimestamp.cs b/DuckDB.NET.Bindings/DuckDBTimestamp.cs
index e52b2d2..e2b6e11 100644
--- a/DuckDB.NET.Bindings/DuckDBTimestamp.cs
+++ b/DuckDB.NET.Bindings/DuckDBTimestamp.cs
@@ -6,9 +6,40 @@ namespace DuckDB.NET.Native;
[StructLayout(LayoutKind.Sequential)]
public readonly struct DuckDBTimestamp(DuckDBDateOnly date, DuckDBTimeOnly time)
{
+ ///
+ /// Represents positive infinity for DuckDB timestamps.
+ ///
+ public static readonly DuckDBTimestamp PositiveInfinity =
+ // This is the max timestamp value + 1 microsecond (because timestamps are represented as an int64 of microseconds)
+ // Theoretically: '294247-01-10 04:00:54.775806'::timestamp + INTERVAL '1 microsecond'
+ new(new DuckDBDateOnly(294247, 1, 10), new DuckDBTimeOnly(4, 0, 54, 775807));
+
+ ///
+ /// Represents negative infinity for DuckDB timestamps.
+ ///
+ public static readonly DuckDBTimestamp NegativeInfinity =
+ // This is the min timestamp value - 1 microsecond (because timestamps are represented as an int64 of microseconds)
+ // Theoretically: '290309-12-22 (BC) 00:00:00.000000'::timestamp - INTERVAL '1 microsecond'
+ new(new DuckDBDateOnly(-290308, 12, 21), new DuckDBTimeOnly(23, 59, 59, 999999));
+
public DuckDBDateOnly Date { get; } = date;
public DuckDBTimeOnly Time { get; } = time;
+ ///
+ /// Returns true if this timestamp represents positive or negative infinity.
+ ///
+ public bool IsInfinity => IsPositiveInfinity || IsNegativeInfinity;
+
+ ///
+ /// Returns true if this timestamp represents positive infinity.
+ ///
+ public bool IsPositiveInfinity => Equals(PositiveInfinity);
+
+ ///
+ /// Returns true if this timestamp represents negative infinity.
+ ///
+ public bool IsNegativeInfinity => Equals(NegativeInfinity);
+
public DateTime ToDateTime()
{
return new DateTime(Date.Year, Date.Month, Date.Day).AddTicks(Time.Ticks);
@@ -18,4 +49,33 @@ public static DuckDBTimestamp FromDateTime(DateTime dateTime)
{
return new DuckDBTimestamp(DuckDBDateOnly.FromDateTime(dateTime), DuckDBTimeOnly.FromDateTime(dateTime));
}
-}
\ No newline at end of file
+
+ ///
+ /// Converts a DuckDBTimestampStruct to DuckDBTimestamp, handling infinity values.
+ ///
+ public static DuckDBTimestamp FromDuckDBTimestampStruct(DuckDBTimestampStruct timestampStruct)
+ {
+ if (timestampStruct.IsPositiveInfinity)
+ return PositiveInfinity;
+ if (timestampStruct.IsNegativeInfinity)
+ return NegativeInfinity;
+
+ return NativeMethods.DateTimeHelpers.DuckDBFromTimestamp(timestampStruct);
+ }
+
+ ///
+ /// Converts this DuckDBTimestamp to a DuckDBTimestampStruct, handling infinity values.
+ ///
+ public DuckDBTimestampStruct ToDuckDBTimestampStruct()
+ {
+ if (IsPositiveInfinity)
+ return DuckDBTimestampStruct.PositiveInfinity;
+ if (IsNegativeInfinity)
+ return DuckDBTimestampStruct.NegativeInfinity;
+
+ return NativeMethods.DateTimeHelpers.DuckDBToTimestamp(this);
+ }
+
+ public static implicit operator DateTime(DuckDBTimestamp timestamp) => timestamp.ToDateTime();
+ public static implicit operator DuckDBTimestamp(DateTime timestamp) => DuckDBTimestamp.FromDateTime(timestamp);
+}
diff --git a/DuckDB.NET.Bindings/DuckDBWrapperObjects.cs b/DuckDB.NET.Bindings/DuckDBWrapperObjects.cs
index 65b664b..399967f 100644
--- a/DuckDB.NET.Bindings/DuckDBWrapperObjects.cs
+++ b/DuckDB.NET.Bindings/DuckDBWrapperObjects.cs
@@ -101,7 +101,7 @@ protected override bool ReleaseHandle()
{
value.Dispose();
}
-
+
NativeMethods.Value.DuckDBDestroyValue(ref handle);
return true;
}
@@ -140,26 +140,26 @@ public T GetValue()
DuckDBType.Float => Cast(NativeMethods.Value.DuckDBGetFloat(this)),
DuckDBType.Double => Cast(NativeMethods.Value.DuckDBGetDouble(this)),
-
+
DuckDBType.Decimal => Cast(decimal.Parse(NativeMethods.Value.DuckDBGetVarchar(this), NumberStyles.Any, CultureInfo.InvariantCulture)),
-
+
DuckDBType.Uuid => Cast(new Guid(NativeMethods.Value.DuckDBGetVarchar(this))),
-
+
DuckDBType.HugeInt => Cast(NativeMethods.Value.DuckDBGetHugeInt(this).ToBigInteger()),
DuckDBType.UnsignedHugeInt => Cast(NativeMethods.Value.DuckDBGetUHugeInt(this).ToBigInteger()),
-
+
DuckDBType.Varchar => Cast(NativeMethods.Value.DuckDBGetVarchar(this)),
#if NET6_0_OR_GREATER
- DuckDBType.Date => Cast((DateOnly)NativeMethods.DateTimeHelpers.DuckDBFromDate(NativeMethods.Value.DuckDBGetDate(this))),
+ DuckDBType.Date => Cast((DateOnly)DuckDBDateOnly.FromDuckDBDate(NativeMethods.Value.DuckDBGetDate(this))),
DuckDBType.Time => Cast((TimeOnly)NativeMethods.DateTimeHelpers.DuckDBFromTime(NativeMethods.Value.DuckDBGetTime(this))),
#else
- DuckDBType.Date => Cast(NativeMethods.DateTimeHelpers.DuckDBFromDate(NativeMethods.Value.DuckDBGetDate(this)).ToDateTime()),
+ DuckDBType.Date => Cast(DuckDBDateOnly.FromDuckDBDate(NativeMethods.Value.DuckDBGetDate(this)).ToDateTime()),
DuckDBType.Time => Cast(NativeMethods.DateTimeHelpers.DuckDBFromTime(NativeMethods.Value.DuckDBGetTime(this)).ToDateTime()),
#endif
//DuckDBType.TimeTz => expr,
DuckDBType.Interval => Cast((TimeSpan)NativeMethods.Value.DuckDBGetInterval(this)),
- DuckDBType.Timestamp => Cast(NativeMethods.DateTimeHelpers.DuckDBFromTimestamp(NativeMethods.Value.DuckDBGetTimestamp(this)).ToDateTime()),
+ DuckDBType.Timestamp => Cast(DuckDBTimestamp.FromDuckDBTimestampStruct(NativeMethods.Value.DuckDBGetTimestamp(this)).ToDateTime()),
//DuckDBType.TimestampS => expr,
//DuckDBType.TimestampMs => expr,
//DuckDBType.TimestampNs => expr,
diff --git a/DuckDB.NET.Bindings/NativeMethods/NativeMethods.DateTime.cs b/DuckDB.NET.Bindings/NativeMethods/NativeMethods.DateTime.cs
index f631a9f..da594d0 100644
--- a/DuckDB.NET.Bindings/NativeMethods/NativeMethods.DateTime.cs
+++ b/DuckDB.NET.Bindings/NativeMethods/NativeMethods.DateTime.cs
@@ -30,5 +30,27 @@ public static class DateTimeHelpers
[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_to_timestamp")]
public static extern DuckDBTimestampStruct DuckDBToTimestamp(DuckDBTimestamp dateStruct);
+
+ // NOTE: for boolean return values, MarshalAs(UnmanagedType.I1) is used because the default is to use 4-byte Win32 BOOLs
+ // https://learn.microsoft.com/en-us/dotnet/standard/native-interop/customize-struct-marshalling#customizing-boolean-field-marshalling
+ [DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_is_finite_date")]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static extern bool DuckDBIsFiniteDate(DuckDBDate date);
+
+ [DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_is_finite_timestamp")]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static extern bool DuckDBIsFiniteTimestamp(DuckDBTimestampStruct ts);
+
+ [DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_is_finite_timestamp_s")]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static extern bool DuckDBIsFiniteTimestampS(DuckDBTimestampStruct ts);
+
+ [DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_is_finite_timestamp_ms")]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static extern bool DuckDBIsFiniteTimestampMs(DuckDBTimestampStruct ts);
+
+ [DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_is_finite_timestamp_ns")]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static extern bool DuckDBIsFiniteTimestampNs(DuckDBTimestampStruct ts);
}
-}
\ No newline at end of file
+}
diff --git a/DuckDB.NET.Data/DataChunk/Reader/DateTimeVectorDataReader.cs b/DuckDB.NET.Data/DataChunk/Reader/DateTimeVectorDataReader.cs
index 37f678f..de0eabf 100644
--- a/DuckDB.NET.Data/DataChunk/Reader/DateTimeVectorDataReader.cs
+++ b/DuckDB.NET.Data/DataChunk/Reader/DateTimeVectorDataReader.cs
@@ -28,7 +28,23 @@ protected override T GetValidValue(ulong offset, Type targetType)
{
if (DuckDBType == DuckDBType.Date)
{
- var dateOnly = GetDateOnly(offset);
+ var (dateOnly, isFinite) = GetDateOnly(offset);
+
+ if (!isFinite)
+ {
+ if (targetType == DateTimeType || targetType == DateTimeNullableType)
+ {
+ ThrowInfinityDateException();
+ }
+
+#if NET6_0_OR_GREATER
+ if (targetType == DateOnlyType || targetType == DateOnlyNullableType)
+ {
+ ThrowInfinityDateException();
+ }
+#endif
+ return (T)(object)dateOnly;
+ }
if (targetType == DateTimeType || targetType == DateTimeNullableType)
{
@@ -81,8 +97,8 @@ protected override T GetValidValue(ulong offset, Type targetType)
return DuckDBType switch
{
- DuckDBType.Timestamp or DuckDBType.TimestampS or
- DuckDBType.TimestampTz or DuckDBType.TimestampMs or
+ DuckDBType.Timestamp or DuckDBType.TimestampS or
+ DuckDBType.TimestampTz or DuckDBType.TimestampMs or
DuckDBType.TimestampNs => ReadTimestamp(offset, targetType),
_ => base.GetValidValue(offset, targetType)
};
@@ -90,7 +106,25 @@ DuckDBType.TimestampTz or DuckDBType.TimestampMs or
private T ReadTimestamp(ulong offset, Type targetType)
{
- var (timestamp, additionalTicks) = GetFieldData(offset).ToDuckDBTimestamp(DuckDBType);
+ var timestampStruct = GetFieldData(offset);
+
+ if (!timestampStruct.IsFinite(DuckDBType))
+ {
+ if (targetType == DateTimeType || targetType == DateTimeNullableType)
+ {
+ ThrowInfinityTimestampException();
+ }
+
+ if (targetType == DateTimeOffsetType || targetType == DateTimeOffsetNullableType)
+ {
+ ThrowInfinityTimestampException();
+ }
+
+ var infinityTimestamp = DuckDBTimestamp.FromDuckDBTimestampStruct(timestampStruct);
+ return (T)(object)infinityTimestamp;
+ }
+
+ var (timestamp, additionalTicks) = timestampStruct.ToDuckDBTimestamp(DuckDBType);
if (targetType == DateTimeType || targetType == DateTimeNullableType)
{
@@ -115,7 +149,7 @@ internal override object GetValue(ulong offset, Type targetType)
DuckDBType.Date => GetDate(offset, targetType),
DuckDBType.Time => GetTime(offset, targetType),
DuckDBType.TimeTz => GetDateTimeOffset(offset, targetType),
- DuckDBType.Timestamp or DuckDBType.TimestampS or
+ DuckDBType.Timestamp or DuckDBType.TimestampS or
DuckDBType.TimestampTz or DuckDBType.TimestampMs or
DuckDBType.TimestampNs => GetDateTime(offset, targetType),
_ => base.GetValue(offset, targetType)
@@ -134,14 +168,34 @@ private DuckDBTimeOnly GetTimeOnly(ulong offset)
return NativeMethods.DateTimeHelpers.DuckDBFromTime(GetFieldData(offset));
}
- private DuckDBDateOnly GetDateOnly(ulong offset)
+ private (DuckDBDateOnly dateOnly, bool IsFinite) GetDateOnly(ulong offset)
{
- return NativeMethods.DateTimeHelpers.DuckDBFromDate(GetFieldData(offset));
+ var date = GetFieldData(offset);
+ var isFinite = NativeMethods.DateTimeHelpers.DuckDBIsFiniteDate(date);
+ return (DuckDBDateOnly.FromDuckDBDate(date), isFinite);
}
private object GetDate(ulong offset, Type targetType)
{
- var dateOnly = GetDateOnly(offset);
+ var (dateOnly, isFinite) = GetDateOnly(offset);
+
+ if (!isFinite)
+ {
+ if (targetType == DateTimeType)
+ {
+ ThrowInfinityDateException();
+ }
+
+#if NET6_0_OR_GREATER
+ if (targetType == DateOnlyType)
+ {
+ ThrowInfinityDateException();
+ }
+#endif
+
+ return dateOnly;
+ }
+
if (targetType == DateTimeType)
{
return (DateTime)dateOnly;
@@ -177,7 +231,24 @@ private object GetTime(ulong offset, Type targetType)
private object GetDateTime(ulong offset, Type targetType)
{
- var (timestamp, additionalTicks) = GetFieldData(offset).ToDuckDBTimestamp(DuckDBType);
+ var timestampStruct = GetFieldData(offset);
+
+ if (!timestampStruct.IsFinite(DuckDBType))
+ {
+ if (targetType == typeof(DateTime))
+ {
+ ThrowInfinityTimestampException();
+ }
+
+ if (targetType == DateTimeOffsetType)
+ {
+ ThrowInfinityTimestampException();
+ }
+
+ return DuckDBTimestamp.FromDuckDBTimestampStruct(timestampStruct);
+ }
+
+ var (timestamp, additionalTicks) = timestampStruct.ToDuckDBTimestamp(DuckDBType);
if (targetType == typeof(DateTime))
{
@@ -206,4 +277,18 @@ private object GetDateTimeOffset(ulong offset, Type targetType)
return timeTz;
}
+
+ private static void ThrowInfinityDateException()
+ {
+ throw new InvalidOperationException(
+ "Cannot convert infinite date value to DateTime or DateOnly. " +
+ "Use DuckDBDateOnly to read this value and check IsInfinity, IsPositiveInfinity, or IsNegativeInfinity before converting to .NET types.");
+ }
+
+ private static void ThrowInfinityTimestampException()
+ {
+ throw new InvalidOperationException(
+ "Cannot convert infinite timestamp value to DateTime or DateTimeOffset. " +
+ "Use DuckDBTimestamp to read this value and check IsInfinity, IsPositiveInfinity, or IsNegativeInfinity before converting to .NET types.");
+ }
}
\ No newline at end of file
diff --git a/DuckDB.NET.Data/DataChunk/Writer/DateTimeVectorDataWriter.cs b/DuckDB.NET.Data/DataChunk/Writer/DateTimeVectorDataWriter.cs
index a9e5a0d..1550cd4 100644
--- a/DuckDB.NET.Data/DataChunk/Writer/DateTimeVectorDataWriter.cs
+++ b/DuckDB.NET.Data/DataChunk/Writer/DateTimeVectorDataWriter.cs
@@ -10,7 +10,7 @@ internal override bool AppendDateTime(DateTime value, ulong rowIndex)
{
if (ColumnType == DuckDBType.Date)
{
- return AppendValueInternal(NativeMethods.DateTimeHelpers.DuckDBToDate((DuckDBDateOnly)value.Date), rowIndex);
+ return AppendValueInternal(((DuckDBDateOnly)value.Date).ToDuckDBDate(), rowIndex);
}
var timestamp = value.ToTimestampStruct(ColumnType);
@@ -38,12 +38,12 @@ internal override bool AppendDateTimeOffset(DateTimeOffset value, ulong rowIndex
}
#if NET6_0_OR_GREATER
- internal override bool AppendDateOnly(DateOnly value, ulong rowIndex) => AppendValueInternal(NativeMethods.DateTimeHelpers.DuckDBToDate(value), rowIndex);
+ internal override bool AppendDateOnly(DateOnly value, ulong rowIndex) => AppendValueInternal(((DuckDBDateOnly)value).ToDuckDBDate(), rowIndex);
internal override bool AppendTimeOnly(TimeOnly value, ulong rowIndex) => AppendValueInternal(NativeMethods.DateTimeHelpers.DuckDBToTime(value), rowIndex);
#endif
- internal override bool AppendDateOnly(DuckDBDateOnly value, ulong rowIndex) => AppendValueInternal(NativeMethods.DateTimeHelpers.DuckDBToDate(value), rowIndex);
+ internal override bool AppendDateOnly(DuckDBDateOnly value, ulong rowIndex) => AppendValueInternal(value.ToDuckDBDate(), rowIndex);
internal override bool AppendTimeOnly(DuckDBTimeOnly value, ulong rowIndex) => AppendValueInternal(NativeMethods.DateTimeHelpers.DuckDBToTime(value), rowIndex);
}
diff --git a/DuckDB.NET.Data/Extensions/DateTimeExtensions.cs b/DuckDB.NET.Data/Extensions/DateTimeExtensions.cs
index 3c2c20a..84d0c39 100644
--- a/DuckDB.NET.Data/Extensions/DateTimeExtensions.cs
+++ b/DuckDB.NET.Data/Extensions/DateTimeExtensions.cs
@@ -28,14 +28,14 @@ public static DuckDBTimeTzStruct ToTimeTzStruct(this DateTimeOffset value)
public static DuckDBTimestampStruct ToTimestampStruct(this DateTimeOffset value)
{
- var timestamp = NativeMethods.DateTimeHelpers.DuckDBToTimestamp(DuckDBTimestamp.FromDateTime(value.UtcDateTime));
+ var timestamp = DuckDBTimestamp.FromDateTime(value.UtcDateTime).ToDuckDBTimestampStruct();
return timestamp;
}
public static DuckDBTimestampStruct ToTimestampStruct(this DateTime value, DuckDBType duckDBType)
{
- var timestamp = NativeMethods.DateTimeHelpers.DuckDBToTimestamp(DuckDBTimestamp.FromDateTime(value));
+ var timestamp = DuckDBTimestamp.FromDateTime(value).ToDuckDBTimestampStruct();
if (duckDBType == DuckDBType.TimestampNs)
{
@@ -77,8 +77,21 @@ public static (DuckDBTimestamp result, int additionalTicks) ToDuckDBTimestamp(th
timestamp.Micros *= 1000000;
}
- var result = NativeMethods.DateTimeHelpers.DuckDBFromTimestamp(timestamp);
+ var result = DuckDBTimestamp.FromDuckDBTimestampStruct(timestamp);
return (result, additionalTicks);
}
-}
\ No newline at end of file
+
+ /// Uses the native method corresponding to the timestamp type, as opposed
+ /// to comparing with a constant directly.
+ public static bool IsFinite(this DuckDBTimestampStruct timestamp, DuckDBType duckDBType)
+ {
+ return duckDBType switch
+ {
+ DuckDBType.TimestampNs => NativeMethods.DateTimeHelpers.DuckDBIsFiniteTimestampNs(timestamp),
+ DuckDBType.TimestampMs => NativeMethods.DateTimeHelpers.DuckDBIsFiniteTimestampMs(timestamp),
+ DuckDBType.TimestampS => NativeMethods.DateTimeHelpers.DuckDBIsFiniteTimestampS(timestamp),
+ _ => NativeMethods.DateTimeHelpers.DuckDBIsFiniteTimestamp(timestamp)
+ };
+ }
+}
diff --git a/DuckDB.NET.Data/PreparedStatement/ClrToDuckDBConverter.cs b/DuckDB.NET.Data/PreparedStatement/ClrToDuckDBConverter.cs
index 427719d..e51cb8e 100644
--- a/DuckDB.NET.Data/PreparedStatement/ClrToDuckDBConverter.cs
+++ b/DuckDB.NET.Data/PreparedStatement/ClrToDuckDBConverter.cs
@@ -37,9 +37,9 @@ internal static class ClrToDuckDBConverter
{ DbType.Date, value =>
{
#if NET6_0_OR_GREATER
- var date = NativeMethods.DateTimeHelpers.DuckDBToDate(value is DateOnly dateOnly ? (DuckDBDateOnly)dateOnly : (DuckDBDateOnly)value);
+ var date = (value is DateOnly dateOnly ? (DuckDBDateOnly)dateOnly : (DuckDBDateOnly)value).ToDuckDBDate();
#else
- var date = NativeMethods.DateTimeHelpers.DuckDBToDate((DuckDBDateOnly)value);
+ var date = ((DuckDBDateOnly)value).ToDuckDBDate();
#endif
return NativeMethods.Value.DuckDBCreateDate(date);
}
@@ -54,7 +54,12 @@ internal static class ClrToDuckDBConverter
return NativeMethods.Value.DuckDBCreateTime(time);
}
},
- { DbType.DateTime, value => NativeMethods.Value.DuckDBCreateTimestamp(((DateTime)value).ToTimestampStruct(DuckDBType.Timestamp))},
+ { DbType.DateTime, value =>
+ {
+ var dateTime = (value is DateTime dt ? (DuckDBTimestamp)dt : (DuckDBTimestamp)value).ToDuckDBTimestampStruct();
+ return NativeMethods.Value.DuckDBCreateTimestamp(dateTime);
+ }
+ },
{ DbType.DateTimeOffset, value => NativeMethods.Value.DuckDBCreateTimestampTz(((DateTimeOffset)value).ToTimestampStruct()) },
};
@@ -95,12 +100,12 @@ public static DuckDBValue ToDuckDBValue(this object? item, DuckDBLogicalType log
(DuckDBType.TimestampTz, DateTime value) => NativeMethods.Value.DuckDBCreateTimestampTz(value.ToTimestampStruct(duckDBType)),
(DuckDBType.TimestampTz, DateTimeOffset value) => NativeMethods.Value.DuckDBCreateTimestampTz(value.ToTimestampStruct()),
(DuckDBType.Interval, TimeSpan value) => NativeMethods.Value.DuckDBCreateInterval(value),
- (DuckDBType.Date, DateTime value) => NativeMethods.Value.DuckDBCreateDate(NativeMethods.DateTimeHelpers.DuckDBToDate((DuckDBDateOnly)value)),
- (DuckDBType.Date, DuckDBDateOnly value) => NativeMethods.Value.DuckDBCreateDate(NativeMethods.DateTimeHelpers.DuckDBToDate(value)),
+ (DuckDBType.Date, DateTime value) => NativeMethods.Value.DuckDBCreateDate(((DuckDBDateOnly)value).ToDuckDBDate()),
+ (DuckDBType.Date, DuckDBDateOnly value) => NativeMethods.Value.DuckDBCreateDate(value.ToDuckDBDate()),
(DuckDBType.Time, DateTime value) => NativeMethods.Value.DuckDBCreateTime(NativeMethods.DateTimeHelpers.DuckDBToTime((DuckDBTimeOnly)value)),
(DuckDBType.Time, DuckDBTimeOnly value) => NativeMethods.Value.DuckDBCreateTime(NativeMethods.DateTimeHelpers.DuckDBToTime(value)),
#if NET6_0_OR_GREATER
- (DuckDBType.Date, DateOnly value) => NativeMethods.Value.DuckDBCreateDate(NativeMethods.DateTimeHelpers.DuckDBToDate(value)),
+ (DuckDBType.Date, DateOnly value) => NativeMethods.Value.DuckDBCreateDate(((DuckDBDateOnly)value).ToDuckDBDate()),
(DuckDBType.Time, TimeOnly value) => NativeMethods.Value.DuckDBCreateTime(NativeMethods.DateTimeHelpers.DuckDBToTime(value)),
#endif
(DuckDBType.TimeTz, DateTimeOffset value) => NativeMethods.Value.DuckDBCreateTimeTz(value.ToTimeTzStruct()),
@@ -177,4 +182,4 @@ private static DuckDBValue DecimalToDuckDBValue(decimal value)
return NativeMethods.Value.DuckDBCreateDecimal(new DuckDBDecimal((byte)width, scale, new DuckDBHugeInt(result)));
}
-}
\ No newline at end of file
+}
diff --git a/DuckDB.NET.Data/PreparedStatement/DuckDBTypeMap.cs b/DuckDB.NET.Data/PreparedStatement/DuckDBTypeMap.cs
index e6b8df4..fcba022 100644
--- a/DuckDB.NET.Data/PreparedStatement/DuckDBTypeMap.cs
+++ b/DuckDB.NET.Data/PreparedStatement/DuckDBTypeMap.cs
@@ -28,6 +28,7 @@ internal static class DuckDBTypeMap
{typeof(BigInteger), DbType.VarNumeric},
{typeof(byte[]), DbType.Binary},
{typeof(DateTime), DbType.DateTime},
+ {typeof(DuckDBTimestamp), DbType.DateTime},
{typeof(DateTimeOffset), DbType.DateTimeOffset},
{typeof(DuckDBDateOnly), DbType.Date},
{typeof(DuckDBTimeOnly), DbType.Time},
@@ -53,4 +54,4 @@ public static DbType GetDbTypeForValue(object? value)
return DbType.Object;
}
-}
\ No newline at end of file
+}
diff --git a/DuckDB.NET.Test/DuckDBDataReaderTestAllTypes.cs b/DuckDB.NET.Test/DuckDBDataReaderTestAllTypes.cs
index c02434a..3365dc2 100644
--- a/DuckDB.NET.Test/DuckDBDataReaderTestAllTypes.cs
+++ b/DuckDB.NET.Test/DuckDBDataReaderTestAllTypes.cs
@@ -398,23 +398,37 @@ public void ReadDateList()
VerifyDataList("date_array", 36, new List> { new(), new()
{
new DuckDBDateOnly(1970, 1, 1),
- new DuckDBDateOnly(5881580, 7, 11),
- new DuckDBDateOnly(-5877641, 6, 24),
+ DuckDBDateOnly.PositiveInfinity,
+ DuckDBDateOnly.NegativeInfinity,
null,
new DuckDBDateOnly(2022,5,12),
} });
}
- [Fact(Skip = "These dates can't be expressed by DateTime or is unsupported by this library")]
+ [Fact()]
public void ReadTimeStampList()
{
-
+ VerifyDataList("timestamp_array", 37, new List> { new(), new()
+ {
+ new DuckDBTimestamp(new DuckDBDateOnly(1970,1,1), new DuckDBTimeOnly(0,0,0)),
+ DuckDBTimestamp.PositiveInfinity,
+ DuckDBTimestamp.NegativeInfinity,
+ null,
+ new DuckDBTimestamp(new DuckDBDateOnly(2022,5,12), new DuckDBTimeOnly(16,23,45))
+ } });
}
- [Fact(Skip = "These dates can't be expressed by DateTime or is unsupported by this library")]
+ [Fact()]
public void ReadTimeStampTZList()
{
-
+ VerifyDataList("timestamptz_array", 38, new List> { new(), new()
+ {
+ new DuckDBTimestamp(new DuckDBDateOnly(1970,1,1), new DuckDBTimeOnly(0,0,0)),
+ DuckDBTimestamp.PositiveInfinity,
+ DuckDBTimestamp.NegativeInfinity,
+ null,
+ new DuckDBTimestamp(new DuckDBDateOnly(2022,5,12), new DuckDBTimeOnly(23,23,45))
+ } });
}
[Fact]
diff --git a/DuckDB.NET.Test/DuckDBInfinityTests.cs b/DuckDB.NET.Test/DuckDBInfinityTests.cs
new file mode 100644
index 0000000..0798337
--- /dev/null
+++ b/DuckDB.NET.Test/DuckDBInfinityTests.cs
@@ -0,0 +1,367 @@
+using DuckDB.NET.Data;
+using DuckDB.NET.Native;
+using FluentAssertions;
+using System;
+using Xunit;
+
+namespace DuckDB.NET.Test;
+
+public class DuckDBInfinityTests(DuckDBDatabaseFixture db) : DuckDBTestBase(db)
+{
+ #region Assertion Helpers
+
+ private static void AssertInfinityDateValues(DuckDBDataReader reader)
+ {
+ // Positive infinity
+ var positiveInfinity = reader.GetFieldValue(0);
+ positiveInfinity.Should().Be(DuckDBDateOnly.PositiveInfinity);
+ positiveInfinity.IsPositiveInfinity.Should().BeTrue();
+ positiveInfinity.IsInfinity.Should().BeTrue();
+ NativeMethods.DateTimeHelpers.DuckDBIsFiniteDate(positiveInfinity.ToDuckDBDate()).Should().BeFalse();
+
+ // Negative infinity
+ var negativeInfinity = reader.GetFieldValue(1);
+ negativeInfinity.Should().Be(DuckDBDateOnly.NegativeInfinity);
+ negativeInfinity.IsNegativeInfinity.Should().BeTrue();
+ negativeInfinity.IsInfinity.Should().BeTrue();
+ reader.GetFieldValue(1).Should().Be(DuckDBDateOnly.NegativeInfinity);
+ NativeMethods.DateTimeHelpers.DuckDBIsFiniteDate(negativeInfinity.ToDuckDBDate()).Should().BeFalse();
+
+ // isinf() function results
+ reader.GetBoolean(2).Should().BeTrue("isinf() should return true for positive infinity date");
+ reader.GetBoolean(3).Should().BeTrue("isinf() should return true for negative infinity date");
+
+ // Reading as DateTime throws
+ var actPositive = () => reader.GetDateTime(0);
+ actPositive.Should().Throw().WithMessage("*infinite*DuckDBDateOnly*");
+ var actNegative = () => reader.GetDateTime(1);
+ actNegative.Should().Throw().WithMessage("*infinite*DuckDBDateOnly*");
+
+ // Reading as nullable DateTime throws
+ var actNullablePositive = () => reader.GetFieldValue(0);
+ actNullablePositive.Should().Throw().WithMessage("*infinite*DuckDBDateOnly*");
+
+#if NET6_0_OR_GREATER
+ // Reading as DateOnly throws
+ var actDateOnlyPositive = () => reader.GetFieldValue(0);
+ actDateOnlyPositive.Should().Throw().WithMessage("*infinite*DuckDBDateOnly*");
+ var actDateOnlyNegative = () => reader.GetFieldValue(1);
+ actDateOnlyNegative.Should().Throw().WithMessage("*infinite*DuckDBDateOnly*");
+
+ // Reading as nullable DateOnly throws
+ var actNullableDateOnly = () => reader.GetFieldValue(0);
+ actNullableDateOnly.Should().Throw().WithMessage("*infinite*DuckDBDateOnly*");
+#endif
+ }
+
+ private static void AssertInfinityTimestampValues(DuckDBDataReader reader, DuckDBType duckDBType)
+ {
+ bool IsFinite(DuckDBTimestampStruct timestamp)
+ {
+ return duckDBType switch
+ {
+ DuckDBType.TimestampNs => NativeMethods.DateTimeHelpers.DuckDBIsFiniteTimestampNs(timestamp),
+ DuckDBType.TimestampMs => NativeMethods.DateTimeHelpers.DuckDBIsFiniteTimestampMs(timestamp),
+ DuckDBType.TimestampS => NativeMethods.DateTimeHelpers.DuckDBIsFiniteTimestampS(timestamp),
+ _ => NativeMethods.DateTimeHelpers.DuckDBIsFiniteTimestamp(timestamp)
+ };
+ }
+
+ // Positive infinity
+ var positiveInfinity = reader.GetFieldValue(0);
+ positiveInfinity.Should().Be(DuckDBTimestamp.PositiveInfinity);
+ positiveInfinity.IsPositiveInfinity.Should().BeTrue();
+ positiveInfinity.IsInfinity.Should().BeTrue();
+ IsFinite(positiveInfinity.ToDuckDBTimestampStruct()).Should().BeFalse();
+
+ // Negative infinity
+ var negativeInfinity = reader.GetFieldValue(1);
+ negativeInfinity.Should().Be(DuckDBTimestamp.NegativeInfinity);
+ negativeInfinity.IsNegativeInfinity.Should().BeTrue();
+ negativeInfinity.IsInfinity.Should().BeTrue();
+ IsFinite(negativeInfinity.ToDuckDBTimestampStruct()).Should().BeFalse();
+
+ // isinf() function results
+ reader.GetBoolean(2).Should().BeTrue("isinf() should return true for positive infinity timestamp");
+ reader.GetBoolean(3).Should().BeTrue("isinf() should return true for negative infinity timestamp");
+
+ // Reading as DateTime throws
+ var actPositive = () => reader.GetDateTime(0);
+ actPositive.Should().Throw().WithMessage("*infinite*DuckDBTimestamp*");
+ var actNegative = () => reader.GetDateTime(1);
+ actNegative.Should().Throw().WithMessage("*infinite*DuckDBTimestamp*");
+
+ // Reading as nullable DateTime throws
+ var actNullable = () => reader.GetFieldValue(0);
+ actNullable.Should().Throw().WithMessage("*infinite*DuckDBTimestamp*");
+
+ // Reading as DateTimeOffset throws
+ var actOffsetPositive = () => reader.GetFieldValue(0);
+ actOffsetPositive.Should().Throw().WithMessage("*infinite*DuckDBTimestamp*");
+ var actOffsetNegative = () => reader.GetFieldValue(1);
+ actOffsetNegative.Should().Throw().WithMessage("*infinite*DuckDBTimestamp*");
+
+ // Reading as nullable DateTimeOffset throws
+ var actNullableOffset = () => reader.GetFieldValue(0);
+ actNullableOffset.Should().Throw().WithMessage("*infinite*DuckDBTimestamp*");
+ }
+
+ #endregion
+
+ #region Native IsFinite Tests
+
+ [Fact]
+ public void duckdb_date_is_finite()
+ {
+ var positiveInfinity = DuckDBDate.PositiveInfinity;
+ var negativeInfinity = DuckDBDate.NegativeInfinity;
+ var finitePositive = new DuckDBDate { Days = Int32.MaxValue - 1 };
+ var finiteNegative = new DuckDBDate { Days = -Int32.MaxValue + 1 };
+ NativeMethods.DateTimeHelpers.DuckDBIsFiniteDate(finitePositive).Should().BeTrue();
+ NativeMethods.DateTimeHelpers.DuckDBIsFiniteDate(positiveInfinity).Should().BeFalse();
+ NativeMethods.DateTimeHelpers.DuckDBIsFiniteDate(finiteNegative).Should().BeTrue();
+ NativeMethods.DateTimeHelpers.DuckDBIsFiniteDate(negativeInfinity).Should().BeFalse();
+ }
+
+ [Fact]
+ public void duckdb_timestamp_is_finite()
+ {
+ var positiveInfinity = DuckDBTimestampStruct.PositiveInfinity;
+ var negativeInfinity = DuckDBTimestampStruct.NegativeInfinity;
+ var finitePositive = new DuckDBTimestampStruct { Micros = Int64.MaxValue - 1 };
+ var finiteNegative = new DuckDBTimestampStruct { Micros = -Int64.MaxValue + 1 };
+ NativeMethods.DateTimeHelpers.DuckDBIsFiniteTimestamp(finitePositive).Should().BeTrue();
+ NativeMethods.DateTimeHelpers.DuckDBIsFiniteTimestamp(positiveInfinity).Should().BeFalse();
+ NativeMethods.DateTimeHelpers.DuckDBIsFiniteTimestamp(finiteNegative).Should().BeTrue();
+ NativeMethods.DateTimeHelpers.DuckDBIsFiniteTimestamp(negativeInfinity).Should().BeFalse();
+ }
+
+ [Fact]
+ public void duckdb_timestamp_s_is_finite()
+ {
+ var positiveInfinity = DuckDBTimestampStruct.PositiveInfinity;
+ var negativeInfinity = DuckDBTimestampStruct.NegativeInfinity;
+ var finitePositive = new DuckDBTimestampStruct { Micros = Int64.MaxValue - 1 };
+ var finiteNegative = new DuckDBTimestampStruct { Micros = -Int64.MaxValue + 1 };
+ NativeMethods.DateTimeHelpers.DuckDBIsFiniteTimestampS(finitePositive).Should().BeTrue();
+ NativeMethods.DateTimeHelpers.DuckDBIsFiniteTimestampS(positiveInfinity).Should().BeFalse();
+ NativeMethods.DateTimeHelpers.DuckDBIsFiniteTimestampS(finiteNegative).Should().BeTrue();
+ NativeMethods.DateTimeHelpers.DuckDBIsFiniteTimestampS(negativeInfinity).Should().BeFalse();
+ }
+
+ [Fact]
+ public void duckdb_timestamp_ms_is_finite()
+ {
+ var positiveInfinity = DuckDBTimestampStruct.PositiveInfinity;
+ var negativeInfinity = DuckDBTimestampStruct.NegativeInfinity;
+ var finitePositive = new DuckDBTimestampStruct { Micros = Int64.MaxValue - 1 };
+ var finiteNegative = new DuckDBTimestampStruct { Micros = -Int64.MaxValue + 1 };
+ NativeMethods.DateTimeHelpers.DuckDBIsFiniteTimestampMs(finitePositive).Should().BeTrue();
+ NativeMethods.DateTimeHelpers.DuckDBIsFiniteTimestampMs(positiveInfinity).Should().BeFalse();
+ NativeMethods.DateTimeHelpers.DuckDBIsFiniteTimestampMs(finiteNegative).Should().BeTrue();
+ NativeMethods.DateTimeHelpers.DuckDBIsFiniteTimestampMs(negativeInfinity).Should().BeFalse();
+ }
+
+ [Fact]
+ public void duckdb_timestamp_ns_is_finite()
+ {
+ var positiveInfinity = DuckDBTimestampStruct.PositiveInfinity;
+ var negativeInfinity = DuckDBTimestampStruct.NegativeInfinity;
+ var finitePositive = new DuckDBTimestampStruct { Micros = Int64.MaxValue - 1 };
+ var finiteNegative = new DuckDBTimestampStruct { Micros = -Int64.MaxValue + 1 };
+ NativeMethods.DateTimeHelpers.DuckDBIsFiniteTimestampNs(finitePositive).Should().BeTrue();
+ NativeMethods.DateTimeHelpers.DuckDBIsFiniteTimestampNs(positiveInfinity).Should().BeFalse();
+ NativeMethods.DateTimeHelpers.DuckDBIsFiniteTimestampNs(finiteNegative).Should().BeTrue();
+ NativeMethods.DateTimeHelpers.DuckDBIsFiniteTimestampNs(negativeInfinity).Should().BeFalse();
+ }
+
+ #endregion
+
+ #region Infinity Date Tests
+
+ [Fact]
+ public void ReadInfinityDate()
+ {
+ Command.CommandText = "SELECT 'infinity'::DATE, '-infinity'::DATE, isinf('infinity'::DATE), isinf('-infinity'::DATE)";
+ using var reader = Command.ExecuteReader();
+ reader.Read();
+
+ AssertInfinityDateValues(reader);
+ }
+
+ [Fact]
+ public void ReadInfinityDateWithParameters()
+ {
+ Command.CommandText = "SELECT $1::DATE, $2::DATE, isinf($1::DATE), isinf($2::DATE)";
+ Command.Parameters.Add(new DuckDBParameter(DuckDBDateOnly.PositiveInfinity));
+ Command.Parameters.Add(new DuckDBParameter(DuckDBDateOnly.NegativeInfinity));
+ using var reader = Command.ExecuteReader();
+ reader.Read();
+
+ AssertInfinityDateValues(reader);
+ }
+
+ #endregion
+
+ #region Infinity Timestamp Tests
+
+ [Fact]
+ public void ReadInfinityTimestamp()
+ {
+ Command.CommandText = "SELECT 'infinity'::TIMESTAMP, '-infinity'::TIMESTAMP, isinf('infinity'::TIMESTAMP), isinf('-infinity'::TIMESTAMP)";
+ using var reader = Command.ExecuteReader();
+ reader.Read();
+
+ AssertInfinityTimestampValues(reader, DuckDBType.Timestamp);
+ }
+
+ [Fact]
+ public void ReadInfinityTimestampWithParameters()
+ {
+ Command.CommandText = "SELECT $1::TIMESTAMP, $2::TIMESTAMP, isinf($1::TIMESTAMP), isinf($2::TIMESTAMP)";
+ Command.Parameters.Add(new DuckDBParameter(DuckDBTimestamp.PositiveInfinity));
+ Command.Parameters.Add(new DuckDBParameter(DuckDBTimestamp.NegativeInfinity));
+ using var reader = Command.ExecuteReader();
+ reader.Read();
+
+ AssertInfinityTimestampValues(reader, DuckDBType.Timestamp);
+ }
+
+ [Fact]
+ public void ReadInfinityTimestampNs()
+ {
+ Command.CommandText = "SELECT 'infinity'::TIMESTAMP_NS, '-infinity'::TIMESTAMP_NS, isinf('infinity'::TIMESTAMP_NS), isinf('-infinity'::TIMESTAMP_NS)";
+ using var reader = Command.ExecuteReader();
+ reader.Read();
+
+ AssertInfinityTimestampValues(reader, DuckDBType.TimestampNs);
+ }
+
+ [Fact]
+ public void ReadInfinityTimestampNsWithParameters()
+ {
+ Command.CommandText = "SELECT $1::TIMESTAMP_NS, $2::TIMESTAMP_NS, isinf($1::TIMESTAMP_NS), isinf($2::TIMESTAMP_NS)";
+ Command.Parameters.Add(new DuckDBParameter(DuckDBTimestamp.PositiveInfinity));
+ Command.Parameters.Add(new DuckDBParameter(DuckDBTimestamp.NegativeInfinity));
+ using var reader = Command.ExecuteReader();
+ reader.Read();
+
+ AssertInfinityTimestampValues(reader, DuckDBType.TimestampNs);
+ }
+
+ [Fact]
+ public void ReadInfinityTimestampMs()
+ {
+ Command.CommandText = "SELECT 'infinity'::TIMESTAMP_MS, '-infinity'::TIMESTAMP_MS, isinf('infinity'::TIMESTAMP_MS), isinf('-infinity'::TIMESTAMP_MS)";
+ using var reader = Command.ExecuteReader();
+ reader.Read();
+
+ AssertInfinityTimestampValues(reader, DuckDBType.TimestampMs);
+ }
+
+ [Fact]
+ public void ReadInfinityTimestampMsWithParameters()
+ {
+ Command.CommandText = "SELECT $1::TIMESTAMP_MS, $2::TIMESTAMP_MS, isinf($1::TIMESTAMP_MS), isinf($2::TIMESTAMP_MS)";
+ Command.Parameters.Add(new DuckDBParameter(DuckDBTimestamp.PositiveInfinity));
+ Command.Parameters.Add(new DuckDBParameter(DuckDBTimestamp.NegativeInfinity));
+ using var reader = Command.ExecuteReader();
+ reader.Read();
+
+ AssertInfinityTimestampValues(reader, DuckDBType.TimestampMs);
+ }
+
+ [Fact]
+ public void ReadInfinityTimestampS()
+ {
+ Command.CommandText = "SELECT 'infinity'::TIMESTAMP_S, '-infinity'::TIMESTAMP_S, isinf('infinity'::TIMESTAMP_S), isinf('-infinity'::TIMESTAMP_S)";
+ using var reader = Command.ExecuteReader();
+ reader.Read();
+
+ AssertInfinityTimestampValues(reader, DuckDBType.TimestampS);
+ }
+
+ [Fact]
+ public void ReadInfinityTimestampSWithParameters()
+ {
+ Command.CommandText = "SELECT $1::TIMESTAMP_S, $2::TIMESTAMP_S, isinf($1::TIMESTAMP_S), isinf($2::TIMESTAMP_S)";
+ Command.Parameters.Add(new DuckDBParameter(DuckDBTimestamp.PositiveInfinity));
+ Command.Parameters.Add(new DuckDBParameter(DuckDBTimestamp.NegativeInfinity));
+ using var reader = Command.ExecuteReader();
+ reader.Read();
+
+ AssertInfinityTimestampValues(reader, DuckDBType.TimestampS);
+ }
+
+ [Fact]
+ public void ReadInfinityTimestampTz()
+ {
+ Command.CommandText = "SELECT 'infinity'::TIMESTAMPTZ, '-infinity'::TIMESTAMPTZ, isinf('infinity'::TIMESTAMPTZ), isinf('-infinity'::TIMESTAMPTZ)";
+ using var reader = Command.ExecuteReader();
+ reader.Read();
+
+ AssertInfinityTimestampValues(reader, DuckDBType.TimestampTz);
+ }
+
+ [Fact]
+ public void ReadInfinityTimestampTzWithParameters()
+ {
+ Command.CommandText = "SELECT $1::TIMESTAMPTZ, $2::TIMESTAMPTZ, isinf($1::TIMESTAMPTZ), isinf($2::TIMESTAMPTZ)";
+ Command.Parameters.Add(new DuckDBParameter(DuckDBTimestamp.PositiveInfinity));
+ Command.Parameters.Add(new DuckDBParameter(DuckDBTimestamp.NegativeInfinity));
+ using var reader = Command.ExecuteReader();
+ reader.Read();
+
+ AssertInfinityTimestampValues(reader, DuckDBType.TimestampTz);
+ }
+
+ #endregion
+
+ #region Mixed Infinity and Finite Values
+
+ [Fact]
+ public void ReadMixedInfinityDatesAsDuckDBDateOnly()
+ {
+ Command.CommandText = "SELECT * FROM (VALUES ('infinity'::DATE), ('-infinity'::DATE), ('2024-01-15'::DATE)) AS t(d)";
+ using var reader = Command.ExecuteReader();
+
+ reader.Read();
+ var positiveInfinity = reader.GetFieldValue(0);
+ positiveInfinity.IsPositiveInfinity.Should().BeTrue();
+
+ reader.Read();
+ var negativeInfinity = reader.GetFieldValue(0);
+ negativeInfinity.IsNegativeInfinity.Should().BeTrue();
+
+ reader.Read();
+ var finiteDate = reader.GetFieldValue(0);
+ finiteDate.IsInfinity.Should().BeFalse();
+ finiteDate.Year.Should().Be(2024);
+ finiteDate.Month.Should().Be(1);
+ finiteDate.Day.Should().Be(15);
+ }
+
+ [Fact]
+ public void ReadMixedInfinityTimestampsAsDuckDBTimestamp()
+ {
+ Command.CommandText = "SELECT * FROM (VALUES ('infinity'::TIMESTAMP), ('-infinity'::TIMESTAMP), ('2024-01-15 12:30:45'::TIMESTAMP)) AS t(ts)";
+ using var reader = Command.ExecuteReader();
+
+ reader.Read();
+ var positiveInfinity = reader.GetFieldValue(0);
+ positiveInfinity.IsPositiveInfinity.Should().BeTrue();
+
+ reader.Read();
+ var negativeInfinity = reader.GetFieldValue(0);
+ negativeInfinity.IsNegativeInfinity.Should().BeTrue();
+
+ reader.Read();
+ var finiteTimestamp = reader.GetFieldValue(0);
+ finiteTimestamp.IsInfinity.Should().BeFalse();
+ finiteTimestamp.Date.Year.Should().Be(2024);
+ finiteTimestamp.Date.Month.Should().Be(1);
+ finiteTimestamp.Date.Day.Should().Be(15);
+ }
+
+ #endregion
+}