Count down the days to your next big event

The 4-digit display counts down the days to a birthday, a holiday or the end of term. Set the event from your phone, and the ESP32 keeps the date right with internet time.
  • Level:Intermediate
  • Duration: 1 h
  • Ages 11+
  • Platform: ESP32

Tools for this lesson

Helpful for this build. Open a tool to see tips and more projects that use it.

Count down the days to your next big event – EEMENTOR STEM™ lesson

How many days until your birthday? Until the summer holidays? This countdown knows. The ESP32 gets the exact date and time from the internet, and the 4-digit display shows the days left. On the last day it switches to hours and minutes, and when the moment arrives, it shows "PArt" and blinks. You set the event from a web page on your phone, and the ESP32 remembers it even when it is unplugged.

What you'll learn

  • How the ESP32 gets the date and time from the internet (NTP)
  • How to calculate the time between two dates
  • How to drive a 4-digit display without stopping the web server
  • How to save settings so they survive a power cut

What you need

  • ESP32 board, expansion board and USB cable (ESP32 IoT Starter Kit)
  • 4-digit 7-segment display, common cathode (ESP32 IoT Starter Kit)
  • 4 resistors, 220 Ω (ESP32 IoT Starter Kit)
  • 12 female-to-male jumper wires
  • A 2.4 GHz Wi-Fi network
  • The printed parts from the 3D printing box below (countdown frame), or the cardboard version described there
  • Breadboard and jumper wires (ESP32 IoT Starter Kit)

Wiring

Breadboard wiring diagram: Count down the days to your next big event
Breadboard view. Check each connection against the wiring table below.
From To
Display segments a, b, c, d ESP32 GPIO 23, 22, 21, 19
Display segments e, f, g, dp ESP32 GPIO 18, 5, 17, 16
Display digits D1, D2, D3, D4 220 Ω resistor each, then ESP32 GPIO 25, 26, 27, 14

Check your display's pin order in its datasheet: on most 12-pin displays, pin 1 is at the bottom left with the decimal points facing you. The dice lesson explains how the display works.

The sketch

Replace YOUR_WIFI_NAME and YOUR_WIFI_PASSWORD with your own network details (2.4 GHz only).

Change TIME_ZONE to your own time zone. The example is for Central Europe; search for "POSIX time zone" and your city to find yours (for example EET-2EEST,M3.5.0/3,M10.5.0/4 for Estonia or GMT0BST,M3.5.0/1,M10.5.0 for the UK).

#include <WiFi.h>
#include <WebServer.h>
#include <Preferences.h>
#include <time.h>

const char* WIFI_NAME = "YOUR_WIFI_NAME";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* TIME_ZONE = "CET-1CEST,M3.5.0,M10.5.0/3";   // your time zone

const int SEG_PINS[8] = {23, 22, 21, 19, 18, 5, 17, 16};   // a, b, c, d, e, f, g, dp
const int DIGIT_PINS[4] = {25, 26, 27, 14};
const byte NUMBERS[10] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F};
const byte DASH = 0x40, BLANK = 0x00;
const byte PARTY[4] = {0x73, 0x77, 0x50, 0x78};             // P A r t

WebServer server(80);
Preferences prefs;
String eventName;
String eventDate;            // "2026-12-24"
byte shown[4] = {DASH, DASH, DASH, DASH};
int current = 0;
unsigned long lastSwitch = 0;

// Light the next digit for 2 ms. Called all the time from loop(), so it never blocks.
void refresh() {
  if (micros() - lastSwitch < 2000) return;
  lastSwitch = micros();
  digitalWrite(DIGIT_PINS[current], HIGH);           // old digit off
  current = (current + 1) % 4;
  for (int s = 0; s < 8; s++) digitalWrite(SEG_PINS[s], bitRead(shown[current], s));
  digitalWrite(DIGIT_PINS[current], LOW);            // new digit on (common cathode)
}

time_t eventTime() {                 // midnight at the start of the event day
  if (eventDate.length() != 10) return 0;
  struct tm t = {};
  t.tm_year = eventDate.substring(0, 4).toInt() - 1900;
  t.tm_mon = eventDate.substring(5, 7).toInt() - 1;
  t.tm_mday = eventDate.substring(8, 10).toInt();
  t.tm_isdst = -1;
  return mktime(&t);
}

