Date comparison always returns true - Swift - swift

I want to track when was the last time the user refreshed the api, so I decided to do it like this:
#AppStorage("lastTimeUserRefreshedApi") var lastTimeUserRefreshedApi: Date = Date()
func canUserRefreshAPI() -> Bool {
let readyToBeRefreshed: Date = lastTimeUserRefreshedApi.addingTimeInterval(5) // in seconds
let currentTime: Date = Date()
var canUserRefresh: Bool = false
if(currentTime > readyToBeRefreshed) {
canUserRefresh = true
lastTimeUserRefreshedApi = lastTimeUserRefreshedApi.addingTimeInterval(5)
} else {
canUserRefresh = false
}
return canUserRefresh
}
The problem is that it's always returning true, but why? Also is there a simpler way to do this?
Thanks
EDIT:
This is the extension I'm using to be able to store Date in the #AppStorage:
extension Date: RawRepresentable {
public var rawValue: String {
self.timeIntervalSinceReferenceDate.description
}
public init?(rawValue: String) {
self = Date(timeIntervalSinceReferenceDate: Double(rawValue) ?? 0.0)
}
}

You are making it much harder than it should. Just save the "expiration" date. When you read it just compare if it is past or not.
#AppStorage("expiration")
var expiration: Date = Date(timeIntervalSinceNow: 5)
func canUserRefreshAPI() -> Bool {
let now = Date()
if expiration < now {
expiration = Date(timeIntervalSinceNow: 5)
return true
} else {
return false
}
}

Related

How to make the value of a variable reset every day

