Hello community,

here is the log from the commit of package python-arrow for openSUSE:Factory 
checked in at 2019-11-22 10:22:06
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-arrow (Old)
 and      /work/SRC/openSUSE:Factory/.python-arrow.new.26869 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-arrow"

Fri Nov 22 10:22:06 2019 rev:11 rq:747337 version:0.15.4

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-arrow/python-arrow.changes        
2019-09-23 12:18:59.809791185 +0200
+++ /work/SRC/openSUSE:Factory/.python-arrow.new.26869/python-arrow.changes     
2019-11-22 10:22:07.437328108 +0100
@@ -1,0 +2,29 @@
+Sat Nov  9 14:44:41 UTC 2019 - Arun Persaud <a...@gmx.de>
+
+- update to version 0.15.4:
+  * [FIX] Fixed an issue that caused package installs to fail on Conda
+    Forge.
+
+- changes from version 0.15.3:
+  * [NEW] factory.get() can now create arrow objects from a ISO
+    calendar tuple, for example:
+        >>> arrow.get((2013, 18, 7))
+       <Arrow [2013-05-05T00:00:00+00:00]>
+  * [NEW] Added a new token x to allow parsing of integer timestamps
+    with milliseconds and microseconds.
+  * [NEW] Formatting now supports escaping of characters using the
+    same syntax as parsing, for example:
+        >>> arw = arrow.now()
+        >>> fmt = "YYYY-MM-DD h [h] m"
+        >>> arw.format(fmt)
+        '2019-11-02 3 h 32'
+  * [NEW] Added humanize week granularity translations for Chinese,
+    Spanish and Vietnamese.
+  * [CHANGE] Added ParserError to module exports.
+  * [FIX] Added support for midnight at end of day. See #703 for
+    details.
+  * [INTERNAL] Created Travis build for macOS.
+  * [INTERNAL] Test parsing and formatting against full timezone
+    database.
+
+-------------------------------------------------------------------

Old:
----
  arrow-0.15.2.tar.gz

New:
----
  arrow-0.15.4.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-arrow.spec ++++++
--- /var/tmp/diff_new_pack.4Y77st/_old  2019-11-22 10:22:07.897328036 +0100
+++ /var/tmp/diff_new_pack.4Y77st/_new  2019-11-22 10:22:07.897328036 +0100
@@ -19,11 +19,10 @@
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %bcond_without python2
 Name:           python-arrow
-Version:        0.15.2
+Version:        0.15.4
 Release:        0
 Summary:        Better dates and times for Python
 License:        Apache-2.0
-Group:          Development/Languages/Python
 URL:            https://github.com/crsmithdev/arrow
 Source:         
https://files.pythonhosted.org/packages/source/a/arrow/arrow-%{version}.tar.gz
 BuildRequires:  %{python_module chai}

++++++ arrow-0.15.2.tar.gz -> arrow-0.15.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arrow-0.15.2/CHANGELOG.rst 
new/arrow-0.15.4/CHANGELOG.rst
--- old/arrow-0.15.2/CHANGELOG.rst      2019-09-14 12:22:12.000000000 +0200
+++ new/arrow-0.15.4/CHANGELOG.rst      2019-11-03 03:54:39.000000000 +0100
@@ -1,6 +1,37 @@
 Changelog
 =========
 
+0.15.4 (2019-11-02)
+-------------------
+
+- [FIX] Fixed an issue that caused package installs to fail on Conda Forge.
+
+0.15.3 (2019-11-02)
+-------------------
+
+- [NEW] ``factory.get()`` can now create arrow objects from a ISO calendar 
tuple, for example:
+
+.. code-block:: python
+
+    >>> arrow.get((2013, 18, 7))
+    <Arrow [2013-05-05T00:00:00+00:00]>
+
+- [NEW] Added a new token ``x`` to allow parsing of integer timestamps with 
milliseconds and microseconds.
+- [NEW] Formatting now supports escaping of characters using the same syntax 
as parsing, for example:
+
+.. code-block:: python
+
+    >>> arw = arrow.now()
+    >>> fmt = "YYYY-MM-DD h [h] m"
+    >>> arw.format(fmt)
+    '2019-11-02 3 h 32'
+
+- [NEW] Added ``humanize`` week granularity translations for Chinese, Spanish 
and Vietnamese.
+- [CHANGE] Added ``ParserError`` to module exports.
+- [FIX] Added support for midnight at end of day. See `#703 
<https://github.com/crsmithdev/arrow/issues/703>`_ for details.
+- [INTERNAL] Created Travis build for macOS.
+- [INTERNAL] Test parsing and formatting against full timezone database.
+
 0.15.2 (2019-09-14)
 -------------------
 
@@ -239,7 +270,7 @@
 
 - [NEW] struct_time addition. (mhworth)
 - [NEW] Version grep (eirnym)
-- [NEW] Default to ISO-8601 format (emonty)
+- [NEW] Default to ISO 8601 format (emonty)
 - [NEW] Raise TypeError on comparison (sniekamp)
 - [NEW] Adding Macedonian(mk) locale (krisfremen)
 - [FIX] Fix for ISO seconds and fractional seconds (sdispater) (andrewelkins)
@@ -321,7 +352,7 @@
 0.4.0
 -----
 
-- [NEW] Format-free ISO-8601 parsing in factory ``get`` method
+- [NEW] Format-free ISO 8601 parsing in factory ``get`` method
 - [NEW] Support for 'week' / 'weeks' in ``span``, ``range``, ``span_range``, 
``floor`` and ``ceil``
 - [NEW] Support for 'weeks' in ``replace``
 - [NEW] Norwegian locale (Martinp)
@@ -332,7 +363,7 @@
 - [FIX] Corrected plurals of Ukrainian and Russian nouns (Catchagain)
 - [CHANGE] Old 0.1 ``arrow`` module method removed
 - [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)
+- [CHANGE] Dropped parsing of single string as tz string in factory ``get`` 
method (replaced by ISO 8601)
 
 0.3.5
 -----
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arrow-0.15.2/LICENSE new/arrow-0.15.4/LICENSE
--- old/arrow-0.15.2/LICENSE    2018-03-07 23:09:56.000000000 +0100
+++ new/arrow-0.15.4/LICENSE    2019-10-01 01:09:21.000000000 +0200
@@ -1,4 +1,192 @@
-Copyright 2013 Chris Smith
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2019 Chris Smith
 
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arrow-0.15.2/PKG-INFO new/arrow-0.15.4/PKG-INFO
--- old/arrow-0.15.2/PKG-INFO   2019-09-14 12:27:48.000000000 +0200
+++ new/arrow-0.15.4/PKG-INFO   2019-11-03 03:55:05.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: arrow
-Version: 0.15.2
+Version: 0.15.4
 Summary: Better dates & times for Python
 Home-page: https://arrow.readthedocs.io
 Author: Chris Smith
@@ -52,7 +52,7 @@
         - Too many types: date, time, datetime, tzinfo, timedelta, 
relativedelta, etc.
         - Timezones and timestamp conversions are verbose and unpleasant
         - Timezone naivety is the norm
-        - Gaps in functionality: ISO-8601 parsing, timespans, humanization
+        - Gaps in functionality: ISO 8601 parsing, timespans, humanization
         
         Features
         --------
@@ -63,7 +63,7 @@
         - Provides super-simple creation options for many common input 
scenarios
         - :code:`shift` method with support for relative offsets, including 
weeks
         - Formats and parses strings automatically
-        - Wide support for ISO-8601
+        - Wide support for ISO 8601
         - Timezone conversion
         - Timestamp available as a property
         - Generates time spans, ranges, floors and ceilings for time frames 
ranging from microsecond to year
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arrow-0.15.2/README.rst new/arrow-0.15.4/README.rst
--- old/arrow-0.15.2/README.rst 2019-09-08 17:46:21.000000000 +0200
+++ new/arrow-0.15.4/README.rst 2019-10-12 21:52:50.000000000 +0200
@@ -41,7 +41,7 @@
 - Too many types: date, time, datetime, tzinfo, timedelta, relativedelta, etc.
 - Timezones and timestamp conversions are verbose and unpleasant
 - Timezone naivety is the norm
-- Gaps in functionality: ISO-8601 parsing, timespans, humanization
+- Gaps in functionality: ISO 8601 parsing, timespans, humanization
 
 Features
 --------
@@ -52,7 +52,7 @@
 - Provides super-simple creation options for many common input scenarios
 - :code:`shift` method with support for relative offsets, including weeks
 - Formats and parses strings automatically
-- Wide support for ISO-8601
+- Wide support for ISO 8601
 - Timezone conversion
 - Timestamp available as a property
 - Generates time spans, ranges, floors and ceilings for time frames ranging 
from microsecond to year
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arrow-0.15.2/arrow/__init__.py 
new/arrow-0.15.4/arrow/__init__.py
--- old/arrow-0.15.2/arrow/__init__.py  2019-08-04 23:57:00.000000000 +0200
+++ new/arrow-0.15.4/arrow/__init__.py  2019-11-02 15:48:09.000000000 +0100
@@ -3,3 +3,4 @@
 from .api import get, now, utcnow
 from .arrow import Arrow
 from .factory import ArrowFactory
+from .parser import ParserError
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arrow-0.15.2/arrow/_version.py 
new/arrow-0.15.4/arrow/_version.py
--- old/arrow-0.15.2/arrow/_version.py  2019-09-14 12:22:12.000000000 +0200
+++ new/arrow-0.15.4/arrow/_version.py  2019-11-03 03:54:39.000000000 +0100
@@ -1 +1 @@
-__version__ = "0.15.2"
+__version__ = "0.15.4"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arrow-0.15.2/arrow/arrow.py 
new/arrow-0.15.4/arrow/arrow.py
--- old/arrow-0.15.2/arrow/arrow.py     2019-09-11 16:30:55.000000000 +0200
+++ new/arrow-0.15.4/arrow/arrow.py     2019-10-12 21:52:50.000000000 +0200
@@ -40,7 +40,7 @@
 
         - A ``tzinfo`` object.
         - A ``str`` describing a timezone, similar to 'US/Pacific', or 
'Europe/Berlin'.
-        - A ``str`` in ISO-8601 style, as in '+07:00'.
+        - A ``str`` in ISO 8601 style, as in '+07:00'.
         - A ``str``, one of the following:  'local', 'utc', 'UTC'.
 
     Usage::
@@ -150,8 +150,13 @@
 
         if tzinfo is None:
             tzinfo = dateutil_tz.tzlocal()
