Spaces:
Running
Running
admin
commited on
Commit
·
751f3b1
1
Parent(s):
8fc3eaa
upl ksa
Browse files- .gitattributes +13 -11
- .gitignore +4 -0
- README.md +6 -6
- activate.py +157 -0
- app.py +41 -17
- config.py +34 -0
- restart.py +23 -0
- smtp.py +44 -0
- times.py +21 -0
.gitattributes
CHANGED
@@ -1,35 +1,37 @@
|
|
1 |
*.7z filter=lfs diff=lfs merge=lfs -text
|
2 |
*.arrow filter=lfs diff=lfs merge=lfs -text
|
3 |
*.bin filter=lfs diff=lfs merge=lfs -text
|
|
|
4 |
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
6 |
*.ftz filter=lfs diff=lfs merge=lfs -text
|
7 |
*.gz filter=lfs diff=lfs merge=lfs -text
|
8 |
*.h5 filter=lfs diff=lfs merge=lfs -text
|
9 |
*.joblib filter=lfs diff=lfs merge=lfs -text
|
10 |
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
12 |
*.model filter=lfs diff=lfs merge=lfs -text
|
13 |
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
16 |
*.onnx filter=lfs diff=lfs merge=lfs -text
|
17 |
*.ot filter=lfs diff=lfs merge=lfs -text
|
18 |
*.parquet filter=lfs diff=lfs merge=lfs -text
|
19 |
*.pb filter=lfs diff=lfs merge=lfs -text
|
20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
22 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
23 |
*.pth filter=lfs diff=lfs merge=lfs -text
|
24 |
*.rar filter=lfs diff=lfs merge=lfs -text
|
25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
26 |
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
27 |
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
29 |
*.tflite filter=lfs diff=lfs merge=lfs -text
|
30 |
*.tgz filter=lfs diff=lfs merge=lfs -text
|
31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
32 |
*.xz filter=lfs diff=lfs merge=lfs -text
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
-
*.
|
35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
*.7z filter=lfs diff=lfs merge=lfs -text
|
2 |
*.arrow filter=lfs diff=lfs merge=lfs -text
|
3 |
*.bin filter=lfs diff=lfs merge=lfs -text
|
4 |
+
*.bin.* filter=lfs diff=lfs merge=lfs -text
|
5 |
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
|
|
6 |
*.ftz filter=lfs diff=lfs merge=lfs -text
|
7 |
*.gz filter=lfs diff=lfs merge=lfs -text
|
8 |
*.h5 filter=lfs diff=lfs merge=lfs -text
|
9 |
*.joblib filter=lfs diff=lfs merge=lfs -text
|
10 |
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
|
|
11 |
*.model filter=lfs diff=lfs merge=lfs -text
|
12 |
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
13 |
*.onnx filter=lfs diff=lfs merge=lfs -text
|
14 |
*.ot filter=lfs diff=lfs merge=lfs -text
|
15 |
*.parquet filter=lfs diff=lfs merge=lfs -text
|
16 |
*.pb filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
17 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
18 |
*.pth filter=lfs diff=lfs merge=lfs -text
|
19 |
*.rar filter=lfs diff=lfs merge=lfs -text
|
|
|
20 |
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
21 |
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
|
|
22 |
*.tflite filter=lfs diff=lfs merge=lfs -text
|
23 |
*.tgz filter=lfs diff=lfs merge=lfs -text
|
|
|
24 |
*.xz filter=lfs diff=lfs merge=lfs -text
|
25 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
26 |
+
*.zstandard filter=lfs diff=lfs merge=lfs -text
|
27 |
+
*.tfevents* filter=lfs diff=lfs merge=lfs -text
|
28 |
+
*.db* filter=lfs diff=lfs merge=lfs -text
|
29 |
+
*.ark* filter=lfs diff=lfs merge=lfs -text
|
30 |
+
**/*ckpt*data* filter=lfs diff=lfs merge=lfs -text
|
31 |
+
**/*ckpt*.meta filter=lfs diff=lfs merge=lfs -text
|
32 |
+
**/*ckpt*.index filter=lfs diff=lfs merge=lfs -text
|
33 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
34 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
35 |
+
*.gguf* filter=lfs diff=lfs merge=lfs -text
|
36 |
+
*.ggml filter=lfs diff=lfs merge=lfs -text
|
37 |
+
*.llamafile* filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
test.*
|
2 |
+
rename.sh
|
3 |
+
*__pycache__*
|
4 |
+
*.txt
|
README.md
CHANGED
@@ -1,12 +1,12 @@
|
|
1 |
---
|
2 |
-
title:
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
-
colorTo:
|
6 |
sdk: gradio
|
7 |
-
sdk_version: 5.
|
8 |
app_file: app.py
|
9 |
pinned: false
|
10 |
license: apache-2.0
|
11 |
-
short_description:
|
12 |
---
|
|
|
1 |
---
|
2 |
+
title: ksa
|
3 |
+
emoji: 📊
|
4 |
+
colorFrom: yellow
|
5 |
+
colorTo: blue
|
6 |
sdk: gradio
|
7 |
+
sdk_version: 5.22.0
|
8 |
app_file: app.py
|
9 |
pinned: false
|
10 |
license: apache-2.0
|
11 |
+
short_description: --
|
12 |
---
|
activate.py
ADDED
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
import time
|
3 |
+
import requests
|
4 |
+
import threading
|
5 |
+
from tqdm import tqdm
|
6 |
+
from huggingface_hub import HfApi
|
7 |
+
from smtp import send_email
|
8 |
+
from config import *
|
9 |
+
|
10 |
+
|
11 |
+
def get_space_status(repo_id: str):
|
12 |
+
response: list = requests.get(
|
13 |
+
url=f"{HF_DOMAIN}/api/spaces/semantic-search",
|
14 |
+
params={"q": repo_id},
|
15 |
+
headers=HEADER,
|
16 |
+
timeout=TIMEOUT,
|
17 |
+
).json()
|
18 |
+
|
19 |
+
if not (response and response[0]["id"] == repo_id):
|
20 |
+
return "SLEEPING" # 口袋罪
|
21 |
+
|
22 |
+
return response[0]["runtime"]["stage"]
|
23 |
+
|
24 |
+
|
25 |
+
def get_spaces(username: str):
|
26 |
+
sleepings, errors = [], []
|
27 |
+
try:
|
28 |
+
spaces = HfApi().list_spaces(author=username)
|
29 |
+
for space in spaces:
|
30 |
+
status = get_space_status(space.id)
|
31 |
+
if status == "SLEEPING":
|
32 |
+
space_id = space.id.replace("/", "-").replace("_", "-").lower()
|
33 |
+
if space.sdk == "gradio":
|
34 |
+
sleepings.append(f"https://{space_id}.hf.space")
|
35 |
+
else:
|
36 |
+
sleepings.append(f"https://{space_id}.static.hf.space")
|
37 |
+
|
38 |
+
elif "ERROR" in status:
|
39 |
+
errors.append(f"{HF_DOMAIN}/spaces/{space.id}")
|
40 |
+
|
41 |
+
return sleepings, errors
|
42 |
+
|
43 |
+
except Exception as e:
|
44 |
+
print(f"An error occurred in the request: {e}")
|
45 |
+
|
46 |
+
return [], []
|
47 |
+
|
48 |
+
|
49 |
+
def activate_space(url: str):
|
50 |
+
try:
|
51 |
+
response = requests.get(url, headers=HEADER, timeout=TIMEOUT)
|
52 |
+
response.raise_for_status()
|
53 |
+
|
54 |
+
except Exception as e:
|
55 |
+
print(e)
|
56 |
+
|
57 |
+
|
58 |
+
def get_studios(username: str):
|
59 |
+
try:
|
60 |
+
response = requests.put(
|
61 |
+
f"{MS_DOMAIN}/api/v1/studios/{username}/list",
|
62 |
+
data=json.dumps(
|
63 |
+
{
|
64 |
+
"PageNumber": 1,
|
65 |
+
"PageSize": 1000,
|
66 |
+
"Name": "",
|
67 |
+
"SortBy": "gmt_modified",
|
68 |
+
"Order": "desc",
|
69 |
+
}
|
70 |
+
),
|
71 |
+
headers=HEADER,
|
72 |
+
timeout=TIMEOUT,
|
73 |
+
)
|
74 |
+
response.raise_for_status()
|
75 |
+
spaces: list = response.json()["Data"]["Studios"]
|
76 |
+
if spaces:
|
77 |
+
studios, errors = [], []
|
78 |
+
for space in spaces:
|
79 |
+
status = space["Status"]
|
80 |
+
if status == "Expired":
|
81 |
+
studios.append(f"{username}/{space['Name']}")
|
82 |
+
elif status == "Failed":
|
83 |
+
errors.append(f"{MS_DOMAIN}/studios/{username}/{space['Name']}")
|
84 |
+
|
85 |
+
return studios, errors
|
86 |
+
|
87 |
+
except requests.exceptions.Timeout as e:
|
88 |
+
print(f"Timeout: {e}, retrying...")
|
89 |
+
time.sleep(DELAY)
|
90 |
+
return get_studios(username)
|
91 |
+
|
92 |
+
except Exception as e:
|
93 |
+
print(f"Requesting error: {e}")
|
94 |
+
|
95 |
+
return [], []
|
96 |
+
|
97 |
+
|
98 |
+
def activate_studio(repo: str, holding_delay=5):
|
99 |
+
repo_page = f"{MS_DOMAIN}/studios/{repo}"
|
100 |
+
status_api = f"{MS_DOMAIN}/api/v1/studio/{repo}/status"
|
101 |
+
start_expired_api = f"{MS_DOMAIN}/api/v1/studio/{repo}/start_expired"
|
102 |
+
try:
|
103 |
+
response = requests.put(start_expired_api, headers=HEADER, timeout=TIMEOUT)
|
104 |
+
response.raise_for_status()
|
105 |
+
while (
|
106 |
+
requests.get(status_api, headers=HEADER, timeout=TIMEOUT).json()["Data"][
|
107 |
+
"Status"
|
108 |
+
]
|
109 |
+
!= "Running"
|
110 |
+
):
|
111 |
+
requests.get(repo_page, headers=HEADER, timeout=TIMEOUT)
|
112 |
+
time.sleep(holding_delay)
|
113 |
+
|
114 |
+
except requests.exceptions.Timeout as e:
|
115 |
+
print(f"Failed to activate {repo}: {e}, retrying...")
|
116 |
+
activate_studio(repo)
|
117 |
+
|
118 |
+
except Exception as e:
|
119 |
+
print(e)
|
120 |
+
|
121 |
+
|
122 |
+
def trigger(users=USERS):
|
123 |
+
spaces, studios, failures = [], [], []
|
124 |
+
usernames = users.split(";")
|
125 |
+
for user in tqdm(usernames, desc="Collecting spaces"):
|
126 |
+
username = user.strip()
|
127 |
+
if username:
|
128 |
+
sleeps, errors = get_spaces(username)
|
129 |
+
spaces += sleeps
|
130 |
+
failures += errors
|
131 |
+
time.sleep(DELAY)
|
132 |
+
|
133 |
+
for space in tqdm(spaces, desc="Activating spaces"):
|
134 |
+
activate_space(space)
|
135 |
+
time.sleep(DELAY)
|
136 |
+
|
137 |
+
for user in tqdm(usernames, desc="Collecting studios"):
|
138 |
+
username = user.strip()
|
139 |
+
if username:
|
140 |
+
sleeps, errors = get_studios(username)
|
141 |
+
studios += sleeps
|
142 |
+
failures += errors
|
143 |
+
time.sleep(DELAY)
|
144 |
+
|
145 |
+
for studio in tqdm(studios, desc="Activating studios"):
|
146 |
+
threading.Thread(target=activate_studio, args=(studio,), daemon=True).start()
|
147 |
+
time.sleep(DELAY)
|
148 |
+
|
149 |
+
print("\n".join(spaces + studios) + "\nActivation complete!")
|
150 |
+
content = ""
|
151 |
+
for failure in failures:
|
152 |
+
errepo: str = failure
|
153 |
+
errepo = errepo.replace(HF_DOMAIN, "").replace(MS_DOMAIN, "")
|
154 |
+
content += f"<br><a href='{failure}'>{errepo[1:]}</a><br>"
|
155 |
+
|
156 |
+
if content:
|
157 |
+
send_email(content)
|
app.py
CHANGED
@@ -1,27 +1,51 @@
|
|
1 |
-
import
|
|
|
|
|
2 |
import gradio as gr
|
|
|
|
|
|
|
|
|
3 |
|
4 |
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
text=True,
|
13 |
-
)
|
14 |
|
15 |
-
|
16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
|
18 |
|
19 |
if __name__ == "__main__":
|
|
|
20 |
gr.Interface(
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
description="输入一个 Linux 命令, 点击提交查看其执行结果",
|
26 |
flagging_mode="never",
|
27 |
).launch()
|
|
|
1 |
+
import time
|
2 |
+
import schedule
|
3 |
+
import threading
|
4 |
import gradio as gr
|
5 |
+
from activate import trigger
|
6 |
+
from restart import restart
|
7 |
+
from config import *
|
8 |
+
from times import *
|
9 |
|
10 |
|
11 |
+
def run_schedule():
|
12 |
+
global START_TIME
|
13 |
+
START_TIME = datetime.now()
|
14 |
+
trigger()
|
15 |
+
while True:
|
16 |
+
schedule.run_pending()
|
17 |
+
time.sleep(DELAY)
|
|
|
|
|
18 |
|
19 |
+
|
20 |
+
def monitor(period=PERIOD):
|
21 |
+
print(f"Monitor is on and triggered every {period}h...")
|
22 |
+
schedule.every(int(period)).hours.do(trigger)
|
23 |
+
threading.Thread(target=run_schedule, daemon=True).start()
|
24 |
+
schedule.every(47).hours.do(restart)
|
25 |
+
|
26 |
+
|
27 |
+
def tasklist():
|
28 |
+
running4 = f"Has been running for {time_diff(START_TIME, datetime.now())}"
|
29 |
+
print(running4)
|
30 |
+
outputs = ""
|
31 |
+
jobs = schedule.get_jobs()
|
32 |
+
for job in jobs:
|
33 |
+
last_run = fix_datetime(job.last_run)
|
34 |
+
if not last_run:
|
35 |
+
last_run = "never"
|
36 |
+
|
37 |
+
next_run = fix_datetime(job.next_run)
|
38 |
+
outputs += f"Every {job.interval}h do {job.job_func} (last run: {last_run}, next run: {next_run})\n\n"
|
39 |
+
|
40 |
+
return f"{outputs}{running4}"
|
41 |
|
42 |
|
43 |
if __name__ == "__main__":
|
44 |
+
monitor()
|
45 |
gr.Interface(
|
46 |
+
title="See current task status",
|
47 |
+
fn=tasklist,
|
48 |
+
inputs=None,
|
49 |
+
outputs=gr.TextArea(label="Current task details"),
|
|
|
50 |
flagging_mode="never",
|
51 |
).launch()
|
config.py
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
|
3 |
+
PERIOD = os.getenv("period")
|
4 |
+
USERS = os.getenv("users")
|
5 |
+
API = os.getenv("api")
|
6 |
+
EMAIL = os.getenv("email")
|
7 |
+
SMTP = os.getenv("smtp")
|
8 |
+
COOKIE = os.getenv("cookie")
|
9 |
+
if not (PERIOD and USERS and API and EMAIL and SMTP and COOKIE):
|
10 |
+
print("Please check the environment variables!")
|
11 |
+
exit()
|
12 |
+
else:
|
13 |
+
PERIOD = int(PERIOD)
|
14 |
+
|
15 |
+
TAG = os.getenv("target")
|
16 |
+
if not TAG:
|
17 |
+
TAG = EMAIL
|
18 |
+
|
19 |
+
HOST = os.getenv("host")
|
20 |
+
PORT = os.getenv("port")
|
21 |
+
if not (HOST and PORT):
|
22 |
+
HOST = "smtp.qq.com"
|
23 |
+
PORT = 587
|
24 |
+
else:
|
25 |
+
PORT = int(PORT)
|
26 |
+
|
27 |
+
DELAY = 1
|
28 |
+
TIMEOUT = 15
|
29 |
+
START_TIME = None
|
30 |
+
HF_DOMAIN = "https://huggingface.co"
|
31 |
+
MS_DOMAIN = "https://www.modelscope.cn"
|
32 |
+
HEADER = {
|
33 |
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537"
|
34 |
+
}
|
restart.py
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import requests
|
2 |
+
from smtp import send_email
|
3 |
+
from config import *
|
4 |
+
|
5 |
+
|
6 |
+
def restart_private_space(repo: str, cookie: str):
|
7 |
+
try:
|
8 |
+
response = requests.post(
|
9 |
+
f"{HF_DOMAIN}/api/spaces/{repo}/restart",
|
10 |
+
headers={
|
11 |
+
"User-Agent": HEADER["User-Agent"],
|
12 |
+
"cookie": cookie,
|
13 |
+
},
|
14 |
+
timeout=TIMEOUT,
|
15 |
+
)
|
16 |
+
response.raise_for_status()
|
17 |
+
|
18 |
+
except Exception as e:
|
19 |
+
send_email(f"{e}", "Restart failure", f"Failed to restart {repo}")
|
20 |
+
|
21 |
+
|
22 |
+
def restart():
|
23 |
+
restart_private_space("kakamond/ksa", COOKIE)
|
smtp.py
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import requests
|
2 |
+
from config import *
|
3 |
+
|
4 |
+
|
5 |
+
def send_email(
|
6 |
+
content,
|
7 |
+
title="Runtime error detected",
|
8 |
+
header="Please fix following repo(s):",
|
9 |
+
email=EMAIL,
|
10 |
+
password=SMTP,
|
11 |
+
target=TAG,
|
12 |
+
host=HOST,
|
13 |
+
port=PORT,
|
14 |
+
):
|
15 |
+
body = f"""
|
16 |
+
<html>
|
17 |
+
<body>
|
18 |
+
<h2>{header}</h2>
|
19 |
+
{content}
|
20 |
+
</body>
|
21 |
+
</html>
|
22 |
+
"""
|
23 |
+
response = requests.get(
|
24 |
+
API,
|
25 |
+
params={
|
26 |
+
"host": host,
|
27 |
+
"Port": port,
|
28 |
+
"key": password, # apikey
|
29 |
+
"email": email, # from
|
30 |
+
"mail": target, # to
|
31 |
+
"title": title, # subject
|
32 |
+
"name": "ksa", # nickname
|
33 |
+
"text": body, # content
|
34 |
+
},
|
35 |
+
)
|
36 |
+
if response.status_code == 200:
|
37 |
+
result: dict = response.json()
|
38 |
+
if result.get("status") == "success":
|
39 |
+
print("Email sent successfully!")
|
40 |
+
else:
|
41 |
+
print(f"Failed to send email: {result.get('message')}")
|
42 |
+
|
43 |
+
else:
|
44 |
+
print(f"Request failed with status code: {response.status_code}")
|
times.py
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from datetime import datetime
|
2 |
+
from zoneinfo import ZoneInfo
|
3 |
+
from tzlocal import get_localzone
|
4 |
+
|
5 |
+
|
6 |
+
def fix_datetime(naive_time: datetime, target_tz=ZoneInfo("Asia/Shanghai")):
|
7 |
+
if not naive_time:
|
8 |
+
return None
|
9 |
+
|
10 |
+
local_tz = get_localzone()
|
11 |
+
aware_local = naive_time.replace(tzinfo=local_tz)
|
12 |
+
return aware_local.astimezone(target_tz).strftime("%Y-%m-%d %H:%M:%S")
|
13 |
+
|
14 |
+
|
15 |
+
def time_diff(time1: datetime, time2: datetime):
|
16 |
+
t_diff = time2 - time1
|
17 |
+
days = t_diff.days
|
18 |
+
hours = t_diff.seconds // 3600
|
19 |
+
minutes = (t_diff.seconds % 3600) // 60
|
20 |
+
seconds = t_diff.seconds % 60
|
21 |
+
return f"{days} d {hours} h {minutes} m {seconds} s"
|