]> OCCT Git - occt.git/commitdiff
Foundation Classes - Optimize Quantity package (#834)
authorPasukhin Dmitry <dpasukhi@opencascade.com>
Mon, 17 Nov 2025 09:54:17 +0000 (09:54 +0000)
committerGitHub <noreply@github.com>
Mon, 17 Nov 2025 09:54:17 +0000 (09:54 +0000)
- Converted validation macros to inline functions for better type safety
- Added `noexcept` specifiers to non-throwing functions for compiler optimization opportunities
- Added `constexpr` to compile-time evaluable functions (comparison operators, leap year calculation)
- Enhanced `Quantity_Color::StringName()` to return "UNDEFINED" instead of throwing exceptions
- Introduced shared time constants header for better maintainability

14 files changed:
src/FoundationClasses/TKernel/GTests/FILES.cmake
src/FoundationClasses/TKernel/GTests/Quantity_ColorRGBA_Test.cxx [new file with mode: 0644]
src/FoundationClasses/TKernel/GTests/Quantity_Color_Test.cxx [new file with mode: 0644]
src/FoundationClasses/TKernel/GTests/Quantity_Date_Test.cxx [new file with mode: 0644]
src/FoundationClasses/TKernel/GTests/Quantity_Period_Test.cxx [new file with mode: 0644]
src/FoundationClasses/TKernel/Quantity/Quantity_Color.cxx
src/FoundationClasses/TKernel/Quantity/Quantity_Color.hxx
src/FoundationClasses/TKernel/Quantity/Quantity_ColorRGBA.cxx
src/FoundationClasses/TKernel/Quantity/Quantity_ColorRGBA.hxx
src/FoundationClasses/TKernel/Quantity/Quantity_Date.cxx
src/FoundationClasses/TKernel/Quantity/Quantity_Date.hxx
src/FoundationClasses/TKernel/Quantity/Quantity_Period.cxx
src/FoundationClasses/TKernel/Quantity/Quantity_Period.hxx
src/FoundationClasses/TKernel/Quantity/Quantity_TimeConstants.pxx [new file with mode: 0644]

index 582fc9d16b3d42245258c20ded46e9c083b6a5bb..5bc29c2ab9d5bacd80eee9ce5b28e687dcb0a933 100644 (file)
@@ -22,6 +22,10 @@ set(OCCT_TKernel_GTests_FILES
   NCollection_Vector_Test.cxx
   OSD_Path_Test.cxx
   OSD_PerfMeter_Test.cxx
+  Quantity_Color_Test.cxx
+  Quantity_ColorRGBA_Test.cxx
+  Quantity_Date_Test.cxx
+  Quantity_Period_Test.cxx
   Standard_ArrayStreamBuffer_Test.cxx
   Standard_Atomic_Test.cxx
   Standard_Character_Test.cxx
diff --git a/src/FoundationClasses/TKernel/GTests/Quantity_ColorRGBA_Test.cxx b/src/FoundationClasses/TKernel/GTests/Quantity_ColorRGBA_Test.cxx
new file mode 100644 (file)
index 0000000..78adb4b
--- /dev/null
@@ -0,0 +1,285 @@
+// Copyright (c) 2025 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#include <Quantity_ColorRGBA.hxx>
+#include <Quantity_Color.hxx>
+
+#include <gtest/gtest.h>
+#include <cmath>
+
+// Test fixture for Quantity_ColorRGBA tests
+class Quantity_ColorRGBATest : public testing::Test
+{
+protected:
+  void SetUp() override {}
+
+  void TearDown() override {}
+
+  // Helper to compare floating point values
+  bool IsNear(Standard_ShortReal theValue1,
+              Standard_ShortReal theValue2,
+              Standard_ShortReal theTolerance = 0.001f) const
+  {
+    return std::abs(theValue1 - theValue2) < theTolerance;
+  }
+};
+
+// Test basic construction
+TEST_F(Quantity_ColorRGBATest, BasicConstruction)
+{
+  // Default constructor
+  Quantity_ColorRGBA aColor1;
+  EXPECT_TRUE(IsNear(1.0f, aColor1.GetRGB().Red()));   // YELLOW = RGB(1,1,0)
+  EXPECT_TRUE(IsNear(1.0f, aColor1.GetRGB().Green())); // YELLOW = RGB(1,1,0)
+  EXPECT_TRUE(IsNear(0.0f, aColor1.GetRGB().Blue()));  // YELLOW = RGB(1,1,0)
+  EXPECT_TRUE(IsNear(1.0f, aColor1.Alpha()));
+
+  // Constructor with RGB + alpha
+  Quantity_ColorRGBA aColor2(Quantity_Color(0.5, 0.6, 0.7, Quantity_TOC_RGB), 0.8f);
+  EXPECT_TRUE(IsNear(0.5f, aColor2.GetRGB().Red()));
+  EXPECT_TRUE(IsNear(0.6f, aColor2.GetRGB().Green()));
+  EXPECT_TRUE(IsNear(0.7f, aColor2.GetRGB().Blue()));
+  EXPECT_TRUE(IsNear(0.8f, aColor2.Alpha()));
+
+  // Constructor with RGBA floats
+  Quantity_ColorRGBA aColor3(0.3f, 0.4f, 0.5f, 0.6f);
+  EXPECT_TRUE(IsNear(0.3f, aColor3.GetRGB().Red()));
+  EXPECT_TRUE(IsNear(0.4f, aColor3.GetRGB().Green()));
+  EXPECT_TRUE(IsNear(0.5f, aColor3.GetRGB().Blue()));
+  EXPECT_TRUE(IsNear(0.6f, aColor3.Alpha()));
+}
+
+// Test constexpr capabilities
+TEST_F(Quantity_ColorRGBATest, ConstexprGetters)
+{
+  const Quantity_ColorRGBA aColor(0.2f, 0.4f, 0.6f, 0.8f);
+
+  // These should work with constexpr
+  Standard_ShortReal aAlpha = aColor.Alpha();
+  EXPECT_TRUE(IsNear(0.8f, aAlpha));
+}
+
+// Test SetValues
+TEST_F(Quantity_ColorRGBATest, SetValues)
+{
+  Quantity_ColorRGBA aColor;
+
+  aColor.SetValues(0.1f, 0.2f, 0.3f, 0.4f);
+  EXPECT_TRUE(IsNear(0.1f, aColor.GetRGB().Red()));
+  EXPECT_TRUE(IsNear(0.2f, aColor.GetRGB().Green()));
+  EXPECT_TRUE(IsNear(0.3f, aColor.GetRGB().Blue()));
+  EXPECT_TRUE(IsNear(0.4f, aColor.Alpha()));
+}
+
+// Test hex color parsing (tests new HEX_BASE constant)
+TEST_F(Quantity_ColorRGBATest, HexColorParsing_RGB)
+{
+  Quantity_ColorRGBA aColor;
+
+  // Test standard RGB hex format: #RRGGBB
+  bool aResult = Quantity_ColorRGBA::ColorFromHex("#FF0000", aColor, true);
+  EXPECT_TRUE(aResult);
+  EXPECT_TRUE(IsNear(1.0f, aColor.GetRGB().Red(), 0.01f));
+  EXPECT_TRUE(IsNear(0.0f, aColor.GetRGB().Green(), 0.01f));
+  EXPECT_TRUE(IsNear(0.0f, aColor.GetRGB().Blue(), 0.01f));
+
+  // Test without # prefix
+  aResult = Quantity_ColorRGBA::ColorFromHex("00FF00", aColor, true);
+  EXPECT_TRUE(aResult);
+  EXPECT_TRUE(IsNear(0.0f, aColor.GetRGB().Red(), 0.01f));
+  EXPECT_TRUE(IsNear(1.0f, aColor.GetRGB().Green(), 0.01f));
+  EXPECT_TRUE(IsNear(0.0f, aColor.GetRGB().Blue(), 0.01f));
+
+  // Test blue
+  aResult = Quantity_ColorRGBA::ColorFromHex("#0000FF", aColor, true);
+  EXPECT_TRUE(aResult);
+  EXPECT_TRUE(IsNear(0.0f, aColor.GetRGB().Red(), 0.01f));
+  EXPECT_TRUE(IsNear(0.0f, aColor.GetRGB().Green(), 0.01f));
+  EXPECT_TRUE(IsNear(1.0f, aColor.GetRGB().Blue(), 0.01f));
+}
+
+// Test RGBA hex format
+TEST_F(Quantity_ColorRGBATest, HexColorParsing_RGBA)
+{
+  Quantity_ColorRGBA aColor;
+
+  // Test RGBA hex format: #RRGGBBAA
+  bool aResult = Quantity_ColorRGBA::ColorFromHex("#FF000080", aColor, false);
+  EXPECT_TRUE(aResult);
+  EXPECT_TRUE(IsNear(1.0f, aColor.GetRGB().Red(), 0.01f));
+  EXPECT_TRUE(IsNear(0.0f, aColor.GetRGB().Green(), 0.01f));
+  EXPECT_TRUE(IsNear(0.0f, aColor.GetRGB().Blue(), 0.01f));
+  EXPECT_TRUE(IsNear(0.5f, aColor.Alpha(), 0.02f)); // 0x80 / 0xFF ~= 0.5
+}
+
+// Test short hex format (tests HEX_BITS_PER_COMPONENT_SHORT constant)
+TEST_F(Quantity_ColorRGBATest, HexColorParsing_ShortRGB)
+{
+  Quantity_ColorRGBA aColor;
+
+  // Test short RGB hex format: #RGB (each component is 0-F)
+  bool aResult = Quantity_ColorRGBA::ColorFromHex("#F00", aColor, true);
+  EXPECT_TRUE(aResult);
+  EXPECT_TRUE(IsNear(1.0f, aColor.GetRGB().Red(), 0.1f));
+  EXPECT_TRUE(IsNear(0.0f, aColor.GetRGB().Green(), 0.1f));
+  EXPECT_TRUE(IsNear(0.0f, aColor.GetRGB().Blue(), 0.1f));
+
+  // Test short format for white
+  aResult = Quantity_ColorRGBA::ColorFromHex("#FFF", aColor, true);
+  EXPECT_TRUE(aResult);
+  EXPECT_TRUE(IsNear(1.0f, aColor.GetRGB().Red(), 0.1f));
+  EXPECT_TRUE(IsNear(1.0f, aColor.GetRGB().Green(), 0.1f));
+  EXPECT_TRUE(IsNear(1.0f, aColor.GetRGB().Blue(), 0.1f));
+}
+
+// Test short RGBA hex format
+TEST_F(Quantity_ColorRGBATest, HexColorParsing_ShortRGBA)
+{
+  Quantity_ColorRGBA aColor;
+
+  // Test short RGBA hex format: #RGBA
+  bool aResult = Quantity_ColorRGBA::ColorFromHex("#F008", aColor, false);
+  EXPECT_TRUE(aResult);
+  EXPECT_TRUE(IsNear(1.0f, aColor.GetRGB().Red(), 0.1f));
+  EXPECT_TRUE(IsNear(0.0f, aColor.GetRGB().Green(), 0.1f));
+  EXPECT_TRUE(IsNear(0.0f, aColor.GetRGB().Blue(), 0.1f));
+  EXPECT_TRUE(IsNear(0.5f, aColor.Alpha(), 0.1f)); // 0x8 / 0xF ~= 0.533
+}
+
+// Test invalid hex formats
+TEST_F(Quantity_ColorRGBATest, HexColorParsing_Invalid)
+{
+  Quantity_ColorRGBA aColor;
+
+  // Empty string
+  EXPECT_FALSE(Quantity_ColorRGBA::ColorFromHex("", aColor, true));
+
+  // Invalid length
+  EXPECT_FALSE(Quantity_ColorRGBA::ColorFromHex("#FFFF", aColor, true));
+
+  // Invalid characters
+  EXPECT_FALSE(Quantity_ColorRGBA::ColorFromHex("#GGGGGG", aColor, true));
+
+  // RGBA format when alpha is disabled
+  EXPECT_FALSE(Quantity_ColorRGBA::ColorFromHex("#FF0000FF", aColor, true));
+}
+
+// Test mixed case hex parsing
+TEST_F(Quantity_ColorRGBATest, HexColorParsing_MixedCase)
+{
+  Quantity_ColorRGBA aColor;
+
+  bool aResult = Quantity_ColorRGBA::ColorFromHex("#FfAa00", aColor, true);
+  EXPECT_TRUE(aResult);
+  EXPECT_TRUE(IsNear(1.0f, aColor.GetRGB().Red(), 0.01f));
+  // AA = 170/255 = 0.667 sRGB, converts to ~0.402 linear RGB
+  EXPECT_TRUE(IsNear(0.402f, aColor.GetRGB().Green(), 0.01f));
+  EXPECT_TRUE(IsNear(0.0f, aColor.GetRGB().Blue(), 0.01f));
+}
+
+// Test specific hex value from our constant migration (regression test)
+TEST_F(Quantity_ColorRGBATest, HexColorParsing_SpecificValues)
+{
+  Quantity_ColorRGBA aColor;
+
+  // Test a color that would use our HEX_BASE constant (16)
+  bool aResult = Quantity_ColorRGBA::ColorFromHex("#102030", aColor, true);
+  EXPECT_TRUE(aResult);
+
+  // Hex values are sRGB that get converted to linear RGB
+  // 0x10 = 16/255 = 0.0627 sRGB -> 0.00518 linear RGB
+  // 0x20 = 32/255 = 0.1255 sRGB -> 0.01444 linear RGB
+  // 0x30 = 48/255 = 0.1882 sRGB -> 0.02956 linear RGB
+  EXPECT_TRUE(IsNear(0.00518f, aColor.GetRGB().Red(), 0.0001f));
+  EXPECT_TRUE(IsNear(0.01444f, aColor.GetRGB().Green(), 0.0001f));
+  EXPECT_TRUE(IsNear(0.02956f, aColor.GetRGB().Blue(), 0.0001f));
+}
+
+// Test equality comparison
+TEST_F(Quantity_ColorRGBATest, EqualityComparison)
+{
+  Quantity_ColorRGBA aColor1(0.5f, 0.6f, 0.7f, 0.8f);
+  Quantity_ColorRGBA aColor2(0.5f, 0.6f, 0.7f, 0.8f);
+  Quantity_ColorRGBA aColor3(0.5f, 0.6f, 0.7f, 0.9f); // Different alpha
+
+  EXPECT_TRUE(aColor1.IsEqual(aColor2));
+  EXPECT_FALSE(aColor1.IsEqual(aColor3));
+}
+
+// Test GetRGB and ChangeRGB
+TEST_F(Quantity_ColorRGBATest, RGBAccess)
+{
+  Quantity_ColorRGBA aColor(0.2f, 0.4f, 0.6f, 0.8f);
+
+  const Quantity_Color& aRGB = aColor.GetRGB();
+  EXPECT_TRUE(IsNear(0.2f, aRGB.Red()));
+  EXPECT_TRUE(IsNear(0.4f, aRGB.Green()));
+  EXPECT_TRUE(IsNear(0.6f, aRGB.Blue()));
+
+  aColor.ChangeRGB().SetValues(0.3f, 0.5f, 0.7f, Quantity_TOC_RGB);
+  EXPECT_TRUE(IsNear(0.3f, aColor.GetRGB().Red()));
+  EXPECT_TRUE(IsNear(0.5f, aColor.GetRGB().Green()));
+  EXPECT_TRUE(IsNear(0.7f, aColor.GetRGB().Blue()));
+  EXPECT_TRUE(IsNear(0.8f, aColor.Alpha())); // Alpha unchanged
+}
+
+// Test SetAlpha
+TEST_F(Quantity_ColorRGBATest, SetAlpha)
+{
+  Quantity_ColorRGBA aColor(0.5f, 0.5f, 0.5f, 1.0f);
+  EXPECT_TRUE(IsNear(1.0f, aColor.Alpha()));
+
+  aColor.SetAlpha(0.5f);
+  EXPECT_TRUE(IsNear(0.5f, aColor.Alpha()));
+
+  // RGB should be unchanged
+  EXPECT_TRUE(IsNear(0.5f, aColor.GetRGB().Red()));
+  EXPECT_TRUE(IsNear(0.5f, aColor.GetRGB().Green()));
+  EXPECT_TRUE(IsNear(0.5f, aColor.GetRGB().Blue()));
+}
+
+// Test edge cases
+TEST_F(Quantity_ColorRGBATest, EdgeCases)
+{
+  // Fully transparent black
+  Quantity_ColorRGBA aTransparent(0.0f, 0.0f, 0.0f, 0.0f);
+  EXPECT_TRUE(IsNear(0.0f, aTransparent.Alpha()));
+
+  // Fully opaque white
+  Quantity_ColorRGBA aOpaque(1.0f, 1.0f, 1.0f, 1.0f);
+  EXPECT_TRUE(IsNear(1.0f, aOpaque.Alpha()));
+  EXPECT_TRUE(IsNear(1.0f, aOpaque.GetRGB().Red()));
+}
+
+// Test color component extraction in correct order (RGB_COMPONENT_LAST_INDEX)
+TEST_F(Quantity_ColorRGBATest, ComponentOrder)
+{
+  Quantity_ColorRGBA aColor;
+
+  // Parse a color where each component is distinct
+  bool aResult = Quantity_ColorRGBA::ColorFromHex("#123456", aColor, true);
+  EXPECT_TRUE(aResult);
+
+  // Verify the order is correct: R=0x12, G=0x34, B=0x56
+  // Hex values are sRGB that get converted to linear RGB
+  // 0x12 = 18/255 = 0.0706 sRGB -> 0.00605 linear RGB
+  // 0x34 = 52/255 = 0.2039 sRGB -> 0.03434 linear RGB
+  // 0x56 = 86/255 = 0.3373 sRGB -> 0.09306 linear RGB
+  float aExpectedR = 0.00605f;
+  float aExpectedG = 0.03434f;
+  float aExpectedB = 0.09306f;
+
+  EXPECT_TRUE(IsNear(aExpectedR, aColor.GetRGB().Red(), 0.0001f));
+  EXPECT_TRUE(IsNear(aExpectedG, aColor.GetRGB().Green(), 0.0001f));
+  EXPECT_TRUE(IsNear(aExpectedB, aColor.GetRGB().Blue(), 0.0001f));
+}
diff --git a/src/FoundationClasses/TKernel/GTests/Quantity_Color_Test.cxx b/src/FoundationClasses/TKernel/GTests/Quantity_Color_Test.cxx
new file mode 100644 (file)
index 0000000..2ddf33d
--- /dev/null
@@ -0,0 +1,293 @@
+// Copyright (c) 2025 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#include <Quantity_Color.hxx>
+#include <Quantity_NameOfColor.hxx>
+
+#include <gtest/gtest.h>
+#include <cmath>
+
+// Test fixture for Quantity_Color tests
+class Quantity_ColorTest : public testing::Test
+{
+protected:
+  void SetUp() override {}
+
+  void TearDown() override {}
+
+  // Helper to compare floating point values
+  bool IsNear(Standard_Real theValue1,
+              Standard_Real theValue2,
+              Standard_Real theTolerance = 0.001) const
+  {
+    return std::abs(theValue1 - theValue2) < theTolerance;
+  }
+};
+
+// Test basic construction
+TEST_F(Quantity_ColorTest, BasicConstruction)
+{
+  // Default constructor
+  Quantity_Color aColor1;
+  EXPECT_TRUE(IsNear(1.0, aColor1.Red()));   // YELLOW = RGB(1,1,0)
+  EXPECT_TRUE(IsNear(1.0, aColor1.Green())); // YELLOW = RGB(1,1,0)
+  EXPECT_TRUE(IsNear(0.0, aColor1.Blue()));  // YELLOW = RGB(1,1,0)
+
+  // RGB constructor
+  Quantity_Color aColor2(0.5, 0.6, 0.7, Quantity_TOC_RGB);
+  EXPECT_TRUE(IsNear(0.5, aColor2.Red()));
+  EXPECT_TRUE(IsNear(0.6, aColor2.Green()));
+  EXPECT_TRUE(IsNear(0.7, aColor2.Blue()));
+
+  // Named color constructor
+  Quantity_Color aColor3(Quantity_NOC_RED);
+  EXPECT_TRUE(IsNear(1.0, aColor3.Red()));
+  EXPECT_TRUE(IsNear(0.0, aColor3.Green()));
+  EXPECT_TRUE(IsNear(0.0, aColor3.Blue()));
+}
+
+// Test constexpr getters (compile-time evaluation capability)
+TEST_F(Quantity_ColorTest, ConstexprGetters)
+{
+  const Quantity_Color aColor(0.3, 0.5, 0.7, Quantity_TOC_RGB);
+
+  // These should work at compile-time with constexpr
+  Standard_Real aR = aColor.Red();
+  Standard_Real aG = aColor.Green();
+  Standard_Real aB = aColor.Blue();
+
+  EXPECT_TRUE(IsNear(0.3, aR));
+  EXPECT_TRUE(IsNear(0.5, aG));
+  EXPECT_TRUE(IsNear(0.7, aB));
+}
+
+// Test equality comparison (noexcept guarantee)
+TEST_F(Quantity_ColorTest, EqualityComparison)
+{
+  Quantity_Color aColor1(0.5, 0.6, 0.7, Quantity_TOC_RGB);
+  Quantity_Color aColor2(0.5, 0.6, 0.7, Quantity_TOC_RGB);
+  Quantity_Color aColor3(0.5, 0.6, 0.8, Quantity_TOC_RGB);
+
+  EXPECT_TRUE(aColor1.IsEqual(aColor2));
+  EXPECT_TRUE(aColor1 == aColor2);
+  EXPECT_FALSE(aColor1.IsDifferent(aColor2));
+  EXPECT_FALSE(aColor1 != aColor2);
+
+  EXPECT_FALSE(aColor1.IsEqual(aColor3));
+  EXPECT_FALSE(aColor1 == aColor3);
+  EXPECT_TRUE(aColor1.IsDifferent(aColor3));
+  EXPECT_TRUE(aColor1 != aColor3);
+}
+
+// Test distance calculation (noexcept guarantee)
+TEST_F(Quantity_ColorTest, DistanceCalculation)
+{
+  Quantity_Color aColor1(0.0, 0.0, 0.0, Quantity_TOC_RGB);
+  Quantity_Color aColor2(0.3, 0.4, 0.0, Quantity_TOC_RGB);
+
+  // Distance should be sqrt(0.3^2 + 0.4^2) = sqrt(0.09 + 0.16) = sqrt(0.25) = 0.5
+  Standard_Real aDist = aColor1.Distance(aColor2);
+  EXPECT_TRUE(IsNear(0.5, aDist));
+
+  Standard_Real aSquareDist = aColor1.SquareDistance(aColor2);
+  EXPECT_TRUE(IsNear(0.25, aSquareDist));
+}
+
+// Test sRGB to HLS conversion
+TEST_F(Quantity_ColorTest, RGB_to_HLS_Conversion)
+{
+  // Pure red in sRGB
+  Quantity_Color          aRed(Quantity_NOC_RED);
+  NCollection_Vec3<float> aHLS = Quantity_Color::Convert_sRGB_To_HLS(aRed.Rgb());
+
+  EXPECT_TRUE(IsNear(0.0, aHLS[0], 1.0)); // Hue for red should be ~0
+  EXPECT_TRUE(IsNear(1.0, aHLS[1]));      // Lightness should be 1 (max value)
+  EXPECT_TRUE(IsNear(1.0, aHLS[2]));      // Saturation should be 1 (fully saturated)
+
+  // Gray (no saturation)
+  Quantity_Color          aGray(0.5, 0.5, 0.5, Quantity_TOC_RGB);
+  NCollection_Vec3<float> aHLS_Gray = Quantity_Color::Convert_sRGB_To_HLS(aGray.Rgb());
+
+  EXPECT_TRUE(IsNear(0.5, aHLS_Gray[1])); // Lightness
+  EXPECT_TRUE(IsNear(0.0, aHLS_Gray[2])); // Saturation should be 0 for gray
+}
+
+// Test Linear RGB to CIE Lab conversion (uses new constexpr constants)
+TEST_F(Quantity_ColorTest, LinearRGB_to_Lab_Conversion)
+{
+  // White should convert to L=100, a=0, b=0 in Lab
+  Quantity_Color          aWhite(1.0, 1.0, 1.0, Quantity_TOC_RGB);
+  NCollection_Vec3<float> aLab = Quantity_Color::Convert_LinearRGB_To_Lab(aWhite.Rgb());
+
+  EXPECT_TRUE(IsNear(100.0, aLab[0], 1.0)); // L should be near 100
+  EXPECT_TRUE(IsNear(0.0, aLab[1], 5.0));   // a should be near 0
+  EXPECT_TRUE(IsNear(0.0, aLab[2], 5.0));   // b should be near 0
+
+  // Black should convert to L=0
+  Quantity_Color          aBlack(0.0, 0.0, 0.0, Quantity_TOC_RGB);
+  NCollection_Vec3<float> aLabBlack = Quantity_Color::Convert_LinearRGB_To_Lab(aBlack.Rgb());
+
+  EXPECT_TRUE(IsNear(0.0, aLabBlack[0], 1.0)); // L should be 0
+}
+
+// Test Lab to Lch conversion
+TEST_F(Quantity_ColorTest, Lab_to_Lch_Conversion)
+{
+  // Test with known Lab values
+  NCollection_Vec3<float> aLab(50.0f, 25.0f, 25.0f);
+  NCollection_Vec3<float> aLch = Quantity_Color::Convert_Lab_To_Lch(aLab);
+
+  EXPECT_TRUE(IsNear(50.0, aLch[0])); // L should be preserved
+
+  // C (chroma) should be sqrt(25^2 + 25^2) = sqrt(1250) ~= 35.36
+  EXPECT_TRUE(IsNear(35.36, aLch[1], 0.1));
+
+  // H (hue) should be atan2(25, 25) * 180/pi = 45 degrees
+  EXPECT_TRUE(IsNear(45.0, aLch[2], 1.0));
+}
+
+// Test Lch to Lab conversion (round-trip)
+TEST_F(Quantity_ColorTest, Lch_to_Lab_RoundTrip)
+{
+  NCollection_Vec3<float> aLab1(50.0f, 25.0f, 25.0f);
+  NCollection_Vec3<float> aLch  = Quantity_Color::Convert_Lab_To_Lch(aLab1);
+  NCollection_Vec3<float> aLab2 = Quantity_Color::Convert_Lch_To_Lab(aLch);
+
+  EXPECT_TRUE(IsNear(aLab1[0], aLab2[0], 0.01));
+  EXPECT_TRUE(IsNear(aLab1[1], aLab2[1], 0.01));
+  EXPECT_TRUE(IsNear(aLab1[2], aLab2[2], 0.01));
+}
+
+// Test Lab to RGB conversion (round-trip validation)
+TEST_F(Quantity_ColorTest, Lab_to_RGB_RoundTrip)
+{
+  Quantity_Color          aOriginal(0.5, 0.6, 0.7, Quantity_TOC_RGB);
+  NCollection_Vec3<float> aLab = Quantity_Color::Convert_LinearRGB_To_Lab(aOriginal.Rgb());
+  NCollection_Vec3<float> aRGB = Quantity_Color::Convert_Lab_To_LinearRGB(aLab);
+
+  EXPECT_TRUE(IsNear(aOriginal.Red(), aRGB[0], 0.01));
+  EXPECT_TRUE(IsNear(aOriginal.Green(), aRGB[1], 0.01));
+  EXPECT_TRUE(IsNear(aOriginal.Blue(), aRGB[2], 0.01));
+}
+
+// Test DeltaE2000 color difference (uses Epsilon() function - regression test for bug fix)
+TEST_F(Quantity_ColorTest, DeltaE2000_Calculation)
+{
+  // Same color should have DeltaE = 0
+  Quantity_Color aColor1(0.5, 0.6, 0.7, Quantity_TOC_RGB);
+  Quantity_Color aColor2(0.5, 0.6, 0.7, Quantity_TOC_RGB);
+
+  Standard_Real aDeltaE = aColor1.DeltaE2000(aColor2);
+  EXPECT_TRUE(IsNear(0.0, aDeltaE, 0.01));
+
+  // Different colors should have non-zero DeltaE
+  Quantity_Color aColor3(0.3, 0.4, 0.5, Quantity_TOC_RGB);
+  Standard_Real  aDeltaE2 = aColor1.DeltaE2000(aColor3);
+  EXPECT_GT(aDeltaE2, 0.0);
+}
+
+// Test named color conversion
+TEST_F(Quantity_ColorTest, NamedColors)
+{
+  // Test a few standard colors
+  Quantity_Color aRed(Quantity_NOC_RED);
+  EXPECT_TRUE(IsNear(1.0, aRed.Red()));
+  EXPECT_TRUE(IsNear(0.0, aRed.Green()));
+  EXPECT_TRUE(IsNear(0.0, aRed.Blue()));
+
+  Quantity_Color aGreen(Quantity_NOC_GREEN);
+  EXPECT_TRUE(IsNear(0.0, aGreen.Red()));
+  EXPECT_GT(aGreen.Green(), 0.5); // Green should be significant
+  EXPECT_TRUE(IsNear(0.0, aGreen.Blue()));
+
+  Quantity_Color aBlue(Quantity_NOC_BLUE);
+  EXPECT_TRUE(IsNear(0.0, aBlue.Red()));
+  EXPECT_TRUE(IsNear(0.0, aBlue.Green()));
+  EXPECT_TRUE(IsNear(1.0, aBlue.Blue()));
+}
+
+// Test SetValues and modification
+TEST_F(Quantity_ColorTest, SetValues)
+{
+  Quantity_Color aColor;
+
+  aColor.SetValues(0.2, 0.4, 0.6, Quantity_TOC_RGB);
+  EXPECT_TRUE(IsNear(0.2, aColor.Red()));
+  EXPECT_TRUE(IsNear(0.4, aColor.Green()));
+  EXPECT_TRUE(IsNear(0.6, aColor.Blue()));
+
+  aColor.SetValues(Quantity_NOC_YELLOW);
+  EXPECT_TRUE(IsNear(1.0, aColor.Red()));
+  EXPECT_TRUE(IsNear(1.0, aColor.Green()));
+  EXPECT_TRUE(IsNear(0.0, aColor.Blue()));
+}
+
+// Test HLS values extraction
+TEST_F(Quantity_ColorTest, HLS_Extraction)
+{
+  Quantity_Color aRed(Quantity_NOC_RED);
+
+  // For pure red, hue should be ~0, saturation should be 1, lightness should be 1
+  Standard_Real aHue   = aRed.Hue();
+  Standard_Real aLight = aRed.Light();
+  Standard_Real aSat   = aRed.Saturation();
+
+  EXPECT_TRUE(IsNear(0.0, aHue, 5.0) || IsNear(360.0, aHue, 5.0)); // Hue wraps around
+  EXPECT_TRUE(IsNear(1.0, aLight, 0.01));
+  EXPECT_TRUE(IsNear(1.0, aSat, 0.01));
+}
+
+// Test thread-safety of Epsilon getter/setter
+TEST_F(Quantity_ColorTest, EpsilonThreadSafety)
+{
+  Standard_Real aOriginalEpsilon = Quantity_Color::Epsilon();
+
+  // Set new epsilon
+  Quantity_Color::SetEpsilon(0.0002);
+  EXPECT_TRUE(IsNear(0.0002, Quantity_Color::Epsilon()));
+
+  // Restore original
+  Quantity_Color::SetEpsilon(aOriginalEpsilon);
+  EXPECT_TRUE(IsNear(aOriginalEpsilon, Quantity_Color::Epsilon()));
+}
+
+// Test color name string conversion
+TEST_F(Quantity_ColorTest, ColorNameString)
+{
+  Standard_CString aRedName = Quantity_Color::StringName(Quantity_NOC_RED);
+  EXPECT_STREQ("RED", aRedName);
+
+  Standard_CString aBlueName = Quantity_Color::StringName(Quantity_NOC_BLUE);
+  EXPECT_STREQ("BLUE", aBlueName);
+}
+
+// Test edge cases and boundary conditions
+TEST_F(Quantity_ColorTest, EdgeCases)
+{
+  // Test with zero values
+  Quantity_Color aBlack(0.0, 0.0, 0.0, Quantity_TOC_RGB);
+  EXPECT_TRUE(IsNear(0.0, aBlack.Red()));
+  EXPECT_TRUE(IsNear(0.0, aBlack.Green()));
+  EXPECT_TRUE(IsNear(0.0, aBlack.Blue()));
+
+  // Test with max values
+  Quantity_Color aWhite(1.0, 1.0, 1.0, Quantity_TOC_RGB);
+  EXPECT_TRUE(IsNear(1.0, aWhite.Red()));
+  EXPECT_TRUE(IsNear(1.0, aWhite.Green()));
+  EXPECT_TRUE(IsNear(1.0, aWhite.Blue()));
+
+  // Test equality with epsilon tolerance
+  Quantity_Color aColor1(0.5, 0.5, 0.5, Quantity_TOC_RGB);
+  Quantity_Color aColor2(0.50001, 0.50001, 0.50001, Quantity_TOC_RGB);
+  EXPECT_TRUE(aColor1.IsEqual(aColor2)); // Should be equal within epsilon
+}
diff --git a/src/FoundationClasses/TKernel/GTests/Quantity_Date_Test.cxx b/src/FoundationClasses/TKernel/GTests/Quantity_Date_Test.cxx
new file mode 100644 (file)
index 0000000..932d312
--- /dev/null
@@ -0,0 +1,409 @@
+// Copyright (c) 2025 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#include <Quantity_Date.hxx>
+#include <Quantity_Period.hxx>
+
+#include <gtest/gtest.h>
+
+// Test fixture for Quantity_Date tests
+class Quantity_DateTest : public testing::Test
+{
+protected:
+  void SetUp() override {}
+
+  void TearDown() override {}
+};
+
+// Test basic construction and default initialization
+TEST_F(Quantity_DateTest, BasicConstruction)
+{
+  // Default constructor creates January 1, 1979, 00:00:00
+  Quantity_Date    aDate1;
+  Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics;
+  aDate1.Values(mm, dd, yy, hh, mn, ss, mis, mics);
+
+  EXPECT_EQ(1, mm);    // January
+  EXPECT_EQ(1, dd);    // 1st
+  EXPECT_EQ(1979, yy); // 1979
+  EXPECT_EQ(0, hh);    // 00 hours
+  EXPECT_EQ(0, mn);    // 00 minutes
+  EXPECT_EQ(0, ss);    // 00 seconds
+
+  // Parameterized constructor
+  Quantity_Date aDate2(6, 15, 2025, 14, 30, 45, 123, 456);
+  aDate2.Values(mm, dd, yy, hh, mn, ss, mis, mics);
+
+  EXPECT_EQ(6, mm);
+  EXPECT_EQ(15, dd);
+  EXPECT_EQ(2025, yy);
+  EXPECT_EQ(14, hh);
+  EXPECT_EQ(30, mn);
+  EXPECT_EQ(45, ss);
+  EXPECT_EQ(123, mis);
+  EXPECT_EQ(456, mics);
+}
+
+// Test constexpr comparison operators
+TEST_F(Quantity_DateTest, ConstexprComparisons)
+{
+  Quantity_Date aDate1(1, 1, 2020, 0, 0, 0, 0, 0);
+  Quantity_Date aDate2(1, 1, 2020, 0, 0, 0, 0, 0);
+  Quantity_Date aDate3(1, 2, 2020, 0, 0, 0, 0, 0);
+
+  // Test IsEqual (constexpr)
+  EXPECT_TRUE(aDate1.IsEqual(aDate2));
+  EXPECT_FALSE(aDate1.IsEqual(aDate3));
+
+  // Test IsEarlier (constexpr)
+  EXPECT_TRUE(aDate1.IsEarlier(aDate3));
+  EXPECT_FALSE(aDate3.IsEarlier(aDate1));
+  EXPECT_FALSE(aDate1.IsEarlier(aDate2));
+
+  // Test IsLater (constexpr)
+  EXPECT_TRUE(aDate3.IsLater(aDate1));
+  EXPECT_FALSE(aDate1.IsLater(aDate3));
+  EXPECT_FALSE(aDate1.IsLater(aDate2));
+}
+
+// Test leap year detection (constexpr)
+TEST_F(Quantity_DateTest, LeapYearDetection)
+{
+  // Leap years
+  EXPECT_TRUE(Quantity_Date::IsLeap(2000)); // Divisible by 400
+  EXPECT_TRUE(Quantity_Date::IsLeap(2004)); // Divisible by 4, not by 100
+  EXPECT_TRUE(Quantity_Date::IsLeap(2020));
+
+  // Non-leap years
+  EXPECT_FALSE(Quantity_Date::IsLeap(1900)); // Divisible by 100 but not 400
+  EXPECT_FALSE(Quantity_Date::IsLeap(2001)); // Not divisible by 4
+  EXPECT_FALSE(Quantity_Date::IsLeap(2100)); // Divisible by 100 but not 400
+}
+
+// Test validation with leap years (thread-safety regression test)
+TEST_F(Quantity_DateTest, ValidationWithLeapYear)
+{
+  // Valid date in leap year (February 29)
+  EXPECT_TRUE(Quantity_Date::IsValid(2, 29, 2020, 0, 0, 0, 0, 0));
+
+  // Invalid date in non-leap year (February 29)
+  EXPECT_FALSE(Quantity_Date::IsValid(2, 29, 2019, 0, 0, 0, 0, 0));
+
+  // Valid February 28 in any year
+  EXPECT_TRUE(Quantity_Date::IsValid(2, 28, 2019, 0, 0, 0, 0, 0));
+  EXPECT_TRUE(Quantity_Date::IsValid(2, 28, 2020, 0, 0, 0, 0, 0));
+}
+
+// Test month boundary validation (uses constexpr getDaysInMonth)
+TEST_F(Quantity_DateTest, MonthBoundaries)
+{
+  // January has 31 days
+  EXPECT_TRUE(Quantity_Date::IsValid(1, 31, 2020, 0, 0, 0, 0, 0));
+  EXPECT_FALSE(Quantity_Date::IsValid(1, 32, 2020, 0, 0, 0, 0, 0));
+
+  // April has 30 days
+  EXPECT_TRUE(Quantity_Date::IsValid(4, 30, 2020, 0, 0, 0, 0, 0));
+  EXPECT_FALSE(Quantity_Date::IsValid(4, 31, 2020, 0, 0, 0, 0, 0));
+
+  // December has 31 days
+  EXPECT_TRUE(Quantity_Date::IsValid(12, 31, 2020, 0, 0, 0, 0, 0));
+  EXPECT_FALSE(Quantity_Date::IsValid(12, 32, 2020, 0, 0, 0, 0, 0));
+}
+
+// Test time validation (uses time constants)
+TEST_F(Quantity_DateTest, TimeValidation)
+{
+  // Valid times
+  EXPECT_TRUE(Quantity_Date::IsValid(1, 1, 2020, 23, 59, 59, 999, 999));
+  EXPECT_TRUE(Quantity_Date::IsValid(1, 1, 2020, 0, 0, 0, 0, 0));
+
+  // Invalid hours
+  EXPECT_FALSE(Quantity_Date::IsValid(1, 1, 2020, 24, 0, 0, 0, 0));
+  EXPECT_FALSE(Quantity_Date::IsValid(1, 1, 2020, -1, 0, 0, 0, 0));
+
+  // Invalid minutes
+  EXPECT_FALSE(Quantity_Date::IsValid(1, 1, 2020, 0, 60, 0, 0, 0));
+  EXPECT_FALSE(Quantity_Date::IsValid(1, 1, 2020, 0, -1, 0, 0, 0));
+
+  // Invalid seconds
+  EXPECT_FALSE(Quantity_Date::IsValid(1, 1, 2020, 0, 0, 60, 0, 0));
+  EXPECT_FALSE(Quantity_Date::IsValid(1, 1, 2020, 0, 0, -1, 0, 0));
+
+  // Invalid milliseconds
+  EXPECT_FALSE(Quantity_Date::IsValid(1, 1, 2020, 0, 0, 0, 1000, 0));
+  EXPECT_FALSE(Quantity_Date::IsValid(1, 1, 2020, 0, 0, 0, -1, 0));
+
+  // Invalid microseconds
+  EXPECT_FALSE(Quantity_Date::IsValid(1, 1, 2020, 0, 0, 0, 0, 1000));
+  EXPECT_FALSE(Quantity_Date::IsValid(1, 1, 2020, 0, 0, 0, 0, -1));
+}
+
+// Test SetValues and Values round-trip (tests SECONDS_PER_* constants)
+TEST_F(Quantity_DateTest, SetValuesRoundTrip)
+{
+  Quantity_Date aDate;
+  aDate.SetValues(3, 15, 2021, 10, 25, 30, 500, 750);
+
+  Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics;
+  aDate.Values(mm, dd, yy, hh, mn, ss, mis, mics);
+
+  EXPECT_EQ(3, mm);
+  EXPECT_EQ(15, dd);
+  EXPECT_EQ(2021, yy);
+  EXPECT_EQ(10, hh);
+  EXPECT_EQ(25, mn);
+  EXPECT_EQ(30, ss);
+  EXPECT_EQ(500, mis);
+  EXPECT_EQ(750, mics);
+}
+
+// Test individual component getters
+TEST_F(Quantity_DateTest, IndividualGetters)
+{
+  // Test with a simple date first: January 2, 1979
+  Quantity_Date aDate1(1, 2, 1979, 0, 0, 0, 0, 0);
+  EXPECT_EQ(1, aDate1.Month());
+  EXPECT_EQ(2, aDate1.Day());
+  EXPECT_EQ(1979, aDate1.Year());
+
+  // Test with July 20, 2024 at midnight
+  Quantity_Date aDate2(7, 20, 2024, 0, 0, 0, 0, 0);
+  EXPECT_EQ(7, aDate2.Month());
+  EXPECT_EQ(20, aDate2.Day());
+  EXPECT_EQ(2024, aDate2.Year());
+
+  // Test with July 20, 2024 with time components
+  Quantity_Date aDate3(7, 20, 2024, 15, 45, 30, 123, 456);
+  EXPECT_EQ(7, aDate3.Month());
+  EXPECT_EQ(20, aDate3.Day());
+  EXPECT_EQ(2024, aDate3.Year());
+  EXPECT_EQ(15, aDate3.Hour());
+  EXPECT_EQ(45, aDate3.Minute());
+  EXPECT_EQ(30, aDate3.Second());
+  EXPECT_EQ(123, aDate3.MilliSecond());
+  EXPECT_EQ(456, aDate3.MicroSecond());
+}
+
+// Test date difference calculation (uses USECS_PER_SEC constant)
+TEST_F(Quantity_DateTest, DateDifference)
+{
+  Quantity_Date aDate1(1, 1, 2020, 0, 0, 0, 0, 0);
+  Quantity_Date aDate2(1, 1, 2020, 1, 0, 0, 0, 0); // 1 hour later
+
+  Quantity_Period aPeriod = aDate2.Difference(aDate1);
+
+  Standard_Integer ss, mics;
+  aPeriod.Values(ss, mics);
+
+  EXPECT_EQ(3600, ss); // 1 hour = 3600 seconds (SECONDS_PER_HOUR)
+  EXPECT_EQ(0, mics);
+}
+
+// Test adding period to date (uses USECS_PER_SEC constant)
+TEST_F(Quantity_DateTest, AddPeriod)
+{
+  Quantity_Date   aDate(1, 1, 2020, 0, 0, 0, 0, 0);
+  Quantity_Period aPeriod(1, 0, 0, 0, 0, 0); // 1 day
+
+  Quantity_Date aNewDate = aDate.Add(aPeriod);
+
+  Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics;
+  aNewDate.Values(mm, dd, yy, hh, mn, ss, mis, mics);
+
+  EXPECT_EQ(1, mm);
+  EXPECT_EQ(2, dd); // Next day
+  EXPECT_EQ(2020, yy);
+}
+
+// Test subtracting period from date
+TEST_F(Quantity_DateTest, SubtractPeriod)
+{
+  Quantity_Date   aDate(1, 2, 2020, 0, 0, 0, 0, 0); // January 2
+  Quantity_Period aPeriod(1, 0, 0, 0, 0, 0);        // 1 day
+
+  Quantity_Date aNewDate = aDate.Subtract(aPeriod);
+
+  Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics;
+  aNewDate.Values(mm, dd, yy, hh, mn, ss, mis, mics);
+
+  EXPECT_EQ(1, mm);
+  EXPECT_EQ(1, dd); // Previous day
+  EXPECT_EQ(2020, yy);
+}
+
+// Test year boundary crossing
+TEST_F(Quantity_DateTest, YearBoundary)
+{
+  Quantity_Date   aDate(12, 31, 2020, 23, 59, 59, 0, 0);
+  Quantity_Period aPeriod(0, 0, 0, 1, 0, 0); // 1 second
+
+  Quantity_Date aNewDate = aDate.Add(aPeriod);
+
+  Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics;
+  aNewDate.Values(mm, dd, yy, hh, mn, ss, mis, mics);
+
+  EXPECT_EQ(1, mm);    // January
+  EXPECT_EQ(1, dd);    // 1st
+  EXPECT_EQ(2021, yy); // Next year
+  EXPECT_EQ(0, hh);
+  EXPECT_EQ(0, mn);
+  EXPECT_EQ(0, ss);
+}
+
+// Test leap year boundary (Feb 28/29)
+TEST_F(Quantity_DateTest, LeapYearBoundary)
+{
+  // Leap year: Feb 28 + 1 day = Feb 29
+  Quantity_Date   aDate1(2, 28, 2020, 0, 0, 0, 0, 0);
+  Quantity_Period aPeriod1(1, 0, 0, 0, 0, 0);
+  Quantity_Date   aNewDate1 = aDate1.Add(aPeriod1);
+
+  Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics;
+  aNewDate1.Values(mm, dd, yy, hh, mn, ss, mis, mics);
+
+  EXPECT_EQ(2, mm);
+  EXPECT_EQ(29, dd); // Feb 29 exists in 2020
+  EXPECT_EQ(2020, yy);
+
+  // Non-leap year: Feb 28 + 1 day = Mar 1
+  Quantity_Date   aDate2(2, 28, 2021, 0, 0, 0, 0, 0);
+  Quantity_Period aPeriod2(1, 0, 0, 0, 0, 0);
+  Quantity_Date   aNewDate2 = aDate2.Add(aPeriod2);
+
+  aNewDate2.Values(mm, dd, yy, hh, mn, ss, mis, mics);
+
+  EXPECT_EQ(3, mm); // March
+  EXPECT_EQ(1, dd); // 1st
+  EXPECT_EQ(2021, yy);
+}
+
+// Test microsecond overflow (uses USECS_PER_SEC constant)
+TEST_F(Quantity_DateTest, MicrosecondOverflow)
+{
+  Quantity_Date   aDate(1, 1, 2020, 0, 0, 0, 999, 999);
+  Quantity_Period aPeriod(0, 0, 0, 0, 0, 1); // 1 microsecond
+
+  Quantity_Date aNewDate = aDate.Add(aPeriod);
+
+  Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics;
+  aNewDate.Values(mm, dd, yy, hh, mn, ss, mis, mics);
+
+  EXPECT_EQ(1, mm);
+  EXPECT_EQ(1, dd);
+  EXPECT_EQ(2020, yy);
+  EXPECT_EQ(0, hh);
+  EXPECT_EQ(0, mn);
+  EXPECT_EQ(1, ss); // Should overflow to next second
+  EXPECT_EQ(0, mis);
+  EXPECT_EQ(0, mics);
+}
+
+// Test specific date calculations (regression test for time constants)
+TEST_F(Quantity_DateTest, SpecificDateCalculations)
+{
+  // Test that 24 hours = 1 day (SECONDS_PER_DAY = 86400)
+  Quantity_Date   aDate1(1, 1, 2020, 0, 0, 0, 0, 0);
+  Quantity_Period aPeriod24h(0, 24, 0, 0, 0, 0); // 24 hours
+
+  Quantity_Date aDate2 = aDate1.Add(aPeriod24h);
+
+  Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics;
+  aDate2.Values(mm, dd, yy, hh, mn, ss, mis, mics);
+
+  EXPECT_EQ(1, mm);
+  EXPECT_EQ(2, dd); // Next day
+  EXPECT_EQ(2020, yy);
+  EXPECT_EQ(0, hh);
+}
+
+// Test minimum date (January 1, 1979)
+TEST_F(Quantity_DateTest, MinimumDate)
+{
+  EXPECT_TRUE(Quantity_Date::IsValid(1, 1, 1979, 0, 0, 0, 0, 0));
+  EXPECT_FALSE(Quantity_Date::IsValid(12, 31, 1978, 23, 59, 59, 999, 999));
+}
+
+// Test thread-safety of getDaysInMonth (implicit test)
+TEST_F(Quantity_DateTest, MultipleLeapYearChecks)
+{
+  // Repeatedly check different months and years
+  // This would catch thread-safety issues with mutable month_table
+  for (int year = 2000; year <= 2024; ++year)
+  {
+    for (int month = 1; month <= 12; ++month)
+    {
+      int maxDay;
+      if (month == 2)
+      {
+        maxDay = Quantity_Date::IsLeap(year) ? 29 : 28;
+      }
+      else if (month == 4 || month == 6 || month == 9 || month == 11)
+      {
+        maxDay = 30;
+      }
+      else
+      {
+        maxDay = 31;
+      }
+
+      EXPECT_TRUE(Quantity_Date::IsValid(month, maxDay, year, 0, 0, 0, 0, 0));
+      EXPECT_FALSE(Quantity_Date::IsValid(month, maxDay + 1, year, 0, 0, 0, 0, 0));
+    }
+  }
+}
+
+// Test date difference with epoch (Jan 1, 1979 00:00) - special case
+TEST_F(Quantity_DateTest, DifferenceFromEpoch)
+{
+  Quantity_Date aEpoch;                           // Defaults to Jan 1, 1979 00:00
+  Quantity_Date aDate(1, 2, 1979, 0, 0, 0, 0, 0); // Jan 2, 1979
+
+  Quantity_Period aPeriod = aEpoch.Difference(aDate);
+
+  Standard_Integer ss, mics;
+  aPeriod.Values(ss, mics);
+
+  EXPECT_EQ(86400, ss); // 1 day = 86400 seconds
+  EXPECT_EQ(0, mics);
+}
+
+// Test date difference with microsecond underflow
+TEST_F(Quantity_DateTest, DifferenceWithMicrosecondUnderflow)
+{
+  Quantity_Date aDate1(1, 1, 2020, 0, 0, 1, 200, 0); // 1.2 seconds
+  Quantity_Date aDate2(1, 1, 2020, 0, 0, 0, 500, 0); // 0.5 seconds
+
+  Quantity_Period aPeriod = aDate1.Difference(aDate2);
+
+  Standard_Integer ss, mics;
+  aPeriod.Values(ss, mics);
+
+  EXPECT_EQ(0, ss);
+  EXPECT_EQ(700000, mics); // 1.2 - 0.5 = 0.7 seconds = 700000 microseconds
+}
+
+// Test date difference with reversed dates (absolute value)
+TEST_F(Quantity_DateTest, DifferenceReversed)
+{
+  Quantity_Date aDate1(1, 1, 2020, 1, 0, 0, 0, 0); // 1 hour
+  Quantity_Date aDate2(1, 1, 2020, 3, 0, 0, 0, 0); // 3 hours
+
+  // Difference should be absolute value
+  Quantity_Period aPeriod = aDate1.Difference(aDate2);
+
+  Standard_Integer ss, mics;
+  aPeriod.Values(ss, mics);
+
+  EXPECT_EQ(7200, ss); // 2 hours = 7200 seconds
+  EXPECT_EQ(0, mics);
+}
diff --git a/src/FoundationClasses/TKernel/GTests/Quantity_Period_Test.cxx b/src/FoundationClasses/TKernel/GTests/Quantity_Period_Test.cxx
new file mode 100644 (file)
index 0000000..d68a2d3
--- /dev/null
@@ -0,0 +1,393 @@
+// Copyright (c) 2025 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#include <Quantity_Period.hxx>
+
+#include <gtest/gtest.h>
+
+// Test fixture for Quantity_Period tests
+class Quantity_PeriodTest : public testing::Test
+{
+protected:
+  void SetUp() override {}
+
+  void TearDown() override {}
+};
+
+// Test basic construction
+TEST_F(Quantity_PeriodTest, BasicConstruction)
+{
+  // Constructor with days, hours, minutes, seconds, milliseconds, microseconds
+  Quantity_Period aPeriod1(1, 2, 3, 4, 5, 6);
+
+  Standard_Integer dd, hh, mn, ss, mis, mics;
+  aPeriod1.Values(dd, hh, mn, ss, mis, mics);
+
+  EXPECT_EQ(1, dd);
+  EXPECT_EQ(2, hh);
+  EXPECT_EQ(3, mn);
+  EXPECT_EQ(4, ss);
+  EXPECT_EQ(5, mis);
+  EXPECT_EQ(6, mics);
+
+  // Constructor with seconds and microseconds
+  Quantity_Period aPeriod2(3600, 500000); // 1 hour, 500000 microseconds
+
+  Standard_Integer ss2, mics2;
+  aPeriod2.Values(ss2, mics2);
+
+  EXPECT_EQ(3600, ss2); // SECONDS_PER_HOUR
+  EXPECT_EQ(500000, mics2);
+}
+
+// Test constexpr comparison operators
+TEST_F(Quantity_PeriodTest, ConstexprComparisons)
+{
+  Quantity_Period aPeriod1(0, 0, 0, 100, 0, 0);
+  Quantity_Period aPeriod2(0, 0, 0, 100, 0, 0);
+  Quantity_Period aPeriod3(0, 0, 0, 200, 0, 0);
+
+  // Test IsEqual (constexpr)
+  EXPECT_TRUE(aPeriod1.IsEqual(aPeriod2));
+  EXPECT_FALSE(aPeriod1.IsEqual(aPeriod3));
+
+  // Test IsShorter (constexpr)
+  EXPECT_TRUE(aPeriod1.IsShorter(aPeriod3));
+  EXPECT_FALSE(aPeriod3.IsShorter(aPeriod1));
+  EXPECT_FALSE(aPeriod1.IsShorter(aPeriod2));
+
+  // Test IsLonger (constexpr)
+  EXPECT_TRUE(aPeriod3.IsLonger(aPeriod1));
+  EXPECT_FALSE(aPeriod1.IsLonger(aPeriod3));
+  EXPECT_FALSE(aPeriod1.IsLonger(aPeriod2));
+}
+
+// Test validation
+TEST_F(Quantity_PeriodTest, Validation)
+{
+  // Valid periods
+  EXPECT_TRUE(Quantity_Period::IsValid(1, 2, 3, 4, 5, 6));
+  EXPECT_TRUE(Quantity_Period::IsValid(0, 0, 0, 0, 0, 0));
+
+  // Invalid periods (negative values)
+  EXPECT_FALSE(Quantity_Period::IsValid(-1, 0, 0, 0, 0, 0));
+  EXPECT_FALSE(Quantity_Period::IsValid(0, -1, 0, 0, 0, 0));
+  EXPECT_FALSE(Quantity_Period::IsValid(0, 0, -1, 0, 0, 0));
+  EXPECT_FALSE(Quantity_Period::IsValid(0, 0, 0, -1, 0, 0));
+  EXPECT_FALSE(Quantity_Period::IsValid(0, 0, 0, 0, -1, 0));
+  EXPECT_FALSE(Quantity_Period::IsValid(0, 0, 0, 0, 0, -1));
+
+  // Validation with seconds and microseconds only
+  EXPECT_TRUE(Quantity_Period::IsValid(100, 500));
+  EXPECT_FALSE(Quantity_Period::IsValid(-1, 500));
+  EXPECT_FALSE(Quantity_Period::IsValid(100, -1));
+}
+
+// Test SetValues and Values round-trip (tests time constants)
+TEST_F(Quantity_PeriodTest, SetValuesRoundTrip)
+{
+  Quantity_Period aPeriod(0, 0); // Initialize with zero period
+  aPeriod.SetValues(2, 3, 4, 5, 6, 7);
+
+  Standard_Integer dd, hh, mn, ss, mis, mics;
+  aPeriod.Values(dd, hh, mn, ss, mis, mics);
+
+  EXPECT_EQ(2, dd);
+  EXPECT_EQ(3, hh);
+  EXPECT_EQ(4, mn);
+  EXPECT_EQ(5, ss);
+  EXPECT_EQ(6, mis);
+  EXPECT_EQ(7, mics);
+}
+
+// Test conversion between formats (uses SECONDS_PER_DAY, etc.)
+TEST_F(Quantity_PeriodTest, FormatConversion)
+{
+  // 1 day should equal 86400 seconds (SECONDS_PER_DAY)
+  Quantity_Period aPeriod1(1, 0, 0, 0, 0, 0);
+
+  Standard_Integer ss, mics;
+  aPeriod1.Values(ss, mics);
+
+  EXPECT_EQ(86400, ss); // SECONDS_PER_DAY = 24 * 3600
+
+  // 1 hour should equal 3600 seconds (SECONDS_PER_HOUR)
+  Quantity_Period aPeriod2(0, 1, 0, 0, 0, 0);
+  aPeriod2.Values(ss, mics);
+
+  EXPECT_EQ(3600, ss); // SECONDS_PER_HOUR
+
+  // 1 minute should equal 60 seconds (SECONDS_PER_MINUTE)
+  Quantity_Period aPeriod3(0, 0, 1, 0, 0, 0);
+  aPeriod3.Values(ss, mics);
+
+  EXPECT_EQ(60, ss); // SECONDS_PER_MINUTE
+}
+
+// Test millisecond to microsecond conversion (uses USECS_PER_MSEC)
+TEST_F(Quantity_PeriodTest, MillisecondConversion)
+{
+  // 1 millisecond = 1000 microseconds (USECS_PER_MSEC)
+  Quantity_Period aPeriod(0, 0, 0, 0, 1, 0);
+
+  Standard_Integer ss, mics;
+  aPeriod.Values(ss, mics);
+
+  EXPECT_EQ(0, ss);
+  EXPECT_EQ(1000, mics); // USECS_PER_MSEC
+
+  // Mixed milliseconds and microseconds
+  Quantity_Period aPeriod2(0, 0, 0, 0, 5, 250);
+  aPeriod2.Values(ss, mics);
+
+  EXPECT_EQ(0, ss);
+  EXPECT_EQ(5250, mics); // 5 * 1000 + 250
+}
+
+// Test microsecond overflow (uses USECS_PER_SEC constant)
+TEST_F(Quantity_PeriodTest, MicrosecondOverflow)
+{
+  // Create period with > 1,000,000 microseconds
+  Quantity_Period aPeriod(0, 0); // Initialize with zero period
+  aPeriod.SetValues(0, 1500000); // 1.5 seconds in microseconds
+
+  Standard_Integer ss, mics;
+  aPeriod.Values(ss, mics);
+
+  EXPECT_EQ(1, ss);        // 1 second
+  EXPECT_EQ(500000, mics); // 0.5 second = 500000 microseconds
+}
+
+// Test adding periods (uses USECS_PER_SEC constant)
+TEST_F(Quantity_PeriodTest, AddPeriods)
+{
+  Quantity_Period aPeriod1(0, 1, 0, 0, 0, 0); // 1 hour
+  Quantity_Period aPeriod2(0, 2, 0, 0, 0, 0); // 2 hours
+
+  Quantity_Period aResult = aPeriod1.Add(aPeriod2);
+
+  Standard_Integer ss, mics;
+  aResult.Values(ss, mics);
+
+  EXPECT_EQ(10800, ss); // 3 hours = 3 * 3600 = 10800 seconds
+  EXPECT_EQ(0, mics);
+}
+
+// Test adding with microsecond overflow
+TEST_F(Quantity_PeriodTest, AddWithMicrosecondOverflow)
+{
+  Quantity_Period aPeriod1(0, 0, 0, 0, 0, 600000); // 0.6 seconds
+  Quantity_Period aPeriod2(0, 0, 0, 0, 0, 600000); // 0.6 seconds
+
+  Quantity_Period aResult = aPeriod1.Add(aPeriod2);
+
+  Standard_Integer ss, mics;
+  aResult.Values(ss, mics);
+
+  EXPECT_EQ(1, ss);        // Should overflow to 1 second
+  EXPECT_EQ(200000, mics); // Remaining 0.2 seconds
+}
+
+// Test subtracting periods (uses USECS_PER_SEC constant)
+TEST_F(Quantity_PeriodTest, SubtractPeriods)
+{
+  Quantity_Period aPeriod1(0, 3, 0, 0, 0, 0); // 3 hours
+  Quantity_Period aPeriod2(0, 1, 0, 0, 0, 0); // 1 hour
+
+  Quantity_Period aResult = aPeriod1.Subtract(aPeriod2);
+
+  Standard_Integer ss, mics;
+  aResult.Values(ss, mics);
+
+  EXPECT_EQ(7200, ss); // 2 hours = 2 * 3600
+  EXPECT_EQ(0, mics);
+}
+
+// Test subtracting with microsecond underflow
+TEST_F(Quantity_PeriodTest, SubtractWithMicrosecondUnderflow)
+{
+  Quantity_Period aPeriod1(0, 0, 0, 1, 0, 200000); // 1.2 seconds
+  Quantity_Period aPeriod2(0, 0, 0, 0, 0, 500000); // 0.5 seconds
+
+  Quantity_Period aResult = aPeriod1.Subtract(aPeriod2);
+
+  Standard_Integer ss, mics;
+  aResult.Values(ss, mics);
+
+  EXPECT_EQ(0, ss);
+  EXPECT_EQ(700000, mics); // 1.2 - 0.5 = 0.7 seconds
+}
+
+// Test subtracting larger from smaller (absolute value)
+TEST_F(Quantity_PeriodTest, SubtractNegative)
+{
+  Quantity_Period aPeriod1(0, 1, 0, 0, 0, 0); // 1 hour
+  Quantity_Period aPeriod2(0, 3, 0, 0, 0, 0); // 3 hours
+
+  Quantity_Period aResult = aPeriod1.Subtract(aPeriod2);
+
+  Standard_Integer ss, mics;
+  aResult.Values(ss, mics);
+
+  // Result should be absolute value (2 hours)
+  EXPECT_EQ(7200, ss); // 2 * 3600
+  EXPECT_EQ(0, mics);
+}
+
+// Test complex period calculations
+TEST_F(Quantity_PeriodTest, ComplexCalculations)
+{
+  // 1 day, 2 hours, 30 minutes, 45 seconds, 500 milliseconds, 250 microseconds
+  Quantity_Period aPeriod(1, 2, 30, 45, 500, 250);
+
+  Standard_Integer ss, mics;
+  aPeriod.Values(ss, mics);
+
+  // Calculate expected seconds:
+  // 1 day = 86400 seconds
+  // 2 hours = 7200 seconds
+  // 30 minutes = 1800 seconds
+  // 45 seconds = 45 seconds
+  // Total = 95445 seconds
+  EXPECT_EQ(95445, ss);
+
+  // Calculate expected microseconds:
+  // 500 milliseconds = 500000 microseconds
+  // 250 microseconds = 250 microseconds
+  // Total = 500250 microseconds
+  EXPECT_EQ(500250, mics);
+}
+
+// Test extraction back to components (uses all time constants)
+TEST_F(Quantity_PeriodTest, ComponentExtraction)
+{
+  Quantity_Period aPeriod(2, 3, 45, 30, 123, 456);
+
+  Standard_Integer dd, hh, mn, ss, mis, mics;
+  aPeriod.Values(dd, hh, mn, ss, mis, mics);
+
+  EXPECT_EQ(2, dd);
+  EXPECT_EQ(3, hh);
+  EXPECT_EQ(45, mn);
+  EXPECT_EQ(30, ss);
+  EXPECT_EQ(123, mis);
+  EXPECT_EQ(456, mics);
+}
+
+// Test zero period
+TEST_F(Quantity_PeriodTest, ZeroPeriod)
+{
+  Quantity_Period aPeriod(0, 0, 0, 0, 0, 0);
+
+  Standard_Integer dd, hh, mn, ss, mis, mics;
+  aPeriod.Values(dd, hh, mn, ss, mis, mics);
+
+  EXPECT_EQ(0, dd);
+  EXPECT_EQ(0, hh);
+  EXPECT_EQ(0, mn);
+  EXPECT_EQ(0, ss);
+  EXPECT_EQ(0, mis);
+  EXPECT_EQ(0, mics);
+
+  // Zero period should be equal to itself
+  Quantity_Period aZero2(0, 0, 0, 0, 0, 0);
+  EXPECT_TRUE(aPeriod.IsEqual(aZero2));
+}
+
+// Test large values (stress test for constants)
+TEST_F(Quantity_PeriodTest, LargeValues)
+{
+  // 100 days
+  Quantity_Period aPeriod(100, 0, 0, 0, 0, 0);
+
+  Standard_Integer ss, mics;
+  aPeriod.Values(ss, mics);
+
+  EXPECT_EQ(8640000, ss); // 100 * 86400
+  EXPECT_EQ(0, mics);
+}
+
+// Test specific time constant values (regression test)
+TEST_F(Quantity_PeriodTest, TimeConstantValues)
+{
+  // Test that 24 hours = 1 day
+  Quantity_Period aPeriod24h(0, 24, 0, 0, 0, 0);
+  Quantity_Period aPeriod1d(1, 0, 0, 0, 0, 0);
+
+  Standard_Integer ss1, mics1, ss2, mics2;
+  aPeriod24h.Values(ss1, mics1);
+  aPeriod1d.Values(ss2, mics2);
+
+  EXPECT_EQ(ss1, ss2); // Should both be 86400 seconds
+
+  // Test that 60 seconds = 1 minute
+  Quantity_Period aPeriod60s(0, 0, 0, 60, 0, 0);
+  Quantity_Period aPeriod1m(0, 0, 1, 0, 0, 0);
+
+  aPeriod60s.Values(ss1, mics1);
+  aPeriod1m.Values(ss2, mics2);
+
+  EXPECT_EQ(ss1, ss2); // Should both be 60 seconds
+
+  // Test that 60 minutes = 1 hour
+  Quantity_Period aPeriod60m(0, 0, 60, 0, 0, 0);
+  Quantity_Period aPeriod1h(0, 1, 0, 0, 0, 0);
+
+  aPeriod60m.Values(ss1, mics1);
+  aPeriod1h.Values(ss2, mics2);
+
+  EXPECT_EQ(ss1, ss2); // Should both be 3600 seconds
+}
+
+// Test that 1000 milliseconds = 1000000 microseconds = 1 second
+TEST_F(Quantity_PeriodTest, MillisecondToSecondConversion)
+{
+  Quantity_Period aPeriod(0, 0, 0, 0, 1000, 0);
+
+  Standard_Integer ss, mics;
+  aPeriod.Values(ss, mics);
+
+  EXPECT_EQ(1, ss);   // Should overflow to 1 second
+  EXPECT_EQ(0, mics); // No remaining microseconds
+}
+
+// Test period subtraction with large microsecond underflow (verifies O(1) optimization)
+TEST_F(Quantity_PeriodTest, SubtractLargeMicrosecondUnderflow)
+{
+  Quantity_Period aPeriod1(0, 0, 0, 10, 0, 200000); // 10.2 seconds
+  Quantity_Period aPeriod2(0, 0, 0, 0, 0, 2700000); // 2.7 seconds
+
+  Quantity_Period aResult = aPeriod1.Subtract(aPeriod2);
+
+  Standard_Integer ss, mics;
+  aResult.Values(ss, mics);
+
+  EXPECT_EQ(7, ss); // 10.2 - 2.7 = 7.5 seconds
+  EXPECT_EQ(500000, mics);
+}
+
+// Test period subtraction with negative seconds and positive microseconds
+TEST_F(Quantity_PeriodTest, SubtractNegativeWithMicroseconds)
+{
+  Quantity_Period aPeriod1(0, 0, 0, 5, 0, 300000); // 5.3 seconds
+  Quantity_Period aPeriod2(0, 0, 0, 8, 0, 100000); // 8.1 seconds
+
+  Quantity_Period aResult = aPeriod1.Subtract(aPeriod2);
+
+  Standard_Integer ss, mics;
+  aResult.Values(ss, mics);
+
+  // Absolute value: 8.1 - 5.3 = 2.8 seconds
+  EXPECT_EQ(2, ss);
+  EXPECT_EQ(800000, mics);
+}
index 60a948a6eb7eb3abff3e6db2337f984652dc734c..fde68e2cd074e23a44517d84049dac9aabb4e974 100644 (file)
 #include <Standard_Dump.hxx>
 #include <TCollection_AsciiString.hxx>
 
-#define RGBHLS_H_UNDEFINED -1.0
+namespace
+{
+static constexpr float  RGBHLS_H_UNDEFINED = -1.0f;
+static constexpr double DEG_TO_RAD         = M_PI / 180.0;
+static constexpr double RAD_TO_DEG         = 180.0 / M_PI;
+static constexpr double POW_25_7           = 6103515625.0;         // 25^7 used in CIEDE2000
+static constexpr double CIELAB_EPSILON     = 0.008856451679035631; // (6/29)^3
+static constexpr double CIELAB_KAPPA       = 7.787037037037037;    // (1/3) * (29/6)^2
+static constexpr double CIELAB_OFFSET      = 16.0 / 116.0;
+static constexpr double CIELAB_L_COEFF     = 116.0;
+static constexpr double CIELAB_A_COEFF     = 500.0;
+static constexpr double CIELAB_B_COEFF     = 200.0;
+static constexpr double CIELAB_L_OFFSET    = 16.0;
+// D65 / 2 deg (CIE 1931) standard illuminant reference white point
+static constexpr double D65_REF_X = 95.047;
+static constexpr double D65_REF_Y = 100.000;
+static constexpr double D65_REF_Z = 108.883;
+// sRGB to XYZ conversion matrix (D65 illuminant, 2 deg observer)
+static constexpr double RGB_TO_XYZ_R_X = 0.4124564;
+static constexpr double RGB_TO_XYZ_R_Y = 0.2126729;
+static constexpr double RGB_TO_XYZ_R_Z = 0.0193339;
+static constexpr double RGB_TO_XYZ_G_X = 0.3575761;
+static constexpr double RGB_TO_XYZ_G_Y = 0.7151522;
+static constexpr double RGB_TO_XYZ_G_Z = 0.1191920;
+static constexpr double RGB_TO_XYZ_B_X = 0.1804375;
+static constexpr double RGB_TO_XYZ_B_Y = 0.0721750;
+static constexpr double RGB_TO_XYZ_B_Z = 0.9503041;
+// XYZ to sRGB conversion matrix (D65 illuminant, 2 deg observer)
+static constexpr double XYZ_TO_RGB_X_R = 3.2404542;
+static constexpr double XYZ_TO_RGB_X_G = -0.9692660;
+static constexpr double XYZ_TO_RGB_X_B = 0.0556434;
+static constexpr double XYZ_TO_RGB_Y_R = -1.5371385;
+static constexpr double XYZ_TO_RGB_Y_G = 1.8760108;
+static constexpr double XYZ_TO_RGB_Y_B = -0.2040259;
+static constexpr double XYZ_TO_RGB_Z_R = -0.4985314;
+static constexpr double XYZ_TO_RGB_Z_G = 0.0415560;
+static constexpr double XYZ_TO_RGB_Z_B = 1.0572252;
+} // namespace
 
-static Standard_Real TheEpsilon = 0.0001;
+namespace
+{
+// Returns a reference to the epsilon value.
+inline Standard_Real& getEpsilonRef() noexcept
+{
+  static Standard_Real theEpsilon = 0.0001;
+  return theEpsilon;
+}
 
-// Throw exception if RGB values are out of range.
-#define Quantity_ColorValidateRgbRange(theR, theG, theB)                                           \
-  if (theR < 0.0 || theR > 1.0 || theG < 0.0 || theG > 1.0 || theB < 0.0 || theB > 1.0)            \
-  {                                                                                                \
-    throw Standard_OutOfRange("Color out");                                                        \
+// Validate RGB values are in range [0, 1].
+inline void validateRgbRange(Standard_Real theR, Standard_Real theG, Standard_Real theB)
+{
+  if (theR < 0.0 || theR > 1.0 || theG < 0.0 || theG > 1.0 || theB < 0.0 || theB > 1.0)
+  {
+    throw Standard_OutOfRange("Color out");
   }
+}
 
-// Throw exception if HLS values are out of range.
-#define Quantity_ColorValidateHlsRange(theH, theL, theS)                                           \
-  if ((theH < 0.0 && theH != RGBHLS_H_UNDEFINED && theS != 0.0) || (theH > 360.0) || theL < 0.0    \
-      || theL > 1.0 || theS < 0.0 || theS > 1.0)                                                   \
-  {                                                                                                \
-    throw Standard_OutOfRange("Color out");                                                        \
+// Validate HLS values are in valid ranges.
+inline void validateHlsRange(Standard_Real theH, Standard_Real theL, Standard_Real theS)
+{
+  if ((theH < 0.0 && theH != RGBHLS_H_UNDEFINED && theS != 0.0) || (theH > 360.0) || theL < 0.0
+      || theL > 1.0 || theS < 0.0 || theS > 1.0)
+  {
+    throw Standard_OutOfRange("Color out");
   }
+}
 
-// Throw exception if CIELab color values are out of range.
-#define Quantity_ColorValidateLabRange(theL, thea, theb)                                           \
-  if (theL < 0. || theL > 100. || thea < -100. || thea > 100. || theb < -110. || theb > 100.)      \
-  {                                                                                                \
-    throw Standard_OutOfRange("Color out");                                                        \
+// Validate CIELab color values are in valid ranges.
+inline void validateLabRange(Standard_Real theL, Standard_Real thea, Standard_Real theb)
+{
+  if (theL < 0. || theL > 100. || thea < -100. || thea > 100. || theb < -110. || theb > 100.)
+  {
+    throw Standard_OutOfRange("Color out");
   }
+}
 
-// Throw exception if CIELch color values are out of range.
-#define Quantity_ColorValidateLchRange(theL, thec, theh)                                           \
-  if (theL < 0. || theL > 100. || thec < 0. || thec > 135. || theh < 0.0 || theh > 360.)           \
-  {                                                                                                \
-    throw Standard_OutOfRange("Color out");                                                        \
+// Validate CIELch color values are in valid ranges.
+inline void validateLchRange(Standard_Real theL, Standard_Real thec, Standard_Real theh)
+{
+  if (theL < 0. || theL > 100. || thec < 0. || thec > 135. || theh < 0.0 || theh > 360.)
+  {
+    throw Standard_OutOfRange("Color out");
   }
+}
+} // anonymous namespace
 
 namespace
 {
@@ -64,10 +117,10 @@ struct Quantity_StandardColor
   NCollection_Vec3<float> RgbValues;
   Quantity_NameOfColor    EnumName;
 
-  Quantity_StandardColor(Quantity_NameOfColor           theName,
-                         const char*                    theStringName,
-                         const NCollection_Vec3<float>& thesRGB,
-                         const NCollection_Vec3<float>& theRGB)
+  constexpr Quantity_StandardColor(Quantity_NameOfColor           theName,
+                                   const char*                    theStringName,
+                                   const NCollection_Vec3<float>& thesRGB,
+                                   const NCollection_Vec3<float>& theRGB) noexcept
       : StringName(theStringName),
         sRgbValues(thesRGB),
         RgbValues(theRGB),
@@ -85,22 +138,22 @@ struct Quantity_StandardColor
                          NCollection_Vec3<float>(theR##f, theG##f, theB##f))
 
 //! Name list of standard materials (defined within enumeration).
-static const Quantity_StandardColor THE_COLORS[] = {
+static constexpr Quantity_StandardColor THE_COLORS[] = {
 #include "Quantity_ColorTable.pxx"
 };
 
 //=================================================================================================
 
-Standard_Real Quantity_Color::Epsilon()
+Standard_Real Quantity_Color::Epsilon() noexcept
 {
-  return TheEpsilon;
+  return getEpsilonRef();
 }
 
 //=================================================================================================
 
-void Quantity_Color::SetEpsilon(const Standard_Real theEpsilon)
+void Quantity_Color::SetEpsilon(const Standard_Real theEpsilon) noexcept
 {
-  TheEpsilon = theEpsilon;
+  getEpsilonRef() = theEpsilon;
 }
 
 //=================================================================================================
@@ -132,11 +185,11 @@ NCollection_Vec3<float> Quantity_Color::valuesOf(const Quantity_NameOfColor theN
 
 //=================================================================================================
 
-Standard_CString Quantity_Color::StringName(const Quantity_NameOfColor theName)
+Standard_CString Quantity_Color::StringName(const Quantity_NameOfColor theName) noexcept
 {
   if ((Standard_Integer)theName < 0 || (Standard_Integer)theName > Quantity_NOC_WHITE)
   {
-    throw Standard_OutOfRange("Bad name");
+    return "UNDEFINED";
   }
   return THE_COLORS[theName].StringName;
 }
@@ -144,7 +197,7 @@ Standard_CString Quantity_Color::StringName(const Quantity_NameOfColor theName)
 //=================================================================================================
 
 Standard_Boolean Quantity_Color::ColorFromName(const Standard_CString theName,
-                                               Quantity_NameOfColor&  theColor)
+                                               Quantity_NameOfColor&  theColor) noexcept
 {
   TCollection_AsciiString aName(theName);
   aName.UpperCase();
@@ -249,7 +302,7 @@ Quantity_Color::Quantity_Color(const Standard_Real        theC1,
 Quantity_Color::Quantity_Color(const NCollection_Vec3<float>& theRgb)
     : myRgb(theRgb)
 {
-  Quantity_ColorValidateRgbRange(theRgb.r(), theRgb.g(), theRgb.b());
+  validateRgbRange(theRgb.r(), theRgb.g(), theRgb.b());
 }
 
 //=================================================================================================
@@ -286,31 +339,31 @@ void Quantity_Color::SetValues(const Standard_Real        theC1,
   switch (theType)
   {
     case Quantity_TOC_RGB: {
-      Quantity_ColorValidateRgbRange(theC1, theC2, theC3);
+      validateRgbRange(theC1, theC2, theC3);
       myRgb.SetValues(float(theC1), float(theC2), float(theC3));
       break;
     }
     case Quantity_TOC_sRGB: {
-      Quantity_ColorValidateRgbRange(theC1, theC2, theC3);
+      validateRgbRange(theC1, theC2, theC3);
       myRgb.SetValues((float)Convert_sRGB_To_LinearRGB(theC1),
                       (float)Convert_sRGB_To_LinearRGB(theC2),
                       (float)Convert_sRGB_To_LinearRGB(theC3));
       break;
     }
     case Quantity_TOC_HLS: {
-      Quantity_ColorValidateHlsRange(theC1, theC2, theC3);
+      validateHlsRange(theC1, theC2, theC3);
       myRgb =
         Convert_HLS_To_LinearRGB(NCollection_Vec3<float>(float(theC1), float(theC2), float(theC3)));
       break;
     }
     case Quantity_TOC_CIELab: {
-      Quantity_ColorValidateLabRange(theC1, theC2, theC3);
+      validateLabRange(theC1, theC2, theC3);
       myRgb =
         Convert_Lab_To_LinearRGB(NCollection_Vec3<float>(float(theC1), float(theC2), float(theC3)));
       break;
     }
     case Quantity_TOC_CIELch: {
-      Quantity_ColorValidateLchRange(theC1, theC2, theC3);
+      validateLchRange(theC1, theC2, theC3);
       myRgb = Convert_Lab_To_LinearRGB(
         Convert_Lch_To_Lab(NCollection_Vec3<float>(float(theC1), float(theC2), float(theC3))));
       break;
@@ -346,21 +399,20 @@ Standard_Real Quantity_Color::DeltaE2000(const Quantity_Color& theOther) const
   Standard_Real aLx_mean = 0.5 * (aL1 + aL2);
 
   // mean C
-  Standard_Real       aC1          = Sqrt(aa1 * aa1 + ab1 * ab1);
-  Standard_Real       aC2          = Sqrt(aa2 * aa2 + ab2 * ab2);
-  Standard_Real       aC_mean      = 0.5 * (aC1 + aC2);
-  Standard_Real       aC_mean_pow7 = Pow(aC_mean, 7);
-  static const double a25_pow7     = Pow(25., 7);
-  Standard_Real       aG           = 0.5 * (1. - Sqrt(aC_mean_pow7 / (aC_mean_pow7 + a25_pow7)));
-  Standard_Real       aa1x         = aa1 * (1. + aG);
-  Standard_Real       aa2x         = aa2 * (1. + aG);
-  Standard_Real       aC1x         = Sqrt(aa1x * aa1x + ab1 * ab1);
-  Standard_Real       aC2x         = Sqrt(aa2x * aa2x + ab2 * ab2);
-  Standard_Real       aCx_mean     = 0.5 * (aC1x + aC2x);
+  Standard_Real aC1          = Sqrt(aa1 * aa1 + ab1 * ab1);
+  Standard_Real aC2          = Sqrt(aa2 * aa2 + ab2 * ab2);
+  Standard_Real aC_mean      = 0.5 * (aC1 + aC2);
+  Standard_Real aC_mean_pow7 = Pow(aC_mean, 7);
+  Standard_Real aG           = 0.5 * (1. - Sqrt(aC_mean_pow7 / (aC_mean_pow7 + POW_25_7)));
+  Standard_Real aa1x         = aa1 * (1. + aG);
+  Standard_Real aa2x         = aa2 * (1. + aG);
+  Standard_Real aC1x         = Sqrt(aa1x * aa1x + ab1 * ab1);
+  Standard_Real aC2x         = Sqrt(aa2x * aa2x + ab2 * ab2);
+  Standard_Real aCx_mean     = 0.5 * (aC1x + aC2x);
 
   // mean H
-  Standard_Real ah1x = (aC1x > TheEpsilon ? ATan2(ab1, aa1x) * 180. / M_PI : 270.);
-  Standard_Real ah2x = (aC2x > TheEpsilon ? ATan2(ab2, aa2x) * 180. / M_PI : 270.);
+  Standard_Real ah1x = (aC1x > Epsilon() ? ATan2(ab1, aa1x) * RAD_TO_DEG : 270.);
+  Standard_Real ah2x = (aC2x > Epsilon() ? ATan2(ab2, aa2x) * RAD_TO_DEG : 270.);
   if (ah1x < 0.)
     ah1x += 360.;
   if (ah2x < 0.)
@@ -376,13 +428,13 @@ Standard_Real Quantity_Color::DeltaE2000(const Quantity_Color& theOther) const
   // deltas
   Standard_Real aDeltaLx = aL2 - aL1;
   Standard_Real aDeltaCx = aC2x - aC1x;
-  Standard_Real aDeltaHx = 2. * Sqrt(aC1x * aC2x) * Sin(0.5 * aDeltahx * M_PI / 180.);
+  Standard_Real aDeltaHx = 2. * Sqrt(aC1x * aC2x) * Sin(0.5 * aDeltahx * DEG_TO_RAD);
 
   // factors
-  Standard_Real aT = 1. - 0.17 * Cos((aHx_mean - 30.) * M_PI / 180.)
-                     + 0.24 * Cos((2. * aHx_mean) * M_PI / 180.)
-                     + 0.32 * Cos((3. * aHx_mean + 6.) * M_PI / 180.)
-                     - 0.20 * Cos((4. * aHx_mean - 63.) * M_PI / 180.);
+  Standard_Real aT = 1. - 0.17 * Cos((aHx_mean - 30.) * DEG_TO_RAD)
+                     + 0.24 * Cos((2. * aHx_mean) * DEG_TO_RAD)
+                     + 0.32 * Cos((3. * aHx_mean + 6.) * DEG_TO_RAD)
+                     - 0.20 * Cos((4. * aHx_mean - 63.) * DEG_TO_RAD);
 
   Standard_Real aLx_mean50_2 = (aLx_mean - 50.) * (aLx_mean - 50.);
   Standard_Real aS_L         = 1. + 0.015 * aLx_mean50_2 / Sqrt(20. + aLx_mean50_2);
@@ -391,8 +443,8 @@ Standard_Real Quantity_Color::DeltaE2000(const Quantity_Color& theOther) const
 
   Standard_Real aDelta_theta  = 30. * Exp(-(aHx_mean - 275.) * (aHx_mean - 275.) / 625.);
   Standard_Real aCx_mean_pow7 = Pow(aCx_mean, 7);
-  Standard_Real aR_C          = 2. * Sqrt(aCx_mean_pow7 / (aCx_mean_pow7 + a25_pow7));
-  Standard_Real aR_T          = -aR_C * Sin(2. * aDelta_theta * M_PI / 180.);
+  Standard_Real aR_C          = 2. * Sqrt(aCx_mean_pow7 / (aCx_mean_pow7 + POW_25_7));
+  Standard_Real aR_T          = -aR_C * Sin(2. * aDelta_theta * DEG_TO_RAD);
 
   // finally, the difference
   Standard_Real aDL         = aDeltaLx / aS_L;
@@ -523,7 +575,8 @@ NCollection_Vec3<float> Quantity_Color::Convert_HLS_To_sRGB(const NCollection_Ve
 // function : Convert_sRGB_To_HLS
 // purpose  : Reference: La synthese d'images, Collection Hermes
 // =======================================================================
-NCollection_Vec3<float> Quantity_Color::Convert_sRGB_To_HLS(const NCollection_Vec3<float>& theRgb)
+NCollection_Vec3<float> Quantity_Color::Convert_sRGB_To_HLS(
+  const NCollection_Vec3<float>& theRgb) noexcept
 {
   float aPlus = 0.0f;
   float aDiff = theRgb.g() - theRgb.b();
@@ -573,10 +626,10 @@ NCollection_Vec3<float> Quantity_Color::Convert_sRGB_To_HLS(const NCollection_Ve
 // purpose  : non-linear function transforming XYZ coordinates to CIE Lab
 // see http://www.brucelindbloom.com/index.html?Equations.html
 // =======================================================================
-static inline double CIELab_f(double theValue)
+static inline double CIELab_f(double theValue) noexcept
 {
-  return theValue > 0.008856451679035631 ? Pow(theValue, 1. / 3.)
-                                         : (7.787037037037037 * theValue) + 16. / 116.;
+  return theValue > CIELAB_EPSILON ? Pow(theValue, 1. / 3.)
+                                   : (CIELAB_KAPPA * theValue) + CIELAB_OFFSET;
 }
 
 // =======================================================================
@@ -584,10 +637,10 @@ static inline double CIELab_f(double theValue)
 // purpose  : inverse of non-linear function transforming XYZ coordinates to CIE Lab
 // see http://www.brucelindbloom.com/index.html?Equations.html
 // =======================================================================
-static inline double CIELab_invertf(double theValue)
+static inline double CIELab_invertf(double theValue) noexcept
 {
   double aV3 = theValue * theValue * theValue;
-  return aV3 > 0.008856451679035631 ? aV3 : (theValue - 16. / 116.) / 7.787037037037037;
+  return aV3 > CIELAB_EPSILON ? aV3 : (theValue - CIELAB_OFFSET) / CIELAB_KAPPA;
 }
 
 // =======================================================================
@@ -596,7 +649,7 @@ static inline double CIELab_invertf(double theValue)
 // see https://www.easyrgb.com/en/math.php
 // =======================================================================
 NCollection_Vec3<float> Quantity_Color::Convert_LinearRGB_To_Lab(
-  const NCollection_Vec3<float>& theRgb)
+  const NCollection_Vec3<float>& theRgb) noexcept
 {
   double aR = theRgb[0];
   double aG = theRgb[1];
@@ -604,18 +657,18 @@ NCollection_Vec3<float> Quantity_Color::Convert_LinearRGB_To_Lab(
 
   // convert to XYZ normalized to D65 / 2 deg (CIE 1931) standard illuminant intensities
   // see http://www.brucelindbloom.com/index.html?Equations.html
-  double aX = (aR * 0.4124564 + aG * 0.3575761 + aB * 0.1804375) * 100. / 95.047;
-  double aY = (aR * 0.2126729 + aG * 0.7151522 + aB * 0.0721750) * 100. / 100.000;
-  double aZ = (aR * 0.0193339 + aG * 0.1191920 + aB * 0.9503041) * 100. / 108.883;
+  double aX = (aR * RGB_TO_XYZ_R_X + aG * RGB_TO_XYZ_G_X + aB * RGB_TO_XYZ_B_X) * 100. / D65_REF_X;
+  double aY = (aR * RGB_TO_XYZ_R_Y + aG * RGB_TO_XYZ_G_Y + aB * RGB_TO_XYZ_B_Y);
+  double aZ = (aR * RGB_TO_XYZ_R_Z + aG * RGB_TO_XYZ_G_Z + aB * RGB_TO_XYZ_B_Z) * 100. / D65_REF_Z;
 
   // convert to Lab
   double afX = CIELab_f(aX);
   double afY = CIELab_f(aY);
   double afZ = CIELab_f(aZ);
 
-  double aL = 116. * afY - 16.;
-  double aa = 500. * (afX - afY);
-  double ab = 200. * (afY - afZ);
+  double aL = CIELAB_L_COEFF * afY - CIELAB_L_OFFSET;
+  double aa = CIELAB_A_COEFF * (afX - afY);
+  double ab = CIELAB_B_COEFF * (afY - afZ);
 
   return NCollection_Vec3<float>((float)aL, (float)aa, (float)ab);
 }
@@ -626,7 +679,7 @@ NCollection_Vec3<float> Quantity_Color::Convert_LinearRGB_To_Lab(
 // see https://www.easyrgb.com/en/math.php
 // =======================================================================
 NCollection_Vec3<float> Quantity_Color::Convert_Lab_To_LinearRGB(
-  const NCollection_Vec3<float>& theLab)
+  const NCollection_Vec3<float>& theLab) noexcept
 {
   double aL = theLab[0];
   double aa = theLab[1];
@@ -643,19 +696,19 @@ NCollection_Vec3<float> Quantity_Color::Convert_Lab_To_LinearRGB(
     double aC = aRate / (double)NBSTEPS;
 
     // convert to XYZ for D65 / 2 deg (CIE 1931) standard illuminant
-    double afY = (aL + 16.) / 116.;
-    double afX = aC * aa / 500. + afY;
-    double afZ = afY - aC * ab / 200.;
+    double afY = (aL + CIELAB_L_OFFSET) / CIELAB_L_COEFF;
+    double afX = aC * aa / CIELAB_A_COEFF + afY;
+    double afZ = afY - aC * ab / CIELAB_B_COEFF;
 
-    double aX = CIELab_invertf(afX) * 95.047;
-    double aY = CIELab_invertf(afY) * 100.000;
-    double aZ = CIELab_invertf(afZ) * 108.883;
+    double aX = CIELab_invertf(afX) * D65_REF_X;
+    double aY = CIELab_invertf(afY) * D65_REF_Y;
+    double aZ = CIELab_invertf(afZ) * D65_REF_Z;
 
     // convert to RGB
     // see http://www.brucelindbloom.com/index.html?Equations.html
-    double aR = (aX * 3.2404542 + aY * -1.5371385 + aZ * -0.4985314) / 100.;
-    double aG = (aX * -0.9692660 + aY * 1.8760108 + aZ * 0.0415560) / 100.;
-    double aB = (aX * 0.0556434 + aY * -0.2040259 + aZ * 1.0572252) / 100.;
+    double aR = (aX * XYZ_TO_RGB_X_R + aY * XYZ_TO_RGB_Y_R + aZ * XYZ_TO_RGB_Z_R) / 100.;
+    double aG = (aX * XYZ_TO_RGB_X_G + aY * XYZ_TO_RGB_Y_G + aZ * XYZ_TO_RGB_Z_G) / 100.;
+    double aB = (aX * XYZ_TO_RGB_X_B + aY * XYZ_TO_RGB_Y_B + aZ * XYZ_TO_RGB_Z_B) / 100.;
 
     // exit if we are in range or at zero C
     if (aRate == 0 || (aR >= 0. && aR <= 1. && aG >= 0. && aG <= 1. && aB >= 0. && aB <= 1.))
@@ -670,13 +723,14 @@ NCollection_Vec3<float> Quantity_Color::Convert_Lab_To_LinearRGB(
 // purpose  : convert CIE Lab color to CIE Lch color
 // see https://www.easyrgb.com/en/math.php
 // =======================================================================
-NCollection_Vec3<float> Quantity_Color::Convert_Lab_To_Lch(const NCollection_Vec3<float>& theLab)
+NCollection_Vec3<float> Quantity_Color::Convert_Lab_To_Lch(
+  const NCollection_Vec3<float>& theLab) noexcept
 {
   double aa = theLab[1];
   double ab = theLab[2];
 
   double aC = Sqrt(aa * aa + ab * ab);
-  double aH = (aC > TheEpsilon ? ATan2(ab, aa) * 180. / M_PI : 0.);
+  double aH = (aC > Epsilon() ? ATan2(ab, aa) * RAD_TO_DEG : 0.);
 
   if (aH < 0.)
     aH += 360.;
@@ -689,12 +743,13 @@ NCollection_Vec3<float> Quantity_Color::Convert_Lab_To_Lch(const NCollection_Vec
 // purpose  : convert CIE Lch color to CIE Lab color
 // see https://www.easyrgb.com/en/math.php
 // =======================================================================
-NCollection_Vec3<float> Quantity_Color::Convert_Lch_To_Lab(const NCollection_Vec3<float>& theLch)
+NCollection_Vec3<float> Quantity_Color::Convert_Lch_To_Lab(
+  const NCollection_Vec3<float>& theLch) noexcept
 {
   double aC = theLch[1];
   double aH = theLch[2];
 
-  aH *= M_PI / 180.;
+  aH *= DEG_TO_RAD;
 
   double aa = aC * Cos(aH);
   double ab = aC * Sin(aH);
index a01ab8d56868b079ed489ef33e64d4cd8b9fd77a..6e818833d4586dc6ac2560d8edecf0ccafc4485e 100644 (file)
@@ -65,16 +65,16 @@ public:
   Standard_EXPORT Quantity_NameOfColor Name() const;
 
   //! Updates the color from specified named color.
-  void SetValues(const Quantity_NameOfColor theName)
+  void SetValues(const Quantity_NameOfColor theName) noexcept
   {
     myRgb = valuesOf(theName, Quantity_TOC_RGB);
   }
 
   //! Return the color as vector of 3 float elements.
-  const NCollection_Vec3<float>& Rgb() const { return myRgb; }
+  constexpr const NCollection_Vec3<float>& Rgb() const noexcept { return myRgb; }
 
   //! Return the color as vector of 3 float elements.
-  operator const NCollection_Vec3<float>&() const { return myRgb; }
+  constexpr operator const NCollection_Vec3<float>&() const noexcept { return myRgb; }
 
   //! Returns in theC1, theC2 and theC3 the components of this color
   //! according to the color system definition theType.
@@ -91,21 +91,21 @@ public:
                                  const Quantity_TypeOfColor theType);
 
   //! Returns the Red component (quantity of red) of the color within range [0.0; 1.0].
-  Standard_Real Red() const { return myRgb.r(); }
+  constexpr Standard_Real Red() const noexcept { return myRgb.r(); }
 
   //! Returns the Green component (quantity of green) of the color within range [0.0; 1.0].
-  Standard_Real Green() const { return myRgb.g(); }
+  constexpr Standard_Real Green() const noexcept { return myRgb.g(); }
 
   //! Returns the Blue component (quantity of blue) of the color within range [0.0; 1.0].
-  Standard_Real Blue() const { return myRgb.b(); }
+  constexpr Standard_Real Blue() const noexcept { return myRgb.b(); }
 
   //! Returns the Hue component (hue angle) of the color
   //! in degrees within range [0.0; 360.0], 0.0 being Red.
   //! -1.0 is a special value reserved for grayscale color (S should be 0.0)
-  Standard_Real Hue() const { return Convert_LinearRGB_To_HLS(myRgb)[0]; }
+  Standard_Real Hue() const noexcept { return Convert_LinearRGB_To_HLS(myRgb)[0]; }
 
   //! Returns the Light component (value of the lightness) of the color within range [0.0; 1.0].
-  Standard_Real Light() const { return Convert_LinearRGB_To_HLS(myRgb)[1]; }
+  Standard_Real Light() const noexcept { return Convert_LinearRGB_To_HLS(myRgb)[1]; }
 
   //! Increases or decreases the intensity (variation of the lightness).
   //! The delta is a percentage. Any value greater than zero will increase the intensity.
@@ -114,7 +114,7 @@ public:
 
   //! Returns the Saturation component (value of the saturation) of the color within range
   //! [0.0; 1.0].
-  Standard_Real Saturation() const { return Convert_LinearRGB_To_HLS(myRgb)[2]; }
+  Standard_Real Saturation() const noexcept { return Convert_LinearRGB_To_HLS(myRgb)[2]; }
 
   //! Increases or decreases the contrast (variation of the saturation).
   //! The delta is a percentage. Any value greater than zero will increase the contrast.
@@ -122,29 +122,32 @@ public:
   Standard_EXPORT void ChangeContrast(const Standard_Real theDelta);
 
   //! Returns TRUE if the distance between two colors is greater than Epsilon().
-  Standard_Boolean IsDifferent(const Quantity_Color& theOther) const
+  Standard_Boolean IsDifferent(const Quantity_Color& theOther) const noexcept
   {
     return (SquareDistance(theOther) > Epsilon() * Epsilon());
   }
 
   //! Alias to IsDifferent().
-  Standard_Boolean operator!=(const Quantity_Color& theOther) const
+  Standard_Boolean operator!=(const Quantity_Color& theOther) const noexcept
   {
     return IsDifferent(theOther);
   }
 
   //! Returns TRUE if the distance between two colors is no greater than Epsilon().
-  Standard_Boolean IsEqual(const Quantity_Color& theOther) const
+  Standard_Boolean IsEqual(const Quantity_Color& theOther) const noexcept
   {
     return (SquareDistance(theOther) <= Epsilon() * Epsilon());
   }
 
   //! Alias to IsEqual().
-  Standard_Boolean operator==(const Quantity_Color& theOther) const { return IsEqual(theOther); }
+  Standard_Boolean operator==(const Quantity_Color& theOther) const noexcept
+  {
+    return IsEqual(theOther);
+  }
 
   //! Returns the distance between two colors. It's a value between 0 and the square root of 3 (the
   //! black/white distance).
-  Standard_Real Distance(const Quantity_Color& theColor) const
+  Standard_Real Distance(const Quantity_Color& theColor) const noexcept
   {
     return (NCollection_Vec3<Standard_Real>(myRgb)
             - NCollection_Vec3<Standard_Real>(theColor.myRgb))
@@ -152,7 +155,7 @@ public:
   }
 
   //! Returns the square of distance between two colors.
-  Standard_Real SquareDistance(const Quantity_Color& theColor) const
+  Standard_Real SquareDistance(const Quantity_Color& theColor) const noexcept
   {
     return (NCollection_Vec3<Standard_Real>(myRgb)
             - NCollection_Vec3<Standard_Real>(theColor.myRgb))
@@ -186,20 +189,20 @@ public:
   }
 
   //! Returns the name of the color identified by the given Quantity_NameOfColor enumeration value.
-  Standard_EXPORT static Standard_CString StringName(const Quantity_NameOfColor theColor);
+  Standard_EXPORT static Standard_CString StringName(const Quantity_NameOfColor theColor) noexcept;
 
   //! Finds color from predefined names.
   //! For example, the name of the color which corresponds to "BLACK" is Quantity_NOC_BLACK.
   //! Returns FALSE if name is unknown.
   Standard_EXPORT static Standard_Boolean ColorFromName(const Standard_CString theName,
-                                                        Quantity_NameOfColor&  theColor);
+                                                        Quantity_NameOfColor&  theColor) noexcept;
 
   //! Finds color from predefined names.
   //! @param theColorNameString the color name
   //! @param theColor a found color
   //! @return false if the color name is unknown, or true if the search by color name was successful
   static Standard_Boolean ColorFromName(const Standard_CString theColorNameString,
-                                        Quantity_Color&        theColor)
+                                        Quantity_Color&        theColor) noexcept
   {
     Quantity_NameOfColor aColorName = Quantity_NOC_BLACK;
     if (!ColorFromName(theColorNameString, aColorName))
@@ -223,7 +226,7 @@ public:
 
   //! Returns hex sRGB string in format "#FFAAFF".
   static TCollection_AsciiString ColorToHex(const Quantity_Color& theColor,
-                                            const bool            theToPrefixHash = true)
+                                            const bool            theToPrefixHash = true) noexcept
   {
     NCollection_Vec3<Standard_ShortReal> anSRgb =
       Convert_LinearRGB_To_sRGB((NCollection_Vec3<Standard_ShortReal>)theColor);
@@ -240,40 +243,42 @@ public:
 
   //! Converts sRGB components into HLS ones.
   Standard_EXPORT static NCollection_Vec3<float> Convert_sRGB_To_HLS(
-    const NCollection_Vec3<float>& theRgb);
+    const NCollection_Vec3<float>& theRgb) noexcept;
 
   //! Converts HLS components into RGB ones.
   Standard_EXPORT static NCollection_Vec3<float> Convert_HLS_To_sRGB(
     const NCollection_Vec3<float>& theHls);
 
   //! Converts Linear RGB components into HLS ones.
-  static NCollection_Vec3<float> Convert_LinearRGB_To_HLS(const NCollection_Vec3<float>& theRgb)
+  static NCollection_Vec3<float> Convert_LinearRGB_To_HLS(
+    const NCollection_Vec3<float>& theRgb) noexcept
   {
     return Convert_sRGB_To_HLS(Convert_LinearRGB_To_sRGB(theRgb));
   }
 
   //! Converts HLS components into linear RGB ones.
-  static NCollection_Vec3<float> Convert_HLS_To_LinearRGB(const NCollection_Vec3<float>& theHls)
+  static NCollection_Vec3<float> Convert_HLS_To_LinearRGB(
+    const NCollection_Vec3<float>& theHls) noexcept
   {
     return Convert_sRGB_To_LinearRGB(Convert_HLS_To_sRGB(theHls));
   }
 
   //! Converts linear RGB components into CIE Lab ones.
   Standard_EXPORT static NCollection_Vec3<float> Convert_LinearRGB_To_Lab(
-    const NCollection_Vec3<float>& theRgb);
+    const NCollection_Vec3<float>& theRgb) noexcept;
 
   //! Converts CIE Lab components into CIE Lch ones.
   Standard_EXPORT static NCollection_Vec3<float> Convert_Lab_To_Lch(
-    const NCollection_Vec3<float>& theLab);
+    const NCollection_Vec3<float>& theLab) noexcept;
 
   //! Converts CIE Lab components into linear RGB ones.
   //! Note that the resulting values may be out of the valid range for RGB.
   Standard_EXPORT static NCollection_Vec3<float> Convert_Lab_To_LinearRGB(
-    const NCollection_Vec3<float>& theLab);
+    const NCollection_Vec3<float>& theLab) noexcept;
 
   //! Converts CIE Lch components into CIE Lab ones.
   Standard_EXPORT static NCollection_Vec3<float> Convert_Lch_To_Lab(
-    const NCollection_Vec3<float>& theLch);
+    const NCollection_Vec3<float>& theLch) noexcept;
 
   //! Convert the color value to ARGB integer value, with alpha equals to 0.
   //! So the output is formatted as 0x00RRGGBB.
@@ -281,7 +286,8 @@ public:
   //! as would be usually expected for RGB color packed into 4 bytes.
   //! @param[in] theColor  color to convert
   //! @param[out] theARGB  result color encoded as integer
-  static void Color2argb(const Quantity_Color& theColor, Standard_Integer& theARGB)
+  static constexpr void Color2argb(const Quantity_Color& theColor,
+                                   Standard_Integer&     theARGB) noexcept
   {
     const NCollection_Vec3<Standard_Integer> aColor(
       static_cast<Standard_Integer>(255.0f * theColor.myRgb.r() + 0.5f),
@@ -293,7 +299,7 @@ public:
   //! Convert integer ARGB value to Color. Alpha bits are ignored.
   //! Note that this packing does NOT involve linear -> non-linear sRGB conversion,
   //! as would be usually expected to preserve higher (for human eye) color precision in 4 bytes.
-  static void Argb2color(const Standard_Integer theARGB, Quantity_Color& theColor)
+  static void Argb2color(const Standard_Integer theARGB, Quantity_Color& theColor) noexcept
   {
     const NCollection_Vec3<Standard_Real> aColor(
       static_cast<Standard_Real>((theARGB & 0xff0000) >> 16),
@@ -307,7 +313,7 @@ public:
 
   //! Convert linear RGB component into sRGB using OpenGL specs formula (double precision), also
   //! known as gamma correction.
-  static Standard_Real Convert_LinearRGB_To_sRGB(Standard_Real theLinearValue)
+  static Standard_Real Convert_LinearRGB_To_sRGB(Standard_Real theLinearValue) noexcept
   {
     return theLinearValue <= 0.0031308 ? theLinearValue * 12.92
                                        : Pow(theLinearValue, 1.0 / 2.4) * 1.055 - 0.055;
@@ -315,7 +321,7 @@ public:
 
   //! Convert linear RGB component into sRGB using OpenGL specs formula (single precision), also
   //! known as gamma correction.
-  static float Convert_LinearRGB_To_sRGB(float theLinearValue)
+  static float Convert_LinearRGB_To_sRGB(float theLinearValue) noexcept
   {
     return theLinearValue <= 0.0031308f ? theLinearValue * 12.92f
                                         : powf(theLinearValue, 1.0f / 2.4f) * 1.055f - 0.055f;
@@ -323,7 +329,7 @@ public:
 
   //! Convert sRGB component into linear RGB using OpenGL specs formula (double precision), also
   //! known as gamma correction.
-  static Standard_Real Convert_sRGB_To_LinearRGB(Standard_Real thesRGBValue)
+  static Standard_Real Convert_sRGB_To_LinearRGB(Standard_Real thesRGBValue) noexcept
   {
     return thesRGBValue <= 0.04045 ? thesRGBValue / 12.92
                                    : Pow((thesRGBValue + 0.055) / 1.055, 2.4);
@@ -331,7 +337,7 @@ public:
 
   //! Convert sRGB component into linear RGB using OpenGL specs formula (single precision), also
   //! known as gamma correction.
-  static float Convert_sRGB_To_LinearRGB(float thesRGBValue)
+  static float Convert_sRGB_To_LinearRGB(float thesRGBValue) noexcept
   {
     return thesRGBValue <= 0.04045f ? thesRGBValue / 12.92f
                                     : powf((thesRGBValue + 0.055f) / 1.055f, 2.4f);
@@ -339,7 +345,7 @@ public:
 
   //! Convert linear RGB components into sRGB using OpenGL specs formula.
   template <typename T>
-  static NCollection_Vec3<T> Convert_LinearRGB_To_sRGB(const NCollection_Vec3<T>& theRGB)
+  static NCollection_Vec3<T> Convert_LinearRGB_To_sRGB(const NCollection_Vec3<T>& theRGB) noexcept
   {
     return NCollection_Vec3<T>(Convert_LinearRGB_To_sRGB(theRGB.r()),
                                Convert_LinearRGB_To_sRGB(theRGB.g()),
@@ -348,7 +354,7 @@ public:
 
   //! Convert sRGB components into linear RGB using OpenGL specs formula.
   template <typename T>
-  static NCollection_Vec3<T> Convert_sRGB_To_LinearRGB(const NCollection_Vec3<T>& theRGB)
+  static NCollection_Vec3<T> Convert_sRGB_To_LinearRGB(const NCollection_Vec3<T>& theRGB) noexcept
   {
     return NCollection_Vec3<T>(Convert_sRGB_To_LinearRGB(theRGB.r()),
                                Convert_sRGB_To_LinearRGB(theRGB.g()),
@@ -356,20 +362,20 @@ public:
   }
 
   //! Convert linear RGB component into sRGB using approximated uniform gamma coefficient 2.2.
-  static float Convert_LinearRGB_To_sRGB_approx22(float theLinearValue)
+  static float Convert_LinearRGB_To_sRGB_approx22(float theLinearValue) noexcept
   {
     return powf(theLinearValue, 2.2f);
   }
 
   //! Convert sRGB component into linear RGB using approximated uniform gamma coefficient 2.2
-  static float Convert_sRGB_To_LinearRGB_approx22(float thesRGBValue)
+  static float Convert_sRGB_To_LinearRGB_approx22(float thesRGBValue) noexcept
   {
     return powf(thesRGBValue, 1.0f / 2.2f);
   }
 
   //! Convert linear RGB components into sRGB using approximated uniform gamma coefficient 2.2
   static NCollection_Vec3<float> Convert_LinearRGB_To_sRGB_approx22(
-    const NCollection_Vec3<float>& theRGB)
+    const NCollection_Vec3<float>& theRGB) noexcept
   {
     return NCollection_Vec3<float>(Convert_LinearRGB_To_sRGB_approx22(theRGB.r()),
                                    Convert_LinearRGB_To_sRGB_approx22(theRGB.g()),
@@ -378,7 +384,7 @@ public:
 
   //! Convert sRGB components into linear RGB using approximated uniform gamma coefficient 2.2
   static NCollection_Vec3<float> Convert_sRGB_To_LinearRGB_approx22(
-    const NCollection_Vec3<float>& theRGB)
+    const NCollection_Vec3<float>& theRGB) noexcept
   {
     return NCollection_Vec3<float>(Convert_sRGB_To_LinearRGB_approx22(theRGB.r()),
                                    Convert_sRGB_To_LinearRGB_approx22(theRGB.g()),
@@ -391,7 +397,7 @@ public:
                      const Standard_Real theS,
                      Standard_Real&      theR,
                      Standard_Real&      theG,
-                     Standard_Real&      theB)
+                     Standard_Real&      theB) noexcept
   {
     const NCollection_Vec3<float> anRgb =
       Convert_HLS_To_sRGB(NCollection_Vec3<float>((float)theH, (float)theL, (float)theS));
@@ -406,7 +412,7 @@ public:
                      const Standard_Real theB,
                      Standard_Real&      theH,
                      Standard_Real&      theL,
-                     Standard_Real&      theS)
+                     Standard_Real&      theS) noexcept
   {
     const NCollection_Vec3<float> aHls =
       Convert_sRGB_To_HLS(NCollection_Vec3<float>((float)theR, (float)theG, (float)theB));
@@ -417,10 +423,10 @@ public:
 
 public:
   //! Returns the value used to compare two colors for equality; 0.0001 by default.
-  Standard_EXPORT static Standard_Real Epsilon();
+  Standard_EXPORT static Standard_Real Epsilon() noexcept;
 
   //! Set the value used to compare two colors for equality.
-  Standard_EXPORT static void SetEpsilon(const Standard_Real theEpsilon);
+  Standard_EXPORT static void SetEpsilon(const Standard_Real theEpsilon) noexcept;
 
   //! Dumps the content of me into the stream
   Standard_EXPORT void DumpJson(Standard_OStream& theOStream, Standard_Integer theDepth = -1) const;
index 8de4958a9ecb7219bcbd3b5b52729313c73a2da4..47dfd17c9cdd2526316c1868659e870f1c84f74b 100644 (file)
@@ -35,6 +35,11 @@ enum HexColorLength
   HexColorLength_RGBA      = 8  //!< RGBA hex color format
 };
 
+static constexpr ColorInteger HEX_BASE                     = 16; // Hexadecimal number base
+static constexpr int          HEX_BITS_PER_COMPONENT       = 8; // 8 bits per component (256 values)
+static constexpr int          HEX_BITS_PER_COMPONENT_SHORT = 4; // 4 bits per component (16 values)
+static constexpr int          RGB_COMPONENT_LAST_INDEX = 2; // Last RGB component index (B in RGB)
+
 //! Takes next color component from the integer representing a color (it is a step in a process of a
 //! conversion implemented by the function ConvertIntegerToColorRGBA)
 //! @param theColorInteger the integer representing a color
@@ -77,7 +82,8 @@ static bool convertIntegerToColorRGBA(ColorInteger        theColorInteger,
       takeColorComponentFromInteger(theColorInteger, theColorComponentBase);
     aColor.a() = anAlphaComponent;
   }
-  for (Standard_Integer aColorComponentIndex = 2; aColorComponentIndex >= 0; --aColorComponentIndex)
+  for (Standard_Integer aColorComponentIndex = RGB_COMPONENT_LAST_INDEX; aColorComponentIndex >= 0;
+       --aColorComponentIndex)
   {
     const Standard_ShortReal aColorComponent =
       takeColorComponentFromInteger(theColorInteger, theColorComponentBase);
@@ -164,7 +170,7 @@ bool Quantity_ColorRGBA::ColorFromHex(const char* const   theHexColorString,
   }
 
   ColorInteger aHexColorInteger;
-  if (!convertStringToInteger(aHexColorString, aHexColorInteger, 16u))
+  if (!convertStringToInteger(aHexColorString, aHexColorInteger, HEX_BASE))
   {
     return false;
   }
@@ -197,8 +203,8 @@ bool Quantity_ColorRGBA::ColorFromHex(const char* const   theHexColorString,
     return false;
   }
 
-  const ColorInteger THE_HEX_COLOR_COMPONENT_BASE       = 1 << 8;
-  const ColorInteger THE_HEX_COLOR_COMPONENT_SHORT_BASE = 1 << 4;
+  const ColorInteger THE_HEX_COLOR_COMPONENT_BASE       = 1 << HEX_BITS_PER_COMPONENT;
+  const ColorInteger THE_HEX_COLOR_COMPONENT_SHORT_BASE = 1 << HEX_BITS_PER_COMPONENT_SHORT;
   const ColorInteger aColorComponentBase =
     isShort ? THE_HEX_COLOR_COMPONENT_SHORT_BASE : THE_HEX_COLOR_COMPONENT_BASE;
   return convertIntegerToColorRGBA(aHexColorInteger,
index c6a723c4dd45c3c040677caa92cf26e5d385f3b0..7b877e7926f7e9c70b199d9bfc2d8dacf420ebd3 100644 (file)
@@ -28,14 +28,14 @@ public:
   }
 
   //! Creates the color with specified RGB value.
-  explicit Quantity_ColorRGBA(const Quantity_Color& theRgb)
+  constexpr explicit Quantity_ColorRGBA(const Quantity_Color& theRgb)
       : myRgb(theRgb),
         myAlpha(1.0f)
   {
   }
 
   //! Creates the color with specified RGBA values.
-  Quantity_ColorRGBA(const Quantity_Color& theRgb, float theAlpha)
+  constexpr Quantity_ColorRGBA(const Quantity_Color& theRgb, float theAlpha)
       : myRgb(theRgb),
         myAlpha(theAlpha)
   {
@@ -56,49 +56,55 @@ public:
   }
 
   //! Assign new values to the color.
-  void SetValues(float theRed, float theGreen, float theBlue, float theAlpha)
+  void SetValues(float theRed, float theGreen, float theBlue, float theAlpha) noexcept
   {
     myRgb.SetValues(theRed, theGreen, theBlue, Quantity_TOC_RGB);
     myAlpha = theAlpha;
   }
 
   //! Return RGB color value.
-  const Quantity_Color& GetRGB() const { return myRgb; }
+  constexpr const Quantity_Color& GetRGB() const noexcept { return myRgb; }
 
   //! Modify RGB color components without affecting alpha value.
-  Quantity_Color& ChangeRGB() { return myRgb; }
+  constexpr Quantity_Color& ChangeRGB() noexcept { return myRgb; }
 
   //! Assign RGB color components without affecting alpha value.
-  void SetRGB(const Quantity_Color& theRgb) { myRgb = theRgb; }
+  constexpr void SetRGB(const Quantity_Color& theRgb) noexcept { myRgb = theRgb; }
 
   //! Return alpha value (1.0 means opaque, 0.0 means fully transparent).
-  Standard_ShortReal Alpha() const { return myAlpha; }
+  constexpr Standard_ShortReal Alpha() const noexcept { return myAlpha; }
 
   //! Assign the alpha value.
-  void SetAlpha(const Standard_ShortReal theAlpha) { myAlpha = theAlpha; }
+  constexpr void SetAlpha(const Standard_ShortReal theAlpha) noexcept { myAlpha = theAlpha; }
 
   //! Return the color as vector of 4 float elements.
-  operator const NCollection_Vec4<float>&() const { return *(const NCollection_Vec4<float>*)this; }
+  constexpr operator const NCollection_Vec4<float>&() const noexcept
+  {
+    return *(const NCollection_Vec4<float>*)this;
+  }
 
   //! Returns true if the distance between colors is greater than Epsilon().
-  bool IsDifferent(const Quantity_ColorRGBA& theOther) const
+  bool IsDifferent(const Quantity_ColorRGBA& theOther) const noexcept
   {
     return myRgb.IsDifferent(theOther.GetRGB())
            || Abs(myAlpha - theOther.myAlpha) > (float)Quantity_Color::Epsilon();
   }
 
   //! Returns true if the distance between colors is greater than Epsilon().
-  bool operator!=(const Quantity_ColorRGBA& theOther) const { return IsDifferent(theOther); }
+  bool operator!=(const Quantity_ColorRGBA& theOther) const noexcept
+  {
+    return IsDifferent(theOther);
+  }
 
   //! Two colors are considered to be equal if their distance is no greater than Epsilon().
-  bool IsEqual(const Quantity_ColorRGBA& theOther) const
+  bool IsEqual(const Quantity_ColorRGBA& theOther) const noexcept
   {
     return myRgb.IsEqual(theOther.GetRGB())
            && Abs(myAlpha - theOther.myAlpha) <= (float)Quantity_Color::Epsilon();
   }
 
   //! Two colors are considered to be equal if their distance is no greater than Epsilon().
-  bool operator==(const Quantity_ColorRGBA& theOther) const { return IsEqual(theOther); }
+  bool operator==(const Quantity_ColorRGBA& theOther) const noexcept { return IsEqual(theOther); }
 
 public:
   //! Finds color from predefined names.
@@ -108,7 +114,7 @@ public:
   //! @param theColor a found color
   //! @return false if the color name is unknown, or true if the search by color name was successful
   static Standard_Boolean ColorFromName(const Standard_CString theColorNameString,
-                                        Quantity_ColorRGBA&    theColor)
+                                        Quantity_ColorRGBA&    theColor) noexcept
   {
     Quantity_ColorRGBA aColor;
     if (!Quantity_Color::ColorFromName(theColorNameString, aColor.ChangeRGB()))
@@ -133,7 +139,7 @@ public:
 
   //! Returns hex sRGBA string in format "#RRGGBBAA".
   static TCollection_AsciiString ColorToHex(const Quantity_ColorRGBA& theColor,
-                                            const bool                theToPrefixHash = true)
+                                            const bool theToPrefixHash = true) noexcept
   {
     NCollection_Vec4<Standard_ShortReal> anSRgb =
       Convert_LinearRGB_To_sRGB((NCollection_Vec4<Standard_ShortReal>)theColor);
@@ -151,7 +157,8 @@ public:
 
 public:
   //! Convert linear RGB components into sRGB using OpenGL specs formula.
-  static NCollection_Vec4<float> Convert_LinearRGB_To_sRGB(const NCollection_Vec4<float>& theRGB)
+  static NCollection_Vec4<float> Convert_LinearRGB_To_sRGB(
+    const NCollection_Vec4<float>& theRGB) noexcept
   {
     return NCollection_Vec4<float>(Quantity_Color::Convert_LinearRGB_To_sRGB(theRGB.r()),
                                    Quantity_Color::Convert_LinearRGB_To_sRGB(theRGB.g()),
@@ -160,7 +167,8 @@ public:
   }
 
   //! Convert sRGB components into linear RGB using OpenGL specs formula.
-  static NCollection_Vec4<float> Convert_sRGB_To_LinearRGB(const NCollection_Vec4<float>& theRGB)
+  static NCollection_Vec4<float> Convert_sRGB_To_LinearRGB(
+    const NCollection_Vec4<float>& theRGB) noexcept
   {
     return NCollection_Vec4<float>(Quantity_Color::Convert_sRGB_To_LinearRGB(theRGB.r()),
                                    Quantity_Color::Convert_sRGB_To_LinearRGB(theRGB.g()),
index bb60c726c359e3152935f0c07eb584b834038d62..ec46c6abae3dd5c1f0a335c47c38c41af2aba57a 100644 (file)
 #include <Quantity_Period.hxx>
 #include <Standard_OutOfRange.hxx>
 
-static int month_table[12] = {31,  // January
-                              28,  // February
-                              31,  // March
-                              30,  // April
-                              31,  // May
-                              30,  // June
-                              31,  // July
-                              31,  // August
-                              30,  // September
-                              31,  // October
-                              30,  // November
-                              31}; // December
-
-static int SecondsByYear     = 365 * 24 * 3600; // Normal Year
-static int SecondsByLeapYear = 366 * 24 * 3600; // Leap Year
+#include "Quantity_TimeConstants.pxx"
+
+namespace
+{
+
+static constexpr int month_table[12] = {31,  // January
+                                        28,  // February
+                                        31,  // March
+                                        30,  // April
+                                        31,  // May
+                                        30,  // June
+                                        31,  // July
+                                        31,  // August
+                                        30,  // September
+                                        31,  // October
+                                        30,  // November
+                                        31}; // December
+
+static constexpr int SecondsByYear     = 365 * SECONDS_PER_DAY; // Normal Year
+static constexpr int SecondsByLeapYear = 366 * SECONDS_PER_DAY; // Leap Year
+
+// Returns the number of days in a month for a given year (handles leap years)
+constexpr Standard_Integer getDaysInMonth(const Standard_Integer theMonth,
+                                          const Standard_Integer theYear) noexcept
+{
+  if (theMonth == 2)
+  {
+    return Quantity_Date::IsLeap(theYear) ? 29 : 28;
+  }
+  return month_table[theMonth - 1];
+}
+} // anonymous namespace
 
 // -----------------------------------------
 // Initialize a date to January,1 1979 00:00
@@ -69,12 +86,7 @@ Standard_Boolean Quantity_Date::IsValid(const Standard_Integer mm,
   if (yy < 1979)
     return Standard_False;
 
-  if (Quantity_Date::IsLeap(yy))
-    month_table[1] = 29;
-  else
-    month_table[1] = 28;
-
-  if (dd < 1 || dd > month_table[mm - 1])
+  if (dd < 1 || dd > getDaysInMonth(mm, yy))
     return Standard_False;
 
   if (hh < 0 || hh > 23)
@@ -135,11 +147,6 @@ void Quantity_Date::SetValues(const Standard_Integer mm,
   if (!Quantity_Date::IsValid(mm, dd, yy, hh, mn, ss, mis, mics))
     throw Quantity_DateDefinitionError("Quantity_Date::Quantity_Date invalid parameters");
 
-  if (Quantity_Date::IsLeap(yy))
-    month_table[1] = 29;
-  else
-    month_table[1] = 28;
-
   mySec  = 0;
   myUSec = 0;
   for (i = 1979; i < yy; i++)
@@ -152,18 +159,18 @@ void Quantity_Date::SetValues(const Standard_Integer mm,
 
   for (i = 1; i < mm; i++)
   {
-    mySec += month_table[i - 1] * 3600 * 24;
+    mySec += getDaysInMonth(i, yy) * SECONDS_PER_DAY;
   }
 
-  mySec += 3600 * 24 * (dd - 1);
+  mySec += SECONDS_PER_DAY * (dd - 1);
 
-  mySec += 3600 * hh;
+  mySec += SECONDS_PER_HOUR * hh;
 
-  mySec += 60 * mn;
+  mySec += SECONDS_PER_MINUTE * mn;
 
   mySec += ss;
 
-  myUSec += mis * 1000;
+  myUSec += mis * USECS_PER_MSEC;
 
   myUSec += mics;
 }
@@ -183,13 +190,12 @@ void Quantity_Date::Values(Standard_Integer& mm,
                            Standard_Integer& mics) const
 {
 
-  Standard_Integer i, carry;
+  Standard_Integer carry;
 
   for (yy = 1979, carry = mySec;; yy++)
   {
     if (!Quantity_Date::IsLeap(yy))
     {
-      month_table[1] = 28; // normal year
       if (carry >= SecondsByYear)
         carry -= SecondsByYear;
       else
@@ -197,7 +203,6 @@ void Quantity_Date::Values(Standard_Integer& mm,
     }
     else
     {
-      month_table[1] = 29; // Leap year
       if (carry >= SecondsByLeapYear)
         carry -= SecondsByLeapYear;
       else
@@ -207,42 +212,20 @@ void Quantity_Date::Values(Standard_Integer& mm,
 
   for (mm = 1;; mm++)
   {
-    i = month_table[mm - 1] * 3600 * 24;
+    Standard_Integer i = getDaysInMonth(mm, yy) * SECONDS_PER_DAY;
     if (carry >= i)
       carry -= i;
     else
       break;
   }
 
-  i = 3600 * 24;
-  for (dd = 1;; dd++)
-  {
-    if (carry >= i)
-      carry -= i;
-    else
-      break;
-  }
-
-  for (hh = 0;; hh++)
-  {
-    if (carry >= 3600)
-      carry -= 3600;
-    else
-      break;
-  }
+  // Extract day within the month
+  // carry holds seconds since the beginning of the current month
+  dd = carry / SECONDS_PER_DAY + 1;    // Convert 0-based to 1-based day
+  carry -= (dd - 1) * SECONDS_PER_DAY; // Remove day component from carry
 
-  for (mn = 0;; mn++)
-  {
-    if (carry >= 60)
-      carry -= 60;
-    else
-      break;
-  }
-
-  ss = carry;
-
-  mis  = myUSec / 1000;
-  mics = myUSec - (mis * 1000);
+  extractTimeComponents(carry, hh, mn, ss);
+  extractMillisAndMicros(myUSec, mis, mics);
 }
 
 // ---------------------------------------------------------------------
@@ -252,9 +235,10 @@ void Quantity_Date::Values(Standard_Integer& mm,
 
 Quantity_Period Quantity_Date::Difference(const Quantity_Date& OtherDate)
 {
-
   Standard_Integer i1, i2;
 
+  // Special case: if this date is the epoch (Jan 1, 1979 00:00),
+  // return OtherDate as a period (time elapsed since epoch)
   if (mySec == 0 && myUSec == 0)
   {
     i1 = OtherDate.mySec;
@@ -266,28 +250,21 @@ Quantity_Period Quantity_Date::Difference(const Quantity_Date& OtherDate)
     i2 = myUSec - OtherDate.myUSec;
   }
 
-  if (i1 >= 0 && i2 < 0)
-  {
-    i1--;
-    i2 = 1000000 + i2;
-  }
-  else if (i1 < 0 && i2 >= 0)
+  // Normalize: handle microsecond underflow
+  normalizeSubtractionBorrow(i1, i2);
+
+  // Period is always absolute value, convert negative result
+  if (i1 < 0)
   {
-    i1 = Abs(i1);
+    i1 = -i1;
     if (i2 > 0)
     {
       i1--;
-      i2 = 1000000 - i2;
+      i2 = USECS_PER_SEC - i2;
     }
   }
-  else if (i1 < 0 && i2 < 0)
-  {
-    i1 = Abs(i1);
-    i2 = Abs(i2);
-  }
 
   Quantity_Period result(i1, i2);
-
   return (result);
 }
 
@@ -308,11 +285,7 @@ Quantity_Date Quantity_Date::Subtract(const Quantity_Period& During)
   result.mySec -= ss;
   result.myUSec -= mics;
 
-  if (result.mySec >= 0 && result.myUSec < 0)
-  {
-    result.mySec--;
-    result.myUSec = 1000000 + result.myUSec;
-  }
+  normalizeSubtractionBorrow(result.mySec, result.myUSec);
 
   if (result.mySec < 0)
     throw Quantity_DateDefinitionError(
@@ -332,11 +305,7 @@ Quantity_Date Quantity_Date::Add(const Quantity_Period& During)
   During.Values(result.mySec, result.myUSec);
   result.mySec += mySec;
   result.myUSec += myUSec;
-  if (result.myUSec >= 1000000)
-  {
-    result.mySec++;
-    result.myUSec -= 1000000;
-  }
+  normalizeAdditionOverflow(result.mySec, result.myUSec);
   return (result);
 }
 
@@ -346,8 +315,8 @@ Quantity_Date Quantity_Date::Add(const Quantity_Period& During)
 // ----------------------------------------------------------------------
 Standard_Integer Quantity_Date::Year()
 {
-  Standard_Integer dummy, year;
-  Values(dummy, dummy, year, dummy, dummy, dummy, dummy, dummy);
+  Standard_Integer mm, dd, year, hh, mn, ss, mis, mics;
+  Values(mm, dd, year, hh, mn, ss, mis, mics);
   return (year);
 }
 
@@ -357,9 +326,9 @@ Standard_Integer Quantity_Date::Year()
 // ----------------------------------------------------------------------
 Standard_Integer Quantity_Date::Month()
 {
-  Standard_Integer dummy, month;
-  Values(month, dummy, dummy, dummy, dummy, dummy, dummy, dummy);
-  return (month);
+  Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics;
+  Values(mm, dd, yy, hh, mn, ss, mis, mics);
+  return (mm);
 }
 
 // ----------------------------------------------------------------------
@@ -369,9 +338,9 @@ Standard_Integer Quantity_Date::Month()
 
 Standard_Integer Quantity_Date::Day()
 {
-  Standard_Integer dummy, day;
-  Values(dummy, day, dummy, dummy, dummy, dummy, dummy, dummy);
-  return (day);
+  Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics;
+  Values(mm, dd, yy, hh, mn, ss, mis, mics);
+  return (dd);
 }
 
 // ----------------------------------------------------------------------
@@ -381,9 +350,9 @@ Standard_Integer Quantity_Date::Day()
 
 Standard_Integer Quantity_Date::Hour()
 {
-  Standard_Integer dummy, hour;
-  Values(dummy, dummy, dummy, hour, dummy, dummy, dummy, dummy);
-  return (hour);
+  Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics;
+  Values(mm, dd, yy, hh, mn, ss, mis, mics);
+  return (hh);
 }
 
 // ----------------------------------------------------------------------
@@ -393,9 +362,9 @@ Standard_Integer Quantity_Date::Hour()
 
 Standard_Integer Quantity_Date::Minute()
 {
-  Standard_Integer dummy, min;
-  Values(dummy, dummy, dummy, dummy, min, dummy, dummy, dummy);
-  return (min);
+  Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics;
+  Values(mm, dd, yy, hh, mn, ss, mis, mics);
+  return (mn);
 }
 
 // ----------------------------------------------------------------------
@@ -405,9 +374,9 @@ Standard_Integer Quantity_Date::Minute()
 
 Standard_Integer Quantity_Date::Second()
 {
-  Standard_Integer dummy, sec;
-  Values(dummy, dummy, dummy, dummy, dummy, sec, dummy, dummy);
-  return (sec);
+  Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics;
+  Values(mm, dd, yy, hh, mn, ss, mis, mics);
+  return (ss);
 }
 
 // ----------------------------------------------------------------------
@@ -417,9 +386,9 @@ Standard_Integer Quantity_Date::Second()
 
 Standard_Integer Quantity_Date::MilliSecond()
 {
-  Standard_Integer dummy, msec;
-  Values(dummy, dummy, dummy, dummy, dummy, dummy, msec, dummy);
-  return (msec);
+  Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics;
+  Values(mm, dd, yy, hh, mn, ss, mis, mics);
+  return (mis);
 }
 
 // ----------------------------------------------------------------------
@@ -429,47 +398,7 @@ Standard_Integer Quantity_Date::MilliSecond()
 
 Standard_Integer Quantity_Date::MicroSecond()
 {
-  Standard_Integer dummy, msec;
-  Values(dummy, dummy, dummy, dummy, dummy, dummy, dummy, msec);
-  return (msec);
-}
-
-// ----------------------------------------------------------------------
-// IsEarlier : Return true if the date is earlier than an other date
-// ~~~~~~~~~
-// ----------------------------------------------------------------------
-
-Standard_Boolean Quantity_Date::IsEarlier(const Quantity_Date& other) const
-{
-  if (mySec < other.mySec)
-    return Standard_True;
-  else if (mySec > other.mySec)
-    return Standard_False;
-  else
-    return ((myUSec < other.myUSec) ? Standard_True : Standard_False);
-}
-
-// ----------------------------------------------------------------------
-// IsLater : Return true if the date is later than an other date
-// ~~~~~~~
-// ----------------------------------------------------------------------
-
-Standard_Boolean Quantity_Date::IsLater(const Quantity_Date& other) const
-{
-  if (mySec > other.mySec)
-    return Standard_True;
-  else if (mySec < other.mySec)
-    return Standard_False;
-  else
-    return ((myUSec > other.myUSec) ? Standard_True : Standard_False);
-}
-
-// ----------------------------------------------------------------------
-// IsEqual : Return true if the date is the same than an other date
-// ~~~~~~~
-// ----------------------------------------------------------------------
-
-Standard_Boolean Quantity_Date::IsEqual(const Quantity_Date& other) const
-{
-  return ((myUSec == other.myUSec && mySec == other.mySec) ? Standard_True : Standard_False);
+  Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics;
+  Values(mm, dd, yy, hh, mn, ss, mis, mics);
+  return (mics);
 }
index 8230ea16dff131e1b4a66301eb0f130c23e7e0bd..ea5afdee3cb6aa0a4f8602cc4e170388cbbf859d 100644 (file)
@@ -146,19 +146,37 @@ public:
 
   //! Returns TRUE if both <me> and <other> are equal.
   //! This method is an alias of operator ==.
-  Standard_EXPORT Standard_Boolean IsEqual(const Quantity_Date& anOther) const;
+  constexpr Standard_Boolean IsEqual(const Quantity_Date& anOther) const noexcept
+  {
+    return (myUSec == anOther.myUSec && mySec == anOther.mySec);
+  }
 
-  Standard_Boolean operator==(const Quantity_Date& anOther) const { return IsEqual(anOther); }
+  constexpr Standard_Boolean operator==(const Quantity_Date& anOther) const noexcept
+  {
+    return IsEqual(anOther);
+  }
 
   //! Returns TRUE if <me> is earlier than <other>.
-  Standard_EXPORT Standard_Boolean IsEarlier(const Quantity_Date& anOther) const;
+  constexpr Standard_Boolean IsEarlier(const Quantity_Date& anOther) const noexcept
+  {
+    return (mySec < anOther.mySec) || (mySec == anOther.mySec && myUSec < anOther.myUSec);
+  }
 
-  Standard_Boolean operator<(const Quantity_Date& anOther) const { return IsEarlier(anOther); }
+  constexpr Standard_Boolean operator<(const Quantity_Date& anOther) const noexcept
+  {
+    return IsEarlier(anOther);
+  }
 
   //! Returns TRUE if <me> is later then <other>.
-  Standard_EXPORT Standard_Boolean IsLater(const Quantity_Date& anOther) const;
+  constexpr Standard_Boolean IsLater(const Quantity_Date& anOther) const noexcept
+  {
+    return (mySec > anOther.mySec) || (mySec == anOther.mySec && myUSec > anOther.myUSec);
+  }
 
-  Standard_Boolean operator>(const Quantity_Date& anOther) const { return IsLater(anOther); }
+  constexpr Standard_Boolean operator>(const Quantity_Date& anOther) const noexcept
+  {
+    return IsLater(anOther);
+  }
 
   //! Checks the validity of a date - returns true if a
   //! date defined from the year yyyy, the month mm,
@@ -189,7 +207,7 @@ public:
   //! Returns true if a year is a leap year.
   //! The leap years are divisible by 4 and not by 100 except
   //! the years divisible by 400.
-  static Standard_Boolean IsLeap(const Standard_Integer yy)
+  static constexpr Standard_Boolean IsLeap(const Standard_Integer yy) noexcept
   {
     return ((yy % 4 == 0) && (yy % 100 != 0)) || (yy % 400) == 0;
   }
index 9a4f4c202f0ea9f1196e33d81bc911b92ef88385..6e91c0719fdcc1cb8a9971bfc1d1985953eadfa2 100644 (file)
@@ -21,6 +21,8 @@
 #include <Quantity_Period.hxx>
 #include <Quantity_PeriodDefinitionError.hxx>
 
+#include "Quantity_TimeConstants.pxx"
+
 // -----------------------------------------------------------
 // IsValid : Checks the validity of a date
 // With:
@@ -38,9 +40,7 @@ Standard_Boolean Quantity_Period::IsValid(const Standard_Integer dd,
                                           const Standard_Integer mis,
                                           const Standard_Integer mics)
 {
-
-  return ((dd < 0 || hh < 0 || mn < 0 || ss < 0 || mis < 0 || mics < 0) ? Standard_False
-                                                                        : Standard_True);
+  return (dd >= 0 && hh >= 0 && mn >= 0 && ss >= 0 && mis >= 0 && mics >= 0);
 }
 
 // -------------------------------------------------------------
@@ -51,8 +51,7 @@ Standard_Boolean Quantity_Period::IsValid(const Standard_Integer dd,
 // -------------------------------------------------------------
 Standard_Boolean Quantity_Period::IsValid(const Standard_Integer ss, const Standard_Integer mics)
 {
-
-  return ((ss < 0 || mics < 0) ? Standard_False : Standard_True);
+  return (ss >= 0 && mics >= 0);
 }
 
 // -------------------------------------------------------------
@@ -98,15 +97,10 @@ void Quantity_Period::Values(Standard_Integer& dd,
                              Standard_Integer& mics) const
 {
   Standard_Integer carry = mySec;
-  dd                     = carry / (24 * 3600);
-  carry -= dd * 24 * 3600;
-  hh = carry / 3600;
-  carry -= 3600 * hh;
-  mn = carry / 60;
-  carry -= mn * 60;
-  ss   = carry;
-  mis  = myUSec / 1000;
-  mics = myUSec - (mis * 1000);
+  dd                     = carry / SECONDS_PER_DAY;
+  carry -= dd * SECONDS_PER_DAY;
+  extractTimeComponents(carry, hh, mn, ss);
+  extractMillisAndMicros(myUSec, mis, mics);
 }
 
 // -------------------------------------------------------------
@@ -131,7 +125,8 @@ void Quantity_Period::SetValues(const Standard_Integer dd,
                                 const Standard_Integer mils,
                                 const Standard_Integer mics)
 {
-  SetValues((dd * 24 * 3600) + (hh * 3600) + (60 * mn) + ss, mils * 1000 + mics);
+  SetValues((dd * SECONDS_PER_DAY) + (hh * SECONDS_PER_HOUR) + (SECONDS_PER_MINUTE * mn) + ss,
+            mils * USECS_PER_MSEC + mics);
 }
 
 // -------------------------------------------------------------
@@ -146,11 +141,7 @@ void Quantity_Period::SetValues(const Standard_Integer ss, const Standard_Intege
 
   mySec  = ss;
   myUSec = mics;
-  while (myUSec > 1000000)
-  {
-    myUSec -= 1000000;
-    mySec++;
-  }
+  normalizeAdditionOverflow(mySec, myUSec);
 }
 
 // -------------------------------------------------------------
@@ -164,25 +155,19 @@ Quantity_Period Quantity_Period::Subtract(const Quantity_Period& OtherPeriod) co
   result.mySec -= OtherPeriod.mySec;
   result.myUSec -= OtherPeriod.myUSec;
 
-  if (result.mySec >= 0 && result.myUSec < 0)
-  {
-    result.mySec--;
-    result.myUSec = 1000000 + result.myUSec;
-  }
-  else if (result.mySec < 0 && result.myUSec >= 0)
+  normalizeSubtractionBorrow(result.mySec, result.myUSec);
+
+  // Handle negative result (convert to absolute value)
+  // Note: after normalization, myUSec is always in [0, 999999]
+  if (result.mySec < 0)
   {
     result.mySec = Abs(result.mySec);
     if (result.myUSec > 0)
     {
       result.mySec--;
-      result.myUSec = 1000000 - result.myUSec;
+      result.myUSec = USECS_PER_SEC - result.myUSec;
     }
   }
-  else if (result.mySec < 0 && result.myUSec < 0)
-  {
-    result.mySec  = Abs(result.mySec);
-    result.myUSec = Abs(result.myUSec);
-  }
   return (result);
 }
 
@@ -196,51 +181,6 @@ Quantity_Period Quantity_Period::Add(const Quantity_Period& OtherPeriod) const
   Quantity_Period result(mySec, myUSec);
   result.mySec += OtherPeriod.mySec;
   result.myUSec += OtherPeriod.myUSec;
-  if (result.myUSec > 1000000)
-  {
-    result.myUSec -= 1000000;
-    result.mySec++;
-  }
+  normalizeAdditionOverflow(result.mySec, result.myUSec);
   return (result);
 }
-
-// -------------------------------------------------------------
-// IsEqual : returns true if two periods are equal
-// ~~~~~~~
-// -------------------------------------------------------------
-Standard_Boolean Quantity_Period::IsEqual(const Quantity_Period& OtherPeriod) const
-{
-
-  return ((mySec == OtherPeriod.mySec && myUSec == OtherPeriod.myUSec) ? Standard_True
-                                                                       : Standard_False);
-}
-
-// -------------------------------------------------------------
-// IsShorter : returns true if a date is shorter then an other
-// ~~~~~~~~~   date
-// -------------------------------------------------------------
-Standard_Boolean Quantity_Period::IsShorter(const Quantity_Period& OtherPeriod) const
-{
-
-  if (mySec < OtherPeriod.mySec)
-    return Standard_True;
-  else if (mySec > OtherPeriod.mySec)
-    return Standard_False;
-  else
-    return ((myUSec < OtherPeriod.myUSec) ? Standard_True : Standard_False);
-}
-
-// -------------------------------------------------------------
-// IsLonger : returns true if a date is longer then an other
-// ~~~~~~~~   date
-// -------------------------------------------------------------
-Standard_Boolean Quantity_Period::IsLonger(const Quantity_Period& OtherPeriod) const
-{
-
-  if (mySec > OtherPeriod.mySec)
-    return Standard_True;
-  else if (mySec < OtherPeriod.mySec)
-    return Standard_False;
-  else
-    return ((myUSec > OtherPeriod.myUSec) ? Standard_True : Standard_False);
-}
index 483b7e7642331ea8ec76c41472a03bd60f0faaa1..e781c176c1acfb942eb8fc631e795aa5b792be20 100644 (file)
@@ -112,19 +112,37 @@ public:
   Quantity_Period operator+(const Quantity_Period& anOther) const { return Add(anOther); }
 
   //! Returns TRUE if both <me> and <other> are equal.
-  Standard_EXPORT Standard_Boolean IsEqual(const Quantity_Period& anOther) const;
+  constexpr Standard_Boolean IsEqual(const Quantity_Period& anOther) const noexcept
+  {
+    return (mySec == anOther.mySec && myUSec == anOther.myUSec);
+  }
 
-  Standard_Boolean operator==(const Quantity_Period& anOther) const { return IsEqual(anOther); }
+  constexpr Standard_Boolean operator==(const Quantity_Period& anOther) const noexcept
+  {
+    return IsEqual(anOther);
+  }
 
   //! Returns TRUE if <me> is shorter than <other>.
-  Standard_EXPORT Standard_Boolean IsShorter(const Quantity_Period& anOther) const;
+  constexpr Standard_Boolean IsShorter(const Quantity_Period& anOther) const noexcept
+  {
+    return (mySec < anOther.mySec) || (mySec == anOther.mySec && myUSec < anOther.myUSec);
+  }
 
-  Standard_Boolean operator<(const Quantity_Period& anOther) const { return IsShorter(anOther); }
+  constexpr Standard_Boolean operator<(const Quantity_Period& anOther) const noexcept
+  {
+    return IsShorter(anOther);
+  }
 
   //! Returns TRUE if <me> is longer then <other>.
-  Standard_EXPORT Standard_Boolean IsLonger(const Quantity_Period& anOther) const;
-
-  Standard_Boolean operator>(const Quantity_Period& anOther) const { return IsLonger(anOther); }
+  constexpr Standard_Boolean IsLonger(const Quantity_Period& anOther) const noexcept
+  {
+    return (mySec > anOther.mySec) || (mySec == anOther.mySec && myUSec > anOther.myUSec);
+  }
+
+  constexpr Standard_Boolean operator>(const Quantity_Period& anOther) const noexcept
+  {
+    return IsLonger(anOther);
+  }
 
   //! Checks the validity of a Period in form (dd,hh,mn,ss,mil,mic)
   //! With:
diff --git a/src/FoundationClasses/TKernel/Quantity/Quantity_TimeConstants.pxx b/src/FoundationClasses/TKernel/Quantity/Quantity_TimeConstants.pxx
new file mode 100644 (file)
index 0000000..2add6db
--- /dev/null
@@ -0,0 +1,96 @@
+// Copyright (c) 2025 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _Quantity_TimeConstants_HeaderFile
+#define _Quantity_TimeConstants_HeaderFile
+
+//! @file Quantity_TimeConstants.pxx
+//! Internal header providing shared time conversion constants and helper functions
+//! for Quantity_Date and Quantity_Period classes.
+
+#include <Standard_Integer.hxx>
+
+namespace
+{
+
+//! Time conversion constants
+constexpr int SECONDS_PER_MINUTE = 60;
+constexpr int SECONDS_PER_HOUR   = 3600;  // 60 * 60
+constexpr int SECONDS_PER_DAY    = 86400; // 24 * 3600
+
+//! Microsecond conversion constants
+constexpr int USECS_PER_MSEC = 1000;    // microseconds per millisecond
+constexpr int USECS_PER_SEC  = 1000000; // microseconds per second
+
+//! Extracts milliseconds and remaining microseconds from total microseconds
+//! @param theUSec total microseconds
+//! @param theMis output: milliseconds part
+//! @param theMics output: remaining microseconds part
+inline void extractMillisAndMicros(const Standard_Integer theUSec,
+                                   Standard_Integer&      theMis,
+                                   Standard_Integer&      theMics) noexcept
+{
+  theMis  = theUSec / USECS_PER_MSEC;
+  theMics = theUSec - (theMis * USECS_PER_MSEC);
+}
+
+//! Extracts hours, minutes, and seconds from remaining seconds in a day
+//! @param theCarry input/output: seconds to extract from, updated with remainder
+//! @param theHH output: hours
+//! @param theMN output: minutes
+//! @param theSS output: seconds
+inline void extractTimeComponents(Standard_Integer& theCarry,
+                                  Standard_Integer& theHH,
+                                  Standard_Integer& theMN,
+                                  Standard_Integer& theSS) noexcept
+{
+  theHH = theCarry / SECONDS_PER_HOUR;
+  theCarry -= SECONDS_PER_HOUR * theHH;
+  theMN = theCarry / SECONDS_PER_MINUTE;
+  theCarry -= theMN * SECONDS_PER_MINUTE;
+  theSS = theCarry;
+}
+
+//! Normalizes time values when microseconds overflow into seconds
+//! (handles addition overflow: myUSec >= 1000000)
+//! Uses division for O(1) complexity
+//! @param theSec input/output: seconds, incremented if overflow occurs
+//! @param theUSec input/output: microseconds, normalized to 0..999999
+inline void normalizeAdditionOverflow(Standard_Integer& theSec, Standard_Integer& theUSec) noexcept
+{
+  if (theUSec >= USECS_PER_SEC)
+  {
+    const Standard_Integer overflow = theUSec / USECS_PER_SEC;
+    theSec += overflow;
+    theUSec -= overflow * USECS_PER_SEC;
+  }
+}
+
+//! Normalizes time values when microseconds require borrowing from seconds
+//! (handles subtraction borrow: myUSec < 0)
+//! Uses ceiling division for O(1) complexity
+//! @param theSec input/output: seconds, decremented if borrow occurs
+//! @param theUSec input/output: microseconds, normalized to 0..999999
+inline void normalizeSubtractionBorrow(Standard_Integer& theSec, Standard_Integer& theUSec) noexcept
+{
+  if (theUSec < 0)
+  {
+    const Standard_Integer borrow = (-theUSec + USECS_PER_SEC - 1) / USECS_PER_SEC;
+    theSec -= borrow;
+    theUSec += borrow * USECS_PER_SEC;
+  }
+}
+
+} // anonymous namespace
+
+#endif // _Quantity_TimeConstants_HeaderFile