From 2791b449107ddea016bc394997974e6a4b3180f0 Mon Sep 17 00:00:00 2001 From: Bob Wen Date: Tue, 8 Aug 2023 17:38:15 +0800 Subject: [PATCH] win32: detect local date Detect windows date format by command `reg query`; Update dependencies; --- adjust_time.coffee | 40 +++++++---- argv.coffee | 99 +++++++++++++++++++++++---- index.coffee | 167 ++++++++++++++------------------------------- package.json | 43 ++++++------ 4 files changed, 185 insertions(+), 164 deletions(-) diff --git a/adjust_time.coffee b/adjust_time.coffee index cb62a85..755a3ba 100644 --- a/adjust_time.coffee +++ b/adjust_time.coffee @@ -1,12 +1,12 @@ util = require 'util' -{ spawn } = require 'child_process' +{ spawn, execSync } = require 'child_process' dayjs = require 'dayjs' delay = require './delay' dayjs.extend require './dayjs_format_ms' platform = require('os').platform() -adjust_time = (delta) -> +adjust_time = (delta, adjust_command) -> console.log 'Adjust time...' shell = spawn spawn_args... shell.stderr.on 'data', (data) -> console.error "> " + "#{data}".trim() @@ -28,30 +28,40 @@ adjust_time = (delta) -> if platform in ['win32'] await wait_data() new_time = dayjs().add(delta, 'ms') - cmd = new_time.format adjust_time.command + cmd = new_time.format adjust_command else - adjust_time.command = adjust_time.command.replace /ss\.S+/, 'ss' + adjust_command = adjust_command.replace /ss\.S+/, 'ss' new_time = dayjs().add(delta, 'ms') ms = new_time.get('millisecond') if ms > 0 wait_time = 1000 - ms new_time = new_time.add(wait_time, 'ms') delay wait_time - cmd = new_time.format adjust_time.command + cmd = new_time.format adjust_command await input_line cmd await input_line 'exit' -COMMANDS = { - # FIXME get the perfect win32 command with right format - # yes I got it, weird but effective - # reg query "HKEY_CURRENT_USER\Control Panel\International" /v sShortDate - # various formats need to be recognized... - #* @see # https://calendars.wikia.org/wiki/Date_format_by_country - win32: '[time ]HH:mm:ss.SS[ && date ]MM-DD-YY' - linux: '[date -s ]"YYYY-MM-DD HH:mm:ss"' -} -adjust_time.command = COMMANDS[platform] or COMMANDS.linux +# win32: get local date format by command: +# reg query "HKCU\Control Panel\International" /v sShortDate +#* @see # https://calendars.wikia.org/wiki/Date_format_by_country +if platform isnt 'win32' + adjust_time.COMMAND = '[date -s ]"YYYY-MM-DD HH:mm:ss"' +else + date_format = 'MM-DD-YY' + try + date_format_raw = ('' + execSync( + 'reg query "HKCU\\Control Panel\\International" /v sShortDate' + )).trim().split(/[\n\r]+/).pop()?.trim().split(/\s+/).pop() + catch err + null + if date_format_raw and date_format_raw isnt '' + date_format = date_format_raw. + replace(/[^YMDymd]+/g, '-'). + replace(/y+/gi, 'YY'). + replace(/m+/gi, 'MM'). + replace(/d+/gi, 'DD') + adjust_time.COMMAND = '[time ]HH:mm:ss.SS[ && date ]' + date_format SHELL_ARGS = { diff --git a/argv.coffee b/argv.coffee index 5ec6aa9..2cf4cd5 100644 --- a/argv.coffee +++ b/argv.coffee @@ -4,10 +4,90 @@ minimist = require 'minimist' minimist_opt = require 'minimist-options' { orderBy } = require 'natural-orderby' info = require './package.json' +adjust_time = require './adjust_time' platform = require('os').platform() -DEFAULT_OPTIONS = { +CLI_OPTIONS = { + threshold: { + alias: 't' + describe: 'At least how many milliseconds are considered to adjust system time' + default: 1500 + type: 'number' + } + set: { + describe: 'Adjust system time if necessary' + alias: 's' + default: false + type: 'boolean' + } + protocol: { + describe: 'Use this protocol when no protocol is specified in the URL' + alias: 'p' + default: 'https' + type: 'string' + } + method: { + describe: 'HTTP method' + alias: 'm' + default: 'HEAD' + type: 'string' + } + http2: { + describe: 'Try to choose either HTTP/1.1 or HTTP/2 depending on the ALPN protocol' + default: false + type: 'boolean' + } + count: { + describe: 'The number of requests for each URL' + alias: 'c' + default: 4 + type: 'number' + } + retry: { + alias: 'r' + default: 0 + type: 'number' + } + redirect: { + describe: 'If redirect responses should be followed' + alias: 'R' + default: false + type: 'boolean' + } + timeout: { + alias: 'T' + default: 10000 + type: 'number' + } + insecure: { + describe: 'Allow insecure server connections when using https' + alias: 'k' + default: false + type: 'boolean' + } + command: { + describe: 'Command to adjust system time, in https://day.js.org/ UTC format' + alias: 'C' + default: adjust_time.COMMAND + type: 'string' + } + interval: { + describe: 'The minimum milliseconds between requests' + alias: 'i' + default: 500 + type: 'number' + } + 'user-agent': { + alias: 'u' + type: 'string' + } + verbose: { + describe: 'Make the operation more talkative' + alias: 'v' + default: false + type: 'boolean' + } help: { describe: 'This help text' type: 'boolean' @@ -105,23 +185,18 @@ print_examples = (exam = []) -> -module.exports = (opt, exam) -> - cli_options = { - DEFAULT_OPTIONS... - opt... - } - argv = minimist process.argv.slice(2), minimist_opt cli_options +module.exports = (exam) -> + argv = minimist process.argv.slice(2), minimist_opt CLI_OPTIONS if argv.version print_version() process.exit 0 if argv.help or argv._.length is 0 - print_usage cli_options + print_usage CLI_OPTIONS print_examples exam console.log() - if argv.help - process.exit 0 - console.error "\nError: Missing server URL, at least one URL should be specified" - process.exit 0 + if not argv.help + console.error "\nError: Missing server URL, at least one URL should be specified" + return process.exit 0 if argv.count < 1 argv.count = 1 if argv.retry < 0 diff --git a/index.coffee b/index.coffee index 38ee965..9e619c3 100755 --- a/index.coffee +++ b/index.coffee @@ -1,6 +1,5 @@ #!/usr/bin/env coffee -got = require 'got' Agent = require 'agentkeepalive' { HttpProxyAgent, HttpsProxyAgent } = require('hpagent') dayjs = require 'dayjs' @@ -10,88 +9,7 @@ adjust_time = require './adjust_time' delay = require './delay' -argv = require('./argv') { - threshold: { - alias: 't' - describe: 'At least how many milliseconds are considered to adjust system time' - default: 1500 - type: 'number' - } - set: { - describe: 'Adjust system time if necessary' - alias: 's' - default: false - type: 'boolean' - } - protocol: { - describe: 'Use this protocol when no protocol is specified in the URL' - alias: 'p' - default: 'https' - type: 'string' - } - method: { - describe: 'HTTP method' - alias: 'm' - default: 'HEAD' - type: 'string' - } - http2: { - describe: 'Try to choose either HTTP/1.1 or HTTP/2 depending on the ALPN protocol' - default: false - type: 'boolean' - } - count: { - describe: 'The number of requests for each URL' - alias: 'c' - default: 4 - type: 'number' - } - retry: { - alias: 'r' - default: 0 - type: 'number' - } - redirect: { - describe: 'If redirect responses should be followed' - alias: 'R' - default: false - type: 'boolean' - } - timeout: { - alias: 'T' - default: 6000 - type: 'number' - } - insecure: { - describe: 'Allow insecure server connections when using https' - alias: 'k' - default: false - type: 'boolean' - } - command: { - describe: 'Command to adjust system time, in https://day.js.org/ UTC format' - alias: 'C' - default: adjust_time.command - type: 'string' - } - interval: { - describe: 'The minimum milliseconds between requests' - alias: 'i' - default: 500 - type: 'number' - } - 'user-agent': { - alias: 'u' - type: 'string' - } - verbose: { - describe: 'Make the operation more talkative' - alias: 'v' - default: false - type: 'boolean' - } -} -adjust_time.command = argv.command +argv = null agent_opt = { keepAlive: true, @@ -115,49 +33,48 @@ else https: new HttpsProxyAgent agent_opt } -req_opt = { - method: argv.method.trim().toUpperCase() - followRedirect: argv.redirect - timeout: argv.timeout - retry: 0 - dnsCache: true - cache: false - agent - headers: { - 'user-agent': argv['user-agent'] - } - https: { - rejectUnauthorized: not argv.insecure - } - http2: argv.http2 -} -client = got.extend {} -get_server_time = (url) -> +client = false +get_server_time = (url, req_opt) -> + if not client + got = await import('got') + client = got.default.extend {} + abort_controller = new AbortController + check_timer = setTimeout -> + abort_controller.abort() + , argv.timeout try - return await client url, req_opt + r = await client url, { + ...req_opt + signal: abort_controller.signal + } + clearTimeout check_timer + return r catch e + clearTimeout check_timer return e.response if e.response?.timings? throw e -get_time_delta = (url) -> +get_time_delta = (url, req_opt) -> if not /^https?:\/\//.test url url = "#{argv.protocol}://#{url}" console.log "#{argv.method} #{url}" for i in [1 .. argv.count] step = "\##{i}: ".padStart 8 + server_moment = false + r = false for retry in [0 .. argv.retry] start_at = Date.now() try - r = await get_server_time url + r = await get_server_time url, req_opt server_moment = + dayjs r.headers.date catch e - console.log "#{step}#{e}" + message = if e.code in ['ERR_ABORTED', 'ETIMEDOUT'] then 'Timeout' else "#{e.code} #{message}" + console.log "#{step}#{message}" await delay argv.interval + start_at - Date.now() - if r?.timings? and server_moment? - break - continue if not server_moment? + break if server_moment + continue if not server_moment or not r or not r.timings? if not r.timings.secureConnect? upload_at = r.timings.connect else @@ -178,6 +95,27 @@ get_time_delta = (url) -> do -> + argv = await require('./argv')() + req_opt = { + method: argv.method.trim().toUpperCase() + followRedirect: argv.redirect + timeout: { + request: argv.timeout + } + retry: { + limit: 0 + } + dnsCache: true + cache: false + agent + headers: { + 'user-agent': argv['user-agent'] + } + https: { + rejectUnauthorized: not argv.insecure + } + http2: argv.http2 + } if proxy isnt '' msg = '' if argv.http2 @@ -186,15 +124,16 @@ do -> console.debug "Using explicit proxy server #{proxy}#{msg}" values = [] for url in argv._ - values.push (await get_time_delta url)... + values.push (await get_time_delta url, req_opt)... if values.length is 0 console.log "Network failure" - process.exit 2 - return + return process.exit 2 delta = median values console.log "Median: " + "#{delta} ms".padStart 10 - return if not argv.set + if not argv.set + return process.exit 0 if Math.abs(delta) < argv.threshold console.log "There is no need to adjust the time" - return - await adjust_time delta + return process.exit 0 + await adjust_time delta, argv.command + return process.exit 0 diff --git a/package.json b/package.json index 97f7442..abd446e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "htpdate", - "version": "1.0.2", + "version": "1.0.3", "description": "a tool to synchronize system time from web servers", "keywords": [ "htp", @@ -23,44 +23,41 @@ "url": "git+https://github.com/bobwen-dev/htpdate.git" }, "scripts": { - "compile": "npx coffee -bc --no-header ./", + "compile": "coffee -bc --no-header ./", "clean": "rm *.js", - "b": "npx nexe -i index.js -r node_modules/**/package.json", - "b-win-x86": "npm run b -- -t windows-x86-14.15.3 -o dists/htpdate-windows-x86.exe", - "b-win-x64": "npm run b -- -t windows-x64-14.17.0 -o dists/htpdate-windows-x64.exe", - "b-linux-x64": "npm run b -- -t linux-x64-14.15.3 -o dists/htpdate-linux-x64", - "b-linux-arm64": "npm run b -- -t linux-arm64-14.15.4 -o dists/htpdate-linux-arm64", - "b-linux-armv7l": "npm run b -- -t linux-arm-14.18.1 -o dists/htpdate-linux-armv7l", - "b-mac-x64": "npm run b -- -t mac-x64-14.15.3 -o dists/htpdate-mac-x64", - "b-all": "npm run b-win-x86 && npm run b-win-x64 && npm run b-linux-x64 && npm run b-linux-arm64 && npm run b-linux-armv7l && npm run b-mac-x64", - "c-win-x86": "cd dists && cp htpdate-windows-x86.exe htpdate.exe && 7z a htpdate-windows-x86.7z htpdate.exe && rm -rf htpdate.exe", - "c-win-x64": "cd dists && cp htpdate-windows-x64.exe htpdate.exe && 7z a htpdate-windows-x64.7z htpdate.exe && rm -rf htpdate.exe", + "b": "nexe -i index.js -r node_modules/**/package.json", + "b-win-x86": "npm run b -- -t windows-x86-18.12.1 -o dists/htpdate-windows-x86.exe", + "b-win-x64": "npm run b -- -t windows-x64-18.12.1 -o dists/htpdate-windows-x64.exe", + "b-linux-x64": "npm run b -- -t linux-x64-18.12.1 -o dists/htpdate-linux-x64", + "b-linux-arm64": "npm run b -- -t linux-arm64-18.12.1 -o dists/htpdate-linux-arm64", + "b-linux-armv7l": "npm run b -- -t linux-arm-18.12.1 -o dists/htpdate-linux-armv7l", + "b-mac-x64": "npm run b -- -t mac-x64-18.12.1 -o dists/htpdate-mac-x64", + "c-win-x86": "cd dists && copy htpdate-windows-x86.exe htpdate.exe && 7z a htpdate-windows-x86.7z htpdate.exe && del -rf htpdate.exe", + "c-win-x64": "cd dists && copy htpdate-windows-x64.exe htpdate.exe && 7z a htpdate-windows-x64.7z htpdate.exe && del -rf htpdate.exe", "c-linux-x64": "cd dists && cp htpdate-linux-x64 htpdate && 7z a htpdate-linux-x64.7z htpdate && rm -rf htpdate", "c-linux-arm64": "cd dists && cp htpdate-linux-arm64 htpdate && 7z a htpdate-linux-arm64.7z htpdate && rm -rf htpdate", "c-linux-armv7l": "cd dists && cp htpdate-linux-armv7l htpdate && 7z a htpdate-linux-armv7l.7z htpdate && rm -rf htpdate", "c-mac-x64": "cd dists && cp htpdate-mac-x64 htpdate && 7z a htpdate-mac-x64.7z htpdate && rm -rf htpdate", - "compress": "npm run c-win-x86 && npm run c-win-x64 && npm run c-linux-x64 && npm run c-linux-arm64 && npm run c-linux-armv7l && npm run c-mac-x64", - "build": "npm run compile && npm run b-all && npm run clean", - "test": "npx coffee index.coffee www.pool.ntp.org" + "test": "coffee index.coffee www.pool.ntp.org" }, "main": "index.js", "bin": { "htpdate": "index.coffee" }, "engines": { - "node": ">=10.19.0" + "node": ">=18.12.0" }, "dependencies": { - "agentkeepalive": "^4.1.4", - "dayjs": "^1.10.7", - "got": "^11.8.0", - "hpagent": "^0.1.2", - "minimist": "^1.2.5", + "agentkeepalive": "^4.5.0", + "dayjs": "^1.11.9", + "got": "^13.0.0", + "hpagent": "^1.2.0", + "minimist": "^1.2.8", "minimist-options": "^4.1.0", - "natural-orderby": "^2.0.3" + "natural-orderby": "^3.0.2" }, "devDependencies": { - "coffeescript": "^2.5.1", + "coffeescript": "^2.7.0", "nexe": "^4.0.0-beta.16" } }