diff --git a/CHANGELOG b/CHANGELOG index 0b1ce3f68..8c51a5fed 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ KeePassDX(4.1.0) * Generate keyfile #1290 * Hide template group #1894 * Group count sum recursively #421 + * Fix when selecting date fields #1695 * Small fixes #1711 #1831 #1780 #1821 #1863 #1889 KeePassDX(4.0.8) diff --git a/app/build.gradle b/app/build.gradle index 561308a84..c70ab51f2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -126,7 +126,7 @@ dependencies { // Autofill implementation "androidx.autofill:autofill:1.1.0" // Time - implementation 'joda-time:joda-time:2.10.13' + implementation 'joda-time:joda-time:2.13.0' // Color implementation 'com.github.Kunzisoft:AndroidClearChroma:2.6' // Education diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt index a1da94f6b..17b426cc4 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt @@ -30,7 +30,6 @@ import android.util.Log import android.view.Menu import android.view.MenuItem import android.view.View -import android.view.ViewGroup import android.widget.AdapterView import android.widget.ProgressBar import android.widget.Spinner @@ -73,6 +72,7 @@ import com.kunzisoft.keepass.database.element.template.Template import com.kunzisoft.keepass.education.EntryEditActivityEducation import com.kunzisoft.keepass.magikeyboard.MagikeyboardService import com.kunzisoft.keepass.model.AttachmentState +import com.kunzisoft.keepass.model.DataTime import com.kunzisoft.keepass.model.EntryAttachmentState import com.kunzisoft.keepass.model.RegisterInfo import com.kunzisoft.keepass.model.SearchInfo @@ -87,6 +87,7 @@ import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager import com.kunzisoft.keepass.timeout.TimeoutHelper +import com.kunzisoft.keepass.utils.TimeUtil.datePickerToDataDate import com.kunzisoft.keepass.utils.UriUtil.getDocumentFile import com.kunzisoft.keepass.utils.getParcelableExtraCompat import com.kunzisoft.keepass.view.ToolbarAction @@ -301,7 +302,7 @@ class EntryEditActivity : DatabaseLockActivity(), // Launch the time picker MaterialTimePicker.Builder().build().apply { addOnPositiveButtonClickListener { - mEntryEditViewModel.selectTime(this.hour, this.minute) + mEntryEditViewModel.selectTime(DataTime(this.hour, this.minute)) } show(supportFragmentManager, "TimePickerFragment") } @@ -309,7 +310,7 @@ class EntryEditActivity : DatabaseLockActivity(), // Launch the date picker MaterialDatePicker.Builder.datePicker().build().apply { addOnPositiveButtonClickListener { - mEntryEditViewModel.selectDate(it) + mEntryEditViewModel.selectDate(datePickerToDataDate(it)) } show(supportFragmentManager, "DatePickerFragment") } diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt index f2717a04a..19d963469 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt @@ -84,6 +84,7 @@ import com.kunzisoft.keepass.database.helper.SearchHelper import com.kunzisoft.keepass.database.search.SearchParameters import com.kunzisoft.keepass.education.GroupActivityEducation import com.kunzisoft.keepass.magikeyboard.MagikeyboardService +import com.kunzisoft.keepass.model.DataTime import com.kunzisoft.keepass.model.GroupInfo import com.kunzisoft.keepass.model.RegisterInfo import com.kunzisoft.keepass.model.SearchInfo @@ -96,6 +97,7 @@ import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.utils.BACK_PREVIOUS_KEYBOARD_ACTION import com.kunzisoft.keepass.utils.KeyboardUtil.showKeyboard +import com.kunzisoft.keepass.utils.TimeUtil.datePickerToDataDate import com.kunzisoft.keepass.utils.UriUtil.openUrl import com.kunzisoft.keepass.utils.getParcelableCompat import com.kunzisoft.keepass.utils.getParcelableExtraCompat @@ -440,7 +442,7 @@ class GroupActivity : DatabaseLockActivity(), // Launch the time picker MaterialTimePicker.Builder().build().apply { addOnPositiveButtonClickListener { - mGroupEditViewModel.selectTime(this.hour, this.minute) + mGroupEditViewModel.selectTime(DataTime(this.hour, this.minute)) } show(supportFragmentManager, "TimePickerFragment") } @@ -448,7 +450,7 @@ class GroupActivity : DatabaseLockActivity(), // Launch the date picker MaterialDatePicker.Builder.datePicker().build().apply { addOnPositiveButtonClickListener { - mGroupEditViewModel.selectDate(it) + mGroupEditViewModel.selectDate(datePickerToDataDate(it)) } show(supportFragmentManager, "DatePickerFragment") } diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/GroupEditDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/GroupEditDialogFragment.kt index 46894e1d5..58414ee3b 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/GroupEditDialogFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/GroupEditDialogFragment.kt @@ -45,7 +45,6 @@ import com.kunzisoft.keepass.view.InheritedCompletionView import com.kunzisoft.keepass.view.TagsCompletionView import com.kunzisoft.keepass.viewmodels.GroupEditViewModel import com.tokenautocomplete.FilteredArrayAdapter -import org.joda.time.DateTime class GroupEditDialogFragment : DatabaseDialogFragment() { @@ -90,27 +89,21 @@ class GroupEditDialogFragment : DatabaseDialogFragment() { mPopulateIconMethod?.invoke(iconButtonView, mGroupInfo.icon) } - mGroupEditViewModel.onDateSelected.observe(this) { dateMilliseconds -> + mGroupEditViewModel.onDateSelected.observe(this) { date -> // Save the date - mGroupInfo.expiryTime = DateInstant( - DateTime(mGroupInfo.expiryTime.date) - .withMillis(dateMilliseconds) - .toDate()) + mGroupInfo.expiryTime.setDate(date.year, date.month, date.day) expirationView.dateTime = mGroupInfo.expiryTime if (expirationView.dateTime.type == DateInstant.Type.DATE_TIME) { - val instantTime = DateInstant(mGroupInfo.expiryTime.date, DateInstant.Type.TIME) // Trick to recall selection with time - mGroupEditViewModel.requestDateTimeSelection(instantTime) + mGroupEditViewModel.requestDateTimeSelection( + DateInstant(mGroupInfo.expiryTime.instant, DateInstant.Type.TIME) + ) } } mGroupEditViewModel.onTimeSelected.observe(this) { viewModelTime -> // Save the time - mGroupInfo.expiryTime = DateInstant( - DateTime(mGroupInfo.expiryTime.date) - .withHourOfDay(viewModelTime.hours) - .withMinuteOfHour(viewModelTime.minutes) - .toDate(), mGroupInfo.expiryTime.type) + mGroupInfo.expiryTime.setTime(viewModelTime.hour, viewModelTime.minute) expirationView.dateTime = mGroupInfo.expiryTime } diff --git a/app/src/main/java/com/kunzisoft/keepass/autofill/AutofillHelper.kt b/app/src/main/java/com/kunzisoft/keepass/autofill/AutofillHelper.kt index 4fc0c399e..7af54b23f 100644 --- a/app/src/main/java/com/kunzisoft/keepass/autofill/AutofillHelper.kt +++ b/app/src/main/java/com/kunzisoft/keepass/autofill/AutofillHelper.kt @@ -176,8 +176,8 @@ object AutofillHelper { } if (entryInfo.expires) { - val year = entryInfo.expiryTime.getYearInt() - val month = entryInfo.expiryTime.getMonthInt() + val year = entryInfo.expiryTime.getYear() + val month = entryInfo.expiryTime.getMonth() val monthString = month.toString().padStart(2, '0') val day = entryInfo.expiryTime.getDay() val dayString = day.toString().padStart(2, '0') @@ -192,7 +192,7 @@ object AutofillHelper { } else { datasetBuilder.addValueToDatasetBuilder( it, - AutofillValue.forDate(entryInfo.expiryTime.date.time) + AutofillValue.forDate(entryInfo.expiryTime.toJavaMilliseconds()) ) } } diff --git a/app/src/main/java/com/kunzisoft/keepass/model/DataDate.kt b/app/src/main/java/com/kunzisoft/keepass/model/DataDate.kt new file mode 100644 index 000000000..efa877dd6 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/model/DataDate.kt @@ -0,0 +1,3 @@ +package com.kunzisoft.keepass.model + +data class DataDate(val year: Int, val month: Int, val day: Int) \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/model/DataTime.kt b/app/src/main/java/com/kunzisoft/keepass/model/DataTime.kt new file mode 100644 index 000000000..c0a8a30a5 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/model/DataTime.kt @@ -0,0 +1,3 @@ +package com.kunzisoft.keepass.model + +data class DataTime(val hour: Int, val minute: Int) \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/utils/TimeUtil.kt b/app/src/main/java/com/kunzisoft/keepass/utils/TimeUtil.kt index 29f2cc237..e86162b22 100644 --- a/app/src/main/java/com/kunzisoft/keepass/utils/TimeUtil.kt +++ b/app/src/main/java/com/kunzisoft/keepass/utils/TimeUtil.kt @@ -3,13 +3,17 @@ package com.kunzisoft.keepass.utils import android.content.res.Resources import androidx.core.os.ConfigurationCompat import com.kunzisoft.keepass.database.element.DateInstant +import com.kunzisoft.keepass.model.DataDate import java.text.DateFormat +import java.util.Calendar import java.util.Locale +import java.util.TimeZone object TimeUtil { fun DateInstant.getDateTimeString(resources: Resources): String { val locale = ConfigurationCompat.getLocales(resources.configuration)[0] ?: Locale.ROOT + val date = instant.toDate() return when (type) { DateInstant.Type.DATE -> DateFormat.getDateInstance( DateFormat.MEDIUM, @@ -26,4 +30,22 @@ object TimeUtil { .format(date) } } + + // https://github.com/material-components/material-components-android/issues/882#issuecomment-1111374962 + // To fix UTC time in date picker + fun datePickerToDataDate(millis: Long): DataDate { + val selectedUtc = Calendar.getInstance(TimeZone.getTimeZone("UTC")) + selectedUtc.timeInMillis = millis + val selectedLocal = Calendar.getInstance() + selectedLocal.clear() + selectedLocal.set( + selectedUtc.get(Calendar.YEAR), + selectedUtc.get(Calendar.MONTH), + selectedUtc.get(Calendar.DAY_OF_MONTH)) + return DataDate( + selectedLocal.get(Calendar.YEAR), + selectedLocal.get(Calendar.MONTH) + 1, + selectedLocal.get(Calendar.DAY_OF_MONTH), + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/view/DataTime.kt b/app/src/main/java/com/kunzisoft/keepass/view/DataTime.kt deleted file mode 100644 index cc88ff2bc..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/view/DataTime.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.kunzisoft.keepass.view - -data class DataTime(val hours: Int, val minutes: Int) \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/view/DateTimeEditFieldView.kt b/app/src/main/java/com/kunzisoft/keepass/view/DateTimeEditFieldView.kt index 43b6d9bb4..9346f9807 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/DateTimeEditFieldView.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/DateTimeEditFieldView.kt @@ -111,7 +111,7 @@ class DateTimeEditFieldView @JvmOverloads constructor(context: Context, mDefault } set(value) { - mDateTime = DateInstant(value.date, mDateTime.type) + mDateTime = DateInstant(value.instant, mDateTime.type) entryExpiresTextView.text = if (entryExpiresCheckBox.isChecked) { mDateTime.getDateTimeString(resources) } else { diff --git a/app/src/main/java/com/kunzisoft/keepass/view/DateTimeFieldView.kt b/app/src/main/java/com/kunzisoft/keepass/view/DateTimeFieldView.kt index 4eb0565c4..7b33e7079 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/DateTimeFieldView.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/DateTimeFieldView.kt @@ -128,7 +128,7 @@ class DateTimeFieldView @JvmOverloads constructor(context: Context, mDefault } set(value) { - mDateTime = DateInstant(value.date, mDateTime.type) + mDateTime = DateInstant(value.instant, mDateTime.type) assignExpiresDateText() } diff --git a/app/src/main/java/com/kunzisoft/keepass/view/TemplateEditView.kt b/app/src/main/java/com/kunzisoft/keepass/view/TemplateEditView.kt index 151a9a422..470e19a69 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/TemplateEditView.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/TemplateEditView.kt @@ -17,8 +17,9 @@ import com.kunzisoft.keepass.database.element.template.TemplateAttribute import com.kunzisoft.keepass.database.element.template.TemplateAttributeAction import com.kunzisoft.keepass.database.element.template.TemplateField import com.kunzisoft.keepass.database.helper.getLocalizedName +import com.kunzisoft.keepass.model.DataDate +import com.kunzisoft.keepass.model.DataTime import com.kunzisoft.keepass.otp.OtpEntryFields -import org.joda.time.DateTime class TemplateEditView @JvmOverloads constructor(context: Context, @@ -211,35 +212,31 @@ class TemplateEditView @JvmOverloads constructor(context: Context, val dateTimeView = getFieldViewById(viewId) if (dateTimeView is DateTimeEditFieldView) { dateTimeView.dateTime = DateInstant( - action.invoke(dateTimeView.dateTime).date, - dateTimeView.dateTime.type) + action.invoke(dateTimeView.dateTime).instant, + dateTimeView.dateTime.type + ) } } } - fun setCurrentDateTimeValue(dateMilliseconds: Long) { + fun setCurrentDateTimeValue(date: DataDate) { // Save the date - setCurrentDateTimeSelection { instant -> - val newDateInstant = DateInstant( - DateTime(instant.date) - .withMillis(dateMilliseconds) - .toDate(), instant.type) - if (instant.type == DateInstant.Type.DATE_TIME) { - val instantTime = DateInstant(instant.date, DateInstant.Type.TIME) + setCurrentDateTimeSelection { dateInstant -> + dateInstant.setDate(date.year, date.month, date.day) + if (dateInstant.type == DateInstant.Type.DATE_TIME) { // Trick to recall selection with time - mOnDateInstantClickListener?.invoke(instantTime) + mOnDateInstantClickListener?.invoke( + DateInstant(dateInstant.instant, DateInstant.Type.TIME) + ) } - newDateInstant + dateInstant } } fun setCurrentTimeValue(time: DataTime) { - setCurrentDateTimeSelection { instant -> - DateInstant( - DateTime(instant.date) - .withHourOfDay(time.hours) - .withMinuteOfHour(time.minutes) - .toDate(), instant.type) + setCurrentDateTimeSelection { dateInstant -> + dateInstant.setTime(time.hour, time.minute) + dateInstant } } diff --git a/app/src/main/java/com/kunzisoft/keepass/viewmodels/NodeEditViewModel.kt b/app/src/main/java/com/kunzisoft/keepass/viewmodels/NodeEditViewModel.kt index e649063c0..0ed8d504e 100644 --- a/app/src/main/java/com/kunzisoft/keepass/viewmodels/NodeEditViewModel.kt +++ b/app/src/main/java/com/kunzisoft/keepass/viewmodels/NodeEditViewModel.kt @@ -4,7 +4,8 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel import com.kunzisoft.keepass.database.element.DateInstant import com.kunzisoft.keepass.database.element.icon.IconImage -import com.kunzisoft.keepass.view.DataTime +import com.kunzisoft.keepass.model.DataDate +import com.kunzisoft.keepass.model.DataTime abstract class NodeEditViewModel : ViewModel() { @@ -23,8 +24,8 @@ abstract class NodeEditViewModel : ViewModel() { val requestDateTimeSelection : LiveData get() = _requestDateTimeSelection private val _requestDateTimeSelection = SingleLiveEvent() - val onDateSelected : LiveData get() = _onDateSelected - private val _onDateSelected = SingleLiveEvent() + val onDateSelected : LiveData get() = _onDateSelected + private val _onDateSelected = SingleLiveEvent() val onTimeSelected : LiveData get() = _onTimeSelected private val _onTimeSelected = SingleLiveEvent() @@ -57,12 +58,12 @@ abstract class NodeEditViewModel : ViewModel() { _requestDateTimeSelection.value = dateInstant } - fun selectDate(dateMilliseconds: Long) { - _onDateSelected.value = dateMilliseconds + fun selectDate(date: DataDate) { + _onDateSelected.value = date } - fun selectTime(hours: Int, minutes: Int) { - _onTimeSelected.value = DataTime(hours, minutes) + fun selectTime(dataTime: DataTime) { + _onTimeSelected.value = dataTime } private enum class ColorRequest { diff --git a/database/build.gradle b/database/build.gradle index b11df6359..f55d878b9 100644 --- a/database/build.gradle +++ b/database/build.gradle @@ -36,7 +36,7 @@ android { dependencies { // Time - implementation 'joda-time:joda-time:2.10.13' + implementation 'joda-time:joda-time:2.13.0' // Apache Commons implementation 'commons-io:commons-io:2.8.0' implementation 'commons-codec:commons-codec:1.15' diff --git a/database/src/main/java/com/kunzisoft/keepass/database/element/DateInstant.kt b/database/src/main/java/com/kunzisoft/keepass/database/element/DateInstant.kt index ad4c627ed..1b7e8d20b 100644 --- a/database/src/main/java/com/kunzisoft/keepass/database/element/DateInstant.kt +++ b/database/src/main/java/com/kunzisoft/keepass/database/element/DateInstant.kt @@ -21,27 +21,27 @@ package com.kunzisoft.keepass.database.element import android.os.Parcel import android.os.Parcelable -import com.kunzisoft.keepass.utils.readSerializableCompat import com.kunzisoft.keepass.utils.readEnum +import com.kunzisoft.keepass.utils.readSerializableCompat import com.kunzisoft.keepass.utils.writeEnum +import org.joda.time.DateTime +import org.joda.time.DateTimeZone import org.joda.time.Duration import org.joda.time.Instant import org.joda.time.LocalDate import org.joda.time.LocalDateTime import org.joda.time.LocalTime -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.Date -import java.util.Locale -import java.util.TimeZone +import org.joda.time.format.DateTimeFormat +import org.joda.time.format.DateTimeFormatter + class DateInstant : Parcelable { - private var jDate: Date = Date() + private var mInstant: Instant = Instant.now() private var mType: Type = Type.DATE_TIME - val date: Date - get() = jDate + val instant: Instant + get() = mInstant var type: Type get() = mType @@ -50,42 +50,37 @@ class DateInstant : Parcelable { } constructor(source: DateInstant) { - this.jDate = Date(source.jDate.time) + this.mInstant = Instant(source.mInstant) this.mType = source.mType } - constructor(date: Date, type: Type = Type.DATE_TIME) { - jDate = Date(date.time) + constructor(instant: Instant, type: Type = Type.DATE_TIME) { + mInstant = Instant(instant) mType = type } - constructor(millis: Long, type: Type = Type.DATE_TIME) { - jDate = Date(millis) - mType = type - } - - private fun parse(value: String, type: Type): Date { + private fun parse(value: String, type: Type): Instant { return when (type) { - Type.DATE -> dateFormat.parse(value) ?: jDate - Type.TIME -> timeFormat.parse(value) ?: jDate - else -> dateTimeFormat.parse(value) ?: jDate + Type.DATE -> Instant(dateFormat.parseDateTime(value) ?: DateTime()) + Type.TIME -> Instant(timeFormat.parseDateTime(value) ?: DateTime()) + else -> Instant(dateTimeFormat.parseDateTime(value) ?: DateTime()) } } constructor(string: String, type: Type = Type.DATE_TIME) { try { - jDate = parse(string, type) + mInstant = parse(string, type) mType = type } catch (e: Exception) { // Retry with second format try { when (type) { Type.TIME -> { - jDate = parse(string, Type.DATE) + mInstant = parse(string, Type.DATE) mType = Type.DATE } else -> { - jDate = parse(string, Type.TIME) + mInstant = parse(string, Type.TIME) mType = Type.TIME } } @@ -93,11 +88,11 @@ class DateInstant : Parcelable { // Retry with third format when (type) { Type.DATE, Type.TIME -> { - jDate = parse(string, Type.DATE_TIME) + mInstant = parse(string, Type.DATE_TIME) mType = Type.DATE_TIME } else -> { - jDate = parse(string, Type.DATE) + mInstant = parse(string, Type.DATE) mType = Type.DATE } } @@ -110,11 +105,11 @@ class DateInstant : Parcelable { } constructor() { - jDate = Date() + mInstant = Instant.now() } constructor(parcel: Parcel) { - jDate = parcel.readSerializableCompat() ?: jDate + mInstant = parcel.readSerializableCompat() ?: mInstant mType = parcel.readEnum() ?: mType } @@ -123,47 +118,82 @@ class DateInstant : Parcelable { } override fun writeToParcel(dest: Parcel, flags: Int) { - dest.writeSerializable(jDate) + dest.writeSerializable(mInstant) dest.writeEnum(mType) } - fun getYearInt(): Int { - val dateFormat = SimpleDateFormat("yyyy", Locale.ENGLISH) - return dateFormat.format(date).toInt() + fun setDate(year: Int, month: Int, day: Int) { + mInstant = DateTime(mInstant) + .withYear(year) + .withMonthOfYear(month) + .withDayOfMonth(day) + .toInstant() } - fun getMonthInt(): Int { - val dateFormat = SimpleDateFormat("MM", Locale.ENGLISH) - return dateFormat.format(date).toInt() + fun setTime(hour: Int, minute: Int) { + mInstant = DateTime(mInstant) + .withHourOfDay(hour) + .withMinuteOfHour(minute) + .toInstant() + } + + fun getYear(): Int { + return mInstant.toDateTime().year + } + + fun getMonth(): Int { + return mInstant.toDateTime().monthOfYear } fun getDay(): Int { - val dateFormat = SimpleDateFormat("dd", Locale.ENGLISH) - return dateFormat.format(date).toInt() + return mInstant.toDateTime().dayOfMonth + } + + fun getHour(): Int { + return mInstant.toDateTime().hourOfDay + } + + fun getMinute(): Int { + return mInstant.toDateTime().minuteOfHour + } + + fun getSecond(): Int { + return mInstant.toDateTime().secondOfMinute } // If expireDate is before NEVER_EXPIRE date less 1 month (to be sure) // it is not expires fun isNeverExpires(): Boolean { - return LocalDateTime(jDate) - .isBefore( - LocalDateTime.fromDateFields(NEVER_EXPIRES.date) - .minusMonths(1)) + return mInstant.isBefore(NEVER_EXPIRES.instant.minus(Duration.standardDays(30))) } fun isCurrentlyExpire(): Boolean { return when (type) { - Type.DATE -> LocalDate.fromDateFields(jDate).isBefore(LocalDate.now()) - Type.TIME -> LocalTime.fromDateFields(jDate).isBefore(LocalTime.now()) - else -> LocalDateTime.fromDateFields(jDate).isBefore(LocalDateTime.now()) + Type.DATE -> LocalDate.fromDateFields(mInstant.toDate()).isBefore(LocalDate.now()) + Type.TIME -> LocalTime.fromDateFields(mInstant.toDate()).isBefore(LocalTime.now()) + else -> LocalDateTime.fromDateFields(mInstant.toDate()).isBefore(LocalDateTime.now()) } } + fun toDotNetSeconds(): Long { + val duration = Duration(JAVA_EPOCH_DATE_TIME, mInstant) + val seconds = duration.millis / 1000L + return seconds + EPOCH_OFFSET + } + + fun toJavaMilliseconds(): Long { + return mInstant.millis + } + + fun toDateTimeSecondsFormat(): String { + return dateTimeSecondsFormat.print(mInstant) + } + override fun toString(): String { return when (type) { - Type.DATE -> dateFormat.format(jDate) - Type.TIME -> timeFormat.format(jDate) - else -> dateTimeFormat.format(jDate) + Type.DATE -> dateFormat.print(mInstant) + Type.TIME -> timeFormat.print(mInstant) + else -> dateTimeFormat.print(mInstant) } } @@ -171,47 +201,78 @@ class DateInstant : Parcelable { if (this === other) return true if (other !is DateInstant) return false - if (jDate != other.jDate) return false if (mType != other.mType) return false - + if (mType == Type.DATE || mType == Type.DATE_TIME) { + if (getYear() != other.getYear()) return false + if (getMonth() != other.getMonth()) return false + if (getDay() != other.getDay()) return false + if (getHour() != other.getHour()) return false + } + if (mType == Type.TIME || mType == Type.DATE_TIME) { + if (getMinute() != other.getMinute()) return false + if (getSecond() != other.getSecond()) return false + } return true } override fun hashCode(): Int { - var result = jDate.hashCode() + var result = mInstant.hashCode() result = 31 * result + mType.hashCode() return result } + fun isBefore(dateInstant: DateInstant): Boolean { + return this.mInstant.isBefore(dateInstant.mInstant) + } + + fun isAfter(dateInstant: DateInstant): Boolean { + return this.mInstant.isAfter(dateInstant.mInstant) + } + + fun compareTo(other: DateInstant?): Int { + return mInstant.compareTo(other?.mInstant) + } + enum class Type { DATE_TIME, DATE, TIME } companion object { - val NEVER_EXPIRES = DateInstant(Calendar.getInstance().apply { - set(Calendar.YEAR, 2999) - set(Calendar.MONTH, 11) - set(Calendar.DAY_OF_MONTH, 28) - set(Calendar.HOUR, 23) - set(Calendar.MINUTE, 59) - set(Calendar.SECOND, 59) - }.time) - val IN_ONE_MONTH_DATE_TIME = DateInstant( - Instant.now().plus(Duration.standardDays(30)).toDate(), Type.DATE_TIME) - val IN_ONE_MONTH_DATE = DateInstant( - Instant.now().plus(Duration.standardDays(30)).toDate(), Type.DATE) - val IN_ONE_HOUR_TIME = DateInstant( - Instant.now().plus(Duration.standardHours(1)).toDate(), Type.TIME) + private val DOT_NET_EPOCH_DATE_TIME = DateTime(1, 1, 1, 0, 0, 0, DateTimeZone.UTC) + private val JAVA_EPOCH_DATE_TIME = DateTime(1970, 1, 1, 0, 0, 0, DateTimeZone.UTC) + private val EPOCH_OFFSET = (JAVA_EPOCH_DATE_TIME.millis - DOT_NET_EPOCH_DATE_TIME.millis) / 1000L + private val NEVER_EXPIRES_DATE_TIME = DateTime(2999, 11, 28, 23, 59, 59, DateTimeZone.UTC) - private val dateTimeFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'", Locale.ROOT).apply { - timeZone = TimeZone.getTimeZone("UTC") + val NEVER_EXPIRES = DateInstant(NEVER_EXPIRES_DATE_TIME.toInstant()) + val IN_ONE_MONTH_DATE_TIME = DateInstant( + Instant.now().plus(Duration.standardDays(30)), Type.DATE_TIME) + val IN_ONE_MONTH_DATE = DateInstant( + Instant.now().plus(Duration.standardDays(30)), Type.DATE) + val IN_ONE_HOUR_TIME = DateInstant( + Instant.now().plus(Duration.standardHours(1)), Type.TIME) + + val dateTimeSecondsFormat: DateTimeFormatter = + DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss'Z'") + .withZoneUTC() + var dateTimeFormat: DateTimeFormatter = + DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm'Z'") + .withZoneUTC() + var dateFormat: DateTimeFormatter = + DateTimeFormat.forPattern("yyyy-MM-dd'Z'") + .withZoneUTC() + var timeFormat: DateTimeFormatter = + DateTimeFormat.forPattern("HH:mm'Z'") + .withZoneUTC() + + fun fromDotNetSeconds(seconds: Long): DateInstant { + val dt = DOT_NET_EPOCH_DATE_TIME.plus(seconds * 1000L) + // Switch corrupted dates to a more recent date that won't cause issues on the client + return DateInstant((if (dt.isBefore(JAVA_EPOCH_DATE_TIME)) { JAVA_EPOCH_DATE_TIME } else dt).toInstant()) } - private val dateFormat = SimpleDateFormat("yyyy-MM-dd'Z'", Locale.ROOT).apply { - timeZone = TimeZone.getTimeZone("UTC") - } - private val timeFormat = SimpleDateFormat("HH:mm'Z'", Locale.ROOT).apply { - timeZone = TimeZone.getTimeZone("UTC") + + fun fromDateTimeSecondsFormat(value: String): DateInstant { + return DateInstant(dateTimeSecondsFormat.parseDateTime(value).toInstant()) } @JvmField diff --git a/database/src/main/java/com/kunzisoft/keepass/database/element/SortNodeEnum.kt b/database/src/main/java/com/kunzisoft/keepass/database/element/SortNodeEnum.kt index 97535141a..4358cf9b1 100644 --- a/database/src/main/java/com/kunzisoft/keepass/database/element/SortNodeEnum.kt +++ b/database/src/main/java/com/kunzisoft/keepass/database/element/SortNodeEnum.kt @@ -23,7 +23,6 @@ package com.kunzisoft.keepass.database.element import com.kunzisoft.keepass.database.element.group.GroupVersionedInterface import com.kunzisoft.keepass.database.element.node.NodeVersionedInterface import com.kunzisoft.keepass.database.element.node.Type -import java.util.* enum class SortNodeEnum { DB, TITLE, USERNAME, CREATION_TIME, LAST_MODIFY_TIME, LAST_ACCESS_TIME; @@ -173,8 +172,8 @@ enum class SortNodeEnum { ) : NodeComparator(database, sortNodeParameters) { override fun compareBySpecificOrder(object1: T, object2: T): Int { - val creationCompare = object1.creationTime.date - .compareTo(object2.creationTime.date) + val creationCompare = object1.creationTime + .compareTo(object2.creationTime) return if (creationCompare == 0) NodeNaturalComparator(database, sortNodeParameters) .compare(object1, object2) @@ -192,8 +191,8 @@ enum class SortNodeEnum { ) : NodeComparator(database, sortNodeParameters) { override fun compareBySpecificOrder(object1: T, object2: T): Int { - val lastModificationCompare = object1.lastModificationTime.date - .compareTo(object2.lastModificationTime.date) + val lastModificationCompare = object1.lastModificationTime + .compareTo(object2.lastModificationTime) return if (lastModificationCompare == 0) NodeNaturalComparator(database, sortNodeParameters) .compare(object1, object2) @@ -211,8 +210,8 @@ enum class SortNodeEnum { ) : NodeComparator(database, sortNodeParameters) { override fun compareBySpecificOrder(object1: T, object2: T): Int { - val lastAccessCompare = object1.lastAccessTime.date - .compareTo(object2.lastAccessTime.date) + val lastAccessCompare = object1.lastAccessTime + .compareTo(object2.lastAccessTime) return if (lastAccessCompare == 0) NodeNaturalComparator(database, sortNodeParameters) .compare(object1, object2) diff --git a/database/src/main/java/com/kunzisoft/keepass/database/element/entry/EntryKDBX.kt b/database/src/main/java/com/kunzisoft/keepass/database/element/entry/EntryKDBX.kt index 8045a7ef8..69e3b8a6e 100644 --- a/database/src/main/java/com/kunzisoft/keepass/database/element/entry/EntryKDBX.kt +++ b/database/src/main/java/com/kunzisoft/keepass/database/element/entry/EntryKDBX.kt @@ -36,13 +36,12 @@ import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.database.element.security.ProtectedString +import com.kunzisoft.keepass.utils.UnsignedLong import com.kunzisoft.keepass.utils.readParcelableCompat import com.kunzisoft.keepass.utils.readStringIntMap import com.kunzisoft.keepass.utils.readStringParcelableMap import com.kunzisoft.keepass.utils.writeStringIntMap import com.kunzisoft.keepass.utils.writeStringParcelableMap -import com.kunzisoft.keepass.utils.UnsignedLong -import java.util.Date import java.util.UUID class EntryKDBX : EntryVersioned, NodeKDBXInterface { @@ -363,18 +362,16 @@ class EntryKDBX : EntryVersioned, NodeKDBXInte } fun removeOldestEntryFromHistory(): EntryKDBX? { - var min: Date? = null + var min: DateInstant? = null var index = -1 - for (i in history.indices) { val entry = history[i] - val lastMod = entry.lastModificationTime.date - if (min == null || lastMod.before(min)) { + val lastModification = entry.lastModificationTime + if (min == null || lastModification.isBefore(min)) { index = i - min = lastMod + min = lastModification } } - return if (index != -1) { history.removeAt(index) } else null diff --git a/database/src/main/java/com/kunzisoft/keepass/database/file/DatabaseKDBXXML.kt b/database/src/main/java/com/kunzisoft/keepass/database/file/DatabaseKDBXXML.kt index a9548a1e2..8cfdcbd39 100644 --- a/database/src/main/java/com/kunzisoft/keepass/database/file/DatabaseKDBXXML.kt +++ b/database/src/main/java/com/kunzisoft/keepass/database/file/DatabaseKDBXXML.kt @@ -19,9 +19,6 @@ */ package com.kunzisoft.keepass.database.file -import java.text.SimpleDateFormat -import java.util.* - object DatabaseKDBXXML { const val ElemDocNode = "KeePassFile" @@ -128,8 +125,4 @@ object DatabaseKDBXXML { const val ElemCustomData = "CustomData" const val ElemStringDictExItem = "Item" - - val DateFormatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ROOT).apply { - timeZone = TimeZone.getTimeZone("UTC") - } } diff --git a/database/src/main/java/com/kunzisoft/keepass/database/file/DateKDBXUtil.kt b/database/src/main/java/com/kunzisoft/keepass/database/file/DateKDBXUtil.kt deleted file mode 100644 index cec17db10..000000000 --- a/database/src/main/java/com/kunzisoft/keepass/database/file/DateKDBXUtil.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2019 Jeremy Jamet / Kunzisoft. - * - * This file is part of KeePassDX. - * - * KeePassDX is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * KeePassDX is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with KeePassDX. If not, see . - * - */ -package com.kunzisoft.keepass.database.file - -import org.joda.time.DateTime -import org.joda.time.DateTimeZone -import org.joda.time.Duration -import java.util.* - -object DateKDBXUtil { - - private val dotNetEpoch = DateTime(1, 1, 1, 0, 0, 0, DateTimeZone.UTC) - private val javaEpoch = DateTime(1970, 1, 1, 0, 0, 0, DateTimeZone.UTC) - private val epochOffset = (javaEpoch.millis - dotNetEpoch.millis) / 1000L - - fun convertKDBX4Time(seconds: Long): Date { - val dt = dotNetEpoch.plus(seconds * 1000L) - // Switch corrupted dates to a more recent date that won't cause issues on the client - return if (dt.isBefore(javaEpoch)) { - javaEpoch.toDate() - } else dt.toDate() - } - - fun convertDateToKDBX4Time(date: Date): Long { - val duration = Duration(javaEpoch, DateTime(date)) - val seconds = duration.millis / 1000L - return seconds + epochOffset - } -} \ No newline at end of file diff --git a/database/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDB.kt b/database/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDB.kt index c4ff17da9..a310d1507 100644 --- a/database/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDB.kt +++ b/database/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDB.kt @@ -165,7 +165,7 @@ class DatabaseInputKDB(database: DatabaseKDB) } 0x0003 -> { newGroup?.let { group -> - group.creationTime = DateInstant(cipherInputStream.readBytes5ToDate()) + group.creationTime = cipherInputStream.readBytes5ToDate() } ?: newEntry?.let { entry -> var iconId = cipherInputStream.readBytes4ToUInt().toKotlinInt() @@ -178,7 +178,7 @@ class DatabaseInputKDB(database: DatabaseKDB) } 0x0004 -> { newGroup?.let { group -> - group.lastModificationTime = DateInstant(cipherInputStream.readBytes5ToDate()) + group.lastModificationTime = cipherInputStream.readBytes5ToDate() } ?: newEntry?.let { entry -> entry.title = cipherInputStream.readBytesToString(fieldSize) @@ -186,7 +186,7 @@ class DatabaseInputKDB(database: DatabaseKDB) } 0x0005 -> { newGroup?.let { group -> - group.lastAccessTime = DateInstant(cipherInputStream.readBytes5ToDate()) + group.lastAccessTime = cipherInputStream.readBytes5ToDate() } ?: newEntry?.let { entry -> entry.url = cipherInputStream.readBytesToString(fieldSize) @@ -194,7 +194,7 @@ class DatabaseInputKDB(database: DatabaseKDB) } 0x0006 -> { newGroup?.let { group -> - group.expiryTime = DateInstant(cipherInputStream.readBytes5ToDate()) + group.expiryTime = cipherInputStream.readBytes5ToDate() } ?: newEntry?.let { entry -> entry.username = cipherInputStream.readBytesToString(fieldSize) @@ -221,22 +221,22 @@ class DatabaseInputKDB(database: DatabaseKDB) group.groupFlags = cipherInputStream.readBytes4ToUInt().toKotlinInt() } ?: newEntry?.let { entry -> - entry.creationTime = DateInstant(cipherInputStream.readBytes5ToDate()) + entry.creationTime = cipherInputStream.readBytes5ToDate() } } 0x000A -> { newEntry?.let { entry -> - entry.lastModificationTime = DateInstant(cipherInputStream.readBytes5ToDate()) + entry.lastModificationTime = cipherInputStream.readBytes5ToDate() } } 0x000B -> { newEntry?.let { entry -> - entry.lastAccessTime = DateInstant(cipherInputStream.readBytes5ToDate()) + entry.lastAccessTime = cipherInputStream.readBytes5ToDate() } } 0x000C -> { newEntry?.let { entry -> - entry.expiryTime = DateInstant(cipherInputStream.readBytes5ToDate()) + entry.expiryTime = cipherInputStream.readBytes5ToDate() } } 0x000D -> { diff --git a/database/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt b/database/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt index ba96188ef..09d2be6d8 100644 --- a/database/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt +++ b/database/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt @@ -42,7 +42,6 @@ import com.kunzisoft.keepass.database.exception.* import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_40 import com.kunzisoft.keepass.database.file.DatabaseKDBXXML -import com.kunzisoft.keepass.database.file.DateKDBXUtil import com.kunzisoft.keepass.stream.HashedBlockInputStream import com.kunzisoft.keepass.stream.HmacBlockInputStream import com.kunzisoft.keepass.tasks.ProgressTaskUpdater @@ -827,11 +826,10 @@ class DatabaseInputKDBX(database: DatabaseKDBX) @Throws(IOException::class, XmlPullParserException::class) private fun readDateInstant(xpp: XmlPullParser): DateInstant { val sDate = readString(xpp) - var utcDate: Date? = null - + var utcDate = DateInstant() if (mDatabase.kdbxVersion.isBefore(FILE_VERSION_40)) { try { - utcDate = DatabaseKDBXXML.DateFormatter.parse(sDate) + utcDate = DateInstant.fromDateTimeSecondsFormat(sDate) } catch (e: ParseException) { // Catch with null test below } @@ -842,12 +840,10 @@ class DatabaseInputKDBX(database: DatabaseKDBX) System.arraycopy(buf, 0, buf8, 0, min(buf.size, 8)) buf = buf8 } - val seconds = bytes64ToLong(buf) - utcDate = DateKDBXUtil.convertKDBX4Time(seconds) + utcDate = DateInstant.fromDotNetSeconds(seconds) } - - return DateInstant(utcDate ?: Date(0L)) + return utcDate } @Throws(IOException::class, XmlPullParserException::class) diff --git a/database/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDBX.kt b/database/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDBX.kt index 423024e34..f6eb8a403 100644 --- a/database/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDBX.kt +++ b/database/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDBX.kt @@ -41,7 +41,6 @@ import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_40 import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_41 import com.kunzisoft.keepass.database.file.DatabaseKDBXXML -import com.kunzisoft.keepass.database.file.DateKDBXUtil import com.kunzisoft.keepass.stream.HashedBlockOutputStream import com.kunzisoft.keepass.stream.HmacBlockOutputStream import com.kunzisoft.keepass.utils.* @@ -411,14 +410,15 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX) } @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeDateInstant(name: String, value: DateInstant) { - val date = value.date + private fun writeDateInstant(name: String, date: DateInstant) { if (header!!.version.isBefore(FILE_VERSION_40)) { - writeString(name, DatabaseKDBXXML.DateFormatter.format(date)) + writeString(name, date.toDateTimeSecondsFormat()) } else { - val buf = longTo8Bytes(DateKDBXUtil.convertDateToKDBX4Time(date)) - val b64 = String(Base64.encode(buf, BASE64_FLAG)) - writeString(name, b64) + writeString(name, String( + Base64.encode( + longTo8Bytes(date.toDotNetSeconds()), BASE64_FLAG) + ) + ) } } diff --git a/database/src/main/java/com/kunzisoft/keepass/database/file/output/EntryOutputKDB.kt b/database/src/main/java/com/kunzisoft/keepass/database/file/output/EntryOutputKDB.kt index 2e4508124..2d20e2a0e 100644 --- a/database/src/main/java/com/kunzisoft/keepass/database/file/output/EntryOutputKDB.kt +++ b/database/src/main/java/com/kunzisoft/keepass/database/file/output/EntryOutputKDB.kt @@ -75,16 +75,16 @@ class EntryOutputKDB(private val mDatabase: DatabaseKDB, writeStringToStream(mOutputStream, mEntry.notes) // Create date - writeDate(CREATE_FIELD_TYPE, dateTo5Bytes(mEntry.creationTime.date)) + writeDate(CREATE_FIELD_TYPE, dateTo5Bytes(mEntry.creationTime)) // Modification date - writeDate(MOD_FIELD_TYPE, dateTo5Bytes(mEntry.lastModificationTime.date)) + writeDate(MOD_FIELD_TYPE, dateTo5Bytes(mEntry.lastModificationTime)) // Access date - writeDate(ACCESS_FIELD_TYPE, dateTo5Bytes(mEntry.lastAccessTime.date)) + writeDate(ACCESS_FIELD_TYPE, dateTo5Bytes(mEntry.lastAccessTime)) // Expiration date - writeDate(EXPIRE_FIELD_TYPE, dateTo5Bytes(mEntry.expiryTime.date)) + writeDate(EXPIRE_FIELD_TYPE, dateTo5Bytes(mEntry.expiryTime)) // Binary description mOutputStream.write(BINARY_DESC_FIELD_TYPE) diff --git a/database/src/main/java/com/kunzisoft/keepass/database/file/output/GroupOutputKDB.kt b/database/src/main/java/com/kunzisoft/keepass/database/file/output/GroupOutputKDB.kt index 9a2e62c03..481d9f18b 100644 --- a/database/src/main/java/com/kunzisoft/keepass/database/file/output/GroupOutputKDB.kt +++ b/database/src/main/java/com/kunzisoft/keepass/database/file/output/GroupOutputKDB.kt @@ -48,22 +48,22 @@ class GroupOutputKDB(private val mGroup: GroupKDB, // Create date mOutputStream.write(CREATE_FIELD_TYPE) mOutputStream.write(DATE_FIELD_SIZE) - mOutputStream.write(dateTo5Bytes(mGroup.creationTime.date)) + mOutputStream.write(dateTo5Bytes(mGroup.creationTime)) // Modification date mOutputStream.write(MOD_FIELD_TYPE) mOutputStream.write(DATE_FIELD_SIZE) - mOutputStream.write(dateTo5Bytes(mGroup.lastModificationTime.date)) + mOutputStream.write(dateTo5Bytes(mGroup.lastModificationTime)) // Access date mOutputStream.write(ACCESS_FIELD_TYPE) mOutputStream.write(DATE_FIELD_SIZE) - mOutputStream.write(dateTo5Bytes(mGroup.lastAccessTime.date)) + mOutputStream.write(dateTo5Bytes(mGroup.lastAccessTime)) // Expiration date mOutputStream.write(EXPIRE_FIELD_TYPE) mOutputStream.write(DATE_FIELD_SIZE) - mOutputStream.write(dateTo5Bytes(mGroup.expiryTime.date)) + mOutputStream.write(dateTo5Bytes(mGroup.expiryTime)) // Image ID mOutputStream.write(IMAGEID_FIELD_TYPE) diff --git a/database/src/main/java/com/kunzisoft/keepass/database/merge/DatabaseKDBXMerger.kt b/database/src/main/java/com/kunzisoft/keepass/database/merge/DatabaseKDBXMerger.kt index 0196f4f0d..1f83169ee 100644 --- a/database/src/main/java/com/kunzisoft/keepass/database/merge/DatabaseKDBXMerger.kt +++ b/database/src/main/java/com/kunzisoft/keepass/database/merge/DatabaseKDBXMerger.kt @@ -187,34 +187,34 @@ class DatabaseKDBXMerger(private var database: DatabaseKDBX) { fun merge(databaseToMerge: DatabaseKDBX) { // Merge settings - if (database.nameChanged.date.before(databaseToMerge.nameChanged.date)) { + if (database.nameChanged.isBefore(databaseToMerge.nameChanged)) { database.name = databaseToMerge.name database.nameChanged = databaseToMerge.nameChanged } - if (database.descriptionChanged.date.before(databaseToMerge.descriptionChanged.date)) { + if (database.descriptionChanged.isBefore(databaseToMerge.descriptionChanged)) { database.description = databaseToMerge.description database.descriptionChanged = databaseToMerge.descriptionChanged } - if (database.defaultUserNameChanged.date.before(databaseToMerge.defaultUserNameChanged.date)) { + if (database.defaultUserNameChanged.isBefore(databaseToMerge.defaultUserNameChanged)) { database.defaultUserName = databaseToMerge.defaultUserName database.defaultUserNameChanged = databaseToMerge.defaultUserNameChanged } - if (database.keyLastChanged.date.before(databaseToMerge.keyLastChanged.date)) { + if (database.keyLastChanged.isBefore(databaseToMerge.keyLastChanged)) { database.keyChangeRecDays = databaseToMerge.keyChangeRecDays database.keyChangeForceDays = databaseToMerge.keyChangeForceDays database.isKeyChangeForceOnce = databaseToMerge.isKeyChangeForceOnce database.keyLastChanged = databaseToMerge.keyLastChanged } - if (database.recycleBinChanged.date.before(databaseToMerge.recycleBinChanged.date)) { + if (database.recycleBinChanged.isBefore(databaseToMerge.recycleBinChanged)) { database.isRecycleBinEnabled = databaseToMerge.isRecycleBinEnabled database.recycleBinUUID = databaseToMerge.recycleBinUUID database.recycleBinChanged = databaseToMerge.recycleBinChanged } - if (database.entryTemplatesGroupChanged.date.before(databaseToMerge.entryTemplatesGroupChanged.date)) { + if (database.entryTemplatesGroupChanged.isBefore(databaseToMerge.entryTemplatesGroupChanged)) { database.entryTemplatesGroup = databaseToMerge.entryTemplatesGroup database.entryTemplatesGroupChanged = databaseToMerge.entryTemplatesGroupChanged } - if (database.settingsChanged.date.before(databaseToMerge.settingsChanged.date)) { + if (database.settingsChanged.isBefore(databaseToMerge.settingsChanged)) { database.color = databaseToMerge.color database.compressionAlgorithm = databaseToMerge.compressionAlgorithm database.historyMaxItems = databaseToMerge.historyMaxItems @@ -245,8 +245,7 @@ class DatabaseKDBXMerger(private var database: DatabaseKDBX) { } // Merge root group - if (rootGroup.lastModificationTime.date - .before(rootGroupToMerge.lastModificationTime.date)) { + if (rootGroup.lastModificationTime.isBefore(rootGroupToMerge.lastModificationTime)) { rootGroup.updateWith(rootGroupToMerge, updateParents = false) } // Merge children @@ -293,7 +292,7 @@ class DatabaseKDBXMerger(private var database: DatabaseKDBX) { val customIconToMerge = databaseToMerge.iconsManager.getIcon(customIconUuid) val customIconModificationToMerge = customIconToMerge?.lastModificationTime if (customIconModification != null && customIconModificationToMerge != null) { - if (customIconModification.date.before(customIconModificationToMerge.date)) { + if (customIconModification.isBefore(customIconModificationToMerge)) { customIcon.updateWith(customIconToMerge) } } else if (customIconModificationToMerge != null) { @@ -310,19 +309,17 @@ class DatabaseKDBXMerger(private var database: DatabaseKDBX) { val databaseIcon = database.iconsManager.getIcon(deletedObjectId) val databaseIconModificationTime = databaseIcon?.lastModificationTime if (databaseEntry != null - && deletedObject.deletionTime.date - .after(databaseEntry.lastModificationTime.date)) { + && deletedObject.deletionTime.isAfter(databaseEntry.lastModificationTime)) { database.removeEntryFrom(databaseEntry, databaseEntry.parent) } if (databaseGroup != null - && deletedObject.deletionTime.date - .after(databaseGroup.lastModificationTime.date)) { + && deletedObject.deletionTime.isAfter(databaseGroup.lastModificationTime)) { database.removeGroupFrom(databaseGroup, databaseGroup.parent) } if (databaseIcon != null && ( databaseIconModificationTime == null - || (deletedObject.deletionTime.date.after(databaseIconModificationTime.date)) + || (deletedObject.deletionTime.isAfter(databaseIconModificationTime)) ) ) { database.removeCustomIcon(deletedObjectId) @@ -343,8 +340,7 @@ class DatabaseKDBXMerger(private var database: DatabaseKDBX) { val customDataItemModification = customDataItem.lastModificationTime val customDataItemToMergeModification = customDataItemToMerge.lastModificationTime if (customDataItemModification != null && customDataItemToMergeModification != null) { - if (customDataItemModification.date - .before(customDataItemToMergeModification.date)) { + if (customDataItemModification.isBefore(customDataItemToMergeModification)) { customData.put(customDataItemToMerge) } } else { @@ -399,8 +395,7 @@ class DatabaseKDBXMerger(private var database: DatabaseKDBX) { // If it's a deleted object, but another instance was updated // If entry parent to add exists and in current database if ((deletedObject == null - || deletedObject.deletionTime.date - .before(entryToMerge.lastModificationTime.date)) + || deletedObject.deletionTime.isBefore(entryToMerge.lastModificationTime)) && parentEntryToMerge != null) { database.addEntryTo(entryToMerge, parentEntryToMerge) } @@ -408,8 +403,7 @@ class DatabaseKDBXMerger(private var database: DatabaseKDBX) { // Merge independently custom data mergeCustomData(entry.customData, entryToMerge.customData) // Merge by modification time - if (entry.lastModificationTime.date - .before(entryToMerge.lastModificationTime.date) + if (entry.lastModificationTime.isBefore(entryToMerge.lastModificationTime) ) { addHistory(entry, entryToMerge) if (parentEntryToMerge == entry.parent) { @@ -421,8 +415,7 @@ class DatabaseKDBXMerger(private var database: DatabaseKDBX) { database.addEntryTo(entryToMerge, parentEntryToMerge) } } - } else if (entry.lastModificationTime.date - .after(entryToMerge.lastModificationTime.date) + } else if (entry.lastModificationTime.isAfter(entryToMerge.lastModificationTime) ) { addHistory(entryToMerge, entry) } @@ -477,8 +470,7 @@ class DatabaseKDBXMerger(private var database: DatabaseKDBX) { if (group == null) { // If group parent to add exists and in current database if ((deletedObject == null - || deletedObject.deletionTime.date - .before(groupToMerge.lastModificationTime.date)) + || deletedObject.deletionTime.isBefore(groupToMerge.lastModificationTime)) && parentGroupToMerge != null) { database.addGroupTo(groupToMerge, parentGroupToMerge) } @@ -486,8 +478,7 @@ class DatabaseKDBXMerger(private var database: DatabaseKDBX) { // Merge independently custom data mergeCustomData(group.customData, groupToMerge.customData) // Merge by modification time - if (group.lastModificationTime.date - .before(groupToMerge.lastModificationTime.date) + if (group.lastModificationTime.isBefore(groupToMerge.lastModificationTime) ) { if (parentGroupToMerge == group.parent) { group.updateWith(groupToMerge, false) diff --git a/database/src/main/java/com/kunzisoft/keepass/model/EntryInfo.kt b/database/src/main/java/com/kunzisoft/keepass/model/EntryInfo.kt index 4dfe323b4..463e24841 100644 --- a/database/src/main/java/com/kunzisoft/keepass/model/EntryInfo.kt +++ b/database/src/main/java/com/kunzisoft/keepass/model/EntryInfo.kt @@ -232,7 +232,7 @@ class EntryInfo : NodeInfo { } creditCard?.expiration?.let { expires = true - expiryTime = DateInstant(creditCard.expiration.millis) + expiryTime = DateInstant(creditCard.expiration.toInstant()) } creditCard?.number?.let { addUniqueField(Field(TemplateField.LABEL_NUMBER, ProtectedString(false, it))) diff --git a/database/src/main/java/com/kunzisoft/keepass/utils/StreamBytesUtils.kt b/database/src/main/java/com/kunzisoft/keepass/utils/StreamBytesUtils.kt index 223ef9fd6..4cad18242 100644 --- a/database/src/main/java/com/kunzisoft/keepass/utils/StreamBytesUtils.kt +++ b/database/src/main/java/com/kunzisoft/keepass/utils/StreamBytesUtils.kt @@ -19,6 +19,9 @@ */ package com.kunzisoft.keepass.utils +import com.kunzisoft.keepass.database.element.DateInstant +import org.joda.time.DateTime +import org.joda.time.Instant import java.io.IOException import java.io.InputStream import java.io.OutputStream @@ -93,7 +96,7 @@ fun InputStream.readBytes2ToUShort(): Int { } @Throws(IOException::class) -fun InputStream.readBytes5ToDate(): Date { +fun InputStream.readBytes5ToDate(): DateInstant { return bytes5ToDate(readBytesLength(5)) } @@ -211,7 +214,7 @@ fun bytes16ToUuid(buf: ByteArray): UUID { * Unpack date from 5 byte format. The five bytes at 'offset' are unpacked * to a java.util.Date instance. */ -fun bytes5ToDate(buf: ByteArray, calendar: Calendar = Calendar.getInstance()): Date { +fun bytes5ToDate(buf: ByteArray): DateInstant { val dateSize = 5 val cDate = ByteArray(dateSize) System.arraycopy(buf, 0, cDate, 0, dateSize) @@ -234,9 +237,14 @@ fun bytes5ToDate(buf: ByteArray, calendar: Calendar = Calendar.getInstance()): D // File format is a 1 based month, java Calendar uses a zero based month // File format is a 1 based day, java Calendar uses a 1 based day - calendar.set(year, month - 1, day, hour, minute, second) - - return calendar.time + return DateInstant(Instant.ofEpochMilli(DateTime( + year, + month - 1, + day, + hour, + minute, + second + ).millis)) } @@ -284,19 +292,17 @@ fun uuidTo16Bytes(uuid: UUID): ByteArray { return buf } -fun dateTo5Bytes(date: Date, calendar: Calendar = Calendar.getInstance()): ByteArray { - val buf = ByteArray(5) - calendar.time = date - - val year = calendar.get(Calendar.YEAR) +fun dateTo5Bytes(dateInstant: DateInstant): ByteArray { + val year = dateInstant.getYear() // File format is a 1 based month, java Calendar uses a zero based month - val month = calendar.get(Calendar.MONTH) + 1 + val month = dateInstant.getMonth() + 1 // File format is a 1 based day, java Calendar uses a 1 based day - val day = calendar.get(Calendar.DAY_OF_MONTH) - val hour = calendar.get(Calendar.HOUR_OF_DAY) - val minute = calendar.get(Calendar.MINUTE) - val second = calendar.get(Calendar.SECOND) + val day = dateInstant.getDay() + val hour = dateInstant.getHour() + val minute = dateInstant.getMinute() + val second = dateInstant.getSecond() + val buf = ByteArray(5) buf[0] = UnsignedInt(year shr 6 and 0x0000003F).toKotlinByte() buf[1] = UnsignedInt(year and 0x0000003F shl 2 or (month shr 2 and 0x00000003)).toKotlinByte() buf[2] = (month and 0x00000003 shl 6 diff --git a/database/src/test/java/com/kunzisoft/keepass/tests/utils/ValuesTest.kt b/database/src/test/java/com/kunzisoft/keepass/tests/utils/ValuesTest.kt index 68d91fcac..3471febe4 100644 --- a/database/src/test/java/com/kunzisoft/keepass/tests/utils/ValuesTest.kt +++ b/database/src/test/java/com/kunzisoft/keepass/tests/utils/ValuesTest.kt @@ -22,6 +22,8 @@ package com.kunzisoft.keepass.tests.utils import com.kunzisoft.keepass.database.element.DateInstant import com.kunzisoft.keepass.utils.* import junit.framework.TestCase +import org.joda.time.DateTime +import org.joda.time.Instant import org.junit.Assert.assertArrayEquals import java.io.ByteArrayOutputStream import java.util.* @@ -136,24 +138,29 @@ class ValuesTest : TestCase() { } fun testDate() { - val cal = Calendar.getInstance() + val expected = DateInstant( + Instant.ofEpochMilli( + DateTime( + 2008, + 1, + 2, + 3, + 4, + 5 + ).millis)) - val expected = Calendar.getInstance() - expected.set(2008, 1, 2, 3, 4, 5) + val actual = DateInstant(bytes5ToDate(dateTo5Bytes(expected))) - val actual = Calendar.getInstance() - actual.time = DateInstant(bytes5ToDate(dateTo5Bytes(expected.time, cal), cal)).date - - val jDate = DateInstant(System.currentTimeMillis()) + val jDate = DateInstant() val intermediate = DateInstant(jDate) - val cDate = DateInstant(bytes5ToDate(dateTo5Bytes(intermediate.date))) + val cDate = DateInstant(bytes5ToDate(dateTo5Bytes(intermediate))) - assertEquals("Year mismatch: ", 2008, actual.get(Calendar.YEAR)) - assertEquals("Month mismatch: ", 1, actual.get(Calendar.MONTH)) - assertEquals("Day mismatch: ", 2, actual.get(Calendar.DAY_OF_MONTH)) - assertEquals("Hour mismatch: ", 3, actual.get(Calendar.HOUR_OF_DAY)) - assertEquals("Minute mismatch: ", 4, actual.get(Calendar.MINUTE)) - assertEquals("Second mismatch: ", 5, actual.get(Calendar.SECOND)) + assertEquals("Year mismatch: ", 2008, actual.getYear()) + assertEquals("Month mismatch: ", 1, actual.getMonth()) + assertEquals("Day mismatch: ", 2, actual.getDay()) + assertEquals("Hour mismatch: ", 3, actual.getHour()) + assertEquals("Minute mismatch: ", 4, actual.getMinute()) + assertEquals("Second mismatch: ", 5, actual.getSecond()) assertTrue("jDate and intermediate not equal", jDate == intermediate) assertTrue("jDate $jDate and cDate $cDate not equal", cDate == jDate) } diff --git a/fastlane/metadata/android/en-US/changelogs/132.txt b/fastlane/metadata/android/en-US/changelogs/132.txt index a22243662..0378f07ac 100644 --- a/fastlane/metadata/android/en-US/changelogs/132.txt +++ b/fastlane/metadata/android/en-US/changelogs/132.txt @@ -2,4 +2,5 @@ * Generate keyfile #1290 * Hide template group #1894 * Group count sum recursively #421 + * Fix when selecting date fields #1695 * Small fixes #1711 #1831 #1780 #1821 #1863 #1889 \ No newline at end of file