Commit bd9f439d authored by Benjamin Löwe's avatar Benjamin Löwe
Browse files

Addressee: Determine if the birthday has been set with a time

Implemented a way to determine if there has been set a explicit time
with the date of birth and that the time is only exported to a vcard
if it has been explicitly specified. Some vcard implementations (e.g.
on Android) ignore the birthday field if it comes with a time.

The problem here is that QDateTime (when set with a valid date) always
has a valid time (midnight by default). I therefore added the boolean
withTime to setBirthday() which is stored inside Addressee and can be
queried with birthdayHasTime(). I also added setBirthday(QDate).

I changed VCardTool and LDIFConverter to make use of this new methods
when importing/exporting contacts.

Unrelated to this changes I implemented a way in VCardTool to import/
export dates without a year. Dates without a year ('--MMdd') are
imported as year=-1 and during export, years <= 0 are written as '--'.
Also added import/export without year to LDIFConverter.

REVIEW: 128743
parent ba7202ce
......@@ -19,7 +19,7 @@ include(KDECMakeSettings)
include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE)
include(ECMQtDeclareLoggingCategory)
set(PIM_VERSION "5.3.43")
set(PIM_VERSION "5.3.44")
set(KCONTACTS_LIB_VERSION ${PIM_VERSION})
......
......@@ -69,7 +69,7 @@ void AddresseeTest::storeTest()
addressee.setPrefix(QStringLiteral("Sir"));
addressee.setSuffix(QStringLiteral("II"));
addressee.setNickName(QStringLiteral("ghosthunter"));
addressee.setBirthday(QDateTime(QDate(1982, 7, 19)));
addressee.setBirthday(QDate(1982, 7, 19));
addressee.setMailer(QStringLiteral("mutt"));
addressee.setTimeZone(KContacts::TimeZone(2));
addressee.setGeo(KContacts::Geo(42, 23));
......@@ -112,7 +112,9 @@ void AddresseeTest::storeTest()
QVERIFY(addressee.prefix() == QStringLiteral("Sir"));
QVERIFY(addressee.suffix() == QStringLiteral("II"));
QVERIFY(addressee.nickName() == QStringLiteral("ghosthunter"));
QVERIFY(addressee.birthday() == QDateTime(QDate(1982, 7, 19)));
QVERIFY(addressee.birthday().date() == QDate(1982, 7, 19));
QVERIFY(addressee.birthday().time() == QTime(0, 0));
QVERIFY(!addressee.birthdayHasTime());
QVERIFY(addressee.mailer() == QStringLiteral("mutt"));
QVERIFY(addressee.timeZone() == KContacts::TimeZone(2));
QVERIFY(addressee.geo() == KContacts::Geo(42, 23));
......
......@@ -58,6 +58,29 @@ void BirthDayTest::shouldExportVcard4()
QCOMPARE(ba, expected);
}
void BirthDayTest::shouldExportVcard4WithoutTime()
{
KContacts::AddresseeList lst;
KContacts::Addressee addr;
addr.setEmails(QStringList() << QStringLiteral("foo@kde.org") << QStringLiteral("bla@kde.org"));
addr.setUid(QStringLiteral("testuid"));
const QDate d(1976, 5, 5);
addr.setBirthday(d);
lst << addr;
KContacts::VCardTool vcard;
const QByteArray ba = vcard.exportVCards(lst, KContacts::VCard::v4_0);
QByteArray expected("BEGIN:VCARD\r\n"
"VERSION:4.0\r\n"
"BDAY:19760505\r\n"
"EMAIL:foo@kde.org\r\n"
"EMAIL:bla@kde.org\r\n"
"N:;;;;\r\n"
"UID:testuid\r\n"
"END:VCARD\r\n\r\n");
QCOMPARE(ba, expected);
}
void BirthDayTest::shouldExportVcard3()
{
KContacts::AddresseeList lst;
......@@ -81,4 +104,27 @@ void BirthDayTest::shouldExportVcard3()
QCOMPARE(ba, expected);
}
void BirthDayTest::shouldExportVcard3WithoutTime()
{
KContacts::AddresseeList lst;
KContacts::Addressee addr;
addr.setEmails(QStringList() << QStringLiteral("foo@kde.org") << QStringLiteral("bla@kde.org"));
addr.setUid(QStringLiteral("testuid"));
const QDate d(1976, 5, 5);
addr.setBirthday(d);
lst << addr;
KContacts::VCardTool vcard;
const QByteArray ba = vcard.exportVCards(lst, KContacts::VCard::v3_0);
QByteArray expected("BEGIN:VCARD\r\n"
"VERSION:3.0\r\n"
"BDAY:1976-05-05\r\n"
"EMAIL:foo@kde.org\r\n"
"EMAIL:bla@kde.org\r\n"
"N:;;;;\r\n"
"UID:testuid\r\n"
"END:VCARD\r\n\r\n");
QCOMPARE(ba, expected);
}
QTEST_MAIN(BirthDayTest)
......@@ -32,7 +32,9 @@ public:
private Q_SLOTS:
void shouldExportVcard4();
void shouldExportVcard4WithoutTime();
void shouldExportVcard3();
void shouldExportVcard3WithoutTime();
};
#endif // BIRTHDAYTEST_H
......@@ -42,43 +42,53 @@ void DateTimeTest::shouldParseDateTime()
dt = VCardTool::parseDateTime(QStringLiteral("2016-01-20T12:33:30+0200"));
expected = QDateTime(QDate(2016, 1, 20), QTime(12, 33, 30), Qt::OffsetFromUTC, 2 * 3600);
QCOMPARE(dt, expected);
QCOMPARE(dt.isValid(), true);
QVERIFY(dt.isValid());
dt = VCardTool::parseDateTime(QStringLiteral("2016-01-20T12:33:30+02"));
QCOMPARE(dt, expected);
QCOMPARE(dt.isValid(), true);
QVERIFY(dt.isValid());
dt = VCardTool::parseDateTime(QStringLiteral("20160120T123330+0200"));
QCOMPARE(dt, expected);
QCOMPARE(dt.isValid(), true);
QVERIFY(dt.isValid());
dt = VCardTool::parseDateTime(QStringLiteral("2016-01-20T12:33Z"));
expected = QDateTime(QDate(2016, 1, 20), QTime(12, 33, 0), Qt::UTC);
QCOMPARE(dt, expected);
QCOMPARE(dt.isValid(), true);
QVERIFY(dt.isValid());
dt = VCardTool::parseDateTime(QStringLiteral("2016-01-20T12:33-0300"));
expected = QDateTime(QDate(2016, 1, 20), QTime(12, 33, 0), Qt::OffsetFromUTC, -3 * 3600);
QCOMPARE(dt, expected);
QCOMPARE(dt.isValid(), true);
QVERIFY(dt.isValid());
dt = VCardTool::parseDateTime(QStringLiteral("2016-01-20T12:33"), &timeIsValid);
expected = QDateTime(QDate(2016, 1, 20), QTime(12, 33, 0), Qt::LocalTime);
QCOMPARE(dt, expected);
QCOMPARE(timeIsValid, true);
QCOMPARE(dt.isValid(), true);
QVERIFY(timeIsValid);
QVERIFY(dt.isValid());
dt = VCardTool::parseDateTime(QStringLiteral("2016-01-20"), &timeIsValid);
expected = QDateTime(QDate(2016, 1, 20), QTime(), Qt::LocalTime);
QCOMPARE(dt, expected);
QCOMPARE(dt.isValid(), true);
QCOMPARE(timeIsValid, false);
QVERIFY(dt.isValid());
QVERIFY(!timeIsValid);
QCOMPARE(dt.time(), QTime(0, 0));
dt = VCardTool::parseDateTime(QStringLiteral("T1233Z"), &timeIsValid);
QCOMPARE(dt.isValid(), false);
QCOMPARE(timeIsValid, true);
QVERIFY(!dt.isValid());
QVERIFY(timeIsValid);
QCOMPARE(dt.time(), QTime(12, 33));
dt = VCardTool::parseDateTime(QStringLiteral("--0120"));
expected = QDateTime(QDate(-1, 1, 20));
QCOMPARE(dt, expected);
QVERIFY(dt.isValid());
dt = VCardTool::parseDateTime(QStringLiteral("--01-20"));
expected = QDateTime(QDate(-1, 1, 20));
QCOMPARE(dt, expected);
QVERIFY(dt.isValid());
}
void DateTimeTest::shouldCreateDateTime()
......@@ -122,6 +132,44 @@ void DateTimeTest::shouldCreateDateTime()
str = VCardTool::createDateTime(dt, VCard::v3_0);
expected = QString();
QCOMPARE(str, expected);
dt = QDateTime(QDate(2016, 1, 20), QTime(12, 33, 30));
str = VCardTool::createDateTime(dt, VCard::v3_0, false);
expected = QStringLiteral("2016-01-20");
QCOMPARE(str, expected);
dt = QDateTime(QDate(2016, 1, 20), QTime(12, 33, 30));
str = VCardTool::createDateTime(dt, VCard::v4_0, false);
expected = QStringLiteral("20160120");
QCOMPARE(str, expected);
}
void DateTimeTest::shouldCreateDate()
{
using namespace KContacts;
QDate d;
QString str, expected;
d = QDate(2016, 1, 20);
str = VCardTool::createDate(d, VCard::v3_0);
expected = QStringLiteral("2016-01-20");
QCOMPARE(str, expected);
d = QDate(2016, 1, 20);
str = VCardTool::createDate(d, VCard::v4_0);
expected = QStringLiteral("20160120");
QCOMPARE(str, expected);
d = QDate(-1, 1, 20);
str = VCardTool::createDate(d, VCard::v3_0);
expected = QStringLiteral("--01-20");
QCOMPARE(str, expected);
d = QDate(-1, 1, 20);
str = VCardTool::createDate(d, VCard::v4_0);
expected = QStringLiteral("--0120");
QCOMPARE(str, expected);
}
QTEST_MAIN(DateTimeTest)
......@@ -33,6 +33,7 @@ public:
private Q_SLOTS:
void shouldParseDateTime();
void shouldCreateDateTime();
void shouldCreateDate();
};
#endif // DATETIMETEST_H
......@@ -96,6 +96,30 @@ void LDifConverterTest::shouldImportStandardBirthday()
QCOMPARE(lst.count(), 1);
QVERIFY(lst.at(0).birthday().date().isValid());
QCOMPARE(lst.at(0).birthday().date(), QDate(2015, 3, 19));
QVERIFY(!lst.at(0).birthdayHasTime());
QCOMPARE(contactGroup.count(), 0);
}
void LDifConverterTest::shouldImportStandardBirthdayWithoutYear()
{
QString str = QStringLiteral("dn: cn=laurent,mail=foo@kde.org\n"
"sn: laurent\n"
"cn: laurent\n"
"uid: d1d5cdd4-7d5d-484b-828d-58864d8efe74\n"
"birthmonth: 3\n"
"birthday: 19\n"
"mail: foo@kde.org\n"
"objectclass: top_n"
"objectclass: person\n"
"objectclass: organizationalPerson");
AddresseeList lst;
ContactGroup::List contactGroup;
bool result = LDIFConverter::LDIFToAddressee(str, lst, contactGroup);
QVERIFY(result);
QCOMPARE(lst.count(), 1);
QVERIFY(lst.at(0).birthday().date().isValid());
QCOMPARE(lst.at(0).birthday().date(), QDate(-1, 3, 19));
QVERIFY(!lst.at(0).birthdayHasTime());
QCOMPARE(contactGroup.count(), 0);
}
......@@ -117,6 +141,7 @@ void LDifConverterTest::shouldImportTheBatsBirthday()
QCOMPARE(lst.count(), 1);
QVERIFY(lst.at(0).birthday().date().isValid());
QCOMPARE(lst.at(0).birthday().date(), QDate(2015, 3, 19));
QVERIFY(!lst.at(0).birthdayHasTime());
QCOMPARE(contactGroup.count(), 0);
}
......@@ -322,7 +347,7 @@ void LDifConverterTest::shouldExportBirthday()
ContactGroup::List contactGroup;
Addressee addr;
QDate date(2015, 3, 3);
addr.setBirthday(QDateTime(date));
addr.setBirthday(date);
addr.setEmails(QStringList() << QStringLiteral("foo@kde.org"));
addr.setUid(QStringLiteral("testuid"));
lst << addr;
......@@ -343,6 +368,32 @@ void LDifConverterTest::shouldExportBirthday()
QCOMPARE(str, expected);
}
void LDifConverterTest::shouldExportBirthdayWithoutYear()
{
AddresseeList lst;
ContactGroup::List contactGroup;
Addressee addr;
QDate date(-1, 3, 3);
addr.setBirthday(date);
addr.setEmails(QStringList() << QStringLiteral("foo@kde.org"));
addr.setUid(QStringLiteral("testuid"));
lst << addr;
QString str;
bool result = LDIFConverter::addresseeAndContactGroupToLDIF(lst, contactGroup, str);
QVERIFY(result);
QString expected = QStringLiteral("dn: cn=,mail=foo@kde.org\n"
"objectclass: top\n"
"objectclass: person\n"
"objectclass: organizationalPerson\n"
"uid: testuid\n"
"mail: foo@kde.org\n"
"birthmonth: 3\n"
"birthday: 3\n"
"\n");
QCOMPARE(str, expected);
}
void LDifConverterTest::shouldExportTitle()
{
AddresseeList lst;
......@@ -379,7 +430,7 @@ void LDifConverterTest::shouldExportMultiEntries()
Addressee addr2;
QDate date(2015, 3, 3);
addr2.setBirthday(QDateTime(date));
addr2.setBirthday(date);
addr2.setEmails(QStringList() << QStringLiteral("foo@kde.org"));
addr2.setUid(QStringLiteral("testuid"));
lst << addr2;
......
......@@ -35,6 +35,7 @@ private Q_SLOTS:
void shouldImportEmail();
void shouldImportMultiEmails();
void shouldImportStandardBirthday();
void shouldImportStandardBirthdayWithoutYear();
void shouldImportTheBatsBirthday();
void shouldImportTheBatsEmails();
void shouldImportTitle();
......@@ -46,6 +47,7 @@ private Q_SLOTS:
//Export
void shouldExportEmail();
void shouldExportBirthday();
void shouldExportBirthdayWithoutYear();
void shouldExportTitle();
void shouldExportMultiEntries();
void shouldExportGroup();
......
......@@ -49,6 +49,7 @@ public:
: mUid(QUuid::createUuid().toString().mid(1, 36))
, mEmpty(true)
, mChanged(false)
, mBirthdayWithTime(false)
{
//We avoid the curly braces so the string is RFC4122 compliant and can be used as urn
}
......@@ -65,6 +66,7 @@ public:
mPrefix = other.mPrefix;
mSuffix = other.mSuffix;
mBirthday = other.mBirthday;
mBirthdayWithTime = other.mBirthdayWithTime;
mMailer = other.mMailer;
mTimeZone = other.mTimeZone;
mGeo = other.mGeo;
......@@ -153,6 +155,7 @@ public:
NickName::List mNickNameExtraList;
bool mEmpty : 1;
bool mChanged : 1;
bool mBirthdayWithTime;
static KContacts::SortMode *mSortMode;
};
......@@ -231,7 +234,7 @@ bool Addressee::operator==(const Addressee &addressee) const
return false;
}
if (d->mBirthday != addressee.d->mBirthday) {
if (d->mBirthday != addressee.d->mBirthday || d->mBirthdayWithTime != addressee.d->mBirthdayWithTime) {
qCDebug(KCONTACTS_LOG) << "birthday differs";
return false;
}
......@@ -777,14 +780,29 @@ QString Addressee::nickNameLabel()
return i18n("Nick Name");
}
void Addressee::setBirthday(const QDateTime &birthday)
void Addressee::setBirthday(const QDateTime &birthday, bool withTime)
{
if (birthday == d->mBirthday) {
if (birthday == d->mBirthday && d->mBirthdayWithTime == withTime) {
return;
}
d->mEmpty = false;
d->mBirthday = birthday;
if (!withTime) {
d->mBirthday.setTime(QTime());
}
d->mBirthdayWithTime = withTime;
}
void Addressee::setBirthday(const QDate &birthday)
{
if (birthday == d->mBirthday.date() && !d->mBirthdayWithTime) {
return;
}
d->mEmpty = false;
d->mBirthday = QDateTime(birthday, QTime());
d->mBirthdayWithTime = false;
}
QDateTime Addressee::birthday() const
......@@ -792,6 +810,11 @@ QDateTime Addressee::birthday() const
return d->mBirthday;
}
bool Addressee::birthdayHasTime() const
{
return d->mBirthdayWithTime;
}
QString Addressee::birthdayLabel()
{
return i18n("Birthday");
......@@ -2389,6 +2412,7 @@ QDataStream &KContacts::operator<<(QDataStream &s, const Addressee &a)
s << a.d->mPrefix;
s << a.d->mSuffix;
s << a.d->mBirthday;
s << a.d->mBirthdayWithTime;
s << a.d->mMailer;
s << a.d->mTimeZone;
s << a.d->mGeo;
......@@ -2440,6 +2464,7 @@ QDataStream &KContacts::operator>>(QDataStream &s, Addressee &a)
s >> a.d->mPrefix;
s >> a.d->mSuffix;
s >> a.d->mBirthday;
s >> a.d->mBirthdayWithTime;
s >> a.d->mMailer;
s >> a.d->mTimeZone;
s >> a.d->mGeo;
......
......@@ -284,15 +284,27 @@ public:
static QString nickNameLabel();
/**
Set birthday.
Set birthday (date and time). If withTime is false the time will be set
to midnight and birthdayHasTime() will return false afterwards.
*/
void setBirthday(const QDateTime &birthday);
void setBirthday(const QDateTime &birthday, bool withTime = true);
/**
Return birthday.
Set birthday (date only). birthdayHasTime() will return false afterwards.
*/
void setBirthday(const QDate &birthday);
/**
Return birthday. (If a valid date has been set, birthday().time() will
always return a valid QTime!)
*/
QDateTime birthday() const;
/**
Returns true if birthday has been set with a time. Returns false otherwise.
*/
bool birthdayHasTime() const;
/**
Return translated label for birthday field.
*/
......
......@@ -245,12 +245,14 @@ bool LDIFConverter::addresseeToLDIF(const Addressee &addr, QString &str)
ldif_out(t, QStringLiteral("modifytimestamp"), dateToVCardString(addr.revision()));
}
const QDateTime birthday = addr.birthday();
if (birthday.date().isValid()) {
const QDate date = birthday.date();
ldif_out(t, QStringLiteral("birthyear"), QString::number(date.year()));
ldif_out(t, QStringLiteral("birthmonth"), QString::number(date.month()));
ldif_out(t, QStringLiteral("birthday"), QString::number(date.day()));
const QDate birthday = addr.birthday().date();
if (birthday.isValid()) {
const int year = birthday.year();
if (year > 0) {
ldif_out(t, QStringLiteral("birthyear"), QString::number(year));
}
ldif_out(t, QStringLiteral("birthmonth"), QString::number(birthday.month()));
ldif_out(t, QStringLiteral("birthday"), QString::number(birthday.day()));
}
t << "\n";
......@@ -296,7 +298,7 @@ bool LDIFConverter::LDIFToAddressee(const QString &str, AddresseeList &addrList,
case Ldif::EndEntry: {
if (contactGroup.count() == 0) {
// if the new address is not empty, append it
QDateTime birthDate(QDate(birthyear, birthmonth, birthday));
QDate birthDate(birthyear, birthmonth, birthday);
if (birthDate.isValid()) {
a.setBirthday(birthDate);
}
......@@ -618,7 +620,11 @@ void KContacts::evaluatePair(Addressee &a, Address &homeAddr,
}
if (fieldname == QLatin1String("birthyear")) {
birthyear = value.toInt();
bool ok;
birthyear = value.toInt(&ok);
if (!ok) {
birthyear = -1;
}
return;
}
if (fieldname == QLatin1String("birthmonth")) {
......@@ -632,7 +638,7 @@ void KContacts::evaluatePair(Addressee &a, Address &homeAddr,
if (fieldname == QLatin1String("xbatbirthday")) {
QDate dt = QDate::fromString(value, QStringLiteral("yyyyMMdd"));
if (dt.isValid()) {
a.setBirthday(QDateTime(dt));
a.setBirthday(dt);
}
return;
}
......
......@@ -419,7 +419,7 @@ bool Field::setValue(KContacts::Addressee &a, const QString &value)
a.setNote(value);
return true;
case Private::Birthday:
a.setBirthday(QDateTime::fromString(value, Qt::ISODate));
a.setBirthday(QDate::fromString(value, Qt::ISODate));
return true;
case Private::CustomField:
a.insertCustom(d->app(), d->key(), value);
......
......@@ -220,7 +220,9 @@ QByteArray VCardTool::createVCards(const Addressee::List &list,
}
// BDAY
card.addLine(VCardLine(QStringLiteral("BDAY"), createDateTime((*addrIt).birthday(), version)));
const bool withTime = (*addrIt).birthdayHasTime();
const QString birthdayString = createDateTime((*addrIt).birthday(), version, withTime);
card.addLine(VCardLine(QStringLiteral("BDAY"), birthdayString));
//Laurent: 31 Jan 2015. Not necessary to export it. When Categories were changes as AkonadiTag nobody thought that it was break categorie support...
//=> not necessary to export just tag...
......@@ -776,7 +778,9 @@ Addressee::List VCardTool::parseVCards(const QByteArray &vcard) const
}
// BDAY
else if (identifier == QLatin1String("bday")) {
addr.setBirthday(parseDateTime((*lineIt).value().toString()));
bool withTime;
const QDateTime bday = parseDateTime((*lineIt).value().toString(), &withTime);
addr.setBirthday(bday, withTime);
}
// CATEGORIES
......@@ -1186,8 +1190,13 @@ QDateTime VCardTool::parseDateTime(const QString &str, bool *timeValid)
const QStringList strings = str.split(QLatin1Char('T'));
QString dateString = strings.at(0);
const bool noYear = dateString.startsWith(QLatin1String("--"));
dateString = dateString.replace(QLatin1String("-"), QString());
QDate date = QDate::fromString(dateString, QStringLiteral("yyyyMMdd"));
if (noYear) {
date = QDate::fromString(dateString, QStringLiteral("MMdd"));
date = date.addYears(-1900);
}
QTime time;
Qt::TimeSpec spec = Qt::LocalTime;
......@@ -1239,12 +1248,15 @@ QDateTime VCardTool::parseDateTime(const QString &str, bool *timeValid)
return QDateTime(date, time, spec, offsetSecs);
}
QString VCardTool::createDateTime(const QDateTime &dateTime, VCard::Version version)
QString VCardTool::createDateTime(const QDateTime &dateTime, VCard::Version version, bool withTime)
{
if (!dateTime.date().isValid()) {
return QString();
}
QString str = createDate(dateTime.date(), version);
if (!withTime) {
return str;
}
str += createTime(dateTime.time(), version);
if (dateTime.timeSpec() == Qt::UTC) {
str += QLatin1Char('Z');
......@@ -1268,10 +1280,14 @@ QString VCardTool::createDateTime(const QDateTime &dateTime, VCard::Version vers
QString VCardTool::createDate(const QDate &date, VCard::Version version)
{
QString format;
if (version == VCard::v4_0) {
if (date.year() > 0) {
format = QStringLiteral("yyyyMMdd");
} else {
format = QStringLiteral("yyyy-MM-dd");
format = QStringLiteral("--MMdd");
}
if (version != VCard::v4_0) {
format = format.replace(QStringLiteral("yyyy"), QStringLiteral("yyyy-"));
format = format.replace(QStringLiteral("MM"), QStringLiteral("MM-"));
}
return date.toString(format);
}
......
......@@ -59,7 +59,7 @@ public:
Addressee::List parseVCards(const QByteArray &vcard) const;
static QDateTime parseDateTime(const QString &str, bool *timeValid = 0);
static QString createDateTime(const QDateTime &dateTime, VCard::Version version);
static QString createDateTime(const QDateTime &dateTime, VCard::Version version, bool withTime = true);
static QString createDate(const QDate &date, VCard::Version version);
static QString createTime(const QTime &time, VCard::Version version);
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment