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
36 changes: 18 additions & 18 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@

- name: Install activerecord and testsuite from PyPI
run: |
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30" # NOSONAR
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/extract-epoch"

Check warning on line 58 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Omitting "--only-binary :all:" can lead to the execution of setup scripts. Make sure it is safe here.

See more on https://sonarcloud.io/project/issues?id=rhosocial_python-activerecord-sqlserver&issues=AaG7ee_I_mnc5PvfVX-B&open=AaG7ee_I_mnc5PvfVX-B&pullRequest=11

Check warning on line 58 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using dependencies without locking resolved versions is security-sensitive.

See more on https://sonarcloud.io/project/issues?id=rhosocial_python-activerecord-sqlserver&issues=AaG7ee_J_mnc5PvfVX-C&open=AaG7ee_J_mnc5PvfVX-C&pullRequest=11

- name: Install dependencies
run: |
Expand Down Expand Up @@ -144,8 +144,8 @@

- name: Install activerecord and testsuite from PyPI
run: |
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30" # NOSONAR
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/extract-epoch"

Check warning on line 148 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using dependencies without locking resolved versions is security-sensitive.

See more on https://sonarcloud.io/project/issues?id=rhosocial_python-activerecord-sqlserver&issues=AaG7ee_J_mnc5PvfVX-E&open=AaG7ee_J_mnc5PvfVX-E&pullRequest=11

Check warning on line 148 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Omitting "--only-binary :all:" can lead to the execution of setup scripts. Make sure it is safe here.

See more on https://sonarcloud.io/project/issues?id=rhosocial_python-activerecord-sqlserver&issues=AaG7ee_J_mnc5PvfVX-D&open=AaG7ee_J_mnc5PvfVX-D&pullRequest=11

- name: Install dependencies
run: |
Expand Down Expand Up @@ -234,8 +234,8 @@

- name: Install activerecord and testsuite from PyPI
run: |
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30" # NOSONAR
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/extract-epoch"

Check warning on line 238 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Omitting "--only-binary :all:" can lead to the execution of setup scripts. Make sure it is safe here.

See more on https://sonarcloud.io/project/issues?id=rhosocial_python-activerecord-sqlserver&issues=AaG7ee_J_mnc5PvfVX-F&open=AaG7ee_J_mnc5PvfVX-F&pullRequest=11

Check warning on line 238 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using dependencies without locking resolved versions is security-sensitive.

See more on https://sonarcloud.io/project/issues?id=rhosocial_python-activerecord-sqlserver&issues=AaG7ee_J_mnc5PvfVX-G&open=AaG7ee_J_mnc5PvfVX-G&pullRequest=11

- name: Install dependencies
run: |
Expand Down Expand Up @@ -324,8 +324,8 @@

- name: Install activerecord and testsuite from PyPI
run: |
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30" # NOSONAR
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/extract-epoch"

Check warning on line 328 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using dependencies without locking resolved versions is security-sensitive.

See more on https://sonarcloud.io/project/issues?id=rhosocial_python-activerecord-sqlserver&issues=AaG7ee_J_mnc5PvfVX-H&open=AaG7ee_J_mnc5PvfVX-H&pullRequest=11

- name: Install dependencies
run: |
Expand Down Expand Up @@ -414,8 +414,8 @@

- name: Install activerecord and testsuite from PyPI
run: |
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30" # NOSONAR
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/extract-epoch"

Check warning on line 418 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using dependencies without locking resolved versions is security-sensitive.

See more on https://sonarcloud.io/project/issues?id=rhosocial_python-activerecord-sqlserver&issues=AaG7ee_J_mnc5PvfVX-I&open=AaG7ee_J_mnc5PvfVX-I&pullRequest=11

- name: Install dependencies
run: |
Expand Down Expand Up @@ -504,8 +504,8 @@

- name: Install activerecord and testsuite from PyPI
run: |
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30" # NOSONAR
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/extract-epoch"

Check warning on line 508 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Omitting "--only-binary :all:" can lead to the execution of setup scripts. Make sure it is safe here.

See more on https://sonarcloud.io/project/issues?id=rhosocial_python-activerecord-sqlserver&issues=AaG7ee_J_mnc5PvfVX-J&open=AaG7ee_J_mnc5PvfVX-J&pullRequest=11

Check warning on line 508 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using dependencies without locking resolved versions is security-sensitive.

See more on https://sonarcloud.io/project/issues?id=rhosocial_python-activerecord-sqlserver&issues=AaG7ee_J_mnc5PvfVX-K&open=AaG7ee_J_mnc5PvfVX-K&pullRequest=11

- name: Install dependencies
run: |
Expand Down Expand Up @@ -595,8 +595,8 @@