-        timestamp = cls._get_timestamp_from_input(timestamp)
-        dt = datetime.fromtimestamp(timestamp, tzinfo)
+
+        if not util.is_timestamp(timestamp):
+            raise ValueError(
+                "The provided timestamp '{}' is invalid.".format(timestamp)
+            )
+
+        dt = datetime.fromtimestamp(float(timestamp), tzinfo)
 
         return cls(
             dt.year,
@@ -172,8 +177,12 @@
 
         """
 
-        timestamp = cls._get_timestamp_from_input(timestamp)
-        dt = datetime.utcfromtimestamp(timestamp)
+        if not util.is_timestamp(timestamp):
+            raise ValueError(
+                "The provided timestamp '{}' is invalid.".format(timestamp)
+            )
+
+        dt = datetime.utcfromtimestamp(float(timestamp))
 
         return cls(
             dt.year,
@@ -414,7 +423,7 @@
 
             - A ``tzinfo`` object.
             - A ``str`` describing a timezone, similar to 'US/Pacific', or 
'Europe/Berlin'.
-            - A ``str`` in ISO-8601 style, as in '+07:00'.
+            - A ``str`` in ISO 8601 style, as in '+07:00'.
             - A ``str``, one of the following:  'local', 'utc', 'UTC'.
 
         Usage:
@@ -1382,14 +1391,6 @@
                 return end, sys.maxsize
             return end, limit
 
-    @staticmethod
-    def _get_timestamp_from_input(timestamp):
-
-        try:
-            return float(timestamp)
-        except Exception:
-            raise ValueError("cannot parse '{}' as a 
timestamp".format(timestamp))
-
 
 Arrow.min = Arrow.fromdatetime(datetime.min)
 Arrow.max = Arrow.fromdatetime(datetime.max)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arrow-0.15.2/arrow/constants.py 
new/arrow-0.15.4/arrow/constants.py
--- old/arrow-0.15.2/arrow/constants.py 1970-01-01 01:00:00.000000000 +0100
+++ new/arrow-0.15.4/arrow/constants.py 2019-10-07 18:14:07.000000000 +0200
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+
+# Output of time.mktime(datetime.max.timetuple()) on macOS
+# This value must be hardcoded for compatibility with Windows
+# Platform-independent max timestamps are hard to form
+# https://stackoverflow.com/q/46133223
+MAX_TIMESTAMP = 253402318799.0
+MAX_TIMESTAMP_MS = MAX_TIMESTAMP * 1000
+MAX_TIMESTAMP_US = MAX_TIMESTAMP * 1000000
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arrow-0.15.2/arrow/factory.py 
new/arrow-0.15.4/arrow/factory.py
--- old/arrow-0.15.2/arrow/factory.py   2019-09-13 01:21:06.000000000 +0200
+++ new/arrow-0.15.4/arrow/factory.py   2019-10-12 21:52:50.000000000 +0200
@@ -17,7 +17,7 @@
 
 from arrow import parser
 from arrow.arrow import Arrow
-from arrow.util import is_timestamp, isstr
+from arrow.util import is_timestamp, iso_to_gregorian, isstr
 
 
 class ArrowFactory(object):
@@ -69,12 +69,12 @@
             >>> arrow.get(1367992474)
             <Arrow [2013-05-08T05:54:34+00:00]>
 
-        **One** ISO-8601-formatted ``str``, to parse it::
+        **One** ISO 8601-formatted ``str``, to parse it::
 
             >>> arrow.get('2013-09-29T01:26:43.830580')
             <Arrow [2013-09-29T01:26:43.830580+00:00]>
 
-        **One** ISO-8601-formatted ``str``, in basic format, to parse it::
+        **One** ISO 8601-formatted ``str``, in basic format, to parse it::
 
             >>> arrow.get('20160413T133656.456289')
             <Arrow [2016-04-13T13:36:56.456289+00:00]>
@@ -99,6 +99,16 @@
             >>> arrow.get(date(2013, 5, 5))
             <Arrow [2013-05-05T00:00:00+00:00]>
 
+        **One** time.struct time::
+
+            >>> arrow.get(gmtime(0))
+            <Arrow [1970-01-01T00:00:00+00:00]>
+
+        **One** iso calendar ``tuple``, to get that week date in UTC::
+
+            >>> arrow.get((2013, 18, 7))
+            <Arrow [2013-05-05T00:00:00+00:00]>
+
         **Two** arguments, a naive or aware ``datetime``, and a replacement
         :ref:`timezone expression <tz-expr>`::
 
@@ -126,11 +136,6 @@
             >>> arrow.get(2013, 5, 5, 12, 30, 45)
             <Arrow [2013-05-05T12:30:45+00:00]>
 
-        **One** time.struct time::
-
-            >>> arrow.get(gmtime(0))
-            <Arrow [1970-01-01T00:00:00+00:00]>
-
         """
 
         arg_count = len(args)
@@ -164,19 +169,19 @@
                 return self.type.utcnow()
 
             # try (int, float) -> utc, from timestamp.
-            if is_timestamp(arg):
+            elif not isstr(arg) and is_timestamp(arg):
                 return self.type.utcfromtimestamp(arg)
 
             # (Arrow) -> from the object's datetime.
-            if isinstance(arg, Arrow):
+            elif isinstance(arg, Arrow):
                 return self.type.fromdatetime(arg.datetime)
 
             # (datetime) -> from datetime.
-            if isinstance(arg, datetime):
+            elif isinstance(arg, datetime):
                 return self.type.fromdatetime(arg)
 
             # (date) -> from date.
-            if isinstance(arg, date):
+            elif isinstance(arg, date):
                 return self.type.fromdate(arg)
 
             # (tzinfo) -> now, @ tzinfo.
@@ -192,9 +197,14 @@
             elif isinstance(arg, struct_time):
                 return self.type.utcfromtimestamp(calendar.timegm(arg))
 
+            # (iso calendar) -> convert then from date
+            elif isinstance(arg, tuple) and len(arg) == 3:
+                dt = iso_to_gregorian(*arg)
+                return self.type.fromdate(dt)
+
             else:
                 raise TypeError(
-                    "Can't parse single argument type of 
'{}'".format(type(arg))
+                    "Can't parse single argument of type 
'{}'".format(type(arg))
                 )
 
         elif arg_count == 2:
@@ -232,7 +242,7 @@
 
             else:
                 raise TypeError(
-                    "Can't parse two arguments of types '{}', '{}'".format(
+                    "Can't parse two arguments of types '{}' and '{}'".format(
                         type(arg_1), type(arg_2)
                     )
                 )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arrow-0.15.2/arrow/formatter.py 
new/arrow-0.15.4/arrow/formatter.py
--- old/arrow-0.15.2/arrow/formatter.py 2019-08-04 23:57:00.000000000 +0200
+++ new/arrow-0.15.4/arrow/formatter.py 2019-11-02 15:48:09.000000000 +0100
@@ -11,8 +11,12 @@
 
 class DateTimeFormatter(object):
 
+    # This pattern matches characters enclosed in square brackes are matched as
+    # an atomic group. For more info on atomic groups and how to they are
+    # emulated in Python's re library, see 
https://stackoverflow.com/a/13577411/2701578
+    # TODO: test against full timezone DB
     _FORMAT_RE = re.compile(
-        
r"(YYY?Y?|MM?M?M?|Do|DD?D?D?|d?dd?d?|HH?|hh?|mm?|ss?|SS?S?S?S?S?|ZZ?Z?|a|A|X)"
+        
r"(\[(?:(?=(?P<literal>[^]]))(?P=literal))*\]|YYY?Y?|MM?M?M?|Do|DD?D?D?|d?dd?d?|HH?|hh?|mm?|ss?|SS?S?S?S?S?|ZZ?Z?|a|A|X)"
     )
 
     def __init__(self, locale="en_us"):
@@ -25,6 +29,9 @@
 
     def _format_token(self, dt, token):
 
+        if token and token.startswith("[") and token.endswith("]"):
+            return token[1:-1]
+
         if token == "YYYY":
             return self.locale.year_full(dt.year)
         if token == "YY":
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arrow-0.15.2/arrow/locales.py 
new/arrow-0.15.4/arrow/locales.py
--- old/arrow-0.15.2/arrow/locales.py   2019-09-14 11:27:45.000000000 +0200
+++ new/arrow-0.15.4/arrow/locales.py   2019-10-23 22:44:09.000000000 +0200
@@ -374,6 +374,8 @@
         "hours": "{0} horas",
         "day": "un día",
         "days": "{0} días",
+        "week": "una semana",
+        "weeks": "{0} semanas",
         "month": "un mes",
         "months": "{0} meses",
         "year": "un año",
@@ -803,6 +805,8 @@
         "hours": "{0}小时",
         "day": "1天",
         "days": "{0}天",
+        "week": "一周",
+        "weeks": "{0}周",
         "month": "1个月",
         "months": "{0}个月",
         "year": "1年",
@@ -1991,6 +1995,8 @@
         "hours": "{0} giờ",
         "day": "một ngày",
         "days": "{0} ngày",
+        "week": "một tuần",
+        "weeks": "{0} tuần",
         "month": "một tháng",
         "months": "{0} tháng",
         "year": "một năm",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arrow-0.15.2/arrow/parser.py 
new/arrow-0.15.4/arrow/parser.py
--- old/arrow-0.15.2/arrow/parser.py    2019-09-11 16:30:55.000000000 +0200
+++ new/arrow-0.15.4/arrow/parser.py    2019-11-02 15:48:09.000000000 +0100
@@ -7,6 +7,7 @@
 from dateutil import tz
 
 from arrow import locales
+from arrow.constants import MAX_TIMESTAMP, MAX_TIMESTAMP_MS, MAX_TIMESTAMP_US
 
 try:
     from functools import lru_cache
@@ -30,7 +31,7 @@
 class DateTimeParser(object):
 
     _FORMAT_RE = re.compile(
-        r"(YYY?Y?|MM?M?M?|Do|DD?D?D?|d?d?d?d|HH?|hh?|mm?|ss?|S+|ZZ?Z?|a|A|X)"
+        r"(YYY?Y?|MM?M?M?|Do|DD?D?D?|d?d?d?d|HH?|hh?|mm?|ss?|S+|ZZ?Z?|a|A|x|X)"
     )
     _ESCAPE_RE = re.compile(r"\[[^\[\]]*\]")
 
@@ -45,7 +46,8 @@
     _TZ_NAME_RE = re.compile(r"\w[\w+\-/]+")
     # NOTE: timestamps cannot be parsed from natural language strings (by 
removing the ^...$) because it will
     # break cases like "15 Jul 2000" and a format list (see issue #447)
-    _TIMESTAMP_RE = re.compile(r"^-?\d+\.?\d+$")
+    _TIMESTAMP_RE = re.compile(r"^\-?\d+\.?\d+$")
+    _TIMESTAMP_EXPANDED_RE = re.compile(r"^\-?\d+$")
     _TIME_RE = 
re.compile(r"^(\d{2})(?:\:?(\d{2}))?(?:\:?(\d{2}))?(?:([\.\,])(\d+))?$")
 
     _BASE_INPUT_RE_MAP = {
@@ -66,6 +68,7 @@
         "ss": _TWO_DIGIT_RE,
         "s": _ONE_OR_TWO_DIGIT_RE,
         "X": _TIMESTAMP_RE,
+        "x": _TIMESTAMP_EXPANDED_RE,
         "ZZZ": _TZ_NAME_RE,
         "ZZ": _TZ_ZZ_RE,
         "Z": _TZ_Z_RE,
@@ -107,7 +110,7 @@
                 self._generate_pattern_re
             )
 
-    # TODO: since we support more than ISO-8601, we should rename this function
+    # TODO: since we support more than ISO 8601, we should rename this function
     # IDEA: break into multiple functions
     def parse_iso(self, datetime_string):
         # TODO: add a flag to normalize whitespace (useful in logs, ref issue 
#421)
@@ -125,7 +128,7 @@
         has_time = has_space_divider or has_t_divider
         has_tz = False
 
-        # date formats (ISO-8601 and others) to test against
+        # date formats (ISO 8601 and others) to test against
         # NOTE: YYYYMM is omitted to avoid confusion with YYMMDD (no longer 
part of ISO 8601, but is still often used)
         formats = [
             "YYYY-MM-DD",
@@ -162,9 +165,13 @@
                     "Invalid time component provided. Please specify a format 
or provide a valid time component in the basic or extended ISO 8601 time 
format."
                 )
 
