【问题标题】:Week of year calculation differences among Java and multi SQL RDBMSJava 和多 SQL RDBMS 之间的年计算差异
【发布时间】:2014-07-04 04:41:41
【问题描述】:

我必须做一些报告,并且必须在 Java 和 MySQL 中计算任何日期的周数 但他们有不同的算法。

首先,请注意,通过某些方法,某年年底的某些日子是下一年的第一周。示例:2012-12-31 是 2013 年的第 1 周(我不记得确切的年份,但大概是这样)。所以我必须计算日期的正确周年

在 Java 中,它基于 2 个条件,具体取决于语言环境:

  • 一周的第一天(周日或周一)
  • 第一周的最少天数(1 或 4)

    Calendar cal = Calendar.getInstance();
    int javaWeek = cal.get(Calendar.WEEK_OF_YEAR);
    int weekYear = cal.getWeekYear();
    

在 MySQL 中,它基于 2 个条件,取决于 mode(0 到 7):

  • 一周的第一天(星期日或星期一)(在 Java 中也是如此)
  • 第一周的最少天数是 4 或第一周包含一周的第一天

    SELECT WEEK('2012-12-12', 1);
    SELECT YEARWEEK('2012-12-12', 1);
    

在 MySQL 中,某些模式会以奇怪的结果计算周数,例如,一年的第 0 周只有 1 天,而其他周有整整 7 天(我认为第 1 周实际上是第一周,而第0周是前一周或其他东西)。我会忽略这些模式

场景是:我在 Java 中根据我的系统语言环境(设置的语言环境:getFirstDayOfWeek()==SUNDAYgetMinimalDaysInFirstWeek==1)计算日期列表的星期,我想在 MySQL 中计算结果一样

但我在 MySQL 中尝试了所有 8 种模式,但没有一个匹配。我失败了,因为在 MySQL 中没有基于第一周最小天数的模式计算周为 1(只有某些模式为 4),所以我手动 setMinimalDaysInFirstWeek(4) 并且 MySQL 中的模式 6 与 Java 中的结果匹配

(我要写一个测试,比较Java和MySQL在2年内每天的结果)

所以我的解决方案是,我必须修复setMinimalDaysInFirstWeek(4),如果getFirstDayOfWeek()是星期一,我在MySQL中选择模式3 ,如果getFirstDayOfWeek()是SUNDAY,我在MySQL中选择模式6。我认为这只是一个临时解决方案,真的很奇怪。

还有一个问题,我真的很害怕如果我的系统从MySQL换到其他RDBMS,它会有其他方法,我无法处理所有这些(Oracle中的示例没有WEEK功能)。那么有没有什么方法可以满足很多 RDBMS 的计算周数呢?

我有一个想法是在一个平台(Java 或 MySQL)中计算周,然后将结果传递给另一个:

  • 如果从 Java 传递到 MySQL,我已经将结果传递给查询字符串,或者构建一个包含我的结果的表

  • 如果从 MySQL 传递到 Java,我必须编写一个过程从 Java 调用它,并且必须解决“找到计算周数的方法满足许多 RDBMS”的问题

但我必须计算大量日期,这些都不能让我满意,有人有想法解决我的问题吗?

【问题讨论】:

  • 你打算在 MySQL 方面做什么?一般来说,您不想在列中的日期上调用这些函数,因为它会使索引的使用无效(即,不要用于按周分组)...
  • "Oracle 没有 WEEK 函数)" - Oracle 确实具有从日期中提取星期的功能。它只是不叫week():你需要使用to_char(some_date, 'IW')
  • @a_horse_with_no_name 我的意思是当我将 DB 更改为 oracle 时,我也必须更改我的 sql 查询。最后决定用日历表(Dimension Date表),一般用在报表中

标签: java sql date datetime week-number


【解决方案1】:

ISO 8601

虽然我在几周内工作的经验并不多,但在我看来,最明智的做法是遵循ISO 8601 标准。该标准显然是defines a week 和周数。

标准说:

  • 一周从星期一开始。
  • 星期几编号为17。星期一 = 1。
  • 第一周包含一年中的第一个星期四
  • 周数从0153。 (没有00。)

MySQL 中的 WEEKOFYEAR()

虽然我不了解 MySQL(我是 Postgres 一类的人),但我确实读过 this documentation on date-and-time functions

WEEK( date, mode ) 函数的模式 3 似乎符合 ISO 8601,其中一周的第一天是星期一,周编号为 1-53,今年有 4 天或更多天(不是官方定义,而是另一种说法'包含第一个星期四')。

此外,MySQL 提供了WEEKOFYEAR( date ) 函数,专门作为模式 3 调用 WEEK 的简写。所以我建议你坚持调用 WEEKOFYEAR。

时区

时区至关重要。日期由时区决定。 2012 年 12 月 12 日星期三晚上,当蒙特利尔正在享受午夜前的最后时刻时,巴黎星期四已经到来,日期是 13 日。宇宙历史上的同一时刻,但日期和时间不同。

我假设但不知道 MySQL 将日期时间值存储在 UTC 中。所以我也假设通过不应用任何时区调整,调用他们与星期相关的函数在 UTC 中有效地工作。

