<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Print Systems Lab]]></title><description><![CDATA[Technical articles on deterministic calendar grids, printable A4 and Letter layouts, SVG/PDF rendering, civil dates and paper-oriented interface design.]]></description><link>https://2027.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6a943ae441f0dc5b4c7acc9d/727f117b-6250-41b6-9459-35a3da647f75.png</url><title>Print Systems Lab</title><link>https://2027.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Tue, 13 Oct 2026 16:51:34 GMT</lastBuildDate><atom:link href="https://2027.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[The Calendar Layout Bugs You Only Find on Paper: A4, Letter, Six-Week Months and Year Boundaries]]></title><description><![CDATA[A calendar can pass every date-arithmetic test and still print incorrectly. Its day numbers may be correct while a column clips, writing cells become too short, or the last week moves onto a second pa]]></description><link>https://2027.hashnode.dev/the-calendar-layout-bugs-you-only-find-on-paper-a4-letter-six-week-months-and-year-boundaries</link><guid isPermaLink="true">https://2027.hashnode.dev/the-calendar-layout-bugs-you-only-find-on-paper-a4-letter-six-week-months-and-year-boundaries</guid><category><![CDATA[Testing]]></category><category><![CDATA[Software Engineering]]></category><dc:creator><![CDATA[Beta Calendars]]></dc:creator><pubDate>Wed, 07 Oct 2026 08:24:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a943ae441f0dc5b4c7acc9d/6aec8828-0177-40df-b028-93d1b899dd71.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A calendar can pass every date-arithmetic test and still print incorrectly. Its day numbers may be correct while a column clips, writing cells become too short, or the last week moves onto a second page. These defects live at the seams between civil time, grid policy, page geometry and the output device.</p>
<p>This guide treats those seams as explicit contracts. A compact fixture window—November and December 2026, followed by January and February 2027—crosses a year boundary, a Sunday-start month, and a common-year February. It gives a reproducible starting point for failures that otherwise get dismissed as ‘works on my machine.’</p>
<h2>A boundary fixture</h2>
<p>The values below use Gregorian dates and Monday-first weeks. Natural grids use the minimum whole number of rows; fixed grids reserve six rows.</p>
<table>
<thead>
<tr>
<th>Month</th>
<th>Days</th>
<th>Starts on</th>
<th>Monday offset</th>
<th>Natural rows</th>
<th>Fixed rows</th>
</tr>
</thead>
<tbody><tr>
<td>November 2026</td>
<td>30</td>
<td>Sunday</td>
<td>6</td>
<td>6</td>
<td>6</td>
</tr>
<tr>
<td>December 2026</td>
<td>31</td>
<td>Tuesday</td>
<td>1</td>
<td>5</td>
<td>6</td>
</tr>
<tr>
<td>January 2027</td>
<td>31</td>
<td>Friday</td>
<td>4</td>
<td>5</td>
<td>6</td>
</tr>
<tr>
<td>February 2027</td>
<td>28</td>
<td>Monday</td>
<td>0</td>
<td>4</td>
<td>6</td>
</tr>
</tbody></table>
<p>For a day whose weekday index is <code>w</code> and a configured week start <code>s</code>, the leading offset is <code>(w - s) % 7</code>. With <code>D</code> days, the natural row count is <code>(offset + D + 6) // 7</code>. Fixed mode has 42 slots. These are different display policies; don’t let a renderer switch between them implicitly.</p>
<h2>Fifteen failures worth making visible</h2>
<ol>
<li><strong>Wrong weekday offset.</strong> A Sunday-first index is passed to a Monday-first grid. Assert the normalized offset for all seven week starts.</li>
<li><strong>Time-zone date shift.</strong> A civil date is stored as UTC midnight and reformatted west of UTC. Keep month topology in date-only values; test that changing the process time zone does not move a day.</li>
<li><strong>Fixed-grid overflow.</strong> The renderer adds a seventh row instead of enforcing 42 slots. Assert both slot count and current-month day count.</li>
<li><strong>Stale natural-grid height.</strong> A six-row month changes to four rows but retains the old cell height. Derive cell height from the actual row count each render.</li>
<li><strong>Treating six rows as the month definition.</strong> Fixed and natural grids serve different layouts. Test each policy separately.</li>
<li><strong>Leap-year mistakes.</strong> A divisibility-by-four shortcut makes 1900 incorrectly leap. Check 1900, 2000, 2024 and 2027.</li>
<li><strong>Year rollover defects.</strong> Incrementing December to month 13 skips January. Assert the chronological sequence <code>(2026, 12) → (2027, 1)</code>.</li>
<li><strong>Paper aspect-ratio reuse.</strong> A4 and US Letter have different proportions. Recalculate from each page box, not width alone.</li>
<li><strong>Print-margin clipping.</strong> CSS page margins cannot enlarge a printer’s imageable area. Keep critical content inside a safe inset and verify at 100%.</li>
<li><strong>SVG physical-size mismatch.</strong> A correct <code>viewBox</code> does not set the physical sheet. Check outer width and height as well as aspect ratio.</li>
<li><strong>PDF page-box mismatch.</strong> Keep the PDF MediaBox derived from the same paper model as SVG; Letter is 612 × 792 points, A4 about 595.28 × 841.89.</li>
<li><strong>Writing-space collapse.</strong> Title, weekday labels and notes consume the grid area. Assert minimum cell dimensions after reserving these regions.</li>
<li><strong>Adjacent-month ambiguity.</strong> A zero or duplicated neighboring date is not an empty slot. Represent out-of-month cells explicitly.</li>
<li><strong>Locale mismatch.</strong> Runtime defaults can change week starts and labels. Make both explicit inputs and verify header order against date placement.</li>
<li><strong>Screen/print divergence.</strong> Screen-only controls or dark backgrounds leak into print. Test the print stylesheet and the page box independently from the viewport.</li>
</ol>
<h2>The page is not the viewport</h2>
<p>A responsive screen grid can be fluid; a paper layout has fixed dimensions, margins and printer constraints. Use millimetres, inches or points for page-level geometry, and convert once. CSS defines one inch as 96 CSS pixels and one point as 1/72 inch. SVG’s <code>viewBox</code> describes drawing coordinates, while its outer dimensions describe the viewport. PDF pages use points. They are related coordinate systems, not interchangeable declarations.</p>
<p>A basic print profile could be: <code>@page { size: A4 portrait; margin: 12mm; }</code>. Reserve the margins before dividing the remaining body into rows. The browser can request A4; it cannot guarantee borderless printing. Keep essential rules and labels away from sheet edges, and state whether “fit to page” scaling is acceptable because it changes writing-cell size.</p>
<h2>Make failures reproducible</h2>
<p>Record year, month, week start, grid policy, paper, orientation, margins, notes area and renderer version with every failing output. ‘February is broken’ is not enough. A useful fixture says, for example: 2027-02, Sunday-first, natural grid, Letter landscape, 12 mm margins, notes enabled. Then assert the expected day sequence, row count, cell bounds and serialized page box.</p>
<p>Keep checks layered: first date topology; then positive geometry and containment; then SVG/PDF page dimensions; finally visual and physical print checks. A screenshot can reveal a clipped numeral, but it cannot prove that a PDF is 210 mm wide. For a device proof, print a ruler and safe-area marks at actual size, measure them, and record the printer and scaling setting. A driver can shrink a correct PDF, and a hardware margin can clip content that remains inside the nominal page.</p>
<p>Test boundaries by name as well as with broad property loops. November 2026 exercises a Sunday start and six natural rows. December-to-January catches the civil-year transition. February 2027 verifies common-year length and a four-row natural grid. Add separate leap-century cases; don’t infer leap behavior from the year-turn fixture. Include all seven week starts so a locale default cannot hide in the host environment.</p>
<p>The <a href="https://2027.hashnode.dev/the-geometry-behind-a-printable-year-building-a-deterministic-calendar-rendering-pipeline">architecture and deterministic pipeline article</a> describes how to separate civil dates, grid topology, layout constraints and rendering. Here are useful human-readable comparisons: <a href="https://www.betacalendars.com/november-calendar.html">November</a>, <a href="https://www.betacalendars.com/december-calendar.html">December</a>, <a href="https://www.betacalendars.com/january-calendar.html">January</a>, <a href="https://www.betacalendars.com/february-calendar.html">February</a>, and the <a href="https://www.betacalendars.com/blank-calendar">blank calendar layout</a>.</p>
<h2>References</h2>
<ul>
<li><a href="https://docs.python.org/3/library/datetime.html">Python date and datetime documentation</a></li>
<li><a href="https://www.w3.org/TR/css-values-4/">W3C CSS Values and Units</a></li>
<li><a href="https://www.w3.org/TR/SVG2/coords.html">W3C SVG coordinate systems</a></li>
<li><a href="https://www.w3.org/TR/css-page-3/">W3C CSS Paged Media</a></li>
<li><a href="https://www.iso.org/standard/36631.html">ISO 216 paper sizes</a></li>
<li><a href="https://www.archives.gov/research/catalog/lcdrg/authority-lists/dimension">US National Archives: Letter dimensions</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[The Geometry Behind a Printable Year: Building a Deterministic Calendar Rendering Pipeline]]></title><description><![CDATA[A printable calendar is often treated as a date-formatting feature: find the first day, place numbers in boxes, then call window.print(). That description skips most of the hard parts. A reliable resu]]></description><link>https://2027.hashnode.dev/the-geometry-behind-a-printable-year-building-a-deterministic-calendar-rendering-pipeline</link><guid isPermaLink="true">https://2027.hashnode.dev/the-geometry-behind-a-printable-year-building-a-deterministic-calendar-rendering-pipeline</guid><category><![CDATA[Python]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[SVG]]></category><dc:creator><![CDATA[Beta Calendars]]></dc:creator><pubDate>Wed, 07 Oct 2026 08:22:05 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a943ae441f0dc5b4c7acc9d/cf7ea9c5-a6a6-4b1d-b5b1-9833bdea145d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A printable calendar is often treated as a date-formatting feature: find the first day, place numbers in boxes, then call <code>window.print()</code>. That description skips most of the hard parts. A reliable result must preserve civil-date meaning, produce a grid under a stated week-start convention, allocate legible writing space, fit a physical sheet, and render the same structure across SVG, PDF and browser print paths.</p>
<p>The useful mental model is a pipeline:</p>
<pre><code class="language-text">CivilDate → MonthTopology → WeekGrid → LayoutConstraints → PaperGeometry → SVG/PDF/Print → Validation
</code></pre>
<p>Each stage has a narrow contract. When a rendering bug appears, those contracts make it possible to tell whether the error came from date arithmetic, grid policy, geometry or output conversion.</p>
<h2>Start with civil dates, not timestamps</h2>
<p>A calendar page describes civil dates. A month is a year and month in a calendar system; it is not an interval of UTC instants. Turning “January 1” into midnight UTC and then formatting it in a western time zone can display December 31. The conversion is internally consistent, but it answers a different question.</p>
<p>Keep the calendar model in date-only values. In Python, <code>datetime.date</code> models a proleptic Gregorian date and has no time-of-day or time-zone component. Its <code>weekday()</code> method numbers Monday as zero through Sunday as six. That makes it a useful boundary type for month topology. Use timestamps only at actual event boundaries, and convert them to the intended civil zone before extracting a date.</p>
<p>The Gregorian month length is determined by the year and month. A leap year is divisible by four, except century years unless divisible by 400. This rule should live in one tested function rather than being reconstructed inside a view template.</p>
<pre><code class="language-python">from calendar import monthrange
from datetime import date


def days_in_month(year: int, month: int) -&gt; int:
    if not 1 &lt;= month &lt;= 12:
        raise ValueError("month must be in 1..12")
    return monthrange(year, month)[1]


def monday_offset(year: int, month: int, week_starts_on: int = 0) -&gt; int:
    """Return leading cells before day 1; Monday=0 ... Sunday=6."""
    if not 0 &lt;= week_starts_on &lt;= 6:
        raise ValueError("week_starts_on must be in 0..6")
    first_weekday = date(year, month, 1).weekday()
    return (first_weekday - week_starts_on) % 7
</code></pre>
<p>The modulo operation is the normalization step. If a month starts on Friday (weekday 4) and the week begins Monday (0), four leading cells are needed. If the week starts Sunday (6), the same Friday start needs five. The month did not change; only the presentation convention did.</p>
<h2>Month topology is separate from grid policy</h2>
<p>For a month with <code>D</code> days and leading offset <code>L</code>, the minimum row count is:</p>
<pre><code class="language-text">rows = ceil((L + D) / 7) = (L + D + 6) // 7
</code></pre>
<p>A natural grid uses that minimum number of rows. It avoids empty rows, but its height varies between four, five and six weeks. A fixed grid always reserves six rows, yielding a 42-cell invariant. Fixed grids align every month in a multi-month layout and simplify downstream drawing; natural grids can give each day a taller cell on a single-month sheet.</p>
<p>These are product choices, not competing definitions of a month. A renderer should accept a grid policy explicitly instead of silently padding or trimming. In a natural grid, storage can still be padded to whole rows; only the visible row count changes.</p>
<pre><code class="language-python">def build_month_grid(year: int, month: int, week_starts_on: int = 0,
                     fixed_six_rows: bool = False) -&gt; list[list[int | None]]:
    count = days_in_month(year, month)
    offset = monday_offset(year, month, week_starts_on)
    rows = 6 if fixed_six_rows else (offset + count + 6) // 7
    slots: list[int | None] = [None] * offset + list(range(1, count + 1))
    slots += [None] * (rows * 7 - len(slots))
    return [slots[i:i + 7] for i in range(0, len(slots), 7)]
</code></pre>
<p>The null cells are intentional. They mean “outside this month,” not the last day of the previous month and not a zero-valued date. Keeping that distinction in the model lets an adjacent-month design opt in to neighboring dates later, without corrupting the basic month grid.</p>
<p>For every valid month, the following invariants should hold:</p>
<ul>
<li><p>Flattening the grid and removing empty cells yields exactly <code>1..D</code>, in order.</p>
</li>
<li><p>Every row has seven positions.</p>
</li>
<li><p>The number of current-month cells is <code>D</code>.</p>
</li>
<li><p>A fixed grid has exactly six rows and 42 positions.</p>
</li>
<li><p>A natural grid has the minimum row count from the equation above.</p>
</li>
</ul>
<h2>Paper geometry is a separate coordinate system</h2>
<p>A4 portrait is 210 × 297 mm. US Letter portrait is 8.5 × 11 inches, or 215.9 × 279.4 mm. The paper choice changes the usable shape: Letter is wider and shorter than A4. A layout that works on one is not proven on the other.</p>
<p>Start with page dimensions, subtract margins and reserve any title, weekday-label, notes or footer regions. The remainder is the calendar grid’s usable rectangle. If the grid has seven columns and <code>R</code> rows, a simple uniform cell has width <code>grid_width / 7</code> and height <code>grid_height / R</code>. Validate that both are positive and above the product’s minimums for readable numerals and handwriting.</p>
<pre><code class="language-python">from dataclasses import dataclass

@dataclass(frozen=True)
class Paper:
    width_mm: float
    height_mm: float

@dataclass(frozen=True)
class Insets:
    top_mm: float
    right_mm: float
    bottom_mm: float
    left_mm: float

A4 = Paper(210.0, 297.0)
US_LETTER = Paper(215.9, 279.4)


def usable_area(paper: Paper, inset: Insets, title_mm: float,
                weekday_header_mm: float, notes_mm: float = 0) -&gt; tuple[float, float]:
    width = paper.width_mm - inset.left_mm - inset.right_mm
    height = (paper.height_mm - inset.top_mm - inset.bottom_mm
              - title_mm - weekday_header_mm - notes_mm)
    if width &lt;= 0 or height &lt;= 0:
        raise ValueError("margins and reserved regions consume the page")
    return width, height
</code></pre>
<p>Portrait and landscape are not just rotations of a preview screenshot. They alter the ratio available to the grid and often change what “comfortable writing space” means. Treat orientation as a transform of page dimensions before computing layout. Do not rotate only the final image and expect text or margins to stay correct.</p>
<p>The CSS absolute units define useful conversions: 1 inch is 96 CSS pixels and 1 point is 1/72 inch. For print-oriented geometry, use millimetres, inches or points at the page boundary and convert once. Repeatedly rounding between units can introduce drift. Browser CSS pixels are a convenient screen coordinate, but a CSS pixel is not a promise that a home printer will lay ink on a physical 1/96-inch square.</p>
<h2>Turn the grid into a layout contract</h2>
<p>The grid is a topological structure; assigning rectangles is a separate calculation. Let the safe page rectangle begin at <code>(x₀, y₀)</code> and have width <code>W</code> and height <code>H</code>. Reserve the title and weekday header before splitting the remainder into <code>R</code> equal rows. For column <code>c</code> and row <code>r</code>, the cell origin is <code>x₀ + c(W/7)</code> and <code>y₀ + r(H/R)</code>. Derive every edge from the same origin and step instead of accumulating rounded widths. If the first six columns are rounded independently, the seventh can end one or more device pixels outside the intended boundary.</p>
<p>For handwriting-first calendars, equal cells are a starting point rather than a final design. A title band, weekday row, calendar body and notes region can have different heights. Model them as named regions, then subdivide only the body into date rows. That makes a notes area a real input instead of a late CSS override. It also gives tests stable objects to inspect: a page rectangle, safe rectangle, title rectangle, weekday rectangle, body rectangle and optional notes rectangle.</p>
<p>A layout solver should fail clearly when constraints cannot be met. For example, choose a minimum cell height <code>h_min</code> from the product’s writing-space requirement. The usable calendar-body height must satisfy <code>H_body &gt;= R × h_min</code>; otherwise the renderer can choose a different orientation, remove an optional notes area, or return a constraint error. Silently compressing the cells is the worst option because the PDF remains syntactically valid while the paper becomes unusable.</p>
<pre><code class="language-python">def cell_size(grid_width_mm: float, grid_height_mm: float,
              rows: int, columns: int = 7) -&gt; tuple[float, float]:
    if rows &lt;= 0 or columns != 7:
        raise ValueError("a month grid needs positive rows and seven columns")
    width, height = grid_width_mm / columns, grid_height_mm / rows
    if width &lt;= 0 or height &lt;= 0:
        raise ValueError("grid dimensions must be positive")
    return width, height
</code></pre>
<p>A renderer can keep geometry in floating-point millimetres or points through layout, then round only when rasterizing. SVG supports fractional user coordinates; PDF content streams also support decimal coordinates. Rounding each intermediate margin to a whole CSS pixel creates avoidable error, especially when the page is converted more than once.</p>
<h2>Locale changes labels, not the underlying month</h2>
<p>Week starts and weekday names are presentation inputs. A locale may imply Sunday-first weeks, localized month names, or a different abbreviation width. Do not let the host machine’s locale become an invisible default. Pass <code>week_starts_on</code> explicitly, choose the locale for labels separately, and keep ISO-like machine fixture values independent from translated UI text.</p>
<p>The week-start convention must be visible to readers. If the model is Monday-first but the header says Sunday first, the dates are numerically valid but misleading. Tests should verify the header order and the grid offset together. If Sunday-first is selected, Monday-indexed <code>weekday()</code> values still work after modulo normalization; only the configured start value changes.</p>
<p>Localization also affects page geometry. “Wednesday” may be wider than a two-letter abbreviation; a different script can need a different font fallback; right-to-left text may alter alignment without changing the seven-column sequence. Measure text after font selection, reserve a label region that accommodates the supported strings, and avoid baking translated labels into the date model. A label-overflow check is a typography test, while day placement remains a topology test.</p>
<h2>SVG and PDF need an explicit physical contract</h2>
<p>An SVG <code>viewBox</code> defines a user-space rectangle and maps it into the rendered viewport. It does not, by itself, guarantee the physical paper size. Give the outer SVG a physical width and height (for example, <code>210mm</code> by <code>297mm</code>) and a matching <code>viewBox</code>; then keep all drawing coordinates inside that coordinate system. This lets a browser scale the SVG for display while retaining a paper-sized page for print or PDF conversion.</p>
<p>For PDF, define the page box in points and make the content transform explicit. Since one point is 1/72 inch, a Letter page is 612 × 792 points. An A4 page is approximately 595.28 × 841.89 points. Keep the PDF page dimensions and the SVG physical dimensions derived from the same paper model; do not hard-code a separate set of numbers in a PDF exporter.</p>
<p><code>@page { size: A4 portrait; }</code> communicates page intent to a browser print pipeline, while <code>@media print</code> can remove navigation, backgrounds or interactive controls. The printer’s non-printable region remains device-specific. A page can be correctly sized in CSS and still be clipped by a printer that cannot print to the edge. Offer a safe margin, avoid placing essential lines on the paper boundary, and make the “fit to page” behavior an explicit user choice because scaling changes cell size.</p>
<h2>Reproducibility comes from contracts and fixtures</h2>
<p>A deterministic renderer should take explicit inputs: year, month, week start, grid policy, paper, orientation, margins, reserved regions and output profile. It should not read the system time zone to decide where a date belongs or use locale defaults without recording them. Given the same inputs and version, it should produce the same geometry and content.</p>
<p>A small fixture set should cross temporal edges and layout variants. The following table is computed from Python’s Gregorian calendar implementation using Monday-first weeks; fixed mode reserves six rows in every case. The month references are human-readable comparisons for the rendered shape, not a substitute for numeric assertions.</p>
<table>
<thead>
<tr>
<th>Month</th>
<th>Days</th>
<th>First weekday</th>
<th>Natural rows (Mon-first)</th>
<th>Fixed rows</th>
<th>Human-readable reference</th>
</tr>
</thead>
<tbody><tr>
<td>January</td>
<td>31</td>
<td>Friday</td>
<td>5</td>
<td>6</td>
<td><a href="https://www.betacalendars.com/january-calendar.html">2027 January calendar reference</a></td>
</tr>
<tr>
<td>February</td>
<td>28</td>
<td>Monday</td>
<td>4</td>
<td>6</td>
<td><a href="https://www.betacalendars.com/february-calendar.html">2027 February calendar reference</a></td>
</tr>
<tr>
<td>March</td>
<td>31</td>
<td>Monday</td>
<td>5</td>
<td>6</td>
<td><a href="https://www.betacalendars.com/march-calendar.html">2027 March calendar reference</a></td>
</tr>
<tr>
<td>April</td>
<td>30</td>
<td>Thursday</td>
<td>5</td>
<td>6</td>
<td><a href="https://www.betacalendars.com/april-calendar.html">2027 April calendar reference</a></td>
</tr>
<tr>
<td>May</td>
<td>31</td>
<td>Saturday</td>
<td>6</td>
<td>6</td>
<td><a href="https://www.betacalendars.com/may-calendar.html">2027 May calendar reference</a></td>
</tr>
<tr>
<td>June</td>
<td>30</td>
<td>Tuesday</td>
<td>5</td>
<td>6</td>
<td><a href="https://www.betacalendars.com/june-calendar.html">2027 June calendar reference</a></td>
</tr>
<tr>
<td>July</td>
<td>31</td>
<td>Thursday</td>
<td>5</td>
<td>6</td>
<td><a href="https://www.betacalendars.com/july-calendar.html">2027 July calendar reference</a></td>
</tr>
<tr>
<td>August</td>
<td>31</td>
<td>Sunday</td>
<td>6</td>
<td>6</td>
<td><a href="https://www.betacalendars.com/august-calendar.html">2027 August calendar reference</a></td>
</tr>
<tr>
<td>September</td>
<td>30</td>
<td>Wednesday</td>
<td>5</td>
<td>6</td>
<td><a href="https://www.betacalendars.com/september-calendar.html">2027 September calendar reference</a></td>
</tr>
<tr>
<td>October</td>
<td>31</td>
<td>Friday</td>
<td>5</td>
<td>6</td>
<td><a href="https://www.betacalendars.com/october-calendar.html">2027 October calendar reference</a></td>
</tr>
<tr>
<td>November</td>
<td>30</td>
<td>Monday</td>
<td>5</td>
<td>6</td>
<td><a href="https://www.betacalendars.com/november-calendar.html">2027 November calendar reference</a></td>
</tr>
<tr>
<td>December</td>
<td>31</td>
<td>Wednesday</td>
<td>5</td>
<td>6</td>
<td><a href="https://www.betacalendars.com/december-calendar.html">2027 December calendar reference</a></td>
</tr>
</tbody></table>
<p>The fixed-grid column means six rows for every month. For natural grids, a month may use fewer rows, and that difference must flow into cell height rather than be hidden by clipping. January 2027 begins on Friday; February begins on Monday and has 28 days. Those two months exercise different leading-space and height behavior.</p>
<h2>Test the model before the pixels</h2>
<p>Tests can validate the pure grid independently of typography:</p>
<pre><code class="language-python">from datetime import date
from calendar import monthrange

