在Swift中使用Date()并在调整时区以在Firestore / Firebase中存储和读取时遇到问题

发布时间:2020-07-07 12:18

我在Firestore中存储来自Swift项目的日期没有任何问题。日期会转换为UTC格式,并作为时间戳存储在Firestore中。一切都很好。

然后,回到客户端,我可以将其读回并应用Bool,然后根据用户当前所在的时区对日期/时间进行相应的调整。

因此,例如,原来的时间:

9:00 pm墨尔本时间(格林尼治标准时间+10),

如果用户在纽约,则

显示为上午7:00。

太好了。

但是我有一些项目要针对时区进行调整(如上所述),而另一些则不需要。

所以说我有两个与上面的示例相同的项目,但是一个是警报,我想保留最初设置的时间,而不考虑新的时区...因此仍将其保留在9:00下午。

我在数据库中保存了一个ignoreTimezone标志,说extension Calendar { func offsetFromMidnight(for date: Date) -> TimeInterval { return date.timeIntervalSince(startOfDay(for: date)) } } ,但是当我以UTC格式从Firestore读取时间戳并取回时间戳时,我在Swift中如何做到这一点迷失了到原来的晚上9:00。

我找到的所有Q&A都与转换时区等有关,但实际上并不是在忽略示例并将日期和时间设置为最初设置的时区的示例中。

在此先感谢您的帮助和/或建议。

问题已按照建议更新

我现在已经合并了建议的代码。因此,有一个日历扩展名:

Date()

然后执行推荐的步骤。

从午夜开始偏移,在这种情况下为当前let offsetSinceMidnight = UInt64(Calendar.current.offsetFromMidnight(for: Date()))

//Create a calendar for the target timezone guard let chicagoTimeZone = TimeZone(identifier: "America/Chicago") else { fatalError() } var chicagoCalendar = Calendar(identifier: .gregorian) chicagoCalendar.timeZone = chicagoTimeZone //Calculate midngiht in the target calendar let chicagoMidnight = chicagoCalendar.startOfDay(for: Date()) //calculate the same time-of-day in the new timezone let adjustedChicagoTime = Date(timeInterval: TimeInterval(offsetSinceMidnight), since: chicagoMidnight)

然后将该值存储在服务器上。 我目前在澳大利亚墨尔本,所以用于测试的日期和时间是7月9日下午2:00。

在不同时区在客户端上检索到它时,我正在使用推荐的代码:

offsetSinceMidnight

在芝加哥,输出设置为正确的时间,即2:00 pm,但是由于日期不同(芝加哥仍然是7月8日),因此将午夜时间间隔应用于错误的日期。所以我7月8日下午2:00。

我假设我还需要捕获原始日期成分,以将col_a应用于newTimeZone中具有匹配日期成分的日期???还是有更好的方法呢?

回答1

Date对象可以在世界任何地方即时存储时间。无论时区如何,他们都没有把握时间的想法。

为此,我建议计算一个offsetFromMidnight值。

已修改以固定返回值。

extension Calendar {
  func offsetFromMidnight(for date: Date) -> TimeInterval {
    return date.timeIntervalSince(startOfDay(for: date))
    }
}

您将在用户当前日历中调用该函数,以获取自用户当前时区午夜以来的秒数。将其保存到您的数据库。 (您可以舍入为一个长整数,而精度损失很小。)

我碰巧在纽约时区(EDT),所以将其用作目的地时区对我来说不起作用,因为它不会改变任何内容。相反,我将显示代码以将我的时区转换为GMT:

//Run on user's local machine (in EDT in my case):
let offsetSinceMidnight = UInt64(Calendar.current.offsetFromMidnight(for: Date()))
//Save offset to FireStore

然后,如果您想在新时区中的同一时间,请使用以下代码:

//Create a calendar for the target time zone (or the user's local time zone on the destination machine)
guard let gmt = TimeZone(abbreviation: "GMT") else { fatalError() }
var gmtCalendar = Calendar(identifier: .gregorian)
gmtCalendar.timeZone = gmt



//Read time offset from FireStore
let offsetFromNYC = Calendar.current.offsetFromMidnight(for: Date())

//Calculate midnight in target calendar
let gmtMidnight = gmtCalendar.startOfDay(for: Date())

//Calculate the same time-of-day in the GMT time zone
let gmtTimeToday = Date(timeInterval: TimeInterval(offsetSinceMidnight), since: gmtMidnight)

print(gmtTimeToday)

请注意,以上内容将为您提供与offsetFromMidnight时间相同的小时/分钟/秒。

编辑:

如果您的目标是将闹钟设置为本地时区的下一个未来日期,则需要添加逻辑以检查计算出的日期/时间是否在过去并调整:

//Change adjustedChicagoTime to a var
var adjustedChicagoTime = Date(timeInterval: TimeInterval(offsetSinceMidnight), since: chicagoMidnight)

//If the alarm time is in the past, add a day to the date.
if adjustedChicagoTime < Date() {
   adjustedChicagoTime = Calendar.current.date(byAdding: .day,
     value: 1, to: adjustedChicagoTime, wrappingComponents: false)
}

编辑#2:

前后往复,听起来好像您有时想保存一个与时区无关的日期和时间,例如7月10日上午9:30。如果我在EDT中创建该日期,而您在Melborne中查看,则该日期始终是7月10日上午9:30。

其他时间,您要上传和下载符合时区的日期和时间。

为了轻松做到这两者,我建议将2个不同的字符串日期/时间字段保存到FireStore,其中一个带有时区,另一个不带时区。具有时区(或相对于GMT有所偏移)的时区可以捕获世界各地的时间,并可以转换为本地时间。

没有时区的人会描述当地时间的天/月/年/小时/分钟。

您可以使用以下日期格式化程序在Swift中生成/解析这些字符串:

let baseFormatString = "YYYY-MM-dd'T'HH:mm"
let timeZoneFormatString = baseFormatString + "ZZZ"

let  noTimeZoneFormatter = DateFormatter()
noTimeZoneFormatter.dateFormat = baseFormatString

let  timeZoneFormatter = DateFormatter()
timeZoneFormatter.dateFormat = timeZoneFormatString

请注意,默认情况下,日期格式器使用系统的时区,因此“无时区格式器”将采用本地时区。如果您使用它将日期字符串转换为日期,则会假定该日期位于本地时区。