def dt_to_other_timezone(dt: datetime, destination_timezone_name: str, origin_timezone_name: str = 'UTC') -> datetime: """ The only, safest, way I know to convert datetime object from one timezone to another while accounting for things like Daylight Saving Time (also known as "Summer Time") and "leap" stuff. Tried many many other ways and anything that work with pure offsets is plain bad. Must work with proper tx names and pytz is the best way on Python. Offsets plainly don't work because DST-vs-no-DST varies based on specific locale. For example, US state of Arizona, while being part of `US/Mountain` time zone does NOT observe Daylight Saving Time, like rest of that time zone. As a result, it's effectively on US Mountain time zone in the winter and in US Pacific (right?) for most of rest of the year. Then, add to that the fact that Summer Time starts and ends on different dates depending on a country and, as a result, noon in San Diego, California is not guaranteed to be noon in Tijuana - a city only 30 kilometers *South* As a result of all the above, learned to specify timezone names as specifically as possible. Say, "America/Los_Angeles" vs "US/Pacific" and work only with time-zone-aware datetimes and only with timezones that are timezone-name aware and support something like Olson timezone DB (https://en.wikipedia.org/wiki/Tz_database) :param datetime.datetime dt: Some datetime object. May be timezone-naive, in which case origin timezone name is required and is used to localize the incoming dt before tz conversion. :param str destination_timezone_name: 'UTC' or some standard tz name string like "America/Los_Angeles" :param str origin_timezone_name: 'UTC' (default) or some standard tz name string like "Europe/Paris" :return: """ from pytz import UTC, timezone as Timezone from pytz.tzinfo import DstTzInfo if dt.tzinfo is None: assert origin_timezone_name origin_tz = Timezone(origin_timezone_name) # this step properly bakes together origin tz and dt tz_local_dt = origin_tz.localize(dt) elif dt.tzinfo == UTC or isinstance(dt.tzinfo, DstTzInfo): # this is an easy way out. These TZs properly react to # .normalize() method so no need to do anything with dt tz_local_dt = dt else: # We are here if tzinfo is set on dt, # but we don't know what the implementation is # (possibly some offset-based thing) # and, thus don't trust it to do the right thing # Hence, flipping it to UTC-based safe intermediate state # which does not have daylight saving time issues. tz_local_dt = dt.astimezone(UTC) destination_tz = Timezone(destination_timezone_name) # this step properly (with account for Daylight saving time) moves # dt to other timezone. return destination_tz.normalize(tz_local_dt)
def get_clearsky_irradiance(start_time: datetime.datetime = None, end_time: datetime.datetime = None, timezone: pytz.timezone = None, latitude: float = None, longitude: float = None, sun_zenith: pd.DataFrame = None, granularity: int = 60, clearsky_estimation_method: str = 'pysolar', google_api_key: str = None): if (clearsky_estimation_method == 'pysolar' or google_api_key == None): ################################################################################## # # Pandas .apply based code, but it is slower than while loop # from helpers import granularity_to_freq # # datetime_series = pd.date_range(start_time, end_time, freq=granularity_to_freq(granularity)) # datetime_series_localized = datetime_series.tz_localize(timezone) # data = pd.DataFrame({'time':datetime_series_localized}) # data['altitude_deg'] = data['time'].apply(lambda timestamp: pysolar.solar.get_altitude(latitude, longitude, timestamp)) # data['clearsky'] = data.apply(lambda row: pysolar.solar.radiation.get_radiation_direct(row['time'], row['altitude_deg']), axis=1) # data['time'] = data['time'].apply(lambda x: x.replace(tzinfo=pytz.utc).replace(tzinfo=None)) ################################################################################## # localizing the datetime based on the timezone start: datetime.datetime = timezone.localize(start_time) end: datetime.datetime = timezone.localize(end_time) # create arrays to store time and irradiance clearsky: List[int] = [] time_: List[datetime.datetime] = [] # go through all the hours between the two dates while start <= end: # get the altitude degree for the given location altitude_deg: float = pysolar.solar.get_altitude( latitude, longitude, start) # get the clearsky based on the time and altitude clear_sky: float = pysolar.solar.radiation.get_radiation_direct( start, altitude_deg) # removing the timezone information dt: datetime.datetime = start.replace(tzinfo=pytz.utc).replace( tzinfo=None) # saving the data in the lists clearsky.append(clear_sky) time_.append(dt) # increasing the time by 1 hrs, normazlizing it to handle DST start = timezone.normalize(start + datetime.timedelta(seconds=granularity)) # create dataframe from lists irradiance: pd.DataFrame = pd.DataFrame({ 'time': time_, 'clearsky': clearsky }) elif (clearsky_estimation_method == 'lau_model' and google_api_key != None): # use google maps python api to get elevation gmaps = googlemaps.Client(key=google_api_key) elevation_api_response: list = gmaps.elevation((latitude, longitude)) elevation_km: float = elevation_api_response[0]['elevation'] / 1000 # create a date_range and set it as a time column in a dataframe datetime_series = pd.date_range(start_time, end_time, freq=granularity_to_freq(granularity)) # datetime_series_localized = datetime_series.tz_localize(timezone) irradiance = pd.DataFrame({'time': datetime_series}) # based on "E. G. Laue. 1970. The Measurement of Solar Spectral Irradiance at DifferentTerrestrial Elevations.Solar Energy13 (1970)", # Check details on this model on Section 2.4 on PVeducation.org irradiance['air_mass'] = 1 / ( np.cos(sun_zenith) + 0.50572 * pow(96.07995 - np.rad2deg(sun_zenith), -1.6364)) irradiance['clearsky_direct'] = 1.361 * ( (1 - 0.14 * elevation_km) * pow(0.7, irradiance['air_mass']**0.678) + 0.14 * elevation_km) irradiance['clearsky'] = 1000 * 1.1 * irradiance['clearsky_direct'] # replace nan with 0 and keep only time and clearsky columns irradiance['clearsky'] = irradiance['clearsky'].fillna(0) irradiance = irradiance[['time', 'clearsky']] else: raise ValueError( 'Invalid argument for clearsky_estimation_method or google_api_key.' ) return irradiance