[{"data":1,"prerenderedAt":4371},["ShallowReactive",2],{"page-\u002Fsql\u002F17-date-and-time":3},{"id":4,"title":5,"body":6,"description":16,"extension":4365,"meta":4366,"navigation":273,"path":4367,"seo":4368,"stem":4369,"__hash__":4370},"content\u002Fsql\u002F17-date-and-time.md","17 — Date & Time Handling",{"type":7,"value":8,"toc":4343},"minimark",[9,13,17,20,25,174,179,359,380,394,396,400,550,556,558,562,568,615,773,792,794,798,901,1011,1027,1029,1033,1038,1054,1260,1264,1398,1408,1410,1414,1656,1670,1672,1676,1892,1908,1910,1914,1920,2182,2190,2192,2196,2199,3175,3181,3221,3223,3227,3340,3342,3346,3438,3440,3444,3721,3727,3729,3733,3860,3862,3866,4079,4081,4085,4096,4182,4317,4319,4322,4339],[10,11,5],"h1",{"id":12},"_17-date-time-handling",[14,15,16],"p",{},"Date and time in SQL is a field littered with traps: time zones, daylight saving time, ambiguous local times, variable-length intervals, and the critical distinction between \"a moment in time\" and \"a wall-clock reading.\" This chapter covers the types, the operations, and the production patterns that avoid the traps.",[18,19],"hr",{},[21,22,24],"h2",{"id":23},"the-type-system","The Type System",[26,27,28,47],"table",{},[29,30,31],"thead",{},[32,33,34,38,41,44],"tr",{},[35,36,37],"th",{},"Type",[35,39,40],{},"Stores",[35,42,43],{},"Example",[35,45,46],{},"Use Case",[48,49,50,70,88,106,128,152],"tbody",{},[32,51,52,59,62,67],{},[53,54,55],"td",{},[56,57,58],"code",{},"DATE",[53,60,61],{},"Calendar date (y, m, d), no time, no zone",[53,63,64],{},[56,65,66],{},"'2024-03-15'",[53,68,69],{},"Birthdays, holidays",[32,71,72,77,80,85],{},[53,73,74],{},[56,75,76],{},"TIME",[53,78,79],{},"Clock time, no date, no zone",[53,81,82],{},[56,83,84],{},"'14:30:00'",[53,86,87],{},"Daily schedule (zone-agnostic)",[32,89,90,95,98,103],{},[53,91,92],{},[56,93,94],{},"TIMETZ",[53,96,97],{},"Clock time + zone",[53,99,100],{},[56,101,102],{},"'14:30:00-05'",[53,104,105],{},"Almost never useful",[32,107,108,113,120,125],{},[53,109,110],{},[56,111,112],{},"TIMESTAMP",[53,114,115,116],{},"Date + time, ",[117,118,119],"strong",{},"no zone",[53,121,122],{},[56,123,124],{},"'2024-03-15 14:30:00'",[53,126,127],{},"Wall-clock readings, implicit zone",[32,129,130,135,142,147],{},[53,131,132],{},[56,133,134],{},"TIMESTAMPTZ",[53,136,137,138,141],{},"A ",[117,139,140],{},"moment"," — date + time, stored as UTC",[53,143,144],{},[56,145,146],{},"'2024-03-15 14:30:00-05'",[53,148,149],{},[117,150,151],{},"Default for event timestamps",[32,153,154,159,162,171],{},[53,155,156],{},[56,157,158],{},"INTERVAL",[53,160,161],{},"A span of time, not a point",[53,163,164,167,168],{},[56,165,166],{},"'30 days'",", ",[56,169,170],{},"'1 hour'",[53,172,173],{},"Duration math",[175,176,178],"h3",{"id":177},"the-critical-choice-timestamp-vs-timestamptz","The Critical Choice: TIMESTAMP vs TIMESTAMPTZ",[180,181,183],"code-wrapper",{"language":182},"sql",[184,185,189],"pre",{"className":186,"code":187,"language":182,"meta":188,"style":188},"language-sql shiki shiki-themes github-light github-dark","-- TIMESTAMPTZ: stores a specific MOMENT (UTC internally), displays in session zone\n-- Two clients in different zones see the same moment in their local time\nCREATE TABLE events (\n  id BIGSERIAL PRIMARY KEY,\n  created_at TIMESTAMPTZ NOT NULL DEFAULT now()   -- ✅ correct: a specific moment\n);\n\n-- Insert with explicit zone → converted to UTC for storage\nINSERT INTO events (created_at) VALUES ('2024-03-15 14:30:00-05');\n-- Stored as 2024-03-15 19:30:00 UTC (regardless of session TimeZone)\n\n-- TIMESTAMP (without zone): stores a WALL-CLOCK reading, no zone info\nCREATE TABLE schedules (\n  id BIGSERIAL PRIMARY KEY,\n  start_time TIMESTAMP NOT NULL   -- ⚠️ '14:30:00' — New York? Tokyo? No way to tell\n);\n","",[56,190,191,200,206,224,239,262,268,275,281,301,307,312,318,330,341,354],{"__ignoreMap":188},[192,193,196],"span",{"class":194,"line":195},"line",1,[192,197,199],{"class":198},"sdCPZ","-- TIMESTAMPTZ: stores a specific MOMENT (UTC internally), displays in session zone\n",[192,201,203],{"class":194,"line":202},2,[192,204,205],{"class":198},"-- Two clients in different zones see the same moment in their local time\n",[192,207,209,213,216,220],{"class":194,"line":208},3,[192,210,212],{"class":211},"svdQ7","CREATE",[192,214,215],{"class":211}," TABLE",[192,217,219],{"class":218},"sIsaT"," events",[192,221,223],{"class":222},"ssxIu"," (\n",[192,225,227,230,233,236],{"class":194,"line":226},4,[192,228,229],{"class":222},"  id ",[192,231,232],{"class":211},"BIGSERIAL",[192,234,235],{"class":211}," PRIMARY KEY",[192,237,238],{"class":222},",\n",[192,240,242,245,247,250,253,256,259],{"class":194,"line":241},5,[192,243,244],{"class":222},"  created_at ",[192,246,134],{"class":211},[192,248,249],{"class":211}," NOT NULL",[192,251,252],{"class":211}," DEFAULT",[192,254,255],{"class":211}," now",[192,257,258],{"class":222},"()   ",[192,260,261],{"class":198},"-- ✅ correct: a specific moment\n",[192,263,265],{"class":194,"line":264},6,[192,266,267],{"class":222},");\n",[192,269,271],{"class":194,"line":270},7,[192,272,274],{"emptyLinePlaceholder":273},true,"\n",[192,276,278],{"class":194,"line":277},8,[192,279,280],{"class":198},"-- Insert with explicit zone → converted to UTC for storage\n",[192,282,284,287,290,293,296,299],{"class":194,"line":283},9,[192,285,286],{"class":211},"INSERT INTO",[192,288,289],{"class":222}," events (created_at) ",[192,291,292],{"class":211},"VALUES",[192,294,295],{"class":222}," (",[192,297,146],{"class":298},"sJ6F3",[192,300,267],{"class":222},[192,302,304],{"class":194,"line":303},10,[192,305,306],{"class":198},"-- Stored as 2024-03-15 19:30:00 UTC (regardless of session TimeZone)\n",[192,308,310],{"class":194,"line":309},11,[192,311,274],{"emptyLinePlaceholder":273},[192,313,315],{"class":194,"line":314},12,[192,316,317],{"class":198},"-- TIMESTAMP (without zone): stores a WALL-CLOCK reading, no zone info\n",[192,319,321,323,325,328],{"class":194,"line":320},13,[192,322,212],{"class":211},[192,324,215],{"class":211},[192,326,327],{"class":218}," schedules",[192,329,223],{"class":222},[192,331,333,335,337,339],{"class":194,"line":332},14,[192,334,229],{"class":222},[192,336,232],{"class":211},[192,338,235],{"class":211},[192,340,238],{"class":222},[192,342,344,347,349,351],{"class":194,"line":343},15,[192,345,346],{"class":222},"  start_time ",[192,348,112],{"class":211},[192,350,249],{"class":211},[192,352,353],{"class":198},"   -- ⚠️ '14:30:00' — New York? Tokyo? No way to tell\n",[192,355,357],{"class":194,"line":356},16,[192,358,267],{"class":222},[14,360,361,366,367,167,370,167,373,376,377,379],{},[117,362,363,364],{},"Default to ",[56,365,134],{}," for event timestamps (",[56,368,369],{},"created_at",[56,371,372],{},"updated_at",[56,374,375],{},"order_time","). Reserve ",[56,378,112],{}," (without zone) for wall-clock readings with no meaningful zone (a daily alarm \"2:30 PM\" that applies in whatever local zone the reader is in).",[14,381,382,384,385,389,390,393],{},[56,383,134],{}," doesn't actually store a time zone — it stores UTC. The \"with time zone\" name means it ",[386,387,388],"em",{},"performs"," zone conversion on input and output. The stored value is always UTC; display depends on the session's ",[56,391,392],{},"TimeZone"," setting.",[18,395],{},[21,397,399],{"id":398},"inserting-and-displaying","Inserting and Displaying",[180,401,402],{"language":182},[184,403,405],{"className":186,"code":404,"language":182,"meta":188,"style":188},"-- Insert with explicit zone offset → converted to UTC for storage\nINSERT INTO events (created_at) VALUES ('2024-03-15 14:30:00-05');\n-- Stored: 2024-03-15 19:30:00 UTC\n\n-- Insert with no zone → interpreted as the session's TimeZone for TIMESTAMPTZ\nSET TimeZone = 'America\u002FNew_York';\nINSERT INTO events (created_at) VALUES ('2024-03-15 14:30:00');\n-- Session is New_York → interpreted as 2024-03-15 14:30:00-05 → stored as 19:30 UTC\n\n-- Display in a specific zone (same stored value, different display)\nSET TimeZone = 'Asia\u002FTokyo';\nSELECT created_at FROM events;\n-- 2024-03-16 04:30:00+09   (same moment, Tokyo time)\n\nSET TimeZone = 'UTC';\nSELECT created_at FROM events;\n-- 2024-03-15 19:30:00+00   (same moment, UTC)\n",[56,406,407,412,426,431,435,440,457,471,476,480,485,498,512,517,521,534,544],{"__ignoreMap":188},[192,408,409],{"class":194,"line":195},[192,410,411],{"class":198},"-- Insert with explicit zone offset → converted to UTC for storage\n",[192,413,414,416,418,420,422,424],{"class":194,"line":202},[192,415,286],{"class":211},[192,417,289],{"class":222},[192,419,292],{"class":211},[192,421,295],{"class":222},[192,423,146],{"class":298},[192,425,267],{"class":222},[192,427,428],{"class":194,"line":208},[192,429,430],{"class":198},"-- Stored: 2024-03-15 19:30:00 UTC\n",[192,432,433],{"class":194,"line":226},[192,434,274],{"emptyLinePlaceholder":273},[192,436,437],{"class":194,"line":241},[192,438,439],{"class":198},"-- Insert with no zone → interpreted as the session's TimeZone for TIMESTAMPTZ\n",[192,441,442,445,448,451,454],{"class":194,"line":264},[192,443,444],{"class":211},"SET",[192,446,447],{"class":222}," TimeZone ",[192,449,450],{"class":211},"=",[192,452,453],{"class":298}," 'America\u002FNew_York'",[192,455,456],{"class":222},";\n",[192,458,459,461,463,465,467,469],{"class":194,"line":270},[192,460,286],{"class":211},[192,462,289],{"class":222},[192,464,292],{"class":211},[192,466,295],{"class":222},[192,468,124],{"class":298},[192,470,267],{"class":222},[192,472,473],{"class":194,"line":277},[192,474,475],{"class":198},"-- Session is New_York → interpreted as 2024-03-15 14:30:00-05 → stored as 19:30 UTC\n",[192,477,478],{"class":194,"line":283},[192,479,274],{"emptyLinePlaceholder":273},[192,481,482],{"class":194,"line":303},[192,483,484],{"class":198},"-- Display in a specific zone (same stored value, different display)\n",[192,486,487,489,491,493,496],{"class":194,"line":309},[192,488,444],{"class":211},[192,490,447],{"class":222},[192,492,450],{"class":211},[192,494,495],{"class":298}," 'Asia\u002FTokyo'",[192,497,456],{"class":222},[192,499,500,503,506,509],{"class":194,"line":314},[192,501,502],{"class":211},"SELECT",[192,504,505],{"class":222}," created_at ",[192,507,508],{"class":211},"FROM",[192,510,511],{"class":222}," events;\n",[192,513,514],{"class":194,"line":320},[192,515,516],{"class":198},"-- 2024-03-16 04:30:00+09   (same moment, Tokyo time)\n",[192,518,519],{"class":194,"line":332},[192,520,274],{"emptyLinePlaceholder":273},[192,522,523,525,527,529,532],{"class":194,"line":343},[192,524,444],{"class":211},[192,526,447],{"class":222},[192,528,450],{"class":211},[192,530,531],{"class":298}," 'UTC'",[192,533,456],{"class":222},[192,535,536,538,540,542],{"class":194,"line":356},[192,537,502],{"class":211},[192,539,505],{"class":222},[192,541,508],{"class":211},[192,543,511],{"class":222},[192,545,547],{"class":194,"line":546},17,[192,548,549],{"class":198},"-- 2024-03-15 19:30:00+00   (same moment, UTC)\n",[14,551,552,553,555],{},"The stored value is the same; only the display changes. This is the power of ",[56,554,134],{}," — one stored moment, correct display in any zone.",[18,557],{},[21,559,561],{"id":560},"at-time-zone-the-direction-flipping-operator","AT TIME ZONE: The Direction-Flipping Operator",[14,563,564,567],{},[56,565,566],{},"AT TIME ZONE","'s behavior flips based on the input type — the most common source of timezone bugs:",[26,569,570,586],{},[29,571,572],{},[32,573,574,577,583],{},[35,575,576],{},"Input Type",[35,578,579,582],{},[56,580,581],{},"AT TIME ZONE 'X'"," returns",[35,584,585],{},"Meaning",[48,587,588,602],{},[32,589,590,594,599],{},[53,591,592],{},[56,593,134],{},[53,595,596,598],{},[56,597,112],{}," (no zone)",[53,600,601],{},"Wall-clock time in zone X",[32,603,604,608,612],{},[53,605,606,598],{},[56,607,112],{},[53,609,610],{},[56,611,134],{},[53,613,614],{},"The moment that wall time represents in zone X",[180,616,617],{"language":182},[184,618,620],{"className":186,"code":619,"language":182,"meta":188,"style":188},"-- TIMESTAMPTZ → TIMESTAMP: \"what does this moment look like in New York?\"\nSELECT created_at AT TIME ZONE 'America\u002FNew_York' FROM events;\n-- Input: 2024-03-15 19:30:00 UTC → Output: 2024-03-15 14:30:00 (NY wall time, no zone)\n\n-- TIMESTAMP → TIMESTAMPTZ: \"what moment does this wall time represent in New York?\"\nSELECT TIMESTAMP '2024-03-15 14:30:00' AT TIME ZONE 'America\u002FNew_York';\n-- Input: 2024-03-15 14:30:00 (no zone) → Output: 2024-03-15 19:30:00+00 (the UTC moment)\n\n-- Get the current moment in a specific zone (now() returns TIMESTAMPTZ → AT TIME ZONE → TIMESTAMP)\nSELECT now() AT TIME ZONE 'Asia\u002FKolkata';   -- current Kolkata wall time as TIMESTAMP\n\n-- List available time zones (use full zone names, not abbreviations)\nSELECT name, utc_offset, is_dst FROM pg_timezone_names\nWHERE name LIKE 'America\u002F%' ORDER BY name LIMIT 10;\n",[56,621,622,627,649,654,658,663,684,689,693,698,722,726,731,746],{"__ignoreMap":188},[192,623,624],{"class":194,"line":195},[192,625,626],{"class":198},"-- TIMESTAMPTZ → TIMESTAMP: \"what does this moment look like in New York?\"\n",[192,628,629,631,633,636,639,642,644,647],{"class":194,"line":202},[192,630,502],{"class":211},[192,632,505],{"class":222},[192,634,635],{"class":211},"AT",[192,637,638],{"class":211}," TIME",[192,640,641],{"class":211}," ZONE",[192,643,453],{"class":298},[192,645,646],{"class":211}," FROM",[192,648,511],{"class":222},[192,650,651],{"class":194,"line":208},[192,652,653],{"class":198},"-- Input: 2024-03-15 19:30:00 UTC → Output: 2024-03-15 14:30:00 (NY wall time, no zone)\n",[192,655,656],{"class":194,"line":226},[192,657,274],{"emptyLinePlaceholder":273},[192,659,660],{"class":194,"line":241},[192,661,662],{"class":198},"-- TIMESTAMP → TIMESTAMPTZ: \"what moment does this wall time represent in New York?\"\n",[192,664,665,667,670,673,676,678,680,682],{"class":194,"line":264},[192,666,502],{"class":211},[192,668,669],{"class":211}," TIMESTAMP",[192,671,672],{"class":298}," '2024-03-15 14:30:00'",[192,674,675],{"class":211}," AT",[192,677,638],{"class":211},[192,679,641],{"class":211},[192,681,453],{"class":298},[192,683,456],{"class":222},[192,685,686],{"class":194,"line":270},[192,687,688],{"class":198},"-- Input: 2024-03-15 14:30:00 (no zone) → Output: 2024-03-15 19:30:00+00 (the UTC moment)\n",[192,690,691],{"class":194,"line":277},[192,692,274],{"emptyLinePlaceholder":273},[192,694,695],{"class":194,"line":283},[192,696,697],{"class":198},"-- Get the current moment in a specific zone (now() returns TIMESTAMPTZ → AT TIME ZONE → TIMESTAMP)\n",[192,699,700,702,704,707,709,711,713,716,719],{"class":194,"line":303},[192,701,502],{"class":211},[192,703,255],{"class":211},[192,705,706],{"class":222},"() ",[192,708,635],{"class":211},[192,710,638],{"class":211},[192,712,641],{"class":211},[192,714,715],{"class":298}," 'Asia\u002FKolkata'",[192,717,718],{"class":222},";   ",[192,720,721],{"class":198},"-- current Kolkata wall time as TIMESTAMP\n",[192,723,724],{"class":194,"line":309},[192,725,274],{"emptyLinePlaceholder":273},[192,727,728],{"class":194,"line":314},[192,729,730],{"class":198},"-- List available time zones (use full zone names, not abbreviations)\n",[192,732,733,735,738,741,743],{"class":194,"line":320},[192,734,502],{"class":211},[192,736,737],{"class":211}," name",[192,739,740],{"class":222},", utc_offset, is_dst ",[192,742,508],{"class":211},[192,744,745],{"class":222}," pg_timezone_names\n",[192,747,748,751,753,756,759,762,764,767,771],{"class":194,"line":332},[192,749,750],{"class":211},"WHERE",[192,752,737],{"class":211},[192,754,755],{"class":211}," LIKE",[192,757,758],{"class":298}," 'America\u002F%'",[192,760,761],{"class":211}," ORDER BY",[192,763,737],{"class":211},[192,765,766],{"class":211}," LIMIT",[192,768,770],{"class":769},"snvgF"," 10",[192,772,456],{"class":222},[14,774,775,778,779,782,783,785,786,782,789,791],{},[117,776,777],{},"Memorize",": ",[56,780,781],{},"TIMESTAMPTZ AT TIME ZONE 'X'"," → ",[56,784,112],{}," (wall time in X); ",[56,787,788],{},"TIMESTAMP AT TIME ZONE 'X'",[56,790,134],{}," (the moment that wall time represents in X).",[18,793],{},[21,795,797],{"id":796},"current-datetime-functions","Current Date\u002FTime Functions",[26,799,800,813],{},[29,801,802],{},[32,803,804,807,810],{},[35,805,806],{},"Function",[35,808,809],{},"Returns",[35,811,812],{},"Constant in transaction?",[48,814,815,828,841,863,876,889],{},[32,816,817,822,825],{},[53,818,819],{},[56,820,821],{},"CURRENT_DATE",[53,823,824],{},"Today's date (session date)",[53,826,827],{},"N\u002FA (date)",[32,829,830,835,838],{},[53,831,832],{},[56,833,834],{},"CURRENT_TIME",[53,836,837],{},"Current time with zone",[53,839,840],{},"✅ Yes",[32,842,843,855,860],{},[53,844,845,848,849,848,852],{},[56,846,847],{},"CURRENT_TIMESTAMP"," \u002F ",[56,850,851],{},"now()",[56,853,854],{},"transaction_timestamp()",[53,856,857,858],{},"Current ",[56,859,134],{},[53,861,862],{},"✅ Yes (transaction start)",[32,864,865,870,874],{},[53,866,867],{},[56,868,869],{},"LOCALTIMESTAMP",[53,871,857,872,598],{},[56,873,112],{},[53,875,840],{},[32,877,878,883,886],{},[53,879,880],{},[56,881,882],{},"statement_timestamp()",[53,884,885],{},"Start of current statement",[53,887,888],{},"❌ No",[32,890,891,896,899],{},[53,892,893],{},[56,894,895],{},"clock_timestamp()",[53,897,898],{},"Real wall clock, per call",[53,900,888],{},[180,902,903],{"language":182},[184,904,906],{"className":186,"code":905,"language":182,"meta":188,"style":188},"-- now() is constant within a transaction — same value for all statements\nBEGIN;\nSELECT now();   -- 2024-03-15 10:00:00+00\n-- ... other statements ...\nSELECT now();   -- still 2024-03-15 10:00:00+00 (transaction start)\nCOMMIT;\n\n-- clock_timestamp() is the real wall clock — changes per call\nSELECT clock_timestamp();   -- 2024-03-15 10:00:00.123456+00\nSELECT clock_timestamp();   -- 2024-03-15 10:00:00.124001+00 (microseconds later)\n\n-- Use clock_timestamp() for elapsed-time measurement within a transaction\nSELECT clock_timestamp() - transaction_timestamp() AS tx_elapsed;\n",[56,907,908,913,920,932,937,948,955,959,964,974,983,987,992],{"__ignoreMap":188},[192,909,910],{"class":194,"line":195},[192,911,912],{"class":198},"-- now() is constant within a transaction — same value for all statements\n",[192,914,915,918],{"class":194,"line":202},[192,916,917],{"class":211},"BEGIN",[192,919,456],{"class":222},[192,921,922,924,926,929],{"class":194,"line":208},[192,923,502],{"class":211},[192,925,255],{"class":211},[192,927,928],{"class":222},"();   ",[192,930,931],{"class":198},"-- 2024-03-15 10:00:00+00\n",[192,933,934],{"class":194,"line":226},[192,935,936],{"class":198},"-- ... other statements ...\n",[192,938,939,941,943,945],{"class":194,"line":241},[192,940,502],{"class":211},[192,942,255],{"class":211},[192,944,928],{"class":222},[192,946,947],{"class":198},"-- still 2024-03-15 10:00:00+00 (transaction start)\n",[192,949,950,953],{"class":194,"line":264},[192,951,952],{"class":211},"COMMIT",[192,954,456],{"class":222},[192,956,957],{"class":194,"line":270},[192,958,274],{"emptyLinePlaceholder":273},[192,960,961],{"class":194,"line":277},[192,962,963],{"class":198},"-- clock_timestamp() is the real wall clock — changes per call\n",[192,965,966,968,971],{"class":194,"line":283},[192,967,502],{"class":211},[192,969,970],{"class":222}," clock_timestamp();   ",[192,972,973],{"class":198},"-- 2024-03-15 10:00:00.123456+00\n",[192,975,976,978,980],{"class":194,"line":303},[192,977,502],{"class":211},[192,979,970],{"class":222},[192,981,982],{"class":198},"-- 2024-03-15 10:00:00.124001+00 (microseconds later)\n",[192,984,985],{"class":194,"line":309},[192,986,274],{"emptyLinePlaceholder":273},[192,988,989],{"class":194,"line":314},[192,990,991],{"class":198},"-- Use clock_timestamp() for elapsed-time measurement within a transaction\n",[192,993,994,996,999,1002,1005,1008],{"class":194,"line":320},[192,995,502],{"class":211},[192,997,998],{"class":222}," clock_timestamp() ",[192,1000,1001],{"class":211},"-",[192,1003,1004],{"class":222}," transaction_timestamp() ",[192,1006,1007],{"class":211},"AS",[192,1009,1010],{"class":222}," tx_elapsed;\n",[14,1012,1013,848,1015,848,1017,1019,1020,1023,1024,1026],{},[56,1014,851],{},[56,1016,847],{},[56,1018,854],{}," all return the ",[117,1021,1022],{},"transaction start time"," — they're constant within a transaction. Use ",[56,1025,895],{}," for the real wall clock (e.g., measuring elapsed time).",[18,1028],{},[21,1030,1032],{"id":1031},"interval-arithmetic","INTERVAL Arithmetic",[14,1034,1035,1037],{},[56,1036,158],{}," is a span of time, not a point. It has two categories with fundamentally different semantics:",[1039,1040,1041,1048],"ul",{},[1042,1043,1044,1047],"li",{},[117,1045,1046],{},"Fixed-length",": days, hours, minutes, seconds — always the same duration.",[1042,1049,1050,1053],{},[117,1051,1052],{},"Variable-length",": months, years — a month is 28–31 days depending on the calendar.",[180,1055,1056],{"language":182},[184,1057,1059],{"className":186,"code":1058,"language":182,"meta":188,"style":188},"-- Basic intervals\nSELECT INTERVAL '30 days';                -- 30 days\nSELECT INTERVAL '1 hour 30 minutes';      -- 1:30:00\nSELECT INTERVAL '2 months 3 days';        -- 2 mons 3 days\n\n-- Timestamp arithmetic\nSELECT now() + INTERVAL '7 days';         -- a week from now (TIMESTAMPTZ)\nSELECT now() - INTERVAL '1 hour';         -- an hour ago\n\n-- Date subtraction returns integer days (not an interval)\nSELECT '2024-03-15'::date - '2024-03-01'::date;   -- 14 (integer, not interval)\n\n-- Age: returns an interval (years\u002Fmonths\u002Fdays)\nSELECT age('2024-03-15'::date, '2000-01-01'::date);   -- 24 years 2 mons 14 days\nSELECT age(now(), '2000-01-01'::timestamptz);          -- same, with time component\n",[56,1060,1061,1066,1081,1096,1111,1115,1120,1142,1161,1165,1170,1198,1202,1207,1235],{"__ignoreMap":188},[192,1062,1063],{"class":194,"line":195},[192,1064,1065],{"class":198},"-- Basic intervals\n",[192,1067,1068,1070,1073,1075,1078],{"class":194,"line":202},[192,1069,502],{"class":211},[192,1071,1072],{"class":222}," INTERVAL ",[192,1074,166],{"class":298},[192,1076,1077],{"class":222},";                ",[192,1079,1080],{"class":198},"-- 30 days\n",[192,1082,1083,1085,1087,1090,1093],{"class":194,"line":208},[192,1084,502],{"class":211},[192,1086,1072],{"class":222},[192,1088,1089],{"class":298},"'1 hour 30 minutes'",[192,1091,1092],{"class":222},";      ",[192,1094,1095],{"class":198},"-- 1:30:00\n",[192,1097,1098,1100,1102,1105,1108],{"class":194,"line":226},[192,1099,502],{"class":211},[192,1101,1072],{"class":222},[192,1103,1104],{"class":298},"'2 months 3 days'",[192,1106,1107],{"class":222},";        ",[192,1109,1110],{"class":198},"-- 2 mons 3 days\n",[192,1112,1113],{"class":194,"line":241},[192,1114,274],{"emptyLinePlaceholder":273},[192,1116,1117],{"class":194,"line":264},[192,1118,1119],{"class":198},"-- Timestamp arithmetic\n",[192,1121,1122,1124,1126,1128,1131,1133,1136,1139],{"class":194,"line":270},[192,1123,502],{"class":211},[192,1125,255],{"class":211},[192,1127,706],{"class":222},[192,1129,1130],{"class":211},"+",[192,1132,1072],{"class":222},[192,1134,1135],{"class":298},"'7 days'",[192,1137,1138],{"class":222},";         ",[192,1140,1141],{"class":198},"-- a week from now (TIMESTAMPTZ)\n",[192,1143,1144,1146,1148,1150,1152,1154,1156,1158],{"class":194,"line":277},[192,1145,502],{"class":211},[192,1147,255],{"class":211},[192,1149,706],{"class":222},[192,1151,1001],{"class":211},[192,1153,1072],{"class":222},[192,1155,170],{"class":298},[192,1157,1138],{"class":222},[192,1159,1160],{"class":198},"-- an hour ago\n",[192,1162,1163],{"class":194,"line":283},[192,1164,274],{"emptyLinePlaceholder":273},[192,1166,1167],{"class":194,"line":303},[192,1168,1169],{"class":198},"-- Date subtraction returns integer days (not an interval)\n",[192,1171,1172,1174,1177,1180,1183,1186,1189,1191,1193,1195],{"class":194,"line":309},[192,1173,502],{"class":211},[192,1175,1176],{"class":298}," '2024-03-15'",[192,1178,1179],{"class":222},"::",[192,1181,1182],{"class":211},"date",[192,1184,1185],{"class":211}," -",[192,1187,1188],{"class":298}," '2024-03-01'",[192,1190,1179],{"class":222},[192,1192,1182],{"class":211},[192,1194,718],{"class":222},[192,1196,1197],{"class":198},"-- 14 (integer, not interval)\n",[192,1199,1200],{"class":194,"line":314},[192,1201,274],{"emptyLinePlaceholder":273},[192,1203,1204],{"class":194,"line":320},[192,1205,1206],{"class":198},"-- Age: returns an interval (years\u002Fmonths\u002Fdays)\n",[192,1208,1209,1211,1214,1216,1218,1220,1222,1225,1227,1229,1232],{"class":194,"line":332},[192,1210,502],{"class":211},[192,1212,1213],{"class":222}," age(",[192,1215,66],{"class":298},[192,1217,1179],{"class":222},[192,1219,1182],{"class":211},[192,1221,167],{"class":222},[192,1223,1224],{"class":298},"'2000-01-01'",[192,1226,1179],{"class":222},[192,1228,1182],{"class":211},[192,1230,1231],{"class":222},");   ",[192,1233,1234],{"class":198},"-- 24 years 2 mons 14 days\n",[192,1236,1237,1239,1241,1244,1247,1249,1251,1254,1257],{"class":194,"line":343},[192,1238,502],{"class":211},[192,1240,1213],{"class":222},[192,1242,1243],{"class":211},"now",[192,1245,1246],{"class":222},"(), ",[192,1248,1224],{"class":298},[192,1250,1179],{"class":222},[192,1252,1253],{"class":211},"timestamptz",[192,1255,1256],{"class":222},");          ",[192,1258,1259],{"class":198},"-- same, with time component\n",[175,1261,1263],{"id":1262},"interval-components-and-extraction","Interval Components and Extraction",[180,1265,1266],{"language":182},[184,1267,1269],{"className":186,"code":1268,"language":182,"meta":188,"style":188},"-- Extract a single component\nSELECT EXTRACT(days   FROM INTERVAL '2 months 3 days');   -- 3\nSELECT EXTRACT(months FROM INTERVAL '2 months 3 days');   -- 2\nSELECT EXTRACT(hours  FROM INTERVAL '1 hour 30 minutes'); -- 1\n\n-- Convert to total seconds (epoch)\nSELECT EXTRACT(epoch FROM INTERVAL '1 hour');             -- 3600\nSELECT EXTRACT(epoch FROM INTERVAL '2 months 3 days');    -- 5616000 (uses avg 30 days\u002Fmonth)\n\n-- ⚠️ EXTRACT(epoch ...) on intervals with months uses an average month (30 days)\n--    Don't use epoch for variable-length intervals — the result is approximate\n",[56,1270,1271,1276,1298,1316,1338,1342,1347,1366,1384,1388,1393],{"__ignoreMap":188},[192,1272,1273],{"class":194,"line":195},[192,1274,1275],{"class":198},"-- Extract a single component\n",[192,1277,1278,1280,1283,1286,1289,1291,1293,1295],{"class":194,"line":202},[192,1279,502],{"class":211},[192,1281,1282],{"class":222}," EXTRACT(",[192,1284,1285],{"class":211},"days",[192,1287,1288],{"class":211},"   FROM",[192,1290,1072],{"class":222},[192,1292,1104],{"class":298},[192,1294,1231],{"class":222},[192,1296,1297],{"class":198},"-- 3\n",[192,1299,1300,1302,1305,1307,1309,1311,1313],{"class":194,"line":208},[192,1301,502],{"class":211},[192,1303,1304],{"class":222}," EXTRACT(months ",[192,1306,508],{"class":211},[192,1308,1072],{"class":222},[192,1310,1104],{"class":298},[192,1312,1231],{"class":222},[192,1314,1315],{"class":198},"-- 2\n",[192,1317,1318,1320,1322,1325,1328,1330,1332,1335],{"class":194,"line":226},[192,1319,502],{"class":211},[192,1321,1282],{"class":222},[192,1323,1324],{"class":211},"hours",[192,1326,1327],{"class":211},"  FROM",[192,1329,1072],{"class":222},[192,1331,1089],{"class":298},[192,1333,1334],{"class":222},"); ",[192,1336,1337],{"class":198},"-- 1\n",[192,1339,1340],{"class":194,"line":241},[192,1341,274],{"emptyLinePlaceholder":273},[192,1343,1344],{"class":194,"line":264},[192,1345,1346],{"class":198},"-- Convert to total seconds (epoch)\n",[192,1348,1349,1351,1354,1356,1358,1360,1363],{"class":194,"line":270},[192,1350,502],{"class":211},[192,1352,1353],{"class":222}," EXTRACT(epoch ",[192,1355,508],{"class":211},[192,1357,1072],{"class":222},[192,1359,170],{"class":298},[192,1361,1362],{"class":222},");             ",[192,1364,1365],{"class":198},"-- 3600\n",[192,1367,1368,1370,1372,1374,1376,1378,1381],{"class":194,"line":277},[192,1369,502],{"class":211},[192,1371,1353],{"class":222},[192,1373,508],{"class":211},[192,1375,1072],{"class":222},[192,1377,1104],{"class":298},[192,1379,1380],{"class":222},");    ",[192,1382,1383],{"class":198},"-- 5616000 (uses avg 30 days\u002Fmonth)\n",[192,1385,1386],{"class":194,"line":283},[192,1387,274],{"emptyLinePlaceholder":273},[192,1389,1390],{"class":194,"line":303},[192,1391,1392],{"class":198},"-- ⚠️ EXTRACT(epoch ...) on intervals with months uses an average month (30 days)\n",[192,1394,1395],{"class":194,"line":309},[192,1396,1397],{"class":198},"--    Don't use epoch for variable-length intervals — the result is approximate\n",[14,1399,1400,1403,1404,1407],{},[117,1401,1402],{},"Months and years are variable-length"," — a month is 28–31 days. ",[56,1405,1406],{},"EXTRACT(epoch ...)"," on an interval with months uses an average month (30 days), which is wrong for calendar math.",[18,1409],{},[21,1411,1413],{"id":1412},"date-truncation-and-extraction","Date Truncation and Extraction",[180,1415,1416],{"language":182},[184,1417,1419],{"className":186,"code":1418,"language":182,"meta":188,"style":188},"-- EXTRACT: pull a single component from a timestamp\nSELECT\n  EXTRACT(YEAR    FROM ordered_on) AS yr,      -- 2024\n  EXTRACT(MONTH   FROM ordered_on) AS mo,      -- 3\n  EXTRACT(DAY     FROM ordered_on) AS dy,      -- 15\n  EXTRACT(HOUR    FROM ordered_on) AS hr,      -- 14\n  EXTRACT(DOW     FROM ordered_on) AS dow,     -- 0=Sunday, 1=Monday, ... 6=Saturday\n  EXTRACT(DOY     FROM ordered_on) AS doy,     -- day of year (1-366)\n  EXTRACT(QUARTER FROM ordered_on) AS q,       -- 1-4\n  EXTRACT(EPOCH   FROM ordered_on) AS unix_ts  -- seconds since 1970-01-01 UTC\nFROM orders;\n\n-- date_trunc: round DOWN to the start of a unit (returns TIMESTAMPTZ)\nSELECT date_trunc('month', ordered_on) AS month_start;   -- 2024-03-01 00:00:00+00\nSELECT date_trunc('hour',   ordered_on) AS hour_start;   -- 2024-03-15 14:00:00+00\nSELECT date_trunc('week',   ordered_on) AS week_start;   -- 2024-03-11 (Monday start)\n",[56,1420,1421,1426,1431,1453,1471,1491,1510,1527,1544,1563,1580,1587,1591,1596,1617,1637],{"__ignoreMap":188},[192,1422,1423],{"class":194,"line":195},[192,1424,1425],{"class":198},"-- EXTRACT: pull a single component from a timestamp\n",[192,1427,1428],{"class":194,"line":202},[192,1429,1430],{"class":211},"SELECT\n",[192,1432,1433,1436,1439,1442,1445,1447,1450],{"class":194,"line":208},[192,1434,1435],{"class":222},"  EXTRACT(",[192,1437,1438],{"class":211},"YEAR",[192,1440,1441],{"class":211},"    FROM",[192,1443,1444],{"class":222}," ordered_on) ",[192,1446,1007],{"class":211},[192,1448,1449],{"class":222}," yr,      ",[192,1451,1452],{"class":198},"-- 2024\n",[192,1454,1455,1457,1460,1462,1464,1466,1469],{"class":194,"line":226},[192,1456,1435],{"class":222},[192,1458,1459],{"class":211},"MONTH",[192,1461,1288],{"class":211},[192,1463,1444],{"class":222},[192,1465,1007],{"class":211},[192,1467,1468],{"class":222}," mo,      ",[192,1470,1297],{"class":198},[192,1472,1473,1475,1478,1481,1483,1485,1488],{"class":194,"line":241},[192,1474,1435],{"class":222},[192,1476,1477],{"class":211},"DAY",[192,1479,1480],{"class":211},"     FROM",[192,1482,1444],{"class":222},[192,1484,1007],{"class":211},[192,1486,1487],{"class":222}," dy,      ",[192,1489,1490],{"class":198},"-- 15\n",[192,1492,1493,1495,1498,1500,1502,1504,1507],{"class":194,"line":264},[192,1494,1435],{"class":222},[192,1496,1497],{"class":211},"HOUR",[192,1499,1441],{"class":211},[192,1501,1444],{"class":222},[192,1503,1007],{"class":211},[192,1505,1506],{"class":222}," hr,      ",[192,1508,1509],{"class":198},"-- 14\n",[192,1511,1512,1515,1517,1519,1521,1524],{"class":194,"line":270},[192,1513,1514],{"class":222},"  EXTRACT(DOW     ",[192,1516,508],{"class":211},[192,1518,1444],{"class":222},[192,1520,1007],{"class":211},[192,1522,1523],{"class":222}," dow,     ",[192,1525,1526],{"class":198},"-- 0=Sunday, 1=Monday, ... 6=Saturday\n",[192,1528,1529,1532,1534,1536,1538,1541],{"class":194,"line":277},[192,1530,1531],{"class":222},"  EXTRACT(DOY     ",[192,1533,508],{"class":211},[192,1535,1444],{"class":222},[192,1537,1007],{"class":211},[192,1539,1540],{"class":222}," doy,     ",[192,1542,1543],{"class":198},"-- day of year (1-366)\n",[192,1545,1546,1548,1551,1553,1555,1557,1560],{"class":194,"line":283},[192,1547,1435],{"class":222},[192,1549,1550],{"class":211},"QUARTER",[192,1552,646],{"class":211},[192,1554,1444],{"class":222},[192,1556,1007],{"class":211},[192,1558,1559],{"class":222}," q,       ",[192,1561,1562],{"class":198},"-- 1-4\n",[192,1564,1565,1568,1570,1572,1574,1577],{"class":194,"line":303},[192,1566,1567],{"class":222},"  EXTRACT(EPOCH   ",[192,1569,508],{"class":211},[192,1571,1444],{"class":222},[192,1573,1007],{"class":211},[192,1575,1576],{"class":222}," unix_ts  ",[192,1578,1579],{"class":198},"-- seconds since 1970-01-01 UTC\n",[192,1581,1582,1584],{"class":194,"line":309},[192,1583,508],{"class":211},[192,1585,1586],{"class":222}," orders;\n",[192,1588,1589],{"class":194,"line":314},[192,1590,274],{"emptyLinePlaceholder":273},[192,1592,1593],{"class":194,"line":320},[192,1594,1595],{"class":198},"-- date_trunc: round DOWN to the start of a unit (returns TIMESTAMPTZ)\n",[192,1597,1598,1600,1603,1606,1609,1611,1614],{"class":194,"line":332},[192,1599,502],{"class":211},[192,1601,1602],{"class":222}," date_trunc(",[192,1604,1605],{"class":298},"'month'",[192,1607,1608],{"class":222},", ordered_on) ",[192,1610,1007],{"class":211},[192,1612,1613],{"class":222}," month_start;   ",[192,1615,1616],{"class":198},"-- 2024-03-01 00:00:00+00\n",[192,1618,1619,1621,1623,1626,1629,1631,1634],{"class":194,"line":343},[192,1620,502],{"class":211},[192,1622,1602],{"class":222},[192,1624,1625],{"class":298},"'hour'",[192,1627,1628],{"class":222},",   ordered_on) ",[192,1630,1007],{"class":211},[192,1632,1633],{"class":222}," hour_start;   ",[192,1635,1636],{"class":198},"-- 2024-03-15 14:00:00+00\n",[192,1638,1639,1641,1643,1646,1648,1650,1653],{"class":194,"line":356},[192,1640,502],{"class":211},[192,1642,1602],{"class":222},[192,1644,1645],{"class":298},"'week'",[192,1647,1628],{"class":222},[192,1649,1007],{"class":211},[192,1651,1652],{"class":222}," week_start;   ",[192,1654,1655],{"class":198},"-- 2024-03-11 (Monday start)\n",[14,1657,1658,1661,1662,1665,1666,1669],{},[56,1659,1660],{},"date_trunc('month', ts)"," returns the first moment of the month containing ",[56,1663,1664],{},"ts"," — perfect for grouping by month: ",[56,1667,1668],{},"GROUP BY date_trunc('month', ordered_on)",".",[18,1671],{},[21,1673,1675],{"id":1674},"index-friendly-date-filtering","Index-Friendly Date Filtering",[180,1677,1678],{"language":182},[184,1679,1681],{"className":186,"code":1680,"language":182,"meta":188,"style":188},"-- ❌ WRONG: wraps the column in a function → defeats the index (seq scan)\nSELECT * FROM orders WHERE DATE(ordered_on) = '2024-03-15';\nSELECT * FROM orders WHERE EXTRACT(YEAR FROM ordered_on) = 2024;\n\n-- ✅ RIGHT: range on the raw column → uses the index\nSELECT * FROM orders\nWHERE ordered_on >= '2024-03-15'::timestamptz\n  AND ordered_on \u003C  '2024-03-16'::timestamptz;\n\n-- ❌ WRONG: BETWEEN is inclusive on both ends — misses times after 23:59:59\nSELECT * FROM orders WHERE ordered_on BETWEEN '2024-01-01' AND '2024-01-31';\n-- Misses orders at 2024-01-31 18:00:00 (only captures 2024-01-31 00:00:00)\n\n-- ✅ RIGHT: half-open range — correct, composable, no off-by-one\nSELECT * FROM orders\nWHERE ordered_on >= '2024-01-01'::timestamptz\n  AND ordered_on \u003C  '2024-02-01'::timestamptz;   -- [Jan 1, Feb 1) = all of January\n",[56,1682,1683,1688,1714,1741,1745,1750,1761,1778,1797,1801,1806,1834,1839,1843,1848,1858,1872],{"__ignoreMap":188},[192,1684,1685],{"class":194,"line":195},[192,1686,1687],{"class":198},"-- ❌ WRONG: wraps the column in a function → defeats the index (seq scan)\n",[192,1689,1690,1692,1695,1697,1700,1702,1705,1708,1710,1712],{"class":194,"line":202},[192,1691,502],{"class":211},[192,1693,1694],{"class":211}," *",[192,1696,646],{"class":211},[192,1698,1699],{"class":222}," orders ",[192,1701,750],{"class":211},[192,1703,1704],{"class":211}," DATE",[192,1706,1707],{"class":222},"(ordered_on) ",[192,1709,450],{"class":211},[192,1711,1176],{"class":298},[192,1713,456],{"class":222},[192,1715,1716,1718,1720,1722,1724,1726,1728,1730,1732,1734,1736,1739],{"class":194,"line":208},[192,1717,502],{"class":211},[192,1719,1694],{"class":211},[192,1721,646],{"class":211},[192,1723,1699],{"class":222},[192,1725,750],{"class":211},[192,1727,1282],{"class":222},[192,1729,1438],{"class":211},[192,1731,646],{"class":211},[192,1733,1444],{"class":222},[192,1735,450],{"class":211},[192,1737,1738],{"class":769}," 2024",[192,1740,456],{"class":222},[192,1742,1743],{"class":194,"line":226},[192,1744,274],{"emptyLinePlaceholder":273},[192,1746,1747],{"class":194,"line":241},[192,1748,1749],{"class":198},"-- ✅ RIGHT: range on the raw column → uses the index\n",[192,1751,1752,1754,1756,1758],{"class":194,"line":264},[192,1753,502],{"class":211},[192,1755,1694],{"class":211},[192,1757,646],{"class":211},[192,1759,1760],{"class":222}," orders\n",[192,1762,1763,1765,1768,1771,1773,1775],{"class":194,"line":270},[192,1764,750],{"class":211},[192,1766,1767],{"class":222}," ordered_on ",[192,1769,1770],{"class":211},">=",[192,1772,1176],{"class":298},[192,1774,1179],{"class":222},[192,1776,1777],{"class":211},"timestamptz\n",[192,1779,1780,1783,1785,1788,1791,1793,1795],{"class":194,"line":277},[192,1781,1782],{"class":211},"  AND",[192,1784,1767],{"class":222},[192,1786,1787],{"class":211},"\u003C",[192,1789,1790],{"class":298},"  '2024-03-16'",[192,1792,1179],{"class":222},[192,1794,1253],{"class":211},[192,1796,456],{"class":222},[192,1798,1799],{"class":194,"line":283},[192,1800,274],{"emptyLinePlaceholder":273},[192,1802,1803],{"class":194,"line":303},[192,1804,1805],{"class":198},"-- ❌ WRONG: BETWEEN is inclusive on both ends — misses times after 23:59:59\n",[192,1807,1808,1810,1812,1814,1816,1818,1820,1823,1826,1829,1832],{"class":194,"line":309},[192,1809,502],{"class":211},[192,1811,1694],{"class":211},[192,1813,646],{"class":211},[192,1815,1699],{"class":222},[192,1817,750],{"class":211},[192,1819,1767],{"class":222},[192,1821,1822],{"class":211},"BETWEEN",[192,1824,1825],{"class":298}," '2024-01-01'",[192,1827,1828],{"class":211}," AND",[192,1830,1831],{"class":298}," '2024-01-31'",[192,1833,456],{"class":222},[192,1835,1836],{"class":194,"line":314},[192,1837,1838],{"class":198},"-- Misses orders at 2024-01-31 18:00:00 (only captures 2024-01-31 00:00:00)\n",[192,1840,1841],{"class":194,"line":320},[192,1842,274],{"emptyLinePlaceholder":273},[192,1844,1845],{"class":194,"line":332},[192,1846,1847],{"class":198},"-- ✅ RIGHT: half-open range — correct, composable, no off-by-one\n",[192,1849,1850,1852,1854,1856],{"class":194,"line":343},[192,1851,502],{"class":211},[192,1853,1694],{"class":211},[192,1855,646],{"class":211},[192,1857,1760],{"class":222},[192,1859,1860,1862,1864,1866,1868,1870],{"class":194,"line":356},[192,1861,750],{"class":211},[192,1863,1767],{"class":222},[192,1865,1770],{"class":211},[192,1867,1825],{"class":298},[192,1869,1179],{"class":222},[192,1871,1777],{"class":211},[192,1873,1874,1876,1878,1880,1883,1885,1887,1889],{"class":194,"line":546},[192,1875,1782],{"class":211},[192,1877,1767],{"class":222},[192,1879,1787],{"class":211},[192,1881,1882],{"class":298},"  '2024-02-01'",[192,1884,1179],{"class":222},[192,1886,1253],{"class":211},[192,1888,718],{"class":222},[192,1890,1891],{"class":198},"-- [Jan 1, Feb 1) = all of January\n",[14,1893,1894,295,1897,1900,1901,1903,1904,1907],{},[117,1895,1896],{},"Use half-open ranges",[56,1898,1899],{},">= start AND \u003C end",") instead of ",[56,1902,1822],{}," for timestamp ranges. ",[56,1905,1906],{},"[2024-01-01, 2024-02-01)"," captures the entire month correctly and composes cleanly with adjacent months.",[18,1909],{},[21,1911,1913],{"id":1912},"generating-series-filling-gaps","Generating Series (Filling Gaps)",[14,1915,1916,1919],{},[56,1917,1918],{},"generate_series"," produces a complete range of timestamps — essential for time-series reports with no gaps (days with zero orders should show 0, not be missing):",[180,1921,1922],{"language":182},[184,1923,1925],{"className":186,"code":1924,"language":182,"meta":188,"style":188},"-- Orders per day, including days with zero orders (LEFT JOIN fills gaps)\nSELECT\n  day::date                      AS day,\n  COUNT(o.id)                    AS orders,\n  COALESCE(SUM(o.amount), 0)     AS revenue\nFROM generate_series(\n  '2024-01-01'::timestamptz,     -- start (inclusive)\n  '2024-01-31'::timestamptz,     -- end (inclusive)\n  INTERVAL '1 day'               -- step\n) AS day\nLEFT JOIN orders o\n  ON date_trunc('day', o.ordered_on) = day   -- join on the day bucket\nGROUP BY day\nORDER BY day;\n\n-- generate_series with months (variable-length — handles Feb correctly)\nSELECT * FROM generate_series(\n  '2024-01-01'::timestamptz,\n  '2024-12-01'::timestamptz,\n  INTERVAL '1 month'             -- each step is exactly 1 calendar month\n);\n",[56,1926,1927,1932,1936,1953,1977,2010,2020,2035,2049,2060,2070,2078,2106,2113,2122,2126,2131,2143,2154,2166,2177],{"__ignoreMap":188},[192,1928,1929],{"class":194,"line":195},[192,1930,1931],{"class":198},"-- Orders per day, including days with zero orders (LEFT JOIN fills gaps)\n",[192,1933,1934],{"class":194,"line":202},[192,1935,1430],{"class":211},[192,1937,1938,1941,1943,1945,1948,1951],{"class":194,"line":208},[192,1939,1940],{"class":211},"  day",[192,1942,1179],{"class":222},[192,1944,1182],{"class":211},[192,1946,1947],{"class":211},"                      AS",[192,1949,1950],{"class":211}," day",[192,1952,238],{"class":222},[192,1954,1955,1958,1961,1964,1966,1969,1972,1974],{"class":194,"line":226},[192,1956,1957],{"class":769},"  COUNT",[192,1959,1960],{"class":222},"(",[192,1962,1963],{"class":769},"o",[192,1965,1669],{"class":222},[192,1967,1968],{"class":769},"id",[192,1970,1971],{"class":222},")                    ",[192,1973,1007],{"class":211},[192,1975,1976],{"class":222}," orders,\n",[192,1978,1979,1982,1984,1987,1989,1991,1993,1996,1999,2002,2005,2007],{"class":194,"line":241},[192,1980,1981],{"class":769},"  COALESCE",[192,1983,1960],{"class":222},[192,1985,1986],{"class":769},"SUM",[192,1988,1960],{"class":222},[192,1990,1963],{"class":769},[192,1992,1669],{"class":222},[192,1994,1995],{"class":769},"amount",[192,1997,1998],{"class":222},"), ",[192,2000,2001],{"class":769},"0",[192,2003,2004],{"class":222},")     ",[192,2006,1007],{"class":211},[192,2008,2009],{"class":222}," revenue\n",[192,2011,2012,2014,2017],{"class":194,"line":264},[192,2013,508],{"class":211},[192,2015,2016],{"class":769}," generate_series",[192,2018,2019],{"class":222},"(\n",[192,2021,2022,2025,2027,2029,2032],{"class":194,"line":270},[192,2023,2024],{"class":298},"  '2024-01-01'",[192,2026,1179],{"class":222},[192,2028,1253],{"class":211},[192,2030,2031],{"class":222},",     ",[192,2033,2034],{"class":198},"-- start (inclusive)\n",[192,2036,2037,2040,2042,2044,2046],{"class":194,"line":277},[192,2038,2039],{"class":298},"  '2024-01-31'",[192,2041,1179],{"class":222},[192,2043,1253],{"class":211},[192,2045,2031],{"class":222},[192,2047,2048],{"class":198},"-- end (inclusive)\n",[192,2050,2051,2054,2057],{"class":194,"line":283},[192,2052,2053],{"class":222},"  INTERVAL ",[192,2055,2056],{"class":298},"'1 day'",[192,2058,2059],{"class":198},"               -- step\n",[192,2061,2062,2065,2067],{"class":194,"line":303},[192,2063,2064],{"class":222},") ",[192,2066,1007],{"class":211},[192,2068,2069],{"class":211}," day\n",[192,2071,2072,2075],{"class":194,"line":309},[192,2073,2074],{"class":211},"LEFT JOIN",[192,2076,2077],{"class":222}," orders o\n",[192,2079,2080,2083,2085,2088,2090,2092,2094,2097,2099,2101,2103],{"class":194,"line":314},[192,2081,2082],{"class":211},"  ON",[192,2084,1602],{"class":222},[192,2086,2087],{"class":298},"'day'",[192,2089,167],{"class":222},[192,2091,1963],{"class":769},[192,2093,1669],{"class":222},[192,2095,2096],{"class":769},"ordered_on",[192,2098,2064],{"class":222},[192,2100,450],{"class":211},[192,2102,1950],{"class":211},[192,2104,2105],{"class":198},"   -- join on the day bucket\n",[192,2107,2108,2111],{"class":194,"line":320},[192,2109,2110],{"class":211},"GROUP BY",[192,2112,2069],{"class":211},[192,2114,2115,2118,2120],{"class":194,"line":332},[192,2116,2117],{"class":211},"ORDER BY",[192,2119,1950],{"class":211},[192,2121,456],{"class":222},[192,2123,2124],{"class":194,"line":343},[192,2125,274],{"emptyLinePlaceholder":273},[192,2127,2128],{"class":194,"line":356},[192,2129,2130],{"class":198},"-- generate_series with months (variable-length — handles Feb correctly)\n",[192,2132,2133,2135,2137,2139,2141],{"class":194,"line":546},[192,2134,502],{"class":211},[192,2136,1694],{"class":211},[192,2138,646],{"class":211},[192,2140,2016],{"class":769},[192,2142,2019],{"class":222},[192,2144,2146,2148,2150,2152],{"class":194,"line":2145},18,[192,2147,2024],{"class":298},[192,2149,1179],{"class":222},[192,2151,1253],{"class":211},[192,2153,238],{"class":222},[192,2155,2157,2160,2162,2164],{"class":194,"line":2156},19,[192,2158,2159],{"class":298},"  '2024-12-01'",[192,2161,1179],{"class":222},[192,2163,1253],{"class":211},[192,2165,238],{"class":222},[192,2167,2169,2171,2174],{"class":194,"line":2168},20,[192,2170,2053],{"class":222},[192,2172,2173],{"class":298},"'1 month'",[192,2175,2176],{"class":198},"             -- each step is exactly 1 calendar month\n",[192,2178,2180],{"class":194,"line":2179},21,[192,2181,267],{"class":222},[14,2183,2184,2186,2187,2189],{},[56,2185,1918],{}," + ",[56,2188,2074],{}," is the standard \"fill the gaps\" pattern for time-series reports.",[18,2191],{},[21,2193,2195],{"id":2194},"complex-implementation-multi-timezone-scheduling-system","Complex Implementation: Multi-Timezone Scheduling System",[14,2197,2198],{},"A scheduling system that stores events in UTC, displays in the user's local zone, and handles DST transitions safely:",[180,2200,2201],{"language":182},[184,2202,2204],{"className":186,"code":2203,"language":182,"meta":188,"style":188},"-- ============================================================================\n-- Schema: multi-timezone meeting scheduler\n-- ============================================================================\nCREATE TABLE users (\n  id BIGSERIAL PRIMARY KEY,\n  name TEXT NOT NULL,\n  timezone TEXT NOT NULL DEFAULT 'UTC'   -- full IANA zone name (e.g., 'America\u002FNew_York')\n);\n\nCREATE TABLE meetings (\n  id BIGSERIAL PRIMARY KEY,\n  title TEXT NOT NULL,\n  -- Always store in TIMESTAMPTZ: a specific moment, unambiguous, DST-safe\n  start_at TIMESTAMPTZ NOT NULL,\n  duration INTERVAL NOT NULL DEFAULT INTERVAL '1 hour',  -- fixed-length duration\n  created_by BIGINT NOT NULL REFERENCES users(id)\n);\n\nCREATE INDEX meetings_start_at_idx ON meetings(start_at);\n\n-- ============================================================================\n-- Insert: store as TIMESTAMPTZ (the input zone is converted to UTC automatically)\n-- ============================================================================\n-- A user in New York schedules a meeting for \"2:30 PM local time on March 15\"\nINSERT INTO meetings (title, start_at, duration, created_by)\nVALUES (\n  'Quarterly Review',\n  '2024-03-15 14:30:00 America\u002FNew_York'::timestamptz,  -- stored as UTC\n  INTERVAL '1 hour',\n  1\n);\n-- Stored: 2024-03-15 19:30:00 UTC (= 2024-03-15 14:30:00-05 EDT)\n\n-- ============================================================================\n-- Display: convert to each viewer's local time for rendering\n-- ============================================================================\n-- Viewer in Tokyo\nSELECT\n  m.title,\n  m.start_at AT TIME ZONE u.timezone AS local_start,   -- TIMESTAMPTZ → TIMESTAMP (wall time)\n  (m.start_at + m.duration) AT TIME ZONE u.timezone AS local_end\nFROM meetings m\nJOIN users u ON u.id = 2   -- Tokyo user\nWHERE m.start_at >= now() AND m.start_at \u003C now() + INTERVAL '7 days'\nORDER BY m.start_at;\n-- local_start: 2024-03-16 04:30:00 (Tokyo wall time)\n\n-- Viewer in New York\nSELECT\n  m.title,\n  m.start_at AT TIME ZONE 'America\u002FNew_York' AS local_start\nFROM meetings m\nWHERE m.id = 1;\n-- local_start: 2024-03-15 14:30:00 (NY wall time)\n\n-- ============================================================================\n-- DST-safe interval arithmetic: adding fixed-length intervals to TIMESTAMPTZ\n-- ============================================================================\n-- Adding INTERVAL '1 hour' to a TIMESTAMPTZ is always exactly 1 hour,\n-- even across a DST transition (the UTC moment shifts by exactly 3600 seconds)\nSELECT\n  start_at,\n  start_at + duration AS end_at,                         -- UTC-correct end moment\n  (start_at + duration) AT TIME ZONE 'America\u002FNew_York' AS end_ny  -- NY wall time end\nFROM meetings WHERE id = 1;\n\n-- ⚠️ WARNING: adding INTERVAL '1 month' is NOT fixed-length\n-- Adding 1 month to 2024-01-31 gives 2024-02-29 (or 2024-02-28 in non-leap years)\nSELECT '2024-01-31'::timestamptz + INTERVAL '1 month';   -- 2024-02-29 00:00:00+00\n\n-- ============================================================================\n-- Recurring meetings: generate next N occurrences with generate_series\n-- ============================================================================\n-- Weekly meeting for 8 weeks, starting from the first occurrence\nSELECT\n  gs AS occurrence_start,\n  gs AT TIME ZONE 'America\u002FNew_York' AS ny_wall_time,   -- display in creator's zone\n  gs + INTERVAL '1 hour' AS occurrence_end\nFROM generate_series(\n  '2024-03-15 14:30:00 America\u002FNew_York'::timestamptz,\n  '2024-03-15 14:30:00 America\u002FNew_York'::timestamptz + INTERVAL '7 weeks',\n  INTERVAL '1 week'                                        -- fixed: always 7 days\n) AS gs;\n-- Note: each occurrence is 7*24*3600 seconds later in UTC,\n-- but the NY wall time stays 14:30 — until DST shifts it (March 10 → March 17 is fine,\n-- but a series crossing Nov 3 would show 14:30 EDT then 13:30 EST — the UTC moment is fixed)\n\n-- ============================================================================\n-- Find all meetings in a user's \"today\" (their local today, not UTC today)\n-- ============================================================================\nSELECT m.*\nFROM meetings m\nJOIN users u ON u.id = 2\n-- Convert start_at to the user's wall time, then check if it's today in their zone\nWHERE (m.start_at AT TIME ZONE u.timezone)::date = CURRENT_DATE;\n",[56,2205,2206,2211,2216,2220,2231,2241,2253,2270,2274,2278,2289,2299,2310,2315,2326,2346,2362,2366,2370,2386,2390,2394,2400,2405,2411,2419,2426,2434,2449,2458,2464,2469,2475,2480,2485,2491,2496,2502,2507,2520,2553,2596,2604,2631,2672,2685,2691,2696,2702,2707,2718,2740,2747,2765,2771,2776,2781,2787,2792,2798,2804,2809,2815,2833,2860,2879,2884,2890,2896,2918,2923,2928,2934,2939,2945,2950,2961,2982,2998,3007,3018,3036,3047,3057,3063,3069,3075,3080,3085,3091,3096,3107,3114,3134,3140],{"__ignoreMap":188},[192,2207,2208],{"class":194,"line":195},[192,2209,2210],{"class":198},"-- ============================================================================\n",[192,2212,2213],{"class":194,"line":202},[192,2214,2215],{"class":198},"-- Schema: multi-timezone meeting scheduler\n",[192,2217,2218],{"class":194,"line":208},[192,2219,2210],{"class":198},[192,2221,2222,2224,2226,2229],{"class":194,"line":226},[192,2223,212],{"class":211},[192,2225,215],{"class":211},[192,2227,2228],{"class":218}," users",[192,2230,223],{"class":222},[192,2232,2233,2235,2237,2239],{"class":194,"line":241},[192,2234,229],{"class":222},[192,2236,232],{"class":211},[192,2238,235],{"class":211},[192,2240,238],{"class":222},[192,2242,2243,2246,2249,2251],{"class":194,"line":264},[192,2244,2245],{"class":211},"  name",[192,2247,2248],{"class":211}," TEXT",[192,2250,249],{"class":211},[192,2252,238],{"class":222},[192,2254,2255,2258,2261,2263,2265,2267],{"class":194,"line":270},[192,2256,2257],{"class":222},"  timezone ",[192,2259,2260],{"class":211},"TEXT",[192,2262,249],{"class":211},[192,2264,252],{"class":211},[192,2266,531],{"class":298},[192,2268,2269],{"class":198},"   -- full IANA zone name (e.g., 'America\u002FNew_York')\n",[192,2271,2272],{"class":194,"line":277},[192,2273,267],{"class":222},[192,2275,2276],{"class":194,"line":283},[192,2277,274],{"emptyLinePlaceholder":273},[192,2279,2280,2282,2284,2287],{"class":194,"line":303},[192,2281,212],{"class":211},[192,2283,215],{"class":211},[192,2285,2286],{"class":218}," meetings",[192,2288,223],{"class":222},[192,2290,2291,2293,2295,2297],{"class":194,"line":309},[192,2292,229],{"class":222},[192,2294,232],{"class":211},[192,2296,235],{"class":211},[192,2298,238],{"class":222},[192,2300,2301,2304,2306,2308],{"class":194,"line":314},[192,2302,2303],{"class":222},"  title ",[192,2305,2260],{"class":211},[192,2307,249],{"class":211},[192,2309,238],{"class":222},[192,2311,2312],{"class":194,"line":320},[192,2313,2314],{"class":198},"  -- Always store in TIMESTAMPTZ: a specific moment, unambiguous, DST-safe\n",[192,2316,2317,2320,2322,2324],{"class":194,"line":332},[192,2318,2319],{"class":222},"  start_at ",[192,2321,134],{"class":211},[192,2323,249],{"class":211},[192,2325,238],{"class":222},[192,2327,2328,2331,2334,2336,2338,2340,2343],{"class":194,"line":343},[192,2329,2330],{"class":222},"  duration INTERVAL ",[192,2332,2333],{"class":211},"NOT NULL",[192,2335,252],{"class":211},[192,2337,1072],{"class":222},[192,2339,170],{"class":298},[192,2341,2342],{"class":222},",  ",[192,2344,2345],{"class":198},"-- fixed-length duration\n",[192,2347,2348,2351,2354,2356,2359],{"class":194,"line":356},[192,2349,2350],{"class":222},"  created_by ",[192,2352,2353],{"class":211},"BIGINT",[192,2355,249],{"class":211},[192,2357,2358],{"class":211}," REFERENCES",[192,2360,2361],{"class":222}," users(id)\n",[192,2363,2364],{"class":194,"line":546},[192,2365,267],{"class":222},[192,2367,2368],{"class":194,"line":2145},[192,2369,274],{"emptyLinePlaceholder":273},[192,2371,2372,2374,2377,2380,2383],{"class":194,"line":2156},[192,2373,212],{"class":211},[192,2375,2376],{"class":211}," INDEX",[192,2378,2379],{"class":218}," meetings_start_at_idx",[192,2381,2382],{"class":211}," ON",[192,2384,2385],{"class":222}," meetings(start_at);\n",[192,2387,2388],{"class":194,"line":2168},[192,2389,274],{"emptyLinePlaceholder":273},[192,2391,2392],{"class":194,"line":2179},[192,2393,2210],{"class":198},[192,2395,2397],{"class":194,"line":2396},22,[192,2398,2399],{"class":198},"-- Insert: store as TIMESTAMPTZ (the input zone is converted to UTC automatically)\n",[192,2401,2403],{"class":194,"line":2402},23,[192,2404,2210],{"class":198},[192,2406,2408],{"class":194,"line":2407},24,[192,2409,2410],{"class":198},"-- A user in New York schedules a meeting for \"2:30 PM local time on March 15\"\n",[192,2412,2414,2416],{"class":194,"line":2413},25,[192,2415,286],{"class":211},[192,2417,2418],{"class":222}," meetings (title, start_at, duration, created_by)\n",[192,2420,2422,2424],{"class":194,"line":2421},26,[192,2423,292],{"class":211},[192,2425,223],{"class":222},[192,2427,2429,2432],{"class":194,"line":2428},27,[192,2430,2431],{"class":298},"  'Quarterly Review'",[192,2433,238],{"class":222},[192,2435,2437,2440,2442,2444,2446],{"class":194,"line":2436},28,[192,2438,2439],{"class":298},"  '2024-03-15 14:30:00 America\u002FNew_York'",[192,2441,1179],{"class":222},[192,2443,1253],{"class":211},[192,2445,2342],{"class":222},[192,2447,2448],{"class":198},"-- stored as UTC\n",[192,2450,2452,2454,2456],{"class":194,"line":2451},29,[192,2453,2053],{"class":222},[192,2455,170],{"class":298},[192,2457,238],{"class":222},[192,2459,2461],{"class":194,"line":2460},30,[192,2462,2463],{"class":769},"  1\n",[192,2465,2467],{"class":194,"line":2466},31,[192,2468,267],{"class":222},[192,2470,2472],{"class":194,"line":2471},32,[192,2473,2474],{"class":198},"-- Stored: 2024-03-15 19:30:00 UTC (= 2024-03-15 14:30:00-05 EDT)\n",[192,2476,2478],{"class":194,"line":2477},33,[192,2479,274],{"emptyLinePlaceholder":273},[192,2481,2483],{"class":194,"line":2482},34,[192,2484,2210],{"class":198},[192,2486,2488],{"class":194,"line":2487},35,[192,2489,2490],{"class":198},"-- Display: convert to each viewer's local time for rendering\n",[192,2492,2494],{"class":194,"line":2493},36,[192,2495,2210],{"class":198},[192,2497,2499],{"class":194,"line":2498},37,[192,2500,2501],{"class":198},"-- Viewer in Tokyo\n",[192,2503,2505],{"class":194,"line":2504},38,[192,2506,1430],{"class":211},[192,2508,2510,2513,2515,2518],{"class":194,"line":2509},39,[192,2511,2512],{"class":769},"  m",[192,2514,1669],{"class":222},[192,2516,2517],{"class":769},"title",[192,2519,238],{"class":222},[192,2521,2523,2525,2527,2530,2532,2534,2536,2539,2541,2544,2547,2550],{"class":194,"line":2522},40,[192,2524,2512],{"class":769},[192,2526,1669],{"class":222},[192,2528,2529],{"class":769},"start_at",[192,2531,675],{"class":211},[192,2533,638],{"class":211},[192,2535,641],{"class":211},[192,2537,2538],{"class":769}," u",[192,2540,1669],{"class":222},[192,2542,2543],{"class":769},"timezone",[192,2545,2546],{"class":211}," AS",[192,2548,2549],{"class":222}," local_start,   ",[192,2551,2552],{"class":198},"-- TIMESTAMPTZ → TIMESTAMP (wall time)\n",[192,2554,2556,2559,2562,2564,2566,2569,2572,2574,2577,2579,2581,2583,2585,2587,2589,2591,2593],{"class":194,"line":2555},41,[192,2557,2558],{"class":222},"  (",[192,2560,2561],{"class":769},"m",[192,2563,1669],{"class":222},[192,2565,2529],{"class":769},[192,2567,2568],{"class":211}," +",[192,2570,2571],{"class":769}," m",[192,2573,1669],{"class":222},[192,2575,2576],{"class":769},"duration",[192,2578,2064],{"class":222},[192,2580,635],{"class":211},[192,2582,638],{"class":211},[192,2584,641],{"class":211},[192,2586,2538],{"class":769},[192,2588,1669],{"class":222},[192,2590,2543],{"class":769},[192,2592,2546],{"class":211},[192,2594,2595],{"class":222}," local_end\n",[192,2597,2599,2601],{"class":194,"line":2598},42,[192,2600,508],{"class":211},[192,2602,2603],{"class":222}," meetings m\n",[192,2605,2607,2610,2613,2616,2618,2620,2622,2625,2628],{"class":194,"line":2606},43,[192,2608,2609],{"class":211},"JOIN",[192,2611,2612],{"class":222}," users u ",[192,2614,2615],{"class":211},"ON",[192,2617,2538],{"class":769},[192,2619,1669],{"class":222},[192,2621,1968],{"class":769},[192,2623,2624],{"class":211}," =",[192,2626,2627],{"class":769}," 2",[192,2629,2630],{"class":198},"   -- Tokyo user\n",[192,2632,2634,2636,2638,2640,2642,2645,2647,2649,2652,2654,2656,2658,2661,2663,2665,2667,2669],{"class":194,"line":2633},44,[192,2635,750],{"class":211},[192,2637,2571],{"class":769},[192,2639,1669],{"class":222},[192,2641,2529],{"class":769},[192,2643,2644],{"class":211}," >=",[192,2646,255],{"class":211},[192,2648,706],{"class":222},[192,2650,2651],{"class":211},"AND",[192,2653,2571],{"class":769},[192,2655,1669],{"class":222},[192,2657,2529],{"class":769},[192,2659,2660],{"class":211}," \u003C",[192,2662,255],{"class":211},[192,2664,706],{"class":222},[192,2666,1130],{"class":211},[192,2668,1072],{"class":222},[192,2670,2671],{"class":298},"'7 days'\n",[192,2673,2675,2677,2679,2681,2683],{"class":194,"line":2674},45,[192,2676,2117],{"class":211},[192,2678,2571],{"class":769},[192,2680,1669],{"class":222},[192,2682,2529],{"class":769},[192,2684,456],{"class":222},[192,2686,2688],{"class":194,"line":2687},46,[192,2689,2690],{"class":198},"-- local_start: 2024-03-16 04:30:00 (Tokyo wall time)\n",[192,2692,2694],{"class":194,"line":2693},47,[192,2695,274],{"emptyLinePlaceholder":273},[192,2697,2699],{"class":194,"line":2698},48,[192,2700,2701],{"class":198},"-- Viewer in New York\n",[192,2703,2705],{"class":194,"line":2704},49,[192,2706,1430],{"class":211},[192,2708,2710,2712,2714,2716],{"class":194,"line":2709},50,[192,2711,2512],{"class":769},[192,2713,1669],{"class":222},[192,2715,2517],{"class":769},[192,2717,238],{"class":222},[192,2719,2721,2723,2725,2727,2729,2731,2733,2735,2737],{"class":194,"line":2720},51,[192,2722,2512],{"class":769},[192,2724,1669],{"class":222},[192,2726,2529],{"class":769},[192,2728,675],{"class":211},[192,2730,638],{"class":211},[192,2732,641],{"class":211},[192,2734,453],{"class":298},[192,2736,2546],{"class":211},[192,2738,2739],{"class":222}," local_start\n",[192,2741,2743,2745],{"class":194,"line":2742},52,[192,2744,508],{"class":211},[192,2746,2603],{"class":222},[192,2748,2750,2752,2754,2756,2758,2760,2763],{"class":194,"line":2749},53,[192,2751,750],{"class":211},[192,2753,2571],{"class":769},[192,2755,1669],{"class":222},[192,2757,1968],{"class":769},[192,2759,2624],{"class":211},[192,2761,2762],{"class":769}," 1",[192,2764,456],{"class":222},[192,2766,2768],{"class":194,"line":2767},54,[192,2769,2770],{"class":198},"-- local_start: 2024-03-15 14:30:00 (NY wall time)\n",[192,2772,2774],{"class":194,"line":2773},55,[192,2775,274],{"emptyLinePlaceholder":273},[192,2777,2779],{"class":194,"line":2778},56,[192,2780,2210],{"class":198},[192,2782,2784],{"class":194,"line":2783},57,[192,2785,2786],{"class":198},"-- DST-safe interval arithmetic: adding fixed-length intervals to TIMESTAMPTZ\n",[192,2788,2790],{"class":194,"line":2789},58,[192,2791,2210],{"class":198},[192,2793,2795],{"class":194,"line":2794},59,[192,2796,2797],{"class":198},"-- Adding INTERVAL '1 hour' to a TIMESTAMPTZ is always exactly 1 hour,\n",[192,2799,2801],{"class":194,"line":2800},60,[192,2802,2803],{"class":198},"-- even across a DST transition (the UTC moment shifts by exactly 3600 seconds)\n",[192,2805,2807],{"class":194,"line":2806},61,[192,2808,1430],{"class":211},[192,2810,2812],{"class":194,"line":2811},62,[192,2813,2814],{"class":222},"  start_at,\n",[192,2816,2818,2820,2822,2825,2827,2830],{"class":194,"line":2817},63,[192,2819,2319],{"class":222},[192,2821,1130],{"class":211},[192,2823,2824],{"class":222}," duration ",[192,2826,1007],{"class":211},[192,2828,2829],{"class":222}," end_at,                         ",[192,2831,2832],{"class":198},"-- UTC-correct end moment\n",[192,2834,2836,2839,2841,2844,2846,2848,2850,2852,2854,2857],{"class":194,"line":2835},64,[192,2837,2838],{"class":222},"  (start_at ",[192,2840,1130],{"class":211},[192,2842,2843],{"class":222}," duration) ",[192,2845,635],{"class":211},[192,2847,638],{"class":211},[192,2849,641],{"class":211},[192,2851,453],{"class":298},[192,2853,2546],{"class":211},[192,2855,2856],{"class":222}," end_ny  ",[192,2858,2859],{"class":198},"-- NY wall time end\n",[192,2861,2863,2865,2868,2870,2873,2875,2877],{"class":194,"line":2862},65,[192,2864,508],{"class":211},[192,2866,2867],{"class":222}," meetings ",[192,2869,750],{"class":211},[192,2871,2872],{"class":222}," id ",[192,2874,450],{"class":211},[192,2876,2762],{"class":769},[192,2878,456],{"class":222},[192,2880,2882],{"class":194,"line":2881},66,[192,2883,274],{"emptyLinePlaceholder":273},[192,2885,2887],{"class":194,"line":2886},67,[192,2888,2889],{"class":198},"-- ⚠️ WARNING: adding INTERVAL '1 month' is NOT fixed-length\n",[192,2891,2893],{"class":194,"line":2892},68,[192,2894,2895],{"class":198},"-- Adding 1 month to 2024-01-31 gives 2024-02-29 (or 2024-02-28 in non-leap years)\n",[192,2897,2899,2901,2903,2905,2907,2909,2911,2913,2915],{"class":194,"line":2898},69,[192,2900,502],{"class":211},[192,2902,1831],{"class":298},[192,2904,1179],{"class":222},[192,2906,1253],{"class":211},[192,2908,2568],{"class":211},[192,2910,1072],{"class":222},[192,2912,2173],{"class":298},[192,2914,718],{"class":222},[192,2916,2917],{"class":198},"-- 2024-02-29 00:00:00+00\n",[192,2919,2921],{"class":194,"line":2920},70,[192,2922,274],{"emptyLinePlaceholder":273},[192,2924,2926],{"class":194,"line":2925},71,[192,2927,2210],{"class":198},[192,2929,2931],{"class":194,"line":2930},72,[192,2932,2933],{"class":198},"-- Recurring meetings: generate next N occurrences with generate_series\n",[192,2935,2937],{"class":194,"line":2936},73,[192,2938,2210],{"class":198},[192,2940,2942],{"class":194,"line":2941},74,[192,2943,2944],{"class":198},"-- Weekly meeting for 8 weeks, starting from the first occurrence\n",[192,2946,2948],{"class":194,"line":2947},75,[192,2949,1430],{"class":211},[192,2951,2953,2956,2958],{"class":194,"line":2952},76,[192,2954,2955],{"class":222},"  gs ",[192,2957,1007],{"class":211},[192,2959,2960],{"class":222}," occurrence_start,\n",[192,2962,2964,2966,2968,2970,2972,2974,2976,2979],{"class":194,"line":2963},77,[192,2965,2955],{"class":222},[192,2967,635],{"class":211},[192,2969,638],{"class":211},[192,2971,641],{"class":211},[192,2973,453],{"class":298},[192,2975,2546],{"class":211},[192,2977,2978],{"class":222}," ny_wall_time,   ",[192,2980,2981],{"class":198},"-- display in creator's zone\n",[192,2983,2985,2987,2989,2991,2993,2995],{"class":194,"line":2984},78,[192,2986,2955],{"class":222},[192,2988,1130],{"class":211},[192,2990,1072],{"class":222},[192,2992,170],{"class":298},[192,2994,2546],{"class":211},[192,2996,2997],{"class":222}," occurrence_end\n",[192,2999,3001,3003,3005],{"class":194,"line":3000},79,[192,3002,508],{"class":211},[192,3004,2016],{"class":769},[192,3006,2019],{"class":222},[192,3008,3010,3012,3014,3016],{"class":194,"line":3009},80,[192,3011,2439],{"class":298},[192,3013,1179],{"class":222},[192,3015,1253],{"class":211},[192,3017,238],{"class":222},[192,3019,3021,3023,3025,3027,3029,3031,3034],{"class":194,"line":3020},81,[192,3022,2439],{"class":298},[192,3024,1179],{"class":222},[192,3026,1253],{"class":211},[192,3028,2568],{"class":211},[192,3030,1072],{"class":222},[192,3032,3033],{"class":298},"'7 weeks'",[192,3035,238],{"class":222},[192,3037,3039,3041,3044],{"class":194,"line":3038},82,[192,3040,2053],{"class":222},[192,3042,3043],{"class":298},"'1 week'",[192,3045,3046],{"class":198},"                                        -- fixed: always 7 days\n",[192,3048,3050,3052,3054],{"class":194,"line":3049},83,[192,3051,2064],{"class":222},[192,3053,1007],{"class":211},[192,3055,3056],{"class":222}," gs;\n",[192,3058,3060],{"class":194,"line":3059},84,[192,3061,3062],{"class":198},"-- Note: each occurrence is 7*24*3600 seconds later in UTC,\n",[192,3064,3066],{"class":194,"line":3065},85,[192,3067,3068],{"class":198},"-- but the NY wall time stays 14:30 — until DST shifts it (March 10 → March 17 is fine,\n",[192,3070,3072],{"class":194,"line":3071},86,[192,3073,3074],{"class":198},"-- but a series crossing Nov 3 would show 14:30 EDT then 13:30 EST — the UTC moment is fixed)\n",[192,3076,3078],{"class":194,"line":3077},87,[192,3079,274],{"emptyLinePlaceholder":273},[192,3081,3083],{"class":194,"line":3082},88,[192,3084,2210],{"class":198},[192,3086,3088],{"class":194,"line":3087},89,[192,3089,3090],{"class":198},"-- Find all meetings in a user's \"today\" (their local today, not UTC today)\n",[192,3092,3094],{"class":194,"line":3093},90,[192,3095,2210],{"class":198},[192,3097,3099,3101,3104],{"class":194,"line":3098},91,[192,3100,502],{"class":211},[192,3102,3103],{"class":222}," m.",[192,3105,3106],{"class":211},"*\n",[192,3108,3110,3112],{"class":194,"line":3109},92,[192,3111,508],{"class":211},[192,3113,2603],{"class":222},[192,3115,3117,3119,3121,3123,3125,3127,3129,3131],{"class":194,"line":3116},93,[192,3118,2609],{"class":211},[192,3120,2612],{"class":222},[192,3122,2615],{"class":211},[192,3124,2538],{"class":769},[192,3126,1669],{"class":222},[192,3128,1968],{"class":769},[192,3130,2624],{"class":211},[192,3132,3133],{"class":769}," 2\n",[192,3135,3137],{"class":194,"line":3136},94,[192,3138,3139],{"class":198},"-- Convert start_at to the user's wall time, then check if it's today in their zone\n",[192,3141,3143,3145,3147,3149,3151,3153,3155,3157,3159,3161,3163,3165,3168,3170,3172],{"class":194,"line":3142},95,[192,3144,750],{"class":211},[192,3146,295],{"class":222},[192,3148,2561],{"class":769},[192,3150,1669],{"class":222},[192,3152,2529],{"class":769},[192,3154,675],{"class":211},[192,3156,638],{"class":211},[192,3158,641],{"class":211},[192,3160,2538],{"class":769},[192,3162,1669],{"class":222},[192,3164,2543],{"class":769},[192,3166,3167],{"class":222},")::",[192,3169,1182],{"class":211},[192,3171,2624],{"class":211},[192,3173,3174],{"class":222}," CURRENT_DATE;\n",[14,3176,3177,3180],{},[117,3178,3179],{},"Key design decisions",":",[3182,3183,3184,3192,3200,3212],"ol",{},[1042,3185,3186,3191],{},[117,3187,3188,3189],{},"Store as ",[56,3190,134],{}," — unambiguous moments, UTC internally, DST-safe arithmetic.",[1042,3193,3194,3199],{},[117,3195,3196,3198],{},[56,3197,566],{}," for display"," — convert to the viewer's wall time only at query time.",[1042,3201,3202,295,3205,167,3208,3211],{},[117,3203,3204],{},"Fixed-length intervals",[56,3206,3207],{},"INTERVAL '1 hour'",[56,3209,3210],{},"INTERVAL '1 week'",") — always exact, even across DST.",[1042,3213,3214,295,3217,3220],{},[117,3215,3216],{},"Variable-length intervals",[56,3218,3219],{},"INTERVAL '1 month'",") — calendar-correct but not fixed-duration; use carefully.",[18,3222],{},[21,3224,3226],{"id":3225},"anti-pattern-storing-timestamps-without-timezone","Anti-Pattern: Storing Timestamps Without Timezone",[180,3228,3229],{"language":182},[184,3230,3232],{"className":186,"code":3231,"language":182,"meta":188,"style":188},"-- ❌ WRONG: TIMESTAMP without zone — no way to know what moment it represents\nCREATE TABLE bad_events (\n  id BIGSERIAL PRIMARY KEY,\n  occurred_at TIMESTAMP NOT NULL   -- '2024-03-15 14:30:00' — New York? UTC? Tokyo?\n);\n\n-- A server in UTC writes '2024-03-15 14:30:00' (meaning UTC)\n-- A server in New York writes '2024-03-15 14:30:00' (meaning NY = 19:30 UTC)\n-- Both rows have the same value but represent different moments → data corruption\n\n-- ✅ RIGHT: TIMESTAMPTZ — every write specifies (or defaults to) a zone, stored as UTC\nCREATE TABLE good_events (\n  id BIGSERIAL PRIMARY KEY,\n  occurred_at TIMESTAMPTZ NOT NULL   -- '2024-03-15 14:30:00-05' → stored as 19:30 UTC\n);\n",[56,3233,3234,3239,3250,3260,3272,3276,3280,3285,3290,3295,3299,3304,3315,3325,3336],{"__ignoreMap":188},[192,3235,3236],{"class":194,"line":195},[192,3237,3238],{"class":198},"-- ❌ WRONG: TIMESTAMP without zone — no way to know what moment it represents\n",[192,3240,3241,3243,3245,3248],{"class":194,"line":202},[192,3242,212],{"class":211},[192,3244,215],{"class":211},[192,3246,3247],{"class":218}," bad_events",[192,3249,223],{"class":222},[192,3251,3252,3254,3256,3258],{"class":194,"line":208},[192,3253,229],{"class":222},[192,3255,232],{"class":211},[192,3257,235],{"class":211},[192,3259,238],{"class":222},[192,3261,3262,3265,3267,3269],{"class":194,"line":226},[192,3263,3264],{"class":222},"  occurred_at ",[192,3266,112],{"class":211},[192,3268,249],{"class":211},[192,3270,3271],{"class":198},"   -- '2024-03-15 14:30:00' — New York? UTC? Tokyo?\n",[192,3273,3274],{"class":194,"line":241},[192,3275,267],{"class":222},[192,3277,3278],{"class":194,"line":264},[192,3279,274],{"emptyLinePlaceholder":273},[192,3281,3282],{"class":194,"line":270},[192,3283,3284],{"class":198},"-- A server in UTC writes '2024-03-15 14:30:00' (meaning UTC)\n",[192,3286,3287],{"class":194,"line":277},[192,3288,3289],{"class":198},"-- A server in New York writes '2024-03-15 14:30:00' (meaning NY = 19:30 UTC)\n",[192,3291,3292],{"class":194,"line":283},[192,3293,3294],{"class":198},"-- Both rows have the same value but represent different moments → data corruption\n",[192,3296,3297],{"class":194,"line":303},[192,3298,274],{"emptyLinePlaceholder":273},[192,3300,3301],{"class":194,"line":309},[192,3302,3303],{"class":198},"-- ✅ RIGHT: TIMESTAMPTZ — every write specifies (or defaults to) a zone, stored as UTC\n",[192,3305,3306,3308,3310,3313],{"class":194,"line":314},[192,3307,212],{"class":211},[192,3309,215],{"class":211},[192,3311,3312],{"class":218}," good_events",[192,3314,223],{"class":222},[192,3316,3317,3319,3321,3323],{"class":194,"line":320},[192,3318,229],{"class":222},[192,3320,232],{"class":211},[192,3322,235],{"class":211},[192,3324,238],{"class":222},[192,3326,3327,3329,3331,3333],{"class":194,"line":332},[192,3328,3264],{"class":222},[192,3330,134],{"class":211},[192,3332,249],{"class":211},[192,3334,3335],{"class":198},"   -- '2024-03-15 14:30:00-05' → stored as 19:30 UTC\n",[192,3337,3338],{"class":194,"line":343},[192,3339,267],{"class":222},[18,3341],{},[21,3343,3345],{"id":3344},"anti-pattern-double-converting-timezones","Anti-Pattern: Double-Converting Timezones",[180,3347,3348],{"language":182},[184,3349,3351],{"className":186,"code":3350,"language":182,"meta":188,"style":188},"-- ❌ WRONG: applying AT TIME ZONE twice — undoes the conversion\nSELECT\n  start_at AT TIME ZONE 'America\u002FNew_York' AT TIME ZONE 'America\u002FNew_York'\nFROM meetings;\n-- start_at (TIMESTAMPTZ) → AT TIME ZONE 'NY' → TIMESTAMP (NY wall time)\n-- TIMESTAMP → AT TIME ZONE 'NY' → TIMESTAMPTZ (reinterprets NY wall time as NY moment)\n-- Result: the original moment shifted by the session zone offset — wrong\n\n-- ✅ RIGHT: convert once, to the target zone for display\nSELECT start_at AT TIME ZONE 'America\u002FNew_York' FROM meetings;\n-- TIMESTAMPTZ → TIMESTAMP (NY wall time) — correct\n",[56,3352,3353,3358,3362,3383,3390,3395,3400,3405,3409,3414,3433],{"__ignoreMap":188},[192,3354,3355],{"class":194,"line":195},[192,3356,3357],{"class":198},"-- ❌ WRONG: applying AT TIME ZONE twice — undoes the conversion\n",[192,3359,3360],{"class":194,"line":202},[192,3361,1430],{"class":211},[192,3363,3364,3366,3368,3370,3372,3374,3376,3378,3380],{"class":194,"line":208},[192,3365,2319],{"class":222},[192,3367,635],{"class":211},[192,3369,638],{"class":211},[192,3371,641],{"class":211},[192,3373,453],{"class":298},[192,3375,675],{"class":211},[192,3377,638],{"class":211},[192,3379,641],{"class":211},[192,3381,3382],{"class":298}," 'America\u002FNew_York'\n",[192,3384,3385,3387],{"class":194,"line":226},[192,3386,508],{"class":211},[192,3388,3389],{"class":222}," meetings;\n",[192,3391,3392],{"class":194,"line":241},[192,3393,3394],{"class":198},"-- start_at (TIMESTAMPTZ) → AT TIME ZONE 'NY' → TIMESTAMP (NY wall time)\n",[192,3396,3397],{"class":194,"line":264},[192,3398,3399],{"class":198},"-- TIMESTAMP → AT TIME ZONE 'NY' → TIMESTAMPTZ (reinterprets NY wall time as NY moment)\n",[192,3401,3402],{"class":194,"line":270},[192,3403,3404],{"class":198},"-- Result: the original moment shifted by the session zone offset — wrong\n",[192,3406,3407],{"class":194,"line":277},[192,3408,274],{"emptyLinePlaceholder":273},[192,3410,3411],{"class":194,"line":283},[192,3412,3413],{"class":198},"-- ✅ RIGHT: convert once, to the target zone for display\n",[192,3415,3416,3418,3421,3423,3425,3427,3429,3431],{"class":194,"line":303},[192,3417,502],{"class":211},[192,3419,3420],{"class":222}," start_at ",[192,3422,635],{"class":211},[192,3424,638],{"class":211},[192,3426,641],{"class":211},[192,3428,453],{"class":298},[192,3430,646],{"class":211},[192,3432,3389],{"class":222},[192,3434,3435],{"class":194,"line":309},[192,3436,3437],{"class":198},"-- TIMESTAMPTZ → TIMESTAMP (NY wall time) — correct\n",[18,3439],{},[21,3441,3443],{"id":3442},"daylight-saving-time-traps","Daylight Saving Time Traps",[180,3445,3446],{"language":182},[184,3447,3449],{"className":186,"code":3448,"language":182,"meta":188,"style":188},"-- ============================================================================\n-- Spring-forward gap: 2:30 AM doesn't exist on 2024-03-10 in US zones\n-- ============================================================================\nSET TimeZone = 'America\u002FNew_York';\nSELECT '2024-03-10 02:30:00'::timestamptz;\n-- PostgreSQL advances it to 2024-03-10 03:30:00-04 (interprets as after the gap)\n-- (Or errors with 'discontinuity' depending on settings)\n\n-- ============================================================================\n-- Fall-back ambiguity: 1:30 AM happens twice on 2024-11-03\n-- ============================================================================\nSELECT '2024-11-03 01:30:00 America\u002FNew_York'::timestamptz;\n-- Interpreted as the FIRST occurrence (EDT, -04) — before the clocks fall back\n\nSELECT '2024-11-03 01:30:00-05'::timestamptz;\n-- Explicit offset → the SECOND occurrence (EST, -05) — after the clocks fall back\n\n-- ============================================================================\n-- DST-safe: store in UTC, convert for display — UTC has no DST\n-- ============================================================================\n-- A meeting stored as UTC is never ambiguous:\nINSERT INTO meetings (title, start_at, duration, created_by)\nVALUES ('DST Test', '2024-11-03 05:30:00+00'::timestamptz, INTERVAL '1 hour', 1);\n-- 05:30 UTC = 01:30 EDT (first occurrence) — unambiguous\n\n-- ============================================================================\n-- \"Missing hour\" in a schedule: spring-forward causes a gap\n-- ============================================================================\n-- A daily schedule from 2:00 to 3:00 on March 10 in NY:\n-- 2:00 AM doesn't exist (clocks jump to 3:00 AM)\n-- A naive schedule generator would create a phantom slot\nSELECT gs AT TIME ZONE 'America\u002FNew_York' AS slot\nFROM generate_series(\n  '2024-03-10 02:00:00 America\u002FNew_York'::timestamptz,\n  '2024-03-10 03:00:00 America\u002FNew_York'::timestamptz,\n  INTERVAL '30 minutes'\n) AS gs;\n-- The 02:00 slot is shifted to 03:00 (or 03:30) — the gap is silently skipped\n",[56,3450,3451,3455,3460,3464,3476,3489,3494,3499,3503,3507,3512,3516,3529,3534,3538,3551,3556,3560,3564,3569,3573,3578,3584,3614,3619,3623,3627,3632,3636,3641,3646,3651,3671,3679,3690,3701,3708,3716],{"__ignoreMap":188},[192,3452,3453],{"class":194,"line":195},[192,3454,2210],{"class":198},[192,3456,3457],{"class":194,"line":202},[192,3458,3459],{"class":198},"-- Spring-forward gap: 2:30 AM doesn't exist on 2024-03-10 in US zones\n",[192,3461,3462],{"class":194,"line":208},[192,3463,2210],{"class":198},[192,3465,3466,3468,3470,3472,3474],{"class":194,"line":226},[192,3467,444],{"class":211},[192,3469,447],{"class":222},[192,3471,450],{"class":211},[192,3473,453],{"class":298},[192,3475,456],{"class":222},[192,3477,3478,3480,3483,3485,3487],{"class":194,"line":241},[192,3479,502],{"class":211},[192,3481,3482],{"class":298}," '2024-03-10 02:30:00'",[192,3484,1179],{"class":222},[192,3486,1253],{"class":211},[192,3488,456],{"class":222},[192,3490,3491],{"class":194,"line":264},[192,3492,3493],{"class":198},"-- PostgreSQL advances it to 2024-03-10 03:30:00-04 (interprets as after the gap)\n",[192,3495,3496],{"class":194,"line":270},[192,3497,3498],{"class":198},"-- (Or errors with 'discontinuity' depending on settings)\n",[192,3500,3501],{"class":194,"line":277},[192,3502,274],{"emptyLinePlaceholder":273},[192,3504,3505],{"class":194,"line":283},[192,3506,2210],{"class":198},[192,3508,3509],{"class":194,"line":303},[192,3510,3511],{"class":198},"-- Fall-back ambiguity: 1:30 AM happens twice on 2024-11-03\n",[192,3513,3514],{"class":194,"line":309},[192,3515,2210],{"class":198},[192,3517,3518,3520,3523,3525,3527],{"class":194,"line":314},[192,3519,502],{"class":211},[192,3521,3522],{"class":298}," '2024-11-03 01:30:00 America\u002FNew_York'",[192,3524,1179],{"class":222},[192,3526,1253],{"class":211},[192,3528,456],{"class":222},[192,3530,3531],{"class":194,"line":320},[192,3532,3533],{"class":198},"-- Interpreted as the FIRST occurrence (EDT, -04) — before the clocks fall back\n",[192,3535,3536],{"class":194,"line":332},[192,3537,274],{"emptyLinePlaceholder":273},[192,3539,3540,3542,3545,3547,3549],{"class":194,"line":343},[192,3541,502],{"class":211},[192,3543,3544],{"class":298}," '2024-11-03 01:30:00-05'",[192,3546,1179],{"class":222},[192,3548,1253],{"class":211},[192,3550,456],{"class":222},[192,3552,3553],{"class":194,"line":356},[192,3554,3555],{"class":198},"-- Explicit offset → the SECOND occurrence (EST, -05) — after the clocks fall back\n",[192,3557,3558],{"class":194,"line":546},[192,3559,274],{"emptyLinePlaceholder":273},[192,3561,3562],{"class":194,"line":2145},[192,3563,2210],{"class":198},[192,3565,3566],{"class":194,"line":2156},[192,3567,3568],{"class":198},"-- DST-safe: store in UTC, convert for display — UTC has no DST\n",[192,3570,3571],{"class":194,"line":2168},[192,3572,2210],{"class":198},[192,3574,3575],{"class":194,"line":2179},[192,3576,3577],{"class":198},"-- A meeting stored as UTC is never ambiguous:\n",[192,3579,3580,3582],{"class":194,"line":2396},[192,3581,286],{"class":211},[192,3583,2418],{"class":222},[192,3585,3586,3588,3590,3593,3595,3598,3600,3602,3605,3607,3609,3612],{"class":194,"line":2402},[192,3587,292],{"class":211},[192,3589,295],{"class":222},[192,3591,3592],{"class":298},"'DST Test'",[192,3594,167],{"class":222},[192,3596,3597],{"class":298},"'2024-11-03 05:30:00+00'",[192,3599,1179],{"class":222},[192,3601,1253],{"class":211},[192,3603,3604],{"class":222},", INTERVAL ",[192,3606,170],{"class":298},[192,3608,167],{"class":222},[192,3610,3611],{"class":769},"1",[192,3613,267],{"class":222},[192,3615,3616],{"class":194,"line":2407},[192,3617,3618],{"class":198},"-- 05:30 UTC = 01:30 EDT (first occurrence) — unambiguous\n",[192,3620,3621],{"class":194,"line":2413},[192,3622,274],{"emptyLinePlaceholder":273},[192,3624,3625],{"class":194,"line":2421},[192,3626,2210],{"class":198},[192,3628,3629],{"class":194,"line":2428},[192,3630,3631],{"class":198},"-- \"Missing hour\" in a schedule: spring-forward causes a gap\n",[192,3633,3634],{"class":194,"line":2436},[192,3635,2210],{"class":198},[192,3637,3638],{"class":194,"line":2451},[192,3639,3640],{"class":198},"-- A daily schedule from 2:00 to 3:00 on March 10 in NY:\n",[192,3642,3643],{"class":194,"line":2460},[192,3644,3645],{"class":198},"-- 2:00 AM doesn't exist (clocks jump to 3:00 AM)\n",[192,3647,3648],{"class":194,"line":2466},[192,3649,3650],{"class":198},"-- A naive schedule generator would create a phantom slot\n",[192,3652,3653,3655,3658,3660,3662,3664,3666,3668],{"class":194,"line":2471},[192,3654,502],{"class":211},[192,3656,3657],{"class":222}," gs ",[192,3659,635],{"class":211},[192,3661,638],{"class":211},[192,3663,641],{"class":211},[192,3665,453],{"class":298},[192,3667,2546],{"class":211},[192,3669,3670],{"class":222}," slot\n",[192,3672,3673,3675,3677],{"class":194,"line":2477},[192,3674,508],{"class":211},[192,3676,2016],{"class":769},[192,3678,2019],{"class":222},[192,3680,3681,3684,3686,3688],{"class":194,"line":2482},[192,3682,3683],{"class":298},"  '2024-03-10 02:00:00 America\u002FNew_York'",[192,3685,1179],{"class":222},[192,3687,1253],{"class":211},[192,3689,238],{"class":222},[192,3691,3692,3695,3697,3699],{"class":194,"line":2487},[192,3693,3694],{"class":298},"  '2024-03-10 03:00:00 America\u002FNew_York'",[192,3696,1179],{"class":222},[192,3698,1253],{"class":211},[192,3700,238],{"class":222},[192,3702,3703,3705],{"class":194,"line":2493},[192,3704,2053],{"class":222},[192,3706,3707],{"class":298},"'30 minutes'\n",[192,3709,3710,3712,3714],{"class":194,"line":2498},[192,3711,2064],{"class":222},[192,3713,1007],{"class":211},[192,3715,3056],{"class":222},[192,3717,3718],{"class":194,"line":2504},[192,3719,3720],{"class":198},"-- The 02:00 slot is shifted to 03:00 (or 03:30) — the gap is silently skipped\n",[14,3722,3723,3726],{},[117,3724,3725],{},"Store in UTC, display in the user's zone"," — this avoids most DST ambiguity. When you must accept local-time input, validate it against the zone's DST transitions.",[18,3728],{},[21,3730,3732],{"id":3731},"tips-tricks","💡 Tips & Tricks",[1039,3734,3735,3747,3762,3782,3812,3830,3844],{},[1042,3736,3737,3740,3741,3743,3744,3746],{},[117,3738,3739],{},"Idiom"," — default to ",[56,3742,134],{}," for all event timestamps: it stores a specific moment (UTC internally) and displays correctly in any session zone. Reserve ",[56,3745,112],{}," for wall-clock readings with no meaningful zone.",[1042,3748,3749,3751,3752,3754,3755,3758,3759,3761],{},[117,3750,3739],{}," — use ",[56,3753,566],{}," for display conversion: ",[56,3756,3757],{},"ts AT TIME ZONE 'America\u002FNew_York'"," converts a ",[56,3760,134],{}," to the NY wall time. Apply once, at the presentation layer.",[1042,3763,3764,3751,3766,3768,3769,167,3771,3773,3774,3776,3777,2186,3779,3781],{},[117,3765,3739],{},[56,3767,1660],{}," (or ",[56,3770,2087],{},[56,3772,1625],{},") for time-bucket aggregation: it rounds down to the start of the bucket, giving clean ",[56,3775,2110],{}," keys. Pair with ",[56,3778,1918],{},[56,3780,2074],{}," to fill zero-activity periods.",[1042,3783,3784,3751,3786,3789,3790,167,3792,167,3794,167,3797,3800,3801,3803,3804,3807,3808,3811],{},[117,3785,3739],{},[56,3787,3788],{},"EXTRACT"," for components (",[56,3791,1438],{},[56,3793,1459],{},[56,3795,3796],{},"DOW",[56,3798,3799],{},"EPOCH",") and ",[56,3802,158],{}," for duration math. For day counts, use date subtraction (",[56,3805,3806],{},"date - date"," → integer), not ",[56,3809,3810],{},"age()"," (which returns an interval).",[1042,3813,3814,3817,3818,3821,3822,3825,3826,3829],{},[117,3815,3816],{},"Performance"," — filter on the ",[117,3819,3820],{},"raw timestamp column"," with a range, not on ",[56,3823,3824],{},"DATE(col)"," or ",[56,3827,3828],{},"EXTRACT(YEAR FROM col)"," — wrapping the column in a function defeats the index.",[1042,3831,3832,3835,3836,3839,3840,3843],{},[117,3833,3834],{},"Session"," — ",[56,3837,3838],{},"SET TimeZone = 'UTC'"," for server-side processing (unambiguous), convert to user zones at the edge. ",[56,3841,3842],{},"SET TimeZone"," affects display, not storage.",[1042,3845,3846,3835,3849,3851,3852,3855,3856,3859],{},[117,3847,3848],{},"Portability",[56,3850,3207],{}," is PostgreSQL; MySQL uses ",[56,3853,3854],{},"DATE_ADD(ts, INTERVAL 1 HOUR)","; SQLite uses ",[56,3857,3858],{},"datetime(ts, '+1 hour')",". Timestamp arithmetic is one of the least portable areas.",[18,3861],{},[21,3863,3865],{"id":3864},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[1039,3867,3868,3880,3896,3902,3916,3931,3940,3963,3983,4004,4027,4042,4061],{},[1042,3869,3870,778,3875,3877,3878,1669],{},[117,3871,3872,3874],{},[56,3873,112],{}," (without zone) is ambiguous",[56,3876,124],{}," has no zone — you can't know what moment it represents. When data crosses time zones, this causes bugs. Use ",[56,3879,134],{},[1042,3881,3882,778,3887,848,3889,848,3891,3893,3894,1026],{},[117,3883,3884,3886],{},[56,3885,851],{}," is constant within a transaction",[56,3888,851],{},[56,3890,847],{},[56,3892,854],{}," all return the transaction start time. Use ",[56,3895,895],{},[1042,3897,3898,3901],{},[117,3899,3900],{},"DST spring-forward gap",": 2:30 AM doesn't exist on spring-forward day. PostgreSQL advances it to 3:30 (or errors). Validate local-time input against DST transitions.",[1042,3903,3904,3907,3908,3911,3912,3915],{},[117,3905,3906],{},"DST fall-back ambiguity",": 1:30 AM happens twice on fall-back day. Specify the offset (",[56,3909,3910],{},"-04"," EDT vs ",[56,3913,3914],{},"-05"," EST) to disambiguate.",[1042,3917,3918,778,3923,3926,3927,3930],{},[117,3919,3920,3922],{},[56,3921,1822],{}," on timestamps is inclusive on both ends",[56,3924,3925],{},"BETWEEN '2024-01-01' AND '2024-01-31 23:59:59'"," still misses ",[56,3928,3929],{},"23:59:59.5",". Use half-open ranges.",[1042,3932,3933,3939],{},[117,3934,3935,3938],{},[56,3936,3937],{},"EXTRACT(epoch FROM ts)"," returns POSIX seconds",": useful for Unix timestamps, but leap seconds aren't represented — every day has exactly 86400 epoch seconds.",[1042,3941,3942,3947,3948,3950,3951,3954,3955,3958,3959,3962],{},[117,3943,3944,3946],{},[56,3945,3219],{}," is variable-length",": adding ",[56,3949,3219],{}," to ",[56,3952,3953],{},"'2024-01-31'"," gives ",[56,3956,3957],{},"'2024-02-29'"," (leap year) or ",[56,3960,3961],{},"'2024-02-28'"," — not 30 days. Months vary 28–31 days.",[1042,3964,3965,778,3971,3974,3975,3978,3979,3982],{},[117,3966,3967,3970],{},[56,3968,3969],{},"age(ts)"," returns years\u002Fmonths\u002Fdays",[56,3972,3973],{},"age('2024-03-15', '2000-01-01')"," = ",[56,3976,3977],{},"24 years 2 mons 14 days",", not a number of days. Use ",[56,3980,3981],{},"EXTRACT(days FROM ...)"," or date subtraction for day counts.",[1042,3984,3985,778,3994,3996,3997,4000,4001,4003],{},[117,3986,3987,3990,3991],{},[56,3988,3989],{},"'24:00:00'"," vs ",[56,3992,3993],{},"'00:00:00'",[56,3995,3989],{}," is accepted by PostgreSQL and represents midnight of the ",[386,3998,3999],{},"next"," day (ISO 8601). ",[56,4002,3993],{}," is midnight of the current day. They're different references.",[1042,4005,4006,778,4009,4012,4013,4016,4017,4020,4021,4023,4024,4026],{},[117,4007,4008],{},"Time zone names vs abbreviations",[56,4010,4011],{},"America\u002FNew_York"," is a zone name (with full DST rules); ",[56,4014,4015],{},"EST","\u002F",[56,4018,4019],{},"EDT"," are abbreviations (fixed offsets, no DST rules). Prefer zone names — ",[56,4022,4015],{}," doesn't switch to ",[56,4025,4019],{}," in summer.",[1042,4028,4029,778,4034,4037,4038,3825,4040,1669],{},[117,4030,4031,4033],{},[56,4032,94],{}," is rarely useful",[56,4035,4036],{},"TIME WITH TIME ZONE"," is almost never what you want — a clock time with a zone doesn't represent a moment (no date). Use ",[56,4039,134],{},[56,4041,76],{},[1042,4043,4044,778,4050,4053,4054,4056,4057,4060],{},[117,4045,4046,4047,4049],{},"Session ",[56,4048,392],{}," affects display, not storage",[56,4051,4052],{},"SET TimeZone = 'Asia\u002FTokyo'"," changes how ",[56,4055,134],{}," values are ",[386,4058,4059],{},"displayed",", not what's stored. The stored value is always UTC.",[1042,4062,4063,778,4069,4072,4073,4075,4076,1669],{},[117,4064,4065,4068],{},[56,4066,4067],{},"date_trunc"," truncates in the session zone",[56,4070,4071],{},"date_trunc('day', ts)"," truncates to midnight in the session's ",[56,4074,392],{},", not the business zone. For business-day grouping, convert first: ",[56,4077,4078],{},"date_trunc('day', ts AT TIME ZONE 'America\u002FNew_York')",[18,4080],{},[21,4082,4084],{"id":4083},"spot-the-bug","🧠 Spot the Bug",[14,4086,4087,4088,4090,4091,4093,4094,1669],{},"A report groups orders by day, but orders placed late in the evening (after 7 PM New York time) appear on the ",[386,4089,3999],{}," day's row. The server runs in UTC. The ",[56,4092,2096],{}," column is ",[56,4095,134],{},[180,4097,4098],{"language":182},[184,4099,4101],{"className":186,"code":4100,"language":182,"meta":188,"style":188},"-- Server's session TimeZone is UTC\nSET TimeZone = 'UTC';\n\nSELECT\n  date_trunc('day', ordered_on)::date AS day,\n  COUNT(*) AS n\nFROM orders\nGROUP BY day\nORDER BY day;\n",[56,4102,4103,4108,4120,4124,4128,4146,4162,4168,4174],{"__ignoreMap":188},[192,4104,4105],{"class":194,"line":195},[192,4106,4107],{"class":198},"-- Server's session TimeZone is UTC\n",[192,4109,4110,4112,4114,4116,4118],{"class":194,"line":202},[192,4111,444],{"class":211},[192,4113,447],{"class":222},[192,4115,450],{"class":211},[192,4117,531],{"class":298},[192,4119,456],{"class":222},[192,4121,4122],{"class":194,"line":208},[192,4123,274],{"emptyLinePlaceholder":273},[192,4125,4126],{"class":194,"line":226},[192,4127,1430],{"class":211},[192,4129,4130,4133,4135,4138,4140,4142,4144],{"class":194,"line":241},[192,4131,4132],{"class":222},"  date_trunc(",[192,4134,2087],{"class":298},[192,4136,4137],{"class":222},", ordered_on)::",[192,4139,1182],{"class":211},[192,4141,2546],{"class":211},[192,4143,1950],{"class":211},[192,4145,238],{"class":222},[192,4147,4148,4150,4152,4155,4157,4159],{"class":194,"line":264},[192,4149,1957],{"class":769},[192,4151,1960],{"class":222},[192,4153,4154],{"class":211},"*",[192,4156,2064],{"class":222},[192,4158,1007],{"class":211},[192,4160,4161],{"class":222}," n\n",[192,4163,4164,4166],{"class":194,"line":270},[192,4165,508],{"class":211},[192,4167,1760],{"class":222},[192,4169,4170,4172],{"class":194,"line":277},[192,4171,2110],{"class":211},[192,4173,2069],{"class":211},[192,4175,4176,4178,4180],{"class":194,"line":283},[192,4177,2117],{"class":211},[192,4179,1950],{"class":211},[192,4181,456],{"class":222},[4183,4184,4185,4189,4210,4287,4305],"details",{},[4186,4187,4188],"summary",{},"Answer",[14,4190,4191,4194,4195,4198,4199,3974,4202,4205,4206,4209],{},[56,4192,4193],{},"date_trunc('day', ordered_on)"," truncates to the start of the day ",[117,4196,4197],{},"in the session's time zone"," — which is UTC, not New York. An order at 7 PM New York (",[56,4200,4201],{},"2024-03-15 19:00-05",[56,4203,4204],{},"2024-03-16 00:00 UTC",") gets truncated to ",[56,4207,4208],{},"2024-03-16"," in UTC — appearing on the next day, even though in New York it was still March 15.",[180,4211,4212],{"language":182},[184,4213,4215],{"className":186,"code":4214,"language":182,"meta":188,"style":188},"-- ✅ Fix: convert to the business zone BEFORE truncating\nSELECT\n  date_trunc('day', ordered_on AT TIME ZONE 'America\u002FNew_York')::date AS day,\n  COUNT(*) AS n\nFROM orders\nGROUP BY day\nORDER BY day;\n",[56,4216,4217,4222,4226,4253,4267,4273,4279],{"__ignoreMap":188},[192,4218,4219],{"class":194,"line":195},[192,4220,4221],{"class":198},"-- ✅ Fix: convert to the business zone BEFORE truncating\n",[192,4223,4224],{"class":194,"line":202},[192,4225,1430],{"class":211},[192,4227,4228,4230,4232,4235,4237,4239,4241,4243,4245,4247,4249,4251],{"class":194,"line":208},[192,4229,4132],{"class":222},[192,4231,2087],{"class":298},[192,4233,4234],{"class":222},", ordered_on ",[192,4236,635],{"class":211},[192,4238,638],{"class":211},[192,4240,641],{"class":211},[192,4242,453],{"class":298},[192,4244,3167],{"class":222},[192,4246,1182],{"class":211},[192,4248,2546],{"class":211},[192,4250,1950],{"class":211},[192,4252,238],{"class":222},[192,4254,4255,4257,4259,4261,4263,4265],{"class":194,"line":226},[192,4256,1957],{"class":769},[192,4258,1960],{"class":222},[192,4260,4154],{"class":211},[192,4262,2064],{"class":222},[192,4264,1007],{"class":211},[192,4266,4161],{"class":222},[192,4268,4269,4271],{"class":194,"line":241},[192,4270,508],{"class":211},[192,4272,1760],{"class":222},[192,4274,4275,4277],{"class":194,"line":264},[192,4276,2110],{"class":211},[192,4278,2069],{"class":211},[192,4280,4281,4283,4285],{"class":194,"line":270},[192,4282,2117],{"class":211},[192,4284,1950],{"class":211},[192,4286,456],{"class":222},[14,4288,4289,4292,4293,4295,4296,4298,4299,4302,4303,393],{},[56,4290,4291],{},"ordered_on AT TIME ZONE 'America\u002FNew_York'"," converts the ",[56,4294,134],{}," to a ",[56,4297,112],{}," (NY wall time), then ",[56,4300,4301],{},"date_trunc('day', ...)"," truncates that wall time to NY midnight. The result is stable regardless of the session's ",[56,4304,392],{},[14,4306,4307,778,4310,4312,4313,4316],{},[117,4308,4309],{},"The lesson",[56,4311,4071],{}," truncates in the ",[117,4314,4315],{},"session zone",", which may not be the business zone. For reports that must group by \"business day in New York,\" convert to the business zone before truncating.",[18,4318],{},[21,4320,4321],{"id":4186},"Summary",[14,4323,4324,4325,4327,4328,4330,4331,4016,4333,4335,4336,4338],{},"You can now choose between ",[56,4326,134],{}," and ",[56,4329,112],{},", manipulate dates\u002Ftimes with ",[56,4332,3788],{},[56,4334,4067],{},"\u002Fintervals, build time-series reports with ",[56,4337,1918],{},", use half-open ranges and index-friendly filtering, and navigate DST gaps and ambiguities. Next: JSON and array columns.",[4340,4341,4342],"style",{},"html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}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 .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}",{"title":188,"searchDepth":202,"depth":202,"links":4344},[4345,4348,4349,4350,4351,4354,4355,4356,4357,4358,4359,4360,4361,4362,4363,4364],{"id":23,"depth":202,"text":24,"children":4346},[4347],{"id":177,"depth":208,"text":178},{"id":398,"depth":202,"text":399},{"id":560,"depth":202,"text":561},{"id":796,"depth":202,"text":797},{"id":1031,"depth":202,"text":1032,"children":4352},[4353],{"id":1262,"depth":208,"text":1263},{"id":1412,"depth":202,"text":1413},{"id":1674,"depth":202,"text":1675},{"id":1912,"depth":202,"text":1913},{"id":2194,"depth":202,"text":2195},{"id":3225,"depth":202,"text":3226},{"id":3344,"depth":202,"text":3345},{"id":3442,"depth":202,"text":3443},{"id":3731,"depth":202,"text":3732},{"id":3864,"depth":202,"text":3865},{"id":4083,"depth":202,"text":4084},{"id":4186,"depth":202,"text":4321},"md",{},"\u002Fsql\u002F17-date-and-time",{"title":5,"description":16},"sql\u002F17-date-and-time","FiPN72ISdFWET_7JF0f4nG2mq85qXCgFsxz9xKXZWCs",1789924654735]