Skip to main content

Nepali Calendar Utilities

A pure-Python library for working with Nepali (Bikram Sambat) dates: conversion between the Nepali and Gregorian calendars, month details, locale-aware formatting, date arithmetic, digit-script localization, ISO 8601 date-times, selectable-date rules, and a calendar-event SPI with working-day arithmetic. No UI, no runtime dependencies.


version  license  API reference  npm web-component


Table of contents

Installation

Requires Python 3.11+. There are no third-party runtime dependencies: the package is pure standard library, and Nepal time is a fixed +05:45 offset, so no IANA time zone database is needed.

pip install nepali_calendar_utils

Every public name is re-exported from the package root:

from nepali_calendar_utils import *

# Or import only what you need
from nepali_calendar_utils import (
    NepaliDateConverter, CustomCalendar, SimpleDate, SimpleTime,
    NepaliDateLocale, NepaliCalendarUtilsLang, NameFormat, NepaliDateFormatStyle,
    NepaliCalendarDefaults,
)

The names are also importable from their defining modules (nepali_calendar_utils.calendar_model.nepali_date_converter and friends) if you prefer explicit paths.

Quick start

from nepali_calendar_utils import NepaliDateConverter, SimpleDate

converter = NepaliDateConverter()

# Today, in both calendars
converter.today_nepali_calendar     # CustomCalendar in Bikram Sambat
converter.today_english_calendar    # CustomCalendar in Gregorian

# Convert either way
nepali = NepaliDateConverter.convert_english_to_nepali(2021, 6, 21)
nepali.year, nepali.month, nepali.day_of_month   # (2078, 3, 7)

english = NepaliDateConverter.convert_nepali_to_english(2081, 3, 21)
english.year, english.month, english.day_of_month   # (2024, 7, 5)

NepaliDateConverter is the facade for everything in the library. Only the four "now" readings (today_nepali_calendar, today_english_calendar, today_nepali_simple_date, today_english_simple_date) and current_time are instance properties; every other member is a static method you can call on the class.

Conventions and supported range

  • 1-based indexing throughout. Months run 1..12 (1 = Baisakh / January, 12 = Chaitra / December). Weekdays run 1..7 (1 = Sunday, 7 = Saturday).
  • era: 1 for AD (Gregorian), 2 for BS (Bikram Sambat). CustomCalendar.calendar_system is the named form of the same value.
  • Weekend defaults to Saturday only, the single-day weekend Nepal observes (NepaliWeekend.Default == frozenset({7})).
  • Times are always in Nepal time (Asia/Kathmandu, fixed +05:45).

Conversion is table-driven, so it is bounded. The ranges live on NepaliCalendarDefaults:

NepaliCalendarDefaults.NepaliYearRange    # range(1970, 2101)  -> BS 1970..2100
NepaliCalendarDefaults.EnglishYearRange   # range(1913, 2044)  -> AD 1913..2043

# The English years covered by the default Nepali range, and by any sub-range of it
NepaliCalendarDefaults.GregorianYearRange              # range(1913, 2044)
NepaliCalendarDefaults.gregorian_year_range_for(range(2080, 2091))   # range(2023, 2035)

The two calendars start mid-year relative to each other, so a year inside EnglishYearRange is not by itself enough to know a date converts:

NepaliCalendarDefaults.minConvertibleEnglishDate   # SimpleDate(1913, 4, 13)
NepaliCalendarDefaults.maxConvertibleEnglishDate   # SimpleDate(2043, 12, 31)

NepaliDateConverter.is_english_date_convertible(1913, 4, 12)   # False
NepaliDateConverter.is_english_date_convertible(1913, 4, 13)   # True

Calls that read the Gregorian calendar alone (get_english_calendar, get_english_days_in_between, get_english_date_nepali_time_from_iso_format) need no conversion anchor, so they answer for any year. Anything that crosses between the calendars is bounded by the ranges above.

Core types

@dataclass(frozen=True, order=True)
class SimpleDate:
    year: int
    month: int
    day_of_month: int = 1           # ordered: <, sorted(), min(), max() all work

@dataclass(frozen=True)
class SimpleTime:
    hour: int                       # 0-23
    minute: int
    second: int
    nanosecond: int

@dataclass(frozen=True)
class CustomCalendar:               # a full day in either calendar
    year: int
    month: int
    day_of_month: int
    era: int                        # 1 = AD, 2 = BS
    first_day_of_month: int
    last_day_of_month: int
    total_days_in_month: int
    day_of_week_in_month: int = -1
    day_of_week: int = -1
    day_of_year: int = -1
    week_of_month: int = -1
    week_of_year: int = -1

@dataclass(frozen=True)
class NepaliMonthCalendar:          # a Bikram Sambat month's grid geometry
    year: int
    month: int
    total_days_in_month: int
    first_day_of_month: int
    last_day_of_month: int
    days_from_start_of_week_to_first_of_month: int   # derived, leading blank cells

@dataclass(frozen=True)
class MonthCalendar:                # a month of either calendar, tagged with its system
    calendar_system: CalendarSystem
    year: int
    month: int
    total_days_in_month: int
    first_day_of_month: int
    last_day_of_month: int

@dataclass(frozen=True)
class CustomDateTime:
    custom_calendar: CustomCalendar
    simple_time: SimpleTime

CalendarSystem is the named form of era, so layout code does not have to care which calendar produced a month:

from nepali_calendar_utils import CalendarSystem, NepaliDateConverter

CalendarSystem.BIKRAM_SAMBAT.era        # 2
CalendarSystem.GREGORIAN.era            # 1
CalendarSystem.from_era(1)              # CalendarSystem.GREGORIAN (None for anything but 1 or 2)
CalendarSystem.BIKRAM_SAMBAT.opposite() # CalendarSystem.GREGORIAN

NepaliDateConverter.get_nepali_calendar(2082, 1, 1).calendar_system   # BIKRAM_SAMBAT
NepaliDateConverter.get_english_calendar(2024, 9, 9).calendar_system  # GREGORIAN

Conversion helpers move between the shapes: CustomCalendar.to_simple_date(), CustomCalendar.to_nepali_month_calendar(), NepaliMonthCalendar.to_month_calendar() and MonthCalendar.to_nepali_month_calendar().

Usage

Today's date and the current time

converter = NepaliDateConverter()

converter.today_nepali_calendar       # CustomCalendar (BS)
converter.today_english_calendar      # CustomCalendar (AD)
converter.today_nepali_simple_date    # SimpleDate (BS)
converter.today_english_simple_date   # SimpleDate (AD)
converter.current_time                # SimpleTime, in Nepal time

# The same values through the conversion helpers
converter.today_nepali_calendar.to_simple_date()             # SimpleDate
converter.today_english_calendar.to_nepali_month_calendar()  # NepaliMonthCalendar

Date conversion

NepaliDateConverter.convert_english_to_nepali(2021, 6, 21)   # CustomCalendar(2078, 3, 7, era=2, ...)
NepaliDateConverter.convert_nepali_to_english(2081, 3, 21)   # CustomCalendar(2024, 7, 5, era=1, ...)

# Full details for a Bikram Sambat date, without converting
NepaliDateConverter.get_nepali_calendar(2082, 4, 16)         # CustomCalendar(..., day_of_week=6, ...)

# Full details for a Gregorian date, read directly. Needs no conversion anchor, so it
# answers for any year, not only those the conversion table covers.
NepaliDateConverter.get_english_calendar(2024, 9, 9)         # CustomCalendar(era=1, ...)

Month details

NepaliDateConverter.get_total_days_in_nepali_month(2081, 10)    # 30
NepaliDateConverter.get_total_days_in_english_month(2024, 2)    # 29

asar_2078 = NepaliDateConverter.get_nepali_month_calendar(2078, 3)
# NepaliMonthCalendar(year=2078, month=3, total_days_in_month=31,
#                     first_day_of_month=3, last_day_of_month=5,
#                     days_from_start_of_week_to_first_of_month=2)

# The Gregorian counterpart, tagged with the system it belongs to
september_2026 = NepaliDateConverter.get_english_month_calendar(2026, 9)   # MonthCalendar
september_2026.calendar_system                              # CalendarSystem.GREGORIAN
september_2026.days_from_start_of_week_to_first_of_month    # 2, the leading blank cells before day 1

# Moving between the two shapes. to_nepali_month_calendar copies the year and month verbatim,
# so narrowing a Gregorian month yields a NepaliMonthCalendar holding Gregorian numbers.
asar_2078.to_month_calendar().to_nepali_month_calendar() == asar_2078    # True

Reading a whole month at once

# Every day of a Gregorian month, in day order
NepaliDateConverter.get_english_calendars_in_month(2026, 9)     # list[CustomCalendar], 30 entries

# The Bikram Sambat equivalent of every day of a Gregorian month. A day before the conversion
# anchor (AD 1913-04-13) has no equivalent and comes back as None rather than a guess.
paired = NepaliDateConverter.get_nepali_calendars_in_english_month(2026, 9)
convertible = [day for day in paired if day is not None]

# The mirror: every day of a Bikram Sambat month as a Gregorian calendar
NepaliDateConverter.get_english_calendars_in_nepali_month(2082, 1)   # list[CustomCalendar], 31 entries

Each of these converts the whole month in one pass, so prefer them over calling convert_english_to_nepali or convert_nepali_to_english in a loop.

Date arithmetic

Adds or subtracts days from a Bikram Sambat date, rolling over month and year boundaries.

# Add 10 days to 2081-03-15
NepaliDateConverter.get_nepali_calendar_after_addition_or_subtraction(2081, 3, 15, 10)
# CustomCalendar(year=2081, month=3, day_of_month=25, ...)

# Subtract 5 days from 2081-03-15
NepaliDateConverter.get_nepali_calendar_after_addition_or_subtraction(2081, 3, 15, -5)
# CustomCalendar(year=2081, month=3, day_of_month=10, ...)

# Add 50 days, crossing into the next year
NepaliDateConverter.get_nepali_calendar_after_addition_or_subtraction(2081, 11, 15, 50)
# CustomCalendar(year=2082, month=1, day_of_month=5, ...)

Comparing and sorting dates

Both comparison helpers return a negative number when the first date is earlier, zero when the two are equal, and a positive number when the first date is later.

today = NepaliDateConverter().today_nepali_calendar

NepaliDateConverter.compare_simple_dates(today.to_simple_date(), 2090, 2, 12)   # negative
NepaliDateConverter.compare_calendar_dates(today, today)                       # 0

SimpleDate is ordered, so the Python operators work directly:

from nepali_calendar_utils import SimpleDate

SimpleDate(2081, 5, 24) < SimpleDate(2081, 5, 25)   # True

dates = [SimpleDate(2082, 1, 1), SimpleDate(2080, 12, 30), SimpleDate(2081, 5, 24)]
sorted(dates)       # [2080-12-30, 2081-05-24, 2082-01-01]
min(dates), max(dates)

Days between two dates

The end date is excluded from the count; add 1 to include it.

NepaliDateConverter.get_nepali_days_in_between(SimpleDate(1998, 11, 23), SimpleDate(2098, 4, 21))
# 36313

NepaliDateConverter.get_english_days_in_between(SimpleDate(2009, 6, 21), SimpleDate(2500, 3, 23))
# 179244

get_nepali_days_in_between raises ValueError when either year is outside NepaliYearRange. get_english_days_in_between works on Gregorian dates alone, so it is not bounded by the table.

ISO 8601 date-times

The SimpleTime you pass in is read as Nepal time and written out in UTC, which is what makes the result safe to store or hand to another time zone.

converter = NepaliDateConverter()
time = SimpleTime(14, 30, 15, 28900000)

NepaliDateConverter.format_english_date_nepali_time_to_iso(SimpleDate(2025, 1, 25), time)
# "2025-01-25T08:45:15.028900Z"

NepaliDateConverter.format_nepali_datetime_to_iso(SimpleDate(2081, 10, 12), time)
# "2025-01-25T08:45:15.028900Z"

The fractional part is written only when the nanosecond is non-zero. simple_time is optional and defaults to the current time.

Reading back gives a CustomDateTime, a CustomCalendar plus the SimpleTime in Nepal time:

NepaliDateConverter.get_nepali_date_time_from_iso_format("2024-09-09T09:00:15Z")
# CustomDateTime(custom_calendar=CustomCalendar(year=2081, month=5, day_of_month=24, era=2, ...),
#                simple_time=SimpleTime(hour=14, minute=45, second=15, nanosecond=0))

NepaliDateConverter.get_english_date_nepali_time_from_iso_format("2024-09-09T09:00:15Z")
# CustomDateTime(custom_calendar=CustomCalendar(year=2024, month=9, day_of_month=9, era=1, ...),
#                simple_time=SimpleTime(hour=14, minute=45, second=15, nanosecond=0))

Both accept the usual ISO 8601 shapes: "2020-08-30T18:43:00Z", "2020-08-30T18:43:00.503Z", "2020-08-30T18:40:00+03:00", "2011-11-04", "2011-11-04 00:05:23.283", and so on. An unparseable string raises ValueError.

Formatting with a Unicode pattern

converter = NepaliDateConverter()
time = SimpleTime(14, 45, 15, 0)

# Time only
NepaliDateConverter.format_time_by_unicode_pattern(
    unicode_pattern="hh:mm:ss a", time=time, language=NepaliCalendarUtilsLang.NEPALI
)   # "०२:४५:१५ दिउँसो"

NepaliDateConverter.format_time_by_unicode_pattern(
    unicode_pattern="hh:mm:ss A", time=time, language=NepaliCalendarUtilsLang.ENGLISH
)   # "02:45:15 PM"

# Nepali date only
nepali_calendar = NepaliDateConverter.get_nepali_calendar(2081, 5, 24)

NepaliDateConverter.format_nepali_date_by_unicode_pattern(
    unicode_pattern="EEEE, MMMM dd yyyy",
    calendar=nepali_calendar,
    language=NepaliCalendarUtilsLang.NEPALI,   # use ENGLISH for English output
)   # "सोमबार, भदौ २४ २०८१"

# English date only
english_calendar = NepaliDateConverter.get_english_calendar(2025, 5, 24)

NepaliDateConverter.format_english_date_by_unicode_pattern(
    unicode_pattern="E, MMM dd yyyy",
    calendar=english_calendar,
    language=NepaliCalendarUtilsLang.ENGLISH,  # use NEPALI for Nepali output
)   # "Sat, May 24 2025"

# Date and time together
NepaliDateConverter.format_nepali_date_time_by_unicode_pattern(
    unicode_pattern="yyyy MMMM dd, EEEE a hh:mm:ss",
    calendar=nepali_calendar,
    time=time,
    language=NepaliCalendarUtilsLang.NEPALI,
)   # "२०८१ भदौ २४, सोमबार दिउँसो ०२:४५:१५"

NepaliDateConverter.format_english_date_time_by_unicode_pattern(
    unicode_pattern="yyyy MMMM dd, EEEE hh:mm:ss A",
    calendar=NepaliDateConverter.get_english_calendar(2025, 5, 26),
    time=time,
    language=NepaliCalendarUtilsLang.ENGLISH,
)   # "2025 May 26, Monday 02:45:15 PM"

time is optional on the date-time functions; omit it to format the date alone.

Supported placeholders:

Field Tokens
Year yyyy (2025), yy (25)
Month MMMM (full name), MMM (short name), MM (01), M (1)
Day dd (04), d (4), D (day of year, 1-366)
Week w (week of year)
Weekday EEEE (full), E (medium), EEEEE (shortest), ee (02), e (2)
Hour HH / H (24-hour), hh / h (12-hour)
Minute, second mm / m, ss / s
Fractional second SSSS, SSS, SS, S
Period A (PM), a (pm). Both render the localized period in Nepali (दिउँसो)

Month and weekday names follow language; so do the digits, which render in Devanagari for NEPALI and Latin for ENGLISH.

Locale-aware formatting for display

NepaliDateLocale controls the language, the date style, and how weekday and month names are abbreviated. Set digit_script to render digits in a script other than the language's default.

converter = NepaliDateConverter()
today_nepali = converter.today_nepali_calendar
today_english = converter.today_english_calendar

full_nepali = NepaliDateLocale(
    language=NepaliCalendarUtilsLang.NEPALI,
    date_format=NepaliDateFormatStyle.FULL,
    week_day_name=NameFormat.FULL,
    month_name=NameFormat.FULL,
)

# For a calendar read on 2081-10-12 (a Saturday):
NepaliDateConverter.format_nepali_date_from_calendar(today_nepali, full_nepali)
# "शनिबार, माघ १२, २०८१"
NepaliDateConverter.format_nepali_date_from_calendar(today_nepali, NepaliDateLocale())
# "Magh 12, 2081"
NepaliDateConverter.format_nepali_date_from_calendar(today_nepali, NepaliCalendarDefaults.DefaultLocale)
# "Magh 12, 2081"

# Without a calendar object, so without date validation: you supply the day of the week
NepaliDateConverter.format_nepali_date(2081, 3, 21, 5, NepaliCalendarDefaults.DefaultLocale)
# "Asar 21, 2081"

# The English calendar, same locale rules. For 2025-01-25 (a Saturday):
NepaliDateConverter.format_english_date_from_calendar(today_english, NepaliDateLocale())
# "January 25, 2025"
NepaliDateConverter.format_english_date(2025, 1, 25, 7, full_nepali)
# "शनिबार, जनवरी २५, २०२५"

NepaliDateFormatStyle offers FULL, LONG (the default), MEDIUM, SHORT_MDY, SHORT_YMD, COMPACT_MDY and COMPACT_YMD.

Weekday and month names

NepaliDateConverter.get_weekday_name(2, NameFormat.FULL, NepaliCalendarUtilsLang.NEPALI)     # "सोमबार"
NepaliDateConverter.get_weekday_name(5, NameFormat.MEDIUM, NepaliCalendarUtilsLang.ENGLISH)  # "Thu"

NepaliDateConverter.get_month_name(12, NameFormat.FULL, NepaliCalendarUtilsLang.NEPALI)      # "चैत"
NepaliDateConverter.get_month_name(3, NameFormat.SHORT, NepaliCalendarUtilsLang.ENGLISH)     # "Asa"

NepaliDateConverter.get_english_month_name(6, NameFormat.FULL, NepaliCalendarUtilsLang.NEPALI)  # "जुन"

get_month_name names Bikram Sambat months; get_english_month_name names Gregorian ones. Both raise ValueError outside 1..12, as get_weekday_name does outside 1..7.

Formatting a time for display

time = SimpleTime(0, 4, 0, 0)

NepaliDateConverter.get_formatted_time_in_nepali(simple_time=time, use_12_hour_format=True)
# "राति १२ : ०४"
NepaliDateConverter.get_formatted_time_in_english(simple_time=time, use_12_hour_format=False)
# "0:04"

Digit localization

DigitScript holds the ten code points for digits 0-9 in a script, decoupled from language, so any locale sharing the Devanagari digits can reuse it.

from nepali_calendar_utils import (
    NepaliDateConverter, DigitScript, NepaliDateLocale, NepaliCalendarUtilsLang,
)

NepaliDateConverter.localize_digits("2082/02/14", DigitScript.DEVANAGARI)   # "२०८२/०२/१४"
NepaliDateConverter.to_latin_digits("२०८२/०२/१४")                            # "2082/02/14"

# Render Nepali month names with Latin digits
locale = NepaliDateLocale(language=NepaliCalendarUtilsLang.NEPALI, digit_script=DigitScript.LATIN)
locale.resolved_digit_script    # DigitScript.LATIN; left as None it follows the language

The older string helpers remain, and work on whole strings, leaving non-digits untouched:

NepaliDateConverter.convert_to_nepali_number("Today is 2024")     # "Today is २०२४"
NepaliDateConverter.convert_to_english_number("२०२४ सोमबार")       # "2024 सोमबार"

# localize_number renders Latin digits in the language's script. It is a no-op for ENGLISH,
# since Latin is already the English script; use convert_to_english_number to go the other way.
NepaliDateConverter.localize_number("Today is 2024", NepaliCalendarUtilsLang.NEPALI)   # "Today is २०२४"

replace_delimiter swaps separators in an already-formatted string. With no old_delimiter, every non-alphanumeric character is replaced:

NepaliDateConverter.replace_delimiter("2024/06/21", "-")        # "2024-06-21"
NepaliDateConverter.replace_delimiter("२०२४/०६/२१", "-")         # "२०२४-०६-२१"
NepaliDateConverter.replace_delimiter("09:45 AM", " ", ":")     # "09 45 AM"

Short date strings with NepaliDateFormatter

For text-field input and simple numeric output. DatePattern offers YYYY_SLASH_MM_SLASH_DD, YYYY_DASH_MM_DASH_DD, DD_SLASH_MM_SLASH_YYYY and DD_DASH_MM_DASH_YYYY.

from nepali_calendar_utils import NepaliDateFormatter, DatePattern, DigitScript, SimpleDate

NepaliDateFormatter.format(SimpleDate(2082, 2, 14), DatePattern.YYYY_SLASH_MM_SLASH_DD)
# "2082/02/14"
NepaliDateFormatter.format(SimpleDate(2082, 2, 14), DatePattern.DD_DASH_MM_DASH_YYYY, DigitScript.DEVANAGARI)
# "१४-०२-२०८२"

# Parsing accepts Latin and Devanagari digits, and returns None on anything invalid
NepaliDateFormatter.parse("2082/02/14", DatePattern.YYYY_SLASH_MM_SLASH_DD)   # SimpleDate(2082, 2, 14)
NepaliDateFormatter.parse("२०८२/०२/१४", DatePattern.YYYY_SLASH_MM_SLASH_DD)   # SimpleDate(2082, 2, 14)
NepaliDateFormatter.parse("2082/13/14", DatePattern.YYYY_SLASH_MM_SLASH_DD)   # None, bad month

parse checks shape only: month must be 1..12 and day 1..32, since some Bikram Sambat months have 32 days. Validating the day against the actual month length is the caller's job. For long-form, locale-aware output use format_nepali_date_from_calendar instead.

Times on the wire with NepaliTimeFormatter

The transport form of a SimpleTime: HH:mm:ss, fixed to Latin digits and a 24-hour clock, with a nine-digit fractional part appended only when the nanosecond is non-zero.

