Readme

CronGPT

This is a minisite to help you create cron expressions, particularly for crons on Val Town. It was inspired by Cron Prompt, but also does the timezone conversion from wherever you are to UTC (typically the server timezone).

Tech

  • Hono for routing (GET / and POST /compile.)
  • Hono JSX
  • HTMX (probably overcomplicates things; should remove)
  • @stevekrouse/openai, which is a light wrapper around @std/openai

I'm finding HTMX a bit overpowered for this, so I have two experimental forks without it:

  1. Vanilla client-side JavaScript: @stevekrouse/cron_client_side_script_fork
  2. Client-side ReactJS (no SSR): @stevekrouse/cron_client_react_fork

I think (2) Client-side React without any SSR is the simplest architecture. Maybe will move to that.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/** @jsxImportSource npm:hono@3/jsx */
import { chat } from "https://esm.town/v/stevekrouse/openai";
import cronstrue from "npm:cronstrue";
import { Hono } from "npm:hono@3";
const app = new Hono();
app.get("/", (c) =>
c.html(
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.tailwindcss.com" />
<script
src="https://unpkg.com/htmx.org@1.9.11"
integrity="sha384-0gxUXCCR8yv9FM2b+U3FDbsKthCI66oH5IA9fHppQq9DDMHuMauqq1ZHBpJxQ0J0"
crossorigin="anonymous"
>
</script>
<style
dangerouslySetInnerHTML={{
__html: `
.htmx-indicator{
display:none;
}
.htmx-request .htmx-indicator{
display:inline;
}
.htmx-request.htmx-indicator{
display:inline;
}
`,
}}
>
</style>
<title>Cron Compiler</title>
</head>
<body class="flex p-6 flex-col space-y-4 max-w-2xl mx-auto">
<div>
<h1 class="text-3xl">Cron Compiler</h1>
<p>Compile natural language to cron via ChatGPT</p>
</div>
<form hx-post="/compile" hx-target="#cron" class="flex space-x-2" hx-disabled-elt="button">
<input
name="description"
value="On weekdays at noon"
required
class="w-full border-2 rounded-lg p-2"
>
</input>
<input type="hidden" name="timezone" id="timezone"></input>
<script
dangerouslySetInnerHTML={{
__html: `document.querySelector('#timezone').value = Intl.DateTimeFormat().resolvedOptions().timeZone`,
}}
/>
<button class="bg-purple-500 hover:bg-purple-700 text-white font-bold py-2 px-4 rounded disabled:hidden">
Compile
</button>
<button class="htmx-indicator text-white font-bold py-2 px-4 rounded bg-gray-400" disabled>
Loading...
</button>
</form>
<div hx-swap-oob="true" id="cron">
{renderCron("0 16 * * 1-5", "America/New_York")}
</div>
<div>
Inspired by <a href="https://cronprompt.com/" class="text-blue-500 hover:text-blue-700">Cron Prompt</a>.{" "}
<a href="https://www.val.town/v/stevekrouse/cron" class="text-blue-500 hover:text-blue-700">View source</a>.
</div>
</body>
</html>,
));
function renderCron(cron: string, timezone: string) {
return (
<div>
<pre class="font-mono text-xl text-center w-full border-2 p-2 bg-gray-200">{cron}</pre>
<p class="text-center">
{cronstrue.toString(cron, { tzOffset: getOffset(timezone), use24HourTimeFormat: false })}
, {shortenTimezoneString(timezone)}
</p>
</div>
);
}
// convert a timezone string like America/New_York to "EST"
function shortenTimezoneString(timeZone: string) {
const string = new Date().toLocaleString("en-US", { timeZone, timeZoneName: "short" });
return string.split(" ").at(-1);
}
// https://stackoverflow.com/a/68593283
const getOffset = (timeZone: string) => {
const now = new Date();
const utcDate = new Date(now.toLocaleString("en-US", { timeZone: "UTC" }));
const tzDate = new Date(now.toLocaleString("en-US", { timeZone }));
return (tzDate.getTime() - utcDate.getTime()) / (60 * 60 * 1000);
};
Only the latest version can be previewed
👆 This is a val. Vals are TypeScript snippets of code, written in the browser and run on our servers. Create scheduled functions, email yourself, and persist small pieces of data — all from the browser.