Repository: kuzmoyev/beautiful-date (language: python)

Issue:
Rails like syntax possible?
In Rails you can call datetime methods on integers that return datetime objects such as `1.month.from_now`.

Would the package benefit from some addition like that? Perhaps add an option to enable/disable rails like datetime monkeypatching.  


Discussion / hints:
That looks interesting. Unfortunately this specifically isn't possible (to my knowledge). But something like 
```
1 * months.from_now
```
is possible. Could you please share a list of available methods (or how these are called in Rails) like that? I'll think about implementing these in my free time.

Great suggestion! Thanks
https://api.rubyonrails.org/v3.1/classes/Numeric.html#method-i-from_now

Should help. 


Yeah, I think ` 1 * month.from_now` is more intuitive. The first time I saw the Rails syntax was jarring. 

Fix patch:
diff --git a/README.md b/README.md
index ddcaeb2..7fbf48f 100644
--- a/README.md
+++ b/README.md
@@ -65,22 +65,6 @@ BeautifulDate(2018, 3, 25)
 BeautifulDate(2018, 3, 25)
 ```
 
-Available formats (needed only if you create dates using `D@`):
-
-```python3
-class DMY(BaseDateFormat):
-    _format = 'day', 'month', 'year'
-
-class MDY(BaseDateFormat):
-    _format = 'month', 'day', 'year'
-
-class YMD(BaseDateFormat):
-    _format = 'year', 'month', 'day'
-
-class YDM(BaseDateFormat):
-    _format = 'year', 'day', 'month'
-``` 
-
 You can also easily retrieve current date as a `BeautifulDate` object and current time using:
 
 ```python3
@@ -89,6 +73,12 @@ BeautifulDate(2020, 8, 24)
 
 >>> D.now()
 datetime.datetime(2020, 8, 24, 0, 59, 12, 451363)
+
+>>> D.tomorrow()
+BeautifulDate(2020, 8, 25)
+
+>>> D.yesterday()
+BeautifulDate(2020, 8, 23)
 ```
 
 ### Create Datetime
@@ -132,10 +122,31 @@ This library also provides simple interface for
 [relativedelta](http://dateutil.readthedocs.io/en/stable/relativedelta.html) from 
 [dateutil](http://dateutil.readthedocs.io/en/stable/index.html)
 
-#### Adding/Subtracting/Setting timedeltas:
-
 Notice that singular time unit (year, month, ...) sets given value, plural (years, months,) adds it.
 
+#### Shortcuts:
+
+```python
+>>> 5*days.from_today
+BeautifulDate(2023, 9, 17)
+
+>>> 1*hours.from_now
+datetime.datetime(2023, 9, 12, 12, 53, 56)
+
+>>> 3*days.since(15/Mar/2023)
+BeautifulDate(2023, 3, 18)
+
+>>> 5*days.until_today
+BeautifulDate(2023, 9, 7)
+
+>>> 1*hours.until_now
+datetime.datetime(2023, 9, 12, 11, 13, 4)
+
+>>> 3*days.until(15/Mar/2023)
+BeautifulDate(2023, 3, 12)
+```
+
+#### Adding/Subtracting/Setting timedeltas:
 
 ```python3
 >>> d = 26/Mar/2018
diff --git a/beautiful_date/beautiful_date.py b/beautiful_date/beautiful_date.py
index 330a5a5..9424862 100644
--- a/beautiful_date/beautiful_date.py
+++ b/beautiful_date/beautiful_date.py
@@ -1,5 +1,7 @@
 from datetime import date, datetime
 
+from dateutil.relativedelta import relativedelta
+
 
 class BeautifulDate(date):
     """Date object that can be extended to datetime by using Python indexing/slicing:
@@ -102,6 +104,16 @@ def today():
     def now(tz=None):
         return datetime.now(tz=tz)
 
+    @staticmethod
+    def tomorrow():
+        day = D.today() + relativedelta(days=1)
+        return BeautifulDate(year=day.year, month=day.month, day=day.day)
+
+    @staticmethod
+    def yesterday():
+        day = D.today() - relativedelta(days=1)
+        return BeautifulDate(year=day.year, month=day.month, day=day.day)
+
 
 class DMY(BaseDateFormat):
     _format = 'day', 'month', 'year'
diff --git a/beautiful_date/beautiful_timedelta.py b/beautiful_date/beautiful_timedelta.py
index c7753a6..558c6bb 100644
--- a/beautiful_date/beautiful_timedelta.py
+++ b/beautiful_date/beautiful_timedelta.py
@@ -1,10 +1,10 @@
 from dateutil.relativedelta import relativedelta, weekdays
 from datetime import date, datetime
 
-from beautiful_date import BeautifulDate
+from beautiful_date import BeautifulDate, D
 
 
-class RelativeDelta(relativedelta):
+class BeautifulRelativeDelta(relativedelta):
     """Same as relativedelta, but returns BeautifulDate in the result.
 
     Examples:
@@ -23,21 +23,74 @@ def __add__(self, d):
 
 
 class BeautifulTimedelta:
-    """Creates timedelta with specified time unit using operator '*'
+    """Creates timedelta with specified time unit using operator '*'."""
 
-    Examples:
-        >>> 3*years
-        RelativeDelta(years=+3)
-
-        >>> -5*weeks
-        RelativeDelta(days=-35)
-    """
-
-    def __init__(self, name):
+    def __init__(self, name, start=None, is_until=False):
         self.name = name
+        self.start = start
+        self.is_until = is_until
 
     def __rmul__(self, n):
-        return RelativeDelta(**{self.name: n})
+        """If start is set, returns BeautifulDate or datetime (depending on the start's type).
+        Examples:
+            >>> 5*days.from_today
+            BeautifulDate(2023, 9, 17)
+
+            >>> 1*hours.from_now
+            datetime.datetime(2023, 9, 12, 12, 53, 56)
+
+            >>> 5*days.until_today
+            BeautifulDate(2023, 9, 7)
+
+            >>> 1*hours.until_now
+            datetime.datetime(2023, 9, 12, 11, 13, 4)
+
+
+
+        Otherwise, returns BeautifulRelativeDelta.
+        Examples:
+            >>> 3*years
+            BeautifulRelativeDelta(years=+3)
+
+            >>> -5*weeks
+            BeautifulRelativeDelta(days=-35)
+        """
+
+        if self.start is not None:
+            if self.is_until:
+                return self.start + BeautifulRelativeDelta(**{self.name: -n})
+            else:
+                return self.start + BeautifulRelativeDelta(**{self.name: n})
+        else:
+            return BeautifulRelativeDelta(**{self.name: n})
+
+    def from_(self, start):
+        return BeautifulTimedelta(self.name, start=start)
+
+    since = from_
+
+    @property
+    def from_now(self):
+        return self.from_(D.now())
+
+    @property
+    def from_today(self):
+        return self.from_(D.today())
+
+    def until(self, start):
+        return BeautifulTimedelta(self.name, start=start, is_until=True)
+
+    before = until
+
+    @property
+    def until_now(self):
+        return self.until(D.now())
+
+    ago = until_now
+
+    @property
+    def until_today(self):
+        return self.until(D.today())
 
 
 _ = BeautifulTimedelta
diff --git a/setup.py b/setup.py
index 1455ecb..d583307 100644
--- a/setup.py
+++ b/setup.py
@@ -64,6 +64,7 @@ def run(self):
     "pytest>=5.4",
     "pytest-cov>=2.10",
     "flake8>3.8.3",
+    "freezegun",
     "tox"
 ]
 
diff --git a/tox.ini b/tox.ini
index 50f2b2c..03ddd92 100644
--- a/tox.ini
+++ b/tox.ini
@@ -30,6 +30,7 @@ omit =
 deps =
     pyfakefs
     pytest
+    freezegun
 commands =
     pytest
 
@@ -38,6 +39,7 @@ deps =
     pyfakefs
     pytest
     pytest-cov
+    freezegun
 commands =
     pytest --cov-report xml --cov=beautiful_date beautiful_tests
 


Test patch:
diff --git a/beautiful_tests/test_beautiful_date.py b/beautiful_tests/test_beautiful_date.py
index c07034c..a43f0ca 100644
--- a/beautiful_tests/test_beautiful_date.py
+++ b/beautiful_tests/test_beautiful_date.py
@@ -41,9 +41,11 @@ def test_datetime_create_by_prefix(self):
         self.assertEqual((D @ 29 - 2 - 1988)[10:11:12],
                          datetime(day=29, month=2, year=1988, hour=10, minute=11, second=12))
 
