I'm trying to make an attendance app and I am really confused about date and time in iOS and Firebase.
I use date as Key, this is the structure of my Firebase database.
--Employees
--Unique_ID
--Details
Name: John
--Attendance
--dateToday
Timein: 8:00 AM
Timeout: 5:00 PM
BreakStart: 12:00 PM
BreakFinish: 1:00 PM
This is my code to get the date timestamp I used as Key
override func viewDidLoad() {
super.viewDidLoad()
let now = NSDate()
let nowTimeStamp = self.getCurrentTimeStampWOMiliseconds(dateToConvert: now)
// I save this dateToday as Key in Firebase
dateToday = nowTimeStamp
}
func getCurrentTimeStampWOMiliseconds(dateToConvert: NSDate) -> String {
let objDateformat: DateFormatter = DateFormatter()
objDateformat.dateFormat = "yyyy-MM-dd"
let strTime: String = objDateformat.string(from: dateToConvert as Date)
let objUTCDate: NSDate = objDateformat.date(from: strTime)! as NSDate
let milliseconds: Int64 = Int64(objUTCDate.timeIntervalSince1970)
let strTimeStamp: String = "\(milliseconds)"
return strTimeStamp
}
But when I convert it back to date I get 2017-09-22 16:00:00 +0000, which is wrong because it is 23rd of September in my location.
What is the right code to use so that I can get the correct date timestamp and time timestamp?
For saving Current time to firebase database I use Unic Epoch Conversation:
let timestamp = NSDate().timeIntervalSince1970
and For Decoding Unix Epoch time to Date().
let myTimeInterval = TimeInterval(timestamp)
let time = NSDate(timeIntervalSince1970: TimeInterval(myTimeInterval))
If you just want the unix timestamp, create an extension:
extension Date {
func currentTimeMillis() -> Int64 {
return Int64(self.timeIntervalSince1970 * 1000)
}
}
Then you can use it just like in other programming languages:
let timestamp = Date().currentTimeMillis()
First I would recommend you to store your timestamp as a NSNumber in your Firebase Database, instead of storing it as a String.
Another thing worth mentioning here, is that if you want to manipulate dates with Swift, you'd better use Date instead of NSDate, except if you're interacting with some Obj-C code in your app.
You can of course use both, but the Documentation states:
Date bridges to the NSDate class. You can use these interchangeably in
code that interacts with Objective-C APIs.
Now to answer your question, I think the problem here is because of the timezone.
For example if you print(Date()), as for now, you would get:
2017-09-23 06:59:34 +0000
This is the Greenwich Mean Time (GMT).
So depending on where you are located (or where your users are located) you need to adjust the timezone before (or after, when you try to access the data for example) storing your Date:
let now = Date()
let formatter = DateFormatter()
formatter.timeZone = TimeZone.current
formatter.dateFormat = "yyyy-MM-dd HH:mm"
let dateString = formatter.string(from: now)
Then you have your properly formatted String, reflecting the current time at your location, and you're free to do whatever you want with it :) (convert it to a Date / NSNumber, or store it directly as a String in the database..)
in Swift 5
extension Date {
static var currentTimeStamp: Int64{
return Int64(Date().timeIntervalSince1970 * 1000)
}
}
call like this:
let timeStamp = Date.currentTimeStamp
print(timeStamp)
Thanks #lenooh
The simple way to create Current TimeStamp. like below,
func generateCurrentTimeStamp () -> String {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy_MM_dd_hh_mm_ss"
return (formatter.string(from: Date()) as NSString) as String
}
you can call like this:
let timeStmp = generateCurrentTimeStamp()
print("time stamp: \(timeStmp)")
If you code for iOS 13.0 or later and want a timestamp, then you can use:
let currentDate = NSDate.now
On expanding #MacacoAzul's answer here is my current working example :
import SwiftUI
struct TimestampDemo: View {
var body: some View {
Text(getActualTimeStamp(1))
.padding(10)
Text(getActualTimeStamp(2))
.padding(10)
Text(getActualTimeStamp(3))
.padding(10)
Text(getActualTimeStamp(4))
.padding(10)
}
func getActualTimeStamp(_ tipo:Int) -> String {
let date = Date()
let formatter = DateFormatter()
if tipo == 1{
formatter.dateFormat = "dd/MM/yyyy"
} else if tipo == 2{
formatter.dateFormat = "yyyy-MM-dd HH:mm"
}else if tipo == 3{
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
}
else if tipo == 4 {
formatter.dateFormat = "dd-MM-yyyy"
}
return formatter.string(from: date)
}
}
struct TimestampDemo_Previews: PreviewProvider {
static var previews: some View {
TimestampDemo()
}
}
Swift Language Version : 5
When we convert a UTC timestamp (2017-11-06 20:15:33 -08:00) into a Date object, the time zone is zeroed out to GMT. For calculating time intervals, this isn't an issue, but it can be for rendering times in the UI.
I favor the RFC3339 format (2017-11-06T20:15:33-08:00) for its universality. The date format in Swift is yyyy-MM-dd'T'HH:mm:ssXXXXX but RFC3339 allows us to take advantage of the ISO8601DateFormatter:
func getDateFromUTC(RFC3339: String) -> Date? {
let formatter = ISO8601DateFormatter()
return formatter.date(from: RFC3339)
}
RFC3339 also makes time-zone extraction simple:
func getTimeZoneFromUTC(RFC3339: String) -> TimeZone? {
switch RFC3339.suffix(6) {
case "+05:30":
return TimeZone(identifier: "Asia/Kolkata")
case "+05:45":
return TimeZone(identifier: "Asia/Kathmandu")
default:
return nil
}
}
There are 37 or so other time zones we'd have to account for and it's up to you to determine which ones, because there is no definitive list. Some standards count fewer time zones, some more. Most time zones break on the hour, some on the half hour, some on 0:45, some on 0:15.
We can combine the two methods above into something like this:
func getFormattedDateFromUTC(RFC3339: String) -> String? {
guard let date = getDateFromUTC(RFC3339: RFC3339),
let timeZone = getTimeZoneFromUTC(RFC3339: RFC3339) else {
return nil
}
let formatter = DateFormatter()
formatter.dateFormat = "h:mma EEE, MMM d yyyy"
formatter.amSymbol = "AM"
formatter.pmSymbol = "PM"
formatter.timeZone = timeZone // preserve local time zone
return formatter.string(from: date)
}
And so the string "2018-11-06T17:00:00+05:45", which represents 5:00PM somewhere in Kathmandu, will print 5:00PM Tue, Nov 6 2018, displaying the local time, regardless of where the machine is.
As an aside, I recommend storing dates as strings remotely (including Firestore which has a native date object) because, I think, remote data should agnostic to create as little friction between servers and clients as possible.
you can even create a function to return different time stamps depending on your necessity:
func dataatual(_ tipo:Int) -> String {
let date = Date()
let formatter = DateFormatter()
if tipo == 1{
formatter.dateFormat = "dd/MM/yyyy"
} else if tipo == 2{
formatter.dateFormat = "yyyy-MM-dd HH:mm"
} else {
formatter.dateFormat = "dd-MM-yyyy"
}
return formatter.string(from: date)
}
Related
How can I get local date and time in Swift?
let last_login = String(NSDate.date())
update: Xcode 8.2.1 • Swift 3.0.2
You can also use the Date method description(with locale: Locale?) to get user's localized time description:
A string representation of the Date, using the given locale, or if the locale
argument is nil, in the international format YYYY-MM-DD HH:MM:SS
±HHMM, where ±HHMM represents the time zone offset in hours and
minutes from UTC (for example, “2001-03-24 10:45:32 +0600”).
description(with locale: Locale?)
Date().description(with: .current) // "Monday, February 9, 2015 at 05:47:51 Brasilia Summer Time"
The method above it is not meant to use when displaying date and time to the user. It is for debugging purposes only.
When displaying local date and time (current timezone) to the user you should respect the users locale and device settings. The only thing you can control is the date and time style (short, medium, long or full). Fore more info on that you can check this post shortDateTime.
If your intent is to create a time stamp UTC for encoding purposes (iso8601) you can check this post iso8601
In case you want to get a Date object and not a string representation you can use the following snippet:
extension Date {
func localDate() -> Date {
let nowUTC = Date()
let timeZoneOffset = Double(TimeZone.current.secondsFromGMT(for: nowUTC))
guard let localDate = Calendar.current.date(byAdding: .second, value: Int(timeZoneOffset), to: nowUTC) else {return Date()}
return localDate
}
}
Use it like this:
let now = Date().localDate()
Leo's answer great. I just wanted to add a way to use it as a computed property.
var currentTime: String {
Date().description(with: .current)
}
Use it like so:
print(currentTime)
Or you can encapsulate it:
extension String {
static var currentTime: String {
Date().description(with: .current)
}
}
And then you can use it anywhere you use a string:
var time: String = .currentTime
use NSDateFormatter, either by setting the format
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "hh:mm"
println(dateFormatter.stringFromDate(NSDate()))
or styles
dateFormatter.dateStyle = .NoStyle
dateFormatter.timeStyle = .MediumStyle
I already found the answer.
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MM-dd-yyyy HH:mm"
let dateInFormat = dateFormatter.stringFromDate(NSDate())
let expiryDate: Date = ...
let localizedDateString = DateFormatter.localizedString(from: expiryDate, dateStyle: .medium, timeStyle: .short)
"10 Sep 2017, 14:37"
To get back the most common string formats (when dealing with queries and databases):
Swift 4, 5
2019-01-09T01:07:04Z (RFC3339 in GMT/Zulu time)
let f = ISO8601DateFormatter()
f.formatOptions = [.withInternetDateTime]
let s = f.string(from: Date())
2019-01-08T17:04:16-08:00 (RFC3339 accounting for local time zone)
let f = ISO8601DateFormatter()
f.formatOptions = [.withInternetDateTime]
f.timeZone = TimeZone.current
let s = f.string(from: Date())
2019-01-09 (standard date stamp in GMT/Zulu time)
let f = ISO8601DateFormatter()
f.formatOptions = [.withFullDate, .withDashSeparatorInDate]
let s = f.string(from: Date())
2019-01-08 (standard date stamp accounting for local time zone)
let f = ISO8601DateFormatter()
f.formatOptions = [.withFullDate, .withDashSeparatorInDate]
f.timeZone = TimeZone.current
let s = f.string(from: Date())
All four strings represent the exact same point in time. And remember that sorting these strings in alphabetical order also sorts them into chronological order, which makes this data database agnostic (which I always aim for).
You have to use NSDateFormatter
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MMMM dd, yyyy HH:mm"
dateFormatter.locale = "en" // Change "en" to change the default locale if you want
let stringDate = dateFormatter.stringFromDate(date)
Refactor the answer with swift 5 base on #lajosdeme. My location is in China.
import Foundation
let date = Date() // It is not the local time, less than 8 hours
print(date) // 2022-08-05 08:04:20 +0000
extension Date {
static func localDate() -> Date {
let nowUTC = Date()
let timeZoneOffset = Double(TimeZone.current.secondsFromGMT(for: nowUTC))
guard let localDate = Calendar.current.date(byAdding: .second, value: Int(timeZoneOffset), to: nowUTC) else {
return nowUTC
}
return localDate
}
}
// It is the local time
print(Date.localDate()) // 2022-08-05 16:04:20 +0000
Swift 4
To get current date and time
let currentDate = Date()
print(currentDate) //this will return current date and time
but that will be in date type to convert date into string
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd-MM-yyyy HH:mm" //give the formate according to your need
let dateStr = dateFormatter.string(from: currentDate) //which will give the string of current date and time in required dateformate
My understanding of Swift Date is that Date is a time point without any calendar or timezone information. I think it is GMT time. If you want to show a date in a specified timezone, you have to use DateFormat API to format the date to a string.
I have an iOS app TapToCount-3W to make notes with date and GPS location information. When I travel, I use it to record/tap a note with date and GPS. The dates are local date when I am in travel countries. However, the problem I found is that when I come back home, the travel dates displayed are in my home country dates instead of those travel country timezones.
I am working on updates with my app now. The solution is to add timezone information when a tap is made. With date and timezone information, the localized dates will be correctly displayed.
The method as recommended in this QA to extend Date is actually to create date from Date() from second offset from GMT time. It is a GMT time and different date from Date().
The following codes are from my updates(I also included #lajosdeme method as comparison):
extension Date {
private func getLocalByID(_ identifier: String?) -> Locale
{
let local: Locale
if let id = identifier, !id.isEmpty {
local = Locale(identifier: id)
} else {
local = Locale.current
}
return local
}
func localizedString(
timezone: String?,
dateStyle: DateFormatter.Style = .short,
timeStyle: DateFormatter.Style = .long
) -> String
{
let dtFormater = DateFormatter()
let tz: String = timezone ?? ""
dtFormater.locale = getLocalByID(tz)
dtFormater.dateStyle = dateStyle
dtFormater.timeStyle = timeStyle
if let timeZone = TimeZone(identifier: tz) {
dtFormater.timeZone = timeZone
}
return dtFormater.string(from: self)
}
func dateForTimezone(_ timezone: String?) -> Date {
let nowUTC = Date()
let tz: TimeZone
if let timezone = timezone,
let v = TimeZone(identifier: timezone)
{
tz = v
} else {
tz = TimeZone.current
}
let timeZoneOffset =
Double(tz.secondsFromGMT(for: nowUTC))
if let dt =
Calendar.current.date(byAdding: .second, value: Int(timeZoneOffset), to: nowUTC)
{
return dt
}
else {
return Date()
}
}
}
// Test above extension in Playground
// [SwiftFiddle][3]
let dt1 = Date()
let tz = "America/Edmonton"
let dt2 = dt1.description(with: .current)
let dt3 = dt1.localizedString(timezone: tz)
let dt4 = dt1.dateForTimezone(tz)
print("Timezone: \(tz)\nDate: \(dt1)\ndescription: \(dt2)\nlocalized string: \(dt3)\ndateForTimezone: \(dt4)")
Here are the test result from SwiftFiddle playground:
Timezone: America/Edmonton
Date: 2022-06-03 15:41:23 +0000
description: Friday, June 3, 2022 at 3:41:23 PM Coordinated Universal Time
localized string: 6/3/22, 9:41:23 AM GMT-6
dateForTimezone: 2022-06-03 09:41:23 +0000
My use-case requires me to display different format of strings with respect to user's 12-hour/24-hour preference in "Date & Time" settings.
To be precise, my string needs to ignore the minute component and include the "AM/PM" suffix for 12-hour time and do the exact opposite for the 24-hour time.
I recently got to know about using "jj" template to achieve this. More on this (Thanks to #larme)
Here's my approach:
let df = DateFormatter()
df.setLocalizedDateFormatFromTemplate("jj:mm") // As of current time in my locale, this'll display "17:01"
df.locale = .current
if df.string(from: passedDate).count > 5 {
// User has 12-hour time setting i.e: The string has AM/PM suffix
df.setLocalizedDateFormatFromTemplate("jj") // Setting this will ignore the minutes and provide me with the hour and the AM/PM suffix i.e: "5 PM" according to current time
}
Now, this solves my problem but I wonder if there is a cleaner way to get this done.
You can use DateFormatter static method dateFormat(fromTemplate tmplate: String, options opts: Int, locale: Locale?) -> String? passing j format and .current locale and check if it contains "a":
extension DateFormatter {
static var is24Hour: Bool {
dateFormat(fromTemplate: "j", options: 0, locale: .current)?.contains("a") == false
}
}
extension Formatter {
static let customHour: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.setLocalizedDateFormatFromTemplate("jj")
return dateFormatter
}()
}
extension Date {
var customHour: String { Formatter.customHour.string(from: self) }
}
DateFormatter.is24Hour // false
Date().customHour // "11 AM"
Note that there is no need to check if 24hour setting is on or not unless the user changes it after the formatter has been initialized. If want to make sure it reflects this as well:
extension Formatter {
static let date = DateFormatter()
}
extension Date {
var customHour: String {
Formatter.date.setLocalizedDateFormatFromTemplate("jj")
return Formatter.date.string(from: self)
}
}
Date().customHour // "11 AM"
You can create a Date extension with get12HourFormat and get24HourFormat
extension Date {
var get12HourFormat: String? {
let formatter = DateFormatter()
formatter.dateFormat = "dd-MM-yyyy hh:mm a"
return formatter.string(from: self)
}
var get24HourFormat: String? {
let formatter = DateFormatter()
formatter.dateFormat = "dd-MM-yyyy HH:mm"
return formatter.string(from: self)
}
}
Example:
Date().get12HourFormat //"21-09-2020 07:19 PM"
Date().get24HourFormat //"21-09-2020 19:19"
This question already has an answer here:
DateFormatter doesn't return date for "HH:mm:ss"
(1 answer)
Closed 2 years ago.
I have some troubles parsing string date to Date format.
I recibe from one API the date string with this format 2020-08-27 12:39:32 (the date come in 24 hour format) and I have an extension of String to convert it to Date. So I do the convertion like this:
apiTime.toDate(format: "yyyy-MM-dd HH:mm:ss")
All works fine if the phone is configured with 24 hour. The problem came when the app runs in a AM/PM date format because the date return nil.
This is my extension to convert String to Date.
extension String {
func toDate(format: String) -> Date? {
let dateFormatter = DateFormatter()
dateFormatter.amSymbol = "AM"
dateFormatter.pmSymbol = "PM"
dateFormatter.dateFormat = format
return dateFormatter.date(from: self) //here return nil with AM/PM format
}
}
As suggested by #LeoDabus it's better not to create DateFormatter every time (this is very resource-consuming). You can extract it, eg. as a static property.
Here is a simple demo:
class Test {
static let dateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
return dateFormatter
}()
func parseDateExample() {
let apiTime = "2020-08-27 12:39:32"
if let date = apiTime.toDate(formatter: Self.dateFormatter, format: "yyyy-MM-dd HH:mm:ss") {
print(date)
} else {
print("error")
}
}
}
You can reuse the formatter in your String extension instead of creating a new one every time you call toDate:
extension String {
func toDate(formatter: DateFormatter, format: String) -> Date? {
formatter.dateFormat = format
return formatter.date(from: self)
}
}
I have a date stored on my online server database which is in GMT. I load the date and convert it to the user's timezone using the following code :
if let messagedate = oneitem["timestamp"] as? String {
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let date = dateFormatter.dateFromString(messagedate)
let source_timezone = NSTimeZone(abbreviation: "GMT")
let local_timezone = NSTimeZone.systemTimeZone()
let source_EDT_offset = source_timezone?.secondsFromGMTForDate(date!)
let destination_EDT_offset = local_timezone.secondsFromGMTForDate(date!)
let time_interval : NSTimeInterval = Double(destination_EDT_offset - source_EDT_offset!)
let final_date = NSDate(timeInterval: time_interval, sinceDate: date!)
curr_item.date = final_date
}
Now I need to convert the date back to GMT in order to communicate it to the server, however I'm not sure how to convert it back to GMT.
Simpler version:
extension Date {
func convertToTimeZone(initTimeZone: TimeZone, timeZone: TimeZone) -> Date {
let delta = TimeInterval(timeZone.secondsFromGMT(for: self) - initTimeZone.secondsFromGMT(for: self))
return addingTimeInterval(delta)
}
}
Couldn't you just use your data formatter again with a different time zone and convert it? Such as
dateFormatter.timeZone = NSTimeZone(abbreviation: "GMT")
let gmtDate = dateFormatter.dateFromString(string: "your old date as string here")
At time of writing, most answers contain an edge case bug near DST switchover times (see my note about other answers below). If you just want to convert a date string with no time offset to a Date in a particular time zone, Amloelxer's answer is best, but for the benefit of those with the question of "how to convert a Date between timezones", there are two cases:
Case 1:
Convert a Date to another time zone while preserving the day and time from the initial time zone.
E.g. for GMT to EST: 2020-03-08T10:00:00Z to 2020-03-08T10:00:00-04:00
Case 2:
Convert a Date to the day and time from another time zone while preserving the initial time zone.
E.g. for EST to GMT: 2020-03-08T06:00:00-04:00 to 2020-03-08T10:00:00-04:00 (because the initial Date is 10am in GMT)
These two cases are actually the same (the example start and end Dates are identical), except they are worded differently to swap which time zone is the "initial" and which is the "target". The two solutions below are therefore equivalent if you swap the time zones between them, so you can choose the one that conceptually fits your use case better.
extension Calendar {
// case 1
func dateBySetting(timeZone: TimeZone, of date: Date) -> Date? {
var components = dateComponents(in: self.timeZone, from: date)
components.timeZone = timeZone
return self.date(from: components)
}
// case 2
func dateBySettingTimeFrom(timeZone: TimeZone, of date: Date) -> Date? {
var components = dateComponents(in: timeZone, from: date)
components.timeZone = self.timeZone
return self.date(from: components)
}
}
// example values
let initTz = TimeZone(abbreviation: "GMT")!
let targetTz = TimeZone(abbreviation: "EST")!
let initDate = Calendar.current.date(from: .init(timeZone: initTz, year: 2020, month: 3, day: 8, hour: 4))!
// usage
var calendar = Calendar.current
calendar.timeZone = initTz
let case1TargetDate = calendar.dateBySetting(timeZone: targetTz, of: initDate)!
let case2TargetDate = calendar.dateBySettingTimeFrom(timeZone: targetTz, of: initDate)!
// print results
let formatter = ISO8601DateFormatter()
formatter.timeZone = targetTz // case 1 is concerned with what the `Date` looks like in the target time zone
print(formatter.string(from: case1TargetDate)) // 2020-03-08T04:00:00-04:00
// for case 2, find the initial `Date`'s time in the target time zone
print(formatter.string(from: initDate)) // 2020-03-07T23:00:00-05:00 (the target date should have this same time)
formatter.timeZone = initTz // case 2 is concerned with what the `Date` looks like in the initial time zone
print(formatter.string(from: case2TargetDate)) // 2020-03-07T23:00:00Z
A note about other answers
At time of writing, most other answers assume one of the two above cases, but more importantly, they share a bug - they attempt to calculate the time difference between the time zones, where the sign of the difference determines the case:
Case 1:
initialTz.secondsFromGMT(for: initialDate) - targetTz.secondsFromGMT(for: initialDate)
Case 2:
targetTz.secondsFromGMT(for: initialDate) - initialTz.secondsFromGMT(for: initialDate)
secondsFromGMT takes the Date for which you want to know the offset, so in both cases the target offset should really be targetTz.secondsFromGMT(for: targetDate), which is a catch-22, since we don't know the target date yet. However, in most cases where the Dates are close, as they are here, targetTz.secondsFromGMT(for: initialDate) and targetTz.secondsFromGMT(for: targetDate) are equal - a bug only occurs when they differ, which happens when the time offset changes between the two Dates in the target time zone, e.g. for DST. Here is a bugged example for each case:
extension Date {
// case 1 (bugged)
func converting(from initTz: TimeZone, to targetTz: TimeZone) -> Date {
return self + Double(initTz.secondsFromGMT(for: self) - targetTz.secondsFromGMT(for: self))
}
// case 2 (bugged)
func convertingTime(from initTz: TimeZone, to targetTz: TimeZone) -> Date {
return self + Double(targetTz.secondsFromGMT(for: self) - initTz.secondsFromGMT(for: self))
}
}
let formatter = ISO8601DateFormatter()
// case 1
do {
// example values
let initTz = TimeZone(abbreviation: "GMT")!
let targetTz = TimeZone(abbreviation: "EST")!
let initDate = Calendar.current.date(from: .init(timeZone: initTz, year: 2020, month: 3, day: 8, hour: 4))!
// usage
let targetDate = initDate.converting(from: initTz, to: targetTz)
// print results
formatter.timeZone = targetTz // case 1 is concerned with what the `Date` looks like in the target time zone
print(formatter.string(from: targetDate)) // 2020-03-08T05:00:00-04:00 (should be 4am)
}
// case 2
do {
// example values
let initTz = TimeZone(abbreviation: "EST")!
let targetTz = TimeZone(abbreviation: "GMT")!
let initDate = Calendar.current.date(from: .init(timeZone: initTz, year: 2020, month: 3, day: 8, hour: 1))!
// usage
let targetDate = initDate.convertingTime(from: initTz, to: targetTz)
// print results
formatter.timeZone = targetTz // for case 2, find the initial `Date`'s time in the target time zone
print(formatter.string(from: initDate)) // 2020-03-08T06:00:00Z (the target date should have this same time)
formatter.timeZone = initTz // case 2 is concerned with what the `Date` looks like in the initial time zone
print(formatter.string(from: targetDate)) // 2020-03-08T07:00:00-04:00 (should be 6am)
}
If you adjust the example dates just a few hours forwards or backwards, the bug does not occur. Calendrical calculations are complex, and attempting to roll your own will almost always result in buggy edge cases. Since a time zone is a calendrical unit, to avoid bugs, you should use the existing Calendar interface, as in my initial example.
runs more effeciently
extension Date {
func convertToLocalTime(fromTimeZone timeZoneAbbreviation: String) -> Date? {
if let timeZone = TimeZone(abbreviation: timeZoneAbbreviation) {
let targetOffset = TimeInterval(timeZone.secondsFromGMT(for: self))
let localOffeset = TimeInterval(TimeZone.autoupdatingCurrent.secondsFromGMT(for: self))
return self.addingTimeInterval(targetOffset - localOffeset)
}
return nil
}
}
Based on mukaissi's answer, but the order of deductible in the expression has been corrected.
extension Date {
func convert(from initTimeZone: TimeZone, to targetTimeZone: TimeZone) -> Date {
let delta = TimeInterval(initTimeZone.secondsFromGMT() - targetTimeZone.secondsFromGMT())
return addingTimeInterval(delta)
}
}
Since NSDate is always in GMT/UTC the time zone only becomes relevant when displaying it to, or getting it from, the user. Just always assume it's UTC internally, convert it for the user (by setting it on the NSDateFormatter) as necessary, and you no longer have to worry about the problem.
So this is mukaissi's answer enhanced with valeCocoa's suggestion for daylight saving time:
func convert(from initTimeZone: TimeZone, to targetTimeZone: TimeZone) -> Date {
let delta = TimeInterval(targetTimeZone.secondsFromGMT(for: self) - initTimeZone.secondsFromGMT(for: self))
return addingTimeInterval(delta)
}
The answer from dbplunkett is exactly right that daylight saving time isn't effectively handled by using secondsFromGMT(for: date), however their extension example is for Calendar. The below extension is for date which achieves the same aim:
extension Date {
func convert(from timeZone: TimeZone, to destinationTimeZone: TimeZone) -> Date {
let calendar = Calendar.current
var components = calendar.dateComponents(in: timeZone, from: self)
components.timeZone = destinationTimeZone
return calendar.date(from: components)!
}
}
Details
Xcode 11.4.1 (11E503a), Swift 5.2
Solution 1
Based on mukaissi answer
import Foundation
extension Date {
func to(timeZone outputTimeZone: TimeZone, from inputTimeZone: TimeZone) -> Date {
let delta = TimeInterval(outputTimeZone.secondsFromGMT(for: self) - inputTimeZone.secondsFromGMT(for: self))
return addingTimeInterval(delta)
}
}
Usage of solution 1
let utcTimeZone = TimeZone(abbreviation: "UTC")!
let dateString = "2020-06-03T01:43:44.888Z"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
let date = dateFormatter.date(from: dateString)
print(date)
print(date?.to(timeZone: .autoupdatingCurrent, from: utcTimeZone))
print(date?.to(timeZone: .current, from: utcTimeZone))
print(date?.to(timeZone: TimeZone(abbreviation: "PDT")!, from: utcTimeZone))
Solution 2
Do not forget to paste the Solution 1 code here
extension DateFormatter {
func date(from string: String, timeZoneInString: TimeZone, outputTimeZone: TimeZone = .autoupdatingCurrent) -> Date? {
date(from: string)?.to(timeZone: outputTimeZone, from: timeZoneInString)
}
}
Usage of solution 2
let utcTimeZone = TimeZone(abbreviation: "UTC")!
let pdtTimeZone = TimeZone(abbreviation: "PDT")!
let dateString = "2020-06-03T01:43:44.888Z"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
print(dateFormatter.date(from: dateString))
print(dateFormatter.date(from: dateString, timeZoneInString: utcTimeZone))
print(dateFormatter.date(from: dateString, timeZoneInString: utcTimeZone, outputTimeZone: pdtTimeZone))
I suggest
you set the GMT timezone on your dateFormatter to get back directly a NSDate in UTC (having only NSDates in UTC is a good practice)
when you need to display it you use another NSDateFormatter with the local time zone set on it (it is by default)
when you need to send a date to your server, you use dateFormatter again to generate a string
Find GMT ( Time Difference )
func findGMT() -> (hour: Int, minute: Int, second: Int) {
var formatterOnlyHour: DateFormatter = {
let formatter1 = DateFormatter()
formatter1.dateFormat = "HH"
formatter1.locale = Locale(identifier: Locale.preferredLanguages.first ?? "en")
return formatter1
}()
var hourGMT: Int = (Int(formatterOnlyHour.string(from: Date())) ?? 0) - (Int(Date().preciseGMTTime) ?? 0)
// print("Your GMT: \(GMTvalue)")
var minuteGMT: Int = hourGMT * 60
var secondGMT: Int = hourGMT * 3600
return (hourGMT, minuteGMT, secondGMT)}
Extention 1:
extension Formatter {
// create static date formatters for your date representations
static let preciseLocalTime: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "HH"
return formatter
}()
static let preciseGMTTime: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "HH"
return formatter
}()}
Extention 2:
extension Date {
// you can create a read-only computed property to return just the nanoseconds from your date time
var nanosecond: Int { return Calendar.current.component(.nanosecond, from: self) }
// the same for your local time
var preciseLocalTime: String {
return Formatter.preciseLocalTime.string(for: self) ?? ""
}
// or GMT time
var preciseGMTTime: String {
return Formatter.preciseGMTTime.string(for: self) ?? ""
}}
How do you create a date object from a date in swift xcode.
eg in javascript you would do:
var day = new Date('2014-05-20');
Swift has its own Date type. No need to use NSDate.
Creating a Date and Time in Swift
In Swift, dates and times are stored in a 64-bit floating point number measuring the number of seconds since the reference date of January 1, 2001 at 00:00:00 UTC. This is expressed in the Date structure. The following would give you the current date and time:
let currentDateTime = Date()
For creating other date-times, you can use one of the following methods.
Method 1
If you know the number of seconds before or after the 2001 reference date, you can use that.
let someDateTime = Date(timeIntervalSinceReferenceDate: -123456789.0) // Feb 2, 1997, 10:26 AM
Method 2
Of course, it would be easier to use things like years, months, days and hours (rather than relative seconds) to make a Date. For this you can use DateComponents to specify the components and then Calendar to create the date. The Calendar gives the Date context. Otherwise, how would it know what time zone or calendar to express it in?
// Specify date components
var dateComponents = DateComponents()
dateComponents.year = 1980
dateComponents.month = 7
dateComponents.day = 11
dateComponents.timeZone = TimeZone(abbreviation: "JST") // Japan Standard Time
dateComponents.hour = 8
dateComponents.minute = 34
// Create date from components
let userCalendar = Calendar(identifier: .gregorian) // since the components above (like year 1980) are for Gregorian
let someDateTime = userCalendar.date(from: dateComponents)
Other time zone abbreviations can be found here. If you leave that blank, then the default is to use the user's time zone.
Method 3
The most succinct way (but not necessarily the best) could be to use DateFormatter.
let formatter = DateFormatter()
formatter.dateFormat = "yyyy/MM/dd HH:mm"
let someDateTime = formatter.date(from: "2016/10/08 22:31")
The Unicode technical standards show other formats that DateFormatter supports.
Notes
See my full answer for how to display the date and time in a readable format. Also read these excellent articles:
How to work with dates and times in Swift 3, part 1: Dates, Calendars, and DateComponents
How to work with dates and times in Swift 3, part 2: DateFormatter
How to work with dates and times in Swift 3, part 3: Date arithmetic
This is best done using an extension to the existing NSDate class.
The following extension adds a new initializer which will create a date in the current locale using the date string in the format you specified.
extension NSDate
{
convenience
init(dateString:String) {
let dateStringFormatter = NSDateFormatter()
dateStringFormatter.dateFormat = "yyyy-MM-dd"
dateStringFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
let d = dateStringFormatter.dateFromString(dateString)!
self.init(timeInterval:0, sinceDate:d)
}
}
Now you can create an NSDate from Swift just by doing:
NSDate(dateString:"2014-06-06")
Please note that this implementation does not cache the NSDateFormatter, which you might want to do for performance reasons if you expect to be creating many NSDates in this way.
Please also note that this implementation will simply crash if you try to initialize an NSDate by passing in a string that cannot be parsed correctly. This is because of the forced unwrap of the optional value returned by dateFromString. If you wanted to return a nil on bad parses, you would ideally use a failible initializer; but you cannot do that now (June 2015), because of a limitation in Swift 1.2, so then you're next best choice is to use a class factory method.
A more elaborate example, which addresses both issues, is here: https://gist.github.com/algal/09b08515460b7bd229fa .
Update for Swift 5
extension Date {
init(_ dateString:String) {
let dateStringFormatter = DateFormatter()
dateStringFormatter.dateFormat = "yyyy-MM-dd"
dateStringFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX") as Locale
let date = dateStringFormatter.date(from: dateString)!
self.init(timeInterval:0, since:date)
}
}
Swift doesn't have its own Date type, but you to use the existing Cocoa NSDate type, e.g:
class Date {
class func from(year: Int, month: Int, day: Int) -> Date {
let gregorianCalendar = NSCalendar(calendarIdentifier: .gregorian)!
var dateComponents = DateComponents()
dateComponents.year = year
dateComponents.month = month
dateComponents.day = day
let date = gregorianCalendar.date(from: dateComponents)!
return date
}
class func parse(_ string: String, format: String = "yyyy-MM-dd") -> Date {
let dateFormatter = DateFormatter()
dateFormatter.timeZone = NSTimeZone.default
dateFormatter.dateFormat = format
let date = dateFormatter.date(from: string)!
return date
}
}
Which you can use like:
var date = Date.parse("2014-05-20")
var date = Date.from(year: 2014, month: 05, day: 20)
Here's how I did it in Swift 4.2:
extension Date {
/// Create a date from specified parameters
///
/// - Parameters:
/// - year: The desired year
/// - month: The desired month
/// - day: The desired day
/// - Returns: A `Date` object
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
}
}
Usage:
let marsOpportunityLaunchDate = Date.from(year: 2003, month: 07, day: 07)
According to Apple documentation
Example :
var myObject = NSDate()
let futureDate = myObject.dateByAddingTimeInterval(10)
let timeSinceNow = myObject.timeIntervalSinceNow
According to #mythz answer, I decide to post updated version of his extension using swift3 syntax.
extension Date {
static func from(_ year: Int, _ month: Int, _ day: Int) -> Date?
{
let gregorianCalendar = Calendar(identifier: .gregorian)
let dateComponents = DateComponents(calendar: gregorianCalendar, year: year, month: month, day: day)
return gregorianCalendar.date(from: dateComponents)
}
}
I don't use parse method, but if someone needs, I will update this post.
In, Swift 3.0 you have set date object for this way.
extension Date
{
init(dateString:String) {
let dateStringFormatter = DateFormatter()
dateStringFormatter.dateFormat = "yyyy-MM-dd"
dateStringFormatter.locale = Locale(identifier: "en_US_POSIX")
let d = dateStringFormatter.date(from: dateString)!
self(timeInterval:0, since:d)
}
}
According to Apple's Data Formatting Guide
Creating a date formatter is not a cheap operation. If you are likely
to use a formatter frequently, it is typically more efficient to cache
a single instance than to create and dispose of multiple instances.
One approach is to use a static variable
And while I agree with #Leon that this should preferably be a failable initializer, it doesn't always have to be (just like there is UIImage(imageLiteralResourceName:) that will crash if the resource doesn't exist).
So here's my approach:
extension DateFormatter {
static let yyyyMMdd: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
formatter.calendar = Calendar(identifier: .iso8601)
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.locale = Locale(identifier: "en_US_POSIX")
return formatter
}()
}
extension Date {
init?(yyyyMMdd: String) {
guard let date = DateFormatter.yyyyMMdd.date(from: yyyyMMdd) else { return nil }
self.init(timeInterval: 0, since: date)
}
init(dateLiteralString yyyyMMdd: String) {
let date = DateFormatter.yyyyMMdd.date(from: yyyyMMdd)!
self.init(timeInterval: 0, since: date)
}
}
And now enjoy simply calling:
// For cases where the date exists for sure (eg. seed data)
Date(dateLiteralString: "2020-03-30")
// The rest of the time (eg. parsing unknown data)
guard let date = Date(yyyyMMdd: "2020-03-30") else { return nil }
Personally I think it should be a failable initialiser:
extension Date {
init?(dateString: String) {
let dateStringFormatter = DateFormatter()
dateStringFormatter.dateFormat = "yyyy-MM-dd"
if let d = dateStringFormatter.date(from: dateString) {
self.init(timeInterval: 0, since: d)
} else {
return nil
}
}
}
Otherwise a string with an invalid format will raise an exception.
I often have a need to combine date values from one place with time values for another. I wrote a helper function to accomplish this.
let startDateTimeComponents = NSDateComponents()
startDateTimeComponents.year = NSCalendar.currentCalendar().components(NSCalendarUnit.Year, fromDate: date).year
startDateTimeComponents.month = NSCalendar.currentCalendar().components(NSCalendarUnit.Month, fromDate: date).month
startDateTimeComponents.day = NSCalendar.currentCalendar().components(NSCalendarUnit.Day, fromDate: date).day
startDateTimeComponents.hour = NSCalendar.currentCalendar().components(NSCalendarUnit.Hour, fromDate: time).hour
startDateTimeComponents.minute = NSCalendar.currentCalendar().components(NSCalendarUnit.Minute, fromDate: time).minute
let startDateCalendar = NSCalendar(identifier: NSCalendarIdentifierGregorian)
combinedDateTime = startDateCalendar!.dateFromComponents(startDateTimeComponents)!
Since iOS15, Xcode 13 you can also create simple dates like this:
let date = try? Date("2022-02-14T20:15:00Z", strategy: .iso8601)