-            hours, minutes, seconds, subseconds_sep, subseconds = (
-                time_components.groups()
-            )
+            (
+                hours,
+                minutes,
+                seconds,
+                subseconds_sep,
+                subseconds,
+            ) = time_components.groups()
 
             has_tz = len(time_parts) == 2
             has_minutes = minutes is not None
@@ -347,6 +354,9 @@
         elif token == "X":
             parts["timestamp"] = float(value)
 
+        elif token == "x":
+            parts["expanded_timestamp"] = int(value)
+
         elif token in ["ZZZ", "ZZ", "Z"]:
             parts["tzinfo"] = TzinfoParser.parse(value)
 
@@ -364,6 +374,24 @@
         if timestamp is not None:
             return datetime.fromtimestamp(timestamp, tz=tz.tzutc())
 
+        expanded_timestamp = parts.get("expanded_timestamp")
+
+        if expanded_timestamp is not None:
+
+            if expanded_timestamp > MAX_TIMESTAMP:
+                if expanded_timestamp < MAX_TIMESTAMP_MS:
+                    expanded_timestamp /= 1000.0
+                elif expanded_timestamp < MAX_TIMESTAMP_US:
+                    expanded_timestamp /= 1000000.0
+                else:
+                    raise ValueError(
+                        "The specified timestamp '{}' is too large.".format(
+                            expanded_timestamp
+                        )
+                    )
+
+            return datetime.fromtimestamp(expanded_timestamp, tz=tz.tzutc())
+
         day_of_year = parts.get("day_of_year")
 
         if day_of_year is not None:
@@ -399,6 +427,21 @@
         elif am_pm == "am" and hour == 12:
             hour = 0
 
