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.

January 2024

  • 04 (Thu)
  • 10 (Wed) (new moon)
  • 18 (Thu)
  • 25 (Thu) (full moon)

February 2024

  • 02 (Fri)
  • 09 (Fri) (new moon)
  • 17 (Sat)
  • 24 (Sat) (full moon) - Magha Puja Day

March 2024

  • 03 (Sun)
  • 09 (Sat) (new moon)
  • 17 (Sun)
  • 24 (Sun) (full moon)

April 2024

  • 01 (Mon)
  • 08 (Mon) (new moon)
  • 16 (Tue)
  • 23 (Tue) (full moon)

May 2024

  • 01 (Wed)
  • 07 (Tue) (new moon)
  • 15 (Wed)
  • 22 (Wed) (full moon) - Wesak Day / Visakha Puja
  • 30 (Thu) - Atthami Puja Day

June 2024

  • 06 (Thu) (new moon)
  • 14 (Fri)
  • 21 (Fri) (full moon)
  • 29 (Sat)

July 2024

  • 05 (Fri) (new moon)
  • 13 (Sat)
  • 20 (Sat) (full moon) - Asalha Puja
  • 28 (Sun)

August 2024

  • 04 (Sun) (new moon)
  • 12 (Mon)
  • 19 (Mon) (full moon)
  • 27 (Tue)

September 2024

  • 02 (Mon) (new moon)
  • 10 (Tue)
  • 17 (Tue) (full moon)
  • 25 (Wed)

October 2024

  • 02 (Wed) (new moon)
  • 10 (Thu)
  • 17 (Thu) (full moon)
  • 25 (Fri)
  • 31 (Thu) (new moon)

November 2024

  • 08 (Fri)
  • 15 (Fri) (full moon)
  • 23 (Sat)
  • 30 (Sat) (new moon)

December 2024

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

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.
local buddhistDays = [[
20240104 20240110 20240118 20240125
20240202 20240209 20240217 20240224
20240303 20240309 20240317 20240324
20240401 20240408 20240416 20240423
20240501 20240507 20240515 20240522 20240530
20240606 20240614 20240621 20240629
20240705 20240713 20240720 20240728
20240804 20240812 20240819 20240827
20240902 20240910 20240917 20240925
20241002 20241010 20241017 20241025 20241031
20241108 20241115 20241123 20241130
20241208 20241215 20241223 20241229
]]
 
-- 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