week range in swift with weekYear - swift

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

Related

Initialising DateComponents with weekOfMonth returning wrong date

Here is the code:
let components = DateComponents(year: 2022,month: 9,weekOfMonth: 3)
let date = Calendar.autoupdatingCurrent.date(from: components)!
let formatter = DateFormatter()
formatter.dateStyle = .long
formatter.timeStyle = .none
print(formatter.string(from: date))
Here is output:
September 1, 2022
But the correct answer should be September 11, 2022. Why? Is this a bug or I just missed something?
This method will create a date for start of the given year, month and week in month. This will return nil if the start of the week is not in the month specified:
func startOfWeek(in year: Int, month: Int, week: Int, calendar: Calendar = Calendar(identifier: .iso8601)) -> Date?{
//You would need to set this calendar according when your week starts.
//For me it´s monday so I need .iso8601
//For sunday set it to gregorian
var components = DateComponents(calendar: calendar, year: year, month: month, weekOfMonth: week )
//Get the first day of the month. This will probably never fail but....
guard let firstOfMonth = components.date else{
return nil
}
// Handling edge case first of month is start of week
// if the first is a monday and we look for the first return it
let startOfWeek = calendar.dateComponents([.calendar, .yearForWeekOfYear, .weekOfYear], from: firstOfMonth).date
if startOfWeek == firstOfMonth, week == 1{
return firstOfMonth
}
// else just ommit the additional week and move on
else if startOfWeek == firstOfMonth {
components = DateComponents(calendar: calendar, year: year, month: month, weekOfMonth: week)
}
// search for the next date starting at first of month according to components given
return calendar.nextDate(after: firstOfMonth, matching: components, matchingPolicy: .nextTime)
}
Most of the code should be self explanatory.
Testing:
let dates = [(2022, 1, 2), (2022, 2, 1), (2022, 3, 4), (2022, 8, 1), (2022, 9, 1), (2022, 9, 2), (2022, 9, 3), (2022, 9, 4), (2022, 12, 4) , (2021, 11, 5)]
let formatter = DateFormatter()
formatter.dateFormat = "dd MM yyyy"
dates.forEach { year, month, week in
let date = startOfWeek(in: year, month: month, week: week, calendar: Calendar(identifier: .gregorian))
print(date != nil ? formatter.string(from: date!) : "no date found")
}
Output:
02 01 2022
no date found
20 03 2022
no date found
no date found
04 09 2022
11 09 2022 <-- thrid week in september 2022
18 09 2022
18 12 2022
28 11 2021

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.

ISO8601dateformatter 1970-01-01 00:00:00 issue

How to deal with the new year and ISO8601 returning last year as year component.
To my horror, I realized ISO8601DateFormatter was returning 1977 as a year to the 1978-01-01 00:00:00
It took a while to realize this. That turned out is not wrong. Nonetheless, given the specific year of 1978, for the formatted to return 1977 is shocking.
I don't even need the timestamp. How can I reliably retrieve the specified year without having to add a second to every calendar date?
import Foundation
let datestring = "1978/1/1"
var formatter = ISO8601DateFormatter()
formatter.timeZone = TimeZone(abbreviation: "UTC")
formatter.formatOptions = [.withFullDate]
let date2 = formatter.date(from: datestring) ?? Date()
print(date2)
var calendar = Calendar(identifier: .iso8601)
var year = calendar.component(.year, from: date2)
var month = calendar.component(.month, from: date2)
var day = calendar.component(.day, from: date2)
var era = calendar.component(.era, from: date2)
print("year \(year) month \(month) day \(day) era: \(era)")
===
1978-01-01 00:00:00 +0000
year 1977 month 12 day 31 era: 1
By default the Calendar instance will have your local timeZone. You can see this by printing print(calendar.timeZone.abbreviation() ?? "UNKNOWN"). In my case (in Seattle, WA, USA) it prints "PDT". If you simply set your calendar timezone to UTC it prints exactly what you expect:
year 1978 month 1 day 1 era: 1

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)")

Subtracting 2 Dates using DateComponents gives undefined behavior

Given the following method I added to the Date object in an extension:
extension Date {
static func -(left: Date, right: Date) -> DateComponents {
let components: Set<Calendar.Component> = [
.year,
.month,
.day,
.hour,
.minute,
.second,
//.weekOfYear, // issue!
]
let gregorian = Calendar(identifier: .gregorian)
let dateComponents = gregorian.dateComponents(components, from: right, to: left)
return dateComponents
}
}
And given the following dates (format is Month Day, Year, hh:mm:ss) you get 1:
let day1 = Date(timeIntervalSince1970: 1516003200) // January 15, 2018, 00:00:00
let day2 = Date(timeIntervalSince1970: 1516089600) // January 16, 2018, 00:00:00
let diff = day2 - day1
print(diff.day!) // 1
But given the following dates, you also get 1:
let day3 = Date(timeIntervalSince1970: 1515916800) // January 14, 2018, 00:00:00
let day4 = Date(timeIntervalSince1970: 1516089600) // January 16, 2018, 00:00:00
let diff2 = day4 - day3
print(diff.day!) // 1
So my first question is wondering why the day difference is the same even though one of the pair is 1 day apart and the other is 2 days apart.
Finally, given the following dates, you get the correct number of 14:
let day5 = Date(timeIntervalSince1970: 1516089600) // January 16, 2018, 00:00:00
let day6 = Date(timeIntervalSince1970: 1517299200) // January 30, 2018, 00:00:00
let diff3 = day6 - day5
print(diff3.day!)
But if you were to go back to static func -(left:right:) and uncomment .weekOfYear and re run the above block of code with day5 and day6 you get 0 days. So my second question is why I'm getting 0 days if I add the .weekOfYear component to the Set.
I included two screenshots of a playground below, first one with .weekOfYear not included and the second one including .weekOfYear:
In the following code
let day3 = Date(timeIntervalSince1970: 1515916800) // January 14, 2018, 00:00:00
let day4 = Date(timeIntervalSince1970: 1516089600) // January 16, 2018, 00:00:00
let diff2 = day4 - day3
print(diff.day!) // 1
you are using diff.day when in fact you should be using diff2.day
As for your second issue, the calendar will return date components by assigning from the maximum to minimum. That is to say that when you add weekOfYear, it assigns a value of 2 to weekOfYear. If you were to print out diff3.weekOfYear, you will get 2 (because the dates come up to be 2 weeks). Since there are no days left behind after the date is converted into weeks, you get 0 days for diff3.day. If you were to use the dates Jan 16th and Jan 31st, you would get a value of 2 for week and 1 for day