Added by Thomas Weidner, last edited by Thomas Weidner on Jan 25, 2007  (view change)

Labels

 
(None)

Zend Framework: Zend_Measure Component Proposal

Proposed Component Name Zend_Measure
Developer Notes http://framework.zend.com/wiki/display/ZFDEV/Zend_Measure
Proposers Thomas Weidner
Gavin (Zend-liaison)
Revision 4.0 - 25 Jan 2007: API rework, simpler useage
3.1 - 23 July 2006: Corrections based on actual work
3.0 - 18 July 2006: Class redesign based on actual work and comments from Zend
2.0 - 26 June 2006: Complete redesign of class skeleton, added new functions
1.1 - 20 June 2006: Changed class to be static
1.0 - 19 June 2006: Initial release extracted from Zend_Locale Proposal V1.0. (wiki revision: 28)

Table of Contents

1. Overview

Zend_Measure is a locale aware Class which can convert measurement units.
Hint: The name Zend_Measure was taken because Zend_Convert implements to convert between different file-types (Text->Excel, Windows->Unix,...) and not measurement units.

2. References

3. Component Requirements, Constraints, and Acceptance Criteria

  • Lightweight and fast implementation
  • Simple use for ZF-user
  • Handling of different input formats

4. Dependencies on Other Framework Components

5. Theory of Operation

Zend_Measure can convert measurement units between different locales. The Conversion can be done for

  • Length
  • Weight
  • Temperature
  • Area
  • Power
  • Energy
  • and much more...

6. Milestones / Tasks

zone: Missing {zone-data:milestones}

7. Class Index

  • Zend_Measure_Exception
  • Zend_Measure

8. Use Cases

Get output of Kelvin in actual locale

Get formatted output in different locale

Different Input Types which would be recognized

Convert Fahrenheit to Celsius

Convert Kilo to Tonne with other locale

Maths with different units

9. Class Skeletons

This seems like it would be better implemented at as a library of helper function rather than one big class – especially given that the locale is passed to each method. If it was a library of functions it would be infinitely extensible without needing to include the whole giant class.

The locale is not passed explicit.
When no locale is defined, the standard locale will be taken.

BUT you can define that the returned result is displayed in a different locale.
So all locale-parameters are optional as you will see at the first usecase.

How would the "library of helper functions" look like and be implemented in your thought ?

Btw: This class is extensible, as its complete PUBLIC.
Btw2: There are only 11 functions so I don't think thas this is a BIG CLASS.

It seems like the methods would be better implemented as static methods, since they wouldn't vary between instances of the original class (though would if the class was extended, in which case it would have a different name anyway and it wouldn't matter if they were static). Beyond that, I think the class is well-designed. I would suggest adding a method to set an override for the default locale and have all methods use that locale unless a different one is specified in calls to them.

EXAMPLE 1:

/* Return value would use the default locale */
Zend_Unit.toWeight($units);

EXAMPLE 2:

Zend_Unit.setLocale('fr');

/* Return value would use the 'fr' locale instead of the default */
Zend_Unit.toWeight($units);

/* Return value would use the 'en' locale instead of the 'fr' locale */
Zend_Unit.toWeight($units, $format, 'en');

Also, this site as a reference, as it likely has all the information you'll need in one place and possibly even other conversions you haven't thought to include yet.

I like the staticification of Zend_Unit and will change this with next wiki-update.

I am not clear how it would look, but ultimately there are hundreds of units and the class would be HUGE in order to be complete. Also what happens with:

$unit = Zend_Unit('25742.54 kg',Zend_Unit::KILOGRAM);
echo $unit->toPressure(Zend_Unit::WATT,"n.2",de_DE); // outputs ?

Maybe break it up into individual static classes for each type of unit like:

class Zend_Units_Weight
class Zend_Units_Length
class Zend_Units_Volume
...

Sure there are hundreds of units.
This was the reason why I split them in types.

There are only a few types like weight, lenght, temperature and so on.
These recognise their known types.

The example you gave would throw 2 exeptions :

  • WATT is no unit of type Pressure
  • KILOGRAM can not be converted to WATT

Conversion can only done between same types.

When you give an type-less input all works right :

When split into individual classes each class will have 1 function... I don't think this is nessecary.

"When split into individual classes each class will have 1 function... I don't think this is nessecary."

