""" AUTHOR: Khushal P Soonderji DATE: Friday, 21st jun, 2024 OBJECTIVE: To provide an easy way to work with time. REFERENCES: N/A DOWNLOADS: N/A """ # ***************************************************************************************************************** # ***** **** # *** IMPORT *** # ***** **** # ***************************************************************************************************************** # To make sibling directories accessible for imports: import sys sys.path.append(".") sys.path.append("..") # For date and time handling: import pytz from datetime import datetime, time, timedelta, tzinfo import dateparser # To handle date-time objects from a Numpy array and Pandas Dataframe: import numpy as np import pandas as pd # To work with datatypes: from typing import List # ***************************************************************************************************************** # ***** **** # *** MACROS / ONE-TIME INIT *** # ***** **** # ***************************************************************************************************************** # Date-Time Formats: DATE_TIME_FORMATS = ( "%d/%m/%y", "%d-%b-%y", "%d-%m-%y", "%d.%m.%y", "%d/%m/%Y", "%d-%b-%Y", "%d-%m-%Y", "%d.%m.%Y", "%d/%b", "%d%m%Y", "%Y%m%d", "%Y-%m-%d %H:%M:%S" ) # Useful Timezones: TIMEZONE_UTC = pytz.timezone("UTC") TIMEZONE_IST = pytz.timezone("Asia/Kolkata") TIMEZONE_ET = pytz.timezone("America/New_York") TIMEZONE_CT = pytz.timezone("America/Chicago") TIMEZONE_MT = pytz.timezone("America/Denver") TIMEZONE_PT = pytz.timezone("America/Los_Angeles") TIMEZONE_JST = pytz.timezone("Asia/Tokyo") TIMEZONE_CET = pytz.timezone("Europe/Paris") TIMEZONE_GMT = pytz.timezone("GMT") TIMEZONE_AEST = pytz.timezone("Australia/Sydney") TIMEZONE_NZST = pytz.timezone("Pacific/Auckland") TIMEZONE_CST = pytz.timezone("Asia/Shanghai") TIMEZONE_KST = pytz.timezone("Asia/Seoul") TIMEZONE_MSK = pytz.timezone("Europe/Moscow") TIMEZONE_BRT = pytz.timezone("America/Sao_Paulo") TIMEZONE_GST = pytz.timezone("Asia/Dubai") TIMEZONE_SAST = pytz.timezone("Africa/Johannesburg") TIMEZONE_AST = pytz.timezone("Asia/Riyadh") # ***************************************************************************************************************** # ***** **** # *** VARIABLES *** # ***** **** # ***************************************************************************************************************** # --- Nothing Yet # ***************************************************************************************************************** # ***** **** # *** FUNCTIONS *** # ***** **** # ***************************************************************************************************************** def translate_date_time_string( datetime_string: str, source_format: str = None, destination_format: str = "%Y-%m-%dT%H:%M:%S" ) -> str | None: """ To convert an input datetime string to a different format. :param datetime_string: The datetime string to translate. :param source_format: The current format of the string. If not provided, dateparser will be used. :param destination_format: The format to convert to. :return: The converted datetime string. """ try: if source_format is None: datetime_obj = dateparser.parse(datetime_string) else: datetime_obj = datetime.strptime(datetime_string, source_format) return datetime_obj.strftime(destination_format) except Exception as exception: return None # --------------------------------------------------------------------------------------------------------------------- def parse_date_time( input_value: datetime | str | int | float, timezone: str | tzinfo = None, date_formats: List[str] = None ) -> datetime: """ To take any kind of input and interpret the datetime from it. :param input_value: Either a string or an integer or some form of datetime representation. :param timezone: The timezone to apply to the interpreted datetime. IF THE INPUT IS NAIVE, THIS TIMEZONE WILL BE APPLIED AS IS, ELSE THE TIMEZONE WILL BE TRANSLATED. :param date_formats: The string formats to consider when parsing a string input. :return: The parsed datetime or null. """ datetime_object = None date_formats = date_formats or DATE_TIME_FORMATS # It could either be in seconds or milliseconds from epoch time's base date (January 1, 1970), # or it could be days since Microsoft Excel's base date (December 31, 1899). if isinstance(input_value, (int, float, np.number)) and not np.isnan(input_value): if input_value > 9999999999.0: datetime_object = datetime.fromtimestamp(input_value / 1000.0) if input_value > 999999.0: datetime_object = datetime.fromtimestamp(input_value) else: datetime_object = datetime.fromtimestamp(input_value * 24 * 60 * 60.0) - timedelta(days = 25569) # The input can even be a pre-formatted date: if isinstance(input_value, str): datetime_object = dateparser.parse( input_value, date_formats = date_formats, settings = { "DATE_ORDER": "DMY", "PREFER_DAY_OF_MONTH": "first", } ) # If the type is a datetime object, then return it as it is: if isinstance(input_value, datetime): datetime_object = input_value # If the type is the native datetime format of pandas: if isinstance(input_value, pd._libs.tslibs.timestamps.Timestamp): datetime_object = input_value.to_pydatetime() # Process the timezone: if datetime_object is not None and timezone is not None: datetime_object = to_timezone(datetime_object, timezone) # Done here: return datetime_object # --------------------------------------------------------------------------------------------------------------------- def get_current_date_time( timezone: str | tzinfo = None, as_string: bool = False ) -> datetime: """ Returns the current time as a datetime object. :param timezone: The timezone to apply to the returned datetime. :param as_string: Whether, or not, you want the output as a string. :return: The datetime object/string representing the current time. """ if timezone is not None and isinstance(timezone, str): timezone = pytz.timezone(timezone) now = datetime.now(timezone) return now.isoformat() if as_string else now # --------------------------------------------------------------------------------------------------------------------- def get_current_ist_date_time( as_string = False ) -> datetime: """ Gives out the current time in IST timezone. :param as_string: Whether, or not, you want the output as a string. :return: The datetime object or string representing the current time. """ return get_current_date_time( timezone = TIMEZONE_IST, as_string = as_string ) # --------------------------------------------------------------------------------------------------------------------- def get_current_utc_date_time( as_string: bool = False ) -> datetime: """ Gives out the current time in UTC timezone. :param as_string: Whether, or not, you want the output as a string. :return: The datetime object or string representing the current time. """ return get_current_date_time( timezone = TIMEZONE_UTC, as_string = as_string ) # --------------------------------------------------------------------------------------------------------------------- def as_if_timezone( datetime_object: datetime, timezone: str | tzinfo ) -> datetime: """ Ignores existing timezone info and applies the intended timezone. The time stays the same, only the timezone marker changes. e.g. for IST to UTC: 2024-08-09 00:00:00+05:30 --> 2024-08-09 00:00:00+00:00 HINT: IT PRETENDS "AS IF" THE TIMEZONE WAS THE INPUT TIMEZONE. :param datetime_object: The datetime object on which the timezone needs to be applied. :param timezone: The timezone that needs to be applied. :return: A timezone-aware datetime object. """ tz_object = pytz.timezone(timezone) if isinstance(timezone, str) else timezone return tz_object.localize(datetime_object.replace(tzinfo = None)) # --------------------------------------------------------------------------------------------------------------------- def to_timezone( datetime_object: datetime, timezone: str | tzinfo ) -> datetime: """ Converts from one timezone to another. The time is adjusted by computing the difference between the two timezones. NOTE: THIS FUNCTION APPLIES TH EINPUT TIMEZONE IF THE INPUT DATETIME WAS TIMEZONE-NAIVE. e.g. for IST to UTC: 2024-08-09 00:00:00+05:30 --> 2024-08-08 18:30:00+00:00 :param datetime_object: The datetime object on which the timezone needs to be applied. :param timezone: The timezone that needs to be applied. :return: A timezone-aware datetime object. """ if isinstance(timezone, str): timezone = pytz.timezone(timezone) if datetime_object.tzinfo is None: return datetime_object.replace(tzinfo = timezone) return datetime_object.astimezone(timezone) # --------------------------------------------------------------------------------------------------------------------- def round_datetime( datetime_object: datetime, seconds: int ) -> datetime: """ 'Snaps' the time to the closest 'n-second' window. For example, if you want to round the time off to the nearest 5-minute period (maybe for use cases like trading), you set the value of seconds to 300. This way any input value of, say, 12:01:50 AM gets converted to 12:00:00 AM; and a value of 12:02:31 AM gets converted to 12:05:00 AM. :param datetime_object: The input datetime that you want to round off. :param seconds: The period of rounding in seconds. 60 for 1 minute, 300 for 5 minutes and so on. :return: The rounded date-time (with the original timezone). """ # For timezone-naive cases: if datetime_object.tzinfo is None: total_seconds = datetime_object.timestamp() rounded_seconds = round(total_seconds / seconds) * seconds rounded_datetime = datetime.fromtimestamp(rounded_seconds) return rounded_datetime # For timezone-aware cases: else: original_tz = datetime_object.tzinfo _dt = to_timezone(datetime_object, timezone = original_tz) total_seconds = _dt.timestamp() rounded_seconds = round(total_seconds / seconds) * seconds rounded_datetime = datetime.fromtimestamp(rounded_seconds, tz = original_tz) return rounded_datetime # ***************************************************************************************************************** # ***** **** # *** MAIN PROGRAM *** # ***** **** # ***************************************************************************************************************** if __name__ == "__main__": pass