epoch
Preview¶
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 | |
> epoch -h
Convert epoch timestamps to human-readable times across multiple timezones.
Usage:
epoch [epoch] [OPTIONS]
Script args:
--epoch int (optional) Epoch time to convert. Defaults to current time if omitted.
> epoch 1700000000000
Timezone Time
π¬π§ Europe/London 2023-11-14 22:13:20
πΊπΈ America/New_York 2023-11-14 17:13:20
π―π΅ Asia/Tokyo 2023-11-15 07:13:20
π¦πΊ Australia/Melbourne 2023-11-15 09:13:20
> epoch
Epoch millis: 1769378597483
Timezone Time
π¬π§ Europe/London 2026-01-25 22:03:17
πΊπΈ America/New_York 2026-01-25 17:03:17
π―π΅ Asia/Tokyo 2026-01-26 07:03:17
π¦πΊ Australia/Melbourne 2026-01-26 09:03:17
Tutorial: Building epoch¶
Motivation¶
When debugging distributed systems or reading logs, you'll often encounter epoch timestamps - large integers representing milliseconds (or seconds, or nanoseconds) since 1970. Converting these to human-readable times is tedious, and when you're coordinating across timezones, you often want to see the same moment in multiple locations at once.
Let's build a script that converts an epoch timestamp into a table showing that moment across several timezones.
Writing the script¶
We can use rad to create the script file for us.
rad new epoch -s
This will set us up with an executable script named epoch, and the -s simplifies the template it's instantiated with to contain just a shebang.
The shebang will allow us to invoke the script as epoch from the CLI rather than writing out rad ./epoch. Open up epoch in your editor, and you should see something like this:
1 | |
Let's begin editing it. First, we want to quickly describe what the script is aiming to do, so we'll add a file header.
1 2 3 4 | |
Adding an optional argument¶
We want the script to accept an epoch timestamp, but it should also work without one - defaulting to the current time. We declare this with int?, where the ? makes it optional (it will be null if not provided).
1 2 3 4 5 6 | |
Now we handle the case where no epoch was provided:
1 2 3 4 5 6 7 8 9 10 | |
The now() function returns a map with various time fields. We access .epoch.millis to get the current epoch in milliseconds, then print it so the user knows what value we're working with.
Setting up our timezone data¶
We'll store our timezones alongside their flag emoji in a list of pairs. Each inner list contains [timezone_id, flag]:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
Building the display columns¶
For our table output, we need two columns: Timezone (for display) and Time (the converted times).
First, let's create the Timezone column. We want it to show the flag followed by the timezone name, like "π¬π§ Europe/London". We'll use .map() with a lambda to transform each pair:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
Each x is a [timezone, flag] pair, so x[1] is the flag and x[0] is the timezone name. We interpolate them into a string with the flag first.
List comprehension alternative
You could also write this as a list comprehension, which is a bit more concise in this case:
Timezone = ["{x[1]} {x[0]}" for x in tz_to_flag]
Iterating with unpacking¶
Now we need to build the Time column by converting the epoch for each timezone. When iterating over a list of lists, we can unpack each inner list directly into named variables:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | |
The for tz, _ in tz_to_flag: line unpacks each [timezone, flag] pair. The first element becomes tz, and we use _ for the second element to indicate we don't need the flag here - it's a convention meaning "discard this value."
This is cleaner than writing:
for pair in tz_to_flag:
tz = pair[0]
// ...
We're calling a parse_time function we haven't written yet - let's do that next.
The helper function¶
We'll define a function that takes an epoch and timezone, then returns a formatted string:
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 | |
The parse_epoch() function converts an epoch timestamp to a time map, accepting a tz parameter for the timezone. We then format the .date and .time fields into a string.
Note the type annotations epoch: int, tz: str and -> str - these are optional but help document what the function expects and returns.
The display block¶
Finally, we use a display block to render our two columns as a formatted table:
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 | |
The display block takes our pre-populated lists and renders them as aligned columns. The variable names (Timezone, Time) become the column headers.
Try it out¶
> epoch 1700000000000
Timezone Time
π¬π§ Europe/London 2023-11-14 22:13:20
πΊπΈ America/New_York 2023-11-14 17:13:20
π―π΅ Asia/Tokyo 2023-11-15 07:13:20
π¦πΊ Australia/Melbourne 2023-11-15 09:13:20
Or without an argument, to see the current time:
> epoch
Epoch millis: 1769378597483
Timezone Time
π¬π§ Europe/London 2026-01-25 22:03:17
πΊπΈ America/New_York 2026-01-25 17:03:17
π―π΅ Asia/Tokyo 2026-01-26 07:03:17
π¦πΊ Australia/Melbourne 2026-01-26 09:03:17
Concepts demonstrated¶
| Concept | Where |
|---|---|
| Optional arguments | epoch int? |
| Null checking | if not epoch: |
Built-in now() |
now().epoch.millis |
| List of lists | tz_to_flag |
.map() with lambdas |
tz_to_flag.map(fn(x) ...) |
| Loop unpacking | for tz, _ in tz_to_flag: |
| Functions with types | fn parse_time(epoch: int, tz: str) -> str: |
parse_epoch() |
Time conversion |
display blocks |
Tabular output |