from nepali_calendar_utils import NepaliTimeFormatter, SimpleTime

NepaliTimeFormatter.format(SimpleTime(9, 30, 0, 0))             # "09:30:00"
NepaliTimeFormatter.format(SimpleTime(23, 59, 59, 123456789))   # "23:59:59.123456789"

# Parsing accepts Latin and Devanagari digits, and returns None on anything it would not have written
NepaliTimeFormatter.parse("09:30:00")    # SimpleTime(9, 30, 0, 0)
NepaliTimeFormatter.parse("9:5:3")       # SimpleTime(9, 5, 3, 0), field widths are not enforced
NepaliTimeFormatter.parse("०९:३०:००")     # SimpleTime(9, 30, 0, 0)
NepaliTimeFormatter.parse("24:00:00")    # None, hour out of range

The fractional part is a plain count of nanoseconds, so ".7" means seven nanoseconds. For something shown to a user, reach for get_formatted_time_in_english, get_formatted_time_in_nepali or format_time_by_unicode_pattern instead.

Restricting selectable dates

NepaliSelectableDates is a predicate pair: one test for a date, one for a year. The factories live on NepaliDateConverter.

from nepali_calendar_utils import NepaliDateConverter, SimpleDate

before = NepaliDateConverter.before_date_selectable(SimpleDate(2081, 5, 24))
after = NepaliDateConverter.after_date_selectable(SimpleDate(2081, 5, 24), include_date=True)
in_range = NepaliDateConverter.date_range_selectable(SimpleDate(2081, 1, 1), SimpleDate(2081, 12, 30))

calendar = NepaliDateConverter.get_nepali_calendar(2081, 5, 23)
before.is_selectable_date(calendar)    # True
before.is_selectable_year(2082)        # False

Both bounds are exclusive by default; pass include_date=True (or include_min_date / include_max_date on date_range_selectable) to include them. Subclass NepaliSelectableDates for any rule the factories do not cover.

Events, closures and working days

A holiday is one kind of calendar event, alongside festivals, school programmes, deadlines and birthdays. What separates a closure from an ordinary day is the closes_offices flag on the event, not its category.

No event data ships with this library by design. Nepali holiday lists change year to year and every institution keeps its own, so you supply a NepaliEventProvider.

from nepali_calendar_utils import (
    SimpleDate, NepaliEventProvider, NepaliCalendarEvent, NepaliEventKind,
    NepaliCalendarPolicy, NepaliSelectableDates,
    working_days_between, next_working_day, add_working_days,
    excluding_weekends, excluding_closures,
)

class MyEvents(NepaliEventProvider):
    _by_year = {
        2082: {
            NepaliCalendarEvent(SimpleDate(2082, 1, 1), "नयाँ वर्ष",
                                NepaliEventKind.GOVERNMENT_PUBLIC),
        },
    }

    def events(self, year):
        return self._by_year.get(year, set())

provider = MyEvents()

NepaliEventKind is deliberately narrow: GOVERNMENT_PUBLIC, RELIGIOUS, REGIONAL and OBSERVANCE. closes_offices defaults to what the kind usually means (an OBSERVANCE does not close, the other three do) and can be overridden per event. id and payload are carried through untouched for an app to correlate a day back to its own record.

Providers compose:

combined = national_list + my_own_list          # everything either side reports
public_only = combined.filtered(lambda event: event.kind is NepaliEventKind.GOVERNMENT_PUBLIC)

Use NoOpEventProvider when you want the event-aware APIs but have not wired a data source yet.

Working-day arithmetic follows Excel WORKDAY semantics. The weekend defaults to Saturday only; pass your own set of day-of-week numbers to override.

working_days_between(SimpleDate(2082, 1, 1), SimpleDate(2082, 1, 15), provider)   # 11
next_working_day(SimpleDate(2082, 1, 1), provider)                                # SimpleDate(2082, 1, 2)
add_working_days(SimpleDate(2082, 1, 1), 5, provider)                             # SimpleDate(2082, 1, 7)

# A Friday-and-Saturday weekend
add_working_days(SimpleDate(2082, 1, 1), 5, provider, weekend=frozenset({6, 7}))  # SimpleDate(2082, 1, 8)

working_days_between counts the half-open range [start, end). add_working_days(date, 0) returns the date unchanged even if it is a closure; use next_working_day to adjust onto a working day. The same three helpers are also available as static methods on NepaliDateConverter.

A policy states the week an institution keeps and the events it names together, and every helper accepts one in place of a provider:

office = NepaliCalendarPolicy(provider=provider)            # closed Saturdays, Nepal's usual week
school = NepaliCalendarPolicy(frozenset({7, 1}), provider)  # closed Saturday and Sunday

school.status_of(SimpleDate(2082, 1, 1))            # NepaliDayStatus
school.status_of(SimpleDate(2082, 1, 1)).names      # ['नयाँ वर्ष']
school.month_status(2082, 1)                        # one NepaliDayStatus per day, index 0 is day 1
school.is_non_working_day(SimpleDate(2082, 1, 1))   # True
school.events_on(SimpleDate(2082, 1, 1))            # events on one day, strongest kind first
school.events_in(2082, 1)                           # every event in a month, in date order

next_working_day(SimpleDate(2082, 1, 1), school)    # the policy carries its own weekend

Pass a policy or a provider-and-weekend pair, never both: the helpers raise ValueError if you do.

NepaliDayStatus answers what one day is: is_weekly_off, events, is_non_working, primary_kind, names and closures. Marking a day and refusing it stay separate decisions, so a policy blocks nothing until you ask it to:

selectable = school.as_selectable_dates()

# Or compose the wrappers yourself
selectable = excluding_closures(excluding_weekends(NepaliSelectableDates()), provider)

Multi-day spans. An event covers exactly one day, so a festival or a stretch of leave expands to one entry per day, each carrying everything but the date unchanged:

NepaliCalendarEvent(SimpleDate(2082, 6, 17), "Dashain", NepaliEventKind.RELIGIOUS,
                    id="dashain-2082").spanning_days(10)              # 10 events

NepaliCalendarEvent(SimpleDate(2082, 6, 17), "Annual leave",
                    NepaliEventKind.OBSERVANCE).spanning_through(SimpleDate(2082, 6, 26))   # 10 events

Give the event an id first when the days have to be recognized as one thing again.

Interoperating with Kotlin, Swift and JavaScript clients

This package is a port of the :core module of the sibling Nepali-Date-Picker project, so a payload written here reads on any of its platforms. The conversion tables, every conversion result, and the wire strings below are identical across them.

The Kotlin side has an optional kotlinx-serialization artifact that defines these shapes. There is no Python counterpart and none is needed: dataclasses and json produce the same bytes.

Type On the wire Produce it here with
SimpleDate "2082-02-14" NepaliDateFormatter.format(date, DatePattern.YYYY_DASH_MM_DASH_DD)
SimpleDate (struct form) {"year": 2082, "month": 2, "dayOfMonth": 14} dataclasses.asdict, renaming day_of_month
SimpleTime "09:30:00", "23:59:59.123456789" NepaliTimeFormatter.format(time)
CustomCalendar 12-field object: year, month, dayOfMonth, era, firstDayOfMonth, lastDayOfMonth, totalDaysInMonth, dayOfWeekInMonth, dayOfWeek, dayOfYear, weekOfMonth, weekOfYear dataclasses.asdict, camel-casing the keys
CalendarSystem 1 for Gregorian, 2 for Bikram Sambat system.era
NepaliCalendarEvent {"date": "2082-01-01", "name": "...", "kind": "GovernmentPublic"}, plus closesOffices, id and payload when set see the note on kind below
NepaliDayStatus {"isWeeklyOff": false, "events": [...]} is_weekly_off plus each event written as above

The last five CustomCalendar fields are optional going back into Kotlin and default to -1, so a payload that omits them still decodes. When all you mean is a day, send the SimpleDate string rather than a whole calendar record.

Field names differ by convention. This package is snake_case and the others are camelCase, so day_of_month travels as dayOfMonth. Convert at the boundary; nothing in the values changes.

kind needs translating. Kotlin writes the enum's own name, GovernmentPublic, Religious, Regional, Observance. This package spells the same members GOVERNMENT_PUBLIC, RELIGIOUS, REGIONAL, OBSERVANCE, and the Swift and JavaScript APIs use governmentPublic. Kotlin rejects a name it does not know rather than guessing, since the guess would decide whether a day closes an office:

_TO_WIRE = {
    NepaliEventKind.GOVERNMENT_PUBLIC: "GovernmentPublic",
    NepaliEventKind.RELIGIOUS: "Religious",
    NepaliEventKind.REGIONAL: "Regional",
    NepaliEventKind.OBSERVANCE: "Observance",
}
_FROM_WIRE = {wire: kind for kind, wire in _TO_WIRE.items()}

A span is many entries. A Kotlin event covers exactly one day, so something that runs longer travels as one entry per day sharing an id. spanning_days and spanning_through already build that shape; collapse it back with id on the way in.

Migrating from 3.0.0

The holiday API became the event API in 3.1.0, because a holiday is one kind of calendar event. The former names still resolve and still work, including providers written against NepaliHolidayProvider; importing from nepali_calendar_utils.holiday raises a DeprecationWarning naming the replacement.

3.0.0 3.1.0
HolidayEntry NepaliCalendarEvent
HolidayKind NepaliEventKind
NepaliHolidayProvider NepaliEventProvider
NoOpHolidayProvider NoOpEventProvider
provider.holidays(year) provider.events(year)
provider.is_holiday(date) provider.closes_on(date)
excluding_holidays(base, provider) excluding_closures(base, provider)
nepali_calendar_utils.holiday nepali_calendar_utils.event

NepaliWeekend moved packages without being renamed. from nepali_calendar_utils import * no longer binds the former names, since __all__ now lists the event surface; import them explicitly if you still need them.

Documentation

The full API reference is published at shivathapaa.github.io/nepali_calendar_utils, generated from the source docstrings.

  • NepaliDateConverter - the main facade: conversion, formatting, comparison, digit scripts, selectable-date factories, working-day helpers.
  • Data types - CustomCalendar, SimpleDate, SimpleTime, NepaliMonthCalendar, MonthCalendar, CalendarSystem, DigitScript, NepaliDateFormatter, NepaliTimeFormatter, locale types.
  • Events and working days - NepaliEventProvider, NepaliCalendarEvent, NepaliCalendarPolicy, NepaliDayStatus, spans and working-day arithmetic.
  • Selectable dates - NepaliSelectableDates.

Release notes live on the releases page.

Other platforms and a date picker UI

This package is the Python port of the Nepali Date Picker calendar core. The same tables are ported to Kotlin, Swift and JavaScript, so conversions match across all four, and every other platform ships a ready-made picker UI that this package deliberately does not:

Platform Guide Packages
Python / backend This README PyPI
Kotlin / Android / KMP main README io.github.shivathapaa:nepali-date-picker-ui (Material3 pickers) and :nepali-date-picker-core (engine only) on Maven Central
Swift / iOS README-spm.md Nepali-Date-Picker-SPM
JavaScript / TypeScript / web README-js.md @nepali-date-picker/web-component (framework-agnostic elements) and @nepali-date-picker/core (engine only, the JavaScript counterpart of this package)

Live demos: the Compose demo and the web-component demo.

Support

You can contribute to this project in several ways:

  • Have an idea for an improvement or a new feature? I'm open to suggestions! Feel free to suggest changes, request enhancements, or report issues here.
  • Share the project with your network to help others discover it.
  • Want to contribute directly? You're welcome to open a pull request! Be sure to review the CONTRIBUTING.md guide before getting started.
  • Show your support by giving this repository a Star⭐. It means a lot! 😊

License

This project is licensed under the Mozilla Public License 2.0 (MPL 2.0), a permissive open-source license that lets you use, modify and distribute the code, provided that modifications to the MPL-licensed files are made available under the same license.

To keep improvements to the core library open and useful to everyone: any modification you make to the files of this library is subject to the terms of the license, and if you modify the library you must make the source of your modifications available to all recipients of the modified library under those same terms.

For the full text, see the LICENSE file.

Metadata

Release files for nepali-calendar-utils 3.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for nepali-calendar-utils 3.1.0
File Size Uploaded
nepali_calendar_utils-3.1.0.tar.gz 110.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for nepali-calendar-utils 3.1.0
File Interpreter ABI Platform
nepali_calendar_utils-3.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 183.6 kB

Release files / nepali_calendar_utils-3.1.0.tar.gz

Download URL nepali_calendar_utils-3.1.0.tar.gz
Size 110.1 kB
Tags Source
SHA-256 checksum
How to use checksums
1c18cc689a4b248e2046bff33e7639e0721baa0b3a5c2c798974917bdf36a1db
BLAKE2b-256 checksum
How to use checksums
5957843e1c19b1487a6a31139a292d4cda3b81e90705bff5b51f62c45f564841
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.

Transparency log

Release files / nepali_calendar_utils-3.1.0-py3-none-any.whl

Download URL nepali_calendar_utils-3.1.0-py3-none-any.whl
Size 73.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e77e22d481da2effe3a6ede411fddd07e4984be2a580ebca6b299f9b792abb69
BLAKE2b-256 checksum
How to use checksums
1700cfcfb4ff7757583731a61135556bafb83002b90e453376daecbd6db64480
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

3.1.0 This release

2 release files

3.0.0

2 release files

2.0.1

2 release files

2.0.0

2 release files

1.1.0

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page