How do I compute equinox/solstice moments? - equinox

What algorithms or formulas are available for computing the equinoxes and solstices? I found one of these a few years ago and implemented it, but the precision was not great: the time of day seemed to be assumed at 00:00, 06:00, 12:00, and 18:00 UTC depending on which equinox or solstice was computed. Wikipedia gives these computed out to the minute, so something more exact must be possible. Libraries for my favorite programming language also come out to those hardcoded times, so I assume they are using the same or a similar algorithm as the one I implemented.
I also once tried using a library that gave me the solar longitude and implementing a search routine to zero in on the exact moments of 0, 90, 180, and 270 degrees; this worked down to the second but did not agree with the times in Wikipedia, so I assume there was something wrong with this approach. I am, however, pleasantly surprised to discover that Maimonides (medieval Jewish scholar) proposed an algorithm using the exact same idea a millenium ago.

A great source for the (complex!) underlying formulas and algorithms is Astronomical Algorithms by Jean Meeus.
Using the PyMeeus implementation of those algorithms, and the code below, you can get the following values for the 2018 winter solstice (where "winter" refers to the northern hemisphere).
winter solstice for 2018 in Terrestrial Time is at:
(2018, 12, 21, 22, 23, 52.493725419044495)
winter solstice for 2018 in UTC, if last leap second was (2016, 12):
(2018, 12, 21, 22, 22, 43.30972542127711)
winter solstice for 2018 in local time, if last leap second was (2016, 12)
and local time offset is -7.00 hours:
(2018, 12, 21, 15, 22, 43.30973883232218)
i.e. 2018-12-21T15:22:43.309725-07:00
Of course, the answer is not accurate down to microseconds, but I also wanted to show how to do high-precision conversions with arrow.
Code:
from pymeeus.Sun import Sun
from pymeeus.Epoch import Epoch
year = 2018 # datetime.datetime.now().year
target="winter"
# Get terrestrial time of given solstice for given year
solstice_epoch = Sun.get_equinox_solstice(year, target=target)
print("%s solstice for %d in Terrestrial Time is at:\n %s" %
(target, year, solstice_epoch.get_full_date()))
print("%s solstice for %d in UTC, if last leap second was %s:\n %s" %
(target, year, Epoch.get_last_leap_second()[:2], solstice_epoch.get_full_date(utc=True)))
solstice_local = (solstice_epoch + Epoch.utc2local()/(24*60*60))
print("%s solstice for %d in local time, if last leap second was %s\n"
" and local time offset is %.2f hours:\n %s" %
(target, year, Epoch.get_last_leap_second()[:2],
Epoch.utc2local() / 3600., solstice_local.get_full_date(utc=True)))
Using the very cool more ISO and TZ aware module Arrow: better dates and times for Python, that can be printed more nicely:
import arrow
import math
slutc = solstice_epoch.get_full_date(utc=True)
frac, whole = math.modf(slutc[5])
print("i.e. %s" % arrow.get(*slutc[:5], int(whole), round(frac * 1e6)).to('local'))

I'm not sure if this is an accurate enough solution for you, but I found a NASA website that has some code snippets for calculating the vernal equinox as well as some other astronomical-type information. I've also found some references to a book called Astronomical Algorithms which may have the answers you need if the info somehow isn't available online.

I know you're looking for something that'll paste into an answer here, but I have to mention SPICE, a toolkit produced by NAIF at JPL, funded by NASA. It might be overkill for Farmer's Almanac stuff, but you mentioned interest in precision and this toolkit is routinely used in planetary science.

I have implemented Jean Meeus' (the author of the Astronomical Algorithms referenced above) equinox and solstice algorithm in C and Java, if you're interested.

Related

How to calculate the difference in minutes between two bedtimes (NOT time elapsed)?

I'm trying to get Google Sheets functions to calculate the difference in minutes between two bedtimes and have been spinning my wheels for at least five hours on this. Here are four examples of what I'm trying to accomplish:
BEDTIME 1 BEDTIME 2 DIFF IN MINS
9:00 PM 9:15 PM 15
9:00 PM 10:00 PM 60
11:30 PM 1:00 AM 90
1:00 AM 11:00 PM 120
As you can see, the date doesn't figure at all. I apologize for not offering up code, but I've tried at least half a dozen approaches from other answers and they aren't working -- mainly, I suspect, because most people are looking to find the time elapsed between the two times whereas I'm looking to determine "how much earlier" or "how much later" one bedtime is relative to another (always expressed as a positive value).
Any help would be appreciated. Thanks.
Times are stored as numbers between 0 and 1. If you subtract two times and multiply the result by 24 x 60 = 1440 and format as a number you’ll get number of minutes. I think you’ll need something like:
=MIN(ABS(1440*(B1-A1)), ABS(1440*(B1-A1-1)), ABS(1440*(B1-A1+1)))
The difference between two times is a duration. The question requests that durations be converted to "digital minutes", but that is often not as readable as one would think. 175 minutes is more difficult to understand than 2:55 hours.
There is therefore usually no point in multiplying by 24 * 60 — instead, just use the duration value as is:
=min( abs(B2 - A2), abs(B2 - A2 - 1), abs(B2 - A2 + 1) )
Format the result cell as Format > Number > Duration.
See this answer for an explanation of how date and time values work in spreadsheets.
use arrayformula:
=INDEX(IFERROR(1/(1/TRANSPOSE(QUERY(TRANSPOSE(
IF(A2:A&B2:B="", 0, ABS(1440*(B2:B-A2:A+{-1, 0, 1})))),
"select "&TEXTJOIN(",", 1,
"min(Col"&ROW(A2:A)-ROW(A2)+1&")"))))),, 2)
or:
=INDEX(IFERROR(1/(1/QUERY(SPLIT(FLATTEN(
ROW(A2:A)&"×"&ABS(1440*(B2:B-A2:A+{-1, 0, 1}))), "×"),
"select min(Col2) group by Col1 label min(Col2)''"))))
Try to implement a modulus function in your code. It would basically do something like this:
If x = -5, then y = f(x) = – (-5) = 5, since x is less than zero
If x = 10, then y = f(x) = 10, since x is greater than zero
If x = 0, then y = f(x) = 0, since x is equal to zero
Therefore calculating how much time passed without negative values.

Using Context-Free Grammar To Parse Options Spread Order Strings?

