Skip to content

Instantly share code, notes, and snippets.

@cprima
Created December 30, 2024 21:56
Show Gist options
  • Save cprima/b6fcef2670581ec3f6a1aded2787fd6b to your computer and use it in GitHub Desktop.
Save cprima/b6fcef2670581ec3f6a1aded2787fd6b to your computer and use it in GitHub Desktop.
Calculate Sunrise, Sunset, and Civil Twilight with Python

Solar Event Calculator: Civil Twilight, Sunrise, Sunset, and Meridian

Description

This Python script calculates civil twilight, sunrise, sunset, and solar noon (meridian) times for a list of geographic locations provided in a CSV file. The output includes local timezone information and UTC offsets for each location, ensuring precise timing based on coordinates.

Features

  • Calculates civil dawn, sunrise, meridian (solar noon), sunset, and civil dusk.
  • Adjusts times to the local timezone of each location.
  • Outputs the timezone name and UTC offset for clarity.
  • Processes multiple dates between a specified start and end date.

Input Data Format

The script expects the input CSV file to have the following columns:

Column Name Description
X Longitude of the location (decimal degrees).
Y Latitude of the location (decimal degrees).
name Name of the location (e.g., "San Francisco").
@type Type of the place (e.g., city, village, hamlet).
Additional Columns Other metadata, not used in calculations.

Example Input Data:

X,Y,id,@id,@type,capital,ele,name,place,population,short_name,website,wikidata,wikipedia
-122.4193286,37.7792588,node/26819236,"26819236",node,"6",,San Francisco,city,"873965",SF,,Q62,en:San Francisco
-120.01296,38.8560185,node/141045122,"141045122",node,,"1936",Meyers,village,"2163",,,Q6826467,"en:Meyers, California"
-110.1497981,37.6351618,node/150934572,"150934572",node,,"1638",Fry Canyon,hamlet,,,,,

Required Columns:

  • X (Longitude): Geographic longitude of the location.
  • Y (Latitude): Geographic latitude of the location.
  • name: Location name (for reference in output).

Other columns are optional and can contain metadata.

Output Format

The script generates a CSV file with the following columns:

Column Name Description
date Date of the calculation (YYYY-MM-DD).
civil_dawn Time of civil dawn (local timezone, ISO 8601 format).
sunrise Time of sunrise (local timezone, ISO 8601 format).
meridian Time of solar noon/meridian (local timezone, ISO 8601 format).
sunset Time of sunset (local timezone, ISO 8601 format).
civil_dusk Time of civil dusk (local timezone, ISO 8601 format).
timezone Local timezone identifier (e.g., America/Los_Angeles).
offset UTC offset in hours (e.g., -7.0 for PDT).
name Name of the location (as provided in input).

Setup Instructions

  1. Install Python Libraries:

    pip install pandas astral timezonefinder pytz
  2. Prepare Input File:

    • Ensure your CSV file follows the described format.
    • Update the script with the correct file path for the input and output files.
  3. Run the Script: Execute the script in your Python environment:

    python solar_event_calculator.py
  4. Review Output: The results will be saved as a CSV file containing calculated solar event times.

Example Usage

Input data example (CSV):

X,Y,name
-122.4193286,37.7792588,San Francisco
-120.01296,38.8560185,Meyers
-110.1497981,37.6351618,Fry Canyon

Output example (CSV):

date,civil_dawn,sunrise,meridian,sunset,civil_dusk,timezone,offset,name
2025-05-15,2025-05-15T05:34:00-07:00,2025-05-15T06:04:00-07:00,2025-05-15T12:45:00-07:00,2025-05-15T19:26:00-07:00,2025-05-15T19:56:00-07:00,America/Los_Angeles,-7.0,San Francisco

Notes

  • The script handles timezones dynamically using the timezonefinder library.
  • Locations with invalid or missing timezones are skipped with a warning in the console.
  • Civil twilight is calculated based on astronomical definitions, as described in the requirements.

License

This project is open-source and available under the MIT License.

import pandas as pd
from astral.sun import sun
from astral import Observer
from datetime import datetime, timedelta
from timezonefinder import TimezoneFinder
import pytz
# Load CSV file
file_path = "C:/Users/cpm/Downloads/cpm_travel_2025_plan_.csv"
data = pd.read_csv(file_path)
# Columns for latitude and longitude
data['Latitude'] = data['Y']
data['Longitude'] = data['X']
# Timezone Finder instance
tf = TimezoneFinder()
# Filter by dates
start_date = datetime(2025, 5, 15)
end_date = datetime(2025, 6, 30)
date_range = [start_date + timedelta(days=i) for i in range((end_date - start_date).days + 1)]
# Define function to calculate sunrise, sunset, civil twilight, and meridian in local timezone
def calculate_sun_times(row, date_range):
times = []
local_tz_name = tf.timezone_at(lat=row['Latitude'], lng=row['Longitude'])
if not local_tz_name:
print(f"Timezone not found for {row['name']} ({row['Latitude']}, {row['Longitude']})")
return pd.DataFrame()
local_tz = pytz.timezone(local_tz_name)
for date in date_range:
try:
observer = Observer(latitude=row['Latitude'], longitude=row['Longitude'])
s = sun(observer, date=date, tzinfo=local_tz)
times.append({
"date": date.strftime("%Y-%m-%d"),
"civil_dawn": s['dawn'].isoformat() if s['dawn'] else None,
"sunrise": s['sunrise'].isoformat() if s['sunrise'] else None,
"meridian": s['noon'].isoformat() if s['noon'] else None,
"sunset": s['sunset'].isoformat() if s['sunset'] else None,
"civil_dusk": s['dusk'].isoformat() if s['dusk'] else None,
"timezone": local_tz.zone,
"offset": s['sunrise'].utcoffset().total_seconds() / 3600 if s['sunrise'] else None # Offset in hours
})
except Exception as e:
print(f"Error calculating sun times for {row['name']} on {date}: {e}")
return pd.DataFrame(times)
# Collect results
results = []
for _, row in data.iterrows():
sun_times = calculate_sun_times(row, date_range)
sun_times['name'] = row['name']
results.append(sun_times)
# Combine all results into a single DataFrame
final_results = pd.concat(results, ignore_index=True)
# Save to CSV
output_file = "C:/Users/cpm/Downloads/sunrise_sunset_times.csv"
final_results.to_csv(output_file, index=False)
print(f"Results saved to {output_file}")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment