Cloud Firestore で collection の基本操作

Cloud Firestore使ったときのメモ
検証用のメモだからダブルクウォートとシングルクウォートが混ざってるのは気にするな

Create

const docRef = await addDoc(collection(db, "collection"), {
  title: "title",
  description: "description",
  created_at: new Date(),
});
console.log("Document written with ID: ", docRef.id);

Read

// 単一の取得
const ref = doc(db, "collection", "document ID");
const snap = await getDoc(ref);
console.log(snap.data());

// 複数取得
const q = query(collection(db, "collection", where('title', '==', 'title')));
const snap = await getDocs(q);
snap.forEach((doc) => console.log(doc.data()));

// 順番と件数を指定して複数取得
const q = query(collection(db, "collection"), orderBy('created_at', 'desc'), limit(2));
const snap = await getDocs(q);
snap.forEach((doc) => console.log(doc.data()));

Update

const ref = doc(db, "collection", "document ID");
await updateDoc(ref, {
    title: "update title",
});

Delete

await deleteDoc(doc(db, 'collection', 'document ID'))

参考

firebase.google.com


Atomフィードで取得したRedmineのチケット情報をSlackに通知する

いつ使うの?

以下の状態のRedmineを使用しているとき

  • チケット操作時にメール通知が来ない
  • APIアクセスキーが発行されていない
  • 管理権限を持っていない

Slack App のRSSインテグレーションも使ったんだけど新規チケットの通知しか来ないし思いの外役立たずでした

前提

  • Googleアカウント作成済み
  • Slackbot作成済み

コード

Slackへのリクエストは以下のライブラリを使用しています

github.com

function myFunction() {
  // Redmine のチケット情報を取得する
  const feedURL = 'https://{Redmineのドメイン}/issues.atom?query_id={適当なquery_id}&key={Atomアクセスキー}';
  const response = UrlFetchApp.fetch(feedURL);
  const xml = XmlService.parse(response.getContentText());
  const rootDoc = xml.getRootElement();
  const atom = XmlService.getNamespace('http://www.w3.org/2005/Atom')

  const items = rootDoc.getChildren('entry', atom)
  if (items.length === 0) {
      return;
  }
  const slackUserId = '{SlackのUserId}';
  let message = '<@' + slackUserId + '> さん宛の Redmine のチケットが' + items.length + '件あります。\n';

  items.forEach((item) => {
    const title = item.getChild('title', atom).getText();
    // `/1111` のようにスラッシュ+数字の羅列をチケットIDとみなして検索
    const issueId = item.getChild('id', atom).getText().match(/\/([0-9]+)/)[1];
    // タイトルの文字列にチケットのリンクを貼る
    message += '<https://{Redmineのドメイン}/issues/' + issueId + '|' + title + '>\n';
  })

  const options = {
    username: 'Redmine',
    link_name: true,
    icon_emoji: ":redmine:"
  };
  // slackに投稿
  const slackApp = SlackApp.create('xoxb-XXXXXXXXXXXX'); // Slackbot のtoken
  slackApp.postMessage("#Slackのチャンネル名", message, options);
}

((GASのシンタックスハイライトってjavascriptでいいの…?))

後は定期実行のトリガーを任意で作成してSlackに通知が来ればOK

感想

早くGASでもテンプレートリテラル使えるようにしてくれ


株式会社エイルシステムではWebエンジニア・モバイルアプリエンジニアを募集しています。
実務経験がなくてもOKです。ご興味のある方は弊社HPよりご連絡ください。


Toggl の作業内容を Redmine に登録する

弊社では作業管理ツールに Redmine を使用しています。
チケットと呼ばれる各タスクに作業時間を入力して、↓みたいに退社時に Slackbot で1日の作業内容がわかるようになっています。

(ちなみに上記のSlackbotは弊社代表の自作です。)

ただ、これが死ぬほどめんどくさいんです。
作業開始時の時刻を覚えておいて作業が完了したら終了時の時刻から逆引きして時間を出すっていう、算数の引き算でつまずいた人間からすると地獄です。冗談抜きに引き算だけで30分くらい取られます。
ということでめんどくさいことはプログラムにやらせよう!

事前準備

Toggl アカウントの作成

toggl.com

これがなきゃ始まりません。
アカウント作成後の profile 画面に API Token が発行されているのでコピります。

Toggl 拡張機能のインストールと設定

chrome.google.com

なくても成立はするんですが、Redmine 上でタイマーを打ちたいのでインストールします。
インストール後に、拡張機能の設定で Integrations メニューの中で Redmine のURLを有効にします。
(自社サーバーに Redmine を立ててる場合は Custom URLs for integrationsドメインを登録すればOK)

最終的にRedmine のチケットを見た時に Toggl のボタンが表示されてればOK

プログラムの作成

最低限必要な処理以外は省略してるので「リクエスト周りのエラーハンドリングどうすんの?」とか言う輩は帰ってください。
アーキテクチャ警察も帰ってください。

API Document は以下を参照しました

① 当日の作業記録を取得する

<?php
$togglUri = 'https://api.track.toggl.com/api/v8/time_entries';
$options = ['auth' => [$_ENV['TOGGL_API_TOKEN'], 'api_token']];
$dateTime = new DateTimeImmutable();
$query = http_build_query([
    'start_date' => $dateTime->setTime(00, 00, 00)->format('c'),
    'end_date' => $dateTime->setTime(23, 59, 59)->format('c'),
]);
$entryRequest = (new \GuzzleHttp\Client())->request('GET', "{$togglUri}?{$query}", $options);

$res = $entryRequest->getBody()->getContents();
$entries = json_decode($res, true);

② Toggl の作業内容から Redmine に登録する作業時間データを用意する

<?php
// 活動の選択肢
$activities = ['作業内容1' => 1, '作業内容2' => 2];
foreach ($entries as $entry) {
    // 作業時間のコメント
    $description = $entry['description'] ?? '';

    // 作業時間の時間
    // 開始・終了の差分
    $start = new DateTimeImmutable($entry['start']);
    $stop = new DateTimeImmutable($entry['stop']);
    $diff = $stop->diff($start);
    $min = round(($diff->i / 60), 3);
    $diffHours = $diff->h + $min;

    // Redmine でタイマーを打ったときのフォーマットに沿って` #1234 `の文字列をチケットIDとして検索
    preg_match('/\ #([0-9]+)\ /', $description, $results);
    // description にチケットIDがあればサブパターンマッチの文字列を使用する
    // プロジェクトに紐付かない場合は任意のデフォルトのチケットIDを指定
    $issueId = $results[1] ?? $_ENV['REDMINE_DEFAULT_ISSUE_ID'];

    // タグの登録があれば1番目のタグを活動とする
    // なければデフォルトの活動IDを指定
    $activityId = $activities[$entry['tags'][0] ?? 1];

    // xmlの生成
    $xmlValues = [
        'issue_id' => $issueId,
        'spent_on' => $dateTime->format('Y-m-d'),
        'hours' => $diffHours,
        'activity_id' => $activityId,
        'user_id' => $_ENV['REDMINE_USER_ID'],
        'comments' => $description,
    ];
    $xml = new SimpleXMLElement('<time_entry></time_entry>');
    foreach ($xmlValues as $key => $value) {
        $xml->addChild($key, $value);
    }
}

Toggl の作業レコードごとに以下のようなXMLが生成されます。

<time_entry>
    <issue_id>1234</issue_id>
    <spent_on>2022-02-19</spent_on>
    <hours>1.0</hours>
    <activity_id>1</activity_id>
    <comments>タスク #1234 テキストテキストテキストテキスト</comments>
    <user_id>1</user_id>
</time_entry>

Redmine に作業時間を登録する

<?php
$redmineUri = "{$_ENV['REDMINE_URL']}time_entries.xml?key={$_ENV['REDMINE_API_KEY']}";
$request = (new \GuzzleHttp\Client())->request('POST', $redmineUri, [
    'headers' => ['Content-Type' => 'text/xml',],
    'body' => $xml->asXML()
]);

APIキーが発行されてない Redmine を使ってる場合は知りません!管理者に頼もう!

作業の登録

ここからがやりたかったことです

  1. 作業の前後で Redmine のチケット画面に表示されてる Toggl のタイマーをクリック

    タグを付けると活動がわかるのでタグだけ任意でつけておく
  2. 退勤前にプログラムを実行する
  3. Redmine に作業内容が登録されるのを確認する

最終的に↓みたいにして使ってます!

コード全文は以下に置いてあります。

https://github.com/m1y474/toggl-redmine

最後に

Toggl 過激派なので Toggl 布教の記事も書きたい…(Toggl Trackという名称を使ってない時点で過激派ではない)

ちなみにこのプログラムを作るまでは、下記サイトをiframeに埋め込んだものを拡張機能にして手動で計算してました!お世話になりました!2桁以上の引き算は苦手です!!

www.jalps.net

GitHubにpushしたらHerokuへデプロイする

f:id:IlIIlIIIlIIlI:20220217233028p:plain

Herokuデビューしたときの備忘録
結構前のだから手順抜けてるかも

前提

  • GitHubとHerokuのアカウントが作成済み
  • GitHubとHerokuのアカウントが連携済み

App作成

App nameは空欄にしとけばHeroku側で勝手に作ってくれるのでそのままCreate appを押す
名前つけたい場合は使われてない名前なら任意でつけてOK

f:id:IlIIlIIIlIIlI:20220217231302p:plain

Deploy設定

App作成後にDeployの画面が表示されるのでDeployment MethodGitHubを選択する

f:id:IlIIlIIIlIIlI:20220217231449p:plain

Connect to GitHubが表示されるので対象のRepositoryのConnectを押す

f:id:IlIIlIIIlIIlI:20220217231728p:plain

Automatic deploysで任意のブランチを選択してEnable Automatic Deploysをオンにする

f:id:IlIIlIIIlIIlI:20220217231932p:plain

手動デプロイがしたいときはManual deployから任意のブランチを選択してDeploy Branch押すだけ f:id:IlIIlIIIlIIlI:20220217232040p:plain


textareaの入力文字数によって高さを可変にする

こういうやつ

コード

<textarea></textarea>
<script>
  const textarea = document.querySelector('textarea');
  textarea.addEventListener('input', () => {
    textarea.style.height = null;
    textarea.style.height = `${textarea.scrollHeight}px`;
  });
</script>

改行の数をカウントしてtextareaのrowsに入れたりしたけど, それだと文字の折返しのときに高さが取れなかったのでtextareaのスクロール量を高さに入れるようにした

実際使うときは高さの上限決めた方がいいかも

LINEスタンプ作ったときの備忘録

直近死んだ犬2匹を素材にしてLINEスタンプを作りました。

store.line.me

store.line.me

これでトークの中でレオンとネネは生き続ける。嬉しい。
一気に2匹も死んでペットロス半端なかったけどこれで少しは緩和されるといいな

多分またいつか作るから備忘録というか忘れそうな部分をまとめておきます


作り方

[方法1] LINEスタンプメーカーを使って作る

creator.line.me 風呂でスマホ触っててスタンプ作ろうってなったときにこれにたどり着きました。
作成〜申請までスマホで1Hちょっとで作れたけど、写真素材の透過が甘かったり用意されてるステッカーがダサいあらかじめ決められてるから自由度は低いと思います。

[方法2] LINEクリエイターズマーケットを使って作る

自分でスタンプ素材を作ってWeb上で申請する方法。
LINEスタンプメーカーで作ったものが気に入らなくて作り直したときに使いました。
Photoshopなどのフォトエディタツールで作ったスタンプ素材をLINEクリエイターズマーケットに登録して、必要情報を入力して申請しました
スタンプ素材は自分の好きなように作れるけど、LINEスタンプメーカーよりは申請までの入力項目が多いので手間だと思います。

