Skip to content

Commit

Permalink
add bsdate util
Browse files Browse the repository at this point in the history
  • Loading branch information
skshetry committed Jan 2, 2024
1 parent 1235a01 commit 1005892
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 20 deletions.
10 changes: 10 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,13 @@ repos:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
- repo: https://github.com/codespell-project/codespell
rev: v2.2.6
hooks:
- id: codespell
additional_dependencies: ["tomli"]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: 'v1.8.0'
hooks:
- id: mypy
exclude: '^tests/'
59 changes: 56 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,61 @@
### Bikram Sambat Calendar
## Bikram Sambat Calendar

#### Usage
### Installation

1. Using pip
```console
pip install bscal
bscal
```

2. Using pipx

```console
pipx install bscal
```

### Usage


#### Display calendar in BS format

```console
$ bscal
Poush 2080
Su Mo Tu We Th Fr Sa
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29
```

> [!TIP]
> You can provide `year` to display a calendar for that year, and also optionally specify the `month` for a specific month's calendar.
#### Display the current BS date:

```console
$ bsdate
Tue Poush 17 09:14:14 +0545 2080
```

#### Display a specific date (from given ISO format)

```console
$ bsdate 2016-09-08
Thu Bhadra 23 09:17:21 +0545 2073
```

#### Display a specific date (represented as a Unix timestamp):

```console
$ bsdate 1473305798
Thu Bhadra 23 09:21:38 +0545 2073
```

#### Convert BS date to AD format

```console
$ bsdate -c 2073-05-23
Thu Sep 8 09:19:00 +0545 2016
```
77 changes: 63 additions & 14 deletions bscal.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import calendar
from datetime import date, timedelta
import contextlib
from datetime import date, datetime, timedelta
from itertools import repeat
from typing import Optional, Tuple
from typing import Iterator, Optional, Sequence, Tuple

lookup = {
# bs_year: [days_in_each_month, ...]
Expand Down Expand Up @@ -316,19 +317,21 @@ def ad_to_bs(ad: date, start: Optional[int] = None) -> Tuple[int, int, int]:


class BSCalendar(calendar.TextCalendar):
def __init__(self, firstweekday: int = 0, to_highlight=()) -> None:
def __init__(self, firstweekday: int = 0, to_highlight: Tuple[int, ...] = ()):
super().__init__(firstweekday)
self._to_highlight = to_highlight
self._formatting = ()
self._formatting_ctx: Tuple[int, ...] = ()

def formatmonthname(self, theyear, themonth, width, withyear=True):
def formatmonthname(
self, theyear: int, themonth: int, width: int, withyear: bool = True
) -> str:
"""Return a formatted month name."""
s = months[themonth - 1]
if withyear:
s = f"{s} {theyear!r}"
return s.center(width)

def itermonthdays(self, year, month):
def itermonthdays(self, year: int, month: int) -> Iterator[int]:
"""Like itermonthdates(), but will yield day numbers. For days outside
the specified month the day number is 0.
"""
Expand All @@ -342,17 +345,63 @@ def itermonthdays(self, year, month):
yield from repeat(0, days_after)

def formatmonth(self, theyear: int, themonth: int, w: int = 0, l: int = 0) -> str: # noqa: E741
self._formatting = (theyear, themonth)
self._formatting_ctx = (theyear, themonth)
return super().formatmonth(theyear, themonth, w, l)

def formatday(self, day: int, weekday: int, width: int) -> str:
s = super().formatday(day, weekday, width)
if (*self._formatting, day) == self._to_highlight:
if (*self._formatting_ctx, day) == self._to_highlight:
s = f"\033[30;47m{s}\033[0m"
return s


def main(args=None):
def bsconv(bs_datestring: str) -> None:
ad = bs_to_ad(*map(int, bs_datestring.split("-")[:3]))
ad_tz = datetime.combine(ad, datetime.now().time()).astimezone()
print(ad_tz.strftime("%a %b %e %T %Z %Y"))


def bsdate(args: Optional[Sequence[str]] = None) -> None:
import argparse

parser = argparse.ArgumentParser()
parser.add_argument(
"-c",
required=False,
metavar="<bs_date>",
help="Convert BS date to AD format",
dest="convert",
)
parser.add_argument(
"date",
nargs="?",
metavar="STRING",
help="Convert given datetime string (isoformat/unix-timestamp) to BS format",
)
opt = parser.parse_args(args)
if opt.convert:
bsconv(opt.convert)
return

dt: Optional[datetime] = None
if opt.date:
with contextlib.suppress(ValueError):
dt = datetime.fromtimestamp(int(opt.date.lstrip("@")))
if not dt and opt.date:
try:
_date = date.fromisoformat(opt.date)
except ValueError:
dt = datetime.fromisoformat(opt.date)
else:
dt = datetime.combine(_date, datetime.now().time())

dt = dt or datetime.now()
dt = dt.astimezone()
year, month, day = ad_to_bs(dt.date())
print(dt.strftime("%a"), months[month - 1], day, dt.strftime("%T %Z"), year)


def cal(args: Optional[Sequence[str]] = None) -> None:
import argparse
import sys

Expand All @@ -364,16 +413,16 @@ def main(args=None):
type=int,
help="month number (1-12, text only)",
)
args = parser.parse_args(args)
opt = parser.parse_args(args)

year, month, day = ad_to_bs(date.today())
cal = BSCalendar(6, to_highlight=(year, month, day) if sys.stdout.isatty() else ())
if not args.year or args.month:
result = cal.formatmonth(args.year or year, args.month or month)
if not opt.year or opt.month:
result = cal.formatmonth(opt.year or year, opt.month or month)
else:
result = cal.formatyear(args.year)
result = cal.formatyear(opt.year)
print(result)


if __name__ == "__main__":
main()
cal()
11 changes: 10 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ classifiers = ["Programming Language :: Python :: 3"]
dynamic = ["version"]

[project.scripts]
bscal = "bscal:main"
bscal = "bscal:cal"
bsdate = "bscal:bsdate"

[project.optional-dependencies]
tests = ["pytest", "hypothesis"]
Expand All @@ -22,6 +23,11 @@ license-files = ["LICENSE"]

[tool.setuptools_scm]

[tool.mypy]
check_untyped_defs = true
files = ["bscal.py"]
strict = true

[tool.ruff]
ignore = ["ISC001", "S101"]
select = [
Expand All @@ -33,6 +39,9 @@ select = [
show-source = true
show-fixes = true

[tool.ruff.format]
preview = true

[tool.ruff.flake8-type-checking]
strict = true

Expand Down
4 changes: 2 additions & 2 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pytest

from bscal import main
from bscal import cal

results_2080_poush = """\
Poush 2080
Expand Down Expand Up @@ -61,7 +61,7 @@
],
)
def test_cli(capsys, args, expected):
main(args)
cal(args)
out, err = capsys.readouterr()
assert out == expected
assert not err

0 comments on commit 1005892

Please sign in to comment.