Преобразование даты между строковыми представлениями

Пашка Шульга

Мне приходят данные в виде 'Март 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"? Создать словарь (он же ассоциативный массив) и искать по ключу значение или можно это сделать иначе?

3 ответа

Пашка Шульга

При преобразовании из одного строкового формата в другой, особенно если входной формат нефиксированный и возможны вариации, то полезно использовать промежуточный объект-дату, чтобы убедиться что значения были интерпретированы корректно.

входная строка -> объект-дата -> выходная строка

Например:

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() вызова.


Пашка Шульга

Кратко

Многое в решении вашей проблемы зависит от формата строки.

Если названия месяцев соответствуют русской локали и записаны либо полностью, либо сокращённо до трёх букв, то можете воспользоваться стандартной библиотекой 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


Пашка Шульга

Добавление элемента в список или в ассоциативный массив

Добавление элемента в список производится с помощью функции 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.

(правда это к спискам не имеет никакого отношения).

licensed under cc by-sa 3.0 with attribution.