Date marshalling

Feb 6, 2014 at 5:12 AM
Hi again,

Based on my tests, it seems that the Date type, which is one of the few core JS types, is not marshalled across the JS/.NET boundary.
It would be very convenient if ClearScript could marshal it from & to the DateTime .NET type. Is this something you would consider?

Thanks
Thomas
Coordinator
Feb 6, 2014 at 4:03 PM
Edited Feb 8, 2014 at 6:57 PM
Hi Thomas,

In general, ClearScript favors seamless access over data conversion. We believe that this approach maximizes flexibility. There are exceptions, of course. If ClearScript didn't convert strings and numbers, for example, using it would be very painful :)

Instead of converting JavaScript dates to DateTime structures, ClearScript makes it easy for hosts to use JavaScript dates directly and convert them to DateTime structures if necessary:
dynamic date = engine.Evaluate("new Date()");
Console.WriteLine(date.toString());

var dateTime = new DateTime(
    date.getFullYear(),
    date.getMonth() + 1,
    date.getDate(),
    date.getHours(),
    date.getMinutes(),
    date.getSeconds(),
    date.getMilliseconds()
);
Console.WriteLine(dateTime);
ClearScript also makes it easy to deal with DateTime structures in script code:
engine.AddHostType("DateTime", typeof(DateTime));
engine.AddHostType("Console", typeof(Console));
engine.Execute(@"
    var dateTime = DateTime.Now;
    Console.WriteLine(dateTime);

    var date = new Date(0);
    date.setFullYear(dateTime.Year, dateTime.Month - 1, dateTime.Day);
    date.setHours(dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond);
    Console.WriteLine(date.toString());
");
You could of course come up with more efficient conversion routines, but this is just an example.

Good luck!
Feb 7, 2014 at 4:28 AM
Thanks for your reply.
I kind of forgot that it was indeed possible to call methods of JS objects from the .NET side.

Cheers
Thomas
Feb 8, 2014 at 1:11 AM
I'm having some trouble with date marshaling as well. In my scenario I'm using JavaScript as a hook into how the application performs certain actions. I would like to expose a clean interface that leverages the native functionality in JavaScript including Date. This is important because I'm prioritizing the JavaScript syntax over the work it would take to accomplish it on the .NET side.

Here is my ideal script:
        JScriptEngine engine = new JScriptEngine();

        DateTime yesterday = DateTime.Now.AddDays( -1 );
        DateTime tomorrow = DateTime.Now.AddDays( 1 );

        engine.AddHostObject( "yesterday", yesterday );
        engine.AddHostObject( "tomorrow", tomorrow );

        StringBuilder scriptBuilder = new StringBuilder();
        scriptBuilder.AppendLine( "var now = new Date();" );
        scriptBuilder.AppendLine( "var todayAfterYesterday = now > yesterday;" );
        scriptBuilder.AppendLine( "var todayAfterTomorrow = now > tomorrow;" );

        engine.Execute( scriptBuilder.ToString() );

        Console.WriteLine( "TodayAfterYesterday: " + engine.Script.todayAfterYesterday );
        Console.WriteLine( "TodayAfterTomorrow: " + engine.Script.todayAfterTomorrow );
This example returns false for both outputs.

Even if I use a .NET DateTime.Now to compare my DateTime variables I still get false on both outputs.
        JScriptEngine engine = new JScriptEngine();

        DateTime yesterday = DateTime.Now.AddDays( -1 );
        DateTime tomorrow = DateTime.Now.AddDays( 1 );

        engine.AddHostObject( "yesterday", yesterday );
        engine.AddHostObject( "tomorrow", tomorrow );
        engine.AddHostObject( "now", DateTime.Now );

        StringBuilder scriptBuilder = new StringBuilder();
        scriptBuilder.AppendLine( "var todayAfterYesterday = now > yesterday;" );
        scriptBuilder.AppendLine( "var todayAfterTomorrow = now > tomorrow;" );

        engine.Execute( scriptBuilder.ToString() );

        Console.WriteLine( "TodayAfterYesterday: " + engine.Script.todayAfterYesterday );
        Console.WriteLine( "TodayAfterTomorrow: " + engine.Script.todayAfterTomorrow );
This returns false to both outputs as well.

The only solution I've found is to use method to compare the dates.
        JScriptEngine engine = new JScriptEngine();

        DateTime yesterday = DateTime.Now.AddDays( -1 );
        DateTime tomorrow = DateTime.Now.AddDays( 1 );

        engine.AddHostObject( "DateTimeComparer", new DateTimeComparer() );

        engine.AddHostObject( "yesterday", yesterday );
        engine.AddHostObject( "tomorrow", tomorrow );
        engine.AddHostObject( "now", DateTime.Now );

        StringBuilder scriptBuilder = new StringBuilder();
        scriptBuilder.AppendLine( "var todayAfterYesterday = DateTimeComparer.DifferenceInDays( now, yesterday );" );
        scriptBuilder.AppendLine( "var todayAfterTomorrow = DateTimeComparer.DifferenceInDays( now, tomorrow );" );

        engine.Execute( scriptBuilder.ToString() );

        Console.WriteLine( "TodayAfterYesterday: " + engine.Script.todayAfterYesterday );
        Console.WriteLine( "TodayAfterTomorrow: " + engine.Script.todayAfterTomorrow );
This example does return 1 and -1.

Has anyone else run into this? Why doesn't the compare operations work with both objects are .NET DateTime objects? Any better solutions? Any ideas, thoughts, insights would be much appreciated.

Regards,

Jamin
Coordinator
Feb 8, 2014 at 7:03 PM
Edited Feb 9, 2014 at 11:38 AM
Greetings Jamin!

I would like to expose a clean interface that leverages the native functionality in JavaScript including Date. This is important because I'm prioritizing the JavaScript syntax over the work it would take to accomplish it on the .NET side.

That makes a lot of sense, and as we demonstrated above, ClearScript makes it easy to expose an interface that supports JavaScript dates. Your implementation can use them directly or convert them internally to DateTime structures if necessary.

Regarding your code examples, the first two can't be made to work because JavaScript comparisons can't be redefined or overloaded to operate meaningfully on host objects.

Your third example can be simplified because DateTime already has a comparison method:
// JavaScript
var todayAfterYesterday = now.CompareTo(yesterday) > 0;
var todayAfterTomorrow = now.CompareTo(tomorrow) > 0;
Cheers!
Feb 10, 2014 at 4:16 PM
JavaScript comparisons can't be redefined or overloaded to operate meaningfully on host objects.
This is good to know. I wont waste any more time trying to find a way to manipulate the JavaScript operators to perform this task.

The simplification provided I think will be my best option.

Thank you!
Mar 3 at 4:40 PM
Edited Mar 3 at 4:41 PM
I've toyed a bit with this issue because I like to keep the .NET DateTime as they are on the script side but the comparison operators are a bit of a trap when we write our business rules.

I wonder if javascript's valueOf is not a good solution.

The problem is to attach the valueOf method to the .NET DateTime objetcs, as expected I can't "monkey patch" the object, and it would not be convenient to have to do it that way, but if it was possible to globally define the javascript "prototype" for a .NET class (or struct), we could just set it as { valueOf: function() { return this.Ticks } } for DateTime and the operators would work (similar for TimeSpan).

The API could be something nice (another overload to provide the complete prototype could also be available):
engine.AddHostType<DateTime>(valueOf: (instance) => instance.Ticks);
Coordinator
Mar 3 at 5:25 PM
Hi guillaume86,

You can add valueOf as an extension method:
public static class DateTimeExtensions {
    public static double valueOf(this DateTime value) {
        return value.Ticks;
    }
}
and then:
engine.AddHostType(typeof(DateTime));
engine.AddHostType(typeof(DateTimeExtensions));
Console.WriteLine(engine.Evaluate("a = DateTime.Now; b = DateTime.Now; b >= a"));
Note that this only works on V8, as JScript doesn't follow the valueOf protocol for host objects.

Good luck!
Mar 3 at 6:28 PM
Hi,

I can't get it to work, do I need a version more recent that the latest nuget release? (actually I had already tried a class with a valueOf method and it didn't work either)
Your test case prints true but I suspects only because it's the same value and it doesn't actually use valueOf().

