0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

あれ、月齢の計算って面倒じゃね?

Last updated at Posted at 2025-12-06

Motivation

個人で作った、子供の成長を記録するアプリで、DateTime型の記録日を月齢毎に分けて表示したくなった⇨DateTimeを月齢へ変換する関数を作成

Problems

いざやろうとすると、なんか考慮しなきゃいけないこと多くね?って気づいた

  • 月またぎ
  • 誕生日付きよりも前の月
  • 閏年
  • 基準日が誕生日よりも早い
  • 誕生日が月末
  • 法律上の誕生日 (なんだそれ)

Action

とりあえずテストで外堀を埋める

その後に、テストが通るよう条件分岐しながらyearとmonthを算出

LunarAge convertToLunarAge({
  required DateTime datetime,
  required DateTime birthday,
}) {
  // From the Japan low, the base date is one day before of the birthday
  // ref: https://sayumi-aug.hateblo.jp/entry/2022/03/01/160427
  final DateTime actualBirthday = birthday.add(Duration(days: -1));
  final Duration duration = datetime.difference(actualBirthday);
  final int year = duration.inDays ~/ 365;
  int month = 0;

  if (_isFasterThanBirth(datetime, actualBirthday)) {
    return LunarAge(year: year, month: month);
  }

  if (actualBirthday.month > datetime.month) {
    month = datetime.month + 12 - actualBirthday.month;
  } else {
    month = datetime.month - actualBirthday.month;
  }

  if (datetime.day < actualBirthday.day) {
    month -= 1;
  }

  if (datetime.month != actualBirthday.month &&
      _isComparisonBetweenMonthEnds(datetime, actualBirthday)) {
    month += 1;
  }

  return LunarAge(year: year, month: month);
}

LunarAgeGroup groupByLunarAge(List<RecordItem> records, DateTime birthday) {
  final LunarAgeGroup lunarAgeGroup = {};

  for (RecordItem record in records) {
    final LunarAge lunarAge = convertToLunarAge(
      datetime: record.date,
      birthday: birthday,
    );
    if (lunarAgeGroup[lunarAge] == null) {
      lunarAgeGroup[lunarAge] = [];
    }
    lunarAgeGroup[lunarAge]!.add(record);
  }

  return lunarAgeGroup;
}

bool _isFasterThanBirth(DateTime datetime, DateTime birthday) {
  return birthday.year >= datetime.year && birthday.month > datetime.month;
}

bool _isComparisonBetweenMonthEnds(DateTime datetime, DateTime birthday) {
  return _isMonthEnd(datetime) && _isMonthEnd(birthday);
}

bool _isMonthEnd(DateTime datetime) {
  const Map<int, int> endDay = {
    1: 31,
    2: 28,
    3: 31,
    4: 30,
    5: 31,
    6: 30,
    7: 31,
    8: 31,
    9: 30,
    10: 31,
    11: 30,
    12: 31,
  };

  return datetime.day >= endDay[datetime.month]!;
}

最後に、既存の記録を月齢毎に分けて表示

Zaregoto

既に先人がやっていそうだが、とりあえず面白くはあるので車輪の再発明でも良いかと思っている今日この頃。後絶対テストは足りていない。多分閏年挟むと破綻する。でもってWidgetの使い方下手じゃね?もっとシンプルな方法あるはず。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?