Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions builtin_date.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,11 +198,25 @@ func (r *Runtime) dateproto_toTimeString(call FunctionCall) Value {
panic(r.NewTypeError("Method Date.prototype.toTimeString is called on incompatible receiver"))
}

func (d *dateObject) withTimeZoneOpts(r *Runtime, call FunctionCall) time.Time {
t := d.time()
if arg1 := call.Argument(1); arg1 != _undefined {
opts := r.toObject(arg1)
if tz := opts.Get("timeZone"); tz != nil && tz != _undefined {
if loc, err := time.LoadLocation(tz.String()); err == nil {
return t.In(loc)
}
}
}
return t
}

func (r *Runtime) dateproto_toLocaleString(call FunctionCall) Value {
obj := r.toObject(call.This)
if d, ok := obj.self.(*dateObject); ok {
if d.isSet() {
return asciiString(d.time().Format(datetimeLayout_en_GB))
t := d.withTimeZoneOpts(r, call)
return asciiString(t.Format(datetimeLayout_en_GB))
} else {
return stringInvalidDate
}
Expand All @@ -214,7 +228,8 @@ func (r *Runtime) dateproto_toLocaleDateString(call FunctionCall) Value {
obj := r.toObject(call.This)
if d, ok := obj.self.(*dateObject); ok {
if d.isSet() {
return asciiString(d.time().Format(dateLayout_en_GB))
t := d.withTimeZoneOpts(r, call)
return asciiString(t.Format(dateLayout_en_GB))
} else {
return stringInvalidDate
}
Expand All @@ -226,7 +241,8 @@ func (r *Runtime) dateproto_toLocaleTimeString(call FunctionCall) Value {
obj := r.toObject(call.This)
if d, ok := obj.self.(*dateObject); ok {
if d.isSet() {
return asciiString(d.time().Format(timeLayout_en_GB))
t := d.withTimeZoneOpts(r, call)
return asciiString(t.Format(timeLayout_en_GB))
} else {
return stringInvalidDate
}
Expand Down Expand Up @@ -989,9 +1005,9 @@ func createDateProtoTemplate() *objectTemplate {
t.putStr("toString", func(r *Runtime) Value { return r.methodProp(r.dateproto_toString, "toString", 0) })
t.putStr("toDateString", func(r *Runtime) Value { return r.methodProp(r.dateproto_toDateString, "toDateString", 0) })
t.putStr("toTimeString", func(r *Runtime) Value { return r.methodProp(r.dateproto_toTimeString, "toTimeString", 0) })
t.putStr("toLocaleString", func(r *Runtime) Value { return r.methodProp(r.dateproto_toLocaleString, "toLocaleString", 0) })
t.putStr("toLocaleDateString", func(r *Runtime) Value { return r.methodProp(r.dateproto_toLocaleDateString, "toLocaleDateString", 0) })
t.putStr("toLocaleTimeString", func(r *Runtime) Value { return r.methodProp(r.dateproto_toLocaleTimeString, "toLocaleTimeString", 0) })
t.putStr("toLocaleString", func(r *Runtime) Value { return r.methodProp(r.dateproto_toLocaleString, "toLocaleString", 2) })
t.putStr("toLocaleDateString", func(r *Runtime) Value { return r.methodProp(r.dateproto_toLocaleDateString, "toLocaleDateString", 2) })
t.putStr("toLocaleTimeString", func(r *Runtime) Value { return r.methodProp(r.dateproto_toLocaleTimeString, "toLocaleTimeString", 2) })
t.putStr("valueOf", func(r *Runtime) Value { return r.methodProp(r.dateproto_valueOf, "valueOf", 0) })
t.putStr("getTime", func(r *Runtime) Value { return r.methodProp(r.dateproto_getTime, "getTime", 0) })
t.putStr("getFullYear", func(r *Runtime) Value { return r.methodProp(r.dateproto_getFullYear, "getFullYear", 0) })
Expand Down
57 changes: 57 additions & 0 deletions date_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,63 @@ func TestTimezoneOffset(t *testing.T) {
testScript(SCRIPT, intToValue(-60), t)
}

func TestDateLocaleTime(t *testing.T) {
const SCRIPT = `
var d = new Date(Date.UTC(2012, 11, 20, 3, 0, 0));
d.toLocaleString("en-GB");
`

l := time.Local
defer func() {
time.Local = l
}()
var err error
time.Local, err = time.LoadLocation("Europe/London")
if err != nil {
t.Fatal(err)
}

testScript(SCRIPT, asciiString("12/20/2012, 03:00:00"), t)
}

func TestDateLocaleTimeZones(t *testing.T) {
const SCRIPT = `
var d = new Date(Date.UTC(2012, 11, 20, 3, 0, 0));
d.toLocaleString("en-GB", { timeZone: "Europe/Berlin" });
`

l := time.Local
defer func() {
time.Local = l
}()
var err error
time.Local, err = time.LoadLocation("Europe/London")
if err != nil {
t.Fatal(err)
}

testScript(SCRIPT, asciiString("12/20/2012, 04:00:00"), t)
}

func TestDateUnsupportedOptions(t *testing.T) {
const SCRIPT = `
var d = new Date(Date.UTC(2012, 11, 20, 3, 0, 0));
d.toLocaleString("en-GB", { foo: "bar" });
`

l := time.Local
defer func() {
time.Local = l
}()
var err error
time.Local, err = time.LoadLocation("Europe/London")
if err != nil {
t.Fatal(err)
}

testScript(SCRIPT, asciiString("12/20/2012, 03:00:00"), t)
}

func TestDateValueOf(t *testing.T) {
const SCRIPT = `
var d9 = new Date(1.23e15);
Expand Down
16 changes: 16 additions & 0 deletions object.go
Original file line number Diff line number Diff line change
Expand Up @@ -967,6 +967,9 @@ func (o *baseObject) export(ctx *objectExportCtx) interface{} {
itemNameStr := itemName.String()
v := o.val.self.getStr(itemName.string(), nil)
if v != nil {
if IsUndefined(v) && o.val.runtime.exportOptions.dropUndefinedKeys {
continue
}
m[itemNameStr] = exportValue(v, ctx)
} else {
m[itemNameStr] = nil
Expand Down Expand Up @@ -1822,3 +1825,16 @@ func (i *privateId) String() string {
func (i *privateId) string() unistring.String {
return privateIdString(i.name)
}

type exportOptions struct {
dropUndefinedKeys bool
}

type ExportOptions func(*exportOptions)

// WithDropUndefinedKeys configures object exports to drop undefined keys instead of converting to nil.
func WithDropUndefinedKeys() ExportOptions {
return func(opts *exportOptions) {
opts.dropUndefinedKeys = true
}
}
20 changes: 20 additions & 0 deletions object_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,26 @@ func TestExportToSliceNonIterable(t *testing.T) {
}
}

func TestExportDropUndefinedKeys(t *testing.T) {
vm := New()
vm.SetExportOptions(WithDropUndefinedKeys())

o := vm.NewObject()
o.Set("foo", vm.ToValue("bar"))
o.Set("baz", _undefined)
var a map[string]any
err := vm.ExportTo(o, &a)
if err != nil {
t.Fatal(err)
}
if len(a) != 1 {
t.Fatalf("a: %v", a)
}
if a["foo"] != "bar" {
t.Fatalf("Unexpected a[foo]: %v", a["foo"])
}
}

func ExampleRuntime_ExportTo_iterableToSlice() {
vm := New()
v, err := vm.RunString(`
Expand Down
9 changes: 9 additions & 0 deletions runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ type Runtime struct {

fieldNameMapper FieldNameMapper

exportOptions exportOptions

vm *vm
hash *maphash.Hash
idSeq uint64
Expand Down Expand Up @@ -2441,6 +2443,13 @@ func (r *Runtime) SetParserOptions(opts ...parser.Option) {
r.parserOptions = opts
}

// SetExportOptions sets the export options for this Runtime.
func (r *Runtime) SetExportOptions(opts ...ExportOptions) {
for _, o := range opts {
o(&r.exportOptions)
}
}

// SetMaxCallStackSize sets the maximum function call depth. When exceeded, a *StackOverflowError is thrown and
// returned by RunProgram or by a Callable call. This is useful to prevent memory exhaustion caused by an
// infinite recursion. The default value is math.MaxInt32.
Expand Down