@quenty/metricutils 2.4.3 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +16 -54
  2. package/README.md +80 -2
  3. package/deploy.nevermore.json +10 -0
  4. package/package.json +22 -4
  5. package/src/Shared/Definitions/MetricUnitDefinitions.lua +1082 -0
  6. package/src/Shared/Definitions/MetricUnitDefinitions.spec.lua +55 -0
  7. package/src/Shared/Definitions/MetricUnitDimensionUtils.lua +164 -0
  8. package/src/Shared/Definitions/MetricUnitDimensionUtils.spec.lua +49 -0
  9. package/src/Shared/Definitions/MetricUnitKinds.lua +237 -0
  10. package/src/Shared/Definitions/MetricUnitKinds.spec.lua +51 -0
  11. package/src/Shared/Formatting/MetricUnitDisplayUnitUtils.lua +129 -0
  12. package/src/Shared/Formatting/MetricUnitFormatUtils.lua +924 -0
  13. package/src/Shared/Formatting/MetricUnitFormatUtils.spec.lua +280 -0
  14. package/src/Shared/Formatting/RxMetricUnitFormatUtils.lua +62 -0
  15. package/src/Shared/Localization/MetricUnitFallbackStrings.lua +289 -0
  16. package/src/Shared/Localization/MetricUnitFallbackStrings.spec.lua +82 -0
  17. package/src/Shared/Localization/MetricUnitTranslator/ar.json.txt +182 -0
  18. package/src/Shared/Localization/MetricUnitTranslator/de.json.txt +182 -0
  19. package/src/Shared/Localization/MetricUnitTranslator/en.json.txt +182 -0
  20. package/src/Shared/Localization/MetricUnitTranslator/es.json.txt +182 -0
  21. package/src/Shared/Localization/MetricUnitTranslator/fr.json.txt +182 -0
  22. package/src/Shared/Localization/MetricUnitTranslator/id.json.txt +182 -0
  23. package/src/Shared/Localization/MetricUnitTranslator/init.lua +92 -0
  24. package/src/Shared/Localization/MetricUnitTranslator/it.json.txt +182 -0
  25. package/src/Shared/Localization/MetricUnitTranslator/ja.json.txt +182 -0
  26. package/src/Shared/Localization/MetricUnitTranslator/ko.json.txt +182 -0
  27. package/src/Shared/Localization/MetricUnitTranslator/pl.json.txt +182 -0
  28. package/src/Shared/Localization/MetricUnitTranslator/pt.json.txt +182 -0
  29. package/src/Shared/Localization/MetricUnitTranslator/ru.json.txt +182 -0
  30. package/src/Shared/Localization/MetricUnitTranslator/th.json.txt +182 -0
  31. package/src/Shared/Localization/MetricUnitTranslator/tr.json.txt +182 -0
  32. package/src/Shared/Localization/MetricUnitTranslator/vi.json.txt +182 -0
  33. package/src/Shared/Localization/MetricUnitTranslator/zh-cn.json.txt +182 -0
  34. package/src/Shared/Localization/MetricUnitTranslator/zh-tw.json.txt +182 -0
  35. package/src/Shared/Localization/MetricUnitTranslator.spec.lua +309 -0
  36. package/src/Shared/Localization/PluralCategoryUtils.lua +103 -0
  37. package/src/Shared/Localization/PluralCategoryUtils.spec.lua +77 -0
  38. package/src/Shared/Logarithmic/DecibelUtils.lua +72 -0
  39. package/src/Shared/Logarithmic/DecibelUtils.spec.lua +35 -0
  40. package/src/Shared/MetricUnit.Angles.spec.lua +80 -0
  41. package/src/Shared/MetricUnit.Customary.spec.lua +104 -0
  42. package/src/Shared/MetricUnit.Derived.spec.lua +173 -0
  43. package/src/Shared/MetricUnit.Display.spec.lua +153 -0
  44. package/src/Shared/MetricUnit.Electrical.spec.lua +114 -0
  45. package/src/Shared/MetricUnit.Math.spec.lua +112 -0
  46. package/src/Shared/MetricUnit.Roblox.spec.lua +34 -0
  47. package/src/Shared/MetricUnit.SI.spec.lua +127 -0
  48. package/src/Shared/MetricUnit.Spring.spec.lua +94 -0
  49. package/src/Shared/MetricUnit.Temperature.spec.lua +212 -0
  50. package/src/Shared/MetricUnit.Time.spec.lua +202 -0
  51. package/src/Shared/MetricUnit.Units.spec.lua +210 -0
  52. package/src/Shared/MetricUnit.lua +1730 -0
  53. package/src/Shared/MetricUnit.spec.lua +362 -0
  54. package/src/Shared/MetricUtils.lua +47 -16
  55. package/src/Shared/MetricUtils.spec.lua +76 -0
  56. package/src/Shared/Parsing/MetricUnitParseUtils.lua +799 -0
  57. package/src/Shared/Parsing/MetricUnitParseUtils.spec.lua +189 -0
  58. package/src/Shared/Systems/MetricUnitSystem.lua +322 -0
  59. package/src/Shared/Systems/MetricUnitSystem.spec.lua +181 -0
  60. package/src/Shared/Systems/MetricUnitSystems.lua +225 -0
  61. package/src/jest.config.lua +3 -0
  62. package/src/node_modules.project.json +7 -0
  63. package/test/default.project.json +25 -0
  64. package/test/scripts/Server/ServerMain.server.lua +14 -0
