How to calculate days difference between two NSDate objects in different time zones? - ios

I have two NSDate which are initiated from UTC dates.
Lets call them (A & B)
I know that the A represents a day in China and B represents a day in USA. (I know the time zones.) How can I calculate the difference in days between the two...?
I have been using the following method which is obviously incorrect.
class func daysDifferenceIn(firstDate: NSDate, firstTimeZone: String, secondDate: NSDate, secondTimeZone: String) -> Int {
objc_sync_enter(self)
let firstDateComponents = NSCalendar.CommonCalendar.componentsInTimeZone(NSTimeZone(name: firstTimeZone)!, fromDate: firstDate)
let secondDateComponents = NSCalendar.CommonCalendar.componentsInTimeZone(NSTimeZone(name: secondTimeZone)!, fromDate: secondDate)
NSCalendar.CommonCalendar.timeZone = NSTimeZone(name: firstTimeZone)!
let firstCalDate = NSCalendar.CommonCalendar.dateFromComponents(firstDateComponents)
NSCalendar.CommonCalendar.timeZone = NSTimeZone(name: secondTimeZone)!
let secondCalDate = NSCalendar.CommonCalendar.dateFromComponents(secondDateComponents)
objc_sync_exit(self)
return firstCalDate!.numberOfDaysUntilDateTime(secondCalDate!)
}
func numberOfDaysUntilDateTime(toDateTime: NSDate, inTimeZone timeZone: NSTimeZone? = nil) -> Int {
let calendar = NSCalendar.CommonCalendar
if let timeZone = timeZone {
calendar.timeZone = timeZone
}
var fromDate: NSDate?, toDate: NSDate?
calendar.rangeOfUnit(.Day, startDate: &fromDate, interval: nil, forDate: self)
calendar.rangeOfUnit(.Day, startDate: &toDate, interval: nil, forDate: toDateTime)
let difference = calendar.components(.Day, fromDate: fromDate!, toDate: toDate!, options: [])
return difference.day
}
I can manually subtract day components from firstDateComponents and secondDateComponents which I don't want to do as I have to look for edge cases of 31 and 28 and so on.
Any help is appreciated. Thanks.
UPDATE
First date is 2017-02-10 16:15:00 +0000
Second date is 2017-02-11 03:20:00 +0000 Both are UTC.
firstTimeZone String "Asia/Shanghai"
secondTimeZone String "America/Los_Angeles"
So the day difference is -1 Day. Basically I am implementing flight status and you can see the following link as the flight lands 1 day prior to take day. As it flies from West to East.
https://www.google.co.in/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=ua+890
Also a description of Date components.
Printing description of firstDateComponents:
<NSDateComponents: 0x600000142310>
Calendar: <CFCalendar 0x60000088b6d0 [0x10c5d3df0]>{identifier = 'gregorian'}
TimeZone: Asia/Shanghai (GMT+8) offset 28800
Era: 1
Calendar Year: 2017
Month: 2
Leap month: no
Day: 11
Hour: 0
Minute: 15
Second: 0
Nanosecond: 0
Quarter: 0
Year for Week of Year: 2017
Week of Year: 6
Week of Month: 2
Weekday: 7
Weekday Ordinal: 2
Printing description of secondDateComponents:
<NSDateComponents: 0x60000014b8f0>
Calendar: <CFCalendar 0x60000049b620 [0x10c5d3df0]>{identifier = 'gregorian'}
TimeZone: America/Los_Angeles (PST) offset -28800
Era: 1
Calendar Year: 2017
Month: 2
Leap month: no
Day: 10
Hour: 19
Minute: 20
Second: 0
Nanosecond: 0
Quarter: 0
Year for Week of Year: 2017
Week of Year: 6
Week of Month: 2
Weekday: 6
Weekday Ordinal: 2

This is an odd case. You're looking for the difference in calendar dates between two Dates when those dates are evaluated in a specific time zone.
I did some playing, and came up with code that works for dates that fall in the same year:
let date = Date()
guard let nycTimeZone = TimeZone(abbreviation: "EST"),
let nzTimeZone = TimeZone(abbreviation: "NZDT") else {
fatalError()
}
var nycCalendar = Calendar(identifier: .gregorian)
nycCalendar.timeZone = nycTimeZone
var nzCalendar = Calendar(identifier: .gregorian)
nzCalendar.timeZone = nzTimeZone
let now = Date()
let nycDayOfYear = nycCalendar.ordinality(of: .day, in: .year, for: now)
var nzDayOfYear = nzCalendar.ordinality(of: .day, in: .year, for: now)
I'm using New York and Aukland, NZ as my time zones because as of the time of this writing, those zones are on different dates.
As of now (~12:00 PM on Feb 11, 2017 in US Eastern Standard Time (UTC - 5) the code above gives
nycDayOfYear = 42
and
nzDayOfYear = 43
It would take some work to make that calculation work across year boundaries.
Curiously, the following code:
var nzDayOfEra = nzCalendar.ordinality(of: .day, in: .era, for: now)
let nycDayOfEra = nycCalendar.ordinality(of: .day, in: .era, for: now)
Gives the same value for both NZ and NYC. I'm not sure why.
EDIT:
Ok, I did some experimenting and got code that works. What I do is to convert both dates to month/day/year date components using a calendar set to the local time zone for each time. Then I use a method dateComponents(_:from:to:) to calculate the difference between those 2 DateComponents, in days:
import UIKit
let date = Date()
guard let nycTimeZone = TimeZone(abbreviation: "EST"),
let nzTimeZone = TimeZone(abbreviation: "NZDT") else {
fatalError()
}
var nycCalendar = Calendar(identifier: .gregorian)
nycCalendar.timeZone = nycTimeZone
var nzCalendar = Calendar(identifier: .gregorian)
nzCalendar.timeZone = nzTimeZone
let now = Date()
let nycDateComponents = nycCalendar.dateComponents([.month, .day, .year], from: now)
let nzDateComponents = nzCalendar.dateComponents([.month, .day, .year], from: now)
let difference = Calendar.current.dateComponents([.day],
from: nycDateComponents,
to: nzDateComponents)
let daysDifference = difference.days
As of this writing that gives a daysDifference of 1. Since we're using the dateComponents(_:from:to:) function, it takes care of the math to calculate the number of days difference between the 2 month/day/year DateComponents.

A NSDate represents a moment in time. It has no time zone. Only string representations of dates have time zone information.
If you have the dates, just take the number of days between them. Don't worry about time zones.
Usually:
let difference = calendar.components(.Day, fromDate: self, toDate: toDate!, options: [])
return difference.day
should be enough.

Have you tried using let interval = laterDate.timeIntervalSinceDate(earlierDate)? This will return the difference between the two dates in seconds.

Related

Number of days in month returns wrong value after 10:00 PM

I am having a small issue with getting the total days in a month using Swift.
I have extended the Date class and created this function:
func daysInMonth() -> Int {
print(self.day) ##30
print(self.month) ##12
print(self) ## 2021-11-30 23:46:29 +0000
print(Calendar.current.range(of: .day, in: .month, for: self)?.count) ##31
return Calendar.current.range(of: .day, in: .month, for: self)?.count ?? 0
}
I have set the Date&Time to the 30th of November, at 11:45 PM in the settings of my Mac, in Preferences.
I called the above function at 11:46 PM and obtained the above results (inline, next to the print statements).
The date output is correct as well as the day. The month output is wrong and the result is 31 days in the month of November.
If I run this exact same code before 10:00 PM, I get the right result which is 30 days.
Does anyone know why this is happening?
Thank you,
Paprika
It's a GMT offset issue combined with the current day in a month.
When you create a date without set a day, it will be set to the first day of the month.
So, if your timezone offset is for example -4 means your are 4 hours behind the GMT 0 and by default the timezone defined at Calendar.current is equal the system timezone. So what it means? Means you'll obtain the previous month if you test it in a boundary of 23 + (-4) or the next month if your offset is positive.
You can test this behaviour copying'n paste the following code in the Playground.
func getDaysInMonth(month: Int, year: Int, offset: Int = 0) -> Int? {
let someDate = DateComponents(year: year, month: month, hour: 3)
var current = Calendar.current
let timezone = TimeZone(secondsFromGMT: 60 * 60 * offset)!
current.timeZone = timezone
guard let someDay = current.date(from: someDate) else { return nil }
print("date: \(someDay)") // this will always
return someDay.daysInCurrentMonth
}
for hour in -12...12 {
print("hour: \(hour)\ndays: \(getDaysInMonth(month: 10, year: 2021, offset: hour) ?? -1)")
print("---\n")
}
extension Date {
var daysInCurrentMonth: Int? {
Calendar.current.range(of: .day, in: .month, for: self)?.count
}
}
Notice the days will change starting by your current system time zone (notice only the month will change).
How to fix this?
In your case, I guess you just want to show how many days a month have, so you can just set the to zero like this:
TimeZone(secondsFromGMT: 0)
Do this change at a instance of Calendar.current and check if it works for you.
It appears there something wrong with your Date extension methods for .day and .month.
Without seeing code it's hard to determine what the problem is though. Below is some code for returning the current month (Int) and current numbered day of month (Int)
extension Date
{
var month: Int
{
let date = Date()
let calendar = Calendar.current
let components = calendar.dateComponents([.month], from: date)
return components.month
}
var day: Int
{
let date = Date()
let calendar = Calendar.current
let components = calendar.dateComponents([.day], from: self)
return components.day
}
}
Please also ensure your time/date settings are correct on your mac/simulator/device. If these are wrong - it could have been jumping to a different month if you were in a timezone that was ahead a few hours.

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

How to get the today's and tomorrow's date in swift 4

How to get the current date in unix-epoch?
timeIntervalSince1970 prints the current time. Is there any way to get today's time at 12 AM?
For example, The current time is : Jan 7, 2018 5:30 PM. timeIntervalSince1970 will print the current time i.e. 1546903800000.
Current date in epoch system will be Jan 7, 2018 00:00 AM. i.e 1546848000000
This can be done very simply using the following code. No need for date components or other complications.
var calendar = Calendar.current
// Use the following line if you want midnight UTC instead of local time
//calendar.timeZone = TimeZone(secondsFromGMT: 0)
let today = Date()
let midnight = calendar.startOfDay(for: today)
let tomorrow = calendar.date(byAdding: .day, value: 1, to: midnight)!
let midnightEpoch = midnight.timeIntervalSince1970
let tomorrowEpoch = tomorrow.timeIntervalSince1970
I would do this with components.
Assuming you need time in seconds as defined by time(2). If you need in milliseconds as defined by time(3), then you can multiply it out by 1000.
// Get right now as it's `DateComponents`.
let now = Calendar.current.dateComponents(in: .current, from: Date())
// Create the start of the day in `DateComponents` by leaving off the time.
let today = DateComponents(year: now.year, month: now.month, day: now.day)
let dateToday = Calendar.current.date(from: today)!
print(dateToday.timeIntervalSince1970)
// Add 1 to the day to get tomorrow.
// Don't worry about month and year wraps, the API handles that.
let tomorrow = DateComponents(year: now.year, month: now.month, day: now.day! + 1)
let dateTomorrow = Calendar.current.date(from: tomorrow)!
print(dateTomorrow.timeIntervalSince1970)
You can get yesterday by subtracting 1.
If you need this in the universal time (UTC, GMT, Z… whatever name you give universal time), then use the following.
let utc = TimeZone(abbreviation: "UTC")!
let now = Calendar.current.dateComponents(in: utc, from: Date())
Use this extension to get today's and tomorrow's date
extension Date {
static var tomorrow: Date { return Date().dayAfter }
static var today: Date {return Date()}
var dayAfter: Date {
return Calendar.current.date(byAdding: .day, value: 1, to: Date())!
}
}
Also try adding following code in date extension:
extension Date
{
var startOfDay: Date
{
return Calendar.current.startOfDay(for: self)
}
func getDate(dayDifference: Int) -> Date {
var components = DateComponents()
components.day = dayDifference
return Calendar.current.date(byAdding: components, to:startOfDay)!
}
}
You can use the following method to get any date by adding days or months or years
by specifying the Calendar Component and the increment value of this component:
func getSpecificDate(byAdding component: Calendar.Component, value: Int) -> Date {
let noon = Calendar.current.date(bySettingHour: 12, minute: 0, second: 0, of: self)!
return Calendar.current.date(byAdding: component, value: value, to: noon)!
}
Where the component wil be one from the following option :
( .day , .month , .year ) and the value will be the amount you want to add for this component
for example to get the next year date you can use the following code:
var nextYear = getSpecificDate(byAdding: .year, value: 1).timeIntervalSince1970

how to get NSDate of a specific next day and time

I have some events for which I need to calculate NSDates.
For example I'm trying to get the next Monday at 8:00 AM.
So I tried some stuff but nothing works:
1.
let nextMonday = NSCalendar.currentCalendar().dateBySettingUnit(NSCalendarUnit.Weekday, value: 2, ofDate: startDate, options: NSCalendarOptions.MatchNextTime)
let nextMondayEight = NSCalendar.currentCalendar().dateBySettingUnit(NSCalendarUnit.Hour, value: 8, ofDate: nextMonday!, options: NSCalendarOptions.MatchNextTime)
I get:
2016-04-12 05:00:00 +0000
That's Tuesday at 8:00 (the time difference is my local time GMT -3).
2.
let unitFlags: NSCalendarUnit = [.Day, .Month, .Year]
let comp = NSCalendar.currentCalendar().components(unitFlags, fromDate: NSDate())
comp.timeZone = NSTimeZone.localTimeZone()
comp.weekday = 1
comp.hour = 8
comp.minute = 0
comp.second = 0
let compDate = NSCalendar.currentCalendar().dateFromComponents(comp)
print("time: \(compDate!)")
I get:
2016-04-11 05:00:00 +0000
That's today at 8:00 and not next Monday at 8:00.
Any suggestions?
Thanks
NSCalendar has a method nextDateAfterDate:matchingComponents:options for this kind of date math.
let calendar = NSCalendar.currentCalendar()
let components = NSDateComponents()
components.hour = 8 // 8:00
components.weekday = 2 // Monday in Gregorian Calendar
let nextMondayEightOClock = calendar.nextDateAfterDate(NSDate(), matchingComponents: components, options: .MatchStrictly)

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