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.
Related
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.
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
I'm grabbing data from an api, and one of the values I'm getting is for day of the week, the data returned from api looks like this:
"time": 1550376000
I created this function to get the date:
func getDate(value: Int) -> String {
let date = Calendar.current.date(byAdding: .day, value: value, to: Date())
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "E"
return dateFormatter.string(from: date!)
}
but was told there is a much safer way to get it instead of assuming we get consecutive days starting with today. Does anyone know how to build a date out of the time field (it is seconds since midnight 1970) and then use Calendar and DateComponent to figure out the day?
Looks like you are receiving json data so you should structure your data and conform to Decodable protocol to convert your data to an object properly structured.
struct Object: Decodable {
let time: Date
}
Don't forget to set the decoder dateDecodingStrategy property to secondsSince1970
do {
let obj = try decoder.decode(Object.self, from: Data(json.utf8))
let date = obj.time // "Feb 17, 2019 at 1:00 AM"
print(date.description(with: .current))// "Sunday, February 17, 2019 at 1:00:00 AM Brasilia Standard Time\n"
} catch {
print(error)
}
Then you just need to get the weekday component (1...7 = Sun...Sat) and get the calendar shortWeekdaySymbols (localised), subtract 1 from the component value and use it as index to get correspondent symbol. Same approach I used in this post How to print name of the day of the week? to get the full week day name:
extension Date {
var weekDay: Int {
return Calendar.current.component(.weekday, from: self)
}
var weekdaySymbolShort: String {
return Calendar.current.shortWeekdaySymbols[weekDay-1]
}
}
print(date.weekdaySymbolShort) // "Sun\n"
You can use Calendar to get date component from the Date:
let date = Date(timeIntervalSince1970: time)// time is your value 1550376000
let timeComponents = Calendar.current.dateComponents([.weekday, .day, .month, .year], from: date)
print("\(timeComponents.weekday) \(timeComponents.day!) \(timeComponents.month!) \(timeComponents.year!)") // print "7 16 2 2019"
print("\(\(Calendar.current.shortWeekdaySymbols[timeComponents.weekday!-1]))") // print "Sat"
Hope this helps.
I know how to get local date and time, but what I want to do is getting the date and time from different places. For example, I want to find out what the time and date is in New York. How can i solve this simple problem?
Here is my code for local date and time :
let date = NSDate()
let calendar = Calendar.current
let components = calendar.dateComponents([.hour, .minute, .month, .year, .day, .second, .weekOfMonth], from: date as Date)
let currentDate = calendar.date(from: components)
I searched about it here, but i didn't find what i want and I'm still looking for the date libaries. If you know any source or sample to redirect me, I really appreciate that.
There are several different concepts involved here, and we need to understand (almost) all of them to get this right...
1) a Date (NSDate as was, in Swift) is an absolute point in time - it's slightly mis-named, because it has nothing to do with an actual date like 13th November 2017, because to get to that we need to define ...
2) a Calendar, because 13th November 2017 in the western Gregorian calendar could also be 23rd Safar 1439 in the Islamic calendar, or the 24th of Heshvan 5778 in the Hebrew calendar, or some other things in the many other calendars that iOS & MacOS support;
3) in turn Calendar changes not only what values are returned in the DateComponents that we have to use to unpack a Date + Calendar into days, months, years & eras (e.g. BC/AD), or even week number, etc..., but also some calendars might not have the same components as others;
4) time-of-day (as you know) depends on TimeZone, so the same absolute time can be one of many different times "o'clock" depending on where you are. It may also (as you can see in the example below) change the date as well as the "o'clock". This of course could be automatic (where you are) or set by the programmer;
5) further, we have DateFormatter (which is a convenience that wraps up DateComponents), because 13th November 2017 could be represented as 13/11/17 or 11/13/17 depending on whether you are British or American. We may also wish to choose whether we use text or numeric months, and, if displaying times, whether we want 12 hour or 24 hour format - all of these are covered by DateFormatter, but text representation may be "13e Novembre 2017" if you are French, which introduces the notion of
6) Locale, which can be set, like TimeZone, as being default (as chosen when you set up the device) or specified by the programmer.
The code you posted won't work, because all it does is takes a Date, transforms it through a Calendar to DateComponents (all good so far), but then recreates a Date from the components - all you will get is the original Date - the same absolute point in time.
What I believe from the question and your answers to questions in the comments is that you want a function that takes an absolute time (eg "now") aka a Date and displays it in a specific TimeZone. This works:
func timeComponents(date: Date, timeZone: TimeZone) -> DateComponents {
var calendar = Calendar.current
calendar.timeZone = timeZone
return calendar.dateComponents([.hour, .minute, .month, .year, .day, .second, .weekOfMonth], from: date)
}
let absTime: Date = Date() // Now
let edinburgh = TimeZone(abbreviation: "GMT")!
let newYork = TimeZone(abbreviation: "EST")!
let ec = timeComponents(date: absTime, timeZone: edinburgh)
let nycc = timeComponents(date: absTime, timeZone: newYork)
print(ec)// year: 2017 month: 11 day: 14 hour: 0 minute: 44 second: 10 weekOfMonth: 3 isLeapMonth: false
print(nycc) // year: 2017 month: 11 day: 13 hour: 19 minute: 44 second: 10 weekOfMonth: 3 isLeapMonth: false
... which I think answers the minimum of your question, but to finesse it, we need to move from DateComponents to DateFormatter
func timeString(date: Date, timeZone: TimeZone, timeStyle: DateFormatter.Style) -> String {
let dateFormatter = DateFormatter()
dateFormatter.timeZone = timeZone
dateFormatter.dateStyle = .none
dateFormatter.timeStyle = timeStyle
return dateFormatter.string(from: date)
}
let es = timeString(date: absTime, timeZone: edinburgh, timeStyle: .full)
let nycs = timeString(date: absTime, timeZone: newYork, timeStyle: .full)
print(es) // 12:44:10 AM Greenwich Mean Time
print(nycs) // 7:44:10 PM Eastern Standard Time
You can go on, and start to use Locale, if you want to internationalise your app, but I'l leave that as an exercise!
p.s. These are not all of the concepts - see here
p.p.s. See also this answer and this answer (neither duplicates)
If you just want to format the date to a string, consider using a DateFormatter instead:
let date = Date()
let formatter = DateFormatter()
formatter.timeZone = TimeZone(identifier: "America/New_York")
formatter.dateStyle = .long
formatter.timeStyle = .long
formatter.string(from: date)
If you want to get the date components and process them, use the dateComponents(in:from:) method.
let components = Calendar.current.dateComponents(in: TimeZone(identifier: "America/New_York")!, from: date)
If you don't know the time zone of the place you are searching for, you can use the CoreLocation's CLGeocoder and search on an address string. Then you can get the timezone for that place and translate that into the time you're looking for:
let geocoder = CLGeocoder()
geocoder.geocodeAddressString("New York, New York") { (placemarks, error) in
guard error == nil else {
print("Error")
print(error!.localizedDescription)
return
}
guard let placemarks = placemarks,
let place = placemarks.first else {
print("No results")
return
}
if let timeZone = place.timeZone {
print("TimeZone: \(timeZone.identifier)")
// TimeZone: America/New_York
//Ignore the time zone offset from this one, it will be the difference between the current time and the new york time
let dateInNewYork = Date().addingTimeInterval(TimeInterval.init(timeZone.secondsFromGMT()))
print(dateInNewYork)
// 2017-11-13 15:03:05 +0000
//Or
let formatter = DateFormatter()
formatter.timeZone = TimeZone(identifier: timeZone.identifier)
formatter.dateStyle = .long
formatter.timeStyle = .long
let formattedDateInNewYork = formatter.string(from: Date())
print(formattedDateInNewYork)
// November 13, 2017 at 3:03:05 PM EST
//Or
let components = Calendar.current.dateComponents(in: TimeZone(identifier: timeZone.identifier)!, from: Date())
print(components.date!)
// 2017-11-13 20:03:05 +0000
}
}
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: