Unitful: parsing currencies

I have a table with cash operations. String representation of an operation contains a value and a currency name, e.g. 1.5 US$ or 1.5 RUB. I’ve decided to use Unitful.jl: Defining new units for keeping different currencies.

I want to write a function parsecash(s) -> Unitful.Quantity which will take care about currencies names as they stored in table and my “Unitful” currencies.

How can I parse different strings which represents same currencies? So,

  • "1 RUB" and "1 ₽" must be parsed as Unitful.Quantity(1, RUB);
  • "1 USD" and "1 US$" must be parsed as Unitful.Quantity(1, USD).

My code seems to parse "1.5 USD" and "1.5 RUB" correctly, but it can’t parse their aliases "1.5 US$" and "1.5 ₽".

Here is what I came with

using Unitful

# This is how UnitfulAssets.jl https://github.com/rmsrosa/UnitfulAssets.jl defines currencies

@dimension 𝐑𝐔𝐁 "𝐑𝐔𝐁" RussianRoubleCurrency
@refunit RUB "RUB" RussianRoubleRefUnit 𝐑𝐔𝐁 false false
@unit RussianRouble "₽" RussianRoubleSign 1RUB false false

@dimension 𝐔𝐒𝐃 "𝐔𝐒𝐃" USDollarCurrency
@refunit USD "USD" USDollarRefUnit 𝐔𝐒𝐃 false false
@unit USDollar "US\$" USDollarSign 1USD false false

# Seems unneeded (?)
# const localpromotion = Unitful.promotion
# function __init__()
#     Unitful.register(@__MODULE__)
#     merge!(Unitful.promotion, localpromotion)
# end
# __init__()

function parsecash(s)
    qstr, abbr = rsplit(s; limit=2)
    q = parse(Float64, qstr)
    return Quantity(q, uparse(abbr; unit_context=@__MODULE__))
end

@show parsecash("1.5 RUB")  # OK
@show parsecash("1.5 USD")  # OK

@show parsecash("1.5 RUB") + parsecash("1.5 RUB")  # OK

@show parsecash("1.5 ₽")
#=
ERROR: LoadError: ArgumentError: Symbol ₽ could not be found in unit modules Module[Main]
Stacktrace:
 [1] lookup_units(unitmods::Vector{Module}, sym::Symbol)
   @ Unitful ~/.julia/packages/Unitful/SUQzL/src/user.jl:707
 [2] lookup_units
   @ ~/.julia/packages/Unitful/SUQzL/src/user.jl:719 [inlined]
 [3] uparse(str::SubString{String}; unit_context::Module)
   @ Unitful ~/.julia/packages/Unitful/SUQzL/src/user.jl:662
 [4] parsecash(s::String)
   @ Main ~/Temp/discourse.jl:21
 [5] top-level scope
   @ show.jl:955
=#


@show parsecash("1.5 US\$")
#=
ERROR: LoadError: ArgumentError: Expr head incomplete must equal :call or :tuple
Stacktrace:
 [1] lookup_units(unitmods::Module, ex::Expr)
   @ Unitful ~/.julia/packages/Unitful/SUQzL/src/user.jl:688
 [2] uparse(str::SubString{String}; unit_context::Module)
   @ Unitful ~/.julia/packages/Unitful/SUQzL/src/user.jl:662
 [3] parsecash(s::String)
   @ Main ~/Temp/discourse.jl:21
 [4] top-level scope
   @ show.jl:955
=#

Hey, I saw this now when googling something related to Unitful. I am not sure you are still into this, but let me try to see if I can help.

The thing is that you need to use ₽ itself to define the unit:

@unit ₽ "₽" RussianRoubleSign 1RUB false false

The term within quotes is just to say how it will be displayed. It is the first term that defines how it will be used for parsing.

I can add that symbol in UnitfulAssets.jl if it is of any help.

1 Like

Each currency should not be its own dimension, they should be different units of the same dimension (e.g. UnitfulAssets.jl calls it cash). So you should create

  1. A cash or currency dimension
  2. A reference unit
  3. any number of additional units

Having them all be the same dimension allows you to convert between them easily.

Also, why not just use UnitfulAssets.jl? You can add new currencies in a single line as described by the comment above (either as a pull request to that repo or in your code).

This defines two separate units, just with the same value. I wouldn’t recommend that. Instead, you can create as an alias for RUB as follows:

@refunit RUB "RUB" RussianRoubleRefUnit 𝐑𝐔𝐁 false false
const ₽ = RUB

This is used in the Unitful package itself, for example to make the angstrom unit also accessible as Å.

Yeah, I agree, that is better. Thanks.

Hmm, actually, one of the main points of that was to have a different display for the currency. If I use an alias for RUB instead of a different unit, as currently implemented for USD, then I get it still displayed as RUB:

julia> 1u"USD"
1 USD

julia> 1u"USdollar"
1 US$

julia> 1u"RUB"
1 RUB

julia> 1u"₽"
1 RUB

The problem with that is that the exchange rate of US$ is not attached to that of USD, they are treated as different units/currencies. But the idea was to have that in the case someone is not so much interested in exchange rates but rather on working on a specific currency, with a more common notation.