diff --git a/common/utilities/CMakeLists.txt b/common/utilities/CMakeLists.txt index 393270348a..4c88eacfab 100644 --- a/common/utilities/CMakeLists.txt +++ b/common/utilities/CMakeLists.txt @@ -7,4 +7,8 @@ target_sources(${LRS_TARGET} "${CMAKE_CURRENT_LIST_DIR}/time/stopwatch.h" "${CMAKE_CURRENT_LIST_DIR}/time/timer.h" "${CMAKE_CURRENT_LIST_DIR}/time/periodic_timer.h" + "${CMAKE_CURRENT_LIST_DIR}/time/work_week.h" + "${CMAKE_CURRENT_LIST_DIR}/time/work_week.cpp" + "${CMAKE_CURRENT_LIST_DIR}/time/l500/get-mfr-ww.h" + "${CMAKE_CURRENT_LIST_DIR}/time/l500/get-mfr-ww.cpp" ) diff --git a/common/utilities/time/l500/get-mfr-ww.cpp b/common/utilities/time/l500/get-mfr-ww.cpp new file mode 100644 index 0000000000..050cdc02d2 --- /dev/null +++ b/common/utilities/time/l500/get-mfr-ww.cpp @@ -0,0 +1,41 @@ +//// License: Apache 2.0. See LICENSE file in root directory. +//// Copyright(c) 2020 Intel Corporation. All Rights Reserved. + +#include "get-mfr-ww.h" +#include +#include + + +namespace utilities { +namespace time { +namespace l500 { +// The Serial Number format is PYWWXXXX: +// P – Site Name(ex.“F” for Fabrinet) +// Y – Year(ex.“9” for 2019, "0" for 2020, , "1" for 2021 ..etc) +// WW – Work Week +// XXXX – Sequential number +utilities::time::work_week get_manufacture_work_week( const std::string & serial ) +{ + if( serial.size() != 8 ) + throw std::runtime_error( "Invalid serial number \"" + serial + "\" length" ); + unsigned Y = serial[1] - '0'; // Converts char to int, '0'-> 0, '1'-> 1, ... + unsigned man_year = 0; + // using Y from serial number to get manufactoring year + if( Y == 9 ) + man_year = 2019; + else if( Y < 9 ) + man_year = 2020 + Y; + else + throw std::runtime_error( "Invalid serial number \"" + serial + "\" year" ); + // using WW from serial number to get manufactoring work week + unsigned WW_tens = serial[2] - '0'; + unsigned WW_singles = serial[3] - '0'; + unsigned man_ww = ( (WW_tens)*10 ) + WW_singles; + if (man_ww > 53) + throw std::runtime_error( "Invalid serial number \"" + serial + "\" work week" ); + return utilities::time::work_week( man_year, man_ww ); +} + +} // namespace l500 +} // namespace time +} // namespace utilities diff --git a/common/utilities/time/l500/get-mfr-ww.h b/common/utilities/time/l500/get-mfr-ww.h new file mode 100644 index 0000000000..e020f2846a --- /dev/null +++ b/common/utilities/time/l500/get-mfr-ww.h @@ -0,0 +1,18 @@ +// License: Apache 2.0. See LICENSE file in root directory. +// Copyright(c) 2020 Intel Corporation. All Rights Reserved. + +#pragma once + +#include +#include "../work_week.h" + + +namespace utilities { +namespace time { +namespace l500 { + +utilities::time::work_week get_manufacture_work_week( const std::string & serial ); + +} // namespace l500 +} // namespace time +} // namespace utilities diff --git a/common/utilities/time/work_week.cpp b/common/utilities/time/work_week.cpp new file mode 100644 index 0000000000..2d4e72ad29 --- /dev/null +++ b/common/utilities/time/work_week.cpp @@ -0,0 +1,114 @@ +//// License: Apache 2.0. See LICENSE file in root directory. +//// Copyright(c) 2020 Intel Corporation. All Rights Reserved. + +#include "work_week.h" +#include +#include +#include + +static int work_weeks_between_years( unsigned year, unsigned other_year ) +{ + unsigned Jan_1_JDN = utilities::time::jdn( year, 1, 1 ); + unsigned other_Jan_1_JDN = utilities::time::jdn( other_year, 1, 1 ); + // We need to compare between weeks, so we get the JDN of the Sunday of the wanted weeks + // (JDN + 1) % 7 gives us the day of the week for the JDN. 0 -> Sun, 1 -> Mon ... + int start_of_year_first_work_week = Jan_1_JDN - ( ( Jan_1_JDN + 1 ) % 7 ); + int start_of_other_year_first_work_week = other_Jan_1_JDN - ( ( other_Jan_1_JDN + 1 ) % 7 ); + // Subtracting the JDNs results in the number of days between 2 dates + return ( ( start_of_year_first_work_week - start_of_other_year_first_work_week ) / 7 ); +} + +static unsigned days_in_month( unsigned year, unsigned month ) +{ + if( month == 2 ) + { + if( ( year % 400 == 0 ) || ( year % 4 == 0 && year % 100 != 0 ) ) + return 29; + else + return 28; + } + // Months with 30 days + else if( month == 4 || month == 6 || month == 9 || month == 11 ) + return 30; + else + return 31; +} + +namespace utilities { +namespace time { + +work_week::work_week( unsigned year, unsigned ww ) +{ + if( ww == 0 || ww > work_weeks_between_years( year + 1, year ) ) + { + std::ostringstream message; + message << "Invalid work week given: " << year << " doesn't have a work week " << ww; + throw std::runtime_error(message.str()); + } + _year = year; + _ww = ww; +} + +work_week::work_week( const std::time_t & t ) +{ + auto time = std::localtime( &t ); + + _year = time->tm_year + 1900; // The tm_year field contains the number of years + // since 1900, we add 1900 to get current year + int Jan_1_wday = ( time->tm_wday - time->tm_yday ) % 7; + if( Jan_1_wday < 0 ) + Jan_1_wday += 7; + _ww = ( ( time->tm_yday + Jan_1_wday ) / 7 ) + 1; + // As explained in the header file, the last few days of a year may belong to work week 1 of the + // following year. This is the case if we are at the end of December, and the next year start + // before the week ends + if( _ww == 53 && ( 31 - time->tm_mday ) < ( 6 - time->tm_wday ) ) + { + _year++; + _ww = 1; + } +} + +unsigned work_week::get_year() const +{ + return _year; +} + +unsigned work_week::get_work_week() const +{ + return _ww; +} + +int work_week::operator-( const work_week & other ) const +{ + return work_weeks_between_years( _year, other._year ) + ( this->_ww - other._ww ); +} + +work_week work_week::current() +{ + auto t = std::time( nullptr ); + return work_week( t ); +} + +unsigned get_work_weeks_since( const work_week & start ) +{ + auto now = work_week::current(); + unsigned age = now - start; + return age; +} + +// Calculation is according to formula found in the link provided in the headet file +unsigned jdn( unsigned year, unsigned month, unsigned day ) +{ + if (month == 0 || day == 0 || month > 12 || day > days_in_month(year, month)) + { + std::ostringstream message; + message << "Invalid date given: " << day << "/" << month << "/" << year; + throw std::runtime_error(message.str()); + } + return ( ( 1461 * ( year + 4800 + ( ( (int)month - 14 ) / 12 ) ) ) / 4 ) + + ( ( 367 * ( month - 2 - ( 12 * ( ( (int)month - 14 ) / 12 ) ) ) ) / 12 ) + - ( ( 3 * ( ( year + 4900 + ( ( (int)month - 14 ) / 12 ) ) / 100 ) ) / 4 ) + day - 32075; +} +} // namespace time +} // namespace utilities diff --git a/common/utilities/time/work_week.h b/common/utilities/time/work_week.h new file mode 100644 index 0000000000..e5804ee79f --- /dev/null +++ b/common/utilities/time/work_week.h @@ -0,0 +1,44 @@ +// License: Apache 2.0. See LICENSE file in root directory. +// Copyright(c) 2020 Intel Corporation. All Rights Reserved. + + +/* +This class is for any use that requires work weeks. +Read more on work weeks at https://en.wikipedia.org/wiki/ISO_week_date +Note that we use the US accounting method, in which the 1st of January is always in work week 1. +for example, 1/1/2022 is a Saturday, so that whole week is work week 1, i.e 26-31/12/2021 are +actualy in work week 1 of 2022 +*/ +#pragma once + +#include +#include + + +namespace utilities { +namespace time { + +class work_week +{ + unsigned _year; + unsigned _ww; //starts at 1 + +public: + work_week(unsigned year, unsigned ww); + work_week( const std::time_t & time ); + static work_week current(); + work_week(const work_week & ) = default; + + unsigned get_year() const; + unsigned get_work_week() const; + int operator-( const work_week & ww ) const; +}; + +// Returns the number of work weeks since given time +unsigned get_work_weeks_since( const work_week & start ); + +// Calulates and returns the Julian day number of the given date. +//read more in https://en.wikipedia.org/wiki/Julian_day +unsigned jdn( unsigned year, unsigned month, unsigned day ); +} // namespace time +} // namespace utilities diff --git a/include/librealsense2/h/rs_types.h b/include/librealsense2/h/rs_types.h index a9a553a397..e79a7bd2d2 100644 --- a/include/librealsense2/h/rs_types.h +++ b/include/librealsense2/h/rs_types.h @@ -70,20 +70,26 @@ typedef struct rs2_intrinsics /** \brief Video DSM (Digital Sync Module) parameters for calibration (same layout as in FW ac_depth_params) This is the block in MC that converts angles to dimensionless integers reported to MA (using "DSM coefficients"). */ -typedef struct rs2_dsm_params -{ - unsigned long long timestamp; /**< system_clock::time_point::time_since_epoch().count() */ - unsigned short version; /**< MAJOR<<12 | MINOR<<4 | PATCH */ - unsigned char model; /**< rs2_dsm_correction_model */ - unsigned char flags[5]; /**< TBD, now 0s */ - float h_scale; /**< the scale factor to horizontal DSM scale thermal results */ - float v_scale; /**< the scale factor to vertical DSM scale thermal results */ - float h_offset; /**< the offset to horizontal DSM offset thermal results */ - float v_offset; /**< the offset to vertical DSM offset thermal results */ - float rtd_offset; /**< the offset to the Round-Trip-Distance delay thermal results */ - unsigned char temp_x2; /**< the temperature recorded times 2 (ldd for depth; hum for rgb) */ - unsigned char reserved[11]; -} rs2_dsm_params; +#pragma pack( push, 1 ) + typedef struct rs2_dsm_params + { + unsigned long long timestamp; /**< system_clock::time_point::time_since_epoch().count() */ + unsigned short version; /**< MAJOR<<12 | MINOR<<4 | PATCH */ + unsigned char model; /**< rs2_dsm_correction_model */ + unsigned char flags[5]; /**< TBD, now 0s */ + float h_scale; /**< the scale factor to horizontal DSM scale thermal results */ + float v_scale; /**< the scale factor to vertical DSM scale thermal results */ + float h_offset; /**< the offset to horizontal DSM offset thermal results */ + float v_offset; /**< the offset to vertical DSM offset thermal results */ + float rtd_offset; /**< the offset to the Round-Trip-Distance delay thermal results */ + unsigned char temp_x2; /**< the temperature recorded times 2 (ldd for depth; hum for rgb) */ + float mc_h_scale; /**< the scale factor to horizontal LOS coefficients in MC */ + float mc_v_scale; /**< the scale factor to vertical LOS coefficients in MC */ + unsigned char weeks_since_calibration; /**< time (in weeks) since factory calibration */ + unsigned char ac_weeks_since_calibaration; /**< time (in weeks) between factory calibration and last AC event */ + unsigned char reserved[1]; + } rs2_dsm_params; +#pragma pack( pop ) typedef enum rs2_dsm_correction_model { diff --git a/src/l500/l500-device.cpp b/src/l500/l500-device.cpp index faf564df62..c2072c15b3 100644 --- a/src/l500/l500-device.cpp +++ b/src/l500/l500-device.cpp @@ -26,6 +26,8 @@ #include "ac-trigger.h" #include "algo/depth-to-rgb-calibration/debug.h" #include "../common/utilities/time/periodic_timer.h" +#include "../common/utilities/time/work_week.h" +#include "../common/utilities/time/l500/get-mfr-ww.h" @@ -126,6 +128,24 @@ namespace librealsense register_info(RS2_CAMERA_INFO_PRODUCT_LINE, "L500"); register_info(RS2_CAMERA_INFO_CAMERA_LOCKED, _is_locked ? "YES" : "NO"); + // If FW supportes the SET_AGE command, we update the age of the device in weeks to aid projection of aging + if( ( _fw_version >= firmware_version( "1.5.4.0" ) ) ) + { + try + { + auto manufacture + = utilities::time::l500::get_manufacture_work_week( optic_serial ); + auto age + = utilities::time::get_work_weeks_since( manufacture ); + command cmd( fw_cmd::SET_AGE, (uint8_t)age ); + _hw_monitor->send( cmd ); + } + catch( ... ) + { + LOG_ERROR( "Failed to set units age" ); + } + } + configure_depth_options(); } @@ -222,14 +242,23 @@ namespace librealsense { if( status == RS2_CALIBRATION_SUCCESSFUL ) { + rs2_dsm_params new_dsm_params = _autocal->get_dsm_params(); + // We update the age of the device in weeks and the time between factory + // calibration and last AC to aid projection + auto manufacture = utilities::time::l500::get_manufacture_work_week( + get_info( RS2_CAMERA_INFO_SERIAL_NUMBER ) ); + auto age + = utilities::time::get_work_weeks_since( manufacture ); + new_dsm_params.weeks_since_calibration = (uint8_t)age; + new_dsm_params.ac_weeks_since_calibaration = (uint8_t)age; + // We override the DSM params first, because it can throw if the parameters // are exceeding spec! This may throw!! - get_depth_sensor().override_dsm_params( _autocal->get_dsm_params() ); - + get_depth_sensor().override_dsm_params( new_dsm_params ); auto & color_sensor = *get_color_sensor(); color_sensor.override_intrinsics( _autocal->get_raw_intrinsics() ); color_sensor.override_extrinsics( _autocal->get_extrinsics() ); - color_sensor.set_k_thermal_intrinsics(_autocal->get_thermal_intrinsics()); + color_sensor.set_k_thermal_intrinsics( _autocal->get_thermal_intrinsics() ); } notify_of_calibration_change( status ); } ); diff --git a/src/l500/l500-private.h b/src/l500/l500-private.h index 865c88846d..71eeac1a08 100644 --- a/src/l500/l500-private.h +++ b/src/l500/l500-private.h @@ -72,7 +72,8 @@ namespace librealsense RGB_INTRINSIC_GET = 0x81, RGB_EXTRINSIC_GET = 0x82, FALL_DETECT_ENABLE = 0x9D, // Enable (by default) free-fall sensor shutoff (0=disable; 1=enable) - GET_SPECIAL_FRAME = 0xA0 // Request auto-calibration (0) special frames (#) + GET_SPECIAL_FRAME = 0xA0, // Request auto-calibration (0) special frames (#) + SET_AGE = 0x5B // Sets the age of the unit in weeks }; #pragma pack(push, 1) @@ -143,14 +144,14 @@ namespace librealsense // Write a table to firmware template< typename T > - void write_fw_table( hw_monitor& hwm, uint16_t const table_id, T const & table ) + void write_fw_table( hw_monitor& hwm, uint16_t const table_id, T const & table, uint16_t const version = 0x0100 ) { command cmd( fw_cmd::WRITE_TABLE, 0 ); cmd.data.resize( sizeof( table_header ) + sizeof( table ) ); table_header * h = (table_header *)cmd.data.data(); - h->major = 1; - h->minor = 0; + h->major = version >> 8; + h->minor = version & 0xFF; h->table_id = table_id; h->table_size = sizeof( T ); h->reserved = 0xFFFFFFFF; diff --git a/unit-tests/utilities/time/test-l500-mfr.cpp b/unit-tests/utilities/time/test-l500-mfr.cpp new file mode 100644 index 0000000000..19f882caf4 --- /dev/null +++ b/unit-tests/utilities/time/test-l500-mfr.cpp @@ -0,0 +1,58 @@ +// License: Apache 2.0. See LICENSE file in root directory. +// Copyright(c) 2020 Intel Corporation. All Rights Reserved. + +// Unit Test Goals: +// Test the get_manufacture_work_week function + +//#cmake:add-file ../../../common/utilities/time/work_week.h +//#cmake:add-file ../../../common/utilities/time/work_week.cpp +//#cmake:add-file ../../../common/utilities/time/l500/get-mfr-ww.h +//#cmake:add-file ../../../common/utilities/time/l500/get-mfr-ww.cpp + + +#include "common.h" +#include "../common/utilities/time/work_week.h" +#include "../common/utilities/time/l500/get-mfr-ww.h" + +#define INVALID_MESSAGE( serial, invalid ) "Invalid serial number \"" + serial + "\" " + invalid + +using namespace utilities::time; + +// Test description: +// > Test error throws fron the get_manufacture_work_week function +TEST_CASE( "test get_manufacture_work_week", "[work_week]" ) +{ + std::string serial = "xxx"; + CHECK_THROWS_WITH( l500::get_manufacture_work_week( serial ), + INVALID_MESSAGE( serial, "length" ) ); + + serial = "xxxxxxxx"; + CHECK_THROWS_WITH( l500::get_manufacture_work_week( serial ), + INVALID_MESSAGE( serial, "year" ) ); + + serial = "x9xxxxxx"; + CHECK_THROWS_WITH( l500::get_manufacture_work_week( serial ), + INVALID_MESSAGE( serial, "work week" ) ); + + serial = "x92xxxxx"; + CHECK_THROWS_WITH( l500::get_manufacture_work_week( serial ), + INVALID_MESSAGE( serial, "work week" ) ); + + serial = "x9x5xxxx"; + CHECK_THROWS_WITH( l500::get_manufacture_work_week( serial ), + INVALID_MESSAGE( serial, "work week" ) ); + + serial = "x962xxxx"; + CHECK_THROWS_WITH( l500::get_manufacture_work_week( serial ), + INVALID_MESSAGE( serial, "work week" ) ); + + serial = "x955xxxx"; + CHECK_THROWS_WITH( l500::get_manufacture_work_week( serial ), + INVALID_MESSAGE( serial, "work week" ) ); + + serial = "x925xxxx"; + work_week mfr(0,1); + CHECK_NOTHROW( mfr = l500::get_manufacture_work_week( serial ) ); + CHECK(mfr.get_work_week() == 25); + CHECK(mfr.get_year() == 2019); +} \ No newline at end of file diff --git a/unit-tests/utilities/time/test-work_week.cpp b/unit-tests/utilities/time/test-work_week.cpp new file mode 100644 index 0000000000..254f6ae593 --- /dev/null +++ b/unit-tests/utilities/time/test-work_week.cpp @@ -0,0 +1,146 @@ +// License: Apache 2.0. See LICENSE file in root directory. +// Copyright(c) 2020 Intel Corporation. All Rights Reserved. + +//#cmake:add-file ../../../common/utilities/time/work_week.h +//#cmake:add-file ../../../common/utilities/time/work_week.cpp + + +#include "common.h" +#include "../common/utilities/time/work_week.h" +#include + +using namespace utilities::time; + +// Test description: +// > Test the c'tor +TEST_CASE( "test work_week c'tor", "[work_week]" ) +{ + tm set_time = { 0 }; + set_time.tm_year = 117; // 2017 + set_time.tm_mday = 1; + set_time.tm_isdst = -1; + // The work week of the first day of the year is 1 because work weeks are 1 based + work_week first_day( std::mktime( &set_time ) ); + CHECK( first_day.get_work_week() == 1 ); + set_time.tm_mday = 7; + set_time.tm_wday = 6; + // This should be the last day of the first work week because 2017 started on Sunday + work_week sixth_day( std::mktime( &set_time ) ); + CHECK( sixth_day.get_work_week() == 1 ); + set_time.tm_mday = 8; + set_time.tm_wday = 0; + // This should be the first day of the second work week + work_week seventh_day( std::mktime( &set_time ) ); + CHECK( seventh_day.get_work_week() == 2 ); + // If the year didn't start on a Sunday (for example 2018), Jan 7th should be in work week 2 + set_time.tm_year = 118; // 2018 + set_time.tm_mday = 7; + work_week second_week( std::mktime( &set_time ) ); + CHECK( second_week.get_work_week() == 2 ); + + // Checking valid and invalid wprk weeks + CHECK_THROWS_WITH( work_week( 2020, 0 ), + "Invalid work week given: 2020 doesn't have a work week 0" ); + CHECK_THROWS_WITH( work_week( 2020, 53 ), + "Invalid work week given: 2020 doesn't have a work week 53" ); + CHECK_NOTHROW( work_week( 2016, 53 ) ); + + // checking edge case of 31st of December is actualy part of work week 1 of the next year + set_time.tm_year = 117; // 2017 + set_time.tm_mon = 11; // December (it's 0 based) + set_time.tm_mday = 31; + work_week belons_to_next_year( std::mktime( &set_time ) ); + CHECK( belons_to_next_year.get_work_week() == 1 ); + CHECK( belons_to_next_year.get_year() == 2018 ); +} + +// Test description: +// > Test all work_week creation options are equivalent +TEST_CASE( "test work_week c'tor equivalence", "[work_week]" ) +{ + // Compare current with c'tor using current time + auto current_ww = work_week::current(); + auto now = std::time( nullptr ); + work_week now_to_work_week( now ); + CHECK( current_ww.get_year() == now_to_work_week.get_year() ); + CHECK( current_ww.get_work_week() == now_to_work_week.get_work_week() ); + + // Test copy c'tor + work_week copy( current_ww ); + CHECK( current_ww.get_year() == copy.get_year() ); + CHECK( current_ww.get_work_week() == copy.get_work_week() ); + + // Compare manual c'tor with c'tor from given time + tm set_time = { 0 }; + set_time.tm_year = 117; // 2017 + set_time.tm_mday = 1; + set_time.tm_isdst = -1; + work_week manual( 2017, 1 ); + work_week from_time( std::mktime( &set_time ) ); + CHECK( manual.get_year() == from_time.get_year() ); + CHECK( manual.get_work_week() == from_time.get_work_week() ); +} + +// Test description: +// > Test the subtraction operator for work_week +TEST_CASE( "test work_week subtraction", "[work_week]" ) +{ + // Simple cases + CHECK( work_week( 2019, 7 ) - work_week( 2018, 7 ) == 52 ); + CHECK( work_week( 2019, 7 ) - work_week( 2018, 3 ) == 56 ); + CHECK( work_week( 2019, 7 ) - work_week( 2018, 10 ) == 49 ); + + // Simple cases with negative results + CHECK( work_week( 2018, 7 ) - work_week( 2019, 7 ) == -52 ); + CHECK( work_week( 2018, 3 ) - work_week( 2019, 7 ) == -56 ); + CHECK( work_week( 2018, 10 ) - work_week( 2019, 7 ) == -49 ); + + // 2016 had 53 work weeks + CHECK( work_week( 2017, 7 ) - work_week( 2016, 7 ) == 53 ); + CHECK( work_week( 2017, 7 ) - work_week( 2016, 3 ) == 57 ); + CHECK( work_week( 2017, 7 ) - work_week( 2016, 10 ) == 50 ); + CHECK( work_week( 2017, 1 ) - work_week( 2016, 52 ) == 2 ); + + // Subtraction of consecutive weeks + CHECK( work_week( 2017, 1 ) - work_week( 2016, 53 ) == 1 ); + tm Jan_1_2017 = { 0 }; + Jan_1_2017.tm_year = 117; // 2017 + Jan_1_2017.tm_mon = 0; // January (it's 0 based) + Jan_1_2017.tm_mday = 1; + Jan_1_2017.tm_wday = 0; + Jan_1_2017.tm_yday = 0; + Jan_1_2017.tm_isdst = -1; + tm Dec_31_2016 = { 0 }; + Dec_31_2016.tm_year = 116; // 2016 + Dec_31_2016.tm_mon = 11; // December (it's 0 based) + Dec_31_2016.tm_mday = 31; + Dec_31_2016.tm_wday = 5; + Dec_31_2016.tm_yday = 365; + Dec_31_2016.tm_isdst = -1; + CHECK( work_week( std::mktime( &Jan_1_2017 ) ) - work_week( std::mktime( &Dec_31_2016 ) ) + == 1 ); + + // Subtracting days from the same work week. December 31st 2017 is part of work week 1 of 2018 + tm Jan_1_2018 = { 0 }; + Jan_1_2018.tm_year = 118; // 2018 + Jan_1_2018.tm_mon = 0; // January (it's 0 based) + Jan_1_2018.tm_mday = 1; + Jan_1_2018.tm_isdst = -1; + tm Dec_31_2017 = { 0 }; + Dec_31_2017.tm_year = 117; // 2017 + Dec_31_2017.tm_mon = 11; // December (it's 0 based) + Dec_31_2017.tm_mday = 31; + Dec_31_2017.tm_isdst = -1; + CHECK( work_week( std::mktime( &Jan_1_2018 ) ) - work_week( std::mktime( &Dec_31_2017 ) ) + == 0 ); +} + +// Test description: +// > Test the get_work_weeks_since function +TEST_CASE( "test get_work_weeks_since", "[work_week]" ) +{ + CHECK( get_work_weeks_since( work_week::current() ) == 0 ); + + work_week zero( 0, 1 ); + CHECK( get_work_weeks_since( zero ) == work_week::current() - zero ); +} \ No newline at end of file