They would be polymorphic if the all had the same interface with a convert() method. That might be handy for something.

I really, really agree with this. Each helper class can identify itself as a weight, volume, or length, and then the conversion process can be delegated down.

So you want to have

and then call for example

.....

I don't think this is handy. From the site of object orientation is makes sense, but for the user not. More tipping and less useability.

But again...

"How do you want the class to look like ?"

I've thought more about it. Scratch the delegating down part.

> "How do you want the class to look like ?"

Each unit type class would be measured (internally only) against a benchmark unit. For length it could be the meter, for volume the liter, and for weight the gram, and so on.

The design goals should be:

  • Zend_Unit is easy to extend
  • Zend_Unit includes Zend_Unit_Length, Zend_Unit_Weight, etc.
  • Users do not have to include string indicators within the number, and do not have to depend on Zend_Locale. The number is an integer or float. The return value is an integer or float. The mechanism for conversion is indicated by parameters.

Given that, here's how I would implement it:

Ta-da!

This is a more object-oriented approach than in the proposal, as well. Here's a complete class skeleton:

View the rest of this thread. Most recent comment: Jun 25, 2006
5 more comments by: Simon Mundy, Matthew Ratzloff, Thomas Weidner

A concern I have is the name "Zend_Unit", my first thought went to Unit testing, What is a "Unit"

Since this properly formats a "Unit" would it not make more sense for it to be part of Zend_Locale?

Zend_Locale_Unit makes more sense?

The first approach was to do a "Zend_Locale_Unit".

But as the other classes like Zend_Date and Zend_Currency which make extend use of Zend_Locale were all explicit extracted from Zend_Locale, it would make no sense to let the Unit Handling in Locale.

Zend_Locale should ONLY handle direct Locale and Translation Issues.

Conversion between Types and Type-Like-Formatting which is done by Zend_Unit has nothing to do with Locale Handling. This was the reason why to do a own Class / Proposal.

In the spirit of it-does-what-it-says, why not simply Zend_Unit_Converter or Zend_Convert?

$unit = Zend_Convert::toUnit('86,7°F',Zend_Unit::FAHRENHEIT);
echo Zend_Convert::toTemperature($unit,Zend_Unit::CELSIUS);

Zend_Converter sounds handy

But it doesn't only convert, it also handles locale-formatting.

I would let the desicion for changing the Class name to our Zend_Boys
I like both.

Zend Feedback

Zend_Unit is conditionally accepted subject to the
conventions, stipulations, requirements, and changes listed below.

A ZF default locale object should be used in most places where a
component or function expects an optional locale object.
The default locale object should be constructed from an instance of
Zend_Config shared by all ZF components.

Parsing, normalization, conversion, and formatting function names
could benefit from sharing common looking names with other locale-related
classes.

Why should rounding be performed during instantiation, instead of formatting?

If the unit type is specified (e.g. Zend_Unit::KILOGRAM), can Zend_Unit
do the "right" thing when given a raw number like "1.250" or "12,5"?
A couple use case would help clarify this.

A large majority of the world uses Celsius. Should Zend_Unit::CELSIUS
be required, if the default locale belongs to the majority?

The name of the class needs clarification, probably by changing it to one of:

  • Zend_UnitConversion
  • Zend_UnitConvert
  • Zend_MeasurementConverter (most accurate, but terrible to type)
  • Zend_Convert
  • Zend_Converter
  • Zend_Measure
  • suggestions?

Are the Zend_Unit constants, like Zend_Unit::SOME_UNIT_TYPE, absolute,
precise, unambiguously unique designators of units, or can they represent
something more abstract? For example, "ton" has several meanings,
depending on the locale. If the units are unambigous, then what is the reason
for having both a parameter for Zend_Unit::CONSTANT_UNIT_TYPE and a
parameter for a locale in the convertTo() method?

Fundamentally, we have data input, parsing, and normalization in the context
of a particular locale, followed by formatting for output according to the
conventions of optionally, a different locale.
Methods should not combine input parsing, normalization, and output
formatting all in one step. Instead, parsing and normalization could be
encapsulated by a constructor. Then a "convertTo($someLocale)" can be
applied to the instance, possibly more than once, if the same unit must
be shown in different locales. Thus, __constructor()'s methods return
normalized instances, not converted instances.

