├── .gitignore ├── .jshintrc ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── appveyor.yml ├── binding.gyp ├── index.js ├── package.json ├── src └── time.cc └── test ├── date.js ├── exports.js └── tzset.js /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /node_modules 3 | .idea -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "asi": true, 3 | "laxcomma": true, 4 | "node": true, 5 | "strict": false 6 | } 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | env: 4 | - CXX=g++-4.8 5 | addons: 6 | apt: 7 | sources: 8 | - ubuntu-toolchain-r-test 9 | packages: 10 | - g++-4.8 11 | 12 | language: node_js 13 | 14 | node_js: 15 | - "0.8" 16 | - "0.10" 17 | - "0.12" 18 | - "1" 19 | - "2" 20 | - "3" 21 | - "4" 22 | - "5" 23 | - "6" 24 | - "7" 25 | 26 | install: 27 | - PATH="`npm bin`:`npm bin -g`:$PATH" 28 | # Node 0.8 comes with a too obsolete npm 29 | - if [[ "`node --version`" =~ ^v0\.8\. ]]; then npm install -g npm@1.4.28 ; fi 30 | # Install dependencies and build 31 | - npm install 32 | 33 | script: 34 | # Output useful info for debugging 35 | - node --version 36 | - npm --version 37 | # Run tests 38 | - npm test 39 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 0.12.0 / 2017-03-02 2 | =================== 3 | 4 | * [[`c60aed6762`](https://github.com/TooTallNate/node-time/commit/c60aed6762)] - add appveyor badge (Nathan Rajlich) 5 | * [[`530b2bfb7d`](https://github.com/TooTallNate/node-time/commit/530b2bfb7d)] - upgraded to nan 2.2.0 (sambros) 6 | * [[`70288ec41e`](https://github.com/TooTallNate/node-time/commit/70288ec41e)] - add appveyor Windows CI (Nathan Rajlich) 7 | * [[`fdc84473a9`](https://github.com/TooTallNate/node-time/commit/fdc84473a9)] - fix bug (hanbowen) 8 | * [[`68046da00c`](https://github.com/TooTallNate/node-time/commit/68046da00c)] - fix visual studio 2015 compile error (hanbowen) (#88) 9 | * [[`9687519a7d`](https://github.com/TooTallNate/node-time/commit/9687519a7d)] - test node v7 (Nathan Rajlich) 10 | * [[`e5dc933b67`](https://github.com/TooTallNate/node-time/commit/e5dc933b67)] - **travis**: test node v6 (Nathan Rajlich) 11 | * [[`90c4d5b394`](https://github.com/TooTallNate/node-time/commit/90c4d5b394)] - **travis**: test node v5 (Nathan Rajlich) 12 | * [[`947378e337`](https://github.com/TooTallNate/node-time/commit/947378e337)] - **travis**: test io.js and node v4 (Nathan Rajlich) 13 | * [[`360dc08552`](https://github.com/TooTallNate/node-time/commit/360dc08552)] - **package**: update deps (Nathan Rajlich) 14 | 15 | 0.11.4 / 2015-08-28 16 | =================== 17 | 18 | * package: allow any "nan" v2 19 | * package: looser "bindings" version 20 | * package: upgrade to nan v2 (#77, @santigimeno) 21 | 22 | 0.11.3 / 2015-05-18 23 | =================== 24 | 25 | * package: update "nan" to ~1.8.0 (#72, @imyller) 26 | 27 | 0.11.2 / 2015-03-29 28 | =================== 29 | 30 | * package: allow any "debug" v2 31 | * package: update "nan" to v1.7.0 32 | 33 | 0.11.1 / 2015-02-11 34 | =================== 35 | 36 | * travis: test v0.12, don't test v0.6 and v0.11 37 | * test: fix failing test (#67, @hmalphettes) 38 | * package: upgrade to the latest "nan" (#66, @hmalphettes) 39 | * README: use svg for Travis badge 40 | 41 | 0.11.0 / 2014-06-02 42 | =================== 43 | 44 | * package: add "license" field 45 | * travis: don't test node v0. and, v0.9, do test v0.6 46 | * update "nan" to v1.1.2 API, and pin all the deps 47 | * package: remove "engines" field 48 | * package: update "description" 49 | * fix timezone formatting in toTimeString() (#56, @benbria) 50 | * remove wscript file 51 | * remove Makefile 52 | * package: remove "should" from devDependencies 53 | * test: remove "should" usage from tests 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Nathan Rajlich 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | node-time 2 | ========= 3 | ### "[time.h][]" bindings for [Node.js][Node]. 4 | [![Build Status](https://travis-ci.org/TooTallNate/node-time.svg?branch=master)](https://travis-ci.org/TooTallNate/node-time) 5 | [![Build Status](https://ci.appveyor.com/api/projects/status/weml61hm3ebh5mnt/branch/master?svg=true)](https://ci.appveyor.com/project/TooTallNate/node-time/branch/master) 6 | 7 | 8 | This module offers simple bindings for the C [time.h][] APIs. 9 | It also offers an extended native `Date` object with `getTimezone()` 10 | and `setTimezone()` functions, which aren't normally part of JavaScript. 11 | 12 | 13 | Installation 14 | ------------ 15 | 16 | `node-time` is available through npm: 17 | 18 | ``` bash 19 | $ npm install time 20 | ``` 21 | 22 | 23 | Example 24 | ------- 25 | 26 | ``` javascript 27 | var time = require('time'); 28 | 29 | // Create a new Date instance, representing the current instant in time 30 | var now = new time.Date(); 31 | 32 | now.setTimezone("America/Los_Angeles"); 33 | // `.getDate()`, `.getDay()`, `.getHours()`, etc. 34 | // will return values according to UTC-8 35 | 36 | now.setTimezone("America/New_York"); 37 | // `.getDate()`, `.getDay()`, `.getHours()`, etc. 38 | // will return values according to UTC-5 39 | 40 | 41 | // You can also set the timezone during instantiation 42 | var azDate = new time.Date(2010, 0, 1, 'America/Phoenix'); 43 | azDate.getTimezone(); // 'America/Phoenix' 44 | ``` 45 | 46 | ### Extending the global `Date` object 47 | 48 | `node-time` provides a convenient `time.Date` object, which is its own Date 49 | constructor independent from your own (or the global) Date object. There are often 50 | times, however, when you would like the benefits of node-time on *all* Date 51 | instances. To extend the global Date object, simply pass it in as an argument to 52 | the node-time module when requiring: 53 | 54 | ``` js 55 | var time = require('time')(Date); 56 | 57 | var d = new Date(); 58 | d.setTimezone('UTC'); 59 | ``` 60 | 61 | 62 | API 63 | --- 64 | 65 | 66 | ### Date() -> Date 67 | #### new time.Date() 68 | #### new time.Date(millisecondsFromUTC) 69 | #### new time.Date(dateString [, timezone ]) 70 | #### new time.Date(year, month, day [, hour, minute, second, millisecond ] [, timezone ]) 71 | 72 | A special `Date` constructor that returns a "super" Date instance, that has 73 | magic _timezone_ capabilities! You can also pass a `timezone` as the last 74 | argument in order to have a Date instance in the specified timezone. 75 | 76 | ``` javascript 77 | var now = new time.Date(); 78 | var another = new time.Date('Aug 9, 1995', 'UTC'); 79 | var more = new time.Date(1970, 0, 1, 'Europe/Amsterdam'); 80 | ``` 81 | 82 | 83 | #### date.setTimezone(timezone [, relative ]) -> Undefined 84 | 85 | Sets the timezone for the `Date` instance. By default this function makes it so 86 | that calls to `getHours()`, `getDays()`, `getMinutes()`, etc. will be relative to 87 | the timezone specified. If you pass `true` in as the second argument, then 88 | instead of adjusting the local "get" functions to match the specified timezone, 89 | instead the internal state of the Date instance is changed, such that the local 90 | "get" functions retain their values from before the setTimezone call. 91 | 92 | ``` javascript 93 | date.setTimezone("America/Argentina/San_Juan") 94 | 95 | // Default behavior: 96 | a = new time.Date() 97 | a.toString() 98 | // 'Wed Aug 31 2011 09:45:31 GMT-0700 (PDT)' 99 | a.setTimezone('UTC') 100 | a.toString() 101 | // 'Wed Aug 31 2011 16:45:31 GMT+0000 (UTC)' 102 | 103 | // Relative behavior: 104 | b = new time.Date() 105 | b.toString() 106 | // 'Wed Aug 31 2011 10:48:03 GMT-0700 (PDT)' 107 | b.setTimezone('UTC', true) 108 | b.toString() 109 | // 'Wed Aug 31 2011 10:48:03 GMT+0000 (UTC)' 110 | ``` 111 | 112 | 113 | #### date.getTimezone() -> String 114 | 115 | Returns a String containing the currently configured timezone for the date instance. 116 | This must be called _after_ `setTimezone()` has been called. 117 | 118 | ``` javascript 119 | date.getTimezone(); 120 | // "America/Argentina/San_Juan" 121 | ``` 122 | 123 | 124 | #### date.getTimezoneAbbr() -> String 125 | 126 | Returns the abbreviated timezone name, also taking daylight savings into consideration. 127 | Useful for the presentation layer of a Date instance. 128 | 129 | ``` javascript 130 | date.getTimezoneAbbr(); 131 | // "ART" 132 | ``` 133 | 134 | 135 | ### Date.parse(dateStr [, timezone ]) -> Number 136 | 137 | Same as the native JavaScript `Date.parse()` function, only this version allows 138 | for a second, optional, `timezone` argument, which specifies the timezone in 139 | which the date string parsing will be resolved against. This function is also 140 | aliased as `time.parse()`. 141 | 142 | ``` javascript 143 | time.Date.parse("1970, January 1"); // <- Local Time 144 | // 28800000 145 | time.Date.parse("1970, January 1", "Europe/Copenhagen"); 146 | // -3600000 147 | time.Date.parse("1970, January 1", "UTC"); 148 | // 0 149 | ``` 150 | 151 | 152 | ### extend(date) -> Date 153 | 154 | Transforms a "regular" Date instance into one of `node-time`'s "extended" Date instances. 155 | 156 | ``` javascript 157 | var d = new Date(); 158 | // `d.setTimezone()` does not exist... 159 | time.extend(d); 160 | d.setTimezone("UTC"); 161 | ``` 162 | 163 | 164 | ### time() -> Number 165 | 166 | Binding for `time()`. Returns the number of seconds since Jan 1, 1900 UTC. 167 | These two are equivalent: 168 | 169 | ``` javascript 170 | time.time(); 171 | // 1299827226 172 | Math.floor(Date.now() / 1000); 173 | // 1299827226 174 | ``` 175 | 176 | 177 | ### tzset(timezone) -> Object 178 | 179 | Binding for `tzset()`. Sets up the timezone information that `localtime()` will 180 | use based on the specified _timezone_ variable, or the current `process.env.TZ` 181 | value if none is specified. Returns an Object containing information about the 182 | newly set timezone, or throws an Error if no timezone information could be loaded 183 | for the specified timezone. 184 | 185 | ``` javascript 186 | time.tzset('US/Pacific'); 187 | // { tzname: [ 'PST', 'PDT' ], 188 | // timezone: 28800, 189 | // daylight: 1 } 190 | ``` 191 | 192 | 193 | ### localtime(Number) -> Object 194 | 195 | Binding for `localtime()`. Accepts a Number with the number of seconds since the 196 | Epoch (i.e. the result of `time()`), and returns a "broken-down" Object 197 | representation of the timestamp, according the the currently configured timezone 198 | (see `tzset()`). 199 | 200 | ``` javascript 201 | time.localtime(Date.now()/1000); 202 | // { seconds: 38, 203 | // minutes: 7, 204 | // hours: 23, 205 | // dayOfMonth: 10, 206 | // month: 2, 207 | // year: 111, 208 | // dayOfWeek: 4, 209 | // dayOfYear: 68, 210 | // isDaylightSavings: false, 211 | // gmtOffset: -28800, 212 | // timezone: 'PST' } 213 | ``` 214 | 215 | 216 | ### currentTimezone -> String 217 | 218 | The `currentTimezone` property always contains a String to the current timezone 219 | being used by `node-time`. This property is reset every time the `tzset()` 220 | function is called. Individual `time.Date` instances may have independent 221 | timezone settings than what this one is... 222 | 223 | 224 | [Node]: http://nodejs.org 225 | [time.h]: http://en.wikipedia.org/wiki/Time.h 226 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # http://www.appveyor.com/docs/appveyor-yml 2 | 3 | # Test against these versions of Node.js. 4 | environment: 5 | # Visual Studio Version 6 | MSVS_VERSION: 2013 7 | # Test against these versions of Node.js and io.js 8 | matrix: 9 | # node.js 10 | - nodejs_version: "0.10" 11 | - nodejs_version: "0.12" 12 | # io.js 13 | - nodejs_version: "2" 14 | - nodejs_version: "3.2" 15 | - nodejs_version: "4" 16 | - nodejs_version: "5" 17 | - nodejs_version: "6" 18 | - nodejs_version: "7" 19 | 20 | platform: 21 | - x86 22 | - x64 23 | 24 | # Install scripts. (runs after repo cloning) 25 | install: 26 | - ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) 27 | - npm install -g npm@3 28 | - set PATH=%APPDATA%\npm;%PATH% 29 | # Typical npm stuff. 30 | - npm install --msvs_version=%MSVS_VERSION% 31 | 32 | # Post-install test scripts. 33 | test_script: 34 | # Output useful info for debugging. 35 | - node --version 36 | - npm --version 37 | # run tests 38 | - npm test 39 | 40 | # Don't actually build. 41 | build: off 42 | 43 | # Set build version format here instead of in the admin panel. 44 | version: "{build}" 45 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'targets': [ 3 | { 4 | 'target_name': 'time', 5 | 'include_dirs': [ 6 | ' 0) { 80 | var d = possibleTzdirs.shift(); 81 | debug('checking if directory exists:', d); 82 | try { 83 | if (fs.statSync(d).isDirectory()) { 84 | TZDIR = d; 85 | break; 86 | } 87 | } catch (e) { 88 | debug(e); 89 | } 90 | } 91 | possibleTzdirs = null; // garbage collect 92 | if (TZDIR) { 93 | debug('found timezone directory at:', TZDIR); 94 | } else { 95 | debug('WARN: Could not find timezone directory. listTimezones() won\'t work'); 96 | } 97 | 98 | /** 99 | * Older versions of node-time would require the user to have the TZ 100 | * environment variable set, otherwise undesirable results would happen. Now 101 | * node-time tries to automatically determine the current timezone for you. 102 | */ 103 | 104 | if (!exports.currentTimezone) { 105 | debug('`process.env.TZ` not initially set, attempting to resolve'); 106 | try { 107 | var currentTimezonePath = fs.readlinkSync('/etc/localtime'); 108 | if (currentTimezonePath.substring(0, TZDIR.length) === TZDIR) { 109 | // Got It! 110 | var zone = currentTimezonePath.substring(TZDIR.length + 1); 111 | exports.currentTimezone = process.env.TZ = zone; 112 | debug('resolved initial timezone:', zone); 113 | } 114 | } catch (e) { 115 | debug(e); 116 | } 117 | } 118 | 119 | if (!exports.currentTimezone) { 120 | debug('"currentTimezone" still not set. Checking "/etc/timezone"'); 121 | try { 122 | var zone = fs.readFileSync('/etc/timezone', 'utf8').trim(); 123 | exports.currentTimezone = process.env.TZ = zone; 124 | debug('resolved initial timezone:', zone); 125 | } catch (e) { 126 | debug(e); 127 | } 128 | } 129 | 130 | if (!exports.currentTimezone) { 131 | debug('"currentTimezone" still not set. Checking "/etc/TZ"'); 132 | try { 133 | var zone = fs.readFileSync('/etc/TZ', 'utf8').trim(); 134 | exports.currentTimezone = process.env.TZ = zone; 135 | debug('resolved initial timezone:', zone); 136 | } catch (e) { 137 | debug(e); 138 | } 139 | } 140 | 141 | /** 142 | * The user-facing 'tzset' function is a thin wrapper around the native binding to 143 | * 'tzset()'. This function accepts a timezone String to set the process' timezone 144 | * to. Returns an object with the zoneinfo for the timezone. 145 | * 146 | * Throws (on *some* platforms) when the desired timezone could not be loaded. 147 | * 148 | * Sets the `currentTimezone` property on the exports. 149 | */ 150 | 151 | function tzset (tz) { 152 | if (tz) { 153 | process.env.TZ = tz; 154 | } 155 | var usedTz = process.env.TZ; 156 | var rtn = bindings.tzset(); 157 | debug('set the current timezone to:', usedTz); 158 | if (!rtn.tzname[1] && rtn.timezone === 0) { 159 | debug('got bad zoneinfo object:', rtn); 160 | var err = new Error("Unknown Timezone: '" + usedTz + "'"); 161 | for (var i in rtn) { 162 | err[i] = rtn[i]; 163 | } 164 | throw err; 165 | } 166 | exports.currentTimezone = usedTz; 167 | exports._currentZoneinfo = rtn; 168 | return rtn; 169 | } 170 | exports.tzset = tzset; 171 | 172 | /** 173 | * Lists the timezones that the current system can accept. It does this by going 174 | * on a recursive walk through the timezone dir and collecting filenames. 175 | */ 176 | 177 | function listTimezones () { 178 | if (arguments.length == 0) { 179 | throw new Error("You must set a callback"); 180 | } 181 | if (typeof arguments[arguments.length - 1] != "function") { 182 | throw new Error("You must set a callback"); 183 | } 184 | var cb = arguments[arguments.length - 1] 185 | , subset = (arguments.length > 1 ? arguments[0] : null) 186 | 187 | return listTimezonesFolder(subset ? subset + "/" : "", subset ? path.join(TZDIR, "/" + subset) : TZDIR, function (err, tzs) { 188 | if (err) return cb(err); 189 | cb(null, tzs.sort()); 190 | }); 191 | } 192 | exports.listTimezones = listTimezones; 193 | 194 | function listTimezonesFolder(prefix, folder, cb) { 195 | var timezones = []; 196 | 197 | fs.readdir(folder, function (err, files) { 198 | if (err) return cb(err); 199 | 200 | var pending_stats = files.length; 201 | 202 | for (var i = 0; i < files.length; i++) { 203 | if (~TZ_BLACKLIST.indexOf(files[i]) 204 | || files[i].indexOf(".") >= 0 205 | || files[i][0].toUpperCase() != files[i][0]) { 206 | pending_stats--; 207 | continue 208 | } 209 | fs.stat(path.join(folder, files[i]), (function (file) { 210 | return function (err, stats) { 211 | if (!err) { 212 | if (stats.isDirectory()) { 213 | listTimezonesFolder(prefix + file + "/", path.join(folder, file), function (err, tzs) { 214 | if (!err) { 215 | timezones = timezones.concat(tzs); 216 | } 217 | pending_stats--; 218 | if (pending_stats == 0) cb(null, timezones); 219 | }); 220 | return; 221 | } 222 | if (prefix.length > 0) timezones.push(prefix + file); 223 | } 224 | pending_stats--; 225 | if (pending_stats == 0) cb(null, timezones); 226 | }; 227 | })(files[i])); 228 | } 229 | }); 230 | } 231 | 232 | /** 233 | * The "setTimezone" function is the "entry point" for a Date instance. 234 | * It must be called after an instance has been created. After, the 'getSeconds()', 235 | * 'getHours()', 'getDays()', etc. functions will return values relative 236 | * to the time zone specified. 237 | */ 238 | 239 | function setTimezone (timezone, relative) { 240 | debug('Date#setTimezone(%s, %s)', timezone, relative); 241 | 242 | // If `true` is passed in as the second argument, then the Date instance 243 | // will have it's timezone set, but it's current local values will remain 244 | // the same (i.e. the Date's internal time value will be changed) 245 | var ms, s, m, h, d, mo, y 246 | if (relative) { 247 | y = this.getFullYear() 248 | mo = this.getMonth() 249 | d = this.getDate() 250 | h = this.getHours() 251 | m = this.getMinutes() 252 | s = this.getSeconds() 253 | ms = this.getMilliseconds() 254 | } 255 | 256 | // If the current process timezone doesn't match the desired timezone, then call 257 | // tzset() to change the current timezone of the process. 258 | var oldTz = exports.currentTimezone 259 | , tz = exports._currentZoneinfo; 260 | if (!tz || oldTz !== timezone) { 261 | debug('current timezone is not "%s", calling tzset()', timezone); 262 | tz = exports.tzset(timezone); 263 | } 264 | 265 | // Get the zoneinfo for this Date instance's time value 266 | var zoneInfo = exports.localtime(this.getTime() / 1000); 267 | 268 | // Change the timezone back if we changed it originally 269 | if (oldTz != timezone) { 270 | debug('setting timezone back to "%s"', oldTz); 271 | exports.tzset(oldTz); 272 | } 273 | oldTz = null; 274 | 275 | // If we got to here without throwing an Error, then 276 | // a valid timezone was requested, and we should have 277 | // a valid zoneInfo Object. 278 | this.getTimezone = function getTimezone() { 279 | return timezone; 280 | } 281 | 282 | // Returns the day of the month (1-31) for the specified date according to local time. 283 | this.getDate = function getDate() { 284 | return zoneInfo.dayOfMonth; 285 | } 286 | // Returns the day of the week (0-6) for the specified date according to local time. 287 | this.getDay = function getDay() { 288 | return zoneInfo.dayOfWeek; 289 | } 290 | // Deprecated. Returns the year (usually 2-3 digits) in the specified date according 291 | // to local time. Use `getFullYear()` instead. 292 | this.getYear = function getYear() { 293 | return zoneInfo.year; 294 | } 295 | // Returns the year (4 digits for 4-digit years) of the specified date according to local time. 296 | this.getFullYear = function getFullYear() { 297 | return zoneInfo.year + 1900; 298 | } 299 | // Returns the hour (0-23) in the specified date according to local time. 300 | this.getHours = function getHours() { 301 | return zoneInfo.hours; 302 | } 303 | // Returns the minutes (0-59) in the specified date according to local time. 304 | this.getMinutes = function getMinutes() { 305 | return zoneInfo.minutes; 306 | } 307 | // Returns the month (0-11) in the specified date according to local time. 308 | this.getMonth = function getMonth() { 309 | return zoneInfo.month; 310 | } 311 | // Returns the seconds (0-59) in the specified date according to local time. 312 | this.getSeconds = function getSeconds() { 313 | return zoneInfo.seconds; 314 | } 315 | // Returns the timezone offset from GMT the Date instance currently is in, 316 | // in minutes. Also, left of GMT is positive, right of GMT is negative. 317 | this.getTimezoneOffset = function getTimezoneOffset() { 318 | return -zoneInfo.gmtOffset / 60; 319 | } 320 | // NON-STANDARD: Returns the abbreviation (e.g. EST, EDT) for the specified time zone. 321 | this.getTimezoneAbbr = function getTimezoneAbbr() { 322 | return tz.tzname[zoneInfo.isDaylightSavings ? 1 : 0]; 323 | } 324 | 325 | // Sets day, month and year at once 326 | this.setAllDateFields = function setAllDateFields(y,mo,d) { 327 | return this.setFullYear(y,mo,d); 328 | } 329 | // Sets the day of the month (from 1-31) in the current timezone 330 | this.setDate = function setDate(d) { 331 | zoneInfo.dayOfMonth = d; 332 | return mktime.call(this); 333 | } 334 | // Sets the year (four digits) in the current timezone 335 | this.setFullYear = function setFullYear(y,mo,d) { 336 | zoneInfo.year = y - 1900; 337 | if(arguments.length > 1) 338 | zoneInfo.month = mo; 339 | if(arguments.length > 2) 340 | zoneInfo.dayOfMonth = d; 341 | return mktime.call(this); 342 | } 343 | // Sets the hour (from 0-23) in the current timezone 344 | this.setHours = function setHours(h,m,s,ms) { 345 | zoneInfo.hours = h; 346 | if(arguments.length > 1) 347 | zoneInfo.minutes = m; 348 | if(arguments.length > 2) 349 | zoneInfo.seconds = s; 350 | if(arguments.length > 3) { 351 | mktime.call(this); 352 | var diff = ms - this.getMilliseconds(); 353 | return this.setTime(this.getTime() + diff); 354 | } else 355 | return mktime.call(this); 356 | } 357 | // Sets the milliseconds (from 0-999) in the current timezone 358 | this.setMilliseconds = function setMilliseconds(ms) { 359 | var diff = ms - this.getMilliseconds(); 360 | return this.setTime(this.getTime() + diff); 361 | } 362 | // Set the minutes (from 0-59) in the current timezone 363 | this.setMinutes = function setMinutes(m,s,ms) { 364 | zoneInfo.minutes = m; 365 | if(arguments.length > 1) 366 | zoneInfo.seconds = s; 367 | if(arguments.length > 2) { 368 | mktime.call(this); 369 | var diff = ms - this.getMilliseconds(); 370 | return this.setTime(this.getTime() + diff); 371 | } else 372 | return mktime.call(this); 373 | } 374 | // Sets the month (from 0-11) in the current timezone 375 | this.setMonth = function setMonth(mo,d) { 376 | zoneInfo.month = mo; 377 | if(arguments.length > 1) 378 | zoneInfo.dayOfMonth = d; 379 | return mktime.call(this); 380 | } 381 | // Sets the seconds (from 0-59) in the current timezone 382 | this.setSeconds = function setSeconds(s,ms) { 383 | zoneInfo.seconds = s; 384 | if(arguments.length > 1) { 385 | mktime.call(this); 386 | var diff = ms - this.getMilliseconds(); 387 | return this.setTime(this.getTime() + diff); 388 | } else 389 | return mktime.call(this); 390 | } 391 | // Sets a date and time by adding or subtracting a specified number of 392 | // milliseconds to/from midnight January 1, 1970. 393 | this.setTime = function setTime(v) { 394 | var rtn = _Date.prototype.setTime.call(this, v); 395 | // Since this function changes the internal UTC epoch date value, we need to 396 | // re-setup these timezone translation functions to reflect the new value 397 | reset.call(this); 398 | return rtn; 399 | } 400 | // Sets the day of the month, according to universal time (from 1-31) 401 | this.setUTCDate = function setUTCDate(d) { 402 | var rtn = _Date.prototype.setUTCDate.call(this, d); 403 | reset.call(this); 404 | return rtn; 405 | } 406 | // Sets the year, according to universal time (four digits) 407 | this.setUTCFullYear = function setUTCFullYear(y,mo,d) { 408 | var rtn; 409 | switch(arguments.length) { 410 | case 1: 411 | rtn = _Date.prototype.setUTCFullYear.call(this, y); break; 412 | case 2: 413 | rtn = _Date.prototype.setUTCFullYear.call(this, y,mo); break; 414 | case 3: 415 | rtn = _Date.prototype.setUTCFullYear.call(this, y,mo,d); break; 416 | } 417 | reset.call(this); 418 | return rtn; 419 | } 420 | // Sets the hour, according to universal time (from 0-23) 421 | this.setUTCHours = function setUTCHours(h,m,s,ms) { 422 | var rtn; 423 | switch(arguments.length) { 424 | case 1: 425 | rtn = _Date.prototype.setUTCHours.call(this, h); break; 426 | case 2: 427 | rtn = _Date.prototype.setUTCHours.call(this, h,m); break; 428 | case 3: 429 | rtn = _Date.prototype.setUTCHours.call(this, h,m,s); break; 430 | case 4: 431 | rtn = _Date.prototype.setUTCHours.call(this, h,m,s,ms); break; 432 | } 433 | reset.call(this); 434 | return rtn; 435 | } 436 | // Sets the milliseconds, according to universal time (from 0-999) 437 | this.setUTCMilliseconds = function setUTCMillseconds(ms) { 438 | var rtn = _Date.prototype.setUTCMilliseconds.call(this, ms); 439 | reset.call(this); 440 | return rtn; 441 | } 442 | // Set the minutes, according to universal time (from 0-59) 443 | this.setUTCMinutes = function setUTCMinutes(m,s,ms) { 444 | var rtn; 445 | switch(arguments.length) { 446 | case 1: 447 | rtn = _Date.prototype.setUTCMinutes.call(this, m); break; 448 | case 2: 449 | rtn = _Date.prototype.setUTCMinutes.call(this, m,s); break; 450 | case 3: 451 | rtn = _Date.prototype.setUTCMinutes.call(this, m,s,ms); break; 452 | } 453 | reset.call(this); 454 | return rtn; 455 | } 456 | // Sets the month, according to universal time (from 0-11) 457 | this.setUTCMonth = function setUTCMonth(mo,d) { 458 | var rtn; 459 | switch(arguments.length) { 460 | case 1: 461 | rtn = _Date.prototype.setUTCMonth.call(this, mo); break; 462 | case 2: 463 | rtn = _Date.prototype.setUTCMonth.call(this, mo,d); break; 464 | } 465 | reset.call(this); 466 | return rtn; 467 | } 468 | // Set the seconds, according to universal time (from 0-59) 469 | this.setUTCSeconds = function setUTCSeconds(s,ms) { 470 | var rtn; 471 | switch(arguments.length) { 472 | case 1: 473 | rtn = _Date.prototype.setUTCSeconds.call(this, s); break; 474 | case 2: 475 | rtn = _Date.prototype.setUTCSeconds.call(this, s,ms); break; 476 | } 477 | reset.call(this); 478 | return rtn; 479 | } 480 | 481 | this.toDateString = function toDateString() { 482 | return DAYS_OF_WEEK[this.getDay()].substring(0, 3) + ' ' + MONTHS[this.getMonth()].substring(0, 3) + ' ' + pad(this.getDate(), 2) + ' ' + this.getFullYear(); 483 | } 484 | 485 | this.toTimeString = function toTimeString() { 486 | var offset = Math.abs(zoneInfo.gmtOffset / 60); // total minutes 487 | // split into HHMM: 488 | var hours = pad(Math.floor(offset / 60), 2); 489 | var minutes = pad(offset % 60, 2); 490 | return this.toLocaleTimeString() + ' GMT' + (zoneInfo.gmtOffset >= 0 ? '+' : '-') + hours + minutes 491 | + ' (' + tz.tzname[zoneInfo.isDaylightSavings ? 1 : 0] + ')'; 492 | } 493 | 494 | this.toString = function toString() { 495 | return this.toDateString() + ' ' + this.toTimeString(); 496 | } 497 | 498 | this.toLocaleDateString = function toLocaleDateString() { 499 | return DAYS_OF_WEEK[this.getDay()] + ', ' + MONTHS[this.getMonth()] + ' ' + pad(this.getDate(), 2) + ', ' + this.getFullYear(); 500 | } 501 | 502 | this.toLocaleTimeString = function toLocaleTimeString() { 503 | return pad(this.getHours(), 2) + ':' + pad(this.getMinutes(), 2) + ':' + pad(this.getSeconds(), 2); 504 | } 505 | 506 | this.toLocaleString = this.toString; 507 | 508 | if (relative) { 509 | this.setAllDateFields(y,mo,d) 510 | this.setHours(h) 511 | this.setMinutes(m) 512 | this.setSeconds(s) 513 | this.setMilliseconds(ms) 514 | ms = s = m = h = d = mo = y = null 515 | } 516 | 517 | 518 | // Used internally by the 'set*' functions above... 519 | function reset () { 520 | this.setTimezone(this.getTimezone()); 521 | } 522 | // 'mktime' calls 'reset' implicitly through 'setTime()' 523 | function mktime () { 524 | var oldTz = process.env.TZ; 525 | exports.tzset(this.getTimezone()); 526 | zoneInfo.isDaylightSavings = -1; // Auto-detect the timezone 527 | var t = exports.mktime(zoneInfo); 528 | if (oldTz) { 529 | exports.tzset(oldTz); 530 | oldTz = null; 531 | } 532 | return this.setTime( (t * MILLIS_PER_SECOND) + this.getMilliseconds() ); 533 | } 534 | 535 | return this; 536 | } 537 | 538 | // Returns a "String" of the last value set in "setTimezone". 539 | // TODO: Return something when 'setTimezone' hasn't been called yet. 540 | function getTimezone () { 541 | throw new Error('You must call "setTimezone(tz)" before "getTimezone()" may be called'); 542 | } 543 | 544 | // NON-STANDARD: Returns the abbreviated timezone name, also taking daylight 545 | // savings into consideration. Useful for the presentation layer of a Date 546 | // instance. 547 | function getTimezoneAbbr () { 548 | var str = this.toString().match(/\([A-Z]+\)/)[0]; 549 | return str.substring(1, str.length-1); 550 | } 551 | 552 | // Export the modified 'Date' instance. Users should either use this with the 553 | // 'new' operator, or extend an already existing Date instance with 'extend()'. 554 | // An optional, NON-STANDARD, "timezone" argument may be appended as the final 555 | // argument, in order to specify the initial timezone the Date instance should 556 | // be created with. 557 | function Date (year, month, day, hour, minute, second, millisecond, timezone) { 558 | if (!(this instanceof Date)) { 559 | return new Date(year, month, day, hour, minute, second, millisecond, timezone).toString(); 560 | } 561 | var argc = arguments.length 562 | , d; 563 | // So that we don't have to do the switch block below twice! 564 | while (argc > 0 && typeof arguments[argc-1] === 'undefined') { 565 | argc--; 566 | } 567 | // An optional 'timezone' argument may be passed as the final argument 568 | if (argc >= 2 && typeof arguments[argc - 1] === 'string') { 569 | timezone = arguments[argc - 1]; 570 | argc--; 571 | } 572 | // Ugly, but the native Date constructor depends on arguments.length in order 573 | // to create a Date instance in the intended fashion. 574 | switch (argc) { 575 | case 0: 576 | d = new _Date(); break; 577 | case 1: 578 | d = new _Date(year); break; 579 | case 2: 580 | d = new _Date(year, month); break; 581 | case 3: 582 | d = new _Date(year, month, day); break; 583 | case 4: 584 | d = new _Date(year, month, day, hour); break; 585 | case 5: 586 | d = new _Date(year, month, day, hour, minute); break; 587 | case 6: 588 | d = new _Date(year, month, day, hour, minute, second); break; 589 | case 7: 590 | d = new _Date(year, month, day, hour, minute, second, millisecond); break; 591 | } 592 | if (timezone) { 593 | // set time given timezone relative to the currently set local time 594 | // (changing the internal "time" milliseconds value unless ms specified) 595 | d.setTimezone(timezone, !(argc == 1 && typeof year === 'number')); 596 | } else { 597 | d.setTimezone(exports.currentTimezone); 598 | } 599 | return d; 600 | } 601 | Date.prototype = _Date.prototype; 602 | exports.Date = Date; 603 | 604 | 605 | // We also overwrite `Date.parse()`. It can accept an optional 'timezone' 606 | // second argument. 607 | function parse (dateStr, timezone) { 608 | return new Date(dateStr, timezone).getTime(); 609 | } 610 | exports.parse = parse; 611 | 612 | // 'now()', 'parse()', and 'UTC()' all need to be re-defined on Date as don't enum 613 | Object.defineProperty(Date, 'now', { value: _Date.now, writable: true, enumerable: false }); 614 | Object.defineProperty(Date, 'parse', { value: parse, writable: true, enumerable: false }); 615 | Object.defineProperty(Date, 'UTC', { value: _Date.UTC, writable: true, enumerable: false }); 616 | 617 | 618 | 619 | // Turns a "regular" Date instance into one of our "extended" Date instances. 620 | // The return value is negligible, as the original Date instance is modified. 621 | // DEPRECATED: Just extend the Date's prototype using the Date-extend function. 622 | exports.extend = function extend (date) { 623 | if (!date) return date; 624 | date.getTimezone = getTimezone; 625 | date.setTimezone = setTimezone; 626 | date.getTimezoneAbbr = getTimezoneAbbr; 627 | return date; 628 | } 629 | 630 | 631 | /** 632 | * Pads a number with 0s if required. 633 | */ 634 | 635 | function pad (num, padLen) { 636 | var padding = '0000'; 637 | num = String(num); 638 | return padding.substring(0, padLen - num.length) + num; 639 | } 640 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "time", 3 | "description": "\"time.h\" bindings for Node.js", 4 | "keywords": [ 5 | "date", 6 | "time", 7 | "time.h", 8 | "timezone", 9 | "setTimezone", 10 | "getTimezone" 11 | ], 12 | "version": "0.12.0", 13 | "repository": { 14 | "type": "git", 15 | "url": "git://github.com/TooTallNate/node-time.git" 16 | }, 17 | "author": "Nathan Rajlich (http://tootallnate.net)", 18 | "license": "MIT", 19 | "contributors": [ 20 | { 21 | "name": "Diogo Resende", 22 | "email": "dresende@thinkdigital.pt" 23 | } 24 | ], 25 | "main": "./index.js", 26 | "scripts": { 27 | "test": "mocha --reporter spec" 28 | }, 29 | "dependencies": { 30 | "bindings": "^1.2.0", 31 | "debug": "^2.2.0", 32 | "nan": "^2.2.0" 33 | }, 34 | "devDependencies": { 35 | "mocha": "*" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/time.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "nan.h" 6 | 7 | using namespace node; 8 | using namespace v8; 9 | 10 | class Time { 11 | public: 12 | static void Init(Handle target) { 13 | Nan::HandleScope scope; 14 | 15 | // time(3) 16 | Nan::SetMethod(target, "time", Time_); 17 | 18 | // tzset(3) 19 | Nan::SetMethod(target, "tzset", Tzset); 20 | 21 | // localtime 22 | Nan::SetMethod(target, "localtime", Localtime); 23 | 24 | // mktime 25 | Nan::SetMethod(target, "mktime", Mktime); 26 | } 27 | 28 | static NAN_METHOD(Time_) { 29 | Nan::EscapableHandleScope scope; 30 | info.GetReturnValue().Set(scope.Escape(Nan::New(time(NULL)))); 31 | } 32 | 33 | static NAN_METHOD(Tzset) { 34 | Nan::EscapableHandleScope scope; 35 | 36 | // Set up the timezone info from the current TZ environ variable 37 | tzset(); 38 | 39 | // Set up a return object that will hold the results of the timezone change 40 | Local obj = Nan::New(); 41 | 42 | // The 'tzname' char * [] gets put into a JS Array 43 | int tznameLength = 2; 44 | Local tznameArray = Nan::New( tznameLength ); 45 | #if _MSC_VER >= 1900 46 | char szTzName[128]; 47 | 48 | for (int i = 0; i < tznameLength; i++) { 49 | size_t strLength; 50 | _get_tzname(&strLength, szTzName, 128, i); 51 | Nan::Set(tznameArray, i, Nan::New(szTzName).ToLocalChecked()); 52 | } 53 | 54 | Nan::Set(obj, Nan::New("tzname").ToLocalChecked(), tznameArray); 55 | 56 | // The 'timezone' long is the "seconds West of UTC" 57 | long timezone; 58 | _get_timezone(&timezone); 59 | Nan::Set(obj, Nan::New("timezone").ToLocalChecked(), Nan::New(timezone)); 60 | 61 | // The 'daylight' int is obselete actually, but I'll include it here for 62 | // curiosity's sake. See the "Notes" section of "man tzset" 63 | int daylight; 64 | _get_daylight(&daylight); 65 | Nan::Set(obj, Nan::New("daylight").ToLocalChecked(), Nan::New(daylight)); 66 | #else 67 | for (int i=0; i < tznameLength; i++) { 68 | Nan::Set(tznameArray, i, Nan::New(tzname[i]).ToLocalChecked()); 69 | } 70 | 71 | Nan::Set(obj, Nan::New("tzname").ToLocalChecked(), tznameArray); 72 | 73 | // The 'timezone' long is the "seconds West of UTC" 74 | Nan::Set(obj, Nan::New("timezone").ToLocalChecked(), Nan::New( timezone )); 75 | 76 | // The 'daylight' int is obselete actually, but I'll include it here for 77 | // curiosity's sake. See the "Notes" section of "man tzset" 78 | Nan::Set(obj, Nan::New("daylight").ToLocalChecked(), Nan::New( daylight )); 79 | #endif 80 | info.GetReturnValue().Set(scope.Escape(obj)); 81 | } 82 | 83 | static NAN_METHOD(Localtime) { 84 | Nan::EscapableHandleScope scope; 85 | 86 | // Construct the 'tm' struct 87 | time_t rawtime = static_cast(info[0]->IntegerValue()); 88 | struct tm *timeinfo = localtime( &rawtime ); 89 | 90 | // Create the return "Object" 91 | Local obj = Nan::New(); 92 | 93 | if (timeinfo) { 94 | Nan::Set(obj, Nan::New("seconds").ToLocalChecked(), Nan::New(timeinfo->tm_sec) ); 95 | Nan::Set(obj, Nan::New("minutes").ToLocalChecked(), Nan::New(timeinfo->tm_min) ); 96 | Nan::Set(obj, Nan::New("hours").ToLocalChecked(), Nan::New(timeinfo->tm_hour) ); 97 | Nan::Set(obj, Nan::New("dayOfMonth").ToLocalChecked(), Nan::New(timeinfo->tm_mday) ); 98 | Nan::Set(obj, Nan::New("month").ToLocalChecked(), Nan::New(timeinfo->tm_mon) ); 99 | Nan::Set(obj, Nan::New("year").ToLocalChecked(), Nan::New(timeinfo->tm_year) ); 100 | Nan::Set(obj, Nan::New("dayOfWeek").ToLocalChecked(), Nan::New(timeinfo->tm_wday) ); 101 | Nan::Set(obj, Nan::New("dayOfYear").ToLocalChecked(), Nan::New(timeinfo->tm_yday) ); 102 | Nan::Set(obj, Nan::New("isDaylightSavings").ToLocalChecked(), Nan::New(timeinfo->tm_isdst > 0) ); 103 | 104 | #if defined HAVE_TM_GMTOFF 105 | // Only available with glibc's "tm" struct. Most Linuxes, Mac OS X... 106 | Nan::Set(obj, Nan::New("gmtOffset").ToLocalChecked(), Nan::New(timeinfo->tm_gmtoff) ); 107 | Nan::Set(obj, Nan::New("timezone").ToLocalChecked(), Nan::New(timeinfo->tm_zone).ToLocalChecked() ); 108 | 109 | #elif defined HAVE_TIMEZONE 110 | // Compatibility for Cygwin, Solaris, probably others... 111 | long scd; 112 | if (timeinfo->tm_isdst > 0) { 113 | #ifdef HAVE_ALTZONE 114 | scd = -altzone; 115 | #else 116 | scd = -timezone + 3600; 117 | #endif // HAVE_ALTZONE 118 | } else { 119 | scd = -timezone; 120 | } 121 | Nan::Set(obj, Nan::New("gmtOffset").ToLocalChecked(), Nan::New(scd)); 122 | Nan::Set(obj, Nan::New("timezone").ToLocalChecked(), Nan::New(tzname[timeinfo->tm_isdst])); 123 | #endif // HAVE_TM_GMTOFF 124 | } else { 125 | Nan::Set(obj, Nan::New("invalid").ToLocalChecked(), Nan::New(true)); 126 | } 127 | 128 | info.GetReturnValue().Set(scope.Escape(obj)); 129 | } 130 | 131 | static NAN_METHOD(Mktime) { 132 | Nan::EscapableHandleScope scope; 133 | 134 | if (info.Length() < 1) { 135 | return Nan::ThrowTypeError("localtime() Object expected"); 136 | } 137 | 138 | Local arg = info[0].As(); 139 | 140 | struct tm tmstr; 141 | tmstr.tm_sec = Nan::Get(arg, Nan::New("seconds").ToLocalChecked()).ToLocalChecked()->Int32Value(); 142 | tmstr.tm_min = Nan::Get(arg, Nan::New("minutes").ToLocalChecked()).ToLocalChecked()->Int32Value(); 143 | tmstr.tm_hour = Nan::Get(arg, Nan::New("hours").ToLocalChecked()).ToLocalChecked()->Int32Value(); 144 | tmstr.tm_mday = Nan::Get(arg, Nan::New("dayOfMonth").ToLocalChecked()).ToLocalChecked()->Int32Value(); 145 | tmstr.tm_mon = Nan::Get(arg, Nan::New("month").ToLocalChecked()).ToLocalChecked()->Int32Value(); 146 | tmstr.tm_year = Nan::Get(arg, Nan::New("year").ToLocalChecked()).ToLocalChecked()->Int32Value(); 147 | tmstr.tm_isdst = Nan::Get(arg, Nan::New("isDaylightSavings").ToLocalChecked()).ToLocalChecked()->Int32Value(); 148 | // tm_wday and tm_yday are ignored for input, but properly set after 'mktime' is called 149 | 150 | info.GetReturnValue().Set(scope.Escape(Nan::New(static_cast(mktime( &tmstr ))))); 151 | } 152 | 153 | }; 154 | 155 | extern "C" { 156 | static void init (Handle target) { 157 | Time::Init(target); 158 | } 159 | NODE_MODULE(time, init) 160 | } 161 | -------------------------------------------------------------------------------- /test/date.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var assert = require('assert') 7 | var time = require('../') 8 | 9 | describe('Date', function () { 10 | 11 | describe('constructor', function() { 12 | 13 | it('should parse strings relative to TZ', function() { 14 | 15 | var d = new time.Date('2012-1-12 02:00 PM', 'America/New_York') 16 | assert.equal(d.getTime(), 1326394800000) 17 | assert.equal(d.getTimezone(), 'America/New_York') 18 | 19 | d = new time.Date('2012-1-12 02:00 PM', 'America/Los_Angeles') 20 | assert.equal(d.getTime(), 1326405600000) 21 | assert.equal(d.getTimezone(), 'America/Los_Angeles') 22 | }) 23 | 24 | it('should interpret date parts relative to TZ', function() { 25 | 26 | var d = new time.Date(2012, 0, 12, 14, 'America/New_York') 27 | assert.equal(d.getTime(), 1326394800000) 28 | assert.equal(d.getFullYear(), 2012) 29 | assert.equal(d.getTimezone(), 'America/New_York') 30 | 31 | d = new time.Date(2012, 0, 12, 14, 'America/Los_Angeles') 32 | assert.equal(d.getTime(), 1326405600000) 33 | assert.equal(d.getTimezone(), 'America/Los_Angeles') 34 | }) 35 | 36 | it('should accept milliseconds regardless of TZ', function() { 37 | 38 | var d1 = new time.Date(1352005200000, 'America/New_York') 39 | var d2 = new time.Date(1352005200000, 'America/Los_Angeles') 40 | 41 | assert.equal(d1.getTime(), d2.getTime()) 42 | assert.equal(d1.getTimezone(), 'America/New_York') 43 | assert.equal(d2.getTimezone(), 'America/Los_Angeles') 44 | }) 45 | 46 | 47 | it('should parse strings around 2038', function() { 48 | 49 | // Before threshold 50 | var d = new time.Date('2037-12-31 11:59:59 PM', 'UTC') 51 | assert.equal(d.getTime(), 2145916799000) 52 | 53 | // After threshold 54 | d = new time.Date('2038-01-01 00:00:00 AM', 'UTC') 55 | assert.equal(d.getTime(), 2145916800000) 56 | 57 | // Before threshold 58 | d = new time.Date('2038-1-19 03:14:06 AM', 'UTC') 59 | assert.equal(d.getTime(), 2147483646000) 60 | 61 | // After threshold 62 | d = new time.Date('2038-1-19 03:14:07 AM', 'UTC') 63 | assert.equal(d.getTime(), 2147483647000) 64 | }) 65 | }) 66 | 67 | it('should accept js1.3 extended set* arguments', function() { 68 | 69 | var d = new time.Date(2000, 1, 2, 3, 4, 5, 6, 'America/Chicago') 70 | d.setFullYear(2001, 2, 3) 71 | assert.equal(d.getTime(), 983610245006) 72 | d.setFullYear(2002, 3) 73 | assert.equal(d.getTime(), 1017824645006) 74 | d.setMonth(4, 5) 75 | assert.equal(d.getTime(), 1020585845006) 76 | d.setHours(4, 5, 6, 7) 77 | assert.equal(d.getTime(), 1020589506007) 78 | d.setHours(5, 6, 7) 79 | assert.equal(d.getTime(), 1020593167007) 80 | d.setHours(6, 7) 81 | assert.equal(d.getTime(), 1020596827007) 82 | d.setMinutes(8, 9, 10) 83 | assert.equal(d.getTime(), 1020596889010) 84 | d.setMinutes(9, 10) 85 | assert.equal(d.getTime(), 1020596950010) 86 | d.setSeconds(11, 12) 87 | assert.equal(d.getTime(), 1020596951012) 88 | }) 89 | 90 | it('should accept js1.3 extended setUTC* arguments', function() { 91 | 92 | var d = new time.Date(2000, 1, 2, 3, 4, 5, 6, 'America/Chicago') 93 | d.setUTCFullYear(2001, 2, 3) 94 | assert.equal(d.getTime(), 983610245006) 95 | d.setUTCFullYear(2002, 3) 96 | assert.equal(d.getTime(), 1017824645006) 97 | d.setUTCMonth(4, 5) 98 | assert.equal(d.getTime(), 1020589445006) 99 | d.setUTCHours(4, 5, 6, 7) 100 | assert.equal(d.getTime(), 1020571506007) 101 | d.setUTCHours(5, 6, 7) 102 | assert.equal(d.getTime(), 1020575167007) 103 | d.setUTCHours(6, 7) 104 | assert.equal(d.getTime(), 1020578827007) 105 | d.setUTCMinutes(8, 9, 10) 106 | assert.equal(d.getTime(), 1020578889010) 107 | d.setUTCMinutes(9, 10) 108 | assert.equal(d.getTime(), 1020578950010) 109 | d.setUTCSeconds(11, 12) 110 | assert.equal(d.getTime(), 1020578951012) 111 | }) 112 | 113 | describe('#setTimezone()', function () { 114 | 115 | beforeEach(function () { 116 | time.tzset('UTC') 117 | }) 118 | 119 | it('should clean up after itself', function () { 120 | var initial = process.env.TZ 121 | , d = new time.Date() 122 | d.setTimezone('America/Argentina/San_Juan') 123 | assert.equal(initial, process.env.TZ) 124 | }) 125 | 126 | it('should be chainable', function () { 127 | var initial = process.env.TZ 128 | , d = new time.Date().setTimezone('America/Argentina/San_Juan') 129 | assert.equal(d.getTimezone(), 'America/Argentina/San_Juan') 130 | }) 131 | 132 | it('should change the "timezone offset"', function () { 133 | var d = new time.Date() 134 | , offset = d.getTimezoneOffset() 135 | d.setTimezone('US/Pacific') 136 | assert.notEqual(d.getTimezoneOffset(), offset) 137 | }) 138 | 139 | it('should match the UTC values when set to "UTC"', function () { 140 | var d = new time.Date() 141 | d.setTimezone('UTC') 142 | assert.equal(d.getUTCDay(), d.getDay()) 143 | assert.equal(d.getUTCDate(), d.getDate()) 144 | assert.equal(d.getUTCFullYear(), d.getFullYear()) 145 | assert.equal(d.getUTCHours(), d.getHours()) 146 | assert.equal(d.getUTCMilliseconds(), d.getMilliseconds()) 147 | assert.equal(d.getUTCMinutes(), d.getMinutes()) 148 | assert.equal(d.getUTCMonth(), d.getMonth()) 149 | assert.equal(d.getUTCSeconds(), d.getSeconds()) 150 | assert.equal(d.getTimezoneOffset(), 0) 151 | }) 152 | 153 | it('should especially change the "hours" value', function () { 154 | var d = new time.Date() 155 | , hours = d.getHours() 156 | 157 | d.setTimezone('US/Pacific') 158 | assert.notEqual(d.getHours(), hours) 159 | hours = d.getHours() 160 | 161 | d.setTimezone('US/Eastern') 162 | assert.notEqual(d.getHours(), hours) 163 | hours = d.getHours() 164 | 165 | d.setTimezone('America/Argentina/San_Juan') 166 | assert.notEqual(d.getHours(), hours) 167 | }) 168 | 169 | 170 | describe('relative', function () { 171 | 172 | it('should change the timezone', function () { 173 | var d = new time.Date() 174 | d.setTimezone('US/Pacific', true) 175 | assert.notEqual(d.getTimezone(), process.env.TZ) 176 | }) 177 | 178 | it('should keep local values', function () { 179 | var d = new time.Date() 180 | , millis = d.getMilliseconds() 181 | , seconds = d.getSeconds() 182 | , minutes = d.getMinutes() 183 | , hours = d.getHours() 184 | , date = d.getDate() 185 | , month = d.getMonth() 186 | , year = d.getFullYear() 187 | d.setTimezone('US/Pacific', true) 188 | 189 | assert.equal( d.getMilliseconds(), millis) 190 | assert.equal(d.getSeconds(), seconds) 191 | assert.equal(d.getMinutes(), minutes) 192 | assert.equal(d.getHours(), hours) 193 | assert.equal(d.getDate(), date) 194 | assert.equal(d.getMonth(), month) 195 | assert.equal(d.getFullYear(), year) 196 | }) 197 | 198 | it('should change the date\'s internal time value', function () { 199 | var d = new time.Date() 200 | , old = d.getTime() 201 | d.setTimezone('US/Pacific', true) 202 | assert.notEqual(d.getTime(), old) 203 | }) 204 | 205 | it('should calculate correctly when UTC date is day after timezone date', function () { 206 | var forwards = { 207 | timezone: 'US/Pacific', hour: 22, minute: 47, 208 | year: 2013, month: 1, date: 31 209 | } 210 | var d = new time.Date( 211 | forwards.year, forwards.month - 1, forwards.date, 212 | forwards.hour, forwards.minute, 1, 1, forwards.timezone 213 | ) 214 | assert.equal(d.toString(), 'Thu Jan 31 2013 22:47:01 GMT-0800 (PST)') 215 | d.setTimezone('UTC') 216 | assert.equal(d.toString(), 'Fri Feb 01 2013 06:47:01 GMT+0000 (UTC)') 217 | }) 218 | 219 | it('should calculate correctly when UTC date is day before timezone date', function () { 220 | var d = new time.Date(2010, 0, 31, 19, 0, 0, 0, 'UTC') 221 | assert.equal(d.toString(), 'Sun Jan 31 2010 19:00:00 GMT+0000 (UTC)') 222 | 223 | var backwards = { 224 | timezone: 'Australia/Sydney', hour: 2, minute: 47, 225 | year: 2013, month: 2, date: 1 226 | } 227 | 228 | var d2 = new time.Date( 229 | backwards.year, backwards.month - 1, backwards.date, 230 | backwards.hour, backwards.minute, 1, 1, backwards.timezone 231 | ) 232 | assert.equal(d2.toString(), 'Fri Feb 01 2013 02:47:01 GMT+1100 (AEDT)') 233 | d2.setTimezone('UTC') 234 | assert.equal(d2.toString(), 'Thu Jan 31 2013 15:47:01 GMT+0000 (UTC)') 235 | }) 236 | 237 | it('should calculate correctly on edge of months', function() { 238 | var d = new time.Date("2013-02-01 01:02:03", 'US/Pacific') 239 | assert.equal(d.toString(), 'Fri Feb 01 2013 01:02:03 GMT-0800 (PST)') 240 | d.setTimezone('UTC') 241 | assert.equal(d.toString(), 'Fri Feb 01 2013 09:02:03 GMT+0000 (UTC)') 242 | }) 243 | 244 | it('should produce the right time string for half-hour time offsets', function() { 245 | var d = new time.Date("2014-01-17T13:14:15-0330") 246 | d.setTimezone('America/St_Johns') 247 | assert.equal(d.toString(), 'Fri Jan 17 2014 13:14:15 GMT-0330 (NST)') 248 | d.setTimezone('UTC') 249 | assert.equal(d.toString(), 'Fri Jan 17 2014 16:44:15 GMT+0000 (UTC)') 250 | }) 251 | 252 | }) 253 | 254 | }) 255 | }) 256 | -------------------------------------------------------------------------------- /test/exports.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module depedencies. 4 | */ 5 | 6 | var assert = require('assert') 7 | var time = require('../') 8 | 9 | describe('exports', function () { 10 | 11 | it('should be a function', function () { 12 | assert.equal(typeof time, 'function') 13 | }) 14 | 15 | it('should return itself when invoked', function () { 16 | var dummy = function () {} 17 | assert.equal(time(dummy), time) 18 | }) 19 | 20 | it('should add node-time extensions to the passed in function\'s prototype', 21 | function () { 22 | var dummy = function () {} 23 | var proto = dummy.prototype 24 | 25 | assert.notEqual('function', typeof proto.setTimezone); 26 | assert.notEqual('function', typeof proto.getTimezone); 27 | assert.notEqual('function', typeof proto.getTimezoneAbbr); 28 | time(dummy) 29 | assert.equal('function', typeof proto.setTimezone); 30 | assert.equal('function', typeof proto.getTimezone); 31 | assert.equal('function', typeof proto.getTimezoneAbbr); 32 | }) 33 | 34 | it('should throw if in invalid object is passed into it', function () { 35 | assert.throws(time) 36 | }) 37 | 38 | it('should have a "currentTimezone" property', function () { 39 | assert.equal('string', typeof time.currentTimezone); 40 | assert(time.currentTimezone) 41 | }) 42 | 43 | describe('localtime()', function () { 44 | 45 | // GH-40 46 | it('should not segfault on a NaN Date value', function () { 47 | var invalid = new Date(NaN) 48 | var local = time.localtime(invalid.getTime()) 49 | assert.deepEqual({ invalid: true }, local) 50 | }) 51 | 52 | }) 53 | 54 | describe('Date', function () { 55 | 56 | it('should have a "Date" property', function () { 57 | assert.equal('function', typeof time.Date); 58 | }) 59 | 60 | it('should *not* be the global "Date" object', function () { 61 | assert.notStrictEqual(time.Date, Date); 62 | }) 63 | 64 | it('should return a real "Date" instance', function () { 65 | var d = new time.Date() 66 | assert.equal(Object.prototype.toString.call(d), '[object Date]') 67 | }) 68 | 69 | it('should pass `time.Date` instanceof', function () { 70 | var d = new time.Date() 71 | assert(d instanceof time.Date) 72 | }) 73 | 74 | it('should not pass global instanceof', function () { 75 | var d = new time.Date() 76 | assert.equal(d instanceof Date, false) 77 | }) 78 | 79 | it('should already have the node-time extensions', function () { 80 | assert.equal('function', typeof time.Date.prototype.setTimezone) 81 | assert.equal('function', typeof time.Date.prototype.getTimezone) 82 | assert.equal('function', typeof time.Date.prototype.getTimezoneAbbr) 83 | }) 84 | 85 | it('should have all the regular Date properties', function () { 86 | assert.equal('function', typeof time.Date.now) 87 | assert.equal('function', typeof time.Date.parse) 88 | assert.equal('function', typeof time.Date.UTC) 89 | }) 90 | 91 | }) 92 | 93 | }) 94 | -------------------------------------------------------------------------------- /test/tzset.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var assert = require('assert') 7 | var time = require('../') 8 | 9 | describe('tzset()', function () { 10 | 11 | beforeEach(function () { 12 | process.env.TZ = 'UTC' 13 | }) 14 | 15 | it('should work with no arguments', function () { 16 | process.env.TZ = 'US/Pacific' 17 | time.tzset() 18 | assert.equal(time.currentTimezone, 'US/Pacific') 19 | }) 20 | 21 | it('should work with 1 argument', function () { 22 | time.tzset('US/Pacific') 23 | assert.equal(time.currentTimezone, 'US/Pacific') 24 | }) 25 | 26 | it('should return a "zoneinfo" object', function () { 27 | var info = time.tzset() 28 | assert('tzname' in info) 29 | assert(info.tzname.length, 2) 30 | assert('timezone' in info) 31 | assert('daylight' in info) 32 | }) 33 | 34 | it('should set `process.env.TZ`', function () { 35 | time.tzset('US/Pacific') 36 | assert.equal(process.env.TZ, 'US/Pacific') 37 | }) 38 | 39 | it('should work with known values', function () { 40 | var info 41 | 42 | info = time.tzset('UTC') 43 | assert.equal(info.tzname[0], 'UTC') 44 | assert.equal(info.timezone, 0) 45 | assert.equal(info.daylight, 0) 46 | 47 | info = time.tzset('America/Los_Angeles') 48 | assert.equal(info.tzname[0], 'PST') 49 | assert.equal(info.tzname[1], 'PDT') 50 | assert.notEqual(info.timezone, 0) 51 | 52 | info = time.tzset('America/Phoenix') 53 | assert.equal(info.tzname[0], 'MST') 54 | assert.equal(info.tzname[1], 'MDT') 55 | assert.notEqual(info.timezone, 0) 56 | 57 | info = time.tzset('Europe/Copenhagen') 58 | assert.equal(info.tzname[0], 'CET') 59 | assert.equal(info.tzname[1], 'CEST') 60 | assert.notEqual(info.timezone, 0) 61 | }) 62 | 63 | }) 64 | --------------------------------------------------------------------------------