Birthdays without year



  • It would be nice, if you support birthdays without a year in carddav with vcard 4.0.


  • developer

    @jofrax Do you know whether Android actually supports that? START_DATE seems to be a String, but which format does it have for birthdays without year?

    Also, I have never seen a contacts app supporting this yet. Maybe they will just crash… do you know a contacts app supporting this?



  • The google contact app supports this.

    There is an option yearOptional to activate this in res/xml/contacts.xml:

    <DataKind kind="event" dateWithTime="false" >
    <Type maxOccurs="1" type="birthday" yearOptional="false" />
    <Type maxOccurs="1" type="anniversary" />
    </DataKind>

    I think START_DATE has to be in format 2017-05-24 or --05-24


  • developer

    @jofrax Which server do you use? So that I can test it



  • @rfc2822 I extended my server to speak VCard 4.0 and wanted to test this with Davdroid. So I stumbled on that problem. It will soon be available.

    I'm not an android developer, but it should support dates without year. If I sync my contacts with google the year is optional.
    Also I saw the property yearOptional in the Davdroid source code.

    VCard 4.0 does support dates without year in the rfc 6350, the ezvcard-library has a getPartialDate() function, that supports dates without year.


  • developer

    Seems like for format is really "--mm-dd":

    https://android.googlesource.com/platform/packages/apps/ContactsCommon/+/android-7.1.2_r11/tests/src/com/android/contacts/common/RawContactModifierTests.java#1052

            // No year format is not supported by Exchange.
            mockNameValues.put(Event.START_DATE, "--06-01");
            mockNameValues.put(Event.TYPE, Event.TYPE_BIRTHDAY);
            oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
            mockNameValues = new ContentValues();
            mockNameValues.put(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE);
            mockNameValues.put(Event.START_DATE, "1980-08-02");
            // Anniversary is not supported by Exchange
            mockNameValues.put(Event.TYPE, Event.TYPE_ANNIVERSARY);
            oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
    

    So this should be possible.


  • developer

    Should be implemented by vcard4android/0cb821c7 and davdroid/fab9dbaf, but needs some real-life testing. Could you do that?



  • You are really fast.
    One thing you should notice is that PartialDate is not working with VCard 3.0, because in 3.0 the date must have a year. Ezvcard will throw an exception.

    I will test it on friday.


  • developer

    @jofrax said in Birthdays without year:

    One thing you should notice is that PartialDate is not working with VCard 3.0, because in 3.0 the date must have a year. Ezvcard will throw an exception.

    In my tests, it just doesn't output a BDAY in VCard/3 when there's only a partial date.



  • I did some tests with the server and got NullPointerExceptions. I fixed this in your code and will make a MergeRequest.

    For VCard 3.0 I set the year in your code to 1900, if there was no year - otherwise you cannot save the birthday. Maybe you can set a VCard property - apple makes it this way (BDAY;X-APPLE-OMIT-YEAR=1604:1604-08-14), but the server must understand this.



  • Seems I'm not allowed to make MergeRequests on your project!

    I forked the project to https://gitlab.com/jofrax/vcard4android and changed 3 files. Everything (VCard 3.0, 4.0, with and without year) is working for me so far.


  • developer

    @jofrax Can you please post a patch here for the latest vcard4android?

    And I'll still have to find a server which I can use to test, I'll try with Nextcloud



  • This post is deleted!


  • This post is deleted!


  • Index: src/main/java/at/bitfire/vcard4android/AndroidContact.java
    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    ===================================================================
    --- src/main/java/at/bitfire/vcard4android/AndroidContact.java	(revision 0cb821c7b4b5cb30995b1e75802f7aefd85ef5f3)
    +++ src/main/java/at/bitfire/vcard4android/AndroidContact.java	(revision 7ce4ce8ae74a0a482a9e81a5384dcb5a4136d1be)
    @@ -46,6 +46,7 @@
     import java.io.FileNotFoundException;
     import java.io.IOException;
     import java.io.InputStream;
    +import java.text.SimpleDateFormat;
     import java.util.ArrayList;
     import java.util.Iterator;
     import java.util.LinkedList;
    @@ -1231,6 +1232,17 @@
         }
     
         protected void insertEvent(BatchOperation batch, int type, DateOrTimeProperty dateOrTime) {
    +        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
    +        String startDate;
    +        if (dateOrTime.getPartialDate() != null) {
    +            startDate = dateOrTime.getPartialDate().toString();
    +        } else if (dateOrTime.getDate() != null) {
    +            startDate = formatter.format(dateOrTime.getDate());
    +        } else {
    +            Constants.log.warning("Ignoring contact event (birthday/anniversary) without date");
    +            return;
    +        }
    +
             final BatchOperation.Operation op;
             final ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(dataSyncURI());
             if (id == null)
    @@ -1241,7 +1253,7 @@
             }
             builder .withValue(Event.MIMETYPE, Event.CONTENT_ITEM_TYPE)
                     .withValue(Event.TYPE, type)
    -                .withValue(Event.START_DATE, dateOrTime.getPartialDate().toString());
    +                .withValue(Event.START_DATE, startDate);
             batch.enqueue(op);
             Constants.log.log(Level.FINER, "Built Event data row", builder.build());
         }
    Index: src/main/java/at/bitfire/vcard4android/Contact.java
    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    ===================================================================
    --- src/main/java/at/bitfire/vcard4android/Contact.java	(revision 7ce4ce8ae74a0a482a9e81a5384dcb5a4136d1be)
    +++ src/main/java/at/bitfire/vcard4android/Contact.java	(revision 4f3c2508b33309f95d1d0f01745f9741aa942c14)
    @@ -17,6 +17,7 @@
     import java.net.URI;
     import java.net.URISyntaxException;
     import java.nio.charset.Charset;
    +import java.util.Calendar;
     import java.util.LinkedList;
     import java.util.List;
     import java.util.Map;
    @@ -502,8 +503,22 @@
             if (anniversary != null)
                 vCard.setAnniversary(anniversary);
             // BDAY
    -        if (birthDay != null)
    +        if (birthDay != null) {
    +            if (vCardVersion == VCardVersion.V4_0) {
    -            vCard.setBirthday(birthDay);
    +                vCard.setBirthday(birthDay);
    +            } else {
    +                if (birthDay.getPartialDate() == null) {
    +                    vCard.setBirthday(birthDay);
    +                } else {
    +                    Calendar c = Calendar.getInstance();
    +                    c.clear();
    +                    c.set(Calendar.YEAR, birthDay.getPartialDate().getYear() == null ? 1900 : birthDay.getPartialDate().getYear());
    +                    c.set(Calendar.MONTH, birthDay.getPartialDate().getMonth() - 1);
    +                    c.set(Calendar.DAY_OF_MONTH, birthDay.getPartialDate().getDate());
    +                    vCard.setBirthday(new Birthday(c.getTime()));
    +                }
    +            }
    +        }
     
             // RELATED
             for (Related related : relations)
    Index: src/test/java/at/bitfire/vcard4android/ContactTest.java
    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    ===================================================================
    --- src/test/java/at/bitfire/vcard4android/ContactTest.java	(revision 4f3c2508b33309f95d1d0f01745f9741aa942c14)
    +++ src/test/java/at/bitfire/vcard4android/ContactTest.java	(revision 8567d01ce5b63939e5868bc19f0102f028afd8bc)
    @@ -365,7 +365,8 @@
         @Test
         public void testVCard4FieldsAsVCard3() throws IOException {
             Contact c = regenerate(parseContact("vcard4.vcf", null), VCardVersion.V3_0);
    -        assertNull(c.birthDay);
    +        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
    +        assertEquals("1900-04-16", dateFormat.format(c.birthDay.getDate()));
         }
     
         @Test
    
    


  • Please also make the changes in src/main/java/at/bitfire/vcard4android/Contact.java.
    vcard.setBirthday runs into an exception, if vcard version is 3.0 and the birthday contains a PartialDate.


  • developer

    @jofrax Are you sure that it throws an exception? Which kind of exception? In my tests, there's only a validation warning and ez-vcard just doesn't output the BDAY, which seems to be the best solution in this case.



  • Hm. You're right, but I'm sure, I got an exception - maybe it was in another version of ezvcard.

    The vcard that is produced contains an empty BDAY.

    VERSION:3.0
    PRODID:ez-vcard 0.10.2
    BDAY:
    END:VCARD
    ```
    I'm not sure, if an empty BDAY is allowed in rfc.
    Also the given birthday will be lost completely when using VCard 3.0.