Skip to content

Commit

Permalink
Merge PR #470 - implement automatic DST toggling
Browse files Browse the repository at this point in the history
Implements logic to automatically offset daylight saving time settings
when calculating timezone offsets. This should make the DST functions
work automatically with no need for user input in most cases.

Reviewed-by: Matheus Afonso Martins Moreira <[email protected]>
GitHub-Pull-Request: #470
  • Loading branch information
matheusmoreira committed Sep 8, 2024
2 parents 5a8a49a + 5ae88e4 commit ac5bf8c
Show file tree
Hide file tree
Showing 32 changed files with 295 additions and 165 deletions.
187 changes: 99 additions & 88 deletions movement/movement.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ of debounce time.
#include "filesystem.h"
#include "movement.h"
#include "shell.h"
#include "watch_utility.h"

#ifndef MOVEMENT_FIRMWARE
#include "movement_config.h"
Expand Down Expand Up @@ -130,6 +131,11 @@ of debounce time.
#define MOVEMENT_DEFAULT_BIRTHDATE_DAY 0
#endif

// Default to having DST get set
#ifndef MOVEMENT_DEFAULT_DST_ACTIVE
#define MOVEMENT_DEFAULT_DST_ACTIVE true
#endif

#if __EMSCRIPTEN__
#include <emscripten.h>
#endif
Expand All @@ -141,7 +147,8 @@ const int32_t movement_le_inactivity_deadlines[8] = {INT_MAX, 600, 3600, 7200, 2
const int16_t movement_timeout_inactivity_deadlines[4] = {60, 120, 300, 1800};
movement_event_t event;

const int16_t movement_timezone_offsets[] = {
#define NUM_TIME_ZONES 41
const int16_t movement_timezone_offsets[NUM_TIME_ZONES] = {
0, // 0 : 0:00:00 (UTC)
60, // 1 : 1:00:00 (Central European Time)
120, // 2 : 2:00:00 (South African Standard Time)
Expand Down Expand Up @@ -198,94 +205,50 @@ const int16_t movement_timezone_offsets[] = {
* having to separately change the hour and timezone info
* in the time set face.
*/
const uint8_t movement_dst_jump_table[] = {
1, // 0 UTC + 1 = CET
2, // 1 CET + 1 = SAST
3, // 2 SAST + 1 = AST
5, // 3 AST + 1 = GST
6, // 4 IST + 1 = AT
7, // 5 GST + 1 = PST
8, // 6 AT + 1 = IST
10, // 7 PST + 1 = KT
11, // 8 IST + 1 = MT
9, // 9 Nepal has no equivalent DST timezone, but they don't observe DST anyway
12, // 10 KT + 1 = TST
11, // 11 Myanmar has no equivalent DST timezone, but they don't observe DST anyway
13, // 12 TST + 1 = CST
15, // 13 CST + 1 = JST
14, // 14 ACWST has no equivalent DST timezone, but they don't observe DST anyway
17, // 15 JST + 1 = AEST
18, // 16 ACST + 1 = LHST
19, // 17 AEST + 1 = SIT
18, // 18 LHST has no equivalent DST timezone, but they don't observe DST anyway
20, // 19 SIT + 1 = NZST
22, // 20 NZST + 1 = TT
23, // 21 CST + 1 = CDT
24, // 22 TT + 1 = LIT
23, // 23 CDT is already a daylight timezone
24, // 24 LIT has no equivalent DST timezone, but they don't observe DST anyway
26, // 25 BIT + 1 = NT
27, // 26 NT + 1 = HAST
29, // 27 HAST + 1 = AST
28, // 28 MIT has no equivalent DST timezone, but they don't observe DST anyway
30, // 29 AST + 1 = PST
31, // 30 PST + 1 = MST
32, // 31 MST + 1 = CST
33, // 32 CST + 1 = EST
35, // 33 EST + 1 = AST
36, // 34 VST + 1 = NST
37, // 35 AST + 1 = BT
38, // 36 NST + 1 = NDT
39, // 37 BT + 1 = 39
38, // 38 NDT is already a daylight timezone
40, // 39 FNT + 1 = AST
const int16_t movement_timezone_dst_offsets[NUM_TIME_ZONES] = {
60, // 0 UTC + 1 = CET
120, // 1 CET + 1 = SAST
189, // 2 SAST + 1 = AST
240, // 3 AST + 1 = GST
270, // 4 IST + 1 = AT
300, // 5 GST + 1 = PST
330, // 6 AT + 1 = IST
360, // 7 PST + 1 = KT
390, // 8 IST + 1 = MT
345, // 9 Nepal has no equivalent DST timezone, but they don't observe DST anyway
420, // 10 KT + 1 = TST
390, // 11 Myanmar has no equivalent DST timezone, but they don't observe DST anyway
480, // 12 TST + 1 = CST
540, // 13 CST + 1 = JST
525, // 14 ACWST has no equivalent DST timezone, but they don't observe DST anyway
600, // 15 JST + 1 = AEST
630, // 16 ACST + 1 = LHST
660, // 17 AEST + 1 = SIT
630, // 18 LHST has no equivalent DST timezone, but they don't observe DST anyway
720, // 19 SIT + 1 = NZST
780, // 20 NZST + 1 = TT
825, // 21 CST + 1 = CDT
840, // 22 TT + 1 = LIT
825, // 23 CDT is already a daylight timezone
840, // 24 LIT has no equivalent DST timezone, but they don't observe DST anyway
-660, // 25 BIT + 1 = NT
-600, // 26 NT + 1 = HAST
-540, // 27 HAST + 1 = AST
-570, // 28 MIT has no equivalent DST timezone, but they don't observe DST anyway
-480, // 29 AST + 1 = PST
-420, // 30 PST + 1 = MST
-360, // 31 MST + 1 = CST
-300, // 32 CST + 1 = EST
-240, // 33 EST + 1 = AST
-210, // 34 VST + 1 = NST
-180, // 35 AST + 1 = BT
-150, // 36 NST + 1 = NDT
-120, // 37 BT + 1 = 39
-150, // 38 NDT is already a daylight timezone
-60, // 39 FNT + 1 = AST
0 // 40 AST + 1 = UTC
};

const uint8_t movement_dst_inverse_jump_table[] = {
40, // 0
0, // 1
1, // 2
2, // 3
4, // 4
3, // 5
4, // 6
5, // 7
6, // 8
9, // 9
7, // 10
8, // 11
10, // 12
12, // 13
14, // 14
13, // 15
16, // 16
15, // 17
16, // 18
17, // 19
19, // 20
21, // 21
20, // 22
21, // 23
24, // 24
25, // 25
25, // 26
26, // 27
28, // 28
27, // 29
29, // 30
30, // 31
31, // 32
32, // 33
34, // 34
33, // 35
34, // 36
35, // 37
36, // 38
37, // 39
39 // 40
};

const char movement_valid_position_0_chars[] = " AaBbCcDdEeFGgHhIiJKLMNnOoPQrSTtUuWXYZ-='+\\/0123456789";
const char movement_valid_position_1_chars[] = " ABCDEFHlJLNORTtUX-='01378";

Expand Down Expand Up @@ -323,6 +286,31 @@ static inline void _movement_disable_fast_tick_if_possible(void) {
}
}

static bool _check_and_act_on_daylight_savings(void) {
if (!movement_state.settings.bit.dst_active) return false;
watch_date_time date_time = watch_rtc_get_date_time();
// No need for all of the unix time calculations for times not at the beginning or end of the hour
if (date_time.unit.minute > 1 && date_time.unit.minute < 59) return false;
uint8_t dst_result = get_dst_status(date_time);
bool dst_skip_rolling_back = get_dst_skip_rolling_back();

if (dst_skip_rolling_back && (dst_result == DST_ENDED)) {
clear_dst_skip_rolling_back();
}
else if (dst_result == DST_ENDING && !dst_skip_rolling_back) {
date_time.unit.hour = (date_time.unit.hour + 24 - 1) % 24;
watch_rtc_set_date_time(date_time);
set_dst_skip_rolling_back();
return true;
}
else if (dst_result == DST_STARTING) {
date_time.unit.hour = (date_time.unit.hour + 1) % 24;
watch_rtc_set_date_time(date_time);
return true;
}
return false;
}

static void _movement_handle_background_tasks(void) {
for(uint8_t i = 0; i < MOVEMENT_NUM_FACES; i++) {
// For each face, if the watch face wants a background task...
Expand All @@ -332,6 +320,7 @@ static void _movement_handle_background_tasks(void) {
watch_faces[i].loop(background_event, &movement_state.settings, watch_face_contexts[i]);
}
}
_check_and_act_on_daylight_savings();
movement_state.needs_background_tasks_handled = false;
}

Expand Down Expand Up @@ -529,6 +518,12 @@ uint8_t movement_claim_backup_register(void) {
return movement_state.next_available_backup_register++;
}

int16_t get_timezone_offset(uint8_t timezone_idx, watch_date_time date_time) {
if (movement_state.settings.bit.dst_active && dst_occurring(date_time))
return movement_timezone_dst_offsets[timezone_idx];
return movement_timezone_offsets[timezone_idx];
}

void app_init(void) {
#if defined(NO_FREQCORR)
watch_rtc_freqcorr_write(0, 0);
Expand All @@ -551,6 +546,20 @@ void app_init(void) {
movement_state.birthdate.bit.year = MOVEMENT_DEFAULT_BIRTHDATE_YEAR;
movement_state.birthdate.bit.month = MOVEMENT_DEFAULT_BIRTHDATE_MONTH;
movement_state.birthdate.bit.day = MOVEMENT_DEFAULT_BIRTHDATE_DAY;
movement_state.settings.bit.dst_active = MOVEMENT_DEFAULT_DST_ACTIVE;

#ifdef MAKEFILE_TIMEZONE
timezone_offsets = dst_occurring(watch_rtc_get_date_time()) ? movement_timezone_dst_offsets : movement_timezone_offsets;
for (int i = 0; i < NUM_TIME_ZONES; i++) {
if (timezone_offsets[i] == MAKEFILE_TIMEZONE) {
movement_state.settings.bit.time_zone = i;
break;
}
}
#else
movement_state.settings.bit.time_zone = 35; // Atlantic Time as default
#endif

movement_state.light_ticks = -1;
movement_state.alarm_ticks = -1;
movement_state.next_available_backup_register = 4;
Expand All @@ -559,11 +568,13 @@ void app_init(void) {
filesystem_init();

#if __EMSCRIPTEN__
const int16_t* timezone_offsets;
int32_t time_zone_offset = EM_ASM_INT({
return -new Date().getTimezoneOffset();
});
for (int i = 0, count = sizeof(movement_timezone_offsets) / sizeof(movement_timezone_offsets[0]); i < count; i++) {
if (movement_timezone_offsets[i] == time_zone_offset) {
timezone_offsets = dst_occurring(watch_rtc_get_date_time()) ? movement_timezone_dst_offsets : movement_timezone_offsets;
for (int i = 0; i < NUM_TIME_ZONES; i++) {
if (timezone_offsets[i] == time_zone_offset) {
movement_state.settings.bit.time_zone = i;
break;
}
Expand Down
4 changes: 2 additions & 2 deletions movement/movement.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,7 @@ typedef struct {
} movement_event_t;

extern const int16_t movement_timezone_offsets[];
extern const uint8_t movement_dst_jump_table[];
extern const uint8_t movement_dst_inverse_jump_table[];
extern const int16_t movement_timezone_dst_offsets[];
extern const char movement_valid_position_0_chars[];
extern const char movement_valid_position_1_chars[];

Expand Down Expand Up @@ -321,5 +320,6 @@ void movement_play_alarm(void);
void movement_play_alarm_beeps(uint8_t rounds, BuzzerNote alarm_note);

uint8_t movement_claim_backup_register(void);
int16_t get_timezone_offset(uint8_t timezone_idx, watch_date_time date_time);

#endif // MOVEMENT_H_
7 changes: 7 additions & 0 deletions movement/movement_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,11 @@ const watch_face_t watch_faces[] = {
#define MOVEMENT_DEFAULT_BIRTHDATE_MONTH 0
#define MOVEMENT_DEFAULT_BIRTHDATE_DAY 0

/* Set if using DST
* Valid values are:
* false: Don't allow the watch to use DST
* true: Allow the watch to use DST
*/
#define MOVEMENT_DEFAULT_DST_ACTIVE true

#endif // MOVEMENT_CONFIG_H_
4 changes: 2 additions & 2 deletions movement/watch_faces/clock/beats_face.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ bool beats_face_loop(movement_event_t event, movement_settings_t *settings, void
case EVENT_ACTIVATE:
case EVENT_TICK:
date_time = watch_rtc_get_date_time();
centibeats = clock2beats(date_time.unit.hour, date_time.unit.minute, date_time.unit.second, event.subsecond, movement_timezone_offsets[settings->bit.time_zone]);
centibeats = clock2beats(date_time.unit.hour, date_time.unit.minute, date_time.unit.second, event.subsecond, get_timezone_offset(settings->bit.time_zone, date_time));
if (centibeats == state->last_centibeat_displayed) {
// we missed this update, try again next subsecond
state->next_subsecond_update = (event.subsecond + 1) % BEAT_REFRESH_FREQUENCY;
Expand All @@ -76,7 +76,7 @@ bool beats_face_loop(movement_event_t event, movement_settings_t *settings, void
case EVENT_LOW_ENERGY_UPDATE:
if (!watch_tick_animation_is_running()) watch_start_tick_animation(432);
date_time = watch_rtc_get_date_time();
centibeats = clock2beats(date_time.unit.hour, date_time.unit.minute, date_time.unit.second, event.subsecond, movement_timezone_offsets[settings->bit.time_zone]);
centibeats = clock2beats(date_time.unit.hour, date_time.unit.minute, date_time.unit.second, event.subsecond, get_timezone_offset(settings->bit.time_zone, date_time));
sprintf(buf, "bt %4lu ", centibeats / 100);

watch_display_string(buf, 0);
Expand Down
5 changes: 3 additions & 2 deletions movement/watch_faces/clock/day_night_percentage_face.c
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ void day_night_percentage_face_setup(movement_settings_t *settings, uint8_t watc
if (*context_ptr == NULL) {
*context_ptr = malloc(sizeof(day_night_percentage_state_t));
day_night_percentage_state_t *state = (day_night_percentage_state_t *)*context_ptr;
watch_date_time utc_now = watch_utility_date_time_convert_zone(watch_rtc_get_date_time(), movement_timezone_offsets[settings->bit.time_zone] * 60, 0);
watch_date_time date_time = watch_rtc_get_date_time();
watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, get_timezone_offset(settings->bit.time_zone, date_time) * 60, 0);
recalculate(utc_now, state);
}
}
Expand All @@ -77,7 +78,7 @@ bool day_night_percentage_face_loop(movement_event_t event, movement_settings_t

char buf[12];
watch_date_time date_time = watch_rtc_get_date_time();
watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60, 0);
watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, get_timezone_offset(settings->bit.time_zone, date_time) * 60, 0);

switch (event.event_type) {
case EVENT_ACTIVATE:
Expand Down
2 changes: 1 addition & 1 deletion movement/watch_faces/clock/mars_time_face.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ static void _h_to_hms(mars_clock_hms_t *date_time, double h) {
static void _update(movement_settings_t *settings, mars_time_state_t *state) {
char buf[11];
watch_date_time date_time = watch_rtc_get_date_time();
uint32_t now = watch_utility_date_time_to_unix_time(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60);
uint32_t now = watch_utility_date_time_to_unix_time(date_time, get_timezone_offset(settings->bit.time_zone, date_time) * 60);
// TODO: I'm skipping over some steps here.
// https://www.giss.nasa.gov/tools/mars24/help/algorithm.html
double jdut = 2440587.5 + ((double)now / 86400.0);
Expand Down
7 changes: 4 additions & 3 deletions movement/watch_faces/clock/world_clock2_face.c
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ void world_clock2_face_activate(movement_settings_t *settings, void *context)
movement_request_tick_frequency(4);
break;
}
state->tz = get_timezone_offset(settings->bit.time_zone, watch_rtc_get_date_time());
refresh_face = true;
}

Expand Down Expand Up @@ -183,8 +184,8 @@ static bool mode_display(movement_event_t event, movement_settings_t *settings,

/* Determine current time at time zone and store date/time */
date_time = watch_rtc_get_date_time();
timestamp = watch_utility_date_time_to_unix_time(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60);
date_time = watch_utility_date_time_from_unix_time(timestamp, movement_timezone_offsets[state->current_zone] * 60);
timestamp = watch_utility_date_time_to_unix_time(date_time, state->tz * 60);
date_time = watch_utility_date_time_from_unix_time(timestamp, state->tz * 60);
previous_date_time = state->previous_date_time;
state->previous_date_time = date_time.reg;

Expand Down Expand Up @@ -289,7 +290,7 @@ static bool mode_settings(movement_event_t event, movement_settings_t *settings,
watch_clear_indicator(WATCH_INDICATOR_PM);
refresh_face = false;
}
result = div(movement_timezone_offsets[state->current_zone], 60);
result = div(state->tz, 60);
hours = result.quot;
minutes = result.rem;

Expand Down
1 change: 1 addition & 0 deletions movement/watch_faces/clock/world_clock2_face.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ typedef struct {
world_clock2_mode_t current_mode;
uint8_t current_zone;
uint32_t previous_date_time;
int16_t tz;
} world_clock2_state_t;

void world_clock2_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void **context_ptr);
Expand Down
11 changes: 6 additions & 5 deletions movement/watch_faces/clock/world_clock_face.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,16 @@ static bool world_clock_face_do_display_mode(movement_event_t event, movement_se
watch_date_time date_time;
switch (event.event_type) {
case EVENT_ACTIVATE:
if (settings->bit.clock_mode_24h && !settings->bit.clock_24h_leading_zero) watch_set_indicator(WATCH_INDICATOR_24H);
state->tz = get_timezone_offset(settings->bit.time_zone, watch_rtc_get_date_time());
if (settings->bit.clock_mode_24h) watch_set_indicator(WATCH_INDICATOR_24H);
watch_set_colon();
state->previous_date_time = 0xFFFFFFFF;
// fall through
case EVENT_TICK:
case EVENT_LOW_ENERGY_UPDATE:
date_time = watch_rtc_get_date_time();
timestamp = watch_utility_date_time_to_unix_time(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60);
date_time = watch_utility_date_time_from_unix_time(timestamp, movement_timezone_offsets[state->settings.bit.timezone_index] * 60);
timestamp = watch_utility_date_time_to_unix_time(date_time, state->tz * 60);
date_time = watch_utility_date_time_from_unix_time(timestamp, state->tz * 60);
previous_date_time = state->previous_date_time;
state->previous_date_time = date_time.reg;

Expand Down Expand Up @@ -176,8 +177,8 @@ static bool _world_clock_face_do_settings_mode(movement_event_t event, movement_
sprintf(buf, "%c%c %3d%02d ",
movement_valid_position_0_chars[state->settings.bit.char_0],
movement_valid_position_1_chars[state->settings.bit.char_1],
(int8_t) (movement_timezone_offsets[state->settings.bit.timezone_index] / 60),
(int8_t) (movement_timezone_offsets[state->settings.bit.timezone_index] % 60) * (movement_timezone_offsets[state->settings.bit.timezone_index] < 0 ? -1 : 1));
(int8_t) (state->tz / 60),
(int8_t) (state->tz % 60) * (state->tz < 0 ? -1 : 1));
watch_set_colon();
watch_clear_indicator(WATCH_INDICATOR_PM);

Expand Down
Loading

0 comments on commit ac5bf8c

Please sign in to comment.