Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 months ago.
Improve this question
I ran into such a problem: from the server I receive such a figure in the string format "20760.326586753041" (example), but I want to change it so that the user's screen has such a figure 20,761.93.
How can I format it?
Tried to do like this:
func setup(coin: Coin) {
self.nameCoin.text = coin.name
self.symbolCoin.text = coin.symbol
self.priceCoin.text = String(format: "%.2f", coin.priceUsd)
}
Show 0.0 on screen
You should use a NumberFormatter
import Foundation
let coinFormatter : NumberFormatter = {
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 2
formatter.numberStyle = .decimal
formatter.locale = Locale(identifier: "en-US")
return formatter
}()
let stringFromServer = "20760.326586753041"
if let value = coinFormatter.number(from: stringFromServer),
let reformattedString = coinFormatter.string(for: value) {
print(reformattedString)
}
I create a number formatter called coinFormatter that format numbers into decimals that have at most 2 decimal places. The code below that shows how you might use such a number formatter to convert the string from the server to a number, then the number back to a string with the expected format.
It also looks like you might be trying to format the number as a currency value. There are mechanisms in NumberFormatter for properly formatting currency values that you should look into as well.
P.S. As you will see in the comments below, NumberFormatter takes into account many complexities like the Locale and common radix marks used, how you want negative numbers represented, or whether the currency symbol should be written before or after the number when representing money. Please take the time to learn more about NumberFormatter and the power it offers you.
I need to set the timezone information of a remote clock to the one on the iOS device.
The remote clock only supports GNU lib C TZ format of:
std offset dst [offset],start[/time],end[/time]
e.g: EST+5EDT,M3.2.0/2,M11.1.0/2
So I need to produce a string similar to above from NSTimeZone.local time zone in Swift. Can't seem to access the current timezone rules as they would be in the IANA TZ database to produce the output.
Can this be done without the horrifying idea of caching a local copy of the TZ database in the app?
Update:
I haven't been able to find anything useful even through other programming languages. The best I was able to find was essentially parsing the tzfile in linux and making my own NSDictionary containing the info.
This was a fun exploration, largely because fitting the data into just the right format is pretty complex. Problem components:
We need the "current" TZ database rule that applies for a given time zone. This is a bit of a loaded concept, because:
Darwin platforms don't actually use the TZ database directly for most applications, but instead use ICU's time zone database, which comes in a different format and is more complex. Even if you produce a string in this format, it's not necessarily descriptive of the actual time behavior on device
While it is possible to read and parse the TZ database on iOS dynamically, the TZ database itself is not guaranteed to store information in the format needed here. rfc8536, the RFC governing the Time Zone Information Format says the following about the format you want:
The TZ string in a version 3 TZif file MAY use the following extensions to POSIX TZ strings. These extensions are described using the terminology of Section 8.3 of the "Base Definitions" volume of [POSIX].
Example: <-03>3<-02>,M3.5.0/-2,M10.5.0/-1
Example: EST5EDT,0/0,J365/25
While spelunking through the iOS TZ database, I found some database entries that do offer a rule at the end of the file in this format, but they appear to be a minority. You could parse these dynamically, but it's likely not worth it
So, we need to use APIs to produce a string in this format.
In order to produce a "rule" that is at least approximately correct on a given date, you need to know information about DST transitions around that date. This is an extremely thorny topic, because DST rules change all the time, and don't always make as much sense as you'd hope. At the very least:
Many time zones in the Northern hemisphere observe DST beginning in the spring and ending in the fall
Many time zones in the Southern hemisphere observe DST beginning in the fall and ending in the spring
Some time zones don't observe DST (are in standard time year-round)
Some time zones don't observe DST and are in daylight time year-round
Because the rules are so complex, the rest of this answer assumes you're okay with producing a "good enough" answer that represents a specific date in time, and is willing to send further strings to your clock some time in the future when corrections are needed. e.g., to describe "now", we will be assuming that producing a rule based off of the last DST transition (if any) and the next DST transition (if any) is "good enough", but this may not work for all situations in many time zones
Foundation provides DST transition information on TimeZone in the form of TimeZone.nextDaylightSavingTimeTransition/TimeZone.nextDaylightSavingTimeTransition(after:). Frustratingly, however, there's no way to get information about previous DST transitions, so we'll need to rectify that:
Foundation's localization support (including calendars and time zones) is based directly on the ICU library, which ships internally on all Apple platforms. ICU does provide a way to get information about previous DST transitions, but Foundation just doesn't offer this as API, so we'll need to expose it ourselves
ICU is a semi-private library on Apple platforms. The library is guaranteed to be present, and Xcode will offer you libicucore.tbd to link against in <Project> > <Target> > Build Phases > Link Binary with Libraries, but the actual headers and symbols are not directly exposed to apps. You can successfully link against libicucore, but you'll need to forward-declare the functionality we need in an Obj-C header imported into Swift
Somewhere in the Swift project, we need to expose the following ICU functionality:
#include <stdint.h>
typedef void * _Nonnull UCalendar;
typedef double UDate;
typedef int8_t UBool;
typedef uint16_t UChar;
typedef enum UTimeZoneTransitionType {
UCAL_TZ_TRANSITION_NEXT,
UCAL_TZ_TRANSITION_NEXT_INCLUSIVE,
UCAL_TZ_TRANSITION_PREVIOUS,
UCAL_TZ_TRANSITION_PREVIOUS_INCLUSIVE,
} UTimeZoneTransitionType;
typedef enum UCalendarType {
UCAL_TRADITIONAL,
UCAL_DEFAULT,
UCAL_GREGORIAN,
} UCalendarType;
typedef enum UErrorCode {
U_ZERO_ERROR = 0,
} UErrorCode;
UCalendar * _Nullable ucal_open(const UChar *zoneID, int32_t len, const char *locale, UCalendarType type, UErrorCode *status);
void ucal_setMillis(const UCalendar * _Nonnull cal, UDate date, UErrorCode * _Nonnull status);
UBool ucal_getTimeZoneTransitionDate(const UCalendar * _Nonnull cal, UTimeZoneTransitionType type, UDate * _Nonnull transition, UErrorCode * _Nonnull status);
These are all forward declarations / constants, so no need to worry about implementation (since we get that by linking against libicucore).
You can see the values in UTimeZoneTransitionType — TimeZone.nextDaylightSavingTimeTransition just calls ucal_getTimeZoneTransitionDate with a value of UCAL_TZ_TRANSITION_NEXT, so we can offer roughly the same functionality by calling the method with UCAL_TZ_TRANSITION_PREVIOUS:
extension TimeZone {
func previousDaylightSavingTimeTransition(before: Date) -> Date? {
// We _must_ pass a status variable for `ucal_open` to write into, but the actual initial
// value doesn't matter.
var status = U_ZERO_ERROR
// `ucal_open` requires the time zone identifier be passed in as UTF-16 code points.
// `String.utf16` doesn't offer a contiguous buffer for us to pass directly into `ucal_open`
// so we have to create our own by copying the values into an `Array`, then
let timeZoneIdentifier = Array(identifier.utf16)
guard let calendar = Locale.current.identifier.withCString({ localeIdentifier in
ucal_open(timeZoneIdentifier, // implicit conversion of Array to a pointer, but convenient!
Int32(timeZoneIdentifier.count),
localeIdentifier,
UCAL_GREGORIAN,
&status)
}) else {
// Figure out some error handling here -- we failed to find a "calendar" for this time
// zone; i.e., there's no time zone date for this time zone.
//
// With more enum cases copied from `UErrorCode` you may find a good way to report an
// error here if needed. `u_errorName` turns a `UErrorCode` into a string.
return nil
}
// `UCalendar` functions operate on the calendar's current timestamp, so we have to apply
// `date` to it. `UDate`s are the number of milliseconds which have passed since January 1,
// 1970, while `Date` offers its time interval in seconds.
ucal_setMillis(calendar, before.timeIntervalSince1970 * 1000.0, &status)
var result: UDate = 0
guard ucal_getTimeZoneTransitionDate(calendar, UCAL_TZ_TRANSITION_PREVIOUS, &result, &status) != 0 else {
// Figure out some error handling here -- same as above (check status).
return nil
}
// Same transition but in reverse.
return Date(timeIntervalSince1970: result / 1000.0)
}
}
So, with all of this in place, we can fill out a crude method to produce a string in the format you need:
extension TimeZone {
struct Transition {
let abbreviation: String
let offsetFromGMT: Int
let date: Date
let components: DateComponents
init(for timeZone: TimeZone, on date: Date, using referenceCalendar: Calendar) {
abbreviation = timeZone.abbreviation(for: date) ?? ""
offsetFromGMT = timeZone.secondsFromGMT(for: date)
self.date = date
components = referenceCalendar.dateComponents([.month, .weekOfMonth, .weekdayOrdinal, .hour, .minute, .second], from: date)
}
}
func approximateTZEntryRule(on date: Date = Date(), using calendar: Calendar? = nil) -> String? {
var referenceCalendar = calendar ?? Calendar(identifier: .gregorian)
referenceCalendar.timeZone = self
guard let year = referenceCalendar.dateInterval(of: .year, for: date) else {
return nil
}
// If no prior DST transition has ever occurred, we're likely in a time zone which is either
// standard or daylight year-round. We'll cap the definition here to the very start of the
// year.
let previousDSTTransition = Transition(for: self, on: previousDaylightSavingTimeTransition(before: date) ?? year.start, using: referenceCalendar)
// Same with the following DST transition -- if no following DST transition will ever come,
// we'll cap it to the end of the year.
let nextDSTTransition = Transition(for: self, on: nextDaylightSavingTimeTransition(after: date) ?? year.end, using: referenceCalendar)
let standardToDaylightTransition: Transition
let daylightToStandardTransition: Transition
if isDaylightSavingTime(for: date) {
standardToDaylightTransition = previousDSTTransition
daylightToStandardTransition = nextDSTTransition
} else {
standardToDaylightTransition = nextDSTTransition
daylightToStandardTransition = previousDSTTransition
}
let standardAbbreviation = daylightToStandardTransition.abbreviation
let standardOffset = formatOffset(daylightToStandardTransition.offsetFromGMT)
let daylightAbbreviation = standardToDaylightTransition.abbreviation
let startDate = formatDate(components: standardToDaylightTransition.components)
let endDate = formatDate(components: daylightToStandardTransition.components)
return "\(standardAbbreviation)\(standardOffset)\(daylightAbbreviation),\(startDate),\(endDate)"
}
/* These formatting functions can be way better. You'll also want to actually cache the
DateComponentsFormatter somewhere.
*/
func formatOffset(_ dateComponents: DateComponents) -> String {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.hour, .minute, .second]
formatter.zeroFormattingBehavior = .dropTrailing
return formatter.string(from: dateComponents) ?? ""
}
func formatOffset(_ seconds: Int) -> String {
return formatOffset(DateComponents(second: seconds))
}
func formatDate(components: DateComponents) -> String {
let month = components.month ?? 0
let week = components.weekOfMonth ?? 0
let day = components.weekdayOrdinal ?? 0
let offset = formatOffset(DateComponents(hour: components.hour, minute: components.minute, second: components.second))
return "M\(month).\(week).\(day)/\(offset)"
}
}
Note that there's lots to improve here, especially in clarity and performance. (Formatters are notoriously expensive, so you'll definitely want to cache them.) This also currently only produces dates in the expanded form "Mm.w.d" and not Julian days, but that can be bolted on. The code also assumes that it's "good enough" to restrict unbounded rules to the current calendar year, since this is what the GNU C library docs seem to imply about e.g. time zones which are always in standard/daylight time. (This also doesn't recognize well-known time zones like GMT/UTC, which might be sufficient to just write out as "GMT".)
I have not extensively tested this code for various time zones, and the above code should be considered a basis for additional iteration. For my time zone of America/New_York, this produces "EST-5EDT,M3.3.2/3,M11.2.1/1", which appears correct to me at first glance, but many other edge cases might be good to explore:
Boundary conditions around the start/end of the year
Giving a date which exactly matches a DST transition (consider TRANSITION_PREVIOUS vs. TRANSITION_PREVIOUS_INCLUSIVE)
Time zones which are always standard/daylight
Non-standard daylight/timezone offsets
There's a lot more to this, and in general, I'd recommend trying to find an alternative method of setting a time on this device (preferably using named time zones), but this might hopefully at least get you started.
I'm trying to write an app that displays information about a given timezone. It displays which periods of time that timezone is observing DST, and which periods of time it is not. Most importantly, it highlights times where the timezone changes irregularly, such as when Britain observed double DST during WWII, or when Samoa skipped a day in 2011.
For that, I would need to get a list of all the historical timezone offset transitions, as stored in the TZ database (I think there is a copy of the database in every macOS/iOS device). To be more specific, a "transition" (similar to java.time.zone.ZoneOffsetTransition) is modelled by the following 3 things:
the Date when it happened
the GMT offset in seconds before it happened
the GMT offset in seconds after it happened
From what I can see from the TimeZone API docs, there is no built-in method that does this (unlike how java.time does). The closest method I could find is nextDaylightSavingTimeTransition(after:), but that only tells me the transition date of one transition, when given a date, and I'm also not sure what date to give.
How can I get the list of transitions?
Example output for Asia/Ho_Chi_Minh:
Transition at 1906-06-30T16:53:20Z from 25600 to 25590
Transition at 1911-04-30T16:53:30Z from 25590 to 25200
Transition at 1942-12-31T16:00:00Z from 25200 to 28800
Transition at 1945-03-14T15:00:00Z from 28800 to 32400
Transition at 1945-09-01T15:00:00Z from 32400 to 25200
Transition at 1947-03-31T17:00:00Z from 25200 to 28800
Transition at 1955-06-30T16:00:00Z from 28800 to 25200
Transition at 1959-12-31T16:00:00Z from 25200 to 28800
Transition at 1975-06-12T16:00:00Z from 28800 to 25200
One solution I worked out is, start with passing distantPast to nextDaylightSavingTimeTransition:
someTimeZone.nextDaylightSavingTimeTransition(after: .distantPast)
This gets you the first transition date. Then do:
// second transition date
someTimeZone.nextDaylightSavingTimeTransition(after:
someTimeZone.nextDaylightSavingTimeTransition(after: .distantPast)
)
// third transition date
someTimeZone.nextDaylightSavingTimeTransition(after:
someTimeZone.nextDaylightSavingTimeTransition(after:
someTimeZone.nextDaylightSavingTimeTransition(after: .distantPast)
)
)
and so on. Of course, we will put this in a loop. The offset before and after the transition can be found by secondsFromGMT(for:). We will pass a two dates that only differ by something like 1 second.
Also note that although this is named nextDaylightSavingTimeTransition, it also gives you non-DST transitions, which is exactly what you want, so that's great!
import Foundation
struct OffsetTransition {
let instant: Date
let offsetBefore: Int
let offsetAfter: Int
}
var date = Date.distantPast
let timeZone = TimeZone(identifier: "Some Time Zone ID")!
let dateFormatter = ISO8601DateFormatter()
dateFormatter.formatOptions = [.withInternetDateTime]
var transitions = [OffsetTransition]()
while date < Date() {
guard let instant = timeZone.nextDaylightSavingTimeTransition(after: date) else {
break
}
let offsetBefore = timeZone.secondsFromGMT(for: instant.addingTimeInterval(-1))
let offsetAfter = timeZone.secondsFromGMT(for: instant)
if offsetBefore == offsetAfter { continue }
transitions.append(.init(instant: instant, offsetBefore: offsetBefore, offsetAfter: offsetAfter))
print("Transition at \(dateFormatter.string(from: instant)) from \(offsetBefore) to \(offsetAfter)")
date = instant
}
Note the if offsetBefore == offsetAfter { continue } check. I added this check because without it, it will sometimes produce transitions with offsetBefore == offsetAfter. This could be an indication that I am doing something wrong, but this check seems to fix the problem...
The output of this almost matches the output of a similar code using Java's getTransitions. However, there is one other slight problem that I found with this solution. For some timezones, such as Europe/London, the transition from the local mean time to the standardised offset is missing, but for other timezones (e.g. Asia/Hong_Kong), it exists. For example, a similar code using getTransitions in Java would produce a "Transition at 1847-12-01T00:01:15Z from -75 to 0" for Europe/London, but the Swift code's output does not include this line. This is not too big of a problem for me though.
I want to format dates and times in the form of "5 hours before", just like the Calendar app does. And I also want the fomatted string be localized for many languages.
There is a new API called RelativeDateTimeFormatter introduced in iOS 13. However, this API can only produce localized string like "2 hours ago" and "in 2 hours". This is not suitable for my purpose.
I wonder if there is an API that provides the functionality I'm looking for.
If you are interested how Apple does it, the localization is available in EventKitUI framework.
import EventKitUI
...
let bundle = EventKitUIBundle()!
// access the string that adds "before" information to an interval
let beforeKey = bundle.localizedString(forKey: "%# before", value: nil, table: nil)
// access the string that localizes some interval (days, in this case)
let intervalKey = bundle.localizedString(forKey: "interval_days_long", value: nil, table: nil)
// use the strings with a value
print(String(format: beforeKey, String(format: intervalKey, 1))) // 1 day before
print(String(format: beforeKey, String(format: intervalKey, 3))) // 3 days before
The localization dictionary contains proper pluralization for number values 1-9.
You can do exactly the same. I don't recommend using Apple's localization strings since these are not documented.
If it is possible for you to localized the before part, maybe consider using DateComponentsFormatter?
(Maybe this is more suitable as a comment, but seems to be too long)
For example:
let componentsList = [
DateComponents(minute:5),
DateComponents(minute:10),
DateComponents(minute:15),
DateComponents(minute:30),
DateComponents(hour:1),
DateComponents(hour:2),
DateComponents(day:1),
DateComponents(day:2),
DateComponents(weekOfMonth:1)
]
for components in componentsList {
if let text = DateComponentsFormatter.localizedString(from: components, unitsStyle: .full) {
print("\(text) before")
}
}
I'm extremely new to Swift and programming in general so please be patient with me while I'm trying to get the hang of this.
I've been following a beginners course in Swift from Treehouse and managed to develop a simple app that generates random quotes. So far so good. Now before moving on to more advanced lessons I figured I'd try to update the existing app just to make sure I get some solid practice before moving on.
So here's my question: I've managed to generate a random number through the GameKit framework, but the problem is that sometimes quotes appear consecutively. How can I avoid this from happening?
Here's my code:
import GameKit
struct FactProvider {
let facts = [
"Ants stretch when they wake up in the morning.",
"Ostriches can run faster than horses.",
"Olympic gold medals are actually made mostly of silver.",
"You are born with 300 bones; by the time you are an adult you will have 206.",
"It takes about 8 minutes for light from the Sun to reach Earth.",
"Some bamboo plants can grow almost a meter in just one day.",
"The state of Florida is bigger than England.",
"Some penguins can leap 2-3 meters out of the water.",
"On average, it takes 66 days to form a new habit.",
"Mammoths still walked the Earth when the Great Pyramid was being built."
]
func randomFact() -> String {
let randomNumber = GKRandomSource.sharedRandom().nextInt(upperBound: facts.count)
return facts[randomNumber]
}
}
You can store last random number or last fact in a variable and check it in your randomFact function. Like this:
var lastRandomNumber = -1
func randomFact() -> String {
let randomNumber = GKRandomSource.sharedRandom().nextInt(upperBound: facts.count)
if randomNumber == lastRandomNumber {
return randomFact()
} else {
lastRandomNumber = randomNumber
return facts[randomNumber]
}
}