I want to make an exercise app to help users track their daily exercise and I have a variable exerciseTime that I want to reset every day. Is there any way to do that?
The variable is stored in app storage #AppStorage("exerciseTime") var exerciseTime = 0.0 and connected to a timer.
Text("Time: \(format(seconds: timerStruct.countdownTimer))")
.padding()
.onReceive(timer) { _ in
if timerStruct.countdownTimer > 0 && timerStruct.timerRunning == true {
timerStruct.countdownTimer -= 1
exerciseTime += 1.0
} else {
timerStruct.timerRunning = false
if timerStruct.countdownTimer <= 0, timerStruct.timerRunning == false {
timerStruct.isAlertpresented = true
reset()
}
}
This function checks if the current date is in the same day as a saved date in UserDefaults
Just call it always when the app becomes active
func isNewDay() -> Bool {
let defaults = UserDefaults.standard
let now = Date.now
if let savedDate = defaults.object(forKey: "currentDate") as? Date,
Calendar.current.compare(savedDate, to: now, toGranularity: .day) == .orderedSame {
return false
}
defaults.set(now, forKey: "currentDate")
return true
}

Swift string to date

I'm trying to convert string "2020-04-05T16:00:00Z" to date in Swift. I'm using following code:
extension String {
var iso8601Date: Date? {
Formatter.iso8601Formatter.date(from: self)
}
}
extension Formatter {
static let iso8601Formatter = ISO8601DateFormatter([.withInternetDateTime])
}
extension ISO8601DateFormatter {
convenience init(_ formatOptions: Options, timeZone: TimeZone = TimeZone(secondsFromGMT: 0) ?? .current) {
self.init()
self.formatOptions = formatOptions
self.timeZone = timeZone
}
}
This conversion takes 100ms (per 1000 iteration) on emulator, and 70ms on device. Is there a way to do this conversion faster? Thanks.

Updating Text Using SwiftUI and FireStore

I store a value called month hours in my application that keeps track of the hours a person has used the apps and displays it in a line of text. The text if part of a stack in Swift UI, but I can't figure out how to make the text update once the information has been queried from I've tried quite a few ways of making this work from structs to classes to using #State.
This is just the latest thing I tried that didn't work if anyone can help that would be greatly appreciated.
let db = Firestore.firestore()
class Month {
var monthHours = "0"
func getMonthHours() {
db.addSnapshotListener(. //Im removing the actual query part to keep that private but the print statement below confirms the query is not the issue.
{ (docSnapShot, err) in
if let e = err {
print("There was an error retrieving the monthly hours:\n\(e.localizedDescription)")
} else {
let data = docSnapShot?.data()
if let h = data?[K.FStore.monthHoursField] as? Double {
self.monthHours = String(h.rounded())
print("These are the hours:\n\(self.monthHours)")
}
}
})
}
func getMonth() -> String {
let date = Date()
let formatter = DateFormatter()
formatter.dateFormat = "MMMM yyyy"
let result = formatter.string(from: date)
return result
}
init() {
getMonthHours()
}
}
struct ChartView : View {
#State private var month = Month()
//Struct variables
var body : some View {
ZStack {
Color(UIColor(named: K.BrandColors.grey)!).edgesIgnoringSafeArea(.all)
VStack {
Text("HOURS THIS MONTH \(month.monthHours)")
.font(.system(size: 18))
.fontWeight(.heavy)
}
}
}
This outlines one possible approach. The crux is to deal with the asynchronous function "getMonthHours". You need to wait till it is finished its fetching before you can use the results.
class Month {
var monthHours = "0"
// async fetch the month hours from Firestore, ... error handling todo
static func getMonthHours(handler: #escaping (String) -> Void) {
db.addSnapshotListener{ (docSnapShot, err) in
if let e = err {
print("There was an error retrieving the monthly hours:\n\(e.localizedDescription)")
return handler("") // should return some error here .... todo
} else {
if let data = docSnapShot?.data(),
let h = data?[K.FStore.monthHoursField] as? Double {
// return the result
return handler(String(h.rounded()))
} else {
return handler("") // should return some error here .... todo
}
}
}
}
func getMonth() -> String {
let date = Date()
let formatter = DateFormatter()
formatter.dateFormat = "MMMM yyyy"
let result = formatter.string(from: date)
return result
}
init() { }
}
struct ChartView : View {
#State private var monthHours = ""
var body : some View {
ZStack {
Color(UIColor(named: K.BrandColors.grey)!).edgesIgnoringSafeArea(.all)
VStack {
Text("HOURS THIS MONTH \(monthHours)")
.font(.system(size: 18))
.fontWeight(.heavy)
}
}.onAppear(perform: loadData)
}
}
func loadData() {
// when the fetching is done it will update the view
Month.getMonthHours() { hours in
self.monthHours = hours
}
}

Swift: How do I include or use darwin compactdecimalformat?

I've been investigating and trying to code a compact number format otherwise known as a short scale formatter or a humanizing big numbers into smaller ones in Swift.
I suddenly thought rather than trying to recode the wheel maybe Unicode already solved this issue and saw that there is indeed some code to compact larger numbers.
In the open-source code for Apple Swift on Github there is a reference to compactdecimalformat.h at:
https://github.com/apple/swift-corelibs-foundation/blob/master/bootstrap/x86_64-apple-darwin/usr/local/include/unicode/compactdecimalformat.h
In it, it refers to numberformatter.h; which I found a reference from Unicode ICU 67.1 at:
https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/numberformatter_8h.html
However, I am not sure how to use it. I've imported Darwin, but I am not sure how to actually call compactdecimalformat or the public identifier DecimalFormat
I'd like to run some number tests against it to see if it is what I'm looking for in terms of compacting / short scaling numbers.
Thus my question is, how do you include and use the compactdecimalnumber.h file that comes as part of the Darwin import?
With thanks
edit: I think upon reading the code and comments the .h file only handles really big numbers?
I don't think there is such formatter in Swift but you can subclass NumberFormatter and create your own CompactNumberFormatter if needed:
class CompactDecimalFormatter: NumberFormatter {
override func string(for obj: Any?) -> String? {
guard var value = (obj as? NSNumber)?.doubleValue else { return nil }
let suffixes = ["", "k", "m", "b","t"]
var index = suffixes.startIndex
while index < suffixes.endIndex && value.magnitude >= 1000 {
index = suffixes.index(after: index)
value /= 1000
}
formatter.positiveSuffix = suffixes[index]
formatter.negativeSuffix = suffixes[index]
return formatter.string(for: value)
}
private let formatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.roundingMode = .down
formatter.minimumFractionDigits = 0
formatter.maximumFractionDigits = 1
return formatter
}()
// You can override all the properties you thing you might need
override var minimumIntegerDigits: Int {
get { formatter.minimumIntegerDigits }
set { formatter.minimumIntegerDigits = newValue }
}
override var maximumIntegerDigits: Int {
get { formatter.maximumIntegerDigits }
set { formatter.maximumIntegerDigits = newValue }
}
override var minimumFractionDigits: Int {
get { formatter.minimumFractionDigits }
set { formatter.minimumFractionDigits = newValue }
}
override var maximumFractionDigits: Int {
get { formatter.maximumFractionDigits }
set { formatter.maximumFractionDigits = newValue }
}
override var positivePrefix: String! {
get { formatter.positivePrefix }
set { formatter.positivePrefix = newValue }
}
override var negativePrefix: String! {
get { formatter.negativePrefix }
set { formatter.negativePrefix = newValue }
}
override var roundingMode: RoundingMode {
get{ formatter.roundingMode }
set{ formatter.roundingMode = newValue }
}
// or disable them
override var numberStyle: Style { get { .decimal } set { } }
override var positiveSuffix: String! { get { "" } set { } }
override var negativeSuffix: String! { get { "" } set { } }
}
let formatter = CompactDecimalFormatter()
formatter.string(for: 2500) // "2.5k"

How to filter based on Date so there is only one element for each week?

I'm having difficulty wording this, but here goes.
I have an array of Datapoint objects and each of these objects has a createdAt NSDate property. The array has about 10 or so Datapoint objects for each day of the past week.
I want to filter the array so that there is only one datapoint for each day of the week.
I was thinking something along the lines of:
let today = NSDate()
var endRange = today.dateByAddingTimeInterval(-7 * 24 * 60 * 60)
var allThisWeeksPoints = datapoints.filter({ $0.createdAt >= endRange && $0.createdAt < today })
let c = NSCalendar.currentCalendar()
var thisWeeksPoints : [Datapoint] = []
while !c.isDate(endRange, inSameDayAsDate: today) {
//take one datapoint, eliminate all the other datapoints in the same day and then increment the endRange date by a day
}
Here is the code for my Datapoint object:
class Datapoint: NSObject {
let object : PFObject
let objectId : String
let userId : String
let createdAt : NSDate
let totalPosts : Int
let followerCount : Int
let followingCount : Int
let totalLikes : Int
let averageLikes : Float
let totalComments : Int
let averageComments : Float
}
Step One:
Make NSDate Comparable Found here
public func ==(lhs: NSDate, rhs: NSDate) -> Bool {
return lhs === rhs || lhs.compare(rhs) == .OrderedSame
}
public func <(lhs: NSDate, rhs: NSDate) -> Bool {
return lhs.compare(rhs) == .OrderedAscending
}
extension NSDate: Comparable { }
A few general remarks:
Use an input and output.
Respect upper and lower camel naming conventions
Split the function in two parts, one for dividing the points in weeks, another to apply the filter. This makes it easier to maintain.
Use dateByAddingUnit as suggested in the comments. This will prevent localisation issues. Not every week is the same and not every calendar used in the world uses the same system of weeks.
func weeklyPoints(dataPoints: [DataPoint]) -> [[DataPoint]]? {
guard let firstPoint = dataPoints.first else { // alternative to an .isEmpty check since we need the first point anyway
return nil
}
guard let gregorian = NSCalendar(identifier:NSCalendarIdentifierGregorian) else {
return nil
}
var weeklies : [[DataPoint]] = [[]]
var weekStart : NSDate = firstPoint.createdAt // force unwrap allowed because of the guard at the beginning
var maybeWeekEnd : NSDate? = gregorian.dateByAddingUnit(.WeekOfYear, value: 1, toDate: weekStart, options: [])
for point in dataPoints {
guard let weekEnd = maybeWeekEnd else {
break
}
guard point.createdAt >= weekStart && point.createdAt < weekEnd else {
weekStart = weekEnd
maybeWeekEnd = gregorian.dateByAddingUnit(.WeekOfYear, value: 1, toDate: weekStart, options: [])
weeklies.append([])
continue
}
let currentWeekIndex = (weeklies.count - 1)
weeklies[currentWeekIndex].append(point)
}
return weeklies
}
The very simple filter function.
func onePointPerWeek(dataPoints: [DataPoint]) -> [DataPoint]? {
guard let weeklies = weeklyPoints(dataPoints) else {
return nil
}
var pointPerWeek : [DataPoint] = []
for week in weeklies {
guard let point = week.first else { // maybe sort them first if needed
continue
}
pointPerWeek.append(point)
}
return pointPerWeek
}
So I figured out a way to do it:
func thisWeeksPoints() -> [Datapoint] {
let today = NSDate()
var endRange = today.dateByAddingTimeInterval(-7 * 24 * 60 * 60)
var allThisWeeksPoints = datapoints.filter({ $0.createdAt >= endRange && $0.createdAt < today })
let c = NSCalendar.currentCalendar()
var thisWeeksPoints : [Datapoint] = []
while !c.isDate(endRange, inSameDayAsDate: today) {
if let point = allThisWeeksPoints.first {
allThisWeeksPoints = allThisWeeksPoints.filter({!c.isDate(point.createdAt, inSameDayAsDate: $0.createdAt)})
thisWeeksPoints.append(point)
}
endRange = endRange.dateByAddingTimeInterval(24 * 60 * 60)
}
return thisWeeksPoints
}