so in my app, I need to deal with date like \/Date(1440156888750-0700)\/, i think the first part stands for the seconds from Jan 1st, 1970 and the second part is the timezone.
I don't know how to handle data like this and display it the way we all understand in Xcode 7 using Swift 2?
(The previous version of this answer was actually wrong, it did not handle the time zone correctly.)
According to Stand-Alone JSON Serialization:
DateTime values appear as JSON strings in the form of "/Date(700000+0500)/", where the first number (700000 in the example provided) is the number of milliseconds in the GMT time zone, regular (non-daylight savings) time since midnight, January 1, 1970. The number may be negative to represent earlier times. The part that consists of "+0500" in the example is optional and indicates that the time is of the Local kind - that is, should be converted to the local time zone on deserialization. If it is absent, the time is deserialized as Utc. The actual number ("0500" in this example) and its sign (+ or -) are ignored.
and Use JSON.NET to parse json date of format Date(epochTime-offset)
... In this [screwy format][1], the timestamp portion is still based solely on UTC. The offset is extra information. It doesn't change the timestamp. You can give a different offset, or omit it entirely and it's still the same moment in time.
the first number in \/Date(1440156888750-0700)\/ is the number of
milliseconds since the "epoch" January 1, 1970 GMT, and the time
zone part -0700 must simply be ignored.
Here is a Swift 5 extension method for Date which checks
the validity of the string with a regular expression
(accepting both \/Date(...)\/ and /Date(...)/, with or without
a time zone specification) and converts the given number of
milliseconds to a Date:
extension Date {
init?(jsonDate: String) {
let pattern = #"\\?/Date\((\d+)([+-]\d{4})?\)\\?/"#
let regex = try! NSRegularExpression(pattern: pattern)
guard let match = regex.firstMatch(in: jsonDate, range: NSRange(jsonDate.startIndex..., in: jsonDate)) else {
return nil
}
// Extract milliseconds:
let dateString = jsonDate[Range(match.range(at: 1), in: jsonDate)!]
// Convert to UNIX timestamp in seconds:
let timeStamp = Double(dateString)! / 1000.0
// Create Date from timestamp:
self.init(timeIntervalSince1970: timeStamp)
}
}
Example:
let jsonDate = "\\/Date(1440156888750-0700)\\/"
print("JSON Date:", jsonDate)
if let theDate = Date(jsonDate: jsonDate) {
print("Date:", theDate)
} else {
print("wrong format")
}
Output:
JSON Date: \/Date(1440156888750-0700)\/
Date: 2015-08-21 11:34:48 +0000
(Versions for Swift 3 and Swift 4 can be found in the edit history.)
After a bit of experimenting around I came up with the following solution:
let dx = "/Date(1440156888750-0700)/"
let timestamp = (dx as NSString).substringWithRange(NSRange(location: 6,length: 13))
let timezone = (dx as NSString).substringWithRange(NSRange(location: 19,length: 5))
let dateIntermediate = NSDate(timeIntervalSince1970: Double(timestamp)! / 1000)
let outp = NSDateFormatter()
outp.dateFormat = "dd.MM.yyyy hh:mm::ssSSS"
outp.timeZone = NSTimeZone(forSecondsFromGMT: 0)
let input = outp.stringFromDate(dateIntermediate) + " " + timezone
let inp = NSDateFormatter()
inp.dateFormat = "dd.MM.yyyy hh:mm::ssSSS Z"
let finalDate = inp.dateFromString(input)
print(finalDate)
Let me explain:
we extract the millisecond timestamp and the timezone from the original string
we create a date from the timestamp to be able to split it into its different components
we output that date in a more standard way (not as timestamp) and append the previously extracted timezone to that string
we then read that string and parse a date from it again
Note
As #Phoen1xUK mentioned the timestamp might have a different length than 13 digits. You can handle that situation by stripping the /Date( and )/ and then splitting the string before the - (or +).
Related
I am parsing JSON data from a server, which I can set to 'UK' or 'Asia'
Function:
func dateToStringConverter(jsonDate: String) -> String {
let isoDate = jsonDate
let dateFormatter = ISO8601DateFormatter()
let processedDate = dateFormatter.date(from:isoDate)!
return processedDate.asString()}
Extension:
extension Date {
func asString() -> String {
let template = "EEEEd MMMM, h:mm a"
let formatter = DateFormatter()
let format = DateFormatter.dateFormat(fromTemplate: template, options: 0, locale: NSLocale.current)
formatter.dateFormat = format
return formatter.string(from: self)}}
print(dateToStringConverter(jsonDate: jsonDate))
Then, the following attribute from the server differs based on if I set 'UK or 'Asia':
let jsonDate = "2020-10-05T02:15:00+08:00" // server set to Asia
let jsonDate = "2020-10-04T19:15:00+01:00" // server set to UK
However, BOTH these values result in the SAME OUTPUT from the print statement!
Sunday, October 4, 7:15 PM
I believe what is happening here is, even if I set the server to Asia, Swift knows I am in the UK, and therefore prints Sunday, October 4, 7:15 PM
Is this correct?
If so, is there any way to mimic what print statement from Asia would see? I have tried changing the region in the simulator, but this didn't seem to make a difference
Thanks
The good thing about zoned ISO 8601 timestamps is that they represent a specific moment in UTC time. They do not contain information about which time zone they're from, as this is highly ambiguous. If you aim for easier human comparability, understandability and a reduction in ambiguity, I suggest going for UTC time.
In stead of
2020-01-01T02:00:00+01:00
you'd have
2020-01-01T01:00:00Z
Z is a shortcut for +00:00
When it ends in Z, it's always UTC time and thus easily comparable.
I'm adding a second to an instance of Foundation's date, but the result is off by an entire minute.
var calendar = Calendar(identifier: .iso8601)
calendar.locale = Locale(identifier: "en")
calendar.timeZone = TimeZone(identifier: "GMT")!
let date1 = Date(timeIntervalSinceReferenceDate: -62544967141.9)
let date2 = calendar.date(byAdding: DateComponents(second: 1),
to: date1,
wrappingComponents: true)!
ISO8601DateFormatter().string(from: date1) // => 0019-01-11T22:00:58Z
ISO8601DateFormatter().string(from: date2) // => 0019-01-11T21:59:59Z
Interestingly, one of the following makes the error go away:
round time interval since reference date
don't add time zone to calendar
set wrappingComponents to false (even though it shouldn't wrap in this case)
I don't really need sub-second precision in my code, so I created this extension that allows me to discard it.
extension Date {
func roundedToSeconds() -> Date {
return Date(timeIntervalSinceReferenceDate: round(timeIntervalSinceReferenceDate))
}
}
I want to know this:
Why does this error happen?
Am I doing something wrong?
Is there any issue with my workaround?
Why does this error happen?
I would say this is a bug in Core Foundation (CF).
Calendar.date(byAdding:to:wrappingComponents:) calls down to the internal Core Foundation function _CFCalendarAddComponentsV, which in turn uses the ICU Calendar C API. ICU represents a time as an floating-point number of milliseconds since the Unix epoch, while CF uses a floating-point number of seconds since the NeXT reference date. So CF has to convert its representation to ICU's representation before calling into ICU, and convert back to return the result to you.
Here's how it converts from a CF timestamp to an ICU timestamp:
double startingInt;
double startingFrac = modf(*atp, &startingInt);
UDate udate = (startingInt + kCFAbsoluteTimeIntervalSince1970) * 1000.0;
The modf function splits a floating-point number into its integer and fractional parts. Let's plug in your example date:
var startingInt: Double = 0
var startingFrac: Double = modf(date1.timeIntervalSinceReferenceDate, &startingInt)
print(startingInt, startingFrac)
// Output:
-62544967141.0 -0.9000015258789062
Next, CF calls __CFCalendarAdd to add one second to -62544967141. Note that -62544967141 lies in the round one-minute interval -62544967200 ..< -62544967140.0. So when CF adds one second to -62544967141, it gets -62544967140, which would be in the next round one-minute interval. Since you specified wrapping components, CF isn't allowed to change the minute part of the date, so it wraps back to the beginning of the original round one-minute interval, -62544967200.
Finally, CF converts the ICU time back to a CF time, adding in the fractional part of the original time:
*atp = (udate / 1000.0) - kCFAbsoluteTimeIntervalSince1970 + startingFrac + (nanosecond * 1.0e-9);
So it returns -62544967200 + -0.9000015258789062 = -62544967200.9, exactly 59 seconds earlier than the input time.
Am I doing something wrong?
No, the bug is in CF, not in your code.
Is there any issue with my workaround?
If you don't need sub-second precision, your workaround should be fine.
I can reproduce it with more recent dates but so far only with negative reference dates, e.g. Date(timeIntervalSinceReferenceDate: -1008899941.9), which is 1969-01-11T22:00:58Z.
Any negative timeIntervalSinceReferenceDate in the last second of a minute interval should cause the problem. The bug effectively makes the first round whole minute prior to time 0 span from -60.99999999999999 through -1.0, but it should span from -60.0 through -5e324. All more-negative round minute intervals are similarly offset.
I am trying to use Date class which is provided from Swift 3 library. I am not sure if I am doing it right.
When I print Date it prints correct date, but when I try to convert it from date to string it changes the date to something else.
let today = Date()
print(" Date object : \(today)")
let format = DateFormatter()
format.dateFormat = "mm/dd/yy"
print(" Date to String : \(format.string(from: today)")
Which gives the output:
Date object : 2017-06-03 18:13:39 +0000
Date to String : 13/03/17
mm is the format specifier for minutes, hence why the output returns 13 instead of 06, which is the time in minutes at which you called Date().
You'll need to use MM to display the month.
See the unicode report on date specifiers for more information: http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Format_Patterns
No it doesnt.
Your format is Minute, Day and Year.
Works exactly as it should.
Try MM istead of mm.
This question already has answers here:
How to Convert UNIX epoch time to Date and time in ios swift
(3 answers)
Closed 2 years ago.
I am doing this in swift:
let date = NSDate(timeIntervalSince1970: 1432233446145.0)
println("date is \(date)")
The log gives me this:
date is 47355-09-02 23:55:45 +0000
Then I try to get an interval back out just for testing:
let tI = expirationDate.timeIntervalSinceDate(date)
println("tI = \(tI)")
I get:
tI = 0.0
What am I doing wrong?
I can seem to make the timeIntervalSince1970 call work properly. Is there any known issued with that in Swift, or am I missing something here?
1432233446145 most probably is a time interval given in milliseconds:
let date = NSDate(timeIntervalSince1970: 1432233446145.0/1000.0)
print("date is \(date)")
// date is 2015-05-21 18:37:26 +0000
Swift 3 and later:
let date = Date(timeIntervalSince1970: 1432233446145.0/1000.0)
I think your timestamp is incorrect. This results in your date being september 2nd, 47355.
If I use the following I get the date for (right about) now:
let date = NSDate(timeIntervalSince1970: 1431024488)
println("date is \(date)")
// "date is 2015-05-07 18:48:08 +0000"
The printed date is not a localized timestamp, so you'll have to do some localization of your own I suppose. An example:
let formatter = NSDateFormatter()
formatter.dateFormat = "dd-MM-yyyy HH:mm:ss"
println("formatted date is \(formatter.stringFromDate(date))")
// "formatted date is 07-05-2015 20:48:08"
And for completeness I also checked with an expiration date that's 1100 seconds larger than the initial date:
let expirationDate = NSDate(timeIntervalSince1970: 1431025588)
let diff = expirationDate.timeIntervalSinceDate(date)
println("expires in: \(diff)")
// "expires in: 1100.0"
So, the timeIntervalSince1970 seems to work fine, I think your interval was just not what you wanted it to be.
From your code and the log content follows:
date.isEqual(expirationDate)
--> Your stuff has just expired :-).
I'm trying to convert the UNIX epoc time to datetime format using the below code
var epocTime = NSTimeInterval(1429162809359)
let myDate = NSDate(timeIntervalSince1970: epocTime)
println("Converted Time \(myDate)")
the actual result is (Thu, 16 Apr 2015 05:40:09 GMT) but am getting something like (47258-05-14 05:15:59 +0000) Can anyone please tel me how to achieve this.
update: Xcode 8.2.1 • Swift 3.0.2 or later
You need to convert it from milliseconds dividing it by 1000:
let epochTime = TimeInterval(1429162809359) / 1000
let date = Date(timeIntervalSince1970: epochTime) // "Apr 16, 2015, 2:40 AM"
print("Converted Time \(date)") // "Converted Time 2015-04-16 05:40:09 +0000\n"
Swift 5
I am dealing with a date in a JSON api which is defined as an Int and an example of the timestamp is 1587288545760 (UTC)
The only way I could display that value as a Date in a way that made any sense was to truncate the last 3 digits and convert THAT resultant date to "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
This was the function I created to achieve that.
func convertDate(dateValue: Int) -> String {
let truncatedTime = Int(dateValue / 1000)
let date = Date(timeIntervalSince1970: TimeInterval(truncatedTime))
let formatter = DateFormatter()
formatter.timeZone = TimeZone(abbreviation: "UTC")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
return formatter.string(from: date)
}
It works for me and I end up with a date that looks like this:
"2020-04-19T09:29:05.000Z"
..and it reflects the fact that the original time stamp is exactly that date.
Hope that helps anyone having the same issue.
It seems that your time information is "milliseconds since 1970". Should have been straightforward to figure out: We are about 46 years after 1970, but your date is about 45,000 after 1970.
NSTimeInterval is based on SECONDS.
Convert your number to double and divide by 1000.0.