Buddhist Observance Days are days where lay followers (i.e. non-monks) of Buddhism observe a larger subset of religious practices. They occur roughly on the new, full and quarter moons of each month. I track these dates mainly for my wife and her family.

December 2024

  • 08 (Sun)
  • 15 (Sun) (full moon)
  • 23 (Mon)
  • 29 (Sun) (new moon)

January 2025

  • 6 (Mon)
  • 13 (Mon) (full moon)
  • 21 (Tue)
  • 28 (Tue) (new moon)

February 2025

  • 5 (Wed)
  • 12 (Wed) (full moon)
  • 20 (Thu)
  • 26 (Wed) (new moon)

March 2025

  • 6 (Thu)
  • 13 (Thu) (full moon)
  • 21 (Fri)
  • 28 (Fri) (new moon)

April 2025

  • 5 (Sat)
  • 12 (Sat) (full moon)
  • 20 (Sun)
  • 26 (Sat) (new moon)

May 2025

  • 4 (Sun)
  • 11 (Sun) (full Moon)
  • 19 (Mon)
  • 26 (Mon) (new moon)

June 2025

  • 3 (Tue)
  • 10 (Tue) (full moon)
  • 18 (Wed)
  • 25 (Wed) (new moon)

July 2025

  • 3 (Thu)
  • 10 (Thu) (full moon)
  • 18 (Fri)
  • 25 (Fri) (new moon)

August 2025

  • 2 (Sat)
  • 9 (Sat) (full moon)
  • 17 (Sun)
  • 23 (Sat) (new moon)
  • 31 (Sun)

September 2025

  • 7 (Sun) (full moon)
  • 15 (Mon)
  • 22 (Mon) (new moon)
  • 30 (Tue)

October 2025

  • 7 (Tue) (full moon)
  • 15 (Wed)
  • 21 (Tue) (new moon)
  • 29 (Wed)

November 2025

  • 5 (Wed) (full moon)
  • 13 (Thu)
  • 20 (Thu) (new moon)
  • 28 (Fri)

December 2025

  • 5 (Fri) (full moon)
  • 13 (Sat)
  • 19 (Fri) (new moon)
  • 27 (Sat)

Sourced from https://thailand.yinteing.com/buddhist-observance-uposatha-days/

iCalendar Generation for Imports

I wanted to generate an iCalendar file of these dates for importing into Google calendar. The following content is technical documentation on how I achieve this.

In Neovim, use vio to select inside the code block, then :lua to execute. It will open a buffer with the ICS contents, which can then be saved as using :w /path/to/example.ics. The resulting ICS file can be imported into most other calendar systems.

-- Function to generate a UUID
local function generateUUID()
  local random = math.random
  local template ='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
  return string.gsub(template, '[xy]', function (c)
    local v = (c == 'x') and random(0, 0xf) or random(8, 0xb)
    return string.format('%x', v)
  end)
end
 
-- This list of date stamps needs to be manually populated.
-- Historically I have used creative vim motions and macros
-- to transform the human-readable list above into the
-- format below. I only need to do this once a year, so
-- I haven't put much thought into automating this part.
-- NOTE: the date format is *without* dashes
local buddhistDays = [[
20260103 20260111 20260118 20260126
20260202 20260210 20260216 20260224
20260303 20260311 20260318 20260326
20260402 20260410 20260416 20260424
20260501 20260509 20260516 20260524 20260531
20260608 20260614 20260622 20260629
20260707 20260714 20260722 20260729
20260806 20260813 20260821 20260828
20260905 20260911 20260919 20260926
20261004 20261011 20261019 20261026
20261103 20261109 20261117 20261124
20261202 20261209 20261217 20261224
]]
 
-- Split the string into individual date stamps
local dates = {}
for date in buddhistDays:gmatch("%d%d%d%d%d%d%d%d") do
  table.insert(dates, date)
end
 
-- Generate the ICS formatted events
local function generateICSEvents(dates)
  local icsString = "BEGIN:VCALENDAR\nPRODID:hungyiloo\nVERSION:2.0\n"
 
  for _, date in ipairs(dates) do
    icsString = icsString .. string.format([[
BEGIN:VEVENT
SUMMARY:πŸ™ Buddhist Day
UID:%s
DTSTART;VALUE=DATE:%s
DTSTAMP:%s
TRANSP:TRANSPARENT
END:VEVENT
]], generateUUID(), date, os.date("!%Y%m%dT%H%M%SZ"))
  end
 
  return icsString .. "END:VCALENDAR"
end
 
-- Generate the ICS string
local result = generateICSEvents(dates)
 
-- Show a buffer with the result
local lines = {}
for line in result:gmatch("([^\n]*)\n?") do
  table.insert(lines, line)
end
local buf = vim.api.nvim_create_buf(true, false)
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
vim.api.nvim_command('vsplit')
vim.api.nvim_win_set_buf(0, buf)

A Vim Macro To Extract Dates

I composed a mega macro from many smaller macros to process the source plain text data into a list of dates used above.

Grab the year and put it in register y
gg/20\d\d<cr>"yye

Delete all lines that don't start with a number (not dates)
:%s/^\D.*\n//g<cr>

Delete excess new lines
:%s/\n\n\n/\r\r/g<cr>

Delete unnecessary content off the end of the date listings
:%s/ \+(<del>.*//g<cr>

Add exactly one blank line at the start of the file
ggO<esc>d/\w<cr>O<esc>

Add a blank line at the end of the file
Go<Esc>

Start a month counter in the register m
ggi00<Esc>vh"md

Process the dates for each month,
incrementing the month counter each time.
"mp<C-A>vh"mdj<C-V>}kI<C-R>y-<C-R>m-<Esc>}
"mp<C-A>vh"mdj<C-V>}kI<C-R>y-<C-R>m-<Esc>}
"mp<C-A>vh"mdj<C-V>}kI<C-R>y-<C-R>m-<Esc>}
"mp<C-A>vh"mdj<C-V>}kI<C-R>y-<C-R>m-<Esc>}
"mp<C-A>vh"mdj<C-V>}kI<C-R>y-<C-R>m-<Esc>}
"mp<C-A>vh"mdj<C-V>}kI<C-R>y-<C-R>m-<Esc>}
"mp<C-A>vh"mdj<C-V>}kI<C-R>y-<C-R>m-<Esc>}
"mp<C-A>vh"mdj<C-V>}kI<C-R>y-<C-R>m-<Esc>}
"mp<C-A>vh"mdj<C-V>}kI<C-R>y-<C-R>m-<Esc>}
"mp<C-A>vh"mdj<C-V>}kI<C-R>y-<C-R>m-<Esc>}
"mp<C-A>vh"mdj<C-V>}kI<C-R>y-<C-R>m-<Esc>}
"mp<C-A>vh"mdj<C-V>}kI<C-R>y-<C-R>m-<Esc>}

Fix zero padding on single digit dates
:%s/-\(\d\)\n/-0\1\r/<cr>

Put everything on one line separated by spaces
:%s/\n/ /<cr>

Double spaces happen to be month separations,
so convert those to a new line for readability
:%s/  /\r/g<cr>

Trim any leading whitespace on all lines
:%s/^ //<cr>

Remove any blank lines
:%s/\n\n//<cr>

The final macro is below, combined into one string. It can be loaded using my macro keymaps in my Neovim config.

gg/20\d\d<CR>"yye:%s/^\D.*\n//g<CR>:%s/\n\n\n/\r\r/g<CR>:%s/<Space>\+(<Del>.*//g<CR>ggO<Esc>d/\w<CR>O<Esc>Go<Esc>ggi00<Esc>vh"md"mp<C-A>vh"mdj<C-V>}kI<C-R>y-<C-R>m-<Esc>}"mp<C-A>vh"mdj<C-V>}kI<C-R>y-<C-R>m-<Esc>}"mp<C-A>vh"mdj<C-V>}kI<C-R>y-<C-R>m-<Esc>}"mp<C-A>vh"mdj<C-V>}kI<C-R>y-<C-R>m-<Esc>}"mp<C-A>vh"mdj<C-V>}kI<C-R>y-<C-R>m-<Esc>}"mp<C-A>vh"mdj<C-V>}kI<C-R>y-<C-R>m-<Esc>}"mp<C-A>vh"mdj<C-V>}kI<C-R>y-<C-R>m-<Esc>}"mp<C-A>vh"mdj<C-V>}kI<C-R>y-<C-R>m-<Esc>}"mp<C-A>vh"mdj<C-V>}kI<C-R>y-<C-R>m-<Esc>}"mp<C-A>vh"mdj<C-V>}kI<C-R>y-<C-R>m-<Esc>}"mp<C-A>vh"mdj<C-V>}kI<C-R>y-<C-R>m-<Esc>}"mp<C-A>vh"mdj<C-V>}kI<C-R>y-<C-R>m-<Esc>}:%s/-\(\d\)\n/-0\1\r/<CR>:%s/\n/<Space>/<CR>:%s/<Space><Space>/\r/g<CR>:%s/^<Space>//<CR>:%s/\n\n//<CR><NL>

Example ICS Template

This example is for reference, and contains a single all-day event that isn’t marked as busy. The UID must be unique and the DTSTAMP is the date time of creation of the record (unrelated to the event date and time)

BEGIN:VCALENDAR
PRODID:hungyiloo
VERSION:02.0
BEGIN:VEVENT
SUMMARY:πŸ™ Buddhist Day
UID:7e865e89-0481-4a7d-ad79-ee0fb33abcb7
DTSTART;VALUE=DATE:20240104
DTSTAMP:20240602T213800Z
TRANSP:TRANSPARENT
END:VEVENT
END:VCALENDAR