I creating an itinerary generation app where the user is required to enter the dates of his/her trip. The only problem is, using UIDatePicker the dates are always given as the current time for a given day/month/year.
In a separate file I've extended Date class to try and write a simple method that will return midnight for a given date.
First I tried
var midnight:Date{
let cal = Calendar(identifier: .gregorian)
return cal.startOfDay(for: self)
}
However this always gave me either 04:00 or 05:00 depending on daylights savings, which gave me the idea that I should simply remove 4 or 5 hours depending on daylight savings, and so I created the following methods:
var timezone:TimeZone{
return TimeZone.current
}
///Returns the first instance of the date, e.g. 2018-02-26 00:00:00
var trueMidnight:Date{
let cal = Calendar(identifier: .gregorian)
let midnight = cal.startOfDay(for: self)
let secondsFromGMT = TimeZone.current.secondsFromGMT()
print("Daylight savings? \(daylightSavings)")
return midnight.addingTimeInterval(Double(secondsFromGMT))
}
///If this var returns true, then daylight savings time is active and an hour of daylight is gained (during the summer).
var isDaylightSavings:Bool{
return timezone.daylightSavingTimeOffset(for: self) == 0 ? false : true
}
var daylightSavings:Double{
return isDaylightSavings ? 3600.0 : 0.0
}
However these methods sometimes return midnight, 23:00, or even 22:00 the previous day.
I'm a relatively inexperienced programmer so I feel like I'm lacking a basic understanding for the date class or missing a large concept. Why is it so difficult for me to simply find midnight on a given date?
I even forsook the idea of returning midnight and tried to just find noon on a given day with the code:
var noon:Date{
let gregorian = Calendar(identifier: .gregorian)
var components = gregorian.dateComponents([.year, .month, .day, .hour, .minute, .second], from: self)
components.hour = 12
components.minute = 0
components.second = 0
return gregorian.date(from: components)!
}
But this returns 16:00 or 17:00 as opposed to noon. Any help would be appreciated.
When you print a date, it is printed in UTC time. So when you print your Dates, they differ from your local time by 4/5 hours.
If you use the following code instead
print(yourDate.description(with: .current))
Where yourDate is your date, it will be in the correct time zone.
You're confused.
If you use
print(Date())
You will get a date in UTC. If you're in the UTC+5 time zone, that date will be 5 hours greater than your local time. Thus if you try to display midnight local time in UTC, it will show up as 5:00 AM in UTC.
Try this:
extension Date {
func localString(dateStyle: DateFormatter.Style = .medium,
timeStyle: DateFormatter.Style = .medium) -> String {
return DateFormatter.localizedString(
from: self,
dateStyle: dateStyle,
timeStyle: timeStyle)
}
var midnight:Date{
let cal = Calendar(identifier: .gregorian)
return cal.startOfDay(for: self)
}
}
print("Tonight at midnight is " + Date().midnight.localString())
That code uses a function localString() that takes advantage of a DateFormatter method localizedString(from:dateStyle:timeStyle:) that converts a Date to a string in the current locale (which includes the local time zone.
I suggest adding that extension to your apps.
Related
I'm using this extension to convert a string containing date to Date() object:
extension String {
func toDate() -> Date?{
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd/MM/YYYY"
let date = dateFormatter.date(from: self)
return date
}
}
the result always containing a time in it. I'm curious where is the time coming from, why it is not all zero?
print("11/12/2021".toDate())
result is ->
2020-12-19 21:00:00 +0000
In the time that I run the code, it is showing 21:00:00, so why it is 21? I believe It is not related to my time because I run it at different times.
A Date object indicates an instant in time anywhere on the planet, independent of time zone.
A DateFormatter can convert a String to a Date (or a Date to a String, but ignore that for now). When it converts a String to a Date, it may make assumptions about the time of day if that is not included in the String. I believe it assumes that the time is midnight (00:00:00) in the date formatter's time zone. (And by the way, midnight is the starting point of a day, so midnight is zero hours/minutes/seconds into the day. Essentially midnight IS zeros for time.)
So when you call your String extension to convert "11/12/2021" to a Date, the extension creates a DateFormatter which defaults to the device time zone. It creates a Date assuming Midnight in the local time zone.
When you print that date, it gets displayed in GMT.
It looks like your format string has problems though. You're getting the wrong year and month. I think you must be using the wrong month or day string in your formatter. (I always have to look those up when I use them.)
Edit:
You likely want a format string of "MM-dd-yyyy"
(2-digit month, 2-digit day of month, and 4-digit year.)
Lower-case "m" or "mm" is minutes. Upper-case "Y" is for "week of year" based calendars, which you probably don't want.
Try this code:
func toDate() -> Date?{
let dateFormatter = DateFormatter()
let posixLocale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "MM-dd-yyyy"
dateFormatter.locale = posixLocale
let date = dateFormatter.date(from: self)
return date
}
}
And to use it:
let dateString = "12/11/2021"
let date = dateString.toDate()
print(date)
if let date = date {
let convertedDateString = DateFormatter.localizedString(from: date, dateStyle: .medium, timeStyle: .medium)
print(convertedDateString)
} else {
print("Can't convert \(dateString) to a date")
}
That displays "Dec 11, 2021 at 12:00:00 AM" in my US locale (US Date formatting.) Note that since I use the DateFormatter class method localizedString(from:dateStyle:timeStyle:) I see midnight as the displayed time (The time you get from a DateFormatter when you don't specify a time, but displayed in the local time zone.)
The answer is:
when we are converting a string to a Date Object the important part is the time zone that we are converting it to.
for example, if you convert your string date to a UTC time zone when you want to bring it back you have to set the time zone of the date to UTC.
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone(identifier: "UTC")
so this is the reason why when we are printing the Date() object it is deferred from our string date.
extension String {
func toDate() -> Date?{
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "mm-dd-yyyy"
let date = dateFormatter.date(from: self)
return date
}
}
extension Date {
func toString() -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "mm-dd-yyyy"
return dateFormatter.string(from: self)
}
}
let stringDate = "01-12-2021"
let date = "01-12-2021".toDate()
let convertBack = date?.toString()
print("(\(stringDate)) -> (\(date!)) -> (\(convertBack!))")
and the result is:
(01-12-2021) -> (2021-01-11 21:01:00 +0000) -> (01-12-2021)
so at the end when we convert back the Date object it will be the same. because that 2 dateFormatter in the extensions are using the default time zone. and if you want to specify a specific time zone you have to declare it in converting from and to string together.
I'm trying to get the date only, I wrote this:
func getCurrentDate() -> Date {
dateFormatter.dateFormat = "dd.MM.yyyy"
let result = dateFormatter.string(from: date)
print(result)
let date = dateFormatter.date(from: result)
return date!
}
printing 'result' I get:
12.07.2019
which is what I need, but converting it to Date type and printing it results in:
2019-07-11 21:00:00 +0000
Why does that happen and how can I fix it ?
Date was very unfortunately named. It has nothing to do with dates. It represents an instant in time. An instant that all observers (ignoring details like Relativity) would agree on. It isn't "a day on the calendar." It's a point in time, independent of any calendar.
The fact that it prints out in a "calendar-like" way is just a convenience for debugging.
If you want "a specific day on a calendar" then the tools you want is DateComponents and Calendar, not Date:
func currentDay() -> DateComponents {
return Calendar.current.dateComponents([.year, .month, .day], from: Date())
}
Keep in mind that the user's current calendar may not be Gregorian. If you require the Gregorian calendar, you need to say so:
func currentDay() -> DateComponents {
return Calendar(identifier: .gregorian).dateComponents([.year, .month, .day],
from: Date())
}
The Gregorian calendar is the one where the current year is 2019. In the Hebrew calendar, it's 5779. In the Buddhist calendar, it's 2562. In the Islamic calendar, it's 1440. There is no one "correct" calendar, which is why you need to provide one.
Reading your comments, it's possible you mean "the start of 'today' using the current calendar." If that's what you want, then you would use this:
func currentDay() -> Date {
return Calendar.current.startOfDay(for: Date())
}
(Or use the Gregorian calendar if needed, though I don't believe any supported calendar disagrees about what the start of the day is.)
I want to check if my local time is after midnight in another time zone.
Specifically, if right now where I am at is 11 PM Saturday, or 1 AM Sunday local time, I want to see if it is the start of a new week in Central Time (after 12 AM Sunday).
You can use Calendar's dateComponents(in: TimeZone, from: Date) to check the time and date in another timezone. For your specific application:
// create current date, central time zone, and get the current calendar
let now = Date()
let centralTimeZone = TimeZone(abbreviation: "CST")!
let calendar = Calendar.current
let components = calendar.dateComponents(in: centralTimeZone, from: now)
if components.weekday == 1 {
print("It is Sunday in Central Standard Time.")
} else {
print("It is not Sunday in Central Standard Time.")
}
What you're doing there is asking the current calendar to give you a full set of DateComponents in the specified timezone. Then components.weekday gives the day of the week as an Int, starting with 1 for Sunday in the Gregorian calendar.
If you want to know more generally if it's "tomorrow" somewhere, here's a simple method:
func isItTomorrow(in zone: TimeZone) -> Bool {
var calendarInZone = Calendar(identifier: Calendar.current.identifier)
calendarInZone.timeZone = TimeZone(abbreviation: "CST")!
return calendarInZone.isDateInTomorrow(Date())
}
if isItTomorrow(in: centralTimeZone) {
print("It is tomorrow.")
} else {
print("It is not tomorrow.")
}
isItTomorrow(in: TimeZone) creates a new calendar of the same type as the current calendar (presumably .gregorian, but you never know) and sets its timezone to the desired one. Then it uses the neat built-in Calendar method .isDateInTomorrow() to check if the current time is "tomorrow" in the target timezone.
There are lots of other ways to do it, and depending on your specific need there may be a built-in method that will save you a lot of work, so it's well worth reading through the docs on Calendar and DateComponents to see what's available.
Say time string value is "7:00 AM" call it reminder time.
Now all I need to do is compare this time with the current dates time say its "9:00 AM" if reminder time is later than current time - return true else false. This is the format "h:mm a" for date formatters.
Simple right? It should be but I have burned too much time on this. I can get hour and minute values but when the AM/PM is considered it gets harder.
I just want to compare two time values and determine if the first is later or after the second one. The date is always today or current date so I only care about the time part of the date. Of course you have to convert to dates to do the comparison but current date is easy to get however date from "7:00 AM" string does not seem to work right in comparisons.
Anyone have a function to do this?
Thanks.
the approach would be lets date the Date() object from your current time object so you will get
default date + your time = 2000-01-01 00:00:00 +your time (7.00 AM or 9.00 PM)
now we will get the current time from today only, in same format. (Only time)
it will be something like 3.56 PM
now again we will convert this 3.56 PM to Date() with default date as prev. so now we will have two date time object with same Date(2000-01-01) and respective times.
2000-01-01 7:00:00 => this will your 7.00 AM with default date
2000-01-01 15:56:00 => this will be current time with default date
now we will compare two date object.
Check the fiddle Fiddle
func CompareMyTimeInString(myTime:String)->Bool
{
// create the formatter - we are expecting only "hh:mm a" format
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "hh:mm a"
dateFormatter.locale = Locale.init(identifier: "en_GB")
// default date with my time
var dt_MyTime = dateFormatter.date(from: yourTime)!
// current time in same format as string "hh:mm a"
var currentTimString = dateFormatter.string(from: Date());
print("Current Time is - "+currentTimString);
// current time with default date.
var dt_CurrentTime = dateFormatter.date(from: currentTimString)!
// now just compare two date objects :)
return dt_MyTime > dt_CurrentTime;
}
// then call it like
var yourTime = "7.00 AM"
var isDue = CompareMyTimeInString(myTime:yourTime);
print(isDue);
My solution was as follows.
private func ReminderAfterCurrentTime(reminderTimeString: String) -> Bool {
//Compare the two time strings and if reminderTimeString is later than current time string
//return true since Reminder is after current time.
//Get the current date and time
let currentDateTime = Date()
// Create calendar object
let calendar = NSCalendar.current
// Get current date hour and minute values for comparison.
let currentHourValue = Int(calendar.component(.hour, from: currentDateTime))
let currentMinuteValue = Int(calendar.component(.minute, from: currentDateTime))
//Now get a date from the time string passed in so we can get just the hours and minutes to compare
let dateformatter = DateFormatter()
dateformatter.dateStyle = DateFormatter.Style.none
dateformatter.timeStyle = DateFormatter.Style.short
//Now get the date using formatter.
let reminderDateTime = dateformatter.date(from: reminderTimeString)
print("reminderDateTime = \(reminderDateTime)")
//Get reminder hour and minute for comparison.
let reminderHourValue = Int(calendar.component(.hour, from: reminderDateTime!))
let reminderMinuteValue = Int(calendar.component(.minute, from: reminderDateTime!))
print("currentHourValue = \(currentHourValue)")
print("currentMinuteValue = \(currentMinuteValue)")
print("reminderHourValue = \(reminderHourValue)")
print("reminderMinuteValue = \(reminderMinuteValue)")
//This works due to 24 hour clock. Thus AM/PM is already taken into account.
if currentHourValue < reminderHourValue {
return true
}
//Check for same hour then use minutes
if currentHourValue == reminderHourValue {
if currentMinuteValue < reminderMinuteValue {
return true
}
}
//Otherwise return false
return false
}
I need to check to see if the current date is during daylight savings time. In pseudocode that would be like this:
let date = NSDate()
if date.isDaylightSavingsTime {
print("Success")
}
I haven't been able to find the solution to this anywhere on the internet.
An NSDate alone represents an absolute point in time.
To decide if a date is during daylight savings time or not
it needs to be interpreted in the context of a time zone.
Therefore you'll find that method in the NSTimeZone class and not
in the NSDate class. Example:
let date = NSDate()
let tz = NSTimeZone.localTimeZone()
if tz.isDaylightSavingTimeForDate(date) {
}
Update for Swift 3/4:
let date = Date()
let tz = TimeZone.current
if tz.isDaylightSavingTime(for: date) {
print("Summertime, and the livin' is easy ... 🎶")
}
Swift 4.0 or later
You can check a date isDaylightSavingTime in two ways by time zone identifier or abbreviation.
let timeZone = TimeZone(identifier: "America/New_York")!
if timeZone.isDaylightSavingTime(for: Date()) {
print("Yes, daylight saving time at a given date")
}
let timeZone = TimeZone(abbreviation: "EST")!
if timeZone.isDaylightSavingTime(for: Date()) {
print("Yes, daylight saving time at a given date")
}