From 1cc1fc657b07654659f680f842a6b4435eec3089 Mon Sep 17 00:00:00 2001 From: ZenDrx Date: Mon, 20 Jul 2026 17:28:13 +0100 Subject: [PATCH 01/27] Add time namespace with clock and timestamp functions --- src/lib/time.buzz | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/lib/time.buzz diff --git a/src/lib/time.buzz b/src/lib/time.buzz new file mode 100644 index 00000000..077da7ee --- /dev/null +++ b/src/lib/time.buzz @@ -0,0 +1,16 @@ +namespace time; + +/// Real-time (wall-clock) in milliseconds since Unix epoch +ecport extern fun now() > double; + +/// Monotonic clock in milliseconds (never goes backwards for intervals) +export extern fun monotonic() > double; + +/// Cpu time used by the current process in milliseconds +export extern fun cpu() > double !> errors\UnexpectedError; + +/// Format a timestamp as ISO 8601 string +export extern fun format(timestamp: double) > str; + +/// Parse a ISO 8601 string as timestamp +export extern fun parse(iso: str) > double; From d1a60a32713e2ca82c20d94933159f8e6d58fad2 Mon Sep 17 00:00:00 2001 From: ZenDrx Date: Mon, 20 Jul 2026 17:38:36 +0100 Subject: [PATCH 02/27] Implement time functions in buzz_time.zig --- src/lib/buzz_time.zig | 81 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 src/lib/buzz_time.zig diff --git a/src/lib/buzz_time.zig b/src/lib/buzz_time.zig new file mode 100644 index 00000000..52474cd6 --- /dev/null +++ b/src/lib/buzz_time.zig @@ -0,0 +1,81 @@ +const api = @import("buzz_api.zig"); +const std = @import("std"); + +pub export fn timeNow(ctx: *api.NativeCtx) callconv(.c) c_int { + const ts = std.Io.Clock.now(ctx.getIo(), .real); + const ms = ts.toMilliseconds(); + ctx.vm.bz_push(api.Value.fromDouble(@as(api.Double, @floatFromInt(ms)))); + return 1; +} + +pub export fn timeMonotonic(ctx: *api.NativeCtx) callconv(.c) c_int { + const ts = std.Io.Clock.now(ctx.getIo(), .awake); + const ms = ts.toMilliseconds(); + ctx.vm.bz_push(api.Value.fromDouble(@as(api.Double, @floatFromInt(ms)))); + return 1; +} + +pub export fn timeCpu(ctx: *api.NativeCtx) callconv(.c) c_int { + const ts = std.Io.Clock.now(ctx.getIo(), .cpu_process) catch { + ctx.vm.pushError("errors.UnexpectedError", "CPU clock not available"); + return -1; + }; + const ms = ts.toMilliseconds(); + ctx.vm.bz_push(api.Value.fromDouble(@as(api.Double, @floatFromInt(ms)))); + return 1; +} + +pub export fn timeFormat(ctx: *api.NativeCtx) callconv(.c) c_int { + var len: usize = 0; + const ts_ptr = ctx.vm.bz_peek(0).bz_valueToString(&len); + const ts_str = ts_ptr.?[0..len]; + + const ts = std.fmt.parseFloat(api.Double, ts_str) catch { + ctx.vm.pushError("errors.InvalidArgumentError", "Invalid timestamp"); + return -1; + }; + + const seconds: i64 = @as(i64, @intFromFloat(@floor(ts / 1000.0))); + const nanos: i64 = @as(i64, @intFromFloat(@mod(ts, 1000.0) * 1_000_000)); + + const epoch = std.Io.Clock.Timestamp.fromNanoseconds(@as(i96, seconds) * 1_000_000_000 + @as(i96, nanos)); + + var buf: [64]u8 = undefined; + const format = "{s}"; // ISO 8601 format + const formatted = std.Io.Clock.Timestamp.formatNumber(epoch, format, &buf) catch { + ctx.vm.pushError("errors.UnexpectedError", "Failed to format time"); + return -1; + }; + + ctx.vm.bz_push( + api.VM.bz_stringToValue(ctx.vm, formatted.ptr, formatted.len), + ); + + return 1; +} + +pub export fn timeParse(ctx: *api.NativeCtx) callconv(.c) c_int { + var len: usize = 0; + const iso_ptr = ctx.vm.bz_peek(0).bz_valueToString(&len); + const iso_str = iso_ptr.?[0..len]; + + const parsed = std.Io.Clock.Timestamp.parse(iso_str) catch { + ctx.vm.pushError("errors.InvalidArgumentError", "Invalid ISO 8601 string"); + return -1; + }; + + const ms = parsed.toMilliseconds(); + ctx.vm.bz_push(api.Value.fromDouble(@as(api.Double, @floatFromInt(ms)))); + return 1; +} + +pub const library = api.BuzzApi( + "time", + &.{ + &.{ "now", timeNow }, + &.{ "monotonic", timeMonotonic }, + &.{ "cpu", timeCpu }, + &.{ "format", timeFormat }, + &.{ "parse", timeParse }, + }, +){}; From ea8797bc160bed56e53ba043a8ad0434ad7f37d3 Mon Sep 17 00:00:00 2001 From: ZenDrx Date: Mon, 20 Jul 2026 17:41:50 +0100 Subject: [PATCH 03/27] Add time module tests for various functionalities --- tests/behavior/time.buzz | 55 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 tests/behavior/time.buzz diff --git a/tests/behavior/time.buzz b/tests/behavior/time.buzz new file mode 100644 index 00000000..37f78e8c --- /dev/null +++ b/tests/behavior/time.buzz @@ -0,0 +1,55 @@ +import "buzz:std"; +import "buzz:time"; + +test "now returns a positive number" { + let now = time\now(); + std\assert(now > 0, "now should be greater than 0"); +} + +test "monotonic increases" { + let start = time\monotonic(); + time\sleep(10); + let end = time\monotonic(); + std\assert(end > start, "monotonic should increase after sleep"); +} + +test "cpu returns a positive number" { + let cpu = time\cpu() ! { + // CPU clock might not be available on all platforms + return; + }; + std\assert(cpu >= 0, "cpu time should be >= 0"); +} + +test "format and parse roundtrip" { + let original = time\now(); + let formatted = time\format(original) ! { + std\assert(false, "format failed: {}", .{error}); + return; + }; + let parsed = time\parse(formatted) ! { + std\assert(false, "parse failed: {}", .{error}); + return; + }; + let diff = original - parsed; + std\assert(diff < 1000, "roundtrip error should be less than 1 second, got {}", .{diff}); +} + +test "parse iso 8601" { + let iso = "2026-07-20T14:30:00Z"; + let ts = time\parse(iso) ! { + std\assert(false, "parse failed: {}", .{error}); + return; + }; + std\assert(ts > 0, "parsed timestamp should be positive"); +} + +test "format iso 8601" { + let ts = 1721497800000.0; // 2026-07-20T14:30:00Z + let formatted = time\format(ts) ! { + std\assert(false, "format failed: {}", .{error}); + return; + }; + // The exact string may vary by timezone, so just check it's not empty + std\assert(formatted.len() > 0, "formatted string should not be empty"); +} From 27fc6b982a3b4a28910241652d649994ebfbf7a4 Mon Sep 17 00:00:00 2001 From: ZenDrx Date: Mon, 20 Jul 2026 18:55:23 +0100 Subject: [PATCH 04/27] Add support for buzz_time.zig in static libraries --- src/lib/static_libraries.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/static_libraries.zig b/src/lib/static_libraries.zig index 6463a3c5..63f8032c 100644 --- a/src/lib/static_libraries.zig +++ b/src/lib/static_libraries.zig @@ -30,6 +30,7 @@ pub const all = [_]Library{ .{ .header = static_headers.os, .zig_path = "buzz_os.zig", .wasm_native = false }, .{ .header = static_headers.serialize, .zig_path = "buzz_serialize.zig", .wasm_native = true }, .{ .header = static_headers.std, .zig_path = "buzz_std.zig", .wasm_native = true }, + .{ .header = static_header.time, .zig_path = "buzz_time.zig", .wasm_native = false }, .{ .header = static_headers.testing, .zig_path = null, .wasm_native = false }, }; @@ -98,6 +99,7 @@ fn nativeMethods(comptime library: Library) std.StaticStringMap(buzz_api.NativeF if (std.mem.eql(u8, zig_path, "buzz_os.zig")) return checkedNativeMethods(library, @import("buzz_os.zig").library); if (std.mem.eql(u8, zig_path, "buzz_serialize.zig")) return checkedNativeMethods(library, @import("buzz_serialize.zig").library); if (std.mem.eql(u8, zig_path, "buzz_std.zig")) return checkedNativeMethods(library, @import("buzz_std.zig").library); + if (std.mem.eql(u8, zig_path, "buzz_time.zig")) return checkedNativeMethods(library, @import("buzz_time.zig").library); @compileError("unknown native library path: " ++ zig_path); } From a89822339daef5c086950e65b79176e8e5bbf974 Mon Sep 17 00:00:00 2001 From: zendrx Date: Mon, 20 Jul 2026 22:38:25 +0100 Subject: [PATCH 05/27] Update time.buzz --- src/lib/time.buzz | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib/time.buzz b/src/lib/time.buzz index 077da7ee..8dc604bd 100644 --- a/src/lib/time.buzz +++ b/src/lib/time.buzz @@ -1,9 +1,9 @@ namespace time; -/// Real-time (wall-clock) in milliseconds since Unix epoch +/// Real-time (wall-clock) in milliseconds since Unix epoch ecport extern fun now() > double; -/// Monotonic clock in milliseconds (never goes backwards for intervals) +/// Monotonic clock in milliseconds (never goes backwards for intervals) export extern fun monotonic() > double; /// Cpu time used by the current process in milliseconds @@ -13,4 +13,5 @@ export extern fun cpu() > double !> errors\UnexpectedError; export extern fun format(timestamp: double) > str; /// Parse a ISO 8601 string as timestamp +/// returns the timestamp as a double export extern fun parse(iso: str) > double; From 47ca54d7a0a9c63e871f81c95413ac4177d12136 Mon Sep 17 00:00:00 2001 From: ZenDrx Date: Tue, 21 Jul 2026 08:49:39 +0100 Subject: [PATCH 06/27] Ensure formatted string is not empty in tests From 6a26b36a9af4e1873f17abe30bedac995221ade1 Mon Sep 17 00:00:00 2001 From: ZenDrx Date: Tue, 21 Jul 2026 08:50:43 +0100 Subject: [PATCH 07/27] Refactor time.buzz test cases for improved clarity Updated test cases in time.buzz to improve readability and consistency in error messaging. --- tests/behavior/time.buzz | 52 +++++++--------------------------------- 1 file changed, 9 insertions(+), 43 deletions(-) diff --git a/tests/behavior/time.buzz b/tests/behavior/time.buzz index 37f78e8c..55d11307 100644 --- a/tests/behavior/time.buzz +++ b/tests/behavior/time.buzz @@ -2,54 +2,20 @@ import "buzz:std"; import "buzz:time"; test "now returns a positive number" { - let now = time\now(); - std\assert(now > 0, "now should be greater than 0"); -} - -test "monotonic increases" { - let start = time\monotonic(); - time\sleep(10); - let end = time\monotonic(); - std\assert(end > start, "monotonic should increase after sleep"); -} - -test "cpu returns a positive number" { - let cpu = time\cpu() ! { - // CPU clock might not be available on all platforms - return; - }; - std\assert(cpu >= 0, "cpu time should be >= 0"); + var now = time\now(); + std\assert(now > 0, message: "now should be greater than 0"); } test "format and parse roundtrip" { - let original = time\now(); - let formatted = time\format(original) ! { - std\assert(false, "format failed: {}", .{error}); - return; - }; - let parsed = time\parse(formatted) ! { - std\assert(false, "parse failed: {}", .{error}); + var original = time\now(); + var formatted = time\format(original) catch { + std\assert(false, message: "format failed"); return; }; - let diff = original - parsed; - std\assert(diff < 1000, "roundtrip error should be less than 1 second, got {}", .{diff}); -} - -test "parse iso 8601" { - let iso = "2026-07-20T14:30:00Z"; - let ts = time\parse(iso) ! { - std\assert(false, "parse failed: {}", .{error}); - return; - }; - std\assert(ts > 0, "parsed timestamp should be positive"); -} - -test "format iso 8601" { - let ts = 1721497800000.0; // 2026-07-20T14:30:00Z - let formatted = time\format(ts) ! { - std\assert(false, "format failed: {}", .{error}); + var parsed = time\parse(formatted) catch { + std\assert(false, message: "parse failed"); return; }; - // The exact string may vary by timezone, so just check it's not empty - std\assert(formatted.len() > 0, "formatted string should not be empty"); + var diff = original - parsed; + std\assert(diff < 1000, message: "roundtrip error should be less than 1 second"); } From c6043038e00a18173fd439150bf8681d6850f636 Mon Sep 17 00:00:00 2001 From: ZenDrx Date: Tue, 21 Jul 2026 08:56:15 +0100 Subject: [PATCH 08/27] Fix typo in export declaration for now function --- src/lib/time.buzz | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/time.buzz b/src/lib/time.buzz index 8dc604bd..70fff34a 100644 --- a/src/lib/time.buzz +++ b/src/lib/time.buzz @@ -1,7 +1,7 @@ namespace time; /// Real-time (wall-clock) in milliseconds since Unix epoch -ecport extern fun now() > double; +export extern fun now() > double; /// Monotonic clock in milliseconds (never goes backwards for intervals) export extern fun monotonic() > double; From 634c46affa3ef89117ecee0babb0aa0bd34ed645 Mon Sep 17 00:00:00 2001 From: ZenDrx Date: Tue, 21 Jul 2026 09:04:11 +0100 Subject: [PATCH 09/27] Refactor time functions to use updated API calls --- src/lib/buzz_time.zig | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib/buzz_time.zig b/src/lib/buzz_time.zig index 52474cd6..919b9fe7 100644 --- a/src/lib/buzz_time.zig +++ b/src/lib/buzz_time.zig @@ -2,16 +2,17 @@ const api = @import("buzz_api.zig"); const std = @import("std"); pub export fn timeNow(ctx: *api.NativeCtx) callconv(.c) c_int { + conspub export fn timeNow(ctx: *api.NativeCtx) callconv(.c) c_int { const ts = std.Io.Clock.now(ctx.getIo(), .real); const ms = ts.toMilliseconds(); - ctx.vm.bz_push(api.Value.fromDouble(@as(api.Double, @floatFromInt(ms)))); + ctx.vm.bz_push(.fromDouble(@floatFromInt(ms))); return 1; } pub export fn timeMonotonic(ctx: *api.NativeCtx) callconv(.c) c_int { const ts = std.Io.Clock.now(ctx.getIo(), .awake); const ms = ts.toMilliseconds(); - ctx.vm.bz_push(api.Value.fromDouble(@as(api.Double, @floatFromInt(ms)))); + ctx.vm.bz_push(.fromDouble(@floatFromInt(ms))); return 1; } @@ -21,7 +22,7 @@ pub export fn timeCpu(ctx: *api.NativeCtx) callconv(.c) c_int { return -1; }; const ms = ts.toMilliseconds(); - ctx.vm.bz_push(api.Value.fromDouble(@as(api.Double, @floatFromInt(ms)))); + ctx.vm.bz_push(.fromDouble(@floatFromInt(ms))); return 1; } @@ -35,14 +36,13 @@ pub export fn timeFormat(ctx: *api.NativeCtx) callconv(.c) c_int { return -1; }; - const seconds: i64 = @as(i64, @intFromFloat(@floor(ts / 1000.0))); - const nanos: i64 = @as(i64, @intFromFloat(@mod(ts, 1000.0) * 1_000_000)); + const seconds: i64 = @intFromFloat(@floor(ts / 1000.0))); + const nanos: i64 = @intFromFloat(@mod(ts, 1000.0) * 1_000_000)); const epoch = std.Io.Clock.Timestamp.fromNanoseconds(@as(i96, seconds) * 1_000_000_000 + @as(i96, nanos)); - var buf: [64]u8 = undefined; - const format = "{s}"; // ISO 8601 format - const formatted = std.Io.Clock.Timestamp.formatNumber(epoch, format, &buf) catch { + var buf: [64]u8 = undefined + const formatted = std.Io.Clock.Timestamp.formatNumber(epoch, "{s}", &buf) catch { ctx.vm.pushError("errors.UnexpectedError", "Failed to format time"); return -1; }; From d7dcdecd2652232e63dfa4bdc13912a3d4e8e2c3 Mon Sep 17 00:00:00 2001 From: ZenDrx Date: Tue, 21 Jul 2026 09:20:20 +0100 Subject: [PATCH 10/27] Simplify push of milliseconds to Buzz API --- src/lib/buzz_time.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/buzz_time.zig b/src/lib/buzz_time.zig index 919b9fe7..0901acb3 100644 --- a/src/lib/buzz_time.zig +++ b/src/lib/buzz_time.zig @@ -65,7 +65,7 @@ pub export fn timeParse(ctx: *api.NativeCtx) callconv(.c) c_int { }; const ms = parsed.toMilliseconds(); - ctx.vm.bz_push(api.Value.fromDouble(@as(api.Double, @floatFromInt(ms)))); + ctx.vm.bz_push(.fromDouble(@floatFromInt(ms))); return 1; } From 483b4e2d4e4595423d799df9f112c5502933a2a8 Mon Sep 17 00:00:00 2001 From: ZenDrx Date: Tue, 21 Jul 2026 19:13:28 +0100 Subject: [PATCH 11/27] Fix function declaration for timeNow --- src/lib/buzz_time.zig | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/buzz_time.zig b/src/lib/buzz_time.zig index 0901acb3..953c36cc 100644 --- a/src/lib/buzz_time.zig +++ b/src/lib/buzz_time.zig @@ -2,7 +2,6 @@ const api = @import("buzz_api.zig"); const std = @import("std"); pub export fn timeNow(ctx: *api.NativeCtx) callconv(.c) c_int { - conspub export fn timeNow(ctx: *api.NativeCtx) callconv(.c) c_int { const ts = std.Io.Clock.now(ctx.getIo(), .real); const ms = ts.toMilliseconds(); ctx.vm.bz_push(.fromDouble(@floatFromInt(ms))); From 9852dc1e8bde537aad0608d4653747d5897f66b6 Mon Sep 17 00:00:00 2001 From: ZenDrx Date: Tue, 21 Jul 2026 19:30:13 +0100 Subject: [PATCH 12/27] Change var to final in time.buzz tests --- tests/behavior/time.buzz | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/behavior/time.buzz b/tests/behavior/time.buzz index 55d11307..2e004cf9 100644 --- a/tests/behavior/time.buzz +++ b/tests/behavior/time.buzz @@ -2,20 +2,20 @@ import "buzz:std"; import "buzz:time"; test "now returns a positive number" { - var now = time\now(); + final now = time\now(); std\assert(now > 0, message: "now should be greater than 0"); } test "format and parse roundtrip" { - var original = time\now(); - var formatted = time\format(original) catch { - std\assert(false, message: "format failed"); - return; - }; - var parsed = time\parse(formatted) catch { + final original = time\now(); + final formatted = time\format(original) catch from { + std\assert(false, message: "format failed"); + return; + }; + final parsed = time\parse(formatted) catch { std\assert(false, message: "parse failed"); return; }; - var diff = original - parsed; + final diff = original - parsed; std\assert(diff < 1000, message: "roundtrip error should be less than 1 second"); } From e8bddcba4355228fbfe847e4ddb3f2cfeda147ba Mon Sep 17 00:00:00 2001 From: ZenDrx Date: Fri, 24 Jul 2026 08:47:48 +0100 Subject: [PATCH 13/27] Refactor assertions for better readability --- tests/behavior/time.buzz | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/tests/behavior/time.buzz b/tests/behavior/time.buzz index 2e004cf9..3f3c32ed 100644 --- a/tests/behavior/time.buzz +++ b/tests/behavior/time.buzz @@ -3,19 +3,29 @@ import "buzz:time"; test "now returns a positive number" { final now = time\now(); - std\assert(now > 0, message: "now should be greater than 0"); + std\assert( + now > 0, + message: "now should be greater than 0" + ); } test "format and parse roundtrip" { final original = time\now(); final formatted = time\format(original) catch from { - std\assert(false, message: "format failed"); - return; + std\assert( + false, + message: "format failed" + ); }; - final parsed = time\parse(formatted) catch { - std\assert(false, message: "parse failed"); - return; + final parsed = time\parse(formatted) catch from { + std\assert( + false, + message: "parse failed" + ); }; final diff = original - parsed; - std\assert(diff < 1000, message: "roundtrip error should be less than 1 second"); + std\assert( + diff < 1000, + message: "roundtrip error should be less than 1 second" + ); } From b2604fe46359504830d057672cad771c6151e834 Mon Sep 17 00:00:00 2001 From: ZenDrx Date: Fri, 24 Jul 2026 08:56:06 +0100 Subject: [PATCH 14/27] Change CI workflow branch to 'time-feature' --- .github/workflows/ci.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5fa644b7..a3f3c88a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,8 +1,9 @@ on: push: - branches: [main] + branches: [time-feature] pull_request: branches: [main] + workflow_dispatch: jobs: tests: From dcdf11bc740884dac0c412e7c61220a4855ee513 Mon Sep 17 00:00:00 2001 From: ZenDrx Date: Fri, 24 Jul 2026 08:57:25 +0100 Subject: [PATCH 15/27] Fix indentation for workflow_dispatch in ci.yaml --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a3f3c88a..91b16ab8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -3,7 +3,7 @@ on: branches: [time-feature] pull_request: branches: [main] - workflow_dispatch: + workflow_dispatch: jobs: tests: From 64a0434be6a3bff716ce454f3a92b41bf8f298e9 Mon Sep 17 00:00:00 2001 From: ZenDrx Date: Fri, 24 Jul 2026 09:05:27 +0100 Subject: [PATCH 16/27] Fix syntax error in buzz_time.zig --- src/lib/buzz_time.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/buzz_time.zig b/src/lib/buzz_time.zig index 953c36cc..098932fe 100644 --- a/src/lib/buzz_time.zig +++ b/src/lib/buzz_time.zig @@ -35,7 +35,7 @@ pub export fn timeFormat(ctx: *api.NativeCtx) callconv(.c) c_int { return -1; }; - const seconds: i64 = @intFromFloat(@floor(ts / 1000.0))); + const seconds: i64 = @intFromFloat(@floor(ts / 1000.0)); const nanos: i64 = @intFromFloat(@mod(ts, 1000.0) * 1_000_000)); const epoch = std.Io.Clock.Timestamp.fromNanoseconds(@as(i96, seconds) * 1_000_000_000 + @as(i96, nanos)); From 02c369077482258d0d7b67cfd0078298645a95e7 Mon Sep 17 00:00:00 2001 From: ZenDrx Date: Fri, 24 Jul 2026 09:14:00 +0100 Subject: [PATCH 17/27] Add 'time' header to static_headers.zig --- src/lib/static_headers.zig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/static_headers.zig b/src/lib/static_headers.zig index 4ece9fa0..649a6730 100644 --- a/src/lib/static_headers.zig +++ b/src/lib/static_headers.zig @@ -22,8 +22,10 @@ pub const math = Header{ .name = "math", .path = "math.buzz" }; pub const os = Header{ .name = "os", .path = "os.buzz" }; pub const serialize = Header{ .name = "serialize", .path = "serialize.buzz" }; pub const std = Header{ .name = "std", .path = "std.buzz" }; +pub const time = Header{ .name = "time", .path = "time.buzz" }; pub const testing = Header{ .name = "test", .path = "testing.buzz" }; + /// Buzz headers bundled with the compiler/runtime and installed for tooling. pub const all = [_]Header{ buffer, @@ -41,4 +43,5 @@ pub const all = [_]Header{ serialize, std, testing, + time, }; From 9e26bf1e4271bc50c5899219ae1190b79b98f00d Mon Sep 17 00:00:00 2001 From: ZenDrx Date: Fri, 24 Jul 2026 09:21:30 +0100 Subject: [PATCH 18/27] Fix header reference for time in static libraries --- src/lib/static_libraries.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/static_libraries.zig b/src/lib/static_libraries.zig index 63f8032c..d64f59bb 100644 --- a/src/lib/static_libraries.zig +++ b/src/lib/static_libraries.zig @@ -30,7 +30,7 @@ pub const all = [_]Library{ .{ .header = static_headers.os, .zig_path = "buzz_os.zig", .wasm_native = false }, .{ .header = static_headers.serialize, .zig_path = "buzz_serialize.zig", .wasm_native = true }, .{ .header = static_headers.std, .zig_path = "buzz_std.zig", .wasm_native = true }, - .{ .header = static_header.time, .zig_path = "buzz_time.zig", .wasm_native = false }, + .{ .header = static_headers.time, .zig_path = "buzz_time.zig", .wasm_native = false }, .{ .header = static_headers.testing, .zig_path = null, .wasm_native = false }, }; From 9dadc955b0bfb2e8f97613aa1adc39c563f2eab1 Mon Sep 17 00:00:00 2001 From: ZenDrx Date: Fri, 24 Jul 2026 09:22:45 +0100 Subject: [PATCH 19/27] Fix formatting of nanos calculation in buzz_time.zig --- src/lib/buzz_time.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/buzz_time.zig b/src/lib/buzz_time.zig index 098932fe..7ff3d419 100644 --- a/src/lib/buzz_time.zig +++ b/src/lib/buzz_time.zig @@ -36,7 +36,7 @@ pub export fn timeFormat(ctx: *api.NativeCtx) callconv(.c) c_int { }; const seconds: i64 = @intFromFloat(@floor(ts / 1000.0)); - const nanos: i64 = @intFromFloat(@mod(ts, 1000.0) * 1_000_000)); + const nanos: i64 = @intFromFloat(@mod(ts, 1000.0) * 1_000_000); const epoch = std.Io.Clock.Timestamp.fromNanoseconds(@as(i96, seconds) * 1_000_000_000 + @as(i96, nanos)); From 39ed452d1cc4e8c780d2565def8bd6b423b70f24 Mon Sep 17 00:00:00 2001 From: ZenDrx Date: Fri, 24 Jul 2026 09:29:08 +0100 Subject: [PATCH 20/27] Fix formatting of timestamp in buzz_time.zig --- src/lib/buzz_time.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/buzz_time.zig b/src/lib/buzz_time.zig index 7ff3d419..93162e42 100644 --- a/src/lib/buzz_time.zig +++ b/src/lib/buzz_time.zig @@ -40,7 +40,7 @@ pub export fn timeFormat(ctx: *api.NativeCtx) callconv(.c) c_int { const epoch = std.Io.Clock.Timestamp.fromNanoseconds(@as(i96, seconds) * 1_000_000_000 + @as(i96, nanos)); - var buf: [64]u8 = undefined + var buf: [64]u8 = undefined; const formatted = std.Io.Clock.Timestamp.formatNumber(epoch, "{s}", &buf) catch { ctx.vm.pushError("errors.UnexpectedError", "Failed to format time"); return -1; From e1081e98e35be351748096f5551f1fd630146d9b Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Fri, 24 Jul 2026 11:01:58 +0200 Subject: [PATCH 21/27] fix: Root default values on the stack to prevent bad GC sweep --- src/GC.zig | 6 ---- src/buzz_api.zig | 10 ++++-- src/obj.zig | 62 ++++++++++++++++++++++++---------- src/vm.zig | 5 +-- tests/behavior/c-buzz-api.buzz | 44 ++++++++++++++++++++++++ tests/behavior/objects.buzz | 49 +++++++++++++++++++++++++++ tests/behavior/upvalues.buzz | 20 +++++++++++ tests/utils/buzz_c_api.buzz | 2 ++ tests/utils/buzz_c_api.c | 25 ++++++++++++++ 9 files changed, 195 insertions(+), 28 deletions(-) diff --git a/src/GC.zig b/src/GC.zig index 2b4b1ee1..54009aa8 100644 --- a/src/GC.zig +++ b/src/GC.zig @@ -463,12 +463,6 @@ fn freeObj(self: *GC, obj: *o.Obj) (std.mem.Allocator.Error || std.fmt.BufPrintE }, .UpValue => { const obj_upvalue = o.ObjUpValue.cast(obj).?; - if (obj_upvalue.closed) |value| { - if (value.isObj()) { - try freeObj(self, value.obj()); - } - } - free(self, o.ObjUpValue, obj_upvalue); }, .Closure => { diff --git a/src/buzz_api.zig b/src/buzz_api.zig index 9b856686..08c894f3 100644 --- a/src/buzz_api.zig +++ b/src/buzz_api.zig @@ -775,6 +775,9 @@ export fn bz_newQualifiedObjectInstance(self: *VM, qualified_name: [*]const u8, @panic("Could not create error"); }; + // Keep the new instance rooted while cloning defaults may allocate. + self.push(instance.toValue()); + // Set instance fields with default values for (object.defaults, 0..) |default, idx| { if (default) |udefault| { @@ -786,7 +789,7 @@ export fn bz_newQualifiedObjectInstance(self: *VM, qualified_name: [*]const u8, } } - return instance.toValue(); + return self.pop(); } fn instanciateError( @@ -890,6 +893,9 @@ export fn bz_newObjectInstance(vm: *VM, object_value: v.Value, typedef_value: v. ) catch @panic("Out of memory"), ) catch @panic("Could not instanciate object"); + // Keep the new instance rooted while cloning defaults may allocate. + vm.push(instance.toValue()); + // If not anonymous, set default fields if (object) |uobject| { for (uobject.defaults, 0..) |default, idx| { @@ -903,7 +909,7 @@ export fn bz_newObjectInstance(vm: *VM, object_value: v.Value, typedef_value: v. } } - return instance.toValue(); + return vm.pop(); } export fn bz_getObjectField(object_value: v.Value, field_idx: usize) callconv(.c) v.Value { diff --git a/src/obj.zig b/src/obj.zig index 4a216b0d..c453956c 100644 --- a/src/obj.zig +++ b/src/obj.zig @@ -1571,16 +1571,20 @@ pub const ObjObjectInstance = struct { type_def: *ObjTypeDef, gc: *GC, ) !Self { + const fields = try gc.allocateMany( + Value, + type_def.resolved_type.?.ObjectInstance.of + .resolved_type.?.Object + .propertiesCount(), + ); + + @memset(fields, Value.Null); + return .{ .vm = vm, .object = object, .type_def = type_def, - .fields = try gc.allocateMany( - Value, - type_def.resolved_type.?.ObjectInstance.of - .resolved_type.?.Object - .propertiesCount(), - ), + .fields = fields, }; } @@ -1759,15 +1763,23 @@ pub const ObjObject = struct { property_count: ?usize = 0, pub fn init(allocator: Allocator, type_def: *ObjTypeDef) !Self { + const fields = try allocator.alloc( + Value, + type_def.resolved_type.?.Object.fields.count(), + ); + errdefer allocator.free(fields); + + const defaults = try allocator.alloc( + ?Value, + type_def.resolved_type.?.Object.propertiesCount(), + ); + + @memset(fields, Value.Void); + @memset(defaults, null); + const self = Self{ - .fields = try allocator.alloc( - Value, - type_def.resolved_type.?.Object.fields.count(), - ), - .defaults = try allocator.alloc( - ?Value, - type_def.resolved_type.?.Object.propertiesCount(), - ), + .fields = fields, + .defaults = defaults, .type_def = type_def, }; @@ -5925,24 +5937,38 @@ pub fn cloneObject(obj: *Obj, vm: *VM) !Value { .List => { const list = ObjList.cast(obj).?; + var items = try list.items.clone(vm.gc.allocator); + errdefer items.deinit(vm.gc.allocator); + + const methods = try vm.gc.allocator.alloc(?*ObjNative, list.methods.len); + errdefer vm.gc.allocator.free(methods); + + @memcpy(methods, list.methods); return (try vm.gc.allocateObject( ObjList{ .type_def = list.type_def, - .items = try list.items.clone(vm.gc.allocator), - .methods = list.methods, + .items = items, + .methods = methods, }, )).toValue(); }, .Map => { const map = ObjMap.cast(obj).?; + var cloned_map = try map.map.clone(vm.gc.allocator); + errdefer cloned_map.deinit(vm.gc.allocator); + + const methods = try vm.gc.allocator.alloc(?*ObjNative, map.methods.len); + errdefer vm.gc.allocator.free(methods); + + @memcpy(methods, map.methods); return (try vm.gc.allocateObject( ObjMap{ .type_def = map.type_def, - .map = try map.map.clone(vm.gc.allocator), - .methods = map.methods, + .map = cloned_map, + .methods = methods, }, )).toValue(); }, diff --git a/src/vm.zig b/src/vm.zig index 23bf3a2b..451fc45f 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -3084,6 +3084,9 @@ pub const VM = struct { unreachable; }; + // Keep the new instance rooted while cloning defaults may allocate. + self.push(obj_instance.toValue()); + // If not anonymous, set default fields if (object) |uobject| { // Set instance fields with default values @@ -3104,8 +3107,6 @@ pub const VM = struct { } } - self.push(obj_instance.toValue()); - const frame = self.currentFrame().?; const next_full_instruction = self.readInstruction(frame); @call( diff --git a/tests/behavior/c-buzz-api.buzz b/tests/behavior/c-buzz-api.buzz index 28d23891..f6e11987 100644 --- a/tests/behavior/c-buzz-api.buzz +++ b/tests/behavior/c-buzz-api.buzz @@ -1,6 +1,43 @@ import "buzz:std"; import "../utils/buzz_c_api"; +object ApiDefaultClone { + tokens: [int] = [ + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 69, 70, 71, 72, + 73, 74, 75, 76, 77, 78, 79, 80, + 81, 82, 83, 84, 85, 86, 87, 88, + 89, 90, 91, 92, 93, 94, 95, 96, + 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, + 113, 114, 115, 116, 117, 118, 119, 120, + 121, 122, 123, 124, 125, 126, 127, 128, + 129, 130, 131, 132, 133, 134, 135, 136, + 137, 138, 139, 140, 141, 142, 143, 144, + 145, 146, 147, 148, 149, 150, 151, 152, + 153, 154, 155, 156, 157, 158, 159, 160, + 161, 162, 163, 164, 165, 166, 167, 168, + 169, 170, 171, 172, 173, 174, 175, 176, + 177, 178, 179, 180, 181, 182, 183, 184, + 185, 186, 187, 188, 189, 190, 191, 192, + 193, 194, 195, 196, 197, 198, 199, 200, + 201, 202, 203, 204, 205, 206, 207, 208, + 209, 210, 211, 212, 213, 214, 215, 216, + 217, 218, 219, 220, 221, 222, 223, 224, + 225, 226, 227, 228, 229, 230, 231, 232, + 233, 234, 235, 236, 237, 238, 239, 240, + 241, 242, 243, 244, 245, 246, 247, 248, + 249, 250, 251, 252, 253, 254, 255, 256, + ], +} + test "C buzz api library" { std\assert( buzz_c_api\add(40, rhs: 2) == 42, @@ -22,4 +59,11 @@ test "C buzz api library" { buzz_c_api\argCount(1, b: 2, c: 3) == 3, message: "C library can read NativeCtx arg_count" ); + std\assert( + buzz_c_api\instantiateDefaultedObject( + ApiDefaultClone, + typedef_value: typeof ApiDefaultClone{} + ) == 5120000, + message: "C library can instantiate objects with cloneable defaults under GC pressure" + ); } diff --git a/tests/behavior/objects.buzz b/tests/behavior/objects.buzz index dc8df491..250aee44 100644 --- a/tests/behavior/objects.buzz +++ b/tests/behavior/objects.buzz @@ -41,3 +41,52 @@ test "Object with static fields" { std\assert(second.id == Second.nextId, message: "could use static fields"); } + +object CloneDefault { + tokens: [int] = [ + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 69, 70, 71, 72, + 73, 74, 75, 76, 77, 78, 79, 80, + 81, 82, 83, 84, 85, 86, 87, 88, + 89, 90, 91, 92, 93, 94, 95, 96, + 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, + 113, 114, 115, 116, 117, 118, 119, 120, + 121, 122, 123, 124, 125, 126, 127, 128, + 129, 130, 131, 132, 133, 134, 135, 136, + 137, 138, 139, 140, 141, 142, 143, 144, + 145, 146, 147, 148, 149, 150, 151, 152, + 153, 154, 155, 156, 157, 158, 159, 160, + 161, 162, 163, 164, 165, 166, 167, 168, + 169, 170, 171, 172, 173, 174, 175, 176, + 177, 178, 179, 180, 181, 182, 183, 184, + 185, 186, 187, 188, 189, 190, 191, 192, + 193, 194, 195, 196, 197, 198, 199, 200, + 201, 202, 203, 204, 205, 206, 207, 208, + 209, 210, 211, 212, 213, 214, 215, 216, + 217, 218, 219, 220, 221, 222, 223, 224, + 225, 226, 227, 228, 229, 230, 231, 232, + 233, 234, 235, 236, 237, 238, 239, 240, + 241, 242, 243, 244, 245, 246, 247, 248, + 249, 250, 251, 252, 253, 254, 255, 256, + ], +} + +test "Objects keep cloneable defaults alive during instantiation" { + var i = 0; + var total = 0; + + while (i < 20000) { + total = total + CloneDefault{}.tokens[0]; + i = i + 1; + } + + std\assert(total == 20000, message: "object instances survive GC while cloning defaults"); +} diff --git a/tests/behavior/upvalues.buzz b/tests/behavior/upvalues.buzz index aed8bcaf..4cd49700 100644 --- a/tests/behavior/upvalues.buzz +++ b/tests/behavior/upvalues.buzz @@ -1,4 +1,10 @@ import "buzz:std"; +import "buzz:gc"; + +enum CapturedEnum { + one, + two, +} fun upvals() > fun () { final upvalue = 12; @@ -10,3 +16,17 @@ fun upvals() > fun () { test "Upvalues" { upvals()(); } + +fun captureEnumCase() > fun () > CapturedEnum { + final captured = CapturedEnum.two; + + return fun () > CapturedEnum => captured; +} + +test "Closed upvalues can capture enum cases through GC" { + _ = captureEnumCase(); + + gc\collect(); + + std\assert(true, message: "collecting discarded closures with enum captures does not corrupt GC"); +} diff --git a/tests/utils/buzz_c_api.buzz b/tests/utils/buzz_c_api.buzz index c8133c56..12d8ccda 100644 --- a/tests/utils/buzz_c_api.buzz +++ b/tests/utils/buzz_c_api.buzz @@ -5,3 +5,5 @@ export extern fun add(lhs: int, rhs: int) > int; export extern fun isNumber(value: any) > bool; export extern fun argCount(a: int, b: int, c: int) > int; + +export extern fun instantiateDefaultedObject(object_value: any, typedef_value: type) > int; diff --git a/tests/utils/buzz_c_api.c b/tests/utils/buzz_c_api.c index 388b8a4c..3b6d55ca 100644 --- a/tests/utils/buzz_c_api.c +++ b/tests/utils/buzz_c_api.c @@ -23,6 +23,27 @@ static int argCount(NativeCtx *ctx) { return 1; } +static int instantiateDefaultedObject(NativeCtx *ctx) { + Value object_value = bz_peek(ctx->vm, 1); + Value typedef_value = bz_peek(ctx->vm, 0); + Integer total = 0; + + for (int i = 0; i < 20000; i++) { + Value instance = bz_newObjectInstance( + ctx->vm, + object_value, + typedef_value + ); + Value tokens = bz_getObjectInstanceProperty(instance, 0); + + total += (Integer)bz_listLen(tokens); + } + + bz_push(ctx->vm, bz_valueFromInteger(total)); + + return 1; +} + NativeFn buzz_c_api(const char *symbol) { if (strcmp(symbol, "add") == 0) { return add; @@ -36,5 +57,9 @@ NativeFn buzz_c_api(const char *symbol) { return argCount; } + if (strcmp(symbol, "instantiateDefaultedObject") == 0) { + return instantiateDefaultedObject; + } + return 0; } From 9496c1b9d84e1c645c9f2d0062ccce3d9a10b6fb Mon Sep 17 00:00:00 2001 From: ZenDrx Date: Fri, 24 Jul 2026 11:56:49 +0100 Subject: [PATCH 22/27] Fix argument order in Clock.now calls --- src/lib/buzz_time.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/buzz_time.zig b/src/lib/buzz_time.zig index 93162e42..6118f5fe 100644 --- a/src/lib/buzz_time.zig +++ b/src/lib/buzz_time.zig @@ -2,21 +2,21 @@ const api = @import("buzz_api.zig"); const std = @import("std"); pub export fn timeNow(ctx: *api.NativeCtx) callconv(.c) c_int { - const ts = std.Io.Clock.now(ctx.getIo(), .real); + const ts = std.Io.Clock.now(.real, ctx.getIo()); const ms = ts.toMilliseconds(); ctx.vm.bz_push(.fromDouble(@floatFromInt(ms))); return 1; } pub export fn timeMonotonic(ctx: *api.NativeCtx) callconv(.c) c_int { - const ts = std.Io.Clock.now(ctx.getIo(), .awake); + const ts = std.Io.Clock.now(.awake, ctx.getIo()); const ms = ts.toMilliseconds(); ctx.vm.bz_push(.fromDouble(@floatFromInt(ms))); return 1; } pub export fn timeCpu(ctx: *api.NativeCtx) callconv(.c) c_int { - const ts = std.Io.Clock.now(ctx.getIo(), .cpu_process) catch { + const ts = std.Io.Clock.now(.cpu_process, ctx.getIo()) catch { ctx.vm.pushError("errors.UnexpectedError", "CPU clock not available"); return -1; }; From e828709c0bbf205f44553a2f86ad6ed4bcf528fb Mon Sep 17 00:00:00 2001 From: ZenDrx Date: Fri, 24 Jul 2026 12:26:12 +0100 Subject: [PATCH 23/27] Remove unnecessary complex format/parse --- src/lib/buzz_time.zig | 54 +++---------------------------------------- 1 file changed, 3 insertions(+), 51 deletions(-) diff --git a/src/lib/buzz_time.zig b/src/lib/buzz_time.zig index 6118f5fe..8497af90 100644 --- a/src/lib/buzz_time.zig +++ b/src/lib/buzz_time.zig @@ -2,79 +2,31 @@ const api = @import("buzz_api.zig"); const std = @import("std"); pub export fn timeNow(ctx: *api.NativeCtx) callconv(.c) c_int { - const ts = std.Io.Clock.now(.real, ctx.getIo()); + const ts = std.Io.Timestamp.now(ctx.getIo(), .real); const ms = ts.toMilliseconds(); ctx.vm.bz_push(.fromDouble(@floatFromInt(ms))); return 1; } pub export fn timeMonotonic(ctx: *api.NativeCtx) callconv(.c) c_int { - const ts = std.Io.Clock.now(.awake, ctx.getIo()); + const ts = std.Io.Timestamp.now(ctx.getIo(), .awake); const ms = ts.toMilliseconds(); ctx.vm.bz_push(.fromDouble(@floatFromInt(ms))); return 1; } pub export fn timeCpu(ctx: *api.NativeCtx) callconv(.c) c_int { - const ts = std.Io.Clock.now(.cpu_process, ctx.getIo()) catch { - ctx.vm.pushError("errors.UnexpectedError", "CPU clock not available"); - return -1; - }; + const ts = std.Io.Timestamp.now(ctx.getIo(), .cpu_process); const ms = ts.toMilliseconds(); ctx.vm.bz_push(.fromDouble(@floatFromInt(ms))); return 1; } -pub export fn timeFormat(ctx: *api.NativeCtx) callconv(.c) c_int { - var len: usize = 0; - const ts_ptr = ctx.vm.bz_peek(0).bz_valueToString(&len); - const ts_str = ts_ptr.?[0..len]; - - const ts = std.fmt.parseFloat(api.Double, ts_str) catch { - ctx.vm.pushError("errors.InvalidArgumentError", "Invalid timestamp"); - return -1; - }; - - const seconds: i64 = @intFromFloat(@floor(ts / 1000.0)); - const nanos: i64 = @intFromFloat(@mod(ts, 1000.0) * 1_000_000); - - const epoch = std.Io.Clock.Timestamp.fromNanoseconds(@as(i96, seconds) * 1_000_000_000 + @as(i96, nanos)); - - var buf: [64]u8 = undefined; - const formatted = std.Io.Clock.Timestamp.formatNumber(epoch, "{s}", &buf) catch { - ctx.vm.pushError("errors.UnexpectedError", "Failed to format time"); - return -1; - }; - - ctx.vm.bz_push( - api.VM.bz_stringToValue(ctx.vm, formatted.ptr, formatted.len), - ); - - return 1; -} - -pub export fn timeParse(ctx: *api.NativeCtx) callconv(.c) c_int { - var len: usize = 0; - const iso_ptr = ctx.vm.bz_peek(0).bz_valueToString(&len); - const iso_str = iso_ptr.?[0..len]; - - const parsed = std.Io.Clock.Timestamp.parse(iso_str) catch { - ctx.vm.pushError("errors.InvalidArgumentError", "Invalid ISO 8601 string"); - return -1; - }; - - const ms = parsed.toMilliseconds(); - ctx.vm.bz_push(.fromDouble(@floatFromInt(ms))); - return 1; -} - pub const library = api.BuzzApi( "time", &.{ &.{ "now", timeNow }, &.{ "monotonic", timeMonotonic }, &.{ "cpu", timeCpu }, - &.{ "format", timeFormat }, - &.{ "parse", timeParse }, }, ){}; From 5421f863532d19b500aa175dfa36c256cf5c9045 Mon Sep 17 00:00:00 2001 From: ZenDrx Date: Fri, 24 Jul 2026 12:27:27 +0100 Subject: [PATCH 24/27] Refactor time.buzz to adjust function exports --- src/lib/time.buzz | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/lib/time.buzz b/src/lib/time.buzz index 70fff34a..15d7f98f 100644 --- a/src/lib/time.buzz +++ b/src/lib/time.buzz @@ -1,5 +1,7 @@ namespace time; +import "buzz:errors"; + /// Real-time (wall-clock) in milliseconds since Unix epoch export extern fun now() > double; @@ -8,10 +10,3 @@ export extern fun monotonic() > double; /// Cpu time used by the current process in milliseconds export extern fun cpu() > double !> errors\UnexpectedError; - -/// Format a timestamp as ISO 8601 string -export extern fun format(timestamp: double) > str; - -/// Parse a ISO 8601 string as timestamp -/// returns the timestamp as a double -export extern fun parse(iso: str) > double; From 4648015d53f9b7ea50cc0454b5d6193dc50e4581 Mon Sep 17 00:00:00 2001 From: ZenDrx Date: Fri, 24 Jul 2026 12:32:07 +0100 Subject: [PATCH 25/27] Refactor time tests to add monotonic and CPU checks Updated tests to include monotonic time checks and CPU time validation. --- tests/behavior/time.buzz | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/behavior/time.buzz b/tests/behavior/time.buzz index 3f3c32ed..a62c3b60 100644 --- a/tests/behavior/time.buzz +++ b/tests/behavior/time.buzz @@ -4,28 +4,28 @@ import "buzz:time"; test "now returns a positive number" { final now = time\now(); std\assert( - now > 0, - message: "now should be greater than 0" + now > 0, + message: "now should be greater than 0" ); } -test "format and parse roundtrip" { - final original = time\now(); - final formatted = time\format(original) catch from { - std\assert( - false, - message: "format failed" - ); - }; - final parsed = time\parse(formatted) catch from { - std\assert( - false, - message: "parse failed" - ); +test "monotonic increases" { + final start = time\monotonic(); + os\sleep(10.0); + final end = time\monotonic(); + std\assert( + end > start, + message: "monotonic should increase after sleep" + ); +} + +test "cpu returns a positive number" { + final cpu = time\cpu() catch { + // CPU clock might not be available on all platforms + return; }; - final diff = original - parsed; std\assert( - diff < 1000, - message: "roundtrip error should be less than 1 second" + cpu >= 0, + message: "cpu time should be >= 0" ); } From 7eaef1cd90fac06e891d107379ca0567d481dfac Mon Sep 17 00:00:00 2001 From: zendrx Date: Sat, 25 Jul 2026 12:23:06 +0100 Subject: [PATCH 26/27] Refactor time tests to add monotonic and CPU checks Updated tests to include monotonic time checks and CPU time validation. --- tests/behavior/time.buzz | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/behavior/time.buzz b/tests/behavior/time.buzz index 3f3c32ed..a62c3b60 100644 --- a/tests/behavior/time.buzz +++ b/tests/behavior/time.buzz @@ -4,28 +4,28 @@ import "buzz:time"; test "now returns a positive number" { final now = time\now(); std\assert( - now > 0, - message: "now should be greater than 0" + now > 0, + message: "now should be greater than 0" ); } -test "format and parse roundtrip" { - final original = time\now(); - final formatted = time\format(original) catch from { - std\assert( - false, - message: "format failed" - ); - }; - final parsed = time\parse(formatted) catch from { - std\assert( - false, - message: "parse failed" - ); +test "monotonic increases" { + final start = time\monotonic(); + os\sleep(10.0); + final end = time\monotonic(); + std\assert( + end > start, + message: "monotonic should increase after sleep" + ); +} + +test "cpu returns a positive number" { + final cpu = time\cpu() catch { + // CPU clock might not be available on all platforms + return; }; - final diff = original - parsed; std\assert( - diff < 1000, - message: "roundtrip error should be less than 1 second" + cpu >= 0, + message: "cpu time should be >= 0" ); } From 1562f669624a24079fca0e21fc3bdfc594154b68 Mon Sep 17 00:00:00 2001 From: zendrx Date: Mon, 27 Jul 2026 02:52:10 +0100 Subject: [PATCH 27/27] Update static_headers.zig --- src/lib/static_headers.zig | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/static_headers.zig b/src/lib/static_headers.zig index 649a6730..905bba89 100644 --- a/src/lib/static_headers.zig +++ b/src/lib/static_headers.zig @@ -25,7 +25,6 @@ pub const std = Header{ .name = "std", .path = "std.buzz" }; pub const time = Header{ .name = "time", .path = "time.buzz" }; pub const testing = Header{ .name = "test", .path = "testing.buzz" }; - /// Buzz headers bundled with the compiler/runtime and installed for tooling. pub const all = [_]Header{ buffer,