Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.nyc_output
build
node_modules
1 change: 1 addition & 0 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"<!(node -e \"require('nan')\")"
],
"sources": [
"src/monitor.cc",
"src/binding.cc"
]
}]
Expand Down
7 changes: 7 additions & 0 deletions example-hook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
var resetCache = require('reset-date-cache');

resetCache.registerHook(() => console.log(new Date().toString()));
console.log(new Date().toString());

//The timezone notifier is not referenced, set a dummy interval to keep running
setInterval(()=>{},1000);
4 changes: 3 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict'

const bindings = require('bindings')('resetdatecache')
var bindings = require('bindings')('resetdatecache');

module.exports = bindings.reset

module.exports.registerHook = bindings.setHook;
29 changes: 28 additions & 1 deletion src/binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include <v8.h>
#include <time.h>

#include "monitor.h"

using v8::Date;
using v8::FunctionTemplate;
using v8::Isolate;
Expand All @@ -11,19 +13,44 @@ using Nan::GetFunction;
using Nan::New;
using Nan::Set;

NAN_METHOD(Reset) {
TimeZoneMonitor *_monitor;
Nan::Callback *_hook = NULL;

void TimeZoneMonitor::Notify() {
Nan::HandleScope scope;

Isolate *isolate = Isolate::GetCurrent();
#ifdef _WIN32
_tzset();
#else
tzset();
#endif
Date::DateTimeConfigurationChangeNotification(isolate);
if(_hook) {
_hook->Call(0, NULL);
}
}

NAN_METHOD(Reset) {
_monitor->Notify();
}

NAN_METHOD(SetHook) {
if(_hook)
delete _hook;

if(info[0]->IsFunction())
_hook = new Nan::Callback(v8::Local<v8::Function>::Cast(info[0]));
else
_hook = NULL;
}

NAN_MODULE_INIT(Init) {
_monitor = TimeZoneMonitor::Create();
Set(target, New<String>("reset").ToLocalChecked(),
GetFunction(New<FunctionTemplate>(Reset)).ToLocalChecked());
Set(target, New<String>("setHook").ToLocalChecked(),
GetFunction(New<FunctionTemplate>(SetHook)).ToLocalChecked());
}

NODE_MODULE(resetdatecache, Init)
48 changes: 48 additions & 0 deletions src/monitor.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include "monitor.h"

static const uint64_t kDebounceDelay = 500;

void TimeZoneMonitor::_asyncWorkCb(uv_async_t *work) {
TimeZoneMonitor *monitor = static_cast<TimeZoneMonitor *>(work->data);
monitor->ThreadedCallback();
monitor->DebouncedNotify();
}

void TimeZoneMonitor::tzDelayExpired(uv_timer_t* handle) {
TimeZoneMonitor *monitor = static_cast<TimeZoneMonitor *>(handle->data);
monitor->Notify();
}

TimeZoneMonitor::TimeZoneMonitor() {
uv_async_init(uv_default_loop(), &_asyncWork, _asyncWorkCb);
uv_timer_init(uv_default_loop(), &_debouncer);
_asyncWork.data = this;
_debouncer.data = this;

uv_unref((uv_handle_t*)&_asyncWork);
uv_unref((uv_handle_t*)&_debouncer);
}

void TimeZoneMonitor::ThreadedNotify() {
uv_async_send(&_asyncWork);
}

void TimeZoneMonitor::DebouncedNotify() {
//Some platforms write the file instead of symlinking it, add a small delay just to be sure
uv_timer_start(&_debouncer, TimeZoneMonitor::tzDelayExpired, kDebounceDelay, 0);
}

#ifdef _WIN32
# include "monitor_windows.cc"
#elif __ANDROID__
# include "monitor_android.cc"
#elif __APPLE__
# include "monitor_unix.cc"
#elif __unix__
# include "monitor_unix.cc"
#else
// static
TimeZoneMonitor *TimeZoneMonitor::Create() {
return new TimeZoneMonitor();
}
#endif
23 changes: 23 additions & 0 deletions src/monitor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

#ifndef _TZD_MONITOR_H
#define _TZD_MONITOR_H
#include <uv.h>

class TimeZoneMonitor {
public:
// Returns a new TimeZoneMonitor object specific to the platform.
static TimeZoneMonitor *Create();
void Notify();
protected:
TimeZoneMonitor(); //Creation can only occur through create.
void DebouncedNotify();
void ThreadedNotify();
virtual void ThreadedCallback() {}
private:
static void tzDelayExpired(uv_timer_t* handle);
static void _asyncWorkCb(uv_async_t *work);
uv_async_t _asyncWork;
uv_timer_t _debouncer;
};

#endif
9 changes: 9 additions & 0 deletions src/monitor_android.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

//TODO: We should catch the TIMEZONE_CHANGED intent and fire ThreadedNotify
//Unfortunatly, it looks like this part requires some jni magic which may not work from inside a module
//Lets disable the notifications for now...

// static
TimeZoneMonitor *TimeZoneMonitor::Create() {
return new TimeZoneMonitor();
}
53 changes: 53 additions & 0 deletions src/monitor_unix.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#include <uv.h>
#include <string.h>
#include "monitor.h"


// There is no true standard for where time zone information is actually
// stored. glibc uses /etc/localtime, uClibc uses /etc/TZ, and some older
// systems store the name of the time zone file within /usr/share/zoneinfo
// in /etc/timezone. Different libraries and custom builds may mean that
// still more paths are used. Just watch all three of these paths, because
// false positives are harmless, assuming the false positive rate is
// reasonable.
static const char* kFilesToWatch[] = {
"localtime",
"timezone",
"TZ",
};
// libuv seems to have some troubles when monitoring symlinks.
//It does however work if we monitor the parent folder non-recursively
static const char* kWatchFolder = "/etc/";

class TimeZoneMonitorUnix : public TimeZoneMonitor {
public:
TimeZoneMonitorUnix();
private:
static void tzChange(uv_fs_event_t *handle, const char *filename, int events, int status);
uv_fs_event_t fileWatcher;
};


void TimeZoneMonitorUnix::tzChange(uv_fs_event_t *handle, const char *filename, int events, int status) {
TimeZoneMonitorUnix *monitor = static_cast<TimeZoneMonitorUnix *>(handle->data);
for (size_t i = 0; i < sizeof(kFilesToWatch)/sizeof(kFilesToWatch[0]); i++) {
if(strcmp(kFilesToWatch[i], filename) == 0) {
monitor->DebouncedNotify(); //Debounce the notification (sometimes multiple files change)
return;
}
}
}

TimeZoneMonitorUnix::TimeZoneMonitorUnix()
{
uv_fs_event_init(uv_default_loop(), &fileWatcher);
fileWatcher.data = this;
uv_unref((uv_handle_t*)&fileWatcher);

uv_fs_event_start(&fileWatcher, TimeZoneMonitorUnix::tzChange, kWatchFolder, 0);
}

// static
TimeZoneMonitor *TimeZoneMonitor::Create() {
return new TimeZoneMonitorUnix();
}
60 changes: 60 additions & 0 deletions src/monitor_windows.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#include "monitor.h"

static const DWORD dwFilter = REG_NOTIFY_CHANGE_LAST_SET;

class TimeZoneMonitorWindows : public TimeZoneMonitor {
public:
TimeZoneMonitorWindows();
protected:
void ThreadedCallback();
private:
void Notify();
void SetUp(void);
static void TimeZoneChangeThreadedCallback(PVOID context, BOOLEAN timeout);
HANDLE hTZEvent;
HKEY hRegKey;
};


void TimeZoneMonitorWindows::ThreadedCallback() {
//Setup only causes notify to be called once
SetUp();
}

void TimeZoneMonitorWindows::SetUp() {
ResetEvent(hTZEvent);
// Watch the registry key for a change of value, must be run on main thread
RegNotifyChangeKeyValue(hRegKey, TRUE,
dwFilter, hTZEvent, TRUE);
}

TimeZoneMonitorWindows::TimeZoneMonitorWindows() {

HANDLE hWait;
LONG lErrorCode;
lErrorCode = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", 0, KEY_NOTIFY, &hRegKey);
if (lErrorCode != ERROR_SUCCESS) return;

// Create an event.
hTZEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (hTZEvent == NULL) return;

SetUp();

//Invoke the TimeZoneChangeThreadedCallback upon a change
RegisterWaitForSingleObject(
&hWait, hTZEvent, TimeZoneChangeThreadedCallback,
this, INFINITE, WT_EXECUTEINWAITTHREAD
);
}

//static
void TimeZoneMonitorWindows::TimeZoneChangeThreadedCallback(PVOID context, BOOLEAN timeout) {
TimeZoneMonitorWindows *monitor = static_cast<TimeZoneMonitorWindows *>(context);
monitor->ThreadedNotify(); //First enter the main eventloop, then debounce the notify
}

// static
TimeZoneMonitor *TimeZoneMonitor::Create() {
return new TimeZoneMonitorWindows();
}