不同机器上的WEEK_OF_YEAR不一致

5
更新:好的,我似乎已经找到了一半的答案。如果我使用没有参数的getInstance创建我的日历,我得到的WEEK_OF_YEAR是52。然而,如果我使用Local.getDefault()提供给getInstance来创建它,我得到的WEEK_OF_YEAR是1。完全没想到这点...我想重新阅读一下Calendar文档。

从时间戳构建一个日历,对应于2011年1月1日星期六00:00:00 GMT

相同的代码,使用java.util.Date、Calendar和TimeZone,在不同的机器上(具有相同的区域设置)的行为不同;除了WEEK_OF_YEAR之外,日历中的所有字段都是相同的。在我的机器上,它是52(实际上是我的两台机器)。在我的同事机器上,它是1(似乎是正确的)。

import java.util.Date;
import java.util.TimeZone;
import java.util.Calendar;
import java.util.Locale;

public class CalendarTest {

    public static void main(String[] args) {

        Locale l = Locale.getDefault();
        System.out.println(l);
        Long d = new Long(1293840000000l);

        Calendar c = Calendar.getInstance();
        c.setTimeZone(TimeZone.getTimeZone("UTC"));
        c.setTime(new Date(d));

        System.out.println(c.toString());
}

本地化为en_US,但日历是:

>java.util.GregorianCalendar[time=1293840000000,
areFieldsSet=true,
areAllFieldsSet=true,
lenient=true,
zone=sun.util.calendar.ZoneInfo[
id="UTC",
offset=0,
dstSavings=0,
useDaylight=false,
transitions=0,lastRule=null
],
firstDayOfWeek=2,
minimalDaysInFirstWeek=4,
ERA=1,
YEAR=2011,
MONTH=0,
WEEK_OF_YEAR=52,
WEEK_OF_MONTH=0,
DAY_OF_MONTH=1,
DAY_OF_YEAR=1,
DAY_OF_WEEK=7,
DAY_OF_WEEK_IN_MONTH=1,
AM_PM=0,HOUR=0,
HOUR_OF_DAY=0,
MINUTE=0,
SECOND=0,
MILLISECOND=0,
ZONE_OFFSET=0,
DST_OFFSET=0]

可能是什么原因导致这个周数不一致?

如果同事的机器时间不同,会有影响吗?请查看同事机器的机器时间。 - My God
1
Java版本和操作系统(+版本)等信息可能很有趣(它应该不相关,但我不确定它们是否相关...) - Marco13
1
我的猜测是:其中一台计算机正在使用Java 8,而另一台计算机正在使用Java 7。这个猜测正确吗? - Marco13
尽管我之前的评论是如此,但是输出结果显示firstDayOfWeek=2,这是“不寻常的”。我测试了Locale.US和*所有可用的TimeZone(在Java7和Java8上都是如此),没有任何组合会显示firstDayOfWeek=2的结果。再次强调,提供一些实际设置的更多信息可能会有所帮助(并且可能需要一个易于比较的输出版本 - 它几乎肯定会说firstDayOfWeek=1... 但是我还没有弄清楚差异可能来自哪里....) - Marco13
所有的机器都在使用Java 7,而且如果Java在7版本中存在这种向后不兼容性,我会非常惊讶。 - alexakarpov
显示剩余5条评论
2个回答

6

firstDayOfWeekminimalDaysInFirstWeek

原来这不是bug,而是一个特性。

不同行为的原因是你在问题中展示的输出中报告了两个设置:

