NSDateComponents weekOfYear returns wrong date - swift

so i am making a basic week picker, where you can pick a week number and year, and get the corresponding date from the weeks startpoint.
A basic test scenario
let calendar = NSCalendar.currentCalendar()
let components = NSDateComponents()
components.year = 2015
components.weekOfYear = 15
print(calendar.dateFromComponents(components)!)
You can see, that i set the wanted year to 2015 and the wanted week to 15, but the result i get is: "2014-12-31 23:00:00 +0000"
... and thats really gets me hanging. No matter what week and year i choose, it will always be off and in december.
Thank you in advance for your help.

weekOfYear works in conjunction with yearForWeekOfYear:
let calendar = NSCalendar.currentCalendar()
let components = NSDateComponents()
components.yearForWeekOfYear = 2015
components.weekOfYear = 15
print(calendar.dateFromComponents(components)!)
// 2015-04-05 22:00:00 +0000
Update for Swift 3 and later:
let calendar = Calendar.current
var components = DateComponents()
components.yearForWeekOfYear = 2015
components.weekOfYear = 15
print(calendar.date(from: components)!)

Related

Is there a way to get the exact number of weeks in a given year?

Is there a way to get the exact number of weeks in a given year in Swift (e.g. 52 or 53)?
In my region (week starts with Monday, min 4 days in first week), we get 52 weeks (e.g. 2019, 2021-2025) or 53 weeks (e.g. 2020, 2026). That's my basic for the background of my question.
You can get number of weeks by combining Calendar and DateComponents like this:
let year = 2023
let calendar = Calendar.current
let dateComponents = DateComponents(calendar: calendar, year: year)
let date = calendar.date(from: dateComponents)!
let range = calendar.range(of: .weekOfYear, in: .year, for: date)!
let numberOfWeeks = range.count

Swift - Problem converting year + weekOfYear components to Date [duplicate]

When I run this code:
let calendar = Calendar.current
var dateComponents = DateComponents()
dateComponents.weekday = calendar.firstWeekday
dateComponents.weekOfYear = 2
dateComponents.year = 2017
let startOfWeek = calendar.date(from: dateComponents)
let endOfWeek = calendar.date(byAdding: .day, value: 6, to: startOfWeek!)
let formatter = DateFormatter()
formatter.dateStyle = .short
print(formatter.string(from: startOfWeek!))
print(formatter.string(from: endOfWeek!))
It prints this:
1/8/17
1/14/17
When I change the code to this:
dateComponents.weekOfYear = 1
dateComponents.year = 2017
It prints this:
12/31/17
1/6/18
Why is it 12/31/17?
When I use .full style to print the dates, I get Sunday, December 31, 2017 for the first date, but it's obviously wrong because December 31 is a Thursday.
If you want to get the correct date, use yearForWeekOfYear instead of year. Docs:
You can use the yearForWeekOfYear property with the weekOfYear and weekday properties to get the date corresponding to a particular weekday of a given week of a year. For example, the 6th day of the 53rd week of the year 2005 (ISO 2005-W53-6) corresponds to Sat 1 January 2005 on the Gregorian calendar.
Alternative, you can be a little naughty and not listen to the docs and use weekOfYear = 54:
let calendar = Calendar.current
var dateComponents = DateComponents()
dateComponents.weekday = calendar.firstWeekday
dateComponents.weekOfYear = 54
dateComponents.year = 2017
let startOfWeek = calendar.date(from: dateComponents)
let endOfWeek = calendar.date(byAdding: .day, value: 6, to: startOfWeek!)
let formatter = DateFormatter()
formatter.dateStyle = .short
print(formatter.string(from: startOfWeek!))
print(formatter.string(from: endOfWeek!))
This prints:
1/1/17
1/7/17
which is coincidentally, the correct dates.

Xcode playground prints wrong date value despite showing the correct value in the sidebar

