우리 말로는 컨텐츠 보안 정책. XSS 같은 웹 보안 취약점 공격을 탐지하고 완화(mitigate)하기 위해 사용되는 추가적인 보안 계층이다. CSP를 통해 웹 페이지에서 유효한 리소스(스크립트, CSS, 이미지, 폰트 등)의 정책을 명시하고, 브라우저는 이 정책에 따라 리소스를 허용하거나 차단하는 식으로 작동한다.
이 기술이 모질라 재단에 의해 소개된 것은 2004년이지만, CSP 1.0의 첫 공개 초안은 2012년이나 되어서야 발표되었다.
# 현재 도메인과 https://trusted.com에서 불러온 스크립트만 실행을 허용한다
Content-Security-Policy: script-src 'self' https://trusted.com;
# 현재 도메인에서 불러온 스크립트와 인라인으로 작성된 스크립트, CSS 실행을 허용한다
Content-Security-Policy: script-src 'self' 'unsafe-inline';
CSP는 HTTP 헤더 Content-Security-Policy
를 통해 구현되며, 보통 웹 서버에서 설정한다. 구 버전에선 X-Content-Security-Policy
라는 이름으로 쓰였다.
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; img-src https://*; child-src 'none';" />
다른 방법으로 <meta>
태그를 통해서 정책을 설정할 수 있지만, 서버 수준의 구현과 함께 고려되는 것이 권장된다.
Content-Security-Policy: directive <value>;
Content-Security-Policy: directive <value>; directive <value>; ...
Content-Security-Policy: directive <value> <value> ...;
Content-Security-Policy: directive <value> ...; directive <value> ...; ...
디렉티브는 출처를 제한할 리소스의 유형을 나타내는 항목이다. 한 번에 여러 디렉티브를 지정할 수 있고, 디렉티브 사이의 구분은 세미콜론;
으로 한다.
현재(2024-02-16) 디렉티브는 총 29개(…)가 있고, 주요 디렉티브는 다음과 같다.
fetch 디렉티브:
default-src
: 명시하지 않은 모든 리소스에 대한 출처child-src
: 웹 워커와 <frame>
, <iframe>
에 대한 출처connect-src
: XMLHttpRequest, WebSocket, Fetch 등의 출처font-src
: 폰트 파일의 출처frame-src
: <frame>
및 <iframe>
태그를 통해 불러올 수 있는 출처img-src
: 이미지 파일 출처manifest-src
: 매니페스트 파일의 출처media-src
: 오디오 및 비디오 파일을 로드할 수 있는 출처object-src
: <object>
, <embed>
, <applet>
태그를 통해 불러올 수 있는 리소스의 출처script-src
: 자바스크립트에 대한 검증된 출처style-src
: 스타일시트 출처worker-src
: Worker
, SharedWorker
, ServiceWorker
스크립트를 불러올 수 있는 출처…를 제한하거나 지정하는 디렉티브다.
문서 디렉티브:
base-uri
: <base>
태그에서 사용할 수 있는 URL을 제한한다.탐색 디렉티브:
form-action
: <form>
의 제출(submissions) 대상을 제한하는 지시어frame-ancestors
: 이 페이지를 포함할 수 있는 상위 항목(<frame>
, <iframe>
, <object>
, <embed>
)을 지정한다.보고 정책 디렉티브:
report-uri
: CSP 위반 보고를 전송할 URL을 지정한다. 이 항목은 deprecated 상태고, report-to
가 권장된다.report-to
: CSP 위반 보고를 전송할 그룹을 지정한다.기타 디렉티브:
upgrade-insecure-requests
: TODO리소스의 출처가 어디인지를 명시한다. 키워드가 미리 정해진 keyword values와 호스트 주소를 직접 작성하는 hosts values, 특수 규칙인 other values로 나뉜다.
하나의 디렉티브에 여러 value를 지정할 수 있고, value 사이의 구분은 공백으로 한다. 그리고 hosts values를 제외한 나머지는 작은따옴표로 감싸야 한다.
Keyword Values:
'none'
: 어떠한 소스도 로드할 수 없도록 한다.'self'
: 현재 출처(도메인)의 리소스만 허용한다.'strict-dynamic'
: TODO'report-sample'
: TODO'inline-speculation-rules'
: TODOUnsafe Keyword Values:
'unsafe-inline'
: 인라인 자바스크립트와 CSS를 허용한다.'unsafe-eval'
: eval()
, setTimeout()
, window.execScript()
같은 동적 코드 평가(evaluation) 사용을 허용한다. setTimeout()
은 함수 자체가 아니라, setTimeout('console.log(123)', 100);
처럼 첫 번째 인수가 문자열인 경우를 의미한다.'unsafe-hashes'
: TODO'wasm-unsafe-eval'
: TODOHosts Values:
*
로 범위를 지정할 수 있고, 스킴(=프로토콜), 포트번호, path(도메인 다음의 경로)를 특정할 수 있다. (예시: example.com
, *.example.com
, https://*.example.com:12/path/to/file.js
) 슬래시/
로 끝나는 path는 접두어처럼 작동하지만, 이 외의 path는 완전히 일치해야만 한다. 예를 들어 example.com/api/
는 example.com/api/users/new
와 일치한다. 그리고 example.com/file.js
는 https://example.com/file.js
와 일치하지만, https://example.com/file.js.old
와는 일치하지 않는다.:
으로 끝나야 한다. (예시: https:
, data:
, blob:
)Other Values:
'nonce-*'
: TODO'sha*-*'
: sha256, sha384, sha512 중에 하나. 하이픈-
다음에 이어지는 값은 스크립트나 스타일 코드에 대한 해시값이다. (예시: 'sha256-abc123'
) 특정 인라인 코드를 허용할 때 사용한다. eval()
은 해당 없음# eval(), setTimeout() 등의 동적 코드 평가와 인라인 스크립트, 인라인 CSS 그리고 data: 스킴에 대한 모든 요청을 허용하고,
# 리소스를 가져올 사이트를 현재 출처(=접속한 사이트)와 www.google-analytics.com, fonts.googleapis.com, fonts.gstatic.com, chart.apis.google.com만을 허용한다
Content-Security-Policy: default-src 'self' 'unsafe-eval' 'unsafe-inline' data: www.google-analytics.com fonts.googleapis.com fonts.gstatic.com;
표준 내장 객체 Set
에 대해서 정리한 글.
Set
은 중복을 허용하지 않는 집합을 나타내는 데이터 구조다. 형태는 배열과 비슷하지만(실제로 유사 배열로 취급된다) 중복 값이 자동으로 제거되는 특징을 이용해 집합 연산이나 필터링 등에서 사용한다.
수학의 집합을 표현하기 좋은 데이터 구조이며 집합 연산(교집합, 합집합, 차집합)에 사용하기도 수월하다.
var st = new Set();
st.add(1);
st.add(1);
st; // Set [ 1 ]
var st2 = new Set([1, 2, 2, 3]);
st2; // Set(3) [ 1, 2, 3 ]
Array.from(st); // Array(3) [ 1, 2, 3 ]
특이하게도 Set
은 값의 유일성과 순서를 위한 데이터 구조라서 그런지 getter 류의 메서드가 존재하지 않는다.
요소가 객체일 때는, 객체 내부의 값이 아니라 객체 자체의 참조값을 기준으로 중복을 제거한다.
예를 들어 다음과 같은 코드를 실행하면:
const set = new Set([[1], [1]]);
console.log([...set]);
결과는 [[1], [1]]
이 된다.
배열의 중복을 제거하려면 다른 방법을 사용해야 한다. 예를 들어 JSON.stringify
와 JSON.parse
함수를 사용하여 배열을 문자열로 변환한 후 중복을 제거하고 다시 배열로 변환하는 방법이 있다:
const arr = [[1], [1]];
const set = new Set(arr.map(JSON.stringify));
const result = [...set].map(JSON.parse);
result; // [[1]]
몇 개의 요소가 있는지를 반환하는 Set.prototype.size
하나 있다.
인스턴스에 새 요소를 (이미 동일한 값의 요소가 없는 경우에 한하여) 추가한다.
var mySet = new Set([1, 2, 3]);
mySet.add(1);
mySet.add(2);
mySet.add(3);
mySet.add(2); // 이 값은 무시됨
console.log(mySet); // Set(3) { 1, 2, 3 }
Set
인스턴스의 모든 요소를 제거한다.
var mySet = new Set([1, 2, 3]);
console.log(mySet.size); // 3
mySet.clear();
console.log(mySet.size); // 0
지정된 값이 인스턴스에 있는 경우 해당 값을 제거한다.
var mySet = new Set([1, 2, 3]);
mySet.delete(2);
console.log(mySet.size); // 2
특징 값이 인스턴스에 있는지를 boolean
타입으로 반환한다.
var mySet = new Set([1, 2, 3]);
console.log(mySet.has(3)); // true
Set 인스턴스의 항목() 메서드는 삽입 순서대로 이 집합의 각 요소에 대한 [값, 값] 배열을 포함하는 새로운 집합 반복자 개체를 반환합니다. Set 객체의 경우 Map 객체와 같은 키가 없습니다. 그러나 API를 Map 객체와 유사하게 유지하기 위해 각 항목은 여기에서 키와 값에 대해 동일한 값을 가지므로 [값, 값] 배열이 반환됩니다.
이 메서드는 Set
인스턴스에 추가된 순서 그대로의 [값, 값]
배열을 포함하는 이터레이터(iterator)를, 호출할 때마다 새로 만들어 반환한다.
키 자리에는 값을 대신 채운 특이한 형태인데, Map
과의 유사성을 유지하기 위함이라나 뭐라나…
var mySet = new Set([1, 2, 3]);
console.log(mySet.entries());
// Set Iterator { [ 1, 1 ], [ 2, 2 ], [ 3, 3 ] }
Set
인스턴스 요소들의 값만을 포함하는 이터레이터를 반환한다.
var mySet = new Set([1, 2, 3]);
console.log(mySet.values()); // Set Iterator { 1, 2, 3 }
이 메서드는 Set.prototype.values()
의 별칭이다.
set.forEach(callbackFn)
set.forEach(callbackFn, thisArg)
function callbackFn(value, key, set) {}
인스턴스의 요소가 추가된 순서대로 반복하며 제공된 함수를 호출한다. 다른 메서드들이 그런것 처럼, 키가 없기 때문에 key
에는 값이 할당된다.
var mySet = new Set([1, 2, 3]);
mySet.forEach((value, key, set) => {
console.log('value:', value);
console.log('key:', key);
console.log('set:', set);
});
// value: 1
// key: 1
// set: Set(3) { 1, 2, 3 }
// value: 2
// key: 2
// set: Set(3) { 1, 2, 3 }
// value: 3
// key: 3
// set: Set(3) { 1, 2, 3 }
TODO
TODO
TODO
TODO
TODO
TODO
TODO
끗.
]]>표준 내장 객체 Map
에 대한 정리 글.
Map
은 키-값(key-value) 쌍을 저장하는 데이터 구조다. 자바스크립트에서 Map의 키는 문자열 외에도 Symbol
이나 숫자같은 데이터 타입도 허용된다.
키는 유일성이 보장되며, 동일한(=중복되는) 키를 반복 사용하면 기존의 값을 덮어씌우게 된다. 그리고 Map
은 요소가 추가된 순서를 기억한다.
var m = new Map();
m.set(123, '456');
m.get(123); // "456"
m.size; // 1
m.delete(123); // true
m.size; // 0
var m2 = new Map();
m2.set('b', 1);
m2.set('c', 1);
m2.set('a', 1);
log(m2.keys()); // MapIterator { "b", "c", "a", }
Map에 저장된 데이터의 수를 반환하는 size
하나가 끝.
Map.groupBy(items, callbackFn)
function callbackFn(element, index) {}
Map
을 이용한 그룹화 함수. 배열과 사용자 정의 함수를 인자로 받으며, 배열의 구성요소를 사용자 정의 함수에서 반환한 값으로 묶어 Map
타입으로 반환한다:
var inventory = [
{ name: 'asparagus', quantity: 9 },
{ name: 'bananas', quantity: 5 },
{ name: 'goat', quantity: 23 },
{ name: 'cherries', quantity: 12 },
{ name: 'fish', quantity: 22 },
];
var result = Map.groupBy(inventory, element => {
return element.quantity > 9 ? 'greaterThan' : 'lesserThanEqual'
});
console.log(result instanceof Map); // true;
console.log(result.get('greaterThan'));
// [{ name: "goat", quantity: 23 }, { name: "cherries", quantity: 12 }, { name: "fish", quantity: 22 }]
console.log(result.get('lesserThanEqual'));
// [{ name: "asparagus", quantity: 9 }, { name: "bananas", quantity: 5 }]
최신 기술이라 아직 지원하지 않는 브라우저가 있으니 주의할 것.
인스턴스의 모든 요소를 제거한다.
var m = new Map();
m.set('a', 1);
m.set('b', 2);
console.log(m.size); // 2
m.clear();
console.log(m.size); // 0
인스턴스에서 특정 요소를 제거한다. 첫 번째 인자로 키 이름을 받는다.
var m = new Map();
m.set('a', 1);
m.set('b', 2);
m.delete('b');
console.log(m); // Map(1) { "a": 1, }
평범한 getter니까 생략
setter도 생략
특정 키의 요소가 있는지를 boolean
타입으로 반환한다.
var map = new Map();
console.log(map.has('foo')); // false
Map
인스턴스에 추가된 순서 그대로의 키-값 쌍을 갖는 이터레이터(iterator)를, 호출할 때마다 새로 만들어 반환한다.
var m = new Map();
m.set('a', 1);
m.set('b', 2);
console.log(m.entries()); // Map Iterator { [ "a", 1 ], [ "b", 2 ] }
entries()
와 비슷한데 이 메서드가 반환하는 이터레이터는 키만 갖는다.
console.log(m.keys()); // Map Iterator { "a", "b" }
이 메서드는 이터레이터가 키 대신 값만 갖는다.
console.log(m.values()); // Map Iterator { 1, 2 }
map.forEach(callbackFn)
map.forEach(callbackFn, thisArg)
function callbackFn(value, key, map)
인스턴스의 키-값 쌍이 추가된 순서대로 반복하며 사용자 정의 함수를 실행한다.
var m = new Map();
m.set('a', 1);
m.set('b', 2);
m.forEach((value, key, map) => {
console.log('value:', value);
console.log('key:', key);
console.log('map:', map);
});
// value: 1
// key: a
// map: Map(2) { "a": 1, "b": 2, }
// value: 2
// key: b
// map: Map(2) { "a": 1, "b": 2, }
같은 이름인 Array.prototype.forEach()
와 비교하면 콜백 함수 파라미터에서 차이가 있다(Array
는 (element, index, object)
).
끟.
]]>서브라임 머지 기본 설정값과 팁, 단축키에 대한 기록.
TODO
TODO
# 73333a498cc22070f090993b5287b90cb5b5cb22 커밋 기준, edupass-ddl.sql 파일 719-801 라인의 변경점 보기
file:"database/ddl-2/edupass-ddl.sql" line:719-801 from:73333a498cc22070f090993b5287b90cb5b5cb22
TODO line
옵션에 대한 연구 필요
현재(2022-05-04) 공식 문서에서 command 목록을 찾을 수가 없다. 그래서 누군가 답답해서 만들어버린 걸 패키지로 설치해서 확인해야 함.
[
{ "keys": ["f1"], "command": "show_command_palette" },
{ "keys": ["ctrl+shift+d"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Delete Line.sublime-macro"} },
{ "keys": ["ctrl+shift+k"], "command": "duplicate_line" },
{ "keys": ["ctrl+p"], "command": "quick_switch_repository" },
{ "keys": ["ctrl+alt+shift+a"], "command": "stage_all" },
{ "keys": ["ctrl+alt+shift+u"], "command": "unstage_all" },
{ "keys": ["ctrl+alt+shift+d"], "command": "discard_all_modified" },
{ "keys": ["ctrl+,"], "command": "open_preferences" },
{ "keys": ["ctrl+shift+t"], "command": "show_command_palette", "args": {"command": "create_tag"} },
{ "keys": ["ctrl+shift+c"], "command": "show_command_palette", "args": {"command": "commit"} }
// {
// "keys": ["ctrl+alt+shift+enter"],
// "command": "commit",
// "args": { "mode": "commit --amend" },
// "context": [
// { "key": "setting.commit_message" },
// { "key": "can_commit" }
// ]
// }
]
stage_all
은 untracked 파일도 같이 스테이징하는 명령이다.discard_all_modified
은 모든 변경 사항을 취소하니 주의해서 사용할 것.Microsoft에서 만든 TypeScript(이하 타입스크립트)의 기본적인 내용을 정리한 글.
타입스크립트는 이름처럼 정적 데이터 타입이 추가된 언어로, Node.js 환경에서 작동한다. 자바스크립트의 슈퍼셋(superset)이라 하기도 한다.
타입스크립트로 작성된 소스는 브라우저나 자바스크립트 엔진이 읽을 수 없기 때문에 컴파일을 통해 자바스크립트 코드로 변환된다. 이 때 정적 타입 검사기(static type checker)가 작동하며 문제를 발견하면 컴파일 에러를 발생시킨다.
타입스크립트의 주요 장점은 스크립트를 실행하기 전에 미리 문제를 발견할 수 있다는 점이다. 그러나 이 장점은 자바스크립트의 유연성을 제물로 바친 대가이며, 엄격한 타입 체크가 때로는 생산성을 떨어트리는 단점이 되기도 한다.
npm install -g typescript
런타임이 Bun인 경우 따로 설치하지 않아도 됨.
# 버전 확인
tsc -v
tsc --version
# 도움말 보기
tsc -h
tsc --help
CLI 명령으로 타입스크립트 프로젝트의 기본 구조를 생성하는 방법이다:
tsc --init
…라고 했지만 사실 현재 경로에 tsconfig.json
을 만들어주는 게 끝.
TypeScript Documentation | tsconfig
타입스크립트 컴파일러가 프로젝트를 어떻게 컴파일 할지에 대한 설정을 정의한다. 저 아래 '빌드하기' 항목에서처럼 빌드할 항목 등을 직접 지정하는 방법도 있지만 그건 귀찮으니께…
target
: 컴파일될 자바스크립트 코드의 ECMAScript 버전을 지정한다. es3
, es5
, es6/es2015
, es2016
, es2017
, es2018
, es2019
, es2020
, es2021
, es2022
, esnext
중에 하나이며 대소문자는 상관 없는 모양이다.module
: 모듈 시스템을 지정한다. none
, commonjs
, amd
, umd
, system
, es6/es2015
, es2020
, es2022
, esnext
, node16
, nodenext
이것도 대소문자는 가리지 않는 걸로 보인다.outDir
: 컴파일된 파일들이 저장된 디렉터리 경로rootDir
: TODOnoImplicitAny
: (타입을 명시하지 않아 발생하는) 암묵적인 any
타입을 허용하지 않는다.strictNullChecks
: 이 설정이 true
면 null
과 undefined
를 다른 타입의 값으로 할당할 수 없다.files
: TODOinclude
: TODOexclude
: TODOtsc --init
은 아래처럼 만들어줌:
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
tsc
tsc -b
tsc --build
만약 TypeScript 패키지를 전역으로 설치하지 않았고, tsconfig.json
이 없다면?
# NPM: tsc 앞 뒤의 --는 outdir 옵션 처리를 위한 것
npm exec -- tsc -- .\src\test\tsc-me.ts --outdir ./dist
# Yarn
yarn run tsc .\src\test\tsc-me.ts --outdir ./dist
파일의 변화를 감지해 자동으로 빌드한다:
tsc -b -w
tsc --build --watch
string
number
boolean
symbol
bigint
[]
를 붙이는 방식으로 표기한다.
number[]
string[]
any
: 어떠한 것도 할당 가능한 타입이다. 이 타입은 타입 검사를 비활성한다.let foo: number = 123;
위 코드는 foo
가 number
타입임을 선언한다. 사실 타입스크립트는 변수에 할당된 값을 통해 타입을 자동으로 추론하기 때문에 변수에 대한 타입 표기는 선택 사항이다. (가독성을 위해 표기한다는 프로젝트 규칙이 있는 게 아니라면)
당연하지만, 선언한 타입과 다른 타입의 값을 할당하려 하면 타입 에러가 발생한다.
let arr: string[] = [1, 2, 3]; // error TS2322: Type 'number' is not assignable to type 'string'.
타입스크립트에선 특이하게도 값의 범위도 지정할 수 있는데, 이것도 타입 체크의 범주로 포함하며 리터럴 타입 또는 리터럴 집합이라 한다:
let one: 1;
one = 1;
one = 2; // error TS2322: Type '2' is not assignable to type '1'.
변수 one
에 대한 타입으로 리터럴 1
을 명시했기 때문에 1
외에는 할당할 수 없는 변수가 된다.
식별자명 바로 뒤에 콜론:
과 함께 어떤 타입인지를 선언한다:
let obj: {name: string} = {
name: 'John'
};
console.log(obj.name); // John
하지만 명시하지 않은 객체의 프로퍼티에 접근하려 하면 컴파일 에러가 발생하는데:
let obj: {name: string} = {
name: 'John'
};
console.log(obj.sirname); // error TS2339: Property 'sirname' does not exist on type '{ name: string; }'.
특정 프로퍼티가 있을 수도 없을 수도 있다면(이것은 객체 프로퍼티로 undefined
할당을 허용하는 것과 같다) 물음표?
를 붙여서 옵셔널 프로퍼티(Optional properties)로 지정한다:
let obj: {name: string, sirname?: string} = {
name: 'John'
};
console.log(obj.sirname); // undefined
아래처럼 대괄호 표기법을 이용한 동적 프로퍼티 접근 코드는 에러를 유발하는데:
let obj = {
a: 1,
b: 2
};
let b = 'b';
console.log(obj[b]);
// error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ a: number; b: number; c: number; }'.
// No index signature with a parameter of type 'string' was found on type '{ a: number; b: number; c: number; }'.
다음처럼 TypeScript Documentation | Index Signatures로 프로퍼티 이름과 값의 타입을 정의하여 해소할 수 있다:
let obj: {[key: string]: number} = {
a: 1,
b: 2
};
let b = 'b';
console.log(obj[b]); // 2
객체 타입을 좀 더 구체적으로 명시할 때 사용한다. as
혹은 홑화살괄호<>
로 표기한다:
let myCanvas = document.querySelector('#myCanvas') as HTMLCanvasElement;
let myCanvas2 = <HTMLCanvasElement>document.querySelector('#myCanvas');
호출 시그니처는 함수나 메서드의 타입을 설명하는 방법이다. 매개변수의 타입과 반환값의 타입을 정의할 때 사용한다.
function add(a: number, b: number) {
// ...
}
파라미터 a
와 b
모두 number
타입이라는 뜻이다.
얼핏 보면 구조분해 처럼 보이지만 사실은 객체 프로퍼티에 대한 타입을 표기한 것이다. 각 프로퍼티를 구분할 땐 쉼표,
혹은 세미 콜론;
을 사용한다.
function printFooBar(foobar: { foo: string; bar: number }) {
// ...
}
function printFooBar2(foobar: { foo: string, bar: number }) {
console.log(foobar.foo, foobar.bar);
}
printFooBar2({ foo: 'foo', bar: 1 }); // foo 1
하지만 이렇게 객체 프로퍼티에 대한 타입을 지정하면 해당 프로퍼티를 생략했을 때 타입 에러가 발생한다:
function printFooBar2(foobar: { foo: string, bar: number }) {
// ...
}
printFooBar2({ foo: 'foo' });
// error TS2345: Argument of type '{ foo: string; }' is not assignable to parameter of type '{ foo: string; bar: number; }'. Property 'bar' is missing in type '{ foo: string; }' but required in type '{ foo: string; bar: number; }'.
객체 리터럴에서처럼 undefined
할당을 허용하는 옵셔널 프로퍼티로 만들려면 물음표?
를 붙인다:
function printFooBar3(foobar: { foo: string, bar?: number }) {
// ...
}
printFooBar3({ foo: 'foo' });
function getSymbol(): symbol {
return Symbol('me');
}
만약 문맥 상 반환 타입을 예측할 수 있다면 생략할 수 있다.
함수 타입 파라미터(파라미터가 함수)에 대한 타입 표기 방법이다:
function caller(callee: (a: string) => void) {
callee('hello');
}
콜백 함수의 매개변수가 없고 반환 타입이 string
이면 아래처럼 표기한다:
function caller2(callee: () => string) {
return callee();
}
console.log(caller2(() => 'hello')); // hello
익명 함수(화살표 함수 포함)의 타입 표기 생략은 문맥에 따라 결정된다:
let names = ['a', 'b', 'c'];
names.forEach(s => console.log(s.toUpperCase()));
let arr2 = [];
arr2.forEach(n => console.log(n.toFixed(2)));
// error TS7034: Variable 'arr2' implicitly has type 'any[]' in some locations where its type cannot be determined.
위 코드는 이렇게 바꿔야 한다:
let arr2: number[] = [];
arr2.forEach(n => console.log(n.toFixed(2)));
여기서 자동으로 타입을 알아내는 것은 타입 추론이 아니라 문맥적 타입 부여(contextual typing)라고 한다.
익명 함수의 반환 타입은 이렇게 작성한다:
let getWaldo = (): string => 'Waldo';
getWaldo().toUpperCase();
이 또한 문맥 상 자동으로 알 수 있는 상황이라면 생략할 수 있긴 하다. 하지만 아래의 경우라면:
let getWaldo = (name: string) => {
return {name}
};
getWaldo('Waldo').age = 128;
// error TS2339: Property 'age' does not exist on type '{ name: string; }'.
getWaldo()
가 반환하는 객체의 프로퍼티로 age
가 명시되지 않았기 때문에 에러가 발생한다. 이를 해결하려면:
let getWaldo = (name: string): {name: string, age?: number} => {
return {name}
};
getWaldo('Waldo').age = 128;
https://www.typescriptlang.org/docs/handbook/2/functions.html#construct-signatures
TODO
type
키워드로 커스텀 타입을 정의한다. 객체나 유니언(아래에 따로 설명함)의 타입을 제한할 때 주로 사용한다:
function printCoordinate(obj: Coordinate) {
return `${obj.latitude} ${obj.longitude}`
}
printCoordinate({ latitude: 0, longitude: 0 }) // '0 0'
type myNumber3 = number | string;
let num3: myNumber3 = 10;
let num4: myNumber3 = '10';
type person = {
name: string;
age: number;
email: string;
}
let obj: person = {
name: "Waldo",
age: 123,
email: "a@b.co"
}
타입 별칭과 매우 유사한데, 인터페이스는 확장(extends)이 가능하다는 차이가 있다:
interface Person {
name: string;
age: number;
}
interface Developer extends Person {
skills: string[];
}
const person1: Person = {
name: '김사람',
age: 20
};
const person2: Developer = {
name: '김개발',
age: 20,
skills: ['javascript', 'react', 'typescript']
};
인터페이스는 타입 정의 외에도 클래스의 구상화 기능도 제공한다:
// 코드 출처: https://www.typescriptlang.org/docs/handbook/2/classes.html#class-heritage
interface Pingable {
ping(): void;
}
class Sonar implements Pingable {
ping() {
console.log("ping!");
}
}
ping()
은 인터페이스 내에 선언된 메서드 시그니처(method signatures)라고 한다. 이것은 구현 클래스에 ping()
이 반드시 있도록 제한한다:
class Ball implements Pingable {
// error TS2420: Class 'Ball' incorrectly implements interface 'Pingable'.
// Property 'ping' is missing in type 'Ball' but required in type 'Pingable'.
// ping() 메서드를 구현하지 않은 경우
}
⚠️ implements
는 클래스나 메서드의 타입을 변경하지 않는다(It doesn’t change the type of the class or its methods at all: 타입 선언을 의미하는 것 같음). 이것은 인터페이스에 정의된 메서드의 매개변수 타입이 자동으로 구현 클래스에 적용되지 않는다는 말이다:
interface Checkable {
check(name: string): boolean;
}
class NameChecker implements Checkable {
check(s): boolean {
// error TS7006: Parameter 's' implicitly has an 'any' type.
return false;
}
}
클래스에서의 타입 사용은 앞서 언급했던 것들의 조합이다:
class Newbie {
name: string; // 프로퍼티 타입 표기
id: number;
constructor(name: string, id: number) { // 함수 매개변수 타입 표기
this.name = name;
this.id = id;
}
}
추상 클래스는 인스턴스 생성이 불가능하며 상속(extends)을 위해서면 존재하는 클래스다. 다른 클래스로 파생되는 기반 클래스 역할을 하며, 구현 클래스의 형태를 제한한다.
아래는 추상 메서드 getName()
를 갖는 추상 클래스를 선언하는 방법이다:
// 코드 출처: https://www.typescriptlang.org/docs/handbook/2/classes.html#abstract-classes-and-members
abstract class Base {
abstract getName(): string;
printName() {
console.log("Hello, " + this.getName());
}
}
class Derived extends Base {
getName() {
return "world";
}
}
const d = new Derived();
d.printName();
만약 구현 클래스에 getName()
이 없으면 에러가 발생한다:
class Derived extends Base {} // error TS18052: Non-abstract class 'Derived' does not implement all abstract members of 'Base'
타입스크립트에선 파라미터에 대한 타입으로 생성자를 선언할 수 있다. 이것은 그 중 추상 클래스의 생성자 타입을 제한하는 방법이다. (별 게 다 있다 🥲)
abstract class Base {
abstract printName(): void;
}
class Derived extends Base {
printName() {
console.log('Derived instance');
}
}
function greet(ctor: new () => Base) {
const instance = new ctor();
instance.printName();
}
greet(Derived);
추상 클래스 Base
와 Base
를 상속하는 Derived
가 있을 때, greet()
함수에서 Base
와 Base
의 서브 클래스 생성자를 파라미터로 받도록 타입을 제한했다. 그리고 Derived
생성자 함수를 인수로 넘기는 코드다.
만약 Base
생성자 함수를 인수로 하면:
greet(Base);
// error TS2345: Argument of type 'typeof Base' is not assignable to parameter of type 'new () => Base'.
// Cannot assign an abstract constructor type to a non-abstract constructor type.
추상 클래스는 인스턴스를 만들 수 없기 때문에 에러가 발생한다.
🚨 typeof
타입 연산자로도 비슷한 구현이 가능하지만 권장되지 않는다.
TypeScript Documentation | Enums
TODO
여러 타입 중 하나일 수 있음을 나타내는 방법이다. 허용할 타입들은 파이프|
로 구분한다.
type myNumber = number | string;
let n: myNumber = 1;
n = '1'; // OK
n = false; // error TS2322: Type 'boolean' is not assignable to type 'myNumber'.
type myNumber2 = 1 | 2 | 3 | '1' | '2' | '3';
let n2: myNumber2 = 1;
n2 = '3'; // OK
n2 = 4; // error TS2322: Type '4' is not assignable to type 'myNumber2'.
// 배열 내부 요소에 적용
let arr2: myNumber2[] = [1, '2', 3]; // OK
type Person = { name: string, age: number };
type Human = { breathing: boolean };
let waldo: Person | Human;
waldo = {
name: 'waldo',
age: 47,
breathing: true
}; // O
waldo = {
hello: 'Hello there!'
// error TS2353: Object literal may only specify known properties, and 'hello' does not exist in type 'Person | Human'.
};
공식 도움말에선 교집합으로 번역한다. 여러 타입 중 하나(OR)라도 만족하면 되는 유니언과 다르게 인터섹션은 여러 타입을 모두 만족(AND)해야 한다:
type Person = { name: string, age: number };
type Human = { breathing: boolean };
let waldo: Person & Human;
waldo = {
name: 'waldo',
age: 47,
breathing: true,
}; // O
waldo = {
name: 'waldo',
age: 47
// error TS2322: Type '{ name: string; age: number; }' is not assignable to type 'Person & Human'.
// Property 'breathing' is missing in type '{ name: string; age: number; }' but required in type 'Human'.
};
인터섹션 타입으로 원시 타입은 지정할 수 없다. 숫자이면서 동시에 문자열인 값은 존재할 수 없기 때문:
type conflicting = number & string;
let foo1: conflicting;
foo1 = '123'; // error TS2322: Type 'string' is not assignable to type 'never'.
foo1 = 123; // error TS2322: Type 'number' is not assignable to type 'never'.
배열처럼 내부에서 별도로 처리하는 값들의 타입을 정의할 때 사용한다.
type StringArray = Array<string>;
let strArr: StringArray = ['a', 'b', 'c'];
type NumberArray = Array<number>;
let numArr: NumberArray = [1, 2, 3];
numArr.push('4'); // error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
type ObjectWithNameArray = Array<{ name: string }>;
let objArr: ObjectWithNameArray = [{ name: '1' }, { name: '2' }];
아래는 공식 가이드의 예시를 약간 수정한 것:
interface Backpack<Type> {
contents: Array<Type>;
add: (obj: Type) => void;
get: () => Array<Type>;
}
const backpack: Backpack<string> = {
contents: [],
add: function (obj) {
this.contents.push(obj);
},
get: function () {
return this.contents;
},
}
const object = backpack.get();
console.log(object); // []
backpack.add('23'); // OK
console.log(object); // ['23']
backpack.add(23); // error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
변수의 타입 선언을 생략해도 형태를 비교하여 타입을 판단하는 특성을 말한다. 아래 공식 가이드의 코드를 보면:
interface Point {
x: number;
y: number;
}
function printPoint(p: Point) {
console.log(`${p.x}, ${p.y}`);
}
const point = { x: 12, y: 26 };
printPoint(point);
point
는 Point
타입으로 선언된 적이 없지만, 타입 검사기가 알아서 형태를 비교하고 통과시킨다. 따라서 이 코드는 정상이다.
형태를 비교할 때는 부분 집합인지만 통과하면 된다.
const point2 = { x: 12, y: 26, z: 89 }; // OK
const rect = { x: 33, y: 3, width: 30, height: 80 }; // OK
하지만 전혀 엉뚱한 구조라면:
const color = { hex: "#187ABF" };
// error TS2345: Argument of type '{ hex: string; }' is not assignable to parameter of type 'Point'.
// Type '{ hex: string; }' is missing the following properties from type 'Point': x, y
반기는 것은 타입 에러다.
declare
키워드로 변수나 모듈, 함수, 클래스 등의 존재를 타입스크립트에 알리기 위해 사용하는 선언 방식.
declare var myVariable: string;
declare function myFunction(a: number): void;
declare class MyClass { }
앰비언트 선언은 보통 .d.ts
파일(타입스크립트 선언 파일)에 작성한다. 이 파일들은 컴파일 중 코드로 변환되지 않으며, 오직 타입 체크와 자동 완성, 문서화를 위해 사용된다.
TODO 설명 추가
TypeScript Documentation | Creating .d.ts Files from .js files
다운레벨링은 상위 버전의 스크립트를 하위 버전으로 바꿔주는 기능을 의미하는 용어다.
아래처럼 ES2015에 도입된 기술은 템플릿 리터럴을 사용한 코드는:
let person = 'John Doe';
`Hello ${person}.`;
tsconfig.json
의 target
설정이 ES2015보다 낮은 ES5라면 아래처럼 컴파일된다:
"use strict";
var person = 'John Doe';
"Hello ".concat(person, ".");
하지만 다운레벨링이 모든 것을 가능하게 해주는 요술봉은 아니다. 예를 들어 ES5에서 symbol
타입은 사용할 수 없기 때문에 컴파일 에러가 발생한다:
// "target": "es5"
let s: symbol = Symbol('s');
// error TS2585: 'Symbol' only refers to a type, but is being used as a value here. Do you need to change your target library? Try changing the 'lib' compiler option to es2015 or later.
TypeScript Documentation | Keyof Type Operator
TODO
TypeScript Documentation | Typeof Type Operator
TODO
TypeScript Documentation | JSDoc Reference
TODO
]]>메타 속성인 new.target
은 함수가 함수로 호출되었는지, 아니면 생성자로 호출되었는지를 구분할 때 사용한다. ES2015에 추가된 기능이다.
new.target
은 함수 내에서만 사용할 수 있다:
new.target; // Uncaught ReferenceError: target is not defined
함수로 호출된 경우 undefined
를, 생성자일 땐 new
로 호출된 생성자 또는 함수에 대한 참조를 반환한다:
function Foo() {
return new.target;
}
Foo(); // undefined
new Foo(); // function Foo()
new Foo().name; // 'Foo'
이런 식으로 활용한다:
function Newbie(name) {
if (!new.target) {
return new Newbie();
}
this.name = name;
}
var noob = new Newbie('noob-noob');
var noob2 = Newbie('noob-noob');
noob instanceof Newbie; // true
noob2 instanceof Newbie; // true
문법에 따라 미묘한 차이가 있다. 클래스(classes)는 클래스를 반환하거나, super()
가 호출된 경우 서브클래스를 반환한다:
class A {
constructor() {
console.log(new.target.name);
}
}
class B extends A {
constructor() {
super();
}
}
var a = new A(); // logs "A"
var b = new B(); // logs "B"
// 코드 출처: https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/new.target
화살표 함수는 new.target
을 둘러싸는 함수의 new.target
을 반환한다:
function Bar() {
return () => new target
}
new Bar(); // function Bar()
/* 테이블 + 컬럼 코멘트 */
set @table_name = 'table_name';
set @table_schema = 'schema_name';
select 0 as ordinal_position, 'it\'s table' as column_name, '' as column_type, concat('/**', table_name, ' ', table_comment, ' 테이블 클래스', '*/\r\r') as str
from information_schema.tables
where table_name = @table_name
and table_schema = @table_schema
union
select ordinal_position, column_name, column_type, concat('\t/**', column_comment, '*/\r\tprivate ',
case
when column_type like 'int%' then 'Integer'
when column_type like 'varchar%' then 'String'
when column_type like 'datetime%' then 'java.time.LocalDateTime'
when column_type like 'date%' then 'java.time.LocalDate'
when column_type like 'time%' then 'java.time.LocalTime'
when column_type like 'tinyint%' then 'Integer'
when column_type like 'smallint%' then 'Integer'
when column_type like 'mediumint%' then 'Integer'
when column_type like 'bigint%' then 'Long'
when column_type like 'float%' then 'Float'
when column_type like 'double%' then 'Double'
when column_type like 'decimal%' then 'BigDecimal'
when column_type like 'text%' then 'String'
when column_type like 'blob%' then 'String'
when column_type like 'binary%' then 'String'
when column_type like 'char%' then 'String'
when column_type like 'enum%' then 'String'
when column_type like 'set%' then 'String'
when column_type like 'bool%' then 'Boolean'
when column_type like 'boolean%' then 'Boolean'
when column_type like 'tinyblob%' then 'String'
when column_type like 'tinytext%' then 'String'
when column_type like 'mediumblob%' then 'String'
when column_type like 'mediumtext%' then 'String'
when column_type like 'longblob%' then 'String'
when column_type like 'longtext%' then 'String'
end
, ' ', column_name, ';') as str
from information_schema.columns
where table_name = @table_name
and table_schema = @table_schema
order by ordinal_position asc
set
까지 한 번에 실행하면 된다. 결과에서 str
컬럼 전체를 Java 클래스에 붙여넣으면 끗. DBMS 툴에 따라 큰따옴표가 붙을 수 있는데 그냥 전부 지우면 됨.
배열이 아닌 object 타입 객체의 프로퍼티를 반복자(iterator)로 꺼내는 방법을 설명.
요런 객체가 있다고 가정하고:
var loopMe = {
a: 7,
b: 8,
c: 9
};
Object.defineProperty(loopMe, 'd', {
value: 10,
enumerable: false
});
console.log(loopMe); // Object { a: 7, b: 8, c: 9, d: 10 }
프로퍼티 설명자에서 enumerable
이 true
인 프로퍼티에 한하여 반복할 수 있다.
for (let ele in loopMe) {
console.log('ele:', ele);
}
// a b c
object 타입은 iterable object가 아니라서 for-of는 쓸 수 없다.
아래 세 개의 메서드를 활용한다. 공통적으로 객체가 소유하면서 열거 가능한 프로퍼티여야 한다는 조건이 있다.
Object.keys
는 프로퍼티 이름을 배열로 반환한다.
Object.keys(loopMe); // Array(3) [ "a", "b", "c" ]
Object.keys(loopMe).forEach(key => {
console.log(loopMe[key]);
}); // 7 8 9
Object.values
는 프로퍼티 값을 배열로 반환한다.
Object.values(loopMe); // Array(3) [ 7, 8, 9 ]
Object.entries
는 프로퍼티 이름, 프로퍼티 값 pair를 배열로 반환한다.
Object.entries(loopMe); // Array(3) [ Array [ "a", 7 ], Array [ "b", 8 ], Array [ "c", 9 ] ]
끟.
]]>로딩 이미지와 딤 레이어를 설정하는 방법을 작성한 글
참고로 해외에서는 로딩 이미지를 스피너(spinner: 뱅뱅 도는 무언가)라고 부른다. spinner images
로 검색해 볼 것.
#loading {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #ffffff36;
z-index: 9999;
background-image: url("/image/loading.gif");
background-repeat: no-repeat;
background-attachment: fixed;
background-position: center;
}
<div id="loading"></div>
function onLoad() {
let $ele = document.querySelector('#loading');
$ele.style = 'display: block';
}
function onLoaded() {
let $ele = document.querySelector('#loading');
$ele.style = 'display: none';
}
살짝 흐릿한 레이어가 전면을 덮어서 로딩이 완료될 때까지 클릭 등을 막는 용도로 쓴다.
여기에 대충 적당히 움직이는 이미지를 얹으면 끗.
]]>시간을 다루는 데이터 타입 DATE
, DATETIME
, TIME
과 관련 함수 사용법 정리 글.
문자열을 날짜로:
select str_to_date('2022-05-05 08:00:00', '%Y-%m-%d %H:%i:%s')
날짜를 문자열로:
select date_format(d.dummy, '%Y-%m-%d %H:%i:%s') as isoString
from (
select '2022-05-16 01:30:55' as dummy
) d
DATETIME
을 DATE
혹은 TIME
으로:
select date(now());
select time(now());
convert()
함수로 변환:
select convert(now(), date);
select convert(now(), time);
DATE_FORMAT()
은 날짜 데이터를 특정 포맷의 문자열로 변환한다. STR_TO_DATE()
는 반대로 문자열을 날짜 데이터로 변환한다.
select
dd.dummy as dateDefault,
date_format(dd.dummy, '%Y-%m-%d %H:%i:%s') as isoString,
date_format(dd.dummy, '%Y-%m-%dT%H:%i:%s.%f') as isoString2,
date_format(dd.dummy, '%Y-%m-%d') as isoString3,
date_format(dd.dummy, '%Y') as yearOfEra,
date_format(dd.dummy, '%m') as monthOfYear,
date_format(dd.dummy, '%d') as dayOfMonth,
date_format(dd.dummy, '%H') as hours,
date_format(dd.dummy, '%i') as minutes,
date_format(dd.dummy, '%s') as seconds,
date_format(dd.dummy, '%w') as day,
date_format(dd.dummy, '%W') as dayString,
dd.dummy between str_to_date('2022-05-16 01:30:55', '%Y-%m-%d %H:%i:%s') and str_to_date('2022-05-17 05:30:55', '%Y-%m-%d %H:%i:%s') as betweeeeeen
from (
select str_to_date(d.dummy, '%Y-%m-%d %H:%i:%s') as dummy /* 포맷: yyyy-MM-dd HH:mm:dd */
from (
select '2022-05-16 01:30:55' as dummy
union
select '2022-05-17 05:30:55' as dummy
union
select '2022-05-18 11:30:55' as dummy
union
select '2022-05-19 13:30:55' as dummy
union
select '2022-05-20 16:30:55' as dummy
union
select '2022-05-21 20:30:55' as dummy
union
select '2022-05-22 23:30:55' as dummy
) d
) dd
+——————-+——————-+————————–+———-+———+———–+———-+—–+——-+——-+—+———+———–+ |dateDefault |isoString |isoString2 |isoString3|yearOfEra|monthOfYear|dayOfMonth|hours|minutes|seconds|day|dayString|betweeeeeen| +——————-+——————-+————————–+———-+———+———–+———-+—–+——-+——-+—+———+———–+ |2022-05-16 01:30:55|2022-05-16 01:30:55|2022-05-16T01:30:55.000000|2022-05-16|2022 |05 |16 |01 |30 |55 |1 |Monday |1 | |2022-05-17 05:30:55|2022-05-17 05:30:55|2022-05-17T05:30:55.000000|2022-05-17|2022 |05 |17 |05 |30 |55 |2 |Tuesday |1 | |2022-05-18 11:30:55|2022-05-18 11:30:55|2022-05-18T11:30:55.000000|2022-05-18|2022 |05 |18 |11 |30 |55 |3 |Wednesday|0 | |2022-05-19 13:30:55|2022-05-19 13:30:55|2022-05-19T13:30:55.000000|2022-05-19|2022 |05 |19 |13 |30 |55 |4 |Thursday |0 | |2022-05-20 16:30:55|2022-05-20 16:30:55|2022-05-20T16:30:55.000000|2022-05-20|2022 |05 |20 |16 |30 |55 |5 |Friday |0 | |2022-05-21 20:30:55|2022-05-21 20:30:55|2022-05-21T20:30:55.000000|2022-05-21|2022 |05 |21 |20 |30 |55 |6 |Saturday |0 | |2022-05-22 23:30:55|2022-05-22 23:30:55|2022-05-22T23:30:55.000000|2022-05-22|2022 |05 |22 |23 |30 |55 |0 |Sunday |0 | +——————-+——————-+————————–+———-+———+———–+———-+—–+——-+——-+—+———+———–+
지원하는 모든 날짜 포맷은 요 페이지를 보자.
자주 쓰이는 건 요 정도:
%Y
: yearOfEra%m
: monthOfYear%d
: dayOfMonth%H
: hours%i
: minutes%s
: seconds%w
: day%W
: dayStringISO 같은 잘 알려진 포맷은 GET_FORMAT()
함수로 얻을 수 있다:
select get_format(date, 'iso'), get_format(datetime, 'iso')
+———————-+————————–+ |get_format(date,'iso')|get_format(datetime,'iso')| +———————-+————————–+ |%Y-%m-%d |%Y-%m-%d %H:%i:%s | +———————-+————————–+
* 여기서 date
, datetime
은 날짜값이 아니라 유닛이다.
함수를 활용한다:
CURDATE()
: 공식 문서 설명에 따르면 단순히 YYYY-MM-DD
혹은 YYYYMMDD
포맷의 현재 날짜값을 반환한다. 동의어로 current_date
가 있다.CURTIME([precision])
: HH:MM:SS
혹은 HHMMSS.uuuuuu
포맷의 현재 시간값을 반환한다. 동의어로 current_time
이 있다. precision
은 선택사항이다. 마이크로초 정밀도를 의미하며 0에서 6까지의 값을 입력할 수 있다.NOW([precision])
: YYYY-MM-DD HH:MM:SS
혹은 YYYYMMDDHHMMSS.uuuuuu
포맷의 시간과 날짜값을 반환한다. 동의어는 localtime
, localtimestamp
, current_timestamp
. precision
은 마찬가지로 선택사항이며, 0-6 사이의 마이크로초 정밀도다.select now()
LAST_DAY(date)
: date
가 속한 달의 마지막 날을 반환DATE_ADD(date, INTERVAL expr unit)
DATE_SUB(date, INTERVAL expr unit)
adddate()
, subdate()
, addtime()
, subtime()
, add_months()
등 비슷한 함수가 많이 있는데 그냥 위 두 개로 웬만하면 됨.
select
date_add(now(), interval 1 day),
date_sub(now(), interval 2 month)
unit
자리에 올 수 있는 키워드는 이 문서에 있고, 요약하면 아래와 같다:
MICROSECOND
: MicrosecondsSECOND
: SecondsMINUTE
: MinutesHOUR
: HoursDAY
: DaysWEEK
: WeeksMONTH
: MonthsQUARTER
: QuartersYEAR
: YearsSECOND_MICROSECOND
: Seconds.MicrosecondsMINUTE_MICROSECOND
: Minutes.Seconds.MicrosecondsMINUTE_SECOND
: Minutes.SecondsHOUR_MICROSECOND
: Hours.Minutes.Seconds.MicrosecondsHOUR_SECOND
: Hours.Minutes.SecondsHOUR_MINUTE
: Hours.MinutesDAY_MICROSECOND
: Days Hours.Minutes.Seconds.MicrosecondsDAY_SECOND
: Days Hours.Minutes.SecondsDAY_MINUTE
: Days Hours.MinutesDAY_HOUR
: Days HoursYEAR_MONTH
: Years-Months사실 함수를 쓰지 않고 그냥 연산자로 해결하는 방법도 있다.
select
current_date() + interval 3 month,
current_date() - interval 3 year
CONVERT_TZ(dt, from_tz, to_tz)
# UTC에서 KST로 변환(단순히 9시간을 더한 것과 같음)
select convert_tz(now(), 'UTC', 'Asia/Seoul')
이 함수는 dt
가 시간만 있거나(TIME) 날짜만 있는(DATE) 값이어도 날짜 + 시간 형태로 반환한다:
select
now() as now,
convert_tz(now(), 'UTC', 'Asia/Seoul') as nowKst,
convert_tz(now(), 'UTC', 'America/Los_Angeles') as nowLa,
curtime() as curtime,
convert_tz(curtime(), 'UTC', 'UTC') as curtimeUtc,
convert_tz(curtime(), 'UTC', 'Asia/Seoul') as curtimeKst,
convert_tz(curtime(), 'UTC', 'America/Los_Angeles') as curtimeLa,
curdate() as curdate,
convert_tz(curdate(), 'UTC', 'UTC') as curdateUtc,
convert_tz(curdate(), 'UTC', 'Asia/Seoul') as curdateKst,
convert_tz(curdate(), 'UTC', 'America/Los_Angeles') as curdateLa,
str_to_date('2022-05-05 08:00:00', '%Y-%m-%d %H:%i:%s') as std,
convert_tz(str_to_date('2022-05-05 08:00:00', '%Y-%m-%d %H:%i:%s'), 'UTC', 'Asia/Seoul') as stdKst,
convert_tz(str_to_date('2022-05-05 08:00:00', '%Y-%m-%d %H:%i:%s'), 'UTC', 'America/Los_Angeles') as stdLa
타임존이 UTC인 데이터베이스에서 2022-11-09 07:57:31 에 실행하면 이렇게 됨:
COLUMN | VALUE |
---|---|
NOW | 2022-11-09 01:39:30 |
NOW_KST | 2022-11-09 10:39:30 |
NOW_LA | 2022-11-08 17:39:30 |
CURTIME | 01:39:30 |
CURTIME_UTC | 2022-11-09 01:39:30 |
CURTIME_KST | 2022-11-09 10:39:30 |
CURTIME_LA | 2022-11-08 17:39:30 |
CURDATE | 2022-11-09 |
CURDATE_UTC | 2022-11-09 00:00:00 |
CURDATE_KST | 2022-11-09 09:00:00 |
CURDATE_LA | 2022-11-08 16:00:00 |
STD | 2022-05-05 08:00:00 |
STD_KST | 2022-05-05 17:00:00 |
STD_LA | 2022-05-05 01:00:00 |
curdate()
는 타임존을 변환해도 현재 시간을 무시하고 현재 날짜 + 00시 기준으로 변환되기 때문에 의도대로 작동하지 않으니 주의할 것.
EXTRACT(unit FROM date)
select
date_format(v.currentTs, '%Y-%m-%dT%H:%i:%s.%f') AS currentTimestamp,
extract(year from v.currentTs) AS year,
extract(year_month from v.currentTs) AS yearMonth,
extract(month from v.currentTs) AS month,
extract(day from v.currentTs) AS day,
extract(day_hour from v.currentTs) AS dayHour,
extract(day_minute from v.currentTs) AS dayMinute,
extract(day_second from v.currentTs) AS daySecond,
extract(day_microsecond from v.currentTs) AS dayMicrosecond,
extract(hour from v.currentTs) AS hour,
extract(hour_minute from v.currentTs) AS hourMinute,
extract(hour_second from v.currentTs) AS hourSecond,
extract(hour_microsecond from v.currentTs) AS hourMicrosecond,
extract(minute from v.currentTs) AS minute,
extract(minute_second from v.currentTs) AS minuteSecond,
extract(minute_microsecond from v.currentTs) AS minuteMicrosecond,
extract(second from v.currentTs) AS second,
extract(second_microsecond from v.currentTs) AS secondMicrosecond,
extract(microsecond from v.currentTs) AS microsecond,
extract(week from v.currentTs) AS week,
extract(quarter from v.currentTs) AS quarter
from (
select now(6) as currentTs /*마이크로초 여섯 자리까지*/
) v
UNIT | VALUE |
---|---|
CURRENT_TIMESTAMP | 2022-12-14T05:51:06.312446 |
YEAR | 2022 |
YEAR_MONTH | 202212 |
MONTH | 12 |
DAY | 14 |
DAY_HOUR | 1405 |
DAY_MINUTE | 140551 |
DAY_SECOND | 14055106 |
DAY_MICROSECOND | 14055106312446 |
HOUR | 5 |
HOUR_MINUTE | 551 |
HOUR_SECOND | 55106 |
HOUR_MICROSECOND | 55106312446 |
MINUTE | 51 |
MINUTE_SECOND | 5106 |
MINUTE_MICROSECOND | 5106312446 |
SECOND | 6 |
SECOND_MICROSECOND | 6312446 |
MICROSECOND | 312446 |
WEEK | 50 |
QUARTER | 4 |
날짜:
YEAR()
: 주어진 날짜의 연도를 네 자릿수로 반환MONTH()
: 주어진 날짜의 월을 1-12 사이의 값으로 반환DAYOFMONTH()
DAY()
: 주어진 날짜의 날짜를 1-31 사이의 값으로 반환DAYOFWEEK()
: 주어진 날짜가 해당 주의 몇 번째 날인지 반환한다. (1=일요일, 2=월요일, …, 7=토요일)WEEKDAY()
: 주어진 날짜가 해당 주의 몇 번째 날인지 반환한다. (0=월요일, 1=화요일, …, 6=일요일)시간:
HOUR(time)
: 0-23 반환MINUTE(time)
: 0-59 반환SECOND(time)
: 0-59 반환MICROSECOND(expr)
: 0-999999 반환먼저 BETWEEN 비교가 조건항을 포함하는지를 보자:
select
if(0 between 0 and 2, 'true', 'false'), -- true
if(1 between 0 and 2, 'true', 'false'), -- true
if(2 between 0 and 2, 'true', 'false') -- true
포함한다.
select
a.startDate,
a.endDate,
a.compareMe1, if(a.compareMe1 between a.startDate and a.endDate, 'true', 'false') as isInRange1,
a.compareMe2, if(a.compareMe2 between a.startDate and a.endDate, 'true', 'false') as isInRange2,
a.compareMe3, if(a.compareMe3 between a.startDate and a.endDate, 'true', 'false') as isInRange3,
a.compareMe4, if(a.compareMe4 between a.startDate and a.endDate, 'true', 'false') as isInRange4,
a.compareMe5, if(a.compareMe5 between a.startDate and a.endDate, 'true', 'false') as isInRange5
from (
select
str_to_date('2018-01-01', '%Y-%m-%d') as startDate,
str_to_date('2018-01-03', '%Y-%m-%d') as endDate,
str_to_date('2018-01-01 00:00:00', '%Y-%m-%d %H:%i:%s') as compareMe1,
str_to_date('2018-01-02 23:59:59', '%Y-%m-%d %H:%i:%s') as compareMe2,
str_to_date('2018-01-03 00:00:00', '%Y-%m-%d %H:%i:%s') as compareMe3,
str_to_date('2018-01-03 00:30:00', '%Y-%m-%d %H:%i:%s') as compareMe4,
str_to_date('2018-01-03 23:59:59', '%Y-%m-%d %H:%i:%s') as compareMe5
) a
실행해 보면 isInRange4
와 isInRange5
만 false인데, 2018-01-03
을 DATE 타입으로 만들면 시분초 값이 모두 0으로 설정된다는 것을 추측할 수 있음.
따라서 조건으로 대입된 날짜의 23:59:59(23시 59분 59초)까지라고 지정하고 싶으면, DATE를 문자열로 만들고 다시 DATETIME으로 변환하면서 시분초를 붙이는 방법을 고려할 수 있다.
결국 이런 모양이 나옴:
select
b.startDate, b.endDateTime,
b.compareMe1, if(compareMe1 between b.startDate and b.endDateTime, 'true', 'false') as isInRange1,
b.compareMe2, if(compareMe2 between b.startDate and b.endDateTime, 'true', 'false') as isInRange2,
b.compareMe3, if(compareMe3 between b.startDate and b.endDateTime, 'true', 'false') as isInRange3
from (
select
startDate,
convert(concat(date_format(a.endDate, '%Y-%m-%d'), ' 23:59:59'), datetime) as endDateTime,
str_to_date('2018-01-01 00:00:00', '%Y-%m-%d %H:%i:%s') as compareMe1,
str_to_date('2018-01-03 00:00:00', '%Y-%m-%d %H:%i:%s') as compareMe2,
str_to_date('2018-01-03 23:59:59', '%Y-%m-%d %H:%i:%s') as compareMe3
from (
select
str_to_date('2018-01-01', '%Y-%m-%d') as startDate,
str_to_date('2018-01-03', '%Y-%m-%d') as endDate
) a
) b
위에 작성한 쿼리 중 DATE
에 시분초를 더해 DATETIME
을 만드는 부분이 있는데:
convert(concat(date_format(a.endDate, '%Y-%m-%d'), ' 23:59:59'), datetime)
가독성이 별로다 싶으면 하루를 더하고 1초를 빼는 방법도 있다:
date_sub(date_add('2022-12-24', interval 1 day), interval 1 second)
# 혹은
'2022-12-24' + interval 1 day - interval 1 second
문자열 리터럴은 안쓰는 게 좋으니 가급적 이걸 사용하도록 하자. 😏
]]>