DatabaseJobFeature silently sets the global SQL Server `DateTimeConverter.DateStyle` to `Utc`, which shifts all `DateTime.Now` values by -8 hours. Why, and can it be opted out?

DatabaseJobFeature silently sets the global SQL Server DateTimeConverter.DateStyle to Utc, which shifts all DateTime.Now values by -8 hours. Why, and can it be opted out?

Suggested forum category: Customer Forums / OrmLite (or Background Jobs)


TL;DR

After registering DatabaseJobFeature (ServiceStack Background Jobs), every DateTime.Now (Kind = Local) that my application writes to any SQL Server table through OrmLite is stored as UTC. On a server in UTC+8 the stored value is 8 hours earlier than the wall-clock time. This happens because DatabaseJobFeature.Init() contains:

DateTimeConverter dateTimeConverter = Dialect.GetDateTimeConverter();
if (dateTimeConverter.DateStyle == DateTimeKind.Unspecified)
    dateTimeConverter.DateStyle = DateTimeKind.Utc;

DateStyle is a property of the dialect-wide shared converter, not of the job tables, so the side effect leaks into every table that uses the same dialect instance (SqlServerDialect.Provider, SqlServer2017Dialect.Provider, …).

Questions:

  1. Why does DatabaseJobFeature need to change the global DateStyle?

  2. Is there a supported way to disable or scope this change (for example, a feature option, or only applying it to a dedicated NamedConnection / dialect instance)?

  3. If not, would you consider making it opt-in or scoped to the job tables?

Environment

Item Value
Runtime ASP.NET Core, net10.0
ServiceStack 10.4.0 (also reproduced on 10.3.0; the same code exists in 10.0.6)
OrmLite ServiceStack.OrmLite 10.4.0 with SQL Server
Server time zone China Standard Time (UTC+8)
Plugin DatabaseJobFeature + CommandsFeature (RDBMS Background Jobs)

What I observed

At 10:28 local time a customer order was saved with an entity property set by DateTime.Now:

var order = new CoffeeOrder { InputDate = DateTime.Now, /* ... */ };
db.Insert(order);

The OrmLite debug log showed the parameter as @InputDate=2026/10/9 2:28:00, i.e. the Local value was converted to UTC before it was sent to SQL Server. The row in the database contained 02:28.

This behaviour is new in my project. Everything had been stored as local time for years, with no custom DateStyle anywhere in my code. The only thing that changed was that I added Background Jobs a few days earlier (services.AddPlugin(new DatabaseJobFeature { ... })).

Investigation

  1. Release notes. I read the v10.3 and v10.4 release notes. I found no mention of a change to DateTime, UTC, or DateStyle handling in OrmLite.

  2. DateTimeConverter itself did not change. I decompiled ServiceStack.OrmLite.Converters.DateTimeConverter from the NuGet packages 8.10.0, 10.0.6, 10.3.0 and 10.4.0. The code is identical in all four. DateStyle is a plain auto-property that defaults to DateTimeKind.Unspecified.

  3. Without Background Jobs nothing converts the value. With a throw-away console app (OrmLite 10.4.0, SQL Server dialects only) the result was:

    SqlServer2017: DateStyle=Unspecified  Now=12:56  ToDbValue=12:56  kind=Local
    SqlServer2008: DateStyle=Unspecified  Now=12:56  ToDbValue=12:56  kind=Local
    Default:       DateStyle=Unspecified  Now=12:56  ToDbValue=12:56  kind=Local
    
    

    So OrmLite on its own stores DateTime.Now unchanged.

  4. Found the source. I searched the decompiled ServiceStack, ServiceStack.Server and ServiceStack.Mvc assemblies for DateStyle. The only place that assigns it is DatabaseJobFeature.Init() (in 10.0.6, 10.3.0 and 10.4.0):

    protected void Init(IServiceProvider services)
    {
        if (DbFactory == null) { /* resolve IDbConnectionFactory from DI */ }
        if (DbProvider == null) { DbProvider = DbJobsProvider.Create(DbFactory, NamedConnection); }
    
        DateTimeConverter dateTimeConverter = Dialect.GetDateTimeConverter();
        if (dateTimeConverter.DateStyle == DateTimeKind.Unspecified)
        {
            dateTimeConverter.DateStyle = DateTimeKind.Utc;
        }
    }
    
    

    Dialect is the dialect of the IDbConnectionFactory the feature uses. In a normal app that is the application’s default factory, so the change is global for the whole application.

  5. Why the effect is “-8 hours”. With DateStyle = Utc, DateTimeConverter.ToDbValue does dateTime.ToUniversalTime() for values whose Kind is Local. DateTime.Now is Local, so it is converted. (DateTime.UtcNow and values with Kind = Unspecified are not changed.)

Side effects I ran into

  • Every business table that stores DateTime.Now is now wrong by -8 hours (for rows written while the feature was registered).

  • Round-trip inconsistency. When reading, Utc style marks every value as Kind = Utc. Code that used to read Kind = Unspecified values now receives UTC-kind values.

  • UserAuth. OrmLiteAuthRepository / OrmLiteAuthRepositoryBase always writes CreatedDate/ModifiedDate using DateTime.UtcNow. That is unchanged and by design, but in an application whose database convention is “local wall-clock time” it makes the auth tables 8 hours different from every other table. I would also like to know if there is a supported way to control that (see question 4).

What I tried (and why it is not satisfactory)

