Best practices to follow when using DateTime in .NET C#
Introduction
In almost all the software systems we are working on we need to use DateTime to represent a timestamp for some actions.
However, what I noticed is that sometimes we do some mistakes which could be fatal in some occasions.
Therefore, I decided to write this article to share with you some of the best practices to follow when dealing with DateTime in .NET C#
Agenda
In this article we are going to cover the following topics:
DateTime vs DateTimeOffset.
Note when value initialized.
Unify the source.
Static vs Abstracted.
Now, you can buckle up and enjoy the trip.
DateTime vs DateTimeOffset
I am sure that all .NET developers know about DateTime, however, not all of them know about DateTimeOffset. There is a significant difference between both of them.
There is a great article published by Steve Smith about this topic and I encourage you to read it.
As a quick summary, let’s check the notes below.
DateTime
When using DateTime, the current system date is saved and encapsulated inside the variable but without saving or linking to the time zone.
Therefore, this would eventually cause problems if more than one machine with different time zones are used.
DateTimeOffset
On the contrary, DateTimeOffset links the current value to the time zone by saving an offset to the UTC date time.
This way the value saved could be unified as it is saved in a format which could then be transformed to different time zones without messing with the value itself.
Conclusion
I would always recommend using DateTimeOffset whenever you can and the great thing is that it is fully supported by famous modules and frameworks even MS SQL.
Note When Value Initialized
What I noticed is that sometimes developers get sloppy with when to initialize the DateTime or DateTimeOffset value especially when this value would be used in more than one place with a business need to keep using the same value in all places. Let me emphasize more.
Let’s study this small piece of code.
What we are doing here is that we want to call the two methods DoSomething1 and DoSomething2 passing in the same date time stamp which is corresponding to Now.
So, in the code sample, we are doing it in two ways to show the differences. We are doing it on BadTiming and GoodTiming methods.
Bad Timing
Here, when calling the DoSomething1 method we are passing in DateTimeOffset.Now and we are doing the same when calling the DoSomething2 method.
What we should expect now is that the DateTimeOffset values that are being passed to the methods are different as they are captured at different time stamps.
Good Timing
Here, when calling both DoSomething1 and DoSomething2 methods we are passing in the same pre-captured DateTimeOffset value which is already saved in the now variable.
Therefore, it is now obvious that the DateTimeOffset values that are being passed to the methods are identical as they are captured at the same exact time stamp.
Running the Code
When we run the whole code, we will end up with this output.
Testing Bad TimingFirst Stamp: 2022–01–31T11:01:25.3441950+00:00Second Stamp: 2022–01–31T11:01:25.3488150+00:00
Testing Good TimingFirst Stamp: 2022–01–31T11:01:25.3491070+00:00Second Stamp: 2022–01–31T11:01:25.3491070+00:00
You notice the difference? when running the BadTiming method, the time stamp is different between the two methods. On the other hand, when running the GoodTiming method, the time stamp is similar between the two methods.
Unify the Source
When working on a software system, whenever you are capturing the DateTime or the DateTimeOffset value, try to unify the machine where these values are captured.
I noticed that some developers mix between the ways they are capturing these values, sometimes from a browser (client side), sometimes from a server, sometimes from a certain machine,… and so on.
The problem with this approach is that if there is a slight difference between the clocks on these different sources, there could be inconsistency in the data. Additionally, imagine if the difference is big, not just a slight one.
Not applying this best practice could cause you fatal problems with your application and the data saved. You could end up with a disaster if the corrupted data is sensitive like money transfer transactions in a banking system.
Static vs Abstracted
One of the bad practices I always come through is encapsulating the usage of DateTime or DateTimeOffset inside the logic without providing any abstractions. Let me explain.
Let’s assume that we are having a dummy console application where we define a Logger class. This class should define only one Log method which accepts a string message and it returns the same message but prefixed with a date time stamp.
The Static Way
One way to go with this is like this:
So, now running the application, you should get this result:
Simple, right?
If you are only concerned with the runtime result, then it is ok. However, if you are also concerned with your code quality and testability, this would not be that good. Let me emphasize.
Let’s say that we want to cover our Logger class with unit tests. This is what we can do:
As you can see, the best you can do here is to ignore the date time stamp part of the message and only assert that the real message is a part of the actual resulting message.
This means that if someone applies a change on the Logger class, as long as the real message is still a part of the final message, the same test would still pass.
Furthermore, the test is actually not paying any attention to the usage of the DateTimeOffset class. Therefore, a part of the business logic is totally ignored and not covered by tests.
The Abstracted Way
However, the other way to go with this is like this:
What we are doing here is that we abstracted DateTimeOffset.Now into the interface IDateTimeProvider and defined the default implementation SystemDateTimeProvider as a thin wrapper of DateTimeOffset.Now.
Now, our Logger class defines the IDateTimeProvider as a dependency which we can Mock or Stub while testing.
Therefore, as we mentioned testing, this is what we can end up with:
See, now we can fully cover the business logic with unit tests without ignoring any business rules.
Final Thoughts
In this article I shared with you some best practices to follow while using DateTime and DateTimeOffset in .NET C#.
Is it the end of it? No, I encourage you to do your own research and evaluation as this would help you understand more.
Finally, I hope you enjoyed reading this article as I enjoyed writing it.
Kommentarer