<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://attuproject.org/wiki?action=history&amp;feed=atom&amp;title=Module%3AAttuCalendar</id>
	<title>Module:AttuCalendar - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://attuproject.org/wiki?action=history&amp;feed=atom&amp;title=Module%3AAttuCalendar"/>
	<link rel="alternate" type="text/html" href="https://attuproject.org/wiki?title=Module:AttuCalendar&amp;action=history"/>
	<updated>2026-09-16T01:05:20Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.44.5</generator>
	<entry>
		<id>https://attuproject.org/wiki?title=Module:AttuCalendar&amp;diff=30273&amp;oldid=prev</id>
		<title>Hnck: fix epoch</title>
		<link rel="alternate" type="text/html" href="https://attuproject.org/wiki?title=Module:AttuCalendar&amp;diff=30273&amp;oldid=prev"/>
		<updated>2026-09-04T17:19:23Z</updated>

		<summary type="html">&lt;p&gt;fix epoch&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw=&quot;interface&quot;&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;en&quot;&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← Older revision&lt;/td&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;Revision as of 13:19, 4 September 2026&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;
  &lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot;&gt;Line 5:&lt;/td&gt;
  &lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot;&gt;Line 5:&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;
  &lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;local M = {}&lt;/div&gt;&lt;/td&gt;
  &lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;
  &lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;local M = {}&lt;/div&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;
  &lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br /&gt;&lt;/td&gt;
  &lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;
  &lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;
  &lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;local EPOCH_UNIX = &lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;1772229600&lt;/del&gt;  -- Unix timestamp of the epoch snapshot&lt;/div&gt;&lt;/td&gt;
  &lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;
  &lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;local EPOCH_UNIX = &lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;1786741200&lt;/ins&gt;  -- Unix timestamp of the epoch snapshot&lt;/div&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;
  &lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;local EPOCH_YEAR = &lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;76&lt;/del&gt;          -- in-universe PC year at that snapshot&lt;/div&gt;&lt;/td&gt;
  &lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;
  &lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;local EPOCH_YEAR = &lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;84&lt;/ins&gt;          -- in-universe PC year at that snapshot&lt;/div&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;
  &lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;local YEAR_DAYS  = &lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;21&lt;/del&gt;          -- real days per in-universe year&lt;/div&gt;&lt;/td&gt;
  &lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;
  &lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;local YEAR_DAYS  = &lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;14&lt;/ins&gt;          -- real days per in-universe year&lt;/div&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td colspan=&quot;2&quot; class=&quot;diff-empty diff-side-deleted&quot;&gt;&lt;/td&gt;
  &lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;
  &lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td colspan=&quot;2&quot; class=&quot;diff-empty diff-side-deleted&quot;&gt;&lt;/td&gt;
  &lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;
  &lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;
  &lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br /&gt;&lt;/td&gt;
  &lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;
  &lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;
  &lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;-- Return the current in-universe year (PC, integer).&lt;/div&gt;&lt;/td&gt;
  &lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;
  &lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;-- Return the current in-universe year (PC, integer).&lt;/div&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;!-- diff cache key attu_wiki:diff:1.41:old-28815:rev-30273:wikidiff2=table:1.14.2:bc2a06be --&gt;
&lt;/table&gt;</summary>
		<author><name>Hnck</name></author>
	</entry>
	<entry>
		<id>https://attuproject.org/wiki?title=Module:AttuCalendar&amp;diff=28815&amp;oldid=prev</id>
		<title>DoomBot: load_modules: sync</title>
		<link rel="alternate" type="text/html" href="https://attuproject.org/wiki?title=Module:AttuCalendar&amp;diff=28815&amp;oldid=prev"/>
		<updated>2026-05-31T20:42:46Z</updated>

		<summary type="html">&lt;p&gt;load_modules: sync&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;-- Module:AttuCalendar&lt;br /&gt;
-- Haracalnde calendar arithmetic for the Attu Project wiki.&lt;br /&gt;
-- Epoch constants mirror scripts/misc/current_year.py; update both when /fix epoch runs.&lt;br /&gt;
&lt;br /&gt;
local M = {}&lt;br /&gt;
&lt;br /&gt;
local EPOCH_UNIX = 1772229600  -- Unix timestamp of the epoch snapshot&lt;br /&gt;
local EPOCH_YEAR = 76          -- in-universe PC year at that snapshot&lt;br /&gt;
local YEAR_DAYS  = 21          -- real days per in-universe year&lt;br /&gt;
&lt;br /&gt;
-- Return the current in-universe year (PC, integer).&lt;br /&gt;
function M.current_year()&lt;br /&gt;
    local elapsed_days = math.floor((os.time() - EPOCH_UNIX) / 86400)&lt;br /&gt;
    return EPOCH_YEAR + math.floor(elapsed_days / YEAR_DAYS)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Parse a Haracalnde date string and return a fractional year.&lt;br /&gt;
