How to convert an official DateTime to the astronomically exact DateTime? - date

I've been trying to figure out how to convert a birthday (DateTime) to the astronomically "exact" DateTime value. Timezone: UTC+1.
Example:
My friend was born 1984-01-27 11:35
1984 is a leap year. But 1700, 1800 and 1900 were not leap years. So until the 29. February of the year 2000 we are running behind in astronomoically exact time. In 1984 we are "almost" one day behind. So the astronomoically exact time would be after the official DateTime of my friend's birth, right?
These are the Gregorian calendar tweaks I know of:
Every year has 365 days
Every 4th year is a leap year (= has 366 days instead of 365)
Every 100th year is not a leap year
Every 400th year is a leap year (dispite the previous rule)
The additional day is added at the end of February (February has 29 days in a leap year)
Astronomoically a year has 365,2422 days.
Which means that a day is 24,0159254794 hours long.
A time value where the official and astronomoical times are "exactly" the same would be 2000-03-01T00:00:00, right?
So one would need to figure out how big the discrepancy between the official time and the astronomically exact time is at a given official time.
I've been thinking about it for hours, until my head started hurting. I figured I'll share my headache with you. Maybe you guys know any time library that can calculate this?

I came up with a "solution" that seems to be fairly accurate enough. Here's what it does:
The method starts at 1600-03-01T00:00. 18 years after Pope Gregor XIII. (after whom our Gregorian Calendar system is named) fixed the Julian Calendar (named after Julius Caesar) in 1582 by declaring that after the 4th October (Thursday) the next day would be the 15th October (Friday) - so there is actually no 5th to 14th October 1582 in history books - and also adding the 100th and 400th year rules to the calendar system.
The method sums up the discrepany between the official date and the exact date until the given date is reached.
At leap years it applies the correction added by Pope Gregor XIII. It does so at the end of February.
Code:
public static DateTime OfficialDateTimeToExactDateTime(DateTime dtOfficial)
{
const double dExactDayLengthInHours = 24.0159254794;
DateTime dtParse = new DateTime(1600, 3, 1, 0, 0, 0);
double dErrorInHours = 0.0;
while (dtParse <= dtOfficial)
{
dErrorInHours += dExactDayLengthInHours - 24;
dtParse = dtParse.AddDays(1);
if (dtParse.Month == 3 && dtParse.Day == 1 &&
((dtParse.Year % 4 == 0 && dtParse.Year % 100 != 0) ||
(dtParse.Year % 400 == 0)) )
{
dErrorInHours -= 24;
}
}
dErrorInHours += ((double)dtOfficial.Hour + (double)dtOfficial.Minute / 60 + (double)dtOfficial.Second / 3600) * (dExactDayLengthInHours - 24);
return dtOfficial.AddHours(dErrorInHours * -1);
}
I did some sanity testing:
If you pass a date before 2000-03-01T00:00 you get a negative correction. Because we measure days shorter as they in fact are.
If you pass a date after 2000-03-01T00:00 you get a positive correction. This is because 2000 is a leap year (while 1700, 1800 and 1900 are not), but the correction applied is too big. In 24 x 400 = 4800 years the correction would be about one day too big. So in the year 1600 + 4800 = 6400 (if man is still alive), you would need to delcare 6400 a non-leap year, despite the rules of the Gregorian calendar.

Related

How to calculate days between two dates only if they belong to a certain month

For example the date range is
28-01-2022 and 20-03-2022
I want to count separately the days that belong to january (3) february (27) and march (19)
I would really appreciate the help!!
In python, you can do the following:
import calendar
import datetime
from dateutil import relativedelta
start_str = "1-03-2022"
end_str = "20-12-2024"
start = datetime.datetime.strptime(start_str, "%d-%m-%Y")
end = datetime.datetime.strptime(end_str, "%d-%m-%Y")
days = []
while start.year <= end.year and start.month <= end.month:
if start.year == end.year and start.month == end.month:
days.append(end.day - start.day)
else:
days.append(calendar.monthrange(
start.year, start.month)[1]-start.day+1)
start = start.replace(day=1)
start += relativedelta.relativedelta(months=1)
print(*days)
This includes the start day and excludes the end date, meaning that in your example, 4 days belong to January and 19 to March. Does February have 27 days only?

