diff --git a/admin/server/auth.py b/admin/server/auth.py index bd3c0c058ae..65e51b8f053 100644 --- a/admin/server/auth.py +++ b/admin/server/auth.py @@ -179,8 +179,8 @@ def login_admin(email: str, password: str): resp = user.to_json() user.access_token = get_uuid() login_user(user) - user.update_time = (current_timestamp(),) - user.update_date = (datetime_format(datetime.now()),) + user.update_time = current_timestamp() + user.update_date = datetime_format(datetime.utcnow()) user.last_login_time = get_format_time() user.save() msg = "Welcome back!" diff --git a/admin/server/routes.py b/admin/server/routes.py index 658cec48c09..1d363d97c6f 100644 --- a/admin/server/routes.py +++ b/admin/server/routes.py @@ -504,7 +504,7 @@ def generate_user_api_key(username: str) -> tuple[Response, int]: "token": key, "beta": generate_confirmation_token().replace("ragflow-", "")[:32], "create_time": current_timestamp(), - "create_date": datetime_format(datetime.now()), + "create_date": datetime_format(datetime.utcnow()), "update_time": None, "update_date": None, } diff --git a/api/apps/api_app.py b/api/apps/api_app.py index 0d5d62334ed..155253e7f99 100644 --- a/api/apps/api_app.py +++ b/api/apps/api_app.py @@ -31,12 +31,12 @@ def stats(): tenants[0].tenant_id, request.args.get( "from_date", - (datetime.now() - + (datetime.utcnow() - timedelta( days=7)).strftime("%Y-%m-%d 00:00:00")), request.args.get( "to_date", - datetime.now().strftime("%Y-%m-%d %H:%M:%S")), + datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")), "agent" if "canvas_id" in request.args else None) res = {"pv": [], "uv": [], "speed": [], "tokens": [], "round": [], "thumb_up": []} diff --git a/api/apps/chunk_app.py b/api/apps/chunk_app.py index e6ceb66e695..16db60d0154 100644 --- a/api/apps/chunk_app.py +++ b/api/apps/chunk_app.py @@ -325,8 +325,9 @@ async def create(): if not isinstance(d["question_kwd"], list): return get_data_error_result(message="`question_kwd` is required to be a list") d["question_tks"] = rag_tokenizer.tokenize("\n".join(d["question_kwd"])) - d["create_time"] = str(datetime.datetime.now()).replace("T", " ")[:19] - d["create_timestamp_flt"] = datetime.datetime.now().timestamp() + now_utc = datetime.datetime.now(datetime.timezone.utc) + d["create_time"] = now_utc.strftime("%Y-%m-%d %H:%M:%S") + d["create_timestamp_flt"] = now_utc.timestamp() if "tag_kwd" in req: if not isinstance(req["tag_kwd"], list): return get_data_error_result(message="`tag_kwd` is required to be a list") diff --git a/api/apps/restful_apis/system_api.py b/api/apps/restful_apis/system_api.py index 467d9111d90..9707654263a 100644 --- a/api/apps/restful_apis/system_api.py +++ b/api/apps/restful_apis/system_api.py @@ -143,7 +143,7 @@ def new_token(): "token": generate_confirmation_token(), "beta": generate_confirmation_token().replace("ragflow-", "")[:32], "create_time": current_timestamp(), - "create_date": datetime_format(datetime.now()), + "create_date": datetime_format(datetime.utcnow()), "update_time": None, "update_date": None, } diff --git a/api/apps/sdk/doc.py b/api/apps/sdk/doc.py index 244c6f22924..9396b9c4729 100644 --- a/api/apps/sdk/doc.py +++ b/api/apps/sdk/doc.py @@ -952,8 +952,9 @@ async def add_chunk(tenant_id, dataset_id, document_id): d["important_tks"] = rag_tokenizer.tokenize(" ".join(req.get("important_keywords", []))) d["question_kwd"] = [str(q).strip() for q in req.get("questions", []) if str(q).strip()] d["question_tks"] = rag_tokenizer.tokenize("\n".join(req.get("questions", []))) - d["create_time"] = str(datetime.datetime.now()).replace("T", " ")[:19] - d["create_timestamp_flt"] = datetime.datetime.now().timestamp() + now_utc = datetime.datetime.now(datetime.timezone.utc) + d["create_time"] = now_utc.strftime("%Y-%m-%d %H:%M:%S") + d["create_timestamp_flt"] = now_utc.timestamp() d["kb_id"] = dataset_id d["docnm_kwd"] = doc.name d["doc_id"] = document_id diff --git a/api/apps/system_app.py b/api/apps/system_app.py index 833a7819dd5..e53704faacc 100644 --- a/api/apps/system_app.py +++ b/api/apps/system_app.py @@ -14,7 +14,7 @@ # limitations under the License # import logging -from datetime import datetime +from datetime import datetime, timezone import json from api.apps import login_required @@ -127,7 +127,7 @@ def status(): task_executor_heartbeats = {} try: task_executors = REDIS_CONN.smembers("TASKEXE") - now = datetime.now().timestamp() + now = datetime.now(timezone.utc).timestamp() for task_executor_id in task_executors: heartbeats = REDIS_CONN.zrangebyscore(task_executor_id, now - 60 * 30, now) heartbeats = [json.loads(heartbeat) for heartbeat in heartbeats] diff --git a/api/apps/user_app.py b/api/apps/user_app.py index 74248992696..9024a620d83 100644 --- a/api/apps/user_app.py +++ b/api/apps/user_app.py @@ -127,7 +127,7 @@ async def login(): user.access_token = get_uuid() login_user(user) user.update_time = current_timestamp() - user.update_date = datetime_format(datetime.now()) + user.update_date = datetime_format(datetime.utcnow()) user.save() msg = "Welcome back!" diff --git a/api/db/services/api_service.py b/api/db/services/api_service.py index be41dc1b642..5b4535ef10e 100644 --- a/api/db/services/api_service.py +++ b/api/db/services/api_service.py @@ -30,7 +30,7 @@ class APITokenService(CommonService): def used(cls, token): return cls.model.update({ "update_time": current_timestamp(), - "update_date": datetime_format(datetime.now()), + "update_date": datetime_format(datetime.utcnow()), }).where( cls.model.token == token ) diff --git a/api/db/services/common_service.py b/api/db/services/common_service.py index 8ef4bb94b4f..ed60c4fac56 100644 --- a/api/db/services/common_service.py +++ b/api/db/services/common_service.py @@ -207,7 +207,7 @@ def insert(cls, **kwargs): if "id" not in kwargs: kwargs["id"] = get_uuid() timestamp = current_timestamp() - cur_datetime = datetime_format(datetime.now()) + cur_datetime = datetime_format(datetime.utcnow()) kwargs["create_time"] = timestamp kwargs["create_date"] = cur_datetime kwargs["update_time"] = timestamp @@ -228,7 +228,7 @@ def insert_many(cls, data_list, batch_size=100): batch_size (int, optional): Number of records to insert in each batch. Defaults to 100. """ current_ts = current_timestamp() - current_datetime = datetime_format(datetime.now()) + current_datetime = datetime_format(datetime.utcnow()) with DB.atomic(): for d in data_list: d["create_time"] = current_ts @@ -253,7 +253,7 @@ def update_many_by_id(cls, data_list): """ timestamp = current_timestamp() - cur_datetime = datetime_format(datetime.now()) + cur_datetime = datetime_format(datetime.utcnow()) for data in data_list: data["update_time"] = timestamp data["update_date"] = cur_datetime @@ -272,7 +272,7 @@ def update_by_id(cls, pid, data): # Returns: # Number of records updated data["update_time"] = current_timestamp() - data["update_date"] = datetime_format(datetime.now()) + data["update_date"] = datetime_format(datetime.utcnow()) num = cls.model.update(data).where(cls.model.id == pid).execute() return num diff --git a/api/db/services/connector_service.py b/api/db/services/connector_service.py index 85d495d9d63..fe78c22b82b 100644 --- a/api/db/services/connector_service.py +++ b/api/db/services/connector_service.py @@ -198,7 +198,7 @@ def list_sync_tasks(cls, connector_id=None, page_number=None, items_per_page=15) @classmethod def start(cls, id, connector_id): - cls.update_by_id(id, {"status": TaskStatus.RUNNING, "time_started": datetime.now().strftime('%Y-%m-%d %H:%M:%S') }) + cls.update_by_id(id, {"status": TaskStatus.RUNNING, "time_started": datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S') }) ConnectorService.update_by_id(connector_id, {"status": TaskStatus.RUNNING}) @classmethod diff --git a/api/db/services/dialog_service.py b/api/db/services/dialog_service.py index cadf76c2aa8..1535ea11255 100644 --- a/api/db/services/dialog_service.py +++ b/api/db/services/dialog_service.py @@ -103,7 +103,7 @@ def update_many_by_id(cls, data_list): with DB.atomic(): for data in data_list: data["update_time"] = current_timestamp() - data["update_date"] = datetime_format(datetime.now()) + data["update_date"] = datetime_format(datetime.utcnow()) cls.model.update(data).where(cls.model.id == data["id"]).execute() @classmethod diff --git a/api/db/services/document_service.py b/api/db/services/document_service.py index 2a4f21baf18..c34ddbbc630 100644 --- a/api/db/services/document_service.py +++ b/api/db/services/document_service.py @@ -881,13 +881,14 @@ def _sync_progress(cls, docs: list[dict]): # only for special task and parsed docs and unfinished freeze_progress = special_task_running and doc_progress >= 1 and not finished msg = "\n".join(sorted(msg)) + now_utc = datetime.utcnow() begin_at = d.get("process_begin_at") if not begin_at: - begin_at = datetime.now() + begin_at = now_utc # fallback cls.update_by_id(d["id"], {"process_begin_at": begin_at}) - info = {"process_duration": max(datetime.timestamp(datetime.now()) - begin_at.timestamp(), 0), "run": status} + info = {"process_duration": max((now_utc - begin_at).total_seconds(), 0), "run": status} if prg != 0 and not freeze_progress: info["progress"] = prg if msg: @@ -997,14 +998,15 @@ def queue_raptor_o_graphrag_tasks(sample_doc, ty, priority, fake_doc_id="", doc_ hasher.update(str(chunking_config[field]).encode("utf-8")) def new_task(): + now_utc = datetime.utcnow() return { "id": get_uuid(), "doc_id": fake_doc_id, "from_page": 100000000, "to_page": 100000000, "task_type": ty, - "progress_msg": datetime.now().strftime("%H:%M:%S") + " created task " + ty, - "begin_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "progress_msg": now_utc.strftime("%H:%M:%S") + " created task " + ty, + "begin_at": now_utc.strftime("%Y-%m-%d %H:%M:%S"), } task = new_task() @@ -1079,8 +1081,9 @@ def dummy(prog=None, msg=""): d = deepcopy(doc) d.update(ck) d["id"] = xxhash.xxh64((ck["content_with_weight"] + str(d["doc_id"])).encode("utf-8")).hexdigest() - d["create_time"] = str(datetime.now()).replace("T", " ")[:19] - d["create_timestamp_flt"] = datetime.now().timestamp() + now_utc = datetime.utcnow() + d["create_time"] = now_utc.strftime("%Y-%m-%d %H:%M:%S") + d["create_timestamp_flt"] = now_utc.timestamp() if not d.get("image"): docs.append(d) continue diff --git a/api/db/services/evaluation_service.py b/api/db/services/evaluation_service.py index 48255512f5a..3fb0b4fd85b 100644 --- a/api/db/services/evaluation_service.py +++ b/api/db/services/evaluation_service.py @@ -284,7 +284,7 @@ def start_evaluation(cls, dataset_id: str, dialog_id: str, # Create evaluation run run_id = get_uuid() if not name: - name = f"Evaluation Run {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" + name = f"Evaluation Run {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')} UTC" run = { "id": run_id, diff --git a/api/db/services/file2document_service.py b/api/db/services/file2document_service.py index 0ee15107999..7a4ee606eb0 100644 --- a/api/db/services/file2document_service.py +++ b/api/db/services/file2document_service.py @@ -74,8 +74,8 @@ def delete_by_document_id(cls, doc_id): @DB.connection_context() def update_by_file_id(cls, file_id, obj): obj["update_time"] = current_timestamp() - obj["update_date"] = datetime_format(datetime.now()) - cls.model.update(obj).where(cls.model.id == file_id).execute() + obj["update_date"] = datetime_format(datetime.utcnow()) + cls.model.update(obj).where(cls.model.file_id == file_id).execute() return File2Document(**obj) @classmethod diff --git a/api/db/services/knowledgebase_service.py b/api/db/services/knowledgebase_service.py index c66d66a6821..e5302ff468a 100644 --- a/api/db/services/knowledgebase_service.py +++ b/api/db/services/knowledgebase_service.py @@ -526,7 +526,7 @@ def get_kb_by_name(cls, kb_name, user_id): def atomic_increase_doc_num_by_id(cls, kb_id): data = {} data["update_time"] = current_timestamp() - data["update_date"] = datetime_format(datetime.now()) + data["update_date"] = datetime_format(datetime.utcnow()) data["doc_num"] = cls.model.doc_num + 1 num = cls.model.update(data).where(cls.model.id == kb_id).execute() return num @@ -568,7 +568,7 @@ def decrease_document_num_in_delete(cls, kb_id, doc_num_info: dict): 'chunk_num': kb_row.chunk_num - doc_num_info['chunk_num'], 'token_num': kb_row.token_num - doc_num_info['token_num'], 'update_time': current_timestamp(), - 'update_date': datetime_format(datetime.now()) + 'update_date': datetime_format(datetime.utcnow()) } return cls.model.update(update_dict).where(cls.model.id == kb_id).execute() diff --git a/api/db/services/langfuse_service.py b/api/db/services/langfuse_service.py index b571110aba2..b92a543a602 100644 --- a/api/db/services/langfuse_service.py +++ b/api/db/services/langfuse_service.py @@ -59,13 +59,13 @@ def delete_ty_tenant_id(cls, tenant_id): @classmethod def update_by_tenant(cls, tenant_id, langfuse_keys): langfuse_keys["update_time"] = current_timestamp() - langfuse_keys["update_date"] = datetime_format(datetime.now()) + langfuse_keys["update_date"] = datetime_format(datetime.utcnow()) return cls.model.update(**langfuse_keys).where(cls.model.tenant_id == tenant_id).execute() @classmethod def save(cls, **kwargs): current_ts = current_timestamp() - current_date = datetime_format(datetime.now()) + current_date = datetime_format(datetime.utcnow()) kwargs["create_time"] = current_ts kwargs["create_date"] = current_date diff --git a/api/db/services/pipeline_operation_log_service.py b/api/db/services/pipeline_operation_log_service.py index 344e2381b7e..847c63f9b58 100644 --- a/api/db/services/pipeline_operation_log_service.py +++ b/api/db/services/pipeline_operation_log_service.py @@ -189,7 +189,7 @@ def create(cls, document_id, pipeline_id, task_type, task_id=None, referred_docu avatar=avatar, ) timestamp = current_timestamp() - datetime_now = datetime_format(datetime.now()) + datetime_now = datetime_format(datetime.utcnow()) log["create_time"] = timestamp log["create_date"] = datetime_now log["update_time"] = timestamp diff --git a/api/db/services/search_service.py b/api/db/services/search_service.py index 7366a9708b6..e334c9af39b 100644 --- a/api/db/services/search_service.py +++ b/api/db/services/search_service.py @@ -29,7 +29,7 @@ class SearchService(CommonService): @classmethod def save(cls, **kwargs): current_ts = current_timestamp() - current_date = datetime_format(datetime.now()) + current_date = datetime_format(datetime.utcnow()) kwargs["create_time"] = current_ts kwargs["create_date"] = current_date diff --git a/api/db/services/system_settings_service.py b/api/db/services/system_settings_service.py index eac7019e6a1..c9249ef1d16 100644 --- a/api/db/services/system_settings_service.py +++ b/api/db/services/system_settings_service.py @@ -33,7 +33,7 @@ def get_by_name(cls, name): @DB.connection_context() def update_by_name(cls, name, obj): obj["update_time"] = current_timestamp() - obj["update_date"] = datetime_format(datetime.now()) + obj["update_date"] = datetime_format(datetime.utcnow()) cls.model.update(obj).where(cls.model.name == name).execute() return SystemSettings(**obj) diff --git a/api/db/services/task_service.py b/api/db/services/task_service.py index 80817323076..739703873e6 100644 --- a/api/db/services/task_service.py +++ b/api/db/services/task_service.py @@ -124,7 +124,7 @@ def get_task(cls, task_id, doc_ids=[]): if not docs: return None - msg = f"\n{datetime.now().strftime('%H:%M:%S')} Task has been received." + msg = f"\n{datetime.utcnow().strftime('%H:%M:%S')} Task has been received." prog = random.random() / 10.0 if docs[0]["retry_count"] >= 3: msg = "\nERROR: Task is abandoned after 3 times attempts." @@ -342,7 +342,10 @@ def update_progress(cls, id, info): ((prog == -1) | (prog > cls.model.progress)))) ).execute() - process_duration = (datetime.now() - task.begin_at).total_seconds() + if task.begin_at: + process_duration = max((datetime.utcnow() - task.begin_at).total_seconds(), 0) + else: + process_duration = 0 cls.model.update(process_duration=process_duration).where(cls.model.id == id).execute() @classmethod @@ -380,7 +383,7 @@ def new_task(): "progress": 0.0, "from_page": 0, "to_page": 100000000, - "begin_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "begin_at": datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S"), } parse_task_array = [] @@ -500,7 +503,7 @@ def reuse_prev_task_chunks(task: dict, prev_tasks: list[dict], chunking_config: else: task["progress_msg"] = "" task["progress_msg"] = " ".join( - [datetime.now().strftime("%H:%M:%S"), task["progress_msg"], "Reused previous task's chunks."]) + [datetime.utcnow().strftime("%H:%M:%S"), task["progress_msg"], "Reused previous task's chunks."]) prev_task["chunk_ids"] = "" return len(task["chunk_ids"].split()) @@ -533,7 +536,7 @@ def queue_dataflow(tenant_id:str, flow_id:str, task_id:str, doc_id:str=CANVAS_DE to_page=100000000, task_type="dataflow" if not rerun else "dataflow_rerun", priority=priority, - begin_at= datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + begin_at= datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S"), ) if doc_id not in [CANVAS_DEBUG_DOC_ID, GRAPH_RAPTOR_FAKE_DOC_ID]: TaskService.model.delete().where(TaskService.model.doc_id == doc_id).execute() diff --git a/api/db/services/user_service.py b/api/db/services/user_service.py index 6804dbd445d..fda53f93a8a 100644 --- a/api/db/services/user_service.py +++ b/api/db/services/user_service.py @@ -117,7 +117,7 @@ def save(cls, **kwargs): str(kwargs["password"])) current_ts = current_timestamp() - current_date = datetime_format(datetime.now()) + current_date = datetime_format(datetime.utcnow()) kwargs["create_time"] = current_ts kwargs["create_date"] = current_date @@ -139,7 +139,7 @@ def update_user(cls, user_id, user_dict): with DB.atomic(): if user_dict: user_dict["update_time"] = current_timestamp() - user_dict["update_date"] = datetime_format(datetime.now()) + user_dict["update_date"] = datetime_format(datetime.utcnow()) cls.model.update(user_dict).where( cls.model.id == user_id).execute() @@ -150,7 +150,7 @@ def update_user_password(cls, user_id, new_password): update_dict = { "password": generate_password_hash(str(new_password)), "update_time": current_timestamp(), - "update_date": datetime_format(datetime.now()) + "update_date": datetime_format(datetime.utcnow()) } cls.model.update(update_dict).where(cls.model.id == user_id).execute() diff --git a/api/utils/health_utils.py b/api/utils/health_utils.py index 288eb79ff67..a7cff6c1ef7 100644 --- a/api/utils/health_utils.py +++ b/api/utils/health_utils.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -from datetime import datetime +from datetime import datetime, timezone import json import os import requests @@ -309,7 +309,7 @@ def check_task_executor_alive(): task_executor_heartbeats = {} try: task_executors = REDIS_CONN.smembers("TASKEXE") - now = datetime.now().timestamp() + now = datetime.now(timezone.utc).timestamp() for task_executor_id in task_executors: heartbeats = REDIS_CONN.zrangebyscore(task_executor_id, now - 60 * 30, now) heartbeats = [json.loads(heartbeat) for heartbeat in heartbeats] diff --git a/common/time_utils.py b/common/time_utils.py index f501674e8c9..fd904bdfe66 100644 --- a/common/time_utils.py +++ b/common/time_utils.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import calendar import datetime import logging import time @@ -33,33 +34,33 @@ def current_timestamp(): def timestamp_to_date(timestamp, format_string="%Y-%m-%d %H:%M:%S"): """ - Convert a timestamp to formatted date string. + Convert a timestamp to formatted UTC date string. Args: timestamp: Unix timestamp in milliseconds. If None or empty, uses current time. format_string: Format string for the output date (default: "%Y-%m-%d %H:%M:%S") Returns: - str: Formatted date string + str: Formatted UTC date string Example: >>> timestamp_to_date(1704067200000) - '2024-01-01 08:00:00' + '2024-01-01 00:00:00' """ - if not timestamp: - timestamp = time.time() + if timestamp is None or timestamp == "": + timestamp = current_timestamp() timestamp = int(timestamp) / 1000 - time_array = time.localtime(timestamp) + time_array = time.gmtime(timestamp) str_date = time.strftime(format_string, time_array) return str_date def date_string_to_timestamp(time_str, format_string="%Y-%m-%d %H:%M:%S"): """ - Convert a date string to timestamp in milliseconds. + Convert a UTC date string to timestamp in milliseconds. Args: - time_str: Date string to convert + time_str: UTC date string to convert format_string: Format of the input date string (default: "%Y-%m-%d %H:%M:%S") Returns: @@ -70,48 +71,51 @@ def date_string_to_timestamp(time_str, format_string="%Y-%m-%d %H:%M:%S"): 1704067200000 """ time_array = time.strptime(time_str, format_string) - time_stamp = int(time.mktime(time_array) * 1000) + time_stamp = int(calendar.timegm(time_array) * 1000) return time_stamp def datetime_format(date_time: datetime.datetime) -> datetime.datetime: """ - Normalize a datetime object by removing microsecond component. + Normalize a datetime object by removing microsecond component and converting to UTC. Creates a new datetime object with only year, month, day, hour, minute, second. - Microseconds are set to 0. + Microseconds are set to 0. If the input has timezone info, it is converted to UTC. Args: date_time: datetime object to normalize Returns: - datetime.datetime: New datetime object without microseconds + datetime.datetime: New naive datetime object in UTC without microseconds Example: >>> dt = datetime.datetime(2024, 1, 1, 12, 30, 45, 123456) >>> datetime_format(dt) datetime.datetime(2024, 1, 1, 12, 30, 45) """ + if date_time.tzinfo is not None: + date_time = date_time.astimezone(datetime.timezone.utc).replace(tzinfo=None) return datetime.datetime(date_time.year, date_time.month, date_time.day, date_time.hour, date_time.minute, date_time.second) def get_format_time() -> datetime.datetime: """ - Get current datetime normalized without microseconds. + Get current UTC datetime normalized without microseconds. Returns: - datetime.datetime: Current datetime with microseconds set to 0 + datetime.datetime: Current UTC datetime with microseconds set to 0 Example: >>> get_format_time() datetime.datetime(2024, 1, 1, 12, 30, 45) """ - return datetime_format(datetime.datetime.now()) + return datetime_format(datetime.datetime.utcnow()) def delta_seconds(date_string: str): """ Calculate seconds elapsed from a given date string to now. + Both the input and current time are treated as naive UTC datetimes. Args: date_string: Date string in "YYYY-MM-DD HH:MM:SS" format @@ -124,7 +128,7 @@ def delta_seconds(date_string: str): 3600.0 # If current time is 2024-01-01 13:00:00 """ dt = datetime.datetime.strptime(date_string, "%Y-%m-%d %H:%M:%S") - return (datetime.datetime.now() - dt).total_seconds() + return (datetime.datetime.utcnow() - dt).total_seconds() def format_iso_8601_to_ymd_hms(time_str: str) -> str: