diff --git a/lib/arrow/_version.py b/lib/arrow/_version.py index c68196d1..a955fdae 100644 --- a/lib/arrow/_version.py +++ b/lib/arrow/_version.py @@ -1 +1 @@ -__version__ = "1.2.0" +__version__ = "1.2.1" diff --git a/lib/arrow/arrow.py b/lib/arrow/arrow.py index ad95cacd..fef66c10 100644 --- a/lib/arrow/arrow.py +++ b/lib/arrow/arrow.py @@ -75,6 +75,7 @@ _GRANULARITY = Literal[ "day", "week", "month", + "quarter", "year", ] @@ -132,6 +133,7 @@ class Arrow: _SECS_PER_DAY: Final[int] = 60 * 60 * 24 _SECS_PER_WEEK: Final[int] = 60 * 60 * 24 * 7 _SECS_PER_MONTH: Final[float] = 60 * 60 * 24 * 30.5 + _SECS_PER_QUARTER: Final[float] = 60 * 60 * 24 * 30.5 * 3 _SECS_PER_YEAR: Final[int] = 60 * 60 * 24 * 365 _SECS_MAP: Final[Mapping[TimeFrameLiteral, float]] = { @@ -141,6 +143,7 @@ class Arrow: "day": _SECS_PER_DAY, "week": _SECS_PER_WEEK, "month": _SECS_PER_MONTH, + "quarter": _SECS_PER_QUARTER, "year": _SECS_PER_YEAR, } @@ -1245,12 +1248,14 @@ class Arrow: delta = sign * delta_second / self._SECS_PER_WEEK elif granularity == "month": delta = sign * delta_second / self._SECS_PER_MONTH + elif granularity == "quarter": + delta = sign * delta_second / self._SECS_PER_QUARTER elif granularity == "year": delta = sign * delta_second / self._SECS_PER_YEAR else: raise ValueError( "Invalid level of granularity. " - "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month' or 'year'." + "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter' or 'year'." ) if trunc(abs(delta)) != 1: @@ -1258,6 +1263,13 @@ class Arrow: return locale.describe(granularity, delta, only_distance=only_distance) else: + + if not granularity: + raise ValueError( + "Empty granularity list provided. " + "Please select one or more from 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter', 'year'." + ) + timeframes: List[Tuple[TimeFrameLiteral, float]] = [] def gather_timeframes(_delta: float, _frame: TimeFrameLiteral) -> float: @@ -1275,6 +1287,7 @@ class Arrow: delta = float(delta_second) frames: Tuple[TimeFrameLiteral, ...] = ( "year", + "quarter", "month", "week", "day", @@ -1288,7 +1301,7 @@ class Arrow: if len(timeframes) < len(granularity): raise ValueError( "Invalid level of granularity. " - "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month' or 'year'." + "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter' or 'year'." ) return locale.describe_multi(timeframes, only_distance=only_distance) diff --git a/lib/arrow/constants.py b/lib/arrow/constants.py index 085ec392..e41ffa5d 100644 --- a/lib/arrow/constants.py +++ b/lib/arrow/constants.py @@ -143,4 +143,6 @@ DEHUMANIZE_LOCALES = { "ta", "ta-in", "ta-lk", + "ur", + "ur-pk", } diff --git a/lib/arrow/locales.py b/lib/arrow/locales.py index 6221df7a..d6d5c486 100644 --- a/lib/arrow/locales.py +++ b/lib/arrow/locales.py @@ -35,6 +35,8 @@ TimeFrameLiteral = Literal[ "weeks", "month", "months", + "quarter", + "quarters", "year", "years", ] @@ -98,6 +100,8 @@ class Locale: "weeks": "", "month": "", "months": "", + "quarter": "", + "quarters": "", "year": "", "years": "", } @@ -314,6 +318,8 @@ class EnglishLocale(Locale): "weeks": "{0} weeks", "month": "a month", "months": "{0} months", + "quarter": "a quarter", + "quarters": "{0} quarters", "year": "a year", "years": "{0} years", } @@ -2447,7 +2453,7 @@ class AzerbaijaniLocale(Locale): timeframes = { "now": "indi", - "second": "saniyə", + "second": "bir saniyə", "seconds": "{0} saniyə", "minute": "bir dəqiqə", "minutes": "{0} dəqiqə", @@ -2455,9 +2461,11 @@ class AzerbaijaniLocale(Locale): "hours": "{0} saat", "day": "bir gün", "days": "{0} gün", + "week": "bir həftə", + "weeks": "{0} həftə", "month": "bir ay", "months": "{0} ay", - "year": "il", + "year": "bir il", "years": "{0} il", } @@ -3370,15 +3378,15 @@ class HebrewLocale(Locale): "minute": "דקה", "minutes": "{0} דקות", "hour": "שעה", - "hours": {"2": "שעתיים", "general": "{0} שעות"}, + "hours": {"2": "שעתיים", "ten": "{0} שעות", "higher": "{0} שעות"}, "day": "יום", - "days": {"2": "יומיים", "general": "{0} ימים"}, + "days": {"2": "יומיים", "ten": "{0} ימים", "higher": "{0} יום"}, "week": "שבוע", - "weeks": {"2": "שבועיים", "general": "{0} שבועות"}, + "weeks": {"2": "שבועיים", "ten": "{0} שבועות", "higher": "{0} שבועות"}, "month": "חודש", - "months": {"2": "חודשיים", "general": "{0} חודשים"}, + "months": {"2": "חודשיים", "ten": "{0} חודשים", "higher": "{0} חודשים"}, "year": "שנה", - "years": {"2": "שנתיים", "general": "{0} שנים"}, + "years": {"2": "שנתיים", "ten": "{0} שנים", "higher": "{0} שנה"}, } meridians = { @@ -3422,18 +3430,16 @@ class HebrewLocale(Locale): day_names = ["", "שני", "שלישי", "רביעי", "חמישי", "שישי", "שבת", "ראשון"] day_abbreviations = ["", "ב׳", "ג׳", "ד׳", "ה׳", "ו׳", "ש׳", "א׳"] - def _format_timeframe( - self, timeframe: TimeFrameLiteral, delta: Union[float, int] - ) -> str: - """Hebrew couple of aware""" + def _format_timeframe(self, timeframe: TimeFrameLiteral, delta: int) -> str: form = self.timeframes[timeframe] - delta = abs(trunc(delta)) - + delta = abs(delta) if isinstance(form, Mapping): if delta == 2: form = form["2"] + elif delta == 0 or 2 < delta <= 10: + form = form["ten"] else: - form = form["general"] + form = form["higher"] return form.format(delta) @@ -5213,13 +5219,13 @@ class SerbianLocale(Locale): month_names = [ "", - "januar", # Јануар + "januar", # јануар "februar", # фебруар "mart", # март "april", # април "maj", # мај - "juni", # јун - "juli", # јул + "jun", # јун + "jul", # јул "avgust", # август "septembar", # септембар "oktobar", # октобар @@ -5229,18 +5235,18 @@ class SerbianLocale(Locale): month_abbreviations = [ "", - "jan.", - "febr.", - "mart", - "april", + "jan", + "feb", + "mar", + "apr", "maj", - "juni", - "juli", - "avg.", - "sept.", - "okt.", - "nov.", - "dec.", + "jun", + "jul", + "avg", + "sep", + "okt", + "nov", + "dec", ] day_names = [ @@ -5644,3 +5650,248 @@ class AlbanianLocale(Locale): "sht", "die", ] + + +class SinhalaLocale(Locale): + + names = ["si", "si-lk"] + + past = "{0}ට පෙර" + future = "{0}" + and_word = "සහ" + + timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[Mapping[str, str], str]]] = { + "now": "දැන්", + "second": { + "past": "තත්පරයක", + "future": "තත්පරයකින්", + }, # ක් is the article + "seconds": { + "past": "තත්පර {0} ක", + "future": "තත්පර {0} කින්", + }, + "minute": { + "past": "විනාඩියක", + "future": "විනාඩියකින්", + }, + "minutes": { + "past": "විනාඩි {0} ක", + "future": "මිනිත්තු {0} කින්", + }, + "hour": {"past": "පැයක", "future": "පැයකින්"}, + "hours": { + "past": "පැය {0} ක", + "future": "පැය {0} කින්", + }, + "day": {"past": "දිනක", "future": "දිනකට"}, + "days": { + "past": "දින {0} ක", + "future": "දින {0} කින්", + }, + "week": {"past": "සතියක", "future": "සතියකින්"}, + "weeks": { + "past": "සති {0} ක", + "future": "සති {0} කින්", + }, + "month": {"past": "මාසයක", "future": "එය මාසය තුළ"}, + "months": { + "past": "මාස {0} ක", + "future": "මාස {0} කින්", + }, + "year": {"past": "වසරක", "future": "වසරක් තුළ"}, + "years": { + "past": "අවුරුදු {0} ක", + "future": "අවුරුදු {0} තුළ", + }, + } + # Sinhala: the general format to describe timeframe is different from past and future, + # so we do not copy the original timeframes dictionary + timeframes_only_distance = dict() + timeframes_only_distance["second"] = "තත්පරයක්" + timeframes_only_distance["seconds"] = "තත්පර {0}" + timeframes_only_distance["minute"] = "මිනිත්තුවක්" + timeframes_only_distance["minutes"] = "විනාඩි {0}" + timeframes_only_distance["hour"] = "පැයක්" + timeframes_only_distance["hours"] = "පැය {0}" + timeframes_only_distance["day"] = "දවසක්" + timeframes_only_distance["days"] = "දවස් {0}" + timeframes_only_distance["week"] = "සතියක්" + timeframes_only_distance["weeks"] = "සති {0}" + timeframes_only_distance["month"] = "මාසයක්" + timeframes_only_distance["months"] = "මාස {0}" + timeframes_only_distance["year"] = "අවුරුද්දක්" + timeframes_only_distance["years"] = "අවුරුදු {0}" + + def _format_timeframe(self, timeframe: TimeFrameLiteral, delta: int) -> str: + """ + Sinhala awares time frame format function, takes into account + the differences between general, past, and future forms (three different suffixes). + """ + abs_delta = abs(delta) + form = self.timeframes[timeframe] + + if isinstance(form, str): + return form.format(abs_delta) + + if delta > 0: + key = "future" + else: + key = "past" + form = form[key] + + return form.format(abs_delta) + + def describe( + self, + timeframe: TimeFrameLiteral, + delta: Union[float, int] = 1, # key is always future when only_distance=False + only_distance: bool = False, + ) -> str: + """Describes a delta within a timeframe in plain language. + + :param timeframe: a string representing a timeframe. + :param delta: a quantity representing a delta in a timeframe. + :param only_distance: return only distance eg: "11 seconds" without "in" or "ago" keywords + """ + + if not only_distance: + return super().describe(timeframe, delta, only_distance) + # Sinhala uses a different case without 'in' or 'ago' + humanized = self.timeframes_only_distance[timeframe].format(trunc(abs(delta))) + + return humanized + + month_names = [ + "", + "ජනවාරි", + "පෙබරවාරි", + "මාර්තු", + "අප්‍රේල්", + "මැයි", + "ජූනි", + "ජූලි", + "අගෝස්තු", + "සැප්තැම්බර්", + "ඔක්තෝබර්", + "නොවැම්බර්", + "දෙසැම්බර්", + ] + + month_abbreviations = [ + "", + "ජන", + "පෙබ", + "මාර්", + "අප්‍රේ", + "මැයි", + "ජුනි", + "ජූලි", + "අගෝ", + "සැප්", + "ඔක්", + "නොවැ", + "දෙසැ", + ] + + day_names = [ + "", + "සදුදා", + "අඟහරැවදා", + "බදාදා", + "බ්‍රහස්‍පතින්‍දා", + "සිකුරාදා", + "සෙනසුරාදා", + "ඉරිදා", + ] + + day_abbreviations = [ + "", + "සදුද", + "බදා", + "බදා", + "සිකු", + "සෙන", + "අ", + "ඉරිදා", + ] + + +class UrduLocale(Locale): + + names = ["ur", "ur-pk"] + + past = "پہلے {0}" + future = "میں {0}" + and_word = "اور" + + timeframes = { + "now": "ابھی", + "second": "ایک سیکنڈ", + "seconds": "{0} سیکنڈ", + "minute": "ایک منٹ", + "minutes": "{0} منٹ", + "hour": "ایک گھنٹے", + "hours": "{0} گھنٹے", + "day": "ایک دن", + "days": "{0} دن", + "week": "ایک ہفتے", + "weeks": "{0} ہفتے", + "month": "ایک مہینہ", + "months": "{0} ماہ", + "year": "ایک سال", + "years": "{0} سال", + } + + month_names = [ + "", + "جنوری", + "فروری", + "مارچ", + "اپریل", + "مئی", + "جون", + "جولائی", + "اگست", + "ستمبر", + "اکتوبر", + "نومبر", + "دسمبر", + ] + + month_abbreviations = [ + "", + "جنوری", + "فروری", + "مارچ", + "اپریل", + "مئی", + "جون", + "جولائی", + "اگست", + "ستمبر", + "اکتوبر", + "نومبر", + "دسمبر", + ] + + day_names = [ + "", + "سوموار", + "منگل", + "بدھ", + "جمعرات", + "جمعہ", + "ہفتہ", + "اتوار", + ] + + day_abbreviations = [ + "", + "سوموار", + "منگل", + "بدھ", + "جمعرات", + "جمعہ", + "ہفتہ", + "اتوار", + ]