4

I have been trying to make a function that lists the time between dates using the correct words, in particular I want it to account for plural. I know I can do this by running all the numbers through and if statement, but I really was hoping to find a more efficient way to accomplish my goal.

IE: I want 221 days, 1:17:06.996068 from datetime to be returned as 221 days, 1 hour, 17 minutes, and 6.99 seconds. With it being important it says day/hour/minute/second or days/hours/minutes/seconds when appropriate.

Example snippet of my code to work with:

from datetime import datetime as dt

now = dt.now(tz=None) # Current local computer time
test = dt(2018,3,25,23,0,0) # March 25th, 2018 23h 0m 0s
countdown = test - now
print(countdown) # 2 days, 1:45:00.685739
# Desired outcome: 2 days, 1 hour, 45 minutes, 0.68 seconds.

Is there any kind of function that labels between singular or plural time that efficiently?

Olivier Melançon
  • 21,584
  • 4
  • 41
  • 73
Vale
  • 1,003
  • 1
  • 9
  • 22
  • 1
    Maybe this can help more than anything: https://docs.python.org/2/library/datetime.html – R.R.C. Mar 24 '18 at 04:30
  • 1
    @DyZ not a duplicate. I know how to format it into hours, minutes, etc.. The issue is with making it account for singular and plural forms efficiently. @ Doug Thanks, I think you're correct. Unfortunately that is what I have been digging through for the last couple of hours already and I still haven't found my answer. :( – Vale Mar 24 '18 at 04:33

3 Answers3

2

In case you cannot find a better solution, you can write a "pluralizing" function:

def pl(number,unit):
    return unit + "s" if number > 1 or isinstance(number, float) else unit 

pl(10, "hour")
#'hours'
pl(.75, "sec")
#'secs'

It will at least hide the ifs from the readers's eyes.

DYZ
  • 55,249
  • 10
  • 64
  • 93
1

You can write a pluralize and then build on this answer.

from datetime import datetime as dt
from collections import OrderedDict

def pluralize(amount, unit):
    if amount != 1:
        unit += 's'
    return '{} {}'.format(amount, unit)

def format_delta(tdelta):
    d = OrderedDict()

    d["day"] = tdelta.days
    d["hour"], rem = divmod(tdelta.seconds, 3600)
    d["minute"], d["second"] = divmod(rem, 60)

    return ', '.join(pluralize(v, k) for k, v in d.items() if v != 0)

now = dt.now(tz=None) # Current local computer time
test = dt(2018,3,25,23,0,0) # March 25th, 2018 23h 0m 0s
countdown = test - now

format_delta(countdown) # '1 day, 22 hours, 13 minutes, 1 second'

I had to do a bit of search to figure out what were the rules for pluralizing decimal amounts and and I found an answer on English Language StackEchange.

Plural exceptions

A little bit of generality. For pluralizing nouns that are exceptions, you would have to write a translation table in the form of a dict.

plurals = {
    'maximum': 'maxima',
    'index': 'indices',
    'bar': 'baz'
}

def pluralize(amount, unit):
    if amount != 1:
        unit = plurals.get(unit, unit + 's')
    return '{} {}'.format(amount, unit)

pluralize(2, 'maximum') # '2 maxima'
pluralize(2, 'cake') # '2 cakes'
Olivier Melançon
  • 21,584
  • 4
  • 41
  • 73
1

For this particular usecase since the words are fairly easy to pluralize, you could use a straightforward pluralize function e.g:

def pluralize(s, count):
  if count > 1 or count == 0 or count < -1:
    return s + "s"
  else:
    return s

Then the key insight is to realize that you have two lists of the same size: the first being the set of data, the second being a set of labels for the data. This is a perfect use-case for the zip() combinator! This combinator takes two lists (e.g: [1,2,3], [4,5,6]) and combines them together into one list of pairs (e.g: [1,4], [2,5], [3,6].)

xs = [1, 12, 1, 46]
ys = ["day", "hour", "minute", "second"]
zs = map(lambda x: [x[0], pluralize(x[1], x[0])], zip(xs,ys))

Then in zs you end up with:

> print(list(zs))
> [[1, 'day'], [12, 'hours'], [1, 'minute'], [46, 'seconds']]

Which you can use to build the user-presentable string.

Robbie
  • 715
  • 9
  • 19