--&lt;br /&gt;
-- Accepts:&lt;br /&gt;
--   &amp;quot;d-m y PC&amp;quot;  → positive fractional year (1 PC = 1.0, no year 0)&lt;br /&gt;
--   &amp;quot;d-m y TT&amp;quot;  → negative fractional year (1 TT = -1.0)&lt;br /&gt;
--   &amp;quot;present&amp;quot;   → current_year() as an integer (no fractional part)&lt;br /&gt;
--&lt;br /&gt;
-- The fractional part encodes month and day within the year so that dates&lt;br /&gt;
-- can be compared and used for pixel calculations.&lt;br /&gt;
function M.parse_date(s)&lt;br /&gt;
    s = mw.text.trim(s)&lt;br /&gt;
    if s:lower() == &amp;quot;present&amp;quot; then&lt;br /&gt;
        return M.current_year()&lt;br /&gt;
    end&lt;br /&gt;
    local d, mo, y, era = s:match(&amp;quot;^(%d+)-(%d+)%s+(%d+)%s+(%a+)$&amp;quot;)&lt;br /&gt;
    if not d then&lt;br /&gt;
        error(&amp;quot;AttuCalendar: cannot parse date: &amp;quot; .. tostring(s))&lt;br /&gt;
    end&lt;br /&gt;
    d  = tonumber(d)&lt;br /&gt;
    mo = tonumber(mo)&lt;br /&gt;
    y  = tonumber(y)&lt;br /&gt;
    -- No year 0: 1 TT → -1, 1 PC → 1.&lt;br /&gt;
    if era:upper() == &amp;quot;TT&amp;quot; then&lt;br /&gt;
        y = -y&lt;br /&gt;
    elseif era:upper() ~= &amp;quot;PC&amp;quot; then&lt;br /&gt;
        error(&amp;quot;AttuCalendar: unknown era: &amp;quot; .. era)&lt;br /&gt;
    end&lt;br /&gt;
    -- Fractional offset within year: months are 1-indexed, days are 1-indexed.&lt;br /&gt;
    -- 12 months × 30 days = 360 days/year.&lt;br /&gt;
    local frac = (mo - 1) / 12 + (d - 1) / 360&lt;br /&gt;
    return y + frac&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Wikitext-callable: years between two Haracalnde dates (absolute value).&lt;br /&gt;
-- {{#invoke:AttuCalendar|years_between|&amp;lt;date1&amp;gt;|&amp;lt;date2&amp;gt;[|&amp;lt;decimals&amp;gt;]}}&lt;br /&gt;
-- Either date may be &amp;quot;present&amp;quot;. decimals defaults to 0.&lt;br /&gt;
function M.years_between(frame)&lt;br /&gt;
    local args     = frame.args&lt;br /&gt;
    local d1_str   = mw.text.trim(args[1] or &amp;quot;&amp;quot;)&lt;br /&gt;
    local d2_str   = mw.text.trim(args[2] or &amp;quot;&amp;quot;)&lt;br /&gt;
    local decimals = math.max(0, math.floor(tonumber(mw.text.trim(args[3] or &amp;quot;&amp;quot;)) or 0))&lt;br /&gt;
&lt;br /&gt;
    if d1_str == &amp;quot;&amp;quot; or d2_str == &amp;quot;&amp;quot; then&lt;br /&gt;
        return &amp;#039;&amp;lt;span class=&amp;quot;error&amp;quot;&amp;gt;AttuCalendar: years_between requires two date arguments&amp;lt;/span&amp;gt;&amp;#039;&lt;br /&gt;
    end&lt;br /&gt;
    local ok1, d1 = pcall(M.parse_date, d1_str)&lt;br /&gt;
    local ok2, d2 = pcall(M.parse_date, d2_str)&lt;br /&gt;
    if not ok1 then return &amp;#039;&amp;lt;span class=&amp;quot;error&amp;quot;&amp;gt;AttuCalendar: &amp;#039; .. tostring(d1) .. &amp;#039;&amp;lt;/span&amp;gt;&amp;#039; end&lt;br /&gt;
    if not ok2 then return &amp;#039;&amp;lt;span class=&amp;quot;error&amp;quot;&amp;gt;AttuCalendar: &amp;#039; .. tostring(d2) .. &amp;#039;&amp;lt;/span&amp;gt;&amp;#039; end&lt;br /&gt;
&lt;br /&gt;
    return string.format(&amp;quot;%.&amp;quot; .. decimals .. &amp;quot;f&amp;quot;, math.abs(d2 - d1))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Convert a date string to a fraction in [0, 1] relative to [period_start, period_end].&lt;br /&gt;
-- Returns nil if the date falls outside the period (clamped to 0 or 1 would hide bugs).&lt;br /&gt;
function M.to_frac(date_str, period_start_str, period_end_str)&lt;br /&gt;
    local d     = M.parse_date(date_str)&lt;br /&gt;
    local ps    = M.parse_date(period_start_str)&lt;br /&gt;
    local pe    = M.parse_date(period_end_str)&lt;br /&gt;
    local span  = pe - ps&lt;br /&gt;
    if span == 0 then return 0 end&lt;br /&gt;
    return (d - ps) / span&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return M&lt;/div&gt;</summary>
		<author><name>DoomBot</name></author>
	</entry>
</feed>