finalized formatters
authorkonrad <konrad@6e3c4bff-ac9f-4ac1-96c5-d2ea494d3e33>
Sun, 20 Jun 2010 13:30:54 +0000 (13:30 +0000)
committerkonrad <konrad@6e3c4bff-ac9f-4ac1-96c5-d2ea494d3e33>
Sun, 20 Jun 2010 13:30:54 +0000 (13:30 +0000)
git-svn-id: https://silmor.de/svn/softmagic/smoke/trunk@508 6e3c4bff-ac9f-4ac1-96c5-d2ea494d3e33

src/misc/misc.cpp
src/misc/misc.h

index b175de1..f3a3077 100644 (file)
@@ -12,6 +12,8 @@
 
 #include "misc.h"
 
+#include <math.h>
+
 #include <QCoreApplication>
 #include <QDateTime>
 
@@ -46,70 +48,46 @@ QString xmlize(QString str,QString newline)
        }
        return out;
 }
-QString cent2str(int c,bool localize)
+QString cent2str(qint64 c,bool localize)
 {
-       QString ret;
-       if(localize)ret=QCoreApplication::translate("misc","%1.%2","price with decimal dot");
-       else ret="%1.%2";
-       return ret.arg(c/100).arg(c%100,2,10,QChar('0'));
+       MLocalFormat mf;
+       if(!localize)mf.setNonLocalized();
+       return mf.formatMoney(c);
 }
 
-int str2cent(QString s,bool localize)
+qint64 str2cent(QString s,bool localize)
 {
-       //convert local decimal dot by international decimal dot
-       if(localize)s=s.replace(QCoreApplication::translate("misc",".","decimal dot in price"),".");
-       //calculate price
-       //pp->price part euro/dollar/...; pc->price part cent; pm=multiplier for cent; md->mode
-       int pp=0,pc=0,pm=10,md=0;
-       for(int i=0;i<s.size();i++){
-               QChar c=s[i];
-               if(c.isDigit()){
-                       if(md==0){//mode==0 -> before the dot
-                               pp*=10;
-                               pp+=c.digitValue();
-                       }else{//mode==1 -> after the dot
-                               pc+=c.digitValue()*pm;
-                               if(pm==1)break;
-                               pm=1;
-                       }
-               }else
-               if(c=='.')md=1;//reached the dot, switch mode
-               //ignore everything else
-       }
-       //return result
-       return pp*100+pc;
+       MLocalFormat mf;
+       if(!localize)mf.setNonLocalized();
+       return mf.scanMoney(s);
 }
 
 QRegExp priceRegExp(bool localize)
 {
-       if(localize)
-               return QRegExp(QCoreApplication::translate("misc","[0-9]+\\.[0-9]{2}","regexp for price"));
-       else
-               return QRegExp("[0-9]+\\.[0-9]{2}");
+       MLocalFormat mf;
+       if(!localize)mf.setNonLocalized();
+       return mf.moneyRegExp(false,false);
 }
 
-QString unix2date(int tm,bool localize)
+QString unix2date(qint64 tm,bool localize)
 {
-       QString format;
-       if(localize)format=QCoreApplication::translate("misc","yyyy-MM-dd","localized date format");
-       else format="yyyy-MM-dd";
-       return QDateTime::fromTime_t(tm).toString(format);
+       MLocalFormat mf;
+       if(!localize)mf.setNonLocalized();
+       return mf.formatDate(tm);
 }
 
-QString unix2time(int tm,bool localize)
+QString unix2time(qint64 tm,bool localize)
 {
-       QString format;
-       if(localize)format=QCoreApplication::translate("misc","hh:mm","localized time format");
-       else format="hh:mm";
-       return QDateTime::fromTime_t(tm).toString(format);
+       MLocalFormat mf;
+       if(!localize)mf.setNonLocalized();
+       return mf.formatTime(tm);
 }
 
