Previous discussion
Last month (2025-06), people discussed the geographical distribution of the typst community on Discord, and Laurenz Mädje replied:
In the last 7 days, our docs pages had visitors from ~120 countries. The top countries are Germany (14%), US (13%), Japan (9%), China (6%), France (6%), and Russia (4%).
mermaid.js code
pie title Docs pages visitors
"Germany": 14
"US": 13
"Japan": 9
"China": 6
"France": 6
"Russia": 4
"Other": 48
These numbers are based on IP addresses. They might not be accurate due to node jumps.
Git logs tell the timezone
git log
shows the history of commits in a repo. Each commit includes author, message, and the date with timezone. In the following example, +0200
means Laurenz Stampfl was offset from UTC by two hours when committing the code.
$ git log
From 9fa41da5e8e194d17b3762d4a3b629dd49660105 Mon Sep 17 00:00:00 2001
From: Laurenz Stampfl
Date: Thu, 17 Jul 2025 20:42:35 +0200 👈🕗 Here's the timezone!
Subject: [PATCH] First working version for PDF export!
---
.../typst-library/src/visualize/image/pdf.rs | 87 ++++++++++++++++++-
...
12 files changed, 196 insertions(+), 20 deletions(-)
With the help of git quick-stats --commits-by-timezone
, we can calculate the distribution throughout the entire repo, kind of unravelling the geographical distribution of the community.
Note that such data may also be inaccurate. For example, timezones in GitHub Codespaces or Gitpod are usually UTC+0, and independent of the author’s real local time.
Results
(For aesthetic reasons, UTC+13 will be drawn in the same way as UTC-11.)
GitHub - typst/typst: A new markup-based typesetting system that is powerful and easy to learn.
GitHub - typst/packages: Packages for Typst.
GitHub - qjcg/awesome-typst: Awesome Typst Links
GitHub - Myriad-Dreamin/tinymist: Tinymist [ˈtaɪni mɪst] is an integrated language service for Typst [taɪpst].
GitHub - cetz-package/cetz: CeTZ: ein Typst Zeichenpaket - A library for drawing stuff with Typst.
GitHub - touying-typ/touying: Touying is a powerful package for creating presentation slides in Typst.
GitHub - Leedehai/typst-physics: physica: vectors, fields, differentials, derivatives, Dirac brakets, tensors, and more. See examples in the manual PDF.
More packages
GitHub - typst/hayagriva: Rusty bibliography management.
GitHub - Dherse/codly: A Typst package for even better code blocks
Even more data can be found in the source code:
main.typ
#let repo = sys.inputs.at("repo", default: "typst/typst")
/// Numbers of commits by timezone
///
/// Generated by https://git-quick-stats.sh `--commits-by-timezone`.
///
// `data.at(repo as str) = (timezones in hours as float, counts as int)`
#let data = (
(
"typst/typst": `Commits TimeZone
30 -0800
45 -0700
10 -0600
86 -0500
157 -0400
86 -0300
71 +0000
1399 +0100
1602 +0200
87 +0300
5 +0500
3 +0530
2 +0600
6 +0700
134 +0800
19 +0900
9 +1000
1 +1100
1 +1200
7 +1300`,
"typst/packages": `Commits TimeZone
38 -0800
74 -0700
34 -0600
66 -0500
74 -0400
64 -0300
76 +0000
559 +0100
700 +0200
56 +0300
1 +0400
10 +0530
1 +0545
7 +0700
447 +0800
53 +0900
5 +1000
3 +1100
12 +1200
10 +1300`,
"typst/hayagriva": `Commits TimeZone
1 -0800
1 -0700
2 -0600
2 -0500
6 -0400
25 -0300
5 +0000
152 +0100
119 +0200
1 +0300
7 +0800`,
"qjcg/awesome-typst": `Commits TimeZone
1 -0800
10 -0700
9 -0600
44 -0500
81 -0400
6 -0300
9 +0000
28 +0100
53 +0200
8 +0300
3 +0500
7 +0530
42 +0800
5 +0900
1 +1000`,
"Myriad-Dreamin/tinymist": `Commits TimeZone
7 -0500
5 -0400
2 +0000
15 +0100
13 +0200
8 +0300
1404 +0800
3 +0900`,
"cetz-package/cetz": `Commits TimeZone
1 -0800
2 -0600
4 -0500
2 -0400
24 +0000
276 +0100
227 +0200
3 +0300
18 +0800
1 +0900
1 +1200
1 +1300`,
"touying-typ/touying": `Commits TimeZone
1 -0700
3 -0400
6 +0100
3 +0200
302 +0800
7 +0900
1 +1300`,
"Leedehai/typst-physics": `Commits TimeZone
1 -0700
26 -0500
31 -0400
4 +0100
5 +0200
11 +0800
2 +0900`,
"Dherse/codly": `Commits TimeZone
2 -0300
1 +0000
101 +0100
37 +0200
1 +0800
1 +1000
3 +1300`,
"Jollywatt/typst-fletcher": `Commits TimeZone
2 -0400
63 +0000
59 +0100
5 +0200
5 +0300
3 +0800
153 +1200
316 +1300`,
"polylux-typ/polylux": `Commits TimeZone
9 -0400
3 -0300
81 +0100
215 +0200
1 +0300
2 +0800`,
"Typsium/typsium": `Commits TimeZone
26 +0100
18 +0200
23 +0800`,
"sahasatvik/typst-theorems": `Commits TimeZone
2 -0400
6 -0300
1 +0100
40 +0530`,
"Marmare314/lemmify": `Commits TimeZone
1 -0600
2 -0300
8 +0100
48 +0200
1 +0300`,
"OrangeX4/typst-theorion": `Commits TimeZone
3 +0100
1 +0200
1 +0700
48 +0800
1 +1300`,
"Andrew15-5/rubby": `Commits TimeZone
34 +0300`,
"lilaq-project/lilaq": `Commits TimeZone
1 -0400
1 +0000
157 +0100
112 +0200
1 +0800`,
"typstyle-rs/typstyle": `Commits TimeZone
4 -0700
4 +0000
2 +0200
464 +0800
3 +0900`,
)
.pairs()
.map(((k, v)) => {
let (header, ..records) = csv(
bytes(v.text.split("\n").map(str.trim).join("\n")),
delimiter: " ",
)
assert.eq(header, ("Commits", "TimeZone"))
let (counts, timezones) = array.zip(..records)
(
k,
(
timezones.map(t => {
assert.eq(t.len(), 5)
let sgn = if t.first() == "+" { +1 } else { -1 }
let hour = int(t.slice(1, 3))
let minute = int(t.slice(3))
sgn * (hour + minute / 60)
}),
counts.map(int),
),
)
})
.to-dict()
)
#if false and "show-total-commits-of-each-repo" {
set page(height: auto, width: auto, margin: 1em)
table(
columns: 2,
align: (end, start),
stroke: none,
table.header()[*Total commits*][*Repo*],
table.hline(),
..data
.pairs()
.map(((repo, data)) => {
let (_, counts) = data
(counts.sum(), repo)
})
.sorted(key: ((counts, repo)) => -counts)
.map(((counts, repo)) => ([#counts], repo))
.flatten(),
)
pagebreak()
}
#let (zones, counts) = data.at(repo)
#let width = 10cm
#set page(height: auto, width: width + 2 * 2em, margin: 2em)
#import "@preview/lilaq:0.4.0" as lq
#if repo != "typst/typst" {
set align(center)
set text(1.5em, weight: "bold")
set par(spacing: 0em)
repo
}
#image("World_Time_Zones_Map.svg")
// https://commons.wikimedia.org/wiki/File:World_Time_Zones_Map.svg
#v(-1em)
#{
let leftmost = -11.2
show: lq.set-diagram(
width: width,
xaxis: (
label: if repo == "typst/typst" { [Timezone] },
lim: (leftmost, leftmost + 24),
ticks: range(-12, 13, step: 4),
stroke: none,
),
yaxis: (
position: -1,
format-ticks: (ticks, ..args) => {
let result = lq.format-ticks-symlog(ticks, ..args)
ticks
.zip(result)
.map(((tick, label)) => if tick < 1 {
none
} else {
set text(0.8em)
label
})
},
),
yscale: "symlog",
)
show: lq.cond-set(lq.grid.with(kind: "x"), stroke: width / 24 + luma(90%))
lq.diagram(
lq.stem(
// Normalize to [leftmost, leftmost + 24).
zones.map(t => calc.rem-euclid(t - leftmost, 24) + leftmost),
counts,
mark-size: 8pt,
),
ylim: (0.5, calc.max(..counts) * 1.3), // `auto`-max somehow does not work here
)
}
#if repo == "typst/typst" {
place(center, dx: 8em, dy: -12.3em, {
set par(leading: 0.3em)
let label = align(end)[Number of commits \ in #strong(repo)]
place({
set text(stroke: white + 3pt)
label
})
label
})
}