申請から承認までの時間

LINEスタンプメーカーとLINEクリエイターズマーケットの両方とも22時とか23時に申請して翌日の13~15時頃には結果が来ていました。

申請がリジェクトされた。再申請から承認までの時間は?

LINEスタンプメーカーで申請したときは一発で承認されたけど、LINEクリエイターズマーケットで申請したときはリジェクトされました。
販売情報のテキストに不備があったので修正して、その日のうちに再申請したら5時間後くらいには承認されました。
リジェクトされたらメッセージセンターに理由が書いてあるのでその通り修正して再申請すれば承認されると思います。

表示情報や販売情報の入力不備に関しては、LINEスタンプメーカーだとアプリが適当にやってくれるのでよっぽど不適切な文字列を入れない限り、リジェクトされる可能性は低いと思う。

承認されたけどリリースされない

承認されたらLINEクリエイターズマーケットのアイテム管理画面の右上らへんに赤いリリースボタンが表示されます。
リリースボタンを押すことでリリースが完了します。
LINEスタンプメーカーのときは忘れた。

リリースしたけど購入用URLに反映されない

5~10分くらい待てば反映されます。

スタンプのタグはなにを設定すればいいの?

特にこだわりがなければ何も設定しなくていいと思う。
設定した場合はLINEのトーク画面で文字入力時にサジェストとしてスタンプが出てくる。
設定しなかった場合はリリース後の2,3日後にLINE側で適当につけてくれる。
再申請は必要になっちゃうけど後日つけられたタグを確認して、このタグ違うなってものがあれば修正すればいい。

売上分配金が入らない

LINEスタンプメーカーで作るとデフォルトで売上分配金なしになってます。
申請前に絶対に確認しよう!
ちゃんと読んでない自分も悪いけどこれ罠だと思う。せめてデフォルト未選択にしてほしいっす…

制作ガイドラインについて

スタンプの制作ガイドラインに以下の記載があります。

トリミングされた画像の外枠とコンテンツの間には10pxぐらいの余白が必要です。画像をつくるときは上下左右のバランスに注意してください。

ガイドラインを一通り読んだ上で自分はこれをガン無視しました。余白なしで外枠ギリギリまで素材を設置しました。
リジェクトされたときに「あーやっぱりか」って思ったけどリジェクトの理由は前述の通り、販売情報のテキストの不備だったので関係ありませんでした。
これ実際どうなんだろう?みんな守ってるのかな?写真スタンプだと関係ないとかある?

使用したツール

LINEクリエイターズマーケットでのスタンプ作成時に以下のツールを使用させていただきました。
ありがとうございました。

medibangpaint.com

lightbox.on.coocan.jp

fukidesign.com

apngasm.sourceforge.net

※本記事に記載されている内容は2022/02/06時点のものです。

hoverでbackground-imageを切り替えたときの画像のチラつきが気になる

↓これのこと*1

なんでチラつくの?

レンダリングのときは読み込まれなくて、hoverのときに初めて画像を読み込んでるから
Networkタブを見てみるとhoverのタイミングで読み込んでるのがわかる

対応策

① preloadで読み込んでおこう

headタグの中にpreloadを指定する

<link rel="preload" href="./images/hover.png" as="image">

あらかじめ読み込んでくれてるので初回hoverでもチラつかない!

preloadって何?

指定したリソースを先に読み込むようにブラウザに指示すること
imageの他にもstyleやscriptなども指定できる
詳しくはMozilla先生に聞いてね developer.mozilla.org

② background-imageにhoverの時の画像も指定しよう

hoverしたときじゃない方のbackground-imageにhoverしたときの画像も指定して非表示にしておく

button {
    background-image: url(./images/normal.png), url(./images/hover.png);
    background-size: contain, 0; // 2枚目はsizeを0にしておく
    &:hover {
        background-image: url(./images/hover.png);
    }
}

