terryutils 1.0.4__py3-none-any.whl → 1.0.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.
- terryutils/__init__.py +3 -0
- terryutils/date_util.py +228 -0
- terryutils/log.py +32 -0
- terryutils/mysql_util.py +108 -0
- {terryutils-1.0.4.dist-info → terryutils-1.0.6.dist-info}/METADATA +1 -1
- terryutils-1.0.6.dist-info/RECORD +8 -0
- terryutils-1.0.6.dist-info/top_level.txt +1 -0
- terryutils-1.0.4.dist-info/RECORD +0 -4
- terryutils-1.0.4.dist-info/top_level.txt +0 -1
- {terryutils-1.0.4.dist-info → terryutils-1.0.6.dist-info}/WHEEL +0 -0
terryutils/__init__.py
ADDED
terryutils/date_util.py
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
from datetime import datetime, date, timedelta
|
|
2
|
+
import calendar
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
class DateUtils:
|
|
6
|
+
"""企业级 Python 日期工具类"""
|
|
7
|
+
|
|
8
|
+
COMMON_FORMATS = [
|
|
9
|
+
"%Y-%m-%d %H:%M:%S",
|
|
10
|
+
"%Y-%m-%d %H:%M",
|
|
11
|
+
"%Y-%m-%d",
|
|
12
|
+
"%d/%m/%Y",
|
|
13
|
+
"%d-%m-%Y",
|
|
14
|
+
"%Y/%m/%d",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
# -------------------- 基础获取 --------------------
|
|
18
|
+
@staticmethod
|
|
19
|
+
def now(fmt="%Y-%m-%d %H:%M:%S"):
|
|
20
|
+
"""当前时间"""
|
|
21
|
+
return datetime.now().strftime(fmt)
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def today(fmt="%Y-%m-%d"):
|
|
25
|
+
"""今天"""
|
|
26
|
+
return date.today().strftime(fmt)
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def datetime_now():
|
|
30
|
+
"""返回datetime对象"""
|
|
31
|
+
return datetime.now()
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def date_today():
|
|
35
|
+
"""返回date对象"""
|
|
36
|
+
return date.today()
|
|
37
|
+
|
|
38
|
+
# -------------------- 字符串与datetime互转 --------------------
|
|
39
|
+
@staticmethod
|
|
40
|
+
def parse(date_str, fmt=None):
|
|
41
|
+
"""尝试多种格式解析字符串"""
|
|
42
|
+
if isinstance(date_str, (int, float)):
|
|
43
|
+
return datetime.fromtimestamp(date_str)
|
|
44
|
+
if isinstance(date_str, datetime):
|
|
45
|
+
return date_str
|
|
46
|
+
if fmt:
|
|
47
|
+
return datetime.strptime(date_str, fmt)
|
|
48
|
+
for f in DateUtils.COMMON_FORMATS:
|
|
49
|
+
try:
|
|
50
|
+
return datetime.strptime(date_str, f)
|
|
51
|
+
except:
|
|
52
|
+
continue
|
|
53
|
+
raise ValueError(f"无法解析日期: {date_str}")
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
def format(dt, fmt="%Y-%m-%d %H:%M:%S"):
|
|
57
|
+
if isinstance(dt, str):
|
|
58
|
+
dt = DateUtils.parse(dt)
|
|
59
|
+
return dt.strftime(fmt)
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def to_timestamp(dt):
|
|
63
|
+
dt = DateUtils.parse(dt) if isinstance(dt, (str, int, float)) else dt
|
|
64
|
+
return int(dt.timestamp())
|
|
65
|
+
|
|
66
|
+
@staticmethod
|
|
67
|
+
def from_timestamp(ts):
|
|
68
|
+
return datetime.fromtimestamp(ts)
|
|
69
|
+
|
|
70
|
+
# -------------------- 日期加减 --------------------
|
|
71
|
+
@staticmethod
|
|
72
|
+
def add_days(dt, days):
|
|
73
|
+
dt = DateUtils.parse(dt)
|
|
74
|
+
return dt + timedelta(days=days)
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def add_seconds(dt, seconds, ft):
|
|
78
|
+
dt = DateUtils.parse(dt, ft)
|
|
79
|
+
return dt + timedelta(seconds=seconds)
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def add_months(dt, months):
|
|
83
|
+
dt = DateUtils.parse(dt)
|
|
84
|
+
month = dt.month - 1 + months
|
|
85
|
+
year = dt.year + month // 12
|
|
86
|
+
month = month % 12 + 1
|
|
87
|
+
day = min(dt.day, calendar.monthrange(year, month)[1])
|
|
88
|
+
return dt.replace(year=year, month=month, day=day)
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def add_years(dt, years):
|
|
92
|
+
dt = DateUtils.parse(dt)
|
|
93
|
+
try:
|
|
94
|
+
return dt.replace(year=dt.year + years)
|
|
95
|
+
except ValueError:
|
|
96
|
+
# 闰年2月29日处理
|
|
97
|
+
return dt.replace(month=2, day=28, year=dt.year + years)
|
|
98
|
+
|
|
99
|
+
# -------------------- 周/月/季度起止 --------------------
|
|
100
|
+
@staticmethod
|
|
101
|
+
def start_of_day(dt=None):
|
|
102
|
+
dt = DateUtils.parse(dt) if dt else datetime.now()
|
|
103
|
+
return datetime(dt.year, dt.month, dt.day, 0, 0, 0)
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
def end_of_day(dt=None):
|
|
107
|
+
dt = DateUtils.parse(dt) if dt else datetime.now()
|
|
108
|
+
return datetime(dt.year, dt.month, dt.day, 23, 59, 59)
|
|
109
|
+
|
|
110
|
+
@staticmethod
|
|
111
|
+
def start_of_week(dt=None):
|
|
112
|
+
dt = DateUtils.parse(dt) if dt else datetime.now()
|
|
113
|
+
start = dt - timedelta(days=dt.weekday())
|
|
114
|
+
return datetime(start.year, start.month, start.day)
|
|
115
|
+
|
|
116
|
+
@staticmethod
|
|
117
|
+
def end_of_week(dt=None):
|
|
118
|
+
dt = DateUtils.parse(dt) if dt else datetime.now()
|
|
119
|
+
end = dt + timedelta(days=6 - dt.weekday())
|
|
120
|
+
return datetime(end.year, end.month, end.day, 23, 59, 59)
|
|
121
|
+
|
|
122
|
+
@staticmethod
|
|
123
|
+
def start_of_month(dt=None):
|
|
124
|
+
dt = DateUtils.parse(dt) if dt else datetime.now()
|
|
125
|
+
return datetime(dt.year, dt.month, 1)
|
|
126
|
+
|
|
127
|
+
@staticmethod
|
|
128
|
+
def end_of_month(dt=None):
|
|
129
|
+
dt = DateUtils.parse(dt) if dt else datetime.now()
|
|
130
|
+
last_day = calendar.monthrange(dt.year, dt.month)[1]
|
|
131
|
+
return datetime(dt.year, dt.month, last_day, 23, 59, 59)
|
|
132
|
+
|
|
133
|
+
@staticmethod
|
|
134
|
+
def start_of_quarter(dt=None):
|
|
135
|
+
dt = DateUtils.parse(dt) if dt else datetime.now()
|
|
136
|
+
month = ((dt.month - 1) // 3) * 3 + 1
|
|
137
|
+
return datetime(dt.year, month, 1)
|
|
138
|
+
|
|
139
|
+
@staticmethod
|
|
140
|
+
def end_of_quarter(dt=None):
|
|
141
|
+
dt = DateUtils.parse(dt) if dt else datetime.now()
|
|
142
|
+
month = ((dt.month - 1) // 3 + 1) * 3
|
|
143
|
+
last_day = calendar.monthrange(dt.year, month)[1]
|
|
144
|
+
return datetime(dt.year, month, last_day, 23, 59, 59)
|
|
145
|
+
|
|
146
|
+
# -------------------- 日期判断 --------------------
|
|
147
|
+
@staticmethod
|
|
148
|
+
def is_leap_year(year):
|
|
149
|
+
return calendar.isleap(year)
|
|
150
|
+
|
|
151
|
+
@staticmethod
|
|
152
|
+
def weekday(dt=None):
|
|
153
|
+
dt = DateUtils.parse(dt) if dt else datetime.now()
|
|
154
|
+
return dt.weekday() # 0=周一
|
|
155
|
+
|
|
156
|
+
@staticmethod
|
|
157
|
+
def is_weekend(dt=None):
|
|
158
|
+
return DateUtils.weekday(dt) >= 5
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def days_between(start, end):
|
|
162
|
+
start = DateUtils.parse(start)
|
|
163
|
+
end = DateUtils.parse(end)
|
|
164
|
+
return (end - start).days
|
|
165
|
+
|
|
166
|
+
# -------------------- 日期区间 --------------------
|
|
167
|
+
@staticmethod
|
|
168
|
+
def date_range(start, end, step=1):
|
|
169
|
+
start = DateUtils.parse(start)
|
|
170
|
+
end = DateUtils.parse(end)
|
|
171
|
+
days = []
|
|
172
|
+
delta = timedelta(days=step)
|
|
173
|
+
current = start
|
|
174
|
+
while current <= end:
|
|
175
|
+
days.append(current)
|
|
176
|
+
current += delta
|
|
177
|
+
return days
|
|
178
|
+
|
|
179
|
+
# -------------------- 自然语言解析 --------------------
|
|
180
|
+
@staticmethod
|
|
181
|
+
def smart_parse(text):
|
|
182
|
+
"""支持 '今天','明天','昨天','上周一','下个月15号'"""
|
|
183
|
+
today = datetime.now()
|
|
184
|
+
text = text.strip()
|
|
185
|
+
if text == "今天":
|
|
186
|
+
return today
|
|
187
|
+
elif text == "明天":
|
|
188
|
+
return today + timedelta(days=1)
|
|
189
|
+
elif text == "昨天":
|
|
190
|
+
return today - timedelta(days=1)
|
|
191
|
+
m = re.match(r"上周([一二三四五六日])", text)
|
|
192
|
+
if m:
|
|
193
|
+
weekday_map = {"一":0,"二":1,"三":2,"四":3,"五":4,"六":5,"日":6}
|
|
194
|
+
target_weekday = weekday_map[m.group(1)]
|
|
195
|
+
delta_days = today.weekday() - target_weekday + 7
|
|
196
|
+
return today - timedelta(days=delta_days)
|
|
197
|
+
# TODO: 可继续扩展自然语言解析
|
|
198
|
+
return DateUtils.parse(text)
|
|
199
|
+
|
|
200
|
+
@staticmethod
|
|
201
|
+
def add_days_str(date_str, days, fmt="%Y-%m-%d"):
|
|
202
|
+
"""直接对字符串日期加减天数,返回字符串"""
|
|
203
|
+
dt = datetime.strptime(date_str, fmt)
|
|
204
|
+
dt += timedelta(days=days)
|
|
205
|
+
return dt.strftime(fmt)
|
|
206
|
+
|
|
207
|
+
@staticmethod
|
|
208
|
+
def format_day(date_str, fmt="%Y-%m-%d"):
|
|
209
|
+
"""直接对字符串日期加减天数,返回字符串"""
|
|
210
|
+
dt = datetime.strptime(date_str, fmt)
|
|
211
|
+
return dt.strftime("%Y年%m月%d日")
|
|
212
|
+
|
|
213
|
+
@staticmethod
|
|
214
|
+
def to_timestamp(dt):
|
|
215
|
+
dt = DateUtils.parse(dt) if isinstance(dt, (str, int, float)) else dt
|
|
216
|
+
return int(dt.timestamp())
|
|
217
|
+
# ------------------------ 测试 ------------------------
|
|
218
|
+
if __name__ == "__main__":
|
|
219
|
+
print("现在时间:", DateUtils.now())
|
|
220
|
+
print("今天:", DateUtils.today())
|
|
221
|
+
print("月初:", DateUtils.start_of_month())
|
|
222
|
+
print("月末:", DateUtils.end_of_month())
|
|
223
|
+
print("季度开始:", DateUtils.start_of_quarter())
|
|
224
|
+
print("季度结束:", DateUtils.end_of_quarter())
|
|
225
|
+
print("加3天:", DateUtils.add_days(datetime.now(), 3))
|
|
226
|
+
print("是否周末:", DateUtils.is_weekend())
|
|
227
|
+
print("自然语言解析 明天:", DateUtils.smart_parse("明天"))
|
|
228
|
+
print("日期区间:", [d.strftime("%Y-%m-%d") for d in DateUtils.date_range("2025-12-01", "2025-12-05")])
|
terryutils/log.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from logging.handlers import TimedRotatingFileHandler
|
|
4
|
+
|
|
5
|
+
# 配置日志
|
|
6
|
+
def setup_logger(app_name="MyApp", log_dir="./logs"):
|
|
7
|
+
os.makedirs(log_dir, exist_ok=True)
|
|
8
|
+
log_filename = os.path.join(log_dir, f'{app_name}.log') # 固定名字,滚动文件自动加日期
|
|
9
|
+
logger = logging.getLogger(app_name)
|
|
10
|
+
logger.setLevel(logging.DEBUG)
|
|
11
|
+
|
|
12
|
+
# 每天生成一个新文件,保留30天
|
|
13
|
+
file_handler = TimedRotatingFileHandler(
|
|
14
|
+
log_filename, when='midnight', interval=1, backupCount=30, encoding='utf-8'
|
|
15
|
+
)
|
|
16
|
+
file_handler.setLevel(logging.DEBUG)
|
|
17
|
+
|
|
18
|
+
console_handler = logging.StreamHandler()
|
|
19
|
+
console_handler.setLevel(logging.INFO)
|
|
20
|
+
|
|
21
|
+
formatter = logging.Formatter(
|
|
22
|
+
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
23
|
+
datefmt='%Y-%m-%d %H:%M:%S'
|
|
24
|
+
)
|
|
25
|
+
file_handler.setFormatter(formatter)
|
|
26
|
+
console_handler.setFormatter(formatter)
|
|
27
|
+
|
|
28
|
+
logger.handlers = []
|
|
29
|
+
logger.addHandler(file_handler)
|
|
30
|
+
logger.addHandler(console_handler)
|
|
31
|
+
|
|
32
|
+
return logger
|
terryutils/mysql_util.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import pymysql
|
|
2
|
+
import traceback
|
|
3
|
+
|
|
4
|
+
import pymysql
|
|
5
|
+
from pymysql.cursors import DictCursor
|
|
6
|
+
|
|
7
|
+
class MysqlUtil:
|
|
8
|
+
def __init__(self, host, user, password, database, port=3306, dict_result=False, debug=True):
|
|
9
|
+
"""
|
|
10
|
+
初始化数据库连接
|
|
11
|
+
:param dict_result: 是否返回 dict
|
|
12
|
+
:param debug: 是否打印 SQL 调试信息
|
|
13
|
+
"""
|
|
14
|
+
self.conn_params = {
|
|
15
|
+
"host": host,
|
|
16
|
+
"user": user,
|
|
17
|
+
"password": password,
|
|
18
|
+
"database": database,
|
|
19
|
+
"port": port,
|
|
20
|
+
"charset": "utf8mb4",
|
|
21
|
+
"autocommit": False, # 手动事务管理
|
|
22
|
+
}
|
|
23
|
+
if dict_result:
|
|
24
|
+
self.conn_params["cursorclass"] = DictCursor
|
|
25
|
+
|
|
26
|
+
self.conn = None
|
|
27
|
+
self.debug = debug
|
|
28
|
+
self._connect()
|
|
29
|
+
|
|
30
|
+
def _connect(self):
|
|
31
|
+
"""建立连接"""
|
|
32
|
+
self.conn = pymysql.connect(**self.conn_params)
|
|
33
|
+
|
|
34
|
+
def _ensure_connection(self):
|
|
35
|
+
"""确保连接可用,失效时自动重连"""
|
|
36
|
+
try:
|
|
37
|
+
self.conn.ping(reconnect=True)
|
|
38
|
+
except Exception:
|
|
39
|
+
self._connect()
|
|
40
|
+
|
|
41
|
+
def _execute(self, func, sql=None, params=None):
|
|
42
|
+
"""
|
|
43
|
+
通用执行器,保证 commit/rollback
|
|
44
|
+
"""
|
|
45
|
+
self._ensure_connection()
|
|
46
|
+
try:
|
|
47
|
+
if self.debug and sql:
|
|
48
|
+
# print(f"[DEBUG SQL] {sql} | params={params}")
|
|
49
|
+
pass
|
|
50
|
+
result = func()
|
|
51
|
+
self.conn.commit()
|
|
52
|
+
return result
|
|
53
|
+
except Exception:
|
|
54
|
+
traceback.print_exc()
|
|
55
|
+
self.conn.rollback()
|
|
56
|
+
raise
|
|
57
|
+
|
|
58
|
+
def execute(self, sql, params=None):
|
|
59
|
+
"""执行单条增删改"""
|
|
60
|
+
def run():
|
|
61
|
+
with self.conn.cursor() as cursor:
|
|
62
|
+
return cursor.execute(sql, params or ())
|
|
63
|
+
return self._execute(run, sql, params)
|
|
64
|
+
|
|
65
|
+
def execute_by_sqls(self, sqls):
|
|
66
|
+
"""执行多条 SQL"""
|
|
67
|
+
def run():
|
|
68
|
+
with self.conn.cursor() as cursor:
|
|
69
|
+
for sql in sqls:
|
|
70
|
+
if self.debug:
|
|
71
|
+
print(f"[DEBUG SQL] {sql}")
|
|
72
|
+
cursor.execute(sql)
|
|
73
|
+
return self._execute(run)
|
|
74
|
+
|
|
75
|
+
def executemany(self, sql, param_list):
|
|
76
|
+
"""批量执行"""
|
|
77
|
+
def run():
|
|
78
|
+
with self.conn.cursor() as cursor:
|
|
79
|
+
return cursor.executemany(sql, param_list)
|
|
80
|
+
return self._execute(run, sql, param_list)
|
|
81
|
+
|
|
82
|
+
def fetchone(self, sql, params=None):
|
|
83
|
+
"""查询一条"""
|
|
84
|
+
def run():
|
|
85
|
+
with self.conn.cursor() as cursor:
|
|
86
|
+
cursor.execute(sql, params or ())
|
|
87
|
+
return cursor.fetchone()
|
|
88
|
+
return self._execute(run, sql, params)
|
|
89
|
+
|
|
90
|
+
def fetchall(self, sql, params=None):
|
|
91
|
+
"""查询多条"""
|
|
92
|
+
def run():
|
|
93
|
+
with self.conn.cursor() as cursor:
|
|
94
|
+
cursor.execute(sql, params or ())
|
|
95
|
+
return cursor.fetchall()
|
|
96
|
+
return self._execute(run, sql, params)
|
|
97
|
+
|
|
98
|
+
def close(self):
|
|
99
|
+
"""关闭连接"""
|
|
100
|
+
if self.conn:
|
|
101
|
+
self.conn.close()
|
|
102
|
+
|
|
103
|
+
def __enter__(self):
|
|
104
|
+
return self
|
|
105
|
+
|
|
106
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
107
|
+
self.close()
|
|
108
|
+
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
terryutils/__init__.py,sha256=rDDyXE01Cmz90yMbiDhWa9AOdsB7JX-KqSmYBYt9PAY,99
|
|
2
|
+
terryutils/date_util.py,sha256=KdsTOd8EjKu7p9gbfBjm0Yd06zokM8WDAD2NfnogPu0,7832
|
|
3
|
+
terryutils/log.py,sha256=ul9DF7-6AwjH-vE7aPB96hFM0OKrYUjjUFmd4xP4678,1063
|
|
4
|
+
terryutils/mysql_util.py,sha256=UwnF8z-KNJlMuOnH03iHVPKkpSNj073YSsrvI3x8pkM,3292
|
|
5
|
+
terryutils-1.0.6.dist-info/METADATA,sha256=4V80JUySGSBCEisXF1jeiBGelCvLGPz7tJ4cre5CpZk,344
|
|
6
|
+
terryutils-1.0.6.dist-info/WHEEL,sha256=WnJ8fYhv8N4SYVK2lLYNI6N0kVATA7b0piVUNvqIIJE,91
|
|
7
|
+
terryutils-1.0.6.dist-info/top_level.txt,sha256=LQtzsZsXViaVLHb_rJ4SJiT9ScUfK6dUoy89dP1mLIo,11
|
|
8
|
+
terryutils-1.0.6.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
terryutils
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
terryutils-1.0.4.dist-info/METADATA,sha256=NfNgRDQzWtg4cW8azjgYPDNTVftt1SklFn6P2KYPe7g,344
|
|
2
|
-
terryutils-1.0.4.dist-info/WHEEL,sha256=WnJ8fYhv8N4SYVK2lLYNI6N0kVATA7b0piVUNvqIIJE,91
|
|
3
|
-
terryutils-1.0.4.dist-info/top_level.txt,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
4
|
-
terryutils-1.0.4.dist-info/RECORD,,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
File without changes
|