-QString unix2dateTime(int tm,bool localize)
+QString unix2dateTime(qint64 tm,bool localize)
 {
-       QString format;
-       if(localize)format=QCoreApplication::translate("misc","yyyy-MM-dd hh:mm","localized date + time format");
-       else format="yyyy-MM-dd hh:mm";
-       return QDateTime::fromTime_t(tm).toString(format);
+       MLocalFormat mf;
+       if(!localize)mf.setNonLocalized();
+       return mf.formatDateTime(tm);
 }
 
 
@@ -142,6 +120,7 @@ MLocalFormat& MLocalFormat::operator=(const MLocalFormat&f)
        m_thousanddigits=f.m_thousanddigits;
        m_am=f.m_am;
        m_pm=f.m_pm;
+       m_moneyneg=f.m_moneyneg;
        return *this;
 }
 
@@ -217,17 +196,19 @@ void MLocalFormat::setShortMonths(const QStringList&m)
        if(m.size()==12)
                m_smonth=m;
 }
-void MLocalFormat::setMoneyFormat(QString c,int  n)
+void MLocalFormat::setMoneyFormat(QString c,int  n,QString g)
 {
        m_currency=c;
        m_moneydecimals=n;
+       if(g.size()>0)m_moneyneg=g.left(2);
+       else m_moneyneg=QCoreApplication::translate("MLocalFormat","-","negative sign for money values, the first char is put in front, the optional second one behind the number, use a newline to omit the first char");
 }
 
 void MLocalFormat::setAP(QString am,QString pm)
 {
-       if(am=="--")m_am=CoreApplication::translate("MLocalFormat","am","AM/PM time component");
+       if(am=="--")m_am=QCoreApplication::translate("MLocalFormat","am","AM/PM time component");
        else m_am=am;
-       if(pm=="--")m_pm=CoreApplication::translate("MLocalFormat","pm","AM/PM time component");
+       if(pm=="--")m_pm=QCoreApplication::translate("MLocalFormat","pm","AM/PM time component");
        else m_pm=pm;
 }
 
@@ -236,12 +217,23 @@ void MLocalFormat::setNumberFormat(QChar dec,QChar thou,int dig)
        if(dec.isNull())m_decimal=QCoreApplication::translate("MLocalFormat",".","decimal dot")[0];
        else m_decimal=dec;
        
-       if(thou.isNull())m_thousand=QCoreApplication::translate("MLocalFormat",",","thousand division")[0];
+       if(thou.isNull())m_thousand=QCoreApplication::translate("MLocalFormat",",","thousand division character")[0];
        else m_thousand=thou;
        
-       if(dig<0)m_thousanddigits=QCoreApplication::translate("MLocalFormat","0","digits between thousand division chars").toInt();
+       if(dig<0)m_thousanddigits=QCoreApplication::translate("MLocalFormat","0","digits between thousand division chars, <=0 means none").toInt();
        else m_thousanddigits=dig;
 }
+
+void MLocalFormat::setNonLocalized()
+{
+       //reset number format
+       m_decimal='.';m_thousanddigits=0;
+       //reset money signs
+       m_moneyneg="-";
+       m_currency="";
+}
+
+
 QString MLocalFormat::formatDate(const QDate&date,QString format)const
 {
        if(format=="")format=QCoreApplication::translate("MLocalFormat","%Y-%M-%D","date format");
@@ -262,7 +254,7 @@ QString MLocalFormat::formatTime(qint64 time,QString format)const
 }
 QString MLocalFormat::formatDateTime(const QDateTime&time,QString format)const
 {
-       if(format=="")format=QCoreApplication::translate("MLocalFormat","%Y-%M-%D %H:%I","date and time format");
+       if(format=="")format=QCoreApplication::translate("MLocalFormat","%Y-%M-%D %h:%I","date and time format");
        //parse
        QString out;
        bool inp=false;
@@ -317,6 +309,13 @@ QString MLocalFormat::formatDateTime(const QDateTime&time,QString format)const
                                        out+=t;
                                        break;
                                }
+                               case 'z':out+=QString::number(time.time().msec());break;
+                               case 'Z':{
+                                       QString t=QString::number(time.time().msec());
+                                       while(t.size()<3)t="0"+t;
+                                       out+=t;
+                                       break;
+                               }
                                case 'a':{
                                        int t=time.time().hour()%12;if(t==0)t=12;
                                        out+=QString::number(t);
@@ -341,11 +340,30 @@ QString MLocalFormat::formatDateTime(const QDateTime&time,QString format)const
                                        else out+="PM";
                                        break;
                                }
-                               //% sign
+                               case 't':case 'T':{
+                                       //get diff from UTC
+                                       int d=time.secsTo(time.toUTC())/60;
+                                       //west(-) or east(+)?
+                                       QString t;
+                                       if(d<0){t="+";d*=-1;}else t="-";
+                                       //hours
+                                       QString s=QString::number(d/60);
+                                       if(s.size()<2)s="0"+s;
+                                       t+=s;
+                                       //minutes
+                                       s=QString::number(d%60);
+                                       if(s.size()<2)s="0"+s;
+                                       t+=s;
+                                       //append
+                                       out+=t;
+                                       break;
+                               }
+                               // % sign
                                case '%':out+="%";break;
                                //mistakes
                                default:out+="%"+format[i];break;
                        }
+                       inp=false;
                }else{
                        if(format[i]=='%')inp=true;
                        else out+=format[i];
@@ -365,3 +383,206 @@ QDateTime MLocalFormat::unix2localTime(qint64 time)const
        //TODO: use proper time zones
        return QDateTime::fromTime_t(time);
 }
+
+QString MLocalFormat::formatNumber(qint64 n)const
+{
+       //shortcut if we use standard format anyway
+       if(m_thousanddigits<=0)return QString::number(n);
+       //convert number to decimals
+       bool neg=false;
+       if(n<0){n*=-1;neg=true;}
+       QString s=QString::number(n);
+       //insert dividers
+       QString r;
+       int l=s.size();
+       for(int i=0;i<s.size();i++){
+               if(i>0 && ((l-i)%m_thousanddigits)==0)r+=m_thousand;
+               r+=s[i];
+       }
+       if(neg)r="-"+r;
+       return r;
+}
+
+QString MLocalFormat::formatNumber(double num,uint decimals)const
+{
+       //check negative
+       bool neg=false;
+       if(num<0.){num*=-1.;neg=true;}
+       //before the dot
+       qint64 be=floorl(num);
+       QString r=formatNumber(be);
+       //fractions
+       if(decimals>0){
+               r+=m_decimal;
+               num-=be;
+               for(int i=0;i<(int)decimals;i++){
+                       num*=10.;
+                       int d=floorl(num);
+                       r+=QString::number(d);
+                       num-=d;
+               }
+       }
+       //add negative sign
+       if(neg)r="-"+r;
+       //result
+       return r;
+}
+
+QString MLocalFormat::formatMoney(qint64 num,bool usethousand)const
+{
+       //check value
+       bool neg=false;
+       if(num<0){num*=-1;neg=true;}
+       //split main and fraction
+       qint64 mod=1;for(int i=0;i<m_moneydecimals;i++)mod*=10;
+       qint64 frac=num%mod;
+       num/=mod;
+       //create main unit
+       QString ms;
+       if(usethousand)ms=formatNumber(num);
+       else ms=QString::number(num);
+       //create fractional unit
+       QString fs=QString::number(frac);
+       for(int i=fs.size();i<m_moneydecimals;i++)fs="0"+fs;
+       //combine and add negative
+       QString r;
+       if(neg){
+               if(m_moneyneg.size()>=2){
+                       if(m_moneyneg[0]!='\n')r+=m_moneyneg[0];
+               }else{
+                       if(m_moneyneg.size()>=1)r+=m_moneyneg[0];
+                       else r+='-';//emergency fallback
+               }
+       }
+       r+=ms+m_decimal+fs;
+       if(neg && m_moneyneg.size()>=2)
+               r+=m_moneyneg[1];
+       //add currency
+       r+=m_currency;
+       //return result
+       return r;
+}
+
+qint64 MLocalFormat::scanInt(QString num)const
+{
+       num=num.trimmed();
+       qint64 r=0;
+       bool neg=false;
+       for(int i=0;i<num.size();i++){
+               QChar c=num[i];
+               //negative sign
+               if(c=='-')neg=true;else
+               //digits
+               if(c.isDigit()){
+                       r*=10;
+                       r+=c.digitValue();
+               }else
+               //ignore thousand divider
+               if(c==m_thousand)continue;
+               //non-numericals break
+               else break;
+       }
+       if(neg)r*=-1;
+       return r;
+}
+qint64 MLocalFormat::scanMoney(QString num)const
+{
+       //init
+       num=num.trimmed();
+       qint64 r=0,d=0,nd=0;
+       bool neg=false,frc=false;
+       //scan string; r=main unit; d=fractions
+       for(int i=0;i<num.size();i++){
+               QChar c=num[i];
+               //negative sign
+               if(m_moneyneg.contains(c) || c=='-')neg=true;else
+               //decimal dot
+               if(c==m_decimal)frc=true;else
+               //digits
+               if(c.isDigit()){
+                       if(frc){
+                               if(nd>=m_moneydecimals)break;
+                               d*=10;d+=c.digitValue();
+                               nd++;
+                       }else{
+                               r*=10;r+=c.digitValue();
+                       }
+               }else
+               //ignore thousand divider
+               if(c==m_thousand)continue;
+               //non-numericals break
+               else break;
+       }
+       //move main to left
+       for(int i=0;i<m_moneydecimals;i++)r*=10;
+       //add fractions
+       for(;nd<m_moneydecimals;nd++)d*=10;
+       r+=d;
+       //negative?
+       if(neg)r*=-1;
+       //result
+       return r;
+}
+
+double MLocalFormat::scanFloat(QString num)const
+{
+       //init
+       num=num.trimmed();
+       double r=0.;
+       double ff=1.;
+       bool neg=false,frc=false;
+       //scan
+       for(int i=0;i<num.size();i++){
+               QChar c=num[i];
+               //negative sign
+               if(c=='-')neg=true;else
+               //decimal dot
+               if(c==m_decimal)frc=true;else
+               //digits
+               if(c.isDigit()){
+                       if(frc){
+                               ff/=10.;
+                               r+=c.digitValue()*ff;
+                       }else{
+                               r*=10.;
+                               r+=c.digitValue();
+                       }
+               }else
+               //ignore thousand divider
+               if(c==m_thousand)continue;
+               //non-numericals break
+               else break;
+       }
+       //return
+       if(neg)r*=-1.;
+       return r;
+}
+
+QRegExp MLocalFormat::moneyRegExp(bool allownegative,bool allowcurrency)const
+{
+       //main unit
+       QString r="[\\d"+QRegExp::escape(m_thousand)+"]+";
+       //fractions
+       if(m_moneydecimals>0){
+               r+=QRegExp::escape(m_decimal);
+               r+="\\d{"+QString::number(m_moneydecimals)+"}";
+       }
+       //negative
+       if(allownegative){
+               //front
+               QString f="[-";
+               if(m_moneyneg.size()>0)
+                       if(m_moneyneg[0]!='\n' && m_moneyneg[0]!='-')
+                               f+=QRegExp::escape(m_moneyneg[0]);
+               f+="]?";
+               r=f+r;
+               //back
+               if(m_moneyneg.size()>1)
+                       r+=QRegExp::escape(m_moneyneg[1])+"?";
+       }
+       //currency
+       if(allowcurrency && m_currency!="")
+               r+="("+QRegExp::escape(m_currency)+")?";
+       //return
+       return QRegExp(r);
+}
index 343076d..01706cd 100644 (file)
@@ -25,19 +25,19 @@ QString htmlize(QString str);
 QString xmlize(QString str,QString newline="\n");
 
 /**converts a cent value into a (localized) string*/
