I need Objective C method for converting Gregorian date to Julian days same as this PHP method (GregorianToJD).
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).
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
let date = Date() // now
let cal = Calendar.current
var day = 0
day = cal.ordinality(of: .day, in: .year, for: date) ?? 0
Related
I'm writing a GUI for a legacy data object where a date is saved in the integer properties day of the year and year (real "genius" design...)
How can I convert this data into day and month given the year?
I'm currently trying:
int theYear = 2016;
int theDayOfTheYear = 222;
NSCalendar* gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setYear:theYear];
[components setDay:theDayOfTheYear];
NSDate* theDate = [gregorian dateFromComponents:components];
This seems a bit too hacky to me. It could be just coincidence that it works with days >31 - haven't found any mention of this in the NSDateComponents class reference.
When I debug, po theDate gives me 2016-08-08 22:00:00 +0000
--> close, but it should be 2016-08-09 (!)
To perform the reverse operation, i.e. to convert a normal DD-MM-YYYY date into day of the year, I'm using this code:
NSDate *fullDate = [NSDate date];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSUInteger dayOfYear = [gregorian ordinalityOfUnit:NSDayCalendarUnit
inUnit:NSYearCalendarUnit
forDate:fullDate];
But I can't seem to find the proper methods to do the opposite...
Sometimes a test is all takes :)
Don't forget that NSDate are GMT, so you might want to add [[NSTimeZone localTimeZone] secondsFromGMT] seconds to it.
It seems that you have it working :)
- (void) testDate
{
int startYear = 2016;
int startMonth = 1;
int startDay = 1;
// Create the startDate
NSDateComponents * startDateComp = [NSDateComponents new];
startDateComp.day = startDay;
startDateComp.month = startMonth;
startDateComp.year = startYear;
NSDate * startDate = [[NSCalendar currentCalendar] dateFromComponents:startDateComp];
for(int i=0 ; i < 365; i++)
{
// add i day to startDate
NSDate * testDate = [startDate dateByAddingTimeInterval:i * 24 * 60 * 60];
NSUInteger dayOfYear = [[NSCalendar currentCalendar] ordinalityOfUnit:NSDayCalendarUnit
inUnit:NSYearCalendarUnit
forDate:testDate];
XCTAssert(dayOfYear == (i + 1), #"the day of the year should be i + 1");
// Create a date using day of year
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setYear:startYear];
[components setDay:dayOfYear];
NSDate* resultDate = [[NSCalendar currentCalendar] dateFromComponents:components];
// testing result date against test date
NSInteger test, result;
test = [[NSCalendar currentCalendar] component:NSCalendarUnitDay fromDate:testDate];
result = [[NSCalendar currentCalendar] component:NSCalendarUnitDay fromDate:resultDate];
XCTAssert(test == result);
test = [[NSCalendar currentCalendar] component:NSCalendarUnitMonth fromDate:testDate];
result = [[NSCalendar currentCalendar] component:NSCalendarUnitMonth fromDate:resultDate];
XCTAssert(test == result);
test = [[NSCalendar currentCalendar] component:NSCalendarUnitYear fromDate:testDate];
result = [[NSCalendar currentCalendar] component:NSCalendarUnitYear fromDate:resultDate];
XCTAssert(test == result);
}
Please try this. Its the larger calendar unit NSCalendarUnitEra :)
NSDate *fullDate = [NSDate date];
NSCalendar *gregorian = [NSCalendar currentCalendar];
gregorian.timeZone = [NSTimeZone systemTimeZone];
NSUInteger dayOfYear = [gregorian ordinalityOfUnit:NSCalendarUnitDay
inUnit:NSCalendarUnitEra
forDate:fullDate];
NSDateComponents *dateCom = [[NSDateComponents alloc] init];
dateCom.day = dayOfYear;
NSDate *expectedDate = [gregorian dateFromComponents:dateCom];
NSLog(#"%#",expectedDate);
When passing dates over the wire from client to server and back again, I format the date to a string for JSON using the format #"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" with the #"en_US_POSIX" locale. The same formatter provides an instance of NSDate from the date strings returned from the server to the client.
To test the conversions, I am trying to use NSDateComponents and NSCalendar to generate an independent date to use to validate the date from the formatter. However, the NSDate instances created from the NSCalendar and NSDateComponents vary ever so slightly from the NSDate instances provided by the NSDateFormatter. I do not understand why. Different dates produce a different number of variances. I apologize for yet another NSDate/NSDateFormatter question, but hope you find it somewhat novel.
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US_POSIX"];
dateFormatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
dateFormatter.dateFormat = #"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
dateFormatter.calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSMutableArray *valuesWithError = [NSMutableArray arrayWithCapacity:750];
NSMutableArray *valuesThatMatch = [NSMutableArray arrayWithCapacity:250];
for (NSInteger milliseconds = 0; milliseconds < 1000; milliseconds++) {
NSString *dateString = [NSString stringWithFormat:#"2001-01-01T00:00:00.%03dZ", milliseconds];
NSLog(#"%#", dateString);
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
calendar.locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US_POSIX"];
calendar.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
NSDateComponents *dc = [[NSDateComponents alloc] init];
dc.year = 2001;
dc.month = 1;
dc.day = 1;
dc.hour = 0;
dc.minute = 0;
dc.second = 0;
NSDate *expectedDate = [calendar dateFromComponents:dc];
NSTimeInterval baseInterval = [expectedDate timeIntervalSinceReferenceDate];
double mulitplier = 0.001;
NSTimeInterval millisecondsToAdd = milliseconds * mulitplier;
baseInterval += millisecondsToAdd;
expectedDate = [NSDate dateWithTimeIntervalSinceReferenceDate:baseInterval];
NSDate *dateFromFormat = [dateFormatter dateFromString:dateString];
NSTimeInterval expectedDateFromReferenceDate = [expectedDate timeIntervalSinceReferenceDate];
NSTimeInterval dateFromFormatFromReferenceDate = [dateFromFormat timeIntervalSinceReferenceDate];
NSTimeInterval differenceByTimeIntervalSubtraction = ABS(expectedDateFromReferenceDate - dateFromFormatFromReferenceDate);
NSTimeInterval differenceByTimeIntervalFromDate = [expectedDate timeIntervalSinceDate:dateFromFormat];
if (![expectedDate isEqualToDate:dateFromFormat]) {
NSLog(#"Difference = %e", differenceByTimeIntervalSubtraction);
[valuesWithError addObject:#(milliseconds)];
} else {
[valuesThatMatch addObject:#(milliseconds)];
}
}
I want to compare two NSDates with NOW ([NSDate date]).
NSDate *date1 = [NSDate dateWithString:#"1982-02-12 07:00:00 +0100"];
NSDate *now = [NSDate dateWithString:#"2012-01-25 10:19:00 +0100"]; //example
NSDate *date2 = [NSDate dateWithString:#"1989-02-12 15:00:00 +0100"];
I would like to check if now is between date1 and date2. In the example above this is the case. The date component should be completely ignored, so only the time component should be compared. How could I accomplish this?
Thanks in advance!
unsigned int flags = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
NSCalendar* calendar = [NSCalendar currentCalendar];
NSDateComponents* components = [calendar components:flags fromDate:date1];
NSDate* timeOnly = [calendar dateFromComponents:components];
This will give you a date object where everything but the hours/minutes/seconds have been reset to some common value. Then you can use the standard NSDate compare functions on them.
For reference, here is the opposite question to yours: Comparing two NSDates and ignoring the time component
You can create a date representing the start of today and add the time as components to it to get the boundary dates.
NSDate *now = [NSDate date];
NSDate *startOfToday;
[[NSCalendar currentCalendar] rangeOfUnit:NSDayCalendarUnit startDate:&startOfToday interval:NULL forDate:now];
NSDateComponents *startComps = [[NSDateComponents alloc] init];
startComps.hour = 7;
startComps.minute = 30;
NSDateComponents *endComps = [[NSDateComponents alloc] init];
endComps.hour = 20;
NSDate *startDate = [[NSCalendar currentCalendar] dateByAddingComponents:startComps toDate:startOfToday options:0];
NSDate *endDate = [[NSCalendar currentCalendar] dateByAddingComponents:endComps toDate:startOfToday options:0];
if ([startDate timeIntervalSince1970] < [now timeIntervalSince1970] && [now timeIntervalSince1970] < [endDate timeIntervalSince1970]) {
NSLog(#"good");
}
NSDateFormatter* formatterDate = [[[NSDateFormatter alloc] init] autorelease];
formatterDate.dateStyle = NSDateFormatterMediumStyle; // whatever format you like
NSDate *first_Date = [formatterDate dateFromString:#""];
NSDate *second_Date = [formatterDate dateFromString:#""];
NSDate *todaysDate = [NSDate date];
NSTimeInterval timeIntFormFirstDate = [todaysDate timeIntervalSinceDate:First_Date];
NSTimeInterval timeIntFronLastDate = [second_Date timeIntervalSinceDate:todaysDate];
int interval1 = timeIntFormFirstDate/60;
int interval2 = timeIntFronLastDate/60;
if (interval1 >0 && interval2 >0)
{
NSLog(#"Today's date is between first and second date");
}
I want to set the NSDate time with my desired hours:minutes:seconds
currently im working with NSDate component but it is not giving the desired result
[comps setHour: -hours];
[comps setMinute:0];
[comps setSecond:0];
NSDate *minDate = [calendar_c dateFromComponents:comps];
This works great as an NSDate category.
/** Returns a new NSDate object with the time set to the indicated hour,
* minute, and second.
* #param hour The hour to use in the new date.
* #param minute The number of minutes to use in the new date.
* #param second The number of seconds to use in the new date.
*/
-(NSDate *) dateWithHour:(NSInteger)hour
minute:(NSInteger)minute
second:(NSInteger)second
{
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *components = [calendar components: NSYearCalendarUnit|
NSMonthCalendarUnit|
NSDayCalendarUnit
fromDate:self];
[components setHour:hour];
[components setMinute:minute];
[components setSecond:second];
NSDate *newDate = [calendar dateFromComponents:components];
return newDate;
}
With the above category, if you have an existing date you want to change the time on, you do so like this:
NSDate *newDate = [someDate dateWithHour:10 minute:30 second:00];
If, however, you are trying to add or subtract hours from an existing date, a category method to do that is also straightforward:
/** Returns a new date with the given number of hours added or subtracted.
* #param hours The number of hours to add or subtract from the date.
*/
-(NSDate*)dateByAddingHours:(NSInteger)hours
{
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setHour:hours];
return [[NSCalendar currentCalendar]
dateByAddingComponents:components toDate:self options:0];
}
Your approach should work fine. I needed a solution for this type problem (setting the individual date components) and the following code works as expected for me. My situation: I wanted to create a date object that used the current date but had the time set to a value that was passed in as a string.
NSString *string = #"7:00";
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US_POSIX"];
NSDateFormatter *timeOnlyFormatter = [[NSDateFormatter alloc] init];
[timeOnlyFormatter setLocale:locale];
[timeOnlyFormatter setDateFormat:#"h:mm"];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDate *today = [NSDate date];
NSDateComponents *todayComps = [calendar components:(NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit) fromDate:today];
NSDateComponents *comps = [calendar components:(NSHourCalendarUnit | NSMinuteCalendarUnit) fromDate:[timeOnlyFormatter dateFromString:string]];
comps.day = todayComps.day;
comps.month = todayComps.month;
comps.year = todayComps.year;
NSDate *date = [calendar dateFromComponents:comps];
[calendar release];
[timeOnlyFormatter release];
[locale release];
One thing to note is that you really have to pay attention to time zones when you are judging whether a time appears to be accurate. For example, in my app, when you stop at a breakpoint, you will see the time in GMT (so it looks different than the input time, which is in my local time), but when the time is actually displayed on screen in the app, it is being formatted to display in the local timezone. You may need to take this into consideration to determine whether the result is actually different from what you would expect.
If this does not help, can you elaborate on "not giving the desired result"? What result is it giving and how does that compare to what you expected?
is Swift2
extension NSDate {
func dateWithHour (hour: Int, minute:Int, second:Int) ->NSDate?{
let calendar = NSCalendar.currentCalendar(),
components = calendar.components([.Day,.Month,.Year], fromDate: self)
components.hour = hour;
components.minute = minute;
components.second = second;
return calendar.dateFromComponents(components)
}
}
You can set 0 to hour, min, and second.
NSDateFormatter *tFmt = [[NSDateFormatter alloc] init];
tFmt.dateFormat = #"yyyy-MM-dd";
NSString *strNowDate = [NSString stringWithFormat:#"%# 00:00:00",[tFmt stringFromDate:[NSDate date]]];
NSDate *nowDate = [NSDate dateWithString:strNowDate formatString:#"yyyy-MM-dd HH:mm:ss"];
Swift 5 solution (based on #dattk answer) for those who fear Deprecation warnings :)
func date(withHour hour: Int, withMinute minute: Int, withSeconds second: Int) -> Date? {
let now = Date()
let calendar = NSCalendar.current
var components = calendar.dateComponents([.day,.month,.year], from: now)
components.hour = hour
components.minute = minute
components.second = second
return calendar.date(from: components)
}
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDateComponents *comps = [calendar components:(NSCalendarUnitHour | NSCalendarUnitMinute) fromDate:[NSDate date]];
comps.hour = 0;
comps.minute = 15;
NSDate *date = [calendar dateFromComponents:comps];
iOS 8
This is my method I use for increasing by one day in my navigationBar, and setting the name of the day as a title. I know it's wrong because I set "today" variable every time its called. But I can't figure out how to increase +1 day every time I call this method.
-(void)stepToNextDay:(id)sender
{
today = [NSDate date];
NSDate *datePlusOneDay = [today dateByAddibgTimeInterval:(60 * 60 * 24)];
NSDateFormatter *dateformatterBehaviour = [[[NSDateFormatter alloc]init]autorelease];
[dateFormatter setDateFormat:#"EEEE"];
NSString *dateString = [dateFormatter stringFromDate:datePlusOneDay];
self.navigationItem.title = datestring;
}
Store the date your are showing in a property (ivar, ...) of your view controller. That way you can retrieve the current setting when you go to the next day.
If you want to reliably add dates, use NSCalendar and NSDateComponents to get a "1 day" unit, and add that to the current date.
NSCalendar* calendar = [[[NSCalendar alloc] initWithCalendarIdentifier: NSGregorianCalendar] autorelease];
NSDateComponents* components = [[[NSDateComponents alloc] init] autorelease];
components.day = 1;
NSDate* newDate = [calendar dateByAddingComponents: components toDate: self.date options: 0];
As from iOS 8 NSGregorianCalendar is depricated, the updated answer will be,
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDateComponents *components = [[NSDateComponents alloc] init];
components.day = 1;
NSDate *newDate = [calendar dateByAddingComponents:components toDate:today options:0];
Swift Code:
var today:NSDate = NSDate()
let calender:NSCalendar! = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)
var components:NSDateComponents = NSDateComponents()
components.setValue(1, forComponent: NSCalendarUnit.CalendarUnitDay)
var newDate:NSDate! = calender.dateByAddingComponents(components, toDate:today, options: NSCalendarOptions(0))