(環境によるのかもだけど)preload指定してNetworkタブで事前に読み込まれてるのも確認してるのに、何故かチラつくことがあるので②のほうが確実だと思う

感想

正直俺は気にならん


*1:チラつかないときはブラウザキャッシュを削除してみてください

PHPUnit で作成したテスト項目をHTMLで出力する

PHPUnit--log-junit オプションでxmlを出力する

# ./vendor/bin/phpunit tests/Feature/SampleControllerTest.php --log-junit output.xml
PHPUnit 9.5.10 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 00:00.901, Memory: 24.00 MB

OK (1 test, 1 assertion)

xsltproc を使用して html に変換する

# xsltproc phpunit.xslt output.xml > output.html 

(※ xsltproc コマンドがない場合は別途インストールする。Macにはデフォルトで入ってるはず。。)
出力用のテンプレ(phpunit.xslt)は以下よりDL

PHPUnit xslt · GitHub

htmlが出力されていることを確認
テンプレを変えたい場合は phpunit.xslt を任意で修正すればOK

参考

https://phpunit.readthedocs.io/ja/latest/textui.htmlphpunit.readthedocs.io

inokara.hateblo.jp

qiita.com


株式会社エイルシステムではWebエンジニア・モバイルアプリエンジニアを募集しています。
実務経験がなくてもOKです。ご興味のある方は弊社HPよりご連絡ください。


Markdown でリストの中にコードブロックを含めると連番がリセットされる

問題

下記のようにリストの中にコードブロックが含まれる場合、リスト3 の番号が1になってしまう。

Markdown

1. リスト1
1. リスト2

\```php
<?php echo 1; ?>
\```
1. リスト3

*1

実際の表示

  1. リスト1
  2. リスト2
<?php echo 1; ?>
  1. リスト3

解決策

リスト内のコードブロックにインデントを設定しよう!

Markdown

1. リスト1
1. リスト2

   ```php
   <?php echo 1; ?>
   ```
1. リスト3

実際の表示

  1. リスト1
  2. リスト2

    <?php echo 1; ?>
    
  3. リスト3

※コードブロック前に3スペース入れています。

*1:※バックスラッシュはphpのコードブロックが親コードブロックの終了とみなされてしまうため入れています。

Mac で撮ったスクショの影を削除する

Mac でアプリケーションウィンドウ全体のスクショを撮ったときの影、邪魔じゃねって思ったときの備忘録

defaults write com.apple.screencapture disable-shadow -boolean true

Before

f:id:IlIIlIIIlIIlI:20211231143500p:plain

After

f:id:IlIIlIIIlIIlI:20211231143613p:plain


影ありに戻したいときはfalseにすればOK

defaults write com.apple.screencapture disable-shadow -boolean false

Mac に Xdebug をインストールして VSCode でデバッグする

経緯

ちょっとしたサンプルプログラムをホストマシンで試すときに、いつも var_dump() デバッグ使ってたけどちょっとしたサンプルプログラムだろうが何だろうが Xdebug 使ったほうがもっと楽になるよね、ハム太郎

手順

Debug Buildno になっていることを確認

$ php -i | grep 'Debug Build'
Debug Build => no

ついでに Xdebug がインストールされていないことも確認

$ php -m | grep xdebug

何も表示されなければOK

Xdebug のインストール

  1. 下記のダウンロードサイトから Xdebug をDLする。
    http://www.xdebug.org/download.php
    ※どのversionをDLすればいいかわからない場合は $ php -i | pbcopy を下記にペーストして調べる。
    https://xdebug.org/wizard
  2. 解凍して任意のディレクトリに移動

     $ tar -zxvf ~/Downloads/xdebug-xxx.tgz && mv ~/xdebug-xxx ~/{任意のディレクトリ} && cd ~/{任意のディレクトリ}/xdebug-xxx
    
  3. xdebug-xxx ディレクトリにいる状態で以下を順に実行していく

     $ phpize  
     $ ./configure  
     $ make  
    

    完了したら modules ディレクトリに xdebug.so があることを確認する
    ない場合はどこかの手順でエラーが出ているはずなので随時解消する

  4. php.ini の修正

    php.iniの場所を確認

     $ php --ini
     Configuration File (php.ini) Path: /opt/homebrew/etc/php/7.4
     Loaded Configuration File:         /opt/homebrew/etc/php/7.4/php.ini
     Scan for additional .ini files in: /opt/homebrew/etc/php/7.4/conf.d
     Additional .ini files parsed:      /opt/homebrew/etc/php/7.4/conf.d/ext-opcache.ini
    

    xdebug.so までのフルパスを追記

     zend_extension={xdebug.so までのフルパス}
    
  5. Xdebug が有効になっていることを確認する

     $ php -m | grep xdebug
     xdebug
    

    xdebug が表示されればOK

VSCodeの設定

  1. 拡張機能 PHP Debug をインストールする
    VSCode拡張機能php debug で検索、もしくは下記よりインストールする
    https://marketplace.visualstudio.com/items?itemName=felixfbecker.php-debug
  2. プロジェクト直下に .vscode/launch.json を作成する
    ⌘ + Shift + D でデバッグメニューを表示すると初回は launch.json を作る、みたいなリンクがあるのでクリックする
    プロジェクト直下に .vscode/launch.json が作成されることを確認
  3. php.ini にconfigurations.portの番号を指定

     zend_extension={xdebug.so までのフルパス}
    
     [PHP]
     xdebug.mode=coverage,develop,debug
     xdebug.discover_client_host=1
     xdebug.client_port=9003 ; .vscode/launch.json の configurations.port の値を貼り付ける
     xdebug.start_with_request=yes
    

    Xdebug2と3で設定値の名前が結構変わっているのでversionごとの違いは下記を参照

    xdebug.org

確認

適当なプログラムにbreakpointを設定して止まればOK

参考

xdebug.org

qiita.com

www.aiship.jp


株式会社エイルシステムではWebエンジニア・モバイルアプリエンジニアを募集しています。
実務経験がなくてもOKです。ご興味のある方は弊社HPよりご連絡ください。


Laravel でアクセストークンを header に含むときのAPIのテストを書く

f:id:IlIIlIIIlIIlI:20220129185719p:plain

いつ使うの?

  • SPAではないプロジェクト(アクセストークンの取得タイミングがMPAでのログイン時)
  • 一部画面でアクセストークンを含むAPIを叩いている

コード

<?php

use Tests\TestCase;

class SampleControllerTest extends TestCase
{
    public function setUp(): void
    {
        parent::setUp();

        // ログイン処理
        $res = $this->post('/login', [
            'email' => 'hoge@example.jp',
            'password' => 'password'
        ]);
        // 取得したアクセストークンをプロパティに格納
        $this->accessToken = $res->baseResponse->getSession()->get('access_token');
    }

    public function testAPIリクエスト()
    {
        // プロパティに入っているアクセストークンを使用してリクエスト
        $res = $this->post('api/hoge', ['product_id' => 1], ['Authorization' => 'Bearer ' . $this->accessToken]);

        $res->assertOk()->assertJson(['succeeded' => true]);
    }
}

要点

  • setUp() 時にアクセストークンの発行処理を実施し、プロパティにアクセストークンを入れておく
  • テストの時にプロパティに入っているアクセストークンを含めてリクエストの発行をする
  • アクセストークン取得時のリクエストはAPIのエンドポイントではないのでアクセストークン自体は \Illuminate\Http\Response が入っている baseResponse から取得する
    • API経由で取得する場合は $res->json($key) で取得できるっぽい(未検証)
      (※decodeResponseJson()の引数にkeyを指定して取得できてたみたいだけどLaravel8からは引数を取らないみたい)

github.com

readouble.com

qiita.com


Laravel で画像をアップロードする Controller のテストを書く

Laravel で画像アップロードのController周りのテストを書いたことがなかったので備忘録

<?php

use Tests\TestCase;
use Illuminate\Http\UploadedFile;

class SampleControllerTest extends TestCase
{
    public function test画像アップロードができる()
    {
        $res = $this->post('image/upload', ['images' => UploadedFile::fake()->image('hoge.jpg')]);

        $res->assertRedirect('image/upload/completed');
        $res->assertSessionHas('success', 'アップロードに成功しました。');
    }
}

UploadedFile facadeのfake()を使えばOK
勝手に面倒くさそうと思ってたけどめちゃめちゃ簡単だった👌

readouble.com


株式会社エイルシステムではWebエンジニア・モバイルアプリエンジニアを募集しています。
実務経験がなくてもOKです。ご興味のある方は弊社HPよりご連絡ください。

親のチェックボックスに紐付けたチェックボックスを全てチェックする

親のチェックボックスのチェック状態を親に紐付けた子供のチェックボックスにも反映させたい
↓こういうやつ

これをピュアJSで書いたときの備忘録

コード

解説

HTML

  1. チェックボックスに子供を紐付ける用のカスタムデータ属性(data-id="1")をつける
    (親であることを判別できるのであればclass名などで管理してもOK)
<label>親チェック1<input type="checkbox" data-id="1" class="check-parent"></label>
  1. 親に紐付ける子供となるチェックボックスに親のid(data-parent-id="1")を紐付ける
<label>親1子供1<input type="checkbox" data-parent-id="1"></label>

JavaScript

  1. 親となるチェックボックス要素のchangeイベントをループ処理で実施する
  2. 親のdataset.idに紐付いている子供の要素を取得して, ループ処理で親のチェック状態を子供のチェック状態と同じにする
Array.from(document.querySelectorAll('.check-parent')).map(checkParent => {
  checkParent.addEventListener('change', () => {
    Array.from(document.querySelectorAll(`[data-parent-id="${checkParent.dataset.id}"]`)).map(checkChild => {
      checkChild.checked = checkParent.checked
    })
  })
})

Carbon の diffInDays() は時間まで見ている

みんな大好きCarbonで対象日が今日の何日後かを知りたいときに, 想定していた挙動と違っていたのでメモ

対象日が本日の何日後かを知りたい

2021-11-15が本日(2021-11-14)の何日後かを知りたいとき

>>> now()->diffInDays(Carbon\Carbon::parse('2021-11-15'))
=> 0

期待値は1だったけど, 0が返ってきた

実装を見てみる

Carbon\Traits\Difference を見てみると, 継承元の DateTime クラスの diff() を見に行ってたので DateTime での挙動も確認してみる

>>> (int) (new DateTime())->diff(new DateTime('2021-11-15'))->format('%r%a')
=> 0

(※ intへのcastとformat()はCarbon準拠)

(当たり前なんだけど)同じく0が返ってくる

現在時刻の時間を指定してみる

>>> now()->setTime(0, 0)->diffInDays(Carbon\Carbon::parse('2021-11-15'))
=> 1
>>> (int) (new DateTime())->setTime(0, 0)->diff(new DateTime('2021-11-15'))->format('%r%a')
=> 1

期待値の1が返ってきた。

対応策

現在時刻と対象日時の日付だけを考慮してdiffを取りたいときは現在日時刻の時間もセットしよう!

感想

DateTime クラス関してはdiffっていう命名からしてまっとうな挙動だと思うんだけど, Carbon のdiffInDays命名ややこしすぎ
日時を指定されても日数のdiff返せや(横暴)
ちなみに自分は Laravel 触るまでCarbonの存在知らなかったマンです。

※実際のコードにはImmutableクラス使おうね


github.com


株式会社エイルシステムではWebエンジニア・モバイルアプリエンジニアを募集しています。
実務経験がなくてもOKです。ご興味のある方は弊社HPよりご連絡ください。