  • firstDayOfWeek
  • minimalDaysInFirstWeek

阅读类和子类的文档非常重要:

第二个文档详细解释了上述两个设置对确定本地化星期的重要性。
日历注意事项。2011年的第一天是星期六。月份的第二天是星期日,星期日是美国默认的一周开始日。

Calendar of month of January 2011 showing the first of the month is a Saturday

在设置为美国区域设置的Mac OS X计算机上,这些设置都是1。如果所需的最小天数为1,则第一天落在本地化的第1周。Java报告了这个问题。
但是,在您报告的问题机器上,这些设置分别为2和4。我不知道您如何将这些设置改变为与通常默认值不同,但您确实这样做了。
  • firstDayOfWeek|12(星期日与星期一)
  • minimalDaysInFirstWeek|14
最少4天意味着第一天不符合新年的一周。因此它是去年(2010年)的第52周。2011年的第一周是1月2日至1月8日。
因此,您看到的行为符合Java 7中java.util.Calendar类的文档的预期。谜团是您的问题机器上如何更改这些设置以使其远离默认设置?

ISO 8601

顺便提一下,文档中提到设置为2和4可以给您提供 ISO 8601标准定义的行为,如我的另一个答案所述。这可能是为什么这些设置在您的问题机器上不是默认设置的线索。某个系统管理员或程序员可能正在尝试获取标准行为而不是本地化行为。

示例代码

让我们用一些代码来演示这个问题。我们将使用一个修改过的问题代码版本。在这里,我们明确设置了相关变量。因此,您可以在任何机器上运行此示例,包括正常或有问题的机器。首先,我们强制使用默认情况下在美国语言环境机器上找到的设置,即11。然后我们使用在问题中报告的设置,即24

Locale l = Locale.getDefault();
System.out.println( l + "\n" );
Long d = new Long( 1293840000000l );

Calendar c = Calendar.getInstance();
c.setTimeZone( TimeZone.getTimeZone( "UTC" ) );
c.setTime( new Date( d ) );

// Running Java 8 Update 11, Mac OS 10.8.5, virtual machine in Parallels 9, hosted on Mac with Mavericks.

// Force the use of default settings found on a machine set for United States locale (using Apple defaults).
c.setFirstDayOfWeek( 1 );
c.setMinimalDaysInFirstWeek( 1 );
// Reports: WEEK_OF_YEAR=1
System.out.println( "Default US settings:\n" + c.toString() + "\n" );

// Using reported settings (Coincides with ISO 8601 Week definition).
c.setFirstDayOfWeek( 2 );
c.setMinimalDaysInFirstWeek( 4 );
// Reports: WEEK_OF_YEAR=52
System.out.println( "Reported settings (ISO 8601):\n" + c.toString() + "\n" );

当运行时...
en_US

Default US settings:
java.util.GregorianCalendar[time=1293840000000,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="UTC",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2011,MONTH=0,WEEK_OF_YEAR=1,WEEK_OF_MONTH=1,DAY_OF_MONTH=1,DAY_OF_YEAR=1,DAY_OF_WEEK=7,DAY_OF_WEEK_IN_MONTH=1,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=0,DST_OFFSET=0]

Reported settings (ISO 8601):
java.util.GregorianCalendar[time=1293840000000,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="UTC",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null],firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2011,MONTH=0,WEEK_OF_YEAR=52,WEEK_OF_MONTH=0,DAY_OF_MONTH=1,DAY_OF_YEAR=1,DAY_OF_WEEK=7,DAY_OF_WEEK_IN_MONTH=1,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=0,DST_OFFSET=0]

故事寓意

使用ISO 8601标准周


感谢Marco13对这个问题的评论,激发了我写这篇答案。


感谢您提供详细的答案。天啊,日历真是一项复杂的业务... - alexakarpov
另外,这就解释了为什么重新安装整个Mac Os X会有所不同(正如团队成员建议的那样-因为谁知道在之前对该操作系统做了什么)。 - alexakarpov
1
PS:现在我正在尝试弄清楚是什么导致了我的JVM日历设置出现问题。我已经创建了一个新的主题/问题...问题最糟糕的部分似乎是'Locale.setDefault(Locale.getDefault())'正在解决问题-但这是荒谬和错误的... - alexakarpov

0

[请看下面的更新。答案不正确。]

对于ISO 8601周,其中第一周是从星期四开始计算的,正确答案2010-W52

为了比较和实验,请尝试使用Joda-Time或Java 8中的新java.time包(受Joda-Time启发)。无论如何,您都应该使用它们,因为java.util.Date和.Calendar类是出了名的麻烦。

Joda-Time

DateTime dateTime = new DateTime( 2011, 1, 1, 0, 0, 0, DateTimeZone.UTC );
int weekNumber = dateTime.getWeekOfWeekYear();
String output = ISODateTimeFormat.weekDate().print( dateTime );

更新:答案无关

我错误地假设java.util.Calendar按照ISO 8601标准确定年周。实际上并非如此。它使用Locale来影响其getFirstDayOfWeek()方法的结果。然后,它使用以该日为起始的最早的七天作为其第一周。请参见文档中的“第一周”部分

因此,在调试问题时使用Joda-Time或java.time将无济于事。我保留这个答案来强调(a)使用标准周与本地化周之间的差异,以及(b)不要做出假设的重要性。


谢谢您的建议;我刚到新工作第三天,也许现在不是引入新依赖项的最佳时机;而且,这对其他人来说“运作”得很好...所以这更多是为了让我理解正在发生的事情,而不是解决一些紧迫的问题。 - alexakarpov
1
@alexakarpov 将Joda-Time添加到项目中将是巩固你在新工作中声誉的绝佳方式。;-) j.u.Date/Calendar类确实很糟糕。但在短期内,我的建议仅是使用Joda-Time进行比较作为您调查的一部分。如果相同版本的Joda-Time在两个环境中都能够一致地工作(或者不行),那么这将为您提供更多信息。正如其他人建议的那样,问题可能是机器上当前时间设置或时区、j.u.Date/Calendar在不同版本中显示的错误、过时的tz数据库或其他问题。 - Basil Bourque
我同意;但最紧迫的目标是找出为什么日历和默认语言环境会出现故障。 - alexakarpov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接