Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DenyListedApiDetector.kt seems to be missing java.util.Calendar #347

Open
jbduncan opened this issue Jan 24, 2025 · 5 comments
Open

DenyListedApiDetector.kt seems to be missing java.util.Calendar #347

jbduncan opened this issue Jan 24, 2025 · 5 comments

Comments

@jbduncan
Copy link

Currently, DenyListedApiDetector.kt finds references to old date and time types like java.util.Date.

However, java.util.Calendar seems to be missing, and I was wondering if it was just missed or if there was another reason for its absence?

@ZacSweers
Copy link
Collaborator

No real reason. What would be the replacement in java.time?

@jbduncan
Copy link
Author

Good question! Here's a list of how our codebases use (Gregorian)Calendar and how I imagine they'd be replaced.

Note:

  • (Gregorian)Calendar can be subclassed, but such subclasses may be untrusted code or override key methods, so I think it's okay to ignore them.
  • java.time seems to require Android API level 26 according to Android Studio, so it seems sensible for slack-lints to detect this and make no suggestions on projects targeting older versions.

Hour of the day

From (1)

// Some Date representing "now", either real (like Date()) or a fake.
val now: Date = ... 
// One of Calendar.getInstance(), GregorianCalendar.getInstance() or
// a GregorianCalendar(...) instance with the current time zone.
val calendar: Calendar = ...
calendar.time = now
val currentHourOfDay: Int = calendar.get(Calendar.HOUR_OF_DAY)

From (2)

// One of Calendar.getInstance(), GregorianCalendar.getInstance() or
// a GregorianCalendar(...) instance with the current time zone.
val calendar = ...
val currentHourOfDay = calendar.get(Calendar.HOUR_OF_DAY)

To

// Some Clock representing "now", either real (like
// Clock.systemDefaultZone()) or a fake.
val clock: Clock = ...
val currentHourOfDay: Int = LocalTime.now(clock).hour

Current time in UTC milliseconds since the epoch

From

// Any Calendar or GregorianCalendar instance; time zone doesn't matter.
val calendar: Calendar = ...
val utcTimeInMillis: Long = calendar.timeInMillis

To

// Some Clock representing "now" in UTC, either real (like Clock.systemUTC())
// or a fake.
val clock: Clock = ...
val utcTimeInMillis = clock.millis()

Adding/subtracting time

From

// One of Calendar.getInstance(), GregorianCalendar.getInstance() or
// a GregorianCalendar(...) instance with the current time zone.
val calendar = ...
calendar.add(Calendar.SECOND, 3600)
val future: Date = calendar.time

To Instant

// Some Clock representing "now", either real (like
// Clock.systemDefaultZone()) or a fake.
val clock: Clock = ...
val future: Instant = clock.instant().plus(3600, ChronoUnit.SECONDS) // or .plusSeconds(3600)

To ZonedDateTime

// Some Clock representing "now", either real (like
// Clock.systemDefaultZone()) or a fake.
val clock: Clock = ...
val future: ZonedDateTime = ZonedDateTime.from(clock).plus(3600, ChronoUnit.SECONDS) // or .plusSeconds(3600)

Get today at midnight

From

// One of Calendar.getInstance(), GregorianCalendar.getInstance() or
// a GregorianCalendar(...) instance with the current time zone.
val calendar = ...
calendar.set(
    calendar.get(Calendar.YEAR),
    calendar.get(Calendar.MONTH),
    calendar.get(Calendar.DATE),
    0,
    0,
    0
)
calendar.set(Calendar.MILLISECOND, 0)
val todayAtMidnight: Date = calendar.time

To Instant

val todayAtMidnight: Instant =
    ZonedDateTime.now(Clock.systemDefaultZone())
        .truncatedTo(ChronoUnit.DAYS)
        .toInstant()

To ZonedDateTime

val todayAtMidnight: ZonedDateTime =
    ZonedDateTime.now(Clock.systemDefaultZone())
        .truncatedTo(ChronoUnit.DAYS)

Schedule an operation for 2am in millis since UTC epoch

From

val HOUR_OF_DAY_FOR_REBOOT = 2
// One of Calendar.getInstance(), GregorianCalendar.getInstance() or
// a GregorianCalendar(...) instance with the current time zone.
val calendar = ...
calendar.apply {
    if (get(Calendar.HOUR_OF_DAY) >= HOUR_OF_DAY_FOR_REBOOT) {
        add(Calendar.DATE, 1)
    }
    set(Calendar.HOUR_OF_DAY, HOUR_OF_DAY_FOR_REBOOT)
    set(Calendar.MINUTE, 0)
    set(Calendar.SECOND, 0)
    set(Calendar.MILLISECOND, 0)
}
val scheduleTimeInUtcMillis = calendar.timeInMillis

To

val HOUR_OF_DAY_FOR_REBOOT = 2
// Some Clock representing "now", either real (like
// Clock.systemDefaultZone()) or a fake.
val clock: Clock = ...
val scheduledTime =
    ZonedDateTime.now(clock)
        .let { now ->
            now.plus(
                Duration.ofDays(
                    if (now.hour >= HOUR_OF_DAY_FOR_REBOOT) 1 else 0
                )
            )
        }
        .truncatedTo(ChronoUnit.DAYS)
        .withHour(HOUR_OF_DAY_FOR_REBOOT)
        .toInstant()
val scheduledTimeInUtcMillis = scheduledTime.toEpochSecond()

@ZacSweers
Copy link
Collaborator

Sounds reasonable to me.

Re: minSdk 26 - library desugaring exists in android and anyone not using it for some reason can be assumed to just disable this lint, or is possibly not using extra static analysis tooling anyway

@jbduncan
Copy link
Author

Re: minSdk 26 - library desugaring exists in android and anyone not using it for some reason can be assumed to just disable this lint, or is possibly not using extra static analysis tooling anyway

Oh, good shout, thanks for reminding me about the existence of desugaring. 👍

Changing the subject, I understand this project is similar to Guava in that most/all development is done internally, but if there's anything I can do to help move this issue forward, please let me know!

@ZacSweers
Copy link
Collaborator

PR welcome!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants