Формат времени для чтения (с хорошей грамматикой!)

#python #time #control-flow

#python #время #поток управления

Вопрос:

Я читал сообщения за сообщениями о методах преобразования входного количества секунд, которое должно выводиться в виде формальной строки с заданной длительностью (часы, минуты, секунды). Но я хочу знать, как отформатировать его так, чтобы он учитывал сингуляризацию / плюрализацию, когда я знаю, например, 62 что секунды должны читаться как "1 minute and 2 seconds" в отличие от 120 секунд, которые просто "2 minutes" .

Еще одним критерием является то, что он должен возвращаться "now" , если секунды есть 0 .

Вот мой код до сих пор:

 def format_duration(seconds, granularity = 2):
    intervals = (('hours', 3600), ('minutes', 60), ('seconds', 1))
    human_time = []
    for name, count in intervals: 
        value = seconds // count
        if value: 
            seconds -= value * count
            if value == 1:
                name = name.rstrip('s')
            human_time.append("{} {}".format(value, name))
        else:
            return "now"
    return ','.join(human_time[:granularity])
  

Пожалуйста, помогите! Спасибо!

MJ

Ответ №1:

Ваш код уже работает довольно хорошо, у вас есть только одна проблема, return "now" которую я исправил в приведенном ниже коде. Что еще вы хотите, чтобы ваш код делал?

 def prettyList(human_time):
    if len(human_time) > 1:
        return ' '.join([', '.join(human_time[:-1]), "and", human_time[-1]])
    elif len(human_time) == 1:
        return human_time[0]
    else:
        return ""

def format_duration(seconds, granularity = 2):
    intervals = (('hours', 3600), ('minutes', 60), ('seconds', 1))
    human_time = []
    for name, count in intervals: 
        value = seconds // count
        if value: 
            seconds -= value * count
            if value == 1:
                name = name.rstrip('s')
            human_time.append("{} {}".format(value, name))
    if not human_time:
        return "now"
    human_time = human_time[:granularity]
    return prettyList(human_time)
  

Редактировать: итак, я добавил функцию для улучшения вывода, последние термины в списке будут разделены символом «и», а все остальные — запятой. Это все равно будет работать, даже если вы добавите больше интервалов в свой код (например ('days', 86400) ). Вывод теперь выглядит как 2 hours, 1 minute and 43 seconds или 25 minutes and 14 seconds .

Комментарии:

1. это на самом деле очень полезно — в настоящее время в моем коде отсутствует правильное соединение «и», скажем, для примера, такого как «1 минута и 2 секунды». Я думаю, что это исправлено с помощью настройки строки 10, да? Есть идеи? «и» должно быть там в зависимости…

2. Я думаю, вам нужны «и» для последнего соединения и запятые для всего, что было раньше, чтобы оно выводило «2 часа, 1 минуту и 58 секунд»?

3. точно! и это, конечно, применимо и к более коротким временным интервалам, где я учитывал не часы, а только минуты и секунды. Есть идеи?

4. хорошо, учитывая текущий код, пользовательский интерфейс «1 минута и 2 секунды» в настоящее время отображается как «1 минута, 2 секунды» — очевидно, оператор join, который нуждается в настройке.

5. Это сделано. В качестве примечания, если вы хотите сделать свой вывод еще более «искусственным», есть num2words библиотека, которая может преобразовать ваши числа в обычный текст.

Ответ №2:

Внесены некоторые изменения для удобства чтения :

 def pretty_list(human_time):
    return human_time[0] if len(human_time) == 1 else ' '.join([', '.join(human_time[:-1]), "and", human_time[-1]])


def get_intervals(seconds):
    m, s = divmod(seconds, 60)
    h, m = divmod(m, 60)
    return (
        ("hour", h),
        ("minute", m),
        ("second", s)
    )


def format_duration(seconds, granularity=3):

    intervals = get_intervals(seconds)
    human_time = []
    for name, value in intervals:
        if value == 0:
            continue
        elif value == 1:
            human_time.append("{} {}".format(value, name))
        else:
            human_time.append("{} {}s".format(value, name))
    return (pretty_list(human_time[:granularity])) if len(human_time) != 0 else "now"
  

