Как да конвертирате дати от различни източници на данни в python DateTime.

Времевите клейма са най-важният елемент от данните за специалиста по данни. Времевите отпечатъци осигуряват дълбочина и слоеве на данните. И така, какъв е проблемът? Различни формати! Как да се справя с това?

Сценарият

В един проект събирам записи на данни за събития и транзакции от много различни източници на данни и ги вмъквам в една таблица. Имам нужда от клеймото за време, за да подредя събитията. Всички файлове идват в различни формати. В някои случаи ми се предоставят данни от друга група. Дори не знам какъв може да е източникът на данни. Това създава доста загуба на време за проучване на източника на данни и съответния формат.

Решението

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

Работата ми не зависи от часовите зони, така че не се работи в тази област. Планирам да споделя някои функции за часова зона, добавени към Python 3.9 в друга статия.

За моето тестване използвах предстоящия петък 13-ти, за да отпразнувам предстоящите празници Хелоуин.

# import packages
import re
import string
import datetime
import dateutil.parser
import time
# test input
list = [  '1605291193'                     # epoch
        , '1605291193000'                  # epoch with milliseconds
        , '2020-11-13T13:13:13.000Z'       # ISO, Oracle, MongoDB
        , '2020–11–13–13.13.13.000000'     # db2a
        , '2020–11–13–13:13:13:000000'     # db2b
        , 'Fri Nov 13 13:13:13 +0000 2020' # twitter
        , 'Fri Nov 13 13:13:13 2020'       # SAS
        , 'FRI Nov 13 13:13:13 2020'       # SAS shouting!
        , 'Nov 13, 2020, 01:13:13 PM'      # UTC
        , '2020-11-13 13:13:13'            # 'standard', google big query, pandas
        , '2020-11-13 13:13:13.000'        # presto
        , '11/13/2020'                     # mm/dd/yyyy American
        #, '13/11/2020'                    # dd/mm/yyyy European
      
       ]
