Using Union (or other struct) as a time unit converter?

I have this Union declared in a library that I wrote some time ago. I didn't create this union myself, it came from someone as an answer to a question on one of these forums. It's been so long, I cannot remember.

union cTime {
    double seconds;

    class milli_t {
        double seconds;
    public:
        milli_t &operator=(double ms) {
            seconds = ms / 1000.0;
            return *this;
        }
        operator double() const { return seconds * 1000; }
    } millis;
};

I can use this union to convert between Seconds and Milliseconds in this way

cTime time;
time.millis = 14000;
long seconds = time.seconds;

OR the other direction

cTime time;
time.seconds = 35;
long milliseconds = time.millis;

This gives me the freedom to work with the time variable within either context, seconds or milliseconds, then pull out of the variable, whichever version of the value I want.

I am oversimplifying my use case, but what I wanted to make sure to clarify is that what matters, is that I can declare a variable as cTime, assign it a value in either unit, work with the value and change it etc. in either unit, then when I need to return the value, or use it in whatever context ... I can simply use the variableName.unit and it comes back in that unit.

Obviously, seconds get rounded etc., but that's ok.

What I would like to do, is somehow expand the union to include the ability to also get microseconds from the datatype so that I can flexibly do anything such as

cTime time;
time.micros = 254000;

long seconds = time.seconds;
long milliseconds = time.millis;

etc.

Does anyone know if this is possible? If not with this union structure, then maybe with an enum or a struct somehow? Because I really like the convenience of it in addition to the simplification it offers in terms of code readability and writability.

C++ is not my strength as I don't use it that often and I never go deep in it whenever I do use it, and this is deeper than I currently understand where C++ is concerned.

What you have may work, but it's sketchy C++. You're relying on the 'seconds' element of the union occupying the same memory location as the private data member 'seconds' of the milli_t class. It probably will, but they're really two different variables. Putting data of one type into a union and retrieving it as another type invokes implementation-dependent behavior.

So then, how do we do what I wanna do?

Mike

How about just dividing milliseconds by 1000 (or faster, multiplying by 0.001).

The union declares seconds as type double, then you copy it into a long. Why?

I don't get the point why a union should be more readable than dividing by 1000 , but I guess if a class can divide by 1000, it also can divide by 1000000

// useless waste of program memory - don't copy-paste
// https://forum.arduino.cc/t/using-union-or-other-struct-as-a-time-unit-converter/1020478/
// https://wokwi.com/projects/339480054996140626
union cTime {
  double seconds;

  class milli_t {
      double seconds;
    public:
      milli_t &operator=(double ms) {
        seconds = ms / 1000.0;
        return *this;
      }
      operator double() const {
        return seconds * 1000;
      }
  } millis;

  class micro_t {
      double seconds;
    public:
      micro_t &operator=(double us) {
        seconds = us / 1000000.0;
        return *this;
      }
      operator double() const {
        return seconds * 1000000;
      }
  } micros;

};

void setup() {
  Serial.begin(115200);

  cTime time;
  time.millis = 14000;
  long seconds = time.seconds;
  Serial.println(seconds);

  long micro = time.micros;
  Serial.println(micro);

}

void loop() {
  // put your main code here, to run repeatedly:

}

prints:

14
14000000

In this particular case, this way of doing it is not only a convenience, it does make the code much more concise and easier to read and understand.

Thank you, I had tried something KINDA similar but I tried to use the same milli_t class.

Here is what I DONT understand about this custom datatype and the way it's being engaged.

Declaring a class and then placing a name AFTER the classes closing bracket ... I have no understanding of what that does or even why it's allowed, though I do realize that name is what provides the ability to call the variable declared with the union and then be able to access the value of milli and now micros ... but why that works, and how a class can even be a value in the first place really makes no sense to me.

Then the fact that seconds exists because it is declared OUTSIDE of the classes YET in both cases it is used as the operand for converting the stored value and of course it makes sense that the operand is 1,000 for millis and 1,000,000 for micros.

Then the question in my mind is ... when I instantiate a variable with that union, HOW does a value assigned to micros, for example, end up being converted into seconds when I call seconds since both classes have a connection to seconds ... well now that I'm typing this out and thinking about it ... I suppose that would have a lot to do with the fact that all of these variables (classes / WHATEVER) share the same memory space so that kind of explains that.

The whole thing is just not very intuitive and if I were doing something like this in Java, I would probably just use an enum since there are some creative things one can do with enums in Java. But to my knowledge, Java doesn't have anything at all that even resembles the union in C++.

Anyways, thanks again, I appreciate the help.