-QString cent2str(int cent,bool localize=true);
+QString cent2str(qint64 cent,bool localize=true);
 
 /**converts a (localized) string back into a cent value (must not contain spaces or extra dots)*/
-int str2cent(QString s,bool fromlocal=true);
+qint64 str2cent(QString s,bool fromlocal=true);
 
 /**converts a unix timestamp into a date*/
-QString unix2date(int,bool localize=true);
+QString unix2date(qint64,bool localize=true);
 
 /**converts a unix timestamp into a time (ommitting the date)*/
-QString unix2time(int,bool localize=true);
+QString unix2time(qint64,bool localize=true);
 
 /**converts a unix timestamp into a date-time-string*/
-QString unix2dateTime(int,bool localize=true);
+QString unix2dateTime(qint64,bool localize=true);
 
 /**return a (localized) regular expression that validates prices*/
 QRegExp priceRegExp(bool localize=true);
@@ -65,14 +65,15 @@ time values can be formatted with the following variables:
 <tr><td>%%H<td>hour as two digit number (eg. 07) in 24-hour format</tr>
 <tr><td>%%a<td>hour as simple number (eg. 7) in 12-hour format</tr>
 <tr><td>%%A<td>hour as two digit number (eg. 07) in 12-hour format</tr>
-<tr><td>%%i<td>minute</tr>
-<tr><td>%%I<td>minute</tr>
-<tr><td>%%s<td>second</tr>
-<tr><td>%%S<td>second</tr>
-<tr><td>%%z<td>milli-seconds</tr>
-<tr><td>%%Z<td>milli-seconds</tr>
-<tr><td>%%p<td>"am" or "pm" according to current time</tr>
-<tr><td>%%P<td>"AM" or "PM" according to current time</tr>
+<tr><td>%%i<td>minute as simple number (eg. 7)</tr>
+<tr><td>%%I<td>minute as two digit number (eg. 07)</tr>
+<tr><td>%%s<td>second as simple number (eg. 7)</tr>
+<tr><td>%%S<td>second as two digit number (eg. 07)</tr>
+<tr><td>%%z<td>milli-seconds as simple number (eg. 7)</tr>
+<tr><td>%%Z<td>milli-seconds as three digit number (eg. 007)</tr>
+<tr><td>%%p<td>"am" or "pm" according to current time, this is the one set with setAP</tr>
+<tr><td>%%P<td>"AM" or "PM" according to current time, this is toUpper executed on the one set with setAP</tr>
+<tr><td>%%T<td>ISO timezone as +/-hhmm (eg. -0100 or +0100) - does not work reliably on all systems</tr>
 </table>
 
 Any occurrence of "%%" will be translated to a literal "%" sign.
