Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
93 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means.1/**
2 * The one way money is written on g1t: a sum in millionths of a dollar
3 * (micros, as every service counts it) as `$1,234.56`. Every page, the
4 * top bar's pill, Spend, Home, Usage, the statement and the price book
5 * read the same arithmetic, so the same sum reads the same everywhere.
6 *
7 * - Rounding is on whole micros, half up at the last place shown; never on
8 * a floating-point `toFixed`, which rounds `.5` either way.
9 * - Negatives lead with a minus sign (U+2212); thousands are separated.
10 * - By default, to the cent, and a fraction of a cent reads `<$0.01`
11 * rather than `$0.00` or `$0.004`: a total is a total. Exactly nothing
12 * is `$0.00`.
13 * - `precise` shows up to four places (`$0.0063`), trailing zeros trimmed
14 * down to two, for where the fraction of a cent is the point: a
15 * statement's lines, a receipt, the price book, per-token rates.
16 * - `compact` is an axis label: `$0`, `$0.50`, `$2`, `$1.2K`.
17 *
18 * Pure, so it is tested on its own; it imports nothing.
19 */
20
21/** Millionths of a dollar in one dollar, as `@g1t/contracts` `MICROS_PER_DOLLAR`; here so this file imports nothing. */
22export const MICROS_PER_DOLLAR = 1_000_000;
23
24export type MoneyOptions = {
25 /** Up to four places, trailing zeros trimmed to two: `$0.0063`, `$0.25`. */
26 precise?: boolean;
27 /** An axis label: whole dollars without places, thousands as `$1.2K`. */
28 compact?: boolean;
29};
30
31const MINUS = "−";
32
33/** `micros` as whole micros, rounded half up at `unit` (10_000 for a cent, 100 for a hundredth of one), as a count of units. Non-negative input. */
34function unitsOf(micros: number, unit: number): number {
35 return Math.floor((micros + unit / 2) / unit);
36}
37
38/** Thousands separated: `1234567` as `1,234,567`. */
39function grouped(whole: number): string {
40 return String(whole).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
41}
42
43/** `units` of `scale` places as `whole.fraction`, the fraction trimmed down to `min` places. */
44function withPlaces(units: number, places: number, min: number): string {
45 const per = 10 ** places;
46 const whole = Math.floor(units / per);
47 let fraction = String(units % per).padStart(places, "0");
48 while (fraction.length > min && fraction.endsWith("0")) fraction = fraction.slice(0, -1);
49 return fraction ? `${grouped(whole)}.${fraction}` : grouped(whole);
50}
51
52/** Dollars, to the cent. See the file's notes for `precise` and `compact`. */
53export function money(micros: number, options: MoneyOptions = {}): string {
54 const sign = micros < 0 ? MINUS : "";
55 // Whole micros only: a float that reached here is rounded before anything else.
56 const abs = Math.round(Math.abs(micros));
57 if (abs === 0) return options.compact ? "$0" : "$0.00";
58 if (options.compact) {
59 if (abs >= 1_000_000 * MICROS_PER_DOLLAR) return `${sign}$${withPlaces(unitsOf(abs, 100_000 * MICROS_PER_DOLLAR), 1, 0)}M`;
60 if (abs >= 1_000 * MICROS_PER_DOLLAR) return `${sign}$${withPlaces(unitsOf(abs, 100 * MICROS_PER_DOLLAR), 1, 0)}K`;
61 if (abs % MICROS_PER_DOLLAR === 0) return `${sign}$${grouped(abs / MICROS_PER_DOLLAR)}`;
62 return `${sign}$${withPlaces(unitsOf(abs, 100), 4, 2)}`;
63 }
64 if (options.precise) {
65 const units = unitsOf(abs, 100);
66 return units === 0 ? `<${sign}$0.0001` : `${sign}$${withPlaces(units, 4, 2)}`;
67 }
68 if (abs < 10_000) return `<${sign}$0.01`;
69 return `${sign}$${withPlaces(unitsOf(abs, 10_000), 2, 2)}`;
70}
71
72/** Whole dollars when they are whole, else to the cent: `$1,000`, `$0.10`. The price book's and the prepay presets' style. */
73export function wholeDollars(micros: number): string {
74 const abs = Math.round(Math.abs(micros));
75 if (abs % MICROS_PER_DOLLAR !== 0) return money(micros);
76 return `${micros < 0 ? MINUS : ""}$${grouped(abs / MICROS_PER_DOLLAR)}`;
77}
78
79/**
80 * Dollars for a form field, with no sign or symbol, to the cent: `12.50`;
81 * with `whole`, `20` for a whole sum. Rounded the same way as `money`.
82 */
83export function plainDollars(micros: number, options: { whole?: boolean } = {}): string {
84 const abs = Math.round(Math.abs(micros));
85 const cents = unitsOf(abs, 10_000);
86 const text = options.whole && cents % 100 === 0 ? String(cents / 100) : `${Math.floor(cents / 100)}.${String(cents % 100).padStart(2, "0")}`;
87 return micros < 0 ? `-${text}` : text;
88}
89
90/** A sum in dollars (a float from a service that counts in dollars) as whole micros. */
91export function microsOf(dollars: number): number {
92 return Math.round(dollars * MICROS_PER_DOLLAR);
93}