Swift - Handling Firebase Timestamp - swift

I am working on firebase project, I have Timestamp field in a Collection, which looks below image
Firebase screenshot
When I read that time stamp from Indian time zone, I am getting date string as Sun Jan 26, 2020 12:00 AM
But When I read it from Pago Pago - American Samoa(GMT-11) time Zone, I am getting date string as Sat Jan 25, 2020 07:30 AM
I am using below code
let formatter = DateFormatter()
formatter.dateFormat = "EEE MMM dd, yyyy hh:mm a"
let coll = Firestore.firestore().collection("Collection1")
coll.getDocuments { (snap, error) in
for doc in snap!.documents {
let date = doc["createdOn"] as! Timestamp
print(formatter.string(from: date.dateValue()))
}
}
In the firebase I configured that datefield value as Sun Jan 26, 2020 12:00 AM UTC +5:30.
My question is regardless of time zone, I want the date string result to be Sun Jan 26, 2020, how can I achieve this

Firestore timestamps don't have a sense of local timezone. What you see in the Firebase console is the timestamp interpreted in the timezone configured on the local machine. It will appear differently when viewed from different timezones, but the point in time represented by the timestamp always remains the same. Internally, timestamps use UTC. From the API documentation:
A Timestamp represents a point in time independent of any time zone or
calendar, represented as seconds and fractions of seconds at
nanosecond resolution in UTC Epoch time. It is encoded using the
Proleptic Gregorian Calendar which extends the Gregorian calendar
backwards to year one. It is encoded assuming all minutes are 60
seconds long, i.e. leap seconds are “smeared” so that no leap second
table is needed for interpretation. Range is from 0001-01-01T00:00:00Z
to 9999-12-31T23:59:59.999999999Z. By restricting to that range, we
ensure that we can convert to and from RFC 3339 date strings.
If you want to format the time according to a specific local timezone, you will have to make use of some library to help you with this. The timestamp object itself will not accomodate your local time formatting needs.

Related

How to fix incorrect Date due to timezone in Core Data from using Calendar.current.startOfDay?

