aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bun.js/bindings/BunJSCModule.cpp33
-rw-r--r--src/bun.js/bindings/JSEnvironmentVariableMap.cpp72
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.cpp12
-rw-r--r--src/bun.js/bindings/bindings.zig5
-rw-r--r--src/bun.js/bun-jsc.exports.js2
-rw-r--r--src/bun_js.zig7
-rw-r--r--src/cli/test_command.zig13
7 files changed, 144 insertions, 0 deletions
diff --git a/src/bun.js/bindings/BunJSCModule.cpp b/src/bun.js/bindings/BunJSCModule.cpp
index 63721a878..5809a9813 100644
--- a/src/bun.js/bindings/BunJSCModule.cpp
+++ b/src/bun.js/bindings/BunJSCModule.cpp
@@ -420,6 +420,38 @@ JSC_DEFINE_HOST_FUNCTION(functionDrainMicrotasks, (JSGlobalObject * globalObject
return JSValue::encode(jsUndefined());
}
+JSC_DEFINE_HOST_FUNCTION(functionSetTimeZone, (JSGlobalObject * globalObject, CallFrame* callFrame))
+{
+ VM& vm = globalObject->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+
+ if (callFrame->argumentCount() < 1) {
+ throwTypeError(globalObject, scope, "setTimeZone requires a timezone string"_s);
+ return encodedJSValue();
+ }
+
+ if (!callFrame->argument(0).isString()) {
+ throwTypeError(globalObject, scope, "setTimeZone requires a timezone string"_s);
+ return encodedJSValue();
+ }
+
+ String timeZoneName = callFrame->argument(0).toWTFString(globalObject);
+ RETURN_IF_EXCEPTION(scope, encodedJSValue());
+
+ double time = callFrame->argument(1).toNumber(globalObject);
+ RETURN_IF_EXCEPTION(scope, encodedJSValue());
+
+ if (!WTF::setTimeZoneOverride(timeZoneName)) {
+ throwTypeError(globalObject, scope, makeString("Invalid timezone: \""_s, timeZoneName, "\""_s));
+ return encodedJSValue();
+ }
+ vm.dateCache.resetIfNecessarySlow();
+ WTF::Vector<UChar, 32> buffer;
+ WTF::getTimeZoneOverride(buffer);
+ WTF::String timeZoneString(buffer.data(), buffer.size());
+ return JSValue::encode(jsString(vm, timeZoneString));
+}
+
JSC_DEFINE_HOST_FUNCTION(functionRunProfiler, (JSGlobalObject * globalObject, CallFrame* callFrame))
{
JSC::VM& vm = globalObject->vm();
@@ -528,6 +560,7 @@ JSC::JSObject* createJSCModule(JSC::JSGlobalObject* globalObject)
object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "getProtectedObjects"_s), 1, functionGetProtectedObjects, ImplementationVisibility::Public, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0);
object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "generateHeapSnapshotForDebugging"_s), 0, functionGenerateHeapSnapshotForDebugging, ImplementationVisibility::Public, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0);
object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "profile"_s), 0, functionRunProfiler, ImplementationVisibility::Public, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0);
+ object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "setTimeZone"_s), 0, functionSetTimeZone, ImplementationVisibility::Public, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0);
}
return object;
diff --git a/src/bun.js/bindings/JSEnvironmentVariableMap.cpp b/src/bun.js/bindings/JSEnvironmentVariableMap.cpp
index b90d9f44c..5c0357066 100644
--- a/src/bun.js/bindings/JSEnvironmentVariableMap.cpp
+++ b/src/bun.js/bindings/JSEnvironmentVariableMap.cpp
@@ -50,6 +50,64 @@ JSC_DEFINE_CUSTOM_SETTER(jsSetterEnvironmentVariable, (JSGlobalObject * globalOb
return true;
}
+JSC_DEFINE_CUSTOM_GETTER(jsTimeZoneEnvironmentVariableGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName propertyName))
+{
+ VM& vm = globalObject->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+
+ auto* thisObject = jsDynamicCast<JSObject*>(JSValue::decode(thisValue));
+ if (UNLIKELY(!thisObject))
+ return JSValue::encode(jsUndefined());
+
+ auto* clientData = WebCore::clientData(vm);
+
+ ZigString name = toZigString(propertyName.publicName());
+ ZigString value = { nullptr, 0 };
+
+ if (auto hasExistingValue = thisObject->getIfPropertyExists(globalObject, clientData->builtinNames().dataPrivateName())) {
+ return JSValue::encode(hasExistingValue);
+ }
+
+ if (!Bun__getEnvValue(globalObject, &name, &value) || value.len == 0) {
+ return JSValue::encode(jsUndefined());
+ }
+
+ JSValue out = jsString(vm, Zig::toStringCopy(value));
+ thisObject->putDirect(vm, clientData->builtinNames().dataPrivateName(), out, 0);
+
+ return JSValue::encode(out);
+}
+
+// In Node.js, the "TZ" environment variable is special.
+// Setting it automatically updates the timezone.
+// We also expose an explicit setTimeZone function in bun:jsc
+JSC_DEFINE_CUSTOM_SETTER(jsTimeZoneEnvironmentVariableSetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, EncodedJSValue value, PropertyName propertyName))
+{
+ VM& vm = globalObject->vm();
+ JSC::JSObject* object = JSValue::decode(thisValue).getObject();
+ if (!object)
+ return false;
+
+ JSValue decodedValue = JSValue::decode(value);
+ if (decodedValue.isString()) {
+ auto timeZoneName = decodedValue.toWTFString(globalObject);
+ if (timeZoneName.length() < 32) {
+ if (WTF::setTimeZoneOverride(timeZoneName)) {
+ vm.dateCache.resetIfNecessarySlow();
+ }
+ }
+ }
+
+ auto* clientData = WebCore::clientData(vm);
+ auto* builtinNames = &clientData->builtinNames();
+ auto privateName = builtinNames->dataPrivateName();
+ object->putDirect(vm, privateName, JSValue::decode(value), 0);
+
+ // Recreate this because the property visibility needs to be set correctly
+ object->putDirectCustomAccessor(vm, propertyName, JSC::CustomGetterSetter::create(vm, jsTimeZoneEnvironmentVariableGetter, jsTimeZoneEnvironmentVariableSetter), JSC::PropertyAttribute::CustomAccessor | 0);
+ return true;
+}
+
JSValue createEnvironmentVariablesMap(Zig::GlobalObject* globalObject)
{
VM& vm = globalObject->vm();
@@ -65,11 +123,25 @@ JSValue createEnvironmentVariablesMap(Zig::GlobalObject* globalObject)
object = constructEmptyObject(globalObject, globalObject->objectPrototype());
}
+ static NeverDestroyed<String> TZ = MAKE_STATIC_STRING_IMPL("TZ");
+ bool hasTZ = false;
for (size_t i = 0; i < count; i++) {
auto name = Zig::toStringCopy(names[i]);
+ if (name == TZ) {
+ hasTZ = true;
+ continue;
+ }
object->putDirectCustomAccessor(vm, Identifier::fromString(vm, name), JSC::CustomGetterSetter::create(vm, jsGetterEnvironmentVariable, jsSetterEnvironmentVariable), JSC::PropertyAttribute::CustomAccessor | 0);
}
+ unsigned int TZAttrs = JSC::PropertyAttribute::CustomAccessor | 0;
+ if (!hasTZ) {
+ TZAttrs |= JSC::PropertyAttribute::DontEnum;
+ }
+ object->putDirectCustomAccessor(
+ vm,
+ Identifier::fromString(vm, TZ), JSC::CustomGetterSetter::create(vm, jsTimeZoneEnvironmentVariableGetter, jsTimeZoneEnvironmentVariableSetter), TZAttrs);
+
return object;
}
} \ No newline at end of file
diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp
index 663c2a491..b3da8a98f 100644
--- a/src/bun.js/bindings/ZigGlobalObject.cpp
+++ b/src/bun.js/bindings/ZigGlobalObject.cpp
@@ -3768,6 +3768,18 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
visitor.addOpaqueRoot(context);
}
+extern "C" bool JSGlobalObject__setTimeZone(JSC::JSGlobalObject* globalObject, const ZigString* timeZone)
+{
+ auto& vm = globalObject->vm();
+
+ if (WTF::setTimeZoneOverride(Zig::toString(*timeZone))) {
+ vm.dateCache.resetIfNecessarySlow();
+ return true;
+ }
+
+ return false;
+}
+
extern "C" void JSGlobalObject__throwTerminationException(JSC::JSGlobalObject* globalObject)
{
globalObject->vm().setHasTerminationRequest();
diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig
index 373bca8ec..a03737119 100644
--- a/src/bun.js/bindings/bindings.zig
+++ b/src/bun.js/bindings/bindings.zig
@@ -2354,6 +2354,11 @@ pub const JSGlobalObject = extern struct {
extern fn JSGlobalObject__throwTerminationException(this: *JSGlobalObject) void;
pub const throwTerminationException = JSGlobalObject__throwTerminationException;
pub const clearTerminationException = JSGlobalObject__clearTerminationException;
+ extern fn JSGlobalObject__setTimeZone(this: *JSGlobalObject, timeZone: *const ZigString) bool;
+
+ pub fn setTimeZone(this: *JSGlobalObject, timeZone: *const ZigString) bool {
+ return JSGlobalObject__setTimeZone(this, timeZone);
+ }
pub fn throwInvalidArguments(
this: *JSGlobalObject,
diff --git a/src/bun.js/bun-jsc.exports.js b/src/bun.js/bun-jsc.exports.js
index 765f9aeb5..d49e41851 100644
--- a/src/bun.js/bun-jsc.exports.js
+++ b/src/bun.js/bun-jsc.exports.js
@@ -31,3 +31,5 @@ export const getProtectedObjects = jsc.getProtectedObjects;
export const generateHeapSnapshotForDebugging = jsc.generateHeapSnapshotForDebugging;
export const profile = jsc.profile;
export default jsc;
+export const setTimeZone = jsc.setTimeZone;
+export const setTimezone = setTimeZone;
diff --git a/src/bun_js.zig b/src/bun_js.zig
index 5a4eb4f8a..00cb51d20 100644
--- a/src/bun_js.zig
+++ b/src/bun_js.zig
@@ -204,6 +204,13 @@ pub const Run = struct {
vm.is_main_thread = true;
JSC.VirtualMachine.is_main_thread_vm = true;
+ // Allow setting a custom timezone
+ if (vm.bundler.env.get("TZ")) |tz| {
+ if (tz.len > 0) {
+ _ = vm.global.setTimeZone(&JSC.ZigString.init(tz));
+ }
+ }
+
var callback = OpaqueWrap(Run, Run.start);
vm.global.vm().holdAPILock(&run, callback);
}
diff --git a/src/cli/test_command.zig b/src/cli/test_command.zig
index 996289ac4..8330a786b 100644
--- a/src/cli/test_command.zig
+++ b/src/cli/test_command.zig
@@ -382,6 +382,7 @@ const Scanner = struct {
pub const TestCommand = struct {
pub const name = "test";
pub const old_name = "wiptest";
+
pub fn exec(ctx: Command.Context) !void {
if (comptime is_bindgen) unreachable;
// print the version so you know its doing stuff if it takes a sec
@@ -460,6 +461,18 @@ pub const TestCommand = struct {
vm.is_main_thread = true;
JSC.VirtualMachine.is_main_thread_vm = true;
+ // For tests, we default to UTC time zone
+ // unless the user inputs TZ="", in which case we use local time zone
+ var TZ_NAME: string =
+ // We use the string "Etc/UTC" instead of "UTC" so there is no normalization difference.
+ "Etc/UTC";
+ if (vm.bundler.env.get("TZ")) |tz| {
+ TZ_NAME = tz;
+ }
+ if (TZ_NAME.len > 0) {
+ _ = vm.global.setTimeZone(&JSC.ZigString.init(TZ_NAME));
+ }
+
var scanner = Scanner{
.dirs_to_scan = Scanner.Fifo.init(ctx.allocator),
.options = &vm.bundler.options,