Creating NSDate from components without "timezone correction" - ios

I have a function
func createDate(day: Int, month: Int, year: Int) -> NSDate {
var comp = NSDateComponents()
comp.day = day
comp.month = month
comp.year = year
var cal = NSCalendar.currentCalendar()
if var date = cal.dateFromComponents(comp) {
return date
}
return NSDate()
}
If I call it with
createDate(07, 01, 1984)
the output is 1984-01-06 23:00:00 +0000. I assume that there is some influence from the time zone. How can I adjust my code so that the output will be 1984-01-07 00:00:00 +0000? What am I getting wrong here? How am I confused?

There is no need to use NSDateComponents to input time. NSCalendar already has a method for you to input your date using your local time. It is called dateWithEra.
"+0000" it is not your localTime (CET) it is UTC time. Your timeZone is not stored with your NSDate object, only the corresponding UTC date and time for your localTime input.
let myDate = NSCalendar.currentCalendar().dateWithEra(1, year: 1984, month: 1, day: 7, hour: 0, minute: 0, second: 0, nanosecond: 0)!
myDate.descriptionWithLocale(NSLocale.currentLocale())! // "Saturday, January 7, 1984 at 12:00:00 AM Brasilia Standard Time"
NSTimeZone.localTimeZone().secondsFromGMT

Don't worry about the log output. It will be an unformatted date. The actual date seems to be exactly what you intended.
If you want to show the exact date, you have to display it using a NSDateFormatter.

Related

Check if NSDate is in current week not working for specific date?

I have tried couple of ways to check if NSDate is in current week:
Method 1
func isInThisWeek(date: Date) -> Bool
{
return Calendar.current.isDate(date, equalTo: Date(), toGranularity: .weekOfYear)
}
Method 2
func dateFallsInCurrentWeek(date: Date) -> Bool
{
let currentWeek = Calendar.current.component(Calendar.Component.weekOfYear, from: Date())
let datesWeek = Calendar.current.component(Calendar.Component.weekOfYear, from: date)
return (currentWeek == datesWeek)
}
Now here is the case where I am getting FALSE though this date is in current week.
I tested on: Monday, August 10, 2020 6:00:00 PM (My time zone: +5:30 GMT). So as per calendar, this date belongs to 10 Aug - 16 Aug week.
What may be wrong? In my iPad in which I am testing this, has starting day of Week is Monday as following:
All calendars would consider sunday as the first weekday. If you would like to consider monday as the start of your week you need to use iso8601 calendar.
let date = Date(timeIntervalSince1970: 1597516200) // .description // "2020-08-15 18:30:00 +0000"
let tz = TimeZone(secondsFromGMT: 5*3600 + 1800)! // GMT+0530 (fixed)
let components = Calendar.current.dateComponents(in: tz, from: date)
components.day // 16
components.weekday // Sunday
// Current Calendar starts on sunday so it goes from 9...15 and 16...22
// To get the current week starting on monday you need iso8601 calendar
let equalToDate = DateComponents(calendar: .current, timeZone: tz, year: 2020, month: 8, day: 10, hour: 18).date!
equalToDate.description // "2020-08-10 12:30:00 +0000"
Calendar(identifier: .iso8601).isDate(date, equalTo: equalToDate, toGranularity: .weekOfYear) // true

HKActivitySummary dateComponents a day behind

For some strange reason when executing a HKActivitySummaryQuery the returned date component for each summary is a day behind.
The query returns data from the correct date but the dateComponents date of the data is behind by a day. I've tried setting the timezone and locale but results remain the same.
Summary Model
struct ActivitySummary {
init?(_ summary: HKActivitySummary) {
var calendar = Calendar.current
calendar.timeZone = TimeZone.current
guard let date = summary.dateComponents(for: calendar).date else { return nil }
print("ORIGINAL: ", date.description(with: Locale.current))
//Expected: Tuesday, January 30, 2018 at 7:00:00 PM Eastern Standard Time
//Results: Monday, January 29, 2018 at 7:00:00 PM Eastern Standard Time
let other = calendar.dateComponents( [ .year, .month, .day ], from: date)
print("START OF DAY: ", date.startOfDay.description(with: Locale.current))
//Expected: Tuesday, January 30, 2018 at 12:00:00 AM Eastern Standard Time
//Results: Monday, January 29, 2018 at 12:00:00 AM Eastern Standard Time
}
}
HKAcitivitySummaryQuery
func summaryQuery(){
let predicate = HKQuery.predicate(forActivitySummariesBetweenStart: fromDate.components(), end: toDate!.components())
let query = HKActivitySummaryQuery(predicate: predicate) { (query, summaries, error) in
guard let summaries = summaries, summaries.count > 0 else {
return
}
//
var activitySummaries: [ActivitySummary] = []
activitySummaries = summaries.compactMap({
ActivitySummary($0)
})
}
}
Maybe the calendar you're working with is wrong. set your calendar like this :
let calendar = Calendar.current
Try manually setting the time zone for the DateComponents before converting them into a Date:
let calendar = Calendar.current
var dateComponents = summary.dateComponents(in: calendar)
dateComponents.timeZone = calendar.timeZone
let date = dateComponents.date!
The DateComponents associated with each HKActivitySummary do not include a time zone. The resulting Date may be slightly off from what you’re expecting because it’s not in the correct time zone (Swift defaults to UTC if a time zone is not specified). Manually specifying the time zone should resolve this issue.

1st april dates of 80s failed to parse in iOS 10.0

I found that DateFormatter date(from:) method can't parse a couple of specific dates. Method returns nil for the 1st april of 1981-1984 years. Is it a bug of Foundation? What can we do to perform parsing of such dates?
Xcode 8.0, iOS SDK 10.0. Here is a screenshot of a short playground example:
This problem occurs if daylight saving time starts exactly on
midnight, as it was the case in Moscow in the years 1981–1984 (see for example Clock Changes in Moscow, Russia (Moskva)).
This was also observed in
Why does NSDateFormatter return nil date for these 4 time zones? and
Why NSDateFormatter is returning null for a 19/10/2014 in a Brazilian time zone?
For example, at midnight of April 1st 1984, the clocks were adjusted one hour forward, which means that the date "1984-04-01 00:00"
does not exist in that timezone:
let dFmt = DateFormatter()
dFmt.dateFormat = "yyyy-MM-dd"
dFmt.timeZone = TimeZone(identifier: "Europe/Moscow")
print(dFmt.date(from: "1984-04-01")) // nil
As a solution, you can tell the date formatter to be "lenient":
dFmt.isLenient = true
and then it will return the first valid date on that day:
dFmt.isLenient = true
if let date = dFmt.date(from: "1984-04-01") {
dFmt.dateFormat = "yyyy-MM-dd HH:mm:ss"
print(dFmt.string(from: date))
}
// 1984-04-01 01:00:00
A different solution
was given by rob mayoff, which is to make the date formatter use noon instead of midnight as the
default date. Here is a translation of rob's code from Objective-C to Swift:
let noon = DateComponents(calendar: dFmt.calendar, timeZone: dFmt.timeZone,
year: 2001, month: 1, day: 1, hour: 12, minute: 0, second: 0)
dFmt.defaultDate = noon.date
if let date = dFmt.date(from: "1984-04-01") {
dFmt.dateFormat = "yyyy-MM-dd HH:mm:ss"
print(dFmt.string(from: date))
}
// 1984-04-01 12:00:00

Creating NSDate from Int components

I have a calendar with which I select a date:
func SACalendarDate(calendar: SACalendar!, didSelectDate day: Int32, month: Int32, year: Int32) {
var date = Date.from(year: Int(year), month: Int(month), day: Int(day))
print("\(year) and \(month) and \(day)")
let formatter = NSDateFormatter()
formatter.dateStyle = NSDateFormatterStyle.MediumStyle
formatter.timeStyle = NSDateFormatterStyle.NoStyle
let dateString = formatter.stringFromDate(date)
buttonSelectDates.setTitle("\(dateString)", forState: UIControlState.Normal)
getUsers(date)
}
When I feed "date" in this method:
class func from(#year:Int, month:Int, day:Int) -> NSDate {
var c = NSDateComponents()
c.year = year
c.month = month
c.day = day
var gregorian = NSCalendar(identifier:NSCalendarIdentifierGregorian)
var date = gregorian!.dateFromComponents(c)
return date!
}
I get a date that is one day behind: selectedDate: 2015-07-14 22:00:00 +0000
And the parameters are: 2015, 7 and 15. Why does this happen?
NSDate is just a single point in time represented as seconds since 1.1.1970, it does not care about timezones or anything - its "timezone" is GMT and is +0000 (no offset). If you create a NSDate from a calendar with the local timezone, for example +0200 that timezone offset will be taken off the actual date you provide to represent a point in time without any timezone. To get a readable date representation of the NSDate back you need to use a NSDateFormatter which knows your current timezone:
let date = from(2015, month: 7, day: 15)
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
print(dateFormatter.stringFromDate(date))
print(date)
You will receive the following printed output:
2015-07-15 00:00:00 +0200
2015-07-14 22:00:00 +0000

Date from week of year returning date not in week

I have come across a rather strange "bug". When getting a date for a week of a year using this method:
let dates = NSMutableArray()
let cal = NSCalendar.currentCalendar()
cal.firstWeekday = 2
let formatter = NSDateFormatter()
formatter.dateFormat = "ww YYYY"
formatter.calendar = cal
let date = formatter.dateFromString(week as String)
println(date)
The string week is 52 2014, so the expected date would be Monday December 22th, but instead it returns Saturday December 20th, at 23:00. First of all, I thought I'd handled the first day of week by setting the firstWeekday-option of the calendar, but no luck. In addition, the date returned isn't even in week 52.
Just to double check I ran cal.components(NSCalendarUnit.WeekOfYearCalendarUnit, fromDate: date!).weekOfYear to double check I'm not an idiot, and no sir, the week for the date produced is 51, the week before the desired week.
Any idea how I can reach the expected result?
Any idea how I can reach the expected result?
What actually is your desired result? Do you want to know the first day of the week or the first day in the last day? Than you could tray this:
let now = NSDate()
var startDate: NSDate? = nil
var duration: NSTimeInterval = 0
let cal = NSCalendar.currentCalendar()
cal.firstWeekday = 2
cal.rangeOfUnit(.WeekCalendarUnit, startDate: &startDate, interval: &duration, forDate: now);
let endDate = startDate?.dateByAddingTimeInterval(duration)
print(startDate)
print(endDate)
it prints
"Optional(2014-12-21 23:00:00 +0000)"
"Optional(2014-12-28 23:00:00 +0000)"
the endDate is the first second that is not in the week anymore.
Note that the offset of 1 hour results from the fact that it is printed in UTC time, that is actually GMT winter time. Indeed these dates are 2014-12-22 00:00:00 and 2014-12-29 00:00:00 in my time zone (GMT+1)
or simply
let components = NSDateComponents()
components.weekOfYear = 52
components.weekday = 2
components.year = 2014
let cal = NSCalendar.currentCalendar()
let day = cal.dateFromComponents(components)
This code adapted to respect user's calendar:
let cal = NSCalendar.currentCalendar()
let components = NSDateComponents()
components.weekOfYear = 52
components.weekday = cal.firstWeekday
components.year = 2014
Changing the firstWeekday from 1 to 2 won't change the date, it will change just the First weekday from Sunday to Monday.
You can do it like this:
func dateFromWeekOfYear(year:Int, weekOfYear:Int, weekday:Int) -> NSDate {
return NSCalendar.currentCalendar().dateWithEra(1, yearForWeekOfYear: year, weekOfYear: weekOfYear, weekday: weekday, hour: 0, minute: 0, second: 0, nanosecond: 0)!
}
let date1 = dateFromWeekOfYear(2014, 52, 1) // Dec 21, 2014, 12:00 AM
let date2 = dateFromWeekOfYear(2014, 52, 2) // Dec 22, 2014, 12:00 AM
let date3 = dateFromWeekOfYear(2014, 52, 3) // Dec 23, 2014, 12:00 AM
If dealing with a string and you want to set he Stand Alone local day of week you can do it like this:
let myDate = "2 52 2014"
let cal = NSCalendar.currentCalendar()
let formatter = NSDateFormatter()
formatter.dateFormat = "c ww Y"
formatter.calendar = cal
if let date1 = formatter.dateFromString(myDate) {
date1 // "Dec 22, 2014, 12:00 AM"
}
If you need further reference you can use this:

Resources