To JulianDateNumbers and back again to Gregorian CalendarDates

Hello.

When dealing with Astronomy, it can be useful to convert between Julian Day numbers, and Gregorian Calendar date.
If the topic intrigues you, then I recommend reading the article at Wikipedia Referenced in the code.

I have tested both handlers, and the conversion from a Julian Date Number seems to work for every date after but not including 15th October 1582, which was the day the Gregorian Calendar Reform was implemented.
You can test the handlers towards this calculator.


# Version 1.5 corrected faulty truncation to integer in 
# timeTriplet from JD
# Added handler for returning UTTIME(JD)
# Added handler converting from JDEtoJD(), and renamed its
# twin to JDtoJDE()
# Version 1.4 corrected faulty truncation to integer in 
# gregorianCalendarTriplet
# Version 1.3 added: JDN_0h that returns Julian day at 0h
# Added JD_dayOfWeek that returns the weekday.
# version 1.2 Fixed up deltaT with horner and put here.
# Version 1.1 Added handler timeTriplet from JDN that 
# returns time in "standard format
property EpochJD2000 : 2.451545E+6
property JulianDaysInCentury : 36525
# Added 19 sep 2013
# http://en.wikipedia.org/wiki/Julian_day
# http://macscripter.net/viewtopic.php?pid=166599#p166599
#  JD stands for Julian Date. 0h is 00:00 midnight, 12h
#  is 12:00 noon, GMT unless specified else wise.
# http://en.wikipedia.org/wiki/Julian_day

# set jdnr to JDN(1600, 1, 1, 12, 1, 17.8)
# for playing with time below.
# set jdnr to JDN(1600, 1, 1, 11, 30, 17.8)
#set time_now_GMT to (current date)
#tell time_now_GMT
#	
#	
#	set year of it to 1995
#	set month of it to 10
#	set day of it to 1
#	
#	set hours of it to 9
#	set minutes of it to 0
#	set seconds of it to 0
#	
#end tell
#tell (my datelib's makeDateTimeSextet(time_now_GMT))
#	set JD to (my JulianDateLib's JDN(item 1, item 2, item 3, item 4, item 5, item 6))
#end tell
# my JulianDateLib's JDN(1995, 10, 1, 9, 0, 0)
# set jdnr to JDN(1582, 10, 16, 0, 0, 0)
# set mtime to timeTriplet from jdnr
# set gregTriplet to gregorianCalendarTriplet from jdnr


# Handlers that adjust the time of a JDN back to the
# civil system, where the day starts at 0h midnight with
# a 24 hour clock. It is your responsibility to differ 
# Between UT and TD times (deltaT). Both handlers deliver
# the times in decimal time.

# returns the Julian day number at UT time 0h for that day.
on JDN_UT0h(JD)
	return (JD - ((JD + 0.5) mod 1))
end JDN_UT0h

# returns the UTTime for a Julian Day number.
# the time is translated back to normal, that is
# 12 hours are added, since we are counting hours from
# Midnight, and not from noon the previous day!

on UTTIME(JD)
	return ((JD + 0.5) mod 1)
end UTTIME

# returns a time triplet, from a Julian day number,
# Adjusted to return UT.

on timeTriplet from JDN
	# This returns as standard time triplet, where the first point of time of a day is 00:00
	# and the last point of a time is 23:59:59.9999999 and so on...
	local timeFraction, tempvar, _hours, _minutes, _seconds
	set timeFraction to (JDN mod 1) * 24
	set tempvar to (timeFraction div 1 as integer)
	set _hours to (tempvar + 12) mod 24
	set timeFraction to (timeFraction - tempvar)
	set _minutes to timeFraction * 60 div 1
	set _seconds to (timeFraction - (_minutes / 60)) * 3600
	return {_hours, _minutes, _seconds}
end timeTriplet


# Handlers that generates the Julian Day Number JD,
# or the Julian Ephemeris Time Daynber JED.
# for a given date and time sextet.

