Date since 1600 to NSDate? - cocoa-touch

I have a date that's stored as a number of days since January 1, 1600 that I need to deal with. This is a legacy date format that I need to read many, many times in my application.
Previously, I'd been creating a calendar, empty date components and root date like this:
self.gregorian = [[[NSCalendar alloc] initWithCalendarIdentifier: NSGregorianCalendar
] autorelease];
id rootComponents = [[[NSDateComponents alloc] init] autorelease];
[rootComponents setYear: 1600];
[rootComponents setMonth: 1];
[rootComponents setDay: 1];
self.rootDate = [gregorian dateFromComponents: rootComponents];
self.offset = [[[NSDateComponents alloc] init] autorelease];
Then, to convert the integer later to a date, I use this:
[offset setDay: theLegacyDate];
id eventDate = [gregorian dateByAddingComponents: offset
toDate: rootDate
options: 0];
(I never change any values in offset anywhere else.)
The problem is I'm getting a different time for rootDate on iOS vs. Mac OS X. On Mac OS X, I'm getting midnight. On iOS, I'm getting 8:12:28. (So far, it seems to be consistent about this.) When I add my number of days later, the weird time stays.
OS | legacyDate | rootDate | eventDate
======== | ========== | ==========================|==========================
Mac OS X | 143671 | 1600-01-01 00:00:00 -0800 | 1993-05-11 00:00:00 -0700
iOS | 143671 | 1600-01-01 08:12:28 +0000 | 1993-05-11 07:12:28 +0000
In the previous release of my product, I didn't care about the time; now I do. Why the weird time on iOS, and what should I do about it? (I'm assuming the hour difference is DST.)
I've tried setting the hour, minute and second of rootComponents to 0. This has no impact. If I set them to something other than 0, it adds them to 8:12:28. I've been wondering if this has something to do with leap seconds or other cumulative clock changes.
Or is this entirely the wrong approach to use on iOS?

I imagine you're right about the leap seconds/cumulative clock changes accounting for the time issue. Are the dates you're dealing with actually in the past, or is it purely an arbitrary epoch?
In either case, you could try defining a new epoch that's much closer to present day (say, the Cocoa epoch). Calculate a day delta between the new epoch and the old and save it as a constant. When you need to process a date, apply this delta to the date and then use your existing NSCalendar technique, but with your new epoch instead of the old. That will hopefully avoid the clock drift issue you're seeing.

It looks like the right answer is to make things simpler. Instead of making a rootDate, I just build the date from components every time. This should be no slower, and is still keeps the code really close to the idea.
Initial setup:
self.gregorian = [[[NSCalendar alloc] initWithCalendarIdentifier: NSGregorianCalendar
] autorelease];
self.components = [[[NSDateComponents alloc] init] autorelease];
[components setYear: 1600];
[components setMonth: 1];
(Obviously, properties and ivars are adjusted.)
Later, to actually convert a legacy date to a NSDate:
[components setDay: 1 + theLegacyDate];
id eventDate = [gregorian dateFromComponents: components];
This has these advantages for me:
It users fewer ivars.
It's less code.
It always returns midnight on that day, regardless of whether DST is in effect.

Note that iOS takes into account very obscure rules for various time zones. It is most likely that midnight, Jan 1st. 1600 in your timezone actually was at 7:12:28 UTC. There have been many cases where people complained about bugs in date conversions and then someone figured out that actually they are in a time zone that made some strange calendar change many years ago.
You need to find out first what exact NSDate your data represents. "Number of days since Jan 1st 1600" is nonsense, because you need a time zone! What you should do: Find a "legacy" number where you know what day it is supposed to represent. For example, if you "know" that 143671 is supposed to be 11th May 1993 in your time zone, then start with that date as the root date and add (x - 143671) days to it.

Related

objective-c create and compare time - running from different countries