-    def test_today_now(self):
+    def test_today_now_tomorrow_yesterday(self):
         self.assertIsInstance(D.today(), BeautifulDate)
         self.assertIsInstance(D.now(), datetime)
+        self.assertIsInstance(D.tomorrow(), BeautifulDate)
+        self.assertIsInstance(D.yesterday(), BeautifulDate)
 
     def test_time_type_error(self):
         with self.assertRaises(TypeError):
diff --git a/beautiful_tests/test_beautiful_timedelta.py b/beautiful_tests/test_beautiful_timedelta.py
index c4dc474..0dbff69 100644
--- a/beautiful_tests/test_beautiful_timedelta.py
+++ b/beautiful_tests/test_beautiful_timedelta.py
@@ -1,10 +1,12 @@
 import unittest
 from datetime import date, datetime
 
+from freezegun import freeze_time
+
 from beautiful_date import Feb, Mar, Oct, \
     years, months, weeks, days, hours, minutes, seconds, microseconds, leapday, \
     year, month, day, hour, minute, second, microsecond, yearday, nlyearday, \
-    MO, SA
+    MO, SA, D
 
 
 class TestBeautifulTimedelta(unittest.TestCase):
@@ -101,3 +103,24 @@ def test_beautiful_weekdays(self):
         self.assertEqual(d + MO(2), date(2018, 4, 9))
         self.assertEqual(d - SA(2), date(2018, 3, 17))
         self.assertEqual(d + SA(-2), date(2018, 3, 17))
+
+    def test_with_start(self):
+        initial_date = D.today()
+        with freeze_time(initial_date):
+            self.assertEqual(initial_date + 2 * days, 2 * days.from_today)
+            self.assertEqual(initial_date + 3 * days, 2 * days.from_(initial_date + 1 * days))
+            self.assertEqual(initial_date + 3 * days, 2 * days.since(initial_date + 1 * days))
+
+            self.assertEqual(initial_date - 2 * days, 2 * days.until_today)
+            self.assertEqual(initial_date - 3 * days, 2 * days.until(initial_date - 1 * days))
+            self.assertEqual(initial_date - 3 * days, 2 * days.before(initial_date - 1 * days))
+
+        initial_time = D.now()
+        with freeze_time(initial_time):
+            self.assertEqual(initial_time + 2 * hours, 2 * hours.from_now)
+            self.assertEqual(initial_time + 3 * hours, 2 * hours.from_(initial_time + 1 * hours))
+            self.assertEqual(initial_time + 3 * hours, 2 * hours.since(initial_time + 1 * hours))
+
+            self.assertEqual(initial_time - 2 * hours, 2 * hours.until_now)
+            self.assertEqual(initial_time - 3 * hours, 2 * hours.until(initial_time - 1 * hours))
+            self.assertEqual(initial_time - 3 * hours, 2 * hours.before(initial_time - 1 * hours))
diff --git a/beautiful_tests/test_date_range.py b/beautiful_tests/test_date_range.py
index 358bae8..ed0c83d 100644
--- a/beautiful_tests/test_date_range.py
+++ b/beautiful_tests/test_date_range.py
@@ -99,9 +99,9 @@ def test_beautiful_drange_datetimes(self):
 
     def test_repr(self):
         self.assertEqual(repr(drange(27 / Mar / 1994, 5 / Apr / 1994)),
-                         'drange(1994-03-27, 1994-04-05, RelativeDelta(days=+1))')
+                         'drange(1994-03-27, 1994-04-05, BeautifulRelativeDelta(days=+1))')
         self.assertEqual(repr(drange((27 / Mar / 1994)[10:25], (4 / Apr / 1994)[10:10], 12 * hours)),
-                         'drange(1994-03-27 10:25:00, 1994-04-04 10:10:00, RelativeDelta(hours=+12))')
+                         'drange(1994-03-27 10:25:00, 1994-04-04 10:10:00, BeautifulRelativeDelta(hours=+12))')
 
     def test_zero_step_error(self):
         with self.assertRaises(ValueError):