Transparency

How your data works.

Timetide handles your data in two distinct ways — your schedule, which never leaves your device, and community stats, which are entirely opt-in. Pick what you want to understand. (Separately, the app runs anonymous usage analytics — no schedule data involved — and, only if you open the optional tip jar, contacts RevenueCat to show prices and process a tip; see the Privacy Policy.)

Data flow · Personal

Your iPhone
All data written locally · SwiftData
Stays on your device
Timetable · lesson schedule · stats
Always
optional iCloud sync
iCloud Private Database
Apple's infrastructure · only you control it
Optional
Your other Apple devices
Synced privately through your iCloud
✕
Timetide servers — not in this chain. Your schedule can't reach them — structurally impossible, not just policy.

01

Your data never leaves your iPhone.

Everything is written to SwiftData — local storage on your device. There's no server-side database because there's no way for the data to get there. Structurally impossible, not just policy.

  • Timetable data
  • Personal stats

02

No account. No profile. Nothing.

Your personal data has no server-side destination — there's nowhere to sign into and no account to create. We have no name, email, or identifier that could connect you to your timetable. Can't sell what we don't have.

  • Your name or email
  • Device or advertising ID
  • IP address or precise location
  • Advertising or cross-app tracking

03

iCloud sync is yours. Not ours.

Enable iCloud sync and your data moves through Apple's encrypted infrastructure — which only you control. We're not in that chain. We never see it pass through.

04

Personal Recap calculated locally.

Every personal stat is calculated on-device. None of it needs to leave to be computed. It's all local arithmetic.1

  1. Personal Recap ships in a later update. The on-device behaviour described here applies from the day it does — no version of it sends your stats anywhere.

Data flow · Community (opt-in only)

Your iPhone
Collects: subjects · lesson types · school · lesson time boundaries
Never collected
Your name Apple ID Location Lesson notes Hardware identifiers
Random UUID generated
No connection to your name, Apple ID, or any identity
Anonymous
weekly sync
Supabase (EU)
EU-hosted Postgres · GDPR-compliant
Profile counters
School, faculty & student type stored as aggregate counts only. No individual record. Increments and decrements when you change a value.
Stats row (upsert)
One anonymous row per user per year. Weekly sync replaces the previous entry — never accumulates. Keyed to your random UUID only.
Year in Review
Anonymised community stats · published annually

01

Off by default. You choose.

If you opt in, your anonymised data contributes to the community Year in Review — aggregate trends across all contributing students, like subject distribution across faculties or lesson type breakdowns by school.1 Your personal data is always private regardless. You can opt out at any time.

02

Only what's necessary. Nothing more.

Never sent
  • Your name
  • Apple ID or email
  • Device or hardware ID
  • Location
  • Lesson notes
Sent (opt-in only)
  • School, faculty & student type
  • Subject names & count
  • Lesson type breakdown
  • Earliest & latest class time

03

Profile on opt-in. Stats weekly.

Profile data (school, faculty, student type) is sent when you opt in. After that it's only updated if you change one of those values — changing your student type doesn't touch your school or faculty count.

Stats data (subject names, lesson type distribution, and the start of your earliest and end of your latest class) is sent weekly — the next time you open the app after 7 days have passed. Each sync replaces the previous entry entirely. If you had 2 subjects last week and now have 3, the community total reflects 3, not 5.

04

Profile fields become counters. Stats become one row.

Profile data (school, faculty, student type) is stored as aggregate counts only — the server tracks how many students belong to each value, not a list of who they are. When you change your student type, the old count decrements and the new one increments. No individual record is kept.

Stats data is stored as one anonymous row per user per year, always reflecting your latest state. Each row is keyed only to a random UUID — we can see what an anonymous user contributed, but there is no way to connect that to a person, a name, or any identity.

05

Data belongs to the year it happened.

Stats are bucketed by the year the events actually occurred — not the year the sync runs. Subject and lesson creation is attributed to the year the item was created. Stats are also sent per semester, dated by when the semester ran.

This means if you create lessons in December 2026 but the app syncs in January 2027, that data still counts towards the 2026 Year in Review — not 2027.

06

A random ID. No identity attached.

Each weekly sync needs to update an existing entry rather than create a duplicate, so the app generates a random UUID the moment you first opt in. This UUID is per user — not per device. It has no connection to your name, Apple ID, or any personal information. It's stored in iCloud Key-Value Store — a separate, always-on iCloud mechanism that works independently of the app's iCloud sync setting — so it carries over automatically whenever you get a new device on the same iCloud account.

If you opt out and opt back in, the same UUID is reused — your data slot stays consistent and no duplicate entry is created. A new UUID is only generated if you sign into a different iCloud account, in which case previously contributed data becomes permanently orphaned and cannot be identified or removed.

07

Anonymous data isn't personal data.

Under GDPR, truly anonymous data falls outside the definition of personal data — there's no identifiable data subject and no way to link contributions back to an individual. That's not a loophole. It's why we designed the system this way: anonymise before it leaves your device, aggregate on arrival, store nothing that could identify you.

✓

Right to object — Opt out anytime in Settings → Community Year in Review. Syncing stops immediately. No forms, no waiting.

Supabase EU · GDPR-compliant · Debug builds are excluded from Year in Review totals · Questions: hello@timetide.eu

The sync, field by field

Every field, accounted for.

The complete shape of a weekly sync — taken from the code that sends it. If it isn't here, it never leaves the phone.

