gpt4 book ai didi

java - 日历set()在Android API 23及更低版本上损坏-java.util.Calendar

转载 作者:行者123 更新时间:2023-11-29 18:44:47 24 4
gpt4 key购买 nike

我正在使用java.util.Calendar通过其set()方法查找给定星期的开始。

  • 这在Android Nougat +上完美运行,但不适用于棉花糖以下的任何Android版本。
  • 我已经在物理设备和仿真器上进行了测试。
  • 我已使用调试器来验证问题出在日历代码上,而不是显示它时出现了一些问题。
  • 我已经使用Kotlin和Java来创建不同的最小示例,但问题仍然存在。

  • 这是Kotlin的最小示例,其中TextView显示日期,而Button用于将该日期增加一周:
    class MainActivity : AppCompatActivity() {

    var week = 10

    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    // Set TextView to show the date of the 10th week in 2018.
    setCalendarText(week)

    // Increase the week on every button click, and show the new date.
    button.setOnClickListener { setCalendarText(++week) }
    }

    /**
    * Set the text of a TextView, defined in XML, to the date of
    * a given week in 2018.
    */
    fun setCalendarText(week: Int) {
    val cal = Calendar.getInstance().apply {
    firstDayOfWeek = Calendar.MONDAY
    set(Calendar.YEAR, 2018)
    set(Calendar.WEEK_OF_YEAR, week)
    set(Calendar.DAY_OF_WEEK, Calendar.MONDAY)
    set(Calendar.HOUR_OF_DAY, 0)
    set(Calendar.MINUTE, 0)
    set(Calendar.SECOND, 1)
    }
    textView.text = SimpleDateFormat("dd MMMM yyyy", Locale.UK).format(cal.time)
    }
    }

    当按预期方式工作时, Activity 将启动,并且TextView设置为显示“2018年3月5日”。单击该按钮后,此值将更改为每连续一周的第一天。

    在Android棉花糖及以下版本上:
  • TextView的初始值设置为当前星期的开始(2018年9月3日)。
  • 单击该按钮时,日期不会更改。
  • 如果将日期设置为Calendar.SUNDAY,则日历可以正确检索当前星期的最后一天。其他任何星期都将无法使用。

  • 编辑:我试图创建一个Java MVCE,它允许您通过运行 CalendarTester.test()来快速检查基本问题是否出现。
    import android.util.Log;
    import java.text.SimpleDateFormat;
    import java.util.Calendar;
    import java.util.Locale;

    class CalendarTester {

    /**
    * Check that the Calendar returns the correct date for
    * the start of the 10th week of 2018 instead of returning
    * the start of the current week.
    */
    public static void test() {
    // en_US on my machine, but should probably be en_GB.
    String locale = Locale.getDefault().toString();
    Log.v("CalendarTester", "The locale is " + locale);

    Long startOfTenthWeek = getStartOfGivenWeek(10);
    String startOfTenthWeekFormatted = formatDate(startOfTenthWeek);

    boolean isCorrect = "05 March 2018".equals(startOfTenthWeekFormatted);

    Log.v("CalendarTester", String.format("The calculated date is %s, which is %s",
    startOfTenthWeekFormatted, isCorrect ? "CORRECT" : "WRONG"));
    }

    public static Long getStartOfGivenWeek(int week) {
    Calendar cal = Calendar.getInstance();
    cal.setFirstDayOfWeek(Calendar.MONDAY);
    cal.set(Calendar.YEAR, 2018);
    cal.set(Calendar.WEEK_OF_YEAR, week);
    cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
    cal.set(Calendar.HOUR_OF_DAY, 0);
    cal.set(Calendar.MINUTE, 0);
    cal.set(Calendar.SECOND, 1);

    return cal.getTimeInMillis();
    }

    public static String formatDate(Long timeInMillis) {
    return new SimpleDateFormat("dd MMMM yyyy", Locale.UK).format(timeInMillis);
    }
    }

    最佳答案

    tl; dr

    使用反向移植到早期Android的java.time类。

    问题陈述:从当前日期开始,移至上一个或同一周一,然后移至该日期所在的周年的标准ISO 8601第10周的星期一,加一个星期,并为结果日期生成标准ISO 8601格式的文本。

    org.threeten.bp.LocalDate.now(         // Represent a date-only value, without time-of-day and without time zone.
    ZoneId.of( "Europe/London" ) // Determining current date requires a time zone. For any given moment, the date and time vary around the globe by zone.
    ) // Returns a `LocalDate`. Per immutable objects pattern, any further actions generate another object rather than changing (“mutating”) this object.
    .with(
    TemporalAdjusters.previousOrSame( // Move to another date.
    DayOfWeek.MONDAY // Specify desired day-of-week using `DayOfWeek` enum, with seven objects pre-defined for each day-of-week.
    )
    ) // Renders another `LocalDate` object.
    .with(
    IsoFields.WEEK_OF_WEEK_BASED_YEAR ,
    10
    )
    .plusWeeks( 1 )
    .toString()

    2018-03-12



    简化问题

    当追踪神秘或错误的行为时,只需简单地编程即可再现问题。在这种情况下,请剥离不相关的GUI代码以专注于日期时间类。

    如在科学实验中一样,控制各种变量。在这种情况下,时区和 Locale都会影响 Calendar的行为。一方面, Calendar中一周的定义因 Locale而异。因此,通过硬编码明确指定这些方面。

    设置特定的日期和时间,因为不同区域中不同日期的不同时间会影响行为。
    Calendar是具有各种实现的父类(super class)。如果您期望 GregorianCalendar,请在调试时显式使用它。

    因此,请尝试在各种工具方案中运行类似以下内容的程序来解决问题。
    TimeZone tz = TimeZone.getTimeZone( "America/Los_Angeles" );
    Locale locale = Locale.US;
    GregorianCalendar gc = new GregorianCalendar( tz , locale );
    gc.set( 2018 , 9- 1 , 3 , 0 , 0 , 0 ); // Subtract 1 from month number to account for nonsensical month numbering used by this terrible class.
    gc.set( Calendar.MILLISECOND , 0 ); // Clear fractional second.
    System.out.println( "gc (original): " + gc.toString() );
    System.out.println( gc.toZonedDateTime() + "\n" ); // Generate a more readable string, using modern java.time classes. Delete this line if running on Android <26.

    int week = 10;
    gc.set( Calendar.WEEK_OF_YEAR , week );
    System.out.println( "gc (week=10): " + gc.toString() );
    System.out.println( gc.toZonedDateTime() + "\n" );

    int weekAfter = ( week + 1 );
    gc.set( Calendar.WEEK_OF_YEAR , weekAfter );
    System.out.println( "gc (weekAfter): " + gc.toString() );
    System.out.println( gc.toZonedDateTime() + "\n" );

    运行时。

    gc (original): java.util.GregorianCalendar[time=?,areFieldsSet=false,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2018,MONTH=8,WEEK_OF_YEAR=36,WEEK_OF_MONTH=2,DAY_OF_MONTH=3,DAY_OF_YEAR=251,DAY_OF_WEEK=7,DAY_OF_WEEK_IN_MONTH=2,AM_PM=1,HOUR=2,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=-28800000,DST_OFFSET=3600000]

    2018-09-03T00:00-07:00[America/Los_Angeles]

    gc (week=10): java.util.GregorianCalendar[time=?,areFieldsSet=false,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2018,MONTH=8,WEEK_OF_YEAR=10,WEEK_OF_MONTH=2,DAY_OF_MONTH=3,DAY_OF_YEAR=246,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=1,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=-28800000,DST_OFFSET=3600000]

    2018-03-05T00:00-08:00[America/Los_Angeles]

    gc (weekAfter): java.util.GregorianCalendar[time=?,areFieldsSet=false,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2018,MONTH=2,WEEK_OF_YEAR=11,WEEK_OF_MONTH=2,DAY_OF_MONTH=5,DAY_OF_YEAR=64,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=1,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=-28800000,DST_OFFSET=0]

    2018-03-12T00:00-07:00[America/Los_Angeles]



    java.time

    确实,您的问题是有争议的,因为您根本不应该使用可怕的旧 Calendar类。它是几年前麻烦的旧日期时间类的一部分,而现代的java.time类取代了它。对于早期的Android,请参阅下面底部的最新项目符号。

    Calendar / GregorianCalendar中,一周的定义因 Locale而异,默认情况下,在java.time中不是这样,它使用 ISO 8601标准 definition of a week
  • 第1周是日历年的第一个星期四。
  • 星期一是一周的第一天。
  • 以一周为基础的年份有52或53周。
  • 日历的前几天/后几天可能出现在上一周/下一周。
  • LocalDate
    LocalDate 类表示没有日期和时区的仅日期值。

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

    如果未指定时区,则JVM隐式应用其当前的默认时区。该默认值可能在运行时(!)期间为 change at any moment,因此您的结果可能会有所不同。最好将 desired/expected time zone明确指定为参数。

    continent/region的格式指定 proper time zone name,例如 America/Montreal Africa/Casablanca Pacific/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。
    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( 2018 , Month.SEPTEMBER , 3 ) ;
    TemporalAdjuster
    要移至上一个星期一,或者如果已经是星期一,则停留在该日期,请使用 TemporalAdjuster类中提供的 TemporalAdjusters实现。使用 DayOfWeek枚举指定所需的星期几。
    LocalDate monday = ld.with( TemporalAdjusters.previousOrSame( DayOfWeek.MONDAY ) ) ;
    IsoFields
    java.time类在数周内的支持有限。使用 IsoFields 类及其常量 WEEK_OF_WEEK_BASED_YEARWEEK_BASED_YEAR
    LocalDate mondayOfWeekTen = monday.with( IsoFields.WEEK_OF_WEEK_BASED_YEAR , 10 ) ;

    ISO 8601

    ISO 8601标准定义了许多有用的实用格式,用于将日期时间值表示为文本。这包括几周。让我们生成这样的文本作为输出。
    String weekLaterOutput = 
    weekLater
    .get( IsoFields.WEEK_BASED_YEAR )
    + "-W"
    + String.format( "%02d" , weekLater.get( IsoFields.WEEK_OF_WEEK_BASED_YEAR ) )
    + "-"
    + weekLater.getDayOfWeek().getValue()
    ; // Generate standard ISO 8601 output. Ex: 2018-W11-1

    转储到控制台。
    System.out.println("ld.toString(): " + ld);
    System.out.println("monday.toString(): " +monday);
    System.out.println("weekLater.toString(): " + weekLater);
    System.out.println( "weekLaterOutput: " + weekLaterOutput ) ;

    运行时。

    ld.toString(): 2018-09-03

    monday.toString(): 2018-09-03

    weekLater.toString(): 2018-03-12

    weekLaterOutput: 2018-W11-1



    Java技巧(非Android):如果需要花费数周的时间,请考虑添加 ThreeTen-Extra库以访问其 YearWeek类。

    关于java.time

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

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

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

    您可以直接与数据库交换java.time对象。使用兼容 JDBC driver或更高版本的 JDBC 4.2。不需要字符串,不需要 java.sql.*类。

    在哪里获取java.time类?
  • Java SE 8Java SE 9Java SE 10Java SE 11和更高版本-具有 bundle 实施的标准Java API的一部分。
  • Java 9添加了一些次要功能和修复。
  • Java SE 6Java SE 7
  • 大部分java.time功能都在ThreeTen-Backport中反向移植到Java 6和7。
  • Android
  • 更高版本的java.time类的Android bundle 实现。
  • 对于早期的Android(<26),ThreeTenABP项目改编了ThreeTen-Backport(如上所述)。参见How to use ThreeTenABP…
  • 关于java - 日历set()在Android API 23及更低版本上损坏-java.util.Calendar,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52235669/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com