Realm NSDate is not read correctly - date

My situation is as follows: I get a ticket info from an API Call, and I can see in my Realm Browser, that the dates of when I activated the ticket and when it expires are saved correctly in the database in UTC.
In the Database, using Realm Browser, I can see that startTime is Apr 25, 2017, 1:45:30 PM and endTime is Apr 26, 2017, 6:45:30 AM. (My local time was 9:45:30 AM at the time of activating my ticket - so this is correctly setup on servers end)
However, when I access that date later on in code and retrieve it from database it gives me a date with an offset!!! (And no, it's not a date in local timezone - it should've been a date saved in UTC).
Here's some code I use to get the info from database and display it:
func getTickets() -> [Ticket] {
let tickets = userInfo?.tickets.filter("state == %#", "activated").map({ (dbTicket) -> Ticket in
var startTime: Date? = nil
var endTime: Date? = nil
if let start = dbTicket.startTime, let end = dbTicket.endTime {
print("START ", dbTicket.startTime,
"\nNOW ", NSDate(),
"\nEND ", dbTicket.endTime)
startTime = start as Date
endTime = end as Date
}
print("START ", dbTicket.startTime,
"\nNOW ", Date(),
"\nEND ", dbTicket.endTime)
return Ticket(id: dbTicket.id, startTime: startTime, endTime: endTime)
}) ?? []
return tickets
}
And here's what gets printed in the console:
START Optional(2017-04-25 17:45:30 +0000)
NOW 2017-04-25 13:46:15 +0000
END Optional(2017-04-26 10:45:30 +0000)
START Optional(2017-04-25 17:45:30 +0000)
NOW 2017-04-25 13:46:15 +0000
END Optional(2017-04-26 10:45:30 +0000)
Which is incorrect! START should be almost the same as NOW. So why START and END dates are read from Realm database incorrectly ? Especially that I can see then in the Realm Browser and there they are saved correctly.

NSDate represents a specific point in time, independent of any time zone. The only time that a time zone is associated with an NSDate is when creating a string representation of it (in your case, this happens when print ends up calling -[NSDate description]). If you want to control which time zone the date is formatted with you can explicitly convert NSDate to the string using NSDateFormatter, which allow you to control the time zone that's used.

Related

Truncating time off a date retains some time component [duplicate]

When I try to log the current date:
print(NSDate())
or
print(Date())
(in Swift 3)
Or any date object, it shows the wrong time. For example, it's about 16:12 now, but the above displayed
2016-10-08 20:11:40 +0000
Is my date in the wrong time zone? How do I fix my date to have the correct time zone?
Why is that, and how to I fix it? How do I easily display an arbitrary date in my local time zone, either in print statements or in the debugger?
(Note that this question is a "ringer" so that I can provide a simple Swift 3/Swift 2 Date/NSDate extension that lets you easily display any date object in your local time zone.
NSDate (or Date in Swift ≥ V3) does not have a time zone. It records an instant in time all over the world.
Internally, date objects record the number of seconds since the "epoch date", or Midnight on January 1, 2001 in Greenwich Mean Time, a.k.a UTC.
We normally think of dates in our local time zone.
If you log a date using
print(NSDate())
The system displays the current date, but it expresses it in UTC/Greenwich Mean Time. So the only place the time will look correct is in that time zone.
You get the same issue in the debugger if you issue the debugger command
e NSDate()
This is a pain. I personally wish iOS/Mac OS would display dates using the user's current time zone, but they don't.
EDIT #2:
An improvement on my previous use of localized string that makes it a little easier to use is to create an extension to the Date class:
extension Date {
func localString(dateStyle: DateFormatter.Style = .medium, timeStyle: DateFormatter.Style = .medium) -> String {
return DateFormatter.localizedString(from: self, dateStyle: dateStyle, timeStyle: timeStyle)
}
}
That way you can just use an expression like Date().localString(), or if you want to only print the time, you can use Date().localString(dateStyle:.none)
EDIT:
I just discovered that NSDateFormatter (DateFormatter in Swift 3) has a class method localizedString. That does what my extension below does, but more simply and cleanly. Here is the declaration:
class func localizedString(from date: Date, dateStyle dstyle: DateFormatter.Style, timeStyle tstyle: DateFormatter.Style) -> String
So you'd simply use
let now = Date()
print (DateFormatter.localizedString(
from: now,
dateStyle: .short,
timeStyle: .short))
You can pretty much ignore everything below.
I have created a category of the NSDate class (Date in swift 3) that has a method localDateString that displays a date in the user's local time zone.
Here is the category in Swift 3 form: (filename Date_displayString.swift)
extension Date {
#nonobjc static var localFormatter: DateFormatter = {
let dateStringFormatter = DateFormatter()
dateStringFormatter.dateStyle = .medium
dateStringFormatter.timeStyle = .medium
return dateStringFormatter
}()
func localDateString() -> String
{
return Date.localFormatter.string(from: self)
}
}
And in Swift 2 form:
extension NSDate {
#nonobjc static var localFormatter: NSDateFormatter = {
let dateStringFormatter = NSDateFormatter()
dateStringFormatter.dateStyle = .MediumStyle
dateStringFormatter.timeStyle = .MediumStyle
return dateStringFormatter
}()
public func localDateString() -> String
{
return NSDate.localFormatter.stringFromDate(self)
}
}
(If you prefer a different date format it's pretty easy to modify the format used by the date formatters. It's also straightforward to display the date and time in any timezone you need.)
I would suggest putting the appropriate Swift 2/Swift 3 version of this file in all of your projects.
You can then use
Swift 2:
print(NSDate().localDateString())
Swift 3:
print(Date().localDateString())
A simple way to correct the Date for your timezone would be to use TimeZone.current.secondsFromGMT()
Something like this for a local timestamp value for example:
let currentLocalTimestamp = (Int(Date().timeIntervalSince1970) + TimeZone.current.secondsFromGMT())

Print of Date() displays time minus 1 hour [duplicate]

When I try to log the current date:
print(NSDate())
or
print(Date())
(in Swift 3)
Or any date object, it shows the wrong time. For example, it's about 16:12 now, but the above displayed
2016-10-08 20:11:40 +0000
Is my date in the wrong time zone? How do I fix my date to have the correct time zone?
Why is that, and how to I fix it? How do I easily display an arbitrary date in my local time zone, either in print statements or in the debugger?
(Note that this question is a "ringer" so that I can provide a simple Swift 3/Swift 2 Date/NSDate extension that lets you easily display any date object in your local time zone.
NSDate (or Date in Swift ≥ V3) does not have a time zone. It records an instant in time all over the world.
Internally, date objects record the number of seconds since the "epoch date", or Midnight on January 1, 2001 in Greenwich Mean Time, a.k.a UTC.
We normally think of dates in our local time zone.
If you log a date using
print(NSDate())
The system displays the current date, but it expresses it in UTC/Greenwich Mean Time. So the only place the time will look correct is in that time zone.
You get the same issue in the debugger if you issue the debugger command
e NSDate()
This is a pain. I personally wish iOS/Mac OS would display dates using the user's current time zone, but they don't.
EDIT #2:
An improvement on my previous use of localized string that makes it a little easier to use is to create an extension to the Date class:
extension Date {
func localString(dateStyle: DateFormatter.Style = .medium, timeStyle: DateFormatter.Style = .medium) -> String {
return DateFormatter.localizedString(from: self, dateStyle: dateStyle, timeStyle: timeStyle)
}
}
That way you can just use an expression like Date().localString(), or if you want to only print the time, you can use Date().localString(dateStyle:.none)
EDIT:
I just discovered that NSDateFormatter (DateFormatter in Swift 3) has a class method localizedString. That does what my extension below does, but more simply and cleanly. Here is the declaration:
class func localizedString(from date: Date, dateStyle dstyle: DateFormatter.Style, timeStyle tstyle: DateFormatter.Style) -> String
So you'd simply use
let now = Date()
print (DateFormatter.localizedString(
from: now,
dateStyle: .short,
timeStyle: .short))
You can pretty much ignore everything below.
I have created a category of the NSDate class (Date in swift 3) that has a method localDateString that displays a date in the user's local time zone.
Here is the category in Swift 3 form: (filename Date_displayString.swift)
extension Date {
#nonobjc static var localFormatter: DateFormatter = {
let dateStringFormatter = DateFormatter()
dateStringFormatter.dateStyle = .medium
dateStringFormatter.timeStyle = .medium
return dateStringFormatter
}()
func localDateString() -> String
{
return Date.localFormatter.string(from: self)
}
}
And in Swift 2 form:
extension NSDate {
#nonobjc static var localFormatter: NSDateFormatter = {
let dateStringFormatter = NSDateFormatter()
dateStringFormatter.dateStyle = .MediumStyle
dateStringFormatter.timeStyle = .MediumStyle
return dateStringFormatter
}()
public func localDateString() -> String
{
return NSDate.localFormatter.stringFromDate(self)
}
}
(If you prefer a different date format it's pretty easy to modify the format used by the date formatters. It's also straightforward to display the date and time in any timezone you need.)
I would suggest putting the appropriate Swift 2/Swift 3 version of this file in all of your projects.
You can then use
Swift 2:
print(NSDate().localDateString())
Swift 3:
print(Date().localDateString())
A simple way to correct the Date for your timezone would be to use TimeZone.current.secondsFromGMT()
Something like this for a local timestamp value for example:
let currentLocalTimestamp = (Int(Date().timeIntervalSince1970) + TimeZone.current.secondsFromGMT())