+        # Support for midnight at the end of day
+        if hour == 24:
+            if parts.get("minute", 0) != 0:
+                raise ParserError("Midnight at the end of day must not contain 
minutes")
+            if parts.get("second", 0) != 0:
+                raise ParserError("Midnight at the end of day must not contain 
seconds")
+            if parts.get("microsecond", 0) != 0:
+                raise ParserError(
+                    "Midnight at the end of day must not contain microseconds"
+                )
+            hour = 0
+            day_increment = 1
+        else:
+            day_increment = 0
+
         # account for rounding up to 1000000
         microsecond = parts.get("microsecond", 0)
         if microsecond == 1000000:
@@ -407,7 +450,7 @@
         else:
             second_increment = 0
 
-        increment = timedelta(seconds=second_increment)
+        increment = timedelta(days=day_increment, seconds=second_increment)
 
         return (
             datetime(
@@ -450,7 +493,6 @@
 
 
 class TzinfoParser(object):
-    # TODO: test against full timezone DB
     _TZINFO_RE = re.compile(r"^([\+\-])?(\d{2})(?:\:?(\d{2}))?$")
 
     @classmethod
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arrow-0.15.2/arrow/util.py 
new/arrow-0.15.4/arrow/util.py
--- old/arrow-0.15.2/arrow/util.py      2019-09-13 01:21:06.000000000 +0200
+++ new/arrow-0.15.4/arrow/util.py      2019-10-07 18:03:04.000000000 +0200
@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 from __future__ import absolute_import
 
-from datetime import datetime
+import datetime
 
 
 def total_seconds(td):  # pragma: no cover
@@ -9,15 +9,39 @@
 
 
 def is_timestamp(value):
+    """Check if value is a valid timestamp."""
     if isinstance(value, bool):
         return False
+    if not (
+        isinstance(value, int) or isinstance(value, float) or 
isinstance(value, str)
+    ):
+        return False
     try:
-        datetime.fromtimestamp(value)
+        float(value)
         return True
-    except TypeError:
+    except ValueError:
         return False
 
 
+# Credit to https://stackoverflow.com/a/1700069
+def iso_to_gregorian(iso_year, iso_week, iso_day):
+    """Converts an ISO week date tuple into a datetime object."""
+
+    if not 1 <= iso_week <= 53:
+        raise ValueError("ISO Calendar week value must be between 1-53.")
+
+    if not 1 <= iso_day <= 7:
+        raise ValueError("ISO Calendar day value must be between 1-7")
+
+    # The first week of the year always contains 4 Jan.
+    fourth_jan = datetime.date(iso_year, 1, 4)
+    delta = datetime.timedelta(fourth_jan.isoweekday() - 1)
+    year_start = fourth_jan - delta
+    gregorian = year_start + datetime.timedelta(days=iso_day - 1, 
weeks=iso_week - 1)
+
+    return gregorian
+
+
 # Python 2.7 / 3.0+ definitions for isstr function.
 
 try:  # pragma: no cover
@@ -33,4 +57,4 @@
         return isinstance(s, str)
 
 
-__all__ = ["total_seconds", "is_timestamp", "isstr"]
+__all__ = ["total_seconds", "is_timestamp", "isstr", "iso_to_gregorian"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arrow-0.15.2/arrow.egg-info/PKG-INFO 
new/arrow-0.15.4/arrow.egg-info/PKG-INFO
--- old/arrow-0.15.2/arrow.egg-info/PKG-INFO    2019-09-14 12:27:48.000000000 
+0200
+++ new/arrow-0.15.4/arrow.egg-info/PKG-INFO    2019-11-03 03:55:04.000000000 
+0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: arrow
-Version: 0.15.2
+Version: 0.15.4
 Summary: Better dates & times for Python
 Home-page: https://arrow.readthedocs.io
 Author: Chris Smith
@@ -52,7 +52,7 @@
         - Too many types: date, time, datetime, tzinfo, timedelta, 
relativedelta, etc.
         - Timezones and timestamp conversions are verbose and unpleasant
         - Timezone naivety is the norm
-        - Gaps in functionality: ISO-8601 parsing, timespans, humanization
+        - Gaps in functionality: ISO 8601 parsing, timespans, humanization
         
         Features
         --------
@@ -63,7 +63,7 @@
         - Provides super-simple creation options for many common input 
scenarios
         - :code:`shift` method with support for relative offsets, including 
weeks
         - Formats and parses strings automatically
-        - Wide support for ISO-8601
+        - Wide support for ISO 8601
         - Timezone conversion
         - Timestamp available as a property
         - Generates time spans, ranges, floors and ceilings for time frames 
ranging from microsecond to year
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arrow-0.15.2/arrow.egg-info/SOURCES.txt 
new/arrow-0.15.4/arrow.egg-info/SOURCES.txt
--- old/arrow-0.15.2/arrow.egg-info/SOURCES.txt 2019-09-14 12:27:48.000000000 
+0200
+++ new/arrow-0.15.4/arrow.egg-info/SOURCES.txt 2019-11-03 03:55:05.000000000 
+0100
@@ -8,6 +8,7 @@
 arrow/_version.py
 arrow/api.py
 arrow/arrow.py
+arrow/constants.py
 arrow/factory.py
 arrow/formatter.py
 arrow/locales.py
@@ -31,4 +32,5 @@
 tests/formatter_tests.py
 tests/locales_tests.py
 tests/parser_tests.py
-tests/util_tests.py
\ No newline at end of file
+tests/util_tests.py
+tests/utils.py
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arrow-0.15.2/docs/index.rst 
new/arrow-0.15.4/docs/index.rst
--- old/arrow-0.15.2/docs/index.rst     2019-09-14 11:27:45.000000000 +0200
+++ new/arrow-0.15.4/docs/index.rst     2019-11-02 16:50:26.000000000 +0100
@@ -67,7 +67,7 @@
     >>> arrow.get('June was born in May 1980', 'MMMM YYYY')
     <Arrow [1980-05-01T00:00:00+00:00]>
 
-Some ISO-8601 compliant strings are recognized and parsed without a format 
string:
+Some ISO 8601 compliant strings are recognized and parsed without a format 
string:
 
     >>> arrow.get('2013-09-30T15:34:00.000-07:00')
     <Arrow [2013-09-30T15:34:00-07:00]>
@@ -365,7 +365,9 @@
 
+--------------------------------+--------------+-------------------------------------------+
 |                                |Z             |-0700, -0600 ... +0600, 
+0700, +08, Z      |
 
+--------------------------------+--------------+-------------------------------------------+
-|**Timestamp**                   |X             |1381685817, 1381685817.915482 
... [#t5]_   |
+|**Seconds Timestamp**           |X             |1381685817, 1381685817.915482 
... [#t5]_   |
++--------------------------------+--------------+-------------------------------------------+
+|**ms or µs Timestamp**          |x             |1569980330813, 
1569980330813221            |
 
+--------------------------------+--------------+-------------------------------------------+
 
 .. rubric:: Footnotes
@@ -379,7 +381,7 @@
 Escaping Formats
 ~~~~~~~~~~~~~~~~
 
-Tokens, phrases, and regular expressions in a format string can be escaped 
when parsing by enclosing them within square brackets.
+Tokens, phrases, and regular expressions in a format string can be escaped 
when parsing and formatting by enclosing them within square brackets.
 
 Tokens & Phrases
 ++++++++++++++++
@@ -389,16 +391,22 @@
 .. code-block:: python
 
     >>> fmt = "YYYY-MM-DD h [h] m"
-    >>> arrow.get("2018-03-09 8 h 40", fmt)
+    >>> arw = arrow.get("2018-03-09 8 h 40", fmt)
     <Arrow [2018-03-09T08:40:00+00:00]>
+    >>> arw.format(fmt)
+    '2018-03-09 8 h 40'
 
     >>> fmt = "YYYY-MM-DD h [hello] m"
-    >>> arrow.get("2018-03-09 8 hello 40", fmt)
+    >>> arw = arrow.get("2018-03-09 8 hello 40", fmt)
     <Arrow [2018-03-09T08:40:00+00:00]>
+    >>> arw.format(fmt)
+    '2018-03-09 8 hello 40'
 
     >>> fmt = "YYYY-MM-DD h [hello world] m"
-    >>> arrow.get("2018-03-09 8 hello world 40", fmt)
+    >>> arw = arrow.get("2018-03-09 8 hello world 40", fmt)
     <Arrow [2018-03-09T08:40:00+00:00]>
+    >>> arw.format(fmt)
+    '2018-03-09 8 hello world 40'
 
 This can be useful for parsing dates in different locales such as French, in 
which it is common to format time strings as "8 h 40" rather than "8:40".
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arrow-0.15.2/tests/arrow_tests.py 
new/arrow-0.15.4/tests/arrow_tests.py
--- old/arrow-0.15.2/tests/arrow_tests.py       2019-09-09 17:15:45.000000000 
+0200
+++ new/arrow-0.15.4/tests/arrow_tests.py       2019-10-07 18:03:04.000000000 
+0200
@@ -103,6 +103,19 @@
             datetime.fromtimestamp(timestamp, tz.gettz("Europe/Paris")),
         )
 
+        with self.assertRaises(ValueError):
+            arrow.Arrow.fromtimestamp("invalid timestamp")
+
+    def test_utcfromtimestamp(self):
+
+        timestamp = time.time()
+
+        result = arrow.Arrow.utcfromtimestamp(timestamp)
+        assertDtEqual(result._datetime, 
datetime.utcnow().replace(tzinfo=tz.tzutc()))
+
+        with self.assertRaises(ValueError):
+            arrow.Arrow.utcfromtimestamp("invalid timestamp")
+
     def test_fromdatetime(self):
 
         dt = datetime(2013, 2, 3, 12, 30, 45, 1)
@@ -1805,16 +1818,6 @@
             get_tzinfo("abc")
         self.assertFalse("{}" in str(raise_ctx.exception))
 
-    def test_get_timestamp_from_input(self):
-
-        self.assertEqual(arrow.Arrow._get_timestamp_from_input(123), 123)
-        self.assertEqual(arrow.Arrow._get_timestamp_from_input(123.4), 123.4)
-        self.assertEqual(arrow.Arrow._get_timestamp_from_input("123"), 123.0)
-        self.assertEqual(arrow.Arrow._get_timestamp_from_input("123.4"), 123.4)
-
-        with self.assertRaises(ValueError):
-            arrow.Arrow._get_timestamp_from_input("abc")
-
     def test_get_iteration_params(self):
 
         self.assertEqual(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arrow-0.15.2/tests/factory_tests.py 
new/arrow-0.15.4/tests/factory_tests.py
--- old/arrow-0.15.2/tests/factory_tests.py     2019-09-13 01:21:06.000000000 
+0200
+++ new/arrow-0.15.4/tests/factory_tests.py     2019-10-07 18:03:04.000000000 
+0200
@@ -68,8 +68,8 @@
             self.factory.get(str(float_timestamp))
 
         # Regression test for issue #216
-        timestamp = 99999999999999999999999999
-        # Python 3 raises `OverflowError`, Python 2 raises `ValueError`
+        # Python 3 raises OverflowError, Python 2 raises ValueError
+        timestamp = 99999999999999999999999999.99999999999999999999999999
         with self.assertRaises((OverflowError, ValueError)):
             self.factory.get(timestamp)
 
@@ -140,6 +140,35 @@
 
         assertDtEqual(self.factory.get(dt.isoformat()), 
dt.replace(tzinfo=tz.tzutc()))
 
+    def test_one_arg_iso_calendar(self):
+
+        pairs = [
+            (datetime(2004, 1, 4), (2004, 1, 7)),
+            (datetime(2008, 12, 30), (2009, 1, 2)),
+            (datetime(2010, 1, 2), (2009, 53, 6)),
+            (datetime(2000, 2, 29), (2000, 9, 2)),
+            (datetime(2005, 1, 1), (2004, 53, 6)),
+            (datetime(2010, 1, 4), (2010, 1, 1)),
+            (datetime(2010, 1, 3), (2009, 53, 7)),
+            (datetime(2003, 12, 29), (2004, 1, 1)),
+        ]
+
+        for pair in pairs:
+            dt, iso = pair
+            self.assertEqual(self.factory.get(iso), self.factory.get(dt))
+
+        with self.assertRaises(TypeError):
+            self.factory.get((2014, 7, 1, 4))
+
+        with self.assertRaises(TypeError):
+            self.factory.get((2014, 7))
+
+        with self.assertRaises(ValueError):
+            self.factory.get((2014, 70, 1))
+
+        with self.assertRaises(ValueError):
+            self.factory.get((2014, 7, 10))
+
     def test_one_arg_other(self):
 
         with self.assertRaises(TypeError):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arrow-0.15.2/tests/formatter_tests.py 
new/arrow-0.15.4/tests/formatter_tests.py
--- old/arrow-0.15.2/tests/formatter_tests.py   2019-08-04 23:57:00.000000000 
+0200
+++ new/arrow-0.15.4/tests/formatter_tests.py   2019-11-02 15:48:09.000000000 
+0100
@@ -8,6 +8,8 @@
 
 from arrow import formatter
 
+from .utils import make_full_tz_list
+
 
 class DateTimeFormatterFormatTokenTests(Chai):
     def setUp(self):
@@ -121,18 +123,13 @@
 
     def test_timezone_formatter(self):
 
-        tz_map = {
-            # 'BRST': 'America/Sao_Paulo', TODO investigate why this fails
-            "CET": "Europe/Berlin",
-            "JST": "Asia/Tokyo",
-            "PST": "US/Pacific",
-        }
-
-        for abbreviation, full_name in tz_map.items():
+        for full_name in make_full_tz_list():
             # This test will fail if we use "now" as date as soon as we change 
from/to DST
             dt = datetime(1986, 2, 14, tzinfo=pytz.timezone("UTC")).replace(
                 tzinfo=dateutil_tz.gettz(full_name)
             )
+            abbreviation = dt.tzname()
+
             result = self.formatter._format_token(dt, "ZZZ")
             self.assertEqual(result, abbreviation)
 
@@ -150,3 +147,53 @@
         dt = datetime(2012, 1, 1, 11)
         self.assertEqual(self.formatter._format_token(dt, None), None)
         self.assertEqual(self.formatter._format_token(dt, "NONSENSE"), None)
+
+    def test_escape(self):
+
+        self.assertEqual(
+            self.formatter.format(
+                datetime(2015, 12, 10, 17, 9), "MMMM D, YYYY [at] h:mma"
+            ),
+            "December 10, 2015 at 5:09pm",
+        )
+
+        self.assertEqual(
+            self.formatter.format(
+                datetime(2015, 12, 10, 17, 9), "[MMMM] M D, YYYY [at] h:mma"
+            ),
+            "MMMM 12 10, 2015 at 5:09pm",
+        )
+
+        self.assertEqual(
+            self.formatter.format(
+                datetime(1990, 11, 25),
+                "[It happened on] MMMM Do [in the year] YYYY [a long time 
ago]",
+            ),
+            "It happened on November 25th in the year 1990 a long time ago",
+        )
+
+        self.assertEqual(
+            self.formatter.format(
+                datetime(1990, 11, 25),
+                "[It happened on] MMMM Do [in the][ year] YYYY [a long time 
ago]",
+            ),
+            "It happened on November 25th in the year 1990 a long time ago",
+        )
+
+        self.assertEqual(
+            self.formatter.format(
+                datetime(1, 1, 1), "[I'm][ entirely][ escaped,][ weee!]"
+            ),
+            "I'm entirely escaped, weee!",
+        )
+
+        # Special RegEx characters
+        self.assertEqual(
+            self.formatter.format(
+                datetime(2017, 12, 31, 2, 0), "MMM DD, YYYY |^${}().*+?<>-& 
h:mm A"
+            ),
+            "Dec 31, 2017 |^${}().*+?<>-& 2:00 AM",
+        )
+
+        # Escaping is atomic: brackets inside brackets are treated litterally
+        self.assertEqual(self.formatter.format(datetime(1, 1, 1), "[[[ ]]"), 
"[[ ]")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arrow-0.15.2/tests/locales_tests.py 
new/arrow-0.15.4/tests/locales_tests.py
--- old/arrow-0.15.2/tests/locales_tests.py     2019-09-14 11:27:45.000000000 
+0200
+++ new/arrow-0.15.4/tests/locales_tests.py     2019-09-18 06:21:53.000000000 
+0200
@@ -150,6 +150,54 @@
 
         self.assertEqual(locale.ordinal_number(1), "1º")
 
+    def test_format_timeframe(self):
+        locale = locales.SpanishLocale()
+        self.assertEqual(locale._format_timeframe("now", 0), "ahora")
+        self.assertEqual(locale._format_timeframe("seconds", 1), "segundos")
+        self.assertEqual(locale._format_timeframe("seconds", 3), "segundos")
+        self.assertEqual(locale._format_timeframe("seconds", 30), "segundos")
+        self.assertEqual(locale._format_timeframe("minute", 1), "un minuto")
+        self.assertEqual(locale._format_timeframe("minutes", 4), "4 minutos")
+        self.assertEqual(locale._format_timeframe("minutes", 40), "40 minutos")
+        self.assertEqual(locale._format_timeframe("hour", 1), "una hora")
+        self.assertEqual(locale._format_timeframe("hours", 5), "5 horas")
+        self.assertEqual(locale._format_timeframe("hours", 23), "23 horas")
+        self.assertEqual(locale._format_timeframe("day", 1), "un día")
+        self.assertEqual(locale._format_timeframe("days", 6), "6 días")
+        self.assertEqual(locale._format_timeframe("days", 12), "12 días")
+        self.assertEqual(locale._format_timeframe("week", 1), "una semana")
+        self.assertEqual(locale._format_timeframe("weeks", 2), "2 semanas")
+        self.assertEqual(locale._format_timeframe("weeks", 3), "3 semanas")
+        self.assertEqual(locale._format_timeframe("month", 1), "un mes")
+        self.assertEqual(locale._format_timeframe("months", 7), "7 meses")
+        self.assertEqual(locale._format_timeframe("months", 11), "11 meses")
+        self.assertEqual(locale._format_timeframe("year", 1), "un año")
+        self.assertEqual(locale._format_timeframe("years", 8), "8 años")
+        self.assertEqual(locale._format_timeframe("years", 12), "12 años")
+
+        self.assertEqual(locale._format_timeframe("now", 0), "ahora")
+        self.assertEqual(locale._format_timeframe("seconds", -1), "segundos")
+        self.assertEqual(locale._format_timeframe("seconds", -9), "segundos")
+        self.assertEqual(locale._format_timeframe("seconds", -12), "segundos")
+        self.assertEqual(locale._format_timeframe("minute", -1), "un minuto")
+        self.assertEqual(locale._format_timeframe("minutes", -2), "2 minutos")
+        self.assertEqual(locale._format_timeframe("minutes", -10), "10 
minutos")
+        self.assertEqual(locale._format_timeframe("hour", -1), "una hora")
+        self.assertEqual(locale._format_timeframe("hours", -3), "3 horas")
+        self.assertEqual(locale._format_timeframe("hours", -11), "11 horas")
+        self.assertEqual(locale._format_timeframe("day", -1), "un día")
+        self.assertEqual(locale._format_timeframe("days", -2), "2 días")
+        self.assertEqual(locale._format_timeframe("days", -12), "12 días")
+        self.assertEqual(locale._format_timeframe("week", -1), "una semana")
+        self.assertEqual(locale._format_timeframe("weeks", -2), "2 semanas")
+        self.assertEqual(locale._format_timeframe("weeks", -3), "3 semanas")
+        self.assertEqual(locale._format_timeframe("month", -1), "un mes")
+        self.assertEqual(locale._format_timeframe("months", -3), "3 meses")
+        self.assertEqual(locale._format_timeframe("months", -13), "13 meses")
+        self.assertEqual(locale._format_timeframe("year", -1), "un año")
+        self.assertEqual(locale._format_timeframe("years", -4), "4 años")
+        self.assertEqual(locale._format_timeframe("years", -14), "14 años")
+
 
 class FrenchLocalesTests(Chai):
     def test_ordinal_number(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arrow-0.15.2/tests/parser_tests.py 
new/arrow-0.15.4/tests/parser_tests.py
--- old/arrow-0.15.2/tests/parser_tests.py      2019-09-12 18:12:22.000000000 
+0200
+++ new/arrow-0.15.4/tests/parser_tests.py      2019-11-02 15:48:09.000000000 
+0100
@@ -10,8 +10,11 @@
 from dateutil import tz
 
 from arrow import parser
+from arrow.constants import MAX_TIMESTAMP_US
 from arrow.parser import DateTimeParser, ParserError, ParserMatchError
 
+from .utils import make_full_tz_list
+
 
 class DateTimeParserTests(Chai):
     def setUp(self):
@@ -257,6 +260,40 @@
         with self.assertRaises(ParserError):
             self.parser.parse(".1565982019", "X")
 
+    def test_parse_expanded_timestamp(self):
+        # test expanded timestamps that include milliseconds
+        # and microseconds as multiples rather than decimals
+        # requested in issue #357
+
+        tz_utc = tz.tzutc()
+        timestamp = 1569982581.413132
+        timestamp_milli = int(round(timestamp * 1000))
+        timestamp_micro = int(round(timestamp * 1000000))
+
+        # "x" token should parse integer timestamps below MAX_TIMESTAMP 
normally
+        self.expected = datetime.fromtimestamp(int(timestamp), tz=tz_utc)
+        self.assertEqual(
+            self.parser.parse("{:d}".format(int(timestamp)), "x"), 
self.expected
+        )
+
+        self.expected = datetime.fromtimestamp(round(timestamp, 3), tz=tz_utc)
+        self.assertEqual(
+            self.parser.parse("{:d}".format(timestamp_milli), "x"), 
self.expected
+        )
+
+        self.expected = datetime.fromtimestamp(timestamp, tz=tz_utc)
+        self.assertEqual(
+            self.parser.parse("{:d}".format(timestamp_micro), "x"), 
self.expected
+        )
+
+        # anything above max µs timestamp should fail
+        with self.assertRaises(ValueError):
+            self.parser.parse("{:d}".format(int(MAX_TIMESTAMP_US) + 1), "x")
+
+        # floats are not allowed with the "x" token
+        with self.assertRaises(ParserMatchError):
+            self.parser.parse("{:f}".format(timestamp), "x")
+
     def test_parse_names(self):
 
         self.expected = datetime(2012, 1, 1)
@@ -297,23 +334,7 @@
         )
 
     def test_parse_tz_name_zzz(self):
-        for tz_name in (
-            # best solution would be to test on every available tz name from
-            # the tz database but it is actually tricky to retrieve them from
-            # dateutil so here is short list that should match all
-            # naming patterns/conventions in used tz database
-            "Africa/Tripoli",
-            "America/Port_of_Spain",
-            "Australia/LHI",
-            "Etc/GMT-11",
-            "Etc/GMT0",
-            "Etc/UCT",
-            "Etc/GMT+9",
-            "GMT+0",
-            "CST6CDT",
-            "GMT-0",
-            "W-SU",
-        ):
+        for tz_name in make_full_tz_list():
             self.expected = datetime(2013, 1, 1, tzinfo=tz.gettz(tz_name))
             self.assertEqual(
                 self.parser.parse("2013-01-01 %s" % tz_name, "YYYY-MM-DD ZZZ"),
@@ -565,6 +586,48 @@
         with self.assertRaises(ParserError):
             self.parser.parse("145", "DDDD")
 
+    def test_parse_HH_24(self):
+        self.assertEqual(
+            self.parser.parse("2019-10-30T24:00:00", "YYYY-MM-DDTHH:mm:ss"),
+            datetime(2019, 10, 31, 0, 0, 0, 0),
+        )
+        self.assertEqual(
+            self.parser.parse("2019-10-30T24:00", "YYYY-MM-DDTHH:mm"),
+            datetime(2019, 10, 31, 0, 0, 0, 0),
+        )
+        self.assertEqual(
+            self.parser.parse("2019-10-30T24", "YYYY-MM-DDTHH"),
+            datetime(2019, 10, 31, 0, 0, 0, 0),
+        )
+        self.assertEqual(
+            self.parser.parse("2019-10-30T24:00:00.0", 
"YYYY-MM-DDTHH:mm:ss.S"),
+            datetime(2019, 10, 31, 0, 0, 0, 0),
+        )
+        self.assertEqual(
+            self.parser.parse("2019-10-31T24:00:00", "YYYY-MM-DDTHH:mm:ss"),
+            datetime(2019, 11, 1, 0, 0, 0, 0),
+        )
+        self.assertEqual(
+            self.parser.parse("2019-12-31T24:00:00", "YYYY-MM-DDTHH:mm:ss"),
+            datetime(2020, 1, 1, 0, 0, 0, 0),
+        )
+        self.assertEqual(
+            self.parser.parse("2019-12-31T23:59:59.9999999", 
"YYYY-MM-DDTHH:mm:ss.S"),
+            datetime(2020, 1, 1, 0, 0, 0, 0),
+        )
+
+        with self.assertRaises(ParserError):
+            self.parser.parse("2019-12-31T24:01:00", "YYYY-MM-DDTHH:mm:ss")
+
+        with self.assertRaises(ParserError):
+            self.parser.parse("2019-12-31T24:00:01", "YYYY-MM-DDTHH:mm:ss")
+
+        with self.assertRaises(ParserError):
+            self.parser.parse("2019-12-31T24:00:00.1", "YYYY-MM-DDTHH:mm:ss.S")
+
+        with self.assertRaises(ParserError):
+            self.parser.parse("2019-12-31T24:00:00.999999", 
"YYYY-MM-DDTHH:mm:ss.S")
+
 
 class DateTimeParserRegexTests(Chai):
     def setUp(self):
@@ -619,6 +682,10 @@
 
         self.assertEqual(self.format_regex.findall("X"), ["X"])
 
+    def test_format_timestamp_milli(self):
+
+        self.assertEqual(self.format_regex.findall("x"), ["x"])
+
     def test_escape(self):
 
         escape_regex = parser.DateTimeParser._ESCAPE_RE
@@ -690,10 +757,22 @@
         self.assertEqual(
             timestamp_re.findall("1565707550.452729"), ["1565707550.452729"]
         )
+        self.assertEqual(
+            timestamp_re.findall("-1565707550.452729"), ["-1565707550.452729"]
+        )
+        self.assertEqual(timestamp_re.findall("-1565707550"), ["-1565707550"])
         self.assertEqual(timestamp_re.findall("1565707550"), ["1565707550"])
         self.assertEqual(timestamp_re.findall("1565707550."), [])
         self.assertEqual(timestamp_re.findall(".1565707550"), [])
 
+    def test_timestamp_milli(self):
+        timestamp_expanded_re = parser.DateTimeParser._TIMESTAMP_EXPANDED_RE
+        self.assertEqual(timestamp_expanded_re.findall("-1565707550"), 
["-1565707550"])
+        self.assertEqual(timestamp_expanded_re.findall("1565707550"), 
["1565707550"])
+        self.assertEqual(timestamp_expanded_re.findall("1565707550.452729"), 
[])
+        self.assertEqual(timestamp_expanded_re.findall("1565707550."), [])
+        self.assertEqual(timestamp_expanded_re.findall(".1565707550"), [])
+
     def test_time(self):
         time_re = parser.DateTimeParser._TIME_RE
         time_seperators = [":", ""]
@@ -1139,6 +1218,44 @@
         with self.assertRaises(ParserError):
             self.parser.parse_iso("20180517T1055213Z")
 
+    def test_midnight_end_day(self):
+        self.assertEqual(
+            self.parser.parse_iso("2019-10-30T24:00:00"),
+            datetime(2019, 10, 31, 0, 0, 0, 0),
+        )
+        self.assertEqual(
+            self.parser.parse_iso("2019-10-30T24:00"),
+            datetime(2019, 10, 31, 0, 0, 0, 0),
+        )
+        self.assertEqual(
+            self.parser.parse_iso("2019-10-30T24:00:00.0"),
+            datetime(2019, 10, 31, 0, 0, 0, 0),
+        )
+        self.assertEqual(
+            self.parser.parse_iso("2019-10-31T24:00:00"),
+            datetime(2019, 11, 1, 0, 0, 0, 0),
+        )
+        self.assertEqual(
+            self.parser.parse_iso("2019-12-31T24:00:00"),
+            datetime(2020, 1, 1, 0, 0, 0, 0),
+        )
+        self.assertEqual(
+            self.parser.parse_iso("2019-12-31T23:59:59.9999999"),
+            datetime(2020, 1, 1, 0, 0, 0, 0),
+        )
+
+        with self.assertRaises(ParserError):
+            self.parser.parse_iso("2019-12-31T24:01:00")
+
+        with self.assertRaises(ParserError):
+            self.parser.parse_iso("2019-12-31T24:00:01")
+
+        with self.assertRaises(ParserError):
+            self.parser.parse_iso("2019-12-31T24:00:00.1")
+
+        with self.assertRaises(ParserError):
+            self.parser.parse_iso("2019-12-31T24:00:00.999999")
+
 
 class TzinfoParserTests(Chai):
     def setUp(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arrow-0.15.2/tests/util_tests.py 
new/arrow-0.15.4/tests/util_tests.py
--- old/arrow-0.15.2/tests/util_tests.py        2019-09-09 17:15:45.000000000 
+0200
+++ new/arrow-0.15.4/tests/util_tests.py        2019-10-07 18:03:04.000000000 
+0200
@@ -13,19 +13,23 @@
 
         self.assertTrue(util.is_timestamp(timestamp_int))
         self.assertTrue(util.is_timestamp(timestamp_float))
+        self.assertTrue(util.is_timestamp(str(timestamp_int)))
+        self.assertTrue(util.is_timestamp(str(timestamp_float)))
 
-        self.assertFalse(util.is_timestamp(str(timestamp_int)))
-        self.assertFalse(util.is_timestamp(str(timestamp_float)))
         self.assertFalse(util.is_timestamp(True))
         self.assertFalse(util.is_timestamp(False))
 
+        class InvalidTimestamp:
+            pass
+
+        self.assertFalse(util.is_timestamp(InvalidTimestamp()))
+
         full_datetime = "2019-06-23T13:12:42"
         self.assertFalse(util.is_timestamp(full_datetime))
 
-        overflow_timestamp_float = 
99999999999999999999999999.99999999999999999999999999
-        with self.assertRaises((OverflowError, ValueError)):
-            util.is_timestamp(overflow_timestamp_float)
-
-        overflow_timestamp_int = int(overflow_timestamp_float)
-        with self.assertRaises((OverflowError, ValueError)):
-            util.is_timestamp(overflow_timestamp_int)
+    def test_iso_gregorian(self):
+        with self.assertRaises(ValueError):
+            util.iso_to_gregorian(2013, 0, 5)
+
+        with self.assertRaises(ValueError):
+            util.iso_to_gregorian(2013, 8, 0)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/arrow-0.15.2/tests/utils.py 
new/arrow-0.15.4/tests/utils.py
--- old/arrow-0.15.2/tests/utils.py     1970-01-01 01:00:00.000000000 +0100
+++ new/arrow-0.15.4/tests/utils.py     2019-11-01 02:21:55.000000000 +0100
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+import pytz
+from dateutil.zoneinfo import get_zonefile_instance
+
+
+def make_full_tz_list():
+    dateutil_zones = set(get_zonefile_instance().zones)
+    pytz_zones = set(pytz.all_timezones)
+    return dateutil_zones.union(pytz_zones)


Reply via email to