Do this one prints true for you?
            engine.AddHostType(typeof(DateTime));
            engine.AddHostType(typeof(DateTimeExtensions));
            Console.WriteLine(engine.Evaluate("a = new DateTime(2016, 1, 1); b = new DateTime(2016, 1, 2); b > a"));
In my tests valueOf never gets called.
Coordinator
Mar 3 at 6:57 PM
Hi again,

Yes, sorry, you need this fix. The hiding of built-in properties such as toString and valueOf is broken in the current release.

Cheers!
Mar 3 at 7:04 PM
Edited Mar 3 at 7:53 PM
Thanks a lot!
Mar 6 at 12:35 PM
Sorry to bother you again, could you "release" a new version so that the Nuget maintainers can also push an update with correct version numbers?

Have a nice day.
Coordinator
Mar 7 at 3:12 PM
No bother at all. We're hoping to post Version 5.4.5 sometime this week. Thanks again!
Mar 11 at 9:09 AM
Hi, thanks for the release, I've tested it and it's working fine!

I just have a issue with the equality comparison (which use a reference comparison when both operands are of the same type),
Would that be possible to make equality operators work with .NET structs types?
I guess there's probably a technical reason preventing that, just asking to be sure.

Thanks.
Coordinator
Mar 11 at 9:10 PM
Hello!

Unfortunately valueOf isn't used to prepare values for equality comparison, nor is it possible for the host to overload JavaScript operators. And as you probably know, comparing structs by reference doesn't even work for managed code :)

ClearScript does have a scheme in place that makes JavaScript equality comparison work for .NET enum values. It's somewhat memory intensive, but that's offset by the tiny value range of most enums. In theory the same technique could work for other value types, but currently it's enabled only for enums due to memory consumption concerns.

Thanks!