Compare commits

..

1 Commits

Author SHA1 Message Date
Timo Furrer fa1c4eb600 Include epoch microseconds in DT comparison methods. Closes #156 2019-08-24 23:22:35 +02:00
7 changed files with 58 additions and 235 deletions
+13 -10
View File
@@ -1,6 +1,6 @@
name: Continuous Integration and Deployment name: Continuous Integration and Deployment
on: [push, pull_request] on: [push]
jobs: jobs:
build: build:
@@ -8,14 +8,15 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
fail-fast: false fail-fast: false
max-parallel: 4
matrix: matrix:
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] python-version: [2.7, 3.5, 3.6, 3.7]
os: [ubuntu-latest, macOS-latest, windows-latest] os: [ubuntu-latest, macOS-latest]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v1
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4.2.0 uses: actions/setup-python@v1
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Setup build and test environment - name: Setup build and test environment
@@ -45,9 +46,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v1
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4.2.0 uses: actions/setup-python@v1
with: with:
python-version: 3.7 python-version: 3.7
- name: Setup docs environment - name: Setup docs environment
@@ -61,18 +62,20 @@ jobs:
needs: [build, docs] needs: [build, docs]
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: startsWith(github.event.ref, 'refs/tags') && github.ref == 'refs/heads/master'
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v1
- name: Set up Python 3.7 - name: Set up Python 3.7
uses: actions/setup-python@v4.2.0 if: startsWith(github.event.ref, 'refs/tags')
uses: actions/setup-python@v1
with: with:
python-version: 3.7 python-version: 3.7
- name: Build Package - name: Build Package
if: startsWith(github.event.ref, 'refs/tags')
run: | run: |
python -m pip install --upgrade pip setuptools wheel python -m pip install --upgrade pip setuptools wheel
python setup.py sdist bdist_wheel --universal python setup.py sdist bdist_wheel --universal
- name: Publish Package on PyPI - name: Publish Package on PyPI
if: startsWith(github.event.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@master uses: pypa/gh-action-pypi-publish@master
with: with:
user: __token__ user: __token__
+18
View File
@@ -0,0 +1,18 @@
name: "Close Stale Issues and Pull Requests"
on:
schedule:
- cron: "0 * * * *"
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 60
days-before-close: 7
stale-issue-label: stale
stale-pr-label: state
stale-issue-message: 'This Issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days'
stale-pr-message: 'This Pull Request is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days'
+11 -6
View File
@@ -92,11 +92,6 @@ Behold, datetimes for humans!
>>> dt.snap('@d+3h').rfc2822() >>> dt.snap('@d+3h').rfc2822()
'Mon, 21 Feb 1994 03:00:00 GMT' 'Mon, 21 Feb 1994 03:00:00 GMT'
# snap modifiers within a timezone
>>> dt = maya.when('Mon, 21 Feb 1994 21:21:42 GMT')
>>> dt.snap_tz('+3h@d', 'Australia/Perth').rfc2822()
'Mon, 21 Feb 1994 16:00:00 GMT'
☤ Advanced Usage of Maya ☤ Advanced Usage of Maya
------------------------ ------------------------
@@ -131,7 +126,7 @@ From here, there are a number of methods available to you, which you can use to
☤ What about Delorean_, Arrow_, & Pendulum_? ☤ What about Delorean_, Arrow_, & Pendulum_?
-------------------------------------------- -----------------------------------------
All these projects complement each other, and are friends. Pendulum, for example, helps power Maya's parsing. All these projects complement each other, and are friends. Pendulum, for example, helps power Maya's parsing.
@@ -154,6 +149,16 @@ Installation is easy, with:
$ pip install maya $ pip install maya
☤ Demo
------
Try ``maya`` interactively using this online demo:
.. image:: https://user-images.githubusercontent.com/1155573/49400125-16e73500-f722-11e8-9275-e1d7eb3bbf99.png
:target: https://notebooks.ai/demo/gh/kennethreitz/maya
:alt: Open Live Demo
How to Contribute How to Contribute
----------------- -----------------
+1 -1
View File
@@ -73,7 +73,7 @@ release = maya.__version__
# #
# This is also used if you do content translation via gettext catalogs. # This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases. # Usually you set "language" from the command line for these cases.
language = "en" language = None
# List of patterns, relative to source directory, that match files and # List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files. # directories to ignore when looking for source files.
+15 -52
View File
@@ -78,7 +78,10 @@ class MayaDT(object):
def __init__(self, epoch): def __init__(self, epoch):
super(MayaDT, self).__init__() super(MayaDT, self).__init__()
self._epoch = epoch # round the given epoch's microseconds to 6 digits
# to be compatible with Python's datetime.microseconds.
# See: https://docs.python.org/3/library/datetime.html#datetime.datetime.microsecond
self._epoch = round(epoch, 6)
def __repr__(self): def __repr__(self):
return "<MayaDT epoch={}>".format(self._epoch) return "<MayaDT epoch={}>".format(self._epoch)
@@ -92,30 +95,30 @@ class MayaDT(object):
@validate_class_type_arguments("==") @validate_class_type_arguments("==")
def __eq__(self, maya_dt): def __eq__(self, maya_dt):
return int(self._epoch) == int(maya_dt._epoch) return self._epoch == maya_dt._epoch
@validate_class_type_arguments("!=") @validate_class_type_arguments("!=")
def __ne__(self, maya_dt): def __ne__(self, maya_dt):
return int(self._epoch) != int(maya_dt._epoch) return self._epoch != maya_dt._epoch
@validate_class_type_arguments("<") @validate_class_type_arguments("<")
def __lt__(self, maya_dt): def __lt__(self, maya_dt):
return int(self._epoch) < int(maya_dt._epoch) return self._epoch < maya_dt._epoch
@validate_class_type_arguments("<=") @validate_class_type_arguments("<=")
def __le__(self, maya_dt): def __le__(self, maya_dt):
return int(self._epoch) <= int(maya_dt._epoch) return self._epoch <= maya_dt._epoch
@validate_class_type_arguments(">") @validate_class_type_arguments(">")
def __gt__(self, maya_dt): def __gt__(self, maya_dt):
return int(self._epoch) > int(maya_dt._epoch) return self._epoch > maya_dt._epoch
@validate_class_type_arguments(">=") @validate_class_type_arguments(">=")
def __ge__(self, maya_dt): def __ge__(self, maya_dt):
return int(self._epoch) >= int(maya_dt._epoch) return self._epoch >= maya_dt._epoch
def __hash__(self): def __hash__(self):
return hash(int(self.epoch)) return hash(self.epoch)
def __add__(self, duration): def __add__(self, duration):
return self.add(seconds=_seconds_or_timedelta(duration).total_seconds()) return self.add(seconds=_seconds_or_timedelta(duration).total_seconds())
@@ -153,17 +156,6 @@ class MayaDT(object):
""" """
return self.from_datetime(snaptime.snap(self.datetime(), instruction)) return self.from_datetime(snaptime.snap(self.datetime(), instruction))
def snap_tz(self, instruction, in_timezone):
"""
Returns a new MayaDT object modified by the given instruction.
The modifications happen in the given timezone.
Powered by snaptime. See https://github.com/zartstrom/snaptime
for a complete documentation about the snaptime instructions.
"""
dt_tz = self.datetime(to_timezone=in_timezone)
return self.from_datetime(snaptime.snap_tz(dt_tz, instruction, dt_tz.tzinfo))
# Timezone Crap # Timezone Crap
# ------------- # -------------
@property @property
@@ -223,16 +215,6 @@ class MayaDT(object):
"""Returns MayaDT instance from iso8601 string.""" """Returns MayaDT instance from iso8601 string."""
return parse(iso8601_string) return parse(iso8601_string)
@classmethod
def from_long_count(klass, long_count_string):
"""Returns MayaDT instance from Maya Long Count string."""
days_since_creation = -1856305
factors = (144000, 7200, 360, 20, 1)
for i, value in enumerate(long_count_string.split('.')):
days_since_creation += int(value) * factors[i]
days_since_creation *= 3600 * 24
return klass(epoch=days_since_creation)
@staticmethod @staticmethod
def from_rfc2822(rfc2822_string): def from_rfc2822(rfc2822_string):
"""Returns MayaDT instance from rfc2822 string.""" """Returns MayaDT instance from rfc2822 string."""
@@ -257,10 +239,7 @@ class MayaDT(object):
if to_timezone: if to_timezone:
dt = self.datetime().astimezone(pytz.timezone(to_timezone)) dt = self.datetime().astimezone(pytz.timezone(to_timezone))
else: else:
try: dt = Datetime.utcfromtimestamp(self._epoch)
dt = Datetime.utcfromtimestamp(self._epoch)
except: # Fallback for before year 1970 issue
dt = Datetime.utcfromtimestamp(0) + timedelta(microseconds=self._epoch*1000000)
dt.replace(tzinfo=self._tz) dt.replace(tzinfo=self._tz)
# Strip the timezone info if requested to do so. # Strip the timezone info if requested to do so.
if naive: if naive:
@@ -293,22 +272,6 @@ class MayaDT(object):
"""Returns an RFC 3339 representation of the MayaDT.""" """Returns an RFC 3339 representation of the MayaDT."""
return self.datetime().strftime("%Y-%m-%dT%H:%M:%S.%f")[:-5] + "Z" return self.datetime().strftime("%Y-%m-%dT%H:%M:%S.%f")[:-5] + "Z"
def long_count(self):
"""Returns a Mayan Long Count representation of the Maya DT."""
# Creation (0.0.0.0.0) occurred on -3114-08-11
# 1856305 is distance (in days) between Creation and UNIX epoch
days_since_creation = int(1856305 + self._epoch / (3600 * 24))
caps = (0, 20, 20, 18, 20)
lc_date = [0, 0, 0, 0, days_since_creation]
for i in range(4, 0, -1):
if lc_date[i] >= caps[i]:
lc_date[i - 1] += int(lc_date[i] / caps[i])
lc_date[i] %= caps[i]
elif lc_date[i] < 0:
lc_date[i - 1] += int(lc_date[i] / caps[i])
lc_date[i] = 0
return '.'.join(str(i) for i in lc_date)
# Properties # Properties
# ---------- # ----------
@property @property
@@ -375,7 +338,7 @@ class MayaDT(object):
except KeyError: except KeyError:
pass pass
delta = humanize.time._abs_timedelta( delta = humanize.time.abs_timedelta(
timedelta(seconds=(self.epoch - now().epoch)) timedelta(seconds=(self.epoch - now().epoch))
) )
@@ -616,7 +579,7 @@ class MayaInterval(object):
start += duration start += duration
def quantize(self, duration, snap_out=False, timezone="UTC"): def quantize(self, duration, snap_out=False, timezone="UTC"):
"""Returns a quantized interval.""" """Returns a quanitzed interval."""
# Convert seconds to timedelta, if appropriate. # Convert seconds to timedelta, if appropriate.
duration = _seconds_or_timedelta(duration) duration = _seconds_or_timedelta(duration)
timezone = pytz.timezone(timezone) timezone = pytz.timezone(timezone)
@@ -724,7 +687,7 @@ def when(string, timezone="UTC", prefer_dates_from="current_period"):
Keyword Arguments: Keyword Arguments:
string -- string to be parsed string -- string to be parsed
timezone -- timezone referenced from (default: 'UTC') timezone -- timezone referenced from (default: 'UTC')
prefer_dates_from -- what dates are preferred when `string` is ambiguous. prefer_dates_from -- what dates are prefered when `string` is ambigous.
options are 'past', 'future', and 'current_period' options are 'past', 'future', and 'current_period'
(default: 'current_period'). see: [1] (default: 'current_period'). see: [1]
-73
View File
@@ -1,6 +1,3 @@
import datetime
import pytz
from freezegun import freeze_time from freezegun import freeze_time
import pytest import pytest
@@ -26,73 +23,3 @@ def frozen_now(request):
now_string, tz_offset = request.param now_string, tz_offset = request.param
with freeze_time(now_string, tz_offset=tz_offset): with freeze_time(now_string, tz_offset=tz_offset):
yield yield
@pytest.fixture(params=[
datetime.datetime(2020, 8, 10, 22, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 8, 10, 23, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 8, 11, 0, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 8, 11, 1, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 8, 11, 2, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 8, 11, 3, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 8, 11, 4, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 8, 11, 5, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 8, 11, 6, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 8, 11, 7, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 8, 11, 8, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 8, 11, 9, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 8, 11, 10, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 8, 11, 11, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 8, 11, 12, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 8, 11, 13, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 8, 11, 14, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 8, 11, 15, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 8, 11, 16, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 8, 11, 17, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 8, 11, 18, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 8, 11, 19, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 8, 11, 20, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 8, 11, 21, 2, 0, tzinfo=pytz.timezone('UTC')),
], ids=str)
def frozen_2020_08_11_in_paris(request):
"""
fixture setting datetime.now() to every hour of the 11th of august 2020 in Paris
(summer time, GMT+2)
"""
with freeze_time(request.param):
yield
@pytest.fixture(params=[
datetime.datetime(2020, 2, 10, 23, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 2, 11, 0, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 2, 11, 1, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 2, 11, 2, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 2, 11, 3, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 2, 11, 4, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 2, 11, 5, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 2, 11, 6, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 2, 11, 7, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 2, 11, 8, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 2, 11, 9, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 2, 11, 10, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 2, 11, 11, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 2, 11, 12, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 2, 11, 13, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 2, 11, 14, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 2, 11, 15, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 2, 11, 16, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 2, 11, 17, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 2, 11, 18, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 2, 11, 19, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 2, 11, 20, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 2, 11, 21, 2, 0, tzinfo=pytz.timezone('UTC')),
datetime.datetime(2020, 2, 11, 22, 2, 0, tzinfo=pytz.timezone('UTC')),
], ids=str)
def frozen_2020_02_11_in_paris(request):
"""
fixture setting datetime.now() to every hour of the 11th of february 2020 in Paris
(winter time, GMT+1)
"""
with freeze_time(request.param):
yield
-93
View File
@@ -30,21 +30,6 @@ def test_iso8601(string, expected):
assert r == d.iso8601() assert r == d.iso8601()
@pytest.mark.parametrize(
"string,expected",
[
('January 1, 1970', "12.17.16.7.5"),
('December 21, 2012', "13.0.0.0.0"),
('March 4, 1900', "12.14.5.10.0"),
],
)
def test_long_count(string, expected):
r = maya.parse(string).long_count()
d = maya.MayaDT.from_long_count(r)
assert r == expected
assert r == d.long_count()
@pytest.mark.parametrize( @pytest.mark.parametrize(
"string,expected", "string,expected",
[ [
@@ -86,28 +71,6 @@ def test_issue_104():
t = maya.MayaDT.from_struct(t) t = maya.MayaDT.from_struct(t)
assert str(t) == "Wed, 11 Oct 2017 21:12:11 GMT" assert str(t) == "Wed, 11 Oct 2017 21:12:11 GMT"
def test_before_1970():
d1 = maya.when("1899-17-11 08:09:10")
assert d1.year == 1899
assert d1.month == 11
assert d1.day == 17
assert d1.week == 46
assert d1.weekday == 5
assert d1.hour == 8
assert d1.minute == 9
assert d1.second == 10
assert d1.microsecond == 0
# Test properties for maya.parse()
d2 = maya.parse("February 29, 1904 13:12:34")
assert d2.year == 1904
assert d2.month == 2
assert d2.day == 29
assert d2.week == 9
assert d2.weekday == 1
assert d2.hour == 13
assert d2.minute == 12
assert d2.second == 34
assert d2.microsecond == 0
def test_human_when(): def test_human_when():
r1 = maya.when("yesterday") r1 = maya.when("yesterday")
@@ -115,42 +78,6 @@ def test_human_when():
assert (r2.day - r1.day) in (1, -30, -29, -28, -27) assert (r2.day - r1.day) in (1, -30, -29, -28, -27)
@pytest.mark.usefixtures("frozen_2020_08_11_in_paris")
def test_human_when_today_with_timezone_summer_time():
d = maya.when("today", timezone='Europe/Paris')
assert str(d.datetime(to_timezone="Europe/Paris").date()) == '2020-08-11'
@pytest.mark.usefixtures("frozen_2020_02_11_in_paris")
def test_human_when_today_with_timezone_winter_time():
d = maya.when("today", timezone='Europe/Paris')
assert str(d.datetime(to_timezone="Europe/Paris").date()) == '2020-02-11'
@pytest.mark.usefixtures("frozen_2020_08_11_in_paris")
def test_human_when_yesterday_with_timezone_summer_time():
d = maya.when("yesterday", timezone='Europe/Paris')
assert str(d.datetime(to_timezone="Europe/Paris").date()) == '2020-08-10'
@pytest.mark.usefixtures("frozen_2020_02_11_in_paris")
def test_human_when_yesterday_with_timezone_winter_time():
d = maya.when("yesterday", timezone='Europe/Paris')
assert str(d.datetime(to_timezone="Europe/Paris").date()) == '2020-02-10'
@pytest.mark.usefixtures("frozen_2020_08_11_in_paris")
def test_human_when_midnight_with_timezone_summer_time():
d = maya.when("midnight", timezone='Europe/Paris')
assert str(d.datetime(to_timezone="Europe/Paris")) == '2020-08-11 00:00:00+02:00'
@pytest.mark.usefixtures("frozen_2020_02_11_in_paris")
def test_human_when_midnight_with_timezone_winter_time():
d = maya.when("midnight", timezone='Europe/Paris')
assert str(d.datetime(to_timezone="Europe/Paris")) == '2020-02-11 00:00:00+01:00'
def test_machine_parse(): def test_machine_parse():
r1 = maya.parse("August 14, 2015") r1 = maya.parse("August 14, 2015")
assert r1.day == 14 assert r1.day == 14
@@ -441,23 +368,3 @@ def test_snaptime(when_str, snap_str, expected_when):
dt = dt.snap(snap_str) dt = dt.snap(snap_str)
# then # then
assert dt == maya.when(expected_when) assert dt == maya.when(expected_when)
@pytest.mark.parametrize(
"when_str,snap_str,timezone,expected_when",
[
(
"Mon, 21 Feb 1994 21:21:42 GMT",
"@d",
"Australia/Perth",
"Mon, 21 Feb 1994 16:00:00 GMT",
)
],
)
def test_snaptime_tz(when_str, snap_str, timezone, expected_when):
# given
dt = maya.when(when_str)
# when
dt = dt.snap_tz(snap_str, timezone)
# then
assert dt == maya.when(expected_when)