mirror of
https://github.com/freqtrade/freqtrade.git
synced 2025-12-17 05:11:15 +00:00
refactor: Update timezone.utc to UTC
This commit is contained in:
@@ -5,7 +5,7 @@ Freqtrade is the main module of this bot. It contains the class Freqtrade()
|
|||||||
import logging
|
import logging
|
||||||
import traceback
|
import traceback
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from datetime import datetime, time, timedelta, timezone
|
from datetime import UTC, datetime, time, timedelta
|
||||||
from math import isclose
|
from math import isclose
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
from time import sleep
|
from time import sleep
|
||||||
@@ -266,7 +266,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)(
|
strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)(
|
||||||
current_time=datetime.now(timezone.utc)
|
current_time=datetime.now(UTC)
|
||||||
)
|
)
|
||||||
|
|
||||||
with self._measure_execution:
|
with self._measure_execution:
|
||||||
@@ -296,7 +296,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
self._schedule.run_pending()
|
self._schedule.run_pending()
|
||||||
Trade.commit()
|
Trade.commit()
|
||||||
self.rpc.process_msg_queue(self.dataprovider._msg_queue)
|
self.rpc.process_msg_queue(self.dataprovider._msg_queue)
|
||||||
self.last_process = datetime.now(timezone.utc)
|
self.last_process = datetime.now(UTC)
|
||||||
|
|
||||||
def process_stopped(self) -> None:
|
def process_stopped(self) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -421,7 +421,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
|
|
||||||
except InvalidOrderException as e:
|
except InvalidOrderException as e:
|
||||||
logger.warning(f"Error updating Order {order.order_id} due to {e}.")
|
logger.warning(f"Error updating Order {order.order_id} due to {e}.")
|
||||||
if order.order_date_utc - timedelta(days=5) < datetime.now(timezone.utc):
|
if order.order_date_utc - timedelta(days=5) < datetime.now(UTC):
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Order is older than 5 days. Assuming order was fully cancelled."
|
"Order is older than 5 days. Assuming order was fully cancelled."
|
||||||
)
|
)
|
||||||
@@ -755,7 +755,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
logger.debug(f"Calling adjust_trade_position for pair {trade.pair}")
|
logger.debug(f"Calling adjust_trade_position for pair {trade.pair}")
|
||||||
stake_amount, order_tag = self.strategy._adjust_trade_position_internal(
|
stake_amount, order_tag = self.strategy._adjust_trade_position_internal(
|
||||||
trade=trade,
|
trade=trade,
|
||||||
current_time=datetime.now(timezone.utc),
|
current_time=datetime.now(UTC),
|
||||||
current_rate=current_entry_rate,
|
current_rate=current_entry_rate,
|
||||||
current_profit=current_entry_profit,
|
current_profit=current_entry_profit,
|
||||||
min_stake=min_entry_stake,
|
min_stake=min_entry_stake,
|
||||||
@@ -916,7 +916,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
amount=amount,
|
amount=amount,
|
||||||
rate=enter_limit_requested,
|
rate=enter_limit_requested,
|
||||||
time_in_force=time_in_force,
|
time_in_force=time_in_force,
|
||||||
current_time=datetime.now(timezone.utc),
|
current_time=datetime.now(UTC),
|
||||||
entry_tag=enter_tag,
|
entry_tag=enter_tag,
|
||||||
side=trade_side,
|
side=trade_side,
|
||||||
):
|
):
|
||||||
@@ -987,7 +987,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
# Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL
|
# Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL
|
||||||
fee = self.exchange.get_fee(symbol=pair, taker_or_maker="maker")
|
fee = self.exchange.get_fee(symbol=pair, taker_or_maker="maker")
|
||||||
base_currency = self.exchange.get_pair_base_currency(pair)
|
base_currency = self.exchange.get_pair_base_currency(pair)
|
||||||
open_date = datetime.now(timezone.utc)
|
open_date = datetime.now(UTC)
|
||||||
|
|
||||||
funding_fees = self.exchange.get_funding_fees(
|
funding_fees = self.exchange.get_funding_fees(
|
||||||
pair=pair,
|
pair=pair,
|
||||||
@@ -1106,7 +1106,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
)(
|
)(
|
||||||
pair=pair,
|
pair=pair,
|
||||||
trade=trade,
|
trade=trade,
|
||||||
current_time=datetime.now(timezone.utc),
|
current_time=datetime.now(UTC),
|
||||||
proposed_rate=enter_limit_requested,
|
proposed_rate=enter_limit_requested,
|
||||||
entry_tag=entry_tag,
|
entry_tag=entry_tag,
|
||||||
side=trade_side,
|
side=trade_side,
|
||||||
@@ -1124,7 +1124,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
else:
|
else:
|
||||||
leverage = strategy_safe_wrapper(self.strategy.leverage, default_retval=1.0)(
|
leverage = strategy_safe_wrapper(self.strategy.leverage, default_retval=1.0)(
|
||||||
pair=pair,
|
pair=pair,
|
||||||
current_time=datetime.now(timezone.utc),
|
current_time=datetime.now(UTC),
|
||||||
current_rate=enter_limit_requested,
|
current_rate=enter_limit_requested,
|
||||||
proposed_leverage=1.0,
|
proposed_leverage=1.0,
|
||||||
max_leverage=max_leverage,
|
max_leverage=max_leverage,
|
||||||
@@ -1157,7 +1157,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
self.strategy.custom_stake_amount, default_retval=stake_amount
|
self.strategy.custom_stake_amount, default_retval=stake_amount
|
||||||
)(
|
)(
|
||||||
pair=pair,
|
pair=pair,
|
||||||
current_time=datetime.now(timezone.utc),
|
current_time=datetime.now(UTC),
|
||||||
current_rate=enter_limit_requested,
|
current_rate=enter_limit_requested,
|
||||||
proposed_stake=stake_amount,
|
proposed_stake=stake_amount,
|
||||||
min_stake=min_stake_amount,
|
min_stake=min_stake_amount,
|
||||||
@@ -1222,7 +1222,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
"quote_currency": self.exchange.get_pair_quote_currency(trade.pair),
|
"quote_currency": self.exchange.get_pair_quote_currency(trade.pair),
|
||||||
"fiat_currency": self.config.get("fiat_display_currency", None),
|
"fiat_currency": self.config.get("fiat_display_currency", None),
|
||||||
"amount": order.safe_amount_after_fee if fill else (order.safe_amount or trade.amount),
|
"amount": order.safe_amount_after_fee if fill else (order.safe_amount or trade.amount),
|
||||||
"open_date": trade.open_date_utc or datetime.now(timezone.utc),
|
"open_date": trade.open_date_utc or datetime.now(UTC),
|
||||||
"current_rate": current_rate,
|
"current_rate": current_rate,
|
||||||
"sub_trade": sub_trade,
|
"sub_trade": sub_trade,
|
||||||
}
|
}
|
||||||
@@ -1361,7 +1361,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
exits: list[ExitCheckTuple] = self.strategy.should_exit(
|
exits: list[ExitCheckTuple] = self.strategy.should_exit(
|
||||||
trade,
|
trade,
|
||||||
exit_rate,
|
exit_rate,
|
||||||
datetime.now(timezone.utc),
|
datetime.now(UTC),
|
||||||
enter=enter,
|
enter=enter,
|
||||||
exit_=exit_,
|
exit_=exit_,
|
||||||
force_stoploss=0,
|
force_stoploss=0,
|
||||||
@@ -1496,7 +1496,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
if self.exchange.stoploss_adjust(stoploss_norm, order, side=trade.exit_side):
|
if self.exchange.stoploss_adjust(stoploss_norm, order, side=trade.exit_side):
|
||||||
# we check if the update is necessary
|
# we check if the update is necessary
|
||||||
update_beat = self.strategy.order_types.get("stoploss_on_exchange_interval", 60)
|
update_beat = self.strategy.order_types.get("stoploss_on_exchange_interval", 60)
|
||||||
upd_req = datetime.now(timezone.utc) - timedelta(seconds=update_beat)
|
upd_req = datetime.now(UTC) - timedelta(seconds=update_beat)
|
||||||
if trade.stoploss_last_update_utc and upd_req >= trade.stoploss_last_update_utc:
|
if trade.stoploss_last_update_utc and upd_req >= trade.stoploss_last_update_utc:
|
||||||
# cancelling the current stoploss on exchange first
|
# cancelling the current stoploss on exchange first
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -1584,7 +1584,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
if fully_cancelled or (
|
if fully_cancelled or (
|
||||||
open_order
|
open_order
|
||||||
and self.strategy.ft_check_timed_out(
|
and self.strategy.ft_check_timed_out(
|
||||||
trade, open_order, datetime.now(timezone.utc)
|
trade, open_order, datetime.now(UTC)
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
self.handle_cancel_order(
|
self.handle_cancel_order(
|
||||||
@@ -1683,7 +1683,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
trade=trade,
|
trade=trade,
|
||||||
order=order_obj,
|
order=order_obj,
|
||||||
pair=trade.pair,
|
pair=trade.pair,
|
||||||
current_time=datetime.now(timezone.utc),
|
current_time=datetime.now(UTC),
|
||||||
proposed_rate=proposed_rate,
|
proposed_rate=proposed_rate,
|
||||||
current_order_rate=order_obj.safe_placement_price,
|
current_order_rate=order_obj.safe_placement_price,
|
||||||
entry_tag=trade.enter_tag,
|
entry_tag=trade.enter_tag,
|
||||||
@@ -2075,7 +2075,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
)(
|
)(
|
||||||
pair=trade.pair,
|
pair=trade.pair,
|
||||||
trade=trade,
|
trade=trade,
|
||||||
current_time=datetime.now(timezone.utc),
|
current_time=datetime.now(UTC),
|
||||||
proposed_rate=proposed_limit_rate,
|
proposed_rate=proposed_limit_rate,
|
||||||
current_profit=current_profit,
|
current_profit=current_profit,
|
||||||
exit_tag=exit_reason,
|
exit_tag=exit_reason,
|
||||||
@@ -2106,7 +2106,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
time_in_force=time_in_force,
|
time_in_force=time_in_force,
|
||||||
exit_reason=exit_reason,
|
exit_reason=exit_reason,
|
||||||
sell_reason=exit_reason, # sellreason -> compatibility
|
sell_reason=exit_reason, # sellreason -> compatibility
|
||||||
current_time=datetime.now(timezone.utc),
|
current_time=datetime.now(UTC),
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
logger.info(f"User denied exit for {trade.pair}.")
|
logger.info(f"User denied exit for {trade.pair}.")
|
||||||
@@ -2202,7 +2202,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
"enter_tag": trade.enter_tag,
|
"enter_tag": trade.enter_tag,
|
||||||
"exit_reason": trade.exit_reason,
|
"exit_reason": trade.exit_reason,
|
||||||
"open_date": trade.open_date_utc,
|
"open_date": trade.open_date_utc,
|
||||||
"close_date": trade.close_date_utc or datetime.now(timezone.utc),
|
"close_date": trade.close_date_utc or datetime.now(UTC),
|
||||||
"stake_amount": trade.stake_amount,
|
"stake_amount": trade.stake_amount,
|
||||||
"stake_currency": self.config["stake_currency"],
|
"stake_currency": self.config["stake_currency"],
|
||||||
"base_currency": self.exchange.get_pair_base_currency(trade.pair),
|
"base_currency": self.exchange.get_pair_base_currency(trade.pair),
|
||||||
@@ -2257,7 +2257,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
"enter_tag": trade.enter_tag,
|
"enter_tag": trade.enter_tag,
|
||||||
"exit_reason": trade.exit_reason,
|
"exit_reason": trade.exit_reason,
|
||||||
"open_date": trade.open_date,
|
"open_date": trade.open_date,
|
||||||
"close_date": trade.close_date or datetime.now(timezone.utc),
|
"close_date": trade.close_date or datetime.now(UTC),
|
||||||
"stake_currency": self.config["stake_currency"],
|
"stake_currency": self.config["stake_currency"],
|
||||||
"base_currency": self.exchange.get_pair_base_currency(trade.pair),
|
"base_currency": self.exchange.get_pair_base_currency(trade.pair),
|
||||||
"quote_currency": self.exchange.get_pair_quote_currency(trade.pair),
|
"quote_currency": self.exchange.get_pair_quote_currency(trade.pair),
|
||||||
@@ -2338,7 +2338,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
def _update_trade_after_fill(self, trade: Trade, order: Order, send_msg: bool) -> Trade:
|
def _update_trade_after_fill(self, trade: Trade, order: Order, send_msg: bool) -> Trade:
|
||||||
if order.status in constants.NON_OPEN_EXCHANGE_STATES:
|
if order.status in constants.NON_OPEN_EXCHANGE_STATES:
|
||||||
strategy_safe_wrapper(self.strategy.order_filled, default_retval=None)(
|
strategy_safe_wrapper(self.strategy.order_filled, default_retval=None)(
|
||||||
pair=trade.pair, trade=trade, order=order, current_time=datetime.now(timezone.utc)
|
pair=trade.pair, trade=trade, order=order, current_time=datetime.now(UTC)
|
||||||
)
|
)
|
||||||
# If a entry order was closed, force update on stoploss on exchange
|
# If a entry order was closed, force update on stoploss on exchange
|
||||||
if order.ft_order_side == trade.entry_side:
|
if order.ft_order_side == trade.entry_side:
|
||||||
@@ -2371,7 +2371,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
)
|
)
|
||||||
profit = trade.calc_profit_ratio(current_rate)
|
profit = trade.calc_profit_ratio(current_rate)
|
||||||
self.strategy.ft_stoploss_adjust(
|
self.strategy.ft_stoploss_adjust(
|
||||||
current_rate, trade, datetime.now(timezone.utc), profit, 0, after_fill=True
|
current_rate, trade, datetime.now(UTC), profit, 0, after_fill=True
|
||||||
)
|
)
|
||||||
# Updating wallets when order is closed
|
# Updating wallets when order is closed
|
||||||
self.wallets.update()
|
self.wallets.update()
|
||||||
@@ -2397,7 +2397,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
|
|
||||||
def handle_protections(self, pair: str, side: LongShort) -> None:
|
def handle_protections(self, pair: str, side: LongShort) -> None:
|
||||||
# Lock pair for one candle to prevent immediate re-entries
|
# Lock pair for one candle to prevent immediate re-entries
|
||||||
self.strategy.lock_pair(pair, datetime.now(timezone.utc), reason="Auto lock", side=side)
|
self.strategy.lock_pair(pair, datetime.now(UTC), reason="Auto lock", side=side)
|
||||||
prot_trig = self.protections.stop_per_pair(pair, side=side)
|
prot_trig = self.protections.stop_per_pair(pair, side=side)
|
||||||
if prot_trig:
|
if prot_trig:
|
||||||
msg: RPCProtectionMsg = {
|
msg: RPCProtectionMsg = {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import secrets
|
import secrets
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import UTC, datetime, timedelta
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import jwt
|
import jwt
|
||||||
@@ -89,15 +89,15 @@ async def validate_ws_token(
|
|||||||
def create_token(data: dict, secret_key: str, token_type: str = "access") -> str: # noqa: S107
|
def create_token(data: dict, secret_key: str, token_type: str = "access") -> str: # noqa: S107
|
||||||
to_encode = data.copy()
|
to_encode = data.copy()
|
||||||
if token_type == "access": # noqa: S105
|
if token_type == "access": # noqa: S105
|
||||||
expire = datetime.now(timezone.utc) + timedelta(minutes=15)
|
expire = datetime.now(UTC) + timedelta(minutes=15)
|
||||||
elif token_type == "refresh": # noqa: S105
|
elif token_type == "refresh": # noqa: S105
|
||||||
expire = datetime.now(timezone.utc) + timedelta(days=30)
|
expire = datetime.now(UTC) + timedelta(days=30)
|
||||||
else:
|
else:
|
||||||
raise ValueError()
|
raise ValueError()
|
||||||
to_encode.update(
|
to_encode.update(
|
||||||
{
|
{
|
||||||
"exp": expire,
|
"exp": expire,
|
||||||
"iat": datetime.now(timezone.utc),
|
"iat": datetime.now(UTC),
|
||||||
"type": token_type,
|
"type": token_type,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from typing import Any, Literal
|
from typing import Any, Literal, NotRequired
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from typing_extensions import NotRequired, TypedDict
|
from typing_extensions import TypedDict
|
||||||
|
|
||||||
from freqtrade.exchange.exchange import Exchange
|
from freqtrade.exchange.exchange import Exchange
|
||||||
|
|
||||||
|
|||||||
@@ -266,7 +266,7 @@ class ExternalMessageConsumer:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"Error handling producer message: {e}")
|
logger.exception(f"Error handling producer message: {e}")
|
||||||
|
|
||||||
except (asyncio.TimeoutError, websockets.exceptions.ConnectionClosed):
|
except (TimeoutError, websockets.exceptions.ConnectionClosed):
|
||||||
# We haven't received data yet. Check the connection and continue.
|
# We haven't received data yet. Check the connection and continue.
|
||||||
try:
|
try:
|
||||||
# ping
|
# ping
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ This module defines the interface to apply for strategies
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import UTC, datetime, timedelta
|
||||||
from math import isinf, isnan
|
from math import isinf, isnan
|
||||||
|
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
@@ -1149,7 +1149,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
manually from within the strategy, to allow an easy way to unlock pairs.
|
manually from within the strategy, to allow an easy way to unlock pairs.
|
||||||
:param pair: Unlock pair to allow trading again
|
:param pair: Unlock pair to allow trading again
|
||||||
"""
|
"""
|
||||||
PairLocks.unlock_pair(pair, datetime.now(timezone.utc))
|
PairLocks.unlock_pair(pair, datetime.now(UTC))
|
||||||
|
|
||||||
def unlock_reason(self, reason: str) -> None:
|
def unlock_reason(self, reason: str) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -1158,7 +1158,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
manually from within the strategy, to allow an easy way to unlock pairs.
|
manually from within the strategy, to allow an easy way to unlock pairs.
|
||||||
:param reason: Unlock pairs to allow trading again
|
:param reason: Unlock pairs to allow trading again
|
||||||
"""
|
"""
|
||||||
PairLocks.unlock_reason(reason, datetime.now(timezone.utc))
|
PairLocks.unlock_reason(reason, datetime.now(UTC))
|
||||||
|
|
||||||
def is_pair_locked(
|
def is_pair_locked(
|
||||||
self, pair: str, *, candle_date: datetime | None = None, side: str = "*"
|
self, pair: str, *, candle_date: datetime | None = None, side: str = "*"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import re
|
import re
|
||||||
from datetime import datetime, timezone
|
from datetime import UTC, datetime
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
import humanize
|
import humanize
|
||||||
@@ -9,7 +9,7 @@ from freqtrade.constants import DATETIME_PRINT_FORMAT
|
|||||||
|
|
||||||
def dt_now() -> datetime:
|
def dt_now() -> datetime:
|
||||||
"""Return the current datetime in UTC."""
|
"""Return the current datetime in UTC."""
|
||||||
return datetime.now(timezone.utc)
|
return datetime.now(UTC)
|
||||||
|
|
||||||
|
|
||||||
def dt_utc(
|
def dt_utc(
|
||||||
@@ -22,7 +22,7 @@ def dt_utc(
|
|||||||
microsecond: int = 0,
|
microsecond: int = 0,
|
||||||
) -> datetime:
|
) -> datetime:
|
||||||
"""Return a datetime in UTC."""
|
"""Return a datetime in UTC."""
|
||||||
return datetime(year, month, day, hour, minute, second, microsecond, tzinfo=timezone.utc)
|
return datetime(year, month, day, hour, minute, second, microsecond, tzinfo=UTC)
|
||||||
|
|
||||||
|
|
||||||
def dt_ts(dt: datetime | None = None) -> int:
|
def dt_ts(dt: datetime | None = None) -> int:
|
||||||
@@ -68,7 +68,7 @@ def dt_from_ts(timestamp: float) -> datetime:
|
|||||||
if timestamp > 1e10:
|
if timestamp > 1e10:
|
||||||
# Timezone in ms - convert to seconds
|
# Timezone in ms - convert to seconds
|
||||||
timestamp /= 1000
|
timestamp /= 1000
|
||||||
return datetime.fromtimestamp(timestamp, tz=timezone.utc)
|
return datetime.fromtimestamp(timestamp, tz=UTC)
|
||||||
|
|
||||||
|
|
||||||
def shorten_date(_date: str) -> str:
|
def shorten_date(_date: str) -> str:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from datetime import datetime, timezone
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
from cachetools import TTLCache
|
from cachetools import TTLCache
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ class PeriodicCache(TTLCache):
|
|||||||
|
|
||||||
def __init__(self, maxsize, ttl, getsizeof=None):
|
def __init__(self, maxsize, ttl, getsizeof=None):
|
||||||
def local_timer():
|
def local_timer():
|
||||||
ts = datetime.now(timezone.utc).timestamp()
|
ts = datetime.now(UTC).timestamp()
|
||||||
offset = ts % ttl
|
offset = ts % ttl
|
||||||
return ts - offset
|
return ts - offset
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user