branch: elpa/casual
commit a889720f2cd1336e69adf09d6e359ff2aabd65d4
Merge: 4753a54e00 7ae03fe439
Author: Charles Choi <[email protected]>
Commit: GitHub <[email protected]>

    Merge pull request #81 from kickingvegas/64-build-casual-calendar
    
    Initial Entry - Casual Calendar
---
 README.org                                     |   8 +
 docs/agenda.org                                |   1 +
 docs/bookmarks.org                             |   1 +
 docs/calc.org                                  |   3 +-
 docs/calendar.org                              |  94 +++++
 docs/dired.org                                 |   1 +
 docs/editkit.org                               |   1 +
 docs/ibuffer.org                               |   1 +
 docs/images/casual-calendar-calendars-menu.png | Bin 0 -> 302886 bytes
 docs/images/casual-calendar-diary-menu.png     | Bin 0 -> 299188 bytes
 docs/images/casual-calendar-lunar-menu.png     | Bin 0 -> 408235 bytes
 docs/images/casual-calendar-screenshot.png     | Bin 0 -> 445243 bytes
 docs/images/casual-calendar-settings-menu.png  | Bin 0 -> 370442 bytes
 docs/info.org                                  |   1 +
 docs/isearch.org                               |   1 +
 docs/re-builder.org                            |   3 +-
 lisp/Makefile                                  |   5 +
 lisp/Makefile-calendar.make                    |  31 ++
 lisp/casual-calendar-constants.el              | 152 ++++++++
 lisp/casual-calendar-settings.el               | 170 +++++++++
 lisp/casual-calendar-utils.el                  | 471 +++++++++++++++++++++++++
 lisp/casual-calendar.el                        | 100 ++++++
 lisp/casual.el                                 |   4 +
 tests/Makefile                                 |   5 +
 tests/casual-calendar-test-utils.el            |  41 +++
 tests/casual-lib-test-utils.el                 |   6 +
 tests/test-casual-calendar-constants.el        |  65 ++++
 tests/test-casual-calendar-settings.el         |  61 ++++
 tests/test-casual-calendar-utils.el            | 316 +++++++++++++++++
 tests/test-casual-calendar.el                  | 130 +++++++
 30 files changed, 1670 insertions(+), 2 deletions(-)

diff --git a/README.org b/README.org
index 48f43a7435..878750527f 100644
--- a/README.org
+++ b/README.org
@@ -20,6 +20,13 @@ Casual is organized into different user interface (UI) 
libraries tuned for diffe
 
   [[file:docs/calc.org][file:docs/images/casual-calc-tmenu.png]]
   
+** [[file:docs/calendar.org][Calendar]] (Elisp library: ~casual-calendar~)
+
+An interface for 
[[https://www.gnu.org/software/emacs/manual/html_node/emacs/Calendar_002fDiary.html][Calendar
 & Diary]]. 
+
+[[file:docs/images/casual-calendar-screenshot.png]]
+
+
 ** [[file:docs/dired.org][Dired]] (Elisp library: ~casual-dired~)
   An interface for the venerable file manager Dired.
 
@@ -86,6 +93,7 @@ Configuration of a particular Casual user interface is 
performed per mode. Go to
 - [[file:docs/agenda.org::*Install][Agenda]]
 - [[file:docs/bookmarks.org::*Install][Bookmarks]]
 - [[file:docs/calc.org::*Install][Calc]]
+- [[file:docs/calendar.org::*Install][Calendar]]
 - [[file:docs/dired.org::*Install][Dired]]
 - [[file:docs/editkit.org::*Install][EditKit]]
 - [[file:docs/ibuffer.org::*Install][IBuffer]]  
diff --git a/docs/agenda.org b/docs/agenda.org
index 7a6a6827ac..60b1abfee5 100644
--- a/docs/agenda.org
+++ b/docs/agenda.org
@@ -73,6 +73,7 @@ If you enjoy using Casual Agenda, consider making a modest 
financial contributio
 * See Also
 - [[file:bookmarks.org][Bookmarks]]
 - [[file:calc.org][Calc]]
+- [[file:calendar.org][Calendar]]
 - [[file:dired.org][Dired]]
 - [[file:editkit.org][EditKit (numerous editing commands)]]
 - [[file:ibuffer.org][IBuffer]]
diff --git a/docs/bookmarks.org b/docs/bookmarks.org
index 1b664f80fc..91e7f32319 100644
--- a/docs/bookmarks.org
+++ b/docs/bookmarks.org
@@ -70,6 +70,7 @@ If you enjoy using Casual Bookmarks, consider making a modest 
financial contribu
 * See Also
 - [[file:agenda.org][Agenda]]
 - [[file:calc.org][Calc]]
+- [[file:calendar.org][Calendar]]
 - [[file:dired.org][Dired]]
 - [[file:editkit.org][EditKit (numerous editing commands)]]
 - [[file:ibuffer.org][IBuffer]]
