Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DateTime.UtcNow.TimeOfDay always returns zero #48

Open
mamen opened this issue Apr 11, 2024 · 4 comments
Open

DateTime.UtcNow.TimeOfDay always returns zero #48

mamen opened this issue Apr 11, 2024 · 4 comments
Assignees
Labels
question/investigation Further information or investigation is required

Comments

@mamen
Copy link

mamen commented Apr 11, 2024

I am trying to move away from Fakes and stumbled upon this repository. I was able to successfully convert most of my tests which used Fakes with Pose(r). However, I was not able to convert some of the tests which use DateTime.

In one of the tests, the Shim is set up like this:

var dateTimeShim = Shim.Replace(() => DateTime.UtcNow)
                        .With(() => new DateTime(2017, 10, 6, 15, 16, 17, 745, DateTimeKind.Utc));
                                          
PoseContext.Isolate(() =>
{
    actual = _fixture.DoSomething();
}, dateTimeShim);

In the method under test, DateTime is accessed like this:

var now = DateTime.UtcNow;
return (long)now.TimeOfDay.TotalMilliseconds / 100;

If I run this test, the returned value for TimeOfDay is 00:00:00 and thus, TotalMilliseconds is also 0. However, when using Fakes, TimeOfDay returns 15:16:17.7450000 and TotalMilliseconds returns 54977745.

Am I doing something wrong or is this a (known) bug?

-- Edit --
I fixed it by introducing a second Shim for the TimeOfDay-property:

var dateTimeShim = Shim.Replace(() => DateTime.UtcNow).With(() => timestamp);
var timeOfDayShim = Shim.Replace(() => Is.A<DateTime>().TimeOfDay)
                        .With((ref DateTime @this) => timestamp.TimeOfDay);
@Miista
Copy link
Owner

Miista commented Apr 11, 2024

@mamen It's good that you found a solution. However, I would still have expected DateTime.UtcNow to return the shimmed value, and therefore, TimeOfDay to also return the expected value.

@Miista
Copy link
Owner

Miista commented Apr 11, 2024

@mamen What is your target framework?

@Miista Miista self-assigned this Apr 11, 2024
@Miista Miista added the question/investigation Further information or investigation is required label Apr 11, 2024
@mamen
Copy link
Author

mamen commented Apr 15, 2024

I am on .NET Framework 4.7.2

@Miista
Copy link
Owner

Miista commented Apr 21, 2024

@mamen I ran an experiment. Code follows below.

