11

I need Objective C method for converting Gregorian date to Julian days same as this PHP method (GregorianToJD).

atiqkhaled
  • 386
  • 4
  • 19
wod
  • 812
  • 1
  • 10
  • 23
  • 2
    Use a NSDateFormatter with a dateFormat of "g". – Hot Licks Apr 03 '14 at 12:10
  • @HotLicks: You are right! I had never noticed the "g" format. – Martin R Apr 03 '14 at 12:25
  • @MartinR - Whenever you want to do anything weird with dates you should always scan through the [date format standard](http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Format_Patterns). – Hot Licks Apr 03 '14 at 12:29
  • @HotLicks: Thanks for reminding me :-) (But this way I learned something about the Julian day.) – Martin R Apr 03 '14 at 12:38
  • The "g" specifier is not exactly a Julian day. From the [docs](http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Format_Patterns), "Modified Julian day. This is different from the conventional Julian day number in two regards. First, it demarcates days at local zone midnight, rather than noon GMT. Second, it is a local number; that is, it depends on the local time zone. It can be thought of as a single number that encompasses all the date-related fields." – Bill Nov 17 '17 at 14:54

3 Answers3

13

Precision: Incorporating time of day in Julian Date conversions

These Julian date conversion methods yield results identical to the U.S. Naval Observatory Online Julian Date Converter, which is more precise than NSDateFormatter's Julian Date conversion. Specifically, the functions below incorporate time-of-day (e.g. hour, minute and seconds), whereas NSDateFormatter rounds to noon GMT.

Swift examples:

func jdFromDate(date : NSDate) -> Double {
    let JD_JAN_1_1970_0000GMT = 2440587.5
    return JD_JAN_1_1970_0000GMT + date.timeIntervalSince1970 / 86400
}

func dateFromJd(jd : Double) -> NSDate {
    let JD_JAN_1_1970_0000GMT = 2440587.5
    return  NSDate(timeIntervalSince1970: (jd - JD_JAN_1_1970_0000GMT) * 86400)
}

Objective-C examples:

double jdFromDate(NSDate *date) {
   double JD_JAN_1_1970_0000GMT = 2440587.5;
   return JD_JAN_1_1970_0000GMT + date.timeIntervalSince1970 / 86400;
}

NSDate dataFromJd(double jd) {
   double JD_JAN_1_1970_0000GMT = 2440587.5;
   return [[NSDate alloc] initWithTimeIntervalSince1970: (jd - JD_JAN_1_1970_0000GMT) * 86400)];        
}

Note: Research confirms that the accepted answer rounds the date to a 24-hour interval because it uses the g format-specifier of NSDateFormatter, which returns the Modified Julian Day, according to the UNICODE standard's Date Format Patterns that Apple's date formatting APIs adhere to (according to the Date Formatting Guide).

clearlight
  • 12,255
  • 11
  • 57
  • 75
  • 1
    In your Objective-C example, that should be `NSDate *date`, not `NSDate date` – Victor Engel Nov 11 '17 at 15:56
  • 2
    I did not do that because last time I tried a change of just one character, I got an edit that said I had to change more than that, an absurd edit, if you ask me. – Victor Engel Nov 11 '17 at 16:17
8

According to http://en.wikipedia.org/wiki/Julian_day, the Julian day number for January 1, 2000, was 2,451,545. So you can compute the number of days between your date and this reference date. For example (Jan 1, 2014):

NSUInteger julianDayFor01012000 = 2451545;
NSCalendar *cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
[cal setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
NSDateComponents *comp = [[NSDateComponents alloc] init];
comp.year = 2014;
comp.month = 1;
comp.day = 1;
NSDate *date = [cal dateFromComponents:comp];
comp.year = 2000;
comp.month = 1;
comp.day = 1;
NSDate *ref = [cal dateFromComponents:comp];

NSDateComponents *diff = [cal components:NSDayCalendarUnit fromDate:ref toDate:date options:0];
NSInteger julianDays = diff.day + julianDayFor01012000;
NSLog(@"%ld", (long)julianDays);
// Output: 2456659

This gives the same result as http://www.php.net/manual/en/function.gregoriantojd.php:

<?php
$jd = GregorianToJD(1, 1, 2014);
echo "$jd\n";
?>

Inverse direction (Julian days to Gregorian year/month/day):

NSInteger julianDays = 2456659; // From above example
NSUInteger julianDayFor01012000 = 2451545;
NSCalendar *cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
[cal setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
NSDateComponents *comp = [[NSDateComponents alloc] init];
comp.year = 2000;
comp.month = 1;
comp.day = 1;
NSDate *ref = [cal dateFromComponents:comp];
NSDateComponents *diff = [[NSDateComponents alloc] init];
diff.day = julianDays - julianDayFor01012000;

NSDate *date = [cal dateByAddingComponents:diff toDate:ref options:0];
comp = [cal components:NSDayCalendarUnit|NSMonthCalendarUnit|NSYearCalendarUnit fromDate:date];
NSLog(@"%04ld-%02ld-%02ld", (long)comp.year, (long)comp.month, (long)comp.day);
// Output: 2014-01-01

UPDATE: As Hot Licks correctly stated in a comment, it is easier to use a date formatter with the "g" format:

NSCalendar *cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *comp = [[NSDateComponents alloc] init];
comp.year = 2014;
comp.month = 1;
comp.day = 1;
NSDate *date = [cal dateFromComponents:comp];
NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
[fmt setDateFormat:@"g"];
NSInteger julianDays = [[fmt stringFromDate:date] integerValue];
NSLog(@"%ld", (long)julianDays);
// Output: 2456659

And for the inverse direction:

NSInteger julianDays = 2456659;
NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
[fmt setDateFormat:@"g"];
NSDate *date = [fmt dateFromString:[@(julianDays) stringValue]];
NSCalendar *cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *comp = [cal components:NSDayCalendarUnit|NSMonthCalendarUnit|NSYearCalendarUnit fromDate:date];
NSLog(@"%04ld-%02ld-%02ld", (long)comp.year, (long)comp.month, (long)comp.day);
// Output: 2014-01-01
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Thanks , also i need the inverse like this method ( JDToGregorian) – wod Apr 03 '14 at 11:28
  • There is an ambiguity in this answer and this solution should be used with caution. @nerdist colony's answer is better. Why? Because the Julian date value given at the start of this post (2,451,545) is the value for Jan 1 2000 at 12:00 GMT (noon). Times BEFORE 12:00 on that date range from 2,451,544.5 - 2,451,545.0 and times AFTER 12:00 on that date range from 2,451,545.0 to 2,451,545.5. One can depend on rounding to help but be clear about what you are doing. – Cliff Ribaudo Mar 25 '16 at 09:50
  • The "g" specifier is not exactly a Julian day. From the [docs](http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Format_Patterns), "Modified Julian day. This is different from the conventional Julian day number in two regards. First, it demarcates days at local zone midnight, rather than noon GMT. Second, it is a local number; that is, it depends on the local time zone. It can be thought of as a single number that encompasses all the date-related fields." – Bill Nov 17 '17 at 14:54
  • @wod: It seems that the other answer https://stackoverflow.com/a/27709317/1187415 is better (and simpler), you might consider to accept that one instead of mine. – Martin R Nov 17 '17 at 15:13
  • @Bill: Thanks for the feedback. I have suggested to OP to accept the other answer instead (which is simpler *and correct* if I understand it correctly). I will then delete this one. – Martin R Nov 17 '17 at 15:19
0
let date = Date() // now
let cal = Calendar.current
var day = 0
day = cal.ordinality(of: .day, in: .year, for: date) ?? 0
  • 2
    Please don't post code-only answers. The main audience, future readers, will be grateful to see explained *why* this answers the question instead of having to infer it from the code. Also, since this is an old, well answered question, please explain how it complements the other answers. – Gert Arnold Dec 17 '22 at 21:37