Sync payload schema — mirrors the app's sync code

FieldTypeExampleWhy it exists
Profile sent on opt-in — after that, only when a value changes
universitytextCharles UniversityPowers the schools chart. Set by you in the app — there is no account to read it from.
facultytextComputer SciencePowers the faculty breakdown.
studentTypetext, optionaluniversityLets stats be split by study level. Optional — absent if you never set it.
universityChanged · facultyChanged · studentTypeChangedbool ×3falseTell the server whether a profile counter needs to move at all. On a normal weekly sync, all three are false.
previousUniversity · previousFaculty · previousStudentTypetext, optional ×3—Included only when a value just changed, so the server can decrement the old counter as it increments the new one. Otherwise absent.
Stats one bucket per calendar year, cumulative — replaced in full on every sync
statsByYearmap year → stats{ "2026": … }Everything below lives in per-year buckets, so December data synced in January still lands in the right review.
subjectCountint9How many subjects you studied that year.
subjectNamestext[]["Linear Algebra", …]Powers "what the world studied". Subject names only — never lesson names, rooms, or notes.
scheduledByTypemap type → intlecture: 86, seminar: 41, …The lesson-types chart. Counts per type, attributed to the year the lesson was created.
earliestStartMinuteint480Start of your earliest lesson, in minutes since midnight (480 = 8:00). Powers the community "earliest class" superlative. Only two boundary times per year — no individual class schedule is sent.
latestEndMinuteint1020End of your latest lesson, in minutes since midnight (1020 = 17:00). Powers the community "latest class" superlative.
timeRangeByTypemap type → minuteslecture: 7740, seminar: 3690, …Total minutes spent per lesson type across the year. Time spent (duration), not times of day — powers the community "hours in class" totals.
Semester stats one entry per semester — replaced in full on every sync
statsBySemesterlist of semesters[ { … } ]The same stats as above, bucketed by when a semester actually ran rather than the year a class was typed in. The semester's name is never sent. Subjects not assigned to a semester are left out.
startYear · startMonth · endYear · endMonthint ×42026 · 2 · 2026 · 6When the semester ran, as month numbers — never season names, because a Feb–Jun term is summer in one hemisphere and autumn in the other.
durationWeeksint18Lets semesters of different lengths be compared fairly.
isCompletebooltrueWhether the semester's end date has passed, so a half-finished term isn't compared with a finished one.
Sync metadata
deviceIduuid8f3a6c2e-…Random, generated at opt-in, kept in iCloud key-value storage — per user, not per device, linked to nothing and no one. Lets each sync replace the last instead of double-counting.
appVersion / appBuildtext ×21.4 / 213Catches version-specific data bugs before they skew the totals.
environmenttextreleaseDebug builds flag themselves and are excluded from every total.

Planned for a future update: anonymous day-of-week and start-hour histograms, which will power the rhythm and start-time charts in the Year in Review. They'll be documented in this table the moment they ship — until then, that data doesn't leave the phone either.

One complete sync — everything that leaves the phone

{
  "deviceId": "8f3a6c2e-91d4-4c7b-b2ae-67f0c14e4b9d",  // random — created at opt-in
  "university": "Charles University",
  "faculty": "Computer Science",
  "studentType": "university",
  "universityChanged": false,    // nothing changed → no profile counters move.
  "facultyChanged": false,       // "previous…" fields are only included
  "studentTypeChanged": false,   // when one of these is true
  "statsByYear": {
    "2026": {
      "subjectCount": 9,
      "subjectNames": ["Linear Algebra", "Operating Systems", "Statistics", "…"],
      "scheduledByType": { "lecture": 86, "seminar": 41, "lab": 28 },
      "earliestStartMinute": 480,   // 8:00 — minutes since midnight
      "latestEndMinute": 1020,       // 17:00 — minutes since midnight
      "timeRangeByType": { "lecture": 7740, "seminar": 3690, "lab": 2520 }  // minutes spent, per type
    }
  },
  "statsBySemester": [
    {
      "startYear": 2026, "startMonth": 2,   // month numbers, never season names
      "endYear": 2026, "endMonth": 6,
      "durationWeeks": 18,
      "isComplete": true,
      "subjectCount": 5,
      "…": "same stats as each year above"   // no semester name
    }
  ],
  "appVersion": "1.4",
  "appBuild": "213",
  "environment": "release"       // debug builds are excluded from totals
}

No name. No email. No hardware identifiers. No lesson names, no rooms, no notes, no individual class times or schedule — just the two boundary times (earliest start, latest end) and the total minutes spent per lesson type. A sync is the year's running totals — not a copy of your timetable. And it's write-only by design: the app can call exactly one server function — sync. Nothing reads individual rows back out.

Summary

Data When sent How stored Linked to you?
School name On opt-in; if changed Aggregate count No
Faculty On opt-in; if changed Aggregate count No
Student type On opt-in; if changed Aggregate count No
Subject names & count On opt-in, then weekly Per-user row (upsert) No — UUID only
Lesson type distribution On opt-in, then weekly Per-user row (upsert) No — UUID only
Earliest & latest class time On opt-in, then weekly Per-user row (upsert) No — UUID only
Your name Never — —
Apple ID / email Never — —
  1. No Community Year in Review has been published yet. The first edition comes after Timetide has been available for a full academic year — the figures on the preview pages are sample data until then.