PCT 30/31 Month Deadline Calculators: Architecture & Implementation Guide

The transition from the international Patent Cooperation Treaty (PCT) phase to national or regional prosecution is one of the highest-risk inflection points in patent portfolio management, and a PCT 30/31 month deadline calculator exists to close the specific gap between a WIPO priority date and the jurisdiction-specific filing window it implies. This page specifies how to build that calculator as a deterministic component: given an earliest valid priority date and a set of designated offices, it must return an auditable due date for each office, correctly applying the 30-month baseline of PCT Article 22 (and Article 39 for offices where a demand alters the timeline), the 31-month window used by the European Patent Office (EPO), calendar rollover rules, and business-day shifts. Miscalculation directly triggers application abandonment, so month-based arithmetic, version-pinned rule mapping, and strict input validation are non-negotiable. This component slots into the wider Automated Deadline Calculation & Rule Engines pipeline and hands its output to the National Phase Entry Date Logic module for downstream reminder scheduling.

PCT 30/31-month deadline calculation data flow A vertical pipeline: priority-date ingestion feeds priority-chain resolution, then jurisdiction rule lookup, which branches into a 30-month path for most designated offices (PCT Article 22) and a highlighted 31-month path for the EPO (PCT Article 39). Both branches merge into a calendar and business-day shift, ending in an audit-hashed deadline output. 1 Priority-date ingestion WIPO PATENTSCOPE / docketing sync 2 Priority-chain resolution earliest valid, non-withdrawn claim 3 Jurisdiction rule lookup version-pinned contracting states 30 months Most designated offices (US, JP, CN) PCT Article 22(1) 31 months • EPO European regional phase branch PCT Article 39(1)(b) 4 Calendar / business-day shift roll past weekends & office closures 5 Audit-hashed deadline statutory + docketed date, SHA-256
Deterministic pipeline from an anchored priority date to an audit-hashed entry deadline, with the EPO 31-month branch (PCT Art. 39) separated from the 30-month Art. 22 baseline.

Compliance & Scope Boundaries

A deadline calculator produces computational outputs; it does not constitute legal advice, and every deployment must encode that boundary explicitly. The 30-month baseline of PCT Article 22(1) is a treaty floor that contracting states may extend, restrict, or condition through reservations and national law. The calculator is therefore permitted to compute and surface a candidate deadline, flag applications approaching a statutory limit, and record the exact rule version applied — but it must not auto-file, auto-pay national fees, or suppress a paralegal review gate. Restoration of the priority right (PCT Rule 26bis.3 and Rule 49ter) and discretionary late-entry petitions require substantive attorney judgment and must never be resolved by arithmetic alone.

Scope is deliberately narrow: this component resolves the entry deadline only. Translation-filing windows, national fee schedules, and post-entry examination clocks are separate obligations owned by adjacent modules — for example, the PCT National Phase Entry Rules framework governs the office-by-office documentary requirements that follow entry. When the calculator ingests upstream office data, it must respect the rate limits and robots.txt obligations of the source portals; bulk WIPO status queries should route through the sanctioned polling patterns rather than scraping, and any cached contracting-states data must carry a retrieval timestamp so stale reservations are never silently trusted.

Operational Action: Encode a hard “advice boundary” flag on every response payload and route any application inside a restoration or late-entry window to a mandatory human review queue. Never let the calculator emit a deadline that has bypassed the review gate for a flagged matter.

Prerequisites & Dependency Map

Before the first deadline is computed, the following upstream data sources, libraries, and configuration artifacts must be in place. Treat each as a pinned dependency, not an ambient assumption.

  • Priority data source. A structured priority claim list per application (priority number, country, date, withdrawal status), sourced from your docketing system of record or synced from WIPO PATENTSCOPE. The earliest valid, non-withdrawn claim is the anchor date.
  • Contracting-states rule set. The current 30-vs-31-month window per designated office, derived from the WIPO PCT Contracting States table. Store this as version-controlled configuration, never as inline constants.
  • Holiday calendars. Per-jurisdiction business-day and office-closure data (for the USPTO, U.S. federal holidays under 37 CFR § 1.7; for the EPO, closure days published in the Official Journal). Pin these to a dated release.
  • Library versions. Python 3.11+ (for the stdlib zoneinfo module and datetime improvements), python-dateutil 2.8+ for calendar-aware relativedelta, and pydantic 2.x for request/response validation.
  • Rule-version identifier. A monotonic version string (e.g. pct-rules-2026.02) that stamps every computation so a deadline can be forensically reconstructed against the exact rule snapshot that produced it.

Operational Action: Fail closed. If the contracting-states configuration cannot be loaded, or its retrieval timestamp is older than your staleness threshold, the calculator must refuse to compute rather than fall back to a hardcoded default window.

Step-by-Step Implementation

The calculation proceeds as a short, verifiable pipeline. Each step is independently testable, and no step may mutate state owned by a prior one.

Step 1 — Resolve the priority anchor date

Multi-priority applications require selecting the earliest valid claim while discarding withdrawn or malformed predecessors. Resolve this before any month arithmetic so the entire family tree collapses to a single defensible baseline.

from dataclasses import dataclass
from datetime import date

@dataclass(frozen=True)
class PriorityClaim:
    number: str
    country: str          # ISO 3166-1 alpha-2
    claimed_date: date
    withdrawn: bool = False

def resolve_anchor(claims: list[PriorityClaim]) -> date:
    """Return the earliest non-withdrawn priority date.

    Raises ValueError when no valid claim exists, so downstream
    arithmetic can never run against a missing anchor.
    """
    valid = [c.claimed_date for c in claims if not c.withdrawn]
    if not valid:
        raise ValueError("No valid (non-withdrawn) priority claim to anchor on")
    return min(valid)

Step 2 — Apply corresponding-date month arithmetic

Naive addition with datetime.timedelta fails here because it operates on fixed day counts, not calendar months. PCT deadlines follow the “corresponding date” rule: if the target month lacks the original day (for example, 31 August + 6 months lands in a month with no 31st), the deadline defaults to the last valid day of that month. dateutil.relativedelta clamps to month-end automatically.

from datetime import date
from dateutil.relativedelta import relativedelta

def add_months(anchor: date, months: int) -> date:
    """Add calendar months with corresponding-date rollover.

    relativedelta clamps to month-end when the source day exceeds the
    target month's length (e.g. 31 Aug + 6 months -> 28/29 Feb).
    Reference: https://dateutil.readthedocs.io/en/stable/relativedelta.html
    """
    return anchor + relativedelta(months=months)

Step 3 — Look up the jurisdictional window

Hardcoding thresholds creates compliance drift. Externalize per-office rules into declarative configuration keyed by ISO 3166-1 alpha-2 (plus the EP regional code), each entry carrying a source citation.

# pct_jurisdiction_rules.yaml
# Source: WIPO PCT Contracting States table (verify retrieval date before deploy)
# https://www.wipo.int/pct/en/pct_contracting_states.html
rule_version: "pct-rules-2026.02"
retrieved_utc: "2026-02-01T00:00:00Z"
jurisdictions:
  US:
    base_months: 30          # 35 U.S.C. 371(b); PCT Art. 22(1)
    restoration_available: true
    business_day_shift: "following"
  EP:
    base_months: 31          # PCT Art. 39(1)(b) as applied under the EPC
    restoration_available: true
    business_day_shift: "following"
  JP:
    base_months: 30          # PCT Art. 22(1); JPO national entry
    restoration_available: true
    business_day_shift: "following"
  CN:
    base_months: 30          # PCT Art. 22(1); no reservation on file
    restoration_available: false
    business_day_shift: "following"

Step 4 — Apply the calendar (business-day) shift

A statutory deadline that lands on a weekend or an office-closure day rolls forward to the next business day. Keep this logic separate from the month math so the holiday dataset can be updated independently and the raw statutory date remains inspectable in the audit record.

from datetime import date, timedelta

def shift_to_business_day(target: date, closures: frozenset[date]) -> date:
    """Roll forward to the next open day, skipping weekends and office closures.

    'closures' is a jurisdiction-specific set of non-working dates
    (federal holidays, EPO Official-Journal closure days, etc.).
    """
    while target.weekday() >= 5 or target in closures:  # 5=Sat, 6=Sun
        target += timedelta(days=1)
    return target

Because the corresponding-date rule (Step 2) and the business-day shift (Step 4) are distinct operations, the calculator retains both the raw statutory date and the adjusted date — a distinction that malpractice reviewers rely on when reconstructing why a given date was docketed.

API Contract & Schema

High-throughput docketing systems expect a stateless, idempotent endpoint that returns an audit-ready payload. The Pydantic v2 contract below enforces strict input validation, supports multiple calculation modes, and stamps every result with a rule version and an audit hash so downstream compliance dashboards can trace exactly which configuration snapshot produced a deadline. The same audit-hash discipline underpins the append-only audit trail that governs access to finalized deadline records.

import hashlib
from datetime import date, datetime, timezone
from typing import Literal
from pydantic import BaseModel, Field, field_validator

class PCTDeadlineRequest(BaseModel):
    priority_date: date
    jurisdictions: list[str] = Field(..., min_length=1, max_length=50)
    calculation_mode: Literal["standard", "restoration", "grace_period"] = "standard"
    idempotency_key: str = Field(..., min_length=8, max_length=64)

    @field_validator("jurisdictions")
    @classmethod
    def validate_iso_codes(cls, v: list[str]) -> list[str]:
        if not all(len(code) == 2 and code.isalpha() for code in v):
            raise ValueError("Jurisdictions must be ISO 3166-1 alpha-2 codes")
        return [code.upper() for code in v]

class PCTDeadlineResult(BaseModel):
    jurisdiction: str
    statutory_deadline: date          # raw corresponding-date result
    docketed_deadline: date           # after business-day shift
    applied_months: int
    calculation_mode: str
    rule_version: str
    audit_hash: str
    computed_at_utc: str

def build_audit_hash(
    priority_date: date, jurisdiction: str, docketed: date, rule_version: str
) -> str:
    payload = f"{priority_date.isoformat()}|{jurisdiction}|{docketed.isoformat()}|{rule_version}"
    return hashlib.sha256(payload.encode()).hexdigest()

def now_utc_iso() -> str:
    return datetime.now(timezone.utc).isoformat()

The idempotency_key prevents duplicate docketing during network retries: a repeated request with the same key must return the previously computed result rather than recomputing (and re-emitting) a deadline. The audit_hash is a SHA-256 digest over the priority date, jurisdiction, docketed deadline, and rule version — a change in any input yields a different hash, so silent rule drift becomes detectable.

Edge Cases & Failure Modes

  • Month-end rollover. A 31 August priority date plus 30 months resolves to 28/29 February; relativedelta clamps correctly, but a naive timedelta(days=30*months) silently overshoots. Guard this with an explicit regression test (below).
  • Weekend and holiday collisions. A statutory deadline on a Saturday, Sunday, or office-closure day rolls forward. Note that consecutive closures (a holiday adjoining a weekend) may push the date several days — the loop in Step 4 handles this, but the holiday dataset must be complete for the shift to be correct.
  • Timezone off-by-one. A deadline expiring at 17:00 in Geneva can already be the next calendar day in Tokyo. Normalize priority dates using zoneinfo at the boundary and treat the date as the office-local civil date; never derive it from a machine-local datetime.now().
  • Stale contracting-states data. A contracting state may file or withdraw a reservation that changes its effective window. Trusting a cached rule set past its staleness threshold can silently produce a wrong month count — fail closed instead (see Prerequisites).
  • Article 39 vs Article 22. Filing a demand for international preliminary examination historically affected certain offices’ timelines; most now apply a uniform 30/31-month window regardless, but the calculator must key on the current per-office rule, not a blanket assumption.
  • Retry storms and circuit breaking. When the calculator pulls upstream office data, wrap remote calls with exponential backoff and a circuit breaker so a portal outage degrades gracefully rather than hammering the source or emitting partial results.
