From 7148254b35788cd32e1cf97d91561679d134a224 Mon Sep 17 00:00:00 2001 From: Pabloader Date: Fri, 10 Apr 2026 14:50:00 +0000 Subject: [PATCH] Fix time formatting --- build/server.ts | 1 + package.json | 2 +- src/common/utils.ts | 37 ++++++++++----- test/common/utils.test.ts | 95 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 121 insertions(+), 14 deletions(-) diff --git a/build/server.ts b/build/server.ts index 545ca8b..673d5f5 100644 --- a/build/server.ts +++ b/build/server.ts @@ -59,6 +59,7 @@ Bun.serve({ clients.delete(ws); if (clients.size === 0) wsClients.delete(game); } + console.log(`[css-hot] ${game} disconnected (${wsClients.get(game)!.size} clients)`); }, message() { }, }, diff --git a/package.json b/package.json index 3fc5d13..5d9daca 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "1.0.0", "type": "module", "scripts": { - "start": "bun --hot build/server.ts", + "start": "bun build/server.ts", "bake": "bun build/build.ts", "test": "bun test" }, diff --git a/src/common/utils.ts b/src/common/utils.ts index 97005a8..f1a4d3a 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -120,13 +120,25 @@ export const formatNumber = (n: number): string => { }; export const formatTime = (seconds: number): string => { - const y = Math.floor(seconds / 31536000); - const mo = Math.floor((seconds % 31536000) / 2592000); - const w = Math.floor((seconds % 2592000) / 604800); - const d = Math.floor((seconds % 604800) / 86400); - const h = Math.floor((seconds % 86400) / 3600); - const mi = Math.floor((seconds % 3600) / 60); - const s = Math.floor(seconds % 60); + const secInMinute = 60; + const secInHour = 60 * secInMinute; + const secInDay = 24 * secInHour; + const secInWeek = 7 * secInDay; + const secInMonth = 30 * secInDay; + const secInYear = 365 * secInDay; + + const y = Math.floor(seconds / secInYear); + let rem = seconds % secInYear; + const mo = Math.floor(rem / secInMonth); + rem %= secInMonth; + const w = Math.floor(rem / secInWeek); + rem %= secInWeek; + const d = Math.floor(rem / secInDay); + rem %= secInDay; + const h = Math.floor(rem / secInHour); + rem %= secInHour; + const mi = Math.floor(rem / secInMinute); + const s = Math.floor(rem % secInMinute); const parts: string[] = []; if (y > 0) parts.push(`${y}y`); @@ -135,9 +147,10 @@ export const formatTime = (seconds: number): string => { if (d > 0) parts.push(`${d}d`); const hasBigParts = parts.length > 0; - const hasTime = h > 0 || mi > 0 || s > 0; - if (hasBigParts || hasTime) { - parts.push(`${h}:${String(mi).padStart(2, '0')}:${String(s).padStart(2, '0')}`); - } - return parts.join(' ') || '0:00:00'; + const timeStr = hasBigParts || h > 0 + ? `${h}:${String(mi).padStart(2, '0')}:${String(s).padStart(2, '0')}` + : `${mi}:${String(s).padStart(2, '0')}`; + + parts.push(timeStr); + return parts.join(' '); }; diff --git a/test/common/utils.test.ts b/test/common/utils.test.ts index a5595b2..b34c351 100644 --- a/test/common/utils.test.ts +++ b/test/common/utils.test.ts @@ -16,6 +16,8 @@ import { sinHash, throttle, callUpdater, + formatTime, + formatNumber, } from '@common/utils'; describe('utils', () => { @@ -268,7 +270,13 @@ describe('utils', () => { it('should be deterministic', () => { const hash1 = sinHash(1, 2, 3); const hash2 = sinHash(1, 2, 3); - expect(hash1).toBe(hash2); + expect(hash1).toBeCloseTo(hash2); + }); + + it('should produce different hashes for different inputs', () => { + const hash1 = sinHash(1, 2, 3); + const hash2 = sinHash(4, 5, 6); + expect(hash1).not.toBeCloseTo(hash2); }); }); @@ -300,4 +308,89 @@ describe('utils', () => { expect(callUpdater(10, 5)).toBe(10); }); }); + + describe('formatNumber', () => { + it('should return small numbers as-is', () => { + expect(formatNumber(0)).toBe('0'); + expect(formatNumber(42)).toBe('42'); + expect(formatNumber(999)).toBe('999'); + }); + + it('should format thousands with k suffix', () => { + expect(formatNumber(1_000)).toBe('1.00k'); + expect(formatNumber(5_500)).toBe('5.50k'); + expect(formatNumber(999_999)).toBe('1000.00k'); + }); + + it('should format millions with M suffix', () => { + expect(formatNumber(1_000_000)).toBe('1.00M'); + expect(formatNumber(2_500_000)).toBe('2.50M'); + expect(formatNumber(999_999_999)).toBe('1000.00M'); + }); + + it('should format billions with B suffix', () => { + expect(formatNumber(1_000_000_000)).toBe('1.00B'); + expect(formatNumber(3_750_000_000)).toBe('3.75B'); + expect(formatNumber(999_999_999_999)).toBe('1000.00B'); + }); + + it('should format trillions with T suffix', () => { + expect(formatNumber(1_000_000_000_000)).toBe('1.00T'); + expect(formatNumber(5_250_000_000_000)).toBe('5.25T'); + }); + }); + + describe('formatTime', () => { + it('should return 0:00 for zero seconds', () => { + expect(formatTime(0)).toBe('0:00'); + }); + + it('should format seconds correctly', () => { + expect(formatTime(1)).toBe('0:01'); + expect(formatTime(30)).toBe('0:30'); + expect(formatTime(59)).toBe('0:59'); + }); + + it('should format minutes correctly', () => { + expect(formatTime(60)).toBe('1:00'); + expect(formatTime(2 * 60 + 5)).toBe('2:05'); + expect(formatTime(59 * 60 + 59)).toBe('59:59'); + }); + + it('should format hours correctly', () => { + expect(formatTime(60 * 60)).toBe('1:00:00'); + expect(formatTime(2 * 60 * 60 + 60 + 5)).toBe('2:01:05'); + }); + + it('should format days correctly', () => { + expect(formatTime(24 * 60 * 60)).toBe('1d 0:00:00'); + expect(formatTime(2 * 24 * 60 * 60)).toBe('2d 0:00:00'); + }); + + it('should format weeks correctly', () => { + expect(formatTime(7 * 24 * 60 * 60)).toBe('1w 0:00:00'); + }); + + it('should format months correctly', () => { + const month = 30 * 24 * 60 * 60; + expect(formatTime(month)).toBe('1m 0:00:00'); + }); + + it('should format years correctly', () => { + const year = 365 * 24 * 60 * 60; + expect(formatTime(year)).toBe('1y 0:00:00'); + }); + + it('should format complex durations', () => { + const total = + 365 * 24 * 60 * 60 + // 1y + 30 * 24 * 60 * 60 + // 1m + 7 * 24 * 60 * 60 + // 1w + 24 * 60 * 60 + // 1d + 60 * 60 + // 1h + 60 + // 1m + 1; // 1s + expect(formatTime(total)).toBe('1y 1m 1w 1d 1:01:01'); + }); + }); });