Is it NSCalendar bug? - ios

Why are the last two lines of the output the same?
Use NSCalendar to calculate the diff between startTime and endTime, find that the diff between #"2008-02-28 00:00:00" and #"2022-02-28 00:00:00" and the diff between #"2008-02-29 00:00:00" and #"2022-02-28 00:00:00" are the same. It looks like a bug of NSCalendar, maybe about leapMonth?
code:
- (void)viewDidLoad
{
[super viewDidLoad];
[self printDiffBetweenStartTime:#"2008-02-27 00:00:00" endTime:#"2022-02-28 00:00:00"];
[self printDiffBetweenStartTime:#"2008-02-28 00:00:00" endTime:#"2022-02-28 00:00:00"];
[self printDiffBetweenStartTime:#"2008-02-29 00:00:00" endTime:#"2022-02-28 00:00:00"];
}
- (void)printDiffBetweenStartTime:(NSString *)startTime endTime:(NSString *)endTime
{
static NSDateFormatter *dateFormatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = #"yyyy-MM-dd HH:mm:ss";
dateFormatter.calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian];
});
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDate *startDate = [dateFormatter dateFromString:startTime];
NSDate *endDate = [dateFormatter dateFromString:endTime];
NSCalendarUnit unitFlags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
NSDateComponents *components = [calendar components:unitFlags fromDate:startDate toDate:endDate options:0];
NSLog(#"\"%#\" to \"%#\" : %# year %# month %# day %# hour %# minute %# second", startTime, endTime, #(components.year), #(components.month), #(components.day), #(components.hour), #(components.minute), #(components.second));
}
output:
"2008-02-27 00:00:00" to "2022-02-28 00:00:00" : 14 year 0 month 1 day 0 hour 0 minute 0 second
"2008-02-28 00:00:00" to "2022-02-28 00:00:00" : 14 year 0 month 0 day 0 hour 0 minute 0 second
"2008-02-29 00:00:00" to "2022-02-28 00:00:00" : 14 year 0 month 0 day 0 hour 0 minute 0 second

This is expected. There are many ways to do these period calculations, and the one that NSCalendar uses turns out to not be the one you expected.
The documentation briefly describes what it does:
Some operations can be ambiguous, and the behavior of the computation is calendar-specific, but generally larger components will be computed before smaller components; for example, in the Gregorian calendar a result might be 1 month and 5 days instead of, for example, 0 months and 35 days.
What this means is that it will compute how many years are in between the two dates first, then months, then days, and so on. "Years" is the biggest component you requested.
And NSCalendar finds that adding 14 years to 2008-02-28 makes exactly 2022-02-28. Adding 14 years to 2008-02-29 is also exactly 2022-02-28, because 2022 is not a leap year. Note that "adding a year" does not mean the same as "adding 12 months" or "adding 365 days".
For a difference to appear in this case, you need to compute the days first. One period has 5114 days, and the other has 5113.
A few more examples:
If you instead compute the year, month, day period between 2008-02-28 and 2022-02-01, and the period between 2008-02-29 and 2022-02-01. You wouldn't see a difference, both are 13 years, 11 months, and 4 days. This is because adding 13 years to both 2008-02-29 and 2008-02-28 gets you to 2021-02-28, then adding 11 months is 2022-01-28. 4 days after that is 2022-02-01.
However, if you only compute months and days, the period between 2008-02-28 and 2022-02-01, and the period between 2008-02-29 and 2022-02-01 are different.
The period between 2008-02-28 and 2022-02-01 is 167 months and 4 days. Adding 167 months to 2008-02-28 is 2022-01-28. 4 days after that is 2022-02-01.
The period between 2008-02-29 and 2022-02-01 is 167 months and 3 days. Adding 167 months to 2008-02-29 is 2022-01-29. 3 days after that is 2022-02-01.
Period calculations are weird, aren't they! But they are consistent in a unique way.

Related

NSDateComponents: Get nearest future date

I am creating date components and using the largest unit to display
For example:
1 day 23 hour 2 minute -> 2 days to go (as hour is close to next day
+1 day value)
1 day 10 hour 53 minute -> 1 day to go
0 day 10 hour 50 minute -> 11 hours to go (as minutes are close to next hour +1 hour value)
I'm using below logic to create components
NSCalendar *const calendar = [NSCalendar currentCalendar];
NSDateComponents *const components = [calendar components:(NSCalendarUnitDay |
NSCalendarUnitHour |
NSCalendarUnitMinute)
fromDate:[NSDate date]
toDate:date
options:NSCalendarWrapComponents];
if components.day {return components.day}
if components.hour {return components.hour}
if component.minute {return component.minute}
But above code returns below
1 day 23 hour 2 minute -> 1 day to go
1 day 10 hour 53 minute -> 1 day to go
0 day 10 hour 50 minute -> 10 hours to go
Is there any formatter options that can be used to get rounded up day/hour based upon value of hour/minutes after that?
I know I can check for value of hour if day is present and +1 the day but i'm looking if theres anything supported from iOS

iPhone 6 and 6 plus date format #"YYYY-MM-dd'T'HH:mm:ss" does not work unless system date is set to 24 hours in the Settings

On my iPhone 6 and 6 plus, the date format #"YYYY-MM-dd'T'HH:mm:ss" does not work unless system date is set to 24 hours in the Settings.
NSDateFormatter * dateFormater = [[NSDateFormatter alloc]init];
[dateFormater setDateFormat:#"YYYY-MM-dd'T'HH:mm:ss"];
dateFormater.locale = [NSLocale currentLocale];
NSString *dateString = #"2017-06-12T22:20:04+05:30";
dateFormater.timeZone = [NSTimeZone localTimeZone];
NSDate * startDate = [dateFormater dateFromString:[dateString substringToIndex:19]];
I am getting nil as start date.
Try this :
For 12 Hrs
[dateFormater setDateFormat:#"YYYY-MM-dd'T'hh:mm:ss"];
For 24 Hrs:
[dateFormater setDateFormat:#"YYYY-MM-dd'T'HH:mm:ss"];
Unless you specifically mention the local
dateFormater.locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US"];
or otherwise ,in iPhone 6 and 6 plus with iOS version 10.3.2 the conversion of NSString to NSDate will not be give nil value for NSDate. When different local is selected in phone settings.
NSDate * startDate = [dateFormater dateFromString:[dateString substringToIndex:19]];
Its not issue of any particular device. Your dateformater component is wrong.
hh -> This is used for 12 hours formate.
HH -> This is used for 24 hours formate.
Here is brief description about all of them, so you can get idea:
a: AM/PM
A: 0~86399999 (Millisecond of Day)
c/cc: 1~7 (Day of Week)
ccc: Sun/Mon/Tue/Wed/Thu/Fri/Sat
cccc: Sunday/Monday/Tuesday/Wednesday/Thursday/Friday/Saturday
d: 1~31 (0 padded Day of Month)
D: 1~366 (0 padded Day of Year)
e: 1~7 (0 padded Day of Week)
E~EEE: Sun/Mon/Tue/Wed/Thu/Fri/Sat
EEEE: Sunday/Monday/Tuesday/Wednesday/Thursday/Friday/Saturday
F: 1~5 (0 padded Week of Month, first day of week = Monday)
g: Julian Day Number (number of days since 4713 BC January 1)
G~GGG: BC/AD (Era Designator Abbreviated)
GGGG: Before Christ/Anno Domini
h: 1~12 (0 padded Hour (12hr))
H: 0~23 (0 padded Hour (24hr))
k: 1~24 (0 padded Hour (24hr)
K: 0~11 (0 padded Hour (12hr))
L/LL: 1~12 (0 padded Month)
LLL: Jan/Feb/Mar/Apr/May/Jun/Jul/Aug/Sep/Oct/Nov/Dec
LLLL: January/February/March/April/May/June/July/August/September/October/November/December
m: 0~59 (0 padded Minute)
M/MM: 1~12 (0 padded Month)
MMM: Jan/Feb/Mar/Apr/May/Jun/Jul/Aug/Sep/Oct/Nov/Dec
MMMM: January/February/March/April/May/June/July/August/September/October/November/December
q/qq: 1~4 (0 padded Quarter)
qqq: Q1/Q2/Q3/Q4
qqqq: 1st quarter/2nd quarter/3rd quarter/4th quarter
Q/QQ: 1~4 (0 padded Quarter)
QQQ: Q1/Q2/Q3/Q4
QQQQ: 1st quarter/2nd quarter/3rd quarter/4th quarter
s: 0~59 (0 padded Second)
S: (rounded Sub-Second)
u: (0 padded Year)
v~vvv: (General GMT Timezone Abbreviation)
vvvv: (General GMT Timezone Name)
w: 1~53 (0 padded Week of Year, 1st day of week = Sunday, NB: 1st week of year starts from the last Sunday of last year)
W: 1~5 (0 padded Week of Month, 1st day of week = Sunday)
y/yyyy: (Full Year)
yy/yyy: (2 Digits Year)
Y/YYYY: (Full Year, starting from the Sunday of the 1st week of year)
YY/YYY: (2 Digits Year, starting from the Sunday of the 1st week of year)
z~zzz: (Specific GMT Timezone Abbreviation)
zzzz: (Specific GMT Timezone Name)
Z: +0000 (RFC 822 Timezone)

NSDateComponents returns wrong weekday

If you'll run this code:
// January 16th 2015 10:20 AM in Amsterdam
var date = NSDate(timeIntervalSince1970: 1421400000)
var formatter = NSDateFormatter()
formatter.dateFormat = "dd-MMM"
let calendar = NSCalendar.currentCalendar()
calendar.firstWeekday = 2 // default when region == Netherlands
let units = NSCalendarUnit.YearCalendarUnit | NSCalendarUnit.MonthCalendarUnit
| NSCalendarUnit.WeekOfMonthCalendarUnit | NSCalendarUnit.DayCalendarUnit
| NSCalendarUnit.WeekdayCalendarUnit
// Loop days in January
for day in 1...14 {
// select day in month
var components = calendar.components(units, fromDate: date)
components.day = day
// get day and components
let _date = calendar.dateFromComponents(components)!
var _components = calendar.components(units, fromDate: _date)
// retrieve characteristics
let weekOfMonth = _components.weekOfMonth
let dayOfWeek = _components.weekday
let month = _components.month
println("\(formatter.stringFromDate(_date)) is day \(dayOfWeek) of week \(weekOfMonth) of month \(month) \n")
}
You'll probably get back:
01-Jan is day 5 of week 1 of month 1
02-Jan is day 6 of week 1 of month 1
03-Jan is day 7 of week 1 of month 1
04-Jan is day 1 of week 1 of month 1
05-Jan is day 2 of week 2 of month 1
06-Jan is day 3 of week 2 of month 1
07-Jan is day 4 of week 2 of month 1
....
Those weekdays are wrong.
Not only should the the 1st of january be the 4th day (a thursday), it's also strange that the 3rd of january seems to be on day 7 of week 1 and the 4th of january seems to be day 1 of that same week.
Obviously I'm doing something wrong here, who could help me out?
When you remove the calendar.firstWeekday = 2 line you'll get:
01-Jan is day 5 of week 1 of month 1
02-Jan is day 6 of week 1 of month 1
03-Jan is day 7 of week 1 of month 1
04-Jan is day 1 of week 2 of month 1
05-Jan is day 2 of week 2 of month 1
06-Jan is day 3 of week 2 of month 1
07-Jan is day 4 of week 2 of month 1
....
That makes more sense, but I really need the first day of the week to be a monday here..
I've set up a demo project for you to test this behaviour yourself. https://github.com/tiemevanveen/NSDateComponentsTest/tree/master
As sha points out, components.weekday does not change if your week does not start on a sSnday. Strange that components.weekOfMonth does change when your week starts on Monday..
Solution to my problem
An answer to another question let me to a way to find the desired weekday if the calendar's week does not start with a Sunday.
dayOfWeek = calendar.ordinalityOfUnit(.WeekdayCalendarUnit, inUnit: .WeekCalendarUnit, forDate: _date)
That code could also be used to find the week of the month or the month itself:
dayOfWeek = calendar.ordinalityOfUnit(.WeekdayCalendarUnit, inUnit: .WeekCalendarUnit, forDate: _date)
month = calendar.ordinalityOfUnit(.MonthCalendarUnit, inUnit: .YearCalendarUnit, forDate: _date)
It's all correct. If you look at Apple Documentation.
you can see that 1 is Sunday, 2 - is Monday and so forth. So 5 is Thursday as expected.

How to convert difference between two times into hours only?

I found the answer to this, but unfortunately it's using Java. I have two times, formatted as HHmm (no colons). I need to figure out how many 15 minute time segments are in the difference. For example, I have a start time of 1000 and an end time of 1130 (military time).
When I subtract the two dates, I get 130, which is meaningless for computations.
Is there an existing method that will do this for me? (I have spent the last 6 hours trying SO and Google, but found nothing).
UPDATE: I would appreciate it if whoever downvoted me please reverse it. The question is very pertinent and others will find it useful. Thank you.
Parse each time and convert to minutes. So 1000 becomes 10 hours 0 minutes for a total of 600 minutes. 1130 becomes 11 hours 30 minutes for a total of 690 minutes. Subtract the two values for a difference of 90 minutes. Now divide by 15 to get 6.
The following assumes all times are 4 digit military times:
NSString *startTime = #"1000";
NSString *endTime = #"1130";
int startMinues = [[startTime substringToIndex:2] intValue] * 60 + [[startTime substringFromIndex:2] intValue];
int endMinues = [[endTime substringToIndex:2] intValue] * 60 + [[endTime substringFromIndex:2] intValue];
int minutes = endMinutes - startMinutes;
int units = minutes / 15;
This gives whole units of your 15 minute blocks.
Use -[NSCalendar components:fromDate:toDate:options], like this:
NSDateComponents *components = [[NSCalendar currentCalendar] components:NSCalenderUnitMinute fromDate:startDate toDate:endDate options:0];
NSInteger numberOfMinutes = [components minute];
Once you have the number of minutes, it should just be a matter of dividing by 15 to get the number of 15 minute chunks.
Try using NSDateComponents:
NSCalendar *calendar = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease];
NSDateComponents *components = [calendar components:NSMinuteCalendarUnit|NSHourCalendarUnit
fromDate:dateA
toDate:dateB
options:0];
int increments = components.hour*4 + components.minute/15;
Format is rather simple:
int HHmm = [date intValue];
int HH = HHmm / 100;
int mm = HHmm % 100;
Diff, for two parsed dates:
int diff = ((HH2 * 60 + mm2) - (HH1 * 60 + mm1)) / 15;

Convert day of the year to date [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions asking for code must demonstrate a minimal understanding of the problem being solved. Include attempted solutions, why they didn't work, and the expected results. See also: Stack Overflow question checklist
Closed 9 years ago.
Improve this question
Currently I failed to convert a day of the year integer to a formatted string / date like mm-dd. I tried some different solution approaches, including most of the solutions which are posted here - without success.
I expect this: Today is the 359 day of the year - and it should be converted to 12-25
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents* components = [[NSDateComponents alloc] init];
[components setDay:359];
[components setYear:2013];
[components setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
NSDate* day359 = [gregorian dateFromComponents:components];
output
2013-12-25 00:00:00 +0000
Really?, even without knowing objective-c it seems quite stright forward:
const JANUARY = 31;
const FEBURARY = JANUARY + 28;
const MARCH = FEBRUARY + 31;
...
const NOVEMBER = OCTOBER + 30;
if( day < JANUARY ) { dd = day; mm = 1; }
else if( day < FEBRUARY ) { dd = day - JANUARY; mm = 2 }
else if( day < MARCH ) { dd = day - FEBRUARY; mm = 3 }
...
else if( day < NOVEMBER ) { dd = day - OCTOBER; mm = 11 }
else { dd == day - NOVEMBER; mm = 12 }
print "$dd/$mm";

Resources