1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
|
{-# LANGUAGE EmptyDataDecls, RankNTypes, BangPatterns, ForeignFunctionInterface, RecordWildCards #-}
-- |
-- Module : Data.Text.ICU.NumberFormatter
-- Copyright : (c) 2021 Torsten Kemps-Benedix
--
-- License : BSD-style
-- Maintainer : bos@serpentine.com
-- Stability : experimental
-- Portability : GHC
--
-- Number formatter implemented as bindings to
-- the International Components for Unicode (ICU) libraries.
module Data.Text.ICU.NumberFormatter
(
-- * Data
NumberFormatter,
-- * Formatter
numberFormatter,
-- $skeleton
-- * Formatting functions
formatIntegral, formatIntegral', formatDouble, formatDouble'
) where
#include <unicode/unumberformatter.h>
import Data.Int (Int32, Int64)
import Data.Text (Text)
import Data.Text.ICU.Error.Internal (UErrorCode, handleError, handleOverflowError)
import Data.Text.ICU.Internal (LocaleName(..), UChar, withLocaleName, newICUPtr, fromUCharPtr, useAsUCharPtr)
import Foreign.C.String (CString)
import Foreign.C.Types (CDouble(..))
import Foreign.ForeignPtr (withForeignPtr, ForeignPtr)
import Foreign.Ptr (FunPtr, Ptr)
import Prelude hiding (last)
import System.IO.Unsafe (unsafePerformIO)
-- $skeleton
--
-- Here are some examples for number skeletons, see
-- https://unicode-org.github.io/icu/userguide/format_parse/numbers/skeletons.html#examples for more:
--
-- +----------------------------+-----------------+--------+--------------+-------------------------------------------------------------+
-- | Long Skeleton | Concise Skeleton | Input | en-US Output | Comments |
-- +============================+==================+=======+==============+=============================================================+
-- | percent | % | 25 | 25% | |
-- | .00 |.00 | 25 | 25.00 | Equivalent to Precision::fixedFraction(2) |
-- | percent .00 | % .00 | 25 | 25.00% | |
-- | scale/100 | scale/100 | 0.3 | 30 | Multiply by 100 before formatting |
-- | percent scale/100 | %x100 | 0.3 | 30% | |
-- | measure-unit/length-meter | unit/meter | 5 | 5 m | UnitWidth defaults to Short |
-- | unit-width-full-name | unit/meter | 5 | 5 meters | |
-- | compact-short | K | 5000 | 5K | |
-- | compact-long | KK | 5000 | 5 thousand | |
-- | group-min2 | ,? | 5000 | 5000 | Require 2 digits in group for separator |
-- | group-min2 | ,? | 15000 | 15,000 | |
-- | sign-always | +! | 60 | +60 | Show sign on all numbers |
-- | sign-always | +! | 0 | +0 | |
-- | sign-except-zero | +? | 60 | +60 | Show sign on all numbers except 0 |
-- | sign-except-zero | +? | 0 | 0 | |
-- +----------------------------+-----------------+--------+--------------+-------------------------------------------------------------+
data UNumberFormatter
data UFormattedNumber
newtype NumberFormatter = NumberFormatter (ForeignPtr UNumberFormatter)
-- | Create a new 'NumberFormatter'.
--
-- See https://unicode-org.github.io/icu/userguide/format_parse/numbers/skeletons.html for how to specify
-- the number skeletons. And use 'availableLocales' in order to find the allowed locale names. These
-- usuallly look like "en", "de", "de_AT" etc. See 'formatIntegral' and 'formatDouble' for some examples.
numberFormatter :: Text -> LocaleName -> IO NumberFormatter
numberFormatter skel loc =
withLocaleName loc $ \locale ->
useAsUCharPtr skel $ \skelPtr skelLen ->
newICUPtr NumberFormatter unumf_close $
handleError $ unumf_openForSkeletonAndLocale skelPtr (fromIntegral skelLen) locale
-- | Format an integral number.
--
-- See https://unicode-org.github.io/icu/userguide/format_parse/numbers/skeletons.html for how to specify
-- the number skeletons.
--
-- >>> import Data.Text
-- >>> nf <- numberFormatter (pack "precision-integer") (Locale "de")
-- >>> formatIntegral nf 12345
-- "12.345"
-- >>> nf2 <- numberFormatter (pack "precision-integer") (Locale "fr")
-- >>> formatIntegral nf2 12345
-- "12\8239\&345"
formatIntegral :: Integral a => NumberFormatter -> a -> Text
formatIntegral (NumberFormatter nf) x = unsafePerformIO $ do
withForeignPtr nf $ \nfPtr -> do
resultPtr <- newResult
withForeignPtr resultPtr $ \resPtr -> do
handleError $ unumf_formatInt nfPtr (fromIntegral x) resPtr
t <- handleOverflowError (fromIntegral (64 :: Int))
(\dptr dlen -> unumf_resultToString resPtr dptr dlen)
(\dptr dlen -> fromUCharPtr dptr (fromIntegral dlen))
pure t
-- | Create a number formatter and apply it to an integral number.
formatIntegral' :: (Integral a) => Text -> LocaleName -> a -> Text
formatIntegral' skel loc x = unsafePerformIO $ do
nf <- numberFormatter skel loc
pure $ formatIntegral nf x
-- | Format a Double.
--
-- See https://unicode-org.github.io/icu/userguide/format_parse/numbers/skeletons.html for how to specify
-- the number skeletons.
--
-- >>> import Data.Text
-- >>> nf3 <- numberFormatter (pack "precision-currency-cash") (Locale "it")
-- >>> formatDouble nf3 12345.6789
-- "12.345,68"
formatDouble :: NumberFormatter -> Double -> Text
formatDouble (NumberFormatter nf) x = unsafePerformIO $ do
withForeignPtr nf $ \nfPtr -> do
resultPtr <- newResult
withForeignPtr resultPtr $ \resPtr -> do
handleError $ unumf_formatDouble nfPtr (CDouble x) resPtr
t <- handleOverflowError (fromIntegral (64 :: Int))
(\dptr dlen -> unumf_resultToString resPtr dptr dlen)
(\dptr dlen -> fromUCharPtr dptr (fromIntegral dlen))
pure t
-- | Create a number formatter and apply it to a Double.
formatDouble' :: Text -> LocaleName -> Double -> Text
formatDouble' skel loc x = unsafePerformIO $ do
nf <- numberFormatter skel loc
pure $ formatDouble nf x
newResult :: IO (ForeignPtr UFormattedNumber)
newResult = newICUPtr id unumf_closeResult $ handleError unumf_openResult
foreign import ccall unsafe "hs_text_icu.h __hs_unumf_openForSkeletonAndLocale" unumf_openForSkeletonAndLocale
:: Ptr UChar -> Int32 -> CString -> Ptr UErrorCode -> IO (Ptr UNumberFormatter)
foreign import ccall unsafe "hs_text_icu.h &__hs_unumf_close" unumf_close
:: FunPtr (Ptr UNumberFormatter -> IO ())
foreign import ccall unsafe "hs_text_icu.h __hs_unumf_openResult" unumf_openResult
:: Ptr UErrorCode -> IO (Ptr UFormattedNumber)
foreign import ccall unsafe "hs_text_icu.h &__hs_unumf_closeResult" unumf_closeResult
:: FunPtr (Ptr UFormattedNumber -> IO ())
foreign import ccall unsafe "hs_text_icu.h __hs_unumf_formatInt" unumf_formatInt
:: Ptr UNumberFormatter -> Int64 -> Ptr UFormattedNumber -> Ptr UErrorCode -> IO ()
foreign import ccall unsafe "hs_text_icu.h __hs_unumf_formatDouble" unumf_formatDouble
:: Ptr UNumberFormatter -> CDouble -> Ptr UFormattedNumber -> Ptr UErrorCode -> IO ()
foreign import ccall unsafe "hs_text_icu.h __hs_unumf_resultToString" unumf_resultToString
:: Ptr UFormattedNumber -> Ptr UChar -> Int32 -> Ptr UErrorCode -> IO Int32
|