From 9e0cb4f71b4d4fa71e88940f6d46b428603db508 Mon Sep 17 00:00:00 2001 From: anishnya Date: Wed, 26 May 2021 03:44:04 -0400 Subject: [PATCH] Added new Excel Date Feature. --- arrow/arrow.py | 37 ++++++++++++++++++++++++++++++++++++ docs/index.rst | 12 ++++++++++++ tests/test_arrow.py | 46 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) diff --git a/arrow/arrow.py b/arrow/arrow.py index d01fa894..6421d681 100644 --- a/arrow/arrow.py +++ b/arrow/arrow.py @@ -32,6 +32,7 @@ from dateutil import tz as dateutil_tz from dateutil.relativedelta import relativedelta +import arrow from arrow import formatter, locales, parser, util from arrow.constants import DEFAULT_LOCALE, DEHUMANIZE_LOCALES from arrow.locales import TimeFrameLiteral @@ -1787,6 +1788,42 @@ def _get_tzinfo(tz_expr: Optional[TZ_EXPR]) -> dt_tzinfo: except parser.ParserError: raise ValueError(f"{tz_expr!r} not recognized as a timezone.") + @classmethod + def excel_date( + cls, delta: Union[int, float], default_windows_date: bool = True + ) -> "Arrow": + """Returns a new :class:`Arrow ` object, that represents + the date of an Excel Serial formatted date. + + :param delta: a ``int`` or ``float`` representing an Excel Serial Date. + :param default_windows_date: (optional) a ``bool`` specifying whether a user + wants to use the Windows or macOS date system. Defaults to 'True' for Windows + date system. + + Usage:: + + >>> arw = Arrow.excel_date(34519) + >>> arw + + >>> arw = Arrow.excel_date(34519, default_windows_date = False) + >>> arw + + + """ + + if default_windows_date: + # Need to have this clause as Excel incorrectly considers 1900 a leap year + if delta < 60: + start_date = arrow.get("1899-12-31") + else: + start_date = arrow.get("1899-12-30") + else: + start_date = arrow.get("1904-01-01") + + shifted_time = start_date.shift(days=delta) + + return shifted_time + @classmethod def _get_datetime( cls, expr: Union["Arrow", dt_datetime, int, float, str] diff --git a/docs/index.rst b/docs/index.rst index 43895b04..e1631523 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -82,6 +82,18 @@ Arrow objects can be instantiated directly too, with the same arguments as a dat >>> arrow.Arrow(2013, 5, 5) +Arrow objects can be instantiated using the Excel Serial Date format. Both Windows and macOS date systems supported: + + .. code-block:: python + + >>> arw = Arrow.excel_date(34519) + >>> arw + + + >>> arw = Arrow.excel_date(34519, default_windows_date = False) + >>> arw + + Properties ~~~~~~~~~~ diff --git a/tests/test_arrow.py b/tests/test_arrow.py index bc72bf31..dc05b557 100644 --- a/tests/test_arrow.py +++ b/tests/test_arrow.py @@ -2867,6 +2867,52 @@ def test_value_error_exception(self): target.span("week", week_start=55) +class TestExcelDate: + def test_windows_date_leap_year_edge_case(self): + start = arrow.Arrow.fromdatetime(datetime(1900, 2, 28)) + test = arrow.Arrow.excel_date(59) + assert start == test + + def test_windows_date_leap_year_edge_case_after(self): + # Extra day loops back to be 02/28/1900 + start = arrow.Arrow.fromdatetime(datetime(1900, 2, 28)) + test = arrow.Arrow.excel_date(60) + assert start == test + + def test_windows_date_begin(self): + # Testing in Excel shows 1/1/1900 is serial number 1, + start = arrow.Arrow.fromdatetime(datetime(1900, 1, 1)) + test = arrow.Arrow.excel_date(1) + assert start == test + + def test_windows_date_large(self): + start = arrow.Arrow.fromdatetime(datetime(1994, 7, 4)) + test = arrow.Arrow.excel_date(34519) + assert start == test + + def test_mac_date_leap_year_edge_case(self): + start = arrow.Arrow.fromdatetime(datetime(1904, 2, 29)) + test = arrow.Arrow.excel_date(59, default_windows_date=False) + assert start == test + + def test_mac_date_leap_year_edge_case_after(self): + # Should be no lopp back as no edge case with macOS date system + start = arrow.Arrow.fromdatetime(datetime(1904, 3, 1)) + test = arrow.Arrow.excel_date(60, default_windows_date=False) + assert start == test + + def test_mac_date_begin(self): + # Testing in Excel shows 1/1/1904 is serial number 0, + start = arrow.Arrow.fromdatetime(datetime(1904, 1, 1)) + test = arrow.Arrow.excel_date(0, default_windows_date=False) + assert start == test + + def test_mac_date_large(self): + start = arrow.Arrow.fromdatetime(datetime(1998, 7, 5)) + test = arrow.Arrow.excel_date(34519, default_windows_date=False) + assert start == test + + class TestArrowUtil: def test_get_datetime(self):