How to convert Julian dates to and from different representations using integer arithmetic - date

The question is how to convert dates between different representations using integer arithmetic, specifically between a "Days since year zero ( Jan 1, 0000 )" representation, and either Year/Day or Year/Month/Day forms, in the Julian calendar. The different representations are useful for date input, date display and date arithmetic.
Specifically, a date such as June 5th, 2020 is represented in Year/Month/Day representation as
2020 * 512 + 6 * 32 + 5
or in Year/Day representation as
2020 * 512 + 157 ( June 5th is day 157 in a leap year ).
Yesterday, I wanted to write code to do this, and surprisingly didn't find much help online, so I thought I would document my solution here ( I will be answering my own question ).

First here is code for converting from Year/Day to Days ( the language is very similar to Microsoft SQL Server T-SQL, except variable names do not begin with '#', and there is a 'bool" data type ) :
CREATE FUNCTION [date].[YearDayToDays]( yd int ) RETURNS int AS
BEGIN
-- Given a date in Year/Day representation stored as y * 512 + d where 1 <= d <= 366 ( so d is day in year )
-- returns the number of days since "day zero" (1 Jan 0000)
-- using the Gregorian calendar where days divisible by 4 are leap years, except if divisible by 100, except if divisible by 400.
DECLARE y int, d int, cycle int
-- Extract y and d from yd.
SET y = yd / 512, d = yd % 512 - 1
SET cycle = y / 400, y = y % 400 -- The Gregorian calendar repeats every 400 years.
-- Result days come from cycles, from years having at least 365 days, from leap years and finally d.
-- 146097 is the number of the days in a 400 year cycle ( 400 * 365 + 97 leap years ).
RETURN cycle * 146097
+ y * 365
+ ( y + 3 ) / 4 - ( y + 99 ) / 100 + ( y + 399 ) / 400
+ d
END
Now the opposite conversion, Days to Year/Day:
CREATE FUNCTION [date].[DaysToYearDay]( days int ) returns int as
begin
-- Given a date represented by the number of days since 1 Jan 0000
-- calculate a date in Year/Day representation stored as
-- y * 512 + d where d is 1..366
DECLARE y int, d int, cycle int
-- 146097 is the number of the days in a 400 year cycle ( 400 * 365 + 97 leap years )
SET cycle = days / 146097
SET days = days % 146097
SET y = days / 365
SET d = days % 365
-- Need to adjust d to allow for leap years.
-- Leap years are 0, 4, 8, 12 ... 96, not 100, 104 ... not 200... not 300, 400, 404 ... not 500.
-- Adjustment as function of y is 0 => 0, 1 => 1, 2 =>1, 3 => 1, 4 => 1, 5 => 2 ..
SET d = d - ( y + 3 ) / 4 - ( y + 99 ) / 100 + ( y + 399 ) / 400
IF d < 0
BEGIN
SET y = y - 1
SET d = d + CASE WHEN date.IsLeapYear( y ) THEN 366 ELSE 365 END
END
RETURN date.YearDay( cycle * 400 + y, d + 1 )
END
The auxiliary function date.IsLeapYear:
CREATE FUNCTION [date].[IsLeapYear]( y int ) RETURNS bool AS
BEGIN
RETURN y % 4 = 0 AND ( y % 100 != 0 OR y % 400 = 0 )
END
and date.YearDay :
CREATE FUNCTION [date].[YearMonthDay]( year int, month int, day int ) RETURNS int AS
BEGIN
RETURN year * 512 + month * 32 + day
END
Conversion from Year/Day to Year/Month/Day:
CREATE FUNCTION [date].[YearDayToYearMonthDay]( yd int ) returns int AS
BEGIN
DECLARE y int, d int, leap bool, fdm int, m int, dim int
SET y = yd / 512
SET d = yd % 512 - 1
SET leap = date.IsLeapYear( y )
-- Jan = 0..30, Feb = 0..27 or 0..28
IF NOT leap AND d >= 59 SET d = d + 1
SET fdm = CASE
WHEN d < 31 THEN 0 -- Jan
WHEN d < 60 THEN 31 -- Feb
WHEN d < 91 THEN 60 -- Mar
WHEN d < 121 THEN 91 -- Apr
WHEN d < 152 THEN 121 -- May
WHEN d < 182 THEN 152 -- Jun
WHEN d < 213 THEN 182 -- Jul
WHEN d < 244 THEN 213 -- Aug
WHEN d < 274 THEN 244 -- Sep
WHEN d < 305 THEN 274 -- Oct
WHEN d < 335 THEN 305 -- Nov
ELSE 335 -- Dec
END
SET dim = d - fdm
SET m = ( d - dim + 28 ) / 31
RETURN date.YearMonthDay( y, m+1, dim+1 )
END
The auxiliary function date.YearMonthDay:
CREATE FUNCTION [date].[YearMonthDay]( year int, month int, day int ) RETURNS int AS
BEGIN
RETURN year * 512 + month * 32 + day
END
Finally conversion from Year/Month/Day to Year/Day:
CREATE FUNCTION [date].[YearMonthDayToYearDay]( ymd int ) RETURNS int AS
BEGIN
DECLARE y int, m int, d int
-- Extract y, m, d from ymd
SET d = ymd % 32, m = ymd / 32
SET y = m / 16, m = m % 16
-- Incorporate m into d ( assuming Feb has 29 days ).
SET d = d + CASE
WHEN m = 1 THEN 0 -- Jan
WHEN m = 2 THEN 31 -- Feb
WHEN m = 3 THEN 60 -- Mar
WHEN m = 4 THEN 91 -- Apr
WHEN m = 5 THEN 121 -- May
WHEN m = 6 THEN 152 -- Jun
WHEN m = 7 THEN 182 -- Jul
WHEN m = 8 THEN 213 -- Aug
WHEN m = 9 THEN 244 -- Sep
WHEN m = 10 THEN 274 -- Oct
WHEN m = 11 THEN 305 -- Nov
ELSE 335 -- Dec
END
-- Allow for Feb being only 28 days in a non-leap-year.
IF m >= 3 AND NOT date.IsLeapYear( y ) SET d = d - 1
RETURN date.YearDay( y, d )
END
I hope it's useful to someone, and I hope it's correct - I have tested by generating test ranges of days to check the generated calendar looks correct, with the correct number of days in each month, especially February. The functions do not check whether the input is valid, that is assumed. This is part of a project to implement SQL in C#.

Related

Calculate days between two dates in Haskell from a custom date ADT

-- a)
leapYear :: Int -> Bool
leapYear n = (n `mod` 4 == 0) && ( (n `mod` 100 /= 0) || (n `mod` 400 == 0) )
-- b)
data Month = Jan | Feb | Mar | Apr | May | Aug | Jun | Jul | Sep | Oct | Nov | Dec
deriving (Enum, Eq, Show)
-- c)
type Day = Int
type Year = Int
data Date = Date Day Month Year
deriving Show
-- d)
dateIsValid :: Date -> Bool
dateIsValid (Date d m y) | y < 0 || y > 9999 = False
| d < 0 = False
| m `elem` [Jan, Mar, May, Jul, Aug, Oct, Dec] = d < 32
| m `elem` [Apr, Jun, Sep, Nov, Feb] = d < 31
| m == Feb = if leapYear y then d < 30 else d < 29
| otherwise = False
-- e)
daysBetween :: Date -> Date -> Int
daysBetween (Date a b c) (Date x y z) = if dateDay (Date a b c) > dateDay (Date x y z)
then dateDay (Date a b c) - dateDay (Date x y z)
else dateDay (Date x y z) - dateDay (Date a b c)
where monthDay m = case m of Jan -> 0
Feb -> 31
Mar -> 59
Apr -> 90
May -> 120
Jun -> 151
Jul -> 181
Aug -> 211
Sep -> 243
Oct -> 273
Nov -> 304
Dec -> 334
countLeapYears (Date d m y) | m `elem` [Jan, Feb, May] = (y-1) `div` 4 - (y-1) `div` 100 + (y-1) `div` 400
| otherwise = y `div` 4 - y `div` 100 + y `div` 400
dateDay (Date d m y) = d + monthDay m + (y-1)*365 + countLeapYears (Date d m y)
getMonthNr :: Month -> Int
getMonthNr m = case m of Jan -> 1
Feb -> 2
Mar -> 3
Apr -> 4
May -> 5
Jun -> 6
Jul -> 7
Aug -> 8
Sep -> 9
Oct -> 10
Nov -> 11
Dec -> 12
-- f)
data Dow = Mon | Tue | Wen | Thu | Fri | Sat | Sun
deriving Show
-- g)
weekday :: Date -> Dow
weekday (Date d m y) = case daysBetween (Date d m y) (Date 3 Jan 2000) `mod` 7 of 0 -> Sun
1 -> Mon
2 -> Tue
3 -> Wen
4 -> Thu
5 -> Fri
_ -> Sat
I have got this code where I need to calculate the number of days between two days and then find the weekday of that date. While this code is correct in finding the answer for the given examples in the exercises daysBetween (Date 4 Nov 2021) (Date 1 Jan 2000) → 7978 and January 1st, 2021 was a Friday, it shows problems with most other dates. One of my tests involved the date Date 13 Aug 2009 and Date 3 Jan 2000 where the error is one day. I am not allowed to use predefined functions to find the difference between two dates or the specific weekday. Is there something wrong with the logic I have used? Or is there another way to implement an algorithm that searches for the difference between two days?
getMonthNr can be ignored or not, since I wrote it down while experimenting and left it there in case I would need it.
Edit: After correcting the code a 1 day shift occurs when the days are a few years apart (almost a decade) or a 2 day shift when the days are centuries to a few millennia apart.
Do you just have Aug in the wrong place? It should be between Jul and Sep but it is between May and Jun in your code.

MATLAB create constant spacing between numbers on same line

Good morning,
I'm sure there's a built in function but I can't find it. I want to create static positioning for information being sent to a text document in MATLAB. For example:
height weight age favorite number
------------------------------------------------------------
60 140 24 9
30 45 3 10000000
48 100 9 19
9 7 1 1
currently, i'm just doing an fprint call with padded spaces to get it lined up, but the issue arises where having different length numbers causes the alignment to be off, like so:
height weight age favorite number
------------------------------------------------------------
60 140 24 9
30 45 3 10000000
48 100 9 19
1 7 1 1
Thanks in advance.
here's an example script that'll show what I mean:
fid1 = fopen('stackoverflowtest', 'w');
if fid1 < 3,
error('ERROR');
end;
fprintf(fid1, 'height weight age favorite number \n');
fprintf(fid1, '------------------------------------------------------------ \n');
height = 0;
weight = 10;
age = 100;
number = 3;
for i = 1:100
fprintf(fid1, "%d ', height);
fprintf(fid1, "%d ', weight);
fprintf(fid1, "%d ', age);
fprintf(fid1, "%d \n", number);
height = height + 3;
weight = weight + 6;
age = age - 1;
number = number + 23;
end
You can do this with the fprintf format specification, for example %-15d.
Here, the - is a flag which specifies left justification, and the 15 specifies how much space to leave around the representation.
We can reproduce your example with
A = [60 140 24 9
30 45 3 10000000
48 100 9 19
9 7 1 1];
fprintf('height weight age favorite number \n'),...
fprintf('------------------------------------------------------------ \n'),...
fprintf('%-15d %-15d %-12d %-15d \n',A')
which displays
height weight age favorite number
------------------------------------------------------------
60 140 24 9
30 45 3 10000000
48 100 9 19
9 7 1 1
EDIT: You can store this data as a table:
height = A(:,1)
weight = A(:,2);
age = A(:,3);
favourite_number = A(:,4);
tab1 = table(height, weight, age, favourite_number);
disp(tab1);
This prints to screen
height weight age favourite_number
______ ______ ___ ________________
60 140 24 9
30 45 3 1e+07
48 100 9 19
9 7 1 1
but I'm not sure how to save this representation to a file.

adding the values in a matrix based on the corresponding unique values of another matrix

e=[40 19 18 20 30 34 65 97 155 160];
If there is a minimum difference between two consecutive values (for e.g. (19,18), (30, 34) and (155,160)) then merge these values..
Similar values also...Whatever condition can be used to solve this..Kindly help to solve this..
Iteratively,
e = [ 40 19 18 20 30 34 65 97 155 160];
current = e + 1; % init
prev = e;
while ~isequal( current, prev )
prev = current;
d = [ diff( prev ) < 5 true]; % always keep the last one
current = prev( d );
end

Find the longest run of sequential integers in a vector

I have a routine that returns a list of integers as a vector.
Those integers come from groups of sequential numbers; for example, it may look like this:
vector = 6 7 8 12 13 14 15 26 27 28 29 30 55 56
Note that above, there are four 'runs' of numbers (6-8, 12-15, 26-30 & 55-56). What I'd like to do is forward the longest 'run' of numbers to a new vector. In this case, that would be the 26-30 run, so I'd like to produce:
newVector = 26 27 28 29 30
This calculation has to be performed many, many times on various vectors, so the more efficiently I can do this the better! Any wisdom would be gratefully received.
You can try this:
v = [ 6 7 8 12 13 14 15 26 27 28 29 30 55 56];
x = [0 cumsum(diff(v)~=1)];
v(x==mode(x))
This results in
ans =
26 27 28 29 30
Here is a solution to get the ball rolling . . .
vector = [6 7 8 12 13 14 15 26 27 28 29 30 55 56]
d = [diff(vector) 0]
maxSequence = 0;
maxSequenceIdx = 0;
lastIdx = 1;
while lastIdx~=find(d~=1, 1, 'last')
idx = find(d~=1, 1);
if idx-lastIdx > maxSequence
maxSequence = idx-lastIdx;
maxSequenceIdx = lastIdx;
end
d(idx) = 1;
lastIdx=idx;
end
output = vector(1+maxSequenceIdx:maxSequenceIdx+maxSequence)
In this example, the diff command is used to find consecutive numbers. When numbers are consecutive, the difference is 1. A while loop is then used to find the longest group of ones, and the index of this consecutive group is stored. However, I'm confident that this could be optimised further.
Without loops using diff:
vector = [6 7 8 12 13 14 15 26 27 28 29 30 55 56];
seqGroups = [1 find([1 diff(vector)]~=1) numel(vector)+1]; % beginning of group
[~, groupIdx] = max( diff(seqGroups)); % bigger group index
output = vector( seqGroups(groupIdx):seqGroups(groupIdx+1)-1)
output vector is
ans =
26 27 28 29 30
Without loops - should be faster
temp = find ( ([(vector(2:end) - vector(1:end-1))==1 0])==0);
[len,ind]=max(temp(2:end)-temp(1:end-1));
vec_out = vector(temp(ind)+1:temp(ind)+len)

Converting numbers between Number Bases

I'm working on a program that converts between number bases. For example Octal is 8, decimal is 10. Letters A to Z could be considered as base 26.
I want to convert a number like "A" into 0, Z into 25, "AA" into 27 and "BA" into 53.
Before I start coding I'm doing it on paper so I understand the process. To start out I'm trying to convert 533 to base 26.
What algorithm is best for doing this?
You need to assign a "digit" to each letter, like:
A = 0 N = 13
B = 1 O = 14
C = 2 P = 15
D = 3 Q = 16
E = 4 R = 17
F = 5 S = 18
G = 6 T = 19
H = 7 U = 20
I = 8 V = 21
J = 9 W = 22
K = 10 X = 23
L = 11 Y = 24
M = 12 Z = 25
Then, your {20,13} becomes UN.
Converting back is UN -> {20,13} -> (20 * 26 + 13) -> 52.
By way of further example, let's try the number 10163, just plucked out of the air at random.
Divide that by 26 until you get a number less than 26 (i.e., twice), and you get 15 with a fractional part of 0.03402366.
Multiply that by 26 and you get 0 with a fractional part of 0.88461516.
Multiply that by 26 and you get 23 (actually 22.99999416 on my calculator but, since the initial division was only two steps, we stop here - the very slight inaccuracy is due to the fact that the floating point numbers are being rounded).
So the "digits" are {15,0,23} which is the "number" PAX. Wow, what a coincidence?
To convert PAX back into decimal, its
P * 262 + A * 261 + X * 260
or
(15 * 676) + (0 * 26) + 23
= 10140 + 0 + 23
= 10163
Let's take a step back for a second, and look at decimal.
What does a number like "147" mean? Or rather, what do the characters '1', '4' and '7', when arranged like that, indicate?
There are ten digits in decimal, and after that, we add another digit to the left of the first, and so on as our number increases. So after "9" = 9*1, we get "10" = 1*10 + 0*1. So "147" is 1*10^2 + 4*10 + 7*1 = 147. Similarly, we can go backwards - 147/10^2 = 1, which maps to the character '1'. (147 % 10^2) / 10 = 4, which maps to the character '4'. And 147 % 10 = 7, which maps to the character '7'.
This works works for any base N - if we get the number 0, that maps to the first character in our set. The number 1 maps to the second character, and so on until the number N-1 maps to the last character in our set of digits.
You convert 20 and 13 to the symbols that represent 20 and 13 in your base 26 notation. It sounds like you are using the letters of the alphabet so, that would be UN (where A is 0 and Z is 25).
What language are you writing this in? If you're doing this in Perl you can use the CPAN module Math::Fleximal that I wrote many years ago while I was bored. If you're using a language with infinite precision integers, then life becomes much easier. All you have to do is take characters, convert them into an array of integers, then do the calculation to turn that into a number.