I have erroneously used Calendar.current.startOfDay(for: Date()) to populate a date attribute in Core Data. This means that when users cross different timezones I may have different dates unintentially stored in the date attribute field e.g.
Timezone 1 - 25th 23:00
Timezone 2 - 25th 22:00
Timezone 3 - 26th 05:00
I need to update the Calendar to use UTC Timezone but I need to also perform a migration so that the existing entries in Core Data read like this…
Result:
Timezone 1 - 26th 00:00
Timezone 2 - 26th 00:00
Timezone 3 - 26th 00:00
What are the steps to perform this migration. If I do a UTC startOfDay on it Timezone 1 would get 25th 00:00 instead of 26th 0:00 which is what it should be. Is it possible to accurately update existing entries?
Edit:
For some context I need a reliable way to get all the entries for the 26th for example. I used startOfDay to store the date as it meant I could query by it too and have the relevant entry returned (at any moment in time get the startOfDay and it will give me the entries for the whole day). For historical dates I can do the same - let's say the user has navigated back 2 days I can take startOfDay and subtract 2 days using Calendar.current.date(byAdding: .day, value: -2, to: date) and query for that.
So now the timezone breaks the above logic but is there some way to fix this? If I loop through the entries I can figure out the date it was supposed to be for and perhaps change the attribute to a string - e.g. 26-05-2021 or start to store day, month, year instead and query that.
From reading your answer Duncan I don't think I want to use UTC calendar as it would start to store the entry against the incorrect date from the users perspective dependent on their timezone e.g. user moves to next day and utc is still on previous.
Edit 2:
In a migration I will take the date that is stored and map it to new day, month and year properties storing those instead by getting them from Calendar.current.dateComponents([.day, .month, .year], from: date). Then instead of query by date I will query by day month and year of the Calendar.current where the user is. The side effect here is there is potential the user adds something for today (27th) changes timezone and sees 26th data but I don't think it can be avoided and the old data will then show as intended.
If you took the current time and used Calendar.current.startOfDay(for: Date()) to calculate midnight in the user's local time zone, you have a loss of information. You don't know what time of day the operation was performed. If you saved the time of day in the local time zone in another field, you could reconstruct a Date in UTC.
It isn't clear that what you did was wrong. The day, month, and year is only meaningful in a specific time zone. I am in the Washington DC metro area. We are in daylight savings time (EDT). It is currently 20:56 on the 26th of May. However, it's 1:56 AM on the 27th of May in London, 2:57 AM in Munich, and 3:57 AM in Tel Aviv. All at the exact same instant in time. In UTC it is 0:57 AM on the 27th of May.
Most people think of the calendar date in their local time zone. That is their frame of reference. If you ask me the date right now, I'll tell you it's the evening of the 26th of May. Unless I know you are in a different time zone, that's the "right" answer to me.
If I start out at midnight on a given day in my time zone, calling Calendar.current.startOfDay(for: Date()) each hour, I'll get midnight that day for all 24 hours in my local time zone. For the first 20 hours of the day, that would be the same result I would get if I created a Calendar in UTC and made the same call. However, at 20:00 EDT, I would start getting the next calendar day if I made the same query in UTC.
If you don't know what time of day you made the call to Calendar.current.startOfDay(for: Date()), there is no foolproof to figure out the day/month year in UTC at the instant you made the call. It depends on the time of day in the local timezone, and that timezone's offset from UTC.
Consider this code:
var calendarUTC = Calendar(identifier: .gregorian)
if let utcTimeZone = TimeZone(identifier: "UTC") {
print("Valid time zone")
calendarUTC.timeZone = utcTimeZone
}
print ("Start of day in UTC is \(calendarUTC.startOfDay(for: Date()))")
print ("Start of day in local time zone is \(Calendar.current.startOfDay(for: Date()))")
That outputs:
Start of day in UTC is 2021-05-27 00:00:00 +0000
Start of day in local time zone is 2021-05-26 04:00:00 +0000
That's because right now, which is 20:56 on 26 May in my time zone, it's 0:56 on 27 May in UTC. So if I ask the UTC calendar for the start of day for now (Date()) I get midnight on 27 May, in UTC.
If I ask the same question of my local calendar, I get midnight on 26 may in my time zone, which is 4:00 AM on 26 May in UTC.
If I ran the same code this morning at 8:00 AM in my time zone, I would have gotten the output:
Start of day in UTC is 2021-05-26 00:00:00 +0000
Start of day in local time zone is 2021-05-26 04:00:00 +0000
(Since at 8:00 AM on 26 May in EDT is also 26 May in UTC.)
It's tricky and not 100% reliable and only works if you know that all days were created using startOfDay. But first you need to decide what you want. Say one date was created at 10pm in the New York, and one at exactly the same moment in London, at 4am the next day. What day do you want to be stored?
If your date stored is 25th, 10pm, then you know it was created in a timezone where the day started at 10pm UTC. You are lucky, there are only two time zones that would have created this, one without DST, one with DST. So you know it happened in one of these two time zones, within 24 hours.
Unfortunately, time zones cover 26 hours. Fortunately, only some islands in the Pacific Ocean have same time and different dates (+13 and -11 hours). For these places, you cannot possibly know which date is correct, but very few people would be affected.

Is it possible to specify an ISO Date with a timezone (i.e. GMT offset) but without a time?

I'm trying to see if you can specify a date with a timezone in ISO but without also specifying a time.
This may seem odd to ask about having a timezone without actually having a time, but technically a date represents a range between two times... the 24-hour period spanning from midnight to midnight, and that 'midnight' has to be in a timezone.
In our case, we have an API that wants to say 'Filter things on-or-before date X and on-or-after date Y' and we want the user to specify 'April 9th' (in their time zone) for both to get all things that happen on that day.
Of course we solve this by adding a day to the first date, then changing it to a pure 'before', but the front-end is required to do that math. We can't do it on the backend because having to send a date with a time means we would be sending April 9th at midnight, then on the backend adding a day to that, but what if someone passed in 4pm?
We could fail the date if it has a non-midnight time, but then we're back to why pass it in the first place.
So again, can you have a date with a timezone but not have a time component?
To decode ISO8601 dates only with year-month-day and time zone set the appropriate formatOptions of the ISO8601DateFormatter
let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withFullDate, .withTimeZone]
If by time zone you mean a UTC offset (as used with ISO 8601 dates with times), this is no problem. If by time zone you mean a true time zone with historic, present and known future offsets from UTC, including for example summer time/DST, like America/New_York or North American Eastern Time, then ISO 8601 does not support that, neither for dates with nor without time of day.
2020-04-25-04:00
This is perfectly valid ISO 8601 for April 25 this year at offset -04:00. So you may use it for representing the interval from 2020-04-25T00:00-04:00 (inclusive) to 2020-04-26T00:00-04:00 (exclusive). Which would then be equivalent to 2020-04-25T04:00Z to 2020-04-26T04:00Z (Z meaning UTC).
Java example code
I don’t know any Swift, so cannot tell you how to format or parse such a string in Swift. In Java formatting it is not bad. Example:
LocalDate date = LocalDate.of(2020, Month.APRIL, 25);
String isoOffsetDateString = date
.atStartOfDay(ZoneId.of("America/New_York"))
.format(DateTimeFormatter.ISO_OFFSET_DATE);
System.out.println(isoOffsetDateString);
Output:
2020-04-25-04:00
I am using Java’s built-in ISO_OFFSET_DATE formatter. The documentation informs us that this formnatter is:
The ISO date formatter that formats or parses a date with an offset,
such as '2011-12-03+01:00'.
Parsing the string and producing the start and end of the day takes a little more:
TemporalAccessor parsed
= DateTimeFormatter.ISO_OFFSET_DATE.parse(isoOffsetDateString);
Instant start = LocalDate.from(parsed)
.atStartOfDay(ZoneOffset.from(parsed))
.toInstant();
Instant end = start.plus(1, ChronoUnit.DAYS);
System.out.println("From " + start + " inclusive to " + end + " exclusive");
From 2020-04-25T04:00:00Z inclusive to 2020-04-26T04:00:00Z exclusive
I have opted to convert to Instant, the class for a moment in time independent of offset or time zone. Instants print in UTC, as the trailing Z on each says. In your Java code you may prefer not to do this conversion or to do a different conversion, all depending on circumstances.
Link
Documentation of DateTimeFormatter.ISO_OFFSET_DATE

Does Date() always init UTC date?

I'd like to know if Date() always init UTC date.
Example:
App is running in location UTC-8
Call Date() => date in UTC or UTC-8?
Thanks.
Date() initiates a single point in time, independent of any particular calendar system or time zone. Date values represent a time interval relative to an absolute reference date.
So the displayed value will be different depending on timezone, e.g.
01/01/2020T00:00:00 in UTC
will be presented as
31/12/2019T23:00:00 in UTC+1

Convert UTC millisecond to UTC date in java

