Guest User

Untitled

a guest
Feb 4th, 2025
115
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 18.35 KB | None | 0 0
  1. #include <Wire.h>
  2. #include <hd44780.h>
  3. #include <hd44780ioClass/hd44780_I2Cexp.h>
  4. #include <DS3231.h>
  5. #include "icons.h"
  6.  
  7. // LCD
  8. // const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 7, d7 = 8;
  9. // hd44780_pinIO lcd(rs, en, d4, d5, d6, d7);
  10. hd44780_I2Cexp lcd;
  11.  
  12. // LCD RGB
  13. const int red_pin = 9;
  14. const int green_pin = 10;
  15. const int blue_pin = 11;
  16.  
  17. int colour[3] = {255, 255, 255};
  18.  
  19. // RTC
  20. DS3231 rtc;
  21. int year = 2025;
  22. int month = 2;
  23. int day = 2;
  24. int hour = 22;
  25. int minute = 20;
  26. int second = 0;
  27. bool dst = false;
  28.  
  29.  
  30. // Encoder
  31. const int enc_a = 3;
  32. const int enc_b = 2;
  33. const int enc_sw = 13;
  34.  
  35. unsigned long last_inc_read_time = micros();
  36. unsigned long last_dec_read_time = micros();
  37. int pause_length = 25000;
  38.  
  39. volatile int counter = 0;
  40.  
  41. // Program
  42. bool inputs[2] = {false, false}; // States of the input buttons at each loop.
  43. bool btn_lock[2] = {false, false}; // Button lock for each of the input buttons.
  44. bool held = false;
  45.  
  46. bool timer_active = false;
  47. bool world_clock = false;
  48.  
  49. int screen = 0;
  50. bool just_entered = true;
  51.  
  52. bool stopwatch_running = false;
  53. int stopwatch_begin = 0; // Timestamp of the most recent starting of the stopwatch.
  54. int stopwatch_total = 0; // Total time on the stopwatch. Enables resuming from a pause without the pause time being a part of the total.
  55.  
  56. // Screens
  57. #define CLOCK_FACE 0
  58. #define OPTIONS_MENU 1
  59. #define TIMER_MENU 2
  60. #define STOPWATCH 3
  61. #define ALARM_MENU 4
  62. #define WORLD_CLOCK_MENU 5
  63. #define COLOUR_MENU 6
  64. #define CLOCK_SETUP 7
  65. #define DISPLAY_OFF 8
  66.  
  67. ///// END VARIABLES
  68.  
  69. void setup() {
  70.  
  71. pinMode(red_pin, OUTPUT);
  72. pinMode(green_pin, OUTPUT);
  73. pinMode(blue_pin, OUTPUT);
  74.  
  75. pinMode(enc_a, INPUT_PULLUP);
  76. pinMode(enc_b, INPUT_PULLUP);
  77. attachInterrupt(digitalPinToInterrupt(enc_a), readEncoder, CHANGE);
  78. attachInterrupt(digitalPinToInterrupt(enc_b), readEncoder, CHANGE);
  79.  
  80. pinMode(13, INPUT_PULLUP);
  81.  
  82. int status;
  83. status = lcd.begin(16, 2);
  84. if(status){ // Non-zero result indicates failure to initialise LCD
  85. hd44780::fatalError(status); // Blink status code using onboard LED
  86. }
  87.  
  88. // Setup user data e.g. their selected colour, modes, RTC time, etc.
  89. analogWrite(green_pin, 255);
  90.  
  91. rtc.setClockMode(false);
  92. getClockData();
  93.  
  94. Serial.begin(9600);
  95.  
  96. }
  97.  
  98. void loop() {
  99.  
  100. processInput();
  101.  
  102. // Each screen's function manages inputs to process within its own context, inputs that change the active screen, and its own rendering
  103. switch(screen){
  104. case CLOCK_FACE:
  105. clockFace();
  106. break;
  107. case OPTIONS_MENU:
  108. optionsMenu();
  109. break;
  110. case STOPWATCH:
  111. stopwatch();
  112. break;
  113. case COLOUR_MENU:
  114. colourMenu();
  115. break;
  116. case CLOCK_SETUP:
  117. clockSetup();
  118. break;
  119. case DISPLAY_OFF:
  120. displayOff();
  121. break;
  122. }
  123.  
  124. }
  125.  
  126. void enterScreen(int new_screen, bool expire_input){ // expire_input - index of input to expire in inputs[]
  127. screen = new_screen;
  128. just_entered = true;
  129.  
  130. if(expire_input > -1){ // Expire the input that was used to enter the new screen, so that it does not trigger any actions in the new screen
  131. inputs[expire_input] = false;
  132. }
  133.  
  134. lcd.clear(); // No leftovers from the previous screen!
  135.  
  136. }
  137.  
  138. void processInput(){
  139. // PROCESS BUTTONS
  140. // will need to be updated to support long/short pressing. Short press: action happens upon release, after short hold. Long press: action happens during hold, after a specified time has passed
  141. inputs[0] = false; inputs[1] = false;
  142.  
  143. // Reading is currently inverted - LOW is pressed, HIGH is released.
  144. if(digitalRead(enc_sw) == LOW && btn_lock[0] == false){
  145. btn_lock[0] = true;
  146. inputs[0] = true;
  147. }else if(digitalRead(enc_sw) == HIGH && btn_lock[0] == true){
  148. btn_lock[0] = false;
  149. Serial.println("Unlocked");
  150. }
  151.  
  152. // READ ENCODER ROTATION
  153. static int last_counter = 0;
  154. if(counter != last_counter){
  155. last_counter = counter;
  156. }
  157.  
  158. }
  159.  
  160. void optionsMenu(){
  161.  
  162. static int prev_selected;
  163.  
  164. if(just_entered){
  165. counter = 0; // Reset counter so that scrollable menu starts on option 0 (back)
  166. prev_selected = 1; // selected will initially be 0, therefore this will trigger an initial render of the menu
  167. just_entered = false;
  168.  
  169. // Load initial icons (the selected option will have its icon dynamically updated later)
  170. //for(int i = 0; i < 8; i++){
  171. //lcd.createChar(i, icon_lookup[i*2]);
  172. //}
  173.  
  174. lcd.createChar(0, icon_lookup[0]);
  175. lcd.createChar(1, icon_lookup[1]);
  176. }
  177.  
  178. static const char *option_names[8] = {"Back", "Timer", "Stopwatch", "Alarm", "World Clock", "Colours", "Clock Setup", "Display Off"};
  179.  
  180. int selected = counter % 8;
  181. if(selected < 0){
  182. selected = 8 - abs(selected);
  183. }
  184.  
  185. if(inputs[0] == true){
  186. switch(selected){
  187. case 0:
  188. enterScreen(CLOCK_FACE, 0);
  189. break;
  190. case 2:
  191. enterScreen(STOPWATCH, 0);
  192. break;
  193. case 5:
  194. enterScreen(COLOUR_MENU, 0);
  195. break;
  196. case 6:
  197. enterScreen(CLOCK_SETUP, 0);
  198. break;
  199. case 7:
  200. enterScreen(DISPLAY_OFF, 0);
  201. break;
  202. }
  203.  
  204. //lcd.noCursor(); // Disable cursor so it doesn't appear outside of here
  205. return;
  206. }
  207.  
  208.  
  209. if(selected != prev_selected){
  210.  
  211. //lcd.createChar(selected, icon_lookup[(selected * 2) + 1]); // Invert icon of selected option
  212. //lcd.createChar(prev_selected, icon_lookup[prev_selected * 2]); // Undo inversion of previously selected option
  213.  
  214. lcd.clear();
  215. //lcd.noCursor();
  216.  
  217. lcd.setCursor(4, 1);
  218. for(int i = 0; i < 8; i++){ // Render icon slots 0-7
  219. //lcd.write(i);
  220.  
  221. if(i == selected){
  222. lcd.write(1);
  223. }else{
  224. lcd.write(0);
  225. }
  226.  
  227. }
  228.  
  229. int leftmost_char = 16 - (strlen(option_names[selected]));
  230. lcd.setCursor(leftmost_char, 0);
  231. lcd.print(option_names[selected]);
  232.  
  233. lcd.setCursor(0, 1);
  234.  
  235. //lcd.cursor();
  236. //lcd.setCursor(selected + 4, 1); // To show the cursor at the selected option.
  237.  
  238. prev_selected = selected;
  239. }
  240. }
  241.  
  242. void colourMenu(){
  243.  
  244. static int c_menu_stage;
  245.  
  246. static int final_hue = 0;
  247. static int final_lightness = 50;
  248.  
  249. if(just_entered){
  250. counter = 0;
  251. c_menu_stage = 0;
  252. just_entered = false;
  253.  
  254. lcd.setCursor(1, 0);
  255. lcd.print("Set Colour");
  256. lcd.setCursor(1, 1);
  257. lcd.print("then press");
  258. }
  259.  
  260. if(c_menu_stage == 0){
  261. final_hue = abs(counter % 360);
  262. updateColour(final_hue, 50);
  263. }else if(c_menu_stage == 1){
  264. constrain(counter, 0, 100);
  265. //final_lightness = counter; brightness effect doesn't look very good
  266. updateColour(final_hue, final_lightness);
  267. } // Add display contrast?
  268.  
  269. analogWrite(red_pin, colour[0]);
  270. analogWrite(green_pin, colour[1]);
  271. analogWrite(blue_pin, colour[2]);
  272.  
  273. if(inputs[0] == true){
  274. if(c_menu_stage == 0){
  275. c_menu_stage = 1;
  276. counter = 50;
  277. lcd.setCursor(1, 0);
  278. lcd.print("Set Brightness");
  279. }else if(c_menu_stage == 1){
  280. // c_menu_stage = 2; For changing contrast
  281. enterScreen(OPTIONS_MENU, 0);
  282. return;
  283. }
  284. }
  285.  
  286. }
  287.  
  288. void clockSetup(){
  289.  
  290. static int s_menu_stage;
  291.  
  292. if(just_entered){
  293. s_menu_stage = 0;
  294.  
  295. counter = hour;
  296.  
  297. lcd.setCursor(1, 0);
  298. lcd.print("Set");
  299. lcd.setCursor(0, 1);
  300. lcd.print("Time");
  301.  
  302. just_entered = false;
  303. }
  304.  
  305. switch(s_menu_stage){
  306. case 0:
  307. timeSelector(&second, &minute, &hour);
  308. break;
  309. case 1:
  310. dateSelector(&day, &month, &year);
  311. Serial.print("Updated year: ");
  312. Serial.println(year);
  313. break;
  314. case 2:
  315. // DST setting menu - turn the dial to toggle between on or off (counter % 2)? This case can't be reached for now
  316. break;
  317. }
  318.  
  319. // Process inputs
  320. if(inputs[0] == true){
  321. switch(s_menu_stage){
  322. case 0:
  323. lcd.setCursor(0, 1);
  324. lcd.print("Date");
  325. counter = year;
  326. Serial.print("Setting counter to year: ");
  327. Serial.println(year);
  328. break;
  329. case 1:
  330. Serial.print("Saving year: ");
  331. Serial.println(year);
  332. setClockData(); // Save changes to RTC
  333. enterScreen(OPTIONS_MENU, 0);
  334. return;
  335. case 2:
  336. // DST choice completed - achieve it by setting the RTC time forward or back by 1h
  337. enterScreen(OPTIONS_MENU, 0);
  338. return;
  339.  
  340. }
  341.  
  342. s_menu_stage += 1;
  343. }
  344.  
  345. }
  346.  
  347. // Both this and date selector take pointers and update the values directly, and hence don't need to return anything
  348. void timeSelector(int* secondP, int* minuteP, int* hourP){
  349.  
  350. static int ts_menu_stage;
  351.  
  352. switch(ts_menu_stage){
  353. case 0:
  354. counter = constrain(counter, 0, 23);
  355. *hourP = counter;
  356. break;
  357. case 1:
  358. counter = constrain(counter, 0, 59);
  359. *minuteP = counter;
  360. break;
  361. case 2:
  362. counter = constrain(counter, 0, 59);
  363. *secondP = counter;
  364. break;
  365. }
  366.  
  367. // Rendering
  368. char time_string[9];
  369. snprintf(time_string, sizeof(time_string), "%02d:%02d:%02d", *hourP, *minuteP, *secondP); // Change snprintf to string functions (strcpy, strcat) if running out of code space
  370. lcd.setCursor(8, 0);
  371. lcd.print(time_string);
  372.  
  373. int indicator_loc = 8 + ((ts_menu_stage % 3) * 2) + (ts_menu_stage % 3);
  374. lcd.setCursor(6, 1);
  375. lcd.print(" "); // Clear existing ^^
  376. lcd.setCursor(indicator_loc, 1);
  377. lcd.print("^^");
  378.  
  379. // Process inputs
  380. if(inputs[0] == true){
  381. switch(ts_menu_stage){
  382. case 0:
  383. inputs[0] = false;
  384. counter = *minuteP;
  385. break;
  386. case 1:
  387. inputs[0] = false;
  388. counter = *secondP;
  389. break;
  390. case 2:
  391. ts_menu_stage = 0;
  392. return;
  393. }
  394.  
  395. ts_menu_stage += 1;
  396. }
  397.  
  398. }
  399.  
  400. void dateSelector(int* dayP, int* monthP, int* yearP){
  401.  
  402. static int ds_menu_stage;
  403.  
  404. switch(ds_menu_stage){
  405. case 0:
  406. counter = constrain(counter, 2024, 2100);
  407. *yearP = counter;
  408. break;
  409. case 1:
  410. counter = constrain(counter, 1, 12);
  411. *monthP = counter;
  412. break;
  413. case 2:
  414. counter = day_constrain(counter, *monthP, *yearP); // Different range depending on which month is selected.
  415. *dayP = counter;
  416. break;
  417. }
  418.  
  419. // Rendering
  420. char date_string[11];
  421. snprintf(date_string, sizeof(date_string), "%02d/%02d/%d", *dayP, *monthP, *yearP);
  422. lcd.setCursor(6, 0);
  423. lcd.print(date_string);
  424.  
  425. int indicator_loc = 12 - ((ds_menu_stage % 3) * 2) - (ds_menu_stage % 3);
  426. lcd.setCursor(6, 1);
  427. lcd.print(" "); // Clear existing ^^
  428. lcd.setCursor(indicator_loc, 1);
  429. if(ds_menu_stage == 0){
  430. lcd.print("^^^^");
  431. }else if(ds_menu_stage < 3){
  432. lcd.print("^^");
  433. }
  434.  
  435. // Process inputs
  436. if(inputs[0] == true){
  437. switch(ds_menu_stage){
  438. case 0:
  439. inputs[0] = false;
  440. counter = *monthP;
  441. break;
  442. case 1:
  443. inputs[0] = false;
  444. counter = *dayP;
  445. break;
  446. case 2:
  447. ds_menu_stage = 0;
  448. return;
  449. }
  450.  
  451. ds_menu_stage += 1;
  452. }
  453.  
  454. }
  455.  
  456. void stopwatch(){
  457.  
  458. static int prev_selected = 0;
  459. static bool update_menu = false;
  460.  
  461. if(just_entered){
  462. counter = 0;
  463. prev_selected = 1;
  464. just_entered = false;
  465.  
  466. // Load icons
  467. for(int i=0; i<6; i++){
  468. lcd.createChar(i, sw_icon_lookup[i]);
  469. }
  470.  
  471. // Swap to pause icon if we are re-entering the stopwatch screen after it has been running in the background
  472. if(stopwatch_running){ // Better way of doing this that isn't just overwriting the work we did in the for loop above?
  473. lcd.createChar(2, sw_icon_lookup[6]);
  474. lcd.createChar(3, sw_icon_lookup[7]);
  475. }
  476.  
  477. lcd.createChar(6, icon_lookup[4]); // Check the lookup index later once main menu swapped over to raised icon selection indicator
  478. }
  479.  
  480. int selected = counter % 3;
  481. if(selected < 0){
  482. selected = 3 - abs(selected);
  483. }
  484.  
  485. // Process inputs
  486. if(inputs[0] == true){
  487. switch(selected){
  488. case 0: // Back button
  489. enterScreen(OPTIONS_MENU, 0);
  490. return;
  491.  
  492. case 1: // Start or pause stopwatch
  493. if(stopwatch_running){
  494. // Add runtime until now to the total
  495. stopwatch_running = false;
  496. lcd.createChar(2, sw_icon_lookup[2]); // Swap to play icon
  497. lcd.createChar(3, sw_icon_lookup[3]);
  498. }else{
  499. // Set start time to now
  500. stopwatch_running = true;
  501. lcd.createChar(2, sw_icon_lookup[6]); // Swap to pause icon
  502. lcd.createChar(3, sw_icon_lookup[7]);
  503. }
  504. break;
  505.  
  506. case 2: // Reset stopwatch
  507. stopwatch_running = false;
  508. stopwatch_total = 0; // Will this need to be a long? Intend to enable stopwatch to support up to 23:59:59 of total runtime
  509. lcd.createChar(2, sw_icon_lookup[2]); // Swap to play icon
  510. lcd.createChar(3, sw_icon_lookup[3]);
  511. break;
  512. }
  513.  
  514. update_menu = true;
  515. }
  516.  
  517. // Render stopwatch time string
  518. lcd.setCursor(4, 0);
  519. if(stopwatch_running){
  520. // total + calculated time since start
  521. lcd.print("12:34:56");
  522. }else{
  523. if(stopwatch_total == 0){
  524. lcd.print("00:00:00");
  525. }else{
  526. // stopwatch_total
  527. lcd.print("01:01:01");
  528. }
  529. }
  530.  
  531. // Render stopwatch controls menu
  532. if(selected != prev_selected || update_menu){
  533. lcd.setCursor(0, 1);
  534. for(int i=0; i<3; i++){
  535. if(i == selected){
  536. lcd.write(i*2 + 1);
  537. }else{
  538. lcd.write(i*2);
  539. }
  540. }
  541.  
  542. lcd.setCursor(11, 1);
  543. switch(selected){
  544. case 0:
  545. lcd.print(" Back");
  546. break;
  547. case 1:
  548. if(stopwatch_running){
  549. lcd.print("Pause");
  550. }else{
  551. lcd.print("Start");
  552. }
  553. break;
  554. case 2:
  555. lcd.print("Reset");
  556. break;
  557. }
  558.  
  559. prev_selected = selected;
  560. update_menu = false;
  561. }
  562. }
  563.  
  564. void clockFace(){
  565.  
  566. static bool show_colon = false;
  567. static unsigned long colon_last_millis = 0;
  568.  
  569. if(inputs[0] == true){
  570. enterScreen(OPTIONS_MENU, 0);
  571. return;
  572. }
  573.  
  574. getClockData();
  575. Serial.print("Year: ");
  576. Serial.println(year);
  577.  
  578. char time_str[6];
  579. if(show_colon){
  580. snprintf(time_str, 6, "%02d:%02d", hour, minute); // Look into snprintf to assign the hour and minute values as required
  581. }else{
  582. snprintf(time_str, 6, "%02d %02d", hour, minute);
  583. }
  584.  
  585. if(millis() - colon_last_millis > 1000){
  586. show_colon = !show_colon;
  587. colon_last_millis = millis();
  588. }
  589.  
  590. lcd.setCursor(1, 0);
  591. lcd.print(time_str);
  592.  
  593. char date_str[6];
  594. snprintf(date_str, 6, "%02d/%02d", day, month);
  595.  
  596. lcd.setCursor(10, 1);
  597. lcd.print(date_str);
  598.  
  599. if(timer_active){
  600. // Render timer, bottom left
  601. }
  602.  
  603. if(world_clock){
  604. // Render world clock, top right
  605. }else{
  606. // Render seconds progressbar, top right
  607. }
  608.  
  609. }
  610.  
  611. void displayOff(){ // What about interrupts for the button here, so the MCU can sleep while display is off?
  612.  
  613. if(just_entered){
  614. lcd.noDisplay();
  615.  
  616. analogWrite(red_pin, 0);
  617. analogWrite(green_pin, 0);
  618. analogWrite(blue_pin, 0);
  619.  
  620. just_entered = false;
  621. }
  622.  
  623. if(inputs[0] == true){
  624. enterScreen(CLOCK_FACE, 0);
  625.  
  626. lcd.display();
  627.  
  628. analogWrite(red_pin, colour[0]);
  629. analogWrite(green_pin, colour[1]);
  630. analogWrite(blue_pin, colour[2]);
  631. }
  632.  
  633. }
  634.  
  635.  
  636. void readEncoder() {
  637.  
  638. static uint8_t old_AB = 3;
  639. static int8_t encval = 0;
  640. static const int8_t enc_states[] = {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0};
  641.  
  642. old_AB <<= 2;
  643.  
  644. if(digitalRead(enc_a)) old_AB |= 0x02; // Add current state of pin A
  645. if(digitalRead(enc_b)) old_AB |= 0x01; // Add current state of pin B
  646.  
  647. encval += enc_states[(old_AB & 0x0f)];
  648.  
  649. if(encval > 3){ // Four steps forward
  650. int change_value = 1;
  651. if(micros() - last_inc_read_time < pause_length){
  652. change_value *= 3;
  653. }
  654.  
  655. last_inc_read_time = micros();
  656. counter += change_value;
  657. encval = 0;
  658. }else if(encval < -3){ // Four steps backward
  659. int change_value = -1;
  660. if(micros() - last_dec_read_time < pause_length){
  661. change_value *= 3;
  662. }
  663.  
  664. last_dec_read_time = micros();
  665. counter += change_value;
  666. encval = 0;
  667. }
  668.  
  669. }
  670.  
  671. void getClockData(){
  672. second = rtc.getSecond();
  673. minute = rtc.getMinute();
  674.  
  675. bool twelveHour, pmHour;
  676. hour = rtc.getHour(twelveHour, pmHour);
  677.  
  678. day = rtc.getDate();
  679.  
  680. bool centuryBit;
  681. month = rtc.getMonth(centuryBit);
  682.  
  683. year = rtc.getYear();
  684. }
  685.  
  686. void setClockData(){
  687. rtc.setSecond(second);
  688. rtc.setMinute(minute);
  689. rtc.setHour(hour);
  690. rtc.setDate(day);
  691. rtc.setMonth(month);
  692.  
  693. Serial.println(year);
  694. year = 2026;
  695. rtc.setYear(2026);
  696. Serial.println(rtc.getYear());
  697. }
  698.  
  699.  
  700. void updateColour(float hue, float lightness) {
  701. // Implement changing Lightness to allow for different shades. Hue = pick colour, Lightness = pick shade, Sat will always be 1
  702.  
  703. hue = hue / 360;
  704.  
  705. double r, g, b;
  706. lightness = lightness / 100.0;
  707. static const float saturation = 1;
  708.  
  709. if (saturation == 0){
  710. r = g = b = lightness; // achromatic
  711. }else{
  712. auto q = lightness < 0.5 ? lightness * (1 + saturation) : lightness + saturation - lightness * saturation;
  713. auto p = 2 * lightness - q;
  714.  
  715. r = hue2rgb(p, q, hue + 1 / 3.0);
  716. g = hue2rgb(p, q, hue);
  717. b = hue2rgb(p, q, hue - 1 / 3.0);
  718. }
  719.  
  720. uint8_t red = static_cast<uint8_t>(r * 255);
  721. uint8_t green = static_cast<uint8_t>(g * 255);
  722. uint8_t blue = static_cast<uint8_t>(b * 255);
  723.  
  724. colour[0] = red;
  725. colour[1] = green;
  726. colour[2] = blue;
  727.  
  728. /*
  729. Serial.print("Red = ");
  730. Serial.print(red);
  731. Serial.print(" Green = ");
  732. Serial.print(green);
  733. Serial.print(" Blue = ");
  734. Serial.println(blue);
  735. */
  736. }
  737.  
  738. double hue2rgb(double p, double q, double t){
  739. if (t < 0) t += 1;
  740. if (t > 1) t -= 1;
  741. if (t < 1 / 6.0) return p + (q - p) * 6 * t;
  742. if (t < 1 / 2.0) return q;
  743. if (t < 2 / 3.0) return p + (q - p) * (2 / 3.0 - t) * 6;
  744. return p;
  745. }
  746.  
  747. int day_constrain(int value, int month, int year){
  748. if(value < 1) return 1;
  749.  
  750. int month_max = 31;
  751. switch(month){
  752. case 2:
  753. month_max = 28;
  754.  
  755. if(year % 4 == 0){ // Check leap year
  756. if(year % 100 == 0){
  757. if(year % 400 == 0){
  758. month_max = 29;
  759. }
  760. }else{
  761. month_max = 29;
  762. }
  763. }
  764. break;
  765.  
  766. case 4:
  767. case 6:
  768. case 9:
  769. case 11:
  770. month_max = 30;
  771. }
  772.  
  773. if(value > month_max) return month_max;
  774. return value;
  775. }
  776.  
Add Comment
Please, Sign In to add comment