def in_words_float(amount, _gender=FEMALE): """ Float in words @param amount: float numeral @type amount: C{float} or C{Decimal} @return: in-words reprsentation of float numeral @rtype: C{unicode} @raise L{pytils.err.InputParameterError}: input parameters' check failed (when amount is not C{float}) @raise ValueError: when ammount is negative """ utils.check_positive(amount) pts = [] # преобразуем целую часть pts.append(sum_string(int(amount), 2, (u"целая", u"целых", u"целых"))) # теперь то, что после запятой remainder = _get_float_remainder(amount) signs = len(str(remainder)) - 1 pts.append(sum_string(int(remainder), 2, FRACTIONS[signs])) return u" ".join(pts)
def in_words(amount, gender=None): """ Numeral in words @param amount: numeral @type amount: C{int}, C{long} or C{float} @param gender: gender (MALE, FEMALE or NEUTER) @type gender: C{int} @return: in-words reprsentation of numeral @rtype: C{unicode} raise L{pytils.err.InputParameterError}: input parameters' check failed (when amount not C{int} or C{float}, gender is not C{int} (and not None), gender isn't in (MALE, FEMALE, NEUTER)) raise ValueError: when amount is negative """ check_positive(amount) if gender is None: args = (amount,) else: args = (amount, gender) # если целое if isinstance(amount, (int, long)): return in_words_int(*args) # если дробное elif isinstance(amount, float): return in_words_float(*args) # ни float, ни int else: # до сюда не должно дойти raise RuntimeError()
def rubles(amount, zero_for_kopeck=False): """ Get string for money @param amount: amount of money @type amount: C{int}, C{long} or C{float} @param zero_for_kopeck: If false, then zero kopecks ignored @type zero_for_kopeck: C{bool} @return: in-words representation of money's amount @rtype: C{unicode} @raise L{pytils.err.InputParameterError}: input parameters' check failed (amount neither C{int}, no C{float}) @raise ValueError: amount is negative """ check_positive(amount) pts = [] amount = round(amount, 2) pts.append(sum_string(int(amount), 1, (u"рубль", u"рубля", u"рублей"))) remainder = _get_float_remainder(amount, 2) iremainder = int(remainder) if iremainder != 0 or zero_for_kopeck: # если 3.1, то это 10 копеек, а не одна if iremainder < 10 and len(remainder) == 1: iremainder *= 10 pts.append( sum_string(iremainder, 2, (u"копейка", u"копейки", u"копеек"))) return u" ".join(pts)
def sum_string(amount, gender, items=None): """ Get sum in words @param amount: amount of objects @type amount: C{integer types} @param gender: gender of object (MALE, FEMALE or NEUTER) @type gender: C{int} @param items: variants of object in three forms: for one object, for two objects and for five objects @type items: 3-element C{sequence} of C{unicode} or just C{unicode} (three variants with delimeter ',') @return: in-words representation objects' amount @rtype: C{unicode} @raise ValueError: items isn't 3-element C{sequence} or C{unicode} @raise ValueError: amount bigger than 10**36-1 @raise ValueError: amount is negative """ if isinstance(items, six.text_type): items = split_values(items) if items is None: items = (u"", u"", u"") try: one_item, two_items, five_items = items except ValueError: raise ValueError("Items must be 3-element sequence") check_positive(amount) if amount == 0: return u"ноль %s" % five_items into = u'' tmp_val = amount # единицы into, tmp_val = _sum_string_fn(into, tmp_val, gender, items) names = ( (FEMALE, (u"тысяча", u"тысячи", u"тысяч")), # тысячи (MALE, (u"миллион", u"миллиона", u"миллионов")), # миллионы (MALE, (u"миллиард", u"миллиарда", u"миллиардов")), # миллиарды (MALE, (u"триллион", u"триллиона", u"триллионов")), # триллионы (MALE, (u"квадриллион", u"квадриллиона", u"квадриллионов")), # квадриллионы (MALE, (u"квинтиллион", u"квинтиллиона", u"квинтиллионов")), # квинтиллион (MALE, (u"секстилион", u"секстилиона", u"секстилионов")), # секстилион (MALE, (u"септиллион", u"септиллиона", u"септиллионов")), # септиллион (MALE, (u"октиллион", u"октиллиона", u"октиллионов")), # октиллион (MALE, (u"нониллион", u"нониллиона", u"нониллионов")), # нониллион (MALE, (u"дециллион", u"дециллиона", u"дециллионов")), # дециллион ) for name in names: into, tmp_val = _sum_string_fn(into, tmp_val, *name) if tmp_val == 0: return into raise ValueError("Cannot operand with numbers bigger than 10**36-1")
def rubles(amount, zero_for_kopeck=False): """ Get string for money @param amount: amount of money @type amount: C{integer types}, C{float} or C{Decimal} @param zero_for_kopeck: If false, then zero kopecks ignored @type zero_for_kopeck: C{bool} @return: in-words representation of money's amount @rtype: C{unicode} @raise ValueError: amount is negative """ check_positive(amount) pts = [] amount = round(amount, 2) pts.append(sum_string(int(amount), 1, ("рубль", "рубля", "рублей"))) remainder = _get_float_remainder(amount, 2) iremainder = int(remainder) if iremainder != 0 or zero_for_kopeck: # если 3.1, то это 10 копеек, а не одна if iremainder < 10 and len(remainder) == 1: iremainder *= 10 pts.append(sum_string(iremainder, 2, ("копейка", "копейки", "копеек"))) return " ".join(pts)
def in_words_float(amount, _gender=FEMALE): """ Float in words @param amount: float numeral @type amount: C{float} @return: in-words reprsentation of float numeral @rtype: C{unicode} @raise L{pytils.err.InputParameterError}: input parameters' check failed (when amount is not C{float}) @raise ValueError: when ammount is negative """ utils.check_positive(amount) pts = [] # преобразуем целую часть pts.append(sum_string(int(amount), 2, (u"целая", u"целых", u"целых"))) # теперь то, что после запятой remainder = _get_float_remainder(amount) signs = len(str(remainder)) - 1 pts.append(sum_string(int(remainder), 2, FRACTIONS[signs])) return u" ".join(pts)
def in_words(amount, gender=None): """ Numeral in words @param amount: numeral @type amount: C{int}, C{long} or C{float} @param gender: gender (MALE, FEMALE or NEUTER) @type gender: C{int} @return: in-words reprsentation of numeral @rtype: C{unicode} raise L{pytils.err.InputParameterError}: input parameters' check failed (when amount not C{int} or C{float}, gender is not C{int} (and not None), gender isn't in (MALE, FEMALE, NEUTER)) raise ValueError: when amount is negative """ check_positive(amount) if gender is None: args = (amount, ) else: args = (amount, gender) # если целое if isinstance(amount, (int, long)): return in_words_int(*args) # если дробное elif isinstance(amount, float): return in_words_float(*args) # ни float, ни int else: # до сюда не должно дойти raise RuntimeError()
def choose_plural_amount(amount, variants): """ Choose proper case depending on amount, for "N of M items" case @param amount: amount of objects @type amount: C{int} or C{long} @param variants: variants (forms) of object in such form: (1 object, 2 objects). @type variants: 2-element C{sequence} of C{unicode} or C{unicode} (two variants with delimeter ',') @return: proper variant @rtype: C{unicode} @raise L{pytils.err.InputParameterError}: input parameters' check failed (amount isn't C{int}, variants isn't C{sequence}) @raise ValueError: amount is negative @raise ValueError: variants' length lesser than 2 """ if isinstance(variants, unicode): variants = utils.split_values(variants) check_length(variants, 2) check_positive(amount) amount_str = str(amount) if amount == 1 or (amount_str[-1::] == "1" and amount_str[-2::] != "11"): variant = 0 else: variant = 1 return variants[variant]
def rubles(amount, zero_for_kopeck=False): """ Get string for money @param amount: amount of money @type amount: C{int}, C{long}, C{float} or C{Decimal} @param zero_for_kopeck: If false, then zero kopecks ignored @type zero_for_kopeck: C{bool} @return: in-words representation of money's amount @rtype: C{unicode} @raise L{pytils.err.InputParameterError}: input parameters' check failed (amount neither C{int}, no C{float}) @raise ValueError: amount is negative """ check_positive(amount) pts = [] amount = round(amount, 2) pts.append(sum_string(int(amount), 1, (u"рубль", u"рубля", u"рублей"))) remainder = _get_float_remainder(amount, 2) iremainder = int(remainder) if iremainder != 0 or zero_for_kopeck: # если 3.1, то это 10 копеек, а не одна if iremainder < 10 and len(remainder) == 1: iremainder *= 10 pts.append(sum_string(iremainder, 2, (u"копейка", u"копейки", u"копеек"))) return u" ".join(pts)
def sum_string(amount, gender, items=None, case=NOMINATIVE): """ Get sum in words @param amount: amount of objects @type amount: C{integer types} @param gender: gender of object (MALE, FEMALE or NEUTER) @type gender: C{int} @param gender: gender (NOMINATIVE or GENITIVE) @type gender: C{int} @param items: variants of object in three forms: for one object, for two objects and for five objects @type items: 3-element C{sequence} of C{unicode} or just C{unicode} (three variants with delimeter ',') @return: in-words representation objects' amount @rtype: C{unicode} @raise ValueError: items isn't 3-element C{sequence} or C{unicode} @raise ValueError: amount bigger than 10**11 @raise ValueError: amount is negative """ if isinstance(items, six.text_type): items = split_values(items) if items is None: items = (u"", u"", u"") try: one_item, two_items, five_items = items except ValueError: raise ValueError("Items must be 3-element sequence") check_positive(amount) if amount == 0: return u"ноль %s" % five_items into = u'' tmp_val = amount # единицы into, tmp_val = _sum_string_fn(into, tmp_val, gender, items, case=case) # тысячи into, tmp_val = _sum_string_fn(into, tmp_val, FEMALE, (u"тысяча", u"тысячи", u"тысяч"), case=case) # миллионы into, tmp_val = _sum_string_fn(into, tmp_val, MALE, (u"миллион", u"миллиона", u"миллионов"), case=case) # миллиарды into, tmp_val = _sum_string_fn(into, tmp_val, MALE, (u"миллиард", u"миллиарда", u"миллиардов"), case=case) if tmp_val == 0: return into else: raise ValueError("Cannot operand with numbers bigger than 10**11")
def sum_string(amount, gender, items=None): """ Get sum in words @param amount: amount of objects @type amount: C{integer types} @param gender: gender of object (MALE, FEMALE or NEUTER) @type gender: C{int} @param items: variants of object in three forms: for one object, for two objects and for five objects @type items: 3-element C{sequence} of C{unicode} or just C{unicode} (three variants with delimeter ',') @return: in-words representation objects' amount @rtype: C{unicode} @raise ValueError: items isn't 3-element C{sequence} or C{unicode} @raise ValueError: amount bigger than 10**11 @raise ValueError: amount is negative """ if isinstance(items, six.text_type): items = split_values(items) if items is None: items = (u"", u"", u"") try: one_item, two_items, five_items = items except ValueError: raise ValueError("Items must be 3-element sequence") check_positive(amount) if amount == 0: if five_items: return u"ноль %s" % five_items else: return u"ноль" into = u'' tmp_val = amount # единицы into, tmp_val = _sum_string_fn(into, tmp_val, gender, items) # тысячи into, tmp_val = _sum_string_fn(into, tmp_val, FEMALE, (u"тысяча", u"тысячи", u"тысяч")) # миллионы into, tmp_val = _sum_string_fn(into, tmp_val, MALE, (u"миллион", u"миллиона", u"миллионов")) # миллиарды into, tmp_val = _sum_string_fn(into, tmp_val, MALE, (u"миллиард", u"миллиарда", u"миллиардов")) if tmp_val == 0: return into else: raise ValueError("Cannot operand with numbers bigger than 10**11")
def sum_string(amount, gender, items=None): """ Get sum in words @param amount: amount of objects @type amount: C{int} or C{long} @param gender: gender of object (MALE, FEMALE or NEUTER) @type gender: C{int} @param items: variants of object in three forms: for one object, for two objects and for five objects @type items: 3-element C{sequence} of C{unicode} or just C{unicode} (three variants with delimeter ',') @return: in-words representation objects' amount @rtype: C{unicode} @raise L{pytils.err.InputParameterError}: input parameters' check failed @raise ValueError: items isn't 3-element C{sequence} or C{unicode} @raise ValueError: amount bigger than 10**11 @raise ValueError: amount is negative """ if isinstance(items, str): items = utils.split_values(items) if items is None: items = ("", "", "") try: one_item, two_items, five_items = items except ValueError: raise ValueError("Items must be 3-element sequence") check_positive(amount) if amount == 0: return "ноль %s" % five_items into = '' tmp_val = amount # единицы into, tmp_val = _sum_string_fn(into, tmp_val, gender, items) # тысячи into, tmp_val = _sum_string_fn(into, tmp_val, FEMALE, ("тысяча", "тысячи", "тысяч")) # миллионы into, tmp_val = _sum_string_fn(into, tmp_val, MALE, ("миллион", "миллиона", "миллионов")) # миллиарды into, tmp_val = _sum_string_fn(into, tmp_val, MALE, ("миллиард", "миллиарда", "миллиардов")) if tmp_val == 0: return into else: raise ValueError("Cannot operand with numbers bigger than 10**11")
def _get_float_remainder(fvalue, signs=9): """ Get remainder of float, i.e. 2.05 -> '05' @param fvalue: input value @type fvalue: C{integer types}, C{float} or C{Decimal} @param signs: maximum number of signs @type signs: C{integer types} @return: remainder @rtype: C{str} @raise ValueError: fvalue is negative @raise ValueError: signs overflow """ check_positive(fvalue) if isinstance(fvalue, six.integer_types): return "0" if isinstance(fvalue, Decimal) and fvalue.as_tuple()[2] == 0: # Decimal.as_tuple() -> (sign, digit_tuple, exponent) # если экспонента "0" -- значит дробной части нет return "0" signs = min(signs, len(FRACTIONS)) # нужно remainder в строке, потому что дробные X.0Y # будут "ломаться" до X.Y if 'e' in str(fvalue).lower(): remainder = str(Decimal(str(fvalue))).split('.')[1] else: remainder = str(fvalue).split('.')[1] iremainder = int(remainder) orig_remainder = remainder factor = len(str(remainder)) - signs if factor > 0: # после запятой цифр больше чем signs, округляем iremainder = int(round(iremainder / (10.0**factor))) format = "%%0%dd" % min(len(remainder), signs) remainder = format % iremainder if len(remainder) > signs: # при округлении цифр вида 0.998 ругаться raise ValueError("Signs overflow: I can't round only fractional part \ of %s to fit %s in %d signs" % \ (str(fvalue), orig_remainder, signs)) return remainder
def _get_float_remainder(fvalue, signs=9): """ Get remainder of float, i.e. 2.05 -> '05' @param fvalue: input value @type fvalue: C{int}, C{long}, C{float} or C{Decimal} @param signs: maximum number of signs @type signs: C{int} or C{long} @return: remainder @rtype: C{str} @raise L{pytils.err.InputParameterError}: input parameters' check failed (fvalue neither C{int}, no C{float}) @raise ValueError: fvalue is negative @raise ValueError: signs overflow """ check_positive(fvalue) if isinstance(fvalue, (int, long)): return "0" if isinstance(fvalue, Decimal) and fvalue.as_tuple()[2] == 0: # Decimal.as_tuple() -> (sign, digit_tuple, exponent) # если экспонента "0" -- значит дробной части нет return "0" signs = min(signs, len(FRACTIONS)) # нужно remainder в строке, потому что дробные X.0Y # будут "ломаться" до X.Y remainder = str(fvalue).split('.')[1] iremainder = int(remainder) orig_remainder = remainder factor = len(str(remainder)) - signs if factor > 0: # после запятой цифр больше чем signs, округляем iremainder = int(round(iremainder / (10.0**factor))) format = "%%0%dd" % min(len(remainder), signs) remainder = format % iremainder if len(remainder) > signs: # при округлении цифр вида 0.998 ругаться raise ValueError("Signs overflow: I can't round only fractional part \ of %s to fit %s in %d signs" % \ (str(fvalue), orig_remainder, signs)) return remainder
def _get_float_remainder(fvalue, signs=9): """ Get remainder of float, i.e. 2.05 -> '05' @param fvalue: input value @type fvalue: C{int}, C{long} or C{float} @param signs: maximum number of signs @type signs: C{int} or C{long} @return: remainder @rtype: C{str} @raise L{pytils.err.InputParameterError}: input parameters' check failed (fvalue neither C{int}, no C{float}) @raise ValueError: fvalue is negative @raise ValueError: signs overflow """ check_positive(fvalue) if isinstance(fvalue, (int, long)): return "0" signs = min(signs, len(FRACTIONS)) # нужно remainder в строке, потому что дробные X.0Y # будут "ломаться" до X.Y remainder = str(fvalue).split('.')[1] iremainder = int(remainder) orig_remainder = remainder factor = len(str(remainder)) - signs if factor > 0: # после запятой цифр больше чем signs, округляем iremainder = int(round(iremainder / (10.0**factor))) format = "%%0%dd" % min(len(remainder), signs) remainder = format % iremainder if len(remainder) > signs: # при округлении цифр вида 0.998 ругаться raise ValueError("Signs overflow: I can't round only fractional part \ of %s to fit %s in %d signs" % \ (str(fvalue), orig_remainder, signs)) return remainder
def in_words_int(amount, gender=MALE): """ Integer in words @param amount: numeral @type amount: C{integer types} @param gender: gender (MALE, FEMALE or NEUTER) @type gender: C{int} @return: in-words reprsentation of numeral @rtype: C{unicode} @raise ValueError: amount is negative """ check_positive(amount) return sum_string(amount, gender)
def in_words_int(amount, gender=MALE): """ Integer in words @param amount: numeral @type amount: C{int} or C{long} @param gender: gender (MALE, FEMALE or NEUTER) @type gender: C{int} @return: in-words reprsentation of numeral @rtype: C{unicode} @raise L{pytils.err.InputParameterError}: input parameters' check failed (when amount is not C{int}) @raise ValueError: amount is negative """ check_positive(amount) return sum_string(amount, gender)
def in_words(amount, gender=None, case=NOMINATIVE): """ Numeral in words @param amount: numeral @type amount: C{integer types}, C{float} or C{Decimal} @param gender: gender (MALE, FEMALE or NEUTER) @type gender: C{int} @param gender: gender (NOMINATIVE or GENITIVE) @type gender: C{int} @return: in-words reprsentation of numeral @rtype: C{unicode} raise ValueError: when amount is negative """ check_positive(amount) if isinstance(amount, Decimal) and amount.as_tuple()[2] == 0: # если целое, # т.е. Decimal.as_tuple -> (sign, digits tuple, exponent), exponent=0 # то как целое amount = int(amount) if gender is None: args = (amount,) else: args = (amount, gender) # если целое if isinstance(amount, six.integer_types): return in_words_int(*args, case=case) # если дробное elif isinstance(amount, (float, Decimal)): return in_words_float(*args) # ни float, ни int, ни Decimal else: # до сюда не должно дойти raise TypeError( "amount should be number type (int, long, float, Decimal), got %s" % type(amount))
def in_words(amount, gender=None): """ Numeral in words @param amount: numeral @type amount: C{int}, C{long}, C{float} or C{Decimal} @param gender: gender (MALE, FEMALE or NEUTER) @type gender: C{int} @return: in-words reprsentation of numeral @rtype: C{unicode} raise L{pytils.err.InputParameterError}: input parameters' check failed (when amount not C{int} or C{float}, gender is not C{int} (and not None), gender isn't in (MALE, FEMALE, NEUTER)) raise ValueError: when amount is negative """ check_positive(amount) if isinstance(amount, Decimal) and amount.as_tuple()[2] == 0: # если целое, # т.е. Decimal.as_tuple -> (sign, digits tuple, exponent), exponent=0 # то как целое amount = int(amount) if gender is None: args = (amount, ) else: args = (amount, gender) # если целое if isinstance(amount, (int, long)): return in_words_int(*args) # если дробное elif isinstance(amount, (float, Decimal)): return in_words_float(*args) # ни float, ни int, ни Decimal else: # до сюда не должно дойти raise RuntimeError()
def in_words(amount, gender=None): """ Numeral in words @param amount: numeral @type amount: C{int}, C{long}, C{float} or C{Decimal} @param gender: gender (MALE, FEMALE or NEUTER) @type gender: C{int} @return: in-words reprsentation of numeral @rtype: C{unicode} raise L{pytils.err.InputParameterError}: input parameters' check failed (when amount not C{int} or C{float}, gender is not C{int} (and not None), gender isn't in (MALE, FEMALE, NEUTER)) raise ValueError: when amount is negative """ check_positive(amount) if isinstance(amount, Decimal) and amount.as_tuple()[2] == 0: # если целое, # т.е. Decimal.as_tuple -> (sign, digits tuple, exponent), exponent=0 # то как целое amount = int(amount) if gender is None: args = (amount,) else: args = (amount, gender) # если целое if isinstance(amount, (int, long)): return in_words_int(*args) # если дробное elif isinstance(amount, (float, Decimal)): return in_words_float(*args) # ни float, ни int, ни Decimal else: # до сюда не должно дойти raise RuntimeError()
def in_words_float(amount, _gender=FEMALE): """ Float in words @param amount: float numeral @type amount: C{float} or C{Decimal} @return: in-words reprsentation of float numeral @rtype: C{unicode} @raise ValueError: when ammount is negative """ check_positive(amount) pts = [] # преобразуем целую часть pts.append(sum_string(int(amount), 2, ("целая", "целых", "целых"))) # теперь то, что после запятой remainder = _get_float_remainder(amount) signs = len(str(remainder)) - 1 pts.append(sum_string(int(remainder), 2, FRACTIONS[signs])) return " ".join(pts)
def in_words(amount, gender=None): """ Numeral in words @param amount: numeral @type amount: C{integer types}, C{float} or C{Decimal} @param gender: gender (MALE, FEMALE or NEUTER) @type gender: C{int} @return: in-words reprsentation of numeral @rtype: C{unicode} raise ValueError: when amount is negative """ check_positive(amount) if isinstance(amount, Decimal) and amount.as_tuple()[2] == 0: # если целое, # т.е. Decimal.as_tuple -> (sign, digits tuple, exponent), exponent=0 # то как целое amount = int(amount) if gender is None: args = (amount, ) else: args = (amount, gender) # если целое if isinstance(amount, six.integer_types): return in_words_int(*args) # если дробное elif isinstance(amount, (float, Decimal)): return in_words_float(*args) # ни float, ни int, ни Decimal else: # до сюда не должно дойти raise TypeError( "amount should be number type (int, long, float, Decimal), got %s" % type(amount))
def choose_plural(amount, variants): """ Choose proper case depending on amount @param amount: amount of objects @type amount: C{int} or C{long} @param variants: variants (forms) of object in such form: (1 object, 2 objects, 5 objects). @type variants: 3-element C{sequence} of C{unicode} or C{unicode} (three variants with delimeter ',') @return: proper variant @rtype: C{unicode} @raise L{pytils.err.InputParameterError}: input parameters' check failed (amount isn't C{int}, variants isn't C{sequence}) @raise ValueError: amount is negative @raise ValueError: variants' length lesser than 3 """ if isinstance(variants, unicode): variants = utils.split_values(variants) check_length(variants, 3) check_positive(amount) if amount % 10 == 1 and amount % 100 != 11: variant = 0 elif amount % 10 >= 2 and amount % 10 <= 4 and \ (amount % 100 < 10 or amount % 100 >= 20): variant = 1 else: variant = 2 return variants[variant]
def distance_of_time_in_words(from_time, accuracy=1, to_time=None): """ Represents distance of time in words @param from_time: source time (in seconds from epoch) @type from_time: C{int}, C{float} or C{datetime.datetime} @param accuracy: level of accuracy (1..3), default=1 @type accuracy: C{int} @param to_time: target time (in seconds from epoch), default=None translates to current time @type to_time: C{int}, C{float} or C{datetime.datetime} @return: distance of time in words @rtype: unicode @raise ValueError: accuracy is lesser or equal zero """ current = False if to_time is None: current = True to_time = datetime.datetime.now() check_positive(accuracy, strict=True) if not isinstance(from_time, datetime.datetime): from_time = datetime.datetime.fromtimestamp(from_time) if not isinstance(to_time, datetime.datetime): to_time = datetime.datetime.fromtimestamp(to_time) if from_time.tzinfo and not to_time.tzinfo: to_time = to_time.replace(tzinfo=from_time.tzinfo) dt_delta = to_time - from_time difference = dt_delta.days*86400 + dt_delta.seconds minutes_orig = int(abs(difference)/60.0) hours_orig = int(abs(difference)/3600.0) days_orig = int(abs(difference)/86400.0) in_future = from_time > to_time words = [] values = [] alternatives = [] days = days_orig hours = hours_orig - days_orig*24 words.append(u"%d %s" % (days, numeral.choose_plural(days, DAY_VARIANTS))) values.append(days) words.append(u"%d %s" % \ (hours, numeral.choose_plural(hours, HOUR_VARIANTS))) values.append(hours) days == 0 and hours == 1 and current and alternatives.append(u"час") minutes = minutes_orig - hours_orig*60 words.append(u"%d %s" % (minutes, numeral.choose_plural(minutes, MINUTE_VARIANTS))) values.append(minutes) days == 0 and hours == 0 and minutes == 1 and current and \ alternatives.append(u"минуту") # убираем из values и words конечные нули while values and not values[-1]: values.pop() words.pop() # убираем из values и words начальные нули while values and not values[0]: values.pop(0) words.pop(0) limit = min(accuracy, len(words)) real_words = words[:limit] real_values = values[:limit] # снова убираем конечные нули while real_values and not real_values[-1]: real_values.pop() real_words.pop() limit -= 1 real_str = u" ".join(real_words) # альтернативные варианты нужны только если в real_words одно значение # и, вдобавок, если используется текущее время alter_str = limit == 1 and current and alternatives and \ alternatives[0] _result_str = alter_str or real_str result_str = in_future and u"%s %s" % (PREFIX_IN, _result_str) \ or u"%s %s" % (_result_str, SUFFIX_AGO) # если же прошло менее минуты, то real_words -- пустой, и поэтому # нужно брать alternatives[0], а не result_str zero_str = minutes == 0 and not real_words and \ (in_future and u"менее чем через минуту" \ or u"менее минуты назад") # нужно использовать вчера/позавчера/завтра/послезавтра # если days 1..2 и в real_words одно значение day_alternatives = DAY_ALTERNATIVES.get(days, False) alternate_day = day_alternatives and current and limit == 1 and \ ((in_future and day_alternatives[1]) \ or day_alternatives[0]) final_str = not real_words and zero_str or alternate_day or result_str return final_str
def _sum_string_fn(into, tmp_val, gender, items=None): """ Make in-words representation of single order @param into: in-words representation of lower orders @type into: C{unicode} @param tmp_val: temporary value without lower orders @type tmp_val: C{int} or C{long} @param gender: gender (MALE, FEMALE or NEUTER) @type gender: C{int} @param items: variants of objects @type items: 3-element C{sequence} of C{unicode} @return: new into and tmp_val @rtype: C{tuple} @raise L{pytils.err.InputParameterError}: input parameters' check failed @raise ValueError: tmp_val is negative """ if items is None: items = (u"", u"", u"") one_item, two_items, five_items = items check_positive(tmp_val) if tmp_val == 0: return into, tmp_val words = [] rest = tmp_val % 1000 tmp_val = tmp_val / 1000 if rest == 0: # последние три знака нулевые if into == u"": into = u"%s " % five_items return into, tmp_val # начинаем подсчет с rest end_word = five_items # сотни words.append(HUNDREDS[rest / 100]) # десятки rest = rest % 100 rest1 = rest / 10 # особый случай -- tens=1 tens = rest1 == 1 and TENS[rest] or TENS[rest1] words.append(tens) # единицы if rest1 < 1 or rest1 > 1: amount = rest % 10 end_word = choose_plural(amount, items) words.append(ONES[amount][gender-1]) words.append(end_word) # добавляем то, что уже было words.append(into) # убираем пустые подстроки words = filter(lambda x: len(x) > 0, words) # склеиваем и отдаем return u" ".join(words).strip(), tmp_val
@param amount: numeral @type amount: C{integer types} @param gender: gender (MALE, FEMALE or NEUTER) @type gender: C{int} @param gender: gender (NOMINATIVE or GENITIVE) @type gender: C{int} @return: in-words reprsentation of numeral @rtype: C{unicode} @raise ValueError: amount is negative """ check_positive(amount) return sum_string(amount, gender=gender, case=case) def in_words_float(amount, _gender=FEMALE): """ Float in words @param amount: float numeral @type amount: C{float} or C{Decimal} @return: in-words representation of float numeral @rtype: C{unicode} @raise ValueError: when amount is negative """
def _sum_string_fn(into, tmp_val, gender, items=None): """ Make in-words representation of single order @param into: in-words representation of lower orders @type into: C{unicode} @param tmp_val: temporary value without lower orders @type tmp_val: C{int} or C{long} @param gender: gender (MALE, FEMALE or NEUTER) @type gender: C{int} @param items: variants of objects @type items: 3-element C{sequence} of C{unicode} @return: new into and tmp_val @rtype: C{tuple} @raise L{pytils.err.InputParameterError}: input parameters' check failed @raise ValueError: tmp_val is negative """ if items is None: items = (u"", u"", u"") one_item, two_items, five_items = items check_positive(tmp_val) if tmp_val == 0: return into, tmp_val rest = rest1 = end_word = None words = [] rest = tmp_val % 1000 tmp_val = tmp_val / 1000 if rest == 0: # последние три знака нулевые if into == u"": into = u"%s " % five_items return into, tmp_val # начинаем подсчет с rest end_word = five_items # сотни words.append(HUNDREDS[rest / 100]) # десятки rest = rest % 100 rest1 = rest / 10 # особый случай -- tens=1 tens = rest1 == 1 and TENS[rest] or TENS[rest1] words.append(tens) # единицы if rest1 < 1 or rest1 > 1: amount = rest % 10 end_word = choose_plural(amount, items) words.append(ONES[amount][gender - 1]) words.append(end_word) # добавляем то, что уже было words.append(into) # убираем пустые подстроки words = filter(lambda x: len(x) > 0, words) # склеиваем и отдаем return u" ".join(words).strip(), tmp_val
def distance_of_time_in_words(from_time, accuracy=1, to_time=None): """ Represents distance of time in words @param from_time: source time (in seconds from epoch) @type from_time: C{int}, C{float} or C{datetime.datetime} @param accuracy: level of accuracy (1..3), default=1 @type accuracy: C{int} @param to_time: target time (in seconds from epoch), default=None translates to current time @type to_time: C{int}, C{float} or C{datetime.datetime} @return: distance of time in words @rtype: unicode @raise ValueError: accuracy is lesser or equal zero """ current = False if to_time is None: current = True to_time = datetime.datetime.now() check_positive(accuracy, strict=True) if not isinstance(from_time, datetime.datetime): from_time = datetime.datetime.fromtimestamp(from_time) if not isinstance(to_time, datetime.datetime): to_time = datetime.datetime.fromtimestamp(to_time) dt_delta = to_time - from_time difference = dt_delta.days * 86400 + dt_delta.seconds minutes_orig = int(abs(difference) / 60.0) hours_orig = int(abs(difference) / 3600.0) days_orig = int(abs(difference) / 86400.0) in_future = from_time > to_time words = [] values = [] alternatives = [] days = days_orig hours = hours_orig - days_orig * 24 words.append(u"%d %s" % (days, numeral.choose_plural(days, DAY_VARIANTS))) values.append(days) words.append(u"%d %s" % \ (hours, numeral.choose_plural(hours, HOUR_VARIANTS))) values.append(hours) days == 0 and hours == 1 and current and alternatives.append(u"годину") minutes = minutes_orig - hours_orig * 60 words.append(u"%d %s" % (minutes, numeral.choose_plural(minutes, MINUTE_VARIANTS))) values.append(minutes) days == 0 and hours == 0 and minutes == 1 and current and \ alternatives.append(u"хвилину") # убираем из values и words конечные нули while values and not values[-1]: values.pop() words.pop() # убираем из values и words начальные нули while values and not values[0]: values.pop(0) words.pop(0) limit = min(accuracy, len(words)) real_words = words[:limit] real_values = values[:limit] # снова убираем конечные нули while real_values and not real_values[-1]: real_values.pop() real_words.pop() limit -= 1 real_str = u" ".join(real_words) # альтернативные варианты нужны только если в real_words одно значение # и, вдобавок, если используется текущее время alter_str = limit == 1 and current and alternatives and \ alternatives[0] _result_str = alter_str or real_str result_str = in_future and u"%s %s" % (PREFIX_IN, _result_str) \ or u"%s %s" % (_result_str, SUFFIX_AGO) # если же прошло менее минуты, то real_words -- пустой, и поэтому # нужно брать alternatives[0], а не result_str zero_str = minutes == 0 and not real_words and \ (in_future and u"менше ніж через хвилину" \ or u"щойно") # нужно использовать вчера/позавчера/завтра/послезавтра # если days 1..2 и в real_words одно значение day_alternatives = DAY_ALTERNATIVES.get(days, False) alternate_day = day_alternatives and current and limit == 1 and \ ((in_future and day_alternatives[1]) \ or day_alternatives[0]) final_str = not real_words and zero_str or alternate_day or result_str return final_str