Мне приходят данные в виде 'Март 1, 2010', 'Сен. 1, 2010' и т.п. Меняю им вид вот так:
def date_convertion(datetime):
res = datetime.split(' ')
res.reverse()
return (res[0] + '-' + res[2][:-1] + '-' + res[1][:-1])
datetime получаю вот такой:
['Март 1, 2010']
на выходе функции это уже вот так:
['2010-Март-1']
Вопрос: как "Март" преобразовать в "03"? Создать словарь (он же ассоциативный массив)
и искать по ключу значение или можно это сделать иначе?
Ответы
Ответ 1
При преобразовании из одного строкового формата в другой, особенно если входной формат
нефиксированный и возможны вариации, то полезно использовать промежуточный объект-дату,
чтобы убедиться что значения были интерпретированы корректно.
входная строка -> объект-дата -> выходная строка
Например:
datetime.strptime("входная строка", "формат") ->
datetime объект ->
dt.strftime("формат вывода")
setlocale() вариант
Если русская локаль установлена для программы или известно её название на данной
платформе, то можно её активировать и попробовать распознать входные строки, используя
фиксированный список форматов:
#!/usr/bin/env python3
import locale
from datetime import datetime
locale.setlocale(locale.LC_TIME, 'ru_RU.UTF-8') # the ru locale is installed
date_strings = ['Март 1, 2010', 'Сен. 1, 2010', '2015-Апрель-26']
print(date_strings)
date_formats = '%B %d, %Y', '%b %d, %Y', '%Y-%B-%d'
dates = []
for date_str in date_strings:
date_str = date_str.replace('Сен.', 'Сент.') # fix the abbr.
for date_fmt in date_formats:
try:
dates.append(datetime.strptime(date_str, date_fmt).date())
except ValueError:
pass
else:
break
else:
print('failed to parse %r' % date_str)
output_date_strings = list(map(str, dates))
print(output_date_strings)
Вывод
['Март 1, 2010', 'Сен. 1, 2010', '2015-Апрель-26']
['2010-03-01', '2010-09-01', '2015-04-26']
ICU вариант
Если есть возможность установить PyICU, то можно использовать несколько локалей независимо
от наличия соответствующей системной локали и без изменения глобального состояния программы
(может быть полезно в многопоточном приложении) [синтакс ICU форматов для времени]:
#!/usr/bin/env python3
from datetime import datetime
import icu # PyICU
date_strings = ['Март 1, 2010', 'Сен. 1, 2010', '2015-Апрель-26']
print(date_strings)
df = icu.SimpleDateFormat('', icu.Locale('ru'))
output_df = icu.SimpleDateFormat('yyyy-MM-dd')
output_date_strings = []
for date_str in date_strings:
date_str = date_str.replace('Сен.', 'Сент.') # fix the abbr.
for pattern in 'LLLL d, yyyy', 'yyyy-LLLL-dd':
df.applyPattern(pattern)
try:
output_date_strings.append(output_df.format(df.parse(date_str)))
except icu.ICUError:
pass
else:
break
else:
print('failed to parse %r' % date_str)
print(output_date_strings)
Результат такой же как у первой программы.
str.replace() вариант
Если входные данные более разнообразны, то можно ещё добавить предварительный шаг,
который сделает их более регулярными, например, как date_str.replace() выше (можно
словарь использовать со списком замен). Например, можно избавиться от зависимости на
локаль, заменяя все названия соответствующими цифрами:
for old, new in [('Март', '3'), ('Сен.', '9'), ('Апрель', '4')]:
date_str = date_str.replace(old, new)
После этого можно использовать datetime.strptime() c '%m %d, %Y', '%Y-%m-%d' форматами
без setlocale() вызова.
Ответ 2
Кратко
Многое в решении вашей проблемы зависит от формата строки.
Если названия месяцев соответствуют русской локали и записаны либо полностью, либо
сокращённо до трёх букв, то можете воспользоваться стандартной библиотекой datetime.
В противном случае, если единого формата нет, можете задать словарь замен. Например
(для двух месяцев):
month = {'Март': '03', 'Апр.': '04'}
Использовать его в вашем случае легко: month[res[2][:-1]].
Небольшое замечание. Решение с использованием reverse и split, конечно, работоспособно,
но не очень хорошо читается. Я бы использовал на вашем месте регулярные выражения.
Тогда при замене формата строки изменения в программе будут минимальными.
Замена с помощью datetime
Для начала задайте локаль (достаточно один раз в начале программы). Если у вас система
уже настроена на русский язык, то достаточно выполнить следующие команды:
import locale
locale.setlocale(locale.LC_ALL, '')
Если язык системы другой, то вместо пустых кавычек укажите требуемую локаль.
При помощи метода strptime вы можете проанализировать строку на соответствие некоторому
формату.
Пример:
from datetime import datetime
d = datetime.strptime("Апр. 6, 2015", "%b. %d, %Y")
print(d)
2015-04-06 00:00:00
Здесь %b — краткое название месяца в текущей локали, %d — номер дня, %Y — номер года
(4 цифры).
В переменной d будет находиться экземпляр класса datetime, который можно привести
к любому другому виду.
В вашем случае:
print(d.strftime('%Y-%m-%d'))
2015-04-06
Перечисленный операции можно объединить в одну функцию и использовать её для добавления
элементов в список:
def date_convert(s):
return datetime.strptime(s, "%b. %d, %Y").strftime('%Y-%m-%d')
[date_convert(d) for d in dates]
Здесь dates — список строк, хранящих даты в указанном вами формате.
Если требуется одновременно использовать две локали, то действия с датами можно выполнять,
например, внутри with написав контекстный процессор.
Библиотека datetime
Ответ 3
Добавление элемента в список или в ассоциативный массив
Добавление элемента в список производится с помощью функции append:
my_list.append( new_element )
Другой способ:
my_list += [ new_element ]
Добавление в ассоциативный массив выполняется так:
my_dict.update({ new_key : new_element })
или ещё проще:
my_dict[ new_key ] = new_element
Это если говорить в общем. У вас же вопрос немного туманный и не совсем понятно,
что вы пытаетесь сделать.
Преобразование даты между форматами
Преобразование даты осуществляется обычно с помощью функции strptime, которая из
вашей строки сделает объект даты, с которым можно выполнять дальнейшие манипуляции,
в том числе форматирование в другой формат.
Это будет выглядеть так:
d = datetime.datetime.strptime(d_string, fmt)
d_string2 = d.strfime(fmt2)
В данном случае я преобразовал исходную строку d_string из формата fmt в формат fmt2,
результат записан в d_string2.
(правда это к спискам не имеет никакого отношения).
Ответ 4
import functools, datetime, icu
_Replace = [('фев', 'февр'), ('ноя', 'нояб'), ('май', 'мая'), ('сен', 'сент'), ]
MonthReplace = (_Replace + [(s.capitalize(), r) for (s, r) in _Replace]) # для автозамены
+ capitalize
AllLocales = (En, Ru,) = list(map(icu.Locale, ['en', 'ru', ]))
TZ = icu.ICUtzinfo.getDefault() # time zone, ~ в 2012г была другая, эти даты парсит
на час назад
s_rus = 'авдимнопсфчя' # первые буквы имен месяцев и недель
S_Rus = set(s_rus + s_rus.upper())
IcuDateMasks = ['dd/MMM/yy HH:mm:ss', 'dd/MMM/yy HH:mm', 'EEE, dd MMM y HH:mm:ss',
'EEE, dd MMM y HH:mm', 'EEE, dd MMM y', 'EEE MMM dd HH:mm:ss y', ] # http://userguide.icu-project.org/formatparse/datetime
DateMaskForIcu = {(mask, lcl): icu.SimpleDateFormat(mask, lcl) for mask in IcuDateMasks
for lcl in AllLocales}
@functools.lru_cache()
def str_to_datetime(datetime_str: str) -> 'datetime.datetime':
(dtime_str, lcl) = _find_locale(datetime_str.split('+', 1)[0].strip())
for mask in IcuDateMasks: # выбрать правильный формат маски
try: return datetime.datetime.fromtimestamp(icu.DateFormat.parse(DateMaskForIcu[mask,
lcl], dtime_str), TZ, ).replace(tzinfo=None)
except icu.ICUError: continue
raise UserWarning(f'"{dtime_str}" не распознана в локали "{lcl}", ни по одной
из масок "{IcuDateMasks}" !!!')
def _find_locale(datetime_str: str) -> (str, 'icu.Locale'):
if any((s in datetime_str) for s in S_Rus): # рус locale
for (s, r) in MonthReplace:
if s in datetime_str:
datetime_str = datetime_str.replace(s, r, 1) # 'правильные' имена
месяцев
break
return datetime_str, Ru
else: return datetime_str, En
if __name__ == '__main__':
print(str_to_datetime('04/июн/12 11:56')) # 2012-06-04 11:56:00
print(str_to_datetime('15/Jan/19 2:49')) # 2019-01-15 02:49:00
print(str_to_datetime('15/Фев/19 20:42:01')) # 2019-02-15 20:42:01
print(str_to_datetime('Пт, 02 ноя 2018 14:26:00')) # 2018-11-02 14:26:00