on JDN(_year, _month, _day, _hour, _min, _sec)
	# From Wikipedia
	# The Julian Date (JD) of any instant is the Julian day number
	# for the preceding noon plus the fraction of the day since
	# that instant. Julian Dates are expressed as a Julian day 
	# number with a decimal fraction added. For example, The Julian
	# Date for 00:30:00.0 1 January 2013 is 2456293.520833.
	local a, y, m, _JDN, time_fraction
	set a to ((14 - _month) div 12)
	set y to _year + 4800 - a
	set m to _month + (12 * a) - 3
	#  Gregorian Calendar starts October 15, 1582
	if _year > 1582 then
		set _JDN to _day + ((153 * m + 2) div 5) + 365 * y + (y div 4) - y div 100 + y div 400 - 32045
	else if _year = 1582 and _month > 10 then
		set _JDN to _day + ((153 * m + 2) div 5) + 365 * y + (y div 4) - y div 100 + y div 400 - 32045
	else if _year = 1582 and _month = 10 and _day ≥ 15 then
		set _JDN to _day + ((153 * m + 2) div 5) + 365 * y + (y div 4) - y div 100 + y div 400 - 32045
	else
		# Julian calendar before oct 15, 1582
		set _JDN to _day + ((153 * m + 2) div 5) + 365 * y + (y div 4) - 32083
	end if
	set _hour to (_hour - 12) / 24
	if _min ≠ 0 then
		set _min to _min / 1440 # minutes per day
	end if
	
	if _sec ≠ 0 then
		set _sec to _sec / 86400 # seconds per day
	end if
	
	set _JDN to _JDN + _hour + _min + _sec
	
	return _JDN
end JDN

on JDE(_year, _month, _day, _hour, _min, _sec)
	# it is a way to get JDE directly when not
	# JD is needed.
	# From Wikipedia
	# The Julian Date (JD) of any instant is the Julian day number
	# for the preceding noon plus the fraction of the day since
	# that instant. Julian Dates are expressed as a Julian day 
	# number with a decimal fraction added. For example, The Julian
	# Date for 00:30:00.0 1 January 2013 is 2456293.520833.
	local a, y, m, _JDN, time_fraction
	set a to ((14 - _month) div 12)
	set y to _year + 4800 - a
	set m to _month + (12 * a) - 3
	#  Gregorian Calendar starts October 15, 1582
	if _year > 1582 then
		set _JDN to _day + ((153 * m + 2) div 5) + 365 * y + (y div 4) - y div 100 + y div 400 - 32045
	else if _year = 1582 and _month > 10 then
		set _JDN to _day + ((153 * m + 2) div 5) + 365 * y + (y div 4) - y div 100 + y div 400 - 32045
	else if _year = 1582 and _month = 10 and _day ≥ 15 then
		set _JDN to _day + ((153 * m + 2) div 5) + 365 * y + (y div 4) - y div 100 + y div 400 - 32045
	else
		# Julian calendar before oct 15, 1582
		set _JDN to _day + ((153 * m + 2) div 5) + 365 * y + (y div 4) - 32083
	end if
	
	set _hour to (_hour - 12) / 24
	if _min ≠ 0 then
		set _min to _min / 1440 # minutes per day
	end if
	
	if _sec ≠ 0 then
		set _sec to _sec / 86400 # seconds per day
	end if
	
	set _JDN to _JDN + _hour + _min + _sec
	return (_JDN + ((deltaT(decimalYear(_year, _month))) / 86400))
end JDE

# utility handler that converts between JD and JDE dates
# (Adjusts for dynamical time)

on JDtoJDE(JD, yr, mnt)
	# Meus p. 131, but the  deltaT is from Nasa	
	return (JD + ((deltaT(decimalYear(yr, mnt))) / 86400))
end JDtoJDE

on JDEtoJD(JDE, yr, mnt)
	# Meus p. 131, but the  deltaT is from Nasa	
	return (JDE - ((deltaT(decimalYear(yr, mnt))) / 86400))
end JDEtoJD

# returns the number of Julian centuries that has passed
# since the JDE
on JulianCenturies by JDE
	# Meeus (11.1)
	return ((JDE - (my EpochJD2000)) / (my JulianDaysInCentury))
end JulianCenturies

# returns the day of a week for a JD 0 is sunday, 6 is Saturday
on JD_dayofweek(JD)
	# Meeus p. 65
	return (((JD div 1 + 0.5) + 1.5) mod 7)
end JD_dayofweek

# http://www2.arnes.si/~gljsentvid10/sidereal.htm 
# The formula below will find the days since J2000.o for any
# date after 1900 and before 2099.

on JDAysSinceJD_2000(_year, _month, _day, _hour, _min, _sec)
	# Faster way if you want the difference
	set dwhole to 367 * _year - ¬
		((7 * (_year + ((_month + 9) / 12) as integer) / 4) ¬
			as integer) + ¬
		((275 * _month / 9) as integer) + ¬
		_day - 7.305315E+5
	set dfrac to (_hour + _min / 60 + _sec / 3600) / 24
	set JD to dwhole + dfrac + 1
	# I have added one for the same result as if I 
	# subtracted 2.451545E+6 julian date 1 of januar 2000 00:00
	# from a date for consistency
	return JD
end JDAysSinceJD_2000



on gregorianCalendarTriplet from JDN
	# This is an algorithm by Richards to convert a Julian Day Number, J, to a date in the Gregorian calendar
	# (proleptic, when applicable). Richards does not state which dates the algorithm is valid for.
	# it seems to work for dates after, BUT NOT INCLUDING 15th of oct 1582.
	# I have extended it to return a hour, min, sec triplet for the fractional part of the JDN.
	local y, m, n, r, p, v, s, w, b, c, f, g, h, d, MT, yr, JD
	set y to 4716
	set j to 1401
	set m to 2
	set n to 12
	set r to 4
	set p to 1461
	set v to 3
	set u to 5
	set s to 153
	set w to 2
	set b to 274277
	set c to -38
	
	set JD to JDN div 1
	set f to JD + j + (((4 * JD + b) div 146097) * 3) div 4 + c
	set e to r * f + v
	set g to (e mod p) div r
	set h to u * g + w
	set d to (h mod s) div u + 1
	set MT to (h div s + m) mod n + 1
	set yr to e div p - y + (n + m - MT) div n
	return {yr, MT, d}
end gregorianCalendarTriplet

# aDecimalYear = year + (month - 0.5) / 12
# returns a decimal year as needed by deltaT
on decimalYear(yr, mnth)
	return (yr + (mnth - 0.5) / 12)
end decimalYear

on deltaT(aDecimalYear)
	# http://eclipse.gsfc.nasa.gov/SEcat5/deltatpoly.html
	# simplifed, halved the contents of the if tests, 
	# and speeded up the multiplication of powers thanks to Nigel Garvey
	# Further improved speed and space by implementing Horners rule. Sedgewick Algorithms
	# second edition p. 525, but I found that a left to right approach with regards to
	# coeffecients for the powers, (like below), was a *much* easier scheme to implement.
	local t
	if aDecimalYear < -500 then
		set t to (aDecimalYear - 1820) / 100
		return (-20 + 32 * (t ^ 2))
	else if aDecimalYear < 500 then
		set t to aDecimalYear / 100
		return (1.05836E+4 + (t * (-1014.41 + (t * (33.78311 + (t * (-5.952053 + (t * (-0.1798452 + (t * (0.022174192 + (t * 0.0090316521))))))))))))
	else if aDecimalYear < 1600 then
		set t to (aDecimalYear - 1000) / 100
		return (1574.2 + (t * (-556.01 + (t * (71.23472 + (t * (0.319781 + (t * (-0.8503463 + (t * (-0.005050998 + (t * 0.0083572073))))))))))))
	else if aDecimalYear < 1700 then
		set t to aDecimalYear - 1600
		return (120 + (t * (-0.9808 + (t * (-0.01532 + (t / 7129))))))
	else if aDecimalYear < 1800 then
		set t to aDecimalYear - 1700
		return (8.83 + (t * (0.1603 + (t * (-0.0059285 + (t * (1.3336E-4 - (t / 1174000))))))))
	else if aDecimalYear < 1860 then
		set t to aDecimalYear - 1800
		return (13.72 + (t * (-0.332447 + (t * (0.0068612 + (t * (0.0041116 + (t * (-3.7436E-4 + (t * (1.21272E-5 + (t * (-1.69E-7 + (t * 8.75E-10))))))))))))))
	else if aDecimalYear < 1900 then
		set t to aDecimalYear - 1860
		return (7.62 + (t * (0.5737 + (t * (-0.251574 + (t * (0.01680668 + (t * (-4.473624E-4 + (t / 233174))))))))))
	else if aDecimalYear < 1920 then
		set t to aDecimalYear - 1900
		return (-2.79 + (t * (1.494119 + (t * (-0.0598939 + (t * (0.0061966 - (t * 1.97E-4))))))))
	else if aDecimalYear < 1941 then
		set t to aDecimalYear - 1920
		return (21.2 + (t * (0.84493 + (t * (-0.0761 + (t * 0.0020936))))))
	else if aDecimalYear < 1961 then
		set t to aDecimalYear - 1950
		return (29.07 + (t * (0.407 + (t * (-1 / 233 + (t / 2547))))))
	else if aDecimalYear < 1986 then
		set t to aDecimalYear - 1975
		return (45.45 + (t * (1.067 + (t * (-1 / 260 - (t / 718))))))
	else if aDecimalYear < 2005 then
		set t to aDecimalYear - 2000
		return (63.86 + (t * (0.3345 + (t * (-0.060374 + (t * (0.0017275 + (t * (6.51814E-4 + (t * 2.373599E-5))))))))))
	else if aDecimalYear < 2050 then
		set t to aDecimalYear - 2000 # (correct!)
		return (62.92 + (t * (0.32217 + (t * 0.005589))))
	else if aDecimalYear < 2150 then
		return (-20 + (32 * (((aDecimalYear - 1820) / 100) ^ 2 - (0.5628 * (2150 - aDecimalYear)))))
	else
		set t to (aDecimalYear - 1820) / 100
		return (-20 + (32 * (t ^ 2)))
	end if