After creating DateComponents() and fetching the Calendar.current and the Date(), I altered some date components and created a new date variable based on the altered components. The value of this variable displays correctly in the sidebar on Xcode playground but when I print this value it shows the wrong date. The same happens in the app code too (i.e. not just in the Playground).
// Initial set-up
var dateComponents = DateComponents()
let date = Date()
let calendar = Calendar.current
// Getting some components
var month = calendar.component(.month, from: date)
var year = calendar.component(.year, from: date)
var day = calendar.component(.day, from: date)
// Some sample code
if day > 15 {
day = 1
if month != 12 {
month += 1
} else {
month == 1
}
} else {
day = 15
}
// Create date from components
// Generic
dateComponents.hour = 00
dateComponents.minute = 00
// Specific
dateComponents.day = day
dateComponents.month = month
dateComponents.year = year
// Finalise the next resetDate
let resetDate = calendar.date(from: dateComponents)
resetDate // Displays correct value in the side bar
print(resetDate!) // Prints wrong value
The expected result of course is that the value of resetDate in both the last two lines should agree. On 31 August 2019 this is what I get in the sidebar:
"Sep 1, 2019 at 12:00 AM"
versus what the print statement displays:
2019-08-31 18:30:00 +0000
Output is correct. Please note that print date object direct alway received result on timezone +0 UTC. In your brain, just plus with your timezone is time in your timezone.

Swift - Date from components is incorrect when printed. Date has 2 values?