var dateTimeShim = Shim.Replace(() => DateTime.UtcNow)
                .With(() => new DateTime(2017, 10, 6, 15, 16, 17, 745, DateTimeKind.Utc));
            PoseContext.Isolate(
                () =>
                {
                    Console.WriteLine("In the following:");
                    Console.WriteLine(" - [A] means the actual non-shimmed value");
                    Console.WriteLine(" - [S] means the shimmed value");
                    Console.WriteLine(" - [C] means the computed value in cases where that makes sense");
                    Console.WriteLine();
                    
                    var shimmedNow = DateTime.UtcNow;
                    var actualNow = new DateTime(2017, 10, 6, 15, 16, 17, 745, DateTimeKind.Utc);
                    
                    Console.WriteLine("We start by comparing the shimmed instance of DateTime.UtcNow with an actual instance.");
                    Console.WriteLine($"Now [A]: {actualNow}");
                    Console.WriteLine($"Now [S]: {shimmedNow}");

                    Console.WriteLine();
                    Console.WriteLine("Their TimeOfDays report the same (remarkably) empty TimeOfDay.");
                    Console.WriteLine($"TimeOfDay [A]: {actualNow.TimeOfDay}");
                    Console.WriteLine($"TimeOfDay [S]: {shimmedNow.TimeOfDay}");

                    Console.WriteLine();
                    Console.WriteLine("Let's look at the underlying values.");
                    Console.WriteLine("When constructing the TimeOfDay, the dateData field is used.");
                    Console.WriteLine("Computations follow below.");
                    var shimmedDateData = (ulong) typeof(DateTime).GetField("dateData", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(shimmedNow);
                    Console.WriteLine($"dateData [S]: {shimmedDateData}");
                    var actualDateData = (ulong) typeof(DateTime).GetField("dateData", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(actualNow);
                    Console.WriteLine($"dateData [A]: {actualDateData}");
                    Console.WriteLine();

                    Console.WriteLine("In the following sections we also use [C] to mean that we have calculated the value ourselves using the data from the shimmed instance.");
                    Console.WriteLine();
                    
                    Console.WriteLine("This value is retrieved (and manipulated) by calling the InternalTicks property.");
                    Console.WriteLine("The values are as follows:");
                    var actualInternalTicks = typeof(DateTime).GetProperty("InternalTicks", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(actualNow);
                    Console.WriteLine($"InternalTicks [A]: {actualInternalTicks}");
                    var shimmedInternalTicks = typeof(DateTime).GetProperty("InternalTicks", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(shimmedNow);
                    Console.WriteLine($"InternalTicks [S]: {shimmedInternalTicks}");
                    var internalTicks = (long)shimmedDateData & 4611686018427387903L;
                    Console.WriteLine($"InternalTicks [C]: {internalTicks}");
                    Console.WriteLine();

                    Console.WriteLine($"TimeOfDay [A]: {actualNow.TimeOfDay}");
                    Console.WriteLine($"TimeOfDay [S]: {shimmedNow.TimeOfDay}");
                    var timeSpan = new TimeSpan(internalTicks % 864000000000L);
                    Console.WriteLine($"TimeOfDay [C]: {timeSpan}");
                    Console.WriteLine();
                    
                    var actualResult = (long)actualNow.TimeOfDay.TotalMilliseconds / 100;
                    Console.WriteLine($"Result [A]: {actualResult}");
                    var shimmedResult = (long)shimmedNow.TimeOfDay.TotalMilliseconds / 100;
                    Console.WriteLine($"Result [S]: {shimmedResult}");
                    var calculatedMut = (long)timeSpan.TotalMilliseconds / 100;
                    Console.WriteLine($"Result [C]: {calculatedMut}");
                    Console.WriteLine();
                    
                    Console.WriteLine("From this we can conclude that something goes wrong in the call to TimeOfDay.");
                    Console.WriteLine("As of now, I don't know what.");
                }, dateTimeShim);

The output (and the result) is the following:

In the following:
 - [A] means the actual non-shimmed value
 - [S] means the shimmed value
 - [C] means the computed value in cases where that makes sense

We start by comparing the shimmed instance of DateTime.UtcNow with an actual instance.
Now [A]: 06-10-2017 15:16:17
Now [S]: 06-10-2017 15:16:17

Their TimeOfDays report the same (remarkably) empty TimeOfDay.
TimeOfDay [A]: 00:00:00
TimeOfDay [S]: 00:00:00

Let's look at the underlying values.
When constructing the TimeOfDay, the dateData field is used.
Computations follow below.
dateData [S]: 5248115016204837904
dateData [A]: 5248115016204837904

In the following sections we also use [C] to mean that we have calculated the value ourselves using the data from the shimmed instance.

This value is retrieved (and manipulated) by calling the InternalTicks property.
The values are as follows:
InternalTicks [A]: 636428997777450000
InternalTicks [S]: 636428997777450000
InternalTicks [C]: 636428997777450000

TimeOfDay [A]: 00:00:00
TimeOfDay [S]: 00:00:00
TimeOfDay [C]: 15:16:17.7450000

Result [A]: 0
Result [S]: 0
Result [C]: 549777

From this we can conclude that something goes wrong in the call to TimeOfDay.
As of now, I don't know what.

Something goes wrong when invoking TimeOfDay but only in the isolated code. If I create an instance outside of isolation, everything works as expected.

Next steps would be to verify whether this happens simply due to the rewrite process. We can verify this by passing in a shim which will never be applied.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question/investigation Further information or investigation is required
Projects
None yet
Development

No branches or pull requests

2 participants