├── requirements.txt ├── .gitignore ├── jbeautiful_date ├── __init__.py ├── date_range.py ├── jbeautiful_timedelta.py └── jbeautiful_date.py ├── .github ├── workflows │ └── tests.yml └── CODE_OF_CONDUCT.md ├── tox.ini ├── LICENSE ├── setup.py └── README.md /requirements.txt: -------------------------------------------------------------------------------- 1 | jdatetime 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv*/ 2 | .idea 3 | 4 | *.py[cod] 5 | 6 | .pytest_cache 7 | beautiful_date.egg-info 8 | dist/ 9 | build 10 | 11 | coverage.* 12 | .coverage 13 | .tox -------------------------------------------------------------------------------- /jbeautiful_date/__init__.py: -------------------------------------------------------------------------------- 1 | from jbeautiful_date.jbeautiful_date import JBeautifulDate, \ 2 | D, MDY, DMY, \ 3 | Far, Ord, Khr, Tir, Mor, Sha, Mehr, Aban, Azar, Dey, Bah, Esf 4 | 5 | from jbeautiful_date.jbeautiful_timedelta import \ 6 | years, months, weeks, days, hours, minutes, seconds, microseconds, leapday, \ 7 | year, month, day, hour, minute, second, microsecond, yearday, nlyearday, \ 8 | DO, SE, CH, PA, JO, SH, YE 9 | 10 | from jbeautiful_date.date_range import drange 11 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | run: 11 | 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | python-version: [ '3.7', '3.8', '3.9', '3.10', '3.11' ] 16 | include: 17 | - python-version: '3.11' 18 | note: with-style-check 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Set up Python ${{ matrix.python-version }} 23 | uses: actions/setup-python@v2 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | 27 | - name: Install tox 28 | run: pip install tox tox-gh-actions 29 | 30 | - name: Running tests 31 | run: tox -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = pytest, code-cov, flake8 3 | 4 | [gh-actions] 5 | python = 6 | 3.6: pytest 7 | 3.7: pytest 8 | 3.8: pytest 9 | 3.9: pytest 10 | 3.10: pytest 11 | 3.11: pytest, flake8 12 | 13 | [flake8] 14 | max-line-length = 120 15 | exclude = __init__.py 16 | 17 | [coverage:report] 18 | exclude_lines = 19 | # Have to re-enable the standard pragma 20 | pragma: no cover 21 | 22 | # Don't complain if tests don't hit defensive assertion code: 23 | pass 24 | omit = 25 | */__init__.py 26 | 27 | 28 | 29 | [testenv:pytest] 30 | deps = 31 | pyfakefs 32 | pytest 33 | commands = 34 | pytest 35 | 36 | [testenv:coverage] 37 | deps = 38 | pyfakefs 39 | pytest 40 | pytest-cov 41 | commands = 42 | pytest --cov-report xml --cov=beautiful_date beautiful_tests 43 | 44 | [testenv:flake8] 45 | deps = 46 | flake8 47 | pep8-naming 48 | commands = 49 | flake8 beautiful_date beautiful_tests setup.py 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Mohammadreza Amani 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /jbeautiful_date/date_range.py: -------------------------------------------------------------------------------- 1 | from jdatetime import date, datetime 2 | 3 | from .jbeautiful_date import JBeautifulDate 4 | from jbeautiful_date.jbeautiful_timedelta import days 5 | 6 | 7 | def timedelta_is_negative(td): 8 | """Checks whether timedelta is negative (would move date/datetime to the past).""" 9 | return datetime.now() > datetime.now() + td 10 | 11 | 12 | class drange: # noqa: N801 13 | """ 14 | drange(stop) -> drange object 15 | drange(start, stop[, step]) -> drange object 16 | 17 | Return an object that produces a sequence of dates/datetimes (depending on parameter type) 18 | from start (inclusive) to stop (exclusive) by step. 19 | 20 | drange(stop) produces range of dates/datetimes from now to given stop date/datetime. 21 | drange(start, stop[, step]) produces range of dates/datetimes from start to stop. 22 | 23 | When step is given, it specifies the increment (or decrement). 24 | When step is not given, 1-day step is used. 25 | """ 26 | 27 | def __init__(self, start_or_stop, stop=None, step=1 * days): 28 | if stop is None: 29 | if isinstance(start_or_stop, datetime): 30 | start = datetime.now() 31 | else: 32 | now = date.today() 33 | start = JBeautifulDate(now.year, now.month, now.day) 34 | 35 | stop = start_or_stop 36 | else: 37 | start = start_or_stop 38 | 39 | if not step: 40 | raise ValueError("drange() step must be positive or negative step, not 0") 41 | 42 | self._backwards = timedelta_is_negative(step) 43 | 44 | self._start = start 45 | self._stop = stop 46 | self._step = step 47 | 48 | def __repr__(self): 49 | return "drange({}, {}, {})".format(self._start, self._stop, self._step) 50 | 51 | def __iter__(self): 52 | return self 53 | 54 | def __next__(self): 55 | if (self._backwards and self._start <= self._stop) or ( 56 | not self._backwards and self._start >= self._stop 57 | ): 58 | raise StopIteration 59 | 60 | ret = self._start 61 | self._start += self._step 62 | return ret 63 | -------------------------------------------------------------------------------- /jbeautiful_date/jbeautiful_timedelta.py: -------------------------------------------------------------------------------- 1 | from dateutil.relativedelta import relativedelta, weekdays 2 | from jdatetime import date, datetime 3 | from datetime import timedelta as _timedelta, date as _date, datetime as _datetime 4 | from .jbeautiful_date import JBeautifulDate 5 | 6 | 7 | class RelativeDelta(relativedelta): 8 | """Same as relativedelta, but returns JBeautifulDate in the result. 9 | 10 | Examples: 11 | >>> 17/Dey/1380 + 5*days 12 | JBeautifulDate(1380, 10, 22) 13 | """ 14 | 15 | def __add__(self, d): 16 | if isinstance(d, JBeautifulDate): 17 | d = datetime.togregorian(d) 18 | print(d, type(d)) 19 | new_date = super().__add__(d) 20 | if isinstance(new_date, date) and not isinstance(new_date, datetime): 21 | new_date = datetime.fromgregorian(date=new_date) 22 | return JBeautifulDate(new_date.year, new_date.month, new_date.day) 23 | else: 24 | return new_date 25 | 26 | __radd__ = __add__ 27 | 28 | 29 | class BeautifulTimedelta: 30 | """Creates timedelta with specified time unit using operator '*' 31 | 32 | Examples: 33 | >>> 3*years 34 | RelativeDelta(years=+3) 35 | 36 | >>> -5*weeks 37 | RelativeDelta(days=-35) 38 | """ 39 | 40 | def __init__(self, name): 41 | self.name = name 42 | 43 | def __rmul__(self, n): 44 | return RelativeDelta(**{self.name: n}) 45 | 46 | 47 | _ = BeautifulTimedelta 48 | 49 | years = _('years') 50 | months = _('months') 51 | weeks = _('weeks') 52 | days = _('days') 53 | hours = _('hours') 54 | minutes = _('minutes') 55 | seconds = _('seconds') 56 | microseconds = _('microseconds') 57 | leapdays = _('leapdays') 58 | leapday = 1 * leapdays 59 | 60 | year = _('year') 61 | month = _('month') 62 | day = _('day') 63 | hour = _('hour') 64 | minute = _('minute') 65 | second = _('second') 66 | microsecond = _('microsecond') 67 | 68 | yearday = _('yearday') 69 | nlyearday = _('nlyearday') 70 | 71 | _weekday = _('weekday') 72 | 73 | 74 | class BeautifulWeekday: 75 | """ 76 | 77 | Examples: 78 | Get next Monday: 79 | >>> d = 29/Mar/2018 # Thursday 80 | >>> d + MO # Equivalent to MO(1) 81 | JBeautifulDate(2018, 4, 2) 82 | 83 | Get second to next Monday: 84 | >>> d = 29/Mar/2018 85 | >>> d + MO(2) 86 | JBeautifulDate(2018, 4, 9) 87 | 88 | Get last Saturday: 89 | >>> d = 29/Mar/2018 90 | >>> d - SA 91 | JBeautifulDate(2018, 3, 24) 92 | 93 | Get second to last Saturday: 94 | >>> d = 29/Mar/2018 95 | >>> d - SA(2) 96 | JBeautifulDate(2018, 3, 17) 97 | 98 | Get second to last Saturday (same as previous): 99 | >>> d = 29/Mar/2018 100 | >>> d + SA(-2) 101 | JBeautifulDate(2018, 3, 17) 102 | """ 103 | 104 | def __init__(self, wd, n=1): 105 | self.wd = wd 106 | self.n = n 107 | 108 | def __radd__(self, other): 109 | return other + self.wd(self.n) * _weekday 110 | 111 | def __rsub__(self, other): 112 | return other + self.wd(-self.n) * _weekday 113 | 114 | def __call__(self, n): 115 | return BeautifulWeekday(self.wd, n) 116 | 117 | 118 | weekdays = DO, SE, CH, PA, JO, SH, YE = [BeautifulWeekday(weekdays[i]) for i in range(7)] 119 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import io 4 | import os 5 | import sys 6 | from shutil import rmtree 7 | 8 | from setuptools import setup, Command 9 | 10 | here = os.path.abspath(os.path.dirname(__file__)) 11 | 12 | 13 | class UploadCommand(Command): 14 | """Support setup.py upload.""" 15 | 16 | description = 'Build and publish the package.' 17 | user_options = [] 18 | 19 | @staticmethod 20 | def status(s): 21 | """Prints things in bold.""" 22 | print('\033[1m{0}\033[0m'.format(s)) 23 | 24 | def initialize_options(self): 25 | pass 26 | 27 | def finalize_options(self): 28 | pass 29 | 30 | def run(self): 31 | try: 32 | self.status('Removing previous builds…') 33 | rmtree(os.path.join(here, 'dist')) 34 | except OSError: 35 | pass 36 | 37 | self.status('Building Source and Wheel (universal) distribution…') 38 | os.system('{0} setup.py sdist bdist_wheel --universal'.format(sys.executable)) 39 | 40 | self.status('Uploading the package to PyPi via Twine…') 41 | os.system('twine upload dist/*') 42 | 43 | self.status('Pushing git tags…') 44 | os.system('git tag v{0}'.format(VERSION)) 45 | os.system('git push --tags') 46 | 47 | sys.exit() 48 | 49 | 50 | NAME = 'jbeautiful-date' 51 | DESCRIPTION = 'Simple and beautiful way to create jalali date and datetime objects in Python.' 52 | URL = 'https://github.com/MohammadrezaAmani/Jbeautiful-date' 53 | DOWNLOAD_URL = 'https://github.com/kuzmoyev/beautiful-date/archive/1.0.tar.gz' 54 | EMAIL = 'more.amani@yahoo.com' 55 | AUTHOR = 'Mohammadreza Amani' 56 | REQUIRES_PYTHON = '>=3.5.0' 57 | VERSION = '0.2.5' 58 | 59 | REQUIRED = [ 60 | 'python-dateutil>=2.7.0', 61 | ] 62 | 63 | TESTS_REQUIRED = [ 64 | "pytest>=5.4", 65 | "pytest-cov>=2.10", 66 | "flake8>3.8.3", 67 | "tox" 68 | ] 69 | 70 | with io.open(os.path.join(here, 'README.md'), encoding='utf-8') as f: 71 | long_description = '\n' + f.read() 72 | 73 | setup( 74 | name=NAME, 75 | version=VERSION, 76 | description=DESCRIPTION, 77 | long_description=long_description, 78 | long_description_content_type="text/markdown", 79 | author=AUTHOR, 80 | author_email=EMAIL, 81 | python_requires=REQUIRES_PYTHON, 82 | url=URL, 83 | download_url=DOWNLOAD_URL, 84 | py_modules=['beautiful_date', 'beautiful_timedelta', 'date_range'], 85 | packages=['beautiful_date'], 86 | install_requires=REQUIRED, 87 | extras_require={ 88 | 'dev': TESTS_REQUIRED, 89 | 'tests': TESTS_REQUIRED 90 | }, 91 | include_package_data=True, 92 | license='MIT', 93 | classifiers=[ 94 | 'License :: OSI Approved :: MIT License', 95 | 'Natural Language :: English', 96 | 'Programming Language :: Python', 97 | 'Programming Language :: Python :: 3.5', 98 | 'Programming Language :: Python :: 3.6', 99 | 'Programming Language :: Python :: 3.7', 100 | 'Programming Language :: Python :: 3.8', 101 | 'Programming Language :: Python :: 3.9', 102 | 'Programming Language :: Python :: 3.10', 103 | 'Programming Language :: Python :: 3.11', 104 | ], 105 | keywords=['beautiful', 'date', 'simple', 'timedelta', 'date-range'], 106 | cmdclass={ 107 | 'upload': UploadCommand, 108 | }, 109 | ) 110 | -------------------------------------------------------------------------------- /jbeautiful_date/jbeautiful_date.py: -------------------------------------------------------------------------------- 1 | from jdatetime import date, datetime 2 | 3 | 4 | class JBeautifulDate(date): 5 | """Date object that can be extended to datetime by using Python indexing/slicing: 6 | 7 | Examples: 8 | >>> (Dey / 17 / 1380)[:] 9 | datetime.datetime(1380, 10, 17, 0, 0) 10 | 11 | >>> (Dey / 17 / 1380)[23] 12 | datetime.datetime(1380, 10, 17, 23, 0) 13 | 14 | >>> (Dey / 17 / 1380)[23:14] 15 | datetime.datetime(1380, 10, 17, 23, 14) 16 | 17 | >>> (Dey / 17 / 1380)[23:14:10] 18 | datetime.datetime(1380, 10, 17, 23, 14, 10) 19 | """ 20 | 21 | def __getitem__(self, t): 22 | """ 23 | Converts date to datetime with provided time [hours[:minutes[:seconds]]] 24 | :return: datetime object. 25 | """ 26 | 27 | if isinstance(t, slice): 28 | h, m, s = t.start or 0, t.stop or 0, t.step or 0 29 | elif isinstance(t, int): 30 | h, m, s = t, 0, 0 31 | else: 32 | raise TypeError("Time values must be integer or slice, not {!r}".format(t.__class__.__name__)) 33 | 34 | return datetime(self.year, self.month, self.day, hour=h, minute=m, second=s) 35 | 36 | def to_date(self): 37 | """ 38 | Converts JBeautifulDate to a simple Python date 39 | :return: date object 40 | """ 41 | return date(self.year, self.month, self.day) 42 | 43 | def __add__(self, timedelta): 44 | return datetime.fromgregorian(date=self.to_date() + timedelta) 45 | 46 | __radd__ = __add__ 47 | # Classes to build date 48 | # D @ 16/10/1995 (16/Oct/1995) 49 | # D @ 5/19/2006 (May/19/2006) 50 | 51 | class _PartialDate: 52 | """Date builder that uses operator "/" or "-" between values of day, month and year 53 | 54 | Examples: 55 | >>> D @ 17/10/1380 56 | JBeautifulDate(1380, 10, 17) 57 | 58 | >>> D @ 17-10-1380 59 | JBeautifulDate(1380, 10, 17) 60 | """ 61 | 62 | def __init__(self, first, _format): 63 | self._date_values = [first] 64 | self._format = _format 65 | 66 | def __truediv__(self, value): 67 | self._date_values.append(value) 68 | if len(self._date_values) == 3: 69 | return JBeautifulDate(**dict(zip(self._format, self._date_values))) 70 | else: 71 | return self 72 | 73 | __sub__ = __truediv__ 74 | 75 | 76 | class BaseDateFormat: 77 | """Base class for date format. 78 | 79 | Used to create PartialDate with a format specified in the inherited classes. 80 | 81 | Examples: 82 | >>> D @ 11 83 | _PartialDate(11) 84 | 85 | >>> D @ 22/10 86 | _PartialDate(22/10) 87 | """ 88 | 89 | # List of strings 'day', 'month', and 'year' in desired order. 90 | # Should be overridden in the inherited classes 91 | _format = None 92 | 93 | def __matmul__(self, first): 94 | return _PartialDate(first, self._format) 95 | 96 | def __repr__(self): 97 | return '{}{}'.format(self.__class__.__name__, self._format) 98 | 99 | @staticmethod 100 | def today(): 101 | today = date.today() 102 | return JBeautifulDate(year=today.year, month=today.month, day=today.day) 103 | 104 | @staticmethod 105 | def now(tz=None): 106 | return datetime.now(tz=tz) 107 | 108 | 109 | class DMY(BaseDateFormat): 110 | _format = 'day', 'month', 'year' 111 | 112 | 113 | class MDY(BaseDateFormat): 114 | _format = 'month', 'day', 'year' 115 | 116 | 117 | class YMD(BaseDateFormat): 118 | _format = 'year', 'month', 'day' 119 | 120 | 121 | class YDM(BaseDateFormat): 122 | _format = 'year', 'day', 'month' 123 | 124 | 125 | D = DMY() 126 | 127 | 128 | # Classes to build date with month name 129 | # 16/Oct/1995 130 | # May-19-2006 131 | 132 | class _Day: 133 | """Second step of creating date object 134 | 135 | Stores month and day numbers. If applied operator '/' or '-', returns JBeautifulDate with provided value of the year 136 | 137 | Examples: 138 | >>> 12/Khr/1382 139 | JBeautifulDate(1382, 3, 12) 140 | 141 | >>> Dey-17-1380 142 | JBeautifulDate(1380, 10, 17) 143 | 144 | """ 145 | 146 | def __init__(self, d, m): 147 | self.d = d 148 | self.m = m 149 | 150 | def __sub__(self, y): 151 | return JBeautifulDate(year=y, month=self.m, day=self.d) 152 | 153 | __truediv__ = __sub__ 154 | 155 | 156 | class _Month: 157 | """First step of creating date object 158 | 159 | Stores month number. If applied operator '/' or '-', returns _Day with provided value of the day. 160 | 161 | Examples: 162 | >>> 16/Oct 163 | _Day(16, 10) 164 | 165 | >>> May-19 166 | _Day(19, 5) 167 | """ 168 | 169 | def __init__(self, m): 170 | self.m = m 171 | 172 | def __sub__(self, d): 173 | return _Day(d, self.m) 174 | 175 | __rtruediv__ = __rsub__ = __truediv__ = __sub__ 176 | 177 | 178 | M = _, Far, Ord, Khr, Tir, Mor, Sha, Mehr, Aban, Azar, Dey, Bah, Esf = [_Month(i) for i in range(13)] 179 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | kuzmovich.goog@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Beautiful Date 2 | 3 | > This project is inspired by [beautiful-date](https://github.com/kuzmoyev/beautiful-date) and is only its Persian version. 4 | 5 | 6 | Simple and beautiful way to create date and datetime objects in Python. 7 | 8 | **UNDER DEVELOPMENT** 9 | 10 | 11 | **Before**: 12 | 13 | ```python3 14 | from jdatetime import date, datetime 15 | 16 | d = date(year=1382, month=3, day=12) 17 | t = datetime(year=1382, month=3, day=12, hour=23, minute=45) 18 | ``` 19 | 20 | **After**: 21 | 22 | ```python3 23 | from jbeautiful_date import * 24 | 25 | d = 12/Khr/1382 26 | t = (12/Khr/1382)[23:45] 27 | ``` 28 | 29 | ## Installation 30 | 31 | ```bash 32 | pip install jbeautiful-date 33 | ``` 34 | 35 | ## Examples 36 | 37 | ### Create Date 38 | 39 | Using months names: 40 | 41 | ```python3 42 | >>> from jbeautiful_date import * 43 | 44 | >>> 2/Sha/1356 # European format 45 | JBeautifulDate(1356, 6, 2) 46 | >>> Sha/2/1356 # US format 47 | JBeautifulDate(1356, 6, 2) 48 | ``` 49 | 50 | Using months numbers: 51 | 52 | ```python3 53 | >>> 24/M[12]/1387 # European format 54 | JBeautifulDate(1387, 12, 24) 55 | >>> M[12]/24/1387 # US format 56 | JBeautifulDate(1387, 12, 24) 57 | ``` 58 | 59 | Or alternatively: 60 | 61 | ```python3 62 | >>> D @ 12/3/1382 # European format (default) 63 | JBeautifulDate(1382, 3, 12) 64 | 65 | >>> D = MDY() # Add this at the top of your script to use US format. 66 | >>> d = D @ 3/12/1382 # US format 67 | JBeautifulDate(1382, 3, 12) 68 | ``` 69 | 70 | Available formats (needed only if you create dates using `D@`): 71 | 72 | ```python3 73 | class DMY(BaseDateFormat): 74 | _format = 'day', 'month', 'year' 75 | 76 | class MDY(BaseDateFormat): 77 | _format = 'month', 'day', 'year' 78 | 79 | class YMD(BaseDateFormat): 80 | _format = 'year', 'month', 'day' 81 | 82 | class YDM(BaseDateFormat): 83 | _format = 'year', 'day', 'month' 84 | ``` 85 | 86 | You can also easily retrieve current date as a `JBeautifulDate` object and current time using: 87 | 88 | ```python3 89 | >>> D.today() 90 | JBeautifulDate(1399, 8, 24) 91 | 92 | >>> D.now() 93 | jdatetime.datetime(1399, 8, 24, 3, 58, 11, 451333) 94 | ``` 95 | 96 | ### Create Datetime 97 | 98 | Previous methods create `JBeautifulDate` objects which are inherited from `date` but can be 99 | easily extended to `jdatetime` using indexing/slicing: 100 | 101 | ```python3 102 | >>> (Oct/16/1995)[:] 103 | datetime.datetime(1995, 10, 16, 0, 0) 104 | 105 | >>> (Oct/16/1995)[23] 106 | datetime.datetime(1995, 10, 16, 23, 0) 107 | 108 | >>> (Oct/16/1995)[23:14] 109 | datetime.datetime(1995, 10, 16, 23, 14) 110 | 111 | >>> (Oct/16/1995)[23:14:10] 112 | datetime.datetime(1995, 10, 16, 23, 14, 10) 113 | ``` 114 | 115 | You can also use prefix `D @` if you need months by their numbers: 116 | 117 | ```python3 118 | >>> (D @ 16/10/1995)[:] 119 | datetime.datetime(1995, 10, 16, 0, 0) 120 | 121 | >>> (D @ 16/10/1995)[23] 122 | datetime.datetime(1995, 10, 16, 23, 0) 123 | 124 | >>> (D @ 16/10/1995)[23:14] 125 | datetime.datetime(1995, 10, 16, 23, 14) 126 | 127 | >>> (D @ 16/10/1995)[23:14:10] 128 | datetime.datetime(1995, 10, 16, 23, 14, 10) 129 | ``` 130 | 131 | ### Date/Datetime manipulations: 132 | 133 | This library also provides simple interface for 134 | [relativedelta](http://dateutil.readthedocs.io/en/stable/relativedelta.html) from 135 | [dateutil](http://dateutil.readthedocs.io/en/stable/index.html) 136 | 137 | #### Adding/Subtracting/Setting timedeltas: 138 | 139 | Notice that singular time unit (year, month, ...) sets given value, plural (years, months,) adds it. 140 | 141 | 142 | ```python3 143 | >>> d = 26/Mar/2018 144 | >>> t = d[12:23:15] 145 | 146 | >>> d + 2 * years 147 | BeautifulDate(2020, 3, 26) 148 | >>> d - 2 * days 149 | BeautifulDate(2018, 3, 24) 150 | 151 | >>> t + 25 * hours 152 | datetime.datetime(2018, 3, 27, 13, 23, 15) 153 | ``` 154 | 155 | Available deltas: `years`, `months`, `weeks`, `days`, `hours`, `minutes`, 156 | `seconds`, `microseconds`, `leapdays` 157 | (see [relativedelta](http://dateutil.readthedocs.io/en/stable/relativedelta.html)). 158 | 159 | ```python3 160 | >>> d = 26/Mar/2018 161 | >>> t = d[12:23:15] 162 | 163 | >>> d + 2022 * year 164 | BeautifulDate(2022, 3, 26) 165 | >>> d += 2 * day 166 | >>> d 167 | BeautifulDate(2018, 3, 2) 168 | 169 | >>> t + 22 * hour 170 | datetime.datetime(2018, 3, 26, 22, 23, 15) 171 | >>> t += 22 * hour 172 | >>> t 173 | datetime.datetime(2018, 3, 26, 22, 23, 15) 174 | ``` 175 | 176 | Available setters: `year`, `month`, `day`, `hour`, `minute`, `second`, `microsecond`, 177 | `yearday` and `nlyearday` 178 | (see [relativedelta](http://dateutil.readthedocs.io/en/stable/relativedelta.html)). 179 | 180 | #### Weekdays: 181 | 182 | Get next Monday: 183 | 184 | ```python3 185 | >>> d = 29/Mar/2018 # Thursday 186 | >>> d + MO # Equivalent to MO(1) 187 | BeautifulDate(2018, 4, 2) 188 | ``` 189 | 190 | Get second to next Monday: 191 | 192 | ```python3 193 | >>> d = 29/Mar/2018 194 | >>> d + MO(2) 195 | BeautifulDate(2018, 4, 9) 196 | ``` 197 | 198 | Get last Saturday: 199 | 200 | ```python3 201 | >>> d = 29/Mar/2018 202 | >>> d - SA 203 | BeautifulDate(2018, 3, 24) 204 | ``` 205 | 206 | Get second to last Saturday: 207 | 208 | ```python3 209 | >>> d = 29/Mar/2018 210 | >>> d - SA(2) 211 | BeautifulDate(2018, 3, 17) 212 | ``` 213 | 214 | Get second to last Saturday (same as previous): 215 | 216 | ```python3 217 | >>> d = 29/Mar/2018 218 | >>> d + SA(-2) 219 | BeautifulDate(2018, 3, 17) 220 | ``` 221 | 222 | ### Util 223 | 224 | #### drange: 225 | 226 | You can use `drange` to generate ranges of dates: 227 | 228 | ```python3 229 | >>> for d in drange(17/Dey/1380, 12/Khr/1382): 230 | ... print(d) 231 | 1994-03-27 232 | 1994-03-28 233 | 1994-03-29 234 | 1994-03-30 235 | 1994-03-31 236 | 1994-04-01 237 | 1994-04-02 238 | 1994-04-03 239 | 1994-04-04 240 | 241 | >>> for d in drange(27/Mar/1994, 5/Apr/1994, 2*days): 242 | ... print(d) 243 | 1994-03-27 244 | 1994-03-29 245 | 1994-03-31 246 | 1994-04-02 247 | 1994-04-04 248 | ``` 249 | 250 | and datetimes: 251 | 252 | ```python3 253 | >>> for dt in drange((27/Mar/1994)[10:25], (4/Apr/1994)[10:10]): 254 | ... print(dt) 255 | 1994-03-27 10:25:00 256 | 1994-03-28 10:25:00 257 | 1994-03-29 10:25:00 258 | 1994-03-30 10:25:00 259 | 1994-03-31 10:25:00 260 | 1994-04-01 10:25:00 261 | 1994-04-02 10:25:00 262 | 1994-04-03 10:25:00 263 | 264 | >>> for dt in drange((27/Mar/1994)[10:25], (4/Apr/1994)[10:10], 20*hours): 265 | ... print(dt) 266 | 1994-03-27 10:25:00 267 | 1994-03-28 06:25:00 268 | 1994-03-29 02:25:00 269 | 1994-03-29 22:25:00 270 | 1994-03-30 18:25:00 271 | 1994-03-31 14:25:00 272 | 1994-04-01 10:25:00 273 | 1994-04-02 06:25:00 274 | 1994-04-03 02:25:00 275 | 1994-04-03 22:25:00 276 | ``` 277 | --------------------------------------------------------------------------------