I'm trying to store a time of day in a Date:
let calendar = NSCalendar.init(identifier: .gregorian)
let components = NSDateComponents()
components.hour = 7
components.minute = 0
var newDate = calendar?.date(from: components as DateComponents)!
print(newDate!)
However, this yields some bizarre results when I try to print or otherwise use the value. Here's a Swift Playground of the results:
How can newDate be both 7:00AM and 11:56AM at the same time?
You didn't specify a time zone (by setting the timeZone property of either components or calendar). So the system used your local time zone to convert components to a date.
The playground system used your local time zone to convert newDate to the string “Jan 1, 1 at 7:00 AM”. (The playground system has special-case code for displaying Date objects. The special-case code uses your local time zone.)
The print function used the UTC time zone to convert newDate to the string “0001-01-01 11:56:02 +0000”. (The print function uses the CustomStringConvertible protocol. Date's CustomStringConvertible implementation uses the UTC time zone.)
I deduce that your local time zone is US/Eastern time, also known as America/New_York:
import Foundation
var calendar = Calendar(identifier: .gregorian)
let components = NSDateComponents()
components.hour = 7
components.minute = 0
for timeZoneIdentifier in TimeZone.knownTimeZoneIdentifiers {
calendar.timeZone = TimeZone(identifier: timeZoneIdentifier)!
let date = calendar.date(from: components as DateComponents)!
let dateString = "\(date)"
if dateString == "0001-01-01 11:56:02 +0000" {
print("\(timeZoneIdentifier) \(date)")
}
}
// Only one line of output: America/New_York 0001-01-01 11:56:02 +0000
So why the weird minutes and seconds in UTC? Because at noon on November 18, 1883, the US and Canada railway companies began using a new, standard time system, which is the time system we still use today. Observe:
import Foundation
let formatter = ISO8601DateFormatter()
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = TimeZone(identifier: "America/New_York")!
let components = NSDateComponents()
components.year = 1883
components.month = 11
components.day = 18
components.hour = 11
print(formatter.string(from: calendar.date(from: components as DateComponents)!))
// prints 1883-11-18T15:56:02Z
components.hour = 12
print(formatter.string(from: calendar.date(from: components as DateComponents)!))
// prints 1883-11-18T17:00:00Z
Prior to noon, the difference from UTC time is 4:56:02.
Before the advent of standard railway time, we typically defined local time based on local apparent noon (the moment when the sun is highest in the sky and shadows point exactly north or south or disappear entirely if the sun is directly overhead).
If we look at the definition of America/New_York in the tz Time Zone Database, we find this:
# From Paul Eggert (2014-09-06):
# Monthly Notices of the Royal Astronomical Society 44, 4 (1884-02-08), 208
# says that New York City Hall time was 3 minutes 58.4 seconds fast of
# Eastern time (i.e., -4:56:01.6) just before the 1883 switch. Round to the
# nearest second.
and a bit further down:
Zone America/New_York -4:56:02 - LMT 1883 Nov 18 12:03:58
This explains the difference we see above prior to noon on November 18, 1883.

Swift: unexpected behavior when setting DateComponents year

The example code below gets DateComponents from the current Date, modifies the components, and creates a new Date from the modified components. It also shows creating a new DateComponents object, filling it out, and then creating a new Date from that.
import Foundation
let utcHourOffset = -7.0
let tz = TimeZone(secondsFromGMT: Int(utcHourOffset*60.0*60.0))!
let calendar = Calendar(identifier: .gregorian)
var now = calendar.dateComponents(in: tz, from: Date())
// Get and display current date
print("\nCurrent Date:")
print("\(now.month!)/\(now.day!)/\(now.year!) \(now.hour!):\(now.minute!):\(now.second!) \(now.timeZone!)")
let curDate = calendar.date(from: now)
print("\(curDate!)")
// Modify and display current date
now.year = 2010
now.month = 2
now.day = 24
now.minute = 0
print("\nModified Date:")
print("\(now.month!)/\(now.day!)/\(now.year!) \(now.hour!):\(now.minute!):\(now.second!) \(now.timeZone!)")
let modDate = calendar.date(from: now)
print("\(modDate!)")
// Create completely new date
var dc = DateComponents()
dc.year = 2014
dc.month = 12
dc.day = 25
dc.hour = 10
dc.minute = 12
dc.second = 34
print("\nNew Date:")
print("\(dc.month!)/\(dc.day!)/\(dc.year!) \(dc.hour!):\(dc.minute!):\(dc.second!) \(now.timeZone!)")
let newDate = calendar.date(from: dc)
print("\(newDate!)")
In the case where I modify the components, setting different year, month, day, etc., then use the components to get a date, I get the unexpected result that the new date has all the modified components except the year, which remains unchanged.
In the case where I create a DateComponents object and fill it out then create a Date from it, it works as expected.
The output of the code is shown below:
Current Date:
3/9/2017 19:5:30 GMT-0700 (fixed)
2017-03-10 02:05:30 +0000
Modified Date:
2/24/2010 19:0:30 GMT-0700 (fixed)
2017-02-25 02:00:30 +0000
New Date:
12/25/2014 10:12:34 GMT-0700 (fixed)
2014-12-25 17:12:34 +0000
I expected the modified Date to be 2010-02-25 02:00:30 +0000 rather than 2017-02-25 02:00:30 +0000. Why isn't it? Why does it work in the second case?
The docs for DateComponents say: "An instance of NSDateComponents is not responsible for answering questions about a date beyond the information with which it was initialized...". Being that the DateComponents object was initialized with a year, it doesn't seem like this would apply, but it's the only thing I saw in the docs that might explain the behavior I observe.
If you log now and dc you will see the problem. now is being created from a Date. This fills in all of the date components including yearForWeekOfYear and several of the weekday related components. These components are causing modDate to come out incorrectly.
newDate works as expected because only the specific components are being set.
You can get modDate to come out correctly if you reset some of the extra components. Specifically, adding:
now.yearForWeekOfYear = nil
just before creating modDate will result in the expected date for modDate. Of course the best solution is to create a new instance of DateComponents and use specific values from the previous DateComponents as needed:
let mod = DateComponents()
mod.timeZone = now.timeZone
mod.year = 2010
mod.month = 2
mod.day = 24
mod.hour = now.hour
mod.minute = 0
mod.second = now.second
print("\nModified Date:")
print("\(mod.month!)/\(mod.day!)/\(mod.year!) \(mod.hour!):\(mod.minute!):\(mod.second!) \(mod.timeZone!)")
let modDate = calendar.date(from: mod)
print("\(modDate!)")