I have an app that is used in different countries. In my app I am performing a simple calculation that states if the current time is later than 3pm, do something.
This is how I am creating the 3pm.
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDateComponents *dateAttempt = [[NSDateComponents alloc] init];
[dateAttempt setYear:2016];
[dateAttempt setMonth:05];
[dateAttempt setDay:18];
[dateAttempt setHour:15];
[dateAttempt setMinute:00];
NSDate *threePm = [calendar dateFromComponents:dateAttempt];
NSLog(#"%#", dateAttempt);
NSLog(#"%#", threePm);
I am creating the app in NewYork. When I run the above code I get...
2016-05-18 11:17:53.815 x[1312:37272] 2016-05-18 19:00:00 +0000
I guess that makes sense because NewYork is 4 hours behind UTC. (it's 11am at the time of me writing this)
So the threePm is giving me the UTC equivalent of 3pm in NY, and its working as expected. The problem is when I change the time on the laptop simulator to simulate being in another country. For example Greece.
If I switch the time on the pc to greece, the same code above gives a different result due to a different time adjustment to UTC.
How can I say... no matter where in the world the app is running, if time is later than 3pm (EST)... do something?
NSDate stores absolute time - actually the time offset from a fixed point in UTC time.
NSDateComponents has a timeZone property which is used as the basis for the conversion to NSDate, and this will default to the current timezone - as your results show.
If you always want 3pm in New York set this property. New York is one of the standard time zones: America/New_York; so you can set this property using:
dateAttempt.timeZone = [NSTimeZone timeZoneWithName:#"America/New_York"];
An NSTimeZone understands daylight savings and dependent on the date you set the conversion will translate from EST or EDT as appropriate.
HTH

Am I using NSCalendarUnitWeekOfMonth and NSCalendarUnitWeekOfYear improperly?

everyone. I have a quick question regarding the use of NSCalendar and the constant NSCalendarUnitWeekOfMonth (and/or NSCalendarUnitWeekOfYear). I want to get a loop-able range of weeks in a certain month and year. I thought doing so would be quite straight-forward. Of course, however, it hasn't been. What am I doing wrong? Here's what I've got:
- (NSRange)rangeOfWeeksInMonth:(NSUInteger)month year:(NSUInteger)year
{
NSDateComponents *comps = [NSDateComponents new];
comps.month = month;
comps.year = year;
NSDate *date = [self.calendar dateFromComponents:comps];
return [self.calendar rangeOfUnit:NSCalendarUnitWeekOfMonth // or ... WeekOfYear
inUnit:NSCalendarUnitMonth
forDate:date];
}
I figured that if I input month = 4 and year = 2014, the above method would return something like:
=> NSRange: {14, 5}
In other words, 14 would be the week number in the year and 5 would be the number of weeks in the month.
When using the NSWeekCalendarUnit constant, the above example output is exactly what I get. However, after checking out the constants in NSCalendar.h, NSWeekCalendarUnit has been deprecated as of OS X 10.9. Apple's docs don't mention it anywhere, but I generally take cues from the actual header files when it comes to working with stuff I'm not sure about.
Anyway, because I don't want to get behind in the times, I tried both NSCalendarUnitWeekOfMonth and NSCalendarUnitWeekOfYear, and the returned range for both is {NSNotFound, NSNotFound}. What's the deal? Anyone have a clue as to why Apple doesn't write documentation for this stuff?

Why does NSDateFormatter return nil date for these 4 time zones?

Try running this in iOS6 (haven't tested pre iOS6):
NSDateFormatter *julianDayDateFormatter = nil;
julianDayDateFormatter = [[NSDateFormatter alloc] init];
[julianDayDateFormatter setDateFormat:#"g"];
for (NSString *timeZone in [NSTimeZone knownTimeZoneNames]) {
julianDayDateFormatter.timeZone = [NSTimeZone timeZoneWithName: timeZone];
NSDate *date = [julianDayDateFormatter dateFromString:[NSString stringWithFormat:#"%d", 2475213]];
if (date == nil)
NSLog(#"timeZone = %#", timeZone);
}
and you get the following output:
America/Bahia
America/Campo_Grande
America/Cuiaba
America/Sao_Paulo
Can anyone explain why these four time zones behave like this with NSDateFormatter set to julian day numbers? All other time zones makes NSDateFormatter return actual NSDates.
I have a suspicion. Only a suspicion, but a pretty strong one.
That value represents October 19th 2064. The Brazilian time zones observe daylight saving time starting at local midnight - that's when their clocks go forward, so midnight itself doesn't exist. October 19th is one of those transitions.
Here's some sample code using Noda Time, my .NET date/time API. It checks whether the start of the day in every time zone it knows about is actually midnight:
using System;
using NodaTime;
class Test
{
static void Main()
{
var localDate = new LocalDate(2064, 10, 19);
var provider = DateTimeZoneProviders.Tzdb;
foreach (var id in provider.Ids)
{
var zone = provider[id];
var startOfDay = zone.AtStartOfDay(localDate).LocalDateTime.TimeOfDay;
if (startOfDay != LocalTime.Midnight)
{
Console.WriteLine(id);
}
}
}
}
That produces a very similar list:
America/Bahia
America/Campo_Grande
America/Cuiaba
America/Sao_Paulo
Brazil/East
I suspect Brazil/East may be an alias for America/Sao_Paolo, which is why it's not on your list.
Anyway, to get back to your Julian day issue - I suspect the formatter always wants to return an NSDate * which is at the local midnight. That doesn't exist for October 19th 2064 in those time zones... hence it returns nil. Personally I'd suggest it should return the 1am value instead, but hey...
Credits to Jon Skeet for putting me on the right track. However, I just want to clarify his answer in an iOS context.
When you ask NSDateFormatter to convert a julian day number into an NSDate, you can only specify whole numbers (usually you can specify a decimal part for the hours/minutes/secs of the day) in the string to be parsed.
Because Apple demarcates julian days at midnight (as opposed to noon in astronomy, read more here: http://www.unicode.org/reports/tr35/#Date_Field_Symbol_Table) and some midnights simply doesn't exists (thanks for pointing that out #JonSkeet) NSDateFormatter identifies that that particular point in time doesn't exist in that time zone and returns nil.
For the record, iOS5 does not behave like this and I agree with Jon Skeet, that NSDateFormatter should return an NSDate adjusted for DST instead of nil, as that particular julian day in fact exists! I filed a bug with Apple.

NSDateFormatter dateFromString returns incorrect date [duplicate]

This question already has answers here:
Get NSDate from NSDate adjusted with timezone
(2 answers)
Closed 9 years ago.
I am trying to use NSDateFormatter in my app which takes a date string and formats it to an NSDate so that I can do Date Comparisons, however I am finding when I use dateFromString and format it the date is losing one day.
NSString *dateString = #"02-06-2012";
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"dd-MM-yyyy"];
NSDate *dateFromString = [[NSDate alloc] init];
dateFromString = [dateFormatter dateFromString:dateString];
NSLog(#"My Date = %#", dateFromString);
[dateFormatter release];
This outputs to the console:
My Date = 2012-06-01 23:00:00 +0000
Try adding this lines to your code,
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"GMT+0:00"]];
or
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:#"GMT"]];
SWIFT update :
Code from quetion,
let dateString = "02-06-2012"
var dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "dd-MM-yyyy"
var dateFromString : NSDate = dateFormatter.dateFromString(dateString)!
println("My Date \(dateFromString)")
And Solution ,
dateFormatter.timeZone = NSTimeZone(name: "GMT")
OR
dateFormatter.timeZone = NSTimeZone(abbreviation: "GMT+0:00")
I don't believe that Dhruv's answer is correct. In fact, it's not clear there's any problem at all. You just seem to have an incorrect expectation of what should happen and/or interpretation of what's happening.
NSDate represents a moment in time. This moment does not have one unique name. It will be known by different names in different places and under different naming systems (time zones, calendars). NSDate doesn't deal with any of this, except lamely in its -description method, where it has to produce a string representation of that moment.
Second, a string like "02-06-2012" doesn't specify a precise moment in time. First of all, it's just a date with no time information, so NSDateFormatter just defaults to the first moment for that date. Second, it doesn't specify the time zone. The first moment of the calendar day is a different moment in each time zone. Unless you specify a time zone with -setTimeZone: or the string itself carries time zone information, NSDateFormatter assumes that any date strings you ask it to parse are in the current time zone.
So, your dateFromString object represents the first moment of the specified date, 02-06-2012, in your time zone. I expect this is what you wanted. However, you then got confused by the way that NSDate describes itself when logged. As I said, NSDate has to pick some "name" (string representation) for the moment it represents and which name it picks is fairly arbitrary. These days it is picking the name that the moment is known by in UTC. I gather from the log output shown in your question that you are located at UTC+0100. So, the date may look like it's one day earlier but it really is the same moment you specified. In other words, "2012-06-01 23:00:00 +0000" and "2012-06-02 00:00:00 +0100" are two equivalent names for exactly the same moment in time. You just aren't used to seeing the first one and misinterpreted it.
The lesson is that you have to stop relying on NSDate's self-description to be in any particular time zone. Really, you have to not rely on anything about it, since it's not documented. In fact, the docs for -[NSDate description] state, "The representation is not guaranteed to remain constant across different releases of the operating system."
Dhruv's solution seems to help merely because it causes NSDateFormatter and -[NSDate description] to agree on the time zone. But that's unreliable. It wouldn't work on Snow Leopard, for example, because -[NSDate description] used the local time zone instead of UTC in that version of the frameworks.
More importantly, though, it alters the actual moment represented by the NSDate object you get from NSDateFormatter's interpretation of your date string. I suspect you really want that to have a specific meaning – you want the string to be interpreted as being in the local time zone – and his solution thwarts your intent.
tl;dr: you were getting the date you wanted all along; don't rely on -[NSDate description]; don't use Dhruv's solution

In Objective-C, to get the current hour and minute as integers, we need to use NSDateComponents and NSCalendar?

I'd like to get the current hour and minute as integers. So if right now is 3:16am, I'd like to get the two integers: 3 and 16.
But it looks like [NSDate date] will give the number of seconds since 1970, or it can give a string of the current time representation, but there is no easy way to get them as integers?
I see a post in Getting current time, but it involved NSDateComponents and NSCalendar? That's way too complicated... all that was need is something like
NSDate *date = [NSDate date];
int hour = [date getHour]; // which is not possible
Is there a simpler way than using 3 classes NSDate, NSDateComponents, and NSCalendar to get the current hour as an integer, or typically, in Objective-C, would we typically still use C language's localtime and tm to get the hour as an integer?
How you interpret the seconds since 1970 depends on the calendar that you are using. There is simply no other option. Fortunately it is not that difficult to set up. See the 'Data and Time Programming Guide' for lots of examples. In your case:
// Assume you have a 'date'
NSCalendar *gregorianCal = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *dateComps = [gregorianCal components: (NSHourCalendarUnit | NSMinuteCalendarUnit)
fromDate: date];
// Then use it
[dateComps minute];
[dateComps hour];
So it really isn't that complicated.
Also note that you could create a 'Class Category' to encapsulate this as:
#interface NSDate (MyGregorianDateComponents)
- (NSInteger) getGregorianHour;
- (NSInteger) getGregorianMinute;
#end
NSDate just holds the time that has passed since a certain reference date, to get more meaningful numbers out of this (eg. after taking care of DST, leap years and all the other stupid time stuff), you have to use NSDateComponents with the appropriate NSCalendar.
My class can help.
https://github.com/TjeerdVurig/Vurig-Calendar/blob/master/Vurig%20Calendar/NSDate%2Bconvenience.m
I'm sure you can figure out the minute part :)