iOS TimeZone get three letter abbreviation - ios

I need to get three/four letter abbreviation for the country called 'Africa/Nairobi', I have tried using
print(TimeZone(abbreviation: "SAST")?.identifier) from the NSTimeZone.abbreviationDictionary, results nil.
How to get the three/four letter abbreviations for this country 'Africa/Nairobi'. Thanks in Advance

You can get a full standard localized name of timezone by the following method.
let timezone:TimeZone = TimeZone.init(identifier: "Africa/Nairobi") ?? TimeZone.current
print(timezone.localizedName(for: .generic, locale: .autoupdatingCurrent))
print(timezone.localizedName(for: .standard, locale: .autoupdatingCurrent))
Will give you following output
Optional("East Africa Time")
Optional("East Africa Time")
I think now you get easily get EAT form East Africa Time by spliting and combining string
let fullName = timezone.localizedName(for: .standard, locale: .autoupdatingCurrent) ?? ""
var result = ""
fullName.enumerateSubstrings(in: fullName.startIndex..<fullName.endIndex, options: .byWords) { (substring, _, _, _) in
if let substring = substring { result += substring.prefix(1) }
}
print(result)
Will give you out put
EAT

Related

iOS Swift: looking for ranges of matching word in a string

I need to make a function that returns me ranges of matching words in a given string, for example, given the sentence below:
Hey, bro! Your brother is also her brother.
I want to find an array of Range in the sentence that matches the word "bro", it should match the exact word (case insensitive), so "bro" should only match "bro" but not "brother".
I thought about:
split the sentence, e.g. "hey", "bro", "your", "brother", "is", "also", "her", "brother"
map each word to a word with range, e.g. "hey" would become ["hey", 0...2]
filter and map the word and range array, matching "bro"
Step 2 needs some treatment to make sure the range for each word (in the sentence) can be mapped to the right word, e.g. the first "brother" and second "brother" should have different ranges depending on where they are located.
Is there any smarter way of doing this?
Edit:
Sorry, I forgot to mention, the reason for not using Regex was that sometimes the word has a dot in it, for example:
there is orange in the basket.
from the above sentence, finding the string "or.ge" using regex would match "orange" as well.
I have tested in Playground, You can use this extension to get the values matching this reg ex.
extension String {
func ranges(of substring: String, options: CompareOptions = [], locale: Locale? = nil) -> [Range<Index>] {
var ranges: [Range<Index>] = []
while ranges.last.map({ $0.upperBound < self.endIndex }) ?? true,
let range = self.range(of: substring, options: options, range: (ranges.last?.upperBound ?? self.startIndex)..<self.endIndex, locale: locale)
{
ranges.append(range)
}
return ranges
}
}
let searchString = "bro"
var str = "Hey, bro! Your brother is also her brother."
var reg = str.ranges(of: "(?<![\\p{L}\\d])\(searchString)(?![\\p{L}\\d])", options: [.regularExpression, .caseInsensitive])
str.removeSubrange(reg.first!)
print(str)
Credits to,
iOS - regex to match word boundary, including underscore
One simple solution is to use regular expressions with \b to match “word boundaries”, e.g.
let searchString = "bro"
let sentence = "Hey, Bro! Your brother is also her brother."
let regex = try! NSRegularExpression(pattern: #"\b\#(searchString)\b"#, options: .caseInsensitive)
regex.enumerateMatches(in: sentence, range: NSRange(sentence.startIndex..., in: sentence)) { match, _, _ in
guard let match = match else { return }
print(match.range)
// or, if you want a String.Range
if let range = Range(match.range, in: sentence) {
print(sentence[range])
}
}
There are other richer API (e.g. the Natural Language framework), which, while not perfect, provide richer parsing of natural language text. For example, the below will differentiate between the verb “saw” and noun “saw”:
import NaturalLanguage
let text = "I saw the hammer. I did not see a saw."
let tagger = NLTagger(tagSchemes: [.lexicalClass])
tagger.string = text
let options: NLTagger.Options = [.omitWhitespace, .joinContractions]
tagger.enumerateTags(in: text.startIndex..<text.endIndex, unit: .word, scheme: .lexicalClass, options: options) { tag, range in
guard let tag = tag else { return true }
print(tag, String(text[range]))
return true
}
Producing:
NLTag(_rawValue: Pronoun) I
NLTag(_rawValue: Verb) saw
NLTag(_rawValue: Determiner) the
NLTag(_rawValue: Noun) hammer
NLTag(_rawValue: SentenceTerminator) .
NLTag(_rawValue: Pronoun) I
NLTag(_rawValue: Verb) did
NLTag(_rawValue: Adverb) not
NLTag(_rawValue: Verb) see
NLTag(_rawValue: Determiner) a
NLTag(_rawValue: Noun) saw
NLTag(_rawValue: SentenceTerminator) .

How to convert timezone format in iOS?

I got timezone format like this  GMT+5:30.
TimeZone.current.abbreviation(), this will return string value like: //GMT+5:30
 
But I need to convert the above format to Asia/Kolkata
How to solve this issue?
Instead of calling:
TimeZone.current.abbreviation()
call:
TimeZone.current.identifier
In your case you will get Asia/Kolkata instead of GMT+5:30.
But let's assume you only have a string with a timezone abbreviation such as "GMT+5:30". You can't easily convert that to a specific timezone identifier because there can be more than one timezone at a given time offset.
Here's a little function that creates a timezone from the abbreviation string and then finds all matching timezone identifiers that have the same offset.
func matchingTimeZones(abbreviation: String) -> [TimeZone]? {
if let tz = TimeZone(abbreviation: tzstr) {
return TimeZone.knownTimeZoneIdentifiers.compactMap { TimeZone(identifier: $0) }.filter { $0.secondsFromGMT() == tz.secondsFromGMT() }
} else {
return nil
}
}
You can get the matching list for "GMT+5:30" with:
let matches = matchingTimeZones(abbreviation: "GMT+5:30")
If you print that result you will see one of them is "Asia/Calcutta" (in an English locale).

Detect numeric year from string using Swift

I am looking for a way to detect years e.g. 2019. The requirements I think would be that the numbers are in a row, have four digits and are not adjacent to letters or special characters. So I'd like to get the method to return "2019" in each of the following cases:
"2019"
"in 2019
"Before 2019, all IOS apps were written in one of 2 ways"
But exclude it from:
"1234z20191234
There are a lot of ways to detect the numbers in a string as seen here such as
let newString = origString
.components(separatedBy:CharacterSet.decimalDigits.inverted)
.joined(separator: "")
But they don't pull out each series of numbers to test for length and adjacency.
Data detector can try to pull out a date but going from the possible date to the year that might have been in the starting text seems error prone e.g. working with something like:
“2018-08-31 04:00:00 +0000”, “America/Los_Angeles”, 18000.0
Can anyone suggest a reliable way to retrieve a numerical year from a string?
You might use regular expression, searching for four digits ([0-9]{4}) between word boundaries (\b), e.g.
let strings = [
"2019",
"in 2019",
"Before 2019, all IOS apps were written in one of 2 ways",
"1234z20191234"
]
for string in strings {
if let range = string.range(of: #"\b[0-9]{4}\b"#, options: .regularExpression) {
let year = String(string[range])
print(year)
} else {
print("No year found")
}
}
Producing:
2019
2019
2019
No year found
Note, the above uses Swift 5’s Extended String Delimiters, #" and "#. If doing this in earlier Swift versions, you’ll want to just escape the backslashes with yet another backslash:
if let range = string.range(of: "\\b[0-9]{4}\\b", options: .regularExpression) {
...
}
Clearly if you want to narrow the range of four digit numbers recognized as years, you’d have to tweak the regular expression or supplement it with some logic. But your question doesn’t identify what criteria you want to use to detect years.
You can do this with a regular expression. This code will find all years in a given string. You can set the check to confirm the number is within whatever range you wish to accept.
func findYears(in text: String) -> [String] {
let yearRE = try! NSRegularExpression(pattern: "(?:\\b)[0-9]{4}(?:\\b)")
var results = [String]()
yearRE.enumerateMatches(in: text, range: NSRange(text.startIndex..<text.endIndex, in: text)) { (result, flags, stop) in
if let result = result {
let match = String(text[Range(result.range(at: 0), in: text)!])
// Set whatever range you wish to accept
if let year = Int(match), year > 1600 && year < 2200 {
results.append(match)
}
}
}
return results
}
let yearStrings = [
"2019",
"in 2019 and 2020",
"Before 2019, all IOS apps were written in one of 2 ways",
"1234z20191234",
"2018-08-31 04:00:00 +0000",
]
for yearString in yearStrings {
print(findYears(in: yearString))
}
Output:
["2019"]
["2019", "2020"]
["2019"]
[]
["2018"]

Can Locale identify country by currency code?

I was looking for that answer in here, and also on official documentation, but I can't find the answer for that. I've got currency codes (e.g. "EUR"). I need to get country code (e.g "EU"). So I've seen that I can do it inversely (getting currency code by using country code), but I was trying to change this solution for my needs, and I got nil as result. Also I know I can use simple solution - remove last letter from currency code to get country code (It works for most cases from my API data, but not everywhere) - but I feel this approach is not safe. So my question is like in title: Can I identify country from currency code by using Locale?
Here is my approach:
with NSLocale
extension NSLocale {
static func currencySymbolFromCode(code: String) -> String? {
let localeIdentifier = NSLocale.localeIdentifier(fromComponents: [NSLocale.Key.currencyCode.rawValue : code])
let locale = NSLocale(localeIdentifier: localeIdentifier)
return locale.object(forKey: NSLocale.Key.countryCode) as? String
}
}
with Locale
extension Locale {
static let countryCode: [String: String] = Locale.isoCurrencyCodes.reduce(into: [:]) {
let locale = Locale(identifier: Locale.identifier(fromComponents: [NSLocale.Key.currencyCode.rawValue: $1]))
$0[$1] = (locale.regionCode)
}
}
A currency can be associated with multiple countries, you should create your own custom data implementation, such as following json
{
"EUR":"PL",
"USD":"CA"
}
Create a file in your Xcode project and name it for e.g. data.json, and paste such JSON as above, finally use following method
func getCountryCode(currency: String) -> String? {
var countryCode: String?
let url = Bundle.main.url(forResource: "data", withExtension: "json")!
do {
let data = try Data(contentsOf: url)
let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: String]
countryCode = json?[currency]
} catch {
print(error.localizedDescription)
}
return countryCode
}
Usage
if let countryCode = getCountryCode(currency: "EUR") {
print(countryCode)
}
The answer from #AamirR is a bit not accurate.
EUR != PL, PL = PLN = Poland
USD != CA, CA = CAD = Canada
In majority of cases (with some exceptions), first 2 letters of currency code = ISO 3166-2 country code:
PLN = PL = Poland
GBP = GB = Great Britain
USD = US = United States of America
AMD = AM = Armenia
CZK = CZ = Czech Republic
BRL = BR = Brazilia
and so on ...
So if you have currency codes you can get country names (with 99% accuracy) as follows:
let currencyCode = "USD"
let countryCode = String(currencyCode.prefix(2))
let countryName = Locale.current.localizedString(forRegionCode: countryCode)
print(countryName)
Optional("United States")
However, as some commented above, EU for example, is not a country, and looks like in your specific case you do not need a country flag, you need "currency flag", so for EUR you will display EU flag, which is correct, as you cant determine which EU country flag to display - France, Germany, etc ... so this approach might work for mapping currencies to countries flags.
Though there are some exceptions, in terms of first 2 letters of currency code not matching first 2 letters of country name, from ISO 3166-2 country code perspective, they still match:
CHF = CH = Switzerland
HRK = HR = Croatia
DKK = DK = Denmark
... and so on
The other interesting pattern is that first 2 letters of currency codes, would usually match country internet domain, but again, there are some exceptions from this too.

iOS Calendar App Time Zone List

As you can see in the screen below, iOS Calendar app shows the list of available time zone in City, Country format.
I want to attain the same list. Where can I get this list?
You cannot get a "City, County" format, but you can get a "City, Continent" format using this piece of code:
let timeZones = TimeZone.knownTimeZoneIdentifiers.flatMap{ id->String? in
let components = id.components(separatedBy: "/")
guard components.count == 2, let continent = components.first, let city = components.last else {return nil}
return "\(city), \(continent)"
}
Output will be:
"Abidjan, Africa", "Accra, Africa", "Addis_Ababa, Africa", "Algiers, Africa", "Asmara, Africa", "Bamako, Africa", "Bangui, Africa", "Banjul, Africa"...
TimeZone has a static function knownTimeZoneIdentifiers which returns an array of String identifiers. Documentation

Resources