Welcome, guest | Sign In | My Account | Store | Cart

A small contribution to the developer community.

This module caters to the need of developers who want to put date & time of post in terms like
"X days, Y hrs ago", "A hours B mins ago", etc. in their applications rather then a basic timestamp
like "2009-08-15 03:03:00". Additionally it also
provides since epoch for a given datetime.

It takes in a Python datetime object as an input
and provides a fancy datetime (as I call it) and
the seconds since epoch.

Python, 150 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
"""::::LICENSE::::
Copyright 2009 Jai Vikram Singh Verma (jaivikram[dot]verma[at]gmail[dot]com)

Licensed under the Apache License, Version 2.0 (the "License"); 
you may not use this file except in compliance with the License. 
You may obtain a copy of the License at 

http://www.apache.org/licenses/LICENSE-2.0 

Unless required by applicable law or agreed to in writing, 
software distributed under the License is distributed on an 
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 
either express or implied. 
See the License for the specific language governing permissions 
and limitations under the License. 
"""

"""Module: parsedatetime

This module caters to the need of developers who
want to put time of post in terms on 
"X days, Y hrs ago", "A hours B mins ago", etc. 
in their applications rather then a basic timestamp
like "2009-08-15 03:03:00". Additionally it also 
provides since epoch for a given datetime.

It takes in a python datetime object as an input 
and provides a fancy datetime (as I call it) and
the seconds since epoch.

::::DISCLAIMER::::
1.  This module was written for my needs of this
    kind of datetime representation and it works just fine for me.
    If you feel there are imperfections, problems,
    etc. feel free to fix and publish, or bring them to my
    notice at the above mentioned email, I shall fix it.

2.  It does not take into consideration 31 day-month and  
    30 day-month, it jus uses 30 as a standard month-duration. 
    For the simple reason that if the developer
    wants to represent datetime as "X months, Y days ago" then 
    the relative importance of 30 or 31 is insignificant.

3.  It does not take leap years into consideration, again for the
    same reason when the representation is "X year, Y months ago"
    the relative importance of 365 or 366 is insignificant.

4. Again, in any given case it provides only two most significant 
   durations of time. e.g.: "1 year, 3 months, 2 days, 
   12 hours, 2 minutes ago" does not make sense, 'coz the gap is 
   large (in years), so the things beyond days (in this case) do
   not make much sense, hence the output shall be
   "1 year, 3 months ago".

Use, resuse, modify, contribute-back, have Fun!!
"""

import datetime
import time
import logging
logging.basicConfig(level = logging.DEBUG)
log = logging.getLogger('parsedatetime')

def makeEpochTime(date_time):
    """
    provides the seconds since epoch give a python datetime object.
    
    @param date_time: Python datetime object

    @return:
        seconds_since_epoch:: int 
    """
    date_time = date_time.isoformat().split('.')[0].replace('T',' ')
    #'2009-07-04 18:30:47'
    pattern = '%Y-%m-%d %H:%M:%S'
    seconds_since_epoch = int(time.mktime(time.strptime(date_time, pattern)))
    return seconds_since_epoch 

def convertToHumanReadable(date_time):
    """
    converts a python datetime object to the 
    format "X days, Y hours ago"

    @param date_time: Python datetime object

    @return:
        fancy datetime:: string
    """
    current_datetime = datetime.datetime.now()
    delta = str(current_datetime - date_time)
    if delta.find(',') > 0:
        days, hours = delta.split(',')
        days = int(days.split()[0].strip())
        hours, minutes = hours.split(':')[0:2]
    else:
        hours, minutes = delta.split(':')[0:2]
        days = 0
    days, hours, minutes = int(days), int(hours), int(minutes)
    datelets =[]
    years, months, xdays = None, None, None
    plural = lambda x: 's' if x!=1 else ''
    if days >= 365:
        years = int(days/365)
        datelets.append('%d year%s' % (years, plural(years)))
        days = days%365
    if days >= 30 and days < 365:
        months = int(days/30)
        datelets.append('%d month%s' % (months, plural(months)))        
        days = days%30
    if not years and days > 0 and days < 30:
        xdays =days
        datelets.append('%d day%s' % (xdays, plural(xdays)))        
    if not (months or years) and hours != 0:
        datelets.append('%d hour%s' % (hours, plural(hours)))        
    if not (xdays or months or years):
        datelets.append('%d minute%s' % (minutes, plural(minutes)))        
    return ', '.join(datelets) + ' ago.'
    