I need to create a tool that reads in an options spread order in string format and spits it out in human readable format.
Examples:
Input:
BUY +6 VERTICAL LUV 100 (Weeklys) 28 AUG 20 37.5/36.5 PUT #.49 LMT
Output:
VERTICAL
BUY +6 LUV 28 AUG 20 (Weeklys) 37.5 PUT
SELL -6 LUV 28 AUG 20 (Weeklys) 36.5 PUT
.49 DEBIT LMT
Input:
BUY +1 DIAGONAL AMGN 100 (Weeklys) 4 SEP 20/28 AUG 20 245/240 CALL #.07 LMT
Output:
DIAGONAL
BUY +1 AMGN 4 SEP 20 (Weeklys) 245 CALL
SELL +1 AMGN 28 AUG 20 (Weeklys) 240 CALL
-.07 CREDIT LMT
On the surface a context-free grammar appears to be a good solution to express the various syntax (diagonal spreads are more complicated). But having almost no experience with context-free grammars I am not sure how I would carry the numbers over and also how I would for instance add the SELL orders which are not explicitly mentioned in the original order string. The SELL leg is assumed due to it being a vertical spread for example.
Hope this makes sense even if you are not an option trader ;-) The basic idea here is that translating the original string requires a bit of intelligence and is not just a matter of generating different text.
Any insights and pointers would be welcome.
It's a little hard to tell from only 2 examples, but my guess is, using a context-free grammar (especially if you have almost no experience with them) is probably overkill. The grammar itself would probably be simple enough, but you would need to either add 'actions' to transform the recognized input into the desired output, or have the parser build a syntax-tree and then write code to extract the data from the tree and generate the desired output.
It would be simpler to use regular expressions with capturing. For instance, here's some python3 code that pretty much handles your 2 examples:
import sys, re
for line in sys.stdin:
mo = re.fullmatch(r'BUY \+(\d+) (VERTICAL|DIAGONAL) (\S+) 100 \(Weeklys\) (\d+ \w+ \d+)(?:/(\d+ \w+ \d+))? ([\d.]+)/([\d.]+) (PUT|CALL) #(.\d+) LMT\n', line)
(n_units, vert_or_diag, name, date1, date2, price1, price2, put_or_call, limit) = mo.groups()
if vert_or_diag == 'VERTICAL':
assert date2 is None
date2 = date1
print()
print(vert_or_diag)
print(f"BUY +{n_units} {name} {date1} (Weeklys) {price1} {put_or_call}")
print(f"SELL -{n_units} {name} {date2} (Weeklys) {price2} {put_or_call}")
print(f"{limit} DEBIT LMT")
It's not perfect, because the problem isn't perfectly specified (e.g., it's unclear what causes the human readable format to have a positive DEBIT vs a negative CREDIT). And the space of inputs is no doubt larger than the regex currently handles.
The point is just to show that, based on the examples given, regular expressions could be a compact solution to the general problem.

How do I get the first two digits from a number?

I need to make a code that tells you the century when you give the year. I have this:
local kata = {}
function kata.century(number)
if number%100 == 0 then >I need to get the first two numbers
return
else
return number/100 + 1
end
end
return kata
I basically need a line that gives me the first two numbers of the year for years like "1700" and "2000"
so I can divide them by 100 and add 1.
(i'm a beginner btw)
In Lua 5.3+, use number//100.
For earlier versions, use math.floor(number/100).
According to the Gregorian calendar, 1 CE was the first year of the 1st Century CE. Since a century is a period of 100 years, this means that the first year of any century in the common era ends with a 1; thus 2000 was the last year of the 20th Century, and 2001 was the first year of the 21st Century.
Finding the century from the first two digits of the year alone will not work for this strictly correct method of identification. Taking the first two digits of 2000, and adding 1 would yield the 21st Century. But, instead of using math.floor to truncate the result of division by 10, one can use math.ceil to get the smallest integer greater than the result of the division.
function century (year)
return math.ceil(year / 100)
end
This century function gives the correct century given a year in the common era:
> century(1)
1
> century(100)
1
> century(101)
2
> century(2000)
20
> century(2001)
21
There is a convention in popular usage that centuries should be numbered based on shared digits instead of the Gregorian calendar. In this usage all years beginning with 20 are in the 21st Century, making 2000 the first year of the 21st Century. Since there is no year 0 in the Gregorian calendar, this means that the 1st Century (from 1 CE to 99 CE under this convention) spans 99 years, but all other centuries in the common era span 100 years (e.g., 100 CE to 199 CE). Finding the century from the year using this convention can be done by dividing the year by 100 and taking the floor of the result.
If the goal is to match popular expectations and follow the general popular misunderstanding of numbering centuries, use the floor method. But, if the goal is to get correct and consistent numbering of centuries based on the Gregorian calendar, use the ceiling method.

Find the start of a given week and year with Delphi

I'm looking for a Delphi (10+) function that returns a TDate with a given year and a week number:
function StartDate(2021, 53): TDate should return 2021-01-01.
While WeekOfTheYear(EncodeDate(2021,1,1)) returns 53 (correct), I can't do the other way round with StartOfAWeek(2021,53, 1) nor StartOfAWeek(2021,53, 5) (5=it's a friday) - it's not recognized as a valid date (=exception). Any suggestions?
Edited:
I'm looking for a ISO 8601 compliant function (like the internal Delphi routines), with Monday=1 and special week consideration (like 2021-01-01), or to be more precise: the "vice versa" routine of WeekOfTheYear
The function you need is:
StartOfAWeek(Year, Week, 1)
You observe that StartOfAWeek(2021, 53, 1) raises an exception. That is correct because 2021 does not have 53 weeks. It only has 52. Week 52 ends on the last day of 2021.
You are getting confused by the result of
WeekOfTheYear(EncodeDate(2021,1,1))
This returns 53, but because the date is at the start of the year, this is week 53 of 2020.

why pytz.country_timezones('cn') in centos system have different result?

Two computer install centos 6.5, kernel is 3.10.44, have different result.
one result is [u'Asia/Shanghai', u'Asia/Urumqi'], and the other is ['Asia/Shanghai', 'Asia/Harbin', 'Asia/Chongqing', 'Asia/Urumqi', 'Asia/Kashgar'].
Is there any config that make the first result same as the second result?
I have following python code:
def get_date():
date = datetime.utcnow()
from_zone = pytz.timezone("UTC")
to_zone = pytz.timezone("Asia/Urumqi")
date = from_zone.localize(date)
date = date.astimezone(to_zone)
return date
def get_curr_time_stamp():
date = get_date()
stamp = time.mktime(date.timetuple())
return stamp
cur_time = get_curr_time_stamp()
print "1", time.strftime("%Y %m %d %H:%M:%S", time.localtime(time.time()))
print "2", time.strftime("%Y %m %d %H:%M:%S", time.localtime(cur_time))
When use this code to get time, the result of one computer(have 2 results) is:
1 2016 04 20 08:53:18
2 2016 04 20 06:53:18
and the other(have 5 results) is:
1 2016 04 20 08:53:18
2 2016 04 20 08:53:18
I don't know why?
You probably just have an outdated version of pytz on the system returning five time zones (or perhaps on both systems). You can find the latest releases here. It's important to stay on top of time zone updates, as the various governments of the world change their time zones often.
Like most systems, pytz gets its data from the tz database. The five time zones for China were reduced to two in version 2014f (corresponding to pytz 2014.6). From the release notes:
China's five zones have been simplified to two, since the post-1970
differences in the other three seem to have been imaginary. The
zones Asia/Harbin, Asia/Chongqing, and Asia/Kashgar have been
removed; backwards-compatibility links still work, albeit with
different behaviors for time stamps before May 1980. Asia/Urumqi's
1980 transition to UTC+8 has been removed, so that it is now at
UTC+6 and not UTC+8. (Thanks to Luther Ma and to Alois Treindl;
Treindl sent helpful translations of two papers by Guo Qingsheng.)
Also, you may wish to read Wikipedia's Time in China article, which explains that the Asia/Urumqui entry is for "Ürümqi Time", which is used unofficially in some parts of the Xinjiang region. This zone is not recognized by the Chinese government, and is considered a politically charged issue. As such, many systems choose to omit the Urumqi time zone, despite it being in listed in the tz database.

Resources