{"record":{"id":"73130102b50d1fdd","repo":"dotnet/aspnetcore","slug":"the-absolute-expiration-value-must-be-in-the-futur","errorCode":null,"errorMessage":"The absolute expiration value must be in the future.","messagePattern":"The absolute expiration value must be in the future\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Caching/SqlServer/src/DatabaseOperations.cs","lineNumber":383,"sourceCode":"        {\n            return ex.Errors.Cast<SqlError>().Any(error => error.Number == DuplicateKeyErrorId);\n        }\n        return false;\n    }\n\n    private static DateTimeOffset? GetAbsoluteExpiration(DateTimeOffset utcNow, DistributedCacheEntryOptions options)\n    {\n        // calculate absolute expiration\n        DateTimeOffset? absoluteExpiration = null;\n        if (options.AbsoluteExpirationRelativeToNow.HasValue)\n        {\n            absoluteExpiration = utcNow.Add(options.AbsoluteExpirationRelativeToNow.Value);\n        }\n        else if (options.AbsoluteExpiration.HasValue)\n        {\n            if (options.AbsoluteExpiration.Value <= utcNow)\n            {\n                throw new InvalidOperationException(\"The absolute expiration value must be in the future.\");\n            }\n\n            absoluteExpiration = options.AbsoluteExpiration.Value;\n        }\n        return absoluteExpiration;\n    }\n\n    private static void ValidateOptions(TimeSpan? slidingExpiration, DateTimeOffset? absoluteExpiration)\n    {\n        if (!slidingExpiration.HasValue && !absoluteExpiration.HasValue)\n        {\n            throw new InvalidOperationException(\"Either absolute or sliding expiration needs \" +\n                \"to be provided.\");\n        }\n    }\n}\n","sourceCodeStart":365,"sourceCodeEnd":400,"githubUrl":"https://github.com/dotnet/aspnetcore/blob/3600ca084e9c8b5f4174fc5e747f4c52d2100806/src/Caching/SqlServer/src/DatabaseOperations.cs#L365-L400","documentation":"Thrown by the SQL Server distributed cache when DistributedCacheEntryOptions.AbsoluteExpiration is set to a value that is not in the future relative to the current UTC time. GetAbsoluteExpiration rejects past or 'now' expirations because they would yield a cache entry that is already invalid at insert time.","triggerScenarios":"Calling SetAsync with options.AbsoluteExpiration <= utcNow (the cache's SystemClock now). Either the supplied DateTimeOffset is in the past, or it equals now exactly (<=).","commonSituations":"Computing AbsoluteExpiration from a stale 'now' captured earlier in the request; passing an absolute time sourced from another timezone that resolved to the past; clock skew between the server computing the value and the cache host; reusing a cached expiration value after a delay.","solutions":["Ensure AbsoluteExpiration is strictly greater than the current UTC time when SetAsync is called.","Prefer AbsoluteExpirationRelativeToNow (a TimeSpan) to avoid timezone/clock-skew mistakes.","If computing AbsoluteExpiration from a captured timestamp, recompute it at the moment of the SetAsync call.","Synchronize clocks (NTP) across servers if skew is the cause."],"exampleFix":"// before: stale timestamp, may be in the past\nvar opts = new DistributedCacheEntryOptions { AbsoluteExpiration = requestStart.AddMinutes(5) };\nawait cache.SetStringAsync(key, value, opts);\n\n// after: relative offset computed at call time\nvar opts = new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5) };\nawait cache.SetStringAsync(key, value, opts);","handlingStrategy":"validation","validationCode":"if (options.AbsoluteExpiration.HasValue && options.AbsoluteExpiration.Value <= DateTimeOffset.UtcNow) { throw new ArgumentOutOfRangeException(nameof(options.AbsoluteExpiration)); }","typeGuard":"static bool AbsoluteExpirationIsValid(DateTimeOffset? abs) => !abs.HasValue || abs.Value > DateTimeOffset.UtcNow;","tryCatchPattern":null,"preventionTips":["Prefer AbsoluteExpirationRelativeToNow over absolute DateTimeOffset values.","Compute expirations at the SetAsync call site, not earlier in the request.","Sync server clocks via NTP."],"tags":["caching","sqlserver","aspnetcore","distributed-cache","configuration"],"backgroundTag":null,"analyzedSha":"3600ca084e9c8b5f4174fc5e747f4c52d2100806","analyzedAt":"2026-08-11T16:32:30.678Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}