32

I would like a python datetime object to output (and use the result in django) like this:

Thu the 2nd at 4:30

But I find no way in python to output st, nd, rd, or th like I can with PHP datetime format with the S string (What they call "English Ordinal Suffix") (http://uk.php.net/manual/en/function.date.php).

Is there a built-in way to do this in django/python? strftime isn't good enough (http://docs.python.org/library/datetime.html#strftime-strptime-behavior).

Django has a filter which does what I want, but I want a function, not a filter, to do what I want. Either a django or python function will be fine.

CoatedMoose
  • 3,624
  • 1
  • 20
  • 36
Alexander Bird
  • 38,679
  • 42
  • 124
  • 159
  • If you don't find a ready-made solution, there are some good ones here you could convert: http://stackoverflow.com/questions/165713/how-do-i-provide-a-suffix-for-days-of-the-month – paxdiablo Sep 05 '10 at 00:16

8 Answers8

34

The django.utils.dateformat has a function format that takes two arguments, the first one being the date (a datetime.date [[or datetime.datetime]] instance, where datetime is the module in Python's standard library), the second one being the format string, and returns the resulting formatted string. The uppercase-S format item (if part of the format string, of course) is the one that expands to the proper one of 'st', 'nd', 'rd' or 'th', depending on the day-of-month of the date in question.

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • 1
    This could use more detail. For example you can use DateFormat(max_date).format("jS M, Y") which would format max_date as "1st May, 2019" – Scott Warren May 23 '19 at 07:20
26

dont know about built in but I used this...

def ord(n):
    return str(n)+("th" if 4<=n%100<=20 else {1:"st",2:"nd",3:"rd"}.get(n%10, "th"))

and:

def dtStylish(dt,f):
    return dt.strftime(f).replace("{th}", ord(dt.day))

dtStylish can be called as follows to get Thu the 2nd at 4:30. Use {th} where you want to put the day of the month ("%d" python format code)

dtStylish(datetime(2019, 5, 2, 16, 30), '%a the {th} at %I:%M')
Arvind Sridharan
  • 3,885
  • 4
  • 29
  • 54
Frosty Snowman
  • 435
  • 5
  • 5
  • 8
    @Arijoon That part covers the 'teens, which don't follow the same rules as everything else. If you remove it, you'll get `11st` or `13rd` when they should be `11th` and `13th`. – Status Oct 03 '15 at 21:58
12

You can do this simply by using the humanize library

from django.contrib.humanize.templatetags.humanize import ordinal

You can then just give ordinal any integer, ie

ordinal(2) will return 2nd

Adrian Krige
  • 121
  • 1
  • 2
9

I've just written a small function to solve the same problem within my own code:

def foo(myDate):
    date_suffix = ["th", "st", "nd", "rd"]

    if myDate % 10 in [1, 2, 3] and myDate not in [11, 12, 13]:
        return date_suffix[myDate % 10]
    else:
        return date_suffix[0]
Edster
  • 143
  • 2
  • 8
1

My own version. Use python's strftime's format codes, substituting {th} where you want to see the ordinal suffix.

def format_date_with_ordinal(d, format_string):
    ordinal = {'1':'st', '2':'nd', '3':'rd'}.get(str(d.day)[-1:], 'th')
    return d.strftime(format_string).replace('{th}', ordinal)

or

format_date_with_ordinal = lambda d,f: d.strftime(f).replace('{th}', {'1':'st', '2':'nd', '3':'rd'}.get(str(d.day)[-1:], 'th'))
jobu1342
  • 150
  • 7
1

Here's something that I use:

def get_ordinal_suffix(day: int) -> str:
    return {1: 'st', 2: 'nd', 3: 'rd'}.get(day % 10, 'th') if day not in (11, 12, 13) else 'th'
tschmitz
  • 116
  • 4
0

Implementing @Frosty Snomwan's Method And An Expansion Of It

When I first tried to implement Frosty Snowman's outstanding answer, I misapplied it in my haste. His is the ord2 function below. My "not as good function" is ord1. He passes in a number. Mine takes a string. Both could be easily edited to receive different input types. I will be using his function, but I thought some codes might find the full "experimental" implementation of both helpful.

from datetime import datetime


def ord1(n):
    if 4 <= int(n) <= 20 or int(n[-1]) >= 4 or int(n[-1]) == 0:
        return f"{n}th"
    elif int(n[-1]) == 1:
        return f"{n}st"
    elif int(n[-1]) == 2:
        return f"{n}nd"
    elif int(n[-1]) == 3:
        return f"{n}rd"


def ord2(n):
    return str(n) + (
        "th" if 4 <= n % 100 <= 20 else {1: "st", 2: "nd", 3: "rd"}.get(n % 10, "th")
    )


today = datetime.now()
date = today.strftime("%d")
date = ord2(int(date))
full_date_time = today.strftime(f"%A, {date} %B %Y %I:%M:%S %p")
print(full_date_time)

for date in range(1, 32, 1):
    date_ord1 = ord1(str(date))
    date_ord2 = ord2(date)
    print(date_ord1, date_ord2)

Output is

Monday, 21st August 2023 01:33:11 PM
1st 1st
2nd 2nd
3rd 3rd
4th 4th
5th 5th
... all th's ...
20th 20th
21st 21st
22nd 22nd
23rd 23rd
24th 24th
... all th's ...
30th 30th
31st 31st

I final functional form that might be more overall useful would be ...

def format_date_time(a_date_time):
    date = a_date_time.strftime("%d")
    d = int(date)
    date = date + (
        "th" if 4 <= d % 100 <= 20 else {1: "st", 2: "nd", 3: "rd"}.get(d % 10, "th")
    )
    full_date_time = a_date_time.strftime(f"%A, {date} %B %Y %I:%M:%S %p")
    
    return full_date_time
Thom Ives
  • 3,642
  • 3
  • 30
  • 29
-2

Following solution I got for above problem:

datetime.strptime(mydate, '%dnd %B %Y')
datetime.strptime(mydate, '%dst %B %Y')
datetime.strptime(mydate, '%dth %B %Y')
datetime.strptime(mydate, '%drd %B %Y')
Til
  • 5,150
  • 13
  • 26
  • 34
eegloo
  • 161
  • 2
  • 9