diff --git a/preload/apps/digiclk/__init__.py b/preload/apps/digiclk/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..72e33fecaa98a9b2cf78574ebf55c3389d4fb610
--- /dev/null
+++ b/preload/apps/digiclk/__init__.py
@@ -0,0 +1,348 @@
+import os
+import sys
+import leds
+import display
+import buttons
+
+sys.path.append("/apps/digiclk/")
+import monotime as utime
+import draw
+
+DIGITS = [
+    (True, True, True, True, True, True, False),
+    (False, True, True, False, False, False, False),
+    (True, True, False, True, True, False, True),
+    (True, True, True, True, False, False, True),
+    (False, True, True, False, False, True, True),
+    (True, False, True, True, False, True, True),
+    (True, False, True, True, True, True, True),
+    (True, True, True, False, False, False, False),
+    (True, True, True, True, True, True, True),
+    (True, True, True, True, False, True, True),
+]
+
+
+def renderNum(d, num, x):
+    draw.Grid7Seg(d, x, 0, 7, DIGITS[num // 10], (255, 255, 255))
+    draw.Grid7Seg(d, x + 5, 0, 7, DIGITS[num % 10], (255, 255, 255))
+
+
+def renderColon(d):
+    draw.GridVSeg(d, 11, 2, 7, 2, (255, 255, 255))
+    draw.GridVSeg(d, 11, 4, 7, 2, (255, 255, 255))
+
+
+def renderText(d, text, blankidx=None):
+    bs = bytearray(text)
+
+    if blankidx != None:
+        bs[blankidx : blankidx + 1] = b"_"
+
+    d.print(
+        MODES[MODE] + " " + bs.decode(), fg=(255, 255, 255), bg=None, posx=0, posy=7 * 8
+    )
+
+
+def renderBar(d, num):
+    d.rect(20, 78, 20 + num * 2, 80, col=(255, 255, 255))
+
+
+def render(d):
+    ltime = utime.localtime()
+    years = ltime[0]
+    months = ltime[1]
+    days = ltime[2]
+    hours = ltime[3]
+    mins = ltime[4]
+    secs = ltime[5]
+
+    d.clear()
+
+    if MODE == CHANGE_YEAR:
+        renderNum(d, years // 100, 1)
+        renderNum(d, years % 100, 13)
+    elif MODE == CHANGE_MONTH:
+        renderNum(d, months, 13)
+    elif MODE == CHANGE_DAY:
+        renderNum(d, days, 13)
+    else:
+        renderNum(d, hours, 1)
+        renderNum(d, mins, 13)
+
+    if MODE not in (CHANGE_YEAR, CHANGE_MONTH, CHANGE_DAY) and secs % 2 == 0:
+        renderColon(d)
+
+    renderText(d, NAME, None)
+    renderBar(d, secs)
+
+    d.update()
+
+
+LONG_DELAY = 400
+BUTTON_UPDATE_TIME = 100
+BUTTON_SEL = 1 << 0
+BUTTON_SEL_LONG = 1 << 1
+BUTTON_UP = 1 << 2
+BUTTON_UP_LONG = 1 << 3
+BUTTON_DOWN = 1 << 4
+BUTTON_DOWN_LONG = 1 << 5
+pressed_prev = 0
+button_long_prev = {BUTTON_SEL: False, BUTTON_UP: False, BUTTON_DOWN: False}
+button_times = {BUTTON_SEL: 0, BUTTON_UP: 0, BUTTON_DOWN: 0}
+
+
+def checkButton(button, button_long, osbutton, pressed, t):
+    cur_buttons = 0
+
+    if pressed & osbutton and not pressed_prev & osbutton:
+        button_times[button] = t
+        button_long_prev[button] = False
+    elif pressed_prev & osbutton:
+        if button_times[button] + LONG_DELAY < t:
+            cur_buttons |= button_long
+            button_times[button] = t
+            button_long_prev[button] = True
+        elif not pressed & osbutton and not button_long_prev[button]:
+            cur_buttons |= button
+
+    return cur_buttons
+
+
+def checkButtons():
+    global pressed_prev
+
+    t = utime.time_monotonic_ms()
+    pressed = buttons.read(
+        buttons.BOTTOM_LEFT | buttons.TOP_RIGHT | buttons.BOTTOM_RIGHT
+    )
+    cur_buttons = 0
+
+    cur_buttons |= checkButton(
+        BUTTON_SEL, BUTTON_SEL_LONG, buttons.BOTTOM_LEFT, pressed, t
+    )
+    cur_buttons |= checkButton(BUTTON_UP, BUTTON_UP_LONG, buttons.TOP_RIGHT, pressed, t)
+    cur_buttons |= checkButton(
+        BUTTON_DOWN, BUTTON_DOWN_LONG, buttons.BOTTOM_RIGHT, pressed, t
+    )
+
+    pressed_prev = pressed
+    return cur_buttons
+
+
+def modTime(yrs, mth, day, hrs, mns, sec):
+    ltime = utime.localtime()
+    new = utime.mktime(
+        (
+            ltime[0] + yrs,
+            ltime[1] + mth,
+            ltime[2] + day,
+            ltime[3] + hrs,
+            ltime[4] + mns,
+            ltime[5] + sec,
+            None,
+            None,
+        )
+    )
+    utime.set_time(new)
+
+
+def ctrl_display(bs):
+    global MODE, updated
+    updated = True
+    if bs & BUTTON_SEL_LONG:
+        MODE = CHANGE_HOURS
+    else:
+        updated = False
+
+
+def ctrl_chg_hrs(bs):
+    global MODE, updated
+    updated = True
+    if bs & BUTTON_SEL_LONG:
+        MODE = DISPLAY
+    elif bs & BUTTON_SEL:
+        MODE = CHANGE_MINUTES
+    elif bs & BUTTON_UP_LONG:
+        modTime(0, 0, 0, 10, 0, 0)
+    elif bs & BUTTON_DOWN_LONG:
+        modTime(0, 0, 0, -10, 0, 0)
+    elif bs & BUTTON_UP:
+        modTime(0, 0, 0, 1, 0, 0)
+    elif bs & BUTTON_DOWN:
+        modTime(0, 0, 0, -1, 0, 0)
+    else:
+        updated = False
+
+
+def ctrl_chg_mns(bs):
+    global MODE, updated
+    updated = True
+    if bs & BUTTON_SEL_LONG:
+        MODE = DISPLAY
+    elif bs & BUTTON_SEL:
+        MODE = CHANGE_SECONDS
+    elif bs & BUTTON_UP_LONG:
+        modTime(0, 0, 0, 0, 10, 0)
+    elif bs & BUTTON_DOWN_LONG:
+        modTime(0, 0, 0, 0, -10, 0)
+    elif bs & BUTTON_UP:
+        modTime(0, 0, 0, 0, 1, 0)
+    elif bs & BUTTON_DOWN:
+        modTime(0, 0, 0, 0, -1, 0)
+    else:
+        updated = False
+
+
+def ctrl_chg_sec(bs):
+    global MODE, updated
+    updated = True
+    if bs & BUTTON_SEL_LONG:
+        MODE = DISPLAY
+    elif bs & BUTTON_SEL:
+        MODE = CHANGE_YEAR
+    elif bs & BUTTON_UP_LONG:
+        modTime(0, 0, 0, 0, 0, 10)
+    elif bs & BUTTON_DOWN_LONG:
+        modTime(0, 0, 0, 0, 0, -10)
+    elif bs & BUTTON_UP:
+        modTime(0, 0, 0, 0, 0, 1)
+    elif bs & BUTTON_DOWN:
+        modTime(0, 0, 0, 0, 0, -1)
+    else:
+        updated = False
+
+
+def ctrl_chg_yrs(bs):
+    global MODE, updated
+    updated = True
+    if bs & BUTTON_SEL_LONG:
+        MODE = DISPLAY
+    elif bs & BUTTON_SEL:
+        MODE = CHANGE_MONTH
+    elif bs & BUTTON_UP_LONG:
+        modTime(10, 0, 0, 0, 0, 0)
+    elif bs & BUTTON_DOWN_LONG:
+        modTime(-10, 0, 0, 0, 0, 0)
+    elif bs & BUTTON_UP:
+        modTime(1, 0, 0, 0, 0, 0)
+    elif bs & BUTTON_DOWN:
+        modTime(-1, 0, 0, 0, 0, 0)
+    else:
+        updated = False
+
+
+def ctrl_chg_mth(bs):
+    global MODE, updated
+    updated = True
+    if bs & BUTTON_SEL_LONG:
+        MODE = DISPLAY
+    elif bs & BUTTON_SEL:
+        MODE = CHANGE_DAY
+    elif bs & BUTTON_UP_LONG:
+        modTime(0, 6, 0, 0, 0, 0)
+    elif bs & BUTTON_DOWN_LONG:
+        modTime(0, -6, 0, 0, 0, 0)
+    elif bs & BUTTON_UP:
+        modTime(0, 1, 0, 0, 0, 0)
+    elif bs & BUTTON_DOWN:
+        modTime(0, -1, 0, 0, 0, 0)
+    else:
+        updated = False
+
+
+def ctrl_chg_day(bs):
+    global MODE, updated
+    updated = True
+    if bs & BUTTON_SEL_LONG:
+        MODE = DISPLAY
+    elif bs & BUTTON_SEL:
+        MODE = CHANGE_HOURS
+    elif bs & BUTTON_UP_LONG:
+        modTime(0, 0, 10, 0, 0, 0)
+    elif bs & BUTTON_DOWN_LONG:
+        modTime(0, 0, -10, 0, 0, 0)
+    elif bs & BUTTON_UP:
+        modTime(0, 0, 1, 0, 0, 0)
+    elif bs & BUTTON_DOWN:
+        modTime(0, 0, -1, 0, 0, 0)
+    else:
+        updated = False
+
+
+NAME = None
+FILENAME = "nickname.txt"
+
+
+def load_nickname():
+    global NAME
+    if FILENAME in os.listdir("."):
+        with open("nickname.txt", "rb") as f:
+            name = f.read().strip()
+    else:
+        name = b"no nick"
+
+    if len(name) > 7:
+        name = name[0:7]
+    else:
+        name = b" " * (7 - len(name)) + name
+
+    NAME = name
+
+
+# MODE values
+DISPLAY = 0
+CHANGE_HOURS = 1
+CHANGE_MINUTES = 2
+CHANGE_SECONDS = 3
+CHANGE_YEAR = 4
+CHANGE_MONTH = 5
+CHANGE_DAY = 6
+
+MODE = DISPLAY
+MODES = {
+    DISPLAY: "---",
+    CHANGE_HOURS: "HRS",
+    CHANGE_MINUTES: "MNS",
+    CHANGE_SECONDS: "SEC",
+    CHANGE_YEAR: "YRS",
+    CHANGE_MONTH: "MTH",
+    CHANGE_DAY: "DAY",
+}
+updated = False
+
+CTRL_FNS = {
+    DISPLAY: ctrl_display,
+    CHANGE_HOURS: ctrl_chg_hrs,
+    CHANGE_MINUTES: ctrl_chg_mns,
+    CHANGE_SECONDS: ctrl_chg_sec,
+    CHANGE_YEAR: ctrl_chg_yrs,
+    CHANGE_MONTH: ctrl_chg_mth,
+    CHANGE_DAY: ctrl_chg_day,
+}
+
+
+def main():
+    global updated
+    try:
+        load_nickname()
+        with display.open() as d:
+            last_secs, secs = 0, 0
+            last_msecs, msecs = 0, 0
+            while True:
+                updated = False
+
+                bs = checkButtons()
+                CTRL_FNS[MODE](bs)
+
+                last_secs, secs = secs, utime.time_monotonic()
+                if updated or secs > last_secs:
+                    render(d)
+
+                last_msecs, msecs = msecs, utime.time_monotonic_ms()
+                if msecs - last_msecs < BUTTON_UPDATE_TIME:
+                    utime.sleep_ms(BUTTON_UPDATE_TIME - (msecs - last_msecs))
+    except KeyboardInterrupt:
+        pass
+
+
+main()
diff --git a/preload/apps/digiclk/draw.py b/preload/apps/digiclk/draw.py
new file mode 100644
index 0000000000000000000000000000000000000000..e513b13b41c274f2aa0ec08d15d52ace87c15d84
--- /dev/null
+++ b/preload/apps/digiclk/draw.py
@@ -0,0 +1,82 @@
+def _ceilDiv(a, b):
+    return (a + (b - 1)) // b
+
+
+def TipHeight(w):
+    return _ceilDiv(w, 2) - 1
+
+
+def Tip(d, x, y, w, c, invert=False, swapAxes=False):
+    h = TipHeight(w)
+    for dy in range(h):
+        for dx in range(dy + 1, w - 1 - dy):
+            px = x + dx
+            py = y + dy if not invert else y + h - 1 - dy
+            if swapAxes:
+                px, py = py, px
+            d.pixel(px, py, col=c)
+
+
+def Seg(d, x, y, w, h, c, swapAxes=False):
+    tip_h = TipHeight(w)
+    body_h = h - 2 * tip_h
+
+    Tip(d, x, y, w, c, invert=True, swapAxes=swapAxes)
+
+    px1, px2 = x, x + w
+    py1, py2 = y + tip_h, y + tip_h + body_h
+    if swapAxes:
+        px1, px2, py1, py2 = py1, py2, px1, px2
+    d.rect(px1, py1, px2 - 1, py2 - 1, col=c)
+
+    Tip(d, x, y + tip_h + body_h, w, c, invert=False, swapAxes=swapAxes)
+
+
+def VSeg(d, x, y, w, l, c):
+    Seg(d, x, y, w, l, c)
+
+
+def HSeg(d, x, y, w, l, c):
+    Seg(d, y, x, w, l, c, swapAxes=True)
+
+
+def GridSeg(d, x, y, w, l, c, swapAxes=False):
+    sw = w - 2
+    tip_h = TipHeight(sw)
+
+    x = x * w
+    y = y * w
+    l = (l - 1) * w
+    Seg(d, x + 1, y + tip_h + 3, sw, l - 3, c, swapAxes=swapAxes)
+
+
+def GridVSeg(d, x, y, w, l, c):
+    GridSeg(d, x, y, w, l, c)
+
+
+def GridHSeg(d, x, y, w, l, c):
+    GridSeg(d, y, x, w, l, c, swapAxes=True)
+
+
+def Grid(d, x1, y1, x2, y2, w, c):
+    for x in range(x1 * w, x2 * w):
+        for y in range(y1 * w, y2 * w):
+            if x % w == 0 or x % w == w - 1 or y % w == 0 or y % w == w - 1:
+                d.pixel(x, y, col=c)
+
+
+def Grid7Seg(d, x, y, w, segs, c):
+    if segs[0]:
+        GridHSeg(d, x, y, w, 4, c)
+    if segs[1]:
+        GridVSeg(d, x + 3, y, w, 4, c)
+    if segs[2]:
+        GridVSeg(d, x + 3, y + 3, w, 4, c)
+    if segs[3]:
+        GridHSeg(d, x, y + 6, w, 4, c)
+    if segs[4]:
+        GridVSeg(d, x, y + 3, w, 4, c)
+    if segs[5]:
+        GridVSeg(d, x, y, w, 4, c)
+    if segs[6]:
+        GridHSeg(d, x, y + 3, w, 4, c)
diff --git a/preload/apps/digiclk/metadata.json b/preload/apps/digiclk/metadata.json
new file mode 100644
index 0000000000000000000000000000000000000000..ff0c81709b7773e8248f784c63196385391e60f7
--- /dev/null
+++ b/preload/apps/digiclk/metadata.json
@@ -0,0 +1,7 @@
+{
+    "name": "DigiClk",
+    "description": "A Digital Clock, configurable via buttons",
+    "category": "wearable",
+    "author": "yrlf",
+    "revision": 1
+}
diff --git a/preload/apps/digiclk/monotime.py b/preload/apps/digiclk/monotime.py
new file mode 100644
index 0000000000000000000000000000000000000000..3cfbbbc54b9659c36928f772944899108a85eeb8
--- /dev/null
+++ b/preload/apps/digiclk/monotime.py
@@ -0,0 +1,71 @@
+import utime as _utime
+
+_offset_ms = 0
+
+
+def time_monotonic():
+    return _utime.time() + _offset_ms // 1000
+
+
+def time_monotonic_ms():
+    return _utime.time_ms() + _offset_ms
+
+
+def sleep(s):
+    return _utime.sleep(s)
+
+
+def sleep_ms(ms):
+    return _utime.sleep_ms(ms)
+
+
+def sleep_us(us):
+    return _utime.sleep_us(us)
+
+
+def time():
+    return _utime.time()
+
+
+def time_ms():
+    return _utime.time_ms()
+
+
+def set_time(t):
+    global _offset_ms
+
+    cur_t = _utime.time_ms()
+    _utime.set_time(t)
+    new_t = _utime.time_ms()
+
+    diff = cur_t - new_t
+    _offset_ms += diff
+
+
+def set_unix_time(t):
+    global _offset_ms
+
+    cur_t = _utime.time_ms()
+    _utime.set_unix_time(t)
+    new_t = _utime.time_ms()
+
+    diff = cur_t - new_t
+    _offset_ms += diff
+
+
+def localtime(s=None):
+    if s != None:
+        return _utime.localtime(s)
+    else:
+        return _utime.localtime()
+
+
+def mktime(t):
+    return _utime.mktime(t)
+
+
+def alarm(s, cb=None):
+    if cb != None:
+        return _utime.alarm(s, cb)
+    else:
+        return _utime.alarm(s)