dataquant 1.1.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,502 @@
1
+ # -*- coding: utf-8 -*-
2
+ import io
3
+ import time
4
+ import six
5
+ import abc
6
+ import warnings
7
+ import requests
8
+ import numpy as np
9
+ import pandas as pd
10
+ import json
11
+ import urllib3
12
+ import pyarrow as pa
13
+ import pyarrow.feather as feather
14
+
15
+
16
+ from dataquant.utils.common import (
17
+ ConnectionStatus,
18
+ ERROR_NO,
19
+ ERROR_INFO,
20
+ DATA
21
+ )
22
+ from dataquant.utils.error import (
23
+ ConnectionException,
24
+ ConnectionTimeOut,
25
+ RequestTimeOut,
26
+ RequestException,
27
+ GatewayException,
28
+ ServerException,
29
+ GATEWAY_ERROR_DICT,
30
+ SERVER_ERROR_DICT,
31
+ )
32
+
33
+
34
+ class AbstractConnection(six.with_metaclass(abc.ABCMeta)):
35
+ """
36
+ 连接抽象类
37
+ """
38
+ def create(self, *args, **kwargs):
39
+ """
40
+ 创建连接对象
41
+ :param args:
42
+ :param kwargs:
43
+ :return:
44
+ """
45
+ raise NotImplementedError
46
+
47
+ def connect(self, timeout=5):
48
+ """
49
+ 启动连接
50
+ :param timeout:
51
+ :return:
52
+ """
53
+ raise NotImplementedError
54
+
55
+ def set_timeout(self, timeout):
56
+ """
57
+ 设置超时时间
58
+ :param timeout:
59
+ :return:
60
+ """
61
+ raise NotImplementedError
62
+
63
+ def send(self, *args, **kwargs):
64
+ """
65
+ 同步发送数据
66
+ :param args:
67
+ :param kwargs:
68
+ :return:
69
+ """
70
+ raise NotImplementedError
71
+
72
+ def async_send(self, *args, **kwargs):
73
+ """
74
+ 异步发送数据
75
+ :param args:
76
+ :param kwargs:
77
+ :return:
78
+ """
79
+ raise NotImplementedError
80
+
81
+ def receive(self, *args, **kwargs):
82
+ """
83
+ 接收数据
84
+ :param args:
85
+ :param kwargs:
86
+ :return:
87
+ """
88
+ raise NotImplementedError
89
+
90
+ def status(self):
91
+ """
92
+ 连接状态
93
+ :return:
94
+ """
95
+ raise NotImplementedError
96
+
97
+ def check(self):
98
+ """
99
+ 检查连接是否可用
100
+ :return:
101
+ """
102
+ raise NotImplementedError
103
+
104
+ def error(self):
105
+ """
106
+ 连接错误信息
107
+ :return:
108
+ """
109
+ raise NotImplementedError
110
+
111
+ def close(self):
112
+ """
113
+ 关闭链接
114
+ :return:
115
+ """
116
+ raise NotImplementedError
117
+
118
+
119
+ class Connection(object):
120
+ """
121
+ 连接工厂类
122
+ """
123
+ @classmethod
124
+ def get_connection(cls, config):
125
+ from dataquant.utils.common import Protocol
126
+ protocol = config.get("protocol")
127
+ conn_timeout = config.get("connect_timeout")
128
+ try:
129
+ with ConnectionContext(conn_timeout):
130
+ return globals()[Protocol[protocol].value]().create(config)
131
+ except KeyError:
132
+ raise RuntimeError("创建链接对象失败,请检查配置信息是否正确?config={}".format(config))
133
+
134
+
135
+ class HttpConnection(AbstractConnection):
136
+ """
137
+ HTTP连接对象类
138
+ """
139
+
140
+ def create(self, config):
141
+ """
142
+ 创建连接对象
143
+ :param config:
144
+ :return:
145
+ """
146
+ # 解析config文件中的信息
147
+ url = config.get("url")
148
+ auth = config.get("auth")
149
+
150
+ assert url
151
+ assert auth
152
+
153
+ self._url = url
154
+ self._user = auth.get("username")
155
+ self._pwd = auth.get("password")
156
+ self._timeout = config.get("request_timeout")
157
+ self._page_size = config.get("page_size")
158
+
159
+ assert self._user
160
+ assert self._pwd
161
+
162
+ self._conn = requests.Session()
163
+ self._status = ConnectionStatus.Connected
164
+ self._conn_u3 = urllib3.PoolManager()
165
+ return self
166
+
167
+ def set_timeout(self, timeout):
168
+ """
169
+ 设置超时时间
170
+ :param timeout:
171
+ :return:
172
+ """
173
+ self._timeout = timeout
174
+
175
+ def _build_data(self, **kwargs):
176
+ api_type = kwargs.get("api_type", "normal")
177
+ if api_type in {"normal"}:
178
+ data = {
179
+ "params": kwargs,
180
+ "page_no": kwargs.get("page_no", "1"),
181
+ "page_size": kwargs.get("page_size", self._page_size),
182
+ }
183
+ elif api_type in {"quote", "kline", "summary"}:
184
+ data = {
185
+ "params": kwargs
186
+ }
187
+ elif api_type in {"sql"}:
188
+ data = {
189
+ "params": kwargs
190
+ }
191
+ else:
192
+ raise RuntimeError("未知的API类型,当前仅支持normal/quote/kline")
193
+ return data
194
+
195
+ def _build_ret(self, rsp, **kwargs):
196
+ api_type = kwargs.get("api_type", "normal")
197
+ ret = {}
198
+ # 拆卸其他返回参数,处理DF,常规处理模式
199
+ if api_type == "normal":
200
+ df = pd.DataFrame()
201
+ for k, v in rsp.items():
202
+ if k == "data":
203
+ if isinstance(rsp[k], dict):
204
+ for dk, dv in rsp[k].items():
205
+ if dk not in {"rows"}:
206
+ ret[dk] = dv
207
+
208
+ else:
209
+ ret[k] = v
210
+ if rsp.get("data"):
211
+ df = pd.DataFrame(rsp["data"]["rows"])
212
+ self._mod_ret_df(df, kwargs, ret)
213
+ # 行情接口处理模式
214
+ elif api_type in ("quote", "kline", "summary"):
215
+ df = pd.DataFrame()
216
+ if rsp['data_type'] == 'data':
217
+ ret['page_num'] = rsp['page_num']
218
+ data_bin = rsp['content']
219
+ format_type = kwargs.get('format', 'Arrow')
220
+
221
+ if api_type == "kline":
222
+ # 不复权Kline数据,返回格式为Parquet/Arrow格式
223
+ # 前复权Kline数据,返回格式为CSV格式
224
+ # 后复权Kline数据,1d以上返回Parquet/Arrow格式,其余返回CSV格式
225
+ if kwargs.get('candle_mode', '0') == '0':
226
+ pass
227
+ elif kwargs.get('candle_mode', '0') == '1':
228
+ format_type = 'CSVWithNames'
229
+ elif kwargs.get('candle_mode', '0') == '2':
230
+ if kwargs.get('candle_period', '6') in ('6', '7', '8', '9'):
231
+ pass
232
+ else:
233
+ format_type = 'CSVWithNames'
234
+ elif api_type == "summary":
235
+ format_type = 'TSVWithNames'
236
+
237
+ try:
238
+ if format_type == 'CSVWithNames':
239
+ df = pd.read_csv(io.StringIO(data_bin.decode('utf8')), sep=',')
240
+ elif format_type == 'Parquet':
241
+ df = pd.read_parquet(io.BytesIO(data_bin))
242
+ elif format_type == 'Arrow':
243
+ df = feather.read_feather(pa.py_buffer(data_bin))
244
+ elif format_type == 'TSVWithNames':
245
+ df = pd.read_csv(io.StringIO(data_bin.decode('utf8')), sep='\t', names=kwargs['fix_cols'])
246
+ pass
247
+ except Exception as e:
248
+ warn_str = "数据转换异常,错误说明={},数据长度={}".format(str(e),len(data_bin))
249
+ warnings.warn(warn_str)
250
+ pass
251
+ else:
252
+ format_type = 'json'
253
+ for k, v in rsp.items():
254
+ if k == "data":
255
+ if isinstance(rsp[k], dict):
256
+ for dk, dv in rsp[k].items():
257
+ if dk not in {"cols", "items"}:
258
+ ret[dk] = dv
259
+ else:
260
+ ret[k] = v
261
+
262
+ # 20220806 行情单独处理
263
+ self._mod_ret_df_quote(df, kwargs, ret,format_type)
264
+ # SQL接口处理模式
265
+ elif api_type == "sql":
266
+ ret = dict()
267
+ ret['result_code'] = rsp['result_code']
268
+ ret['result_msg'] = rsp['result_msg']
269
+ tmp_rsp = rsp['data'].replace('null', '\"\"')
270
+ ret['data'] = pd.DataFrame(eval(tmp_rsp))
271
+
272
+ return ret
273
+
274
+ def _mod_ret_df(self, df, kwargs, ret):
275
+ # DF后期加工
276
+ df.columns = df.columns.map(lambda x: x.lower())
277
+ if not df.empty:
278
+ if isinstance(kwargs['cols'], list):
279
+ df = df[kwargs['cols']]
280
+ elif isinstance(kwargs['cols'], str):
281
+ cols = kwargs['cols'].replace(' ', '')
282
+ df = df[cols.split(',')]
283
+
284
+ for int_type in kwargs.get("int_param"):
285
+ if int_type in df.columns:
286
+ df[int_type] = pd.Series(df[int_type], dtype='int64')
287
+ for float_type in kwargs.get("float_param"):
288
+ if float_type in df.columns:
289
+ df[float_type] = pd.Series(df[float_type], dtype='float64')
290
+ else:
291
+ ret["data"] = None
292
+ return
293
+
294
+ if not(kwargs.get("rslt_type", 0)):
295
+ ret["data"] = df
296
+ elif kwargs.get("rslt_type", 0) == 1:
297
+ ret["data"] = np.array(df.to_records(index=False))
298
+ else:
299
+ raise ValueError("rslt_type must in (0,1)")
300
+
301
+ def _mod_ret_df_quote(self, df, kwargs, ret, format_type):
302
+ if (not df.empty) and len(df) > 0:
303
+ # 20220818 前复权数据类型规整,但本质是tsv,csv需要
304
+ if format_type == 'CSVWithNames':
305
+ for int_type in kwargs.get("int_param"):
306
+ if int_type in df.columns:
307
+ df[int_type] = pd.Series(df[int_type], dtype='float64')
308
+ for float_type in kwargs.get("float_param"):
309
+ if float_type in df.columns:
310
+ df[float_type] = pd.Series(df[float_type], dtype='float64')
311
+ pass
312
+ else:
313
+ df = None
314
+ ret["data"] = df
315
+ return
316
+
317
+ def send(self, method, **kwargs):
318
+ """
319
+ 同步发送数据
320
+ :param method:
321
+ :param kwargs:
322
+ :return:
323
+ """
324
+
325
+ api_type = kwargs.get("api_type", "normal")
326
+ # 已连接状态时
327
+ if self.check():
328
+ if self._user == "license":
329
+ url = "".join((self._url, "/", method, "?app_key=", self._pwd))
330
+ else:
331
+ raise RuntimeError("当前只支持license登录方式")
332
+
333
+ data = self._build_data(**kwargs)
334
+
335
+ try:
336
+ response_u3 = self._conn_u3.request(
337
+ 'POST',
338
+ url,
339
+ body=json.dumps(data),
340
+ headers={'Content-Type': 'application/json', 'Accept-Encoding': 'br,gzip,deflate'},
341
+ timeout=self._timeout)
342
+ response = ResponseU3(response_u3)
343
+ except requests.exceptions.ConnectTimeout as ex:
344
+ raise RequestTimeOut(ex)
345
+ # 状态码200表示请求成功
346
+ if response.status_code == 200:
347
+ # requests获取后直接调用json()方法转变为python字典请求获取HttpResponse对象
348
+ if api_type in {'quote', 'kline', 'summary'}:
349
+ rsp = {
350
+ 'data_type':None,
351
+ 'content': response.content,
352
+ 'result_code': '0',
353
+ 'page_num': 1,
354
+ }
355
+ if 'page_num' in kwargs:
356
+ rsp['page_num'] = kwargs['page_num']
357
+ if response.content[0:1] == b'{':
358
+ rsp = response.json()
359
+ rsp['data_type'] = 'json'
360
+ if "result_code" not in rsp:
361
+ if "resultCode" in rsp:
362
+ rsp['result_code'] = rsp["resultCode"]
363
+ else:
364
+ rsp['result_code'] = '0'
365
+ if "result_msg" not in rsp:
366
+ if "resultMsg" in rsp:
367
+ rsp['result_msg'] = rsp["resultMsg"]
368
+ else:
369
+ rsp['result_msg'] = ''
370
+ else:
371
+ rsp['data_type'] = 'data'
372
+
373
+ else:
374
+ rsp = response.json() # 180ms
375
+ # 从数据服务返回的数据
376
+ if "result_code" in rsp:
377
+ if int(rsp["result_code"]) == 0:
378
+ ret = self._build_ret(rsp, **kwargs)
379
+ return ret
380
+ # 获取error中异常信息
381
+ if SERVER_ERROR_DICT.get(int(rsp["result_code"]), None):
382
+ # warnings.warn("app_key权限认证失败,请联系管理员,error_info={}".format(rsp["resultMsg"]))
383
+ # warnings.warn("app_key权限已过期,请重新申请,申请站点:{}".format(rsp["resultMsg"]))
384
+ warn_str = "请求异常,异常信息:{}".format(rsp["result_msg"])
385
+ warnings.warn(warn_str)
386
+ raise RequestException(rsp["result_msg"])
387
+ else:
388
+ warn_str = "获取数据异常,path={},params={},error_info={}".format(
389
+ method, kwargs, rsp["result_msg"])
390
+ warnings.warn(warn_str)
391
+ raise RequestException("请求发生未定义错误,错误信息:{}".format(rsp["result_msg"]))
392
+ elif 400 <= response.status_code < 500:
393
+ if api_type == 'quote': # 行情接口
394
+ warn_str = "服务异常,异常信息:{}".format(response.text)
395
+ warnings.warn(warn_str)
396
+ raise ServerException(response.text)
397
+ else:
398
+ rsp = response.json()
399
+ # 从新版网关返回异常
400
+ if ERROR_NO in rsp:
401
+ # 获取错误代码
402
+ if GATEWAY_ERROR_DICT.get(rsp[ERROR_NO], None):
403
+ warnings.warn("网关返回错误,错误信息:{}".format(rsp[ERROR_INFO]))
404
+ raise GatewayException(rsp[ERROR_INFO])
405
+ else:
406
+ raise GatewayException("网关发生未定义错误,错误信息:{}".format(rsp[ERROR_INFO]))
407
+ # 从老版网关返回异常
408
+ elif DATA in rsp:
409
+ if isinstance(rsp[DATA], list):
410
+ error_dict = rsp[DATA][0]
411
+ else:
412
+ error_dict = rsp[DATA]
413
+
414
+ if GATEWAY_ERROR_DICT.get(error_dict[ERROR_NO], None):
415
+ warnings.warn("网关返回错误,错误信息:{}".format(error_dict[ERROR_INFO]))
416
+ raise GatewayException(error_dict[ERROR_INFO])
417
+ else:
418
+ raise GatewayException("网关发生未定义错误,错误信息:{}".format(error_dict[ERROR_INFO]))
419
+ else:
420
+ warnings.warn("网关返回未定义异常,异常信息:{}".format(response.text))
421
+ raise GatewayException(response.text)
422
+ # 服务错误
423
+ elif 500 <= response.status_code < 601:
424
+ warnings.warn("服务异常,异常信息:{}".format(response.text))
425
+ raise ServerException(response.text)
426
+ # 其他流程错误
427
+ else:
428
+ warnings.warn("异常流程,返回信息:{}".format(response.text))
429
+ raise Exception(response.text)
430
+ # 如果没连接则raise连接异常
431
+ raise ConnectionException("连接已关闭,无法获取数据")
432
+
433
+ def async_send(self, *args, **kwargs):
434
+ """
435
+ 异步发送数据
436
+ :param args:
437
+ :param kwargs:
438
+ :return:
439
+ """
440
+ raise NotImplementedError
441
+
442
+ def receive(self, *args, **kwargs):
443
+ """
444
+ 接收数据
445
+ :param args:
446
+ :param kwargs:
447
+ :return:
448
+ """
449
+ raise NotImplementedError
450
+
451
+ def status(self):
452
+ """
453
+ 连接状态
454
+ :return:
455
+ """
456
+ return self._status
457
+
458
+ def check(self):
459
+ return self._status in [ConnectionStatus.Connected, ConnectionStatus.SafeConnected]
460
+
461
+ def error(self):
462
+ """
463
+ 连接错误信息
464
+ :return:
465
+ """
466
+ raise NotImplementedError
467
+
468
+ def close(self):
469
+ """
470
+ 关闭链接
471
+ :return:
472
+ """
473
+ self._conn.close()
474
+ self._status = ConnectionStatus.Disconnected
475
+
476
+
477
+ class ConnectionContext(object):
478
+ def __init__(self, timeout=5):
479
+ assert timeout
480
+ self.timeout = timeout
481
+ self.start = None
482
+
483
+ def __enter__(self):
484
+ self.start = time.time()
485
+
486
+ def __exit__(self, exc_type, exc_val, exc_tb):
487
+ if time.time() - self.start > self.timeout:
488
+ raise ConnectionTimeOut
489
+
490
+
491
+ class ResponseU3(object):
492
+ def __init__(self,resp_u3):
493
+ self.status_code = resp_u3.status
494
+ self.content = resp_u3.data
495
+ self.ori_resp = resp_u3
496
+
497
+ @property
498
+ def text(self):
499
+ return self.content.decode(errors='backslashreplace')
500
+
501
+ def json(self):
502
+ return json.loads(self.text)
@@ -0,0 +1,67 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from collections import deque
4
+ from threading import Lock, Semaphore #Semaphore 是用于控制进入数量的锁,控制同时进行的线程
5
+ from contextlib import contextmanager #上下文管理
6
+
7
+ from dataquant.utils.connection import Connection
8
+ from dataquant.utils.decorators import retry
9
+ from dataquant.utils.config import read_config
10
+
11
+
12
+ CONFIG = read_config()['system']
13
+ PARALLEL_MODE = CONFIG.get("parallel_mode")
14
+
15
+
16
+ class ConnectionPool(object):
17
+ def __init__(self, config):
18
+ self._config = config.copy()
19
+ self._lock = Lock()
20
+ self._connections = deque()
21
+ self._connect_timeout = config.pop("connect_timeout")
22
+ self._signal = Semaphore(config.pop("pool_size"))
23
+ self._send_deque = deque()
24
+
25
+
26
+ @property
27
+ def config(self):
28
+ return self._config
29
+
30
+ @retry(3, exp_name="RequestTimeOut")
31
+ def send(self, method, **kwargs):
32
+ with self._signal:
33
+ with self._yield_conn() as conn:
34
+ return conn.send(method, **kwargs)
35
+
36
+ @contextmanager
37
+ def _yield_conn(self):
38
+ conn = self._get_connection()
39
+ try:
40
+ yield conn #返回连接
41
+ except Exception as ex:
42
+ conn.close() #连接异常时关闭连接
43
+ raise ex
44
+ else:
45
+ with self._lock:
46
+ self._connections.append(conn) #若已加锁则将当前连接加入连接队列
47
+
48
+ def _get_connection(self):
49
+ with self._lock:
50
+ while self._connections:
51
+ conn = self._connections.popleft() #从队列左边取出一个连接
52
+ if conn.check(): #检查连接是否可用
53
+ return conn
54
+ else:
55
+ conn.close()
56
+
57
+ return self._cretate_connection()
58
+
59
+ @retry(5, exp_name="ConnectTimeout")
60
+ def _cretate_connection(self):
61
+ return Connection.get_connection(self._config) #创建连接
62
+
63
+ def close(self):
64
+ with self._lock:
65
+ for conn in self._connections: #逐个关闭连接
66
+ conn.close()
67
+ self._connections.clear() #清空队列
@@ -0,0 +1 @@
1
+ # -*- coding: utf-8 -*-
@@ -0,0 +1,54 @@
1
+ # -*- coding: utf-8 -*-
2
+ import io
3
+ from dataquant.utils.parallel import parallel_df
4
+ import pandas as pd
5
+ from itertools import groupby
6
+
7
+
8
+ def convert_param(_type, param):
9
+ if param is None:
10
+ return _type() #返回该类型空值
11
+
12
+ return param
13
+
14
+
15
+ def convert_fields(field):
16
+ if field is None:
17
+ return []
18
+
19
+ if isinstance(field, list):
20
+ return field
21
+
22
+ if isinstance(field, str):
23
+ return [field]
24
+
25
+ raise ValueError("字段[{}]类型转换失败,支持[None, str, list]类型,实参类型为[{}]".format(field, type(field)))
26
+
27
+
28
+ def sort_merge_df(df_list, sort_cols, ignore_index=True):
29
+ df = None
30
+ if len(df_list) <= 0:
31
+ return df
32
+ if len(df_list) == 1:
33
+ return df_list[0] # 节省10ms左右
34
+
35
+ sorted_dfs = sorted(df_list, key=lambda df: '|'.join(df.iloc[0][sort_cols].astype(str)))
36
+ df = pd.concat(sorted_dfs, ignore_index=ignore_index)
37
+ return df
38
+
39
+
40
+ def sort_merge_df_dic(df_dic, ignore_index=True):
41
+ df = None
42
+ if len(df_dic) <= 0:
43
+ return df
44
+ sorted_dfs = list(zip(*sorted(df_dic.items(),key=lambda kv:kv[0])))[1]
45
+ if len(df_dic) == 1:
46
+ return sorted_dfs[0] # 节省10ms左右
47
+ df = pd.concat(sorted_dfs, ignore_index=ignore_index)
48
+ return df
49
+
50
+
51
+ def split_df(df: pd.DataFrame):
52
+ new_str = df.to_csv(sep='|', header=False, index=False) # 47ms
53
+ df = pd.read_csv(io.StringIO(new_str.replace('"', '')), header=None, sep='|', dtype='float64') # 38ms
54
+ return df
@@ -0,0 +1,77 @@
1
+ # -*- coding: utf-8 -*-
2
+ import datetime
3
+
4
+
5
+ def convert_date(dt, now=True):
6
+ # if dt == "": #时间为空时返回当前时间或19900101
7
+ # if now:
8
+ # return datetime.datetime.now().date().strftime("%Y%m%d")
9
+ # else:
10
+ # return "19900101""19900101"
11
+ if dt == None:
12
+ return
13
+
14
+ if isinstance(dt, datetime.datetime): #datetime类型数据进行数据转换成固定格式
15
+ return dt.date().strftime("%Y%m%d")
16
+
17
+ if isinstance(dt, datetime.date): #datetime.date数据类型转换为固定格式
18
+ return dt.strftime("%Y%m%d")
19
+
20
+ if isinstance(dt, str):
21
+ if len(dt) == 0:
22
+ return dt
23
+ else:
24
+ from dateutil.parser import parse
25
+ return parse(dt).date().strftime("%Y%m%d") #用parse进行格式转换,变成数据类型
26
+
27
+ if hasattr(dt, "to_pydatetime"): #判断一个对象里面是否有"to_pydatetime"属性或者"to_pydatetime"方法
28
+ return dt.to_pydatetime().date().strftime("%Y%m%d")
29
+
30
+ if hasattr(dt, "dtype") and dt.dtype.char == "M": #M表示带分钟的dtype类型
31
+ from dateutil.parser import parse
32
+ return parse(dt).date().strftime("%Y%m%d")
33
+
34
+ raise ValueError("转换日期格式异常,实参为[{}]".format(dt)) #不在上述所列类型抛出异常
35
+
36
+
37
+ def convert_date_mins(dt, end=False, now=True):
38
+ if dt == None:
39
+ return
40
+ count = 0
41
+ if isinstance(dt, datetime.datetime):
42
+ pass
43
+ elif isinstance(dt, datetime.date):
44
+ count = 8
45
+ elif isinstance(dt, str):
46
+ from dateutil.parser import parse
47
+ count = count_num(dt)
48
+ dt = parse(dt)
49
+ elif hasattr(dt, "to_pydatetime"):
50
+ dt = dt.to_pydatetime()
51
+ elif hasattr(dt, "dtype") and dt.dtype.char == "M":
52
+ from dateutil.parser import parse
53
+ dt = parse(dt)
54
+ else:
55
+ raise ValueError("转换日期格式异常,实参为[{}]".format(dt))
56
+
57
+ if end and count == 8:
58
+ dt = datetime.datetime.combine(dt, datetime.time.max)
59
+
60
+ return dt.strftime("%Y%m%d %H:%M:%S")
61
+
62
+
63
+ def count_num(str_dt):
64
+ dig_sum = 0
65
+ for strs in str_dt:
66
+ if strs.isdigit():
67
+ dig_sum += 1
68
+ return dig_sum
69
+
70
+
71
+ def get_current_date():
72
+ """
73
+ 获取当前日期
74
+ :return:
75
+ """
76
+ return datetime.datetime.now().strftime('%Y%m%d')
77
+