businessdate/ymd.py
# -*- coding: utf-8 -*-
# businessdate
# ------------
# Python library for generating business dates for fast date operations
# and rich functionality.
#
# Author: sonntagsgesicht, based on a fork of Deutsche Postbank [pbrisk]
# Version: 0.5, copyright Wednesday, 18 September 2019
# Website: https://github.com/sonntagsgesicht/businessdate
# License: Apache License 2.0 (see LICENSE file)
from math import floor
#: list(int): non-leap year number of days per month
_days_per_month = \
[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
#: list(int): non-leap year cumulative number of days per month
_cum_month_days = \
[0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]
#: dict: {year: (month, day)} of easter sunday dates from 1899 to 2200
_easter_dates = {
1899: (1899, 4, 2), 1900: (1900, 4, 15), 1901: (1901, 4, 7), 1902: (1902, 3, 30), 1903: (1903, 4, 12),
1904: (1904, 4, 3), 1905: (1905, 4, 23), 1906: (1906, 4, 15), 1907: (1907, 3, 31), 1908: (1908, 4, 19),
1909: (1909, 4, 11), 1910: (1910, 3, 27), 1911: (1911, 4, 16), 1912: (1912, 4, 7), 1913: (1913, 3, 23),
1914: (1914, 4, 12), 1915: (1915, 4, 4), 1916: (1916, 4, 23), 1917: (1917, 4, 8), 1918: (1918, 3, 31),
1919: (1919, 4, 20), 1920: (1920, 4, 4), 1921: (1921, 3, 27), 1922: (1922, 4, 16), 1923: (1923, 4, 1),
1924: (1924, 4, 20), 1925: (1925, 4, 12), 1926: (1926, 4, 4), 1927: (1927, 4, 17), 1928: (1928, 4, 8),
1929: (1929, 3, 31), 1930: (1930, 4, 20), 1931: (1931, 4, 5), 1932: (1932, 3, 27), 1933: (1933, 4, 16),
1934: (1934, 4, 1), 1935: (1935, 4, 21), 1936: (1936, 4, 12), 1937: (1937, 3, 28), 1938: (1938, 4, 17),
1939: (1939, 4, 9), 1940: (1940, 3, 24), 1941: (1941, 4, 13), 1942: (1942, 4, 5), 1943: (1943, 4, 25),
1944: (1944, 4, 9), 1945: (1945, 4, 1), 1946: (1946, 4, 21), 1947: (1947, 4, 6), 1948: (1948, 3, 28),
1949: (1949, 4, 17), 1950: (1950, 4, 9), 1951: (1951, 3, 25), 1952: (1952, 4, 13), 1953: (1953, 4, 5),
1954: (1954, 4, 18), 1955: (1955, 4, 10), 1956: (1956, 4, 1), 1957: (1957, 4, 21), 1958: (1958, 4, 6),
1959: (1959, 3, 29), 1960: (1960, 4, 17), 1961: (1961, 4, 2), 1962: (1962, 4, 22), 1963: (1963, 4, 14),
1964: (1964, 3, 29), 1965: (1965, 4, 18), 1966: (1966, 4, 10), 1967: (1967, 3, 26), 1968: (1968, 4, 14),
1969: (1969, 4, 6), 1970: (1970, 3, 29), 1971: (1971, 4, 11), 1972: (1972, 4, 2), 1973: (1973, 4, 22),
1974: (1974, 4, 14), 1975: (1975, 3, 30), 1976: (1976, 4, 18), 1977: (1977, 4, 10), 1978: (1978, 3, 26),
1979: (1979, 4, 15), 1980: (1980, 4, 6), 1981: (1981, 4, 19), 1982: (1982, 4, 11), 1983: (1983, 4, 3),
1984: (1984, 4, 22), 1985: (1985, 4, 7), 1986: (1986, 3, 30), 1987: (1987, 4, 19), 1988: (1988, 4, 3),
1989: (1989, 3, 26), 1990: (1990, 4, 15), 1991: (1991, 3, 31), 1992: (1992, 4, 19), 1993: (1993, 4, 11),
1994: (1994, 4, 3), 1995: (1995, 4, 16), 1996: (1996, 4, 7), 1997: (1997, 3, 30), 1998: (1998, 4, 12),
1999: (1999, 4, 4), 2000: (2000, 4, 23), 2001: (2001, 4, 15), 2002: (2002, 3, 31), 2003: (2003, 4, 20),
2004: (2004, 4, 11), 2005: (2005, 3, 27), 2006: (2006, 4, 16), 2007: (2007, 4, 8), 2008: (2008, 3, 23),
2009: (2009, 4, 12), 2010: (2010, 4, 4), 2011: (2011, 4, 24), 2012: (2012, 4, 8), 2013: (2013, 3, 31),
2014: (2014, 4, 20), 2015: (2015, 4, 5), 2016: (2016, 3, 27), 2017: (2017, 4, 16), 2018: (2018, 4, 1),
2019: (2019, 4, 21), 2020: (2020, 4, 12), 2021: (2021, 4, 4), 2022: (2022, 4, 17), 2023: (2023, 4, 9),
2024: (2024, 3, 31), 2025: (2025, 4, 20), 2026: (2026, 4, 5), 2027: (2027, 3, 28), 2028: (2028, 4, 16),
2029: (2029, 4, 1), 2030: (2030, 4, 21), 2031: (2031, 4, 13), 2032: (2032, 3, 28), 2033: (2033, 4, 17),
2034: (2034, 4, 9), 2035: (2035, 3, 25), 2036: (2036, 4, 13), 2037: (2037, 4, 5), 2038: (2038, 4, 25),
2039: (2039, 4, 10), 2040: (2040, 4, 1), 2041: (2041, 4, 21), 2042: (2042, 4, 6), 2043: (2043, 3, 29),
2044: (2044, 4, 17), 2045: (2045, 4, 9), 2046: (2046, 3, 25), 2047: (2047, 4, 14), 2048: (2048, 4, 5),
2049: (2049, 4, 18), 2050: (2050, 4, 10), 2051: (2051, 4, 2), 2052: (2052, 4, 21), 2053: (2053, 4, 6),
2054: (2054, 3, 29), 2055: (2055, 4, 18), 2056: (2056, 4, 2), 2057: (2057, 4, 22), 2058: (2058, 4, 14),
2059: (2059, 3, 30), 2060: (2060, 4, 18), 2061: (2061, 4, 10), 2062: (2062, 3, 26), 2063: (2063, 4, 15),
2064: (2064, 4, 6), 2065: (2065, 3, 29), 2066: (2066, 4, 11), 2067: (2067, 4, 3), 2068: (2068, 4, 22),
2069: (2069, 4, 14), 2070: (2070, 3, 30), 2071: (2071, 4, 19), 2072: (2072, 4, 10), 2073: (2073, 3, 26),
2074: (2074, 4, 15), 2075: (2075, 4, 7), 2076: (2076, 4, 19), 2077: (2077, 4, 11), 2078: (2078, 4, 3),
2079: (2079, 4, 23), 2080: (2080, 4, 7), 2081: (2081, 3, 30), 2082: (2082, 4, 19), 2083: (2083, 4, 4),
2084: (2084, 3, 26), 2085: (2085, 4, 15), 2086: (2086, 3, 31), 2087: (2087, 4, 20), 2088: (2088, 4, 11),
2089: (2089, 4, 3), 2090: (2090, 4, 16), 2091: (2091, 4, 8), 2092: (2092, 3, 30), 2093: (2093, 4, 12),
2094: (2094, 4, 4), 2095: (2095, 4, 24), 2096: (2096, 4, 15), 2097: (2097, 3, 31), 2098: (2098, 4, 20),
2099: (2099, 4, 12), 2100: (2100, 3, 28), 2101: (2101, 4, 17), 2102: (2102, 4, 9), 2103: (2103, 3, 25),
2104: (2104, 4, 13), 2105: (2105, 4, 5), 2106: (2106, 4, 18), 2107: (2107, 4, 10), 2108: (2108, 4, 1),
2109: (2109, 4, 21), 2110: (2110, 4, 6), 2111: (2111, 3, 29), 2112: (2112, 4, 17), 2113: (2113, 4, 2),
2114: (2114, 4, 22), 2115: (2115, 4, 14), 2116: (2116, 3, 29), 2117: (2117, 4, 18), 2118: (2118, 4, 10),
2119: (2119, 3, 26), 2120: (2120, 4, 14), 2121: (2121, 4, 6), 2122: (2122, 3, 29), 2123: (2123, 4, 11),
2124: (2124, 4, 2), 2125: (2125, 4, 22), 2126: (2126, 4, 14), 2127: (2127, 3, 30), 2128: (2128, 4, 18),
2129: (2129, 4, 10), 2130: (2130, 3, 26), 2131: (2131, 4, 15), 2132: (2132, 4, 6), 2133: (2133, 4, 19),
2134: (2134, 4, 11), 2135: (2135, 4, 3), 2136: (2136, 4, 22), 2137: (2137, 4, 7), 2138: (2138, 3, 30),
2139: (2139, 4, 19), 2140: (2140, 4, 3), 2141: (2141, 3, 26), 2142: (2142, 4, 15), 2143: (2143, 3, 31),
2144: (2144, 4, 19), 2145: (2145, 4, 11), 2146: (2146, 4, 3), 2147: (2147, 4, 16), 2148: (2148, 4, 7),
2149: (2149, 3, 30), 2150: (2150, 4, 12), 2151: (2151, 4, 4), 2152: (2152, 4, 23), 2153: (2153, 4, 15),
2154: (2154, 3, 31), 2155: (2155, 4, 20), 2156: (2156, 4, 11), 2157: (2157, 3, 27), 2158: (2158, 4, 16),
2159: (2159, 4, 8), 2160: (2160, 3, 23), 2161: (2161, 4, 12), 2162: (2162, 4, 4), 2163: (2163, 4, 24),
2164: (2164, 4, 8), 2165: (2165, 3, 31), 2166: (2166, 4, 20), 2167: (2167, 4, 5), 2168: (2168, 3, 27),
2169: (2169, 4, 16), 2170: (2170, 4, 1), 2171: (2171, 4, 21), 2172: (2172, 4, 12), 2173: (2173, 4, 4),
2174: (2174, 4, 17), 2175: (2175, 4, 9), 2176: (2176, 3, 31), 2177: (2177, 4, 20), 2178: (2178, 4, 5),
2179: (2179, 3, 28), 2180: (2180, 4, 16), 2181: (2181, 4, 1), 2182: (2182, 4, 21), 2183: (2183, 4, 13),
2184: (2184, 3, 28), 2185: (2185, 4, 17), 2186: (2186, 4, 9), 2187: (2187, 3, 25), 2188: (2188, 4, 13),
2189: (2189, 4, 5), 2190: (2190, 4, 25), 2191: (2191, 4, 10), 2192: (2192, 4, 1), 2193: (2193, 4, 21),
2194: (2194, 4, 6), 2195: (2195, 3, 29), 2196: (2196, 4, 17), 2197: (2197, 4, 9), 2198: (2198, 3, 25),
2199: (2199, 4, 14), 2200: (2200, 4, 6)
}
def easter(year):
"""
returns (year, month, day) tuple for sunday dates for given calendar year between 1899 and 2200
:param int year: calendar year
:return bool:
"""
return _easter_dates[year]
def is_leap_year(year):
"""
returns True for leap year and False otherwise
:param int year: calendar year
:return bool:
"""
# return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)
return year % 100 != 0 or year % 400 == 0 if year % 4 == 0 else False
def days_in_year(year):
"""
returns number of days in the given calendar year
:param int year: calendar year
:return int:
"""
return 366 if is_leap_year(year) else 365
def days_in_month(year, month):
"""
returns number of days for the given year and month
:param int year: calendar year
:param int month: calendar month
:return int:
"""
eom = _days_per_month[month - 1]
if is_leap_year(year) and month == 2:
eom += 1
return eom
def end_of_quarter_month(month):
"""
method to return last month of quarter
:param int month:
:return: int
"""
while month % 3:
month += 1
return month
def is_valid_ymd(year, month, day):
"""
return True if (year,month, day) can be represented in Excel-notation
(number of days since 30.12.1899) for calendar days, otherwise False
:param int year: calendar year
:param int month: calendar month
:param int day: calendar day
:return bool:
"""
return 1 <= month <= 12 and 1 <= day <= days_in_month(year, month) and year >= 1899
def from_excel_to_ymd(excel_int):
"""
converts date in Microsoft Excel representation style and returns `(year, month, day)` tuple
:param int excel_int: date as int (days since 1899-12-31)
:return tuple(int, int, int):
"""
int_date = int(floor(excel_int))
int_date -= 1 if excel_int > 60 else 0
# jd: There are two errors in excels own date <> int conversion.
# The first is that there exists the 00.01.1900 and the second that there never happened to be a 29.2.1900 since it
# was no leap year. So there is the int 60 <> 29.2.1900 which has to be jumped over.
year = (int_date - 1) // 365
rest_days = int_date - 365 * year - (year + 3) // 4 + (year + 99) // 100 - (year + 299) // 400
year += 1900
while rest_days <= 0:
year -= 1
rest_days += days_in_year(year)
month = 1
if is_leap_year(year) and rest_days == 60:
month = 2
day = 29
else:
if is_leap_year(year) and rest_days > 60:
rest_days -= 1
while rest_days > _cum_month_days[month]:
month += 1
day = rest_days - _cum_month_days[month - 1]
return year, month, day
def from_ymd_to_excel(year, month, day):
"""
converts date as `year, month, day` tuple into Microsoft Excel representation style
:param int year:
:param int month:
:param int day:
:return int:
"""
if not is_valid_ymd(year, month, day):
raise ValueError("Invalid date {0}.{1}.{2}".format(year, month, day))
days = _cum_month_days[month - 1] + day
days += 1 if (is_leap_year(year) and month > 2) else 0
years_distance = year - 1900
days += \
years_distance * 365 + (years_distance + 3) // 4 - (years_distance + 99) // 100 + (years_distance + 299) // 400
# count days since 30.12.1899 (excluding 30.12.1899) (workaround for excel bug)
days += 1 if (year, month, day) > (1900, 2, 28) else 0
return days