diff --git a/thrimbletrimmer/src/common/convertTime.tsx b/thrimbletrimmer/src/common/convertTime.tsx new file mode 100644 index 0000000..df3e7b2 --- /dev/null +++ b/thrimbletrimmer/src/common/convertTime.tsx @@ -0,0 +1,96 @@ +import { l } from "vite/dist/node/types.d-aGj9QkWt"; +import { DateTime } from "../external/luxon.min"; + +export function dateTimeFromWubloaderTime(wubloaderTime: string): DateTime | null { + const dt = DateTime.fromISO(wubloaderTime); + if (dt.isValid) { + return dt; + } + return null; +} + +export function wubloaderTimeFromDateTime(dateTime: DateTime): string { + // Not using ISO here because Luxon doesn't give us a quick way to print an ISO8601 string with no offset. + return dateTime.toFormat("yyyy-LL-dd'T'HH:mm:ss.SSS"); +} + +class DateTimeMathObject { + hours: number; + minutes: number; + seconds: number; +} + +function dateTimeMathObjectFromBusTime(busTime: string): DateTimeMathObject | null { + // We need to handle inputs like "-0:10:15" in a way that consistently makes the time negative. + // Since we can't assign the negative sign to any particular part, we'll check for the whole thing here. + let direction = 1; + if (busTime.startsWith("-")) { + busTime = busTime.slice(1); + direction = -1; + } + + const parts = busTime.split(":", 3); + const hours = parseInt(parts[0], 10) * direction; + const minutes = parts.length > 1 ? parseInt(parts[1], 10) * direction : 0; + const seconds = parts.length > 2 ? parseInt(parts[2], 10) * direction : 0; + return { hours: hours, minutes: minutes, seconds: seconds }; +} + +export function dateTimeFromBusTime(busStartTime: DateTime, busTime: string): DateTime | null { + const busMathObject = dateTimeMathObjectFromBusTime(busTime); + if (busMathObject === null) { + return null; + } + return busStartTime.plus(busMathObject); +} + +export function busTimeFromDateTime(busStartTime: DateTime, time: DateTime): string { + const diff = time.diff(busStartTime); + if (diff.milliseconds < 0) { + const negativeInterval = diff.negate(); + return `-${negativeInterval.toFormat("hh:mm:ss.SSS")}`; + } + return diff.toFormat("hh:mm:ss.SSS"); +} + +export function dateTimeFromTimeAgo(timeAgo: string): DateTime | null { + const parts = timeAgo.split(":"); + const properties = ["hours", "minutes", "seconds"]; + const mathObj = {}; + + while (parts.length > 0) { + const nextPart = parts.pop(); + if (properties.length === 0) { + return null; + } + const nextProp = properties.pop(); + const partNumber = parseInt(nextPart, 10); + if (isNaN(partNumber)) { + return null; + } + mathObj[nextProp] = partNumber; + } + + const now = DateTime.utc(); + return now.plus(mathObj); +} + +export function timeAgoFromDateTime(dateTime: DateTime): string { + const currentTime = DateTime.utc(); + const interval = currentTime.diff(dateTime, "seconds"); + let timeAgoSeconds = interval.seconds; + + let negative = ""; + if (timeAgoSeconds < 0) { + negative = "-"; + timeAgoSeconds = -timeAgoSeconds; + } + + const seconds = (((timeAgoSeconds % 60) * 1000) | 0) / 1000 + const secondsString = seconds < 10 ? `0${seconds}` : seconds.toString(); + const minutes = (timeAgoSeconds / 60) % 60 | 0; + const minutesString = minutes < 10 ? `0${minutes}` : minutes.toString(); + const hours = Math.floor(timeAgoSeconds / 3600); + + return `${negative}${hours}:${minutesString}:${secondsString}`; +} diff --git a/thrimbletrimmer/src/utilities/Clock.tsx b/thrimbletrimmer/src/utilities/Clock.tsx index dd26931..487ce23 100644 --- a/thrimbletrimmer/src/utilities/Clock.tsx +++ b/thrimbletrimmer/src/utilities/Clock.tsx @@ -1,19 +1,17 @@ -import { Component, createSignal, onCleanup, onMount } from "solid-js"; +import { Accessor, Component, createSignal, onCleanup, onMount } from "solid-js"; import { DateTime, Interval } from "../external/luxon.min"; -const Clock: Component = () => { +interface ClockProps { + busStartTime: Accessor; +} + +const Clock: Component = (props) => { const [delay, setDelay] = createSignal(10); const [time, setTime] = createSignal(DateTime.utc()); - const [busStartTime, setBusStartTime] = createSignal(null); + const busStartTime = props.busStartTime; const timer = setInterval(() => setTime(DateTime.utc()), 250); - onMount(async () => { - const dataResponse = await fetch("/thrimshim/defaults"); - const data = await dataResponse.json(); - setBusStartTime(DateTime.fromISO(data.bustime_start)); - }); - onCleanup(() => { clearInterval(timer); }); diff --git a/thrimbletrimmer/src/utilities/TimeConverter.tsx b/thrimbletrimmer/src/utilities/TimeConverter.tsx new file mode 100644 index 0000000..e0bfefd --- /dev/null +++ b/thrimbletrimmer/src/utilities/TimeConverter.tsx @@ -0,0 +1,136 @@ +import { Accessor, Component, createSignal } from "solid-js"; +import { DateTime } from "../external/luxon.min"; +import { + dateTimeFromWubloaderTime, + dateTimeFromBusTime, + dateTimeFromTimeAgo, + wubloaderTimeFromDateTime, + busTimeFromDateTime, + timeAgoFromDateTime, +} from "../common/convertTime"; + +interface TimeConverterProps { + busStartTime: Accessor; +} + +enum TimeType { + UTC, + BusTime, + TimeAgo, +} + +const TimeConverter: Component = (props) => { + const [enteredTime, setEnteredTime] = createSignal(""); + const [startTimeType, setStartTimeType] = createSignal(TimeType.UTC); + const [outputTimeType, setOutputTimeType] = createSignal(TimeType.UTC); + + const outputString = (): string => { + const busStartTime = props.busStartTime(); + if (busStartTime === null) { + return ""; + } + const startType = startTimeType(); + let dateTime: DateTime | null = null; + if (startType === TimeType.UTC) { + dateTime = dateTimeFromWubloaderTime(enteredTime()); + } else if (startType === TimeType.BusTime) { + dateTime = dateTimeFromBusTime(busStartTime, enteredTime()); + } else if (startType === TimeType.TimeAgo) { + dateTime = dateTimeFromTimeAgo(enteredTime()); + } + if (dateTime === null) { + return ""; + } + + const outputType = outputTimeType(); + if (outputType === TimeType.UTC) { + return wubloaderTimeFromDateTime(dateTime); + } + if (outputType === TimeType.BusTime) { + return busTimeFromDateTime(busStartTime, dateTime); + } + if (outputType === TimeType.TimeAgo) { + return timeAgoFromDateTime(dateTime); + } + return ""; + }; + + return ( +
+

Convert Times

+ { + setEnteredTime(event.currentTarget.value); + }} + /> +
+ From: + + + +
+
+ To: + + + +
+
Converted Time: {outputString()}
+
+ ); +}; + +export default TimeConverter; diff --git a/thrimbletrimmer/src/utilities/Utilities.tsx b/thrimbletrimmer/src/utilities/Utilities.tsx index cff6175..a28c7c9 100644 --- a/thrimbletrimmer/src/utilities/Utilities.tsx +++ b/thrimbletrimmer/src/utilities/Utilities.tsx @@ -1,10 +1,21 @@ -import { Component } from "solid-js"; +import { Component, createSignal, onMount } from "solid-js"; +import { DateTime } from "../external/luxon.min"; import Clock from "./Clock"; +import TimeConverter from "./TimeConverter"; const Utilities: Component = () => { + const [busStartTime, setBusStartTime] = createSignal(null); + + onMount(async () => { + const dataResponse = await fetch("/thrimshim/defaults"); + const data = await dataResponse.json(); + setBusStartTime(DateTime.fromISO(data.bustime_start)); + }); + return ( <> - + + ); }; diff --git a/thrimbletrimmer/vite.config.ts b/thrimbletrimmer/vite.config.ts index 92dc5b5..9a7227a 100644 --- a/thrimbletrimmer/vite.config.ts +++ b/thrimbletrimmer/vite.config.ts @@ -11,6 +11,7 @@ export default defineConfig({ }, build: { target: "esnext", + // minify: false, // Uncomment this line if you need to debug unminified code rollupOptions: { input: { index: fileURLToPath(new URL("index.html", import.meta.url)),