我怀疑这可能是您问题的根源。如果您忽略指定一个,java.util.Calendar 类分配 JVM 的默认时区。顺便说一句,这是一个重要的教训:始终指定时区而不是依赖隐式默认值。所以 MySQL 是按 UTC 计算一年中的一周,而 Calendar 是按其他时区(巴黎、加尔各答等)计算,那么显然结果会有所不同。

我的猜测是解决方案是:

  • 始终使用 UTC 完成 Java 工作
  • 对 MySQL 中的日期时间值应用时区调整(如果可能的话,我不知道)。

哪种解决方案最好取决于您的业务政策。一些企业可能希望按其家庭办公室或主要供应商/客户等的时区工作。其他企业,尤其是那些关注不同时区的企业,选择以 UTC 定义一切。

我怀疑,在这个世界不断缩小的时代,从长远来看,对于大多数人来说,最明智的选择是始终在 UTC 工作。但我不是经营你们公司的人。

Joda-Time 或 java.time

至于 Java 方面,避免使用 java.util.Date 和 .Calendar 类。他们是出了名的麻烦。甚至 Sun/Oracle 也放弃了它们,用 Java 8 中的新 java.time package 取代它们。该软件包的灵感来自Joda-Time。 Joda-Time 继续作为一个可行的项目,java.time 和 Joda-Time 各有优缺点。两者都很好地支持 ISO 8601,并在许多默认行为中使用该标准。两者都支持一年中的一周。 Joda-Time 团队要求我们迁移到 java.time。

大部分 java.time 功能已在 ThreeTen-Backport 中向后移植到 Java 6 和 7,并在 ThreeTenABP 中进一步适应 Android。

参见 Oracle Tutorial 关于 java.time 主题。

java.time 中的示例代码(从 Java 8 开始)

请参阅my Answer 到类似的问题。

OffsetDateTime 类表示时间线上的一个点,分辨率为nanoseconds,调整为offset-from-UTC(不是完整时区)。

OffsetDateTime twelves = OffsetDateTime.of( 2012, 12, 12, 0, 0, 0, 0, ZoneOffset.UTC );

OffsetDateTime::get 方法允许您访问值的任何部分。每个部分都定义为TemporalFieldIsoFields 类提供特定于 ISO-8601 日历系统的 TemporalField 的实现。这包括我们需要的两个:

正在使用中……

int week = twelves.get ( IsoFields.WEEK_OF_WEEK_BASED_YEAR );
int weekYear = twelves.get ( IsoFields.WEEK_BASED_YEAR );

时区是与 UTC 的偏移量加上一组用于处理诸如夏令时 (DST) 等异常情况的规则。在某些时区,一天不是从00:00:00.0 开始的。因此,我们通过 LocalDate 类(仅日期值)让 java.time 确定一天中的第一个时刻。

ZoneId zoneId = ZoneId.of( "America/Montreal" );
LocalDate twelves = LocalDate.of( 2012 , 12 , 12 );
ZonedDateTime twelvesMontreal = twelves.atStartOfDay( zoneId );
int week = twelvesMontreal.get ( IsoFields.WEEK_OF_WEEK_BASED_YEAR );
int weekYear = twelvesMontreal.get ( IsoFields.WEEK_BASED_YEAR );

如果您确定时间和时区无关紧要(请三思!),那么您可以单独使用 LocalDate,因为它也有 get 方法。

LocalDate twelves = LocalDate.of( 2012 , 12 , 12 );
int week = twelves.get ( IsoFields.WEEK_OF_WEEK_BASED_YEAR );
int weekYear = twelves.get ( IsoFields.WEEK_BASED_YEAR );

Joda-Time 2.3 中的示例代码。

方法getWeekOfWeekYear 获取周数。 ISODateTimeFormat 类具有用于为 ISO 8601 定义的各种与周相关的格式生成格式化程序的工厂方法。

DateTime twelves = new DateTime( 2012, 12, 12, 0, 0, 0, DateTimeZone.UTC );
int weekOfTwelves = twelves.getWeekOfWeekyear();

创建字符串表示。请注意,如果您希望格式化程序对其字符串生成应用时区调整,您可以附加对withZone 的调用。否则,将使用 DateTime 分配的时区。

String outputWeek = ISODateTimeFormat.weekyearWeek().print(  twelves );
String outputWeekDate = ISODateTimeFormat.weekDate().print(  twelves );

转储到控制台。

System.out.println( "twelves: " + twelves );
System.out.println( "weekOfTwelves: " + weekOfTwelves );
System.out.println( "outputWeek: " + outputWeek );
System.out.println( "outputWeekDate: " + outputWeekDate );

运行时。

twelves: 2012-12-12T00:00:00.000Z
weekOfTwelves: 50
outputWeek: 2012-W50
outputWeekDate: 2012-W50-3

便携性

至于移植到 MySQL 以外的数据库,我认为坚持越来越普遍的 ISO 8601 标准将有助于提高可移植性。

Postgres 例如 offers the functions isoyearweekisodow(星期几),记录为符合 ISO 8601。

【讨论】:

    猜你喜欢
    • 2011-11-16
    • 2015-04-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-31
    相关资源
    最近更新 更多