Instances should be serializable.

Posted by Gavin at Jul 17, 2006 17:37

A ZF default locale object should be used in most places where a
component or function expects an optional locale object.
The default locale object should be constructed from an instance of
Zend_Config shared by all ZF components.

 All parameters witch expect a locale object are optional.
When the parameter is left empty the function will take the standard locale
which is set or recognized by ZF automatically.
Therefor all "locale" parameters are marked OPTIONAL

Why should rounding be performed during instantiation, instead of formatting?

There will be no rounding or formatting by instantiation.
By instantiation the value will be extracted and stored internally in the standard type.
Formatting will only be done when requesting the output string.

If the unit type is specified (e.g. Zend_Unit::KILOGRAM), can Zend_Unit
do the "right" thing when given a raw number like "1.250" or "12,5"?
A couple use case would help clarify this.

I added some new use-cases to make this clear.
Generally an integer, float or string can be given by instantiation
and the first recognised "value" will be taken but locale-aware.
That means a english '12.5' is identical with an german '12,5'.

A large majority of the world uses Celsius. Should Zend_Unit::CELSIUS
be required, if the default locale belongs to the majority?

I reworked this.
You can make Zend_Measure::TEMPERATURE which will take the standard for
temperature measurements which is Celsius.
Zend_Measure_Temperature::CELSIUS is identically with Zend_Measure::TEMPERATURE.

The name of the class needs clarification, probably by changing it to one of:

  • Zend_UnitConversion
  • Zend_UnitConvert
  • Zend_MeasurementConverter (most accurate, but terrible to type)
  • Zend_Convert
  • Zend_Converter
  • Zend_Measure
  • suggestions?

In my eyes Zend_Measure makes all clear. Handling of all measurement related things as
getting value out of string, conversion, adding and so on.

Are the Zend_Unit constants, like Zend_Unit::SOME_UNIT_TYPE, absolute,
precise, unambiguously unique designators of units, or can they represent
something more abstract? For example, "ton" has several meanings,
depending on the locale. If the units are unambigous, then what is the reason
for having both a parameter for Zend_Unit::CONSTANT_UNIT_TYPE and a
parameter for a locale in the convertTo() method?

The constants are absolute and precise units.
There are no double units.
When there are locale dependent units as feet the class will have
FEET, FEET_FRENCH, FEET_EGYPT, FEET_IRAQ
as all feet's are calculated different. But there will always an international standard FEET unit.

To mention:
Zend_Measure can only recognize input values.
It does not know "Celsius" and maybe "Celsian".
It only knows "1234" as this is locale independant.
When given "1234 Celsius" only 1234 would be recognised,
so you have to say which expected type you are instantiating.

The type parameter in the convertTo method can be used to change the
unit type, so instead of METER we can produce LIGHT_SECOND output
of the object value. So 100,000,000 meter would become 1 light second.

Otherwise the locale parameter is used to know how a string has to be parsed.
Grouping in english ','  in german '.'
Decimalpoint in english '.'    in german ','
Also different number formats can be defined in the locale.
For example there are slangs which group by 5 instead of 3 as it will be done in english.
So 14,000,000 would become 140,00000.

Fundamentally, we have data input, parsing, and normalization in the context
of a particular locale, followed by formatting for output according to the
conventions of optionally, a different locale.
Methods should not combine input parsing, normalization, and output
formatting all in one step. Instead, parsing and normalization could be
encapsulated by a constructor. Then a "convertTo($someLocale)" can be
applied to the instance, possibly more than once, if the same unit must
be shown in different locales. Thus, __constructor()'s methods return
normalized instances, not converted instances.

Extraction of the right value and normalization to the standardunit will be done in an extra function "extractValue".
As normalization is different for different types the encapsulating would be a problem, as each type must be normalized in an other way.
Length "12,345678m"    Circular "12'34"5678°"    Currency "$ 12.345,678"

Internally the values are always stored in standard unit.

Instances should be serializable.

I added this in my proposal.


I hope I did'nt forgot something, but I think all questions should be answered.

1 thing i forgot to mention:

The method "extractValue" makes use of Zend_Locale_Format->getNumber
to extract the proper value.

But special values as circular must be parsed locally.
Currency on the other hand can be pased using Zend_Locale_Format->getNumber
as there is no difference in the parsing algorithmus.

