[{"data":1,"prerenderedAt":1150},["ShallowReactive",2],{"navigation_docs":3,"-guide-pieces-and-sets":101,"-guide-pieces-and-sets-surround":1145},[4,23,66],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":22},"Guide","i-lucide-book-open","\u002Fguide","1.guide",[10,14,18],{"title":11,"path":12,"stem":13},"Introduction","\u002Fguide\u002Fintroduction","1.guide\u002F1.introduction",{"title":15,"path":16,"stem":17},"Getting started","\u002Fguide\u002Fgetting-started","1.guide\u002F2.getting-started",{"title":19,"path":20,"stem":21},"Pieces and sets","\u002Fguide\u002Fpieces-and-sets","1.guide\u002F3.pieces-and-sets",false,{"title":24,"icon":25,"path":26,"stem":27,"children":28},"Design (ADRs)","i-lucide-drafting-compass","\u002Fdesign","2.design",[29,32],{"title":30,"path":26,"stem":31},"Design decisions (ADRs)","2.design\u002Findex",{"title":33,"icon":34,"path":35,"stem":36,"children":37,"page":22},"Decision records","i-lucide-scroll-text","\u002Fdesign\u002Fadrs","2.design\u002Fadrs",[38,42,46,50,54,58,62],{"title":39,"path":40,"stem":41},"ADR-001: The refinement-chain model for calendar points","\u002Fdesign\u002Fadrs\u002F001-refinement-chain","2.design\u002Fadrs\u002F001-refinement-chain",{"title":43,"path":44,"stem":45},"ADR-002: Sets first — the Span algebra","\u002Fdesign\u002Fadrs\u002F002-set-primary-spans","2.design\u002Fadrs\u002F002-set-primary-spans",{"title":47,"path":48,"stem":49},"ADR-003: Overflow policy — clamp by default, skip on request","\u002Fdesign\u002Fadrs\u002F003-overflow-policy","2.design\u002Fadrs\u002F003-overflow-policy",{"title":51,"path":52,"stem":53},"ADR-004: Fortnights as every-k scopes","\u002Fdesign\u002Fadrs\u002F004-fortnight-scopes","2.design\u002Fadrs\u002F004-fortnight-scopes",{"title":55,"path":56,"stem":57},"ADR-005: One denotation function, and what DST does to it","\u002Fdesign\u002Fadrs\u002F005-shared-denotation","2.design\u002Fadrs\u002F005-shared-denotation",{"title":59,"path":60,"stem":61},"ADR-006: Zocam is a standalone, released library","\u002Fdesign\u002Fadrs\u002F006-standalone-library","2.design\u002Fadrs\u002F006-standalone-library",{"title":63,"path":64,"stem":65},"ADR-007: A set is one value — struct answers, enumerable shapes","\u002Fdesign\u002Fadrs\u002F007-one-value-sets","2.design\u002Fadrs\u002F007-one-value-sets",{"title":67,"path":68,"stem":69,"children":70,"icon":100},"API Reference","\u002Fapi","3.api\u002Findex",[71,72,76,80,84,88,92,96],{"title":67,"path":68,"stem":69},{"title":73,"path":74,"stem":75},"Zocam","\u002Fapi\u002Fzocam","3.api\u002Fzocam",{"title":77,"path":78,"stem":79},"Zocam.Intervals","\u002Fapi\u002Fzocam-intervals","3.api\u002Fzocam-intervals",{"title":81,"path":82,"stem":83},"Zocam.ISO","\u002Fapi\u002Fzocam-iso","3.api\u002Fzocam-iso",{"title":85,"path":86,"stem":87},"Zocam.Point","\u002Fapi\u002Fzocam-point","3.api\u002Fzocam-point",{"title":89,"path":90,"stem":91},"Zocam.Point.ComposeError","\u002Fapi\u002Fzocam-point-composeerror","3.api\u002Fzocam-point-composeerror",{"title":93,"path":94,"stem":95},"Zocam.Span","\u002Fapi\u002Fzocam-span","3.api\u002Fzocam-span",{"title":97,"path":98,"stem":99},"Zocam.Span.Arc","\u002Fapi\u002Fzocam-span-arc","3.api\u002Fzocam-span-arc","i-lucide-braces",{"id":102,"title":19,"body":103,"description":1139,"extension":1140,"links":1141,"meta":1142,"navigation":413,"path":20,"seo":1143,"stem":21,"__hash__":1144},"docs\u002F1.guide\u002F3.pieces-and-sets.md",{"type":104,"value":105,"toc":1122},"minimark",[106,124,129,137,140,167,170,224,230,240,338,351,355,362,377,425,431,441,456,467,473,495,530,536,550,604,615,619,622,628,643,660,675,681,689,701,729,743,773,778,782,811,821,855,867,874,877,925,930,943,976,986,995,1030,1058,1069,1107,1118],[107,108,109,115,116,123],"p",{},[110,111,114],"span",{"className":112},[113],"ai-slop","AI SLOP"," An AI agent wrote this page. ",[117,118,122],"a",{"href":119,"rel":120},"https:\u002F\u002Fgithub.com\u002Fyuri4n",[121],"nofollow","yuri4n",", a senior engineer, gave the direction and did the review. The review is human, thus errors can stay.",[125,126,128],"h2",{"id":127},"two-questions-place-every-type","Two questions place every type",[107,130,131,132,136],{},"This page gives you the map of the data. When you know which type you hold, you know which operations apply, what shape their answer has, and what ",[133,134,135],"code",{},"Enum"," hands you when you walk the value. The end of this page states the two rules that carry all of that.",[107,138,139],{},"Four types carry all data in zocam. When you meet one, ask two questions:",[141,142,143,156],"ol",{},[144,145,146,147,151,152,155],"li",{},"Does this value hold ",[148,149,150],"strong",{},"one piece"," of time, or a ",[148,153,154],{},"set"," of pieces?",[144,157,158,159,162,163,166],{},"Is it ",[148,160,161],{},"symbolic"," — a calendar shape that repeats, with no timezone — or ",[148,164,165],{},"concrete"," — a window on the real timeline, timezone already resolved?",[107,168,169],{},"The answers form a two-by-two map:",[171,172,173,187],"table",{},[174,175,176],"thead",{},[177,178,179,182,184],"tr",{},[180,181],"th",{},[180,183,150],{},[180,185,186],{},"a set of pieces",[188,189,190,207],"tbody",{},[177,191,192,198,202],{},[193,194,195,197],"td",{},[148,196,161],{}," (repeats)",[193,199,200],{},[133,201,97],{},[193,203,204],{},[133,205,206],{},"Zocam.Span.t",[177,208,209,214,219],{},[193,210,211,213],{},[148,212,165],{}," (happens once)",[193,215,216],{},[133,217,218],{},"Zocam.Intervals.interval",[193,220,221],{},[133,222,223],{},"Zocam.Intervals.t",[107,225,226],{},[227,228,229],"em",{},"Figure 1 — The two questions and the four names they place. AI generated, human reviewed.",[107,231,232,233,235,236,239],{},"A ",[133,234,85],{}," sits below this map: a point names one calendar thing, and ",[133,237,238],{},"Zocam.Span.of\u002F1"," lifts it onto the map as a one-arc set.",[241,242,247],"pre",{"className":243,"code":244,"language":245,"meta":246,"style":246},"language-mermaid shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","flowchart TB\n  subgraph sym[\"SYMBOLIC — repeats, no timezone\"]\n    direction LR\n    arc[\"Zocam.Span.Arc\u003Cbr\u002F>one piece: 'Fri..Mon'\u003Cbr\u002F>struct: bounds + closings + step\"]\n    span[\"Zocam.Span.t\u003Cbr\u002F>a set: 'Wednesdays in May'\u003Cbr\u002F>tagged tree: arcs, union,\u003Cbr\u002F>intersection, complement, nth\"]\n    arc -- \"lives inside an\u003Cbr\u002F>{:arcs, ...} node\" --> span\n  end\n  subgraph conc[\"CONCRETE — happens once, timezone resolved\"]\n    direction LR\n    iv[\"Zocam.Intervals.interval\u003Cbr\u002F>one piece: %{from, until, left, right}\u003Cbr\u002F>plain map, Time\u002FDateTime endpoints\"]\n    ivs[\"Zocam.Intervals.t\u003Cbr\u002F>a set: %Zocam.Intervals{intervals: [...]}\u003Cbr\u002F>sorted, disjoint; [] = empty set\"]\n    iv -- \"listed and\u003Cbr\u002F>compressed\" --> ivs\n  end\n  span -- \"ground\u002F3 (horizon, timezone):\u003Cbr\u002F>each arc instance lands as\u003Cbr\u002F>0, 1, or 2 concrete pieces\" --> ivs\n  iv -. \"absolute!\u002F1 lifts one concrete\u003Cbr\u002F>piece up into the algebra\" .-> span\n","mermaid","",[133,248,249,256,262,268,274,280,286,292,298,303,309,315,321,326,332],{"__ignoreMap":246},[110,250,253],{"class":251,"line":252},"line",1,[110,254,255],{},"flowchart TB\n",[110,257,259],{"class":251,"line":258},2,[110,260,261],{},"  subgraph sym[\"SYMBOLIC — repeats, no timezone\"]\n",[110,263,265],{"class":251,"line":264},3,[110,266,267],{},"    direction LR\n",[110,269,271],{"class":251,"line":270},4,[110,272,273],{},"    arc[\"Zocam.Span.Arc\u003Cbr\u002F>one piece: 'Fri..Mon'\u003Cbr\u002F>struct: bounds + closings + step\"]\n",[110,275,277],{"class":251,"line":276},5,[110,278,279],{},"    span[\"Zocam.Span.t\u003Cbr\u002F>a set: 'Wednesdays in May'\u003Cbr\u002F>tagged tree: arcs, union,\u003Cbr\u002F>intersection, complement, nth\"]\n",[110,281,283],{"class":251,"line":282},6,[110,284,285],{},"    arc -- \"lives inside an\u003Cbr\u002F>{:arcs, ...} node\" --> span\n",[110,287,289],{"class":251,"line":288},7,[110,290,291],{},"  end\n",[110,293,295],{"class":251,"line":294},8,[110,296,297],{},"  subgraph conc[\"CONCRETE — happens once, timezone resolved\"]\n",[110,299,301],{"class":251,"line":300},9,[110,302,267],{},[110,304,306],{"class":251,"line":305},10,[110,307,308],{},"    iv[\"Zocam.Intervals.interval\u003Cbr\u002F>one piece: %{from, until, left, right}\u003Cbr\u002F>plain map, Time\u002FDateTime endpoints\"]\n",[110,310,312],{"class":251,"line":311},11,[110,313,314],{},"    ivs[\"Zocam.Intervals.t\u003Cbr\u002F>a set: %Zocam.Intervals{intervals: [...]}\u003Cbr\u002F>sorted, disjoint; [] = empty set\"]\n",[110,316,318],{"class":251,"line":317},12,[110,319,320],{},"    iv -- \"listed and\u003Cbr\u002F>compressed\" --> ivs\n",[110,322,324],{"class":251,"line":323},13,[110,325,291],{},[110,327,329],{"class":251,"line":328},14,[110,330,331],{},"  span -- \"ground\u002F3 (horizon, timezone):\u003Cbr\u002F>each arc instance lands as\u003Cbr\u002F>0, 1, or 2 concrete pieces\" --> ivs\n",[110,333,335],{"class":251,"line":334},15,[110,336,337],{},"  iv -. \"absolute!\u002F1 lifts one concrete\u003Cbr\u002F>piece up into the algebra\" .-> span\n",[107,339,340],{},[227,341,342,343,346,347,350],{},"Figure 2 — The four names on one map: piece or set (columns), symbolic or concrete (rows). ",[133,344,345],{},"ground\u002F3"," is the only way down; ",[133,348,349],{},"absolute!\u002F1"," is the only way up. AI generated, human reviewed.",[125,352,354],{"id":353},"the-four-names-one-by-one","The four names, one by one",[356,357,359,361],"h3",{"id":358},"zocamspanarc-one-symbolic-piece",[133,360,97],{}," — one symbolic piece",[107,363,364,365,368,369,372,373,376],{},"A struct with two bounds (point chains), a closing per side, an optional step, and an overflow policy. It ",[148,366,367],{},"repeats",": ",[133,370,371],{},"Fri..Mon"," happens once in every week. An arc never travels alone — it always sits inside an ",[133,374,375],{},"{:arcs, scope, grain, arcs}"," node of a span, and the node owns the scope and the grain:",[241,378,382],{"className":379,"code":380,"language":381,"meta":246,"style":246},"language-elixir shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{:arcs, :week, :day, [arc]} =\n  Zocam.Span.arc!(\n    from: Zocam.Point.weekday(:friday),\n    until: Zocam.Point.weekday(:monday)\n  )\n\narc.from   #=> [weekday: :friday]\narc.until  #=> [weekday: :monday]\n","elixir",[133,383,384,389,394,399,404,409,415,420],{"__ignoreMap":246},[110,385,386],{"class":251,"line":252},[110,387,388],{},"{:arcs, :week, :day, [arc]} =\n",[110,390,391],{"class":251,"line":258},[110,392,393],{},"  Zocam.Span.arc!(\n",[110,395,396],{"class":251,"line":264},[110,397,398],{},"    from: Zocam.Point.weekday(:friday),\n",[110,400,401],{"class":251,"line":270},[110,402,403],{},"    until: Zocam.Point.weekday(:monday)\n",[110,405,406],{"class":251,"line":276},[110,407,408],{},"  )\n",[110,410,411],{"class":251,"line":282},[110,412,414],{"emptyLinePlaceholder":413},true,"\n",[110,416,417],{"class":251,"line":288},[110,418,419],{},"arc.from   #=> [weekday: :friday]\n",[110,421,422],{"class":251,"line":294},[110,423,424],{},"arc.until  #=> [weekday: :monday]\n",[356,426,428,430],{"id":427},"zocamspant-a-symbolic-set",[133,429,206],{}," — a symbolic set",[107,432,433,434,437,438,440],{},"A span is ",[148,435,436],{},"not a struct",". It is a recursive tagged tuple — the expression tree of a set algebra. Even a single point lifts to a one-arc ",[227,439,154],{},":",[241,442,444],{"className":379,"code":443,"language":381,"meta":246,"style":246},"Zocam.Span.of(Zocam.Point.month(:may))\n#=> {:arcs, :year, :month, [%Zocam.Span.Arc{from: [month: :may], ...}]}\n",[133,445,446,451],{"__ignoreMap":246},[110,447,448],{"class":251,"line":252},[110,449,450],{},"Zocam.Span.of(Zocam.Point.month(:may))\n",[110,452,453],{"class":251,"line":258},[110,454,455],{},"#=> {:arcs, :year, :month, [%Zocam.Span.Arc{from: [month: :may], ...}]}\n",[107,457,458,459,462,463,466],{},"Why is the set primary? ",[148,460,461],{},"Closure."," The complement of one arc is already two pieces, and a difference can cut one piece into two. A lone piece cannot be closed under its own operators, so every operator answers with a set (",[117,464,465],{"href":44},"ADR-002",").",[356,468,470,472],{"id":469},"zocamintervalsinterval-one-concrete-piece",[133,471,218],{}," — one concrete piece",[107,474,475,476,479,480,483,484,487,488,491,492,494],{},"A plain map with exactly four keys. The endpoints are ",[133,477,478],{},"Time"," or ",[133,481,482],{},"DateTime"," values; ",[133,485,486],{},"nil"," means \"unbounded on this side\" (a ray). Nothing repeats: this window happens once. The ",[133,489,490],{},"horizon"," argument of ",[133,493,345],{}," is exactly one such interval:",[241,496,498],{"className":379,"code":497,"language":381,"meta":246,"style":246},"horizon = %{\n  from: ~U[2026-05-01 00:00:00Z],\n  until: ~U[2026-06-01 00:00:00Z],\n  left: :closed,\n  right: :open\n}\n",[133,499,500,505,510,515,520,525],{"__ignoreMap":246},[110,501,502],{"class":251,"line":252},[110,503,504],{},"horizon = %{\n",[110,506,507],{"class":251,"line":258},[110,508,509],{},"  from: ~U[2026-05-01 00:00:00Z],\n",[110,511,512],{"class":251,"line":264},[110,513,514],{},"  until: ~U[2026-06-01 00:00:00Z],\n",[110,516,517],{"class":251,"line":270},[110,518,519],{},"  left: :closed,\n",[110,521,522],{"class":251,"line":276},[110,523,524],{},"  right: :open\n",[110,526,527],{"class":251,"line":282},[110,528,529],{},"}\n",[356,531,533,535],{"id":532},"zocamintervalst-a-concrete-set",[133,534,223],{}," — a concrete set",[107,537,538,539,542,543,546,547,549],{},"The ",[133,540,541],{},"%Zocam.Intervals{}"," struct wraps one list of intervals, kept sorted and disjoint by ",[133,544,545],{},"compress\u002F1",". An empty list inside is the empty set. ",[133,548,345],{}," is the bridge from the symbolic column to the concrete one:",[241,551,553],{"className":379,"code":552,"language":381,"meta":246,"style":246},"weds = Zocam.Span.of(Zocam.Point.weekday(:wednesday))\nmay  = Zocam.Span.of(Zocam.Point.month(:may))\nspan = Zocam.Span.intersection([weds, may])\n\nZocam.Span.ground(span, horizon, \"Etc\u002FUTC\")\n#=> %Zocam.Intervals{intervals: [\n#=>   %{from: ~U[2026-05-06 00:00:00Z], until: ~U[2026-05-07 00:00:00Z],\n#=>     left: :closed, right: :open},\n#=>   ...three more Wednesdays...\n#=> ]}\n",[133,554,555,560,565,570,574,579,584,589,594,599],{"__ignoreMap":246},[110,556,557],{"class":251,"line":252},[110,558,559],{},"weds = Zocam.Span.of(Zocam.Point.weekday(:wednesday))\n",[110,561,562],{"class":251,"line":258},[110,563,564],{},"may  = Zocam.Span.of(Zocam.Point.month(:may))\n",[110,566,567],{"class":251,"line":264},[110,568,569],{},"span = Zocam.Span.intersection([weds, may])\n",[110,571,572],{"class":251,"line":270},[110,573,414],{"emptyLinePlaceholder":413},[110,575,576],{"class":251,"line":276},[110,577,578],{},"Zocam.Span.ground(span, horizon, \"Etc\u002FUTC\")\n",[110,580,581],{"class":251,"line":282},[110,582,583],{},"#=> %Zocam.Intervals{intervals: [\n",[110,585,586],{"class":251,"line":288},[110,587,588],{},"#=>   %{from: ~U[2026-05-06 00:00:00Z], until: ~U[2026-05-07 00:00:00Z],\n",[110,590,591],{"class":251,"line":294},[110,592,593],{},"#=>     left: :closed, right: :open},\n",[110,595,596],{"class":251,"line":300},[110,597,598],{},"#=>   ...three more Wednesdays...\n",[110,600,601],{"class":251,"line":305},[110,602,603],{},"#=> ]}\n",[107,605,606,607,610,611,614],{},"On a DST fall-back day one arc instance lands as ",[148,608,609],{},"two"," concrete pieces, and inside a spring-forward gap it lands as ",[148,612,613],{},"none"," — that is why the count is \"0, 1, or 2\", never always 1.",[125,616,618],{"id":617},"a-recipe-from-a-sentence-to-concrete-windows","A recipe: from a sentence to concrete windows",[107,620,621],{},"You want the Wednesdays of May 2026 as UTC windows. Do these steps. Step 1 builds points — the raw material below the map. Each step after it lands in one cell of the map above:",[107,623,624,627],{},[148,625,626],{},"Step 1 — name the pieces of the sentence as points."," \"May\" and \"a Wednesday\" are each one calendar thing:",[241,629,631],{"className":379,"code":630,"language":381,"meta":246,"style":246},"may_point = Zocam.Point.month(:may)         # repeats each year\nwed_point = Zocam.Point.weekday(:wednesday) # repeats each week\n",[133,632,633,638],{"__ignoreMap":246},[110,634,635],{"class":251,"line":252},[110,636,637],{},"may_point = Zocam.Point.month(:may)         # repeats each year\n",[110,639,640],{"class":251,"line":258},[110,641,642],{},"wed_point = Zocam.Point.weekday(:wednesday) # repeats each week\n",[107,644,645,648,649,652,653,656,657,659],{},[148,646,647],{},"Step 2 — lift each point into the set algebra."," ",[133,650,651],{},"Span.of\u002F1"," turns a point into a one-arc ",[227,654,655],{},"symbolic set"," (",[133,658,206],{},", top-right cell):",[241,661,663],{"className":379,"code":662,"language":381,"meta":246,"style":246},"may  = Zocam.Span.of(may_point)\nweds = Zocam.Span.of(wed_point)\n",[133,664,665,670],{"__ignoreMap":246},[110,666,667],{"class":251,"line":252},[110,668,669],{},"may  = Zocam.Span.of(may_point)\n",[110,671,672],{"class":251,"line":258},[110,673,674],{},"weds = Zocam.Span.of(wed_point)\n",[107,676,677,680],{},[148,678,679],{},"Step 3 — intersect."," The two sets live in different cycles (year and week), so the result has no finite form yet. It stays symbolic, as data:",[241,682,683],{"className":379,"code":569,"language":381,"meta":246,"style":246},[133,684,685],{"__ignoreMap":246},[110,686,687],{"class":251,"line":252},[110,688,569],{},[107,690,691,694,695,656,698,700],{},[148,692,693],{},"Step 4 — choose a horizon."," The horizon is ",[227,696,697],{},"one concrete piece",[133,699,218],{},", bottom-left cell): the window of the real timeline you care about.",[241,702,703],{"className":379,"code":497,"language":381,"meta":246,"style":246},[133,704,705,709,713,717,721,725],{"__ignoreMap":246},[110,706,707],{"class":251,"line":252},[110,708,504],{},[110,710,711],{"class":251,"line":258},[110,712,509],{},[110,714,715],{"class":251,"line":264},[110,716,514],{},[110,718,719],{"class":251,"line":270},[110,720,519],{},[110,722,723],{"class":251,"line":276},[110,724,524],{},[110,726,727],{"class":251,"line":282},[110,728,529],{},[107,730,731,648,734,736,737,656,740,742],{},[148,732,733],{},"Step 5 — ground.",[133,735,345],{}," crosses from the symbolic row to the concrete row. The answer is a ",[227,738,739],{},"concrete set",[133,741,223],{},", bottom-right cell) — May 2026 has four Wednesdays:",[241,744,746],{"className":379,"code":745,"language":381,"meta":246,"style":246},"Zocam.Span.ground(span, horizon, \"Etc\u002FUTC\")\n#=> %Zocam.Intervals{intervals: [\n#=>   %{from: ~U[2026-05-06 00:00:00Z], until: ~U[2026-05-07 00:00:00Z],\n#=>     left: :closed, right: :open},\n#=>   ...May 13, May 20, May 27...\n#=> ]}\n",[133,747,748,752,756,760,764,769],{"__ignoreMap":246},[110,749,750],{"class":251,"line":252},[110,751,578],{},[110,753,754],{"class":251,"line":258},[110,755,583],{},[110,757,758],{"class":251,"line":264},[110,759,588],{},[110,761,762],{"class":251,"line":270},[110,763,593],{},[110,765,766],{"class":251,"line":276},[110,767,768],{},"#=>   ...May 13, May 20, May 27...\n",[110,770,771],{"class":251,"line":282},[110,772,603],{},[107,774,538,775,777],{},[133,776,93],{}," reference opens with more recipes (\"every second Friday, 09:00–12:00\", \"the last working day of the month\"), and every example there runs as a doctest.",[125,779,781],{"id":780},"one-rule-for-shapes-an-operation-on-sets-answers-with-a-set","One rule for shapes: an operation on sets answers with a set",[107,783,784,786,787,790,791,790,794,790,797,790,800,802,803,806,807,810],{},[133,785,77],{}," accepts all three spellings of \"some intervals\" — one bare map, a plain list, or the struct. The answer does not follow the input. An operation on sets (",[133,788,789],{},"union\u002F2",", ",[133,792,793],{},"intersect\u002F2",[133,795,796],{},"diff\u002F2",[133,798,799],{},"complement\u002F1",[133,801,545],{},") always answers with the set struct, whatever shape the operands came in. A constructor of a piece answers with a piece: ",[133,804,805],{},"new!\u002F1"," builds one interval map. A question answers with a boolean: ",[133,808,809],{},"overlaps?\u002F2",".",[107,812,813,814,816,817,820],{},"Why one shape? So that \"nothing remains\" has exactly one spelling. When the answer followed the input, a piece-level call answered ",[133,815,486],{}," and a list-level call answered ",[133,818,819],{},"[]",", and every caller had to guard for both. Now the empty set is the only \"nothing\":",[241,822,824],{"className":379,"code":823,"language":381,"meta":246,"style":246},"block = Zocam.Intervals.new!(from: ~T[09:00:00], until: ~T[17:00:00])\nblock  # a piece constructor answers with a piece: one plain map\n#=> %{from: ~T[09:00:00], until: ~T[17:00:00], left: :closed, right: :open}\n\nZocam.Intervals.diff(block, block)      #=> %Zocam.Intervals{intervals: []}\nZocam.Intervals.diff([block], [block])  #=> %Zocam.Intervals{intervals: []}\n",[133,825,826,831,836,841,845,850],{"__ignoreMap":246},[110,827,828],{"class":251,"line":252},[110,829,830],{},"block = Zocam.Intervals.new!(from: ~T[09:00:00], until: ~T[17:00:00])\n",[110,832,833],{"class":251,"line":258},[110,834,835],{},"block  # a piece constructor answers with a piece: one plain map\n",[110,837,838],{"class":251,"line":264},[110,839,840],{},"#=> %{from: ~T[09:00:00], until: ~T[17:00:00], left: :closed, right: :open}\n",[110,842,843],{"class":251,"line":270},[110,844,414],{"emptyLinePlaceholder":413},[110,846,847],{"class":251,"line":276},[110,848,849],{},"Zocam.Intervals.diff(block, block)      #=> %Zocam.Intervals{intervals: []}\n",[110,851,852],{"class":251,"line":282},[110,853,854],{},"Zocam.Intervals.diff([block], [block])  #=> %Zocam.Intervals{intervals: []}\n",[107,856,857,858,860,861,863,864],{},"The symbolic row already obeyed this rule: every ",[133,859,93],{}," operator answers with a span, even over one arc (",[117,862,465],{"href":44},"). Both layers now say one sentence: ",[227,865,866],{},"an operation on sets answers with a set; a constructor of a piece answers with a piece.",[125,868,870,871,873],{"id":869},"three-ways-to-walk-what-enum-means-here","Three ways to walk: what ",[133,872,135],{}," means here",[107,875,876],{},"A set is one value that holds pieces, so the next question is natural: what do you get when you walk it? The answer depends on where the value sits on the map.",[171,878,879,889],{},[174,880,881],{},[177,882,883,886],{},[180,884,885],{},"you walk",[180,887,888],{},"you get",[188,890,891,903,915],{},[177,892,893,900],{},[193,894,895,897,898],{},[133,896,541],{}," with ",[133,899,135],{},[193,901,902],{},"its concrete pieces, in order",[177,904,905,912],{},[193,906,907,908,897,910],{},"a ",[133,909,97],{},[133,911,135],{},[193,913,914],{},"the symbolic cells of one instance",[177,916,917,922],{},[193,918,919],{},[133,920,921],{},"Zocam.Span.stream\u002F3",[193,923,924],{},"concrete occurrences over real time, lazily, from an instant on",[107,926,927],{},[227,928,929],{},"Figure 3 — The three walks: concrete pieces out of a set, symbolic cells out of an arc, and a lazy stream of real occurrences out of a span. AI generated, human reviewed.",[107,931,932,935,936,939,940,942],{},[148,933,934],{},"A concrete set yields concrete pieces."," The struct implements ",[133,937,938],{},"Enumerable",", so ",[133,941,135],{}," walks its intervals in normal-form order. Ground a span, then read one field off every piece:",[241,944,946],{"className":379,"code":945,"language":381,"meta":246,"style":246},"weds = Zocam.Span.of(Zocam.Point.weekday(:wednesday))\nhorizon = %{from: ~U[2026-01-05 00:00:00Z], until: ~U[2026-01-19 00:00:00Z],\n            left: :closed, right: :open}\n\nZocam.Span.ground(weds, horizon, \"Etc\u002FUTC\") |> Enum.map(& &1.from)\n#=> [~U[2026-01-07 00:00:00Z], ~U[2026-01-14 00:00:00Z]]\n",[133,947,948,952,957,962,966,971],{"__ignoreMap":246},[110,949,950],{"class":251,"line":252},[110,951,559],{},[110,953,954],{"class":251,"line":258},[110,955,956],{},"horizon = %{from: ~U[2026-01-05 00:00:00Z], until: ~U[2026-01-19 00:00:00Z],\n",[110,958,959],{"class":251,"line":264},[110,960,961],{},"            left: :closed, right: :open}\n",[110,963,964],{"class":251,"line":270},[110,965,414],{"emptyLinePlaceholder":413},[110,967,968],{"class":251,"line":276},[110,969,970],{},"Zocam.Span.ground(weds, horizon, \"Etc\u002FUTC\") |> Enum.map(& &1.from)\n",[110,972,973],{"class":251,"line":282},[110,974,975],{},"#=> [~U[2026-01-07 00:00:00Z], ~U[2026-01-14 00:00:00Z]]\n",[107,977,978,979,982,983,810],{},"One trap here: ",[133,980,981],{},"Enum.member?\u002F2"," asks \"is this value one of the pieces?\", not \"is this instant covered by the set?\". For coverage, keep the span and ask ",[133,984,985],{},"Zocam.Span.member?\u002F2",[107,987,988,991,992,994],{},[148,989,990],{},"An arc yields the symbolic cells of one instance."," An arc repeats, so ",[133,993,135],{}," cannot walk all of it. It walks one instance, cell by cell, and stays symbolic — no timezone, no dates:",[241,996,998],{"className":379,"code":997,"language":381,"meta":246,"style":246},"{:arcs, _, _, [weekend]} =\n  Zocam.Span.arc!(\n    from: Zocam.Point.weekday(:friday),\n    until: Zocam.Point.weekday(:monday)\n  )\n\nEnum.to_list(weekend)  #=> [:friday, :saturday, :sunday, :monday]\n",[133,999,1000,1005,1009,1013,1017,1021,1025],{"__ignoreMap":246},[110,1001,1002],{"class":251,"line":252},[110,1003,1004],{},"{:arcs, _, _, [weekend]} =\n",[110,1006,1007],{"class":251,"line":258},[110,1008,393],{},[110,1010,1011],{"class":251,"line":264},[110,1012,398],{},[110,1014,1015],{"class":251,"line":270},[110,1016,403],{},[110,1018,1019],{"class":251,"line":276},[110,1020,408],{},[110,1022,1023],{"class":251,"line":282},[110,1024,414],{"emptyLinePlaceholder":413},[110,1026,1027],{"class":251,"line":288},[110,1028,1029],{},"Enum.to_list(weekend)  #=> [:friday, :saturday, :sunday, :monday]\n",[107,1031,1032,1033,1036,1037,1040,1041,1044,1045,1047,1048,1050,1051,1054,1055,1057],{},"Closings are honored: an ",[133,1034,1035],{},":open"," side drops that bound's cell. A wrap walks through the cycle seam: ",[133,1038,1039],{},"Nov..Feb"," is four month cells. A step samples every n-th cell, so a stepped ",[133,1042,1043],{},":time"," arc yields its sampled ",[133,1046,478],{}," values. The pitfall is the ",[133,1049,1043],{}," arc ",[227,1052,1053],{},"without"," a step: it is continuous — between any two instants sit infinitely many more — so ",[133,1056,135],{}," on it raises and names the two ways out: give the arc a step, or ground the span and walk the concrete intervals.",[107,1059,1060,648,1063,1065,1066,1068],{},[148,1061,1062],{},"A stream yields concrete occurrences over real time.",[133,1064,135],{}," over an arc shows you the shape of one instance. When you want the instances themselves — real windows, one after the other, with no right bound to pick first — ask ",[133,1067,921],{},". It grounds chunk by chunk and stays lazy, so take what you need:",[241,1070,1072],{"className":379,"code":1071,"language":381,"meta":246,"style":246},"weds = Zocam.Span.of(Zocam.Point.weekday(:wednesday))\n\nZocam.Span.stream(weds, ~U[2026-01-01 00:00:00Z], \"Etc\u002FUTC\") |> Enum.take(2)\n#=> [%{from: ~U[2026-01-07 00:00:00Z], until: ~U[2026-01-08 00:00:00Z],\n#=>    left: :closed, right: :open},\n#=>  %{from: ~U[2026-01-14 00:00:00Z], until: ~U[2026-01-15 00:00:00Z],\n#=>    left: :closed, right: :open}]\n",[133,1073,1074,1078,1082,1087,1092,1097,1102],{"__ignoreMap":246},[110,1075,1076],{"class":251,"line":252},[110,1077,559],{},[110,1079,1080],{"class":251,"line":258},[110,1081,414],{"emptyLinePlaceholder":413},[110,1083,1084],{"class":251,"line":264},[110,1085,1086],{},"Zocam.Span.stream(weds, ~U[2026-01-01 00:00:00Z], \"Etc\u002FUTC\") |> Enum.take(2)\n",[110,1088,1089],{"class":251,"line":270},[110,1090,1091],{},"#=> [%{from: ~U[2026-01-07 00:00:00Z], until: ~U[2026-01-08 00:00:00Z],\n",[110,1093,1094],{"class":251,"line":276},[110,1095,1096],{},"#=>    left: :closed, right: :open},\n",[110,1098,1099],{"class":251,"line":282},[110,1100,1101],{},"#=>  %{from: ~U[2026-01-14 00:00:00Z], until: ~U[2026-01-15 00:00:00Z],\n",[110,1103,1104],{"class":251,"line":288},[110,1105,1106],{},"#=>    left: :closed, right: :open}]\n",[107,1108,1109,1110,790,1112,1114,1115,1117],{},"The same rules live in the API reference — in the ",[133,1111,73],{},[133,1113,77],{},", and ",[133,1116,97],{}," module docs — and each example there runs as a doctest, so the test suite proves that this page's claims stay true.",[1119,1120,1121],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":246,"searchDepth":258,"depth":258,"links":1123},[1124,1125,1135,1136,1137],{"id":127,"depth":258,"text":128},{"id":353,"depth":258,"text":354,"children":1126},[1127,1129,1131,1133],{"id":358,"depth":264,"text":1128},"Zocam.Span.Arc — one symbolic piece",{"id":427,"depth":264,"text":1130},"Zocam.Span.t — a symbolic set",{"id":469,"depth":264,"text":1132},"Zocam.Intervals.interval — one concrete piece",{"id":532,"depth":264,"text":1134},"Zocam.Intervals.t — a concrete set",{"id":617,"depth":258,"text":618},{"id":780,"depth":258,"text":781},{"id":869,"depth":258,"text":1138},"Three ways to walk: what Enum means here","The four data names of zocam — Span.Arc, Span, Intervals.interval, and Intervals — and how to keep them apart.","md",null,{},{"title":19,"description":1139},"1nsb4d61KobZTaM2-gbuRbxEBj0-9-1K7GX2FnjotwU",[1146,1148],{"title":15,"path":16,"stem":17,"description":1147,"children":-1},"Add zocam to a Mix project, run its tests, and build its documentation.",{"title":30,"path":26,"stem":31,"description":1149,"children":-1},"One record for each irreversible design choice in the zocam time library, with the reasoning behind it.",1786065169074]