├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── README.md ├── japaFile.js ├── package.json ├── providers └── TimezoneProvider.js ├── src ├── Middleware.js ├── Timezone.js └── Trait.js └── test ├── functional └── timezone-provider.spec.js └── unit ├── timezone.spec.js └── trait.spec.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | es6: true, 4 | node: true 5 | }, 6 | extends: [ 7 | 'standard' 8 | ], 9 | globals: { 10 | Atomics: 'readonly', 11 | SharedArrayBuffer: 'readonly' 12 | }, 13 | parserOptions: { 14 | ecmaVersion: 2018 15 | }, 16 | rules: { 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/node 2 | 3 | ### Node ### 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # Coveralls configuration file 24 | .coveralls.yml 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (http://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Typescript v1 declaration files 46 | typings/ 47 | 48 | # Optional npm cache directory 49 | .npm 50 | 51 | # Optional eslint cache 52 | .eslintcache 53 | 54 | # Optional REPL history 55 | .node_repl_history 56 | 57 | # Output of 'npm pack' 58 | *.tgz 59 | 60 | # Yarn Integrity file 61 | .yarn-integrity 62 | 63 | # Yarn lock file 64 | yarn.lock 65 | 66 | # Package lock file 67 | package-lock.json 68 | 69 | # dotenv environment variables file 70 | .env 71 | 72 | # tmp dirs 73 | test/tmp 74 | test/database 75 | 76 | 77 | # End of https://www.gitignore.io/api/node -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - node 4 | - 8.0.0 5 | sudo: false 6 | install: 7 | - yarn 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Adonis Timezone Provider 2 | 3 | This library provides an easy way to start using timezones with AdonisJS. 4 | 5 | [![Coverage Status](https://coveralls.io/repos/github/Rocketseat/adonis-timezone/badge.svg?branch=master)](https://coveralls.io/github/Rocketseat/adonis-timezone?branch=master) 6 | 7 | ## Install 8 | 9 | `adonis install @rocketseat/adonis-timezone` 10 | 11 | ### Include adonis-timezone provider into providers list 12 | 13 | Insert provider reference: `@rocketseat/adonis-timezone/providers/TimezoneProvider` into providers list. 14 | 15 | `file path: start/app.js` 16 | 17 | 18 | ```js 19 | const providers = [ 20 | ... 21 | '@rocketseat/adonis-timezone/providers/TimezoneProvider', 22 | 23 | ]; 24 | ``` 25 | 26 | 27 | ## Use 28 | 29 | To use this provider you need to add a `trait` inside your `Model`, like this: 30 | 31 | ```js 32 | class User extends Model { 33 | static boot() { 34 | super.boot(); 35 | 36 | this.addTrait("@provider:Timezone/Trait"); 37 | } 38 | } 39 | ``` 40 | 41 | ``` 42 | OBS: Unfortunately cannot overwrite castDates 43 | ``` 44 | 45 | And you need to create a middleware and inside it, use the `timezone` context variable. 46 | 47 | On the timezone variable, you can choose the timezone, that your model will provide the timestamps and custom time & date. 48 | 49 | ```js 50 | class Timezone { 51 | async handle({ timezone }, next) { 52 | timezone.activate("America/Sao_Paulo"); 53 | await next(); 54 | } 55 | } 56 | ``` 57 | 58 | If you'd like to add user's timezone, then you should add a named middleware to the `timezone` and the user must have a timezone field in their Model. 59 | 60 | ```js 61 | class Timezone { 62 | async handle({ timezone, auth }, next) { 63 | timezone.activate(auth.user.timezone); 64 | await next(); 65 | } 66 | } 67 | ``` 68 | -------------------------------------------------------------------------------- /japaFile.js: -------------------------------------------------------------------------------- 1 | const { configure } = require('japa') 2 | configure({ 3 | files: ['test/**/*.spec.js'] 4 | }) 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rocketseat/adonis-timezone", 3 | "version": "0.7.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "posttest": "npm run coverage", 8 | "test:local": "node japaFile.js", 9 | "test": "nyc npm run test:local", 10 | "coverage": "nyc report --reporter=text-lcov | coveralls" 11 | }, 12 | "nyc": { 13 | "exclude": [ 14 | "**/*.spec.js", 15 | "bin" 16 | ] 17 | }, 18 | "directories": { 19 | "test": "test" 20 | }, 21 | "devDependencies": { 22 | "@adonisjs/fold": "^4.0.9", 23 | "coveralls": "^3.0.6", 24 | "eslint": "^6.3.0", 25 | "eslint-config-standard": "^14.1.0", 26 | "eslint-plugin-import": "^2.18.2", 27 | "eslint-plugin-node": "^10.0.0", 28 | "eslint-plugin-promise": "^4.2.1", 29 | "eslint-plugin-standard": "^4.0.1", 30 | "japa": "^3.0.1", 31 | "moment": "^2.24.0", 32 | "nyc": "^14.1.1" 33 | }, 34 | "keywords": [ 35 | "timezone", 36 | "adonis", 37 | "adonisjs", 38 | "adonis-js", 39 | "adonis-timezone" 40 | ], 41 | "repository": { 42 | "type": "git", 43 | "url": "git+https://github.com/Rocketseat/adonis-timezone.git" 44 | }, 45 | "dependencies": { 46 | "moment-timezone": "^0.5.26" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /providers/TimezoneProvider.js: -------------------------------------------------------------------------------- 1 | const { ServiceProvider } = require('@adonisjs/fold') 2 | 3 | class TimezoneProvider extends ServiceProvider { 4 | _registerTimezone () { 5 | this.app.singleton('Rocketseat/Timezone', () => { 6 | const Timezone = require('../src/Timezone') 7 | const asyncHooks = require('async_hooks') 8 | 9 | return new Timezone(asyncHooks) 10 | }) 11 | 12 | this.app.alias('Rocketseat/Timezone', 'Timezone') 13 | } 14 | 15 | _registerMiddleware () { 16 | this.app.singleton('Rocketseat/Timezone/Middleware', () => { 17 | const Middleware = require('../src/Middleware') 18 | 19 | const Timezone = this.app.use('Rocketseat/Timezone') 20 | return new Middleware(Timezone) 21 | }) 22 | 23 | this.app.alias('Rocketseat/Timezone/Middleware', 'Timezone/Middleware') 24 | } 25 | 26 | _registerTrait () { 27 | this.app.bind('Rocketseat/Trait/Timezone', () => { 28 | const Timezone = this.app.use('Rocketseat/Timezone') 29 | const Trait = require('../src/Trait') 30 | 31 | return new Trait(Timezone) 32 | }) 33 | 34 | this.app.alias('Rocketseat/Trait/Timezone', 'Timezone/Trait') 35 | } 36 | 37 | register () { 38 | this._registerTimezone() 39 | this._registerMiddleware() 40 | this._registerTrait() 41 | } 42 | 43 | boot () { 44 | this.app.use('Server').registerGlobal(['Timezone/Middleware']) 45 | 46 | const Timezone = this.app.use('Rocketseat/Timezone') 47 | 48 | const Context = this.app.use('Adonis/Src/HttpContext') 49 | Context.getter('timezone', function () { 50 | return { 51 | activate (timezone) { 52 | Timezone.activate(timezone) 53 | }, 54 | timezone () { 55 | return Timezone.timezone() 56 | } 57 | } 58 | }) 59 | } 60 | } 61 | 62 | module.exports = TimezoneProvider 63 | -------------------------------------------------------------------------------- /src/Middleware.js: -------------------------------------------------------------------------------- 1 | class Middleware { 2 | constructor (timezone) { 3 | this.timezone = timezone 4 | } 5 | 6 | async handle (_, next) { 7 | await this.timezone.run(next) 8 | } 9 | } 10 | 11 | module.exports = Middleware 12 | -------------------------------------------------------------------------------- /src/Timezone.js: -------------------------------------------------------------------------------- 1 | class Timezone { 2 | constructor (asyncHooks) { 3 | this.current = {} 4 | asyncHooks.createHook({ 5 | init: () => { 6 | }, 7 | destroy: () => { 8 | } 9 | }).enable() 10 | } 11 | 12 | run (next) { 13 | return new Promise((resolve, reject) => { 14 | process.nextTick(() => { 15 | next().then(resolve, reject) 16 | }) 17 | }) 18 | } 19 | 20 | activate (timezone) { 21 | this.current.activate = timezone 22 | } 23 | 24 | timezone () { 25 | return this.current.activate 26 | } 27 | } 28 | 29 | module.exports = Timezone 30 | -------------------------------------------------------------------------------- /src/Trait.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment-timezone') 2 | const DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss' 3 | 4 | class Trait { 5 | constructor (Timezone) { 6 | this.Timezone = Timezone 7 | } 8 | 9 | register (Model) { 10 | const Timezone = this.Timezone 11 | function timezoneFormat (value, format = DATE_FORMAT) { 12 | const timeZone = Timezone.timezone() 13 | 14 | if (!timeZone) { 15 | return moment(value).format(format) 16 | } 17 | 18 | const datetime = moment.tz(value, format, timeZone).format(format) 19 | 20 | return datetime 21 | } 22 | 23 | Object.defineProperties(Model, { 24 | castDates: { 25 | value: function (key, value) { 26 | return timezoneFormat(value) 27 | } 28 | }, 29 | timezoneFormat: { 30 | value: timezoneFormat 31 | } 32 | }) 33 | } 34 | } 35 | 36 | module.exports = Trait 37 | -------------------------------------------------------------------------------- /test/functional/timezone-provider.spec.js: -------------------------------------------------------------------------------- 1 | 2 | const test = require('japa') 3 | const Ioc = require('@adonisjs/fold/src/Ioc') 4 | const Registrar = require('@adonisjs/fold/src/Registrar') 5 | const { join } = require('path') 6 | 7 | test.group('TimezoneProvider', (group) => { 8 | let ioc, registrar 9 | 10 | group.beforeEach(() => { 11 | ioc = new Ioc() 12 | registrar = new Registrar(ioc) 13 | registrar.providers([ 14 | join(__dirname, '..', '..', 'providers', 'TimezoneProvider') 15 | ]) 16 | 17 | registrar.register() 18 | }) 19 | 20 | test('should save timezone in context', async (assert) => { 21 | assert.plan(2) 22 | const middleware = ioc.use('Timezone/Middleware') 23 | 24 | await Promise.all([ 25 | middleware.handle({}, async () => { 26 | ioc.use('Timezone').activate('America/Sao_Paulo') 27 | await new Promise(resolve => setImmediate(resolve)) 28 | assert.equal(ioc.use('Timezone').timezone(), 'America/Sao_Paulo') 29 | }), 30 | middleware.handle({}, async () => { 31 | assert.equal(ioc.use('Timezone').timezone(), 'America/Sao_Paulo') 32 | }) 33 | ]) 34 | }) 35 | 36 | test('should get trait instance', async (assert) => { 37 | assert.plan(1) 38 | 39 | const Trait = ioc.use('Timezone/Trait') 40 | 41 | assert.instanceOf(Trait, require('../../src/Trait')) 42 | }) 43 | 44 | test('should register global middleware on boot', async (assert) => { 45 | assert.plan(1) 46 | 47 | ioc.fake('Server', () => { 48 | return { 49 | registerGlobal (namespaces) { 50 | assert.deepEqual(namespaces, ['Timezone/Middleware']) 51 | } 52 | } 53 | }) 54 | 55 | ioc.fake('Adonis/Src/HttpContext', () => { 56 | return { 57 | getter (name, callback) { 58 | 59 | } 60 | } 61 | }) 62 | 63 | await registrar.boot() 64 | }) 65 | 66 | test('should register timezone in context', async (assert) => { 67 | assert.plan(2) 68 | 69 | ioc.fake('Server', () => { 70 | return { 71 | registerGlobal (namespaces) { 72 | } 73 | } 74 | }) 75 | 76 | let caller 77 | 78 | ioc.fake('Adonis/Src/HttpContext', () => { 79 | return { 80 | getter (name, callback) { 81 | caller = callback 82 | } 83 | } 84 | }) 85 | 86 | await registrar.boot() 87 | 88 | caller = caller() 89 | 90 | caller.activate('America/Sao_Paulo') 91 | 92 | assert.equal(ioc.use('Timezone').timezone(), 'America/Sao_Paulo') 93 | 94 | ioc.use('Timezone').activate('Europe/Berlin') 95 | 96 | assert.equal(caller.timezone(), 'Europe/Berlin') 97 | }) 98 | }) 99 | -------------------------------------------------------------------------------- /test/unit/timezone.spec.js: -------------------------------------------------------------------------------- 1 | const test = require('japa') 2 | const Timezone = require('../../src/Timezone') 3 | 4 | test.group('Timezone', (group) => { 5 | let timezone 6 | 7 | group.beforeEach(() => { 8 | const asyncHooks = { createHook: () => ({ enable: () => {} }) } 9 | timezone = new Timezone(asyncHooks) 10 | }) 11 | 12 | test('should current context is empty', (assert) => { 13 | assert.deepEqual(timezone.current, {}) 14 | }) 15 | 16 | test('should change timezone value', (assert) => { 17 | timezone.activate('America/Sao_Paulo') 18 | 19 | assert.deepEqual(timezone.current, { 20 | activate: 'America/Sao_Paulo' 21 | }) 22 | }) 23 | 24 | test('should get timezone value', (assert) => { 25 | timezone.current = { activate: 'America/Sao_Paulo' } 26 | 27 | assert.equal(timezone.timezone(), 'America/Sao_Paulo') 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /test/unit/trait.spec.js: -------------------------------------------------------------------------------- 1 | const test = require('japa') 2 | const Trait = require('../../src/Trait') 3 | 4 | const moment = require('moment') 5 | 6 | test.group('Trait', (group) => { 7 | let trait 8 | 9 | group.beforeEach(() => { 10 | trait = new Trait({ 11 | timezone () { 12 | return 'Europe/Berlin' 13 | } 14 | }) 15 | }) 16 | 17 | test('should override model static methods', (assert) => { 18 | assert.plan(1) 19 | class Model { 20 | static castDates (field, value) { 21 | throw new Error() 22 | } 23 | } 24 | 25 | class User extends Model {} 26 | 27 | trait.register(User) 28 | 29 | const date = moment('2018-09-01 16:01:36') 30 | 31 | const result = User.castDates('test', date) 32 | 33 | assert.equal(result, '2018-09-01 21:01:36') 34 | }) 35 | 36 | test('should use a different format than specific', (assert) => { 37 | assert.plan(1) 38 | class Model { 39 | static castDates (field, value) { 40 | throw new Error() 41 | } 42 | } 43 | 44 | class User extends Model {} 45 | 46 | trait.register(User) 47 | 48 | const date = moment('2018-09-01 16:01:36') 49 | 50 | const result = User.timezoneFormat(date, 'YYYY-MM-DD') 51 | 52 | assert.equal(result, '2018-09-01') 53 | }) 54 | 55 | test('make sure that only the registered model has had its methods override', (assert) => { 56 | assert.plan(3) 57 | class Model { 58 | static findBy () { 59 | assert.isOk(true) 60 | } 61 | 62 | static castDates (field, value) { 63 | throw new Error() 64 | } 65 | } 66 | 67 | class User extends Model { 68 | static castDates (field, value) { 69 | // This will never be called 70 | throw new Error() 71 | } 72 | } 73 | 74 | class Token extends Model {} 75 | 76 | trait.register(User) 77 | 78 | User.findBy() 79 | 80 | const date = moment('2018-09-01 16:01:36') 81 | 82 | const result = User.castDates('test', date) 83 | 84 | assert.equal(result, '2018-09-01 21:01:36') 85 | 86 | try { 87 | Token.castDates() 88 | } catch (err) { 89 | assert.isOk(true) 90 | } 91 | }) 92 | 93 | test('should\'t anything happens when timezone isn\'t provided', (assert) => { 94 | assert.plan(1) 95 | class Model { 96 | static castDates (field, value) { 97 | throw new Error() 98 | } 99 | } 100 | 101 | class User extends Model { 102 | static castDates (field, value) { 103 | return super.castDates(field, value) 104 | } 105 | } 106 | 107 | trait.Timezone.timezone = function overrideTimezone () { 108 | return null 109 | } 110 | 111 | trait.register(User) 112 | 113 | const date = moment('2018-09-01 16:01:36') 114 | 115 | const result = User.castDates('test', date) 116 | 117 | assert.equal(result, '2018-09-01 16:01:36') 118 | }) 119 | }) 120 | --------------------------------------------------------------------------------