日付時刻の最小値最大値とMySQL

Java8 以降、java.time.LocalDate と java.time.LocalDateTime の最小値/最大値は、

LocalDate.MIN → -999999999-01-01
LocalDate.MAX → +999999999-12-31

LocalDateTime.MIN → -999999999-01-01T00:00
LocalDateTime.MIN → +999999999-12-31T23:59:59.999999999

である。
MySQL データベースのTIMESTAMP型は、このような言語の数学的な最小値と最大値は
データを作れません。
JSTのdatabase の場合、UTCエポック時刻 +9:00 1970-01-01 09:00:00
の時刻でデータを作ろうとすると、
ERROR 1292: 1292: Incorrect datetime value:
+1秒の 1970-01-01 09:00:01 ならデータを作れる。
これはつまり、Java エポックの時刻(JST

LocalDateTime.ofEpochSecond(3600*9L, 0, ZoneOffset.UTC)

や、

OffsetDateTime.of(LocalDate.of(1970, 1, 1), LocalTime.of(0, 0, 0), ZoneOffset.UTC)||<

は、ダメで

LocalDateTime.ofEpochSecond(3600*9L + 1, 0, ZoneOffset.UTC)
OffsetDateTime.of(LocalDate.of(1970, 1, 1), LocalTime.of(0, 0, 1), ZoneOffset.UTC)||<

ならばOKなのである。
2038年問題の 2038-01-19 03:14:07 UTC でも同様のことが、TIMESTAMP では、
ERROR 1292: 1292: Incorrect datetime value:
のエラーを引き起こします。

解決策として、DATETIME型を用いることなのですが、
DATETIME型の場合、、

0000-01-01 00:00:00
9999-12-31 23:59:59

という値を入れることができて、
Javaでは、

OffsetDateTime.of(LocalDate.of(0, 1, 1), LocalTime.of(0, 0, 0), ZoneOffset.UTC)

OffsetDateTime.of(LocalDate.of(9999, 12, 31), LocalTime.of(14, 59, 59), ZoneOffset.UTC)

でデータをセットできます。

サクラエディタ マークダウン表を作成するマクロ

マークダウン表を作る場合、オンラインの以下も便利なのですが、、
https://www.tablesgenerator.com/markdown_tables

下記、紹介の引用です。
たいへん有難い。
[小ネタ]backlogテキスト整形のルール~表の生成技~
ヌーラボの backLog の表を作成する場合です。
登録するマクロ
backLogTable.mac

//キーボードマクロのファイル
ReplaceAll('"', '', 28);	// すべて置換
ReplaceAll('\\r\\n', '\\r', 28);	// すべて置換
ReplaceAll('\\n', '&br;', 28);	// すべて置換
ReplaceAll('\\r', '|\\r\\n', 28);	// すべて置換
ReplaceAll('\\t', '|', 28);	// すべて置換
ReplaceAll('^', '|', 28);	// すべて置換
ReDraw(0);	// 再描画
GoFileTop(0);	// ファイルの先頭に移動
GoLineEnd(0);	// 行末に移動(折り返し単位)
Char(104);	// 文字入力

では、git-hub のマークダウンの表を作成するマクロを書いてみましょう。
githubTable.mac

//キーボードマクロのファイル
GoFileTop(0);	// ファイルの先頭に移動
GoLineEnd_Sel(0);	// (選択)行末に移動(折り返し単位)
Copy(0);	// コピー
Right(0);	// カーソル右移動
Char(13);	// 文字入力
Paste(0);	// 貼り付け
GoLineTop_Sel(0);	// (選択)行頭に移動(折り返し単位)
ReplaceAll('[^\\t\\r\\n]+', ':---', 188);	// すべて置換
ReplaceAll('"', '', 28);	// すべて置換
ReplaceAll('\\r\\n', '\\r', 28);	// すべて置換
ReplaceAll('\\n', '&br;', 28);	// すべて置換
ReplaceAll('\\r', '|\\r\\n', 28);	// すべて置換
ReplaceAll('\\t', '|', 28);	// すべて置換
ReplaceAll('^', '|', 28);	// すべて置換
ReDraw(0);	// 再描画

タブで区切ったテキストから
以下の様に変換します。
変換前

A	B	C
11	12	13
21	22	23
31	32	33

変換後

|A|B|C|
|:---|:---|:---|
|11|12|13|
|21|22|23|
|31|32|33|

サクラエディタ ユニコード変換マクロ

ユニコードのままの文字列を元の2byte文字に変換するマクロ

unicodeEscape.js

var cnt = GetLineCount(0);
var result = ''; 

for (var i = 1; i <= cnt; i++) {
    // i行目を取得
    var str = GetLineStr(i);
    // \u.... の文字列の配列で取得、大文字小文字は問わない
    uniArray = str.match(/\\u.{4}/ig);
    if (uniArray) { 
        for (var j = 0, len = uniArray.length; j < len; j++) {
            str = str.replace(uniArray[j], String.fromCharCode(uniArray[j].replace('\\u', '0x')))
        }
    }
    result += str;
}
SelectAll(0);
InsText(result);

(変換の例)

 \u3042\u3044\u3046\u3048\u304a_abc123_\uff76\uff77\uff78\uff79\uff7a

マクロ実行後

あいうえお_abc123_カキクケコ

サクラエディタのマクロの登録は、下記を参考に。。。
サクラエディタで snakecase ⇔ camelcase - Oboe吹きプログラマの黙示録

サクラエディタのマクロで JSON を整形する

JSON整形をブラウザでオンラインツールなどで行うなら、過去に紹介した
JSON Editor の紹介 - Oboe吹きプログラマの黙示録
を使えば良いが、
サクラエディタのマクロに登録しておくものがある。
しかし、ネットで紹介されてるマクロは、複数の項目 or 値がある時、
区切りのカンマ文字の前で改行している。カンマ区切りの後で改行してほしい。
そこで、ちょっと手を加えてあげればよい。
登録するマクロを次のようにする。

jsonpretty.js

var cnt = GetLineCount(0);
var textAll = "";
for(var i = 1; i <= cnt; i++){
   var str = GetLineStr(i).replace(/\n/,"").replace(/\r\n/,"");
   textAll += str;
}
var editJson = "";
var innerFlg = false;
var intentLv = 0;
var quoteType = [];
var blockType = [];
var preChr = "";
var indent = "\t";   // インデント
var rtnChr = "\r\n"; // 改行
for(var i=0; i < textAll.length; i++){
   var prefix = "";
   var suffix = "";
   var rtn = "";
   var c = textAll.substring(i, i + 1);
   if (!innerFlg){
      if (c == " "){
         continue;
      }
      if (c == "\"" || c == "'"){
         innerFlg = true;
         quoteType[quoteType.length] = c;
         // JSON または 配列の1つ目の要素の時はスペース1個を追加
         var chkText = editJson.replace(/( |\t|\r\n|\n)/g, "");
         if (chkText.length > 0){
            var preChar = chkText.substring(chkText.length - 1, chkText.length);
         }
      }else{
         if (c == "{" || c == "["){
            suffix = "";
            intentLv = intentLv + 1;
            blockType[blockType.length] = c;
            for(var j = 0; j < intentLv; j++){
               suffix = suffix + indent;
            }
            rtn = rtnChr;
         }
         if (c == "}" || c == "]"){
            prefix = rtnChr;
            intentLv = intentLv - 1;
            blockType.pop(1);
         }
         if (c == ","){
            rtn = rtnChr;
            for(var j = 0; j < intentLv; j++){
               suffix = suffix + indent;
            }
         }
         if (c == ":"){
            c = " : ";
         }
      }
   }else{
      if (quoteType.length > 0 && c == quoteType[quoteType.length - 1]){
         innerFlg = false;
      }
   }
   if (prefix != ""){
      for(var j=0; j < intentLv; j++){
         prefix = prefix + indent;
      }
   }
   var line = prefix + c + rtn + suffix;
   if (preChr == "" && line.substring(0, 1) == rtnChr){
      line = line.substring(1);
   }
   editJson = editJson + line;
   preChr = line.substring(line.length - 1, line.length);
}
SelectAll(0);
InsText(editJson + rtnChr);

これをマクロとして登録すれば良い。
サクラエディタのマクロの登録は、下記を参考に。。。
サクラエディタで snakecase ⇔ camelcase - Oboe吹きプログラマの黙示録

APIのテストには、Insomnia

APIのテストに、curl コマンドばかり使っていたが、
Insomnia という便利なツールがあるんです。

https://insomnia.rest/

Insomnia Docs

https://docs.insomnia.rest/insomnia/get-started

Insomnia のWindows版ダウンロード
https://insomnia.rest/download


いろんな開発者に出会ったけど、curl コマンドを使うことに変なプライドがあるように
見受けられる場面があって辟易していた。

SourceTree における変更の破棄

Windows環境では SourceTreeをアップデートしてもツールバーの破棄ボタン、

あるいは、変更ソースを右クリックして表示されるコンテキストメニュー
破棄は消えていないが、

Mac環境にインストールした SoureceTree ではこの「破棄」が存在しない。
GITコマンドで破棄の操作、git checkout の操作をした方が良いかも。
戻したい対象を指定する場合

git checkout {対象ソースのPATH}

全て戻したい場合

git checkout .

同様に戸惑った人はいるみたいで、下記にありました。隠れてるんですね。
https://qiita.com/Kobutorina_hato/items/9732db0e96fe0685d48f

TSVを総称型指定のクラスで読込み

昔作成して公開している CSVの読み書き、
GitHub - yipuran/yipuran-csv: Java CSV read and write
の中の
csvobject · yipuran/yipuran-csv Wiki · GitHub
から、CsvObject を継承して、TSV を総称型指定のクラスで読込む。
変更点は、

  • org.yipuran.csv4j.CSVReader からデリミタ指定をカンマ区切りからタブ文字に変えるだけ
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Stream;
import org.yipuran.csv.BOMfunction;
import org.yipuran.csv.CsvObject;
import org.yipuran.csv4j.CSVReader;
import org.yipuran.csv4j.ProcessingException;
/**
 * TSV を総称型指定のクラスで読込み
 */
public class TsvObject<T> extends CsvObject<T>{
    private static final char DELIMITER = '\t';
    private boolean blankIsNull = false;
    private Class<T> cls;
    private List<Class<?>> typelist;
    private List<Method> methodlist;
    private DateTimeFormatter dateFormatter =  DateTimeFormatter.ISO_LOCAL_DATE;//  DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private DateTimeFormatter localdatetimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
    private DateTimeFormatter timeFormatter = DateTimeFormatter.ISO_LOCAL_TIME;
    private Function<String, Boolean> booleanReader = s -> Boolean.parseBoolean(s.toLowerCase());

    /**
     * コンストラクタ.
     * @param t 可変長引数、指定しても使用はされない
     */
    @SuppressWarnings("unchecked")
    public TsvObject(T...t) {
        cls = (Class<T>)t.getClass().getComponentType();
    }
    /**
     * ブランク→null指定.
     * @param true=ブランク、",," は、null として読み込む。
     */
    public void setBlanknull(boolean blankIsNull) {
        this.blankIsNull = blankIsNull;
    }

    /**
     * 総称型指定TSV読込み実行.
     * @param inReader InputStreamReader
     * @param biconsumer コンテンツ行BiConsumer、TSV行読込みカウント(1始まり)と総称型Tのオブジェクト
     * @throws IOException
     * @throws ProcessingException
     */
    @Override
    public void read(InputStreamReader inReader, BiConsumer<Integer, T> biconsumer) throws IOException, ProcessingException{
        CSVReader reader = new CSVReader(new BufferedReader(inReader),
                                         Charset.forName(inReader.getEncoding()),
                                         DELIMITER, getComment(),
                                         blankIsNull);
        try{
            typelist = new ArrayList<>();
            methodlist = new ArrayList<>();
            int lineCount = 0;
            while(true){
                try{
                    List<String> fields = reader.readLine();
                    if (fields.size()==0) break;
                    if (isHasHeader() && lineCount==0){
                        String rep = fields.get(0);
                        if (BOMfunction.match(rep)) {
                            fields.remove(0);
                            fields.add(0, BOMfunction.chop(rep));
                        }
                        for(String f:fields){
                            try{
                                Class<?> c =  cls.getDeclaredField(f).getType();
                                Method m = cls.getDeclaredMethod("set" + f.substring(0, 1).toUpperCase() + f.substring(1), c);
                                typelist.add(c);
                                methodlist.add(m);
                            }catch(NoSuchFieldException | NoSuchMethodException e){
                                typelist.add(null);
                                methodlist.add(null);
                            }
                        }
                    }else{
                        T t = cls.getConstructor().newInstance();
                        int i=0;
                        for(String f:fields){
                            Method m = methodlist.get(i);
                            if (m != null){
                                setValue(m, t, i, f);
                            }
                            i++;
                        }
                        biconsumer.accept(lineCount, t);
                    }
                }catch(Exception e){
                    throw new ProcessingException(e, reader.getLineNumber());
                }
                lineCount++;
            }
        }finally{
            reader.close();
        }
    }
    /**
     * 総称型指定TSV読込み結果Stream生成.
     * @param inReader InputStreamReader
     * @return 総称型 T のStream
     * @throws IOException
     * @throws ProcessingException
     */
    @Override
    public Stream<T> read(InputStreamReader inReader) throws IOException, ProcessingException{
        Stream.Builder<T> builder = Stream.builder();
        CSVReader reader = new CSVReader(new BufferedReader(inReader),
                                         Charset.forName(inReader.getEncoding()),
                                         DELIMITER,
                                         getComment(),
                                         blankIsNull);
        try{
            typelist = new ArrayList<>();
            methodlist = new ArrayList<>();
            int lineCount = 0;
            while(true){
                try{
                    List<String> fields = reader.readLine();
                    if (fields.size()==0) break;
                    if (isHasHeader() && lineCount==0){
                        String rep = fields.get(0);
                        if (BOMfunction.match(rep)) {
                            fields.remove(0);
                            fields.add(0, BOMfunction.chop(rep));
                        }
                        for(String f:fields){
                            try{
                                Class<?> c =  cls.getDeclaredField(f).getType();
                                Method m = cls.getDeclaredMethod("set" + f.substring(0, 1).toUpperCase() + f.substring(1), c);
                                typelist.add(c);
                                methodlist.add(m);
                            }catch(NoSuchFieldException | NoSuchMethodException e){
                                typelist.add(null);
                                methodlist.add(null);
                            }
                        }
                    }else{
                        T t = cls.getConstructor().newInstance();
                        int i=0;
                        for(String f:fields){
                            Method m = methodlist.get(i);
                            if (m != null){
                                setValue(m, t, i, f);
                            }
                            i++;
                        }
                        builder.add(t);
                    }
                }catch(Exception e){
                    throw new ProcessingException(e, reader.getLineNumber());
                }
                lineCount++;
            }
        }finally{
            reader.close();
        }
        return builder.build();
    }
    /**
     * 総称型指定TSV読込み実行(コンバーター指定).
     * @param inReader InputStreamReader
     * @param converter TSV1行分の文字列リストから、総称型Tを生成取得するコンバーター
     * @param biconsumer コンテンツ行BiConsumer、TSV行読込みカウント(1始まり)と総称型Tのオブジェクト
     * @throws IOException
     * @throws ProcessingException
     */
    @Override
    public void read(InputStreamReader inReader, Function<List<String>, T> converter,  BiConsumer<Integer, T> biconsumer)
    throws IOException, ProcessingException{
        CSVReader reader = new CSVReader(new BufferedReader(inReader),
                                         Charset.forName(inReader.getEncoding()),
                                         DELIMITER,
                                         getComment(),
                                         blankIsNull);
        try{
            methodlist = new ArrayList<>();
            int lineCount = 0;
            while(true){
                try{
                    List<String> fields = reader.readLine();
                    if (fields.size()==0) break;
                    if (!isHasHeader() || lineCount > 0){
                        biconsumer.accept(lineCount, converter.apply(fields));
                    }
                }catch(Exception e){
                    throw new ProcessingException(e, reader.getLineNumber());
                }
                lineCount++;
            }
        }finally{
            reader.close();
        }
    }
    /**
     * 総称型指定TSV読込み結果Stream生成(コンバーター指定).
     * @param inReader InputStreamReader
     * @param converter TSV1行分の文字列リストから、総称型Tを生成取得するコンバーター
     * @return 総称型 T のStream
     * @throws IOException
     * @throws ProcessingException
     */
    @Override
    public Stream<T> read(InputStreamReader inReader, Function<List<String>, T> converter)
    throws IOException, ProcessingException{
        Stream.Builder<T> builder = Stream.builder();
        CSVReader reader = new CSVReader(new BufferedReader(inReader),
                                         Charset.forName(inReader.getEncoding()),
                                         DELIMITER,
                                         getComment(),
                                         blankIsNull);
        try{
            int lineCount = 0;
            while(true){
                try{
                    List<String> fields = reader.readLine();
                    if (fields.size()==0) break;
                    if (!isHasHeader() || lineCount > 0){
                        builder.add(converter.apply(fields));
                    }
                }catch(Exception e){
                    throw new ProcessingException(e, reader.getLineNumber());
                }
                lineCount++;
            }
        }finally{
            reader.close();
        }
        return builder.build();
    }

    private void setValue(Method m, Object obj, int n, String str) throws NoSuchMethodException, SecurityException
    , IllegalAccessException, IllegalArgumentException {
        try{
            if (typelist.get(n).isPrimitive()) {
                if (blankIsNull && str==null) return;
                Class<?> c = typelist.get(n);
                if (c.equals(int.class)) {
                    m.invoke(obj, Integer.parseInt(str));
                }else if(c.equals(long.class)) {
                    m.invoke(obj, Long.parseLong(str));
                }else if(c.equals(double.class)) {
                    m.invoke(obj, Double.parseDouble(str));
                }else if(c.equals(short.class)) {
                    m.invoke(obj, Short.parseShort(str));
                }else if(c.equals(float.class)) {
                    m.invoke(obj, Float.parseFloat(str));
                }else if(c.equals(boolean.class)) {
                    m.invoke(obj, booleanReader.apply(str));
                }
            }else{
                if (typelist.get(n).equals(String.class)) {
                    m.invoke(obj, str);
                }else if(typelist.get(n).equals(Boolean.class)) {
                    m.invoke(obj, booleanReader.apply(str));
                }else if(typelist.get(n).equals(LocalDate.class)) {
                    m.invoke(obj, LocalDate.parse(str, dateFormatter));
                }else if(typelist.get(n).equals(LocalDateTime.class)) {
                    m.invoke(obj, LocalDateTime.parse(str, localdatetimeFormatter));
                }else if(typelist.get(n).equals(LocalTime.class)) {
                    m.invoke(obj, LocalTime.parse(str, timeFormatter));
                }else{
                    Method getter = typelist.get(n).getDeclaredMethod("valueOf", String.class);
                    m.invoke(obj, getter.invoke(null, str));
                }
            }
        }catch(InvocationTargetException e){
        }
    }
}

(使用例)

TSVの1行を任意クラスFoo として読み込む。ただしヘッダ1行目がフィールド名である。

TsvObject<Foo> co = new TsvObject<>();
co.setBlanknull(true);
co.read(new InputStreamReader(in, StandardCharsets.UTF_8), (i, f)->{
    // i=読み出し行番号 1 始まり、
    // f=Fooインスタンス(1行 → Foo 変換結果)
    System.out.println("["+i+"]# "+f.toString());
});
in.close();
TsvObject<Foo> co = new TsvObject<>();
co.setBlanknull(true);
co.read(new InputStreamReader(in, StandardCharsets.UTF_8)).forEach(f->{
    System.out.println(f.toString());
});
in.close();