#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# contrib/misc/determine_lat_lon.py
#
# This script tries to determines the longitude in Tehran defined by the
# World Centre for the origin of the Badi calendar.
#
import os
import sys
PWD = os.path.dirname(os.path.abspath(__file__))
BASE_DIR = os.path.dirname(os.path.dirname(PWD))
sys.path.append(BASE_DIR)
from badidatetime import BahaiCalendar, GregorianCalendar
"""
These are the sunsets which are for the most part before the Vernal Equinox.
All dates are corrected for Tehran Iran.
Max longitude 183 (2026) : 51.5036157
Max longitude 216 (2059) : 51.337186
The longitude midway point: 51.2344395
Median Longitude : 51.288701 CURRENTLY USED
Min longitude 183 (2026) : 51.131693
Min longitude 216 (2059) : 51.0360365
The minimum (35.672° N) and maximum (35.7167° N) latitude of the
city of Tehran, with a mid point of approximately 35.69435° N.
The latitude makes little difference so we use: 35.682376
The minimum (51.24° E) and maximum (51.42° E) longitude of the
city of Tehran, with a mid point of approximately 51.33° E.
Hour calculator:
https://www.calculatorsoup.com/calculators/time/time-calculator.php
Gregorian Date of Gregorian Date of
Vernal Equinox Sunset of Epoch
(2015, 3, 21, 2, 15) (2015, 3, 20, 18, 16)
(2016, 3, 20, 8, 00) (2016, 3, 19, 18, 15)
(2017, 3, 20, 13, 59) (2017, 3, 19, 18, 15)
(2018, 3, 20, 19, 45) (2018, 3, 20, 18, 16)
(2019, 3, 21, 1, 28) (2019, 3, 20, 18, 16)
(2020, 3, 20, 7, 20) (2020, 3, 19, 18, 16)
(2021, 3, 20, 13, 7) (2021, 3, 19, 18, 15)
(2022, 3, 20, 19, 3) (2022, 3, 20, 18, 16)
(2023, 3, 21, 0, 54) (2023, 3, 20, 18, 16)
(2024, 3, 20, 6, 36) (2024, 3, 19, 18, 15)
(2025, 3, 20, 12, 31) (2025, 3, 19, 18, 15)
(2026, 3, 20, 18, 16) (2026, 3, 20, 18, 16) ?
(2027, 3, 20, 23, 55) (2027, 3, 20, 18, 16)
(2028, 3, 20, 5, 47) (2028, 3, 19, 18, 16)
(2029, 3, 20, 11, 32) (2029, 3, 19, 18, 15)
(2030, 3, 20, 17, 22) (2030, 3, 19, 18, 15) ?
(2031, 3, 20, 23, 11) (2031, 3, 20, 18, 16)
(2032, 3, 20, 4, 52) (2032, 3, 19, 18, 16)
(2033, 3, 20, 10, 52) (2033, 3, 19, 18, 15)
(2034, 3, 20, 16, 47) (2034, 3, 19, 18, 15) ?
(2035, 3, 20, 22, 32) (2035, 3, 20, 18, 16)
(2036, 3, 20, 4, 33) (2036, 3, 19, 18, 16)
(2037, 3, 20, 10, 20) (2037, 3, 19, 18, 15)
(2038, 3, 20, 16, 10) (2038, 3, 19, 18, 15) ?
(2039, 3, 20, 22, 2) (2039, 3, 20, 18, 16)
(2040, 3, 20, 3, 41) (2040, 3, 19, 18, 16)
(2041, 3, 20, 9, 37) (2041, 3, 19, 18, 15)
(2042, 3, 20, 15, 23) (2042, 3, 19, 18, 15) ?
(2043, 3, 20, 20, 57) (2043, 3, 20, 18, 16)
(2044, 3, 20, 2, 50) (2044, 3, 19, 18, 16)
(2045, 3, 20, 8, 37) (2045, 3, 19, 18, 15)
(2046, 3, 20, 14, 28) (2046, 3, 19, 18, 15)
(2047, 3, 20, 20, 22) (2047, 3, 20, 18, 16)
(2048, 3, 20, 2, 4) (2048, 3, 19, 18, 16)
(2049, 3, 20, 7, 58) (2049, 3, 19, 18, 15)
(2050, 3, 20, 13, 49) (2050, 3, 19, 18, 15)
(2051, 3, 20, 19, 29) (2051, 3, 20, 18, 16)
(2052, 3, 20, 1, 26) (2052, 3, 19, 18, 16)
(2053, 3, 20, 7, 17) (2053, 3, 19, 18, 15)
(2054, 3, 20, 13, 4) (2054, 3, 19, 18, 15)
(2055, 3, 20, 18, 58) (2055, 3, 20, 18, 16)
(2056, 3, 20, 0, 41) (2056, 3, 19, 18, 16)
(2057, 3, 20, 6, 38) (2057, 3, 19, 18, 15)
(2058, 3, 20, 12, 35) (2058, 3, 19, 18, 15)
(2059, 3, 20, 18, 14) (2059, 3, 19, 18, 15) ?
(2060, 3, 20, 0, 8) (2060, 3, 19, 18, 16)
(2061, 3, 20, 5, 56) (2061, 3, 19, 18, 15)
(2062, 3, 20, 11, 37) (2062, 3, 19, 18, 15)
(2063, 3, 20, 17, 29) (2063, 3, 19, 18, 15)
(2064, 3, 19, 23, 9) (2064, 3, 19, 18, 16)
"""
[docs]
class DumpFindMomentOfEquinoxesOrSolstices(BahaiCalendar):
# Year WC Naw-Ruz NASA Vernal Equinox (GMT)
WC_DATA = (
(172, (2015, 3, 20), (2015, 3, 20, 22, 45)),
(173, (2016, 3, 19), (2016, 3, 20, 4, 30)),
(174, (2017, 3, 19), (2017, 3, 20, 10, 29)),
(175, (2018, 3, 20), (2018, 3, 20, 16, 15)),
(176, (2019, 3, 20), (2019, 3, 20, 21, 58)),
(177, (2020, 3, 19), (2020, 3, 20, 3, 50)),
(178, (2021, 3, 19), (2021, 3, 20, 9, 37)),
(179, (2022, 3, 20), (2022, 3, 20, 15, 33)),
(180, (2023, 3, 20), (2023, 3, 20, 21, 24)),
(181, (2024, 3, 19), (2024, 3, 20, 3, 6)),
(182, (2025, 3, 19), (2025, 3, 20, 9, 1)),
(183, (2026, 3, 20), (2026, 3, 20, 14, 46)), # ?
(184, (2027, 3, 20), (2027, 3, 20, 20, 25)),
(185, (2028, 3, 19), (2028, 3, 20, 2, 17)),
(186, (2029, 3, 19), (2029, 3, 20, 8, 2)),
(187, (2030, 3, 19), (2030, 3, 20, 13, 52)),
(188, (2031, 3, 20), (2031, 3, 20, 19, 41)),
(189, (2032, 3, 19), (2032, 3, 20, 1, 22)),
(190, (2033, 3, 19), (2033, 3, 20, 7, 22)),
(191, (2034, 3, 19), (2034, 3, 20, 13, 17)),
(192, (2035, 3, 20), (2035, 3, 20, 19, 2)),
(193, (2036, 3, 19), (2036, 3, 20, 1, 3)),
(194, (2037, 3, 19), (2037, 3, 20, 6, 50)),
(195, (2038, 3, 19), (2038, 3, 20, 12, 40)),
(196, (2039, 3, 20), (2039, 3, 20, 18, 32)),
(197, (2040, 3, 19), (2040, 3, 20, 0, 11)),
(198, (2041, 3, 19), (2041, 3, 20, 6, 7)),
(199, (2042, 3, 19), (2042, 3, 20, 11, 53)),
(200, (2043, 3, 20), (2043, 3, 20, 17, 27)),
(201, (2044, 3, 19), (2044, 3, 19, 23, 20)),
(202, (2045, 3, 19), (2045, 3, 20, 5, 7)),
(203, (2046, 3, 19), (2046, 3, 20, 10, 58)),
(204, (2047, 3, 20), (2047, 3, 20, 16, 52)),
(205, (2048, 3, 19), (2048, 3, 19, 22, 34)),
(206, (2049, 3, 19), (2049, 3, 20, 4, 28)),
(207, (2050, 3, 19), (2050, 3, 20, 10, 19)),
(208, (2051, 3, 20), (2051, 3, 20, 15, 59)),
(209, (2052, 3, 19), (2052, 3, 19, 21, 56)),
(210, (2053, 3, 19), (2053, 3, 20, 3, 47)),
(211, (2054, 3, 19), (2054, 3, 20, 9, 34)),
(212, (2055, 3, 20), (2055, 3, 20, 15, 28)),
(213, (2056, 3, 19), (2056, 3, 19, 21, 11)),
(214, (2057, 3, 19), (2057, 3, 20, 3, 8)),
(215, (2058, 3, 19), (2058, 3, 20, 9, 5)),
(216, (2059, 3, 19), (2059, 3, 20, 14, 44)), # ?
(217, (2060, 3, 19), (2060, 3, 19, 20, 38)),
(218, (2061, 3, 19), (2061, 3, 20, 2, 26)),
(219, (2062, 3, 19), (2062, 3, 20, 8, 7)),
(220, (2063, 3, 19), (2063, 3, 20, 13, 59)),
(221, (2064, 3, 19), (2064, 3, 19, 19, 39)),
)
"""
tuple: World Centre dates.
:meta hide-value:
"""
# The overlap years
WC_DATA_183_216 = (
(183, (2026, 3, 20), (2026, 3, 20, 14, 46)),
(216, (2059, 3, 19), (2059, 3, 20, 14, 44)),
)
def __init__(self):
super().__init__()
self.gc = GregorianCalendar()
#self.BADI_COORD = self._BAHAI_LOCATION[:3]
self.BADI_COORD = (35.69435, 51.288701, 3.5)
[docs]
def dump_sunset_after_ve(self, options):
"""
Find the sunset before the Vernal Equinox or the sunset after if
it is less than or equal to 1 minute after the Vernal Equinox.
Use -v
"""
data = []
for year, wc_ss, nasa_ve in self.WC_DATA:
(my_local_ss, my_ss_ut, my_ve_ut, nasa_ve_date, my_local_date,
nasa_ve_ut) = self._find_data(year, wc_ss, nasa_ve, options)
diff = round(my_ve_ut - nasa_ve_ut, 6)
hms = self.gc._hms_from_decimal_day(abs(diff))
data.append((year, # Baha'i year
wc_ss, # WC sunset
my_local_ss, # My Gregorian sunset
round(my_ss_ut-my_ve_ut, 6), # Sunset VE difference
nasa_ve_date, # Adjusted NASA VE
my_local_date, # My VE
diff, # VE difference
hms, # HH:MM:SS
))
return data
[docs]
def find_longitude(self, options):
"""
Find the best longitude for all 50 years.
.. note::
1. Sunset is later going west.
2. Sunset must be before the VE unless it's only a minute before
then it can be after.
-l with -L
Optional -F, But be ware this will take many hours to complete.
"""
data = []
start_lon = 51
dec_start = 240000 # Low range longitude (131000 orig)
dec_end = 420000 # High range longitude (337200 orig)
year_dict = {}
wc_data = self.WC_DATA if options.full else self.WC_DATA_183_216
for year, wc_ss, nasa_ve in wc_data:
min_max = []
print(f"Working on year {year}", file=sys.stderr)
# Capture data for each longitude only if the WC sunset is equal
# to the derived sunset.
for dp in range(dec_start, dec_end):
lon = round(start_lon + dp / 1e6, 6)
if lon > round(start_lon + dec_end / 1e6, 6): break
(my_g_ss, my_ss_jd, my_ve_jd, nasa_ve_3p5, my_ve_g,
nasa_ve_jd) = self._find_data(year, wc_ss, nasa_ve,
options, test_lon=lon)
jd_diff = round(my_ss_jd - my_ve_jd, 6)
if wc_ss == my_g_ss[:3]:
min_max.append((lon, jd_diff, wc_ss, my_g_ss[:3]))
if not len(min_max):
print("The min_max list is empty indicating the start and "
"end criteria may need to be altered.", file=sys.stderr)
break
min_l = 100
max_l = 0
# Weed out longitudes that are greater than 100 or less than 0.
for lon, diff, wc_ss, my_g_ss in min_max:
if min_l > lon:
min_l = lon
diff0 = diff
wc_date0 = wc_ss
my_date0 = my_g_ss
if max_l < lon:
max_l = lon
diff1 = diff
wc_date1 = wc_ss
my_date1 = my_g_ss
year_dict[year] = (wc_date0, my_date0, min_l, diff0,
wc_date1, my_date1, max_l, diff1)
for year, (wc_date0, my_date0, min_l, diff0,
wc_date1, my_date1, max_l, diff1) in year_dict.items():
data.append((year, wc_date0, my_date0, min_l, diff0,
wc_date1, my_date1, max_l, diff1))
return data
# Support methods
[docs]
def _find_data(self, year, wc_ss, nasa_ve, options, *, test_lon=None):
# We do not use Astronomical version of the date to JD formula as it
# will not work with any of Meeus' equations.
lat, lon, zone = self.BADI_COORD
lon = test_lon if test_lon and options.longitude else lon
first_of_march = (wc_ss[0], wc_ss[1], 1) # Replace day with the 1st
jd = self.gc.jd_from_gregorian_date(first_of_march) # Get JD day
my_ve_ut = self._find_moment_of_equinoxes_or_solstices(jd)
my_ss_ut = self._sun_setting(my_ve_ut, lat, lon)
# It is allowed to have a Vernal Equinox to be up to one minute
# before sunset and still use that sunset as the beginning of
# the year. If a day = 1 then 1 minute is 0.0006944444444444444
if my_ve_ut < (my_ss_ut - 0.0006944444444444444):
my_ss_ut = self._sun_setting(my_ve_ut - 1, lat, lon)
my_local_jd = self._local_zone_correction(my_ss_ut, zone, mod_jd=True)
my_local_ss = self.gc.gregorian_date_from_jd(my_local_jd, hms=True)
my_local_ve = self._local_zone_correction(my_ve_ut, zone, mod_jd=True)
my_local_date = self.gc.gregorian_date_from_jd(my_local_ve, hms=True)
nasa_ve_ut = self.gc.jd_from_gregorian_date(nasa_ve)
nasa_local_ve = self._local_zone_correction(nasa_ve_ut, zone,
mod_jd=True)
nasa_ve_date = self.gc.gregorian_date_from_jd(nasa_local_ve, hms=True)
return (my_local_ss, my_ss_ut, my_ve_ut, nasa_ve_date, my_local_date,
nasa_ve_ut)
[docs]
def fmt_float(value, left=4, right=4, leading_zero=False):
"""
Format one float so that it is visually centered on the decimal point.
Parameters
----------
value : float | int | str
The number to format.
left : int
Width to reserve on the left of the decimal (including any minus sign).
right : int
Number of digits to show after the decimal.
"""
value = round(value, right)
s = f"{value:.{right}f}"
left_part, right_part = s.split(".")
# If leading_zero=True, pad *numerical digits* but not the sign
if leading_zero and left_part.startswith("-"):
sign = "-"
digits = left_part[1:]
padded = sign + digits.zfill(left - 1)
elif leading_zero:
padded = left_part.zfill(left)
else:
padded = left_part.rjust(left)
return f"{padded}.{right_part.ljust(right)}"
if __name__ == "__main__":
import time
import argparse
parser = argparse.ArgumentParser(
description=("Find the latitude and longitude using the "
"Vernal Equinox and sunset."))
parser.add_argument(
'-v', '--ve-ss', action='store_true', default=False, dest='ve_ss',
help="Find the Vernal Equinox and sunset.")
parser.add_argument(
'-l', '--find-longitude', action='store_true', default=False,
dest='find_longitude', help="Find the length of each of the 50 years.")
parser.add_argument(
'-F', '--full', action='store_true', default=False,
dest='full', help=("Use the full spread of years from 172 to 221. "
"Caution, this will take many hours to complete."))
parser.add_argument(
'-L', '--longitude', action='store_true', default=False,
dest='longitude', help=("If True use generated longitudes, else "
"if False used default longitude."))
options = parser.parse_args()
cfmes = DumpFindMomentOfEquinoxesOrSolstices()
ret = 0
basename = os.path.basename(__file__)
if options.ve_ss: # -v
if options.longitude:
print("Cannot use option -L (--longitude) with option -v "
"(--ve-ss).", file=sys.stderr)
ret = 1
else:
data = cfmes.dump_sunset_after_ve(options)
print("The SS Diff is the difference between the Julian Period "
"days of the World Centre and my sunset times in Tehran and "
"the VE Diff is the difference\nbetween the Julian Period "
"days of the USNO Vernal Equinox, and my Vernal Equinox. "
"The USNO Vernal Equinox date and time was originally in "
"UTC\ntime which I converted to Tehran standard time "
"(+03:30).\n")
print("Year WC Sunset My Gregorian Sunset "
"SS VE Dif USNO's Vernal Equinox (Tehran) "
"My Vernal Equinox (Tehran) VE Diff HH:MM:SS Diff")
underline_length = 146
print('-' * underline_length)
[print(f"{year} "
f"{str(wc_ss):<13} "
f"{str(my_g_ss):<30} "
f"{fmt_float(ss_diff, 2, 6)} "
f"{str(nasa_ve):<30} "
f"{str(my_ve):<30} "
f"{fmt_float(ve_diff, 1, 6)} "
f"{str(hms)}"
) for (year, wc_ss, my_g_ss, ss_diff,
nasa_ve, my_ve, ve_diff, hms) in data]
print('-' * underline_length)
print(f"\n./contrib/misc/{basename} -v")
elif options.find_longitude: # -l
if not options.longitude:
print("Option -L (--longitude) must be used with option -l "
"(--find-longitude).",
file=sys.stderr)
ret = 1
else:
start_time = time.time()
F = 'F' if options.full else ''
data = cfmes.find_longitude(options)
print(f"./contrib/misc/{basename} -lL{F}")
print("Year | WC Date My Date Min Lon JD Diff | "
"WC Date My Date Max Lon JD Diff")
print("-" * 4, "|", "-" * 47, "|", "-" * 47)
[print(f"{year:04} | "
f"{str(wc_date0):<13} "
f"{str(my_date0):<13} "
f"{min_l:<9} "
f"{fmt_float(diff0, 2, 6)} | "
f"{str(wc_date1):<13} "
f"{str(my_date1):<13} "
f"{max_l:<9} "
f"{fmt_float(diff1, 2, 6)}"
) for (year, wc_date0, my_date0, min_l, diff0,
wc_date1, my_date1, max_l, diff1) in data]
r_min_l = r_max_l = 0
for (year, wc_date0, my_date0, min_l, diff0,
wc_date1, my_date1, max_l, diff1) in data:
if min_l > r_min_l:
r_min_l = min_l
elif max_l > r_max_l:
r_max_l = max_l
optimal_lon = (r_max_l - r_min_l) / 2 + r_min_l
print(f"\nThe optimal longitude is: {optimal_lon}")
end_time = time.time()
days, hours, minutes, seconds = cfmes._dhms_from_seconds(
end_time - start_time)
print(f"\nElapsed time: {hours:02} hours, {minutes:02} minutes, "
f"{round(seconds, 6):02.6} seconds.")
else:
parser.print_help()
sys.exit(ret)