1 #include "calendar.h"
2 
3 #include <algorithm>
4 #include <array>
5 #include <cmath>
6 #include <cstdlib>
7 #include <limits>
8 #include <string>
9 
10 #include "cata_assert.h"
11 #include "debug.h"
12 #include "options.h"
13 #include "rng.h"
14 #include "string_formatter.h"
15 #include "translations.h"
16 
17 /** How much light moon provides per lit-up quarter (Full-moon light is four times this value) */
18 static constexpr double moonlight_per_quarter = 2.25;
19 
20 // Divided by 100 to prevent overflowing when converted to moves
21 const int calendar::INDEFINITELY_LONG( std::numeric_limits<int>::max() / 100 );
22 const time_duration calendar::INDEFINITELY_LONG_DURATION(
23     time_duration::from_turns( std::numeric_limits<int>::max() ) );
24 static bool is_eternal_season = false;
25 static int cur_season_length = 1;
26 
27 const time_point calendar::before_time_starts = time_point::from_turn( -1 );
28 const time_point calendar::turn_zero = time_point::from_turn( 0 );
29 
30 time_point calendar::start_of_cataclysm = calendar::turn_zero;
31 time_point calendar::start_of_game = calendar::turn_zero;
32 time_point calendar::turn = calendar::turn_zero;
33 season_type calendar::initial_season = SPRING;
34 
35 // Internal constants, not part of the calendar interface.
36 // Times for sunrise, sunset at equinoxes
37 
38 /** Hour of sunrise at winter solstice */
39 static constexpr int sunrise_winter = 7;
40 
41 /** Hour of sunrise at summer solstice */
42 static constexpr int sunrise_summer = 5;
43 
44 /** Hour of sunrise at fall and spring equinox */
45 static constexpr int sunrise_equinox = ( sunrise_summer + sunrise_winter ) / 2;
46 
47 /** Hour of sunset at winter solstice */
48 static constexpr int sunset_winter = 17;
49 
50 /** Hour of sunset at summer solstice */
51 static constexpr int sunset_summer = 21;
52 
53 /** Hour of sunset at fall and spring equinox */
54 static constexpr int sunset_equinox = ( sunset_summer + sunset_winter ) / 2;
55 
56 // How long, does sunrise/sunset last?
57 static const time_duration twilight_duration = 1_hours;
58 
default_daylight_level()59 double default_daylight_level()
60 {
61     return 100.0;
62 }
63 
lunar_month()64 time_duration lunar_month()
65 {
66     return 29.530588853 * 1_days;
67 }
68 
69 namespace io
70 {
71 // *INDENT-OFF*
72 template<>
enum_to_string(moon_phase phase_num)73 std::string enum_to_string<moon_phase>( moon_phase phase_num )
74 {
75     switch( phase_num ) {
76         case moon_phase::MOON_NEW: return "MOON_NEW";
77         case moon_phase::MOON_WAXING_CRESCENT: return "MOON_WAXING_CRESCENT";
78         case moon_phase::MOON_HALF_MOON_WAXING: return "MOON_HALF_MOON_WAXING";
79         case moon_phase::MOON_WAXING_GIBBOUS: return "MOON_WAXING_GIBBOUS";
80         case moon_phase::MOON_FULL: return "MOON_FULL";
81         case moon_phase::MOON_WANING_CRESCENT: return "MOON_WANING_CRESCENT";
82         case moon_phase::MOON_HALF_MOON_WANING: return "MOON_HALF_MOON_WANING";
83         case moon_phase::MOON_WANING_GIBBOUS: return "MOON_WANING_GIBBOUS";
84         case moon_phase::MOON_PHASE_MAX: break;
85     }
86     debugmsg( "Invalid moon_phase %d", phase_num );
87     abort();
88 }
89 // *INDENT-ON*
90 } // namespace io
91 
get_moon_phase(const time_point & p)92 moon_phase get_moon_phase( const time_point &p )
93 {
94     const time_duration moon_phase_duration = calendar::season_from_default_ratio() * lunar_month();
95     // Switch moon phase at noon so it stays the same all night
96     const int num_middays = to_days<int>( p - calendar::turn_zero + 1_days / 2 );
97     const time_duration nearest_midnight = num_middays * 1_days;
98     const double phase_change = nearest_midnight / moon_phase_duration;
99     const int current_phase = static_cast<int>( std::round( phase_change * MOON_PHASE_MAX ) ) %
100                               static_cast<int>( MOON_PHASE_MAX );
101     return static_cast<moon_phase>( current_phase );
102 }
103 
104 // TODO: Refactor sunrise / sunset
105 // The only difference between them is the start_hours array
sunrise(const time_point & p)106 time_point sunrise( const time_point &p )
107 {
108     static_assert( static_cast<int>( SPRING ) == 0,
109                    "Expected spring to be the first season.  If not, code below will use wrong index into array" );
110 
111     static const std::array<int, 4> start_hours = { { sunrise_equinox, sunrise_summer, sunrise_equinox, sunrise_winter, } };
112     const size_t season = static_cast<size_t>( season_of_year( p ) );
113     cata_assert( season < start_hours.size() );
114 
115     const double start_hour = start_hours[season];
116     const double end_hour = start_hours[( season + 1 ) % 4];
117 
118     const double into_month = static_cast<double>( day_of_season<int>( p ) ) / to_days<int>
119                               ( calendar::season_length() );
120     const double time = start_hour * ( 1.0 - into_month ) + end_hour * into_month;
121 
122     const time_point midnight = p - time_past_midnight( p );
123     return midnight + time_duration::from_minutes( static_cast<int>( time * 60 ) );
124 }
125 
sunset(const time_point & p)126 time_point sunset( const time_point &p )
127 {
128     static_assert( static_cast<int>( SPRING ) == 0,
129                    "Expected spring to be the first season.  If not, code below will use wrong index into array" );
130 
131     static const std::array<int, 4> start_hours = { { sunset_equinox, sunset_summer, sunset_equinox, sunset_winter, } };
132     const size_t season = static_cast<size_t>( season_of_year( p ) );
133     cata_assert( season < start_hours.size() );
134 
135     const double start_hour = start_hours[season];
136     const double end_hour = start_hours[( season + 1 ) % 4];
137 
138     const double into_month = static_cast<double>( day_of_season<int>( p ) ) / to_days<int>
139                               ( calendar::season_length() );
140     const double time = start_hour * ( 1.0 - into_month ) + end_hour * into_month;
141 
142     const time_point midnight = p - time_past_midnight( p );
143     return midnight + time_duration::from_minutes( static_cast<int>( time * 60 ) );
144 }
145 
night_time(const time_point & p)146 time_point night_time( const time_point &p )
147 {
148     return sunset( p ) + twilight_duration;
149 }
150 
daylight_time(const time_point & p)151 time_point daylight_time( const time_point &p )
152 {
153     // TODO: Actual daylight should start 18 degrees before sunrise
154     return sunrise( p ) + 15_minutes;
155 }
156 
is_night(const time_point & p)157 bool is_night( const time_point &p )
158 {
159     const time_duration now = time_past_midnight( p );
160     const time_duration sunrise = time_past_midnight( ::sunrise( p ) );
161     const time_duration sunset = time_past_midnight( ::sunset( p ) );
162 
163     return now >= sunset + twilight_duration || now <= sunrise;
164 }
165 
is_day(const time_point & p)166 bool is_day( const time_point &p )
167 {
168     const time_duration now = time_past_midnight( p );
169     const time_duration sunrise = time_past_midnight( ::sunrise( p ) );
170     const time_duration sunset = time_past_midnight( ::sunset( p ) );
171 
172     return now >= sunrise + twilight_duration && now <= sunset;
173 }
174 
is_dusk(const time_point & p)175 bool is_dusk( const time_point &p )
176 {
177     const time_duration now = time_past_midnight( p );
178     const time_duration sunset = time_past_midnight( ::sunset( p ) );
179 
180     return now >= sunset && now <= sunset + twilight_duration;
181 }
182 
is_dawn(const time_point & p)183 bool is_dawn( const time_point &p )
184 {
185     const time_duration now = time_past_midnight( p );
186     const time_duration sunrise = time_past_midnight( ::sunrise( p ) );
187 
188     return now >= sunrise && now <= sunrise + twilight_duration;
189 }
190 
current_daylight_level(const time_point & p)191 double current_daylight_level( const time_point &p )
192 {
193     const double percent = static_cast<double>( day_of_season<int>( p ) ) / to_days<int>
194                            ( calendar::season_length() );
195     double modifier = 1.0;
196     // For ~Boston: solstices are +/- 25% sunlight intensity from equinoxes
197     static double deviation = 0.25;
198 
199     switch( season_of_year( p ) ) {
200         case SPRING:
201             modifier = 1. + ( percent * deviation );
202             break;
203         case SUMMER:
204             modifier = ( 1. + deviation ) - ( percent * deviation );
205             break;
206         case AUTUMN:
207             modifier = 1. - ( percent * deviation );
208             break;
209         case WINTER:
210             modifier = ( 1. - deviation ) + ( percent * deviation );
211             break;
212         default:
213             debugmsg( "Invalid season" );
214     }
215 
216     return modifier * default_daylight_level();
217 }
218 
sunlight(const time_point & p,const bool vision)219 float sunlight( const time_point &p, const bool vision )
220 {
221     const time_duration now = time_past_midnight( p );
222 
223     const double daylight = current_daylight_level( p );
224 
225     int current_phase = static_cast<int>( get_moon_phase( p ) );
226     if( current_phase > static_cast<int>( MOON_PHASE_MAX ) / 2 ) {
227         current_phase = static_cast<int>( MOON_PHASE_MAX ) - current_phase;
228     }
229 
230     const double moonlight = vision ? 1. + moonlight_per_quarter * current_phase : 0.;
231 
232     if( is_night( p ) ) {
233         return moonlight;
234     } else if( is_dawn( p ) ) {
235         const time_duration sunrise = time_past_midnight( ::sunrise( p ) );
236         const double percent = ( now - sunrise ) / twilight_duration;
237         return moonlight * ( 1. - percent ) + daylight * percent;
238     } else if( is_dusk( p ) ) {
239         const time_duration sunset = time_past_midnight( ::sunset( p ) );
240         const double percent = ( now - sunset ) / twilight_duration;
241         return daylight * ( 1. - percent ) + moonlight * percent;
242     } else {
243         return daylight;
244     }
245 }
246 
to_string_clipped(const int num,const clipped_unit type,const clipped_align align)247 static std::string to_string_clipped( const int num, const clipped_unit type,
248                                       const clipped_align align )
249 {
250     switch( align ) {
251         default:
252         case clipped_align::none:
253             switch( type ) {
254                 default:
255                 case clipped_unit::forever:
256                     return _( "forever" );
257                 case clipped_unit::second:
258                     return string_format( ngettext( "%d second", "%d seconds", num ), num );
259                 case clipped_unit::minute:
260                     return string_format( ngettext( "%d minute", "%d minutes", num ), num );
261                 case clipped_unit::hour:
262                     return string_format( ngettext( "%d hour", "%d hours", num ), num );
263                 case clipped_unit::day:
264                     return string_format( ngettext( "%d day", "%d days", num ), num );
265                 case clipped_unit::week:
266                     return string_format( ngettext( "%d week", "%d weeks", num ), num );
267                 case clipped_unit::season:
268                     return string_format( ngettext( "%d season", "%d seasons", num ), num );
269                 case clipped_unit::year:
270                     return string_format( ngettext( "%d year", "%d years", num ), num );
271             }
272         case clipped_align::right:
273             switch( type ) {
274                 default:
275                 case clipped_unit::forever:
276                     //~ Right-aligned time string. should right-align with other strings with this same comment
277                     return _( "    forever" );
278                 case clipped_unit::second:
279                     //~ Right-aligned time string. should right-align with other strings with this same comment
280                     return string_format( ngettext( "%3d  second", "%3d seconds", num ), num );
281                 case clipped_unit::minute:
282                     //~ Right-aligned time string. should right-align with other strings with this same comment
283                     return string_format( ngettext( "%3d  minute", "%3d minutes", num ), num );
284                 case clipped_unit::hour:
285                     //~ Right-aligned time string. should right-align with other strings with this same comment
286                     return string_format( ngettext( "%3d    hour", "%3d   hours", num ), num );
287                 case clipped_unit::day:
288                     //~ Right-aligned time string. should right-align with other strings with this same comment
289                     return string_format( ngettext( "%3d     day", "%3d    days", num ), num );
290                 case clipped_unit::week:
291                     //~ Right-aligned time string. should right-align with other strings with this same comment
292                     return string_format( ngettext( "%3d    week", "%3d   weeks", num ), num );
293                 case clipped_unit::season:
294                     //~ Right-aligned time string. should right-align with other strings with this same comment
295                     return string_format( ngettext( "%3d  season", "%3d seasons", num ), num );
296                 case clipped_unit::year:
297                     //~ Right-aligned time string. should right-align with other strings with this same comment
298                     return string_format( ngettext( "%3d    year", "%3d   years", num ), num );
299             }
300         case clipped_align::compact:
301             switch( type ) {
302                 default:
303                 case clipped_unit::forever:
304                     return _( "forever" );
305                 case clipped_unit::second:
306                     return string_format( ngettext( "%d sec", "%d secs", num ), num );
307                 case clipped_unit::minute:
308                     return string_format( ngettext( "%d min", "%d mins", num ), num );
309                 case clipped_unit::hour:
310                     return string_format( ngettext( "%d hr", "%d hrs", num ), num );
311                 case clipped_unit::day:
312                     return string_format( ngettext( "%d day", "%d days", num ), num );
313                 case clipped_unit::week:
314                     return string_format( ngettext( "%d wk", "%d wks", num ), num );
315                 case clipped_unit::season:
316                     return string_format( ngettext( "%d seas", "%d seas", num ), num );
317                 case clipped_unit::year:
318                     return string_format( ngettext( "%d yr", "%d yrs", num ), num );
319             }
320     }
321 }
322 
clipped_time(const time_duration & d)323 std::pair<int, clipped_unit> clipped_time( const time_duration &d )
324 {
325     if( d >= calendar::INDEFINITELY_LONG_DURATION ) {
326         return { 0, clipped_unit::forever };
327     }
328 
329     if( d < 1_minutes ) {
330         const int sec = to_seconds<int>( d );
331         return { sec, clipped_unit::second };
332     } else if( d < 1_hours ) {
333         const int min = to_minutes<int>( d );
334         return { min, clipped_unit::minute };
335     } else if( d < 1_days ) {
336         const int hour = to_hours<int>( d );
337         return { hour, clipped_unit::hour };
338     } else if( d < 7_days ) {
339         const int day = to_days<int>( d );
340         return { day, clipped_unit::day };
341     } else if( d < calendar::season_length() || calendar::eternal_season() ) {
342         // eternal seasons means one season is indistinguishable from the next,
343         // therefore no way to count them
344         const int week = to_weeks<int>( d );
345         return { week, clipped_unit::week };
346     } else if( d < calendar::year_length() && !calendar::eternal_season() ) {
347         // TODO: consider a to_season function, but season length is variable, so
348         // this might be misleading
349         const int season = to_turns<int>( d ) / to_turns<int>( calendar::season_length() );
350         return { season, clipped_unit::season };
351     } else {
352         // TODO: consider a to_year function, but year length is variable, so
353         // this might be misleading
354         const int year = to_turns<int>( d ) / to_turns<int>( calendar::year_length() );
355         return { year, clipped_unit::year };
356     }
357 }
358 
to_string_clipped(const time_duration & d,const clipped_align align)359 std::string to_string_clipped( const time_duration &d,
360                                const clipped_align align )
361 {
362     const std::pair<int, clipped_unit> time = clipped_time( d );
363     return to_string_clipped( time.first, time.second, align );
364 }
365 
to_string(const time_duration & d,const bool compact)366 std::string to_string( const time_duration &d, const bool compact )
367 {
368     if( d >= calendar::INDEFINITELY_LONG_DURATION ) {
369         return _( "forever" );
370     }
371 
372     if( d <= 1_minutes ) {
373         return to_string_clipped( d );
374     }
375 
376     time_duration divider = 0_turns;
377     if( d < 1_hours ) {
378         divider = 1_minutes;
379     } else if( d < 1_days ) {
380         divider = 1_hours;
381     } else if( d < 1_weeks ) {
382         divider = 1_days;
383     } else if( d < calendar::season_length() || calendar::eternal_season() ) {
384         divider = 1_weeks;
385     } else if( d < calendar::year_length() ) {
386         divider = calendar::season_length();
387     } else {
388         divider = calendar::year_length();
389     }
390 
391     if( d % divider != 0_turns ) {
392         if( compact ) {
393             //~ %1$s - greater units of time (e.g. 3 hours), %2$s - lesser units of time (e.g. 11 minutes).
394             return string_format( pgettext( "time duration", "%1$s %2$s" ),
395                                   to_string_clipped( d, clipped_align::compact ),
396                                   to_string_clipped( d % divider, clipped_align::compact ) );
397         } else {
398             //~ %1$s - greater units of time (e.g. 3 hours), %2$s - lesser units of time (e.g. 11 minutes).
399             return string_format( _( "%1$s and %2$s" ),
400                                   to_string_clipped( d ),
401                                   to_string_clipped( d % divider ) );
402         }
403     }
404     return to_string_clipped( d );
405 }
406 
to_string_approx(const time_duration & dur,const bool verbose)407 std::string to_string_approx( const time_duration &dur, const bool verbose )
408 {
409     time_duration d = dur;
410     const auto make_result = [verbose]( const time_duration & d, const char *verbose_str,
411     const char *short_str ) {
412         return string_format( verbose ? verbose_str : short_str, to_string_clipped( d ) );
413     };
414 
415     time_duration divider = 0_turns;
416     time_duration vicinity = 0_turns;
417 
418     // Minutes and seconds can be estimated precisely.
419     if( d > 1_days ) {
420         divider = 1_days;
421         vicinity = 2_hours;
422     } else if( d > 1_hours ) {
423         divider = 1_hours;
424         vicinity = 5_minutes;
425     }
426 
427     if( divider != 0_turns ) {
428         const time_duration remainder = d % divider;
429 
430         if( remainder >= divider - vicinity ) {
431             d += divider;
432         } else if( remainder > vicinity ) {
433             if( remainder < divider / 2 ) {
434                 //~ %s - time (e.g. 2 hours).
435                 return make_result( d, _( "more than %s" ), ">%s" );
436             } else {
437                 //~ %s - time (e.g. 2 hours).
438                 return make_result( d + divider, _( "less than %s" ), "<%s" );
439             }
440         }
441     }
442     //~ %s - time (e.g. 2 hours).
443     return make_result( d, _( "about %s" ), "%s" );
444 }
445 
to_string_writable(const time_duration & dur)446 std::string to_string_writable( const time_duration &dur )
447 {
448     if( dur % 1_days == 0_seconds ) {
449         return string_format( "%d d", static_cast<int>( dur / 1_days ) );
450     } else if( dur % 1_hours == 0_seconds ) {
451         return string_format( "%d h", static_cast<int>( dur / 1_hours ) );
452     } else if( dur % 1_minutes == 0_seconds ) {
453         return string_format( "%d m", static_cast<int>( dur / 1_minutes ) );
454     } else {
455         return string_format( "%d s", static_cast<int>( dur / 1_seconds ) );
456     }
457 }
458 
to_string_time_of_day(const time_point & p)459 std::string to_string_time_of_day( const time_point &p )
460 {
461     const int hour = hour_of_day<int>( p );
462     const int minute = minute_of_hour<int>( p );
463     const int second = ( to_seconds<int>( time_past_midnight( p ) ) ) % 60;
464     const std::string format_type = get_option<std::string>( "24_HOUR" );
465 
466     if( format_type == "military" ) {
467         return string_format( "%02d%02d.%02d", hour, minute, second );
468     } else if( format_type == "24h" ) {
469         //~ hour:minute (24hr time display)
470         return string_format( _( "%02d:%02d:%02d" ), hour, minute, second );
471     } else {
472         int hour_param = hour % 12;
473         if( hour_param == 0 ) {
474             hour_param = 12;
475         }
476         // Padding is removed as necessary to prevent clipping with SAFE notification in wide sidebar mode
477         const std::string padding = hour_param < 10 ? " " : "";
478         if( hour < 12 ) {
479             return string_format( _( "%d:%02d:%02d%sAM" ), hour_param, minute, second, padding );
480         } else {
481             return string_format( _( "%d:%02d:%02d%sPM" ), hour_param, minute, second, padding );
482         }
483     }
484 }
485 
day_of_week(const time_point & p)486 weekdays day_of_week( const time_point &p )
487 {
488     /* Design rationale:
489      * <kevingranade> here's a question
490      * <kevingranade> what day of the week is day 0?
491      * <wito> Sunday
492      * <GlyphGryph> Why does it matter?
493      * <GlyphGryph> For like where people are and stuff?
494      * <wito> 7 is also Sunday
495      * <kevingranade> NOAA weather forecasts include day of week
496      * <GlyphGryph> Also by day0 do you mean the day people start day 0
497      * <GlyphGryph> Or actual day 0
498      * <kevingranade> good point, turn 0
499      * <GlyphGryph> So day 5
500      * <wito> Oh, I thought we were talking about week day numbering in general.
501      * <wito> Day 5 is a thursday, I think.
502      * <wito> Nah, Day 5 feels like a thursday. :P
503      * <wito> Which would put the apocalypse on a saturday?
504      * <Starfyre> must be a thursday.  I was never able to get the hang of those.
505      * <ZChris13> wito: seems about right to me
506      * <wito> kevingranade: add four for thursday. ;)
507      * <kevingranade> sounds like consensus to me
508      * <kevingranade> Thursday it is */
509     const int day_since_cataclysm = to_days<int>( p - calendar::turn_zero );
510     static const weekdays start_day = weekdays::THURSDAY;
511     const int result = day_since_cataclysm + static_cast<int>( start_day );
512     return static_cast<weekdays>( result % 7 );
513 }
514 
eternal_season()515 bool calendar::eternal_season()
516 {
517     return is_eternal_season;
518 }
519 
year_length()520 time_duration calendar::year_length()
521 {
522     return season_length() * 4;
523 }
524 
season_length()525 time_duration calendar::season_length()
526 {
527     return time_duration::from_days( std::max( cur_season_length, 1 ) );
528 }
set_eternal_season(bool is_eternal)529 void calendar::set_eternal_season( bool is_eternal )
530 {
531     is_eternal_season = is_eternal;
532 }
set_season_length(const int dur)533 void calendar::set_season_length( const int dur )
534 {
535     cur_season_length = dur;
536 }
537 
538 static constexpr int real_world_season_length = 91;
539 static constexpr int default_season_length = real_world_season_length;
540 
season_ratio()541 float calendar::season_ratio()
542 {
543     return to_days<float>( season_length() ) / real_world_season_length;
544 }
545 
season_from_default_ratio()546 float calendar::season_from_default_ratio()
547 {
548     return to_days<float>( season_length() ) / default_season_length;
549 }
550 
once_every(const time_duration & event_frequency)551 bool calendar::once_every( const time_duration &event_frequency )
552 {
553     return ( calendar::turn - calendar::turn_zero ) % event_frequency == 0_turns;
554 }
555 
name_season(season_type s)556 std::string calendar::name_season( season_type s )
557 {
558     static const std::array<std::string, 5> season_names_untranslated = {{
559             //~First letter is supposed to be uppercase
560             std::string( translate_marker( "Spring" ) ),
561             //~First letter is supposed to be uppercase
562             std::string( translate_marker( "Summer" ) ),
563             //~First letter is supposed to be uppercase
564             std::string( translate_marker( "Autumn" ) ),
565             //~First letter is supposed to be uppercase
566             std::string( translate_marker( "Winter" ) ),
567             std::string( translate_marker( "End times" ) )
568         }
569     };
570     if( s >= SPRING && s <= WINTER ) {
571         return _( season_names_untranslated[ s ] );
572     }
573 
574     return _( season_names_untranslated[ 4 ] );
575 }
576 
rng(time_duration lo,time_duration hi)577 time_duration rng( time_duration lo, time_duration hi )
578 {
579     return time_duration( rng( lo.turns_, hi.turns_ ) );
580 }
581 
x_in_y(const time_duration & a,const time_duration & b)582 bool x_in_y( const time_duration &a, const time_duration &b )
583 {
584     return ::x_in_y( to_turns<int>( a ), to_turns<int>( b ) );
585 }
586 
587 const std::vector<std::pair<std::string, time_duration>> time_duration::units = { {
588         { "turns", 1_turns },
589         { "turn", 1_turns },
590         { "t", 1_turns },
591         { "seconds", 1_seconds },
592         { "second", 1_seconds },
593         { "s", 1_seconds },
594         { "minutes", 1_minutes },
595         { "minute", 1_minutes },
596         { "m", 1_minutes },
597         { "hours", 1_hours },
598         { "hour", 1_hours },
599         { "h", 1_hours },
600         { "days", 1_days },
601         { "day", 1_days },
602         { "d", 1_days },
603         // TODO: maybe add seasons?
604         // TODO: maybe add years? Those two things depend on season length!
605     }
606 };
607 
season_of_year(const time_point & p)608 season_type season_of_year( const time_point &p )
609 {
610     static time_point prev_turn = calendar::before_time_starts;
611     static season_type prev_season = calendar::initial_season;
612 
613     if( p != prev_turn ) {
614         prev_turn = p;
615         if( calendar::eternal_season() ) {
616             // If we use calendar::start to determine the initial season, and the user shortens the season length
617             // mid-game, the result could be the wrong season!
618             return prev_season = calendar::initial_season;
619         }
620         return prev_season = static_cast<season_type>(
621                                  to_turn<int>( p ) / to_turns<int>( calendar::season_length() ) % 4
622                              );
623     }
624 
625     return prev_season;
626 }
627 
to_string(const time_point & p)628 std::string to_string( const time_point &p )
629 {
630     const int year = to_turns<int>( p - calendar::turn_zero ) / to_turns<int>
631                      ( calendar::year_length() ) + 1;
632     const std::string time = to_string_time_of_day( p );
633     if( calendar::eternal_season() ) {
634         const int day = to_days<int>( time_past_new_year( p ) );
635         //~ 1 is the year, 2 is the day (of the *year*), 3 is the time of the day in its usual format
636         return string_format( _( "Year %1$d, day %2$d %3$s" ), year, day, time );
637     } else {
638         const int day = day_of_season<int>( p ) + 1;
639         //~ 1 is the year, 2 is the season name, 3 is the day (of the season), 4 is the time of the day in its usual format
640         return string_format( _( "Year %1$d, %2$s, day %3$d %4$s" ), year,
641                               calendar::name_season( season_of_year( p ) ), day, time );
642     }
643 }
644 
time_point()645 time_point::time_point()
646 {
647     turn_ = 0;
648 }
649