for year in range(1990, 2031):
    for month in range(1, 13):
        grid = build_month_grid(year, month)
        values = [value for row in grid for value in row if value is not None]
        assert values == list(range(1, monthrange(year, month)[1] + 1))
        assert all(len(row) == 7 for row in grid)
        fixed = build_month_grid(year, month, fixed_six_rows=True)
        assert len(fixed) == 6 and sum(len(row) for row in fixed) == 42
</code></pre>
<p>Then test the geometry model: positive usable dimensions, cells contained inside the grid rectangle, and grid bounds contained inside the printable safe area. Finally, render representative pages and compare both semantic output (dates, labels, page boxes) and visual output (clipping, contrast, line weights). A screenshot diff is useful, but it cannot prove that a PDF has the intended physical page size; inspect page metadata as well.</p>
<p>Accessibility belongs in this pipeline too. A calendar is a table-like structure, so preserve a meaningful reading order, label weekday columns with full accessible names where abbreviations are displayed, and do not use color alone to distinguish states. SVG should expose a title and description when embedded as an image, or the surrounding document should provide equivalent text. Printed output also needs enough contrast and line weight for ordinary printers.</p>
<p>The important design choice is not “SVG versus HTML.” It is whether every stage receives an explicit, testable contract. Civil dates determine month topology; topology determines a grid; layout constraints allocate space; paper geometry anchors the page; renderers serialize that model; validation checks each boundary. Once those responsibilities are separated, the output can be reproduced, compared and debugged without asking the browser to guess what a month means.</p>
<p>For the failure matrix covering paper clipping, week boundaries and regression fixtures, read <a href="https://2027.hashnode.dev/the-calendar-layout-bugs-you-only-find-on-paper-a4-letter-six-week-months-and-year-boundaries">The Calendar Layout Bugs You Only Find on Paper</a>.</p>
<p>A useful implementation review can compare its visual grid with the <a href="https://www.betacalendars.com/blank-calendar">blank calendar layout</a> and the <a href="https://www.betacalendars.com/">Beta Calendars project</a>.</p>
<h2>References</h2>
<ul>
<li><p><a href="https://docs.python.org/3/library/datetime.html">Python <code>datetime</code> and <code>date</code> documentation</a></p>
</li>
<li><p><a href="https://www.w3.org/TR/css-values-4/">W3C CSS Values and Units</a></p>
</li>
<li><p><a href="https://www.w3.org/TR/SVG2/coords.html">W3C SVG coordinate systems and <code>viewBox</code></a></p>
</li>
<li><p><a href="https://www.w3.org/TR/css-page-3/">W3C CSS Paged Media</a></p>
</li>
<li><p><a href="https://www.iso.org/standard/36631.html">ISO 216 paper sizes</a></p>
</li>
<li><p><a href="https://www.archives.gov/research/catalog/authority-lists/dimension">US National Archives dimension authority: Letter paper</a></p>
</li>
</ul>
]]></content:encoded></item></channel></rss>