References for implementing calendar functionality in an embedded system? - c

I have an embedded system that currently keeps track of seconds until an event is supposed to occur using a real-time clock driven by a watch crystal.
Now it needs to keep track of the actual date and time. So, I need to be able to calculate the day, month, year, hour, minute and second from a start date/time and offset in seconds.
Could anyone point me in the right direction for taking into account leap years, daylight savings time (DST) and other complications?
Hardware solutions are not an option as this feature is being added to an existing product. An RTC peripheral is integrated into the MCU chosen for the next generation device.

The C Snippets archive has some date and time functions. Update: unfortunately, the C snippets archive is now defunct. I have updated the link to point to the web archive of the page.
See also "Julian day", Wikipedia, which includes formulas for Julian date calculation.
A "julian date calculation" Google search should uncover more if you want to search further.

Calendar code can be a bit complex - if the C runtime library you're using doesn't have such support built-in (and some way to integrate your clock counter to it) you might consider looking at the code in P.J. Plauger's "The Standard C Library" and adapting it to your needs.

I'm bored, couldn't resist trying a solution. Here's a prototype in ruby - should be clear enough to translate to C.
Given offset and a start date stored as: Baseyear, Baseday, Basesec where day 0 = Jan1,
you can calculate the date as
#initialize outputs
year= Baseyear
day = Baseday
sec = Basesec+offset
#days & seconds remaining in the current year
is_leap = is_leap_year(year)
days_remaining = 365+(is_leap ? 1 : 0) - day
secs_remaining = SEC_PER_DAY*days_remaining
#advance by year
while (sec>=secs_remaining)
sec-=secs_remaining
year+=1
is_leap = is_leap_year(year)
days_remaining = 365+(is_leap ? 1 : 0)
secs_remaining = SEC_PER_DAY*days_remaining
day=0
end
#sec holds seconds into the current year, split into days+seconds
day += sec / SEC_PER_DAY
day = day.to_i #cast to int
sec %= SEC_PER_DAY
#lookup month
for i in (0..11)
dpm = DAYS_PER_MONTH[i] # =[31,28,31,30,...]
if (i==1 && is_leap)
dpm+=1
end
if day < dpm
month = i
break
else
day-=dpm
end
end
day+=1 #1-based
hour = sec/3600
min = (sec%3600)/60
sec = sec%60
puts "%s %d, %d # %02d:%02d:%02d" % [MONTHNAME[month],day,year, hour, min, sec]
It should be easy to add a check that the day is between the begin and end days for DST in the current locale, and adjust the hour accordingly.

The following function determines whether a given year is a leap year:
bool is_leap_year(int year)
{
return ((0 == year % 400) || ((0 == year % 4) && (0 != year % 100)));
}

Related

Cant get right localtime in C

Im trying to get the current localtime in C, lets say Italy, I tried the code below but it returns a time three hours earlier than the real one.
For example if executed at 17:34 it will return 14:34, what am I doing wrong?
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
setenv("TZ", "UTC+1", 1); // Italy is UTC+1
tzset();
time_t sec = 1634224877; // Just for test
struct tm * end = localtime(&sec);
// Check for daylight save
if(end->tm_isdst == 1)
{
end->tm_hour += 1;
}
printf("Daylight save time?: %d\n", end->tm_isdst);
printf("Time is says: %.2d/%.2d/%d - %.2d:%.2d:%.2d\n", end->tm_mday, end->tm_mon + 1, end->tm_year + 1900,
end->tm_hour, end->tm_min, end->tm_sec);
}
Thanks
tl;dr If you want the time at a location, you have to set TZ to a location.
1634224877 is 2021-10-14 15:21:17 UTC. setenv takes POSIX time zones which don't work like you think. UTC+1 means one hour head of UTC. That is 14:21:17. If you want "UTC+1" you actually ask for UTC-1, one hour behind UTC.
But don't ask for UTC-1.
setenv("TZ", "UTC+1", 1); // Italy is UTC+1
That comment is not correct. Italy is not UTC+1. Sometimes Italy is UTC+1, sometimes it is UTC+2. In order to know about daylight savings time (and other wacky time zone issues), TZ needs to know your location.
Set TZ to the city closest to you, like Europe/Rome. Now localtime can figure out whether it's daylight savings time or not, you do not need to correct it.
int main()
{
setenv("TZ", "Europe/Rome", 1);
tzset();
time_t sec = 1634224877; // Just for test
struct tm * end = localtime(&sec);
printf("Daylight savings time?: %d\n", end->tm_isdst);
printf("Time is says: %.2d/%.2d/%d - %.2d:%.2d:%.2d\n", end->tm_mday, end->tm_mon + 1, end->tm_year + 1900,
end->tm_hour, end->tm_min, end->tm_sec);
}
Daylight savings time?: 1
Time is says: 14/10/2021 - 17:21:17
The system that manages time zones is called tzdata. It is a database of locations, their time zone information, daylight savings time switches, and a host of other wacky time zone information. It's what lets your computer know that Rome is usually UTC+1 but should sometimes be UTC+2.
A list of all tzdata locations can be had on Wikipedia, but these do not necessarily match the tzdata installed on your machine.
The TZ environment variable value FOO+1 (I changed it from UTC+1 to avoid confusion) is interpreted as a standard time zone designated "FOO" with no alternate (daylight savings) zone. The +1 (the + is optional) means that 1 hour needs to be added to the local time to convert it to Coordinated Universal Time (UTC). To specify an alternate (daylight savings) time zone, it is added after the standard time offset, e.g. FOO+1BAR0. The offset after the alternate zone can be omitted in which case it defaults to one less than the standard offset, so FOO+1BAR0 can be shortened to FOO1BAR. This means that local time will be 1 hour behind UTC when standard time is in effect and will be UTC when alternate (daylight savings) time is in effect. Optionally, the offset can include minutes or minutes and seconds, e.g. FOO+01:00BAR+00:00:00.
Italy uses Central European Time (CET, corresponding to UTC+1) when standard time is in effect (e.g. during winter), and uses Central European Summer Time (CEST, corresponding to UTC+1) when alternate (daylight savings) time is in effect (e.g. during summer). That can be expressed by the TZ environment variable value CET-1CEST-2 or CET-1CEST. Notice that the offsets used in the TZ environment variable have the opposite sign to the usual convention.
When TZ has one of the previously mentioned values with an alternate time (e.g. CET-1CEST), it is left up to the system libraries to use some arbitrary (and usually incorrect for most of the world) rule to determine the date and time of transitions between standard time and alternate time. Simple rules for the date and time of exactly two transitions per year can be encoded after the alternate zone designation and offset in the TZ variable, separated by commas. The transition date can be specified as Mm.n.d, meaning the dth day (0 = Sunday, 1 = Monday, ..., 6 = Saturday) of the nth week (counting from 1) of the mth month (1 = January, ..., 12 = December). n = 5 is interpreted as the last d day of the month m. The transition date can also be specified as Jn where n is the day of the year, not counting February 29 (so March 1 is always day 60). The (optional) transition time can be specified as /time, where time specifies the current local time on the transition date at which the transition to the other time is made, defaulting to 02:00:00. The time value can be abbreviated in the same way as time zone offsets, so /2 is the same as /02:00:00, but no leading plus or minus sign is allowed (by the current standard).
Italy currently operates on EU time zone transition rules (the abolition of which is currently postponed), where the transitions occur at 01:00 UTC and local time advances 1 hour on the last Sunday of March (M3.5.0) and retreats 1 hour on the last Sunday of October (M10.5.0). For Italy, the local transition times are 02:00:00 in March (advancing to 03:00:00) and 03:00:00 in October (retreating to 02:00:00), so the rules are M3.5.0/2 and M10.5.3. (M3.5.0/2 can be shortened to M3.5.0 since it uses the default transition time.)
The following, modified code will work to show time in Italy, at least until the current EU time zone rules are abolished:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
setenv("TZ", "CET-1CEST,M3.5.0,M10.5.0/3", 1); // Italy rules!
tzset();
time_t sec;
#if 0
sec = 1634224877; // Just for test
#else
sec = time(NULL); // For current time
#endif
struct tm * end = localtime(&sec);
printf("Daylight save time?: %d\n", end->tm_isdst);
printf("Time is says: %.2d/%.2d/%d - %.2d:%.2d:%.2d\n",
end->tm_mday, end->tm_mon + 1, end->tm_year + 1900,
end->tm_hour, end->tm_min, end->tm_sec);
}

count number of weekdays between two date strings in C99

I have two date strings in the form yyyy-mm-dd , just like
const char* date_start = "2015-09-30";
const char* date_end = "2015-10-03";
How do I calculate the number of weekdays (number of days which are neither Saturday nor Sunday) between the two dates? Dates where start and end day are equal can exists (the day count should be equal to 1 (workday) or 0 (weekend) then). All input dates are guaranteed to be valid (e.g. no 30th of February).
The solution need to work with C99 on OS X as well as Windows and independent of the system locale settings.
I would prefer to use as little external code (i.e. libraries or frameworks) as possible.
Pseudo Code
Form time structures
struct tm start = {0};
start.tm_year = 2105-1900;
start.tm_mon = 9-1;
start.tm_mday = 30;
start.tm_isdst = 0;
struct tm end = ...
Form time number and set tm_wday field
time_t tstart = mktime(&start);
time_t tend = mktime(&end);
Find day difference
double day_diff = difftime(&tend, &tstart)/(24.0*60*60);
Some magic per weekday (left for OP)
numweekdays = ((long)day_diff/7)*5 + foo(start->tm_wday, end->tm_wday);
convert date-strings to something more handable, like "long date since 1.1.1970", if that suits your use case; or "struct tm"
calculate the difference in days (end minus start plus one)
for each complete week inside the difference (> 7) add 5 weekdays/2 weekends
calculate the weekday-status for the rest (at least 6) days and add them accordinly

Converting days since Jan.1 1900 to today's date

typedef struct dbdatetime
{ // Internal representation of DATETIME data type
LONG dtdays; // No of days since Jan-1-1900 (maybe negative)
ULONG dttime; // No. of 300 hundredths of a second since midnight
} DBDATETIME;
I am trying to convert this struct into today's date. I don't suspect the time will give me much trouble but I am having problems with the logic of converting the total number of days to the proper month and day.
Ex. Friday November 7th is 41948 days.
You can divide by 365.2425+1900 to get the current year but how would you get the proper month / date.
Does C have anything built in to handle this? I am not a C programmer by trade.
There's nothing in the C standard directly to handle this, but if you are willing to write OS specific code, or can import libraries like boost::date_time, this is the best option. Don't attempt to handle it yourself unless you are okay with edge cases being wrong. Dates and times are notoriously difficult to get right.
Here are the docs for date_time which can do arithmetic on dates, including "add N days to 1/1/1900". http://www.boost.org/doc/libs/1_56_0/doc/html/date_time/gregorian.html#date_time.gregorian.date_duration
date d(1900, Jan, 1)
d += days(dtdays);
EDIT: OP can't use boost, but I'll leave this here in case a future visitor could use the info.
As you said, you can divide the number of days by 365.2425 to get a approx estimation of the year. Once you have that, use the number of years and leap years between 1900 and that year to calculate the number of days till that year.
You need to make sure that the logic for detecting leap years is sound. Once you know which years are leap years, you'll be fine. For example, 1900 is NOT a leap year, even though it is divisible by 4. Google leap year rules to find out why.
Once you have that, the remaining days will belong to the current year. Use the leap year logic again to determine whether you the current year is leap or not. Then on it's just a matter of counting days for each month.
C++ is out of the question, thanks for all the input guys.
I did find this algorithm that seems to work so far but am testing it now.
int l = nSerialDate + 68569 + 2415019;
int n = int(( 4 * l ) / 146097);
l = l - int(( 146097 * n + 3 ) / 4);
int i = int(( 4000 * ( l + 1 ) ) / 1461001);
l = l - int(( 1461 * i ) / 4) + 31;
int j = int(( 80 * l ) / 2447);
nDay = l - int(( 2447 * j ) / 80);
l = int(j / 11);
nMonth = j + 2 - ( 12 * l );
nYear = 100 * ( n - 49 ) + i + l;
Trying to find some theory behind it now.
Thanks for all of the input everyone, here is what I ended up doing.
Convert dtdays to seconds, minus the offset of the number of seconds between Jan-1-1900(Serial Date) and Jan-1-1970(UNIX Time). Which is 2208988800, this leaves us with UNIX time.
Convert the hundredths of seconds into seconds and add to the total.
Convert UNIX time using gmtime() to a tm struct.

How can I find out all the dates of a given day in this month?

I have to write a program in C which, given the name of a day, returns all of the dates in this month that the day will be on.
For example, if the input is "Sunday" the output should be: 5,12,19,26 (which are the days Sunday will be on this month.)
Does any one have any idea how to do this? I have tried a lot.
You can use the time() function to get the current time.
Then use the localtime() function to get a struct (struct tm) with the information (year, month, day, ...) of the current time.
From the "struct tm" get the tm_mday and the tm_wday. Use these fields to determine the next or previous sunday. e.g. if tm_mday is 12, and tm_wday is 3 (wednesday), then we now that the 9th of this month is a sunday (12-3 = 9). From that number simply add or subtract 7 to get all other sundays.
You need to know it for a given year too? Or is this for only this year? If you need to know it for any given year, you can do a "days per month" enum, having one for the leap years and one for the non-leap years.
You just need to know in which day of week started a year (i.e: "Monday", "Tuesday", etc)
You will, at least, have 5 dates for any given month, so, you can have a fixed length array of ints.
You know that the gregorian calendar repeats itself each 400 years, and that if a year X started with day "Y", then, year X + 1 will start with day ("Y" + 1) % 7 if x is not a leap year, if it is a leap year, it will start with day ("Y" + 2).
that could give you the first date of any year, and knowing how many days have all the months for any given year, you can easily get what date that month starts in ("Monday", etc).
Then, all you have to do, is something like
int offset = 0;
int i;
while (myDate + offset != monthStartingDate) {
offset++;
}
i = offset + monthStartingDate;
(myDate is the number of day of week, and monthStartingDate is the number of day of week for the first day of that month)
when you go out of that loop, you will have the first ocurrence, then, you just add 7 until i is out of month bounds.
you can add each i into the array..
int res[5] = {0,0,0,0,0}
for ( ; i < daysOfMonth(month, year); i += 7) {
int res[i / 7] = i;
}
then you just return res.
Oh, I dind't know that you were able to use date functions :P I think the idea of the excercise was practicing C :P
1) Take a string (weekday name) from the input (use scanf or gets)
2) Convert it to a number (find it's index in a table of weekdays using loop and strcmp), assign 0 to Sunday, 1 for Monday ...
3) Get current time with time function and convert it to tm struct with localtime function
4) From tm struct calculate the first day in current month of a given weekday
first_mday_of_given_wday = (tm_struct.tm_mday + given_wday - tm_struct.tm_wday + 6) % 7 + 1
5) Find out how many days is in current month. To do this:
put 1 into tm_mday and 0 into tm_isdst of your tm struct
duplicate the struct
increase by 1 tm_mon in the duplicate (watch for the last month! in that case increase tm_year by 1 and set tm_mon to 1)
convert booth strutcs to time_t with mktime function and calculate difference (just subtract these time_t values), convert result from seconds to days (divide by 60*60*24)
6) Run a loop though calculated range:
for (i = first_mday_of_given_wday; i <= num_days_in_month; i += 7) printf("%d, ", i)
5th step can be omitted in a certain situations. We know that a month can have from 28 to 31 days. If any of hypothetical days 29,30,31 of current month cannot be a given weekday we can assume that current month has 28 days.
So simply - assume we have 28 days in current month if first_mday_of_given_wday is more then 3, otherwise calculate the number like shown in 5th step.

Converting unix timestamp to YYYY-MM-DD HH:MM:SS

I have a Unix timestamp that I need to get the individual year, month, day, hour, minute and second values from. I never was very good in math class so I was wondering if you guys could help me out a little :)
I have to do everything myself (no time.h functions). The language is C.
Disclaimer: The following code does not account for leap years or leap seconds [Unix time does not account for leap seconds. They're overrated, anyway. -Ed]. Also, I did not test it, so there may be bugs. It may kick your cat and insult your mother. Have a nice day.
Let's try a little psuedocode (Python, really):
# Define some constants here...
# You'll have to figure these out. Don't forget about February (leap years)...
secondsPerMonth = [ SECONDS_IN_JANUARY, SECONDS_IN_FEBRUARY, ... ]
def formatTime(secondsSinceEpoch):
# / is integer division in this case.
# Account for leap years when you get around to it :)
year = 1970 + secondsSinceEpoch / SECONDS_IN_YEAR
acc = secondsSinceEpoch - year * SECONDS_IN_YEAR
for month in range(12):
if secondsPerMonth[month] < acc:
acc -= month
month += 1
month += 1
# Again, / is integer division.
days = acc / SECONDS_PER_DAY
acc -= days * SECONDS_PER_DAY
hours = acc / SECONDS_PER_HOUR
acc -= hours * SECONDS_PER_HOUR
minutes = acc / SECONDS_PER_MINUTE
acc -= minutes * SECONDS_PER_MINUTE
seconds = acc
return "%d-%d-%d %d:%d%d" % (year, month, day, hours, minutes, seconds)
If I goofed up, please let me know. Doing this in C shouldn't be too much harder.
You really do not want to do this by hand. You could write up some simple code that assumes years, months, days, hours, minutes and seconds are all the same lengths (12 months; 28, 30, or 31 days; 24 hours; 60 minutes; and 60 seconds) and come up with the wrong answer.
To get the right answer, you have to handle leap years and leap seconds, and convert to the local time zone (with the right DST mode). (Unless you choose to only display in UTC time.)
I suggest that you have a look at the code of glibc and see how strftime works.
Edit: UNIX time does not use the leap second.
That format is often called ISO 8601, and searching for that might help you out.
You should have done a search - I posted a complete answer to this question over here just the other day (for UTC, at least - to adjust for other timezones just add or subtract the timezone offset in seconds from the unixtime before you call the function).
You don't have to do math. This can be easily handled in C like this,
char *ISO_Time (
time_t time
)
{
static char mStr[128];
struct tm *gmt;
gmt = gmtime (&time);
strftime (mStr, sizeof(mStr), "%Y-%m-%dT%H:%M:%SZ", gmt);
return mStr;
}
Since yours is not exactly ISO time, you just need to change one line,
strftime (mStr, sizeof(mStr), "%Y-%m-%d %H:%M:%S", gmt);

Resources