Mike

Because when doing non-blocking time calcs in the library, I need it to be an unsigned long and I merely used the code for the union that was provided without modifying it.

Is there an enormous expense in terms of CPU time to do conversions between datatypes? If so, then I need to optimize since the library is all about time and the more efficient I can make it, the better it will be in terms of usefulness in a range of scenarios.

Mike

I don't see a more efficent way than to use the common "BlinkWithoutDelay" concept like

if (millis() - previousMillis > interval)

only a substraction and a comparison.
It will not get any better (perfomance wise) if you add a multiplication and a division to that, which will be performed during runtime.

May be you want to showcase two MRE's
a) one where where you show off the advantage of that union and explain why it makes the code easier to read for you - instead of an separate multiplication / division.
b) a second one with the division / multiplication in your source (if needed).

There is an expense.

It seems rather silly to call a single line of code, doing one multiply or divide operation "blocking". But, have fun with the project.

My goal in this particular case was not library performance, but code writing and reading simplicity.

Consider the following code

BlockNot::BlockNot(unsigned long time, Unit units, Global globalReset) {
    baseUnits = units;
    initDuration(time);
    reset();
    global = globalReset;
    if (global == GLOBAL_RESET) addToTimerList();
}

void BlockNot::initDuration(unsigned long time) {
    switch(baseUnits) {
        case MICROSECONDS:
            duration.micros = time;
            break;
        case MILLISECONDS:
            duration.millis = time;
            break;
        case SECONDS:
            duration.seconds = time;
            break;
    }
}

When the user instantiates the library and in the second argument, specifies whether the timer is to be calculated in SECONDS, MILLISECONDS, or MICROSECONDS, all that I need to do is set the global variable baseUnits to whichever option they passed in, then I call the initDuration method (which is a method used by MULTIPLE class constructors) which then looks at the baseUnits, then it assigns the value for the timer's duration into A SINGLE VARIABLE called duration, and then in the rest of the library code, I need to only reference that one variable with the dot and the units to do whatever I need to do with the timer.

It - in essence - allows me to write the code using one variable for the timer's duration without needing to be concerned about whether or not the timer is using seconds, milliseconds or microseconds since those conversions happen automatically by the definition of the variable.

I can set the one variable using milliseconds, then pull out that value in seconds or microseconds without having to write those conversions in those methods.

For me personally, it makes the code much simpler and easier to both read and write because when you're reading the methods, your brain doesn't have to work through the math in every method that would require those conversions... it then becomes obvious exactly what is happening when you see duration.seconds or duration.microseconds without the brain having to examine the math for those references.

And since these conversions only happen when the user initiates changes to the timers values, it will have no impact on the use of the library in terms of reading the values for timing triggers.

The ONLY benefit that is gained doing things this way, is that I can write the math ONCE, and it gets applied everywhere in every method where that object is referenced.

Granted, the equations are extremely simple, I understand that. However, this is a personal choice to be able to write my code using a structure that in my opinion, ends up providing code that is much easier to read and write.

So the only benefit here, is that I get to write my code the way I want to write it. That is the ONLY advantage that I was seeking when I asked this question.

So though it may not resonate with everyone and people might be critical of doing things this way, please understand that this isn't about anything other than my own personal preference for code writing style.

This is in fact EXACTLY how the library checks the passing of time in relation to interval which I call duration. Only difference is that I use >=

Those calculations are not connected with the Union object other than that the interval exists as a Union object, so the equation ends up looking like this during execution:

if (millis() - startTime) >= duration.milliseconds;

and if its a microsecond timer, then it looks like this:

if (micros() - startTime) >= duration.microseconds;

What I was after (and I do have it working the way I want it to) was to be able to simply define duration in any unit (SECONDS, MILLISECONDS or MICROSECONDS) and then reference that duration anywhere else in the libaray by any of the optional units without needing to write the conversions every single time.

One variable + three "flavors" = code easier to read/write.

But the comparison against the clock happens just as you described.

So in the end you write a member function to set / handle a variable in different units.
Others would just set a prescaler for that variable.

But hey, it's a free world. Write your code you like. But don't be surprised if others don't like it.

You may wish to read:
Units and measures for C++ 11 - CodeProject

C++ is at version 23 (C++23) and templates have grown in capability.

Also,
How to Create a Basic Math Template in C++ - dummies

I looked them over, and I don't see - immediately anyways - how I could get the ability that I have now, where I can use a single variable then just tap it for the different units

duration.seconds
duration.milliseconds
duration.microseconds

Assign a value to any one of those and then read back from any one of those without regards to whats going on "under the hood".