print first monday of every week of current month Flutter/Dart

I have a calendar in my Flutter App and I need to print a list of the weeks in the current month. But rather than starting on the 1st day of each month, it needs to start with the first Monday of the month (e.g. 05 April 2021 as the first Monday of April 2021). Then I need to print out the following weeks in that month, again starting from Monday. This includes the days of the next month that the final week of the current month follows on from (e.g. 26 April 2021 - 02 May 2021). It should print like this:
05 Apr - 11 Apr
12 Apr - 18 Apr
19 Apr - 25 Apr
26 Apr - 02 May
Start by figuring out how to find how many days there are from a given weekday to the Monday on or after that day. Examples help; if the given weekday is:
Monday, add 0 days.
Tuesday, add 6 days.
Wednesday, add 5 days.
... etc. ...
Sunday, add 1 day.
We could make a lookup table, but we also can devise that the offset (in days) from a given weekday to the Monday on or after that day has the form (7 - x) % 7
where x corresponds to the given weekday. We'd want that value to be 0 for Monday, 1 for Tuesday, and so on, until 6 for Sunday. Dart's DateTime.weekday uses values 1 (DateTime.monday) through 7 (DateTime.sunday), so we can easily map that to the value we want via DateTime.weekday - DateTime.monday.
Once we compute that offset, we can find the first day of the current month, add that offset to find the first Monday of the month, and then you can iteratively add 7 days until you reach the next month, and we can use DateFormat from package:intl to format the dates the way you want:
import 'package:intl/intl.dart';
String formatDate(DateTime dateTime) => DateFormat('dd MMM').format(dateTime);
void main() {
var now = DateTime.now();
var firstOfMonth = DateTime(now.year, now.month, 1);
var firstMonday =
firstOfMonth.addCalendarDays((7 - (firstOfMonth.weekday - DateTime.monday)) % 7);
var currentMonday = firstMonday;
while (currentMonday.month == now.month) {
var nextMonday = currentMonday.addCalendarDays(7);
var nextSunday = nextMonday.addCalendarDays(-1);
print('${formatDate(currentMonday)} - ${formatDate(nextSunday)}');
currentMonday = nextMonday;
}
}
See https://stackoverflow.com/a/68216029/ for the implementation of the addCalendarDays extension method.

Swift Number Of Weeks in Month

I'm using Swift 5. I'm trying to get the number of weeks in a month. Jan 2021 prints 6 weeks, but May prints 5 weeks. I'm thinking the problem is firstWeekday is set to 1. Is there a way to get the number of weeks in a given month without setting firstWeekday?
var localCalendar = Calendar.current
localCalendar.firstWeekday = 1
let weekRange = localCalendar.range(of: .weekOfMonth, in: .month, for: dateFormatter.date(from: monthName)!)
if let weekRange = weekRange {
print("\(monthName) has \(weekRange.count) weeks.")
}
It seems you do not want the number of weeks in a month at all. You want the number of rows in a calendar printout.
The formula for the number of rows needed to represent a month on a standard Gregorian-style calendar is as follows.
Start with the number of days in the month. Add to that the number of blank days before the start of the month, beginning with Sunday. For example, January 2021 is 31 + 5 = 36, because it's 31 days long but 5 days (Sunday thru Thursday) are not part of it before the start. To put it another way: the first day of January 2021 is a Friday; that is day 5 of the week if we call Sunday day 0, so we get 31 + 5.
Now integer-divide by 7. We need at least that number of rows. So (31+5)/7 using integer division is 5. The question is: is that all the rows we need?
To find out, get the remainder of that division. If it is not zero, add another row. So (31+5)%7 is 1, which tells us that one more day needs to be accommodated so we need another row. That makes 6.
Thus:
// `startingOn` pretends that Sunday is day 0
func rowsNeededForMonthWith(numberOfDays n: Int, startingOn i: Int) -> Int {
let (quot,rem) = (n+i).quotientAndRemainder(dividingBy: 7)
return quot + (rem == 0 ? 0 : 1)
}
Here are some quick sanity tests:
// January 2021
rowsNeededForMonthWith(numberOfDays: 31, startingOn: 5) // 6
// But suppose it had 30 days?
rowsNeededForMonthWith(numberOfDays: 30, startingOn: 5) // 5
// Or suppose it had started on Thursday? (cf July 2021)
rowsNeededForMonthWith(numberOfDays: 31, startingOn: 4) // 5

Serial date number since "Jan 1, 0000": is this -1 BC or +1 CE?

I was working on dates in Matlab and Octave, and the "serial date number" format is documented as
A single number equal to the number of days since January 0, 0000 in the proleptic ISO calendar (specifying use of the Gregorian calendar).
In Octave, they document
Return the date/time input as a serial day number, with Jan 1, 0000 defined as day 1.
The gregorian calender does not use a year zero. But Matlab and Octave refer to the year zero. Does this mean that they refer to year -1 BC, as in the astronomical year numbering?
Days before "October 15, 1582" are "wrong by as much as eleven days", according to the octave manual, which is considerably smaller than a complete year. So I'm trying to sort out this ambiguity.
Firstly, note that the MATLAB and Octave definitions are equivalent
[MATLAB] N = "number of days since Jan 0, 0000" ⇔ [OCTAVE] "Jan 1, 0000 is day 1"
Since N = 1 on day 1.
The Wikipedia page on "Year Zero" (which you linked to) offers this:
[...] the year 1 BC is followed by AD 1. However, there is a year zero in astronomical year numbering (where it coincides with the Julian year 1 BC) and in ISO 8601:2004 (where it coincides with the Gregorian year 1 BC), as well as in all Buddhist and Hindu calendars.
MATLAB and Octave appear to have followed the ISO standard, as stated in the datetime docs:
datetime arrays represent points in time using the proleptic ISO calendar
So the year zero, and hence the datenum value of 1 days, coincides with the first day of 1BC.
Per the definitions at the top of this answer
"day 1"
= 1/Jan/0000
= datenum(1)
= datetime( 1, 'ConvertFrom', 'datenum' )
= datetime( 0, 0, 1 )
We can test using datenum (number of days) and datetime (datetime type object)
datenum( 0, 0, 1 ) % = 1, as defined by the docs
datetime( 1, 'ConvertFrom', 'datenum' )
% = 1/Jan/0000 00:00:00
datetime( 1 + 366, 'ConvertFrom', 'datenum' )
% = 1/Jan/0001 00:00:00
% First day of year 1 after 366 days (leap year 0000 + 1 for Jan 1 )

How can I convert a timestamp to a user-friendly time string

I want to be able to present "today" and "yesterday" for recent dates in my application. I've got a date formatter in use currently to show dates (retrieved from data records) and will keep using this for anything more than a couple of days old. I just really like the way the SMS app in the iPhone shows dates for recent messages and would like to emulate this.
The time-stamps that I have to work with are generated on a server that the phone downloads the data records from. All times are therefore generated at UTC (i.e. GMT) time.
I've been fiddling about with this for a while the solutions I've devised just seem horribly long-winded.
Can anyone suggest how to implement a method that could do this?
Cheers - Steve.
If this is a web app, you might find PrettyDate useful. I made a vb.net implementation that could easily be converted to another language:
Public Function formatDate(ByVal time As DateTime) As String
Dim datediff As TimeSpan = Now.Subtract(time)
Dim days As Integer = datediff.TotalDays
If days < 1 Then
Dim seconds As Integer = datediff.TotalSeconds
Select Case seconds
Case 0 To 60
Return "just now"
Case 61 To 120
Return "1 minute ago"
Case 121 To 3600
Return Math.Floor(seconds / 60) & " minutes ago"
Case 3601 To 7200
Return "1 hour ago"
Case 7201 To 86400
Return Math.Floor(seconds / 3600) & " hours ago"
End Select
ElseIf days < 31 Then
Select Case days
Case 1
Return "yesterday"
Case 2 To 7
Return days & " days ago"
Case Is > 7
Return Math.Ceiling(days / 7) & " weeks ago"
End Select
Else : Return time.ToString("MM/dd/yyyy")
End If
End Function