Ovulation calculator
From the first day of your last period, see your likely ovulation day, fertile window and next period: a period starting 1 March 2026 with a 28-day cycle points to ovulation on 15 March. Plain day counting, same answer in any time zone. Free in your browser.
- Free
- Runs in your browser
- No sign-up
- Your data stays on your device
Run — free
Worked example
- First day of last period
- 2026-03-01
- Cycle length (days)
- 28
- Luteal phase (days)
- 14
- Estimated ovulation date
- 2026-03-15
- Start of fertile window
- 2026-03-10
- End of fertile window
- 2026-03-16
- Next period
- 2026-03-29
free online
How the three dates are worked out
Everything starts from the first day of your last menstrual period. The cycle is split in two: the luteal phase, the days between ovulation and the next period, is treated as fixed at 14 days unless you change it, anywhere from 10 to 16. The days before it absorb any difference in cycle length. So ovulation is estimated at the cycle length minus the luteal phase, counted from that first day. The fertile window runs from five days before ovulation to one day after, six days in all, because sperm can survive several days while the egg stays viable for about a day. The next period is simply one full cycle later: a period on 1 March 2026 with a 28-day cycle points to 29 March.
What the estimate can and cannot tell you
The calculator only counts whole calendar days, with no averaging or hidden adjustments, so the result is exactly as good as the numbers you enter. If your cycle length changes from month to month, the dates reflect only the length you typed, which is why it helps to use your typical cycle and to recalculate when it shifts. The dates do not depend on your time zone, so the same entry gives the same days on any device. Cycles from 20 to 45 days are accepted. Treat the result as general guidance for planning, not as a medical diagnosis or a method of contraception.
The counting-day model
The calculator works from a counting-day model anchored on your last menstrual period. Every date output is produced by adding a whole number of days to the LMP in UTC, never in local time, which keeps results identical regardless of the caller's timezone. For the sample input 2026-03-01 with a 28-day cycle and a 14-day luteal phase, the tool returns an ovulation date of 2026-03-15.
No averaging, smoothing, or hidden constants are involved: it is plain integer day arithmetic on the calendar.
What you can do with it
Cycle tracking apps
Turn a stored last-period date and a user's typical cycle length into ovulation, fertile-window and next-period dates for a calendar view.
Health content and quizzes
Embed a deterministic, timezone-safe estimator on an editorial page without shipping medical logic or calling an external clock.
FAQ
What date format does lmp need?
A strict YYYY-MM-DD string, validated against the real UTC calendar.
Is this medical advice?
No. It is a general estimate based on average cycle timing and should not be used as contraception or a substitute for a clinician.
How is the ovulation date calculated?
The luteal phase is subtracted from the cycle length, and that many days are added to the first day of your last period. With a 28-day cycle and a 14-day luteal phase, ovulation lands 14 days later.
When is my fertile window?
It covers six days: the five days before the estimated ovulation, the ovulation day itself and the day after. For a period starting on 1 March 2026 with default values, it runs from 10 to 16 March.
What if my cycle is longer or shorter than 28 days?
Enter your usual cycle length, anywhere from 20 to 45 days. The luteal phase stays fixed, so ovulation and the fertile window move with the cycle, and the next period is your cycle length after the last one.
Related tools
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/dev/ovulation-calculator \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"lmp":"2026-03-01"}'const res = await fetch("https://api.kit.forhosting.com/dev/ovulation-calculator", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"lmp": "2026-03-01"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/dev/ovulation-calculator",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"lmp": "2026-03-01"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/dev/ovulation-calculator", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"lmp":"2026-03-01"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"lmp":"2026-03-01"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/dev/ovulation-calculator", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"lmp": "2026-03-01"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "dev.ovulation_calculator",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |
Technical notes
Predicts your ovulation date, a six-day fertile window and the next period from the last menstrual period (lmp), cycle_days and luteal_days, using whole-day UTC arithmetic. API from $0.002.
The counting-day model
You pass lmp as a strict YYYY-MM-DD string, plus optional cycle_days (default 28, accepted 20 to 45) and luteal_days (default 14, accepted 10 to 16). The engine computes an ovulation offset of cycle_days minus luteal_days; with the defaults that offset is 14, so the ovulation date lands 14 days after the LMP.
Fertile window and next period
Beyond the single ovulation date, the response maps a fertile window and the expected next period. fertile_start is the ovulation offset minus five days and fertile_end is the offset plus one day, reflecting that sperm can survive several days before ovulation while the egg stays viable for roughly a day after. With the 2026-03-01 example the window runs from fertile_start 2026-03-10 to fertile_end 2026-03-16, six calendar days framing the predicted ovulation on 2026-03-15. Separately, next_period simply adds the full cycle_days to the LMP, giving 2026-03-29 for a 28-day cycle. Because the luteal phase is treated as fixed while the follicular phase absorbs cycle-length variation, changing cycle_days shifts ovulation and the fertile window without moving the luteal count, matching how longer or shorter cycles behave in practice.
Strict dates, typed errors and determinism
Input handling is strict and typed. The output is general fertility guidance, not medical advice or contraception. The lmp value must be a string matching YYYY-MM-DD, and it is re-checked against the real calendar in UTC, so an impossible date such as 2026-02-30 is rejected rather than silently rolled forward. cycle_days and luteal_days must be integers inside their documented ranges; anything outside those ranges, or a missing or non-string LMP, raises a typed invalid_input error naming the offending field instead of an untyped exception.
Integration use cases
Fertility planning
Show a couple the six-day fertile window around predicted ovulation so they can time attempts, adjusting cycle_days and luteal_days to their own history.
Technical questions
What formula does it use?
Ovulation is placed at cycle_days minus luteal_days after the LMP. The fertile window is that offset minus 5 to plus 1 day, and next_period is the LMP plus cycle_days.
What are the default cycle and luteal lengths?
cycle_days defaults to 28 (valid 20-45) and luteal_days defaults to 14 (valid 10-16). Omit them to use the defaults.
Is it free, or does the API cost?
The on-page widget is free (tier A). The API is deterministic and priced from $0.002 per call, charged only on success.