Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
85 changes: 77 additions & 8 deletions DuckDB.NET.Bindings/DuckDBDateOnly.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,26 @@ 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.
// It is theoretically equal to the max date value plus one day:
// '5881580-07-10'::date + 1
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.
// 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;
Expand All @@ -14,19 +34,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
}
}
30 changes: 30 additions & 0 deletions DuckDB.NET.Bindings/DuckDBNativeObjects.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,22 @@ public void Close()
[StructLayout(LayoutKind.Sequential)]
public struct DuckDBDate
{
/// <summary>
/// Represents DuckDB's positive infinity date value.
/// This is the value used in the DuckDB source code for +infinity dates.
/// </summary>
public static readonly DuckDBDate PositiveInfinity = new() { Days = int.MaxValue };
/// <summary>
/// Represents DuckDB's negative infinity date value.
/// This is the value used in the DuckDB source code for -infinity dates.
/// </summary>
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 +155,22 @@ public struct DuckDBTimeTz
[StructLayout(LayoutKind.Sequential)]
public struct DuckDBTimestampStruct
{
/// <summary>
/// Represents DuckDB's positive infinity timestamp value.
/// This is the value used in the DuckDB source code for +infinity timestamps.
/// </summary>
public static readonly DuckDBTimestampStruct PositiveInfinity = new() { Micros = long.MaxValue };
/// <summary>
/// Represents DuckDB's negative infinity timestamp value.
/// This is the value used in the DuckDB source code for -infinity timestamps.
/// </summary>
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 =
// 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));

/// <summary>
/// Represents negative infinity for DuckDB timestamps.
/// </summary>
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;

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

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

/// <summary>
/// Returns true if this timestamp represents negative infinity.
/// </summary>
public bool IsNegativeInfinity => Equals(NegativeInfinity);

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