Комментарии:

1. Вы можете добавить часть pretty_list от @Efferalgan, чтобы получить лучшее из обоих миров!

2. мне очень нравится этот формат из-за его удобочитаемости, его гораздо легче усваивать строка за строкой, но он по-прежнему не учитывает «и», необходимые в примере, таком как «1 минута и 2 секунды»

3. Готово! 🙂 Разделил функцию тоже

Ответ №3:

Вы можете попробовать написать код для каждого варианта:

 def timestamp(ctime):
    sec = int(ctime)
    if sec == 0:
        return "Now"
    m, s = divmod(sec, 60)
    h, m = divmod(m, 60)
    if h == 1: hr_t = 'Hour'
    else: hr_t = 'Hours'
    if m == 1: mn_t = 'Minute'
    else: mn_t = 'Minutes'
    if s == 1: sc_t = 'Second'
    else: sc_t = 'Seconds'
    time_stamp = ""
    if h > 0 and m ==0 and s ==0:
        time_stamp = "d %s " % (h, hr_t)
    elif h > 0:
        time_stamp = "d %s, " % (h, hr_t)
    if m > 0 and s !=0:
        time_stamp = time_stamp  "d %s and d %s" % (m, mn_t, s, sc_t)
    elif m > 0 and s == 0:
        time_stamp = time_stamp  "d %s" % (m, mn_t)
    elif m == 0  and s != 0:
        time_stamp = time_stamp  "d %s" % (s, sc_t)
    return time_stamp
print (timestamp(11024))
print (timestamp(0))
print (timestamp(31))
print (timestamp(102))
print (timestamp(61))
print (timestamp(60))
print (timestamp(3600))
print (timestamp(3632))
03 Hours, 03 Minutes and 44 Seconds
Now
31 Seconds
01 Minute and 42 Seconds
01 Minute and 01 Second
01 Minute
01 Hour 
01 Hour, 32 Seconds
  

Или вы можете использовать relativedelta опцию in dateutil , а затем извлечь из нее кости.

 from dateutil.relativedelta import relativedelta
attrs = ['years', 'months', 'days', 'hours', 'minutes', 'seconds']
human_readable = lambda delta: ['%d %s ' % (getattr(delta, attr), getattr(delta, attr) != 1 and attr   or attr[:-1]) for attr in attrs if getattr(delta, attr) or attr == attrs[-1]]
readable=''
for i in human_readable(relativedelta(seconds=1113600)):
    readable  = i
print readable
print human_readable(relativedelta(seconds=13600))
print human_readable(relativedelta(seconds=36))
print human_readable(relativedelta(seconds=60))
print human_readable(relativedelta(seconds=3600))
12 days 21 hours 20 minutes 0 seconds 
['3 hours ', '46 minutes ', '40 seconds ']
['36 seconds ']
['1 minute ', '0 seconds ']
['1 hour ', '0 seconds ']
  

Дополнительные примеры второго примера см.: http://code.activestate.com/recipes/578113-human-readable-format-for-a-given-time-delta/
именно оттуда я украл почти весь второй набор кода

Комментарии:

1. Я собирался опубликовать что-то в этом роде. Спасибо, что избавили меня от исследований 🙂

Ответ №4:

Я нашел это! На самом деле мне нужно было больше интервалов — запрошенные длительности были длиннее, чем я думал…

 def prettyList(human_time):
    if len(human_time) > 1:
        return ' '.join([', '.join(human_time[:-1]), "and", human_time[-1]])
    elif len(human_time) == 1:
        return human_time[0]
    else:
        return ""

def format_duration(seconds, granularity = 4):
    intervals = (('years', 29030400), ('months', 2419200), ('weeks', 604800),('days', 86400),('hours', 3600), ('minutes', 60), ('seconds', 1))
    human_time = []
    for name, count in intervals: 
        value = seconds // count
        if value: 
            seconds -= value * count
            if value == 1:
                name = name.rstrip('s')
            human_time.append("{} {}".format(value, name))
    if not human_time:
        return "now"
    human_time = human_time[:granularity]
    return prettyList(human_time)
  

Комментарии:

1. упс! теперь в нем говорится, что «6 месяцев, 2 недели, 1 час и 44 минуты» должны равняться «182 дням, 1 часу, 44 минутам и 40 секундам» …… есть идеи?

2. Что вы имеете в виду? Вы вводите количество секунд, соответствующее 6 месяцам, И выводите «182 дня и т. Д.»?

3. по какой-то причине теперь запрашиваются дни, часы, минуты и секунды.

4. @Efferalgan есть мысли?

5. Я думаю, что ваша проблема в том, что месяц — это не фиксированный промежуток времени: он может длиться 30, 31, 28 или 29 дней.

Ответ №5:

Я думаю, что я рассмотрел здесь все основы, но я уверен, что кто-нибудь сообщит мне, если я допустил ошибку (большую или маленькую) 🙂

 from dateutil.relativedelta import relativedelta
def convertdate(secs):
    raw_date = relativedelta(seconds=secs)
    years, days = divmod(raw_date.days, 365) # To crudely cater for leap years / 365.2425
    hours = raw_date.hours
    minutes = raw_date.minutes
    seconds = raw_date.seconds
    full = [years,days,hours,minutes,seconds]
    date_text=['','','','','']
    if years == 1: date_text[0] = "Year"
    else: date_text[0] = "Years"
    if days == 1: date_text[1] = "Day"
    else: date_text[1] = "Days"
    if hours == 1: date_text[2] = "Hour"
    else: date_text[2] = "Hours"
    if minutes == 1: date_text[3] = "Minute"
    else: date_text[3] = "Minutes"
    if seconds == 1: date_text[4] = "Second"
    else: date_text[4] = "Seconds"
    first_pos = 0
    final_pos = 0
    element_count = 0
    # Find the first and final set positions and the number of returned values
    for i in range(5):
        if full[i] != 0:
            final_pos = i
            element_count  =1
            if first_pos == 0:
                first_pos = i
    # Return "now" and any single value
    if element_count == 0:
        return "Now"
    if element_count == 1:
        return "d %s" % (full[final_pos],date_text[final_pos])
    # Initially define the separators
    separators=['','','','','']
    ret_str=''
    for i in range(4):
        if full[i] != 0:
            separators[i] = ', '
    separators[final_pos] = ''
    # Redefine the final separator
    for i in range(4,-1,-1):
        if separators[i] == ', ':
            separators[i] = ' and '
            break
    #Build the readable formatted time string
    for i in range(5):
        if full[i] != 0:
            ret_str  = "d %s%s" % (full[i],date_text[i],separators[i])
    return ret_str
print convertdate(1111113601)
print convertdate(1111113635)
print convertdate(1111113600)
print convertdate(1111111200)
print convertdate(1111104000)
print convertdate(1111104005)
print convertdate(11113240)
print convertdate(11059240)
print convertdate(11113600)
print convertdate(36)
print convertdate(60)
print convertdate(61)
print convertdate(121)
print convertdate(122)
print convertdate(120)
print convertdate(3600)
print convertdate(3601)

35 Years, 85 Days, 02 Hours, 40 Minutes and 01 Second
35 Years, 85 Days, 02 Hours, 40 Minutes and 35 Seconds
35 Years, 85 Days, 02 Hours and 40 Minutes
35 Years, 85 Days and 02 Hours
35 Years and 85 Days
35 Years, 85 Days and 05 Seconds
128 Days, 15 Hours and 40 Seconds
128 Days and 40 Seconds
128 Days, 15 Hours, 06 Minutes and 40 Seconds
36 Seconds
01 Minute
01 Minute and 01 Second
02 Minutes and 01 Second
02 Minutes and 02 Seconds
02 Minutes
01 Hour
01 Hour and 01 Second