Move DateUtilities to kmp

pull/2998/head
Alex Baker 4 months ago
parent fcc4b2ef2a
commit 865cec7220

@ -5,452 +5,372 @@
*/
package com.todoroo.andlib.utility
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.tasks.Freeze.Companion.freezeAt
import org.tasks.SuspendFreeze
import org.tasks.SuspendFreeze.Companion.freezeAt
import org.tasks.TestUtilities.withLocale
import org.tasks.date.DateTimeUtils
import org.tasks.extensions.Context.is24HourFormat
import org.tasks.extensions.Context.is24HourOverride
import org.tasks.kmp.formatDayOfWeek
import org.tasks.kmp.org.tasks.time.DateStyle
import org.tasks.kmp.org.tasks.time.TextStyle
import org.tasks.kmp.org.tasks.time.getRelativeDateTime
import org.tasks.kmp.org.tasks.time.getRelativeDay
import org.tasks.kmp.org.tasks.time.getTimeString
import org.tasks.time.DateTime
import java.time.format.FormatStyle
import java.util.Locale
@RunWith(AndroidJUnit4::class)
class DateUtilitiesTest {
@After
fun after() {
DateUtilities.is24HourOverride = null
is24HourOverride = null
}
@Test
fun testGet24HourTime() {
DateUtilities.is24HourOverride = true
assertEquals("09:05", DateUtilities.getTimeString(ApplicationProvider.getApplicationContext(), DateTime(2014, 1, 4, 9, 5, 36)))
assertEquals("13:00", DateUtilities.getTimeString(ApplicationProvider.getApplicationContext(), DateTime(2014, 1, 4, 13, 0, 1)))
is24HourOverride = true
assertEquals("09:05", getTimeString(DateTime(2014, 1, 4, 9, 5, 36).millis, is24HourFormat))
assertEquals("13:00", getTimeString(DateTime(2014, 1, 4, 13, 0, 1).millis, is24HourFormat))
}
@Test
fun testGetTime() {
DateUtilities.is24HourOverride = false
assertEquals("9:05 AM", DateUtilities.getTimeString(ApplicationProvider.getApplicationContext(), DateTime(2014, 1, 4, 9, 5, 36)))
assertEquals("1:05 PM", DateUtilities.getTimeString(ApplicationProvider.getApplicationContext(), DateTime(2014, 1, 4, 13, 5, 36)))
is24HourOverride = false
assertEquals("9:05 AM", getTimeString(DateTime(2014, 1, 4, 9, 5, 36).millis, is24HourFormat))
assertEquals("1:05 PM", getTimeString(DateTime(2014, 1, 4, 13, 5, 36).millis, is24HourFormat))
}
@Test
fun testGetTimeWithNoMinutes() {
DateUtilities.is24HourOverride = false
assertEquals("1 PM", DateUtilities.getTimeString(ApplicationProvider.getApplicationContext(), DateTime(2014, 1, 4, 13, 0, 59))) // derp?
is24HourOverride = false
assertEquals("1 PM", getTimeString(DateTime(2014, 1, 4, 13, 0, 59).millis, is24HourFormat)) // derp?
}
@Test
fun testGetDateStringWithYear() {
assertEquals("Jan 4, 2014", DateUtilities.getDateString(ApplicationProvider.getApplicationContext(), DateTime(2014, 1, 4, 0, 0, 0)))
fun testGetDateStringWithYear() = runBlocking {
assertEquals("Jan 4, 2014", getRelativeDay(DateTime(2014, 1, 4, 0, 0, 0).millis))
}
@Test
fun testGetDateStringHidingYear() {
fun testGetDateStringHidingYear() = runBlocking {
freezeAt(DateTimeUtils.newDate(2014, 2, 1)) {
assertEquals("Jan 1", DateUtilities.getDateString(ApplicationProvider.getApplicationContext(), DateTime(2014, 1, 1)))
assertEquals("Jan 1", getRelativeDay(DateTime(2014, 1, 1).millis))
}
}
@Test
fun testGetDateStringWithDifferentYear() {
fun testGetDateStringWithDifferentYear() = runBlocking {
freezeAt(DateTimeUtils.newDate(2013, 12, 1)) {
assertEquals("Jan 1, 2014", DateUtilities.getDateString(ApplicationProvider.getApplicationContext(), DateTime(2014, 1, 1, 0, 0, 0)))
assertEquals("Jan 1, 2014", getRelativeDay(DateTime(2014, 1, 1, 0, 0, 0).millis))
}
}
@Test
fun testGetWeekdayLongString() {
assertEquals("Sunday", DateUtilities.getWeekday(DateTimeUtils.newDate(2013, 12, 29), Locale.US))
assertEquals("Monday", DateUtilities.getWeekday(DateTimeUtils.newDate(2013, 12, 30), Locale.US))
assertEquals("Tuesday", DateUtilities.getWeekday(DateTimeUtils.newDate(2013, 12, 31), Locale.US))
assertEquals("Wednesday", DateUtilities.getWeekday(DateTimeUtils.newDate(2014, 1, 1), Locale.US))
assertEquals("Thursday", DateUtilities.getWeekday(DateTimeUtils.newDate(2014, 1, 2), Locale.US))
assertEquals("Friday", DateUtilities.getWeekday(DateTimeUtils.newDate(2014, 1, 3), Locale.US))
assertEquals("Saturday", DateUtilities.getWeekday(DateTimeUtils.newDate(2014, 1, 4), Locale.US))
fun testGetWeekdayLongString() = withLocale(Locale.US) {
assertEquals("Sunday", formatDayOfWeek(DateTimeUtils.newDate(2013, 12, 29).millis, TextStyle.FULL))
assertEquals("Monday", formatDayOfWeek(DateTimeUtils.newDate(2013, 12, 30).millis, TextStyle.FULL))
assertEquals("Tuesday", formatDayOfWeek(DateTimeUtils.newDate(2013, 12, 31).millis, TextStyle.FULL))
assertEquals("Wednesday", formatDayOfWeek(DateTimeUtils.newDate(2014, 1, 1).millis, TextStyle.FULL))
assertEquals("Thursday", formatDayOfWeek(DateTimeUtils.newDate(2014, 1, 2).millis, TextStyle.FULL))
assertEquals("Friday", formatDayOfWeek(DateTimeUtils.newDate(2014, 1, 3).millis, TextStyle.FULL))
assertEquals("Saturday", formatDayOfWeek(DateTimeUtils.newDate(2014, 1, 4).millis, TextStyle.FULL))
}
@Test
fun testGetWeekdayShortString() {
assertEquals("Sun", DateUtilities.getWeekdayShort(DateTimeUtils.newDate(2013, 12, 29), Locale.US))
assertEquals("Mon", DateUtilities.getWeekdayShort(DateTimeUtils.newDate(2013, 12, 30), Locale.US))
assertEquals("Tue", DateUtilities.getWeekdayShort(DateTimeUtils.newDate(2013, 12, 31), Locale.US))
assertEquals("Wed", DateUtilities.getWeekdayShort(DateTimeUtils.newDate(2014, 1, 1), Locale.US))
assertEquals("Thu", DateUtilities.getWeekdayShort(DateTimeUtils.newDate(2014, 1, 2), Locale.US))
assertEquals("Fri", DateUtilities.getWeekdayShort(DateTimeUtils.newDate(2014, 1, 3), Locale.US))
assertEquals("Sat", DateUtilities.getWeekdayShort(DateTimeUtils.newDate(2014, 1, 4), Locale.US))
fun testGetWeekdayShortString() = withLocale(Locale.US) {
assertEquals("Sun", formatDayOfWeek(DateTimeUtils.newDate(2013, 12, 29).millis, TextStyle.SHORT))
assertEquals("Mon", formatDayOfWeek(DateTimeUtils.newDate(2013, 12, 30).millis, TextStyle.SHORT))
assertEquals("Tue", formatDayOfWeek(DateTimeUtils.newDate(2013, 12, 31).millis, TextStyle.SHORT))
assertEquals("Wed", formatDayOfWeek(DateTimeUtils.newDate(2014, 1, 1).millis, TextStyle.SHORT))
assertEquals("Thu", formatDayOfWeek(DateTimeUtils.newDate(2014, 1, 2).millis, TextStyle.SHORT))
assertEquals("Fri", formatDayOfWeek(DateTimeUtils.newDate(2014, 1, 3).millis, TextStyle.SHORT))
assertEquals("Sat", formatDayOfWeek(DateTimeUtils.newDate(2014, 1, 4).millis, TextStyle.SHORT))
}
@Test
fun getRelativeFullDate() {
fun getRelativeFullDate() = withLocale(Locale.US) {
freezeAt(DateTime(2018, 1, 1)) {
assertEquals(
"Sunday, January 14",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14).millis,
Locale.US,
FormatStyle.FULL))
"Sunday, January 14",
getRelativeDateTime(DateTime(2018, 1, 14).millis, is24HourFormat, DateStyle.FULL)
)
}
}
@Test
fun getRelativeFullDateWithYear() {
fun getRelativeFullDateWithYear() = withLocale(Locale.US) {
freezeAt(DateTime(2017, 12, 12)) {
assertEquals(
"Sunday, January 14, 2018",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14).millis,
Locale.US,
FormatStyle.FULL))
"Sunday, January 14, 2018",
getRelativeDateTime(DateTime(2018, 1, 14).millis, is24HourFormat, DateStyle.FULL)
)
}
}
@Test
fun getRelativeFullDateTime() {
fun getRelativeFullDateTime() = withLocale(Locale.US) {
freezeAt(DateTime(2018, 1, 1)) {
assertMatches(
"Sunday, January 14( at)? 1:43 PM",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14, 13, 43, 1).millis,
Locale.US,
FormatStyle.FULL))
"Sunday, January 14( at)? 1:43 PM",
getRelativeDateTime(DateTime(2018, 1, 14, 13, 43, 1).millis, is24HourFormat, DateStyle.FULL)
)
}
}
@Test
@Ignore("Fails on CI - need to investigate")
fun getRelativeDateTimeWithAlwaysDisplayFullDateOption() {
fun getRelativeDateTimeWithAlwaysDisplayFullDateOption() = withLocale(Locale.US) {
freezeAt(DateTime(2020, 1, 1)) {
assertMatches(
"Thursday, January 2 at 11:50 AM",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2020, 1, 2, 11, 50, 1).millis,
Locale.US,
FormatStyle.FULL,
true,
false
))
"Thursday, January 2 at 11:50 AM",
getRelativeDateTime(DateTime(2020, 1, 2, 11, 50, 1).millis, is24HourFormat, DateStyle.FULL, true, false)
)
}
}
@Test
fun getRelativeFullDateTimeWithYear() {
fun getRelativeFullDateTimeWithYear() = withLocale(Locale.US) {
freezeAt(DateTime(2017, 12, 12)) {
assertMatches(
"Sunday, January 14, 2018( at)? 11:50 AM",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14, 11, 50, 1).millis,
Locale.US,
FormatStyle.FULL))
"Sunday, January 14, 2018( at)? 11:50 AM",
getRelativeDateTime(DateTime(2018, 1, 14, 11, 50, 1).millis, is24HourFormat, DateStyle.FULL)
)
}
}
@Test
fun getRelativeDayWithAlwaysDisplayFullDateOption() {
fun getRelativeDayWithAlwaysDisplayFullDateOption() = withLocale(Locale.US) {
freezeAt(DateTime(2020, 1, 1)) {
assertEquals(
"Thursday, January 2",
DateUtilities.getRelativeDay(
ApplicationProvider.getApplicationContext(),
DateTime(2020, 1, 2, 11, 50, 1).millis,
Locale.US,
FormatStyle.FULL,
true,
true
)
getRelativeDay(DateTime(2020, 1, 2, 11, 50, 1).millis, DateStyle.FULL, alwaysDisplayFullDate = true, lowercase = true)
)
}
}
@Test
fun getRelativeDayWithoutAlwaysDisplayFullDateOption() {
fun getRelativeDayWithoutAlwaysDisplayFullDateOption() = withLocale(Locale.US) {
freezeAt(DateTime(2020, 1, 1)) {
assertEquals(
"tomorrow",
DateUtilities.getRelativeDay(
ApplicationProvider.getApplicationContext(),
DateTime(2020, 1, 2, 11, 50, 1).millis,
Locale.US,
FormatStyle.FULL,
false,
true
)
getRelativeDay(DateTime(2020, 1, 2, 11, 50, 1).millis, DateStyle.FULL, lowercase = true)
)
}
}
@Test
fun germanDateNoYear() {
fun germanDateNoYear() = withLocale(Locale.GERMAN) {
freezeAt(DateTime(2018, 1, 1)) {
assertEquals(
"Sonntag, 14. Januar",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14).millis,
Locale.GERMAN,
FormatStyle.FULL))
"Sonntag, 14. Januar",
getRelativeDateTime(DateTime(2018, 1, 14).millis, is24HourFormat, DateStyle.FULL)
)
}
}
@Test
fun germanDateWithYear() {
fun germanDateWithYear() = withLocale(Locale.GERMAN) {
freezeAt(DateTime(2017, 12, 12)) {
assertEquals(
"Sonntag, 14. Januar 2018",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14).millis,
Locale.GERMAN,
FormatStyle.FULL))
"Sonntag, 14. Januar 2018",
getRelativeDateTime(DateTime(2018, 1, 14).millis, is24HourFormat, DateStyle.FULL)
)
}
}
@Test
fun koreanDateNoYear() {
fun koreanDateNoYear() = withLocale(Locale.KOREAN) {
freezeAt(DateTime(2018, 1, 1)) {
assertEquals(
"1월 14일 일요일",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14).millis,
Locale.KOREAN,
FormatStyle.FULL))
"1월 14일 일요일",
getRelativeDateTime(DateTime(2018, 1, 14).millis, is24HourFormat, DateStyle.FULL)
)
}
}
@Test
fun koreanDateWithYear() {
fun koreanDateWithYear() = withLocale(Locale.KOREAN) {
freezeAt(DateTime(2017, 12, 12)) {
assertEquals(
"2018년 1월 14일 일요일",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14).millis,
Locale.KOREAN,
FormatStyle.FULL))
"2018년 1월 14일 일요일",
getRelativeDateTime(DateTime(2018, 1, 14).millis, is24HourFormat, DateStyle.FULL)
)
}
}
@Test
fun japaneseDateNoYear() {
fun japaneseDateNoYear() = withLocale(Locale.JAPANESE) {
freezeAt(DateTime(2018, 1, 1)) {
assertEquals(
"1月14日日曜日",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14).millis,
Locale.JAPANESE,
FormatStyle.FULL))
"1月14日日曜日",
getRelativeDateTime(DateTime(2018, 1, 14).millis, is24HourFormat, DateStyle.FULL)
)
}
}
@Test
fun japaneseDateWithYear() {
fun japaneseDateWithYear() = withLocale(Locale.JAPANESE) {
freezeAt(DateTime(2017, 12, 12)) {
assertEquals(
"2018年1月14日日曜日",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14).millis,
Locale.JAPANESE,
FormatStyle.FULL))
"2018年1月14日日曜日",
getRelativeDateTime(DateTime(2018, 1, 14).millis, is24HourFormat, DateStyle.FULL)
)
}
}
@Test
fun chineseDateNoYear() {
fun chineseDateNoYear() = withLocale(Locale.CHINESE) {
freezeAt(DateTime(2018, 1, 1)) {
assertEquals(
"1月14日星期日",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14).millis,
Locale.CHINESE,
FormatStyle.FULL))
"1月14日星期日",
getRelativeDateTime(DateTime(2018, 1, 14).millis, is24HourFormat, DateStyle.FULL)
)
}
}
@Test
fun chineseDateWithYear() {
freezeAt(DateTime(2017, 12, 12)) {
fun chineseDateWithYear() = withLocale(Locale.CHINESE) {
SuspendFreeze.freezeAt(DateTime(2017, 12, 12)) {
assertEquals(
"2018年1月14日星期日",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14).millis,
Locale.CHINESE,
FormatStyle.FULL))
"2018年1月14日星期日",
getRelativeDateTime(DateTime(2018, 1, 14).millis, is24HourFormat, DateStyle.FULL)
)
}
}
@Test
fun chineseDateTimeNoYear() {
fun chineseDateTimeNoYear() = withLocale(Locale.CHINESE) {
freezeAt(DateTime(2018, 1, 1)) {
assertEquals(
"1月14日星期日 上午11:53",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14, 11, 53, 1).millis,
Locale.CHINESE,
FormatStyle.FULL))
"1月14日星期日 上午11:53",
getRelativeDateTime(DateTime(2018, 1, 14, 11, 53, 1).millis, is24HourFormat, DateStyle.FULL)
)
}
}
@Test
fun chineseDateTimeWithYear() {
fun chineseDateTimeWithYear() = withLocale(Locale.CHINESE) {
freezeAt(DateTime(2017, 12, 12)) {
assertEquals(
"2018年1月14日星期日 下午1:45",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14, 13, 45, 1).millis,
Locale.CHINESE,
FormatStyle.FULL))
"2018年1月14日星期日 下午1:45",
getRelativeDateTime(DateTime(2018, 1, 14, 13, 45, 1).millis, is24HourFormat, DateStyle.FULL)
)
}
}
@Test
fun frenchDateTimeWithYear() {
fun frenchDateTimeWithYear() = withLocale(Locale.FRENCH) {
freezeAt(DateTime(2017, 12, 12)) {
assertMatches(
"dimanche 14 janvier 2018( à)? 13:45",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14, 13, 45, 1).millis,
Locale.FRENCH,
FormatStyle.FULL))
"dimanche 14 janvier 2018( à)? 13:45",
getRelativeDateTime(DateTime(2018, 1, 14, 13, 45, 1).millis, is24HourFormat, DateStyle.FULL)
)
}
}
@Test
fun indiaDateTimeWithYear() {
fun indiaDateTimeWithYear() = withLocale(Locale.forLanguageTag("hi-IN")) {
freezeAt(DateTime(2017, 12, 12)) {
assertMatches(
"रविवार, 14 जनवरी 2018( को)? 1:45 pm",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14, 13, 45, 1).millis,
Locale.forLanguageTag("hi-IN"),
FormatStyle.FULL))
"रविवार, 14 जनवरी 2018( को)? 1:45 pm",
getRelativeDateTime(DateTime(2018, 1, 14, 13, 45, 1).millis, is24HourFormat, DateStyle.FULL)
)
}
}
@Test
fun russiaDateTimeNoYear() {
fun russiaDateTimeNoYear() = withLocale(Locale.forLanguageTag("ru")) {
freezeAt(DateTime(2018, 12, 12)) {
assertMatches(
"воскресенье, 14 января,? 13:45",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14, 13, 45, 1).millis,
Locale.forLanguageTag("ru"),
FormatStyle.FULL))
"воскресенье, 14 января,? 13:45",
getRelativeDateTime(DateTime(2018, 1, 14, 13, 45, 1).millis, is24HourFormat, DateStyle.FULL)
)
}
}
@Test
fun russiaDateTimeWithYear() {
fun russiaDateTimeWithYear() = withLocale(Locale.forLanguageTag("ru")) {
freezeAt(DateTime(2017, 12, 12)) {
assertMatches(
"воскресенье, 14 января 2018 г.,? 13:45",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14, 13, 45, 1).millis,
Locale.forLanguageTag("ru"),
FormatStyle.FULL))
"воскресенье, 14 января 2018 г.,? 13:45",
getRelativeDateTime(DateTime(2018, 1, 14, 13, 45, 1).millis, is24HourFormat, DateStyle.FULL)
)
}
}
@Test
fun brazilDateTimeNoYear() {
fun brazilDateTimeNoYear() = withLocale(Locale.forLanguageTag("pt-br")) {
freezeAt(DateTime(2018, 12, 12)) {
assertEquals(
"domingo, 14 de janeiro 13:45",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14, 13, 45, 1).millis,
Locale.forLanguageTag("pt-br"),
FormatStyle.FULL))
"domingo, 14 de janeiro 13:45",
getRelativeDateTime(DateTime(2018, 1, 14, 13, 45, 1).millis, is24HourFormat, DateStyle.FULL)
)
}
}
@Test
fun brazilDateTimeWithYear() {
fun brazilDateTimeWithYear() = withLocale(Locale.forLanguageTag("pt-br")) {
freezeAt(DateTime(2017, 12, 12)) {
assertEquals(
"domingo, 14 de janeiro de 2018 13:45",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14, 13, 45, 1).millis,
Locale.forLanguageTag("pt-br"),
FormatStyle.FULL))
"domingo, 14 de janeiro de 2018 13:45",
getRelativeDateTime(DateTime(2018, 1, 14, 13, 45, 1).millis, is24HourFormat, DateStyle.FULL)
)
}
}
@Test
fun spainDateTimeNoYear() {
fun spainDateTimeNoYear() = withLocale(Locale.forLanguageTag("es")) {
freezeAt(DateTime(2018, 12, 12)) {
assertMatches(
"domingo, 14 de enero,? 13:45",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14, 13, 45, 1).millis,
Locale.forLanguageTag("es"),
FormatStyle.FULL))
"domingo, 14 de enero,? 13:45",
getRelativeDateTime(DateTime(2018, 1, 14, 13, 45, 1).millis, is24HourFormat, DateStyle.FULL)
)
}
}
@Test
fun spainDateTimeWithYear() {
fun spainDateTimeWithYear() = withLocale(Locale.forLanguageTag("es")) {
freezeAt(DateTime(2017, 12, 12)) {
assertMatches(
"domingo, 14 de enero de 2018,? 13:45",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14, 13, 45, 1).millis,
Locale.forLanguageTag("es"),
FormatStyle.FULL))
"domingo, 14 de enero de 2018,? 13:45",
getRelativeDateTime(DateTime(2018, 1, 14, 13, 45, 1).millis, is24HourFormat, DateStyle.FULL)
)
}
}
@Test
fun hebrewDateTimeNoYear() {
fun hebrewDateTimeNoYear() = withLocale(Locale.forLanguageTag("iw")) {
freezeAt(DateTime(2018, 12, 12)) {
assertMatches(
"יום ראשון, 14 בינואר( בשעה)? 13:45",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14, 13, 45, 1).millis,
Locale.forLanguageTag("iw"),
FormatStyle.FULL))
"יום ראשון, 14 בינואר( בשעה)? 13:45",
getRelativeDateTime(DateTime(2018, 1, 14, 13, 45, 1).millis, is24HourFormat, DateStyle.FULL)
)
}
}
@Test
fun hebrewDateTimeWithYear() {
fun hebrewDateTimeWithYear() = withLocale(Locale.forLanguageTag("iw")) {
freezeAt(DateTime(2017, 12, 12)) {
assertMatches(
"יום ראשון, 14 בינואר 2018( בשעה)? 13:45",
DateUtilities.getRelativeDateTime(
ApplicationProvider.getApplicationContext(),
DateTime(2018, 1, 14, 13, 45, 1).millis,
Locale.forLanguageTag("iw"),
FormatStyle.FULL))
"יום ראשון, 14 בינואר 2018( בשעה)? 13:45",
getRelativeDateTime(DateTime(2018, 1, 14, 13, 45, 1).millis, is24HourFormat, DateStyle.FULL)
)
}
}
private fun assertMatches(regex: String, actual: String) =
assertTrue("expected=$regex\nactual=$actual", actual.matches(Regex(regex)))
private val is24HourFormat: Boolean
get() = InstrumentationRegistry.getInstrumentation().targetContext.is24HourFormat
}