end deltaT


Added a handler timeTriplet from JDN that returns time in “standard format” that is, that the hours starts at midnight, and ends at 23.59 by a 24 hour clock. Julian Dates uses a clock that starts at previous noon internally.

Having said that, there are a lot of benefits when working with Julian Date Numbers (JD), like that you can just test for lesser than, and greater than, and add days as well, or subtract them, and then convert the resulting daynumber to a Gregorian Calendar date (Usable from 16th October 1582 onwards here) . By the way: File Maker Pro uses Julian dates for their time stamp data type.

Version 1.3 Added handler JDAysSinceJD_2000 that returns
number of days since JD2000 which is 1 of jan 2000 12:00
that has the number 2.451545E+6
Version 1.2 Removed bug, concerning adding of hours in JDN().

I have made the JDAysSinceJD_2000 handler be consistent with regular subtraction of dates, so the dates since, is the same number you would obtain if you subtracted the date. (So it delivers really days since, + 1 ).

Hello.

I have added a handler JDN_0h that returns theJulian day at 0h, and a handler:JD_dayOfWeek that returns the weekday, 0 is sunday. I have also fixed a bug that was a faulty rounding to integer, instead of truncation to integer in gregorianCalendarTriplet. I have also added a handler time_TinJulianCenturies, that returns the time in centuries since the Julian Epoch 2000.0.

Hello.

So I have corrected another faulty truncation to integer in timeTriplet from JD ( I have been using as integer, instead of div 1 ). Since the JulianDayNumbers are having the slightly unusal convetion of using Astronomical time where a new day starts at noon, I added a handler that corrects the time back to civil time where the day starts at midnight: UTTIME(JD). I also changed the name of the calculateJDE() handler to JDtoJDE(), and created its twin JDEtoJD() and time_TInJulianCenturies() became: JulianCenturies().

I hope someone can use this. Filemaker, uses a version of JulianDaynumbers behind the scenes, where you see a time stamp. Given the fact that we do have AppleScript Date objects, that can do a whole lot for us, there aren’t any immediate productivity gains in using Julian Day Numbers, but they should cater for a nice way to store dates in either text files, or in Database Events files, and Julian Day Numbers are very easy to calculate with! It is also a must if you intend to calculate on astronomical objects with AppleScript, (which is a lot more fun than you think it is.) :slight_smile:

Hi.

