@sustny note

忘れっぽさを補うためのメモ

Moment.js(for GAS)の日付計算

Moment.gsとでも言うのがいいのだろうか。

GASのMoment.jsライブラリを使った日付操作でハマったのでメモ。

function daycalc() {
  var day =Moment.moment([2017,1,1]);
  //この日付をベースに仕様を確かめていく
  
  var day0 = day.clone().subtract(1,'days').format('YYYY/MM/DD');
  Logger.log(day0);
  //結果: 2017/01/31 ... 引き算する項目を"日"、引き算する値を"1"と指定すると日が1減る→わかる
  
  var day1 = day.clone().subtract(1,'months').format('YYYY/MM/DD');
  Logger.log(day1);
  //結果: 2017/01/01 ... 引き算する項目を"月"、引き算する値を"1"と指定すると変わらない→わからない
  
  var day2 = day.clone().subtract(2,'months').format('YYYY/MM/DD');
  Logger.log(day2);
  //結果: 2016/12/01 ... 引き算する項目を"月"、引き算する値を"2"と指定すると月が1減る→わかってきた
  
  var day3 = day.clone().subtract(0,'months').format('YYYY/MM/DD');
  Logger.log(day3);
  //結果: 2017/02/01 ... 引き算する項目を"月"、引き算する値を"0"と指定すると月が1増える→ですよね
  
  var day4 = day.clone().subtract(0,'years').format('YYYY/MM/DD');
  Logger.log(day4);
  //結果: 2017/02/01 ... 引き算する項目を"年"、引き算する値を"0"と指定すると月が1増える→おまえもか
  
  /* ----- 試しにnew Date()でもやってみよう ----- */
  var nday = new Date('2017/1/1');
  
  var nday1 = new Date(nday.getYear(), nday.getMonth(), nday.getDate());
  nday1 = Utilities.formatDate(nday1, 'JST', 'yyyy/MM/dd');
  Logger.log(nday1); //結果: 2017/01/01 →想定通りで安心
  
  var mday = new Date(day); //冒頭にMomentで作っておいた'2017/1/1'をnew Dateで取得してみる
  
  var mday1 = new Date(mday.getYear(), mday.getMonth(), mday.getDate());
  mday1 = Utilities.formatDate(mday1, 'JST', 'yyyy/MM/dd');
  Logger.log(mday1); //結果: 2017/02/01 →なんで!!!
  
  var day5 = Moment.moment(nday);
  day5 = day5.clone().subtract(0,'years').format('YYYY/MM/DD');
  Logger.log(day5);
  //結果: 2017/01/01 →new Date()で取得した日付をMoment()の引数にすると思ってたとおりに動く
  
  var day6 = Moment.moment(nday);
  day6 = day6.clone().subtract(1,'months').format('YYYY/MM/DD');
  Logger.log(day6);
  //結果: 2016/12/01 →同上
  
  var day7 = Moment.moment(nday);
  day7 = day7.clone().subtract(1,'days').format('YYYY/MM/DD');
  Logger.log(day7);
  //結果: 2016/12/31 →同上
}

当初は「月の計算が0始まりなの意味分からんわ〜!」って記事にしようと思ったのに、試しに年計算もやってみたらより混迷を極める結果になった。

Moment.moment()で日付を取得すると年がおかしくなるのに対して、new Date()で取得したものをMoment.moment()で取得し直し、それ以降の操作をMomentを使っておこなうと直感で分かりやすく日付の計算を扱えるようです(以下にその仕様でのみ操作をした例を記載します)。

function daycalc() {
  var base = new Date('2017/1/1');
  
  var day1 = Moment.moment(base);
  day1 = day1.clone().add(3,'years').format('YYYY/MM/DD');
  Logger.log(day1); //結果: 2020/01/01
  
  var day2 = Moment.moment(base);
  day2 = day2.clone().subtract(13,'months').format('YYYY/MM/DD');
  Logger.log(day2); //結果: 2015/12/01
  
  var day3 = Moment.moment(base);
  day3 = day3.clone().add(5,'days').format('YYYY/MM/DD');
  Logger.log(day3); //結果: 2017/01/06
}

ちなみに、Moment.jsをGASで使う方法については上の記事を参照しました。

tonari-it.com

以上です。

早朝自転車部

3連休の開始前日、なんと21時頃に寝落ちしてしまい2時頃起床するというジジイサイクルが発動してしまい、そのまま二度寝もできなかったので5時過ぎにひとっ走りしてきました。

普段ならトラックやらバスやらで混雑する西巣鴨飛鳥山へ抜ける道もこんな感じ。朝活最高やん。

行きの荒サイで正面から太陽浴びるの変な感じ。

このカゲーーー!

荒サイ自体も野球キッズたちがいないから走りやすくて、普段より10分ぐらい早く到着しました。

きれいな青空やで……

葛西臨海公園駅ニューデイズで売ってた。北海道かな?

それにしても早朝も結構自転車乗りがいるもんで、荒サイを10人ぐらいでトレイン組んで走ってる人たちとか、葛西臨海公園駅に集合してどこかへ向かう人たちを見かけました。ぼっち自転車乗りだと思ってた人が次々に仲間と集合していく様を見せつけられて泣きました。

ところでこの日をもってこの自転車の総走行距離が3,000kmを突破しました。ネジが破断したシートクランプと、後輪のパンクしたチューブとすり減ったタイヤを交換したぐらいでそれ以外まともなメンテナンスもしていない状態なので、そろそろガッツリ整備したいです。

前輪チューブ+タイヤの交換、チェーン交換、スプロケット洗浄、各種ワイヤーの交換、ヘッドセットからの異音解消(多分ベアリングのグリス切れ)、あたりかなあ。とりあえずはお店に持ち込んでみるのがいいんだろうけど、お金との相談になりそう。

メンテがひと通り済んだら、ハンドルカット+バーエンドバーの取り付け、ビンディングペダルに交換する、あたりが目標です。10,000kmはどうかわからないけど、とりあえず5,000kmに向けて準備していきます。

このカゲーーー!(2回目)

釣り人もたくさんいました。あと1時間で満潮らしい。

西巣鴨まで戻ってきたのが8時過ぎ。流石に混んできましたが、夕方ほどではない。


というわけで、人も車も少なくて気温も30℃以下の早朝自転車部最高でした。夏場は4時に起きてひとっ走り→7時に戻ってきてシャワー浴びて出社っていうのが理想です。が、理想は理想です。きっとやらない。

すべての写真は以下。

photos.app.goo.gl

そういえば朝の荒サイはカニが地面を歩いているので走行時は気をつけてください。

以上です。

リアルポタリング

自転車乗りの言うポタリングが100kmを下回らないので、やりました。

相変わらず飽きもせず冴えカノ♭聖地巡礼をしております。

環状5の1号線第3工区に大きな変化を確認。東池袋交差点側から千登世橋へ向かう道が若干区役所寄りを抜けるようになりました。

f:id:sustny:20170710120433j:plain

こんな感じ。

開通は2019年頃でまだまだ先ですが、引き続き追っていきたいです。

出ましたのぞき坂。探偵坂とも加藤恵坂ともいう。

俺にはおしるこが見える。

俺には加藤恵が見える。

都電早稲田駅。冴えカノ2期7話で出てくる。

都電学習院下駅。もう度々出てくる。

1期9話とかで出てきた小学校ここだろ! と思って来てみたけど、違うくさいな。

2期3話で出てきた公園。誰もいないし首都高下でうるさいし憩う場所ではなかった。

最後。前回記事でも言及したけど加藤家の最寄りと思わしき都電雑司ヶ谷駅。

アニメで出てきた角度で、電車が離合するところまでバッチリ合わせられたので大満足。

sustny.hateblo.jp


すべての写真は以下から。

goo.gl

以上です。

事実上の冴えカノ聖地巡礼

圧縮効果を楽しむ日

そんなつもりはなくてもそうなってしまったものは仕方がないだろ!

これは都電荒川線です。

のぞき坂。冴えカノの中では探偵坂と呼ばれてたやつ。

2期では写真右側のエントランスにおしるこの缶が置かれていたことでも有名。

俺には坂の上に加藤恵が見える。

1期ではここを都電が走る姿がOP冒頭で出てきます。

遠くに見える学習院下駅と更に遠くに見えるブリリアタワー池袋(豊島区役所)。

豊ヶ崎学園からの最寄りがここなのか早稲田なのかが曖昧で、もう少し精密に描いてくれれば雑司が谷がより賑わったのではという地元民的な気持ちがある。

加藤恵の最寄り駅。※アングルはだいぶ違う

2期3話でここを降りていったので実質我が家に住んでいるといっても過言ではない。


冴えカノ聖地巡礼以上。家を出ればすぐ聖地ってのは最高だしそれが好きな作品なら優勝です。優勝しました。

以下、関係ない写真。

以上です。

Rebuild of 潮位情報のプログラム

//
// TideInfo.gs
// Created on 2017-06-30 10:00
// Created by sustny(http://sustny.me/)
//

getInfo = function(DATE, SPOT) {
  //1. 日付と行く場所を指定する(行く場所は特定の観測地点である必要はなく、純粋に自身の目的地を入力すればよい)
  var YEAR = parseInt(DATE.substr(0,4));
  var MONTH = parseInt(DATE.substr(4,2));
  var DAY = parseInt(DATE.substr(6,2));
  
  //1-1. 頭が0の2桁(ex:08)をparseIntするとNaNになるのでその処理
  if( parseInt(DATE.substr(4,1)) == 0 ) {
    MONTH = parseInt(DATE.substr(5,1));
  }
  if( parseInt(DATE.substr(6,1)) == 0 ) {
    DAY = parseInt(DATE.substr(7,1));
  }
  
  //2. 入力されたSPOTを緯度・経度に分解する
  for(var i=0;i<SPOT.length;i++) {
    if(SPOT.substr(i,1) == ',') {
      var lat = parseFloat(SPOT.substr(0,i));
      var lon = parseFloat(SPOT.substr(i+1));
    }
  }
  
  //3. 気象庁が公表している潮位表の掲載地点一覧を呼び出し、行く場所から最も近い観測地点を探す
  var FILE = SpreadsheetApp.getActiveSpreadsheet();
  var SHEET = FILE.getSheetByName('Data');
  var CELL = SHEET.getDataRange().getValues();
  var DIST = [];
  for(i=1;i<CELL.length;i++) {
    //3-1. 経緯度差を三平方の定理から求めて、
    var Dlat = parseFloat(CELL[i][3].substr(0,2)) + (parseFloat(CELL[i][3].substr(3)) / 60) - lat;
    var Dlon = parseFloat(CELL[i][4].substr(0,3)) + (parseFloat(CELL[i][4].substr(4)) / 60) - lon;
    DIST[i-1] = Math.sqrt( (Dlat*Dlat) + (Dlon*Dlon) );
    //3-2. 最も近いところを探す
    if(i == 1) {
      var min = DIST[i-1];
      var j = i;
    } else {
      if(min>DIST[i-1]) {
        min = DIST[i-1];
        j = i;
      }
    }
  }
  
  //4. 最も近い観測地点の情報から当該日付の情報(1行)を抜き出す
  var URL = UrlFetchApp.fetch('http://www.data.jma.go.jp/kaiyou/data/db/tide/suisan/txt/' + YEAR +'/' + CELL[j][1] + '.txt');
  var TXT = URL.getContentText();
  
  if(YEAR % 4 != 0) {
    var arrMonth = [0,31,59,90,120,151,181,212,243,273,304,334]; //各月の開始行メモ
    var Row = TXT.substr(( (137*arrMonth[MONTH-1]) + (137*(DAY-1)) ),136);
  } else {
    var arrMonth = [0,31,60,91,121,152,182,213,244,274,305,335]; //各月の開始行メモ
    var Row = TXT.substr(( (137*arrMonth[MONTH-1]) + (137*(DAY-1)) ),136);
  }
  
  //5. 抜き出した情報を分割して配列に格納する
  //Info = [地名、満潮時刻1、満潮潮位1、満潮時刻2、満潮潮位2、満潮時刻3、満潮潮位3、満潮時刻4、満潮潮位4、干潮時刻1、干潮潮位1、干潮時刻2、干潮潮位2、干潮時刻3、干潮潮位3、干潮時刻4、干潮潮位4]
  var Info = [CELL[j][2],
              '' + parseInt(Row.substr(80,2)) + ':' + parseInt(Row.substr(82,2)), parseInt(Row.substr(84,3)),
              '' + parseInt(Row.substr(87,2)) + ':' + parseInt(Row.substr(89,2)), parseInt(Row.substr(91,3)),
              '' + parseInt(Row.substr(94,2)) + ':' + parseInt(Row.substr(96,2)), parseInt(Row.substr(98,3)),
              '' + parseInt(Row.substr(101,2)) + ':' + parseInt(Row.substr(103,2)), parseInt(Row.substr(105,3)),
              '' + parseInt(Row.substr(108,2)) + ':' + parseInt(Row.substr(110,2)), parseInt(Row.substr(112,3)),
              '' + parseInt(Row.substr(115,2)) + ':' + parseInt(Row.substr(117,2)), parseInt(Row.substr(119,3)),
              '' + parseInt(Row.substr(122,2)) + ':' + parseInt(Row.substr(124,2)), parseInt(Row.substr(126,3)),
              '' + parseInt(Row.substr(129,2)) + ':' + parseInt(Row.substr(131,2)), parseInt(Row.substr(133,3))];
  
  //6. 情報が格納された配列を返す
  return Info;
}

function TideInfo_Main() {
  var date = Browser.inputBox('日付を入れろ(ex:20170101)', Browser.Buttons.OK_CANCEL);
  if(date == '') { //入力がないなら今日
    date = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyyMMdd');
  }
  var spot = Browser.inputBox('場所を入れろ(ex:35.305276, 139.317403)', Browser.Buttons.OK_CANCEL);
  if(spot == '') { //入力がないなら東京
    spot = '35.640342, 139.855059';
  }
  var info = getInfo(date, spot);
  var message = '' + info[0] + 'の潮位(' + date.substr(0,4) + '/' + date.substr(4,2) + '/' + date.substr(6,2) + ')\\n'
             + '満潮1: ' + info[1] + ' ' + info[2] + 'cm\\n'
             + '満潮2: ' + info[3] + ' ' + info[4] + 'cm\\n'
             + '満潮3: ' + info[5] + ' ' + info[6] + 'cm\\n'
             + '満潮4: ' + info[7] + ' ' + info[8] + 'cm\\n'
             + '干潮1: ' + info[9] + ' ' + info[10] + 'cm\\n'
             + '干潮2: ' + info[11] + ' ' + info[12] + 'cm\\n'
             + '干潮3: ' + info[13] + ' ' + info[14] + 'cm\\n'
             + '干潮4: ' + info[15] + ' ' + info[16] + 'cm';
  Browser.msgBox(message);
}

スプレッドシートのURL

気象庁が公開している潮位のテキストデータから情報を生成するプログラム。

指定するのは日付(形式: yyyyMMdd)と行先(形式: DEG)の2つ。

行先は自分が行く場所を指定すれば、あとは勝手に行先から最も近い観測地点の情報を返すようにした。

ちなみに日付を指定しなければ今日の情報、行先を指定しなければ東京の情報を返す。

これが一番シンプルな実装かも。

潮位を表示するために必要な場所のIDマップ

drive.google.com

前回記事で公開した潮位を調べるプログラム内で、場所を指定する際に決められたID(1から327までの整数)を入力する必要がありましたが、地名だけだと具体的な場所が分かりにくいのでGoogle Mapにピン立てしました。

立てたピンに今日明日分ぐらいの潮位情報を載せてGoogle Map内で完結できれば一番使い勝手がいい気がしますが、仕様的にも自身の技術的にも出来るのかどうかから調べていく必要があります。

熱が冷めないうちに頑張ります〜。

潮位を表示するプログラム

釣り好きが高じて潮位を表示するプログラムを書いた。

//
// FishingInfo.gs
// Created on 2017-06-20 13:00
// Created by sustny
//
// tide info: http://fishing-community.appspot.com/tidexml/doc
//

function FishingInfo(p, y, m, d) {
  var feedURL = 'http://fishing-community.appspot.com/tidexml/index?portid='+p+'&year='+y+'&month='+m+'&day='+d;
  var response = UrlFetchApp.fetch(feedURL);
  var xml = XmlService.parse(response.getContentText());
  var xmlStr1 = ['port-id', 'port-name', 'latitude1', 'longitude1', 'latitude2', 'longitude2', 'year', 'month', 'day', 'youbi', 'sunrise-time', 'sunset-time', 'moonrise-time', 'moonset-time', 'tide-name', 'tidedetails'];
  var xmlStr2 = ['tide-time', 'tide-level'];
  
  //タイトル
  var message = '\n■□' + xml.getRootElement().getChild(xmlStr1[1]).getText() + 'の潮位情報□■';
  
  //日付...yyyy年mm月dd日(ddd)
  message += '\n◆' + y + '年' + m + '月' + d + '日(' + xml.getRootElement().getChild(xmlStr1[9]).getText() + ')';
  
  var items = ""
  for(i=0;i<2;i++) {
    items = xml.getRootElement().getChildren(xmlStr1[xmlStr1.length-1]);
  }
  
  message += ' - ' + xml.getRootElement().getChild(xmlStr1[14]).getText();
  for(var i=0; i<items.length; i++) {
    var time = items[i].getChild(xmlStr2[0]).getText();
    var level = items[i].getChild(xmlStr2[1]).getText();
    if( (time != "") || (level != "") ) {
      message += '\n時刻: ' + time + ' / 潮位: ' + level + "cm";
    }
  }
  
  //日出・日入時刻
  message += '\n\n◇こよみ\n日出: ' + xml.getRootElement().getChild(xmlStr1[10]).getText() + ' / 日入: ' + xml.getRootElement().getChild(xmlStr1[11]).getText();
  //月出・月入時刻
  message += '\n月出: ' + xml.getRootElement().getChild(xmlStr1[12]).getText() + ' / 月入: ' + xml.getRootElement().getChild(xmlStr1[13]).getText();
  //地図
  message += '\n\n◇場所\nhttps://www.google.co.jp/maps/place/'+ xml.getRootElement().getChild(xmlStr1[4]).getText() + ',' + xml.getRootElement().getChild(xmlStr1[5]).getText();
  Logger.log(message);
}

function Main() {
  var place = 89; //ex: 96-晴海 / 89-立山 / 116-江の島
  var year = 2017;
  var month = 07;
  var day = 07;
  
  FishingInfo(place, year, month, day);
}

GitHubこちら

相変わらずGASで書いています。

ちなみに潮位情報は釣りコミュニティーサービス[fishstar]というところから引っ張っています。

商用利用でなければ無料。ありがとうございます。


場所と日付をコード内で指定するのではなく、外部からの入力によって変化させたりとかアイディアはたくさんありますが、とりあえずコード内で完結できる最少の状態を公開しておきます。

変なところとかあれば教えていただけると幸いです。(xmlから情報引っ張ってくるのとか初めてやった)

以上です。