Julian dates for proleptic Gregorian and Julian calendars

Julian dates for proleptic Gregorian and Julian calendars

The Python module jdcal provides a few functions for calculating Julian dates for Gregorian and Julian calendar dates.  Julian dates are stored as two floating point numbers for maximum precision.


The module can be installed using easy_install or pip.

$ pip install jdcal


$ easy_install jdcal

The module can be downloaded from the pypi page for the module.

The source code repository is at http://github.com/phn/jdcal .

The module is available under the BSD license (http://www.opensource.org/licenses/bsd-license.php).


The function gcal2jd accepts the year, month and day of a Gregorian calendar date and returns corresponding Julian date. The function jcal2jd accepts the year, month and day of a Julian calendar date and returns the corresponding Julian date. Functions jd2gcal and jd2jcal take Julian dates and returns Gregorian and Julian calendar dates, respectively.

Different regions of the world switched to Gregorian calendar from Julian calendar on different dates. Having separate functions for Julian and Gregorian calendars allow maximum flexibility in choosing the relevant calendar.

All the above functions are “proleptic”. This means that they work for dates on which the concerned calendar is not valid. For example, Gregorian calendar was not used prior to around October 1582.

Julian dates are stored in two floating point numbers (double). Julian dates, and Modified Julian dates, are large numbers. If only one number is used, then the precision of the time stored is limited. Using two numbers, time can be split in a manner that will allow maximum precision. For example, the first number could be the Julian date for the beginning of a day and the second number could be the fractional day. Calculations that need the latter part can now work with maximum precision.

The function is_leap can be used to test whether a given Gregorian calendar year is a leap year or not.

Zero point of Modified Julian Date (MJD) and the MJD of 2000/1/1 12:00:00 are defined as MJD_0 and MJD_J2000 respectively.

This module is based on the TPM C library, by Jeffery W. Percival.


Gregorian calendar to Julian dates

Function gcal2jd returns the Julian date for midnight of the given Calendar date.

>>> gcal2jd(2000,1,1)
(2400000.5, 51544.0)

>>> 2400000.5 + 51544.0 + 0.5

Negative months and days are valid. For example, 2000/-2/-4 => 1999/+12-2/-4 => 1999/10/-4 => 1999/9/30-4 => 1999/9/26.

>>> gcal2jd(2000, -2, -4)
(2400000.5, 51447.0)
>>> gcal2jd(1999, 9, 26)
(2400000.5, 51447.0)

>>> gcal2jd(2000, 2, -1)
(2400000.5, 51573.0)
>>> gcal2jd(2000, 1, 30)
(2400000.5, 51573.0)

>>> gcal2jd(2000, 3, -1)
(2400000.5, 51602.0)
>>> gcal2jd(2000, 2, 28)
(2400000.5, 51602.0)

Month 0 becomes previous month.

>>> gcal2jd(2000, 0, 1)
(2400000.5, 51513.0)
>>> gcal2jd(1999, 12, 1)
(2400000.5, 51513.0)

Day number 0 becomes last day of previous month.

>>> gcal2jd(2000, 3, 0)
(2400000.5, 51603.0)
>>> gcal2jd(2000, 2, 29)
(2400000.5, 51603.0)

If day is greater than the number of days in month, then it gets carried over to the next month.

>>> gcal2jd(2000,2,30)
(2400000.5, 51604.0)

>>> gcal2jd(2000,3,1)
(2400000.5, 51604.0)

>>> gcal2jd(2001,2,30)
(2400000.5, 51970.0)

>>> gcal2jd(2001,3,2)
(2400000.5, 51970.0)

Function jd2gcal converts Julian dates to Gregorian calendar dates. The values returned are year, month, day and fraction of the day.

>>> jd2gcal(*gcal2jd(2000,1,1))
(2000, 1, 1, 0.0)

>>> jd2gcal(*gcal2jd(1950,1,1))
(1950, 1, 1, 0.0)

>>> gcal2jd(2000,1,1)
(2400000.5, 51544.0)
>>> jd2gcal(2400000.5, 51544.0)
(2000, 1, 1, 0.0)

>>> jd2gcal(2400000.5, 51544.5)
(2000, 1, 1, 0.5)
>>> jd2gcal(2400000.5, 51544.245)
(2000, 1, 1, 0.24500000000261934)

>>> jd2gcal(2400000.5, 51544.1)
(2000, 1, 1, 0.099999999998544808)
>>> jd2gcal(2400000.5, 51544.75)
(2000, 1, 1, 0.75)

Out of range months and days are carried over to the next/previous year or next/previous month.

>>> jd2gcal(*gcal2jd(1999,10,12))
(1999, 10, 12, 0.0)

>>> jd2gcal(*gcal2jd(2000,2,30))
(2000, 3, 1, 0.0)

>>> jd2gcal(*gcal2jd(-1999,10,12))
(-1999, 10, 12, 0.0)

>>> jd2gcal(*gcal2jd(2000, -2, -4))
(1999, 9, 26, 0.0)

Julian calendar to Julian dates

Julian calendar dates can be converted to Julian dates using jcal2jd. Unlike gcal2jd, negative months and days can result in incorrect Julian dates.

>>> jcal2jd(2000, 1, 1)
 (2400000.5, 51557.0)

The function jd2jcal converts Julian dates to Julian calendar dates.

>>> jd2jcal(*jcal2jd(2000, 1, 1))
(2000, 1, 1, 0.0)

>>> jd2jcal(*jcal2jd(-4000, 10, 11))
(-4000, 10, 11, 0.0)

>>> jcal2jd(2000, 1, 1)
(2400000.5, 51557.0)
>>> jd2jcal(2400000.5, 51557.0)
(2000, 1, 1, 0.0)

>>> jd2jcal(2400000.5, 51557.5)
(2000, 1, 1, 0.5)
>>> jd2jcal(2400000.5, 51557.245)
(2000, 1, 1, 0.24500000000261934)

>>> jd2jcal(2400000.5, 51557.1)
(2000, 1, 1, 0.099999999998544808)
>>> jd2jcal(2400000.5, 51557.75)
(2000, 1, 1, 0.75)

Leap years

Use the function is_leap to determine whether a Gregorian calendar year is a leap year or not.

>>> is_leap(2000)
>>> is_leap(2100)



The zero point of Modified Julian Date (MJD) is stored in MJD_0. The MJD for 2000/1/1 12:00:00 is stored in MJD_J2000.

>>> print MJD_0

>>> print MJD_JD2000
This entry was posted in Astronomy, Python and tagged , , , . Bookmark the permalink.

8 Responses to Julian dates for proleptic Gregorian and Julian calendars

  1. keflavich says:

    Could you include a decimal-gregorian-year to JD function? There are some astro catalogs that report dates as, e.g., 2007.2392876. I’m not sure how to convert that to y/m/d using jdcal, though I’ll figure it out using python’s date.

    • Prasanth says:

      [Github Gist: https://gist.github.com/2594168. Code is not showing up properly in the comment.]

      The following functions should do the job. With your example date:

      In [75]: gyear2gcal(2007.2392876)
      Out[75]: (2007, 3, 28, 0.33997399996587774)
      In [76]: gcal2gyear(2007,3,28) + (_[-1]/ days_in_year(2007))
      Out[76]: 2007.2392876

      But I haven’t tested it extensively. It would be great if you could compare this with any other method you use. Until I test this I don’t want to include it in jdcal.

      Especially, I am confused about there being day 0 while calculating decimal year. For example, should 2001/01/01 00:00:00 be day 1.0 or day 0.0. Following TPM C code, the code below assumes that it is 1.0. So 2000.0 is not 2000/01/01 00:00:00.

      In [71]: gcal2gyear(2000,1,1)
      Out[71]: 2000.0027322404371
      In [72]: gyear2gcal(_)
      Out[72]: (1999, 12, 31, 0.9999999999854481)

      In [73]: gcal2gyear(2000,1,0)
      Out[73]: 2000.0
      In [74]: gyear2gcal(_)
      Out[74]: (1999, 12, 31, 0.0)

      The result above is 2000/01/01 00:00:00 to within ~1^-6 seconds. Another issue to consider before releasing to public!

      In addition I am not sure if I want to include them in jdcal at all, since I am trying to keep it just for Julian date calendar date conversion. I was working on a calendar module that can do all types of conversions, but got distracted by other things. The code below is salvaged from that work.

      import jdcal
      from jdcal import ipart, fpart, is_leap, gcal2jd, jd2gcal

      def days_in_year(year, c=”g”):
      “””Number of days in a calendar year (Gregorian/Julian).”””
      if c.lower() == “g”:
      return 366 if is_leap(year) else 365
      elif c.lower() == “j”:
      return 365
      raise ValueError(“Unknow calendar type %s .” % c)

      def gyear2jd(year):
      y = ipart(year)
      d = fpart(year) * days_in_year(y)
      day = ipart(d)
      jd1, jd2 = gcal2jd(y, 1, day)
      jd2 += fpart(d)
      return jd1, jd2

      def gyear2gcal(year):
      jd1, jd2 = gyear2jd(year)
      return jd2gcal(jd1, jd2)

      def jd2gyear(jd1, jd2):
      y, m, d, f = jd2gcal(jd1, jd2)
      jdt_1, jdt_2 = gcal2jd(y, 1, 0)
      day_diff = (jd1 – jdt_1) + (jd2 – jdt_2)
      return y + (day_diff / days_in_year(y))

      def gcal2gyear(y, m, d):
      jd1, jd2 = gcal2jd(y, m, d)
      return jd2gyear(jd1, jd2)

      Hope this helps, and let me know if there are any problems.

      • keflavich says:

        That’s pretty good. I tried the following, which is offset from your solution by 1 day:

        import jdcal
        import datetime
        import calendar

        def decimal_year_to_JD(yr):
        Convert a decimal year (e.g. 2007.4523) to a Julian date
        days_in_year = calendar.isleap(int(yr))
        date = datetime.timedelta(days=jdcal.fpart(yr)*days_in_year) + datetime.datetime(year=jdcal.ipart(yr),month=1,day=1)

        jd,mjd = jdcal.gcal2jd(date.year,date.month,date.day)
        mjd += (date.hour + date.minute/60. + date.second/3600.)/24.

        return mjd

        and I’m not entirely clear why; I agree with you that 2000.0 = (2000,1,1) [1999,12,31,0.999999 is close enough]. Problem is, I get this:
        In [170]: gyear2gcal(2000)
        Out[170]: (1999, 12, 31, 0.0)

  2. Prasanth says:


    >> I agree with you that 2000.0 = (2000,1,1) [1999,12,31,0.999999 is close enough]. Problem is, I >> get this:
    >> In [170]: gyear2gcal(2000)
    >> Out[170]: (1999, 12, 31, 0.0)

    No, according to the algorithm I have, year 2000.0 == date (1999,12,31, 00:00:00), and year 2000.0027322404371 == date (2000,1,1, 00:00:00), i.e., 2000.0 + (1/366.0) == (2000,1,1, 00:00:00). So in the convention I use the year starts at 2000.00273… .

    I think this explains the fact that the output from your code is +1 day from mine.

    What I meant was that I do not know if all astronomy code use the convention that I have adopted. I am using the convention in TPM C library; which as far as I can gather is used at some observatories, and so has most likely been well tested. This is why I was wondering if you have experience with some other code!

    One way to test this would be see if you can identify the date used by the author for the original example you mentioned.

    Since I had to stop working on the calendar module I was working on, I haven’t had an opportunity to properly explore all conventions. We could ask the astropy mailing list and/or the GalaxyZoo forum.

  3. keflavich says:

    Ah, alright, knowing the convention is important. Unfortunately, it’s not entirely clear in the example I’m looking at: http://surveys.roe.ac.uk/wsa/www/WSA_TABLE_gpsSourceSchema.html#gpsSource (“epoch”).

    • Prasanth says:

      Oh, this could be a very different thing: the Julian Epoch of the observation date, and not the calendar date as a decimal year!!! Now you can see why there can be a lot of confusion regarding dates!

      The only way to be absolutely sure is to ask them directly. If not, I think it would safe to assume that this is the Julian epoch, and not calendar date as decimal year. There are two epochs: Julian and Besselian. Modern, i.e., on and after 1980.0, values are always supposed to be Julian epochs.

      I have updates the Gist with two functions, and a constant, that can calculate the Julian Epoch of a given JD, and vice-versa.

      In [3]: jepoch2jd(2007.23467)
      Out[3]: (2400000.5, 54186.963217500015)
      In [4]: jd2jepoch(*_)
      Out[4]: 2007.23467

      Comparison with SLALIB:

      In [9]: slalib.sla_epj2d(2007.23467)
      Out[9]: 54186.963217500015
      In [10]: slalib.sla_epj(_)
      Out[10]: 2007.23467

  4. Robert says:

    It is not clear how the is_leap function is determining leap years for years prior to 1582 when using the proleptic Gregorian calendar. Is it using the 4:400:100 year rule, or is it using only the 4 year rule, or something else? Should we expect that the number of days per year will always be the same for Julian as they are for proleptic Gregorian?

    • Prasanth says:


      A proleptic calendar uses its rule even for time periods where that rule is historically invalid. So a proleptic Gregorian calendar
      uses the Gregorian leap year rule (“4:400:100”) even for periods before 1582. And a proleptic Julian uses the Julian calendar
      rules even for years after 1582.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s