ICU-20919 Merge maint/maint-66 (release-66-preview) to master
diff --git a/.cpyskip.txt b/.cpyskip.txt
index fc8af8a..878546e 100644
--- a/.cpyskip.txt
+++ b/.cpyskip.txt
@@ -16,6 +16,8 @@
.travis.yml
.appveyor.yml
.ci-builds/*
+# PGP Keys
+KEYS
# suffix matches - start with '*'. They are turned into as RE, '.brk$'
*.brk
*.bz2
diff --git a/KEYS b/KEYS
new file mode 100644
index 0000000..6af2eb7
--- /dev/null
+++ b/KEYS
@@ -0,0 +1,817 @@
+This file contains the PGP keys of ICU developers.
+Compare to http://www.apache.org/dist/ant/KEYS and read https://www.apache.org/dev/openpgp.html for background
+
+To import these keys:
+ gpg --import KEYS
+
+To update this file:
+replace $KEYID is your key's ID:
+ (gpg --list-sigs $KEYID && gpg --armor --export $KEYID ) >> KEYS
+
+
+pub 2048R/825EA2CD 2010-11-02
+uid ICU Project @IBM (International Components for Unicode Development at IBM) <icuintl@us.ibm.com>
+sig 3 825EA2CD 2010-11-02 ICU Project @IBM (International Components for Unicode Development at IBM) <icuintl@us.ibm.com>
+sig 3 FD8FABF1 2013-12-12 Steven R. Loomis (ICU Project) <srl@icu-project.org>
+sig 0F0DE47D 2014-09-17 Steven R. Loomis (CODE SIGNING KEY) <srl@icu-project.org>
+sub 2048R/1D06B1BF 2010-11-02
+sig 825EA2CD 2010-11-02 ICU Project @IBM (International Components for Unicode Development at IBM) <icuintl@us.ibm.com>
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v2.0.14 (GNU/Linux)
+
+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
+AZA1XWLRgZ3+5+vD
+=NkHr
+-----END PGP PUBLIC KEY BLOCK-----
+
+
+pub 4096R/0F0DE47D 2014-09-17 [expires: 2019-09-16]
+uid Steven R. Loomis (CODE SIGNING KEY) <srl@icu-project.org>
+sig 3 0F0DE47D 2014-09-17 Steven R. Loomis (CODE SIGNING KEY) <srl@icu-project.org>
+sig FD8FABF1 2014-09-17 Steven R. Loomis (ICU Project) <srl@icu-project.org>
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v2.0.14 (GNU/Linux)
+
+mQINBFQZ3doBEACpnA2AbRRVxu9TeDTnjHtz6h5NyMyGZIPZOX7Iv2bNS0JSAS/H
+9f2WP9+3ANHuKI32rYh0snKpWjYsgUCxZ8x2Wp4cu8YnTvvpL6a5fn5n04dTyd4s
+/gPLb51qHIL8L6MDfK2ARgukntzfLR2CoL+RwGLDOjNlnoGNw65XEdjkzZ65s05Q
+/YHdlMYJE6gQ7VzJf7JYO6z6MpclLSj106TDs0hmOSetRm66nt+G7q1iY/jMXSMm
+bs3CFe4MNoVlRnl2THFZWjGSrVNYkenKexrjJ5HD9sm2nFF5pXgMQBoyOE5gic/b
+H5hMeh3v4vmhxRL5GPmI9Ir25LwqYCaWZDu6TC8mDDJj82YQMOzvElpRU/5V8KVc
+R7e4FN7D6tXDPb8IMgHb1U7zEnKYAc+uAS5NQ6Vq/S3NoueEQNk+rwqy44yIigtD
+iOJOnLvf1JFUPNFUxRu2JUy7YzvvcyGG9PY7bxQEb/MLwa424GWRTHV4Yw+hHP6D
++XgylCyd36wfwgF48+gFeDza9gMPPDgGqJu059PjEEG9ELgBFkTVjM0+rqq1M6Jk
++WPFRgM9ioccwdVIvfVZNnkp+EUjpl/PHLYlFvP0orkK3SGc1jaqW5m3Y6P60RzM
+7OovZqtqUDTEfMZ4bQKT4EdLayBPFALvz4TgndxrZwoPi2QGmFU9Dy+snwARAQAB
+tDlTdGV2ZW4gUi4gTG9vbWlzIChDT0RFIFNJR05JTkcgS0VZKSA8c3JsQGljdS1w
+cm9qZWN0Lm9yZz6JAj4EEwECACgFAlQZ3doCGwMFCQlmAYAGCwkIBwMCBhUIAgkK
+CwQWAgMBAh4BAheAAAoJEKqprpwPDeR9St0P/RJzlQDOgtD/rq8ThV5clhcgzCQU
+J34bC9+SdvQVtG6cOu+/mE9fjPcRuMSC2saj3e6KdfdCF51ZUelFgg7Nf4VjzKXK
+egYFe11CJSYFt0YQyT3CwlX5a6lKv9BH4RJPdAjeACSNCLvuZYlkxfAFPAwJFMAH
+CeiY6P7Llm2sHHIIubK+yFviZiD1ic92ADt2llM06VwniCS2Cs0kx4NAl70jd/vW
+B/j9+k+PphZFA2IUuB80qnTdrEb+nVLQ9n0SMFwFm89bEm8IhXe0Lb//jl5ZbZCr
+afn02GvdSqU73jqOfEHS7hHemB1KSM4hEdWGFRhUf0+Xpc31aOsnKY5Wu89A8Sz/
+sKp554qAhGmBGeEjkKbw0t+k4liP+LW2efmVBt+/6msF+TGq0Rr5vFQEUNCYeIzz
+DS5nKDlKvRwgx/41RnSUYiLJS9GkCB6skxIQZvW2G7rnkGQkgmP3/tkTTX0EP0rT
+GlW4ivfp+AKoYozkZ5FNLXC06yzCSKYL0z9uoL38uR6opXo4768byzzkBOvSut3Q
+V9gbvAtZvwEibwOt0A4UhbIyv96+Oi2WI66q37Wl33F/QIsxpC9UjXju1RrAajOv
+0gY8VMxwuWHN8ewslykX5rohxgA7mkTf/YeELSiYEM1WpmKmj30uJapgDwVRpsTC
+dfSvIc/nOLji4u0iiQEcBBABAgAGBQJUGfLZAAoJEKyl2+H9j6vx+58IAMIzjJTv
+9n7Yq2iyncFyg7fmPmlKo28l6cIrmveGtzLWgO7pXw9hZy8mTwUAhg9nMnNbnjDw
+qSA+YH8rNy0MB/AaTj46faE/RlqB8bCUlnLlRk9b6IxKODOVAarFk0ltCUeNt1IP
+w8YziwUaJewmb2//tEjwG4RuliQeo8EPcg2MejsxctNJ1gWQxZFOSMk2M6I9NwW6
+xMUmqGzzKdPChdwzOBaHK2f4lB3IlPqpJVqAWuXdINzi91tCIKfo508hJSpzGm/n
+Lq4yQHJPjQjxTldFBxC8Ji5yYIQ2ZcWLaYndWH4aVbAkEelU3eIp44mU4tqhqCP6
+zJJjdh87G81yUoE=
+=pu4w
+-----END PGP PUBLIC KEY BLOCK-----
+pub 2048R/FD8FABF1 2013-12-12 [expires: 2020-10-03]
+uid Steven R. Loomis (ICU Project) <srl@icu-project.org>
+sig 3 FD8FABF1 2017-10-04 Steven R. Loomis (ICU Project) <srl@icu-project.org>
+uid [jpeg image of size 10375]
+sig 3 FD8FABF1 2017-10-04 Steven R. Loomis (ICU Project) <srl@icu-project.org>
+sig 3 FD8FABF1 2013-12-12 Steven R. Loomis (ICU Project) <srl@icu-project.org>
+sig 3 FD8FABF1 2013-12-12 Steven R. Loomis (ICU Project) <srl@icu-project.org>
+sub 2048R/A6EB1469 2013-12-12 [expires: 2019-10-04]
+sig FD8FABF1 2017-10-04 Steven R. Loomis (ICU Project) <srl@icu-project.org>
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQENBFKpD3cBCADklZIzuPsYrEy08rASB+MRnFF8hqDDUkDHuvS0bJwEDge2EEHT
+l9rnUOt3SGtSrokeniQbwut26vuY/V59SSpwClytbHovIRJ/ovjq3nUasCRPOxDL
+mlmftkRGrO6WYH6hSzmRy2x3NTgQOlE/YRtOya3CAVlkiybpYXL3L1mJA4e80NpF
+V4UG7vyfw44OHFeO6mInqkcxVgKkxwp3wBbck3FEySEZT0DTT+sQHI52fgUftb+P
+xBH1s58lLAub4o2z1hnImPN/BR0lZpiNj59vzNlqmcnRHTpa6KIz+Qeep9dwLL7i
+71lMfCsEgVyDZxkQxFpNoRYRHRkfvBEY7JLpABEBAAG0NFN0ZXZlbiBSLiBMb29t
+aXMgKElDVSBQcm9qZWN0KSA8c3JsQGljdS1wcm9qZWN0Lm9yZz6JAT4EEwECACgC
+GwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheABQJZ1XNBBQkMz/5KAAoJEKyl2+H9
+j6vxKIEH/0aFGlFOtgXNHKEG7F1mVxhjNyFlZD5O/9t8oI46vYCgVfwsn6BsHFpv
+9yAtzA6z26Zt2NsPjH6dVRfc3sGCojBIKQ/lxnyQqjAYaEbVVQ/3jhAhgnm+uAF4
+3Rr54g+6FL7aHB33nDy9FmRtmQsyVf1OySR7VXbHpwEVzWeLrgmPKR1t8P1hdiSo
+YfroXVAPy2pynXUWgWbMbuqAcTj0iAQQ7LuVOVZ8bYA922LRgUxg/ZPxiGGm2g3M
+LF/QVWMH6FAHtSf9DOaFjZqQuypkUoOZZuFfNMvKaYIe+XviZRtWls5M6H8wOz6c
+bFrLBV5XBBwKbOXULpDv/3+oehDO22vR/wAAKJ3/AAAomAEQAAEBAAAAAAAAAAAA
+AAAA/9j/4AAQSkZJRgABAQEASABIAAD/4hscSUNDX1BST0ZJTEUAAQEAABsMYXBw
+bAIQAABtbnRyUkdCIFhZWiAH3QALAAUADwAzADFhY3NwQVBQTAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFkZXNjAAABUAAAAGJkc2NtAAAB
+tAAABA5jcHJ0AAAFxAAAACN3dHB0AAAF6AAAABRyWFlaAAAF/AAAABRnWFlaAAAG
+EAAAABRiWFlaAAAGJAAAABRyVFJDAAAGOAAACAxhYXJnAAAORAAAACB2Y2d0AAAO
+ZAAABhJuZGluAAAUeAAABj5jaGFkAAAauAAAACxtbW9kAAAa5AAAAChiVFJDAAAG
+OAAACAxnVFJDAAAGOAAACAxhYWJnAAAORAAAACBhYWdnAAAORAAAACBkZXNjAAAA
+AAAAAAhEaXNwbGF5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbWx1
+YwAAAAAAAAAhAAAADGhySFIAAAAUAAABnGtvS1IAAAAMAAABsG5iTk8AAAASAAAB
+vGlkAAAAAAASAAABzmh1SFUAAAAUAAAB4GNzQ1oAAAAWAAAB9GRhREsAAAAcAAAC
+CnVrVUEAAAAcAAACJmFyAAAAAAAUAAACQml0SVQAAAAUAAACVnJvUk8AAAASAAAC
+am5sTkwAAAAWAAACfGhlSUwAAAAWAAACkmVzRVMAAAASAAACamZpRkkAAAAQAAAC
+qHpoVFcAAAAMAAACuHZpVk4AAAAOAAACxHNrU0sAAAAWAAAC0npoQ04AAAAMAAAC
+uHJ1UlUAAAAkAAAC6GZyRlIAAAAWAAADDG1zAAAAAAASAAADImNhRVMAAAAYAAAD
+NHRoVEgAAAAMAAADTGRlREUAAAAQAAADWGVuVVMAAAASAAADaHB0QlIAAAAYAAAD
+enBsUEwAAAASAAADkmVsR1IAAAAiAAADpHN2U0UAAAAQAAADxnRyVFIAAAAUAAAD
+1mphSlAAAAAOAAAD6nB0UFQAAAAWAAAD+ABMAEMARAAgAHUAIABiAG8AagBpzuy3
+7AAgAEwAQwBEAEYAYQByAGcAZQAtAEwAQwBEAEwAQwBEACAAVwBhAHIAbgBhAFMA
+egDtAG4AZQBzACAATABDAEQAQgBhAHIAZQB2AG4A/QAgAEwAQwBEAEwAQwBEAC0A
+ZgBhAHIAdgBlAHMAawDmAHIAbQQaBD4EOwRMBD4EQAQ+BDIEOAQ5ACAATABDAEQg
+DwBMAEMARAAgBkUGRAZIBkYGKQBMAEMARAAgAGMAbwBsAG8AcgBpAEwAQwBEACAA
+YwBvAGwAbwByAEsAbABlAHUAcgBlAG4ALQBMAEMARCAPAEwAQwBEACAF5gXRBeIF
+1QXgBdkAVgDkAHIAaQAtAEwAQwBEX2mCcgAgAEwAQwBEAEwAQwBEACAATQDgAHUA
+RgBhAHIAZQBiAG4A6QAgAEwAQwBEBCYEMgQ1BEIEPQQ+BDkAIAQWBBoALQQ0BDgE
+QQQ/BDsENQQ5AEwAQwBEACAAYwBvAHUAbABlAHUAcgBXAGEAcgBuAGEAIABMAEMA
+RABMAEMARAAgAGUAbgAgAGMAbwBsAG8AcgBMAEMARAAgDioONQBGAGEAcgBiAC0A
+TABDAEQAQwBvAGwAbwByACAATABDAEQATABDAEQAIABDAG8AbABvAHIAaQBkAG8A
+SwBvAGwAbwByACAATABDAEQDiAOzA8cDwQPJA7wDtwAgA78DuAPMA70DtwAgAEwA
+QwBEAEYA5AByAGcALQBMAEMARABSAGUAbgBrAGwAaQAgAEwAQwBEMKsw6TD8ACAA
+TABDAEQATABDAEQAIABhACAAQwBvAHIAZQBzAAB0ZXh0AAAAAENvcHlyaWdodCBB
+cHBsZSBJbmMuLCAyMDEzAABYWVogAAAAAAAA81IAAQAAAAEWz1hZWiAAAAAAAABq
+egAAPKoAAAYFWFlaIAAAAAAAAGhwAACq+AAAH7dYWVogAAAAAAAAI+wAABheAACt
+cWN1cnYAAAAAAAAEAAAAAAUACgAPABQAGQAeACMAKAAtADIANgA7AEAARQBKAE8A
+VABZAF4AYwBoAG0AcgB3AHwAgQCGAIsAkACVAJoAnwCjAKgArQCyALcAvADBAMYA
+ywDQANUA2wDgAOUA6wDwAPYA+wEBAQcBDQETARkBHwElASsBMgE4AT4BRQFMAVIB
+WQFgAWcBbgF1AXwBgwGLAZIBmgGhAakBsQG5AcEByQHRAdkB4QHpAfIB+gIDAgwC
+FAIdAiYCLwI4AkECSwJUAl0CZwJxAnoChAKOApgCogKsArYCwQLLAtUC4ALrAvUD
+AAMLAxYDIQMtAzgDQwNPA1oDZgNyA34DigOWA6IDrgO6A8cD0wPgA+wD+QQGBBME
+IAQtBDsESARVBGMEcQR+BIwEmgSoBLYExATTBOEE8AT+BQ0FHAUrBToFSQVYBWcF
+dwWGBZYFpgW1BcUF1QXlBfYGBgYWBicGNwZIBlkGagZ7BowGnQavBsAG0QbjBvUH
+BwcZBysHPQdPB2EHdAeGB5kHrAe/B9IH5Qf4CAsIHwgyCEYIWghuCIIIlgiqCL4I
+0gjnCPsJEAklCToJTwlkCXkJjwmkCboJzwnlCfsKEQonCj0KVApqCoEKmAquCsUK
+3ArzCwsLIgs5C1ELaQuAC5gLsAvIC+EL+QwSDCoMQwxcDHUMjgynDMAM2QzzDQ0N
+Jg1ADVoNdA2ODakNww3eDfgOEw4uDkkOZA5/DpsOtg7SDu4PCQ8lD0EPXg96D5YP
+sw/PD+wQCRAmEEMQYRB+EJsQuRDXEPURExExEU8RbRGMEaoRyRHoEgcSJhJFEmQS
+hBKjEsMS4xMDEyMTQxNjE4MTpBPFE+UUBhQnFEkUahSLFK0UzhTwFRIVNBVWFXgV
+mxW9FeAWAxYmFkkWbBaPFrIW1hb6Fx0XQRdlF4kXrhfSF/cYGxhAGGUYihivGNUY
++hkgGUUZaxmRGbcZ3RoEGioaURp3Gp4axRrsGxQbOxtjG4obshvaHAIcKhxSHHsc
+oxzMHPUdHh1HHXAdmR3DHeweFh5AHmoelB6+HukfEx8+H2kflB+/H+ogFSBBIGwg
+mCDEIPAhHCFIIXUhoSHOIfsiJyJVIoIiryLdIwojOCNmI5QjwiPwJB8kTSR8JKsk
+2iUJJTglaCWXJccl9yYnJlcmhya3JugnGCdJJ3onqyfcKA0oPyhxKKIo1CkGKTgp
+aymdKdAqAio1KmgqmyrPKwIrNitpK50r0SwFLDksbiyiLNctDC1BLXYtqy3hLhYu
+TC6CLrcu7i8kL1ovkS/HL/4wNTBsMKQw2zESMUoxgjG6MfIyKjJjMpsy1DMNM0Yz
+fzO4M/E0KzRlNJ402DUTNU01hzXCNf02NzZyNq426TckN2A3nDfXOBQ4UDiMOMg5
+BTlCOX85vDn5OjY6dDqyOu87LTtrO6o76DwnPGU8pDzjPSI9YT2hPeA+ID5gPqA+
+4D8hP2E/oj/iQCNAZECmQOdBKUFqQaxB7kIwQnJCtUL3QzpDfUPARANER0SKRM5F
+EkVVRZpF3kYiRmdGq0bwRzVHe0fASAVIS0iRSNdJHUljSalJ8Eo3Sn1KxEsMS1NL
+mkviTCpMcky6TQJNSk2TTdxOJU5uTrdPAE9JT5NP3VAnUHFQu1EGUVBRm1HmUjFS
+fFLHUxNTX1OqU/ZUQlSPVNtVKFV1VcJWD1ZcVqlW91dEV5JX4FgvWH1Yy1kaWWlZ
+uFoHWlZaplr1W0VblVvlXDVchlzWXSddeF3JXhpebF69Xw9fYV+zYAVgV2CqYPxh
+T2GiYfViSWKcYvBjQ2OXY+tkQGSUZOllPWWSZedmPWaSZuhnPWeTZ+loP2iWaOxp
+Q2maafFqSGqfavdrT2una/9sV2yvbQhtYG25bhJua27Ebx5veG/RcCtwhnDgcTpx
+lXHwcktypnMBc11zuHQUdHB0zHUodYV14XY+dpt2+HdWd7N4EXhueMx5KnmJeed6
+RnqlewR7Y3vCfCF8gXzhfUF9oX4BfmJ+wn8jf4R/5YBHgKiBCoFrgc2CMIKSgvSD
+V4O6hB2EgITjhUeFq4YOhnKG14c7h5+IBIhpiM6JM4mZif6KZIrKizCLlov8jGOM
+yo0xjZiN/45mjs6PNo+ekAaQbpDWkT+RqJIRknqS45NNk7aUIJSKlPSVX5XJljSW
+n5cKl3WX4JhMmLiZJJmQmfyaaJrVm0Kbr5wcnImc951kndKeQJ6unx2fi5/6oGmg
+2KFHobaiJqKWowajdqPmpFakx6U4pammGqaLpv2nbqfgqFKoxKk3qamqHKqPqwKr
+davprFys0K1ErbiuLa6hrxavi7AAsHWw6rFgsdayS7LCszizrrQltJy1E7WKtgG2
+ebbwt2i34LhZuNG5SrnCuju6tbsuu6e8IbybvRW9j74KvoS+/796v/XAcMDswWfB
+48JfwtvDWMPUxFHEzsVLxcjGRsbDx0HHv8g9yLzJOsm5yjjKt8s2y7bMNcy1zTXN
+tc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ
+8dp22vvbgNwF3IrdEN2W3hzeot8p36/gNuC94UThzOJT4tvjY+Pr5HPk/OWE5g3m
+lucf56noMui86Ubp0Opb6uXrcOv77IbtEe2c7ijutO9A78zwWPDl8XLx//KM8xnz
+p/Q09ML1UPXe9m32+/eK+Bn4qPk4+cf6V/rn+3f8B/yY/Sn9uv5L/tz/bf//cGFy
+YQAAAAAAAwAAAAJmZgAA8qcAAA1ZAAAT0AAACg52Y2d0AAAAAAAAAAAAAwEAAAIA
+AAAIACQAVwCkAQYBRgGTAe8CTAK0AycDoAQnBLoFWAYABrUHeAhGCRsJ+ArhC9IM
+zg3RDxQQaBG8ExkUdhXZF0kYuBotG6YdKx6vIDghyiNgJPomkygwKdUrZSzsLnUv
+9zF5Mvk0bzXjN084sToMO2M8tD3+P0VAhUHFQvlEHkVFRmpHkUi2SdxLAUwmTUlO
+bU+PULFR01L2VBlVPFZSV2pYf1mWWq1bw1zZXe5fAmAWYSliPGNOZGFldGaBZ4Bo
+gmmDaoRrhmyJbYpujG+OcJFxk3KVc5d0m3WVdnJ3UHgteQt56nrIe6Z8hn1nfkh/
+KoANgPGB1oK7g5+EhYVxhmOHX4hniXuKoYvXjR2Ob4/PkTySkJOylNaV+JcbmECZ
+ZpqOm7mc6Z4cn1KgjaHKovikDqUmpj+nWah3qZiqvKvlrRKuQ694sLGx67MftFC1
+frart9W4/boku0q8b72Vvr2/5cEPwjbDUcRhxWPGVsc7yBLI3MmbylXLDcvFzILN
+Rs4Rzt/PrdB80UrSGdLn07PUf9VJ1hLW2teZ2FHZBtm82nTbLtvo3KXdYt4h3uDf
+n+Bf4R/h3+Kc41bkDuTD5XPmIObL53LoGOi86WTqD+rC63XsKOzd7ZLuSO7/77jw
+c/Ev8ezywfOp9JL1dvZa9z/4Kvkf+iP7QvyG/gv//wAAAAcAHgBKAIsA4wEqAW4B
+wAIZAnQC2gNHA70EQQTSBW0GEQa9B3QINgkACdUKrQuODIENlg7MEAARPBJ6E7oV
+AxZPF6MY+BpUG7QdGh6GH/IhZCLWJE0lxycvKJMp9ytXLLMuCy9gMLAx+DM8NHc1
+rTbeOAo5MDpWO3c8kD2dPqw/ukDKQdlC6UP5RQpGGkcqSD1JTkpgS3FMg02UTp5P
+qVCyUbtSxlPQVNlV4lbqV/NY+1oDWwtcFF0dXiFfIWAfYR1iG2MZZBZlE2YPZwpo
+BWkAaftq9WvwbOdt2G7Ib7ZwpHGQcntzZHRLdTF2Fnb6d954wnmmeod7Z3xKfS5+
+FX8Cf/OA6oHpgvCD/YUShi2HTYhuiYuKp4vEjOKOAY8hkEORZpKNk7WU4JYNlzyY
+ZZmHmqqbzJzxnhefP6BpoZaixqP5pS6mZqegqNqqFKtOrIatvq72sCyxZLKbs9W1
+D7ZLt4m4x7oBuzq8cL2jvtG/+cEbwjbDTMRcxWnGcsdtyFrJQ8oryxPL+8zizcjO
+rs+T0HjRXtJD0yTUAdTd1bnWlNdv2ErZJdn/2tnbstyM3WbeQN8b3/fg1OGx4o3j
+auRH5STmAebd57nolOlp6j3rEevl7Lvtku5q70TwIPD98dzyu/Oc9I71lPas99X5
+C/pG+3n8mf2c/oD/Sf//AAAABQAWADUAZACkAPUBLQFqAbACAAJTArADFgOBA/UE
+cAT1BYMGHAa+B2UIEwjGCX4KQwsqDCYNJg4lDy4QNxFHElcTbhSGFaUWxxfxGRka
+SBt5HK0d4x8bIEQhaiKNI7Ak0yXwJwsoISk1KkArSCxKLUcuQS82MCoxHDIGMukz
+yTSqNY02bzdQODM5FDn1Otc7uDyaPXw+Xj9AQCJBBUHpQs5Ds0SYRX5GY0dISCxJ
+EEn1StlLvEyhTYZOaU9IUCdRBlHlUsRTolSBVV5WO1cZV/ZY01mvWo1baVxCXRtd
+817MX6Rge2FSYihi/mPTZKllfmZTZyhn+mjLaZxqbWs9bA1s3G2rbnlvRXAScN5x
+qXJ1cztz/3TBdYN2Q3cCd794e3kyeed6m3tKe/h8rH1xflp/R4A0gSKCEIL/g+2E
+2oXHhrOHnYiIiXCKaotpjGiNaI5pj2mQaJFmkmKTXZRVlUyWQZdEmFOZZ5p7m5Gc
+pp26ns2f3aDrofejAKQHpR6mU6ePqMyqC6tNrJGt2q8osHmxzrMntIO13LckuGi5
+prrcvAu9Mr5Sv2zAgMGRwp/DrcS4xcLGy8fWyOLJ78r+zA/NI846z1PQb9GN0rLT
+2dUB1irXU9h92ana1twF3TXeZ9+b4O/iheQo5d/nv+nQ7CHuuPGn9N/4XPwV//8A
+AG5kaW4AAAAAAAAGNgAAmj0AAFllAABRRwAAjYQAACXWAAAXCgAAUA0AAFQ5AAKK
+PQACRR4AAYzMAAMBAAACAAAACwAiAD0AWgB3AJUAswDRAPABEAEwAVEBcgGUAa8B
+ywHoAgYCJAJDAmMCgwKjAsQC5gMIAysDTgNxA5UDugPfBAQEKgRQBHcEngTGBO8F
+GAVBBWsFmAXHBfYGJgZXBooGvQbyBycHXweXB9IIDghMCI0IzwkTCVkJoQnsCjgK
+hwrXCykLgQveDDsMmgz6DVwNwA4lDowO9Q9gD8wQOhCqERwRjxIEEnoS8hNyE/IU
+dRT5FX8WBxaRFx0XrBg8GM8ZZBn7GpQbLxvLHGwdGR3GHnYfKB/cIJEhSiIEIsAj
+fiQ/JQIlxyaNJ1koRyk3KisrISwaLRYuFC8TMBUxGDIdMyM0LDU6Nkc3UThWOVI6
+RTsvPA885j22PoA/RkAIQMVBgEJRQ0JEM0UnRh1HE0gLSQNJ+0rzS+lM303UTshP
+u1CuUaZSwFPaVPVWEVctWEhZYlp7W5JcqF27Xs5f32DwYgBjFmQzZVJmdmecaMdp
+9WsmbFptkW7JcANxPXJ5c7R08HY6d5F4+np5fBR90H+2gcGD5YYNiCqKLYwnjiSQ
+JJIolDKWQ5hbmn2cpp7voV6jzKY4qKKrCq1yr9yySbS5ty65sLxFvvHBt8SXx43K
+lM2L0F/TN9YQ2Orbwt6W4WjkOOa26PrrVO258B7yevTC9uv46fq6/Fj9uv7p//8A
+AAANACcARgBnAIcAqADKAOwBDwEyAVcBfAGfAb4B3QH+Ah8CQQJkAogCrALRAvYD
+HANDA2oDkgO6A+MEDAQ2BGEEjAS4BOUFEgVABW4FoQXUBgkGPgZ1Bq4G5wcjB2AH
+nwffCCMIaAiwCPoJRwmWCegKPQqTCuwLRwurDA8MdgzeDUcNsw4gDo8O/w9yD+YQ
+WxDTEUwRxxJEEsITRhPMFFQU3xVrFfkWiRccF7EYSBjhGXwaGhq5G1ob/RymHVQe
+BB63H2wgJCDeIZsiWyMeI+MkqiV0JkEnDyfnKMQppSqJK3IsXi1PLkUvPzA9MT8y
+RDNMNFo1bDZ+N5E4oTmuOrc7uzy5PbM+qD+aQIpBd0JoQ11EVUVORkhHREhBST5K
+PEs6TDhNNk40TzNQMVEvUjlTSFRYVWpWfFePWKNZtlrJW9xc7l4AXxJgI2E1YkZj
+WWRuZYRmnme5aNdp9msYbDttYG6Gb6xw03H6cyJ0SnV0dqN31XkKekN7gXzFfhF/
+ZIDBgiaDlIULhomIDomzi3KNNI76kMOSkpRlljyYGJn3m9mdv5+6obujv6XIp9Wp
+56v9rhiwNrJZtH62pLjMuvW9Ib9RwYPDusX0yDTKd8zDzybRi9Pz1lrYwdsl3Yjf
+6eJK5KrnCukv6y7tFu7n8KryZ/Qr9gT4BPpD/OP//wAAABIANQBbAIMArADVAP4B
+KQFVAYMBqwHRAfgCIQJKAnUCoALNAvoDKQNYA4gDuQPqBB0EUASEBLkE7wUmBV4F
+mgXYBhgGWgadBuIHKgd0B8EIEAhjCLkJEwlxCdIKNwqfCwoLewvzDG4M6w1qDe0O
+cQ75D4MQEBCgETIRxxJeEvgTlBQyFNIVdRYaFsIXbRgbGMwZfxo2Gu8bqhxpHS8d
++B7DH5IgZCE5IhIi7iPNJK8llSZ9J2koXClSKkwrSSxLLVAuWi9nMHgxjTKlM8A0
+5TYNNzk4aTmdOtY8FD1YPqA/7UE+QptEBEVxRuZIY0nrS4BNIU7RUIxSF1NkVLJW
+AldUWKhZ/1tbXLleHV+FYPFiYmO6ZRBmaGfBaRxqe2vdbURur3AgcZdzE3SUdfp3
+VniyehB7b3zQfjV/nYEKgnyD9IVyhvWIfonTix2MaI2zjv+QSZGTktyUI5VplqyX
+75kvmm+brpzsni6fgqDbojqjoaUQpoioC6mZqzGs1K6AsDKx6rOmtWi3Lrj3usC8
+i75VwB/B6MOvxXTHN8j5yrrMec4uz+TRm9NU1Q3WyNiD2j7b+d20327hKeLj5J7m
+Ked+6M7qF+tZ7JHtuu7V7+fw4/Hb8r/zoPRw9UH1//a993P4I/jU+Xj6HfrA+1z7
++PyS/Sf9vP5Q/uD/b///AABzZjMyAAAAAAABDEIAAAXe///zJgAAB5IAAP2R///7
+ov///aMAAAPcAADAbG1tb2QAAAAAAAAGEAAAnKUAAAAAw7GoAAAAAAAAAAAAAAAA
+AAAAAAD/4QCARXhpZgAATU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAA
+SgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAIdpAAQAAAABAAAAWgAAAAAAAABIAAAA
+AQAAAEgAAAABAAKgAgAEAAAAAQAAADugAwAEAAAAAQAAAFMAAAAA/9sAQwACAQEC
+AQECAgECAgICAgMFAwMDAwMGBAQDBQcGBwcHBgYGBwgLCQcICggGBgkNCQoLCwwM
+DAcJDQ4NDA4LDAwL/9sAQwECAgIDAgMFAwMFCwgGCAsLCwsLCwsLCwsLCwsLCwsL
+CwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsL/8AAEQgAUwA7AwEiAAIR
+AQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMC
+BAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJ
+ChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3
+eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS
+09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAA
+AAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEH
+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
+DyXQOcA=
+=x0WA
+-----END PGP PUBLIC KEY BLOCK-----
+
+pub rsa4096 2015-11-15 [SC]
+ FFA9129A180D765B7A5BEA1C9B432B27D1BA20D7
+uid [desconocida] Fredrik Roubert <fredrik@roubert.name>
+sig C191C1EE0DED33EA 2015-11-23 [ID de usuario no encontrado]
+sig 216D7E92EB61819E 2015-11-25 [ID de usuario no encontrado]
+sig 1C2C9D2A7564DC9F 2015-12-02 [ID de usuario no encontrado]
+sig A42898C74BF155DB 2015-12-03 [ID de usuario no encontrado]
+sig 3 C44AB8B850327DF9 2015-11-15 [ID de usuario no encontrado]
+sig 4719E4FC1E16C930 2015-12-02 [ID de usuario no encontrado]
+sig 4484F5552D1E8BB4 2015-12-02 [ID de usuario no encontrado]
+sig 6C6580E77BD756C4 2015-12-02 [ID de usuario no encontrado]
+sig 7F61756177978F76 2015-12-08 [ID de usuario no encontrado]
+sig 47712171F2ED62FB 2016-04-06 [ID de usuario no encontrado]
+sig 2 80D88B22D4330331 2015-11-21 [ID de usuario no encontrado]
+sig 3 9C187BA29B2157F8 2015-12-01 [ID de usuario no encontrado]
+sig 3 6DFD2ACE211BAAD0 2015-11-23 [ID de usuario no encontrado]
+sig 3 4445C665FFAD9AE0 2015-11-23 [ID de usuario no encontrado]
+sig 3 2DFF526BB17F76C6 2016-06-11 [ID de usuario no encontrado]
+sig 3 9B432B27D1BA20D7 2015-11-15 Fredrik Roubert <fredrik@roubert.name>
+uid [desconocida] Fredrik Roubert <roubert@google.com>
+sig C191C1EE0DED33EA 2015-11-23 [ID de usuario no encontrado]
+sig 216D7E92EB61819E 2015-11-25 [ID de usuario no encontrado]
+sig 1C2C9D2A7564DC9F 2015-12-02 [ID de usuario no encontrado]
+sig A42898C74BF155DB 2015-12-03 [ID de usuario no encontrado]
+sig 3 C44AB8B850327DF9 2015-11-15 [ID de usuario no encontrado]
+sig 4719E4FC1E16C930 2015-12-02 [ID de usuario no encontrado]
+sig 4484F5552D1E8BB4 2015-12-02 [ID de usuario no encontrado]
+sig 6C6580E77BD756C4 2015-12-02 [ID de usuario no encontrado]
+sig 7F61756177978F76 2015-12-08 [ID de usuario no encontrado]
+sig 47712171F2ED62FB 2016-04-06 [ID de usuario no encontrado]
+sig 2 80D88B22D4330331 2015-11-21 [ID de usuario no encontrado]
+sig 3 9C187BA29B2157F8 2015-12-01 [ID de usuario no encontrado]
+sig 3 6DFD2ACE211BAAD0 2015-11-23 [ID de usuario no encontrado]
+sig 3 4445C665FFAD9AE0 2015-11-23 [ID de usuario no encontrado]
+sig 3 2DFF526BB17F76C6 2016-06-11 [ID de usuario no encontrado]
+sig 3 9B432B27D1BA20D7 2015-11-15 Fredrik Roubert <fredrik@roubert.name>
+sub rsa4096 2015-11-15 [E]
+sig 9B432B27D1BA20D7 2015-11-15 Fredrik Roubert <fredrik@roubert.name>
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+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
+=vIM2
+-----END PGP PUBLIC KEY BLOCK-----
+
+pub rsa4096 2018-06-28 [SC]
+ 827320CB5E847C916CF49D06147473802F35F6CB
+uid [ultimate] Jeff Genovy <jefgen@microsoft.com>
+sig 3 147473802F35F6CB 2019-12-04 Jeff Genovy <jefgen@microsoft.com>
+uid [ultimate] Jeff Genovy <Jeff.Genovy@microsoft.com>
+sig 3 147473802F35F6CB 2019-12-04 Jeff Genovy <jefgen@microsoft.com>
+sub rsa4096 2018-06-28 [E]
+sig 147473802F35F6CB 2018-06-28 Jeff Genovy <jefgen@microsoft.com>
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+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
+TMuS5W4GIP4f7uR1uQ==
+=Veoq
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/icu4c/source/common/uloc_tag.cpp b/icu4c/source/common/uloc_tag.cpp
index 1c10c48..2b76a92 100644
--- a/icu4c/source/common/uloc_tag.cpp
+++ b/icu4c/source/common/uloc_tag.cpp
@@ -1110,6 +1110,19 @@
}
}
+static void _sortVariants(VariantListEntry* first) {
+ for (VariantListEntry* var1 = first; var1 != NULL; var1 = var1->next) {
+ for (VariantListEntry* var2 = var1->next; var2 != NULL; var2 = var2->next) {
+ // Swap var1->variant and var2->variant.
+ if (uprv_compareInvCharsAsAscii(var1->variant, var2->variant) > 0) {
+ const char* temp = var1->variant;
+ var1->variant = var2->variant;
+ var2->variant = temp;
+ }
+ }
+ }
+}
+
static void
_appendVariantsToLanguageTag(const char* localeID, icu::ByteSink& sink, UBool strict, UBool *hadPosix, UErrorCode* status) {
char buf[ULOC_FULLNAME_CAPACITY];
@@ -1199,6 +1212,9 @@
if (varFirst != NULL) {
int32_t varLen;
+ /* per UTS35, we should sort the variants */
+ _sortVariants(varFirst);
+
/* write out validated/normalized variants to the target */
var = varFirst;
while (var != NULL) {
@@ -2822,6 +2838,7 @@
}
/* variants */
+ _sortVariants(lt.getAlias()->variants);
n = ultag_getVariantsSize(lt.getAlias());
if (n > 0) {
if (noRegion) {
diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp
index d19f651..4015250 100644
--- a/icu4c/source/i18n/decimfmt.cpp
+++ b/icu4c/source/i18n/decimfmt.cpp
@@ -1834,7 +1834,8 @@
char16_t localBuffer[localCapacity];
char16_t* ptr = localBuffer + localCapacity;
int8_t group = 0;
- for (int8_t i = 0; i < fields->fastData.maxInt && (input != 0 || i < fields->fastData.minInt); i++) {
+ int8_t minInt = (fields->fastData.minInt < 1)? 1: fields->fastData.minInt;
+ for (int8_t i = 0; i < fields->fastData.maxInt && (input != 0 || i < minInt); i++) {
if (group++ == 3 && fields->fastData.cpGroupingSeparator != 0) {
*(--ptr) = fields->fastData.cpGroupingSeparator;
group = 1;
diff --git a/icu4c/source/i18n/dtptngen.cpp b/icu4c/source/i18n/dtptngen.cpp
index c5f8618..4948d2c 100644
--- a/icu4c/source/i18n/dtptngen.cpp
+++ b/icu4c/source/i18n/dtptngen.cpp
@@ -2162,6 +2162,25 @@
}
skeletonResult.type[field] = subField;
}
+
+ // #20739, we have a skeleton with milliseconde, but no seconds
+ if (!skeletonResult.original.isFieldEmpty(UDATPG_FRACTIONAL_SECOND_FIELD)
+ && skeletonResult.original.isFieldEmpty(UDATPG_SECOND_FIELD)) {
+ // Force the use of seconds
+ for (i = 0; dtTypes[i].patternChar != 0; i++) {
+ if (dtTypes[i].field == UDATPG_SECOND_FIELD) {
+ // first entry for UDATPG_SECOND_FIELD
+ skeletonResult.original.populate(UDATPG_SECOND_FIELD, dtTypes[i].patternChar, dtTypes[i].minLen);
+ skeletonResult.baseOriginal.populate(UDATPG_SECOND_FIELD, dtTypes[i].patternChar, dtTypes[i].minLen);
+ // We add value.length, same as above, when type is first initialized.
+ // The value we want to "fake" here is "s", and 1 means "s".length()
+ int16_t subField = dtTypes[i].type;
+ skeletonResult.type[UDATPG_SECOND_FIELD] = (subField > 0) ? subField + 1 : subField;
+ break;
+ }
+ }
+ }
+
// #13183, handle special behavior for day period characters (a, b, B)
if (!skeletonResult.original.isFieldEmpty(UDATPG_HOUR_FIELD)) {
if (skeletonResult.original.getFieldChar(UDATPG_HOUR_FIELD)==LOW_H || skeletonResult.original.getFieldChar(UDATPG_HOUR_FIELD)==CAP_K) {
diff --git a/icu4c/source/i18n/number_compact.cpp b/icu4c/source/i18n/number_compact.cpp
index 3d25999..e3c6bbc 100644
--- a/icu4c/source/i18n/number_compact.cpp
+++ b/icu4c/source/i18n/number_compact.cpp
@@ -215,19 +215,25 @@
/// END OF CompactData.java; BEGIN CompactNotation.java ///
///////////////////////////////////////////////////////////
-CompactHandler::CompactHandler(CompactStyle compactStyle, const Locale &locale, const char *nsName,
- CompactType compactType, const PluralRules *rules,
- MutablePatternModifier *buildReference, const MicroPropsGenerator *parent,
- UErrorCode &status)
- : rules(rules), parent(parent) {
+CompactHandler::CompactHandler(
+ CompactStyle compactStyle,
+ const Locale &locale,
+ const char *nsName,
+ CompactType compactType,
+ const PluralRules *rules,
+ MutablePatternModifier *buildReference,
+ bool safe,
+ const MicroPropsGenerator *parent,
+ UErrorCode &status)
+ : rules(rules), parent(parent), safe(safe) {
data.populate(locale, nsName, compactStyle, compactType, status);
- if (buildReference != nullptr) {
+ if (safe) {
// Safe code path
precomputeAllModifiers(*buildReference, status);
- safe = TRUE;
} else {
// Unsafe code path
- safe = FALSE;
+ // Store the MutablePatternModifier reference.
+ unsafePatternModifier = buildReference;
}
}
@@ -309,8 +315,9 @@
// C++ Note: Use unsafePatternInfo for proper lifecycle.
ParsedPatternInfo &patternInfo = const_cast<CompactHandler *>(this)->unsafePatternInfo;
PatternParser::parseToPatternInfo(UnicodeString(patternString), patternInfo, status);
- static_cast<MutablePatternModifier*>(const_cast<Modifier*>(micros.modMiddle))
- ->setPatternInfo(&patternInfo, UNUM_COMPACT_FIELD);
+ unsafePatternModifier->setPatternInfo(&unsafePatternInfo, UNUM_COMPACT_FIELD);
+ unsafePatternModifier->setNumberProperties(quantity.signum(), StandardPlural::Form::COUNT);
+ micros.modMiddle = unsafePatternModifier;
}
// We already performed rounding. Do not perform it again.
diff --git a/icu4c/source/i18n/number_compact.h b/icu4c/source/i18n/number_compact.h
index dda5f9f..199d39f 100644
--- a/icu4c/source/i18n/number_compact.h
+++ b/icu4c/source/i18n/number_compact.h
@@ -56,10 +56,16 @@
class CompactHandler : public MicroPropsGenerator, public UMemory {
public:
- CompactHandler(CompactStyle compactStyle, const Locale &locale, const char *nsName,
- CompactType compactType, const PluralRules *rules,
- MutablePatternModifier *buildReference, const MicroPropsGenerator *parent,
- UErrorCode &status);
+ CompactHandler(
+ CompactStyle compactStyle,
+ const Locale &locale,
+ const char *nsName,
+ CompactType compactType,
+ const PluralRules *rules,
+ MutablePatternModifier *buildReference,
+ bool safe,
+ const MicroPropsGenerator *parent,
+ UErrorCode &status);
~CompactHandler() U_OVERRIDE;
@@ -74,6 +80,7 @@
int32_t precomputedModsLength = 0;
CompactData data;
ParsedPatternInfo unsafePatternInfo;
+ MutablePatternModifier* unsafePatternModifier;
UBool safe;
/** Used by the safe code path */
diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp
index 2c4c2ce..abbc23d 100644
--- a/icu4c/source/i18n/number_decimalquantity.cpp
+++ b/icu4c/source/i18n/number_decimalquantity.cpp
@@ -319,10 +319,14 @@
}
Signum DecimalQuantity::signum() const {
- if (isNegative()) {
+ bool isZero = (isZeroish() && !isInfinite());
+ bool isNeg = isNegative();
+ if (isZero && isNeg) {
+ return SIGNUM_NEG_ZERO;
+ } else if (isZero) {
+ return SIGNUM_POS_ZERO;
+ } else if (isNeg) {
return SIGNUM_NEG;
- } else if (isZeroish() && !isInfinite()) {
- return SIGNUM_ZERO;
} else {
return SIGNUM_POS;
}
diff --git a/icu4c/source/i18n/number_formatimpl.cpp b/icu4c/source/i18n/number_formatimpl.cpp
index 2e2c4a9..708aaa5 100644
--- a/icu4c/source/i18n/number_formatimpl.cpp
+++ b/icu4c/source/i18n/number_formatimpl.cpp
@@ -111,7 +111,6 @@
return;
}
fMicroPropsGenerator->processQuantity(inValue, microsOut, status);
- microsOut.rounder.apply(inValue, status);
microsOut.integerWidth.apply(inValue, status);
}
@@ -124,7 +123,6 @@
return fMicros; // must always return a value
}
fMicroPropsGenerator->processQuantity(inValue, fMicros, status);
- fMicros.rounder.apply(inValue, status);
fMicros.integerWidth.apply(inValue, status);
return fMicros;
}
@@ -384,11 +382,7 @@
patternModifier->setSymbols(fMicros.symbols, currencySymbols, unitWidth, nullptr);
}
if (safe) {
- fImmutablePatternModifier.adoptInstead(patternModifier->createImmutableAndChain(chain, status));
- chain = fImmutablePatternModifier.getAlias();
- } else {
- patternModifier->addToChain(chain);
- chain = patternModifier;
+ fImmutablePatternModifier.adoptInstead(patternModifier->createImmutable(status));
}
// Outer modifier (CLDR units and currency long names)
@@ -418,8 +412,6 @@
}
// Compact notation
- // NOTE: Compact notation can (but might not) override the middle modifier and rounding.
- // It therefore needs to go at the end of the chain.
if (macros.notation.fType == Notation::NTN_COMPACT) {
CompactType compactType = (isCurrency && unitWidth != UNUM_UNIT_WIDTH_FULL_NAME)
? CompactType::TYPE_CURRENCY : CompactType::TYPE_DECIMAL;
@@ -429,7 +421,8 @@
nsName,
compactType,
resolvePluralRules(macros.rules, macros.locale, status),
- safe ? patternModifier : nullptr,
+ patternModifier,
+ safe,
chain,
status);
if (newCompactHandler == nullptr) {
@@ -440,6 +433,15 @@
chain = fCompactHandler.getAlias();
}
+ // Always add the pattern modifier as the last element of the chain.
+ if (safe) {
+ fImmutablePatternModifier->addToChain(chain);
+ chain = fImmutablePatternModifier.getAlias();
+ } else {
+ patternModifier->addToChain(chain);
+ chain = patternModifier;
+ }
+
return chain;
}
@@ -507,6 +509,12 @@
// Add the fraction digits
length += writeFractionDigits(micros, quantity, string, length + index, status);
+
+ if (length == 0) {
+ // Force output of the digit for value 0
+ length += utils::insertDigitFromSymbols(
+ string, index, 0, *micros.symbols, UNUM_INTEGER_FIELD, status);
+ }
}
return length;
diff --git a/icu4c/source/i18n/number_formatimpl.h b/icu4c/source/i18n/number_formatimpl.h
index 206c5f5..084bc4a 100644
--- a/icu4c/source/i18n/number_formatimpl.h
+++ b/icu4c/source/i18n/number_formatimpl.h
@@ -95,7 +95,7 @@
LocalPointer<const ParsedPatternInfo> fPatternInfo;
LocalPointer<const ScientificHandler> fScientificHandler;
LocalPointer<MutablePatternModifier> fPatternModifier;
- LocalPointer<const ImmutablePatternModifier> fImmutablePatternModifier;
+ LocalPointer<ImmutablePatternModifier> fImmutablePatternModifier;
LocalPointer<const LongNameHandler> fLongNameHandler;
LocalPointer<const CompactHandler> fCompactHandler;
diff --git a/icu4c/source/i18n/number_longnames.cpp b/icu4c/source/i18n/number_longnames.cpp
index 817aa0e..5519398 100644
--- a/icu4c/source/i18n/number_longnames.cpp
+++ b/icu4c/source/i18n/number_longnames.cpp
@@ -308,7 +308,7 @@
if (U_FAILURE(status)) { return; }
SimpleFormatter compiledFormatter(simpleFormat, 0, 1, status);
if (U_FAILURE(status)) { return; }
- fModifiers[i] = SimpleModifier(compiledFormatter, field, false, {this, SIGNUM_ZERO, plural});
+ fModifiers[i] = SimpleModifier(compiledFormatter, field, false, {this, SIGNUM_POS_ZERO, plural});
}
}
@@ -325,7 +325,7 @@
if (U_FAILURE(status)) { return; }
SimpleFormatter compoundCompiled(compoundFormat, 0, 1, status);
if (U_FAILURE(status)) { return; }
- fModifiers[i] = SimpleModifier(compoundCompiled, field, false, {this, SIGNUM_ZERO, plural});
+ fModifiers[i] = SimpleModifier(compoundCompiled, field, false, {this, SIGNUM_POS_ZERO, plural});
}
}
diff --git a/icu4c/source/i18n/number_mapper.cpp b/icu4c/source/i18n/number_mapper.cpp
index 40fd528..f0d313f 100644
--- a/icu4c/source/i18n/number_mapper.cpp
+++ b/icu4c/source/i18n/number_mapper.cpp
@@ -125,10 +125,8 @@
}
// Validate min/max int/frac.
// For backwards compatibility, minimum overrides maximum if the two conflict.
- // The following logic ensures that there is always a minimum of at least one digit.
if (minInt == 0 && maxFrac != 0) {
- // Force a digit after the decimal point.
- minFrac = minFrac <= 0 ? 1 : minFrac;
+ minFrac = (minFrac < 0 || (minFrac == 0 && maxInt == 0)) ? 1 : minFrac;
maxFrac = maxFrac < 0 ? -1 : maxFrac < minFrac ? minFrac : maxFrac;
minInt = 0;
maxInt = maxInt < 0 ? -1 : maxInt > kMaxIntFracSig ? -1 : maxInt;
diff --git a/icu4c/source/i18n/number_microprops.h b/icu4c/source/i18n/number_microprops.h
index d2393ae..56512f5 100644
--- a/icu4c/source/i18n/number_microprops.h
+++ b/icu4c/source/i18n/number_microprops.h
@@ -37,7 +37,7 @@
// Note: This struct has no direct ownership of the following pointers.
const DecimalFormatSymbols* symbols;
const Modifier* modOuter;
- const Modifier* modMiddle;
+ const Modifier* modMiddle = nullptr;
const Modifier* modInner;
// The following "helper" fields may optionally be used during the MicroPropsGenerator.
diff --git a/icu4c/source/i18n/number_modifiers.h b/icu4c/source/i18n/number_modifiers.h
index c84c6aa..e3820b6 100644
--- a/icu4c/source/i18n/number_modifiers.h
+++ b/icu4c/source/i18n/number_modifiers.h
@@ -319,12 +319,12 @@
private:
// NOTE: mods is zero-initialized (to nullptr)
- const Modifier *mods[3 * StandardPlural::COUNT] = {};
+ const Modifier *mods[4 * StandardPlural::COUNT] = {};
inline static int32_t getModIndex(Signum signum, StandardPlural::Form plural) {
- U_ASSERT(signum >= -1 && signum <= 1);
+ U_ASSERT(signum >= 0 && signum < SIGNUM_COUNT);
U_ASSERT(plural >= 0 && plural < StandardPlural::COUNT);
- return static_cast<int32_t>(plural) * 3 + (signum + 1);
+ return static_cast<int32_t>(plural) * SIGNUM_COUNT + signum;
}
};
diff --git a/icu4c/source/i18n/number_patternmodifier.cpp b/icu4c/source/i18n/number_patternmodifier.cpp
index 724f5b9..892ac37 100644
--- a/icu4c/source/i18n/number_patternmodifier.cpp
+++ b/icu4c/source/i18n/number_patternmodifier.cpp
@@ -55,12 +55,6 @@
}
ImmutablePatternModifier* MutablePatternModifier::createImmutable(UErrorCode& status) {
- return createImmutableAndChain(nullptr, status);
-}
-
-ImmutablePatternModifier*
-MutablePatternModifier::createImmutableAndChain(const MicroPropsGenerator* parent, UErrorCode& status) {
-
// TODO: Move StandardPlural VALUES to standardplural.h
static const StandardPlural::Form STANDARD_PLURAL_VALUES[] = {
StandardPlural::Form::ZERO,
@@ -81,8 +75,10 @@
for (StandardPlural::Form plural : STANDARD_PLURAL_VALUES) {
setNumberProperties(SIGNUM_POS, plural);
pm->adoptModifier(SIGNUM_POS, plural, createConstantModifier(status));
- setNumberProperties(SIGNUM_ZERO, plural);
- pm->adoptModifier(SIGNUM_ZERO, plural, createConstantModifier(status));
+ setNumberProperties(SIGNUM_NEG_ZERO, plural);
+ pm->adoptModifier(SIGNUM_NEG_ZERO, plural, createConstantModifier(status));
+ setNumberProperties(SIGNUM_POS_ZERO, plural);
+ pm->adoptModifier(SIGNUM_POS_ZERO, plural, createConstantModifier(status));
setNumberProperties(SIGNUM_NEG, plural);
pm->adoptModifier(SIGNUM_NEG, plural, createConstantModifier(status));
}
@@ -90,20 +86,22 @@
delete pm;
return nullptr;
}
- return new ImmutablePatternModifier(pm, fRules, parent); // adopts pm
+ return new ImmutablePatternModifier(pm, fRules); // adopts pm
} else {
// Faster path when plural keyword is not needed.
setNumberProperties(SIGNUM_POS, StandardPlural::Form::COUNT);
pm->adoptModifierWithoutPlural(SIGNUM_POS, createConstantModifier(status));
- setNumberProperties(SIGNUM_ZERO, StandardPlural::Form::COUNT);
- pm->adoptModifierWithoutPlural(SIGNUM_ZERO, createConstantModifier(status));
+ setNumberProperties(SIGNUM_NEG_ZERO, StandardPlural::Form::COUNT);
+ pm->adoptModifierWithoutPlural(SIGNUM_NEG_ZERO, createConstantModifier(status));
+ setNumberProperties(SIGNUM_POS_ZERO, StandardPlural::Form::COUNT);
+ pm->adoptModifierWithoutPlural(SIGNUM_POS_ZERO, createConstantModifier(status));
setNumberProperties(SIGNUM_NEG, StandardPlural::Form::COUNT);
pm->adoptModifierWithoutPlural(SIGNUM_NEG, createConstantModifier(status));
if (U_FAILURE(status)) {
delete pm;
return nullptr;
}
- return new ImmutablePatternModifier(pm, nullptr, parent); // adopts pm
+ return new ImmutablePatternModifier(pm, nullptr); // adopts pm
}
}
@@ -120,13 +118,16 @@
}
}
-ImmutablePatternModifier::ImmutablePatternModifier(AdoptingModifierStore* pm, const PluralRules* rules,
- const MicroPropsGenerator* parent)
- : pm(pm), rules(rules), parent(parent) {}
+ImmutablePatternModifier::ImmutablePatternModifier(AdoptingModifierStore* pm, const PluralRules* rules)
+ : pm(pm), rules(rules), parent(nullptr) {}
void ImmutablePatternModifier::processQuantity(DecimalQuantity& quantity, MicroProps& micros,
UErrorCode& status) const {
parent->processQuantity(quantity, micros, status);
+ micros.rounder.apply(quantity, status);
+ if (micros.modMiddle != nullptr) {
+ return;
+ }
applyToMicros(micros, quantity, status);
}
@@ -148,6 +149,10 @@
}
}
+void ImmutablePatternModifier::addToChain(const MicroPropsGenerator* parent) {
+ this->parent = parent;
+}
+
/** Used by the unsafe code path. */
MicroPropsGenerator& MutablePatternModifier::addToChain(const MicroPropsGenerator* parent) {
@@ -158,6 +163,10 @@
void MutablePatternModifier::processQuantity(DecimalQuantity& fq, MicroProps& micros,
UErrorCode& status) const {
fParent->processQuantity(fq, micros, status);
+ micros.rounder.apply(fq, status);
+ if (micros.modMiddle != nullptr) {
+ return;
+ }
// The unsafe code path performs self-mutation, so we need a const_cast.
// This method needs to be const because it overrides a const method in the parent class.
auto nonConstThis = const_cast<MutablePatternModifier*>(this);
@@ -263,7 +272,12 @@
/** This method contains the heart of the logic for rendering LDML affix strings. */
void MutablePatternModifier::prepareAffix(bool isPrefix) {
PatternStringUtils::patternInfoToStringBuilder(
- *fPatternInfo, isPrefix, fSignum, fSignDisplay, fPlural, fPerMilleReplacesPercent, currentAffix);
+ *fPatternInfo,
+ isPrefix,
+ PatternStringUtils::resolveSignDisplay(fSignDisplay, fSignum),
+ fPlural,
+ fPerMilleReplacesPercent,
+ currentAffix);
}
UnicodeString MutablePatternModifier::getSymbol(AffixPatternType type) const {
diff --git a/icu4c/source/i18n/number_patternmodifier.h b/icu4c/source/i18n/number_patternmodifier.h
index b2c90e0..4034c9a 100644
--- a/icu4c/source/i18n/number_patternmodifier.h
+++ b/icu4c/source/i18n/number_patternmodifier.h
@@ -50,9 +50,11 @@
const Modifier* getModifier(Signum signum, StandardPlural::Form plural) const;
+ // Non-const method:
+ void addToChain(const MicroPropsGenerator* parent);
+
private:
- ImmutablePatternModifier(AdoptingModifierStore* pm, const PluralRules* rules,
- const MicroPropsGenerator* parent);
+ ImmutablePatternModifier(AdoptingModifierStore* pm, const PluralRules* rules);
const LocalPointer<AdoptingModifierStore> pm;
const PluralRules* rules;
@@ -165,21 +167,6 @@
*/
ImmutablePatternModifier *createImmutable(UErrorCode &status);
- /**
- * Creates a new quantity-dependent Modifier that behaves the same as the current instance, but which is immutable
- * and can be saved for future use. The number properties in the current instance are mutated; all other properties
- * are left untouched.
- *
- * <p>
- * CREATES A NEW HEAP OBJECT; THE CALLER GETS OWNERSHIP.
- *
- * @param parent
- * The QuantityChain to which to chain this immutable.
- * @return An immutable that supports both positive and negative numbers.
- */
- ImmutablePatternModifier *
- createImmutableAndChain(const MicroPropsGenerator *parent, UErrorCode &status);
-
MicroPropsGenerator &addToChain(const MicroPropsGenerator *parent);
void processQuantity(DecimalQuantity &, MicroProps µs, UErrorCode &status) const U_OVERRIDE;
diff --git a/icu4c/source/i18n/number_patternstring.cpp b/icu4c/source/i18n/number_patternstring.cpp
index c7212c1..55b83f1 100644
--- a/icu4c/source/i18n/number_patternstring.cpp
+++ b/icu4c/source/i18n/number_patternstring.cpp
@@ -1000,23 +1000,19 @@
}
void PatternStringUtils::patternInfoToStringBuilder(const AffixPatternProvider& patternInfo, bool isPrefix,
- Signum signum, UNumberSignDisplay signDisplay,
+ PatternSignType patternSignType,
StandardPlural::Form plural,
bool perMilleReplacesPercent, UnicodeString& output) {
// Should the output render '+' where '-' would normally appear in the pattern?
- bool plusReplacesMinusSign = signum != -1 && (
- signDisplay == UNUM_SIGN_ALWAYS || signDisplay == UNUM_SIGN_ACCOUNTING_ALWAYS || (
- signum == 1 && (
- signDisplay == UNUM_SIGN_EXCEPT_ZERO ||
- signDisplay == UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO))) &&
- patternInfo.positiveHasPlusSign() == false;
+ bool plusReplacesMinusSign = (patternSignType == PATTERN_SIGN_TYPE_POS_SIGN)
+ && !patternInfo.positiveHasPlusSign();
- // Should we use the affix from the negative subpattern? (If not, we will use the positive
- // subpattern.)
- // TODO: Deal with signum
- bool useNegativeAffixPattern = patternInfo.hasNegativeSubpattern() && (
- signum == -1 || (patternInfo.negativeHasMinusSign() && plusReplacesMinusSign));
+ // Should we use the affix from the negative subpattern?
+ // (If not, we will use the positive subpattern.)
+ bool useNegativeAffixPattern = patternInfo.hasNegativeSubpattern()
+ && (patternSignType == PATTERN_SIGN_TYPE_NEG
+ || (patternInfo.negativeHasMinusSign() && plusReplacesMinusSign));
// Resolve the flags for the affix pattern.
int flags = 0;
@@ -1035,8 +1031,8 @@
bool prependSign;
if (!isPrefix || useNegativeAffixPattern) {
prependSign = false;
- } else if (signum == -1) {
- prependSign = signDisplay != UNUM_SIGN_NEVER;
+ } else if (patternSignType == PATTERN_SIGN_TYPE_NEG) {
+ prependSign = true;
} else {
prependSign = plusReplacesMinusSign;
}
@@ -1065,4 +1061,60 @@
}
}
+PatternSignType PatternStringUtils::resolveSignDisplay(UNumberSignDisplay signDisplay, Signum signum) {
+ switch (signDisplay) {
+ case UNUM_SIGN_AUTO:
+ case UNUM_SIGN_ACCOUNTING:
+ switch (signum) {
+ case SIGNUM_NEG:
+ case SIGNUM_NEG_ZERO:
+ return PATTERN_SIGN_TYPE_NEG;
+ case SIGNUM_POS_ZERO:
+ case SIGNUM_POS:
+ return PATTERN_SIGN_TYPE_POS;
+ default:
+ break;
+ }
+ break;
+
+ case UNUM_SIGN_ALWAYS:
+ case UNUM_SIGN_ACCOUNTING_ALWAYS:
+ switch (signum) {
+ case SIGNUM_NEG:
+ case SIGNUM_NEG_ZERO:
+ return PATTERN_SIGN_TYPE_NEG;
+ case SIGNUM_POS_ZERO:
+ case SIGNUM_POS:
+ return PATTERN_SIGN_TYPE_POS_SIGN;
+ default:
+ break;
+ }
+ break;
+
+ case UNUM_SIGN_EXCEPT_ZERO:
+ case UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO:
+ switch (signum) {
+ case SIGNUM_NEG:
+ return PATTERN_SIGN_TYPE_NEG;
+ case SIGNUM_NEG_ZERO:
+ case SIGNUM_POS_ZERO:
+ return PATTERN_SIGN_TYPE_POS;
+ case SIGNUM_POS:
+ return PATTERN_SIGN_TYPE_POS_SIGN;
+ default:
+ break;
+ }
+ break;
+
+ case UNUM_SIGN_NEVER:
+ return PATTERN_SIGN_TYPE_POS;
+
+ default:
+ break;
+ }
+
+ UPRV_UNREACHABLE;
+ return PATTERN_SIGN_TYPE_POS;
+}
+
#endif /* #if !UCONFIG_NO_FORMATTING */
diff --git a/icu4c/source/i18n/number_patternstring.h b/icu4c/source/i18n/number_patternstring.h
index 1191d29..54f37fd 100644
--- a/icu4c/source/i18n/number_patternstring.h
+++ b/icu4c/source/i18n/number_patternstring.h
@@ -22,6 +22,18 @@
// Forward declaration
class PatternParser;
+// Note: the order of fields in this enum matters for parsing.
+enum PatternSignType {
+ /** Render using normal positive subpattern rules */
+ PATTERN_SIGN_TYPE_POS,
+ /** Render using rules to force the display of a plus sign */
+ PATTERN_SIGN_TYPE_POS_SIGN,
+ /** Render using negative subpattern rules */
+ PATTERN_SIGN_TYPE_NEG,
+ /** Count for looping over the possibilities */
+ PATTERN_SIGN_TYPE_COUNT
+};
+
// Exported as U_I18N_API because it is a public member field of exported ParsedSubpatternInfo
struct U_I18N_API Endpoints {
int32_t start = 0;
@@ -295,10 +307,12 @@
* substitution, and plural forms for CurrencyPluralInfo.
*/
static void patternInfoToStringBuilder(const AffixPatternProvider& patternInfo, bool isPrefix,
- Signum signum, UNumberSignDisplay signDisplay,
+ PatternSignType patternSignType,
StandardPlural::Form plural, bool perMilleReplacesPercent,
UnicodeString& output);
+ static PatternSignType resolveSignDisplay(UNumberSignDisplay signDisplay, Signum signum);
+
private:
/** @return The number of chars inserted. */
static int escapePaddingString(UnicodeString input, UnicodeString& output, int startIndex,
diff --git a/icu4c/source/i18n/number_rounding.cpp b/icu4c/source/i18n/number_rounding.cpp
index 813d4b6..3ffce67 100644
--- a/icu4c/source/i18n/number_rounding.cpp
+++ b/icu4c/source/i18n/number_rounding.cpp
@@ -294,9 +294,7 @@
}
RoundingImpl RoundingImpl::passThrough() {
- RoundingImpl retval;
- retval.fPassThrough = true;
- return retval;
+ return {};
}
bool RoundingImpl::isSignificantDigits() const {
diff --git a/icu4c/source/i18n/number_roundingutils.h b/icu4c/source/i18n/number_roundingutils.h
index 9c2c47b..3e37f31 100644
--- a/icu4c/source/i18n/number_roundingutils.h
+++ b/icu4c/source/i18n/number_roundingutils.h
@@ -150,7 +150,7 @@
*/
class RoundingImpl {
public:
- RoundingImpl() = default; // default constructor: leaves object in undefined state
+ RoundingImpl() = default; // defaults to pass-through rounder
RoundingImpl(const Precision& precision, UNumberFormatRoundingMode roundingMode,
const CurrencyUnit& currency, UErrorCode& status);
@@ -186,7 +186,7 @@
private:
Precision fPrecision;
UNumberFormatRoundingMode fRoundingMode;
- bool fPassThrough;
+ bool fPassThrough = true; // default value
};
diff --git a/icu4c/source/i18n/number_types.h b/icu4c/source/i18n/number_types.h
index d62aa6a..641e082 100644
--- a/icu4c/source/i18n/number_types.h
+++ b/icu4c/source/i18n/number_types.h
@@ -92,9 +92,11 @@
};
enum Signum {
- SIGNUM_NEG = -1,
- SIGNUM_ZERO = 0,
- SIGNUM_POS = 1
+ SIGNUM_NEG = 0,
+ SIGNUM_NEG_ZERO = 1,
+ SIGNUM_POS_ZERO = 2,
+ SIGNUM_POS = 3,
+ SIGNUM_COUNT = 4,
};
diff --git a/icu4c/source/i18n/numparse_affixes.cpp b/icu4c/source/i18n/numparse_affixes.cpp
index cf8bab4..ca293e7 100644
--- a/icu4c/source/i18n/numparse_affixes.cpp
+++ b/icu4c/source/i18n/numparse_affixes.cpp
@@ -271,8 +271,6 @@
// Use initial capacity of 6, the highest possible number of AffixMatchers.
UnicodeString sb;
bool includeUnpaired = 0 != (parseFlags & PARSE_FLAG_INCLUDE_UNPAIRED_AFFIXES);
- UNumberSignDisplay signDisplay = (0 != (parseFlags & PARSE_FLAG_PLUS_SIGN_ALLOWED)) ? UNUM_SIGN_ALWAYS
- : UNUM_SIGN_AUTO;
int32_t numAffixMatchers = 0;
int32_t numAffixPatternMatchers = 0;
@@ -281,13 +279,23 @@
AffixPatternMatcher* posSuffix = nullptr;
// Pre-process the affix strings to resolve LDML rules like sign display.
- for (int8_t signumInt = 1; signumInt >= -1; signumInt--) {
- auto signum = static_cast<Signum>(signumInt);
+ for (int8_t typeInt = 0; typeInt < PATTERN_SIGN_TYPE_COUNT; typeInt++) {
+ auto type = static_cast<PatternSignType>(typeInt);
+
+ // Skip affixes in some cases
+ if (type == PATTERN_SIGN_TYPE_POS
+ && 0 != (parseFlags & PARSE_FLAG_PLUS_SIGN_ALLOWED)) {
+ continue;
+ }
+ if (type == PATTERN_SIGN_TYPE_POS_SIGN
+ && 0 == (parseFlags & PARSE_FLAG_PLUS_SIGN_ALLOWED)) {
+ continue;
+ }
// Generate Prefix
bool hasPrefix = false;
PatternStringUtils::patternInfoToStringBuilder(
- patternInfo, true, signum, signDisplay, StandardPlural::OTHER, false, sb);
+ patternInfo, true, type, StandardPlural::OTHER, false, sb);
fAffixPatternMatchers[numAffixPatternMatchers] = AffixPatternMatcher::fromAffixPattern(
sb, *fTokenWarehouse, parseFlags, &hasPrefix, status);
AffixPatternMatcher* prefix = hasPrefix ? &fAffixPatternMatchers[numAffixPatternMatchers++]
@@ -296,13 +304,13 @@
// Generate Suffix
bool hasSuffix = false;
PatternStringUtils::patternInfoToStringBuilder(
- patternInfo, false, signum, signDisplay, StandardPlural::OTHER, false, sb);
+ patternInfo, false, type, StandardPlural::OTHER, false, sb);
fAffixPatternMatchers[numAffixPatternMatchers] = AffixPatternMatcher::fromAffixPattern(
sb, *fTokenWarehouse, parseFlags, &hasSuffix, status);
AffixPatternMatcher* suffix = hasSuffix ? &fAffixPatternMatchers[numAffixPatternMatchers++]
: nullptr;
- if (signum == 1) {
+ if (type == PATTERN_SIGN_TYPE_POS) {
posPrefix = prefix;
posSuffix = suffix;
} else if (equals(prefix, posPrefix) && equals(suffix, posSuffix)) {
@@ -311,17 +319,17 @@
}
// Flags for setting in the ParsedNumber; the token matchers may add more.
- int flags = (signum == -1) ? FLAG_NEGATIVE : 0;
+ int flags = (type == PATTERN_SIGN_TYPE_NEG) ? FLAG_NEGATIVE : 0;
// Note: it is indeed possible for posPrefix and posSuffix to both be null.
// We still need to add that matcher for strict mode to work.
fAffixMatchers[numAffixMatchers++] = {prefix, suffix, flags};
if (includeUnpaired && prefix != nullptr && suffix != nullptr) {
// The following if statements are designed to prevent adding two identical matchers.
- if (signum == 1 || !equals(prefix, posPrefix)) {
+ if (type == PATTERN_SIGN_TYPE_POS || !equals(prefix, posPrefix)) {
fAffixMatchers[numAffixMatchers++] = {prefix, nullptr, flags};
}
- if (signum == 1 || !equals(suffix, posSuffix)) {
+ if (type == PATTERN_SIGN_TYPE_POS || !equals(suffix, posSuffix)) {
fAffixMatchers[numAffixMatchers++] = {nullptr, suffix, flags};
}
}
diff --git a/icu4c/source/i18n/smpdtfmt.cpp b/icu4c/source/i18n/smpdtfmt.cpp
index 5fcbb58..6246e8f 100644
--- a/icu4c/source/i18n/smpdtfmt.cpp
+++ b/icu4c/source/i18n/smpdtfmt.cpp
@@ -996,7 +996,8 @@
// Use subFormat() to format a repeated pattern character
// when a different pattern or non-pattern character is seen
if (ch != prevCh && count > 0) {
- subFormat(appendTo, prevCh, count, capitalizationContext, fieldNum++, handler, *workCal, status);
+ subFormat(appendTo, prevCh, count, capitalizationContext, fieldNum++,
+ prevCh, handler, *workCal, status);
count = 0;
}
if (ch == QUOTE) {
@@ -1023,7 +1024,8 @@
// Format the last item in the pattern, if any
if (count > 0) {
- subFormat(appendTo, prevCh, count, capitalizationContext, fieldNum++, handler, *workCal, status);
+ subFormat(appendTo, prevCh, count, capitalizationContext, fieldNum++,
+ prevCh, handler, *workCal, status);
}
if (calClone != NULL) {
@@ -1402,10 +1404,11 @@
//---------------------------------------------------------------------
void
SimpleDateFormat::subFormat(UnicodeString &appendTo,
- UChar ch,
+ char16_t ch,
int32_t count,
UDisplayContext capitalizationContext,
int32_t fieldNum,
+ char16_t fieldToOutput,
FieldPositionHandler& handler,
Calendar& cal,
UErrorCode& status) const
@@ -1853,8 +1856,11 @@
// In either case, fall back to am/pm.
if (toAppend == NULL || toAppend->isBogus()) {
// Reformat with identical arguments except ch, now changed to 'a'.
- subFormat(appendTo, 0x61, count, capitalizationContext, fieldNum,
- handler, cal, status);
+ // We are passing a different fieldToOutput because we want to add
+ // 'b' to field position. This makes this fallback stable when
+ // there is a data change on locales.
+ subFormat(appendTo, u'a', count, capitalizationContext, fieldNum, u'b', handler, cal, status);
+ return;
} else {
appendTo += *toAppend;
}
@@ -1874,9 +1880,11 @@
if (ruleSet == NULL) {
// Data doesn't exist for the locale we're looking for.
// Falling back to am/pm.
- subFormat(appendTo, 0x61, count, capitalizationContext, fieldNum,
- handler, cal, status);
- break;
+ // We are passing a different fieldToOutput because we want to add
+ // 'B' to field position. This makes this fallback stable when
+ // there is a data change on locales.
+ subFormat(appendTo, u'a', count, capitalizationContext, fieldNum, u'B', handler, cal, status);
+ return;
}
// Get current display time.
@@ -1945,8 +1953,11 @@
if (periodType == DayPeriodRules::DAYPERIOD_AM ||
periodType == DayPeriodRules::DAYPERIOD_PM ||
toAppend->isBogus()) {
- subFormat(appendTo, 0x61, count, capitalizationContext, fieldNum,
- handler, cal, status);
+ // We are passing a different fieldToOutput because we want to add
+ // 'B' to field position iterator. This makes this fallback stable when
+ // there is a data change on locales.
+ subFormat(appendTo, u'a', count, capitalizationContext, fieldNum, u'B', handler, cal, status);
+ return;
}
else {
appendTo += *toAppend;
@@ -1990,7 +2001,7 @@
}
#endif
- handler.addAttribute(fgPatternIndexToDateFormatField[patternCharIndex], beginOffset, appendTo.length());
+ handler.addAttribute(DateFormatSymbols::getPatternCharIndex(fieldToOutput), beginOffset, appendTo.length());
}
//----------------------------------------------------------------------
diff --git a/icu4c/source/i18n/udateintervalformat.cpp b/icu4c/source/i18n/udateintervalformat.cpp
index d9eaae4..e16df08 100644
--- a/icu4c/source/i18n/udateintervalformat.cpp
+++ b/icu4c/source/i18n/udateintervalformat.cpp
@@ -128,8 +128,27 @@
}
auto* resultImpl = UFormattedDateIntervalApiHelper::validate(result, *status);
DateInterval interval = DateInterval(fromDate,toDate);
- resultImpl->fImpl = reinterpret_cast<const DateIntervalFormat*>(formatter)
- ->formatToValue(interval, *status);
+ if (resultImpl != nullptr) {
+ resultImpl->fImpl = reinterpret_cast<const DateIntervalFormat*>(formatter)
+ ->formatToValue(interval, *status);
+ }
+}
+
+U_DRAFT void U_EXPORT2
+udtitvfmt_formatCalendarToResult(
+ const UDateIntervalFormat* formatter,
+ UCalendar* fromCalendar,
+ UCalendar* toCalendar,
+ UFormattedDateInterval* result,
+ UErrorCode* status) {
+ if (U_FAILURE(*status)) {
+ return;
+ }
+ auto* resultImpl = UFormattedDateIntervalApiHelper::validate(result, *status);
+ if (resultImpl != nullptr) {
+ resultImpl->fImpl = reinterpret_cast<const DateIntervalFormat*>(formatter)
+ ->formatToValue(*(Calendar *)fromCalendar, *(Calendar *)toCalendar, *status);
+ }
}
diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h
index 42d4687..58b13c3 100644
--- a/icu4c/source/i18n/unicode/numberformatter.h
+++ b/icu4c/source/i18n/unicode/numberformatter.h
@@ -155,6 +155,8 @@
class NumberRangeFormatterImpl;
struct RangeMacroProps;
struct UFormattedNumberImpl;
+class MutablePatternModifier;
+class ImmutablePatternModifier;
/**
* Used for NumberRangeFormatter and implemented in numrange_fluent.cpp.
@@ -980,9 +982,13 @@
friend struct impl::MacroProps;
friend struct impl::MicroProps;
- // To allow NumberFormatterImpl to access isBogus() and perform other operations:
+ // To allow NumberFormatterImpl to access isBogus():
friend class impl::NumberFormatterImpl;
+ // To allow the use of this class when formatting:
+ friend class impl::MutablePatternModifier;
+ friend class impl::ImmutablePatternModifier;
+
// So that NumberPropertyMapper can create instances
friend class impl::NumberPropertyMapper;
diff --git a/icu4c/source/i18n/unicode/smpdtfmt.h b/icu4c/source/i18n/unicode/smpdtfmt.h
index 79fa817..b4b0e5f 100644
--- a/icu4c/source/i18n/unicode/smpdtfmt.h
+++ b/icu4c/source/i18n/unicode/smpdtfmt.h
@@ -1274,6 +1274,7 @@
int32_t count,
UDisplayContext capitalizationContext,
int32_t fieldNum,
+ char16_t fieldToOutput,
FieldPositionHandler& handler,
Calendar& cal,
UErrorCode& status) const; // in case of illegal argument
diff --git a/icu4c/source/i18n/unicode/udateintervalformat.h b/icu4c/source/i18n/unicode/udateintervalformat.h
index b42223a..9777c4b 100644
--- a/icu4c/source/i18n/unicode/udateintervalformat.h
+++ b/icu4c/source/i18n/unicode/udateintervalformat.h
@@ -14,6 +14,7 @@
#if !UCONFIG_NO_FORMATTING
+#include "unicode/ucal.h"
#include "unicode/umisc.h"
#include "unicode/localpointer.h"
#include "unicode/uformattedvalue.h"
@@ -278,6 +279,31 @@
UDate fromDate,
UDate toDate,
UErrorCode* status);
+
+/**
+ * Formats a date/time range using the conventions established for the
+ * UDateIntervalFormat object.
+ * @param formatter
+ * The UDateIntervalFormat object specifying the format conventions.
+ * @param fromCalendar
+ * The starting point of the range.
+ * @param toCalendar
+ * The ending point of the range.
+ * @param result
+ * The UFormattedDateInterval to contain the result of the
+ * formatting operation.
+ * @param status
+ * A pointer to a UErrorCode to receive any errors.
+ * @draft ICU 67
+ */
+
+U_DRAFT void U_EXPORT2
+udtitvfmt_formatCalendarToResult(
+ const UDateIntervalFormat* formatter,
+ UCalendar* fromCalendar,
+ UCalendar* toCalendar,
+ UFormattedDateInterval* result,
+ UErrorCode* status);
#endif /* U_HIDE_DRAFT_API */
diff --git a/icu4c/source/i18n/unicode/unumberformatter.h b/icu4c/source/i18n/unicode/unumberformatter.h
index b27507f..1d6b460 100644
--- a/icu4c/source/i18n/unicode/unumberformatter.h
+++ b/icu4c/source/i18n/unicode/unumberformatter.h
@@ -336,7 +336,7 @@
/**
* Show the minus sign on negative numbers and the plus sign on positive numbers. Do not show a
- * sign on zero or NaN, unless the sign bit is set (-0.0 gets a sign).
+ * sign on zero, numbers that round to zero, or NaN.
*
* @stable ICU 61
*/
@@ -344,9 +344,8 @@
/**
* Use the locale-dependent accounting format on negative numbers, and show the plus sign on
- * positive numbers. Do not show a sign on zero or NaN, unless the sign bit is set (-0.0 gets a
- * sign). For more information on the accounting format, see the ACCOUNTING sign display
- * strategy.
+ * positive numbers. Do not show a sign on zero, numbers that round to zero, or NaN. For more
+ * information on the accounting format, see the ACCOUNTING sign display strategy.
*
* @stable ICU 61
*/
diff --git a/icu4c/source/test/cintltst/cdateintervalformattest.c b/icu4c/source/test/cintltst/cdateintervalformattest.c
index d8a8066..6635de2 100644
--- a/icu4c/source/test/cintltst/cdateintervalformattest.c
+++ b/icu4c/source/test/cintltst/cdateintervalformattest.c
@@ -21,6 +21,7 @@
static void TestDateIntervalFormat(void);
static void TestFPos_SkelWithSeconds(void);
static void TestFormatToResult(void);
+static void TestFormatCalendarToResult(void);
void addDateIntervalFormatTest(TestNode** root);
@@ -31,12 +32,15 @@
TESTCASE(TestDateIntervalFormat);
TESTCASE(TestFPos_SkelWithSeconds);
TESTCASE(TestFormatToResult);
+ TESTCASE(TestFormatCalendarToResult);
}
static const char tzUSPacific[] = "US/Pacific";
static const char tzAsiaTokyo[] = "Asia/Tokyo";
#define Date201103021030 1299090600000.0 /* 2011-Mar-02 1030 in US/Pacific, 2011-Mar-03 0330 in Asia/Tokyo */
#define Date201009270800 1285599629000.0 /* 2010-Sep-27 0800 in US/Pacific */
+#define Date158210140000 -12219379142000.0
+#define Date158210160000 -12219206342000.0
#define _MINUTE (60.0*1000.0)
#define _HOUR (60.0*60.0*1000.0)
#define _DAY (24.0*60.0*60.0*1000.0)
@@ -352,7 +356,7 @@
UPRV_LENGTHOF(expectedFieldPositions));
}
{
- const char* message = "Field position test 1";
+ const char* message = "Field position test 2";
const UChar* expectedString = u"27. September 2010, 15:00–22:00 Uhr";
udtitvfmt_formatToResult(fmt, fdi, Date201009270800, Date201009270800 + 7*_HOUR, &ec);
assertSuccess("Formatting", &ec);
@@ -380,4 +384,144 @@
udtitvfmt_closeResult(fdi);
}
+static void TestFormatCalendarToResult() {
+ UErrorCode ec = U_ZERO_ERROR;
+ UCalendar* ucal1 = ucal_open(zoneGMT, -1, "de", UCAL_DEFAULT, &ec);
+ ucal_setMillis(ucal1, Date201009270800, &ec);
+ UCalendar* ucal2 = ucal_open(zoneGMT, -1, "de", UCAL_DEFAULT, &ec);
+ ucal_setMillis(ucal2, Date201103021030, &ec);
+ UCalendar* ucal3 = ucal_open(zoneGMT, -1, "de", UCAL_DEFAULT, &ec);
+ ucal_setMillis(ucal3, Date201009270800 + 7*_HOUR, &ec);
+ UCalendar* ucal4 = ucal_open(zoneGMT, -1, "de", UCAL_DEFAULT, &ec);
+ UCalendar* ucal5 = ucal_open(zoneGMT, -1, "de", UCAL_DEFAULT, &ec);
+
+ UDateIntervalFormat* fmt = udtitvfmt_open("de", u"dMMMMyHHmm", -1, zoneGMT, -1, &ec);
+ UFormattedDateInterval* fdi = udtitvfmt_openResult(&ec);
+ assertSuccess("Opening", &ec);
+
+ {
+ const char* message = "Field position test 1";
+ const UChar* expectedString = u"27. September 2010, 15:00 – 2. März 2011, 18:30";
+ udtitvfmt_formatCalendarToResult(fmt, ucal1, ucal2, fdi, &ec);
+ assertSuccess("Formatting", &ec);
+ static const UFieldPositionWithCategory expectedFieldPositions[] = {
+ // category, field, begin index, end index
+ {UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 0, 0, 25},
+ {UFIELD_CATEGORY_DATE, UDAT_DATE_FIELD, 0, 2},
+ {UFIELD_CATEGORY_DATE, UDAT_MONTH_FIELD, 4, 13},
+ {UFIELD_CATEGORY_DATE, UDAT_YEAR_FIELD, 14, 18},
+ {UFIELD_CATEGORY_DATE, UDAT_HOUR_OF_DAY0_FIELD, 20, 22},
+ {UFIELD_CATEGORY_DATE, UDAT_MINUTE_FIELD, 23, 25},
+ {UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 1, 28, 47},
+ {UFIELD_CATEGORY_DATE, UDAT_DATE_FIELD, 28, 29},
+ {UFIELD_CATEGORY_DATE, UDAT_MONTH_FIELD, 31, 35},
+ {UFIELD_CATEGORY_DATE, UDAT_YEAR_FIELD, 36, 40},
+ {UFIELD_CATEGORY_DATE, UDAT_HOUR_OF_DAY0_FIELD, 42, 44},
+ {UFIELD_CATEGORY_DATE, UDAT_MINUTE_FIELD, 45, 47}};
+ checkMixedFormattedValue(
+ message,
+ udtitvfmt_resultAsValue(fdi, &ec),
+ expectedString,
+ expectedFieldPositions,
+ UPRV_LENGTHOF(expectedFieldPositions));
+ }
+ {
+ const char* message = "Field position test 2";
+ const UChar* expectedString = u"27. September 2010, 15:00–22:00 Uhr";
+ udtitvfmt_formatCalendarToResult(fmt, ucal1, ucal3, fdi, &ec);
+ assertSuccess("Formatting", &ec);
+ static const UFieldPositionWithCategory expectedFieldPositions[] = {
+ // category, field, begin index, end index
+ {UFIELD_CATEGORY_DATE, UDAT_DATE_FIELD, 0, 2},
+ {UFIELD_CATEGORY_DATE, UDAT_MONTH_FIELD, 4, 13},
+ {UFIELD_CATEGORY_DATE, UDAT_YEAR_FIELD, 14, 18},
+ {UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 0, 20, 25},
+ {UFIELD_CATEGORY_DATE, UDAT_HOUR_OF_DAY0_FIELD, 20, 22},
+ {UFIELD_CATEGORY_DATE, UDAT_MINUTE_FIELD, 23, 25},
+ {UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 1, 26, 31},
+ {UFIELD_CATEGORY_DATE, UDAT_HOUR_OF_DAY0_FIELD, 26, 28},
+ {UFIELD_CATEGORY_DATE, UDAT_MINUTE_FIELD, 29, 31},
+ {UFIELD_CATEGORY_DATE, UDAT_AM_PM_FIELD, 32, 35}};
+ checkMixedFormattedValue(
+ message,
+ udtitvfmt_resultAsValue(fdi, &ec),
+ expectedString,
+ expectedFieldPositions,
+ UPRV_LENGTHOF(expectedFieldPositions));
+ }
+ {
+ const char* message = "Field position test 3";
+ // Date across Julian Gregorian change date.
+ ucal_setMillis(ucal4, Date158210140000, &ec);
+ ucal_setMillis(ucal5, Date158210160000, &ec);
+ // 1 2 3 4
+ // 012345678901234567890123456789012345678901234567890
+ const UChar* expectedString = u"4. Oktober 1582, 00:00 – 16. Oktober 1582, 00:00";
+ udtitvfmt_formatCalendarToResult(fmt, ucal4, ucal5, fdi, &ec);
+ assertSuccess("Formatting", &ec);
+ static const UFieldPositionWithCategory expectedFieldPositions[] = {
+ // category, field, begin index, end index
+ {UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 0, 0, 22},
+ {UFIELD_CATEGORY_DATE, UDAT_DATE_FIELD, 0, 1},
+ {UFIELD_CATEGORY_DATE, UDAT_MONTH_FIELD, 3, 10},
+ {UFIELD_CATEGORY_DATE, UDAT_YEAR_FIELD, 11, 15},
+ {UFIELD_CATEGORY_DATE, UDAT_HOUR_OF_DAY0_FIELD, 17, 19},
+ {UFIELD_CATEGORY_DATE, UDAT_MINUTE_FIELD, 20, 22},
+ {UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 1, 25, 48},
+ {UFIELD_CATEGORY_DATE, UDAT_DATE_FIELD, 25, 27},
+ {UFIELD_CATEGORY_DATE, UDAT_MONTH_FIELD, 29, 36},
+ {UFIELD_CATEGORY_DATE, UDAT_YEAR_FIELD, 37, 41},
+ {UFIELD_CATEGORY_DATE, UDAT_HOUR_OF_DAY0_FIELD, 43, 45},
+ {UFIELD_CATEGORY_DATE, UDAT_MINUTE_FIELD, 46, 48}};
+ checkMixedFormattedValue(
+ message,
+ udtitvfmt_resultAsValue(fdi, &ec),
+ expectedString,
+ expectedFieldPositions,
+ UPRV_LENGTHOF(expectedFieldPositions));
+ }
+ {
+ // Date across Julian Gregorian change date.
+ // We set the Gregorian Change way back.
+ ucal_setGregorianChange(ucal5, (UDate)(-8.64e15), &ec);
+ ucal_setGregorianChange(ucal4, (UDate)(-8.64e15), &ec);
+ ucal_setMillis(ucal4, Date158210140000, &ec);
+ ucal_setMillis(ucal5, Date158210160000, &ec);
+ const char* message = "Field position test 4";
+ // 1 2 3 4
+ // 012345678901234567890123456789012345678901234567890
+ const UChar* expectedString = u"14. Oktober 1582, 00:00 – 16. Oktober 1582, 00:00";
+ udtitvfmt_formatCalendarToResult(fmt, ucal4, ucal5, fdi, &ec);
+ assertSuccess("Formatting", &ec);
+ static const UFieldPositionWithCategory expectedFieldPositions[] = {
+ // category, field, begin index, end index
+ {UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 0, 0, 23},
+ {UFIELD_CATEGORY_DATE, UDAT_DATE_FIELD, 0, 2},
+ {UFIELD_CATEGORY_DATE, UDAT_MONTH_FIELD, 4, 11},
+ {UFIELD_CATEGORY_DATE, UDAT_YEAR_FIELD, 12, 16},
+ {UFIELD_CATEGORY_DATE, UDAT_HOUR_OF_DAY0_FIELD, 18, 20},
+ {UFIELD_CATEGORY_DATE, UDAT_MINUTE_FIELD, 21, 23},
+ {UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 1, 26, 49},
+ {UFIELD_CATEGORY_DATE, UDAT_DATE_FIELD, 26, 28},
+ {UFIELD_CATEGORY_DATE, UDAT_MONTH_FIELD, 30, 37},
+ {UFIELD_CATEGORY_DATE, UDAT_YEAR_FIELD, 38, 42},
+ {UFIELD_CATEGORY_DATE, UDAT_HOUR_OF_DAY0_FIELD, 44, 46},
+ {UFIELD_CATEGORY_DATE, UDAT_MINUTE_FIELD, 47, 49}};
+ checkMixedFormattedValue(
+ message,
+ udtitvfmt_resultAsValue(fdi, &ec),
+ expectedString,
+ expectedFieldPositions,
+ UPRV_LENGTHOF(expectedFieldPositions));
+ }
+
+ ucal_close(ucal1);
+ ucal_close(ucal2);
+ ucal_close(ucal3);
+ ucal_close(ucal4);
+ ucal_close(ucal5);
+ udtitvfmt_close(fmt);
+ udtitvfmt_closeResult(fdi);
+}
+
#endif /* #if !UCONFIG_NO_FORMATTING */
diff --git a/icu4c/source/test/cintltst/cloctst.c b/icu4c/source/test/cintltst/cloctst.c
index 0a1a0ae..36cd782 100644
--- a/icu4c/source/test/cintltst/cloctst.c
+++ b/icu4c/source/test/cintltst/cloctst.c
@@ -6029,7 +6029,10 @@
{"aa_BB_CYRL", "aa-BB-x-lvariant-cyrl", NULL},
{"en_US_1234", "en-US-1234", "en-US-1234"},
{"en_US_VARIANTA_VARIANTB", "en-US-varianta-variantb", "en-US-varianta-variantb"},
- {"ja__9876_5432", "ja-9876-5432", "ja-9876-5432"},
+ {"en_US_VARIANTB_VARIANTA", "en-US-varianta-variantb", "en-US-varianta-variantb"}, /* ICU-20478 */
+ {"ja__9876_5432", "ja-5432-9876", "ja-5432-9876"}, /* ICU-20478 */
+ {"sl__ROZAJ_BISKE_1994", "sl-1994-biske-rozaj", "sl-1994-biske-rozaj"}, /* ICU-20478 */
+ {"en__SCOUSE_FONIPA", "en-fonipa-scouse", "en-fonipa-scouse"}, /* ICU-20478 */
{"zh_Hant__VAR", "zh-Hant-x-lvariant-var", NULL},
{"es__BADVARIANT_GOODVAR", "es-goodvar", NULL},
{"en@calendar=gregorian", "en-u-ca-gregory", "en-u-ca-gregory"},
@@ -6187,7 +6190,16 @@
{"bogus", "bogus", FULL_LENGTH},
{"boguslang", "", 0},
{"EN-lATN-us", "en_Latn_US", FULL_LENGTH},
- {"und-variant-1234", "__VARIANT_1234", FULL_LENGTH},
+ {"und-variant-1234", "__1234_VARIANT", FULL_LENGTH}, /* ICU-20478 */
+ {"ja-9876-5432", "ja__5432_9876", FULL_LENGTH}, /* ICU-20478 */
+ {"en-US-varianta-variantb", "en_US_VARIANTA_VARIANTB", FULL_LENGTH}, /* ICU-20478 */
+ {"en-US-variantb-varianta", "en_US_VARIANTA_VARIANTB", FULL_LENGTH}, /* ICU-20478 */
+ {"sl-rozaj-1994-biske", "sl__1994_BISKE_ROZAJ", FULL_LENGTH}, /* ICU-20478 */
+ {"sl-biske-1994-rozaj", "sl__1994_BISKE_ROZAJ", FULL_LENGTH}, /* ICU-20478 */
+ {"sl-1994-rozaj-biske", "sl__1994_BISKE_ROZAJ", FULL_LENGTH}, /* ICU-20478 */
+ {"sl-rozaj-biske-1994", "sl__1994_BISKE_ROZAJ", FULL_LENGTH}, /* ICU-20478 */
+ {"en-fonipa-scouse", "en__FONIPA_SCOUSE", FULL_LENGTH}, /* ICU-20478 */
+ {"en-scouse-fonipa", "en__FONIPA_SCOUSE", FULL_LENGTH}, /* ICU-20478 */
{"und-varzero-var1-vartwo", "__VARZERO", 11},
{"en-u-ca-gregory", "en@calendar=gregorian", FULL_LENGTH},
{"en-U-cu-USD", "en@currency=usd", FULL_LENGTH},
diff --git a/icu4c/source/test/cintltst/cnumtst.c b/icu4c/source/test/cintltst/cnumtst.c
index dbbac08..daeff15 100644
--- a/icu4c/source/test/cintltst/cnumtst.c
+++ b/icu4c/source/test/cintltst/cnumtst.c
@@ -74,6 +74,7 @@
static void TestSetMaxFracAndRoundIncr(void);
static void TestIgnorePadding(void);
static void TestSciNotationMaxFracCap(void);
+static void TestMinIntMinFracZero(void);
#define TESTCASE(x) addTest(root, &x, "tsformat/cnumtst/" #x)
@@ -114,6 +115,7 @@
TESTCASE(TestSetMaxFracAndRoundIncr);
TESTCASE(TestIgnorePadding);
TESTCASE(TestSciNotationMaxFracCap);
+ TESTCASE(TestMinIntMinFracZero);
}
/* test Parse int 64 */
@@ -3475,4 +3477,103 @@
}
}
+static void TestMinIntMinFracZero(void) {
+ UErrorCode status = U_ZERO_ERROR;
+ UNumberFormat* unum = unum_open(UNUM_DECIMAL, NULL, 0, "en_US", NULL, &status);
+ if ( U_FAILURE(status) ) {
+ log_data_err("unum_open UNUM_DECIMAL for en_US fails with %s\n", u_errorName(status));
+ } else {
+ UChar ubuf[kUBufMax];
+ char bbuf[kBBufMax];
+ int minInt, minFrac, ulen;
+
+ unum_setAttribute(unum, UNUM_MIN_INTEGER_DIGITS, 0);
+ unum_setAttribute(unum, UNUM_MIN_FRACTION_DIGITS, 0);
+ minInt = unum_getAttribute(unum, UNUM_MIN_INTEGER_DIGITS);
+ minFrac = unum_getAttribute(unum, UNUM_MIN_FRACTION_DIGITS);
+ if (minInt != 0 || minFrac != 0) {
+ log_err("after setting minInt=minFrac=0, get minInt %d, minFrac %d\n", minInt, minFrac);
+ }
+
+ ulen = unum_toPattern(unum, FALSE, ubuf, kUBufMax, &status);
+ if ( U_FAILURE(status) ) {
+ log_err("unum_toPattern fails with %s\n", u_errorName(status));
+ } else if (ulen < 3 || u_strstr(ubuf, u"#.#")==NULL) {
+ u_austrncpy(bbuf, ubuf, kUBufMax);
+ log_info("after setting minInt=minFrac=0, expect pattern to contain \"#.#\", but get (%d): \"%s\"\n", ulen, bbuf);
+ }
+
+ status = U_ZERO_ERROR;
+ ulen = unum_formatDouble(unum, 10.0, ubuf, kUBufMax, NULL, &status);
+ if ( U_FAILURE(status) ) {
+ log_err("unum_formatDouble 10.0 ulen %d fails with %s\n", ulen, u_errorName(status));
+ } else if (u_strcmp(ubuf, u"10") != 0) {
+ u_austrncpy(bbuf, ubuf, kUBufMax);
+ log_err("unum_formatDouble 10.0 expected \"10\", got \"%s\"\n", bbuf);
+ }
+
+ status = U_ZERO_ERROR;
+ ulen = unum_formatDouble(unum, 0.9, ubuf, kUBufMax, NULL, &status);
+ if ( U_FAILURE(status) ) {
+ log_err("unum_formatDouble 0.9 ulen %d fails with %s\n", ulen, u_errorName(status));
+ } else if (u_strcmp(ubuf, u".9") != 0) {
+ u_austrncpy(bbuf, ubuf, kUBufMax);
+ log_err("unum_formatDouble 0.9 expected \".9\", got \"%s\"\n", bbuf);
+ }
+
+ status = U_ZERO_ERROR;
+ ulen = unum_formatDouble(unum, 0.0, ubuf, kUBufMax, NULL, &status);
+ if ( U_FAILURE(status) ) {
+ log_err("unum_formatDouble 0.0 ulen %d fails with %s\n", ulen, u_errorName(status));
+ } else if (u_strcmp(ubuf, u"0") != 0) {
+ u_austrncpy(bbuf, ubuf, kUBufMax);
+ log_err("unum_formatDouble 0.0 expected \"0\", got \"%s\"\n", bbuf);
+ }
+
+ unum_close(unum);
+ status = U_ZERO_ERROR;
+ unum = unum_open(UNUM_CURRENCY, NULL, 0, "en_US", NULL, &status);
+ if ( U_FAILURE(status) ) {
+ log_data_err("unum_open UNUM_CURRENCY for en_US fails with %s\n", u_errorName(status));
+ } else {
+ unum_setAttribute(unum, UNUM_MIN_INTEGER_DIGITS, 0);
+ unum_setAttribute(unum, UNUM_MIN_FRACTION_DIGITS, 0);
+ minInt = unum_getAttribute(unum, UNUM_MIN_INTEGER_DIGITS);
+ minFrac = unum_getAttribute(unum, UNUM_MIN_FRACTION_DIGITS);
+ if (minInt != 0 || minFrac != 0) {
+ log_err("after setting CURRENCY minInt=minFrac=0, get minInt %d, minFrac %d\n", minInt, minFrac);
+ }
+
+ status = U_ZERO_ERROR;
+ ulen = unum_formatDouble(unum, 10.0, ubuf, kUBufMax, NULL, &status);
+ if ( U_FAILURE(status) ) {
+ log_err("unum_formatDouble (CURRRENCY) 10.0 ulen %d fails with %s\n", ulen, u_errorName(status));
+ } else if (u_strcmp(ubuf, u"$10") != 0) {
+ u_austrncpy(bbuf, ubuf, kUBufMax);
+ log_err("unum_formatDouble (CURRRENCY) 10.0 expected \"$10\", got \"%s\"\n", bbuf);
+ }
+
+ status = U_ZERO_ERROR;
+ ulen = unum_formatDouble(unum, 0.9, ubuf, kUBufMax, NULL, &status);
+ if ( U_FAILURE(status) ) {
+ log_err("unum_formatDouble (CURRRENCY) 0.9 ulen %d fails with %s\n", ulen, u_errorName(status));
+ } else if (u_strcmp(ubuf, u"$.9") != 0) {
+ u_austrncpy(bbuf, ubuf, kUBufMax);
+ log_err("unum_formatDouble (CURRRENCY) 0.9 expected \"$.9\", got \"%s\"\n", bbuf);
+ }
+
+ status = U_ZERO_ERROR;
+ ulen = unum_formatDouble(unum, 0.0, ubuf, kUBufMax, NULL, &status);
+ if ( U_FAILURE(status) ) {
+ log_err("unum_formatDouble (CURRRENCY) 0.0 ulen %d fails with %s\n", ulen, u_errorName(status));
+ } else if (u_strcmp(ubuf, u"$0") != 0) {
+ u_austrncpy(bbuf, ubuf, kUBufMax);
+ log_err("unum_formatDouble (CURRRENCY) 0.0 expected \"$0\", got \"%s\"\n", bbuf);
+ }
+
+ unum_close(unum);
+ }
+ }
+}
+
#endif /* #if !UCONFIG_NO_FORMATTING */
diff --git a/icu4c/source/test/intltest/dtfmttst.cpp b/icu4c/source/test/intltest/dtfmttst.cpp
index 9684e88..d4fca1c 100644
--- a/icu4c/source/test/intltest/dtfmttst.cpp
+++ b/icu4c/source/test/intltest/dtfmttst.cpp
@@ -128,6 +128,7 @@
TESTCASE_AUTO(TestDayPeriodParsing);
TESTCASE_AUTO(TestParseRegression13744);
TESTCASE_AUTO(TestAdoptCalendarLeak);
+ TESTCASE_AUTO(Test20741_ABFields);
TESTCASE_AUTO_END;
}
@@ -4912,7 +4913,22 @@
{Locale::getEnglish(), "jjmm", "h:mm a"},
{Locale::getEnglish(), "JJmm", "hh:mm"},
{Locale::getGerman(), "jjmm", "HH:mm"},
- {Locale::getGerman(), "JJmm", "HH:mm"}
+ {Locale::getGerman(), "JJmm", "HH:mm"},
+ // Ticket #20739
+ {Locale::getEnglish(), "SSSSm", "mm:ss.SSSS"},
+ {Locale::getEnglish(), "mSSSS", "mm:ss.SSSS"},
+ {Locale::getEnglish(), "SSSm", "mm:ss.SSS"},
+ {Locale::getEnglish(), "mSSS", "mm:ss.SSS"},
+ {Locale::getEnglish(), "SSm", "mm:ss.SS"},
+ {Locale::getEnglish(), "mSS", "mm:ss.SS"},
+ {Locale::getEnglish(), "Sm", "mm:ss.S"},
+ {Locale::getEnglish(), "mS", "mm:ss.S"},
+ {Locale::getEnglish(), "S", "S"},
+ {Locale::getEnglish(), "SS", "SS"},
+ {Locale::getEnglish(), "SSS", "SSS"},
+ {Locale::getEnglish(), "SSSS", "SSSS"},
+ {Locale::getEnglish(), "jmsSSS", "h:mm:ss.SSS a"},
+ {Locale::getEnglish(), "jmSSS", "h:mm:ss.SSS a"}
};
for (size_t i = 0; i < UPRV_LENGTHOF(TESTDATA); i++) {
@@ -5555,6 +5571,51 @@
sdf.adoptCalendar(Calendar::createInstance(status));
}
+/**
+ * Test that 'a' and 'B' fields are not duplicated in the field position iterator.
+ */
+void DateFormatTest::Test20741_ABFields() {
+ IcuTestErrorCode status(*this, "Test20741_ABFields");
+
+ const char16_t timeZone[] = u"PST8PDT";
+
+ UnicodeString skeletons[] = {u"EEEEEBBBBB", u"EEEEEbbbbb"};
+
+ for (int32_t j = 0; j < 2; j++) {
+ UnicodeString skeleton = skeletons[j];
+
+ int32_t count = 0;
+ const Locale* locales = Locale::getAvailableLocales(count);
+ for (int32_t i = 0; i < count; i++) {
+ if (quick && (i % 17) != 0) { continue; }
+
+ const Locale locale = locales[i];
+ LocalPointer<DateTimePatternGenerator> gen(DateTimePatternGenerator::createInstance(locale, status));
+ UnicodeString pattern = gen->getBestPattern(skeleton, status);
+
+ SimpleDateFormat dateFormat(pattern, locale, status);
+ FieldPositionIterator fpositer;
+ UnicodeString result;
+ LocalPointer<Calendar> calendar(Calendar::createInstance(TimeZone::createTimeZone(timeZone), status));
+ calendar->setTime(UDate(0), status);
+ dateFormat.format(*calendar, result, &fpositer, status);
+
+ FieldPosition curFieldPosition;
+ FieldPosition lastFieldPosition;
+ lastFieldPosition.setBeginIndex(-1);
+ lastFieldPosition.setEndIndex(-1);
+ while(fpositer.next(curFieldPosition)) {
+ assertFalse("Field missing on pattern", pattern.indexOf(PATTERN_CHARS[curFieldPosition.getField()]) == -1);
+ if (curFieldPosition.getBeginIndex() == lastFieldPosition.getBeginIndex() && curFieldPosition.getEndIndex() == lastFieldPosition.getEndIndex()) {
+ assertEquals("Different fields at same position", PATTERN_CHARS[curFieldPosition.getField()], PATTERN_CHARS[lastFieldPosition.getField()]);
+ }
+
+ lastFieldPosition = curFieldPosition;
+ }
+ }
+ }
+}
+
#endif /* #if !UCONFIG_NO_FORMATTING */
//eof
diff --git a/icu4c/source/test/intltest/dtfmttst.h b/icu4c/source/test/intltest/dtfmttst.h
index fd07b5a..ac36919 100644
--- a/icu4c/source/test/intltest/dtfmttst.h
+++ b/icu4c/source/test/intltest/dtfmttst.h
@@ -263,6 +263,7 @@
void TestDayPeriodParsing();
void TestParseRegression13744();
void TestAdoptCalendarLeak();
+ void Test20741_ABFields();
private:
UBool showParse(DateFormat &format, const UnicodeString &formattedString);
diff --git a/icu4c/source/test/intltest/loctest.cpp b/icu4c/source/test/intltest/loctest.cpp
index 73f1b73..06fd617 100644
--- a/icu4c/source/test/intltest/loctest.cpp
+++ b/icu4c/source/test/intltest/loctest.cpp
@@ -3156,6 +3156,7 @@
static const char tag_ill[] = "!";
static const char tag_no_nul[] = { 'e', 'n', '-', 'G', 'B' };
static const char tag_ext[] = "en-GB-1-abc-efg-a-xyz";
+ static const char tag_var[] = "sl-rozaj-biske-1994";
static const Locale loc_en("en_US");
static const Locale loc_oed("en_GB_OXENDICT");
@@ -3163,6 +3164,7 @@
static const Locale loc_null("");
static const Locale loc_gb("en_GB");
static const Locale loc_ext("en_GB@1=abc-efg;a=xyz");
+ static const Locale loc_var("sl__1994_BISKE_ROZAJ");
Locale result_en = Locale::forLanguageTag(tag_en, status);
status.errIfFailureAndReset("\"%s\"", tag_en);
@@ -3176,6 +3178,10 @@
status.errIfFailureAndReset("\"%s\"", tag_af);
assertEquals(tag_af, loc_af.getName(), result_af.getName());
+ Locale result_var = Locale::forLanguageTag(tag_var, status);
+ status.errIfFailureAndReset("\"%s\"", tag_var);
+ assertEquals(tag_var, loc_var.getName(), result_var.getName());
+
Locale result_ill = Locale::forLanguageTag(tag_ill, status);
assertEquals(tag_ill, U_ILLEGAL_ARGUMENT_ERROR, status.reset());
assertTrue(result_ill.getName(), result_ill.isBogus());
@@ -3210,12 +3216,14 @@
static const Locale loc_ext("en@0=abc;a=xyz");
static const Locale loc_empty("");
static const Locale loc_ill("!");
+ static const Locale loc_variant("sl__ROZAJ_BISKE_1994");
static const char tag_c[] = "en-US-u-va-posix";
static const char tag_en[] = "en-US";
static const char tag_af[] = "af-t-ar-i0-handwrit-u-ca-coptic-x-foo";
static const char tag_ext[] = "en-0-abc-a-xyz";
static const char tag_und[] = "und";
+ static const char tag_variant[] = "sl-1994-biske-rozaj";
std::string result;
StringByteSink<std::string> sink(&result);
@@ -3247,6 +3255,10 @@
status.errIfFailureAndReset("\"%s\"", loc_ill.getName());
assertEquals(loc_ill.getName(), tag_und, result_ill.c_str());
+ std::string result_variant = loc_variant.toLanguageTag<std::string>(status);
+ status.errIfFailureAndReset("\"%s\"", loc_variant.getName());
+ assertEquals(loc_variant.getName(), tag_variant, result_variant.c_str());
+
Locale loc_bogus;
loc_bogus.setToBogus();
std::string result_bogus = loc_bogus.toLanguageTag<std::string>(status);
diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h
index 7f5bcdf..adccc9e 100644
--- a/icu4c/source/test/intltest/numbertest.h
+++ b/icu4c/source/test/intltest/numbertest.h
@@ -68,6 +68,7 @@
// TODO: Add this method if currency symbols override support is added.
//void symbolsOverride();
void sign();
+ void signNearZero();
void signCoverage();
void decimal();
void scale();
diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp
index 4e4f0b7..6b4024a 100644
--- a/icu4c/source/test/intltest/numbertest_api.cpp
+++ b/icu4c/source/test/intltest/numbertest_api.cpp
@@ -86,6 +86,7 @@
// TODO: Add this method if currency symbols override support is added.
//TESTCASE_AUTO(symbolsOverride);
TESTCASE_AUTO(sign);
+ TESTCASE_AUTO(signNearZero);
TESTCASE_AUTO(signCoverage);
TESTCASE_AUTO(decimal);
TESTCASE_AUTO(scale);
@@ -1647,7 +1648,7 @@
u".8765",
u".08765",
u".008765",
- u""); // TODO: Avoid the empty string here?
+ u"0"); // see ICU-20844
assertFormatDescending(
u"Integer Width Zero Fill 3",
@@ -1694,6 +1695,57 @@
u"00.008765",
u"00");
+ assertFormatDescending(
+ u"Integer Width Compact",
+ u"compact-short integer-width/000",
+ NumberFormatter::with()
+ .notation(Notation::compactShort())
+ .integerWidth(IntegerWidth::zeroFillTo(3).truncateAt(3)),
+ Locale::getEnglish(),
+ u"088K",
+ u"008.8K",
+ u"876",
+ u"088",
+ u"008.8",
+ u"000.88",
+ u"000.088",
+ u"000.0088",
+ u"000");
+
+ assertFormatDescending(
+ u"Integer Width Scientific",
+ u"scientific integer-width/000",
+ NumberFormatter::with()
+ .notation(Notation::scientific())
+ .integerWidth(IntegerWidth::zeroFillTo(3).truncateAt(3)),
+ Locale::getEnglish(),
+ u"008.765E4",
+ u"008.765E3",
+ u"008.765E2",
+ u"008.765E1",
+ u"008.765E0",
+ u"008.765E-1",
+ u"008.765E-2",
+ u"008.765E-3",
+ u"000E0");
+
+ assertFormatDescending(
+ u"Integer Width Engineering",
+ u"engineering integer-width/000",
+ NumberFormatter::with()
+ .notation(Notation::engineering())
+ .integerWidth(IntegerWidth::zeroFillTo(3).truncateAt(3)),
+ Locale::getEnglish(),
+ u"087.65E3",
+ u"008.765E3",
+ u"876.5E0",
+ u"087.65E0",
+ u"008.765E0",
+ u"876.5E-3",
+ u"087.65E-3",
+ u"008.765E-3",
+ u"000E0");
+
assertFormatSingle(
u"Integer Width Remove All A",
u"integer-width/00",
@@ -2104,6 +2156,49 @@
u"-444,444.00 US dollars");
}
+void NumberFormatterApiTest::signNearZero() {
+ // https://unicode-org.atlassian.net/browse/ICU-20709
+ IcuTestErrorCode status(*this, "signNearZero");
+ const struct TestCase {
+ UNumberSignDisplay sign;
+ double input;
+ const char16_t* expected;
+ } cases[] = {
+ { UNUM_SIGN_AUTO, 1.1, u"1" },
+ { UNUM_SIGN_AUTO, 0.9, u"1" },
+ { UNUM_SIGN_AUTO, 0.1, u"0" },
+ { UNUM_SIGN_AUTO, -0.1, u"-0" }, // interesting case
+ { UNUM_SIGN_AUTO, -0.9, u"-1" },
+ { UNUM_SIGN_AUTO, -1.1, u"-1" },
+ { UNUM_SIGN_ALWAYS, 1.1, u"+1" },
+ { UNUM_SIGN_ALWAYS, 0.9, u"+1" },
+ { UNUM_SIGN_ALWAYS, 0.1, u"+0" },
+ { UNUM_SIGN_ALWAYS, -0.1, u"-0" },
+ { UNUM_SIGN_ALWAYS, -0.9, u"-1" },
+ { UNUM_SIGN_ALWAYS, -1.1, u"-1" },
+ { UNUM_SIGN_EXCEPT_ZERO, 1.1, u"+1" },
+ { UNUM_SIGN_EXCEPT_ZERO, 0.9, u"+1" },
+ { UNUM_SIGN_EXCEPT_ZERO, 0.1, u"0" }, // interesting case
+ { UNUM_SIGN_EXCEPT_ZERO, -0.1, u"0" }, // interesting case
+ { UNUM_SIGN_EXCEPT_ZERO, -0.9, u"-1" },
+ { UNUM_SIGN_EXCEPT_ZERO, -1.1, u"-1" },
+ };
+ for (auto& cas : cases) {
+ auto sign = cas.sign;
+ auto input = cas.input;
+ auto expected = cas.expected;
+ auto actual = NumberFormatter::with()
+ .sign(sign)
+ .precision(Precision::integer())
+ .locale(Locale::getUS())
+ .formatDouble(input, status)
+ .toString(status);
+ assertEquals(
+ DoubleToUnicodeString(input) + " @ SignDisplay " + Int64ToUnicodeString(sign),
+ expected, actual);
+ }
+}
+
void NumberFormatterApiTest::signCoverage() {
// https://unicode-org.atlassian.net/browse/ICU-20708
IcuTestErrorCode status(*this, "signCoverage");
@@ -2114,7 +2209,7 @@
{ UNUM_SIGN_AUTO, { u"-∞", u"-1", u"-0", u"0", u"1", u"∞", u"NaN", u"-NaN" } },
{ UNUM_SIGN_ALWAYS, { u"-∞", u"-1", u"-0", u"+0", u"+1", u"+∞", u"+NaN", u"-NaN" } },
{ UNUM_SIGN_NEVER, { u"∞", u"1", u"0", u"0", u"1", u"∞", u"NaN", u"NaN" } },
- { UNUM_SIGN_EXCEPT_ZERO, { u"-∞", u"-1", u"-0", u"0", u"+1", u"+∞", u"NaN", u"-NaN" } },
+ { UNUM_SIGN_EXCEPT_ZERO, { u"-∞", u"-1", u"0", u"0", u"+1", u"+∞", u"NaN", u"NaN" } },
};
double negNaN = std::copysign(uprv_getNaN(), -0.0);
const double inputs[] = {
diff --git a/icu4c/source/test/intltest/numbertest_patternmodifier.cpp b/icu4c/source/test/intltest/numbertest_patternmodifier.cpp
index 8f61242..1d1e634 100644
--- a/icu4c/source/test/intltest/numbertest_patternmodifier.cpp
+++ b/icu4c/source/test/intltest/numbertest_patternmodifier.cpp
@@ -41,7 +41,10 @@
mod.setPatternAttributes(UNUM_SIGN_ALWAYS, false);
assertEquals("Pattern a0b", u"+a", getPrefix(mod, status));
assertEquals("Pattern a0b", u"b", getSuffix(mod, status));
- mod.setNumberProperties(SIGNUM_ZERO, StandardPlural::Form::COUNT);
+ mod.setNumberProperties(SIGNUM_NEG_ZERO, StandardPlural::Form::COUNT);
+ assertEquals("Pattern a0b", u"-a", getPrefix(mod, status));
+ assertEquals("Pattern a0b", u"b", getSuffix(mod, status));
+ mod.setNumberProperties(SIGNUM_POS_ZERO, StandardPlural::Form::COUNT);
assertEquals("Pattern a0b", u"+a", getPrefix(mod, status));
assertEquals("Pattern a0b", u"b", getSuffix(mod, status));
mod.setPatternAttributes(UNUM_SIGN_EXCEPT_ZERO, false);
@@ -66,7 +69,10 @@
mod.setPatternAttributes(UNUM_SIGN_ALWAYS, false);
assertEquals("Pattern a0b;c-0d", u"c+", getPrefix(mod, status));
assertEquals("Pattern a0b;c-0d", u"d", getSuffix(mod, status));
- mod.setNumberProperties(SIGNUM_ZERO, StandardPlural::Form::COUNT);
+ mod.setNumberProperties(SIGNUM_NEG_ZERO, StandardPlural::Form::COUNT);
+ assertEquals("Pattern a0b;c-0d", u"c-", getPrefix(mod, status));
+ assertEquals("Pattern a0b;c-0d", u"d", getSuffix(mod, status));
+ mod.setNumberProperties(SIGNUM_POS_ZERO, StandardPlural::Form::COUNT);
assertEquals("Pattern a0b;c-0d", u"c+", getPrefix(mod, status));
assertEquals("Pattern a0b;c-0d", u"d", getSuffix(mod, status));
mod.setPatternAttributes(UNUM_SIGN_EXCEPT_ZERO, false);
@@ -76,9 +82,8 @@
assertEquals("Pattern a0b;c-0d", u"c-", getPrefix(mod, status));
assertEquals("Pattern a0b;c-0d", u"d", getSuffix(mod, status));
mod.setPatternAttributes(UNUM_SIGN_NEVER, false);
- // TODO: What should this behavior be?
- assertEquals("Pattern a0b;c-0d", u"c-", getPrefix(mod, status));
- assertEquals("Pattern a0b;c-0d", u"d", getSuffix(mod, status));
+ assertEquals("Pattern a0b;c-0d", u"a", getPrefix(mod, status));
+ assertEquals("Pattern a0b;c-0d", u"b", getSuffix(mod, status));
assertSuccess("Spot 5", status);
}
diff --git a/icu4c/source/test/testdata/numberpermutationtest.txt b/icu4c/source/test/testdata/numberpermutationtest.txt
index b9c8558..4b4656e 100644
--- a/icu4c/source/test/testdata/numberpermutationtest.txt
+++ b/icu4c/source/test/testdata/numberpermutationtest.txt
@@ -2273,15 +2273,15 @@
es-MX
0
+92 k
- -0
+ 0
zh-TW
0
+9萬
- -0
+ 0
bn-BD
০
+৯২ হা
- -০
+ ০
compact-short .000 sign-accounting-except-zero
es-MX
@@ -4849,15 +4849,15 @@
es-MX
0 %
+91,827 %
- -0 %
+ 0 %
zh-TW
0%
+91,827%
- -0%
+ 0%
bn-BD
০%
+৯১,৮২৭%
- -০%
+ ০%
percent .000 sign-accounting-except-zero
es-MX
@@ -4905,15 +4905,15 @@
es-MX
EUR 0
+EUR 91,827
- -EUR 0
+ EUR 0
zh-TW
€0
+€91,827
- (€0)
+ €0
bn-BD
০€
+৯১,৮২৭€
- (০ €)
+ ০€
currency/EUR .000 sign-accounting-except-zero
es-MX
@@ -4961,15 +4961,15 @@
es-MX
0 fur
+91,827 fur
- -0 fur
+ 0 fur
zh-TW
0 化朗
+91,827 化朗
- -0 化朗
+ 0 化朗
bn-BD
০ ফার্লং
+৯১,৮২৭ ফার্লং
- -০ ফার্লং
+ ০ ফার্লং
measure-unit/length-furlong .000 sign-accounting-except-zero
es-MX
@@ -6627,15 +6627,15 @@
es-MX
0
+91,827
- -0
+ 0
zh-TW
0
+91,827
- -0
+ 0
bn-BD
০
+৯১,৮২৭
- -০
+ ০
unit-width-narrow .000 sign-accounting-except-zero
es-MX
@@ -6683,15 +6683,15 @@
es-MX
0
+91,827
- -0
+ 0
zh-TW
0
+91,827
- -0
+ 0
bn-BD
০
+৯১,৮২৭
- -০
+ ০
unit-width-full-name .000 sign-accounting-except-zero
es-MX
@@ -7943,15 +7943,15 @@
es-MX
00
+1827
- -00
+ 00
zh-TW
00
+1,827
- -00
+ 00
bn-BD
০০
+১,৮২৭
- -০০
+ ০০
.000 integer-width/##00 sign-accounting-except-zero
es-MX
@@ -8167,15 +8167,15 @@
es-MX
0
+45,914
- -0
+ 0
zh-TW
0
+45,914
- -0
+ 0
bn-BD
০
+৪৫,৯১৪
- -০
+ ০
.000 scale/0.5 sign-accounting-except-zero
es-MX
@@ -8335,15 +8335,15 @@
es-MX
0
+91,827
- -0
+ 0
zh-TW
0
+91,827
- -0
+ 0
bn-BD
০
+৯১,৮২৭
- -০
+ ০
.000 group-on-aligned sign-accounting-except-zero
es-MX
@@ -8447,15 +8447,15 @@
es-MX
0
+91,827
- -0
+ 0
zh-TW
0
+91,827
- -0
+ 0
bn-BD
0
+91,827
- -0
+ 0
.000 latin sign-accounting-except-zero
es-MX
@@ -8559,15 +8559,15 @@
es-MX
0.
+91,827.
- -0.
+ 0.
zh-TW
0.
+91,827.
- -0.
+ 0.
bn-BD
০.
+৯১,৮২৭.
- -০.
+ ০.
.000 sign-accounting-except-zero decimal-always
es-MX
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/locale/InternalLocaleBuilder.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/locale/InternalLocaleBuilder.java
index 615156b..2a676a36 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/locale/InternalLocaleBuilder.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/locale/InternalLocaleBuilder.java
@@ -9,6 +9,7 @@
package com.ibm.icu.impl.locale;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -326,7 +327,8 @@
_script = langtag.getScript();
_region = langtag.getRegion();
- List<String> bcpVariants = langtag.getVariants();
+ ArrayList<String> bcpVariants = new ArrayList<String>(langtag.getVariants());
+ Collections.sort(bcpVariants);
if (bcpVariants.size() > 0) {
StringBuilder var = new StringBuilder(bcpVariants.get(0));
for (int i = 1; i < bcpVariants.size(); i++) {
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/AdoptingModifierStore.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/AdoptingModifierStore.java
index 7e3459d..93da08b 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/AdoptingModifierStore.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/AdoptingModifierStore.java
@@ -3,6 +3,7 @@
package com.ibm.icu.impl.number;
import com.ibm.icu.impl.StandardPlural;
+import com.ibm.icu.impl.number.Modifier.Signum;
/**
* This implementation of ModifierStore adopts references to Modifiers.
@@ -11,7 +12,8 @@
*/
public class AdoptingModifierStore implements ModifierStore {
private final Modifier positive;
- private final Modifier zero;
+ private final Modifier posZero;
+ private final Modifier negZero;
private final Modifier negative;
final Modifier[] mods;
boolean frozen;
@@ -22,9 +24,10 @@
* <p>
* If this constructor is used, a plural form CANNOT be passed to {@link #getModifier}.
*/
- public AdoptingModifierStore(Modifier positive, Modifier zero, Modifier negative) {
+ public AdoptingModifierStore(Modifier positive, Modifier posZero, Modifier negZero, Modifier negative) {
this.positive = positive;
- this.zero = zero;
+ this.posZero = posZero;
+ this.negZero = negZero;
this.negative = negative;
this.mods = null;
this.frozen = true;
@@ -39,13 +42,14 @@
*/
public AdoptingModifierStore() {
this.positive = null;
- this.zero = null;
+ this.posZero = null;
+ this.negZero = null;
this.negative = null;
- this.mods = new Modifier[3 * StandardPlural.COUNT];
+ this.mods = new Modifier[4 * StandardPlural.COUNT];
this.frozen = false;
}
- public void setModifier(int signum, StandardPlural plural, Modifier mod) {
+ public void setModifier(Signum signum, StandardPlural plural, Modifier mod) {
assert !frozen;
mods[getModIndex(signum, plural)] = mod;
}
@@ -54,21 +58,34 @@
frozen = true;
}
- public Modifier getModifierWithoutPlural(int signum) {
+ public Modifier getModifierWithoutPlural(Signum signum) {
assert frozen;
assert mods == null;
- return signum == 0 ? zero : signum < 0 ? negative : positive;
+ assert signum != null;
+ switch (signum) {
+ case POS:
+ return positive;
+ case POS_ZERO:
+ return posZero;
+ case NEG_ZERO:
+ return negZero;
+ case NEG:
+ return negative;
+ default:
+ throw new AssertionError("Unreachable");
+ }
}
- public Modifier getModifier(int signum, StandardPlural plural) {
+ @Override
+ public Modifier getModifier(Signum signum, StandardPlural plural) {
assert frozen;
assert positive == null;
return mods[getModIndex(signum, plural)];
}
- private static int getModIndex(int signum, StandardPlural plural) {
- assert signum >= -1 && signum <= 1;
+ private static int getModIndex(Signum signum, StandardPlural plural) {
+ assert signum != null;
assert plural != null;
- return plural.ordinal() * 3 + (signum + 1);
+ return plural.ordinal() * Signum.COUNT + signum.ordinal();
}
}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java
index d551c32..7d30c58 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java
@@ -7,6 +7,7 @@
import java.text.FieldPosition;
import com.ibm.icu.impl.StandardPlural;
+import com.ibm.icu.impl.number.Modifier.Signum;
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.text.UFieldPosition;
@@ -130,8 +131,8 @@
/** @return Whether the value represented by this {@link DecimalQuantity} is less than zero. */
public boolean isNegative();
- /** @return -1 if the value is negative; 1 if positive; or 0 if zero. */
- public int signum();
+ /** @return The appropriate value from the Signum enum. */
+ public Signum signum();
/** @return Whether the value represented by this {@link DecimalQuantity} is infinite. */
@Override
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java
index badc530..09c8893 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java
@@ -9,6 +9,7 @@
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.impl.Utility;
+import com.ibm.icu.impl.number.Modifier.Signum;
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.text.PluralRules.Operand;
import com.ibm.icu.text.UFieldPosition;
@@ -303,8 +304,18 @@
}
@Override
- public int signum() {
- return isNegative() ? -1 : (isZeroish() && !isInfinite()) ? 0 : 1;
+ public Signum signum() {
+ boolean isZero = (isZeroish() && !isInfinite());
+ boolean isNeg = isNegative();
+ if (isZero && isNeg) {
+ return Signum.NEG_ZERO;
+ } else if (isZero) {
+ return Signum.POS_ZERO;
+ } else if (isNeg) {
+ return Signum.NEG;
+ } else {
+ return Signum.POS;
+ }
}
@Override
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java
index c0c6a74..459e514 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java
@@ -12,6 +12,7 @@
import com.ibm.icu.impl.SimpleFormatterImpl;
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.impl.UResource;
+import com.ibm.icu.impl.number.Modifier.Signum;
import com.ibm.icu.number.NumberFormatter.UnitWidth;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.text.PluralRules;
@@ -262,7 +263,7 @@
String compiled = SimpleFormatterImpl.compileToStringMinMaxArguments(simpleFormat, sb, 0, 1);
Modifier.Parameters parameters = new Modifier.Parameters();
parameters.obj = this;
- parameters.signum = 0;
+ parameters.signum = null;// Signum ignored
parameters.plural = plural;
modifiers.put(plural, new SimpleModifier(compiled, field, false, parameters));
}
@@ -281,7 +282,7 @@
.compileToStringMinMaxArguments(compoundFormat, sb, 0, 1);
Modifier.Parameters parameters = new Modifier.Parameters();
parameters.obj = this;
- parameters.signum = 0;
+ parameters.signum = null; // Signum ignored
parameters.plural = plural;
modifiers.put(plural, new SimpleModifier(compoundCompiled, field, false, parameters));
}
@@ -296,7 +297,8 @@
}
@Override
- public Modifier getModifier(int signum, StandardPlural plural) {
+ public Modifier getModifier(Signum signum, StandardPlural plural) {
+ // Signum ignored
return modifiers.get(plural);
}
}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Modifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Modifier.java
index cc56329..699ba3b 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Modifier.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Modifier.java
@@ -17,6 +17,15 @@
*/
public interface Modifier {
+ static enum Signum {
+ NEG,
+ NEG_ZERO,
+ POS_ZERO,
+ POS;
+
+ static final int COUNT = Signum.values().length;
+ };
+
/**
* Apply this Modifier to the string builder.
*
@@ -65,7 +74,7 @@
*/
public static class Parameters {
public ModifierStore obj;
- public int signum;
+ public Signum signum;
public StandardPlural plural;
}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ModifierStore.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ModifierStore.java
index 1751c1c..2546cf2 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ModifierStore.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ModifierStore.java
@@ -3,6 +3,7 @@
package com.ibm.icu.impl.number;
import com.ibm.icu.impl.StandardPlural;
+import com.ibm.icu.impl.number.Modifier.Signum;
/**
* This is *not* a modifier; rather, it is an object that can return modifiers
@@ -14,5 +15,5 @@
/**
* Returns a Modifier with the given parameters (best-effort).
*/
- Modifier getModifier(int signum, StandardPlural plural);
+ Modifier getModifier(Signum signum, StandardPlural plural);
}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java
index 67f79b9..2a44151 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java
@@ -50,7 +50,7 @@
PluralRules rules;
// Number details
- int signum;
+ Signum signum;
StandardPlural plural;
// QuantityChain details
@@ -129,7 +129,7 @@
* The plural form of the number, required only if the pattern contains the triple
* currency sign, "¤¤¤" (and as indicated by {@link #needsPlurals()}).
*/
- public void setNumberProperties(int signum, StandardPlural plural) {
+ public void setNumberProperties(Signum signum, StandardPlural plural) {
assert (plural != null) == needsPlurals();
this.signum = signum;
this.plural = plural;
@@ -155,44 +155,35 @@
* @return An immutable that supports both positive and negative numbers.
*/
public ImmutablePatternModifier createImmutable() {
- return createImmutableAndChain(null);
- }
-
- /**
- * Creates a new quantity-dependent Modifier that behaves the same as the current instance, but which
- * is immutable and can be saved for future use. The number properties in the current instance are
- * mutated; all other properties are left untouched.
- *
- * @param parent
- * The QuantityChain to which to chain this immutable.
- * @return An immutable that supports both positive and negative numbers.
- */
- public ImmutablePatternModifier createImmutableAndChain(MicroPropsGenerator parent) {
FormattedStringBuilder a = new FormattedStringBuilder();
FormattedStringBuilder b = new FormattedStringBuilder();
if (needsPlurals()) {
// Slower path when we require the plural keyword.
AdoptingModifierStore pm = new AdoptingModifierStore();
for (StandardPlural plural : StandardPlural.VALUES) {
- setNumberProperties(1, plural);
- pm.setModifier(1, plural, createConstantModifier(a, b));
- setNumberProperties(0, plural);
- pm.setModifier(0, plural, createConstantModifier(a, b));
- setNumberProperties(-1, plural);
- pm.setModifier(-1, plural, createConstantModifier(a, b));
+ setNumberProperties(Signum.POS, plural);
+ pm.setModifier(Signum.POS, plural, createConstantModifier(a, b));
+ setNumberProperties(Signum.POS_ZERO, plural);
+ pm.setModifier(Signum.POS_ZERO, plural, createConstantModifier(a, b));
+ setNumberProperties(Signum.NEG_ZERO, plural);
+ pm.setModifier(Signum.NEG_ZERO, plural, createConstantModifier(a, b));
+ setNumberProperties(Signum.NEG, plural);
+ pm.setModifier(Signum.NEG, plural, createConstantModifier(a, b));
}
pm.freeze();
- return new ImmutablePatternModifier(pm, rules, parent);
+ return new ImmutablePatternModifier(pm, rules);
} else {
// Faster path when plural keyword is not needed.
- setNumberProperties(1, null);
+ setNumberProperties(Signum.POS, null);
Modifier positive = createConstantModifier(a, b);
- setNumberProperties(0, null);
- Modifier zero = createConstantModifier(a, b);
- setNumberProperties(-1, null);
+ setNumberProperties(Signum.POS_ZERO, null);
+ Modifier posZero = createConstantModifier(a, b);
+ setNumberProperties(Signum.NEG_ZERO, null);
+ Modifier negZero = createConstantModifier(a, b);
+ setNumberProperties(Signum.NEG, null);
Modifier negative = createConstantModifier(a, b);
- AdoptingModifierStore pm = new AdoptingModifierStore(positive, zero, negative);
- return new ImmutablePatternModifier(pm, null, parent);
+ AdoptingModifierStore pm = new AdoptingModifierStore(positive, posZero, negZero, negative);
+ return new ImmutablePatternModifier(pm, null);
}
}
@@ -222,20 +213,30 @@
public static class ImmutablePatternModifier implements MicroPropsGenerator {
final AdoptingModifierStore pm;
final PluralRules rules;
- final MicroPropsGenerator parent;
+ /* final */ MicroPropsGenerator parent;
ImmutablePatternModifier(
AdoptingModifierStore pm,
- PluralRules rules,
- MicroPropsGenerator parent) {
+ PluralRules rules) {
this.pm = pm;
this.rules = rules;
+ this.parent = null;
+ }
+
+ public ImmutablePatternModifier addToChain(MicroPropsGenerator parent) {
this.parent = parent;
+ return this;
}
@Override
public MicroProps processQuantity(DecimalQuantity quantity) {
MicroProps micros = parent.processQuantity(quantity);
+ if (micros.rounder != null) {
+ micros.rounder.apply(quantity);
+ }
+ if (micros.modMiddle != null) {
+ return micros;
+ }
applyToMicros(micros, quantity);
return micros;
}
@@ -270,6 +271,12 @@
@Override
public MicroProps processQuantity(DecimalQuantity fq) {
MicroProps micros = parent.processQuantity(fq);
+ if (micros.rounder != null) {
+ micros.rounder.apply(fq);
+ }
+ if (micros.modMiddle != null) {
+ return micros;
+ }
if (needsPlurals()) {
StandardPlural pluralForm = RoundingUtils.getPluralSafe(micros.rounder, rules, fq);
setNumberProperties(fq.signum(), pluralForm);
@@ -367,8 +374,7 @@
}
PatternStringUtils.patternInfoToStringBuilder(patternInfo,
isPrefix,
- signum,
- signDisplay,
+ PatternStringUtils.resolveSignDisplay(signDisplay, signum),
plural,
perMilleReplacesPercent,
currentAffix);
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringUtils.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringUtils.java
index 26eb457..601f1c8 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringUtils.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringUtils.java
@@ -5,6 +5,7 @@
import java.math.BigDecimal;
import com.ibm.icu.impl.StandardPlural;
+import com.ibm.icu.impl.number.Modifier.Signum;
import com.ibm.icu.impl.number.Padder.PadPosition;
import com.ibm.icu.number.NumberFormatter.SignDisplay;
import com.ibm.icu.text.DecimalFormatSymbols;
@@ -14,6 +15,18 @@
*/
public class PatternStringUtils {
+ // Note: the order of fields in this enum matters for parsing.
+ public static enum PatternSignType {
+ // Render using normal positive subpattern rules
+ POS,
+ // Render using rules to force the display of a plus sign
+ POS_SIGN,
+ // Render using negative subpattern rules
+ NEG;
+
+ public static final PatternSignType[] VALUES = PatternSignType.values();
+ };
+
/**
* Determine whether a given roundingIncrement should be ignored for formatting
* based on the current maxFrac value (maximum fraction digits). For example a
@@ -23,7 +36,7 @@
* it should not be ignored if maxFrac is 2 or more (but a roundingIncrement of
* 0.005 is treated like 0.001 for significance).
*
- * This test is needed for both NumberPropertyMapper.oldToNew and
+ * This test is needed for both NumberPropertyMapper.oldToNew and
* PatternStringUtils.propertiesToPatternString, but NumberPropertyMapper
* is package-private so we have it here.
*
@@ -416,25 +429,19 @@
public static void patternInfoToStringBuilder(
AffixPatternProvider patternInfo,
boolean isPrefix,
- int signum,
- SignDisplay signDisplay,
+ PatternSignType patternSignType,
StandardPlural plural,
boolean perMilleReplacesPercent,
StringBuilder output) {
- // Should the output render '+' where '-' would normally appear in the pattern?
- boolean plusReplacesMinusSign = signum != -1
- && (signDisplay == SignDisplay.ALWAYS
- || signDisplay == SignDisplay.ACCOUNTING_ALWAYS
- || (signum == 1
- && (signDisplay == SignDisplay.EXCEPT_ZERO
- || signDisplay == SignDisplay.ACCOUNTING_EXCEPT_ZERO)))
- && patternInfo.positiveHasPlusSign() == false;
+ boolean plusReplacesMinusSign = (patternSignType == PatternSignType.POS_SIGN)
+ && !patternInfo.positiveHasPlusSign();
- // Should we use the affix from the negative subpattern? (If not, we will use the positive
- // subpattern.)
+ // Should we use the affix from the negative subpattern?
+ // (If not, we will use the positive subpattern.)
boolean useNegativeAffixPattern = patternInfo.hasNegativeSubpattern()
- && (signum == -1 || (patternInfo.negativeHasMinusSign() && plusReplacesMinusSign));
+ && (patternSignType == PatternSignType.NEG
+ || (patternInfo.negativeHasMinusSign() && plusReplacesMinusSign));
// Resolve the flags for the affix pattern.
int flags = 0;
@@ -453,8 +460,8 @@
boolean prependSign;
if (!isPrefix || useNegativeAffixPattern) {
prependSign = false;
- } else if (signum == -1) {
- prependSign = signDisplay != SignDisplay.NEVER;
+ } else if (patternSignType == PatternSignType.NEG) {
+ prependSign = true;
} else {
prependSign = plusReplacesMinusSign;
}
@@ -483,4 +490,53 @@
}
}
+ public static PatternSignType resolveSignDisplay(SignDisplay signDisplay, Signum signum) {
+ switch (signDisplay) {
+ case AUTO:
+ case ACCOUNTING:
+ switch (signum) {
+ case NEG:
+ case NEG_ZERO:
+ return PatternSignType.NEG;
+ case POS_ZERO:
+ case POS:
+ return PatternSignType.POS;
+ }
+ break;
+
+ case ALWAYS:
+ case ACCOUNTING_ALWAYS:
+ switch (signum) {
+ case NEG:
+ case NEG_ZERO:
+ return PatternSignType.NEG;
+ case POS_ZERO:
+ case POS:
+ return PatternSignType.POS_SIGN;
+ }
+ break;
+
+ case EXCEPT_ZERO:
+ case ACCOUNTING_EXCEPT_ZERO:
+ switch (signum) {
+ case NEG:
+ return PatternSignType.NEG;
+ case NEG_ZERO:
+ case POS_ZERO:
+ return PatternSignType.POS;
+ case POS:
+ return PatternSignType.POS_SIGN;
+ }
+ break;
+
+ case NEVER:
+ return PatternSignType.POS;
+
+ default:
+ break;
+ }
+
+ throw new AssertionError("Unreachable");
+ }
+
}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/RoundingUtils.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/RoundingUtils.java
index 0f35a66..7452929 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/RoundingUtils.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/RoundingUtils.java
@@ -228,6 +228,9 @@
*/
public static StandardPlural getPluralSafe(
Precision rounder, PluralRules rules, DecimalQuantity dq) {
+ if (rounder == null) {
+ return dq.getStandardPlural(rules);
+ }
// TODO(ICU-20500): Avoid the copy?
DecimalQuantity copy = dq.createCopy();
rounder.apply(copy);
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java
index 4292504..51b8df0 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java
@@ -12,7 +12,7 @@
import com.ibm.icu.impl.number.AffixPatternProvider;
import com.ibm.icu.impl.number.AffixUtils;
import com.ibm.icu.impl.number.PatternStringUtils;
-import com.ibm.icu.number.NumberFormatter.SignDisplay;
+import com.ibm.icu.impl.number.PatternStringUtils.PatternSignType;
/**
* @author sffc
@@ -90,20 +90,27 @@
StringBuilder sb = new StringBuilder();
ArrayList<AffixMatcher> matchers = new ArrayList<>(6);
boolean includeUnpaired = 0 != (parseFlags & ParsingUtils.PARSE_FLAG_INCLUDE_UNPAIRED_AFFIXES);
- SignDisplay signDisplay = (0 != (parseFlags & ParsingUtils.PARSE_FLAG_PLUS_SIGN_ALLOWED))
- ? SignDisplay.ALWAYS
- : SignDisplay.AUTO;
AffixPatternMatcher posPrefix = null;
AffixPatternMatcher posSuffix = null;
// Pre-process the affix strings to resolve LDML rules like sign display.
- for (int signum = 1; signum >= -1; signum--) {
+ for (PatternSignType type : PatternSignType.VALUES) {
+
+ // Skip affixes in some cases
+ if (type == PatternSignType.POS
+ && 0 != (parseFlags & ParsingUtils.PARSE_FLAG_PLUS_SIGN_ALLOWED)) {
+ continue;
+ }
+ if (type == PatternSignType.POS_SIGN
+ && 0 == (parseFlags & ParsingUtils.PARSE_FLAG_PLUS_SIGN_ALLOWED)) {
+ continue;
+ }
+
// Generate Prefix
PatternStringUtils.patternInfoToStringBuilder(patternInfo,
true,
- signum,
- signDisplay,
+ type,
StandardPlural.OTHER,
false,
sb);
@@ -113,15 +120,14 @@
// Generate Suffix
PatternStringUtils.patternInfoToStringBuilder(patternInfo,
false,
- signum,
- signDisplay,
+ type,
StandardPlural.OTHER,
false,
sb);
AffixPatternMatcher suffix = AffixPatternMatcher
.fromAffixPattern(sb.toString(), factory, parseFlags);
- if (signum == 1) {
+ if (type == PatternSignType.POS) {
posPrefix = prefix;
posSuffix = suffix;
} else if (Objects.equals(prefix, posPrefix) && Objects.equals(suffix, posSuffix)) {
@@ -130,17 +136,17 @@
}
// Flags for setting in the ParsedNumber; the token matchers may add more.
- int flags = (signum == -1) ? ParsedNumber.FLAG_NEGATIVE : 0;
+ int flags = (type == PatternSignType.NEG) ? ParsedNumber.FLAG_NEGATIVE : 0;
// Note: it is indeed possible for posPrefix and posSuffix to both be null.
// We still need to add that matcher for strict mode to work.
matchers.add(getInstance(prefix, suffix, flags));
if (includeUnpaired && prefix != null && suffix != null) {
// The following if statements are designed to prevent adding two identical matchers.
- if (signum == 1 || !Objects.equals(prefix, posPrefix)) {
+ if (type == PatternSignType.POS || !Objects.equals(prefix, posPrefix)) {
matchers.add(getInstance(prefix, null, flags));
}
- if (signum == 1 || !Objects.equals(suffix, posSuffix)) {
+ if (type == PatternSignType.POS || !Objects.equals(suffix, posSuffix)) {
matchers.add(getInstance(null, suffix, flags));
}
}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/CompactNotation.java b/icu4j/main/classes/core/src/com/ibm/icu/number/CompactNotation.java
index a8b704e..a4dad2d 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/CompactNotation.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/CompactNotation.java
@@ -66,9 +66,10 @@
CompactType compactType,
PluralRules rules,
MutablePatternModifier buildReference,
+ boolean safe,
MicroPropsGenerator parent) {
// TODO: Add a data cache? It would be keyed by locale, nsName, compact type, and compact style.
- return new CompactHandler(this, locale, nsName, compactType, rules, buildReference, parent);
+ return new CompactHandler(this, locale, nsName, compactType, rules, buildReference, safe, parent);
}
private static class CompactHandler implements MicroPropsGenerator {
@@ -76,6 +77,7 @@
final PluralRules rules;
final MicroPropsGenerator parent;
final Map<String, ImmutablePatternModifier> precomputedMods;
+ final MutablePatternModifier unsafePatternModifier;
final CompactData data;
private CompactHandler(
@@ -85,6 +87,7 @@
CompactType compactType,
PluralRules rules,
MutablePatternModifier buildReference,
+ boolean safe,
MicroPropsGenerator parent) {
this.rules = rules;
this.parent = parent;
@@ -94,13 +97,15 @@
} else {
data.populate(notation.compactCustomData);
}
- if (buildReference != null) {
+ if (safe) {
// Safe code path
precomputedMods = new HashMap<>();
precomputeAllModifiers(buildReference);
+ unsafePatternModifier = null;
} else {
// Unsafe code path
precomputedMods = null;
+ unsafePatternModifier = buildReference;
}
}
@@ -145,13 +150,14 @@
} else {
// Unsafe code path.
// Overwrite the PatternInfo in the existing modMiddle.
- assert micros.modMiddle instanceof MutablePatternModifier;
ParsedPatternInfo patternInfo = PatternStringParser.parseToPatternInfo(patternString);
- ((MutablePatternModifier) micros.modMiddle).setPatternInfo(patternInfo, NumberFormat.Field.COMPACT);
+ unsafePatternModifier.setPatternInfo(patternInfo, NumberFormat.Field.COMPACT);
+ unsafePatternModifier.setNumberProperties(quantity.signum(), null);
+ micros.modMiddle = unsafePatternModifier;
}
// We already performed rounding. Do not perform it again.
- micros.rounder = Precision.constructPassThrough();
+ micros.rounder = null;
return micros;
}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java
index 497800e..ff0a0a0 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java
@@ -327,7 +327,7 @@
/**
* Show the minus sign on negative numbers and the plus sign on positive numbers. Do not show a
- * sign on zero or NaN, unless the sign bit is set (-0.0 gets a sign).
+ * sign on zero, numbers that round to zero, or NaN.
*
* @stable ICU 61
* @see NumberFormatter
@@ -336,9 +336,8 @@
/**
* Use the locale-dependent accounting format on negative numbers, and show the plus sign on
- * positive numbers. Do not show a sign on zero or NaN, unless the sign bit is set (-0.0 gets a
- * sign). For more information on the accounting format, see the ACCOUNTING sign display
- * strategy.
+ * positive numbers. Do not show a sign on zero, numbers that round to zero, or NaN. For more
+ * information on the accounting format, see the ACCOUNTING sign display strategy.
*
* @stable ICU 61
* @see NumberFormatter
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java
index 5b772bf..48607c0 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java
@@ -17,6 +17,7 @@
import com.ibm.icu.impl.number.MicroPropsGenerator;
import com.ibm.icu.impl.number.MultiplierFormatHandler;
import com.ibm.icu.impl.number.MutablePatternModifier;
+import com.ibm.icu.impl.number.MutablePatternModifier.ImmutablePatternModifier;
import com.ibm.icu.impl.number.Padder;
import com.ibm.icu.impl.number.PatternStringParser;
import com.ibm.icu.impl.number.PatternStringParser.ParsedPatternInfo;
@@ -97,7 +98,6 @@
*/
public MicroProps preProcess(DecimalQuantity inValue) {
MicroProps micros = microPropsGenerator.processQuantity(inValue);
- micros.rounder.apply(inValue);
if (micros.integerWidth.maxInt == -1) {
inValue.setMinInteger(micros.integerWidth.minInt);
} else {
@@ -111,7 +111,6 @@
MicroProps micros = new MicroProps(false);
MicroPropsGenerator microPropsGenerator = macrosToMicroGenerator(macros, micros, false);
micros = microPropsGenerator.processQuantity(inValue);
- micros.rounder.apply(inValue);
if (micros.integerWidth.maxInt == -1) {
inValue.setMinInteger(micros.integerWidth.minInt);
} else {
@@ -341,10 +340,9 @@
} else {
patternMod.setSymbols(micros.symbols, currency, unitWidth, null);
}
+ ImmutablePatternModifier immPatternMod = null;
if (safe) {
- chain = patternMod.createImmutableAndChain(chain);
- } else {
- chain = patternMod.addToChain(chain);
+ immPatternMod = patternMod.createImmutable();
}
// Outer modifier (CLDR units and currency long names)
@@ -367,8 +365,6 @@
}
// Compact notation
- // NOTE: Compact notation can (but might not) override the middle modifier and rounding.
- // It therefore needs to go at the end of the chain.
if (macros.notation instanceof CompactNotation) {
if (rules == null) {
// Lazily create PluralRules
@@ -381,10 +377,18 @@
micros.nsName,
compactType,
rules,
- safe ? patternMod : null,
+ patternMod,
+ safe,
chain);
}
+ // Always add the pattern modifier as the last element of the chain.
+ if (safe) {
+ chain = immPatternMod.addToChain(chain);
+ } else {
+ chain = patternMod.addToChain(chain);
+ }
+
return chain;
}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/Precision.java b/icu4j/main/classes/core/src/com/ibm/icu/number/Precision.java
index 0aa395f..d8ce8e4 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/Precision.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/Precision.java
@@ -384,8 +384,6 @@
static final CurrencyRounderImpl MONETARY_STANDARD = new CurrencyRounderImpl(CurrencyUsage.STANDARD);
static final CurrencyRounderImpl MONETARY_CASH = new CurrencyRounderImpl(CurrencyUsage.CASH);
- static final PassThroughRounderImpl PASS_THROUGH = new PassThroughRounderImpl();
-
static Precision constructInfinite() {
return NONE;
}
@@ -474,10 +472,6 @@
return returnValue.withMode(base.mathContext);
}
- static Precision constructPassThrough() {
- return PASS_THROUGH;
- }
-
/**
* Returns a valid working Rounder. If the Rounder is a CurrencyRounder, applies the given currency.
* Otherwise, simply passes through the argument.
@@ -713,17 +707,6 @@
}
}
- static class PassThroughRounderImpl extends Precision {
-
- public PassThroughRounderImpl() {
- }
-
- @Override
- public void apply(DecimalQuantity value) {
- // TODO: Assert that value has already been rounded
- }
- }
-
private static int getRoundingMagnitudeFraction(int maxFrac) {
if (maxFrac == -1) {
return Integer.MIN_VALUE;
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/ScientificNotation.java b/icu4j/main/classes/core/src/com/ibm/icu/number/ScientificNotation.java
index 10945cd..e751688 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/ScientificNotation.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/ScientificNotation.java
@@ -196,7 +196,7 @@
}
// We already performed rounding. Do not perform it again.
- micros.rounder = Precision.constructPassThrough();
+ micros.rounder = null;
return micros;
}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/ChineseDateFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/ChineseDateFormat.java
index 735fac6..a101504 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/text/ChineseDateFormat.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/text/ChineseDateFormat.java
@@ -126,12 +126,13 @@
char ch, int count, int beginOffset,
int fieldNum, DisplayContext capitalizationContext,
FieldPosition pos,
+ char patternCharToOutput,
Calendar cal) {
// Logic to handle 'G' for chinese calendar is moved into SimpleDateFormat,
// and obsolete pattern char 'l' is now ignored in SimpleDateFormat, so we
// just use its implementation
- super.subFormat(buf, ch, count, beginOffset, fieldNum, capitalizationContext, pos, cal);
+ super.subFormat(buf, ch, count, beginOffset, fieldNum, capitalizationContext, pos, patternCharToOutput, cal);
// The following is no longer an issue for this subclass...
// TODO: add code to set FieldPosition for 'G' and 'l' fields. This
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/DateTimePatternGenerator.java b/icu4j/main/classes/core/src/com/ibm/icu/text/DateTimePatternGenerator.java
index 70c136c..eb23318 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/text/DateTimePatternGenerator.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/text/DateTimePatternGenerator.java
@@ -2649,6 +2649,25 @@
if (subField > 0) subField += value.length();
type[field] = subField;
}
+
+ // #20739, we have a skeleton with milliseconde, but no seconds
+ if (!original.isFieldEmpty(FRACTIONAL_SECOND) && original.isFieldEmpty(SECOND)) {
+ // Force the use of seconds
+ for (int i = 0; i < types.length; ++i) {
+ int[] row = types[i];
+ if (row[1] == SECOND) {
+ // first entry for SECOND
+ original.populate(SECOND, (char)row[0], row[3]);
+ baseOriginal.populate(SECOND, (char)row[0], row[3]);
+ // We add value.length, same as above, when type is first initialized.
+ // The value we want to "fake" here is "s", and 1 means "s".length()
+ int subField = row[2];
+ type[SECOND] = (subField > 0) ? subField + 1 : subField;
+ break;
+ }
+ }
+ }
+
// #13183, handle special behavior for day period characters (a, b, B)
if (!original.isFieldEmpty(HOUR)) {
if (original.getFieldChar(HOUR)=='h' || original.getFieldChar(HOUR)=='K') {
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/SimpleDateFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/SimpleDateFormat.java
index 090e956..733e540 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/text/SimpleDateFormat.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/text/SimpleDateFormat.java
@@ -1395,10 +1395,10 @@
}
if (useFastFormat) {
subFormat(toAppendTo, item.type, item.length, toAppendTo.length(),
- i, capitalizationContext, pos, cal);
+ i, capitalizationContext, pos, item.type, cal);
} else {
toAppendTo.append(subFormat(item.type, item.length, toAppendTo.length(),
- i, capitalizationContext, pos, cal));
+ i, capitalizationContext, pos, item.type, cal));
}
if (attributes != null) {
// Check the sub format length
@@ -1547,7 +1547,7 @@
throws IllegalArgumentException
{
// Note: formatData is ignored
- return subFormat(ch, count, beginOffset, 0, DisplayContext.CAPITALIZATION_NONE, pos, cal);
+ return subFormat(ch, count, beginOffset, 0, DisplayContext.CAPITALIZATION_NONE, pos, ch, cal);
}
/**
@@ -1561,10 +1561,11 @@
protected String subFormat(char ch, int count, int beginOffset,
int fieldNum, DisplayContext capitalizationContext,
FieldPosition pos,
+ char patternCharToOutput,
Calendar cal)
{
StringBuffer buf = new StringBuffer();
- subFormat(buf, ch, count, beginOffset, fieldNum, capitalizationContext, pos, cal);
+ subFormat(buf, ch, count, beginOffset, fieldNum, capitalizationContext, pos, patternCharToOutput, cal);
return buf.toString();
}
@@ -1586,6 +1587,7 @@
char ch, int count, int beginOffset,
int fieldNum, DisplayContext capitalizationContext,
FieldPosition pos,
+ char patternCharToOutput,
Calendar cal) {
final int maxIntCount = Integer.MAX_VALUE;
@@ -1944,7 +1946,10 @@
if (toAppend == null) {
// Time isn't exactly midnight or noon (as displayed) or localized string doesn't
// exist for requested period. Fall back to am/pm instead.
- subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, cal);
+ // We are passing a different patternCharToOutput because we want to add
+ // 'b' to field position. This makes this fallback stable when
+ // there is a data change on locales.
+ subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, 'b', cal);
} else {
buf.append(toAppend);
}
@@ -1959,8 +1964,11 @@
if (ruleSet == null) {
// Data doesn't exist for the locale we're looking for.
// Fall back to am/pm.
- subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, cal);
- break;
+ // We are passing a different patternCharToOutput because we want to add
+ // 'B' to field position. This makes this fallback stable when
+ // there is a data change on locales.
+ subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, 'B', cal);
+ return;
}
// Get current display time.
@@ -2025,7 +2033,11 @@
if (periodType == DayPeriodRules.DayPeriod.AM ||
periodType == DayPeriodRules.DayPeriod.PM ||
toAppend == null) {
- subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, cal);
+ // We are passing a different patternCharToOutput because we want to add
+ // 'B' to field position. This makes this fallback stable when
+ // there is a data change on locales.
+ subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, 'B', cal);
+ return;
}
else {
buf.append(toAppend);
@@ -2089,12 +2101,13 @@
}
// Set the FieldPosition (for the first occurrence only)
+ int outputCharIndex = getIndexFromChar(patternCharToOutput);
if (pos.getBeginIndex() == pos.getEndIndex()) {
- if (pos.getField() == PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex]) {
+ if (pos.getField() == PATTERN_INDEX_TO_DATE_FORMAT_FIELD[outputCharIndex]) {
pos.setBeginIndex(beginOffset);
pos.setEndIndex(beginOffset + buf.length() - bufstart);
} else if (pos.getFieldAttribute() ==
- PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex]) {
+ PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[outputCharIndex]) {
pos.setBeginIndex(beginOffset);
pos.setEndIndex(beginOffset + buf.length() - bufstart);
}
@@ -4368,10 +4381,10 @@
PatternItem item = (PatternItem)items[i];
if (useFastFormat) {
subFormat(appendTo, item.type, item.length, appendTo.length(),
- i, capSetting, pos, fromCalendar);
+ i, capSetting, pos, item.type, fromCalendar);
} else {
appendTo.append(subFormat(item.type, item.length, appendTo.length(),
- i, capSetting, pos, fromCalendar));
+ i, capSetting, pos, item.type, fromCalendar));
}
}
}
@@ -4386,10 +4399,10 @@
PatternItem item = (PatternItem)items[i];
if (useFastFormat) {
subFormat(appendTo, item.type, item.length, appendTo.length(),
- i, capSetting, pos, toCalendar);
+ i, capSetting, pos, item.type, toCalendar);
} else {
appendTo.append(subFormat(item.type, item.length, appendTo.length(),
- i, capSetting, pos, toCalendar));
+ i, capSetting, pos, item.type, toCalendar));
}
}
}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/util/ULocale.java b/icu4j/main/classes/core/src/com/ibm/icu/util/ULocale.java
index a8d8f7f..06de947 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/util/ULocale.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/util/ULocale.java
@@ -3246,7 +3246,10 @@
}
List<String>subtags = tag.getVariants();
- for (String s : subtags) {
+ // ICU-20478: Sort variants per UTS35.
+ ArrayList<String> variants = new ArrayList<String>(subtags);
+ Collections.sort(variants);
+ for (String s : variants) {
buf.append(LanguageTag.SEP);
buf.append(LanguageTag.canonicalizeVariant(s));
}
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberpermutationtest.txt b/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberpermutationtest.txt
index 25f7da8..4b4656e 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberpermutationtest.txt
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberpermutationtest.txt
@@ -1,4 +1,4 @@
-# © 2017 and later: Unicode, Inc. and others.
+# © 2019 and later: Unicode, Inc. and others.
# License & terms of use: http://www.unicode.org/copyright.html
compact-short percent unit-width-narrow
@@ -2273,15 +2273,15 @@
es-MX
0
+92 k
- -0
+ 0
zh-TW
0
+9萬
- -0
+ 0
bn-BD
০
+৯২ হা
- -০
+ ০
compact-short .000 sign-accounting-except-zero
es-MX
@@ -4849,15 +4849,15 @@
es-MX
0 %
+91,827 %
- -0 %
+ 0 %
zh-TW
0%
+91,827%
- -0%
+ 0%
bn-BD
০%
+৯১,৮২৭%
- -০%
+ ০%
percent .000 sign-accounting-except-zero
es-MX
@@ -4905,15 +4905,15 @@
es-MX
EUR 0
+EUR 91,827
- -EUR 0
+ EUR 0
zh-TW
€0
+€91,827
- (€0)
+ €0
bn-BD
০€
+৯১,৮২৭€
- (০ €)
+ ০€
currency/EUR .000 sign-accounting-except-zero
es-MX
@@ -4961,15 +4961,15 @@
es-MX
0 fur
+91,827 fur
- -0 fur
+ 0 fur
zh-TW
0 化朗
+91,827 化朗
- -0 化朗
+ 0 化朗
bn-BD
০ ফার্লং
+৯১,৮২৭ ফার্লং
- -০ ফার্লং
+ ০ ফার্লং
measure-unit/length-furlong .000 sign-accounting-except-zero
es-MX
@@ -6627,15 +6627,15 @@
es-MX
0
+91,827
- -0
+ 0
zh-TW
0
+91,827
- -0
+ 0
bn-BD
০
+৯১,৮২৭
- -০
+ ০
unit-width-narrow .000 sign-accounting-except-zero
es-MX
@@ -6683,15 +6683,15 @@
es-MX
0
+91,827
- -0
+ 0
zh-TW
0
+91,827
- -0
+ 0
bn-BD
০
+৯১,৮২৭
- -০
+ ০
unit-width-full-name .000 sign-accounting-except-zero
es-MX
@@ -7943,15 +7943,15 @@
es-MX
00
+1827
- -00
+ 00
zh-TW
00
+1,827
- -00
+ 00
bn-BD
০০
+১,৮২৭
- -০০
+ ০০
.000 integer-width/##00 sign-accounting-except-zero
es-MX
@@ -8167,15 +8167,15 @@
es-MX
0
+45,914
- -0
+ 0
zh-TW
0
+45,914
- -0
+ 0
bn-BD
০
+৪৫,৯১৪
- -০
+ ০
.000 scale/0.5 sign-accounting-except-zero
es-MX
@@ -8335,15 +8335,15 @@
es-MX
0
+91,827
- -0
+ 0
zh-TW
0
+91,827
- -0
+ 0
bn-BD
০
+৯১,৮২৭
- -০
+ ০
.000 group-on-aligned sign-accounting-except-zero
es-MX
@@ -8447,15 +8447,15 @@
es-MX
0
+91,827
- -0
+ 0
zh-TW
0
+91,827
- -0
+ 0
bn-BD
0
+91,827
- -0
+ 0
.000 latin sign-accounting-except-zero
es-MX
@@ -8559,15 +8559,15 @@
es-MX
0.
+91,827.
- -0.
+ 0.
zh-TW
0.
+91,827.
- -0.
+ 0.
bn-BD
০.
+৯১,৮২৭.
- -০.
+ ০.
.000 sign-accounting-except-zero decimal-always
es-MX
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_SimpleStorage.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_SimpleStorage.java
index d8aae25..cb1bc57 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_SimpleStorage.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_SimpleStorage.java
@@ -9,6 +9,7 @@
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.impl.number.DecimalQuantity;
+import com.ibm.icu.impl.number.Modifier.Signum;
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.text.PluralRules.Operand;
import com.ibm.icu.text.UFieldPosition;
@@ -517,8 +518,18 @@
}
@Override
- public int signum() {
- return isNegative() ? -1 : isZeroish() ? 0 : 1;
+ public Signum signum() {
+ boolean isZero = (isZeroish() && !isInfinite());
+ boolean isNeg = isNegative();
+ if (isZero && isNeg) {
+ return Signum.NEG_ZERO;
+ } else if (isZero) {
+ return Signum.POS_ZERO;
+ } else if (isNeg) {
+ return Signum.NEG;
+ } else {
+ return Signum.POS;
+ }
}
private void setNegative(boolean isNegative) {
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatTest.java
index f2e3849..42b9ed3 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatTest.java
@@ -49,6 +49,7 @@
import com.ibm.icu.text.DateFormat;
import com.ibm.icu.text.DateFormat.BooleanAttribute;
import com.ibm.icu.text.DateFormatSymbols;
+import com.ibm.icu.text.DateTimePatternGenerator;
import com.ibm.icu.text.DisplayContext;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.text.SimpleDateFormat;
@@ -5431,4 +5432,67 @@
dfmt.parse(inDate, pos);
assertEquals("Error index", inDate.length(), pos.getErrorIndex());
}
+
+ @Test
+ public void test20739_MillisecondsWithoutSeconds() {
+ String[][] cases = new String[][]{
+ {"SSSSm", "mm:ss.SSSS"},
+ {"mSSSS", "mm:ss.SSSS"},
+ {"SSSm", "mm:ss.SSS"},
+ {"mSSS", "mm:ss.SSS"},
+ {"SSm", "mm:ss.SS"},
+ {"mSS", "mm:ss.SS"},
+ {"Sm", "mm:ss.S"},
+ {"mS", "mm:ss.S"},
+ {"S", "S"},
+ {"SS", "SS"},
+ {"SSS", "SSS"},
+ {"SSSS", "SSSS"},
+ {"jmsSSS", "h:mm:ss.SSS a"},
+ {"jmSSS", "h:mm:ss.SSS a"}
+ };
+
+ ULocale locale = ULocale.ENGLISH;
+ for (String[] cas : cases) {
+ DateFormat fmt = DateFormat.getInstanceForSkeleton( cas[0], locale);
+ String pattern = ((SimpleDateFormat) fmt).toPattern();
+ assertEquals("Format pattern", cas[1], pattern);
+ }
+ }
+
+ @Test
+ public void test20741_ABFields() {
+ String [] skeletons = {"EEEEEBBBBB", "EEEEEbbbbb"};
+ ULocale[] locales = ULocale.getAvailableLocales();
+ for (String skeleton : skeletons) {
+ for (int i = 0; i < locales.length; i++) {
+ ULocale locale = locales[i];
+ if (isQuick() && (i % 17 != 0)) continue;
+
+ DateTimePatternGenerator gen = DateTimePatternGenerator.getInstance(locale);
+ String pattern = gen.getBestPattern(skeleton);
+ SimpleDateFormat dateFormat = new SimpleDateFormat(pattern, locale);
+ Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("PST8PDT"));
+ calendar.setTime(new Date(0));
+
+ FieldPosition pos_c = new FieldPosition(DateFormat.Field.DAY_OF_WEEK);
+ dateFormat.format(calendar, new StringBuffer(""), pos_c);
+ assertFalse("'Day of week' field was not found", pos_c.getBeginIndex() == 0 && pos_c.getEndIndex() == 0);
+
+ if (skeleton.equals("EEEEEBBBBB")) {
+ FieldPosition pos_B = new FieldPosition(DateFormat.Field.FLEXIBLE_DAY_PERIOD);
+ dateFormat.format(calendar, new StringBuffer(""), pos_B);
+ assertFalse("'Flexible day period' field was not found", pos_B.getBeginIndex() == 0 && pos_B.getEndIndex() == 0);
+ } else {
+ FieldPosition pos_b = new FieldPosition(DateFormat.Field.AM_PM_MIDNIGHT_NOON);
+ dateFormat.format(calendar, new StringBuffer(""), pos_b);
+ assertFalse("'AM/PM/Midnight/Noon' field was not found", pos_b.getBeginIndex() == 0 && pos_b.getEndIndex() == 0);
+ }
+
+ FieldPosition pos_a = new FieldPosition(DateFormat.Field.AM_PM);
+ dateFormat.format(calendar, new StringBuffer(""), pos_a);
+ assertTrue("'AM/PM' field was found", pos_a.getBeginIndex() == 0 && pos_a.getEndIndex() == 0);
+ }
+ }
+ }
}
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/MutablePatternModifierTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/MutablePatternModifierTest.java
index 9f8585b..180919c 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/MutablePatternModifierTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/MutablePatternModifierTest.java
@@ -12,6 +12,7 @@
import com.ibm.icu.impl.number.DecimalQuantity;
import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
import com.ibm.icu.impl.number.MicroProps;
+import com.ibm.icu.impl.number.Modifier.Signum;
import com.ibm.icu.impl.number.MutablePatternModifier;
import com.ibm.icu.impl.number.PatternStringParser;
import com.ibm.icu.number.NumberFormatter.SignDisplay;
@@ -32,19 +33,22 @@
UnitWidth.SHORT,
null);
- mod.setNumberProperties(1, null);
+ mod.setNumberProperties(Signum.POS, null);
assertEquals("a", getPrefix(mod));
assertEquals("b", getSuffix(mod));
mod.setPatternAttributes(SignDisplay.ALWAYS, false);
assertEquals("+a", getPrefix(mod));
assertEquals("b", getSuffix(mod));
- mod.setNumberProperties(0, null);
+ mod.setNumberProperties(Signum.POS_ZERO, null);
assertEquals("+a", getPrefix(mod));
assertEquals("b", getSuffix(mod));
+ mod.setNumberProperties(Signum.NEG_ZERO, null);
+ assertEquals("-a", getPrefix(mod));
+ assertEquals("b", getSuffix(mod));
mod.setPatternAttributes(SignDisplay.EXCEPT_ZERO, false);
assertEquals("a", getPrefix(mod));
assertEquals("b", getSuffix(mod));
- mod.setNumberProperties(-1, null);
+ mod.setNumberProperties(Signum.NEG, null);
assertEquals("-a", getPrefix(mod));
assertEquals("b", getSuffix(mod));
mod.setPatternAttributes(SignDisplay.NEVER, false);
@@ -53,24 +57,27 @@
mod.setPatternInfo(PatternStringParser.parseToPatternInfo("a0b;c-0d"), null);
mod.setPatternAttributes(SignDisplay.AUTO, false);
- mod.setNumberProperties(1, null);
+ mod.setNumberProperties(Signum.POS, null);
assertEquals("a", getPrefix(mod));
assertEquals("b", getSuffix(mod));
mod.setPatternAttributes(SignDisplay.ALWAYS, false);
assertEquals("c+", getPrefix(mod));
assertEquals("d", getSuffix(mod));
- mod.setNumberProperties(0, null);
+ mod.setNumberProperties(Signum.POS_ZERO, null);
assertEquals("c+", getPrefix(mod));
assertEquals("d", getSuffix(mod));
+ mod.setNumberProperties(Signum.NEG_ZERO, null);
+ assertEquals("c-", getPrefix(mod));
+ assertEquals("d", getSuffix(mod));
mod.setPatternAttributes(SignDisplay.EXCEPT_ZERO, false);
assertEquals("a", getPrefix(mod));
assertEquals("b", getSuffix(mod));
- mod.setNumberProperties(-1, null);
+ mod.setNumberProperties(Signum.NEG, null);
assertEquals("c-", getPrefix(mod));
assertEquals("d", getSuffix(mod));
mod.setPatternAttributes(SignDisplay.NEVER, false);
- assertEquals("c-", getPrefix(mod)); // TODO: What should this behavior be?
- assertEquals("d", getSuffix(mod));
+ assertEquals("a", getPrefix(mod));
+ assertEquals("b", getSuffix(mod));
}
@Test
@@ -112,7 +119,7 @@
Currency.getInstance("USD"),
UnitWidth.SHORT,
null);
- mod.setNumberProperties(1, null);
+ mod.setNumberProperties(Signum.POS_ZERO, null);
// Unsafe Code Path
FormattedStringBuilder nsb = new FormattedStringBuilder();
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
index e5a872c..a12468f 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
@@ -1625,6 +1625,57 @@
"00.008765",
"00");
+ assertFormatDescending(
+ "Integer Width Compact",
+ "compact-short integer-width/000",
+ NumberFormatter.with()
+ .notation(Notation.compactShort())
+ .integerWidth(IntegerWidth.zeroFillTo(3).truncateAt(3)),
+ ULocale.ENGLISH,
+ "088K",
+ "008.8K",
+ "876",
+ "088",
+ "008.8",
+ "000.88",
+ "000.088",
+ "000.0088",
+ "000");
+
+ assertFormatDescending(
+ "Integer Width Scientific",
+ "scientific integer-width/000",
+ NumberFormatter.with()
+ .notation(Notation.scientific())
+ .integerWidth(IntegerWidth.zeroFillTo(3).truncateAt(3)),
+ ULocale.ENGLISH,
+ "008.765E4",
+ "008.765E3",
+ "008.765E2",
+ "008.765E1",
+ "008.765E0",
+ "008.765E-1",
+ "008.765E-2",
+ "008.765E-3",
+ "000E0");
+
+ assertFormatDescending(
+ "Integer Width Engineering",
+ "engineering integer-width/000",
+ NumberFormatter.with()
+ .notation(Notation.engineering())
+ .integerWidth(IntegerWidth.zeroFillTo(3).truncateAt(3)),
+ ULocale.ENGLISH,
+ "087.65E3",
+ "008.765E3",
+ "876.5E0",
+ "087.65E0",
+ "008.765E0",
+ "876.5E-3",
+ "087.65E-3",
+ "008.765E-3",
+ "000E0");
+
assertFormatSingle(
"Integer Width Remove All A",
"integer-width/00",
@@ -2030,13 +2081,52 @@
}
@Test
+ public void signNearZero() {
+ // https://unicode-org.atlassian.net/browse/ICU-20709
+ Object[][] cases = {
+ { SignDisplay.AUTO, 1.1, "1" },
+ { SignDisplay.AUTO, 0.9, "1" },
+ { SignDisplay.AUTO, 0.1, "0" },
+ { SignDisplay.AUTO, -0.1, "-0" }, // interesting case
+ { SignDisplay.AUTO, -0.9, "-1" },
+ { SignDisplay.AUTO, -1.1, "-1" },
+ { SignDisplay.ALWAYS, 1.1, "+1" },
+ { SignDisplay.ALWAYS, 0.9, "+1" },
+ { SignDisplay.ALWAYS, 0.1, "+0" },
+ { SignDisplay.ALWAYS, -0.1, "-0" },
+ { SignDisplay.ALWAYS, -0.9, "-1" },
+ { SignDisplay.ALWAYS, -1.1, "-1" },
+ { SignDisplay.EXCEPT_ZERO, 1.1, "+1" },
+ { SignDisplay.EXCEPT_ZERO, 0.9, "+1" },
+ { SignDisplay.EXCEPT_ZERO, 0.1, "0" }, // interesting case
+ { SignDisplay.EXCEPT_ZERO, -0.1, "0" }, // interesting case
+ { SignDisplay.EXCEPT_ZERO, -0.9, "-1" },
+ { SignDisplay.EXCEPT_ZERO, -1.1, "-1" },
+ };
+ for (Object[] cas : cases) {
+ SignDisplay sign = (SignDisplay) cas[0];
+ double input = (Double) cas[1];
+ String expected = (String) cas[2];
+ String actual = NumberFormatter.with()
+ .sign(sign)
+ .precision(Precision.integer())
+ .locale(Locale.US)
+ .format(input)
+ .toString();
+ assertEquals(
+ input + " @ SignDisplay " + sign,
+ expected, actual);
+ }
+ }
+
+ @Test
public void signCoverage() {
// https://unicode-org.atlassian.net/browse/ICU-20708
Object[][][] cases = new Object[][][] {
{ {SignDisplay.AUTO}, { "-∞", "-1", "-0", "0", "1", "∞", "NaN", "-NaN" } },
{ {SignDisplay.ALWAYS}, { "-∞", "-1", "-0", "+0", "+1", "+∞", "+NaN", "-NaN" } },
{ {SignDisplay.NEVER}, { "∞", "1", "0", "0", "1", "∞", "NaN", "NaN" } },
- { {SignDisplay.EXCEPT_ZERO}, { "-∞", "-1", "-0", "0", "+1", "+∞", "NaN", "-NaN" } },
+ { {SignDisplay.EXCEPT_ZERO}, { "-∞", "-1", "0", "0", "+1", "+∞", "NaN", "NaN" } },
};
double negNaN = Math.copySign(Double.NaN, -0.0);
double inputs[] = new double[] {
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberPermutationTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberPermutationTest.java
index 3ce326f..d7056b9 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberPermutationTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberPermutationTest.java
@@ -96,7 +96,7 @@
// Build up the golden data string as we evaluate all permutations
ArrayList<String> resultLines = new ArrayList<>();
- resultLines.add("# © 2017 and later: Unicode, Inc. and others.");
+ resultLines.add("# © 2019 and later: Unicode, Inc. and others.");
resultLines.add("# License & terms of use: http://www.unicode.org/copyright.html");
resultLines.add("");
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/ULocaleTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/ULocaleTest.java
index fc2dc4f..4c4ba97 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/ULocaleTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/ULocaleTest.java
@@ -4106,8 +4106,8 @@
{"aa_BB_CYRL", "aa-BB-x-lvariant-cyrl"},
{"en_US_1234", "en-US-1234"},
{"en_US_VARIANTA_VARIANTB", "en-US-varianta-variantb"},
- {"en_US_VARIANTB_VARIANTA", "en-US-variantb-varianta"},
- {"ja__9876_5432", "ja-9876-5432"},
+ {"en_US_VARIANTB_VARIANTA", "en-US-varianta-variantb"}, /* ICU-20478 */
+ {"ja__9876_5432", "ja-5432-9876"}, /* ICU-20478 */
{"zh_Hant__VAR", "zh-Hant-x-lvariant-var"},
{"es__BADVARIANT_GOODVAR", "es"},
{"es__GOODVAR_BAD_BADVARIANT", "es-goodvar-x-lvariant-bad"},
@@ -4131,6 +4131,9 @@
{"en@a=bar;attribute=baz;calendar=islamic-civil;x=u-foo", "en-a-bar-u-baz-ca-islamic-civil-x-u-foo"},
/* ICU-20320*/
{"en@9=efg;a=baz", "en-9-efg-a-baz"},
+ /* ICU-20478 */
+ {"sl__ROZAJ_BISKE_1994", "sl-1994-biske-rozaj"},
+ {"en__SCOUSE_FONIPA", "en-fonipa-scouse"},
};
for (int i = 0; i < locale_to_langtag.length; i++) {
@@ -4228,7 +4231,7 @@
{"bogus", "bogus", NOERROR},
{"boguslang", "", Integer.valueOf(0)},
{"EN-lATN-us", "en_Latn_US", NOERROR},
- {"und-variant-1234", "__VARIANT_1234", NOERROR},
+ {"und-variant-1234", "__1234_VARIANT", NOERROR}, /* ICU-20478 */
{"und-varzero-var1-vartwo", "__VARZERO", Integer.valueOf(12)},
{"en-u-ca-gregory", "en@calendar=gregorian", NOERROR},
{"en-U-cu-USD", "en@currency=usd", NOERROR},
@@ -4274,6 +4277,16 @@
/* #20410 */
{"art-lojban-x-0", "jbo@x=0", NOERROR},
{"zh-xiang-u-nu-thai-x-0", "hsn@numbers=thai;x=0", NOERROR},
+ /* ICU-20478 */
+ {"ja-9876-5432", "ja__5432_9876", NOERROR},
+ {"en-US-variantb-varianta", "en_US_VARIANTA_VARIANTB", NOERROR},
+ {"en-US-varianta-variantb", "en_US_VARIANTA_VARIANTB", NOERROR},
+ {"sl-rozaj-biske-1994", "sl__1994_BISKE_ROZAJ", NOERROR},
+ {"sl-biske-rozaj-1994", "sl__1994_BISKE_ROZAJ", NOERROR},
+ {"sl-biske-1994-rozaj", "sl__1994_BISKE_ROZAJ", NOERROR},
+ {"sl-1994-biske-rozaj", "sl__1994_BISKE_ROZAJ", NOERROR},
+ {"en-fonipa-scouse", "en__FONIPA_SCOUSE", NOERROR},
+ {"en-scouse-fonipa", "en__FONIPA_SCOUSE", NOERROR},
};
for (int i = 0; i < langtag_to_locale.length; i++) {