microsoft / ClearScript

A library for adding scripting to .NET applications. Supports V8 (Windows, Linux, macOS) and JScript/VBScript (Windows).
https://microsoft.github.io/ClearScript/
MIT License
1.77k stars 148 forks source link

How to change the local timezone used by the engine's date functions #579

Closed lucasoares closed 5 months ago

lucasoares commented 5 months ago

Can't find documentation on how to change the default timezone of the engine for date manipulations (ex.: new Date().toString().

I need to allow users to configure the timezone used by their date functions.

Thanks for the awesome library!

ClearScriptLib commented 5 months ago

Hi @lucasoares,

Thanks for your kind words 😊

Can't find documentation on how to change the default timezone of the engine for date manipulations (ex.: new Date().toString().

It would seem that Date.prototype.toString always uses the local (system) timezone. However, JavaScript makes it very easy to override built-in methods. For example:

engine.Execute(@"
    Date.prototype.toString = function () {
        return this.toLocaleString(Date.overrideLocales, {
            year: 'numeric', month: 'long', day: 'numeric', weekday: 'long',
            hour: 'numeric', minute: '2-digit', second: '2-digit',
            timeZone: Date.overrideTimeZone, timeZoneName: 'long'
        });
    };
");

With this setup in place, you can do things like this:

engine.AddHostType(typeof(Console));
engine.Execute(@"
    const now = new Date();
    Console.WriteLine(now.toString());
    Date.overrideTimeZone = 'America/Sao_Paulo';
    Console.WriteLine(now.toString());
    Date.overrideLocales = 'pt-BR'
    Console.WriteLine(now.toString());
");

On our test system, that produces the following output:

Saturday, June 1, 2024 at 4:01:30 PM Eastern Daylight Time
Saturday, June 1, 2024 at 5:01:30 PM Brasilia Standard Time
sábado, 1 de junho de 2024 às 17:01:30 Horário Padrao de Brasília

Of course, that's just a simple example. In a real application, it might be a good idea to override toDateString and toTimeString in a similar way, tailor the output to look more like the original methods, etc.

Good luck!

lucasoares commented 5 months ago

It would seem that Date.prototype.toString always uses the local (system) timezone. However, JavaScript makes it very easy to override built-in methods. For example:

The problem is that return new Date('2021-01-01T00:00:10').toString(); should consider the 2021-01-01T00:00:10 in a timezone that I can control.. But as far my knowledge goes for JS, I can't change the constructor implementation, right?

ClearScriptLib commented 5 months ago

But as far my knowledge goes for JS, I can't change the constructor implementation, right?

You can. Extending the previous example:

engine.Execute(@"(function () {
    Date.prototype.toString = function () {
        return this.toLocaleString(Date.overrideLocales, {
            year: 'numeric', month: 'long', day: 'numeric', weekday: 'long',
            hour: 'numeric', minute: '2-digit', second: '2-digit',
            timeZone: Date.overrideTimeZone, timeZoneName: 'long'
        });
    };
    const getTimeZoneOffset = () => {
        const format = new Intl.DateTimeFormat(Date.overrideLocales, {
            timeZone: Date.overrideTimeZone, timeZoneName: 'longOffset'
        });
        const parts = format.formatToParts(new Date());
        return parts.find(part => part.type === 'timeZoneName').value.substring(3);
    }
    Date = new Proxy(Date, {
        construct: (target, args, newTarget) => {
            if ((args.length === 1) && (typeof args[0] === 'string')) {
                if (!/Z|[+-]\d\d:\d\d$/.test(args[0])) {
                    args[0] += getTimeZoneOffset();
                }
            }
            return Reflect.construct(target, args, newTarget);
        }
    });
})()");

And now you can do this:

engine.AddHostType(typeof(Console));
engine.Execute(@"
    const inputString = '2024-06-03T12:25:49';
    Console.WriteLine(new Date(inputString).toString());
    Date.overrideLocales = 'pt-BR';
    Date.overrideTimeZone = 'America/Sao_Paulo';
    Console.WriteLine(new Date(inputString).toString());
");

Which produces the following output on our test machine:

Monday, June 3, 2024 at 12:25:49 PM Eastern Daylight Time
segunda-feira, 3 de junho de 2024 às 12:25:49 Horário Padrao de Brasília

Note that these modifications change the behavior of the standard Date class. Scripts that rely on them may not work as expected in standard JavaScript environments.

Good luck!

lucasoares commented 5 months ago

hahaha that's something HAHAHAHA

Nice workaround, but I think this may be too much, I will probably mess too much and make things worse... I also have the reflections disabled in my engine...

For now I will modify only the prototype for toString, toDateString and toTimeString and them let the users use a time.parseDate helper I'm providing using c# implementation... And then if they want their scripts to work in standard JavaScript environment, they can easly define time.parseDate as they want.

It would be nice if V8 had the option to set the local timezone instead, but I didn't find anything related to it, not even an issue or wip implementations...

Thank you very much... I'm closing this for now.