Why is Calendar.date(from: DateComponents) adding time? - swift

I create an instance of DateComponents using the following code:
let dateComponents = DateComponents(
calendar: .current,
timeZone: Calendar.current.timeZone,
era: nil,
year: nil,
month: nil,
day: nil,
hour: 9,
minute: 0,
second: 0,
nanosecond: 0,
weekday: 2,
weekdayOrdinal: nil,
quarter: nil,
weekOfMonth: nil,
weekOfYear: nil,
yearForWeekOfYear: nil)
I then print the dateComponents object and get the following (expected) output:
calendar: gregorian (current) timeZone: Europe/London (current) hour: 9 minute: 0 second: 0 nanosecond: 0 weekday: 2 isLeapMonth: false
Immediately following this, I print the date created using the following code:
print(Calendar.current.date(from: dateComponents)!)
To my great dismay and thorough unhappiness, the following is outputted:
0001-01-01 09:01:15 +0000
The date(from: dateComponents) function appears to have added just over a minute to the dateComponents before creating a date from them.
Thanks for any help in advance.

NSDate has some strange and undocumented behaviors for ancient dates. The change seems to have happened around 1895:
for year in 1890..<1900 {
// January 1 of each year # 9AM
let dateComponents = DateComponents(
calendar: .current,
timeZone: Calendar.current.timeZone,
year: year,
month: 1,
day: 1,
hour: 9)
if dateComponents.isValidDate {
print(dateComponents.date!)
}
}
My calendar is Gregorian and timezone is EDT (UTC -0500). This is the output:
1890-01-01 14:17:32 +0000
1891-01-01 14:17:32 +0000
1892-01-01 14:17:32 +0000
1893-01-01 14:17:32 +0000
1894-01-01 14:17:32 +0000 // not correct
1895-01-01 14:00:00 +0000 // correct
1896-01-01 14:00:00 +0000
1897-01-01 14:00:00 +0000
1898-01-01 14:00:00 +0000
1899-01-01 14:00:00 +0000
So for the years prior to 1895, Apple somehow added 17 minutes and 32 second to my time. You got a different offset, which is likely due your locale settings.
I couldn't find anything historical event about the Gregorian calendar in 1895. This question mentions that Britain started to switch over to GMT and the Greenwich Observatory started adjusting date/time standards across the British Isles in the 1890s so that may have accounted for this offset. Perhaps someone can delve into the source code for Date / NSDate and figure it out?
If you want to use DateComponent to store a repeating schedule, use nextDate(after:matching:matchingPolicy:) to find the next occurance of your schedule:
let dateComponents = DateComponents(calendar: .current, timeZone: .current, hour: 9, weekday: 2)
// 9AM of the next Monday
let nextOccurance = Calendar.current.nextDate(after: Date(), matching: dateComponents, matchingPolicy: .nextTime)!

Related

Swift date components incorrect return of date day number

I need to obtain a date from some variable values
So I specify year, month and day and I need a Date as return
Doing the following works except for the day because it return the day input - 1
let todayDate: Date = Calendar.current.startOfDay(for: Date.from(year: 2022, month: 09, day: 05)!)
print("today date = \(todayDate)")
extension Date {
static func from(year: Int, month: Int, day: Int) -> Date? {
let calendar = Calendar(identifier: .gregorian)
var dateComponents = DateComponents()
dateComponents.year = year
dateComponents.month = month
dateComponents.day = day
return calendar.date(from: dateComponents) ?? nil
}
}
And the output is
today date = 2022-09-04 22:00:00 +0000
Date and time can be a bit tricky. The Date struct stores a point in time relative to GMT. If you print it it will show exactly that.
Solution:
DonĀ“t use print, use a proper Dateformatter. To illustrate what I mean use this in a playground:
let date = Calendar.current.startOfDay(for: Date())
print(date)
//2022-09-03 22:00:00 +0000
// when it is 4.th of september 00:00 in my timezone (+- Daylight saving) it is this time in GMT
let formatter = DateFormatter()
formatter.dateFormat = "dd MM yyyy HH:mm:ss"
print(formatter.string(from: date))
//04 09 2022 00:00:00
// this is the time in my timezone
So the issue here is not that it has the wrong time, it is just not presented in the correct time zone.

week range in swift with weekYear

Could you help me, I don't know what configuration I'm missing
I want to obtain the week range for the year 2021, week 37 in Mexico that starts from Monday to Sunday but it seems that my code brings another week number and also starts from Sunday to Monday
The week in Mexico starts from
Week 37 September 13, 2021 September 19, 2021
var weekNumber: Int = 0
var calendar = Calendar(identifier: .gregorian)
calendar.firstWeekday = 2
calendar.minimumDaysInFirstWeek = 4
let year = calendar.component(.year, from: Date())
weekNumber = calendar.component(.weekOfYear, from: Date())
print("weekNumber",weekNumber)
print("Date",Date())
let startComponents = DateComponents(weekOfYear: weekNumber, yearForWeekOfYear: year)
let startDate = calendar.date(from: startComponents)!
let endComponents = DateComponents(day:7, second: 0)
let endDate = calendar.date(byAdding: endComponents, to: startDate)!
print("startDate",startDate)
print("endDate",endDate)
You are using the wrong calendar. The first weekday of Gregorian calendar is Sunday, what you need is to use ISO8601 calendar. Also when using weekOfYear you would use yearForWeekOfYear:
let now = Date()
var calendar = Calendar(identifier: .iso8601)
let yearForWeekOfYear = calendar.component(.yearForWeekOfYear, from: now)
let weekNumber = calendar.component(.weekOfYear, from: now)
print("weekNumber", weekNumber)
print("Date", now.description(with: .current))
let startDate = DateComponents(calendar: calendar, weekOfYear: weekNumber, yearForWeekOfYear: yearForWeekOfYear).date!
let endDate = calendar.date(byAdding: .weekOfYear, value: 1, to: startDate)!
print("startDate", startDate.description(with: .current))
print("endDate", endDate.description(with: .current))
This will print:
weekNumber 37
Date Sunday, September 19, 2021 at 9:07:02 PM Brasilia Standard Time
startDate Monday, September 13, 2021 at 12:00:00 AM Brasilia Standard Time
endDate Monday, September 20, 2021 at 12:00:00 AM Brasilia Standard Time

GMT to local time conversion ERROR with swift UIKIT

Does anyone have an answer to why I am getting -8 hours instead of -7 (my timezone) hours difference upon converting time from GMT to local i using the formula below:
print("TIMEZONE IN HOURS: \(timeZone/3600)")
let interval = Date(timeIntervalSinceReferenceDate: 93866.4142533315)
print("GMT TIME: \(interval)")
let intervalDateComponents = calendar.dateComponents([.hour, .minute, .second], from: interval)
let mHour = intervalDateComponents.hour ?? 0
let mMinute = intervalDateComponents.minute ?? 0
print("INTERVAL DATE COMPONENT: \(intervalDateComponents)")
let formatter = DateFormatter()
formatter.timeZone = TimeZone.current
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let intervalDateString = formatter.string(from: interval)
print("DATE STRING FORMAT: \(intervalDateString)")
prints out the following: (please pay attention to date)
TIMEZONE IN HOURS: -7
GMT TIME: 2001-01-02 02:04:26 +0000 (January 2)
INTERVAL DATE COMPONENT: hour: 18 minute: 4 second: 26 isLeapMonth: false
DATE STRING FORMAT: 2001-01-01 18:04:26 (January 1)
interval HOURS: 18
edit/update:
let mDateString = formatter.string(from: interval)
print("DATE STRING FORMAT: (mDateString)")
let mdate = formatter.date(from: mDateString)
print("MDATE: (mdate)")
let mDateComponents = calendar.dateComponents([.hour, .minute, .second], from: mdate!)
print("M DATE COMPONENT: (mDateComponents)")
prints out:
DATE STRING FORMAT: 2001-01-01 19:01:27 -0700
MDATE: Optional(2001-01-02 02:01:27 +0000)
M DATE COMPONENT: hour: 18 minute: 1 second: 27 isLeapMonth: false
If you notice above in the code my intention is to extract the local time component to trigger alarm at a later time daily. So I fed the string that shows
2001-01-01 19:01:27 -0700
I converted the string to mdate in order to get time components that I need to use for the alarm to work on time so I got (hour: 18) rather than hour: 19 which I used to generate the mdate string.
The problem there is that you are getting the timezone offset for today. You need to get the timezone offset for the same date you are using:
let timeZone = TimeZone.current.secondsFromGMT(for: interval)
print("TIMEZONE IN HOURS: \(timeZone/3600)")

Swift Calendar.current.nextDate timeZone issues

I am trying to get a Date object for the next occurring future time where the hour in UTC time is 18. However my code doesn't work as expected. I have the following:
let dateComponents = DateComponents(timeZone: TimeZone(abbreviation: "GMT"), hour: 18)
let date = Calendar.current.nextDate(after: Date(), matching: dateComponents, matchingPolicy: .nextTime)
print(date)
The problem is that this results in 2019-02-09 23:00:00 +0000
The date is for the next occurring time where the hour is 18 in EST.
I would have expected, since the the dateComponents has the timezone set to UTC and the hour to 18, that the date would be 2019-02-09 18:00:00 +0000. Furthermore, changing the timezone seems to have no effect on the nextDate found.
Why doesn't the nextDate function respect the timezone set in the dateComponents passed to it?
It looks like the timezone in DateComponents is ignored.
However when you set the timezone in a new calendar you get correct results.
let dateComponents = DateComponents(hour: 18)
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = TimeZone(secondsFromGMT: 0)!
let date = calendar.nextDate(after: Date(), matching: dateComponents, matchingPolicy: .nextTime)
print(date) // Optional(2020-09-29 18:00:00 +0000)
I live in GMT time, and running this in a playground produces the expected result you are looking for. You are setting the time zone of your date component, but I would imagine your own calendar (Calendar.current) is set to EST. You would need to account for the offset in EST vs GMT for your required result

How to prevent DateComponents from updating for time zone

I am trying to get date components from a date value from an API endpoint. I need to preserve the date values as they are already adjusted for time zone. Unfortunately, I have no control over how the API returns a date value.
When I get the date value (sessionTime) from the API, it is returned as follows:
2017-12-05 08:00:00 +0000
I need to set up a local notification based on that time, however, when I try to extract components from that date object with the following code:
let notifyTime = Calendar.current.dateComponents(
[.year, .month, .day, .hour, .minute, .second], from: sessionTime)
I get this:
year: 2017 month: 12 day: 5 hour: 2 minute: 0 second: 0 isLeapMonth: false
I am six hours from GMT so it is obvious what is going on, but I wish I could prevent it and extract the date components exactly as they are. The desired output would be:
year: 2017 month: 12 day: 5 hour: 8 minute: 0 second: 0 isLeapMonth: false
Can anyone help? Thanks!
You can specify the timeZone that DateComponents uses.
Like this:
let notifyTime = Calendar.current.dateComponents(in: TimeZone.current, from: sessionTime)