@ -1,16 +1,17 @@
package com.todoroo.andlib.utility
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.tasks.Freeze
import org.tasks.kmp.org.tasks.time.DateStyle
import org.tasks.kmp.org.tasks.time.getRelativeDay
import org.tasks.time.DateTime
import java.time.format.FormatStyle
import java.util.*
import java.util.Locale
@RunWith(AndroidJUnit4::class)
class RelativeDayTest {
@ -67,12 +68,12 @@ class RelativeDayTest {
checkRelativeDay(DateTime().plusDays(7), "January 7, 2014", "Jan 7, 2014")
}
private fun checkRelativeDay(now: DateTime, full: String, abbreviated: String) {
private fun checkRelativeDay(now: DateTime, full: String, abbreviated: String) = runBlocking {
assertEquals(
full,
DateUtilities.getRelativeDay(ApplicationProvider.getApplicationContext(), now.millis, Locale.US, FormatStyle.LONG))
getRelativeDay(now.millis, DateStyle.LONG))
assertEquals(
abbreviated,
DateUtilities.getRelativeDay(ApplicationProvider.getApplicationContext(), now.millis, Locale.US, FormatStyle.MEDIUM))
getRelativeDay(now.millis))
}
}

@ -1,205 +0,0 @@
/*
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.andlib.utility
import android.content.Context
import android.text.format.DateFormat
import org.tasks.BuildConfig
import org.tasks.R
import org.tasks.data.entity.Task.Companion.hasDueTime
import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.time.DateTime
import org.tasks.time.ONE_DAY
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.time.format.TextStyle
import java.util.Locale
import kotlin.math.abs
object DateUtilities {
var is24HourOverride: Boolean? = null
/* ======================================================================
* =========================================================== formatters
* ====================================================================== */
private fun is24HourFormat(context: Context): Boolean {
return if (BuildConfig.DEBUG && is24HourOverride != null
) is24HourOverride!!
else DateFormat.is24HourFormat(context)
}
@JvmStatic
fun getTimeString(context: Context, date: DateTime): String {
val value = if (is24HourFormat(context)) {
"HH:mm"
} else if (date.minuteOfHour == 0) {
"h a"
} else {
"h:mm a"
}
return date.toString(value)
}
fun getLongDateString(date: DateTime, locale: Locale): String {
return getFullDate(date, locale, FormatStyle.LONG)
}
/**
* @param date date to format
* @return date, with month, day, and year
*/
fun getDateString(context: Context, date: DateTime): String {
return getRelativeDay(
context, date.millis, Locale.getDefault(), FormatStyle.MEDIUM
)
}
fun getWeekday(date: DateTime, locale: Locale?): String {
return date.toLocalDate()!!
.dayOfWeek.getDisplayName(TextStyle.FULL, locale)
}
/** @return weekday
*/
fun getWeekdayShort(date: DateTime, locale: Locale?): String {
return date.toLocalDate()!!
.dayOfWeek.getDisplayName(TextStyle.SHORT, locale)
}
fun getLongDateStringWithTime(timestamp: Long, locale: Locale): String {
return getFullDateTime(newDateTime(timestamp), locale, FormatStyle.LONG)
}
fun getRelativeDateTime(
context: Context, date: Long, locale: Locale, style: FormatStyle
): String {
return getRelativeDateTime(context, date, locale, style, false, false)
}
fun getRelativeDateTime(
context: Context, date: Long, locale: Locale, style: FormatStyle, lowercase: Boolean
): String {
return getRelativeDateTime(context, date, locale, style, false, lowercase)
}
fun getRelativeDateTime(
context: Context,
date: Long,
locale: Locale,
style: FormatStyle,
alwaysDisplayFullDate: Boolean,
lowercase: Boolean
): String {
if (alwaysDisplayFullDate || !isWithinSixDays(date)) {
return if (hasDueTime(date)
) getFullDateTime(newDateTime(date), locale, style)
else getFullDate(newDateTime(date), locale, style)
}
val day = getRelativeDay(context, date, locale, isAbbreviated(style), lowercase)
if (hasDueTime(date)) {
val time = getTimeString(context, newDateTime(date))
return if (newDateTime().startOfDay()
.equals(newDateTime(date).startOfDay())
) time else String.format("%s %s", day, time)
} else {
return day
}
}
private fun isAbbreviated(style: FormatStyle): Boolean {
return style == FormatStyle.SHORT || style == FormatStyle.MEDIUM
}
fun getRelativeDay(
context: Context,
date: Long,
locale: Locale,
style: FormatStyle
): String {
return getRelativeDay(context, date, locale, style, false, false)
}
fun getRelativeDay(
context: Context,
date: Long,
locale: Locale,
style: FormatStyle,
alwaysDisplayFullDate: Boolean,
lowercase: Boolean
): String {
if (alwaysDisplayFullDate) {
return getFullDate(newDateTime(date), locale, style)
}
return if (isWithinSixDays(date)
) getRelativeDay(context, date, locale, isAbbreviated(style), lowercase)
else getFullDate(newDateTime(date), locale, style)
}
private fun getFullDate(date: DateTime, locale: Locale, style: FormatStyle): String {
return stripYear(
DateTimeFormatter.ofLocalizedDate(style)
.withLocale(locale)
.format(date.toLocalDate()),
newDateTime().year
)
}
private fun getFullDateTime(date: DateTime, locale: Locale, style: FormatStyle): String {
return stripYear(
DateTimeFormatter.ofLocalizedDateTime(style, FormatStyle.SHORT)
.withLocale(locale)
.format(date.toLocalDateTime()),
newDateTime().year
)
}
private fun stripYear(date: String, year: Int): String {
return date.replace("(?: de |, |/| )?$year(?:年|년 | г\\.)?".toRegex(), "")
}
private fun getRelativeDay(
context: Context,
date: Long,
locale: Locale,
abbreviated: Boolean,
lowercase: Boolean
): String {
val startOfToday = newDateTime().startOfDay()
val startOfDate = newDateTime(date).startOfDay()
if (startOfToday.equals(startOfDate)) {
return context.getString(if (lowercase) R.string.today_lowercase else R.string.today)
}
if (startOfToday.plusDays(1).equals(startOfDate)) {
return context.getString(
if (abbreviated
) if (lowercase) R.string.tomorrow_abbrev_lowercase else R.string.tmrw
else if (lowercase) R.string.tomorrow_lowercase else R.string.tomorrow
)
}
if (startOfDate.plusDays(1).equals(startOfToday)) {
return context.getString(
if (abbreviated
) if (lowercase) R.string.yesterday_abbrev_lowercase else R.string.yest
else if (lowercase) R.string.yesterday_lowercase else R.string.yesterday
)
}
val dateTime = newDateTime(date)
return if (abbreviated
) getWeekdayShort(dateTime, locale)
else getWeekday(dateTime, locale)
}
private fun isWithinSixDays(date: Long): Boolean {
val startOfToday = newDateTime().startOfDay()
val startOfDate = newDateTime(date).startOfDay()
return abs((startOfToday.millis - startOfDate.millis).toDouble()) <= ONE_DAY * 6
}
}

