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 = [[
20250106 20250113 20250121 20250128
20250205 20250212 20250220 20250226
20250306 20250313 20250321 20250328
20250405 20250412 20250420 20250426
20250504 20250511 20250519 20250526
20250603 20250610 20250618 20250625
20250703 20250710 20250718 20250725
20250802 20250809 20250817 20250823 20250831
20250907 20250915 20250922 20250930
20251007 20251015 20251021 20251029
20251105 20251113 20251120 20251128
20251205 20251213 20251219 20251227
]]
 
-- 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