EDIT: thanks to Alexander - Reinstate Monica. See the link in the comments for more info.
I ran into something odd today. I'm using firebase storage for an app and I want to compare the date of the file on the server with the one stored locally which I have manually set the attributes for when creating the file. If the dates are different then the app will re-load the file from the server like so:
if localModDate != serverModDate {
//re-download file
}
most of the time this works correctly, but sometimes the if statement get triggered even though the dates match (checked via print statements) and if I convert the stamps to strings then the dates always match correctly:
if String(describing: localModDate) != String(describing: serverModDate) {
//re-download file
}
my guess is that swift is converting to epoch time (I tested this and the epoch times are indeed off in like the 6th decimal place). Is this correct or is there something else going on here?
Date is just a TimeInterval (which is a typealias for Double) of elapsed seconds since the "reference date" (Jan 1 2001). The == operator just compares those two Doubles, so it's subject to the same floating point precision issues of regular Doubles.
Use Calendar.isDate(_:equalTo:toGranularity:) to check if the dates are "close enough":
if Calendar.current.isDate(localModDate, equalTo: serverModDate, toGranularity: .second) {
...
}
Related
I currently have the following use case:
User receives a date in UTC from the backend
This date is transformed into local time for displaying purposes
The date is displayed in different inputs. One input for date and other for time
User can select time independently
The date should be sent back to the backend in UTC format as well
I'm not very experienced with time zones and I'm getting beaten by trying to allow the user to set up only the time (or date) in a datetime field.
My pseudo code is the following:
When receiving the from backend simply convert the date to show it to the user, making the orignal date stay in UTC
When the user picks the hour and minute (all in one action) use setHours and setMinutes from date-fns library
Use native toISOString() to set in models
Code so far [playground]:
import { utcToZonedTime, format } from "date-fns-tz";
import { setHours, setMinutes } from "date-fns";
const UTCStringdate = "2022-04-06T10:00:00.000000Z";
const userTimezone = "Asia/Bangkok";
const localizedTime = utcToZonedTime(UTCStringdate, userTimezone);
// Prints the correct information
// 10:00 in UTC is equal to 17:00 in Bangkok
console.log(format(localizedTime, "HH:mm"));
// Now I expext to set only the `minutes` and `hours`
// to this `localizedTime` so that I can update the backend
const [hours, minutes] = "10:30".split(":");
// Somewhere over here the `setHours` and `setMinutes`
// is turning the Date object into my own timezone
// and not using `Asia/Bangkok` timezone anymore
let newTime = setHours(localizedTime, hours);
newTime = setMinutes(newTime, minutes);
// Now I expect to print 17:30 since we only
// set up 30 minutes forward than the original one
// but it ends up printing 10:30
console.log(format(newTime, 'HH:mm'));
I understand that somewhere along the way (most likely in setHours and setMinutes) the date-fns library turns back the localizedTime back into my own timezone, completely ruining the idea of turning the Asia/Bangkok time into UTC.
Questions
First, is this the best approach to manipulate only the time part of a date when considering timezones? If not, anyone can point me to articles? I wasn't able to find anything on the topic
Second, how can I use setHours and setMinutes and still maintain the timezone?
There are no multiple time zones in JavaScript. There is UTC and there is your local one. What date-fns-tz does, is adjusting the time to the chosen user time zone. In your example, you can see this when printing both the original and the localized time
const utcTime = new Date(UTCStringdate);
console.log(utcTime.toISOString()); // -> 2022-04-06T10:00:00.000Z
console.log(localizedTime.toISOString()); // -> 2022-04-06T14:00:00.000Z
To solve your issue, convert UTC time to users time and let the user to adjust hours and minutes in his local time zone. After that, convert the time object back to UTC using zonedTimeToUtc
newTime = zonedTimeToUtc(newTime, userTimezone);
and then use newTime.toISOString() to send it back to the server.
console.log(newTime.toISOString());
I am trying to retrieve the timezone abbreviations of the local time zone by using the following code.
private func getLocalTimezone() -> String {
guard let localTimezone = TimeZone.current.abbreviation() else {
return ""
}
return localTimezone
}
But when I am in Indian time zone I am always getting GMT+5:30 where I need it as IST. Its coming correctly when I am in CST or PST. Can anyone please suggest a way to reach to the solution for this issue.
Thanks a lot in advance
This is because time zone abbreviations are locale sensitive. IST only means India Standard Time (Asia/Kolkata) in India. In other parts of the world, it could mean Israel Standard Time, or Irish Standard/Summer Time. Here's a site that shows you the list of abbreviations. You can see for yourself how ambiguous they can be.
This is why abbreviation() takes into account the region of your phone, i.e. this setting:
abbreviation() will give you "IST" if your device's region is India. If your phone is somewhere else, it shows "GMT+5:30" because that is the safest, most unambiguous option.
If you want it to output IST no matter where your device is, you need to hard code this by creating a dictionary of time zone identifiers to abbreviations that you want. There is a built in abbreviationDictionary that goes the other way - abbreviations to identifiers. You can search it, and it will work for IST (Asia/Kolkata), but might not work for whatever other time zone that you are interested in.
let abbreviation = TimeZone.abbreviationDictionary
.first(where: { $1 == TimeZone.current.identifier })?.key
and I'm not sure whether the contents of this dictionary will stay the same in future versions of iOS. Use it at your own risk.
This question is best stated in an example:
It is currently 9:00am. User wants to do activity at 4:00pm the following day. They use UIDatePicker to select 4:00pm the next day, and then hit a button. I know firebase does times in milliseconds from 1970, so what I want to do is "add" the number of milliseconds from 9:00am to 4:00pm the following day to the ServerValue.timestamp(), like so:
activitiesRef.child(newActivity.id).setValue([
"id": newActivity.id,
"name": newActivity.name,
"isActive": newActivity.isActive,
"locString": newActivity.locationString,
"locLat": newActivity.locLat,
"locLong": newActivity.locLong,
"privacySetting": newActivity.privacySetting,
"targetTime": ServerValue.timestamp()]) // + some added value of time
//"targetTime": [".sv": "timestamp"]])
The reason for this is because I will be displaying a countdown timer elsewhere in the app until it reaches the targetTime. If I can push to firebase the targetTime, then the countdown timer will be a simple comparison of the current time on the user's phone to the targetTime itself.
The error I keep getting when trying to add a double value to the ServerValue.timestamp() is "Contextual type 'Any' cannot be used with dictionary literal"
If it is not possible to do so, what other options do I have? Thank you.
ServerValue.timestamp() is not a number that you can use to perform date arithmetic. It's a special placeholder value that the server side interprets with its sense of time.
The best you can do is write the timestamp, read it back out as a number, then perform math on it.
trying to compute the time for Sun's next setting time, I obtain a value for tomorrow - but I'm computing while the Sun is surely above of the horizon!
m is my observing site; that's what I got on terminal:
>>> ephem.now()
2012/10/16 16:02:00
>>> print m.next_setting(ephem.Sun(), use_center=True)
2012/10/17 16:38:36
that's the time for tomorrow. Also:
>>> m.previous_setting(ephem.Sun()) < ephem.now()
False
>>> m.previous_setting(ephem.Sun())
2012/10/16 16:42:14
So previous_setting is in the future.
Where am I wrong?
The next_setting() and previous_setting() functions do not consult the current value of ephem.now(). Instead, they look for dates previous to or subsequent to the .date of the Observer object — so the results you are getting only happen in my own experiments here on my laptop if the observer .date somehow gets set to a day in the future. Here are the setting times that I get if I use exactly the date and time you supplied:
import ephem
m = ephem.Observer()
m.long = '7:42:00'
m.lat = '45:04:00'
m.date = '2012/10/16 16:02:00'
print m.next_setting(ephem.Sun(), use_center=True)
# --> 2012/10/16 16:40:27
print m.previous_setting(ephem.Sun())
# --> 2012/10/15 16:44:05
Could you try running this script and see what you get as a result? The ephem.__version__ that currently displays on my laptop is 3.7.5.1, just in case that is a difference between us.
So: if you see odd results like this, then instead of printing and comparing the value of ephem.now() with the sunset times, you need to investigate the relationship between the observer's .date attribute and the sunset times you get out. If you can produce a little sample script like the one I show above, that sets its own times, instead of relying on now() to show a mistake that PyEphem is making (since by the time I see your question, my now() is obviously a bit different from your original "now"!), then I'll do my best to track down the problem.
I've got an applescript that I call for a song to set the play count and another to set the last played date. These are called from a perl script fetching data from last.fm. Unfortunately, whenever I call the last played date script, I get an error message setItunesLastPlayed.scpt: execution error: iTunes got an error: A descriptor type mismatch occurred. (-10001).
I'm calling it like this:
osascript /Users/gms8994/setItunesLastPlayed.scpt "<ARTIST>" "<TITLE>" "Wednesday, July 05, 2011 07:14:11 AM"
I did notice that if I request the value of the last play from itunes, it comes back without zero-padding days and hours, so I tried removing those, to no avail.
Here's my script.
on run argv
tell application "iTunes"
set theArtist to (item 1 of argv)
set theTitle to (item 2 of argv)
set theLastPlay to (item 3 of argv)
set theLastPlay to date theLastPlay
set results to (every file track of playlist "Library" whose artist contains theArtist and name contains theTitle)
repeat with t in results
tell t
say theLastPlay
set played date of t to theLastPlay
end tell
end repeat
end tell
end run
Can anyone point me to the fix?
The played date property of a track requires a date object, not just the date string. You will need to convert your date into a date object:
set newDate to (current date)
set month of newDate to "July"
set year of newDate to "2011"
(* You can fill in the rest *)
set played date of t to newDate
I don't think there is a way to directly create a date object from a given string, so you'll have to maintain a function to do that for you in case last.fm (or the technology they rely to store and report back dates) decides to change their date format.