How to Restrict Using DateTime.Now In .NET Projects
One of the issues we always deal with in projects is time. As you know, in .NET, the DateTime
struct is the common type for handling date and time. For instance, we use this struct to keep track of the creation or modification date of a product, the date an order is placed, or the date a payment is made.
One common source of silent bugs in programs is the simultaneous use of DateTime.Now
and DateTime.UtcNow
. Particularly in team-based development, it's often observed that both are used concurrently, causing conflicts and confusion among the developers.
What is the difference between DateTime.Now and DateTime.UtcNow
DateTime.UtcNow tells you the date and time as it would be in Coordinated Universal Time, which is also called the Greenwich Mean Time time zone. DateTime.Now gives the date and time as it would appear to someone in your current locale.
You could set a convention at the start of the project in your team to always use DateTime.UtcNow
, but mistakes can still happen. To enforce this, we want to apply settings on the .NET compiler (Roslyn
), to ban the use of DateTime.Now
. By making this change, any use of DateTime.Now
in the code will result in a compiler error, thus achieving our goal.
We can achieve this using a Roslyn Analyzer
. There are two ways to do this: either write a custom analyzer or use the Microsoft.CodeAnalysis.BannedApiAnalyzers
. The second method is simpler, so we'll use it for now.
First, we need to add the package reference for this analyzer to our project:
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4">
</PackageReference>
</ItemGroup>
After that, add a text file named BannedSymbols.txt
to your project and include it as AdditionalFiles
in your .csproj file:
<ItemGroup>
<AdditionalFiles Include="BannedSymbols.txt" />
</ItemGroup>
To add a symbol to the banned list, add an entry in the format below to one of the configuration files (Description Text will be displayed as a description in diagnostics, which is optional):
{Documentation Comment ID string for the symbol}[;Description Text]
For example, to ban the use of DateTime.Now
, we add the following line to the BannedSymbols.txt
file:
P:System.DateTime.Now;do not use DateTime.Now. use DateTime.UtcNow instead
Now we use DateTime.Now
in our project and build it:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/GetTime", () => DateTime.Now);
app.Run();
As you can see in the output, a warning with the code RS0030
is displayed.
The symbol 'DateTime.Noew' is banned in this project: do not use DateTime.Now. use DateTime.UtcNow instead
To prevent the build and raise a compiler error, we need to introduce this warning as an error in the project file.
so put the following code in the project file:
<PropertyGroup>
<WarningsAsErrors>RS0030</WarningsAsErrors>
</PropertyGroup>
Now if we build the project, we encounter the following error.
Using the BannedApiAnalyzers
, we can prohibit the use of any other API as well. In the table below, I have provided examples of various cases.
The following example source is BannedApiAnalyzers.Help. md
namespace N
{
class BannedType
{
public BannedType() {}
public int BannedMethod() {}
public void BannedMethod(int i) {}
public void BannedMethod<T>(T t) {}
public void BannedMethod<T>(Func<T> f) {}
public string BannedField;
public string BannedProperty { get; }
public event EventHandler BannedEvent;
}
class BannedType<T>
{
}
}
Symbol in Source | Sample Entry in BannedSymbols.txt |
---|---|
class BannedType |
T:N.BannedType;Don't use BannedType |
class BannedType<T> |
T:N.BannedType`1;Don't use BannedType<T> |
BannedType() |
M:N.BannedType.#ctor |
int BannedMethod() |
M:N.BannedType.BannedMethod |
void BannedMethod(int i) |
M:N.BannedType.BannedMethod(System.Int32);Don't use BannedMethod |
void BannedMethod<T>(T t) |
M:N.BannedType.BannedMethod`1(``0) |
void BannedMethod<T>(Func<T> f) |
M:N.BannedType.BannedMethod`1(System.Func{``0}) |
string BannedField |
F:N.BannedType.BannedField |
string BannedProperty { get; } |
P:N.BannedType.BannedProperty |
event EventHandler BannedEvent; |
E:N.BannedType.BannedEvent |
namespace N |
N:N |