@@ -0,0 +1,1082 @@
1
+ --!strict
2
+ --[=[
3
+ Every unit [MetricUnit] can be made in, read in, parsed from and written in. This list is where a unit is
4
+ declared: its id, dimensions, size in SI base units, symbol, measurement system, English names and the other
5
+ text it may be typed as. [MetricUnit], [MetricUnitFormatUtils], [MetricUnitParseUtils] and
6
+ [MetricUnitFallbackStrings] all read it. Symbols are the SI ones (or the customary ones for US and imperial
7
+ units), which do not change with the locale.
8
+
9
+ To add a unit:
10
+
11
+ 1. Add a row to the `define` call for its dimension and system below. See [UnitRow] for the fields. Give an SI
12
+ unit `prefixes` rather than adding its prefixed forms: `kilo`, `nano` and the rest are made from it.
13
+ 2. Add its id to the `UnitId` union, with any prefixed forms games commonly name. luau-lsp rejects a row whose
14
+ id is missing from it; other prefixed ids work at runtime.
15
+ 3. Add it to a system's display units in [MetricUnitSystems] if it should be picked by size.
16
+ 4. If it is written by name, add `units.<id>.long.<category>` and `units.<id>.short.<category>` for all six
17
+ plural categories to every `Localization/MetricUnitTranslator/<locale>.json.txt`: English from this row, the
18
+ other languages from CLDR's unit patterns. Prefixed forms need no entry; they are written from the
19
+ `prefixes` section and the unit's own entry.
20
+ 5. Only for a unit games construct constantly, add a named constructor on [MetricUnit].
21
+
22
+ The specs catch a named unit without English names or translation keys, and a unit that does not round-trip
23
+ through [MetricUnit.new] and [MetricUnit.In].
24
+
25
+ @class MetricUnitDefinitions
26
+ ]=]
27
+
28
+ local require = require(script.Parent.loader).load(script)
29
+
30
+ local MetricUnitDimensionUtils = require("MetricUnitDimensionUtils")
31
+ local MetricUnitKinds = require("MetricUnitKinds")
32
+ local Time = require("Time")
33
+
34
+ local MetricUnitDefinitions = {}
35
+
36
+ --[=[
37
+ A unit's id: the singular, camelCase name of the unit. Every id has an entry in [MetricUnitDefinitions]; reading,
38
+ formatting and [MetricUnit.new] all take it, so a typo is a type error.
39
+
40
+ @type UnitId "microsecond" | "millisecond" | "second" | "minute" | "hour" | "day" | "week" | "month" | "quarter" | "year" | "perSecond" | "perMinute" | "perHour" | "perDay" | "perWeek" | "perMonth" | "perYear" | "hertz" | "percent" | "permille" | "millimeter" | "centimeter" | "meter" | "kilometer" | "inch" | "foot" | "yard" | "mile" | "stud" | "squareMillimeter" | "squareCentimeter" | "squareMeter" | "hectare" | "squareKilometer" | "squareFoot" | "acre" | "milliliter" | "liter" | "cubicMeter" | "usFluidOunce" | "usGallon" | "imperialGallon" | "bit" | "kilobit" | "megabit" | "gigabit" | "byte" | "kilobyte" | "megabyte" | "gigabyte" | "kibibyte" | "mebibyte" | "gibibyte" | "bitPerSecond" | "kilobitPerSecond" | "megabitPerSecond" | "gigabitPerSecond" | "bytePerSecond" | "kilobytePerSecond" | "milligram" | "gram" | "kilogram" | "tonne" | "ounce" | "pound" | "stone" | "robloxMassUnit" | "meterPerSecond" | "kilometerPerHour" | "milePerHour" | "knot" | "studPerSecond" | "meterPerSecondSquared" | "studPerSecondSquared" | "newton" | "kilonewton" | "meganewton" | "poundForce" | "joule" | "kilojoule" | "megajoule" | "kilowattHour" | "kilocalorie" | "watt" | "kilowatt" | "megawatt" | "pascal" | "kilopascal" | "megapascal" | "bar" | "poundForcePerSquareInch" | "kilogramPerCubicMeter" | "gramPerCubicCentimeter" | "kelvin" | "celsius" | "fahrenheit" | "radian" | "degree" | "revolution" | "radianPerSecond" | "degreePerSecond" | "revolutionPerMinute" | "radianPerSecondSquared" | "microampere" | "milliampere" | "ampere" | "coulomb" | "millivolt" | "volt" | "kilovolt" | "ohm" | "kilohm" | "megohm" | "siemens" | "picofarad" | "nanofarad" | "microfarad" | "farad" | "microhenry" | "millihenry" | "henry" | "weber" | "millitesla" | "tesla" | "nanometer" | "micrometer" | "centiliter" | "deciliter" | "terabyte" | "petabyte" | "microgram" | "milliwatt" | "gigawatt" | "kilohertz" | "megahertz" | "gigahertz" | "becquerel" | "mole" | "millimole" | "micromole" | "kilomole" | "gramPerMole" | "molePerLiter" | "millimolePerLiter" | "katal" | "candela" | "steradian" | "lumen" | "lux" | "kilolux" | "gray" | "milligray" | "sievert" | "millisievert" | "microsievert" | "nauticalMile" | "literPer100Kilometers" | "squareInch" | "squareYard" | "squareMile" | "cubicInch" | "cubicFoot" | "cubicYard" | "usTeaspoon" | "usTablespoon" | "usCup" | "usPint" | "usQuart" | "imperialFluidOunce" | "imperialPint" | "imperialQuart" | "shortTon" | "footPerSecond" | "footPerSecondSquared" | "newtonMeter" | "britishThermalUnit" | "poundFoot" | "metricHorsepower" | "horsepower" | "inchOfMercury" | "poundPerCubicFoot" | "kilometerPerLiter" | "milePerUsGallon" | "milePerImperialGallon" | "rankine"
41
+ @within MetricUnitDefinitions
42
+ ]=]
43
+ export type UnitId =
44
+ "microsecond"
45
+ | "millisecond"
46
+ | "second"
47
+ | "minute"
48
+ | "hour"
49
+ | "day"
50
+ | "week"
51
+ | "month"
52
+ | "quarter"
53
+ | "year"
54
+ | "perSecond"
55
+ | "perMinute"
56
+ | "perHour"
57
+ | "perDay"
58
+ | "perWeek"
59
+ | "perMonth"
60
+ | "perYear"
61
+ | "hertz"
62
+ | "percent"
63
+ | "permille"
64
+ | "millimeter"
65
+ | "centimeter"
66
+ | "meter"
67
+ | "kilometer"
68
+ | "inch"
69
+ | "foot"
70
+ | "yard"
71
+ | "mile"
72
+ | "stud"
73
+ | "squareMillimeter"
74
+ | "squareCentimeter"
75
+ | "squareMeter"
76
+ | "hectare"
77
+ | "squareKilometer"
78
+ | "squareFoot"
79
+ | "acre"
80
+ | "milliliter"
81
+ | "liter"
82
+ | "cubicMeter"
83
+ | "usFluidOunce"
84
+ | "usGallon"
85
+ | "imperialGallon"
86
+ | "bit"
87
+ | "kilobit"
88
+ | "megabit"
89
+ | "gigabit"
90
+ | "byte"
91
+ | "kilobyte"
92
+ | "megabyte"
93
+ | "gigabyte"
94
+ | "kibibyte"
95
+ | "mebibyte"
96
+ | "gibibyte"
97
+ | "bitPerSecond"
98
+ | "kilobitPerSecond"
99
+ | "megabitPerSecond"
100
+ | "gigabitPerSecond"
101
+ | "bytePerSecond"
102
+ | "kilobytePerSecond"
103
+ | "milligram"
104
+ | "gram"
105
+ | "kilogram"
106
+ | "tonne"
107
+ | "ounce"
108
+ | "pound"
109
+ | "stone"
110
+ | "robloxMassUnit"
111
+ | "meterPerSecond"
112
+ | "kilometerPerHour"
113
+ | "milePerHour"
114
+ | "knot"
115
+ | "studPerSecond"
116
+ | "meterPerSecondSquared"
117
+ | "studPerSecondSquared"
118
+ | "newton"
119
+ | "kilonewton"
120
+ | "meganewton"
121
+ | "poundForce"
122
+ | "joule"
123
+ | "kilojoule"
124
+ | "megajoule"
125
+ | "kilowattHour"
126
+ | "kilocalorie"
127
+ | "watt"
128
+ | "kilowatt"
129
+ | "megawatt"
130
+ | "pascal"
131
+ | "kilopascal"
132
+ | "megapascal"
133
+ | "bar"
134
+ | "poundForcePerSquareInch"
135
+ | "kilogramPerCubicMeter"
136
+ | "gramPerCubicCentimeter"
137
+ | "kelvin"
138
+ | "celsius"
139
+ | "fahrenheit"
140
+ | "radian"
141
+ | "degree"
142
+ | "revolution"
143
+ | "radianPerSecond"
144
+ | "degreePerSecond"
145
+ | "revolutionPerMinute"
146
+ | "radianPerSecondSquared"
147
+ | "microampere"
148
+ | "milliampere"
149
+ | "ampere"
150
+ | "coulomb"
151
+ | "millivolt"
152
+ | "volt"
153
+ | "kilovolt"
154
+ | "ohm"
155
+ | "kilohm"
156
+ | "megohm"
157
+ | "siemens"
158
+ | "picofarad"
159
+ | "nanofarad"
160
+ | "microfarad"
161
+ | "farad"
162
+ | "microhenry"
163
+ | "millihenry"
164
+ | "henry"
165
+ | "weber"
166
+ | "millitesla"
167
+ | "tesla"
168
+ | "nanometer"
169
+ | "micrometer"
170
+ | "centiliter"
171
+ | "deciliter"
172
+ | "terabyte"
173
+ | "petabyte"
174
+ | "microgram"
175
+ | "milliwatt"
176
+ | "gigawatt"
177
+ | "kilohertz"
178
+ | "megahertz"
179
+ | "gigahertz"
180
+ | "becquerel"
181
+ | "mole"
182
+ | "millimole"
183
+ | "micromole"
184
+ | "kilomole"
185
+ | "gramPerMole"
186
+ | "molePerLiter"
187
+ | "millimolePerLiter"
188
+ | "katal"
189
+ | "candela"
190
+ | "steradian"
191
+ | "lumen"
192
+ | "lux"
193
+ | "kilolux"
194
+ | "gray"
195
+ | "milligray"
196
+ | "sievert"
197
+ | "millisievert"
198
+ | "microsievert"
199
+ | "nauticalMile"
200
+ | "literPer100Kilometers"
201
+ | "squareInch"
202
+ | "squareYard"
203
+ | "squareMile"
204
+ | "cubicInch"
205
+ | "cubicFoot"
206
+ | "cubicYard"
207
+ | "usTeaspoon"
208
+ | "usTablespoon"
209
+ | "usCup"
210
+ | "usPint"
211
+ | "usQuart"
212
+ | "imperialFluidOunce"
213
+ | "imperialPint"
214
+ | "imperialQuart"
215
+ | "shortTon"
216
+ | "footPerSecond"
217
+ | "footPerSecondSquared"
218
+ | "newtonMeter"
219
+ | "britishThermalUnit"
220
+ | "poundFoot"
221
+ | "metricHorsepower"
222
+ | "horsepower"
223
+ | "inchOfMercury"
224
+ | "poundPerCubicFoot"
225
+ | "kilometerPerLiter"
226
+ | "milePerUsGallon"
227
+ | "milePerImperialGallon"
228
+ | "rankine"
229
+
230
+ --[=[
231
+ A unit id, any of `@quenty/time`'s names for a time unit, such as `"hours"` or `"h"`, or a unit itself, such as
232
+ `MINI:GetUnit("stud")` from a [MetricUnitSystem] of the game's own scale.
233
+
234
+ @type UnitLike UnitId | Time.TimeUnit | UnitDefinition
235
+ @within MetricUnitDefinitions
236
+ ]=]
237
+ export type UnitLike = UnitId | Time.TimeUnit | UnitDefinition
238
+
239
+ --[=[
240
+ The name of the [MetricUnitSystem] that declares a unit: `"metric"`, `"usCustomary"`, `"imperial"`,
241
+ `"usCustomaryAndImperial"` for the units the United States and Britain share (the inch, mile, pound, mph, BTU,
242
+ horsepower), `"roblox"`, or a game's own system. Time units and bare rates belong to none, since every system
243
+ shares them.
244
+
245
+ @type UnitFamily string
246
+ @within MetricUnitDefinitions
247
+ ]=]
248
+ export type UnitFamily = string
249
+
250
+ --[=[
251
+ A singular and plural form of a unit's English name or phrase.
252
+
253
+ @interface UnitForms
254
+ .one string
255
+ .other string
256
+ @within MetricUnitDefinitions
257
+ ]=]
258
+ export type UnitForms = { one: string, other: string }
259
+
260
+ --[=[
261
+ One unit.
262
+
263
+ - `factor` is its size in SI base units: seconds, metres, kilograms, bytes, kelvin, radians, amperes, moles or
264
+ candelas, or their product for a derived unit, so a newton is 1 (kg·m/s²) and a kilowatt-hour 3,600,000 (J).
265
+ - `names` is its American English name, for units written by name. Commonwealth spelling (metre, litre, tonne)
266
+ is derived.
267
+ - `shortTemplates` replaces the default short phrase, the symbol after a space, where that reads wrongly
268
+ (`45%`, `45°`, `1 stud`). `%s` is the number.
269
+ - `duration` marks the time units, which durations are written in.
270
+ - `prefix` is set on a unit made from a prefix and another unit, such as `kilometer`.
271
+
272
+ The other fields are as in [UnitRow].
273
+
274
+ @interface UnitDefinition
275
+ .id UnitId
276
+ .dimensions Dimensions
277
+ .factor number
278
+ .symbol string
279
+ .system UnitFamily?
280
+ .names UnitForms?
281
+ .shortTemplates UnitForms?
282
+ .aliases { string }?
283
+ .zero number?
284
+ .per UnitId?
285
+ .duration boolean?
286
+ .prefix UnitPrefixOf?
287
+ @within MetricUnitDefinitions
288
+ ]=]
289
+ export type UnitDefinition = {
290
+ id: UnitId,
291
+ dimensions: MetricUnitDimensionUtils.Dimensions,
292
+ factor: number,
293
+ symbol: string,
294
+ system: UnitFamily?,
295
+ names: UnitForms?,
296
+ shortTemplates: UnitForms?,
297
+ aliases: { string }?,
298
+ zero: number?,
299
+ per: UnitId?,
300
+ duration: boolean?,
301
+ prefix: UnitPrefixOf?,
302
+ }
303
+
304
+ --[=[
305
+ For a unit made by prefixing another, the prefix's name and the unit it prefixes: `kilometer` is `kilo` and
306
+ `meter`. A translator writes it from those two when it has no entry for the unit itself.
307
+
308
+ @interface UnitPrefixOf
309
+ .name string
310
+ .base UnitId
311
+ @within MetricUnitDefinitions
312
+ ]=]
313
+ export type UnitPrefixOf = { name: string, base: UnitId }
314
+
315
+ --[=[
316
+ One row of the list below, for a unit of the kind (`"length"`, `"speed"`) and system its `define` call gives.
317
+
318
+ - `factor` is its size in SI base units.
319
+ - `name` is its American English singular, for units written by name; `plural` defaults to `name` plus "s".
320
+ Time units are written with `@quenty/time`'s words and bare rates with per phrases, so they have none.
321
+ - `short` replaces the symbol-after-a-space short phrase, as one template or a singular and plural.
322
+ - `aliases` is other text it may be typed as when parsed, such as `"kph"`.
323
+ - `zero` is where the unit's scale starts, in base units. A unit with one is a scale whose readings are points:
324
+ [MetricUnit.new] makes absolute values in it. Only the temperature scales have one.
325
+ - `per` is the time unit a bare rate such as `perHour` counts per. Hertz is a rate too, but is written by name.
326
+ - `prefixes` makes a unit for each prefix of a [PrefixSet]: `meter` with `"si"` makes `kilometer`, `nanometer`
327
+ and the rest, with names, symbols and aliases from the unit's. A unit declared outright keeps its own row.
328
+ - `elidesPrefixVowel` drops a prefix's final vowel before the unit, as SI writes `kilohm` and `megohm`.
329
+
330
+ @interface UnitRow
331
+ .id UnitId
332
+ .factor number
333
+ .symbol string
334
+ .name string?
335
+ .plural string?
336
+ .short (string | UnitForms)?
337
+ .aliases { string }?
338
+ .zero number?
339
+ .per UnitId?
340
+ .prefixes PrefixSet?
341
+ .elidesPrefixVowel boolean?
342
+ @within MetricUnitDefinitions
343
+ ]=]
344
+ export type UnitRow = {
345
+ id: UnitId,
346
+ factor: number,
347
+ symbol: string,
348
+ name: string?,
349
+ plural: string?,
350
+ short: (string | UnitForms)?,
351
+ aliases: { string }?,
352
+ zero: number?,
353
+ per: UnitId?,
354
+ prefixes: PrefixSet?,
355
+ elidesPrefixVowel: boolean?,
356
+ }
357
+
358
+ --[=[
359
+ A metric or binary prefix: `kilo` is `k` and 1000.
360
+
361
+ @interface Prefix
362
+ .name string
363
+ .symbol string
364
+ .factor number
365
+ @within MetricUnitDefinitions
366
+ ]=]
367
+ export type Prefix = { name: string, symbol: string, factor: number }
368
+
369
+ --[=[
370
+ Which prefixes a unit takes: `"si"` all twenty-four SI prefixes, `"siMultiples"` only those of 10 and above (no
371
+ millibytes), and `"siMultiplesAndBinary"` those and the binary ones (`KiB`, `MiB`).
372
+
373
+ @type PrefixSet "si" | "siMultiples" | "siMultiplesAndBinary"
374
+ @within MetricUnitDefinitions
375
+ ]=]
376
+ export type PrefixSet = "si" | "siMultiples" | "siMultiplesAndBinary"
377
+
378
+ --[=[
379
+ Metres in a Roblox stud, as Roblox documents it against the default avatar: 28 cm.
380
+
381
+ @prop ROBLOX_METERS_PER_STUD number
382
+ @within MetricUnitDefinitions
383
+ ]=]
384
+ MetricUnitDefinitions.ROBLOX_METERS_PER_STUD = 0.28
385
+
386
+ -- stylua: ignore
387
+ local SI_PREFIXES: { Prefix } = {
388
+ { name = "quecto", symbol = "q", factor = 1e-30 }, { name = "ronto", symbol = "r", factor = 1e-27 },
389
+ { name = "yocto", symbol = "y", factor = 1e-24 }, { name = "zepto", symbol = "z", factor = 1e-21 },
390
+ { name = "atto", symbol = "a", factor = 1e-18 }, { name = "femto", symbol = "f", factor = 1e-15 },
391
+ { name = "pico", symbol = "p", factor = 1e-12 }, { name = "nano", symbol = "n", factor = 1e-9 },
392
+ { name = "micro", symbol = "µ", factor = 1e-6 }, { name = "milli", symbol = "m", factor = 1e-3 },
393
+ { name = "centi", symbol = "c", factor = 1e-2 }, { name = "deci", symbol = "d", factor = 1e-1 },
394
+ { name = "deca", symbol = "da", factor = 1e1 }, { name = "hecto", symbol = "h", factor = 1e2 },
395
+ { name = "kilo", symbol = "k", factor = 1e3 }, { name = "mega", symbol = "M", factor = 1e6 },
396
+ { name = "giga", symbol = "G", factor = 1e9 }, { name = "tera", symbol = "T", factor = 1e12 },
397
+ { name = "peta", symbol = "P", factor = 1e15 }, { name = "exa", symbol = "E", factor = 1e18 },
398
+ { name = "zetta", symbol = "Z", factor = 1e21 }, { name = "yotta", symbol = "Y", factor = 1e24 },
399
+ { name = "ronna", symbol = "R", factor = 1e27 }, { name = "quetta", symbol = "Q", factor = 1e30 },
400
+ }
401
+
402
+ -- stylua: ignore
403
+ local BINARY_PREFIXES: { Prefix } = {
404
+ { name = "kibi", symbol = "Ki", factor = 1024 }, { name = "mebi", symbol = "Mi", factor = 1024 ^ 2 },
405
+ { name = "gibi", symbol = "Gi", factor = 1024 ^ 3 }, { name = "tebi", symbol = "Ti", factor = 1024 ^ 4 },
406
+ { name = "pebi", symbol = "Pi", factor = 1024 ^ 5 }, { name = "exbi", symbol = "Ei", factor = 1024 ^ 6 },
407
+ { name = "zebi", symbol = "Zi", factor = 1024 ^ 7 }, { name = "yobi", symbol = "Yi", factor = 1024 ^ 8 },
408
+ }
409
+
410
+ local SI_MULTIPLES: { Prefix } = {}
411
+ for _, prefix in SI_PREFIXES do
412
+ if prefix.factor > 1 then
413
+ table.insert(SI_MULTIPLES, prefix)
414
+ end
415
+ end
416
+
417
+ local SI_MULTIPLES_AND_BINARY: { Prefix } = table.clone(SI_MULTIPLES)
418
+ table.move(BINARY_PREFIXES, 1, #BINARY_PREFIXES, #SI_MULTIPLES_AND_BINARY + 1, SI_MULTIPLES_AND_BINARY)
419
+
420
+ local PREFIX_SETS: { [PrefixSet]: { Prefix } } = {
421
+ si = SI_PREFIXES,
422
+ siMultiples = SI_MULTIPLES,
423
+ siMultiplesAndBinary = SI_MULTIPLES_AND_BINARY,
424
+ }
425
+
426
+ local ALL_PREFIXES: { Prefix } = table.clone(SI_PREFIXES)
427
+ table.move(BINARY_PREFIXES, 1, #BINARY_PREFIXES, #ALL_PREFIXES + 1, ALL_PREFIXES)
428
+
429
+ local QUENTY_TIME_UNIT_ALIASES: { [string]: string } = table.freeze({
430
+ milliseconds = "millisecond",
431
+ ms = "millisecond",
432
+ seconds = "second",
433
+ s = "second",
434
+ minutes = "minute",
435
+ m = "minute",
436
+ hours = "hour",
437
+ h = "hour",
438
+ days = "day",
439
+ d = "day",
440
+ D = "day",
441
+ date = "day",
442
+ weeks = "week",
443
+ w = "week",
444
+ months = "month",
445
+ M = "month",
446
+ quarters = "quarter",
447
+ Q = "quarter",
448
+ years = "year",
449
+ y = "year",
450
+ }) :: any
451
+
452
+ local function toDefinition(kind: MetricUnitKinds.KindName, system: UnitFamily?, row: UnitRow): UnitDefinition
453
+ local dimensions = MetricUnitKinds.fromName(kind)
454
+ local name = row.name
455
+ local short = row.short
456
+ return {
457
+ id = row.id,
458
+ dimensions = dimensions,
459
+ factor = row.factor,
460
+ symbol = row.symbol,
461
+ system = system,
462
+ names = if name then { one = name, other = row.plural or name .. "s" } else nil,
463
+ shortTemplates = if type(short) == "string" then { one = short, other = short } else short,
464
+ aliases = row.aliases,
465
+ zero = row.zero,
466
+ per = row.per,
467
+ duration = if MetricUnitDimensionUtils.equal(dimensions, MetricUnitKinds.TIME) then true else nil,
468
+ }
469
+ end
470
+
471
+ local function withPrefix(prefix: Prefix, word: string, elidesPrefixVowel: boolean?): string
472
+ local lastLetter = string.sub(prefix.name, -1)
473
+ if elidesPrefixVowel and (lastLetter == "a" or lastLetter == "o") then
474
+ return string.sub(prefix.name, 1, -2) .. word
475
+ end
476
+ return prefix.name .. word
477
+ end
478
+
479
+ local function prefixedAliases(prefix: Prefix, base: UnitDefinition): { string }?
480
+ local aliases: { string } = {}
481
+ if prefix.name == "micro" then
482
+ table.insert(aliases, "u" .. base.symbol)
483
+ end
484
+ local baseAliases: { string } = base.aliases or {}
485
+ for _, alias in baseAliases do
486
+ local isWord = string.match(alias, "^%a%a%a+$") ~= nil
487
+ table.insert(aliases, if isWord then prefix.name .. alias else prefix.symbol .. alias)
488
+ end
489
+ return if #aliases > 0 then aliases else nil
490
+ end
491
+
492
+ local function toPrefixedDefinition(prefix: Prefix, base: UnitDefinition, elidesPrefixVowel: boolean?): UnitDefinition
493
+ local names = base.names
494
+ return {
495
+ id = withPrefix(prefix, base.id, elidesPrefixVowel) :: any,
496
+ dimensions = base.dimensions,
497
+ factor = prefix.factor * base.factor,
498
+ symbol = prefix.symbol .. base.symbol,
499
+ system = base.system,
500
+ names = if names
501
+ then {
502
+ one = withPrefix(prefix, names.one, elidesPrefixVowel),
503
+ other = withPrefix(prefix, names.other, elidesPrefixVowel),
504
+ }
505
+ else nil,
506
+ aliases = prefixedAliases(prefix, base),
507
+ prefix = { name = prefix.name, base = base.id },
508
+ }
509
+ end
510
+
511
+ type Define = (kind: MetricUnitKinds.KindName, system: UnitFamily?, rows: { UnitRow }) -> ()
512
+
513
+ local function robloxRows(metersPerStud: number): { { kind: MetricUnitKinds.KindName, row: UnitRow } }
514
+ local waterKilogramsPerCubicMeter = 1_000
515
+ -- stylua: ignore
516
+ return {
517
+ { kind = "length", row = { id = "stud", factor = metersPerStud, symbol = "stud", name = "stud", short = { one = "%s stud", other = "%s studs" } } },
518
+ { kind = "mass", row = { id = "robloxMassUnit", factor = metersPerStud ^ 3 * waterKilogramsPerCubicMeter, symbol = "RMU", name = "Roblox mass unit" } },
519
+ { kind = "speed", row = { id = "studPerSecond", factor = metersPerStud, symbol = "stud/s", name = "stud per second", plural = "studs per second", short = { one = "%s stud/s", other = "%s studs/s" }, aliases = { "studs/s" } } },
520
+ { kind = "acceleration", row = { id = "studPerSecondSquared", factor = metersPerStud, symbol = "stud/s²", name = "stud per second squared", plural = "studs per second squared", short = { one = "%s stud/s²", other = "%s studs/s²" }, aliases = { "studs/s²", "studs/s2" } } },
521
+ }
522
+ end
523
+
524
+ local DAY = 86_400
525
+ -- Julian year, an elapsed-time average for long-range math; calendar years are @quenty/time's Time.add
526
+ local JULIAN_YEAR = 365.25 * DAY
527
+ local AVERAGE_MONTH = JULIAN_YEAR / 12
528
+ local INCH = 0.0254
529
+ local MILE = 1_609.344
530
+ local FOOT = 12 * INCH
531
+ local US_GALLON = 3.785411784e-3
532
+ local IMPERIAL_GALLON = 4.54609e-3
533
+ local POUND = 0.45359237
534
+ local STANDARD_GRAVITY = 9.80665
535
+ local DEGREE = math.pi / 180
536
+ local REVOLUTION = 2 * math.pi
537
+ local CELSIUS_ZERO = 273.15
538
+ local BIT = 1 / 8
539
+ local POUND_FORCE = POUND * STANDARD_GRAVITY
540
+
541
+ -- stylua: ignore
542
+ local function declareWellKnownUnits(define: Define)
543
+ define("time", nil, {
544
+ { id = "microsecond", factor = 1e-6, symbol = "µs", name = "microsecond", aliases = { "us" } },
545
+ { id = "millisecond", factor = 1e-3, symbol = "ms", aliases = { "msec", "msecs" } },
546
+ { id = "second", factor = 1, symbol = "s", aliases = { "sec", "secs" } },
547
+ { id = "minute", factor = 60, symbol = "min", aliases = { "mins" } },
548
+ { id = "hour", factor = 3_600, symbol = "h", aliases = { "hr", "hrs" } },
549
+ { id = "day", factor = DAY, symbol = "d" },
550
+ { id = "week", factor = 7 * DAY, symbol = "wk", aliases = { "wks" } },
551
+ { id = "month", factor = AVERAGE_MONTH, symbol = "mo", aliases = { "mos" } },
552
+ { id = "quarter", factor = JULIAN_YEAR / 4, symbol = "qtr" },
553
+ { id = "year", factor = JULIAN_YEAR, symbol = "yr", aliases = { "yrs" } },
554
+ })
555
+
556
+ define("rate", nil, {
557
+ { id = "perSecond", factor = 1, symbol = "/s", per = "second" },
558
+ { id = "perMinute", factor = 1 / 60, symbol = "/min", per = "minute" },
559
+ { id = "perHour", factor = 1 / 3_600, symbol = "/h", per = "hour" },
560
+ { id = "perDay", factor = 1 / DAY, symbol = "/d", per = "day" },
561
+ { id = "perWeek", factor = 1 / (7 * DAY), symbol = "/wk", per = "week" },
562
+ { id = "perMonth", factor = 12 / JULIAN_YEAR, symbol = "/mo", per = "month" },
563
+ { id = "perYear", factor = 1 / JULIAN_YEAR, symbol = "/yr", per = "year" },
564
+ })
565
+ define("rate", "metric", {
566
+ { id = "hertz", factor = 1, symbol = "Hz", name = "hertz", plural = "hertz", prefixes = "si" },
567
+ { id = "becquerel", factor = 1, symbol = "Bq", name = "becquerel", prefixes = "si" },
568
+ })
569
+
570
+ -- Proportions
571
+ define("none", "metric", {
572
+ { id = "percent", factor = 1e-2, symbol = "%", name = "percent", plural = "percent", short = "%s%", aliases = { "pct" } },
573
+ { id = "permille", factor = 1e-3, symbol = "‰", name = "per mille", plural = "per mille", short = "%s‰" },
574
+ })
575
+
576
+ define("length", "metric", {
577
+ { id = "meter", factor = 1, symbol = "m", name = "meter", prefixes = "si" },
578
+ })
579
+ define("length", "usCustomaryAndImperial", {
580
+ { id = "inch", factor = INCH, symbol = "in", name = "inch", plural = "inches" },
581
+ { id = "foot", factor = FOOT, symbol = "ft", name = "foot", plural = "feet" },
582
+ { id = "yard", factor = 3 * FOOT, symbol = "yd", name = "yard" },
583
+ { id = "mile", factor = MILE, symbol = "mi", name = "mile" },
584
+ { id = "nauticalMile", factor = 1_852, symbol = "nmi", name = "nautical mile" },
585
+ })
586
+
587
+ define("area", "metric", {
588
+ { id = "squareMillimeter", factor = 1e-6, symbol = "mm²", name = "square millimeter", aliases = { "mm2" } },
589
+ { id = "squareCentimeter", factor = 1e-4, symbol = "cm²", name = "square centimeter", aliases = { "cm2" } },
590
+ { id = "squareMeter", factor = 1, symbol = "m²", name = "square meter", aliases = { "m2", "sq m" } },
591
+ { id = "hectare", factor = 1e4, symbol = "ha", name = "hectare" },
592
+ { id = "squareKilometer", factor = 1e6, symbol = "km²", name = "square kilometer", aliases = { "km2", "sq km" } },
593
+ })
594
+ define("area", "metric", {
595
+ { id = "literPer100Kilometers", factor = 1e-3 / 1e5, symbol = "L/100 km", name = "liter per 100 kilometers", plural = "liters per 100 kilometers", aliases = { "L/100km", "l/100km" } },
596
+ })
597
+ define("area", "usCustomaryAndImperial", {
598
+ { id = "squareInch", factor = INCH ^ 2, symbol = "in²", name = "square inch", plural = "square inches", aliases = { "in2", "sq in" } },
599
+ { id = "squareFoot", factor = FOOT ^ 2, symbol = "ft²", name = "square foot", plural = "square feet", aliases = { "ft2", "sq ft" } },
600
+ { id = "squareYard", factor = (3 * FOOT) ^ 2, symbol = "yd²", name = "square yard", aliases = { "yd2", "sq yd" } },
601
+ { id = "acre", factor = 4_046.8564224, symbol = "ac", name = "acre" },
602
+ { id = "squareMile", factor = MILE ^ 2, symbol = "mi²", name = "square mile", aliases = { "mi2", "sq mi" } },
603
+ })
604
+
605
+ define("volume", "metric", {
606
+ { id = "liter", factor = 1e-3, symbol = "L", name = "liter", aliases = { "l" }, prefixes = "si" },
607
+ { id = "cubicMeter", factor = 1, symbol = "m³", name = "cubic meter", aliases = { "m3" } },
608
+ })
609
+ define("volume", "usCustomaryAndImperial", {
610
+ { id = "cubicInch", factor = INCH ^ 3, symbol = "in³", name = "cubic inch", plural = "cubic inches", aliases = { "in3", "cu in" } },
611
+ { id = "cubicFoot", factor = FOOT ^ 3, symbol = "ft³", name = "cubic foot", plural = "cubic feet", aliases = { "ft3", "cu ft" } },
612
+ { id = "cubicYard", factor = (3 * FOOT) ^ 3, symbol = "yd³", name = "cubic yard", aliases = { "yd3", "cu yd" } },
613
+ })
614
+ define("volume", "usCustomary", {
615
+ { id = "usTeaspoon", factor = US_GALLON / 768, symbol = "tsp", name = "teaspoon" },
616
+ { id = "usTablespoon", factor = US_GALLON / 256, symbol = "tbsp", name = "tablespoon" },
617
+ { id = "usFluidOunce", factor = US_GALLON / 128, symbol = "US fl oz", name = "US fluid ounce", aliases = { "fl oz" } },
618
+ { id = "usCup", factor = US_GALLON / 16, symbol = "cup", name = "cup", short = { one = "%s cup", other = "%s cups" } },
619
+ { id = "usPint", factor = US_GALLON / 8, symbol = "US pt", name = "US pint", aliases = { "pt" } },
620
+ { id = "usQuart", factor = US_GALLON / 4, symbol = "US qt", name = "US quart", aliases = { "qt" } },
621
+ { id = "usGallon", factor = US_GALLON, symbol = "US gal", name = "US gallon", aliases = { "gal" } },
622
+ })
623
+ define("volume", "imperial", {
624
+ { id = "imperialFluidOunce", factor = IMPERIAL_GALLON / 160, symbol = "imp fl oz", name = "imperial fluid ounce" },
625
+ { id = "imperialPint", factor = IMPERIAL_GALLON / 8, symbol = "imp pt", name = "imperial pint" },
626
+ { id = "imperialQuart", factor = IMPERIAL_GALLON / 4, symbol = "imp qt", name = "imperial quart" },
627
+ { id = "imperialGallon", factor = IMPERIAL_GALLON, symbol = "imp gal", name = "imperial gallon" },
628
+ })
629
+
630
+ define("memory", "metric", {
631
+ { id = "bit", factor = BIT, symbol = "bit", name = "bit", prefixes = "siMultiples" },
632
+ { id = "byte", factor = 1, symbol = "B", name = "byte", prefixes = "siMultiplesAndBinary" },
633
+ { id = "kilobyte", factor = 1e3, symbol = "kB", name = "kilobyte", aliases = { "KB" } },
634
+ })
635
+
636
+ define("dataRate", "metric", {
637
+ { id = "bitPerSecond", factor = BIT, symbol = "bit/s", name = "bit per second", plural = "bits per second", aliases = { "bps" } },
638
+ { id = "kilobitPerSecond", factor = 1e3 * BIT, symbol = "kbit/s", name = "kilobit per second", plural = "kilobits per second", aliases = { "kbps" } },
639
+ { id = "megabitPerSecond", factor = 1e6 * BIT, symbol = "Mbit/s", name = "megabit per second", plural = "megabits per second", aliases = { "Mbps" } },
640
+ { id = "gigabitPerSecond", factor = 1e9 * BIT, symbol = "Gbit/s", name = "gigabit per second", plural = "gigabits per second", aliases = { "Gbps" } },
641
+ { id = "bytePerSecond", factor = 1, symbol = "B/s", name = "byte per second", plural = "bytes per second" },
642
+ { id = "kilobytePerSecond", factor = 1e3, symbol = "kB/s", name = "kilobyte per second", plural = "kilobytes per second", aliases = { "KB/s" } },
643
+ })
644
+
645
+ define("mass", "metric", {
646
+ { id = "gram", factor = 1e-3, symbol = "g", name = "gram", prefixes = "si" },
647
+ { id = "tonne", factor = 1e3, symbol = "t", name = "metric ton" },
648
+ })
649
+ define("mass", "usCustomaryAndImperial", {
650
+ { id = "ounce", factor = POUND / 16, symbol = "oz", name = "ounce" },
651
+ { id = "pound", factor = POUND, symbol = "lb", name = "pound", aliases = { "lbs" } },
652
+ })
653
+ define("mass", "usCustomary", {
654
+ { id = "shortTon", factor = 2_000 * POUND, symbol = "tn", name = "short ton", aliases = { "ton", "tons" } },
655
+ })
656
+ define("mass", "imperial", {
657
+ { id = "stone", factor = 14 * POUND, symbol = "st", name = "stone", plural = "stone" },
658
+ })
659
+
660
+ define("speed", "metric", {
661
+ { id = "meterPerSecond", factor = 1, symbol = "m/s", name = "meter per second", plural = "meters per second" },
662
+ { id = "kilometerPerHour", factor = 1e3 / 3_600, symbol = "km/h", name = "kilometer per hour", plural = "kilometers per hour", aliases = { "kph", "kmh", "km/hr" } },
663
+ })
664
+ define("speed", "usCustomaryAndImperial", {
665
+ { id = "milePerHour", factor = MILE / 3_600, symbol = "mph", name = "mile per hour", plural = "miles per hour", aliases = { "mi/h" } },
666
+ { id = "footPerSecond", factor = FOOT, symbol = "ft/s", name = "foot per second", plural = "feet per second", aliases = { "fps" } },
667
+ { id = "knot", factor = 1_852 / 3_600, symbol = "kn", name = "knot", aliases = { "kt" } },
668
+ })
669
+
670
+ define("acceleration", "metric", {
671
+ { id = "meterPerSecondSquared", factor = 1, symbol = "m/s²", name = "meter per second squared", plural = "meters per second squared", aliases = { "m/s2" } },
672
+ })
673
+ define("acceleration", "usCustomaryAndImperial", {
674
+ { id = "footPerSecondSquared", factor = FOOT, symbol = "ft/s²", name = "foot per second squared", plural = "feet per second squared", aliases = { "ft/s2" } },
675
+ })
676
+
677
+ define("force", "metric", {
678
+ { id = "newton", factor = 1, symbol = "N", name = "newton", prefixes = "si" },
679
+ })
680
+ define("force", "usCustomaryAndImperial", {
681
+ { id = "poundForce", factor = POUND_FORCE, symbol = "lbf", name = "pound-force", plural = "pounds-force" },
682
+ })
683
+
684
+ define("energy", "metric", {
685
+ { id = "joule", factor = 1, symbol = "J", name = "joule", prefixes = "si" },
686
+ { id = "kilowattHour", factor = 3.6e6, symbol = "kWh", name = "kilowatt-hour" },
687
+ { id = "kilocalorie", factor = 4_184, symbol = "kcal", name = "kilocalorie" },
688
+ { id = "newtonMeter", factor = 1, symbol = "N·m", name = "newton-meter", aliases = { "Nm", "N m" } },
689
+ })
690
+ define("energy", "usCustomaryAndImperial", {
691
+ { id = "britishThermalUnit", factor = 1_055.05585262, symbol = "Btu", name = "British thermal unit", aliases = { "BTU" } },
692
+ { id = "poundFoot", factor = POUND_FORCE * FOOT, symbol = "lbf·ft", name = "pound-foot", plural = "pound-feet", aliases = { "lb-ft", "lb·ft", "ft-lb", "ft·lbf" } },
693
+ })
694
+
695
+ define("power", "metric", {
696
+ { id = "watt", factor = 1, symbol = "W", name = "watt", prefixes = "si" },
697
+ { id = "metricHorsepower", factor = 735.49875, symbol = "PS", name = "metric horsepower", plural = "metric horsepower", aliases = { "CV", "ch" } },
698
+ })
699
+ define("power", "usCustomaryAndImperial", {
700
+ { id = "horsepower", factor = 550 * POUND_FORCE * FOOT, symbol = "hp", name = "horsepower", plural = "horsepower", aliases = { "bhp" } },
701
+ })
702
+
703
+ define("pressure", "metric", {
704
+ { id = "pascal", factor = 1, symbol = "Pa", name = "pascal", prefixes = "si" },
705
+ { id = "bar", factor = 1e5, symbol = "bar", name = "bar" },
706
+ })
707
+ define("pressure", "usCustomaryAndImperial", {
708
+ { id = "poundForcePerSquareInch", factor = POUND_FORCE / INCH ^ 2, symbol = "psi", name = "pound-force per square inch", plural = "pounds-force per square inch" },
709
+ { id = "inchOfMercury", factor = 3_386.389, symbol = "inHg", name = "inch of mercury", plural = "inches of mercury" },
710
+ })
711
+
712
+ define("density", "metric", {
713
+ { id = "kilogramPerCubicMeter", factor = 1, symbol = "kg/m³", name = "kilogram per cubic meter", plural = "kilograms per cubic meter", aliases = { "kg/m3" } },
714
+ { id = "gramPerCubicCentimeter", factor = 1e3, symbol = "g/cm³", name = "gram per cubic centimeter", plural = "grams per cubic centimeter", aliases = { "g/cm3" } },
715
+ })
716
+ define("density", "usCustomaryAndImperial", {
717
+ { id = "poundPerCubicFoot", factor = POUND / FOOT ^ 3, symbol = "lb/ft³", name = "pound per cubic foot", plural = "pounds per cubic foot", aliases = { "lb/ft3" } },
718
+ })
719
+
720
+ define("fuelEconomy", "metric", {
721
+ { id = "kilometerPerLiter", factor = 1e3 / 1e-3, symbol = "km/L", name = "kilometer per liter", plural = "kilometers per liter", aliases = { "kmpl" } },
722
+ })
723
+ define("fuelEconomy", "usCustomary", {
724
+ { id = "milePerUsGallon", factor = MILE / US_GALLON, symbol = "mpg (US)", name = "mile per US gallon", plural = "miles per US gallon", aliases = { "mpg" } },
725
+ })
726
+ define("fuelEconomy", "imperial", {
727
+ { id = "milePerImperialGallon", factor = MILE / IMPERIAL_GALLON, symbol = "mpg (UK)", name = "mile per imperial gallon", plural = "miles per imperial gallon", aliases = { "mpg UK", "mpg imp" } },
728
+ })
729
+
730
+ define("temperature", "metric", {
731
+ { id = "kelvin", factor = 1, symbol = "K", name = "kelvin", zero = 0 },
732
+ { id = "celsius", factor = 1, symbol = "°C", name = "degree Celsius", plural = "degrees Celsius", aliases = { "C", "degC", "°c" }, zero = CELSIUS_ZERO },
733
+ })
734
+ define("temperature", "usCustomaryAndImperial", {
735
+ { id = "fahrenheit", factor = 5 / 9, symbol = "°F", name = "degree Fahrenheit", plural = "degrees Fahrenheit", aliases = { "F", "degF", "°f" }, zero = 459.67 * 5 / 9 },
736
+ { id = "rankine", factor = 5 / 9, symbol = "°R", name = "degree Rankine", plural = "degrees Rankine", zero = 0 },
737
+ })
738
+
739
+ define("angle", "metric", {
740
+ { id = "radian", factor = 1, symbol = "rad", name = "radian", aliases = { "rads" } },
741
+ { id = "degree", factor = DEGREE, symbol = "°", name = "degree", short = "%s°", aliases = { "deg", "degs" } },
742
+ { id = "revolution", factor = REVOLUTION, symbol = "rev", name = "revolution", aliases = { "turn", "turns", "revs" } },
743
+ })
744
+
745
+ define("angularVelocity", "metric", {
746
+ { id = "radianPerSecond", factor = 1, symbol = "rad/s", name = "radian per second", plural = "radians per second" },
747
+ { id = "degreePerSecond", factor = DEGREE, symbol = "°/s", name = "degree per second", plural = "degrees per second", aliases = { "deg/s" } },
748
+ { id = "revolutionPerMinute", factor = REVOLUTION / 60, symbol = "rpm", name = "revolution per minute", plural = "revolutions per minute", aliases = { "RPM", "rev/min" } },
749
+ })
750
+
751
+ define("angularAcceleration", "metric", {
752
+ { id = "radianPerSecondSquared", factor = 1, symbol = "rad/s²", name = "radian per second squared", plural = "radians per second squared", aliases = { "rad/s2" } },
753
+ })
754
+
755
+ define("current", "metric", {
756
+ { id = "ampere", factor = 1, symbol = "A", name = "ampere", aliases = { "amp", "amps" }, prefixes = "si" },
757
+ })
758
+
759
+ define("charge", "metric", {
760
+ { id = "coulomb", factor = 1, symbol = "C", name = "coulomb", prefixes = "si" },
761
+ })
762
+
763
+ define("voltage", "metric", {
764
+ { id = "volt", factor = 1, symbol = "V", name = "volt", prefixes = "si" },
765
+ })
766
+
767
+ define("resistance", "metric", {
768
+ { id = "ohm", factor = 1, symbol = "Ω", name = "ohm", prefixes = "si", elidesPrefixVowel = true },
769
+ })
770
+
771
+ define("conductance", "metric", {
772
+ { id = "siemens", factor = 1, symbol = "S", name = "siemens", plural = "siemens", prefixes = "si" },
773
+ })
774
+
775
+ define("capacitance", "metric", {
776
+ { id = "farad", factor = 1, symbol = "F", name = "farad", prefixes = "si" },
777
+ })
778
+
779
+ define("inductance", "metric", {
780
+ { id = "henry", factor = 1, symbol = "H", name = "henry", plural = "henries", prefixes = "si" },
781
+ })
782
+
783
+ define("magneticFlux", "metric", {
784
+ { id = "weber", factor = 1, symbol = "Wb", name = "weber", prefixes = "si" },
785
+ })
786
+
787
+ define("amount", "metric", {
788
+ { id = "mole", factor = 1, symbol = "mol", name = "mole", prefixes = "si" },
789
+ })
790
+ define("molarMass", "metric", {
791
+ { id = "gramPerMole", factor = 1e-3, symbol = "g/mol", name = "gram per mole", plural = "grams per mole" },
792
+ })
793
+ define("molarConcentration", "metric", {
794
+ { id = "molePerLiter", factor = 1e3, symbol = "mol/L", name = "mole per liter", plural = "moles per liter" },
795
+ { id = "millimolePerLiter", factor = 1, symbol = "mmol/L", name = "millimole per liter", plural = "millimoles per liter" },
796
+ })
797
+ define("catalyticActivity", "metric", {
798
+ { id = "katal", factor = 1, symbol = "kat", name = "katal", prefixes = "si" },
799
+ })
800
+
801
+ define("luminousIntensity", "metric", {
802
+ { id = "candela", factor = 1, symbol = "cd", name = "candela", prefixes = "si" },
803
+ })
804
+ define("solidAngle", "metric", {
805
+ { id = "steradian", factor = 1, symbol = "sr", name = "steradian" },
806
+ })
807
+ define("luminousFlux", "metric", {
808
+ { id = "lumen", factor = 1, symbol = "lm", name = "lumen", prefixes = "si" },
809
+ })
810
+ define("illuminance", "metric", {
811
+ { id = "lux", factor = 1, symbol = "lx", name = "lux", plural = "lux", prefixes = "si" },
812
+ })
813
+
814
+ define("absorbedDose", "metric", {
815
+ { id = "gray", factor = 1, symbol = "Gy", name = "gray", prefixes = "si" },
816
+ { id = "sievert", factor = 1, symbol = "Sv", name = "sievert", prefixes = "si" },
817
+ })
818
+
819
+ for _, robloxRow in robloxRows(MetricUnitDefinitions.ROBLOX_METERS_PER_STUD) do
820
+ define(robloxRow.kind, "roblox", { robloxRow.row })
821
+ end
822
+
823
+ define("magneticFluxDensity", "metric", {
824
+ { id = "tesla", factor = 1, symbol = "T", name = "tesla", prefixes = "si" },
825
+ })
826
+ end
827
+
828
+ type Prefixing = { prefixes: { Prefix }, elidesPrefixVowel: boolean? }
829
+
830
+ type Registry = {
831
+ declared: { UnitDefinition },
832
+ byId: { [string]: UnitDefinition },
833
+ prefixingByBaseId: { [string]: Prefixing },
834
+ prefixableInOrder: { UnitDefinition },
835
+ everyUnit: { UnitDefinition }?,
836
+ }
837
+
838
+ local registry: Registry? = nil
839
+
840
+ local function getRegistry(): Registry
841
+ local found = registry
842
+ if found then
843
+ return found
844
+ end
845
+
846
+ local declared: { UnitDefinition } = {}
847
+ local byId: { [string]: UnitDefinition } = {}
848
+ local prefixingByBaseId: { [string]: Prefixing } = {}
849
+ local prefixableInOrder: { UnitDefinition } = {}
850
+ declareWellKnownUnits(function(kind: MetricUnitKinds.KindName, system: UnitFamily?, rows: { UnitRow })
851
+ for _, row in rows do
852
+ local unit = table.freeze(toDefinition(kind, system, row))
853
+ assert(byId[unit.id] == nil, string.format("Unit %q is declared twice", unit.id))
854
+ table.insert(declared, unit)
855
+ byId[unit.id] = unit
856
+ local prefixSet = row.prefixes
857
+ if prefixSet then
858
+ prefixingByBaseId[unit.id] =
859
+ { prefixes = PREFIX_SETS[prefixSet :: PrefixSet], elidesPrefixVowel = row.elidesPrefixVowel }
860
+ table.insert(prefixableInOrder, unit)
861
+ end
862
+ end
863
+ end)
864
+
865
+ local built: Registry = {
866
+ declared = table.freeze(declared),
867
+ byId = byId,
868
+ prefixingByBaseId = prefixingByBaseId,
869
+ prefixableInOrder = prefixableInOrder,
870
+ everyUnit = nil,
871
+ }
872
+ registry = built
873
+ return built
874
+ end
875
+
876
+ local function findPrefixedUnitById(id: string): UnitDefinition?
877
+ local byId = getRegistry().byId
878
+ for _, prefix in ALL_PREFIXES do
879
+ for _, prefixText in { prefix.name, string.sub(prefix.name, 1, -2) } do
880
+ local base = if string.sub(id, 1, #prefixText) == prefixText
881
+ then byId[string.sub(id, #prefixText + 1)]
882
+ else nil
883
+ local unit = if base then MetricUnitDefinitions.getPrefixedUnit(prefix, base) else nil
884
+ if unit and unit.id == id then
885
+ return unit
886
+ end
887
+ end
888
+ end
889
+ return nil
890
+ end
891
+
892
+ --[=[
893
+ The unit named `unit`, or nil. Takes any [UnitLike], so `"hours"` and `"h"` find `"hour"`, and a unit is
894
+ returned as it is.
895
+
896
+ @param unit UnitLike
897
+ @return UnitDefinition?
898
+ ]=]
899
+ function MetricUnitDefinitions.getUnit(unit: string | UnitDefinition): UnitDefinition?
900
+ if type(unit) == "table" then
901
+ return if MetricUnitDefinitions.isUnitDefinition(unit) then unit :: UnitDefinition else nil
902
+ end
903
+ local byId = getRegistry().byId
904
+ return byId[unit] or byId[QUENTY_TIME_UNIT_ALIASES[unit] or ""] or findPrefixedUnitById(unit)
905
+ end
906
+
907
+ --[=[
908
+ The unit `prefix` makes of `base`, such as `kilometer` for `kilo` and `meter`, or nil when `base` does not take
909
+ that prefix.
910
+
911
+ @param prefix Prefix
912
+ @param base UnitDefinition
913
+ @return UnitDefinition?
914
+ ]=]
915
+ function MetricUnitDefinitions.getPrefixedUnit(prefix: Prefix, base: UnitDefinition): UnitDefinition?
916
+ local found = getRegistry()
917
+ local prefixing = found.prefixingByBaseId[base.id]
918
+ if not (prefixing and table.find(prefixing.prefixes, prefix)) then
919
+ return nil
920
+ end
921
+ local id = withPrefix(prefix, base.id, prefixing.elidesPrefixVowel)
922
+ local existing = found.byId[id]
923
+ if existing then
924
+ return existing
925
+ end
926
+ local unit = table.freeze(toPrefixedDefinition(prefix, base, prefixing.elidesPrefixVowel))
927
+ found.byId[id] = unit
928
+ return unit
929
+ end
930
+
931
+ --[=[
932
+ The units declared outright, without their prefixed forms, in the order declared. Cheaper than
933
+ [MetricUnitDefinitions.getUnits]; [MetricUnitDefinitions.getPrefixedUnit] finds the rest.
934
+
935
+ @return { UnitDefinition }
936
+ ]=]
937
+ function MetricUnitDefinitions.getDeclaredUnits(): { UnitDefinition }
938
+ return getRegistry().declared
939
+ end
940
+
941
+ --[=[
942
+ Every unit, in the order declared, then every prefixed form. Building the prefixed forms costs a few
943
+ milliseconds the first time, so look units up with [MetricUnitDefinitions.getUnit] where you can.
944
+
945
+ @return { UnitDefinition }
946
+ ]=]
947
+ function MetricUnitDefinitions.getUnits(): { UnitDefinition }
948
+ local found = getRegistry()
949
+ local everyUnit = found.everyUnit
950
+ if everyUnit then
951
+ return everyUnit
952
+ end
953
+ local units = table.clone(found.declared)
954
+ for _, base in found.prefixableInOrder do
955
+ for _, prefix in found.prefixingByBaseId[base.id].prefixes do
956
+ local unit = MetricUnitDefinitions.getPrefixedUnit(prefix, base)
957
+ local isDeclaredOutright = unit ~= nil and unit.prefix == nil
958
+ if unit and not isDeclaredOutright then
959
+ table.insert(units, unit)
960
+ end
961
+ end
962
+ end
963
+ local frozen = table.freeze(units)
964
+ found.everyUnit = frozen
965
+ return frozen
966
+ end
967
+
968
+ --[=[
969
+ The ids of every unit of `dimensions`, in the order declared.
970
+
971
+ @param dimensions Dimensions
972
+ @return { UnitId }
973
+ ]=]
974
+ function MetricUnitDefinitions.getUnitIds(dimensions: MetricUnitDimensionUtils.Dimensions): { UnitId }
975
+ local found: { UnitId } = {}
976
+ for _, unit in MetricUnitDefinitions.getUnits() do
977
+ if MetricUnitDimensionUtils.equal(unit.dimensions, dimensions) then
978
+ table.insert(found, unit.id)
979
+ end
980
+ end
981
+ return found
982
+ end
983
+
984
+ --[=[
985
+ Every prefix a unit can take: the twenty-four SI prefixes, smallest first, then the binary ones.
986
+
987
+ @return { Prefix }
988
+ ]=]
989
+ function MetricUnitDefinitions.getPrefixes(): { Prefix }
990
+ return ALL_PREFIXES
991
+ end
992
+
993
+ --[=[
994
+ `@quenty/time`'s other names for its time units, such as `"h"` for `"hour"`, mapped to unit ids.
995
+
996
+ @return { [string]: UnitId }
997
+ ]=]
998
+ function MetricUnitDefinitions.getTimeAliases(): { [string]: UnitId }
999
+ return QUENTY_TIME_UNIT_ALIASES :: any
1000
+ end
1001
+
1002
+ --[=[
1003
+ Whether `value` is a [UnitDefinition], such as one [MetricUnitSystem.GetUnit] returns.
1004
+
1005
+ @param value any
1006
+ @return boolean
1007
+ ]=]
1008
+ function MetricUnitDefinitions.isUnitDefinition(value: any): boolean
1009
+ return type(value) == "table"
1010
+ and type(value.id) == "string"
1011
+ and type(value.factor) == "number"
1012
+ and type(value.symbol) == "string"
1013
+ and MetricUnitDimensionUtils.isDimensions(value.dimensions)
1014
+ end
1015
+
1016
+ --[=[
1017
+ Units for a [MetricUnitSystem] of a game's own, from rows of one kind as in [UnitRow], with their prefixed forms.
1018
+ They are not added to the well-known units; only a system holding them knows them.
1019
+
1020
+ ```lua
1021
+ local units = MetricUnitDefinitions.createUnits("length", "space", {
1022
+ { id = "lightYear", factor = 9.4607e15, symbol = "ly", name = "light-year" },
1023
+ })
1024
+ ```
1025
+
1026
+ @param kind KindName
1027
+ @param system string -- the name of the system that declares them
1028
+ @param rows { UnitRow }
1029
+ @return { UnitDefinition }
1030
+ ]=]
1031
+ function MetricUnitDefinitions.createUnits(
1032
+ kind: MetricUnitKinds.KindName,
1033
+ system: string,
1034
+ rows: { UnitRow }
1035
+ ): { UnitDefinition }
1036
+ local definitions: { UnitDefinition } = {}
1037
+ local prefixables: { { base: UnitDefinition, row: UnitRow } } = {}
1038
+ for _, row in rows do
1039
+ local definition = toDefinition(kind, system, row)
1040
+ table.insert(definitions, definition)
1041
+ if row.prefixes then
1042
+ table.insert(prefixables, { base = definition, row = row })
1043
+ end
1044
+ end
1045
+ local declaredIds: { [string]: true } = {}
1046
+ for _, definition in definitions do
1047
+ declaredIds[definition.id] = true
1048
+ end
1049
+ for _, prefixable in prefixables do
1050
+ for _, prefix in PREFIX_SETS[prefixable.row.prefixes :: PrefixSet] do
1051
+ local unit = toPrefixedDefinition(prefix, prefixable.base, prefixable.row.elidesPrefixVowel)
1052
+ if not declaredIds[unit.id] then
1053
+ table.insert(definitions, unit)
1054
+ end
1055
+ end
1056
+ end
1057
+ for index, definition in definitions do
1058
+ definitions[index] = table.freeze(definition)
1059
+ end
1060
+ return table.freeze(definitions)
1061
+ end
1062
+
1063
+ --[=[
1064
+ The Roblox units at a stud of `metersPerStud`: `stud`, `studPerSecond`, `studPerSecondSquared` and
1065
+ `robloxMassUnit`, a cubic stud of water, so it scales with the cube of the stud.
1066
+
1067
+ @param system string -- the name of the system that declares them
1068
+ @param metersPerStud number
1069
+ @return { UnitDefinition }
1070
+ ]=]
1071
+ function MetricUnitDefinitions.createRobloxUnits(system: string, metersPerStud: number): { UnitDefinition }
1072
+ assert(type(metersPerStud) == "number" and metersPerStud > 0, "Bad metersPerStud")
1073
+ local definitions: { UnitDefinition } = {}
1074
+ for _, robloxRow in robloxRows(metersPerStud) do
1075
+ for _, definition in MetricUnitDefinitions.createUnits(robloxRow.kind, system, { robloxRow.row }) do
1076
+ table.insert(definitions, definition)
1077
+ end
1078
+ end
1079
+ return table.freeze(definitions)
1080
+ end
1081
+
1082
+ return table.freeze(MetricUnitDefinitions)