I am working in application where facing issue with time zones.
Want to convert UTC millisecond to UTC date object.
I already tried
TimeZone utcZone = TimeZone.getTimeZone("UTC");
Calendar date = getInstance(utcZone);
date.setTimeInMillis(utcMillisecond);
date.getTime();
date.getTime is still returning my local time zone that is EST. I know that millisecond that I am getting from UI is in UTC millisecond.
The old class java.util.Calendar silently applied your JVM’s current default time zone. You assumed it would be in UTC but it is not.
java.time
You are using old troublesome date-time classes that have been supplanted by the java.time framework in Java 8 and later.
I assume that by "UTC millisecond" you mean a count of milliseconds since the first moment of 1970 in UTC, 1970-01-01T00:00:00Z. That can be used directly to create a java.time.Instant, a moment on the timeline in UTC.
By the way be aware that java.time has nanosecond resolution, much finer than milliseconds.
Instant instant = Instant.ofEpochMilli( yourMillisNumber );
Call toString to generate a String as a textual representation of the date-time value in a format compliant with the ISO 8601 standard. For example:
2016-01-23T12:34:56.789Z

JPA Date and Timezone confusion

I'm having a problem reading dates from a database using JPA. I'm using EclipseLink and PostgreSQL
I've populated my database from a CSV file, witch had dates as strings (in this format: 6/30/2009-23:59:56). I used the following snipet to convert it to a Date object:
public static Date parseDate(String s){
DateFormat formatter = new SimpleDateFormat("d/M/yyyy-k:m:s");
try {
return new Date( ((java.util.Date)formatter.parse(s)).getTime() );
} catch (ParseException ex) {
Logger.getLogger(Type.class.getName()).log(Level.SEVERE, null, ex);
return null;
}
}
The date is correctly converted and stored in the database as expected. Here is how i map the Date object to the database:
#Column(name="data_ts", nullable=false)
#Temporal(TemporalType.TIMESTAMP)
private Date dataTs;
The problem seems to happen when i try to read the Date from the database to use it in a chart(Highcharts). The record with that same timestamp above get read as:
Mon Jun 06 23:59:56 BRT 2011 and it's timestamp as 1307415596000
Note that it is in Brazilian Time(+3h), so the timestamp (that is calculated from GMT) is 3 hours shifted. Once ploted, the timestamp turns to point to 07/06/2011 02:59:56
Here's an example:
List<TimedataEnt> timeData = currentWellsite.getTimeData();
String debug = timeData.get(timeData.size()-1).getDataTs().toString() + ">>>" + timeData.get(timeData.size()-1).getDataTs().getTime();
where currentWellsite is and JPA Entity, and getDataTs() returns a java.util.Date object.
The string turns out to be "Tue Jun 30 23:59:56 BRT 2009>>>1246417196000"
How do I tell JPA not to convert the timestamp read from the database?
As said, Date and Timestamps have no timezone consideration. It seems that the issue is caused because Java considers that the time it reads from the database is the current default timezone.
So, if the database contais 2011-04-04 14:00:00 and my current timezone is +3, assigning that to java Date will generate 2011-04-04 14:00:00 BRT time(+3), with a timestamp shifted 3 hours (since timestamps are caclulated from UTC).
Solved the issue by getting an calculated timestamp:
long ts = myDate().getTime() + TimeZone.getDefault().getRawOffset();
It's important to say that getRawOffset() does not take Daylight Saving periods in consideration. For that, use getOffset()
Your date is 6/30/2009-23:59:56. I read that as 30 june 2009, 23:59:56. So the format to parse it should be M/d/yyyy-HH:mm:ss or M/d/yyyy-kk:mm:ss (depending on if your hours go from 1 to 24 or from 0 to 23). But definitely not d/M/yyyy-k:m:s: the month comes before the day.
Also, a Timestamp doesn't have any time zone. It's a universal instant in time. It's only when you display its value that the timezone is important, because then you have to choose which time zone to use to display the time. Use a DateFormat with the appropriate timezone set to display your timestamp.
Your issue seems to be that you are storing your Timestamp (which does not have a timezone) into a java.util.Date (which has a timezone offset).
If you want control over how the timezone is set, then store your Timestamp as a java.sql.Timestamp, or use your own #Converter.
In general Calendar should be used in Java instead of java.util.Date, which is for the most part deprecated. Calendar also has a Timezone, so you may have similar issues.