def makeFancyDatetime(req_datetime):
    """
    a consolidate method to provide a nice output 
    taken from the other two methods as a dictionary,
    easily convertible to json.
    
    @param req_datetime: python datetime object
    
    @return:
        Python dictionay object with two key, value pairs
        representing 'fancy_datetime' and 'seconds_since_epoch'
    """
    return {'fancy_datetime': convertToHumanReadable(req_datetime), 
            'seconds_since_epoch': makeEpochTime(req_datetime)
            }

def test():
    """
    a small set of tests
    """
    bkwd_date = lambda x: datetime.datetime.now()-datetime.timedelta(seconds = x)
    siad = 60*60*24
    xs = [456, 365, 232, 23, 12.5, 0.5, 0.3]
    for x in xs:
        req_datetime = bkwd_date(siad*x)
        log.info("\nINPUT:  %s\nOutput:  %s\n*********" % \
                     (str(req_datetime), str(makeFancyDatetime(req_datetime))))


if __name__ == '__main__':
    test()

2 comments

Charlie Clark 12 years, 3 months ago  # | flag

My first comment would be that you should look at mx.DateTime which provides wonderful access to all the underlying C libraries that know about dates or the dateutil.

But your assumptions about months and years are reasonable for the scope. But I don't like the use of a formatted string to get at the elements of the datetime.delta object as this already exposes attributes days and seconds. I think this inflates and obscures the code unnecessarily. Another obfuscation in my view is your use of lambdas. I prefer to avoid lambdas in code as they scan differently. If they are used then I prefer to see them as the throwaway function they represent. As Python lets you embed functions in each other there is no reason in my view for plurals or bkwd_date not to be defined as regular functions. Anyway in my implementation I've done without them entirely. I've also reduced the number of hard-coded if statements by using instance attributes and Python's introspection, I suspect this has a negligible effect on the performance (profiling suggests that my attribut lookups are about 10% faster than your string splitting and searching) but it does make the code easier to maintain. Date formatting is something you probably want to delegate to the locale library.

class FancyDateTimeDelta(object):
    """
    Format the date / time difference between the supplied date and
    the current time using approximate measurement boundaries
    """

    def __init__(self, dt):
        now = datetime.datetime.now()
        delta = now - dt
        self.year = delta.days / 365
        self.month = delta.days / 30 - (12 * self.year)
        if self.year > 0:
            self.day = 0
        else: 
            self.day = delta.days % 30
        self.hour = delta.seconds / 3600
        self.minute = delta.seconds / 60 - (60 * self.hour)

    def format(self):
        fmt = []
        for period in ['year', 'month', 'day', 'hour', 'minute']:
            value = getattr(self, period)
            if value:
                if value > 1:
                    period += "s"
                fmt.append("%s %s" % (value, period))
        return ", ".join(fmt) + " ago"
Jai Vikram Singh Verma (author) 12 years, 3 months ago  # | flag

Thanks Charlie

For the two cents.

Its just that I was too "lazy" to do the "beautiful" things once the code was done and working, you can call it negligence if you want to, no issues. :)

About the delta properties, point taken.

About use of lambda, the methods are too short, so using a nested function or lambda is just a matter of style/preference.

About introspection, I use it very often, just missed it here, I can see how bad the if's are looking now.:(

But yes, your code looks "pretty".

I think I will overcome the laziness and be careful about beautiful-code in future postings.

JV