Production-ready, zero-dependency reference implementations. Copy-paste into your project. Integer millisecond arithmetic avoids float drift.
Civil display uses an 86,400-second mean-day interface baseline; ISO/UTC remains authoritative.
ofs-core.ts into your project (zero dependencies).ofs-tz.ts if you need timezone construction via Intl.DateTimeFormat.Zero dependencies. All arithmetic uses integer milliseconds (msPerPulse = 864) to avoid float drift.
/**
* OFS SDK (Core) — OFS-2.1
* Production-ready, copy-paste friendly, zero dependencies.
*
* Civil Display token: Φ.ψψψψψ
* - Φ: 0..9 (phase)
* - ψ: 5 digits "00000".."09999" (pulse within phase)
* - "." is a separator, NOT a decimal point
*
* Design baseline:
* - mean civil day = 86,400 seconds = 86,400,000 ms
* - pulsesPerDay = 100,000
* - msPerPulse = 864 ms (since 0.864 s)
*/
export const OFS = {
version: "2.1",
phasesPerDay: 10,
pulsesPerPhase: 10_000,
pulsesPerDay: 100_000,
secondsPerDay: 86_400,
msPerDay: 86_400_000,
secondsPerPhase: 8_640,
msPerPhase: 8_640_000,
secondsPerPulse: 0.864,
msPerPulse: 864,
} as const;
export type Phase = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
export type CivilToken = `${Phase}.${string}`;
export class OFSError extends Error {
readonly code: string;
constructor(code: string, message: string) {
super(message);
this.name = "OFSError";
this.code = code;
}
}
function clampInt(n: number, min: number, max: number): number {
if (!Number.isFinite(n)) return min;
const x = Math.trunc(n);
return x < min ? min : x > max ? max : x;
}
function pad5(n: number): string {
const s = String(n);
return s.length >= 5 ? s : "0".repeat(5 - s.length) + s;
}
function isDigits(s: string): boolean {
return /^[0-9]+$/.test(s);
}
/** Convert ms-of-day (local) → pulses-of-day (0..99999) */
export function pulsesOfDayFromMs(msOfDay: number): number {
if (!Number.isFinite(msOfDay)) {
throw new OFSError("E_MS_NAN", "msOfDay must be a finite number.");
}
const ms = Math.max(0, Math.min(OFS.msPerDay - 1, Math.trunc(msOfDay)));
const pulses = Math.floor(ms / OFS.msPerPulse);
return clampInt(pulses, 0, OFS.pulsesPerDay - 1);
}
/** Convert pulses-of-day → Civil token Φ.ψψψψψ */
export function civilFromPulsesOfDay(pulsesOfDay: number): CivilToken {
const p = clampInt(pulsesOfDay, 0, OFS.pulsesPerDay - 1);
const phase = Math.floor(p / OFS.pulsesPerPhase) as Phase;
const within = p % OFS.pulsesPerPhase;
return `${phase}.${pad5(within)}` as CivilToken;
}
/** Convert ms-of-day (local) → Civil token */
export function civilFromMsOfDay(msOfDay: number): CivilToken {
return civilFromPulsesOfDay(pulsesOfDayFromMs(msOfDay));
}
/** Convert local time parts (0..23 etc) → Civil token */
export function civilFromTimeParts(input: {
hour: number;
minute?: number;
second?: number;
millisecond?: number;
}): CivilToken {
const h = clampInt(input.hour, 0, 23);
const m = clampInt(input.minute ?? 0, 0, 59);
const s = clampInt(input.second ?? 0, 0, 59);
const ms = clampInt(input.millisecond ?? 0, 0, 999);
const msOfDay = h * 3_600_000 + m * 60_000 + s * 1_000 + ms;
return civilFromMsOfDay(msOfDay);
}
/** Strict parse + validate Civil token "Φ.ψψψψψ" */
export function parseCivil(token: string): {
phase: Phase; pulse: number; pulsesOfDay: number
} {
if (typeof token !== "string") {
throw new OFSError("E_CIVIL_TYPE", "Civil token must be a string.");
}
const parts = token.split(".");
if (parts.length !== 2) {
throw new OFSError("E_CIVIL_FORMAT",
'Civil token must be "Φ.ψψψψψ" with exactly one dot separator.');
}
const [phiStr, psiStr] = parts;
if (phiStr.length !== 1 || !isDigits(phiStr)) {
throw new OFSError("E_PHASE_FORMAT", "Φ must be a single digit 0–9.");
}
const phaseNum = Number(phiStr);
if (psiStr.length !== 5 || !isDigits(psiStr)) {
throw new OFSError("E_PULSE_FORMAT",
"ψ must be exactly 5 digits 00000–09999.");
}
const pulseNum = Number(psiStr);
const pulsesOfDay = phaseNum * OFS.pulsesPerPhase + pulseNum;
return { phase: phaseNum as Phase, pulse: pulseNum, pulsesOfDay };
}
/** Flow-Time: monotonic integer pulses. FT = floor(unixMs / 864) */
export function toFT(unixMs: number): number {
if (!Number.isFinite(unixMs))
throw new OFSError("E_UNIXMS_NAN", "unixMs must be a finite number.");
return Math.floor(unixMs / OFS.msPerPulse);
}
export function fromFT(ft: number): Date {
if (!Number.isFinite(ft))
throw new OFSError("E_FT_NAN", "ft must be a finite number.");
return new Date(Math.trunc(ft) * OFS.msPerPulse);
}Deterministic outputs for civilFromTimeParts. All implementations must match these values exactly.
| Local Time | Civil Token |
|---|---|
| 00:00 | 0.00000 |
| 02:00 | 0.08333 |
| 04:00 | 1.06666 |
| 06:00 | 2.05000 |
| 08:00 | 3.03333 |
| 10:00 | 4.01666 |
| 12:00 | 5.00000 |
| 14:00 | 5.08333 |
| 16:00 | 6.06666 |
| 18:00 | 7.05000 |
| 20:00 | 8.03333 |
| 21:00 | 8.07500 |
| 22:00 | 9.01666 |
GMT+8 Quick Reference mappings are identical to the above when the local timezone offset is applied.
No tracking. OFS pulses are coordination units, not surveillance timestamps. Never use pulse-level resolution to track individual behavior or location.
No scoring. OFS must not be used for productivity scoring, behavioral profiling, or performance measurement of individuals.
Privacy-first defaults. Meaning-Time events default to privacy: "private" and require explicit consent: { mode: "explicit" } when handling personal data.
Implementations that violate these normative safeguards are non-compliant with OFS-2.1.
Civil display uses an 86,400-second mean-day interface baseline. ISO/UTC remains authoritative for all legal and precision contexts. Leap-second handling follows a smear strategy to maintain monotonic flow.