【问题标题】:Setting DAY_OF_WEEK returns unexpected result设置 DAY_OF_WEEK 会返回意外结果
【发布时间】:2018-02-08 09:21:37
【问题描述】:

我想将给定日历实例的时间戳设置为一周的开始(星期一),而不是返回一个看似完全不相关的时间戳 - 除非我在此之前访问任何日历的字段。我在下面包含了一个示例,另请参阅Ideone 中的这个可运行示例。

这是预期的行为吗?这背后的逻辑是什么?是的,我听说过 Joda Time。

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;


class MyTest {

private static Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("CET"), Locale.FRANCE);
private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");

public static void main(String[] args) {

    // Set to any date.
    calendar.set(2013, 10, 3);
    System.out.println(dateFormat.format(calendar.getTime()));

    // Set to another day.
    calendar.set(2014, 0, 15);
    // --- THE WTF STARTS HERE ---
    // Uncommenting the line below returns the correct date in the end.
    // calendar.getTime();

    // Set to monday of current week.
    calendar.set(Calendar.DAY_OF_WEEK, calendar.getFirstDayOfWeek());

    // Expected outdate is 20140113
    System.out.println(dateFormat.format(calendar.getTime()));

}

}

【问题讨论】:

  • 你能这样设置第一天吗:calendar.setFirstDayOfWeek(0); calendar.set(Calendar.DAY_OF_WEEK, calendar.getFirstDayOfWeek());这将返回:20140111 编辑:零是星期日等。
  • 是的,但我想要星期一 20140113。
  • 似乎getTime 在内部触发了computeTime...可能连续设置两个日期会弄乱日历的内部状态。
  • 有趣的是,它似乎适用于任何“一周的第一天”除了星期一...
  • Calendar 和 Joda-Time 都是 passé,被 java.time 类所取代。

标签: java calendar


【解决方案1】:

文档中的字段操作章节清楚地解释了它。但它只是工作起来很奇怪。

http://docs.oracle.com/javase/6/docs/api/java/util/Calendar.html

示例:考虑一个最初设置为 1999 年 8 月 31 日的 GregorianCalendar。调用 set(Calendar.MONTH, Calendar.SEPTEMBER) 将日期设置为 1999 年 9 月 31 日。 这是一个临时的内部表示,解决到 1999 年 10 月 1 日,如果 然后调用 getTime()。但是,在之前调用 set(Calendar.DAY_OF_MONTH, 30) 调用 getTime() 将日期设置为 1999 年 9 月 30 日,因为不会发生重新计算 在 set() 本身之后。

编辑

来自同一文档的日历字段解析部分

If there is any conflict in calendar field values, Calendar gives priorities to 
calendar fields that have been set more recently. The following are the default 
combinations of the calendar fields. The most recent combination, as determined 
by the most recently set single field, will be used.

For the date fields:

 YEAR + MONTH + DAY_OF_MONTH
 YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK
 YEAR + MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK
 YEAR + DAY_OF_YEAR
 YEAR + DAY_OF_WEEK + WEEK_OF_YEAR

我认为 MONTH 和 DAY_OF_WEEK 之间的区别是这样的。如果您在最后一条语句中设置 MONTH,它将与 YEAR+MONTH+DAY_OF_MONTH 匹配并覆盖所有这些。如果您设置 DAY_OF_WEEK 它与 YEAR+DAY_OF_WEEK+WEEK_OF_YEAR 匹配,因此不会覆盖月份值。或类似的东西。老实说,我越看越觉得破碎。这根本没有意义。最好继续使用 JodaTime

【讨论】:

  • calendar.getTime()->getTimeInMillis()->updateTime()->computeTime()
  • (+1) 我完全无法用语言表达,这与您在我的答案中的答案有关。调用 calendar.clear(); 将重置该值并允许使用另一个 clear calendar.set(int, int, int)。
  • @RossC 是的,似乎应该调用 clear() 或 get() 函数
  • 谢谢,很有趣 - 这可以解释它。但是,如果在最后一组中我将Calendar.MONTH 设置为:calendar.set(Calendar.MONTH, Calendar.FEBRUARY);,即使没有先前的calendar.getTime();,它也可以工作,无论如何 - 这非常不直观。
  • “非常不直观”是的,总结一下! :) 很高兴它正在工作并清理干净。