- name: Install activerecord and testsuite from PyPI
run: |
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30" # NOSONAR
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/extract-epoch"

Check warning on line 599 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Omitting "--only-binary :all:" can lead to the execution of setup scripts. Make sure it is safe here.

See more on https://sonarcloud.io/project/issues?id=rhosocial_python-activerecord-sqlserver&issues=AaG7ee_J_mnc5PvfVX-L&open=AaG7ee_J_mnc5PvfVX-L&pullRequest=11

Check warning on line 599 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using dependencies without locking resolved versions is security-sensitive.

See more on https://sonarcloud.io/project/issues?id=rhosocial_python-activerecord-sqlserver&issues=AaG7ee_J_mnc5PvfVX-M&open=AaG7ee_J_mnc5PvfVX-M&pullRequest=11

- name: Install dependencies
run: |
Expand Down Expand Up @@ -685,8 +685,8 @@

- name: Install activerecord and testsuite from PyPI
run: |
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30" # NOSONAR
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/extract-epoch"

Check warning on line 689 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using dependencies without locking resolved versions is security-sensitive.

See more on https://sonarcloud.io/project/issues?id=rhosocial_python-activerecord-sqlserver&issues=AaG7ee_J_mnc5PvfVX-O&open=AaG7ee_J_mnc5PvfVX-O&pullRequest=11

Check warning on line 689 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Omitting "--only-binary :all:" can lead to the execution of setup scripts. Make sure it is safe here.

See more on https://sonarcloud.io/project/issues?id=rhosocial_python-activerecord-sqlserver&issues=AaG7ee_J_mnc5PvfVX-N&open=AaG7ee_J_mnc5PvfVX-N&pullRequest=11

- name: Install dependencies
run: |
Expand Down Expand Up @@ -784,8 +784,8 @@

- name: Install activerecord and testsuite from PyPI
run: |
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30" # NOSONAR
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/extract-epoch"

Check warning on line 788 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using dependencies without locking resolved versions is security-sensitive.

See more on https://sonarcloud.io/project/issues?id=rhosocial_python-activerecord-sqlserver&issues=AaG7ee_J_mnc5PvfVX-Q&open=AaG7ee_J_mnc5PvfVX-Q&pullRequest=11

Check warning on line 788 in .github/workflows/test.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Omitting "--only-binary :all:" can lead to the execution of setup scripts. Make sure it is safe here.

See more on https://sonarcloud.io/project/issues?id=rhosocial_python-activerecord-sqlserver&issues=AaG7ee_J_mnc5PvfVX-P&open=AaG7ee_J_mnc5PvfVX-P&pullRequest=11

- name: Install dependencies
run: |
Expand Down
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,30 @@ CreateTableStatement(
)
```

### Signed seconds between two instants

`seconds_between(start, end)` answers `end - start` in seconds, keeping the
sign and the fraction. SQL Server has no interval subtraction, so the backend
counts milliseconds with `DATEDIFF_BIG` and divides by `1000.0`:

```python
from rhosocial.activerecord.backend.expression import (
Column,
seconds_between,
)

elapsed = seconds_between(dialect, Column(dialect, "started_at"),
Column(dialect, "ended_at")).as_("seconds")
# DATEDIFF_BIG(MILLISECOND, [started_at], [ended_at]) / 1000.0 AS [seconds]
```

`DATEDIFF_BIG` rather than `DATEDIFF` because a millisecond count overflows a
32-bit `int` after about 24 days; the result is exact to a millisecond, which
is the resolution of `DATEDIFF_BIG`, not of `datetime2`. The function arrived
in SQL Server 2016; a server below that floor refuses the expression, and
`supports_functions()["seconds_between"]` reports `False`. An `end` earlier
than `start` subtracts to a negative value with no extra sign handling.

## Version Gates

Gates below are the versions at which this backend's own `supports_*`
Expand Down
2 changes: 2 additions & 0 deletions changelog.d/11.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Render `seconds_between` as `DATEDIFF_BIG(MILLISECOND, ...) / 1000.0`, refusing below SQL Server 2016.

Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,7 @@ class SQLServerDialect(
"datetimeoffsetfromparts": (SQL_SERVER_2012, None),
"timefromparts": (SQL_SERVER_2012, None),
"datediff_big": (SQL_SERVER_2016, None),
"seconds_between": (SQL_SERVER_2016, None),
"approx_count_distinct": (SQL_SERVER_2019, None),
"json_path_exists": (SQL_SERVER_2022, None),
"json_object": (SQL_SERVER_2022, None),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from typing import Any, Tuple, TYPE_CHECKING

from rhosocial.activerecord.backend.dialect.exceptions import UnsupportedFeatureError
from .version_constants import SQL_SERVER_2016

if TYPE_CHECKING:
from ..dialect import SQLServerDialect
Expand Down Expand Up @@ -89,3 +90,33 @@ def format_datetime_diff_expression(self, expr: "Any") -> Tuple[str, tuple]:
end_sql, end_params = expr.end.to_sql()
sql = f"DATEDIFF({expr.unit.value.upper()}, {start_sql}, {end_sql})"
return self.apply_alias(sql, start_params + end_params, expr)

def format_seconds_between_expression(self, expr: "Any") -> Tuple[str, tuple]:
"""Format signed seconds as ``DATEDIFF_BIG`` milliseconds over 1000.0.

``DATEDIFF_BIG`` counts the millisecond boundaries crossed from the
first instant to the second, so ``end`` before ``start`` is already a
negative count and no sign correction is needed. ``BIG`` rather than
plain ``DATEDIFF`` because a millisecond count overflows ``int`` after
roughly 24 days, and a span between two timestamps is routinely longer;
it has been available since SQL Server 2016.

Dividing by ``1000.0`` keeps the millisecond part rather than truncating
to whole seconds. A second is stored with sub-millisecond precision in
``datetime2``, but ``DATEDIFF_BIG`` counts milliseconds, so the rendered
value is exact to a millisecond and the finer part is the engine's
rounding, not this node's.

The SQL writes ``start`` before ``end`` -- that is the argument order
``DATEDIFF_BIG`` takes -- so the parameters are collected in that order.
"""
if self.version < SQL_SERVER_2016:
raise UnsupportedFeatureError(
self.name,
"seconds_between(start, end)",
"DATEDIFF_BIG requires SQL Server 2016 or later.",
)
start_sql, start_params = expr.start.to_sql()
end_sql, end_params = expr.end.to_sql()
sql = f"DATEDIFF_BIG(MILLISECOND, {start_sql}, {end_sql}) / 1000.0"
return self.apply_alias(sql, start_params + end_params, expr)
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
# tests/rhosocial/activerecord_sqlserver_test/feature/backend/expression/test_seconds_between.py
"""``seconds_between`` on SQL Server: ``DATEDIFF_BIG`` milliseconds over 1000.

The node answers a question -- the signed, fractional distance from one instant
to another -- and this backend spells it as a millisecond count divided by
``1000.0``. The offline cases pin the SQL text, the bind order and the alias;
the live cases execute the same statement so the sign, the zero, the fraction
and the calendar boundary are measured rather than assumed.
"""

import pytest

from rhosocial.activerecord.backend.dialect.exceptions import UnsupportedFeatureError
from rhosocial.activerecord.backend.expression import (
Column,
Literal,
QueryStatement,
seconds_between,
)
from rhosocial.activerecord.backend.expression.column_types import TimestampColumn
from rhosocial.activerecord.backend.expression.datetime import SecondsBetweenExpression
from rhosocial.activerecord.backend.expression.value_types import NumericValue
from rhosocial.activerecord.backend.impl.sqlserver.dialect import SQLServerDialect
from rhosocial.activerecord.backend.options import ExecutionOptions
from rhosocial.activerecord.backend.schema import StatementType

DQL_OPTIONS = ExecutionOptions(stmt_type=StatementType.DQL)


@pytest.fixture
def dialect():
"""A SQL Server 2022 dialect."""
return SQLServerDialect(version=(16, 0, 0))


class TestSecondsBetweenSqlText:
"""The spelling, without a server."""

def test_columns(self, dialect):
expr = seconds_between(
dialect,
Column(dialect, "started_at"),
Column(dialect, "ended_at"),
)

sql, params = expr.to_sql()

assert sql == (
"DATEDIFF_BIG(MILLISECOND, [started_at], [ended_at]) / 1000.0"
)
assert params == ()

def test_literal_params_follow_sql_order(self, dialect):
expr = seconds_between(
dialect,
Literal(dialect, "2026-01-01 00:00:00"),
Literal(dialect, "2026-01-02 00:00:00"),
)

sql, params = expr.to_sql()

assert sql == "DATEDIFF_BIG(MILLISECOND, ?, ?) / 1000.0"
# ``start`` is the first argument ``DATEDIFF_BIG`` takes, so it binds
# first -- the SQL order, not the constructor's labels.
assert params == ("2026-01-01 00:00:00", "2026-01-02 00:00:00")

def test_alias_is_applied_after_the_division(self, dialect):
expr = seconds_between(
dialect,
Column(dialect, "started_at"),
Column(dialect, "ended_at"),
).as_("elapsed")

sql, params = expr.to_sql()

assert sql == (
"DATEDIFF_BIG(MILLISECOND, [started_at], [ended_at]) / 1000.0 "
"AS [elapsed]"
)
assert params == ()

def test_alias_keeps_bind_order(self, dialect):
expr = seconds_between(
dialect,
Literal(dialect, "2026-01-01 00:00:00"),
Literal(dialect, "2026-01-02 00:00:00"),
).as_("elapsed")

sql, params = expr.to_sql()

assert sql == "DATEDIFF_BIG(MILLISECOND, ?, ?) / 1000.0 AS [elapsed]"
assert params == ("2026-01-01 00:00:00", "2026-01-02 00:00:00")

def test_factory_coerces_string_to_column(self, dialect):
expr = seconds_between(dialect, "started_at", "ended_at")

sql, params = expr.to_sql()

assert sql == (
"DATEDIFF_BIG(MILLISECOND, [started_at], [ended_at]) / 1000.0"
)
assert params == ()

def test_column_method(self, dialect):
started = TimestampColumn(dialect, "started_at")
ended = TimestampColumn(dialect, "ended_at")

sql, params = started.seconds_between(ended).to_sql()

assert sql == (
"DATEDIFF_BIG(MILLISECOND, [started_at], [ended_at]) / 1000.0"
)
assert params == ()

def test_node_is_a_fractional_number(self, dialect):
expr = seconds_between(
dialect,
Column(dialect, "started_at"),
Column(dialect, "ended_at"),
)

assert isinstance(expr, SecondsBetweenExpression)
assert isinstance(expr, NumericValue)
assert expr.format_method == "format_seconds_between_expression"


class TestSecondsBetweenVersionFloor:
"""``DATEDIFF_BIG`` arrived in SQL Server 2016, and the backend says so."""

def test_before_2016_refuses(self):
dialect = SQLServerDialect(version=(12, 0, 0))
expr = seconds_between(
dialect,
Column(dialect, "started_at"),
Column(dialect, "ended_at"),
)

with pytest.raises(UnsupportedFeatureError) as excinfo:
expr.to_sql()

assert "DATEDIFF_BIG" in str(excinfo.value)
assert "2016" in str(excinfo.value)

def test_support_probe_reports_the_floor(self):
old = SQLServerDialect(version=(12, 0, 0))
new = SQLServerDialect(version=(13, 0, 0))

assert old.supports_functions().get("seconds_between") is False
assert new.supports_functions().get("seconds_between") is True


def _elapsed(backend, start: str, end: str) -> float:
"""Run ``seconds_between(start, end)`` and return the scalar."""
dialect = backend.dialect
expr = seconds_between(
dialect,
Literal(dialect, start),
Literal(dialect, end),
)
query = QueryStatement(dialect, select=[expr.as_("seconds")])
sql, params = query.to_sql()
result = backend.execute(sql, params, options=DQL_OPTIONS)
# ``DATEDIFF_BIG(...) / 1000.0`` is numeric, which pyodbc returns as
# ``Decimal``; the assertions compare the value, not the wrapper.
return float(result.data[0]["seconds"])


class TestSecondsBetweenExecution:
"""The value SQL Server actually produces."""

@pytest.mark.parametrize(
"start,end,expected",
[
("2026-06-04 10:00:00", "2026-06-04 10:00:00", 0.0),
("2026-06-04 10:00:00", "2026-06-04 11:00:00", 3600.0),
("2026-06-04 11:00:00", "2026-06-04 10:00:00", -3600.0),
("2026-01-31 23:59:59", "2026-02-01 00:00:00", 1.0),
("2025-12-31 12:00:00", "2026-01-01 12:00:00", 86400.0),
("2026-06-04 10:00:00.000", "2026-06-04 10:00:00.500", 0.5),
("2026-06-04 10:00:00.250", "2026-06-04 10:00:00.000", -0.25),
],
)
def test_interval(self, sqlserver_backend, start: str, end: str, expected: float):
assert _elapsed(sqlserver_backend, start, end) == pytest.approx(
expected, abs=1e-3
)

def test_span_longer_than_an_int_millisecond_count(self, sqlserver_backend):
"""``DATEDIFF_BIG`` survives a span plain ``DATEDIFF`` overflows.

A millisecond count above ``2**31 - 1`` is roughly 24.8 days, so 60
days is past the limit that makes the ``BIG`` spelling necessary.
"""
seconds = _elapsed(
sqlserver_backend,
"2026-01-01 00:00:00",
"2026-03-02 00:00:00",
)

assert seconds == pytest.approx(60 * 86400.0, abs=1e-3)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# tests/rhosocial/activerecord_sqlserver_test/feature/query/typed_column/test_seconds_between_contract.py
from rhosocial.activerecord.testsuite.feature.query.typed_column.test_seconds_between_contract import * # noqa: F401,F403
Loading