├── nepali ├── tests │ ├── __init__.py │ ├── test_humanize.py │ ├── test_date_converter.py │ ├── test_locations.py │ ├── test_nepaliweek.py │ ├── test_nepalimonth.py │ ├── test_parser.py │ ├── test_timezone.py │ ├── test_phone_number.py │ └── test_datetime.py ├── templatetags │ ├── __init__.py │ ├── nepalinumber.py │ └── nepalidatetime.py ├── __init__.py ├── datetime │ ├── parser │ │ ├── __init__.py │ │ ├── _parser.py │ │ └── validators.py │ ├── __init__.py │ ├── utils.py │ ├── _nepalimonth.py │ ├── _nepaliweek.py │ ├── _humanize.py │ ├── _formatter.py │ └── _datetime.py ├── locations │ ├── __init__.py │ ├── _locations.py │ ├── utils.py │ └── models.py ├── utils.py ├── exceptions.py ├── number │ ├── __init__.py │ ├── _number.py │ └── utils.py ├── constants.py ├── timezone.py ├── char.py ├── phone_number.py └── date_converter.py ├── requirements.txt ├── codecov.yml ├── .gitignore ├── Makefile ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── PULL_REQUEST_TEMPLATE.md ├── workflows │ ├── python-package-publish.yml │ └── python-package.yml └── CODE_OF_CONDUCT.md ├── .pre-commit-config.yaml ├── LICENSE ├── CONTRIBUTING.md ├── setup.py ├── CHANGELOG.md ├── SECURITY.md └── README.md /nepali/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nepali/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | coverage 2 | -------------------------------------------------------------------------------- /nepali/__init__.py: -------------------------------------------------------------------------------- 1 | name = "nepali" 2 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | # This files contains codecov configurations. 2 | 3 | # Disabling comments 4 | comment: false 5 | -------------------------------------------------------------------------------- /nepali/datetime/parser/__init__.py: -------------------------------------------------------------------------------- 1 | from ._parser import parse, strptime 2 | 3 | __all__ = [ 4 | "strptime", 5 | "parse", 6 | ] 7 | -------------------------------------------------------------------------------- /nepali/locations/__init__.py: -------------------------------------------------------------------------------- 1 | from ._locations import districts, municipalities, provinces 2 | 3 | __all__ = ["provinces", "districts", "municipalities"] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info/ 2 | __pycache__/ 3 | .eggs 4 | build 5 | dist 6 | .vscode 7 | htmlcov 8 | .coverage 9 | .coverage.* 10 | .DS_Store 11 | venv/ 12 | .env 13 | -------------------------------------------------------------------------------- /nepali/utils.py: -------------------------------------------------------------------------------- 1 | from nepali.datetime.utils import to_nepalidate, to_nepalidatetime 2 | from nepali.timezone import to_nepali_timezone, to_utc_timezone 3 | 4 | __all__ = [ 5 | "to_nepalidate", 6 | "to_nepalidatetime", 7 | "to_utc_timezone", 8 | "to_nepali_timezone", 9 | ] 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ## 2 | # py-nepali 3 | # 4 | # @file 5 | # @version 0.2 6 | test: 7 | python -m unittest discover nepali/tests -v 8 | 9 | coverage: 10 | coverage run -m unittest discover nepali/tests -v 11 | coverage report 12 | 13 | coverage-html: 14 | rm -rf htmlcov 15 | coverage run -m unittest discover nepali/tests -v 16 | coverage html 17 | open htmlcov/index.html 18 | -------------------------------------------------------------------------------- /nepali/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | Exceptions for nepali 3 | """ 4 | 5 | 6 | class InvalidDateFormatException(Exception): 7 | pass 8 | 9 | 10 | class InvalidDateTimeFormatException(Exception): 11 | pass 12 | 13 | 14 | class InvalidNepaliDateTimeObjectException(Exception): 15 | pass 16 | 17 | 18 | class FormatNotMatchException(Exception): 19 | """ 20 | raised while parsing nepalidatetime format 21 | """ 22 | 23 | pass 24 | -------------------------------------------------------------------------------- /nepali/number/__init__.py: -------------------------------------------------------------------------------- 1 | from ._nepalinumber import nepalinumber 2 | from ._number import NepaliNumber 3 | from .utils import ( 4 | add_comma, 5 | add_comma_english, 6 | convert_and_add_comma, 7 | english_to_nepali, 8 | nepali_to_english, 9 | ) 10 | 11 | __all__ = [ 12 | "NepaliNumber", 13 | "add_comma", 14 | "add_comma_english", 15 | "convert_and_add_comma", 16 | "english_to_nepali", 17 | "nepali_to_english", 18 | "nepalinumber", 19 | ] 20 | -------------------------------------------------------------------------------- /nepali/datetime/__init__.py: -------------------------------------------------------------------------------- 1 | from ._datetime import nepalidate, nepalidatetime, nepalitime 2 | from ._formatter import NepaliDateTimeFormatter 3 | from ._humanize import HumanizeDateTime, nepalihumanize 4 | from ._nepalimonth import nepalimonth 5 | from ._nepaliweek import nepaliweek 6 | 7 | __all__ = [ 8 | "nepalidate", 9 | "nepalitime", 10 | "nepalidatetime", 11 | "nepalihumanize", 12 | "nepalimonth", 13 | "nepaliweek", 14 | "NepaliDateTimeFormatter", 15 | "HumanizeDateTime", 16 | ] 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # Matches multiple files with brace expansion notation 12 | # Set default charset 13 | [*.{py,rst,md}] 14 | charset = utf-8 15 | 16 | # 4 space indentation 17 | [*.py] 18 | indent_style = space 19 | indent_size = 4 20 | 21 | # Max line length of 120 characters 22 | [*.py] 23 | max_line_length = 120 24 | 25 | # Use double quotes instead of single quotes 26 | [*.py] 27 | quote_style = double 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Use Case** 11 | Please describe the specific use case or scenario where this feature would be beneficial. 12 | 13 | **Proposed Solution** 14 | Please outline your proposed solution or approach for implementing this feature. Include any relevant details, such as code snippets or examples, if applicable. 15 | 16 | **Alternative Considered** 17 | Have you considered any alternative solutions or approaches? If so, please describe them and explain why you believe the proposed solution is the best option. 18 | 19 | **Dependencies** 20 | If this feature has any dependencies or requirements, please list them here. 21 | 22 | **Additional context** 23 | Add any other context or screenshots about the feature request here. 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug, help wanted 6 | assignees: aj3sh, sugat009 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Please provide the steps to reproduce the issue. Include code snippets or specific examples if applicable. 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Environment** 20 | - Python version: 21 | - Package version: 22 | - Operating system: 23 | - Any other relevant information about your environment. 24 | 25 | **Additional context** 26 | Please provide any additional context or information that may be helpful in understanding and resolving the issue. This can include: 27 | - Exception stack trace 28 | - Error messages 29 | - Log files 30 | - Screenshots 31 | - Any other relevant details related to the issue. 32 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.4.0 4 | hooks: 5 | - id: check-merge-conflict 6 | - id: check-added-large-files 7 | - id: check-yaml 8 | - id: pretty-format-json 9 | - id: end-of-file-fixer 10 | - id: trailing-whitespace 11 | 12 | - repo: https://github.com/akaihola/darker 13 | rev: 1.7.1 14 | hooks: 15 | - id: darker 16 | args: 17 | - --isort 18 | - --flynt 19 | - --lint=flake8 --max-line-length=88 --ignore=E203,W503 20 | - --lint=mypy # --strict # commenting strict for now as we haven't maintained typing completely 21 | - --lint=pylint --max-line-length=88 --disable=W0511 22 | additional_dependencies: 23 | - black==23.3.0 24 | - flake8==5.0.4 25 | - flynt==0.77 26 | - isort==5.12.0 27 | - mypy==1.3.0 28 | - pylint==2.17.4 29 | 30 | - repo: https://github.com/opensource-nepal/commitlint 31 | rev: v1.0.0 32 | hooks: 33 | - id: commitlint 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 The Python Packaging Authority 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | [Provide a brief description of the changes introduced by the pull request.] 4 | 5 | ## Related Issue 6 | 7 | [If your pull request is related to an existing issue, reference it here using the format "Fixes #issue_number".] 8 | 9 | ## Type of Change 10 | 11 | Please mark the appropriate option below to describe the type of change your pull request introduces: 12 | 13 | - [ ] Bug fix 14 | - [ ] New feature 15 | - [ ] Enhancement 16 | - [ ] Documentation update 17 | - [ ] Refactor 18 | - [ ] Other (please specify) 19 | 20 | ## Checklist 21 | 22 | - [ ] I have used pre-commit hooks. 23 | - [ ] I have added/updated the necessary documentation on `README.md`. 24 | - [ ] I have updated `CHANGELOG.md` for the significant changes. 25 | - [ ] I have added appropriate test cases (if applicable) to ensure the changes are functioning correctly. 26 | - [ ] My pull request has a clear title and description. 27 | 28 | ## Additional Notes 29 | 30 | [Add any additional notes or context that you think the reviewers should know about.] 31 | 32 | By submitting this pull request, I confirm that I have read and complied with the contribution guidelines of this project. 33 | -------------------------------------------------------------------------------- /.github/workflows/python-package-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Python Package Publish 10 | 11 | on: 12 | release: 13 | types: [published] 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | deploy: 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | 25 | - name: Set up Python 26 | uses: actions/setup-python@v3 27 | with: 28 | python-version: "3.x" 29 | 30 | - name: Install dependencies 31 | run: | 32 | python -m pip install --upgrade pip 33 | pip install build 34 | 35 | - name: Build package 36 | run: python -m build 37 | 38 | - name: Publish package 39 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 40 | with: 41 | user: __token__ 42 | password: ${{ secrets.PYPI_API_TOKEN }} 43 | -------------------------------------------------------------------------------- /nepali/constants.py: -------------------------------------------------------------------------------- 1 | """ 2 | This files contains constants for the py-Nepali project. 3 | """ 4 | 5 | # Nepali timezone 6 | NEPAL_TIMEZONE = "Asia/Kathmandu" 7 | 8 | # Nepali months in english 9 | NEPALI_MONTHS_EN = ( 10 | "Baishakh", 11 | "Jestha", 12 | "Ashad", 13 | "Sharwan", 14 | "Bhadra", 15 | "Ashwin", 16 | "Kartik", 17 | "Mangsir", 18 | "Poush", 19 | "Magh", 20 | "Falgun", 21 | "Chaitra", 22 | ) 23 | 24 | # Nepali months in Nepali (devanagari) 25 | NEPALI_MONTHS_NE = ( 26 | "बैशाख", 27 | "जेठ", 28 | "असार", 29 | "साउन", 30 | "भदौ", 31 | "असोज", 32 | "कात्तिक", 33 | "मंसिर", 34 | "पुस", 35 | "माघ", 36 | "फागुन", 37 | "चैत", 38 | ) 39 | 40 | # Week names in english 41 | WEEKS_EN = ( 42 | "Sunday", 43 | "Monday", 44 | "Tuesday", 45 | "Wednesday", 46 | "Thursday", 47 | "Friday", 48 | "Saturday", 49 | ) 50 | 51 | # Week abbreviation name in english 52 | WEEKS_ABBR_EN = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat") 53 | 54 | # Weeks names in Nepali (devanagari) 55 | WEEKS_NE = ( 56 | "आइतबार", 57 | "सोमबार", 58 | "मंगलबार", 59 | "बुधबार", 60 | "बिहीबार", 61 | "शुक्रबार", 62 | "शनिबार", 63 | ) 64 | 65 | # Week abbreviation name in Nepali (devanagari) 66 | WEEKS_ABBR_NE = ("आइत", "सोम", "मंगल", "बुध", "बिही", "शुक्र", "शनि") 67 | -------------------------------------------------------------------------------- /nepali/locations/_locations.py: -------------------------------------------------------------------------------- 1 | from typing import List, Tuple 2 | 3 | from .models import District, Municipality, MunicipalityType, Province 4 | 5 | 6 | def _loadData() -> Tuple[List[Province], List[District], List[Municipality]]: 7 | from ._data import _location_data 8 | 9 | provinces = [] 10 | districts = [] 11 | municipalities = [] 12 | for province_data in _location_data: 13 | province = Province( 14 | name=province_data["name"], name_nepali=province_data["name_nepali"] 15 | ) 16 | provinces.append(province) 17 | 18 | for district_data in province_data["districts"]: 19 | district = District( 20 | province=province, 21 | name=district_data["name"], 22 | name_nepali=district_data["name_nepali"], 23 | ) 24 | districts.append(district) 25 | 26 | for municipality_data in district_data["municipalities"]: 27 | municipality = Municipality( 28 | district=district, 29 | name=municipality_data["name"], 30 | name_nepali=municipality_data["name_nepali"], 31 | municipality_type=MunicipalityType( 32 | municipality_data["municipality_type"] 33 | ), 34 | ) 35 | municipalities.append(municipality) 36 | 37 | return provinces, districts, municipalities 38 | 39 | 40 | provinces, districts, municipalities = _loadData() 41 | -------------------------------------------------------------------------------- /nepali/datetime/utils.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import Any 3 | 4 | from nepali.exceptions import InvalidNepaliDateTimeObjectException 5 | 6 | from ._datetime import nepalidate, nepalidatetime 7 | 8 | 9 | def to_nepalidatetime(datetime_object: Any) -> nepalidatetime: 10 | """ 11 | Converts nepalidate, datetime.datetime, datetime.date to nepalidatetime. 12 | 13 | :param datetime_object: Object to be converted into nepalidatetime 14 | :return: nepalidatetime 15 | :raises InvalidNepaliDateTimeObjectException: If the input data is not a date time objects 16 | """ 17 | if isinstance(datetime_object, nepalidatetime): 18 | return datetime_object 19 | elif isinstance(datetime_object, nepalidate): 20 | return nepalidatetime.from_nepali_date(datetime_object) 21 | elif isinstance(datetime_object, datetime.datetime): 22 | return nepalidatetime.from_datetime(datetime_object) 23 | elif isinstance(datetime_object, datetime.date): 24 | return nepalidatetime.from_date(datetime_object) 25 | raise InvalidNepaliDateTimeObjectException( 26 | "Argument must be instance of nepalidate or nepalidatetime or datetime.datetime or datetime.date" 27 | ) 28 | 29 | 30 | def to_nepalidate(datetime_object: Any) -> nepalidate: 31 | """ 32 | Converts nepalidate, datetime.datetime, datetime.date to nepalidate. 33 | 34 | :param datetime_object: Object to be converted into nepalidate 35 | :return: nepalidate 36 | :raises InvalidNepaliDateTimeObjectException: If the input data is not a date time objects 37 | """ 38 | return to_nepalidatetime(datetime_object).date() 39 | -------------------------------------------------------------------------------- /nepali/locations/utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | from functools import partial 3 | 4 | from ._locations import districts, municipalities, provinces 5 | 6 | __all__ = [ 7 | "get_province", 8 | "get_district", 9 | "get_municipality", 10 | ] 11 | 12 | 13 | def _filter_location( 14 | *, locations, name=None, name_nepali=None, exact=False, multiple=False 15 | ): 16 | if not name and not name_nepali: 17 | raise ValueError("name or name_nepali must be passed") 18 | 19 | if exact and multiple: 20 | raise ValueError("Both the exact and multiple cannot be true") 21 | 22 | # making name as lower case 23 | if name: 24 | name = name.lower() 25 | 26 | # taking field and value for filtering 27 | # eg. field = name and value ="Bagmati Province" 28 | field, value = ("name", name) if name else ("name_nepali", name_nepali) 29 | 30 | # filtering data 31 | if exact: 32 | filtered_locations = [ 33 | location 34 | for location in locations 35 | if getattr(location, field).lower() == value 36 | ] 37 | else: 38 | pattern = re.compile(rf".*{value}.*") 39 | filtered_locations = [ 40 | location 41 | for location in locations 42 | if re.match(pattern, getattr(location, field).lower()) 43 | ] 44 | 45 | # returning data if not filtered location is available 46 | if len(filtered_locations) == 0 and not multiple: 47 | return None 48 | 49 | return filtered_locations if multiple else filtered_locations[0] 50 | 51 | 52 | get_province = partial(_filter_location, locations=provinces) 53 | get_district = partial(_filter_location, locations=districts) 54 | get_municipality = partial(_filter_location, locations=municipalities) 55 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | ## Creating a Separate virtualenv 4 | 5 | Before you contribute on this project you need to create a new separate [virtualenv](https://docs.python.org/3/library/venv.html). 6 | 7 | Here is one example. 8 | 9 | ```bash 10 | python -m venv .env 11 | source .env/bin/activate 12 | ``` 13 | 14 | ## Dependencies 15 | 16 | We have listed all the python dependencies in the `requirements.txt` files. 17 | 18 | ## Pre-commit Hook 19 | 20 | The config file for [`pre-commit`](https://pre-commit.com/) hooks is in [.pre-commit-config.yaml](./.pre-commit-config.yaml) file. To install pre-commit and enable the hook please refer to [https://pre-commit.com](https://pre-commit.com/) 21 | 22 | ## Codestyle 23 | 24 | A [.editorconfig](./editorconfig) is available to maintain the coding style. Besides, your code will automatically gets formatted if you have install the pre-commit hook. 25 | 26 | ## Unit tests 27 | 28 | Run the unittest using the below command: 29 | 30 | ```bash 31 | make test 32 | ``` 33 | 34 | ### Coverage Report 35 | 36 | To run the coverage report: 37 | 38 | ```bash 39 | make coverage 40 | ``` 41 | 42 | To generate HTML coverage report 43 | 44 | ```bash 45 | make coverage-html 46 | ``` 47 | 48 | ### Before submitting 49 | 50 | Before submitting your code please do the following steps: 51 | 52 | 1. Add any changes you want. 53 | 1. Add tests for the new changes. 54 | 1. Update the `CHANGELOG.md` file if necessary. 55 | 1. Edit documentation (`README.md`) if you have changed something significant. 56 | 1. Commit your changes using [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). 57 | Examples: `feat: add JSON parser`, `feat(parser): add JSON parser`. 58 | 59 | ## Other help 60 | 61 | You can contribute by spreading a word about this library. 62 | It would also be a huge contribution to write 63 | a short article on how you are using this project. 64 | You can also share your best practices with us. 65 | -------------------------------------------------------------------------------- /nepali/number/_number.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | from .utils import add_comma, add_comma_english, english_to_nepali, nepali_to_english 4 | 5 | 6 | # Backward compatibility support for legacy NepaliNumber 7 | class NepaliNumber: 8 | @classmethod 9 | def convert_and_add_comma(cls, number): 10 | warnings.warn( 11 | message="NepaliNumber.convert_and_add_comma has been moved to " 12 | "`convert_and_add_comma. This function is depreciated and will " 13 | "be removed in the future release.", 14 | category=DeprecationWarning, 15 | ) 16 | return add_comma(english_to_nepali(number)) 17 | 18 | @staticmethod 19 | def convert(num): 20 | warnings.warn( 21 | message="NepaliNumber.convert has been moved to `english_to_nepali. " 22 | "This function is depreciated and will be removed in the future release.", 23 | category=DeprecationWarning, 24 | ) 25 | return english_to_nepali(num) 26 | 27 | @staticmethod 28 | def revert(num): 29 | warnings.warn( 30 | message="NepaliNumber.revert has been moved to `nepali_to_english. " 31 | "This function is depreciated and will be removed in the future release.", 32 | category=DeprecationWarning, 33 | ) 34 | return nepali_to_english(num) 35 | 36 | @staticmethod 37 | def add_comma(number): 38 | warnings.warn( 39 | message="NepaliNumber.add_comma has been moved to `add_comma. " 40 | "This function is depreciated and will be removed in the future release.", 41 | category=DeprecationWarning, 42 | ) 43 | return add_comma(number) 44 | 45 | @staticmethod 46 | def add_comma_english(number): 47 | warnings.warn( 48 | message="NepaliNumber.add_comma_english has been moved to " 49 | "`add_comma_english. This function is depreciated and will " 50 | "be removed in the future release.", 51 | category=DeprecationWarning, 52 | ) 53 | return add_comma_english(number) 54 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | setup file for nepali package 3 | 4 | - Building a package 5 | 6 | pip install build 7 | python -m build 8 | 9 | 10 | - Publishing a package 11 | You must have twine installed in your system. `pip install twine` 12 | 13 | python setup.py sdist bdist_wheel 14 | twine upload dist/* 15 | 16 | """ 17 | import os 18 | import sys 19 | 20 | import setuptools 21 | 22 | GITHUB_URL = "https://github.com/opensource-nepal/py-nepali" 23 | CHANGELOG_URL = "https://github.com/opensource-nepal/py-nepali/blob/main/CHANGELOG.md" 24 | 25 | with open("README.md", "r") as fh: 26 | long_description = fh.read() 27 | 28 | 29 | if sys.argv[-1] == "publish": 30 | if os.system("pip3 freeze | grep twine"): 31 | print("twine not installed.\nUse `pip install twine`.\nExiting.") 32 | sys.exit() 33 | os.system("rm -rf dist") 34 | os.system("python3 setup.py sdist bdist_wheel") 35 | os.system("twine upload dist/*") 36 | sys.exit() 37 | 38 | 39 | setuptools.setup( 40 | name="nepali", 41 | version="1.1.3", 42 | license="MIT", 43 | author="opensource-nepal", 44 | author_email="aj3sshh@gmail.com, sugatbajracharya49@gmail.com", 45 | description="nepalidatetime compatible with python's datetime feature. " 46 | "Converting nepali date to english, parsing nepali datetime, " 47 | "nepali timezone, and timedelta support in nepali datetime", 48 | long_description=long_description, 49 | long_description_content_type="text/markdown", 50 | keywords=[ 51 | "nepali date conversion", 52 | "convert date", 53 | "nepali date time", 54 | "python convert date", 55 | "parse nepali date time", 56 | ], 57 | url=GITHUB_URL, 58 | packages=setuptools.find_packages(exclude=["tests*"]), 59 | test_suite="nepali.tests", 60 | install_requires=[], 61 | classifiers=[ 62 | "Programming Language :: Python :: 3", 63 | "License :: OSI Approved :: MIT License", 64 | "Operating System :: OS Independent", 65 | ], 66 | project_urls={ 67 | "Source": GITHUB_URL, 68 | "Changelog": CHANGELOG_URL, 69 | }, 70 | ) 71 | -------------------------------------------------------------------------------- /nepali/number/utils.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | NP_NUMBERS = ["०", "१", "२", "३", "४", "५", "६", "७", "८", "९"] 4 | NP_NUMBERS_SET = set(NP_NUMBERS) 5 | 6 | 7 | def english_to_nepali(number: Any) -> str: 8 | """ 9 | Converts english number to nepali. 10 | """ 11 | number = str(number) 12 | converted_number = [] 13 | for n in number: 14 | num = ord(n) - ord("0") 15 | if num in range(0, 10): 16 | converted_number.append(NP_NUMBERS[num]) 17 | else: 18 | converted_number.append(n) 19 | return "".join(converted_number) 20 | 21 | 22 | def nepali_to_english(number: Any) -> str: 23 | """ 24 | Converts nepali number to english. 25 | """ 26 | number = str(number) 27 | converted_number = [] 28 | for n in number: 29 | if n in NP_NUMBERS_SET: 30 | converted_number.append(str(NP_NUMBERS.index(n))) 31 | else: 32 | converted_number.append(n) 33 | return "".join(converted_number) 34 | 35 | 36 | def add_comma_english(number: Any) -> str: 37 | """ 38 | Adds comma in english style 39 | Eg. 123456789 => 123,456,789 40 | """ 41 | return f"{int(number):,}" 42 | 43 | 44 | def add_comma(number: Any, convert=False) -> str: 45 | """ 46 | Adds comma in nepali style 47 | Eg. 123456789 => 12,34,56,789 48 | 49 | :param number Any: Number to be converted 50 | :param convert bool: If true converts english number to nepali 51 | """ 52 | if convert: 53 | number = english_to_nepali(number) 54 | else: 55 | number = str(number) 56 | 57 | number_with_comma = [] 58 | counter = 0 59 | for nepali_number_char in list(str(number))[::-1]: 60 | if counter == 3 or (counter != 1 and (counter - 1) % 2 == 0): 61 | number_with_comma.append(",") 62 | number_with_comma.append(nepali_number_char) 63 | counter += 1 64 | 65 | return "".join(number_with_comma[::-1]) 66 | 67 | 68 | def convert_and_add_comma(number: Any) -> str: 69 | """ 70 | Converts the number into nepali text and adds comma to it. 71 | """ 72 | return add_comma(number, convert=True) 73 | -------------------------------------------------------------------------------- /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python Package 5 | 6 | on: 7 | push: 8 | branches: ['main'] 9 | pull_request: 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | python-version: ['3.8', '3.9', '3.10'] 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - name: Conventional Commitlint 23 | uses: opensource-nepal/commitlint@v1 24 | with: 25 | verbose: true 26 | 27 | - name: Set up Python ${{ matrix.python-version }} 28 | uses: actions/setup-python@v5 29 | with: 30 | python-version: ${{ matrix.python-version }} 31 | 32 | - name: Install dependencies 33 | run: | 34 | python -m pip install --upgrade pip 35 | python -m pip install flake8 36 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 37 | 38 | - name: Lint with flake8 39 | run: | 40 | # stop the build if there are Python syntax errors or undefined names 41 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 42 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 43 | flake8 . --count --exit-zero --max-complexity=10 --ignore=E203,W503 --max-line-length=127 --statistics 44 | # For the reason behind ignoring E203 and W503, visit https://blackq.readthedocs.io/en/stable/faq.html#why-are-flake8-s-e203-and-w503-violated 45 | 46 | - name: Run tests 47 | run: | 48 | coverage run -m unittest discover nepali/tests -v 49 | 50 | - name: Show coverage 51 | run: | 52 | coverage report 53 | 54 | - name: Send coverage to CodeCov 55 | uses: codecov/codecov-action@v4 56 | with: 57 | token: ${{ secrets.CODECOV_TOKEN }} 58 | fail_ci_if_error: false 59 | verbose: true 60 | -------------------------------------------------------------------------------- /nepali/datetime/parser/_parser.py: -------------------------------------------------------------------------------- 1 | from nepali.exceptions import FormatNotMatchException, InvalidDateTimeFormatException 2 | 3 | from .validators import validate 4 | 5 | __all__ = [ 6 | "strptime", 7 | "parse", 8 | ] 9 | 10 | _standard_datetime_format_CACHE = None 11 | 12 | 13 | def strptime(datetime_str, format): 14 | """ 15 | Parses nepalidatetime str with the corresponding format. 16 | returns nepalidatetime object. 17 | """ 18 | nepalidatetime_object = validate(datetime_str, format) 19 | if nepalidatetime_object is None: 20 | raise FormatNotMatchException( 21 | "Datetime string did not match with the given format." 22 | ) 23 | return nepalidatetime_object 24 | 25 | 26 | def _get_standard_formats(): 27 | global _standard_datetime_format_CACHE 28 | if _standard_datetime_format_CACHE is not None: 29 | return _standard_datetime_format_CACHE 30 | 31 | STANDARD_DATE_FORMAT = [ 32 | "%Y-%m-%d", 33 | "%d-%m-%Y", 34 | "%Y %m %d", 35 | "%Y %B %d", 36 | "%d %B %Y", 37 | "%d %B, %Y", 38 | "%B %d, %Y", 39 | "%Y/%m/%d", 40 | "%d/%m/%Y", 41 | ] 42 | 43 | STANDARD_TIME_FORMAT = [ 44 | "%H:%M", 45 | "%I:%M %p", 46 | "%I %p", 47 | "%I%p", 48 | "%H:%M:%S", 49 | "%H:%M:%S:%f", 50 | ] 51 | 52 | _standard_datetime_format_CACHE = STANDARD_DATE_FORMAT.copy() 53 | for time_format in STANDARD_TIME_FORMAT: 54 | for date_format in STANDARD_DATE_FORMAT: 55 | _standard_datetime_format_CACHE += [ 56 | f"{date_format} {time_format}", 57 | f"{date_format}, {time_format}", 58 | ] 59 | return _standard_datetime_format_CACHE 60 | 61 | 62 | def parse(datetime_str): 63 | """ 64 | parses nepali datetime 65 | eg. parse('2078-10-12') => 66 | """ 67 | standard_formats = _get_standard_formats() 68 | for format in standard_formats: 69 | try: 70 | return strptime(datetime_str, format=format) 71 | except FormatNotMatchException: 72 | pass 73 | 74 | raise InvalidDateTimeFormatException("Invalid format to parse nepali datetime.") 75 | -------------------------------------------------------------------------------- /nepali/timezone.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import Union 3 | 4 | from .constants import NEPAL_TIMEZONE 5 | 6 | 7 | class NepaliTimeZone(datetime.tzinfo): 8 | """ 9 | NepaliTimeZone: "Asia/Kathmandu", +05:45 10 | """ 11 | 12 | def utcoffset(self, dt): 13 | return self.dst(dt) + datetime.timedelta(hours=5, minutes=45) 14 | 15 | def dst(self, dt): 16 | return datetime.timedelta(0) 17 | 18 | def tzname(self, dt): 19 | return NEPAL_TIMEZONE 20 | 21 | def __str__(self): 22 | return NEPAL_TIMEZONE 23 | 24 | def __repr__(self): 25 | return NEPAL_TIMEZONE 26 | 27 | def __eq__(self, o: object) -> bool: 28 | return isinstance(o, self.__class__) 29 | 30 | 31 | def get_timezone() -> Union[datetime.tzinfo, None]: 32 | """ 33 | Returns current device's timezone. 34 | Timezone of the machine. 35 | """ 36 | return datetime.datetime.now(datetime.timezone.utc).astimezone().tzinfo 37 | 38 | 39 | def now() -> datetime.datetime: 40 | """Returns current datetime object of the device""" 41 | return datetime.datetime.now(get_timezone()) 42 | 43 | 44 | def utc_now() -> datetime.datetime: 45 | """Returns UTC time datetime object""" 46 | return datetime.datetime.now(datetime.timezone.utc) 47 | 48 | 49 | def to_utc_timezone(datetime_obj: datetime.datetime) -> datetime.datetime: 50 | """Changes the timezone of the given datetime object to UTC.""" 51 | if type(datetime_obj) != datetime.datetime: 52 | # Not a datetime object 53 | return datetime_obj 54 | 55 | if not hasattr(datetime_obj, "tzinfo") or not datetime_obj.tzinfo: 56 | datetime_obj = datetime_obj.replace(tzinfo=get_timezone()) 57 | return datetime_obj.astimezone(datetime.timezone.utc) 58 | 59 | 60 | def to_nepali_timezone(datetime_obj: datetime.datetime) -> datetime.datetime: 61 | """Changes the timezone of the given datetime object to NepaliTimeZone.""" 62 | if type(datetime_obj) != datetime.datetime: 63 | # Not a datetime object 64 | return datetime_obj 65 | 66 | if not hasattr(datetime_obj, "tzinfo") or not datetime_obj.tzinfo: 67 | datetime_obj = datetime_obj.replace(tzinfo=get_timezone()) 68 | return datetime_obj.astimezone(NepaliTimeZone()) 69 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## v1.1.3 4 | 5 | - Update months data for year 2082 6 | 7 | ## v1.1.2 (Feb 11, 2025) 8 | 9 | - Updated months total days data. 10 | 11 | ## v1.1.1 (Mar 13, 2024) 12 | 13 | - Updated dates data for 2081 14 | 15 | ## v1.1.0 (Jun 20, 2023) 16 | 17 | - Depreciated templatetags and added support from `django-nepali` package. 18 | - Updated README.md for `django-nepali` package. 19 | - Added source and changelog on the package information 20 | - Excluded tests from the package 21 | 22 | ## v1.0.1 (May 16, 2023) 23 | - Fixes on `nepalinumber` templatetags 24 | - Bug fix on Django templatetags 'nepalinumber' (was returning in en-US locale) 25 | - Added templatetag `nepali_comma` 26 | - Added templatetag `english_comma` 27 | - Fixed on `nepalidatetime` templatetags 28 | - Added `nepalidate_ne` templatetag 29 | - Added `nepalinow_en` templatetag 30 | - Added `nepalinow_ne` templatetag 31 | - Changed output locale of `nepalidate` and `nepalinow` templatetag to en-US (Changed since v1.0.0) 32 | - Handled exceptions on all templatetags 33 | 34 | ## v1.0.0 - (May 2, 2023) 35 | - Class representation (`__repr__`) added on NepaliTimeZone 36 | - Refactored date converter (Performance optimized) 37 | - Added locations feature 38 | - Added Phone number parse feature 39 | - Removed depreciated class and method 40 | - Added operator overloading on nepalidate 41 | - Changed API of the module `number` into the module function from the class method. 42 | - Added method `strftime_ne` in `nepalidate` and `nepalidatetime` class and now `strftime` returns in en-US locale 43 | - Bug fix for Django templatetags 44 | - Added `CONTRIBUTION.md` file 45 | - Created class `nepalimonth` and `nepaliweek` 46 | - Created class `nepalinumber` supporting all numeric operations 47 | 48 | ## v0.5.6 - (July 7, 2022) 49 | - Test for automatic package publish 50 | - Updated `README.md` 51 | 52 | ## v0.5.5 - (July 7, 2022) 53 | - Added github actions workflow 54 | - Updated package description and keywords 55 | - Removed `to_nepalidatetime` and added `NepaliTimezone` on `nepalidatetime.to_datetime` 56 | - Updated changelog format 57 | 58 | ## v0.5.4 - (June 29, 2022) 59 | - Fixed bug fix while parsing 32 days 60 | 61 | ## v0.5.3 - (May 29, 2022) 62 | - Fixed minor bug fix for `weekday` 63 | 64 | ## v0.5.1 - (May 12, 2022) 65 | - Bug fix on `nepalihumanize` 66 | 67 | ## v0.5.1 - (May 10, 2022) 68 | - Bug fix on `NepaliDate`, `NepaliTime`, and `NepaliDateTime` import 69 | 70 | ## v0.5.0 - (Nov 30, 2021) 71 | - Fixed minor typo. 72 | - CHANGELOG.md added. 73 | - `NepaliDate`, `NepaliTime`, `NepaliDateTime` depreciation. 74 | - Changed datetime directory 75 | - Added `strptime` 76 | - Added parser 77 | - Added Simple test case 78 | - Fixed Typo on Thursday 79 | -------------------------------------------------------------------------------- /nepali/templatetags/nepalinumber.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains django templatetags for nepali number. 3 | """ 4 | import warnings 5 | from typing import Any 6 | 7 | from django import template 8 | 9 | from nepali.number import ( 10 | add_comma, 11 | add_comma_english, 12 | convert_and_add_comma, 13 | english_to_nepali, 14 | ) 15 | 16 | register = template.Library() 17 | 18 | DEPRECIATION_WARNING_MESSAGE = ( 19 | "The templatetag 'nepalinumber' has been depreciated " 20 | "and will be removed in the future release. " 21 | "Please use `django-nepali` package." 22 | ) 23 | 24 | 25 | @register.filter(name="nepalinumber") 26 | def nepalinumber(value: Any) -> str: 27 | """ 28 | Converts the number into nepali number and renders it. 29 | 30 | Usage: 31 | ``` 32 | {{ number|nepalinumber }} 33 | ``` 34 | 35 | :param value: Number to be converted 36 | :returns: Nepali output of given number 37 | """ 38 | warnings.warn( 39 | message=DEPRECIATION_WARNING_MESSAGE, 40 | category=DeprecationWarning, 41 | ) 42 | return english_to_nepali(value) 43 | 44 | 45 | @register.filter(name="nepalinumber_with_comma") 46 | def nepalinumber_with_comma(value: Any) -> str: 47 | """ 48 | Converts the number into nepali number and renders it. 49 | 50 | Usage: 51 | ``` 52 | {{ number|nepalinumber_with_comma }} 53 | ``` 54 | Basically same as `{{ number|nepalinumber|nepali_comma }}` 55 | 56 | :param value: Number to be converted and commas added 57 | :returns: Nepali output of given number with commas 58 | """ 59 | warnings.warn( 60 | message=DEPRECIATION_WARNING_MESSAGE, 61 | category=DeprecationWarning, 62 | ) 63 | return convert_and_add_comma(value) 64 | 65 | 66 | @register.filter(name="nepali_comma") 67 | def nepali_comma(value: Any) -> str: 68 | """ 69 | Renders the given value with commas added in Nepali style without converting the number. 70 | 71 | Usage: 72 | ``` 73 | {{ number|nepali_comma }} 74 | ``` 75 | 76 | :param value: Number to be added with commas 77 | :returns: Output of given number with commas 78 | """ 79 | warnings.warn( 80 | message=DEPRECIATION_WARNING_MESSAGE, 81 | category=DeprecationWarning, 82 | ) 83 | return add_comma(value) 84 | 85 | 86 | @register.filter(name="english_comma") 87 | def english_comma(value: Any) -> str: 88 | """ 89 | Renders the given value with commas added in English style without converting the number. 90 | 91 | Usage: 92 | ``` 93 | {{ number|english_comma }} 94 | ``` 95 | 96 | :param value: Number to be added with commas 97 | :returns: Output of given number with commas 98 | """ 99 | warnings.warn( 100 | message=DEPRECIATION_WARNING_MESSAGE, 101 | category=DeprecationWarning, 102 | ) 103 | return add_comma_english(value) 104 | -------------------------------------------------------------------------------- /nepali/locations/models.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import List 3 | 4 | 5 | class Location: 6 | def __init__(self, name: str, name_nepali: str): 7 | self.__name = name 8 | self.__name_nepali = name_nepali 9 | 10 | def __str__(self): 11 | return self.name 12 | 13 | def __repr__(self): 14 | return self.name 15 | 16 | @property 17 | def name(self): 18 | return self.__name 19 | 20 | @property 21 | def name_nepali(self): 22 | return self.__name_nepali 23 | 24 | 25 | class Province(Location): 26 | def __init__(self, name: str, name_nepali: str): 27 | super().__init__(name, name_nepali) 28 | self.__districts = [] 29 | self.__municipalities = [] 30 | 31 | def _add_district(self, district: "District") -> None: 32 | """Do not use outside of the model, automatically called from the models.""" 33 | self.__districts.append(district) 34 | 35 | def _add_municipality(self, municipality: "Municipality") -> None: 36 | """Do not use outside of the model, automatically called from the models.""" 37 | self.__municipalities.append(municipality) 38 | 39 | @property 40 | def districts(self) -> List["District"]: 41 | return self.__districts 42 | 43 | @property 44 | def municipalities(self) -> List["Municipality"]: 45 | return self.__municipalities 46 | 47 | 48 | class District(Location): 49 | def __init__(self, province: Province, name: str, name_nepali: str): 50 | super().__init__(name, name_nepali) 51 | self.__province = province 52 | self.__municipalities = [] 53 | self.__province._add_district(self) 54 | 55 | def _add_municipality(self, district: "Municipality") -> None: 56 | """Do not use outside of the model, automatically called from the models.""" 57 | self.__municipalities.append(district) 58 | 59 | @property 60 | def province(self) -> Province: 61 | return self.__province 62 | 63 | @property 64 | def municipalities(self) -> List["Municipality"]: 65 | return self.__municipalities 66 | 67 | 68 | class MunicipalityType(Enum): 69 | METROPOLITAN = "Metropolitan City" 70 | SUB_METROPOLITAN = "Sub-Metropolitan City" 71 | MUNICIPALITY = "Municipality" 72 | RURAL_MUNICIPALITY = "Rural Municipality" 73 | 74 | def __str__(self) -> str: 75 | return self.value 76 | 77 | def __repr__(self) -> str: 78 | return self.value 79 | 80 | 81 | class Municipality(Location): 82 | def __init__( 83 | self, 84 | district: District, 85 | name: str, 86 | name_nepali: str, 87 | municipality_type: MunicipalityType, 88 | ): 89 | super().__init__(name, name_nepali) 90 | self.__district = district 91 | self.__municipality_type = municipality_type 92 | self.__district._add_municipality(self) 93 | self.__district.province._add_municipality(self) 94 | 95 | @property 96 | def province(self) -> Province: 97 | return self.district.province 98 | 99 | @property 100 | def district(self) -> District: 101 | return self.__district 102 | 103 | @property 104 | def municipality_type(self) -> MunicipalityType: 105 | return self.__municipality_type 106 | -------------------------------------------------------------------------------- /nepali/char.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | from . import number 4 | from .datetime._nepalimonth import nepalimonth 5 | from .datetime._nepaliweek import nepaliweek 6 | 7 | MONTHS_MAP = { 8 | "बैशाख": "Baishakh", 9 | "जेठ": "Jestha", 10 | "असार": "Ashad", 11 | "साउन": "Sharwan", 12 | "भदौ": "Bhadra", 13 | "असोज": "Ashwin", 14 | "कात्तिक": "Kartik", 15 | "मंसिर": "Mangsir", 16 | "पुस": "Poush", 17 | "माघ": "Magh", 18 | "फागुन": "Falgun", 19 | "चैत": "Chaitra", 20 | } 21 | 22 | DAYS_MAP = { 23 | "आइतबार": "Sunday", 24 | "सोमबार": "Monday", 25 | "मंगलबार": "Tuesday", 26 | "बुधबार": "Wednesday", 27 | "बिहीबार": "Thursday", 28 | "शुक्रबार": "Friday", 29 | "शनिबार": "Saturday", 30 | } 31 | 32 | HALF_DAYS_MAP = { 33 | "आइत": "Sun", 34 | "सोम": "Mon", 35 | "मंगल": "Tue", 36 | "बुध": "Wed", 37 | "बिही": "Thu", 38 | "शुक्र": "Fri", 39 | "शनि": "Sat", 40 | } 41 | 42 | AM_PM_MAP = { 43 | "शुभप्रभात": "AM", 44 | "मध्यान्ह": "PM", 45 | "अपरान्ह": "PM", 46 | } 47 | 48 | 49 | class NepaliChar: 50 | @staticmethod 51 | def number(num): 52 | warnings.warn( 53 | message="NepaliChar.number has been depreciated and will be removed in the future release.", 54 | category=DeprecationWarning, 55 | ) 56 | return number.english_to_nepali(num) 57 | 58 | @staticmethod 59 | def day(day): 60 | warnings.warn( 61 | message="NepaliChar.day has been depreciated and will be removed in the future release.", 62 | category=DeprecationWarning, 63 | ) 64 | return nepaliweek(day).name_ne 65 | 66 | @staticmethod 67 | def half_day(day): 68 | warnings.warn( 69 | message="NepaliChar.half_day has been depreciated and will be removed in the future release.", 70 | category=DeprecationWarning, 71 | ) 72 | return nepaliweek(day).abbr_ne 73 | 74 | @staticmethod 75 | def month(month): 76 | warnings.warn( 77 | message="NepaliChar.month has been depreciated and will be removed in the future release.", 78 | category=DeprecationWarning, 79 | ) 80 | return nepalimonth(month).name_ne 81 | 82 | 83 | class EnglishChar: 84 | @staticmethod 85 | def day(day): 86 | warnings.warn( 87 | message="EnglishChar.day has been depreciated and will be removed in the future release.", 88 | category=DeprecationWarning, 89 | ) 90 | return nepaliweek(day).name 91 | 92 | @staticmethod 93 | def half_day(day): 94 | warnings.warn( 95 | message="EnglishChar.half_day has been depreciated and will be removed in the future release.", 96 | category=DeprecationWarning, 97 | ) 98 | return nepaliweek(day).abbr 99 | 100 | @staticmethod 101 | def month(month): 102 | warnings.warn( 103 | message="EnglishChar.month has been depreciated and will be removed in the future release.", 104 | category=DeprecationWarning, 105 | ) 106 | return nepalimonth(month).name 107 | 108 | 109 | def nepali_to_english_text(text): 110 | # TODO: optimization 111 | 112 | # replacing months 113 | for k, v in MONTHS_MAP.items(): 114 | text = text.replace(k, v) 115 | 116 | # replacing days 117 | for k, v in DAYS_MAP.items(): 118 | text = text.replace(k, v) 119 | 120 | # replacing half days 121 | for k, v in HALF_DAYS_MAP.items(): 122 | text = text.replace(k, v) 123 | 124 | for k, v in AM_PM_MAP.items(): 125 | text = text.replace(k, v) 126 | 127 | return number.nepali_to_english(text) 128 | -------------------------------------------------------------------------------- /nepali/datetime/_nepalimonth.py: -------------------------------------------------------------------------------- 1 | from functools import cached_property 2 | from typing import Any, Dict, Optional, Union 3 | 4 | from nepali.constants import NEPALI_MONTHS_EN, NEPALI_MONTHS_NE 5 | 6 | 7 | class NepaliMonthMeta(type): 8 | _cache: Dict[int, "nepalimonth"] = {} 9 | 10 | def __call__(cls, month: Union[int, str], *args, **kwargs) -> "nepalimonth": 11 | """ 12 | Parses the month data and manages the cache. 13 | 14 | :param month: An integer or string representing the month. 15 | :type month: Union[int, str] 16 | :return: An instance of the nepalimonth class representing the given month. 17 | :rtype: nepalimonth 18 | :raises ValueError: If the given month is invalid. 19 | """ 20 | value: Optional[int] = None 21 | value = None 22 | 23 | if isinstance(month, int): 24 | value = int(month) 25 | elif isinstance(month, str): 26 | try: 27 | value = cls._parse_str(month) 28 | except ValueError: 29 | pass 30 | 31 | # checking if month is valid 32 | if value is None or value < 1 or value > 12: 33 | raise ValueError(f"Invalid month: {month}") 34 | 35 | # checking cache 36 | if value not in cls._cache: 37 | cls._cache[value] = super(NepaliMonthMeta, cls).__call__( 38 | value, *args, **kwargs 39 | ) 40 | 41 | return cls._cache[value] 42 | 43 | @staticmethod 44 | def _parse_str(month: str) -> int: 45 | """ 46 | Parses str value of the month and returns int. 47 | 48 | :param month: A string representing the month. 49 | :type month: str 50 | :return: An integer representing the month. 51 | :rtype: int 52 | :raises ValueError: If the given string does not represent a valid month. 53 | """ 54 | if month.isdigit(): 55 | return int(month) 56 | 57 | month = month.capitalize() 58 | month_names = NEPALI_MONTHS_EN + NEPALI_MONTHS_NE 59 | try: 60 | index = month_names.index(month) 61 | except ValueError: 62 | raise ValueError(f"Invalid month name: {month}") 63 | 64 | return (index % 12) + 1 65 | 66 | 67 | class nepalimonth(metaclass=NepaliMonthMeta): 68 | """ 69 | Represents Nepali month: Baishakh, Jestha, ..., Chaitra. 70 | Baishakh: 1, 71 | Jestha: 2, 72 | ... 73 | Chaitra: 12 74 | 75 | >>> nepalimonth(1) 76 | >>> nepalimonth("Baishakh") 77 | >>> nepalimonth("बैशाख") 78 | 79 | :param Union[int, str] month: Month data to be parsed. 80 | 81 | :raises ValueError: The value is invalid. 82 | """ 83 | 84 | def __init__(self, month) -> None: 85 | self.__value = month 86 | 87 | def __str__(self) -> str: 88 | return self.name 89 | 90 | def __repr__(self) -> str: 91 | return f"" 92 | 93 | def __int__(self) -> int: 94 | return self.value 95 | 96 | def __eq__(self, other: Any) -> bool: 97 | if isinstance(other, nepalimonth): 98 | return self.value == other.value 99 | elif isinstance(other, int): 100 | return self.value == other 101 | 102 | return False 103 | 104 | @cached_property 105 | def value(self) -> int: 106 | return self.__value 107 | 108 | @cached_property 109 | def name(self) -> str: 110 | """Month's english name""" 111 | return NEPALI_MONTHS_EN[self.__value - 1] 112 | 113 | @cached_property 114 | def name_ne(self) -> str: 115 | """Month's nepali name""" 116 | return NEPALI_MONTHS_NE[self.__value - 1] 117 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | The 'py-nepali' project is committed to maintaining the security and integrity of our software. This Security Policy outlines the steps we take to address security vulnerabilities, as well as the responsibilities of our contributors and users in reporting and resolving security-related issues. 4 | 5 | ## Supported Versions 6 | 7 | We provide security updates and support for the latest stable release of the 'py-nepali' package. It is important to keep your software up to date to ensure you have the latest security patches. 8 | 9 | | Version | Supported | 10 | | -------- | ------------------ | 11 | | >= 1.0.0 | :white_check_mark: | 12 | | 0.5.6 | :white_check_mark: | 13 | | < 0.5.6 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | If you discover a security vulnerability within the project, we encourage you to report it the maintainers immediately. Promptly reporting security issues helps us protect our users and take appropriate actions to address the vulnerability. To report a security vulnerability, please follow these steps: 18 | 19 | 1. Email at aj3sshh@gmail.com or sugatbajracharya49@gmail.com with a detailed description of the vulnerability. Please include the following information: 20 | 21 | - Description of the vulnerability, including steps to reproduce if applicable 22 | - Version of the py-nepali package affected 23 | - Any potential mitigations or workarounds 24 | 25 | 2. Our security team will acknowledge your report within 3 to 7 days and provide further instructions and communication regarding the vulnerability. 26 | 27 | 3. We kindly request that you do not publicly disclose the vulnerability until we have had sufficient time to address it and provide a fix. We strive to resolve security vulnerabilities promptly and will work with you to coordinate disclosure if necessary. 28 | 29 | 4. Once the vulnerability is confirmed and addressed, we will release a security update in the form of a new package version. We will credit the reporter if desired. 30 | 31 | ## Security Updates and Mitigations 32 | 33 | Upon receiving a security vulnerability report, our team will evaluate the issue and take appropriate actions to address it. This may include: 34 | 35 | - Developing and testing a patch or fix for the vulnerability 36 | - Coordinating with the reporter or other relevant parties to verify the vulnerability and its impact 37 | - Releasing a new version that includes the necessary security fixes 38 | 39 | We strive to provide security updates in a timely manner and communicate any necessary steps for users to upgrade to the latest secure version. 40 | 41 | ## Responsible Disclosure 42 | 43 | We are committed to responsible disclosure of security vulnerabilities. Once a vulnerability has been addressed and a new package version is released, we encourage users and contributors to upgrade to the latest version to ensure they are protected. 44 | 45 | While we appreciate the efforts of security researchers and users who report vulnerabilities, we kindly request that you follow responsible disclosure practices by allowing us sufficient time to address the issue and release a fix before disclosing the vulnerability publicly. 46 | 47 | ## Disclaimer 48 | 49 | The py-nepali project is provided "as is," without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the package maintainers or contributors be liable for any claim, damages, or other liability arising from the use of the nepali project or any security-related incidents. 50 | 51 | ## Contact 52 | 53 | For any questions, concerns, or additional information regarding the nepali project's security policy, please contact us at aj3sshh@gmail.com or sugatbajracharya49@gmail.com. 54 | -------------------------------------------------------------------------------- /nepali/tests/test_humanize.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import unittest 3 | from unittest.mock import patch 4 | 5 | from nepali.datetime import HumanizeDateTime, nepalidate, nepalidatetime, nepalihumanize 6 | from nepali.exceptions import InvalidNepaliDateTimeObjectException 7 | from nepali.timezone import NepaliTimeZone 8 | 9 | REF_TIME = datetime.datetime(2015, 1, 1, 10, 15, tzinfo=NepaliTimeZone()) 10 | 11 | 12 | @patch("nepali.datetime._humanize.now", return_value=REF_TIME) 13 | class TestNepaliHumanizeFunction(unittest.TestCase): 14 | def test_nepalihumanize_for_just_now(self, *_): 15 | humanize_str = nepalihumanize(REF_TIME) 16 | self.assertEqual(humanize_str, "भर्खरै") 17 | 18 | def test_nepalihumanize_for_seconds(self, *_): 19 | humanize_str = nepalihumanize(REF_TIME - datetime.timedelta(seconds=10)) 20 | self.assertEqual(humanize_str, "१० सेकेन्ड अघि") 21 | 22 | def test_nepalihumanize_for_minutes(self, *_): 23 | humanize_str = nepalihumanize(REF_TIME - datetime.timedelta(minutes=10)) 24 | self.assertEqual(humanize_str, "१० मिनेट अघि") 25 | 26 | def test_nepalihumanize_for_hours(self, *_): 27 | humanize_str = nepalihumanize(REF_TIME - datetime.timedelta(hours=10)) 28 | self.assertEqual(humanize_str, "१० घण्टा अघि") 29 | 30 | def test_nepalihumanize_for_days(self, *_): 31 | humanize_str = nepalihumanize(REF_TIME - datetime.timedelta(days=10)) 32 | self.assertEqual(humanize_str, "१० दिन अघि") 33 | 34 | def test_nepalihumanize_for_months(self, *_): 35 | humanize_str = nepalihumanize(REF_TIME - datetime.timedelta(days=32)) 36 | self.assertEqual(humanize_str, "१ महिना अघि") 37 | 38 | def test_nepalihumanize_for_years(self, *_): 39 | humanize_str = nepalihumanize(REF_TIME - datetime.timedelta(days=366)) 40 | self.assertEqual(humanize_str, "१ वर्ष अघि") 41 | 42 | def test_nepalihumanize_for_future(self, *_): 43 | humanize_str = nepalihumanize(REF_TIME + datetime.timedelta(days=10)) 44 | self.assertEqual(humanize_str, "१० दिन पछि") 45 | 46 | def test_nepalihumanize_returns_date_after_threshold(self, *_): 47 | humanize_str = nepalihumanize( 48 | REF_TIME - datetime.timedelta(seconds=10), threshold=10 49 | ) 50 | self.assertEqual(humanize_str, "पुस १७, २०७१") 51 | 52 | 53 | class TestHumanizeDateTimeClass(unittest.TestCase): 54 | def test_nepali_humanize_raise_exception_on_invalid_input(self, *_): 55 | with self.assertRaises(InvalidNepaliDateTimeObjectException): 56 | HumanizeDateTime("test") 57 | 58 | with self.assertRaises(InvalidNepaliDateTimeObjectException): 59 | HumanizeDateTime(None) 60 | 61 | with self.assertRaises(InvalidNepaliDateTimeObjectException): 62 | HumanizeDateTime("2017-12-04") 63 | 64 | def test_nepali_humanize_supports_python_date(self, *_): 65 | humanize_obj = HumanizeDateTime(datetime.date.today()) 66 | self.assertEqual(type(humanize_obj.to_str()), str) 67 | 68 | def test_nepali_humanize_supports_python_datetime(self, *_): 69 | humanize_obj = HumanizeDateTime(datetime.datetime.now()) 70 | self.assertEqual(type(humanize_obj.to_str()), str) 71 | 72 | def test_nepali_humanize_supports_nepalidate(self, *_): 73 | humanize_obj = HumanizeDateTime(nepalidate.today()) 74 | self.assertEqual(type(humanize_obj.to_str()), str) 75 | 76 | def test_nepali_humanize_supports_nepalidatetime(self, *_): 77 | humanize_obj = HumanizeDateTime(nepalidatetime.now()) 78 | self.assertEqual(type(humanize_obj.to_str()), str) 79 | 80 | @patch("nepali.datetime._humanize.now", return_value=REF_TIME) 81 | def test_test_nepali_humanize_str_and_repr(self, *_): 82 | humanize_obj = HumanizeDateTime(REF_TIME + datetime.timedelta(days=10)) 83 | self.assertEqual(str(humanize_obj), "१० दिन पछि") 84 | self.assertEqual(repr(humanize_obj), "१० दिन पछि") 85 | -------------------------------------------------------------------------------- /nepali/datetime/_nepaliweek.py: -------------------------------------------------------------------------------- 1 | from functools import cached_property 2 | from typing import Any, Dict, Optional, Union 3 | 4 | from nepali.constants import WEEKS_ABBR_EN, WEEKS_ABBR_NE, WEEKS_EN, WEEKS_NE 5 | 6 | 7 | class NepaliWeekMeta(type): 8 | _cache: Dict[int, "nepaliweek"] = {} 9 | 10 | def __call__(cls, week: Union[int, str], *args, **kwargs) -> "nepaliweek": 11 | """ 12 | Parses the week data and manages the cache. 13 | 14 | :param week: An integer or string representing the week. 15 | :type week: Union[int, str] 16 | :return: An instance of the nepaliweek class representing the given week. 17 | :rtype: nepaliweek 18 | :raises ValueError: If the given week is invalid. 19 | """ 20 | value: Optional[int] = None 21 | 22 | if isinstance(week, int): 23 | value = int(week) 24 | elif isinstance(week, str): 25 | try: 26 | value = cls._parse_str(week) 27 | except ValueError: 28 | pass 29 | 30 | # checking if week is valid 31 | if value is None or not 0 <= value <= 6: 32 | raise ValueError(f"Invalid week: {week}") 33 | 34 | # checking cache 35 | if value not in cls._cache: 36 | cls._cache[value] = super().__call__(value, *args, **kwargs) 37 | 38 | return cls._cache[value] 39 | 40 | @staticmethod 41 | def _parse_str(week: str) -> int: 42 | """ 43 | Parses str value of the week and returns int. 44 | 45 | :param week: A string representing the week. 46 | :type week: str 47 | :return: An integer representing the week. 48 | :rtype: int 49 | :raises ValueError: If the given string does not represent a valid week. 50 | """ 51 | if week.isdigit(): 52 | return int(week) 53 | 54 | week = week.capitalize() 55 | week_names = WEEKS_EN + WEEKS_NE + WEEKS_ABBR_EN + WEEKS_ABBR_NE 56 | try: 57 | index = week_names.index(week) 58 | except ValueError: 59 | raise ValueError(f"Invalid week name: {week}") 60 | 61 | return index % 7 62 | 63 | 64 | class nepaliweek(metaclass=NepaliWeekMeta): 65 | """ 66 | Represents Nepali week: Sunday, Monday, ..., Saturday. 67 | Sunday: 0, 68 | Monday: 1, 69 | ... 70 | Saturday: 6 71 | 72 | >>> nepaliweek(0) 73 | >>> nepaliweek("Sunday") 74 | >>> nepaliweek("आइतबार") 75 | >>> nepaliweek("Sun") 76 | 77 | :param Union[int, str] week: Week data to be parsed. 78 | :raises ValueError: The value is invalid. 79 | """ 80 | 81 | def __init__(self, week) -> None: 82 | self.__value = week 83 | 84 | def __str__(self) -> str: 85 | return self.name 86 | 87 | def __repr__(self) -> str: 88 | return f"" 89 | 90 | def __int__(self) -> int: 91 | return self.value 92 | 93 | def __eq__(self, other: Any) -> bool: 94 | if isinstance(other, nepaliweek): 95 | return self.value == other.value 96 | elif isinstance(other, int): 97 | return self.value == other 98 | 99 | return False 100 | 101 | @cached_property 102 | def value(self) -> int: 103 | return self.__value 104 | 105 | @cached_property 106 | def name(self) -> str: 107 | """Week's english name""" 108 | return WEEKS_EN[self.__value] 109 | 110 | @cached_property 111 | def abbr(self) -> str: 112 | """Week's english abbreviated name""" 113 | return WEEKS_ABBR_EN[self.__value] 114 | 115 | @cached_property 116 | def name_ne(self) -> str: 117 | """Week's nepali name""" 118 | return WEEKS_NE[self.__value] 119 | 120 | @cached_property 121 | def abbr_ne(self) -> str: 122 | """Week's nepali abbreviated name""" 123 | return WEEKS_ABBR_NE[self.__value] 124 | -------------------------------------------------------------------------------- /nepali/datetime/_humanize.py: -------------------------------------------------------------------------------- 1 | from nepali import number 2 | from nepali.timezone import now 3 | 4 | from .utils import to_nepalidatetime 5 | 6 | 7 | class HumanizeDateTime: 8 | """ 9 | HumanizeDate converts NepaliDateTime to nepali human readable form 10 | """ 11 | 12 | __past_text = "अघि" 13 | __future_text = "पछि" 14 | __now_text = "भर्खरै" 15 | __year_text = "वर्ष" 16 | __month_text = "महिना" 17 | __day_text = "दिन" 18 | __hour_text = "घण्टा" 19 | __minute_text = "मिनेट" 20 | __second_text = "सेकेन्ड" 21 | 22 | def __init__(self, datetime_obj, **kwargs): 23 | """ 24 | initializes humanize class 25 | datetime_obj: python datetime object to be humanized 26 | threshold (kwargs): threshold to be humanize 27 | format (kwargs): format to display behind threshold 28 | """ 29 | self.datetime_obj = to_nepalidatetime(datetime_obj) 30 | 31 | self.threshold = kwargs.get("threshold") 32 | self.format = kwargs.get("format") 33 | 34 | # seconds after from now to datetime_obj 35 | self.seconds = 0 36 | 37 | def __calc_seconds(self): 38 | """calculates total seconds from now""" 39 | current_date_time = now() 40 | 41 | # TODO (@aj3sh): support datetime - nepalidatetime 42 | self.seconds = int( 43 | (current_date_time - self.datetime_obj.to_datetime()).total_seconds() 44 | ) 45 | 46 | self.interval_tense = self.__past_text 47 | if self.seconds < 0: 48 | self.seconds *= -1 49 | self.interval_tense = self.__future_text 50 | 51 | return self.seconds 52 | 53 | def to_str(self): 54 | """returns humanize string""" 55 | seconds = self.__calc_seconds() # calculating seconds 56 | 57 | if self.threshold is not None and seconds >= self.threshold: 58 | return self._get_datetime_str().strip() 59 | 60 | return self._get_humanize_str().strip() 61 | 62 | def _get_humanize_str(self): 63 | """ 64 | returns humanize datetime 65 | """ 66 | interval_value = 0 67 | interval_text = "" 68 | if self.seconds == 0: 69 | # now 70 | return self.__now_text 71 | 72 | elif self.seconds < 60: 73 | # seconds 74 | interval_value = self.seconds 75 | interval_text = self.__second_text 76 | 77 | elif self.seconds < 3600: 78 | # minute 79 | interval_value = self.seconds // 60 80 | interval_text = self.__minute_text 81 | 82 | elif self.seconds < 86400: 83 | # hour 84 | interval_value = self.seconds // 3600 85 | interval_text = self.__hour_text 86 | 87 | elif self.seconds < 2764800: 88 | # day 89 | interval_value = self.seconds // 86400 90 | interval_text = self.__day_text 91 | 92 | elif self.seconds < 31622400: 93 | # month 94 | interval_value = self.seconds // 2764800 95 | interval_text = self.__month_text 96 | 97 | else: 98 | # year 99 | interval_value = self.seconds // 31622400 100 | interval_text = self.__year_text 101 | 102 | interval_value = number.english_to_nepali(interval_value) 103 | return ( 104 | str(interval_value) + " " + str(interval_text) + " " + self.interval_tense 105 | ) 106 | 107 | def _get_datetime_str(self): 108 | """ 109 | returns date in nepali characters 110 | """ 111 | if not self.format: 112 | self.format = "%B %d, %Y" 113 | return self.datetime_obj.strftime_ne(self.format) 114 | 115 | def __str__(self): 116 | return self.to_str() 117 | 118 | def __repr__(self): 119 | return str(self) 120 | 121 | 122 | def nepalihumanize(datetime_obj, threshold=None, format=None): 123 | """returns to humanize nepalidatetime""" 124 | return HumanizeDateTime(datetime_obj, threshold=threshold, format=format).to_str() 125 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Introduction 4 | 5 | This document outlines the code of conduct for contributors, maintainers, and users of the "py-nepali" project. It aims to foster an inclusive and respectful community, promoting collaboration and positive interactions. By participating in this project, you are expected to adhere to this code of conduct. This code of conduct applies to all project-related spaces, including GitHub repositories, issue trackers, mailing lists, and social media platforms associated with the project. 6 | 7 | ## Our Pledge 8 | 9 | In the interest of fostering an open and welcoming environment, we, as contributors, maintainers, and users of the py-nepali project, pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual orientation. 10 | 11 | ## Expected Behavior 12 | 13 | To create a positive and inclusive environment, we expect all participants to: 14 | 15 | 1. Be respectful and considerate: Treat others with kindness, respect, and empathy, fostering a welcoming and collaborative atmosphere. 16 | 2. Be inclusive and supportive: Welcome individuals from all backgrounds, experiences, and perspectives, ensuring everyone feels valued and heard. 17 | 3. Be mindful of language and tone: Use inclusive language and avoid offensive or derogatory comments or personal attacks. 18 | 4. Be constructive: Provide constructive feedback, suggestions, and criticism in a respectful manner, with the aim of improving the project and fostering growth. 19 | 5. Be open-minded: Embrace diverse ideas, viewpoints, and approaches, encouraging healthy discussions and debates. 20 | 6. Be accountable: Take responsibility for your actions, acknowledge and learn from mistakes, and avoid repeating harmful behavior. 21 | 7. Respect project guidelines: Adhere to the project's technical guidelines, contribution guidelines, and licensing requirements. 22 | 23 | ## Unacceptable Behavior 24 | 25 | The following behaviors are considered unacceptable and will not be tolerated within the community: 26 | 27 | 1. Harassment and discrimination: Engaging in any form of harassment, discrimination, or exclusionary behavior, including but not limited to offensive comments, slurs, insults, derogatory remarks, or personal attacks. 28 | 2. Intimidation and threats: Intimidating or threatening behavior, both online and offline, towards any participant in the community. 29 | 3. Unwelcome advances: Making unwelcome sexual advances, inappropriate comments, or any other behavior of a sexual nature. 30 | 4. Spam and trolling: Posting irrelevant, repetitive, or disruptive content with the intention of annoying or provoking others. 31 | 5. Violation of privacy: Sharing personal or confidential information of others without their explicit consent. 32 | 6. Plagiarism and intellectual property infringement: Claiming the work of others as your own, including copying code or content without proper attribution or permission. 33 | 7. Violation of project guidelines: Repeatedly disregarding the project's technical guidelines, contribution guidelines, or licensing requirements. 34 | 35 | ## Reporting and Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project maintainers at aj3sshh@gmail.com or sugatbajracharya49@gmail.com. All complaints will be reviewed and investigated promptly and fairly. The project maintainers are responsible for maintaining the confidentiality of the reporting individual(s). 38 | 39 | In the case of a verified violation of this code of conduct, appropriate actions will be taken, which may include: 40 | 41 | 1. Educating the individual(s) involved about the code of conduct and providing guidance on acceptable behavior. 42 | 2. Issuing warnings or temporary bans from project-related spaces. 43 | 3. Implementing permanent bans from project-related spaces for repeated or severe violations. 44 | 4. Reporting illegal behavior to relevant authorities if necessary. 45 | 46 | ## Attribution 47 | 48 | This code of conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html) and [Mozilla Community Participation Guidelines](https://www.mozilla.org/en-US/about/governance/policies/participation/). It is licensed under the Creative Commons Attribution 4.0 International License 49 | -------------------------------------------------------------------------------- /nepali/phone_number.py: -------------------------------------------------------------------------------- 1 | import re 2 | from enum import Enum 3 | from typing import Union 4 | 5 | _mobile_number_re = re.compile(r"^(?:\+977|977)?(?:-)?(?:98|97|96)\d{8}$") 6 | _landline_number_re = re.compile( 7 | r"^(?:\+977|977)?(?:-)?(?:0)?(?:[01][1-9]|2[13-9]|[3-9]\d)\d{6,7}$" 8 | ) 9 | 10 | 11 | class Operator(Enum): 12 | NEPAL_TELECOM = "Nepal Telecom" 13 | NCELL = "Ncell" 14 | SMART_CELL = "Smart Cell" 15 | UTL = "UTL" 16 | HELLO_MOBILE = "Hello Mobile" 17 | 18 | def __str__(self) -> str: 19 | return self.value 20 | 21 | def __repr__(self) -> str: 22 | return f"" 23 | 24 | 25 | def is_mobile_number(number: str) -> bool: 26 | """ 27 | Returns True is the input number is mobile number. 28 | 29 | >>> is_mobile = is_mobile_number(number) 30 | >>> if is_mobile: 31 | >>> ... 32 | """ 33 | try: 34 | return bool(_mobile_number_re.match(number)) 35 | except Exception: 36 | return False 37 | 38 | 39 | def is_landline_number(number: str) -> bool: 40 | """ 41 | Returns True is the input number is mobile number. 42 | 43 | >>> is_mobile = is_mobile_number(number) 44 | >>> if is_mobile: 45 | >>> ... 46 | """ 47 | try: 48 | return bool(_landline_number_re.match(number)) 49 | except Exception: 50 | return False 51 | 52 | 53 | def is_valid(number: str) -> bool: 54 | return is_mobile_number(number) or is_landline_number(number) 55 | 56 | 57 | def get_exact_number(number: str) -> str: 58 | # replacing start 977 59 | if number.startswith("977"): 60 | number = number.replace("977", "") 61 | # replacing +977 and all - 62 | return number.replace("+977", "").replace("-", "") 63 | 64 | 65 | def parse(number: str): 66 | """ 67 | Parse and returns the details of the phone number: `dict`. 68 | The return data may vary between mobile and landline numbers. 69 | If the number is invalid it returns `None`. 70 | 71 | If you want to make sure you get a valid response, please use 72 | `is_valid`, `is_mobile_number`, and `is_landline_number`. 73 | 74 | :return: 75 | { 76 | "type": "Mobile" | "Landline", 77 | "number": "XXXXXXX", 78 | ... 79 | } 80 | """ 81 | if not number and type(number) != str: 82 | return None 83 | 84 | number = number.replace("-", "") 85 | 86 | # checking if mobile number 87 | if is_mobile_number(number): 88 | return _parse_mobile_number(number) 89 | 90 | if is_landline_number(number): 91 | return _parse_landline_number(number) 92 | 93 | return None 94 | 95 | 96 | def _get_operator(number: str) -> Union[Operator, None]: 97 | """ 98 | Returns operator from the number. 99 | NOTE: The number should be 10digit mobile number. 100 | """ 101 | starting_number = number[:3] 102 | 103 | # NTC 104 | if starting_number in ["984", "985", "986", "974", "975"]: 105 | return Operator.NEPAL_TELECOM 106 | 107 | # NCELL 108 | if starting_number in ["980", "981", "982"]: 109 | return Operator.NCELL 110 | 111 | # Smart Cell 112 | if starting_number in ["961", "962", "988"]: 113 | return Operator.SMART_CELL 114 | 115 | # UTL 116 | if starting_number == "972": 117 | return Operator.UTL 118 | 119 | # Hello Mobile 120 | if starting_number == "963": 121 | return Operator.HELLO_MOBILE 122 | 123 | return None 124 | 125 | 126 | def _parse_mobile_number(number: str): 127 | """ 128 | Parse and returns mobile number details. 129 | :return: 130 | { 131 | "type": "Mobile", 132 | "number": "98XXXXXXXX", 133 | "operator": 134 | } 135 | """ 136 | number = get_exact_number(number) 137 | operator = _get_operator(number) 138 | 139 | if not operator: 140 | return None 141 | 142 | detail = { 143 | "type": "Mobile", 144 | "number": number, 145 | "operator": operator, 146 | } 147 | return detail 148 | 149 | 150 | def _get_area_code(number) -> str: 151 | """ 152 | Returns area code of the number. 153 | NOTE: The number should be landline number without +977/977. 154 | """ 155 | code = number[:3] 156 | 157 | # Kathmandu, Lalitpur, and Bhaktapur => 01 158 | if number.startswith("01") and code not in ["010", "011", "019"]: 159 | return "01" 160 | 161 | return code 162 | 163 | 164 | def _parse_landline_number(number) -> dict: 165 | """ 166 | Parse and returns mobile number details. 167 | :return: 168 | { 169 | "type": "Landline", 170 | "number": "98XXXXXXXX", 171 | "operator": 172 | } 173 | """ 174 | number = get_exact_number(number) 175 | 176 | # adding zero 177 | if number[0] != "0": 178 | number = f"0{number}" 179 | 180 | return { 181 | "type": "Landline", 182 | "number": number, 183 | "area_code": _get_area_code(number), 184 | } 185 | -------------------------------------------------------------------------------- /nepali/tests/test_date_converter.py: -------------------------------------------------------------------------------- 1 | """ 2 | To run only this unit test use the command below. 3 | 4 | python -m unittest nepali/tests/test_date_converter.py -v 5 | """ 6 | import unittest 7 | 8 | from nepali.date_converter import converter 9 | 10 | 11 | class TestNepaliDateConverter(unittest.TestCase): 12 | """tests for date_converter.converter module""" 13 | # test date range 14 | def test_english_date_range(self): 15 | self.assertEqual(converter.en_min_year(), 1944) 16 | self.assertEqual(converter.en_max_year(), 2042) 17 | 18 | def test_nepali_date_range(self): 19 | self.assertEqual(converter.np_min_year(), 2000) 20 | self.assertEqual(converter.np_max_year(), 2099) 21 | 22 | # english_to_nepali 23 | def test_converter_english_to_nepali_raise_exception_on_max_year_range(self): 24 | with self.assertRaises(Exception): 25 | converter.english_to_nepali(2060, 1, 4) 26 | 27 | def test_converter_english_to_nepali_raise_exception_on_min_year_range(self): 28 | with self.assertRaises(Exception): 29 | converter.english_to_nepali(1920, 1, 4) 30 | 31 | def test_converter_english_to_nepali_raise_exception_on_min_month_range(self): 32 | with self.assertRaises(Exception): 33 | converter.english_to_nepali(2023, 0, 4) 34 | 35 | def test_converter_english_to_nepali_raise_exception_on_max_month_range(self): 36 | with self.assertRaises(Exception): 37 | converter.english_to_nepali(2023, 13, 4) 38 | 39 | def test_converter_english_to_nepali_raise_exception_on_min_day_range(self): 40 | with self.assertRaises(Exception): 41 | converter.english_to_nepali(2023, 1, 0) 42 | 43 | def test_converter_english_to_nepali_raise_exception_on_max_day_range(self): 44 | with self.assertRaises(Exception): 45 | converter.english_to_nepali(2023, 1, 40) 46 | 47 | def test_converter_english_to_nepali_returns_valid_past_nepali_date(self): 48 | y, m, d = converter.english_to_nepali(1994, 8, 13) 49 | self.assertEqual(y, 2051) 50 | self.assertEqual(m, 4) 51 | self.assertEqual(d, 29) 52 | 53 | def test_converter_english_to_nepali_returns_valid_past_nepali_date2(self): 54 | y, m, d = converter.english_to_nepali(1944, 5, 28) 55 | self.assertEqual(y, 2001) 56 | self.assertEqual(m, 2) 57 | self.assertEqual(d, 15) 58 | 59 | def test_converter_english_to_nepali_returns_valid_recent_nepali_date(self): 60 | y, m, d = converter.english_to_nepali(2023, 1, 28) 61 | self.assertEqual(y, 2079) 62 | self.assertEqual(m, 10) 63 | self.assertEqual(d, 14) 64 | 65 | def test_converter_english_to_nepali_returns_valid_future_nepali_date(self): 66 | y, m, d = converter.english_to_nepali(2030, 11, 26) 67 | self.assertEqual(y, 2087) 68 | self.assertEqual(m, 8) 69 | self.assertEqual(d, 10) 70 | 71 | def test_converter_english_to_nepali_for_min_edge_date(self): 72 | y, m, d = converter.english_to_nepali(1944, 1, 1) 73 | self.assertEqual(y, 2000) 74 | self.assertEqual(m, 9) 75 | self.assertEqual(d, 17) 76 | 77 | def test_converter_english_to_nepali_for_max_edge_date(self): 78 | y, m, d = converter.english_to_nepali(2042, 12, 31) 79 | self.assertEqual(y, 2099) 80 | self.assertEqual(m, 9) 81 | self.assertEqual(d, 16) 82 | 83 | # nepali_to_english 84 | def test_converter_nepali_to_englishReturnErrorOnMaxYearRange(self): 85 | with self.assertRaises(Exception): 86 | converter.nepali_to_english(3000, 1, 4) 87 | 88 | def test_converter_nepali_to_english_raise_exception_on_min_year_range(self): 89 | with self.assertRaises(Exception): 90 | converter.nepali_to_english(1920, 1, 4) 91 | 92 | def test_converter_nepali_to_english_raise_exception_on_min_month_range(self): 93 | with self.assertRaises(Exception): 94 | converter.nepali_to_english(2079, 0, 4) 95 | 96 | def test_converter_nepali_to_english_raise_exception_on_max_month_range(self): 97 | with self.assertRaises(Exception): 98 | converter.nepali_to_english(2079, 13, 4) 99 | 100 | def test_converter_nepali_to_english_raise_exception_on_min_day_range(self): 101 | with self.assertRaises(Exception): 102 | converter.nepali_to_english(2079, 1, 0) 103 | 104 | def test_converter_nepali_to_english_raise_exception_on_max_day_range(self): 105 | with self.assertRaises(Exception): 106 | converter.nepali_to_english(2079, 1, 40) 107 | 108 | def test_converter_nepali_to_english_returns_valid_past_english_date(self): 109 | y, m, d = converter.nepali_to_english(2051, 4, 29) 110 | self.assertEqual(y, 1994) 111 | self.assertEqual(m, 8) 112 | self.assertEqual(d, 13) 113 | 114 | def test_converter_nepali_to_english_returns_valid_past_english_date2(self): 115 | y, m, d = converter.nepali_to_english(2001, 2, 15) 116 | self.assertEqual(y, 1944) 117 | self.assertEqual(m, 5) 118 | self.assertEqual(d, 28) 119 | 120 | def test_converter_nepali_to_english_returns_valid_recent_english_date(self): 121 | y, m, d = converter.nepali_to_english(2079, 10, 14) 122 | self.assertEqual(y, 2023) 123 | self.assertEqual(m, 1) 124 | self.assertEqual(d, 28) 125 | 126 | def test_converter_nepali_to_english_returns_valid_future_english_date(self): 127 | y, m, d = converter.nepali_to_english(2087, 8, 10) 128 | self.assertEqual(y, 2030) 129 | self.assertEqual(m, 11) 130 | self.assertEqual(d, 26) 131 | 132 | def test_converter_nepali_to_english_returns_valid_english_leap_year_date(self): 133 | y, m, d = converter.nepali_to_english(2080, 12, 15) 134 | self.assertEqual(y, 2024) 135 | self.assertEqual(m, 3) 136 | self.assertEqual(d, 28) 137 | 138 | def test_converter_nepali_to_english_for_min_edge_date(self): 139 | y, m, d = converter.nepali_to_english(2000, 1, 1) 140 | self.assertEqual(y, 1943) 141 | self.assertEqual(m, 4) 142 | self.assertEqual(d, 14) 143 | 144 | def test_converter_nepali_to_english_for_max_edge_date(self): 145 | y, m, d = converter.nepali_to_english(2099, 12, 30) 146 | self.assertEqual(y, 2043) 147 | self.assertEqual(m, 4) 148 | self.assertEqual(d, 13) 149 | -------------------------------------------------------------------------------- /nepali/tests/test_locations.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from nepali import locations 4 | from nepali.locations.models import ( 5 | District, 6 | Location, 7 | Municipality, 8 | MunicipalityType, 9 | Province, 10 | ) 11 | from nepali.locations.utils import ( 12 | _filter_location, 13 | get_district, 14 | get_municipality, 15 | get_province, 16 | ) 17 | 18 | 19 | class TestLocations(unittest.TestCase): 20 | def test_locations_count(self): 21 | self.assertEqual(len(locations.provinces), 7) 22 | self.assertEqual(len(locations.districts), 77) 23 | self.assertEqual(len(locations.municipalities), 753) 24 | 25 | def test_utils_filter_location(self): 26 | # checking if no argument is passed 27 | with self.assertRaises(ValueError): 28 | _filter_location(locations=locations.provinces) 29 | 30 | # checking pattern match 31 | province = _filter_location(locations=locations.provinces, name="Bagmati") 32 | self.assertEqual(province.name, "Bagmati Province") 33 | 34 | # checking ignore-case match 35 | province = _filter_location(locations=locations.provinces, name="bagmati") 36 | self.assertEqual(province.name, "Bagmati Province") 37 | 38 | # checking with invalid name 39 | province = _filter_location(locations=locations.provinces, name="invalid") 40 | self.assertEqual(province, None) 41 | 42 | # checking using exact True with invalid match 43 | province = _filter_location( 44 | locations=locations.provinces, name="Bagmati", exact=True 45 | ) 46 | self.assertEqual(province, None) 47 | 48 | # checking using exact True with a valid match 49 | province = _filter_location( 50 | locations=locations.provinces, name="Bagmati Province", exact=True 51 | ) 52 | self.assertEqual(province.name, "Bagmati Province") 53 | 54 | # checking assertion if exact and multiple is passes together 55 | with self.assertRaises(ValueError): 56 | _filter_location( 57 | locations=locations.provinces, 58 | name="Bagmati Province", 59 | exact=True, 60 | multiple=True, 61 | ) 62 | 63 | # checking multiple value match 64 | provinces = _filter_location( 65 | locations=locations.provinces, name="Province", multiple=True 66 | ) 67 | self.assertEqual(type(provinces), list) 68 | self.assertEqual(len(provinces), 7) 69 | 70 | # checking multiple value with invalid match 71 | provinces = _filter_location( 72 | locations=locations.provinces, name="invalid", multiple=True 73 | ) 74 | self.assertEqual(len(provinces), 0) 75 | 76 | # checking pattern match with nepali name 77 | province = _filter_location(locations=locations.provinces, name_nepali="बागमती") 78 | self.assertEqual(province.name_nepali, "बागमती प्रदेश") 79 | 80 | # checking using exact True with a valid nepali match 81 | province = _filter_location( 82 | locations=locations.provinces, name_nepali="बागमती प्रदेश", exact=True 83 | ) 84 | self.assertEqual(province.name_nepali, "बागमती प्रदेश") 85 | 86 | 87 | class TestLocationsUtils(unittest.TestCase): 88 | def test_utils_get_province(self): 89 | province = get_province(name="Bagmati") 90 | self.assertEqual(province.name, "Bagmati Province") 91 | 92 | def test_utils_get_district(self): 93 | district = get_district(name="Kathmandu") 94 | self.assertEqual(district.name, "Kathmandu") 95 | 96 | def test_utils_get_municipality(self): 97 | municipality = get_municipality(name="Kathmandu") 98 | self.assertEqual(municipality.name, "Kathmandu Metropolitan City") 99 | 100 | 101 | class TestLocationsModels(unittest.TestCase): 102 | def test_location_data(self): 103 | location = Location("test_en", "test_ne") 104 | self.assertEqual(location.name, "test_en") 105 | self.assertEqual(location.name_nepali, "test_ne") 106 | 107 | def test_location_str(self): 108 | location = Location("test_en", "test_ne") 109 | self.assertEqual(str(location), "test_en") 110 | 111 | def test_location_repr(self): 112 | location = Location("test_en", "test_ne") 113 | self.assertEqual(repr(location), "test_en") 114 | 115 | def test_districts_from_province(self): 116 | province = Province("Province1", "Province1") 117 | district = District(province, "District1", "District1") 118 | self.assertListEqual(province.districts, [district]) 119 | 120 | def test_municipalities_from_province_and_district(self): 121 | province = Province("Province1", "Province1") 122 | district = District(province, "District1", "District1") 123 | municipality = Municipality( 124 | district, "Municipality1", "Municipality1", MunicipalityType.METROPOLITAN 125 | ) 126 | self.assertListEqual(province.municipalities, [municipality]) 127 | self.assertListEqual(district.municipalities, [municipality]) 128 | 129 | def test_municipality_province_district_and_type(self): 130 | province = Province("Province1", "Province1") 131 | district = District(province, "District1", "District1") 132 | municipality = Municipality( 133 | district, "Municipality1", "Municipality1", MunicipalityType.METROPOLITAN 134 | ) 135 | self.assertEqual(municipality.province, province) 136 | self.assertEqual(municipality.district, district) 137 | self.assertEqual(municipality.municipality_type, MunicipalityType.METROPOLITAN) 138 | 139 | # MunicipalityType 140 | def test_municipality_type_with_invalid_data(self): 141 | with self.assertRaises(ValueError): 142 | MunicipalityType("Hello") 143 | 144 | def test_municipality_type_with_valid_data(self): 145 | municipality_type = MunicipalityType("Metropolitan City") 146 | self.assertEqual(municipality_type, MunicipalityType.METROPOLITAN) 147 | 148 | def test_municipality_type_str(self): 149 | municipality_type = MunicipalityType.RURAL_MUNICIPALITY 150 | self.assertEqual(str(municipality_type), municipality_type.value) 151 | 152 | def test_municipality_type_repr(self): 153 | municipality_type = MunicipalityType.SUB_METROPOLITAN 154 | self.assertEqual(repr(municipality_type), municipality_type.value) 155 | -------------------------------------------------------------------------------- /nepali/templatetags/nepalidatetime.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains django templatetags for nepali date and time. 3 | """ 4 | import datetime 5 | import warnings 6 | from typing import Optional, Union 7 | 8 | from django import template 9 | from django.utils import timezone 10 | 11 | from nepali.datetime import nepalidate as _nepalidate 12 | from nepali.datetime import nepalidatetime as _nepalidatetime 13 | from nepali.datetime import nepalihumanize as humanize 14 | from nepali.exceptions import InvalidNepaliDateTimeObjectException 15 | from nepali.utils import to_nepalidatetime 16 | 17 | _DEFAULT_DATE_FORMAT = "%B %d, %Y, %A" 18 | DEPRECIATION_WARNING_MESSAGE = ( 19 | "The templatetag 'nepalidatetime' has been depreciated " 20 | "and will be removed in the future release. " 21 | "Please use `django-nepali` package." 22 | ) 23 | _datetime = Union[datetime.date, datetime.datetime, _nepalidate, _nepalidatetime] 24 | register = template.Library() 25 | 26 | 27 | @register.filter(name="nepalidate") 28 | def nepalidate( 29 | datetime_obj: _datetime, format: str = _DEFAULT_DATE_FORMAT 30 | ) -> Union[str, None]: 31 | """ 32 | Renders the datetime object into nepali datetime format in 'en-US' locale (English). 33 | 34 | Usage: 35 | ``` 36 | {{ datetime_obj|nepalidate }} 37 | {{ datetime_obj|nepalidate:"%Y-%m-%d" }} 38 | ``` 39 | 40 | :param datetime_obj: Datetime object 41 | :param format: Output format, defaults to "%B %d, %Y, %A" 42 | :returns: Nepali datetime format in 'en-US' locale 43 | """ 44 | warnings.warn( 45 | message=DEPRECIATION_WARNING_MESSAGE, 46 | category=DeprecationWarning, 47 | ) 48 | return nepalidate_en(datetime_obj, format=format) 49 | 50 | 51 | @register.filter(name="nepalidate_en") 52 | def nepalidate_en( 53 | datetime_obj: _datetime, format: str = _DEFAULT_DATE_FORMAT 54 | ) -> Union[str, None]: 55 | """ 56 | Renders the datetime object into nepali datetime format in 'en-US' locale (English). 57 | 58 | Usage: 59 | ``` 60 | {{ datetime_obj|nepalidate_en }} 61 | {{ datetime_obj|nepalidate_en:"%Y-%m-%d" }} 62 | ``` 63 | 64 | :param datetime_obj: Datetime object 65 | :param format: Output format, defaults to "%B %d, %Y, %A" 66 | :returns: Nepali datetime format in 'en-US' locale 67 | """ 68 | warnings.warn( 69 | message=DEPRECIATION_WARNING_MESSAGE, 70 | category=DeprecationWarning, 71 | ) 72 | try: 73 | nepali_datetime_obj = to_nepalidatetime(datetime_obj) 74 | return nepali_datetime_obj.strftime_en(format) 75 | except InvalidNepaliDateTimeObjectException: 76 | return None 77 | 78 | 79 | @register.filter(name="nepalidate_ne") 80 | def nepalidate_ne( 81 | datetime_obj: _datetime, format: str = _DEFAULT_DATE_FORMAT 82 | ) -> Union[str, None]: 83 | """ 84 | Renders the datetime object into nepali datetime format in 'ne' locale (Nepali). 85 | 86 | Usage: 87 | ``` 88 | {{ datetime_obj|nepalidate_ne }} 89 | {{ datetime_obj|nepalidate_ne:"%Y-%m-%d" }} 90 | ``` 91 | 92 | :param datetime_obj: Datetime object 93 | :param format: Output format, defaults to "%B %d, %Y, %A" 94 | :returns: Nepali datetime format in 'ne' locale 95 | """ 96 | warnings.warn( 97 | message=DEPRECIATION_WARNING_MESSAGE, 98 | category=DeprecationWarning, 99 | ) 100 | try: 101 | nepali_datetime_obj = to_nepalidatetime(datetime_obj) 102 | return nepali_datetime_obj.strftime_ne(format) 103 | except InvalidNepaliDateTimeObjectException: 104 | return None 105 | 106 | 107 | @register.filter(name="nepalihumanize") 108 | def nepalihumanize( 109 | datetime_obj: _datetime, 110 | threshold: Optional[int] = None, 111 | format: Optional[str] = None, 112 | ) -> Union[str, None]: 113 | """ 114 | Renders the datetime object to a human readable form for 'ne' locale (Nepali). 115 | 116 | Usage: 117 | ``` 118 | {{ datetime_obj|nepalihumanize }} 119 | ``` 120 | 121 | :param datetime_obj: Datetime object 122 | :param threshold: Threshold in seconds that determines when to render 123 | the datetime object in the standard datetime format, optional 124 | :param format: Output format if threshold exceeded, optional 125 | :returns: Datetime object in human readable form 126 | """ 127 | warnings.warn( 128 | message=DEPRECIATION_WARNING_MESSAGE, 129 | category=DeprecationWarning, 130 | ) 131 | try: 132 | nepali_datetime_obj = to_nepalidatetime(datetime_obj) 133 | return humanize(nepali_datetime_obj, threshold=threshold, format=format) 134 | except InvalidNepaliDateTimeObjectException: 135 | return None 136 | 137 | 138 | @register.simple_tag 139 | def nepalinow(format: str = _DEFAULT_DATE_FORMAT) -> str: 140 | """ 141 | Renders the current nepali datetime in 'en-US' locale (English). 142 | 143 | Usage: 144 | ``` 145 | {% nepalinow %} 146 | {% nepalinow '%Y-%m-%d' %} 147 | ``` 148 | 149 | :param datetime_obj: Datetime object 150 | :param format: Output format, defaults to "%B %d, %Y, %A" 151 | :returns: Current nepali datetime 152 | """ 153 | warnings.warn( 154 | message=DEPRECIATION_WARNING_MESSAGE, 155 | category=DeprecationWarning, 156 | ) 157 | return nepalinow_en(format) 158 | 159 | 160 | @register.simple_tag 161 | def nepalinow_en(format: str = _DEFAULT_DATE_FORMAT) -> str: 162 | """ 163 | Renders the current nepali datetime in 'en-US' locale (English). 164 | 165 | Usage: 166 | ``` 167 | {% nepalinow_en %} 168 | {% nepalinow_en '%Y-%m-%d' %} 169 | ``` 170 | 171 | :param datetime_obj: Datetime object 172 | :param format: Output format, defaults to "%B %d, %Y, %A" 173 | :returns: Current nepali datetime 174 | """ 175 | warnings.warn( 176 | message=DEPRECIATION_WARNING_MESSAGE, 177 | category=DeprecationWarning, 178 | ) 179 | return to_nepalidatetime(timezone.now()).strftime(format) 180 | 181 | 182 | @register.simple_tag 183 | def nepalinow_ne(format: str = _DEFAULT_DATE_FORMAT) -> str: 184 | """ 185 | Renders the current nepali datetime in 'ne' locale (Nepali). 186 | 187 | Usage: 188 | ``` 189 | {% nepalinow_ne %} 190 | {% nepalinow_ne '%Y-%m-%d' %} 191 | ``` 192 | 193 | :param datetime_obj: Datetime object 194 | :param format: Output format, defaults to "%B %d, %Y, %A" 195 | :returns: Current nepali datetime 196 | """ 197 | warnings.warn( 198 | message=DEPRECIATION_WARNING_MESSAGE, 199 | category=DeprecationWarning, 200 | ) 201 | return to_nepalidatetime(timezone.now()).strftime_ne(format) 202 | -------------------------------------------------------------------------------- /nepali/tests/test_nepaliweek.py: -------------------------------------------------------------------------------- 1 | """ 2 | To run only this unit test use the command below. 3 | 4 | python -m unittest nepali/tests/test_nepaliweek.py -v 5 | """ 6 | import unittest 7 | 8 | from nepali.datetime import nepaliweek 9 | 10 | 11 | class TestNepaliWeek(unittest.TestCase): 12 | # parse 13 | def test_nepaliweek_parses_int(self): 14 | self.assertEqual(nepaliweek(0).value, 0) 15 | self.assertEqual(nepaliweek(6).value, 6) 16 | 17 | def test_nepaliweek_fails_for_int_less_than_0(self): 18 | with self.assertRaises(ValueError) as ex: 19 | nepaliweek(-100) 20 | self.assertEqual(str(ex.exception), "Invalid week: -100") 21 | 22 | def test_nepaliweek_fails_for_int_more_than_6(self): 23 | with self.assertRaises(ValueError) as ex: 24 | nepaliweek(7) 25 | self.assertEqual(str(ex.exception), "Invalid week: 7") 26 | with self.assertRaises(ValueError): 27 | nepaliweek(100) 28 | 29 | def test_nepaliweek_parses_str_int_number(self): 30 | week = nepaliweek("1") 31 | self.assertEqual(week.value, 1) 32 | 33 | def test_nepaliweek_fails_for_str_int_out_of_range(self): 34 | with self.assertRaises(ValueError): 35 | nepaliweek("-100") 36 | with self.assertRaises(ValueError): 37 | nepaliweek("7") 38 | with self.assertRaises(ValueError): 39 | nepaliweek("100") 40 | 41 | def test_nepaliweek_fails_for_invalid_str(self): 42 | with self.assertRaises(ValueError): 43 | nepaliweek("hello") 44 | with self.assertRaises(ValueError): 45 | nepaliweek("1.0") 46 | 47 | def test_nepaliweek_parse_valid_week_in_english(self): 48 | self.assertEqual(nepaliweek("Sunday").value, 0) 49 | self.assertEqual(nepaliweek("Monday").value, 1) 50 | self.assertEqual(nepaliweek("Tuesday").value, 2) 51 | self.assertEqual(nepaliweek("Wednesday").value, 3) 52 | self.assertEqual(nepaliweek("Thursday").value, 4) 53 | self.assertEqual(nepaliweek("Friday").value, 5) 54 | self.assertEqual(nepaliweek("Saturday").value, 6) 55 | 56 | def test_nepaliweek_parse_valid_week_in_english_lower_case(self): 57 | self.assertEqual(nepaliweek("sunday").value, 0) 58 | self.assertEqual(nepaliweek("monday").value, 1) 59 | self.assertEqual(nepaliweek("tuesday").value, 2) 60 | self.assertEqual(nepaliweek("wednesday").value, 3) 61 | self.assertEqual(nepaliweek("thursday").value, 4) 62 | self.assertEqual(nepaliweek("friday").value, 5) 63 | self.assertEqual(nepaliweek("saturday").value, 6) 64 | 65 | def test_nepaliweek_parse_valid_week_in_english_case_insensitive(self): 66 | self.assertEqual(nepaliweek("sUnday").value, 0) 67 | self.assertEqual(nepaliweek("moNday").value, 1) 68 | self.assertEqual(nepaliweek("tueSday").value, 2) 69 | self.assertEqual(nepaliweek("wednEsday").value, 3) 70 | self.assertEqual(nepaliweek("thursDay").value, 4) 71 | self.assertEqual(nepaliweek("fridaY").value, 5) 72 | self.assertEqual(nepaliweek("saTurday").value, 6) 73 | 74 | def test_nepaliweek_parse_valid_week_in_english_abbreviated_name(self): 75 | self.assertEqual(nepaliweek("Sun").value, 0) 76 | self.assertEqual(nepaliweek("Mon").value, 1) 77 | self.assertEqual(nepaliweek("Tue").value, 2) 78 | self.assertEqual(nepaliweek("Wed").value, 3) 79 | self.assertEqual(nepaliweek("Thu").value, 4) 80 | self.assertEqual(nepaliweek("Fri").value, 5) 81 | self.assertEqual(nepaliweek("Sat").value, 6) 82 | 83 | def test_nepaliweek_parse_valid_week_in_nepali(self): 84 | self.assertEqual(nepaliweek("आइतबार").value, 0) 85 | self.assertEqual(nepaliweek("सोमबार").value, 1) 86 | self.assertEqual(nepaliweek("मंगलबार").value, 2) 87 | self.assertEqual(nepaliweek("बुधबार").value, 3) 88 | self.assertEqual(nepaliweek("बिहीबार").value, 4) 89 | self.assertEqual(nepaliweek("शुक्रबार").value, 5) 90 | self.assertEqual(nepaliweek("शनिबार").value, 6) 91 | 92 | def test_nepaliweek_parse_valid_week_in_nepali_abbreviated_name(self): 93 | self.assertEqual(nepaliweek("आइत").value, 0) 94 | self.assertEqual(nepaliweek("सोम").value, 1) 95 | self.assertEqual(nepaliweek("मंगल").value, 2) 96 | self.assertEqual(nepaliweek("बुध").value, 3) 97 | self.assertEqual(nepaliweek("बिही").value, 4) 98 | self.assertEqual(nepaliweek("शुक्र").value, 5) 99 | self.assertEqual(nepaliweek("शनि").value, 6) 100 | 101 | def test_nepaliweek_fails_for_invalid_data_type(self): 102 | with self.assertRaises(ValueError): 103 | nepaliweek(1.0) # type: ignore 104 | with self.assertRaises(ValueError): 105 | nepaliweek(2j + 1) # type: ignore 106 | with self.assertRaises(ValueError): 107 | nepaliweek([]) # type: ignore 108 | 109 | def test_nepaliweek_str(self): 110 | self.assertEqual(str(nepaliweek(1)), nepaliweek(1).name) 111 | 112 | def test_nepaliweek_repr(self): 113 | self.assertEqual(repr(nepaliweek(1)), "") 114 | 115 | def test_nepaliweek_name(self): 116 | self.assertEqual(nepaliweek(0).name, "Sunday") 117 | self.assertEqual(nepaliweek(6).name, "Saturday") 118 | 119 | def test_nepaliweek_abbr(self): 120 | self.assertEqual(nepaliweek(0).abbr, "Sun") 121 | self.assertEqual(nepaliweek(6).abbr, "Sat") 122 | 123 | def test_nepaliweek_name_ne(self): 124 | self.assertEqual(nepaliweek(0).name_ne, "आइतबार") 125 | self.assertEqual(nepaliweek(6).name_ne, "शनिबार") 126 | 127 | def test_nepaliweek_abbr_ne(self): 128 | self.assertEqual(nepaliweek(0).abbr_ne, "आइत") 129 | self.assertEqual(nepaliweek(6).abbr_ne, "शनि") 130 | 131 | def test_nepaliweek_variable_cache(self): 132 | self.assertEqual(id(nepaliweek(0)), id(nepaliweek(0))) 133 | self.assertEqual(id(nepaliweek(6)), id(nepaliweek(6))) 134 | 135 | def test_nepaliweek_int(self): 136 | self.assertEqual(int(nepaliweek(0)), 0) 137 | self.assertEqual(int(nepaliweek(6)), 6) 138 | 139 | def test_nepaliweek_equal_with_nepaliweek(self): 140 | self.assertEqual(nepaliweek(0), nepaliweek(0)) 141 | self.assertEqual(nepaliweek(6), nepaliweek(6)) 142 | 143 | def test_nepaliweek_not_equal(self): 144 | self.assertNotEqual(nepaliweek(0), nepaliweek(2)) 145 | self.assertNotEqual(nepaliweek(1), 2) 146 | 147 | def test_nepaliweek_equal_with_int(self): 148 | self.assertEqual(nepaliweek(0), 0) 149 | self.assertEqual(nepaliweek(6), 6) 150 | self.assertEqual(1, nepaliweek(1)) 151 | self.assertEqual(6, nepaliweek(6)) 152 | 153 | def test_nepaliweek_not_equal_with_invalid_object(self): 154 | self.assertNotEqual(nepaliweek(1), "invalid") 155 | -------------------------------------------------------------------------------- /nepali/tests/test_nepalimonth.py: -------------------------------------------------------------------------------- 1 | """ 2 | To run only this unit test use the command below. 3 | 4 | python -m unittest nepali/tests/test_nepalimonth.py -v 5 | """ 6 | import unittest 7 | 8 | from nepali.datetime import nepalimonth 9 | 10 | 11 | class TestNepaliMonth(unittest.TestCase): 12 | # parse 13 | def test_nepalimonth_parses_int(self): 14 | self.assertEqual(nepalimonth(1).value, 1) 15 | self.assertEqual(nepalimonth(12).value, 12) 16 | 17 | def test_nepalimonth_fails_for_int_less_than_1(self): 18 | with self.assertRaises(ValueError) as ex: 19 | nepalimonth(0) 20 | self.assertEqual(str(ex.exception), "Invalid month: 0") 21 | with self.assertRaises(ValueError): 22 | nepalimonth(-100) 23 | 24 | def test_nepalimonth_fails_for_int_more_than_12(self): 25 | with self.assertRaises(ValueError) as ex: 26 | nepalimonth(13) 27 | self.assertEqual(str(ex.exception), "Invalid month: 13") 28 | with self.assertRaises(ValueError): 29 | nepalimonth(100) 30 | 31 | def test_nepalimonth_parses_str_int_number(self): 32 | month = nepalimonth("1") 33 | self.assertEqual(month.value, 1) 34 | 35 | def test_nepalimonth_fails_for_str_int_out_of_range(self): 36 | with self.assertRaises(ValueError): 37 | nepalimonth("0") 38 | with self.assertRaises(ValueError): 39 | nepalimonth("-100") 40 | with self.assertRaises(ValueError): 41 | nepalimonth("13") 42 | with self.assertRaises(ValueError): 43 | nepalimonth("100") 44 | 45 | def test_nepalimonth_fails_for_invalid_str(self): 46 | with self.assertRaises(ValueError): 47 | nepalimonth("hello") 48 | with self.assertRaises(ValueError): 49 | nepalimonth("1.0") 50 | 51 | def test_nepalimonth_parse_valid_month_in_english(self): 52 | self.assertEqual(nepalimonth("Baishakh").value, 1) 53 | self.assertEqual(nepalimonth("Jestha").value, 2) 54 | self.assertEqual(nepalimonth("Ashad").value, 3) 55 | self.assertEqual(nepalimonth("Sharwan").value, 4) 56 | self.assertEqual(nepalimonth("Bhadra").value, 5) 57 | self.assertEqual(nepalimonth("Ashwin").value, 6) 58 | self.assertEqual(nepalimonth("Kartik").value, 7) 59 | self.assertEqual(nepalimonth("Mangsir").value, 8) 60 | self.assertEqual(nepalimonth("Poush").value, 9) 61 | self.assertEqual(nepalimonth("Magh").value, 10) 62 | self.assertEqual(nepalimonth("Falgun").value, 11) 63 | self.assertEqual(nepalimonth("Chaitra").value, 12) 64 | 65 | def test_nepalimonth_parse_valid_month_in_english_lower_case(self): 66 | self.assertEqual(nepalimonth("baishakh").value, 1) 67 | self.assertEqual(nepalimonth("jestha").value, 2) 68 | self.assertEqual(nepalimonth("ashad").value, 3) 69 | self.assertEqual(nepalimonth("sharwan").value, 4) 70 | self.assertEqual(nepalimonth("bhadra").value, 5) 71 | self.assertEqual(nepalimonth("ashwin").value, 6) 72 | self.assertEqual(nepalimonth("kartik").value, 7) 73 | self.assertEqual(nepalimonth("mangsir").value, 8) 74 | self.assertEqual(nepalimonth("poush").value, 9) 75 | self.assertEqual(nepalimonth("magh").value, 10) 76 | self.assertEqual(nepalimonth("falgun").value, 11) 77 | self.assertEqual(nepalimonth("chaitra").value, 12) 78 | 79 | def test_nepalimonth_parse_valid_month_in_english_case_insensitive(self): 80 | self.assertEqual(nepalimonth("bAisHakh").value, 1) 81 | self.assertEqual(nepalimonth("jEsTha").value, 2) 82 | self.assertEqual(nepalimonth("aShaD").value, 3) 83 | self.assertEqual(nepalimonth("shaRwaN").value, 4) 84 | self.assertEqual(nepalimonth("BhadRa").value, 5) 85 | self.assertEqual(nepalimonth("ashWIn").value, 6) 86 | self.assertEqual(nepalimonth("KArtIk").value, 7) 87 | self.assertEqual(nepalimonth("MaNGsir").value, 8) 88 | self.assertEqual(nepalimonth("POush").value, 9) 89 | self.assertEqual(nepalimonth("maGh").value, 10) 90 | self.assertEqual(nepalimonth("faLgUn").value, 11) 91 | self.assertEqual(nepalimonth("ChaItRa").value, 12) 92 | 93 | def test_nepalimonth_parse_valid_month_in_nepali(self): 94 | self.assertEqual(nepalimonth("बैशाख").value, 1) 95 | self.assertEqual(nepalimonth("जेठ").value, 2) 96 | self.assertEqual(nepalimonth("असार").value, 3) 97 | self.assertEqual(nepalimonth("साउन").value, 4) 98 | self.assertEqual(nepalimonth("भदौ").value, 5) 99 | self.assertEqual(nepalimonth("असोज").value, 6) 100 | self.assertEqual(nepalimonth("कात्तिक").value, 7) 101 | self.assertEqual(nepalimonth("मंसिर").value, 8) 102 | self.assertEqual(nepalimonth("पुस").value, 9) 103 | self.assertEqual(nepalimonth("माघ").value, 10) 104 | self.assertEqual(nepalimonth("फागुन").value, 11) 105 | self.assertEqual(nepalimonth("चैत").value, 12) 106 | 107 | def test_nepalimonth_fails_for_invalid_data_type(self): 108 | with self.assertRaises(ValueError): 109 | nepalimonth(1.0) # type: ignore 110 | with self.assertRaises(ValueError): 111 | nepalimonth(2j + 1) # type: ignore 112 | with self.assertRaises(ValueError): 113 | nepalimonth([]) # type: ignore 114 | 115 | def test_nepalimonth_str(self): 116 | self.assertEqual(str(nepalimonth(1)), nepalimonth(1).name) 117 | 118 | def test_nepalimonth_repr(self): 119 | self.assertEqual(repr(nepalimonth(1)), "") 120 | 121 | def test_nepalimonth_name(self): 122 | self.assertEqual(nepalimonth(1).name, "Baishakh") 123 | self.assertEqual(nepalimonth(12).name, "Chaitra") 124 | 125 | def test_nepalimonth_name_ne(self): 126 | self.assertEqual(nepalimonth(1).name_ne, "बैशाख") 127 | self.assertEqual(nepalimonth(12).name_ne, "चैत") 128 | 129 | def test_nepalimonth_variable_cache(self): 130 | self.assertEqual(id(nepalimonth(1)), id(nepalimonth(1))) 131 | self.assertEqual(id(nepalimonth(12)), id(nepalimonth(12))) 132 | 133 | def test_nepalimonth_int(self): 134 | self.assertEqual(int(nepalimonth(1)), 1) 135 | self.assertEqual(int(nepalimonth(12)), 12) 136 | 137 | def test_nepalimonth_equal_with_nepalimonth(self): 138 | self.assertEqual(nepalimonth(1), nepalimonth(1)) 139 | self.assertEqual(nepalimonth(12), nepalimonth(12)) 140 | 141 | def test_nepalimonth_not_equal(self): 142 | self.assertNotEqual(nepalimonth(1), nepalimonth(2)) 143 | self.assertNotEqual(nepalimonth(1), 2) 144 | 145 | def test_nepalimonth_equal_with_int(self): 146 | self.assertEqual(nepalimonth(1), 1) 147 | self.assertEqual(nepalimonth(12), 12) 148 | self.assertEqual(1, nepalimonth(1)) 149 | self.assertEqual(12, nepalimonth(12)) 150 | 151 | def test_nepalimonth_not_equal_with_invalid_object(self): 152 | self.assertNotEqual(nepalimonth(1), "invalid") 153 | -------------------------------------------------------------------------------- /nepali/tests/test_parser.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from nepali.char import nepali_to_english_text 4 | from nepali.datetime import nepalidatetime 5 | from nepali.datetime import parser as nepalidatetime_parser 6 | from nepali.datetime.parser.validators import NepaliTimeRE, extract, transform, validate 7 | from nepali.exceptions import FormatNotMatchException, InvalidDateTimeFormatException 8 | 9 | 10 | class TestNepaliDateTimeParserValidators(unittest.TestCase): 11 | """ 12 | Tests nepali date 13 | """ 14 | 15 | def setUp(self) -> None: 16 | self.nepali_time_re = NepaliTimeRE() 17 | return super().setUp() 18 | 19 | # test extract 20 | 21 | def test_simple_extract(self): 22 | extracted_data = extract("2078-01-12", format="%Y-%m-%d") 23 | self.assertEqual(extracted_data.get("Y"), "2078") 24 | self.assertEqual(extracted_data.get("m"), "01") 25 | self.assertEqual(extracted_data.get("d"), "12") 26 | 27 | def test_complex_extract(self): 28 | extracted_data = extract( 29 | "Wed Wednesday 3 28 Sharwan Sharwan 04 51 2051 05 05 AM 28 23", 30 | format="%a %A %w %d %b %B %m %y %Y %H %I %p %M %S", 31 | ) 32 | self.assertEqual(extracted_data.get("a"), "Wed") 33 | self.assertEqual(extracted_data.get("A"), "Wednesday") 34 | self.assertEqual(extracted_data.get("w"), "3") 35 | self.assertEqual(extracted_data.get("d"), "28") 36 | self.assertEqual(extracted_data.get("b"), "Sharwan") 37 | self.assertEqual(extracted_data.get("B"), "Sharwan") 38 | self.assertEqual(extracted_data.get("m"), "04") 39 | self.assertEqual(extracted_data.get("y"), "51") 40 | self.assertEqual(extracted_data.get("Y"), "2051") 41 | self.assertEqual(extracted_data.get("H"), "05") 42 | self.assertEqual(extracted_data.get("I"), "05") 43 | self.assertEqual(extracted_data.get("p"), "AM") 44 | self.assertEqual(extracted_data.get("M"), "28") 45 | self.assertEqual(extracted_data.get("S"), "23") 46 | 47 | def test_nepali_to_english_text_conversion(self): 48 | self.assertEqual( 49 | nepali_to_english_text( 50 | "बुध बुधबार ३ २८ साउन साउन ०४ ५१ २०५१ ०५ ०५ शुभप्रभात २८ २३" 51 | ), 52 | "Wed Wednesday 3 28 Sharwan Sharwan 04 51 2051 05 05 AM 28 23", 53 | ) 54 | 55 | def test_complex_extract_in_nepali(self): 56 | extracted_data = extract( 57 | "बुध बुधबार ३ २८ साउन साउन ०४ ५१ २०५१ ०५ ०५ शुभप्रभात २८ २३", 58 | format="%a %A %w %d %b %B %m %y %Y %H %I %p %M %S", 59 | ) 60 | self.assertEqual(extracted_data.get("a"), "Wed") 61 | self.assertEqual(extracted_data.get("A"), "Wednesday") 62 | self.assertEqual(extracted_data.get("w"), "3") 63 | self.assertEqual(extracted_data.get("d"), "28") 64 | self.assertEqual(extracted_data.get("b"), "Sharwan") 65 | self.assertEqual(extracted_data.get("B"), "Sharwan") 66 | self.assertEqual(extracted_data.get("m"), "04") 67 | self.assertEqual(extracted_data.get("y"), "51") 68 | self.assertEqual(extracted_data.get("Y"), "2051") 69 | self.assertEqual(extracted_data.get("H"), "05") 70 | self.assertEqual(extracted_data.get("I"), "05") 71 | self.assertEqual(extracted_data.get("p"), "AM") 72 | self.assertEqual(extracted_data.get("M"), "28") 73 | self.assertEqual(extracted_data.get("S"), "23") 74 | 75 | # test transform 76 | def test_transform(self): 77 | data = transform( 78 | { 79 | "Y": "2078", 80 | "b": "MAngsir", 81 | "d": "12", 82 | "p": "pm", 83 | "I": "05", 84 | "M": "25", 85 | "S": "31", 86 | "f": "455", 87 | "a": "Wed", 88 | } 89 | ) 90 | self.assertEqual(data.get("year"), 2078) 91 | self.assertEqual(data.get("month"), 8) 92 | self.assertEqual(data.get("day"), 12) 93 | self.assertEqual(data.get("hour"), 17) 94 | self.assertEqual(data.get("minute"), 25) 95 | self.assertEqual(data.get("second"), 31) 96 | self.assertEqual(data.get("microsecond"), 455000) 97 | self.assertEqual(data.get("weekday"), 3) 98 | 99 | # 100 | # test validate 101 | 102 | def test_validate(self): 103 | nepalidatetime_obj = validate("2078-01-12", format="%Y-%m-%d") 104 | self.assertEqual(nepalidatetime_obj.year, 2078) 105 | self.assertEqual(nepalidatetime_obj.month, 1) 106 | self.assertEqual(nepalidatetime_obj.day, 12) 107 | 108 | nepalidatetime_obj = validate("2079-03-32", format="%Y-%m-%d") 109 | self.assertEqual(nepalidatetime_obj.year, 2079) 110 | self.assertEqual(nepalidatetime_obj.month, 3) 111 | self.assertEqual(nepalidatetime_obj.day, 32) 112 | 113 | def test_test_validate(self): 114 | nepalidatetime_obj = validate( 115 | "29 Jestha, 2078, 1:30 PM", format="%d %B, %Y, %I:%M %p" 116 | ) 117 | self.assertEqual(nepalidatetime_obj.year, 2078) 118 | self.assertEqual(nepalidatetime_obj.month, 2) 119 | self.assertEqual(nepalidatetime_obj.day, 29) 120 | self.assertEqual(nepalidatetime_obj.hour, 13) 121 | self.assertEqual(nepalidatetime_obj.minute, 30) 122 | self.assertEqual(nepalidatetime_obj.second, 0) 123 | 124 | def test_validate_with_invalid_data(self): 125 | self.assertEqual(validate("2078-01-12", format="%k-%m-%d"), None) 126 | self.assertEqual(validate("2078-01-12", format="%m-%M-%Y"), None) 127 | 128 | 129 | class TestNepaliDateTimeParser(unittest.TestCase): 130 | """ 131 | Tests nepali datetime parser. 132 | """ 133 | 134 | # test strptime 135 | def test_strptime(self): 136 | nepalidatetime_obj = nepalidatetime_parser.strptime( 137 | "2078-01-12", format="%Y-%m-%d" 138 | ) 139 | self.assertEqual(nepalidatetime_obj.year, 2078) 140 | self.assertEqual(nepalidatetime_obj.month, 1) 141 | self.assertEqual(nepalidatetime_obj.day, 12) 142 | 143 | def test_strptime_fail(self): 144 | with self.assertRaises(FormatNotMatchException): 145 | nepalidatetime_parser.strptime("2078-01-12", format="%Y %d") 146 | 147 | # test parser 148 | 149 | def test_normal_string_parse(self): 150 | parsed_datetime = nepalidatetime_parser.parse("2071-01-24") 151 | test_datetime = nepalidatetime(2071, 1, 24) 152 | self.assertEqual(parsed_datetime, test_datetime) 153 | 154 | def test_complex_string_parse(self): 155 | nepalidatetime_obj = nepalidatetime_parser.parse("29 Jestha, 2078, 1:30 PM") 156 | self.assertEqual(nepalidatetime_obj.year, 2078) 157 | self.assertEqual(nepalidatetime_obj.month, 2) 158 | self.assertEqual(nepalidatetime_obj.day, 29) 159 | self.assertEqual(nepalidatetime_obj.hour, 13) 160 | self.assertEqual(nepalidatetime_obj.minute, 30) 161 | 162 | def test_parse_failed(self): 163 | with self.assertRaises(InvalidDateTimeFormatException): 164 | nepalidatetime_parser.parse("") 165 | -------------------------------------------------------------------------------- /nepali/tests/test_timezone.py: -------------------------------------------------------------------------------- 1 | """ 2 | To run only this unit test use the command below. 3 | 4 | python -m unittest nepali/tests/test_timezone.py -v 5 | """ 6 | import datetime 7 | import unittest 8 | from unittest.mock import patch 9 | 10 | from nepali.constants import NEPAL_TIMEZONE 11 | from nepali.timezone import ( 12 | NepaliTimeZone, 13 | get_timezone, 14 | now, 15 | to_nepali_timezone, 16 | to_utc_timezone, 17 | utc_now, 18 | ) 19 | 20 | 21 | class TestNepaliTimeZone(unittest.TestCase): 22 | def setUp(self): 23 | self.nepali_timezone = NepaliTimeZone() 24 | return super().setUp() 25 | 26 | def test_nepali_timezone_dst(self): 27 | self.assertEqual(self.nepali_timezone.dst(None), datetime.timedelta(0)) 28 | 29 | def test_nepali_timezone_utcoffset(self): 30 | self.assertEqual( 31 | self.nepali_timezone.utcoffset(None), 32 | datetime.timedelta(hours=5, minutes=45), 33 | ) 34 | 35 | def test_nepali_timezone_tzname(self): 36 | self.assertEqual(self.nepali_timezone.tzname(None), NEPAL_TIMEZONE) 37 | 38 | def test_nepali_timezone_str(self): 39 | self.assertEqual(str(self.nepali_timezone), NEPAL_TIMEZONE) 40 | 41 | def test_nepali_timezone_repr(self): 42 | self.assertEqual(repr(self.nepali_timezone), NEPAL_TIMEZONE) 43 | 44 | def test_datetime_with_nepali_timezone(self): 45 | dt = datetime.datetime(2015, 10, 14, tzinfo=self.nepali_timezone) 46 | self.assertEqual(dt.tzinfo, self.nepali_timezone) 47 | self.assertEqual(dt.tzname(), self.nepali_timezone.tzname(dt)) 48 | self.assertEqual(dt.utcoffset(), self.nepali_timezone.utcoffset(dt)) 49 | 50 | 51 | class TestTimezoneUtils(unittest.TestCase): 52 | def test_get_timezone(self): 53 | tz = get_timezone() 54 | self.assertIsNotNone(tz) 55 | 56 | @patch("nepali.timezone.get_timezone") 57 | @patch("nepali.timezone.datetime") 58 | def test_timezone_now(self, mock_datetime, mock_get_timezone): 59 | # mocking data 60 | mock_get_timezone.return_value = ( 61 | datetime.timezone.utc 62 | ) # mocking get_timezone => UTC 63 | datetime_now = mock_datetime.datetime.now 64 | datetime_now.return_value = datetime.datetime( 65 | 2015, 1, 1 66 | ) # mocking datetime.now => 2015/1/1 67 | 68 | # calling now 69 | current_dt = now() 70 | 71 | # checking 72 | self.assertEqual(current_dt, datetime.datetime(2015, 1, 1)) 73 | self.assertSequenceEqual(datetime_now.call_args[0], [datetime.timezone.utc]) 74 | 75 | @patch("nepali.timezone.datetime") 76 | def test_timezone_utc_now(self, mock_datetime): 77 | # mocking data 78 | mock_datetime.timezone.utc = ( 79 | datetime.timezone.utc 80 | ) # reverting the utc timezone mock 81 | datetime_now = mock_datetime.datetime.now 82 | datetime_now.return_value = datetime.datetime( 83 | 2015, 1, 1 84 | ) # mocking datetime.now => 2015/1/1 85 | 86 | # calling utc_now 87 | utc_dt = utc_now() 88 | 89 | # checking 90 | self.assertEqual(utc_dt, datetime.datetime(2015, 1, 1)) 91 | self.assertSequenceEqual(datetime_now.call_args[0], [datetime.timezone.utc]) 92 | 93 | # to_utc_timezone 94 | def test_to_utc_timezone_invalid_input_returns_as_it_is(self): 95 | self.assertEqual(to_utc_timezone("test"), "test") # type: ignore 96 | 97 | @patch("nepali.timezone.get_timezone") 98 | def test_to_utc_timezone_without_timezone(self, mock_get_timezone): 99 | mock_get_timezone.return_value = datetime.timezone( 100 | offset=datetime.timedelta(hours=5, minutes=30) 101 | ) # Indian timezone 102 | dt = datetime.datetime(2014, 11, 14, 10, 15, 11) 103 | utc_dt = to_utc_timezone(dt) 104 | self.assertEqual(utc_dt.tzinfo, datetime.timezone.utc) 105 | self.assertSequenceEqual( 106 | ( 107 | utc_dt.year, 108 | utc_dt.month, 109 | utc_dt.day, 110 | utc_dt.hour, 111 | utc_dt.minute, 112 | utc_dt.second, 113 | ), 114 | (2014, 11, 14, 4, 45, 11), 115 | ) 116 | 117 | def test_to_utc_timezone_with_utc_timezone(self): 118 | dt = datetime.datetime(2014, 11, 14, 10, 15, 11, tzinfo=datetime.timezone.utc) 119 | utc_dt = to_utc_timezone(dt) 120 | self.assertEqual(utc_dt.tzinfo, datetime.timezone.utc) 121 | self.assertSequenceEqual( 122 | ( 123 | utc_dt.year, 124 | utc_dt.month, 125 | utc_dt.day, 126 | utc_dt.hour, 127 | utc_dt.minute, 128 | utc_dt.second, 129 | ), 130 | (2014, 11, 14, 10, 15, 11), 131 | ) 132 | 133 | def test_to_utc_timezone_with_nepali_timezone(self): 134 | dt = datetime.datetime(2014, 11, 14, 10, 15, 11, tzinfo=NepaliTimeZone()) 135 | utc_dt = to_utc_timezone(dt) 136 | self.assertEqual(utc_dt.tzinfo, datetime.timezone.utc) 137 | self.assertSequenceEqual( 138 | ( 139 | utc_dt.year, 140 | utc_dt.month, 141 | utc_dt.day, 142 | utc_dt.hour, 143 | utc_dt.minute, 144 | utc_dt.second, 145 | ), 146 | (2014, 11, 14, 4, 30, 11), 147 | ) 148 | 149 | # to_nepali_timezone 150 | def test_to_nepali_timezone_invalid_input_returns_as_it_is(self): 151 | self.assertEqual(to_nepali_timezone("test"), "test") # type: ignore 152 | 153 | def test_to_nepali_timezone_without_timezone(self): 154 | dt = datetime.datetime(2014, 11, 14, 10, 15, 11) 155 | nepali_dt = to_nepali_timezone(dt) 156 | self.assertEqual(nepali_dt.tzinfo, NepaliTimeZone()) 157 | self.assertSequenceEqual( 158 | (dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second), 159 | (2014, 11, 14, 10, 15, 11), 160 | ) 161 | 162 | def test_to_nepali_timezone_with_utc_timezone(self): 163 | dt = datetime.datetime(2014, 11, 14, 4, 30, 11, tzinfo=datetime.timezone.utc) 164 | nepali_dt = to_nepali_timezone(dt) 165 | self.assertEqual(nepali_dt.tzinfo, NepaliTimeZone()) 166 | self.assertSequenceEqual( 167 | ( 168 | nepali_dt.year, 169 | nepali_dt.month, 170 | nepali_dt.day, 171 | nepali_dt.hour, 172 | nepali_dt.minute, 173 | nepali_dt.second, 174 | ), 175 | (2014, 11, 14, 10, 15, 11), 176 | ) 177 | 178 | def test_to_nepali_timezone_with_nepali_timezone(self): 179 | dt = datetime.datetime(2014, 11, 14, 10, 15, 11, tzinfo=NepaliTimeZone()) 180 | nepali_dt = to_nepali_timezone(dt) 181 | self.assertEqual(nepali_dt.tzinfo, NepaliTimeZone()) 182 | self.assertSequenceEqual( 183 | ( 184 | nepali_dt.year, 185 | nepali_dt.month, 186 | nepali_dt.day, 187 | nepali_dt.hour, 188 | nepali_dt.minute, 189 | nepali_dt.second, 190 | ), 191 | (2014, 11, 14, 10, 15, 11), 192 | ) 193 | -------------------------------------------------------------------------------- /nepali/datetime/_formatter.py: -------------------------------------------------------------------------------- 1 | import datetime as pythonDateTime 2 | 3 | from nepali import number 4 | from nepali.exceptions import ( 5 | InvalidDateFormatException, 6 | InvalidNepaliDateTimeObjectException, 7 | ) 8 | 9 | from ._datetime import nepalidate, nepalidatetime 10 | from ._nepalimonth import nepalimonth 11 | from ._nepaliweek import nepaliweek 12 | 13 | 14 | class NepaliDateTimeFormatter: 15 | """ 16 | NepaliDateTimeFormatter: formats nepali datetime to string ( using strftime ) 17 | """ 18 | 19 | # format according to python's datetime with class method 20 | format_map = { 21 | "a": "weekdayHalf", 22 | "A": "weekdayFull", 23 | "w": "weekdayNumber", 24 | "d": "day", 25 | "-d": "day_nonzero", 26 | "b": "monthFull", 27 | "B": "monthFull", 28 | "m": "monthNumber", 29 | "-m": "monthNumber_nonzero", 30 | "y": "yearHalf", 31 | "Y": "yearFull", 32 | "H": "hour24", 33 | "-H": "hour24_nonzero", 34 | "I": "hour12", 35 | "-I": "hour12_nonzero", 36 | "p": "ampm", 37 | "M": "minute", 38 | "-M": "minute_nonzero", 39 | "S": "second", 40 | "-S": "second_nonzero", 41 | } 42 | 43 | def __init__(self, datetime_object, devanagari=False): 44 | # TODO: Change variable npDateTime into snake case: `np_date_time` 45 | if type(datetime_object) == nepalidatetime: 46 | self.npDateTime = datetime_object 47 | elif type(datetime_object) == nepalidate: 48 | self.npDateTime = datetime_object.to_nepalidatetime() 49 | elif type(datetime_object) == pythonDateTime.date: 50 | self.npDateTime = nepalidatetime.from_date(datetime_object) 51 | elif type(datetime_object) == pythonDateTime.datetime: 52 | self.npDateTime = nepalidatetime.from_datetime(datetime_object) 53 | else: 54 | raise InvalidNepaliDateTimeObjectException( 55 | "Argument must be instance of nepalidate or nepalidatetime or datetime.datetime or datetime.date" 56 | ) 57 | 58 | self.devanagari = devanagari 59 | 60 | def __str__(self): 61 | return str(self.npDateTime) 62 | 63 | def get_str(self, format): 64 | """generates formatted string""" 65 | i, n = 0, len(format) 66 | time_str = [] 67 | try: 68 | while i < n: 69 | ch = format[i] 70 | i += 1 71 | if ch == "%": 72 | if i < n: 73 | ch = format[i] 74 | 75 | if ch == "%": 76 | # for % character 77 | time_str.append("%") 78 | 79 | elif ch == "-": 80 | # special mid characters eg. "-" for non zero padded values 81 | special_ch = ch 82 | if i + 1 < n: 83 | i += 1 84 | ch = format[i] 85 | time_str.append( 86 | getattr(self, self.get_format_map(special_ch + ch)) 87 | ) 88 | else: 89 | # mapping % forwarded character 90 | time_str.append(getattr(self, self.get_format_map(ch))) 91 | i += 1 92 | else: 93 | time_str.append(ch) 94 | except InvalidDateFormatException as e: 95 | raise e 96 | except Exception: 97 | raise Exception("Unable to convert NepaliDateTime to str") 98 | time_str = "".join(time_str) 99 | 100 | return time_str 101 | 102 | def get_format_map(self, ch: str) -> str: 103 | if ch not in self.format_map: 104 | raise InvalidDateFormatException(f"Invalid Date format %{ch}") 105 | return self.format_map[ch] 106 | 107 | @property 108 | def weekdayHalf(self): 109 | """ 110 | %a 111 | """ 112 | week = nepaliweek(self.npDateTime.weekday()) 113 | if not self.devanagari: 114 | return week.abbr 115 | return week.abbr_ne 116 | 117 | @property 118 | def weekdayFull(self): 119 | """ 120 | %A 121 | """ 122 | week = nepaliweek(self.npDateTime.weekday()) 123 | if not self.devanagari: 124 | return week.name 125 | return week.name_ne 126 | 127 | @property 128 | def weekdayNumber(self): 129 | """ 130 | %w 131 | """ 132 | if not self.devanagari: 133 | return str(self.npDateTime.weekday()) 134 | return number.english_to_nepali(self.npDateTime.weekday()) 135 | 136 | @property 137 | def day(self): 138 | """ 139 | %d 140 | """ 141 | day = str(self.npDateTime.day) 142 | if len(day) < 2: 143 | day = "0" + day 144 | if not self.devanagari: 145 | return str(day) 146 | return number.english_to_nepali(day) 147 | 148 | @property 149 | def day_nonzero(self): 150 | """ 151 | %-D 152 | """ 153 | day = str(self.npDateTime.day) 154 | if not self.devanagari: 155 | return str(day) 156 | return number.english_to_nepali(day) 157 | 158 | @property 159 | def monthFull(self): 160 | """ 161 | %B or %b 162 | """ 163 | month = nepalimonth(self.npDateTime.month) 164 | if not self.devanagari: 165 | return month.name 166 | return month.name_ne 167 | 168 | @property 169 | def monthNumber(self): 170 | """ 171 | %m 172 | """ 173 | month = str(self.npDateTime.month) 174 | if len(month) < 2: 175 | month = "0" + month 176 | if not self.devanagari: 177 | return str(month) 178 | return number.english_to_nepali(month) 179 | 180 | @property 181 | def monthNumber_nonzero(self): 182 | """ 183 | %-m 184 | """ 185 | month = str(self.npDateTime.month) 186 | if not self.devanagari: 187 | return str(month) 188 | return number.english_to_nepali(month) 189 | 190 | @property 191 | def yearHalf(self): 192 | """ 193 | %y 194 | """ 195 | if not self.devanagari: 196 | return str(self.npDateTime.year)[2:] 197 | return number.english_to_nepali(str(self.npDateTime.year)[2:]) 198 | 199 | @property 200 | def yearFull(self): 201 | """ 202 | %Y 203 | """ 204 | if not self.devanagari: 205 | return str(self.npDateTime.year) 206 | return number.english_to_nepali(self.npDateTime.year) 207 | 208 | @property 209 | def hour24(self): 210 | """ 211 | %H 212 | """ 213 | hour = str(self.npDateTime.hour) 214 | if len(hour) < 2: 215 | hour = "0" + hour 216 | if not self.devanagari: 217 | return str(hour) 218 | return number.english_to_nepali(hour) 219 | 220 | @property 221 | def hour24_nonzero(self): 222 | """ 223 | %-H 224 | """ 225 | hour = self.npDateTime.hour 226 | if not self.devanagari: 227 | return str(hour) 228 | return number.english_to_nepali(hour) 229 | 230 | @property 231 | def hour12(self): 232 | """ 233 | %I 234 | """ 235 | hour = self.npDateTime.hour 236 | if hour > 12: 237 | hour = hour - 12 238 | if hour == 0: 239 | hour = 12 240 | hour = str(hour) 241 | if len(hour) < 2: 242 | hour = "0" + hour 243 | 244 | if not self.devanagari: 245 | return str(hour) 246 | return number.english_to_nepali(hour) 247 | 248 | @property 249 | def hour12_nonzero(self): 250 | """ 251 | %-I 252 | """ 253 | hour = self.npDateTime.hour 254 | if hour > 12: 255 | hour = hour - 12 256 | if hour == 0: 257 | hour = 12 258 | hour = str(hour) 259 | if not self.devanagari: 260 | return str(hour) 261 | return number.english_to_nepali(hour) 262 | 263 | @property 264 | def ampm(self): 265 | """ 266 | %p 267 | """ 268 | if not self.devanagari: 269 | ampm = "AM" 270 | if self.npDateTime.hour > 12: 271 | ampm = "PM" 272 | return str(ampm) 273 | 274 | ampm = "" 275 | if self.npDateTime.hour < 12: 276 | ampm = "शुभप्रभात" 277 | elif self.npDateTime.hour >= 12 and self.npDateTime.hour < 18: 278 | ampm = "मध्यान्ह" 279 | else: 280 | ampm = "अपरान्ह" 281 | return str(ampm) 282 | 283 | @property 284 | def minute(self): 285 | """ 286 | %M 287 | """ 288 | minute = str(self.npDateTime.minute) 289 | if len(minute) < 2: 290 | minute = "0" + minute 291 | if not self.devanagari: 292 | return str(minute) 293 | return number.english_to_nepali(minute) 294 | 295 | @property 296 | def minute_nonzero(self): 297 | """ 298 | %-M 299 | """ 300 | minute = str(self.npDateTime.minute) 301 | if not self.devanagari: 302 | return str(minute) 303 | return number.english_to_nepali(minute) 304 | 305 | @property 306 | def second(self): 307 | """ 308 | %S 309 | """ 310 | second = str(self.npDateTime.second) 311 | if len(second) < 2: 312 | second = "0" + second 313 | if not self.devanagari: 314 | return str(second) 315 | return number.english_to_nepali(second) 316 | 317 | @property 318 | def second_nonzero(self): 319 | """ 320 | %-S 321 | """ 322 | second = str(self.npDateTime.second) 323 | if not self.devanagari: 324 | return str(second) 325 | return number.english_to_nepali(second) 326 | -------------------------------------------------------------------------------- /nepali/datetime/parser/validators.py: -------------------------------------------------------------------------------- 1 | """ 2 | validates parsing 3 | """ 4 | import re 5 | from typing import Optional, Tuple 6 | 7 | from nepali.char import nepali_to_english_text 8 | from nepali.constants import NEPALI_MONTHS_EN, WEEKS_ABBR_EN, WEEKS_EN 9 | from nepali.datetime import nepalidatetime, nepalimonth, nepaliweek 10 | 11 | __nepali_time_re__CACHE = None 12 | 13 | 14 | class NepaliTimeRE(dict): 15 | def __init__(self): 16 | """Create keys/values. 17 | Order of execution is important for dependency reasons. 18 | """ 19 | base = super() 20 | base.__init__( 21 | { 22 | # The " [1-9]" part of the regex is to make %c from ANSI C work 23 | "d": r"(?P3[0-2]|[1-2]\d|0[1-9]|[1-9]| [1-9])", 24 | "-d": r"(?P3[0-2]|[1-2]\d|0[1-9]|[1-9]| [1-9])", # same as "d" 25 | "f": r"(?P[0-9]{1,6})", 26 | "H": r"(?P2[0-3]|[0-1]\d|\d)", 27 | "-H": r"(?P2[0-3]|[0-1]\d|\d)", 28 | "I": r"(?P1[0-2]|0[1-9]|[1-9])", 29 | "-I": r"(?P1[0-2]|0[1-9]|[1-9])", 30 | "G": r"(?P\d\d\d\d)", 31 | "j": r"(?P36[0-6]|3[0-5]\d|[1-2]\d\d|0[1-9]\d|00[1-9]|[1-9]\d|0[1-9]|[1-9])", 32 | "m": r"(?P1[0-2]|0[1-9]|[1-9])", 33 | "-m": r"(?P1[0-2]|0[1-9]|[1-9])", # same as "m" 34 | "M": r"(?P[0-5]\d|\d)", 35 | "-M": r"(?P[0-5]\d|\d)", # same as "M" 36 | "S": r"(?P6[0-1]|[0-5]\d|\d)", 37 | "-S": r"(?P6[0-1]|[0-5]\d|\d)", # same as "S" 38 | "w": r"(?P[0-6])", 39 | "y": r"(?P\d\d)", 40 | "Y": r"(?P\d\d\d\d)", 41 | "z": r"(?P[+-]\d\d:?[0-5]\d(:?[0-5]\d(\.\d{1,6})?)?|(?-i:Z))", 42 | "A": self.__seqToRE(WEEKS_EN, "A"), 43 | "a": self.__seqToRE(WEEKS_ABBR_EN, "a"), 44 | "B": self.__seqToRE(NEPALI_MONTHS_EN, "B"), 45 | "b": self.__seqToRE(NEPALI_MONTHS_EN, "b"), 46 | "p": self.__seqToRE( 47 | ( 48 | "AM", 49 | "PM", 50 | ), 51 | "p", 52 | ), 53 | "%": "%", 54 | } 55 | ) 56 | 57 | def __seqToRE(self, to_convert, directive): 58 | """Convert a list to a regex string for matching a directive. 59 | Want possible matching values to be from longest to shortest. This 60 | prevents the possibility of a match occurring for a value that also 61 | a substring of a larger value that should have matched (e.g., 'abc' 62 | matching when 'abcdef' should have been the match). 63 | """ 64 | to_convert = sorted(to_convert, key=len, reverse=True) 65 | for value in to_convert: 66 | if value != "": 67 | break 68 | else: 69 | return "" 70 | regex = "|".join(re.escape(stuff) for stuff in to_convert) 71 | regex = f"(?P<{directive}>{regex}" 72 | return f"{regex})" 73 | 74 | def pattern(self, date_format): 75 | """ 76 | Handle conversion from format directives to regex. 77 | """ 78 | processed_format = "" 79 | regex_chars = re.compile(r"([\\.^$*+?\(\){}\[\]|])") 80 | date_format = regex_chars.sub(r"\\\1", date_format) 81 | whitespace_replacement = re.compile(r"\s+") 82 | date_format = whitespace_replacement.sub(r"\\s+", date_format) 83 | while "%" in date_format: 84 | directive_index = date_format.index("%") + 1 85 | index_increment = 1 86 | 87 | if date_format[directive_index] == "-": 88 | index_increment = 2 89 | 90 | if ( 91 | date_format[directive_index : directive_index + index_increment] 92 | not in self 93 | ): 94 | return None 95 | 96 | processed_format = "%s%s%s" % ( 97 | processed_format, 98 | date_format[: directive_index - 1], 99 | self[date_format[directive_index : directive_index + index_increment]], 100 | ) 101 | date_format = date_format[directive_index + index_increment :] 102 | return f"^{processed_format}{date_format}$" 103 | 104 | def compile(self, date_format): 105 | """Return a compiled re object for the format string.""" 106 | return re.compile(self.pattern(date_format), re.IGNORECASE) 107 | 108 | 109 | def get_nepali_time_re_object(): 110 | global __nepali_time_re__CACHE 111 | if __nepali_time_re__CACHE is None: 112 | __nepali_time_re__CACHE = NepaliTimeRE() 113 | return __nepali_time_re__CACHE 114 | 115 | 116 | def extract(datetime_str, format): 117 | """ 118 | Extracts year, month, day, hour, minute, etc from the given format. 119 | 120 | eg. 121 | USAGE: extract("2078-01-12", format="%Y-%m-%d") 122 | INPUT: 123 | datetime_str="2078-01-12" 124 | format="%Y-%m-%d" 125 | 126 | OUTPUT: 127 | { 128 | "Y": 2078, 129 | "m": 1, 130 | "d": 12, 131 | } 132 | """ 133 | 134 | # converting datetime_str to english if any exists 135 | datetime_str = nepali_to_english_text(datetime_str) 136 | 137 | re_compiled_format = get_nepali_time_re_object().compile(format) 138 | match = re_compiled_format.match(datetime_str) 139 | if match is None: 140 | return {} 141 | return match.groupdict() 142 | 143 | 144 | def __convert_12_hour_to_24_hour(hour: int, am_pm: str) -> int: 145 | """Converts hours from 12-hour format to 24-hour format. 146 | 147 | :param hour: The hour value to convert. 148 | :param am_pm: Either "am" or "pm"; signifies whether the hour is in am or pm. 149 | 150 | :returns: The value of `hour` converted to 24-hour format. 151 | """ 152 | am_pm = am_pm.lower() 153 | if am_pm == "am" and hour == 12: 154 | return 0 155 | elif am_pm == "pm" and hour != 12: 156 | return hour + 12 157 | return hour 158 | 159 | 160 | def __calculate_year(data: dict) -> Optional[int]: 161 | """Calculates the year value from given data. 162 | 163 | :param data: The dictionary of the format: 164 | { 165 | "Y": 2078, 166 | "b": "Mangsir", 167 | "d": 12, 168 | ... 169 | } 170 | 171 | :returns: The year value of given date data. 172 | """ 173 | if "y" in data: 174 | return int(data["y"]) + 2000 175 | elif "Y" in data: 176 | return int(data["Y"]) 177 | return None 178 | 179 | 180 | def __calculate_month(data: dict) -> nepalimonth: 181 | """Calculates the month value from given data. 182 | 183 | :param data: The dictionary of the format: 184 | { 185 | "Y": 2078, 186 | "b": "Mangsir", 187 | "d": 12, 188 | ... 189 | } 190 | 191 | :returns: The month value of given date data. 192 | """ 193 | if "m" in data: 194 | return nepalimonth(int(data["m"])) 195 | elif "b" in data: 196 | return nepalimonth(data["b"]) 197 | elif "B" in data: 198 | return nepalimonth(data["B"]) 199 | return nepalimonth(1) 200 | 201 | 202 | def __calculate_day(data: dict) -> int: 203 | """Calculates the day value from given data. 204 | 205 | :param data: The dictionary of the format: 206 | { 207 | "Y": 2078, 208 | "b": "Mangsir", 209 | "d": 12, 210 | ... 211 | } 212 | 213 | :returns: The day value of given date data. 214 | """ 215 | if "d" in data: 216 | return int(data["d"]) 217 | return 1 218 | 219 | 220 | def __calculate_hour_minute_seconds(data: dict) -> Tuple[int, int, int, int]: 221 | """Calculates hour, minutes, seconds and microseconds from given data. 222 | 223 | :param data: The dictionary of the format: 224 | { 225 | "Y": 2078, 226 | "b": "Mangsir", 227 | "d": 12, 228 | "H": 12, 229 | "M": 12, 230 | "S": 12, 231 | "f": 12, 232 | ... 233 | } 234 | 235 | :returns: A tuple of hour, minute, seconds and microseconds. 236 | """ 237 | hour = minute = second = fraction = 0 238 | if "H" in data: 239 | hour = int(data["H"]) 240 | elif "I" in data: 241 | am_pm = data.get("p", "").lower() or "am" 242 | hour = __convert_12_hour_to_24_hour(hour=int(data["I"]), am_pm=am_pm) 243 | 244 | if "M" in data: 245 | minute = int(data["M"]) 246 | 247 | if "S" in data: 248 | second = int(data["S"]) 249 | 250 | if "f" in data: 251 | s = data["f"] 252 | # Pad to always return microseconds. 253 | s += "0" * (6 - len(s)) 254 | fraction = int(s) 255 | 256 | return hour, minute, second, fraction 257 | 258 | 259 | def __calculate_weekday(data: dict) -> Optional[nepaliweek]: 260 | """Calculates the weekday of the date given in data. 261 | 262 | :param data: The data that describes the date. 263 | 264 | :returns: The weekday value; 0 for Sunday, 1 for Monday, etc. 265 | """ 266 | if "a" in data: 267 | return nepaliweek(data["a"]) 268 | elif "A" in data: 269 | return nepaliweek(data["A"]) 270 | elif "w" in data: 271 | return nepaliweek((int(data["w"]) - 1) % 7) 272 | return None 273 | 274 | 275 | def transform(data: dict): 276 | """ 277 | transforms different format data to uniform data 278 | 279 | eg. 280 | INPUT: 281 | data = { 282 | "Y": 2078, 283 | "b": "Mangsir", 284 | "d": 12, 285 | ... 286 | } 287 | 288 | OUTPUT: 289 | { 290 | "year": 2078, 291 | "month": 8, 292 | "day": 12, 293 | ... 294 | } 295 | """ 296 | 297 | year = __calculate_year(data) 298 | month = __calculate_month(data) 299 | day = __calculate_day(data) 300 | hour, minute, second, fraction = __calculate_hour_minute_seconds(data) 301 | weekday = __calculate_weekday(data) 302 | 303 | return { 304 | "year": year, 305 | "month": month, 306 | "day": day, 307 | "hour": hour, 308 | "minute": minute, 309 | "second": second, 310 | "microsecond": fraction, 311 | "weekday": weekday, 312 | } 313 | 314 | 315 | def validate(datetime_str, format): 316 | """ 317 | validates datetime_str with the format 318 | Perform step by step test for fast performance. The steps are: 319 | - 320 | - 321 | returns False if validation failed 322 | returns nepalidatetime object if validation success. 323 | """ 324 | 325 | # 1. validate if format is correct. 326 | if get_nepali_time_re_object().pattern(format) is None: 327 | # regex pattern generation failed 328 | return None 329 | 330 | # 2. validate if parse result if not empty 331 | parsed_result = extract(datetime_str, format) 332 | if parsed_result.get("Y") is None and parsed_result.get("y") is None: 333 | # compilation failed or year included 334 | return None 335 | 336 | # 3. validate if transformation 337 | transformed_data = transform(parsed_result) 338 | if transformed_data.get("year") is None: 339 | # could not transform data, not getting year 340 | return None 341 | 342 | # 4. create the datetime object 343 | return nepalidatetime( 344 | year=transformed_data["year"], 345 | month=transformed_data["month"], 346 | day=transformed_data["day"], 347 | hour=transformed_data["hour"], 348 | minute=transformed_data["minute"], 349 | second=transformed_data["second"], 350 | microsecond=transformed_data["microsecond"], 351 | ) 352 | -------------------------------------------------------------------------------- /nepali/tests/test_phone_number.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest import mock 3 | 4 | from nepali.phone_number import ( 5 | Operator, 6 | _get_area_code, 7 | _get_operator, 8 | _parse_landline_number, 9 | _parse_mobile_number, 10 | get_exact_number, 11 | is_landline_number, 12 | is_mobile_number, 13 | is_valid, 14 | parse, 15 | ) 16 | 17 | 18 | class TestOperator(unittest.TestCase): 19 | def test_operator_with_invalid_data(self): 20 | with self.assertRaises(ValueError): 21 | Operator("Hello") 22 | 23 | def test_operator_with_valid_data(self): 24 | operator = Operator("Nepal Telecom") 25 | self.assertEqual(operator, Operator.NEPAL_TELECOM) 26 | 27 | def test_operator_str(self): 28 | operator = Operator.NEPAL_TELECOM 29 | self.assertEqual(str(operator), operator.value) 30 | 31 | def test_operator_repr(self): 32 | operator = Operator.NEPAL_TELECOM 33 | self.assertEqual(repr(operator), f"") 34 | 35 | 36 | class TestPhoneNumberValidation(unittest.TestCase): 37 | def test_is_mobile_number(self): 38 | # True case 39 | self.assertEqual(is_mobile_number("9779842536789"), True) 40 | self.assertEqual(is_mobile_number("+9779842536789"), True) 41 | self.assertEqual(is_mobile_number("+977-9842536789"), True) 42 | self.assertEqual(is_mobile_number("9842536789"), True) 43 | self.assertEqual(is_mobile_number("9852536789"), True) 44 | self.assertEqual(is_mobile_number("9862536789"), True) 45 | self.assertEqual(is_mobile_number("9742536789"), True) 46 | self.assertEqual(is_mobile_number("9752536789"), True) 47 | self.assertEqual(is_mobile_number("9802536789"), True) 48 | self.assertEqual(is_mobile_number("9812536789"), True) 49 | self.assertEqual(is_mobile_number("9822536789"), True) 50 | self.assertEqual(is_mobile_number("9612536789"), True) 51 | self.assertEqual(is_mobile_number("9622536789"), True) 52 | self.assertEqual(is_mobile_number("9882536789"), True) 53 | self.assertEqual(is_mobile_number("9722536789"), True) 54 | self.assertEqual(is_mobile_number("9632536789"), True) 55 | 56 | # False case 57 | self.assertEqual(is_mobile_number(None), False) # type: ignore 58 | self.assertEqual(is_mobile_number(""), False) 59 | self.assertEqual(is_mobile_number("test"), False) 60 | self.assertEqual(is_mobile_number("977test"), False) 61 | self.assertEqual(is_mobile_number("+977test"), False) 62 | self.assertEqual(is_mobile_number("+977-test"), False) 63 | self.assertEqual(is_mobile_number("9551088967"), False) 64 | self.assertEqual(is_mobile_number("+977-98425367899"), False) 65 | self.assertEqual(is_mobile_number("+977-984253678"), False) 66 | self.assertEqual(is_mobile_number("984253678"), False) 67 | self.assertEqual(is_mobile_number("98425367899"), False) 68 | 69 | def test_is_landline_number(self): 70 | # True case 71 | self.assertEqual(is_landline_number("14231481"), True) 72 | self.assertEqual(is_landline_number("014231481"), True) 73 | self.assertEqual(is_landline_number("0215154579"), True) 74 | self.assertEqual(is_landline_number("+977-142314819"), True) 75 | self.assertEqual(is_landline_number("0154225639"), True) 76 | self.assertEqual(is_landline_number("9770154225639"), True) 77 | self.assertEqual(is_landline_number("0565114679"), True) 78 | self.assertEqual(is_landline_number("021420451"), True) 79 | self.assertEqual(is_landline_number("21420451"), True) 80 | self.assertEqual(is_landline_number("+977-0565114679"), True) 81 | 82 | # False case 83 | self.assertEqual(is_landline_number(None), False) # type: ignore 84 | self.assertEqual(is_landline_number(""), False) 85 | self.assertEqual(is_landline_number("test"), False) 86 | self.assertEqual(is_landline_number("977test"), False) 87 | self.assertEqual(is_landline_number("+977test"), False) 88 | self.assertEqual(is_landline_number("+977-test"), False) 89 | 90 | @mock.patch("nepali.phone_number.is_landline_number", return_value=False) 91 | @mock.patch("nepali.phone_number.is_mobile_number", return_value=False) 92 | def test_if__is_valid__returns_false_for_invalid_numbers(self, *_): 93 | self.assertEqual(is_valid("who_cares"), False) 94 | 95 | @mock.patch("nepali.phone_number.is_landline_number", return_value=True) 96 | @mock.patch("nepali.phone_number.is_mobile_number", return_value=False) 97 | def test_if__is_valid__returns_true_for_landline(self, *_): 98 | self.assertEqual(is_valid("who_cares"), True) 99 | 100 | @mock.patch("nepali.phone_number.is_landline_number", return_value=False) 101 | @mock.patch("nepali.phone_number.is_mobile_number", return_value=True) 102 | def test_if__is_valid__returns_true_for_mobile(self, *_): 103 | self.assertEqual(is_valid("who_cares"), True) 104 | 105 | @mock.patch("nepali.phone_number.is_landline_number", return_value=True) 106 | @mock.patch("nepali.phone_number.is_mobile_number", return_value=True) 107 | def test_if__is_valid__returns_true_if_both_mobile_and_landline_returns_true( 108 | self, *_ 109 | ): 110 | self.assertEqual(is_valid("who_cares"), True) 111 | 112 | 113 | class TestPhoneNumberFunctions(unittest.TestCase): 114 | def test_get_exact_number(self): 115 | self.assertEqual(get_exact_number("9842536789"), "9842536789") 116 | self.assertEqual(get_exact_number("9779842536789"), "9842536789") 117 | self.assertEqual(get_exact_number("+9779842536789"), "9842536789") 118 | self.assertEqual(get_exact_number("+977-9842536789"), "9842536789") 119 | self.assertEqual(get_exact_number(""), "") 120 | self.assertEqual(get_exact_number("test"), "test") 121 | self.assertEqual(get_exact_number("977test"), "test") 122 | self.assertEqual(get_exact_number("+977test"), "test") 123 | self.assertEqual(get_exact_number("+977-test"), "test") 124 | self.assertEqual(get_exact_number("+977-142314819"), "142314819") 125 | self.assertEqual(get_exact_number("0154225639"), "0154225639") 126 | self.assertEqual(get_exact_number("9770154225639"), "0154225639") 127 | self.assertEqual(get_exact_number("0565114679"), "0565114679") 128 | self.assertEqual(get_exact_number("984-2536789"), "9842536789") 129 | self.assertEqual(get_exact_number("984-2536-789"), "9842536789") 130 | with self.assertRaises(AttributeError): 131 | get_exact_number(None) # type: ignore 132 | 133 | 134 | class TestMobileNumberParser(unittest.TestCase): 135 | def test__get_operator__with_invalid_input(self): 136 | self.assertEqual(_get_operator("Hello there"), None) 137 | 138 | def test__get_operator__with_not_matching_input(self): 139 | self.assertEqual(_get_operator("9132536789"), None) 140 | 141 | def test__get_operator__for_ntc(self): 142 | self.assertEqual(_get_operator("9842536789"), Operator.NEPAL_TELECOM) 143 | self.assertEqual(_get_operator("9852536789"), Operator.NEPAL_TELECOM) 144 | self.assertEqual(_get_operator("9862536789"), Operator.NEPAL_TELECOM) 145 | # For CDMA (Nepal Telecom) 146 | self.assertEqual(_get_operator("9742536789"), Operator.NEPAL_TELECOM) 147 | self.assertEqual(_get_operator("9752536789"), Operator.NEPAL_TELECOM) 148 | 149 | def test__get_operator__for_ncell(self): 150 | self.assertEqual(_get_operator("9802536789"), Operator.NCELL) 151 | self.assertEqual(_get_operator("9812536789"), Operator.NCELL) 152 | self.assertEqual(_get_operator("9822536789"), Operator.NCELL) 153 | 154 | def test__get_operator__for_smart_cell(self): 155 | self.assertEqual(_get_operator("9612536789"), Operator.SMART_CELL) 156 | self.assertEqual(_get_operator("9622536789"), Operator.SMART_CELL) 157 | self.assertEqual(_get_operator("9882536789"), Operator.SMART_CELL) 158 | 159 | def test__get_operator__for_utl(self): 160 | self.assertEqual(_get_operator("9722536789"), Operator.UTL) 161 | 162 | def test__get_operator__for_hello_mobile(self): 163 | self.assertEqual(_get_operator("9632536789"), Operator.HELLO_MOBILE) 164 | 165 | @mock.patch("nepali.phone_number._get_operator", return_value="test") 166 | def test__parse_mobile_number_returns_valid_data(self, *_): 167 | data = _parse_mobile_number("+977-9842536789") 168 | self.assertEqual(data and data["type"], "Mobile") 169 | self.assertEqual(data and data["operator"], "test") 170 | self.assertEqual(data and data["number"], "9842536789") 171 | 172 | @mock.patch("nepali.phone_number._get_operator", return_value=None) 173 | def test__parse_mobile_number_on_invalid_operator(self, *_): 174 | data = _parse_mobile_number("+977-14231481") 175 | self.assertEqual(data, None) 176 | 177 | 178 | class TestLandlineNumberParser(unittest.TestCase): 179 | def test__get_area_code_for_kathmandu(self): 180 | self.assertEqual(_get_area_code("0142314819"), "01") 181 | self.assertEqual(_get_area_code("0154225639"), "01") 182 | 183 | def test__get_area_code_for_biratnagar(self): 184 | self.assertEqual(_get_area_code("021420431"), "021") 185 | 186 | def test__get_area_code_for_chitwan(self): 187 | self.assertEqual(_get_area_code("056520198"), "056") 188 | 189 | def test__get_area_code_for_jhapa(self): 190 | self.assertEqual(_get_area_code("023455125"), "023") 191 | 192 | def test__get_area_code_for_rupandehi(self): 193 | self.assertEqual(_get_area_code("071540029"), "071") 194 | 195 | def test__get_area_code_for_kaski(self): 196 | self.assertEqual(_get_area_code("061463076"), "061") 197 | 198 | def test__get_area_code_for_dhading(self): 199 | self.assertEqual(_get_area_code("010520133"), "010") 200 | 201 | def test__get_area_code_for_syangja(self): 202 | self.assertEqual(_get_area_code("063420211"), "063") 203 | 204 | @mock.patch("nepali.phone_number._get_area_code", return_value="test") 205 | def test__parse_landline_number_returns_valid_data(self, *_): 206 | data = _parse_landline_number("0142314819") 207 | self.assertEqual(data["type"], "Landline") 208 | self.assertEqual(data["area_code"], "test") 209 | self.assertEqual(data["number"], "0142314819") 210 | 211 | @mock.patch("nepali.phone_number._get_area_code", return_value="test") 212 | def test__parse_landline_number_returns_valid_data_for_977(self, *_): 213 | data = _parse_landline_number("+977142314819") 214 | self.assertEqual(data["type"], "Landline") 215 | self.assertEqual(data["area_code"], "test") 216 | self.assertEqual(data["number"], "0142314819") 217 | 218 | 219 | class TestParser(unittest.TestCase): 220 | def test_none_number_returns_none(self): 221 | self.assertEqual(parse(None), None) # type: ignore 222 | 223 | def test_empty_number_returns_none(self): 224 | self.assertEqual(parse(""), None) 225 | 226 | @mock.patch("nepali.phone_number.is_mobile_number", return_value=True) 227 | @mock.patch("nepali.phone_number._parse_mobile_number", return_value="Mobile") 228 | def test_mobile_number_parsing(self, *_): 229 | self.assertEqual(parse("number"), "Mobile") 230 | 231 | @mock.patch("nepali.phone_number.is_mobile_number", return_value=False) 232 | @mock.patch("nepali.phone_number.is_landline_number", return_value=True) 233 | @mock.patch("nepali.phone_number._parse_landline_number", return_value="Landline") 234 | def test_landline_number_parsing(self, *_): 235 | self.assertEqual(parse("number"), "Landline") 236 | 237 | @mock.patch("nepali.phone_number.is_mobile_number", return_value=False) 238 | @mock.patch("nepali.phone_number.is_landline_number", return_value=False) 239 | def test_invalid_number_returns_none(self, *_): 240 | self.assertEqual(parse("number"), None) 241 | 242 | 243 | class TestParseIntegration(unittest.TestCase): 244 | def test_parse_for_mobile_number(self): 245 | data = parse("+9779842536789") 246 | self.assertEqual(data and data["type"], "Mobile") 247 | self.assertEqual(data and data["number"], "9842536789") 248 | self.assertEqual(data and data["operator"], Operator.NEPAL_TELECOM) 249 | 250 | def test_parse_for_landline_number(self): 251 | data = parse("+977-142314819") 252 | self.assertEqual(data and data["type"], "Landline") 253 | self.assertEqual(data and data["number"], "0142314819") 254 | self.assertEqual(data and data["area_code"], "01") 255 | -------------------------------------------------------------------------------- /nepali/datetime/_datetime.py: -------------------------------------------------------------------------------- 1 | import datetime as pythonDateTime 2 | 3 | from nepali.date_converter import converter as nepali_date_converter 4 | from nepali.timezone import NepaliTimeZone, to_nepali_timezone, utc_now 5 | 6 | from ._nepalimonth import nepalimonth 7 | 8 | 9 | class formatter_class_mixin: 10 | """ 11 | mixin for date time formatter. 12 | adds methods `strftime(format)` 13 | """ 14 | 15 | def get_formatter_class(self): 16 | return self.__class__.init_formatter_class() 17 | 18 | @classmethod 19 | def init_formatter_class(cls): 20 | if not hasattr(cls, "_formatter_class_cache"): 21 | from ._formatter import NepaliDateTimeFormatter 22 | 23 | cls._formatter_class_cache = NepaliDateTimeFormatter 24 | return cls._formatter_class_cache 25 | 26 | @classmethod 27 | def get_strptime_method(cls): 28 | if not hasattr(cls, "_strptime_method_cache"): 29 | from .parser import strptime 30 | 31 | cls._strptime_method_cache = strptime 32 | return cls._strptime_method_cache 33 | 34 | def strftime(self, format: str) -> str: 35 | return self.strftime_en(format) 36 | 37 | def strftime_en(self, format: str) -> str: 38 | nepali_datetime_formatter = self.get_formatter_class() 39 | formatter = nepali_datetime_formatter(self, devanagari=False) 40 | return formatter.get_str(format) 41 | 42 | def strftime_ne(self, format: str) -> str: 43 | nepali_datetime_formatter = self.get_formatter_class() 44 | formatter = nepali_datetime_formatter(self, devanagari=True) 45 | return formatter.get_str(format) 46 | 47 | 48 | class nepalidate(formatter_class_mixin): 49 | def __init__(self, year, month, day) -> None: 50 | if isinstance(month, nepalimonth): 51 | month = month.value 52 | 53 | self.__year = year 54 | self.__month = month 55 | self.__day = day 56 | 57 | # converting to english date 58 | year_en, month_en, day_en = nepali_date_converter.nepali_to_english( 59 | year, month, day 60 | ) 61 | self.__python_date = pythonDateTime.date(year_en, month_en, day_en) 62 | 63 | def __str__(self): 64 | return self.strftime_en("%Y-%m-%d") 65 | 66 | def __repr__(self): 67 | return f" {self}" 68 | 69 | def to_datetime(self): 70 | return pythonDateTime.datetime.combine( 71 | self.__python_date, pythonDateTime.time(), tzinfo=NepaliTimeZone() 72 | ) 73 | 74 | def to_date(self): 75 | return self.__python_date 76 | 77 | def to_nepalidatetime(self): 78 | return nepalidatetime.from_nepali_date(self) 79 | 80 | # operators overloading 81 | 82 | def __add__(self, other): 83 | """addition""" 84 | if type(other) == pythonDateTime.timedelta: 85 | # timedelta object 86 | return nepalidate.from_date(self.to_date() + other) 87 | return NotImplemented 88 | 89 | def __sub__(self, other): 90 | """subtraction""" 91 | if type(other) == self.__class__: 92 | # nepalidate object 93 | return self.to_date() - other.to_date() 94 | 95 | elif type(other) == pythonDateTime.date: 96 | # python date object 97 | return self.to_date() - other 98 | 99 | elif type(other) == pythonDateTime.timedelta: 100 | # timedelta object 101 | return nepalidate.from_date(self.to_date() - other) 102 | 103 | return NotImplemented 104 | 105 | def __lt__(self, other): 106 | """less than""" 107 | if type(other) == self.__class__: 108 | # nepalidatetime object 109 | return self.to_date() < other.to_date() 110 | 111 | elif type(other) == pythonDateTime.date: 112 | # python date object 113 | return self.to_date() < other 114 | 115 | return NotImplemented 116 | 117 | def __le__(self, other): 118 | """less than equal""" 119 | if type(other) == self.__class__: 120 | # nepalidate object 121 | return self.to_date() <= other.to_date() 122 | 123 | elif type(other) == pythonDateTime.date: 124 | # python date object 125 | return self.to_date() <= other 126 | 127 | return NotImplemented 128 | 129 | def __eq__(self, other): 130 | """equal""" 131 | if type(other) == self.__class__: 132 | # nepalidate object 133 | return self.to_date() == other.to_date() 134 | 135 | elif type(other) == pythonDateTime.date: 136 | # python date object 137 | return self.to_date() == other 138 | 139 | return False 140 | 141 | def __ne__(self, other): 142 | """not equal""" 143 | if type(other) == self.__class__: 144 | # nepalidate object 145 | return self.to_date() != other.to_date() 146 | 147 | elif type(other) == pythonDateTime.date: 148 | # python date object 149 | return self.to_date() != other 150 | 151 | return True 152 | 153 | def __gt__(self, other): 154 | """greater than""" 155 | if type(other) == self.__class__: 156 | # nepalidate object 157 | return self.to_date() > other.to_date() 158 | 159 | elif type(other) == pythonDateTime.date: 160 | # python date object 161 | return self.to_date() > other 162 | 163 | return NotImplemented 164 | 165 | def __ge__(self, other): 166 | """greater than equal""" 167 | if type(other) == self.__class__: 168 | # nepalidate object 169 | return self.to_date() >= other.to_date() 170 | 171 | elif type(other) == pythonDateTime.date: 172 | # python date object 173 | return self.to_date() >= other 174 | 175 | return NotImplemented 176 | 177 | # static methods 178 | @classmethod 179 | def strptime(cls, datetime_str, format): 180 | nepalidatetime_strptime = cls.get_strptime_method() 181 | return nepalidatetime_strptime(datetime_str, format=format).date() 182 | 183 | @staticmethod 184 | def now(): 185 | return nepalidate.today() 186 | 187 | @staticmethod 188 | def today(): 189 | return nepalidate.from_date(pythonDateTime.date.today()) 190 | 191 | @staticmethod 192 | def from_datetime(datetime_object): 193 | return nepalidate.from_date(datetime_object.date()) 194 | 195 | @staticmethod 196 | def from_date(date_object): 197 | return nepalidate( 198 | *nepali_date_converter.english_to_nepali( 199 | date_object.year, date_object.month, date_object.day 200 | ) 201 | ) 202 | 203 | @staticmethod 204 | def from_nepalidatetime(datetime_object): 205 | return datetime_object.date() 206 | 207 | # property 208 | 209 | def weekday(self): 210 | """ 211 | Sunday => 0, Saturday => 6 212 | """ 213 | return (self.__python_date.weekday() + 1) % 7 214 | 215 | # nepali date properties 216 | @property 217 | def year(self): 218 | return self.__year 219 | 220 | @property 221 | def month(self): 222 | return self.__month 223 | 224 | @property 225 | def day(self): 226 | return self.__day 227 | 228 | 229 | class nepalitime(pythonDateTime.time): 230 | def __repr__(self): 231 | return f" {self}" 232 | 233 | # static methods 234 | 235 | @staticmethod 236 | def now(): 237 | dt_now = pythonDateTime.datetime.now() 238 | return nepalitime(dt_now.hour, dt_now.minute, dt_now.second, dt_now.microsecond) 239 | 240 | 241 | class nepalidatetime(formatter_class_mixin): 242 | """ 243 | nepalidatetime 244 | """ 245 | 246 | def __init__(self, year, month, day, hour=0, minute=0, second=0, microsecond=0): 247 | self.__np_date = nepalidate(year, month, day) 248 | self.__np_time = nepalitime(hour, minute, second, microsecond) 249 | 250 | def __str__(self): 251 | return f"{self.__np_date} {self.__np_time}" 252 | 253 | def __repr__(self): 254 | return f" {self}" 255 | 256 | # operator overloading 257 | 258 | def __add__(self, other): 259 | """addition""" 260 | if type(other) == pythonDateTime.timedelta: 261 | # timedelta object 262 | return nepalidatetime.from_datetime(self.to_datetime() + other) 263 | 264 | return NotImplemented 265 | 266 | def __sub__(self, other): 267 | """subtraction""" 268 | if type(other) == self.__class__: 269 | # nepalidatetime object 270 | return self.to_datetime() - other.to_datetime() 271 | 272 | elif type(other) == pythonDateTime.datetime: 273 | # python datetime object 274 | return self.to_datetime() - to_nepali_timezone(other) 275 | 276 | elif type(other) == pythonDateTime.timedelta: 277 | # timedelta object 278 | return nepalidatetime.from_datetime(self.to_datetime() - other) 279 | 280 | return NotImplemented 281 | 282 | def __lt__(self, other): 283 | """less than""" 284 | if type(other) == self.__class__: 285 | # nepalidatetime object 286 | return self.to_datetime() < other.to_datetime() 287 | 288 | elif type(other) == pythonDateTime.datetime: 289 | # python datetime object 290 | return self.to_datetime() < to_nepali_timezone(other) 291 | 292 | return NotImplemented 293 | 294 | def __le__(self, other): 295 | """less than equal""" 296 | if type(other) == self.__class__: 297 | # nepalidatetime object 298 | return self.to_datetime() <= other.to_datetime() 299 | 300 | elif type(other) == pythonDateTime.datetime: 301 | # python datetime object 302 | return self.to_datetime() <= to_nepali_timezone(other) 303 | 304 | return NotImplemented 305 | 306 | def __eq__(self, other): 307 | """equal""" 308 | if type(other) == self.__class__: 309 | # nepalidatetime object 310 | return self.to_datetime() == other.to_datetime() 311 | 312 | elif type(other) == pythonDateTime.datetime: 313 | # python datetime object 314 | return self.to_datetime() == to_nepali_timezone(other) 315 | 316 | return False 317 | 318 | def __ne__(self, other): 319 | """not equal""" 320 | if type(other) == self.__class__: 321 | # nepalidatetime object 322 | return self.to_datetime() != other.to_datetime() 323 | 324 | elif type(other) == pythonDateTime.datetime: 325 | # python datetime object 326 | return self.to_datetime() != to_nepali_timezone(other) 327 | 328 | return True 329 | 330 | def __gt__(self, other): 331 | """greater than""" 332 | if type(other) == self.__class__: 333 | # nepalidatetime object 334 | return self.to_datetime() > other.to_datetime() 335 | 336 | elif type(other) == pythonDateTime.datetime: 337 | # python datetime object 338 | return self.to_datetime() > to_nepali_timezone(other) 339 | 340 | return NotImplemented 341 | 342 | def __ge__(self, other): 343 | """greater than equal""" 344 | if type(other) == self.__class__: 345 | # nepalidatetime object 346 | return self.to_datetime() >= other.to_datetime() 347 | 348 | elif type(other) == pythonDateTime.datetime: 349 | # python datetime object 350 | return self.to_datetime() >= to_nepali_timezone(other) 351 | 352 | return NotImplemented 353 | 354 | # object transformation 355 | 356 | def to_datetime(self): 357 | return pythonDateTime.datetime.combine( 358 | self.__np_date.to_date(), self.__np_time, tzinfo=NepaliTimeZone() 359 | ) 360 | 361 | def to_date(self): 362 | return self.to_datetime().date() 363 | 364 | def date(self): 365 | return self.__np_date 366 | 367 | def time(self): 368 | return self.__np_time 369 | 370 | # static methods 371 | 372 | @classmethod 373 | def strptime(cls, datetime_str, format): 374 | nepalidatetime_strptime = cls.get_strptime_method() 375 | return nepalidatetime_strptime(datetime_str, format=format) 376 | 377 | @staticmethod 378 | def from_datetime(dt): 379 | dt = to_nepali_timezone(dt) 380 | nd = nepalidate.from_date(dt.date()) 381 | return nepalidatetime( 382 | nd.year, nd.month, nd.day, dt.hour, dt.minute, dt.second, dt.microsecond 383 | ) 384 | 385 | @staticmethod 386 | def from_date(date_object): 387 | nepali_date_object = nepalidate.from_date(date_object) 388 | return nepalidatetime.from_nepali_date(nepali_date_object) 389 | 390 | @staticmethod 391 | def from_nepali_date(nepali_date_object): 392 | return nepalidatetime( 393 | nepali_date_object.year, nepali_date_object.month, nepali_date_object.day 394 | ) 395 | 396 | @staticmethod 397 | def now(): 398 | return nepalidatetime.from_datetime(utc_now()) 399 | 400 | # property 401 | 402 | @property 403 | def year(self): 404 | return self.__np_date.year 405 | 406 | @property 407 | def month(self): 408 | return self.__np_date.month 409 | 410 | @property 411 | def day(self): 412 | return self.__np_date.day 413 | 414 | def weekday(self): 415 | """ 416 | Sunday => 0, Saturday => 6 417 | """ 418 | return self.__np_date.weekday() 419 | 420 | @property 421 | def hour(self): 422 | return self.__np_time.hour 423 | 424 | @property 425 | def minute(self): 426 | return self.__np_time.minute 427 | 428 | @property 429 | def second(self): 430 | return self.__np_time.second 431 | -------------------------------------------------------------------------------- /nepali/tests/test_datetime.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import unittest 3 | 4 | from nepali.datetime import nepalidate, nepalidatetime, nepalitime 5 | from nepali.datetime.utils import to_nepalidate, to_nepalidatetime 6 | from nepali.exceptions import InvalidNepaliDateTimeObjectException 7 | from nepali.timezone import NepaliTimeZone 8 | 9 | 10 | class TestNepaliDateTime(unittest.TestCase): 11 | # 12 | # nepalidate 13 | 14 | def test_nepalidate_now_and_today(self): 15 | today = nepalidate.today() 16 | now = nepalidate.now() 17 | self.assertEqual(today, now, msg="nepalidate today is not equals to now.") 18 | 19 | def test_nepalidate_comparison(self): 20 | nepalidate_obj1 = nepalidate(year=2051, month=4, day=28) 21 | 22 | # test equal date 23 | nepalidate_obj2 = nepalidate(year=2051, month=4, day=28) 24 | self.assertEqual( 25 | nepalidate_obj1, nepalidate_obj2, "Same nepali date are not checked equals." 26 | ) 27 | 28 | # test different date 29 | nepalidate_obj3 = nepalidate(year=2051, month=4, day=29) 30 | self.assertNotEqual( 31 | nepalidate_obj1, 32 | nepalidate_obj3, 33 | "Different nepali date are checked as equal.", 34 | ) 35 | 36 | # 37 | # test addition 38 | 39 | # test with timedelta 40 | nt = nepalidate(year=2051, month=4, day=29) 41 | nt = nt + datetime.timedelta(days=1) 42 | self.assertEqual((nt.year, nt.month, nt.day), (2051, 4, 30)) 43 | 44 | # test without timedelta 45 | with self.assertRaises(TypeError): 46 | nt = nt + 1 47 | 48 | # 49 | # test subtraction 50 | 51 | # test with nepali date 52 | td = nepalidate(year=2051, month=4, day=30) - nepalidate( 53 | year=2051, month=4, day=29 54 | ) 55 | self.assertEqual(td.days, 1) 56 | 57 | # test with python date 58 | td = nepalidate(year=2051, month=4, day=30) - datetime.date( 59 | year=1994, month=8, day=13 60 | ) 61 | self.assertEqual(td.days, 1) 62 | 63 | # test with timedelta 64 | nt = nepalidate(year=2051, month=4, day=30) - datetime.timedelta(days=1) 65 | self.assertEqual((nt.year, nt.month, nt.day), (2051, 4, 29)) 66 | 67 | # test without timedelta 68 | with self.assertRaises(TypeError): 69 | nt = nt - 1 70 | 71 | # 72 | # test less than 73 | self.assertEqual(nepalidate(2051, 4, 29) < nepalidate(2051, 4, 30), True) 74 | self.assertEqual(nepalidate(2051, 4, 29) < nepalidate(2051, 4, 29), False) 75 | self.assertEqual(nepalidate(2051, 4, 29) < datetime.date(1994, 8, 14), True) 76 | self.assertEqual(nepalidate(2051, 4, 29) < datetime.date(1994, 8, 13), False) 77 | with self.assertRaises(TypeError): 78 | _ = nepalidate(2051, 4, 29) < 0 79 | 80 | # 81 | # test less than equal 82 | self.assertEqual(nepalidate(2051, 4, 29) <= nepalidate(2051, 4, 30), True) 83 | self.assertEqual(nepalidate(2051, 4, 29) <= nepalidate(2051, 4, 29), True) 84 | self.assertEqual(nepalidate(2051, 4, 30) <= nepalidate(2051, 4, 29), False) 85 | self.assertEqual(nepalidate(2051, 4, 29) <= datetime.date(1994, 8, 14), True) 86 | self.assertEqual(nepalidate(2051, 4, 29) <= datetime.date(1994, 8, 13), True) 87 | self.assertEqual(nepalidate(2051, 4, 30) <= datetime.date(1994, 8, 13), False) 88 | with self.assertRaises(TypeError): 89 | _ = nepalidate(2051, 4, 29) <= 0 90 | 91 | # 92 | # test greater than 93 | self.assertEqual(nepalidate(2051, 4, 30) > nepalidate(2051, 4, 29), True) 94 | self.assertEqual(nepalidate(2051, 4, 30) > nepalidate(2051, 4, 30), False) 95 | self.assertEqual(nepalidate(2051, 4, 30) > datetime.date(1994, 8, 14), False) 96 | self.assertEqual(nepalidate(2051, 4, 30) > datetime.date(1994, 8, 13), True) 97 | with self.assertRaises(TypeError): 98 | _ = nepalidate(2051, 4, 29) > 0 99 | 100 | # 101 | # test greater than equal 102 | self.assertEqual(nepalidate(2051, 4, 30) >= nepalidate(2051, 4, 30), True) 103 | self.assertEqual(nepalidate(2051, 4, 30) >= nepalidate(2051, 4, 29), True) 104 | self.assertEqual(nepalidate(2051, 4, 29) >= nepalidate(2051, 4, 30), False) 105 | self.assertEqual(nepalidate(2051, 4, 30) >= datetime.date(1994, 8, 14), True) 106 | self.assertEqual(nepalidate(2051, 4, 30) >= datetime.date(1994, 8, 13), True) 107 | self.assertEqual(nepalidate(2051, 4, 29) >= datetime.date(1994, 8, 14), False) 108 | with self.assertRaises(TypeError): 109 | _ = nepalidate(2051, 4, 29) >= 0 110 | 111 | def test_nepalidate_year_month_day(self): 112 | nepalidate_obj = nepalidate(year=2051, month=4, day=28) 113 | self.assertEqual(nepalidate_obj.year, 2051) 114 | self.assertEqual(nepalidate_obj.month, 4) 115 | self.assertEqual(nepalidate_obj.day, 28) 116 | self.assertEqual(nepalidate_obj.weekday(), 5) 117 | 118 | def test_nepalidate_from_date(self): 119 | python_date_obj = datetime.date(year=1994, month=8, day=12) 120 | nepalidate_obj = nepalidate.from_date(python_date_obj) 121 | self.assertEqual( 122 | python_date_obj, 123 | nepalidate_obj.to_date(), 124 | "nepalidate from_date and to_date are not equal.", 125 | ) 126 | 127 | def test_nepalidate_from_datetime(self): 128 | python_datetime_obj = datetime.datetime( 129 | year=1994, month=8, day=12, hour=4, minute=8, second=12 130 | ) 131 | nepalidate_obj = nepalidate.from_datetime(python_datetime_obj) 132 | self.assertEqual( 133 | python_datetime_obj.date(), 134 | nepalidate_obj.to_date(), 135 | "nepalidate from_datetime and to_datetime are not equal.", 136 | ) 137 | 138 | def test_nepalidate_strftime(self): 139 | nepalidate_obj = nepalidate(year=2051, month=4, day=28) 140 | self.assertEqual(nepalidate_obj.strftime("%Y-%m-%d"), "2051-04-28") 141 | self.assertEqual( 142 | nepalidate_obj.strftime("%a %A %w %d %b %B %m %y %Y"), 143 | "Fri Friday 5 28 Sharwan Sharwan 04 51 2051", 144 | ) 145 | self.assertEqual(nepalidate_obj.strftime_en("%Y-%m-%d"), "2051-04-28") 146 | self.assertEqual( 147 | nepalidate_obj.strftime_en("%a %A %w %d %b %B %m %y %Y"), 148 | "Fri Friday 5 28 Sharwan Sharwan 04 51 2051", 149 | ) 150 | self.assertEqual(nepalidate_obj.strftime_ne("%Y-%m-%d"), "२०५१-०४-२८") 151 | self.assertEqual( 152 | nepalidate_obj.strftime_ne("%a %A %w %d %b %B %m %y %Y"), 153 | "शुक्र शुक्रबार ५ २८ साउन साउन ०४ ५१ २०५१", 154 | ) 155 | 156 | def test_nepalidate_strptime(self): 157 | nepalidatetime_obj = nepalidate.strptime("2078-01-12", format="%Y-%m-%d") 158 | self.assertEqual(nepalidatetime_obj.year, 2078) 159 | self.assertEqual(nepalidatetime_obj.month, 1) 160 | self.assertEqual(nepalidatetime_obj.day, 12) 161 | 162 | # 163 | # nepalitime 164 | 165 | def test_nepalitime_now(self): 166 | nepalitime_obj, dt_now = nepalitime.now(), datetime.datetime.now() 167 | self.assertEqual(nepalitime_obj.hour, dt_now.hour) 168 | self.assertEqual(nepalitime_obj.minute, dt_now.minute) 169 | self.assertEqual(nepalitime_obj.second, dt_now.second) 170 | 171 | # 172 | # nepalidatetime 173 | 174 | def test_nepalidatetime_from_date(self): 175 | python_datetime_obj = datetime.datetime( 176 | year=1994, month=8, day=12, hour=5, minute=28, second=23 177 | ) 178 | nepalidatetime_obj = nepalidatetime.from_date(python_datetime_obj) 179 | self.assertEqual(python_datetime_obj.date(), nepalidatetime_obj.to_date()) 180 | 181 | def test_nepalidatetime_from_datetime(self): 182 | python_datetime_obj = datetime.datetime( 183 | year=1994, 184 | month=8, 185 | day=12, 186 | hour=5, 187 | minute=28, 188 | second=23, 189 | tzinfo=NepaliTimeZone(), 190 | ) 191 | nepalidatetime_obj = nepalidatetime.from_datetime(python_datetime_obj) 192 | self.assertEqual( 193 | python_datetime_obj, 194 | nepalidatetime_obj.to_datetime(), 195 | msg="{} and {} are not equal.".format( 196 | python_datetime_obj, nepalidatetime_obj.to_datetime() 197 | ), 198 | ) 199 | 200 | def test_nepalidatetime_strftime(self): 201 | nepalidatetime_obj = nepalidatetime( 202 | year=2051, month=4, day=28, hour=5, minute=28, second=23 203 | ) 204 | self.assertEqual( 205 | nepalidatetime_obj.strftime("%Y-%m-%d %H:%M:%S"), "2051-04-28 05:28:23" 206 | ) 207 | self.assertEqual( 208 | nepalidatetime_obj.strftime("%a %A %w %d %b %B %m %y %Y %H %I %p %M %S"), 209 | "Fri Friday 5 28 Sharwan Sharwan 04 51 2051 05 05 AM 28 23", 210 | ) 211 | self.assertEqual( 212 | nepalidatetime_obj.strftime_en("%Y-%m-%d %H:%M:%S"), "2051-04-28 05:28:23" 213 | ) 214 | self.assertEqual( 215 | nepalidatetime_obj.strftime_en("%a %A %w %d %b %B %m %y %Y %H %I %p %M %S"), 216 | "Fri Friday 5 28 Sharwan Sharwan 04 51 2051 05 05 AM 28 23", 217 | ) 218 | self.assertEqual( 219 | nepalidatetime_obj.strftime_en( 220 | "%a %A %w %d %-d %b %B %m %-m %y %Y %H %-H %I %-I %p %M %-M %S %-S %%" 221 | ), 222 | "Fri Friday 5 28 28 Sharwan Sharwan 04 4 51 2051 05 5 05 5 AM 28 28 23 23 %", 223 | ) 224 | self.assertEqual( 225 | nepalidatetime_obj.strftime_ne("%Y-%m-%d %H:%M:%S"), "२०५१-०४-२८ ०५:२८:२३" 226 | ) 227 | self.assertEqual( 228 | nepalidatetime_obj.strftime_ne("%a %A %w %d %b %B %m %y %Y %H %I %p %M %S"), 229 | "शुक्र शुक्रबार ५ २८ साउन साउन ०४ ५१ २०५१ ०५ ०५ शुभप्रभात २८ २३", 230 | ) 231 | 232 | def test_nepalidatetime_strptime(self): 233 | nepalidatetime_obj = nepalidatetime.strptime( 234 | "2078-01-12 13:12", format="%Y-%m-%d %H:%M" 235 | ) 236 | self.assertEqual(nepalidatetime_obj.year, 2078) 237 | self.assertEqual(nepalidatetime_obj.month, 1) 238 | self.assertEqual(nepalidatetime_obj.day, 12) 239 | self.assertEqual(nepalidatetime_obj.hour, 13) 240 | self.assertEqual(nepalidatetime_obj.minute, 12) 241 | 242 | def test_nepalidatetime_timedelta(self): 243 | nepalidatetime_obj1 = nepalidatetime( 244 | year=2051, month=4, day=28, hour=5, minute=28, second=23 245 | ) 246 | nepalidatetime_obj2 = nepalidatetime( 247 | year=2051, month=4, day=29, hour=5, minute=28, second=23 248 | ) 249 | nepalidatetime_obj1 = nepalidatetime_obj1 + datetime.timedelta(days=1) 250 | self.assertEqual( 251 | nepalidatetime_obj1, 252 | nepalidatetime_obj2, 253 | msg=f"{nepalidatetime_obj1} and {nepalidatetime_obj2} are not equal", 254 | ) 255 | 256 | 257 | class TestDatetimeUtils(unittest.TestCase): 258 | # to_nepalidate 259 | def test_to_nepalidate_raises_exception_on_invalid_input(self): 260 | with self.assertRaises(InvalidNepaliDateTimeObjectException): 261 | to_nepalidate("Invalid input") 262 | 263 | def test_to_nepalidate_from_python_date(self): 264 | np_date = to_nepalidate(datetime.date(2023, 2, 26)) 265 | self.assertSequenceEqual( 266 | (np_date.year, np_date.month, np_date.day), (2079, 11, 14) 267 | ) 268 | 269 | def test_to_nepalidate_from_python_datetime(self): 270 | np_date = to_nepalidate(datetime.datetime(2023, 2, 26, 1, 12, 13)) 271 | self.assertSequenceEqual( 272 | (np_date.year, np_date.month, np_date.day), (2079, 11, 14) 273 | ) 274 | 275 | def test_to_nepalidate_from_nepalidate(self): 276 | np_date = to_nepalidate(nepalidate(2079, 11, 14)) 277 | self.assertSequenceEqual( 278 | (np_date.year, np_date.month, np_date.day), (2079, 11, 14) 279 | ) 280 | 281 | def test_to_nepalidate_from_nepalidatetime(self): 282 | np_date = to_nepalidate(nepalidatetime(2079, 11, 14, 1, 12, 13)) 283 | self.assertSequenceEqual( 284 | (np_date.year, np_date.month, np_date.day), (2079, 11, 14) 285 | ) 286 | 287 | # to_nepalidatetime 288 | def test_to_nepalidatetime_raises_exception_on_invalid_input(self): 289 | with self.assertRaises(InvalidNepaliDateTimeObjectException): 290 | to_nepalidatetime("Invalid input") 291 | 292 | def test_to_nepalidatetime_from_python_date(self): 293 | np_date = to_nepalidatetime(datetime.date(2023, 2, 26)) 294 | self.assertSequenceEqual( 295 | ( 296 | np_date.year, 297 | np_date.month, 298 | np_date.day, 299 | np_date.hour, 300 | np_date.minute, 301 | np_date.second, 302 | ), 303 | (2079, 11, 14, 0, 0, 0), 304 | ) 305 | 306 | def test_to_nepalidatetime_from_python_datetime(self): 307 | np_date = to_nepalidatetime( 308 | datetime.datetime(2023, 2, 26, 4, 30, 13, tzinfo=datetime.timezone.utc) 309 | ) 310 | self.assertSequenceEqual( 311 | ( 312 | np_date.year, 313 | np_date.month, 314 | np_date.day, 315 | np_date.hour, 316 | np_date.minute, 317 | np_date.second, 318 | ), 319 | (2079, 11, 14, 10, 15, 13), 320 | ) 321 | 322 | def test_to_nepalidatetime_from_nepalidate(self): 323 | np_date = to_nepalidatetime(nepalidate(2079, 11, 14)) 324 | self.assertSequenceEqual( 325 | ( 326 | np_date.year, 327 | np_date.month, 328 | np_date.day, 329 | np_date.hour, 330 | np_date.minute, 331 | np_date.second, 332 | ), 333 | (2079, 11, 14, 0, 0, 0), 334 | ) 335 | 336 | def test_to_nepalidatetime_from_nepalidatetime(self): 337 | np_date = to_nepalidatetime(nepalidatetime(2079, 11, 14, 1, 12, 13)) 338 | self.assertSequenceEqual( 339 | ( 340 | np_date.year, 341 | np_date.month, 342 | np_date.day, 343 | np_date.hour, 344 | np_date.minute, 345 | np_date.second, 346 | ), 347 | (2079, 11, 14, 1, 12, 13), 348 | ) 349 | -------------------------------------------------------------------------------- /nepali/date_converter.py: -------------------------------------------------------------------------------- 1 | """ 2 | This python file contains nepali date converter module. 3 | 4 | USAGE: 5 | y, m, d = nepali_date_converter.english_to_nepali(2023, 1, 15) 6 | y, m, d = nepali_date_converter.nepali_to_english(2079, 10, 1) 7 | """ 8 | 9 | 10 | class NepaliDateConverter: 11 | """ 12 | Nepali Date Converter 13 | 14 | All the functions here contains the method to convert english date to nepali and nepali date to english. 15 | """ 16 | 17 | # Reference date for conversion is 2000/01/01 BS and 1943/4/14 AD 18 | NP_INITIAL_YEAR = 2000 19 | REFERENCE_EN_DATE = (1943, 4, 14) 20 | 21 | # Nepali months data 22 | # [ 23 | # ((LIST_OF_MONTHS), TOTAL_DAYS_IN_YEAR), 24 | # ... 25 | # ] 26 | # 27 | NP_MONTHS_DATA = [ 28 | ( 29 | (30, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31), 30 | 365, 31 | ), # 2000 BS - 1943/1944 AD 32 | ((31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30), 365), # 2001 BS 33 | ((31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30), 365), 34 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31), 366), 35 | ((30, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31), 365), 36 | ((31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30), 365), 37 | ((31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30), 365), 38 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31), 366), 39 | ((31, 31, 31, 32, 31, 31, 29, 30, 30, 29, 29, 31), 365), 40 | ((31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30), 365), 41 | ((31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30), 365), 42 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31), 366), 43 | ((31, 31, 31, 32, 31, 31, 29, 30, 30, 29, 30, 30), 365), 44 | ((31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30), 365), 45 | ((31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30), 365), 46 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31), 366), 47 | ((31, 31, 31, 32, 31, 31, 29, 30, 30, 29, 30, 30), 365), 48 | ((31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30), 365), 49 | ((31, 32, 31, 32, 31, 30, 30, 29, 30, 29, 30, 30), 365), 50 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31), 366), 51 | ((31, 31, 31, 32, 31, 31, 30, 29, 30, 29, 30, 30), 365), 52 | ((31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30), 365), 53 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 30), 365), 54 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31), 366), 55 | ((31, 31, 31, 32, 31, 31, 30, 29, 30, 29, 30, 30), 365), 56 | ((31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30), 365), 57 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31), 366), 58 | ((30, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31), 365), 59 | ((31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30), 365), 60 | ((31, 31, 32, 31, 32, 30, 30, 29, 30, 29, 30, 30), 365), 61 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31), 366), 62 | ((30, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31), 365), 63 | ((31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30), 365), 64 | ((31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30), 365), 65 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31), 366), 66 | ((30, 32, 31, 32, 31, 31, 29, 30, 30, 29, 29, 31), 365), 67 | ((31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30), 365), 68 | ((31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30), 365), 69 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31), 366), 70 | ((31, 31, 31, 32, 31, 31, 29, 30, 30, 29, 30, 30), 365), 71 | ((31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30), 365), 72 | ((31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30), 365), 73 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31), 366), 74 | ((31, 31, 31, 32, 31, 31, 29, 30, 30, 29, 30, 30), 365), 75 | ((31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30), 365), 76 | ((31, 32, 31, 32, 31, 30, 30, 29, 30, 29, 30, 30), 365), 77 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31), 366), 78 | ((31, 31, 31, 32, 31, 31, 30, 29, 30, 29, 30, 30), 365), 79 | ((31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30), 365), 80 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 30), 365), 81 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31), 366), # 2050 82 | ((31, 31, 31, 32, 31, 31, 30, 29, 30, 29, 30, 30), 365), 83 | ((31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30), 365), 84 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 30), 365), 85 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31), 366), 86 | ((31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30), 365), 87 | ((31, 31, 32, 31, 32, 30, 30, 29, 30, 29, 30, 30), 365), 88 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31), 366), 89 | ((30, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31), 365), 90 | ((31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30), 365), 91 | ((31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30), 365), 92 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31), 366), 93 | ((30, 32, 31, 32, 31, 31, 29, 30, 29, 30, 29, 31), 365), 94 | ((31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30), 365), 95 | ((31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30), 365), 96 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31), 366), 97 | ((31, 31, 31, 32, 31, 31, 29, 30, 30, 29, 29, 31), 365), 98 | ((31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30), 365), 99 | ((31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30), 365), 100 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31), 366), 101 | ((31, 31, 31, 32, 31, 31, 29, 30, 30, 29, 30, 30), 365), # 2070 102 | ((31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30), 365), 103 | ((31, 32, 31, 32, 31, 30, 30, 29, 30, 29, 30, 30), 365), 104 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31), 366), 105 | ((31, 31, 31, 32, 31, 31, 30, 29, 30, 29, 30, 30), 365), 106 | ((31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30), 365), 107 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 30), 365), 108 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31), 366), 109 | ((31, 31, 31, 32, 31, 31, 30, 29, 30, 29, 30, 30), 365), 110 | ((31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30), 365), 111 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 30), 365), # 2080 112 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31), 366), # 2081 113 | ((31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30), 365), # 2082 114 | ((31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30), 365), 115 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31), 366), 116 | ((30, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31), 365), 117 | ((31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30), 365), 118 | ((31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30), 365), 119 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31), 366), 120 | ((30, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31), 365), 121 | ((31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30), 365), 122 | ((31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30), 365), 123 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31), 366), 124 | ((31, 31, 31, 32, 31, 31, 29, 30, 30, 29, 29, 31), 365), 125 | ((31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30), 365), 126 | ((31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30), 365), 127 | ((31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31), 366), 128 | ((31, 31, 31, 32, 31, 31, 29, 30, 30, 29, 30, 30), 365), 129 | ((31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30), 365), 130 | ( 131 | (31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30), 132 | 365, 133 | ), # 2099 BS - 2042/2043 AD 134 | ] 135 | 136 | # english month constant data (will never change) 137 | EN_MONTHS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] 138 | EN_LEAP_YEAR_MONTHS = [ 139 | 31, 140 | 29, 141 | 31, 142 | 30, 143 | 31, 144 | 30, 145 | 31, 146 | 31, 147 | 30, 148 | 31, 149 | 30, 150 | 31, 151 | ] # Leap year months (Just 29 on Feb) 152 | 153 | @classmethod 154 | def en_min_year(cls): 155 | return cls.REFERENCE_EN_DATE[0] + 1 156 | 157 | @classmethod 158 | def en_max_year(cls): 159 | return cls.REFERENCE_EN_DATE[0] + len(cls.NP_MONTHS_DATA) - 1 160 | 161 | @classmethod 162 | def np_min_year(cls): 163 | return cls.NP_INITIAL_YEAR 164 | 165 | # utility methods 166 | 167 | @classmethod 168 | def np_max_year(cls): 169 | return cls.NP_INITIAL_YEAR + len(cls.NP_MONTHS_DATA) - 1 170 | 171 | def _is_leap_year(self, year): 172 | """checks if the english year is leap year or not""" 173 | if year % 4 == 0: 174 | if year % 100 == 0: 175 | return year % 400 == 0 176 | return True 177 | return False 178 | 179 | def _get_en_months(self, year): 180 | return self.EN_LEAP_YEAR_MONTHS if self._is_leap_year(year) else self.EN_MONTHS 181 | 182 | # 183 | # ENGLISH DATE CONVERSION 184 | 185 | def check_english_date(self, year, month, day): 186 | if year < self.en_min_year() or year > self.en_max_year(): 187 | return False 188 | if month < 1 or month > 12: 189 | return False 190 | if day < 1 or day > 31: 191 | return False 192 | return True 193 | 194 | def _get_total_days_from_english_date(self, year, month, day): 195 | """counts and returns total days from the date 0000-01-01""" 196 | total_days = year * 365 + day 197 | for i in range(0, month - 1): 198 | total_days = total_days + self.EN_MONTHS[i] 199 | 200 | # adding leap days (ie. leap year count) 201 | if month <= 2: # checking February month (where leap exists) 202 | year -= 1 203 | total_days += year // 4 - year // 100 + year // 400 204 | 205 | return total_days 206 | 207 | def english_to_nepali(self, year, month, day): 208 | """ 209 | Converts english date to nepali 210 | return year, month, day 211 | """ 212 | # VALIDATION 213 | # checking if date is in range 214 | if not self.check_english_date(year, month, day): 215 | raise ValueError("Date out of range") 216 | 217 | # REFERENCE 218 | np_year, np_month, np_day = self.NP_INITIAL_YEAR, 1, 1 219 | 220 | # DIFFERENCE 221 | # calculating days count from the reference date 222 | difference = abs( 223 | self._get_total_days_from_english_date(year, month, day) 224 | - self._get_total_days_from_english_date(*self.REFERENCE_EN_DATE) 225 | ) 226 | 227 | # YEAR 228 | # Incrementing year until the difference remains less than 365 229 | year_data_index = 0 230 | while difference >= self.NP_MONTHS_DATA[year_data_index][1]: 231 | difference -= self.NP_MONTHS_DATA[year_data_index][1] 232 | np_year += 1 233 | year_data_index += 1 234 | 235 | # MONTH 236 | # Incrementing month until the difference remains less than next nepali month days (mostly 31) 237 | i = 0 238 | while difference >= self.NP_MONTHS_DATA[year_data_index][0][i]: 239 | difference -= self.NP_MONTHS_DATA[year_data_index][0][i] 240 | np_month += 1 241 | i += 1 242 | 243 | # DAY 244 | # Remaining difference is the day 245 | np_day += difference 246 | 247 | return np_year, np_month, np_day 248 | 249 | # 250 | # NEPALI DATE CONVERSION 251 | 252 | def check_nepali_date(self, year, month, day): 253 | if year < self.np_min_year() or year > self.np_max_year(): 254 | return False 255 | if month < 1 or month > 12: 256 | return False 257 | if ( 258 | day < 1 259 | or day > self.NP_MONTHS_DATA[year - self.NP_INITIAL_YEAR][0][month - 1] 260 | ): 261 | return False 262 | return True 263 | 264 | def _get_total_days_from_nepali_date(self, year, month, day): 265 | """counts and returns total days from the nepali initial date""" 266 | total_days = day - 1 # taking ref with Date's day 1, so -1 267 | 268 | # adding days of months of initial year 269 | year_index = year - self.NP_INITIAL_YEAR 270 | for i in range(0, month - 1): 271 | total_days = total_days + self.NP_MONTHS_DATA[year_index][0][i] 272 | 273 | # adding days of year 274 | for i in range(0, year_index): 275 | total_days = total_days + self.NP_MONTHS_DATA[i][1] 276 | 277 | return total_days 278 | 279 | def nepali_to_english(self, year, month, day): 280 | """ 281 | Converts english date to nepali 282 | return year, month, day 283 | """ 284 | # VALIDATION 285 | # checking if date is in range 286 | if not self.check_nepali_date(year, month, day): 287 | raise ValueError("Date out of range") 288 | 289 | # REFERENCE 290 | # For absolute reference, moving date to Jan 1 291 | # Eg. ref: 1943/4/14 => 1943/01/01 292 | en_year, en_month, en_day = self.REFERENCE_EN_DATE[0], 1, 1 293 | # calculating difference from the adjusted reference (eg. 1943/4/14 - 1943/01/01) 294 | ref_year_months = self._get_en_months(en_year) 295 | reference_diff = ( 296 | sum(ref_year_months[0 : self.REFERENCE_EN_DATE[1] - 1]) 297 | + self.REFERENCE_EN_DATE[2] 298 | - 1 # day - 1 299 | ) 300 | 301 | # DIFFERENCE 302 | # calculating days count from the reference date 303 | difference = abs( 304 | self._get_total_days_from_nepali_date( 305 | year, month, day 306 | ) # returns total days from initial date (nepali) 307 | + reference_diff 308 | ) 309 | 310 | # YEAR 311 | # Incrementing year until the difference remains less than 365 (or 365) 312 | while (difference >= 366 and self._is_leap_year(en_year)) or ( 313 | difference >= 365 and not (self._is_leap_year(en_year)) 314 | ): 315 | difference -= 366 if self._is_leap_year(en_year) else 365 316 | en_year += 1 317 | 318 | # MONTH 319 | # Incrementing month until the difference remains less than next english month (mostly 31) 320 | month_days = self._get_en_months(en_year) 321 | i = 0 322 | while difference >= month_days[i]: 323 | difference -= month_days[i] 324 | en_month += 1 325 | i += 1 326 | 327 | # DAY 328 | # Remaining difference is the day 329 | en_day += difference 330 | 331 | return en_year, en_month, en_day 332 | 333 | 334 | converter = NepaliDateConverter() 335 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nepali 2 | 3 | [![PyPI version](https://badge.fury.io/py/nepali.svg)](https://badge.fury.io/py/nepali) 4 | [![CI status](https://github.com/opensource-nepal/py-nepali/actions/workflows/python-package.yml/badge.svg?branch=main)](https://github.com/opensource-nepal/py-nepali/actions) 5 | [![Downloads](https://img.shields.io/pypi/dm/nepali.svg?maxAge=180)](https://pypi.org/project/nepali/) 6 | [![codecov](https://codecov.io/gh/opensource-nepal/py-nepali/branch/main/graph/badge.svg?token=PTUHYWCJ4I)](https://codecov.io/gh/opensource-nepal/py-nepali) 7 | 8 | `nepali` is a python package containing features that will be useful for Nepali projects. 9 | 10 | The major feature of this package is nepalidatetime, which is compatible with python's datetime feature. It helps nepali date to english, parsing nepali datetime, nepali timezone, and timedelta support in nepali datetime. 11 | 12 | ## Example 13 | 14 | ```python 15 | import datetime 16 | from nepali import phone_number 17 | from nepali.datetime import nepalidate, parser 18 | 19 | nepali_datetime = parser.parse('2079-02-15') 20 | # 2079-02-15 00:00:00 21 | 22 | date = datetime.date(2017, 3, 15) 23 | nepali_date = nepalidate.from_date(date) 24 | # 2073-12-02 25 | 26 | phone_number.parse("+977-9845217789") 27 | # { 28 | # 'type': 'Mobile', 29 | # 'number': '9845217789', 30 | # 'operator': 31 | # } 32 | ``` 33 | 34 | ## Requirements 35 | 36 | Python >= 3 37 | 38 | ## Installation 39 | 40 | pip install nepali 41 | 42 | ## Features 43 | 44 | 1. [Date and Time](#date-and-time) 45 | - [date_converter](#date_converter) 46 | - [nepalidate](#nepalidate) 47 | - [nepalidatetime](#nepalidatetime) 48 | - [nepalihumanize](#nepalihumanize) 49 | - [timezone](#timezone) 50 | - [parse](#parse) 51 | - [strftime() and strptime() Format Codes](#strftime-and-strptime-format-codes) 52 | 1. [Numbers](#numbers) 53 | - [nepalinumber](#nepalinumber) 54 | 1. [Phone Number](#phone-number) 55 | 1. [Locations](#locations) 56 | 1. [For Django Template](#for-django-template) 57 | 58 | ## Date and Time 59 | 60 | ### date_converter 61 | 62 | Date converter module converts english date to nepali and nepali date to english. It doesn't contain any extra functionality. 63 | 64 | **Convert English date to Nepali date** 65 | 66 | ```python 67 | from nepali.date_converter import converter 68 | 69 | np_year, np_month, np_date = converter.english_to_nepali(en_year, en_month, en_date) 70 | ``` 71 | 72 | Example 73 | 74 | ```python 75 | from nepali.date_converter import converter 76 | 77 | np_year, np_month, np_date = converter.english_to_nepali(2023, 2, 7) 78 | print(np_year, np_month, np_date) # 2079 10 24 79 | ``` 80 | 81 | **Convert Nepali date to English date** 82 | 83 | ```python 84 | from nepali.date_converter import converter 85 | 86 | en_year, en_month, en_date = converter.nepali_to_english(np_year, np_month, np_date) 87 | ``` 88 | 89 | Example 90 | 91 | ```python 92 | from nepali.date_converter import converter 93 | 94 | en_year, en_month, en_date = converter.nepali_to_english(2079, 10, 24) 95 | print(en_year, en_month, en_date) # 2023 2 7 96 | ``` 97 | 98 | ### nepalidate 99 | 100 | **Creating a new nepalidate object** 101 | 102 | ```python 103 | from nepali.datetime import nepalidate 104 | 105 | # nepalidate object with year, month, day 106 | np_date = nepalidate(year, month, day) 107 | 108 | # nepalidate object with today's date 109 | np_date = nepalidate.today() 110 | 111 | # parse nepali date 112 | np_date = nepalidate.strptime('2078-01-18', format='%Y-%m-%d') 113 | ``` 114 | 115 | **Getting nepalidate object from python datetime** 116 | 117 | ```python 118 | # from date object 119 | np_date = nepalidate.from_date(date_obj) 120 | 121 | # from datetime object 122 | np_date = nepalidate.from_datetime(datetime_obj) 123 | ``` 124 | 125 | **Attributes and Methods** 126 | 127 | ```python 128 | np_date.year # 2078 (year) 129 | np_date.month # 1 (month) 130 | np_date.day # 18 (day) 131 | 132 | np_date.to_date() # datetime.date object 133 | np_date.to_datetime() # datetime.datetime object 134 | np_date.to_nepalidatetime() # nepalidatetime object 135 | 136 | np_date.strftime("%Y-%m-%d") # 2078-01-18 137 | np_date.strftime_ne("%Y-%m-%d") # २०७८-०१-१८ 138 | 139 | np_date.weekday() # Sunday => 0, Monday => 1, ..., Saturday => 6 140 | ``` 141 | 142 | ### nepalidatetime 143 | 144 | **Creating a new nepalidatetime object** 145 | 146 | ```python 147 | from nepali.datetime import nepalidatetime 148 | 149 | # nepalidate object with year, month, day, hour, minute, second 150 | np_datetime = nepalidatetime(year, month, day[, hour[, minute[, second]]]) 151 | 152 | # nepalidate object with current date and time 153 | np_datetime = nepalidate.now() 154 | np_datetime = nepalidate.today() 155 | 156 | # parse nepali datetime 157 | np_datetime = nepalidatetime.strptime('2078-01-12 13:12', format='%Y-%m-%d %H:%M') 158 | ``` 159 | 160 | **Getting nepalidatetime object from python datetime** 161 | 162 | ```python 163 | # from date object 164 | np_datetime = nepalidatetime.from_date(date_obj) 165 | 166 | # from datetime object 167 | np_datetime = nepalidatetime.from_datetime(datetime_obj) 168 | ``` 169 | 170 | **Getting nepalidatetime object from nepalidate** 171 | 172 | ```python 173 | np_datetime = nepalidatetime.from_nepalidate(nepali_date) 174 | ``` 175 | 176 | **Attributes and Methods** 177 | 178 | ```python 179 | np_date.year # 2078 (year) 180 | np_date.month # 1 (month) 181 | np_date.day # 18 (day) 182 | np_date.hour # 23 (hour) 183 | np_date.minute # 59 (minute) 184 | np_date.second # 59 (day) 185 | 186 | np_date.to_date() # datetime.date object 187 | np_date.to_datetime() # datetime.datetime object 188 | np_date.to_nepalidate() # nepalidatetime object 189 | np_date.to_time() # nepalitime object (datetime.time compatible) 190 | 191 | np_date.strftime("%Y-%m-%d %H:%M") # 2078-01-18 23:59 192 | np_date.strftime_ne("%Y-%m-%d %H:%M") # २०७८-०१-१८ २३:५९ 193 | 194 | np_date.weekday() # Sunday => 0, Monday => 1, ..., Saturday => 6 195 | ``` 196 | 197 | **Timedelta support** 198 | 199 | ```python 200 | # timedelta addition and subtraction 201 | np_datetime - datetime.timedelta(days=3) # returns nepalidatetime 202 | 203 | # comparison between two dates 204 | np_datetime1 - np_datetime2 # returns timedelta object 205 | np_datetime1 < np_datetime2 # returns bool (True/False) 206 | np_datetime1 >= datetime.datetime.now() # returns bool (True/False) 207 | ... 208 | ``` 209 | 210 | ### nepalihumanize 211 | 212 | Returns readable form of nepali date. 213 | 214 | ```python 215 | from nepali.datetime import nepalihumanize 216 | 217 | 218 | nepalihumanize(datetime, [threshold, format]) 219 | ``` 220 | 221 | The `threshold` is and optional field and is in seconds and the format is for the `strftime` format. If the datetime object crosses the threshold it print the date with the format. The `format` is also an optional and is `%B %d, %Y` in default. 222 | 223 | Example 224 | 225 | ```python 226 | from nepali.datetime import nepalihumanize, nepalidatetime 227 | 228 | np_datetime = nepalidatetime(2079, 10, 5) 229 | output = nepalihumanize(np_datetime) 230 | # output: ३ महिना अघि 231 | 232 | output = nepalihumanize(np_datetime, threshold=1400) 233 | # 1400 = 2 * 30 * 24; two months threshold 234 | # output: माघ ०५, २०७९ 235 | ``` 236 | 237 | ### timezone 238 | 239 | **NepaliTimeZone** 240 | You can use `NepaliTimeZone` directly to your datetime object. 241 | 242 | ```python 243 | from nepali.timezone import NepaliTimeZone 244 | 245 | datetime.datetime(2018, 8, 12, 16, 23, tzinfo=NepaliTimeZone()) 246 | ``` 247 | 248 | **now** 249 | Returns current datetime object with timezone 250 | 251 | ```python 252 | from nepali import timezone 253 | 254 | timezone.now() 255 | ``` 256 | 257 | `datetime.now()` vs `timezone.now()`: 258 | `datetime.now()` doesn't contain timezone, but `timezone.now()` will contain timezone of the system. 259 | 260 | **utc_now** 261 | Returns current UTC datetime object (with timezone UTC) 262 | 263 | ```python 264 | from nepali import timezone 265 | 266 | timezone.utc_now() 267 | ``` 268 | 269 | ### parse 270 | 271 | Parses date with commonly used date formats. Auto detects date format. If you are sure about the format, please use `strptime`. 272 | 273 | ```python 274 | from nepali.datetime.parser import parse 275 | 276 | np_datetime = parse(datetime_str) 277 | ``` 278 | 279 | Example 280 | 281 | ```python 282 | np_datetime = parse("2079-02-15") # 2079-02-15 00:00:00 283 | np_datetime = parse("२०७८-०१-१८") # 2078-01-15 00:00:00 284 | np_datetime = parse("2079/02/15") # 2079-02-15 00:00:00 285 | np_datetime = parse("2079-02-15 15:23") # 2079-02-15 15:23:00 286 | np_datetime = parse("2079-02-15 5:23 AM") # 2079-02-15 05:23:00 287 | np_datetime = parse("2079-02-15 5:23 AM") # 2079-02-15 05:23:00 288 | np_datetime = parse("Jestha 15, 2079") # 2079-02-15 00:00:00 289 | 290 | ``` 291 | 292 | ### strftime() and strptime() Format Codes 293 | 294 | | Directive | Meaning | Example | 295 | | --------- | --------------------------------------------------------- | ------------------------------ | 296 | | `%a` | Weekday as locale’s abbreviated name. | Sun, Mon, …, Sat (आइत, सोम, …) | 297 | | `%A` | Weekday as locale’s full name. | Sunday, Monday, …, Saturday | 298 | | `%d` | Day of the month as a zero-padded decimal number. | 01, 02, …, 31 | 299 | | `%-d` | Day of the month as a decimal number. | 1, 2, …, 31 | 300 | | `%B` | Month as locale’s full name. | Baishakh, Jestha, …, Chaitra | 301 | | `%m` | Month as a zero-padded decimal number. | 01, 02, …, 12 | 302 | | `%-m` | Month as a decimal number. | 1, 2, …, 12 | 303 | | `%y` | Year without century as a zero-padded decimal number. | 00, 01, …, 99 | 304 | | `%Y` | Year with century as a decimal number. | 2001, 2078, 2079, …, 2099 | 305 | | `%H` | Hour (24-hour clock) as a zero-padded decimal number. | 00, 01, …, 23 | 306 | | `%-H` | Hour (24-hour clock) as a decimal number. | 0, 1, 2, …, 23 | 307 | | `%I` | Hour (12-hour clock) as a zero-padded decimal number. | 01, 02, …, 12 | 308 | | `%-I` | Hour (12-hour clock) as a decimal number. | 1, 2, …, 12 | 309 | | `%p` | Locale’s equivalent of either AM or PM. | AM, PM (en_US) | 310 | | `%M` | Minute as a zero-padded decimal number. | 00, 01, …, 59 | 311 | | `%-M` | Minute as a decimal number. | 0, 1, 2, …, 59 | 312 | | `%S` | Second as a zero-padded decimal number. | 00, 01, …, 59 | 313 | | `%-S` | Second as a decimal number. | 0, 1, 2, …, 59 | 314 | | `%f` | Microsecond as a decimal number, zero-padded to 6 digits. | 000000, 000001, …, 999999 | 315 | | `%%` | A literal `'%'` character. | % | 316 | 317 | --- 318 | 319 | ## Numbers 320 | 321 | ```python 322 | from nepali import number 323 | ``` 324 | 325 | **convert** 326 | Converts english number to nepali. 327 | 328 | ```python 329 | np_number = number.convert("1234567890") # १२३४५६७८९० 330 | ``` 331 | 332 | **revert** 333 | Converts english number to nepali. 334 | 335 | ```python 336 | en_number = number.revert("१२३४५६७८९०") # 1234567890 337 | ``` 338 | 339 | **add_comma** 340 | Adds comma in nepali numbers. 341 | 342 | ```python 343 | number_text = number.add_comma("1234567890") # 1,23,45,67,890 344 | ``` 345 | 346 | ### nepalinumber 347 | `nepalinumber` is a new data type, which can be used to represent Nepali (Devanagari) numbers. It allows us to perform arithmetic operations, just like with int and float. Additionally, it can be used to parse numbers and output them in Devanagari format. 348 | 349 | ```python 350 | from nepali.number import nepalinumber 351 | ``` 352 | 353 | **Parsing** 354 | ```python 355 | a = nepalinumber("१८.२७") 356 | print(a) # 18.27 357 | 358 | b = nepalinumber(15) 359 | print(b) # 15 360 | ``` 361 | 362 | **Nepali (Devanagari) output** 363 | ```python 364 | a = nepalinumber("18.27") 365 | print(a.str_ne()) # १८.२७ 366 | ``` 367 | 368 | **Arithmetic operations** 369 | ```python 370 | a = nepalinumber("1") 371 | b = nepalinumber("२") 372 | c = a + b * 3 373 | print(c) # 7 374 | ``` 375 | --- 376 | 377 | ## Phone Number 378 | 379 | ```python 380 | from nepali import phone_number 381 | ``` 382 | 383 | **is_valid** 384 | Checks is the given number is a valid nepali phone number. 385 | 386 | ```python 387 | phone_number.is_valid("9851377890") # True 388 | phone_number.is_valid("+977-142314819") # True 389 | 390 | phone_number.is_valid("8251377890") # False 391 | ``` 392 | 393 | **parse** 394 | Parse phone number and returns details of the number. 395 | 396 | ```python 397 | phone_number.parse("9851377890") 398 | # {'type': 'Mobile', 'number': '9851377890', 'operator': } 399 | 400 | phone_number.parse("+977-142314819") 401 | # {'type': 'Landline', 'number': '0142314819', 'area_code': '01'} 402 | ``` 403 | 404 | --- 405 | 406 | ## Locations 407 | 408 | Provides details of Nepal's Province, District, and Municipality. 409 | 410 | ```python 411 | from nepali.locations import provinces, districts, municipalities 412 | ``` 413 | 414 | ```python 415 | from nepali.locations.utils import get_province, get_district, get_municipality 416 | 417 | # Province 418 | get_province(name="Bagmati") 419 | # Bagmati Province 420 | 421 | # District 422 | get_district(name="Kathmandu") 423 | # Kathmandu 424 | 425 | # Municipality 426 | get_municipality(name="Kathmandu") 427 | # Kathmandu Metropolitan City 428 | 429 | # Municipality 430 | get_municipality(name_nepali="विराटनगर") 431 | # Biratnagar Metropolitan City 432 | ``` 433 | 434 | --- 435 | 436 | ## For Django 437 | 438 | We have created a new Django package called [django-nepali](https://github.com/opensource-nepal/django-nepali) to support `nepali` package. For more information, please visit [django-nepali](https://github.com/opensource-nepal/django-nepali). 439 | 440 | ## Contribution 441 | 442 | We appreciate feedback and contribution to this package. To get started please see our [contribution guide](./CONTRIBUTING.md) 443 | --------------------------------------------------------------------------------