Attempt Result
Set DateStyle = Local globally DateTime.Now is stored correctly, but Kind = Unspecified values (e.g. new DateTime(2026, 10, 9) or a date coming back from a JSON round-trip in session state) are treated as UTC and shifted by +8 hours, so a date-only value 2026-10-09 00:00 became 08:00.
Reset DateStyle = Unspecified in DatabaseJobFeature.ConfigureDialect Business tables are correct again. But the job tables are written with DateTime.UtcNow and read back with Kind = Unspecified, so they serialize without a Z/offset. The admin UI interprets them as local time and shows them 8 hours early (e.g. 05:24 AM instead of 13:24).
Give DatabaseJobFeature its own OrmLiteConnectionFactory with a new dialect instance (new SqlServer2017OrmLiteDialectProvider()) This is the best workaround so far. DateStyle = Utc only affects the job tables, and the business tables keep the shared dialect with Unspecified.
Admin UI still shows UTC without a time zone My app sets JsConfig.DateHandler = ISO8601DateTime globally, which writes yyyy-MM-dd HH:mm:ss without a zone. I had to add jsconfig=eccn,dh:timestampoffset to the /api requests that come from /admin-ui.
AM/PM in the admin UI The admin UI hard-codes timeFmt12. I had to copy servicestack-client.mjs to wwwroot/js/ and patch it to get 24-hour time.

The workarounds treat the symptom in the display layer. The data already written to the database while DateStyle = Utc was in effect is still 8 hours off, and I would have to repair it manually, table by table.

Questions

  1. Why is the global DateStyle forced to Utc in DatabaseJobFeature.Init()? I understand that the job tables store DateTime.UtcNow and want those values to be read back as Kind = Utc. But is it intended to change a shared converter that other tables in the same database use?

  2. Is there a supported way to disable or restrict it? For example:

    • a property on DatabaseJobFeature (such as UseUtcDateStyle = false), or

    • only set DateStyle when NamedConnection points to a dedicated connection/dialect, or

    • apply the UTC handling to the job table models only (e.g. through [Alias]/custom field converters, or by always reading with DateTime.SpecifyKind(x, DateTimeKind.Utc) in DbJobs) instead of changing the converter.

  3. Could the feature document this behaviour? It is not mentioned in the Background Jobs docs or release notes, and it is very hard to track down because DateStyle is not set anywhere in the application code.

  4. For UserAuth / OrmLiteAuthRepository: is there a recommended way to make CreatedDate/ModifiedDate use local time (or to let the application choose the clock), other than an OrmLiteConfig.InsertFilter/UpdateFilter that converts the values?

Thanks! Happy to provide a minimal repro project if useful.

Minimal reproduction

// Program.cs (net10.0, ServiceStack 10.4.0)
var builder = WebApplication.CreateBuilder(args);

var dbFactory = new OrmLiteConnectionFactory(connectionString, SqlServer2017Dialect.Provider);
builder.Services.AddSingleton<IDbConnectionFactory>(dbFactory);

// (1) Before adding Background Jobs:
Console.WriteLine(SqlServer2017Dialect.Provider.GetDateTimeConverter().DateStyle); // Unspecified

builder.Services.AddPlugin(new CommandsFeature());
builder.Services.AddPlugin(new DatabaseJobFeature());

var app = builder.Build();
app.UseServiceStack(new AppHost());   // plugins are loaded here

// (2) After the plugin has been loaded:
Console.WriteLine(SqlServer2017Dialect.Provider.GetDateTimeConverter().DateStyle); // Utc

using var db = dbFactory.Open();
db.CreateTableIfNotExists<Foo>();
db.Insert(new Foo { CreatedAt = DateTime.Now });  // stored as DateTime.Now - 8h on a UTC+8 machine

public class Foo
{
    [AutoIncrement] public int Id { get; set; }
    public DateTime CreatedAt { get; set; }
}

Expected: Foo.CreatedAt is stored exactly as passed (DateTime.Now), as it was before adding DatabaseJobFeature.
Actual: it is converted with ToUniversalTime().

Thanks for the detailed investigation - you’ve identified the cause. DatabaseJobFeature was changing the DateStyle on its selected dialect provider, which also affects other connections using that same instance.

We’ve added a UseDateStyle property in the latest pre-release packages. You can now prevent Background Jobs from changing the converter with:

services.AddPlugin(new DatabaseJobFeature {
    UseDateStyle = null
});

This preserves the dialect’s existing DateStyle.

However, UTC remains the preferred setting for Jobs, since its timestamps and scheduling logic use UTC internally. DateStyle = Utc also restores DateTimeKind.Utc when reading timestamps from SQL Server, whose datetime/datetime2 columns do not retain that information.

For your App, we recommend giving Jobs its own dialect provider through a named connection:

services
    .AddOrmLite(options =>
        options.UseSqlServer<SqlServer2017OrmLiteDialectProvider>(
            connectionString,
            dialect => dialect.UseJson = false))
    .AddSqlServer<SqlServer2017OrmLiteDialectProvider>(
        "jobs", connectionString);

services.AddPlugin(new CommandsFeature());
services.AddPlugin(new DatabaseJobFeature {
    NamedConnection = "jobs"
});

UseSqlServer creates a new default dialect provider rather than reusing SqlServer2017Dialect.Provider, and AddSqlServer creates another instance for Jobs. Both connections can use the same database: your business connection retains Unspecified, while Jobs uses UTC independently. The UseJson = false callback preserves the legacy JSV serialization default for your existing SqlServer tables.

Your Admin UI serialization observation is also correct: ISO8601DateTime omits timezone information. Jobs responses should use a timezone-preserving format such as ISO8601 or your existing timestamp-offset override.

If changing the format for all API responses is acceptable , replace their existing configuration at startup with:

using ServiceStack.Text;

JsConfig.DateHandler = DateHandler.ISO8601;

You can also set it on a per API basis, e.g:

/api/AdminGetJob?Id=123&jsconfig=eccn,dh:iso8601
1 Like