diff --git a/docs/calc.org b/docs/calc.org
index 43de6ecdd7..d1d5b5c999 100644
--- a/docs/calc.org
+++ b/docs/calc.org
@@ -6,7 +6,7 @@ An opinionated 
[[https://github.com/magit/transient][Transient]]-based user inte
 [[file:images/casual-calc-tmenu.png]]
 
 * Install
-Casual Calc is available on [[https://melpa.org/#/casual-calc][MELPA]].
+Casual Calc is available on [[https://melpa.org/#/casual][MELPA]].
 
 If installed via ~list-packages~ then add this line to your Emacs 
initialization file.
 #+begin_src elisp :lexical no
@@ -56,6 +56,7 @@ If you enjoy using Casual Calc, consider making a modest 
financial contribution
 * See Also
 - [[file:agenda.org][Agenda]]
 - [[file:bookmarks.org][Bookmarks]]
+- [[file:calendar.org][Calendar]]
 - [[file:dired.org][Dired]]
 - [[file:editkit.org][EditKit (numerous editing commands)]]
 - [[file:ibuffer.org][IBuffer]]
diff --git a/docs/calendar.org b/docs/calendar.org
new file mode 100644
index 0000000000..ed585d05da
--- /dev/null
+++ b/docs/calendar.org
@@ -0,0 +1,94 @@
+[[../README.org][❮ Back to Casual]]
+
+* Casual Calendar
+An opinionated [[https://github.com/magit/transient][Transient]]-based user 
interface for 
[[https://www.gnu.org/software/emacs/manual/html_node/emacs/Calendar_002fDiary.html][Calendar]].
 
+
+[[file:images/casual-calendar-screenshot.png]]
+
+* Install
+If installed via [[https://melpa.org/#/casual][MELPA]] then add these lines to 
your Emacs initialization file with your binding of preference. 
+#+begin_src elisp :lexical no
+  (require 'casual-calendar) ; optional if using autoloaded menu
+  (keymap-set calendar-mode-map "C-o" #'casual-calendar)
+#+end_src
+
+* Usage
+** Basic Usage
+
+Whenever the ~calendar~ window (showing three months) is raised, the Casual 
user interface for it can be invoked via the command ~casual-calendar~. This 
command is typically bound to a keybinding (e.g. ~C-o~). The menu displayed 
will offer the following activities:
+
+- Navigation commands organized by time (day, week, month, year).
+- Conversion commands to different calendar systems.
+- Holiday-related commands.
+- Diary-related commands.
+- Almanac commands.
+
+Also made available is access to a Settings menu (more below) and to the Info 
documentation for ~calendar~.
+
+** Diary & Goto Menu
+
+From the main menu, choosing “(D) Diary & Goto›” will present choices for 
diary event insertion and to “goto” a particular date via different 
specifications.
+
+[[file:images/casual-calendar-diary-menu.png]]  
+
+** Calendar System Date Conversion
+Calendar supports conversion of dates from Gregorian (default) to a number of 
different calendar systems. Choose "(c) Conversions›" from the main menu to 
select from a list of such calendars.
+
+[[file:images/casual-calendar-calendars-menu.png]]
+
+ Supported calendar systems:
+
+- Astronomical
+- Bahá’í
+- Ethiopic
+- French Revolutionary
+- Hebrew
+- Islamic
+- Julian
+- Lunar (Chinese)
+- Mayan
+- Persian
+
+Selecting a calendar system will raise a menu that offers date conversion 
commands between it and the Gregorian system. In the screenshot below, the 
Lunar (Chinese) calendar system menu is shown. 
+
+To convert a Gregorian date to Lunar (Chinese):
+
+1. Move cursor (point) in Calendar window to desired date. (The command “(g) 
Goto…” can be used to accomplish this.)
+2. Choose “(c) Date at Cursor”.
+
+To convert a Lunar (Chinese) date to Gregorian:
+
+1. Choose “(G) Goto…” and follow the prompts.
+
+
+[[file:images/casual-calendar-lunar-menu.png]]  
+
+Certain non-Gregorian calendar systems are supported by the diary (Bahá’í, 
Hebrew, Islamic, Lunar (Chinese)). For such systems, their menus will offer 
choices to insert diary events.
+
+Note that inserting non-Gregorian diary events require configuration of the 
variables ~diary-nongregorian-listing-hook~ and 
~diary-nongregorian-marking-hook~ as detailed in 
[[https://www.gnu.org/software/emacs/manual/html_node/emacs/Non_002dGregorian-Diary.html][(emacs)
 Non-Gregorian Diary]]. 
+
+Both of these variables can be configured from the Calendar Settings menu.
+
+** Settings Menu
+From the main menu, choosing “(,) Settings” will provide a menu of frequently 
configured calendar and diary-related variables.
+
+[[file:images/casual-calendar-settings-menu.png]]  
+
+** Unicode Symbol Support
+By enabling “Use Unicode Symbols” from the Settings menu, Casual Calendar will 
use Unicode symbols as appropriate in its menus. 
+
+* Sponsorship
+If you enjoy using Casual Calendar, consider making a modest financial 
contribution to help support its development and maintenance.
+
+[[https://www.buymeacoffee.com/kickingvegas][file:images/default-yellow.png]]
+
+* See Also
+- [[file:agenda.org][Agenda]]
+- [[file:bookmarks.org][Bookmarks]]
+- [[file:calc.org][Calc]]
+- [[file:dired.org][Dired]]
+- [[file:editkit.org][EditKit (numerous editing commands)]]
+- [[file:ibuffer.org][IBuffer]]
+- [[file:info.org][Info]]
+- [[file:isearch.org][I-Search]]
+- [[file:re-builder.org][RE-Builder]]
diff --git a/docs/dired.org b/docs/dired.org
index 1951f6308c..2f09088cdc 100644
--- a/docs/dired.org
+++ b/docs/dired.org
@@ -152,6 +152,7 @@ If you enjoy using Casual Dired, consider making a modest 
financial contribution
 - [[file:agenda.org][Agenda]]
 - [[file:bookmarks.org][Bookmarks]]
 - [[file:calc.org][Calc]]
+- [[file:calendar.org][Calendar]]
 - [[file:editkit.org][EditKit (numerous editing commands)]]
 - [[file:ibuffer.org][IBuffer]]
 - [[file:info.org][Info]]
diff --git a/docs/editkit.org b/docs/editkit.org
index 52bbf01eec..2e205aff3c 100644
--- a/docs/editkit.org
+++ b/docs/editkit.org
@@ -168,6 +168,7 @@ If you enjoy using Casual EditKit, consider making a modest 
financial contributi
 - [[file:agenda.org][Org Agenda]]
 - [[file:bookmarks.org][Bookmarks]]
 - [[file:calc.org][Calc]]
+- [[file:calendar.org][Calendar]]
 - [[file:dired.org][Dired]]
 - [[file:ibuffer.org][IBuffer]]
 - [[file:info.org][Info]]
diff --git a/docs/ibuffer.org b/docs/ibuffer.org
index f132ef76b5..25b30ee708 100644
--- a/docs/ibuffer.org
+++ b/docs/ibuffer.org
@@ -163,6 +163,7 @@ If you enjoy using Casual IBuffer, consider making a modest 
financial contributi
 - [[file:agenda.org][Agenda]]
 - [[file:bookmarks.org][Bookmarks]]
 - [[file:calc.org][Calc]]
+- [[file:calendar.org][Calendar]]
 - [[file:editkit.org][EditKit (numerous editing commands)]]
 - [[file:info.org][Info]]
 - [[file:isearch.org][I-Search]]
diff --git a/docs/images/casual-calendar-calendars-menu.png 
b/docs/images/casual-calendar-calendars-menu.png
new file mode 100644
index 0000000000..f87bcdd580
Binary files /dev/null and b/docs/images/casual-calendar-calendars-menu.png 
differ
diff --git a/docs/images/casual-calendar-diary-menu.png 
b/docs/images/casual-calendar-diary-menu.png
new file mode 100644
index 0000000000..55d8f4e1c3
Binary files /dev/null and b/docs/images/casual-calendar-diary-menu.png differ
diff --git a/docs/images/casual-calendar-lunar-menu.png 
b/docs/images/casual-calendar-lunar-menu.png
new file mode 100644
index 0000000000..132e1459a5
Binary files /dev/null and b/docs/images/casual-calendar-lunar-menu.png differ
diff --git a/docs/images/casual-calendar-screenshot.png 
b/docs/images/casual-calendar-screenshot.png
new file mode 100644
index 0000000000..2b4ae27d8c
Binary files /dev/null and b/docs/images/casual-calendar-screenshot.png differ
diff --git a/docs/images/casual-calendar-settings-menu.png 
b/docs/images/casual-calendar-settings-menu.png
new file mode 100644
index 0000000000..66c03a0c9a
Binary files /dev/null and b/docs/images/casual-calendar-settings-menu.png 
differ
diff --git a/docs/info.org b/docs/info.org
index caed3b3fe6..6c3032c7d1 100644
--- a/docs/info.org
+++ b/docs/info.org
@@ -55,6 +55,7 @@ If you enjoy using Casual Info, consider making a modest 
financial contribution
 - [[file:agenda.org][Agenda]]
 - [[file:bookmarks.org][Bookmarks]]
 - [[file:calc.org][Calc]]
+- [[file:calendar.org][Calendar]]
 - [[file:dired.org][Dired]]
 - [[file:editkit.org][EditKit (numerous editing commands)]]
 - [[file:ibuffer.org][IBuffer]]
diff --git a/docs/isearch.org b/docs/isearch.org
index 55e561d10e..2294ddfd8a 100644
--- a/docs/isearch.org
+++ b/docs/isearch.org
@@ -64,6 +64,7 @@ If you enjoy using Casual I-Search, consider making a modest 
financial contribut
 - [[file:agenda.org][Agenda]]
 - [[file:bookmarks.org][Bookmarks]]
 - [[file:calc.org][Calc]]
+- [[file:calendar.org][Calendar]]
 - [[file:dired.org][Dired]]
 - [[file:editkit.org][EditKit (numerous editing commands)]]
 - [[file:ibuffer.org][IBuffer]]
diff --git a/docs/re-builder.org b/docs/re-builder.org
index 351d79b3ff..d94762a001 100644
--- a/docs/re-builder.org
+++ b/docs/re-builder.org
@@ -6,7 +6,7 @@ An opinionated 
[[https://github.com/magit/transient][Transient]]-based user inte
 [[file:images/casual-re-builder-screenshot.png]]
 
 * Install
-If installed via [[https://melpa.org/#/casual-re-builder][MELPA]] then add 
these lines to your Emacs initialization file with your binding of preference. 
+If installed via [[https://melpa.org/#/casual][MELPA]] then add these lines to 
your Emacs initialization file with your binding of preference. 
 #+begin_src elisp :lexical no
   (require 'casual-re-builder) ; optional
   (keymap-set reb-mode-map "C-o" #'casual-re-builder-tmenu)
@@ -62,6 +62,7 @@ If you enjoy using Casual RE-Builder, consider making a 
modest financial contrib
 - [[file:agenda.org][Agenda]]
 - [[file:bookmarks.org][Bookmarks]]
 - [[file:calc.org][Calc]]
+- [[file:calendar.org][Calendar]]
 - [[file:dired.org][Dired]]
 - [[file:editkit.org][EditKit (numerous editing commands)]]
 - [[file:ibuffer.org][IBuffer]]
diff --git a/lisp/Makefile b/lisp/Makefile
index 5c115f9016..b387f3cda4 100644
--- a/lisp/Makefile
+++ b/lisp/Makefile
@@ -21,6 +21,7 @@ lib-tests                                     \
 agenda-tests                                   \
 bookmarks-tests                                        \
 calc-tests                                     \
+calendar-tests                                 \
 dired-tests                                    \
 editkit-tests                                  \
 ibuffer-tests                                  \
@@ -34,6 +35,7 @@ tests: lib-tests                              \
 agenda-tests                                   \
 bookmarks-tests                                        \
 calc-tests                                     \
+calendar-tests                                 \
 dired-tests                                    \
 editkit-tests                                  \
 ibuffer-tests                                  \
@@ -53,6 +55,9 @@ bookmarks-tests:
 calc-tests:
        $(MAKE) -C $(SRC_DIR) -f Makefile-calc.make tests
 
+calendar-tests:
+       $(MAKE) -C $(SRC_DIR) -f Makefile-calendar.make tests
+
 dired-tests:
        $(MAKE) -C $(SRC_DIR) -f Makefile-dired.make tests
 
diff --git a/lisp/Makefile-calendar.make b/lisp/Makefile-calendar.make
new file mode 100644
index 0000000000..5108a54435
--- /dev/null
+++ b/lisp/Makefile-calendar.make
@@ -0,0 +1,31 @@
+##
+# Copyright 2024 Charles Y. Choi
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+include Makefile--defines.make
+
+PACKAGE_NAME=casual-calendar
+ELISP_INCLUDES=casual-calendar-constants.el \
+casual-calendar-utils.el       \
+casual-calendar-settings.el
+ELISP_PACKAGES=
+ELISP_TEST_INCLUDES=casual-calendar-test-utils.el
+PACKAGE_PATHS=                                 \
+-L $(EMACS_ELPA_DIR)/compat-30.0.0.0           \
+-L $(EMACS_ELPA_DIR)/seq-2.24                  \
+-L $(EMACS_ELPA_DIR)/transient-current         \
+-L $(CASUAL_LIB_LISP_DIR)
+
+include Makefile--rules.make
diff --git a/lisp/casual-calendar-constants.el 
b/lisp/casual-calendar-constants.el
new file mode 100644
index 0000000000..88742c3b90
--- /dev/null
+++ b/lisp/casual-calendar-constants.el
@@ -0,0 +1,152 @@
+;;; casual-calendar-constants.el --- Casual Calendar Constants -*- 
lexical-binding: t; -*-
+
+;; Copyright (C) 2024  Charles Choi
+
+;; Author: Charles Choi <[email protected]>
+;; Keywords: calendar
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+(require 'calendar)
+(require 'casual-lib)
+
+(defconst casual-calendar-unicode-db
+  '((:previous . '("↑" "Previous"))
+    (:next . '("↓" "Next"))
+
+    (:behind . '("←" "Behind"))
+    (:ahead . '("→" "Ahead"))
+
+    (:beginning . '("⇤" "Beginning"))
+    (:end . '("⇥" "End"))
+
+    (:back-3-months . '("← 3 months" "-3 months"))
+    (:forward-3-months . '("→ 3 months" "+3 months"))
+
+    (:redraw . '("⟳" "Refresh"))
+    ;;(:goto . '("🚀" "Goto"))
+    (:goto . '("🔎" "Goto"))
+    (:sunrise . '("🌅" "Sunrise"))
+    (:lunar . '("🌙" "Lunar")))
+
+  "Unicode symbol DB to use for Calendar Transient menus.")
+
+(defun casual-calendar-unicode-get (key)
+  "Lookup Unicode symbol for KEY in DB.
+
+- KEY symbol used to lookup Unicode symbol in DB.
+
+If the value of customizable variable `casual-lib-use-unicode'
+is non-nil, then the Unicode symbol is returned, otherwise a
+plain ASCII-range string."
+  (casual-lib-unicode-db-get key casual-calendar-unicode-db))
+
+
+(defconst casual-calendar--navigation-group
+  ["Navigation"
+   ["Day"
+    ("b" "Behind" calendar-backward-day
+     :description (lambda () (casual-calendar-unicode-get :behind))
+     :transient t)
+    ("f" "Ahead" calendar-forward-day
+     :description (lambda () (casual-calendar-unicode-get :ahead))
+     :transient t)
+    ("." "Today" calendar-goto-today :transient t)
+    ("g" "Goto…" calendar-goto-date
+     ;; :description (lambda () (format "%s…" (casual-calendar-unicode-get 
:goto)))
+     :transient t)]
+   ["Week"
+    ("p" "Behind" calendar-backward-week
+     :description (lambda () (casual-calendar-unicode-get :behind))
+     :transient t)
+    ("n" "Ahead" calendar-forward-week
+     :description (lambda () (casual-calendar-unicode-get :ahead))
+     :transient t)
+    ("a" "Beginning" calendar-beginning-of-week
+     :description (lambda () (casual-calendar-unicode-get :beginning))
+     :transient t)
+    ("e" "End" calendar-end-of-week
+     :description (lambda () (casual-calendar-unicode-get :end))
+     :transient t)
+    ("w" "Goto…" calendar-iso-goto-week
+     ;; :description (lambda () (format "%s…" (casual-calendar-unicode-get 
:goto)))
+     :transient t)]
+
+   ["Month"
+    :pad-keys t
+    ("{" "Behind" calendar-backward-month
+     :description (lambda () (casual-calendar-unicode-get :behind))
+     :transient t)
+    ("}" "Ahead" calendar-forward-month
+     :description (lambda () (casual-calendar-unicode-get :ahead))
+     :transient t)
+    ("M-a" "Beginning" calendar-beginning-of-month
+     :description (lambda () (casual-calendar-unicode-get :beginning))
+     :transient t)
+    ("M-e" "End" calendar-end-of-month
+     :description (lambda () (casual-calendar-unicode-get :end))
+     :transient t)
+    ("o" "Goto…" calendar-other-month
+     ;; :description (lambda () (format "%s…" (casual-calendar-unicode-get 
:goto)))
+     :transient t)]
+
+   ["Year"
+    :pad-keys t
+    ("M-[" "Behind" calendar-backward-year
+     :description (lambda () (casual-calendar-unicode-get :behind))
+     :transient t)
+    ("M-]" "Ahead" calendar-forward-year
+     :description (lambda () (casual-calendar-unicode-get :ahead))
+     :transient t)
+    ("[" "Beginning" calendar-beginning-of-year
+     :description (lambda () (casual-calendar-unicode-get :beginning))
+     :transient t)
+    ("]" "End" calendar-end-of-year
+     :description (lambda () (casual-calendar-unicode-get :end))
+     :transient t)]
+
+   ["Scroll"
+    :pad-keys t
+    ("<" "Behind" calendar-scroll-right
+     :description (lambda () (casual-calendar-unicode-get :behind))
+     :transient t)
+    (">" "Ahead" calendar-scroll-left
+     :description (lambda () (casual-calendar-unicode-get :ahead))
+     :transient t)
+    ("-" "-3 months" calendar-scroll-right-three-months
+     :description (lambda () (casual-calendar-unicode-get :back-3-months))
+     :transient t)
+    ("+" "+3 months" calendar-scroll-left-three-months
+     :description (lambda () (casual-calendar-unicode-get :forward-3-months))
+     :transient t)
+    ("C-l" "Redraw" calendar-redraw
+     :description (lambda () (casual-calendar-unicode-get :redraw))
+     :transient t)]]
+  "Transient navigation group for calendar.")
+
+
+(defconst casual-calendar--menu-navigation-group
+  [:class transient-row
+   (casual-lib-quit-one)
+   ("RET" "Dismiss" transient-quit-all)
+   (casual-lib-quit-all)]
+  "Transient menu navigation group for calendar.")
+
+(provide 'casual-calendar-constants)
+;;; casual-calendar-constants.el ends here
diff --git a/lisp/casual-calendar-settings.el b/lisp/casual-calendar-settings.el
new file mode 100644
index 0000000000..17b91c0df0
--- /dev/null
+++ b/lisp/casual-calendar-settings.el
@@ -0,0 +1,170 @@
+;;; casual-calendar-settings.el --- Casual Calendar Settings -*- 
lexical-binding: t; -*-
+
+;; Copyright (C) 2024 Charles Choi
+
+;; Author: Charles Choi <[email protected]>
+;; Keywords: tools
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+
+;;; Code:
+(require 'solar)
+(require 'org-agenda)
+(require 'casual-calendar-constants)
+
+(transient-define-prefix casual-calendar-settings-tmenu ()
+  "Casual Calendar & Diary settings menu.
+
+Customize settings for Calendar and Diary modes."
+
+  ["Calendar"
+   ["Customize"
+    ("G" "Calendar Group" casual-calendar--customize-calendar-group)
+    ("H" "Mark Holidays" casual-calendar--customize-calendar-mark-holidays-flag
+     :description (lambda ()
+                    (casual-lib-checkbox-label
+                     calendar-mark-holidays-flag
+                     "Mark Holidays")))]
+
+   ["Hooks"
+    ("v" "Move Hook" casual-calendar--customize-calendar-move-hook)]
+
+   ["Location"
+    ("N" "Location Name" casual-calendar--customize-calendar-location-name)
+    ("A" "Latitude" casual-calendar--customize-calendar-latitude)
+    ("O" "Longitude" casual-calendar--customize-calendar-longitude)]]
+
+  ["Diary"
+   ["Customize"
+    ("D" "Diary Group" casual-calendar--customize-diary-group)]
+
+   ["Hooks"
+    ("l" "List" casual-calendar--customize-diary-list-entries-hook)
+    ("m" "Mark" casual-calendar--customize-diary-mark-entries-hook)]
+
+   ["Non-Gregorian Hooks"
+    ("L" "List" casual-calendar--customize-diary-nongregorian-listing-hook)
+    ("M" "Mark" casual-calendar--customize-diary-nongregorian-marking-hook)]
+
+   ["Org Agenda"
+    ("d" "Include Diary" casual-calendar--customize-org-agenda-include-diary
+     :description (lambda ()
+                    (casual-lib-checkbox-label
+                     org-agenda-include-diary
+                     "Include Diary")))]]
+
+  ["General"
+   (casual-lib-customize-unicode)
+   (casual-lib-customize-hide-navigation)]
+
+  [:class transient-row
+          (casual-lib-quit-one)
+          ("a" "About" casual-calendar-about :transient nil)
+
+          (casual-lib-quit-all)])
+
+(defun casual-calendar--customize-calendar-group ()
+  "Customize calendar group."
+  (interactive)
+  (customize-group "calendar"))
+
+(defun casual-calendar--customize-diary-group ()
+  "Customize diary group."
+  (interactive)
+  (customize-group "diary"))
+
+(defun casual-calendar--customize-org-agenda-include-diary ()
+  "Customize variable `org-agenda-include-diary'."
+  (interactive)
+  (customize-variable 'org-agenda-include-diary))
+
+(defun casual-calendar--customize-diary-list-entries-hook ()
+  "Customize variable `diary-list-entries-hook'."
+  (interactive)
+  (customize-variable 'diary-list-entries-hook))
+
+(defun casual-calendar--customize-diary-mark-entries-hook ()
+  "Customize variable `diary-mark-entries-hook'."
+  (interactive)
+  (customize-variable 'diary-mark-entries-hook))
+
+(defun casual-calendar--customize-diary-nongregorian-listing-hook ()
+  "Customize variable `diary-nongregorian-listing-hook'."
+  (interactive)
+  (customize-variable 'diary-nongregorian-listing-hook))
+
+(defun casual-calendar--customize-diary-nongregorian-marking-hook ()
+  "Customize variable `diary-nongregorian-marking-hook'."
+  (interactive)
+  (customize-variable 'diary-nongregorian-marking-hook))
+
+(defun casual-calendar--customize-calendar-mark-holidays-flag ()
+  "Customize variable `calendar-mark-holidays-flag'."
+  (interactive)
+  (customize-variable 'calendar-mark-holidays-flag))
+
+(defun casual-calendar--customize-calendar-location-name ()
+  "Customize variable `calendar-location-name'."
+  (interactive)
+  (customize-variable 'calendar-location-name))
+
+(defun casual-calendar--customize-calendar-latitude ()
+  "Customize variable `calendar-latitude'."
+  (interactive)
+  (customize-variable 'calendar-latitude))
+
+(defun casual-calendar--customize-calendar-longitude ()
+  "Customize variable `calendar-longitude'."
+  (interactive)
+  (customize-variable 'calendar-longitude))
+
+(defun casual-calendar--customize-calendar-move-hook ()
+  "Customize variable `calendar-move-hook'."
+  (interactive)
+  (customize-variable 'calendar-move-hook))
+
+
+(defun casual-calendar-about-calendar ()
+  "Casual Calendar is a Transient menu for Calendar.
+
+Learn more about using Casual Calendar at our discussion group on GitHub.
+Any questions or comments about it should be made there.
+URL `https://github.com/kickingvegas/casual/discussions'
+
+If you find a bug or have an enhancement request, please file an issue.
+Our best effort will be made to answer it.
+URL `https://github.com/kickingvegas/casual/issues'
+
+If you enjoy using Casual Calendar, consider making a modest financial
+contribution to help support its development and maintenance.
+URL `https://www.buymeacoffee.com/kickingvegas'
+
+Casual Calendar was conceived and crafted by Charles Choi in
+San Francisco, California.
+
+Thank you for using Casual Calendar.
+
+Always choose love."
+  (ignore))
+
+(defun casual-calendar-about ()
+  "About information for Casual Calendar."
+  (interactive)
+  (describe-function #'casual-calendar-about-calendar))
+
+(provide 'casual-calendar-settings)
+;;; casual-calendar-settings.el ends here
diff --git a/lisp/casual-calendar-utils.el b/lisp/casual-calendar-utils.el
new file mode 100644
index 0000000000..94a70d012f
--- /dev/null
+++ b/lisp/casual-calendar-utils.el
@@ -0,0 +1,471 @@
+;;; casual-calendar-utils.el --- Casual Calendar Utils -*- lexical-binding: t; 
-*-
+
+;; Copyright (C) 2024 Charles Choi
+
+;; Author: Charles Choi <[email protected]>
+;; Keywords: tools
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+
+;;; Code:
+(require 'casual-calendar-constants)
+
+(transient-define-prefix casual-calendar-diary-and-goto-tmenu ()
+  "Diary Entry Insertion & Goto Date menu.
+
+Diary entry insertion and general goto date commands are offered
+by this menu."
+
+  ["Diary and Goto"
+   ["Diary Insert"
+    ("e" "Entry" diary-insert-entry :transient t)
+    ("w" "Weekly" diary-insert-weekly-entry :transient t)
+    ("m" "Monthly" diary-insert-monthly-entry :transient t)
+    ("y" "Yearly" diary-insert-yearly-entry :transient t)
+    ("a" "Anniversary" diary-insert-anniversary-entry :transient t)]
+
+   ["Goto"
+    ("g" "Date…" calendar-goto-date :transient t)
+    ("i" "ISO Date…" calendar-iso-goto-date :transient t)
+    ("d" "Day of Year…" calendar-goto-day-of-year :transient t)]]
+
+  casual-calendar--menu-navigation-group)
+
+(transient-define-prefix casual-calendar-conversions-tmenu ()
+  "Casual Calendar Conversions Menu.
+
+Selection of different calendar systems.
+
+- Astronomical
+- Bahá’í
+- Ethiopic
+- French Revolutionary
+- Hebrew
+- Islamic
+- Julian
+- Lunar (Chinese)
+- Mayan
+- Persian
+
+Consult info node `(emacs) Other Calendars' for more detail on
+specific supported non-Gregorian calendar system behavior."
+
+  ["Calendars"
+   [("a" "Astronomical›" casual-calendar-astro-tmenu)
+    ("b" "Bahá’í›" casual-calendar-bahai-tmenu)
+    ("c" "Coptic›" casual-calendar-coptic-tmenu)]
+
+   [("e" "Ethiopic›" casual-calendar-ethiopic-tmenu)
+    ("f" "French Revolutionary›" casual-calendar-french-tmenu)
+    ("h" "Hebrew›" casual-calendar-hebrew-tmenu)]
+
+   [("i" "Islamic›" casual-calendar-islamic-tmenu)
+    ("j" "Julian›" casual-calendar-julian-tmenu)
+    ("l" "Lunar (Chinese)›" casual-calendar-lunar-tmenu)]
+
+   [("m" "Mayan›" casual-calendar-mayan-tmenu)
+    ("p" "Persian›" casual-calendar-persian-tmenu)]]
+
+  ["All"
+   ("A" "Convert to all" calendar-print-other-dates :transient t)]
+
+  [:class transient-row
+   (casual-lib-quit-one)
+   ("RET" "Dismiss" transient-quit-all)
+   ("I" "ⓘ Info" (lambda ()
+                   (interactive)
+                   (calendar-exit)
+                   (info "(emacs) Other Calendars")))
+   (casual-lib-quit-all)])
+
+(transient-define-prefix casual-calendar-lunar-tmenu ()
+  "Casual Calendar Lunar (Chinese) Calendar Menu.
+
+This menu provides date conversion commands between a Gregorian
+and a Lunar (Chinese) calendar.
+
+To convert a Gregorian date to Lunar (Chinese):
+
+  1. Move cursor (point) in Calendar window to desired date.
+     (The command “(g) Goto…” can be used to accomplish this.)
+
+  2. Choose “(c) Date at Cursor”.
+
+To convert a Lunar (Chinese) date to Gregorian:
+
+  1. Choose “(G) Goto…” and follow the prompts.
+
+Diary insertion of Lunar (Chinese) events are supported provided
+proper configuration of the variables
+`diary-nongregorian-listing-hook' and
+`diary-nongregorian-marking-hook' as detailed in info
+node `(emacs) Non-Gregorian Diary'.
+
+Both of these variables can be configured from the Casual Calendar
+menu `casual-calendar-settings-tmenu'."
+
+  ["Lunar (Chinese) Calendar"
+   ["Date"
+    ("c" "Date at cursor" calendar-chinese-print-date :transient t)
+    ("G" "Goto…" calendar-chinese-goto-date :transient t)]
+
+   ["Diary Insert"
+    ("i" "Point" diary-chinese-insert-entry)
+    ("m" "Monthly" diary-chinese-insert-monthly-entry)
+    ("y" "Year" diary-chinese-insert-yearly-entry)
+    ("A" "Anniversary" diary-chinese-insert-anniversary-entry)]
+
+   ["Diary View"
+    ("d" "View" diary-view-entries :transient t)
+    ("s" "Show all" diary-show-all-entries)]]
+
+  casual-calendar--navigation-group
+  casual-calendar--menu-navigation-group)
+
+(transient-define-prefix casual-calendar-astro-tmenu ()
+  "Casual Calendar Astronomical calendar menu.
+
+This menu provides date conversion commands between a Gregorian
+and a Astronomical calendar.
+
+To convert a Gregorian date to Astronomical:
+
+  1. Move cursor (point) in Calendar window to desired date.
+     (The command “(g) Goto…” can be used to accomplish this.)
+
+  2. Choose “(c) Date at Cursor”.
+
+To convert a Astronomical date to Gregorian:
+
+  1. Choose “(G) Goto…” and follow the prompts."
+
+  ["Astronomical Calendar"
+   ["Date"
+    ("c" "Day number at cursor" calendar-astro-print-day-number :transient t)
+    ("G" "Goto…" calendar-astro-goto-day-number :transient t)]
+
+   ["Diary View"
+    ("d" "View" diary-view-entries :transient t)
+    ("s" "Show all" diary-show-all-entries)]]
+
+  casual-calendar--navigation-group
+  casual-calendar--menu-navigation-group)
+
+(transient-define-prefix casual-calendar-islamic-tmenu ()
+  "Casual Calendar Islamic calendar menu.
+
+This menu provides date conversion commands between a Gregorian
+and an Islamic calendar.
+
+To convert a Gregorian date to Islamic:
+
+  1. Move cursor (point) in Calendar window to desired date.
+     (The command “(g) Goto…” can be used to accomplish this.)
+
+  2. Choose “(c) Date at Cursor”.
+
+To convert an Islamic date to Gregorian:
+
+  1. Choose “(G) Goto…” and follow the prompts.
+
+Diary insertion of Islamic events are supported provided
+proper configuration of the variables
+`diary-nongregorian-listing-hook' and
+`diary-nongregorian-marking-hook' as detailed in info
+node `(emacs) Non-Gregorian Diary'.
+
+Both of these variables can be configured from the Casual Calendar
+menu `casual-calendar-settings-tmenu'."
+  ["Islamic Calendar"
+   ["Date"
+    ("c" "Date at cursor" calendar-islamic-print-date :transient t)
+    ("G" "Goto…" calendar-islamic-goto-date :transient t)]
+
+   ["Diary Insert"
+    ("i" "Point" diary-islamic-insert-entry)
+    ("m" "Monthly" diary-islamic-insert-monthly-entry)
+    ("y" "Year" diary-islamic-insert-yearly-entry)]
+
+   ["Diary View"
+    ("d" "View" diary-view-entries :transient t)
+    ("s" "Show all" diary-show-all-entries)]]
+
+  casual-calendar--navigation-group
+  casual-calendar--menu-navigation-group)
+
+(transient-define-prefix casual-calendar-hebrew-tmenu ()
+  "Casual Calendar Hebrew calendar menu.
+
+This menu provides date conversion commands between a Gregorian
+and a Hebrew calendar.
+
+To convert a Gregorian date to Hebrew:
+
+  1. Move cursor (point) in Calendar window to desired date.
+     (The command “(g) Goto…” can be used to accomplish this.)
+
+  2. Choose “(c) Date at Cursor”.
+
+To convert a Hebrew date to Gregorian:
+
+  1. Choose “(G) Goto…” and follow the prompts.
+
+Diary insertion of Hebrew events are supported provided
+proper configuration of the variables
+`diary-nongregorian-listing-hook' and
+`diary-nongregorian-marking-hook' as detailed in info
+node `(emacs) Non-Gregorian Diary'.
+
+Both of these variables can be configured from the Casual Calendar
+menu `casual-calendar-settings-tmenu'."
+
+  ["Hebrew Calendar"
+   ["Date"
+    ("c" "Date at cursor" calendar-hebrew-print-date :transient t)
+    ("G" "Goto…" calendar-hebrew-goto-date :transient t)]
+
+   ["Diary Insert"
+    ("i" "Point" diary-hebrew-insert-entry)
+    ("m" "Monthly" diary-hebrew-insert-monthly-entry)
+    ("y" "Year" diary-hebrew-insert-yearly-entry)]
+
+   ["Diary View"
+    ("d" "View" diary-view-entries :transient t)
+    ("s" "Show all" diary-show-all-entries)]]
+
+  casual-calendar--navigation-group
+  casual-calendar--menu-navigation-group)
+
+(transient-define-prefix casual-calendar-bahai-tmenu ()
+  "Casual Calendar Bahá’í calendar menu.
+
+This menu provides date conversion commands between a Gregorian
+and a Bahá’í calendar.
+
+To convert a Gregorian date to Bahá’í:
+
+  1. Move cursor (point) in Calendar window to desired date.
+     (The command “(g) Goto…” can be used to accomplish this.)
+
+  2. Choose “(c) Date at Cursor”.
+
+To convert a Bahá’í date to Gregorian:
+
+  1. Choose “(G) Goto…” and follow the prompts.
+
+Diary insertion of Bahá’í events are supported provided
+proper configuration of the variables
+`diary-nongregorian-listing-hook' and
+`diary-nongregorian-marking-hook' as detailed in info
+node `(emacs) Non-Gregorian Diary'.
+
+Both of these variables can be configured from the Casual Calendar
+menu `casual-calendar-settings-tmenu'."
+
+  ["Bahá’í Calendar"
+   ["Date"
+    ("c" "Date at cursor" calendar-bahai-print-date :transient t)
+    ("G" "Goto…" calendar-bahai-goto-date :transient t)]
+
+   ["Diary Insert"
+    ("i" "Point" diary-bahai-insert-entry)
+    ("m" "Monthly" diary-bahai-insert-monthly-entry)
+    ("y" "Year" diary-bahai-insert-yearly-entry)]
+
+   ["Diary View"
+    ("d" "View" diary-view-entries :transient t)
+    ("s" "Show all" diary-show-all-entries)]]
+
+  casual-calendar--navigation-group
+  casual-calendar--menu-navigation-group)
+
+(transient-define-prefix casual-calendar-ethiopic-tmenu ()
+  "Casual Calendar Ethiopic calendar menu.
+
+This menu provides date conversion commands between a Gregorian
+and an Ethiopic calendar.
+
+To convert a Gregorian date to Ethiopic:
+
+  1. Move cursor (point) in Calendar window to desired date.
+     (The command “(g) Goto…” can be used to accomplish this.)
+
+  2. Choose “(c) Date at Cursor”.
+
+To convert an Ethiopic date to Gregorian:
+
+  1. Choose “(G) Goto…” and follow the prompts."
+
+  ["Ethiopic Calendar"
+   ["Date"
+    ("c" "Date at cursor" calendar-ethiopic-print-date :transient t)
+    ("G" "Goto…" calendar-ethiopic-goto-date :transient t)]
+
+   ["Diary View"
+    ("d" "View" diary-view-entries :transient t)
+    ("s" "Show all" diary-show-all-entries)]]
+
+  casual-calendar--navigation-group
+  casual-calendar--menu-navigation-group)
+
+(transient-define-prefix casual-calendar-french-tmenu ()
+  "Casual Calendar French Revolutionary calendar menu.
+
+This menu provides date conversion commands between a Gregorian
+and a French Revolutionary calendar.
+
+To convert a Gregorian date to French Revolutionary:
+
+  1. Move cursor (point) in Calendar window to desired date.
+     (The command “(g) Goto…” can be used to accomplish this.)
+
+  2. Choose “(c) Date at Cursor”.
+
+To convert a French Revolutionary date to Gregorian:
+
+  1. Choose “(G) Goto…” and follow the prompts."
+
+  ["French Revolutionary Calendar"
+   ["Date"
+    ("c" "Date at cursor" calendar-french-print-date :transient t)
+    ("G" "Goto…" calendar-french-goto-date :transient t)]
+
+   ["Diary View"
+    ("d" "View" diary-view-entries :transient t)
+    ("s" "Show all" diary-show-all-entries)]]
+
+  casual-calendar--navigation-group
+  casual-calendar--menu-navigation-group)
+
+(transient-define-prefix casual-calendar-julian-tmenu ()
+  "Casual Calendar Julian calendar menu.
+
+This menu provides date conversion commands between a Gregorian
+and a Julian calendar.
+
+To convert a Gregorian date to Julian:
+
+  1. Move cursor (point) in Calendar window to desired date.
+     (The command “(g) Goto…” can be used to accomplish this.)
+
+  2. Choose “(c) Date at Cursor”.
+
+To convert a Julian date to Gregorian:
+
+  1. Choose “(G) Goto…” and follow the prompts."
+
+  ["Julian Calendar"
+   ["Date"
+    ("c" "Date at cursor" calendar-julian-print-date :transient t)
+    ("G" "Goto…" calendar-julian-goto-date :transient t)]
+
+   ["Diary View"
+    ("d" "View" diary-view-entries :transient t)
+    ("s" "Show all" diary-show-all-entries)]]
+
+  casual-calendar--navigation-group
+  casual-calendar--menu-navigation-group)
+
+(transient-define-prefix casual-calendar-coptic-tmenu ()
+  "Casual Calendar Coptic calendar menu.
+
+This menu provides date conversion commands between a Gregorian
+and a Coptic calendar.
+
+To convert a Gregorian date to Coptic:
+
+  1. Move cursor (point) in Calendar window to desired date.
+     (The command “(g) Goto…” can be used to accomplish this.)
+
+  2. Choose “(c) Date at Cursor”.
+
+To convert a Coptic date to Gregorian:
+
+  1. Choose “(G) Goto…” and follow the prompts."
+
+  ["Coptic Calendar"
+   ["Date"
+    ("c" "Date at cursor" calendar-coptic-print-date :transient t)
+    ("G" "Goto…" calendar-coptic-goto-date :transient t)]
+
+   ["Diary View"
+    ("d" "View" diary-view-entries :transient t)
+    ("s" "Show all" diary-show-all-entries)]]
+
+  casual-calendar--navigation-group
+  casual-calendar--menu-navigation-group)
+
+(transient-define-prefix casual-calendar-persian-tmenu ()
+  "Casual Calendar Persian calendar menu.
+
+This menu provides date conversion commands between a Gregorian
+and a Persian calendar.
+
+To convert a Gregorian date to Persian:
+
+  1. Move cursor (point) in Calendar window to desired date.
+     (The command “(g) Goto…” can be used to accomplish this.)
+
+  2. Choose “(c) Date at Cursor”.
+
+To convert a Persian date to Gregorian:
+
+  1. Choose “(G) Goto…” and follow the prompts."
+
+  ["Persian Calendar"
+   ["Date"
+    ("c" "Date at cursor" calendar-persian-print-date :transient t)
+    ("G" "Goto…" calendar-persian-goto-date :transient t)]
+
+   ["Diary View"
+    ("d" "View" diary-view-entries :transient t)
+    ("s" "Show all" diary-show-all-entries)]]
+
+  casual-calendar--navigation-group
+  casual-calendar--menu-navigation-group)
+
+(transient-define-prefix casual-calendar-mayan-tmenu ()
+  "Casual Calendar Mayan calendar menu.
+
+This menu provides date conversion commands between a Gregorian
+and a Mayan calendar.
+
+To convert a Gregorian date to Mayan:
+
+  1. Move cursor (point) in Calendar window to desired date.
+     (The command “(g) Goto…” can be used to accomplish this.)
+
+  2. Choose “(c) Date at Cursor”.
+
+To convert a Mayan date to Gregorian:
+
+  1. Choose “(G) Goto…” and follow the prompts."
+
+  ["Mayan Calendar"
+   ["Date"
+    ("c" "Date at cursor" calendar-mayan-print-date :transient t)
+    ("G" "Goto long count…" calendar-mayan-goto-long-count-date :transient t)]
+
+   ["Diary View"
+    ("d" "View" diary-view-entries :transient t)
+    ("s" "Show all" diary-show-all-entries)]]
+
+  casual-calendar--navigation-group
+  casual-calendar--menu-navigation-group)
+
+(provide 'casual-calendar-utils)
+;;; casual-calendar-utils.el ends here
diff --git a/lisp/casual-calendar.el b/lisp/casual-calendar.el
new file mode 100644
index 0000000000..7a3c604e2a
--- /dev/null
+++ b/lisp/casual-calendar.el
@@ -0,0 +1,100 @@
+;;; casual-calendar.el --- Transient UI for Calendar -*- lexical-binding: t; 
-*-
+
+;; Copyright (C) 2024  Charles Choi
+
+;; Author: Charles Choi <[email protected]>
+;; Keywords: tools
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Casual Calendar is an opinionated Transient-based user interface for the
+;; Emacs regular expression editor.
+
+;; INSTALLATION
+;; (require 'casual-calendar) ; optional if using autoloaded menu
+;; (keymap-set calendar-mode-map "C-o" #'casual-calendar)
+
+;; If you are using Emacs ≤ 30.0, you will need to update the built-in package
+;; `transient'. By default, `package.el' will not upgrade a built-in package.
+;; Set the customizable variable `package-install-upgrade-built-in' to `t' to
+;; override this. For more details, please refer to the "Install" section on
+;; this project's repository web page.
+
+;;; Code:
+(require 'casual-calendar-utils)
+(require 'casual-calendar-settings)
+
+;;;###autoload (autoload 'casual-calendar "casual-calendar" nil t)
+(defun casual-calendar ()
+  "Call Casual Calendar main menu.
+
+Helper function for calling `casual-calendar-tmenu'. Calls
+`calendar-redraw' to fix window sizing."
+  (interactive)
+  (call-interactively #'casual-calendar-tmenu)
+  (calendar-redraw))
+
+;;;###autoload (autoload 'casual-calendar-tmenu "casual-calendar" nil t)
+(transient-define-prefix casual-calendar-tmenu ()
+  "Transient menu for Calendar commands.
+
+Main menu for `calendar' commands.
+
+* References
+- Info node `(emacs) Calendar/Diary'"
+  casual-calendar--navigation-group
+
+  [["Conversions"
+    ("c" "Conversions›" casual-calendar-conversions-tmenu)
+    ("A" "Convert to all" calendar-print-other-dates :transient t)
+    ("i" "ISO Date" calendar-iso-print-date :transient t)]
+
+   ["Holidays"
+    ("H" "Holidays in span" calendar-list-holidays :transient t)
+    ("h" "Holidays at point" calendar-cursor-holidays :transient t)
+    ("x" "Mark Holidays" calendar-mark-holidays :transient t)
+    ("u" "Unmark" calendar-unmark :transient t)]
+
+   ["Misc"
+    ("O" "Org Agenda" org-calendar-goto-agenda)
+    ("d" "Diary" diary-view-entries :transient t)
+    ("s" "All Diary" diary-show-all-entries :transient t)
+    ("D" "Diary & Goto›" casual-calendar-diary-and-goto-tmenu)]
+
+   ["Almanac"
+    :pad-keys t
+    ("M" "Lunar Phases" calendar-lunar-phases
+     :description (lambda () (casual-calendar-unicode-get :lunar))
+     :transient t)
+    ("S" "Sunrise/Sunset" calendar-sunrise-sunset
+     :description (lambda () (casual-calendar-unicode-get :sunrise))
+     :transient t)
+    ("M-m" "Sunrise/Sunset Month" calendar-sunrise-sunset-month
+     :description (lambda () (format "%s Month" (casual-calendar-unicode-get 
:sunrise)))
+     :transient t)]]
+
+  [:class transient-row
+   (casual-lib-quit-one)
+   ("RET" "Dismiss" transient-quit-all)
+   ("I" "ⓘ Info" (lambda ()
+                   (interactive)
+                   (calendar-exit)
+                   (calendar-goto-info-node)))
+   ("," "Settings›" casual-calendar-settings-tmenu)
+   ("q" "Quit" calendar-exit)])
+
+(provide 'casual-calendar)
+;;; casual-calendar.el ends here
diff --git a/lisp/casual.el b/lisp/casual.el
index 8d473db6ca..d531ffaa91 100644
--- a/lisp/casual.el
+++ b/lisp/casual.el
@@ -44,6 +44,10 @@
 ;;   An interface for Emacs Calc, an embarrasingly feature-rich calculator.
 ;;   URL `https://github.com/kickingvegas/casual/blob/main/docs/calc.org'
 
+;; - Calendar (Elisp library: `casual-calendar')
+;;   An interface for the built-in calendar and diary of Emacs.
+;;   URL `https://github.com/kickingvegas/casual/blob/main/docs/calendar.org'
+
 ;; - Dired (Elisp library: `casual-dired')
 ;;   An interface for the venerable file manager Dired.
 ;;   URL `https://github.com/kickingvegas/casual/blob/main/docs/dired.org'
diff --git a/tests/Makefile b/tests/Makefile
index b69dabdf57..37c2d62e02 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -21,6 +21,7 @@ lib-tests                                     \
 agenda-tests                                   \
 bookmarks-tests                                        \
 calc-tests                                     \
+calendar-tests                                 \
 dired-tests                                    \
 editkit-tests                                  \
 ibuffer-tests                                  \
@@ -34,6 +35,7 @@ tests: lib-tests                              \
 agenda-tests                                   \
 bookmarks-tests                                        \
 calc-tests                                     \
+calendar-tests                                 \
 dired-tests                                    \
 editkit-tests                                  \
 ibuffer-tests                                  \
@@ -53,6 +55,9 @@ bookmarks-tests:
 calc-tests:
        $(MAKE) -C $(SRC_DIR) $@
 
+calendar-tests:
+       $(MAKE) -C $(SRC_DIR) $@
+
 dired-tests:
        $(MAKE) -C $(SRC_DIR) $@
 
diff --git a/tests/casual-calendar-test-utils.el 
b/tests/casual-calendar-test-utils.el
new file mode 100644
index 0000000000..c6c7c72f5e
--- /dev/null
+++ b/tests/casual-calendar-test-utils.el
@@ -0,0 +1,41 @@
+;;; casual-calendar-test-utils.el --- Casual Test Utils       -*- 
lexical-binding: t; -*-
+
+;; Copyright (C) 2024  Charles Choi
+
+;; Author: Charles Choi <[email protected]>
+;; Keywords: tools
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+(require 'ert)
+(require 'casual-lib)
+(require 'kmacro)
+(require 'calendar)
+(require 'casual-lib-test-utils)
+
+(defun casualt-calendar-setup ()
+  "Casual menu test setup function."
+  )
+
+(defun casualt-calendar-breakdown (&optional clear)
+  "Casual menu test breakdown function, if CLEAR is non-nil then clear state."
+  )
+
+(provide 'casual-calendar-test-utils)
+;;; casual-calendar-test-utils.el ends here
diff --git a/tests/casual-lib-test-utils.el b/tests/casual-lib-test-utils.el
index 85bae52e0a..7e109be8fa 100644
--- a/tests/casual-lib-test-utils.el
+++ b/tests/casual-lib-test-utils.el
@@ -149,5 +149,11 @@ COMMAND is an interactive function."
   (let ((buf (concat (casualt-macro-callable-symbol command) keys)))
     (kmacro buf)))
 
+(defun casualt-unicode-db-assert (key control cmd)
+  "DB Assert."
+  (let ((test (funcall cmd key)))
+    (should (string= test control))))
+
+
 (provide 'casual-lib-test-utils)
 ;;; casual-lib-test-utils.el ends here
diff --git a/tests/test-casual-calendar-constants.el 
b/tests/test-casual-calendar-constants.el
new file mode 100644
index 0000000000..a2054b5b39
--- /dev/null
+++ b/tests/test-casual-calendar-constants.el
@@ -0,0 +1,65 @@
+(require 'ert);;; test-casual-calendar-constants.el --- Casual Calendar Tests 
-*- lexical-binding: t; -*-
+
+;; Copyright (C) 2024  Charles Choi
+
+;; Author: Charles Choi <[email protected]>
+;; Keywords: tools
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+
+
+(require 'casual-lib-test-utils)
+(require 'casual-calendar-test-utils)
+(require 'casual-calendar)
+
+(defun casualt-calendar-unicode-assert (key control)
+ (casualt-unicode-db-assert key control #'casual-calendar-unicode-get))
+
+(ert-deftest test-casual-calendar-unicode-get ()
+  (let ((casual-lib-use-unicode nil))
+    (casualt-calendar-unicode-assert :previous "Previous")
+    (casualt-calendar-unicode-assert :next "Next")
+    (casualt-calendar-unicode-assert :behind "Behind")
+    (casualt-calendar-unicode-assert :ahead "Ahead")
+    (casualt-calendar-unicode-assert :beginning "Beginning")
+    (casualt-calendar-unicode-assert :end "End")
+    (casualt-calendar-unicode-assert :back-3-months "-3 months")
+    (casualt-calendar-unicode-assert :forward-3-months "+3 months")
+    (casualt-calendar-unicode-assert :redraw "Refresh")
+    (casualt-calendar-unicode-assert :goto "Goto")
+    (casualt-calendar-unicode-assert :sunrise "Sunrise")
+    (casualt-calendar-unicode-assert :lunar "Lunar"))
+
+  (let ((casual-lib-use-unicode t))
+    (casualt-calendar-unicode-assert :previous "↑")
+    (casualt-calendar-unicode-assert :next "↓")
+    (casualt-calendar-unicode-assert :behind "←")
+    (casualt-calendar-unicode-assert :ahead "→")
+    (casualt-calendar-unicode-assert :beginning "⇤")
+    (casualt-calendar-unicode-assert :end "⇥")
+    (casualt-calendar-unicode-assert :back-3-months "← 3 months")
+    (casualt-calendar-unicode-assert :forward-3-months "→ 3 months")
+    (casualt-calendar-unicode-assert :redraw "⟳")
+    (casualt-calendar-unicode-assert :goto "🔎")
+    (casualt-calendar-unicode-assert :sunrise "🌅")
+    (casualt-calendar-unicode-assert :lunar "🌙")))
+
+(provide 'test-casual-calendar-constants)
+;;; test-casual-calendar-constants.el ends here
diff --git a/tests/test-casual-calendar-settings.el 
b/tests/test-casual-calendar-settings.el
new file mode 100644
index 0000000000..d4a468c6d4
--- /dev/null
+++ b/tests/test-casual-calendar-settings.el
@@ -0,0 +1,61 @@
+;;; test-casual-calendar-settings.el --- Casual Calendar Settings Tests  -*- 
lexical-binding: t; -*-
+
+;; Copyright (C) 2024  Charles Choi
+
+;; Author: Charles Choi <[email protected]>
+;; Keywords: tools
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+
+(require 'ert)
+(require 'casual-calendar-test-utils)
+(require 'casual-calendar-settings)
+
+(ert-deftest test-casual-calendar-settings-tmenu ()
+  (let ()
+    (casualt-calendar-setup)
+    (cl-letf ()
+      (let ((test-vectors
+             '((:binding "G" :command 
casual-calendar--customize-calendar-group)
+               (:binding "H" :command 
casual-calendar--customize-calendar-mark-holidays-flag)
+               (:binding "v" :command 
casual-calendar--customize-calendar-move-hook)
+               (:binding "N" :command 
casual-calendar--customize-calendar-location-name)
+               (:binding "A" :command 
casual-calendar--customize-calendar-latitude)
+               (:binding "O" :command 
casual-calendar--customize-calendar-longitude)
+               (:binding "D" :command casual-calendar--customize-diary-group)
+               (:binding "l" :command 
casual-calendar--customize-diary-list-entries-hook)
+               (:binding "m" :command 
casual-calendar--customize-diary-mark-entries-hook)
+               (:binding "L" :command 
casual-calendar--customize-diary-nongregorian-listing-hook)
+               (:binding "M" :command 
casual-calendar--customize-diary-nongregorian-marking-hook)
+               (:binding "d" :command 
casual-calendar--customize-org-agenda-include-diary)
+               (:binding "u" :command 
casual-lib-customize-casual-lib-use-unicode)
+               (:binding "n" :command 
casual-lib-customize-casual-lib-hide-navigation)
+               (:binding "a" :command casual-calendar-about))))
+
+        (casualt-suffix-testcase-runner test-vectors
+                                        #'casual-calendar-settings-tmenu
+                                        '(lambda () (random 5000)))))
+    (casualt-calendar-breakdown)))
+
+(ert-deftest test-casual-calendar-about ()
+  (should (stringp (casual-calendar-about))))
+
+(provide 'test-casual-calendar-settings)
+;;; test-casual-calendar-settings.el ends here
diff --git a/tests/test-casual-calendar-utils.el 
b/tests/test-casual-calendar-utils.el
new file mode 100644
index 0000000000..5e2ea11ef9
--- /dev/null
+++ b/tests/test-casual-calendar-utils.el
@@ -0,0 +1,316 @@
+;;; test-casual-calendar-utils.el --- Casual Calendar Utils Tests  -*- 
lexical-binding: t; -*-
+
+;; Copyright (C) 2024  Charles Choi
+
+;; Author: Charles Choi <[email protected]>
+;; Keywords: tools
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+(require 'ert)
+(require 'casual-calendar-test-utils)
+(require 'casual-calendar-utils)
+
+(ert-deftest test-casual-calendar-diary-and-goto-tmenu ()
+  (let ()
+    (casualt-calendar-setup)
+    (cl-letf ((casualt-mock #'diary-insert-entry)
+              (casualt-mock #'diary-insert-weekly-entry)
+              (casualt-mock #'diary-insert-monthly-entry)
+              (casualt-mock #'diary-insert-yearly-entry)
+              (casualt-mock #'diary-insert-anniversary-entry)
+              (casualt-mock #'calendar-goto-date)
+              (casualt-mock #'calendar-iso-goto-date)
+              (casualt-mock #'calendar-goto-day-of-year))
+
+      (let ((test-vectors
+             '((:binding "e" :command diary-insert-entry)
+               (:binding "w" :command diary-insert-weekly-entry)
+               (:binding "m" :command diary-insert-monthly-entry)
+               (:binding "y" :command diary-insert-yearly-entry)
+               (:binding "a" :command diary-insert-anniversary-entry)
+               (:binding "g" :command calendar-goto-date)
+               ;; (:binding "i" :command calendar-iso-goto-date) ; TODO: need 
to mock input
+               (:binding "d" :command calendar-goto-day-of-year))))
+
+        (casualt-suffix-testcase-runner test-vectors
+                                        #'casual-calendar-diary-and-goto-tmenu
+                                        '(lambda () (random 5000)))))
+    (casualt-calendar-breakdown t)))
+
+(ert-deftest test-casual-calendar-conversions-tmenu ()
+  (let ()
+    (casualt-calendar-setup)
+    (cl-letf ((casualt-mock #'calendar-print-other-dates))
+
+      (let ((test-vectors
+             '((:binding "a" :command casual-calendar-astro-tmenu)
+               (:binding "b" :command casual-calendar-bahai-tmenu)
+               (:binding "c" :command casual-calendar-coptic-tmenu)
+               (:binding "e" :command casual-calendar-ethiopic-tmenu)
+               (:binding "f" :command casual-calendar-french-tmenu)
+               (:binding "h" :command casual-calendar-hebrew-tmenu)
+               (:binding "i" :command casual-calendar-islamic-tmenu)
+               (:binding "j" :command casual-calendar-julian-tmenu)
+               (:binding "l" :command casual-calendar-lunar-tmenu)
+               (:binding "m" :command casual-calendar-mayan-tmenu)
+               (:binding "p" :command casual-calendar-persian-tmenu)
+               (:binding "A" :command calendar-print-other-dates))))
+
+        (casualt-suffix-testcase-runner test-vectors
+                                        #'casual-calendar-conversions-tmenu
+                                        '(lambda () (random 5000)))))
+    (casualt-calendar-breakdown)))
+
+(ert-deftest test-casual-calendar-lunar-tmenu ()
+  (let ()
+    (casualt-calendar-setup)
+    (cl-letf ((casualt-mock #'calendar-chinese-print-date)
+              (casualt-mock #'calendar-chinese-goto-date)
+              (casualt-mock #'diary-chinese-insert-entry)
+              (casualt-mock #'diary-chinese-insert-monthly-entry)
+              (casualt-mock #'diary-chinese-insert-yearly-entry)
+              (casualt-mock #'diary-chinese-insert-anniversary-entry)
+              (casualt-mock #'diary-view-entries)
+              (casualt-mock #'diary-show-all-entries))
+
+      (let ((test-vectors
+             '((:binding "c" :command calendar-chinese-print-date)
+               ;;(:binding "G" :command calendar-chinese-goto-date)
+               (:binding "i" :command diary-chinese-insert-entry)
+               (:binding "m" :command diary-chinese-insert-monthly-entry)
+               (:binding "y" :command diary-chinese-insert-yearly-entry)
+               (:binding "A" :command diary-chinese-insert-anniversary-entry)
+               (:binding "d" :command diary-view-entries)
+               (:binding "s" :command diary-show-all-entries))))
+
+        (casualt-suffix-testcase-runner test-vectors
+                                        #'casual-calendar-lunar-tmenu
+                                        '(lambda () (random 5000)))))
+    (casualt-calendar-breakdown)))
+
+(ert-deftest test-casual-calendar-astro-tmenu ()
+  (let ()
+    (casualt-calendar-setup)
+    (cl-letf ((casualt-mock #'calendar-astro-print-day-number)
+              (casualt-mock #'calendar-astro-goto-day-number)
+              (casualt-mock #'diary-view-entries)
+              (casualt-mock #'diary-show-all-entries))
+
+      (let ((test-vectors
+             '((:binding "c" :command calendar-astro-print-day-number)
+               (:binding "d" :command diary-view-entries)
+               (:binding "s" :command diary-show-all-entries))))
+
+        (casualt-suffix-testcase-runner test-vectors
+                                        #'casual-calendar-astro-tmenu
+                                        '(lambda () (random 5000)))))
+    (casualt-calendar-breakdown)))
+
+(ert-deftest test-casual-calendar-islamic-tmenu ()
+  (let ()
+    (casualt-calendar-setup)
+    (cl-letf ((casualt-mock #'calendar-islamic-print-day-number)
+              (casualt-mock #'calendar-islamic-goto-day-number)
+              (casualt-mock #'diary-islamic-insert-entry)
+              (casualt-mock #'diary-islamic-insert-monthly-entry)
+              (casualt-mock #'diary-islamic-insert-yearly-entry)
+              (casualt-mock #'diary-view-entries)
+              (casualt-mock #'diary-show-all-entries))
+
+      (let ((test-vectors
+             '((:binding "c" :command calendar-islamic-print-date)
+               ;;(:binding "G" :command calendar-islamic-goto-date)
+               (:binding "i" :command diary-islamic-insert-entry)
+               (:binding "m" :command diary-islamic-insert-monthly-entry)
+               (:binding "y" :command diary-islamic-insert-yearly-entry)
+               (:binding "d" :command diary-view-entries)
+               (:binding "s" :command diary-show-all-entries))))
+
+        (casualt-suffix-testcase-runner test-vectors
+                                        #'casual-calendar-islamic-tmenu
+                                        '(lambda () (random 5000)))))
+    (casualt-calendar-breakdown)))
+
+(ert-deftest test-casual-calendar-hebrew-tmenu ()
+  (let ()
+    (casualt-calendar-setup)
+    (cl-letf ((casualt-mock #'calendar-hebrew-print-day-number)
+              (casualt-mock #'calendar-hebrew-goto-day-number)
+              (casualt-mock #'diary-hebrew-insert-entry)
+              (casualt-mock #'diary-hebrew-insert-monthly-entry)
+              (casualt-mock #'diary-hebrew-insert-yearly-entry)
+              (casualt-mock #'diary-view-entries)
+              (casualt-mock #'diary-show-all-entries))
+
+      (let ((test-vectors
+             '((:binding "c" :command calendar-hebrew-print-date)
+               ;;(:binding "G" :command calendar-hebrew-goto-date)
+               (:binding "i" :command diary-hebrew-insert-entry)
+               (:binding "m" :command diary-hebrew-insert-monthly-entry)
+               (:binding "y" :command diary-hebrew-insert-yearly-entry)
+               (:binding "d" :command diary-view-entries)
+               (:binding "s" :command diary-show-all-entries))))
+
+        (casualt-suffix-testcase-runner test-vectors
+                                        #'casual-calendar-hebrew-tmenu
+                                        '(lambda () (random 5000)))))
+    (casualt-calendar-breakdown)))
+
+(ert-deftest test-casual-calendar-bahai-tmenu ()
+  (let ()
+    (casualt-calendar-setup)
+    (cl-letf ((casualt-mock #'calendar-bahai-print-day-number)
+              (casualt-mock #'calendar-bahai-goto-day-number)
+              (casualt-mock #'diary-bahai-insert-entry)
+              (casualt-mock #'diary-bahai-insert-monthly-entry)
+              (casualt-mock #'diary-bahai-insert-yearly-entry)
+              (casualt-mock #'diary-view-entries)
+              (casualt-mock #'diary-show-all-entries))
+
+      (let ((test-vectors
+             '((:binding "c" :command calendar-bahai-print-date)
+               ;;(:binding "G" :command calendar-bahai-goto-date)
+               (:binding "i" :command diary-bahai-insert-entry)
+               (:binding "m" :command diary-bahai-insert-monthly-entry)
+               (:binding "y" :command diary-bahai-insert-yearly-entry)
+               (:binding "d" :command diary-view-entries)
+               (:binding "s" :command diary-show-all-entries))))
+
+        (casualt-suffix-testcase-runner test-vectors
+                                        #'casual-calendar-bahai-tmenu
+                                        '(lambda () (random 5000)))))
+    (casualt-calendar-breakdown)))
+
+(ert-deftest test-casual-calendar-ethiopic-tmenu ()
+  (let ()
+    (casualt-calendar-setup)
+    (cl-letf ((casualt-mock #'calendar-ethiopic-print-day-number)
+              (casualt-mock #'calendar-ethiopic-goto-day-number)
+              (casualt-mock #'diary-view-entries)
+              (casualt-mock #'diary-show-all-entries))
+
+      (let ((test-vectors
+             '((:binding "c" :command calendar-ethiopic-print-date)
+               ;;(:binding "G" :command calendar-ethiopic-goto-date)
+               (:binding "d" :command diary-view-entries)
+               (:binding "s" :command diary-show-all-entries))))
+
+        (casualt-suffix-testcase-runner test-vectors
+                                        #'casual-calendar-ethiopic-tmenu
+                                        '(lambda () (random 5000)))))
+    (casualt-calendar-breakdown)))
+
+(ert-deftest test-casual-calendar-french-tmenu ()
+  (let ()
+    (casualt-calendar-setup)
+    (cl-letf ((casualt-mock #'calendar-french-print-day-number)
+              (casualt-mock #'calendar-french-goto-day-number)
+              (casualt-mock #'diary-view-entries)
+              (casualt-mock #'diary-show-all-entries))
+
+      (let ((test-vectors
+             '((:binding "c" :command calendar-french-print-date)
+               ;;(:binding "G" :command calendar-french-goto-date)
+               (:binding "d" :command diary-view-entries)
+               (:binding "s" :command diary-show-all-entries))))
+
+        (casualt-suffix-testcase-runner test-vectors
+                                        #'casual-calendar-french-tmenu
+                                        '(lambda () (random 5000)))))
+    (casualt-calendar-breakdown)))
+
+(ert-deftest test-casual-calendar-julian-tmenu ()
+  (let ()
+    (casualt-calendar-setup)
+    (cl-letf ((casualt-mock #'calendar-julian-print-day-number)
+              (casualt-mock #'calendar-julian-goto-day-number)
+              (casualt-mock #'diary-view-entries)
+              (casualt-mock #'diary-show-all-entries))
+
+      (let ((test-vectors
+             '((:binding "c" :command calendar-julian-print-date)
+               ;;(:binding "G" :command calendar-julian-goto-date)
+               (:binding "d" :command diary-view-entries)
+               (:binding "s" :command diary-show-all-entries))))
+
+        (casualt-suffix-testcase-runner test-vectors
+                                        #'casual-calendar-julian-tmenu
+                                        '(lambda () (random 5000)))))
+    (casualt-calendar-breakdown)))
+
+(ert-deftest test-casual-calendar-coptic-tmenu ()
+  (let ()
+    (casualt-calendar-setup)
+    (cl-letf ((casualt-mock #'calendar-coptic-print-day-number)
+              (casualt-mock #'calendar-coptic-goto-day-number)
+              (casualt-mock #'diary-view-entries)
+              (casualt-mock #'diary-show-all-entries))
+
+      (let ((test-vectors
+             '((:binding "c" :command calendar-coptic-print-date)
+               ;;(:binding "G" :command calendar-coptic-goto-date)
+               (:binding "d" :command diary-view-entries)
+               (:binding "s" :command diary-show-all-entries))))
+
+        (casualt-suffix-testcase-runner test-vectors
+                                        #'casual-calendar-coptic-tmenu
+                                        '(lambda () (random 5000)))))
+    (casualt-calendar-breakdown)))
+
+(ert-deftest test-casual-calendar-persian-tmenu ()
+  (let ()
+    (casualt-calendar-setup)
+    (cl-letf ((casualt-mock #'calendar-persian-print-day-number)
+              (casualt-mock #'calendar-persian-goto-day-number)
+              (casualt-mock #'diary-view-entries)
+              (casualt-mock #'diary-show-all-entries))
+
+      (let ((test-vectors
+             '((:binding "c" :command calendar-persian-print-date)
+               ;;(:binding "G" :command calendar-persian-goto-date)
+               (:binding "d" :command diary-view-entries)
+               (:binding "s" :command diary-show-all-entries))))
+
+        (casualt-suffix-testcase-runner test-vectors
+                                        #'casual-calendar-persian-tmenu
+                                        '(lambda () (random 5000)))))
+    (casualt-calendar-breakdown)))
+
+(ert-deftest test-casual-calendar-mayan-tmenu ()
+  (let ()
+    (casualt-calendar-setup)
+    (cl-letf ((casualt-mock #'calendar-mayan-print-day-number)
+              (casualt-mock #'calendar-mayan-goto-day-number)
+              (casualt-mock #'diary-view-entries)
+              (casualt-mock #'diary-show-all-entries))
+
+      (let ((test-vectors
+             '((:binding "c" :command calendar-mayan-print-date)
+               ;;(:binding "G" :command calendar-mayan-goto-date)
+               (:binding "d" :command diary-view-entries)
+               (:binding "s" :command diary-show-all-entries))))
+
+        (casualt-suffix-testcase-runner test-vectors
+                                        #'casual-calendar-mayan-tmenu
+                                        '(lambda () (random 5000)))))
+    (casualt-calendar-breakdown)))
+
+(provide 'test-casual-calendar-utils)
+;;; test-casual-calendar-utils.el ends here
diff --git a/tests/test-casual-calendar.el b/tests/test-casual-calendar.el
new file mode 100644
index 0000000000..be2593ab83
--- /dev/null
+++ b/tests/test-casual-calendar.el
@@ -0,0 +1,130 @@
+;;; test-casual-calendar.el --- Casual Calendar Tests -*- lexical-binding: t; 
-*-
+
+;; Copyright (C) 2024  Charles Choi
+
+;; Author: Charles Choi <[email protected]>
+;; Keywords: tools
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+
+(require 'ert)
+(require 'casual-lib-test-utils)
+(require 'casual-calendar-test-utils)
+(require 'casual-calendar)
+
+(ert-deftest test-casual-calendar-tmenu ()
+  (let ()
+    (casualt-calendar-setup)
+
+    (cl-letf (;; ((symbol-function #'buffer-file-name) (lambda () t))
+              (casualt-mock #'calendar-backward-day)
+              (casualt-mock #'calendar-forward-day)
+              (casualt-mock #'calendar-goto-today)
+              (casualt-mock #'calendar-goto-date)
+
+              (casualt-mock #'calendar-backward-week)
+              (casualt-mock #'calendar-forward-week)
+              (casualt-mock #'calendar-beginning-of-week)
+              (casualt-mock #'calendar-end-of-week)
+              (casualt-mock #'calendar-iso-goto-week)
+              (casualt-mock #'calendar-iso-print-date)
+
+              (casualt-mock #'calendar-backward-month)
+              (casualt-mock #'calendar-forward-month)
+              (casualt-mock #'calendar-beginning-of-month)
+              (casualt-mock #'calendar-end-of-month)
+              (casualt-mock #'calendar-other-month)
+
+              (casualt-mock #'calendar-backward-year)
+              (casualt-mock #'calendar-forward-year)
+              (casualt-mock #'calendar-beginning-of-year)
+              (casualt-mock #'calendar-end-of-year)
+
+              (casualt-mock #'calendar-scroll-right)
+              (casualt-mock #'calendar-scroll-left)
+              (casualt-mock #'calendar-scroll-right-three-months)
+              (casualt-mock #'calendar-scroll-left-three-months)
+              (casualt-mock #'calendar-redraw)
+
+              (casualt-mock #'calendar-print-other-dates)
+              (casualt-mock #'calendar-list-holidays)
+              (casualt-mock #'calendar-cursor-holidays)
+              (casualt-mock #'calendar-mark-holidays)
+              (casualt-mock #'calendar-unmark)
+              (casualt-mock #'org-calendar-goto-agenda)
+              (casualt-mock #'diary-view-entries)
+              (casualt-mock #'diary-show-all-entries)
+              (casualt-mock #'calendar-lunar-phases)
+              (casualt-mock #'calendar-sunrise-sunset)
+              (casualt-mock #'calendar-sunrise-sunset-month)
+
+              (casualt-mock #'calendar-exit))
+
+      (let ((test-vectors
+             '((:binding "b" :command calendar-backward-day)
+               (:binding "f" :command calendar-forward-day)
+               (:binding "." :command calendar-goto-today)
+               (:binding "g" :command calendar-goto-date)
+
+               (:binding "p" :command calendar-backward-week)
+               (:binding "n" :command calendar-forward-week)
+               (:binding "a" :command calendar-beginning-of-week)
+               (:binding "e" :command calendar-end-of-week)
+               ;;(:binding "w" :command calendar-iso-goto-week) ; TODO: needs 
input handling
+
+               (:binding "{" :command calendar-backward-month)
+               (:binding "}" :command calendar-forward-month)
+               (:binding "M-a" :command calendar-beginning-of-month)
+               (:binding "M-e" :command calendar-end-of-month)
+               (:binding "o" :command calendar-other-month)
+
+               (:binding "M-]" :command calendar-forward-year)
+               (:binding "M-[" :command calendar-backward-year)
+               (:binding "[" :command calendar-beginning-of-year)
+               (:binding "]" :command calendar-end-of-year)
+
+               (:binding "<" :command calendar-scroll-right)
+               (:binding ">" :command calendar-scroll-left)
+               (:binding "-" :command calendar-scroll-right-three-months)
+               (:binding "+" :command calendar-scroll-left-three-months)
+               (:binding "C-l" :command calendar-redraw)
+
+               (:binding "c" :command casual-calendar-conversions-tmenu)
+               (:binding "A" :command calendar-print-other-dates)
+               (:binding "i" :command calendar-iso-print-date)
+               (:binding "H" :command calendar-list-holidays)
+               (:binding "h" :command calendar-cursor-holidays)
+               (:binding "x" :command calendar-mark-holidays)
+               (:binding "u" :command calendar-unmark)
+               (:binding "O" :command org-calendar-goto-agenda)
+               (:binding "d" :command diary-view-entries)
+               (:binding "D" :command casual-calendar-diary-and-goto-tmenu)
+               (:binding "s" :command diary-show-all-entries)
+               (:binding "M" :command calendar-lunar-phases)
+               (:binding "S" :command calendar-sunrise-sunset)
+               (:binding "M-m" :command calendar-sunrise-sunset-month))))
+
+        (casualt-suffix-testcase-runner test-vectors
+                                        #'casual-calendar-tmenu
+                                        '(lambda () (random 5000)))))
+    (casualt-calendar-breakdown)))
+
+(provide 'test-casual-calendar)
+;;; test-casual-calendar.el ends here

Reply via email to