from zoneinfo import ZoneInfo
from datetime import datetime

def office_local_date(instant_utc: datetime, office_tz: str) -> date:
    """Project a UTC instant onto an office's civil date.

    Prevents off-by-one docketing when the priority instant straddles
    midnight in the designated office's timezone.
    """
    return instant_utc.astimezone(ZoneInfo(office_tz)).date()

Verification & Regression Testing

Every rule change must pass a regression suite anchored to known dates. These assertions pin the two behaviours most likely to regress silently: corresponding-date rollover and the EPO 31-month divergence.

from datetime import date

def test_epo_uses_31_months() -> None:
    anchor = date(2023, 1, 15)
    assert add_months(anchor, 31) == date(2025, 8, 15)   # EP window

def test_us_uses_30_months() -> None:
    anchor = date(2023, 1, 15)
    assert add_months(anchor, 30) == date(2025, 7, 15)   # US window

def test_month_end_rollover_clamps() -> None:
    # 31 Aug 2022 + 30 months has no 31st in the target month.
    assert add_months(date(2022, 8, 31), 30) == date(2025, 2, 28)

def test_business_day_shift_skips_weekend() -> None:
    # 15 Jul 2023 is a Saturday -> rolls to Monday 17 Jul.
    assert shift_to_business_day(date(2023, 7, 15), frozenset()) == date(2023, 7, 17)

def test_anchor_ignores_withdrawn_claims() -> None:
    claims = [
        PriorityClaim("P1", "US", date(2022, 1, 1), withdrawn=True),
        PriorityClaim("P2", "US", date(2022, 3, 1)),
    ]
    assert resolve_anchor(claims) == date(2022, 3, 1)

Run this suite nightly against historical prosecution data as well as the synthetic fixtures above; a diff between a recomputed deadline and its stored audit_hash is the earliest signal of rule drift or a holiday-dataset regression.

Operational Action Summary

Before promoting the calculator to production, confirm each deployment decision:

  • Pin the rule set. Every response carries a rule_version, and that version maps to an immutable, committed configuration snapshot. No inline window constants.
  • Fail closed. Missing, malformed, or stale contracting-states configuration blocks computation instead of defaulting.
  • Log immutably. Persist inputs, rule version, statutory date, docketed date, and audit hash to append-only storage for forensic reconstruction.
  • Gate on humans. Restoration and late-entry matters route to a mandatory paralegal review queue before any deadline is treated as final.
  • CI/CD for rules. Treat jurisdictional configuration as code — peer review by patent counsel, automated regression run, and version bump on every merge.
  • Degrade gracefully. Backoff plus circuit breaker on all upstream portal calls; never emit a partial batch as if it were complete.

Frequently Asked Questions

What happens if the 30-month PCT deadline falls on a weekend in the US?
The statutory date is computed first, then rolled forward to the next business day under 37 CFR § 1.7. The calculator retains both dates: the raw statutory date for the audit record and the docketed (adjusted) date for reminders.
Why does the EPO use 31 months when most offices use 30?
The EPO applies a 31-month window under PCT Article 39(1)(b) as implemented in the EPC framework, whereas the Article 22(1) baseline used by most offices is 30 months. The calculator keys the window on the designated office, not a global default.
Can the calculator handle applications with multiple priority claims?
Yes. It resolves the earliest valid, non-withdrawn priority claim as the single anchor date before applying month arithmetic. Withdrawn or malformed claims are excluded at the anchoring step.
Should the calculator ever file or pay national fees automatically?
No. It is scoped to compute and surface candidate deadlines. Filing, fee payment, and any restoration or late-entry petition require attorney judgment and must pass through a human review gate.
How do I keep jurisdiction windows current?
Store windows as version-controlled configuration sourced from the WIPO PCT Contracting States table, stamp each snapshot with a retrieval timestamp, and fail closed when the data is older than your staleness threshold.