chore(utils): ports over utils from the WPF app

This commit is contained in:
Lewis Wynne 2026-03-23 16:19:00 +00:00
parent 949e33f06f
commit 7dc4bd8a11
4 changed files with 129 additions and 0 deletions

View file

@ -0,0 +1,50 @@
import { describe, it, expect } from 'vitest';
import { cmToFeetInches, feetInchesToCm, kgToLb, lbToKg } from './conversions';
describe('cmToFeetInches', () => {
it('converts 180 cm', () => {
expect(cmToFeetInches(180)).toBe('5\'11"');
});
it('converts 152 cm', () => {
expect(cmToFeetInches(152)).toBe('5\'0"');
});
it('converts 0 cm', () => {
expect(cmToFeetInches(0)).toBe('0\'0"');
});
it('converts 30 cm (just inches)', () => {
expect(cmToFeetInches(30)).toBe('1\'0"');
});
});
describe('feetInchesToCm', () => {
it('converts 5\'11" back', () => {
expect(feetInchesToCm(5, 11)).toBeCloseTo(180.34, 0);
});
it('converts 0\'0"', () => {
expect(feetInchesToCm(0, 0)).toBe(0);
});
});
describe('kgToLb', () => {
it('converts 75 kg', () => {
expect(kgToLb(75)).toBeCloseTo(165.35, 0);
});
it('converts 0 kg', () => {
expect(kgToLb(0)).toBe(0);
});
});
describe('lbToKg', () => {
it('converts 165 lb', () => {
expect(lbToKg(165)).toBeCloseTo(74.84, 0);
});
it('converts 0 lb', () => {
expect(lbToKg(0)).toBe(0);
});
});

View file

@ -0,0 +1,22 @@
const CM_PER_INCH = 2.54;
const INCHES_PER_FOOT = 12;
const LB_PER_KG = 2.20462;
export function cmToFeetInches(cm: number): string {
const totalInches = Math.round(cm / CM_PER_INCH);
const feet = Math.floor(totalInches / INCHES_PER_FOOT);
const inches = totalInches % INCHES_PER_FOOT;
return `${feet}'${inches}"`;
}
export function feetInchesToCm(feet: number, inches: number): number {
return (feet * INCHES_PER_FOOT + inches) * CM_PER_INCH;
}
export function kgToLb(kg: number): number {
return kg * LB_PER_KG;
}
export function lbToKg(lb: number): number {
return lb / LB_PER_KG;
}

View file

@ -0,0 +1,33 @@
import { describe, it, expect } from 'vitest';
import { icYear, formatICDate } from './dates';
describe('icYear', () => {
it('adds 442 to the real year', () => {
expect(icYear(2026)).toBe(2468);
});
it('works for other years', () => {
expect(icYear(2000)).toBe(2442);
});
});
describe('formatICDate', () => {
it('formats with ordinal suffixes', () => {
expect(formatICDate(new Date(2026, 2, 1))).toBe('March 1st, 2468');
expect(formatICDate(new Date(2026, 2, 2))).toBe('March 2nd, 2468');
expect(formatICDate(new Date(2026, 2, 3))).toBe('March 3rd, 2468');
expect(formatICDate(new Date(2026, 2, 4))).toBe('March 4th, 2468');
});
it('handles 11th, 12th, 13th', () => {
expect(formatICDate(new Date(2026, 0, 11))).toBe('January 11th, 2468');
expect(formatICDate(new Date(2026, 0, 12))).toBe('January 12th, 2468');
expect(formatICDate(new Date(2026, 0, 13))).toBe('January 13th, 2468');
});
it('handles 21st, 22nd, 23rd', () => {
expect(formatICDate(new Date(2026, 5, 21))).toBe('June 21st, 2468');
expect(formatICDate(new Date(2026, 5, 22))).toBe('June 22nd, 2468');
expect(formatICDate(new Date(2026, 5, 23))).toBe('June 23rd, 2468');
});
});

24
src/lib/utils/dates.ts Normal file
View file

@ -0,0 +1,24 @@
const IC_OFFSET = 442;
const MONTHS = [
'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'
];
function ordinal(n: number): string {
if (n >= 11 && n <= 13) return n + 'th';
switch (n % 10) {
case 1: return n + 'st';
case 2: return n + 'nd';
case 3: return n + 'rd';
default: return n + 'th';
}
}
export function icYear(realYear: number): number {
return realYear + IC_OFFSET;
}
export function formatICDate(date: Date): string {
return `${MONTHS[date.getMonth()]} ${ordinal(date.getDate())}, ${icYear(date.getFullYear())}`;
}