Skip to content

Commit

Permalink
Merge #19080
Browse files Browse the repository at this point in the history
19080: sys/phydat: add functions for Unix time conversion to phydat r=benpicco a=silkeh




Co-authored-by: Silke Hofstra <silke@slxh.eu>
  • Loading branch information
bors[bot] and silkeh authored Jan 20, 2023
2 parents d11a358 + 20282eb commit 340b517
Show file tree
Hide file tree
Showing 7 changed files with 332 additions and 0 deletions.
29 changes: 29 additions & 0 deletions sys/include/phydat.h
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,35 @@ void phydat_fit(phydat_t *dat, const int32_t *values, unsigned int dim);
*/
size_t phydat_to_json(const phydat_t *data, size_t dim, char *buf);

/**
* @brief Convert a date and time contained in phydat structs to a Unix timestamp.
* See phydat_unix() for the date notation and peculiarities.
*
* @param date Date to use in the timestamp.
* @param time Time to use in the timestamp.
* @param offset_seconds Timezone offset in seconds to use in the timestamp.
*
* @return A unix timestamp
*/
int64_t phydat_date_time_to_unix(phydat_t *date, phydat_t *time, int32_t offset_seconds);

/**
* @brief Convert a date and time (per ISO8601) to a Unix timestamp (seconds since 1970).
*
* @param year Year in the Common Era (CE). Note that 0 is 1 BCE, 1 is 2 BCE, etc.
* @param month Month of the year.
* @param day Day of the month.
* @param hour Hour of the day.
* @param minute Minute of the hour.
* @param second Second of the minute.
* @param offset Timezone offset in seconds.
*
* @return A Unix timestamp (seconds since 1970).
*/
int64_t phydat_unix(int16_t year, int16_t month, int16_t day,
int16_t hour, int16_t minute, int16_t second,
int32_t offset);

#ifdef __cplusplus
}
#endif
Expand Down
67 changes: 67 additions & 0 deletions sys/phydat/phydat_unix.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright (C) 2023 Silke Hofstra
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/

#include <stdint.h>
#include <math.h>

#include "phydat.h"

/**
* Offsets of the first day of the month starting with January.
* Months after February have a negative offset to efficiently handle leap years.
*/
static int16_t month_to_yday[] = { 0, 31, -306, -275, -245, -214,
-184, -153, -122, -92, -61, -31 };

static inline int16_t phydat_unscale(int16_t value, int16_t scale)
{
if (scale > 0) {
return value * pow(10, scale);
}

if (scale < 0) {
return value / pow(10, -scale);
}

return value;
}

int64_t phydat_date_time_to_unix(phydat_t *date, phydat_t *time, int32_t offset_seconds)
{
return phydat_unix(
phydat_unscale(date->val[2], date->scale),
phydat_unscale(date->val[1], date->scale),
phydat_unscale(date->val[0], date->scale),
phydat_unscale(time->val[2], time->scale),
phydat_unscale(time->val[1], time->scale),
phydat_unscale(time->val[0], time->scale),
offset_seconds);
}

int64_t phydat_unix(int16_t year, int16_t month, int16_t day,
int16_t hour, int16_t minute, int16_t second,
int32_t offset_seconds)
{
/* Make the year relative to 1900. */
/* Add a year for months after Feb to deal with leap years. */
year += (month > 2 ? 1 : 0) - 1900;

/* Calculate the day of the year based on the month */
day += month_to_yday[(month - 1) % 12] - 1;

/* POSIX calculation of a UNIX timestamp. */
/* See section 4.16 of The Open Group Base Specifications Issue 7. */
int16_t leap_days = ((year - 69) / 4) - ((year - 1) / 100) + ((year + 299) / 400);

return (int64_t)(day + leap_days) * 86400 +
(int64_t)(year - 70) * 31536000 +
(int64_t)(hour) * 3600 +
(int64_t)(minute) * 60 +
(int64_t)(second) -
offset_seconds;
}
6 changes: 6 additions & 0 deletions tests/phydat_unix/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
include ../Makefile.tests_common

USEMODULE += phydat
USEMODULE += embunit

include $(RIOTBASE)/Makefile.include
1 change: 1 addition & 0 deletions tests/phydat_unix/Makefile.ci
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
BOARD_INSUFFICIENT_MEMORY :=
2 changes: 2 additions & 0 deletions tests/phydat_unix/app.config.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
CONFIG_MODULE_PHYDAT=y
CONFIG_MODULE_EMBUNIT=y
213 changes: 213 additions & 0 deletions tests/phydat_unix/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
/*
* Copyright (C) 2023 Silke Hofstra
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/

/**
* @ingroup tests
* @{
*
* @file
* @brief Phydat Unix timestamp tests
*
* @author Silke Hofstra <silke@slxh.eu>
*
* @}
*/

#include <stdio.h>
#include <string.h>
#include <math.h>

#include "phydat.h"
#include "embUnit.h"

#define ENABLE_DEBUG (0)
#include "debug.h"

#ifndef PRIi64
#define PRIi64 "lli"
#endif

typedef struct {
phydat_t date;
phydat_t time;
int32_t offset;
int64_t ts;
} test_t;

static test_t tests[] = {
/* Test various ways of writing 0 */
{
.date = { { 1, 1, 1970 }, UNIT_DATE, 0 },
.time = { { 0, 0, 0 }, UNIT_TIME, 0 },
.ts = 0,
},
{
.date = { { 1, 1, 1970 }, UNIT_DATE, 0 },
.time = { { 0, 0, 2 }, UNIT_TIME, 0 },
.offset = 7200, // UTC +0200
.ts = 0,
},
{
.date = { { 31, 12, 1969 }, UNIT_DATE, 0 },
.time = { { 0, 0, 22 }, UNIT_TIME, 0 },
.offset = -7200, // UTC -0200
.ts = 0,
},
{
.date = { { 1, 1, 1970 }, UNIT_DATE, 0 },
.time = { { 3600, 60, 0 }, UNIT_TIME, 0 },
.offset = 7200, // UTC +0200
.ts = 0,
},
{
.date = { { 31, 12, 1969 }, UNIT_DATE, 0 },
.time = { { 3600, 120, 19 }, UNIT_TIME, 0 },
.offset = -7200, // UTC -0200
.ts = 0,
},

/* Test well-known dates */
{
.date = { { 28, 4, 2021 }, UNIT_DATE, 0 },
.time = { { 0, 0, 0 }, UNIT_TIME, 0 },
.ts = 1619568000,
},
{
.date = { { 29, 2, 2020 }, UNIT_DATE, 0 },
.time = { { 0, 0, 0 }, UNIT_TIME, 0 },
.ts = 1582934400,
},
{
.date = { { 1, 3, 2020 }, UNIT_DATE, 0 },
.time = { { 0, 0, 0 }, UNIT_TIME, 0 },
.ts = 1583020800,
},

/* Test the first of every month */
{
.date = { { 1, 1, 1900 }, UNIT_DATE, 0 },
.time = { { 0, 0, 0 }, UNIT_TIME, 0 },
.ts = -2208988800,
},
{
.date = { { 1, 2, 1900 }, UNIT_DATE, 0 },
.time = { { 0, 0, 0 }, UNIT_TIME, 0 },
.ts = -2206310400,
},
{
.date = { { 1, 3, 1900 }, UNIT_DATE, 0 },
.time = { { 0, 0, 0 }, UNIT_TIME, 0 },
.ts = -2203891200,
},
{
.date = { { 1, 4, 1900 }, UNIT_DATE, 0 },
.time = { { 0, 0, 0 }, UNIT_TIME, 0 },
.ts = -2201212800,
},
{
.date = { { 1, 5, 1900 }, UNIT_DATE, 0 },
.time = { { 0, 0, 0 }, UNIT_TIME, 0 },
.ts = -2198620800,
},
{
.date = { { 1, 6, 1900 }, UNIT_DATE, 0 },
.time = { { 0, 0, 0 }, UNIT_TIME, 0 },
.ts = -2195942400,
},
{
.date = { { 1, 7, 1900 }, UNIT_DATE, 0 },
.time = { { 0, 0, 0 }, UNIT_TIME, 0 },
.ts = -2193350400,
},
{
.date = { { 1, 8, 1900 }, UNIT_DATE, 0 },
.time = { { 0, 0, 0 }, UNIT_TIME, 0 },
.ts = -2190672000,
},
{
.date = { { 1, 9, 1900 }, UNIT_DATE, 0 },
.time = { { 0, 0, 0 }, UNIT_TIME, 0 },
.ts = -2187993600,
},
{
.date = { { 1, 10, 1900 }, UNIT_DATE, 0 },
.time = { { 0, 0, 0 }, UNIT_TIME, 0 },
.ts = -2185401600,
},
{
.date = { { 1, 11, 1900 }, UNIT_DATE, 0 },
.time = { { 0, 0, 0 }, UNIT_TIME, 0 },
.ts = -2182723200,
},
{
.date = { { 1, 12, 1900 }, UNIT_DATE, 0 },
.time = { { 0, 0, 0 }, UNIT_TIME, 0 },
.ts = -2180131200,
},

/* Test scale correction */
{
.date = { { 1, 1, 197 }, UNIT_DATE, 1 },
.time = { { 0, 0, 0 }, UNIT_TIME, 0 },
.ts = 24364800,
},
{
.date = { { 10, 10, 19700 }, UNIT_DATE, -1 },
.time = { { 0, 0, 0 }, UNIT_TIME, 0 },
.ts = 0,
},
{
.date = { { 1, 1, 1970 }, UNIT_DATE, 0 },
.time = { { 36, 0, 0 }, UNIT_TIME, 2 },
.ts = 3600,
},

/* An invalid date that might go out of bounds on the day of the year lookup table */
{
.date = { { 1, 13, 1969 }, UNIT_DATE, 0 },
.time = { { 0, 0, 0 }, UNIT_TIME, 0 },
.ts = 0,
},
};

void test_phydat_date_time_to_unix(void)
{
for (size_t i = 0; i < ARRAY_SIZE(tests); i++) {
int64_t result = phydat_date_time_to_unix(
&(tests[i].date), &(tests[i].time), tests[i].offset);

int32_t offset_hours = tests[i].offset / 3600;
int32_t offset_minutes = (tests[i].offset % 3600) / 60;

DEBUG("Datetime: %04" PRIi16 "-%02" PRIi16 "-%02" PRIi16 "e%" PRIi16 " "
"%02" PRIi16 ":%02" PRIi16 ":%02" PRIi16 "e%" PRIi16 " "
"%+03" PRIi32 ":%02" PRIi32 " -> %" PRIi64 "\n",
tests[i].date.val[2], tests[i].date.val[1], tests[i].date.val[0], tests[i].date.scale,
tests[i].time.val[2], tests[i].time.val[1], tests[i].time.val[0], tests[i].time.scale,
offset_hours, offset_minutes, result);

TEST_ASSERT_EQUAL_INT(tests[i].ts, result);
}
}

Test *tests_phydat_unix(void)
{
EMB_UNIT_TESTFIXTURES(fixtures) {
new_TestFixture(test_phydat_date_time_to_unix),
};
EMB_UNIT_TESTCALLER(senml_tests, NULL, NULL, fixtures);
return (Test *)&senml_tests;
}

int main(void)
{
TESTS_START();
TESTS_RUN(tests_phydat_unix());
TESTS_END();
return 0;
}
14 changes: 14 additions & 0 deletions tests/phydat_unix/tests/01-run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env python3

# Copyright (C) 2017 Freie Universität Berlin
#
# This file is subject to the terms and conditions of the GNU Lesser
# General Public License v2.1. See the file LICENSE in the top level
# directory for more details.

import sys
from testrunner import run_check_unittests


if __name__ == "__main__":
sys.exit(run_check_unittests())

0 comments on commit 340b517

Please sign in to comment.