You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中精准计算出生日期总天数(解决闰年计算误差)

修正年龄计算器的日期计算精度问题

我正在开发一款年龄计算器App,需要以多种格式展示年龄,但当前代码未精准处理闰年,导致总天数、周数等计算结果与预期不符。以下是输出对比:

  • 年龄:25年03月28天 //输出正确
  • 总月数:303个月28天 //输出正确
  • 总周数:1322周3天//正确应为:1321周4天
  • 总天数:9249天 //正确应为:9251天
  • 总小时数:221976小时 //正确应为:222,024小时

附上当前代码,请问如何修正以实现精准计算?

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:intl/intl.dart';

class AgeCalculator extends StatefulWidget {
  const AgeCalculator({super.key});

  @override
  State<AgeCalculator> createState() => _AgeCalculatorState();
}

class _AgeCalculatorState extends State<AgeCalculator> {
  final TextEditingController _dateController = TextEditingController();
  final currentDate = DateTime.now();
  String _ageResult = '';

  @override
  void initState() {
    super.initState();
    _dateController.text = DateFormat('yyyy-MM-dd').format(currentDate);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        backgroundColor: Colors.grey[200],
        title: Text(
          'Age Calculator',
          style: TextStyle(
            fontWeight: FontWeight.w600,
            fontFamily: GoogleFonts.audiowide().fontFamily,
          ),
        ),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.start,
        children: <Widget>[
          const SizedBox(
            height: 20,
          ),
          Text(
            'Todays Date : ${DateFormat('dd-MM-yyyy').format(currentDate)}',
            style: TextStyle(
              fontSize: 20,
              fontWeight: FontWeight.w400,
              fontFamily: GoogleFonts.audiowide().fontFamily,
            ),
          ),
          const SizedBox(height: 20),
          Text(
            'Select Your Date of Birth : ',
            style: TextStyle(
              fontFamily: GoogleFonts.audiowide().fontFamily,
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(20.0),
            child: TextField(
              controller: _dateController,
              readOnly: true,
              onTap: () async {
                final DateTime? date = await showDatePicker(
                  context: context,
                  initialDate: DateTime.now(),
                  firstDate: DateTime(1900),
                  lastDate: DateTime(2100),
                );
                if (date != null) {
                  _dateController.text = DateFormat('yyyy-MM-dd').format(date);
                }
              },
              decoration: const InputDecoration(
                border: OutlineInputBorder(),
                labelText: 'Select Date',
                hintText: 'Select Date',
                suffixIcon: Icon(Icons.calendar_today),
              ),
            ),
          ),
          const SizedBox(height: 20),
          ElevatedButton(
            onPressed: () {
              final birthDate =
                  DateFormat('yyyy-MM-dd').parse(_dateController.text);
              final age = calculateAge(birthDate, currentDate);
              setState(() {
                _ageResult = '$age';
              });
            },
            child: Text(
              'Calculate',
              style: TextStyle(
                fontFamily: GoogleFonts.audiowide().fontFamily,
                fontWeight: FontWeight.w600,
              ),
            ),
          ),
          Text(
            'Your Age is : $_ageResult',
            style: TextStyle(
              fontSize: 20,
              fontWeight: FontWeight.w400,
              fontFamily: GoogleFonts.audiowide().fontFamily,
            ),
          ),
          if (_ageResult.isNotEmpty) ...[
            const SizedBox(height: 10),
            Text(
              'Total Months: ${calculateTotalMonths(_ageResult)} months ${calculateRemainingDays(_ageResult)} days',
              style: TextStyle(
                fontSize: 16,
                fontWeight: FontWeight.w400,
                fontFamily: GoogleFonts.audiowide().fontFamily,
              ),
            ),
            Text(
              'Total Weeks: ${calculateTotalWeeks(_ageResult)} weeks ${calculateTotalDaysAfterWeek(_ageResult)} days',
              style: TextStyle(
                fontSize: 16,
                fontWeight: FontWeight.w400,
                fontFamily: GoogleFonts.audiowide().fontFamily,
              ),
            ),
            Text(
              'Total Hours: ${calculateTotalHours(_ageResult)} hours',
              style: TextStyle(
                fontSize: 16,
                fontWeight: FontWeight.w400,
                fontFamily: GoogleFonts.audiowide().fontFamily,
              ),
            ),
            Text(
              'Total Days: ${calculateTotalDays(_ageResult)} days',
              style: TextStyle(
                fontSize: 16,
                fontWeight: FontWeight.w400,
                fontFamily: GoogleFonts.audiowide().fontFamily,
              ),
            ),
          ],
        ],
      ),
    );
  }

  String calculateAge(DateTime birthDate, DateTime currentDate) {
    int years = currentDate.year - birthDate.year;
    int months = currentDate.month - birthDate.month;
    int days = currentDate.day - birthDate.day;

    if (months < 0 || (months == 0 && days < 0)) {
      years--;
      months += 12;
    }

    return '$years years $months months $days days';
  }

  int calculateTotalMonths(String age) {
    List<String> parts = age.split(' ');
    int years = int.parse(parts[0]);
    int months = int.parse(parts[2]);
    return years * 12 + months;
  }

  int calculateRemainingDays(String age) {
    List<String> parts = age.split(' ');
    return int.parse(parts[4]);
  }

  int calculateTotalWeeks(String age) {
    List<String> parts = age.split(' ');
    int totalDays = int.parse(parts[0]) * 365 +
        countLeapYears(int.parse(parts[0])) +
        int.parse(parts[2]) * 30 +
        int.parse(parts[4]);
    return (totalDays / 7).ceil();
  }

  int calculateTotalDaysAfterWeek(String age) {
    List<String> parts = age.split(' ');
    int totalDays = int.parse(parts[0]) * 365 +
        int.parse(parts[2]) * 30 +
        int.parse(parts[4]);
    return (totalDays % 7).ceil();
  }

  int calculateTotalHours(String age) {
    List<String> parts = age.split(' ');
    int totalDays = int.parse(parts[0]) * 365 +
        countLeapYears(int.parse(parts[0])) +
        int.parse(parts[2]) * 30 +
        int.parse(parts[4]);
    return totalDays * 24;
  }

  int calculateTotalDays(String age) {
    List<String> parts = age.split(' ');
    return int.parse(parts[0]) * 365 +
        countLeapYears(int.parse(parts[0])) +
        int.parse(parts[2]) * 30 +
        int.parse(parts[4]);
  }

  int countLeapYears(int years) {
    // Logic to count leap years
    return (years / 4).floor() - (years / 100).floor() + (years / 400).floor();
  }
}

问题根源

  1. 闰年计算逻辑偏差:仅按总年份数估算闰年,未考虑用户生日是否实际经历该闰年(如生日在2月29日后,当年闰年不计入)。
  2. 月份天数固定估算:用30天统一计算所有月份天数,忽略不同月份的实际天数差异。
  3. 字符串解析传递数据:通过拆分_ageResult字符串计算,易出错且无法利用DateTime的精准计算能力。
  4. 周数与剩余天数逻辑不一致:两个函数的总天数计算逻辑不同,导致结果矛盾。

修正后完整代码

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:intl/intl.dart';

class AgeCalculator extends StatefulWidget {
  const AgeCalculator({super.key});

  @override
  State<AgeCalculator> createState() => _AgeCalculatorState();
}

class _AgeCalculatorState extends State<AgeCalculator> {
  final TextEditingController _dateController = TextEditingController();
  final currentDate = DateTime.now();
  
  // 存储结构化年龄数据,避免字符串解析错误
  int? years;
  int? months;
  int? days;
  int? totalDays;

  @override
  void initState() {
    super.initState();
    _dateController.text = DateFormat('yyyy-MM-dd').format(currentDate);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        backgroundColor: Colors.grey[200],
        title: Text(
          'Age Calculator',
          style: TextStyle(
            fontWeight: FontWeight.w600,
            fontFamily: GoogleFonts.audiowide().fontFamily,
          ),
        ),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.start,
        children: <Widget>[
          const SizedBox(height: 20),
          Text(
            'Todays Date : ${DateFormat('dd-MM-yyyy').format(currentDate)}',
            style: TextStyle(
              fontSize: 20,
              fontWeight: FontWeight.w400,
              fontFamily: GoogleFonts.audiowide().fontFamily,
            ),
          ),
          const SizedBox(height: 20),
          Text(
            'Select Your Date of Birth : ',
            style: TextStyle(
              fontFamily: GoogleFonts.audiowide().fontFamily,
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(20.0),
            child: TextField(
              controller: _dateController,
              readOnly: true,
              onTap: () async {
                final DateTime? date = await showDatePicker(
                  context: context,
                  initialDate: DateTime.now(),
                  firstDate: DateTime(1900),
                  lastDate: DateTime(2100),
                );
                if (date != null) {
                  _dateController.text = DateFormat('yyyy-MM-dd').format(date);
                }
              },
              decoration: const InputDecoration(
                border: OutlineInputBorder(),
                labelText: 'Select Date',
                hintText: 'Select Date',
                suffixIcon: Icon(Icons.calendar_today),
              ),
            ),
          ),
          const SizedBox(height: 20),
          ElevatedButton(
            onPressed: () {
              final birthDate = DateFormat('yyyy-MM-dd').parse(_dateController.text);
              calculatePreciseAge(birthDate, currentDate);
              setState(() {});
            },
            child: Text(
              'Calculate',
              style: TextStyle(
                fontFamily: GoogleFonts.audiowide().fontFamily,
                fontWeight: FontWeight.w600,
              ),
            ),
          ),
          if (years != null) ...[
            Text(
              'Your Age is : $years years $months months $days days',
              style: TextStyle(
                fontSize: 20,
                fontWeight: FontWeight.w400,
                fontFamily: GoogleFonts.audiowide().fontFamily,
              ),
            ),
            const SizedBox(height: 10),
            Text(
              'Total Months: ${years! * 12 + months!} months $days days',
              style: TextStyle(
                fontSize: 16,
                fontWeight: FontWeight.w400,
                fontFamily: GoogleFonts.audiowide().fontFamily,
              ),
            ),
            Text(
              'Total Weeks: ${totalDays! ~/ 7} weeks ${totalDays! % 7} days',
              style: TextStyle(
                fontSize: 16,
                fontWeight: FontWeight.w400,
                fontFamily: GoogleFonts.audiowide().fontFamily,
              ),
            ),
            Text(
              'Total Hours: ${NumberFormat('#,###').format(totalDays! * 24)} hours',
              style: TextStyle(
                fontSize: 16,
                fontWeight: FontWeight.w400,
                fontFamily: GoogleFonts.audiowide().fontFamily,
              ),
            ),
            Text(
              'Total Days: $totalDays days',
              style: TextStyle(
                fontSize: 16,
                fontWeight: FontWeight.w400,
                fontFamily: GoogleFonts.audiowide().fontFamily,
              ),
            ),
          ],
        ],
      ),
    );
  }

  void calculatePreciseAge(DateTime birthDate, DateTime currentDate) {
    // 用DateTime自带方法精准计算总天数,自动处理闰年和月份差异
    totalDays = currentDate.difference(birthDate).inDays;

    // 计算年、月、日格式的年龄
    years = currentDate.year - birthDate.year;
    months = currentDate.month - birthDate.month;
    days = currentDate.day - birthDate.day;

    // 调整未满月的情况,获取上个月实际天数
    if (days! < 0) {
      months!--;
      days = DateTime(currentDate.year, currentDate.month, 0).day + days!;
    }
    // 调整未跨年的情况
    if (months! < 0) {
      years!--;
      months! += 12;
    }
  }
}

关键改进说明

  1. 精准总天数计算:通过currentDate.difference(birthDate).inDays自动处理所有闰年、月份天数差异,无需手动估算。
  2. 结构化数据存储:用单独变量存储年、月、日、总天数,避免字符串解析带来的错误。
  3. 真实月份天数获取:调整天数时,通过DateTime(year, month, 0)获取上个月的实际天数,替代固定30天的估算。
  4. 统一周数计算逻辑:基于总天数直接计算周数和剩余天数,确保结果一致。
  5. 格式化小时数:用NumberFormat添加千位分隔符,符合预期输出格式。

内容的提问来源于stack exchange,提问作者Code Error

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 08:49:58