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

大白菜

我正在使用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)
    }
}

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

在Android棉花糖及以下版本上:

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

编辑:我尝试创建Java MVCE,该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

使用反向移植到早期Androidjava.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一方面,一周内的定义因而CalendarLocale因此,通过硬编码明确指定这些方面。

设置特定的日期和时间,因为不同区域中不同日期的不同时间会影响行为。

Calendar是具有各种实现的超类。如果您期望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(原始):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,转换= 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,minimumDaysInFirstWeek = 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 [美国/洛杉矶]

gc(周= 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,转换= 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,minimumDaysInFirstWeek = 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 [美国/洛杉矶]

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,转换= 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,minimumDaysInFirstWeek = 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 [美国/洛杉矶]

java.time

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

Calendar/中GregorianCalendar,一周的定义Locale因而,默认情况下,java.time中不是这样,它使用ISO 8601标准的week来定义

  • 第一周是日历年的第一个星期四。
  • 星期一是一周的第一天。
  • 以一周为基础的一年有52周或53周。
  • 日历的前几天/后几天可能出现在上一周/下一周。

LocalDate

LocalDate级表示没有时间一天和不同时区的日期,唯一的价值。

时区对于确定日期至关重要。在任何给定时刻,日期都会在全球范围内变化。例如,法国巴黎午夜过后几分钟是新的一天,而在魁北克蒙特利尔仍然是“昨天”

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

指定适当的时区名称,格式continent/region,如America/MontrealAfrica/CasablancaPacific/Auckland切勿使用3-4字母的缩写,例如EST或,IST因为它们不是真实的时区,不是标准化的,甚至不是唯一的(!)。

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在整个代码库中使用这些对象,而不仅仅是一个整数,可以使您的代码更具自文档性,确保有效值并提供类型安全

LocalDate ld = LocalDate.of( 2018 , Month.SEPTEMBER , 3 ) ;

TemporalAdjuster

要移至上一个星期一,或者如果已经是星期一,则停留在该日期,请使用该类中TemporalAdjuster提供实现TemporalAdjustersDayOfWeek枚举指定所需的星期几

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

周后输出:2018-W11-1

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


关于java.time

java.time框架是建立在Java 8和更高版本。这些类取代麻烦的老传统日期时间类,如java.util.DateCalendar,和SimpleDateFormat

现在处于维护模式Joda-Time项目建议迁移到java.time类。

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

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

在哪里获取java.time类?

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

Android API 24及更低版本上的PBKDF2WithHmacSHA256

适用于Android GingerBread及更低版本(API <11)的日历视图

@style属性在api 24及更低版本上崩溃

安卓| Java 通知未出现在 API 25 及更低版本中

Android 4及更低版本上的Android ASyncTask崩溃

KitKat(及更低版本)设备上的Android Material Design

在Android 4.4或更低版本上显示为FAB

Android-Firebase ChangeValueEventListener 停止为 Android API 27(8.1 或更低版本)工作

不展示适用于Android API 8或更低版本的广告

我如何使用java.util.Calendar返回伊斯兰日历?

服务中的错误startActivity错误使用API 23或更低版本

我的应用在 API 21 及更低版本的设备上崩溃

为什么java.util.Calendar类在android上给出不正确的日期?

与java.util.Calendar混淆

Android NDK-无法加载库:Android 17及更低版本上的load_library

IE9及更低版本中的Youtube API

Java 5或更低版本中的抽象类

Android 7.1及更低版本的双通知显示

Android M及更低版本中的LineHeightSpan

为什么Java日期API(java.util.Date,.Calendar)如此混乱?

如何检查位置是否在API 18及更低版本上并且处于较高优先级

应用程序在从 Android Studio 安装时崩溃(仅在 android 5 及更低版本上)

我如何检查运行我的应用程序的Android设备是否在4.2.2及更低版本上运行

我如何检查运行我的应用程序的Android设备是否在4.2.2及更低版本上运行

java.util.Calendar线程是否安全?

奇怪的java.util.calendar输出

关于java.util.Calendar的问题

GWT日期等于java.util.Calendar

Android访问令牌上的Google Calendar API