void updateDisplay() {
  time_t now = time(nullptr);
  time_t target = eventTime();
  if (now < 1700000000 || target == 0) {             // no internet time or no event yet
    for (int d = 0; d < 4; d++) shown[d] = DASH;
    return;
  }
  long left = target - now;
  if (left <= 0) {                                   // it's here!
    bool on = (millis() / 500) % 2;
    for (int d = 0; d < 4; d++) shown[d] = on ? PARTY[d] : BLANK;
  } else if (left >= 86400) {                        // days, without leading zeros
    long days = min(left / 86400 + 1, 9999L);        // "1" on the day before
    for (int d = 3; d >= 0; d--) {
      shown[d] = (days > 0 || d == 3) ? NUMBERS[days % 10] : BLANK;
      days /= 10;
    }
  } else {                                           // last day: hours.minutes
    int h = left / 3600, m = (left / 60) % 60;
    shown[0] = NUMBERS[h / 10];
    shown[1] = NUMBERS[h % 10] | ((millis() / 500) % 2 ? 0x80 : 0);   // blinking dot
    shown[2] = NUMBERS[m / 10];
    shown[3] = NUMBERS[m % 10];
  }
}

String safe(String s) {
  s.replace("&", "&amp;");
  s.replace("<", "&lt;");
  s.replace("\"", "&quot;");
  return s;
}

void handleRoot() {
  String html = "<!doctype html><meta name=viewport content='width=device-width'><title>Countdown</title>"
                "<body style='font-family:sans-serif;max-width:420px;margin:24px auto;padding:0 16px'>"
                "<h1>Countdown</h1><p>Now counting down to <b>" + safe(eventName) + "</b> on " + safe(eventDate) + ".</p>"
                "<form action='/set' method='post'>"
                "<p><label>Event<br><input name='name' maxlength='30' value=\"" + safe(eventName) + "\"></label></p>"
                "<p><label>Date<br><input type='date' name='date' value='" + safe(eventDate) + "'></label></p>"
                "<p><button>Save</button></p></form></body>";
  server.send(200, "text/html", html);
}

void handleSet() {
  eventName = server.arg("name");
  eventDate = server.arg("date");
  prefs.putString("name", eventName);
  prefs.putString("date", eventDate);
  Serial.println("New event: " + eventName + " on " + eventDate);
  server.sendHeader("Location", "/");
  server.send(303);
}

void setup() {
  Serial.begin(115200);
  for (int s = 0; s < 8; s++) pinMode(SEG_PINS[s], OUTPUT);
  for (int d = 0; d < 4; d++) {
    pinMode(DIGIT_PINS[d], OUTPUT);
    digitalWrite(DIGIT_PINS[d], HIGH);
  }
  prefs.begin("countdown");
  eventName = prefs.getString("name", "My birthday");
  eventDate = prefs.getString("date", "");

  WiFi.begin(WIFI_NAME, WIFI_PASSWORD);
  Serial.print("Connecting to Wi-Fi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.print(" connected. Open http://");
  Serial.println(WiFi.localIP());
  configTzTime(TIME_ZONE, "pool.ntp.org");
  server.on("/", handleRoot);
  server.on("/set", HTTP_POST, handleSet);
  server.begin();
}

void loop() {
  server.handleClient();
  static unsigned long lastUpdate = 0;
  if (millis() - lastUpdate > 100) {
    lastUpdate = millis();
    updateDisplay();
  }
  refresh();
}

Set your event

Open the Serial Monitor at 115200 baud: after connecting, the ESP32 prints its address, such as http://192.168.1.42. Open it in a browser on a phone or computer in the same Wi-Fi.

  1. Type a name and pick the date, then tap Save.
  2. The display shows the days left, counting today. The number goes down by one at midnight.
  3. On the last day it switches to hours and minutes, with a blinking dot between them.

How it works

Computers count time as the number of seconds since 1 January 1970. The ESP32 asks a time server on the internet for that number (this is called NTP), and configTzTime() tells it your time zone, including summer time. mktime() turns the event date into the same kind of number, so the time left is a simple subtraction. 86,400 seconds make a day.

The display has to be refreshed all the time, one digit after the other, but the web server also needs attention. So refresh() never waits: it checks with micros() whether 2 ms have passed, and only then moves to the next digit. loop() goes round thousands of times a second and does a little of each job every time.

Preferences stores small settings in the ESP32's flash memory, which keeps its contents without power. That's why your event is still there after unplugging.

Try this next

  • Count down in weeks when it's more than 100 days away.
  • Add a buzzer that plays a tune when the countdown reaches zero.
  • Store three events and show the next one automatically.

Troubleshooting

  • The display only shows dashes: the ESP32 has no internet time yet, or no event is saved. Check the Serial Monitor.
  • The number is one day off: check your time zone. The count includes today, so "1" means tomorrow.
  • Some segments are dark or wrong: check the eight segment wires against the table and your display's datasheet.
  • It never connects to Wi-Fi: check the network name and password (both are case-sensitive) and make sure the network is 2.4 GHz.
  • A printed part is too tight or too loose: printers differ by a few tenths of a millimetre. Sand or file a tight hole, and fix a loose part with a drop of hot glue or a strip of tape.

Build it yourself

Everything used in this lesson comes in one kit, with parts tested by our engineers.

ESP32 IoT Starter Kit | Arduino IDE Compatible | Free Tutorials $57.00

Shop the kit