diff --git a/requirements.txt b/requirements.txt index 30001b109..4a59f377b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ numpy>=1.0 scipy>=1.0 matplotlib>=3.0 netCDF4>=1.4 -requests \ No newline at end of file +requests +pytz diff --git a/rocketpy/Environment.py b/rocketpy/Environment.py index 675803c40..0d72e2a24 100644 --- a/rocketpy/Environment.py +++ b/rocketpy/Environment.py @@ -9,6 +9,7 @@ import bisect import warnings import time +import pytz from datetime import datetime, timedelta from inspect import signature, getsourcelines from collections import namedtuple @@ -80,7 +81,11 @@ class Environment: Environment.elevation : float Launch site elevation. Environment.date : datetime - Date time of launch. + Date time of launch in UTC. + Environment.localDate : datetime + Date time of launch in the local time zone, defined by Environment.timeZone. + Environment.timeZone : string + Local time zone specification. See pytz for time zone info. Topographic information: Environment.elevLonArray: array @@ -290,6 +295,7 @@ def __init__( longitude=0, elevation=0, datum="SIRGAS2000", + timeZone="UTC", ): """Initialize Environment class, saving launch rail length, launch date, location coordinates and elevation. Note that @@ -326,7 +332,13 @@ def __init__( 'Open-Elevation' which uses the Open-Elevation API to find elevation data. For this option, latitude and longitude must also be specified. Default value is 0. - datum: + datum : string + The desired reference ellipsoide model, the following options are + available: "SAD69", "WGS84", "NAD83", and "SIRGAS2000". The default + is "SIRGAS2000", then this model will be used if the user make some + typing mistake. + timeZone : string, optional + Name of the time zone. To see all time zones, import pytz and run Returns ------- @@ -343,9 +355,11 @@ def __init__( # Save date if date != None: - self.setDate(date) + self.setDate(date, timeZone) else: self.date = None + self.localDate = None + self.timeZone = None # Initialize constants self.earthRadius = 6.3781 * (10 ** 6) @@ -378,21 +392,30 @@ def __init__( return None - def setDate(self, date): + def setDate(self, date, timeZone="UTC"): """Set date and time of launch and update weather conditions if date dependent atmospheric model is used. Parameters ---------- - date : Date - Date object specifying launch date and time. + date : Datetime + Datetime object specifying launch date and time. + timeZone : string, optional + Name of the time zone. To see all time zones, import pytz and run + print(pytz.all_timezones). Default time zone is "UTC". Return ------ None """ - # Store date - self.date = datetime(*date) + # Store date and configure time zone + self.timeZone = timeZone + tz = pytz.timezone(self.timeZone) + localDate = datetime(*date) + if localDate.tzinfo == None: + localDate = tz.localize(localDate) + self.localDate = localDate + self.date = self.localDate.astimezone(pytz.UTC) # Update atmospheric conditions if atmosphere type is Forecast, # Reanalysis or Ensemble @@ -478,7 +501,7 @@ def setElevation(self, elevation="Open-Elevation"): response = requests.get(requestURL) results = response.json()["results"] self.elevation = results[0]["elevation"] - print("Elevation received: ", self.elevation) + print("Elevation received:", self.elevation) except: raise RuntimeError("Unabel to reach Open-Elevation API servers.") else: @@ -2820,9 +2843,18 @@ def info(self): """ # Print launch site details print("Launch Site Details") - print("\nLaunch Rail Length: ", self.rL, " m") - if self.date != None: - print("Launch Date: ", self.date, " UTC") + print("\nLaunch Rail Length:", self.rL, " m") + time_format = "%Y-%m-%d %H:%M:%S" + if self.date != None and "UTC" not in self.timeZone: + print( + "Launch Date:", + self.date.strftime(time_format), + "UTC |", + self.localDate.strftime(time_format), + self.timeZone, + ) + elif self.date != None: + print("Launch Date:", self.date.strftime(time_format), "UTC") if self.lat != None and self.lon != None: print("Launch Site Latitude: {:.5f}°".format(self.lat)) print("Launch Site Longitude: {:.5f}°".format(self.lon)) @@ -2839,7 +2871,7 @@ def info(self): # Print atmospheric model details print("\n\nAtmospheric Model Details") modelType = self.atmosphericModelType - print("\nAtmospheric Model Type: ", modelType) + print("\nAtmospheric Model Type:", modelType) print( modelType + " Maximum Height: {:.3f} km".format(self.maxExpectedHeight / 1000) @@ -2850,7 +2882,7 @@ def info(self): endDate = self.atmosphericModelEndDate interval = self.atmosphericModelInterval print(modelType + " Time Period: From ", initDate, " to ", endDate, " UTC") - print(modelType + " Hour Interval: ", interval, " hrs") + print(modelType + " Hour Interval:", interval, " hrs") # Determine latitude and longitude range initLat = self.atmosphericModelInitLat endLat = self.atmosphericModelEndLat @@ -2859,8 +2891,8 @@ def info(self): print(modelType + " Latitude Range: From ", initLat, "° To ", endLat, "°") print(modelType + " Longitude Range: From ", initLon, "° To ", endLon, "°") if modelType == "Ensemble": - print("Number of Ensemble Members: ", self.numEnsembleMembers) - print("Selected Ensemble Member: ", self.ensembleMember, " (Starts from 0)") + print("Number of Ensemble Members:", self.numEnsembleMembers) + print("Selected Ensemble Member:", self.ensembleMember, " (Starts from 0)") # Print atmospheric conditions print("\n\nSurface Atmospheric Conditions") @@ -2947,9 +2979,18 @@ def allInfo(self): # Print launch site details print("\n\nLaunch Site Details") - print("\nLaunch Rail Length: ", self.rL, " m") - if self.date != None: - print("Launch Date: ", self.date, " UTC") + print("\nLaunch Rail Length:", self.rL, " m") + time_format = "%Y-%m-%d %H:%M:%S" + if self.date != None and "UTC" not in self.timeZone: + print( + "Launch Date:", + self.date.strftime(time_format), + "UTC |", + self.localDate.strftime(time_format), + self.timeZone, + ) + elif self.date != None: + print("Launch Date:", self.date.strftime(time_format), "UTC") if self.lat != None and self.lon != None: print("Launch Site Latitude: {:.5f}°".format(self.lat)) print("Launch Site Longitude: {:.5f}°".format(self.lon)) @@ -2958,7 +2999,7 @@ def allInfo(self): # Print atmospheric model details print("\n\nAtmospheric Model Details") modelType = self.atmosphericModelType - print("\nAtmospheric Model Type: ", modelType) + print("\nAtmospheric Model Type:", modelType) print( modelType + " Maximum Height: {:.3f} km".format(self.maxExpectedHeight / 1000) @@ -2969,7 +3010,7 @@ def allInfo(self): endDate = self.atmosphericModelEndDate interval = self.atmosphericModelInterval print(modelType + " Time Period: From ", initDate, " to ", endDate, " UTC") - print(modelType + " Hour Interval: ", interval, " hrs") + print(modelType + " Hour Interval:", interval, " hrs") # Determine latitude and longitude range initLat = self.atmosphericModelInitLat endLat = self.atmosphericModelEndLat @@ -2978,8 +3019,8 @@ def allInfo(self): print(modelType + " Latitude Range: From ", initLat, "° To ", endLat, "°") print(modelType + " Longitude Range: From ", initLon, "° To ", endLon, "°") if modelType == "Ensemble": - print("Number of Ensemble Members: ", self.numEnsembleMembers) - print("Selected Ensemble Member: ", self.ensembleMember, " (Starts from 0)") + print("Number of Ensemble Members:", self.numEnsembleMembers) + print("Selected Ensemble Member:", self.ensembleMember, " (Starts from 0)") # Print atmospheric conditions print("\n\nSurface Atmospheric Conditions") @@ -3506,7 +3547,7 @@ def printEarthDetails(self): # print("Launch Site UTM coordinates: {:.2f} ".format(self.initialEast) # + self.initialEW + " {:.2f} ".format(self.initialNorth) + self.initialHemisphere # ) - # print("Launch Site UTM zone number: ", self.initialUtmZone) + # print("Launch Site UTM zone number:", self.initialUtmZone) # print("Launch Site Surface Elevation: {:.1f} m".format(self.elevation)) print("Earth Radius at Launch site: {:.1f} m".format(self.earthRadius)) print("Gravity acceleration at launch site: Still not implemented :(") diff --git a/setup.py b/setup.py index 7910006b5..4ecc61447 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,13 @@ setuptools.setup( name="rocketpy", version="0.9.9", - install_requires=["numpy>=1.0", "scipy>=1.0", "matplotlib>=3.0", "requests"], + install_requires=[ + "numpy>=1.0", + "scipy>=1.0", + "matplotlib>=3.0", + "requests", + "pytz", + ], maintainer="RocketPy Developers", author="Giovani Hidalgo Ceotto", author_email="ghceotto@gmail.com", diff --git a/tests/test_environment.py b/tests/test_environment.py index 8015d3620..12753df13 100644 --- a/tests/test_environment.py +++ b/tests/test_environment.py @@ -1,4 +1,5 @@ import datetime +import pytz from unittest.mock import patch import pytest @@ -32,7 +33,7 @@ def test_env_set_date(example_env): tomorrow = datetime.date.today() + datetime.timedelta(days=1) example_env.setDate((tomorrow.year, tomorrow.month, tomorrow.day, 12)) assert example_env.date == datetime.datetime( - tomorrow.year, tomorrow.month, tomorrow.day, 12 + tomorrow.year, tomorrow.month, tomorrow.day, 12, tzinfo=pytz.utc )