Skip to content

Conversation

@obabichevjb
Copy link
Collaborator

Description

It's the fix for timestamp() column. That column doesn't support time zones, and should store the values in local timezones.

But it was working different and converting values into UTC timezone. On reading that converted value was recognized as local, and shifted one more time to convert it to UTC.

After fix Exposed will store values with the value in local time zone, and expect that the value in local time zone in the moment of reading.

Tests also read the value from database as string, and check the value, to be sure that the value was not shifted to UTC in the moment of insert, and back in the moment of reading.


Type of Change

Please mark the relevant options with an "X":

  • Bug fix

Affected databases:

  • Mysql5
  • Mysql8
  • SQLite

Checklist


Related Issues

EXPOSED-731 Timestamp support for SQLite is broken

@obabichevjb obabichevjb self-assigned this Jul 22, 2025
@obabichevjb obabichevjb force-pushed the obabichev/exposed-731-timestamp-sqlite branch 3 times, most recently from 3434f22 to 691b5ff Compare July 23, 2025 12:54
@obabichevjb obabichevjb force-pushed the obabichev/exposed-731-timestamp-sqlite branch from 691b5ff to e4aa2ef Compare July 24, 2025 07:45
@obabichevjb obabichevjb requested a review from bog-walk July 24, 2025 08:46
}

private val MYSQL_FRACTION_DATE_TIME_STRING_FORMATTER by lazy {
private val SQLITE_AND_ORACLE_TIMESTAMP_STRING_FORMATTER
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This split into <FORMATTER>_NOTZ and FORMATTER is intended. It solves flaky issue with timezones.

The problem is that lazy caches the value, and that value is cached for the whole test run. And before .withZone() was inside the lazy call, so the first call was caching formatter with particular timezone.

For users it was not a problem, because usually people do not change current timezone in the code. But we do that in tets.

// TODO MYSQL_V8 test does not work on R2DBC now. The problem is that received timestamp is shifted by timezone.
withTables(excludeSettings = listOf(TestDB.MYSQL_V8), tester) {
// Cairo time zone
java.util.TimeZone.setDefault(java.util.TimeZone.getTimeZone("Africa/Cairo"))
Copy link
Member

Choose a reason for hiding this comment

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

Is there any chance these changes fix this TODO, or is that being too hopeful? 😢

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It doesn't fix (I'll recheck one more time) the problem for SQLite

I was moving these setDefault() higher when understood that lazy caches the values, and I wanted that even it's cached, the value for the whole test is the same. But actually it doesn't help on run multiple tests, because in this case the first test would init lazy and all other tests use it.

But I was surprised, default value of time zone is reset before every test (even if one gradle job runs multiple tests)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Actually, I found an intersting issue, I can't move that line back under withTables, it failes (with timestamps errors), but error seems to be in threading now...

The test is green if TimeZone.setDefault() is before withTables, and red otherwise. I tried to find the moment when moving TimeZone.setDefault() starts to break the test.

I stopped in the function withConnection:

private suspend fun <T> withConnection(body: suspend Connection.() -> T): T {
    if (localConnection == null) {
        // If the `setDefault()` here, test is **Green**
        TimeZone.setDefault(TimeZone.getTimeZone("Africa/Cairo")) 
        val awaitFirst = connection.awaitFirst()
        // If the `setDefault()` here, test is **Red**
        TimeZone.setDefault(TimeZone.getTimeZone("Africa/Cairo"))

        localConnection = awaitFirst.also {
            // this starts an explicit transaction with autoCommit mode off
            it.beginTransaction().awaitFirstOrNull()
        }
    }
    return localConnection!!.body()
}

I'm not sure why exactly that line breaks the TimeZone. The main hypothesis is that TimeZone() set default changes time zone only or current thread, and it's not friendly to koroutines, and we should expect that time zone is not changing on runtime.

@obabichevjb obabichevjb merged commit 5af48a7 into main Jul 28, 2025
6 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants