1

I wish to create a Unix time stamp. I have a micro-controller with a ble connection that I send the present time to via a GATT connection. I receive the data several integers.

These are Year, Month, Day, Hour, Minute and Second. e.g:

  • Year = 2020, month = 3, day = 9, hour = 10, minute = 10, second = 10.

I want to convert this to Unix time so I can then increment the value every second via a interrupt based timer. Therefore, what is the correct way to conjoin this data together to form the Unix time. My presumption is as follows:

Create a integer value large enough to store the value called unix time and add the equivalent seconds from the respective time components(year,month etc).

These are the value in seconds

  • Year_in_seconds = 31,556,952
  • Month_in_seconds = 2,630,000
  • Day_in_seconds = 86,400
  • Hour_in_seconds = 3,600
  • Minute_in_seconds = 60

Therefore, Unix time =

  • (nYears * Year_in_seconds)+(nMonths * Month_in_seconds)+(nDays * Days_in_seconds)+(nHours * Hour_in_seconds)+(nMinutes * Minute_in_seconds)+(nSeconds * 1)

Is there anything wrong about my presumptions here?

I don't know how unix time deals with the different number of days in a month as different months have different lengths. Also leap years. So do I need another lookup table or something to make this accurate?

Or what is the correct method for getting the unix time from my set of data?

Thomas Morris
  • 794
  • 5
  • 26
  • 1
    @PaulR It sounds a bit like his microcontroller won't have the full set of posix functions available, hence the duplicate is probably not of much use here. Voting to reopen. – Ctx Mar 09 '20 at 11:35
  • I did have a quick look at examples like that but, I am writing in C not C++ please re open this question I don't feel it should have been closed. – Thomas Morris Mar 09 '20 at 11:40
  • Sorry - re-opened. Note that the linked C++ question probably *does* have all the info you need (it's really not C++ specific) - if you don't have Posix calls like mktime available then you can look at e.g. the Linux or FreeBSD sources for these to see how they work. – Paul R Mar 09 '20 at 11:42
  • Does your system have the standard function `mktime()`? – John Zwinck Mar 09 '20 at 11:43
  • if you work on the uC then why to use Unix style timestamps – 0___________ Mar 09 '20 at 11:44
  • 1
    One thing I could do I change the sent data to a unix value. This comes from my mobile phone using a calendar library. Then on my micro-controller just increment that once per second. I presume this would work. Then the back transfer would just send the unix time back. Then the handling of the timestamp will be done again using the calendar library on my phone. I don't directly have a requirement to get exact readbale time on my micro. Just need to make sure the time is correct for logged data. – Thomas Morris Mar 09 '20 at 11:45
  • @JohnZwinck I don't have access to mktime to my knowledge. I've just tried to add it and, it is undefined. Unless its a different include library I need. – Thomas Morris Mar 09 '20 at 11:48
  • 1
    Are these local times or what? Cause local times don't necessarily have 86,400 seconds per day. /// Re "*I don't know how unix time deals with the different number of days in a month as different months have different lengths.*", Unix time is the number of seconds since 1970-01-01T00:00:00Z (Z means UTC). There's isn't constant amount of seconds per months or seconds per year, so the process is a lot more complicated. – ikegami Mar 09 '20 at 11:52
  • 1
    You don't mention DST. This is some other thing you probably should care about. – pmg Mar 09 '20 at 11:52
  • 1
    The [Julian day article on Wikipedia](https://en.m.wikipedia.org/wiki/Julian_day) has formulas to convert day, month, year to number of days. You can use that as a basis to convert year, month, day, hour, minute, second (timezone, dst?) to unix timestamp. – pmg Mar 09 '20 at 11:56
  • Does you micro-controller use 32-bit `int/unsigned` or other? – chux - Reinstate Monica Mar 09 '20 at 12:14
  • Are the values of `Year, Month, Day, Hour, Minute and Second` confined to there primary range? Like day [1..31], seconds [0...59]? Is your time stamp _local_ time or UTC? Concern about DST? What range is needed? to year 2037, 2099, 2106, beyond? – chux - Reinstate Monica Mar 09 '20 at 12:28

2 Answers2

4

This should be accurate up to 2100:

unsigned int tounix (int year, int month, int day, int hour, int min, int sec) {
    unsigned int epoch = 0;
    // These are the number of days in the year up to the corresponding month
    static const unsigned int monthdays[] = { 0, 31, 59, 90, 120, 151, 181, 211, 242, 272, 303, 333 };
    epoch = (year-1970)*86400*365u +    // Account the years
            ((year-1968)/4)*86400 +    // leap years
            monthdays[month-1]*86400 + // days in past full month
            (day-1)*86400u + hour*3600 + min*60 + sec; // days, hours, mins, secs

    if (!(year%4)&&month < 3) // Correct 1 day if in leap year before Feb-29
        epoch -= 86400;

    return epoch;
}

Input is expected as:

  • Year: 1970 - 2099
  • Month: 1 - 12
  • Day: 1 - 31
  • Hour, Minute, Second: as usual ;)
Ctx
  • 18,090
  • 24
  • 36
  • 51
  • Have you tested this to confirm the date is correct if you pass in todays date? – Thomas Morris Mar 09 '20 at 12:07
  • 2
    @ThomasMorris I always test my code unless I explicitly write _untested_. – Ctx Mar 09 '20 at 12:09
  • 1
    Hmmm, After [y=2028](https://en.wikipedia.org/wiki/Year_2038_problem), `(year-1970)*86400*365` starts overflowing 32-bit `int` math leading to UB. Better to use `unsigned` constants/math. – chux - Reinstate Monica Mar 09 '20 at 12:18
  • "Year: 1970 - 2099" nice! Note that years ..., 1800, 1900, 2100, 2200, 2300, 2500, ... are not leap years. The formula needs adjusting if you want to use these :) – pmg Mar 09 '20 at 12:40
  • 1
    @pmg Yes, that's one of two reasons for the restriction of years to 1970-2100. The other is, that it wouldn't fit in an unsigned int otherwise and maybe the µc would not support longer integers. – Ctx Mar 09 '20 at 12:43
  • 1
    @Ctx It worked very well thank you. Passed in 2020,3,9,12,43,30 and got 1583757810. Which is correct. Many thanks. – Thomas Morris Mar 09 '20 at 12:45
0

The timestamp is nothing but an elapsed time from 1st January 1970 to any specific instance.

With this logic lets see the steps to implementation a method to get timestamp from the date-time.

These are the value in seconds as mentioned in the question itself
Day_in_seconds = 86400
Hour_in_seconds = 3600
Minute_in_seconds = 60

1. First calculate the second for the year you want seconds for:

int yearInSeconds = (year - 1970)*365*86400 + // assume all year has only 365 day and multiple 365 * 86400(seconds in a day)
                    (year - 1968)/4 * 86400 // now here we are adding 1 day seconds for all the leaps which comes once in every 4 years from the first leap 1972 to till date

To check the leap years you can refer this tool.

2. Calculation of seconds for the a given month:

As a month can have following possible days [28, 30, 31] // excluding leap year february. lets create a cumulative month array which have the completed month days count at any particular month

const int daysInMonth[] = { 0, 31, 59, 90, 120, 151, 181, 211, 242, 272, 303, 333 };

seconds for a month = daysInMonth[month-1] * 86400 // daysInMonth[month-1] gives the days count of the full completed months and multiply with 86400(seconds in a day)

3. Calulation of seconds of a given day, hour, minutes and second:

// seconds of given day, hour, minutes and second as totalSeconds
int totalSeconds = day*86400 + hour*3600 + minute*60 + second; // multipling the given data with the above mentioned constants for day, hour and minute.

4. Now lets handle the special case where if you are querying for leaps date.

In the first step we added 1 day seconds for all the leap year, if your query is for leap year with a month on or before february that means we didn't crossed 29th of february.

For this we need subtract on day seconds from the final result.

if(!(year%4) && month <= 2) {
  result = result - 1*86400;
}

final code given below:

int getUnixTimestamp(int year, int month, int day, int hour, int minute, int second) {
    const int daysInMonth[] = {
        0,
        31,
        59,
        90,
        120,
        151,
        181,
        211,
        242,
        272,
        303,
        333
    };
    int unixtime = (year - 1970) * 86400 * 365 +
        ((year - 1968) / 4) * 86400 +
        daysInMonth[month - 1] * 86400 +
        (day - 1) * 86400 +
        hour * 3600 +
        minute * 60 +
        second;
    if (!(year % 4) && month <= 2) {
        unixtime -= 86400;
    }
    return unixtime;
}

This code will work until the year 2100, for more info or to check the time conversions you can use this tool.

Saitejareddy
  • 331
  • 3
  • 3