@ -44,7 +44,6 @@ import androidx.lifecycle.lifecycleScope
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.AppBarLayout.Behavior.DragCallback
import com.todoroo.andlib.utility.AndroidUtilities.atLeastOreoMR1
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.files.FilesControlSet
import com.todoroo.astrid.repeats.RepeatControlSet
@ -57,6 +56,7 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.tasks.R
import org.tasks.Strings.isNullOrEmpty
import org.tasks.analytics.Firebase
@ -89,6 +89,7 @@ import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.dialogs.DateTimePicker
import org.tasks.dialogs.DialogBuilder
import org.tasks.dialogs.Linkify
import org.tasks.extensions.Context.is24HourFormat
import org.tasks.extensions.hideKeyboard
import org.tasks.files.FileHelper
import org.tasks.filters.Filter
@ -98,6 +99,8 @@ import org.tasks.fragments.TaskEditControlSetFragmentManager.Companion.TAG_DESCR
import org.tasks.fragments.TaskEditControlSetFragmentManager.Companion.TAG_DUE_DATE
import org.tasks.fragments.TaskEditControlSetFragmentManager.Companion.TAG_LIST
import org.tasks.fragments.TaskEditControlSetFragmentManager.Companion.TAG_PRIORITY
import org.tasks.kmp.org.tasks.time.DateStyle
import org.tasks.kmp.org.tasks.time.getRelativeDateTime
import org.tasks.markdown.MarkdownProvider
import org.tasks.notifications.NotificationManager
import org.tasks.play.PlayServices
@ -111,7 +114,6 @@ import org.tasks.ui.TaskEditEvent
import org.tasks.ui.TaskEditEventBus
import org.tasks.ui.TaskEditViewModel
import org.tasks.ui.TaskEditViewModel.Companion.stripCarriageReturns
import java.time.format.FormatStyle
import java.util.Locale
import javax.inject.Inject
import kotlin.math.abs
@ -436,18 +438,19 @@ class TaskEditFragment : Fragment(), Toolbar.OnMenuItemClickListener {
@Composable
private fun DueDateRow() {
val dueDate = editViewModel.dueDate.collectAsStateWithLifecycle().value
val context = LocalContext.current
DueDateRow(
dueDate = if (dueDate == 0L) {
null
} else {
DateUtilities.getRelativeDateTime(
LocalContext.current,
dueDate,
locale,
FormatStyle.FULL,
preferences.alwaysDisplayFullDate,
false
)
runBlocking {
getRelativeDateTime(
dueDate,
context.is24HourFormat,
DateStyle.FULL,
alwaysDisplayFullDate = preferences.alwaysDisplayFullDate
)
}
},
overdue = dueDate.isOverdue,
onClick = {

@ -56,12 +56,10 @@ import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.bottomappbar.BottomAppBar
import com.google.android.material.snackbar.Snackbar
import com.todoroo.andlib.utility.AndroidUtilities
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.adapter.TaskAdapter
import com.todoroo.astrid.adapter.TaskAdapterProvider
import com.todoroo.astrid.api.AstridApiConstants.EXTRAS_OLD_DUE_DATE
import com.todoroo.astrid.api.AstridApiConstants.EXTRAS_TASK_ID
import org.tasks.filters.CustomFilter
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.repeats.RepeatTaskHelper
import com.todoroo.astrid.service.TaskCompleter
@ -111,6 +109,7 @@ import org.tasks.dialogs.DialogBuilder
import org.tasks.dialogs.PriorityPicker.Companion.newPriorityPicker
import org.tasks.dialogs.SortSettingsActivity
import org.tasks.extensions.Context.canScheduleExactAlarms
import org.tasks.extensions.Context.is24HourFormat
import org.tasks.extensions.Context.openAppNotificationSettings
import org.tasks.extensions.Context.openReminderSettings
import org.tasks.extensions.Context.openUri
@ -120,12 +119,15 @@ import org.tasks.extensions.hideKeyboard
import org.tasks.extensions.setOnQueryTextListener
import org.tasks.filters.AstridOrderingFilter
import org.tasks.filters.CaldavFilter
import org.tasks.filters.CustomFilter
import org.tasks.filters.Filter
import org.tasks.filters.FilterImpl
import org.tasks.filters.GtasksFilter
import org.tasks.filters.MyTasksFilter
import org.tasks.filters.PlaceFilter
import org.tasks.filters.TagFilter
import org.tasks.kmp.org.tasks.time.DateStyle
import org.tasks.kmp.org.tasks.time.getRelativeDateTime
import org.tasks.markdown.MarkdownProvider
import org.tasks.preferences.Device
import org.tasks.preferences.Preferences
@ -147,7 +149,6 @@ import org.tasks.ui.TaskListEvent
import org.tasks.ui.TaskListEventBus
import org.tasks.ui.TaskListViewModel
import org.tasks.ui.TaskListViewModel.Companion.createSearchQuery
import java.time.format.FormatStyle
import java.util.Locale
import javax.inject.Inject
import kotlin.math.max
@ -176,7 +177,6 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
@Inject lateinit var colorProvider: ColorProvider
@Inject lateinit var shortcutManager: ShortcutManager
@Inject lateinit var taskCompleter: TaskCompleter
@Inject lateinit var locale: Locale
@Inject lateinit var firebase: Firebase
@Inject lateinit var repeatTaskHelper: RepeatTaskHelper
@Inject lateinit var taskListEventBus: TaskListEventBus
@ -1045,8 +1045,11 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
val text = getString(
R.string.repeat_snackbar,
title,
DateUtilities.getRelativeDateTime(
context, task.dueDate, locale, FormatStyle.LONG, true
getRelativeDateTime(
task.dueDate,
context.is24HourFormat,
DateStyle.LONG,
lowercase = true
)
)
makeSnackbar(text)?.setAction(R.string.DLG_undo, undoCompletion)?.show()

@ -14,17 +14,18 @@ import androidx.appcompat.app.AlertDialog
import androidx.compose.ui.platform.ComposeView
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.ui.TimeDurationControlSet
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import org.tasks.R
import org.tasks.compose.edit.TimerRow
import org.tasks.data.entity.Task
import org.tasks.date.DateTimeUtils
import org.tasks.dialogs.DialogBuilder
import org.tasks.extensions.Context.is24HourFormat
import org.tasks.kmp.org.tasks.time.getTimeString
import org.tasks.themes.TasksTheme
import org.tasks.themes.Theme
import org.tasks.time.DateTimeUtils2.currentTimeMillis
import org.tasks.ui.TaskEditControlFragment
import javax.inject.Inject
@ -112,9 +113,10 @@ class TimerControlSet : TaskEditControlFragment() {
viewModel.addComment(String.format(
"%s %s\n%s %s", // $NON-NLS-1$
getString(R.string.TEA_timer_comment_stopped),
DateUtilities.getTimeString(requireContext(), DateTimeUtils.newDateTime()),
getTimeString(currentTimeMillis(), requireContext().is24HourFormat),
getString(R.string.TEA_timer_comment_spent),
elapsedTime),
elapsedTime
),
null)
return model
}
@ -125,7 +127,8 @@ class TimerControlSet : TaskEditControlFragment() {
viewModel.addComment(String.format(
"%s %s",
getString(R.string.TEA_timer_comment_started),
DateUtilities.getTimeString(requireContext(), DateTimeUtils.newDateTime())),
getTimeString(currentTimeMillis(), requireContext().is24HourFormat)
),
null)
return model
}

@ -33,14 +33,12 @@ import org.tasks.extensions.Context.openReminderSettings
import org.tasks.scheduling.NotificationSchedulerIntentService
import org.tasks.themes.TasksTheme
import org.tasks.ui.TaskEditControlFragment
import java.util.Locale
import javax.inject.Inject
@AndroidEntryPoint
class ReminderControlSet : TaskEditControlFragment() {
@Inject lateinit var activity: Activity
@Inject lateinit var dialogBuilder: DialogBuilder
@Inject lateinit var locale: Locale
private val ringMode = mutableStateOf(0)
@ -104,7 +102,6 @@ class ReminderControlSet : TaskEditControlFragment() {
viewModel.addAlarm(Alarm(time = timestamp, type = TYPE_DATE_TIME))
}
AlarmRow(
locale = locale,
alarms = viewModel.selectedAlarms.collectAsStateWithLifecycle().value,
hasNotificationPermissions = hasReminderPermissions &&
(notificationPermissions == null || notificationPermissions.status == PermissionStatus.Granted),

@ -9,28 +9,29 @@ import androidx.compose.ui.platform.ComposeView
import androidx.fragment.app.viewModels
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.andlib.utility.DateUtilities.getTimeString
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.runBlocking
import org.tasks.R
import org.tasks.compose.edit.StartDateRow
import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.dialogs.StartDatePicker
import org.tasks.dialogs.StartDatePicker.Companion.EXTRA_DAY
import org.tasks.dialogs.StartDatePicker.Companion.EXTRA_TIME
import org.tasks.dialogs.StartDatePicker.Companion.NO_DAY
import org.tasks.dialogs.StartDatePicker.Companion.NO_TIME
import org.tasks.extensions.Context.is24HourFormat
import org.tasks.kmp.org.tasks.time.DateStyle
import org.tasks.kmp.org.tasks.time.getRelativeDateTime
import org.tasks.kmp.org.tasks.time.getTimeString
import org.tasks.preferences.Preferences
import org.tasks.themes.TasksTheme
import org.tasks.time.DateTimeUtils2.currentTimeMillis
import org.tasks.time.withMillisOfDay
import org.tasks.ui.TaskEditControlFragment
import java.time.format.FormatStyle
import java.util.Locale
import javax.inject.Inject
@AndroidEntryPoint
class StartDateControlSet : TaskEditControlFragment() {
@Inject lateinit var preferences: Preferences
@Inject lateinit var locale: Locale
private val vm: StartDateViewModel by viewModels()
@ -57,14 +58,14 @@ class StartDateControlSet : TaskEditControlFragment() {
selectedTime = selectedTime,
hasDueDate = viewModel.dueDate.collectAsStateWithLifecycle().value > 0,
printDate = {
DateUtilities.getRelativeDateTime(
requireContext(),
selectedDay + selectedTime,
locale,
FormatStyle.FULL,
preferences.alwaysDisplayFullDate,
false
)
runBlocking {
getRelativeDateTime(
selectedDay + selectedTime,
requireContext().is24HourFormat,
DateStyle.FULL,
alwaysDisplayFullDate = preferences.alwaysDisplayFullDate
)
}
},
onClick = {
val fragmentManager = parentFragmentManager
@ -117,7 +118,7 @@ class StartDateControlSet : TaskEditControlFragment() {
if (time == NO_TIME) {
getString(resId)
} else {
"${getString(resId)} ${getTimeString(this, newDateTime().withMillisOfDay(time))}"
"${getString(resId)} ${getTimeString(currentTimeMillis().withMillisOfDay(time), this.is24HourFormat)}"
}
}
}

@ -5,12 +5,15 @@ import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import com.todoroo.andlib.utility.DateUtilities
import kotlinx.coroutines.runBlocking
import org.tasks.data.entity.Task
import org.tasks.date.DateTimeUtils.toDateTime
import org.tasks.extensions.Context.is24HourFormat
import org.tasks.kmp.org.tasks.time.DateStyle
import org.tasks.kmp.org.tasks.time.getRelativeDateTime
import org.tasks.kmp.org.tasks.time.getTimeString
import org.tasks.themes.TasksIcons
import org.tasks.time.DateTimeUtils2.currentTimeMillis
import org.tasks.time.startOfDay
import java.time.format.FormatStyle
@Composable
fun StartDateChip(
@ -29,16 +32,15 @@ fun StartDateChip(
) {
startDate
.takeIf { Task.hasDueTime(it) }
?.let { DateUtilities.getTimeString(context, it.toDateTime()) }
?.let { getTimeString(currentTimeMillis(), context.is24HourFormat) }
} else {
DateUtilities.getRelativeDateTime(
context,
startDate,
context.resources.configuration.locales[0],
if (compact) FormatStyle.SHORT else FormatStyle.MEDIUM,
false,
false
)
runBlocking {
getRelativeDateTime(
startDate,
context.is24HourFormat,
if (compact) DateStyle.SHORT else DateStyle.MEDIUM
)
}
}
}
}

@ -21,7 +21,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.todoroo.astrid.ui.ReminderControlSetViewModel
import org.tasks.R
import org.tasks.compose.AddAlarmDialog
@ -32,7 +31,6 @@ import org.tasks.compose.TaskEditRow
import org.tasks.data.entity.Alarm
import org.tasks.reminders.AlarmToString
import org.tasks.themes.TasksTheme
import java.util.Locale
@OptIn(ExperimentalComposeUiApi::class)
@Composable
@ -42,7 +40,6 @@ fun AlarmRow(
fixNotificationPermissions: () -> Unit,
alarms: List<Alarm>,
ringMode: Int,
locale: Locale,
addAlarm: (Alarm) -> Unit,
deleteAlarm: (Alarm) -> Unit,
openRingType: () -> Unit,
@ -56,7 +53,6 @@ fun AlarmRow(
Alarms(
alarms = alarms,
ringMode = ringMode,
locale = locale,
replaceAlarm = {
vm.setReplace(it)
vm.showAddAlarm(visible = true)
@ -125,7 +121,6 @@ fun AlarmRow(
fun Alarms(
alarms: List<Alarm>,
ringMode: Int,
locale: Locale,
replaceAlarm: (Alarm) -> Unit,
addAlarm: () -> Unit,
deleteAlarm: (Alarm) -> Unit,
@ -135,7 +130,7 @@ fun Alarms(
Spacer(modifier = Modifier.height(8.dp))
alarms.forEach { alarm ->
AlarmRow(
text = AlarmToString(LocalContext.current, locale).toString(alarm),
text = AlarmToString(LocalContext.current).toString(alarm),
onClick = { replaceAlarm(alarm) },
remove = { deleteAlarm(alarm) }
)
@ -193,7 +188,6 @@ private fun AlarmRow(
}
}
@OptIn(ExperimentalPermissionsApi::class)
@Preview(showBackground = true, widthDp = 320)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320)
@Composable
@ -202,7 +196,6 @@ fun NoAlarms() {
AlarmRow(
alarms = emptyList(),
ringMode = 0,
locale = Locale.getDefault(),
addAlarm = {},
deleteAlarm = {},
openRingType = {},
@ -213,7 +206,6 @@ fun NoAlarms() {
}
}
@OptIn(ExperimentalPermissionsApi::class)
@Preview(showBackground = true, widthDp = 320)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320)
@Composable
@ -222,7 +214,6 @@ fun PermissionDenied() {
AlarmRow(
alarms = emptyList(),
ringMode = 0,
locale = Locale.getDefault(),
addAlarm = {},
deleteAlarm = {},
openRingType = {},

@ -14,13 +14,12 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import com.todoroo.andlib.utility.DateUtilities
import org.tasks.R
import org.tasks.compose.DeleteButton
import org.tasks.compose.TaskEditRow
import org.tasks.data.entity.UserActivity
import org.tasks.data.pictureUri
import java.util.Locale
import org.tasks.kmp.org.tasks.time.getFullDateTime
@Composable
fun CommentsRow(
@ -75,7 +74,7 @@ fun Comment(
)
}
Text(
text = DateUtilities.getLongDateStringWithTime(comment.created!!, Locale.getDefault()),
text = getFullDateTime(comment.created!!),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface,
)

@ -1,7 +1,6 @@
package org.tasks.compose.pickers
import android.content.res.Configuration
import android.os.LocaleList
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
import androidx.compose.foundation.border
@ -49,7 +48,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.os.ConfigurationCompat
import com.todoroo.andlib.utility.DateUtilities
import kotlinx.coroutines.runBlocking
import net.fortuna.ical4j.model.Recur
import net.fortuna.ical4j.model.WeekDay
import org.tasks.R
@ -57,10 +56,10 @@ import org.tasks.compose.OutlinedBox
import org.tasks.compose.OutlinedNumberInput
import org.tasks.compose.OutlinedSpinner
import org.tasks.compose.border
import org.tasks.kmp.org.tasks.time.getRelativeDay
import org.tasks.repeats.CustomRecurrenceViewModel
import org.tasks.themes.TasksTheme
import java.time.DayOfWeek
import java.time.format.FormatStyle
import java.time.format.TextStyle
import java.util.Locale
@ -342,10 +341,11 @@ private fun EndsPicker(
Text(text = stringResource(id = R.string.repeats_on))
Spacer(modifier = Modifier.width(8.dp))
val context = LocalContext.current
val locale = remember { LocaleList.getDefault()[0] }
val endDateString by remember(context, endDate) {
derivedStateOf {
DateUtilities.getRelativeDay(context, endDate, locale, FormatStyle.MEDIUM)
runBlocking {
getRelativeDay(endDate)
}
}
}
var showDatePicker by remember { mutableStateOf(false) }

@ -14,11 +14,13 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.button.MaterialButton
import com.todoroo.andlib.utility.DateUtilities
import org.tasks.R
import org.tasks.date.DateTimeUtils
import org.tasks.extensions.Context.is24HourFormat
import org.tasks.kmp.org.tasks.time.getTimeString
import org.tasks.preferences.Preferences
import org.tasks.themes.Theme
import org.tasks.time.DateTimeUtils2.currentTimeMillis
import org.tasks.time.withMillisOfDay
import javax.inject.Inject
abstract class BaseDateTimePicker : BottomSheetDialogFragment() {
@ -113,10 +115,12 @@ abstract class BaseDateTimePicker : BottomSheetDialogFragment() {
afternoon = preferences.dateShortcutAfternoon + 1000
evening = preferences.dateShortcutEvening + 1000
night = preferences.dateShortcutNight + 1000
morningButton.text = DateUtilities.getTimeString(requireContext(), DateTimeUtils.newDateTime().withMillisOfDay(morning))
afternoonButton.text = DateUtilities.getTimeString(requireContext(), DateTimeUtils.newDateTime().withMillisOfDay(afternoon))
eveningButton.text = DateUtilities.getTimeString(requireContext(), DateTimeUtils.newDateTime().withMillisOfDay(evening))
nightButton.text = DateUtilities.getTimeString(requireContext(), DateTimeUtils.newDateTime().withMillisOfDay(night))
val is24HourFormat = requireContext().is24HourFormat
val now = currentTimeMillis()
morningButton.text = getTimeString(now.withMillisOfDay(morning), is24HourFormat)
afternoonButton.text = getTimeString(now.withMillisOfDay(afternoon), is24HourFormat)
eveningButton.text = getTimeString(now.withMillisOfDay(evening), is24HourFormat)
nightButton.text = getTimeString(now.withMillisOfDay(night), is24HourFormat)
val firstDayOfWeek = preferences.firstDayOfWeek
if (firstDayOfWeek in 1..7) {
calendarView.firstDayOfWeek = firstDayOfWeek

@ -10,11 +10,11 @@ import android.view.ViewGroup
import androidx.core.view.isGone
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.dao.TaskDao
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.tasks.R
import org.tasks.data.createDueDate
import org.tasks.data.entity.Task
@ -22,11 +22,14 @@ import org.tasks.databinding.DialogDateTimePickerBinding
import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.date.DateTimeUtils.toDateTime
import org.tasks.dialogs.MyTimePickerDialog.Companion.newTimePicker
import org.tasks.extensions.Context.is24HourFormat
import org.tasks.kmp.org.tasks.time.getRelativeDay
import org.tasks.kmp.org.tasks.time.getTimeString
import org.tasks.notifications.NotificationManager
import org.tasks.time.DateTime
import org.tasks.time.millisOfDay
import org.tasks.time.startOfDay
import java.time.format.FormatStyle
import org.tasks.time.withMillisOfDay
import java.util.Calendar.FRIDAY
import java.util.Calendar.MONDAY
import java.util.Calendar.SATURDAY
@ -34,14 +37,12 @@ import java.util.Calendar.SUNDAY
import java.util.Calendar.THURSDAY
import java.util.Calendar.TUESDAY
import java.util.Calendar.WEDNESDAY
import java.util.Locale
import javax.inject.Inject
@AndroidEntryPoint
class DateTimePicker : BaseDateTimePicker() {
@Inject lateinit var activity: Activity
@Inject lateinit var locale: Locale
@Inject lateinit var taskDao: TaskDao
@Inject lateinit var notificationManager: NotificationManager
@ -170,7 +171,9 @@ class DateTimePicker : BaseDateTimePicker() {
binding.shortcuts.currentDateSelection.text = if (customDate == MULTIPLE_DAYS) {
requireContext().getString(R.string.date_picker_multiple)
} else {
DateUtilities.getRelativeDay(requireContext(), selectedDay, locale, FormatStyle.MEDIUM)
runBlocking {
getRelativeDay(selectedDay)
}
}
}
}
@ -184,11 +187,15 @@ class DateTimePicker : BaseDateTimePicker() {
customTime = selectedTime
binding.shortcuts.timeGroup.check(R.id.current_time_selection)
binding.shortcuts.currentTimeSelection.visibility = View.VISIBLE
binding.shortcuts.currentTimeSelection.text = if (customTime == MULTIPLE_TIMES) {
requireContext().getString(R.string.date_picker_multiple)
} else {
DateUtilities.getTimeString(requireContext(), today.withMillisOfDay(selectedTime))
}
binding.shortcuts.currentTimeSelection.text =
if (customTime == MULTIPLE_TIMES) {
requireContext().getString(R.string.date_picker_multiple)
} else {
getTimeString(
today.millis.withMillisOfDay(selectedTime),
requireContext().is24HourFormat
)
}
}
}
} else {

@ -8,25 +8,26 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.dao.TaskDao
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.runBlocking
import org.tasks.R
import org.tasks.data.entity.Task
import org.tasks.databinding.DialogStartDatePickerBinding
import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.dialogs.MyTimePickerDialog.Companion.newTimePicker
import org.tasks.extensions.Context.is24HourFormat
import org.tasks.kmp.org.tasks.time.getRelativeDay
import org.tasks.kmp.org.tasks.time.getTimeString
import org.tasks.notifications.NotificationManager
import org.tasks.time.DateTime
import java.time.format.FormatStyle
import java.util.Locale
import org.tasks.time.withMillisOfDay
import javax.inject.Inject
@AndroidEntryPoint
class StartDatePicker : BaseDateTimePicker() {
@Inject lateinit var activity: Activity
@Inject lateinit var locale: Locale
@Inject lateinit var taskDao: TaskDao
@Inject lateinit var notificationManager: NotificationManager
@ -112,7 +113,9 @@ class StartDatePicker : BaseDateTimePicker() {
binding.shortcuts.dateGroup.check(R.id.current_date_selection)
binding.shortcuts.currentDateSelection.visibility = View.VISIBLE
binding.shortcuts.currentDateSelection.text =
DateUtilities.getRelativeDay(requireContext(), selectedDay, locale, FormatStyle.MEDIUM)
runBlocking {
getRelativeDay(selectedDay)
}
}
}
if (Task.hasDueTime(selectedTime.toLong())) {
@ -125,7 +128,10 @@ class StartDatePicker : BaseDateTimePicker() {
customTime = selectedTime
binding.shortcuts.timeGroup.check(R.id.current_time_selection)
binding.shortcuts.currentTimeSelection.visibility = View.VISIBLE
binding.shortcuts.currentTimeSelection.text = DateUtilities.getTimeString(requireContext(), today.withMillisOfDay(selectedTime))
binding.shortcuts.currentTimeSelection.text = getTimeString(
today.millis.withMillisOfDay(selectedTime),
requireContext().is24HourFormat
)
}
}
if (selectedDay == DUE_TIME) {

@ -15,6 +15,7 @@ import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
import android.net.Uri
import android.os.Build
import android.provider.Settings
import android.text.format.DateFormat
import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
@ -22,6 +23,7 @@ import androidx.annotation.AnyRes
import androidx.browser.customtabs.CustomTabsIntent
import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor
import com.todoroo.andlib.utility.AndroidUtilities.atLeastS
import org.tasks.BuildConfig
import org.tasks.R
import org.tasks.notifications.NotificationManager.Companion.NOTIFICATION_CHANNEL_DEFAULT
@ -29,6 +31,14 @@ object Context {
private const val HTTP = "http"
private const val HTTPS = "https"
var is24HourOverride: Boolean? = null
val Context.is24HourFormat: Boolean
get() = if (BuildConfig.DEBUG && is24HourOverride != null)
is24HourOverride!!
else
DateFormat.is24HourFormat(this)
fun Context.safeStartActivity(intent: Intent) {
try {
startActivity(intent)

@ -6,7 +6,6 @@ import android.os.Bundle
import androidx.fragment.app.activityViewModels
import androidx.preference.Preference
import androidx.preference.SwitchPreferenceCompat
import com.todoroo.andlib.utility.DateUtilities
import dagger.hilt.android.AndroidEntryPoint
import org.tasks.PermissionUtil
import org.tasks.R
@ -16,11 +15,11 @@ import org.tasks.drive.DriveLoginActivity
import org.tasks.extensions.Context.toast
import org.tasks.files.FileHelper
import org.tasks.injection.InjectingPreferenceFragment
import org.tasks.kmp.org.tasks.time.getFullDateTime
import org.tasks.preferences.FragmentPermissionRequestor
import org.tasks.preferences.PermissionRequestor
import org.tasks.preferences.Preferences
import org.tasks.preferences.PreferencesViewModel
import java.util.*
import javax.inject.Inject
private const val REQUEST_CODE_BACKUP_DIR = 10001
@ -35,7 +34,6 @@ class Backups : InjectingPreferenceFragment() {
@Inject lateinit var preferences: Preferences
@Inject lateinit var permissionRequestor: FragmentPermissionRequestor
@Inject lateinit var locale: Locale
private val viewModel: PreferencesViewModel by activityViewModels()
@ -101,7 +99,7 @@ class Backups : InjectingPreferenceFragment() {
R.string.last_backup,
timestamp
?.takeIf { it >= 0 }
?.let { DateUtilities.getLongDateStringWithTime(it, locale) }
?.let { getFullDateTime(it) }
?: getString(R.string.last_backup_never)
)
}
@ -119,7 +117,7 @@ class Backups : InjectingPreferenceFragment() {
R.string.last_backup,
timestamp
?.takeIf { it >= 0 }
?.let { DateUtilities.getLongDateStringWithTime(it, locale) }
?.let { getFullDateTime(it) }
?: getString(R.string.last_backup_never)
)
}
@ -137,7 +135,7 @@ class Backups : InjectingPreferenceFragment() {
R.string.last_backup,
timestamp
?.takeIf { it >= 0 }
?.let { DateUtilities.getLongDateStringWithTime(it, locale) }
?.let { getFullDateTime(it) }
?: getString(R.string.last_backup_never)
)
}

@ -16,9 +16,9 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceCategory
import com.google.android.material.textfield.TextInputLayout
import com.todoroo.andlib.utility.DateUtilities
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.tasks.BuildConfig
import org.tasks.LocalBroadcastManager
import org.tasks.R
@ -32,10 +32,10 @@ import org.tasks.data.entity.CaldavAccount.Companion.isPaymentRequired
import org.tasks.extensions.Context.openUri
import org.tasks.extensions.Context.toast
import org.tasks.jobs.WorkManager
import org.tasks.kmp.org.tasks.time.DateStyle
import org.tasks.kmp.org.tasks.time.getRelativeDay
import org.tasks.preferences.IconPreference
import org.tasks.preferences.fragments.MainSettingsFragment.Companion.REQUEST_TASKS_ORG
import java.time.format.FormatStyle
import java.util.Locale
import javax.inject.Inject
@AndroidEntryPoint
@ -44,7 +44,6 @@ class TasksAccount : BaseAccountPreference() {
@Inject lateinit var inventory: Inventory
@Inject lateinit var localBroadcastManager: LocalBroadcastManager
@Inject lateinit var workManager: WorkManager
@Inject lateinit var locale: Locale
private val viewModel: TasksAccountViewModel by viewModels()
@ -98,7 +97,11 @@ class TasksAccount : BaseAccountPreference() {
override fun onResume() {
super.onResume()
viewModel.appPasswords.observe(this) { passwords ->
passwords?.let { refreshPasswords(passwords) }
passwords?.let {
runBlocking {
refreshPasswords(passwords)
}
}
}
viewModel.newPassword.observe(this) {
it?.let {
@ -212,7 +215,7 @@ class TasksAccount : BaseAccountPreference() {
}
}
private fun refreshPasswords(passwords: List<TasksAccountViewModel.AppPassword>) {
private suspend fun refreshPasswords(passwords: List<TasksAccountViewModel.AppPassword>) {
findPreference(R.string.app_passwords_more_info).isVisible = passwords.isEmpty()
val category = findPreference(R.string.app_passwords) as PreferenceCategory
category.removeAll()
@ -243,14 +246,11 @@ class TasksAccount : BaseAccountPreference() {
}
}
private fun formatString(date: Long?): String? = date?.let {
DateUtilities.getRelativeDay(
requireContext(),
date,
locale,
FormatStyle.FULL,
false,
true
private suspend fun formatString(date: Long?): String? = date?.let {
getRelativeDay(
date,
DateStyle.FULL,
lowercase = true
)
}

@ -2,14 +2,13 @@ package org.tasks.reminders
import android.content.Context
import android.content.res.Resources
import com.todoroo.andlib.utility.DateUtilities
import org.tasks.R
import org.tasks.data.entity.Alarm
import java.util.Locale
import org.tasks.kmp.org.tasks.time.getFullDateTime
import java.util.concurrent.TimeUnit
import kotlin.math.absoluteValue
class AlarmToString(context: Context, var locale: Locale) {
class AlarmToString(context: Context) {
private val resources = context.resources
fun toString(alarm: Alarm): String {
@ -41,10 +40,10 @@ class AlarmToString(context: Context, var locale: Locale) {
Alarm.TYPE_SNOOZE ->
resources.getString(
R.string.snoozed_until,
DateUtilities.getLongDateStringWithTime(alarm.time, locale)
getFullDateTime(alarm.time)
)
else ->
DateUtilities.getLongDateStringWithTime(alarm.time, locale)
getFullDateTime(alarm.time)
}
return if (alarm.repeat > 0) {
reminder + "\n" + resources.getRepeatString(alarm.repeat, alarm.interval)

@ -1,23 +1,27 @@
package org.tasks.reminders;
import static com.todoroo.andlib.utility.DateUtilities.getTimeString;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import dagger.hilt.android.AndroidEntryPoint;
import dagger.hilt.android.qualifiers.ApplicationContext;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import org.tasks.R;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.kmp.org.tasks.time.DateUtilitiesKt;
import org.tasks.preferences.Preferences;
import org.tasks.time.DateTime;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
import dagger.hilt.android.qualifiers.ApplicationContext;
@AndroidEntryPoint
public class SnoozeDialog extends DialogFragment {
@ -92,7 +96,11 @@ public class SnoozeDialog extends DialogFragment {
String.format(
"%s (%s)",
getString(snoozeOption.getResId()),
getTimeString(context, snoozeOption.getDateTime())));
DateUtilitiesKt.getTimeString(
snoozeOption.getDateTime().getMillis(),
org.tasks.extensions.Context.INSTANCE.is24HourFormat(context))
)
);
}
items.add(getString(R.string.pick_a_date_and_time));

@ -1,19 +1,31 @@
package org.tasks.repeats
import android.content.Context
import com.todoroo.andlib.utility.DateUtilities
import dagger.hilt.android.qualifiers.ApplicationContext
import net.fortuna.ical4j.model.Recur
import net.fortuna.ical4j.model.Recur.Frequency
import net.fortuna.ical4j.model.Recur.Frequency.*
import net.fortuna.ical4j.model.Recur.Frequency.DAILY
import net.fortuna.ical4j.model.Recur.Frequency.HOURLY
import net.fortuna.ical4j.model.Recur.Frequency.MINUTELY
import net.fortuna.ical4j.model.Recur.Frequency.MONTHLY
import net.fortuna.ical4j.model.Recur.Frequency.WEEKLY
import net.fortuna.ical4j.model.Recur.Frequency.YEARLY
import net.fortuna.ical4j.model.WeekDay.Day
import net.fortuna.ical4j.model.WeekDay.Day.*
import net.fortuna.ical4j.model.WeekDay.Day.FR
import net.fortuna.ical4j.model.WeekDay.Day.MO
import net.fortuna.ical4j.model.WeekDay.Day.SA
import net.fortuna.ical4j.model.WeekDay.Day.SU
import net.fortuna.ical4j.model.WeekDay.Day.TH
import net.fortuna.ical4j.model.WeekDay.Day.TU
import net.fortuna.ical4j.model.WeekDay.Day.WE
import org.tasks.R
import org.tasks.analytics.Firebase
import org.tasks.kmp.org.tasks.time.getFullDate
import org.tasks.repeats.RecurrenceUtils.newRecur
import org.tasks.time.DateTime
import java.text.DateFormatSymbols
import java.util.*
import java.util.Calendar
import java.util.Locale
import javax.inject.Inject
class RepeatRuleToString @Inject constructor(
@ -47,10 +59,10 @@ class RepeatRuleToString @Inject constructor(
repeatUntil == null ->
context.getString(R.string.repeats_single_on, frequencyString, dayString)
else -> context.getString(
R.string.repeats_single_on_until,
frequencyString,
dayString,
DateUtilities.getLongDateString(repeatUntil, locale)
R.string.repeats_single_on_until,
frequencyString,
dayString,
getFullDate(repeatUntil.millis)
)
}
} else if (count > 0) {
@ -60,9 +72,10 @@ class RepeatRuleToString @Inject constructor(
context.getString(R.string.repeats_single, frequencyString)
} else {
context.getString(
R.string.repeats_single_until,
frequencyString,
DateUtilities.getLongDateString(repeatUntil, locale))
R.string.repeats_single_until,
frequencyString,
getFullDate(repeatUntil.millis)
)
}
} else {
val plural = getFrequencyPlural(frequency)
@ -80,10 +93,10 @@ class RepeatRuleToString @Inject constructor(
repeatUntil == null ->
context.getString(R.string.repeats_plural_on, frequencyPlural, dayString)
else -> context.getString(
R.string.repeats_plural_on_until,
frequencyPlural,
dayString,
DateUtilities.getLongDateString(repeatUntil, locale)
R.string.repeats_plural_on_until,
frequencyPlural,
dayString,
getFullDate(repeatUntil.millis)
)
}
} else if (count > 0) {
@ -93,9 +106,10 @@ class RepeatRuleToString @Inject constructor(
context.getString(R.string.repeats_plural, frequencyPlural)
} else {
context.getString(
R.string.repeats_plural_until,
frequencyPlural,
DateUtilities.getLongDateString(repeatUntil, locale))
R.string.repeats_plural_until,
frequencyPlural,
getFullDate(repeatUntil.millis)
)
}
}
} catch (e: Exception) {

@ -2,21 +2,19 @@ package org.tasks.tasklist
import android.content.Context
import androidx.annotation.StringRes
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.core.SortHelper
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.runBlocking
import org.tasks.R
import org.tasks.data.dao.CaldavDao
import org.tasks.kmp.org.tasks.time.DateStyle
import org.tasks.kmp.org.tasks.time.getRelativeDay
import org.tasks.preferences.Preferences
import java.time.format.FormatStyle
import java.util.Locale
import javax.inject.Inject
class HeaderFormatter @Inject constructor(
@ApplicationContext private val context: Context,
private val preferences: Preferences,
private val locale: Locale,
private val caldavDao: CaldavDao,
) {
private val listCache = HashMap<Long, String?>()
@ -25,7 +23,7 @@ class HeaderFormatter @Inject constructor(
value: Long,
groupMode: Int = preferences.groupMode,
alwaysDisplayFullDate: Boolean = preferences.alwaysDisplayFullDate,
style: FormatStyle = FormatStyle.FULL,
style: DateStyle = DateStyle.FULL,
compact: Boolean = false,
) = runBlocking {
headerString(value, groupMode, alwaysDisplayFullDate, style, compact)
@ -35,7 +33,7 @@ class HeaderFormatter @Inject constructor(
value: Long,
groupMode: Int = preferences.groupMode,
alwaysDisplayFullDate: Boolean = preferences.alwaysDisplayFullDate,
style: FormatStyle = FormatStyle.FULL,
style: DateStyle = DateStyle.FULL,
compact: Boolean = false
): String =
when {
@ -50,8 +48,11 @@ class HeaderFormatter @Inject constructor(
else -> R.string.no_date
})
else -> {
val dateString = DateUtilities.getRelativeDay(
context, value, locale, style, alwaysDisplayFullDate, !compact
val dateString = getRelativeDay(
value,
style,
alwaysDisplayFullDate = alwaysDisplayFullDate,
lowercase = !compact
)
when {
compact -> dateString

@ -13,11 +13,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.unit.dp
import androidx.recyclerview.widget.RecyclerView
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.core.SortHelper.SORT_DUE
import com.todoroo.astrid.core.SortHelper.SORT_LIST
import com.todoroo.astrid.core.SortHelper.SORT_START
import com.todoroo.astrid.ui.CheckableImageView
import kotlinx.coroutines.runBlocking
import org.tasks.R
import org.tasks.compose.ChipGroup
import org.tasks.compose.FilterChip
@ -28,13 +28,15 @@ import org.tasks.data.hasNotes
import org.tasks.data.isHidden
import org.tasks.data.isOverdue
import org.tasks.databinding.TaskAdapterRowBinding
import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.dialogs.Linkify
import org.tasks.extensions.Context.is24HourFormat
import org.tasks.filters.CaldavFilter
import org.tasks.filters.Filter
import org.tasks.filters.GtasksFilter
import org.tasks.filters.PlaceFilter
import org.tasks.filters.TagFilter
import org.tasks.kmp.org.tasks.time.getRelativeDateTime
import org.tasks.kmp.org.tasks.time.getTimeString
import org.tasks.markdown.Markdown
import org.tasks.preferences.Preferences
import org.tasks.themes.TasksIcons
@ -43,8 +45,6 @@ import org.tasks.time.DateTimeUtils2.currentTimeMillis
import org.tasks.time.startOfDay
import org.tasks.ui.CheckBoxProvider
import org.tasks.ui.ChipProvider
import java.time.format.FormatStyle
import java.util.Locale
import kotlin.math.max
import kotlin.math.roundToInt
@ -64,7 +64,6 @@ class TaskViewHolder internal constructor(
private val rowPaddingDp: Int,
private val rowPaddingPx: Int,
private val linkify: Linkify,
private val locale: Locale,
private val markdown: Markdown
) : RecyclerView.ViewHolder(binding.root) {
private val row: ViewGroup = binding.row
@ -220,10 +219,16 @@ class TaskViewHolder internal constructor(
&& (task.sortGroup ?: 0) >= currentTimeMillis().startOfDay()
) {
task.takeIf { it.hasDueTime() }?.let {
DateUtilities.getTimeString(context, newDateTime(task.dueDate))
getTimeString(task.dueDate, context.is24HourFormat)
}
} else {
DateUtilities.getRelativeDateTime(context, task.dueDate, locale, FormatStyle.MEDIUM, alwaysDisplayFullDate, false)
runBlocking {
getRelativeDateTime(
task.dueDate,
context.is24HourFormat,
alwaysDisplayFullDate = alwaysDisplayFullDate
)
}
}
dueDate.text = dateValue
dueDate.visibility = View.VISIBLE

@ -16,7 +16,6 @@ import org.tasks.preferences.ResourceResolver
import org.tasks.tasklist.TaskViewHolder.ViewHolderCallbacks
import org.tasks.ui.CheckBoxProvider
import org.tasks.ui.ChipProvider
import java.util.Locale
import javax.inject.Inject
class ViewHolderFactory @Inject constructor(
@ -25,7 +24,6 @@ class ViewHolderFactory @Inject constructor(
private val chipProvider: ChipProvider,
private val checkBoxProvider: CheckBoxProvider,
private val linkify: Linkify,
private val locale: Locale,
private val headerFormatter: HeaderFormatter,
) {
private val textColorSecondary: Int = ResourceResolver.getData(context, android.R.attr.textColorSecondary)
@ -64,7 +62,6 @@ class ViewHolderFactory @Inject constructor(
rowPaddingDp,
rowPaddingPx,
linkify,
locale,
markdown
)
}

@ -142,7 +142,7 @@ class DateTime {
}
fun minusSeconds(seconds: Int): DateTime {
return subtract(Calendar.SECOND, seconds)
return add(Calendar.SECOND, -seconds)
}
fun minusDays(days: Int): DateTime = DateTime(millis.minusDays(days))
@ -199,10 +199,6 @@ class DateTime {
return DateTime(calendar)
}
private fun subtract(field: Int, value: Int): DateTime {
return add(field, -value)
}
private fun add(field: Int, value: Int): DateTime {
val calendar = calendar
calendar.add(field, value)
@ -262,15 +258,14 @@ class DateTime {
throw RuntimeException()
}
override fun equals(o: Any?): Boolean {
if (this === o) {
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (o !is DateTime) {
if (other !is DateTime) {
return false
}
val dateTime = o
return millis == dateTime.millis && timeZone == dateTime.timeZone
return millis == other.millis && timeZone == other.timeZone
}
override fun hashCode(): Int {
@ -303,11 +298,10 @@ class DateTime {
fun from(date: Date?): DateTime {
if (date is net.fortuna.ical4j.model.DateTime) {
val dt = date
val tz: TimeZone? = dt.timeZone
val tz: TimeZone? = date.timeZone
return DateTime(
dt.time,
tz ?: if (dt.isUtc) UTC else TimeZone.getDefault()
date.time,
tz ?: if (date.isUtc) UTC else TimeZone.getDefault()
)
} else {
return from(date as java.util.Date?)

@ -1,14 +1,19 @@
package org.tasks.ui;
import static org.tasks.time.DateTimeUtils2.currentTimeMillis;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import androidx.preference.Preference;
import com.todoroo.andlib.utility.DateUtilities;
import org.tasks.R;
import org.tasks.dialogs.MyTimePickerDialog;
import org.tasks.kmp.org.tasks.time.DateUtilitiesKt;
import org.tasks.time.DateTime;
import org.tasks.time.LongExtensionsKt;
public class TimePreference extends Preference {
@ -47,7 +52,10 @@ public class TimePreference extends Preference {
private void setMillisOfDay(int millisOfDay) {
this.millisOfDay = millisOfDay;
String setting =
DateUtilities.getTimeString(getContext(), new DateTime().withMillisOfDay(millisOfDay));
DateUtilitiesKt.getTimeString(
LongExtensionsKt.withMillisOfDay(currentTimeMillis(), millisOfDay),
org.tasks.extensions.Context.INSTANCE.is24HourFormat(getContext())
);
setSummary(summary == null ? setting : String.format(summary, setting));
}

@ -13,7 +13,6 @@ import org.tasks.markdown.MarkdownProvider
import org.tasks.preferences.DefaultFilterProvider
import org.tasks.preferences.Preferences
import org.tasks.tasklist.HeaderFormatter
import java.util.Locale
import javax.inject.Inject
@AndroidEntryPoint
@ -23,7 +22,6 @@ class TasksWidgetAdapter : RemoteViewsService() {
@Inject lateinit var taskDao: TaskDao
@Inject lateinit var preferences: Preferences
@Inject lateinit var subtasksHelper: SubtasksHelper
@Inject lateinit var locale: Locale
@Inject lateinit var chipProvider: WidgetChipProvider
@Inject lateinit var markdownProvider: MarkdownProvider
@Inject lateinit var headerFormatter: HeaderFormatter
@ -41,7 +39,6 @@ class TasksWidgetAdapter : RemoteViewsService() {
applicationContext,
widgetId,
taskDao,
locale,
chipProvider,
markdownProvider.markdown(false),
headerFormatter,

@ -5,7 +5,6 @@ import android.content.Intent
import android.view.View
import android.widget.RemoteViews
import android.widget.RemoteViewsService.RemoteViewsFactory
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.core.SortHelper
import com.todoroo.astrid.subtasks.SubtasksHelper
import kotlinx.coroutines.runBlocking
@ -17,7 +16,7 @@ import org.tasks.data.dao.TaskDao
import org.tasks.data.hasNotes
import org.tasks.data.isHidden
import org.tasks.data.isOverdue
import org.tasks.date.DateTimeUtils
import org.tasks.extensions.Context.is24HourFormat
import org.tasks.extensions.setBackgroundResource
import org.tasks.extensions.setColorFilter
import org.tasks.extensions.setMaxLines
@ -26,6 +25,9 @@ import org.tasks.extensions.strikethrough
import org.tasks.filters.AstridOrderingFilter
import org.tasks.filters.Filter
import org.tasks.kmp.org.tasks.themes.ColorProvider.priorityColor
import org.tasks.kmp.org.tasks.time.DateStyle
import org.tasks.kmp.org.tasks.time.getRelativeDateTime
import org.tasks.kmp.org.tasks.time.getTimeString
import org.tasks.markdown.Markdown
import org.tasks.tasklist.HeaderFormatter
import org.tasks.tasklist.SectionedDataSource
@ -34,8 +36,6 @@ import org.tasks.time.DateTimeUtils2.currentTimeMillis
import org.tasks.time.startOfDay
import org.tasks.ui.CheckBoxProvider.Companion.getCheckboxRes
import timber.log.Timber
import java.time.format.FormatStyle
import java.util.Locale
import kotlin.math.max
internal class TasksWidgetViewFactory(
@ -45,7 +45,6 @@ internal class TasksWidgetViewFactory(
private val context: Context,
private val widgetId: Int,
private val taskDao: TaskDao,
private val locale: Locale,
private val chipProvider: WidgetChipProvider,
private val markdown: Markdown,
private val headerFormatter: HeaderFormatter,
@ -111,7 +110,7 @@ internal class TasksWidgetViewFactory(
value = section.value,
groupMode = settings.groupMode,
alwaysDisplayFullDate = settings.showFullDate,
style = FormatStyle.MEDIUM,
style = DateStyle.MEDIUM,
compact = settings.compact,
)
} else {
@ -290,12 +289,16 @@ internal class TasksWidgetViewFactory(
!disableGroups
) {
task.takeIf { it.hasDueTime() }?.let {
DateUtilities.getTimeString(context, DateTimeUtils.newDateTime(task.dueDate))
getTimeString(task.dueDate, context.is24HourFormat)
}
} else {
DateUtilities.getRelativeDateTime(
context, task.dueDate, locale, FormatStyle.MEDIUM, settings.showFullDate, false
)
runBlocking {
getRelativeDateTime(
task.dueDate,
context.is24HourFormat,
alwaysDisplayFullDate = settings.showFullDate
)
}
}
setTextViewText(dueDateRes, text)
setTextColor(

@ -4,15 +4,15 @@ import android.content.Context
import android.widget.RemoteViews
import androidx.annotation.ColorInt
import com.mikepenz.iconics.IconicsDrawable
import com.todoroo.andlib.utility.DateUtilities
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.runBlocking
import org.tasks.BuildConfig
import org.tasks.R
import org.tasks.billing.Inventory
import org.tasks.data.TaskContainer
import org.tasks.data.entity.Task
import org.tasks.data.isHidden
import org.tasks.date.DateTimeUtils.toDateTime
import org.tasks.extensions.Context.is24HourFormat
import org.tasks.extensions.setColorFilter
import org.tasks.filters.CaldavFilter
import org.tasks.filters.Filter
@ -21,16 +21,15 @@ import org.tasks.filters.PlaceFilter
import org.tasks.filters.TagFilter
import org.tasks.filters.getIcon
import org.tasks.icons.OutlinedGoogleMaterial
import org.tasks.kmp.org.tasks.time.getRelativeDateTime
import org.tasks.kmp.org.tasks.time.getTimeString
import org.tasks.time.startOfDay
import org.tasks.ui.ChipListCache
import java.time.format.FormatStyle
import java.util.Locale
import javax.inject.Inject
class WidgetChipProvider @Inject constructor(
@ApplicationContext private val context: Context,
private val chipListCache: ChipListCache,
private val locale: Locale,
private val inventory: Inventory,
) {
var isDark = false
@ -59,17 +58,16 @@ class WidgetChipProvider @Inject constructor(
val time = if (sortByStartDate && task.sortGroup?.startOfDay() == task.task.hideUntil.startOfDay()) {
task.task.hideUntil
.takeIf { Task.hasDueTime(it) }
?.let { DateUtilities.getTimeString(context, it.toDateTime()) }
?.let { getTimeString(it, context.is24HourFormat) }
?: return null
} else {
DateUtilities.getRelativeDateTime(
context,
task.task.hideUntil,
locale,
FormatStyle.MEDIUM,
showFullDate,
false
)
runBlocking {
getRelativeDateTime(
task.task.hideUntil,
context.is24HourFormat,
alwaysDisplayFullDate = showFullDate
)
}
}
newChip().apply {
setTextViewText(R.id.chip_text, time)

@ -3,12 +3,12 @@ package org.tasks
import android.content.Context
import at.bitfire.ical4android.Task.Companion.tasksFromReader
import com.squareup.moshi.Moshi
import org.tasks.data.entity.Task
import kotlinx.coroutines.runBlocking
import org.tasks.caldav.applyRemote
import org.tasks.caldav.iCalendar.Companion.reminders
import org.tasks.data.entity.Alarm
import org.tasks.data.entity.CaldavTask
import org.tasks.data.entity.Task
import org.tasks.preferences.Preferences
import org.tasks.sync.microsoft.MicrosoftConverter.applyRemote
import org.tasks.sync.microsoft.Tasks
@ -16,6 +16,7 @@ import org.tasks.time.DateTime
import java.io.StringReader
import java.nio.file.Files
import java.nio.file.Paths
import java.util.Locale
import java.util.TimeZone
object TestUtilities {
@ -34,6 +35,18 @@ object TestUtilities {
}
}
fun withLocale(locale: Locale, runnable: suspend () -> Unit) {
val def = Locale.getDefault()
try {
Locale.setDefault(locale)
runBlocking {
runnable()
}
} finally {
Locale.setDefault(def)
}
}
fun assertEquals(expected: Long, actual: DateTime) =
org.junit.Assert.assertEquals(expected, actual.millis)

@ -58,6 +58,7 @@ android {
}
compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
@ -67,6 +68,7 @@ android {
}
dependencies {
coreLibraryDesugaring(libs.desugar.jdk.libs)
debugImplementation(compose.uiTooling)
}
}

@ -5,6 +5,12 @@ import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import org.tasks.data.BuildConfig
import org.tasks.extensions.formatNumber
import org.tasks.kmp.org.tasks.time.DateStyle
import org.tasks.kmp.org.tasks.time.TextStyle
import org.tasks.kmp.org.tasks.time.toFormatStyle
import org.tasks.kmp.org.tasks.time.toLocalDateTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.Locale
actual fun formatNumber(number: Int) = Locale.getDefault().formatNumber(number)
@ -13,4 +19,32 @@ fun createDataStore(context: Context): DataStore<Preferences> = createDataStore(
producePath = { context.filesDir.resolve(dataStoreFileName).absolutePath }
)
actual val IS_DEBUG = BuildConfig.DEBUG
actual val IS_DEBUG = BuildConfig.DEBUG
actual fun formatDate(timestamp: Long, style: DateStyle): String =
DateTimeFormatter
.ofLocalizedDate(style.toFormatStyle())
.withLocale(Locale.getDefault())
.format(timestamp.toLocalDateTime().toLocalDate())
actual fun formatDateTime(timestamp: Long, style: DateStyle): String =
DateTimeFormatter
.ofLocalizedDateTime(style.toFormatStyle(), FormatStyle.SHORT)
.withLocale(Locale.getDefault())
.format(timestamp.toLocalDateTime())
actual fun formatDayOfWeek(timestamp: Long, style: TextStyle): String =
timestamp
.toLocalDateTime()
.dayOfWeek
.getDisplayName(
when (style) {
TextStyle.FULL -> java.time.format.TextStyle.FULL
TextStyle.SHORT -> java.time.format.TextStyle.SHORT
TextStyle.NARROW -> java.time.format.TextStyle.NARROW
},
Locale.getDefault()
)
actual fun formatDateTime(timestamp: Long, format: String): String =
timestamp.toLocalDateTime().format(DateTimeFormatter.ofPattern(format))

@ -0,0 +1,16 @@
package org.tasks.kmp.org.tasks.time
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.FormatStyle
fun Long.toLocalDateTime(): LocalDateTime =
LocalDateTime.ofInstant(Instant.ofEpochMilli(this), ZoneId.systemDefault())
fun DateStyle.toFormatStyle() = when (this) {
DateStyle.FULL -> FormatStyle.FULL
DateStyle.LONG -> FormatStyle.LONG
DateStyle.MEDIUM -> FormatStyle.MEDIUM
DateStyle.SHORT -> FormatStyle.SHORT
}

@ -18,4 +18,14 @@
<string name="drawer_tags">Tags</string>
<string name="drawer_places">Places</string>
<string name="drawer_local_lists">Local lists</string>
<string name="tmrw">Tmrw</string>
<string name="tomorrow_abbrev_lowercase">tmrw</string>
<string name="tomorrow">Tomorrow</string>
<string name="tomorrow_lowercase">tomorrow</string>
<string name="yest">Yest</string>
<string name="yesterday_abbrev_lowercase">yest</string>
<string name="yesterday">Yesterday</string>
<string name="yesterday_lowercase">yesterday</string>
<string name="today">Today</string>
<string name="today_lowercase">today</string>
</resources>

@ -4,6 +4,8 @@ import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.Preferences
import okio.Path.Companion.toPath
import org.tasks.kmp.org.tasks.time.DateStyle
import org.tasks.kmp.org.tasks.time.TextStyle
expect fun formatNumber(number: Int): String
@ -14,4 +16,12 @@ fun createDataStore(producePath: () -> String): DataStore<Preferences> =
produceFile = { producePath().toPath() }
)
const val dataStoreFileName = "tasks.preferences_pb"
const val dataStoreFileName = "tasks.preferences_pb"
expect fun formatDate(timestamp: Long, style: DateStyle): String
expect fun formatDateTime(timestamp: Long, style: DateStyle): String
expect fun formatDayOfWeek(timestamp: Long, style: TextStyle): String
expect fun formatDateTime(timestamp: Long, format: String): String

@ -0,0 +1,5 @@
package org.tasks.kmp.org.tasks.time
enum class DateStyle {
FULL, LONG, MEDIUM, SHORT
}

@ -0,0 +1,138 @@
package org.tasks.kmp.org.tasks.time
import org.jetbrains.compose.resources.getString
import org.tasks.data.entity.Task.Companion.hasDueTime
import org.tasks.kmp.formatDate
import org.tasks.kmp.formatDateTime
import org.tasks.kmp.formatDayOfWeek
import org.tasks.time.DateTimeUtils2.currentTimeMillis
import org.tasks.time.ONE_DAY
import org.tasks.time.minuteOfHour
import org.tasks.time.plusDays
import org.tasks.time.startOfDay
import org.tasks.time.year
import tasks.kmp.generated.resources.Res
import tasks.kmp.generated.resources.tmrw
import tasks.kmp.generated.resources.today
import tasks.kmp.generated.resources.today_lowercase
import tasks.kmp.generated.resources.tomorrow
import tasks.kmp.generated.resources.tomorrow_abbrev_lowercase
import tasks.kmp.generated.resources.tomorrow_lowercase
import tasks.kmp.generated.resources.yest
import tasks.kmp.generated.resources.yesterday
import tasks.kmp.generated.resources.yesterday_abbrev_lowercase
import tasks.kmp.generated.resources.yesterday_lowercase
import kotlin.math.abs
fun getTimeString(date: Long, is24HourFormat: Boolean): String =
formatDateTime(
timestamp = date,
format = when {
is24HourFormat -> "HH:mm"
date.minuteOfHour == 0 -> "h a"
else -> "h:mm a"
}
)
suspend fun getRelativeDateTime(
date: Long,
is24HourFormat: Boolean,
style: DateStyle = DateStyle.MEDIUM,
alwaysDisplayFullDate: Boolean = false,
lowercase: Boolean = false
): String {
if (alwaysDisplayFullDate || !isWithinSixDays(date)) {
return if (hasDueTime(date))
getFullDateTime(date, style)
else
getFullDate(date, style)
}
val day = getRelativeDay(date, isAbbreviated(style), lowercase)
return if (hasDueTime(date)) {
val time = getTimeString(date, is24HourFormat)
if (currentTimeMillis().startOfDay() == date.startOfDay())
time
else
String.format("%s %s", day, time)
} else {
day
}
}
suspend fun getRelativeDay(
date: Long,
style: DateStyle = DateStyle.MEDIUM,
alwaysDisplayFullDate: Boolean = false,
lowercase: Boolean = false,
): String =
if (alwaysDisplayFullDate || !isWithinSixDays(date)) {
getFullDate(date, style)
} else {
getRelativeDay(date, isAbbreviated(style), lowercase)
}
fun getFullDate(
date: Long,
style: DateStyle = DateStyle.LONG,
): String = stripYear(formatDate(date, style), currentTimeMillis().year)
fun getFullDateTime(
date: Long,
style: DateStyle = DateStyle.LONG,
): String = stripYear(formatDateTime(date, style), currentTimeMillis().year)
private fun isAbbreviated(style: DateStyle): Boolean =
style == DateStyle.SHORT || style == DateStyle.MEDIUM
private fun stripYear(date: String, year: Int): String =
date.replace("(?: de |, |/| )?$year(?:年|년 | г\\.)?".toRegex(), "")
private suspend fun getRelativeDay(
date: Long,
abbreviated: Boolean,
lowercase: Boolean
): String {
val startOfToday = currentTimeMillis().startOfDay()
val startOfDate = date.startOfDay()
if (startOfToday == startOfDate) {
return getString(if (lowercase) Res.string.today_lowercase else Res.string.today)
}
if (startOfToday.plusDays(1) == startOfDate) {
return getString(
if (abbreviated) {
if (lowercase) Res.string.tomorrow_abbrev_lowercase else Res.string.tmrw
} else {
if (lowercase) Res.string.tomorrow_lowercase else Res.string.tomorrow
}
)
}
if (startOfDate.plusDays(1) == startOfToday) {
return getString(
when {
abbreviated ->
if (lowercase) Res.string.yesterday_abbrev_lowercase else Res.string.yest
lowercase ->
Res.string.yesterday_lowercase
else ->
Res.string.yesterday
}
)
}
return formatDayOfWeek(
timestamp = date,
style = if (abbreviated) TextStyle.SHORT else TextStyle.FULL
)
}
private fun isWithinSixDays(date: Long): Boolean {
val startOfToday = currentTimeMillis().startOfDay()
val startOfDate = date.startOfDay()
return abs((startOfToday - startOfDate).toDouble()) <= ONE_DAY * 6
}

@ -7,6 +7,7 @@ import kotlinx.datetime.LocalTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.atTime
import kotlinx.datetime.minus
import kotlinx.datetime.plus
import kotlinx.datetime.toInstant
import kotlinx.datetime.toLocalDateTime
@ -95,6 +96,18 @@ fun Long.withMillisOfDay(millisOfDay: Int): Long =
0
}
fun Long.plusDays(days: Int): Long =
if (this > 0) {
with (toLocalDateTime()) {
date
.plus(days, DateTimeUnit.DAY)
.atTime(time)
.toEpochMilliseconds()
}
} else {
0
}
fun Long.minusDays(days: Int): Long =
if (this > 0) {
with (toLocalDateTime()) {
@ -124,6 +137,12 @@ private fun Long.minus(value: Int, units: DateTimeUnit.TimeBased): Long =
val Long.millisOfDay: Int
get() = if (this > 0) toLocalDateTime().time.toMillisecondOfDay() else 0
val Long.minuteOfHour: Int
get() = if (this > 0) toLocalDateTime().minute else 0
val Long.year: Int
get() = if (this > 0) toLocalDateTime().year else 0
private fun Long.toLocalDateTime(): LocalDateTime =
Instant.fromEpochMilliseconds(this).toLocalDateTime(TimeZone.currentSystemDefault())

@ -0,0 +1,5 @@
package org.tasks.kmp.org.tasks.time
enum class TextStyle {
FULL, SHORT, NARROW
}

@ -1,5 +1,41 @@
package org.tasks.kmp
import org.tasks.kmp.org.tasks.time.DateStyle
import org.tasks.kmp.org.tasks.time.TextStyle
import org.tasks.kmp.org.tasks.time.toFormatStyle
import org.tasks.kmp.org.tasks.time.toLocalDateTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.Locale
actual fun formatNumber(number: Int) = number.toString()
actual val IS_DEBUG = true
actual val IS_DEBUG = true
actual fun formatDate(timestamp: Long, style: DateStyle): String =
DateTimeFormatter
.ofLocalizedDate(style.toFormatStyle())
.withLocale(Locale.getDefault())
.format(timestamp.toLocalDateTime().toLocalDate())
actual fun formatDateTime(timestamp: Long, style: DateStyle): String =
DateTimeFormatter
.ofLocalizedDateTime(style.toFormatStyle(), FormatStyle.SHORT)
.withLocale(Locale.getDefault())
.format(timestamp.toLocalDateTime())
actual fun formatDayOfWeek(timestamp: Long, style: TextStyle): String =
timestamp
.toLocalDateTime()
.dayOfWeek
.getDisplayName(
when (style) {
TextStyle.FULL -> java.time.format.TextStyle.FULL
TextStyle.SHORT -> java.time.format.TextStyle.SHORT
TextStyle.NARROW -> java.time.format.TextStyle.NARROW
},
Locale.getDefault()
)
actual fun formatDateTime(timestamp: Long, format: String): String =
timestamp.toLocalDateTime().format(DateTimeFormatter.ofPattern(format))

@ -0,0 +1,16 @@
package org.tasks.kmp.org.tasks.time
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.FormatStyle
fun Long.toLocalDateTime(): LocalDateTime =
LocalDateTime.ofInstant(Instant.ofEpochMilli(this), ZoneId.systemDefault())
fun DateStyle.toFormatStyle() = when (this) {
DateStyle.FULL -> FormatStyle.FULL
DateStyle.LONG -> FormatStyle.LONG
DateStyle.MEDIUM -> FormatStyle.MEDIUM
DateStyle.SHORT -> FormatStyle.SHORT
}
Loading…
Cancel
Save