public LocalTime? GetTimeNullable()

in modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleReader.cs [333:633]


        public LocalTime? GetTimeNullable(int index) => Seek(index) switch
        {
            { IsEmpty: true } => null,
            { Length: >= 4 and <= 6 } s => ReadTime(s),
            var s => throw GetInvalidLengthException(index, 6, s.Length)
        };

        /// <summary>
        /// Gets a local date and time value.
        /// </summary>
        /// <param name="index">Index.</param>
        /// <returns>Value.</returns>
        public LocalDateTime GetDateTime(int index) => GetDateTimeNullable(index) ?? ThrowNullElementException<LocalDateTime>(index);

        /// <summary>
        /// Gets a local date and time value.
        /// </summary>
        /// <param name="index">Index.</param>
        /// <returns>Value.</returns>
        public LocalDateTime? GetDateTimeNullable(int index) => Seek(index) switch
        {
            { IsEmpty: true } => null,
            { Length: >= 7 and <= 9 } s => ReadDate(s) + ReadTime(s[3..]),
            var s => throw GetInvalidLengthException(index, 9, s.Length)
        };

        /// <summary>
        /// Gets a timestamp (instant) value.
        /// </summary>
        /// <param name="index">Index.</param>
        /// <returns>Value.</returns>
        public Instant GetTimestamp(int index) => GetTimestampNullable(index) ?? ThrowNullElementException<Instant>(index);

        /// <summary>
        /// Gets a timestamp (instant) value.
        /// </summary>
        /// <param name="index">Index.</param>
        /// <returns>Value.</returns>
        public Instant? GetTimestampNullable(int index) => Seek(index) switch
        {
            { IsEmpty: true } => null,
            { Length: 8 } s => Instant.FromUnixTimeSeconds(BinaryPrimitives.ReadInt64LittleEndian(s)),
            { Length: 12 } s => Instant.FromUnixTimeSeconds(BinaryPrimitives.ReadInt64LittleEndian(s))
                .PlusNanoseconds(BinaryPrimitives.ReadInt32LittleEndian(s[8..])),
            var s => throw GetInvalidLengthException(index, 12, s.Length)
        };

        /// <summary>
        /// Gets a duration value.
        /// </summary>
        /// <param name="index">Index.</param>
        /// <returns>Value.</returns>
        public Duration GetDuration(int index) => GetDurationNullable(index) ?? ThrowNullElementException<Duration>(index);

        /// <summary>
        /// Gets a duration value.
        /// </summary>
        /// <param name="index">Index.</param>
        /// <returns>Value.</returns>
        public Duration? GetDurationNullable(int index) => Seek(index) switch
        {
            { IsEmpty: true } => null,
            { Length: 8 } s => Duration.FromSeconds(BinaryPrimitives.ReadInt64LittleEndian(s)),
            { Length: 12 } s => Duration.FromSeconds(BinaryPrimitives.ReadInt64LittleEndian(s))
                .Plus(Duration.FromNanoseconds(BinaryPrimitives.ReadInt32LittleEndian(s[8..]))),
            var s => throw GetInvalidLengthException(index, 12, s.Length)
        };

        /// <summary>
        /// Gets a period value.
        /// </summary>
        /// <param name="index">Index.</param>
        /// <returns>Value.</returns>
        public Period GetPeriod(int index) => GetPeriodNullable(index) ?? ThrowNullElementException<Period>(index);

        /// <summary>
        /// Gets a period value.
        /// </summary>
        /// <param name="index">Index.</param>
        /// <returns>Value.</returns>
        public Period? GetPeriodNullable(int index) => Seek(index) switch
        {
            { IsEmpty: true } => null,
            { Length: 3 } s => Period.FromYears(unchecked((sbyte)s[0])) +
                               Period.FromMonths(unchecked((sbyte)s[1])) +
                               Period.FromDays(unchecked((sbyte)s[2])),
            { Length: 6 } s => Period.FromYears(BinaryPrimitives.ReadInt16LittleEndian(s)) +
                               Period.FromMonths(BinaryPrimitives.ReadInt16LittleEndian(s[2..])) +
                               Period.FromDays(BinaryPrimitives.ReadInt16LittleEndian(s[4..])),
            { Length: 12 } s => Period.FromYears(BinaryPrimitives.ReadInt32LittleEndian(s)) +
                                Period.FromMonths(BinaryPrimitives.ReadInt32LittleEndian(s[4..])) +
                                Period.FromDays(BinaryPrimitives.ReadInt32LittleEndian(s[8..])),
            var s => throw GetInvalidLengthException(index, 12, s.Length)
        };

        /// <summary>
        /// Gets bytes.
        /// </summary>
        /// <param name="index">Index.</param>
        /// <returns>Value.</returns>
        public byte[] GetBytes(int index) => GetBytesNullable(index) ?? throw GetNullElementException(index);

        /// <summary>
        /// Gets bytes.
        /// </summary>
        /// <param name="index">Index.</param>
        /// <returns>Value.</returns>
        public byte[]? GetBytesNullable(int index) => Seek(index) switch
        {
            { IsEmpty: true } => null,
            var s when s[0] == BinaryTupleCommon.VarlenEmptyByte => s[1..].ToArray(),
            var s => s.ToArray()
        };

        /// <summary>
        /// Gets bytes.
        /// </summary>
        /// <param name="index">Index.</param>
        /// <returns>Value.</returns>
        public ReadOnlySpan<byte> GetBytesSpan(int index) => Seek(index) switch
        {
            { IsEmpty: true } => throw GetNullElementException(index),
            var s when s[0] == BinaryTupleCommon.VarlenEmptyByte => s[1..],
            var s => s
        };

        /// <summary>
        /// Gets an object value according to the specified type.
        /// </summary>
        /// <param name="index">Index.</param>
        /// <param name="columnType">Column type.</param>
        /// <param name="scale">Column decimal scale.</param>
        /// <returns>Value.</returns>
        public object? GetObject(int index, ColumnType columnType, int scale = 0) =>
            columnType switch
            {
                ColumnType.Int8 => GetByteNullable(index),
                ColumnType.Int16 => GetShortNullable(index),
                ColumnType.Int32 => GetIntNullable(index),
                ColumnType.Int64 => GetLongNullable(index),
                ColumnType.Float => GetFloatNullable(index),
                ColumnType.Double => GetDoubleNullable(index),
                ColumnType.Uuid => GetGuidNullable(index),
                ColumnType.String => GetStringNullable(index),
                ColumnType.Decimal => GetDecimalNullable(index, scale),
                ColumnType.ByteArray => GetBytesNullable(index),
                ColumnType.Bitmask => GetBitmaskNullable(index),
                ColumnType.Date => GetDateNullable(index),
                ColumnType.Time => GetTimeNullable(index),
                ColumnType.Datetime => GetDateTimeNullable(index),
                ColumnType.Timestamp => GetTimestampNullable(index),
                ColumnType.Number => GetNumberNullable(index),
                ColumnType.Boolean => GetByteAsBoolNullable(index),
                ColumnType.Period => GetPeriodNullable(index),
                ColumnType.Duration => GetDurationNullable(index),
                _ => throw new IgniteClientException(ErrorGroups.Client.Protocol, "Unsupported type: " + columnType)
            };

        /// <summary>
        /// Gets an object value according to the type code at the specified index.
        /// </summary>
        /// <param name="index">Index.</param>
        /// <returns>Value.</returns>
        public object? GetObject(int index)
        {
            if (IsNull(index))
            {
                return null;
            }

            var type = (ColumnType)GetInt(index);
            var scale = GetInt(index + 1);

            return GetObject(index + 2, type, scale);
        }

        private static LocalDate ReadDate(ReadOnlySpan<byte> span)
        {
            // Read int32 from 3 bytes, preserving sign.
            Span<byte> buf = stackalloc byte[4];
            span[..3].CopyTo(buf[1..]);

            int date = BinaryPrimitives.ReadInt32LittleEndian(buf) >> 8;

            int day = date & 31;
            int month = (date >> 5) & 15;
            int year = (date >> 9); // Sign matters.

            return new LocalDate(year, month, day);
        }

        private static LocalTime ReadTime(ReadOnlySpan<byte> span)
        {
            long time = BinaryPrimitives.ReadUInt32LittleEndian(span);
            var length = span.Length;

            int nanos;
            if (length == 4)
            {
                nanos = ((int) time & ((1 << 10) - 1)) * 1000 * 1000;
                time >>= 10;
            }
            else if (length == 5)
            {
                time |= (long)span[4] << 32;
                nanos = ((int) time & ((1 << 20) - 1)) * 1000;
                time >>= 20;
            }
            else
            {
                time |= (long)BinaryPrimitives.ReadUInt16LittleEndian(span[4..]) << 32;
                nanos = ((int)time & ((1 << 30) - 1));
                time >>= 30;
            }

            int second = ((int) time) & 63;
            int minute = ((int) time >> 6) & 63;
            int hour = ((int) time >> 12) & 31;

            return LocalTime.FromHourMinuteSecondNanosecond(hour, minute, second, nanos);
        }

        private static decimal? ReadDecimal(ReadOnlySpan<byte> span, int scale)
        {
            if (span.IsEmpty)
            {
                return null;
            }

            var unscaled = new BigInteger(span, isBigEndian: true);
            var res = (decimal)unscaled;

            if (scale > 0)
            {
                res /= (decimal)BigInteger.Pow(10, scale);
            }

            return res;
        }

        private static T ThrowNullElementException<T>(int index) => throw GetNullElementException(index);

        private static InvalidOperationException GetNullElementException(int index) =>
            new($"Binary tuple element with index {index} is null.");

        private static InvalidOperationException GetInvalidLengthException(int index, int expectedLength, int actualLength) =>
            new($"Binary tuple element with index {index} has invalid length (expected {expectedLength}, actual {actualLength}).");

        private int GetOffset(int position)
        {
            var span = _buffer[position..];

            switch (_entrySize)
            {
                case 1:
                    return span[0];

                case 2:
                    return BinaryPrimitives.ReadUInt16LittleEndian(span);

                case 4:
                {
                    var offset = BinaryPrimitives.ReadInt32LittleEndian(span);

                    if (offset < 0)
                    {
                        throw new InvalidOperationException("Unsupported offset table size");
                    }

                    return offset;
                }

                default:
                    throw new InvalidOperationException("Invalid offset table size");
            }
        }

        private ReadOnlySpan<byte> Seek(int index)
        {
            Debug.Assert(index >= 0, "index >= 0");
            Debug.Assert(index < _numElements, "index < numElements");

            int entry = _entryBase + index * _entrySize;

            int offset = _valueBase;
            if (index > 0)
            {
                offset += GetOffset(entry - _entrySize);
            }

            int nextOffset = _valueBase + GetOffset(entry);

            if (nextOffset < offset)
            {
                throw new InvalidOperationException("Corrupted offset table");
            }

            return _buffer.Slice(offset, nextOffset - offset);
        }
    }
}