Repository: crsmithdev/arrow
Pull request #642 (2019-08-17 23:37:40)

Add week granulation and week locale handling
Fixes conflicts of this stale PR: https://github.com/crsmithdev/arrow/pull/529.

Closes: https://github.com/crsmithdev/arrow/issues/524

codecov-io: # [Codecov](https://codecov.io/gh/crsmithdev/arrow/pull/642?src=pr&el=h1) Report
> Merging [#642](https://codecov.io/gh/crsmithdev/arrow/pull/642?src=pr&el=desc) into [master](https://codecov.io/gh/crsmithdev/arrow/commit/11f5751e6dd6b1854829ab325ff208d79c9bee2d?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/crsmithdev/arrow/pull/642/graphs/tree.svg?width=650&token=4EkGnFsIGn&height=150&src=pr)](https://codecov.io/gh/crsmithdev/arrow/pull/642?src=pr&el=tree)

```diff
@@          Coverage Diff          @@
##           master   #642   +/-   ##
=====================================
  Coverage     100%   100%           
=====================================
  Files           8      8           
  Lines        1519   1526    +7     
  Branches      226    229    +3     
=====================================
+ Hits         1519   1526    +7
```


| [Impacted Files](https://codecov.io/gh/crsmithdev/arrow/pull/642?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [arrow/locales.py](https://codecov.io/gh/crsmithdev/arrow/pull/642/diff?src=pr&el=tree#diff-YXJyb3cvbG9jYWxlcy5weQ==) | `100% <ø> (ø)` | :arrow_up: |
| [arrow/arrow.py](https://codecov.io/gh/crsmithdev/arrow/pull/642/diff?src=pr&el=tree#diff-YXJyb3cvYXJyb3cucHk=) | `100% <100%> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/crsmithdev/arrow/pull/642?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/crsmithdev/arrow/pull/642?src=pr&el=footer). Last update [11f5751...a6a7f51](https://codecov.io/gh/crsmithdev/arrow/pull/642?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/.gitignore b/.gitignore
index 723b757..c68a2fb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+README.rst.new
+
 # Small entry point file for debugging tasks
 test.py
 
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 825d456..22aa587 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -2,17 +2,21 @@ default_language_version:
     python: python3
 repos:
     - repo: https://github.com/pre-commit/pre-commit-hooks
-      rev: v2.2.3
+      rev: v2.3.0
       hooks:
           - id: trailing-whitespace
           - id: end-of-file-fixer
           - id: fix-encoding-pragma
             exclude: ^arrow/_version.py
+          - id: requirements-txt-fixer
+          - id: check-ast
           - id: check-yaml
+          - id: check-case-conflict
+          - id: check-docstring-first
+          - id: check-merge-conflict
           - id: debug-statements
-          - id: requirements-txt-fixer
     - repo: https://github.com/asottile/seed-isort-config
-      rev: v1.9.1
+      rev: v1.9.2
       hooks:
           - id: seed-isort-config
     - repo: https://github.com/pre-commit/mirrors-isort
@@ -20,13 +24,14 @@ repos:
       hooks:
           - id: isort
     - repo: https://github.com/asottile/pyupgrade
-      rev: v1.20.1
+      rev: v1.22.1
       hooks:
           - id: pyupgrade
     - repo: https://github.com/pre-commit/pygrep-hooks
-      rev: v1.4.0
+      rev: v1.4.1
       hooks:
           - id: python-no-eval
+          - id: python-check-blanket-noqa
           - id: rst-backticks
     - repo: https://github.com/python/black
       rev: 19.3b0
diff --git a/HISTORY.md b/CHANGELOG.md
similarity index 95%
rename from HISTORY.md
rename to CHANGELOG.md
index 8cbcacc..edab726 100644
--- a/HISTORY.md
+++ b/CHANGELOG.md
@@ -1,34 +1,34 @@
-## History
+# Changelog
 
-### 0.14.5
+## 0.14.5
 
 - [NEW] Added Afrikaans locale.
 - [CHANGE] Removed deprecated `replace` shift functionality. Users looking to pass plural properties to the `replace` function to shift values should use `shift` instead.
 - [FIX] Fixed bug that occurred when `factory.get()` was passed a locale kwarg.
 
-### 0.14.4
+## 0.14.4
 
 - [FIX] Fixed a regression in 0.14.3 that prevented a tzinfo argument of type string to be passed to the `get()` function. Functionality such as `arrow.get("2019072807", "YYYYMMDDHH", tzinfo="UTC")` should work as normal again.
 - [CHANGE] Moved `backports.functools_lru_cache` dependency from `extra_requires` to `install_requires` for `Python 2.7` installs to fix [#495](https://github.com/crsmithdev/arrow/issues/495).
 
-### 0.14.3
+## 0.14.3
 
 - [NEW] Added full support for Python 3.8.
-- [CHANGE] Added warnings for upcoming factory.get() parsing changes in 0.15.0. Please see https://github.com/crsmithdev/arrow/issues/612 for full details.
+- [CHANGE] Added warnings for upcoming factory.get() parsing changes in 0.15.0. Please see [issue #612](https://github.com/crsmithdev/arrow/issues/612) for full details.
 - [FIX] Extensive refactor and update of documentation.
 - [FIX] factory.get() can now construct from kwargs.
 - [FIX] Added meridians to Spanish Locale.
 
-### 0.14.2
+## 0.14.2
 
 - [CHANGE] Travis CI builds now use tox to lint and run tests.
 - [FIX] Fixed UnicodeDecodeError on certain locales (#600).
 
-### 0.14.1
+## 0.14.1
 
 - [FIX] Fixed "ImportError: No module named 'dateutil'" (#598).
 
-### 0.14.0
+## 0.14.0
 
 - [NEW] Added provisional support for Python 3.8.
 - [CHANGE] Removed support for EOL Python 3.4.
@@ -37,7 +37,7 @@
 - [FIX] Enabled flake8 and black on travis builds.
 - [FIX] Formatted code using black and isort.
 
-### 0.13.2
+## 0.13.2
 
 - [NEW] Add is_between method.
 - [FIX] Improved humanize behaviour for near zero durations (#416).
@@ -45,13 +45,13 @@
 - [FIX] Documentation updates.
 - [FIX] Improvements to German Locale.
 
-### 0.13.1
+## 0.13.1
 
 - [NEW] Add support for Python 3.7.
 - [CHANGE] Remove deprecation decorators for Arrow.range(), Arrow.span_range() and Arrow.interval(), all now return generators, wrap with list() to get old behavior.
 - [FIX] Documentation and docstring updates.
 
-### 0.13.0
+## 0.13.0
 
 - [NEW] Added support for Python 3.6.
 - [CHANGE] Drop support for Python 2.6/3.3.
@@ -68,16 +68,16 @@
 - [FIX] Improve docs for get, now and utcnow methods.
 - [FIX] Correct typo in depreciation warning.
 
-### 0.12.1
+## 0.12.1
 
 - [FIX] Allow universal wheels to be generated and reliably installed.
 - [FIX] Make humanize respect only_distance when granularity argument is also given.
 
-### 0.12.0
+## 0.12.0
 
 - [FIX] Compatibility fix for Python 2.x
 
-### 0.11.0
+## 0.11.0
 
 - [FIX] Fix grammar of ArabicLocale
 - [NEW] Add Nepali Locale
@@ -87,13 +87,13 @@
 - [FIX] Remove pip --user-mirrors flag
 - [NEW] Add Indonesian Locale
 
-### 0.10.0
+## 0.10.0
 
 - [FIX] Fix getattr off by one for quarter
 - [FIX] Fix negative offset for UTC
 - [FIX] Update arrow.py
 
-### 0.9.0
+## 0.9.0
 
 - [NEW] Remove duplicate code
 - [NEW] Support gnu date iso 8601
@@ -108,16 +108,16 @@
 - [NEW] Azerbaijani locale added, locale issue fixed in Turkish.
 - [FIX] Format ParserError's raise message
 
-### 0.8.0
+## 0.8.0
 
 - []
 
-### 0.7.1
+## 0.7.1
 
 - [NEW] Esperanto locale (batisteo)
 
+## 0.7.0
 
-### 0.7.0
 - [FIX] Parse localized strings #228 (swistakm)
 - [FIX] Modify tzinfo parameter in `get` api #221 (bottleimp)
 - [FIX] Fix Czech locale (PrehistoricTeam)
@@ -130,7 +130,7 @@
 - [NEW] Search date in strings (beenje)
 - [NEW] Note that arrow's tokens differ from strptime's. (offby1)
 
-### 0.6.0
+## 0.6.0
 
 - [FIX] Added support for Python 3
 - [FIX] Avoid truncating oversized epoch timestamps. Fixes #216.
@@ -147,8 +147,7 @@
 - [NEW] Add count argument to span method
 - [NEW] Improved docs
 
-
-### 0.5.1 - 0.5.4
+## 0.5.1 - 0.5.4
 
 - [FIX] test the behavior of simplejson instead of calling for_json directly (tonyseek)
 - [FIX] Add Hebrew Locale (doodyparizada)
@@ -156,7 +155,7 @@
 - [FIX] Update setup.py Development Status level (andrewelkins)
 - [FIX] Case insensitive month match (cshowe)
 
-### 0.5.0
+## 0.5.0
 
 - [NEW] struct_time addition. (mhworth)
 - [NEW] Version grep (eirnym)
@@ -170,7 +169,7 @@
 - [FIX] Parse lower-cased 'h' (tamentis)
 - [FIX] Slight modifications to Dutch locale (nvie)
 
-### 0.4.4
+## 0.4.4
 
 - [NEW] Include the docs in the released tarball
 - [NEW] Czech localization Czech localization for Arrow
@@ -188,8 +187,7 @@
 - [FIX] In Norwegian and new Norwegian months and weekdays should not be capitalized
 - [FIX] Fixed discrepancy between specifying 'X' to arrow.get and specifying no template
 
-
-### 0.4.3
+## 0.4.3
 
 - [NEW] Turkish locale (Emre)
 - [NEW] Arabic locale (Mosab Ahmad)
@@ -210,8 +208,7 @@
 - [FIX] Error messages for parse errors are now more descriptive (Maciej Albin)
 - [FIX] The parser now correctly checks for separators in strings (Mschwager)
 
-
-### 0.4.2
+## 0.4.2
 
 - [NEW] Factory ``get`` method now accepts a single ``Arrow`` argument.
 - [NEW] Tokens SSSS, SSSSS and SSSSSS are supported in parsing.
@@ -225,7 +222,7 @@
 - [FIX] ``humanize`` now correctly returns unicode (Shvechikov)
 - [FIX] ``Arrow`` objects now pickle / unpickle correctly (Yoloseem)
 
-### 0.4.1
+## 0.4.1
 
 - [NEW] Table / explanation of formatting & parsing tokens in docs
 - [NEW] Brazilian locale (Augusto2112)
@@ -237,7 +234,7 @@
 - [FIX] Factory ``get`` method should now handle unicode strings correctly (Bwells)
 - [FIX] Midnight and noon should now parse and format correctly (Bwells)
 
-### 0.4.0
+## 0.4.0
 
 - [NEW] Format-free ISO-8601 parsing in factory ``get`` method
 - [NEW] Support for 'week' / 'weeks' in ``span``, ``range``, ``span_range``, ``floor`` and ``ceil``
@@ -252,18 +249,18 @@
 - [CHANGE] Dropped timestamp support in ``range`` and ``span_range`` (never worked correctly)
 - [CHANGE] Dropped parsing of single string as tz string in factory ``get`` method (replaced by ISO-8601)
 
-### 0.3.5
+## 0.3.5
 
 - [NEW] French locale (Cynddl)
 - [NEW] Spanish locale (Slapresta)
 - [FIX] Ranges handle multiple timezones correctly (Ftobia)
 
-### 0.3.4
+## 0.3.4
 
 - [FIX] Humanize no longer sometimes returns the wrong month delta
 - [FIX] ``__format__`` works correctly with no format string
 
-### 0.3.3
+## 0.3.3
 
 - [NEW] Python 2.6 support
 - [NEW] Initial support for locale-based parsing and formatting
@@ -271,15 +268,15 @@
 - [NEW] ``factory`` api method to obtain a factory for a custom type
 - [FIX] Python 3 support and tests completely ironed out
 
-### 0.3.2
+## 0.3.2
 
 - [NEW] Python 3+ support
 
-### 0.3.1
+## 0.3.1
 
 - [FIX] The old ``arrow`` module function handles timestamps correctly as it used to
 
-### 0.3.0
+## 0.3.0
 
 - [NEW] ``Arrow.replace`` method
 - [NEW] Accept timestamps, datetimes and Arrows for datetime inputs, where reasonable
@@ -288,12 +285,12 @@
 - [CHANGE] Plural attribute name semantics altered: single -> absolute, plural -> relative
 - [CHANGE] Plural names no longer supported as properties (e.g. ``arrow.utcnow().years``)
 
-### 0.2.1
+## 0.2.1
 
 - [NEW] Support for localized humanization
 - [NEW] English, Russian, Greek, Korean, Chinese locales
 
-### 0.2.0
+## 0.2.0
 
 - **REWRITE**
 - [NEW] Date parsing
@@ -303,14 +300,14 @@
 - [NEW] ``clone`` method
 - [NEW] ``get``, ``now`` and ``utcnow`` API methods
 
-### 0.1.6
+## 0.1.6
 
 - [NEW] Humanized time deltas
 - [NEW] ``__eq__`` implemented
 - [FIX] Issues with conversions related to daylight savings time resolved
 - [CHANGE] ``__str__`` uses ISO formatting
 
-### 0.1.5
+## 0.1.5
 
 - **Started tracking changes**
 - [NEW] Parsing of ISO-formatted time zone offsets (e.g. '+02:30', '-05:00')
diff --git a/README.rst b/README.rst
index b9a16be..84eaa29 100644
--- a/README.rst
+++ b/README.rst
@@ -32,7 +32,7 @@ What?
 
 Arrow is a Python library that offers a sensible, human-friendly approach to creating, manipulating, formatting and converting dates, times, and timestamps.  It implements and updates the datetime type, plugging gaps in functionality, and provides an intelligent module API that supports many common creation scenarios.  Simply put, it helps you work with dates and times with fewer imports and a lot less code.
 
-Arrow is heavily inspired by `moment.js <https://github.com/timrwood/moment>`_ and `requests <https://github.com/kennethreitz/requests>`_.
+Arrow is heavily inspired by `moment.js <https://github.com/moment/moment>`_ and `requests <https://github.com/psf/requests>`_.
 
 Why?
 ----
@@ -52,7 +52,7 @@ Features
 - Supports Python 2.7, 3.5, 3.6, 3.7 and 3.8
 - Timezone-aware & UTC by default
 - Provides super-simple creation options for many common input scenarios
-- Updated :code:`replace` method with support for relative offsets, including weeks
+- :code:`shift` method with support for relative offsets, including weeks
 - Formats and parses strings automatically
 - Partial support for ISO-8601
 - Timezone conversion
@@ -119,4 +119,4 @@ For full documentation, please visit `arrow.readthedocs.io <https://arrow.readth
 Contributing
 ------------
 
-Contributions are welcome for both code and localization. To get started, find an issue or feature to tackle on `the issue tracker <https://github.com/crsmithdev/arrow/issues>`_ and then fork `the repository <https://github.com/crsmithdev/arrow>`_ on GitHub to begin making changes. If you would like to help with localization, please see `locales.py <https://github.com/crsmithdev/arrow/blob/master/arrow/locales.py>`_ for what locales are currently supported. If you are helping with code, make sure to add tests to ensure that a bug was fixed or the feature works as intended.
+Contributions are welcome for both code and localizations. To get started, find an issue or feature to tackle on `the issue tracker <https://github.com/crsmithdev/arrow/issues>`_ and then fork `this repository <https://github.com/crsmithdev/arrow>`_ on GitHub to begin making changes. If you would like to help with localizations, please see `locales.py <https://github.com/crsmithdev/arrow/blob/master/arrow/locales.py>`_ to see what locales are currently supported. If you are helping with code, make sure to add a few tests to ensure that the bug was fixed or the feature works as intended.
diff --git a/arrow/arrow.py b/arrow/arrow.py
index 08b9b57..0bd5936 100644
--- a/arrow/arrow.py
+++ b/arrow/arrow.py
@@ -9,7 +9,8 @@ from __future__ import absolute_import
 
 import calendar
 import sys
-from datetime import datetime, timedelta, tzinfo
+from datetime import datetime, timedelta
+from datetime import tzinfo as dt_tzinfo
 from math import trunc
 
 from dateutil import tz as dateutil_tz
@@ -59,10 +60,17 @@ class Arrow(object):
     def __init__(
         self, year, month, day, hour=0, minute=0, second=0, microsecond=0, tzinfo=None
     ):
-
-        if util.isstr(tzinfo):
+        if tzinfo is None:
+            tzinfo = dateutil_tz.tzutc()
+        # detect that tzinfo is a pytz object (issue #626)
+        elif (
+            isinstance(tzinfo, dt_tzinfo)
+            and hasattr(tzinfo, "localize")
+            and tzinfo.zone
+        ):
+            tzinfo = parser.TzinfoParser.parse(tzinfo.zone)
+        elif util.isstr(tzinfo):
             tzinfo = parser.TzinfoParser.parse(tzinfo)
-        tzinfo = tzinfo if tzinfo is not None else dateutil_tz.tzutc()
 
         self._datetime = datetime(
             year, month, day, hour, minute, second, microsecond, tzinfo
@@ -84,7 +92,8 @@ class Arrow(object):
 
         """
 
-        tzinfo = tzinfo if tzinfo is not None else dateutil_tz.tzlocal()
+        if tzinfo is None:
+            tzinfo = dateutil_tz.tzlocal()
         dt = datetime.now(tzinfo)
 
         return cls(
@@ -138,7 +147,8 @@ class Arrow(object):
 
         """
 
-        tzinfo = tzinfo if tzinfo is not None else dateutil_tz.tzlocal()
+        if tzinfo is None:
+            tzinfo = dateutil_tz.tzlocal()
         timestamp = cls._get_timestamp_from_input(timestamp)
         dt = datetime.fromtimestamp(timestamp, tzinfo)
 
@@ -219,7 +229,8 @@ class Arrow(object):
         :param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`.  Defaults to UTC.
         """
 
-        tzinfo = tzinfo if tzinfo is not None else dateutil_tz.tzutc()
+        if tzinfo is None:
+            tzinfo = dateutil_tz.tzutc()
 
         return cls(date.year, date.month, date.day, tzinfo=tzinfo)
 
@@ -241,7 +252,8 @@ class Arrow(object):
         """
 
         dt = datetime.strptime(date_str, fmt)
-        tzinfo = tzinfo if tzinfo is not None else dt.tzinfo
+        if tzinfo is None:
+            tzinfo = dt.tzinfo
 
         return cls(
             dt.year,
@@ -677,7 +689,7 @@ class Arrow(object):
 
         """
 
-        if not isinstance(tz, tzinfo):
+        if not isinstance(tz, dt_tzinfo):
             tz = parser.TzinfoParser.parse(tz)
 
         dt = self._datetime.astimezone(tz)
@@ -876,10 +888,16 @@ class Arrow(object):
 
             elif diff < 129600:
                 return locale.describe("day", sign, only_distance=only_distance)
-            elif diff < 2160000:
+            elif diff < 554400:
                 days = sign * int(max(delta / 86400, 2))
                 return locale.describe("days", days, only_distance=only_distance)
 
+            elif diff < 907200:
+                return locale.describe("week", sign, only_distance=only_distance)
+            elif diff < 2419200:
+                weeks = sign * int(max(delta / 604800, 2))
+                return locale.describe("weeks", weeks, only_distance=only_distance)
+
             elif diff < 3888000:
                 return locale.describe("month", sign, only_distance=only_distance)
             elif diff < 29808000:
@@ -907,6 +925,8 @@ class Arrow(object):
                 delta = sign * delta / float(60 * 60)
             elif granularity == "day":
                 delta = sign * delta / float(60 * 60 * 24)
+            elif granularity == "week":
+                delta = sign * delta / float(60 * 60 * 24 * 7)
             elif granularity == "month":
                 delta = sign * delta / float(60 * 60 * 24 * 30.5)
             elif granularity == "year":
@@ -1286,7 +1306,7 @@ class Arrow(object):
 
         if tz_expr is None:
             return dateutil_tz.tzutc()
-        if isinstance(tz_expr, tzinfo):
+        if isinstance(tz_expr, dt_tzinfo):
             return tz_expr
         else:
             try:
diff --git a/arrow/factory.py b/arrow/factory.py
index 8cca6ac..aed4815 100644
--- a/arrow/factory.py
+++ b/arrow/factory.py
@@ -10,7 +10,8 @@ from __future__ import absolute_import
 
 import calendar
 import warnings
-from datetime import date, datetime, tzinfo
+from datetime import date, datetime
+from datetime import tzinfo as dt_tzinfo
 from time import struct_time
 
 from dateutil import tz as dateutil_tz
@@ -163,8 +164,13 @@ class ArrowFactory(object):
 
         # () -> now, @ utc.
         if arg_count == 0:
-            if isinstance(tz, tzinfo):
+            if isstr(tz):
+                tz = parser.TzinfoParser.parse(tz)
                 return self.type.now(tz)
+
+            if isinstance(tz, dt_tzinfo):
+                return self.type.now(tz)
+
             return self.type.utcnow()
 
         if arg_count == 1:
@@ -191,7 +197,7 @@ class ArrowFactory(object):
                 return self.type.fromdate(arg)
 
             # (tzinfo) -> now, @ tzinfo.
-            elif isinstance(arg, tzinfo):
+            elif isinstance(arg, dt_tzinfo):
                 return self.type.now(arg)
 
             # (str) -> parse.
@@ -220,7 +226,7 @@ class ArrowFactory(object):
             if isinstance(arg_1, datetime):
 
                 # (datetime, tzinfo/str) -> fromdatetime replace tzinfo.
-                if isinstance(arg_2, tzinfo) or isstr(arg_2):
+                if isinstance(arg_2, dt_tzinfo) or isstr(arg_2):
                     return self.type.fromdatetime(arg_1, arg_2)
                 else:
                     raise TypeError(
@@ -232,7 +238,7 @@ class ArrowFactory(object):
             elif isinstance(arg_1, date):
 
                 # (date, tzinfo/str) -> fromdate replace tzinfo.
-                if isinstance(arg_2, tzinfo) or isstr(arg_2):
+                if isinstance(arg_2, dt_tzinfo) or isstr(arg_2):
                     return self.type.fromdate(arg_1, tzinfo=arg_2)
                 else:
                     raise TypeError(
@@ -298,7 +304,7 @@ class ArrowFactory(object):
 
         if tz is None:
             tz = dateutil_tz.tzlocal()
-        elif not isinstance(tz, tzinfo):
+        elif not isinstance(tz, dt_tzinfo):
             tz = parser.TzinfoParser.parse(tz)
 
         return self.type.now(tz)
diff --git a/arrow/locales.py b/arrow/locales.py
index 81a7bad..1d4b890 100644
--- a/arrow/locales.py
+++ b/arrow/locales.py
@@ -39,6 +39,8 @@ class Locale(object):
         "hours": "",
         "day": "",
         "days": "",
+        "week": "",
+        "weeks": "",
         "month": "",
         "months": "",
         "year": "",
@@ -208,6 +210,8 @@ class EnglishLocale(Locale):
         "hours": "{0} hours",
         "day": "a day",
         "days": "{0} days",
+        "week": "a week",
+        "weeks": "{0} weeks",
         "month": "a month",
         "months": "{0} months",
         "year": "a year",
@@ -1433,6 +1437,82 @@ class UkrainianLocale(SlavicBaseLocale):
     day_abbreviations = ["", "пн", "вт", "ср", "чт", "пт", "сб", "нд"]
 
 
+class MacedonianLocale(SlavicBaseLocale):
+    names = ["mk", "mk_mk"]
+
+    past = "пред {0}"
+    future = "за {0}"
+
+    timeframes = {
+        "now": "сега",
+        "seconds": "секунди",
+        "minute": "една минута",
+        "minutes": ["{0} минута", "{0} минути", "{0} минути"],
+        "hour": "еден саат",
+        "hours": ["{0} саат", "{0} саати", "{0} саати"],
+        "day": "еден ден",
+        "days": ["{0} ден", "{0} дена", "{0} дена"],
+        "month": "еден месец",
+        "months": ["{0} месец", "{0} месеци", "{0} месеци"],
+        "year": "една година",
+        "years": ["{0} година", "{0} години", "{0} години"],
+    }
+
+    meridians = {"am": "дп", "pm": "пп", "AM": "претпладне", "PM": "попладне"}
+
+    month_names = [
+        "",
+        "Јануари",
+        "Февруари",
+        "Март",
+        "Април",
+        "Мај",
+        "Јуни",
+        "Јули",
+        "Август",
+        "Септември",
+        "Октомври",
+        "Ноември",
+        "Декември",
+    ]
+    month_abbreviations = [
+        "",
+        "Јан.",
+        " Фев.",
+        " Мар.",
+        " Апр.",
+        " Мај",
+        " Јун.",
+        " Јул.",
+        " Авг.",
+        " Септ.",
+        " Окт.",
+        " Ноем.",
+        " Декем.",
+    ]
+
+    day_names = [
+        "",
+        "Понеделник",
+        " Вторник",
+        " Среда",
+        " Четврток",
+        " Петок",
+        " Сабота",
+        " Недела",
+    ]
+    day_abbreviations = [
+        "",
+        "Пон.",
+        " Вт.",
+        " Сре.",
+        " Чет.",
+        " Пет.",
+        " Саб.",
+        " Нед.",
+    ]
+
+
 class _DeutschLocaleCommonMixin(object):
 
     past = "vor {0}"
@@ -2843,82 +2923,6 @@ class FarsiLocale(Locale):
     day_abbreviations = ["", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
 
 
-class MacedonianLocale(Locale):
-    names = ["mk", "mk_mk"]
-
-    past = "пред {0}"
-    future = "за {0}"
-
-    timeframes = {
-        "now": "сега",
-        "seconds": "секунди",
-        "minute": "една минута",
-        "minutes": "{0} минути",
-        "hour": "еден саат",
-        "hours": "{0} саати",
-        "day": "еден ден",
-        "days": "{0} дена",
-        "month": "еден месец",
-        "months": "{0} месеци",
-        "year": "една година",
-        "years": "{0} години",
-    }
-
-    meridians = {"am": "дп", "pm": "пп", "AM": "претпладне", "PM": "попладне"}
-
-    month_names = [
-        "",
-        "Јануари",
-        "Февруари",
-        "Март",
-        "Април",
-        "Мај",
-        "Јуни",
-        "Јули",
-        "Август",
-        "Септември",
-        "Октомври",
-        "Ноември",
-        "Декември",
-    ]
-    month_abbreviations = [
-        "",
-        "Јан.",
-        " Фев.",
-        " Мар.",
-        " Апр.",
-        " Мај",
-        " Јун.",
-        " Јул.",
-        " Авг.",
-        " Септ.",
-        " Окт.",
-        " Ноем.",
-        " Декем.",
-    ]
-
-    day_names = [
-        "",
-        "Понеделник",
-        " Вторник",
-        " Среда",
-        " Четврток",
-        " Петок",
-        " Сабота",
-        " Недела",
-    ]
-    day_abbreviations = [
-        "",
-        "Пон.",
-        " Вт.",
-        " Сре.",
-        " Чет.",
-        " Пет.",
-        " Саб.",
-        " Нед.",
-    ]
-
-
 class HebrewLocale(Locale):
 
     names = ["he", "he_IL"]
diff --git a/arrow/parser.py b/arrow/parser.py
index 7266ff9..f82d3ac 100644
--- a/arrow/parser.py
+++ b/arrow/parser.py
@@ -2,7 +2,7 @@
 from __future__ import absolute_import, unicode_literals
 
 import re
-from datetime import datetime
+from datetime import datetime, timedelta
 
 from dateutil import tz
 
@@ -275,15 +275,28 @@ class DateTimeParser(object):
         elif am_pm == "am" and hour == 12:
             hour = 0
 
-        return datetime(
-            year=parts.get("year", 1),
-            month=parts.get("month", 1),
-            day=parts.get("day", 1),
-            hour=hour,
-            minute=parts.get("minute", 0),
-            second=parts.get("second", 0),
-            microsecond=parts.get("microsecond", 0),
-            tzinfo=parts.get("tzinfo"),
+        # account for rounding up to 1000000
+        microsecond = parts.get("microsecond", 0)
+        if microsecond == 1000000:
+            microsecond = 0
+            second_increment = 1
+        else:
+            second_increment = 0
+
+        increment = timedelta(seconds=second_increment)
+
+        return (
+            datetime(
+                year=parts.get("year", 1),
+                month=parts.get("month", 1),
+                day=parts.get("day", 1),
+                hour=hour,
+                minute=parts.get("minute", 0),
+                second=parts.get("second", 0),
+                microsecond=microsecond,
+                tzinfo=parts.get("tzinfo"),
+            )
+            + increment
         )
 
     def _parse_multiformat(self, string, formats):
diff --git a/requirements.txt b/requirements.txt
index ac54277..f5aa6da 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,9 +2,9 @@ backports.functools_lru_cache==1.5.0
 chai==1.1.2
 nose==1.3.7
 nose-cov==1.6
-pre-commit==1.17.0
-python-dateutil==2.8.0
-pytz==2019.1
-simplejson==3.16.0
-sphinx==1.8.5; python_version == '2.7'
-sphinx==2.1.2; python_version >= '3.5'
+pre-commit==1.18.*
+python-dateutil==2.8.*
+pytz==2019.*
+simplejson==3.16.*
+sphinx==1.8.*; python_version == '2.7'
+sphinx==2.1.*; python_version >= '3.5'


Test patch:
diff --git a/tests/arrow_tests.py b/tests/arrow_tests.py
index cd35786..9c79c7e 100644
--- a/tests/arrow_tests.py
+++ b/tests/arrow_tests.py
@@ -8,6 +8,7 @@ import time
 import warnings
 from datetime import date, datetime, timedelta
 
+import pytz
 import simplejson as json
 from chai import Chai
 from dateutil import tz
@@ -22,12 +23,58 @@ def assertDtEqual(dt1, dt2, within=10):
 
 
 class ArrowInitTests(Chai):
+    def test_init_bad_input(self):
+
+        with self.assertRaises(TypeError):
+            arrow.Arrow(2013)
+
+        with self.assertRaises(TypeError):
+            arrow.Arrow(2013, 2)
+
+        with self.assertRaises(ValueError):
+            arrow.Arrow(2013, 2, 2, 12, 30, 45, 9999999)
+
     def test_init(self):
 
+        result = arrow.Arrow(2013, 2, 2)
+        self.expected = datetime(2013, 2, 2, tzinfo=tz.tzutc())
+        self.assertEqual(result._datetime, self.expected)
+
+        result = arrow.Arrow(2013, 2, 2, 12)
+        self.expected = datetime(2013, 2, 2, 12, tzinfo=tz.tzutc())
+        self.assertEqual(result._datetime, self.expected)
+
+        result = arrow.Arrow(2013, 2, 2, 12, 30)
+        self.expected = datetime(2013, 2, 2, 12, 30, tzinfo=tz.tzutc())
+        self.assertEqual(result._datetime, self.expected)
+
+        result = arrow.Arrow(2013, 2, 2, 12, 30, 45)
+        self.expected = datetime(2013, 2, 2, 12, 30, 45, tzinfo=tz.tzutc())
+        self.assertEqual(result._datetime, self.expected)
+
         result = arrow.Arrow(2013, 2, 2, 12, 30, 45, 999999)
         self.expected = datetime(2013, 2, 2, 12, 30, 45, 999999, tzinfo=tz.tzutc())
+        self.assertEqual(result._datetime, self.expected)
+
+        result = arrow.Arrow(
+            2013, 2, 2, 12, 30, 45, 999999, tzinfo=tz.gettz("Europe/Paris")
+        )
+        self.expected = datetime(
+            2013, 2, 2, 12, 30, 45, 999999, tzinfo=tz.gettz("Europe/Paris")
+        )
+        self.assertEqual(result._datetime, self.expected)
+
+    # regression tests for issue #626
+    def test_init_pytz_timezone(self):
 
+        result = arrow.Arrow(
+            2013, 2, 2, 12, 30, 45, 999999, tzinfo=pytz.timezone("Europe/Paris")
+        )
+        self.expected = datetime(
+            2013, 2, 2, 12, 30, 45, 999999, tzinfo=tz.gettz("Europe/Paris")
+        )
         self.assertEqual(result._datetime, self.expected)
+        assertDtEqual(result._datetime, self.expected, 1)
 
 
 class ArrowFactoryTests(Chai):
@@ -48,9 +95,14 @@ class ArrowFactoryTests(Chai):
         timestamp = time.time()
 
         result = arrow.Arrow.fromtimestamp(timestamp)
-
         assertDtEqual(result._datetime, datetime.now().replace(tzinfo=tz.tzlocal()))
 
+        result = arrow.Arrow.fromtimestamp(timestamp, tzinfo=tz.gettz("Europe/Paris"))
+        assertDtEqual(
+            result._datetime,
+            datetime.fromtimestamp(timestamp, tz.gettz("Europe/Paris")),
+        )
+
     def test_fromdatetime(self):
 
         dt = datetime(2013, 2, 3, 12, 30, 45, 1)
@@ -90,11 +142,18 @@ class ArrowFactoryTests(Chai):
         formatted = datetime(2013, 2, 3, 12, 30, 45).strftime("%Y-%m-%d %H:%M:%S")
 
         result = arrow.Arrow.strptime(formatted, "%Y-%m-%d %H:%M:%S")
-
         self.assertEqual(
             result._datetime, datetime(2013, 2, 3, 12, 30, 45, tzinfo=tz.tzutc())
         )
 
+        result = arrow.Arrow.strptime(
+            formatted, "%Y-%m-%d %H:%M:%S", tzinfo=tz.gettz("Europe/Paris")
+        )
+        self.assertEqual(
+            result._datetime,
+            datetime(2013, 2, 3, 12, 30, 45, tzinfo=tz.gettz("Europe/Paris")),
+        )
+
 
 class ArrowRepresentationTests(Chai):
     def setUp(self):
@@ -1350,6 +1409,8 @@ class ArrowHumanizeTests(Chai):
         self.assertEqual(later105.humanize(self.now, granularity="hour"), "in 27 hours")
         self.assertEqual(self.now.humanize(later105, granularity="day"), "a day ago")
         self.assertEqual(later105.humanize(self.now, granularity="day"), "in a day")
+        self.assertEqual(self.now.humanize(later105, granularity="week"), "0 weeks ago")
+        self.assertEqual(later105.humanize(self.now, granularity="week"), "in 0 weeks")
         self.assertEqual(
             self.now.humanize(later105, granularity="month"), "0 months ago"
         )
@@ -1360,6 +1421,8 @@ class ArrowHumanizeTests(Chai):
         later106 = self.now.shift(seconds=3 * 10 ** 6)
         self.assertEqual(self.now.humanize(later106, granularity="day"), "34 days ago")
         self.assertEqual(later106.humanize(self.now, granularity="day"), "in 34 days")
+        self.assertEqual(self.now.humanize(later106, granularity="week"), "4 weeks ago")
+        self.assertEqual(later106.humanize(self.now, granularity="week"), "in 4 weeks")
         self.assertEqual(
             self.now.humanize(later106, granularity="month"), "a month ago"
         )
@@ -1368,6 +1431,10 @@ class ArrowHumanizeTests(Chai):
         self.assertEqual(later106.humanize(self.now, granularity="year"), "in 0 years")
 
         later506 = self.now.shift(seconds=50 * 10 ** 6)
+        self.assertEqual(
+            self.now.humanize(later506, granularity="week"), "82 weeks ago"
+        )
+        self.assertEqual(later506.humanize(self.now, granularity="week"), "in 82 weeks")
         self.assertEqual(
             self.now.humanize(later506, granularity="month"), "18 months ago"
         )
@@ -1477,6 +1544,26 @@ class ArrowHumanizeTests(Chai):
         later = self.now.shift(days=4)
         self.assertEqual(later.humanize(), "in 4 days")
 
+    def test_week(self):
+
+        later = self.now.shift(weeks=1)
+
+        self.assertEqual(self.now.humanize(later), "a week ago")
+        self.assertEqual(later.humanize(self.now), "in a week")
+
+        self.assertEqual(self.now.humanize(later, only_distance=True), "a week")
+        self.assertEqual(later.humanize(self.now, only_distance=True), "a week")
+
+    def test_weeks(self):
+
+        later = self.now.shift(weeks=2)
+
+        self.assertEqual(self.now.humanize(later), "2 weeks ago")
+        self.assertEqual(later.humanize(self.now), "in 2 weeks")
+
+        self.assertEqual(self.now.humanize(later, only_distance=True), "2 weeks")
+        self.assertEqual(later.humanize(self.now, only_distance=True), "2 weeks")
+
     def test_month(self):
 
         later = self.now.shift(months=1)
diff --git a/tests/factory_tests.py b/tests/factory_tests.py
index acd4c36..3d75ffd 100644
--- a/tests/factory_tests.py
+++ b/tests/factory_tests.py
@@ -30,7 +30,7 @@ class GetTests(Chai):
 
         self.assertEqual(no_arg, one_arg)
 
-    def test_one_arg_non(self):
+    def test_one_arg_none(self):
 
         assertDtEqual(
             self.factory.get(None), datetime.utcnow().replace(tzinfo=tz.tzutc())
@@ -103,6 +103,16 @@ class GetTests(Chai):
 
         assertDtEqual(self.factory.get(tzinfo=tz.gettz("US/Pacific")), self.expected)
 
+    def test_kwarg_tzinfo_string(self):
+
+        self.expected = (
+            datetime.utcnow()
+            .replace(tzinfo=tz.tzutc())
+            .astimezone(tz.gettz("US/Pacific"))
+        )
+
+        assertDtEqual(self.factory.get(tzinfo="US/Pacific"), self.expected)
+
     def test_one_arg_iso_str(self):
 
         dt = datetime.utcnow()
diff --git a/tests/locales_tests.py b/tests/locales_tests.py
index c1a8912..0abf208 100644
--- a/tests/locales_tests.py
+++ b/tests/locales_tests.py
@@ -323,6 +323,35 @@ class BulgarianLocaleTests(Chai):
         self.assertEqual(locale._format_timeframe("minutes", 25), "25 минути")
 
 
+class MacedonianLocaleTests(Chai):
+    def test_plurals_mk(self):
+
+        locale = locales.MacedonianLocale()
+
+        # time
+        self.assertEqual(locale._format_relative("сега", "now", 0), "сега")
+
+        # Hours
+        self.assertEqual(locale._format_timeframe("hours", 0), "0 саати")
+        self.assertEqual(locale._format_timeframe("hours", 1), "1 саат")
+        self.assertEqual(locale._format_timeframe("hours", 2), "2 саати")
+        self.assertEqual(locale._format_timeframe("hours", 4), "4 саати")
+        self.assertEqual(locale._format_timeframe("hours", 5), "5 саати")
+        self.assertEqual(locale._format_timeframe("hours", 21), "21 саат")
+        self.assertEqual(locale._format_timeframe("hours", 22), "22 саати")
+        self.assertEqual(locale._format_timeframe("hours", 25), "25 саати")
+
+        # Minutes
+        self.assertEqual(locale._format_timeframe("minutes", 0), "0 минути")
+        self.assertEqual(locale._format_timeframe("minutes", 1), "1 минута")
+        self.assertEqual(locale._format_timeframe("minutes", 2), "2 минути")
+        self.assertEqual(locale._format_timeframe("minutes", 4), "4 минути")
+        self.assertEqual(locale._format_timeframe("minutes", 5), "5 минути")
+        self.assertEqual(locale._format_timeframe("minutes", 21), "21 минута")
+        self.assertEqual(locale._format_timeframe("minutes", 22), "22 минути")
+        self.assertEqual(locale._format_timeframe("minutes", 25), "25 минути")
+
+
 class HebrewLocaleTests(Chai):
     def test_couple_of_timeframe(self):
         locale = locales.HebrewLocale()
diff --git a/tests/parser_tests.py b/tests/parser_tests.py
index 0ecd13a..095fa50 100644
--- a/tests/parser_tests.py
+++ b/tests/parser_tests.py
@@ -315,6 +315,29 @@ class DateTimeParserParseTests(Chai):
         self.assertEqual(self.parser.parse(string, format), self.expected)
         self.assertEqual(self.parser.parse_iso(string), self.expected)
 
+        # overflow (zero out the subseconds and increment the seconds)
+        # regression tests for issue #636
+        self.expected = datetime(2013, 1, 1, 12, 30, 46)
+        string = "2013-01-01 12:30:45.9999995"
+        self.assertEqual(self.parser.parse(string, format), self.expected)
+        self.assertEqual(self.parser.parse_iso(string), self.expected)
+
+        self.expected = datetime(2013, 1, 1, 12, 31, 0)
+        string = "2013-01-01 12:30:59.9999999"
+        self.assertEqual(self.parser.parse(string, format), self.expected)
+        self.assertEqual(self.parser.parse_iso(string), self.expected)
+
+        self.expected = datetime(2013, 1, 2, 0, 0, 0)
+        string = "2013-01-01 23:59:59.9999999"
+        self.assertEqual(self.parser.parse(string, format), self.expected)
+        self.assertEqual(self.parser.parse_iso(string), self.expected)
+
+        # 6 digits should remain unrounded
+        self.expected = datetime(2013, 1, 1, 12, 30, 45, 999999)
+        string = "2013-01-01 12:30:45.999999"
+        self.assertEqual(self.parser.parse(string, format), self.expected)
+        self.assertEqual(self.parser.parse_iso(string), self.expected)
+
     def test_map_lookup_keyerror(self):
 
         with self.assertRaises(parser.ParserError):