@@ -102,20 +103,24 @@ class MLocalFormat
                /**overrides the short names of months, an empty list resets to the local translation, otherwise the list must be exactly seven entries long and starts with January (Jan)*/
                virtual void setShortMonths(const QStringList&l=QStringList());
                
-               /**overrides the formatting of money - the settings of numbers are re-used; these settings have no translation fallbacks;
-               \param digitsCents defines how many digits are used for sub-amounts (cents), if zero no cents are used, normal are the values 0, 2 and 3
-               \param currency defines the symbol of the currency used, the default is an empty string*/
-               virtual void setMoneyFormat(QString currency=QString(),int digitsCents=2);
+               /**overrides the formatting of money - the settings of numbers are re-used
+               \param digitsCents defines how many digits are used for sub-amounts (cents), if zero no cents are used, normal are the values 0, 2 and 3; the default is 2
+               \param currency defines the symbol of the currency used, the default is an empty string
+               \param negative defines how negative values are formatted, the first character is placed in front of the number, the optional second one behind the number and in front of the currency symbol; if you want no first character use a \n newline instead; the default falls back onto the translation, the default translation uses "-"*/
+               virtual void setMoneyFormat(QString currency=QString(),int digitsCents=2,QString negative=QString());
                
                /**overrides the formatting of numbers and money;
-               \param decimal defines the character used to separate sub-amounts (cents) from the main body, if QChar() is used it returns to the translation default
-               \param thousandDiv defines the character used to separate blocks of digits, if QChar() is used it returns to the translation default
-               \param digitsDiv defines the amount of digits in a block, eg. the value 3 will format 1000 as 1,000*/
+               \param decimal defines the character used to separate decimals from the main body, if QChar() is used it returns to the translation default (a dot "." in default localization)
+               \param thousandDiv defines the character used to separate blocks of digits, if QChar() is used it returns to the translation default (comma "," in default)
+               \param digitsDiv defines the amount of digits in a block, eg. the value 3 will format 1000 as 1,000 (none per default)*/
                virtual void setNumberFormat(QChar decimal=QChar(),QChar thousandDiv=QChar(),int digitsDiv=-1);
                
                /**overrides the formatting of the AM/PM part of time display, resets to the localization if none given*/
                virtual void setAP(QString am="--",QString pm="--");
                
+               /**overrides the formatting to be non-localized, numbers use decimal dot and no thousand separator, no currency symbol and "-" as negative sign; otherwise things stay the same; this is a helper for the shortcut methods like cent2str*/
+               virtual void setNonLocalized();
+               
                /**formats a date according to the given format, if none is given, the translation default is used
                \param date a QDate used as input
                \param format a format string as specified by QDate::toString */
@@ -139,9 +144,71 @@ class MLocalFormat
                /**formats a date and time according to the given format, if none is given, the translation default is used
                \param time a unix timestamp, it is converted to local time*/
                virtual QString formatDateTime(qint64 time,QString format=QString())const;
+               
+               
+               /**formats an integer number*/
+               virtual QString formatNumber(qint64)const;
+               /**formats an integer number*/
+               virtual QString formatNumber(int n)const{return formatNumber(qint64(n));}
+               /**formats an integer number*/
+               virtual QString formatNumber(uint n)const{return formatNumber(qint64(n));}
+               
+               /**formats a floating point number, this version does not use exponential display, but adheres to the settings of local decimal dot and thousands block division
+               \param num the number to be formatted
+               \param decimals the maximum number of digits after the decimal dot*/
+               virtual QString formatNumber(double num,uint decimals=4)const;
+               
+               /**formats a money value
+               \param num the amount in cents
+               \param usethousand if true the thousand block division is inserted */
+               virtual QString formatMoney(qint64 num,bool usethousand=true)const;
+               
+               /**scans an integer number and returns the value, non-numerical chars are ignored*/
+               virtual qint64 scanInt(QString)const;
+               
+               /**scans a money value and returns it in cents, non-numerical chars are ignored*/
+               virtual qint64 scanMoney(QString)const;
+               
+               /**scans a floating point number and returns it, non-numerical chars are ignored*/
+               virtual double scanFloat(QString)const;
+               
+               /**returns the names used for week days*/
+               QStringList weekDayNames()const{return m_day;}
+               /**returns the abbreviations used for week days*/
+               QStringList shortWeekDayNames()const{return m_sday;}
+               
+               /**returns the names used for months*/
+               QStringList monthNames()const{return m_month;}
+               /**returns the names used for months*/
+               QStringList shortMonthNames()const{return m_smonth;}
+               
+               /**returns the currency symbol*/
+               QString currency()const{return m_currency;}
+               
+               /**returns the decimal dot symbol*/
+               QChar decimalDot()const{return m_decimal;}
+               
+               /**returns the thousand separator if used, otherwise an empty string*/
+               QString thousandSeparator()const{if(m_thousanddigits>0)return m_thousand;else return "";}
+               
+               /**returns the amount of decimals in a money value*/
+               int moneyDecimals()const{return m_moneydecimals;}
+               
+               /**returns the text for AM in 12-hour clock format*/
+               QString amText()const{return m_am;}
+               /**returns the text for PM in 12-hour clock format*/
+               QString pmText()const{return m_pm;}
+               
+               /**returns the negative sign chars for money values*/
+               QString moneyNegativeSigns()const{return m_moneyneg;}
+               
+               /**returns a regular expression matching money values
+               \param allownegative if given allows to use negative values
+               \param allowcurrency if given allows to use the currency symbol */
+               QRegExp moneyRegExp(bool allownegative=false,bool allowcurrency=false)const;
        protected:
                QStringList m_day,m_sday,m_month,m_smonth;
-               QString m_currency,m_am,m_pm;
+               QString m_currency,m_am,m_pm,m_moneyneg;
                QChar m_decimal,m_thousand;
                int m_moneydecimals,m_thousanddigits;