I'm trying to create a function that will get the Gregorian start and end dates for a month passed in of any arbitrary calendar.
So if I pass Hebrew calendar, month 3, year 5775, I would expect back something like this (2015-05-19T00:00:00.000-05:00, 2015-06-17T00:00:00.000-05:00)
let (start,end) = gregorianMonthForCalendarMonth(NSCalendar(calendarIdentifier: NSCalendarIdentifierHebrew)!, 3, 5775)
println(start)
println(end)
What I'm getting is (2014-11-23T00:00:00.000-05:00, 2014-12-22T00:00:00.000-05:00)
Not sure what I'm doing wrong.
My functions:
let fullDateFormatter = getFullDateFormatter()
func getFullDateFormatter() -> NSDateFormatter {
let _dateFormatter = NSDateFormatter()
_dateFormatter.calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)
_dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'.'SSSZZZZZ"
return _dateFormatter
}
func gregorianMonthForCalendarMonth(calendar:NSCalendar, month:Int, year:Int) -> (start:String, end:String) {
// get 1st day of month
let components = NSDateComponents()
components.year = year
components.month = month
components.day = 1
let start = calendar.dateFromComponents(components)
// get number of days in the month
let days = calendar.rangeOfUnit(NSCalendarUnit.CalendarUnitDay, inUnit: NSCalendarUnit.CalendarUnitMonth, forDate: start!)
components.day = days.length
let end = calendar.dateFromComponents(components)
return (start!.stringValue(),end!.stringValue())
}
extension NSDate {
func stringValue() -> String {
let strDate = fullDateFormatter.stringFromDate(self)
return strDate
}
}
The problem would appear to stem from the interpretation of what the "third" month of 5775 is. Kislev is the 3rd month of the civil year. And 1 Kislev 5775 is 23 November 2014.
However, Silvan is the 3rd month of the ecclesiastical year and 1 Silvan 5775 is 19 May 2015.
Apple's choice of the civil year month numbering is somewhat understandable as the Hebrew numeric year value increments on Tishrei (the first month of the civil year, the seventh month of the ecclesiastical year).
Admittedly, many Hebrew date conversion sites list the months in the order they occur in the ecclesiastical year, but if you dive into those sites, you'll see that they accurately reflect the year number changing on the first of Tishrei. I've noticed that many of these sites don't ask for numeric month number, but rather let you select from a list of month names, eliminating the sort of confusion that simply referring to the "third" month might otherwise entail.
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 need one function where i will pass any week number and year, that function should return dates for that week. Like if I pass week 2 and year 2016 then function should return me 3 January to 9 January.
Just a very simple solution using NSCalendar functionality:
let week = NSDateComponents()
week.yearForWeekOfYear = 2016 // the year
week.weekOfYear = 10 // week index
let calendar = NSCalendar.currentCalendar()
// start of week or nil if the week does not exist
let weekStart = calendar.dateFromComponents(week)
// add 1 week to start (this is essentially the start of the next week)
let weekEnd = calendar.dateByAddingUnit(.WeekOfYear, value: 1, toDate: weekStart!, options: [])
print(weekStart)
print(weekEnd)
I have a scenario where I'm getting a date string as "46-05-24" (yy-mm-dd), and I need to re-format the date as "1946-05-24". The NSDateFormatter interprets the string as "2046-05-24".
I'm using this code:
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yy-mm-dd"
let gmt : NSTimeZone = NSTimeZone(abbreviation: "GMT")!
dateFormatter.timeZone = gmt
let dateFromString = dateFormatter.dateFromString(date as String)
dateFormatter.dateFormat = "yyyy-mm-dd"
if dateFromString != nil{
let dateString = dateFormatter.stringFromDate(dateFromString!)
print(dateString)
}
Is there something I'm missing here?
It happens because if you're giving NSDateFormatter a two-digit year, it needs to decide what century that year is in. It does this using its twoDigitStartDate property, which sets the earliest date that a two-digit year can represent. It has a default value of December 31, 1949. A date in 46 falls on the low side of 50 so it gets treated as 2046.
You can change the value of twoDigitStartDate to adjust the results. For example, you could set it to a date exactly 100 years in the past. That would mean that any two-digit year would be interpreted as the most recent year with those two digits:
let oneCenturyAgo = NSCalendar.currentCalendar().dateByAddingUnit(NSCalendarUnit.Year, value: -100, toDate: NSDate(), options: NSCalendarOptions(rawValue:0))
dateFormatter.twoDigitStartDate = oneCenturyAgo
Of course if you get someone whose date is over 100 years ago, there's no good way for your code to know which year is appropriate. If the year is "10", was that person born in 1910 or 2010? You have no way of knowing, and all your code can do is make the best guess.
The cause of your problem is that your data omits some relevant information: the century. Using only the last two digits you as the dev have to decide which century the dates are in. If all of them are in the 20th century (1900 - 1999) you can use the following approach:
You could simply prepend 19 before the string to parse. Assuming date is the string that you want to parse you can use
let dateFromString = dateFormatter.dateFromString("19" + (date as String))
instead of your
let dateFromString = dateFormatter.dateFromString(date as String)
If the dates you are going to handle are in the 20th and the 21st century you are going to have a bad time because what year is 05 supposed to reflect? 1905? 2005? 2105?
I am making a TimeTable app, and i have a method that adds 1 week to the current date, this works as it is supposed to, however if the week transitions from December to January, it adds 1 day extra.
Here is my code:
func getWeekDates(var date: NSDate) -> [NSDate] {
var dates: [NSDate] = [NSDate]()
for var i = 0; i < 5; i++ {
date = date.dateAtWeekStart() + 1.day - 1.week
date += i.day
dates.append(date)
}
return dates
}
And dateAtWeekStart():
func dateAtWeekStart() -> NSDate {
let flags : NSCalendarUnit = [NSCalendarUnit.Year,NSCalendarUnit.Month ,
NSCalendarUnit.WeekOfYear,
NSCalendarUnit.Weekday]
let components = NSCalendar.currentCalendar().components(flags, fromDate: self)
components.weekday = 1 // Sunday
components.hour = self.hour
components.minute = self.minute
components.second = self.second
return NSCalendar.currentCalendar().dateFromComponents(components)!
}
(dateAtWeekStart() is a function made in an extension to NSDate)
The reason i am adding 1 day and removing 1 week, is because dateAtWeekStart returns next sunday, so for example 08-10-2015.dateAtWeekStart() returns 11-10-2015.
So this works fine normally, however if we take this year as an example, 29-12-2015.dateAtWeekStart() returns 04-01-2015 instead of 03-01-2016.
By the way, the region on the device is set to Denmark.
dateAtWeekStart, comes from a helper class called SwiftDate made by malcommac: https://github.com/malcommac/SwiftDate
UPDATE EDIT:
I am still having trouble figuring out how to fix this, i tried adding year to components like so: components.year = self.year, but it sets the year to 2014 for some reason when returning the components..
That dateAtWeekStart() method simply does not work.
[.YearForWeekOfYear, .WeekOfYear] are sufficient as calendar units to
determine the (start of a) week uniquely. The additional units can make
the calculation undetermined. Also you can not just set
components.weekday = 1 because in some regions Monday (2) is the first
day of the week.
So it is actually a bit easier:
extension NSDate {
func dateAtWeekStart() -> NSDate {
let cal = NSCalendar.currentCalendar()
// cal.firstWeekday = 1 // If you insist on Sunday being the first day of the week.
let flags : NSCalendarUnit = [.YearForWeekOfYear, .WeekOfYear]
let components = cal.components(flags, fromDate: self)
return cal.dateFromComponents(components)!
}
}
This should work in all cases and give the start of the week (at midnight) for the given date. There are also other methods
one could use, such as rangeOfUnit().
If you want Sunday to be considered as the first day of the week
instead of using the user's regional settings
then you have to set the firstWeekday property of the calendar.
The code to add days or weeks to a date also looks highly suspicious.
The extensions method for Int in the SwiftDate project treats
a day as 24*60*60 seconds. This is not correct, because in regions with
daylight saving times, a day can have 23 or 25 hours when the clocks
are adjusted. The correct way to add one week to a date is to
use calendar components again:
date = cal.dateByAddingUnit(.WeekOfYear, value: 1, toDate: date, options: [])!
Update for Swift 3:
extension Date {
func dateAtWeekStart() -> Date {
var cal = Calendar.current
// cal.firstWeekday = 1 // If you insist on Sunday being the first day of the week.
let components = cal.dateComponents([.yearForWeekOfYear, .weekOfYear], from: self)
return cal.date(from: components)!
}
}
I am querying a database for values between a startDate and an endDate. I am therefore looking for a neat way to get the NSDate for the end of the day for a date. Just like you can get startOfDayForDate().
I guess you could do :
let cal = NSCalendar.currentCalendar()
let startOfDay = cal.startOfDayForDate(NSDate())
let aDay:NSTimeInterval = 60*60*23 + 60*59 + 59
let endofDay = startOfDay.dateByAddingTimeInterval(aDay)
Is adding component.day + 1 to startOfDayForDate the correct method to get the "end of the day" for a date, or is there a better method?
A better way would be to get the start of the next day and subtract 0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001 second.
Do you get what I want to say? It's very hard to define the end of the day. 23:59 is certainly not the end of the day, there is almost a whole minute left until the next day. And even 23:59:59 is not the end of a day. Because there is an infinite amount of fraction seconds between this time and the start of the next day. As far as I know NSDate supports nanoseconds out of the box, so in the current implementation there are at least 1 000 000 000 possible NSDates between 23:59:59 and the next day.
That's why you should see if you can find a way to use the start of the next day.
For example, instead of if (startOfDay <= date && date <= endOfDay) you could use if (startOfDay <= date && date < startOfNextDay).
To calculate the start of the next day you have to use NSCalendar. In iOS8 Apple added a couple of nice methods to make these calculations short:
let calendar = NSCalendar.currentCalendar()
let startOfDay = calendar.startOfDayForDate(NSDate())
let startOfNextDay = calendar.dateByAddingUnit(.CalendarUnitDay, value: 1, toDate: startOfDay, options: nil)!
EDIT: Since you now state that you want to query a database you don't need to find the end of the day. Check if the date is on or after the start of the day, and before the start of the next day.
Try this:
var endOfDay: Date? {
var components = DateComponents()
components.day = 1
components.second = -1
var calendar = Calendar.current
calendar.timeZone = TimeZone(abbreviation: "GMT")!
return calendar.date(bySettingHour: 11, minute: 59, second: 59, of: Date())
}