29

I'm still a bit slow with Python, so I haven't got this figured out beyond what's obviously in the docs, etc.

I've worked with Django a bit, where they've added some datetime formatting options via template tags, but in regular python code how can I get the 12-hour hour without a leading zero?

Is there a straightforward way to do this? I'm looking at the 2.5 and 2.6 docs for "strftime()" and there doesn't seem to be a formatting option there for this case.

Should I be using something else?

Feel free to include any other time-formatting tips that aren't obvious from the docs. =)

anonymous coward
  • 12,594
  • 13
  • 55
  • 97

8 Answers8

22

Nothing built-in to datetime will do it. You'll need to use something like:

datetime.time(1).strftime('%I:%M%p').lstrip('0')

Addendum

As @naktinis points out, this is tailored to the use of this particular strftime parameter. Unfortunately, there is no generic solution if the content of the strftime parameter is unknown or unspecified (e.g. an external parameter), because it becomes a "do what I mean, not what I say" problem.

Thus, given that you have to know what's in your strftime parameter, in a more complex case you could solve this as parts:

tval = datetime.time(1)
tval_str = (tval.strftime('%A, %B ') + tval.strftime('%d').lstrip('0') 
    + tval.strftime(' %Y, ') + tval.strftime('%I:%M').lstrip('0') 
    + tval.strftime('%p').lower())

or with the re module:

tval = datetime.time(1)
tval_str = re.sub(r"^0|(?<=\s)0", "", 
    re.sub(r"(?<=[0-9])[AP]M", lambda m: m.group().lower(), 
    tval.strftime('%A, %B %d %Y, %I:%M%p')))

That said, bear in mind that if the "%p" term gives you uppercase letters, it may be because the user set their locale to work that way, and by changing case you are overriding user preferences, which sometimes leads to bug reports. Also, the user may want something other than "am" or "pm", such as "a.m." and "p.m.". Also note that these are different for different locales (e.g. en_US locale gives AM or PM for %p, but de_DE gives am or pm) and you might not be getting characters in the encoding you assume.

From the documentation on strftime behavior:

Because the format depends on the current locale, care should be taken when making assumptions about the output value. Field orderings will vary (for example, “month/day/year” versus “day/month/year”), and the output may contain Unicode characters encoded using the locale’s default encoding (for example, if the current locale is js_JP, the default encoding could be any one of eucJP, SJIS, or utf-8; use locale.getlocale() to determine the current locale’s encoding).

So, in short, if you think you need to override locale settings, make sure you have a good reason why, so you don't just end up creating new bugs.

Mike DeSimone
  • 41,631
  • 10
  • 72
  • 96
15

This question has already been answered but you can technically get "2:35pm" directly from a Python datetime with .strftime("%-I:%M%P") on linux platforms that use glibc because Python's strftime() uses the c library's strftime().

>>> import datetime
>>> now = datetime.datetime.now()
datetime.datetime(2012, 9, 18, 15, 0, 30, 385186)
>>> now.strftime("%-I:%M%P")
'3:00pm'
>>> datetime.time(14, 35).strftime("%-I:%M%P")
'2:35pm'

See strftime glibc notes on "-".

Uyghur Lives Matter
  • 18,820
  • 42
  • 108
  • 144
  • 3
    This is the correct answer. A note for anyone doing this: strptime() (the reverse) doesn't need the -, it will already handle both padded and non-padded numbers and will actually complain if you give it the %-I, so if you also have to strptime() in the same format, don't store and reuse the same format string with the %-I, use %I – Purrell Aug 02 '13 at 05:12
  • Yes! How is this not the accepted answer? I came here hoping to remove the padding (both space- AND zero-padding) from the XFCE clock widget, and this does the trick! – JamesTheAwesomeDude Feb 21 '16 at 20:00
  • 1
    This is the correct answer **if the code is running on a Linux platform**. If there's any risk of the code running elsewhere, or if the code needs to be portable, then this isn't the answer. – Alex Peters Jan 10 '18 at 00:13
  • 1
    It's a capital `P` for those having trouble spotting the difference. – mpen Dec 10 '19 at 19:26
  • 1
    To amend @AlexPeters' comment: this is the correct answer **if the code is running on a GNU platform** (e.g., most mainstream Linux distros). It won't run on certain embedded or otherwise constrained Linux systems running something like `musl` or `uClibc-ng`. The `%-I` trick is a non-standard (or, rather, _de facto_ standard) extension implemented by, as the answer notes, `glibc` only. – JamesTheAwesomeDude Jun 18 '20 at 15:27
7

While I'm partial to Mike DeSimone's answer, for voting purposes I think this might be a worthwhile contribution...

The Django project contains a "PHP Compatible" date formatting class in django/utils/dateformat.py (trunk). It's used like so (shell example):

>>> import datetime
>>> from django.utils.dateformat import DateFormat
>>> d = datetime.datetime.now()
>>> df =  DateFormat(d)
>>> df.format('g:ia') # Format for Hour-no-leading-0, minutes, lowercase 'AM/PM'
u'9:10a.m.'

It fulfills the requirement here, and may be worth including in your project. With that, I'll say that you should verify the license permits such use... Any comments to clarify are welcome.

Community
  • 1
  • 1
anonymous coward
  • 12,594
  • 13
  • 55
  • 97
  • good answer. though sadly it's not exactly PHP compatible -- and doesn't include a `pm` (just `p.m.`) – philfreo Feb 01 '12 at 03:47
5

Python's strftime() is only guaranteed to support the format codes from C89 (see the list).

Useful additional format codes are defined by other standards (see the Linux man page for strftime()). For this question, the relevant additional codes are:

  • %l — The hour (12-hour clock) as a decimal number (range 1 to 12).
  • %P — Like %p but in lowercase: "am" or "pm" or a corresponding string for the current locale.

I like to use a wrapper function around strftime() to implement additional codes:

def extendedStrftime(dt, format):

    day  = dt.strftime('%d').lstrip('0')
    hour = dt.strftime('%I').lstrip('0')
    ampm = dt.strftime('%p').lower()

    format = format.replace('%e', day)
    format = format.replace('%l', hour)
    format = format.replace('%P', ampm)

    return dt.strftime(format)

To get an output in the format "2:35pm":

extendedStrftime(dt, '%l:%M%P')

It's also very common to want to format the day without the leading zero, which can be done with the additional code %e:

extendedStrftime(dt, '%e %b %Y')  # "1 Jan 2015"
Community
  • 1
  • 1
TachyonVortex
  • 8,242
  • 3
  • 48
  • 63
  • Very good for the lowercase ampm but strftime supports using - for removing the leading 0. Check http://strftime.org/ for a good reference. – Peter Graham Mar 03 '16 at 17:38
  • 1
    @PeterGraham: As [strftime.org](http://strftime.org/) mentions, the use of `-` for removing zero-padding is platform-specific. For example, it doesn't work on Windows (see [here](http://stackoverflow.com/a/2073189)). As I mentioned in my answer, Python's `strftime()` is only guaranteed to support the format codes from C89. That's why I implemented `%e` and `%l` in my answer, to provide a cross-platform solution. – TachyonVortex Apr 06 '16 at 14:13
2

datetime.time objects expose the hour, minute and second fields. Making your own formatting with these is pretty trivial. Something like this:

return "%d:%02d %s" % (foo.hour % 12 + 0 if foo.hour % 12 else 12, #ugh
                       foo.minute,
                       "pm" if foo.hour >= 12 else "am")
badp
  • 11,409
  • 3
  • 61
  • 89
  • Don't downvote me if I got the AM/PM bit wrong, I never use this notation :) – badp May 27 '10 at 22:16
  • 1
    You got the AM/PM thing right, but for the first format parameter, if `foo.hour` is 0 or 12, the output hour should be `12`, not `0`. Things like this make me hate 12-hour time. – Mike DeSimone May 27 '10 at 23:10
  • Would you like `(foo.hour - 1) % 12 + 1` better? They both look ugly to me. – Mike DeSimone May 28 '10 at 10:54
  • Sorry about the 12-hour time bit. It is a hassle, but in particular this is for the lower level humans. I agree that it s-u-x. – anonymous coward May 28 '10 at 13:43
2

Use %l to get the hour as a number between 1..12:

In [2]: datetime.time(hour=14,minute=35).strftime('%l:%M%p')
Out[2]: ' 2:35PM'

For more format codes, see http://au2.php.net/strftime.

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • 1
    I get `':35PM'` on Windows with CPython 2.5.2. It does work on Cygwin though (also 2.5.2). – Brian Neal May 28 '10 at 02:14
  • I think I'd heard of that, and while I didn't specify that the solution should be cross platform - that certainly would be great. So this leaves a space at the beginning? – anonymous coward May 28 '10 at 13:45
1

I know it's pretty cheap, but you could just discard the first character if it's a zero :)

genpfault
  • 51,148
  • 11
  • 85
  • 139
0

A little hack that I've used:

# a time object
mytime = time(hour=time_hour, minute=time_minute)
# return a time as a string without a leading zero in hours.
return "%s:%s" % (mytime.hour, mytime.strftime("%M"))
niko.makela
  • 537
  • 3
  • 14