Все программы на *nix системах (не только python) стартуют, используя C (POSIX) locale до тех пор пока они не вызовут setlocale(LC_ALL, ''), чтобы активировать пользовательскую locale.
Если не хотите locale активировать для процесса, то можно использовать библиотеки, которые явно позволяют язык выбрать, такие как PyICU (пример использования)—есть полегче библиотеки, которые могут работать для конкретной задачи, например humanize:
>>> import datetime
>>> import humanize # $ pip install humanize
>>> _ = humanize.i18n.activate('ru_RU')
>>> humanize.naturaltime(datetime.datetime.now())
'сейчас'
В: но почему же
locale.getlocale()выдаётru_RU?
Я бы также ожидал, что-нибудь вроде (None, None) вместо ('ru_RU', 'UTF-8') до того как setlocale(LC_ALL, '') вызвано в программе. Из документации: "Returns the current setting for the given locale category" не очевидно, что пользовательские настройки из переменных окружения (LC_*, LANG) читаются, а не текущая локаль по умолчанию ("The POSIX locale is the default global locale at entry to main()") возвращается до вызова setlocale(LC_ALL, '').
locale модуль это тонкая обёртка вокруг соответствующей C функциональности, в частности, поведение может зависеть от платформы. Текущая реализация locale.getlocale() эквивалентна C setlocale(category, NULL), которая запрашивает текущую настройку для локали ("query the current global locale setting").
Почему "the current global locale setting" не равна "the default global locale" до вызова setlocale(LC_ALL, '') я не понимаю. Может дело в слове setting vs. locale, то есть текущая настройка до вызова setlocale(LC_ALL, '') берётся из переменных окружения (LC_*, etc), а текущая локаль до вызова setlocale(LC_ALL, ''), используемая strftime(), является C (как и положено). Но это чистая спекуляция с моей стороны.
Quick fix
Got it! янв has to be lower-case in CPython 2.7.12. Code (works in CPy 2.7.12 and CPy 3.4.5 on cygwin):
# coding=utf8
#timeData='[ 24-Янв-17 07:24 ]'
timeData='[ 24-янв-17 07:24 ]' ### lower-case
import datetime
import locale
locale.setlocale(locale.LC_TIME, 'ru_RU.UTF-8')
result = datetime.datetime.strptime(timeData, u'[ %d-%b-%y %H:%M ]')
print(result)
result:
2017-01-24 07:24:00
If I use the upper-case Янв, it works in Py 3, but in Py 2 it gives
ValueError: time data '[ 24-\xd0\xaf\xd0\xbd\xd0\xb2-17 07:24 ]' does not match format '[ %d-%b-%y %H:%M ]'
General case
To handle this in general in Python 2, lower-case first (see this answer):
# coding=utf8
timeData=u'[ 24-Янв-17 07:24 ]'
# ^ unicode data
import datetime
import locale
locale.setlocale(locale.LC_TIME, 'ru_RU.UTF-8')
print(timeData.lower()) # works OK
result = datetime.datetime.strptime(
timeData.lower().encode('utf8'), u'[ %d-%b-%y %H:%M ]')
## ^^^^^^^^^^^^^^ back to a string
## ^^^^^^^ lowercase
print(result)
Result:
[ 24-янв-17 07:24 ]
2017-01-24 07:24:00
I can't test it with your beautifulsoup code, but, in general, get Unicode data and then use the above.
Or, if at all possible, switch to Python 3 :) .
Explanation
So how did I figure this out? I went looking in the CPython source for the code to strptime (search). I found the handy _strptime module, containing class LocaleTime. I also found a mention of LocaleTime. To print the available month names, do this (added on to the end of the code under "Quick fix," above):
from _strptime import LocaleTime
lt = LocaleTime()
print(lt.a_month)
a_month has the abbreviated month names per the source.
On Py3, that yields:
['', 'янв', 'фев', 'мар', 'апр', 'май', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек']
^ lowercase!
On Py2, that yields:
['', '\xd1\x8f\xd0\xbd\xd0\xb2',
and a bunch more. Note that the first character is \xd1\x8f, and in your error message, \xd0\xaf doesn't match.
You can just change russian month name with english:
ru_to_eng_months = {'Янв': 'Jan', } # fill it with other months
def ru_to_eng_datetime(ru) -> string:
s = ru.split('-')
eng_month = ru_to_eng_months[s[1]]
return s[0] + '-' + eng_month + '-' + s[2]
s = u'[ 24-Янв-17 07:24 ]'
dateTime = ru_to_eng_datetime(s)
result = datetime.datetime.strptime(dateTime, u'[ %d-%b-%y %H:%M ]')
print(result) # 2017-01-24 07:24:00
If your application is supposed to support more than one locale then getting localized format of date/time by changing locale (by means of locale.setlocale()) is discouraged. For explanation why it's a bad idea see Alex Martelli's answer to the the question Using Python locale or equivalent in web applications? (basically locale is global and affects whole application so changing it might change behavior of other parts of application)
You can do it cleanly using Babel package like this:
>>> from datetime import date, datetime, time
>>> from babel.dates import format_date, format_datetime, format_time
>>> d = date(2007, 4, 1)
>>> format_date(d, locale='en')
u'Apr 1, 2007'
>>> format_date(d, locale='de_DE')
u'01.04.2007'
See Date and Time section in Babel's documentation.
You can just set the locale like in this example:
>>> import time
>>> print time.strftime("%a, %d %b %Y %H:%M:%S")
Sun, 23 Oct 2005 20:38:56
>>> import locale
>>> locale.setlocale(locale.LC_TIME, "sv_SE") # swedish
'sv_SE'
>>> print time.strftime("%a, %d %b %Y %H:%M:%S")
sön, 23 okt 2005 20:39:15