Check if local time is after midnight in another timezone

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.

Need simple way to compare a time string ONLY to the current dates time value

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
}

Swift 3 - Time Part of Date And Different Timezones

I am having an issue with a date string received from a REST web service and how that is being represented in Swift in different timezones. I have just been able to reproduce this issue, so I am going to relate this specific example.
The information I am getting from the web service is a date-time string and a timezone identifier. In this case, the system is in the America/New_York or Eastern US timezone. I created a playground where I have the following code
let dateFormatString = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
let easternTimeZone = "America/New_York"
let serverDate = "2017-03-01T00:00:00-05:00"
I have a method that takes the date string and the timezone identifier and creates a Date object as follows
func dateForDateString(dateString: String, timeZone: String) -> Date?
{
let formatter = DateFormatter()
if timeZone != ""
{
formatter.timeZone = TimeZone(identifier: timeZone)
}
formatter.dateFormat = dateFormatString
return formatter.date(from: dateString)
}
When I call this method with what's returned from the web service
let easternDate = dateForDateString(dateString: serverDate, timeZone: easternTimeZone)
print("\(easternDate)")
The print statement outputs Optional(2017-03-01 05:00:00 +0000) which is expected. No problems yet. The problem happens when the device is in a different timezone and for the sake of this example, I am testing with Pacific time. In the playground, the easternTime variable shows as Feb 28, 2017, 9:00 PM. Again, not unexpected.
The date is going to be stored in a Firebase database and I don't care about the time at all. So I end up using the Calendar method startOfDay as follows
let myDate = Calendar.current.startOfDay(for: ed)
as you may expect, this returns the Date Feb 28, 2017, 12:00 AM. Obviously not what I want to store in Firebase. The bottom line is that I need the date to be in the timezone the web service returned.
UPDATE
As mentioned in the comments, I also tried to create a Calendar instance and set it's timeZone property to a TimeZone with the identifier received from the server. The code looks like this
var easternCal = Calendar(identifier: .gregorian)
if let etz = TimeZone(identifier: easternTimeZone)
{
easternCal.timeZone = etz
}
if let ed = easternDate
{
let convertedDate = dateToTimeZone(date: ed, toTimezone: easternTimeZone)
let currentCalDate = Calendar.current.startOfDay(for: ed)
let myDate = easternCal.startOfDay(for: ed)
print("\(myDate)")
}
Last night, I could have sworn this didn't work, but when I try it now in the playground, it appears to be working just fine. currentCalDate is the date I don't want, Feb 28, 2017, 12:00 AM since it is using the user's Calendar that has the Pacific timezone set. myDate is correctly showing as Feb 28, 2017, 9:00 PM, which is printing as 2017-03-01 05:00:00 +0000, what I have wanted all along.
I think this one is solved.
As mentioned in the update to my question, creating a Calendar instance and setting the timeZone property to the one returned from the back-end solved my problem.