Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 73 additions & 8 deletions DuckDB.NET.Bindings/DuckDBDateOnly.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,22 @@ namespace DuckDB.NET.Native;
[StructLayout(LayoutKind.Sequential)]
public readonly struct DuckDBDateOnly(int year, byte month, byte day)
{
/// <summary>
/// Represents positive infinity for DuckDB dates.
/// </summary>
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
new(5881580, 7, 11);

/// <summary>
/// Represents negative infinity for DuckDB dates.
/// </summary>
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.
new(-5877641, 6, 24);

public int Year { get; } = year;

public byte Month { get; } = month;
Expand All @@ -14,19 +30,68 @@ public readonly struct DuckDBDateOnly(int year, byte month, byte day)

internal static readonly DuckDBDateOnly MinValue = FromDateTime(DateTime.MinValue);

/// <summary>
/// Returns true if this date represents positive or negative infinity.
/// </summary>
public bool IsInfinity => IsPositiveInfinity || IsNegativeInfinity;

/// <summary>
/// Returns true if this date represents positive infinity.
/// </summary>
public bool IsPositiveInfinity => Equals(PositiveInfinity);

/// <summary>
/// Returns true if this date represents negative infinity.
/// </summary>
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

/// <summary>
/// Converts a DuckDBDate to DuckDBDateOnly, handling infinity values.
/// </summary>
public static DuckDBDateOnly FromDuckDBDate(DuckDBDate date)
{
if (date.IsPositiveInfinity)
return PositiveInfinity;
if (date.IsNegativeInfinity)
return NegativeInfinity;

return NativeMethods.DateTimeHelpers.DuckDBFromDate(date);
}

/// <summary>
/// Converts this DuckDBDateOnly to a DuckDBDate, handling infinity values.
/// </summary>
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
}
}
14 changes: 14 additions & 0 deletions DuckDB.NET.Bindings/DuckDBNativeObjects.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,14 @@ public void Close()
[StructLayout(LayoutKind.Sequential)]
public struct DuckDBDate
{
public static readonly DuckDBDate PositiveInfinity = new() { Days = int.MaxValue };
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)]
Expand All @@ -140,7 +147,14 @@ public struct DuckDBTimeTz
[StructLayout(LayoutKind.Sequential)]
public struct DuckDBTimestampStruct
{
public static readonly DuckDBTimestampStruct PositiveInfinity = new() { Micros = long.MaxValue };
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)]
Expand Down
62 changes: 61 additions & 1 deletion DuckDB.NET.Bindings/DuckDBTimestamp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,40 @@ namespace DuckDB.NET.Native;
[StructLayout(LayoutKind.Sequential)]
public readonly struct DuckDBTimestamp(DuckDBDateOnly date, DuckDBTimeOnly time)
{
/// <summary>
/// Represents positive infinity for DuckDB timestamps.
/// </summary>
public static readonly DuckDBTimestamp PositiveInfinity =
// The +infinity date value is not representable by the timestamp type,
// so this constant should never occur in normal usage
new(DuckDBDateOnly.PositiveInfinity, new DuckDBTimeOnly(0, 0, 0));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Infinity dates for timestamp types are different, should we use them instead?

Image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. The values returned for test_all_types are the min/max which are separate from the infinity values, so for the infinity values I updated to use the min/max +/- one interval, where the interval is the unit of the core type in the duckdb source (days for date_t and microseconds for timestamp_t)


/// <summary>
/// Represents negative infinity for DuckDB timestamps.
/// </summary>
public static readonly DuckDBTimestamp NegativeInfinity =
// The -infinity date value is not representable by the timestamp type,
// so this constant should never occur in normal usage
new(DuckDBDateOnly.NegativeInfinity, new DuckDBTimeOnly(0, 0, 0));

public DuckDBDateOnly Date { get; } = date;
public DuckDBTimeOnly Time { get; } = time;

/// <summary>
/// Returns true if this timestamp represents positive or negative infinity.
/// </summary>
public bool IsInfinity => Date.IsInfinity;

/// <summary>
/// Returns true if this timestamp represents positive infinity.
/// </summary>
public bool IsPositiveInfinity => Date.IsPositiveInfinity;

/// <summary>
/// Returns true if this timestamp represents negative infinity.
/// </summary>
public bool IsNegativeInfinity => Date.IsNegativeInfinity;

public DateTime ToDateTime()
{
return new DateTime(Date.Year, Date.Month, Date.Day).AddTicks(Time.Ticks);
Expand All @@ -18,4 +49,33 @@ public static DuckDBTimestamp FromDateTime(DateTime dateTime)
{
return new DuckDBTimestamp(DuckDBDateOnly.FromDateTime(dateTime), DuckDBTimeOnly.FromDateTime(dateTime));
}
}

/// <summary>
/// Converts a DuckDBTimestampStruct to DuckDBTimestamp, handling infinity values.
/// </summary>
public static DuckDBTimestamp FromDuckDBTimestampStruct(DuckDBTimestampStruct timestampStruct)
{
if (timestampStruct.IsPositiveInfinity)
return PositiveInfinity;
if (timestampStruct.IsNegativeInfinity)
return NegativeInfinity;

return NativeMethods.DateTimeHelpers.DuckDBFromTimestamp(timestampStruct);
}

/// <summary>
/// Converts this DuckDBTimestamp to a DuckDBTimestampStruct, handling infinity values.
/// </summary>
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);
}
16 changes: 8 additions & 8 deletions DuckDB.NET.Bindings/DuckDBWrapperObjects.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ protected override bool ReleaseHandle()
{
value.Dispose();
}

NativeMethods.Value.DuckDBDestroyValue(ref handle);
return true;
}
Expand Down Expand Up @@ -140,26 +140,26 @@ public T GetValue<T>()

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,
Expand Down
24 changes: 23 additions & 1 deletion DuckDB.NET.Bindings/NativeMethods/NativeMethods.DateTime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Loading
Loading