NSDateComponents returns wrong weekday - ios

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.

Related

Is it NSCalendar bug?

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.

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

Find days of week based on weekdays mask value

I have a weekdays mask number which represents the sum of some days of the week:
SUNDAY = ((2**0))
MONDAY = ((2**1))
TUESDAY = ((2**2))
weekdays_mask = SUNDAY + MONDAY + TUESDAY
So if I have the sum of these days stored in DB how can I do the reverse process and find what days of week I will have to display based on the weekdays_mask value. Does anyone know a method for doing this? Thank you!
+ is a bit uncommon. You'd usually use bitwise OR to combine the values:
SUNDAY | MONDAY | TUESDAY #=> 7
SUNDAY | MONDAY #=> 3
and bitwise AND to check for a specific value:
days = SUNDAY | MONDAY
days & SUNDAY #=> 1
days & MONDAY #=> 2
days & TUESDAY #=> 0 <- zero indicates bit not set
You could initialise them by the 0b0000-like pattern and instead of +/- then use binary arithmetics instead;
SUNDAY = 0b000000
MONDAY = 0b000001
TUESDAY = 0b000010
WEDNESDAY = 0b000100
THURSDAY = 0b001000
FRIDAY = 0b010000
SATURDAY = 0b100000
weekend = SATURDAY | SUNDAY
workday = MONDAY | TUESDAY | WEDNESDAY | THURSDAY | FRIDAY
is_friday_weekend = weekend & FRIDAY
# => 0
is_friday_workday = workday & FRIDAY
# => 16 (Which is not 0; so yes)
I'm sure there is a cleaner way, bit here my 2 cents, based on three days, but works also adding more days:
SUNDAY = ((2**0))
MONDAY = ((2**1))
TUESDAY = ((2**2))
weekdays_mask_7 = SUNDAY | MONDAY | TUESDAY
weekdays_mask_6 = MONDAY | TUESDAY
weekdays_mask_5 = SUNDAY | TUESDAY
weekdays_mask_4 = TUESDAY
weekdays_mask_3 = SUNDAY | MONDAY
weekdays_mask_2 = MONDAY
weekdays_mask_1 = SUNDAY
days = %w[SUNDAY MONDAY TUESDAY]
weekdays_mask = weekdays_mask_7 # just to play
day_array = (weekdays_mask.to_s(2).split("").map(&:to_i).reverse).zip(days)
day_array.select{|e| p e[1] if e[0] == 1}

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)

Ruby on Rails - Get date - given is year, month, weeknumber and day

I have some troubles with the following task:
Given are the following parameters:
Year: 2016
Month: 2
Week of Month: 2
Day of week: Monday
The result should be 2016-02-08
Is there a method to get this?
Thanks in advance
A way to do this would be
weekday_index = {monday: 1, tuesday: 2, wednesday: 3, thursday: 4, friday: 5, saturday: 6, sunday: 7}
day_in_a_week = 7
year = year_param # 2016
month = month_param # 8
week_number = 2
day_of_month = (week_number * days_in_a_week) - (days_in_a_week - weekday_index[:monday])
Date.new(year, month, day_of_month).to_s
You can use this code. Values used were supplied in the question month = 2, week_number = 2 and year = 2016
month = 2
year = 2016
week_number = 2
FIRST_DAY = 1 #first day of every month
day = { monday: 1, tuesday: 2, wednesday: 3, thursday: 4, friday: 5, saturday: 6, sunday: 7}
first_wk_of_month = Date.new(year, month, FIRST_DAY).cweek #=> 05
date_week_num = first_wk_of_month + (week_number - 1) #=> 06
date = Date.commercial(year, date_week_num, day[:monday]).to_s # 2016-02-08
There are 52 weeks in a year and a month can have as low as 1 day in a week if it starts on Sunday. Taking that into consideration,
first_wk_of_month finds the week number in the year (between 1 and 52) on which the month in question starts.
date_week_num finds the actual week number in the year the date falls on. In this scenario which is 2nd week of February it returns 6 representing the 6th week of the year
Passing the date_week_num and the day to the Date.commercial allows us to find the date
Tested it with a couple of days and works fine. You could probably change the variable names. Hope this solves your challenge

Resources