【解决方案2】:

你能清除保留值的日历吗?

Kaya's answer about why this would be relevant

例如:

calendar.clear();
// Set to another day.
calendar.set(2014, 0, 14);

这会返回:

20140113 而不是您之前得到的 20131223

来自文档:

void java.util.Calendar.set(int year, int month, int date)

设置日历字段 YEAR、MONTH 和 DAY_OF_MONTH 的值。 保留其他日历字段的先前值。如果这不是 需要,请先调用 clear()。

【讨论】:

  • 我可以这样做 - 但我为什么要这样做?我覆盖了所有必要的字段。
  • 编辑为“星期一”。正如上面其他人所说,“似乎 getTime 在内部触发了 computeTime……可能连续设置两个日期会弄乱日历的内部状态。”由@kaya 在答案中回答。根据我的回答,使用 clear() 将撤消问题。
【解决方案3】:

tl;博士

LocalDate.now( ZoneId.of( "Africa/Tunis" ) )                  // Get the current date for a particular region.
    .with(             
        TemporalAdjusters.previousOrSame( DayOfWeek.MONDAY )  // Move to an earlier date that is Monday, or stay on same date if it already *is* Monday.
    )                                                         // Return `LocalDate` object.

java.time

Calendar 类现在是遗留的,被 java.time 类取代,特别是 ZonedDateTime。所以这个问题现在没有实际意义。

遗留类的问题之一是星期开始的定义因语言环境而异,以及年、月和星期几的疯狂编号方案。相比之下,java.time 默认情况下始终将星期一视为一周的开始,根据ISO 8601 标准一直持续到星期日。和 java.time 使用理智的编号:

  • 2018 是 2018 年,1900 年没有乱七八糟的数学。
  • 1 月至 12 月为 1-12 个月。
  • 1-7 表示周一至周日。

LocalDate

LocalDate 类表示没有时间和时区的仅日期值。

时区对于确定日期至关重要。对于任何给定的时刻,日期在全球范围内因区域而异。例如,Paris France 中午夜后几分钟是新的一天,而 Montréal Québec 中仍然是“昨天”。

如果没有指定时区,JVM 会隐式应用其当前的默认时区。该默认值可能随时更改,因此您的结果可能会有所不同。最好将所需/预期的时区明确指定为参数。

continent/region 的格式指定proper time zone name,例如America/MontrealAfrica/CasablancaPacific/Auckland。切勿使用 3-4 个字母的缩写,例如 ESTIST,因为它们不是真正的时区,没有标准化,甚至不是唯一的 (!)。

ZoneId z = ZoneId.of( "America/Montreal" ) ;  
LocalDate today = LocalDate.now( z ) ;

如果你想使用 JVM 当前的默认时区,请求它并作为参数传递。如果省略,则隐式应用 JVM 的当前默认值。最好是明确的,因为默认值可能会在任何时候在运行时被 JVM 中任何应用程序的任何线程中的任何代码更改。

ZoneId z = ZoneId.systemDefault() ;  // Get JVM’s current default time zone.

或者指定一个日期。您可以通过数字设置月份,1 月至 12 月的编号为 1-12。

LocalDate ld = LocalDate.of( 1986 , 2 , 23 ) ;  // Years use sane direct numbering (1986 means year 1986). Months use sane numbering, 1-12 for January-December.

或者,更好的是,使用预定义的Month 枚举对象,一年中的每个月一个。提示:在整个代码库中使用这些 Month 对象,而不是仅仅使用整数,以使您的代码更具自记录性、确保有效值并提供 type-safety

LocalDate ld = LocalDate.of( 1986 , Month.FEBRUARY , 23 ) ;

调节器

要从一个日期移动到另一个日期,请使用TemporalAdjusters 类中的TemporalAdjuster 实现。使用 DayOfWeek 枚举指定所需的星期几。

LocalDate previousOrSameMonday = today.with( TemporalAdjusters.previousOrSame( DayOfWeek.MONDAY ) ) ;

关于java.time

java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。

要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310

从哪里获得 java.time 类?

ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多