Hope this clears the point of confusion

Looks good. Yes, I see that convertTo() must take 3 parameters, so that the number can be parsed correctly, and the unit of measurement must be specified independently of the data and number format (locale).

Posted by Gavin at Jul 19, 2006 20:02

Hi all,

I had some questions over Zend_Measure I thought to share:

Zend_Measure_Capacity - Is "capacity" really a de facto synonym of
"capacitance?" Despite the answer, I would prefer the name
Zend_Measure_Capacitance, so as to avoid ambiguity - when I first read
"Capacity," I assumed it was for volume, until I saw Zend_Measure_Volume.

Zend_Measure_Flow_* - has a class for Mass; will Mass be included in
Zend_Measure (as well as Zend_Measure_Flow)? I didn't see one in the
proposal...

Zend_Measure_(Illumination|Lightness) - I don't recall from physics what
the difference here might be. I did notice that several of the units
seem to be very similar across the two classes (e.g.,
Lightness::CANDELA_PER_SQUARE_METER and
Illumination::LUMEN_PER_SQUARE_METER).

Best regards,
Darby

Zend_Measure_Capacity - Is "capacity" really a de facto synonym of
"capacitance?" Despite the answer, I would prefer the name
Zend_Measure_Capacitance, so as to avoid ambiguity - when I first read
"Capacity," I assumed it was for volume, until I saw Zend_Measure_Volume.

Checked
I will change this soon

Zend_Measure_Flow_* - has a class for Mass; will Mass be included in
Zend_Measure (as well as Zend_Measure_Flow)? I didn't see one in the
proposal...

Fluidicy is measured in
Flow of mass (f.e. flowing of sand / non fluid)
Flow of mole (includes density of flowing material)
Flow of Volume (f.e. flowing of water / fluids)

As mass and volume could be seen as weight and volume I had to make a own subclass to make things clear.

Zend_Measure_(Illumination|Lightness) - I don't recall from physics what
the difference here might be. I did notice that several of the units
seem to be very similar across the two classes (e.g.,
Lightness::CANDELA_PER_SQUARE_METER and
Illumination::LUMEN_PER_SQUARE_METER).

I didn't see any relationship in this 2 classes.
Lightness - f.e. Candela/m² measures the density of light.
Illumination - f.e. Lumen/m² measures the energy of light

Otherwise described:

Lightness is measured in Lightpower per Square meter... how much power of light per room-angle is found.
Illumination is measured in Lightenergy per Square meter... how much energy of light per room-angle is found.
Only because both units are measured per room-angle doesn't mean that they can be converted into each other without the knowledge of a third parameter.

To get Lumen from Candela (Lightness to Illumination) you would have to multiply candela with
sterad (which is area in m² per square angle in m²).

Thanks for the clarifications, Thomas!

Great work on putting everything together and getting it approved, Thomas!

One comment: Zend_Measure::sub() should just be written out as Zend_Measure::subtract(). "Sub" is probably needlessly short and obfuscated.

-Matt

Thanks

Regarding to sub():
In all locale aware classes the mathematical functions are abbreviated to 3 letters:

add() for addition and sub() for substraction.

Changing sub to substract would mean

  • more typing for user
  • change all other locale related classes...

Speaking for the Zend_Date class I'm actual coding that all subxxx functions will change. And there are much of them

subDay would become substractDay
subYear would become substractYear

and so on...
I think for "add" and "sub" the abbreviation can be used as the meaning is clear.

Greetings
Thomas

Zend Feedback

Hi Thomas,

This change has been approved. The complications are resolved,
if users simply use the needed Measure class directly.

We will need to update the proposal and documentation.

Cheers,
Gavin

> -------- Original Message --------
> Subject: Re: [fw-i18n] RE: [fw-general] Zend_Measure constants wrong?
> Date: Wed, 24 Jan 2007 00:15:00 +0100
> From: Thomas Weidner <thomas.weidner@gmx.at>
>
> Hy Andi and Team,
>
> .
> .
>
> We could also negate the base class...
> So a user would have to use Zend_Measure_Length direct instead of Zend_Measure...
> No base class, no problems
> Would be a good solution in my eyes... it's only the question if this is coding standard conform...
>
>
> .
> .

Posted by Gavin at Jan 25, 2007 10:43