I’m surprised to find that these work on both my Mountain Lion (64-bit Intel) and Leopard (G5 PPC) systems, although the operation’s somewhat outside AppleScript’s official date range:

-- 24th November 4714 BC 12:00:00 (Gregorian) as an AppleScript date!
property JD0 : («data isot303030312D31312D3234543132» as date) - ((«data isot343731342D31312D3234543132» as date) - ((«data isot303030312D31312D3234543132» as date) - 366 * days))

on JulianDayNumber for theDate
	return (theDate - JD0) div days
end JulianDayNumber

on JulianDate for theDate
	return (theDate - JD0) / days
end JulianDate

on GregorianDate for theJDN
	return JD0 + theJDN * days
end GregorianDate

JulianDayNumber for date ("1 1 2000 12:00:00") --> 2451545
JulianDate for date ("1 1 2013 00:30:00") --> 2.45629352083333E+6
GregorianDate for result -- date "Tuesday 1 January 2013 00:30:00"

I said AppleScript’s “official” date range, but in fact the current ASLG makes no mention of one. I’ve just looked out the previous (1999) Guide, which had this to say about date ranges:

AppleScript can certainly handle a wider range of dates now (current bugs notwithstanding), so these handlers should be good for any Gregorian date in the range 30081 BC to 29940 AD (with possibly a few missing at the extremes) ” if you can work out how to turn the very early ones into AppleScript date objects!

Hello.

Now that is totally amazing! Those handlers, are considerably shorter than what I have to do than without them ( converting the as date to a JD with a line or two.) It is also totally amazing that it works, so far outside the range that was Applescripts range for dates (1000AD).

Thanks a lot Nigel! :slight_smile:

By the way, I think defining a date with Unix’s date command, and returning it as a class isot, should be an ok way to define such an early date.

Some other tidbits concerning Julian Day Numbers, and AppleScript:

There is a difference between historical and astronomical years before bc, as far as I remember, there are no 0BC astronomically, so the year 1BC in a history book, would be the year −1 for an astronomer.

There is also the difference between a solar (tropical year) that is some 365.242374 days per year, whereas a JulianDayNumber (JulianDate) year is defined to be 365.25 days exactly. (Something to be aware of if you are working with a resolution of seconds, at least, since ASDates, works with the Tropical year, as do UTC.)

Edit

A year, as defined by Apple Script date object does also consist of 365.25 days just like the Julian years, as the snippet below shows: (I hadn’t expected that, since a Gregorian year is the same or very close to a Solar year, with maybe a discrepancy of some 25 seconds. (365.242374 days per year.)

set a to date (("1.1.2011") as text)
set b to date (("1.1.1911") as text)
set c to (a - b) / 86400
-- > 36525

Edit++
I have changed the explanation about the difference between historically and astronomically years. the year 15 BC is −14 Astronomically.

I was expecting different values here because I’m not on GMT, but I was a bit surprised to see that my last result had a 30 minute difference:

GregorianDate for result --> date "Tuesday, 1 January 2013 0:00:00 +1100"

FWIW, the docs for LongDateTime, which is what AS uses under the hood, say:

Hmmm. It works OK here with my computer set to your time zone and with medium time set to include the zone offset. :confused:

That would slow down Calendar a bit! :wink:

Are you sure you didn’t paste in the wrong result above? The 00:30:00 seems strange – I don’t get that using your zone either.

Yep. Running the ‘GregorianDate’ handler on the result of the ‘JuiianDate’ handler exactly reproduces the date and time fed to the latter.

The date in the script ” half past midnight on the morning of 1st January 2013 ” is used for the Julian Date example in the Wikipedia article linked at the top of this thread. The article gives the equivalent Julian Date as 2456293.520833 (days), which is 0.00000033 days (just under 3/100ths of a second) less than the 2.45629352083333E+6 returned by my ‘JulianDate’ handler.

It turns out that this way of setting the JD 0 date just happens to work with 4714 BC. As a general method, it’s subject to the uncertainies of where the leap years occur.

I’ve just posted a better method for deriving date objects with far-future and BC dates. Using that and precalculating some of the figures, the above could be simplified to:

property JD0 : («data isot303038372D31312D3234543132» as date) - 1.514733696E+11