mirror of
https://github.com/kennethreitz/maya.git
synced 2026-06-05 14:50:19 +00:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cc698817b4 | |||
| 9964e9305e | |||
| d87236a30d | |||
| d44a1dd2f6 | |||
| ed5d72b34c | |||
| 9c1e94f956 | |||
| 6755968f69 | |||
| 3760af2c41 | |||
| df09625e74 | |||
| 2d848bd62a | |||
| a99cddf76f | |||
| 1b432d6626 | |||
| f09d6eec63 | |||
| 62a6283200 | |||
| 9ef43a29d9 | |||
| a2bff42439 | |||
| 5525beda31 | |||
| eeb07d46db | |||
| 4056d1a9aa | |||
| 7a750a1cff | |||
| bc06315abd | |||
| 7cc767781a | |||
| 8468dd2ead | |||
| 046f005ca7 | |||
| cd0b2300d7 | |||
| d3ddb39d9d | |||
| 3c8fe4478c | |||
| a0983132bb | |||
| 9612d70707 | |||
| 363fae1aaf | |||
| be2cd96132 | |||
| 771a2b6ce2 | |||
| 8e644f655a | |||
| 6903f6eb63 | |||
| b05ca8707c |
@@ -24,3 +24,4 @@ In chronological order:
|
||||
- Evan Mattiza <emattiza@gmail.com> (`@emattiza <https://github.com/emattiza>`_)
|
||||
- Dima Spivak <dima@spivak.ch> (`@dimaspivak <https://github.com/dimaspivak>`_)
|
||||
- Tom Barron <tusculum@gmail.com> (`@dtbarron <https://github.com/tbarron>`_)
|
||||
- Alex Ward <alxwrd@gmail.com> (`@alxwrd <https://github.com/alxwrd>`_)
|
||||
|
||||
@@ -22,6 +22,10 @@ Maya is mostly built around the headaches and use-cases around parsing datetime
|
||||
|
||||
.. image:: https://farm4.staticflickr.com/3702/33288285996_5b69d2b8f7_k_d.jpg
|
||||
|
||||
Art by `Sam Flores
|
||||
<https://www.instagram.com/samagram12/>`_ (Photo by `Kenneth Reitz
|
||||
<https://www.instagram.com/kennethreitz/>`_).
|
||||
|
||||
|
||||
☤ Basic Usage of Maya
|
||||
---------------------
|
||||
@@ -73,6 +77,10 @@ Behold, datetimes for humans!
|
||||
>>> m = maya.MayaDT.from_struct(time.gmtime())
|
||||
>>> print(m)
|
||||
Wed, 20 Sep 2017 17:24:32 GMT
|
||||
|
||||
>>> m = maya.MayaDT(time.time())
|
||||
>>> print(m)
|
||||
Wed, 20 Sep 2017 17:24:32 GMT
|
||||
|
||||
>>> rand_day.day
|
||||
7
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
__version__ = '0.3.4'
|
||||
__version__ = '0.5.0'
|
||||
|
||||
+61
-14
@@ -14,6 +14,7 @@ import pendulum
|
||||
import snaptime
|
||||
from tzlocal import get_localzone
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from dateparser.languages.loader import default_loader
|
||||
|
||||
from .compat import cmp, comparable
|
||||
|
||||
@@ -152,7 +153,7 @@ class MayaDT(object):
|
||||
|
||||
def subtract_date(self, **kwargs):
|
||||
"""Returns a timedelta object with the duration between the dates"""
|
||||
return timedelta(self.epoch - kwargs['dt'].epoch)
|
||||
return timedelta(seconds=self.epoch - kwargs['dt'].epoch)
|
||||
|
||||
def snap(self, instruction):
|
||||
"""
|
||||
@@ -257,6 +258,14 @@ class MayaDT(object):
|
||||
dt = dt.replace(tzinfo=self._tz)
|
||||
return dt
|
||||
|
||||
def local_datetime(self):
|
||||
"""Returns a local timezone-aware datetime object
|
||||
|
||||
It's the same as:
|
||||
mayaDt.datetime(to_timezone=mayaDt.local_timezone)
|
||||
"""
|
||||
return self.datetime(to_timezone=self.local_timezone, naive=False)
|
||||
|
||||
def iso8601(self):
|
||||
"""Returns an ISO 8601 representation of the MayaDT."""
|
||||
# Get a timezone-naive datetime.
|
||||
@@ -269,7 +278,7 @@ class MayaDT(object):
|
||||
|
||||
def rfc3339(self):
|
||||
"""Returns an RFC 3339 representation of the MayaDT."""
|
||||
return self.datetime().strftime("%Y-%m-%dT%H:%M:%S.%f")[:-4] + "Z"
|
||||
return self.datetime().strftime("%Y-%m-%dT%H:%M:%S.%f")[:-5] + "Z"
|
||||
|
||||
# Properties
|
||||
# ----------
|
||||
@@ -323,15 +332,39 @@ class MayaDT(object):
|
||||
|
||||
# Human Slang Extras
|
||||
# ------------------
|
||||
def slang_date(self):
|
||||
""""Returns human slang representation of date."""
|
||||
dt = self.datetime(naive=True, to_timezone=self.local_timezone)
|
||||
return humanize.naturaldate(dt)
|
||||
def slang_date(self, locale="en"):
|
||||
""""Returns human slang representation of date.
|
||||
|
||||
def slang_time(self):
|
||||
""""Returns human slang representation of time."""
|
||||
dt = self.datetime(naive=True, to_timezone=self.local_timezone)
|
||||
return humanize.naturaltime(dt)
|
||||
Keyword Arguments:
|
||||
locale -- locale to translate to, e.g. 'fr' for french.
|
||||
(default: 'en' - English)
|
||||
"""
|
||||
dt = pendulum.instance(self.datetime())
|
||||
|
||||
try:
|
||||
return _translate(dt, locale)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
dt.set_formatter("alternative")
|
||||
delta = humanize.time.abs_timedelta(
|
||||
timedelta(seconds=(self.epoch - now().epoch)))
|
||||
|
||||
format_string = "DD MMM"
|
||||
if delta.days >= 365:
|
||||
format_string += " YYYY"
|
||||
|
||||
return dt.format(format_string, locale=locale).title()
|
||||
|
||||
def slang_time(self, locale="en"):
|
||||
""""Returns human slang representation of time.
|
||||
|
||||
Keyword Arguments:
|
||||
locale -- locale to translate to, e.g. 'fr' for french.
|
||||
(default: 'en' - English)
|
||||
"""
|
||||
dt = self.datetime()
|
||||
return pendulum.instance(dt).diff_for_humans(locale=locale)
|
||||
|
||||
|
||||
def utc_offset(time_struct=None):
|
||||
@@ -675,7 +708,7 @@ def now():
|
||||
return MayaDT(epoch=epoch)
|
||||
|
||||
|
||||
def when(string, timezone='UTC', prefer_past=False):
|
||||
def when(string, timezone='UTC', prefer_dates_from='current_period'):
|
||||
""""Returns a MayaDT instance for the human moment specified.
|
||||
|
||||
Powered by dateparser. Useful for scraping websites.
|
||||
@@ -686,16 +719,20 @@ def when(string, timezone='UTC', prefer_past=False):
|
||||
Keyword Arguments:
|
||||
string -- string to be parsed
|
||||
timezone -- timezone referenced from (default: 'UTC')
|
||||
prefer_past -- prefer parsing ambiguous date as in the past
|
||||
prefer_dates_from -- what dates are prefered when `string` is ambigous.
|
||||
options are 'past', 'future', and 'current_period'
|
||||
(default: 'current_period'). see: [1]
|
||||
|
||||
Reference:
|
||||
[1] dateparser.readthedocs.io/en/latest/usage.html#handling-incomplete-dates
|
||||
"""
|
||||
settings = {
|
||||
'TIMEZONE': timezone,
|
||||
'RETURN_AS_TIMEZONE_AWARE': True,
|
||||
'TO_TIMEZONE': 'UTC',
|
||||
'PREFER_DATES_FROM': prefer_dates_from,
|
||||
}
|
||||
if prefer_past:
|
||||
settings['PREFER_DATES_FROM'] = 'past'
|
||||
|
||||
dt = dateparser.parse(string, settings=settings)
|
||||
if dt is None:
|
||||
raise ValueError('invalid datetime input specified.')
|
||||
@@ -747,6 +784,16 @@ def _seconds_or_timedelta(duration):
|
||||
return dt_timedelta
|
||||
|
||||
|
||||
def _translate(dt, target_locale):
|
||||
en = default_loader.get_locale("en")
|
||||
target = default_loader.get_locale(target_locale)
|
||||
naturaldate = humanize.naturaldate(dt)
|
||||
|
||||
base = en.translate(naturaldate, settings=dateparser.conf.settings)
|
||||
|
||||
return target.info["relative-type"][base][-1]
|
||||
|
||||
|
||||
def intervals(start, end, interval):
|
||||
"""
|
||||
Yields MayaDT objects between the start and end MayaDTs given,
|
||||
|
||||
@@ -25,7 +25,7 @@ required = [
|
||||
'pytz',
|
||||
'dateparser>=0.7.0',
|
||||
'tzlocal',
|
||||
'pendulum>=1.0',
|
||||
'pendulum>=1.0, <=1.5.1',
|
||||
'snaptime'
|
||||
]
|
||||
|
||||
|
||||
+84
-14
@@ -1,5 +1,6 @@
|
||||
import copy
|
||||
import time
|
||||
import calendar
|
||||
from datetime import timedelta, datetime as Datetime
|
||||
|
||||
import pytz
|
||||
@@ -157,9 +158,19 @@ def test_slang_date():
|
||||
assert d.slang_date() == 'tomorrow'
|
||||
|
||||
|
||||
def test_slang_date_locale():
|
||||
d = maya.when('tomorrow')
|
||||
assert d.slang_date(locale='fr') == 'demain'
|
||||
|
||||
|
||||
def test_slang_time():
|
||||
d = maya.when('1 hour ago')
|
||||
assert d.slang_time() == 'an hour ago'
|
||||
assert d.slang_time() == '1 hour ago'
|
||||
|
||||
|
||||
def test_slang_time_locale():
|
||||
d = maya.when('1 hour ago')
|
||||
assert d.slang_time(locale='de') == 'vor 1 Stunde'
|
||||
|
||||
|
||||
@pytest.mark.parametrize("string,kwds,expected", [
|
||||
@@ -191,16 +202,46 @@ def test_parse(string, kwds, expected):
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_when_past():
|
||||
next_month = str(maya.now().add(months=1).month)
|
||||
this_year = maya.now().year
|
||||
last_year = this_year - 1
|
||||
future_date = maya.when(next_month)
|
||||
past_date = maya.when(next_month, prefer_past=True)
|
||||
assert future_date.year == this_year
|
||||
if next_month == '1':
|
||||
assert past_date.year == this_year
|
||||
else:
|
||||
assert past_date.year == last_year
|
||||
two_days_away = maya.now().add(days=2)
|
||||
|
||||
past_date = maya.when(
|
||||
two_days_away.slang_date(),
|
||||
prefer_dates_from='past')
|
||||
|
||||
assert past_date < maya.now()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_when_future():
|
||||
two_days_away = maya.now().add(days=2)
|
||||
|
||||
future_date = maya.when(
|
||||
two_days_away.slang_date(),
|
||||
prefer_dates_from='future')
|
||||
|
||||
assert future_date > maya.now()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_when_past_day_name():
|
||||
two_days_away = maya.now().add(days=2)
|
||||
|
||||
past_date = maya.when(
|
||||
calendar.day_name[two_days_away.weekday],
|
||||
prefer_dates_from='past')
|
||||
|
||||
assert past_date < maya.now()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_when_future_day_name():
|
||||
two_days_away = maya.now().add(days=2)
|
||||
|
||||
future_date = maya.when(
|
||||
calendar.day_name[two_days_away.weekday],
|
||||
prefer_dates_from='future')
|
||||
|
||||
assert future_date > maya.now()
|
||||
|
||||
|
||||
def test_datetime_to_timezone():
|
||||
@@ -208,13 +249,20 @@ def test_datetime_to_timezone():
|
||||
assert dt.tzinfo.zone == 'US/Eastern'
|
||||
|
||||
|
||||
def test_rfc3339():
|
||||
def test_rfc3339_epoch():
|
||||
mdt = maya.when('2016-01-01')
|
||||
out = mdt.rfc3339()
|
||||
mdt2 = maya.MayaDT.from_rfc3339(out)
|
||||
assert mdt.epoch == mdt2.epoch
|
||||
|
||||
|
||||
def test_rfc3339_format():
|
||||
rfc3339 = maya.MayaDT.rfc3339(maya.when('2016-01-01T12:03:03Z'))
|
||||
# it's important that the string has got a "max 1-digit millis" fragment
|
||||
# as per https://tools.ietf.org/html/rfc3339#section-5.6
|
||||
assert rfc3339 == '2016-01-01T12:03:03.0Z'
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("frozen_now")
|
||||
def test_comparison_operations():
|
||||
now = maya.now()
|
||||
@@ -289,8 +337,8 @@ def test_dunder_sub():
|
||||
def test_mayaDT_sub():
|
||||
now = maya.now()
|
||||
then = now.add(days=1)
|
||||
assert then - now == timedelta(24 * 60 * 60)
|
||||
assert now - then == timedelta(-24 * 60 * 60)
|
||||
assert then - now == timedelta(seconds=24 * 60 * 60)
|
||||
assert now - then == timedelta(seconds=-24 * 60 * 60)
|
||||
|
||||
|
||||
def test_core_local_timezone(monkeypatch):
|
||||
@@ -311,6 +359,28 @@ def test_core_local_timezone(monkeypatch):
|
||||
assert mdt.local_timezone == 'UTC'
|
||||
|
||||
|
||||
def test_getting_datetime_for_local_timezone(monkeypatch):
|
||||
|
||||
@property
|
||||
def mock_local_tz(self):
|
||||
class StaticTzInfo(object):
|
||||
zone = 'Europe/Zurich'
|
||||
|
||||
def __repr__(self):
|
||||
return "<StaticTzInfo 'Europe/Zurich'>"
|
||||
|
||||
return StaticTzInfo()
|
||||
|
||||
monkeypatch.setattr(maya.MayaDT, '_local_tz', mock_local_tz)
|
||||
|
||||
d = maya.parse('1994-02-21T12:00:00+05:30')
|
||||
|
||||
dt = pytz.timezone('Europe/Zurich').localize(
|
||||
Datetime(1994, 2, 21, 7, 30))
|
||||
|
||||
assert d.local_datetime() == dt
|
||||
|
||||
|
||||
@pytest.mark.parametrize("when_str,snap_str,expected_when", [
|
||||
('Mon, 21 Feb 1994 21:21:42 GMT', '@d',
|
||||
'Mon, 21 Feb 1994 00:00:00 GMT'),
|
||||
|
||||
Reference in New Issue
Block a user