如何使用 Calendar.current.startOfDay 修复由于 Core Data 中的时区导致的错误日期?
Posted
技术标签:
【中文标题】如何使用 Calendar.current.startOfDay 修复由于 Core Data 中的时区导致的错误日期?【英文标题】:How to fix incorrect Date due to timezone in Core Data from using Calendar.current.startOfDay? 【发布时间】:2021-05-26 23:24:34 【问题描述】:我错误地使用Calendar.current.startOfDay(for: Date())
来填充Core Data 中的日期属性。这意味着当用户跨越不同的时区时,我可能会无意中将不同的日期存储在日期属性字段中,例如
Timezone 1 - 25th 23:00
Timezone 2 - 25th 22:00
Timezone 3 - 26th 05:00
我需要更新 Calendar
以使用 UTC 时区,但我还需要执行迁移,以便 Core Data 中的现有条目读取如下......
结果:
Timezone 1 - 26th 00:00
Timezone 2 - 26th 00:00
Timezone 3 - 26th 00:00
执行此迁移的步骤是什么。如果我在其上执行 UTC
startOfDay
时区 1 将得到 25th 00:00
而不是 26th 0:00
这应该是。是否可以准确更新现有条目?
编辑:
例如,在某些情况下,我需要一种可靠的方法来获取 26 日的所有条目。我使用startOfDay
来存储日期,因为这意味着我也可以通过它查询并返回相关条目(随时获取startOfDay
,它会给我一整天的条目)。对于历史日期,我可以这样做 - 假设用户已经导航回 2 天,我可以使用 startOfDay
并使用 Calendar.current.date(byAdding: .day, value: -2, to: date)
减去 2 天并查询。
所以现在时区打破了上述逻辑,但有什么办法可以解决这个问题吗?如果我遍历条目,我可以找出它应该用于的日期,并可能将属性更改为字符串 - 例如26-05-2021
或开始存储日、月、年并进行查询。
通过阅读您的回答 Duncan,我认为我不想使用 UTC 日历,因为它会根据用户的时区(例如用户移动到第二天,UTC 仍然在前一天。
编辑 2:
在迁移中,我将获取存储的日期并将其映射到新的日、月和年属性,而不是通过从 Calendar.current.dateComponents([.day, .month, .year], from : 日期)。然后,我不会按日期查询,而是按用户所在的 Calendar.current 的日期月份和年份进行查询。这里的副作用是用户可能会为今天(27 日)添加一些内容并更改时区并看到 26 日的数据,但我认为这是无法避免的,然后旧数据将按预期显示。
【问题讨论】:
哎呀!? Date() 没有时区,这意味着您已经将时区设置为 UTC。 @ElTomato:startOfDay 是一个日历方法。因此,如果您在美国,它将采用 UTC 日期,将其解释为当地时区中的日期,采用您所在位置的一天开始的确切时间点(几乎总是,但并非总是在午夜),并且将其作为 NSDate 返回。 【参考方案1】:如果您采用当前时间并使用Calendar.current.startOfDay(for: Date())
计算用户当地时区的午夜,您将丢失信息。您不知道手术是在一天中的什么时间进行的。如果您将当地时区的时间保存在另一个字段中,您可以在 UTC 中重建 Date
。
目前尚不清楚您所做的是错误的。日、月、年仅在特定时区有意义。我在华盛顿特区都会区。我们正处于夏令时 (EDT)。现在是 5 月 26 日 20:56。但是,现在是伦敦 5 月 27 日凌晨 1 点 56 分,慕尼黑凌晨 2 点 57 分,特拉维夫凌晨 3 点 57 分。所有这些都发生在完全相同的瞬间。在 UTC 时间是 5 月 27 日凌晨 0:57。
大多数人会想到他们当地时区的日历日期。那是他们的参考框架。如果你现在问我日期,我会告诉你现在是 5 月 26 日晚上。除非我知道你在不同的时区,否则这就是我的“正确”答案。
如果我在我所在时区的某一天午夜开始,每小时拨打Calendar.current.startOfDay(for: Date())
,我将在我当地时区的所有 24 小时内到达当天午夜。对于一天中的前 20 个小时,如果我在 UTC 中创建日历并拨打相同的电话,我会得到相同的结果。但是,在美国东部时间 20:00,如果我在 UTC 中进行相同的查询,我将开始获取下一个日历日。
如果您不知道您在一天中的什么时间拨打了Calendar.current.startOfDay(for: Date())
,那么在您拨打电话的那一刻,就没有万无一失的方法来计算 UTC 中的年月日。这取决于当地时区的时间,以及该时区与 UTC 的偏移量。
考虑这段代码:
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()))")
输出:
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
那是因为现在,在我的时区是 5 月 26 日的 20:56,在 UTC 是 5 月 27 日的 0:56。因此,如果我现在向 UTC 日历询问一天的开始 (Date()
),我会在 5 月 27 日午夜得到 UTC。
如果我问当地日历的相同问题,我会在我所在的时区(UTC 时间 5 月 26 日凌晨 4:00)得到 5 月 26 日午夜。
如果我今天早上 8:00 在我的时区运行相同的代码,我会得到输出:
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
(从美国东部时间 5 月 26 日上午 8:00 开始,UTC 时间也是 5 月 26 日。)
【讨论】:
感谢您的详尽回答。我已经添加了一些关于我想要实现的内容的编辑 - 根据您的回复,我认为我不想使用 UTC,它指向存储日期以外的内容。我更愿意保留日期,因为我还有其他一些使用它的查询。【参考方案2】:这很棘手,而且不是 100% 可靠,并且只有在您知道所有日子都是使用 startOfDay 创建的情况下才有效。但首先你需要决定你想要什么。假设一个日期是在纽约晚上 10 点创建的,另一个是在伦敦的同一时刻,即第二天凌晨 4 点。您想存放在哪一天?
如果您存储的日期是 25 日晚上 10 点,那么您就知道它是在世界标准时间晚上 10 点开始的时区创建的。你很幸运,只有两个时区可以创建这个,一个没有 DST,一个有 DST。所以你知道它发生在这两个时区之一,在 24 小时内。
很遗憾,时区涵盖 26 小时。幸运的是,只有太平洋上的一些岛屿有相同的时间和不同的日期(+13 和-11 小时)。对于这些地方,你不可能知道哪个日期是正确的,但很少有人会受到影响。
【讨论】:
谢谢。是的,所有这些都是使用 startOfDay 创建的。我将假设用户的当前位置是所有条目的创建位置。因此,如果知道时区,您是否会建议通过将存储的属性从日期更改为代表用户日期的字符串来进行迁移,以便准确查询?例如在纽约 25 日晚上 10 点,我将输入 25-05-21,而在伦敦,同一时间将存储为 26-05-21,所以当用户在时区之间移动时,它仍然会返回吗?有没有办法解决这个保留日期属性的问题?以上是关于如何使用 Calendar.current.startOfDay 修复由于 Core Data 中的时区导致的错误日期?的主要内容,如果未能解决你的问题,请参考以下文章
如何在自动布局中使用约束标识符以及如何使用标识符更改约束? [迅速]
如何使用 AngularJS 的 ng-model 创建一个数组以及如何使用 jquery 提交?