# constants
european_flag = 'N'
months = ['jan', 'feb', 'mar', 'apr', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']
days_of_week = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
centuries = ['20', '19']
db2_time_sep = ['.', ':']
db2_dt_sep = ['-']
standard_dt_sep = [ ' ']
iso_dt_sep = ['T']
validation_date = '2020-11-13 13:13:13'
# processing all timestamps on the test input
for ix in list: 
    # initialize
    possible_ts_type = ' '
    has_century = has_year = has_space = has_T = has_month = has_day = 'N'
    converted_value =  db2_formatted = db2_month = db2_day= db2_minutes = db2_seconds  = 'x'
    i_num = lenght_i = 0
    
    # parse incoming data
    i = ix
    century     = i[0:2]
    year        = i[0:4]
    time_sep    = i[13:14]
    time_sep2   = i[16:17]
    dt_sep      = i[10:11]
    first_three = str(i[0:3]).lower()
    
    print()
    print('Starting timestamp:      ' + str(i))
        
    # does it start with a year?
    if century in centuries:
        has_century = 'Y'
        if re.match('^[0-9]*$', year):
            has_year = 'Y'
            
    # is the time separator a : or .?  
    if dt_sep !=  ' ':
            if dt_sep !=   'T':
                if time_sep in db2_time_sep:
                    if has_year == 'Y':
                        possible_ts_type = 'DB2'
                        db2_month   = i[5:7]
                        db2_day     = i[8:10]
                        db2_hour    = i[11:13]
                        db2_minutes = i[14:16]
                        db2_seconds = i[17:19]
                        db2_formatted =  str(year)        + '-' + \
                                         str(db2_month)   + '-' + \
                                         str(db2_day)     + ' ' + \
                                         str(db2_hour)    + '.' + \
                                         str(db2_minutes) + '.' + \
                                         str(db2_seconds)
                        converted_value =  datetime.datetime.strptime(db2_formatted, '%Y-%m-%d %H.%M.%S')
    
    # does it contain a T?  
    if dt_sep in iso_dt_sep:
        possible_ts_type = 'ISO'
        has_T = 'Y'
        converted_value =  datetime.datetime.strptime(i, "%Y-%m-%dT%H:%M:%S.%fZ")
        
    # is the entire string numberic?
    if re.match('^[0-9]*$', i): 
        possible_ts_type = 'epoch'
        i_num = int(i)
        length_i = len(i)
        if length_i > 10:
            i_num = int(i_num/1000)
        converted_value_temp = datetime.datetime.fromtimestamp(i_num).strftime('%c')
        converted_value =  dateutil.parser.parse(converted_value_temp)
        
    # does it start with a month?
    if first_three in months:
        possible_ts_type = 'UTC'
        converted_value =  dateutil.parser.parse(i)
        
    # does it start with a day of the week?
    if first_three in days_of_week :
        if i[20:21] == '+' :
            possible_ts_type = 'twitter'
            converted_value =  datetime.datetime.strftime(datetime.datetime.strptime(i,'%a %b %d %H:%M:%S +0000 %Y'), '%Y-%m-%d %H:%M:%S')
        else:
            possible_ts_type = 'SAS'
            converted_value =  dateutil.parser.parse(i)
        has_day = 'Y'
        
    # is the separator a space?  
    if i[10:11] == ' ' :
        has_space = 'Y'
            
    if len(i) == 19:
        if has_space == 'Y':
            if time_sep == ':':
                possible_ts_type = 'standard'
                converted_value = datetime.datetime.strptime(i, '%Y-%m-%d %H:%M:%S')
    
    if len(i) == 23:
        if has_space == 'Y':
            if time_sep == ':':
                possible_ts_type = 'presto'
                converted_value = datetime.datetime.strptime(i, '%Y-%m-%d %H:%M:%S.%f')
        
    if '/' in i:
        if european_flag == 'Y':
            possible_ts_type = 'european d/m/Y'
            converted_value = datetime.datetime.strptime(i, "%d/%m/%Y")
        else:
            if european_flag == 'N':
                possible_ts_type = 'american m/d/Y'
                converted_value = datetime.datetime.strptime(i, "%m/%d/%Y")
# Evaluate the results
    print('Possible Timestamp Type: ' + possible_ts_type)
    print('Converted Timestamp:     ' + str(converted_value))
    if str(converted_value) == validation_date:
        print('Successful Conversion!')
    else:
        print('Not a complete match')

Резултатът:

Starting timestamp:      1605291193
Possible Timestamp Type: epoch
Converted Timestamp:     2020-11-13 13:13:13
Successful Conversion!

Starting timestamp:      1605291193000
Possible Timestamp Type: epoch
Converted Timestamp:     2020-11-13 13:13:13
Successful Conversion!

Starting timestamp:      2020-11-13T13:13:13.000Z
Possible Timestamp Type: ISO
Converted Timestamp:     2020-11-13 13:13:13
Successful Conversion!

Starting timestamp:      2020–11–13–13.13.13.000000
Possible Timestamp Type: DB2
Converted Timestamp:     2020-11-13 13:13:13
Successful Conversion!

Starting timestamp:      2020–11–13–13:13:13:000000
Possible Timestamp Type: DB2
Converted Timestamp:     2020-11-13 13:13:13
Successful Conversion!

Starting timestamp:      Fri Nov 13 13:13:13 +0000 2020
Possible Timestamp Type: twitter
Converted Timestamp:     2020-11-13 13:13:13
Successful Conversion!

Starting timestamp:      Fri Nov 13 13:13:13 2020
Possible Timestamp Type: SAS
Converted Timestamp:     2020-11-13 13:13:13
Successful Conversion!

Starting timestamp:      FRI Nov 13 13:13:13 2020
Possible Timestamp Type: SAS
Converted Timestamp:     2020-11-13 13:13:13
Successful Conversion!

Starting timestamp:      Nov 13, 2020, 01:13:13 PM
Possible Timestamp Type: UTC
Converted Timestamp:     2020-11-13 13:13:13
Successful Conversion!

Starting timestamp:      2020-11-13 13:13:13
Possible Timestamp Type: standard
Converted Timestamp:     2020-11-13 13:13:13
Successful Conversion!

Starting timestamp:      2020-11-13 13:13:13.000
Possible Timestamp Type: presto
Converted Timestamp:     2020-11-13 13:13:13
Successful Conversion!

Starting timestamp:      11/13/2020
Possible Timestamp Type: american m/d/Y
Converted Timestamp:     2020-11-13 00:00:00
Not a complete match

Чувствайте се свободни да използвате всеки от този скрипт, който работи за вас. Перфектен ли е? Не. Това ли е най-добрият сценарий? Вероятно не. Ако имате друго решение, моля, споделете го в коментарите. Колкото повече опции имаме, толкова по-добре сме всички.