npgsql: Bug: TimeSpan 24:00:00 is truncated to 00:00:00

PostgreSQL allows time type to store values from 00:00:00 to 24:00:00: https://www.postgresql.org/docs/9.6/datatype-datetime.html

However, if I try to store TimeSpan.FromHours(24) using Npgsql, it will be converted to 00:00:00.

In the following unit test only one case will succeed:

public class NpgsqlTimeSpanTests
{
    public static IEnumerable<object[]> TestCases
    {
        get
        {
            yield return new object[] { TimeSpan.FromHours(24).Add(TimeSpan.FromMilliseconds(-1)) };
            // this will fail
            yield return new object[] { TimeSpan.FromHours(24) };
        }
    }

    [Theory]
    [MemberData(nameof(TestCases), MemberType = typeof(NpgsqlTimeSpanTests))]
    public async Task TimeSpan24Hours_Test(TimeSpan time)
    {
        using (var connection = new NpgsqlConnection("..."))
        {
            await connection.OpenAsync();

            using (var transaction = connection.BeginTransaction())
            {
                using (var command = connection.CreateCommand())
                {
                    command.CommandText = "CREATE TABLE IF NOT EXISTS timespantest (ts time without time zone not null)";
                    command.Transaction = transaction;
                    await command.ExecuteNonQueryAsync();
                }

                using (var command = connection.CreateCommand())
                {
                    command.CommandText = "INSERT INTO timespantest (ts) VALUES (@ts)";
                    command.Transaction = transaction;
                    command.Parameters.AddWithValue("@ts", time);
                    await command.ExecuteNonQueryAsync();
                }

                // make sure the returned value is the same what we saved
                using (var command = connection.CreateCommand())
                {
                    command.CommandText = "SELECT ts FROM timespantest LIMIT 1";
                    command.Transaction = transaction;
                    var value = (TimeSpan) await command.ExecuteScalarAsync();

                    Assert.Equal(value, time);
                }

                await transaction.RollbackAsync();
            }
        }
    }
}

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 18 (16 by maintainers)

Most upvoted comments

Mapping TimeSpan to time by default would cause an error for all cases where more than 24 hours are represented.

Oh, right. Wasn’t thinking about that.

Do you still feel we should discuss changing the default mapping? Do we have a good reason to do so, apart from this specific quirk which seems pretty exotic? Otherwise this can be closed as the expected behavior.

Nope, I think we can close as expected (if quirky) behavior.

@austindrenski thanks for the detailed investigation - it’s great to finally understand what’s going here. There does seem to be a bit of discrepancy in the docs, but the basic mappings page does specify that interval is auto-inferred when writing a CLR TimeSpan. IIRC the decision to do this was because .NET TimeSpan supports multiple days, whereas PostgreSQL time does not (up to 24 hours only). Mapping TimeSpan to time by default would cause an error for all cases where more than 24 hours are represented.

As always, NodaTime’s mappings correspond better, as it has distinct LocalTime and Period types…

Do you still feel we should discuss changing the default mapping? Do we have a good reason to do so, apart from this specific quirk which seems pretty exotic? Otherwise this can be closed as the expected behavior.

@austindrenski any chance you can take a look at this again?