Something went wrong. Try again.
zig library for building requests to query inference providers usage limits
Something went wrong. Try again.
5.4 kB · 127 lines
Zig
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128//! ISO-8601 parser for usage feed reset timestamps.//!//! Handles full ISO-8601 strings, e.g.://! - 2026-09-16T08:00:00.037593+00:00//! - 2026-09-15T23:46:00Z//! - 2026-06-09T11:28:39.481128Z//! - 2026-01-01T00:00:00+05:30//! - 2026-01-01T00:00:00-08:00//!//! Returns epoch milliseconds (UTC), matching Date.parse in TypeScript.const std = @import("std");
fn isLeapYear(year: i64) bool { if (@mod(year, 4) != 0) return false; if (@mod(year, 100) != 0) return true; return @mod(year, 400) == 0;}
const days_before_month_common = [_]i64{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };const days_before_month_leap = [_]i64{ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 };
pub fn daysFromCivil(year: i64, month: u8, day: u8) i64 { // Days from 1970-01-01 to start of `year` var leaps: i64 = 0; if (year >= 1970) { const y_prev = year - 1; leaps = @divFloor(y_prev, 4) - @divFloor(y_prev, 100) + @divFloor(y_prev, 400) - (@divFloor(1969, 4) - @divFloor(1969, 100) + @divFloor(1969, 400)); } else { const y_curr = year; leaps = @divFloor(1969, 4) - @divFloor(1969, 100) + @divFloor(1969, 400) - (@divFloor(y_curr - 1, 4) - @divFloor(y_curr - 1, 100) + @divFloor(y_curr - 1, 400)); leaps = -leaps; } const days_in_years = (year - 1970) * 365 + leaps; const m_days = if (isLeapYear(year)) days_before_month_leap[month - 1] else days_before_month_common[month - 1]; return days_in_years + m_days + (day - 1);}
pub fn parseIso8601(str: []const u8) ?i64 { const trimmed = std.mem.trim(u8, str, " \t\r\n"); if (trimmed.len < 19) return null; // YYYY-MM-DDTHH:MM:SS if (trimmed[4] != '-' or trimmed[7] != '-' or (trimmed[10] != 'T' and trimmed[10] != 't' and trimmed[10] != ' ')) return null; if (trimmed[13] != ':' or trimmed[16] != ':') return null;
const year = std.fmt.parseInt(i64, trimmed[0..4], 10) catch return null; const month = std.fmt.parseInt(u8, trimmed[5..7], 10) catch return null; const day = std.fmt.parseInt(u8, trimmed[8..10], 10) catch return null; if (month < 1 or month > 12) return null; if (day < 1 or day > 31) return null;
const hour = std.fmt.parseInt(i64, trimmed[11..13], 10) catch return null; const minute = std.fmt.parseInt(i64, trimmed[14..16], 10) catch return null; const second = std.fmt.parseInt(i64, trimmed[17..19], 10) catch return null; if (hour < 0 or hour > 23 or minute < 0 or minute > 59 or second < 0 or second > 60) return null;
var idx: usize = 19; var millis: i64 = 0; if (idx < trimmed.len and trimmed[idx] == '.') { idx += 1; var frac_digits: usize = 0; var frac_val: i64 = 0; while (idx < trimmed.len and std.ascii.isDigit(trimmed[idx])) : (idx += 1) { if (frac_digits < 3) { frac_val = frac_val * 10 + (trimmed[idx] - '0'); frac_digits += 1; } } while (frac_digits < 3) : (frac_digits += 1) { frac_val *= 10; } millis = frac_val; }
var offset_sec: i64 = 0; if (idx < trimmed.len) { if (trimmed[idx] == 'Z' or trimmed[idx] == 'z') { idx += 1; } else if (trimmed[idx] == '+' or trimmed[idx] == '-') { const sign: i64 = if (trimmed[idx] == '+') 1 else -1; idx += 1; if (idx + 2 <= trimmed.len) { const off_h = std.fmt.parseInt(i64, trimmed[idx .. idx + 2], 10) catch return null; idx += 2; var off_m: i64 = 0; if (idx < trimmed.len and trimmed[idx] == ':') idx += 1; if (idx + 2 <= trimmed.len) { off_m = std.fmt.parseInt(i64, trimmed[idx .. idx + 2], 10) catch return null; idx += 2; } offset_sec = sign * (off_h * 3600 + off_m * 60); } else { return null; } } else { return null; } } if (idx != trimmed.len) return null; // trailing unparsed garbage
const days = daysFromCivil(year, month, day); const total_sec = days * 86400 + hour * 3600 + minute * 60 + second - offset_sec; return total_sec * 1000 + millis;}
test "parseIso8601 with various formats" { const cases = [_]struct { s: []const u8, expected: i64 }{ .{ .s = "2026-09-16T08:00:00.037593+00:00", .expected = 1789545600037 }, .{ .s = "2026-09-15T23:46:00Z", .expected = 1789515960000 }, .{ .s = "2026-06-09T11:28:39.481128Z", .expected = 1781004519481 }, .{ .s = "2026-01-01T00:00:00+05:30", .expected = 1767205800000 }, .{ .s = "2026-01-01T00:00:00-08:00", .expected = 1767254400000 }, .{ .s = "2026-01-01T00:00:00.5Z", .expected = 1767225600500 }, .{ .s = "2026-01-01T00:00:00.12Z", .expected = 1767225600120 }, .{ .s = "2026-01-01T00:00:00.123Z", .expected = 1767225600123 }, .{ .s = "2026-01-01T00:00:00.1234Z", .expected = 1767225600123 }, };
for (cases) |c| { const got = parseIso8601(c.s); try std.testing.expectEqual(c.expected, got.?); }
try std.testing.expect(parseIso8601("") == null); try std.testing.expect(parseIso8601("not-a-date") == null); try std.testing.expect(parseIso8601("2026-09-16") == null);}