Selaa lähdekoodia

Add GetUtcNow to ITimeSystem (#1701)

* Add GetUtcNow to ITimeSystem

* Add a test showing how to use virtualized TimeSystem

* Update GetUtcNow to return DateTimeOffset
Eric J. Smith 1 vuosi sitten
vanhempi
commit
93c5ee5641

+ 1 - 0
Directory.Packages.props

@@ -10,6 +10,7 @@
     <PackageVersion Include="Jurassic" Version="3.2.7" />
     <PackageVersion Include="Meziantou.Analyzer" Version="2.0.116" />
     <PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
+    <PackageVersion Include="Microsoft.Extensions.TimeProvider.Testing" Version="8.0.0" />
     <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
     <PackageVersion Include="MongoDB.Bson.signed" Version="2.19.0" />
     <PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />

+ 1 - 0
Jint.Tests.PublicInterface/Jint.Tests.PublicInterface.csproj

@@ -26,6 +26,7 @@
     <PackageReference Include="MongoDB.Bson.signed" />
     <PackageReference Include="Newtonsoft.Json" />
     <PackageReference Include="NodaTime" />
+    <PackageReference Include="Microsoft.Extensions.TimeProvider.Testing" />
     <PackageReference Include="xunit" />
     <PackageReference Include="xunit.runner.visualstudio" />
   </ItemGroup>

+ 39 - 0
Jint.Tests.PublicInterface/TimeSystemTests.cs

@@ -1,5 +1,6 @@
 using System.Globalization;
 using Jint.Runtime;
+using Microsoft.Extensions.Time.Testing;
 using NodaTime;
 
 namespace Jint.Tests.PublicInterface;
@@ -33,6 +34,29 @@ public class TimeSystemTests
 
         Assert.Equal(expected, engine.Evaluate($"new Date({input}) * 1").AsNumber());
     }
+    
+    [Fact]
+    public void CanUseTimeProvider()
+    {
+        var defaultEngine = new Engine();
+        var defaultNow = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+        var defaultScriptNow = defaultEngine.Evaluate("new Date() * 1").AsNumber();
+        Assert.InRange(defaultScriptNow, defaultNow, defaultNow + 100);
+        
+        var timeProvider = new FakeTimeProvider();
+        timeProvider.SetUtcNow(new DateTimeOffset(2023, 11, 6, 0, 0, 0, 0, TimeSpan.Zero));
+        
+        var timeProviderEngine = new Engine(options =>
+        {
+            options.TimeSystem = new TimeProviderTimeSystem(timeProvider);
+        });
+
+        var timeProviderNow = timeProvider.GetUtcNow().ToUnixTimeMilliseconds();
+        var timeProviderScriptNow = timeProviderEngine.Evaluate("new Date() * 1").AsNumber();
+        Assert.InRange(timeProviderNow, timeProviderScriptNow, timeProviderScriptNow + 100);
+        
+        Assert.NotInRange(timeProviderScriptNow, defaultScriptNow - 10000, defaultScriptNow + 10000);
+    }
 }
 
 file sealed class NodaTimeSystem : DefaultTimeSystem
@@ -52,3 +76,18 @@ file sealed class NodaTimeSystem : DefaultTimeSystem
         return offset.ToTimeSpan();
     }
 }
+
+file sealed class TimeProviderTimeSystem : DefaultTimeSystem
+{
+    private readonly TimeProvider _timeProvider;
+    
+    public TimeProviderTimeSystem(TimeProvider timeProvider) : base(TimeZoneInfo.Utc, CultureInfo.CurrentCulture)
+    {
+        _timeProvider = timeProvider;
+    }
+
+    public override DateTimeOffset GetUtcNow()
+    {
+        return _timeProvider.GetUtcNow();
+    }
+}

+ 3 - 3
Jint/Native/Date/DateConstructor.cs

@@ -98,9 +98,9 @@ internal sealed class DateConstructor : Constructor
         return finalDate.TimeClip().ToJsValue();
     }
 
-    private static JsValue Now(JsValue thisObject, JsValue[] arguments)
+    private JsValue Now(JsValue thisObject, JsValue[] arguments)
     {
-        return (long) (DateTime.UtcNow - Epoch).TotalMilliseconds;
+        return (long) (_timeSystem.GetUtcNow().DateTime - Epoch).TotalMilliseconds;
     }
 
     protected internal override JsValue Call(JsValue thisObject, JsValue[] arguments)
@@ -120,7 +120,7 @@ internal sealed class DateConstructor : Constructor
                 newTarget,
                 static intrinsics => intrinsics.Date.PrototypeObject,
                 static (engine, _, dateValue) => new JsDate(engine, dateValue),
-                (DateTime.UtcNow - Epoch).TotalMilliseconds);
+                (_timeSystem.GetUtcNow().DateTime - Epoch).TotalMilliseconds);
         }
 
         return ConstructUnlikely(arguments, newTarget);

+ 5 - 0
Jint/Runtime/DefaultTimeSystem.cs

@@ -51,6 +51,11 @@ public class DefaultTimeSystem : ITimeSystem
         DefaultTimeZone = timeZoneInfo;
     }
 
+    public virtual DateTimeOffset GetUtcNow()
+    {
+        return DateTimeOffset.UtcNow;
+    }
+
     public TimeZoneInfo DefaultTimeZone { get; }
 
     public virtual bool TryParse(string date, out long epochMilliseconds)

+ 6 - 0
Jint/Runtime/ITimeSystem.cs

@@ -10,6 +10,12 @@ namespace Jint.Runtime;
 /// </remarks>
 public interface ITimeSystem
 {
+    /// <summary>
+    /// Retrieves current UTC time.
+    /// </summary>
+    /// <returns>Current UTC time.</returns>
+    DateTimeOffset GetUtcNow();
+    
     /// <summary>
     /// Return the default time zone system is using. Usually <see cref="TimeZoneInfo.Local"/>, but can be altered via
     /// engine configuration, see <see cref="Options.TimeZone"/>.