apps/app_voicemail.c and main/say.c: Add support for Icelandic language
authorÖrn Arnarson <orn@arnarson.net>
Mon, 6 Jun 2016 16:13:01 +0000 (16:13 +0000)
committerÖrn Arnarson <orn@arnarson.net>
Tue, 7 Jun 2016 11:36:48 +0000 (11:36 +0000)
Icelandic has some weird grammar rules when dealing with dates and
numbers. There are different genders used depending on which number
you're dealing with, and only a handful of numbers do change depending
on the gender. There is also an implied gender in several cases.

This patch was originally written for asterisk 1.6, and has been in use
for several years without crashes. I cleaned it up a bit and rewrote
what was necessary for Asterisk 13.

The functions were copied from other similar languages and modified
where appropriate. If i recall correctly, the German and Danish
functions were used as a base.

ASTERISK-26087
Reported by: Örn Arnarson
Tested by: Örn Arnarson

Change-Id: Ib7d8bd7b0fede5767921ed821315b5b508c0e665

CHANGES
apps/app_voicemail.c
main/say.c

diff --git a/CHANGES b/CHANGES
index 608a4a4..d6f049b 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1010,6 +1010,8 @@ Say
    language. The 'language' parameter in say.conf now recognizes a setting of
    'ja', which will enable Japanese language specific mechanisms for playing
    back numbers, dates, and other items.
+ * Counting, enumeration and dates now supports Icelandic grammar with the
+   'language' parameter set to 'is'.
 
 SayCountPL
 ------------------
@@ -5163,6 +5165,7 @@ Voicemail Changes
      the message as urgent after he has recorded a voicemail by following the voice instructions.
     When listening to voicemails using VoiceMailMain urgent messages will be presented before other
      messages
+  * Added "is" language support
 
 Queue changes
 -------------
index 1d5b2dc..586086c 100644 (file)
@@ -8527,6 +8527,8 @@ static int play_message_datetime(struct ast_channel *chan, struct ast_vm_user *v
                res = ast_say_date_with_format(chan, t, AST_DIGIT_ANY, ast_channel_language(chan), "'vm-received' Q 'digits/at' HM", NULL);
        } else if (!strncasecmp(ast_channel_language(chan), "gr", 2)) {     /* GREEK syntax */
                res = ast_say_date_with_format(chan, t, AST_DIGIT_ANY, ast_channel_language(chan), "'vm-received' q  H 'digits/kai' M ", NULL);
+       } else if (!strncasecmp(ast_channel_language(chan), "is", 2)) {     /* ICELANDIC syntax */
+               res = ast_say_date_with_format(chan, t, AST_DIGIT_ANY, ast_channel_language(chan), "'vm-received' Q 'digits/at' HM", NULL);
        } else if (!strncasecmp(ast_channel_language(chan), "it", 2)) {     /* ITALIAN syntax */
                res = ast_say_date_with_format(chan, t, AST_DIGIT_ANY, ast_channel_language(chan), "'vm-received' q 'digits/at' 'digits/hours' k 'digits/e' M 'digits/minutes'", NULL);
        } else if (!strcasecmp(ast_channel_language(chan),"ja")) {     /* Japanese syntax */
@@ -8753,6 +8755,12 @@ static int play_message(struct ast_channel *chan, struct ast_vm_user *vmu, struc
                                res = wait_file2(chan, vms, "vm-number");
                                res = ast_say_number(chan, vms->curmsg + 1, AST_DIGIT_ANY, ast_channel_language(chan), "f");
                        }
+               /* ICELANDIC syntax */
+               } else if (!strncasecmp(ast_channel_language(chan), "is", 2)) {
+                       res = wait_file2(chan, vms, "vm-message");
+                       if (vms->curmsg && (vms->curmsg != vms->lastmsg)) {
+                               res = ast_say_number(chan, vms->curmsg + 1, AST_DIGIT_ANY, ast_channel_language(chan), "n");
+                       }
                /* VIETNAMESE syntax */
                } else if (!strncasecmp(ast_channel_language(chan), "vi", 2)) {
                        if (!vms->curmsg) {
@@ -9492,6 +9500,75 @@ static int vm_intro_en(struct ast_channel *chan, struct vm_state *vms)
        return res;
 }
 
+/* ICELANDIC syntax */
+static int vm_intro_is(struct ast_channel *chan, struct vm_state *vms)
+{
+       int res;
+
+       /* Introduce messages they have */
+       res = ast_play_and_wait(chan, "vm-youhave");
+       if (!res) {
+               if (vms->urgentmessages) {
+                       /* Digits 1-4 are spoken in neutral and plural when talking about messages,
+                          however, feminine is used for 1 as it is the same as the neutral for plural,
+                          and singular neutral is the same after 1. */
+                       if (vms->urgentmessages < 5) {
+                               char recname[16];
+                               if (vms->urgentmessages == 1)
+                                       snprintf(recname, sizeof(recname), "digits/1kvk");
+                               else
+                                       snprintf(recname, sizeof(recname), "digits/%dhk", vms->urgentmessages);
+                               res = ast_play_and_wait(chan, recname);
+                       } else if (!res)
+                               res = ast_play_and_wait(chan, "vm-Urgent");
+                       if ((vms->oldmessages || vms->newmessages) && !res) {
+                               res = ast_play_and_wait(chan, "vm-and");
+                       } else if (!res)
+                               res = ast_play_and_wait(chan, "vm-messages");
+               }
+               if (vms->newmessages) {
+                       if (vms->newmessages < 5) {
+                               char recname[16];
+                               if (vms->newmessages == 1)
+                                       snprintf(recname, sizeof(recname), "digits/1kvk");
+                               else
+                                       snprintf(recname, sizeof(recname), "digits/%dhk", vms->newmessages);
+                               res = ast_play_and_wait(chan, recname);
+                       } else
+                               res = say_and_wait(chan, vms->newmessages, ast_channel_language(chan));
+                       if (!res)
+                               res = ast_play_and_wait(chan, "vm-INBOX");
+                       if (vms->oldmessages && !res)
+                               res = ast_play_and_wait(chan, "vm-and");
+                       else if (!res)
+                               res = ast_play_and_wait(chan, "vm-messages");
+               }
+               if (!res && vms->oldmessages) {
+                       if (vms->oldmessages < 5) {
+                               char recname[16];
+                               if (vms->oldmessages == 1)
+                                       snprintf(recname, sizeof(recname), "digits/1kvk");
+                               else
+                                       snprintf(recname, sizeof(recname), "digits/%dhk", vms->oldmessages);
+                               res = ast_play_and_wait(chan, recname);
+                       } else
+                               res = say_and_wait(chan, vms->oldmessages, ast_channel_language(chan));
+                       if (!res)
+                               res = ast_play_and_wait(chan, "vm-Old");
+                       if (!res)
+                               res = ast_play_and_wait(chan, "vm-messages");
+               }
+               if (!res) {
+                       if (!vms->urgentmessages && !vms->oldmessages && !vms->newmessages) {
+                               res = ast_play_and_wait(chan, "vm-no");
+                               if (!res)
+                                       res = ast_play_and_wait(chan, "vm-messages");
+                       }
+               }
+       }
+       return res;
+}
+
 /* ITALIAN syntax */
 static int vm_intro_it(struct ast_channel *chan, struct vm_state *vms)
 {
@@ -10156,6 +10233,8 @@ static int vm_intro(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm
                return vm_intro_gr(chan, vms);
        } else if (!strncasecmp(ast_channel_language(chan), "he", 2)) {  /* HEBREW syntax */
                return vm_intro_he(chan, vms);
+       } else if (!strncasecmp(ast_channel_language(chan), "is", 2)) {  /* ICELANDIC syntax */
+               return vm_intro_is(chan, vms);
        } else if (!strncasecmp(ast_channel_language(chan), "it", 2)) {  /* ITALIAN syntax */
                return vm_intro_it(chan, vms);
        } else if (!strncasecmp(ast_channel_language(chan), "ja", 2)) {  /* JAPANESE syntax */
index 9579293..6e51de2 100644 (file)
@@ -317,6 +317,7 @@ static int say_digit_str_full(struct ast_channel *chan, const char *str, const c
       \arg \b es    - Spanish, Mexican
       \arg \b fr    - French
       \arg \b he    - Hebrew
+      \arg \b is    - Icelandic
       \arg \b it    - Italian
       \arg \b nl    - Dutch
       \arg \b no    - Norwegian
@@ -373,6 +374,7 @@ static int ast_say_number_full_en_GB(struct ast_channel *chan, int num, const ch
 static int ast_say_number_full_es(struct ast_channel *chan, int num, const char *ints, const char *language, const char *options, int audiofd, int ctrlfd);
 static int ast_say_number_full_fr(struct ast_channel *chan, int num, const char *ints, const char *language, const char *options, int audiofd, int ctrlfd);
 static int ast_say_number_full_he(struct ast_channel *chan, int num, const char *ints, const char *language, const char *options, int audiofd, int ctrlfd);
+static int ast_say_number_full_is(struct ast_channel *chan, int num, const char *ints, const char *language, const char *options, int audiofd, int ctrlfd);
 static int ast_say_number_full_it(struct ast_channel *chan, int num, const char *ints, const char *language, int audiofd, int ctrlfd);
 static int ast_say_number_full_nl(struct ast_channel *chan, int num, const char *ints, const char *language, int audiofd, int ctrlfd);
 static int ast_say_number_full_no(struct ast_channel *chan, int num, const char *ints, const char *language, const char *options, int audiofd, int ctrlfd);
@@ -394,6 +396,7 @@ static int ast_say_enumeration_full_en(struct ast_channel *chan, int num, const
 static int ast_say_enumeration_full_da(struct ast_channel *chan, int num, const char *ints, const char *language, const char *options, int audiofd, int ctrlfd);
 static int ast_say_enumeration_full_de(struct ast_channel *chan, int num, const char *ints, const char *language, const char *options, int audiofd, int ctrlfd);
 static int ast_say_enumeration_full_he(struct ast_channel *chan, int num, const char *ints, const char *language, const char *options, int audiofd, int ctrlfd);
+static int ast_say_enumeration_full_is(struct ast_channel *chan, int num, const char *ints, const char *language, const char *options, int audiofd, int ctrlfd);
 static int ast_say_enumeration_full_vi(struct ast_channel *chan, int num, const char *ints, const char *language, int audiofd, int ctrlfd);
 
 /* Forward declarations of ast_say_date, ast_say_datetime and ast_say_time functions */
@@ -409,6 +412,7 @@ static int ast_say_date_ka(struct ast_channel *chan, time_t t, const char *ints,
 static int ast_say_date_hu(struct ast_channel *chan, time_t t, const char *ints, const char *lang);
 static int ast_say_date_th(struct ast_channel *chan, time_t t, const char *ints, const char *lang);
 static int ast_say_date_he(struct ast_channel *chan, time_t t, const char *ints, const char *lang);
+static int ast_say_date_is(struct ast_channel *chan, time_t t, const char *ints, const char *lang);
 
 static int ast_say_date_with_format_en(struct ast_channel *chan, time_t t, const char *ints, const char *lang, const char *format, const char *tzone);
 static int ast_say_date_with_format_da(struct ast_channel *chan, time_t t, const char *ints, const char *lang, const char *format, const char *tzone);
@@ -416,6 +420,7 @@ static int ast_say_date_with_format_de(struct ast_channel *chan, time_t t, const
 static int ast_say_date_with_format_es(struct ast_channel *chan, time_t t, const char *ints, const char *lang, const char *format, const char *tzone);
 static int ast_say_date_with_format_he(struct ast_channel *chan, time_t t, const char *ints, const char *lang, const char *format, const char *tzone);
 static int ast_say_date_with_format_fr(struct ast_channel *chan, time_t t, const char *ints, const char *lang, const char *format, const char *tzone);
+static int ast_say_date_with_format_is(struct ast_channel *chan, time_t t, const char *ints, const char *lang, const char *format, const char *tzone);
 static int ast_say_date_with_format_it(struct ast_channel *chan, time_t t, const char *ints, const char *lang, const char *format, const char *tzone);
 static int ast_say_date_with_format_nl(struct ast_channel *chan, time_t t, const char *ints, const char *lang, const char *format, const char *tzone);
 static int ast_say_date_with_format_pl(struct ast_channel *chan, time_t t, const char *ints, const char *lang, const char *format, const char *tzone);
@@ -511,6 +516,8 @@ static int say_number_full(struct ast_channel *chan, int num, const char *ints,
           return ast_say_number_full_he(chan, num, ints, language, options, audiofd, ctrlfd);
        } else if (!strncasecmp(language, "hu", 2)) { /* Hungarian syntax */
                return ast_say_number_full_hu(chan, num, ints, language, audiofd, ctrlfd);
+       } else if (!strncasecmp(language, "is", 2)) { /* Icelandic syntax */
+               return ast_say_number_full_is(chan, num, ints, language, options, audiofd, ctrlfd);
        } else if (!strncasecmp(language, "it", 2)) { /* Italian syntax */
           return ast_say_number_full_it(chan, num, ints, language, audiofd, ctrlfd);
        } else if (!strncasecmp(language, "ka", 2)) { /* Georgian syntax */
@@ -1518,6 +1525,129 @@ static int ast_say_number_full_hu(struct ast_channel *chan, int num, const char
        return res;
 }
 
+/*! \brief  ast_say_number_full_is: Icelandic syntax */
+/* New files:
+ In addition to American English, the following sounds are required:  "hundreds", "millions", "1kvk", "1hk", "2kvk", "2hk", "3kvk", "3hk", "4kvk", "4hk"
+ */
+static int ast_say_number_full_is(struct ast_channel *chan, int num, const char *ints, const char *language, const char *options, int audiofd, int ctrlfd)
+{
+       int res = 0;
+       int playh = 0;
+       int playa = 0;
+       int cn = 1;             /* 1 = masc; 2 = fem; 3 = neut */
+       char fn[256] = "";
+
+       if (!num)
+               return ast_say_digits_full(chan, 0, ints, language, audiofd, ctrlfd);
+
+       if (options && !strncasecmp(options, "f", 1)) cn = 2;
+       if (options && !strncasecmp(options, "c", 1)) cn = 3;
+       /* It seems that sometimes people are using c and sometimes n. */
+       if (options && !strncasecmp(options, "n", 1)) cn = 3;
+
+       while (!res && (num || playh || playa )) {
+               if (num < 0) {
+                       ast_copy_string(fn, "digits/minus", sizeof(fn));
+                       if ( num > INT_MIN ) {
+                               num = -num;
+                       } else {
+                               num = 0;
+                       }
+               } else if (playh) {
+                       if (playh > 1)
+                               ast_copy_string(fn, "digits/hundreds", sizeof(fn));
+                       else
+                               ast_copy_string(fn, "digits/hundred", sizeof(fn));
+                       playh = 0;
+               } else if (playa) {
+                       ast_copy_string(fn, "digits/and", sizeof(fn));
+                       playa = 0;
+               } else if (num < 5 && cn == 2) {
+                       snprintf(fn, sizeof(fn), "digits/%dkvk", num);
+                       num = 0;
+               } else if (num < 5 && cn == 3) {
+                       snprintf(fn, sizeof(fn), "digits/%dhk", num);
+                       num = 0;
+               } else if (num < 20) {
+                       snprintf(fn, sizeof(fn), "digits/%d", num);
+                       num = 0;
+               } else if (num < 100) {
+                       snprintf(fn, sizeof(fn), "digits/%d", (num /10) * 10);
+                       num %= 10;
+                       if (num)
+                               playa++;
+               } else if (num < 1000) {
+                       int hundreds = num / 100;
+                       /* The number prepending hundreds are in neutral */
+                       if (hundreds < 5)
+                               snprintf(fn, sizeof(fn), "digits/%dhk", hundreds);
+                       else
+                               snprintf(fn, sizeof(fn), "digits/%d", (num / 100));
+
+                       playh = hundreds;
+                       num -= 100 * hundreds;
+                       if (num && num < 20)
+                               playa++;
+                       /* The 'and' moves forward on even tens. */
+                       if (num && (num % 10) == 0)
+                               playa++;
+               } else if (num < 1000000) {
+                       res = ast_say_number_full_is(chan, num / 1000, ints, language, "n", audiofd, ctrlfd);
+                       /* Play 'and' if it's an even hundred. */
+                       if ((num % 100) == 0 && (num % 1000 != 0)) {
+                               playa++;
+                       }
+                       if (res)
+                               return res;
+                       ast_copy_string(fn, "digits/thousand", sizeof(fn));
+                       num %= 1000;
+                       if (num && (num < 20 || (num % 10 == 0)))
+                               playa++;
+               } else if (num < 1000000000) {
+                       int millions = num / 1000000;
+                       /* The number of millions is feminine */
+                       res = ast_say_number_full_is(chan, millions, ints, language, "f", audiofd, ctrlfd);
+                       if (res)
+                               return res;
+                       if (millions > 1)
+                               ast_copy_string(fn, "digits/millions", sizeof(fn));
+                       else
+                               ast_copy_string(fn, "digits/million", sizeof(fn));
+                       num %= 1000000;
+                       if (num && num < 100)
+                               playa++;
+               } else if (num < INT_MAX) {
+                       int milliards = num / 1000000000;
+                       /* The number of milliards is masculine */
+                       res = ast_say_number_full_is(chan, milliards, ints, language, "m", audiofd, ctrlfd);
+                       if (res)
+                               return res;
+                       if (milliards > 1)
+                               ast_copy_string(fn, "digits/milliards", sizeof(fn));
+                       else
+                               ast_copy_string(fn, "digits/milliard", sizeof(fn));
+                       num %= 1000000000;
+                       if (num && num < 100)
+                               playa++;
+               } else {
+                               ast_debug(1, "Number '%d' is too big for me\n", num);
+                               res = -1;
+               }
+
+               if (!res) {
+                       if (!ast_streamfile(chan, fn, language)) {
+                               if ((audiofd > -1) && (ctrlfd > -1))
+                                       res = ast_waitstream_full(chan, ints, audiofd, ctrlfd);
+                               else
+                                       res = ast_waitstream(chan, ints);
+                       }
+                       ast_stopstream(chan);
+               }
+       }
+       return res;
+}
+
+
 /*! \brief  ast_say_number_full_it:  Italian */
 static int ast_say_number_full_it(struct ast_channel *chan, int num, const char *ints, const char *language, int audiofd, int ctrlfd)
 {
@@ -2789,6 +2919,8 @@ static int say_enumeration_full(struct ast_channel *chan, int num, const char *i
           return ast_say_enumeration_full_de(chan, num, ints, language, options, audiofd, ctrlfd);
        } else if (!strncasecmp(language, "he", 2)) { /* Hebrew syntax */
                return ast_say_enumeration_full_he(chan, num, ints, language, options, audiofd, ctrlfd);
+       } else if (!strncasecmp(language, "is", 2)) { /* Icelandic syntax */
+               return ast_say_enumeration_full_is(chan, num, ints, language, options, audiofd, ctrlfd);
        } else if (!strncasecmp(language, "vi", 2)) { /* Vietnamese syntax */
                return ast_say_enumeration_full_vi(chan, num, ints, language, audiofd, ctrlfd);
        }
@@ -3329,6 +3461,182 @@ static int ast_say_enumeration_full_he(struct ast_channel *chan, int num, const
        return res;
 }
 
+/*! \brief  ast_say_enumeration_full_is: Icelandic syntax */
+static int ast_say_enumeration_full_is(struct ast_channel *chan, int num, const char *ints, const char *language, const char *options, int audiofd, int ctrlfd)
+{
+       /* options can be: '' or 'm' male gender; 'f' female gender; 'n' neuter gender; 'p' plural */
+       int res = 0, t = 0;
+       char fn[256] = "", fna[256] = "";
+       char *gender;
+
+       if (options && !strncasecmp(options, "f", 1)) {
+               gender = "F";
+       } else if (options && !strncasecmp(options, "n", 1)) {
+               gender = "N";
+       } else {
+               gender = "";
+       }
+
+       if (!num)
+               return ast_say_digits_full(chan, 0, ints, language, audiofd, ctrlfd);
+
+       while (!res && num) {
+               if (num < 0) {
+                       ast_copy_string(fn, "digits/minus", sizeof(fn)); /* kind of senseless for enumerations, but our best effort for error checking */
+                       if ( num > INT_MIN ) {
+                               num = -num;
+                       } else {
+                               num = 0;
+                       }
+               } else if (num < 100 && t) {
+                       ast_copy_string(fn, "digits/and", sizeof(fn));
+                       t = 0;
+               } else if (num < 20) {
+                       snprintf(fn, sizeof(fn), "digits/h-%d%s", num, gender);
+                       num = 0;
+               } else if (num < 100) {
+                       int ones = num % 10;
+                       if (ones) {
+                               int tens = num - ones;
+                               snprintf(fn, sizeof(fn), "digits/h-%d%s", tens, gender);
+                               num = ones;
+                               t++;
+                       }
+                       else if (t) {
+                               snprintf(fn, sizeof(fn), "digits/and");
+                               t = 0;
+                       }
+                       else {
+                               snprintf(fn, sizeof(fn), "digits/h-%d%s", num, gender);
+                               num = 0;
+                       }
+
+               } else if (num == 100 && t == 0) {
+                       snprintf(fn, sizeof(fn), "digits/h-hundred%s", gender);
+                       num = 0;
+               } else if (num < 1000) {
+                       int hundreds = num / 100;
+                       num = num % 100;
+                       if (hundreds == 1) {
+                               ast_copy_string(fn, "digits/1hk", sizeof(fn));
+                       } else {
+                               snprintf(fn, sizeof(fn), "digits/%d", hundreds);
+                       }
+                       if (num) {
+                               ast_copy_string(fna, "digits/hundred", sizeof(fna));
+                       } else {
+                               snprintf(fna, sizeof(fna), "digits/h-hundred%s", gender);
+                       }
+                       t = 1;
+               } else  if (num < 1000000) {
+                       int thousands = num / 1000;
+                       num = num % 1000;
+                       if (thousands == 1) {
+                               if (num) {
+                                       /* Thousand is a neutral word, so use the neutral recording */
+                                       ast_copy_string(fn, "digits/1hk", sizeof(fn));
+                                       ast_copy_string(fna, "digits/thousand", sizeof(fna));
+                               } else {
+                                       if (t) {
+                                               ast_copy_string(fn, "digits/1hk", sizeof(fn));
+                                               snprintf(fna, sizeof(fna), "digits/h-thousand%s", gender);
+                                       } else {
+                                               snprintf(fn, sizeof(fn), "digits/h-thousand%s", gender);
+                                       }
+                               }
+                       } else {
+                               res = ast_say_number_full_is(chan, thousands, ints, language, options, audiofd, ctrlfd);
+                               if (res) {
+                                       return res;
+                               }
+                               if (num) {
+                                       ast_copy_string(fn, "digits/thousand", sizeof(fn));
+                               } else {
+                                       snprintf(fn, sizeof(fn), "digits/h-thousand%s", gender);
+                               }
+                       }
+                       if (num)
+                               t = 1;
+               } else if (num < 1000000000) {
+                       int millions = num / 1000000;
+                       num = num % 1000000;
+                       if (millions == 1) {
+                               if (num) {
+                                       /* Million is a feminine word, so use the female form */
+                                       ast_copy_string(fn, "digits/1kvk", sizeof(fn));
+                                       ast_copy_string(fna, "digits/million", sizeof(fna));
+                               } else {
+                                       ast_copy_string(fn, "digits/1hk", sizeof(fn));
+                                       snprintf(fna, sizeof(fna), "digits/h-million%s", gender);
+                               }
+                       } else {
+                               res = ast_say_number_full_is(chan, millions, ints, language, options, audiofd, ctrlfd);
+                               if (res) {
+                                       return res;
+                               }
+                               if (num) {
+                                       ast_copy_string(fn, "digits/millions", sizeof(fn));
+                               } else {
+                                       snprintf(fn, sizeof(fn), "digits/h-million%s", gender);
+                               }
+                       }
+                       if (num)
+                               t = 1;
+               } else if (num < INT_MAX) {
+                       int billions = num / 1000000000;
+                       num = num % 1000000000;
+                       if (billions == 1) {
+                               if (num) {
+                                       ast_copy_string(fn, "digits/1", sizeof(fn));
+                                       ast_copy_string(fna, "digits/milliard", sizeof(fna));
+                               } else {
+                                       ast_copy_string(fn, "digits/1hk", sizeof(fn));
+                                       snprintf(fna, sizeof(fna), "digits/h-milliard%s", gender);
+                               }
+                       } else {
+                               res = ast_say_number_full_is(chan, billions, ints, language, options, audiofd, ctrlfd);
+                               if (res)
+                                       return res;
+                               if (num) {
+                                       ast_copy_string(fn, "digits/milliards", sizeof(fna));
+                               } else {
+                                       snprintf(fn, sizeof(fna), "digits/h-milliard%s", gender);
+                               }
+                       }
+                       if (num)
+                               t = 1;
+               } else if (num == INT_MAX) {
+                       snprintf(fn, sizeof(fn), "digits/h-last%s", gender);
+                       num = 0;
+               } else {
+                       ast_debug(1, "Number '%d' is too big for me\n", num);
+                       res = -1;
+               }
+
+               if (!res) {
+                       if (!ast_streamfile(chan, fn, language)) {
+                               if ((audiofd > -1) && (ctrlfd > -1))
+                                       res = ast_waitstream_full(chan, ints, audiofd, ctrlfd);
+                               else
+                                       res = ast_waitstream(chan, ints);
+                       }
+                       ast_stopstream(chan);
+                       if (!res) {
+                               if (strlen(fna) != 0 && !ast_streamfile(chan, fna, language)) {
+                                       if ((audiofd > -1) && (ctrlfd > -1)) {
+                                               res = ast_waitstream_full(chan, ints, audiofd, ctrlfd);
+                                       } else {
+                                               res = ast_waitstream(chan, ints);
+                                       }
+                               }
+                               ast_stopstream(chan);
+                               strcpy(fna, "");
+                       }
+               }
+       }
+       return res;
+}
+
 static int say_date(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
 {
        if (!strncasecmp(lang, "en", 2)) {        /* English syntax */
@@ -3353,6 +3661,8 @@ static int say_date(struct ast_channel *chan, time_t t, const char *ints, const
                return ast_say_date_he(chan, t, ints, lang);
        } else if (!strncasecmp(lang, "hu", 2)) { /* Hungarian syntax */
                return ast_say_date_hu(chan, t, ints, lang);
+       } else if (!strncasecmp(lang, "is", 2)) { /* Icelandic syntax */
+               return ast_say_date_is(chan, t, ints, lang);
        } else if (!strncasecmp(lang, "ka", 2)) { /* Georgian syntax */
                return ast_say_date_ka(chan, t, ints, lang);
        } else if (!strncasecmp(lang, "nl", 2)) { /* Dutch syntax */
@@ -3682,6 +3992,55 @@ int ast_say_date_he(struct ast_channel *chan, time_t t, const char *ints, const
        return res;
 }
 
+/* Icelandic syntax */
+int ast_say_date_is(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
+{
+       struct timeval when = { t, 0 };
+       struct ast_tm tm;
+       char fn[256];
+       int res = 0;
+       ast_localtime(&when, &tm, NULL);
+       if (!res) {
+               snprintf(fn, sizeof(fn), "digits/day-%d", tm.tm_wday);
+               res = ast_streamfile(chan, fn, lang);
+               if (!res)
+                       res = ast_waitstream(chan, ints);
+       }
+       if (!res)
+               res = ast_say_enumeration(chan, tm.tm_mday, ints, lang, (char * ) NULL);
+       if (!res)
+               res = ast_waitstream(chan, ints);
+       if (!res) {
+               snprintf(fn, sizeof(fn), "digits/mon-%d", tm.tm_mon);
+               res = ast_streamfile(chan, fn, lang);
+               if (!res)
+                       res = ast_waitstream(chan, ints);
+       }
+       if (!res) {
+               /* Year */
+               int year = tm.tm_year + 1900;
+               if (year > 1999) {      /* year 2000 and later */
+                       res = ast_say_number(chan, year, ints, lang, (char *) NULL);
+               } else {
+                       if (year < 1100) {
+                               /* I'm not going to handle 1100 and prior */
+                               /* We'll just be silent on the year, instead of bombing out. */
+                       } else {
+                           /* year 1100 to 1999. will anybody need this?!? */
+                               snprintf(fn, sizeof(fn), "digits/%d", (year / 100));
+                               res = wait_file(chan, ints, fn, lang);
+                               if (!res) {
+                                       res = wait_file(chan, ints, "digits/hundred", lang);
+                                       if (!res && year % 100 != 0) {
+                                               res = ast_say_number(chan, (year % 100), ints, lang, (char *) NULL);    
+                                       }
+                               }
+                       }
+               }
+       }
+       return res;
+}
+
 static int say_date_with_format(struct ast_channel *chan, time_t t, const char *ints, const char *lang, const char *format, const char *tzone)
 {
        if (!strncasecmp(lang, "en", 2)) {      /* English syntax */
@@ -3698,6 +4057,8 @@ static int say_date_with_format(struct ast_channel *chan, time_t t, const char *
                return ast_say_date_with_format_fr(chan, t, ints, lang, format, tzone);
        } else if (!strncasecmp(lang, "gr", 2)) { /* Greek syntax */
                return ast_say_date_with_format_gr(chan, t, ints, lang, format, tzone);
+       } else if (!strncasecmp(lang, "is", 2)) { /* Icelandic syntax */
+               return ast_say_date_with_format_is(chan, t, ints, lang, format, tzone);
        } else if (!strncasecmp(lang, "ja", 2)) { /* Japanese syntax */
                return ast_say_date_with_format_ja(chan, t, ints, lang, format, tzone);
        } else if (!strncasecmp(lang, "it", 2)) { /* Italian syntax */
@@ -4384,6 +4745,218 @@ int ast_say_date_with_format_de(struct ast_channel *chan, time_t t, const char *
        return res;
 }
 
+/* Icelandic syntax */
+int ast_say_date_with_format_is(struct ast_channel *chan, time_t t, const char *ints, const char *lang, const char *format, const char *tzone)
+{
+       struct timeval when = { t, 0 };
+       struct ast_tm tm;
+       int res=0, offset, sndoffset;
+       char sndfile[256], nextmsg[256];
+
+       if (!format)
+               format = "A dBY HMS";
+
+       ast_localtime(&when, &tm, tzone);
+
+       for (offset=0 ; format[offset] != '\0' ; offset++) {
+               ast_debug(1, "Parsing %c (offset %d) in %s\n", format[offset], offset, format);
+               switch (format[offset]) {
+                       /* NOTE:  if you add more options here, please try to be consistent with strftime(3) */
+                       case '\'':
+                               /* Literal name of a sound file */
+                               for (sndoffset = 0; !strchr("\'\0", format[++offset]) && (sndoffset < sizeof(sndfile) - 1) ; sndoffset++) {
+                                       sndfile[sndoffset] = format[offset];
+                               }
+                               sndfile[sndoffset] = '\0';
+                               res = wait_file(chan, ints, sndfile, lang);
+                               break;
+                       case 'A':
+                       case 'a':
+                               /* Sunday - Saturday */
+                               snprintf(nextmsg, sizeof(nextmsg), "digits/day-%d", tm.tm_wday);
+                               res = wait_file(chan, ints, nextmsg, lang);
+                               break;
+                       case 'B':
+                       case 'b':
+                       case 'h':
+                               /* January - December */
+                               snprintf(nextmsg, sizeof(nextmsg), "digits/mon-%d", tm.tm_mon);
+                               res = wait_file(chan, ints, nextmsg, lang);
+                               break;
+                       case 'm':
+                               /* Month enumerated */
+                               res = ast_say_enumeration(chan, (tm.tm_mon + 1), ints, lang, "m");
+                               break;
+                       case 'd':
+                       case 'e':
+                               /* First - Thirtyfirst */
+                               res = ast_say_enumeration(chan, tm.tm_mday, ints, lang, "m");
+                               break;
+                       case 'Y':
+                               /* Year */
+                               {
+                                       int year = tm.tm_year + 1900;
+                                       if (year > 1999) {      /* year 2000 and later */
+                                               res = ast_say_number(chan, year, ints, lang, (char *) NULL);    
+                                       } else {
+                                               if (year < 1100) {
+                                                       /* I'm not going to handle 1100 and prior */
+                                                       /* We'll just be silent on the year, instead of bombing out. */
+                                               } else {
+                                                   /* year 1100 to 1999. will anybody need this?!? */
+                                                   /* say 1967 as 'nineteen hundred seven and sixty' */
+                                                       snprintf(nextmsg, sizeof(nextmsg), "digits/%d", (year / 100) );
+                                                       res = wait_file(chan, ints, nextmsg, lang);
+                                                       if (!res) {
+                                                               res = wait_file(chan, ints, "digits/hundred", lang);
+                                                               if (!res && year % 100 != 0) {
+                                                                       res = ast_say_number(chan, (year % 100), ints, lang, (char *) NULL);    
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               }
+                               break;
+                       case 'I':
+                       case 'l':
+                               /* 12-Hour */
+                               res = wait_file(chan, ints, "digits/oclock", lang);
+                               if (tm.tm_hour == 0)
+                                       ast_copy_string(nextmsg, "digits/12", sizeof(nextmsg));
+                               else if (tm.tm_hour > 12)
+                                       snprintf(nextmsg, sizeof(nextmsg), "digits/%d", tm.tm_hour - 12);
+                               else
+                                       snprintf(nextmsg, sizeof(nextmsg), "digits/%d", tm.tm_hour);
+                               if (!res) {
+                                       res = wait_file(chan, ints, nextmsg, lang);
+                               }
+                               break;
+                       case 'H':
+                               /* 24-Hour, single digit hours preceeded by "oh" (0) */
+                               if (tm.tm_hour < 10 && tm.tm_hour > 0) {
+                                       res = wait_file(chan, ints, "digits/0", lang);
+                               }
+                               /* FALLTRHU */
+                       case 'k':
+                               /* 24-Hour */
+                               res = ast_say_number(chan, tm.tm_hour, ints, lang, "n");
+                               break;
+                       case 'M':
+                               /* Minute */
+                               if (tm.tm_min > 0 || next_item(&format[offset + 1]) == 'S') { /* zero 'digits/0' only if seconds follow */
+                                       if (tm.tm_min < 10)
+                                               res = wait_file(chan, ints, "digits/0", lang);
+                                       /* Gender depends on whether or not seconds follow */
+                                       if (next_item(&format[offset + 1]) == 'S')
+                                               res = ast_say_number(chan, tm.tm_min, ints, lang, "f");
+                                       else
+                                               res = ast_say_number(chan, tm.tm_min, ints, lang, "n");
+                               }
+                               if (!res && next_item(&format[offset + 1]) == 'S') { /* minutes only if seconds follow */
+                                       /* Say minute/minutes depending on whether minutes end in 1 */
+                                       if ((tm.tm_min % 10 == 1) && (tm.tm_min != 11)) {
+                                               res = wait_file(chan, ints, "digits/minute", lang);
+                                       } else {
+                                               res = wait_file(chan, ints, "digits/minutes", lang);
+                                       }
+                               }
+                               break;
+                       case 'P':
+                       case 'p':
+                               /* AM/PM */
+                               if (tm.tm_hour > 11)
+                                       ast_copy_string(nextmsg, "digits/p-m", sizeof(nextmsg));
+                               else
+                                       ast_copy_string(nextmsg, "digits/a-m", sizeof(nextmsg));
+                               res = wait_file(chan, ints, nextmsg, lang);
+                               break;
+                       case 'Q':
+                               /* Shorthand for "Today", "Yesterday", or AdBY */
+                               /* XXX As emphasized elsewhere, this should the native way in your
+                                * language to say the date, with changes in what you say, depending
+                                * upon how recent the date is. XXX */
+                               {
+                                       struct timeval now = ast_tvnow();
+                                       struct ast_tm tmnow;
+                                       time_t beg_today;
+
+                                       ast_localtime(&now, &tmnow, tzone);
+                                       /* This might be slightly off, if we transcend a leap second, but never more off than 1 second */
+                                       /* In any case, it saves not having to do ast_mktime() */
+                                       beg_today = now.tv_sec - (tmnow.tm_hour * 3600) - (tmnow.tm_min * 60) - (tmnow.tm_sec);
+                                       if (beg_today < t) {
+                                               /* Today */
+                                               res = wait_file(chan, ints, "digits/today", lang);
+                                       } else if (beg_today - 86400 < t) {
+                                               /* Yesterday */
+                                               res = wait_file(chan, ints, "digits/yesterday", lang);
+                                       } else {
+                                               res = ast_say_date_with_format_is(chan, t, ints, lang, "AdBY", tzone);
+                                       }
+                               }
+                               break;
+                       case 'q':
+                               /* Shorthand for "" (today), "Yesterday", A (weekday), or AdBY */
+                               /* XXX As emphasized elsewhere, this should the native way in your
+                                * language to say the date, with changes in what you say, depending
+                                * upon how recent the date is. XXX */
+                               {
+                                       struct timeval now = ast_tvnow();
+                                       struct ast_tm tmnow;
+                                       time_t beg_today;
+
+                                       ast_localtime(&now, &tmnow, tzone);
+                                       /* This might be slightly off, if we transcend a leap second, but never more off than 1 second */
+                                       /* In any case, it saves not having to do ast_mktime() */
+                                       beg_today = now.tv_sec - (tmnow.tm_hour * 3600) - (tmnow.tm_min * 60) - (tmnow.tm_sec);
+                                       if (beg_today < t) {
+                                               /* Today */
+                                       } else if ((beg_today - 86400) < t) {
+                                               /* Yesterday */
+                                               res = wait_file(chan, ints, "digits/yesterday", lang);
+                                       } else if (beg_today - 86400 * 6 < t) {
+                                               /* Within the last week */
+                                               res = ast_say_date_with_format_is(chan, t, ints, lang, "A", tzone);
+                                       } else {
+                                               res = ast_say_date_with_format_is(chan, t, ints, lang, "AdBY", tzone);
+                                       }
+                               }
+                               break;
+                       case 'R':
+                               res = ast_say_date_with_format_is(chan, t, ints, lang, "HM", tzone);
+                               break;
+                       case 'S':
+                               /* Seconds */
+                               res = wait_file(chan, ints, "digits/and", lang);
+                               if (!res) {
+                                       res = ast_say_number(chan, tm.tm_sec, ints, lang, "f");
+                                       /* Say minute/minutes depending on whether seconds end in 1 */
+                                       if (!res && (tm.tm_sec % 10 == 1) && (tm.tm_sec != 11)) {
+                                               res = wait_file(chan, ints, "digits/second", lang);
+                                       } else {
+                                               res = wait_file(chan, ints, "digits/seconds", lang);
+                                       }
+                               }
+                               break;
+                       case 'T':
+                               res = ast_say_date_with_format_is(chan, t, ints, lang, "HMS", tzone);
+                               break;
+                       case ' ':
+                       case '  ':
+                               /* Just ignore spaces and tabs */
+                               break;
+                       default:
+                               /* Unknown character */
+                               ast_log(LOG_WARNING, "Unknown character in datetime format %s: %c at pos %d\n", format, format[offset], offset);
+               }
+               /* Jump out on DTMF */
+               if (res) {
+                       break;
+               }
+       }
+       return res;
+}
+
 /*! \brief Thai syntax */
 int ast_say_date_with_format_th(struct ast_channel *chan, time_t t, const char *ints, const char *lang, const char *format, const char *tzone)
 {