diff --git a/database/2025_02_08_013647_create_user_birthdates_table.php b/database/2025_02_08_013647_create_user_birthdates_table.php
new file mode 100644
index 00000000..0ba107cc
--- /dev/null
+++ b/database/2025_02_08_013647_create_user_birthdates_table.php
@@ -0,0 +1,40 @@
+<?php
+use Index\Db\DbConnection;
+use Index\Db\Migration\DbMigration;
+
+final class CreateUserBirthdatesTable_20250208_013647 implements DbMigration {
+    public function migrate(DbConnection $conn): void {
+        $conn->execute(<<<SQL
+            CREATE TABLE msz_users_birthdates (
+                user_id     INT(10) UNSIGNED NOT NULL,
+                birth_year  SMALLINT(6) NULL DEFAULT NULL,
+                birth_month TINYINT(3) UNSIGNED NOT NULL,
+                birth_day   TINYINT(3) UNSIGNED NOT NULL,
+                PRIMARY KEY (user_id),
+                INDEX users_birthdates_index (birth_month, birth_day),
+                CONSTRAINT users_birthdates_users_foreign
+                    FOREIGN KEY (user_id)
+                    REFERENCES msz_users (user_id)
+                    ON UPDATE CASCADE
+                    ON DELETE CASCADE,
+                CONSTRAINT users_birthdates_ensure_month
+                    CHECK (birth_month BETWEEN 1 AND 12),
+                CONSTRAINT users_birthdates_ensure_day
+                    CHECK (birth_day BETWEEN 1 AND 31)
+            ) ENGINE=InnoDB COLLATE='utf8mb4_bin';
+        SQL);
+
+        $conn->execute(<<<SQL
+            INSERT INTO msz_users_birthdates
+            SELECT user_id, IF(YEAR(user_birthdate) <= 1004, NULL, YEAR(user_birthdate)), MONTH(user_birthdate), DAY(user_birthdate)
+            FROM msz_users
+            WHERE user_birthdate IS NOT NULL
+        SQL);
+
+        $conn->execute(<<<SQL
+            ALTER TABLE msz_users
+                DROP COLUMN user_birthdate,
+                DROP INDEX users_birthdate_index;
+        SQL);
+    }
+}
diff --git a/public-legacy/profile.php b/public-legacy/profile.php
index 071ce852..22915836 100644
--- a/public-legacy/profile.php
+++ b/public-legacy/profile.php
@@ -6,7 +6,7 @@ use InvalidArgumentException;
 use RuntimeException;
 use Index\ByteFormat;
 use Misuzu\Parsers\Parser;
-use Misuzu\Users\User;
+use Misuzu\Users\{User,UsersContext};
 use Misuzu\Users\Assets\UserAvatarAsset;
 use Misuzu\Users\Assets\UserBackgroundAsset;
 
@@ -93,6 +93,7 @@ if($isEditing) {
     Template::set([
         'perms' => $perms,
         'background_attachments' => UserBackgroundAsset::getAttachmentStringOptions(),
+        'birthdate_info' => $msz->usersCtx->birthdates->getUserBirthdate($userInfo),
     ]);
 
     if(!empty($_POST)) {
@@ -172,11 +173,14 @@ if($isEditing) {
                     $birthYear  = (int)($_POST['birthdate']['year'] ?? 0);
                     $birthMonth = (int)($_POST['birthdate']['month'] ?? 0);
                     $birthDay   = (int)($_POST['birthdate']['day'] ?? 0);
-                    $birthValid = $msz->usersCtx->users->validateBirthdate($birthYear, $birthMonth, $birthDay);
+                    $birthValid = UsersContext::validateBirthdate($birthYear, $birthMonth, $birthDay);
 
-                    if($birthValid === '')
-                        $msz->usersCtx->users->updateUser($userInfo, birthYear: $birthYear, birthMonth: $birthMonth, birthDay: $birthDay);
-                    else
+                    if($birthValid === '') {
+                        if($birthMonth === 0 && $birthDay === 0)
+                            $msz->usersCtx->birthdates->deleteUserBirthdate($userInfo);
+                        else
+                            $msz->usersCtx->birthdates->updateUserBirthdate($userInfo, $birthYear === 0 ? null : $birthYear, $birthMonth, $birthDay);
+                    } else
                         $notices[] = $msz->usersCtx->users->validateBirthdateText($birthValid);
                 }
             }
@@ -381,4 +385,5 @@ Template::render('profile.index', [
     'profile_avatar_info' => $avatarInfo,
     'profile_background_info' => $backgroundInfo,
     'profile_can_send_messages' => $viewerPermsGlobal->check(Perm::G_MESSAGES_SEND),
+    'profile_age' => $msz->usersCtx->birthdates->getUserAge($userInfo),
 ]);
diff --git a/public-legacy/settings/data.php b/public-legacy/settings/data.php
index 9103d162..495c14c3 100644
--- a/public-legacy/settings/data.php
+++ b/public-legacy/settings/data.php
@@ -143,7 +143,7 @@ if(isset($_POST['action']) && is_string($_POST['action'])) {
                             $tmpFiles[] = db_to_zip($archive, $userInfo, 'perms_calculated',       ['user_id:s:n', 'forum_id:s:n', 'perms_category:s', 'perms_calculated:i']);
                             $tmpFiles[] = db_to_zip($archive, $userInfo, 'profile_fields_values',  ['field_id:s', 'user_id:s', 'format_id:s', 'field_value:s']);
                             $tmpFiles[] = db_to_zip($archive, $userInfo, 'sessions',               ['session_id:s', 'user_id:s', 'session_key:n', 'session_remote_addr_first:a', 'session_remote_addr_last:a:n', 'session_user_agent:s', 'session_country:s', 'session_expires:t', 'session_expires_bump:b', 'session_created:t', 'session_active:t:n']);
-                            $tmpFiles[] = db_to_zip($archive, $userInfo, 'users',                  ['user_id:s', 'user_name:s', 'user_password:n', 'user_email:s', 'user_remote_addr_first:a', 'user_remote_addr_last:a', 'user_super:b', 'user_country:s', 'user_colour:i:n', 'user_created:t', 'user_active:t:n', 'user_deleted:t:n', 'user_display_role_id:s:n', 'user_about_content:s:n', 'user_about_parser:i', 'user_signature_content:s:n', 'user_signature_parser:i', 'user_birthdate:s:n', 'user_background_settings:i:n', 'user_title:s:n']);
+                            $tmpFiles[] = db_to_zip($archive, $userInfo, 'users',                  ['user_id:s', 'user_name:s', 'user_password:n', 'user_email:s', 'user_remote_addr_first:a', 'user_remote_addr_last:a', 'user_super:b', 'user_country:s', 'user_colour:i:n', 'user_created:t', 'user_active:t:n', 'user_deleted:t:n', 'user_display_role_id:s:n', 'user_about_content:s:n', 'user_about_parser:i', 'user_signature_content:s:n', 'user_signature_parser:i', 'user_background_settings:i:n', 'user_title:s:n']);
                             $tmpFiles[] = db_to_zip($archive, $userInfo, 'users_bans',             ['ban_id:s', 'user_id:s', 'mod_id:n', 'ban_severity:i', 'ban_reason_public:s', 'ban_reason_private:s', 'ban_created:t', 'ban_expires:t:n']);
                             $tmpFiles[] = db_to_zip($archive, $userInfo, 'users_password_resets',  ['reset_id:s', 'user_id:s', 'reset_remote_addr:a', 'reset_requested:t', 'reset_code:n']);
                             $tmpFiles[] = db_to_zip($archive, $userInfo, 'users_warnings',         ['warn_id:s', 'user_id:s', 'mod_id:n', 'warn_body:s', 'warn_created:t']);
diff --git a/src/Home/HomeRoutes.php b/src/Home/HomeRoutes.php
index e8cbc99c..8ba5d677 100644
--- a/src/Home/HomeRoutes.php
+++ b/src/Home/HomeRoutes.php
@@ -218,6 +218,7 @@ class HomeRoutes implements RouteHandler, UrlSource {
             $birthdays[] = [
                 'info' => $birthdayInfo,
                 'colour' => $this->usersCtx->getUserColour($birthdayInfo),
+                'age' => $this->usersCtx->birthdates->getUserAge($birthdayInfo),
             ];
 
         $newestMember = [];
diff --git a/src/Users/UserBirthdateInfo.php b/src/Users/UserBirthdateInfo.php
new file mode 100644
index 00000000..2e1c34eb
--- /dev/null
+++ b/src/Users/UserBirthdateInfo.php
@@ -0,0 +1,31 @@
+<?php
+namespace Misuzu\Users;
+
+use Carbon\CarbonImmutable;
+use Index\Db\DbResult;
+
+class UserBirthdateInfo {
+    public function __construct(
+        public private(set) string $userId,
+        public private(set) ?int $year,
+        public private(set) int $month,
+        public private(set) int $day
+    ) {}
+
+    public static function fromResult(DbResult $result): UserBirthdateInfo {
+        return new UserBirthdateInfo(
+            userId: $result->getString(0),
+            year: $result->getIntegerOrNull(1),
+            month: $result->getInteger(2),
+            day: $result->getInteger(3),
+        );
+    }
+
+    public string $birthdate {
+        get => sprintf('%04d-%02d-%02d', $this->year ?? 0, $this->month, $this->day);
+    }
+
+    public CarbonImmutable $bornAt {
+        get => CarbonImmutable::createFromTimestampUTC('Y-m-d', $this->birthdate, 'UTC');
+    }
+}
diff --git a/src/Users/UserBirthdatesData.php b/src/Users/UserBirthdatesData.php
new file mode 100644
index 00000000..a20e20d6
--- /dev/null
+++ b/src/Users/UserBirthdatesData.php
@@ -0,0 +1,68 @@
+<?php
+namespace Misuzu\Users;
+
+use DateTimeInterface;
+use RuntimeException;
+use Index\Db\{DbConnection,DbStatementCache};
+
+class UserBirthdatesData {
+    private DbStatementCache $cache;
+
+    public function __construct(DbConnection $dbConn) {
+        $this->cache = new DbStatementCache($dbConn);
+    }
+
+    public function getUserBirthdate(UserInfo|string $userInfo): ?UserBirthdateInfo {
+        $stmt = $this->cache->get(<<<SQL
+            SELECT user_id, birth_year, birth_month, birth_day
+            FROM msz_users_birthdates
+            WHERE user_id = ?
+        SQL);
+        $stmt->nextParameter($userInfo instanceof UserInfo ? $userInfo->id : $userInfo);
+        $stmt->execute();
+
+        $result = $stmt->getResult();
+        return $result->next() ? UserBirthdateInfo::fromResult($result) : null;
+    }
+
+    public function deleteUserBirthdate(UserInfo|string $userInfo): void {
+        $stmt = $this->cache->get(<<<SQL
+            DELETE FROM msz_users_birthdates
+            WHERE user_id = ?
+        SQL);
+        $stmt->nextParameter($userInfo instanceof UserInfo ? $userInfo->id : $userInfo);
+        $stmt->execute();
+    }
+
+    public function updateUserBirthdate(UserInfo|string $userInfo, ?int $year, int $month, int $day): UserBirthdateInfo {
+        $stmt = $this->cache->get(<<<SQL
+            REPLACE INTO msz_users_birthdates (
+                user_id, birth_year, birth_month, birth_day
+            ) VALUES (?, ?, ?, ?)
+        SQL);
+        $stmt->nextParameter($userInfo instanceof UserInfo ? $userInfo->id : $userInfo);
+        $stmt->nextParameter($year);
+        $stmt->nextParameter($month);
+        $stmt->nextParameter($day);
+        $stmt->execute();
+
+        $birthdateInfo = $this->getUserBirthdate($userInfo);
+        if($birthdateInfo === null)
+            throw new RuntimeException('failed to set birthdate');
+
+        return $birthdateInfo;
+    }
+
+    public function getUserAge(UserInfo|string $userInfo): ?int {
+        $stmt = $this->cache->get(<<<SQL
+            SELECT TIMESTAMPDIFF(YEAR, CONCAT_WS('-', birth_year, birth_month, birth_day), CURDATE())
+            FROM msz_users_birthdates
+            WHERE user_id = ?
+        SQL);
+        $stmt->nextParameter($userInfo instanceof UserInfo ? $userInfo->id : $userInfo);
+        $stmt->execute();
+
+        $result = $stmt->getResult();
+        return $result->next() ? $result->getIntegerOrNull(0) : null;
+    }
+}
diff --git a/src/Users/UserInfo.php b/src/Users/UserInfo.php
index 17572754..7909e50b 100644
--- a/src/Users/UserInfo.php
+++ b/src/Users/UserInfo.php
@@ -25,7 +25,6 @@ class UserInfo {
         public private(set) int $aboutBodyParser,
         public private(set) ?string $signatureBody,
         public private(set) int $signatureBodyParser,
-        public private(set) ?string $birthdateRaw,
         public private(set) ?int $backgroundSettings,
         public private(set) ?string $title,
     ) {}
@@ -49,9 +48,8 @@ class UserInfo {
             aboutBodyParser: $result->getInteger(14),
             signatureBody: $result->getStringOrNull(15),
             signatureBodyParser: $result->getInteger(16),
-            birthdateRaw: $result->getStringOrNull(17),
-            backgroundSettings: $result->getIntegerOrNull(18),
-            title: $result->getString(19),
+            backgroundSettings: $result->getIntegerOrNull(17),
+            title: $result->getString(18),
         );
     }
 
@@ -114,21 +112,4 @@ class UserInfo {
     public bool $isSignatureBodyMarkdown {
         get => $this->signatureBodyParser === Parser::MARKDOWN;
     }
-
-    public bool $hasBirthdate {
-        get => $this->birthdateRaw !== null;
-    }
-
-    public ?CarbonImmutable $birthdate {
-        get => $this->birthdateRaw === null ? null : CarbonImmutable::createFromFormat('Y-m-d', $this->birthdateRaw, 'UTC');
-    }
-
-    public int $age {
-        get {
-            $birthdate = $this->birthdate;
-            if($birthdate === null || (int)$birthdate->format('Y') < 1900)
-                return -1;
-            return (int)$birthdate->diff(CarbonImmutable::now())->format('%y');
-        }
-    }
 }
diff --git a/src/Users/UsersContext.php b/src/Users/UsersContext.php
index 9cf10327..bd5741e9 100644
--- a/src/Users/UsersContext.php
+++ b/src/Users/UsersContext.php
@@ -3,6 +3,7 @@ namespace Misuzu\Users;
 
 use Index\Colour\Colour;
 use Index\Db\DbConnection;
+use Misuzu\Tools;
 
 class UsersContext {
     public private(set) UsersData $users;
@@ -11,6 +12,7 @@ class UsersContext {
     public private(set) WarningsData $warnings;
     public private(set) ModNotesData $modNotes;
     public private(set) UserTotpsData $totps;
+    public private(set) UserBirthdatesData $birthdates;
 
     /** @var array<string, UserInfo> */
     private array $userInfos = [];
@@ -31,6 +33,7 @@ class UsersContext {
         $this->warnings = new WarningsData($dbConn);
         $this->modNotes = new ModNotesData($dbConn);
         $this->totps = new UserTotpsData($dbConn);
+        $this->birthdates = new UserBirthdatesData($dbConn);
     }
 
     public function getUserInfo(string $value, int|string|null $select = null): UserInfo {
@@ -88,4 +91,19 @@ class UsersContext {
     ): bool {
         return $this->tryGetActiveBan($userInfo, $minimumSeverity) !== null;
     }
+
+    public static function validateBirthdate(?int $year, int $month, int $day, int $yearRange = 100): string {
+        $year ??= 0;
+
+        if($day !== 0 && $month !== 0) {
+            $currentYear = (int)date('Y');
+            if($year > 0 && ($year < $currentYear - $yearRange || $year > $currentYear))
+                return 'year';
+
+            if(!Tools::isValidDate($year, $month, $day))
+                return 'date';
+        }
+
+        return '';
+    }
 }
diff --git a/src/Users/UsersData.php b/src/Users/UsersData.php
index b14557d3..d16c28ed 100644
--- a/src/Users/UsersData.php
+++ b/src/Users/UsersData.php
@@ -57,19 +57,26 @@ class UsersData {
         $hasDeleted = $deleted !== null;
 
         $args = 0;
-        $query = 'SELECT COUNT(*) FROM msz_users';
+        $query = <<<SQL
+            SELECT COUNT(u.*) FROM msz_users
+        SQL;
+        if($hasRoleInfo)
+            $query .= <<<SQL
+                LEFT JOIN msz_users_roles AS ur ON u.user_id = ur.user_id
+            SQL;
+
         if($hasRoleInfo) {
             ++$args;
-            $query .= ' WHERE user_id IN (SELECT user_id FROM msz_users_roles WHERE role_id = ?)';
+            $query .= ' WHERE ur.role_id = ?';
         }
         if($hasAfter)
-            $query .= sprintf(' %s user_id > ?', ++$args > 1 ? 'AND' : 'WHERE');
+            $query .= sprintf(' %s u.user_id > ?', ++$args > 1 ? 'AND' : 'WHERE');
         if($hasLastActiveInMinutes)
-            $query .= sprintf(' %s user_active > NOW() - INTERVAL ? MINUTE', ++$args > 1 ? 'AND' : 'WHERE');
+            $query .= sprintf(' %s u.user_active > NOW() - INTERVAL ? MINUTE', ++$args > 1 ? 'AND' : 'WHERE');
         if($hasNewerThanDays)
-            $query .= sprintf(' %s user_created > NOW() - INTERVAL ? DAY', ++$args > 1 ? 'AND' : 'WHERE');
+            $query .= sprintf(' %s u.user_created > NOW() - INTERVAL ? DAY', ++$args > 1 ? 'AND' : 'WHERE');
         if($hasDeleted)
-            $query .= sprintf(' %s user_deleted %s NULL', ++$args > 1 ? 'AND' : 'WHERE', $deleted ? 'IS NOT' : 'IS');
+            $query .= sprintf(' %s u.user_deleted %s NULL', ++$args > 1 ? 'AND' : 'WHERE', $deleted ? 'IS NOT' : 'IS');
 
         $stmt = $this->cache->get($query);
         if($hasRoleInfo)
@@ -163,34 +170,43 @@ class UsersData {
 
         $args = 0;
         $query = <<<SQL
-            SELECT user_id, user_name, user_password, user_email,
-                INET6_NTOA(user_remote_addr_first), INET6_NTOA(user_remote_addr_last),
-                user_super, user_country, user_colour,
-                UNIX_TIMESTAMP(user_created),
-                UNIX_TIMESTAMP(user_active),
-                UNIX_TIMESTAMP(user_deleted),
-                user_display_role_id, user_about_content,
-                user_about_parser, user_signature_content,
-                user_signature_parser, user_birthdate,
-                user_background_settings, user_title
-            FROM msz_users
+            SELECT u.user_id, u.user_name, u.user_password, u.user_email,
+                INET6_NTOA(u.user_remote_addr_first), INET6_NTOA(u.user_remote_addr_last),
+                u.user_super, u.user_country, u.user_colour,
+                UNIX_TIMESTAMP(u.user_created),
+                UNIX_TIMESTAMP(u.user_active),
+                UNIX_TIMESTAMP(u.user_deleted),
+                u.user_display_role_id,
+                u.user_about_content, u.user_about_parser,
+                u.user_signature_content, u.user_signature_parser,
+                u.user_background_settings, u.user_title
+            FROM msz_users AS u
         SQL;
+        if($hasRoleInfo)
+            $query .= <<<SQL
+                LEFT JOIN msz_users_roles AS ur ON u.user_id = ur.user_id
+            SQL;
+        if($hasBirthdate)
+            $query .= <<<SQL
+                LEFT JOIN msz_users_birthdates AS ub ON u.user_id = ub.user_id
+            SQL;
+
         if($hasRoleInfo) {
             ++$args;
-            $query .= ' WHERE user_id IN (SELECT user_id FROM msz_users_roles WHERE role_id = ?)';
+            $query .= ' WHERE ur.role_id = ?';
         }
         if($hasAfter)
-            $query .= sprintf(' %s user_id > ?', ++$args > 1 ? 'AND' : 'WHERE');
+            $query .= sprintf(' %s u.user_id > ?', ++$args > 1 ? 'AND' : 'WHERE');
         if($hasLastActiveInMinutes)
-            $query .= sprintf(' %s user_active > NOW() - INTERVAL ? MINUTE', ++$args > 1 ? 'AND' : 'WHERE');
+            $query .= sprintf(' %s u.user_active > NOW() - INTERVAL ? MINUTE', ++$args > 1 ? 'AND' : 'WHERE');
         if($hasNewerThanDays)
-            $query .= sprintf(' %s user_created > NOW() - INTERVAL ? DAY', ++$args > 1 ? 'AND' : 'WHERE');
+            $query .= sprintf(' %s u.user_created > NOW() - INTERVAL ? DAY', ++$args > 1 ? 'AND' : 'WHERE');
         if($hasDeleted)
-            $query .= sprintf(' %s user_deleted %s NULL', ++$args > 1 ? 'AND' : 'WHERE', $deleted ? 'IS NOT' : 'IS');
+            $query .= sprintf(' %s u.user_deleted %s NULL', ++$args > 1 ? 'AND' : 'WHERE', $deleted ? 'IS NOT' : 'IS');
         if($hasBirthdate)
-            $query .= sprintf(' %s user_birthdate LIKE ?', ++$args > 1 ? 'AND' : 'WHERE');
+            $query .= sprintf(' %s (ub.birth_month = ? AND ub.birth_day = ?)', ++$args > 1 ? 'AND' : 'WHERE');
         if($hasSearchQuery)
-            $query .= sprintf(' %s user_name LIKE CONCAT("%%", ?, "%%")', ++$args > 1 ? 'AND' : 'WHERE');
+            $query .= sprintf(' %s u.user_name LIKE CONCAT("%%", ?, "%%")', ++$args > 1 ? 'AND' : 'WHERE');
         if($hasOrderBy) {
             $query .= sprintf(' ORDER BY %s', $orderBy[0]);
             if($orderBy[1] !== null)
@@ -210,8 +226,10 @@ class UsersData {
             $stmt->nextParameter($lastActiveInMinutes);
         if($hasNewerThanDays)
             $stmt->nextParameter($newerThanDays);
-        if($hasBirthdate)
-            $stmt->nextParameter($birthdate->format('%-m-d'));
+        if($hasBirthdate) {
+            $stmt->nextParameter($birthdate->format('m'));
+            $stmt->nextParameter($birthdate->format('d'));
+        }
         if($hasSearchQuery)
             $stmt->nextParameter($searchQuery);
         if($hasPagination)
@@ -267,9 +285,9 @@ class UsersData {
                 UNIX_TIMESTAMP(user_created),
                 UNIX_TIMESTAMP(user_active),
                 UNIX_TIMESTAMP(user_deleted),
-                user_display_role_id, user_about_content,
-                user_about_parser, user_signature_content,
-                user_signature_parser, user_birthdate,
+                user_display_role_id,
+                user_about_content, user_about_parser,
+                user_signature_content, user_signature_parser,
                 user_background_settings, user_title
             FROM msz_users
         SQL;
@@ -343,9 +361,6 @@ class UsersData {
         ?int $aboutBodyParser = null,
         ?string $signatureBody = null,
         ?int $signatureBodyParser = null,
-        ?int $birthYear = null,
-        ?int $birthMonth = null,
-        ?int $birthDay = null,
         ?int $backgroundSettings = null,
         ?string $title = null
     ): void {
@@ -413,18 +428,6 @@ class UsersData {
             $values[] = $signatureBodyParser;
         }
 
-        if($birthMonth !== null && $birthDay !== null) {
-            if(self::validateBirthdate($birthYear, $birthMonth, $birthDay) !== '')
-                throw new InvalidArgumentException('$birthYear, $birthMonth and $birthDay contain invalid data!');
-
-            // lowest leap year MariaDB accepts, used a 'no year' value
-            if($birthYear < 1004)
-                $birthYear = 1004;
-
-            $fields[] = 'user_birthdate = ?';
-            $values[] = $birthMonth < 1 || $birthDay < 1 ? null : sprintf('%04d-%02d-%02d', $birthYear, $birthMonth, $birthDay);
-        }
-
         if($backgroundSettings !== null) {
             $fields[] = 'user_background_settings = ?';
             $values[] = $backgroundSettings;
@@ -697,21 +700,6 @@ class UsersData {
         };
     }
 
-    public static function validateBirthdate(?int $year, int $month, int $day, int $yearRange = 100): string {
-        $year ??= 0;
-
-        if($day !== 0 && $month !== 0) {
-            $currentYear = (int)date('Y');
-            if($year > 0 && ($year < $currentYear - $yearRange || $year > $currentYear))
-                return 'year';
-
-            if(!Tools::isValidDate($year, $month, $day))
-                return 'date';
-        }
-
-        return '';
-    }
-
     public static function validateBirthdateText(string $error): string {
         return match($error) {
             'year' => 'The year in your birthdate is too ridiculous.',
diff --git a/templates/home/home.twig b/templates/home/home.twig
index e65282ce..8feabf2b 100644
--- a/templates/home/home.twig
+++ b/templates/home/home.twig
@@ -77,14 +77,14 @@
                     {{ container_title('<i class="fas fa-birthday-cake fa-fw"></i> Happy Birthday!') }}
 
                     {% for birthday in birthdays %}
-                        {% set age = birthday.info.age %}
+                        {% set age = birthday.age %}
                         <a class="landing__latest" style="--user-colour: {{ birthday.colour }}" href="{{ url('user-profile', {'user': birthday.info.id}) }}">
                             <div class="landing__latest__avatar">{{ avatar(birthday.info.id, 50, birthday.info.name) }}</div>
                             <div class="landing__latest__content">
                                 <div class="landing__latest__username">
                                     {{ birthday.info.name }}
                                 </div>
-                                {% if age > 0 %}
+                                {% if age is not null %}
                                     <div class="landing__latest__joined">
                                         Turned {{ age }} today!
                                     </div>
diff --git a/templates/profile/_layout/header.twig b/templates/profile/_layout/header.twig
index 406bdacd..73a60318 100644
--- a/templates/profile/_layout/header.twig
+++ b/templates/profile/_layout/header.twig
@@ -42,13 +42,12 @@
                 {% endif %}
 
                 {% set hasCountryCode = profile_user.countryCode != 'XX' %}
-                {% set age = profile_user.age %}
-                {% set hasAge = age > 0 %}
+                {% set hasAge = profile_age is not null %}
                 {% if hasCountryCode or hasAge %}
                     <div class="profile__header__country">
                         {% if hasCountryCode %}<div class="flag flag--{{ profile_user.countryCode|lower }}"></div>{% endif %}
                         <div class="profile__header__country__name">
-                            {% if hasCountryCode %}{{ profile_user.countryCode|country_name }}{% endif %}{% if hasAge %}{% if hasCountryCode %}, {% endif %}{{ age }} year{{ age != 's' ? 's' : '' }} old{% endif %}
+                            {% if hasCountryCode %}{{ profile_user.countryCode|country_name }}{% endif %}{% if hasAge %}{% if hasCountryCode %}, {% endif %}{{ profile_age }} year{{ profile_age != 1 ? 's' : '' }} old{% endif %}
                         </div>
                     </div>
                 {% endif %}
diff --git a/templates/profile/index.twig b/templates/profile/index.twig
index eda19eaa..adaa190e 100644
--- a/templates/profile/index.twig
+++ b/templates/profile/index.twig
@@ -213,14 +213,14 @@
                                             <div class="profile__birthdate__title">
                                                 Day
                                             </div>
-                                            {{ input_select('birthdate[day]', ['-']|merge(range(1, 31)), profile_user.hasBirthdate ? profile_user.birthdate.day : 0, '', '', true, 'profile__birthdate__select profile__birthdate__select--day') }}
+                                            {{ input_select('birthdate[day]', ['-']|merge(range(1, 31)), birthdate_info.day|default(0), '', '', true, 'profile__birthdate__select profile__birthdate__select--day') }}
                                         </label>
 
                                         <label class="profile__birthdate__label">
                                             <div class="profile__birthdate__title">
                                                 Month
                                             </div>
-                                            {{ input_select('birthdate[month]', ['-']|merge(range(1, 12)), profile_user.hasBirthdate ? profile_user.birthdate.month : 0, '', '', true, 'profile__birthdate__select profile__birthdate__select--month') }}
+                                            {{ input_select('birthdate[month]', ['-']|merge(range(1, 12)), birthdate_info.month|default(0), '', '', true, 'profile__birthdate__select profile__birthdate__select--month') }}
                                         </label>
                                     </div>
 
@@ -229,7 +229,7 @@
                                             <div class="profile__birthdate__title">
                                                 Year (may be left empty)
                                             </div>
-                                            {{ input_select('birthdate[year]', ['-']|merge(range(null|date('Y'), null|date('Y') - 100)), profile_user.birthdate.year|default(0), '', '', true, 'profile__birthdate__select profile__birthdate__select--year') }}
+                                            {{ input_select('birthdate[year]', ['-']|merge(range(null|date('Y'), null|date('Y') - 100)), birthdate_info.year|default(0), '', '', true, 'profile__birthdate__select profile__birthdate__select--year') }}
                                         </label>
                                     </div>
                                 </div>