tag:blogger.com,1999:blog-61540251420329858822024-02-05T22:35:18.226+09:00穀風プログラム全般、無節操に手を出しまくってみるYusukehttp://www.blogger.com/profile/01684623000819625562noreply@blogger.comBlogger366125tag:blogger.com,1999:blog-6154025142032985882.post-24054111230875642822023-09-13T15:41:00.000+09:002023-09-13T15:41:19.235+09:00[Vite] マルチページを作成した際は rewrite を正しく設定しなければならない<pre class="marked">
[Vite](https://vitejs.dev/) では、以下のように `rollupOptions.input` を指定することでマルチページアプリケーションを作ることが可能です。
```js
`title: "vite.config.ts"
export default defineConfig({
// 省略
build: {
rollupOptions: {
input: {
main: resolve(__dirname, 'src/main/index.html'),
admin: resolve(__dirname, 'src/admin/index.html'),
user: resolve(__dirname, 'src/user/index.html'),
},
},
},
});
```
<div class="reference"><div class="title">参考</div><div class="content">
[Build Options | Vite](https://rollupjs.org/configuration-options/#input)
</div></div>
場合によっては便利なのですが、この設定をした上で [Vue Router](https://router.vuejs.org/) のようなルーティング機能を使うと問題がおこるので注意しなければなりません。
例えば `main` 以下に `/main/users/1200` のようなサブページを作りたい場合、ルーターの設定をしただけでは動かないはずです。
この場合、サーバーは `/main/users/1200` などというページが存在しないので、特定のページに rewrite します。
これが、 Vite の dev server の場合、デフォルトで `/index.html` なのです。つまり、そんなページは存在しないとエラーを吐かれてしまいます。
これを、 `/main.html` に rewrite するよう、設定を変更しなければなりません。
<a name='more'></a>
### dev server
`npm run dev` 等で開発サーバーを立ち上げた場合、 Vite が内部に持っているサーバーが立ち上がります。
このサーバーの rewrite を設定するには、以下のように `vite.config.ts` に plugins を設定します。
```js
`title: "vite.config.ts"
export default defineConfig({
// 省略
plugins: [
{
name: 'rewrite-route',
configureServer({ middlewares }) {
middlewares.use((req, _, next) => {
if (req.url != null) {
if (req.url.startsWith('/main/')) {
req.url = '/main.html';
} else if (req.url.startsWith('/admin/')) {
req.url = '/admin.html';
} else if (req.url.startsWith('/user/')) {
req.url = '/user.html';
}
}
next();
});
},
},
],
});
```
<div class="reference"><div class="title">参考</div><div class="content">
https://vitejs.dev/guide/api-plugin.html#configureserver
</div></div>
### Firebase Hosting
この挙動はサーバー側のものですから、サーバーの種類を変えた場合、設定方法も変わります。
本番環境にデプロイする場合は、こちらも考慮しなければなりません。
例えば [Firebase Hosting](https://firebase.google.com/docs/hosting/full-config?authuser=0#rewrites) の場合、以下のような設定ファイルを用意することで rewrite を制御することが可能です。
```js
`title: "firebase.json"
{
"hosting": {
"public": "dist",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [
{
"source": "/main/**",
"destination": "/main.html"
},
{
"source": "/admin/**",
"destination": "/admin.html"
},
{
"source": "/user/**",
"destination": "/user.html"
},
{
"source": "**",
"destination": "/error.html"
}
]
}
}
```
大抵のホスティングサービスは、こういった制御が可能になっていますので、各サービスのマニュアルを参照してください。
</pre>
Yusukehttp://www.blogger.com/profile/01684623000819625562noreply@blogger.com0tag:blogger.com,1999:blog-6154025142032985882.post-46046195797967478832023-01-05T17:14:00.006+09:002023-01-05T17:14:53.577+09:00Android の権限ダイアログで2度「許可しない」が選択されたことを検知する方法<pre class="marked">
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRQoaPxUefi9d8EferjSPySgOr3YWXEq5MyL_xbY3c2LDrBwIDlIaApekzBIMABRZHHZ4rg6rfqOiCH4jv5ANQxvefztS_7HLsIR9p3-3b4xrrWKcv498JD-qnfVF-64WzL_xEtaWCZePrwNmPLh8cf7LrN0HgpRhTSzi2rSqSv8bSt6cuO6opf7Xn/s530/2023-01-05_16-25.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" height="320" data-original-height="530" data-original-width="500" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRQoaPxUefi9d8EferjSPySgOr3YWXEq5MyL_xbY3c2LDrBwIDlIaApekzBIMABRZHHZ4rg6rfqOiCH4jv5ANQxvefztS_7HLsIR9p3-3b4xrrWKcv498JD-qnfVF-64WzL_xEtaWCZePrwNmPLh8cf7LrN0HgpRhTSzi2rSqSv8bSt6cuO6opf7Xn/s320/2023-01-05_16-25.png"/></a></div>
Android 6.0 (API Level 23) 以降、マイクの使用許可などプライバシーに関わるような権限はアプリの中から個別に許可を求めなければいけなくなりました。具体的には以下のようなコードを実行すると上記のようなダイアログが表示されます<sup><span class='barenote'>`AndroidManifest.xml` に `uses-permission` の記述も必要</span></sup>。(`permissionLauncher` の実装は後半を参照)
```kotlin
val granted = ContextCompat.checkSelfPermission(
requireContext(), Manifest.permission.RECORD_AUDIO
)
if (granted != PackageManager.PERMISSION_GRANTED) {
permissionLauncher.launch(Manifest.permission.RECORD_AUDIO)
}
```
以前は毎回このコードを実行することでダイアログを表示することが出来ていたのですが、Android 11 (API Level 30) 以降、ユーザーが2度「許可しない」を選択すると、次からはダイアログが表示されなくなってしまいました。
<a name='more'></a>
自分がユーザーの立場からすると、今は使いたくないからとりあえず「許可しない」を選択しておくことは結構あると思うのです。しかし、いざ使いたいと思った時に反応しなくなるのは困ります。
これを回避する方法は無いものかと調べてみたのですが、結論から言うと回避する方法はなさそうです。
では、どういう実装にするのが良いのでしょうか?少し調べてみました。
> 参考
>
> [アプリの権限をリクエストする | Android Developers](https://developer.android.com/training/permissions/requesting#handle-denial)
>
> [how to determine if the user checked "Never ask again"? | Stack Overflow](https://stackoverflow.com/a/31925748)
以上の情報をまとめると、 `ActivityResultCallback` の中で `shouldShowRequestPermissionRationale` を確認して処理を分けるのが良さそうです。
```kotlin
private val permissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
if (!granted) {
if (shouldShowRequestPermissionRationale(Manifest.permission.RECORD_AUDIO)) {
// 普通の「許可しない」が選択された
// 独自ダイアログ等を表示してパーミッションがなぜ必要なのかを説明する
} else {
// 「許可しない(次回から表示しない)」が選択された
// Preference に保存するなどする
}
}
}
```
開発者の視点からすると、 `shouldShowRequestPermissionRationale` の値に関わらず独自ダイアログ等を表示して何とか許可させたいと思うのですが、それはこの変更の意図を無視した方法なのでお勧めできません。
例えば Preferences に保存する等、2度拒否されたことを記録するだけにし、その情報を元にして以下のような施策を入れるのが良さそうです。
- アイコンを非表示にする
- アイコンを設定画面に移す(それをクリックしたときは独自ダイアログ)
- 設定画面等に説明文とともに「アプリ情報」へのリンクを表示する
なお、「アプリ情報」画面への遷移は以下のようにリクエストすることができます。
```kotlin
val intent = Intent().apply {
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
data = Uri.parse("package:$packageName")
}
startActivity(intent)
```
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEir7wKdNZSRHVy7F97Z46cKtEm90hN-A4UtwAaLPHVY_4kdHPzjCKZhfsO7RZYx8_CB0IvI7daDpSgQQ4fLIfrlBLnbTt6YIbQAg7D3B6Ijv8ZtFVtLQA73UNIg0Llt5KyxebvEK8vvLHPFSXaY4rVYVkJ-_0Be4jvVheW_guTXuGH-wTBC7BpkF97t/s1920/Screenshot_20230105_170909.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" height="320" data-original-height="1920" data-original-width="1080" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEir7wKdNZSRHVy7F97Z46cKtEm90hN-A4UtwAaLPHVY_4kdHPzjCKZhfsO7RZYx8_CB0IvI7daDpSgQQ4fLIfrlBLnbTt6YIbQAg7D3B6Ijv8ZtFVtLQA73UNIg0Llt5KyxebvEK8vvLHPFSXaY4rVYVkJ-_0Be4jvVheW_guTXuGH-wTBC7BpkF97t/s320/Screenshot_20230105_170909.png"/></a></div>
</pre>
<div class='barenote_ref_list'></div>Yusukehttp://www.blogger.com/profile/01684623000819625562noreply@blogger.com0tag:blogger.com,1999:blog-6154025142032985882.post-71820136458028905752021-07-17T17:44:00.002+09:002021-07-17T17:51:55.843+09:00Android の Stroke を特定の辺だけにする<pre class="marked">
[【Android】特定の辺だけにstrokeをつけたい](https://qiita.com/izumin5210/items/3123939043d2b78b4914)
にあるように、特定の辺の stroke を実現するにはコードでの実装が必要という認識だったのですが、最近になって
[InsetDrawable](https://developer.android.com/reference/kotlin/android/graphics/drawable/InsetDrawable) が XML で使用できることを知って<sup><span class='barenote'>というより、InsetDrawable の存在を知った</span></sup>、これを使えば XML だけで stroke を消せるのではないかと思い、やってみました。
<a name='more'></a>
```xml
<?xml version="1.0" encoding="utf-8"?>
<inset android:insetBottom="-6dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<shape>
<stroke
android:width="5dp"
android:color="#FF0000" />
<solid android:color="#DDDDDD" />
<corners
android:topLeftRadius="15dp"
android:topRightRadius="15dp" />
</shape>
</inset>
```
Bottom だけ消してみたら以下のような感じになりました。
注意点としては、inset に入れる値が `stroke` の `width` と全く同じだと、場合によっては表示されてしまう場合があること<sup><span class='barenote'>丸め込み誤差のせいだと思われる</span></sup>。少し大きめの値を指定しましょう。
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmQb1B3QfP0RWsf7RvQq8dzCHHpmONEI1uHiPFQC3YljUD6Ii6EkJ419xExwDAvDJ39BX2jK-3VFxyekp1IqMnFKwh-AG5oolQklP3y7_apc9r1kBOu4eZEW8hE0FBb8T2wSD2I8q7VIg/s568/001.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" height="400" data-original-height="568" data-original-width="359" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmQb1B3QfP0RWsf7RvQq8dzCHHpmONEI1uHiPFQC3YljUD6Ii6EkJ419xExwDAvDJ39BX2jK-3VFxyekp1IqMnFKwh-AG5oolQklP3y7_apc9r1kBOu4eZEW8hE0FBb8T2wSD2I8q7VIg/s400/001.png"/></a></div>
その気になって探してみると、既に情報は出ていました。
[Open-sided Android stroke? - Stack Overflow](https://stackoverflow.com/questions/2422120/open-sided-android-stroke/32145791#32145791)
</pre>
<div class='barenote_ref_list'></div>Yusukehttp://www.blogger.com/profile/01684623000819625562noreply@blogger.com0tag:blogger.com,1999:blog-6154025142032985882.post-87748663079171435522020-03-11T09:17:00.000+09:002020-03-11T10:07:06.454+09:00by ViewModels() を使って ViewModel を取得する方法<pre class="marked">
以前は以下のように [ViewModelProviders](https://developer.android.com/reference/androidx/lifecycle/ViewModelProviders) を使用して [ViewModel](https://developer.android.com/reference/androidx/lifecycle/ViewModel) を取得していましたが、この方法は Deprecated になりました。
```kotlin
val myViewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
```
ドキュメントを見てみると、代わりに kotlin の委譲 `by viewModels()`、もしくは `ViewModelProvider`<sup><span class='barenote'>使い方は最後に参照として書きました</span></sup> を使ってね。と書いてあります。
ところが、`by viewModels()` はどう使うのかが、何処にも書いてありません<sup><span class='barenote'>少なくとも2020年3月10日時点で公式のドキュメントはなさそう</span></sup>。
そこで、ネットの情報とコードを参考に使ってみました。
<a name='more'></a>
### 依存関係
`viewModels` は `fragment-ktx` というパッケージ内に入っているので<sup><span class='barenote'>これも公式には書いてなさそう</span></sup>、これを `implementation` に加えます。
```groovy
`title: "build.gradle in app";
dependencies {
...
implementation 'androidx.fragment:fragment-ktx:1.2.2'
}
```
### コンパイラのバージョンを 1.8 互換に
環境によっては以下のように、JVM のターゲットバージョンが合ってない旨のエラーがでてしまいます。
"Cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper '-jvm-target' option"
これを解決するには、以下のように `build.gradle` に追記します。
```groovy
`title: "build.gradle in app";
android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}
```
この問題、`viewModels()` に限ったことではなく、新しめの kotlin ライブラリを使おうとすると起こります。
> 参考
>
> [android - Cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6 - Stack Overflow](https://stackoverflow.com/a/56996020)
### 基本の使い方
以下のように使用します。
```kotlin
class MainActivity : AppCompatActivity() {
// lazy なので最初に使用する時に初期化される
private val myViewModel by viewModels<MyViewModel>()
fun something() {
myViewModel.users.observe(this, Observer {
// Do something
})
}
}
```
### Factory 指定
Factory を指定した初期化の方法は以下。
```kotlin
private val myViewModel by viewModels<MyViewModel>{ MyViewModelFactory() }
```
`viewModels` の実装を覗いてみると、以下のようになっています。
`Factory` を返す関数を与えられるようになっているわけですね。
```kotlin
@MainThread
inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
val factoryPromise = factoryProducer ?: {
defaultViewModelProviderFactory
}
return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}
```
`ViewModelLazy()` も public なクラスなので、これを直接使えば `ViewModelStoreOwner` (Activity や Fragment 等)を指定できます。
しかし、これは遅延初期化オブジェクト。どのタイミングで呼ばれるかが定かではありません<sup><span class='barenote'>正確には、呼び出しを全部網羅して確認すれば不可能ではないけど、それだと lazy を使う意味はもうない</span></sup>。
Activity や Fragment 等、ライフサイクルによっては正しく動作しないものを指定すべきではありません。
そういった用途には以下の `ViewModelProvider` を使うのが良いでしょう。
### 【参考】ViewModelProvider クラスを使う
ドキュメントには `by viewModels()` もしくは [ViewModelProvider()](https://developer.android.com/reference/androidx/lifecycle/ViewModelProvider) を使って。とかいてあります。
こちらは、`ViewModelProviders.of()` とほぼ同様なので、簡単に使えるかと思います。
```kotlin
// ViewModelProviders.of(this) とほぼ同じように使える
val myViewModel= ViewModelProvider(this).get(MyViewModel::class.java)
```
</pre>
<div class='barenote_ref_list'></div>Yusukehttp://www.blogger.com/profile/01684623000819625562noreply@blogger.com0tag:blogger.com,1999:blog-6154025142032985882.post-81284195754268687702020-03-07T09:51:00.000+09:002020-03-07T09:51:25.600+09:00Android Q で外部ストレージのファイルが開けなくなった<pre class="marked">
Android Pi (API Level 28) まで正しく動いていたアプリが、Android Q 上で動作させると動かなくなってしまいました。
具体的には、External Storage にファイルを書き込もうとすると、以下のような例外が発生してしまうのです。
`java.io.FileNotFoundException: /storage/emulated/0/202003061644.zip: open failed: EACCES (Permission denied)`
Android Pi までは動作していたので、当然 `WRITE_EXTERNAL_STORAGE` と `READ_EXTERNAL_STORAGE` パーミッションは適切に<sup><span class='barenote'>静的にも動的にも</span></sup>取得しています。
<a name='more'></a>
### Android Q から External Storage のアクセス権が厳しくなった
調べてみると、Android Q からは External Storage に直接アクセス出来るエリアが限定されたようです。
具体的には [getExternalFilesDir()](https://developer.android.com/reference/android/content/Context#getExternalFilesDir(java.lang.String)) で取得されるディレクトリ以外はパス指定で読み書き出来ません<sup><span class='barenote'>その代わり、パーミッションは必要なくなった</span></sup>。
> 参考
>
> [Exception 'open failed: EACCES (Permission denied)' on Android - Stack Overflow](https://stackoverflow.com/questions/8854359/exception-open-failed-eacces-permission-denied-on-android)
>
> [対象範囲別外部ストレージ アクセスを管理する | Android デベロッパー | Android Developers](https://developer.android.com/training/data-storage/files/external-scoped)
### 解決策(暫定)
以下のフラグを `AndroidManifest.xml" に書いておくと、API 28 までと同等のアクセス権に変わるとのこと。
```xml
`title: "AndroidManifest.xml";
<application
android:requestLegacyExternalStorage="true"
```
しかし、これは暫定対応です。
来年(2020年)以降リリースのバージョンではこのフラグは動かないらしいです。
### 解決策
そもそも External Storage を使わないか、`getExternalFilesDir()`を使うようにするしかないと思われます。
なお、Media 系と Download ディレクトリは別途アクセスする方法が提供されています。
詳しくは上記、公式サイトをご参考ください。
</pre>
<div class='barenote_ref_list'></div>Yusukehttp://www.blogger.com/profile/01684623000819625562noreply@blogger.com0tag:blogger.com,1999:blog-6154025142032985882.post-15264336891386891862020-03-06T19:44:00.001+09:002020-03-06T19:45:54.414+09:00VS Code のターミナルで Ctrl + p を使えるようにする<pre class="marked">
[Visual Studio Code](https://code.visualstudio.com/) がどんどん便利になってきてます。
最近、[Remote Development](https://github.com/Microsoft/vscode-remote-release) を入れたのですが、遠隔マシン上のコードがさもローカルにあるかのように扱えて重宝しています。
しかし、そうなると気になるのがターミナルの使い勝手。
Remote 中は付属ターミナルの方が圧倒的に便利<sup><span class='barenote'>既にssh接続済みのターミナルが複数作れる</span></sup>なので、別途ターミナルを開くのは、あり得なく感じます。
ところが一つ問題が。Ctrl + p を押すと、"Go to File" 機能が働いて、ターミナルからフォーカスが外れてしまうのです。
<a name='more'></a>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyIuqO2dYAm9Wf9rUE2xzwpfFqWQZjHFVb1jMr_tWWT1kQKWERJl9yhX5oUrXbYVcsUBbx5sAktZ97a_H9bp9I0k6Jc2YpazMNqOADgRrtpS5lS9M5OIDZTX_xlb286ADPvAAClwR3ugs/s1600/001.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyIuqO2dYAm9Wf9rUE2xzwpfFqWQZjHFVb1jMr_tWWT1kQKWERJl9yhX5oUrXbYVcsUBbx5sAktZ97a_H9bp9I0k6Jc2YpazMNqOADgRrtpS5lS9M5OIDZTX_xlb286ADPvAAClwR3ugs/s640/001.png" width="640" height="135" data-original-width="848" data-original-height="179" /></a></div>
私の使っている [ZSH](https://www.zsh.org/) は Ctrl + p が非常に強力で、これを使わないでシェル作業することは考えられません。
何か回避策はあるだろうと探してみたら、公式サイトにちゃんと書いてありました。
> 参考
>
> [Integrated Terminal in Visual Studio Code](https://code.visualstudio.com/docs/editor/integrated-terminal#_forcing-key-bindings-to-pass-through-the-terminal)
`Ctrl + b`, `Ctrl + k`, `Ctrl + f` を使えるようにする方法も同様に載っています。
これらを修正するとかなり便利になりました<sup><span class='barenote'>個人的にはデフォルト設定にしちゃえばいいのにと思う</span></sup>。
### VS Code の Settings から変更する方法
Settings を開き(Ctrl + ,)、"Search settings" に `terminal.integrated.commandsToSkipShell` と入力します。
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZfUwWzxIga_zfkOxYMV7l3p1D5hunMBjF1PjOCZmrIwGJHStse7nUkmuqY9gv1YNrzwIxakxdCZiEWY0i-Eh0QlJ9uZAeP-jAYOWyHnuKINRAPZbku-sTeTu5KOWKzNdjQS9WUr5BBAA/s1600/002.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZfUwWzxIga_zfkOxYMV7l3p1D5hunMBjF1PjOCZmrIwGJHStse7nUkmuqY9gv1YNrzwIxakxdCZiEWY0i-Eh0QlJ9uZAeP-jAYOWyHnuKINRAPZbku-sTeTu5KOWKzNdjQS9WUr5BBAA/s640/002.png" width="640" height="375" data-original-width="967" data-original-height="567" /></a></div>
Add Item を押して、`-workbench.action.quickOpen` を追加すればOK。
### 設定ファイルを直接変更する方法
[Settings files locations](https://code.visualstudio.com/docs/getstarted/settings#_settings-file-locations) を参考に設定ファイルの場所を調べ、開きます。
以下の項目を追加すればOK。
```js
{
"terminal.integrated.commandsToSkipShell": [
"-workbench.action.quickOpen",
]
}
```
</pre>
<div class='barenote_ref_list'></div>Yusukehttp://www.blogger.com/profile/01684623000819625562noreply@blogger.com0tag:blogger.com,1999:blog-6154025142032985882.post-15508596111254377002019-11-21T13:58:00.000+09:002019-11-21T13:58:34.159+09:00electron v7 以降は Raspberry Pi にデフォルトでインストールできない<pre class="marked">
Raspberry Pi で electron をインストールしようとすると以下のように落ちてしまいました。
```
> electron@7.1.2 postinstall /home/pi/test/node_modules/electron
> node install.js
(node:5861) UnhandledPromiseRejectionWarning: HTTPError: Response code 404 (Not Found)
at EventEmitter.<anonymous> (/home/pi/test/node_modules/got/source/as-stream.js:35:24)
at EventEmitter.emit (events.js:210:5)
at module.exports (/home/pi/test/node_modules/got/source/get-response.js:22:10)
at ClientRequest.handleResponse (/home/pi/test/node_modules/got/source/request-as-event-emitter.js:155:5)
at Object.onceWrapper (events.js:300:26)
at ClientRequest.emit (events.js:215:7)
at ClientRequest.origin.emit (/home/pi/test/node_modules/@szmarczak/http-timer/source/index.js:37:11)
at HTTPParser.parserOnIncomingClient [as onIncoming] (_http_client.js:583:27)
at HTTPParser.parserOnHeadersComplete (_http_common.js:115:17)
at TLSSocket.socketOnData (_http_client.js:456:22)
(node:5861) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:5861) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
npm WARN saveError ENOENT: no such file or directory, open '/home/pi/test/package.json'
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN enoent ENOENT: no such file or directory, open '/home/pi/test/package.json'
npm WARN test No description
npm WARN test No repository field.
npm WARN test No README data
npm WARN test No license field.
+ electron@7.1.2
added 87 packages from 91 contributors and audited 104 packages in 22.813s
found 0 vulnerabilities
```
<a name='more'></a>
### 対策
先に対処方法を書いておくと、以下のように `--arch=armv7l` を指定してインストールすれば大丈夫。
```bash
`gutter: false;
$ npm install --arch=armv7l electron
```
もしくは、`~/.npmrc` に以下を追加しておけばOK。
```text
`title: "~/.npmrc";
arch=armv7l
```
> 参考
>
> [Error installing on RPi4 · Issue #20723 · electron/electron](https://github.com/electron/electron/issues/20723)
### 原因
`install.js` の中でリリースファイルをダウンロードしているのですが、ここで `process.arch` を使っています。この値は `arm` になるので、
`https://github.com/electron/electron/releases/download/v7.1.2/electron-v7.1.2-linux-arm.zip` というファイルをダウンロードしようとします。
しかし、アップロードされているファイルは `electron-v7.1.2-linux-armv7l.zip` です。(`arm` ではなく `armv7l`)
electron 7.1.2
```javascript
`title: "electron/install.js"; first-line: 31; highlight: 37;
downloadArtifact({
version,
artifactName: 'electron',
force: process.env.force_no_cache === 'true',
cacheRoot: process.env.electron_config_cache,
platform: process.env.npm_config_platform || process.platform,
arch: process.env.npm_config_arch || process.arch
}).then((zipPath) => extractFile(zipPath)).catch((err) => onerror(err))
```
electron v6 ではどうだったかと言うと、以下のように `process.arch` を使っていません。
electron 6.1.4
```javascript
`title: "electron/install.js"; first-line: 30; highlight: 35;
// downloads if not cached
download({
cache: process.env.electron_config_cache,
version: version,
platform: process.env.npm_config_platform,
arch: process.env.npm_config_arch,
strictSSL: process.env.npm_config_strict_ssl === 'true',
force: process.env.force_no_cache === 'true',
quiet: process.env.npm_config_loglevel === 'silent' || process.env.CI
}, extractFile)
```
`process.env.npm_config_arch` が `undefined` の場合、`download()` の中で `process.arch` を `getNodeArch()` という関数で変換します。
以下のように、`arm` は 適切なアーキテクチャに変換されるため、v6 までは正しく動いていたようです。
```javascript
function getNodeArch(arch) {
if (arch === 'arm') {
switch (process.config.variables.arm_version) {
case '6':
return uname();
case '7':
return 'armv7l';
default:
break;
}
}
return arch;
}
```
> 参考
>
> [electron/get: Download Electron release artifacts](https://github.com/electron/get)
</pre>
<div class='barenote_ref_list'></div>Yusukehttp://www.blogger.com/profile/01684623000819625562noreply@blogger.com0tag:blogger.com,1999:blog-6154025142032985882.post-58216731410996724412019-11-19T08:21:00.000+09:002019-11-19T08:21:06.760+09:00node-ffi 公式は node 12 でビルドが通らない<pre class="marked">
node から直接Cのライブラリを呼び出せる [ffi](https://www.npmjs.com/package/ffi) というライブラリを
インストールしようとしたところ、以下のようなエラーが出てインストール出来ませんでした。
```bash
`gutter: false;
$ npm install --save ffi
> ref@1.3.5 install /home/yusuke/ffi_sample/node_modules/ref
> node-gyp rebuild
make: Entering directory '/home/yusuke/tmp/node_modules/ref/build'
CXX(target) Release/obj.target/binding/src/binding.o
../src/binding.cc: In function ‘Nan::NAN_METHOD_RETURN_TYPE {anonymous}::WriteObject(Nan::NAN_METHOD_ARGS_TYPE)’:
../src/binding.cc:222:43: error: no matching function for call to ‘v8::Value::BooleanValue()’
bool persistent = info[3]->BooleanValue();
... 省略
gyp ERR! build error
gyp ERR! stack Error: `make` failed with exit code: 2
gyp ERR! stack at ChildProcess.onExit (/usr/lib/node_modules/npm/node_modules/node-gyp/lib/build.js:194:23)
gyp ERR! stack at ChildProcess.emit (events.js:210:5)
gyp ERR! stack at Process.ChildProcess._handle.onexit (internal/child_process.js:272:12)
```
依存している [ref](https://www.npmjs.com/package/ref) というライブラリのビルドに失敗しているようです。
この `v8::Value::BooleanValue()` 等の参照関数はだいぶ前から deprecated だったようです。
そして、node v.12 でついに削除されました。
そのため、node v.12 以降ではビルドが通らなくなってしまったのです。
### 解決策(一時的な)
幸いなことに、この問題は既に PullRequest として報告されていました。
<a name='more'></a>
> 参考
>
> [Support Node v12.0.0 by lxe · Pull Request #114 · TooTallNate/ref · GitHub](https://github.com/TooTallNate/ref/pull/114)
ところが、この修正にも問題があって、この修正に対する PullRequest も上がっています<sup><span class='barenote'>そういう場合、本線にはどうマージされるんだろう…?</span></sup>。
そして、`ref` だけでなく `ffi` 自体と関連するライブラリにも同じ問題があり、修正が必要です。
> 参考
>
> [Support Node v12.0.0 by lxe · Pull Request #544 · node-ffi/node-ffi · GitHub](https://github.com/node-ffi/node-ffi/pull/544)
とまぁ、たらい回しに探していった結果、[fishg/node-ffi](https://github.com/fishg/node-ffi) 上の修正が要件をみたしているようです。
レポ指定<sup><span class='barenote'>github の場合、ホスト名等を省略できる。知らなかった…</span></sup>でインストールすれば使えます。
```bash
`gutter: false;
$ npm install "ffi@fishg/node-ffi.git#node-12"
```
しかし、[ref](https://github.com/TooTallNate/ref) や [ffi](https://github.com/TooTallNate/ref) はどちらも1年近くメンテナンスされていません。
この node v.12 に対応する PullRequest も半年以上放置。
正式に対応する日はくるのでしょうか?
そして、deprecated な関数をかなり呼んでます。
つまり、また node のバージョンが上がったらビルドが通らなくなる可能性大。
</pre>
<div class='barenote_ref_list'></div>Yusukehttp://www.blogger.com/profile/01684623000819625562noreply@blogger.com0tag:blogger.com,1999:blog-6154025142032985882.post-59847138875527945652019-10-26T08:06:00.000+09:002019-10-26T08:06:23.662+09:00Electron のデフォルトでは Spectron が正しく動作しない<pre class="marked">
@Electron 7.0.0
@Spectron 9.0.0
[electron-quick-start](https://github.com/electron/electron-quick-start) に [mocha](https://mochajs.org/) と [spectron](https://electronjs.org/spectron) をインストールして、簡単なテストを走らせてみました。
すると、以下のようなエラーが。
```
1) Application launch
shows an initial window:
TypeError: waitUntilWindowLoaded Cannot read property 'isLoading' of undefined
at waitUntil(<Function>, ) - application.js:263:17
```
実際に走らせたのは以下のコードです。
<a name='more'></a>
```js
`highlight: 25;
const Application = require('spectron').Application
const assert = require('assert')
const electronPath = require('electron')
const path = require('path')
describe('Application launch', function () {
this.timeout(10000)
beforeEach(function () {
this.app = new Application({
path: electronPath,
args: [path.join(__dirname, '..')]
})
return this.app.start()
})
afterEach(function () {
if (this.app && this.app.isRunning()) {
return this.app.stop()
}
})
it('shows an initial window', function () {
// 以下の waitUntilWindowLoaded() 内で落ちてる
return this.app.client.waitUntilWindowLoaded().getWindowCount().then(function (count) {
assert.equal(count, 1)
})
})
})
```
`waitUntilWindowLoaded()` 内で問題が起こっているようですが、このコードは `Spectron` の公式ページに書かれているものです。
`waitUntilWindowLoaded()` の使用方法に問題があるとは思えません<sup><span class='barenote'>公式に書いてあっても、ドキュメントが更新されてなく使えないということは多々あるけど…</span></sup>。
### Electron の設定が原因
この問題、実は [Spectron の README.md](https://github.com/electron-userland/spectron#node-integration) にしれっと書いてありました。
> The Electron helpers provided by Spectron require accessing the core Electron APIs in the renderer processes of your application. So, either your Electron application has nodeIntegration set to true or you'll need to expose a require window global to Spectron so it can access the core Electron APIs.
`nodeIntegration` が `false` だと Spectron は正しく動かないとのこと。
そして、この `nodeIntegration`、Electron 5 以降ではデフォルト `false` なのです。(理由は後述)
このことは、公式の [Issue#174](https://github.com/electron-userland/spectron/issues/174) にもあがっていて、他にも `app.electron` や `app.webContents` が `undefined` になってしまう問題があるようです。
実際、`app.webContents.executeJavaScript('1 + 2')` を呼んでみたところ、エラーになりました。
```
1) Application launch
shows an initial window:
TypeError: Cannot read property 'executeJavaScript' of undefined
at Context.<anonymous> (test/spec.js:24:35)
at <anonymous>
at process._tickDomainCallback (internal/process/next_tick.js:228:7)
```
### 対策
`nodeIntegration` を `true` にすれば動作します。
しかし、常に `true` にしてしまうとセキュリティリスクがあるので、以下のようにテストの時だけ `true` になるようにするのが良いかと思います。
```js
mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: (process.env.NODE_ENV === 'test')
}
})
```
私の環境では `NODE_ENV` 環境変数は自動で `test` に設定されました。
ただ、これは環境依存だと思うので `package.json` に以下のように書いておくと安心です。
```js
`title: "package.json";
"scripts": {
"test": "NODE_ENV=test mocha"
},
```
私の環境では公式に載っている `preload.js` 内で `window.electronRequire` を設定する方法は動きませんでした。何でだろう…
### なぜ、デフォルト false になったのか
`nodeIntegration` が `true` だと Renderer プロセスの中から Node.js の API にアクセス出来るようになります。
つまり、Renderer からローカルのファイルやその他のローカルリソースに直接アクセスできてしまうのです。
もし、この状態で悪意のある外部コンテンツを実行すると、とても危険です。
そのため、`nodeIntegration` はデフォルト `false` になったのだと思われます。
セキュリティを高めたのは良いけれど、テスト時と実行時の動作環境があまりに異なってしまうのはどうなんだろう…
</pre>
<div class='barenote_ref_list'></div>Yusukehttp://www.blogger.com/profile/01684623000819625562noreply@blogger.com0tag:blogger.com,1999:blog-6154025142032985882.post-6374379325376613612019-09-11T09:11:00.000+09:002019-09-11T09:11:02.483+09:00[SQL] ウィンドウ関数で累積和を計算する方法<pre class="marked">
| id | date | profit |
|--:|-----------:|-----:|
| 0 | 2019-03-01 | 1000 |
| 1 | 2019-03-04 | 500 |
| 2 | 2019-04-05 | -300 |
| 3 | 2019-05-01 | 100 |
上記 table1 から profit の累積和(cumlative_profit)を求める QUERY は以下<sup><span class='barenote'>私は MariaDB 10.3.10 で動作確認しましたが、「達人に学ぶSQL徹底指南書 第2版」によると、主要なDMBSで動作するとのこと。</span></sup>。
```sql
SELECT *,
SUM(profit) OVER W AS cumulative_profit
FROM table1
WINDOW W AS (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
ORDER BY date;
```
これは、以下のように書いたのと同等。
```sql
SELECT *,
SUM(profit) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative_profit
FROM table1
ORDER BY date;
```
最近読み終わったのだけど、SQL に関しては以下の本が秀逸。
もちろん、ウィンドウ関数の使い方もとてもわかりやすく書いてある。
お勧め。
<div class="separator" style="clear: both; text-align: center;">
<iframe style="width:120px;height:240px;" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" src="https://rcm-fe.amazon-adsystem.com/e/cm?ref=qf_sp_asin_til&t=team00-22&m=amazon&o=9&p=8&l=as1&IS2=1&detail=1&asins=4798157821&linkId=34109bcb5906b251e857c46b66a71a01&bc1=000000<1=_blank&fc1=333333&lc1=0066c0&bg1=ffffff&f=ifr">
</iframe>
</div>
</pre>
<div class='barenote_ref_list'></div>Yusukehttp://www.blogger.com/profile/01684623000819625562noreply@blogger.com0tag:blogger.com,1999:blog-6154025142032985882.post-49622856576578830982019-06-24T08:29:00.000+09:002019-06-24T08:29:00.187+09:00io.grpc.ManagedChannelProvider$ProviderNotFoundException が発生した時の対処方法<pre class="marked">
[Google Photos API](https://developers.google.com/photos/) の Java クライアントライブラリを使おうとしたところ、以下のようなエラーが出てしまいました。
```
io.grpc.ManagedChannelProvider$ProviderNotFoundException: No functional channel service provider found. Try adding a dependency on the grpc-okhttp, grpc-netty, or grpc-netty-shaded artifact
```
> Try adding a dependency on the grpc-okhttp, grpc-netty, or grpc-netty-shaded artifact
とのことなので `ManagedChannelProvider` の実装が必要なことはわかりました。
が、「どうやって指定するのよ…」と悩むこと小一時間。
<a name='more'></a>
### 解決方法
結論から言うと、特に Java のコードですることはなく、依存ライブラリを指定してやればよいだけでした。
例えば、gradle なら以下の1行を追加するだけです<sup><span class='barenote'>grpc-netty 等でも大丈夫だと思いますが、未確認です</span></sup>。
```groovy
`highlight: 2;
dependencies {
implementation 'io.grpc:grpc-okhttp:1.21.0'
}
```
結局、エラーメッセージの指示通りにすれば良かったというオチでした。
### リフレクションを使ってた
[ManagedChannelProvider](https://github.com/grpc/grpc-java/blob/v1.21.x/api/src/main/java/io/grpc/ManagedChannelProvider.java) のコードを見ると、`ManagedChannelProvider` の実装を実行時に探していることがわかります。
```java
`first-line: 37;
private static final ManagedChannelProvider provider = ServiceProviders.load(
ManagedChannelProvider.class,
HARDCODED_CLASSES,
ManagedChannelProvider.class.getClassLoader(),
new PriorityAccessor<ManagedChannelProvider>() {
@Override
public boolean isAvailable(ManagedChannelProvider provider) {
return provider.isAvailable();
}
@Override
public int getPriority(ManagedChannelProvider provider) {
return provider.priority();
}
});
```
### 最適化している場合は注意
上記のようにリフレクションを使っているので、
ProGuard 等で最適化している場合は grpc-okhttp を最適化対象から外しておかないといけません。
</pre>
<div class='barenote_ref_list'></div>Yusukehttp://www.blogger.com/profile/01684623000819625562noreply@blogger.com0tag:blogger.com,1999:blog-6154025142032985882.post-4756473274778568842019-06-08T07:50:00.000+09:002019-06-08T07:50:02.697+09:00Windows に python-lzo をインストール<pre class="marked">
@Windows 10 Pro 64bit
[LZO](http://www.oberhumer.com/opensource/lzo/) というアーカイブ系ライブラリを Python でラップしてくれるパッケージに
[python-lzo](https://pypi.org/project/python-lzo/) があります。
Linux では pip で一発インストール可能なのですが、Windows ではエラーが出てしまいインストールできません。
```shell
`gutter: false;
> pip install python-lzo
Collecting python-lzo
Using cached https://files.pythonhosted.org/packages/af/60/41f17f56c920a956f1d4b9f04f9755c045b2b06b9dd933b33cdd37ab9fd7/python-lzo-1.12.tar.gz
ERROR: Complete output from command python setup.py egg_info:
ERROR: Traceback (most recent call last):
File "<string>", line 1, in <module>
File "C:\Users\yusuk\AppData\Local\Temp\pip-install-jzhjob69\python-lzo\setup.py", line 46, in <module>
raise Exception("please set LZO_DIR to where the lzo source lives")
Exception: please set LZO_DIR to where the lzo source lives
```
公式の Issue によると、これは仕様で、自身でコンパイルしないといけないようです。
> 参考
>
> [unable to install python-lzo using "pip install python-lzo" on Windows · Issue #5 · jd-boyd/python-lzo · GitHub](https://github.com/jd-boyd/python-lzo/issues/5)
<a name='more'></a>
というわけで、やってみました。
### ソースコードをダウンロード
[Download](http://www.oberhumer.com/opensource/lzo/#download) よりソースコードをダウンロードします。
今回は lzo-2.10.tar.gz をダウンロードしてきました。
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrM4c-cFpvytMddG50LwtEX2gDqDfUdJ7cfBcse1_YnJJTUmuYHzm_inclNOhxv1odeciA2zxJYeFv8SltGg8ugkKz59_y2M5HPzvIJgz4jCvMOkVFE_F-al1GT2DWnddb43VHbraDBgY/s1600/003.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrM4c-cFpvytMddG50LwtEX2gDqDfUdJ7cfBcse1_YnJJTUmuYHzm_inclNOhxv1odeciA2zxJYeFv8SltGg8ugkKz59_y2M5HPzvIJgz4jCvMOkVFE_F-al1GT2DWnddb43VHbraDBgY/s1600/003.png" data-original-width="219" data-original-height="139" /></a></div>
### 展開
先のファイルを展開します。
なお、以後のコマンドライン操作は "x64_x86 Cross Tools Command Prompt for VS 2019" 上で行っています<sup><span class='barenote'>x86 系の人は "x86 Native..." を使えばよいはず</span>, <span class='barenote'>VS 2019 じゃなくても大丈夫だと思います</span>, <span class='barenote'>Visual Studio を使わないビルド方法も提供されています</span></sup>。
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWr-1ic6ulVSg6hKtOpKuX6zkCt4mUjvHnv9VW92KxMmInlzNneO7JMgXp7GqtIX8wpKsZ3sq__ZEs8I4dTuui2Ymq7rpCzoHRvgoegDvYGsChfByPEbj7iYNn48edsiILfI3EHiL0tf8/s1600/001.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWr-1ic6ulVSg6hKtOpKuX6zkCt4mUjvHnv9VW92KxMmInlzNneO7JMgXp7GqtIX8wpKsZ3sq__ZEs8I4dTuui2Ymq7rpCzoHRvgoegDvYGsChfByPEbj7iYNn48edsiILfI3EHiL0tf8/s1600/001.png" data-original-width="383" data-original-height="203" /></a></div>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6frJfcbsB69TffIlDQTRKQ82jqGpbEzELgwdDDAIG2aqs8_gzRuMxn0HvlX7VYUg1G2GvuIEJ3PBLHiT38mEny4zyxoCY46r8Mdo9geQCCZ1RDwgNlyfzpuvSOaBFA9qc2ZGlH3COD3Y/s1600/002.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6frJfcbsB69TffIlDQTRKQ82jqGpbEzELgwdDDAIG2aqs8_gzRuMxn0HvlX7VYUg1G2GvuIEJ3PBLHiT38mEny4zyxoCY46r8Mdo9geQCCZ1RDwgNlyfzpuvSOaBFA9qc2ZGlH3COD3Y/s640/002.png" width="640" height="335" data-original-width="979" data-original-height="512" /></a></div>
ダウンロードしたフォルダに移動して、展開します。
```shell
`gutter: false;
> cd %HOMEPATH%\Downloads
> tar xzf lzo-2.10.tar.gz
```
現在では Windows で `tar` が使えるので、これを使わない手はないと思いますが、もちろん [7-Zip](https://sevenzip.osdn.jp/) 等その他のツールを使っても大丈夫なはずです。
> 参考
>
> [Windows その14 - Windows上でcurlとtarコマンドが利用可能に - kledgeb](https://kledgeb.blogspot.com/2017/12/windows-14-windowscurltar.html)
### ビルド
Linux 以外のビルド方法は "B\00README.TXT" に書かれています。
基本的には対象のバッチファイルを実行すればよいだけです。
```shell
`gutter: false;
> cd lzo-2.10
> .\B\win32\vc.bat
...
//
// Building LZO was successful. All done.
```
### pip install
環境変数 `LZO_DIR` に先ほどビルドを実行したフォルダを指定します。
あとは、仮想環境なりユーザー環境なりに `pip install` で完了です。
```shell
> LZO_DIR=%CD%
> pip install python-lzo
Collecting python-lzo
Using cached https://files.pythonhosted.org/packages/af/60/41f17f56c920a956f1d4b9f04f9755c045b2b06b9dd933b33cdd37ab9fd7/python-lzo-1.12.tar.gz
Installing collected packages: python-lzo
Running setup.py install for python-lzo ... done
Successfully installed python-lzo-1.12
```
### 64bit 系の人は注意
"python-lzo-1.12" は 32bit 系のバイナリを想定しているようです。
64bit の Native コンパイラでコンパイルすると、うまくいかないので注意が必要です。
</pre>
<div class='barenote_ref_list'></div>Yusukehttp://www.blogger.com/profile/01684623000819625562noreply@blogger.com0tag:blogger.com,1999:blog-6154025142032985882.post-72190440849587199462019-05-16T23:22:00.002+09:002019-06-06T23:40:26.584+09:00VSCode で Ctrl が Ctrl+CapsLock になってしまう場合の対処方法<pre class="marked">
Ubuntu 18.04 LTS で [Visual Studio Code](https://code.visualstudio.com) を使っていたところ、Ctrl + S を押してもセーブが出来ませんでした。
ステータスバーを見てみると "(Ctrl+CapsLock) was pressed" の文字が。
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyjJHheOT2oANrkDQmcw1EcvpkwQfPHkt7L3V4IWHzcEMAa51tudFUn1wKm5YFpTBYfeArjpfj7cSuIWfJyH_ti98tt5oonpGNFFn8iVo22My5PI2PYDWkULuqFzelWQRNXsC3OpfbJdM/s1600/001.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyjJHheOT2oANrkDQmcw1EcvpkwQfPHkt7L3V4IWHzcEMAa51tudFUn1wKm5YFpTBYfeArjpfj7cSuIWfJyH_ti98tt5oonpGNFFn8iVo22My5PI2PYDWkULuqFzelWQRNXsC3OpfbJdM/s1600/001.png" data-original-width="425" data-original-height="69" /></a></div>
<a name='more'></a>
私は CapsLock キーを Ctrl キーとして使っているので、そのせいだと思われます。
ただ、他のアプリケーションではそのようなことは起こっていません。
調べてみると、既に問題としては認識されていて、解決策も提示されていました。
> 参考
>
> [Keyboard mappings with `setxkbmap` on Linux not working · Issue #23991 · microsoft/vscode · GitHub](https://github.com/Microsoft/vscode/issues/23991#issuecomment-292336504)
具体的には `settings.json` に `"keyboard.dispatch": "keyCode"` を加えて、VSCode を再起動します。
```js
`title: "$HOME/.config/Code/User/settings.json"; gutter: false; highlight: 2;
{
"keyboard.dispatch": "keyCode"
}
```
なお、`settings.json` の場所は現時点<sup><span class='barenote'>VSCode 1.33.1</span></sup>では `$HOME/.config/Code/User/settings.json` です。
> 参考
>
> [Visual Studio Code User and Workspace Settings](https://code.visualstudio.com/docs/getstarted/settings#_settings-file-locations)
この問題、中で使っているキーマップに原因があるようです。
そのキーマップに変更があった場合、また別の不具合が起こる可能性がありますね。
</pre>
<div class='barenote_ref_list'></div>Yusukehttp://www.blogger.com/profile/01684623000819625562noreply@blogger.com0tag:blogger.com,1999:blog-6154025142032985882.post-52910532226350374852019-05-14T23:15:00.004+09:002019-05-14T23:17:09.907+09:00Ubuntu 18.04 でキーボードレイアウトが突然変わってしまった<pre class="marked">
先日のアップデート後だと思うのですが、キーボードレイアウトが英字キーボードになってしまいました。
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaP6MF8npq-4JblSTNbZs1ANq4Ndwf6js5N2v17ElUNvVHlfULPDobdfi8c6q3MzWnQgHdqti6TvkpOeWN37rWOx0M0n3AQHD2ztRzqyonZX-kiu6cowtdaHugIKxpEg2HjV3-DQpkal0/s1600/%253F_003.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaP6MF8npq-4JblSTNbZs1ANq4Ndwf6js5N2v17ElUNvVHlfULPDobdfi8c6q3MzWnQgHdqti6TvkpOeWN37rWOx0M0n3AQHD2ztRzqyonZX-kiu6cowtdaHugIKxpEg2HjV3-DQpkal0/s640/%253F_003.png" width="640" height="282" data-original-width="1464" data-original-height="646" /></a></div>
Mozc の `Show Keyboard Layout` で確認してみると、確かに英字キーボードになっています。
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiu-fy2W2nwCA-59fprFimjbEBSC7_JxaDIGA0UxaI4HzlrkymHUtrXJTDBtDsdIOWFydg3Qi3g7VWqNoCfA3yBmLFkD7Z9PoZW4dR8O7JnRv7QS0b8_gH0Tpc13YHqM26sd3Ou29Q_q4w/s1600/Workspace+1_002.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiu-fy2W2nwCA-59fprFimjbEBSC7_JxaDIGA0UxaI4HzlrkymHUtrXJTDBtDsdIOWFydg3Qi3g7VWqNoCfA3yBmLFkD7Z9PoZW4dR8O7JnRv7QS0b8_gH0Tpc13YHqM26sd3Ou29Q_q4w/s1600/Workspace+1_002.png" data-original-width="229" data-original-height="430" /></a></div>
<a name='more'></a>
### Mozc の設定を修正する
以下の `default` の文字を
```xml
`title: "/usr/share/ibus/component/mozc.xml"; first-line:19;
<layout>default</layout>
```
`jp` に変更。
```xml
`title: "/usr/share/ibus/component/mozc.xml"; first-line:19;
<layout>jp</layout>
```
その後、ログオフ/ログオンで復活しました。
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLKtiTe2A2TIvVLx8ptKDBRFp4VZdg5Sqbtg6vwf113-AHTwwWwVYryZg86y_ZgsJsY-15S5otEp_hHRUhSkHKzY6txoD7nJlRlQf38ZYKUUMxaM-0U_o0vQH2aLtifER-czcC2NoLl9w/s1600/Japanese_004.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLKtiTe2A2TIvVLx8ptKDBRFp4VZdg5Sqbtg6vwf113-AHTwwWwVYryZg86y_ZgsJsY-15S5otEp_hHRUhSkHKzY6txoD7nJlRlQf38ZYKUUMxaM-0U_o0vQH2aLtifER-czcC2NoLl9w/s640/Japanese_004.png" width="640" height="282" data-original-width="1464" data-original-height="646" /></a></div>
### dpkg-reconfigure とは作用している項目が違う
"Ubuntu キーボードレイアウト" 等で検索すると、大抵 `sudo dpkg-reconfigure keyboard-configuration` を使う方法が紹介されています。
これはシステム全体のキーボードレイアウトに作用します。
具体的には `/etc/default/keyboard` を変更します。
```text
`title: "/etc/default/keyboard";
BACKSPACE="guess"
XKBMODEL="pc105"
XKBLAYOUT="jp"
XKBVARIANT=""
XKBOPTIONS=""
```
私の経験上、IME (Mozc) かこちらのどちらかが原因でキーボードレイアウトがおかしくなります。
上記の Mozc の修正で直らない場合はこちらも確認してみると良いでしょう<sup><span class='barenote'>IME に Mozc 以外を使っている場合は、定かではありません</span></sup>。
> 参考
>
> [Ubuntu日本語フォーラム / キーボードレイアウトを設定してもすぐに戻ってしまう](https://forums.ubuntulinux.jp/viewtopic.php?pid=112097)
</pre>
<div class='barenote_ref_list'></div>Yusukehttp://www.blogger.com/profile/01684623000819625562noreply@blogger.com0tag:blogger.com,1999:blog-6154025142032985882.post-101114208502283412019-04-03T00:31:00.000+09:002019-04-03T00:31:12.593+09:00コマンドラインから Android emulator を起動しようとしたら "Missing emulator engine ..." が出てしまった場合の対処方法<pre class="marked">
emulator 28.0.25.0 (build_id 5395263) on Ubuntu 18.04
久しぶりにコマンドラインから Emulator を起動してみると以下のようなエラーが。(Android Studio から起動した場合は問題なく起動しています)
```console
`gutter: false;
$ emulator -avd Pixel_2_API_28
PANIC: Missing emulator engine program for 'x86' CPU.
```
調べてみると、2017年3月の v25.3.0 以降、emulator コマンドのパスが変わったらしい<sup><span class='barenote'>2年もコマンドラインから Emulator 起動してなかったのか</span></sup>。
<a name='more'></a>
> 参考
>
> [Update Your Path For The New Android Emulator Location | Stuart Kent on Software](https://www.stkent.com/2017/08/10/update-your-path-for-the-new-android-emulator-location.html)
確認してみると、確かに `${ANDROID_HOME}/tools/emulator` 以下にパスが通っていました。
```console
`gutter: false;
$ which emulator
${ANDROID_HOME}/tools/emulator
```
というわけで、フルパス指定にしたところ、問題なく起動しました。
パスもアップデートして、問題解決です。
```console
`gutter: false;
$ ${ANDROID_HOME}/emulator/emulator -avd Pixel_2_API_28
```
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhiSL1SYXaFOe6T0lAWErThX9GYA-qv1AnUbnZk0FobMZCUW92jsQRkbZdDz5VAIOhulPUCca4H0o2DBpNerP1D6btySswd-iTkcs6ts5nQFT_avaIE3MTb2Zl08mW2I3gJERSJ_EtE_nY/s1600/Selection_002.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhiSL1SYXaFOe6T0lAWErThX9GYA-qv1AnUbnZk0FobMZCUW92jsQRkbZdDz5VAIOhulPUCca4H0o2DBpNerP1D6btySswd-iTkcs6ts5nQFT_avaIE3MTb2Zl08mW2I3gJERSJ_EtE_nY/s640/Selection_002.png" width="362" height="640" data-original-width="517" data-original-height="915" /></a></div>
参考リンクの考察によると、tools 以下のバイナリが問題なのではなく、ディレクトリ構造に起因した問題のよう。
時間ができたら android sdk 等々、クリーンインストールして確認してみよう<sup><span class='barenote'>いつになることやら…</span></sup>。
</pre>
<div class='barenote_ref_list'></div>Yusukehttp://www.blogger.com/profile/01684623000819625562noreply@blogger.com0tag:blogger.com,1999:blog-6154025142032985882.post-15766010543044948382019-04-02T08:49:00.000+09:002019-04-02T08:49:04.536+09:00[Python] venv のプロンプトを任意のものに変更する<pre class="marked">
@Python 3.6.7
Python2 系も使うことが無くなってきたので、遅ればせながら [virtualenv](https://virtualenv.pypa.io/en/latest/) から [venv](https://docs.python.org/ja/3/library/venv.html) に移行してみました。
調べてみると、venv では virtualenvwrapper のような一元管理を使うのではなく、プロジェクト直下に `venv` というディレクトリを作って管理したほうが良いと書いてあるサイトがちらほら。
> 参考
>
> [venv: Python 仮想環境管理 - Qiita](https://qiita.com/fiftystorm36/items/b2fd47cf32c7694adc2e)
<a name='more'></a>
その方針自体は良い<sup><span class='barenote'>virtualenvwrapper を使っている時も、環境とプロジェクトが分かれていると管理しづらいとは思っていた</span></sup>のですが、そうするとシェルのプロンプトが `(venv)` になってしまいます。
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiStzwbZ_C59rCmB3FlV4zzt5BONEcUMix1nUmJwZjcBhRp7OMRZoexMte0Ar1uC4L9E49tL28vC-vybfS8Q3XEje8u8o3UXDr44mq1Wx0asFqvf4nxmf6RxY3ZIEPa22FNN5dGBt6e54k/s1600/001.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiStzwbZ_C59rCmB3FlV4zzt5BONEcUMix1nUmJwZjcBhRp7OMRZoexMte0Ar1uC4L9E49tL28vC-vybfS8Q3XEje8u8o3UXDr44mq1Wx0asFqvf4nxmf6RxY3ZIEPa22FNN5dGBt6e54k/s1600/001.png" data-original-width="441" data-original-height="84" /></a></div>
### プロンプトは --prompt オプションで変更できる
これだと、「どの仮想環境を使用しているのか分かりづらくらなくなって嫌だな」と思っていたところ、venv のヘルプに `--prompt PROMPT` というオプションが。
これを使うと、プロンプトを任意のものに変更できます。素晴らしい。
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpHUSvi6lS5vDjYLq9yy_4QUzktfe20MtIFxVB3YR5sRzKh3T4Kt9K4xrXB0bMtRnq47DKxeLrxJjIo_7S3c9I60-TuCAAP93Lozxt1NU2jYVMrjogc_Jv_IaafBTaqI49dldgALzhoPo/s1600/002.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpHUSvi6lS5vDjYLq9yy_4QUzktfe20MtIFxVB3YR5sRzKh3T4Kt9K4xrXB0bMtRnq47DKxeLrxJjIo_7S3c9I60-TuCAAP93Lozxt1NU2jYVMrjogc_Jv_IaafBTaqI49dldgALzhoPo/s1600/002.png" data-original-width="589" data-original-height="84" /></a></div>
### 既に作成済のものを変える方法1
`venv/bin/activate` 内を見てみると、環境変数`PS1` を書き換えているだけなのがわかります。
ここを変更すれば、任意のプロンプトにすることが可能です。
```shell
`first-line: 57;
if [ "x(myproject) " != x ] ; then
PS1="(myproject) ${PS1:-}"
```
ただ、以下の「既に作成済のものを変える方法1(未来)」に書いたように、将来的には `pyvenv.cfg` 内に設定が移行することが決定しています。
### 既に作成済のものを変える方法1(未来)
「プロンプトを `activate` に直接書くのではなく `pyvenv.cfg` に記述したほうがよい」という Issue が挙げられ、変更は既に本線にマージ済みのようです。
> 参考
>
> [bpo-35661: Store the venv prompt in pyvenv.cfg by csabella · Pull Request #11440 · python/cpython · GitHub](https://github.com/python/cpython/pull/11440)
将来的に、プロンプトは `venv/bin/activate/` の中に書かれるのではなく `venv/pyvenv.cfg` 内に変数として格納されます。
Python のバージョンによって挙動が変わってくるので注意しましょう。
### 既に作成済のものを変える方法2
以下のように仮想環境を再作成するのもアリかと思います<sup><span class='barenote'>正確には既に作成済みのものを変更するわけでは無いですが</span></sup>。
```console
`gutter: false;
(venv) $ pip freeze > requirements.txt
(venv) $ deactivate
$ mv venv venv.bak
$ python3 -m venv --prompt myproject venv
$ . venv/bin/activate
(myproject) $ pip install -r requirements.txt
```
</pre>
<div class='barenote_ref_list'></div>Yusukehttp://www.blogger.com/profile/01684623000819625562noreply@blogger.com0tag:blogger.com,1999:blog-6154025142032985882.post-70810950632937972452019-02-27T08:06:00.000+09:002019-02-27T08:06:01.867+09:00RTNETLINK answers: File exists が出てしまった時の対処方法<pre class="marked">
@Ubuntu 18.04.1 LTS
一度 DHCP で IPアドレスを取得してしまったネットワークデバイスを静的アドレスに変更すると、以下のようなエラーが出ることがあります。
```console
`gutter: false;
$ sudo ifup eth0
RTNETLINK answers: File exists
Failed to bring up eth0
```
これを解消するには、登録されているIPアドレスを一度消去してやります。
```console
`gutter: false;
$ sudo ip addr flush dev eth0
```
> 参考
>
> [networking - Solving “RTNETLINK answers: File exists” when running ifup - Raspberry Pi Stack Exchange](https://raspberrypi.stackexchange.com/questions/13895/solving-rtnetlink-answers-file-exists-when-running-ifup/51947#51947)
</pre>
Yusukehttp://www.blogger.com/profile/01684623000819625562noreply@blogger.com0tag:blogger.com,1999:blog-6154025142032985882.post-40126039469081003902018-12-15T08:26:00.000+09:002018-12-15T08:26:02.913+09:00Android Studio で "Argument for @NotNull parameter 'message' of ..." が発生するようになってしまった<pre class="marked">
@Android Studio 3.2.1 on Ubuntu 18.04 LTS
Android Studio を 3.2.1 にしたところ、IDE Fatal Errors が発生するようになってしまいました<sup><span class='barenote'>Android Studio をアップデートするまでは特に問題がなかったプロジェクトです</span></sup>。
具体的には以下のようなエラー。
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxEubv3_J7T97CrBnmPg7SN68f8zIVD_YrxJl-2Kc_FGeUiK8xGPdz0oKiHfCGbFGQYyqwC4-Y3jNx87ITOQSnlu9PejbExGBfSP5WAHo2tcYww0dLpWnZvkyLtMD6FhY4VEoRvdXQLko/s1600/IDE+Fatal+Errors_001.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxEubv3_J7T97CrBnmPg7SN68f8zIVD_YrxJl-2Kc_FGeUiK8xGPdz0oKiHfCGbFGQYyqwC4-Y3jNx87ITOQSnlu9PejbExGBfSP5WAHo2tcYww0dLpWnZvkyLtMD6FhY4VEoRvdXQLko/s640/IDE+Fatal+Errors_001.png" width="640" height="288" data-original-width="1327" data-original-height="598" /></a></div>
```
Argument for @NotNull parameter 'message' of com/android/tools/idea/gradle/project/sync/GradleSyncState.syncFailed must not be null
java.lang.IllegalArgumentException: Argument for @NotNull parameter 'message' of com/android/tools/idea/gradle/project/sync/GradleSyncState.syncFailed must not be null
at com.android.tools.idea.gradle.project.sync.GradleSyncState.$$$reportNull$$$0(GradleSyncState.java)
at com.android.tools.idea.gradle.project.sync.GradleSyncState.syncFailed(GradleSyncState.java)
at com.android.tools.idea.gradle.project.sync.idea.IdeaSyncPopulateProjectTask.doPopulateProject(IdeaSyncPopulateProjectTask.java:135)
at com.android.tools.idea.gradle.project.sync.idea.IdeaSyncPopulateProjectTask.populate(IdeaSyncPopulateProjectTask.java:97)
at com.android.tools.idea.gradle.project.sync.idea.IdeaSyncPopulateProjectTask.access$000(IdeaSyncPopulateProjectTask.java:39)
at com.android.tools.idea.gradle.project.sync.idea.IdeaSyncPopulateProjectTask$1.run(IdeaSyncPopulateProjectTask.java:86)
at com.intellij.openapi.progress.impl.CoreProgressManager$TaskRunnable.run(CoreProgressManager.java:750)
at com.intellij.openapi.progress.impl.CoreProgressManager.lambda$runProcess$1(CoreProgressManager.java:157)
at com.intellij.openapi.progress.impl.CoreProgressManager.registerIndicatorAndRun(CoreProgressManager.java:580)
at com.intellij.openapi.progress.impl.CoreProgressManager.executeProcessUnderProgress(CoreProgressManager.java:525)
at com.intellij.openapi.progress.impl.ProgressManagerImpl.executeProcessUnderProgress(ProgressManagerImpl.java:85)
at com.intellij.openapi.progress.impl.CoreProgressManager.runProcess(CoreProgressManager.java:144)
at com.intellij.openapi.progress.impl.CoreProgressManager$4.run(CoreProgressManager.java:395)
at com.intellij.openapi.application.impl.ApplicationImpl$1.run(ApplicationImpl.java:305)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
```
<a name='more'></a>
Android Support plugin の中で発生していて、どうも Gradle と Android Studio の同期がとれない様子。
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvAf8S1D__4g9EIRPdzGoXOp8qeE8v36_qzuKRQrRgpqoZWjsZO4T1-HBa6_b0bpTFrsfIjR8Op-QS9SjKmu6-YvVt-4oRnEGP-Sp3-Y3AYvdWH65LygZkwxwevoo-MJk071mATDQRLcc/s1600/002.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvAf8S1D__4g9EIRPdzGoXOp8qeE8v36_qzuKRQrRgpqoZWjsZO4T1-HBa6_b0bpTFrsfIjR8Op-QS9SjKmu6-YvVt-4oRnEGP-Sp3-Y3AYvdWH65LygZkwxwevoo-MJk071mATDQRLcc/s1600/002.png" data-original-width="338" data-original-height="449" /></a></div>
### 原因はシンボリックリンク
探してみると、同じ問題が指摘されていました。
プロジェクトパスの中にシンボリックリンクが含まれていると起こるようです。
> 参考
>
> [linux - Android Studio 3.2.1 - Cannot sync project with gradle files: Argument for @NotNull parameter 'message' of ... must not be null - Stack Overflow](https://stackoverflow.com/a/52952148)
確かに、問題のプロジェクトのパスはシンボリックリンクを含んでいました。
### 解決策1 シンボリックリンク先のオリジナルを使う
シンボリックリンク先のオリジナルを開くと問題なく動作します。
今の所、これが一番簡単な解決策だと思われます。
### 解決策2 マウントする1
何らかの理由でどうしてもオリジナルを指定したくない場合は、シンボリックリンクを使うのではなく、マウントしてしまうという手があります。
[bindfs](https://bindfs.org/) を使えばユーザー権限で手軽にマウント出来ます。
```console
`gutter: false;
$ bindfs --no-allow-other org_path dst_path
```
### 解決策3 マウントする2
root 権限を持っているのであれば、fstab に書いてしまうという手もあります。
```text
`gutter: false; title: "/etc/fstab";
org_path dst_path none bind 0 0
```
fstab の記述を間違えるとシステムが正しく起動しなくなったりします。
ご注意を。
</pre>
<div class='barenote_ref_list'></div>Yusukehttp://www.blogger.com/profile/01684623000819625562noreply@blogger.com0tag:blogger.com,1999:blog-6154025142032985882.post-17408171494951012002018-12-04T08:48:00.000+09:002018-12-04T08:48:00.529+09:00[Python] date と datetime オブジェクトの相互変換まとめ<pre class="marked">
[datetime オブジェクト、Unix time、文字列の相互変換まとめ](https://kokufu.blogspot.com/2018/12/python-datetime-unix-time.html) を書いたので、ついでと言っては何ですが date と datetime の変換についてもまとめてみました。
<div class="separator" style="clear: both; text-align: center;">
<a href="https://docs.google.com/drawings/d/1L9B8IM7PUe6zUI5ByiCbIAOfNM_jUjhfip-DgN8Y2q8/edit?usp=sharing">
<img src="https://docs.google.com/drawings/d/e/2PACX-1vTPWvai84qdfNjldg4zj1fxHmArjvsf_DBTU-OMLOrZqjKYV7pmiPkJ8DYNOLBIm0n4Yae9J8FW9qJH/pub?w=600&h=4500"></a>
<div style="text-align: right;"><a href="https://docs.google.com/drawings/d/e/2PACX-1vTPWvai84qdfNjldg4zj1fxHmArjvsf_DBTU-OMLOrZqjKYV7pmiPkJ8DYNOLBIm0n4Yae9J8FW9qJH/pub?w=1440&h=1080">画像としてダウンロード</a></div>
</div>
<a name='more'></a>
なお、掲載コードは以下のように import 済みとします。
```python
from datetime import datetime, date, time
import pytz
```
## 1. date と datetime の相互変換
### datetime → date
datetime から date に変換するには [dateteime.date()](https://docs.python.org/3/library/datetime.html#datetime.date) を使用します。
```python
dt_native = datetime.utcfromtimestamp(1541030400) # native
# 2018-11-01 00:00:00
dt_native.date()
# 2018-11-01
```
aware な datetime オブジェクトにも使用できます。当然、タイムゾーンの影響は受けます。
```python
dt = datetime.fromtimestamp(1541030400, tz=pytz.timezone('America/New_York')) # aware
# 2018-10-31 20:00:00-04:00
dt_aware.date()
# 2018-10-31
```
### date → datetime
date から datetime に変換するには [datetime.combine()](https://docs.python.org/3/library/datetime.html#datetime.datetime.combine) を使用します。
しかし、`combine()` にタイムゾーンを指定して aware なオブジェクトを作成するのは危険です。
一度、native に変換してから aware にした方が良いでしょう。
```python
d = date(2018, 11, 1)
dt_native = datetime.combine(d, time())
print(pytz.timezone('Asia/Tokyo').localize(dt_native))
# 2018-11-01 00:00:00+09:00
# tzinfo を指定すると +9:19 になってしまう
datetime.combine(d, time(), tzinfo=pytz.timezone('Asia/Tokyo'))
# 2018-11-01 00:00:00+09:19
```
> 参考
>
> [[Python] datetime.combine() でタイムゾーンは指定しない方がよい | 穀風](https://kokufu.blogspot.com/2018/12/python-datetimecombine.html)
## 2. date と文字列の変換
### date → 文字列
date から文字列に変換するには [date.strftime()](https://docs.python.org/3/library/datetime.html#datetime.date.strftime) を使用します。
```python
d = date(2018, 11, 1)
d.strftime("%Y/%m/%d")
# 2018/11/01
```
### 文字列 → date
date オブジェクトには `datetime.strptime()` 相当のメソッドが用意されていません。
そのため、一度 datetime を経由してから、date に変換するしかなさそうです。
```python
date_string = "2018/11/01"
dt = datetime.strptime(date_string, "%Y/%m/%d")
dt.date()
# 2018-11-01
```
## 3. ISO フォーマット文字列と date の相互変換
[ISO 8601 形式](https://ja.wikipedia.org/wiki/ISO_8601) と date の相互変換も提供されています。
### ISO フォーマット文字列 → date
ISOフォーマット文字列から date オブジェクトの変換は [date.fromisoformat()](https://docs.python.org/3/library/datetime.html#datetime.date.fromisoformat) を使用します。
"YYY-MM-DD" のみ対応。
Python 3.7 で導入されました。
```python
date.fromisoformat("2018-11-01")
# 2018-11-01
```
### date → ISO フォーマット文字列
```python
d = date(2018, 11, 1)
d.isoformat()
# 2018-11-01
```
## 4. Unix time から date への変換
[Unix time](https://ja.wikipedia.org/wiki/UNIX%E6%99%82%E9%96%93) から date オブジェクトの変換には [date.fromtimestamp()](https://docs.python.org/3/library/datetime.html#datetime.date.fromtimestamp) が使えます。
ただ、**ローカルタイムゾーンを適用**して変換されてしまうのでなるべく使わない方が良いでしょう。
```python
date.fromtimestamp(1541030400)
# 2018-11-01 (+09:00 のタイムゾーン設定されているマシンで実行した場合)
# 2018-10-31 (-05:00 のタイムゾーン設定されているマシンで実行した場合)
```
例外的に、 [datetime.now()](https://docs.python.org/3/library/datetime.html#datetime.datetime.now) 等を用いてローカルタイムを処理する場合などは使えると思います。
```python
now = datetime.now()
# 2018-11-30 10:44:35.469159
date.fromtimestamp(now.timestamp())
# 2018-11-30
```
</pre>
Yusukehttp://www.blogger.com/profile/01684623000819625562noreply@blogger.com0tag:blogger.com,1999:blog-6154025142032985882.post-18443084999318443652018-12-03T08:01:00.000+09:002018-12-04T01:24:23.231+09:00[Python] datetime オブジェクト、Unix time、文字列の相互変換まとめ<pre class="marked">
Python で [datetime オブジェクト]() 、 [Unix time](https://ja.wikipedia.org/wiki/UNIX%E6%99%82%E9%96%93)、文字列を相互変換する方法はいろいろあります。
中には直感に反するものもあり<sup><span class='barenote'>native になりそうなのに、ローカルのタイムゾーンを適用しちゃうとか</span></sup>、
過去に1度間違えたのに、再度間違えるということもしばしば。
そこで、2度と間違えないように、個人的にベストと思う方法をまとめてみました。
<div class="separator" style="clear: both; text-align: center;">
<a href="https://docs.google.com/drawings/d/1tt0jKISn4yMsFeT39SVTQRABdEOt9hpNoO7chuScgBM/edit?usp=sharing">
<img src="https://docs.google.com/drawings/d/e/2PACX-1vR7VhOqeq5WpiYrywrgamgtAKtB9j9yTJEyiT2v0eaw7Y6Fm34yt1Hw-v6szpGSz2glLObjxkZ8FBjP/pub?w=600&h=450"></a>
<div style="text-align: right;"><a href="https://docs.google.com/drawings/d/e/2PACX-1vR7VhOqeq5WpiYrywrgamgtAKtB9j9yTJEyiT2v0eaw7Y6Fm34yt1Hw-v6szpGSz2glLObjxkZ8FBjP/pub?w=1440&h=1080">画像としてダウンロード</a></div>
</div>
変換には様々な方法があるので、上記が唯一の方法というわけではありませんが、
なるべく、安全かつシンプルになるものを選んだつもりです。
また、変換可能でも、安全では無いと判断したものは矢印を引いていません<sup><span class='barenote'>安全な方法をご存じの方は教えていただけると幸いです</span></sup>。
<a name='more'></a>
以下2点は Python のバージョンによっては使用できないので、代替案を掲載しておきました。
- `datetime.timestamp()` (3.3 以降で使用可)
- `datetime.fromisoformat()` (3.7 以降で使用可)
なお、掲載コードは以下のように import 済みとします。
```python
from datetime import datetime
import pytz
```
## 1. Unix time と datetime オブジェクトの相互変換
### Unix time → datetime (aware)
Unix time から datetime オブジェクトを生成するには、[fromtimestamp()](https://docs.python.org/3.6/library/datetime.html#datetime.datetime.fromtimestamp) を使用します。
この時、`tz` は必ず指定するようにしましょう。そうしないと、**ローカルのタイムゾーンを適用した native オブジェクト** が生成されてしまいます。
例外的に、[datetime.now()](https://docs.python.org/3/library/datetime.html#datetime.datetime.now) 等でローカル時間を取得して処理する時は `tz` を指定しなくても良いかもしれません。
```python
datetime.fromtimestamp(1541030400, tz=pytz.utc)
# 2018-11-01 00:00:00+00:00
datetime.fromtimestamp(1541030400, tz=pytz.timezone('Asia/Tokyo'))
# 2018-11-01 09:00:00+09:00
datetime.fromtimestamp(1541030400) # native
# 2018-11-01 09:00:00 (+09:00 のタイムゾーン設定されているマシンで実行した場合)
# 2018-10-31 20:00:00 (-05:00 のタイムゾーン設定されているマシンで実行した場合)
```
### Unix time → datetime (native)
Unix Time から native オブジェクトを生成したい場合は、[utcfromtimestamp()](https://docs.python.org/3.6/library/datetime.html#datetime.datetime.utcfromtimestamp) が使えます。
```python
datetime.utcfromtimestamp(1541030400)
# 2018-11-01 00:00:00
```
直感的には、UTC な aware オブジェクトが生成されて欲しいところ。
間違えないように気をつけましょう。
### datetime (aware) → Unix time
datetime オブジェクトから Unix time に変換する場合は、[timestamp()](https://docs.python.org/3.6/library/datetime.html#datetime.datetime.timestamp) を使用します。
この時、native オブジェクトから変換すると、**ローカルのタイムゾーンを適用して変換**されてしまいます。
こちらも、[fromtimestamp()](https://docs.python.org/3.6/library/datetime.html#datetime.datetime.fromtimestamp) と同様、[datetime.now()](https://docs.python.org/3/library/datetime.html#datetime.datetime.now) 等で取得したローカル時間の native オブジェクトを処理する時は良いかもしれませんが、基本的には使わないようにした方が良いでしょう。
```python
dt = datetime.fromtimestamp(1541030400, tz=pytz.utc)
int(dt.timestamp())
# 1541030400
dt = datetime.fromtimestamp(1541030400, tz=pytz.timezone('Asia/Tokyo'))
int(dt.timestamp())
# 1541030400
dt = datetime.utcfromtimestamp(1541030400) # native
int(dt.timestamp())
# 1540998000 (+09:00 のタイムゾーン設定されているマシンで実行した場合)
# 1541044800 (-05:00 のタイムゾーン設定されているマシンで実行した場合)
```
### timestamp() が使えない時
[timestamp()](https://docs.python.org/3.6/library/datetime.html#datetime.datetime.timestamp) は Python 3.3 以降で導入されました。
それ以前のバージョンで Unix time に変換したい場合は、以下のように calendar を使うと良いようです。
この時、UTC の aware に変換しないと想定外の値に変換されてしまうので注意が必要です。
```python
import calendar
dt = datetime.fromtimestamp(1541030400, tz=pytz.timezone('Asia/Tokyo'))
calendar.timegm(dt.astimezone(pytz.utc).timetuple())
# 1541030400
```
> 参考
>
> [Pythonの日付処理とTimeZone | Nekoya press](http://nekoya.github.io/blog/2013/06/21/python-datetime/)
## 2. aware と native の相互変換
### datetime (aware) → datetime (native)
aware から native にする場合は `replace(tzinfo=None)` を使用します。
```python
dt = datetime.fromtimestamp(1541030400, tz=pytz.timezone('Asia/Tokyo'))
# 2018-11-01 09:00:00+09:00
dt.replace(tzinfo=None)
# 2018-11-01 09:00:00
```
### datetime (native) → datetime (aware)
native から aware に変換する時は `replace(tzinfo=xxx)` を**使ってはいけません**。
代わりに `pytz.localize()` を使用します。
```python
dt = datetime.utcfromtimestamp(1541030400)
# 2018-11-01 00:00:00
pytz.timezone('Asia/Tokyo').localize(dt)
# 2018-11-01 00:00:00+09:00
```
タイムゾーンは日付によってオフセットが変わるので<sup><span class='barenote'>サマータイムのある地域だと特に</span></sup>、このような仕様になっているのですが、
かなり紛らわしく間違いやすいので注意が必要です。
> 参考
> - [Pythonでdatetimeにtzinfoを付与するのにreplaceを使ってはいけない | Nekoya press](http://nekoya.github.io/blog/2013/07/05/python-datetime-with-jst/)
> - [pytzの仕様が変わっている - Qiita](https://qiita.com/higitune/items/0ca244373d380cf1c060)
## 3. aware のタイムゾーンを変更する
同じ Unix time を持つ別のタイムゾーンに変換するには [astimezone()](https://docs.python.org/3.6/library/datetime.html#datetime.datetime.astimezone) を使用します。
```python
dt = datetime.fromtimestamp(1541030400, tz=pytz.timezone('Asia/Tokyo'))
# 2018-11-01 09:00:00+09:00
dt.astimezone(pytz.timezone('America/New_York'))
# 2018-10-31 20:00:00-04:00
```
なお、`astimezone(None)` は native に変換するのではなく、**ローカルのタイムゾーンに変更**します。
最初、メリットがわからなかったのですが、これを使ってローカルタイムゾーンを取得するという技が紹介されていました<sup><span class='barenote'>かなりトリッキー。普通にローカルタイムゾーンを取得出来ればそれで良いのだが…</span></sup>。
> 参考
>
> [datetime - Python: Figure out local timezone - Stack Overflow](https://stackoverflow.com/a/39079819)
## 4. 文字列と datetime オブジェクトの相互変換
書式文字列を使用して、任意の文字列と datetime を相互変換することが出来ます。
> 書式文字列のディレクティブ一覧は以下
>
> [strftime() and strptime() Behavior](https://docs.python.org/3.6/library/datetime.html#strftime-strptime-behavior)
### 文字列 → datetime
文字列から datetime オブジェクトに変換する場合は [strptime()](https://docs.python.org/3.6/library/datetime.html#datetime.datetime.strptime) を使用します。
`%z` を含む場合は aware になり、含まない場合は native になります。
なお、`%Z`(大文字)は `strptime()` では使用できないようです。
```python
datetime.strptime("2018/11/01 09:00", "%Y/%m/%d %H:%M")
# 2018-11-01 09:00:00
datetime.strptime("2018/11/01 09:00+0900", "%Y/%m/%d %H:%M%z")
# 2018-11-01 09:00:00+09:00
```
### datetime → 文字列
datetime から文字列に変換するには [strftime()](https://docs.python.org/3.6/library/datetime.html#datetime.datetime.strftime) を使用します。
native に対し `%z` を指定しても何も表示されません。
```python
dt = datetime.utcfromtimestamp(1541030400)
# 2018-11-01 00:00:00
dt.strftime("%Y/%m/%d %H:%M%z")
# 2018/11/01 00:00
dt = datetime.fromtimestamp(1541030400, tz=pytz.timezone('Asia/Tokyo'))
# 2018-11-01 09:00:00+09:00
dt.strftime("%Y/%m/%d %H:%M%z")
# 2018/11/01 09:00+0900
dt.strftime("%Y/%m/%d %H:%M(%Z)")
# 2018/11/01 09:00(JST)
```
## 5. ISO フォーマット文字列と datetime オブジェクトの相互変換
[ISO 8601 形式](https://ja.wikipedia.org/wiki/ISO_8601) と datetime の相互変換も提供されています。
ただ、ISO 8601 にも基本と拡張が存在し、拡張形式にしか対応していない様子。
さらに UTC を表す "Z" には対応していないなど、使い勝手はイマイチです。
個人的には [strptime()](https://docs.python.org/3.6/library/datetime.html#datetime.datetime.strptime) と [strftime()](https://docs.python.org/3.6/library/datetime.html#datetime.datetime.strftime) を使用した方が柔軟性が高く、便利かと思います。
### ISO フォーマット文字列 → datetime オブジェクト
ISO フォーマット文字列から datetime に変換するには [fromisoformat()](https://docs.python.org/3.7/library/datetime.html#datetime.datetime.fromisoformat) を使用します。
上述のように、拡張形式にしか対応していません<sup><span class='barenote'>セパレータは "T" に限定されず、任意の1文字で良い</span></sup>。
```python
iso_string = "2018-11-01T09:00:00+09:00"
datetime.fromisoformat(iso_string)
# 2018-11-01 09:00:00+09:00
```
[fromisoformat()](https://docs.python.org/3.7/library/datetime.html#datetime.datetime.fromisoformat) は Python 3.7 で導入されたので、
それ以前の場合は [strptime()](https://docs.python.org/3.6/library/datetime.html#datetime.datetime.strptime) で代用することになります。
厄介なのは ISO フォーマットと `strptime()` のタイムゾーンの記述方法が異なる点です。
ISO は `+09:00` なのに対し、 `strptime()` は `+0900` でコロンがありません。
あまり良い方法は提供されていないようなので、愚直に置換してやるのがよさそうです
> 参考
>
> [python - parsing timezone with colon - Stack Overflow](https://stackoverflow.com/a/45300534)
```python
iso_string = "2018-11-01T09:00:00+09:00"
# タイムゾーンのフォーマットを変更
dt_string = iso_string if ":" != iso_string[-3:-2] else iso_string[:-3]+iso_string[-2:]
# 2018-11-01T09:00:00+0900
datetime.strptime(dt_string, "%Y-%m-%dT%H:%M:%S%z")
# 2018-11-01 09:00:00+09:00
```
### datetime オブジェクト → ISO フォーマット文字列
datetime から IOSフォーマット文字列に変換するには [isoformat()](https://docs.python.org/3.6/library/datetime.html#datetime.datetime.isoformat) を使用します。
```python
dt = datetime.fromtimestamp(1541030400, tz=pytz.timezone('Asia/Tokyo'))
# 2018-11-01 09:00:00+09:00
dt.isoformat()
# 2018-11-01T09:00:00+09:00
dt.isoformat(timespec='hours')
# 2018-11-01T09+09:00
```
`timespec` や `sep` を用いて多少フォーマットを変更できますが、上述のように全ての ISO 8601 形式をカバーしているわけではないので、使用できるシーンは限られそうです。
## 6. コンストラクタ
コンストラクタでタイムゾーンを指定すると、native に対し `replace()` したのと同等になってしまいます。
コンストラクタで `tzinfo` を指定するのではなく、native で作成してから `pytz.localize()` で aware に変換するようにしましょう。
```python
# こちらが正解
dt = datetime(2018, 9, 1) # native
pytz.timezone('Asia/Tokyo').localize(dt)
# 2018-09-01 00:00:00+09:00
# 以下のようにコンストラクタで tzinfo を指定すると19分というずれが発生
datetime.datetime(2018, 9, 1, tzinfo=pytz.timezone('Asia/Tokyo'))
# 2018-09-01 00:00:00+09:19
```
> 参考
>
> - [[Python] datetime のコンストラクタでタイムゾーンは指定しない方がよい](https://kokufu.blogspot.com/2018/11/python-datetime.html)
> - [pytzの仕様が変わっている - Qiita](https://qiita.com/higitune/items/0ca244373d380cf1c060)
> - [Pythonでdatetimeにtzinfoを付与するのにreplaceを使ってはいけない | Nekoya press](http://nekoya.github.io/blog/2013/07/05/python-datetime-with-jst/)
</pre>
<div class='barenote_ref_list'></div>Yusukehttp://www.blogger.com/profile/01684623000819625562noreply@blogger.com0tag:blogger.com,1999:blog-6154025142032985882.post-61081838279531443652018-12-02T00:58:00.000+09:002018-12-02T00:58:00.282+09:00[Python] datetime.combine() でタイムゾーンは指定しない方がよい<pre class="marked">
先日、「[datetime のコンストラクタでタイムゾーンは指定しない方がよい](https://kokufu.blogspot.com/2018/11/python-datetime.html)」と書いたのですが、
`datetime.combine()` でも同様の問題が起こりました。
以下のように、日本のタイムゾーンが `+9:19` になってしまうのです。
```python
from datetime import datetime, date, time
import pytz
d = date(2018,11,1)
datetime.combine(d, time(), tzinfo=pytz.timezone('Asia/Tokyo'))
# 2018-11-01 00:00:00+09:19
```
<a name='more'></a>
コンストラクタと同様、一度 native で作成してから aware に変換するのが良さそうです。
```python
`first-line: 4;
d = date(2018,11,1)
dt = datetime.combine(d, time())
pytz.timezone('Asia/Tokyo').localize(dt)
# 2018-11-01 00:00:00+09:00
```
</pre>
Yusukehttp://www.blogger.com/profile/01684623000819625562noreply@blogger.com0tag:blogger.com,1999:blog-6154025142032985882.post-46229122156303054572018-12-01T00:49:00.000+09:002018-12-04T01:25:13.966+09:00[Python] サマータイムのある地域で時刻計算する時は、一度 UTC に変換する<pre class="marked">
「サマータイムのある地域で」とタイトルに入れたのですが、地域に関わらず、時刻計算する時は UTC に変換してから行った方が良いです。
特にサマータイムのある地域では問題が起こりやすいので、注意が必要ということです。
### 問題が起こる例
2018年の ニューヨークは 11/4 2:00 am までがサマータイム<sup><span class='barenote'>アメリカでは Daylight Saving Time という</span></sup>でした。
```
2018/11/4 2:00 am より前
EDT -04:00
2018/11/4 1:00 am 以降
EST -05:00
```
このような時刻をまたぐ計算を aware のまま行うと、結果がおかしくなります。
```python
>>> import datetime, pytz
>>> dt = datetime.datetime(2018, 11, 4, 7, 0, 0, 0) # 2018/11/4 7:00 am native
>>> dt_aware = pytz.timezone('America/New_York').localize(dt)
>>> dt_aware
datetime.datetime(2018, 11, 4, 7, 0, tzinfo=<DstTzInfo 'America/New_York' EST-1 day, 19:00:00 STD>)
>>> str(dt_aware)
'2018-11-04 07:00:00-05:00'
```
上記の `dt_aware` から 1日前を計算すると以下のようになります。
```python
>>> calculated = dt_aware - datetime.timedelta(days=1)
>>> calculated
datetime.datetime(2018, 11, 3, 7, 0, tzinfo=<DstTzInfo 'America/New_York' EST-1 day, 19:00:00 STD>)
>>> str(calculated)
'2018-11-03 07:00:00-05:00'
```
ぱっと見正しそうですが、11月3日なのに EST になっていて、間違っています。
<a name='more'></a>
これを回避する方法を以下に挙げておきます。
基本的にはタイトルにあるように、UTC に変換するのが一番ですが、全てのケースで使えるわけではありません。
なお、タイムゾーンをコンストラクタで指定すると結果がおかしくなるので、必ず `localize()` を使用します。
> 参考
>
> [[Python] datetime のコンストラクタでタイムゾーンは指定しない方がよい | 穀風](https://kokufu.blogspot.com/2018/11/python-datetime.html)
### 回避策1: もう一度 タイムゾーンを適用する
`datetime` オブジェクトの中の [Unix time](https://ja.wikipedia.org/wiki/UNIX%E6%99%82%E9%96%93) は保持されているので、タイムゾーンを再度適用すると正しい結果になります。
```python
>>> calculated2 = calculated.astimezone(pytz.timezone('America/New_York'))
>>> calculated2
datetime.datetime(2018, 11, 3, 8, 0, tzinfo=<DstTzInfo 'America/New_York' EDT-1 day, 20:00:00 DST>)
>>> str(calculated2)
'2018-11-03 08:00:00-04:00'
```
これでも大抵の場合はうまくいきます。
ただ、本来存在しない時刻が途中に出てくるのがイマイチです。
### 回避策2: UTC に変換してから計算する
計算する時には必ず UTC にし<sup><span class='barenote'>原理的にはオフセットが変わらないタイムゾーンなら何でも良い。ただ、そんなことが保証されているのは UTC だけだと思う。</span></sup>、その後、再度タイムゾーンを適用します。
```python
>>> dt_aware_utc = dt_aware.astimezone(pytz.utc)
>>> dt_aware_utc
datetime.datetime(2018, 11, 4, 12, 0, tzinfo=<UTC>)
>>> calculated_utc = dt_aware_utc - datetime.timedelta(days=1)
>>> calculated_utc
datetime.datetime(2018, 11, 3, 12, 0, tzinfo=<UTC>)
>>> calculated3 = calculated_utc.astimezone(pytz.timezone('America/New_York'))
>>> calculated3
datetime.datetime(2018, 11, 3, 8, 0, tzinfo=<DstTzInfo 'America/New_York' EDT-1 day, 20:00:00 DST>)
>>> str(calculated3)
'2018-11-03 08:00:00-04:00'
```
少々面倒ですが、常に UTC で計算するようにすると、タイムゾーンに関する諸問題を適切に回避できる方法かと思います。
### 回避策3: native に変換してから計算する
大抵の場合は UTC に変換する方法を使用しておけば良いと思います。
ただ、UTC に直す方法がうまく機能しない場合があります。
例えば、「その地域で3日前の17時が Unix time ではどう表されるか」を知りたい場合等<sup><span class='barenote'>かなり限られた条件だが、実際にそういう需要があったので</span></sup>です。
そういった場合は、native にしてしまうという方法があります。
```python
>>> dt_native = dt_aware.replace(tzinfo=None) # dt と同等
>>> dt_native
datetime.datetime(2018, 11, 4, 7, 0)
>>> calculated_native = dt_native - datetime.timedelta(days=3)
>>> calculated_native
datetime.datetime(2018, 11, 1, 7, 0)
>>> calculated_native2 = calculated_native.replace(hour=17, minute=0, second=0, microsecond=0)
>>> calculated_native2
datetime.datetime(2018, 11, 1, 17, 0)
>>> calculated4 = pytz.timezone('America/New_York').localize(calculated_native2)
>>> calculated4
datetime.datetime(2018, 11, 1, 17, 0, tzinfo=<DstTzInfo 'America/New_York' EDT-1 day, 20:00:00 DST>)
>>> str(calculated4)
'2018-11-01 17:00:00-04:00'
>>> int(calculated4.timestamp()) # Unix time (Python 3.3 以降)
1541106000
```
かなり面倒ですね。
この場合は native な datetime オブジェクトではなく、date オブジェクトに直して、また datetime オブジェクトに戻すという手もつかえます<sup><span class='barenote'>これも、結構面倒</span></sup>。
また、最初に書いたタイムゾーンを適用し直すという方法も使えますが、タイムゾーンを適用するタイミングによって結果が変わるので注意が必要です。
### まとめ
どの方法を使うせよ、**計算結果に対し自動的にオフセットが変わることは無い** ということを念頭においておくと良いでしょう。
その上で、基本的には UTC を基準にした aware で計算するのがベスト<sup><span class='barenote'>サマータイムのあるなしに関わらず、計算する時は UTC に直すのがベストプラクティス</span></sup>。
native を扱わなければいけないケースもありますが、タイムゾーンが落ちることでミスが発生しやすくなります。
どうしても必要な場合、変換はギリギリまで行わず、計算後は aware<sup><span class='barenote'>なるべく UTC</span></sup> に戻すようにすると良いかと思います。
</pre>
<div class='barenote_ref_list'></div>Yusukehttp://www.blogger.com/profile/01684623000819625562noreply@blogger.com0tag:blogger.com,1999:blog-6154025142032985882.post-72791843817917843652018-11-30T07:53:00.000+09:002018-12-18T07:15:44.956+09:00[Python] datetime のコンストラクタでタイムゾーンは指定しない方がよい<pre class="marked">
以下を見てください。
日本のタイムゾーンが `+9:19` になってしまいました。
```python
>>> import datetime, pytz
>>> datetime.datetime(2018, 9, 1, tzinfo=pytz.timezone('Asia/Tokyo'))
datetime.datetime(2018, 9, 1, 0, 0, tzinfo=<DstTzInfo 'Asia/Tokyo' LMT+9:19:00 STD>)
```
調べてみると、pytz 2017.2 以降を使う場合、上記のような動作になるようです。
原因については、以下のコメント欄が参考になります。(以下の記事で触れているのは `replace` でタイムゾーン指定した場合ですが、コンストラクタで指定した場合も同様)
> 参考
>
> [pytzの仕様が変わっている - Qiita](https://qiita.com/higitune/items/0ca244373d380cf1c060)
簡単に言うと、「複数のタイムゾーン(UTC オフセット)を持つエリアでは、日付が決まらないとオフセットが決定できないので、
勝手にデフォルトのオフセットが採用される」ということのようです。
一般的に「複数のタイムゾーンを持つエリア」といえば、サマータイムがあるようなエリアで、日本は当てはまらない気がします。
ところが、日本は 1887年以前は今とは異なるタイムゾーン (`+09:18:59`<sup><span class='barenote'>9:19:00 ではない。pytz は秒まで持てないからね…</span></sup>) だったそうです。
> 参考
>
> [一行入魂 PostgreSQLのtime zoneについて](http://aoyagikouhei.blog8.fc2.com/blog-entry-61.html)
pytz 2017.2 以降はこれを厳密に守るようになったということみたいですね<sup><span class='barenote'>とはいえ、100年以上前のタイムゾーンがデフォルトってどうなのよ?と思いますが…</span></sup>。
というわけで、aware な datetime オブジェクトを作成するには、
一度 native で作成してから aware に変換するのが正しいやり方のようです。
```python
>>> import datetime, pytz
>>> dt = datetime.datetime(2018, 9, 1)
>>> pytz.timezone('Asia/Tokyo').localize(dt)
datetime.datetime(2018, 9, 1, 0, 0, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>)
```
今回、たまたま日本のタイムゾーンで問題が起こって調べましたが、他のタイムゾーン<sup><span class='barenote'>サマータイムがあるなど</span></sup>では同じ問題が前からあったということですね<sup><span class='barenote'>多分。確認はしていないけど。</span></sup>。
</pre>
<div class='barenote_ref_list'></div>Yusukehttp://www.blogger.com/profile/01684623000819625562noreply@blogger.com0tag:blogger.com,1999:blog-6154025142032985882.post-46878202157139762302018-11-22T07:48:00.000+09:002018-11-22T07:48:05.513+09:00GNOME 3 でマウントドライブをデスクトップに表示しないようにする方法<pre class="marked">
@GNOME 3.28.2
gvfs でいろいろマウントしているとデスクトップがゴチャゴチャになってくる<sup><span class='barenote'>アイコンが被ったりする。バグだよなぁ…</span></sup>ので、アイコンを表示しないように出来ないか調べてみた。
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPPBNvjxAY9v-UDQRjmPqMva8Z9yrSyhpUO7jrzF7E0WyAelOWWLGyp73VlWp0A8c4TfBg9Hu4cXxWMRQ79OE3OIz-lY_9ZJeXn2y5QXpUdWDhtWJER-3T50-cI3XTuXcpSzaFgL5h59A/s1600/Selection_001.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPPBNvjxAY9v-UDQRjmPqMva8Z9yrSyhpUO7jrzF7E0WyAelOWWLGyp73VlWp0A8c4TfBg9Hu4cXxWMRQ79OE3OIz-lY_9ZJeXn2y5QXpUdWDhtWJER-3T50-cI3XTuXcpSzaFgL5h59A/s1600/Selection_001.png" data-original-width="141" data-original-height="232" /></a></div>
結論から言うと、dconf の `/org/gnome/nautilus/desktop/volumes-visible` を OFF にすればよい。
<a name='more'></a>
`dconf-editor` を使えば簡単。
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiV-Z6TpASaBF3S9OzKUxakPQ2i6EVW89CTV8urOML7_QLUpcRPcdzkSpTMAxygYv2zgksSFKgVHI55h-NnucElhVuwOkrznIv2f-llIj-_7TMMGO7mk_JsgrvdlCQPv2klRHnrRfA5Pxw/s1600/dconf+Editor_002.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiV-Z6TpASaBF3S9OzKUxakPQ2i6EVW89CTV8urOML7_QLUpcRPcdzkSpTMAxygYv2zgksSFKgVHI55h-NnucElhVuwOkrznIv2f-llIj-_7TMMGO7mk_JsgrvdlCQPv2klRHnrRfA5Pxw/s1600/dconf+Editor_002.png" data-original-width="630" data-original-height="420" /></a></div>
なお、`gsettings` 等で変更したい場合、 schema id は `org.gnome.nautilus.desktop` 。
> 参考
>
> - [GSettings と dconf の関係 | 穀風](https://kokufu.blogspot.com/2018/11/gsettings-dconf.html)
> - [dconf の schema id と schema path の関係 | 穀風](https://kokufu.blogspot.com/2018/11/dconf-schema-id-schema-path.html)
</pre>
<div class='barenote_ref_list'></div>Yusukehttp://www.blogger.com/profile/01684623000819625562noreply@blogger.com0tag:blogger.com,1999:blog-6154025142032985882.post-50948484729013986302018-11-09T08:18:00.000+09:002018-11-10T06:12:52.470+09:00[Python] mmap.flush で OSError が出る場合、アラインメントが不適切な可能性あり<pre class="marked">
Python の [mmap](https://docs.python.jp/3/library/mmap.html) は `flush()` を実行することで変更をディスクに保存します。
この `flush([offset[, size]])` を引数つきで呼ぶときは 「`offset` が `mmap.ALLOCATIONGRANULARITY` の倍数でないといけない」という制約があります<sup><span class='barenote'>エラーメッセージにその旨出して欲しい</span></sup>。
つまり、以下のように、`offset` の位置を調整しないといけません。
```python
aligned_offset = int(offset / mmap.ALLOCATIONGRANULARITY) * mmap.ALLOCATIONGRANULARITY
mm.flush(aligned_offset, offset - aligned_offset + size)
```
> 参考
>
> [Issue 32798: mmap.flush() on Linux does not accept the "offset" and "size" args - Python tracker](https://bugs.python.org/issue32798)
`offset` の調整をしないと、`OSError` が発生してしまいます。
```
OSError: [Errno 22] Invalid argument
```
最初、このエラーが発生した時、指定した範囲が不正なんじゃないか等、無駄に調べてしまいました…
</pre>
<div class='barenote_ref_list'></div>Yusukehttp://www.blogger.com/profile/01684623000819625562noreply@blogger.com0