From 95985ef0327fd11b4cc14d3dde0081677c7d27ed Mon Sep 17 00:00:00 2001 From: krdee1 Date: Sun, 16 Feb 2025 21:12:54 -0800 Subject: [PATCH] initial commit of Gregorian/IFC date conversion tools and unit tests --- DAYSOFWEEK.m | 5 +++ IFC.m | 53 +++++++++++++++++++++++++++ IFCmonths.m | 53 +++++++++++++++++++++++++++ MONTHS.m | 5 +++ isLeap.m | 30 +++++++++++++++ test_IFC.m | 63 ++++++++++++++++++++++++++++++++ test_IFCmonths.m | 95 ++++++++++++++++++++++++++++++++++++++++++++++++ test_isLeap.m | 35 ++++++++++++++++++ 8 files changed, 339 insertions(+) create mode 100644 DAYSOFWEEK.m create mode 100644 IFC.m create mode 100644 IFCmonths.m create mode 100644 MONTHS.m create mode 100644 isLeap.m create mode 100644 test_IFC.m create mode 100644 test_IFCmonths.m create mode 100644 test_isLeap.m diff --git a/DAYSOFWEEK.m b/DAYSOFWEEK.m new file mode 100644 index 0000000..0e1fb68 --- /dev/null +++ b/DAYSOFWEEK.m @@ -0,0 +1,5 @@ +classdef DAYSOFWEEK < uint8 + enumeration + Saturday (0), Sunday (1), Monday (2), Tuesday (3), Wednesday (4), Thursday (5), Friday (6), YearDay (7), LeapDay (8) + end +end \ No newline at end of file diff --git a/IFC.m b/IFC.m new file mode 100644 index 0000000..3424bd0 --- /dev/null +++ b/IFC.m @@ -0,0 +1,53 @@ +function fixed = IFC(gregorian) +arguments (Input) + gregorian (:,1) datetime +end +arguments (Output) + fixed (:, 4) table +end +% gregorian datetime 1 arg input? +% y/m/d 3 args input? +% TODO inputs validation + +% prepare output struct +fixed = struct('Year', [], 'Month', '', 'Day', [], 'DayOfWeek', ''); +fixed = repmat(fixed, length(gregorian), 1); + +% eliminate time from input, only date matters +gregorian.Hour = 0; gregorian.Minute = 0; gregorian.Second = 0; + +% year remains constant +Year = num2cell(uint16(year(gregorian'))); +[fixed.Year] = Year{:}; + +% find number of days into the current year +Days = days(gregorian - datetime([fixed.Year] - 1, 12, 31)'); + +% Calculate IFC month, number of days into that month, and if leap day +% and/or year day occured +[nMonths, nDays, leaped, yeared] = IFCmonths(Days, [fixed.Year]'); + +% write month to output +Month = num2cell(MONTHS(nMonths)); +[fixed.Month] = Month{:}; + +% write number of days in month to output +nDays = num2cell(nDays); +[fixed.Day] = nDays{:}; + +% write day of week to output +DOW = num2cell(DAYSOFWEEK(mod([fixed.Day]', 7))); +[fixed.DayOfWeek] = DOW{:}; + +% Write year days as special days of the week +yearDays = num2cell(repmat(DAYSOFWEEK(7), 1, sum(yeared))); +[fixed(yeared).DayOfWeek] = yearDays{:}; % try 2 year/leap days in a vector input + +% Write leap days as special days of the week +leapDayFlags = ~yeared & leaped & [fixed.Month]' == MONTHS(14) & [fixed.Day]' == 0; +leapDays = num2cell(repmat(DAYSOFWEEK(8), 1, sum(leapDayFlags))); +[fixed(leapDayFlags).DayOfWeek] = leapDays{:}; + +% convert output to table +fixed = struct2table(fixed); +end \ No newline at end of file diff --git a/IFCmonths.m b/IFCmonths.m new file mode 100644 index 0000000..054420d --- /dev/null +++ b/IFCmonths.m @@ -0,0 +1,53 @@ +function [nMonths, Days, leaped, yeared] = IFCmonths(Days, Year) +arguments (Input) + Days (:, 1) uint16 + Year (:, 1) uint16 +end +arguments (Output) + nMonths (:, 1) uint8 + Days (:, 1) uint8 + leaped (:, 1) logical + yeared (:, 1) logical +end + +% intialize output to first month +nMonths = ones(size(Days)); + +% initialize output saying a leap day happened to false +leaped = false(size(Days)); + +% initialize output saying a year day happened to false +yeared = false(size(Days)); + +% check if it's a leap year +leap = isLeap(Year); + +% while more days than 1 month, add months to nMonths +excess = Days > 28; +while any(excess) + % Subtract 28 days and add one month + Days(excess) = Days(excess) - 28; + nMonths(excess) = nMonths(excess) + 1; + + % Find dates which are reaching the leap day (if it exists) + leaping = (leap & nMonths == 7); + % subtract leap day before proceeding to add another month + Days(leaping) = Days(leaping) - 1; + % Set leap day output to true + leaped(leaping) = true; + % Set the month to 14 (no month) if there are no days left + % meaning the last day is the leap day + % (which is not in any month) + noneLeft = ~Days; + nMonths(noneLeft) = 14; + % continue subtracting days and adding months as needed + + % find excess days remaining for next iteration + excess = Days > 28; +end + +% check for if a year day occured +yearing = (nMonths == 14 & Days == 1); +yeared(yearing) = true; % Year day achieved, set flag +Days(yearing) = 0; % decrement Days since year day was taken +end \ No newline at end of file diff --git a/MONTHS.m b/MONTHS.m new file mode 100644 index 0000000..1c4f9e1 --- /dev/null +++ b/MONTHS.m @@ -0,0 +1,5 @@ +classdef MONTHS < uint8 + enumeration + January (1), February (2), March (3), April (4), May (5), June (6), Sol (7), July (8), August (9), September (10), October (11), November (12), December (13), None (14) + end +end \ No newline at end of file diff --git a/isLeap.m b/isLeap.m new file mode 100644 index 0000000..b4d38cc --- /dev/null +++ b/isLeap.m @@ -0,0 +1,30 @@ +function flag = isLeap(Year) +arguments (Input) + Year (:, 1) uint16 +end +arguments (Output) + flag (:, 1) logical +end + +% flag = NaN(size(Year)); + +isDivisibleBy4 = ~mod(Year, 4); +isNotDivisibleBy100 = logical(mod(Year, 100)); +isDivisibleBy400 = ~mod(Year, 400); + +flag = isDivisibleBy4 & (isDivisibleBy400 | isNotDivisibleBy100); + +% if ~mod(Year, 4) +% if ~mod(Year, 100) +% if ~mod(Year, 400) +% flag = true; +% else +% flag = false; +% end +% else +% flag = true; +% end +% else +% flag = false; +% end +end \ No newline at end of file diff --git a/test_IFC.m b/test_IFC.m new file mode 100644 index 0000000..31c1e7e --- /dev/null +++ b/test_IFC.m @@ -0,0 +1,63 @@ +classdef test_IFC < matlab.unittest.TestCase + + methods(TestClassSetup) + % Shared setup for the entire test class + end + + methods(TestMethodSetup) + % Setup for each test + end + + methods(Test) + % Test methods + + function test_today(testCase) + ifc = IFC(datetime('28-Dec-2024')); + testCase.verifyEqual(ifc.Year, uint16(2024)); + testCase.verifyEqual(ifc.Month, MONTHS(13)); + testCase.verifyEqual(ifc.Day, uint8(26)); + testCase.verifyEqual(ifc.DayOfWeek, DAYSOFWEEK(5)); + end + + function test_yearday_noleap(testCase) + ifc = IFC(datetime('31-Dec-2023')); + testCase.verifyEqual(ifc.Year, uint16(2023)); + testCase.verifyEqual(ifc.Month, MONTHS(14)); + testCase.verifyEqual(ifc.Day, uint8(0)); + testCase.verifyEqual(ifc.DayOfWeek, DAYSOFWEEK(7)); + end + + function test_yearday_leap(testCase) + ifc = IFC(datetime('31-Dec-2020')); + testCase.verifyEqual(ifc.Year, uint16(2020)); + testCase.verifyEqual(ifc.Month, MONTHS(14)); + testCase.verifyEqual(ifc.Day, uint8(0)); + testCase.verifyEqual(ifc.DayOfWeek, DAYSOFWEEK(7)); + end + + function test_leapday_noleap(testCase) + ifc = IFC(datetime('17-Jun-2003')); + testCase.verifyEqual(ifc.Year, uint16(2003)); + testCase.verifyEqual(ifc.Month, MONTHS(6)); + testCase.verifyEqual(ifc.Day, uint8(28)); + testCase.verifyEqual(ifc.DayOfWeek, DAYSOFWEEK(0)); + end + + function test_leapday_leap(testCase) + ifc = IFC(datetime('17-Jun-2008')); + testCase.verifyEqual(ifc.Year, uint16(2008)); + testCase.verifyEqual(ifc.Month, MONTHS(14)); + testCase.verifyEqual(ifc.Day, uint8(0)); + testCase.verifyEqual(ifc.DayOfWeek, DAYSOFWEEK(8)); + end + + function test_all_vectorized(testCase) + ifc = IFC(datetime(["28-Dec-2024"; "31-Dec-2023"; "31-Dec-2020"; "17-Jun-2003"; "17-Jun-2008"])); + testCase.verifyEqual([ifc.Year], uint16([2024; 2023; 2020; 2003; 2008])); + testCase.verifyEqual([ifc.Month], MONTHS([13; 14; 14; 6; 14])); + testCase.verifyEqual([ifc.Day], uint8([26; 0; 0; 28; 0])); + testCase.verifyEqual([ifc.DayOfWeek], DAYSOFWEEK([5; 7; 7; 0; 8])); + end + end + +end \ No newline at end of file diff --git a/test_IFCmonths.m b/test_IFCmonths.m new file mode 100644 index 0000000..7a3aba3 --- /dev/null +++ b/test_IFCmonths.m @@ -0,0 +1,95 @@ +classdef test_IFCmonths < matlab.unittest.TestCase + + methods(TestClassSetup) + % Shared setup for the entire test class + end + + methods(TestMethodSetup) + % Setup for each test + end + + methods(Test) + % Test methods + + function test_jan1(testCase) + [nMonths, nDays, leaped, yeared] = IFCmonths(1, 2000); + testCase.verifyEqual(nMonths, uint8(1)); + testCase.verifyEqual(nDays, uint8(1)); + testCase.verifyFalse(leaped); + testCase.verifyFalse(yeared); + end + + function test_jan31(testCase) + [nMonths, nDays, leaped, yeared] = IFCmonths(31, 2000); + testCase.verifyEqual(nMonths, uint8(2)); + testCase.verifyEqual(nDays, uint8(3)); + testCase.verifyFalse(leaped); + testCase.verifyFalse(yeared); + end + + function test_jun17_leap(testCase) + [nMonths, nDays, leaped, yeared] = IFCmonths(168, 2000); + testCase.verifyEqual(nMonths, uint8(6)); + testCase.verifyEqual(nDays, uint8(28)); + testCase.verifyFalse(leaped); + testCase.verifyFalse(yeared); + end + + function test_jun17_noleap(testCase) + [nMonths, nDays, leaped, yeared] = IFCmonths(168, 2001); + testCase.verifyEqual(nMonths, uint8(6)); + testCase.verifyEqual(nDays, uint8(28)); + testCase.verifyFalse(leaped); + testCase.verifyFalse(yeared); + end + + function test_jun18_leap(testCase) + [nMonths, nDays, leaped, yeared] = IFCmonths(169, 2000); + testCase.verifyEqual(nMonths, uint8(14)); + testCase.verifyEqual(nDays, uint8(0)); + testCase.verifyTrue(leaped); + testCase.verifyFalse(yeared); + end + + function test_jun18_noleap(testCase) + [nMonths, nDays, leaped, yeared] = IFCmonths(169, 2001); + testCase.verifyEqual(nMonths, uint8(7)); + testCase.verifyEqual(nDays, uint8(1)); + testCase.verifyFalse(leaped); + testCase.verifyFalse(yeared); + end + + function test_dec31_noleap(testCase) + [nMonths, nDays, leaped, yeared] = IFCmonths(365, 2001); + testCase.verifyEqual(nMonths, uint8(14)); + testCase.verifyEqual(nDays, uint8(0)); + testCase.verifyFalse(leaped); + testCase.verifyTrue(yeared); + end + + function test_dec31_leap(testCase) + [nMonths, nDays, leaped, yeared] = IFCmonths(366, 2000); + testCase.verifyEqual(nMonths, uint8(14)); + testCase.verifyEqual(nDays, uint8(0)); + testCase.verifyTrue(leaped); + testCase.verifyTrue(yeared); + end + + function test_today(testCase) + [nMonths, nDays, leaped, yeared] = IFCmonths(363, 2024); + testCase.verifyEqual(nMonths, uint8(13)); + testCase.verifyEqual(nDays, uint8(26)); + testCase.verifyTrue(leaped); + testCase.verifyFalse(yeared); + end + + function test_vectorized(testCase) + [nMonths, nDays, leaped, yeared] = IFCmonths([1; 31; 168; 168; 169; 169; 365; 366; 363], [2000; 2000; 2000; 2001; 2000; 2001; 2001; 2000; 2024]); + testCase.verifyEqual(nMonths, uint8([1; 2; 6; 6; 14; 7; 14; 14; 13])); + testCase.verifyEqual(nDays, uint8([1; 3; 28; 28; 0; 1; 0; 0; 26])); + testCase.verifyEqual(leaped, [false; false; false; false; true; false; false; true; true]); + testCase.verifyEqual(yeared, [false; false; false; false; false; false; true; true; false]); + end + end + +end \ No newline at end of file diff --git a/test_isLeap.m b/test_isLeap.m new file mode 100644 index 0000000..b760347 --- /dev/null +++ b/test_isLeap.m @@ -0,0 +1,35 @@ +classdef test_isLeap < matlab.unittest.TestCase + + methods(TestClassSetup) + % Shared setup for the entire test class + end + + methods(TestMethodSetup) + % Setup for each test + end + + methods(Test) + % Test methods + + function test_basic_2001(testCase) + testCase.verifyFalse(isLeap(2001)); + end + + function test_basic_1996(testCase) + testCase.verifyTrue(isLeap(1996)); + end + + function test_exception1_2100(testCase) + testCase.verifyFalse(isLeap(2100)); + end + + function test_exception2_2000(testCase) + testCase.verifyTrue(isLeap(2000)); + end + + function test_vectorized_all(testCase) + testCase.verifyEqual([true; false; false; false; true; false], isLeap([2000; 1999; 1998; 1997; 1996; 2100])); + end + end + +end \ No newline at end of file